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.cpp646
1 files changed, 529 insertions, 117 deletions
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 5588c7f..674f68a 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -1,5 +1,8 @@
1#include "tracker_state.h" 1#include "tracker_state.h"
2 2
3#include <fmt/core.h>
4#include <hkutil/string.h>
5
3#include <list> 6#include <list>
4#include <map> 7#include <map>
5#include <mutex> 8#include <mutex>
@@ -9,13 +12,172 @@
9 12
10#include "ap_state.h" 13#include "ap_state.h"
11#include "game_data.h" 14#include "game_data.h"
15#include "global.h"
16#include "logger.h"
12 17
13namespace { 18namespace {
14 19
20struct Requirements {
21 bool disabled = false;
22
23 std::set<int> doors; // non-grouped, handles progressive
24 std::set<int> panel_doors; // non-grouped, handles progressive
25 std::set<int> items; // all other items
26 std::set<int> rooms; // maybe
27 bool mastery = false; // maybe
28 bool panel_hunt = false; // maybe
29 bool postgame = false;
30
31 void Merge(const Requirements& rhs) {
32 if (rhs.disabled) {
33 return;
34 }
35
36 for (int id : rhs.doors) {
37 doors.insert(id);
38 }
39 for (int id : rhs.panel_doors) {
40 panel_doors.insert(id);
41 }
42 for (int id : rhs.items) {
43 items.insert(id);
44 }
45 for (int id : rhs.rooms) {
46 rooms.insert(id);
47 }
48 mastery = mastery || rhs.mastery;
49 panel_hunt = panel_hunt || rhs.panel_hunt;
50 postgame = postgame || rhs.postgame;
51 }
52};
53
54class RequirementCalculator {
55 public:
56 void Reset() {
57 doors_.clear();
58 panels_.clear();
59 }
60
61 const Requirements& GetDoor(int door_id) {
62 if (!doors_.count(door_id)) {
63 Requirements requirements;
64 const Door& door_obj = GD_GetDoor(door_id);
65
66 if (door_obj.type == DoorType::kSunPainting) {
67 if (!AP_IsPilgrimageEnabled()) {
68 requirements.items.insert(door_obj.ap_item_id);
69 } else {
70 requirements.disabled = true;
71 }
72 } else if (door_obj.type == DoorType::kSunwarp) {
73 switch (AP_GetSunwarpAccess()) {
74 case kSUNWARP_ACCESS_NORMAL:
75 // Do nothing.
76 break;
77 case kSUNWARP_ACCESS_DISABLED:
78 requirements.disabled = true;
79 break;
80 case kSUNWARP_ACCESS_UNLOCK:
81 requirements.items.insert(door_obj.group_ap_item_id);
82 break;
83 case kSUNWARP_ACCESS_INDIVIDUAL:
84 case kSUNWARP_ACCESS_PROGRESSIVE:
85 requirements.doors.insert(door_obj.id);
86 break;
87 }
88 } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) {
89 for (int panel_id : door_obj.panels) {
90 const Requirements& panel_reqs = GetPanel(panel_id);
91 requirements.Merge(panel_reqs);
92 }
93 } else if (AP_AreDoorsGrouped() && !door_obj.group_name.empty()) {
94 requirements.items.insert(door_obj.group_ap_item_id);
95 } else {
96 requirements.doors.insert(door_obj.id);
97 }
98
99 doors_[door_id] = requirements;
100 }
101
102 return doors_[door_id];
103 }
104
105 const Requirements& GetPanel(int panel_id) {
106 if (!panels_.count(panel_id)) {
107 Requirements requirements;
108 const Panel& panel_obj = GD_GetPanel(panel_id);
109
110 requirements.rooms.insert(panel_obj.room);
111
112 if (panel_obj.name == "THE MASTER") {
113 requirements.mastery = true;
114 }
115
116 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
117 AP_GetLevel2Requirement() > 1) {
118 requirements.panel_hunt = true;
119 }
120
121 for (int room_id : panel_obj.required_rooms) {
122 requirements.rooms.insert(room_id);
123 }
124
125 for (int door_id : panel_obj.required_doors) {
126 const Requirements& door_reqs = GetDoor(door_id);
127 requirements.Merge(door_reqs);
128 }
129
130 for (int panel_id : panel_obj.required_panels) {
131 const Requirements& panel_reqs = GetPanel(panel_id);
132 requirements.Merge(panel_reqs);
133 }
134
135 if (AP_IsColorShuffle()) {
136 for (LingoColor color : panel_obj.colors) {
137 requirements.items.insert(GD_GetItemIdForColor(color));
138 }
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 }
155
156 panels_[panel_id] = requirements;
157 }
158
159 return panels_[panel_id];
160 }
161
162 private:
163 std::map<int, Requirements> doors_;
164 std::map<int, Requirements> panels_;
165};
166
15struct TrackerState { 167struct TrackerState {
16 std::map<int, bool> reachability; 168 std::map<int, bool> reachability;
17 std::set<int> reachable_doors; 169 std::set<int> reachable_doors;
170 std::set<int> solveable_panels;
171 std::set<int> reachable_paintings;
18 std::mutex reachability_mutex; 172 std::mutex reachability_mutex;
173 RequirementCalculator requirements;
174 std::map<int, std::map<std::string, bool>> door_reports;
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;
19}; 181};
20 182
21enum Decision { kYes, kNo, kMaybe }; 183enum Decision { kYes, kNo, kMaybe };
@@ -30,6 +192,11 @@ class StateCalculator;
30struct StateCalculatorOptions { 192struct StateCalculatorOptions {
31 int start; 193 int start;
32 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
33 StateCalculator* parent = nullptr; 200 StateCalculator* parent = nullptr;
34}; 201};
35 202
@@ -40,15 +207,33 @@ class StateCalculator {
40 explicit StateCalculator(StateCalculatorOptions options) 207 explicit StateCalculator(StateCalculatorOptions options)
41 : options_(options) {} 208 : options_(options) {}
42 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
43 void Calculate() { 220 void Calculate() {
221 painting_mapping_ = AP_GetPaintingMapping();
222 checked_paintings_ = AP_GetCheckedPaintings();
223 sunwarp_mapping_ = AP_GetSunwarpMapping();
224
44 std::list<int> panel_boundary; 225 std::list<int> panel_boundary;
226 std::list<int> painting_boundary;
45 std::list<Exit> flood_boundary; 227 std::list<Exit> flood_boundary;
46 flood_boundary.push_back({.destination_room = options_.start}); 228 flood_boundary.push_back(
229 {.source_room = -1, .destination_room = options_.start});
47 230
48 bool reachable_changed = true; 231 bool reachable_changed = true;
49 while (reachable_changed) { 232 while (reachable_changed) {
50 reachable_changed = false; 233 reachable_changed = false;
51 234
235 std::list<Exit> new_boundary;
236
52 std::list<int> new_panel_boundary; 237 std::list<int> new_panel_boundary;
53 for (int panel_id : panel_boundary) { 238 for (int panel_id : panel_boundary) {
54 if (solveable_panels_.count(panel_id)) { 239 if (solveable_panels_.count(panel_id)) {
@@ -64,7 +249,36 @@ class StateCalculator {
64 } 249 }
65 } 250 }
66 251
67 std::list<Exit> new_boundary; 252 std::list<int> new_painting_boundary;
253 for (int painting_id : painting_boundary) {
254 if (reachable_paintings_.count(painting_id)) {
255 continue;
256 }
257
258 Decision painting_reachable = IsPaintingReachable(painting_id);
259 if (painting_reachable == kYes) {
260 reachable_paintings_.insert(painting_id);
261 reachable_changed = true;
262
263 PaintingExit cur_painting = GD_GetPaintingExit(painting_id);
264 if (painting_mapping_.count(cur_painting.internal_id) &&
265 (checked_paintings_.count(cur_painting.internal_id) ||
266 options_.postgame_detection)) {
267 Exit painting_exit;
268 PaintingExit target_painting =
269 GD_GetPaintingExit(GD_GetPaintingByName(
270 painting_mapping_.at(cur_painting.internal_id)));
271 painting_exit.source_room = cur_painting.room;
272 painting_exit.destination_room = target_painting.room;
273 painting_exit.type = EntranceType::kPainting;
274
275 new_boundary.push_back(painting_exit);
276 }
277 } else if (painting_reachable == kMaybe) {
278 new_painting_boundary.push_back(painting_id);
279 }
280 }
281
68 for (const Exit& room_exit : flood_boundary) { 282 for (const Exit& room_exit : flood_boundary) {
69 if (reachable_rooms_.count(room_exit.destination_room)) { 283 if (reachable_rooms_.count(room_exit.destination_room)) {
70 continue; 284 continue;
@@ -83,6 +297,12 @@ class StateCalculator {
83 reachable_rooms_.insert(room_exit.destination_room); 297 reachable_rooms_.insert(room_exit.destination_room);
84 reachable_changed = true; 298 reachable_changed = true;
85 299
300#ifndef NDEBUG
301 std::list<int> room_path = paths_[room_exit.source_room];
302 room_path.push_back(room_exit.destination_room);
303 paths_[room_exit.destination_room] = room_path;
304#endif
305
86 const Room& room_obj = GD_GetRoom(room_exit.destination_room); 306 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
87 for (const Exit& out_edge : room_obj.exits) { 307 for (const Exit& out_edge : room_obj.exits) {
88 if (out_edge.type == EntranceType::kPainting && 308 if (out_edge.type == EntranceType::kPainting &&
@@ -99,52 +319,56 @@ class StateCalculator {
99 } 319 }
100 320
101 if (AP_IsPaintingShuffle()) { 321 if (AP_IsPaintingShuffle()) {
102 for (const PaintingExit& out_edge : room_obj.paintings) { 322 for (int out_edge : room_obj.paintings) {
103 if (AP_GetPaintingMapping().count(out_edge.id)) { 323 new_painting_boundary.push_back(out_edge);
104 Exit painting_exit;
105 painting_exit.destination_room = GD_GetRoomForPainting(
106 AP_GetPaintingMapping().at(out_edge.id));
107 painting_exit.door = out_edge.door;
108
109 new_boundary.push_back(painting_exit);
110 }
111 } 324 }
112 } 325 }
113 326
114 if (AP_IsSunwarpShuffle()) { 327 if (AP_IsSunwarpShuffle()) {
115 for (int index : room_obj.sunwarps) { 328 for (int index : room_obj.sunwarps) {
116 if (AP_GetSunwarpMapping().count(index)) { 329 if (sunwarp_mapping_.count(index)) {
117 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); 330 const SunwarpMapping& sm = sunwarp_mapping_.at(index);
118 331
119 Exit sunwarp_exit; 332 new_boundary.push_back(
120 sunwarp_exit.destination_room = 333 {.source_room = room_exit.destination_room,
121 GD_GetRoomForSunwarp(sm.exit_index); 334 .destination_room = GD_GetRoomForSunwarp(sm.exit_index),
122 sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1); 335 .door = GD_GetSunwarpDoors().at(sm.dots - 1),
123 336 .type = EntranceType::kSunwarp});
124 new_boundary.push_back(sunwarp_exit);
125 } 337 }
126 } 338 }
127 } 339 }
128 340
129 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { 341 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") {
130 new_boundary.push_back( 342 new_boundary.push_back(
131 {.destination_room = GD_GetRoomByName("Outside The Undeterred"), 343 {.source_room = room_exit.destination_room,
132 .type = EntranceType::kPainting}); 344 .destination_room = GD_GetRoomByName("Color Hallways"),
345 .type = EntranceType::kStaticPainting});
133 } 346 }
134 347
135 if (AP_IsPilgrimageEnabled()) { 348 if (AP_IsPilgrimageEnabled()) {
136 if (room_obj.name == "Hub Room") { 349 int pilgrimage_start_id = GD_GetRoomByName("Hub Room");
350 if (AP_IsSunwarpShuffle()) {
351 for (const auto& [start_index, mapping] : sunwarp_mapping_) {
352 if (mapping.dots == 1) {
353 pilgrimage_start_id = GD_GetRoomForSunwarp(start_index);
354 }
355 }
356 }
357
358 if (room_exit.destination_room == pilgrimage_start_id) {
137 new_boundary.push_back( 359 new_boundary.push_back(
138 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), 360 {.source_room = room_exit.destination_room,
361 .destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
139 .type = EntranceType::kPilgrimage}); 362 .type = EntranceType::kPilgrimage});
140 } 363 }
141 } else { 364 } else {
142 if (room_obj.name == "Starting Room") { 365 if (room_obj.name == "Starting Room") {
143 new_boundary.push_back( 366 new_boundary.push_back(
144 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), 367 {.source_room = room_exit.destination_room,
368 .destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
145 .door = 369 .door =
146 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), 370 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"),
147 .type = EntranceType::kPainting}); 371 .type = EntranceType::kStaticPainting});
148 } 372 }
149 } 373 }
150 374
@@ -156,11 +380,17 @@ class StateCalculator {
156 380
157 flood_boundary = new_boundary; 381 flood_boundary = new_boundary;
158 panel_boundary = new_panel_boundary; 382 panel_boundary = new_panel_boundary;
383 painting_boundary = new_painting_boundary;
159 } 384 }
160 385
161 // Now that we know the full reachable area, let's make sure all doors are evaluated. 386 // Now that we know the full reachable area, let's make sure all doors are
387 // evaluated.
162 for (const Door& door : GD_GetDoors()) { 388 for (const Door& door : GD_GetDoors()) {
163 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]);
164 } 394 }
165 } 395 }
166 396
@@ -172,8 +402,32 @@ class StateCalculator {
172 402
173 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } 403 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; }
174 404
405 const std::set<int>& GetReachablePaintings() const {
406 return reachable_paintings_;
407 }
408
409 const std::map<int, std::map<std::string, bool>>& GetDoorReports() const {
410 return door_report_;
411 }
412
413 bool IsPilgrimageDoable() const { return pilgrimage_doable_; }
414
415 std::string GetPathToRoom(int room_id) const {
416 if (!paths_.count(room_id)) {
417 return "";
418 }
419
420 const std::list<int>& path = paths_.at(room_id);
421 std::vector<std::string> room_names;
422 for (int room_id : path) {
423 room_names.push_back(GD_GetRoom(room_id).name);
424 }
425 return hatkirby::implode(room_names, " -> ");
426 }
427
175 private: 428 private:
176 Decision IsNonGroupedDoorReachable(const Door& door_obj) { 429 template <typename T>
430 Decision IsNonGroupedDoorReachable(const T& door_obj) {
177 bool has_item = AP_HasItem(door_obj.ap_item_id); 431 bool has_item = AP_HasItem(door_obj.ap_item_id);
178 432
179 if (!has_item) { 433 if (!has_item) {
@@ -188,68 +442,71 @@ class StateCalculator {
188 return has_item ? kYes : kNo; 442 return has_item ? kYes : kNo;
189 } 443 }
190 444
191 Decision IsDoorReachable_Helper(int door_id) { 445 Decision AreRequirementsSatisfied(
192 const Door& door_obj = GD_GetDoor(door_id); 446 const Requirements& reqs, std::map<std::string, bool>* report = nullptr) {
193 447 if (reqs.disabled) {
194 if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { 448 return kNo;
195 return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; 449 }
196 } else if (door_obj.type == DoorType::kSunwarp) { 450
197 switch (AP_GetSunwarpAccess()) { 451 if (reqs.postgame && options_.postgame_detection) {
198 case kSUNWARP_ACCESS_NORMAL: 452 return kNo;
199 return kYes; 453 }
200 case kSUNWARP_ACCESS_DISABLED: 454
201 return kNo; 455 Decision final_decision = kYes;
202 case kSUNWARP_ACCESS_UNLOCK: 456
203 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; 457 if (!options_.postgame_detection) {
204 case kSUNWARP_ACCESS_INDIVIDUAL: 458 for (int door_id : reqs.doors) {
205 case kSUNWARP_ACCESS_PROGRESSIVE: 459 const Door& door_obj = GD_GetDoor(door_id);
206 return IsNonGroupedDoorReachable(door_obj); 460 Decision decision = IsNonGroupedDoorReachable(door_obj);
207 } 461
208 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { 462 if (report) {
209 if (!reachable_rooms_.count(door_obj.room)) { 463 (*report)[door_obj.item_name] = (decision == kYes);
210 return kMaybe; 464 }
211 } 465
212 466 if (decision != kYes) {
213 for (int panel_id : door_obj.panels) { 467 final_decision = decision;
214 if (!solveable_panels_.count(panel_id)) {
215 return kMaybe;
216 } 468 }
217 } 469 }
218 470
219 return kYes; 471 for (int panel_door_id : reqs.panel_doors) {
220 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && 472 const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id);
221 !door_obj.group_name.empty()) { 473 Decision decision = IsNonGroupedDoorReachable(panel_door_obj);
222 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
223 } else {
224 return IsNonGroupedDoorReachable(door_obj);
225 }
226 }
227 474
228 Decision IsDoorReachable(int door_id) { 475 if (report) {
229 if (options_.parent) { 476 (*report)[panel_door_obj.item_name] = (decision == kYes);
230 return options_.parent->IsDoorReachable(door_id); 477 }
231 }
232 478
233 if (door_decisions_.count(door_id)) { 479 if (decision != kYes) {
234 return door_decisions_.at(door_id); 480 final_decision = decision;
235 } 481 }
482 }
236 483
237 Decision result = IsDoorReachable_Helper(door_id); 484 for (int item_id : reqs.items) {
238 if (result != kMaybe) { 485 bool has_item = AP_HasItem(item_id);
239 door_decisions_[door_id] = result; 486 if (report) {
487 (*report)[GD_GetItemName(item_id)] = has_item;
488 }
489
490 if (!has_item) {
491 final_decision = kNo;
492 }
493 }
240 } 494 }
241 495
242 return result; 496 for (int room_id : reqs.rooms) {
243 } 497 bool reachable = reachable_rooms_.count(room_id);
244 498
245 Decision IsPanelReachable(int panel_id) { 499 if (report) {
246 const Panel& panel_obj = GD_GetPanel(panel_id); 500 std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\"";
501 (*report)[report_name] = reachable;
502 }
247 503
248 if (!reachable_rooms_.count(panel_obj.room)) { 504 if (!reachable && final_decision != kNo) {
249 return kMaybe; 505 final_decision = kMaybe;
506 }
250 } 507 }
251 508
252 if (panel_obj.name == "THE MASTER") { 509 if (reqs.mastery) {
253 int achievements_accessible = 0; 510 int achievements_accessible = 0;
254 511
255 for (int achieve_id : GD_GetAchievementPanels()) { 512 for (int achieve_id : GD_GetAchievementPanels()) {
@@ -262,12 +519,18 @@ class StateCalculator {
262 } 519 }
263 } 520 }
264 521
265 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 522 bool can_mastery =
266 : kMaybe; 523 (achievements_accessible >= AP_GetMasteryRequirement());
524 if (report) {
525 (*report)["Mastery"] = can_mastery;
526 }
527
528 if (!can_mastery && final_decision != kNo) {
529 final_decision = kMaybe;
530 }
267 } 531 }
268 532
269 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 533 if (reqs.panel_hunt) {
270 AP_GetLevel2Requirement() > 1) {
271 int counting_panels_accessible = 0; 534 int counting_panels_accessible = 0;
272 535
273 for (int solved_panel_id : solveable_panels_) { 536 for (int solved_panel_id : solveable_panels_) {
@@ -278,41 +541,51 @@ class StateCalculator {
278 } 541 }
279 } 542 }
280 543
281 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 544 bool can_level2 =
282 ? kYes 545 (counting_panels_accessible >= AP_GetLevel2Requirement() - 1);
283 : kMaybe; 546 if (report) {
284 } 547 std::string report_name =
548 std::to_string(AP_GetLevel2Requirement()) + " Panels";
549 (*report)[report_name] = can_level2;
550 }
285 551
286 for (int room_id : panel_obj.required_rooms) { 552 if (!can_level2 && final_decision != kNo) {
287 if (!reachable_rooms_.count(room_id)) { 553 final_decision = kMaybe;
288 return kMaybe;
289 } 554 }
290 } 555 }
291 556
292 for (int door_id : panel_obj.required_doors) { 557 return final_decision;
293 Decision door_reachable = IsDoorReachable(door_id); 558 }
294 if (door_reachable == kNo) { 559
295 const Door& door_obj = GD_GetDoor(door_id); 560 Decision IsDoorReachable_Helper(int door_id) {
296 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) 561 return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id));
297 ? kMaybe 562 }
298 : kNo; 563
299 } else if (door_reachable == kMaybe) { 564 Decision IsDoorReachable(int door_id) {
300 return kMaybe; 565 if (options_.parent) {
301 } 566 return options_.parent->IsDoorReachable(door_id);
302 } 567 }
303 568
304 for (int panel_id : panel_obj.required_panels) { 569 if (door_decisions_.count(door_id)) {
305 if (!solveable_panels_.count(panel_id)) { 570 return door_decisions_.at(door_id);
306 return kMaybe;
307 }
308 } 571 }
309 572
310 if (AP_IsColorShuffle()) { 573 Decision result = IsDoorReachable_Helper(door_id);
311 for (LingoColor color : panel_obj.colors) { 574 if (result != kMaybe) {
312 if (!AP_HasItem(GD_GetItemIdForColor(color))) { 575 door_decisions_[door_id] = result;
313 return kNo; 576 }
314 } 577
315 } 578 return result;
579 }
580
581 Decision IsPanelReachable(int panel_id) {
582 return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id));
583 }
584
585 Decision IsPaintingReachable(int painting_id) {
586 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
587 if (painting.door) {
588 return IsDoorReachable(*painting.door);
316 } 589 }
317 590
318 return kYes; 591 return kYes;
@@ -337,7 +610,7 @@ class StateCalculator {
337 if (AP_IsSunwarpShuffle()) { 610 if (AP_IsSunwarpShuffle()) {
338 pilgrimage_pairs = std::vector<std::tuple<int, int>>(5); 611 pilgrimage_pairs = std::vector<std::tuple<int, int>>(5);
339 612
340 for (const auto& [start_index, mapping] : AP_GetSunwarpMapping()) { 613 for (const auto& [start_index, mapping] : sunwarp_mapping_) {
341 if (mapping.dots > 1) { 614 if (mapping.dots > 1) {
342 std::get<1>(pilgrimage_pairs[mapping.dots - 2]) = start_index; 615 std::get<1>(pilgrimage_pairs[mapping.dots - 2]) = start_index;
343 } 616 }
@@ -363,6 +636,8 @@ class StateCalculator {
363 } 636 }
364 } 637 }
365 638
639 pilgrimage_doable_ = true;
640
366 return kYes; 641 return kYes;
367 } 642 }
368 643
@@ -375,7 +650,8 @@ class StateCalculator {
375 !AP_DoesPilgrimageAllowRoofAccess()) { 650 !AP_DoesPilgrimageAllowRoofAccess()) {
376 return kNo; 651 return kNo;
377 } 652 }
378 if (room_exit.type == EntranceType::kPainting && 653 if ((room_exit.type == EntranceType::kPainting ||
654 room_exit.type == EntranceType::kStaticPainting) &&
379 !AP_DoesPilgrimageAllowPaintings()) { 655 !AP_DoesPilgrimageAllowPaintings()) {
380 return kNo; 656 return kNo;
381 } 657 }
@@ -401,16 +677,99 @@ class StateCalculator {
401 std::set<int> reachable_rooms_; 677 std::set<int> reachable_rooms_;
402 std::map<int, Decision> door_decisions_; 678 std::map<int, Decision> door_decisions_;
403 std::set<int> solveable_panels_; 679 std::set<int> solveable_panels_;
680 std::set<int> reachable_paintings_;
681 std::map<int, std::map<std::string, bool>> door_report_;
682 bool pilgrimage_doable_ = false;
683
684 std::map<int, std::list<int>> paths_;
685
686 std::map<std::string, std::string> painting_mapping_;
687 std::set<std::string> checked_paintings_;
688 std::map<int, SunwarpMapping> sunwarp_mapping_;
404}; 689};
405 690
406} // namespace 691} // namespace
407 692
693void ResetReachabilityRequirements() {
694 TrackerLog("Resetting tracker state...");
695
696 std::lock_guard reachability_guard(GetState().reachability_mutex);
697 GetState().requirements.Reset();
698 GetState().reachable_doors.clear();
699 GetState().solveable_panels.clear();
700
701 if (AP_IsPostgameShuffle()) {
702 GetState().non_postgame_areas.clear();
703 GetState().non_postgame_locations.clear();
704 GetState().non_postgame_paintings.clear();
705 } else {
706 StateCalculator postgame_calculator(
707 {.start = GD_GetRoomByName("Menu"), .postgame_detection = true});
708 postgame_calculator.Calculate();
709
710 std::set<int>& non_postgame_areas = GetState().non_postgame_areas;
711 non_postgame_areas.clear();
712
713 std::set<int>& non_postgame_locations = GetState().non_postgame_locations;
714 non_postgame_locations.clear();
715
716 const std::set<int>& reachable_rooms =
717 postgame_calculator.GetReachableRooms();
718 const std::set<int>& solveable_panels =
719 postgame_calculator.GetSolveablePanels();
720
721 for (const MapArea& map_area : GD_GetMapAreas()) {
722 bool area_reachable = false;
723
724 for (const Location& location_section : map_area.locations) {
725 bool reachable = reachable_rooms.count(location_section.room);
726 if (reachable) {
727 for (int panel_id : location_section.panels) {
728 reachable &= (solveable_panels.count(panel_id) == 1);
729 }
730 }
731
732 if (!reachable && IsLocationWinCondition(location_section)) {
733 reachable = true;
734 }
735
736 if (reachable) {
737 non_postgame_locations.insert(location_section.ap_location_id);
738 area_reachable = true;
739 }
740 }
741
742 for (int painting_id : map_area.paintings) {
743 if (postgame_calculator.GetReachablePaintings().count(painting_id)) {
744 area_reachable = true;
745 }
746 }
747
748 if (area_reachable) {
749 non_postgame_areas.insert(map_area.id);
750 }
751 }
752
753 GetState().non_postgame_paintings =
754 postgame_calculator.GetReachablePaintings();
755 }
756}
757
408void RecalculateReachability() { 758void RecalculateReachability() {
759 TrackerLog("Calculating reachability...");
760
761 std::lock_guard reachability_guard(GetState().reachability_mutex);
762
763 // Receiving items and checking paintings should never remove access to doors
764 // or panels, so we can preload any doors and panels we already know are
765 // accessible from previous runs, in order to reduce the work.
409 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); 766 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")});
767 state_calculator.PreloadDoors(GetState().reachable_doors);
768 state_calculator.PreloadPanels(GetState().solveable_panels);
410 state_calculator.Calculate(); 769 state_calculator.Calculate();
411 770
412 const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms(); 771 const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms();
413 const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels(); 772 std::set<int> solveable_panels = state_calculator.GetSolveablePanels();
414 773
415 std::map<int, bool> new_reachability; 774 std::map<int, bool> new_reachability;
416 for (const MapArea& map_area : GD_GetMapAreas()) { 775 for (const MapArea& map_area : GD_GetMapAreas()) {
@@ -435,11 +794,16 @@ void RecalculateReachability() {
435 } 794 }
436 } 795 }
437 796
438 { 797 std::set<int> reachable_paintings = state_calculator.GetReachablePaintings();
439 std::lock_guard reachability_guard(GetState().reachability_mutex); 798 std::map<int, std::map<std::string, bool>> door_reports =
440 std::swap(GetState().reachability, new_reachability); 799 state_calculator.GetDoorReports();
441 std::swap(GetState().reachable_doors, new_reachable_doors); 800
442 } 801 std::swap(GetState().reachability, new_reachability);
802 std::swap(GetState().reachable_doors, new_reachable_doors);
803 std::swap(GetState().solveable_panels, solveable_panels);
804 std::swap(GetState().reachable_paintings, reachable_paintings);
805 std::swap(GetState().door_reports, door_reports);
806 GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable();
443} 807}
444 808
445bool IsLocationReachable(int location_id) { 809bool IsLocationReachable(int location_id) {
@@ -457,3 +821,51 @@ bool IsDoorOpen(int door_id) {
457 821
458 return GetState().reachable_doors.count(door_id); 822 return GetState().reachable_doors.count(door_id);
459} 823}
824
825bool IsPaintingReachable(int painting_id) {
826 std::lock_guard reachability_guard(GetState().reachability_mutex);
827
828 return GetState().reachable_paintings.count(painting_id);
829}
830
831const std::map<std::string, bool>& GetDoorRequirements(int door_id) {
832 std::lock_guard reachability_guard(GetState().reachability_mutex);
833
834 return GetState().door_reports[door_id];
835}
836
837bool IsPilgrimageDoable() {
838 std::lock_guard reachability_guard(GetState().reachability_mutex);
839
840 return GetState().pilgrimage_doable;
841}
842
843bool IsAreaPostgame(int area_id) {
844 std::lock_guard reachability_guard(GetState().reachability_mutex);
845
846 if (GetState().non_postgame_areas.empty()) {
847 return false;
848 } else {
849 return !GetState().non_postgame_areas.count(area_id);
850 }
851}
852
853bool IsLocationPostgame(int location_id) {
854 std::lock_guard reachability_guard(GetState().reachability_mutex);
855
856 if (GetState().non_postgame_locations.empty()) {
857 return false;
858 } else {
859 return !GetState().non_postgame_locations.count(location_id);
860 }
861}
862
863bool IsPaintingPostgame(int painting_id) {
864 std::lock_guard reachability_guard(GetState().reachability_mutex);
865
866 if (GetState().non_postgame_paintings.empty()) {
867 return false;
868 } else {
869 return !GetState().non_postgame_paintings.count(painting_id);
870 }
871}