diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2024-06-09 22:43:20 -0400 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2024-06-09 22:43:20 -0400 |
commit | 475b7a38f66071ad5713f6f00a49c4e1399e0613 (patch) | |
tree | 4dcb76d5bb9e1dbabe19dcbd0cc9676c31f715e6 /src | |
parent | 829bb6ba7fdbef5c4e6fb9e4eabc0c2f962325ae (diff) | |
parent | 14d075e02007aeb53dbadd6c629564ee467cd7b2 (diff) | |
download | lingo-ap-tracker-475b7a38f66071ad5713f6f00a49c4e1399e0613.tar.gz lingo-ap-tracker-475b7a38f66071ad5713f6f00a49c4e1399e0613.tar.bz2 lingo-ap-tracker-475b7a38f66071ad5713f6f00a49c4e1399e0613.zip |
Merge branch 'main' into panels
Diffstat (limited to 'src')
-rw-r--r-- | src/ap_state.cpp | 163 | ||||
-rw-r--r-- | src/ap_state.h | 11 | ||||
-rw-r--r-- | src/area_popup.cpp | 40 | ||||
-rw-r--r-- | src/game_data.cpp | 257 | ||||
-rw-r--r-- | src/game_data.h | 41 | ||||
-rw-r--r-- | src/logger.cpp | 32 | ||||
-rw-r--r-- | src/logger.h | 8 | ||||
-rw-r--r-- | src/main.cpp | 23 | ||||
-rw-r--r-- | src/network_set.cpp | 30 | ||||
-rw-r--r-- | src/network_set.h | 25 | ||||
-rw-r--r-- | src/subway_map.cpp | 728 | ||||
-rw-r--r-- | src/subway_map.h | 92 | ||||
-rw-r--r-- | src/tracker_frame.cpp | 82 | ||||
-rw-r--r-- | src/tracker_frame.h | 14 | ||||
-rw-r--r-- | src/tracker_panel.cpp | 24 | ||||
-rw-r--r-- | src/tracker_state.cpp | 405 | ||||
-rw-r--r-- | src/tracker_state.h | 11 | ||||
-rw-r--r-- | src/version.h | 12 |
18 files changed, 1739 insertions, 259 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 4fd241a..a7565cf 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp | |||
@@ -21,7 +21,6 @@ | |||
21 | #include <tuple> | 21 | #include <tuple> |
22 | 22 | ||
23 | #include "game_data.h" | 23 | #include "game_data.h" |
24 | #include "logger.h" | ||
25 | #include "tracker_frame.h" | 24 | #include "tracker_frame.h" |
26 | #include "tracker_state.h" | 25 | #include "tracker_state.h" |
27 | 26 | ||
@@ -72,11 +71,12 @@ struct APState { | |||
72 | bool sunwarp_shuffle = false; | 71 | bool sunwarp_shuffle = false; |
73 | 72 | ||
74 | std::map<std::string, std::string> painting_mapping; | 73 | std::map<std::string, std::string> painting_mapping; |
74 | std::set<std::string> painting_codomain; | ||
75 | std::map<int, SunwarpMapping> sunwarp_mapping; | 75 | std::map<int, SunwarpMapping> sunwarp_mapping; |
76 | 76 | ||
77 | void Connect(std::string server, std::string player, std::string password) { | 77 | void Connect(std::string server, std::string player, std::string password) { |
78 | if (!initialized) { | 78 | if (!initialized) { |
79 | TrackerLog("Initializing APState..."); | 79 | wxLogVerbose("Initializing APState..."); |
80 | 80 | ||
81 | std::thread([this]() { | 81 | std::thread([this]() { |
82 | for (;;) { | 82 | for (;;) { |
@@ -104,15 +104,16 @@ struct APState { | |||
104 | } | 104 | } |
105 | 105 | ||
106 | tracked_data_storage_keys.push_back("PlayerPos"); | 106 | tracked_data_storage_keys.push_back("PlayerPos"); |
107 | tracked_data_storage_keys.push_back("Paintings"); | ||
107 | 108 | ||
108 | initialized = true; | 109 | initialized = true; |
109 | } | 110 | } |
110 | 111 | ||
111 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | 112 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); |
112 | TrackerLog("Connecting to Archipelago server (" + server + ")..."); | 113 | wxLogStatus("Connecting to Archipelago server (%s)...", server); |
113 | 114 | ||
114 | { | 115 | { |
115 | TrackerLog("Destroying old AP client..."); | 116 | wxLogVerbose("Destroying old AP client..."); |
116 | 117 | ||
117 | std::lock_guard client_guard(client_mutex); | 118 | std::lock_guard client_guard(client_mutex); |
118 | 119 | ||
@@ -139,6 +140,7 @@ struct APState { | |||
139 | color_shuffle = false; | 140 | color_shuffle = false; |
140 | painting_shuffle = false; | 141 | painting_shuffle = false; |
141 | painting_mapping.clear(); | 142 | painting_mapping.clear(); |
143 | painting_codomain.clear(); | ||
142 | mastery_requirement = 21; | 144 | mastery_requirement = 21; |
143 | level_2_requirement = 223; | 145 | level_2_requirement = 223; |
144 | location_checks = kNORMAL_LOCATIONS; | 146 | location_checks = kNORMAL_LOCATIONS; |
@@ -151,16 +153,17 @@ struct APState { | |||
151 | sunwarp_shuffle = false; | 153 | sunwarp_shuffle = false; |
152 | sunwarp_mapping.clear(); | 154 | sunwarp_mapping.clear(); |
153 | 155 | ||
156 | std::mutex connection_mutex; | ||
154 | connected = false; | 157 | connected = false; |
155 | has_connection_result = false; | 158 | has_connection_result = false; |
156 | 159 | ||
157 | apclient->set_room_info_handler([this, player, password]() { | 160 | apclient->set_room_info_handler([this, player, password]() { |
158 | inventory.clear(); | 161 | inventory.clear(); |
159 | 162 | ||
160 | TrackerLog("Connected to Archipelago server. Authenticating as " + | 163 | wxLogStatus("Connected to Archipelago server. Authenticating as %s %s", |
161 | player + | 164 | player, |
162 | (password.empty() ? " without password" | 165 | (password.empty() ? "without password" |
163 | : " with password " + password)); | 166 | : "with password " + password)); |
164 | tracker_frame->SetStatusMessage( | 167 | tracker_frame->SetStatusMessage( |
165 | "Connected to Archipelago server. Authenticating..."); | 168 | "Connected to Archipelago server. Authenticating..."); |
166 | 169 | ||
@@ -172,23 +175,23 @@ struct APState { | |||
172 | [this](const std::list<int64_t>& locations) { | 175 | [this](const std::list<int64_t>& locations) { |
173 | for (const int64_t location_id : locations) { | 176 | for (const int64_t location_id : locations) { |
174 | checked_locations.insert(location_id); | 177 | checked_locations.insert(location_id); |
175 | TrackerLog("Location: " + std::to_string(location_id)); | 178 | wxLogVerbose("Location: %lld", location_id); |
176 | } | 179 | } |
177 | 180 | ||
178 | RefreshTracker(); | 181 | RefreshTracker(false); |
179 | }); | 182 | }); |
180 | 183 | ||
181 | apclient->set_slot_disconnected_handler([this]() { | 184 | apclient->set_slot_disconnected_handler([this]() { |
182 | tracker_frame->SetStatusMessage( | 185 | tracker_frame->SetStatusMessage( |
183 | "Disconnected from Archipelago. Attempting to reconnect..."); | 186 | "Disconnected from Archipelago. Attempting to reconnect..."); |
184 | TrackerLog( | 187 | wxLogStatus( |
185 | "Slot disconnected from Archipelago. Attempting to reconnect..."); | 188 | "Slot disconnected from Archipelago. Attempting to reconnect..."); |
186 | }); | 189 | }); |
187 | 190 | ||
188 | apclient->set_socket_disconnected_handler([this]() { | 191 | apclient->set_socket_disconnected_handler([this]() { |
189 | tracker_frame->SetStatusMessage( | 192 | tracker_frame->SetStatusMessage( |
190 | "Disconnected from Archipelago. Attempting to reconnect..."); | 193 | "Disconnected from Archipelago. Attempting to reconnect..."); |
191 | TrackerLog( | 194 | wxLogStatus( |
192 | "Socket disconnected from Archipelago. Attempting to reconnect..."); | 195 | "Socket disconnected from Archipelago. Attempting to reconnect..."); |
193 | }); | 196 | }); |
194 | 197 | ||
@@ -196,10 +199,10 @@ struct APState { | |||
196 | [this](const std::list<APClient::NetworkItem>& items) { | 199 | [this](const std::list<APClient::NetworkItem>& items) { |
197 | for (const APClient::NetworkItem& item : items) { | 200 | for (const APClient::NetworkItem& item : items) { |
198 | inventory[item.item]++; | 201 | inventory[item.item]++; |
199 | TrackerLog("Item: " + std::to_string(item.item)); | 202 | wxLogVerbose("Item: %lld", item.item); |
200 | } | 203 | } |
201 | 204 | ||
202 | RefreshTracker(); | 205 | RefreshTracker(false); |
203 | }); | 206 | }); |
204 | 207 | ||
205 | apclient->set_retrieved_handler( | 208 | apclient->set_retrieved_handler( |
@@ -208,20 +211,20 @@ struct APState { | |||
208 | HandleDataStorage(key, value); | 211 | HandleDataStorage(key, value); |
209 | } | 212 | } |
210 | 213 | ||
211 | RefreshTracker(); | 214 | RefreshTracker(false); |
212 | }); | 215 | }); |
213 | 216 | ||
214 | apclient->set_set_reply_handler([this](const std::string& key, | 217 | apclient->set_set_reply_handler([this](const std::string& key, |
215 | const nlohmann::json& value, | 218 | const nlohmann::json& value, |
216 | const nlohmann::json&) { | 219 | const nlohmann::json&) { |
217 | HandleDataStorage(key, value); | 220 | HandleDataStorage(key, value); |
218 | RefreshTracker(); | 221 | RefreshTracker(false); |
219 | }); | 222 | }); |
220 | 223 | ||
221 | apclient->set_slot_connected_handler([this]( | 224 | apclient->set_slot_connected_handler([this, &connection_mutex]( |
222 | const nlohmann::json& slot_data) { | 225 | const nlohmann::json& slot_data) { |
223 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | 226 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); |
224 | TrackerLog("Connected to Archipelago!"); | 227 | wxLogStatus("Connected to Archipelago!"); |
225 | 228 | ||
226 | data_storage_prefix = | 229 | data_storage_prefix = |
227 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; | 230 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; |
@@ -266,6 +269,7 @@ struct APState { | |||
266 | for (const auto& mapping_it : | 269 | for (const auto& mapping_it : |
267 | slot_data["painting_entrance_to_exit"].items()) { | 270 | slot_data["painting_entrance_to_exit"].items()) { |
268 | painting_mapping[mapping_it.key()] = mapping_it.value(); | 271 | painting_mapping[mapping_it.key()] = mapping_it.value(); |
272 | painting_codomain.insert(mapping_it.value()); | ||
269 | } | 273 | } |
270 | } | 274 | } |
271 | 275 | ||
@@ -281,11 +285,6 @@ struct APState { | |||
281 | } | 285 | } |
282 | } | 286 | } |
283 | 287 | ||
284 | connected = true; | ||
285 | has_connection_result = true; | ||
286 | |||
287 | RefreshTracker(); | ||
288 | |||
289 | std::list<std::string> corrected_keys; | 288 | std::list<std::string> corrected_keys; |
290 | for (const std::string& key : tracked_data_storage_keys) { | 289 | for (const std::string& key : tracked_data_storage_keys) { |
291 | corrected_keys.push_back(data_storage_prefix + key); | 290 | corrected_keys.push_back(data_storage_prefix + key); |
@@ -302,12 +301,23 @@ struct APState { | |||
302 | 301 | ||
303 | apclient->Get(corrected_keys); | 302 | apclient->Get(corrected_keys); |
304 | apclient->SetNotify(corrected_keys); | 303 | apclient->SetNotify(corrected_keys); |
304 | |||
305 | { | ||
306 | std::lock_guard connection_lock(connection_mutex); | ||
307 | if (!has_connection_result) { | ||
308 | connected = true; | ||
309 | has_connection_result = true; | ||
310 | } | ||
311 | } | ||
305 | }); | 312 | }); |
306 | 313 | ||
307 | apclient->set_slot_refused_handler( | 314 | apclient->set_slot_refused_handler( |
308 | [this](const std::list<std::string>& errors) { | 315 | [this, &connection_mutex](const std::list<std::string>& errors) { |
309 | connected = false; | 316 | { |
310 | has_connection_result = true; | 317 | std::lock_guard connection_lock(connection_mutex); |
318 | connected = false; | ||
319 | has_connection_result = true; | ||
320 | } | ||
311 | 321 | ||
312 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 322 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
313 | 323 | ||
@@ -336,7 +346,7 @@ struct APState { | |||
336 | } | 346 | } |
337 | 347 | ||
338 | std::string full_message = hatkirby::implode(error_messages, " "); | 348 | std::string full_message = hatkirby::implode(error_messages, " "); |
339 | TrackerLog(full_message); | 349 | wxLogError(wxString(full_message)); |
340 | 350 | ||
341 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); | 351 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); |
342 | }); | 352 | }); |
@@ -346,18 +356,29 @@ struct APState { | |||
346 | int timeout = 5000; // 5 seconds | 356 | int timeout = 5000; // 5 seconds |
347 | int interval = 100; | 357 | int interval = 100; |
348 | int remaining_loops = timeout / interval; | 358 | int remaining_loops = timeout / interval; |
349 | while (!has_connection_result) { | 359 | while (true) { |
350 | if (interval == 0) { | 360 | { |
351 | connected = false; | 361 | std::lock_guard connection_lock(connection_mutex); |
352 | has_connection_result = true; | 362 | if (has_connection_result) { |
363 | break; | ||
364 | } | ||
365 | } | ||
353 | 366 | ||
367 | if (interval == 0) { | ||
354 | DestroyClient(); | 368 | DestroyClient(); |
355 | 369 | ||
356 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 370 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
357 | 371 | wxLogStatus("Timeout while connecting to Archipelago server."); | |
358 | TrackerLog("Timeout while connecting to Archipelago server."); | ||
359 | wxMessageBox("Timeout while connecting to Archipelago server.", | 372 | wxMessageBox("Timeout while connecting to Archipelago server.", |
360 | "Connection failed", wxOK | wxICON_ERROR); | 373 | "Connection failed", wxOK | wxICON_ERROR); |
374 | |||
375 | { | ||
376 | std::lock_guard connection_lock(connection_mutex); | ||
377 | connected = false; | ||
378 | has_connection_result = true; | ||
379 | } | ||
380 | |||
381 | break; | ||
361 | } | 382 | } |
362 | 383 | ||
363 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | 384 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
@@ -366,7 +387,8 @@ struct APState { | |||
366 | } | 387 | } |
367 | 388 | ||
368 | if (connected) { | 389 | if (connected) { |
369 | RefreshTracker(); | 390 | ResetReachabilityRequirements(); |
391 | RefreshTracker(true); | ||
370 | } else { | 392 | } else { |
371 | client_active = false; | 393 | client_active = false; |
372 | } | 394 | } |
@@ -375,12 +397,11 @@ struct APState { | |||
375 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { | 397 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { |
376 | if (value.is_boolean()) { | 398 | if (value.is_boolean()) { |
377 | data_storage[key] = value.get<bool>(); | 399 | data_storage[key] = value.get<bool>(); |
378 | TrackerLog("Data storage " + key + " retrieved as " + | 400 | wxLogVerbose("Data storage %s retrieved as %s", key, |
379 | (value.get<bool>() ? "true" : "false")); | 401 | (value.get<bool>() ? "true" : "false")); |
380 | } else if (value.is_number()) { | 402 | } else if (value.is_number()) { |
381 | data_storage[key] = value.get<int>(); | 403 | data_storage[key] = value.get<int>(); |
382 | TrackerLog("Data storage " + key + " retrieved as " + | 404 | wxLogVerbose("Data storage %s retrieved as %d", key, value.get<int>()); |
383 | std::to_string(value.get<int>())); | ||
384 | } else if (value.is_object()) { | 405 | } else if (value.is_object()) { |
385 | if (key.ends_with("PlayerPos")) { | 406 | if (key.ends_with("PlayerPos")) { |
386 | auto map_value = value.get<std::map<std::string, int>>(); | 407 | auto map_value = value.get<std::map<std::string, int>>(); |
@@ -389,7 +410,7 @@ struct APState { | |||
389 | data_storage[key] = value.get<std::map<std::string, int>>(); | 410 | data_storage[key] = value.get<std::map<std::string, int>>(); |
390 | } | 411 | } |
391 | 412 | ||
392 | TrackerLog("Data storage " + key + " retrieved as dictionary"); | 413 | wxLogVerbose("Data storage %s retrieved as dictionary", key); |
393 | } else if (value.is_null()) { | 414 | } else if (value.is_null()) { |
394 | if (key.ends_with("PlayerPos")) { | 415 | if (key.ends_with("PlayerPos")) { |
395 | player_pos = std::nullopt; | 416 | player_pos = std::nullopt; |
@@ -397,7 +418,19 @@ struct APState { | |||
397 | data_storage.erase(key); | 418 | data_storage.erase(key); |
398 | } | 419 | } |
399 | 420 | ||
400 | TrackerLog("Data storage " + key + " retrieved as null"); | 421 | wxLogVerbose("Data storage %s retrieved as null", key); |
422 | } else if (value.is_array()) { | ||
423 | auto list_value = value.get<std::vector<std::string>>(); | ||
424 | |||
425 | if (key.ends_with("Paintings")) { | ||
426 | data_storage[key] = | ||
427 | std::set<std::string>(list_value.begin(), list_value.end()); | ||
428 | } else { | ||
429 | data_storage[key] = list_value; | ||
430 | } | ||
431 | |||
432 | wxLogVerbose("Data storage %s retrieved as list: [%s]", key, | ||
433 | hatkirby::implode(list_value, ", ")); | ||
401 | } | 434 | } |
402 | } | 435 | } |
403 | 436 | ||
@@ -420,22 +453,46 @@ struct APState { | |||
420 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | 453 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); |
421 | } | 454 | } |
422 | 455 | ||
423 | void RefreshTracker() { | 456 | const std::set<std::string>& GetCheckedPaintings() { |
424 | TrackerLog("Refreshing display..."); | 457 | std::string key = data_storage_prefix + "Paintings"; |
458 | if (!data_storage.count(key)) { | ||
459 | data_storage[key] = std::set<std::string>(); | ||
460 | } | ||
461 | |||
462 | return std::any_cast<const std::set<std::string>&>(data_storage.at(key)); | ||
463 | } | ||
464 | |||
465 | bool IsPaintingChecked(const std::string& painting_id) { | ||
466 | const auto& checked_paintings = GetCheckedPaintings(); | ||
467 | |||
468 | return checked_paintings.count(painting_id) || | ||
469 | (painting_mapping.count(painting_id) && | ||
470 | checked_paintings.count(painting_mapping.at(painting_id))); | ||
471 | } | ||
472 | |||
473 | void RefreshTracker(bool reset) { | ||
474 | wxLogVerbose("Refreshing display..."); | ||
425 | 475 | ||
426 | RecalculateReachability(); | 476 | RecalculateReachability(); |
427 | tracker_frame->UpdateIndicators(); | 477 | |
478 | if (reset) { | ||
479 | tracker_frame->ResetIndicators(); | ||
480 | } else { | ||
481 | tracker_frame->UpdateIndicators(); | ||
482 | } | ||
428 | } | 483 | } |
429 | 484 | ||
430 | int64_t GetItemId(const std::string& item_name) { | 485 | int64_t GetItemId(const std::string& item_name) { |
431 | int64_t ap_id = apclient->get_item_id(item_name); | 486 | int64_t ap_id = apclient->get_item_id(item_name); |
432 | if (ap_id == APClient::INVALID_NAME_ID) { | 487 | if (ap_id == APClient::INVALID_NAME_ID) { |
433 | TrackerLog("Could not find AP item ID for " + item_name); | 488 | wxLogError("Could not find AP item ID for %s", item_name); |
434 | } | 489 | } |
435 | 490 | ||
436 | return ap_id; | 491 | return ap_id; |
437 | } | 492 | } |
438 | 493 | ||
494 | std::string GetItemName(int id) { return apclient->get_item_name(id); } | ||
495 | |||
439 | bool HasReachedGoal() { | 496 | bool HasReachedGoal() { |
440 | return data_storage.count(victory_data_storage_key) && | 497 | return data_storage.count(victory_data_storage_key) && |
441 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == | 498 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == |
@@ -474,6 +531,10 @@ bool AP_HasItem(int item_id, int quantity) { | |||
474 | return GetState().HasItem(item_id, quantity); | 531 | return GetState().HasItem(item_id, quantity); |
475 | } | 532 | } |
476 | 533 | ||
534 | std::string AP_GetItemName(int item_id) { | ||
535 | return GetState().GetItemName(item_id); | ||
536 | } | ||
537 | |||
477 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } | 538 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } |
478 | 539 | ||
479 | bool AP_AreDoorsGrouped() { return GetState().group_doors; } | 540 | bool AP_AreDoorsGrouped() { return GetState().group_doors; } |
@@ -482,10 +543,22 @@ bool AP_IsColorShuffle() { return GetState().color_shuffle; } | |||
482 | 543 | ||
483 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } | 544 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } |
484 | 545 | ||
485 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | 546 | const std::map<std::string, std::string>& AP_GetPaintingMapping() { |
486 | return GetState().painting_mapping; | 547 | return GetState().painting_mapping; |
487 | } | 548 | } |
488 | 549 | ||
550 | bool AP_IsPaintingMappedTo(const std::string& painting_id) { | ||
551 | return GetState().painting_codomain.count(painting_id); | ||
552 | } | ||
553 | |||
554 | const std::set<std::string>& AP_GetCheckedPaintings() { | ||
555 | return GetState().GetCheckedPaintings(); | ||
556 | } | ||
557 | |||
558 | bool AP_IsPaintingChecked(const std::string& painting_id) { | ||
559 | return GetState().IsPaintingChecked(painting_id); | ||
560 | } | ||
561 | |||
489 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } | 562 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } |
490 | 563 | ||
491 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } | 564 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } |
diff --git a/src/ap_state.h b/src/ap_state.h index c514489..190b21f 100644 --- a/src/ap_state.h +++ b/src/ap_state.h | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | #include <map> | 4 | #include <map> |
5 | #include <optional> | 5 | #include <optional> |
6 | #include <set> | ||
6 | #include <string> | 7 | #include <string> |
7 | #include <tuple> | 8 | #include <tuple> |
8 | 9 | ||
@@ -48,6 +49,8 @@ bool AP_HasCheckedHuntPanel(int location_id); | |||
48 | 49 | ||
49 | bool AP_HasItem(int item_id, int quantity = 1); | 50 | bool AP_HasItem(int item_id, int quantity = 1); |
50 | 51 | ||
52 | std::string AP_GetItemName(int item_id); | ||
53 | |||
51 | DoorShuffleMode AP_GetDoorShuffleMode(); | 54 | DoorShuffleMode AP_GetDoorShuffleMode(); |
52 | 55 | ||
53 | bool AP_AreDoorsGrouped(); | 56 | bool AP_AreDoorsGrouped(); |
@@ -56,7 +59,13 @@ bool AP_IsColorShuffle(); | |||
56 | 59 | ||
57 | bool AP_IsPaintingShuffle(); | 60 | bool AP_IsPaintingShuffle(); |
58 | 61 | ||
59 | const std::map<std::string, std::string> AP_GetPaintingMapping(); | 62 | const std::map<std::string, std::string>& AP_GetPaintingMapping(); |
63 | |||
64 | bool AP_IsPaintingMappedTo(const std::string& painting_id); | ||
65 | |||
66 | const std::set<std::string>& AP_GetCheckedPaintings(); | ||
67 | |||
68 | bool AP_IsPaintingChecked(const std::string& painting_id); | ||
60 | 69 | ||
61 | int AP_GetMasteryRequirement(); | 70 | int AP_GetMasteryRequirement(); |
62 | 71 | ||
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 3b5d8d4..58d8897 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp | |||
@@ -1,5 +1,7 @@ | |||
1 | #include "area_popup.h" | 1 | #include "area_popup.h" |
2 | 2 | ||
3 | #include <wx/dcbuffer.h> | ||
4 | |||
3 | #include "ap_state.h" | 5 | #include "ap_state.h" |
4 | #include "game_data.h" | 6 | #include "game_data.h" |
5 | #include "global.h" | 7 | #include "global.h" |
@@ -8,6 +10,8 @@ | |||
8 | 10 | ||
9 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) | 11 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) |
10 | : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { | 12 | : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { |
13 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
14 | |||
11 | unchecked_eye_ = | 15 | unchecked_eye_ = |
12 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | 16 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), |
13 | wxBITMAP_TYPE_PNG) | 17 | wxBITMAP_TYPE_PNG) |
@@ -61,6 +65,19 @@ void AreaPopup::UpdateIndicators() { | |||
61 | } | 65 | } |
62 | } | 66 | } |
63 | 67 | ||
68 | if (AP_IsPaintingShuffle()) { | ||
69 | for (int painting_id : map_area.paintings) { | ||
70 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | ||
71 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. | ||
72 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
73 | acc_height += item_height; | ||
74 | |||
75 | if (item_extent.GetWidth() > col_width) { | ||
76 | col_width = item_extent.GetWidth(); | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
64 | int item_width = col_width + 10 + 32; | 81 | int item_width = col_width + 10 + 32; |
65 | int full_width = std::max(header_extent.GetWidth(), item_width) + 20; | 82 | int full_width = std::max(header_extent.GetWidth(), item_width) + 20; |
66 | 83 | ||
@@ -105,10 +122,31 @@ void AreaPopup::UpdateIndicators() { | |||
105 | 122 | ||
106 | cur_height += 10 + 32; | 123 | cur_height += 10 + 32; |
107 | } | 124 | } |
125 | |||
126 | if (AP_IsPaintingShuffle()) { | ||
127 | for (int painting_id : map_area.paintings) { | ||
128 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | ||
129 | bool checked = AP_IsPaintingChecked(painting.internal_id); | ||
130 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | ||
131 | |||
132 | mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); | ||
133 | |||
134 | bool reachable = IsPaintingReachable(painting_id); | ||
135 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | ||
136 | mem_dc.SetTextForeground(*text_color); | ||
137 | |||
138 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. | ||
139 | mem_dc.DrawText(painting.internal_id, | ||
140 | {10 + 32 + 10, | ||
141 | cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); | ||
142 | |||
143 | cur_height += 10 + 32; | ||
144 | } | ||
145 | } | ||
108 | } | 146 | } |
109 | 147 | ||
110 | void AreaPopup::OnPaint(wxPaintEvent& event) { | 148 | void AreaPopup::OnPaint(wxPaintEvent& event) { |
111 | wxPaintDC dc(this); | 149 | wxBufferedPaintDC dc(this); |
112 | PrepareDC(dc); | 150 | PrepareDC(dc); |
113 | dc.DrawBitmap(rendered_, 0, 0); | 151 | dc.DrawBitmap(rendered_, 0, 0); |
114 | 152 | ||
diff --git a/src/game_data.cpp b/src/game_data.cpp index 0567623..1ccf511 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp | |||
@@ -1,5 +1,11 @@ | |||
1 | #include "game_data.h" | 1 | #include "game_data.h" |
2 | 2 | ||
3 | #include <wx/wxprec.h> | ||
4 | |||
5 | #ifndef WX_PRECOMP | ||
6 | #include <wx/wx.h> | ||
7 | #endif | ||
8 | |||
3 | #include <hkutil/string.h> | 9 | #include <hkutil/string.h> |
4 | #include <yaml-cpp/yaml.h> | 10 | #include <yaml-cpp/yaml.h> |
5 | 11 | ||
@@ -7,7 +13,6 @@ | |||
7 | #include <sstream> | 13 | #include <sstream> |
8 | 14 | ||
9 | #include "global.h" | 15 | #include "global.h" |
10 | #include "logger.h" | ||
11 | 16 | ||
12 | namespace { | 17 | namespace { |
13 | 18 | ||
@@ -31,9 +36,7 @@ LingoColor GetColorForString(const std::string &str) { | |||
31 | } else if (str == "purple") { | 36 | } else if (str == "purple") { |
32 | return LingoColor::kPurple; | 37 | return LingoColor::kPurple; |
33 | } else { | 38 | } else { |
34 | std::ostringstream errmsg; | 39 | wxLogError("Invalid color: %s", str); |
35 | errmsg << "Invalid color: " << str; | ||
36 | TrackerLog(errmsg.str()); | ||
37 | 40 | ||
38 | return LingoColor::kNone; | 41 | return LingoColor::kNone; |
39 | } | 42 | } |
@@ -45,12 +48,15 @@ struct GameData { | |||
45 | std::vector<Panel> panels_; | 48 | std::vector<Panel> panels_; |
46 | std::vector<PanelDoor> panel_doors_; | 49 | std::vector<PanelDoor> panel_doors_; |
47 | std::vector<MapArea> map_areas_; | 50 | std::vector<MapArea> map_areas_; |
51 | std::vector<SubwayItem> subway_items_; | ||
52 | std::vector<PaintingExit> paintings_; | ||
48 | 53 | ||
49 | std::map<std::string, int> room_by_id_; | 54 | std::map<std::string, int> room_by_id_; |
50 | std::map<std::string, int> door_by_id_; | 55 | std::map<std::string, int> door_by_id_; |
51 | std::map<std::string, int> panel_by_id_; | 56 | std::map<std::string, int> panel_by_id_; |
52 | std::map<std::string, int> panel_doors_by_id_; | 57 | std::map<std::string, int> panel_doors_by_id_; |
53 | std::map<std::string, int> area_by_id_; | 58 | std::map<std::string, int> area_by_id_; |
59 | std::map<std::string, int> painting_by_id_; | ||
54 | 60 | ||
55 | std::vector<int> door_definition_order_; | 61 | std::vector<int> door_definition_order_; |
56 | 62 | ||
@@ -63,6 +69,9 @@ struct GameData { | |||
63 | 69 | ||
64 | std::vector<int> sunwarp_doors_; | 70 | std::vector<int> sunwarp_doors_; |
65 | 71 | ||
72 | std::map<std::string, int> subway_item_by_painting_; | ||
73 | std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; | ||
74 | |||
66 | bool loaded_area_data_ = false; | 75 | bool loaded_area_data_ = false; |
67 | std::set<std::string> malconfigured_areas_; | 76 | std::set<std::string> malconfigured_areas_; |
68 | 77 | ||
@@ -81,9 +90,7 @@ struct GameData { | |||
81 | ap_id_by_color_[GetColorForString(input_name)] = | 90 | ap_id_by_color_[GetColorForString(input_name)] = |
82 | ids_config["special_items"][color_name].as<int>(); | 91 | ids_config["special_items"][color_name].as<int>(); |
83 | } else { | 92 | } else { |
84 | std::ostringstream errmsg; | 93 | wxLogError("Missing AP item ID for color %s", color_name); |
85 | errmsg << "Missing AP item ID for color " << color_name; | ||
86 | TrackerLog(errmsg.str()); | ||
87 | } | 94 | } |
88 | }; | 95 | }; |
89 | 96 | ||
@@ -158,8 +165,9 @@ struct GameData { | |||
158 | } | 165 | } |
159 | default: { | 166 | default: { |
160 | // This shouldn't happen. | 167 | // This shouldn't happen. |
161 | std::cout << "Error reading game data: " << entrance_it | 168 | std::ostringstream formatted; |
162 | << std::endl; | 169 | formatted << entrance_it; |
170 | wxLogError("Error reading game data: %s", formatted.str()); | ||
163 | break; | 171 | break; |
164 | } | 172 | } |
165 | } | 173 | } |
@@ -256,6 +264,11 @@ struct GameData { | |||
256 | achievement_panels_.push_back(panel_id); | 264 | achievement_panels_.push_back(panel_id); |
257 | } | 265 | } |
258 | 266 | ||
267 | if (panel_it.second["location_name"]) { | ||
268 | panels_[panel_id].location_name = | ||
269 | panel_it.second["location_name"].as<std::string>(); | ||
270 | } | ||
271 | |||
259 | if (panel_it.second["hunt"]) { | 272 | if (panel_it.second["hunt"]) { |
260 | panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); | 273 | panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); |
261 | } | 274 | } |
@@ -279,10 +292,8 @@ struct GameData { | |||
279 | [panels_[panel_id].name] | 292 | [panels_[panel_id].name] |
280 | .as<int>(); | 293 | .as<int>(); |
281 | } else { | 294 | } else { |
282 | std::ostringstream errmsg; | 295 | wxLogError("Missing AP location ID for panel %s - %s", |
283 | errmsg << "Missing AP location ID for panel " | 296 | rooms_[room_id].name, panels_[panel_id].name); |
284 | << rooms_[room_id].name << " - " << panels_[panel_id].name; | ||
285 | TrackerLog(errmsg.str()); | ||
286 | } | 297 | } |
287 | } | 298 | } |
288 | } | 299 | } |
@@ -345,10 +356,8 @@ struct GameData { | |||
345 | [doors_[door_id].name]["item"] | 356 | [doors_[door_id].name]["item"] |
346 | .as<int>(); | 357 | .as<int>(); |
347 | } else { | 358 | } else { |
348 | std::ostringstream errmsg; | 359 | wxLogError("Missing AP item ID for door %s - %s", |
349 | errmsg << "Missing AP item ID for door " << rooms_[room_id].name | 360 | rooms_[room_id].name, doors_[door_id].name); |
350 | << " - " << doors_[door_id].name; | ||
351 | TrackerLog(errmsg.str()); | ||
352 | } | 361 | } |
353 | } | 362 | } |
354 | 363 | ||
@@ -362,10 +371,8 @@ struct GameData { | |||
362 | ids_config["door_groups"][doors_[door_id].group_name] | 371 | ids_config["door_groups"][doors_[door_id].group_name] |
363 | .as<int>(); | 372 | .as<int>(); |
364 | } else { | 373 | } else { |
365 | std::ostringstream errmsg; | 374 | wxLogError("Missing AP item ID for door group %s", |
366 | errmsg << "Missing AP item ID for door group " | 375 | doors_[door_id].group_name); |
367 | << doors_[door_id].group_name; | ||
368 | TrackerLog(errmsg.str()); | ||
369 | } | 376 | } |
370 | } | 377 | } |
371 | 378 | ||
@@ -375,13 +382,11 @@ struct GameData { | |||
375 | } else if (!door_it.second["skip_location"] && | 382 | } else if (!door_it.second["skip_location"] && |
376 | !door_it.second["event"]) { | 383 | !door_it.second["event"]) { |
377 | if (has_external_panels) { | 384 | if (has_external_panels) { |
378 | std::ostringstream errmsg; | 385 | wxLogError( |
379 | errmsg | 386 | "%s - %s has panels from other rooms but does not have an " |
380 | << rooms_[room_id].name << " - " << doors_[door_id].name | 387 | "explicit location name and is not marked skip_location or " |
381 | << " has panels from other rooms but does not have an " | 388 | "event", |
382 | "explicit " | 389 | rooms_[room_id].name, doors_[door_id].name); |
383 | "location name and is not marked skip_location or event"; | ||
384 | TrackerLog(errmsg.str()); | ||
385 | } | 390 | } |
386 | 391 | ||
387 | doors_[door_id].location_name = | 392 | doors_[door_id].location_name = |
@@ -401,10 +406,8 @@ struct GameData { | |||
401 | [doors_[door_id].name]["location"] | 406 | [doors_[door_id].name]["location"] |
402 | .as<int>(); | 407 | .as<int>(); |
403 | } else { | 408 | } else { |
404 | std::ostringstream errmsg; | 409 | wxLogError("Missing AP location ID for door %s - %s", |
405 | errmsg << "Missing AP location ID for door " | 410 | rooms_[room_id].name, doors_[door_id].name); |
406 | << rooms_[room_id].name << " - " << doors_[door_id].name; | ||
407 | TrackerLog(errmsg.str()); | ||
408 | } | 411 | } |
409 | } | 412 | } |
410 | 413 | ||
@@ -478,12 +481,14 @@ struct GameData { | |||
478 | 481 | ||
479 | if (room_it.second["paintings"]) { | 482 | if (room_it.second["paintings"]) { |
480 | for (const auto &painting : room_it.second["paintings"]) { | 483 | for (const auto &painting : room_it.second["paintings"]) { |
481 | std::string painting_id = painting["id"].as<std::string>(); | 484 | std::string internal_id = painting["id"].as<std::string>(); |
482 | room_by_painting_[painting_id] = room_id; | 485 | int painting_id = AddOrGetPainting(internal_id); |
486 | PaintingExit &painting_exit = paintings_[painting_id]; | ||
487 | painting_exit.room = room_id; | ||
483 | 488 | ||
484 | if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { | 489 | if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) && |
485 | PaintingExit painting_exit; | 490 | (!painting["disable"] || !painting["disable"].as<bool>())) { |
486 | painting_exit.id = painting_id; | 491 | painting_exit.entrance = true; |
487 | 492 | ||
488 | if (painting["required_door"]) { | 493 | if (painting["required_door"]) { |
489 | std::string rd_room = rooms_[room_id].name; | 494 | std::string rd_room = rooms_[room_id].name; |
@@ -494,9 +499,9 @@ struct GameData { | |||
494 | painting_exit.door = AddOrGetDoor( | 499 | painting_exit.door = AddOrGetDoor( |
495 | rd_room, painting["required_door"]["door"].as<std::string>()); | 500 | rd_room, painting["required_door"]["door"].as<std::string>()); |
496 | } | 501 | } |
497 | |||
498 | rooms_[room_id].paintings.push_back(painting_exit); | ||
499 | } | 502 | } |
503 | |||
504 | rooms_[room_id].paintings.push_back(painting_exit.id); | ||
500 | } | 505 | } |
501 | } | 506 | } |
502 | 507 | ||
@@ -523,10 +528,8 @@ struct GameData { | |||
523 | progressive_item_id = | 528 | progressive_item_id = |
524 | ids_config["progression"][progressive_item_name].as<int>(); | 529 | ids_config["progression"][progressive_item_name].as<int>(); |
525 | } else { | 530 | } else { |
526 | std::ostringstream errmsg; | 531 | wxLogError("Missing AP item ID for progressive item %s", |
527 | errmsg << "Missing AP item ID for progressive item " | 532 | progressive_item_name); |
528 | << progressive_item_name; | ||
529 | TrackerLog(errmsg.str()); | ||
530 | } | 533 | } |
531 | 534 | ||
532 | if (progression_it.second["doors"]) { | 535 | if (progression_it.second["doors"]) { |
@@ -600,8 +603,21 @@ struct GameData { | |||
600 | std::string room_name = rooms_[room_id].name; | 603 | std::string room_name = rooms_[room_id].name; |
601 | 604 | ||
602 | std::string area_name = room_name; | 605 | std::string area_name = room_name; |
603 | if (fold_areas.count(room_name)) { | 606 | std::string section_name = panel.name; |
604 | int fold_area_id = fold_areas[room_name]; | 607 | std::string location_name = room_name + " - " + panel.name; |
608 | |||
609 | if (!panel.location_name.empty()) { | ||
610 | location_name = panel.location_name; | ||
611 | |||
612 | size_t divider_pos = location_name.find(" - "); | ||
613 | if (divider_pos != std::string::npos) { | ||
614 | area_name = location_name.substr(0, divider_pos); | ||
615 | section_name = location_name.substr(divider_pos + 3); | ||
616 | } | ||
617 | } | ||
618 | |||
619 | if (fold_areas.count(area_name)) { | ||
620 | int fold_area_id = fold_areas[area_name]; | ||
605 | area_name = map_areas_[fold_area_id].name; | 621 | area_name = map_areas_[fold_area_id].name; |
606 | } | 622 | } |
607 | 623 | ||
@@ -617,15 +633,15 @@ struct GameData { | |||
617 | MapArea &map_area = map_areas_[area_id]; | 633 | MapArea &map_area = map_areas_[area_id]; |
618 | // room field should be the original room ID | 634 | // room field should be the original room ID |
619 | map_area.locations.push_back( | 635 | map_area.locations.push_back( |
620 | {.name = panel.name, | 636 | {.name = section_name, |
621 | .ap_location_name = room_name + " - " + panel.name, | 637 | .ap_location_name = location_name, |
622 | .ap_location_id = panel.ap_location_id, | 638 | .ap_location_id = panel.ap_location_id, |
623 | .room = panel.room, | 639 | .room = panel.room, |
624 | .panels = {panel.id}, | 640 | .panels = {panel.id}, |
625 | .classification = classification, | 641 | .classification = classification, |
626 | .hunt = panel.hunt}); | 642 | .hunt = panel.hunt}); |
627 | locations_by_name[map_area.locations.back().ap_location_name] = { | 643 | locations_by_name[location_name] = {area_id, |
628 | area_id, map_area.locations.size() - 1}; | 644 | map_area.locations.size() - 1}; |
629 | } | 645 | } |
630 | 646 | ||
631 | for (int door_id : door_definition_order_) { | 647 | for (int door_id : door_definition_order_) { |
@@ -679,11 +695,101 @@ struct GameData { | |||
679 | } | 695 | } |
680 | } | 696 | } |
681 | 697 | ||
698 | for (const Room &room : rooms_) { | ||
699 | std::string area_name = room.name; | ||
700 | if (fold_areas.count(room.name)) { | ||
701 | int fold_area_id = fold_areas[room.name]; | ||
702 | area_name = map_areas_[fold_area_id].name; | ||
703 | } | ||
704 | |||
705 | if (!room.paintings.empty()) { | ||
706 | int area_id = AddOrGetArea(area_name); | ||
707 | MapArea &map_area = map_areas_[area_id]; | ||
708 | |||
709 | for (int painting_id : room.paintings) { | ||
710 | const PaintingExit &painting_obj = paintings_.at(painting_id); | ||
711 | if (painting_obj.entrance) { | ||
712 | map_area.paintings.push_back(painting_id); | ||
713 | } | ||
714 | } | ||
715 | } | ||
716 | } | ||
717 | |||
682 | // Report errors. | 718 | // Report errors. |
683 | for (const std::string &area : malconfigured_areas_) { | 719 | for (const std::string &area : malconfigured_areas_) { |
684 | std::ostringstream errstr; | 720 | wxLogError("Area data not found for: %s", area); |
685 | errstr << "Area data not found for: " << area; | 721 | } |
686 | TrackerLog(errstr.str()); | 722 | |
723 | // Read in subway items. | ||
724 | YAML::Node subway_config = | ||
725 | YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); | ||
726 | for (const auto &subway_it : subway_config) { | ||
727 | SubwayItem subway_item; | ||
728 | subway_item.id = subway_items_.size(); | ||
729 | subway_item.x = subway_it["pos"][0].as<int>(); | ||
730 | subway_item.y = subway_it["pos"][1].as<int>(); | ||
731 | |||
732 | if (subway_it["door"]) { | ||
733 | subway_item.door = AddOrGetDoor(subway_it["room"].as<std::string>(), | ||
734 | subway_it["door"].as<std::string>()); | ||
735 | } | ||
736 | |||
737 | if (subway_it["paintings"]) { | ||
738 | for (const auto &painting_it : subway_it["paintings"]) { | ||
739 | std::string painting_id = painting_it.as<std::string>(); | ||
740 | |||
741 | subway_item.paintings.push_back(painting_id); | ||
742 | subway_item_by_painting_[painting_id] = subway_item.id; | ||
743 | } | ||
744 | } | ||
745 | |||
746 | if (subway_it["tags"]) { | ||
747 | for (const auto &tag_it : subway_it["tags"]) { | ||
748 | subway_item.tags.push_back(tag_it.as<std::string>()); | ||
749 | } | ||
750 | } | ||
751 | |||
752 | if (subway_it["sunwarp"]) { | ||
753 | SubwaySunwarp sunwarp; | ||
754 | sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); | ||
755 | |||
756 | std::string sunwarp_type = | ||
757 | subway_it["sunwarp"]["type"].as<std::string>(); | ||
758 | if (sunwarp_type == "final") { | ||
759 | sunwarp.type = SubwaySunwarpType::kFinal; | ||
760 | } else if (sunwarp_type == "exit") { | ||
761 | sunwarp.type = SubwaySunwarpType::kExit; | ||
762 | } else { | ||
763 | sunwarp.type = SubwaySunwarpType::kEnter; | ||
764 | } | ||
765 | |||
766 | subway_item.sunwarp = sunwarp; | ||
767 | |||
768 | subway_item_by_sunwarp_[sunwarp] = subway_item.id; | ||
769 | |||
770 | subway_item.door = | ||
771 | AddOrGetDoor("Sunwarps", std::to_string(sunwarp.dots) + " Sunwarp"); | ||
772 | } | ||
773 | |||
774 | if (subway_it["special"]) { | ||
775 | subway_item.special = subway_it["special"].as<std::string>(); | ||
776 | } | ||
777 | |||
778 | subway_items_.push_back(subway_item); | ||
779 | } | ||
780 | |||
781 | // Find singleton subway tags. | ||
782 | std::map<std::string, std::set<int>> subway_tags; | ||
783 | for (const SubwayItem &subway_item : subway_items_) { | ||
784 | for (const std::string &tag : subway_item.tags) { | ||
785 | subway_tags[tag].insert(subway_item.id); | ||
786 | } | ||
787 | } | ||
788 | |||
789 | for (const auto &[tag, items] : subway_tags) { | ||
790 | if (items.size() == 1) { | ||
791 | wxLogWarning("Singleton subway item tag: %s", tag); | ||
792 | } | ||
687 | } | 793 | } |
688 | } | 794 | } |
689 | 795 | ||
@@ -700,8 +806,10 @@ struct GameData { | |||
700 | std::string full_name = room + " - " + door; | 806 | std::string full_name = room + " - " + door; |
701 | 807 | ||
702 | if (!door_by_id_.count(full_name)) { | 808 | if (!door_by_id_.count(full_name)) { |
809 | int door_id = doors_.size(); | ||
703 | door_by_id_[full_name] = doors_.size(); | 810 | door_by_id_[full_name] = doors_.size(); |
704 | doors_.push_back({.room = AddOrGetRoom(room), .name = door}); | 811 | doors_.push_back( |
812 | {.id = door_id, .room = AddOrGetRoom(room), .name = door}); | ||
705 | } | 813 | } |
706 | 814 | ||
707 | return door_by_id_[full_name]; | 815 | return door_by_id_[full_name]; |
@@ -745,6 +853,16 @@ struct GameData { | |||
745 | 853 | ||
746 | return area_by_id_[area]; | 854 | return area_by_id_[area]; |
747 | } | 855 | } |
856 | |||
857 | int AddOrGetPainting(std::string internal_id) { | ||
858 | if (!painting_by_id_.count(internal_id)) { | ||
859 | int painting_id = paintings_.size(); | ||
860 | painting_by_id_[internal_id] = painting_id; | ||
861 | paintings_.push_back({.id = painting_id, .internal_id = internal_id}); | ||
862 | } | ||
863 | |||
864 | return painting_by_id_[internal_id]; | ||
865 | } | ||
748 | }; | 866 | }; |
749 | 867 | ||
750 | GameData &GetState() { | 868 | GameData &GetState() { |
@@ -754,6 +872,10 @@ GameData &GetState() { | |||
754 | 872 | ||
755 | } // namespace | 873 | } // namespace |
756 | 874 | ||
875 | bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { | ||
876 | return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); | ||
877 | } | ||
878 | |||
757 | const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; } | 879 | const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; } |
758 | 880 | ||
759 | const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } | 881 | const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } |
@@ -780,8 +902,12 @@ const Panel &GD_GetPanel(int panel_id) { | |||
780 | return GetState().panels_.at(panel_id); | 902 | return GetState().panels_.at(panel_id); |
781 | } | 903 | } |
782 | 904 | ||
783 | int GD_GetRoomForPainting(const std::string &painting_id) { | 905 | const PaintingExit &GD_GetPaintingExit(int painting_id) { |
784 | return GetState().room_by_painting_.at(painting_id); | 906 | return GetState().paintings_.at(painting_id); |
907 | } | ||
908 | |||
909 | int GD_GetPaintingByName(const std::string &name) { | ||
910 | return GetState().painting_by_id_.at(name); | ||
785 | } | 911 | } |
786 | 912 | ||
787 | const std::vector<int> &GD_GetAchievementPanels() { | 913 | const std::vector<int> &GD_GetAchievementPanels() { |
@@ -799,3 +925,24 @@ const std::vector<int> &GD_GetSunwarpDoors() { | |||
799 | int GD_GetRoomForSunwarp(int index) { | 925 | int GD_GetRoomForSunwarp(int index) { |
800 | return GetState().room_by_sunwarp_.at(index); | 926 | return GetState().room_by_sunwarp_.at(index); |
801 | } | 927 | } |
928 | |||
929 | const std::vector<SubwayItem> &GD_GetSubwayItems() { | ||
930 | return GetState().subway_items_; | ||
931 | } | ||
932 | |||
933 | const SubwayItem &GD_GetSubwayItem(int id) { | ||
934 | return GetState().subway_items_.at(id); | ||
935 | } | ||
936 | |||
937 | int GD_GetSubwayItemForPainting(const std::string &painting_id) { | ||
938 | #ifndef NDEBUG | ||
939 | if (!GetState().subway_item_by_painting_.count(painting_id)) { | ||
940 | wxLogError("No subway item for painting %s", painting_id); | ||
941 | } | ||
942 | #endif | ||
943 | return GetState().subway_item_by_painting_.at(painting_id); | ||
944 | } | ||
945 | |||
946 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { | ||
947 | return GetState().subway_item_by_sunwarp_.at(sunwarp); | ||
948 | } | ||
diff --git a/src/game_data.h b/src/game_data.h index 09824d7..aca4c3d 100644 --- a/src/game_data.h +++ b/src/game_data.h | |||
@@ -50,6 +50,7 @@ struct Panel { | |||
50 | bool exclude_reduce = false; | 50 | bool exclude_reduce = false; |
51 | bool achievement = false; | 51 | bool achievement = false; |
52 | std::string achievement_name; | 52 | std::string achievement_name; |
53 | std::string location_name; | ||
53 | bool non_counting = false; | 54 | bool non_counting = false; |
54 | int ap_location_id = -1; | 55 | int ap_location_id = -1; |
55 | bool hunt = false; | 56 | bool hunt = false; |
@@ -63,6 +64,7 @@ struct ProgressiveRequirement { | |||
63 | }; | 64 | }; |
64 | 65 | ||
65 | struct Door { | 66 | struct Door { |
67 | int id; | ||
66 | int room; | 68 | int room; |
67 | std::string name; | 69 | std::string name; |
68 | std::string location_name; | 70 | std::string location_name; |
@@ -93,14 +95,17 @@ struct Exit { | |||
93 | }; | 95 | }; |
94 | 96 | ||
95 | struct PaintingExit { | 97 | struct PaintingExit { |
96 | std::string id; | 98 | int id; |
99 | int room; | ||
100 | std::string internal_id; | ||
97 | std::optional<int> door; | 101 | std::optional<int> door; |
102 | bool entrance = false; | ||
98 | }; | 103 | }; |
99 | 104 | ||
100 | struct Room { | 105 | struct Room { |
101 | std::string name; | 106 | std::string name; |
102 | std::vector<Exit> exits; | 107 | std::vector<Exit> exits; |
103 | std::vector<PaintingExit> paintings; | 108 | std::vector<int> paintings; |
104 | std::vector<int> sunwarps; | 109 | std::vector<int> sunwarps; |
105 | std::vector<int> panels; | 110 | std::vector<int> panels; |
106 | }; | 111 | }; |
@@ -119,12 +124,37 @@ struct MapArea { | |||
119 | int id; | 124 | int id; |
120 | std::string name; | 125 | std::string name; |
121 | std::vector<Location> locations; | 126 | std::vector<Location> locations; |
127 | std::vector<int> paintings; | ||
122 | int map_x; | 128 | int map_x; |
123 | int map_y; | 129 | int map_y; |
124 | int classification = 0; | 130 | int classification = 0; |
125 | bool hunt = false; | 131 | bool hunt = false; |
126 | }; | 132 | }; |
127 | 133 | ||
134 | enum class SubwaySunwarpType { | ||
135 | kEnter, | ||
136 | kExit, | ||
137 | kFinal | ||
138 | }; | ||
139 | |||
140 | struct SubwaySunwarp { | ||
141 | int dots; | ||
142 | SubwaySunwarpType type; | ||
143 | |||
144 | bool operator<(const SubwaySunwarp& rhs) const; | ||
145 | }; | ||
146 | |||
147 | struct SubwayItem { | ||
148 | int id; | ||
149 | int x; | ||
150 | int y; | ||
151 | std::optional<int> door; | ||
152 | std::vector<std::string> paintings; | ||
153 | std::vector<std::string> tags; | ||
154 | std::optional<SubwaySunwarp> sunwarp; | ||
155 | std::optional<std::string> special; | ||
156 | }; | ||
157 | |||
128 | const std::vector<MapArea>& GD_GetMapAreas(); | 158 | const std::vector<MapArea>& GD_GetMapAreas(); |
129 | const MapArea& GD_GetMapArea(int id); | 159 | const MapArea& GD_GetMapArea(int id); |
130 | int GD_GetRoomByName(const std::string& name); | 160 | int GD_GetRoomByName(const std::string& name); |
@@ -134,10 +164,15 @@ const Door& GD_GetDoor(int door_id); | |||
134 | int GD_GetDoorByName(const std::string& name); | 164 | int GD_GetDoorByName(const std::string& name); |
135 | const Panel& GD_GetPanel(int panel_id); | 165 | const Panel& GD_GetPanel(int panel_id); |
136 | const PanelDoor& GD_GetPanelDoor(int panel_door_id); | 166 | const PanelDoor& GD_GetPanelDoor(int panel_door_id); |
137 | int GD_GetRoomForPainting(const std::string& painting_id); | 167 | const PaintingExit& GD_GetPaintingExit(int painting_id); |
168 | int GD_GetPaintingByName(const std::string& name); | ||
138 | const std::vector<int>& GD_GetAchievementPanels(); | 169 | const std::vector<int>& GD_GetAchievementPanels(); |
139 | int GD_GetItemIdForColor(LingoColor color); | 170 | int GD_GetItemIdForColor(LingoColor color); |
140 | const std::vector<int>& GD_GetSunwarpDoors(); | 171 | const std::vector<int>& GD_GetSunwarpDoors(); |
141 | int GD_GetRoomForSunwarp(int index); | 172 | int GD_GetRoomForSunwarp(int index); |
173 | const std::vector<SubwayItem>& GD_GetSubwayItems(); | ||
174 | const SubwayItem& GD_GetSubwayItem(int id); | ||
175 | int GD_GetSubwayItemForPainting(const std::string& painting_id); | ||
176 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); | ||
142 | 177 | ||
143 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ | 178 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ |
diff --git a/src/logger.cpp b/src/logger.cpp deleted file mode 100644 index 4b722c8..0000000 --- a/src/logger.cpp +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | #include "logger.h" | ||
2 | |||
3 | #include <chrono> | ||
4 | #include <fstream> | ||
5 | #include <mutex> | ||
6 | |||
7 | #include "global.h" | ||
8 | |||
9 | namespace { | ||
10 | |||
11 | class Logger { | ||
12 | public: | ||
13 | Logger() : logfile_(GetAbsolutePath("debug.log")) {} | ||
14 | |||
15 | void LogLine(const std::string& text) { | ||
16 | std::lock_guard guard(file_mutex_); | ||
17 | logfile_ << "[" << std::chrono::system_clock::now() << "] " << text | ||
18 | << std::endl; | ||
19 | logfile_.flush(); | ||
20 | } | ||
21 | |||
22 | private: | ||
23 | std::ofstream logfile_; | ||
24 | std::mutex file_mutex_; | ||
25 | }; | ||
26 | |||
27 | } // namespace | ||
28 | |||
29 | void TrackerLog(const std::string& text) { | ||
30 | static Logger* instance = new Logger(); | ||
31 | instance->LogLine(text); | ||
32 | } | ||
diff --git a/src/logger.h b/src/logger.h deleted file mode 100644 index db9bb49..0000000 --- a/src/logger.h +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | #ifndef LOGGER_H_6E7B9594 | ||
2 | #define LOGGER_H_6E7B9594 | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | void TrackerLog(const std::string& text); | ||
7 | |||
8 | #endif /* end of include guard: LOGGER_H_6E7B9594 */ | ||
diff --git a/src/main.cpp b/src/main.cpp index fe9aceb..b327b25 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
@@ -4,18 +4,41 @@ | |||
4 | #include <wx/wx.h> | 4 | #include <wx/wx.h> |
5 | #endif | 5 | #endif |
6 | 6 | ||
7 | #include <fstream> | ||
8 | |||
9 | #include "global.h" | ||
7 | #include "tracker_config.h" | 10 | #include "tracker_config.h" |
8 | #include "tracker_frame.h" | 11 | #include "tracker_frame.h" |
9 | 12 | ||
13 | static std::ofstream* logfile; | ||
14 | |||
10 | class TrackerApp : public wxApp { | 15 | class TrackerApp : public wxApp { |
11 | public: | 16 | public: |
12 | virtual bool OnInit() { | 17 | virtual bool OnInit() { |
18 | logfile = new std::ofstream(GetAbsolutePath("debug.log")); | ||
19 | wxLog::SetActiveTarget(new wxLogStream(logfile)); | ||
20 | wxLog::SetVerbose(true); | ||
21 | |||
22 | #ifndef NDEBUG | ||
23 | wxLog::SetActiveTarget(new wxLogWindow(nullptr, "Debug Log")); | ||
24 | #endif | ||
25 | |||
13 | GetTrackerConfig().Load(); | 26 | GetTrackerConfig().Load(); |
14 | 27 | ||
15 | TrackerFrame *frame = new TrackerFrame(); | 28 | TrackerFrame *frame = new TrackerFrame(); |
16 | frame->Show(true); | 29 | frame->Show(true); |
17 | return true; | 30 | return true; |
18 | } | 31 | } |
32 | |||
33 | bool OnExceptionInMainLoop() override { | ||
34 | try { | ||
35 | throw; | ||
36 | } catch (const std::exception& ex) { | ||
37 | wxLogError(ex.what()); | ||
38 | } | ||
39 | |||
40 | return false; | ||
41 | } | ||
19 | }; | 42 | }; |
20 | 43 | ||
21 | wxIMPLEMENT_APP(TrackerApp); | 44 | wxIMPLEMENT_APP(TrackerApp); |
diff --git a/src/network_set.cpp b/src/network_set.cpp new file mode 100644 index 0000000..6d2a098 --- /dev/null +++ b/src/network_set.cpp | |||
@@ -0,0 +1,30 @@ | |||
1 | #include "network_set.h" | ||
2 | |||
3 | void NetworkSet::Clear() { | ||
4 | network_by_item_.clear(); | ||
5 | } | ||
6 | |||
7 | void NetworkSet::AddLink(int id1, int id2) { | ||
8 | if (id2 > id1) { | ||
9 | // Make sure id1 < id2 | ||
10 | std::swap(id1, id2); | ||
11 | } | ||
12 | |||
13 | if (!network_by_item_.count(id1)) { | ||
14 | network_by_item_[id1] = {}; | ||
15 | } | ||
16 | if (!network_by_item_.count(id2)) { | ||
17 | network_by_item_[id2] = {}; | ||
18 | } | ||
19 | |||
20 | network_by_item_[id1].insert({id1, id2}); | ||
21 | network_by_item_[id2].insert({id1, id2}); | ||
22 | } | ||
23 | |||
24 | bool NetworkSet::IsItemInNetwork(int id) const { | ||
25 | return network_by_item_.count(id); | ||
26 | } | ||
27 | |||
28 | const std::set<std::pair<int, int>>& NetworkSet::GetNetworkGraph(int id) const { | ||
29 | return network_by_item_.at(id); | ||
30 | } | ||
diff --git a/src/network_set.h b/src/network_set.h new file mode 100644 index 0000000..e6f0c07 --- /dev/null +++ b/src/network_set.h | |||
@@ -0,0 +1,25 @@ | |||
1 | #ifndef NETWORK_SET_H_3036B8E3 | ||
2 | #define NETWORK_SET_H_3036B8E3 | ||
3 | |||
4 | #include <map> | ||
5 | #include <optional> | ||
6 | #include <set> | ||
7 | #include <utility> | ||
8 | #include <vector> | ||
9 | |||
10 | class NetworkSet { | ||
11 | public: | ||
12 | void Clear(); | ||
13 | |||
14 | void AddLink(int id1, int id2); | ||
15 | |||
16 | bool IsItemInNetwork(int id) const; | ||
17 | |||
18 | const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; | ||
19 | |||
20 | private: | ||
21 | |||
22 | std::map<int, std::set<std::pair<int, int>>> network_by_item_; | ||
23 | }; | ||
24 | |||
25 | #endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ | ||
diff --git a/src/subway_map.cpp b/src/subway_map.cpp new file mode 100644 index 0000000..408c4f0 --- /dev/null +++ b/src/subway_map.cpp | |||
@@ -0,0 +1,728 @@ | |||
1 | #include "subway_map.h" | ||
2 | |||
3 | #include <wx/dcbuffer.h> | ||
4 | #include <wx/dcgraph.h> | ||
5 | |||
6 | #include <sstream> | ||
7 | |||
8 | #include "ap_state.h" | ||
9 | #include "game_data.h" | ||
10 | #include "global.h" | ||
11 | #include "tracker_state.h" | ||
12 | |||
13 | constexpr int AREA_ACTUAL_SIZE = 21; | ||
14 | constexpr int OWL_ACTUAL_SIZE = 32; | ||
15 | |||
16 | enum class ItemDrawType { kNone, kBox, kOwl }; | ||
17 | |||
18 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | ||
19 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
20 | |||
21 | map_image_ = | ||
22 | wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG); | ||
23 | if (!map_image_.IsOk()) { | ||
24 | return; | ||
25 | } | ||
26 | |||
27 | owl_image_ = | ||
28 | wxImage(GetAbsolutePath("assets/owl.png").c_str(), wxBITMAP_TYPE_PNG); | ||
29 | if (!owl_image_.IsOk()) { | ||
30 | return; | ||
31 | } | ||
32 | |||
33 | unchecked_eye_ = | ||
34 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | ||
35 | wxBITMAP_TYPE_PNG) | ||
36 | .Scale(32, 32)); | ||
37 | checked_eye_ = wxBitmap( | ||
38 | wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) | ||
39 | .Scale(32, 32)); | ||
40 | |||
41 | tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>( | ||
42 | quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()), | ||
43 | static_cast<float>(map_image_.GetHeight())}); | ||
44 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
45 | tree_->add(subway_item.id); | ||
46 | } | ||
47 | |||
48 | Redraw(); | ||
49 | |||
50 | scroll_timer_ = new wxTimer(this); | ||
51 | |||
52 | Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); | ||
53 | Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); | ||
54 | Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); | ||
55 | Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); | ||
56 | Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); | ||
57 | Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); | ||
58 | |||
59 | zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); | ||
60 | zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); | ||
61 | |||
62 | help_button_ = new wxButton(this, wxID_ANY, "Help"); | ||
63 | help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); | ||
64 | SetUpHelpButton(); | ||
65 | } | ||
66 | |||
67 | void SubwayMap::OnConnect() { | ||
68 | networks_.Clear(); | ||
69 | |||
70 | std::map<std::string, std::vector<int>> tagged; | ||
71 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
72 | if (AP_HasEarlyColorHallways() && | ||
73 | (subway_item.special == "starting_room_paintings" || | ||
74 | subway_item.special == "early_color_hallways")) { | ||
75 | tagged["early_color_hallways"].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::ostringstream tag; | ||
89 | tag << "sunwarp" << subway_item.sunwarp->dots; | ||
90 | |||
91 | tagged[tag.str()].push_back(subway_item.id); | ||
92 | } | ||
93 | |||
94 | if (!AP_IsPilgrimageEnabled() && | ||
95 | (subway_item.special == "sun_painting" || | ||
96 | subway_item.special == "sun_painting_exit")) { | ||
97 | tagged["sun_painting"].push_back(subway_item.id); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | if (AP_IsSunwarpShuffle()) { | ||
102 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { | ||
103 | std::ostringstream tag; | ||
104 | tag << "sunwarp" << mapping.dots; | ||
105 | |||
106 | SubwaySunwarp fromWarp; | ||
107 | if (index < 6) { | ||
108 | fromWarp.dots = index + 1; | ||
109 | fromWarp.type = SubwaySunwarpType::kEnter; | ||
110 | } else { | ||
111 | fromWarp.dots = index - 5; | ||
112 | fromWarp.type = SubwaySunwarpType::kExit; | ||
113 | } | ||
114 | |||
115 | SubwaySunwarp toWarp; | ||
116 | if (mapping.exit_index < 6) { | ||
117 | toWarp.dots = mapping.exit_index + 1; | ||
118 | toWarp.type = SubwaySunwarpType::kEnter; | ||
119 | } else { | ||
120 | toWarp.dots = mapping.exit_index - 5; | ||
121 | toWarp.type = SubwaySunwarpType::kExit; | ||
122 | } | ||
123 | |||
124 | tagged[tag.str()].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); | ||
125 | tagged[tag.str()].push_back(GD_GetSubwayItemForSunwarp(toWarp)); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | for (const auto &[tag, items] : tagged) { | ||
130 | // Pairwise connect all items with the same tag. | ||
131 | for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end(); | ||
132 | tag_it1++) { | ||
133 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); | ||
134 | tag_it2++) { | ||
135 | networks_.AddLink(*tag_it1, *tag_it2); | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | checked_paintings_.clear(); | ||
141 | } | ||
142 | |||
143 | void SubwayMap::UpdateIndicators() { | ||
144 | if (AP_IsPaintingShuffle()) { | ||
145 | for (const std::string &painting_id : AP_GetCheckedPaintings()) { | ||
146 | if (!checked_paintings_.count(painting_id)) { | ||
147 | checked_paintings_.insert(painting_id); | ||
148 | |||
149 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
150 | networks_.AddLink(GD_GetSubwayItemForPainting(painting_id), | ||
151 | GD_GetSubwayItemForPainting( | ||
152 | AP_GetPaintingMapping().at(painting_id))); | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
158 | Redraw(); | ||
159 | } | ||
160 | |||
161 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | ||
162 | SubwaySunwarp to_sunwarp) { | ||
163 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), | ||
164 | GD_GetSubwayItemForSunwarp(to_sunwarp)); | ||
165 | } | ||
166 | |||
167 | void SubwayMap::Zoom(bool in) { | ||
168 | wxPoint focus_point; | ||
169 | |||
170 | if (mouse_position_) { | ||
171 | focus_point = *mouse_position_; | ||
172 | } else { | ||
173 | focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}; | ||
174 | } | ||
175 | |||
176 | if (in) { | ||
177 | if (zoom_ < 3.0) { | ||
178 | SetZoom(zoom_ + 0.25, focus_point); | ||
179 | } | ||
180 | } else { | ||
181 | if (zoom_ > 1.0) { | ||
182 | SetZoom(zoom_ - 0.25, focus_point); | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | |||
187 | void SubwayMap::OnPaint(wxPaintEvent &event) { | ||
188 | if (GetSize() != rendered_.GetSize()) { | ||
189 | wxSize panel_size = GetSize(); | ||
190 | wxSize image_size = map_image_.GetSize(); | ||
191 | |||
192 | render_x_ = 0; | ||
193 | render_y_ = 0; | ||
194 | render_width_ = panel_size.GetWidth(); | ||
195 | render_height_ = panel_size.GetHeight(); | ||
196 | |||
197 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
198 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
199 | render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
200 | image_size.GetWidth(); | ||
201 | render_y_ = (panel_size.GetHeight() - render_height_) / 2; | ||
202 | } else { | ||
203 | render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
204 | image_size.GetHeight(); | ||
205 | render_x_ = (panel_size.GetWidth() - render_width_) / 2; | ||
206 | } | ||
207 | |||
208 | SetZoomPos({zoom_x_, zoom_y_}); | ||
209 | |||
210 | SetUpHelpButton(); | ||
211 | } | ||
212 | |||
213 | wxBufferedPaintDC dc(this); | ||
214 | dc.SetBackground(*wxWHITE_BRUSH); | ||
215 | dc.Clear(); | ||
216 | |||
217 | { | ||
218 | wxMemoryDC rendered_dc; | ||
219 | rendered_dc.SelectObject(rendered_); | ||
220 | |||
221 | int dst_x; | ||
222 | int dst_y; | ||
223 | int dst_w; | ||
224 | int dst_h; | ||
225 | int src_x; | ||
226 | int src_y; | ||
227 | int src_w; | ||
228 | int src_h; | ||
229 | |||
230 | int zoomed_width = render_width_ * zoom_; | ||
231 | int zoomed_height = render_height_ * zoom_; | ||
232 | |||
233 | if (zoomed_width <= GetSize().GetWidth()) { | ||
234 | dst_x = (GetSize().GetWidth() - zoomed_width) / 2; | ||
235 | dst_w = zoomed_width; | ||
236 | src_x = 0; | ||
237 | src_w = map_image_.GetWidth(); | ||
238 | } else { | ||
239 | dst_x = 0; | ||
240 | dst_w = GetSize().GetWidth(); | ||
241 | src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
242 | src_w = | ||
243 | GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_; | ||
244 | } | ||
245 | |||
246 | if (zoomed_height <= GetSize().GetHeight()) { | ||
247 | dst_y = (GetSize().GetHeight() - zoomed_height) / 2; | ||
248 | dst_h = zoomed_height; | ||
249 | src_y = 0; | ||
250 | src_h = map_image_.GetHeight(); | ||
251 | } else { | ||
252 | dst_y = 0; | ||
253 | dst_h = GetSize().GetHeight(); | ||
254 | src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
255 | src_h = | ||
256 | GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; | ||
257 | } | ||
258 | |||
259 | wxGCDC gcdc(dc); | ||
260 | gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD); | ||
261 | gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, | ||
262 | src_w, src_h); | ||
263 | } | ||
264 | |||
265 | if (hovered_item_) { | ||
266 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
267 | // knows when an item has a hover effect. | ||
268 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
269 | if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { | ||
270 | const std::map<std::string, bool> &report = | ||
271 | GetDoorRequirements(*subway_item.door); | ||
272 | |||
273 | int acc_height = 10; | ||
274 | int col_width = 0; | ||
275 | |||
276 | for (const auto &[text, obtained] : report) { | ||
277 | wxSize item_extent = dc.GetTextExtent(text); | ||
278 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
279 | acc_height += item_height; | ||
280 | |||
281 | if (item_extent.GetWidth() > col_width) { | ||
282 | col_width = item_extent.GetWidth(); | ||
283 | } | ||
284 | } | ||
285 | |||
286 | int item_width = col_width + 10 + 32; | ||
287 | int full_width = item_width + 20; | ||
288 | |||
289 | wxPoint popup_pos = | ||
290 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
291 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
292 | |||
293 | if (popup_pos.x + full_width > GetSize().GetWidth()) { | ||
294 | popup_pos.x = GetSize().GetWidth() - full_width; | ||
295 | } | ||
296 | if (popup_pos.y + acc_height > GetSize().GetHeight()) { | ||
297 | popup_pos.y = GetSize().GetHeight() - acc_height; | ||
298 | } | ||
299 | |||
300 | dc.SetPen(*wxTRANSPARENT_PEN); | ||
301 | dc.SetBrush(*wxBLACK_BRUSH); | ||
302 | dc.DrawRectangle(popup_pos, {full_width, acc_height}); | ||
303 | |||
304 | dc.SetFont(GetFont()); | ||
305 | |||
306 | int cur_height = 10; | ||
307 | |||
308 | for (const auto &[text, obtained] : report) { | ||
309 | wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_; | ||
310 | |||
311 | dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); | ||
312 | |||
313 | dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); | ||
314 | wxSize item_extent = dc.GetTextExtent(text); | ||
315 | dc.DrawText( | ||
316 | text, | ||
317 | popup_pos + | ||
318 | wxPoint{10 + 32 + 10, | ||
319 | cur_height + (32 - dc.GetFontMetrics().height) / 2}); | ||
320 | |||
321 | cur_height += 10 + 32; | ||
322 | } | ||
323 | } | ||
324 | |||
325 | if (networks_.IsItemInNetwork(*hovered_item_)) { | ||
326 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
327 | |||
328 | for (const auto &[item_id1, item_id2] : | ||
329 | networks_.GetNetworkGraph(*hovered_item_)) { | ||
330 | const SubwayItem &item1 = GD_GetSubwayItem(item_id1); | ||
331 | const SubwayItem &item2 = GD_GetSubwayItem(item_id2); | ||
332 | |||
333 | wxPoint item1_pos = MapPosToRenderPos( | ||
334 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); | ||
335 | wxPoint item2_pos = MapPosToRenderPos( | ||
336 | {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); | ||
337 | |||
338 | int left = std::min(item1_pos.x, item2_pos.x); | ||
339 | int top = std::min(item1_pos.y, item2_pos.y); | ||
340 | int right = std::max(item1_pos.x, item2_pos.x); | ||
341 | int bottom = std::max(item1_pos.y, item2_pos.y); | ||
342 | |||
343 | int halfwidth = right - left; | ||
344 | int halfheight = bottom - top; | ||
345 | |||
346 | if (halfwidth < 4 || halfheight < 4) { | ||
347 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
348 | dc.DrawLine(item1_pos, item2_pos); | ||
349 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
350 | dc.DrawLine(item1_pos, item2_pos); | ||
351 | } else { | ||
352 | int ellipse_x; | ||
353 | int ellipse_y; | ||
354 | double start; | ||
355 | double end; | ||
356 | |||
357 | if (item1_pos.x > item2_pos.x) { | ||
358 | ellipse_y = top; | ||
359 | |||
360 | if (item1_pos.y > item2_pos.y) { | ||
361 | ellipse_x = left - halfwidth; | ||
362 | |||
363 | start = 0; | ||
364 | end = 90; | ||
365 | } else { | ||
366 | ellipse_x = left; | ||
367 | |||
368 | start = 90; | ||
369 | end = 180; | ||
370 | } | ||
371 | } else { | ||
372 | ellipse_y = top - halfheight; | ||
373 | |||
374 | if (item1_pos.y > item2_pos.y) { | ||
375 | ellipse_x = left - halfwidth; | ||
376 | |||
377 | start = 270; | ||
378 | end = 360; | ||
379 | } else { | ||
380 | ellipse_x = left; | ||
381 | |||
382 | start = 180; | ||
383 | end = 270; | ||
384 | } | ||
385 | } | ||
386 | |||
387 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
388 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
389 | halfheight * 2, start, end); | ||
390 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
391 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
392 | halfheight * 2, start, end); | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | |||
398 | event.Skip(); | ||
399 | } | ||
400 | |||
401 | void SubwayMap::OnMouseMove(wxMouseEvent &event) { | ||
402 | wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); | ||
403 | |||
404 | std::vector<int> hovered = tree_->query( | ||
405 | {static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y), 2, 2}); | ||
406 | if (!hovered.empty()) { | ||
407 | actual_hover_= hovered[0]; | ||
408 | } else { | ||
409 | actual_hover_ = std::nullopt; | ||
410 | } | ||
411 | |||
412 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { | ||
413 | hovered_item_ = actual_hover_; | ||
414 | |||
415 | Refresh(); | ||
416 | } | ||
417 | |||
418 | if (scroll_mode_) { | ||
419 | EvaluateScroll(event.GetPosition()); | ||
420 | } | ||
421 | |||
422 | mouse_position_ = event.GetPosition(); | ||
423 | |||
424 | event.Skip(); | ||
425 | } | ||
426 | |||
427 | void SubwayMap::OnMouseScroll(wxMouseEvent &event) { | ||
428 | double new_zoom = zoom_; | ||
429 | if (event.GetWheelRotation() > 0) { | ||
430 | new_zoom = std::min(3.0, zoom_ + 0.25); | ||
431 | } else { | ||
432 | new_zoom = std::max(1.0, zoom_ - 0.25); | ||
433 | } | ||
434 | |||
435 | if (zoom_ != new_zoom) { | ||
436 | SetZoom(new_zoom, event.GetPosition()); | ||
437 | } | ||
438 | |||
439 | event.Skip(); | ||
440 | } | ||
441 | |||
442 | void SubwayMap::OnMouseLeave(wxMouseEvent &event) { | ||
443 | SetScrollSpeed(0, 0); | ||
444 | mouse_position_ = std::nullopt; | ||
445 | } | ||
446 | |||
447 | void SubwayMap::OnMouseClick(wxMouseEvent &event) { | ||
448 | bool finished = false; | ||
449 | |||
450 | if (actual_hover_) { | ||
451 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); | ||
452 | if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || | ||
453 | networks_.IsItemInNetwork(*hovered_item_)) { | ||
454 | if (actual_hover_ != hovered_item_) { | ||
455 | hovered_item_ = actual_hover_; | ||
456 | |||
457 | if (!hovered_item_) { | ||
458 | sticky_hover_ = false; | ||
459 | } | ||
460 | |||
461 | Refresh(); | ||
462 | } else { | ||
463 | sticky_hover_ = !sticky_hover_; | ||
464 | } | ||
465 | |||
466 | finished = true; | ||
467 | } | ||
468 | } | ||
469 | |||
470 | if (!finished) { | ||
471 | if (scroll_mode_) { | ||
472 | scroll_mode_ = false; | ||
473 | |||
474 | SetScrollSpeed(0, 0); | ||
475 | |||
476 | SetCursor(wxCURSOR_ARROW); | ||
477 | } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || | ||
478 | event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || | ||
479 | event.GetPosition().y < GetSize().GetHeight() / 6 || | ||
480 | event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { | ||
481 | scroll_mode_ = true; | ||
482 | |||
483 | EvaluateScroll(event.GetPosition()); | ||
484 | |||
485 | SetCursor(wxCURSOR_CROSS); | ||
486 | } else { | ||
487 | sticky_hover_ = false; | ||
488 | } | ||
489 | } | ||
490 | } | ||
491 | |||
492 | void SubwayMap::OnTimer(wxTimerEvent &event) { | ||
493 | SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); | ||
494 | Refresh(); | ||
495 | } | ||
496 | |||
497 | void SubwayMap::OnZoomSlide(wxCommandEvent &event) { | ||
498 | double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue(); | ||
499 | |||
500 | if (new_zoom != zoom_) { | ||
501 | SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}); | ||
502 | } | ||
503 | } | ||
504 | |||
505 | void SubwayMap::OnClickHelp(wxCommandEvent &event) { | ||
506 | wxMessageBox( | ||
507 | "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the " | ||
508 | "corner.\nClick on a side of the screen to start panning. It will follow " | ||
509 | "your mouse. Click again to stop.\nHover over a door to see the " | ||
510 | "requirements to open it.\nHover over a warp or active painting to see " | ||
511 | "what it is connected to.\nIn painting shuffle, paintings that have not " | ||
512 | "yet been checked will not show their connections.\nA green shaded owl " | ||
513 | "means that there is a painting entrance there.\nA red shaded owl means " | ||
514 | "that there are only painting exits there.\nClick on a door or " | ||
515 | "warp to make the popup stick until you click again.", | ||
516 | "Subway Map Help"); | ||
517 | } | ||
518 | |||
519 | void SubwayMap::Redraw() { | ||
520 | rendered_ = wxBitmap(map_image_); | ||
521 | |||
522 | wxMemoryDC dc; | ||
523 | dc.SelectObject(rendered_); | ||
524 | |||
525 | wxGCDC gcdc(dc); | ||
526 | |||
527 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
528 | ItemDrawType draw_type = ItemDrawType::kNone; | ||
529 | const wxBrush *brush_color = wxGREY_BRUSH; | ||
530 | std::optional<wxColour> shade_color; | ||
531 | |||
532 | if (AP_HasEarlyColorHallways() && | ||
533 | (subway_item.special == "starting_room_paintings" || | ||
534 | subway_item.special == "early_color_hallways")) { | ||
535 | draw_type = ItemDrawType::kOwl; | ||
536 | |||
537 | if (subway_item.special == "starting_room_paintings") { | ||
538 | shade_color = wxColour(0, 255, 0, 128); | ||
539 | } else { | ||
540 | shade_color = wxColour(255, 0, 0, 128); | ||
541 | } | ||
542 | } else if (subway_item.special == "sun_painting") { | ||
543 | if (!AP_IsPilgrimageEnabled()) { | ||
544 | if (IsDoorOpen(*subway_item.door)) { | ||
545 | draw_type = ItemDrawType::kOwl; | ||
546 | shade_color = wxColour(0, 255, 0, 128); | ||
547 | } else { | ||
548 | draw_type = ItemDrawType::kBox; | ||
549 | brush_color = wxRED_BRUSH; | ||
550 | } | ||
551 | } | ||
552 | } else if (!subway_item.paintings.empty()) { | ||
553 | if (AP_IsPaintingShuffle()) { | ||
554 | bool has_checked_painting = false; | ||
555 | bool has_unchecked_painting = false; | ||
556 | bool has_mapped_painting = false; | ||
557 | bool has_codomain_painting = false; | ||
558 | |||
559 | for (const std::string &painting_id : subway_item.paintings) { | ||
560 | if (checked_paintings_.count(painting_id)) { | ||
561 | has_checked_painting = true; | ||
562 | |||
563 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
564 | has_mapped_painting = true; | ||
565 | } else if (AP_IsPaintingMappedTo(painting_id)) { | ||
566 | has_codomain_painting = true; | ||
567 | } | ||
568 | } else { | ||
569 | has_unchecked_painting = true; | ||
570 | } | ||
571 | } | ||
572 | |||
573 | if (has_unchecked_painting || has_mapped_painting || has_codomain_painting) { | ||
574 | draw_type = ItemDrawType::kOwl; | ||
575 | |||
576 | if (has_checked_painting) { | ||
577 | if (has_mapped_painting) { | ||
578 | shade_color = wxColour(0, 255, 0, 128); | ||
579 | } else { | ||
580 | shade_color = wxColour(255, 0, 0, 128); | ||
581 | } | ||
582 | } | ||
583 | } | ||
584 | } else if (!subway_item.tags.empty()) { | ||
585 | draw_type = ItemDrawType::kOwl; | ||
586 | } | ||
587 | } else if (subway_item.door) { | ||
588 | draw_type = ItemDrawType::kBox; | ||
589 | |||
590 | if (IsDoorOpen(*subway_item.door)) { | ||
591 | brush_color = wxGREEN_BRUSH; | ||
592 | } else { | ||
593 | brush_color = wxRED_BRUSH; | ||
594 | } | ||
595 | } | ||
596 | |||
597 | wxPoint real_area_pos = {subway_item.x, subway_item.y}; | ||
598 | |||
599 | int real_area_size = | ||
600 | (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); | ||
601 | |||
602 | if (draw_type == ItemDrawType::kBox) { | ||
603 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | ||
604 | gcdc.SetBrush(*brush_color); | ||
605 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
606 | } else if (draw_type == ItemDrawType::kOwl) { | ||
607 | wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( | ||
608 | real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); | ||
609 | gcdc.DrawBitmap(owl_bitmap, real_area_pos); | ||
610 | |||
611 | if (shade_color) { | ||
612 | gcdc.SetBrush(wxBrush(*shade_color)); | ||
613 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
614 | } | ||
615 | } | ||
616 | } | ||
617 | } | ||
618 | |||
619 | void SubwayMap::SetUpHelpButton() { | ||
620 | help_button_->SetPosition({ | ||
621 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, | ||
622 | 15, | ||
623 | }); | ||
624 | } | ||
625 | |||
626 | void SubwayMap::EvaluateScroll(wxPoint pos) { | ||
627 | int scroll_x; | ||
628 | int scroll_y; | ||
629 | if (pos.x < GetSize().GetWidth() / 9) { | ||
630 | scroll_x = 20; | ||
631 | } else if (pos.x < GetSize().GetWidth() / 6) { | ||
632 | scroll_x = 5; | ||
633 | } else if (pos.x > 8 * GetSize().GetWidth() / 9) { | ||
634 | scroll_x = -20; | ||
635 | } else if (pos.x > 5 * GetSize().GetWidth() / 6) { | ||
636 | scroll_x = -5; | ||
637 | } else { | ||
638 | scroll_x = 0; | ||
639 | } | ||
640 | if (pos.y < GetSize().GetHeight() / 9) { | ||
641 | scroll_y = 20; | ||
642 | } else if (pos.y < GetSize().GetHeight() / 6) { | ||
643 | scroll_y = 5; | ||
644 | } else if (pos.y > 8 * GetSize().GetHeight() / 9) { | ||
645 | scroll_y = -20; | ||
646 | } else if (pos.y > 5 * GetSize().GetHeight() / 6) { | ||
647 | scroll_y = -5; | ||
648 | } else { | ||
649 | scroll_y = 0; | ||
650 | } | ||
651 | |||
652 | SetScrollSpeed(scroll_x, scroll_y); | ||
653 | } | ||
654 | |||
655 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { | ||
656 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
657 | map_image_.GetSize().GetWidth() + | ||
658 | zoom_x_), | ||
659 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
660 | map_image_.GetSize().GetWidth() + | ||
661 | zoom_y_)}; | ||
662 | } | ||
663 | |||
664 | wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { | ||
665 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
666 | map_image_.GetSize().GetWidth()), | ||
667 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
668 | map_image_.GetSize().GetWidth())}; | ||
669 | } | ||
670 | |||
671 | wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { | ||
672 | return { | ||
673 | std::clamp(static_cast<int>((pos.x - zoom_x_) * map_image_.GetWidth() / | ||
674 | render_width_ / zoom_), | ||
675 | 0, map_image_.GetWidth() - 1), | ||
676 | std::clamp(static_cast<int>((pos.y - zoom_y_) * map_image_.GetWidth() / | ||
677 | render_width_ / zoom_), | ||
678 | 0, map_image_.GetHeight() - 1)}; | ||
679 | } | ||
680 | |||
681 | void SubwayMap::SetZoomPos(wxPoint pos) { | ||
682 | if (render_width_ * zoom_ <= GetSize().GetWidth()) { | ||
683 | zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; | ||
684 | } else { | ||
685 | zoom_x_ = std::clamp( | ||
686 | pos.x, GetSize().GetWidth() - static_cast<int>(render_width_ * zoom_), | ||
687 | 0); | ||
688 | } | ||
689 | if (render_height_ * zoom_ <= GetSize().GetHeight()) { | ||
690 | zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; | ||
691 | } else { | ||
692 | zoom_y_ = std::clamp( | ||
693 | pos.y, GetSize().GetHeight() - static_cast<int>(render_height_ * zoom_), | ||
694 | 0); | ||
695 | } | ||
696 | } | ||
697 | |||
698 | void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { | ||
699 | bool should_timer = (scroll_x != 0 || scroll_y != 0); | ||
700 | if (should_timer != scroll_timer_->IsRunning()) { | ||
701 | if (should_timer) { | ||
702 | scroll_timer_->Start(1000 / 60); | ||
703 | } else { | ||
704 | scroll_timer_->Stop(); | ||
705 | } | ||
706 | } | ||
707 | |||
708 | scroll_x_ = scroll_x; | ||
709 | scroll_y_ = scroll_y; | ||
710 | } | ||
711 | |||
712 | void SubwayMap::SetZoom(double zoom, wxPoint static_point) { | ||
713 | wxPoint map_pos = RenderPosToMapPos(static_point); | ||
714 | zoom_ = zoom; | ||
715 | |||
716 | wxPoint virtual_pos = MapPosToVirtualPos(map_pos); | ||
717 | SetZoomPos(-(virtual_pos - static_point)); | ||
718 | |||
719 | Refresh(); | ||
720 | |||
721 | zoom_slider_->SetValue((zoom - 1.0) / 0.25); | ||
722 | } | ||
723 | |||
724 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { | ||
725 | const SubwayItem &subway_item = GD_GetSubwayItem(id); | ||
726 | return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y), | ||
727 | AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; | ||
728 | } | ||
diff --git a/src/subway_map.h b/src/subway_map.h new file mode 100644 index 0000000..feee8ff --- /dev/null +++ b/src/subway_map.h | |||
@@ -0,0 +1,92 @@ | |||
1 | #ifndef SUBWAY_MAP_H_BD2D843E | ||
2 | #define SUBWAY_MAP_H_BD2D843E | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include <memory> | ||
11 | #include <optional> | ||
12 | #include <set> | ||
13 | #include <string> | ||
14 | #include <vector> | ||
15 | |||
16 | #include <quadtree/Quadtree.h> | ||
17 | |||
18 | #include "game_data.h" | ||
19 | #include "network_set.h" | ||
20 | |||
21 | class SubwayMap : public wxPanel { | ||
22 | public: | ||
23 | SubwayMap(wxWindow *parent); | ||
24 | |||
25 | void OnConnect(); | ||
26 | void UpdateIndicators(); | ||
27 | void UpdateSunwarp(SubwaySunwarp from_sunwarp, SubwaySunwarp to_sunwarp); | ||
28 | void Zoom(bool in); | ||
29 | |||
30 | private: | ||
31 | void OnPaint(wxPaintEvent &event); | ||
32 | void OnMouseMove(wxMouseEvent &event); | ||
33 | void OnMouseScroll(wxMouseEvent &event); | ||
34 | void OnMouseLeave(wxMouseEvent &event); | ||
35 | void OnMouseClick(wxMouseEvent &event); | ||
36 | void OnTimer(wxTimerEvent &event); | ||
37 | void OnZoomSlide(wxCommandEvent &event); | ||
38 | void OnClickHelp(wxCommandEvent &event); | ||
39 | |||
40 | void Redraw(); | ||
41 | void SetUpHelpButton(); | ||
42 | |||
43 | wxPoint MapPosToRenderPos(wxPoint pos) const; | ||
44 | wxPoint MapPosToVirtualPos(wxPoint pos) const; | ||
45 | wxPoint RenderPosToMapPos(wxPoint pos) const; | ||
46 | |||
47 | void EvaluateScroll(wxPoint pos); | ||
48 | |||
49 | void SetZoomPos(wxPoint pos); | ||
50 | void SetScrollSpeed(int scroll_x, int scroll_y); | ||
51 | void SetZoom(double zoom, wxPoint static_point); | ||
52 | |||
53 | wxImage map_image_; | ||
54 | wxImage owl_image_; | ||
55 | wxBitmap unchecked_eye_; | ||
56 | wxBitmap checked_eye_; | ||
57 | |||
58 | wxBitmap rendered_; | ||
59 | int render_x_ = 0; | ||
60 | int render_y_ = 0; | ||
61 | int render_width_ = 1; | ||
62 | int render_height_ = 1; | ||
63 | |||
64 | double zoom_ = 1.0; | ||
65 | int zoom_x_ = 0; // in render space | ||
66 | int zoom_y_ = 0; | ||
67 | |||
68 | bool scroll_mode_ = false; | ||
69 | wxTimer* scroll_timer_; | ||
70 | int scroll_x_ = 0; | ||
71 | int scroll_y_ = 0; | ||
72 | |||
73 | wxSlider *zoom_slider_; | ||
74 | |||
75 | wxButton *help_button_; | ||
76 | |||
77 | std::optional<wxPoint> mouse_position_; | ||
78 | |||
79 | struct GetItemBox { | ||
80 | quadtree::Box<float> operator()(const int &id) const; | ||
81 | }; | ||
82 | |||
83 | std::unique_ptr<quadtree::Quadtree<int, GetItemBox>> tree_; | ||
84 | std::optional<int> hovered_item_; | ||
85 | std::optional<int> actual_hover_; | ||
86 | bool sticky_hover_ = false; | ||
87 | |||
88 | NetworkSet networks_; | ||
89 | std::set<std::string> checked_paintings_; | ||
90 | }; | ||
91 | |||
92 | #endif /* end of include guard: SUBWAY_MAP_H_BD2D843E */ | ||
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index d64e0d3..107ae49 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp | |||
@@ -1,6 +1,8 @@ | |||
1 | #include "tracker_frame.h" | 1 | #include "tracker_frame.h" |
2 | 2 | ||
3 | #include <wx/aboutdlg.h> | ||
3 | #include <wx/choicebk.h> | 4 | #include <wx/choicebk.h> |
5 | #include <wx/notebook.h> | ||
4 | #include <wx/webrequest.h> | 6 | #include <wx/webrequest.h> |
5 | 7 | ||
6 | #include <nlohmann/json.hpp> | 8 | #include <nlohmann/json.hpp> |
@@ -10,6 +12,7 @@ | |||
10 | #include "ap_state.h" | 12 | #include "ap_state.h" |
11 | #include "connection_dialog.h" | 13 | #include "connection_dialog.h" |
12 | #include "settings_dialog.h" | 14 | #include "settings_dialog.h" |
15 | #include "subway_map.h" | ||
13 | #include "tracker_config.h" | 16 | #include "tracker_config.h" |
14 | #include "tracker_panel.h" | 17 | #include "tracker_panel.h" |
15 | #include "version.h" | 18 | #include "version.h" |
@@ -17,9 +20,12 @@ | |||
17 | enum TrackerFrameIds { | 20 | enum TrackerFrameIds { |
18 | ID_CONNECT = 1, | 21 | ID_CONNECT = 1, |
19 | ID_CHECK_FOR_UPDATES = 2, | 22 | ID_CHECK_FOR_UPDATES = 2, |
20 | ID_SETTINGS = 3 | 23 | ID_SETTINGS = 3, |
24 | ID_ZOOM_IN = 4, | ||
25 | ID_ZOOM_OUT = 5, | ||
21 | }; | 26 | }; |
22 | 27 | ||
28 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); | ||
23 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); | 29 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); |
24 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); | 30 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); |
25 | 31 | ||
@@ -35,12 +41,20 @@ TrackerFrame::TrackerFrame() | |||
35 | menuFile->Append(ID_SETTINGS, "&Settings"); | 41 | menuFile->Append(ID_SETTINGS, "&Settings"); |
36 | menuFile->Append(wxID_EXIT); | 42 | menuFile->Append(wxID_EXIT); |
37 | 43 | ||
44 | wxMenu *menuView = new wxMenu(); | ||
45 | zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); | ||
46 | zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); | ||
47 | |||
48 | zoom_in_menu_item_->Enable(false); | ||
49 | zoom_out_menu_item_->Enable(false); | ||
50 | |||
38 | wxMenu *menuHelp = new wxMenu(); | 51 | wxMenu *menuHelp = new wxMenu(); |
39 | menuHelp->Append(wxID_ABOUT); | 52 | menuHelp->Append(wxID_ABOUT); |
40 | menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates"); | 53 | menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates"); |
41 | 54 | ||
42 | wxMenuBar *menuBar = new wxMenuBar(); | 55 | wxMenuBar *menuBar = new wxMenuBar(); |
43 | menuBar->Append(menuFile, "&File"); | 56 | menuBar->Append(menuFile, "&File"); |
57 | menuBar->Append(menuView, "&View"); | ||
44 | menuBar->Append(menuHelp, "&Help"); | 58 | menuBar->Append(menuHelp, "&Help"); |
45 | 59 | ||
46 | SetMenuBar(menuBar); | 60 | SetMenuBar(menuBar); |
@@ -54,18 +68,26 @@ TrackerFrame::TrackerFrame() | |||
54 | Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS); | 68 | Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS); |
55 | Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, | 69 | Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, |
56 | ID_CHECK_FOR_UPDATES); | 70 | ID_CHECK_FOR_UPDATES); |
71 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); | ||
72 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); | ||
73 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); | ||
74 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); | ||
57 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | 75 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); |
58 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | 76 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); |
59 | 77 | ||
60 | wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); | 78 | wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); |
61 | achievements_pane_ = new AchievementsPane(this); | 79 | achievements_pane_ = new AchievementsPane(choicebook); |
62 | choicebook->AddPage(achievements_pane_, "Achievements"); | 80 | choicebook->AddPage(achievements_pane_, "Achievements"); |
63 | 81 | ||
64 | tracker_panel_ = new TrackerPanel(this); | 82 | notebook_ = new wxNotebook(this, wxID_ANY); |
83 | tracker_panel_ = new TrackerPanel(notebook_); | ||
84 | subway_map_ = new SubwayMap(notebook_); | ||
85 | notebook_->AddPage(tracker_panel_, "Map"); | ||
86 | notebook_->AddPage(subway_map_, "Subway"); | ||
65 | 87 | ||
66 | wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); | 88 | wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); |
67 | top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); | 89 | top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); |
68 | top_sizer->Add(tracker_panel_, wxSizerFlags().Expand().Proportion(3)); | 90 | top_sizer->Add(notebook_, wxSizerFlags().Expand().Proportion(3)); |
69 | 91 | ||
70 | SetSizerAndFit(top_sizer); | 92 | SetSizerAndFit(top_sizer); |
71 | SetSize(1280, 728); | 93 | SetSize(1280, 728); |
@@ -96,17 +118,23 @@ void TrackerFrame::SetStatusMessage(std::string message) { | |||
96 | QueueEvent(event); | 118 | QueueEvent(event); |
97 | } | 119 | } |
98 | 120 | ||
121 | void TrackerFrame::ResetIndicators() { | ||
122 | QueueEvent(new wxCommandEvent(STATE_RESET)); | ||
123 | } | ||
124 | |||
99 | void TrackerFrame::UpdateIndicators() { | 125 | void TrackerFrame::UpdateIndicators() { |
100 | QueueEvent(new wxCommandEvent(STATE_CHANGED)); | 126 | QueueEvent(new wxCommandEvent(STATE_CHANGED)); |
101 | } | 127 | } |
102 | 128 | ||
103 | void TrackerFrame::OnAbout(wxCommandEvent &event) { | 129 | void TrackerFrame::OnAbout(wxCommandEvent &event) { |
104 | std::ostringstream message_text; | 130 | wxAboutDialogInfo about_info; |
105 | message_text << "Lingo Archipelago Tracker " << kTrackerVersion | 131 | about_info.SetName("Lingo Archipelago Tracker"); |
106 | << " by hatkirby"; | 132 | about_info.SetVersion(kTrackerVersion.ToString()); |
107 | 133 | about_info.AddDeveloper("hatkirby"); | |
108 | wxMessageBox(message_text.str(), "About lingo-ap-tracker", | 134 | about_info.AddArtist("Brenton Wildes"); |
109 | wxOK | wxICON_INFORMATION); | 135 | about_info.AddArtist("kinrah"); |
136 | |||
137 | wxAboutBox(about_info); | ||
110 | } | 138 | } |
111 | 139 | ||
112 | void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } | 140 | void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } |
@@ -122,7 +150,8 @@ void TrackerFrame::OnConnect(wxCommandEvent &event) { | |||
122 | std::deque<ConnectionDetails> new_history; | 150 | std::deque<ConnectionDetails> new_history; |
123 | new_history.push_back(GetTrackerConfig().connection_details); | 151 | new_history.push_back(GetTrackerConfig().connection_details); |
124 | 152 | ||
125 | for (const ConnectionDetails& details : GetTrackerConfig().connection_history) { | 153 | for (const ConnectionDetails &details : |
154 | GetTrackerConfig().connection_history) { | ||
126 | if (details != GetTrackerConfig().connection_details) { | 155 | if (details != GetTrackerConfig().connection_details) { |
127 | new_history.push_back(details); | 156 | new_history.push_back(details); |
128 | } | 157 | } |
@@ -158,9 +187,34 @@ void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { | |||
158 | CheckForUpdates(/*manual=*/true); | 187 | CheckForUpdates(/*manual=*/true); |
159 | } | 188 | } |
160 | 189 | ||
190 | void TrackerFrame::OnZoomIn(wxCommandEvent &event) { | ||
191 | if (notebook_->GetSelection() == 1) { | ||
192 | subway_map_->Zoom(true); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | void TrackerFrame::OnZoomOut(wxCommandEvent& event) { | ||
197 | if (notebook_->GetSelection() == 1) { | ||
198 | subway_map_->Zoom(false); | ||
199 | } | ||
200 | } | ||
201 | |||
202 | void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { | ||
203 | zoom_in_menu_item_->Enable(event.GetSelection() == 1); | ||
204 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | ||
205 | } | ||
206 | |||
207 | void TrackerFrame::OnStateReset(wxCommandEvent& event) { | ||
208 | tracker_panel_->UpdateIndicators(); | ||
209 | achievements_pane_->UpdateIndicators(); | ||
210 | subway_map_->OnConnect(); | ||
211 | Refresh(); | ||
212 | } | ||
213 | |||
161 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | 214 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { |
162 | tracker_panel_->UpdateIndicators(); | 215 | tracker_panel_->UpdateIndicators(); |
163 | achievements_pane_->UpdateIndicators(); | 216 | achievements_pane_->UpdateIndicators(); |
217 | subway_map_->UpdateIndicators(); | ||
164 | Refresh(); | 218 | Refresh(); |
165 | } | 219 | } |
166 | 220 | ||
@@ -192,8 +246,10 @@ void TrackerFrame::CheckForUpdates(bool manual) { | |||
192 | std::ostringstream message_text; | 246 | std::ostringstream message_text; |
193 | message_text << "There is a newer version of Lingo AP Tracker " | 247 | message_text << "There is a newer version of Lingo AP Tracker " |
194 | "available. You have " | 248 | "available. You have " |
195 | << kTrackerVersion << ", and the latest version is " | 249 | << kTrackerVersion.ToString() |
196 | << latest_version << ". Would you like to update?"; | 250 | << ", and the latest version is " |
251 | << latest_version.ToString() | ||
252 | << ". Would you like to update?"; | ||
197 | 253 | ||
198 | if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == | 254 | if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == |
199 | wxYES) { | 255 | wxYES) { |
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index e5bf97e..f7cb3f2 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h | |||
@@ -8,8 +8,12 @@ | |||
8 | #endif | 8 | #endif |
9 | 9 | ||
10 | class AchievementsPane; | 10 | class AchievementsPane; |
11 | class SubwayMap; | ||
11 | class TrackerPanel; | 12 | class TrackerPanel; |
13 | class wxBookCtrlEvent; | ||
14 | class wxNotebook; | ||
12 | 15 | ||
16 | wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); | ||
13 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); | 17 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); |
14 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); | 18 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); |
15 | 19 | ||
@@ -19,6 +23,7 @@ class TrackerFrame : public wxFrame { | |||
19 | 23 | ||
20 | void SetStatusMessage(std::string message); | 24 | void SetStatusMessage(std::string message); |
21 | 25 | ||
26 | void ResetIndicators(); | ||
22 | void UpdateIndicators(); | 27 | void UpdateIndicators(); |
23 | 28 | ||
24 | private: | 29 | private: |
@@ -27,14 +32,23 @@ class TrackerFrame : public wxFrame { | |||
27 | void OnConnect(wxCommandEvent &event); | 32 | void OnConnect(wxCommandEvent &event); |
28 | void OnSettings(wxCommandEvent &event); | 33 | void OnSettings(wxCommandEvent &event); |
29 | void OnCheckForUpdates(wxCommandEvent &event); | 34 | void OnCheckForUpdates(wxCommandEvent &event); |
35 | void OnZoomIn(wxCommandEvent &event); | ||
36 | void OnZoomOut(wxCommandEvent &event); | ||
37 | void OnChangePage(wxBookCtrlEvent &event); | ||
30 | 38 | ||
39 | void OnStateReset(wxCommandEvent &event); | ||
31 | void OnStateChanged(wxCommandEvent &event); | 40 | void OnStateChanged(wxCommandEvent &event); |
32 | void OnStatusChanged(wxCommandEvent &event); | 41 | void OnStatusChanged(wxCommandEvent &event); |
33 | 42 | ||
34 | void CheckForUpdates(bool manual); | 43 | void CheckForUpdates(bool manual); |
35 | 44 | ||
45 | wxNotebook *notebook_; | ||
36 | TrackerPanel *tracker_panel_; | 46 | TrackerPanel *tracker_panel_; |
37 | AchievementsPane *achievements_pane_; | 47 | AchievementsPane *achievements_pane_; |
48 | SubwayMap *subway_map_; | ||
49 | |||
50 | wxMenuItem *zoom_in_menu_item_; | ||
51 | wxMenuItem *zoom_out_menu_item_; | ||
38 | }; | 52 | }; |
39 | 53 | ||
40 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ | 54 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ |
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 5f9f8ea..d60c1b6 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp | |||
@@ -1,5 +1,7 @@ | |||
1 | #include "tracker_panel.h" | 1 | #include "tracker_panel.h" |
2 | 2 | ||
3 | #include <wx/dcbuffer.h> | ||
4 | |||
3 | #include "ap_state.h" | 5 | #include "ap_state.h" |
4 | #include "area_popup.h" | 6 | #include "area_popup.h" |
5 | #include "game_data.h" | 7 | #include "game_data.h" |
@@ -13,6 +15,8 @@ constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; | |||
13 | constexpr int PLAYER_SIZE = 96; | 15 | constexpr int PLAYER_SIZE = 96; |
14 | 16 | ||
15 | TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | 17 | TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { |
18 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
19 | |||
16 | map_image_ = wxImage(GetAbsolutePath("assets/lingo_map.png").c_str(), | 20 | map_image_ = wxImage(GetAbsolutePath("assets/lingo_map.png").c_str(), |
17 | wxBITMAP_TYPE_PNG); | 21 | wxBITMAP_TYPE_PNG); |
18 | if (!map_image_.IsOk()) { | 22 | if (!map_image_.IsOk()) { |
@@ -54,7 +58,7 @@ void TrackerPanel::OnPaint(wxPaintEvent &event) { | |||
54 | Redraw(); | 58 | Redraw(); |
55 | } | 59 | } |
56 | 60 | ||
57 | wxPaintDC dc(this); | 61 | wxBufferedPaintDC dc(this); |
58 | dc.DrawBitmap(rendered_, 0, 0); | 62 | dc.DrawBitmap(rendered_, 0, 0); |
59 | 63 | ||
60 | if (AP_GetPlayerPosition().has_value()) { | 64 | if (AP_GetPlayerPosition().has_value()) { |
@@ -139,7 +143,8 @@ void TrackerPanel::Redraw() { | |||
139 | for (AreaIndicator &area : areas_) { | 143 | for (AreaIndicator &area : areas_) { |
140 | const MapArea &map_area = GD_GetMapArea(area.area_id); | 144 | const MapArea &map_area = GD_GetMapArea(area.area_id); |
141 | if (!AP_IsLocationVisible(map_area.classification) && | 145 | if (!AP_IsLocationVisible(map_area.classification) && |
142 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels)) { | 146 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && |
147 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { | ||
143 | area.active = false; | 148 | area.active = false; |
144 | continue; | 149 | continue; |
145 | } else { | 150 | } else { |
@@ -167,6 +172,21 @@ void TrackerPanel::Redraw() { | |||
167 | } | 172 | } |
168 | } | 173 | } |
169 | 174 | ||
175 | if (AP_IsPaintingShuffle()) { | ||
176 | for (int painting_id : map_area.paintings) { | ||
177 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); | ||
178 | if (!AP_IsPaintingChecked(painting.internal_id)) { | ||
179 | bool reachable = IsPaintingReachable(painting_id); | ||
180 | |||
181 | if (reachable) { | ||
182 | has_reachable_unchecked = true; | ||
183 | } else { | ||
184 | has_unreachable_unchecked = true; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | } | ||
189 | |||
170 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * | 190 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * |
171 | final_width / image_size.GetWidth(); | 191 | final_width / image_size.GetWidth(); |
172 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * | 192 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * |
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 1881513..c475fb7 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp | |||
@@ -12,9 +12,142 @@ | |||
12 | 12 | ||
13 | namespace { | 13 | namespace { |
14 | 14 | ||
15 | struct Requirements { | ||
16 | bool disabled = false; | ||
17 | |||
18 | std::set<int> doors; // non-grouped, handles progressive | ||
19 | std::set<int> items; // all other items | ||
20 | std::set<int> rooms; // maybe | ||
21 | bool mastery = false; // maybe | ||
22 | bool panel_hunt = false; // maybe | ||
23 | |||
24 | void Merge(const Requirements& rhs) { | ||
25 | if (rhs.disabled) { | ||
26 | return; | ||
27 | } | ||
28 | |||
29 | for (int id : rhs.doors) { | ||
30 | doors.insert(id); | ||
31 | } | ||
32 | for (int id : rhs.items) { | ||
33 | items.insert(id); | ||
34 | } | ||
35 | for (int id : rhs.rooms) { | ||
36 | rooms.insert(id); | ||
37 | } | ||
38 | mastery = mastery || rhs.mastery; | ||
39 | panel_hunt = panel_hunt || rhs.panel_hunt; | ||
40 | } | ||
41 | }; | ||
42 | |||
43 | class RequirementCalculator { | ||
44 | public: | ||
45 | void Reset() { | ||
46 | doors_.clear(); | ||
47 | panels_.clear(); | ||
48 | } | ||
49 | |||
50 | const Requirements& GetDoor(int door_id) { | ||
51 | if (!doors_.count(door_id)) { | ||
52 | Requirements requirements; | ||
53 | const Door& door_obj = GD_GetDoor(door_id); | ||
54 | |||
55 | if (door_obj.type == DoorType::kSunPainting) { | ||
56 | if (!AP_IsPilgrimageEnabled()) { | ||
57 | requirements.items.insert(door_obj.ap_item_id); | ||
58 | } else { | ||
59 | requirements.disabled = true; | ||
60 | } | ||
61 | } else if (door_obj.type == DoorType::kSunwarp) { | ||
62 | switch (AP_GetSunwarpAccess()) { | ||
63 | case kSUNWARP_ACCESS_NORMAL: | ||
64 | // Do nothing. | ||
65 | break; | ||
66 | case kSUNWARP_ACCESS_DISABLED: | ||
67 | requirements.disabled = true; | ||
68 | break; | ||
69 | case kSUNWARP_ACCESS_UNLOCK: | ||
70 | requirements.items.insert(door_obj.group_ap_item_id); | ||
71 | break; | ||
72 | case kSUNWARP_ACCESS_INDIVIDUAL: | ||
73 | case kSUNWARP_ACCESS_PROGRESSIVE: | ||
74 | requirements.doors.insert(door_obj.id); | ||
75 | break; | ||
76 | } | ||
77 | } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { | ||
78 | requirements.rooms.insert(door_obj.room); | ||
79 | |||
80 | for (int panel_id : door_obj.panels) { | ||
81 | const Requirements& panel_reqs = GetPanel(panel_id); | ||
82 | requirements.Merge(panel_reqs); | ||
83 | } | ||
84 | } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && | ||
85 | !door_obj.group_name.empty()) { | ||
86 | requirements.items.insert(door_obj.group_ap_item_id); | ||
87 | } else { | ||
88 | requirements.doors.insert(door_obj.id); | ||
89 | } | ||
90 | |||
91 | doors_[door_id] = requirements; | ||
92 | } | ||
93 | |||
94 | return doors_[door_id]; | ||
95 | } | ||
96 | |||
97 | const Requirements& GetPanel(int panel_id) { | ||
98 | if (!panels_.count(panel_id)) { | ||
99 | Requirements requirements; | ||
100 | const Panel& panel_obj = GD_GetPanel(panel_id); | ||
101 | |||
102 | requirements.rooms.insert(panel_obj.room); | ||
103 | |||
104 | if (panel_obj.name == "THE MASTER") { | ||
105 | requirements.mastery = true; | ||
106 | } | ||
107 | |||
108 | if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && | ||
109 | AP_GetLevel2Requirement() > 1) { | ||
110 | requirements.panel_hunt = true; | ||
111 | } | ||
112 | |||
113 | for (int room_id : panel_obj.required_rooms) { | ||
114 | requirements.rooms.insert(room_id); | ||
115 | } | ||
116 | |||
117 | for (int door_id : panel_obj.required_doors) { | ||
118 | const Requirements& door_reqs = GetDoor(door_id); | ||
119 | requirements.Merge(door_reqs); | ||
120 | } | ||
121 | |||
122 | for (int panel_id : panel_obj.required_panels) { | ||
123 | const Requirements& panel_reqs = GetPanel(panel_id); | ||
124 | requirements.Merge(panel_reqs); | ||
125 | } | ||
126 | |||
127 | if (AP_IsColorShuffle()) { | ||
128 | for (LingoColor color : panel_obj.colors) { | ||
129 | requirements.items.insert(GD_GetItemIdForColor(color)); | ||
130 | } | ||
131 | } | ||
132 | |||
133 | panels_[panel_id] = requirements; | ||
134 | } | ||
135 | |||
136 | return panels_[panel_id]; | ||
137 | } | ||
138 | |||
139 | private: | ||
140 | std::map<int, Requirements> doors_; | ||
141 | std::map<int, Requirements> panels_; | ||
142 | }; | ||
143 | |||
15 | struct TrackerState { | 144 | struct TrackerState { |
16 | std::map<int, bool> reachability; | 145 | std::map<int, bool> reachability; |
146 | std::set<int> reachable_doors; | ||
147 | std::set<int> reachable_paintings; | ||
17 | std::mutex reachability_mutex; | 148 | std::mutex reachability_mutex; |
149 | RequirementCalculator requirements; | ||
150 | std::map<int, std::map<std::string, bool>> door_reports; | ||
18 | }; | 151 | }; |
19 | 152 | ||
20 | enum Decision { kYes, kNo, kMaybe }; | 153 | enum Decision { kYes, kNo, kMaybe }; |
@@ -41,6 +174,7 @@ class StateCalculator { | |||
41 | 174 | ||
42 | void Calculate() { | 175 | void Calculate() { |
43 | std::list<int> panel_boundary; | 176 | std::list<int> panel_boundary; |
177 | std::list<int> painting_boundary; | ||
44 | std::list<Exit> flood_boundary; | 178 | std::list<Exit> flood_boundary; |
45 | flood_boundary.push_back({.destination_room = options_.start}); | 179 | flood_boundary.push_back({.destination_room = options_.start}); |
46 | 180 | ||
@@ -48,6 +182,8 @@ class StateCalculator { | |||
48 | while (reachable_changed) { | 182 | while (reachable_changed) { |
49 | reachable_changed = false; | 183 | reachable_changed = false; |
50 | 184 | ||
185 | std::list<Exit> new_boundary; | ||
186 | |||
51 | std::list<int> new_panel_boundary; | 187 | std::list<int> new_panel_boundary; |
52 | for (int panel_id : panel_boundary) { | 188 | for (int panel_id : panel_boundary) { |
53 | if (solveable_panels_.count(panel_id)) { | 189 | if (solveable_panels_.count(panel_id)) { |
@@ -63,7 +199,33 @@ class StateCalculator { | |||
63 | } | 199 | } |
64 | } | 200 | } |
65 | 201 | ||
66 | std::list<Exit> new_boundary; | 202 | std::list<int> new_painting_boundary; |
203 | for (int painting_id : painting_boundary) { | ||
204 | if (reachable_paintings_.count(painting_id)) { | ||
205 | continue; | ||
206 | } | ||
207 | |||
208 | Decision painting_reachable = IsPaintingReachable(painting_id); | ||
209 | if (painting_reachable == kYes) { | ||
210 | reachable_paintings_.insert(painting_id); | ||
211 | reachable_changed = true; | ||
212 | |||
213 | PaintingExit cur_painting = GD_GetPaintingExit(painting_id); | ||
214 | if (AP_GetPaintingMapping().count(cur_painting.internal_id) && | ||
215 | AP_GetCheckedPaintings().count(cur_painting.internal_id)) { | ||
216 | Exit painting_exit; | ||
217 | PaintingExit target_painting = | ||
218 | GD_GetPaintingExit(GD_GetPaintingByName( | ||
219 | AP_GetPaintingMapping().at(cur_painting.internal_id))); | ||
220 | painting_exit.destination_room = target_painting.room; | ||
221 | |||
222 | new_boundary.push_back(painting_exit); | ||
223 | } | ||
224 | } else if (painting_reachable == kMaybe) { | ||
225 | new_painting_boundary.push_back(painting_id); | ||
226 | } | ||
227 | } | ||
228 | |||
67 | for (const Exit& room_exit : flood_boundary) { | 229 | for (const Exit& room_exit : flood_boundary) { |
68 | if (reachable_rooms_.count(room_exit.destination_room)) { | 230 | if (reachable_rooms_.count(room_exit.destination_room)) { |
69 | continue; | 231 | continue; |
@@ -98,15 +260,8 @@ class StateCalculator { | |||
98 | } | 260 | } |
99 | 261 | ||
100 | if (AP_IsPaintingShuffle()) { | 262 | if (AP_IsPaintingShuffle()) { |
101 | for (const PaintingExit& out_edge : room_obj.paintings) { | 263 | for (int out_edge : room_obj.paintings) { |
102 | if (AP_GetPaintingMapping().count(out_edge.id)) { | 264 | new_painting_boundary.push_back(out_edge); |
103 | Exit painting_exit; | ||
104 | painting_exit.destination_room = GD_GetRoomForPainting( | ||
105 | AP_GetPaintingMapping().at(out_edge.id)); | ||
106 | painting_exit.door = out_edge.door; | ||
107 | |||
108 | new_boundary.push_back(painting_exit); | ||
109 | } | ||
110 | } | 265 | } |
111 | } | 266 | } |
112 | 267 | ||
@@ -155,6 +310,13 @@ class StateCalculator { | |||
155 | 310 | ||
156 | flood_boundary = new_boundary; | 311 | flood_boundary = new_boundary; |
157 | panel_boundary = new_panel_boundary; | 312 | panel_boundary = new_panel_boundary; |
313 | painting_boundary = new_painting_boundary; | ||
314 | } | ||
315 | |||
316 | // Now that we know the full reachable area, let's make sure all doors are | ||
317 | // evaluated. | ||
318 | for (const Door& door : GD_GetDoors()) { | ||
319 | int discard = IsDoorReachable(door.id); | ||
158 | } | 320 | } |
159 | } | 321 | } |
160 | 322 | ||
@@ -166,6 +328,14 @@ class StateCalculator { | |||
166 | 328 | ||
167 | const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } | 329 | const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } |
168 | 330 | ||
331 | const std::set<int>& GetReachablePaintings() const { | ||
332 | return reachable_paintings_; | ||
333 | } | ||
334 | |||
335 | const std::map<int, std::map<std::string, bool>>& GetDoorReports() const { | ||
336 | return door_report_; | ||
337 | } | ||
338 | |||
169 | private: | 339 | private: |
170 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { | 340 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { |
171 | bool has_item = AP_HasItem(door_obj.ap_item_id); | 341 | bool has_item = AP_HasItem(door_obj.ap_item_id); |
@@ -182,68 +352,52 @@ class StateCalculator { | |||
182 | return has_item ? kYes : kNo; | 352 | return has_item ? kYes : kNo; |
183 | } | 353 | } |
184 | 354 | ||
185 | Decision IsDoorReachable_Helper(int door_id) { | 355 | Decision AreRequirementsSatisfied( |
186 | const Door& door_obj = GD_GetDoor(door_id); | 356 | const Requirements& reqs, std::map<std::string, bool>* report = nullptr) { |
187 | 357 | if (reqs.disabled) { | |
188 | if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { | 358 | return kNo; |
189 | return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; | ||
190 | } else if (door_obj.type == DoorType::kSunwarp) { | ||
191 | switch (AP_GetSunwarpAccess()) { | ||
192 | case kSUNWARP_ACCESS_NORMAL: | ||
193 | return kYes; | ||
194 | case kSUNWARP_ACCESS_DISABLED: | ||
195 | return kNo; | ||
196 | case kSUNWARP_ACCESS_UNLOCK: | ||
197 | return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; | ||
198 | case kSUNWARP_ACCESS_INDIVIDUAL: | ||
199 | case kSUNWARP_ACCESS_PROGRESSIVE: | ||
200 | return IsNonGroupedDoorReachable(door_obj); | ||
201 | } | ||
202 | } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { | ||
203 | if (!reachable_rooms_.count(door_obj.room)) { | ||
204 | return kMaybe; | ||
205 | } | ||
206 | |||
207 | for (int panel_id : door_obj.panels) { | ||
208 | if (!solveable_panels_.count(panel_id)) { | ||
209 | return kMaybe; | ||
210 | } | ||
211 | } | ||
212 | |||
213 | return kYes; | ||
214 | } else if (AP_GetDoorShuffleMode() == kDOORS_MODE && AP_AreDoorsGrouped() && | ||
215 | !door_obj.group_name.empty()) { | ||
216 | return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; | ||
217 | } else { | ||
218 | return IsNonGroupedDoorReachable(door_obj); | ||
219 | } | 359 | } |
220 | } | ||
221 | 360 | ||
222 | Decision IsDoorReachable(int door_id) { | 361 | Decision final_decision = kYes; |
223 | if (options_.parent) { | ||
224 | return options_.parent->IsDoorReachable(door_id); | ||
225 | } | ||
226 | 362 | ||
227 | if (door_decisions_.count(door_id)) { | 363 | for (int door_id : reqs.doors) { |
228 | return door_decisions_.at(door_id); | 364 | const Door& door_obj = GD_GetDoor(door_id); |
365 | Decision decision = IsNonGroupedDoorReachable(door_obj); | ||
366 | |||
367 | if (report) { | ||
368 | (*report)[door_obj.item_name] = (decision == kYes); | ||
369 | } | ||
370 | |||
371 | if (decision != kYes) { | ||
372 | final_decision = decision; | ||
373 | } | ||
229 | } | 374 | } |
230 | 375 | ||
231 | Decision result = IsDoorReachable_Helper(door_id); | 376 | for (int item_id : reqs.items) { |
232 | if (result != kMaybe) { | 377 | bool has_item = AP_HasItem(item_id); |
233 | door_decisions_[door_id] = result; | 378 | if (report) { |
379 | (*report)[AP_GetItemName(item_id)] = has_item; | ||
380 | } | ||
381 | |||
382 | if (!has_item) { | ||
383 | final_decision = kNo; | ||
384 | } | ||
234 | } | 385 | } |
235 | 386 | ||
236 | return result; | 387 | for (int room_id : reqs.rooms) { |
237 | } | 388 | bool reachable = reachable_rooms_.count(room_id); |
238 | 389 | ||
239 | Decision IsPanelReachable(int panel_id) { | 390 | if (report) { |
240 | const Panel& panel_obj = GD_GetPanel(panel_id); | 391 | std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\""; |
392 | (*report)[report_name] = reachable; | ||
393 | } | ||
241 | 394 | ||
242 | if (!reachable_rooms_.count(panel_obj.room)) { | 395 | if (!reachable && final_decision != kNo) { |
243 | return kMaybe; | 396 | final_decision = kMaybe; |
397 | } | ||
244 | } | 398 | } |
245 | 399 | ||
246 | if (panel_obj.name == "THE MASTER") { | 400 | if (reqs.mastery) { |
247 | int achievements_accessible = 0; | 401 | int achievements_accessible = 0; |
248 | 402 | ||
249 | for (int achieve_id : GD_GetAchievementPanels()) { | 403 | for (int achieve_id : GD_GetAchievementPanels()) { |
@@ -256,12 +410,18 @@ class StateCalculator { | |||
256 | } | 410 | } |
257 | } | 411 | } |
258 | 412 | ||
259 | return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes | 413 | bool can_mastery = |
260 | : kMaybe; | 414 | (achievements_accessible >= AP_GetMasteryRequirement()); |
415 | if (report) { | ||
416 | (*report)["Mastery"] = can_mastery; | ||
417 | } | ||
418 | |||
419 | if (!can_mastery && final_decision != kNo) { | ||
420 | final_decision = kMaybe; | ||
421 | } | ||
261 | } | 422 | } |
262 | 423 | ||
263 | if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && | 424 | if (reqs.panel_hunt) { |
264 | AP_GetLevel2Requirement() > 1) { | ||
265 | int counting_panels_accessible = 0; | 425 | int counting_panels_accessible = 0; |
266 | 426 | ||
267 | for (int solved_panel_id : solveable_panels_) { | 427 | for (int solved_panel_id : solveable_panels_) { |
@@ -272,41 +432,58 @@ class StateCalculator { | |||
272 | } | 432 | } |
273 | } | 433 | } |
274 | 434 | ||
275 | return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) | 435 | bool can_level2 = |
276 | ? kYes | 436 | (counting_panels_accessible >= AP_GetLevel2Requirement() - 1); |
277 | : kMaybe; | 437 | if (report) { |
278 | } | 438 | std::string report_name = |
439 | std::to_string(AP_GetLevel2Requirement()) + " Panels"; | ||
440 | (*report)[report_name] = can_level2; | ||
441 | } | ||
279 | 442 | ||
280 | for (int room_id : panel_obj.required_rooms) { | 443 | if (!can_level2 && final_decision != kNo) { |
281 | if (!reachable_rooms_.count(room_id)) { | 444 | final_decision = kMaybe; |
282 | return kMaybe; | ||
283 | } | 445 | } |
284 | } | 446 | } |
285 | 447 | ||
286 | for (int door_id : panel_obj.required_doors) { | 448 | return final_decision; |
287 | Decision door_reachable = IsDoorReachable(door_id); | 449 | } |
288 | if (door_reachable == kNo) { | 450 | |
289 | const Door& door_obj = GD_GetDoor(door_id); | 451 | Decision IsDoorReachable_Helper(int door_id) { |
290 | return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) | 452 | if (door_report_.count(door_id)) { |
291 | ? kMaybe | 453 | door_report_[door_id].clear(); |
292 | : kNo; | 454 | } else { |
293 | } else if (door_reachable == kMaybe) { | 455 | door_report_[door_id] = {}; |
294 | return kMaybe; | ||
295 | } | ||
296 | } | 456 | } |
297 | 457 | ||
298 | for (int panel_id : panel_obj.required_panels) { | 458 | return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), |
299 | if (!solveable_panels_.count(panel_id)) { | 459 | &door_report_[door_id]); |
300 | return kMaybe; | 460 | } |
301 | } | 461 | |
462 | Decision IsDoorReachable(int door_id) { | ||
463 | if (options_.parent) { | ||
464 | return options_.parent->IsDoorReachable(door_id); | ||
302 | } | 465 | } |
303 | 466 | ||
304 | if (AP_IsColorShuffle()) { | 467 | if (door_decisions_.count(door_id)) { |
305 | for (LingoColor color : panel_obj.colors) { | 468 | return door_decisions_.at(door_id); |
306 | if (!AP_HasItem(GD_GetItemIdForColor(color))) { | 469 | } |
307 | return kNo; | 470 | |
308 | } | 471 | Decision result = IsDoorReachable_Helper(door_id); |
309 | } | 472 | if (result != kMaybe) { |
473 | door_decisions_[door_id] = result; | ||
474 | } | ||
475 | |||
476 | return result; | ||
477 | } | ||
478 | |||
479 | Decision IsPanelReachable(int panel_id) { | ||
480 | return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id)); | ||
481 | } | ||
482 | |||
483 | Decision IsPaintingReachable(int painting_id) { | ||
484 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | ||
485 | if (painting.door) { | ||
486 | return IsDoorReachable(*painting.door); | ||
310 | } | 487 | } |
311 | 488 | ||
312 | if (panel_obj.panel_door != -1 && AP_GetDoorShuffleMode() == kPANELS_MODE) { | 489 | if (panel_obj.panel_door != -1 && AP_GetDoorShuffleMode() == kPANELS_MODE) { |
@@ -417,11 +594,20 @@ class StateCalculator { | |||
417 | std::set<int> reachable_rooms_; | 594 | std::set<int> reachable_rooms_; |
418 | std::map<int, Decision> door_decisions_; | 595 | std::map<int, Decision> door_decisions_; |
419 | std::set<int> solveable_panels_; | 596 | std::set<int> solveable_panels_; |
597 | std::set<int> reachable_paintings_; | ||
598 | std::map<int, std::map<std::string, bool>> door_report_; | ||
420 | }; | 599 | }; |
421 | 600 | ||
422 | } // namespace | 601 | } // namespace |
423 | 602 | ||
603 | void ResetReachabilityRequirements() { | ||
604 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
605 | GetState().requirements.Reset(); | ||
606 | } | ||
607 | |||
424 | void RecalculateReachability() { | 608 | void RecalculateReachability() { |
609 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
610 | |||
425 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); | 611 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); |
426 | state_calculator.Calculate(); | 612 | state_calculator.Calculate(); |
427 | 613 | ||
@@ -444,10 +630,21 @@ void RecalculateReachability() { | |||
444 | } | 630 | } |
445 | } | 631 | } |
446 | 632 | ||
447 | { | 633 | std::set<int> new_reachable_doors; |
448 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 634 | for (const auto& [door_id, decision] : state_calculator.GetDoorDecisions()) { |
449 | std::swap(GetState().reachability, new_reachability); | 635 | if (decision == kYes) { |
636 | new_reachable_doors.insert(door_id); | ||
637 | } | ||
450 | } | 638 | } |
639 | |||
640 | std::set<int> reachable_paintings = state_calculator.GetReachablePaintings(); | ||
641 | std::map<int, std::map<std::string, bool>> door_reports = | ||
642 | state_calculator.GetDoorReports(); | ||
643 | |||
644 | std::swap(GetState().reachability, new_reachability); | ||
645 | std::swap(GetState().reachable_doors, new_reachable_doors); | ||
646 | std::swap(GetState().reachable_paintings, reachable_paintings); | ||
647 | std::swap(GetState().door_reports, door_reports); | ||
451 | } | 648 | } |
452 | 649 | ||
453 | bool IsLocationReachable(int location_id) { | 650 | bool IsLocationReachable(int location_id) { |
@@ -459,3 +656,21 @@ bool IsLocationReachable(int location_id) { | |||
459 | return false; | 656 | return false; |
460 | } | 657 | } |
461 | } | 658 | } |
659 | |||
660 | bool IsDoorOpen(int door_id) { | ||
661 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
662 | |||
663 | return GetState().reachable_doors.count(door_id); | ||
664 | } | ||
665 | |||
666 | bool IsPaintingReachable(int painting_id) { | ||
667 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
668 | |||
669 | return GetState().reachable_paintings.count(painting_id); | ||
670 | } | ||
671 | |||
672 | const std::map<std::string, bool>& GetDoorRequirements(int door_id) { | ||
673 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
674 | |||
675 | return GetState().door_reports.at(door_id); | ||
676 | } | ||
diff --git a/src/tracker_state.h b/src/tracker_state.h index e73607f..c7857a0 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h | |||
@@ -1,8 +1,19 @@ | |||
1 | #ifndef TRACKER_STATE_H_8639BC90 | 1 | #ifndef TRACKER_STATE_H_8639BC90 |
2 | #define TRACKER_STATE_H_8639BC90 | 2 | #define TRACKER_STATE_H_8639BC90 |
3 | 3 | ||
4 | #include <map> | ||
5 | #include <string> | ||
6 | |||
7 | void ResetReachabilityRequirements(); | ||
8 | |||
4 | void RecalculateReachability(); | 9 | void RecalculateReachability(); |
5 | 10 | ||
6 | bool IsLocationReachable(int location_id); | 11 | bool IsLocationReachable(int location_id); |
7 | 12 | ||
13 | bool IsDoorOpen(int door_id); | ||
14 | |||
15 | bool IsPaintingReachable(int painting_id); | ||
16 | |||
17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); | ||
18 | |||
8 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | 19 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ |
diff --git a/src/version.h b/src/version.h index db75351..7aab91b 100644 --- a/src/version.h +++ b/src/version.h | |||
@@ -1,7 +1,7 @@ | |||
1 | #ifndef VERSION_H_C757E53C | 1 | #ifndef VERSION_H_C757E53C |
2 | #define VERSION_H_C757E53C | 2 | #define VERSION_H_C757E53C |
3 | 3 | ||
4 | #include <iostream> | 4 | #include <sstream> |
5 | #include <regex> | 5 | #include <regex> |
6 | 6 | ||
7 | struct Version { | 7 | struct Version { |
@@ -23,6 +23,12 @@ struct Version { | |||
23 | } | 23 | } |
24 | } | 24 | } |
25 | 25 | ||
26 | std::string ToString() const { | ||
27 | std::ostringstream output; | ||
28 | output << "v" << major << "." << minor << "." << revision; | ||
29 | return output.str(); | ||
30 | } | ||
31 | |||
26 | bool operator<(const Version& rhs) const { | 32 | bool operator<(const Version& rhs) const { |
27 | return (major < rhs.major) || | 33 | return (major < rhs.major) || |
28 | (major == rhs.major && | 34 | (major == rhs.major && |
@@ -31,8 +37,6 @@ struct Version { | |||
31 | } | 37 | } |
32 | }; | 38 | }; |
33 | 39 | ||
34 | std::ostream& operator<<(std::ostream& out, const Version& ver); | 40 | constexpr const Version kTrackerVersion = Version(0, 10, 2); |
35 | |||
36 | constexpr const Version kTrackerVersion = Version(0, 9, 0); | ||
37 | 41 | ||
38 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file | 42 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file |