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.cpp447
1 files changed, 308 insertions, 139 deletions
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index e02ee14..0101e98 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -24,27 +24,151 @@ TrackerState& GetState() {
24 return *instance; 24 return *instance;
25} 25}
26 26
27Decision IsDoorReachable_Helper(int door_id, 27class StateCalculator;
28 const std::set<int>& reachable_rooms,
29 const std::set<int>& solveable_panels) {
30 const Door& door_obj = GD_GetDoor(door_id);
31 28
32 if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { 29struct StateCalculatorOptions {
33 if (!reachable_rooms.count(door_obj.room)) { 30 std::string start = "Menu";
34 return kMaybe; 31 bool pilgrimage = false;
35 } 32 StateCalculator* parent = nullptr;
33};
36 34
37 for (int panel_id : door_obj.panels) { 35class StateCalculator {
38 if (!solveable_panels.count(panel_id)) { 36 public:
39 return kMaybe; 37 StateCalculator() = default;
38
39 explicit StateCalculator(StateCalculatorOptions options)
40 : options_(options) {}
41
42 void Calculate() {
43 std::list<int> panel_boundary;
44 std::list<Exit> flood_boundary;
45 flood_boundary.push_back(
46 {.destination_room = GD_GetRoomByName(options_.start)});
47
48 bool reachable_changed = true;
49 while (reachable_changed) {
50 reachable_changed = false;
51
52 std::list<int> new_panel_boundary;
53 for (int panel_id : panel_boundary) {
54 if (solveable_panels_.count(panel_id)) {
55 continue;
56 }
57
58 Decision panel_reachable = IsPanelReachable(panel_id);
59 if (panel_reachable == kYes) {
60 solveable_panels_.insert(panel_id);
61 reachable_changed = true;
62 } else if (panel_reachable == kMaybe) {
63 new_panel_boundary.push_back(panel_id);
64 }
65 }
66
67 std::list<Exit> new_boundary;
68 for (const Exit& room_exit : flood_boundary) {
69 if (reachable_rooms_.count(room_exit.destination_room)) {
70 continue;
71 }
72
73 bool valid_transition = false;
74
75 Decision exit_usable = IsExitUsable(room_exit);
76 if (exit_usable == kYes) {
77 valid_transition = true;
78 } else if (exit_usable == kMaybe) {
79 new_boundary.push_back(room_exit);
80 }
81
82 if (valid_transition) {
83 reachable_rooms_.insert(room_exit.destination_room);
84 reachable_changed = true;
85
86 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
87 for (const Exit& out_edge : room_obj.exits) {
88 if (out_edge.type == EntranceType::kPainting &&
89 AP_IsPaintingShuffle()) {
90 continue;
91 }
92
93 if (out_edge.type == EntranceType::kSunwarp &&
94 AP_IsSunwarpShuffle()) {
95 continue;
96 }
97
98 new_boundary.push_back(out_edge);
99 }
100
101 if (AP_IsPaintingShuffle()) {
102 for (const PaintingExit& out_edge : room_obj.paintings) {
103 if (AP_GetPaintingMapping().count(out_edge.id)) {
104 Exit painting_exit;
105 painting_exit.destination_room = GD_GetRoomForPainting(
106 AP_GetPaintingMapping().at(out_edge.id));
107 painting_exit.door = out_edge.door;
108
109 new_boundary.push_back(painting_exit);
110 }
111 }
112 }
113
114 if (AP_IsSunwarpShuffle()) {
115 for (int index : room_obj.sunwarps) {
116 if (AP_GetSunwarpMapping().count(index)) {
117 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index);
118
119 Exit sunwarp_exit;
120 sunwarp_exit.destination_room =
121 GD_GetRoomForSunwarp(sm.exit_index);
122 sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1);
123
124 new_boundary.push_back(sunwarp_exit);
125 }
126 }
127 }
128
129 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") {
130 new_boundary.push_back(
131 {.destination_room = GD_GetRoomByName("Outside The Undeterred"),
132 .type = EntranceType::kPainting});
133 }
134
135 if (AP_IsPilgrimageEnabled()) {
136 if (room_obj.name == "Hub Room") {
137 new_boundary.push_back(
138 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
139 .type = EntranceType::kPilgrimage});
140 }
141 } else {
142 if (room_obj.name == "Starting Room") {
143 new_boundary.push_back(
144 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
145 .door =
146 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"),
147 .type = EntranceType::kPainting});
148 }
149 }
150
151 for (int panel_id : room_obj.panels) {
152 new_panel_boundary.push_back(panel_id);
153 }
154 }
40 } 155 }
156
157 flood_boundary = new_boundary;
158 panel_boundary = new_panel_boundary;
41 } 159 }
160 }
42 161
43 return kYes; 162 const std::set<int>& GetReachableRooms() const { return reachable_rooms_; }
44 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && 163
45 !door_obj.group_name.empty()) { 164 const std::map<int, Decision>& GetDoorDecisions() const {
46 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; 165 return door_decisions_;
47 } else { 166 }
167
168 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; }
169
170 private:
171 Decision IsNonGroupedDoorReachable(const Door& door_obj) {
48 bool has_item = AP_HasItem(door_obj.ap_item_id); 172 bool has_item = AP_HasItem(door_obj.ap_item_id);
49 173
50 if (!has_item) { 174 if (!has_item) {
@@ -58,175 +182,220 @@ Decision IsDoorReachable_Helper(int door_id,
58 182
59 return has_item ? kYes : kNo; 183 return has_item ? kYes : kNo;
60 } 184 }
61}
62
63Decision IsPanelReachable_Helper(int panel_id,
64 const std::set<int>& reachable_rooms,
65 const std::set<int>& solveable_panels) {
66 const Panel& panel_obj = GD_GetPanel(panel_id);
67
68 if (!reachable_rooms.count(panel_obj.room)) {
69 return kMaybe;
70 }
71
72 if (panel_obj.name == "THE MASTER") {
73 int achievements_accessible = 0;
74 185
75 for (int achieve_id : GD_GetAchievementPanels()) { 186 Decision IsDoorReachable_Helper(int door_id) {
76 if (solveable_panels.count(achieve_id)) { 187 const Door& door_obj = GD_GetDoor(door_id);
77 achievements_accessible++; 188
189 if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) {
190 return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo;
191 } else if (door_obj.type == DoorType::kSunwarp) {
192 switch (AP_GetSunwarpAccess()) {
193 case kSUNWARP_ACCESS_NORMAL:
194 return kYes;
195 case kSUNWARP_ACCESS_DISABLED:
196 return kNo;
197 case kSUNWARP_ACCESS_UNLOCK:
198 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
199 case kSUNWARP_ACCESS_INDIVIDUAL:
200 case kSUNWARP_ACCESS_PROGRESSIVE:
201 return IsNonGroupedDoorReachable(door_obj);
202 }
203 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
204 if (!reachable_rooms_.count(door_obj.room)) {
205 return kMaybe;
206 }
78 207
79 if (achievements_accessible >= AP_GetMasteryRequirement()) { 208 for (int panel_id : door_obj.panels) {
80 break; 209 if (!solveable_panels_.count(panel_id)) {
210 return kMaybe;
81 } 211 }
82 } 212 }
83 }
84 213
85 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 214 return kYes;
86 : kMaybe; 215 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS &&
216 !door_obj.group_name.empty()) {
217 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
218 } else {
219 return IsNonGroupedDoorReachable(door_obj);
220 }
87 } 221 }
88 222
89 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 223 Decision IsDoorReachable(int door_id) {
90 AP_GetLevel2Requirement() > 1) { 224 if (options_.parent) {
91 int counting_panels_accessible = 0; 225 return options_.parent->IsDoorReachable(door_id);
226 }
92 227
93 for (int solved_panel_id : solveable_panels) { 228 if (door_decisions_.count(door_id)) {
94 const Panel& solved_panel = GD_GetPanel(solved_panel_id); 229 return door_decisions_.at(door_id);
230 }
95 231
96 if (!solved_panel.non_counting) { 232 Decision result = IsDoorReachable_Helper(door_id);
97 counting_panels_accessible++; 233 if (result != kMaybe) {
98 } 234 door_decisions_[door_id] = result;
99 } 235 }
100 236
101 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 237 return result;
102 ? kYes
103 : kMaybe;
104 } 238 }
105 239
106 for (int room_id : panel_obj.required_rooms) { 240 Decision IsPanelReachable(int panel_id) {
107 if (!reachable_rooms.count(room_id)) { 241 const Panel& panel_obj = GD_GetPanel(panel_id);
108 return kMaybe;
109 }
110 }
111 242
112 for (int door_id : panel_obj.required_doors) { 243 if (!reachable_rooms_.count(panel_obj.room)) {
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; 244 return kMaybe;
122 } 245 }
123 }
124 246
125 for (int panel_id : panel_obj.required_panels) { 247 if (panel_obj.name == "THE MASTER") {
126 if (!solveable_panels.count(panel_id)) { 248 int achievements_accessible = 0;
127 return kMaybe;
128 }
129 }
130 249
131 if (AP_IsColorShuffle()) { 250 for (int achieve_id : GD_GetAchievementPanels()) {
132 for (LingoColor color : panel_obj.colors) { 251 if (solveable_panels_.count(achieve_id)) {
133 if (!AP_HasItem(GD_GetItemIdForColor(color))) { 252 achievements_accessible++;
134 return kNo;
135 }
136 }
137 }
138 253
139 return kYes; 254 if (achievements_accessible >= AP_GetMasteryRequirement()) {
140} 255 break;
256 }
257 }
258 }
141 259
142} // namespace 260 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes
261 : kMaybe;
262 }
143 263
144void RecalculateReachability() { 264 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
145 std::set<int> reachable_rooms; 265 AP_GetLevel2Requirement() > 1) {
146 std::set<int> solveable_panels; 266 int counting_panels_accessible = 0;
147 267
148 std::list<int> panel_boundary; 268 for (int solved_panel_id : solveable_panels_) {
149 std::list<Exit> flood_boundary; 269 const Panel& solved_panel = GD_GetPanel(solved_panel_id);
150 flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")});
151 270
152 if (AP_HasEarlyColorHallways()) { 271 if (!solved_panel.non_counting) {
153 flood_boundary.push_back( 272 counting_panels_accessible++;
154 {.destination_room = GD_GetRoomByName("Outside The Undeterred")}); 273 }
155 } 274 }
156 275
157 bool reachable_changed = true; 276 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1)
158 while (reachable_changed) { 277 ? kYes
159 reachable_changed = false; 278 : kMaybe;
279 }
160 280
161 std::list<int> new_panel_boundary; 281 for (int room_id : panel_obj.required_rooms) {
162 for (int panel_id : panel_boundary) { 282 if (!reachable_rooms_.count(room_id)) {
163 if (solveable_panels.count(panel_id)) { 283 return kMaybe;
164 continue;
165 } 284 }
285 }
166 286
167 Decision panel_reachable = 287 for (int door_id : panel_obj.required_doors) {
168 IsPanelReachable_Helper(panel_id, reachable_rooms, solveable_panels); 288 Decision door_reachable = IsDoorReachable(door_id);
169 if (panel_reachable == kYes) { 289 if (door_reachable == kNo) {
170 solveable_panels.insert(panel_id); 290 const Door& door_obj = GD_GetDoor(door_id);
171 reachable_changed = true; 291 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
172 } else if (panel_reachable == kMaybe) { 292 ? kMaybe
173 new_panel_boundary.push_back(panel_id); 293 : kNo;
294 } else if (door_reachable == kMaybe) {
295 return kMaybe;
174 } 296 }
175 } 297 }
176 298
177 std::list<Exit> new_boundary; 299 for (int panel_id : panel_obj.required_panels) {
178 for (const Exit& room_exit : flood_boundary) { 300 if (!solveable_panels_.count(panel_id)) {
179 if (reachable_rooms.count(room_exit.destination_room)) { 301 return kMaybe;
180 continue;
181 } 302 }
303 }
182 304
183 bool valid_transition = false; 305 if (AP_IsColorShuffle()) {
184 if (room_exit.door.has_value()) { 306 for (LingoColor color : panel_obj.colors) {
185 Decision door_reachable = IsDoorReachable_Helper( 307 if (!AP_HasItem(GD_GetItemIdForColor(color))) {
186 *room_exit.door, reachable_rooms, solveable_panels); 308 return kNo;
187 if (door_reachable == kYes) {
188 valid_transition = true;
189 } else if (door_reachable == kMaybe) {
190 new_boundary.push_back(room_exit);
191 } 309 }
192 } else {
193 valid_transition = true;
194 } 310 }
311 }
195 312
196 if (valid_transition) { 313 return kYes;
197 reachable_rooms.insert(room_exit.destination_room); 314 }
198 reachable_changed = true;
199
200 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
201 for (const Exit& out_edge : room_obj.exits) {
202 if (!out_edge.painting || !AP_IsPaintingShuffle()) {
203 new_boundary.push_back(out_edge);
204 }
205 }
206 315
207 if (AP_IsPaintingShuffle()) { 316 Decision IsExitUsable(const Exit& room_exit) {
208 for (const PaintingExit& out_edge : room_obj.paintings) { 317 if (room_exit.type == EntranceType::kPilgrimage) {
209 if (AP_GetPaintingMapping().count(out_edge.id)) { 318 if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) {
210 Exit painting_exit; 319 return kNo;
211 painting_exit.destination_room = GD_GetRoomForPainting( 320 }
212 AP_GetPaintingMapping().at(out_edge.id));
213 painting_exit.door = out_edge.door;
214 321
215 new_boundary.push_back(painting_exit); 322 if (AP_GetSunwarpAccess() != kSUNWARP_ACCESS_NORMAL) {
216 } 323 for (int door_id : GD_GetSunwarpDoors()) {
324 Decision sub_decision = IsDoorReachable(door_id);
325 if (sub_decision != kYes) {
326 return sub_decision;
217 } 327 }
218 } 328 }
329 }
219 330
220 for (int panel_id : room_obj.panels) { 331 static const std::vector<std::tuple<std::string, std::string>>
221 new_panel_boundary.push_back(panel_id); 332 pilgrimage_pairs = {
333 {"Crossroads", "Hot Crusts Area"},
334 // {"Orange Tower Third Floor", "Orange Tower Third Floor"},
335 {"Outside The Initiated", "Orange Tower First Floor"},
336 {"Outside The Undeterred", "Orange Tower Fourth Floor"},
337 {"Color Hunt", "Outside The Agreeable"}};
338
339 for (const auto& [from_room, to_room] : pilgrimage_pairs) {
340 StateCalculator pilgrimage_calculator(
341 {.start = from_room, .pilgrimage = true, .parent = this});
342 pilgrimage_calculator.Calculate();
343
344 if (!pilgrimage_calculator.GetReachableRooms().count(
345 GD_GetRoomByName(to_room))) {
346 return kMaybe;
222 } 347 }
223 } 348 }
349
350 return kYes;
224 } 351 }
225 352
226 flood_boundary = new_boundary; 353 if (options_.pilgrimage) {
227 panel_boundary = new_panel_boundary; 354 if (room_exit.type == EntranceType::kWarp ||
355 room_exit.type == EntranceType::kSunwarp) {
356 return kNo;
357 }
358 if (room_exit.type == EntranceType::kCrossroadsRoofAccess &&
359 !AP_DoesPilgrimageAllowRoofAccess()) {
360 return kNo;
361 }
362 if (room_exit.type == EntranceType::kPainting &&
363 !AP_DoesPilgrimageAllowPaintings()) {
364 return kNo;
365 }
366 }
367
368 if (room_exit.type == EntranceType::kSunwarp) {
369 if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) {
370 return kYes;
371 } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) {
372 return kNo;
373 }
374 }
375
376 if (room_exit.door.has_value()) {
377 return IsDoorReachable(*room_exit.door);
378 }
379
380 return kYes;
228 } 381 }
229 382
383 StateCalculatorOptions options_;
384
385 std::set<int> reachable_rooms_;
386 std::map<int, Decision> door_decisions_;
387 std::set<int> solveable_panels_;
388};
389
390} // namespace
391
392void RecalculateReachability() {
393 StateCalculator state_calculator;
394 state_calculator.Calculate();
395
396 const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms();
397 const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels();
398
230 std::map<int, bool> new_reachability; 399 std::map<int, bool> new_reachability;
231 for (const MapArea& map_area : GD_GetMapAreas()) { 400 for (const MapArea& map_area : GD_GetMapAreas()) {
232 for (size_t section_id = 0; section_id < map_area.locations.size(); 401 for (size_t section_id = 0; section_id < map_area.locations.size();