about summary refs log tree commit diff stats
path: root/src/tracker_state.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tracker_state.cpp')
-rw-r--r--src/tracker_state.cpp251
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
17namespace { 18namespace {
@@ -19,11 +20,13 @@ namespace {
19struct Requirements { 20struct 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 {
148struct TrackerState { 167struct 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
158enum Decision { kYes, kNo, kMaybe }; 183enum Decision { kYes, kNo, kMaybe };
@@ -167,6 +192,11 @@ class StateCalculator;
167struct StateCalculatorOptions { 192struct 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
627void ResetReachabilityRequirements() { 692void 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
632void RecalculateReachability() { 757void 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
842bool 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
852bool 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
862bool 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}