about summary refs log tree commit diff stats
path: root/src/game_data.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game_data.cpp')
-rw-r--r--src/game_data.cpp496
1 files changed, 387 insertions, 109 deletions
diff --git a/src/game_data.cpp b/src/game_data.cpp index 7c9564b..588ffc8 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -1,11 +1,6 @@
1#include "game_data.h" 1#include "game_data.h"
2 2
3#include <wx/wxprec.h> 3#include <fmt/core.h>
4
5#ifndef WX_PRECOMP
6#include <wx/wx.h>
7#endif
8
9#include <hkutil/string.h> 4#include <hkutil/string.h>
10#include <yaml-cpp/yaml.h> 5#include <yaml-cpp/yaml.h>
11 6
@@ -13,51 +8,31 @@
13#include <sstream> 8#include <sstream>
14 9
15#include "global.h" 10#include "global.h"
11#include "logger.h"
16 12
17namespace { 13namespace {
18 14
19LingoColor GetColorForString(const std::string &str) {
20 if (str == "black") {
21 return LingoColor::kBlack;
22 } else if (str == "red") {
23 return LingoColor::kRed;
24 } else if (str == "blue") {
25 return LingoColor::kBlue;
26 } else if (str == "yellow") {
27 return LingoColor::kYellow;
28 } else if (str == "orange") {
29 return LingoColor::kOrange;
30 } else if (str == "green") {
31 return LingoColor::kGreen;
32 } else if (str == "gray") {
33 return LingoColor::kGray;
34 } else if (str == "brown") {
35 return LingoColor::kBrown;
36 } else if (str == "purple") {
37 return LingoColor::kPurple;
38 } else {
39 wxLogError("Invalid color: %s", str);
40
41 return LingoColor::kNone;
42 }
43}
44
45struct GameData { 15struct GameData {
46 std::vector<Room> rooms_; 16 std::vector<Room> rooms_;
47 std::vector<Door> doors_; 17 std::vector<Door> doors_;
48 std::vector<Panel> panels_; 18 std::vector<Panel> panels_;
19 std::vector<PanelDoor> panel_doors_;
49 std::vector<MapArea> map_areas_; 20 std::vector<MapArea> map_areas_;
50 std::vector<SubwayItem> subway_items_; 21 std::vector<SubwayItem> subway_items_;
22 std::vector<PaintingExit> paintings_;
51 23
52 std::map<std::string, int> room_by_id_; 24 std::map<std::string, int> room_by_id_;
53 std::map<std::string, int> door_by_id_; 25 std::map<std::string, int> door_by_id_;
54 std::map<std::string, int> panel_by_id_; 26 std::map<std::string, int> panel_by_id_;
27 std::map<std::string, int> panel_doors_by_id_;
55 std::map<std::string, int> area_by_id_; 28 std::map<std::string, int> area_by_id_;
29 std::map<std::string, int> painting_by_id_;
56 30
57 std::vector<int> door_definition_order_; 31 std::vector<int> door_definition_order_;
58 32
59 std::map<std::string, int> room_by_painting_; 33 std::map<std::string, int> room_by_painting_;
60 std::map<int, int> room_by_sunwarp_; 34 std::map<int, int> room_by_sunwarp_;
35 std::map<int, int> panel_by_solve_index_;
61 36
62 std::vector<int> achievement_panels_; 37 std::vector<int> achievement_panels_;
63 38
@@ -68,6 +43,8 @@ struct GameData {
68 std::map<std::string, int> subway_item_by_painting_; 43 std::map<std::string, int> subway_item_by_painting_;
69 std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; 44 std::map<SubwaySunwarp, int> subway_item_by_sunwarp_;
70 45
46 std::map<int, std::string> item_by_ap_id_;
47
71 bool loaded_area_data_ = false; 48 bool loaded_area_data_ = false;
72 std::set<std::string> malconfigured_areas_; 49 std::set<std::string> malconfigured_areas_;
73 50
@@ -83,10 +60,10 @@ struct GameData {
83 ids_config["special_items"][color_name]) { 60 ids_config["special_items"][color_name]) {
84 std::string input_name = color_name; 61 std::string input_name = color_name;
85 input_name[0] = std::tolower(input_name[0]); 62 input_name[0] = std::tolower(input_name[0]);
86 ap_id_by_color_[GetColorForString(input_name)] = 63 ap_id_by_color_[GetLingoColorForString(input_name)] =
87 ids_config["special_items"][color_name].as<int>(); 64 ids_config["special_items"][color_name].as<int>();
88 } else { 65 } else {
89 wxLogError("Missing AP item ID for color %s", color_name); 66 TrackerLog(fmt::format("Missing AP item ID for color {}", color_name));
90 } 67 }
91 }; 68 };
92 69
@@ -100,8 +77,18 @@ struct GameData {
100 init_color_id("Brown"); 77 init_color_id("Brown");
101 init_color_id("Gray"); 78 init_color_id("Gray");
102 79
80 if (ids_config["special_items"]) {
81 for (const auto& special_item_it : ids_config["special_items"])
82 {
83 item_by_ap_id_[special_item_it.second.as<int>()] =
84 special_item_it.first.as<std::string>();
85 }
86 }
87
103 rooms_.reserve(lingo_config.size() * 2); 88 rooms_.reserve(lingo_config.size() * 2);
104 89
90 std::vector<int> panel_location_ids;
91
105 for (const auto &room_it : lingo_config) { 92 for (const auto &room_it : lingo_config) {
106 int room_id = AddOrGetRoom(room_it.first.as<std::string>()); 93 int room_id = AddOrGetRoom(room_it.first.as<std::string>());
107 94
@@ -111,6 +98,7 @@ struct GameData {
111 auto process_single_entrance = 98 auto process_single_entrance =
112 [this, room_id, from_room_id](const YAML::Node &option) { 99 [this, room_id, from_room_id](const YAML::Node &option) {
113 Exit exit_obj; 100 Exit exit_obj;
101 exit_obj.source_room = from_room_id;
114 exit_obj.destination_room = room_id; 102 exit_obj.destination_room = room_id;
115 103
116 if (option["door"]) { 104 if (option["door"]) {
@@ -139,13 +127,17 @@ struct GameData {
139 exit_obj.type = EntranceType::kCrossroadsRoofAccess; 127 exit_obj.type = EntranceType::kCrossroadsRoofAccess;
140 } 128 }
141 129
130 if (option["static_painting"] && option["static_painting"].as<bool>()) {
131 exit_obj.type = EntranceType::kStaticPainting;
132 }
133
142 rooms_[from_room_id].exits.push_back(exit_obj); 134 rooms_[from_room_id].exits.push_back(exit_obj);
143 }; 135 };
144 136
145 switch (entrance_it.second.Type()) { 137 switch (entrance_it.second.Type()) {
146 case YAML::NodeType::Scalar: { 138 case YAML::NodeType::Scalar: {
147 // This is just "true". 139 // This is just "true".
148 rooms_[from_room_id].exits.push_back({.destination_room = room_id}); 140 rooms_[from_room_id].exits.push_back({.source_room = from_room_id, .destination_room = room_id});
149 break; 141 break;
150 } 142 }
151 case YAML::NodeType::Map: { 143 case YAML::NodeType::Map: {
@@ -163,7 +155,8 @@ struct GameData {
163 // This shouldn't happen. 155 // This shouldn't happen.
164 std::ostringstream formatted; 156 std::ostringstream formatted;
165 formatted << entrance_it; 157 formatted << entrance_it;
166 wxLogError("Error reading game data: %s", formatted.str()); 158 TrackerLog(
159 fmt::format("Error reading game data: {}", formatted.str()));
167 break; 160 break;
168 } 161 }
169 } 162 }
@@ -177,12 +170,12 @@ struct GameData {
177 170
178 if (panel_it.second["colors"]) { 171 if (panel_it.second["colors"]) {
179 if (panel_it.second["colors"].IsScalar()) { 172 if (panel_it.second["colors"].IsScalar()) {
180 panels_[panel_id].colors.push_back(GetColorForString( 173 panels_[panel_id].colors.push_back(GetLingoColorForString(
181 panel_it.second["colors"].as<std::string>())); 174 panel_it.second["colors"].as<std::string>()));
182 } else { 175 } else {
183 for (const auto &color_node : panel_it.second["colors"]) { 176 for (const auto &color_node : panel_it.second["colors"]) {
184 panels_[panel_id].colors.push_back( 177 panels_[panel_id].colors.push_back(
185 GetColorForString(color_node.as<std::string>())); 178 GetLingoColorForString(color_node.as<std::string>()));
186 } 179 }
187 } 180 }
188 } 181 }
@@ -260,6 +253,16 @@ struct GameData {
260 achievement_panels_.push_back(panel_id); 253 achievement_panels_.push_back(panel_id);
261 } 254 }
262 255
256 if (panel_it.second["location_name"]) {
257 panels_[panel_id].location_name =
258 panel_it.second["location_name"].as<std::string>();
259 }
260
261 if (panel_it.second["id"]) {
262 panels_[panel_id].nodepath =
263 panel_it.second["id"].as<std::string>();
264 }
265
263 if (panel_it.second["hunt"]) { 266 if (panel_it.second["hunt"]) {
264 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); 267 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>();
265 } 268 }
@@ -278,13 +281,15 @@ struct GameData {
278 ids_config["panels"][rooms_[room_id].name] && 281 ids_config["panels"][rooms_[room_id].name] &&
279 ids_config["panels"][rooms_[room_id].name] 282 ids_config["panels"][rooms_[room_id].name]
280 [panels_[panel_id].name]) { 283 [panels_[panel_id].name]) {
281 panels_[panel_id].ap_location_id = 284 int location_id = ids_config["panels"][rooms_[room_id].name]
282 ids_config["panels"][rooms_[room_id].name] 285 [panels_[panel_id].name]
283 [panels_[panel_id].name] 286 .as<int>();
284 .as<int>(); 287 panels_[panel_id].ap_location_id = location_id;
288 panel_location_ids.push_back(location_id);
285 } else { 289 } else {
286 wxLogError("Missing AP location ID for panel %s - %s", 290 TrackerLog(fmt::format("Missing AP location ID for panel {} - {}",
287 rooms_[room_id].name, panels_[panel_id].name); 291 rooms_[room_id].name,
292 panels_[panel_id].name));
288 } 293 }
289 } 294 }
290 } 295 }
@@ -346,9 +351,13 @@ struct GameData {
346 ids_config["doors"][rooms_[room_id].name] 351 ids_config["doors"][rooms_[room_id].name]
347 [doors_[door_id].name]["item"] 352 [doors_[door_id].name]["item"]
348 .as<int>(); 353 .as<int>();
354
355 item_by_ap_id_[doors_[door_id].ap_item_id] =
356 doors_[door_id].item_name;
349 } else { 357 } else {
350 wxLogError("Missing AP item ID for door %s - %s", 358 TrackerLog(fmt::format("Missing AP item ID for door {} - {}",
351 rooms_[room_id].name, doors_[door_id].name); 359 rooms_[room_id].name,
360 doors_[door_id].name));
352 } 361 }
353 } 362 }
354 363
@@ -361,9 +370,12 @@ struct GameData {
361 doors_[door_id].group_ap_item_id = 370 doors_[door_id].group_ap_item_id =
362 ids_config["door_groups"][doors_[door_id].group_name] 371 ids_config["door_groups"][doors_[door_id].group_name]
363 .as<int>(); 372 .as<int>();
373
374 item_by_ap_id_[doors_[door_id].group_ap_item_id] =
375 doors_[door_id].group_name;
364 } else { 376 } else {
365 wxLogError("Missing AP item ID for door group %s", 377 TrackerLog(fmt::format("Missing AP item ID for door group {}",
366 doors_[door_id].group_name); 378 doors_[door_id].group_name));
367 } 379 }
368 } 380 }
369 381
@@ -373,11 +385,11 @@ struct GameData {
373 } else if (!door_it.second["skip_location"] && 385 } else if (!door_it.second["skip_location"] &&
374 !door_it.second["event"]) { 386 !door_it.second["event"]) {
375 if (has_external_panels) { 387 if (has_external_panels) {
376 wxLogError( 388 TrackerLog(fmt::format(
377 "%s - %s has panels from other rooms but does not have an " 389 "{} - {} has panels from other rooms but does not have an "
378 "explicit location name and is not marked skip_location or " 390 "explicit location name and is not marked skip_location or "
379 "event", 391 "event",
380 rooms_[room_id].name, doors_[door_id].name); 392 rooms_[room_id].name, doors_[door_id].name));
381 } 393 }
382 394
383 doors_[door_id].location_name = 395 doors_[door_id].location_name =
@@ -397,8 +409,9 @@ struct GameData {
397 [doors_[door_id].name]["location"] 409 [doors_[door_id].name]["location"]
398 .as<int>(); 410 .as<int>();
399 } else { 411 } else {
400 wxLogError("Missing AP location ID for door %s - %s", 412 TrackerLog(fmt::format("Missing AP location ID for door {} - {}",
401 rooms_[room_id].name, doors_[door_id].name); 413 rooms_[room_id].name,
414 doors_[door_id].name));
402 } 415 }
403 } 416 }
404 417
@@ -417,14 +430,107 @@ struct GameData {
417 } 430 }
418 } 431 }
419 432
433 if (room_it.second["panel_doors"]) {
434 for (const auto &panel_door_it : room_it.second["panel_doors"]) {
435 std::string panel_door_name = panel_door_it.first.as<std::string>();
436 int panel_door_id =
437 AddOrGetPanelDoor(rooms_[room_id].name, panel_door_name);
438
439 std::map<std::string, std::vector<std::string>> panel_per_room;
440 int num_panels = 0;
441 for (const auto &panel_node : panel_door_it.second["panels"]) {
442 num_panels++;
443
444 int panel_id = -1;
445
446 if (panel_node.IsScalar()) {
447 panel_id = AddOrGetPanel(rooms_[room_id].name,
448 panel_node.as<std::string>());
449
450 panel_per_room[rooms_[room_id].name].push_back(
451 panel_node.as<std::string>());
452 } else {
453 panel_id = AddOrGetPanel(panel_node["room"].as<std::string>(),
454 panel_node["panel"].as<std::string>());
455
456 panel_per_room[panel_node["room"].as<std::string>()].push_back(
457 panel_node["panel"].as<std::string>());
458 }
459
460 Panel &panel = panels_[panel_id];
461 panel.panel_door = panel_door_id;
462 }
463
464 if (panel_door_it.second["item_name"]) {
465 panel_doors_[panel_door_id].item_name =
466 panel_door_it.second["item_name"].as<std::string>();
467 } else {
468 std::vector<std::string> room_strs;
469 for (const auto &[room_str, panels_str] : panel_per_room) {
470 room_strs.push_back(fmt::format(
471 "{} - {}", room_str, hatkirby::implode(panels_str, ", ")));
472 }
473
474 if (num_panels == 1) {
475 panel_doors_[panel_door_id].item_name =
476 fmt::format("{} (Panel)", room_strs[0]);
477 } else {
478 panel_doors_[panel_door_id].item_name = fmt::format(
479 "{} (Panels)", hatkirby::implode(room_strs, " and "));
480 }
481 }
482
483 if (ids_config["panel_doors"] &&
484 ids_config["panel_doors"][rooms_[room_id].name] &&
485 ids_config["panel_doors"][rooms_[room_id].name]
486 [panel_door_name]) {
487 panel_doors_[panel_door_id].ap_item_id =
488 ids_config["panel_doors"][rooms_[room_id].name][panel_door_name]
489 .as<int>();
490
491 item_by_ap_id_[panel_doors_[panel_door_id].ap_item_id] =
492 panel_doors_[panel_door_id].item_name;
493 } else {
494 TrackerLog(fmt::format("Missing AP item ID for panel door {} - {}",
495 rooms_[room_id].name, panel_door_name));
496 }
497
498 if (panel_door_it.second["panel_group"]) {
499 std::string panel_group =
500 panel_door_it.second["panel_group"].as<std::string>();
501
502 if (ids_config["panel_groups"] &&
503 ids_config["panel_groups"][panel_group]) {
504 panel_doors_[panel_door_id].group_ap_item_id =
505 ids_config["panel_groups"][panel_group].as<int>();
506
507 item_by_ap_id_[panel_doors_[panel_door_id].group_ap_item_id] =
508 panel_group;
509 } else {
510 TrackerLog(fmt::format(
511 "Missing AP item ID for panel door group {}", panel_group));
512 }
513 }
514 }
515 }
516
420 if (room_it.second["paintings"]) { 517 if (room_it.second["paintings"]) {
421 for (const auto &painting : room_it.second["paintings"]) { 518 for (const auto &painting : room_it.second["paintings"]) {
422 std::string painting_id = painting["id"].as<std::string>(); 519 std::string internal_id = painting["id"].as<std::string>();
423 room_by_painting_[painting_id] = room_id; 520 int painting_id = AddOrGetPainting(internal_id);
521 PaintingExit &painting_exit = paintings_[painting_id];
522 painting_exit.room = room_id;
523
524 if (painting["display_name"]) {
525 painting_exit.display_name =
526 painting["display_name"].as<std::string>();
527 } else {
528 painting_exit.display_name = painting_exit.internal_id;
529 }
424 530
425 if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { 531 if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) &&
426 PaintingExit painting_exit; 532 (!painting["disable"] || !painting["disable"].as<bool>())) {
427 painting_exit.id = painting_id; 533 painting_exit.entrance = true;
428 534
429 if (painting["required_door"]) { 535 if (painting["required_door"]) {
430 std::string rd_room = rooms_[room_id].name; 536 std::string rd_room = rooms_[room_id].name;
@@ -435,9 +541,9 @@ struct GameData {
435 painting_exit.door = AddOrGetDoor( 541 painting_exit.door = AddOrGetDoor(
436 rd_room, painting["required_door"]["door"].as<std::string>()); 542 rd_room, painting["required_door"]["door"].as<std::string>());
437 } 543 }
438
439 rooms_[room_id].paintings.push_back(painting_exit);
440 } 544 }
545
546 rooms_[room_id].paintings.push_back(painting_exit.id);
441 } 547 }
442 } 548 }
443 549
@@ -463,33 +569,74 @@ struct GameData {
463 ids_config["progression"][progressive_item_name]) { 569 ids_config["progression"][progressive_item_name]) {
464 progressive_item_id = 570 progressive_item_id =
465 ids_config["progression"][progressive_item_name].as<int>(); 571 ids_config["progression"][progressive_item_name].as<int>();
572
573 item_by_ap_id_[progressive_item_id] = progressive_item_name;
466 } else { 574 } else {
467 wxLogError("Missing AP item ID for progressive item %s", 575 TrackerLog(fmt::format("Missing AP item ID for progressive item {}",
468 progressive_item_name); 576 progressive_item_name));
469 } 577 }
470 578
471 int index = 1; 579 if (progression_it.second["doors"]) {
472 for (const auto &stage : progression_it.second) { 580 int index = 1;
473 int door_id = -1; 581 for (const auto &stage : progression_it.second["doors"]) {
582 int door_id = -1;
583
584 if (stage.IsScalar()) {
585 door_id =
586 AddOrGetDoor(rooms_[room_id].name, stage.as<std::string>());
587 } else {
588 door_id = AddOrGetDoor(stage["room"].as<std::string>(),
589 stage["door"].as<std::string>());
590 }
474 591
475 if (stage.IsScalar()) { 592 doors_[door_id].progressives.push_back(
476 door_id = 593 {.item_name = progressive_item_name,
477 AddOrGetDoor(rooms_[room_id].name, stage.as<std::string>()); 594 .ap_item_id = progressive_item_id,
478 } else { 595 .quantity = index});
479 door_id = AddOrGetDoor(stage["room"].as<std::string>(), 596 index++;
480 stage["door"].as<std::string>());
481 } 597 }
598 }
482 599
483 doors_[door_id].progressives.push_back( 600 if (progression_it.second["panel_doors"]) {
484 {.item_name = progressive_item_name, 601 int index = 1;
485 .ap_item_id = progressive_item_id, 602 for (const auto &stage : progression_it.second["panel_doors"]) {
486 .quantity = index}); 603 int panel_door_id = -1;
487 index++; 604
605 if (stage.IsScalar()) {
606 panel_door_id = AddOrGetPanelDoor(rooms_[room_id].name,
607 stage.as<std::string>());
608 } else {
609 panel_door_id =
610 AddOrGetPanelDoor(stage["room"].as<std::string>(),
611 stage["panel_door"].as<std::string>());
612 }
613
614 panel_doors_[panel_door_id].progressives.push_back(
615 {.item_name = progressive_item_name,
616 .ap_item_id = progressive_item_id,
617 .quantity = index});
618 index++;
619 }
488 } 620 }
489 } 621 }
490 } 622 }
491 } 623 }
492 624
625 // Determine the panel solve indices from the sorted location IDs.
626 std::sort(panel_location_ids.begin(), panel_location_ids.end());
627
628 std::map<int, int> solve_index_by_location_id;
629 for (int i = 0; i < panel_location_ids.size(); i++) {
630 solve_index_by_location_id[panel_location_ids[i]] = i;
631 }
632
633 for (Panel &panel : panels_) {
634 if (panel.ap_location_id != -1) {
635 panel.solve_index = solve_index_by_location_id[panel.ap_location_id];
636 panel_by_solve_index_[panel.solve_index] = panel.id;
637 }
638 }
639
493 map_areas_.reserve(areas_config.size()); 640 map_areas_.reserve(areas_config.size());
494 641
495 std::map<std::string, int> fold_areas; 642 std::map<std::string, int> fold_areas;
@@ -510,13 +657,28 @@ struct GameData {
510 // Only locations for the panels are kept here. 657 // Only locations for the panels are kept here.
511 std::map<std::string, std::tuple<int, int>> locations_by_name; 658 std::map<std::string, std::tuple<int, int>> locations_by_name;
512 659
513 for (const Panel &panel : panels_) { 660 for (Panel &panel : panels_) {
514 int room_id = panel.room; 661 int room_id = panel.room;
515 std::string room_name = rooms_[room_id].name; 662 std::string room_name = rooms_[room_id].name;
516 663
517 std::string area_name = room_name; 664 std::string area_name = room_name;
518 if (fold_areas.count(room_name)) { 665 std::string section_name = panel.name;
519 int fold_area_id = fold_areas[room_name]; 666 std::string location_name = room_name + " - " + panel.name;
667
668 if (!panel.location_name.empty()) {
669 location_name = panel.location_name;
670
671 size_t divider_pos = location_name.find(" - ");
672 if (divider_pos != std::string::npos) {
673 area_name = location_name.substr(0, divider_pos);
674 section_name = location_name.substr(divider_pos + 3);
675 }
676 } else {
677 panel.location_name = location_name;
678 }
679
680 if (fold_areas.count(area_name)) {
681 int fold_area_id = fold_areas[area_name];
520 area_name = map_areas_[fold_area_id].name; 682 area_name = map_areas_[fold_area_id].name;
521 } 683 }
522 684
@@ -528,19 +690,23 @@ struct GameData {
528 } 690 }
529 } 691 }
530 692
693 if (room_name == "Starting Room") {
694 classification |= kLOCATION_SMALL_SPHERE_ONE;
695 }
696
531 int area_id = AddOrGetArea(area_name); 697 int area_id = AddOrGetArea(area_name);
532 MapArea &map_area = map_areas_[area_id]; 698 MapArea &map_area = map_areas_[area_id];
533 // room field should be the original room ID 699 // room field should be the original room ID
534 map_area.locations.push_back( 700 map_area.locations.push_back({.name = section_name,
535 {.name = panel.name, 701 .ap_location_name = location_name,
536 .ap_location_name = room_name + " - " + panel.name, 702 .ap_location_id = panel.ap_location_id,
537 .ap_location_id = panel.ap_location_id, 703 .room = panel.room,
538 .room = panel.room, 704 .panels = {panel.id},
539 .panels = {panel.id}, 705 .classification = classification,
540 .classification = classification, 706 .hunt = panel.hunt,
541 .hunt = panel.hunt}); 707 .single_panel = panel.id});
542 locations_by_name[map_area.locations.back().ap_location_name] = { 708 locations_by_name[location_name] = {area_id,
543 area_id, map_area.locations.size() - 1}; 709 map_area.locations.size() - 1};
544 } 710 }
545 711
546 for (int door_id : door_definition_order_) { 712 for (int door_id : door_definition_order_) {
@@ -591,14 +757,36 @@ struct GameData {
591 for (const Location &location : map_area.locations) { 757 for (const Location &location : map_area.locations) {
592 map_area.classification |= location.classification; 758 map_area.classification |= location.classification;
593 map_area.hunt |= location.hunt; 759 map_area.hunt |= location.hunt;
760 map_area.has_single_panel |= location.single_panel.has_value();
761 }
762 }
763
764 for (const Room &room : rooms_) {
765 std::string area_name = room.name;
766 if (fold_areas.count(room.name)) {
767 int fold_area_id = fold_areas[room.name];
768 area_name = map_areas_[fold_area_id].name;
769 }
770
771 if (!room.paintings.empty()) {
772 int area_id = AddOrGetArea(area_name);
773 MapArea &map_area = map_areas_[area_id];
774
775 for (int painting_id : room.paintings) {
776 PaintingExit &painting_obj = paintings_.at(painting_id);
777 painting_obj.map_area = area_id;
778 if (painting_obj.entrance) {
779 map_area.paintings.push_back(painting_id);
780 }
781 }
594 } 782 }
595 } 783 }
596 784
597 // Report errors. 785 // Report errors.
598 for (const std::string &area : malconfigured_areas_) { 786 for (const std::string &area : malconfigured_areas_) {
599 wxLogError("Area data not found for: %s", area); 787 TrackerLog(fmt::format("Area data not found for: {}", area));
600 } 788 }
601 789
602 // Read in subway items. 790 // Read in subway items.
603 YAML::Node subway_config = 791 YAML::Node subway_config =
604 YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); 792 YAML::LoadFile(GetAbsolutePath("assets/subway.yaml"));
@@ -613,13 +801,10 @@ struct GameData {
613 subway_it["door"].as<std::string>()); 801 subway_it["door"].as<std::string>());
614 } 802 }
615 803
616 if (subway_it["paintings"]) { 804 if (subway_it["painting"]) {
617 for (const auto &painting_it : subway_it["paintings"]) { 805 std::string painting_id = subway_it["painting"].as<std::string>();
618 std::string painting_id = painting_it.as<std::string>(); 806 subway_item.painting = painting_id;
619 807 subway_item_by_painting_[painting_id] = subway_item.id;
620 subway_item.paintings.push_back(painting_id);
621 subway_item_by_painting_[painting_id] = subway_item.id;
622 }
623 } 808 }
624 809
625 if (subway_it["tags"]) { 810 if (subway_it["tags"]) {
@@ -628,6 +813,18 @@ struct GameData {
628 } 813 }
629 } 814 }
630 815
816 if (subway_it["entrances"]) {
817 for (const auto &entrance_it : subway_it["entrances"]) {
818 subway_item.entrances.push_back(entrance_it.as<std::string>());
819 }
820 }
821
822 if (subway_it["exits"]) {
823 for (const auto &exit_it : subway_it["exits"]) {
824 subway_item.exits.push_back(exit_it.as<std::string>());
825 }
826 }
827
631 if (subway_it["sunwarp"]) { 828 if (subway_it["sunwarp"]) {
632 SubwaySunwarp sunwarp; 829 SubwaySunwarp sunwarp;
633 sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); 830 sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>();
@@ -654,6 +851,10 @@ struct GameData {
654 subway_item.special = subway_it["special"].as<std::string>(); 851 subway_item.special = subway_it["special"].as<std::string>();
655 } 852 }
656 853
854 if (subway_it["tilted"]) {
855 subway_item.tilted = subway_it["tilted"].as<bool>();
856 }
857
657 subway_items_.push_back(subway_item); 858 subway_items_.push_back(subway_item);
658 } 859 }
659 860
@@ -667,7 +868,7 @@ struct GameData {
667 868
668 for (const auto &[tag, items] : subway_tags) { 869 for (const auto &[tag, items] : subway_tags) {
669 if (items.size() == 1) { 870 if (items.size() == 1) {
670 wxLogWarning("Singleton subway item tag: %s", tag); 871 TrackerLog(fmt::format("Singleton subway item tag: {}", tag));
671 } 872 }
672 } 873 }
673 } 874 }
@@ -687,7 +888,8 @@ struct GameData {
687 if (!door_by_id_.count(full_name)) { 888 if (!door_by_id_.count(full_name)) {
688 int door_id = doors_.size(); 889 int door_id = doors_.size();
689 door_by_id_[full_name] = doors_.size(); 890 door_by_id_[full_name] = doors_.size();
690 doors_.push_back({.id = door_id, .room = AddOrGetRoom(room), .name = door}); 891 doors_.push_back(
892 {.id = door_id, .room = AddOrGetRoom(room), .name = door});
691 } 893 }
692 894
693 return door_by_id_[full_name]; 895 return door_by_id_[full_name];
@@ -706,6 +908,18 @@ struct GameData {
706 return panel_by_id_[full_name]; 908 return panel_by_id_[full_name];
707 } 909 }
708 910
911 int AddOrGetPanelDoor(std::string room, std::string panel) {
912 std::string full_name = room + " - " + panel;
913
914 if (!panel_doors_by_id_.count(full_name)) {
915 int panel_door_id = panel_doors_.size();
916 panel_doors_by_id_[full_name] = panel_door_id;
917 panel_doors_.push_back({});
918 }
919
920 return panel_doors_by_id_[full_name];
921 }
922
709 int AddOrGetArea(std::string area) { 923 int AddOrGetArea(std::string area) {
710 if (!area_by_id_.count(area)) { 924 if (!area_by_id_.count(area)) {
711 if (loaded_area_data_) { 925 if (loaded_area_data_) {
@@ -719,6 +933,16 @@ struct GameData {
719 933
720 return area_by_id_[area]; 934 return area_by_id_[area];
721 } 935 }
936
937 int AddOrGetPainting(std::string internal_id) {
938 if (!painting_by_id_.count(internal_id)) {
939 int painting_id = paintings_.size();
940 painting_by_id_[internal_id] = painting_id;
941 paintings_.push_back({.id = painting_id, .internal_id = internal_id});
942 }
943
944 return painting_by_id_[internal_id];
945 }
722}; 946};
723 947
724GameData &GetState() { 948GameData &GetState() {
@@ -728,7 +952,12 @@ GameData &GetState() {
728 952
729} // namespace 953} // namespace
730 954
731bool SubwaySunwarp::operator<(const SubwaySunwarp& rhs) const { 955bool SubwayItem::HasWarps() const {
956 return !(this->tags.empty() && this->entrances.empty() &&
957 this->exits.empty());
958}
959
960bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const {
732 return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); 961 return std::tie(dots, type) < std::tie(rhs.dots, rhs.type);
733} 962}
734 963
@@ -746,6 +975,10 @@ const std::vector<Door> &GD_GetDoors() { return GetState().doors_; }
746 975
747const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); } 976const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); }
748 977
978const PanelDoor &GD_GetPanelDoor(int panel_door_id) {
979 return GetState().panel_doors_.at(panel_door_id);
980}
981
749int GD_GetDoorByName(const std::string &name) { 982int GD_GetDoorByName(const std::string &name) {
750 return GetState().door_by_id_.at(name); 983 return GetState().door_by_id_.at(name);
751} 984}
@@ -754,8 +987,20 @@ const Panel &GD_GetPanel(int panel_id) {
754 return GetState().panels_.at(panel_id); 987 return GetState().panels_.at(panel_id);
755} 988}
756 989
757int GD_GetRoomForPainting(const std::string &painting_id) { 990int GD_GetPanelBySolveIndex(int solve_index) {
758 return GetState().room_by_painting_.at(painting_id); 991 return GetState().panel_by_solve_index_.at(solve_index);
992}
993
994const std::vector<PaintingExit> &GD_GetPaintings() {
995 return GetState().paintings_;
996}
997
998const PaintingExit &GD_GetPaintingExit(int painting_id) {
999 return GetState().paintings_.at(painting_id);
1000}
1001
1002int GD_GetPaintingByName(const std::string &name) {
1003 return GetState().painting_by_id_.at(name);
759} 1004}
760 1005
761const std::vector<int> &GD_GetAchievementPanels() { 1006const std::vector<int> &GD_GetAchievementPanels() {
@@ -782,15 +1027,48 @@ const SubwayItem &GD_GetSubwayItem(int id) {
782 return GetState().subway_items_.at(id); 1027 return GetState().subway_items_.at(id);
783} 1028}
784 1029
785int GD_GetSubwayItemForPainting(const std::string& painting_id) { 1030std::optional<int> GD_GetSubwayItemForPainting(const std::string &painting_id) {
786#ifndef NDEBUG 1031 if (GetState().subway_item_by_painting_.count(painting_id)) {
787 if (!GetState().subway_item_by_painting_.count(painting_id)) { 1032 return GetState().subway_item_by_painting_.at(painting_id);
788 wxLogError("No subway item for painting %s", painting_id);
789 } 1033 }
790#endif 1034 return std::nullopt;
791 return GetState().subway_item_by_painting_.at(painting_id);
792} 1035}
793 1036
794int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { 1037int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) {
795 return GetState().subway_item_by_sunwarp_.at(sunwarp); 1038 return GetState().subway_item_by_sunwarp_.at(sunwarp);
796} 1039}
1040
1041std::string GD_GetItemName(int id) {
1042 auto it = GetState().item_by_ap_id_.find(id);
1043 if (it != GetState().item_by_ap_id_.end()) {
1044 return it->second;
1045 } else {
1046 return "Unknown";
1047 }
1048}
1049
1050LingoColor GetLingoColorForString(const std::string &str) {
1051 if (str == "black") {
1052 return LingoColor::kBlack;
1053 } else if (str == "red") {
1054 return LingoColor::kRed;
1055 } else if (str == "blue") {
1056 return LingoColor::kBlue;
1057 } else if (str == "yellow") {
1058 return LingoColor::kYellow;
1059 } else if (str == "orange") {
1060 return LingoColor::kOrange;
1061 } else if (str == "green") {
1062 return LingoColor::kGreen;
1063 } else if (str == "gray") {
1064 return LingoColor::kGray;
1065 } else if (str == "brown") {
1066 return LingoColor::kBrown;
1067 } else if (str == "purple") {
1068 return LingoColor::kPurple;
1069 } else {
1070 TrackerLog(fmt::format("Invalid color: {}", str));
1071
1072 return LingoColor::kNone;
1073 }
1074}