about summary refs log tree commit diff stats
path: root/src/tracker_state.cpp
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-05-16 17:06:33 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2024-05-16 17:06:33 -0400
commit4d8e36245e8ce43eef9b687a9108fd4c353f756f (patch)
treeba63b4a72c2d62816ad4056c6a45a72b3becce95 /src/tracker_state.cpp
parent249817743c12b453338c6d0a355180bf5084c73c (diff)
downloadlingo-ap-tracker-4d8e36245e8ce43eef9b687a9108fd4c353f756f.tar.gz
lingo-ap-tracker-4d8e36245e8ce43eef9b687a9108fd4c353f756f.tar.bz2
lingo-ap-tracker-4d8e36245e8ce43eef9b687a9108fd4c353f756f.zip
Added door popups that report requirements
Diffstat (limited to 'src/tracker_state.cpp')
-rw-r--r--src/tracker_state.cpp314
1 files changed, 230 insertions, 84 deletions
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 5588c7f..ccb3fbd 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -12,10 +12,138 @@
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 (!AP_IsPilgrimageEnabled() &&
56 door_obj.type == DoorType::kSunPainting) {
57 requirements.items.insert(door_obj.ap_item_id);
58 } else if (door_obj.type == DoorType::kSunwarp) {
59 switch (AP_GetSunwarpAccess()) {
60 case kSUNWARP_ACCESS_NORMAL:
61 // Do nothing.
62 break;
63 case kSUNWARP_ACCESS_DISABLED:
64 requirements.disabled = true;
65 break;
66 case kSUNWARP_ACCESS_UNLOCK:
67 requirements.items.insert(door_obj.group_ap_item_id);
68 break;
69 case kSUNWARP_ACCESS_INDIVIDUAL:
70 case kSUNWARP_ACCESS_PROGRESSIVE:
71 requirements.doors.insert(door_obj.id);
72 break;
73 }
74 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
75 requirements.rooms.insert(door_obj.room);
76
77 for (int panel_id : door_obj.panels) {
78 const Requirements& panel_reqs = GetPanel(panel_id);
79 requirements.Merge(panel_reqs);
80 }
81 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS &&
82 !door_obj.group_name.empty()) {
83 requirements.items.insert(door_obj.group_ap_item_id);
84 } else {
85 requirements.doors.insert(door_obj.id);
86 }
87
88 doors_[door_id] = requirements;
89 }
90
91 return doors_[door_id];
92 }
93
94 const Requirements& GetPanel(int panel_id) {
95 if (!panels_.count(panel_id)) {
96 Requirements requirements;
97 const Panel& panel_obj = GD_GetPanel(panel_id);
98
99 requirements.rooms.insert(panel_obj.room);
100
101 if (panel_obj.name == "THE MASTER") {
102 requirements.mastery = true;
103 }
104
105 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
106 AP_GetLevel2Requirement() > 1) {
107 requirements.panel_hunt = true;
108 }
109
110 for (int room_id : panel_obj.required_rooms) {
111 requirements.rooms.insert(room_id);
112 }
113
114 for (int door_id : panel_obj.required_doors) {
115 const Requirements& door_reqs = GetDoor(door_id);
116 requirements.Merge(door_reqs);
117 }
118
119 for (int panel_id : panel_obj.required_panels) {
120 const Requirements& panel_reqs = GetPanel(panel_id);
121 requirements.Merge(panel_reqs);
122 }
123
124 if (AP_IsColorShuffle()) {
125 for (LingoColor color : panel_obj.colors) {
126 requirements.items.insert(GD_GetItemIdForColor(color));
127 }
128 }
129
130 panels_[panel_id] = requirements;
131 }
132
133 return panels_[panel_id];
134 }
135
136 private:
137 std::map<int, Requirements> doors_;
138 std::map<int, Requirements> panels_;
139};
140
15struct TrackerState { 141struct TrackerState {
16 std::map<int, bool> reachability; 142 std::map<int, bool> reachability;
17 std::set<int> reachable_doors; 143 std::set<int> reachable_doors;
18 std::mutex reachability_mutex; 144 std::mutex reachability_mutex;
145 RequirementCalculator requirements;
146 std::map<int, std::map<std::string, bool>> door_reports;
19}; 147};
20 148
21enum Decision { kYes, kNo, kMaybe }; 149enum Decision { kYes, kNo, kMaybe };
@@ -172,6 +300,10 @@ class StateCalculator {
172 300
173 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } 301 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; }
174 302
303 const std::map<int, std::map<std::string, bool>>& GetDoorReports() const {
304 return door_report_;
305 }
306
175 private: 307 private:
176 Decision IsNonGroupedDoorReachable(const Door& door_obj) { 308 Decision IsNonGroupedDoorReachable(const Door& door_obj) {
177 bool has_item = AP_HasItem(door_obj.ap_item_id); 309 bool has_item = AP_HasItem(door_obj.ap_item_id);
@@ -188,68 +320,52 @@ class StateCalculator {
188 return has_item ? kYes : kNo; 320 return has_item ? kYes : kNo;
189 } 321 }
190 322
191 Decision IsDoorReachable_Helper(int door_id) { 323 Decision AreRequirementsSatisfied(const Requirements& reqs, std::map<std::string, bool>* report = nullptr) {
192 const Door& door_obj = GD_GetDoor(door_id); 324 if (reqs.disabled) {
193 325 return kNo;
194 if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { 326 }
195 return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo;
196 } else if (door_obj.type == DoorType::kSunwarp) {
197 switch (AP_GetSunwarpAccess()) {
198 case kSUNWARP_ACCESS_NORMAL:
199 return kYes;
200 case kSUNWARP_ACCESS_DISABLED:
201 return kNo;
202 case kSUNWARP_ACCESS_UNLOCK:
203 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
204 case kSUNWARP_ACCESS_INDIVIDUAL:
205 case kSUNWARP_ACCESS_PROGRESSIVE:
206 return IsNonGroupedDoorReachable(door_obj);
207 }
208 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
209 if (!reachable_rooms_.count(door_obj.room)) {
210 return kMaybe;
211 }
212 327
213 for (int panel_id : door_obj.panels) { 328 Decision final_decision = kYes;
214 if (!solveable_panels_.count(panel_id)) {
215 return kMaybe;
216 }
217 }
218 329
219 return kYes; 330 for (int door_id : reqs.doors) {
220 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && 331 const Door& door_obj = GD_GetDoor(door_id);
221 !door_obj.group_name.empty()) { 332 Decision decision = IsNonGroupedDoorReachable(door_obj);
222 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
223 } else {
224 return IsNonGroupedDoorReachable(door_obj);
225 }
226 }
227 333
228 Decision IsDoorReachable(int door_id) { 334 if (report) {
229 if (options_.parent) { 335 (*report)[door_obj.item_name] = (decision == kYes);
230 return options_.parent->IsDoorReachable(door_id); 336 }
231 }
232 337
233 if (door_decisions_.count(door_id)) { 338 if (decision != kYes) {
234 return door_decisions_.at(door_id); 339 final_decision = decision;
340 }
235 } 341 }
342
343 for (int item_id : reqs.items) {
344 bool has_item = AP_HasItem(item_id);
345 if (report) {
346 (*report)[AP_GetItemName(item_id)] = has_item;
347 }
236 348
237 Decision result = IsDoorReachable_Helper(door_id); 349 if (!has_item) {
238 if (result != kMaybe) { 350 final_decision = kNo;
239 door_decisions_[door_id] = result; 351 }
240 } 352 }
241 353
242 return result; 354 for (int room_id : reqs.rooms) {
243 } 355 bool reachable = reachable_rooms_.count(room_id);
244 356
245 Decision IsPanelReachable(int panel_id) { 357 if (report) {
246 const Panel& panel_obj = GD_GetPanel(panel_id); 358 std::string report_name =
359 "Reach \"" + GD_GetRoom(room_id).name + "\"";
360 (*report)[report_name] = reachable;
361 }
247 362
248 if (!reachable_rooms_.count(panel_obj.room)) { 363 if (!reachable && final_decision != kNo) {
249 return kMaybe; 364 final_decision = kMaybe;
365 }
250 } 366 }
251 367
252 if (panel_obj.name == "THE MASTER") { 368 if (reqs.mastery) {
253 int achievements_accessible = 0; 369 int achievements_accessible = 0;
254 370
255 for (int achieve_id : GD_GetAchievementPanels()) { 371 for (int achieve_id : GD_GetAchievementPanels()) {
@@ -262,12 +378,18 @@ class StateCalculator {
262 } 378 }
263 } 379 }
264 380
265 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 381 bool can_mastery =
266 : kMaybe; 382 (achievements_accessible >= AP_GetMasteryRequirement());
383 if (report) {
384 (*report)["Mastery"] = can_mastery;
385 }
386
387 if (can_mastery && final_decision != kNo) {
388 final_decision = kMaybe;
389 }
267 } 390 }
268 391
269 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 392 if (reqs.panel_hunt) {
270 AP_GetLevel2Requirement() > 1) {
271 int counting_panels_accessible = 0; 393 int counting_panels_accessible = 0;
272 394
273 for (int solved_panel_id : solveable_panels_) { 395 for (int solved_panel_id : solveable_panels_) {
@@ -278,44 +400,52 @@ class StateCalculator {
278 } 400 }
279 } 401 }
280 402
281 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 403 bool can_level2 =
282 ? kYes 404 (counting_panels_accessible >= AP_GetLevel2Requirement() - 1);
283 : kMaybe; 405 if (report) {
406 std::string report_name =
407 std::to_string(AP_GetLevel2Requirement()) + " Panels";
408 (*report)[report_name] = can_level2;
409 }
410
411 if (can_level2 && final_decision != kNo) {
412 final_decision = kMaybe;
413 }
284 } 414 }
285 415
286 for (int room_id : panel_obj.required_rooms) { 416 return final_decision;
287 if (!reachable_rooms_.count(room_id)) { 417 }
288 return kMaybe; 418
289 } 419 Decision IsDoorReachable_Helper(int door_id) {
420 if (door_report_.count(door_id)) {
421 door_report_[door_id].clear();
422 } else {
423 door_report_[door_id] = {};
290 } 424 }
425
426 return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id),
427 &door_report_[door_id]);
428 }
291 429
292 for (int door_id : panel_obj.required_doors) { 430 Decision IsDoorReachable(int door_id) {
293 Decision door_reachable = IsDoorReachable(door_id); 431 if (options_.parent) {
294 if (door_reachable == kNo) { 432 return options_.parent->IsDoorReachable(door_id);
295 const Door& door_obj = GD_GetDoor(door_id);
296 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
297 ? kMaybe
298 : kNo;
299 } else if (door_reachable == kMaybe) {
300 return kMaybe;
301 }
302 } 433 }
303 434
304 for (int panel_id : panel_obj.required_panels) { 435 if (door_decisions_.count(door_id)) {
305 if (!solveable_panels_.count(panel_id)) { 436 return door_decisions_.at(door_id);
306 return kMaybe;
307 }
308 } 437 }
309 438
310 if (AP_IsColorShuffle()) { 439 Decision result = IsDoorReachable_Helper(door_id);
311 for (LingoColor color : panel_obj.colors) { 440 if (result != kMaybe) {
312 if (!AP_HasItem(GD_GetItemIdForColor(color))) { 441 door_decisions_[door_id] = result;
313 return kNo;
314 }
315 }
316 } 442 }
317 443
318 return kYes; 444 return result;
445 }
446
447 Decision IsPanelReachable(int panel_id) {
448 return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id));
319 } 449 }
320 450
321 Decision IsExitUsable(const Exit& room_exit) { 451 Decision IsExitUsable(const Exit& room_exit) {
@@ -401,10 +531,16 @@ class StateCalculator {
401 std::set<int> reachable_rooms_; 531 std::set<int> reachable_rooms_;
402 std::map<int, Decision> door_decisions_; 532 std::map<int, Decision> door_decisions_;
403 std::set<int> solveable_panels_; 533 std::set<int> solveable_panels_;
534 std::map<int, std::map<std::string, bool>> door_report_;
404}; 535};
405 536
406} // namespace 537} // namespace
407 538
539void ResetReachabilityRequirements() {
540 std::lock_guard reachability_guard(GetState().reachability_mutex);
541 GetState().requirements.Reset();
542}
543
408void RecalculateReachability() { 544void RecalculateReachability() {
409 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); 545 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")});
410 state_calculator.Calculate(); 546 state_calculator.Calculate();
@@ -435,10 +571,14 @@ void RecalculateReachability() {
435 } 571 }
436 } 572 }
437 573
574 std::map<int, std::map<std::string, bool>> door_reports =
575 state_calculator.GetDoorReports();
576
438 { 577 {
439 std::lock_guard reachability_guard(GetState().reachability_mutex); 578 std::lock_guard reachability_guard(GetState().reachability_mutex);
440 std::swap(GetState().reachability, new_reachability); 579 std::swap(GetState().reachability, new_reachability);
441 std::swap(GetState().reachable_doors, new_reachable_doors); 580 std::swap(GetState().reachable_doors, new_reachable_doors);
581 std::swap(GetState().door_reports, door_reports);
442 } 582 }
443} 583}
444 584
@@ -457,3 +597,9 @@ bool IsDoorOpen(int door_id) {
457 597
458 return GetState().reachable_doors.count(door_id); 598 return GetState().reachable_doors.count(door_id);
459} 599}
600
601const std::map<std::string, bool>& GetDoorRequirements(int door_id) {
602 std::lock_guard reachability_guard(GetState().reachability_mutex);
603
604 return GetState().door_reports.at(door_id);
605}