about summary refs log blame commit diff stats
path: root/src/subway_map.cpp
blob: 460532c7405c1886e9cf35a3032e3be04b729cfc (plain) (tree)
1
2
3
4
5
6
7
                       
                        

                     



                                    
                                   
 




                         
                                                                    
                                       




                                                                               




                                                                            





                                                                           
           



                                                    

































                                                                                                                  
                                    










                                                                          
   
           






                                                             
                                              
                                         
   
                             
                                 
 

                                                                   
                                           
                                                     



























































                                                                                                         


                                                  













                                                                         
                                     
              
   


                          
                                
                                           







                                                                       
                                           

                                                                      
                                           

                                                            
                       
                                                                         
                                                                    


                             

                                                             
                                        












                                                





























                                                                      





                                                                          






                                                                                




                                                             

                                                          

                                                            
 




                                                                               
#include "subway_map.h"

#include <wx/dcbuffer.h>

#include <sstream>

#include "ap_state.h"
#include "game_data.h"
#include "global.h"
#include "tracker_state.h"

constexpr int AREA_ACTUAL_SIZE = 21;
constexpr int OWL_ACTUAL_SIZE = 32;

enum class ItemDrawType {
  kNone,
  kBox,
  kOwl
};

SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
  SetBackgroundStyle(wxBG_STYLE_PAINT);

  map_image_ =
      wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG);
  if (!map_image_.IsOk()) {
    return;
  }

  owl_image_ =
      wxImage(GetAbsolutePath("assets/owl.png").c_str(), wxBITMAP_TYPE_PNG);
  if (!owl_image_.IsOk()) {
    return;
  }

  tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>(
      quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()),
                           static_cast<float>(map_image_.GetHeight())});
  for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
    tree_->add(subway_item.id);
  }

  Redraw();

  Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this);
  Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this);
}

void SubwayMap::OnConnect() {
  networks_.Clear();

  std::map<std::string, std::vector<int>> tagged;
  for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
    if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) {
      continue;
    }

    for (const std::string &tag : subway_item.tags) {
      tagged[tag].push_back(subway_item.id);
    }

    if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && subway_item.sunwarp->type != SubwaySunwarpType::kFinal) {
      std::ostringstream tag;
      tag << "sunwarp" << subway_item.sunwarp->dots;

      tagged[tag.str()].push_back(subway_item.id);
    }
  }

  for (const auto &[tag, items] : tagged) {
    // Pairwise connect all items with the same tag.
    for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end();
         tag_it1++) {
      for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end();
           tag_it2++) {
        networks_.AddLink(*tag_it1, *tag_it2);
      }
    }
  }

  checked_paintings_.clear();
}

void SubwayMap::UpdateIndicators() {
  if (AP_IsPaintingShuffle()) {
    for (const std::string &painting_id : AP_GetCheckedPaintings()) {
      if (!checked_paintings_.count(painting_id)) {
        checked_paintings_.insert(painting_id);

        if (AP_GetPaintingMapping().count(painting_id)) {
          networks_.AddLink(GD_GetSubwayItemForPainting(painting_id),
                            GD_GetSubwayItemForPainting(
                                AP_GetPaintingMapping().at(painting_id)));
        }
      }
    }
  }

  Redraw();
}

void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp,
  SubwaySunwarp to_sunwarp) {
  networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp),
                    GD_GetSubwayItemForSunwarp(to_sunwarp));
}

void SubwayMap::OnPaint(wxPaintEvent &event) {
  if (GetSize() != rendered_.GetSize()) {
    Redraw();
  }

  wxBufferedPaintDC dc(this);
  dc.DrawBitmap(rendered_, 0, 0);

  if (hovered_item_ && networks_.IsItemInNetwork(*hovered_item_)) {
    dc.SetBrush(*wxTRANSPARENT_BRUSH);

    for (const auto &[item_id1, item_id2] :
         networks_.GetNetworkGraph(*hovered_item_)) {
      const SubwayItem &item1 = GD_GetSubwayItem(item_id1);
      const SubwayItem &item2 = GD_GetSubwayItem(item_id2);

      int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_;
      int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_;

      int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_;
      int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_;

      int left = std::min(item1_x, item2_x);
      int top = std::min(item1_y, item2_y);
      int right = std::max(item1_x, item2_x);
      int bottom = std::max(item1_y, item2_y);

      int halfwidth = right - left;
      int halfheight = bottom - top;

      int ellipse_x;
      int ellipse_y;
      double start;
      double end;

      if (item1_x > item2_x) {
        ellipse_y = top;

        if (item1_y > item2_y) {
          ellipse_x = left - halfwidth;

          start = 0;
          end = 90;
        } else {
          ellipse_x = left;

          start = 90;
          end = 180;
        }
      } else {
        ellipse_y = top - halfheight;

        if (item1_y > item2_y) {
          ellipse_x = left - halfwidth;

          start = 270;
          end = 360;
        } else {
          ellipse_x = left;

          start = 180;
          end = 270;
        }
      }

      dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4));
      dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2,
                         start, end);
      dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
      dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2,
                         start, end);
    }
  }

  event.Skip();
}

void SubwayMap::OnMouseMove(wxMouseEvent &event) {
  int mouse_x = std::clamp(
      (event.GetX() - render_x_) * map_image_.GetWidth() / render_width_,
      0, map_image_.GetWidth() - 1);
  int mouse_y = std::clamp(
      (event.GetY() - render_y_) * map_image_.GetWidth() / render_width_,
      0, map_image_.GetHeight() - 1);

  std::vector<int> hovered = tree_->query(
      {static_cast<float>(mouse_x), static_cast<float>(mouse_y), 2, 2});
  std::optional<int> new_hovered_item;
  if (!hovered.empty()) {
    new_hovered_item = hovered[0];
  }

  if (new_hovered_item != hovered_item_) {
    hovered_item_ = new_hovered_item;

    Refresh();
  }

  event.Skip();
}

void SubwayMap::Redraw() {
  wxSize panel_size = GetSize();
  wxSize image_size = map_image_.GetSize();

  render_x_ = 0;
  render_y_ = 0;
  render_width_ = panel_size.GetWidth();
  render_height_ = panel_size.GetHeight();

  if (image_size.GetWidth() * panel_size.GetHeight() >
      panel_size.GetWidth() * image_size.GetHeight()) {
    render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) /
                     image_size.GetWidth();
    render_y_ = (panel_size.GetHeight() - render_height_) / 2;
  } else {
    render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) /
                    image_size.GetHeight();
    render_x_ = (panel_size.GetWidth() - render_width_) / 2;
  }

  rendered_ = wxBitmap(
      map_image_
          .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR)
          .Size(panel_size, {render_x_, render_y_}, 255, 255, 255));

  wxMemoryDC dc;
  dc.SelectObject(rendered_);

  for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
    ItemDrawType draw_type = ItemDrawType::kNone;
    const wxBrush *brush_color = wxGREY_BRUSH;
    std::optional<wxColour> shade_color;

    if (subway_item.door) {
      draw_type = ItemDrawType::kBox;

      if (IsDoorOpen(*subway_item.door)) {
        if (!subway_item.paintings.empty()) {
          draw_type = ItemDrawType::kOwl;
        } else {
          brush_color = wxGREEN_BRUSH;
        }
      } else {
        brush_color = wxRED_BRUSH;
      }
    } else if (!subway_item.paintings.empty()) {
      if (AP_IsPaintingShuffle()) {
        bool has_checked_painting = false;
        bool has_unchecked_painting = false;
        bool has_mapped_painting = false;

        for (const std::string &painting_id : subway_item.paintings) {
          if (checked_paintings_.count(painting_id)) {
            has_checked_painting = true;

            if (AP_GetPaintingMapping().count(painting_id)) {
              has_mapped_painting = true;
            }
          } else {
            has_unchecked_painting = true;
          }
        }

        if (has_unchecked_painting || has_mapped_painting) {
          draw_type = ItemDrawType::kOwl;

          if (has_unchecked_painting) {
            if (has_checked_painting) {
              shade_color = wxColour(255, 255, 0, 100);
            } else {
              shade_color = wxColour(100, 100, 100, 100);
            }
          }
        }
      } else if (!subway_item.tags.empty()) {
        draw_type = ItemDrawType::kOwl;
      }
    }

    int real_area_x =
        render_x_ + subway_item.x * render_width_ / image_size.GetWidth();
    int real_area_y =
        render_y_ + subway_item.y * render_width_ / image_size.GetWidth();

    int real_area_size =
        render_width_ *
        (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE) /
        image_size.GetWidth();
    if (real_area_size == 0) {
      real_area_size = 1;
    }

    if (draw_type == ItemDrawType::kBox) {
      dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1));
      dc.SetBrush(*brush_color);
      dc.DrawRectangle({real_area_x, real_area_y},
                       {real_area_size, real_area_size});
    } else if (draw_type == ItemDrawType::kOwl) {
      wxBitmap owl_bitmap = wxBitmap(
          owl_image_.Scale(real_area_size, real_area_size,
                           wxIMAGE_QUALITY_BILINEAR));
      dc.DrawBitmap(owl_bitmap, {real_area_x, real_area_y});
    }
  }
}

quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int& id) const {
  const SubwayItem &subway_item = GD_GetSubwayItem(id);
  return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y),
          AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE};
}