diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ap_state.cpp | 13 | ||||
-rw-r--r-- | src/ap_state.h | 2 | ||||
-rw-r--r-- | src/area_popup.cpp | 48 | ||||
-rw-r--r-- | src/game_data.cpp | 29 | ||||
-rw-r--r-- | src/game_data.h | 10 | ||||
-rw-r--r-- | src/godot_variant.cpp | 83 | ||||
-rw-r--r-- | src/godot_variant.h | 28 | ||||
-rw-r--r-- | src/network_set.cpp | 34 | ||||
-rw-r--r-- | src/network_set.h | 16 | ||||
-rw-r--r-- | src/subway_map.cpp | 136 | ||||
-rw-r--r-- | src/tracker_frame.cpp | 36 | ||||
-rw-r--r-- | src/tracker_frame.h | 2 | ||||
-rw-r--r-- | src/tracker_panel.cpp | 62 | ||||
-rw-r--r-- | src/tracker_panel.h | 19 | ||||
-rw-r--r-- | src/tracker_state.cpp | 75 | ||||
-rw-r--r-- | src/tracker_state.h | 2 | ||||
-rw-r--r-- | src/version.h | 2 |
17 files changed, 527 insertions, 70 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 876fdd8..f8d4ee0 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp | |||
@@ -52,6 +52,8 @@ struct APState { | |||
52 | std::list<std::string> tracked_data_storage_keys; | 52 | std::list<std::string> tracked_data_storage_keys; |
53 | std::string victory_data_storage_key; | 53 | std::string victory_data_storage_key; |
54 | 54 | ||
55 | std::string save_name; | ||
56 | |||
55 | std::map<int64_t, int> inventory; | 57 | std::map<int64_t, int> inventory; |
56 | std::set<int64_t> checked_locations; | 58 | std::set<int64_t> checked_locations; |
57 | std::map<std::string, std::any> data_storage; | 59 | std::map<std::string, std::any> data_storage; |
@@ -131,6 +133,7 @@ struct APState { | |||
131 | cert_store); | 133 | cert_store); |
132 | } | 134 | } |
133 | 135 | ||
136 | save_name.clear(); | ||
134 | inventory.clear(); | 137 | inventory.clear(); |
135 | checked_locations.clear(); | 138 | checked_locations.clear(); |
136 | data_storage.clear(); | 139 | data_storage.clear(); |
@@ -221,11 +224,15 @@ struct APState { | |||
221 | RefreshTracker(false); | 224 | RefreshTracker(false); |
222 | }); | 225 | }); |
223 | 226 | ||
224 | apclient->set_slot_connected_handler([this, &connection_mutex]( | 227 | apclient->set_slot_connected_handler([this, player, server, |
228 | &connection_mutex]( | ||
225 | const nlohmann::json& slot_data) { | 229 | const nlohmann::json& slot_data) { |
226 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | 230 | tracker_frame->SetStatusMessage( |
231 | fmt::format("Connected to Archipelago! ({}@{})", player, server)); | ||
227 | TrackerLog("Connected to Archipelago!"); | 232 | TrackerLog("Connected to Archipelago!"); |
228 | 233 | ||
234 | save_name = fmt::format("zzAP_{}_{}.save", apclient->get_seed(), | ||
235 | apclient->get_player_number()); | ||
229 | data_storage_prefix = | 236 | data_storage_prefix = |
230 | fmt::format("Lingo_{}_", apclient->get_player_number()); | 237 | fmt::format("Lingo_{}_", apclient->get_player_number()); |
231 | door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); | 238 | door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); |
@@ -507,6 +514,8 @@ void AP_Connect(std::string server, std::string player, std::string password) { | |||
507 | GetState().Connect(server, player, password); | 514 | GetState().Connect(server, player, password); |
508 | } | 515 | } |
509 | 516 | ||
517 | std::string AP_GetSaveName() { return GetState().save_name; } | ||
518 | |||
510 | bool AP_HasCheckedGameLocation(int location_id) { | 519 | bool AP_HasCheckedGameLocation(int location_id) { |
511 | return GetState().HasCheckedGameLocation(location_id); | 520 | return GetState().HasCheckedGameLocation(location_id); |
512 | } | 521 | } |
diff --git a/src/ap_state.h b/src/ap_state.h index 7af7395..f8936e5 100644 --- a/src/ap_state.h +++ b/src/ap_state.h | |||
@@ -43,6 +43,8 @@ void AP_SetTrackerFrame(TrackerFrame* tracker_frame); | |||
43 | 43 | ||
44 | void AP_Connect(std::string server, std::string player, std::string password); | 44 | void AP_Connect(std::string server, std::string player, std::string password); |
45 | 45 | ||
46 | std::string AP_GetSaveName(); | ||
47 | |||
46 | bool AP_HasCheckedGameLocation(int location_id); | 48 | bool AP_HasCheckedGameLocation(int location_id); |
47 | 49 | ||
48 | bool AP_HasCheckedHuntPanel(int location_id); | 50 | bool AP_HasCheckedHuntPanel(int location_id); |
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 58d8897..8d6487e 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp | |||
@@ -2,10 +2,13 @@ | |||
2 | 2 | ||
3 | #include <wx/dcbuffer.h> | 3 | #include <wx/dcbuffer.h> |
4 | 4 | ||
5 | #include <algorithm> | ||
6 | |||
5 | #include "ap_state.h" | 7 | #include "ap_state.h" |
6 | #include "game_data.h" | 8 | #include "game_data.h" |
7 | #include "global.h" | 9 | #include "global.h" |
8 | #include "tracker_config.h" | 10 | #include "tracker_config.h" |
11 | #include "tracker_panel.h" | ||
9 | #include "tracker_state.h" | 12 | #include "tracker_state.h" |
10 | 13 | ||
11 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) | 14 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) |
@@ -43,15 +46,23 @@ void AreaPopup::UpdateIndicators() { | |||
43 | 46 | ||
44 | mem_dc.SetFont(GetFont()); | 47 | mem_dc.SetFont(GetFont()); |
45 | 48 | ||
49 | TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent()); | ||
50 | |||
46 | std::vector<int> real_locations; | 51 | std::vector<int> real_locations; |
47 | 52 | ||
48 | for (int section_id = 0; section_id < map_area.locations.size(); | 53 | for (int section_id = 0; section_id < map_area.locations.size(); |
49 | section_id++) { | 54 | section_id++) { |
50 | const Location& location = map_area.locations.at(section_id); | 55 | const Location& location = map_area.locations.at(section_id); |
51 | 56 | ||
52 | if (!AP_IsLocationVisible(location.classification) && | 57 | if (tracker_panel->IsPanelsMode()) { |
53 | !(location.hunt && GetTrackerConfig().show_hunt_panels)) { | 58 | if (!location.single_panel) { |
54 | continue; | 59 | continue; |
60 | } | ||
61 | } else { | ||
62 | if (!AP_IsLocationVisible(location.classification) && | ||
63 | !(location.hunt && GetTrackerConfig().show_hunt_panels)) { | ||
64 | continue; | ||
65 | } | ||
55 | } | 66 | } |
56 | 67 | ||
57 | real_locations.push_back(section_id); | 68 | real_locations.push_back(section_id); |
@@ -65,7 +76,7 @@ void AreaPopup::UpdateIndicators() { | |||
65 | } | 76 | } |
66 | } | 77 | } |
67 | 78 | ||
68 | if (AP_IsPaintingShuffle()) { | 79 | if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { |
69 | for (int painting_id : map_area.paintings) { | 80 | for (int painting_id : map_area.paintings) { |
70 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | 81 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); |
71 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. | 82 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. |
@@ -102,10 +113,21 @@ void AreaPopup::UpdateIndicators() { | |||
102 | for (int section_id : real_locations) { | 113 | for (int section_id : real_locations) { |
103 | const Location& location = map_area.locations.at(section_id); | 114 | const Location& location = map_area.locations.at(section_id); |
104 | 115 | ||
105 | bool checked = | 116 | bool checked = false; |
106 | AP_HasCheckedGameLocation(location.ap_location_id) || | 117 | if (IsLocationWinCondition(location)) { |
107 | (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id)) || | 118 | checked = AP_HasReachedGoal(); |
108 | (IsLocationWinCondition(location) && AP_HasReachedGoal()); | 119 | } else if (tracker_panel->IsPanelsMode()) { |
120 | const Panel& panel = GD_GetPanel(*location.single_panel); | ||
121 | if (panel.non_counting) { | ||
122 | checked = AP_HasCheckedGameLocation(location.ap_location_id); | ||
123 | } else { | ||
124 | checked = tracker_panel->GetSolvedPanels().contains(panel.nodepath); | ||
125 | } | ||
126 | } else { | ||
127 | checked = | ||
128 | AP_HasCheckedGameLocation(location.ap_location_id) || | ||
129 | (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id)); | ||
130 | } | ||
109 | 131 | ||
110 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | 132 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; |
111 | 133 | ||
@@ -123,18 +145,18 @@ void AreaPopup::UpdateIndicators() { | |||
123 | cur_height += 10 + 32; | 145 | cur_height += 10 + 32; |
124 | } | 146 | } |
125 | 147 | ||
126 | if (AP_IsPaintingShuffle()) { | 148 | if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { |
127 | for (int painting_id : map_area.paintings) { | 149 | for (int painting_id : map_area.paintings) { |
128 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | 150 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); |
129 | bool checked = AP_IsPaintingChecked(painting.internal_id); | ||
130 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | ||
131 | |||
132 | mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); | ||
133 | 151 | ||
134 | bool reachable = IsPaintingReachable(painting_id); | 152 | bool reachable = IsPaintingReachable(painting_id); |
135 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | 153 | const wxColour* text_color = reachable ? wxWHITE : wxRED; |
136 | mem_dc.SetTextForeground(*text_color); | 154 | mem_dc.SetTextForeground(*text_color); |
137 | 155 | ||
156 | bool checked = reachable && AP_IsPaintingChecked(painting.internal_id); | ||
157 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | ||
158 | mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); | ||
159 | |||
138 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. | 160 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. |
139 | mem_dc.DrawText(painting.internal_id, | 161 | mem_dc.DrawText(painting.internal_id, |
140 | {10 + 32 + 10, | 162 | {10 + 32 + 10, |
diff --git a/src/game_data.cpp b/src/game_data.cpp index e75170e..7b805df 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp | |||
@@ -109,6 +109,7 @@ struct GameData { | |||
109 | auto process_single_entrance = | 109 | auto process_single_entrance = |
110 | [this, room_id, from_room_id](const YAML::Node &option) { | 110 | [this, room_id, from_room_id](const YAML::Node &option) { |
111 | Exit exit_obj; | 111 | Exit exit_obj; |
112 | exit_obj.source_room = from_room_id; | ||
112 | exit_obj.destination_room = room_id; | 113 | exit_obj.destination_room = room_id; |
113 | 114 | ||
114 | if (option["door"]) { | 115 | if (option["door"]) { |
@@ -143,7 +144,7 @@ struct GameData { | |||
143 | switch (entrance_it.second.Type()) { | 144 | switch (entrance_it.second.Type()) { |
144 | case YAML::NodeType::Scalar: { | 145 | case YAML::NodeType::Scalar: { |
145 | // This is just "true". | 146 | // This is just "true". |
146 | rooms_[from_room_id].exits.push_back({.destination_room = room_id}); | 147 | rooms_[from_room_id].exits.push_back({.source_room = from_room_id, .destination_room = room_id}); |
147 | break; | 148 | break; |
148 | } | 149 | } |
149 | case YAML::NodeType::Map: { | 150 | case YAML::NodeType::Map: { |
@@ -264,6 +265,11 @@ struct GameData { | |||
264 | panel_it.second["location_name"].as<std::string>(); | 265 | panel_it.second["location_name"].as<std::string>(); |
265 | } | 266 | } |
266 | 267 | ||
268 | if (panel_it.second["id"]) { | ||
269 | panels_[panel_id].nodepath = | ||
270 | panel_it.second["id"].as<std::string>(); | ||
271 | } | ||
272 | |||
267 | if (panel_it.second["hunt"]) { | 273 | if (panel_it.second["hunt"]) { |
268 | panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); | 274 | panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); |
269 | } | 275 | } |
@@ -563,7 +569,8 @@ struct GameData { | |||
563 | .room = panel.room, | 569 | .room = panel.room, |
564 | .panels = {panel.id}, | 570 | .panels = {panel.id}, |
565 | .classification = classification, | 571 | .classification = classification, |
566 | .hunt = panel.hunt}); | 572 | .hunt = panel.hunt, |
573 | .single_panel = panel.id}); | ||
567 | locations_by_name[location_name] = {area_id, | 574 | locations_by_name[location_name] = {area_id, |
568 | map_area.locations.size() - 1}; | 575 | map_area.locations.size() - 1}; |
569 | } | 576 | } |
@@ -616,6 +623,7 @@ struct GameData { | |||
616 | for (const Location &location : map_area.locations) { | 623 | for (const Location &location : map_area.locations) { |
617 | map_area.classification |= location.classification; | 624 | map_area.classification |= location.classification; |
618 | map_area.hunt |= location.hunt; | 625 | map_area.hunt |= location.hunt; |
626 | map_area.has_single_panel |= location.single_panel.has_value(); | ||
619 | } | 627 | } |
620 | } | 628 | } |
621 | 629 | ||
@@ -673,6 +681,18 @@ struct GameData { | |||
673 | } | 681 | } |
674 | } | 682 | } |
675 | 683 | ||
684 | if (subway_it["entrances"]) { | ||
685 | for (const auto &entrance_it : subway_it["entrances"]) { | ||
686 | subway_item.entrances.push_back(entrance_it.as<std::string>()); | ||
687 | } | ||
688 | } | ||
689 | |||
690 | if (subway_it["exits"]) { | ||
691 | for (const auto &exit_it : subway_it["exits"]) { | ||
692 | subway_item.exits.push_back(exit_it.as<std::string>()); | ||
693 | } | ||
694 | } | ||
695 | |||
676 | if (subway_it["sunwarp"]) { | 696 | if (subway_it["sunwarp"]) { |
677 | SubwaySunwarp sunwarp; | 697 | SubwaySunwarp sunwarp; |
678 | sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); | 698 | sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); |
@@ -784,6 +804,11 @@ GameData &GetState() { | |||
784 | 804 | ||
785 | } // namespace | 805 | } // namespace |
786 | 806 | ||
807 | bool SubwayItem::HasWarps() const { | ||
808 | return !(this->tags.empty() && this->entrances.empty() && | ||
809 | this->exits.empty()); | ||
810 | } | ||
811 | |||
787 | bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { | 812 | bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { |
788 | return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); | 813 | return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); |
789 | } | 814 | } |
diff --git a/src/game_data.h b/src/game_data.h index b787e6f..1f6d247 100644 --- a/src/game_data.h +++ b/src/game_data.h | |||
@@ -43,6 +43,7 @@ struct Panel { | |||
43 | int id; | 43 | int id; |
44 | int room; | 44 | int room; |
45 | std::string name; | 45 | std::string name; |
46 | std::string nodepath; | ||
46 | std::vector<LingoColor> colors; | 47 | std::vector<LingoColor> colors; |
47 | std::vector<int> required_rooms; | 48 | std::vector<int> required_rooms; |
48 | std::vector<int> required_doors; | 49 | std::vector<int> required_doors; |
@@ -83,6 +84,7 @@ struct Door { | |||
83 | }; | 84 | }; |
84 | 85 | ||
85 | struct Exit { | 86 | struct Exit { |
87 | int source_room; | ||
86 | int destination_room; | 88 | int destination_room; |
87 | std::optional<int> door; | 89 | std::optional<int> door; |
88 | EntranceType type = EntranceType::kNormal; | 90 | EntranceType type = EntranceType::kNormal; |
@@ -112,6 +114,7 @@ struct Location { | |||
112 | std::vector<int> panels; | 114 | std::vector<int> panels; |
113 | int classification = 0; | 115 | int classification = 0; |
114 | bool hunt = false; | 116 | bool hunt = false; |
117 | std::optional<int> single_panel; | ||
115 | }; | 118 | }; |
116 | 119 | ||
117 | struct MapArea { | 120 | struct MapArea { |
@@ -123,6 +126,7 @@ struct MapArea { | |||
123 | int map_y; | 126 | int map_y; |
124 | int classification = 0; | 127 | int classification = 0; |
125 | bool hunt = false; | 128 | bool hunt = false; |
129 | bool has_single_panel = false; | ||
126 | }; | 130 | }; |
127 | 131 | ||
128 | enum class SubwaySunwarpType { | 132 | enum class SubwaySunwarpType { |
@@ -144,9 +148,13 @@ struct SubwayItem { | |||
144 | int y; | 148 | int y; |
145 | std::optional<int> door; | 149 | std::optional<int> door; |
146 | std::vector<std::string> paintings; | 150 | std::vector<std::string> paintings; |
147 | std::vector<std::string> tags; | 151 | std::vector<std::string> tags; // 2-way teleports |
152 | std::vector<std::string> entrances; // teleport entrances | ||
153 | std::vector<std::string> exits; // teleport exits | ||
148 | std::optional<SubwaySunwarp> sunwarp; | 154 | std::optional<SubwaySunwarp> sunwarp; |
149 | std::optional<std::string> special; | 155 | std::optional<std::string> special; |
156 | |||
157 | bool HasWarps() const; | ||
150 | }; | 158 | }; |
151 | 159 | ||
152 | const std::vector<MapArea>& GD_GetMapAreas(); | 160 | const std::vector<MapArea>& GD_GetMapAreas(); |
diff --git a/src/godot_variant.cpp b/src/godot_variant.cpp new file mode 100644 index 0000000..1bc906f --- /dev/null +++ b/src/godot_variant.cpp | |||
@@ -0,0 +1,83 @@ | |||
1 | // Godot save decoder algorithm by Chris Souvey. | ||
2 | |||
3 | #include "godot_variant.h" | ||
4 | |||
5 | #include <algorithm> | ||
6 | #include <charconv> | ||
7 | #include <cstddef> | ||
8 | #include <fstream> | ||
9 | #include <string> | ||
10 | #include <tuple> | ||
11 | #include <variant> | ||
12 | #include <vector> | ||
13 | |||
14 | namespace { | ||
15 | |||
16 | uint16_t ReadUint16(std::basic_istream<char>& stream) { | ||
17 | uint16_t result; | ||
18 | stream.read(reinterpret_cast<char*>(&result), 2); | ||
19 | return result; | ||
20 | } | ||
21 | |||
22 | uint32_t ReadUint32(std::basic_istream<char>& stream) { | ||
23 | uint32_t result; | ||
24 | stream.read(reinterpret_cast<char*>(&result), 4); | ||
25 | return result; | ||
26 | } | ||
27 | |||
28 | GodotVariant ParseVariant(std::basic_istream<char>& stream) { | ||
29 | uint16_t type = ReadUint16(stream); | ||
30 | stream.ignore(2); | ||
31 | |||
32 | switch (type) { | ||
33 | case 1: { | ||
34 | // bool | ||
35 | bool boolval = (ReadUint32(stream) == 1); | ||
36 | return {boolval}; | ||
37 | } | ||
38 | case 15: { | ||
39 | // nodepath | ||
40 | uint32_t name_length = ReadUint32(stream) & 0x7fffffff; | ||
41 | uint32_t subname_length = ReadUint32(stream) & 0x7fffffff; | ||
42 | uint32_t flags = ReadUint32(stream); | ||
43 | |||
44 | std::vector<std::string> result; | ||
45 | for (size_t i = 0; i < name_length + subname_length; i++) { | ||
46 | uint32_t char_length = ReadUint32(stream); | ||
47 | uint32_t padded_length = (char_length % 4 == 0) | ||
48 | ? char_length | ||
49 | : (char_length + 4 - (char_length % 4)); | ||
50 | std::vector<char> next_bytes(padded_length); | ||
51 | stream.read(next_bytes.data(), padded_length); | ||
52 | std::string next_piece; | ||
53 | std::copy(next_bytes.begin(), | ||
54 | std::next(next_bytes.begin(), char_length), | ||
55 | std::back_inserter(next_piece)); | ||
56 | result.push_back(next_piece); | ||
57 | } | ||
58 | |||
59 | return {result}; | ||
60 | } | ||
61 | case 19: { | ||
62 | // array | ||
63 | uint32_t length = ReadUint32(stream) & 0x7fffffff; | ||
64 | std::vector<GodotVariant> result; | ||
65 | for (size_t i = 0; i < length; i++) { | ||
66 | result.push_back(ParseVariant(stream)); | ||
67 | } | ||
68 | return {result}; | ||
69 | } | ||
70 | default: { | ||
71 | // eh | ||
72 | return {std::monostate{}}; | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | } // namespace | ||
78 | |||
79 | GodotVariant ParseGodotFile(std::string filename) { | ||
80 | std::ifstream file_stream(filename, std::ios_base::binary); | ||
81 | file_stream.ignore(4); | ||
82 | return ParseVariant(file_stream); | ||
83 | } | ||
diff --git a/src/godot_variant.h b/src/godot_variant.h new file mode 100644 index 0000000..620e569 --- /dev/null +++ b/src/godot_variant.h | |||
@@ -0,0 +1,28 @@ | |||
1 | #ifndef GODOT_VARIANT_H_ED7F2EB6 | ||
2 | #define GODOT_VARIANT_H_ED7F2EB6 | ||
3 | |||
4 | #include <string> | ||
5 | #include <variant> | ||
6 | #include <vector> | ||
7 | |||
8 | struct GodotVariant { | ||
9 | using value_type = std::variant<std::monostate, bool, std::vector<std::string>, std::vector<GodotVariant>>; | ||
10 | |||
11 | value_type value; | ||
12 | |||
13 | GodotVariant(value_type v) : value(v) {} | ||
14 | |||
15 | bool AsBool() const { return std::get<bool>(value); } | ||
16 | |||
17 | const std::vector<std::string>& AsNodePath() const { | ||
18 | return std::get<std::vector<std::string>>(value); | ||
19 | } | ||
20 | |||
21 | const std::vector<GodotVariant>& AsArray() const { | ||
22 | return std::get<std::vector<GodotVariant>>(value); | ||
23 | } | ||
24 | }; | ||
25 | |||
26 | GodotVariant ParseGodotFile(std::string filename); | ||
27 | |||
28 | #endif /* end of include guard: GODOT_VARIANT_H_ED7F2EB6 */ | ||
diff --git a/src/network_set.cpp b/src/network_set.cpp index 6d2a098..45911e3 100644 --- a/src/network_set.cpp +++ b/src/network_set.cpp | |||
@@ -4,9 +4,8 @@ void NetworkSet::Clear() { | |||
4 | network_by_item_.clear(); | 4 | network_by_item_.clear(); |
5 | } | 5 | } |
6 | 6 | ||
7 | void NetworkSet::AddLink(int id1, int id2) { | 7 | void NetworkSet::AddLink(int id1, int id2, bool two_way) { |
8 | if (id2 > id1) { | 8 | if (two_way && id2 > id1) { |
9 | // Make sure id1 < id2 | ||
10 | std::swap(id1, id2); | 9 | std::swap(id1, id2); |
11 | } | 10 | } |
12 | 11 | ||
@@ -17,14 +16,37 @@ void NetworkSet::AddLink(int id1, int id2) { | |||
17 | network_by_item_[id2] = {}; | 16 | network_by_item_[id2] = {}; |
18 | } | 17 | } |
19 | 18 | ||
20 | network_by_item_[id1].insert({id1, id2}); | 19 | NetworkNode node = {id1, id2, two_way}; |
21 | network_by_item_[id2].insert({id1, id2}); | 20 | |
21 | network_by_item_[id1].insert(node); | ||
22 | network_by_item_[id2].insert(node); | ||
23 | } | ||
24 | |||
25 | void NetworkSet::AddLinkToNetwork(int network_id, int id1, int id2, bool two_way) { | ||
26 | if (two_way && id2 > id1) { | ||
27 | std::swap(id1, id2); | ||
28 | } | ||
29 | |||
30 | if (!network_by_item_.count(network_id)) { | ||
31 | network_by_item_[network_id] = {}; | ||
32 | } | ||
33 | |||
34 | NetworkNode node = {id1, id2, two_way}; | ||
35 | |||
36 | network_by_item_[network_id].insert(node); | ||
22 | } | 37 | } |
23 | 38 | ||
24 | bool NetworkSet::IsItemInNetwork(int id) const { | 39 | bool NetworkSet::IsItemInNetwork(int id) const { |
25 | return network_by_item_.count(id); | 40 | return network_by_item_.count(id); |
26 | } | 41 | } |
27 | 42 | ||
28 | const std::set<std::pair<int, int>>& NetworkSet::GetNetworkGraph(int id) const { | 43 | const std::set<NetworkNode>& NetworkSet::GetNetworkGraph(int id) const { |
29 | return network_by_item_.at(id); | 44 | return network_by_item_.at(id); |
30 | } | 45 | } |
46 | |||
47 | bool NetworkNode::operator<(const NetworkNode& rhs) const { | ||
48 | if (entry != rhs.entry) return entry < rhs.entry; | ||
49 | if (exit != rhs.exit) return exit < rhs.exit; | ||
50 | if (two_way != rhs.two_way) return two_way < rhs.two_way; | ||
51 | return false; | ||
52 | } | ||
diff --git a/src/network_set.h b/src/network_set.h index e6f0c07..0f72052 100644 --- a/src/network_set.h +++ b/src/network_set.h | |||
@@ -7,19 +7,29 @@ | |||
7 | #include <utility> | 7 | #include <utility> |
8 | #include <vector> | 8 | #include <vector> |
9 | 9 | ||
10 | struct NetworkNode { | ||
11 | int entry; | ||
12 | int exit; | ||
13 | bool two_way; | ||
14 | |||
15 | bool operator<(const NetworkNode& rhs) const; | ||
16 | }; | ||
17 | |||
10 | class NetworkSet { | 18 | class NetworkSet { |
11 | public: | 19 | public: |
12 | void Clear(); | 20 | void Clear(); |
13 | 21 | ||
14 | void AddLink(int id1, int id2); | 22 | void AddLink(int id1, int id2, bool two_way); |
23 | |||
24 | void AddLinkToNetwork(int network_id, int id1, int id2, bool two_way); | ||
15 | 25 | ||
16 | bool IsItemInNetwork(int id) const; | 26 | bool IsItemInNetwork(int id) const; |
17 | 27 | ||
18 | const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; | 28 | const std::set<NetworkNode>& GetNetworkGraph(int id) const; |
19 | 29 | ||
20 | private: | 30 | private: |
21 | 31 | ||
22 | std::map<int, std::set<std::pair<int, int>>> network_by_item_; | 32 | std::map<int, std::set<NetworkNode>> network_by_item_; |
23 | }; | 33 | }; |
24 | 34 | ||
25 | #endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ | 35 | #endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ |
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index e3b844d..f896693 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp | |||
@@ -16,6 +16,28 @@ constexpr int OWL_ACTUAL_SIZE = 32; | |||
16 | 16 | ||
17 | enum class ItemDrawType { kNone, kBox, kOwl }; | 17 | enum class ItemDrawType { kNone, kBox, kOwl }; |
18 | 18 | ||
19 | namespace { | ||
20 | |||
21 | std::optional<int> GetRealSubwayDoor(const SubwayItem subway_item) { | ||
22 | if (AP_IsSunwarpShuffle() && subway_item.sunwarp && | ||
23 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | ||
24 | int sunwarp_index = subway_item.sunwarp->dots - 1; | ||
25 | if (subway_item.sunwarp->type == SubwaySunwarpType::kExit) { | ||
26 | sunwarp_index += 6; | ||
27 | } | ||
28 | |||
29 | for (const auto &[start_index, mapping] : AP_GetSunwarpMapping()) { | ||
30 | if (start_index == sunwarp_index || mapping.exit_index == sunwarp_index) { | ||
31 | return GD_GetSunwarpDoors().at(mapping.dots - 1); | ||
32 | } | ||
33 | } | ||
34 | } | ||
35 | |||
36 | return subway_item.door; | ||
37 | } | ||
38 | |||
39 | } // namespace | ||
40 | |||
19 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | 41 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { |
20 | SetBackgroundStyle(wxBG_STYLE_PAINT); | 42 | SetBackgroundStyle(wxBG_STYLE_PAINT); |
21 | 43 | ||
@@ -69,10 +91,12 @@ void SubwayMap::OnConnect() { | |||
69 | networks_.Clear(); | 91 | networks_.Clear(); |
70 | 92 | ||
71 | std::map<std::string, std::vector<int>> tagged; | 93 | std::map<std::string, std::vector<int>> tagged; |
94 | std::map<std::string, std::vector<int>> entrances; | ||
95 | std::map<std::string, std::vector<int>> exits; | ||
72 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | 96 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { |
73 | if (AP_HasEarlyColorHallways() && | 97 | if (AP_HasEarlyColorHallways() && |
74 | subway_item.special == "starting_room_paintings") { | 98 | subway_item.special == "starting_room_paintings") { |
75 | tagged["early_ch"].push_back(subway_item.id); | 99 | entrances["early_ch"].push_back(subway_item.id); |
76 | } | 100 | } |
77 | 101 | ||
78 | if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { | 102 | if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { |
@@ -82,21 +106,40 @@ void SubwayMap::OnConnect() { | |||
82 | for (const std::string &tag : subway_item.tags) { | 106 | for (const std::string &tag : subway_item.tags) { |
83 | tagged[tag].push_back(subway_item.id); | 107 | tagged[tag].push_back(subway_item.id); |
84 | } | 108 | } |
109 | for (const std::string &tag : subway_item.entrances) { | ||
110 | entrances[tag].push_back(subway_item.id); | ||
111 | } | ||
112 | for (const std::string &tag : subway_item.exits) { | ||
113 | exits[tag].push_back(subway_item.id); | ||
114 | } | ||
85 | 115 | ||
86 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && | 116 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp) { |
87 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | 117 | std::string tag = fmt::format("sunwarp{}", subway_item.sunwarp->dots); |
88 | std::string tag = fmt::format("subway{}", subway_item.sunwarp->dots); | 118 | switch (subway_item.sunwarp->type) { |
89 | tagged[tag].push_back(subway_item.id); | 119 | case SubwaySunwarpType::kEnter: |
120 | entrances[tag].push_back(subway_item.id); | ||
121 | break; | ||
122 | case SubwaySunwarpType::kExit: | ||
123 | exits[tag].push_back(subway_item.id); | ||
124 | break; | ||
125 | default: | ||
126 | break; | ||
127 | } | ||
90 | } | 128 | } |
91 | 129 | ||
92 | if (!AP_IsPilgrimageEnabled() && | 130 | if (!AP_IsPilgrimageEnabled()) { |
93 | (subway_item.special == "sun_painting" || | 131 | if (subway_item.special == "sun_painting") { |
94 | subway_item.special == "sun_painting_exit")) { | 132 | entrances["sun_painting"].push_back(subway_item.id); |
95 | tagged["sun_painting"].push_back(subway_item.id); | 133 | } else if (subway_item.special == "sun_painting_exit") { |
134 | exits["sun_painting"].push_back(subway_item.id); | ||
135 | } | ||
96 | } | 136 | } |
97 | } | 137 | } |
98 | 138 | ||
99 | if (AP_IsSunwarpShuffle()) { | 139 | if (AP_IsSunwarpShuffle()) { |
140 | SubwaySunwarp final_sunwarp{.dots = 6, .type = SubwaySunwarpType::kFinal}; | ||
141 | int final_sunwarp_item = GD_GetSubwayItemForSunwarp(final_sunwarp); | ||
142 | |||
100 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { | 143 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { |
101 | std::string tag = fmt::format("sunwarp{}", mapping.dots); | 144 | std::string tag = fmt::format("sunwarp{}", mapping.dots); |
102 | 145 | ||
@@ -118,8 +161,14 @@ void SubwayMap::OnConnect() { | |||
118 | toWarp.type = SubwaySunwarpType::kExit; | 161 | toWarp.type = SubwaySunwarpType::kExit; |
119 | } | 162 | } |
120 | 163 | ||
121 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); | 164 | entrances[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); |
122 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); | 165 | exits[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); |
166 | |||
167 | networks_.AddLinkToNetwork( | ||
168 | final_sunwarp_item, GD_GetSubwayItemForSunwarp(fromWarp), | ||
169 | mapping.dots == 6 ? final_sunwarp_item | ||
170 | : GD_GetSubwayItemForSunwarp(toWarp), | ||
171 | false); | ||
123 | } | 172 | } |
124 | } | 173 | } |
125 | 174 | ||
@@ -129,7 +178,17 @@ void SubwayMap::OnConnect() { | |||
129 | tag_it1++) { | 178 | tag_it1++) { |
130 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); | 179 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); |
131 | tag_it2++) { | 180 | tag_it2++) { |
132 | networks_.AddLink(*tag_it1, *tag_it2); | 181 | // two links because tags are bi-directional |
182 | networks_.AddLink(*tag_it1, *tag_it2, true); | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | |||
187 | for (const auto &[tag, items] : entrances) { | ||
188 | if (!exits.contains(tag)) continue; | ||
189 | for (auto exit : exits[tag]) { | ||
190 | for (auto entrance : items) { | ||
191 | networks_.AddLink(entrance, exit, false); | ||
133 | } | 192 | } |
134 | } | 193 | } |
135 | } | 194 | } |
@@ -149,7 +208,7 @@ void SubwayMap::UpdateIndicators() { | |||
149 | AP_GetPaintingMapping().at(painting_id)); | 208 | AP_GetPaintingMapping().at(painting_id)); |
150 | 209 | ||
151 | if (from_id && to_id) { | 210 | if (from_id && to_id) { |
152 | networks_.AddLink(*from_id, *to_id); | 211 | networks_.AddLink(*from_id, *to_id, false); |
153 | } | 212 | } |
154 | } | 213 | } |
155 | } | 214 | } |
@@ -162,7 +221,7 @@ void SubwayMap::UpdateIndicators() { | |||
162 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | 221 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, |
163 | SubwaySunwarp to_sunwarp) { | 222 | SubwaySunwarp to_sunwarp) { |
164 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), | 223 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), |
165 | GD_GetSubwayItemForSunwarp(to_sunwarp)); | 224 | GD_GetSubwayItemForSunwarp(to_sunwarp), false); |
166 | } | 225 | } |
167 | 226 | ||
168 | void SubwayMap::Zoom(bool in) { | 227 | void SubwayMap::Zoom(bool in) { |
@@ -267,9 +326,11 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
267 | // Note that these requirements are duplicated on OnMouseClick so that it | 326 | // Note that these requirements are duplicated on OnMouseClick so that it |
268 | // knows when an item has a hover effect. | 327 | // knows when an item has a hover effect. |
269 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | 328 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); |
270 | if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { | 329 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); |
330 | |||
331 | if (subway_door && !GetDoorRequirements(*subway_door).empty()) { | ||
271 | const std::map<std::string, bool> &report = | 332 | const std::map<std::string, bool> &report = |
272 | GetDoorRequirements(*subway_item.door); | 333 | GetDoorRequirements(*subway_door); |
273 | 334 | ||
274 | int acc_height = 10; | 335 | int acc_height = 10; |
275 | int col_width = 0; | 336 | int col_width = 0; |
@@ -326,10 +387,9 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
326 | if (networks_.IsItemInNetwork(*hovered_item_)) { | 387 | if (networks_.IsItemInNetwork(*hovered_item_)) { |
327 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | 388 | dc.SetBrush(*wxTRANSPARENT_BRUSH); |
328 | 389 | ||
329 | for (const auto &[item_id1, item_id2] : | 390 | for (const auto node : networks_.GetNetworkGraph(*hovered_item_)) { |
330 | networks_.GetNetworkGraph(*hovered_item_)) { | 391 | const SubwayItem &item1 = GD_GetSubwayItem(node.entry); |
331 | const SubwayItem &item1 = GD_GetSubwayItem(item_id1); | 392 | const SubwayItem &item2 = GD_GetSubwayItem(node.exit); |
332 | const SubwayItem &item2 = GD_GetSubwayItem(item_id2); | ||
333 | 393 | ||
334 | wxPoint item1_pos = MapPosToRenderPos( | 394 | wxPoint item1_pos = MapPosToRenderPos( |
335 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); | 395 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); |
@@ -349,6 +409,12 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
349 | dc.DrawLine(item1_pos, item2_pos); | 409 | dc.DrawLine(item1_pos, item2_pos); |
350 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | 410 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); |
351 | dc.DrawLine(item1_pos, item2_pos); | 411 | dc.DrawLine(item1_pos, item2_pos); |
412 | if (!node.two_way) { | ||
413 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2)); | ||
414 | dc.SetBrush(*wxCYAN_BRUSH); | ||
415 | dc.DrawCircle(item2_pos, 4); | ||
416 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
417 | } | ||
352 | } else { | 418 | } else { |
353 | int ellipse_x; | 419 | int ellipse_x; |
354 | int ellipse_y; | 420 | int ellipse_y; |
@@ -391,6 +457,12 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
391 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | 457 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); |
392 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | 458 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, |
393 | halfheight * 2, start, end); | 459 | halfheight * 2, start, end); |
460 | if (!node.two_way) { | ||
461 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2)); | ||
462 | dc.SetBrush(*wxCYAN_BRUSH); | ||
463 | dc.DrawCircle(item2_pos, 4); | ||
464 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
465 | } | ||
394 | } | 466 | } |
395 | } | 467 | } |
396 | } | 468 | } |
@@ -450,7 +522,9 @@ void SubwayMap::OnMouseClick(wxMouseEvent &event) { | |||
450 | 522 | ||
451 | if (actual_hover_) { | 523 | if (actual_hover_) { |
452 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); | 524 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); |
453 | if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || | 525 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); |
526 | |||
527 | if ((subway_door && !GetDoorRequirements(*subway_door).empty()) || | ||
454 | networks_.IsItemInNetwork(*hovered_item_)) { | 528 | networks_.IsItemInNetwork(*hovered_item_)) { |
455 | if (actual_hover_ != hovered_item_) { | 529 | if (actual_hover_ != hovered_item_) { |
456 | hovered_item_ = actual_hover_; | 530 | hovered_item_ = actual_hover_; |
@@ -509,7 +583,8 @@ void SubwayMap::OnClickHelp(wxCommandEvent &event) { | |||
509 | "corner.\nClick on a side of the screen to start panning. It will follow " | 583 | "corner.\nClick on a side of the screen to start panning. It will follow " |
510 | "your mouse. Click again to stop.\nHover over a door to see the " | 584 | "your mouse. Click again to stop.\nHover over a door to see the " |
511 | "requirements to open it.\nHover over a warp or active painting to see " | 585 | "requirements to open it.\nHover over a warp or active painting to see " |
512 | "what it is connected to.\nIn painting shuffle, paintings that have not " | 586 | "what it is connected to.\nFor one-way connections, there will be a " |
587 | "circle at the exit.\nIn painting shuffle, paintings that have not " | ||
513 | "yet been checked will not show their connections.\nA green shaded owl " | 588 | "yet been checked will not show their connections.\nA green shaded owl " |
514 | "means that there is a painting entrance there.\nA red shaded owl means " | 589 | "means that there is a painting entrance there.\nA red shaded owl means " |
515 | "that there are only painting exits there.\nClick on a door or " | 590 | "that there are only painting exits there.\nClick on a door or " |
@@ -529,6 +604,7 @@ void SubwayMap::Redraw() { | |||
529 | ItemDrawType draw_type = ItemDrawType::kNone; | 604 | ItemDrawType draw_type = ItemDrawType::kNone; |
530 | const wxBrush *brush_color = wxGREY_BRUSH; | 605 | const wxBrush *brush_color = wxGREY_BRUSH; |
531 | std::optional<wxColour> shade_color; | 606 | std::optional<wxColour> shade_color; |
607 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | ||
532 | 608 | ||
533 | if (AP_HasEarlyColorHallways() && | 609 | if (AP_HasEarlyColorHallways() && |
534 | subway_item.special == "starting_room_paintings") { | 610 | subway_item.special == "starting_room_paintings") { |
@@ -544,6 +620,16 @@ void SubwayMap::Redraw() { | |||
544 | brush_color = wxRED_BRUSH; | 620 | brush_color = wxRED_BRUSH; |
545 | } | 621 | } |
546 | } | 622 | } |
623 | } else if (subway_item.sunwarp && | ||
624 | subway_item.sunwarp->type == SubwaySunwarpType::kFinal && | ||
625 | AP_IsPilgrimageEnabled()) { | ||
626 | draw_type = ItemDrawType::kBox; | ||
627 | |||
628 | if (IsPilgrimageDoable()) { | ||
629 | brush_color = wxGREEN_BRUSH; | ||
630 | } else { | ||
631 | brush_color = wxRED_BRUSH; | ||
632 | } | ||
547 | } else if (!subway_item.paintings.empty()) { | 633 | } else if (!subway_item.paintings.empty()) { |
548 | if (AP_IsPaintingShuffle()) { | 634 | if (AP_IsPaintingShuffle()) { |
549 | bool has_checked_painting = false; | 635 | bool has_checked_painting = false; |
@@ -577,13 +663,13 @@ void SubwayMap::Redraw() { | |||
577 | } | 663 | } |
578 | } | 664 | } |
579 | } | 665 | } |
580 | } else if (!subway_item.tags.empty()) { | 666 | } else if (subway_item.HasWarps()) { |
581 | draw_type = ItemDrawType::kOwl; | 667 | draw_type = ItemDrawType::kOwl; |
582 | } | 668 | } |
583 | } else if (subway_item.door) { | 669 | } else if (subway_door) { |
584 | draw_type = ItemDrawType::kBox; | 670 | draw_type = ItemDrawType::kBox; |
585 | 671 | ||
586 | if (IsDoorOpen(*subway_item.door)) { | 672 | if (IsDoorOpen(*subway_door)) { |
587 | brush_color = wxGREEN_BRUSH; | 673 | brush_color = wxGREEN_BRUSH; |
588 | } else { | 674 | } else { |
589 | brush_color = wxRED_BRUSH; | 675 | brush_color = wxRED_BRUSH; |
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 80fd137..b9282f5 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp | |||
@@ -2,9 +2,12 @@ | |||
2 | 2 | ||
3 | #include <wx/aboutdlg.h> | 3 | #include <wx/aboutdlg.h> |
4 | #include <wx/choicebk.h> | 4 | #include <wx/choicebk.h> |
5 | #include <wx/filedlg.h> | ||
5 | #include <wx/notebook.h> | 6 | #include <wx/notebook.h> |
7 | #include <wx/stdpaths.h> | ||
6 | #include <wx/webrequest.h> | 8 | #include <wx/webrequest.h> |
7 | 9 | ||
10 | #include <fmt/core.h> | ||
8 | #include <nlohmann/json.hpp> | 11 | #include <nlohmann/json.hpp> |
9 | #include <sstream> | 12 | #include <sstream> |
10 | 13 | ||
@@ -23,6 +26,7 @@ enum TrackerFrameIds { | |||
23 | ID_SETTINGS = 3, | 26 | ID_SETTINGS = 3, |
24 | ID_ZOOM_IN = 4, | 27 | ID_ZOOM_IN = 4, |
25 | ID_ZOOM_OUT = 5, | 28 | ID_ZOOM_OUT = 5, |
29 | ID_OPEN_SAVE_FILE = 6, | ||
26 | }; | 30 | }; |
27 | 31 | ||
28 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); | 32 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); |
@@ -38,6 +42,7 @@ TrackerFrame::TrackerFrame() | |||
38 | 42 | ||
39 | wxMenu *menuFile = new wxMenu(); | 43 | wxMenu *menuFile = new wxMenu(); |
40 | menuFile->Append(ID_CONNECT, "&Connect"); | 44 | menuFile->Append(ID_CONNECT, "&Connect"); |
45 | menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O"); | ||
41 | menuFile->Append(ID_SETTINGS, "&Settings"); | 46 | menuFile->Append(ID_SETTINGS, "&Settings"); |
42 | menuFile->Append(wxID_EXIT); | 47 | menuFile->Append(wxID_EXIT); |
43 | 48 | ||
@@ -71,6 +76,7 @@ TrackerFrame::TrackerFrame() | |||
71 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); | 76 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); |
72 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); | 77 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); |
73 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); | 78 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); |
79 | Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE); | ||
74 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); | 80 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); |
75 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | 81 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); |
76 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | 82 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); |
@@ -131,6 +137,7 @@ void TrackerFrame::OnAbout(wxCommandEvent &event) { | |||
131 | about_info.SetName("Lingo Archipelago Tracker"); | 137 | about_info.SetName("Lingo Archipelago Tracker"); |
132 | about_info.SetVersion(kTrackerVersion.ToString()); | 138 | about_info.SetVersion(kTrackerVersion.ToString()); |
133 | about_info.AddDeveloper("hatkirby"); | 139 | about_info.AddDeveloper("hatkirby"); |
140 | about_info.AddDeveloper("art0007i"); | ||
134 | about_info.AddArtist("Brenton Wildes"); | 141 | about_info.AddArtist("Brenton Wildes"); |
135 | about_info.AddArtist("kinrah"); | 142 | about_info.AddArtist("kinrah"); |
136 | 143 | ||
@@ -204,10 +211,36 @@ void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { | |||
204 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | 211 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); |
205 | } | 212 | } |
206 | 213 | ||
214 | void TrackerFrame::OnOpenFile(wxCommandEvent& event) { | ||
215 | wxFileDialog open_file_dialog( | ||
216 | this, "Open Lingo Save File", | ||
217 | fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable", | ||
218 | wxStandardPaths::Get().GetUserConfigDir().ToStdString()), | ||
219 | AP_GetSaveName(), "Lingo save file (*.save)|*.save", | ||
220 | wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||
221 | if (open_file_dialog.ShowModal() == wxID_CANCEL) { | ||
222 | return; | ||
223 | } | ||
224 | |||
225 | std::string savedata_path = open_file_dialog.GetPath().ToStdString(); | ||
226 | |||
227 | if (panels_panel_ == nullptr) { | ||
228 | panels_panel_ = new TrackerPanel(notebook_); | ||
229 | notebook_->AddPage(panels_panel_, "Panels"); | ||
230 | } | ||
231 | |||
232 | notebook_->SetSelection(notebook_->FindPage(panels_panel_)); | ||
233 | panels_panel_->SetSavedataPath(savedata_path); | ||
234 | } | ||
235 | |||
207 | void TrackerFrame::OnStateReset(wxCommandEvent& event) { | 236 | void TrackerFrame::OnStateReset(wxCommandEvent& event) { |
208 | tracker_panel_->UpdateIndicators(); | 237 | tracker_panel_->UpdateIndicators(); |
209 | achievements_pane_->UpdateIndicators(); | 238 | achievements_pane_->UpdateIndicators(); |
210 | subway_map_->OnConnect(); | 239 | subway_map_->OnConnect(); |
240 | if (panels_panel_ != nullptr) { | ||
241 | notebook_->DeletePage(notebook_->FindPage(panels_panel_)); | ||
242 | panels_panel_ = nullptr; | ||
243 | } | ||
211 | Refresh(); | 244 | Refresh(); |
212 | } | 245 | } |
213 | 246 | ||
@@ -215,6 +248,9 @@ void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | |||
215 | tracker_panel_->UpdateIndicators(); | 248 | tracker_panel_->UpdateIndicators(); |
216 | achievements_pane_->UpdateIndicators(); | 249 | achievements_pane_->UpdateIndicators(); |
217 | subway_map_->UpdateIndicators(); | 250 | subway_map_->UpdateIndicators(); |
251 | if (panels_panel_ != nullptr) { | ||
252 | panels_panel_->UpdateIndicators(); | ||
253 | } | ||
218 | Refresh(); | 254 | Refresh(); |
219 | } | 255 | } |
220 | 256 | ||
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index f7cb3f2..19bd0b3 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h | |||
@@ -35,6 +35,7 @@ class TrackerFrame : public wxFrame { | |||
35 | void OnZoomIn(wxCommandEvent &event); | 35 | void OnZoomIn(wxCommandEvent &event); |
36 | void OnZoomOut(wxCommandEvent &event); | 36 | void OnZoomOut(wxCommandEvent &event); |
37 | void OnChangePage(wxBookCtrlEvent &event); | 37 | void OnChangePage(wxBookCtrlEvent &event); |
38 | void OnOpenFile(wxCommandEvent &event); | ||
38 | 39 | ||
39 | void OnStateReset(wxCommandEvent &event); | 40 | void OnStateReset(wxCommandEvent &event); |
40 | void OnStateChanged(wxCommandEvent &event); | 41 | void OnStateChanged(wxCommandEvent &event); |
@@ -46,6 +47,7 @@ class TrackerFrame : public wxFrame { | |||
46 | TrackerPanel *tracker_panel_; | 47 | TrackerPanel *tracker_panel_; |
47 | AchievementsPane *achievements_pane_; | 48 | AchievementsPane *achievements_pane_; |
48 | SubwayMap *subway_map_; | 49 | SubwayMap *subway_map_; |
50 | TrackerPanel *panels_panel_ = nullptr; | ||
49 | 51 | ||
50 | wxMenuItem *zoom_in_menu_item_; | 52 | wxMenuItem *zoom_in_menu_item_; |
51 | wxMenuItem *zoom_out_menu_item_; | 53 | wxMenuItem *zoom_out_menu_item_; |
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index d60c1b6..27e825a 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp | |||
@@ -1,11 +1,15 @@ | |||
1 | #include "tracker_panel.h" | 1 | #include "tracker_panel.h" |
2 | 2 | ||
3 | #include <fmt/core.h> | ||
3 | #include <wx/dcbuffer.h> | 4 | #include <wx/dcbuffer.h> |
4 | 5 | ||
6 | #include <algorithm> | ||
7 | |||
5 | #include "ap_state.h" | 8 | #include "ap_state.h" |
6 | #include "area_popup.h" | 9 | #include "area_popup.h" |
7 | #include "game_data.h" | 10 | #include "game_data.h" |
8 | #include "global.h" | 11 | #include "global.h" |
12 | #include "godot_variant.h" | ||
9 | #include "tracker_config.h" | 13 | #include "tracker_config.h" |
10 | #include "tracker_state.h" | 14 | #include "tracker_state.h" |
11 | 15 | ||
@@ -53,6 +57,35 @@ void TrackerPanel::UpdateIndicators() { | |||
53 | Redraw(); | 57 | Redraw(); |
54 | } | 58 | } |
55 | 59 | ||
60 | void TrackerPanel::SetSavedataPath(std::string savedata_path) { | ||
61 | if (!panels_mode_) { | ||
62 | wxButton *refresh_button = new wxButton(this, wxID_ANY, "Refresh", {15, 15}); | ||
63 | refresh_button->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this); | ||
64 | } | ||
65 | |||
66 | savedata_path_ = savedata_path; | ||
67 | panels_mode_ = true; | ||
68 | |||
69 | RefreshSavedata(); | ||
70 | } | ||
71 | |||
72 | void TrackerPanel::RefreshSavedata() { | ||
73 | solved_panels_.clear(); | ||
74 | |||
75 | GodotVariant godot_variant = ParseGodotFile(*savedata_path_); | ||
76 | for (const GodotVariant &panel_node : godot_variant.AsArray()) { | ||
77 | const std::vector<GodotVariant> &fields = panel_node.AsArray(); | ||
78 | if (fields[1].AsBool()) { | ||
79 | const std::vector<std::string> &nodepath = fields[0].AsNodePath(); | ||
80 | std::string key = fmt::format("{}/{}", nodepath[3], nodepath[4]); | ||
81 | solved_panels_.insert(key); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | UpdateIndicators(); | ||
86 | Refresh(); | ||
87 | } | ||
88 | |||
56 | void TrackerPanel::OnPaint(wxPaintEvent &event) { | 89 | void TrackerPanel::OnPaint(wxPaintEvent &event) { |
57 | if (GetSize() != rendered_.GetSize()) { | 90 | if (GetSize() != rendered_.GetSize()) { |
58 | Redraw(); | 91 | Redraw(); |
@@ -92,6 +125,10 @@ void TrackerPanel::OnMouseMove(wxMouseEvent &event) { | |||
92 | event.Skip(); | 125 | event.Skip(); |
93 | } | 126 | } |
94 | 127 | ||
128 | void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) { | ||
129 | RefreshSavedata(); | ||
130 | } | ||
131 | |||
95 | void TrackerPanel::Redraw() { | 132 | void TrackerPanel::Redraw() { |
96 | wxSize panel_size = GetSize(); | 133 | wxSize panel_size = GetSize(); |
97 | wxSize image_size = map_image_.GetSize(); | 134 | wxSize image_size = map_image_.GetSize(); |
@@ -142,21 +179,35 @@ void TrackerPanel::Redraw() { | |||
142 | 179 | ||
143 | for (AreaIndicator &area : areas_) { | 180 | for (AreaIndicator &area : areas_) { |
144 | const MapArea &map_area = GD_GetMapArea(area.area_id); | 181 | const MapArea &map_area = GD_GetMapArea(area.area_id); |
145 | if (!AP_IsLocationVisible(map_area.classification) && | 182 | if (panels_mode_) { |
183 | area.active = map_area.has_single_panel; | ||
184 | } else if (!AP_IsLocationVisible(map_area.classification) && | ||
146 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && | 185 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && |
147 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { | 186 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { |
148 | area.active = false; | 187 | area.active = false; |
149 | continue; | ||
150 | } else { | 188 | } else { |
151 | area.active = true; | 189 | area.active = true; |
152 | } | 190 | } |
153 | 191 | ||
192 | if (!area.active) { | ||
193 | continue; | ||
194 | } | ||
195 | |||
154 | bool has_reachable_unchecked = false; | 196 | bool has_reachable_unchecked = false; |
155 | bool has_unreachable_unchecked = false; | 197 | bool has_unreachable_unchecked = false; |
156 | for (const Location §ion : map_area.locations) { | 198 | for (const Location §ion : map_area.locations) { |
157 | bool has_unchecked = false; | 199 | bool has_unchecked = false; |
158 | if (IsLocationWinCondition(section)) { | 200 | if (IsLocationWinCondition(section)) { |
159 | has_unchecked = !AP_HasReachedGoal(); | 201 | has_unchecked = !AP_HasReachedGoal(); |
202 | } else if (panels_mode_) { | ||
203 | if (section.single_panel) { | ||
204 | const Panel &panel = GD_GetPanel(*section.single_panel); | ||
205 | if (panel.non_counting) { | ||
206 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); | ||
207 | } else { | ||
208 | has_unchecked = !solved_panels_.contains(panel.nodepath); | ||
209 | } | ||
210 | } | ||
160 | } else if (AP_IsLocationVisible(section.classification)) { | 211 | } else if (AP_IsLocationVisible(section.classification)) { |
161 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); | 212 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); |
162 | } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { | 213 | } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { |
@@ -172,12 +223,11 @@ void TrackerPanel::Redraw() { | |||
172 | } | 223 | } |
173 | } | 224 | } |
174 | 225 | ||
175 | if (AP_IsPaintingShuffle()) { | 226 | if (AP_IsPaintingShuffle() && !panels_mode_) { |
176 | for (int painting_id : map_area.paintings) { | 227 | for (int painting_id : map_area.paintings) { |
177 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); | 228 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); |
178 | if (!AP_IsPaintingChecked(painting.internal_id)) { | 229 | bool reachable = IsPaintingReachable(painting_id); |
179 | bool reachable = IsPaintingReachable(painting_id); | 230 | if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) { |
180 | |||
181 | if (reachable) { | 231 | if (reachable) { |
182 | has_reachable_unchecked = true; | 232 | has_reachable_unchecked = true; |
183 | } else { | 233 | } else { |
diff --git a/src/tracker_panel.h b/src/tracker_panel.h index 06ec7a0..e1f515d 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h | |||
@@ -7,6 +7,10 @@ | |||
7 | #include <wx/wx.h> | 7 | #include <wx/wx.h> |
8 | #endif | 8 | #endif |
9 | 9 | ||
10 | #include <optional> | ||
11 | #include <set> | ||
12 | #include <string> | ||
13 | |||
10 | class AreaPopup; | 14 | class AreaPopup; |
11 | 15 | ||
12 | class TrackerPanel : public wxPanel { | 16 | class TrackerPanel : public wxPanel { |
@@ -15,6 +19,14 @@ class TrackerPanel : public wxPanel { | |||
15 | 19 | ||
16 | void UpdateIndicators(); | 20 | void UpdateIndicators(); |
17 | 21 | ||
22 | void SetSavedataPath(std::string savedata_path); | ||
23 | |||
24 | bool IsPanelsMode() const { return panels_mode_; } | ||
25 | |||
26 | const std::set<std::string> &GetSolvedPanels() const { | ||
27 | return solved_panels_; | ||
28 | } | ||
29 | |||
18 | private: | 30 | private: |
19 | struct AreaIndicator { | 31 | struct AreaIndicator { |
20 | int area_id = -1; | 32 | int area_id = -1; |
@@ -28,9 +40,12 @@ class TrackerPanel : public wxPanel { | |||
28 | 40 | ||
29 | void OnPaint(wxPaintEvent &event); | 41 | void OnPaint(wxPaintEvent &event); |
30 | void OnMouseMove(wxMouseEvent &event); | 42 | void OnMouseMove(wxMouseEvent &event); |
43 | void OnRefreshSavedata(wxCommandEvent &event); | ||
31 | 44 | ||
32 | void Redraw(); | 45 | void Redraw(); |
33 | 46 | ||
47 | void RefreshSavedata(); | ||
48 | |||
34 | wxImage map_image_; | 49 | wxImage map_image_; |
35 | wxImage player_image_; | 50 | wxImage player_image_; |
36 | wxBitmap rendered_; | 51 | wxBitmap rendered_; |
@@ -42,6 +57,10 @@ class TrackerPanel : public wxPanel { | |||
42 | double scale_y_ = 0; | 57 | double scale_y_ = 0; |
43 | 58 | ||
44 | std::vector<AreaIndicator> areas_; | 59 | std::vector<AreaIndicator> areas_; |
60 | |||
61 | bool panels_mode_ = false; | ||
62 | std::optional<std::string> savedata_path_; | ||
63 | std::set<std::string> solved_panels_; | ||
45 | }; | 64 | }; |
46 | 65 | ||
47 | #endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ | 66 | #endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ |
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 1a2d116..8d5d904 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp | |||
@@ -1,5 +1,8 @@ | |||
1 | #include "tracker_state.h" | 1 | #include "tracker_state.h" |
2 | 2 | ||
3 | #include <fmt/core.h> | ||
4 | #include <hkutil/string.h> | ||
5 | |||
3 | #include <list> | 6 | #include <list> |
4 | #include <map> | 7 | #include <map> |
5 | #include <mutex> | 8 | #include <mutex> |
@@ -9,6 +12,7 @@ | |||
9 | 12 | ||
10 | #include "ap_state.h" | 13 | #include "ap_state.h" |
11 | #include "game_data.h" | 14 | #include "game_data.h" |
15 | #include "logger.h" | ||
12 | 16 | ||
13 | namespace { | 17 | namespace { |
14 | 18 | ||
@@ -148,6 +152,7 @@ struct TrackerState { | |||
148 | std::mutex reachability_mutex; | 152 | std::mutex reachability_mutex; |
149 | RequirementCalculator requirements; | 153 | RequirementCalculator requirements; |
150 | std::map<int, std::map<std::string, bool>> door_reports; | 154 | std::map<int, std::map<std::string, bool>> door_reports; |
155 | bool pilgrimage_doable = false; | ||
151 | }; | 156 | }; |
152 | 157 | ||
153 | enum Decision { kYes, kNo, kMaybe }; | 158 | enum Decision { kYes, kNo, kMaybe }; |
@@ -176,7 +181,8 @@ class StateCalculator { | |||
176 | std::list<int> panel_boundary; | 181 | std::list<int> panel_boundary; |
177 | std::list<int> painting_boundary; | 182 | std::list<int> painting_boundary; |
178 | std::list<Exit> flood_boundary; | 183 | std::list<Exit> flood_boundary; |
179 | flood_boundary.push_back({.destination_room = options_.start}); | 184 | flood_boundary.push_back( |
185 | {.source_room = -1, .destination_room = options_.start}); | ||
180 | 186 | ||
181 | bool reachable_changed = true; | 187 | bool reachable_changed = true; |
182 | while (reachable_changed) { | 188 | while (reachable_changed) { |
@@ -217,7 +223,9 @@ class StateCalculator { | |||
217 | PaintingExit target_painting = | 223 | PaintingExit target_painting = |
218 | GD_GetPaintingExit(GD_GetPaintingByName( | 224 | GD_GetPaintingExit(GD_GetPaintingByName( |
219 | AP_GetPaintingMapping().at(cur_painting.internal_id))); | 225 | AP_GetPaintingMapping().at(cur_painting.internal_id))); |
226 | painting_exit.source_room = cur_painting.room; | ||
220 | painting_exit.destination_room = target_painting.room; | 227 | painting_exit.destination_room = target_painting.room; |
228 | painting_exit.type = EntranceType::kPainting; | ||
221 | 229 | ||
222 | new_boundary.push_back(painting_exit); | 230 | new_boundary.push_back(painting_exit); |
223 | } | 231 | } |
@@ -244,6 +252,12 @@ class StateCalculator { | |||
244 | reachable_rooms_.insert(room_exit.destination_room); | 252 | reachable_rooms_.insert(room_exit.destination_room); |
245 | reachable_changed = true; | 253 | reachable_changed = true; |
246 | 254 | ||
255 | #ifndef NDEBUG | ||
256 | std::list<int> room_path = paths_[room_exit.source_room]; | ||
257 | room_path.push_back(room_exit.destination_room); | ||
258 | paths_[room_exit.destination_room] = room_path; | ||
259 | #endif | ||
260 | |||
247 | const Room& room_obj = GD_GetRoom(room_exit.destination_room); | 261 | const Room& room_obj = GD_GetRoom(room_exit.destination_room); |
248 | for (const Exit& out_edge : room_obj.exits) { | 262 | for (const Exit& out_edge : room_obj.exits) { |
249 | if (out_edge.type == EntranceType::kPainting && | 263 | if (out_edge.type == EntranceType::kPainting && |
@@ -270,32 +284,44 @@ class StateCalculator { | |||
270 | if (AP_GetSunwarpMapping().count(index)) { | 284 | if (AP_GetSunwarpMapping().count(index)) { |
271 | const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); | 285 | const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); |
272 | 286 | ||
273 | Exit sunwarp_exit; | 287 | new_boundary.push_back( |
274 | sunwarp_exit.destination_room = | 288 | {.source_room = room_exit.destination_room, |
275 | GD_GetRoomForSunwarp(sm.exit_index); | 289 | .destination_room = GD_GetRoomForSunwarp(sm.exit_index), |
276 | sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1); | 290 | .door = GD_GetSunwarpDoors().at(sm.dots - 1), |
277 | 291 | .type = EntranceType::kSunwarp}); | |
278 | new_boundary.push_back(sunwarp_exit); | ||
279 | } | 292 | } |
280 | } | 293 | } |
281 | } | 294 | } |
282 | 295 | ||
283 | if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { | 296 | if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { |
284 | new_boundary.push_back( | 297 | new_boundary.push_back( |
285 | {.destination_room = GD_GetRoomByName("Color Hallways"), | 298 | {.source_room = room_exit.destination_room, |
299 | .destination_room = GD_GetRoomByName("Color Hallways"), | ||
286 | .type = EntranceType::kPainting}); | 300 | .type = EntranceType::kPainting}); |
287 | } | 301 | } |
288 | 302 | ||
289 | if (AP_IsPilgrimageEnabled()) { | 303 | if (AP_IsPilgrimageEnabled()) { |
290 | if (room_obj.name == "Hub Room") { | 304 | int pilgrimage_start_id = GD_GetRoomByName("Hub Room"); |
305 | if (AP_IsSunwarpShuffle()) { | ||
306 | for (const auto& [start_index, mapping] : | ||
307 | AP_GetSunwarpMapping()) { | ||
308 | if (mapping.dots == 1) { | ||
309 | pilgrimage_start_id = GD_GetRoomForSunwarp(start_index); | ||
310 | } | ||
311 | } | ||
312 | } | ||
313 | |||
314 | if (room_exit.destination_room == pilgrimage_start_id) { | ||
291 | new_boundary.push_back( | 315 | new_boundary.push_back( |
292 | {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), | 316 | {.source_room = room_exit.destination_room, |
317 | .destination_room = GD_GetRoomByName("Pilgrim Antechamber"), | ||
293 | .type = EntranceType::kPilgrimage}); | 318 | .type = EntranceType::kPilgrimage}); |
294 | } | 319 | } |
295 | } else { | 320 | } else { |
296 | if (room_obj.name == "Starting Room") { | 321 | if (room_obj.name == "Starting Room") { |
297 | new_boundary.push_back( | 322 | new_boundary.push_back( |
298 | {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), | 323 | {.source_room = room_exit.destination_room, |
324 | .destination_room = GD_GetRoomByName("Pilgrim Antechamber"), | ||
299 | .door = | 325 | .door = |
300 | GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), | 326 | GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), |
301 | .type = EntranceType::kPainting}); | 327 | .type = EntranceType::kPainting}); |
@@ -336,6 +362,21 @@ class StateCalculator { | |||
336 | return door_report_; | 362 | return door_report_; |
337 | } | 363 | } |
338 | 364 | ||
365 | bool IsPilgrimageDoable() const { return pilgrimage_doable_; } | ||
366 | |||
367 | std::string GetPathToRoom(int room_id) const { | ||
368 | if (!paths_.count(room_id)) { | ||
369 | return ""; | ||
370 | } | ||
371 | |||
372 | const std::list<int>& path = paths_.at(room_id); | ||
373 | std::vector<std::string> room_names; | ||
374 | for (int room_id : path) { | ||
375 | room_names.push_back(GD_GetRoom(room_id).name); | ||
376 | } | ||
377 | return hatkirby::implode(room_names, " -> "); | ||
378 | } | ||
379 | |||
339 | private: | 380 | private: |
340 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { | 381 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { |
341 | bool has_item = AP_HasItem(door_obj.ap_item_id); | 382 | bool has_item = AP_HasItem(door_obj.ap_item_id); |
@@ -534,6 +575,8 @@ class StateCalculator { | |||
534 | } | 575 | } |
535 | } | 576 | } |
536 | 577 | ||
578 | pilgrimage_doable_ = true; | ||
579 | |||
537 | return kYes; | 580 | return kYes; |
538 | } | 581 | } |
539 | 582 | ||
@@ -574,6 +617,9 @@ class StateCalculator { | |||
574 | std::set<int> solveable_panels_; | 617 | std::set<int> solveable_panels_; |
575 | std::set<int> reachable_paintings_; | 618 | std::set<int> reachable_paintings_; |
576 | std::map<int, std::map<std::string, bool>> door_report_; | 619 | std::map<int, std::map<std::string, bool>> door_report_; |
620 | bool pilgrimage_doable_ = false; | ||
621 | |||
622 | std::map<int, std::list<int>> paths_; | ||
577 | }; | 623 | }; |
578 | 624 | ||
579 | } // namespace | 625 | } // namespace |
@@ -623,6 +669,7 @@ void RecalculateReachability() { | |||
623 | std::swap(GetState().reachable_doors, new_reachable_doors); | 669 | std::swap(GetState().reachable_doors, new_reachable_doors); |
624 | std::swap(GetState().reachable_paintings, reachable_paintings); | 670 | std::swap(GetState().reachable_paintings, reachable_paintings); |
625 | std::swap(GetState().door_reports, door_reports); | 671 | std::swap(GetState().door_reports, door_reports); |
672 | GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable(); | ||
626 | } | 673 | } |
627 | 674 | ||
628 | bool IsLocationReachable(int location_id) { | 675 | bool IsLocationReachable(int location_id) { |
@@ -652,3 +699,9 @@ const std::map<std::string, bool>& GetDoorRequirements(int door_id) { | |||
652 | 699 | ||
653 | return GetState().door_reports[door_id]; | 700 | return GetState().door_reports[door_id]; |
654 | } | 701 | } |
702 | |||
703 | bool IsPilgrimageDoable() { | ||
704 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
705 | |||
706 | return GetState().pilgrimage_doable; | ||
707 | } | ||
diff --git a/src/tracker_state.h b/src/tracker_state.h index c7857a0..a8f155d 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h | |||
@@ -16,4 +16,6 @@ bool IsPaintingReachable(int painting_id); | |||
16 | 16 | ||
17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); | 17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); |
18 | 18 | ||
19 | bool IsPilgrimageDoable(); | ||
20 | |||
19 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | 21 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ |
diff --git a/src/version.h b/src/version.h index b688e27..ec52f44 100644 --- a/src/version.h +++ b/src/version.h | |||
@@ -36,6 +36,6 @@ struct Version { | |||
36 | } | 36 | } |
37 | }; | 37 | }; |
38 | 38 | ||
39 | constexpr const Version kTrackerVersion = Version(0, 10, 5); | 39 | constexpr const Version kTrackerVersion = Version(0, 11, 2); |
40 | 40 | ||
41 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file | 41 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file |