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.cpp405
1 files changed, 310 insertions, 95 deletions
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
13namespace { 13namespace {
14 14
15struct 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
43class 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
15struct TrackerState { 144struct 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
20enum Decision { kYes, kNo, kMaybe }; 153enum 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
603void ResetReachabilityRequirements() {
604 std::lock_guard reachability_guard(GetState().reachability_mutex);
605 GetState().requirements.Reset();
606}
607
424void RecalculateReachability() { 608void 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
453bool IsLocationReachable(int location_id) { 650bool IsLocationReachable(int location_id) {
@@ -459,3 +656,21 @@ bool IsLocationReachable(int location_id) {
459 return false; 656 return false;
460 } 657 }
461} 658}
659
660bool IsDoorOpen(int door_id) {
661 std::lock_guard reachability_guard(GetState().reachability_mutex);
662
663 return GetState().reachable_doors.count(door_id);
664}
665
666bool IsPaintingReachable(int painting_id) {
667 std::lock_guard reachability_guard(GetState().reachability_mutex);
668
669 return GetState().reachable_paintings.count(painting_id);
670}
671
672const 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}