diff options
Diffstat (limited to 'src/subway_map.cpp')
-rw-r--r-- | src/subway_map.cpp | 724 |
1 files changed, 724 insertions, 0 deletions
diff --git a/src/subway_map.cpp b/src/subway_map.cpp new file mode 100644 index 0000000..e3b844d --- /dev/null +++ b/src/subway_map.cpp | |||
@@ -0,0 +1,724 @@ | |||
1 | #include "subway_map.h" | ||
2 | |||
3 | #include <fmt/core.h> | ||
4 | #include <wx/dcbuffer.h> | ||
5 | #include <wx/dcgraph.h> | ||
6 | |||
7 | #include <sstream> | ||
8 | |||
9 | #include "ap_state.h" | ||
10 | #include "game_data.h" | ||
11 | #include "global.h" | ||
12 | #include "tracker_state.h" | ||
13 | |||
14 | constexpr int AREA_ACTUAL_SIZE = 21; | ||
15 | constexpr int OWL_ACTUAL_SIZE = 32; | ||
16 | |||
17 | enum class ItemDrawType { kNone, kBox, kOwl }; | ||
18 | |||
19 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | ||
20 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
21 | |||
22 | map_image_ = | ||
23 | wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG); | ||
24 | if (!map_image_.IsOk()) { | ||
25 | return; | ||
26 | } | ||
27 | |||
28 | owl_image_ = | ||
29 | wxImage(GetAbsolutePath("assets/owl.png").c_str(), wxBITMAP_TYPE_PNG); | ||
30 | if (!owl_image_.IsOk()) { | ||
31 | return; | ||
32 | } | ||
33 | |||
34 | unchecked_eye_ = | ||
35 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | ||
36 | wxBITMAP_TYPE_PNG) | ||
37 | .Scale(32, 32)); | ||
38 | checked_eye_ = wxBitmap( | ||
39 | wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) | ||
40 | .Scale(32, 32)); | ||
41 | |||
42 | tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>( | ||
43 | quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()), | ||
44 | static_cast<float>(map_image_.GetHeight())}); | ||
45 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
46 | tree_->add(subway_item.id); | ||
47 | } | ||
48 | |||
49 | Redraw(); | ||
50 | |||
51 | scroll_timer_ = new wxTimer(this); | ||
52 | |||
53 | Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); | ||
54 | Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); | ||
55 | Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); | ||
56 | Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); | ||
57 | Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); | ||
58 | Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); | ||
59 | |||
60 | zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); | ||
61 | zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); | ||
62 | |||
63 | help_button_ = new wxButton(this, wxID_ANY, "Help"); | ||
64 | help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); | ||
65 | SetUpHelpButton(); | ||
66 | } | ||
67 | |||
68 | void SubwayMap::OnConnect() { | ||
69 | networks_.Clear(); | ||
70 | |||
71 | std::map<std::string, std::vector<int>> tagged; | ||
72 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
73 | if (AP_HasEarlyColorHallways() && | ||
74 | subway_item.special == "starting_room_paintings") { | ||
75 | tagged["early_ch"].push_back(subway_item.id); | ||
76 | } | ||
77 | |||
78 | if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { | ||
79 | continue; | ||
80 | } | ||
81 | |||
82 | for (const std::string &tag : subway_item.tags) { | ||
83 | tagged[tag].push_back(subway_item.id); | ||
84 | } | ||
85 | |||
86 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && | ||
87 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | ||
88 | std::string tag = fmt::format("subway{}", subway_item.sunwarp->dots); | ||
89 | tagged[tag].push_back(subway_item.id); | ||
90 | } | ||
91 | |||
92 | if (!AP_IsPilgrimageEnabled() && | ||
93 | (subway_item.special == "sun_painting" || | ||
94 | subway_item.special == "sun_painting_exit")) { | ||
95 | tagged["sun_painting"].push_back(subway_item.id); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | if (AP_IsSunwarpShuffle()) { | ||
100 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { | ||
101 | std::string tag = fmt::format("sunwarp{}", mapping.dots); | ||
102 | |||
103 | SubwaySunwarp fromWarp; | ||
104 | if (index < 6) { | ||
105 | fromWarp.dots = index + 1; | ||
106 | fromWarp.type = SubwaySunwarpType::kEnter; | ||
107 | } else { | ||
108 | fromWarp.dots = index - 5; | ||
109 | fromWarp.type = SubwaySunwarpType::kExit; | ||
110 | } | ||
111 | |||
112 | SubwaySunwarp toWarp; | ||
113 | if (mapping.exit_index < 6) { | ||
114 | toWarp.dots = mapping.exit_index + 1; | ||
115 | toWarp.type = SubwaySunwarpType::kEnter; | ||
116 | } else { | ||
117 | toWarp.dots = mapping.exit_index - 5; | ||
118 | toWarp.type = SubwaySunwarpType::kExit; | ||
119 | } | ||
120 | |||
121 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); | ||
122 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | for (const auto &[tag, items] : tagged) { | ||
127 | // Pairwise connect all items with the same tag. | ||
128 | for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end(); | ||
129 | tag_it1++) { | ||
130 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); | ||
131 | tag_it2++) { | ||
132 | networks_.AddLink(*tag_it1, *tag_it2); | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | |||
137 | checked_paintings_.clear(); | ||
138 | } | ||
139 | |||
140 | void SubwayMap::UpdateIndicators() { | ||
141 | if (AP_IsPaintingShuffle()) { | ||
142 | for (const std::string &painting_id : AP_GetCheckedPaintings()) { | ||
143 | if (!checked_paintings_.count(painting_id)) { | ||
144 | checked_paintings_.insert(painting_id); | ||
145 | |||
146 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
147 | std::optional<int> from_id = GD_GetSubwayItemForPainting(painting_id); | ||
148 | std::optional<int> to_id = GD_GetSubwayItemForPainting( | ||
149 | AP_GetPaintingMapping().at(painting_id)); | ||
150 | |||
151 | if (from_id && to_id) { | ||
152 | networks_.AddLink(*from_id, *to_id); | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
159 | Redraw(); | ||
160 | } | ||
161 | |||
162 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | ||
163 | SubwaySunwarp to_sunwarp) { | ||
164 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), | ||
165 | GD_GetSubwayItemForSunwarp(to_sunwarp)); | ||
166 | } | ||
167 | |||
168 | void SubwayMap::Zoom(bool in) { | ||
169 | wxPoint focus_point; | ||
170 | |||
171 | if (mouse_position_) { | ||
172 | focus_point = *mouse_position_; | ||
173 | } else { | ||
174 | focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}; | ||
175 | } | ||
176 | |||
177 | if (in) { | ||
178 | if (zoom_ < 3.0) { | ||
179 | SetZoom(zoom_ + 0.25, focus_point); | ||
180 | } | ||
181 | } else { | ||
182 | if (zoom_ > 1.0) { | ||
183 | SetZoom(zoom_ - 0.25, focus_point); | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | |||
188 | void SubwayMap::OnPaint(wxPaintEvent &event) { | ||
189 | if (GetSize() != rendered_.GetSize()) { | ||
190 | wxSize panel_size = GetSize(); | ||
191 | wxSize image_size = map_image_.GetSize(); | ||
192 | |||
193 | render_x_ = 0; | ||
194 | render_y_ = 0; | ||
195 | render_width_ = panel_size.GetWidth(); | ||
196 | render_height_ = panel_size.GetHeight(); | ||
197 | |||
198 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
199 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
200 | render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
201 | image_size.GetWidth(); | ||
202 | render_y_ = (panel_size.GetHeight() - render_height_) / 2; | ||
203 | } else { | ||
204 | render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
205 | image_size.GetHeight(); | ||
206 | render_x_ = (panel_size.GetWidth() - render_width_) / 2; | ||
207 | } | ||
208 | |||
209 | SetZoomPos({zoom_x_, zoom_y_}); | ||
210 | |||
211 | SetUpHelpButton(); | ||
212 | } | ||
213 | |||
214 | wxBufferedPaintDC dc(this); | ||
215 | dc.SetBackground(*wxWHITE_BRUSH); | ||
216 | dc.Clear(); | ||
217 | |||
218 | { | ||
219 | wxMemoryDC rendered_dc; | ||
220 | rendered_dc.SelectObject(rendered_); | ||
221 | |||
222 | int dst_x; | ||
223 | int dst_y; | ||
224 | int dst_w; | ||
225 | int dst_h; | ||
226 | int src_x; | ||
227 | int src_y; | ||
228 | int src_w; | ||
229 | int src_h; | ||
230 | |||
231 | int zoomed_width = render_width_ * zoom_; | ||
232 | int zoomed_height = render_height_ * zoom_; | ||
233 | |||
234 | if (zoomed_width <= GetSize().GetWidth()) { | ||
235 | dst_x = (GetSize().GetWidth() - zoomed_width) / 2; | ||
236 | dst_w = zoomed_width; | ||
237 | src_x = 0; | ||
238 | src_w = map_image_.GetWidth(); | ||
239 | } else { | ||
240 | dst_x = 0; | ||
241 | dst_w = GetSize().GetWidth(); | ||
242 | src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
243 | src_w = | ||
244 | GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_; | ||
245 | } | ||
246 | |||
247 | if (zoomed_height <= GetSize().GetHeight()) { | ||
248 | dst_y = (GetSize().GetHeight() - zoomed_height) / 2; | ||
249 | dst_h = zoomed_height; | ||
250 | src_y = 0; | ||
251 | src_h = map_image_.GetHeight(); | ||
252 | } else { | ||
253 | dst_y = 0; | ||
254 | dst_h = GetSize().GetHeight(); | ||
255 | src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
256 | src_h = | ||
257 | GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; | ||
258 | } | ||
259 | |||
260 | wxGCDC gcdc(dc); | ||
261 | gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD); | ||
262 | gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, | ||
263 | src_w, src_h); | ||
264 | } | ||
265 | |||
266 | if (hovered_item_) { | ||
267 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
268 | // knows when an item has a hover effect. | ||
269 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
270 | if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { | ||
271 | const std::map<std::string, bool> &report = | ||
272 | GetDoorRequirements(*subway_item.door); | ||
273 | |||
274 | int acc_height = 10; | ||
275 | int col_width = 0; | ||
276 | |||
277 | for (const auto &[text, obtained] : report) { | ||
278 | wxSize item_extent = dc.GetTextExtent(text); | ||
279 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
280 | acc_height += item_height; | ||
281 | |||
282 | if (item_extent.GetWidth() > col_width) { | ||
283 | col_width = item_extent.GetWidth(); | ||
284 | } | ||
285 | } | ||
286 | |||
287 | int item_width = col_width + 10 + 32; | ||
288 | int full_width = item_width + 20; | ||
289 | |||
290 | wxPoint popup_pos = | ||
291 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
292 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
293 | |||
294 | if (popup_pos.x + full_width > GetSize().GetWidth()) { | ||
295 | popup_pos.x = GetSize().GetWidth() - full_width; | ||
296 | } | ||
297 | if (popup_pos.y + acc_height > GetSize().GetHeight()) { | ||
298 | popup_pos.y = GetSize().GetHeight() - acc_height; | ||
299 | } | ||
300 | |||
301 | dc.SetPen(*wxTRANSPARENT_PEN); | ||
302 | dc.SetBrush(*wxBLACK_BRUSH); | ||
303 | dc.DrawRectangle(popup_pos, {full_width, acc_height}); | ||
304 | |||
305 | dc.SetFont(GetFont()); | ||
306 | |||
307 | int cur_height = 10; | ||
308 | |||
309 | for (const auto &[text, obtained] : report) { | ||
310 | wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_; | ||
311 | |||
312 | dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); | ||
313 | |||
314 | dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); | ||
315 | wxSize item_extent = dc.GetTextExtent(text); | ||
316 | dc.DrawText( | ||
317 | text, | ||
318 | popup_pos + | ||
319 | wxPoint{10 + 32 + 10, | ||
320 | cur_height + (32 - dc.GetFontMetrics().height) / 2}); | ||
321 | |||
322 | cur_height += 10 + 32; | ||
323 | } | ||
324 | } | ||
325 | |||
326 | if (networks_.IsItemInNetwork(*hovered_item_)) { | ||
327 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
328 | |||
329 | for (const auto &[item_id1, item_id2] : | ||
330 | networks_.GetNetworkGraph(*hovered_item_)) { | ||
331 | const SubwayItem &item1 = GD_GetSubwayItem(item_id1); | ||
332 | const SubwayItem &item2 = GD_GetSubwayItem(item_id2); | ||
333 | |||
334 | wxPoint item1_pos = MapPosToRenderPos( | ||
335 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); | ||
336 | wxPoint item2_pos = MapPosToRenderPos( | ||
337 | {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); | ||
338 | |||
339 | int left = std::min(item1_pos.x, item2_pos.x); | ||
340 | int top = std::min(item1_pos.y, item2_pos.y); | ||
341 | int right = std::max(item1_pos.x, item2_pos.x); | ||
342 | int bottom = std::max(item1_pos.y, item2_pos.y); | ||
343 | |||
344 | int halfwidth = right - left; | ||
345 | int halfheight = bottom - top; | ||
346 | |||
347 | if (halfwidth < 4 || halfheight < 4) { | ||
348 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
349 | dc.DrawLine(item1_pos, item2_pos); | ||
350 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
351 | dc.DrawLine(item1_pos, item2_pos); | ||
352 | } else { | ||
353 | int ellipse_x; | ||
354 | int ellipse_y; | ||
355 | double start; | ||
356 | double end; | ||
357 | |||
358 | if (item1_pos.x > item2_pos.x) { | ||
359 | ellipse_y = top; | ||
360 | |||
361 | if (item1_pos.y > item2_pos.y) { | ||
362 | ellipse_x = left - halfwidth; | ||
363 | |||
364 | start = 0; | ||
365 | end = 90; | ||
366 | } else { | ||
367 | ellipse_x = left; | ||
368 | |||
369 | start = 90; | ||
370 | end = 180; | ||
371 | } | ||
372 | } else { | ||
373 | ellipse_y = top - halfheight; | ||
374 | |||
375 | if (item1_pos.y > item2_pos.y) { | ||
376 | ellipse_x = left - halfwidth; | ||
377 | |||
378 | start = 270; | ||
379 | end = 360; | ||
380 | } else { | ||
381 | ellipse_x = left; | ||
382 | |||
383 | start = 180; | ||
384 | end = 270; | ||
385 | } | ||
386 | } | ||
387 | |||
388 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
389 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
390 | halfheight * 2, start, end); | ||
391 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
392 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
393 | halfheight * 2, start, end); | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | } | ||
398 | |||
399 | event.Skip(); | ||
400 | } | ||
401 | |||
402 | void SubwayMap::OnMouseMove(wxMouseEvent &event) { | ||
403 | wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); | ||
404 | |||
405 | std::vector<int> hovered = tree_->query( | ||
406 | {static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y), 2, 2}); | ||
407 | if (!hovered.empty()) { | ||
408 | actual_hover_ = hovered[0]; | ||
409 | } else { | ||
410 | actual_hover_ = std::nullopt; | ||
411 | } | ||
412 | |||
413 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { | ||
414 | hovered_item_ = actual_hover_; | ||
415 | |||
416 | Refresh(); | ||
417 | } | ||
418 | |||
419 | if (scroll_mode_) { | ||
420 | EvaluateScroll(event.GetPosition()); | ||
421 | } | ||
422 | |||
423 | mouse_position_ = event.GetPosition(); | ||
424 | |||
425 | event.Skip(); | ||
426 | } | ||
427 | |||
428 | void SubwayMap::OnMouseScroll(wxMouseEvent &event) { | ||
429 | double new_zoom = zoom_; | ||
430 | if (event.GetWheelRotation() > 0) { | ||
431 | new_zoom = std::min(3.0, zoom_ + 0.25); | ||
432 | } else { | ||
433 | new_zoom = std::max(1.0, zoom_ - 0.25); | ||
434 | } | ||
435 | |||
436 | if (zoom_ != new_zoom) { | ||
437 | SetZoom(new_zoom, event.GetPosition()); | ||
438 | } | ||
439 | |||
440 | event.Skip(); | ||
441 | } | ||
442 | |||
443 | void SubwayMap::OnMouseLeave(wxMouseEvent &event) { | ||
444 | SetScrollSpeed(0, 0); | ||
445 | mouse_position_ = std::nullopt; | ||
446 | } | ||
447 | |||
448 | void SubwayMap::OnMouseClick(wxMouseEvent &event) { | ||
449 | bool finished = false; | ||
450 | |||
451 | if (actual_hover_) { | ||
452 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); | ||
453 | if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || | ||
454 | networks_.IsItemInNetwork(*hovered_item_)) { | ||
455 | if (actual_hover_ != hovered_item_) { | ||
456 | hovered_item_ = actual_hover_; | ||
457 | |||
458 | if (!hovered_item_) { | ||
459 | sticky_hover_ = false; | ||
460 | } | ||
461 | |||
462 | Refresh(); | ||
463 | } else { | ||
464 | sticky_hover_ = !sticky_hover_; | ||
465 | } | ||
466 | |||
467 | finished = true; | ||
468 | } | ||
469 | } | ||
470 | |||
471 | if (!finished) { | ||
472 | if (scroll_mode_) { | ||
473 | scroll_mode_ = false; | ||
474 | |||
475 | SetScrollSpeed(0, 0); | ||
476 | |||
477 | SetCursor(wxCURSOR_ARROW); | ||
478 | } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || | ||
479 | event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || | ||
480 | event.GetPosition().y < GetSize().GetHeight() / 6 || | ||
481 | event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { | ||
482 | scroll_mode_ = true; | ||
483 | |||
484 | EvaluateScroll(event.GetPosition()); | ||
485 | |||
486 | SetCursor(wxCURSOR_CROSS); | ||
487 | } else { | ||
488 | sticky_hover_ = false; | ||
489 | } | ||
490 | } | ||
491 | } | ||
492 | |||
493 | void SubwayMap::OnTimer(wxTimerEvent &event) { | ||
494 | SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); | ||
495 | Refresh(); | ||
496 | } | ||
497 | |||
498 | void SubwayMap::OnZoomSlide(wxCommandEvent &event) { | ||
499 | double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue(); | ||
500 | |||
501 | if (new_zoom != zoom_) { | ||
502 | SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}); | ||
503 | } | ||
504 | } | ||
505 | |||
506 | void SubwayMap::OnClickHelp(wxCommandEvent &event) { | ||
507 | wxMessageBox( | ||
508 | "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the " | ||
509 | "corner.\nClick on a side of the screen to start panning. It will follow " | ||
510 | "your mouse. Click again to stop.\nHover over a door to see the " | ||
511 | "requirements to open it.\nHover over a warp or active painting to see " | ||
512 | "what it is connected to.\nIn painting shuffle, paintings that have not " | ||
513 | "yet been checked will not show their connections.\nA green shaded owl " | ||
514 | "means that there is a painting entrance there.\nA red shaded owl means " | ||
515 | "that there are only painting exits there.\nClick on a door or " | ||
516 | "warp to make the popup stick until you click again.", | ||
517 | "Subway Map Help"); | ||
518 | } | ||
519 | |||
520 | void SubwayMap::Redraw() { | ||
521 | rendered_ = wxBitmap(map_image_); | ||
522 | |||
523 | wxMemoryDC dc; | ||
524 | dc.SelectObject(rendered_); | ||
525 | |||
526 | wxGCDC gcdc(dc); | ||
527 | |||
528 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
529 | ItemDrawType draw_type = ItemDrawType::kNone; | ||
530 | const wxBrush *brush_color = wxGREY_BRUSH; | ||
531 | std::optional<wxColour> shade_color; | ||
532 | |||
533 | if (AP_HasEarlyColorHallways() && | ||
534 | subway_item.special == "starting_room_paintings") { | ||
535 | draw_type = ItemDrawType::kOwl; | ||
536 | shade_color = wxColour(0, 255, 0, 128); | ||
537 | } else if (subway_item.special == "sun_painting") { | ||
538 | if (!AP_IsPilgrimageEnabled()) { | ||
539 | if (IsDoorOpen(*subway_item.door)) { | ||
540 | draw_type = ItemDrawType::kOwl; | ||
541 | shade_color = wxColour(0, 255, 0, 128); | ||
542 | } else { | ||
543 | draw_type = ItemDrawType::kBox; | ||
544 | brush_color = wxRED_BRUSH; | ||
545 | } | ||
546 | } | ||
547 | } else if (!subway_item.paintings.empty()) { | ||
548 | if (AP_IsPaintingShuffle()) { | ||
549 | bool has_checked_painting = false; | ||
550 | bool has_unchecked_painting = false; | ||
551 | bool has_mapped_painting = false; | ||
552 | bool has_codomain_painting = false; | ||
553 | |||
554 | for (const std::string &painting_id : subway_item.paintings) { | ||
555 | if (checked_paintings_.count(painting_id)) { | ||
556 | has_checked_painting = true; | ||
557 | |||
558 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
559 | has_mapped_painting = true; | ||
560 | } else if (AP_IsPaintingMappedTo(painting_id)) { | ||
561 | has_codomain_painting = true; | ||
562 | } | ||
563 | } else { | ||
564 | has_unchecked_painting = true; | ||
565 | } | ||
566 | } | ||
567 | |||
568 | if (has_unchecked_painting || has_mapped_painting || | ||
569 | has_codomain_painting) { | ||
570 | draw_type = ItemDrawType::kOwl; | ||
571 | |||
572 | if (has_checked_painting) { | ||
573 | if (has_mapped_painting) { | ||
574 | shade_color = wxColour(0, 255, 0, 128); | ||
575 | } else { | ||
576 | shade_color = wxColour(255, 0, 0, 128); | ||
577 | } | ||
578 | } | ||
579 | } | ||
580 | } else if (!subway_item.tags.empty()) { | ||
581 | draw_type = ItemDrawType::kOwl; | ||
582 | } | ||
583 | } else if (subway_item.door) { | ||
584 | draw_type = ItemDrawType::kBox; | ||
585 | |||
586 | if (IsDoorOpen(*subway_item.door)) { | ||
587 | brush_color = wxGREEN_BRUSH; | ||
588 | } else { | ||
589 | brush_color = wxRED_BRUSH; | ||
590 | } | ||
591 | } | ||
592 | |||
593 | wxPoint real_area_pos = {subway_item.x, subway_item.y}; | ||
594 | |||
595 | int real_area_size = | ||
596 | (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); | ||
597 | |||
598 | if (draw_type == ItemDrawType::kBox) { | ||
599 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | ||
600 | gcdc.SetBrush(*brush_color); | ||
601 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
602 | } else if (draw_type == ItemDrawType::kOwl) { | ||
603 | wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( | ||
604 | real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); | ||
605 | gcdc.DrawBitmap(owl_bitmap, real_area_pos); | ||
606 | |||
607 | if (shade_color) { | ||
608 | gcdc.SetBrush(wxBrush(*shade_color)); | ||
609 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
610 | } | ||
611 | } | ||
612 | } | ||
613 | } | ||
614 | |||
615 | void SubwayMap::SetUpHelpButton() { | ||
616 | help_button_->SetPosition({ | ||
617 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, | ||
618 | 15, | ||
619 | }); | ||
620 | } | ||
621 | |||
622 | void SubwayMap::EvaluateScroll(wxPoint pos) { | ||
623 | int scroll_x; | ||
624 | int scroll_y; | ||
625 | if (pos.x < GetSize().GetWidth() / 9) { | ||
626 | scroll_x = 20; | ||
627 | } else if (pos.x < GetSize().GetWidth() / 6) { | ||
628 | scroll_x = 5; | ||
629 | } else if (pos.x > 8 * GetSize().GetWidth() / 9) { | ||
630 | scroll_x = -20; | ||
631 | } else if (pos.x > 5 * GetSize().GetWidth() / 6) { | ||
632 | scroll_x = -5; | ||
633 | } else { | ||
634 | scroll_x = 0; | ||
635 | } | ||
636 | if (pos.y < GetSize().GetHeight() / 9) { | ||
637 | scroll_y = 20; | ||
638 | } else if (pos.y < GetSize().GetHeight() / 6) { | ||
639 | scroll_y = 5; | ||
640 | } else if (pos.y > 8 * GetSize().GetHeight() / 9) { | ||
641 | scroll_y = -20; | ||
642 | } else if (pos.y > 5 * GetSize().GetHeight() / 6) { | ||
643 | scroll_y = -5; | ||
644 | } else { | ||
645 | scroll_y = 0; | ||
646 | } | ||
647 | |||
648 | SetScrollSpeed(scroll_x, scroll_y); | ||
649 | } | ||
650 | |||
651 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { | ||
652 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
653 | map_image_.GetSize().GetWidth() + | ||
654 | zoom_x_), | ||
655 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
656 | map_image_.GetSize().GetWidth() + | ||
657 | zoom_y_)}; | ||
658 | } | ||
659 | |||
660 | wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { | ||
661 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
662 | map_image_.GetSize().GetWidth()), | ||
663 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
664 | map_image_.GetSize().GetWidth())}; | ||
665 | } | ||
666 | |||
667 | wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { | ||
668 | return { | ||
669 | std::clamp(static_cast<int>((pos.x - zoom_x_) * map_image_.GetWidth() / | ||
670 | render_width_ / zoom_), | ||
671 | 0, map_image_.GetWidth() - 1), | ||
672 | std::clamp(static_cast<int>((pos.y - zoom_y_) * map_image_.GetWidth() / | ||
673 | render_width_ / zoom_), | ||
674 | 0, map_image_.GetHeight() - 1)}; | ||
675 | } | ||
676 | |||
677 | void SubwayMap::SetZoomPos(wxPoint pos) { | ||
678 | if (render_width_ * zoom_ <= GetSize().GetWidth()) { | ||
679 | zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; | ||
680 | } else { | ||
681 | zoom_x_ = std::clamp( | ||
682 | pos.x, GetSize().GetWidth() - static_cast<int>(render_width_ * zoom_), | ||
683 | 0); | ||
684 | } | ||
685 | if (render_height_ * zoom_ <= GetSize().GetHeight()) { | ||
686 | zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; | ||
687 | } else { | ||
688 | zoom_y_ = std::clamp( | ||
689 | pos.y, GetSize().GetHeight() - static_cast<int>(render_height_ * zoom_), | ||
690 | 0); | ||
691 | } | ||
692 | } | ||
693 | |||
694 | void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { | ||
695 | bool should_timer = (scroll_x != 0 || scroll_y != 0); | ||
696 | if (should_timer != scroll_timer_->IsRunning()) { | ||
697 | if (should_timer) { | ||
698 | scroll_timer_->Start(1000 / 60); | ||
699 | } else { | ||
700 | scroll_timer_->Stop(); | ||
701 | } | ||
702 | } | ||
703 | |||
704 | scroll_x_ = scroll_x; | ||
705 | scroll_y_ = scroll_y; | ||
706 | } | ||
707 | |||
708 | void SubwayMap::SetZoom(double zoom, wxPoint static_point) { | ||
709 | wxPoint map_pos = RenderPosToMapPos(static_point); | ||
710 | zoom_ = zoom; | ||
711 | |||
712 | wxPoint virtual_pos = MapPosToVirtualPos(map_pos); | ||
713 | SetZoomPos(-(virtual_pos - static_point)); | ||
714 | |||
715 | Refresh(); | ||
716 | |||
717 | zoom_slider_->SetValue((zoom - 1.0) / 0.25); | ||
718 | } | ||
719 | |||
720 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { | ||
721 | const SubwayItem &subway_item = GD_GetSubwayItem(id); | ||
722 | return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y), | ||
723 | AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; | ||
724 | } | ||