diff options
Diffstat (limited to 'src/tracker_state.cpp')
-rw-r--r-- | src/tracker_state.cpp | 251 |
1 files changed, 207 insertions, 44 deletions
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 2ee705c..bf2725a 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp | |||
@@ -12,6 +12,7 @@ | |||
12 | 12 | ||
13 | #include "ap_state.h" | 13 | #include "ap_state.h" |
14 | #include "game_data.h" | 14 | #include "game_data.h" |
15 | #include "global.h" | ||
15 | #include "logger.h" | 16 | #include "logger.h" |
16 | 17 | ||
17 | namespace { | 18 | namespace { |
@@ -19,11 +20,13 @@ namespace { | |||
19 | struct Requirements { | 20 | struct Requirements { |
20 | bool disabled = false; | 21 | bool disabled = false; |
21 | 22 | ||
22 | std::set<int> doors; // non-grouped, handles progressive | 23 | std::set<int> doors; // non-grouped, handles progressive |
23 | std::set<int> items; // all other items | 24 | std::set<int> panel_doors; // non-grouped, handles progressive |
24 | std::set<int> rooms; // maybe | 25 | std::set<int> items; // all other items |
25 | bool mastery = false; // maybe | 26 | std::set<int> rooms; // maybe |
26 | bool panel_hunt = false; // maybe | 27 | bool mastery = false; // maybe |
28 | bool panel_hunt = false; // maybe | ||
29 | bool postgame = false; | ||
27 | 30 | ||
28 | void Merge(const Requirements& rhs) { | 31 | void Merge(const Requirements& rhs) { |
29 | if (rhs.disabled) { | 32 | if (rhs.disabled) { |
@@ -33,6 +36,9 @@ struct Requirements { | |||
33 | for (int id : rhs.doors) { | 36 | for (int id : rhs.doors) { |
34 | doors.insert(id); | 37 | doors.insert(id); |
35 | } | 38 | } |
39 | for (int id : rhs.panel_doors) { | ||
40 | panel_doors.insert(id); | ||
41 | } | ||
36 | for (int id : rhs.items) { | 42 | for (int id : rhs.items) { |
37 | items.insert(id); | 43 | items.insert(id); |
38 | } | 44 | } |
@@ -41,6 +47,7 @@ struct Requirements { | |||
41 | } | 47 | } |
42 | mastery = mastery || rhs.mastery; | 48 | mastery = mastery || rhs.mastery; |
43 | panel_hunt = panel_hunt || rhs.panel_hunt; | 49 | panel_hunt = panel_hunt || rhs.panel_hunt; |
50 | postgame = postgame || rhs.postgame; | ||
44 | } | 51 | } |
45 | }; | 52 | }; |
46 | 53 | ||
@@ -78,15 +85,12 @@ class RequirementCalculator { | |||
78 | requirements.doors.insert(door_obj.id); | 85 | requirements.doors.insert(door_obj.id); |
79 | break; | 86 | break; |
80 | } | 87 | } |
81 | } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { | 88 | } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { |
82 | requirements.rooms.insert(door_obj.room); | ||
83 | |||
84 | for (int panel_id : door_obj.panels) { | 89 | for (int panel_id : door_obj.panels) { |
85 | const Requirements& panel_reqs = GetPanel(panel_id); | 90 | const Requirements& panel_reqs = GetPanel(panel_id); |
86 | requirements.Merge(panel_reqs); | 91 | requirements.Merge(panel_reqs); |
87 | } | 92 | } |
88 | } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && | 93 | } else if (AP_AreDoorsGrouped() && !door_obj.group_name.empty()) { |
89 | !door_obj.group_name.empty()) { | ||
90 | requirements.items.insert(door_obj.group_ap_item_id); | 94 | requirements.items.insert(door_obj.group_ap_item_id); |
91 | } else { | 95 | } else { |
92 | requirements.doors.insert(door_obj.id); | 96 | requirements.doors.insert(door_obj.id); |
@@ -133,6 +137,21 @@ class RequirementCalculator { | |||
133 | requirements.items.insert(GD_GetItemIdForColor(color)); | 137 | requirements.items.insert(GD_GetItemIdForColor(color)); |
134 | } | 138 | } |
135 | } | 139 | } |
140 | |||
141 | if (panel_obj.panel_door != -1 && | ||
142 | AP_GetDoorShuffleMode() == kPANELS_MODE) { | ||
143 | const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_obj.panel_door); | ||
144 | |||
145 | if (panel_door_obj.group_ap_item_id != -1 && AP_AreDoorsGrouped()) { | ||
146 | requirements.items.insert(panel_door_obj.group_ap_item_id); | ||
147 | } else { | ||
148 | requirements.panel_doors.insert(panel_obj.panel_door); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | if (panel_obj.location_name == GetWinCondition()) { | ||
153 | requirements.postgame = true; | ||
154 | } | ||
136 | 155 | ||
137 | panels_[panel_id] = requirements; | 156 | panels_[panel_id] = requirements; |
138 | } | 157 | } |
@@ -148,11 +167,17 @@ class RequirementCalculator { | |||
148 | struct TrackerState { | 167 | struct TrackerState { |
149 | std::map<int, bool> reachability; | 168 | std::map<int, bool> reachability; |
150 | std::set<int> reachable_doors; | 169 | std::set<int> reachable_doors; |
170 | std::set<int> solveable_panels; | ||
151 | std::set<int> reachable_paintings; | 171 | std::set<int> reachable_paintings; |
152 | std::mutex reachability_mutex; | 172 | std::mutex reachability_mutex; |
153 | RequirementCalculator requirements; | 173 | RequirementCalculator requirements; |
154 | std::map<int, std::map<std::string, bool>> door_reports; | 174 | std::map<int, std::map<std::string, bool>> door_reports; |
155 | bool pilgrimage_doable = false; | 175 | bool pilgrimage_doable = false; |
176 | |||
177 | // If these are empty, it actually means everything is non-postgame. | ||
178 | std::set<int> non_postgame_areas; | ||
179 | std::set<int> non_postgame_locations; | ||
180 | std::set<int> non_postgame_paintings; | ||
156 | }; | 181 | }; |
157 | 182 | ||
158 | enum Decision { kYes, kNo, kMaybe }; | 183 | enum Decision { kYes, kNo, kMaybe }; |
@@ -167,6 +192,11 @@ class StateCalculator; | |||
167 | struct StateCalculatorOptions { | 192 | struct StateCalculatorOptions { |
168 | int start; | 193 | int start; |
169 | bool pilgrimage = false; | 194 | bool pilgrimage = false; |
195 | |||
196 | // Treats all items as collected and all paintings as checked, but postgame | ||
197 | // areas cannot be reached. | ||
198 | bool postgame_detection = false; | ||
199 | |||
170 | StateCalculator* parent = nullptr; | 200 | StateCalculator* parent = nullptr; |
171 | }; | 201 | }; |
172 | 202 | ||
@@ -177,7 +207,21 @@ class StateCalculator { | |||
177 | explicit StateCalculator(StateCalculatorOptions options) | 207 | explicit StateCalculator(StateCalculatorOptions options) |
178 | : options_(options) {} | 208 | : options_(options) {} |
179 | 209 | ||
210 | void PreloadPanels(const std::set<int>& panels) { | ||
211 | solveable_panels_ = panels; | ||
212 | } | ||
213 | |||
214 | void PreloadDoors(const std::set<int>& doors) { | ||
215 | for (int door_id : doors) { | ||
216 | door_decisions_[door_id] = kYes; | ||
217 | } | ||
218 | } | ||
219 | |||
180 | void Calculate() { | 220 | void Calculate() { |
221 | painting_mapping_ = AP_GetPaintingMapping(); | ||
222 | checked_paintings_ = AP_GetCheckedPaintings(); | ||
223 | sunwarp_mapping_ = AP_GetSunwarpMapping(); | ||
224 | |||
181 | std::list<int> panel_boundary; | 225 | std::list<int> panel_boundary; |
182 | std::list<int> painting_boundary; | 226 | std::list<int> painting_boundary; |
183 | std::list<Exit> flood_boundary; | 227 | std::list<Exit> flood_boundary; |
@@ -217,12 +261,13 @@ class StateCalculator { | |||
217 | reachable_changed = true; | 261 | reachable_changed = true; |
218 | 262 | ||
219 | PaintingExit cur_painting = GD_GetPaintingExit(painting_id); | 263 | PaintingExit cur_painting = GD_GetPaintingExit(painting_id); |
220 | if (AP_GetPaintingMapping().count(cur_painting.internal_id) && | 264 | if (painting_mapping_.count(cur_painting.internal_id) && |
221 | AP_GetCheckedPaintings().count(cur_painting.internal_id)) { | 265 | (checked_paintings_.count(cur_painting.internal_id) || |
266 | options_.postgame_detection)) { | ||
222 | Exit painting_exit; | 267 | Exit painting_exit; |
223 | PaintingExit target_painting = | 268 | PaintingExit target_painting = |
224 | GD_GetPaintingExit(GD_GetPaintingByName( | 269 | GD_GetPaintingExit(GD_GetPaintingByName( |
225 | AP_GetPaintingMapping().at(cur_painting.internal_id))); | 270 | painting_mapping_.at(cur_painting.internal_id))); |
226 | painting_exit.source_room = cur_painting.room; | 271 | painting_exit.source_room = cur_painting.room; |
227 | painting_exit.destination_room = target_painting.room; | 272 | painting_exit.destination_room = target_painting.room; |
228 | painting_exit.type = EntranceType::kPainting; | 273 | painting_exit.type = EntranceType::kPainting; |
@@ -281,8 +326,8 @@ class StateCalculator { | |||
281 | 326 | ||
282 | if (AP_IsSunwarpShuffle()) { | 327 | if (AP_IsSunwarpShuffle()) { |
283 | for (int index : room_obj.sunwarps) { | 328 | for (int index : room_obj.sunwarps) { |
284 | if (AP_GetSunwarpMapping().count(index)) { | 329 | if (sunwarp_mapping_.count(index)) { |
285 | const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); | 330 | const SunwarpMapping& sm = sunwarp_mapping_.at(index); |
286 | 331 | ||
287 | new_boundary.push_back( | 332 | new_boundary.push_back( |
288 | {.source_room = room_exit.destination_room, | 333 | {.source_room = room_exit.destination_room, |
@@ -296,15 +341,14 @@ class StateCalculator { | |||
296 | if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { | 341 | if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { |
297 | new_boundary.push_back( | 342 | new_boundary.push_back( |
298 | {.source_room = room_exit.destination_room, | 343 | {.source_room = room_exit.destination_room, |
299 | .destination_room = GD_GetRoomByName("Outside The Undeterred"), | 344 | .destination_room = GD_GetRoomByName("Color Hallways"), |
300 | .type = EntranceType::kPainting}); | 345 | .type = EntranceType::kPainting}); |
301 | } | 346 | } |
302 | 347 | ||
303 | if (AP_IsPilgrimageEnabled()) { | 348 | if (AP_IsPilgrimageEnabled()) { |
304 | int pilgrimage_start_id = GD_GetRoomByName("Hub Room"); | 349 | int pilgrimage_start_id = GD_GetRoomByName("Hub Room"); |
305 | if (AP_IsSunwarpShuffle()) { | 350 | if (AP_IsSunwarpShuffle()) { |
306 | for (const auto& [start_index, mapping] : | 351 | for (const auto& [start_index, mapping] : sunwarp_mapping_) { |
307 | AP_GetSunwarpMapping()) { | ||
308 | if (mapping.dots == 1) { | 352 | if (mapping.dots == 1) { |
309 | pilgrimage_start_id = GD_GetRoomForSunwarp(start_index); | 353 | pilgrimage_start_id = GD_GetRoomForSunwarp(start_index); |
310 | } | 354 | } |
@@ -343,6 +387,10 @@ class StateCalculator { | |||
343 | // evaluated. | 387 | // evaluated. |
344 | for (const Door& door : GD_GetDoors()) { | 388 | for (const Door& door : GD_GetDoors()) { |
345 | int discard = IsDoorReachable(door.id); | 389 | int discard = IsDoorReachable(door.id); |
390 | |||
391 | door_report_[door.id] = {}; | ||
392 | discard = AreRequirementsSatisfied( | ||
393 | GetState().requirements.GetDoor(door.id), &door_report_[door.id]); | ||
346 | } | 394 | } |
347 | } | 395 | } |
348 | 396 | ||
@@ -378,7 +426,8 @@ class StateCalculator { | |||
378 | } | 426 | } |
379 | 427 | ||
380 | private: | 428 | private: |
381 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { | 429 | template <typename T> |
430 | Decision IsNonGroupedDoorReachable(const T& door_obj) { | ||
382 | bool has_item = AP_HasItem(door_obj.ap_item_id); | 431 | bool has_item = AP_HasItem(door_obj.ap_item_id); |
383 | 432 | ||
384 | if (!has_item) { | 433 | if (!has_item) { |
@@ -399,29 +448,48 @@ class StateCalculator { | |||
399 | return kNo; | 448 | return kNo; |
400 | } | 449 | } |
401 | 450 | ||
451 | if (reqs.postgame && options_.postgame_detection) { | ||
452 | return kNo; | ||
453 | } | ||
454 | |||
402 | Decision final_decision = kYes; | 455 | Decision final_decision = kYes; |
403 | 456 | ||
404 | for (int door_id : reqs.doors) { | 457 | if (!options_.postgame_detection) { |
405 | const Door& door_obj = GD_GetDoor(door_id); | 458 | for (int door_id : reqs.doors) { |
406 | Decision decision = IsNonGroupedDoorReachable(door_obj); | 459 | const Door& door_obj = GD_GetDoor(door_id); |
460 | Decision decision = IsNonGroupedDoorReachable(door_obj); | ||
407 | 461 | ||
408 | if (report) { | 462 | if (report) { |
409 | (*report)[door_obj.item_name] = (decision == kYes); | 463 | (*report)[door_obj.item_name] = (decision == kYes); |
410 | } | 464 | } |
411 | 465 | ||
412 | if (decision != kYes) { | 466 | if (decision != kYes) { |
413 | final_decision = decision; | 467 | final_decision = decision; |
468 | } | ||
414 | } | 469 | } |
415 | } | ||
416 | 470 | ||
417 | for (int item_id : reqs.items) { | 471 | for (int panel_door_id : reqs.panel_doors) { |
418 | bool has_item = AP_HasItem(item_id); | 472 | const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id); |
419 | if (report) { | 473 | Decision decision = IsNonGroupedDoorReachable(panel_door_obj); |
420 | (*report)[AP_GetItemName(item_id)] = has_item; | 474 | |
475 | if (report) { | ||
476 | (*report)[panel_door_obj.item_name] = (decision == kYes); | ||
477 | } | ||
478 | |||
479 | if (decision != kYes) { | ||
480 | final_decision = decision; | ||
481 | } | ||
421 | } | 482 | } |
422 | 483 | ||
423 | if (!has_item) { | 484 | for (int item_id : reqs.items) { |
424 | final_decision = kNo; | 485 | bool has_item = AP_HasItem(item_id); |
486 | if (report) { | ||
487 | (*report)[GD_GetItemName(item_id)] = has_item; | ||
488 | } | ||
489 | |||
490 | if (!has_item) { | ||
491 | final_decision = kNo; | ||
492 | } | ||
425 | } | 493 | } |
426 | } | 494 | } |
427 | 495 | ||
@@ -490,14 +558,7 @@ class StateCalculator { | |||
490 | } | 558 | } |
491 | 559 | ||
492 | Decision IsDoorReachable_Helper(int door_id) { | 560 | Decision IsDoorReachable_Helper(int door_id) { |
493 | if (door_report_.count(door_id)) { | 561 | return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id)); |
494 | door_report_[door_id].clear(); | ||
495 | } else { | ||
496 | door_report_[door_id] = {}; | ||
497 | } | ||
498 | |||
499 | return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), | ||
500 | &door_report_[door_id]); | ||
501 | } | 562 | } |
502 | 563 | ||
503 | Decision IsDoorReachable(int door_id) { | 564 | Decision IsDoorReachable(int door_id) { |
@@ -549,7 +610,7 @@ class StateCalculator { | |||
549 | if (AP_IsSunwarpShuffle()) { | 610 | if (AP_IsSunwarpShuffle()) { |
550 | pilgrimage_pairs = std::vector<std::tuple<int, int>>(5); | 611 | pilgrimage_pairs = std::vector<std::tuple<int, int>>(5); |
551 | 612 | ||
552 | for (const auto& [start_index, mapping] : AP_GetSunwarpMapping()) { | 613 | for (const auto& [start_index, mapping] : sunwarp_mapping_) { |
553 | if (mapping.dots > 1) { | 614 | if (mapping.dots > 1) { |
554 | std::get<1>(pilgrimage_pairs[mapping.dots - 2]) = start_index; | 615 | std::get<1>(pilgrimage_pairs[mapping.dots - 2]) = start_index; |
555 | } | 616 | } |
@@ -620,23 +681,94 @@ class StateCalculator { | |||
620 | bool pilgrimage_doable_ = false; | 681 | bool pilgrimage_doable_ = false; |
621 | 682 | ||
622 | std::map<int, std::list<int>> paths_; | 683 | std::map<int, std::list<int>> paths_; |
684 | |||
685 | std::map<std::string, std::string> painting_mapping_; | ||
686 | std::set<std::string> checked_paintings_; | ||
687 | std::map<int, SunwarpMapping> sunwarp_mapping_; | ||
623 | }; | 688 | }; |
624 | 689 | ||
625 | } // namespace | 690 | } // namespace |
626 | 691 | ||
627 | void ResetReachabilityRequirements() { | 692 | void ResetReachabilityRequirements() { |
693 | TrackerLog("Resetting tracker state..."); | ||
694 | |||
628 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 695 | std::lock_guard reachability_guard(GetState().reachability_mutex); |
629 | GetState().requirements.Reset(); | 696 | GetState().requirements.Reset(); |
697 | GetState().reachable_doors.clear(); | ||
698 | GetState().solveable_panels.clear(); | ||
699 | |||
700 | if (AP_IsPostgameShuffle()) { | ||
701 | GetState().non_postgame_areas.clear(); | ||
702 | GetState().non_postgame_locations.clear(); | ||
703 | GetState().non_postgame_paintings.clear(); | ||
704 | } else { | ||
705 | StateCalculator postgame_calculator( | ||
706 | {.start = GD_GetRoomByName("Menu"), .postgame_detection = true}); | ||
707 | postgame_calculator.Calculate(); | ||
708 | |||
709 | std::set<int>& non_postgame_areas = GetState().non_postgame_areas; | ||
710 | non_postgame_areas.clear(); | ||
711 | |||
712 | std::set<int>& non_postgame_locations = GetState().non_postgame_locations; | ||
713 | non_postgame_locations.clear(); | ||
714 | |||
715 | const std::set<int>& reachable_rooms = | ||
716 | postgame_calculator.GetReachableRooms(); | ||
717 | const std::set<int>& solveable_panels = | ||
718 | postgame_calculator.GetSolveablePanels(); | ||
719 | |||
720 | for (const MapArea& map_area : GD_GetMapAreas()) { | ||
721 | bool area_reachable = false; | ||
722 | |||
723 | for (const Location& location_section : map_area.locations) { | ||
724 | bool reachable = reachable_rooms.count(location_section.room); | ||
725 | if (reachable) { | ||
726 | for (int panel_id : location_section.panels) { | ||
727 | reachable &= (solveable_panels.count(panel_id) == 1); | ||
728 | } | ||
729 | } | ||
730 | |||
731 | if (!reachable && IsLocationWinCondition(location_section)) { | ||
732 | reachable = true; | ||
733 | } | ||
734 | |||
735 | if (reachable) { | ||
736 | non_postgame_locations.insert(location_section.ap_location_id); | ||
737 | area_reachable = true; | ||
738 | } | ||
739 | } | ||
740 | |||
741 | for (int painting_id : map_area.paintings) { | ||
742 | if (postgame_calculator.GetReachablePaintings().count(painting_id)) { | ||
743 | area_reachable = true; | ||
744 | } | ||
745 | } | ||
746 | |||
747 | if (area_reachable) { | ||
748 | non_postgame_areas.insert(map_area.id); | ||
749 | } | ||
750 | } | ||
751 | |||
752 | GetState().non_postgame_paintings = | ||
753 | postgame_calculator.GetReachablePaintings(); | ||
754 | } | ||
630 | } | 755 | } |
631 | 756 | ||
632 | void RecalculateReachability() { | 757 | void RecalculateReachability() { |
758 | TrackerLog("Calculating reachability..."); | ||
759 | |||
633 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 760 | std::lock_guard reachability_guard(GetState().reachability_mutex); |
634 | 761 | ||
762 | // Receiving items and checking paintings should never remove access to doors | ||
763 | // or panels, so we can preload any doors and panels we already know are | ||
764 | // accessible from previous runs, in order to reduce the work. | ||
635 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); | 765 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); |
766 | state_calculator.PreloadDoors(GetState().reachable_doors); | ||
767 | state_calculator.PreloadPanels(GetState().solveable_panels); | ||
636 | state_calculator.Calculate(); | 768 | state_calculator.Calculate(); |
637 | 769 | ||
638 | const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms(); | 770 | const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms(); |
639 | const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels(); | 771 | std::set<int> solveable_panels = state_calculator.GetSolveablePanels(); |
640 | 772 | ||
641 | std::map<int, bool> new_reachability; | 773 | std::map<int, bool> new_reachability; |
642 | for (const MapArea& map_area : GD_GetMapAreas()) { | 774 | for (const MapArea& map_area : GD_GetMapAreas()) { |
@@ -667,6 +799,7 @@ void RecalculateReachability() { | |||
667 | 799 | ||
668 | std::swap(GetState().reachability, new_reachability); | 800 | std::swap(GetState().reachability, new_reachability); |
669 | std::swap(GetState().reachable_doors, new_reachable_doors); | 801 | std::swap(GetState().reachable_doors, new_reachable_doors); |
802 | std::swap(GetState().solveable_panels, solveable_panels); | ||
670 | std::swap(GetState().reachable_paintings, reachable_paintings); | 803 | std::swap(GetState().reachable_paintings, reachable_paintings); |
671 | std::swap(GetState().door_reports, door_reports); | 804 | std::swap(GetState().door_reports, door_reports); |
672 | GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable(); | 805 | GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable(); |
@@ -705,3 +838,33 @@ bool IsPilgrimageDoable() { | |||
705 | 838 | ||
706 | return GetState().pilgrimage_doable; | 839 | return GetState().pilgrimage_doable; |
707 | } | 840 | } |
841 | |||
842 | bool IsAreaPostgame(int area_id) { | ||
843 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
844 | |||
845 | if (GetState().non_postgame_areas.empty()) { | ||
846 | return false; | ||
847 | } else { | ||
848 | return !GetState().non_postgame_areas.count(area_id); | ||
849 | } | ||
850 | } | ||
851 | |||
852 | bool IsLocationPostgame(int location_id) { | ||
853 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
854 | |||
855 | if (GetState().non_postgame_locations.empty()) { | ||
856 | return false; | ||
857 | } else { | ||
858 | return !GetState().non_postgame_locations.count(location_id); | ||
859 | } | ||
860 | } | ||
861 | |||
862 | bool IsPaintingPostgame(int painting_id) { | ||
863 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
864 | |||
865 | if (GetState().non_postgame_paintings.empty()) { | ||
866 | return false; | ||
867 | } else { | ||
868 | return !GetState().non_postgame_paintings.count(painting_id); | ||
869 | } | ||
870 | } | ||