diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2023-05-05 15:46:58 -0400 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2023-05-05 15:46:58 -0400 |
commit | 149e7c0836927e14a926a952bd1a7f0d1b49e779 (patch) | |
tree | 2c7ac89387eb890d3d345217b79929e9a23f4ecf /src | |
parent | 0dace7831673170bd31eefa6bbe6e705211d3061 (diff) | |
download | lingo-ap-tracker-149e7c0836927e14a926a952bd1a7f0d1b49e779.tar.gz lingo-ap-tracker-149e7c0836927e14a926a952bd1a7f0d1b49e779.tar.bz2 lingo-ap-tracker-149e7c0836927e14a926a952bd1a7f0d1b49e779.zip |
Organised repo
Diffstat (limited to 'src')
-rw-r--r-- | src/ap_state.cpp | 330 | ||||
-rw-r--r-- | src/ap_state.h | 33 | ||||
-rw-r--r-- | src/area_popup.cpp | 54 | ||||
-rw-r--r-- | src/area_popup.h | 25 | ||||
-rw-r--r-- | src/connection_dialog.cpp | 40 | ||||
-rw-r--r-- | src/connection_dialog.h | 30 | ||||
-rw-r--r-- | src/eye_indicator.cpp | 49 | ||||
-rw-r--r-- | src/eye_indicator.h | 30 | ||||
-rw-r--r-- | src/game_data.cpp | 406 | ||||
-rw-r--r-- | src/game_data.h | 135 | ||||
-rw-r--r-- | src/main.cpp | 21 | ||||
-rw-r--r-- | src/tracker_config.cpp | 33 | ||||
-rw-r--r-- | src/tracker_config.h | 19 | ||||
-rw-r--r-- | src/tracker_frame.cpp | 86 | ||||
-rw-r--r-- | src/tracker_frame.h | 33 | ||||
-rw-r--r-- | src/tracker_panel.cpp | 149 | ||||
-rw-r--r-- | src/tracker_panel.h | 39 | ||||
-rw-r--r-- | src/tracker_state.cpp | 181 | ||||
-rw-r--r-- | src/tracker_state.h | 19 |
19 files changed, 1712 insertions, 0 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp new file mode 100644 index 0000000..22a51ee --- /dev/null +++ b/src/ap_state.cpp | |||
@@ -0,0 +1,330 @@ | |||
1 | #include "ap_state.h" | ||
2 | |||
3 | #define HAS_STD_FILESYSTEM | ||
4 | #define _WEBSOCKETPP_CPP11_STRICT_ | ||
5 | #pragma comment(lib, "crypt32") | ||
6 | |||
7 | #include <hkutil/string.h> | ||
8 | |||
9 | #include <apclient.hpp> | ||
10 | #include <apuuid.hpp> | ||
11 | #include <chrono> | ||
12 | #include <exception> | ||
13 | #include <list> | ||
14 | #include <memory> | ||
15 | #include <mutex> | ||
16 | #include <set> | ||
17 | #include <thread> | ||
18 | #include <tuple> | ||
19 | |||
20 | #include "game_data.h" | ||
21 | #include "tracker_frame.h" | ||
22 | #include "tracker_state.h" | ||
23 | |||
24 | constexpr int AP_MAJOR = 0; | ||
25 | constexpr int AP_MINOR = 4; | ||
26 | constexpr int AP_REVISION = 0; | ||
27 | |||
28 | constexpr int ITEM_HANDLING = 7; // <- all | ||
29 | |||
30 | namespace { | ||
31 | |||
32 | APClient* apclient = nullptr; | ||
33 | |||
34 | bool initialized = false; | ||
35 | |||
36 | TrackerFrame* tracker_frame; | ||
37 | |||
38 | bool client_active = false; | ||
39 | std::mutex client_mutex; | ||
40 | |||
41 | bool connected = false; | ||
42 | bool has_connection_result = false; | ||
43 | |||
44 | std::map<int64_t, int> inventory; | ||
45 | std::set<int64_t> checked_locations; | ||
46 | |||
47 | std::map<std::tuple<int, int>, int64_t> ap_id_by_location_id; | ||
48 | std::map<std::string, int64_t> ap_id_by_item_name; | ||
49 | std::map<LingoColor, int64_t> ap_id_by_color; | ||
50 | std::map<int64_t, std::string> progressive_item_by_ap_id; | ||
51 | |||
52 | DoorShuffleMode door_shuffle_mode = kNO_DOORS; | ||
53 | bool color_shuffle = false; | ||
54 | bool painting_shuffle = false; | ||
55 | int mastery_requirement = 21; | ||
56 | |||
57 | std::map<std::string, std::string> painting_mapping; | ||
58 | |||
59 | void RefreshTracker() { | ||
60 | GetTrackerState().CalculateState(); | ||
61 | tracker_frame->UpdateIndicators(); | ||
62 | } | ||
63 | |||
64 | int64_t GetItemId(const std::string& item_name) { | ||
65 | int64_t ap_id = apclient->get_item_id(item_name); | ||
66 | if (ap_id == APClient::INVALID_NAME_ID) { | ||
67 | std::cout << "Could not find AP item ID for " << item_name << std::endl; | ||
68 | } | ||
69 | |||
70 | return ap_id; | ||
71 | } | ||
72 | |||
73 | void DestroyClient() { | ||
74 | client_active = false; | ||
75 | apclient->reset(); | ||
76 | delete apclient; | ||
77 | apclient = nullptr; | ||
78 | } | ||
79 | |||
80 | } // namespace | ||
81 | |||
82 | void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; } | ||
83 | |||
84 | void AP_Connect(std::string server, std::string player, std::string password) { | ||
85 | if (!initialized) { | ||
86 | std::thread([]() { | ||
87 | for (;;) { | ||
88 | { | ||
89 | std::lock_guard client_guard(client_mutex); | ||
90 | if (apclient) { | ||
91 | apclient->poll(); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
96 | } | ||
97 | }).detach(); | ||
98 | |||
99 | initialized = true; | ||
100 | } | ||
101 | |||
102 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | ||
103 | |||
104 | { | ||
105 | std::lock_guard client_guard(client_mutex); | ||
106 | |||
107 | if (apclient) { | ||
108 | DestroyClient(); | ||
109 | } | ||
110 | |||
111 | apclient = new APClient(ap_get_uuid(""), "Lingo", server); | ||
112 | } | ||
113 | |||
114 | inventory.clear(); | ||
115 | checked_locations.clear(); | ||
116 | door_shuffle_mode = kNO_DOORS; | ||
117 | color_shuffle = false; | ||
118 | painting_shuffle = false; | ||
119 | painting_mapping.clear(); | ||
120 | mastery_requirement = 21; | ||
121 | |||
122 | connected = false; | ||
123 | has_connection_result = false; | ||
124 | |||
125 | apclient->set_room_info_handler([player, password]() { | ||
126 | inventory.clear(); | ||
127 | |||
128 | tracker_frame->SetStatusMessage( | ||
129 | "Connected to Archipelago server. Authenticating..."); | ||
130 | |||
131 | apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, | ||
132 | {AP_MAJOR, AP_MINOR, AP_REVISION}); | ||
133 | }); | ||
134 | |||
135 | apclient->set_location_checked_handler( | ||
136 | [](const std::list<int64_t>& locations) { | ||
137 | for (const int64_t location_id : locations) { | ||
138 | checked_locations.insert(location_id); | ||
139 | std::cout << "Location: " << location_id << std::endl; | ||
140 | } | ||
141 | |||
142 | RefreshTracker(); | ||
143 | }); | ||
144 | |||
145 | apclient->set_slot_disconnected_handler([]() { | ||
146 | tracker_frame->SetStatusMessage( | ||
147 | "Disconnected from Archipelago. Attempting to reconnect..."); | ||
148 | }); | ||
149 | |||
150 | apclient->set_socket_disconnected_handler([]() { | ||
151 | tracker_frame->SetStatusMessage( | ||
152 | "Disconnected from Archipelago. Attempting to reconnect..."); | ||
153 | }); | ||
154 | |||
155 | apclient->set_items_received_handler( | ||
156 | [](const std::list<APClient::NetworkItem>& items) { | ||
157 | for (const APClient::NetworkItem& item : items) { | ||
158 | inventory[item.item]++; | ||
159 | std::cout << "Item: " << item.item << std::endl; | ||
160 | } | ||
161 | |||
162 | RefreshTracker(); | ||
163 | }); | ||
164 | |||
165 | apclient->set_slot_connected_handler([](const nlohmann::json& slot_data) { | ||
166 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | ||
167 | |||
168 | door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); | ||
169 | color_shuffle = slot_data["shuffle_colors"].get<bool>(); | ||
170 | painting_shuffle = slot_data["shuffle_paintings"].get<bool>(); | ||
171 | mastery_requirement = slot_data["mastery_achievements"].get<int>(); | ||
172 | |||
173 | if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { | ||
174 | painting_mapping.clear(); | ||
175 | |||
176 | for (const auto& mapping_it : | ||
177 | slot_data["painting_entrance_to_exit"].items()) { | ||
178 | painting_mapping[mapping_it.key()] = mapping_it.value(); | ||
179 | } | ||
180 | } | ||
181 | |||
182 | connected = true; | ||
183 | has_connection_result = true; | ||
184 | |||
185 | RefreshTracker(); | ||
186 | }); | ||
187 | |||
188 | apclient->set_slot_refused_handler([](const std::list<std::string>& errors) { | ||
189 | connected = false; | ||
190 | has_connection_result = true; | ||
191 | |||
192 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | ||
193 | |||
194 | std::vector<std::string> error_messages; | ||
195 | error_messages.push_back("Could not connect to Archipelago."); | ||
196 | |||
197 | for (const std::string& error : errors) { | ||
198 | if (error == "InvalidSlot") { | ||
199 | error_messages.push_back("Invalid player name."); | ||
200 | } else if (error == "InvalidGame") { | ||
201 | error_messages.push_back("The specified player is not playing Lingo."); | ||
202 | } else if (error == "IncompatibleVersion") { | ||
203 | error_messages.push_back( | ||
204 | "The Archipelago server is not the correct version for this " | ||
205 | "client."); | ||
206 | } else if (error == "InvalidPassword") { | ||
207 | error_messages.push_back("Incorrect password."); | ||
208 | } else if (error == "InvalidItemsHandling") { | ||
209 | error_messages.push_back( | ||
210 | "Invalid item handling flag. This is a bug with the tracker. " | ||
211 | "Please report it to the lingo-ap-tracker GitHub."); | ||
212 | } else { | ||
213 | error_messages.push_back("Unknown error."); | ||
214 | } | ||
215 | } | ||
216 | |||
217 | std::string full_message = hatkirby::implode(error_messages, " "); | ||
218 | |||
219 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); | ||
220 | }); | ||
221 | |||
222 | client_active = true; | ||
223 | |||
224 | int timeout = 5000; // 5 seconds | ||
225 | int interval = 100; | ||
226 | int remaining_loops = timeout / interval; | ||
227 | while (!has_connection_result) { | ||
228 | if (interval == 0) { | ||
229 | connected = false; | ||
230 | has_connection_result = true; | ||
231 | |||
232 | DestroyClient(); | ||
233 | |||
234 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | ||
235 | |||
236 | wxMessageBox("Timeout while connecting to Archipelago server.", | ||
237 | "Connection failed", wxOK | wxICON_ERROR); | ||
238 | } | ||
239 | |||
240 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
241 | |||
242 | interval--; | ||
243 | } | ||
244 | |||
245 | if (connected) { | ||
246 | for (const MapArea& map_area : GetGameData().GetMapAreas()) { | ||
247 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
248 | section_id++) { | ||
249 | const Location& location = map_area.locations.at(section_id); | ||
250 | |||
251 | int64_t ap_id = apclient->get_location_id(location.ap_location_name); | ||
252 | if (ap_id == APClient::INVALID_NAME_ID) { | ||
253 | std::cout << "Could not find AP location ID for " | ||
254 | << location.ap_location_name << std::endl; | ||
255 | } else { | ||
256 | ap_id_by_location_id[{map_area.id, section_id}] = ap_id; | ||
257 | } | ||
258 | } | ||
259 | } | ||
260 | |||
261 | for (const Door& door : GetGameData().GetDoors()) { | ||
262 | if (!door.skip_item) { | ||
263 | ap_id_by_item_name[door.item_name] = GetItemId(door.item_name); | ||
264 | |||
265 | if (!door.group_name.empty() && | ||
266 | !ap_id_by_item_name.count(door.group_name)) { | ||
267 | ap_id_by_item_name[door.group_name] = GetItemId(door.group_name); | ||
268 | } | ||
269 | |||
270 | for (const ProgressiveRequirement& prog_req : door.progressives) { | ||
271 | ap_id_by_item_name[prog_req.item_name] = | ||
272 | GetItemId(prog_req.item_name); | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | |||
277 | ap_id_by_color[LingoColor::kBlack] = GetItemId("Black"); | ||
278 | ap_id_by_color[LingoColor::kRed] = GetItemId("Red"); | ||
279 | ap_id_by_color[LingoColor::kBlue] = GetItemId("Blue"); | ||
280 | ap_id_by_color[LingoColor::kYellow] = GetItemId("Yellow"); | ||
281 | ap_id_by_color[LingoColor::kPurple] = GetItemId("Purple"); | ||
282 | ap_id_by_color[LingoColor::kOrange] = GetItemId("Orange"); | ||
283 | ap_id_by_color[LingoColor::kGreen] = GetItemId("Green"); | ||
284 | ap_id_by_color[LingoColor::kBrown] = GetItemId("Brown"); | ||
285 | ap_id_by_color[LingoColor::kGray] = GetItemId("Gray"); | ||
286 | |||
287 | RefreshTracker(); | ||
288 | } else { | ||
289 | client_active = false; | ||
290 | } | ||
291 | } | ||
292 | |||
293 | bool AP_HasCheckedGameLocation(int area_id, int section_id) { | ||
294 | std::tuple<int, int> location_key = {area_id, section_id}; | ||
295 | |||
296 | if (ap_id_by_location_id.count(location_key)) { | ||
297 | return checked_locations.count(ap_id_by_location_id.at(location_key)); | ||
298 | } else { | ||
299 | return false; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | bool AP_HasColorItem(LingoColor color) { | ||
304 | if (ap_id_by_color.count(color)) { | ||
305 | return inventory.count(ap_id_by_color.at(color)); | ||
306 | } else { | ||
307 | return false; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | bool AP_HasItem(const std::string& item, int quantity) { | ||
312 | if (ap_id_by_item_name.count(item)) { | ||
313 | int64_t ap_id = ap_id_by_item_name.at(item); | ||
314 | return inventory.count(ap_id) && inventory.at(ap_id) >= quantity; | ||
315 | } else { | ||
316 | return false; | ||
317 | } | ||
318 | } | ||
319 | |||
320 | DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; } | ||
321 | |||
322 | bool AP_IsColorShuffle() { return color_shuffle; } | ||
323 | |||
324 | bool AP_IsPaintingShuffle() { return painting_shuffle; } | ||
325 | |||
326 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | ||
327 | return painting_mapping; | ||
328 | } | ||
329 | |||
330 | int AP_GetMasteryRequirement() { return mastery_requirement; } | ||
diff --git a/src/ap_state.h b/src/ap_state.h new file mode 100644 index 0000000..d880c71 --- /dev/null +++ b/src/ap_state.h | |||
@@ -0,0 +1,33 @@ | |||
1 | #ifndef AP_STATE_H_664A4180 | ||
2 | #define AP_STATE_H_664A4180 | ||
3 | |||
4 | #include <map> | ||
5 | #include <string> | ||
6 | |||
7 | #include "game_data.h" | ||
8 | |||
9 | class TrackerFrame; | ||
10 | |||
11 | enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 }; | ||
12 | |||
13 | void AP_SetTrackerFrame(TrackerFrame* tracker_frame); | ||
14 | |||
15 | void AP_Connect(std::string server, std::string player, std::string password); | ||
16 | |||
17 | bool AP_HasCheckedGameLocation(int area_id, int section_id); | ||
18 | |||
19 | bool AP_HasColorItem(LingoColor color); | ||
20 | |||
21 | bool AP_HasItem(const std::string& item, int quantity = 1); | ||
22 | |||
23 | DoorShuffleMode AP_GetDoorShuffleMode(); | ||
24 | |||
25 | bool AP_IsColorShuffle(); | ||
26 | |||
27 | bool AP_IsPaintingShuffle(); | ||
28 | |||
29 | const std::map<std::string, std::string> AP_GetPaintingMapping(); | ||
30 | |||
31 | int AP_GetMasteryRequirement(); | ||
32 | |||
33 | #endif /* end of include guard: AP_STATE_H_664A4180 */ | ||
diff --git a/src/area_popup.cpp b/src/area_popup.cpp new file mode 100644 index 0000000..a8ff612 --- /dev/null +++ b/src/area_popup.cpp | |||
@@ -0,0 +1,54 @@ | |||
1 | #include "area_popup.h" | ||
2 | |||
3 | #include "ap_state.h" | ||
4 | #include "game_data.h" | ||
5 | #include "tracker_state.h" | ||
6 | |||
7 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) | ||
8 | : wxPanel(parent, wxID_ANY), area_id_(area_id) { | ||
9 | const MapArea& map_area = GetGameData().GetMapArea(area_id); | ||
10 | |||
11 | wxFlexGridSizer* section_sizer = new wxFlexGridSizer(2, 10, 10); | ||
12 | |||
13 | for (const Location& location : map_area.locations) { | ||
14 | EyeIndicator* eye_indicator = new EyeIndicator(this); | ||
15 | section_sizer->Add(eye_indicator, wxSizerFlags().Expand()); | ||
16 | eye_indicators_.push_back(eye_indicator); | ||
17 | |||
18 | wxStaticText* section_label = new wxStaticText(this, -1, location.name); | ||
19 | section_label->SetForegroundColour(*wxWHITE); | ||
20 | section_sizer->Add( | ||
21 | section_label, | ||
22 | wxSizerFlags().Align(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL)); | ||
23 | section_labels_.push_back(section_label); | ||
24 | } | ||
25 | |||
26 | wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); | ||
27 | |||
28 | wxStaticText* top_label = new wxStaticText(this, -1, map_area.name); | ||
29 | top_label->SetForegroundColour(*wxWHITE); | ||
30 | top_label->SetFont(top_label->GetFont().Bold()); | ||
31 | top_sizer->Add(top_label, | ||
32 | wxSizerFlags().Center().DoubleBorder(wxUP | wxLEFT | wxRIGHT)); | ||
33 | |||
34 | top_sizer->Add(section_sizer, wxSizerFlags().DoubleBorder(wxALL).Expand()); | ||
35 | |||
36 | SetSizerAndFit(top_sizer); | ||
37 | |||
38 | SetBackgroundColour(*wxBLACK); | ||
39 | Hide(); | ||
40 | } | ||
41 | |||
42 | void AreaPopup::UpdateIndicators() { | ||
43 | const MapArea& map_area = GetGameData().GetMapArea(area_id_); | ||
44 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
45 | section_id++) { | ||
46 | bool checked = AP_HasCheckedGameLocation(area_id_, section_id); | ||
47 | bool reachable = | ||
48 | GetTrackerState().IsLocationReachable(area_id_, section_id); | ||
49 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | ||
50 | |||
51 | section_labels_[section_id]->SetForegroundColour(*text_color); | ||
52 | eye_indicators_[section_id]->SetChecked(checked); | ||
53 | } | ||
54 | } | ||
diff --git a/src/area_popup.h b/src/area_popup.h new file mode 100644 index 0000000..b602b63 --- /dev/null +++ b/src/area_popup.h | |||
@@ -0,0 +1,25 @@ | |||
1 | #ifndef AREA_POPUP_H_03FAC988 | ||
2 | #define AREA_POPUP_H_03FAC988 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include "eye_indicator.h" | ||
11 | |||
12 | class AreaPopup : public wxPanel { | ||
13 | public: | ||
14 | AreaPopup(wxWindow* parent, int area_id); | ||
15 | |||
16 | void UpdateIndicators(); | ||
17 | |||
18 | private: | ||
19 | int area_id_; | ||
20 | |||
21 | std::vector<wxStaticText*> section_labels_; | ||
22 | std::vector<EyeIndicator*> eye_indicators_; | ||
23 | }; | ||
24 | |||
25 | #endif /* end of include guard: AREA_POPUP_H_03FAC988 */ | ||
diff --git a/src/connection_dialog.cpp b/src/connection_dialog.cpp new file mode 100644 index 0000000..9dd9984 --- /dev/null +++ b/src/connection_dialog.cpp | |||
@@ -0,0 +1,40 @@ | |||
1 | #include "connection_dialog.h" | ||
2 | |||
3 | #include "tracker_config.h" | ||
4 | |||
5 | ConnectionDialog::ConnectionDialog() | ||
6 | : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { | ||
7 | server_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_server, wxDefaultPosition, | ||
8 | {300, -1}); | ||
9 | player_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_player, wxDefaultPosition, | ||
10 | {300, -1}); | ||
11 | password_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_password, | ||
12 | wxDefaultPosition, {300, -1}); | ||
13 | |||
14 | wxFlexGridSizer* form_sizer = new wxFlexGridSizer(2, 10, 10); | ||
15 | |||
16 | form_sizer->Add( | ||
17 | new wxStaticText(this, -1, "Server:"), | ||
18 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | ||
19 | form_sizer->Add(server_box_, wxSizerFlags().Expand()); | ||
20 | form_sizer->Add( | ||
21 | new wxStaticText(this, -1, "Player:"), | ||
22 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | ||
23 | form_sizer->Add(player_box_, wxSizerFlags().Expand()); | ||
24 | form_sizer->Add( | ||
25 | new wxStaticText(this, -1, "Password:"), | ||
26 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | ||
27 | form_sizer->Add(password_box_, wxSizerFlags().Expand()); | ||
28 | |||
29 | wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); | ||
30 | top_sizer->Add(new wxStaticText( | ||
31 | this, -1, "Enter the details to connect to Archipelago."), | ||
32 | wxSizerFlags().Align(wxALIGN_LEFT).DoubleBorder()); | ||
33 | top_sizer->Add(form_sizer, wxSizerFlags().DoubleBorder().Expand()); | ||
34 | top_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Center()); | ||
35 | |||
36 | SetSizerAndFit(top_sizer); | ||
37 | |||
38 | Center(); | ||
39 | server_box_->SetFocus(); | ||
40 | } | ||
diff --git a/src/connection_dialog.h b/src/connection_dialog.h new file mode 100644 index 0000000..5d2703d --- /dev/null +++ b/src/connection_dialog.h | |||
@@ -0,0 +1,30 @@ | |||
1 | #ifndef CONNECTION_DIALOG_H_E526D0E7 | ||
2 | #define CONNECTION_DIALOG_H_E526D0E7 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include <string> | ||
11 | |||
12 | class ConnectionDialog : public wxDialog { | ||
13 | public: | ||
14 | ConnectionDialog(); | ||
15 | |||
16 | std::string GetServerValue() { return server_box_->GetValue().ToStdString(); } | ||
17 | |||
18 | std::string GetPlayerValue() { return player_box_->GetValue().ToStdString(); } | ||
19 | |||
20 | std::string GetPasswordValue() { | ||
21 | return password_box_->GetValue().ToStdString(); | ||
22 | } | ||
23 | |||
24 | private: | ||
25 | wxTextCtrl* server_box_; | ||
26 | wxTextCtrl* player_box_; | ||
27 | wxTextCtrl* password_box_; | ||
28 | }; | ||
29 | |||
30 | #endif /* end of include guard: CONNECTION_DIALOG_H_E526D0E7 */ | ||
diff --git a/src/eye_indicator.cpp b/src/eye_indicator.cpp new file mode 100644 index 0000000..03e234e --- /dev/null +++ b/src/eye_indicator.cpp | |||
@@ -0,0 +1,49 @@ | |||
1 | #include "eye_indicator.h" | ||
2 | |||
3 | EyeIndicator::EyeIndicator(wxWindow* parent) : wxWindow(parent, wxID_ANY) { | ||
4 | SetMinSize({32, 32}); | ||
5 | |||
6 | Redraw(); | ||
7 | |||
8 | Bind(wxEVT_PAINT, &EyeIndicator::OnPaint, this); | ||
9 | } | ||
10 | |||
11 | void EyeIndicator::SetChecked(bool checked) { | ||
12 | if (intended_checked_ != checked) { | ||
13 | intended_checked_ = checked; | ||
14 | |||
15 | Redraw(); | ||
16 | } | ||
17 | } | ||
18 | |||
19 | const wxImage& EyeIndicator::GetUncheckedImage() { | ||
20 | static wxImage* unchecked_image = | ||
21 | new wxImage("assets/unchecked.png", wxBITMAP_TYPE_PNG); | ||
22 | return *unchecked_image; | ||
23 | } | ||
24 | |||
25 | const wxImage& EyeIndicator::GetCheckedImage() { | ||
26 | static wxImage* checked_image = | ||
27 | new wxImage("assets/checked.png", wxBITMAP_TYPE_PNG); | ||
28 | return *checked_image; | ||
29 | } | ||
30 | |||
31 | void EyeIndicator::OnPaint(wxPaintEvent& event) { | ||
32 | if (GetSize() != rendered_.GetSize() || | ||
33 | intended_checked_ != rendered_checked_) { | ||
34 | Redraw(); | ||
35 | } | ||
36 | |||
37 | wxPaintDC dc(this); | ||
38 | dc.DrawBitmap(rendered_, 0, 0); | ||
39 | |||
40 | event.Skip(); | ||
41 | } | ||
42 | |||
43 | void EyeIndicator::Redraw() { | ||
44 | rendered_ = | ||
45 | wxBitmap((intended_checked_ ? GetCheckedImage() : GetUncheckedImage()) | ||
46 | .Scale(GetSize().GetWidth(), GetSize().GetHeight(), | ||
47 | wxIMAGE_QUALITY_NORMAL)); | ||
48 | rendered_checked_ = intended_checked_; | ||
49 | } | ||
diff --git a/src/eye_indicator.h b/src/eye_indicator.h new file mode 100644 index 0000000..e8fd890 --- /dev/null +++ b/src/eye_indicator.h | |||
@@ -0,0 +1,30 @@ | |||
1 | #ifndef EYE_INDICATOR_H_778150F2 | ||
2 | #define EYE_INDICATOR_H_778150F2 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | class EyeIndicator : public wxWindow { | ||
11 | public: | ||
12 | EyeIndicator(wxWindow* parent); | ||
13 | |||
14 | void SetChecked(bool checked); | ||
15 | |||
16 | private: | ||
17 | static const wxImage& GetUncheckedImage(); | ||
18 | static const wxImage& GetCheckedImage(); | ||
19 | |||
20 | void OnPaint(wxPaintEvent& event); | ||
21 | |||
22 | void Redraw(); | ||
23 | |||
24 | bool intended_checked_ = false; | ||
25 | |||
26 | wxBitmap rendered_; | ||
27 | bool rendered_checked_ = false; | ||
28 | }; | ||
29 | |||
30 | #endif /* end of include guard: EYE_INDICATOR_H_778150F2 */ | ||
diff --git a/src/game_data.cpp b/src/game_data.cpp new file mode 100644 index 0000000..e15847e --- /dev/null +++ b/src/game_data.cpp | |||
@@ -0,0 +1,406 @@ | |||
1 | #include "game_data.h" | ||
2 | |||
3 | #include <hkutil/string.h> | ||
4 | #include <yaml-cpp/yaml.h> | ||
5 | |||
6 | #include <iostream> | ||
7 | |||
8 | LingoColor GetColorForString(const std::string &str) { | ||
9 | if (str == "black") { | ||
10 | return LingoColor::kBlack; | ||
11 | } else if (str == "red") { | ||
12 | return LingoColor::kRed; | ||
13 | } else if (str == "blue") { | ||
14 | return LingoColor::kBlue; | ||
15 | } else if (str == "yellow") { | ||
16 | return LingoColor::kYellow; | ||
17 | } else if (str == "orange") { | ||
18 | return LingoColor::kOrange; | ||
19 | } else if (str == "green") { | ||
20 | return LingoColor::kGreen; | ||
21 | } else if (str == "gray") { | ||
22 | return LingoColor::kGray; | ||
23 | } else if (str == "brown") { | ||
24 | return LingoColor::kBrown; | ||
25 | } else if (str == "purple") { | ||
26 | return LingoColor::kPurple; | ||
27 | } else { | ||
28 | std::cout << "Invalid color: " << str << std::endl; | ||
29 | return LingoColor::kNone; | ||
30 | } | ||
31 | } | ||
32 | |||
33 | GameData::GameData() { | ||
34 | YAML::Node lingo_config = YAML::LoadFile("assets/LL1.yaml"); | ||
35 | YAML::Node areas_config = YAML::LoadFile("assets/areas.yaml"); | ||
36 | |||
37 | rooms_.reserve(lingo_config.size() * 2); | ||
38 | |||
39 | for (const auto &room_it : lingo_config) { | ||
40 | int room_id = AddOrGetRoom(room_it.first.as<std::string>()); | ||
41 | Room &room_obj = rooms_[room_id]; | ||
42 | |||
43 | for (const auto &entrance_it : room_it.second["entrances"]) { | ||
44 | int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); | ||
45 | Room &from_room_obj = rooms_[from_room_id]; | ||
46 | |||
47 | switch (entrance_it.second.Type()) { | ||
48 | case YAML::NodeType::Scalar: { | ||
49 | // This is just "true". | ||
50 | from_room_obj.exits.push_back({.destination_room = room_id}); | ||
51 | break; | ||
52 | } | ||
53 | case YAML::NodeType::Map: { | ||
54 | Exit exit_obj; | ||
55 | exit_obj.destination_room = room_id; | ||
56 | |||
57 | if (entrance_it.second["door"]) { | ||
58 | std::string door_room = room_obj.name; | ||
59 | if (entrance_it.second["room"]) { | ||
60 | door_room = entrance_it.second["room"].as<std::string>(); | ||
61 | } | ||
62 | exit_obj.door = AddOrGetDoor( | ||
63 | door_room, entrance_it.second["door"].as<std::string>()); | ||
64 | } | ||
65 | |||
66 | if (entrance_it.second["painting"]) { | ||
67 | exit_obj.painting = entrance_it.second["painting"].as<bool>(); | ||
68 | } | ||
69 | |||
70 | from_room_obj.exits.push_back(exit_obj); | ||
71 | break; | ||
72 | } | ||
73 | case YAML::NodeType::Sequence: { | ||
74 | for (const auto &option : entrance_it.second) { | ||
75 | Exit exit_obj; | ||
76 | exit_obj.destination_room = room_id; | ||
77 | |||
78 | std::string door_room = room_obj.name; | ||
79 | if (option["room"]) { | ||
80 | door_room = option["room"].as<std::string>(); | ||
81 | } | ||
82 | exit_obj.door = | ||
83 | AddOrGetDoor(door_room, option["door"].as<std::string>()); | ||
84 | |||
85 | if (option["painting"]) { | ||
86 | exit_obj.painting = option["painting"].as<bool>(); | ||
87 | } | ||
88 | |||
89 | from_room_obj.exits.push_back(exit_obj); | ||
90 | } | ||
91 | |||
92 | break; | ||
93 | } | ||
94 | default: { | ||
95 | // This shouldn't happen. | ||
96 | std::cout << "Error reading game data: " << entrance_it << std::endl; | ||
97 | break; | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | if (room_it.second["panels"]) { | ||
103 | for (const auto &panel_it : room_it.second["panels"]) { | ||
104 | int panel_id = | ||
105 | AddOrGetPanel(room_obj.name, panel_it.first.as<std::string>()); | ||
106 | Panel &panel_obj = panels_[panel_id]; | ||
107 | |||
108 | if (panel_it.second["colors"]) { | ||
109 | if (panel_it.second["colors"].IsScalar()) { | ||
110 | panel_obj.colors.push_back( | ||
111 | GetColorForString(panel_it.second["colors"].as<std::string>())); | ||
112 | } else { | ||
113 | for (const auto &color_node : panel_it.second["colors"]) { | ||
114 | panel_obj.colors.push_back( | ||
115 | GetColorForString(color_node.as<std::string>())); | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | if (panel_it.second["required_room"]) { | ||
121 | if (panel_it.second["required_room"].IsScalar()) { | ||
122 | panel_obj.required_rooms.push_back(AddOrGetRoom( | ||
123 | panel_it.second["required_room"].as<std::string>())); | ||
124 | } else { | ||
125 | for (const auto &rr_node : panel_it.second["required_room"]) { | ||
126 | panel_obj.required_rooms.push_back( | ||
127 | AddOrGetRoom(rr_node.as<std::string>())); | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
132 | if (panel_it.second["required_door"]) { | ||
133 | if (panel_it.second["required_door"].IsMap()) { | ||
134 | std::string rd_room = room_obj.name; | ||
135 | if (panel_it.second["required_door"]["room"]) { | ||
136 | rd_room = | ||
137 | panel_it.second["required_door"]["room"].as<std::string>(); | ||
138 | } | ||
139 | |||
140 | panel_obj.required_doors.push_back(AddOrGetDoor( | ||
141 | rd_room, | ||
142 | panel_it.second["required_door"]["door"].as<std::string>())); | ||
143 | } else { | ||
144 | for (const auto &rr_node : panel_it.second["required_door"]) { | ||
145 | std::string rd_room = room_obj.name; | ||
146 | if (rr_node["room"]) { | ||
147 | rd_room = rr_node["room"].as<std::string>(); | ||
148 | } | ||
149 | |||
150 | panel_obj.required_doors.push_back( | ||
151 | AddOrGetDoor(rd_room, rr_node["door"].as<std::string>())); | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | if (panel_it.second["check"]) { | ||
157 | panel_obj.check = panel_it.second["check"].as<bool>(); | ||
158 | } | ||
159 | |||
160 | if (panel_it.second["achievement"]) { | ||
161 | panel_obj.achievement = panel_it.second["achievement"].as<bool>(); | ||
162 | |||
163 | if (panel_obj.achievement) { | ||
164 | achievement_panels_.push_back(panel_id); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | if (panel_it.second["exclude_reduce"]) { | ||
169 | panel_obj.exclude_reduce = | ||
170 | panel_it.second["exclude_reduce"].as<bool>(); | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | |||
175 | if (room_it.second["doors"]) { | ||
176 | for (const auto &door_it : room_it.second["doors"]) { | ||
177 | int door_id = | ||
178 | AddOrGetDoor(room_obj.name, door_it.first.as<std::string>()); | ||
179 | Door &door_obj = doors_[door_id]; | ||
180 | |||
181 | bool has_external_panels = false; | ||
182 | std::vector<std::string> panel_names; | ||
183 | |||
184 | for (const auto &panel_node : door_it.second["panels"]) { | ||
185 | if (panel_node.IsScalar()) { | ||
186 | panel_names.push_back(panel_node.as<std::string>()); | ||
187 | door_obj.panels.push_back( | ||
188 | AddOrGetPanel(room_obj.name, panel_node.as<std::string>())); | ||
189 | } else { | ||
190 | has_external_panels = true; | ||
191 | panel_names.push_back(panel_node["panel"].as<std::string>()); | ||
192 | door_obj.panels.push_back( | ||
193 | AddOrGetPanel(panel_node["room"].as<std::string>(), | ||
194 | panel_node["panel"].as<std::string>())); | ||
195 | } | ||
196 | } | ||
197 | |||
198 | if (door_it.second["skip_location"]) { | ||
199 | door_obj.skip_location = door_it.second["skip_location"].as<bool>(); | ||
200 | } | ||
201 | |||
202 | if (door_it.second["skip_item"]) { | ||
203 | door_obj.skip_item = door_it.second["skip_item"].as<bool>(); | ||
204 | } | ||
205 | |||
206 | if (door_it.second["event"]) { | ||
207 | door_obj.skip_location = door_it.second["event"].as<bool>(); | ||
208 | door_obj.skip_item = door_it.second["event"].as<bool>(); | ||
209 | } | ||
210 | |||
211 | if (door_it.second["item_name"]) { | ||
212 | door_obj.item_name = door_it.second["item_name"].as<std::string>(); | ||
213 | } else if (!door_it.second["skip_item"] && !door_it.second["event"]) { | ||
214 | door_obj.item_name = room_obj.name + " - " + door_obj.name; | ||
215 | } | ||
216 | |||
217 | if (door_it.second["group"]) { | ||
218 | door_obj.group_name = door_it.second["group"].as<std::string>(); | ||
219 | } | ||
220 | |||
221 | if (door_it.second["location_name"]) { | ||
222 | door_obj.location_name = | ||
223 | door_it.second["location_name"].as<std::string>(); | ||
224 | } else if (!door_it.second["skip_location"] && | ||
225 | !door_it.second["event"]) { | ||
226 | if (has_external_panels) { | ||
227 | std::cout | ||
228 | << room_obj.name << " - " << door_obj.name | ||
229 | << " has panels from other rooms but does not have an explicit " | ||
230 | "location name and is not marked skip_location or event" | ||
231 | << std::endl; | ||
232 | } | ||
233 | |||
234 | door_obj.location_name = | ||
235 | room_obj.name + " - " + hatkirby::implode(panel_names, ", "); | ||
236 | } | ||
237 | |||
238 | if (door_it.second["include_reduce"]) { | ||
239 | door_obj.exclude_reduce = | ||
240 | !door_it.second["include_reduce"].as<bool>(); | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | |||
245 | if (room_it.second["paintings"]) { | ||
246 | for (const auto &painting : room_it.second["paintings"]) { | ||
247 | std::string painting_id = painting["id"].as<std::string>(); | ||
248 | room_by_painting_[painting_id] = room_id; | ||
249 | |||
250 | if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { | ||
251 | PaintingExit painting_exit; | ||
252 | painting_exit.id = painting_id; | ||
253 | |||
254 | if (painting["required_door"]) { | ||
255 | std::string rd_room = room_obj.name; | ||
256 | if (painting["required_door"]["room"]) { | ||
257 | rd_room = painting["required_door"]["room"].as<std::string>(); | ||
258 | } | ||
259 | |||
260 | painting_exit.door = AddOrGetDoor( | ||
261 | rd_room, painting["required_door"]["door"].as<std::string>()); | ||
262 | } | ||
263 | |||
264 | room_obj.paintings.push_back(painting_exit); | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | |||
269 | if (room_it.second["progression"]) { | ||
270 | for (const auto &progression_it : room_it.second["progression"]) { | ||
271 | std::string progressive_item_name = | ||
272 | progression_it.first.as<std::string>(); | ||
273 | |||
274 | int index = 1; | ||
275 | for (const auto &stage : progression_it.second) { | ||
276 | int door_id = -1; | ||
277 | |||
278 | if (stage.IsScalar()) { | ||
279 | door_id = AddOrGetDoor(room_obj.name, stage.as<std::string>()); | ||
280 | } else { | ||
281 | door_id = AddOrGetDoor(stage["room"].as<std::string>(), | ||
282 | stage["door"].as<std::string>()); | ||
283 | } | ||
284 | |||
285 | doors_[door_id].progressives.push_back( | ||
286 | {.item_name = progressive_item_name, .quantity = index}); | ||
287 | index++; | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | |||
293 | map_areas_.reserve(areas_config.size()); | ||
294 | |||
295 | std::map<std::string, int> fold_areas; | ||
296 | for (const auto &area_it : areas_config) { | ||
297 | if (area_it.second["map"]) { | ||
298 | int area_id = AddOrGetArea(area_it.first.as<std::string>()); | ||
299 | MapArea &area_obj = map_areas_[area_id]; | ||
300 | area_obj.map_x = area_it.second["map"][0].as<int>(); | ||
301 | area_obj.map_y = area_it.second["map"][1].as<int>(); | ||
302 | } else if (area_it.second["fold_into"]) { | ||
303 | fold_areas[area_it.first.as<std::string>()] = | ||
304 | AddOrGetArea(area_it.second["fold_into"].as<std::string>()); | ||
305 | } | ||
306 | } | ||
307 | |||
308 | for (const Panel &panel : panels_) { | ||
309 | if (panel.check) { | ||
310 | int room_id = panel.room; | ||
311 | std::string room_name = rooms_[room_id].name; | ||
312 | |||
313 | std::string area_name = room_name; | ||
314 | if (fold_areas.count(room_name)) { | ||
315 | int fold_area_id = fold_areas[room_name]; | ||
316 | area_name = map_areas_[fold_area_id].name; | ||
317 | } | ||
318 | |||
319 | int area_id = AddOrGetArea(area_name); | ||
320 | MapArea &map_area = map_areas_[area_id]; | ||
321 | // room field should be the original room ID | ||
322 | map_area.locations.push_back( | ||
323 | {.name = panel.name, | ||
324 | .ap_location_name = room_name + " - " + panel.name, | ||
325 | .room = panel.room, | ||
326 | .panels = {panel.id}}); | ||
327 | } | ||
328 | } | ||
329 | |||
330 | for (const Door &door : doors_) { | ||
331 | if (!door.skip_location) { | ||
332 | int room_id = door.room; | ||
333 | std::string area_name = rooms_[room_id].name; | ||
334 | std::string section_name; | ||
335 | |||
336 | size_t divider_pos = door.location_name.find(" - "); | ||
337 | if (divider_pos == std::string::npos) { | ||
338 | section_name = door.location_name; | ||
339 | } else { | ||
340 | area_name = door.location_name.substr(0, divider_pos); | ||
341 | section_name = door.location_name.substr(divider_pos + 3); | ||
342 | } | ||
343 | |||
344 | if (fold_areas.count(area_name)) { | ||
345 | int fold_area_id = fold_areas[area_name]; | ||
346 | area_name = map_areas_[fold_area_id].name; | ||
347 | } | ||
348 | |||
349 | int area_id = AddOrGetArea(area_name); | ||
350 | MapArea &map_area = map_areas_[area_id]; | ||
351 | // room field should be the original room ID | ||
352 | map_area.locations.push_back({.name = section_name, | ||
353 | .ap_location_name = door.location_name, | ||
354 | .room = door.room, | ||
355 | .panels = door.panels}); | ||
356 | } | ||
357 | } | ||
358 | } | ||
359 | |||
360 | int GameData::AddOrGetRoom(std::string room) { | ||
361 | if (!room_by_id_.count(room)) { | ||
362 | room_by_id_[room] = rooms_.size(); | ||
363 | rooms_.push_back({.name = room}); | ||
364 | } | ||
365 | |||
366 | return room_by_id_[room]; | ||
367 | } | ||
368 | |||
369 | int GameData::AddOrGetDoor(std::string room, std::string door) { | ||
370 | std::string full_name = room + " - " + door; | ||
371 | |||
372 | if (!door_by_id_.count(full_name)) { | ||
373 | door_by_id_[full_name] = doors_.size(); | ||
374 | doors_.push_back({.room = AddOrGetRoom(room), .name = door}); | ||
375 | } | ||
376 | |||
377 | return door_by_id_[full_name]; | ||
378 | } | ||
379 | |||
380 | int GameData::AddOrGetPanel(std::string room, std::string panel) { | ||
381 | std::string full_name = room + " - " + panel; | ||
382 | |||
383 | if (!panel_by_id_.count(full_name)) { | ||
384 | int panel_id = panels_.size(); | ||
385 | panel_by_id_[full_name] = panel_id; | ||
386 | panels_.push_back( | ||
387 | {.id = panel_id, .room = AddOrGetRoom(room), .name = panel}); | ||
388 | } | ||
389 | |||
390 | return panel_by_id_[full_name]; | ||
391 | } | ||
392 | |||
393 | int GameData::AddOrGetArea(std::string area) { | ||
394 | if (!area_by_id_.count(area)) { | ||
395 | int area_id = map_areas_.size(); | ||
396 | area_by_id_[area] = area_id; | ||
397 | map_areas_.push_back({.id = area_id, .name = area}); | ||
398 | } | ||
399 | |||
400 | return area_by_id_[area]; | ||
401 | } | ||
402 | |||
403 | const GameData &GetGameData() { | ||
404 | static GameData *instance = new GameData(); | ||
405 | return *instance; | ||
406 | } | ||
diff --git a/src/game_data.h b/src/game_data.h new file mode 100644 index 0000000..0cc7a7b --- /dev/null +++ b/src/game_data.h | |||
@@ -0,0 +1,135 @@ | |||
1 | #ifndef GAME_DATA_H_9C42AC51 | ||
2 | #define GAME_DATA_H_9C42AC51 | ||
3 | |||
4 | #include <map> | ||
5 | #include <optional> | ||
6 | #include <string> | ||
7 | #include <vector> | ||
8 | |||
9 | enum class LingoColor { | ||
10 | kNone, | ||
11 | kBlack, | ||
12 | kRed, | ||
13 | kBlue, | ||
14 | kYellow, | ||
15 | kGreen, | ||
16 | kOrange, | ||
17 | kPurple, | ||
18 | kBrown, | ||
19 | kGray | ||
20 | }; | ||
21 | |||
22 | struct Panel { | ||
23 | int id; | ||
24 | int room; | ||
25 | std::string name; | ||
26 | std::vector<LingoColor> colors; | ||
27 | std::vector<int> required_rooms; | ||
28 | std::vector<int> required_doors; | ||
29 | bool check = false; | ||
30 | bool exclude_reduce = false; | ||
31 | bool achievement = false; | ||
32 | }; | ||
33 | |||
34 | struct ProgressiveRequirement { | ||
35 | std::string item_name; | ||
36 | int quantity = 0; | ||
37 | }; | ||
38 | |||
39 | struct Door { | ||
40 | int room; | ||
41 | std::string name; | ||
42 | std::string location_name; | ||
43 | std::string item_name; | ||
44 | std::string group_name; | ||
45 | bool skip_location = false; | ||
46 | bool skip_item = false; | ||
47 | std::vector<int> panels; | ||
48 | bool exclude_reduce = true; | ||
49 | std::vector<ProgressiveRequirement> progressives; | ||
50 | }; | ||
51 | |||
52 | struct Exit { | ||
53 | int destination_room; | ||
54 | std::optional<int> door; | ||
55 | bool painting = false; | ||
56 | }; | ||
57 | |||
58 | struct PaintingExit { | ||
59 | std::string id; | ||
60 | std::optional<int> door; | ||
61 | }; | ||
62 | |||
63 | struct Room { | ||
64 | std::string name; | ||
65 | std::vector<Exit> exits; | ||
66 | std::vector<PaintingExit> paintings; | ||
67 | }; | ||
68 | |||
69 | struct Location { | ||
70 | std::string name; | ||
71 | std::string ap_location_name; | ||
72 | int room; | ||
73 | std::vector<int> panels; | ||
74 | }; | ||
75 | |||
76 | struct MapArea { | ||
77 | int id; | ||
78 | std::string name; | ||
79 | std::vector<Location> locations; | ||
80 | int map_x; | ||
81 | int map_y; | ||
82 | }; | ||
83 | |||
84 | class GameData { | ||
85 | public: | ||
86 | GameData(); | ||
87 | |||
88 | const std::vector<MapArea>& GetMapAreas() const { return map_areas_; } | ||
89 | |||
90 | const MapArea& GetMapArea(int id) const { return map_areas_.at(id); } | ||
91 | |||
92 | int GetRoomByName(const std::string& name) const { | ||
93 | return room_by_id_.at(name); | ||
94 | } | ||
95 | |||
96 | const Room& GetRoom(int room_id) const { return rooms_.at(room_id); } | ||
97 | |||
98 | const std::vector<Door>& GetDoors() const { return doors_; } | ||
99 | |||
100 | const Door& GetDoor(int door_id) const { return doors_.at(door_id); } | ||
101 | |||
102 | const Panel& GetPanel(int panel_id) const { return panels_.at(panel_id); } | ||
103 | |||
104 | int GetRoomForPainting(const std::string& painting_id) const { | ||
105 | return room_by_painting_.at(painting_id); | ||
106 | } | ||
107 | |||
108 | const std::vector<int>& GetAchievementPanels() const { | ||
109 | return achievement_panels_; | ||
110 | } | ||
111 | |||
112 | private: | ||
113 | int AddOrGetRoom(std::string room); | ||
114 | int AddOrGetDoor(std::string room, std::string door); | ||
115 | int AddOrGetPanel(std::string room, std::string panel); | ||
116 | int AddOrGetArea(std::string area); | ||
117 | |||
118 | std::vector<Room> rooms_; | ||
119 | std::vector<Door> doors_; | ||
120 | std::vector<Panel> panels_; | ||
121 | std::vector<MapArea> map_areas_; | ||
122 | |||
123 | std::map<std::string, int> room_by_id_; | ||
124 | std::map<std::string, int> door_by_id_; | ||
125 | std::map<std::string, int> panel_by_id_; | ||
126 | std::map<std::string, int> area_by_id_; | ||
127 | |||
128 | std::map<std::string, int> room_by_painting_; | ||
129 | |||
130 | std::vector<int> achievement_panels_; | ||
131 | }; | ||
132 | |||
133 | const GameData& GetGameData(); | ||
134 | |||
135 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ | ||
diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fe9aceb --- /dev/null +++ b/src/main.cpp | |||
@@ -0,0 +1,21 @@ | |||
1 | #include <wx/wxprec.h> | ||
2 | |||
3 | #ifndef WX_PRECOMP | ||
4 | #include <wx/wx.h> | ||
5 | #endif | ||
6 | |||
7 | #include "tracker_config.h" | ||
8 | #include "tracker_frame.h" | ||
9 | |||
10 | class TrackerApp : public wxApp { | ||
11 | public: | ||
12 | virtual bool OnInit() { | ||
13 | GetTrackerConfig().Load(); | ||
14 | |||
15 | TrackerFrame *frame = new TrackerFrame(); | ||
16 | frame->Show(true); | ||
17 | return true; | ||
18 | } | ||
19 | }; | ||
20 | |||
21 | wxIMPLEMENT_APP(TrackerApp); | ||
diff --git a/src/tracker_config.cpp b/src/tracker_config.cpp new file mode 100644 index 0000000..96bb60a --- /dev/null +++ b/src/tracker_config.cpp | |||
@@ -0,0 +1,33 @@ | |||
1 | #include "tracker_config.h" | ||
2 | |||
3 | #include <fstream> | ||
4 | #include <yaml-cpp/yaml.h> | ||
5 | |||
6 | constexpr const char* CONFIG_FILE_NAME = "config.yaml"; | ||
7 | |||
8 | void TrackerConfig::Load() { | ||
9 | try { | ||
10 | YAML::Node file = YAML::LoadFile(CONFIG_FILE_NAME); | ||
11 | |||
12 | ap_server = file["ap_server"].as<std::string>(); | ||
13 | ap_player = file["ap_player"].as<std::string>(); | ||
14 | ap_password = file["ap_password"].as<std::string>(); | ||
15 | } catch (const std::exception&) { | ||
16 | // It's fine if the file can't be loaded. | ||
17 | } | ||
18 | } | ||
19 | |||
20 | void TrackerConfig::Save() { | ||
21 | YAML::Node output; | ||
22 | output["ap_server"] = ap_server; | ||
23 | output["ap_player"] = ap_player; | ||
24 | output["ap_password"] = ap_password; | ||
25 | |||
26 | std::ofstream filewriter(CONFIG_FILE_NAME); | ||
27 | filewriter << output; | ||
28 | } | ||
29 | |||
30 | TrackerConfig& GetTrackerConfig() { | ||
31 | static TrackerConfig* instance = new TrackerConfig(); | ||
32 | return *instance; | ||
33 | } | ||
diff --git a/src/tracker_config.h b/src/tracker_config.h new file mode 100644 index 0000000..e2e6cd7 --- /dev/null +++ b/src/tracker_config.h | |||
@@ -0,0 +1,19 @@ | |||
1 | #ifndef TRACKER_CONFIG_H_36CDD648 | ||
2 | #define TRACKER_CONFIG_H_36CDD648 | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | class TrackerConfig { | ||
7 | public: | ||
8 | void Load(); | ||
9 | |||
10 | void Save(); | ||
11 | |||
12 | std::string ap_server; | ||
13 | std::string ap_player; | ||
14 | std::string ap_password; | ||
15 | }; | ||
16 | |||
17 | TrackerConfig& GetTrackerConfig(); | ||
18 | |||
19 | #endif /* end of include guard: TRACKER_CONFIG_H_36CDD648 */ | ||
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp new file mode 100644 index 0000000..2a862a5 --- /dev/null +++ b/src/tracker_frame.cpp | |||
@@ -0,0 +1,86 @@ | |||
1 | #include "tracker_frame.h" | ||
2 | |||
3 | #include "ap_state.h" | ||
4 | #include "connection_dialog.h" | ||
5 | #include "tracker_config.h" | ||
6 | #include "tracker_panel.h" | ||
7 | |||
8 | enum TrackerFrameIds { ID_CONNECT = 1 }; | ||
9 | |||
10 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); | ||
11 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); | ||
12 | |||
13 | TrackerFrame::TrackerFrame() | ||
14 | : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition, | ||
15 | wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) { | ||
16 | ::wxInitAllImageHandlers(); | ||
17 | |||
18 | AP_SetTrackerFrame(this); | ||
19 | |||
20 | SetSize(1280, 728); | ||
21 | |||
22 | wxMenu *menuFile = new wxMenu(); | ||
23 | menuFile->Append(ID_CONNECT, "&Connect"); | ||
24 | menuFile->Append(wxID_EXIT); | ||
25 | |||
26 | wxMenu *menuHelp = new wxMenu(); | ||
27 | menuHelp->Append(wxID_ABOUT); | ||
28 | |||
29 | wxMenuBar *menuBar = new wxMenuBar(); | ||
30 | menuBar->Append(menuFile, "&File"); | ||
31 | menuBar->Append(menuHelp, "&Help"); | ||
32 | |||
33 | SetMenuBar(menuBar); | ||
34 | |||
35 | CreateStatusBar(); | ||
36 | SetStatusText("Not connected to Archipelago."); | ||
37 | |||
38 | Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); | ||
39 | Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); | ||
40 | Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT); | ||
41 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | ||
42 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | ||
43 | |||
44 | tracker_panel_ = new TrackerPanel(this); | ||
45 | } | ||
46 | |||
47 | void TrackerFrame::SetStatusMessage(std::string message) { | ||
48 | wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED); | ||
49 | event->SetString(message.c_str()); | ||
50 | |||
51 | QueueEvent(event); | ||
52 | } | ||
53 | |||
54 | void TrackerFrame::UpdateIndicators() { | ||
55 | QueueEvent(new wxCommandEvent(STATE_CHANGED)); | ||
56 | } | ||
57 | |||
58 | void TrackerFrame::OnAbout(wxCommandEvent &event) { | ||
59 | wxMessageBox("Lingo Archipelago Tracker by hatkirby", | ||
60 | "About lingo-ap-tracker", wxOK | wxICON_INFORMATION); | ||
61 | } | ||
62 | |||
63 | void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } | ||
64 | |||
65 | void TrackerFrame::OnConnect(wxCommandEvent &event) { | ||
66 | ConnectionDialog dlg; | ||
67 | |||
68 | if (dlg.ShowModal() == wxID_OK) { | ||
69 | GetTrackerConfig().ap_server = dlg.GetServerValue(); | ||
70 | GetTrackerConfig().ap_player = dlg.GetPlayerValue(); | ||
71 | GetTrackerConfig().ap_password = dlg.GetPasswordValue(); | ||
72 | GetTrackerConfig().Save(); | ||
73 | |||
74 | AP_Connect(dlg.GetServerValue(), dlg.GetPlayerValue(), | ||
75 | dlg.GetPasswordValue()); | ||
76 | } | ||
77 | } | ||
78 | |||
79 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | ||
80 | tracker_panel_->UpdateIndicators(); | ||
81 | Refresh(); | ||
82 | } | ||
83 | |||
84 | void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { | ||
85 | SetStatusText(event.GetString()); | ||
86 | } | ||
diff --git a/src/tracker_frame.h b/src/tracker_frame.h new file mode 100644 index 0000000..6f4e4fc --- /dev/null +++ b/src/tracker_frame.h | |||
@@ -0,0 +1,33 @@ | |||
1 | #ifndef TRACKER_FRAME_H_86BD8DFB | ||
2 | #define TRACKER_FRAME_H_86BD8DFB | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | class TrackerPanel; | ||
11 | |||
12 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); | ||
13 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); | ||
14 | |||
15 | class TrackerFrame : public wxFrame { | ||
16 | public: | ||
17 | TrackerFrame(); | ||
18 | |||
19 | void SetStatusMessage(std::string message); | ||
20 | |||
21 | void UpdateIndicators(); | ||
22 | |||
23 | private: | ||
24 | void OnExit(wxCommandEvent &event); | ||
25 | void OnAbout(wxCommandEvent &event); | ||
26 | void OnConnect(wxCommandEvent &event); | ||
27 | void OnStateChanged(wxCommandEvent &event); | ||
28 | void OnStatusChanged(wxCommandEvent &event); | ||
29 | |||
30 | TrackerPanel *tracker_panel_; | ||
31 | }; | ||
32 | |||
33 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ | ||
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp new file mode 100644 index 0000000..0e0569b --- /dev/null +++ b/src/tracker_panel.cpp | |||
@@ -0,0 +1,149 @@ | |||
1 | #include "tracker_panel.h" | ||
2 | |||
3 | #include "ap_state.h" | ||
4 | #include "area_popup.h" | ||
5 | #include "game_data.h" | ||
6 | #include "tracker_state.h" | ||
7 | |||
8 | constexpr int AREA_ACTUAL_SIZE = 64; | ||
9 | constexpr int AREA_BORDER_SIZE = 5; | ||
10 | constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; | ||
11 | |||
12 | TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | ||
13 | map_image_ = wxImage("assets/lingo_map.png", wxBITMAP_TYPE_PNG); | ||
14 | if (!map_image_.IsOk()) { | ||
15 | return; | ||
16 | } | ||
17 | |||
18 | for (const MapArea &map_area : GetGameData().GetMapAreas()) { | ||
19 | AreaIndicator area; | ||
20 | area.area_id = map_area.id; | ||
21 | |||
22 | area.popup = new AreaPopup(this, map_area.id); | ||
23 | area.popup->SetPosition({0, 0}); | ||
24 | |||
25 | areas_.push_back(area); | ||
26 | } | ||
27 | |||
28 | Redraw(); | ||
29 | |||
30 | Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); | ||
31 | Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); | ||
32 | } | ||
33 | |||
34 | void TrackerPanel::UpdateIndicators() { | ||
35 | Redraw(); | ||
36 | |||
37 | for (AreaIndicator &area : areas_) { | ||
38 | area.popup->UpdateIndicators(); | ||
39 | } | ||
40 | } | ||
41 | |||
42 | void TrackerPanel::OnPaint(wxPaintEvent &event) { | ||
43 | if (GetSize() != rendered_.GetSize()) { | ||
44 | Redraw(); | ||
45 | } | ||
46 | |||
47 | wxPaintDC dc(this); | ||
48 | dc.DrawBitmap(rendered_, 0, 0); | ||
49 | |||
50 | event.Skip(); | ||
51 | } | ||
52 | |||
53 | void TrackerPanel::OnMouseMove(wxMouseEvent &event) { | ||
54 | for (AreaIndicator &area : areas_) { | ||
55 | if (area.real_x1 <= event.GetX() && event.GetX() < area.real_x2 && | ||
56 | area.real_y1 <= event.GetY() && event.GetY() < area.real_y2) { | ||
57 | area.popup->Show(); | ||
58 | } else { | ||
59 | area.popup->Hide(); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | event.Skip(); | ||
64 | } | ||
65 | |||
66 | void TrackerPanel::Redraw() { | ||
67 | wxSize panel_size = GetSize(); | ||
68 | wxSize image_size = map_image_.GetSize(); | ||
69 | |||
70 | int final_x = 0; | ||
71 | int final_y = 0; | ||
72 | int final_width = panel_size.GetWidth(); | ||
73 | int final_height = panel_size.GetHeight(); | ||
74 | |||
75 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
76 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
77 | final_height = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
78 | image_size.GetWidth(); | ||
79 | final_y = (panel_size.GetHeight() - final_height) / 2; | ||
80 | } else { | ||
81 | final_width = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
82 | image_size.GetHeight(); | ||
83 | final_x = (panel_size.GetWidth() - final_width) / 2; | ||
84 | } | ||
85 | |||
86 | rendered_ = wxBitmap( | ||
87 | map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) | ||
88 | .Size(panel_size, {final_x, final_y}, 0, 0, 0)); | ||
89 | |||
90 | wxMemoryDC dc; | ||
91 | dc.SelectObject(rendered_); | ||
92 | |||
93 | for (AreaIndicator &area : areas_) { | ||
94 | const wxBrush *brush_color = wxGREY_BRUSH; | ||
95 | |||
96 | const MapArea &map_area = GetGameData().GetMapArea(area.area_id); | ||
97 | bool has_reachable_unchecked = false; | ||
98 | bool has_unreachable_unchecked = false; | ||
99 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
100 | section_id++) { | ||
101 | if (!AP_HasCheckedGameLocation(area.area_id, section_id)) { | ||
102 | if (GetTrackerState().IsLocationReachable(area.area_id, section_id)) { | ||
103 | has_reachable_unchecked = true; | ||
104 | } else { | ||
105 | has_unreachable_unchecked = true; | ||
106 | } | ||
107 | } | ||
108 | } | ||
109 | |||
110 | if (has_reachable_unchecked && has_unreachable_unchecked) { | ||
111 | brush_color = wxYELLOW_BRUSH; | ||
112 | } else if (has_reachable_unchecked) { | ||
113 | brush_color = wxGREEN_BRUSH; | ||
114 | } else if (has_unreachable_unchecked) { | ||
115 | brush_color = wxRED_BRUSH; | ||
116 | } | ||
117 | |||
118 | int real_area_size = | ||
119 | final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); | ||
120 | int actual_border_size = | ||
121 | real_area_size * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; | ||
122 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * | ||
123 | final_width / image_size.GetWidth(); | ||
124 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * | ||
125 | final_width / image_size.GetWidth(); | ||
126 | |||
127 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); | ||
128 | dc.SetBrush(*brush_color); | ||
129 | dc.DrawRectangle({real_area_x, real_area_y}, | ||
130 | {real_area_size, real_area_size}); | ||
131 | |||
132 | area.real_x1 = real_area_x; | ||
133 | area.real_x2 = real_area_x + real_area_size; | ||
134 | area.real_y1 = real_area_y; | ||
135 | area.real_y2 = real_area_y + real_area_size; | ||
136 | |||
137 | int popup_x = | ||
138 | final_x + map_area.map_x * final_width / image_size.GetWidth(); | ||
139 | int popup_y = | ||
140 | final_y + map_area.map_y * final_width / image_size.GetWidth(); | ||
141 | if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { | ||
142 | popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); | ||
143 | } | ||
144 | if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { | ||
145 | popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); | ||
146 | } | ||
147 | area.popup->SetPosition({popup_x, popup_y}); | ||
148 | } | ||
149 | } | ||
diff --git a/src/tracker_panel.h b/src/tracker_panel.h new file mode 100644 index 0000000..a871f90 --- /dev/null +++ b/src/tracker_panel.h | |||
@@ -0,0 +1,39 @@ | |||
1 | #ifndef TRACKER_PANEL_H_D675A54D | ||
2 | #define TRACKER_PANEL_H_D675A54D | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | class AreaPopup; | ||
11 | |||
12 | class TrackerPanel : public wxPanel { | ||
13 | public: | ||
14 | TrackerPanel(wxWindow *parent); | ||
15 | |||
16 | void UpdateIndicators(); | ||
17 | |||
18 | private: | ||
19 | struct AreaIndicator { | ||
20 | int area_id = -1; | ||
21 | AreaPopup *popup = nullptr; | ||
22 | int real_x1 = 0; | ||
23 | int real_y1 = 0; | ||
24 | int real_x2 = 0; | ||
25 | int real_y2 = 0; | ||
26 | }; | ||
27 | |||
28 | void OnPaint(wxPaintEvent &event); | ||
29 | void OnMouseMove(wxMouseEvent &event); | ||
30 | |||
31 | void Redraw(); | ||
32 | |||
33 | wxImage map_image_; | ||
34 | wxBitmap rendered_; | ||
35 | |||
36 | std::vector<AreaIndicator> areas_; | ||
37 | }; | ||
38 | |||
39 | #endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ | ||
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp new file mode 100644 index 0000000..858ec3e --- /dev/null +++ b/src/tracker_state.cpp | |||
@@ -0,0 +1,181 @@ | |||
1 | #include "tracker_state.h" | ||
2 | |||
3 | #include <list> | ||
4 | #include <set> | ||
5 | |||
6 | #include "ap_state.h" | ||
7 | #include "game_data.h" | ||
8 | |||
9 | bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms); | ||
10 | |||
11 | bool IsPanelReachable_Helper(int panel_id, | ||
12 | const std::set<int>& reachable_rooms) { | ||
13 | const Panel& panel_obj = GetGameData().GetPanel(panel_id); | ||
14 | |||
15 | if (!reachable_rooms.count(panel_obj.room)) { | ||
16 | return false; | ||
17 | } | ||
18 | |||
19 | if (panel_obj.name == "THE MASTER") { | ||
20 | int achievements_accessible = 0; | ||
21 | |||
22 | for (int achieve_id : GetGameData().GetAchievementPanels()) { | ||
23 | if (IsPanelReachable_Helper(achieve_id, reachable_rooms)) { | ||
24 | achievements_accessible++; | ||
25 | |||
26 | if (achievements_accessible >= AP_GetMasteryRequirement()) { | ||
27 | break; | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | |||
32 | return (achievements_accessible >= AP_GetMasteryRequirement()); | ||
33 | } | ||
34 | |||
35 | for (int room_id : panel_obj.required_rooms) { | ||
36 | if (!reachable_rooms.count(room_id)) { | ||
37 | return false; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | for (int door_id : panel_obj.required_doors) { | ||
42 | if (!IsDoorReachable_Helper(door_id, reachable_rooms)) { | ||
43 | return false; | ||
44 | } | ||
45 | } | ||
46 | |||
47 | if (AP_IsColorShuffle()) { | ||
48 | for (LingoColor color : panel_obj.colors) { | ||
49 | if (!AP_HasColorItem(color)) { | ||
50 | return false; | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | |||
55 | return true; | ||
56 | } | ||
57 | |||
58 | bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms) { | ||
59 | const Door& door_obj = GetGameData().GetDoor(door_id); | ||
60 | |||
61 | if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { | ||
62 | if (!reachable_rooms.count(door_obj.room)) { | ||
63 | return false; | ||
64 | } | ||
65 | |||
66 | for (int panel_id : door_obj.panels) { | ||
67 | if (!IsPanelReachable_Helper(panel_id, reachable_rooms)) { | ||
68 | return false; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | return true; | ||
73 | } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && | ||
74 | !door_obj.group_name.empty()) { | ||
75 | return AP_HasItem(door_obj.group_name); | ||
76 | } else { | ||
77 | bool has_item = AP_HasItem(door_obj.item_name); | ||
78 | |||
79 | if (!has_item) { | ||
80 | for (const ProgressiveRequirement& prog_req : door_obj.progressives) { | ||
81 | if (AP_HasItem(prog_req.item_name, prog_req.quantity)) { | ||
82 | has_item = true; | ||
83 | break; | ||
84 | } | ||
85 | } | ||
86 | } | ||
87 | |||
88 | return has_item; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | void TrackerState::CalculateState() { | ||
93 | reachability_.clear(); | ||
94 | |||
95 | std::set<int> reachable_rooms; | ||
96 | |||
97 | std::list<Exit> flood_boundary; | ||
98 | flood_boundary.push_back( | ||
99 | {.destination_room = GetGameData().GetRoomByName("Menu")}); | ||
100 | |||
101 | bool reachable_changed = true; | ||
102 | while (reachable_changed) { | ||
103 | reachable_changed = false; | ||
104 | |||
105 | std::list<Exit> new_boundary; | ||
106 | for (const Exit& room_exit : flood_boundary) { | ||
107 | if (reachable_rooms.count(room_exit.destination_room)) { | ||
108 | continue; | ||
109 | } | ||
110 | |||
111 | bool valid_transition = false; | ||
112 | if (room_exit.door.has_value()) { | ||
113 | if (IsDoorReachable_Helper(*room_exit.door, reachable_rooms)) { | ||
114 | valid_transition = true; | ||
115 | } else if (AP_GetDoorShuffleMode() == kNO_DOORS) { | ||
116 | new_boundary.push_back(room_exit); | ||
117 | } | ||
118 | } else { | ||
119 | valid_transition = true; | ||
120 | } | ||
121 | |||
122 | if (valid_transition) { | ||
123 | reachable_rooms.insert(room_exit.destination_room); | ||
124 | reachable_changed = true; | ||
125 | |||
126 | const Room& room_obj = | ||
127 | GetGameData().GetRoom(room_exit.destination_room); | ||
128 | for (const Exit& out_edge : room_obj.exits) { | ||
129 | if (!out_edge.painting || !AP_IsPaintingShuffle()) { | ||
130 | new_boundary.push_back(out_edge); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | if (AP_IsPaintingShuffle()) { | ||
135 | for (const PaintingExit& out_edge : room_obj.paintings) { | ||
136 | if (AP_GetPaintingMapping().count(out_edge.id)) { | ||
137 | Exit painting_exit; | ||
138 | painting_exit.destination_room = GetGameData().GetRoomForPainting( | ||
139 | AP_GetPaintingMapping().at(out_edge.id)); | ||
140 | painting_exit.door = out_edge.door; | ||
141 | |||
142 | new_boundary.push_back(painting_exit); | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | flood_boundary = new_boundary; | ||
150 | } | ||
151 | |||
152 | for (const MapArea& map_area : GetGameData().GetMapAreas()) { | ||
153 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
154 | section_id++) { | ||
155 | const Location& location_section = map_area.locations.at(section_id); | ||
156 | bool reachable = reachable_rooms.count(location_section.room); | ||
157 | if (reachable) { | ||
158 | for (int panel_id : location_section.panels) { | ||
159 | reachable &= IsPanelReachable_Helper(panel_id, reachable_rooms); | ||
160 | } | ||
161 | } | ||
162 | |||
163 | reachability_[{map_area.id, section_id}] = reachable; | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | |||
168 | bool TrackerState::IsLocationReachable(int area_id, int section_id) { | ||
169 | std::tuple<int, int> key = {area_id, section_id}; | ||
170 | |||
171 | if (reachability_.count(key)) { | ||
172 | return reachability_.at(key); | ||
173 | } else { | ||
174 | return false; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | TrackerState& GetTrackerState() { | ||
179 | static TrackerState* instance = new TrackerState(); | ||
180 | return *instance; | ||
181 | } | ||
diff --git a/src/tracker_state.h b/src/tracker_state.h new file mode 100644 index 0000000..879e6f2 --- /dev/null +++ b/src/tracker_state.h | |||
@@ -0,0 +1,19 @@ | |||
1 | #ifndef TRACKER_STATE_H_8639BC90 | ||
2 | #define TRACKER_STATE_H_8639BC90 | ||
3 | |||
4 | #include <map> | ||
5 | #include <tuple> | ||
6 | |||
7 | class TrackerState { | ||
8 | public: | ||
9 | void CalculateState(); | ||
10 | |||
11 | bool IsLocationReachable(int area_id, int section_id); | ||
12 | |||
13 | private: | ||
14 | std::map<std::tuple<int, int>, bool> reachability_; | ||
15 | }; | ||
16 | |||
17 | TrackerState& GetTrackerState(); | ||
18 | |||
19 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | ||