about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ap_state.cpp57
-rw-r--r--src/ap_state.h40
-rw-r--r--src/game_data.cpp132
-rw-r--r--src/game_data.h22
-rw-r--r--src/global.cpp2
-rw-r--r--src/tracker_panel.h2
-rw-r--r--src/tracker_state.cpp451
-rw-r--r--src/version.h2
8 files changed, 504 insertions, 204 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 58670e6..8feb78b 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -64,8 +64,14 @@ struct APState {
64 LocationChecks location_checks = kNORMAL_LOCATIONS; 64 LocationChecks location_checks = kNORMAL_LOCATIONS;
65 VictoryCondition victory_condition = kTHE_END; 65 VictoryCondition victory_condition = kTHE_END;
66 bool early_color_hallways = false; 66 bool early_color_hallways = false;
67 bool pilgrimage_enabled = false;
68 bool pilgrimage_allows_roof_access = false;
69 bool pilgrimage_allows_paintings = false;
70 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL;
71 bool sunwarp_shuffle = false;
67 72
68 std::map<std::string, std::string> painting_mapping; 73 std::map<std::string, std::string> painting_mapping;
74 std::map<int, SunwarpMapping> sunwarp_mapping;
69 75
70 void Connect(std::string server, std::string player, std::string password) { 76 void Connect(std::string server, std::string player, std::string password) {
71 if (!initialized) { 77 if (!initialized) {
@@ -136,6 +142,12 @@ struct APState {
136 location_checks = kNORMAL_LOCATIONS; 142 location_checks = kNORMAL_LOCATIONS;
137 victory_condition = kTHE_END; 143 victory_condition = kTHE_END;
138 early_color_hallways = false; 144 early_color_hallways = false;
145 pilgrimage_enabled = false;
146 pilgrimage_allows_roof_access = false;
147 pilgrimage_allows_paintings = false;
148 sunwarp_access = kSUNWARP_ACCESS_NORMAL;
149 sunwarp_shuffle = false;
150 sunwarp_mapping.clear();
139 151
140 connected = false; 152 connected = false;
141 has_connection_result = false; 153 has_connection_result = false;
@@ -221,6 +233,19 @@ struct APState {
221 slot_data["victory_condition"].get<VictoryCondition>(); 233 slot_data["victory_condition"].get<VictoryCondition>();
222 early_color_hallways = slot_data.contains("early_color_hallways") && 234 early_color_hallways = slot_data.contains("early_color_hallways") &&
223 slot_data["early_color_hallways"].get<int>() == 1; 235 slot_data["early_color_hallways"].get<int>() == 1;
236 pilgrimage_enabled = slot_data.contains("enable_pilgrimage") &&
237 slot_data["enable_pilgrimage"].get<int>() == 1;
238 pilgrimage_allows_roof_access =
239 slot_data.contains("pilgrimage_allows_roof_access") &&
240 slot_data["pilgrimage_allows_roof_access"].get<int>() == 1;
241 pilgrimage_allows_paintings =
242 slot_data.contains("pilgrimage_allows_paintings") &&
243 slot_data["pilgrimage_allows_paintings"].get<int>() == 1;
244 sunwarp_access = slot_data.contains("sunwarp_access")
245 ? slot_data["sunwarp_access"].get<SunwarpAccess>()
246 : kSUNWARP_ACCESS_NORMAL;
247 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") &&
248 slot_data["shuffle_sunwarps"].get<int>() == 1;
224 249
225 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 250 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
226 painting_mapping.clear(); 251 painting_mapping.clear();
@@ -231,6 +256,18 @@ struct APState {
231 } 256 }
232 } 257 }
233 258
259 if (sunwarp_shuffle && slot_data.contains("sunwarp_permutation")) {
260 std::vector<int> inverted_sunwarps;
261 for (const auto& item : slot_data["sunwarp_permutation"]) {
262 inverted_sunwarps.push_back(item);
263 }
264
265 for (int i = 0; i < 6; i++) {
266 sunwarp_mapping[inverted_sunwarps[i]] = SunwarpMapping{
267 .dots = i + 1, .exit_index = inverted_sunwarps[i + 6]};
268 }
269 }
270
234 connected = true; 271 connected = true;
235 has_connection_result = true; 272 has_connection_result = true;
236 273
@@ -346,7 +383,7 @@ struct APState {
346 } else { 383 } else {
347 data_storage.erase(key); 384 data_storage.erase(key);
348 } 385 }
349 386
350 TrackerLog("Data storage " + key + " retrieved as null"); 387 TrackerLog("Data storage " + key + " retrieved as null");
351 } 388 }
352 } 389 }
@@ -461,6 +498,24 @@ bool AP_HasAchievement(const std::string& achievement_name) {
461 498
462bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; } 499bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; }
463 500
501bool AP_IsPilgrimageEnabled() { return GetState().pilgrimage_enabled; }
502
503bool AP_DoesPilgrimageAllowRoofAccess() {
504 return GetState().pilgrimage_allows_roof_access;
505}
506
507bool AP_DoesPilgrimageAllowPaintings() {
508 return GetState().pilgrimage_allows_paintings;
509}
510
511SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; }
512
513bool AP_IsSunwarpShuffle() { return GetState().sunwarp_shuffle; }
514
515const std::map<int, SunwarpMapping>& AP_GetSunwarpMapping() {
516 return GetState().sunwarp_mapping;
517}
518
464bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } 519bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); }
465 520
466std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { 521std::optional<std::tuple<int, int>> AP_GetPlayerPosition() {
diff --git a/src/ap_state.h b/src/ap_state.h index 420a032..6667e0d 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -12,9 +12,31 @@ class TrackerFrame;
12 12
13enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 }; 13enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 };
14 14
15enum VictoryCondition { kTHE_END = 0, kTHE_MASTER = 1, kLEVEL_2 = 2 }; 15enum VictoryCondition {
16 16 kTHE_END = 0,
17enum LocationChecks { kNORMAL_LOCATIONS = 0, kREDUCED_LOCATIONS = 1, kPANELSANITY = 2 }; 17 kTHE_MASTER = 1,
18 kLEVEL_2 = 2,
19 kPILGRIMAGE = 3
20};
21
22enum LocationChecks {
23 kNORMAL_LOCATIONS = 0,
24 kREDUCED_LOCATIONS = 1,
25 kPANELSANITY = 2
26};
27
28enum SunwarpAccess {
29 kSUNWARP_ACCESS_NORMAL = 0,
30 kSUNWARP_ACCESS_DISABLED = 1,
31 kSUNWARP_ACCESS_UNLOCK = 2,
32 kSUNWARP_ACCESS_INDIVIDUAL = 3,
33 kSUNWARP_ACCESS_PROGRESSIVE = 4
34};
35
36struct SunwarpMapping {
37 int dots;
38 int exit_index;
39};
18 40
19void AP_SetTrackerFrame(TrackerFrame* tracker_frame); 41void AP_SetTrackerFrame(TrackerFrame* tracker_frame);
20 42
@@ -46,6 +68,18 @@ bool AP_HasAchievement(const std::string& achievement_name);
46 68
47bool AP_HasEarlyColorHallways(); 69bool AP_HasEarlyColorHallways();
48 70
71bool AP_IsPilgrimageEnabled();
72
73bool AP_DoesPilgrimageAllowRoofAccess();
74
75bool AP_DoesPilgrimageAllowPaintings();
76
77SunwarpAccess AP_GetSunwarpAccess();
78
79bool AP_IsSunwarpShuffle();
80
81const std::map<int, SunwarpMapping>& AP_GetSunwarpMapping();
82
49bool AP_HasReachedGoal(); 83bool AP_HasReachedGoal();
50 84
51std::optional<std::tuple<int, int>> AP_GetPlayerPosition(); 85std::optional<std::tuple<int, int>> AP_GetPlayerPosition();
diff --git a/src/game_data.cpp b/src/game_data.cpp index ee818c4..c98f532 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -53,11 +53,14 @@ struct GameData {
53 std::vector<int> door_definition_order_; 53 std::vector<int> door_definition_order_;
54 54
55 std::map<std::string, int> room_by_painting_; 55 std::map<std::string, int> room_by_painting_;
56 std::map<int, int> room_by_sunwarp_;
56 57
57 std::vector<int> achievement_panels_; 58 std::vector<int> achievement_panels_;
58 59
59 std::map<LingoColor, int> ap_id_by_color_; 60 std::map<LingoColor, int> ap_id_by_color_;
60 61
62 std::vector<int> sunwarp_doors_;
63
61 bool loaded_area_data_ = false; 64 bool loaded_area_data_ = false;
62 std::set<std::string> malconfigured_areas_; 65 std::set<std::string> malconfigured_areas_;
63 66
@@ -66,8 +69,6 @@ struct GameData {
66 YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml")); 69 YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml"));
67 YAML::Node areas_config = 70 YAML::Node areas_config =
68 YAML::LoadFile(GetAbsolutePath("assets/areas.yaml")); 71 YAML::LoadFile(GetAbsolutePath("assets/areas.yaml"));
69 YAML::Node pilgrimage_config =
70 YAML::LoadFile(GetAbsolutePath("assets/pilgrimage.yaml"));
71 YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml")); 72 YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml"));
72 73
73 auto init_color_id = [this, &ids_config](const std::string &color_name) { 74 auto init_color_id = [this, &ids_config](const std::string &color_name) {
@@ -102,6 +103,40 @@ struct GameData {
102 for (const auto &entrance_it : room_it.second["entrances"]) { 103 for (const auto &entrance_it : room_it.second["entrances"]) {
103 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); 104 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
104 105
106 auto process_single_entrance =
107 [this, room_id, from_room_id](const YAML::Node &option) {
108 Exit exit_obj;
109 exit_obj.destination_room = room_id;
110
111 if (option["door"]) {
112 std::string door_room = rooms_[room_id].name;
113 if (option["room"]) {
114 door_room = option["room"].as<std::string>();
115 }
116 exit_obj.door =
117 AddOrGetDoor(door_room, option["door"].as<std::string>());
118 }
119
120 if (option["painting"] && option["painting"].as<bool>()) {
121 exit_obj.type = EntranceType::kPainting;
122 }
123
124 if (option["sunwarp"] && option["sunwarp"].as<bool>()) {
125 exit_obj.type = EntranceType::kSunwarp;
126 }
127
128 if (option["warp"] && option["warp"].as<bool>()) {
129 exit_obj.type = EntranceType::kWarp;
130 }
131
132 if (rooms_[from_room_id].name == "Crossroads" &&
133 rooms_[room_id].name == "Roof") {
134 exit_obj.type = EntranceType::kCrossroadsRoofAccess;
135 }
136
137 rooms_[from_room_id].exits.push_back(exit_obj);
138 };
139
105 switch (entrance_it.second.Type()) { 140 switch (entrance_it.second.Type()) {
106 case YAML::NodeType::Scalar: { 141 case YAML::NodeType::Scalar: {
107 // This is just "true". 142 // This is just "true".
@@ -109,42 +144,12 @@ struct GameData {
109 break; 144 break;
110 } 145 }
111 case YAML::NodeType::Map: { 146 case YAML::NodeType::Map: {
112 Exit exit_obj; 147 process_single_entrance(entrance_it.second);
113 exit_obj.destination_room = room_id;
114
115 if (entrance_it.second["door"]) {
116 std::string door_room = rooms_[room_id].name;
117 if (entrance_it.second["room"]) {
118 door_room = entrance_it.second["room"].as<std::string>();
119 }
120 exit_obj.door = AddOrGetDoor(
121 door_room, entrance_it.second["door"].as<std::string>());
122 }
123
124 if (entrance_it.second["painting"]) {
125 exit_obj.painting = entrance_it.second["painting"].as<bool>();
126 }
127
128 rooms_[from_room_id].exits.push_back(exit_obj);
129 break; 148 break;
130 } 149 }
131 case YAML::NodeType::Sequence: { 150 case YAML::NodeType::Sequence: {
132 for (const auto &option : entrance_it.second) { 151 for (const auto &option : entrance_it.second) {
133 Exit exit_obj; 152 process_single_entrance(option);
134 exit_obj.destination_room = room_id;
135
136 std::string door_room = rooms_[room_id].name;
137 if (option["room"]) {
138 door_room = option["room"].as<std::string>();
139 }
140 exit_obj.door =
141 AddOrGetDoor(door_room, option["door"].as<std::string>());
142
143 if (option["painting"]) {
144 exit_obj.painting = option["painting"].as<bool>();
145 }
146
147 rooms_[from_room_id].exits.push_back(exit_obj);
148 } 153 }
149 154
150 break; 155 break;
@@ -405,6 +410,14 @@ struct GameData {
405 doors_[door_id].exclude_reduce = 410 doors_[door_id].exclude_reduce =
406 !door_it.second["include_reduce"].as<bool>(); 411 !door_it.second["include_reduce"].as<bool>();
407 } 412 }
413
414 if (doors_[door_id].name.ends_with(" Sunwarp")) {
415 sunwarp_doors_.push_back(door_id);
416 doors_[door_id].type = DoorType::kSunwarp;
417 } else if (doors_[door_id].item_name ==
418 "Pilgrim Room - Sun Painting") {
419 doors_[door_id].type = DoorType::kSunPainting;
420 }
408 } 421 }
409 } 422 }
410 423
@@ -432,6 +445,18 @@ struct GameData {
432 } 445 }
433 } 446 }
434 447
448 if (room_it.second["sunwarps"]) {
449 for (const auto &sunwarp : room_it.second["sunwarps"]) {
450 int index = sunwarp["dots"].as<int>() - 1;
451 if (sunwarp["direction"].as<std::string>() == "exit") {
452 index += 6;
453 }
454
455 rooms_[room_id].sunwarps.push_back(index);
456 room_by_sunwarp_[index] = room_id;
457 }
458 }
459
435 if (room_it.second["progression"]) { 460 if (room_it.second["progression"]) {
436 for (const auto &progression_it : room_it.second["progression"]) { 461 for (const auto &progression_it : room_it.second["progression"]) {
437 std::string progressive_item_name = 462 std::string progressive_item_name =
@@ -575,33 +600,6 @@ struct GameData {
575 } 600 }
576 } 601 }
577 602
578 // Set up fake pilgrimage.
579 int fake_pilgrim_panel_id =
580 AddOrGetPanel("Starting Room", "!! Fake Pilgrimage Panel");
581 Panel &fake_pilgrim_panel_obj = panels_[fake_pilgrim_panel_id];
582 fake_pilgrim_panel_obj.non_counting = true;
583
584 for (const auto &config_node : pilgrimage_config) {
585 fake_pilgrim_panel_obj.required_doors.push_back(
586 AddOrGetDoor(config_node["room"].as<std::string>(),
587 config_node["door"].as<std::string>()));
588 }
589
590 int fake_pilgrim_door_id =
591 AddOrGetDoor("Starting Room", "!! Fake Pilgrimage Door");
592 Door &fake_pilgrim_door_obj = doors_[fake_pilgrim_door_id];
593 fake_pilgrim_door_obj.panels.push_back(fake_pilgrim_panel_id);
594 fake_pilgrim_door_obj.skip_location = true;
595 fake_pilgrim_door_obj.skip_item = true;
596 fake_pilgrim_door_obj.is_event = true;
597
598 int starting_room_id = AddOrGetRoom("Starting Room");
599 Room &starting_room_obj = rooms_[starting_room_id];
600 starting_room_obj.panels.push_back(fake_pilgrim_panel_id);
601 starting_room_obj.exits.push_back(
602 Exit{.destination_room = AddOrGetRoom("Pilgrim Antechamber"),
603 .door = fake_pilgrim_door_id});
604
605 // Report errors. 603 // Report errors.
606 for (const std::string &area : malconfigured_areas_) { 604 for (const std::string &area : malconfigured_areas_) {
607 std::ostringstream errstr; 605 std::ostringstream errstr;
@@ -679,6 +677,10 @@ const std::vector<Door> &GD_GetDoors() { return GetState().doors_; }
679 677
680const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); } 678const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); }
681 679
680int GD_GetDoorByName(const std::string &name) {
681 return GetState().door_by_id_.at(name);
682}
683
682const Panel &GD_GetPanel(int panel_id) { 684const Panel &GD_GetPanel(int panel_id) {
683 return GetState().panels_.at(panel_id); 685 return GetState().panels_.at(panel_id);
684} 686}
@@ -694,3 +696,11 @@ const std::vector<int> &GD_GetAchievementPanels() {
694int GD_GetItemIdForColor(LingoColor color) { 696int GD_GetItemIdForColor(LingoColor color) {
695 return GetState().ap_id_by_color_.at(color); 697 return GetState().ap_id_by_color_.at(color);
696} 698}
699
700const std::vector<int> &GD_GetSunwarpDoors() {
701 return GetState().sunwarp_doors_;
702}
703
704int GD_GetRoomForSunwarp(int index) {
705 return GetState().room_by_sunwarp_.at(index);
706}
diff --git a/src/game_data.h b/src/game_data.h index 8a38264..cd09627 100644 --- a/src/game_data.h +++ b/src/game_data.h
@@ -23,6 +23,21 @@ 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 kCrossroadsRoofAccess,
33};
34
35enum class DoorType {
36 kNormal,
37 kSunwarp,
38 kSunPainting,
39};
40
26struct Panel { 41struct Panel {
27 int id; 42 int id;
28 int room; 43 int room;
@@ -61,12 +76,13 @@ struct Door {
61 int ap_item_id = -1; 76 int ap_item_id = -1;
62 int group_ap_item_id = -1; 77 int group_ap_item_id = -1;
63 int ap_location_id = -1; 78 int ap_location_id = -1;
79 DoorType type = DoorType::kNormal;
64}; 80};
65 81
66struct Exit { 82struct Exit {
67 int destination_room; 83 int destination_room;
68 std::optional<int> door; 84 std::optional<int> door;
69 bool painting = false; 85 EntranceType type = EntranceType::kNormal;
70}; 86};
71 87
72struct PaintingExit { 88struct PaintingExit {
@@ -78,6 +94,7 @@ struct Room {
78 std::string name; 94 std::string name;
79 std::vector<Exit> exits; 95 std::vector<Exit> exits;
80 std::vector<PaintingExit> paintings; 96 std::vector<PaintingExit> paintings;
97 std::vector<int> sunwarps;
81 std::vector<int> panels; 98 std::vector<int> panels;
82}; 99};
83 100
@@ -107,9 +124,12 @@ int GD_GetRoomByName(const std::string& name);
107const Room& GD_GetRoom(int room_id); 124const Room& GD_GetRoom(int room_id);
108const std::vector<Door>& GD_GetDoors(); 125const std::vector<Door>& GD_GetDoors();
109const Door& GD_GetDoor(int door_id); 126const Door& GD_GetDoor(int door_id);
127int GD_GetDoorByName(const std::string& name);
110const Panel& GD_GetPanel(int panel_id); 128const Panel& GD_GetPanel(int panel_id);
111int GD_GetRoomForPainting(const std::string& painting_id); 129int GD_GetRoomForPainting(const std::string& painting_id);
112const std::vector<int>& GD_GetAchievementPanels(); 130const std::vector<int>& GD_GetAchievementPanels();
113int GD_GetItemIdForColor(LingoColor color); 131int GD_GetItemIdForColor(LingoColor color);
132const std::vector<int>& GD_GetSunwarpDoors();
133int GD_GetRoomForSunwarp(int index);
114 134
115#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ 135#endif /* end of include guard: GAME_DATA_H_9C42AC51 */
diff --git a/src/global.cpp b/src/global.cpp index bd0dcaa..1eb3f8d 100644 --- a/src/global.cpp +++ b/src/global.cpp
@@ -36,5 +36,7 @@ bool IsLocationWinCondition(const Location& location) {
36 "Orange Tower Seventh Floor - THE MASTER"; 36 "Orange Tower Seventh Floor - THE MASTER";
37 case kLEVEL_2: 37 case kLEVEL_2:
38 return location.ap_location_name == "Second Room - LEVEL 2"; 38 return location.ap_location_name == "Second Room - LEVEL 2";
39 case kPILGRIMAGE:
40 return location.ap_location_name == "Pilgrim Antechamber - PILGRIM";
39 } 41 }
40} 42}
diff --git a/src/tracker_panel.h b/src/tracker_panel.h index b26d971..06ec7a0 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h
@@ -25,7 +25,7 @@ class TrackerPanel : public wxPanel {
25 int real_y2 = 0; 25 int real_y2 = 0;
26 bool active = true; 26 bool active = true;
27 }; 27 };
28 28
29 void OnPaint(wxPaintEvent &event); 29 void OnPaint(wxPaintEvent &event);
30 void OnMouseMove(wxMouseEvent &event); 30 void OnMouseMove(wxMouseEvent &event);
31 31
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index e02ee14..640a159 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -24,27 +24,150 @@ 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 int start;
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({.destination_room = options_.start});
46
47 bool reachable_changed = true;
48 while (reachable_changed) {
49 reachable_changed = false;
50
51 std::list<int> new_panel_boundary;
52 for (int panel_id : panel_boundary) {
53 if (solveable_panels_.count(panel_id)) {
54 continue;
55 }
56
57 Decision panel_reachable = IsPanelReachable(panel_id);
58 if (panel_reachable == kYes) {
59 solveable_panels_.insert(panel_id);
60 reachable_changed = true;
61 } else if (panel_reachable == kMaybe) {
62 new_panel_boundary.push_back(panel_id);
63 }
64 }
65
66 std::list<Exit> new_boundary;
67 for (const Exit& room_exit : flood_boundary) {
68 if (reachable_rooms_.count(room_exit.destination_room)) {
69 continue;
70 }
71
72 bool valid_transition = false;
73
74 Decision exit_usable = IsExitUsable(room_exit);
75 if (exit_usable == kYes) {
76 valid_transition = true;
77 } else if (exit_usable == kMaybe) {
78 new_boundary.push_back(room_exit);
79 }
80
81 if (valid_transition) {
82 reachable_rooms_.insert(room_exit.destination_room);
83 reachable_changed = true;
84
85 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
86 for (const Exit& out_edge : room_obj.exits) {
87 if (out_edge.type == EntranceType::kPainting &&
88 AP_IsPaintingShuffle()) {
89 continue;
90 }
91
92 if (out_edge.type == EntranceType::kSunwarp &&
93 AP_IsSunwarpShuffle()) {
94 continue;
95 }
96
97 new_boundary.push_back(out_edge);
98 }
99
100 if (AP_IsPaintingShuffle()) {
101 for (const PaintingExit& out_edge : room_obj.paintings) {
102 if (AP_GetPaintingMapping().count(out_edge.id)) {
103 Exit painting_exit;
104 painting_exit.destination_room = GD_GetRoomForPainting(
105 AP_GetPaintingMapping().at(out_edge.id));
106 painting_exit.door = out_edge.door;
107
108 new_boundary.push_back(painting_exit);
109 }
110 }
111 }
112
113 if (AP_IsSunwarpShuffle()) {
114 for (int index : room_obj.sunwarps) {
115 if (AP_GetSunwarpMapping().count(index)) {
116 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index);
117
118 Exit sunwarp_exit;
119 sunwarp_exit.destination_room =
120 GD_GetRoomForSunwarp(sm.exit_index);
121 sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1);
122
123 new_boundary.push_back(sunwarp_exit);
124 }
125 }
126 }
127
128 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") {
129 new_boundary.push_back(
130 {.destination_room = GD_GetRoomByName("Outside The Undeterred"),
131 .type = EntranceType::kPainting});
132 }
133
134 if (AP_IsPilgrimageEnabled()) {
135 if (room_obj.name == "Hub Room") {
136 new_boundary.push_back(
137 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
138 .type = EntranceType::kPilgrimage});
139 }
140 } else {
141 if (room_obj.name == "Starting Room") {
142 new_boundary.push_back(
143 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
144 .door =
145 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"),
146 .type = EntranceType::kPainting});
147 }
148 }
149
150 for (int panel_id : room_obj.panels) {
151 new_panel_boundary.push_back(panel_id);
152 }
153 }
40 } 154 }
155
156 flood_boundary = new_boundary;
157 panel_boundary = new_panel_boundary;
41 } 158 }
159 }
42 160
43 return kYes; 161 const std::set<int>& GetReachableRooms() const { return reachable_rooms_; }
44 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && 162
45 !door_obj.group_name.empty()) { 163 const std::map<int, Decision>& GetDoorDecisions() const {
46 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; 164 return door_decisions_;
47 } else { 165 }
166
167 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; }
168
169 private:
170 Decision IsNonGroupedDoorReachable(const Door& door_obj) {
48 bool has_item = AP_HasItem(door_obj.ap_item_id); 171 bool has_item = AP_HasItem(door_obj.ap_item_id);
49 172
50 if (!has_item) { 173 if (!has_item) {
@@ -58,175 +181,231 @@ Decision IsDoorReachable_Helper(int door_id,
58 181
59 return has_item ? kYes : kNo; 182 return has_item ? kYes : kNo;
60 } 183 }
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 184
68 if (!reachable_rooms.count(panel_obj.room)) { 185 Decision IsDoorReachable_Helper(int door_id) {
69 return kMaybe; 186 const Door& door_obj = GD_GetDoor(door_id);
70 } 187
71 188 if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) {
72 if (panel_obj.name == "THE MASTER") { 189 return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo;
73 int achievements_accessible = 0; 190 } else if (door_obj.type == DoorType::kSunwarp) {
74 191 switch (AP_GetSunwarpAccess()) {
75 for (int achieve_id : GD_GetAchievementPanels()) { 192 case kSUNWARP_ACCESS_NORMAL:
76 if (solveable_panels.count(achieve_id)) { 193 return kYes;
77 achievements_accessible++; 194 case kSUNWARP_ACCESS_DISABLED:
195 return kNo;
196 case kSUNWARP_ACCESS_UNLOCK:
197 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
198 case kSUNWARP_ACCESS_INDIVIDUAL:
199 case kSUNWARP_ACCESS_PROGRESSIVE:
200 return IsNonGroupedDoorReachable(door_obj);
201 }
202 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
203 if (!reachable_rooms_.count(door_obj.room)) {
204 return kMaybe;
205 }
78 206
79 if (achievements_accessible >= AP_GetMasteryRequirement()) { 207 for (int panel_id : door_obj.panels) {
80 break; 208 if (!solveable_panels_.count(panel_id)) {
209 return kMaybe;
81 } 210 }
82 } 211 }
83 }
84 212
85 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 213 return kYes;
86 : kMaybe; 214 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS &&
215 !door_obj.group_name.empty()) {
216 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
217 } else {
218 return IsNonGroupedDoorReachable(door_obj);
219 }
87 } 220 }
88 221
89 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 222 Decision IsDoorReachable(int door_id) {
90 AP_GetLevel2Requirement() > 1) { 223 if (options_.parent) {
91 int counting_panels_accessible = 0; 224 return options_.parent->IsDoorReachable(door_id);
225 }
92 226
93 for (int solved_panel_id : solveable_panels) { 227 if (door_decisions_.count(door_id)) {
94 const Panel& solved_panel = GD_GetPanel(solved_panel_id); 228 return door_decisions_.at(door_id);
229 }
95 230
96 if (!solved_panel.non_counting) { 231 Decision result = IsDoorReachable_Helper(door_id);
97 counting_panels_accessible++; 232 if (result != kMaybe) {
98 } 233 door_decisions_[door_id] = result;
99 } 234 }
100 235
101 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 236 return result;
102 ? kYes
103 : kMaybe;
104 } 237 }
105 238
106 for (int room_id : panel_obj.required_rooms) { 239 Decision IsPanelReachable(int panel_id) {
107 if (!reachable_rooms.count(room_id)) { 240 const Panel& panel_obj = GD_GetPanel(panel_id);
108 return kMaybe;
109 }
110 }
111 241
112 for (int door_id : panel_obj.required_doors) { 242 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; 243 return kMaybe;
122 } 244 }
123 }
124 245
125 for (int panel_id : panel_obj.required_panels) { 246 if (panel_obj.name == "THE MASTER") {
126 if (!solveable_panels.count(panel_id)) { 247 int achievements_accessible = 0;
127 return kMaybe;
128 }
129 }
130 248
131 if (AP_IsColorShuffle()) { 249 for (int achieve_id : GD_GetAchievementPanels()) {
132 for (LingoColor color : panel_obj.colors) { 250 if (solveable_panels_.count(achieve_id)) {
133 if (!AP_HasItem(GD_GetItemIdForColor(color))) { 251 achievements_accessible++;
134 return kNo;
135 }
136 }
137 }
138 252
139 return kYes; 253 if (achievements_accessible >= AP_GetMasteryRequirement()) {
140} 254 break;
255 }
256 }
257 }
141 258
142} // namespace 259 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes
260 : kMaybe;
261 }
143 262
144void RecalculateReachability() { 263 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
145 std::set<int> reachable_rooms; 264 AP_GetLevel2Requirement() > 1) {
146 std::set<int> solveable_panels; 265 int counting_panels_accessible = 0;
147 266
148 std::list<int> panel_boundary; 267 for (int solved_panel_id : solveable_panels_) {
149 std::list<Exit> flood_boundary; 268 const Panel& solved_panel = GD_GetPanel(solved_panel_id);
150 flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")});
151 269
152 if (AP_HasEarlyColorHallways()) { 270 if (!solved_panel.non_counting) {
153 flood_boundary.push_back( 271 counting_panels_accessible++;
154 {.destination_room = GD_GetRoomByName("Outside The Undeterred")}); 272 }
155 } 273 }
156 274
157 bool reachable_changed = true; 275 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1)
158 while (reachable_changed) { 276 ? kYes
159 reachable_changed = false; 277 : kMaybe;
278 }
160 279
161 std::list<int> new_panel_boundary; 280 for (int room_id : panel_obj.required_rooms) {
162 for (int panel_id : panel_boundary) { 281 if (!reachable_rooms_.count(room_id)) {
163 if (solveable_panels.count(panel_id)) { 282 return kMaybe;
164 continue;
165 } 283 }
284 }
166 285
167 Decision panel_reachable = 286 for (int door_id : panel_obj.required_doors) {
168 IsPanelReachable_Helper(panel_id, reachable_rooms, solveable_panels); 287 Decision door_reachable = IsDoorReachable(door_id);
169 if (panel_reachable == kYes) { 288 if (door_reachable == kNo) {
170 solveable_panels.insert(panel_id); 289 const Door& door_obj = GD_GetDoor(door_id);
171 reachable_changed = true; 290 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
172 } else if (panel_reachable == kMaybe) { 291 ? kMaybe
173 new_panel_boundary.push_back(panel_id); 292 : kNo;
293 } else if (door_reachable == kMaybe) {
294 return kMaybe;
174 } 295 }
175 } 296 }
176 297
177 std::list<Exit> new_boundary; 298 for (int panel_id : panel_obj.required_panels) {
178 for (const Exit& room_exit : flood_boundary) { 299 if (!solveable_panels_.count(panel_id)) {
179 if (reachable_rooms.count(room_exit.destination_room)) { 300 return kMaybe;
180 continue;
181 } 301 }
302 }
182 303
183 bool valid_transition = false; 304 if (AP_IsColorShuffle()) {
184 if (room_exit.door.has_value()) { 305 for (LingoColor color : panel_obj.colors) {
185 Decision door_reachable = IsDoorReachable_Helper( 306 if (!AP_HasItem(GD_GetItemIdForColor(color))) {
186 *room_exit.door, reachable_rooms, solveable_panels); 307 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 } 308 }
192 } else {
193 valid_transition = true;
194 } 309 }
310 }
195 311
196 if (valid_transition) { 312 return kYes;
197 reachable_rooms.insert(room_exit.destination_room); 313 }
198 reachable_changed = true;
199 314
200 const Room& room_obj = GD_GetRoom(room_exit.destination_room); 315 Decision IsExitUsable(const Exit& room_exit) {
201 for (const Exit& out_edge : room_obj.exits) { 316 if (room_exit.type == EntranceType::kPilgrimage) {
202 if (!out_edge.painting || !AP_IsPaintingShuffle()) { 317 if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) {
203 new_boundary.push_back(out_edge); 318 return kNo;
319 }
320
321 if (AP_GetSunwarpAccess() != kSUNWARP_ACCESS_NORMAL) {
322 for (int door_id : GD_GetSunwarpDoors()) {
323 Decision sub_decision = IsDoorReachable(door_id);
324 if (sub_decision != kYes) {
325 return sub_decision;
204 } 326 }
205 } 327 }
328 }
206 329
207 if (AP_IsPaintingShuffle()) { 330 std::vector<std::tuple<int, int>> pilgrimage_pairs;
208 for (const PaintingExit& out_edge : room_obj.paintings) { 331 if (AP_IsSunwarpShuffle()) {
209 if (AP_GetPaintingMapping().count(out_edge.id)) { 332 pilgrimage_pairs = std::vector<std::tuple<int, int>>(5);
210 Exit painting_exit;
211 painting_exit.destination_room = GD_GetRoomForPainting(
212 AP_GetPaintingMapping().at(out_edge.id));
213 painting_exit.door = out_edge.door;
214 333
215 new_boundary.push_back(painting_exit); 334 for (const auto& [start_index, mapping] : AP_GetSunwarpMapping()) {
216 } 335 if (mapping.dots > 1) {
336 std::get<1>(pilgrimage_pairs[mapping.dots - 2]) = start_index;
337 }
338 if (mapping.dots < 6) {
339 std::get<0>(pilgrimage_pairs[mapping.dots - 1]) =
340 mapping.exit_index;
217 } 341 }
218 } 342 }
343 } else {
344 pilgrimage_pairs = {{6, 1}, {8, 3}, {9, 4}, {10, 5}};
345 }
219 346
220 for (int panel_id : room_obj.panels) { 347 for (const auto& [from_sunwarp, to_sunwarp] : pilgrimage_pairs) {
221 new_panel_boundary.push_back(panel_id); 348 StateCalculator pilgrimage_calculator(
349 {.start = GD_GetRoomForSunwarp(from_sunwarp),
350 .pilgrimage = true,
351 .parent = this});
352 pilgrimage_calculator.Calculate();
353
354 if (!pilgrimage_calculator.GetReachableRooms().count(
355 GD_GetRoomForSunwarp(to_sunwarp))) {
356 return kMaybe;
222 } 357 }
223 } 358 }
359
360 return kYes;
361 }
362
363 if (options_.pilgrimage) {
364 if (room_exit.type == EntranceType::kWarp ||
365 room_exit.type == EntranceType::kSunwarp) {
366 return kNo;
367 }
368 if (room_exit.type == EntranceType::kCrossroadsRoofAccess &&
369 !AP_DoesPilgrimageAllowRoofAccess()) {
370 return kNo;
371 }
372 if (room_exit.type == EntranceType::kPainting &&
373 !AP_DoesPilgrimageAllowPaintings()) {
374 return kNo;
375 }
224 } 376 }
225 377
226 flood_boundary = new_boundary; 378 if (room_exit.type == EntranceType::kSunwarp) {
227 panel_boundary = new_panel_boundary; 379 if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) {
380 return kYes;
381 } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) {
382 return kNo;
383 }
384 }
385
386 if (room_exit.door.has_value()) {
387 return IsDoorReachable(*room_exit.door);
388 }
389
390 return kYes;
228 } 391 }
229 392
393 StateCalculatorOptions options_;
394
395 std::set<int> reachable_rooms_;
396 std::map<int, Decision> door_decisions_;
397 std::set<int> solveable_panels_;
398};
399
400} // namespace
401
402void RecalculateReachability() {
403 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")});
404 state_calculator.Calculate();
405
406 const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms();
407 const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels();
408
230 std::map<int, bool> new_reachability; 409 std::map<int, bool> new_reachability;
231 for (const MapArea& map_area : GD_GetMapAreas()) { 410 for (const MapArea& map_area : GD_GetMapAreas()) {
232 for (size_t section_id = 0; section_id < map_area.locations.size(); 411 for (size_t section_id = 0; section_id < map_area.locations.size();
diff --git a/src/version.h b/src/version.h index 7b9aebc..36bd8c1 100644 --- a/src/version.h +++ b/src/version.h
@@ -37,6 +37,6 @@ struct Version {
37 } 37 }
38}; 38};
39 39
40constexpr const Version kTrackerVersion = Version(0, 8, 0); 40constexpr const Version kTrackerVersion = Version(0, 9, 0);
41 41
42#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file 42#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file