about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xassets/areas.yaml4
-rw-r--r--assets/pilgrimage.yaml45
-rw-r--r--src/ap_state.cpp6
-rw-r--r--src/ap_state.h2
-rw-r--r--src/game_data.cpp103
-rw-r--r--src/game_data.h14
-rw-r--r--src/tracker_state.cpp417
7 files changed, 293 insertions, 298 deletions
diff --git a/assets/areas.yaml b/assets/areas.yaml index a1e3423..d38ceb8 100755 --- a/assets/areas.yaml +++ b/assets/areas.yaml
@@ -35,8 +35,12 @@
35 fold_into: Symmetry Room 35 fold_into: Symmetry Room
36 Outside The Agreeable: 36 Outside The Agreeable:
37 map: [1766, 700] 37 map: [1766, 700]
38 Compass Room:
39 fold_into: Outside The Agreeable
38 Hallway Room: 40 Hallway Room:
39 map: [573, 1631] 41 map: [573, 1631]
42 Hallway Room (1):
43 fold_into: Hallway Room
40 Hallway Room (2): 44 Hallway Room (2):
41 fold_into: Hallway Room 45 fold_into: Hallway Room
42 Hallway Room (3): 46 Hallway Room (3):
diff --git a/assets/pilgrimage.yaml b/assets/pilgrimage.yaml deleted file mode 100644 index 04487da..0000000 --- a/assets/pilgrimage.yaml +++ /dev/null
@@ -1,45 +0,0 @@
1---
2 - room: Second Room
3 door: Exit Door
4 - room: Hub Room
5 door: 1 Sunwarp
6 sunwarp: True
7 - room: Crossroads
8 door: Tower Entrance
9 - room: Orange Tower Fourth Floor
10 door: Hot Crusts Door
11 - room: Hot Crusts Area
12 door: 2 Sunwarp
13 sunwarp: True
14 - room: Orange Tower Third Floor
15 door: 3 Sunwarp
16 sunwarp: True
17 - room: Outside The Initiated
18 door: Shortcut to Hub Room
19 - room: Orange Tower First Floor
20 door: Shortcut to Hub Room
21 - room: Orange Tower First Floor
22 door: 4 Sunwarp
23 sunwarp: True
24 - room: Directional Gallery
25 door: Shortcut to The Undeterred
26 - room: Orange Tower First Floor
27 door: Salt Pepper Door
28 - room: Hub Room
29 door: Crossroads Entrance
30 - room: Orange Tower Fourth Floor
31 door: 5 Sunwarp
32 sunwarp: True
33 - room: Color Hunt
34 door: Shortcut to The Steady
35 - room: The Bearer
36 door: Entrance
37 - room: Art Gallery
38 door: Exit
39 - room: The Tenacious
40 door: Shortcut to Hub Room
41 - room: Outside The Agreeable
42 door: Tenacious Entrance
43 - room: Outside The Agreeable
44 door: 6 Sunwarp
45 sunwarp: True
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index efa94bf..ea74c93 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -60,6 +60,7 @@ struct APState {
60 LocationChecks location_checks = kNORMAL_LOCATIONS; 60 LocationChecks location_checks = kNORMAL_LOCATIONS;
61 VictoryCondition victory_condition = kTHE_END; 61 VictoryCondition victory_condition = kTHE_END;
62 bool early_color_hallways = false; 62 bool early_color_hallways = false;
63 bool pilgrimage_enabled = false;
63 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; 64 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL;
64 65
65 std::map<std::string, std::string> painting_mapping; 66 std::map<std::string, std::string> painting_mapping;
@@ -129,6 +130,7 @@ struct APState {
129 location_checks = kNORMAL_LOCATIONS; 130 location_checks = kNORMAL_LOCATIONS;
130 victory_condition = kTHE_END; 131 victory_condition = kTHE_END;
131 early_color_hallways = false; 132 early_color_hallways = false;
133 pilgrimage_enabled = false;
132 sunwarp_access = kSUNWARP_ACCESS_NORMAL; 134 sunwarp_access = kSUNWARP_ACCESS_NORMAL;
133 135
134 connected = false; 136 connected = false;
@@ -224,6 +226,8 @@ struct APState {
224 slot_data["victory_condition"].get<VictoryCondition>(); 226 slot_data["victory_condition"].get<VictoryCondition>();
225 early_color_hallways = slot_data.contains("early_color_hallways") && 227 early_color_hallways = slot_data.contains("early_color_hallways") &&
226 slot_data["early_color_hallways"].get<int>() == 1; 228 slot_data["early_color_hallways"].get<int>() == 1;
229 pilgrimage_enabled = slot_data.contains("enable_pilgrimage") &&
230 slot_data["enable_pilgrimage"].get<int>() == 1;
227 sunwarp_access = slot_data["sunwarp_access"].get<SunwarpAccess>(); 231 sunwarp_access = slot_data["sunwarp_access"].get<SunwarpAccess>();
228 232
229 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 233 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
@@ -421,4 +425,6 @@ bool AP_HasAchievement(const std::string& achievement_name) {
421 425
422bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; } 426bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; }
423 427
428bool AP_IsPilgrimageEnabled() { return GetState().pilgrimage_enabled; }
429
424SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; } 430SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; }
diff --git a/src/ap_state.h b/src/ap_state.h index 0231628..e145066 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -46,6 +46,8 @@ bool AP_HasAchievement(const std::string& achievement_name);
46 46
47bool AP_HasEarlyColorHallways(); 47bool AP_HasEarlyColorHallways();
48 48
49bool AP_IsPilgrimageEnabled();
50
49SunwarpAccess AP_GetSunwarpAccess(); 51SunwarpAccess AP_GetSunwarpAccess();
50 52
51#endif /* end of include guard: AP_STATE_H_664A4180 */ 53#endif /* end of include guard: AP_STATE_H_664A4180 */
diff --git a/src/game_data.cpp b/src/game_data.cpp index dd6e924..28ca598 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -58,9 +58,6 @@ struct GameData {
58 58
59 std::map<LingoColor, int> ap_id_by_color_; 59 std::map<LingoColor, int> ap_id_by_color_;
60 60
61 std::vector<int> pilgrimage_;
62 std::vector<int> pilgrimage_with_sunwarps_;
63
64 bool loaded_area_data_ = false; 61 bool loaded_area_data_ = false;
65 std::set<std::string> malconfigured_areas_; 62 std::set<std::string> malconfigured_areas_;
66 63
@@ -69,8 +66,6 @@ struct GameData {
69 YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml")); 66 YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml"));
70 YAML::Node areas_config = 67 YAML::Node areas_config =
71 YAML::LoadFile(GetAbsolutePath("assets/areas.yaml")); 68 YAML::LoadFile(GetAbsolutePath("assets/areas.yaml"));
72 YAML::Node pilgrimage_config =
73 YAML::LoadFile(GetAbsolutePath("assets/pilgrimage.yaml"));
74 YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml")); 69 YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml"));
75 70
76 auto init_color_id = [this, &ids_config](const std::string &color_name) { 71 auto init_color_id = [this, &ids_config](const std::string &color_name) {
@@ -105,6 +100,34 @@ struct GameData {
105 for (const auto &entrance_it : room_it.second["entrances"]) { 100 for (const auto &entrance_it : room_it.second["entrances"]) {
106 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); 101 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
107 102
103 auto process_single_entrance =
104 [this, room_id, from_room_id](const YAML::Node &option) {
105 Exit exit_obj;
106 exit_obj.destination_room = room_id;
107
108 if (option["door"]) {
109 std::string door_room = rooms_[room_id].name;
110 if (option["room"]) {
111 door_room = option["room"].as<std::string>();
112 }
113 exit_obj.door = AddOrGetDoor(door_room, option["door"].as<std::string>());
114 }
115
116 if (option["painting"] && option["painting"].as<bool>()) {
117 exit_obj.type = EntranceType::kPainting;
118 }
119
120 if (option["sunwarp"] && option["sunwarp"].as<bool>()) {
121 exit_obj.type = EntranceType::kSunwarp;
122 }
123
124 if (option["warp"] && option["warp"].as<bool>()) {
125 exit_obj.type = EntranceType::kWarp;
126 }
127
128 rooms_[from_room_id].exits.push_back(exit_obj);
129 };
130
108 switch (entrance_it.second.Type()) { 131 switch (entrance_it.second.Type()) {
109 case YAML::NodeType::Scalar: { 132 case YAML::NodeType::Scalar: {
110 // This is just "true". 133 // This is just "true".
@@ -112,50 +135,12 @@ struct GameData {
112 break; 135 break;
113 } 136 }
114 case YAML::NodeType::Map: { 137 case YAML::NodeType::Map: {
115 Exit exit_obj; 138 process_single_entrance(entrance_it.second);
116 exit_obj.destination_room = room_id;
117
118 if (entrance_it.second["door"]) {
119 std::string door_room = rooms_[room_id].name;
120 if (entrance_it.second["room"]) {
121 door_room = entrance_it.second["room"].as<std::string>();
122 }
123 exit_obj.door = AddOrGetDoor(
124 door_room, entrance_it.second["door"].as<std::string>());
125 }
126
127 if (entrance_it.second["painting"]) {
128 exit_obj.painting = entrance_it.second["painting"].as<bool>();
129 }
130
131 if (entrance_it.second["sunwarp"]) {
132 exit_obj.sunwarp = entrance_it.second["sunwarp"].as<bool>();
133 }
134
135 rooms_[from_room_id].exits.push_back(exit_obj);
136 break; 139 break;
137 } 140 }
138 case YAML::NodeType::Sequence: { 141 case YAML::NodeType::Sequence: {
139 for (const auto &option : entrance_it.second) { 142 for (const auto &option : entrance_it.second) {
140 Exit exit_obj; 143 process_single_entrance(option);
141 exit_obj.destination_room = room_id;
142
143 std::string door_room = rooms_[room_id].name;
144 if (option["room"]) {
145 door_room = option["room"].as<std::string>();
146 }
147 exit_obj.door =
148 AddOrGetDoor(door_room, option["door"].as<std::string>());
149
150 if (option["painting"]) {
151 exit_obj.painting = option["painting"].as<bool>();
152 }
153
154 if (option["sunwarp"]) {
155 exit_obj.sunwarp = option["sunwarp"].as<bool>();
156 }
157
158 rooms_[from_room_id].exits.push_back(exit_obj);
159 } 144 }
160 145
161 break; 146 break;
@@ -586,22 +571,6 @@ struct GameData {
586 } 571 }
587 } 572 }
588 573
589 // Set up fake pilgrimage.
590 for (const auto &config_node : pilgrimage_config) {
591 int door_id = AddOrGetDoor(config_node["room"].as<std::string>(),
592 config_node["door"].as<std::string>());
593 if (config_node["sunwarp"] && config_node["sunwarp"].as<bool>()) {
594 pilgrimage_with_sunwarps_.push_back(door_id);
595 }
596 pilgrimage_.push_back(door_id);
597 }
598
599 int starting_room_id = AddOrGetRoom("Starting Room");
600 Room &starting_room_obj = rooms_[starting_room_id];
601 starting_room_obj.exits.push_back(
602 Exit{.destination_room = AddOrGetRoom("Pilgrim Antechamber"),
603 .pilgrimage = true});
604
605 // Report errors. 574 // Report errors.
606 for (const std::string &area : malconfigured_areas_) { 575 for (const std::string &area : malconfigured_areas_) {
607 std::ostringstream errstr; 576 std::ostringstream errstr;
@@ -679,6 +648,10 @@ const std::vector<Door> &GD_GetDoors() { return GetState().doors_; }
679 648
680const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); } 649const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); }
681 650
651int GD_GetDoorByName(const std::string &name) {
652 return GetState().door_by_id_.at(name);
653}
654
682const Panel &GD_GetPanel(int panel_id) { 655const Panel &GD_GetPanel(int panel_id) {
683 return GetState().panels_.at(panel_id); 656 return GetState().panels_.at(panel_id);
684} 657}
@@ -694,11 +667,3 @@ const std::vector<int> &GD_GetAchievementPanels() {
694int GD_GetItemIdForColor(LingoColor color) { 667int GD_GetItemIdForColor(LingoColor color) {
695 return GetState().ap_id_by_color_.at(color); 668 return GetState().ap_id_by_color_.at(color);
696} 669}
697
698const std::vector<int> &GD_GetPilgrimageDoors(bool include_sunwarps) {
699 if (include_sunwarps) {
700 return GetState().pilgrimage_with_sunwarps_;
701 } else {
702 return GetState().pilgrimage_;
703 }
704}
diff --git a/src/game_data.h b/src/game_data.h index c230034..2c18588 100644 --- a/src/game_data.h +++ b/src/game_data.h
@@ -23,6 +23,14 @@ constexpr int kLOCATION_NORMAL = 1;
23constexpr int kLOCATION_REDUCED = 2; 23constexpr int kLOCATION_REDUCED = 2;
24constexpr int kLOCATION_INSANITY = 4; 24constexpr int kLOCATION_INSANITY = 4;
25 25
26enum class EntranceType {
27 kNormal,
28 kPainting,
29 kSunwarp,
30 kWarp,
31 kPilgrimage,
32};
33
26struct Panel { 34struct Panel {
27 int id; 35 int id;
28 int room; 36 int room;
@@ -66,9 +74,7 @@ struct Door {
66struct Exit { 74struct Exit {
67 int destination_room; 75 int destination_room;
68 std::optional<int> door; 76 std::optional<int> door;
69 bool painting = false; 77 EntranceType type = EntranceType::kNormal;
70 bool sunwarp = false;
71 bool pilgrimage = false;
72}; 78};
73 79
74struct PaintingExit { 80struct PaintingExit {
@@ -109,10 +115,10 @@ int GD_GetRoomByName(const std::string& name);
109const Room& GD_GetRoom(int room_id); 115const Room& GD_GetRoom(int room_id);
110const std::vector<Door>& GD_GetDoors(); 116const std::vector<Door>& GD_GetDoors();
111const Door& GD_GetDoor(int door_id); 117const Door& GD_GetDoor(int door_id);
118int GD_GetDoorByName(const std::string& name);
112const Panel& GD_GetPanel(int panel_id); 119const Panel& GD_GetPanel(int panel_id);
113int GD_GetRoomForPainting(const std::string& painting_id); 120int GD_GetRoomForPainting(const std::string& painting_id);
114const std::vector<int>& GD_GetAchievementPanels(); 121const std::vector<int>& GD_GetAchievementPanels();
115int GD_GetItemIdForColor(LingoColor color); 122int GD_GetItemIdForColor(LingoColor color);
116const std::vector<int>& GD_GetPilgrimageDoors(bool include_sunwarps);
117 123
118#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ 124#endif /* end of include guard: GAME_DATA_H_9C42AC51 */
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 43f84b4..cc941ef 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -24,242 +24,299 @@ TrackerState& GetState() {
24 return *instance; 24 return *instance;
25} 25}
26 26
27Decision IsDoorReachable_Helper(int door_id, 27struct StateCalculatorOptions {
28 const std::set<int>& reachable_rooms, 28 std::string start = "Menu";
29 const std::set<int>& solveable_panels) { 29 bool pilgrimage = false;
30 const Door& door_obj = GD_GetDoor(door_id); 30};
31 31
32 if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { 32class StateCalculator {
33 if (!reachable_rooms.count(door_obj.room)) { 33 public:
34 return kMaybe; 34 StateCalculator() = default;
35 }
36 35
37 for (int panel_id : door_obj.panels) { 36 explicit StateCalculator(StateCalculatorOptions options)
38 if (!solveable_panels.count(panel_id)) { 37 : options_(options) {}
39 return kMaybe;
40 }
41 }
42 38
43 return kYes; 39 void Calculate() {
44 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && 40 std::list<int> panel_boundary;
45 !door_obj.group_name.empty()) { 41 std::list<Exit> flood_boundary;
46 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; 42 flood_boundary.push_back(
47 } else { 43 {.destination_room = GD_GetRoomByName(options_.start)});
48 bool has_item = AP_HasItem(door_obj.ap_item_id); 44
45 bool reachable_changed = true;
46 while (reachable_changed) {
47 reachable_changed = false;
49 48
50 if (!has_item) { 49 std::list<int> new_panel_boundary;
51 for (const ProgressiveRequirement& prog_req : door_obj.progressives) { 50 for (int panel_id : panel_boundary) {
52 if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) { 51 if (solveable_panels_.count(panel_id)) {
53 has_item = true; 52 continue;
54 break; 53 }
54
55 Decision panel_reachable = IsPanelReachable(panel_id);
56 if (panel_reachable == kYes) {
57 solveable_panels_.insert(panel_id);
58 reachable_changed = true;
59 } else if (panel_reachable == kMaybe) {
60 new_panel_boundary.push_back(panel_id);
55 } 61 }
56 } 62 }
57 }
58 63
59 return has_item ? kYes : kNo; 64 std::list<Exit> new_boundary;
60 } 65 for (const Exit& room_exit : flood_boundary) {
61} 66 if (reachable_rooms_.count(room_exit.destination_room)) {
67 continue;
68 }
62 69
63Decision IsPanelReachable_Helper(int panel_id, 70 bool valid_transition = false;
64 const std::set<int>& reachable_rooms,
65 const std::set<int>& solveable_panels) {
66 const Panel& panel_obj = GD_GetPanel(panel_id);
67 71
68 if (!reachable_rooms.count(panel_obj.room)) { 72 Decision exit_usable = IsExitUsable(room_exit);
69 return kMaybe; 73 if (exit_usable == kYes) {
70 } 74 valid_transition = true;
75 } else if (exit_usable == kMaybe) {
76 new_boundary.push_back(room_exit);
77 }
78
79 if (valid_transition) {
80 reachable_rooms_.insert(room_exit.destination_room);
81 reachable_changed = true;
82
83 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
84 for (const Exit& out_edge : room_obj.exits) {
85 if (out_edge.type != EntranceType::kPainting ||
86 !AP_IsPaintingShuffle()) {
87 new_boundary.push_back(out_edge);
88 }
89 }
90
91 if (AP_IsPaintingShuffle()) {
92 for (const PaintingExit& out_edge : room_obj.paintings) {
93 if (AP_GetPaintingMapping().count(out_edge.id)) {
94 Exit painting_exit;
95 painting_exit.destination_room = GD_GetRoomForPainting(
96 AP_GetPaintingMapping().at(out_edge.id));
97 painting_exit.door = out_edge.door;
98
99 new_boundary.push_back(painting_exit);
100 }
101 }
102 }
71 103
72 if (panel_obj.name == "THE MASTER") { 104 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") {
73 int achievements_accessible = 0; 105 new_boundary.push_back(
106 {.destination_room = GD_GetRoomByName("Outside The Undeterred"),
107 .type = EntranceType::kPainting});
108 }
74 109
75 for (int achieve_id : GD_GetAchievementPanels()) { 110 if (AP_IsPilgrimageEnabled()) {
76 if (solveable_panels.count(achieve_id)) { 111 if (room_obj.name == "Hub Room") {
77 achievements_accessible++; 112 new_boundary.push_back(
113 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
114 .type = EntranceType::kPilgrimage});
115 }
116 } else {
117 if (room_obj.name == "Starting Room") {
118 new_boundary.push_back(
119 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
120 .door =
121 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"),
122 .type = EntranceType::kPainting});
123 }
124 }
78 125
79 if (achievements_accessible >= AP_GetMasteryRequirement()) { 126 for (int panel_id : room_obj.panels) {
80 break; 127 new_panel_boundary.push_back(panel_id);
128 }
81 } 129 }
82 } 130 }
83 }
84 131
85 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 132 flood_boundary = new_boundary;
86 : kMaybe; 133 panel_boundary = new_panel_boundary;
134 }
87 } 135 }
88 136
89 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 137 const std::set<int>& GetReachableRooms() const { return reachable_rooms_; }
90 AP_GetLevel2Requirement() > 1) {
91 int counting_panels_accessible = 0;
92 138
93 for (int solved_panel_id : solveable_panels) { 139 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; }
94 const Panel& solved_panel = GD_GetPanel(solved_panel_id);
95 140
96 if (!solved_panel.non_counting) { 141 private:
97 counting_panels_accessible++; 142 Decision IsDoorReachable(int door_id) {
143 const Door& door_obj = GD_GetDoor(door_id);
144
145 if (!AP_IsPilgrimageEnabled() &&
146 door_obj.item_name == "Pilgrim Room - Sun Painting") {
147 return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo;
148 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
149 if (!reachable_rooms_.count(door_obj.room)) {
150 return kMaybe;
98 } 151 }
99 }
100 152
101 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 153 for (int panel_id : door_obj.panels) {
102 ? kYes 154 if (!solveable_panels_.count(panel_id)) {
103 : kMaybe; 155 return kMaybe;
104 } 156 }
157 }
105 158
106 for (int room_id : panel_obj.required_rooms) { 159 return kYes;
107 if (!reachable_rooms.count(room_id)) { 160 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS &&
108 return kMaybe; 161 !door_obj.group_name.empty()) {
109 } 162 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
110 } 163 } else {
164 bool has_item = AP_HasItem(door_obj.ap_item_id);
165
166 if (!has_item) {
167 for (const ProgressiveRequirement& prog_req : door_obj.progressives) {
168 if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) {
169 has_item = true;
170 break;
171 }
172 }
173 }
111 174
112 for (int door_id : panel_obj.required_doors) { 175 return has_item ? kYes : kNo;
113 Decision door_reachable =
114 IsDoorReachable_Helper(door_id, reachable_rooms, solveable_panels);
115 if (door_reachable == kNo) {
116 const Door& door_obj = GD_GetDoor(door_id);
117 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
118 ? kMaybe
119 : kNo;
120 } else if (door_reachable == kMaybe) {
121 return kMaybe;
122 } 176 }
123 } 177 }
124 178
125 for (int panel_id : panel_obj.required_panels) { 179 Decision IsPanelReachable(int panel_id) {
126 if (!solveable_panels.count(panel_id)) { 180 const Panel& panel_obj = GD_GetPanel(panel_id);
181
182 if (!reachable_rooms_.count(panel_obj.room)) {
127 return kMaybe; 183 return kMaybe;
128 } 184 }
129 }
130 185
131 if (AP_IsColorShuffle()) { 186 if (panel_obj.name == "THE MASTER") {
132 for (LingoColor color : panel_obj.colors) { 187 int achievements_accessible = 0;
133 if (!AP_HasItem(GD_GetItemIdForColor(color))) {
134 return kNo;
135 }
136 }
137 }
138 188
139 return kYes; 189 for (int achieve_id : GD_GetAchievementPanels()) {
140} 190 if (solveable_panels_.count(achieve_id)) {
191 achievements_accessible++;
141 192
142} // namespace 193 if (achievements_accessible >= AP_GetMasteryRequirement()) {
194 break;
195 }
196 }
197 }
143 198
144void RecalculateReachability() { 199 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes
145 std::set<int> reachable_rooms; 200 : kMaybe;
146 std::set<int> solveable_panels; 201 }
147 202
148 std::list<int> panel_boundary; 203 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
149 std::list<Exit> flood_boundary; 204 AP_GetLevel2Requirement() > 1) {
150 flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")}); 205 int counting_panels_accessible = 0;
151 206
152 if (AP_HasEarlyColorHallways()) { 207 for (int solved_panel_id : solveable_panels_) {
153 flood_boundary.push_back( 208 const Panel& solved_panel = GD_GetPanel(solved_panel_id);
154 {.destination_room = GD_GetRoomByName("Outside The Undeterred")});
155 }
156 209
157 bool reachable_changed = true; 210 if (!solved_panel.non_counting) {
158 while (reachable_changed) { 211 counting_panels_accessible++;
159 reachable_changed = false; 212 }
213 }
214
215 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1)
216 ? kYes
217 : kMaybe;
218 }
160 219
161 std::list<int> new_panel_boundary; 220 for (int room_id : panel_obj.required_rooms) {
162 for (int panel_id : panel_boundary) { 221 if (!reachable_rooms_.count(room_id)) {
163 if (solveable_panels.count(panel_id)) { 222 return kMaybe;
164 continue;
165 } 223 }
224 }
166 225
167 Decision panel_reachable = 226 for (int door_id : panel_obj.required_doors) {
168 IsPanelReachable_Helper(panel_id, reachable_rooms, solveable_panels); 227 Decision door_reachable = IsDoorReachable(door_id);
169 if (panel_reachable == kYes) { 228 if (door_reachable == kNo) {
170 solveable_panels.insert(panel_id); 229 const Door& door_obj = GD_GetDoor(door_id);
171 reachable_changed = true; 230 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
172 } else if (panel_reachable == kMaybe) { 231 ? kMaybe
173 new_panel_boundary.push_back(panel_id); 232 : kNo;
233 } else if (door_reachable == kMaybe) {
234 return kMaybe;
174 } 235 }
175 } 236 }
176 237
177 std::list<Exit> new_boundary; 238 for (int panel_id : panel_obj.required_panels) {
178 for (const Exit& room_exit : flood_boundary) { 239 if (!solveable_panels_.count(panel_id)) {
179 if (reachable_rooms.count(room_exit.destination_room)) { 240 return kMaybe;
180 continue;
181 } 241 }
242 }
182 243
183 bool valid_transition = false; 244 if (AP_IsColorShuffle()) {
184 if (room_exit.door.has_value()) { 245 for (LingoColor color : panel_obj.colors) {
185 Decision door_reachable = kMaybe; 246 if (!AP_HasItem(GD_GetItemIdForColor(color))) {
186 if (room_exit.sunwarp) { 247 return kNo;
187 if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) {
188 door_reachable = kYes;
189 } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) {
190 door_reachable = kNo;
191 } else {
192 door_reachable = IsDoorReachable_Helper(
193 *room_exit.door, reachable_rooms, solveable_panels);
194 }
195 } else {
196 door_reachable = IsDoorReachable_Helper(
197 *room_exit.door, reachable_rooms, solveable_panels);
198 }
199 if (door_reachable == kYes) {
200 valid_transition = true;
201 } else if (door_reachable == kMaybe) {
202 new_boundary.push_back(room_exit);
203 }
204 } else if (room_exit.pilgrimage) {
205 Decision pilgrimage_reachable = kYes;
206 if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) {
207 pilgrimage_reachable = kNo;
208 } 248 }
209 if (pilgrimage_reachable == kYes) {
210 for (int door_id : GD_GetPilgrimageDoors(
211 AP_GetSunwarpAccess() == kSUNWARP_ACCESS_UNLOCK ||
212 AP_GetSunwarpAccess() == kSUNWARP_ACCESS_PROGRESSIVE)) {
213 pilgrimage_reachable = IsDoorReachable_Helper(
214 door_id, reachable_rooms, solveable_panels);
215 if (pilgrimage_reachable != kYes) {
216 break;
217 }
218 }
219 }
220 if (pilgrimage_reachable == kYes) {
221 valid_transition = true;
222 } else if (pilgrimage_reachable == kMaybe) {
223 new_boundary.push_back(room_exit);
224 }
225 } else {
226 valid_transition = true;
227 } 249 }
250 }
228 251
229 if (valid_transition) { 252 return kYes;
230 reachable_rooms.insert(room_exit.destination_room); 253 }
231 reachable_changed = true;
232 254
233 const Room& room_obj = GD_GetRoom(room_exit.destination_room); 255 Decision IsExitUsable(const Exit& room_exit) {
234 for (const Exit& out_edge : room_obj.exits) { 256 if (room_exit.type == EntranceType::kPilgrimage) {
235 if (!out_edge.painting || !AP_IsPaintingShuffle()) { 257 if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) {
236 new_boundary.push_back(out_edge); 258 return kNo;
237 } 259 }
260
261 static const std::vector<std::tuple<std::string, std::string>>
262 pilgrimage_pairs = {
263 {"Crossroads", "Hot Crusts Area"},
264 // {"Orange Tower Third Floor", "Orange Tower Third Floor"},
265 {"Outside The Initiated", "Orange Tower First Floor"},
266 {"Outside The Undeterred", "Orange Tower Fourth Floor"},
267 {"Color Hunt", "Outside The Agreeable"}};
268
269 for (const auto& [from_room, to_room] : pilgrimage_pairs) {
270 StateCalculator pilgrimage_calculator(
271 {.start = from_room, .pilgrimage = true});
272 pilgrimage_calculator.Calculate();
273
274 if (!pilgrimage_calculator.GetReachableRooms().count(
275 GD_GetRoomByName(to_room))) {
276 return kMaybe;
238 } 277 }
278 }
239 279
240 if (AP_IsPaintingShuffle()) { 280 return kYes;
241 for (const PaintingExit& out_edge : room_obj.paintings) { 281 }
242 if (AP_GetPaintingMapping().count(out_edge.id)) {
243 Exit painting_exit;
244 painting_exit.destination_room = GD_GetRoomForPainting(
245 AP_GetPaintingMapping().at(out_edge.id));
246 painting_exit.door = out_edge.door;
247 282
248 new_boundary.push_back(painting_exit); 283 if (options_.pilgrimage) {
249 } 284 if (room_exit.type == EntranceType::kWarp ||
250 } 285 room_exit.type == EntranceType::kSunwarp) {
251 } 286 return kNo;
287 }
288 }
252 289
253 for (int panel_id : room_obj.panels) { 290 if (room_exit.type == EntranceType::kSunwarp) {
254 new_panel_boundary.push_back(panel_id); 291 if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) {
255 } 292 return kYes;
293 } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) {
294 return kNo;
256 } 295 }
257 } 296 }
258 297
259 flood_boundary = new_boundary; 298 if (room_exit.door.has_value()) {
260 panel_boundary = new_panel_boundary; 299 return IsDoorReachable(*room_exit.door);
300 }
301
302 return kYes;
261 } 303 }
262 304
305 StateCalculatorOptions options_;
306
307 std::set<int> reachable_rooms_;
308 std::set<int> solveable_panels_;
309};
310
311} // namespace
312
313void RecalculateReachability() {
314 StateCalculator state_calculator;
315 state_calculator.Calculate();
316
317 const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms();
318 const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels();
319
263 std::map<int, bool> new_reachability; 320 std::map<int, bool> new_reachability;
264 for (const MapArea& map_area : GD_GetMapAreas()) { 321 for (const MapArea& map_area : GD_GetMapAreas()) {
265 for (size_t section_id = 0; section_id < map_area.locations.size(); 322 for (size_t section_id = 0; section_id < map_area.locations.size();