about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-03-08 00:29:17 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2025-03-08 00:29:17 -0500
commite290161d58c98e73ea185855e79efad19cb111a2 (patch)
treea03d0e3822b19a1c57dd83d1309ab32814d670fe /src
parent4fa8f5b6c933dcbab5940d39a515937e86e9d280 (diff)
downloadlingo-ap-tracker-e290161d58c98e73ea185855e79efad19cb111a2.tar.gz
lingo-ap-tracker-e290161d58c98e73ea185855e79efad19cb111a2.tar.bz2
lingo-ap-tracker-e290161d58c98e73ea185855e79efad19cb111a2.zip
Added postgame detection
Diffstat (limited to 'src')
-rw-r--r--src/ap_state.cpp6
-rw-r--r--src/ap_state.h2
-rw-r--r--src/area_popup.cpp11
-rw-r--r--src/game_data.cpp4
-rw-r--r--src/global.cpp16
-rw-r--r--src/global.h2
-rw-r--r--src/tracker_panel.cpp12
-rw-r--r--src/tracker_state.cpp164
-rw-r--r--src/tracker_state.h6
9 files changed, 186 insertions, 37 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 2236d6a..cbe622a 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -82,6 +82,7 @@ struct APState {
82 bool pilgrimage_allows_paintings = false; 82 bool pilgrimage_allows_paintings = false;
83 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; 83 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL;
84 bool sunwarp_shuffle = false; 84 bool sunwarp_shuffle = false;
85 bool postgame_shuffle = true;
85 86
86 std::map<std::string, std::string> painting_mapping; 87 std::map<std::string, std::string> painting_mapping;
87 std::set<std::string> painting_codomain; 88 std::set<std::string> painting_codomain;
@@ -146,6 +147,7 @@ struct APState {
146 sunwarp_access = kSUNWARP_ACCESS_NORMAL; 147 sunwarp_access = kSUNWARP_ACCESS_NORMAL;
147 sunwarp_shuffle = false; 148 sunwarp_shuffle = false;
148 sunwarp_mapping.clear(); 149 sunwarp_mapping.clear();
150 postgame_shuffle = true;
149 } 151 }
150 152
151 apclient->set_room_info_handler( 153 apclient->set_room_info_handler(
@@ -470,6 +472,8 @@ struct APState {
470 : kSUNWARP_ACCESS_NORMAL; 472 : kSUNWARP_ACCESS_NORMAL;
471 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") && 473 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") &&
472 slot_data["shuffle_sunwarps"].get<int>() == 1; 474 slot_data["shuffle_sunwarps"].get<int>() == 1;
475 postgame_shuffle = slot_data.contains("shuffle_postgame") &&
476 slot_data["shuffle_postgame"].get<int>() == 1;
473 477
474 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 478 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
475 painting_mapping.clear(); 479 painting_mapping.clear();
@@ -798,6 +802,8 @@ std::map<int, SunwarpMapping> AP_GetSunwarpMapping() {
798 return GetState().sunwarp_mapping; 802 return GetState().sunwarp_mapping;
799} 803}
800 804
805bool AP_IsPostgameShuffle() { return GetState().postgame_shuffle; }
806
801bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } 807bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); }
802 808
803std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { 809std::optional<std::tuple<int, int>> AP_GetPlayerPosition() {
diff --git a/src/ap_state.h b/src/ap_state.h index 2da0b8e..e2ff22b 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -100,6 +100,8 @@ bool AP_IsSunwarpShuffle();
100 100
101std::map<int, SunwarpMapping> AP_GetSunwarpMapping(); 101std::map<int, SunwarpMapping> AP_GetSunwarpMapping();
102 102
103bool AP_IsPostgameShuffle();
104
103bool AP_HasReachedGoal(); 105bool AP_HasReachedGoal();
104 106
105std::optional<std::tuple<int, int>> AP_GetPlayerPosition(); 107std::optional<std::tuple<int, int>> AP_GetPlayerPosition();
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 9c7406b..d7f45b6 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp
@@ -52,6 +52,9 @@ void AreaPopup::UpdateIndicators() {
52 for (int section_id = 0; section_id < map_area.locations.size(); 52 for (int section_id = 0; section_id < map_area.locations.size();
53 section_id++) { 53 section_id++) {
54 const Location& location = map_area.locations.at(section_id); 54 const Location& location = map_area.locations.at(section_id);
55 if (IsLocationPostgame(location.ap_location_id)) {
56 continue;
57 }
55 58
56 if (tracker_panel->IsPanelsMode()) { 59 if (tracker_panel->IsPanelsMode()) {
57 if (!location.single_panel) { 60 if (!location.single_panel) {
@@ -78,6 +81,10 @@ void AreaPopup::UpdateIndicators() {
78 81
79 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { 82 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) {
80 for (int painting_id : map_area.paintings) { 83 for (int painting_id : map_area.paintings) {
84 if (IsPaintingPostgame(painting_id)) {
85 continue;
86 }
87
81 const PaintingExit& painting = GD_GetPaintingExit(painting_id); 88 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
82 wxSize item_extent = mem_dc.GetTextExtent(painting.display_name); 89 wxSize item_extent = mem_dc.GetTextExtent(painting.display_name);
83 int item_height = 90 int item_height =
@@ -149,6 +156,10 @@ void AreaPopup::UpdateIndicators() {
149 156
150 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { 157 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) {
151 for (int painting_id : map_area.paintings) { 158 for (int painting_id : map_area.paintings) {
159 if (IsPaintingPostgame(painting_id)) {
160 continue;
161 }
162
152 const PaintingExit& painting = GD_GetPaintingExit(painting_id); 163 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
153 164
154 bool reachable = IsPaintingReachable(painting_id); 165 bool reachable = IsPaintingReachable(painting_id);
diff --git a/src/game_data.cpp b/src/game_data.cpp index e92e6a2..5fbd244 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -609,7 +609,7 @@ struct GameData {
609 // Only locations for the panels are kept here. 609 // Only locations for the panels are kept here.
610 std::map<std::string, std::tuple<int, int>> locations_by_name; 610 std::map<std::string, std::tuple<int, int>> locations_by_name;
611 611
612 for (const Panel &panel : panels_) { 612 for (Panel &panel : panels_) {
613 int room_id = panel.room; 613 int room_id = panel.room;
614 std::string room_name = rooms_[room_id].name; 614 std::string room_name = rooms_[room_id].name;
615 615
@@ -625,6 +625,8 @@ struct GameData {
625 area_name = location_name.substr(0, divider_pos); 625 area_name = location_name.substr(0, divider_pos);
626 section_name = location_name.substr(divider_pos + 3); 626 section_name = location_name.substr(divider_pos + 3);
627 } 627 }
628 } else {
629 panel.location_name = location_name;
628 } 630 }
629 631
630 if (fold_areas.count(area_name)) { 632 if (fold_areas.count(area_name)) {
diff --git a/src/global.cpp b/src/global.cpp index 1eb3f8d..63f4a19 100644 --- a/src/global.cpp +++ b/src/global.cpp
@@ -26,17 +26,19 @@ std::string GetAbsolutePath(std::string_view path) {
26 return (GetExecutableDirectory() / path).string(); 26 return (GetExecutableDirectory() / path).string();
27} 27}
28 28
29bool IsLocationWinCondition(const Location& location) { 29std::string GetWinCondition() {
30 switch (AP_GetVictoryCondition()) { 30 switch (AP_GetVictoryCondition()) {
31 case kTHE_END: 31 case kTHE_END:
32 return location.ap_location_name == 32 return "Orange Tower Seventh Floor - THE END";
33 "Orange Tower Seventh Floor - THE END";
34 case kTHE_MASTER: 33 case kTHE_MASTER:
35 return location.ap_location_name == 34 return "Orange Tower Seventh Floor - THE MASTER";
36 "Orange Tower Seventh Floor - THE MASTER";
37 case kLEVEL_2: 35 case kLEVEL_2:
38 return location.ap_location_name == "Second Room - LEVEL 2"; 36 return "Second Room - LEVEL 2";
39 case kPILGRIMAGE: 37 case kPILGRIMAGE:
40 return location.ap_location_name == "Pilgrim Antechamber - PILGRIM"; 38 return "Pilgrim Antechamber - PILGRIM";
41 } 39 }
42} 40}
41
42bool IsLocationWinCondition(const Location& location) {
43 return location.ap_location_name == GetWinCondition();
44}
diff --git a/src/global.h b/src/global.h index 31ebde3..bdfa7ae 100644 --- a/src/global.h +++ b/src/global.h
@@ -10,6 +10,8 @@ const std::filesystem::path& GetExecutableDirectory();
10 10
11std::string GetAbsolutePath(std::string_view path); 11std::string GetAbsolutePath(std::string_view path);
12 12
13std::string GetWinCondition();
14
13bool IsLocationWinCondition(const Location& location); 15bool IsLocationWinCondition(const Location& location);
14 16
15#endif /* end of include guard: GLOBAL_H_44945DBA */ 17#endif /* end of include guard: GLOBAL_H_44945DBA */
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 81b58cc..b4e6697 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp
@@ -203,7 +203,9 @@ void TrackerPanel::Redraw() {
203 203
204 for (AreaIndicator &area : areas_) { 204 for (AreaIndicator &area : areas_) {
205 const MapArea &map_area = GD_GetMapArea(area.area_id); 205 const MapArea &map_area = GD_GetMapArea(area.area_id);
206 if (panels_mode_) { 206 if (IsAreaPostgame(area.area_id)) {
207 area.active = false;
208 } else if (panels_mode_) {
207 area.active = map_area.has_single_panel; 209 area.active = map_area.has_single_panel;
208 } else if (!AP_IsLocationVisible(map_area.classification) && 210 } else if (!AP_IsLocationVisible(map_area.classification) &&
209 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && 211 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) &&
@@ -221,7 +223,9 @@ void TrackerPanel::Redraw() {
221 bool has_unreachable_unchecked = false; 223 bool has_unreachable_unchecked = false;
222 for (const Location &section : map_area.locations) { 224 for (const Location &section : map_area.locations) {
223 bool has_unchecked = false; 225 bool has_unchecked = false;
224 if (IsLocationWinCondition(section)) { 226 if (IsLocationPostgame(section.ap_location_id)) {
227 // Nope.
228 } else if (IsLocationWinCondition(section)) {
225 has_unchecked = !AP_HasReachedGoal(); 229 has_unchecked = !AP_HasReachedGoal();
226 } else if (panels_mode_) { 230 } else if (panels_mode_) {
227 if (section.single_panel) { 231 if (section.single_panel) {
@@ -249,6 +253,10 @@ void TrackerPanel::Redraw() {
249 253
250 if (AP_IsPaintingShuffle() && !panels_mode_) { 254 if (AP_IsPaintingShuffle() && !panels_mode_) {
251 for (int painting_id : map_area.paintings) { 255 for (int painting_id : map_area.paintings) {
256 if (IsPaintingPostgame(painting_id)) {
257 continue;
258 }
259
252 const PaintingExit &painting = GD_GetPaintingExit(painting_id); 260 const PaintingExit &painting = GD_GetPaintingExit(painting_id);
253 bool reachable = IsPaintingReachable(painting_id); 261 bool reachable = IsPaintingReachable(painting_id);
254 if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) { 262 if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) {
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index f7244a7..bcee1d6 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 {
@@ -25,6 +26,7 @@ struct Requirements {
25 std::set<int> rooms; // maybe 26 std::set<int> rooms; // maybe
26 bool mastery = false; // maybe 27 bool mastery = false; // maybe
27 bool panel_hunt = false; // maybe 28 bool panel_hunt = false; // maybe
29 bool postgame = false;
28 30
29 void Merge(const Requirements& rhs) { 31 void Merge(const Requirements& rhs) {
30 if (rhs.disabled) { 32 if (rhs.disabled) {
@@ -45,6 +47,7 @@ struct Requirements {
45 } 47 }
46 mastery = mastery || rhs.mastery; 48 mastery = mastery || rhs.mastery;
47 panel_hunt = panel_hunt || rhs.panel_hunt; 49 panel_hunt = panel_hunt || rhs.panel_hunt;
50 postgame = postgame || rhs.postgame;
48 } 51 }
49}; 52};
50 53
@@ -146,6 +149,10 @@ class RequirementCalculator {
146 } 149 }
147 } 150 }
148 151
152 if (panel_obj.location_name == GetWinCondition()) {
153 requirements.postgame = true;
154 }
155
149 panels_[panel_id] = requirements; 156 panels_[panel_id] = requirements;
150 } 157 }
151 158
@@ -165,6 +172,11 @@ struct TrackerState {
165 RequirementCalculator requirements; 172 RequirementCalculator requirements;
166 std::map<int, std::map<std::string, bool>> door_reports; 173 std::map<int, std::map<std::string, bool>> door_reports;
167 bool pilgrimage_doable = false; 174 bool pilgrimage_doable = false;
175
176 // If these are empty, it actually means everything is non-postgame.
177 std::set<int> non_postgame_areas;
178 std::set<int> non_postgame_locations;
179 std::set<int> non_postgame_paintings;
168}; 180};
169 181
170enum Decision { kYes, kNo, kMaybe }; 182enum Decision { kYes, kNo, kMaybe };
@@ -179,6 +191,11 @@ class StateCalculator;
179struct StateCalculatorOptions { 191struct StateCalculatorOptions {
180 int start; 192 int start;
181 bool pilgrimage = false; 193 bool pilgrimage = false;
194
195 // Treats all items as collected and all paintings as checked, but postgame
196 // areas cannot be reached.
197 bool postgame_detection = false;
198
182 StateCalculator* parent = nullptr; 199 StateCalculator* parent = nullptr;
183}; 200};
184 201
@@ -234,7 +251,8 @@ class StateCalculator {
234 251
235 PaintingExit cur_painting = GD_GetPaintingExit(painting_id); 252 PaintingExit cur_painting = GD_GetPaintingExit(painting_id);
236 if (painting_mapping_.count(cur_painting.internal_id) && 253 if (painting_mapping_.count(cur_painting.internal_id) &&
237 checked_paintings_.count(cur_painting.internal_id)) { 254 (checked_paintings_.count(cur_painting.internal_id) ||
255 options_.postgame_detection)) {
238 Exit painting_exit; 256 Exit painting_exit;
239 PaintingExit target_painting = 257 PaintingExit target_painting =
240 GD_GetPaintingExit(GD_GetPaintingByName( 258 GD_GetPaintingExit(GD_GetPaintingByName(
@@ -419,43 +437,49 @@ class StateCalculator {
419 return kNo; 437 return kNo;
420 } 438 }
421 439
440 if (reqs.postgame && options_.postgame_detection) {
441 return kNo;
442 }
443
422 Decision final_decision = kYes; 444 Decision final_decision = kYes;
423 445
424 for (int door_id : reqs.doors) { 446 if (!options_.postgame_detection) {
425 const Door& door_obj = GD_GetDoor(door_id); 447 for (int door_id : reqs.doors) {
426 Decision decision = IsNonGroupedDoorReachable(door_obj); 448 const Door& door_obj = GD_GetDoor(door_id);
449 Decision decision = IsNonGroupedDoorReachable(door_obj);
427 450
428 if (report) { 451 if (report) {
429 (*report)[door_obj.item_name] = (decision == kYes); 452 (*report)[door_obj.item_name] = (decision == kYes);
430 } 453 }
431 454
432 if (decision != kYes) { 455 if (decision != kYes) {
433 final_decision = decision; 456 final_decision = decision;
457 }
434 } 458 }
435 }
436 459
437 for (int panel_door_id : reqs.panel_doors) { 460 for (int panel_door_id : reqs.panel_doors) {
438 const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id); 461 const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id);
439 Decision decision = IsNonGroupedDoorReachable(panel_door_obj); 462 Decision decision = IsNonGroupedDoorReachable(panel_door_obj);
440 463
441 if (report) { 464 if (report) {
442 (*report)[AP_GetItemName(panel_door_obj.ap_item_id)] = 465 (*report)[AP_GetItemName(panel_door_obj.ap_item_id)] =
443 (decision == kYes); 466 (decision == kYes);
444 } 467 }
445 468
446 if (decision != kYes) { 469 if (decision != kYes) {
447 final_decision = decision; 470 final_decision = decision;
471 }
448 } 472 }
449 }
450 473
451 for (int item_id : reqs.items) { 474 for (int item_id : reqs.items) {
452 bool has_item = AP_HasItem(item_id); 475 bool has_item = AP_HasItem(item_id);
453 if (report) { 476 if (report) {
454 (*report)[AP_GetItemName(item_id)] = has_item; 477 (*report)[AP_GetItemName(item_id)] = has_item;
455 } 478 }
456 479
457 if (!has_item) { 480 if (!has_item) {
458 final_decision = kNo; 481 final_decision = kNo;
482 }
459 } 483 }
460 } 484 }
461 485
@@ -658,6 +682,62 @@ class StateCalculator {
658void ResetReachabilityRequirements() { 682void ResetReachabilityRequirements() {
659 std::lock_guard reachability_guard(GetState().reachability_mutex); 683 std::lock_guard reachability_guard(GetState().reachability_mutex);
660 GetState().requirements.Reset(); 684 GetState().requirements.Reset();
685
686 if (AP_IsPostgameShuffle()) {
687 GetState().non_postgame_areas.clear();
688 GetState().non_postgame_locations.clear();
689 GetState().non_postgame_paintings.clear();
690 } else {
691 StateCalculator postgame_calculator(
692 {.start = GD_GetRoomByName("Menu"), .postgame_detection = true});
693 postgame_calculator.Calculate();
694
695 std::set<int>& non_postgame_areas = GetState().non_postgame_areas;
696 non_postgame_areas.clear();
697
698 std::set<int>& non_postgame_locations = GetState().non_postgame_locations;
699 non_postgame_locations.clear();
700
701 const std::set<int>& reachable_rooms =
702 postgame_calculator.GetReachableRooms();
703 const std::set<int>& solveable_panels =
704 postgame_calculator.GetSolveablePanels();
705
706 for (const MapArea& map_area : GD_GetMapAreas()) {
707 bool area_reachable = false;
708
709 for (const Location& location_section : map_area.locations) {
710 bool reachable = reachable_rooms.count(location_section.room);
711 if (reachable) {
712 for (int panel_id : location_section.panels) {
713 reachable &= (solveable_panels.count(panel_id) == 1);
714 }
715 }
716
717 if (!reachable && IsLocationWinCondition(location_section)) {
718 reachable = true;
719 }
720
721 if (reachable) {
722 non_postgame_locations.insert(location_section.ap_location_id);
723 area_reachable = true;
724 }
725 }
726
727 for (int painting_id : map_area.paintings) {
728 if (postgame_calculator.GetReachablePaintings().count(painting_id)) {
729 area_reachable = true;
730 }
731 }
732
733 if (area_reachable) {
734 non_postgame_areas.insert(map_area.id);
735 }
736 }
737
738 GetState().non_postgame_paintings =
739 postgame_calculator.GetReachablePaintings();
740 }
661} 741}
662 742
663void RecalculateReachability() { 743void RecalculateReachability() {
@@ -736,3 +816,33 @@ bool IsPilgrimageDoable() {
736 816
737 return GetState().pilgrimage_doable; 817 return GetState().pilgrimage_doable;
738} 818}
819
820bool IsAreaPostgame(int area_id) {
821 std::lock_guard reachability_guard(GetState().reachability_mutex);
822
823 if (GetState().non_postgame_areas.empty()) {
824 return false;
825 } else {
826 return !GetState().non_postgame_areas.count(area_id);
827 }
828}
829
830bool IsLocationPostgame(int location_id) {
831 std::lock_guard reachability_guard(GetState().reachability_mutex);
832
833 if (GetState().non_postgame_locations.empty()) {
834 return false;
835 } else {
836 return !GetState().non_postgame_locations.count(location_id);
837 }
838}
839
840bool IsPaintingPostgame(int painting_id) {
841 std::lock_guard reachability_guard(GetState().reachability_mutex);
842
843 if (GetState().non_postgame_paintings.empty()) {
844 return false;
845 } else {
846 return !GetState().non_postgame_paintings.count(painting_id);
847 }
848}
diff --git a/src/tracker_state.h b/src/tracker_state.h index a8f155d..8f1002f 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h
@@ -18,4 +18,10 @@ const std::map<std::string, bool>& GetDoorRequirements(int door_id);
18 18
19bool IsPilgrimageDoable(); 19bool IsPilgrimageDoable();
20 20
21bool IsAreaPostgame(int area_id);
22
23bool IsLocationPostgame(int location_id);
24
25bool IsPaintingPostgame(int painting_id);
26
21#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ 27#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */