about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-04-27 12:48:19 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2024-04-27 12:48:19 -0400
commit2f66b91fbc595fd19acc481567cb9946501e6d92 (patch)
treee5c9835ea2762474cdbe8395a90d2346845b6532
parent004fb711a86d91985d8e94e1b87089db2ac2cadc (diff)
parent9e71e02add40d0108204d0d18ae921e6b82cd77c (diff)
downloadlingo-ap-tracker-2f66b91fbc595fd19acc481567cb9946501e6d92.tar.gz
lingo-ap-tracker-2f66b91fbc595fd19acc481567cb9946501e6d92.tar.bz2
lingo-ap-tracker-2f66b91fbc595fd19acc481567cb9946501e6d92.zip
Merge branch 'main' into panels
-rw-r--r--CHANGELOG.md8
-rw-r--r--VERSION2
-rwxr-xr-xassets/areas.yaml4
-rw-r--r--assets/pilgrimage.yaml27
-rw-r--r--src/ap_state.cpp55
-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.cpp444
-rw-r--r--src/version.h2
12 files changed, 507 insertions, 233 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a2eff1..9ee737e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
1# lingo-ap-tracker Releases 1# lingo-ap-tracker Releases
2 2
3## v0.9.0 - 2024-04-22
4
5- Compatibility update for Archipelago 0.4.6
6
7Download:
8[lingo-ap-tracker-v0.9.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.9.0-win64.zip)<br/>
9Source: [v0.9.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.9.0)
10
3## v0.8.0 - 2024-04-03 11## v0.8.0 - 2024-04-03
4 12
5- Added ability to display the player's position on the map (requires v2.1.0 of 13- Added ability to display the player's position on the map (requires v2.1.0 of
diff --git a/VERSION b/VERSION index 4ea5caf..7965b36 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@
v0.8.0 \ No newline at end of file v0.9.0 \ No newline at end of file
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 7eb8e66..0000000 --- a/assets/pilgrimage.yaml +++ /dev/null
@@ -1,27 +0,0 @@
1---
2 - room: Second Room
3 door: Exit Door
4 - room: Crossroads
5 door: Tower Entrance
6 - room: Orange Tower Fourth Floor
7 door: Hot Crusts Door
8 - room: Outside The Initiated
9 door: Shortcut to Hub Room
10 - room: Orange Tower First Floor
11 door: Shortcut to Hub Room
12 - room: Directional Gallery
13 door: Shortcut to The Undeterred
14 - room: Orange Tower First Floor
15 door: Salt Pepper Door
16 - room: Hub Room
17 door: Crossroads Entrance
18 - room: Color Hunt
19 door: Shortcut to The Steady
20 - room: The Bearer
21 door: Entrance
22 - room: Art Gallery
23 door: Exit
24 - room: The Tenacious
25 door: Shortcut to Hub Room
26 - room: Outside The Agreeable
27 door: Tenacious Entrance
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 62c9f3f..4fd241a 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -65,8 +65,14 @@ struct APState {
65 LocationChecks location_checks = kNORMAL_LOCATIONS; 65 LocationChecks location_checks = kNORMAL_LOCATIONS;
66 VictoryCondition victory_condition = kTHE_END; 66 VictoryCondition victory_condition = kTHE_END;
67 bool early_color_hallways = false; 67 bool early_color_hallways = false;
68 bool pilgrimage_enabled = false;
69 bool pilgrimage_allows_roof_access = false;
70 bool pilgrimage_allows_paintings = false;
71 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL;
72 bool sunwarp_shuffle = false;
68 73
69 std::map<std::string, std::string> painting_mapping; 74 std::map<std::string, std::string> painting_mapping;
75 std::map<int, SunwarpMapping> sunwarp_mapping;
70 76
71 void Connect(std::string server, std::string player, std::string password) { 77 void Connect(std::string server, std::string player, std::string password) {
72 if (!initialized) { 78 if (!initialized) {
@@ -138,6 +144,12 @@ struct APState {
138 location_checks = kNORMAL_LOCATIONS; 144 location_checks = kNORMAL_LOCATIONS;
139 victory_condition = kTHE_END; 145 victory_condition = kTHE_END;
140 early_color_hallways = false; 146 early_color_hallways = false;
147 pilgrimage_enabled = false;
148 pilgrimage_allows_roof_access = false;
149 pilgrimage_allows_paintings = false;
150 sunwarp_access = kSUNWARP_ACCESS_NORMAL;
151 sunwarp_shuffle = false;
152 sunwarp_mapping.clear();
141 153
142 connected = false; 154 connected = false;
143 has_connection_result = false; 155 has_connection_result = false;
@@ -234,6 +246,19 @@ struct APState {
234 slot_data["victory_condition"].get<VictoryCondition>(); 246 slot_data["victory_condition"].get<VictoryCondition>();
235 early_color_hallways = slot_data.contains("early_color_hallways") && 247 early_color_hallways = slot_data.contains("early_color_hallways") &&
236 slot_data["early_color_hallways"].get<int>() == 1; 248 slot_data["early_color_hallways"].get<int>() == 1;
249 pilgrimage_enabled = slot_data.contains("enable_pilgrimage") &&
250 slot_data["enable_pilgrimage"].get<int>() == 1;
251 pilgrimage_allows_roof_access =
252 slot_data.contains("pilgrimage_allows_roof_access") &&
253 slot_data["pilgrimage_allows_roof_access"].get<int>() == 1;
254 pilgrimage_allows_paintings =
255 slot_data.contains("pilgrimage_allows_paintings") &&
256 slot_data["pilgrimage_allows_paintings"].get<int>() == 1;
257 sunwarp_access = slot_data.contains("sunwarp_access")
258 ? slot_data["sunwarp_access"].get<SunwarpAccess>()
259 : kSUNWARP_ACCESS_NORMAL;
260 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") &&
261 slot_data["shuffle_sunwarps"].get<int>() == 1;
237 262
238 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 263 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
239 painting_mapping.clear(); 264 painting_mapping.clear();
@@ -244,6 +269,18 @@ struct APState {
244 } 269 }
245 } 270 }
246 271
272 if (sunwarp_shuffle && slot_data.contains("sunwarp_permutation")) {
273 std::vector<int> inverted_sunwarps;
274 for (const auto& item : slot_data["sunwarp_permutation"]) {
275 inverted_sunwarps.push_back(item);
276 }
277
278 for (int i = 0; i < 6; i++) {
279 sunwarp_mapping[inverted_sunwarps[i]] = SunwarpMapping{
280 .dots = i + 1, .exit_index = inverted_sunwarps[i + 6]};
281 }
282 }
283
247 connected = true; 284 connected = true;
248 has_connection_result = true; 285 has_connection_result = true;
249 286
@@ -476,6 +513,24 @@ bool AP_HasAchievement(const std::string& achievement_name) {
476 513
477bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; } 514bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; }
478 515
516bool AP_IsPilgrimageEnabled() { return GetState().pilgrimage_enabled; }
517
518bool AP_DoesPilgrimageAllowRoofAccess() {
519 return GetState().pilgrimage_allows_roof_access;
520}
521
522bool AP_DoesPilgrimageAllowPaintings() {
523 return GetState().pilgrimage_allows_paintings;
524}
525
526SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; }
527
528bool AP_IsSunwarpShuffle() { return GetState().sunwarp_shuffle; }
529
530const std::map<int, SunwarpMapping>& AP_GetSunwarpMapping() {
531 return GetState().sunwarp_mapping;
532}
533
479bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } 534bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); }
480 535
481std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { 536std::optional<std::tuple<int, int>> AP_GetPlayerPosition() {
diff --git a/src/ap_state.h b/src/ap_state.h index 5c37b56..c514489 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -12,9 +12,31 @@ class TrackerFrame;
12 12
13enum DoorShuffleMode { kNO_DOORS = 0, kPANELS_MODE = 1, kDOORS_MODE = 2 }; 13enum DoorShuffleMode { kNO_DOORS = 0, kPANELS_MODE = 1, kDOORS_MODE = 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
@@ -48,6 +70,18 @@ bool AP_HasAchievement(const std::string& achievement_name);
48 70
49bool AP_HasEarlyColorHallways(); 71bool AP_HasEarlyColorHallways();
50 72
73bool AP_IsPilgrimageEnabled();
74
75bool AP_DoesPilgrimageAllowRoofAccess();
76
77bool AP_DoesPilgrimageAllowPaintings();
78
79SunwarpAccess AP_GetSunwarpAccess();
80
81bool AP_IsSunwarpShuffle();
82
83const std::map<int, SunwarpMapping>& AP_GetSunwarpMapping();
84
51bool AP_HasReachedGoal(); 85bool AP_HasReachedGoal();
52 86
53std::optional<std::tuple<int, int>> AP_GetPlayerPosition(); 87std::optional<std::tuple<int, int>> AP_GetPlayerPosition();
diff --git a/src/game_data.cpp b/src/game_data.cpp index eece8d7..0567623 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -55,11 +55,14 @@ struct GameData {
55 std::vector<int> door_definition_order_; 55 std::vector<int> door_definition_order_;
56 56
57 std::map<std::string, int> room_by_painting_; 57 std::map<std::string, int> room_by_painting_;
58 std::map<int, int> room_by_sunwarp_;
58 59
59 std::vector<int> achievement_panels_; 60 std::vector<int> achievement_panels_;
60 61
61 std::map<LingoColor, int> ap_id_by_color_; 62 std::map<LingoColor, int> ap_id_by_color_;
62 63
64 std::vector<int> sunwarp_doors_;
65
63 bool loaded_area_data_ = false; 66 bool loaded_area_data_ = false;
64 std::set<std::string> malconfigured_areas_; 67 std::set<std::string> malconfigured_areas_;
65 68
@@ -68,8 +71,6 @@ struct GameData {
68 YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml")); 71 YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml"));
69 YAML::Node areas_config = 72 YAML::Node areas_config =
70 YAML::LoadFile(GetAbsolutePath("assets/areas.yaml")); 73 YAML::LoadFile(GetAbsolutePath("assets/areas.yaml"));
71 YAML::Node pilgrimage_config =
72 YAML::LoadFile(GetAbsolutePath("assets/pilgrimage.yaml"));
73 YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml")); 74 YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml"));
74 75
75 auto init_color_id = [this, &ids_config](const std::string &color_name) { 76 auto init_color_id = [this, &ids_config](const std::string &color_name) {
@@ -104,6 +105,40 @@ struct GameData {
104 for (const auto &entrance_it : room_it.second["entrances"]) { 105 for (const auto &entrance_it : room_it.second["entrances"]) {
105 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); 106 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
106 107
108 auto process_single_entrance =
109 [this, room_id, from_room_id](const YAML::Node &option) {
110 Exit exit_obj;
111 exit_obj.destination_room = room_id;
112
113 if (option["door"]) {
114 std::string door_room = rooms_[room_id].name;
115 if (option["room"]) {
116 door_room = option["room"].as<std::string>();
117 }
118 exit_obj.door =
119 AddOrGetDoor(door_room, option["door"].as<std::string>());
120 }
121
122 if (option["painting"] && option["painting"].as<bool>()) {
123 exit_obj.type = EntranceType::kPainting;
124 }
125
126 if (option["sunwarp"] && option["sunwarp"].as<bool>()) {
127 exit_obj.type = EntranceType::kSunwarp;
128 }
129
130 if (option["warp"] && option["warp"].as<bool>()) {
131 exit_obj.type = EntranceType::kWarp;
132 }
133
134 if (rooms_[from_room_id].name == "Crossroads" &&
135 rooms_[room_id].name == "Roof") {
136 exit_obj.type = EntranceType::kCrossroadsRoofAccess;
137 }
138
139 rooms_[from_room_id].exits.push_back(exit_obj);
140 };
141
107 switch (entrance_it.second.Type()) { 142 switch (entrance_it.second.Type()) {
108 case YAML::NodeType::Scalar: { 143 case YAML::NodeType::Scalar: {
109 // This is just "true". 144 // This is just "true".
@@ -111,42 +146,12 @@ struct GameData {
111 break; 146 break;
112 } 147 }
113 case YAML::NodeType::Map: { 148 case YAML::NodeType::Map: {
114 Exit exit_obj; 149 process_single_entrance(entrance_it.second);
115 exit_obj.destination_room = room_id;
116
117 if (entrance_it.second["door"]) {
118 std::string door_room = rooms_[room_id].name;
119 if (entrance_it.second["room"]) {
120 door_room = entrance_it.second["room"].as<std::string>();
121 }
122 exit_obj.door = AddOrGetDoor(
123 door_room, entrance_it.second["door"].as<std::string>());
124 }
125
126 if (entrance_it.second["painting"]) {
127 exit_obj.painting = entrance_it.second["painting"].as<bool>();
128 }
129
130 rooms_[from_room_id].exits.push_back(exit_obj);
131 break; 150 break;
132 } 151 }
133 case YAML::NodeType::Sequence: { 152 case YAML::NodeType::Sequence: {
134 for (const auto &option : entrance_it.second) { 153 for (const auto &option : entrance_it.second) {
135 Exit exit_obj; 154 process_single_entrance(option);
136 exit_obj.destination_room = room_id;
137
138 std::string door_room = rooms_[room_id].name;
139 if (option["room"]) {
140 door_room = option["room"].as<std::string>();
141 }
142 exit_obj.door =
143 AddOrGetDoor(door_room, option["door"].as<std::string>());
144
145 if (option["painting"]) {
146 exit_obj.painting = option["painting"].as<bool>();
147 }
148
149 rooms_[from_room_id].exits.push_back(exit_obj);
150 } 155 }
151 156
152 break; 157 break;
@@ -407,6 +412,14 @@ struct GameData {
407 doors_[door_id].exclude_reduce = 412 doors_[door_id].exclude_reduce =
408 !door_it.second["include_reduce"].as<bool>(); 413 !door_it.second["include_reduce"].as<bool>();
409 } 414 }
415
416 if (doors_[door_id].name.ends_with(" Sunwarp")) {
417 sunwarp_doors_.push_back(door_id);
418 doors_[door_id].type = DoorType::kSunwarp;
419 } else if (doors_[door_id].item_name ==
420 "Pilgrim Room - Sun Painting") {
421 doors_[door_id].type = DoorType::kSunPainting;
422 }
410 } 423 }
411 } 424 }
412 425
@@ -487,6 +500,18 @@ struct GameData {
487 } 500 }
488 } 501 }
489 502
503 if (room_it.second["sunwarps"]) {
504 for (const auto &sunwarp : room_it.second["sunwarps"]) {
505 int index = sunwarp["dots"].as<int>() - 1;
506 if (sunwarp["direction"].as<std::string>() == "exit") {
507 index += 6;
508 }
509
510 rooms_[room_id].sunwarps.push_back(index);
511 room_by_sunwarp_[index] = room_id;
512 }
513 }
514
490 if (room_it.second["progression"]) { 515 if (room_it.second["progression"]) {
491 for (const auto &progression_it : room_it.second["progression"]) { 516 for (const auto &progression_it : room_it.second["progression"]) {
492 std::string progressive_item_name = 517 std::string progressive_item_name =
@@ -654,33 +679,6 @@ struct GameData {
654 } 679 }
655 } 680 }
656 681
657 // Set up fake pilgrimage.
658 int fake_pilgrim_panel_id =
659 AddOrGetPanel("Starting Room", "!! Fake Pilgrimage Panel");
660 Panel &fake_pilgrim_panel_obj = panels_[fake_pilgrim_panel_id];
661 fake_pilgrim_panel_obj.non_counting = true;
662
663 for (const auto &config_node : pilgrimage_config) {
664 fake_pilgrim_panel_obj.required_doors.push_back(
665 AddOrGetDoor(config_node["room"].as<std::string>(),
666 config_node["door"].as<std::string>()));
667 }
668
669 int fake_pilgrim_door_id =
670 AddOrGetDoor("Starting Room", "!! Fake Pilgrimage Door");
671 Door &fake_pilgrim_door_obj = doors_[fake_pilgrim_door_id];
672 fake_pilgrim_door_obj.panels.push_back(fake_pilgrim_panel_id);
673 fake_pilgrim_door_obj.skip_location = true;
674 fake_pilgrim_door_obj.skip_item = true;
675 fake_pilgrim_door_obj.is_event = true;
676
677 int starting_room_id = AddOrGetRoom("Starting Room");
678 Room &starting_room_obj = rooms_[starting_room_id];
679 starting_room_obj.panels.push_back(fake_pilgrim_panel_id);
680 starting_room_obj.exits.push_back(
681 Exit{.destination_room = AddOrGetRoom("Pilgrim Antechamber"),
682 .door = fake_pilgrim_door_id});
683
684 // Report errors. 682 // Report errors.
685 for (const std::string &area : malconfigured_areas_) { 683 for (const std::string &area : malconfigured_areas_) {
686 std::ostringstream errstr; 684 std::ostringstream errstr;
@@ -774,6 +772,10 @@ const PanelDoor &GD_GetPanelDoor(int panel_door_id) {
774 return GetState().panel_doors_.at(panel_door_id); 772 return GetState().panel_doors_.at(panel_door_id);
775} 773}
776 774
775int GD_GetDoorByName(const std::string &name) {
776 return GetState().door_by_id_.at(name);
777}
778
777const Panel &GD_GetPanel(int panel_id) { 779const Panel &GD_GetPanel(int panel_id) {
778 return GetState().panels_.at(panel_id); 780 return GetState().panels_.at(panel_id);
779} 781}
@@ -789,3 +791,11 @@ const std::vector<int> &GD_GetAchievementPanels() {
789int GD_GetItemIdForColor(LingoColor color) { 791int GD_GetItemIdForColor(LingoColor color) {
790 return GetState().ap_id_by_color_.at(color); 792 return GetState().ap_id_by_color_.at(color);
791} 793}
794
795const std::vector<int> &GD_GetSunwarpDoors() {
796 return GetState().sunwarp_doors_;
797}
798
799int GD_GetRoomForSunwarp(int index) {
800 return GetState().room_by_sunwarp_.at(index);
801}
diff --git a/src/game_data.h b/src/game_data.h index aa5ac19..09824d7 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;
@@ -62,6 +77,7 @@ struct Door {
62 int ap_item_id = -1; 77 int ap_item_id = -1;
63 int group_ap_item_id = -1; 78 int group_ap_item_id = -1;
64 int ap_location_id = -1; 79 int ap_location_id = -1;
80 DoorType type = DoorType::kNormal;
65}; 81};
66 82
67struct PanelDoor { 83struct PanelDoor {
@@ -73,7 +89,7 @@ struct PanelDoor {
73struct Exit { 89struct Exit {
74 int destination_room; 90 int destination_room;
75 std::optional<int> door; 91 std::optional<int> door;
76 bool painting = false; 92 EntranceType type = EntranceType::kNormal;
77}; 93};
78 94
79struct PaintingExit { 95struct PaintingExit {
@@ -85,6 +101,7 @@ struct Room {
85 std::string name; 101 std::string name;
86 std::vector<Exit> exits; 102 std::vector<Exit> exits;
87 std::vector<PaintingExit> paintings; 103 std::vector<PaintingExit> paintings;
104 std::vector<int> sunwarps;
88 std::vector<int> panels; 105 std::vector<int> panels;
89}; 106};
90 107
@@ -114,10 +131,13 @@ int GD_GetRoomByName(const std::string& name);
114const Room& GD_GetRoom(int room_id); 131const Room& GD_GetRoom(int room_id);
115const std::vector<Door>& GD_GetDoors(); 132const std::vector<Door>& GD_GetDoors();
116const Door& GD_GetDoor(int door_id); 133const Door& GD_GetDoor(int door_id);
134int GD_GetDoorByName(const std::string& name);
117const Panel& GD_GetPanel(int panel_id); 135const Panel& GD_GetPanel(int panel_id);
118const PanelDoor& GD_GetPanelDoor(int panel_door_id); 136const PanelDoor& GD_GetPanelDoor(int panel_door_id);
119int GD_GetRoomForPainting(const std::string& painting_id); 137int GD_GetRoomForPainting(const std::string& painting_id);
120const std::vector<int>& GD_GetAchievementPanels(); 138const std::vector<int>& GD_GetAchievementPanels();
121int GD_GetItemIdForColor(LingoColor color); 139int GD_GetItemIdForColor(LingoColor color);
140const std::vector<int>& GD_GetSunwarpDoors();
141int GD_GetRoomForSunwarp(int index);
122 142
123#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ 143#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 9d2c6d1..d3dde0d 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() != kDOORS_MODE || 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 }
40 } 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 }
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() == kDOORS_MODE && AP_AreDoorsGrouped() && 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,75 +182,125 @@ Decision IsDoorReachable_Helper(int door_id,
58 182
59 return has_item ? kYes : kNo; 183 return has_item ? kYes : kNo;
60 } 184 }
61}
62 185
63Decision IsPanelReachable_Helper(int panel_id, 186 Decision IsDoorReachable_Helper(int door_id) {
64 const std::set<int>& reachable_rooms, 187 const Door& door_obj = GD_GetDoor(door_id);
65 const std::set<int>& solveable_panels) { 188
66 const Panel& panel_obj = GD_GetPanel(panel_id); 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() != kDOORS_MODE || door_obj.skip_item) {
204 if (!reachable_rooms_.count(door_obj.room)) {
205 return kMaybe;
206 }
67 207
68 if (!reachable_rooms.count(panel_obj.room)) { 208 for (int panel_id : door_obj.panels) {
69 return kMaybe; 209 if (!solveable_panels_.count(panel_id)) {
210 return kMaybe;
211 }
212 }
213
214 return kYes;
215 } else if ((AP_GetDoorShuffleMode() == kDOORS_MODE && AP_AreDoorsGrouped() &&
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 }
70 } 221 }
71 222
72 if (panel_obj.name == "THE MASTER") { 223 Decision IsDoorReachable(int door_id) {
73 int achievements_accessible = 0; 224 if (options_.parent) {
225 return options_.parent->IsDoorReachable(door_id);
226 }
74 227
75 for (int achieve_id : GD_GetAchievementPanels()) { 228 if (door_decisions_.count(door_id)) {
76 if (solveable_panels.count(achieve_id)) { 229 return door_decisions_.at(door_id);
77 achievements_accessible++; 230 }
78 231
79 if (achievements_accessible >= AP_GetMasteryRequirement()) { 232 Decision result = IsDoorReachable_Helper(door_id);
80 break; 233 if (result != kMaybe) {
81 } 234 door_decisions_[door_id] = result;
82 }
83 } 235 }
84 236
85 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 237 return result;
86 : kMaybe;
87 } 238 }
88 239
89 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 240 Decision IsPanelReachable(int panel_id) {
90 AP_GetLevel2Requirement() > 1) { 241 const Panel& panel_obj = GD_GetPanel(panel_id);
91 int counting_panels_accessible = 0;
92 242
93 for (int solved_panel_id : solveable_panels) { 243 if (!reachable_rooms_.count(panel_obj.room)) {
94 const Panel& solved_panel = GD_GetPanel(solved_panel_id); 244 return kMaybe;
245 }
95 246
96 if (!solved_panel.non_counting) { 247 if (panel_obj.name == "THE MASTER") {
97 counting_panels_accessible++; 248 int achievements_accessible = 0;
249
250 for (int achieve_id : GD_GetAchievementPanels()) {
251 if (solveable_panels_.count(achieve_id)) {
252 achievements_accessible++;
253
254 if (achievements_accessible >= AP_GetMasteryRequirement()) {
255 break;
256 }
257 }
98 } 258 }
259
260 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes
261 : kMaybe;
99 } 262 }
100 263
101 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 264 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
102 ? kYes 265 AP_GetLevel2Requirement() > 1) {
103 : kMaybe; 266 int counting_panels_accessible = 0;
104 }
105 267
106 for (int room_id : panel_obj.required_rooms) { 268 for (int solved_panel_id : solveable_panels_) {
107 if (!reachable_rooms.count(room_id)) { 269 const Panel& solved_panel = GD_GetPanel(solved_panel_id);
108 return kMaybe; 270
271 if (!solved_panel.non_counting) {
272 counting_panels_accessible++;
273 }
274 }
275
276 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1)
277 ? kYes
278 : kMaybe;
109 } 279 }
110 }
111 280
112 for (int door_id : panel_obj.required_doors) { 281 for (int room_id : panel_obj.required_rooms) {
113 Decision door_reachable = 282 if (!reachable_rooms_.count(room_id)) {
114 IsDoorReachable_Helper(door_id, reachable_rooms, solveable_panels); 283 return kMaybe;
115 if (door_reachable == kNo) { 284 }
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 } 285 }
123 }
124 286
125 for (int panel_id : panel_obj.required_panels) { 287 for (int door_id : panel_obj.required_doors) {
126 if (!solveable_panels.count(panel_id)) { 288 Decision door_reachable = IsDoorReachable(door_id);
127 return kMaybe; 289 if (door_reachable == kNo) {
290 const Door& door_obj = GD_GetDoor(door_id);
291 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
292 ? kMaybe
293 : kNo;
294 } else if (door_reachable == kMaybe) {
295 return kMaybe;
296 }
297 }
298
299 for (int panel_id : panel_obj.required_panels) {
300 if (!solveable_panels_.count(panel_id)) {
301 return kMaybe;
302 }
128 } 303 }
129 }
130 304
131 if (AP_IsColorShuffle()) { 305 if (AP_IsColorShuffle()) {
132 for (LingoColor color : panel_obj.colors) { 306 for (LingoColor color : panel_obj.colors) {
@@ -156,98 +330,92 @@ Decision IsPanelReachable_Helper(int panel_id,
156 330
157 return has_item ? kYes : kNo; 331 return has_item ? kYes : kNo;
158 } 332 }
159 }
160 333
161 return kYes; 334 return kYes;
162}
163
164} // namespace
165
166void RecalculateReachability() {
167 std::set<int> reachable_rooms;
168 std::set<int> solveable_panels;
169
170 std::list<int> panel_boundary;
171 std::list<Exit> flood_boundary;
172 flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")});
173
174 if (AP_HasEarlyColorHallways()) {
175 flood_boundary.push_back(
176 {.destination_room = GD_GetRoomByName("Outside The Undeterred")});
177 } 335 }
178 336
179 bool reachable_changed = true; 337 Decision IsExitUsable(const Exit& room_exit) {
180 while (reachable_changed) { 338 if (room_exit.type == EntranceType::kPilgrimage) {
181 reachable_changed = false; 339 if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) {
340 return kNo;
341 }
182 342
183 std::list<int> new_panel_boundary; 343 if (AP_GetSunwarpAccess() != kSUNWARP_ACCESS_NORMAL) {
184 for (int panel_id : panel_boundary) { 344 for (int door_id : GD_GetSunwarpDoors()) {
185 if (solveable_panels.count(panel_id)) { 345 Decision sub_decision = IsDoorReachable(door_id);
186 continue; 346 if (sub_decision != kYes) {
347 return sub_decision;
348 }
349 }
187 } 350 }
188 351
189 Decision panel_reachable = 352 static const std::vector<std::tuple<std::string, std::string>>
190 IsPanelReachable_Helper(panel_id, reachable_rooms, solveable_panels); 353 pilgrimage_pairs = {
191 if (panel_reachable == kYes) { 354 {"Crossroads", "Hot Crusts Area"},
192 solveable_panels.insert(panel_id); 355 // {"Orange Tower Third Floor", "Orange Tower Third Floor"},
193 reachable_changed = true; 356 {"Outside The Initiated", "Orange Tower First Floor"},
194 } else if (panel_reachable == kMaybe) { 357 {"Outside The Undeterred", "Orange Tower Fourth Floor"},
195 new_panel_boundary.push_back(panel_id); 358 {"Color Hunt", "Outside The Agreeable"}};
359
360 for (const auto& [from_room, to_room] : pilgrimage_pairs) {
361 StateCalculator pilgrimage_calculator(
362 {.start = from_room, .pilgrimage = true, .parent = this});
363 pilgrimage_calculator.Calculate();
364
365 if (!pilgrimage_calculator.GetReachableRooms().count(
366 GD_GetRoomByName(to_room))) {
367 return kMaybe;
368 }
196 } 369 }
370
371 return kYes;
197 } 372 }
198 373
199 std::list<Exit> new_boundary; 374 if (options_.pilgrimage) {
200 for (const Exit& room_exit : flood_boundary) { 375 if (room_exit.type == EntranceType::kWarp ||
201 if (reachable_rooms.count(room_exit.destination_room)) { 376 room_exit.type == EntranceType::kSunwarp) {
202 continue; 377 return kNo;
378 }
379 if (room_exit.type == EntranceType::kCrossroadsRoofAccess &&
380 !AP_DoesPilgrimageAllowRoofAccess()) {
381 return kNo;
203 } 382 }
383 if (room_exit.type == EntranceType::kPainting &&
384 !AP_DoesPilgrimageAllowPaintings()) {
385 return kNo;
386 }
387 }
204 388
205 bool valid_transition = false; 389 if (room_exit.type == EntranceType::kSunwarp) {
206 if (room_exit.door.has_value()) { 390 if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) {
207 Decision door_reachable = IsDoorReachable_Helper( 391 return kYes;
208 *room_exit.door, reachable_rooms, solveable_panels); 392 } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) {
209 if (door_reachable == kYes) { 393 return kNo;
210 valid_transition = true;
211 } else if (door_reachable == kMaybe) {
212 new_boundary.push_back(room_exit);
213 }
214 } else {
215 valid_transition = true;
216 } 394 }
395 }
217 396
218 if (valid_transition) { 397 if (room_exit.door.has_value()) {
219 reachable_rooms.insert(room_exit.destination_room); 398 return IsDoorReachable(*room_exit.door);
220 reachable_changed = true; 399 }
221 400
222 const Room& room_obj = GD_GetRoom(room_exit.destination_room); 401 return kYes;
223 for (const Exit& out_edge : room_obj.exits) { 402 }
224 if (!out_edge.painting || !AP_IsPaintingShuffle()) {
225 new_boundary.push_back(out_edge);
226 }
227 }
228 403
229 if (AP_IsPaintingShuffle()) { 404 StateCalculatorOptions options_;
230 for (const PaintingExit& out_edge : room_obj.paintings) {
231 if (AP_GetPaintingMapping().count(out_edge.id)) {
232 Exit painting_exit;
233 painting_exit.destination_room = GD_GetRoomForPainting(
234 AP_GetPaintingMapping().at(out_edge.id));
235 painting_exit.door = out_edge.door;
236 405
237 new_boundary.push_back(painting_exit); 406 std::set<int> reachable_rooms_;
238 } 407 std::map<int, Decision> door_decisions_;
239 } 408 std::set<int> solveable_panels_;
240 } 409};
241 410
242 for (int panel_id : room_obj.panels) { 411} // namespace
243 new_panel_boundary.push_back(panel_id);
244 }
245 }
246 }
247 412
248 flood_boundary = new_boundary; 413void RecalculateReachability() {
249 panel_boundary = new_panel_boundary; 414 StateCalculator state_calculator;
250 } 415 state_calculator.Calculate();
416
417 const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms();
418 const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels();
251 419
252 std::map<int, bool> new_reachability; 420 std::map<int, bool> new_reachability;
253 for (const MapArea& map_area : GD_GetMapAreas()) { 421 for (const MapArea& map_area : GD_GetMapAreas()) {
diff --git a/src/version.h b/src/version.h index 859fc69..db75351 100644 --- a/src/version.h +++ b/src/version.h
@@ -33,6 +33,6 @@ struct Version {
33 33
34std::ostream& operator<<(std::ostream& out, const Version& ver); 34std::ostream& operator<<(std::ostream& out, const Version& ver);
35 35
36constexpr const Version kTrackerVersion = Version(0, 8, 0); 36constexpr const Version kTrackerVersion = Version(0, 9, 0);
37 37
38#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file 38#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file