#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};
}