about summary refs log tree commit diff stats
path: root/data/maps/the_congruent/rooms
ModeNameSize
-rw-r--r--C Keyholder.txtpb174log stats plain blame
-rw-r--r--C2 Room.txtpb101log stats plain blame
-rw-r--r--Flipped Magenta Room.txtpb1160log stats plain blame
-rw-r--r--Flipped Yellow Room.txtpb934log stats plain blame
-rw-r--r--G Keyholder.txtpb175log stats plain blame
-rw-r--r--G2 Room.txtpb100log stats plain blame
-rw-r--r--Main Area.txtpb1778log stats plain blame
-rw-r--r--Obverse Magenta Room.txtpb374log stats plain blame
-rw-r--r--Obverse Yellow Room.txtpb250log stats plain blame
-rw-r--r--T Keyholder.txtpb101log stats plain blame
href='#n273'>273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
#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};
}