diff options
Diffstat (limited to 'src/subway_map.cpp')
| -rw-r--r-- | src/subway_map.cpp | 842 |
1 files changed, 677 insertions, 165 deletions
| diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 6070fd5..55ac411 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp | |||
| @@ -1,22 +1,36 @@ | |||
| 1 | #include "subway_map.h" | 1 | #include "subway_map.h" |
| 2 | 2 | ||
| 3 | #include <fmt/core.h> | ||
| 3 | #include <wx/dcbuffer.h> | 4 | #include <wx/dcbuffer.h> |
| 5 | #include <wx/dcgraph.h> | ||
| 4 | 6 | ||
| 5 | #include <sstream> | 7 | #include <sstream> |
| 6 | 8 | ||
| 7 | #include "ap_state.h" | 9 | #include "ap_state.h" |
| 8 | #include "game_data.h" | 10 | #include "game_data.h" |
| 9 | #include "global.h" | 11 | #include "global.h" |
| 12 | #include "report_popup.h" | ||
| 10 | #include "tracker_state.h" | 13 | #include "tracker_state.h" |
| 11 | 14 | ||
| 12 | constexpr int AREA_ACTUAL_SIZE = 21; | 15 | constexpr int AREA_ACTUAL_SIZE = 21; |
| 13 | constexpr int OWL_ACTUAL_SIZE = 32; | 16 | constexpr int OWL_ACTUAL_SIZE = 32; |
| 17 | constexpr int PAINTING_RADIUS = 9; // the actual circles on the map are radius 11 | ||
| 18 | constexpr int PAINTING_EXIT_RADIUS = 6; | ||
| 14 | 19 | ||
| 15 | enum class ItemDrawType { | 20 | enum class ItemDrawType { kNone, kBox, kOwl, kOwlExit }; |
| 16 | kNone, | 21 | |
| 17 | kBox, | 22 | namespace { |
| 18 | kOwl | 23 | |
| 19 | }; | 24 | wxPoint GetSubwayItemMapCenter(const SubwayItem &subway_item) { |
| 25 | if (subway_item.painting) { | ||
| 26 | return {subway_item.x, subway_item.y}; | ||
| 27 | } else { | ||
| 28 | return {subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
| 29 | subway_item.y + AREA_ACTUAL_SIZE / 2}; | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | } // namespace | ||
| 20 | 34 | ||
| 21 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | 35 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { |
| 22 | SetBackgroundStyle(wxBG_STYLE_PAINT); | 36 | SetBackgroundStyle(wxBG_STYLE_PAINT); |
| @@ -42,28 +56,109 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | |||
| 42 | 56 | ||
| 43 | Redraw(); | 57 | Redraw(); |
| 44 | 58 | ||
| 59 | scroll_timer_ = new wxTimer(this); | ||
| 60 | |||
| 45 | Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); | 61 | Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); |
| 46 | Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); | 62 | Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); |
| 63 | Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); | ||
| 64 | Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); | ||
| 65 | Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); | ||
| 66 | Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); | ||
| 67 | |||
| 68 | zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, FromDIP(wxPoint{15, 15})); | ||
| 69 | zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); | ||
| 70 | |||
| 71 | help_button_ = new wxButton(this, wxID_ANY, "Help"); | ||
| 72 | help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); | ||
| 73 | SetUpHelpButton(); | ||
| 74 | |||
| 75 | report_popup_ = new ReportPopup(this); | ||
| 47 | } | 76 | } |
| 48 | 77 | ||
| 49 | void SubwayMap::OnConnect() { | 78 | void SubwayMap::OnConnect() { |
| 50 | networks_.Clear(); | 79 | networks_.Clear(); |
| 51 | 80 | ||
| 52 | std::map<std::string, std::vector<int>> tagged; | 81 | std::map<std::string, std::vector<int>> tagged; |
| 82 | std::map<std::string, std::vector<int>> entrances; | ||
| 83 | std::map<std::string, std::vector<int>> exits; | ||
| 53 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | 84 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { |
| 54 | if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { | 85 | if (AP_HasEarlyColorHallways() && |
| 86 | subway_item.special == "early_color_hallways") { | ||
| 87 | entrances["early_ch"].push_back(subway_item.id); | ||
| 88 | } | ||
| 89 | |||
| 90 | if (AP_IsPaintingShuffle() && subway_item.painting) { | ||
| 55 | continue; | 91 | continue; |
| 56 | } | 92 | } |
| 57 | 93 | ||
| 58 | for (const std::string &tag : subway_item.tags) { | 94 | for (const std::string &tag : subway_item.tags) { |
| 59 | tagged[tag].push_back(subway_item.id); | 95 | tagged[tag].push_back(subway_item.id); |
| 60 | } | 96 | } |
| 97 | for (const std::string &tag : subway_item.entrances) { | ||
| 98 | entrances[tag].push_back(subway_item.id); | ||
| 99 | } | ||
| 100 | for (const std::string &tag : subway_item.exits) { | ||
| 101 | exits[tag].push_back(subway_item.id); | ||
| 102 | } | ||
| 61 | 103 | ||
| 62 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | 104 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp) { |
| 63 | std::ostringstream tag; | 105 | std::string tag = fmt::format("sunwarp{}", subway_item.sunwarp->dots); |
| 64 | tag << "sunwarp" << subway_item.sunwarp->dots; | 106 | switch (subway_item.sunwarp->type) { |
| 107 | case SubwaySunwarpType::kEnter: | ||
| 108 | entrances[tag].push_back(subway_item.id); | ||
| 109 | break; | ||
| 110 | case SubwaySunwarpType::kExit: | ||
| 111 | exits[tag].push_back(subway_item.id); | ||
| 112 | break; | ||
| 113 | default: | ||
| 114 | break; | ||
| 115 | } | ||
| 116 | } | ||
| 65 | 117 | ||
| 66 | tagged[tag.str()].push_back(subway_item.id); | 118 | if (!AP_IsPilgrimageEnabled()) { |
| 119 | if (subway_item.special == "sun_painting") { | ||
| 120 | entrances["sun_painting"].push_back(subway_item.id); | ||
| 121 | } else if (subway_item.special == "sun_painting_exit") { | ||
| 122 | exits["sun_painting"].push_back(subway_item.id); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | if (AP_IsSunwarpShuffle()) { | ||
| 128 | sunwarp_mapping_ = AP_GetSunwarpMapping(); | ||
| 129 | |||
| 130 | SubwaySunwarp final_sunwarp{.dots = 6, .type = SubwaySunwarpType::kFinal}; | ||
| 131 | int final_sunwarp_item = GD_GetSubwayItemForSunwarp(final_sunwarp); | ||
| 132 | |||
| 133 | for (const auto &[index, mapping] : sunwarp_mapping_) { | ||
| 134 | std::string tag = fmt::format("sunwarp{}", mapping.dots); | ||
| 135 | |||
| 136 | SubwaySunwarp fromWarp; | ||
| 137 | if (index < 6) { | ||
| 138 | fromWarp.dots = index + 1; | ||
| 139 | fromWarp.type = SubwaySunwarpType::kEnter; | ||
| 140 | } else { | ||
| 141 | fromWarp.dots = index - 5; | ||
| 142 | fromWarp.type = SubwaySunwarpType::kExit; | ||
| 143 | } | ||
| 144 | |||
| 145 | SubwaySunwarp toWarp; | ||
| 146 | if (mapping.exit_index < 6) { | ||
| 147 | toWarp.dots = mapping.exit_index + 1; | ||
| 148 | toWarp.type = SubwaySunwarpType::kEnter; | ||
| 149 | } else { | ||
| 150 | toWarp.dots = mapping.exit_index - 5; | ||
| 151 | toWarp.type = SubwaySunwarpType::kExit; | ||
| 152 | } | ||
| 153 | |||
| 154 | entrances[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); | ||
| 155 | exits[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); | ||
| 156 | |||
| 157 | networks_.AddLinkToNetwork( | ||
| 158 | final_sunwarp_item, GD_GetSubwayItemForSunwarp(fromWarp), | ||
| 159 | mapping.dots == 6 ? final_sunwarp_item | ||
| 160 | : GD_GetSubwayItemForSunwarp(toWarp), | ||
| 161 | false); | ||
| 67 | } | 162 | } |
| 68 | } | 163 | } |
| 69 | 164 | ||
| @@ -73,115 +168,243 @@ void SubwayMap::OnConnect() { | |||
| 73 | tag_it1++) { | 168 | tag_it1++) { |
| 74 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); | 169 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); |
| 75 | tag_it2++) { | 170 | tag_it2++) { |
| 76 | networks_.AddLink(*tag_it1, *tag_it2); | 171 | // two links because tags are bi-directional |
| 172 | networks_.AddLink(*tag_it1, *tag_it2, true); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | for (const auto &[tag, items] : entrances) { | ||
| 178 | if (!exits.contains(tag)) continue; | ||
| 179 | for (auto exit : exits[tag]) { | ||
| 180 | for (auto entrance : items) { | ||
| 181 | networks_.AddLink(entrance, exit, false); | ||
| 77 | } | 182 | } |
| 78 | } | 183 | } |
| 79 | } | 184 | } |
| 80 | 185 | ||
| 81 | checked_paintings_.clear(); | 186 | checked_paintings_.clear(); |
| 187 | |||
| 188 | UpdateIndicators(); | ||
| 82 | } | 189 | } |
| 83 | 190 | ||
| 84 | void SubwayMap::UpdateIndicators() { | 191 | void SubwayMap::UpdateIndicators() { |
| 192 | if (AP_IsSunwarpShuffle()) { | ||
| 193 | sunwarp_mapping_ = AP_GetSunwarpMapping(); | ||
| 194 | } | ||
| 195 | |||
| 85 | if (AP_IsPaintingShuffle()) { | 196 | if (AP_IsPaintingShuffle()) { |
| 86 | for (const std::string &painting_id : AP_GetCheckedPaintings()) { | 197 | std::map<std::string, std::string> painting_mapping = |
| 198 | AP_GetPaintingMapping(); | ||
| 199 | std::set<std::string> remote_checked_paintings = AP_GetCheckedPaintings(); | ||
| 200 | |||
| 201 | for (const std::string &painting_id : remote_checked_paintings) { | ||
| 87 | if (!checked_paintings_.count(painting_id)) { | 202 | if (!checked_paintings_.count(painting_id)) { |
| 88 | checked_paintings_.insert(painting_id); | 203 | checked_paintings_.insert(painting_id); |
| 89 | 204 | ||
| 90 | if (AP_GetPaintingMapping().count(painting_id)) { | 205 | if (painting_mapping.count(painting_id)) { |
| 91 | networks_.AddLink(GD_GetSubwayItemForPainting(painting_id), | 206 | std::optional<int> from_id = GD_GetSubwayItemForPainting(painting_id); |
| 92 | GD_GetSubwayItemForPainting( | 207 | std::optional<int> to_id = GD_GetSubwayItemForPainting(painting_mapping.at(painting_id)); |
| 93 | AP_GetPaintingMapping().at(painting_id))); | 208 | |
| 209 | if (from_id && to_id) { | ||
| 210 | networks_.AddLink(*from_id, *to_id, false); | ||
| 211 | } | ||
| 94 | } | 212 | } |
| 95 | } | 213 | } |
| 96 | } | 214 | } |
| 97 | } | 215 | } |
| 98 | 216 | ||
| 217 | report_popup_->UpdateIndicators(); | ||
| 218 | |||
| 99 | Redraw(); | 219 | Redraw(); |
| 100 | } | 220 | } |
| 101 | 221 | ||
| 102 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | 222 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, |
| 103 | SubwaySunwarp to_sunwarp) { | 223 | SubwaySunwarp to_sunwarp) { |
| 104 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), | 224 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), |
| 105 | GD_GetSubwayItemForSunwarp(to_sunwarp)); | 225 | GD_GetSubwayItemForSunwarp(to_sunwarp), false); |
| 226 | } | ||
| 227 | |||
| 228 | void SubwayMap::Zoom(bool in) { | ||
| 229 | wxPoint focus_point; | ||
| 230 | |||
| 231 | if (mouse_position_) { | ||
| 232 | focus_point = *mouse_position_; | ||
| 233 | } else { | ||
| 234 | focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}; | ||
| 235 | } | ||
| 236 | |||
| 237 | if (in) { | ||
| 238 | if (zoom_ < 3.0) { | ||
| 239 | SetZoom(zoom_ + 0.25, focus_point); | ||
| 240 | } | ||
| 241 | } else { | ||
| 242 | if (zoom_ > 1.0) { | ||
| 243 | SetZoom(zoom_ - 0.25, focus_point); | ||
| 244 | } | ||
| 245 | } | ||
| 106 | } | 246 | } |
| 107 | 247 | ||
| 108 | void SubwayMap::OnPaint(wxPaintEvent &event) { | 248 | void SubwayMap::OnPaint(wxPaintEvent &event) { |
| 109 | if (GetSize() != rendered_.GetSize()) { | 249 | if (GetSize() != rendered_.GetSize()) { |
| 110 | Redraw(); | 250 | wxSize panel_size = GetSize(); |
| 111 | } | 251 | wxSize image_size = map_image_.GetSize(); |
| 252 | |||
| 253 | render_x_ = 0; | ||
| 254 | render_y_ = 0; | ||
| 255 | render_width_ = panel_size.GetWidth(); | ||
| 256 | render_height_ = panel_size.GetHeight(); | ||
| 257 | |||
| 258 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
| 259 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
| 260 | render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
| 261 | image_size.GetWidth(); | ||
| 262 | render_y_ = (panel_size.GetHeight() - render_height_) / 2; | ||
| 263 | } else { | ||
| 264 | render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
| 265 | image_size.GetHeight(); | ||
| 266 | render_x_ = (panel_size.GetWidth() - render_width_) / 2; | ||
| 267 | } | ||
| 112 | 268 | ||
| 113 | wxBufferedPaintDC dc(this); | 269 | SetZoomPos({zoom_x_, zoom_y_}); |
| 114 | dc.DrawBitmap(rendered_, 0, 0); | ||
| 115 | 270 | ||
| 116 | if (hovered_item_ && networks_.IsItemInNetwork(*hovered_item_)) { | 271 | SetUpHelpButton(); |
| 117 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
| 118 | 272 | ||
| 119 | for (const auto &[item_id1, item_id2] : | 273 | zoom_slider_->SetSize(FromDIP(15), FromDIP(15), wxDefaultCoord, |
| 120 | networks_.GetNetworkGraph(*hovered_item_)) { | 274 | wxDefaultCoord, wxSIZE_AUTO); |
| 121 | const SubwayItem &item1 = GD_GetSubwayItem(item_id1); | 275 | } |
| 122 | const SubwayItem &item2 = GD_GetSubwayItem(item_id2); | ||
| 123 | 276 | ||
| 124 | int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; | 277 | wxBufferedPaintDC dc(this); |
| 125 | int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; | 278 | dc.SetBackground(*wxWHITE_BRUSH); |
| 279 | dc.Clear(); | ||
| 280 | |||
| 281 | { | ||
| 282 | wxMemoryDC rendered_dc; | ||
| 283 | rendered_dc.SelectObject(rendered_); | ||
| 284 | |||
| 285 | int dst_x; | ||
| 286 | int dst_y; | ||
| 287 | int dst_w; | ||
| 288 | int dst_h; | ||
| 289 | int src_x; | ||
| 290 | int src_y; | ||
| 291 | int src_w; | ||
| 292 | int src_h; | ||
| 293 | |||
| 294 | int zoomed_width = render_width_ * zoom_; | ||
| 295 | int zoomed_height = render_height_ * zoom_; | ||
| 296 | |||
| 297 | if (zoomed_width <= GetSize().GetWidth()) { | ||
| 298 | dst_x = (GetSize().GetWidth() - zoomed_width) / 2; | ||
| 299 | dst_w = zoomed_width; | ||
| 300 | src_x = 0; | ||
| 301 | src_w = map_image_.GetWidth(); | ||
| 302 | } else { | ||
| 303 | dst_x = 0; | ||
| 304 | dst_w = GetSize().GetWidth(); | ||
| 305 | src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 306 | src_w = | ||
| 307 | GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 308 | } | ||
| 126 | 309 | ||
| 127 | int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; | 310 | if (zoomed_height <= GetSize().GetHeight()) { |
| 128 | int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; | 311 | dst_y = (GetSize().GetHeight() - zoomed_height) / 2; |
| 312 | dst_h = zoomed_height; | ||
| 313 | src_y = 0; | ||
| 314 | src_h = map_image_.GetHeight(); | ||
| 315 | } else { | ||
| 316 | dst_y = 0; | ||
| 317 | dst_h = GetSize().GetHeight(); | ||
| 318 | src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 319 | src_h = | ||
| 320 | GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 321 | } | ||
| 129 | 322 | ||
| 130 | int left = std::min(item1_x, item2_x); | 323 | wxGCDC gcdc(dc); |
| 131 | int top = std::min(item1_y, item2_y); | 324 | gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD); |
| 132 | int right = std::max(item1_x, item2_x); | 325 | gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, |
| 133 | int bottom = std::max(item1_y, item2_y); | 326 | src_w, src_h); |
| 327 | } | ||
| 134 | 328 | ||
| 135 | int halfwidth = right - left; | 329 | if (hovered_item_) { |
| 136 | int halfheight = bottom - top; | 330 | if (networks_.IsItemInNetwork(*hovered_item_)) { |
| 331 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
| 332 | |||
| 333 | for (const auto node : networks_.GetNetworkGraph(*hovered_item_)) { | ||
| 334 | const SubwayItem &item1 = GD_GetSubwayItem(node.entry); | ||
| 335 | const SubwayItem &item2 = GD_GetSubwayItem(node.exit); | ||
| 336 | |||
| 337 | wxPoint item1_pos = MapPosToRenderPos(GetSubwayItemMapCenter(item1)); | ||
| 338 | wxPoint item2_pos = MapPosToRenderPos(GetSubwayItemMapCenter(item2)); | ||
| 339 | |||
| 340 | int left = std::min(item1_pos.x, item2_pos.x); | ||
| 341 | int top = std::min(item1_pos.y, item2_pos.y); | ||
| 342 | int right = std::max(item1_pos.x, item2_pos.x); | ||
| 343 | int bottom = std::max(item1_pos.y, item2_pos.y); | ||
| 344 | |||
| 345 | int halfwidth = right - left; | ||
| 346 | int halfheight = bottom - top; | ||
| 347 | |||
| 348 | if (halfwidth < 4 || halfheight < 4) { | ||
| 349 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
| 350 | dc.DrawLine(item1_pos, item2_pos); | ||
| 351 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
| 352 | dc.DrawLine(item1_pos, item2_pos); | ||
| 353 | if (!node.two_way) { | ||
| 354 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2)); | ||
| 355 | dc.SetBrush(*wxCYAN_BRUSH); | ||
| 356 | dc.DrawCircle(item2_pos, 4); | ||
| 357 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
| 358 | } | ||
| 359 | } else { | ||
| 360 | int ellipse_x; | ||
| 361 | int ellipse_y; | ||
| 362 | double start; | ||
| 363 | double end; | ||
| 137 | 364 | ||
| 138 | if (halfwidth < 4 || halfheight < 4) { | 365 | if (item1_pos.x > item2_pos.x) { |
| 139 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | 366 | ellipse_y = top; |
| 140 | dc.DrawLine(item1_x, item1_y, item2_x, item2_y); | ||
| 141 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
| 142 | dc.DrawLine(item1_x, item1_y, item2_x, item2_y); | ||
| 143 | } else { | ||
| 144 | int ellipse_x; | ||
| 145 | int ellipse_y; | ||
| 146 | double start; | ||
| 147 | double end; | ||
| 148 | 367 | ||
| 149 | if (item1_x > item2_x) { | 368 | if (item1_pos.y > item2_pos.y) { |
| 150 | ellipse_y = top; | 369 | ellipse_x = left - halfwidth; |
| 151 | 370 | ||
| 152 | if (item1_y > item2_y) { | 371 | start = 0; |
| 153 | ellipse_x = left - halfwidth; | 372 | end = 90; |
| 373 | } else { | ||
| 374 | ellipse_x = left; | ||
| 154 | 375 | ||
| 155 | start = 0; | 376 | start = 90; |
| 156 | end = 90; | 377 | end = 180; |
| 378 | } | ||
| 157 | } else { | 379 | } else { |
| 158 | ellipse_x = left; | 380 | ellipse_y = top - halfheight; |
| 159 | 381 | ||
| 160 | start = 90; | 382 | if (item1_pos.y > item2_pos.y) { |
| 161 | end = 180; | 383 | ellipse_x = left - halfwidth; |
| 162 | } | ||
| 163 | } else { | ||
| 164 | ellipse_y = top - halfheight; | ||
| 165 | 384 | ||
| 166 | if (item1_y > item2_y) { | 385 | start = 270; |
| 167 | ellipse_x = left - halfwidth; | 386 | end = 360; |
| 387 | } else { | ||
| 388 | ellipse_x = left; | ||
| 168 | 389 | ||
| 169 | start = 270; | 390 | start = 180; |
| 170 | end = 360; | 391 | end = 270; |
| 171 | } else { | 392 | } |
| 172 | ellipse_x = left; | 393 | } |
| 173 | 394 | ||
| 174 | start = 180; | 395 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); |
| 175 | end = 270; | 396 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, |
| 397 | halfheight * 2, start, end); | ||
| 398 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
| 399 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
| 400 | halfheight * 2, start, end); | ||
| 401 | if (!node.two_way) { | ||
| 402 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2)); | ||
| 403 | dc.SetBrush(*wxCYAN_BRUSH); | ||
| 404 | dc.DrawCircle(item2_pos, 4); | ||
| 405 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
| 176 | } | 406 | } |
| 177 | } | 407 | } |
| 178 | |||
| 179 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
| 180 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, | ||
| 181 | start, end); | ||
| 182 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
| 183 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, | ||
| 184 | start, end); | ||
| 185 | } | 408 | } |
| 186 | } | 409 | } |
| 187 | } | 410 | } |
| @@ -190,137 +413,426 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
| 190 | } | 413 | } |
| 191 | 414 | ||
| 192 | void SubwayMap::OnMouseMove(wxMouseEvent &event) { | 415 | void SubwayMap::OnMouseMove(wxMouseEvent &event) { |
| 193 | int mouse_x = std::clamp( | 416 | wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); |
| 194 | (event.GetX() - render_x_) * map_image_.GetWidth() / render_width_, | ||
| 195 | 0, map_image_.GetWidth() - 1); | ||
| 196 | int mouse_y = std::clamp( | ||
| 197 | (event.GetY() - render_y_) * map_image_.GetWidth() / render_width_, | ||
| 198 | 0, map_image_.GetHeight() - 1); | ||
| 199 | 417 | ||
| 200 | std::vector<int> hovered = tree_->query( | 418 | std::vector<int> hovered = tree_->query( |
| 201 | {static_cast<float>(mouse_x), static_cast<float>(mouse_y), 2, 2}); | 419 | {static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y), 2, 2}); |
| 202 | std::optional<int> new_hovered_item; | ||
| 203 | if (!hovered.empty()) { | 420 | if (!hovered.empty()) { |
| 204 | new_hovered_item = hovered[0]; | 421 | actual_hover_ = hovered[0]; |
| 422 | } else { | ||
| 423 | actual_hover_ = std::nullopt; | ||
| 205 | } | 424 | } |
| 206 | 425 | ||
| 207 | if (new_hovered_item != hovered_item_) { | 426 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { |
| 208 | hovered_item_ = new_hovered_item; | 427 | EvaluateHover(); |
| 428 | } | ||
| 209 | 429 | ||
| 210 | Refresh(); | 430 | if (scroll_mode_) { |
| 431 | EvaluateScroll(event.GetPosition()); | ||
| 211 | } | 432 | } |
| 212 | 433 | ||
| 434 | mouse_position_ = event.GetPosition(); | ||
| 435 | |||
| 213 | event.Skip(); | 436 | event.Skip(); |
| 214 | } | 437 | } |
| 215 | 438 | ||
| 216 | void SubwayMap::Redraw() { | 439 | void SubwayMap::OnMouseScroll(wxMouseEvent &event) { |
| 217 | wxSize panel_size = GetSize(); | 440 | double new_zoom = zoom_; |
| 218 | wxSize image_size = map_image_.GetSize(); | 441 | if (event.GetWheelRotation() > 0) { |
| 219 | 442 | new_zoom = std::min(3.0, zoom_ + 0.25); | |
| 220 | render_x_ = 0; | ||
| 221 | render_y_ = 0; | ||
| 222 | render_width_ = panel_size.GetWidth(); | ||
| 223 | render_height_ = panel_size.GetHeight(); | ||
| 224 | |||
| 225 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
| 226 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
| 227 | render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
| 228 | image_size.GetWidth(); | ||
| 229 | render_y_ = (panel_size.GetHeight() - render_height_) / 2; | ||
| 230 | } else { | 443 | } else { |
| 231 | render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / | 444 | new_zoom = std::max(1.0, zoom_ - 0.25); |
| 232 | image_size.GetHeight(); | 445 | } |
| 233 | render_x_ = (panel_size.GetWidth() - render_width_) / 2; | 446 | |
| 447 | if (zoom_ != new_zoom) { | ||
| 448 | SetZoom(new_zoom, event.GetPosition()); | ||
| 449 | } | ||
| 450 | |||
| 451 | event.Skip(); | ||
| 452 | } | ||
| 453 | |||
| 454 | void SubwayMap::OnMouseLeave(wxMouseEvent &event) { | ||
| 455 | SetScrollSpeed(0, 0); | ||
| 456 | mouse_position_ = std::nullopt; | ||
| 457 | } | ||
| 458 | |||
| 459 | void SubwayMap::OnMouseClick(wxMouseEvent &event) { | ||
| 460 | bool finished = false; | ||
| 461 | |||
| 462 | if (actual_hover_) { | ||
| 463 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); | ||
| 464 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | ||
| 465 | |||
| 466 | if ((subway_door && !GetDoorRequirements(*subway_door).empty()) || | ||
| 467 | networks_.IsItemInNetwork(*hovered_item_)) { | ||
| 468 | if (actual_hover_ != hovered_item_) { | ||
| 469 | EvaluateHover(); | ||
| 470 | |||
| 471 | if (!hovered_item_) { | ||
| 472 | sticky_hover_ = false; | ||
| 473 | } | ||
| 474 | } else { | ||
| 475 | sticky_hover_ = !sticky_hover_; | ||
| 476 | } | ||
| 477 | |||
| 478 | finished = true; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | |||
| 482 | if (!finished) { | ||
| 483 | if (scroll_mode_) { | ||
| 484 | scroll_mode_ = false; | ||
| 485 | |||
| 486 | SetScrollSpeed(0, 0); | ||
| 487 | |||
| 488 | SetCursor(wxCURSOR_ARROW); | ||
| 489 | } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || | ||
| 490 | event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || | ||
| 491 | event.GetPosition().y < GetSize().GetHeight() / 6 || | ||
| 492 | event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { | ||
| 493 | scroll_mode_ = true; | ||
| 494 | |||
| 495 | EvaluateScroll(event.GetPosition()); | ||
| 496 | |||
| 497 | SetCursor(wxCURSOR_CROSS); | ||
| 498 | } else { | ||
| 499 | sticky_hover_ = false; | ||
| 500 | } | ||
| 501 | } | ||
| 502 | } | ||
| 503 | |||
| 504 | void SubwayMap::OnTimer(wxTimerEvent &event) { | ||
| 505 | SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); | ||
| 506 | Refresh(); | ||
| 507 | } | ||
| 508 | |||
| 509 | void SubwayMap::OnZoomSlide(wxCommandEvent &event) { | ||
| 510 | double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue(); | ||
| 511 | |||
| 512 | if (new_zoom != zoom_) { | ||
| 513 | SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}); | ||
| 234 | } | 514 | } |
| 515 | } | ||
| 235 | 516 | ||
| 236 | rendered_ = wxBitmap( | 517 | void SubwayMap::OnClickHelp(wxCommandEvent &event) { |
| 237 | map_image_ | 518 | wxMessageBox( |
| 238 | .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR) | 519 | "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the " |
| 239 | .Size(panel_size, {render_x_, render_y_}, 255, 255, 255)); | 520 | "corner.\nClick on a side of the screen to start panning. It will follow " |
| 521 | "your mouse. Click again to stop.\nHover over a door to see the " | ||
| 522 | "requirements to open it.\nHover over a warp or active painting to see " | ||
| 523 | "what it is connected to.\nFor one-way connections, there will be a " | ||
| 524 | "circle at the exit.\nCircles represent paintings.\nA red circle means " | ||
| 525 | "that the painting is locked by a door.\nA blue circle means painting " | ||
| 526 | "shuffle is enabled and the painting has not been checked yet.\nA black " | ||
| 527 | "circle means the painting is not a warp.\nA green circle means that the " | ||
| 528 | "painting is a warp.\nPainting exits will be indicated with an X.\nClick " | ||
| 529 | "on a door or warp to make the popup stick until you click again.", | ||
| 530 | "Subway Map Help"); | ||
| 531 | } | ||
| 532 | |||
| 533 | void SubwayMap::Redraw() { | ||
| 534 | rendered_ = wxBitmap(map_image_); | ||
| 240 | 535 | ||
| 241 | wxMemoryDC dc; | 536 | wxMemoryDC dc; |
| 242 | dc.SelectObject(rendered_); | 537 | dc.SelectObject(rendered_); |
| 243 | 538 | ||
| 539 | wxGCDC gcdc(dc); | ||
| 540 | |||
| 541 | std::map<std::string, std::string> painting_mapping = AP_GetPaintingMapping(); | ||
| 542 | |||
| 244 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | 543 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { |
| 245 | ItemDrawType draw_type = ItemDrawType::kNone; | 544 | ItemDrawType draw_type = ItemDrawType::kNone; |
| 246 | const wxBrush *brush_color = wxGREY_BRUSH; | 545 | const wxBrush *brush_color = wxGREY_BRUSH; |
| 247 | std::optional<wxColour> shade_color; | 546 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); |
| 547 | |||
| 548 | if (AP_HasEarlyColorHallways() && | ||
| 549 | subway_item.special == "early_color_hallways") { | ||
| 550 | draw_type = ItemDrawType::kOwl; | ||
| 551 | brush_color = wxGREEN_BRUSH; | ||
| 552 | } else if (subway_item.special == "starting_room_overhead") { | ||
| 553 | // Do not draw. | ||
| 554 | } else if (AP_IsColorShuffle() && subway_item.special && | ||
| 555 | subway_item.special->starts_with("color_")) { | ||
| 556 | std::string color_name = subway_item.special->substr(6); | ||
| 557 | LingoColor lingo_color = GetLingoColorForString(color_name); | ||
| 558 | int color_item_id = GD_GetItemIdForColor(lingo_color); | ||
| 248 | 559 | ||
| 249 | if (subway_item.door) { | ||
| 250 | draw_type = ItemDrawType::kBox; | 560 | draw_type = ItemDrawType::kBox; |
| 251 | 561 | if (AP_HasItemSafe(color_item_id)) { | |
| 252 | if (IsDoorOpen(*subway_item.door)) { | 562 | brush_color = wxGREEN_BRUSH; |
| 253 | if (!subway_item.paintings.empty()) { | 563 | } else { |
| 254 | draw_type = ItemDrawType::kOwl; | 564 | brush_color = wxRED_BRUSH; |
| 255 | } else { | 565 | } |
| 566 | } else if (subway_item.special == "sun_painting") { | ||
| 567 | if (!AP_IsPilgrimageEnabled()) { | ||
| 568 | draw_type = ItemDrawType::kOwl; | ||
| 569 | if (IsDoorOpen(*subway_item.door)) { | ||
| 256 | brush_color = wxGREEN_BRUSH; | 570 | brush_color = wxGREEN_BRUSH; |
| 571 | } else { | ||
| 572 | brush_color = wxRED_BRUSH; | ||
| 257 | } | 573 | } |
| 574 | } | ||
| 575 | } else if (subway_item.sunwarp && | ||
| 576 | subway_item.sunwarp->type == SubwaySunwarpType::kFinal && | ||
| 577 | AP_IsPilgrimageEnabled()) { | ||
| 578 | draw_type = ItemDrawType::kBox; | ||
| 579 | |||
| 580 | if (IsPilgrimageDoable()) { | ||
| 581 | brush_color = wxGREEN_BRUSH; | ||
| 258 | } else { | 582 | } else { |
| 259 | brush_color = wxRED_BRUSH; | 583 | brush_color = wxRED_BRUSH; |
| 260 | } | 584 | } |
| 261 | } else if (!subway_item.paintings.empty()) { | 585 | } else if (subway_item.painting) { |
| 262 | if (AP_IsPaintingShuffle()) { | 586 | if (subway_door && !IsDoorOpen(*subway_door)) { |
| 263 | bool has_checked_painting = false; | 587 | draw_type = ItemDrawType::kOwl; |
| 264 | bool has_unchecked_painting = false; | 588 | brush_color = wxRED_BRUSH; |
| 265 | bool has_mapped_painting = false; | 589 | } else if (AP_IsPaintingShuffle()) { |
| 266 | 590 | if (!checked_paintings_.count(*subway_item.painting)) { | |
| 267 | for (const std::string &painting_id : subway_item.paintings) { | 591 | draw_type = ItemDrawType::kOwl; |
| 268 | if (checked_paintings_.count(painting_id)) { | 592 | brush_color = wxBLUE_BRUSH; |
| 269 | has_checked_painting = true; | 593 | } else if (painting_mapping.count(*subway_item.painting)) { |
| 270 | 594 | draw_type = ItemDrawType::kOwl; | |
| 271 | if (AP_GetPaintingMapping().count(painting_id)) { | 595 | brush_color = wxGREEN_BRUSH; |
| 272 | has_mapped_painting = true; | 596 | } else if (AP_IsPaintingMappedTo(*subway_item.painting)) { |
| 273 | } | 597 | draw_type = ItemDrawType::kOwlExit; |
| 274 | } else { | 598 | brush_color = wxGREEN_BRUSH; |
| 275 | has_unchecked_painting = true; | ||
| 276 | } | ||
| 277 | } | 599 | } |
| 278 | 600 | } else if (subway_item.HasWarps()) { | |
| 279 | if (has_unchecked_painting || has_mapped_painting) { | 601 | brush_color = wxGREEN_BRUSH; |
| 602 | if (!subway_item.exits.empty()) { | ||
| 603 | draw_type = ItemDrawType::kOwlExit; | ||
| 604 | } else { | ||
| 280 | draw_type = ItemDrawType::kOwl; | 605 | draw_type = ItemDrawType::kOwl; |
| 281 | |||
| 282 | if (has_unchecked_painting) { | ||
| 283 | if (has_checked_painting) { | ||
| 284 | shade_color = wxColour(255, 255, 0, 100); | ||
| 285 | } else { | ||
| 286 | shade_color = wxColour(100, 100, 100, 100); | ||
| 287 | } | ||
| 288 | } | ||
| 289 | } | 606 | } |
| 290 | } else if (!subway_item.tags.empty()) { | 607 | } |
| 291 | draw_type = ItemDrawType::kOwl; | 608 | } else if (subway_door) { |
| 609 | draw_type = ItemDrawType::kBox; | ||
| 610 | |||
| 611 | if (IsDoorOpen(*subway_door)) { | ||
| 612 | brush_color = wxGREEN_BRUSH; | ||
| 613 | } else { | ||
| 614 | brush_color = wxRED_BRUSH; | ||
| 292 | } | 615 | } |
| 293 | } | 616 | } |
| 294 | 617 | ||
| 295 | int real_area_x = | 618 | wxPoint real_area_pos = {subway_item.x, subway_item.y}; |
| 296 | render_x_ + subway_item.x * render_width_ / image_size.GetWidth(); | ||
| 297 | int real_area_y = | ||
| 298 | render_y_ + subway_item.y * render_width_ / image_size.GetWidth(); | ||
| 299 | 619 | ||
| 300 | int real_area_size = | 620 | int real_area_size = |
| 301 | render_width_ * | 621 | (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); |
| 302 | (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE) / | ||
| 303 | image_size.GetWidth(); | ||
| 304 | if (real_area_size == 0) { | ||
| 305 | real_area_size = 1; | ||
| 306 | } | ||
| 307 | 622 | ||
| 308 | if (draw_type == ItemDrawType::kBox) { | 623 | if (draw_type == ItemDrawType::kBox) { |
| 309 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | 624 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); |
| 310 | dc.SetBrush(*brush_color); | 625 | gcdc.SetBrush(*brush_color); |
| 311 | dc.DrawRectangle({real_area_x, real_area_y}, | 626 | |
| 312 | {real_area_size, real_area_size}); | 627 | if (subway_item.tilted) { |
| 313 | } else if (draw_type == ItemDrawType::kOwl) { | 628 | constexpr int AREA_TILTED_SIDE = |
| 314 | wxBitmap owl_bitmap = wxBitmap( | 629 | static_cast<int>(AREA_ACTUAL_SIZE / 1.41421356237); |
| 315 | owl_image_.Scale(real_area_size, real_area_size, | 630 | const wxPoint poly_points[] = {{AREA_TILTED_SIDE, 0}, |
| 316 | wxIMAGE_QUALITY_BILINEAR)); | 631 | {2 * AREA_TILTED_SIDE, AREA_TILTED_SIDE}, |
| 317 | dc.DrawBitmap(owl_bitmap, {real_area_x, real_area_y}); | 632 | {AREA_TILTED_SIDE, 2 * AREA_TILTED_SIDE}, |
| 633 | {0, AREA_TILTED_SIDE}}; | ||
| 634 | gcdc.DrawPolygon(4, poly_points, subway_item.x, subway_item.y); | ||
| 635 | } else { | ||
| 636 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
| 637 | } | ||
| 638 | } else if (draw_type == ItemDrawType::kOwl || draw_type == ItemDrawType::kOwlExit) { | ||
| 639 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | ||
| 640 | gcdc.SetBrush(*brush_color); | ||
| 641 | gcdc.DrawCircle(real_area_pos, PAINTING_RADIUS); | ||
| 642 | |||
| 643 | if (draw_type == ItemDrawType::kOwlExit) { | ||
| 644 | gcdc.DrawLine(subway_item.x - PAINTING_EXIT_RADIUS, | ||
| 645 | subway_item.y - PAINTING_EXIT_RADIUS, | ||
| 646 | subway_item.x + PAINTING_EXIT_RADIUS, | ||
| 647 | subway_item.y + PAINTING_EXIT_RADIUS); | ||
| 648 | gcdc.DrawLine(subway_item.x + PAINTING_EXIT_RADIUS, | ||
| 649 | subway_item.y - PAINTING_EXIT_RADIUS, | ||
| 650 | subway_item.x - PAINTING_EXIT_RADIUS, | ||
| 651 | subway_item.y + PAINTING_EXIT_RADIUS); | ||
| 652 | } | ||
| 318 | } | 653 | } |
| 319 | } | 654 | } |
| 320 | } | 655 | } |
| 321 | 656 | ||
| 322 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int& id) const { | 657 | void SubwayMap::SetUpHelpButton() { |
| 658 | help_button_->SetSize(wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, | ||
| 659 | wxDefaultCoord, wxSIZE_AUTO); | ||
| 660 | help_button_->SetPosition({ | ||
| 661 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, | ||
| 662 | 15, | ||
| 663 | }); | ||
| 664 | } | ||
| 665 | |||
| 666 | void SubwayMap::EvaluateScroll(wxPoint pos) { | ||
| 667 | int scroll_x; | ||
| 668 | int scroll_y; | ||
| 669 | if (pos.x < GetSize().GetWidth() / 9) { | ||
| 670 | scroll_x = 20; | ||
| 671 | } else if (pos.x < GetSize().GetWidth() / 6) { | ||
| 672 | scroll_x = 5; | ||
| 673 | } else if (pos.x > 8 * GetSize().GetWidth() / 9) { | ||
| 674 | scroll_x = -20; | ||
| 675 | } else if (pos.x > 5 * GetSize().GetWidth() / 6) { | ||
| 676 | scroll_x = -5; | ||
| 677 | } else { | ||
| 678 | scroll_x = 0; | ||
| 679 | } | ||
| 680 | if (pos.y < GetSize().GetHeight() / 9) { | ||
| 681 | scroll_y = 20; | ||
| 682 | } else if (pos.y < GetSize().GetHeight() / 6) { | ||
| 683 | scroll_y = 5; | ||
| 684 | } else if (pos.y > 8 * GetSize().GetHeight() / 9) { | ||
| 685 | scroll_y = -20; | ||
| 686 | } else if (pos.y > 5 * GetSize().GetHeight() / 6) { | ||
| 687 | scroll_y = -5; | ||
| 688 | } else { | ||
| 689 | scroll_y = 0; | ||
| 690 | } | ||
| 691 | |||
| 692 | SetScrollSpeed(scroll_x, scroll_y); | ||
| 693 | } | ||
| 694 | |||
| 695 | void SubwayMap::EvaluateHover() { | ||
| 696 | hovered_item_ = actual_hover_; | ||
| 697 | |||
| 698 | if (hovered_item_) { | ||
| 699 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
| 700 | // knows when an item has a hover effect. | ||
| 701 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
| 702 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | ||
| 703 | |||
| 704 | if (subway_door && !GetDoorRequirements(*subway_door).empty()) { | ||
| 705 | report_popup_->SetDoorId(*subway_door); | ||
| 706 | |||
| 707 | wxPoint popupPos = | ||
| 708 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
| 709 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
| 710 | |||
| 711 | report_popup_->SetClientSize( | ||
| 712 | report_popup_->GetVirtualSize().GetWidth(), | ||
| 713 | std::min(GetSize().GetHeight(), | ||
| 714 | report_popup_->GetVirtualSize().GetHeight())); | ||
| 715 | |||
| 716 | if (popupPos.x + report_popup_->GetSize().GetWidth() > | ||
| 717 | GetSize().GetWidth()) { | ||
| 718 | popupPos.x = GetSize().GetWidth() - report_popup_->GetSize().GetWidth(); | ||
| 719 | } | ||
| 720 | if (popupPos.y + report_popup_->GetSize().GetHeight() > | ||
| 721 | GetSize().GetHeight()) { | ||
| 722 | popupPos.y = | ||
| 723 | GetSize().GetHeight() - report_popup_->GetSize().GetHeight(); | ||
| 724 | } | ||
| 725 | report_popup_->SetPosition(popupPos); | ||
| 726 | |||
| 727 | report_popup_->Show(); | ||
| 728 | } else { | ||
| 729 | report_popup_->Reset(); | ||
| 730 | report_popup_->Hide(); | ||
| 731 | } | ||
| 732 | } else { | ||
| 733 | report_popup_->Reset(); | ||
| 734 | report_popup_->Hide(); | ||
| 735 | } | ||
| 736 | |||
| 737 | Refresh(); | ||
| 738 | } | ||
| 739 | |||
| 740 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { | ||
| 741 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
| 742 | map_image_.GetSize().GetWidth() + | ||
| 743 | zoom_x_), | ||
| 744 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
| 745 | map_image_.GetSize().GetWidth() + | ||
| 746 | zoom_y_)}; | ||
| 747 | } | ||
| 748 | |||
| 749 | wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { | ||
| 750 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
| 751 | map_image_.GetSize().GetWidth()), | ||
| 752 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
| 753 | map_image_.GetSize().GetWidth())}; | ||
| 754 | } | ||
| 755 | |||
| 756 | wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { | ||
| 757 | return { | ||
| 758 | std::clamp(static_cast<int>((pos.x - zoom_x_) * map_image_.GetWidth() / | ||
| 759 | render_width_ / zoom_), | ||
| 760 | 0, map_image_.GetWidth() - 1), | ||
| 761 | std::clamp(static_cast<int>((pos.y - zoom_y_) * map_image_.GetWidth() / | ||
| 762 | render_width_ / zoom_), | ||
| 763 | 0, map_image_.GetHeight() - 1)}; | ||
| 764 | } | ||
| 765 | |||
| 766 | void SubwayMap::SetZoomPos(wxPoint pos) { | ||
| 767 | if (render_width_ * zoom_ <= GetSize().GetWidth()) { | ||
| 768 | zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; | ||
| 769 | } else { | ||
| 770 | zoom_x_ = std::clamp( | ||
| 771 | pos.x, GetSize().GetWidth() - static_cast<int>(render_width_ * zoom_), | ||
| 772 | 0); | ||
| 773 | } | ||
| 774 | if (render_height_ * zoom_ <= GetSize().GetHeight()) { | ||
| 775 | zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; | ||
| 776 | } else { | ||
| 777 | zoom_y_ = std::clamp( | ||
| 778 | pos.y, GetSize().GetHeight() - static_cast<int>(render_height_ * zoom_), | ||
| 779 | 0); | ||
| 780 | } | ||
| 781 | } | ||
| 782 | |||
| 783 | void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { | ||
| 784 | bool should_timer = (scroll_x != 0 || scroll_y != 0); | ||
| 785 | if (should_timer != scroll_timer_->IsRunning()) { | ||
| 786 | if (should_timer) { | ||
| 787 | scroll_timer_->Start(1000 / 60); | ||
| 788 | } else { | ||
| 789 | scroll_timer_->Stop(); | ||
| 790 | } | ||
| 791 | } | ||
| 792 | |||
| 793 | scroll_x_ = scroll_x; | ||
| 794 | scroll_y_ = scroll_y; | ||
| 795 | } | ||
| 796 | |||
| 797 | void SubwayMap::SetZoom(double zoom, wxPoint static_point) { | ||
| 798 | wxPoint map_pos = RenderPosToMapPos(static_point); | ||
| 799 | zoom_ = zoom; | ||
| 800 | |||
| 801 | wxPoint virtual_pos = MapPosToVirtualPos(map_pos); | ||
| 802 | SetZoomPos(-(virtual_pos - static_point)); | ||
| 803 | |||
| 804 | Refresh(); | ||
| 805 | |||
| 806 | zoom_slider_->SetValue((zoom - 1.0) / 0.25); | ||
| 807 | } | ||
| 808 | |||
| 809 | std::optional<int> SubwayMap::GetRealSubwayDoor(const SubwayItem subway_item) { | ||
| 810 | if (AP_IsSunwarpShuffle() && subway_item.sunwarp && | ||
| 811 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | ||
| 812 | int sunwarp_index = subway_item.sunwarp->dots - 1; | ||
| 813 | if (subway_item.sunwarp->type == SubwaySunwarpType::kExit) { | ||
| 814 | sunwarp_index += 6; | ||
| 815 | } | ||
| 816 | |||
| 817 | for (const auto &[start_index, mapping] : sunwarp_mapping_) { | ||
| 818 | if (start_index == sunwarp_index || mapping.exit_index == sunwarp_index) { | ||
| 819 | return GD_GetSunwarpDoors().at(mapping.dots - 1); | ||
| 820 | } | ||
| 821 | } | ||
| 822 | } | ||
| 823 | |||
| 824 | return subway_item.door; | ||
| 825 | } | ||
| 826 | |||
| 827 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { | ||
| 323 | const SubwayItem &subway_item = GD_GetSubwayItem(id); | 828 | const SubwayItem &subway_item = GD_GetSubwayItem(id); |
| 324 | return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y), | 829 | if (subway_item.painting) { |
| 325 | AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; | 830 | return {static_cast<float>(subway_item.x) - PAINTING_RADIUS, |
| 831 | static_cast<float>(subway_item.y) - PAINTING_RADIUS, | ||
| 832 | PAINTING_RADIUS * 2, PAINTING_RADIUS * 2}; | ||
| 833 | } else { | ||
| 834 | return {static_cast<float>(subway_item.x), | ||
| 835 | static_cast<float>(subway_item.y), AREA_ACTUAL_SIZE, | ||
| 836 | AREA_ACTUAL_SIZE}; | ||
| 837 | } | ||
| 326 | } | 838 | } |
