diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2024-05-16 17:06:33 -0400 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2024-05-16 17:06:33 -0400 |
commit | 4d8e36245e8ce43eef9b687a9108fd4c353f756f (patch) | |
tree | ba63b4a72c2d62816ad4056c6a45a72b3becce95 /src/tracker_state.cpp | |
parent | 249817743c12b453338c6d0a355180bf5084c73c (diff) | |
download | lingo-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.cpp | 314 |
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 | ||
13 | namespace { | 13 | namespace { |
14 | 14 | ||
15 | struct 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 | |||
43 | class 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 | |||
15 | struct TrackerState { | 141 | struct 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 | ||
21 | enum Decision { kYes, kNo, kMaybe }; | 149 | enum 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 | ||
539 | void ResetReachabilityRequirements() { | ||
540 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
541 | GetState().requirements.Reset(); | ||
542 | } | ||
543 | |||
408 | void RecalculateReachability() { | 544 | void 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 | |||
601 | const 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 | } | ||