about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2023-05-05 16:35:09 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2023-05-05 16:35:09 -0400
commitd7212d755dca7f4fd99cf4b775cd0d372d7bcbb2 (patch)
treea4ca0258eda334c4aa68073355b9f4ccd8a7bdef /src
parent017df1397ace9ab8c3f152362d07871dcb6858be (diff)
downloadlingo-ap-tracker-d7212d755dca7f4fd99cf4b775cd0d372d7bcbb2.tar.gz
lingo-ap-tracker-d7212d755dca7f4fd99cf4b775cd0d372d7bcbb2.tar.bz2
lingo-ap-tracker-d7212d755dca7f4fd99cf4b775cd0d372d7bcbb2.zip
Refactored away singletons
(Except TrackerConfig, for now at least)
Diffstat (limited to 'src')
-rw-r--r--src/ap_state.cpp476
-rw-r--r--src/area_popup.cpp7
-rw-r--r--src/game_data.cpp604
-rw-r--r--src/game_data.h59
-rw-r--r--src/tracker_panel.cpp6
-rw-r--r--src/tracker_state.cpp48
-rw-r--r--src/tracker_state.h15
7 files changed, 622 insertions, 593 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 69c9e9f..efbca8c 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -31,307 +31,333 @@ constexpr int ITEM_HANDLING = 7; // <- all
31 31
32namespace { 32namespace {
33 33
34APClient* apclient = nullptr; 34struct APState {
35 std::unique_ptr<APClient> apclient;
35 36
36bool initialized = false; 37 bool initialized = false;
37 38
38TrackerFrame* tracker_frame; 39 TrackerFrame* tracker_frame = nullptr;
39 40
40bool client_active = false; 41 bool client_active = false;
41std::mutex client_mutex; 42 std::mutex client_mutex;
42 43
43bool connected = false; 44 bool connected = false;
44bool has_connection_result = false; 45 bool has_connection_result = false;
45 46
46std::map<int64_t, int> inventory; 47 std::map<int64_t, int> inventory;
47std::set<int64_t> checked_locations; 48 std::set<int64_t> checked_locations;
48 49
49std::map<std::tuple<int, int>, int64_t> ap_id_by_location_id; 50 std::map<std::tuple<int, int>, int64_t> ap_id_by_location_id;
50std::map<std::string, int64_t> ap_id_by_item_name; 51 std::map<std::string, int64_t> ap_id_by_item_name;
51std::map<LingoColor, int64_t> ap_id_by_color; 52 std::map<LingoColor, int64_t> ap_id_by_color;
52std::map<int64_t, std::string> progressive_item_by_ap_id; 53 std::map<int64_t, std::string> progressive_item_by_ap_id;
53 54
54DoorShuffleMode door_shuffle_mode = kNO_DOORS; 55 DoorShuffleMode door_shuffle_mode = kNO_DOORS;
55bool color_shuffle = false; 56 bool color_shuffle = false;
56bool painting_shuffle = false; 57 bool painting_shuffle = false;
57int mastery_requirement = 21; 58 int mastery_requirement = 21;
58 59
59std::map<std::string, std::string> painting_mapping; 60 std::map<std::string, std::string> painting_mapping;
60 61
61void RefreshTracker() { 62 void Connect(std::string server, std::string player, std::string password) {
62 GetTrackerState().CalculateState(); 63 if (!initialized) {
63 tracker_frame->UpdateIndicators(); 64 std::thread([this]() {
64} 65 for (;;) {
65 66 {
66int64_t GetItemId(const std::string& item_name) { 67 std::lock_guard client_guard(client_mutex);
67 int64_t ap_id = apclient->get_item_id(item_name); 68 if (apclient) {
68 if (ap_id == APClient::INVALID_NAME_ID) { 69 apclient->poll();
69 std::cout << "Could not find AP item ID for " << item_name << std::endl; 70 }
70 } 71 }
71 72
72 return ap_id; 73 std::this_thread::sleep_for(std::chrono::milliseconds(100));
73} 74 }
75 }).detach();
74 76
75void DestroyClient() { 77 initialized = true;
76 client_active = false; 78 }
77 apclient->reset();
78 delete apclient;
79 apclient = nullptr;
80}
81 79
82} // namespace 80 tracker_frame->SetStatusMessage("Connecting to Archipelago server....");
83 81
84void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; } 82 {
83 std::lock_guard client_guard(client_mutex);
85 84
86void AP_Connect(std::string server, std::string player, std::string password) { 85 if (apclient) {
87 if (!initialized) { 86 DestroyClient();
88 std::thread([]() { 87 }
89 for (;;) {
90 {
91 std::lock_guard client_guard(client_mutex);
92 if (apclient) {
93 apclient->poll();
94 }
95 }
96 88
97 std::this_thread::sleep_for(std::chrono::milliseconds(100)); 89 std::string cert_store = "";
90 if (std::filesystem::exists(CERT_STORE_PATH)) {
91 cert_store = CERT_STORE_PATH;
98 } 92 }
99 }).detach();
100 93
101 initialized = true; 94 apclient = std::make_unique<APClient>(ap_get_uuid(""), "Lingo", server,
102 } 95 cert_store);
96 }
103 97
104 tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); 98 inventory.clear();
99 checked_locations.clear();
100 door_shuffle_mode = kNO_DOORS;
101 color_shuffle = false;
102 painting_shuffle = false;
103 painting_mapping.clear();
104 mastery_requirement = 21;
105 105
106 { 106 connected = false;
107 std::lock_guard client_guard(client_mutex); 107 has_connection_result = false;
108 108
109 if (apclient) { 109 apclient->set_room_info_handler([this, player, password]() {
110 DestroyClient(); 110 inventory.clear();
111 }
112 111
113 std::string cert_store = ""; 112 tracker_frame->SetStatusMessage(
114 if (std::filesystem::exists(CERT_STORE_PATH)) { 113 "Connected to Archipelago server. Authenticating...");
115 cert_store = CERT_STORE_PATH;
116 }
117 114
118 apclient = new APClient(ap_get_uuid(""), "Lingo", server, cert_store); 115 apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"},
119 } 116 {AP_MAJOR, AP_MINOR, AP_REVISION});
117 });
120 118
121 inventory.clear(); 119 apclient->set_location_checked_handler(
122 checked_locations.clear(); 120 [this](const std::list<int64_t>& locations) {
123 door_shuffle_mode = kNO_DOORS; 121 for (const int64_t location_id : locations) {
124 color_shuffle = false; 122 checked_locations.insert(location_id);
125 painting_shuffle = false; 123 std::cout << "Location: " << location_id << std::endl;
126 painting_mapping.clear(); 124 }
127 mastery_requirement = 21;
128 125
129 connected = false; 126 RefreshTracker();
130 has_connection_result = false; 127 });
131 128
132 apclient->set_room_info_handler([player, password]() { 129 apclient->set_slot_disconnected_handler([this]() {
133 inventory.clear(); 130 tracker_frame->SetStatusMessage(
131 "Disconnected from Archipelago. Attempting to reconnect...");
132 });
134 133
135 tracker_frame->SetStatusMessage( 134 apclient->set_socket_disconnected_handler([this]() {
136 "Connected to Archipelago server. Authenticating..."); 135 tracker_frame->SetStatusMessage(
136 "Disconnected from Archipelago. Attempting to reconnect...");
137 });
137 138
138 apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, 139 apclient->set_items_received_handler(
139 {AP_MAJOR, AP_MINOR, AP_REVISION}); 140 [this](const std::list<APClient::NetworkItem>& items) {
140 }); 141 for (const APClient::NetworkItem& item : items) {
142 inventory[item.item]++;
143 std::cout << "Item: " << item.item << std::endl;
144 }
141 145
142 apclient->set_location_checked_handler( 146 RefreshTracker();
143 [](const std::list<int64_t>& locations) { 147 });
144 for (const int64_t location_id : locations) {
145 checked_locations.insert(location_id);
146 std::cout << "Location: " << location_id << std::endl;
147 }
148 148
149 RefreshTracker(); 149 apclient->set_slot_connected_handler([this](
150 }); 150 const nlohmann::json& slot_data) {
151 tracker_frame->SetStatusMessage("Connected to Archipelago!");
151 152
152 apclient->set_slot_disconnected_handler([]() { 153 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>();
153 tracker_frame->SetStatusMessage( 154 color_shuffle = slot_data["shuffle_colors"].get<bool>();
154 "Disconnected from Archipelago. Attempting to reconnect..."); 155 painting_shuffle = slot_data["shuffle_paintings"].get<bool>();
155 }); 156 mastery_requirement = slot_data["mastery_achievements"].get<int>();
156 157
157 apclient->set_socket_disconnected_handler([]() { 158 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
158 tracker_frame->SetStatusMessage( 159 painting_mapping.clear();
159 "Disconnected from Archipelago. Attempting to reconnect...");
160 });
161 160
162 apclient->set_items_received_handler( 161 for (const auto& mapping_it :
163 [](const std::list<APClient::NetworkItem>& items) { 162 slot_data["painting_entrance_to_exit"].items()) {
164 for (const APClient::NetworkItem& item : items) { 163 painting_mapping[mapping_it.key()] = mapping_it.value();
165 inventory[item.item]++;
166 std::cout << "Item: " << item.item << std::endl;
167 } 164 }
165 }
168 166
169 RefreshTracker(); 167 connected = true;
170 }); 168 has_connection_result = true;
171 169
172 apclient->set_slot_connected_handler([](const nlohmann::json& slot_data) { 170 RefreshTracker();
173 tracker_frame->SetStatusMessage("Connected to Archipelago!"); 171 });
172
173 apclient->set_slot_refused_handler(
174 [this](const std::list<std::string>& errors) {
175 connected = false;
176 has_connection_result = true;
177
178 tracker_frame->SetStatusMessage("Disconnected from Archipelago.");
179
180 std::vector<std::string> error_messages;
181 error_messages.push_back("Could not connect to Archipelago.");
182
183 for (const std::string& error : errors) {
184 if (error == "InvalidSlot") {
185 error_messages.push_back("Invalid player name.");
186 } else if (error == "InvalidGame") {
187 error_messages.push_back(
188 "The specified player is not playing Lingo.");
189 } else if (error == "IncompatibleVersion") {
190 error_messages.push_back(
191 "The Archipelago server is not the correct version for this "
192 "client.");
193 } else if (error == "InvalidPassword") {
194 error_messages.push_back("Incorrect password.");
195 } else if (error == "InvalidItemsHandling") {
196 error_messages.push_back(
197 "Invalid item handling flag. This is a bug with the tracker. "
198 "Please report it to the lingo-ap-tracker GitHub.");
199 } else {
200 error_messages.push_back("Unknown error.");
201 }
202 }
174 203
175 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); 204 std::string full_message = hatkirby::implode(error_messages, " ");
176 color_shuffle = slot_data["shuffle_colors"].get<bool>();
177 painting_shuffle = slot_data["shuffle_paintings"].get<bool>();
178 mastery_requirement = slot_data["mastery_achievements"].get<int>();
179 205
180 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 206 wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR);
181 painting_mapping.clear(); 207 });
182 208
183 for (const auto& mapping_it : 209 client_active = true;
184 slot_data["painting_entrance_to_exit"].items()) {
185 painting_mapping[mapping_it.key()] = mapping_it.value();
186 }
187 }
188 210
189 connected = true; 211 int timeout = 5000; // 5 seconds
190 has_connection_result = true; 212 int interval = 100;
213 int remaining_loops = timeout / interval;
214 while (!has_connection_result) {
215 if (interval == 0) {
216 connected = false;
217 has_connection_result = true;
191 218
192 RefreshTracker(); 219 DestroyClient();
193 });
194 220
195 apclient->set_slot_refused_handler([](const std::list<std::string>& errors) { 221 tracker_frame->SetStatusMessage("Disconnected from Archipelago.");
196 connected = false; 222
197 has_connection_result = true; 223 wxMessageBox("Timeout while connecting to Archipelago server.",
198 224 "Connection failed", wxOK | wxICON_ERROR);
199 tracker_frame->SetStatusMessage("Disconnected from Archipelago.");
200
201 std::vector<std::string> error_messages;
202 error_messages.push_back("Could not connect to Archipelago.");
203
204 for (const std::string& error : errors) {
205 if (error == "InvalidSlot") {
206 error_messages.push_back("Invalid player name.");
207 } else if (error == "InvalidGame") {
208 error_messages.push_back("The specified player is not playing Lingo.");
209 } else if (error == "IncompatibleVersion") {
210 error_messages.push_back(
211 "The Archipelago server is not the correct version for this "
212 "client.");
213 } else if (error == "InvalidPassword") {
214 error_messages.push_back("Incorrect password.");
215 } else if (error == "InvalidItemsHandling") {
216 error_messages.push_back(
217 "Invalid item handling flag. This is a bug with the tracker. "
218 "Please report it to the lingo-ap-tracker GitHub.");
219 } else {
220 error_messages.push_back("Unknown error.");
221 } 225 }
222 }
223 226
224 std::string full_message = hatkirby::implode(error_messages, " "); 227 std::this_thread::sleep_for(std::chrono::milliseconds(100));
225 228
226 wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); 229 interval--;
227 }); 230 }
228 231
229 client_active = true; 232 if (connected) {
233 for (const MapArea& map_area : GD_GetMapAreas()) {
234 for (int section_id = 0; section_id < map_area.locations.size();
235 section_id++) {
236 const Location& location = map_area.locations.at(section_id);
237
238 int64_t ap_id = apclient->get_location_id(location.ap_location_name);
239 if (ap_id == APClient::INVALID_NAME_ID) {
240 std::cout << "Could not find AP location ID for "
241 << location.ap_location_name << std::endl;
242 } else {
243 ap_id_by_location_id[{map_area.id, section_id}] = ap_id;
244 }
245 }
246 }
230 247
231 int timeout = 5000; // 5 seconds 248 for (const Door& door : GD_GetDoors()) {
232 int interval = 100; 249 if (!door.skip_item) {
233 int remaining_loops = timeout / interval; 250 ap_id_by_item_name[door.item_name] = GetItemId(door.item_name);
234 while (!has_connection_result) {
235 if (interval == 0) {
236 connected = false;
237 has_connection_result = true;
238 251
239 DestroyClient(); 252 if (!door.group_name.empty() &&
253 !ap_id_by_item_name.count(door.group_name)) {
254 ap_id_by_item_name[door.group_name] = GetItemId(door.group_name);
255 }
240 256
241 tracker_frame->SetStatusMessage("Disconnected from Archipelago."); 257 for (const ProgressiveRequirement& prog_req : door.progressives) {
258 ap_id_by_item_name[prog_req.item_name] =
259 GetItemId(prog_req.item_name);
260 }
261 }
262 }
242 263
243 wxMessageBox("Timeout while connecting to Archipelago server.", 264 ap_id_by_color[LingoColor::kBlack] = GetItemId("Black");
244 "Connection failed", wxOK | wxICON_ERROR); 265 ap_id_by_color[LingoColor::kRed] = GetItemId("Red");
266 ap_id_by_color[LingoColor::kBlue] = GetItemId("Blue");
267 ap_id_by_color[LingoColor::kYellow] = GetItemId("Yellow");
268 ap_id_by_color[LingoColor::kPurple] = GetItemId("Purple");
269 ap_id_by_color[LingoColor::kOrange] = GetItemId("Orange");
270 ap_id_by_color[LingoColor::kGreen] = GetItemId("Green");
271 ap_id_by_color[LingoColor::kBrown] = GetItemId("Brown");
272 ap_id_by_color[LingoColor::kGray] = GetItemId("Gray");
273
274 RefreshTracker();
275 } else {
276 client_active = false;
245 } 277 }
278 }
246 279
247 std::this_thread::sleep_for(std::chrono::milliseconds(100)); 280 bool HasCheckedGameLocation(int area_id, int section_id) {
281 std::tuple<int, int> location_key = {area_id, section_id};
248 282
249 interval--; 283 if (ap_id_by_location_id.count(location_key)) {
284 return checked_locations.count(ap_id_by_location_id.at(location_key));
285 } else {
286 return false;
287 }
250 } 288 }
251 289
252 if (connected) { 290 bool HasColorItem(LingoColor color) {
253 for (const MapArea& map_area : GetGameData().GetMapAreas()) { 291 if (ap_id_by_color.count(color)) {
254 for (int section_id = 0; section_id < map_area.locations.size(); 292 return inventory.count(ap_id_by_color.at(color));
255 section_id++) { 293 } else {
256 const Location& location = map_area.locations.at(section_id); 294 return false;
257
258 int64_t ap_id = apclient->get_location_id(location.ap_location_name);
259 if (ap_id == APClient::INVALID_NAME_ID) {
260 std::cout << "Could not find AP location ID for "
261 << location.ap_location_name << std::endl;
262 } else {
263 ap_id_by_location_id[{map_area.id, section_id}] = ap_id;
264 }
265 }
266 } 295 }
296 }
267 297
268 for (const Door& door : GetGameData().GetDoors()) { 298 bool HasItem(const std::string& item, int quantity) {
269 if (!door.skip_item) { 299 if (ap_id_by_item_name.count(item)) {
270 ap_id_by_item_name[door.item_name] = GetItemId(door.item_name); 300 int64_t ap_id = ap_id_by_item_name.at(item);
301 return inventory.count(ap_id) && inventory.at(ap_id) >= quantity;
302 } else {
303 return false;
304 }
305 }
271 306
272 if (!door.group_name.empty() && 307 void RefreshTracker() {
273 !ap_id_by_item_name.count(door.group_name)) { 308 RecalculateReachability();
274 ap_id_by_item_name[door.group_name] = GetItemId(door.group_name); 309 tracker_frame->UpdateIndicators();
275 } 310 }
276 311
277 for (const ProgressiveRequirement& prog_req : door.progressives) { 312 int64_t GetItemId(const std::string& item_name) {
278 ap_id_by_item_name[prog_req.item_name] = 313 int64_t ap_id = apclient->get_item_id(item_name);
279 GetItemId(prog_req.item_name); 314 if (ap_id == APClient::INVALID_NAME_ID) {
280 } 315 std::cout << "Could not find AP item ID for " << item_name << std::endl;
281 }
282 } 316 }
283 317
284 ap_id_by_color[LingoColor::kBlack] = GetItemId("Black"); 318 return ap_id;
285 ap_id_by_color[LingoColor::kRed] = GetItemId("Red"); 319 }
286 ap_id_by_color[LingoColor::kBlue] = GetItemId("Blue"); 320
287 ap_id_by_color[LingoColor::kYellow] = GetItemId("Yellow"); 321 void DestroyClient() {
288 ap_id_by_color[LingoColor::kPurple] = GetItemId("Purple");
289 ap_id_by_color[LingoColor::kOrange] = GetItemId("Orange");
290 ap_id_by_color[LingoColor::kGreen] = GetItemId("Green");
291 ap_id_by_color[LingoColor::kBrown] = GetItemId("Brown");
292 ap_id_by_color[LingoColor::kGray] = GetItemId("Gray");
293
294 RefreshTracker();
295 } else {
296 client_active = false; 322 client_active = false;
323 apclient->reset();
324 apclient.reset();
297 } 325 }
326};
327
328APState& GetState() {
329 static APState* instance = new APState();
330 return *instance;
298} 331}
299 332
300bool AP_HasCheckedGameLocation(int area_id, int section_id) { 333} // namespace
301 std::tuple<int, int> location_key = {area_id, section_id};
302 334
303 if (ap_id_by_location_id.count(location_key)) { 335void AP_SetTrackerFrame(TrackerFrame* arg) { GetState().tracker_frame = arg; }
304 return checked_locations.count(ap_id_by_location_id.at(location_key)); 336
305 } else { 337void AP_Connect(std::string server, std::string player, std::string password) {
306 return false; 338 GetState().Connect(server, player, password);
307 } 339}
340
341bool AP_HasCheckedGameLocation(int area_id, int section_id) {
342 return GetState().HasCheckedGameLocation(area_id, section_id);
308} 343}
309 344
310bool AP_HasColorItem(LingoColor color) { 345bool AP_HasColorItem(LingoColor color) {
311 if (ap_id_by_color.count(color)) { 346 return GetState().HasColorItem(color);
312 return inventory.count(ap_id_by_color.at(color));
313 } else {
314 return false;
315 }
316} 347}
317 348
318bool AP_HasItem(const std::string& item, int quantity) { 349bool AP_HasItem(const std::string& item, int quantity) {
319 if (ap_id_by_item_name.count(item)) { 350 return GetState().HasItem(item, quantity);
320 int64_t ap_id = ap_id_by_item_name.at(item);
321 return inventory.count(ap_id) && inventory.at(ap_id) >= quantity;
322 } else {
323 return false;
324 }
325} 351}
326 352
327DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; } 353DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; }
328 354
329bool AP_IsColorShuffle() { return color_shuffle; } 355bool AP_IsColorShuffle() { return GetState().color_shuffle; }
330 356
331bool AP_IsPaintingShuffle() { return painting_shuffle; } 357bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; }
332 358
333const std::map<std::string, std::string> AP_GetPaintingMapping() { 359const std::map<std::string, std::string> AP_GetPaintingMapping() {
334 return painting_mapping; 360 return GetState().painting_mapping;
335} 361}
336 362
337int AP_GetMasteryRequirement() { return mastery_requirement; } 363int AP_GetMasteryRequirement() { return GetState().mastery_requirement; }
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index a8ff612..88dffe0 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp
@@ -6,7 +6,7 @@
6 6
7AreaPopup::AreaPopup(wxWindow* parent, int area_id) 7AreaPopup::AreaPopup(wxWindow* parent, int area_id)
8 : wxPanel(parent, wxID_ANY), area_id_(area_id) { 8 : wxPanel(parent, wxID_ANY), area_id_(area_id) {
9 const MapArea& map_area = GetGameData().GetMapArea(area_id); 9 const MapArea& map_area = GD_GetMapArea(area_id);
10 10
11 wxFlexGridSizer* section_sizer = new wxFlexGridSizer(2, 10, 10); 11 wxFlexGridSizer* section_sizer = new wxFlexGridSizer(2, 10, 10);
12 12
@@ -40,12 +40,11 @@ AreaPopup::AreaPopup(wxWindow* parent, int area_id)
40} 40}
41 41
42void AreaPopup::UpdateIndicators() { 42void AreaPopup::UpdateIndicators() {
43 const MapArea& map_area = GetGameData().GetMapArea(area_id_); 43 const MapArea& map_area = GD_GetMapArea(area_id_);
44 for (int section_id = 0; section_id < map_area.locations.size(); 44 for (int section_id = 0; section_id < map_area.locations.size();
45 section_id++) { 45 section_id++) {
46 bool checked = AP_HasCheckedGameLocation(area_id_, section_id); 46 bool checked = AP_HasCheckedGameLocation(area_id_, section_id);
47 bool reachable = 47 bool reachable = IsLocationReachable(area_id_, section_id);
48 GetTrackerState().IsLocationReachable(area_id_, section_id);
49 const wxColour* text_color = reachable ? wxWHITE : wxRED; 48 const wxColour* text_color = reachable ? wxWHITE : wxRED;
50 49
51 section_labels_[section_id]->SetForegroundColour(*text_color); 50 section_labels_[section_id]->SetForegroundColour(*text_color);
diff --git a/src/game_data.cpp b/src/game_data.cpp index e15847e..8c6dd26 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -5,6 +5,8 @@
5 5
6#include <iostream> 6#include <iostream>
7 7
8namespace {
9
8LingoColor GetColorForString(const std::string &str) { 10LingoColor GetColorForString(const std::string &str) {
9 if (str == "black") { 11 if (str == "black") {
10 return LingoColor::kBlack; 12 return LingoColor::kBlack;
@@ -30,377 +32,423 @@ LingoColor GetColorForString(const std::string &str) {
30 } 32 }
31} 33}
32 34
33GameData::GameData() { 35struct GameData {
34 YAML::Node lingo_config = YAML::LoadFile("assets/LL1.yaml"); 36 std::vector<Room> rooms_;
35 YAML::Node areas_config = YAML::LoadFile("assets/areas.yaml"); 37 std::vector<Door> doors_;
38 std::vector<Panel> panels_;
39 std::vector<MapArea> map_areas_;
36 40
37 rooms_.reserve(lingo_config.size() * 2); 41 std::map<std::string, int> room_by_id_;
42 std::map<std::string, int> door_by_id_;
43 std::map<std::string, int> panel_by_id_;
44 std::map<std::string, int> area_by_id_;
38 45
39 for (const auto &room_it : lingo_config) { 46 std::map<std::string, int> room_by_painting_;
40 int room_id = AddOrGetRoom(room_it.first.as<std::string>());
41 Room &room_obj = rooms_[room_id];
42 47
43 for (const auto &entrance_it : room_it.second["entrances"]) { 48 std::vector<int> achievement_panels_;
44 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
45 Room &from_room_obj = rooms_[from_room_id];
46 49
47 switch (entrance_it.second.Type()) { 50 GameData() {
48 case YAML::NodeType::Scalar: { 51 YAML::Node lingo_config = YAML::LoadFile("assets/LL1.yaml");
49 // This is just "true". 52 YAML::Node areas_config = YAML::LoadFile("assets/areas.yaml");
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 53
66 if (entrance_it.second["painting"]) { 54 rooms_.reserve(lingo_config.size() * 2);
67 exit_obj.painting = entrance_it.second["painting"].as<bool>();
68 }
69 55
70 from_room_obj.exits.push_back(exit_obj); 56 for (const auto &room_it : lingo_config) {
71 break; 57 int room_id = AddOrGetRoom(room_it.first.as<std::string>());
72 } 58 Room &room_obj = rooms_[room_id];
73 case YAML::NodeType::Sequence: { 59
74 for (const auto &option : entrance_it.second) { 60 for (const auto &entrance_it : room_it.second["entrances"]) {
61 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
62 Room &from_room_obj = rooms_[from_room_id];
63
64 switch (entrance_it.second.Type()) {
65 case YAML::NodeType::Scalar: {
66 // This is just "true".
67 from_room_obj.exits.push_back({.destination_room = room_id});
68 break;
69 }
70 case YAML::NodeType::Map: {
75 Exit exit_obj; 71 Exit exit_obj;
76 exit_obj.destination_room = room_id; 72 exit_obj.destination_room = room_id;
77 73
78 std::string door_room = room_obj.name; 74 if (entrance_it.second["door"]) {
79 if (option["room"]) { 75 std::string door_room = room_obj.name;
80 door_room = option["room"].as<std::string>(); 76 if (entrance_it.second["room"]) {
77 door_room = entrance_it.second["room"].as<std::string>();
78 }
79 exit_obj.door = AddOrGetDoor(
80 door_room, entrance_it.second["door"].as<std::string>());
81 } 81 }
82 exit_obj.door =
83 AddOrGetDoor(door_room, option["door"].as<std::string>());
84 82
85 if (option["painting"]) { 83 if (entrance_it.second["painting"]) {
86 exit_obj.painting = option["painting"].as<bool>(); 84 exit_obj.painting = entrance_it.second["painting"].as<bool>();
87 } 85 }
88 86
89 from_room_obj.exits.push_back(exit_obj); 87 from_room_obj.exits.push_back(exit_obj);
88 break;
90 } 89 }
90 case YAML::NodeType::Sequence: {
91 for (const auto &option : entrance_it.second) {
92 Exit exit_obj;
93 exit_obj.destination_room = room_id;
94
95 std::string door_room = room_obj.name;
96 if (option["room"]) {
97 door_room = option["room"].as<std::string>();
98 }
99 exit_obj.door =
100 AddOrGetDoor(door_room, option["door"].as<std::string>());
91 101
92 break; 102 if (option["painting"]) {
93 } 103 exit_obj.painting = option["painting"].as<bool>();
94 default: { 104 }
95 // This shouldn't happen.
96 std::cout << "Error reading game data: " << entrance_it << std::endl;
97 break;
98 }
99 }
100 }
101 105
102 if (room_it.second["panels"]) { 106 from_room_obj.exits.push_back(exit_obj);
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 } 107 }
108
109 break;
110 }
111 default: {
112 // This shouldn't happen.
113 std::cout << "Error reading game data: " << entrance_it
114 << std::endl;
115 break;
117 } 116 }
118 } 117 }
118 }
119 119
120 if (panel_it.second["required_room"]) { 120 if (room_it.second["panels"]) {
121 if (panel_it.second["required_room"].IsScalar()) { 121 for (const auto &panel_it : room_it.second["panels"]) {
122 panel_obj.required_rooms.push_back(AddOrGetRoom( 122 int panel_id =
123 panel_it.second["required_room"].as<std::string>())); 123 AddOrGetPanel(room_obj.name, panel_it.first.as<std::string>());
124 } else { 124 Panel &panel_obj = panels_[panel_id];
125 for (const auto &rr_node : panel_it.second["required_room"]) { 125
126 panel_obj.required_rooms.push_back( 126 if (panel_it.second["colors"]) {
127 AddOrGetRoom(rr_node.as<std::string>())); 127 if (panel_it.second["colors"].IsScalar()) {
128 panel_obj.colors.push_back(GetColorForString(
129 panel_it.second["colors"].as<std::string>()));
130 } else {
131 for (const auto &color_node : panel_it.second["colors"]) {
132 panel_obj.colors.push_back(
133 GetColorForString(color_node.as<std::string>()));
134 }
128 } 135 }
129 } 136 }
130 }
131 137
132 if (panel_it.second["required_door"]) { 138 if (panel_it.second["required_room"]) {
133 if (panel_it.second["required_door"].IsMap()) { 139 if (panel_it.second["required_room"].IsScalar()) {
134 std::string rd_room = room_obj.name; 140 panel_obj.required_rooms.push_back(AddOrGetRoom(
135 if (panel_it.second["required_door"]["room"]) { 141 panel_it.second["required_room"].as<std::string>()));
136 rd_room = 142 } else {
137 panel_it.second["required_door"]["room"].as<std::string>(); 143 for (const auto &rr_node : panel_it.second["required_room"]) {
144 panel_obj.required_rooms.push_back(
145 AddOrGetRoom(rr_node.as<std::string>()));
146 }
138 } 147 }
148 }
139 149
140 panel_obj.required_doors.push_back(AddOrGetDoor( 150 if (panel_it.second["required_door"]) {
141 rd_room, 151 if (panel_it.second["required_door"].IsMap()) {
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; 152 std::string rd_room = room_obj.name;
146 if (rr_node["room"]) { 153 if (panel_it.second["required_door"]["room"]) {
147 rd_room = rr_node["room"].as<std::string>(); 154 rd_room =
155 panel_it.second["required_door"]["room"].as<std::string>();
148 } 156 }
149 157
150 panel_obj.required_doors.push_back( 158 panel_obj.required_doors.push_back(AddOrGetDoor(
151 AddOrGetDoor(rd_room, rr_node["door"].as<std::string>())); 159 rd_room,
160 panel_it.second["required_door"]["door"].as<std::string>()));
161 } else {
162 for (const auto &rr_node : panel_it.second["required_door"]) {
163 std::string rd_room = room_obj.name;
164 if (rr_node["room"]) {
165 rd_room = rr_node["room"].as<std::string>();
166 }
167
168 panel_obj.required_doors.push_back(
169 AddOrGetDoor(rd_room, rr_node["door"].as<std::string>()));
170 }
152 } 171 }
153 } 172 }
154 }
155 173
156 if (panel_it.second["check"]) { 174 if (panel_it.second["check"]) {
157 panel_obj.check = panel_it.second["check"].as<bool>(); 175 panel_obj.check = panel_it.second["check"].as<bool>();
158 } 176 }
159 177
160 if (panel_it.second["achievement"]) { 178 if (panel_it.second["achievement"]) {
161 panel_obj.achievement = panel_it.second["achievement"].as<bool>(); 179 panel_obj.achievement = panel_it.second["achievement"].as<bool>();
162 180
163 if (panel_obj.achievement) { 181 if (panel_obj.achievement) {
164 achievement_panels_.push_back(panel_id); 182 achievement_panels_.push_back(panel_id);
183 }
165 } 184 }
166 }
167 185
168 if (panel_it.second["exclude_reduce"]) { 186 if (panel_it.second["exclude_reduce"]) {
169 panel_obj.exclude_reduce = 187 panel_obj.exclude_reduce =
170 panel_it.second["exclude_reduce"].as<bool>(); 188 panel_it.second["exclude_reduce"].as<bool>();
189 }
171 } 190 }
172 } 191 }
173 }
174 192
175 if (room_it.second["doors"]) { 193 if (room_it.second["doors"]) {
176 for (const auto &door_it : room_it.second["doors"]) { 194 for (const auto &door_it : room_it.second["doors"]) {
177 int door_id = 195 int door_id =
178 AddOrGetDoor(room_obj.name, door_it.first.as<std::string>()); 196 AddOrGetDoor(room_obj.name, door_it.first.as<std::string>());
179 Door &door_obj = doors_[door_id]; 197 Door &door_obj = doors_[door_id];
180 198
181 bool has_external_panels = false; 199 bool has_external_panels = false;
182 std::vector<std::string> panel_names; 200 std::vector<std::string> panel_names;
183 201
184 for (const auto &panel_node : door_it.second["panels"]) { 202 for (const auto &panel_node : door_it.second["panels"]) {
185 if (panel_node.IsScalar()) { 203 if (panel_node.IsScalar()) {
186 panel_names.push_back(panel_node.as<std::string>()); 204 panel_names.push_back(panel_node.as<std::string>());
187 door_obj.panels.push_back( 205 door_obj.panels.push_back(
188 AddOrGetPanel(room_obj.name, panel_node.as<std::string>())); 206 AddOrGetPanel(room_obj.name, panel_node.as<std::string>()));
189 } else { 207 } else {
190 has_external_panels = true; 208 has_external_panels = true;
191 panel_names.push_back(panel_node["panel"].as<std::string>()); 209 panel_names.push_back(panel_node["panel"].as<std::string>());
192 door_obj.panels.push_back( 210 door_obj.panels.push_back(
193 AddOrGetPanel(panel_node["room"].as<std::string>(), 211 AddOrGetPanel(panel_node["room"].as<std::string>(),
194 panel_node["panel"].as<std::string>())); 212 panel_node["panel"].as<std::string>()));
213 }
195 } 214 }
196 }
197 215
198 if (door_it.second["skip_location"]) { 216 if (door_it.second["skip_location"]) {
199 door_obj.skip_location = door_it.second["skip_location"].as<bool>(); 217 door_obj.skip_location = door_it.second["skip_location"].as<bool>();
200 } 218 }
201 219
202 if (door_it.second["skip_item"]) { 220 if (door_it.second["skip_item"]) {
203 door_obj.skip_item = door_it.second["skip_item"].as<bool>(); 221 door_obj.skip_item = door_it.second["skip_item"].as<bool>();
204 } 222 }
205 223
206 if (door_it.second["event"]) { 224 if (door_it.second["event"]) {
207 door_obj.skip_location = door_it.second["event"].as<bool>(); 225 door_obj.skip_location = door_it.second["event"].as<bool>();
208 door_obj.skip_item = door_it.second["event"].as<bool>(); 226 door_obj.skip_item = door_it.second["event"].as<bool>();
209 } 227 }
210 228
211 if (door_it.second["item_name"]) { 229 if (door_it.second["item_name"]) {
212 door_obj.item_name = door_it.second["item_name"].as<std::string>(); 230 door_obj.item_name = door_it.second["item_name"].as<std::string>();
213 } else if (!door_it.second["skip_item"] && !door_it.second["event"]) { 231 } else if (!door_it.second["skip_item"] && !door_it.second["event"]) {
214 door_obj.item_name = room_obj.name + " - " + door_obj.name; 232 door_obj.item_name = room_obj.name + " - " + door_obj.name;
215 } 233 }
216 234
217 if (door_it.second["group"]) { 235 if (door_it.second["group"]) {
218 door_obj.group_name = door_it.second["group"].as<std::string>(); 236 door_obj.group_name = door_it.second["group"].as<std::string>();
219 } 237 }
238
239 if (door_it.second["location_name"]) {
240 door_obj.location_name =
241 door_it.second["location_name"].as<std::string>();
242 } else if (!door_it.second["skip_location"] &&
243 !door_it.second["event"]) {
244 if (has_external_panels) {
245 std::cout
246 << room_obj.name << " - " << door_obj.name
247 << " has panels from other rooms but does not have an "
248 "explicit "
249 "location name and is not marked skip_location or event"
250 << std::endl;
251 }
220 252
221 if (door_it.second["location_name"]) { 253 door_obj.location_name =
222 door_obj.location_name = 254 room_obj.name + " - " + hatkirby::implode(panel_names, ", ");
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 } 255 }
233 256
234 door_obj.location_name = 257 if (door_it.second["include_reduce"]) {
235 room_obj.name + " - " + hatkirby::implode(panel_names, ", "); 258 door_obj.exclude_reduce =
259 !door_it.second["include_reduce"].as<bool>();
260 }
236 } 261 }
262 }
237 263
238 if (door_it.second["include_reduce"]) { 264 if (room_it.second["paintings"]) {
239 door_obj.exclude_reduce = 265 for (const auto &painting : room_it.second["paintings"]) {
240 !door_it.second["include_reduce"].as<bool>(); 266 std::string painting_id = painting["id"].as<std::string>();
267 room_by_painting_[painting_id] = room_id;
268
269 if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) {
270 PaintingExit painting_exit;
271 painting_exit.id = painting_id;
272
273 if (painting["required_door"]) {
274 std::string rd_room = room_obj.name;
275 if (painting["required_door"]["room"]) {
276 rd_room = painting["required_door"]["room"].as<std::string>();
277 }
278
279 painting_exit.door = AddOrGetDoor(
280 rd_room, painting["required_door"]["door"].as<std::string>());
281 }
282
283 room_obj.paintings.push_back(painting_exit);
284 }
241 } 285 }
242 } 286 }
243 }
244 287
245 if (room_it.second["paintings"]) { 288 if (room_it.second["progression"]) {
246 for (const auto &painting : room_it.second["paintings"]) { 289 for (const auto &progression_it : room_it.second["progression"]) {
247 std::string painting_id = painting["id"].as<std::string>(); 290 std::string progressive_item_name =
248 room_by_painting_[painting_id] = room_id; 291 progression_it.first.as<std::string>();
249 292
250 if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { 293 int index = 1;
251 PaintingExit painting_exit; 294 for (const auto &stage : progression_it.second) {
252 painting_exit.id = painting_id; 295 int door_id = -1;
253 296
254 if (painting["required_door"]) { 297 if (stage.IsScalar()) {
255 std::string rd_room = room_obj.name; 298 door_id = AddOrGetDoor(room_obj.name, stage.as<std::string>());
256 if (painting["required_door"]["room"]) { 299 } else {
257 rd_room = painting["required_door"]["room"].as<std::string>(); 300 door_id = AddOrGetDoor(stage["room"].as<std::string>(),
301 stage["door"].as<std::string>());
258 } 302 }
259 303
260 painting_exit.door = AddOrGetDoor( 304 doors_[door_id].progressives.push_back(
261 rd_room, painting["required_door"]["door"].as<std::string>()); 305 {.item_name = progressive_item_name, .quantity = index});
306 index++;
262 } 307 }
263
264 room_obj.paintings.push_back(painting_exit);
265 } 308 }
266 } 309 }
267 } 310 }
268 311
269 if (room_it.second["progression"]) { 312 map_areas_.reserve(areas_config.size());
270 for (const auto &progression_it : room_it.second["progression"]) { 313
271 std::string progressive_item_name = 314 std::map<std::string, int> fold_areas;
272 progression_it.first.as<std::string>(); 315 for (const auto &area_it : areas_config) {
316 if (area_it.second["map"]) {
317 int area_id = AddOrGetArea(area_it.first.as<std::string>());
318 MapArea &area_obj = map_areas_[area_id];
319 area_obj.map_x = area_it.second["map"][0].as<int>();
320 area_obj.map_y = area_it.second["map"][1].as<int>();
321 } else if (area_it.second["fold_into"]) {
322 fold_areas[area_it.first.as<std::string>()] =
323 AddOrGetArea(area_it.second["fold_into"].as<std::string>());
324 }
325 }
273 326
274 int index = 1; 327 for (const Panel &panel : panels_) {
275 for (const auto &stage : progression_it.second) { 328 if (panel.check) {
276 int door_id = -1; 329 int room_id = panel.room;
330 std::string room_name = rooms_[room_id].name;
277 331
278 if (stage.IsScalar()) { 332 std::string area_name = room_name;
279 door_id = AddOrGetDoor(room_obj.name, stage.as<std::string>()); 333 if (fold_areas.count(room_name)) {
280 } else { 334 int fold_area_id = fold_areas[room_name];
281 door_id = AddOrGetDoor(stage["room"].as<std::string>(), 335 area_name = map_areas_[fold_area_id].name;
282 stage["door"].as<std::string>()); 336 }
283 } 337
338 int area_id = AddOrGetArea(area_name);
339 MapArea &map_area = map_areas_[area_id];
340 // room field should be the original room ID
341 map_area.locations.push_back(
342 {.name = panel.name,
343 .ap_location_name = room_name + " - " + panel.name,
344 .room = panel.room,
345 .panels = {panel.id}});
346 }
347 }
348
349 for (const Door &door : doors_) {
350 if (!door.skip_location) {
351 int room_id = door.room;
352 std::string area_name = rooms_[room_id].name;
353 std::string section_name;
354
355 size_t divider_pos = door.location_name.find(" - ");
356 if (divider_pos == std::string::npos) {
357 section_name = door.location_name;
358 } else {
359 area_name = door.location_name.substr(0, divider_pos);
360 section_name = door.location_name.substr(divider_pos + 3);
361 }
284 362
285 doors_[door_id].progressives.push_back( 363 if (fold_areas.count(area_name)) {
286 {.item_name = progressive_item_name, .quantity = index}); 364 int fold_area_id = fold_areas[area_name];
287 index++; 365 area_name = map_areas_[fold_area_id].name;
288 } 366 }
367
368 int area_id = AddOrGetArea(area_name);
369 MapArea &map_area = map_areas_[area_id];
370 // room field should be the original room ID
371 map_area.locations.push_back({.name = section_name,
372 .ap_location_name = door.location_name,
373 .room = door.room,
374 .panels = door.panels});
289 } 375 }
290 } 376 }
291 } 377 }
292 378
293 map_areas_.reserve(areas_config.size()); 379 int AddOrGetRoom(std::string room) {
294 380 if (!room_by_id_.count(room)) {
295 std::map<std::string, int> fold_areas; 381 room_by_id_[room] = rooms_.size();
296 for (const auto &area_it : areas_config) { 382 rooms_.push_back({.name = room});
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 } 383 }
306 }
307 384
308 for (const Panel &panel : panels_) { 385 return room_by_id_[room];
309 if (panel.check) { 386 }
310 int room_id = panel.room;
311 std::string room_name = rooms_[room_id].name;
312 387
313 std::string area_name = room_name; 388 int AddOrGetDoor(std::string room, std::string door) {
314 if (fold_areas.count(room_name)) { 389 std::string full_name = room + " - " + door;
315 int fold_area_id = fold_areas[room_name];
316 area_name = map_areas_[fold_area_id].name;
317 }
318 390
319 int area_id = AddOrGetArea(area_name); 391 if (!door_by_id_.count(full_name)) {
320 MapArea &map_area = map_areas_[area_id]; 392 door_by_id_[full_name] = doors_.size();
321 // room field should be the original room ID 393 doors_.push_back({.room = AddOrGetRoom(room), .name = door});
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 } 394 }
328 }
329 395
330 for (const Door &door : doors_) { 396 return door_by_id_[full_name];
331 if (!door.skip_location) { 397 }
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 398
344 if (fold_areas.count(area_name)) { 399 int AddOrGetPanel(std::string room, std::string panel) {
345 int fold_area_id = fold_areas[area_name]; 400 std::string full_name = room + " - " + panel;
346 area_name = map_areas_[fold_area_id].name;
347 }
348 401
349 int area_id = AddOrGetArea(area_name); 402 if (!panel_by_id_.count(full_name)) {
350 MapArea &map_area = map_areas_[area_id]; 403 int panel_id = panels_.size();
351 // room field should be the original room ID 404 panel_by_id_[full_name] = panel_id;
352 map_area.locations.push_back({.name = section_name, 405 panels_.push_back(
353 .ap_location_name = door.location_name, 406 {.id = panel_id, .room = AddOrGetRoom(room), .name = panel});
354 .room = door.room,
355 .panels = door.panels});
356 } 407 }
408
409 return panel_by_id_[full_name];
357 } 410 }
358}
359 411
360int GameData::AddOrGetRoom(std::string room) { 412 int AddOrGetArea(std::string area) {
361 if (!room_by_id_.count(room)) { 413 if (!area_by_id_.count(area)) {
362 room_by_id_[room] = rooms_.size(); 414 int area_id = map_areas_.size();
363 rooms_.push_back({.name = room}); 415 area_by_id_[area] = area_id;
416 map_areas_.push_back({.id = area_id, .name = area});
417 }
418
419 return area_by_id_[area];
364 } 420 }
421};
365 422
366 return room_by_id_[room]; 423GameData &GetState() {
424 static GameData *instance = new GameData();
425 return *instance;
367} 426}
368 427
369int GameData::AddOrGetDoor(std::string room, std::string door) { 428} // namespace
370 std::string full_name = room + " - " + door;
371 429
372 if (!door_by_id_.count(full_name)) { 430const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; }
373 door_by_id_[full_name] = doors_.size(); 431
374 doors_.push_back({.room = AddOrGetRoom(room), .name = door}); 432const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); }
375 }
376 433
377 return door_by_id_[full_name]; 434int GD_GetRoomByName(const std::string &name) {
435 return GetState().room_by_id_.at(name);
378} 436}
379 437
380int GameData::AddOrGetPanel(std::string room, std::string panel) { 438const Room &GD_GetRoom(int room_id) { return GetState().rooms_.at(room_id); }
381 std::string full_name = room + " - " + panel;
382 439
383 if (!panel_by_id_.count(full_name)) { 440const std::vector<Door> &GD_GetDoors() { return GetState().doors_; }
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 441
390 return panel_by_id_[full_name]; 442const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); }
391}
392 443
393int GameData::AddOrGetArea(std::string area) { 444const Panel &GD_GetPanel(int panel_id) {
394 if (!area_by_id_.count(area)) { 445 return GetState().panels_.at(panel_id);
395 int area_id = map_areas_.size(); 446}
396 area_by_id_[area] = area_id;
397 map_areas_.push_back({.id = area_id, .name = area});
398 }
399 447
400 return area_by_id_[area]; 448int GD_GetRoomForPainting(const std::string &painting_id) {
449 return GetState().room_by_painting_.at(painting_id);
401} 450}
402 451
403const GameData &GetGameData() { 452const std::vector<int> &GD_GetAchievementPanels() {
404 static GameData *instance = new GameData(); 453 return GetState().achievement_panels_;
405 return *instance;
406} 454}
diff --git a/src/game_data.h b/src/game_data.h index 0cc7a7b..75eede3 100644 --- a/src/game_data.h +++ b/src/game_data.h
@@ -81,55 +81,14 @@ struct MapArea {
81 int map_y; 81 int map_y;
82}; 82};
83 83
84class GameData { 84const std::vector<MapArea>& GD_GetMapAreas();
85 public: 85const MapArea& GD_GetMapArea(int id);
86 GameData(); 86int GD_GetRoomByName(const std::string& name);
87 87const Room& GD_GetRoom(int room_id);
88 const std::vector<MapArea>& GetMapAreas() const { return map_areas_; } 88const std::vector<Door>& GD_GetDoors();
89 89const Door& GD_GetDoor(int door_id);
90 const MapArea& GetMapArea(int id) const { return map_areas_.at(id); } 90const Panel& GD_GetPanel(int panel_id);
91 91int GD_GetRoomForPainting(const std::string& painting_id);
92 int GetRoomByName(const std::string& name) const { 92const std::vector<int>& GD_GetAchievementPanels();
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
133const GameData& GetGameData();
134 93
135#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ 94#endif /* end of include guard: GAME_DATA_H_9C42AC51 */
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 0e0569b..736db82 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp
@@ -15,7 +15,7 @@ TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
15 return; 15 return;
16 } 16 }
17 17
18 for (const MapArea &map_area : GetGameData().GetMapAreas()) { 18 for (const MapArea &map_area : GD_GetMapAreas()) {
19 AreaIndicator area; 19 AreaIndicator area;
20 area.area_id = map_area.id; 20 area.area_id = map_area.id;
21 21
@@ -93,13 +93,13 @@ void TrackerPanel::Redraw() {
93 for (AreaIndicator &area : areas_) { 93 for (AreaIndicator &area : areas_) {
94 const wxBrush *brush_color = wxGREY_BRUSH; 94 const wxBrush *brush_color = wxGREY_BRUSH;
95 95
96 const MapArea &map_area = GetGameData().GetMapArea(area.area_id); 96 const MapArea &map_area = GD_GetMapArea(area.area_id);
97 bool has_reachable_unchecked = false; 97 bool has_reachable_unchecked = false;
98 bool has_unreachable_unchecked = false; 98 bool has_unreachable_unchecked = false;
99 for (int section_id = 0; section_id < map_area.locations.size(); 99 for (int section_id = 0; section_id < map_area.locations.size();
100 section_id++) { 100 section_id++) {
101 if (!AP_HasCheckedGameLocation(area.area_id, section_id)) { 101 if (!AP_HasCheckedGameLocation(area.area_id, section_id)) {
102 if (GetTrackerState().IsLocationReachable(area.area_id, section_id)) { 102 if (IsLocationReachable(area.area_id, section_id)) {
103 has_reachable_unchecked = true; 103 has_reachable_unchecked = true;
104 } else { 104 } else {
105 has_unreachable_unchecked = true; 105 has_unreachable_unchecked = true;
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 858ec3e..37a7da8 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -1,16 +1,29 @@
1#include "tracker_state.h" 1#include "tracker_state.h"
2 2
3#include <list> 3#include <list>
4#include <map>
4#include <set> 5#include <set>
6#include <tuple>
5 7
6#include "ap_state.h" 8#include "ap_state.h"
7#include "game_data.h" 9#include "game_data.h"
8 10
11namespace {
12
13struct TrackerState {
14 std::map<std::tuple<int, int>, bool> reachability;
15};
16
17TrackerState& GetState() {
18 static TrackerState* instance = new TrackerState();
19 return *instance;
20}
21
9bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms); 22bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms);
10 23
11bool IsPanelReachable_Helper(int panel_id, 24bool IsPanelReachable_Helper(int panel_id,
12 const std::set<int>& reachable_rooms) { 25 const std::set<int>& reachable_rooms) {
13 const Panel& panel_obj = GetGameData().GetPanel(panel_id); 26 const Panel& panel_obj = GD_GetPanel(panel_id);
14 27
15 if (!reachable_rooms.count(panel_obj.room)) { 28 if (!reachable_rooms.count(panel_obj.room)) {
16 return false; 29 return false;
@@ -19,7 +32,7 @@ bool IsPanelReachable_Helper(int panel_id,
19 if (panel_obj.name == "THE MASTER") { 32 if (panel_obj.name == "THE MASTER") {
20 int achievements_accessible = 0; 33 int achievements_accessible = 0;
21 34
22 for (int achieve_id : GetGameData().GetAchievementPanels()) { 35 for (int achieve_id : GD_GetAchievementPanels()) {
23 if (IsPanelReachable_Helper(achieve_id, reachable_rooms)) { 36 if (IsPanelReachable_Helper(achieve_id, reachable_rooms)) {
24 achievements_accessible++; 37 achievements_accessible++;
25 38
@@ -56,7 +69,7 @@ bool IsPanelReachable_Helper(int panel_id,
56} 69}
57 70
58bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms) { 71bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms) {
59 const Door& door_obj = GetGameData().GetDoor(door_id); 72 const Door& door_obj = GD_GetDoor(door_id);
60 73
61 if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { 74 if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
62 if (!reachable_rooms.count(door_obj.room)) { 75 if (!reachable_rooms.count(door_obj.room)) {
@@ -89,14 +102,15 @@ bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms) {
89 } 102 }
90} 103}
91 104
92void TrackerState::CalculateState() { 105} // namespace
93 reachability_.clear(); 106
107void RecalculateReachability() {
108 GetState().reachability.clear();
94 109
95 std::set<int> reachable_rooms; 110 std::set<int> reachable_rooms;
96 111
97 std::list<Exit> flood_boundary; 112 std::list<Exit> flood_boundary;
98 flood_boundary.push_back( 113 flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")});
99 {.destination_room = GetGameData().GetRoomByName("Menu")});
100 114
101 bool reachable_changed = true; 115 bool reachable_changed = true;
102 while (reachable_changed) { 116 while (reachable_changed) {
@@ -123,8 +137,7 @@ void TrackerState::CalculateState() {
123 reachable_rooms.insert(room_exit.destination_room); 137 reachable_rooms.insert(room_exit.destination_room);
124 reachable_changed = true; 138 reachable_changed = true;
125 139
126 const Room& room_obj = 140 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
127 GetGameData().GetRoom(room_exit.destination_room);
128 for (const Exit& out_edge : room_obj.exits) { 141 for (const Exit& out_edge : room_obj.exits) {
129 if (!out_edge.painting || !AP_IsPaintingShuffle()) { 142 if (!out_edge.painting || !AP_IsPaintingShuffle()) {
130 new_boundary.push_back(out_edge); 143 new_boundary.push_back(out_edge);
@@ -135,7 +148,7 @@ void TrackerState::CalculateState() {
135 for (const PaintingExit& out_edge : room_obj.paintings) { 148 for (const PaintingExit& out_edge : room_obj.paintings) {
136 if (AP_GetPaintingMapping().count(out_edge.id)) { 149 if (AP_GetPaintingMapping().count(out_edge.id)) {
137 Exit painting_exit; 150 Exit painting_exit;
138 painting_exit.destination_room = GetGameData().GetRoomForPainting( 151 painting_exit.destination_room = GD_GetRoomForPainting(
139 AP_GetPaintingMapping().at(out_edge.id)); 152 AP_GetPaintingMapping().at(out_edge.id));
140 painting_exit.door = out_edge.door; 153 painting_exit.door = out_edge.door;
141 154
@@ -149,7 +162,7 @@ void TrackerState::CalculateState() {
149 flood_boundary = new_boundary; 162 flood_boundary = new_boundary;
150 } 163 }
151 164
152 for (const MapArea& map_area : GetGameData().GetMapAreas()) { 165 for (const MapArea& map_area : GD_GetMapAreas()) {
153 for (int section_id = 0; section_id < map_area.locations.size(); 166 for (int section_id = 0; section_id < map_area.locations.size();
154 section_id++) { 167 section_id++) {
155 const Location& location_section = map_area.locations.at(section_id); 168 const Location& location_section = map_area.locations.at(section_id);
@@ -160,22 +173,17 @@ void TrackerState::CalculateState() {
160 } 173 }
161 } 174 }
162 175
163 reachability_[{map_area.id, section_id}] = reachable; 176 GetState().reachability[{map_area.id, section_id}] = reachable;
164 } 177 }
165 } 178 }
166} 179}
167 180
168bool TrackerState::IsLocationReachable(int area_id, int section_id) { 181bool IsLocationReachable(int area_id, int section_id) {
169 std::tuple<int, int> key = {area_id, section_id}; 182 std::tuple<int, int> key = {area_id, section_id};
170 183
171 if (reachability_.count(key)) { 184 if (GetState().reachability.count(key)) {
172 return reachability_.at(key); 185 return GetState().reachability.at(key);
173 } else { 186 } else {
174 return false; 187 return false;
175 } 188 }
176} 189}
177
178TrackerState& GetTrackerState() {
179 static TrackerState* instance = new TrackerState();
180 return *instance;
181}
diff --git a/src/tracker_state.h b/src/tracker_state.h index 879e6f2..d8256e2 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h
@@ -1,19 +1,8 @@
1#ifndef TRACKER_STATE_H_8639BC90 1#ifndef TRACKER_STATE_H_8639BC90
2#define TRACKER_STATE_H_8639BC90 2#define TRACKER_STATE_H_8639BC90
3 3
4#include <map> 4void RecalculateReachability();
5#include <tuple>
6 5
7class TrackerState { 6bool IsLocationReachable(int area_id, int section_id);
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
17TrackerState& GetTrackerState();
18 7
19#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ 8#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */