#include "game_data.h"
#include <fmt/core.h>
#include <hkutil/string.h>
#include <yaml-cpp/yaml.h>
#include <iostream>
#include <sstream>
#include "global.h"
#include "logger.h"
namespace {
LingoColor GetColorForString(const std::string &str) {
if (str == "black") {
return LingoColor::kBlack;
} else if (str == "red") {
return LingoColor::kRed;
} else if (str == "blue") {
return LingoColor::kBlue;
} else if (str == "yellow") {
return LingoColor::kYellow;
} else if (str == "orange") {
return LingoColor::kOrange;
} else if (str == "green") {
return LingoColor::kGreen;
} else if (str == "gray") {
return LingoColor::kGray;
} else if (str == "brown") {
return LingoColor::kBrown;
} else if (str == "purple") {
return LingoColor::kPurple;
} else {
TrackerLog(fmt::format("Invalid color: {}", str));
return LingoColor::kNone;
}
}
struct GameData {
std::vector<Room> rooms_;
std::vector<Door> doors_;
std::vector<Panel> panels_;
std::vector<PanelDoor> panel_doors_;
std::vector<MapArea> map_areas_;
std::vector<SubwayItem> subway_items_;
std::vector<PaintingExit> paintings_;
std::map<std::string, int> room_by_id_;
std::map<std::string, int> door_by_id_;
std::map<std::string, int> panel_by_id_;
std::map<std::string, int> panel_doors_by_id_;
std::map<std::string, int> area_by_id_;
std::map<std::string, int> painting_by_id_;
std::vector<int> door_definition_order_;
std::vector<int> room_definition_order_;
std::map<std::string, int> room_by_painting_;
std::map<int, int> room_by_sunwarp_;
std::vector<int> achievement_panels_;
std::map<LingoColor, int> ap_id_by_color_;
std::vector<int> sunwarp_doors_;
std::map<std::string, int> subway_item_by_painting_;
std::map<SubwaySunwarp, int> subway_item_by_sunwarp_;
bool loaded_area_data_ = false;
std::set<std::string> malconfigured_areas_;
GameData() {
YAML::Node lingo_config =
YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml"));
YAML::Node areas_config =
YAML::LoadFile(GetAbsolutePath("assets/areas.yaml"));
YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml"));
auto init_color_id = [this, &ids_config](const std::string &color_name) {
if (ids_config["special_items"] &&
ids_config["special_items"][color_name]) {
std::string input_name = color_name;
input_name[0] = std::tolower(input_name[0]);
ap_id_by_color_[GetColorForString(input_name)] =
ids_config["special_items"][color_name].as<int>();
} else {
TrackerLog(fmt::format("Missing AP item ID for color {}", color_name));
}
};
init_color_id("Black");
init_color_id("Red");
init_color_id("Blue");
init_color_id("Yellow");
init_color_id("Green");
init_color_id("Orange");
init_color_id("Purple");
init_color_id("Brown");
init_color_id("Gray");
rooms_.reserve(lingo_config.size() * 2);
for (const auto &room_it : lingo_config) {
int room_id = AddOrGetRoom(room_it.first.as<std::string>());
room_definition_order_.push_back(room_id);
for (const auto &entrance_it : room_it.second["entrances"]) {
int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
auto process_single_entrance =
[this, room_id, from_room_id](const YAML::Node &option) {
Exit exit_obj;
exit_obj.source_room = from_room_id;
exit_obj.destination_room = room_id;
if (option["door"]) {
std::string door_room = rooms_[room_id].name;
if (option["room"]) {
door_room = option["room"].as<std::string>();
}
exit_obj.door =
AddOrGetDoor(door_room, option["door"].as<std::string>());
}
if (option["painting"] && option["painting"].as<bool>()) {
exit_obj.type = EntranceType::kPainting;
}
if (option["sunwarp"] && option["sunwarp"].as<bool>()) {
exit_obj.type = EntranceType::kSunwarp;
}
if (option["warp"] && option["warp"].as<bool>()) {
exit_obj.type = EntranceType::kWarp;
}
if (rooms_[from_room_id].name == "Crossroads" &&
rooms_[room_id].name == "Roof") {
exit_obj.type = EntranceType::kCrossroadsRoofAccess;
}
rooms_[from_room_id].exits.push_back(exit_obj);
};
switch (entrance_it.second.Type()) {
case YAML::NodeType::Scalar: {
// This is just "true".
rooms_[from_room_id].exits.push_back({.source_room = from_room_id, .destination_room = room_id});
break;
}
case YAML::NodeType::Map: {
process_single_entrance(entrance_it.second);
break;
}
case YAML::NodeType::Sequence: {
for (const auto &option : entrance_it.second) {
process_single_entrance(option);
}
break;
}
default: {
// This shouldn't happen.
std::ostringstream formatted;
formatted << entrance_it;
TrackerLog(
fmt::format("Error reading game data: {}", formatted.str()));
break;
}
}
}
if (room_it.second["panels"]) {
for (const auto &panel_it : room_it.second["panels"]) {
int panel_id = AddOrGetPanel(rooms_[room_id].name,
panel_it.first.as<std::string>());
rooms_[room_id].panels.push_back(panel_id);
if (panel_it.second["colors"]) {
if (panel_it.second["colors"].IsScalar()) {
panels_[panel_id].colors.push_back(GetColorForString(
panel_it.second["colors"].as<std::string>()));
} else {
for (const auto &color_node : panel_it.second["colors"]) {
panels_[panel_id].colors.push_back(
GetColorForString(color_node.as<std::string>()));
}
}
}
if (panel_it.second["required_room"]) {
if (panel_it.second["required_room"].IsScalar()) {
panels_[panel_id].required_rooms.push_back(AddOrGetRoom(
panel_it.second["required_room"].as<std::string>()));
} else {
for (const auto &rr_node : panel_it.second["required_room"]) {
panels_[panel_id].required_rooms.push_back(
AddOrGetRoom(rr_node.as<std::string>()));
}
}
}
if (panel_it.second["required_door"]) {
if (panel_it.second["required_door"].IsMap()) {
std::string rd_room = rooms_[room_id].name;
if (panel_it.second["required_door"]["room"]) {
rd_room =
panel_it.second["required_door"]["room"].as<std::string>();
}
panels_[panel_id].required_doors.push_back(AddOrGetDoor(
rd_room,
panel_it.second["required_door"]["door"].as<std::string>()));
} else {
for (const auto &rr_node : panel_it.second["required_door"]) {
std::string rd_room = rooms_[room_id].name;
if (rr_node["room"]) {
rd_room = rr_node["room"].as<std::string>();
};
panels_[panel_id].required_doors.push_back(
AddOrGetDoor(rd_room, rr_node["door"].as<std::string>()));
}
}
}
if (panel_it.second["required_panel"]) {
if (panel_it.second["required_panel"].IsMap()) {
std::string rp_room = rooms_[room_id].name;
if (panel_it.second["required_panel"]["room"]) {
rp_room =
panel_it.second["required_panel"]["room"].as<std::string>();
}
int rp_id = AddOrGetPanel(
rp_room,
panel_it.second["required_panel"]["panel"].as<std::string>());
panels_[panel_id].required_panels.push_back(rp_id);
} else {
for (const auto &rp_node : panel_it.second["required_panel"]) {
std::string rp_room = rooms_[room_id].name;
if (rp_node["room"]) {
rp_room = rp_node["room"].as<std::string>();
}
int rp_id =
AddOrGetPanel(rp_room, rp_node["panel"].as<std::string>());
panels_[panel_id].required_panels.push_back(rp_id);
}
}
}
if (panel_it.second["check"]) {
panels_[panel_id].check = panel_it.second["check"].as<bool>();
}
if (panel_it.second["achievement"]) {
panels_[panel_id].achievement = true;
panels_[panel_id].achievement_name =
panel_it.second["achievement"].as<std::string>();
achievement_panels_.push_back(panel_id);
}
if (panel_it.second["location_name"]) {
panels_[panel_id].location_name =
panel_it.second["location_name"].as<std::string>();
}
if (panel_it.second["id"]) {
panels_[panel_id].nodepath =
panel_it.second["id"].as<std::string>();
}
if (panel_it.second["hunt"]) {
panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>();
}
if (panel_it.second["exclude_reduce"]) {
panels_[panel_id].exclude_reduce =
panel_it.second["exclude_reduce"].as<bool>();
}
if (panel_it.second["non_counting"]) {
panels_[panel_id].non_counting =
panel_it.second["non_counting"].as<bool>();
}
if (ids_config["panels"] &&
ids_config["panels"][rooms_[room_id].name] &&
ids_config["panels"][rooms_[room_id].name]
[panels_[panel_id].name]) {
panels_[panel_id].ap_location_id =
ids_config["panels"][rooms_[room_id].name]
[panels_[panel_id].name]
.as<int>();
} else {
TrackerLog(fmt::format("Missing AP location ID for panel {} - {}",
rooms_[room_id].name,
panels_[panel_id].name));
}
}
}
if (room_it.second["doors"]) {
for (const auto &door_it : room_it.second["doors"]) {
int door_id = AddOrGetDoor(rooms_[room_id].name,
door_it.first.as<std::string>());
door_definition_order_.push_back(door_id);
bool has_external_panels = false;
std::vector<std::string> panel_names;
for (const auto &panel_node : door_it.second["panels"]) {
if (panel_node.IsScalar()) {
panel_names.push_back(panel_node.as<std::string>());
doors_[door_id].panels.push_back(AddOrGetPanel(
rooms_[room_id].name, panel_node.as<std::string>()));
} else {
has_external_panels = true;
panel_names.push_back(panel_node["panel"].as<std::string>());
doors_[door_id].panels.push_back(
AddOrGetPanel(panel_node["room"].as<std::string>(),
panel_node["panel"].as<std::string>()));
}
}
if (door_it.second["skip_location"]) {
doors_[door_id].skip_location =
door_it.second["skip_location"].as<bool>();
}
if (door_it.second["skip_item"]) {
doors_[door_id].skip_item = door_it.second["skip_item"].as<bool>();
}
if (door_it.second["event"]) {
doors_[door_id].skip_location = door_it.second["event"].as<bool>();
doors_[door_id].skip_item = door_it.second["event"].as<bool>();
doors_[door_id].is_event = door_it.second["event"].as<bool>();
}
if (door_it.second["item_name"]) {
doors_[door_id].item_name =
door_it.second["item_name"].as<std::string>();
} else if (!door_it.second["skip_item"] && !door_it.second["event"]) {
doors_[door_id].item_name =
rooms_[room_id].name + " - " + doors_[door_id].name;
}
if (!door_it.second["skip_item"] && !door_it.second["event"]) {
if (ids_config["doors"] &&
ids_config["doors"][rooms_[room_id].name] &&
ids_config["doors"][rooms_[room_id].name]
[doors_[door_id].name] &&
ids_config["doors"][rooms_[room_id].name][doors_[door_id].name]
["item"]) {
doors_[door_id].ap_item_id =
ids_config["doors"][rooms_[room_id].name]
[doors_[door_id].name]["item"]
.as<int>();
} else {
TrackerLog(fmt::format("Missing AP item ID for door {} - {}",
rooms_[room_id].name,
doors_[door_id].name));
}
}
if (door_it.second["door_group"]) {
doors_[door_id].group_name =
door_it.second["door_group"].as<std::string>();
if (ids_config["door_groups"] &&
ids_config["door_groups"][doors_[door_id].group_name]) {
doors_[door_id].group_ap_item_id =
ids_config["door_groups"][doors_[door_id].group_name]
.as<int>();
} else {
TrackerLog(fmt::format("Missing AP item ID for door group {}",
doors_[door_id].group_name));
}
}
if (door_it.second["location_name"]) {
doors_[door_id].location_name =
door_it.second["location_name"].as<std::string>();
} else if (!door_it.second["skip_location"] &&
!door_it.second["event"]) {
if (has_external_panels) {
TrackerLog(fmt::format(
"{} - {} has panels from other rooms but does not have an "
"explicit location name and is not marked skip_location or "
"event",
rooms_[room_id].name, doors_[door_id].name));
}
doors_[door_id].location_name =
rooms_[room_id].name + " - " +
hatkirby::implode(panel_names, ", ");
}
if (!door_it.second["skip_location"] && !door_it.second["event"]) {
if (ids_config["doors"] &&
ids_config["doors"][rooms_[room_id].name] &&
ids_config["doors"][rooms_[room_id].name]
[doors_[door_id].name] &&
ids_config["doors"][rooms_[room_id].name][doors_[door_id].name]
["location"]) {
doors_[door_id].ap_location_id =
ids_config["doors"][rooms_[room_id].name]
[doors_[door_id].name]["location"]
.as<int>();
} else {
TrackerLog(fmt::format("Missing AP location ID for door {} - {}",
rooms_[room_id].name,
doors_[door_id].name));
}
}
if (door_it.second["include_reduce"]) {
doors_[door_id].exclude_reduce =
!door_it.second["include_reduce"].as<bool>();
}
if (doors_[door_id].name.ends_with(" Sunwarp")) {
sunwarp_doors_.push_back(door_id);
doors_[door_id].type = DoorType::kSunwarp;
} else if (doors_[door_id].item_name ==
"Pilgrim Room - Sun Painting") {
doors_[door_id].type = DoorType::kSunPainting;
}
}
}
if (room_it.second["panel_doors"]) {
for (const auto &panel_door_it : room_it.second["panel_doors"]) {
std::string panel_door_name = panel_door_it.first.as<std::string>();
int panel_door_id =
AddOrGetPanelDoor(rooms_[room_id].name, panel_door_name);
for (const auto &panel_node : panel_door_it.second["panels"]) {
int panel_id = -1;
if (panel_node.IsScalar()) {
panel_id = AddOrGetPanel(rooms_[room_id].name,
panel_node.as<std::string>());
} else {
panel_id = AddOrGetPanel(panel_node["room"].as<std::string>(),
panel_node["panel"].as<std::string>());
}
Panel &panel = panels_[panel_id];
panel.panel_door = panel_door_id;
}
if (ids_config["panel_doors"] &&
ids_config["panel_doors"][rooms_[room_id].name] &&
ids_config["panel_doors"][rooms_[room_id].name]
[panel_door_name]) {
panel_doors_[panel_door_id].ap_item_id =
ids_config["panel_doors"][rooms_[room_id].name][panel_door_name]
.as<int>();
} else {
TrackerLog(fmt::format("Missing AP item ID for panel door {} - {}",
rooms_[room_id].name, panel_door_name));
}
if (panel_door_it.second["panel_group"]) {
std::string panel_group =
panel_door_it.second["panel_group"].as<std::string>();
if (ids_config["panel_groups"] &&
ids_config["panel_groups"][panel_group]) {
panel_doors_[panel_door_id].group_ap_item_id =
ids_config["panel_groups"][panel_group].as<int>();
} else {
TrackerLog(fmt::format(
"Missing AP item ID for panel door group {}", panel_group));
}
}
}
}
if (room_it.second["paintings"]) {
for (const auto &painting : room_it.second["paintings"]) {
std::string internal_id = painting["id"].as<std::string>();
int painting_id = AddOrGetPainting(internal_id);
PaintingExit &painting_exit = paintings_[painting_id];
painting_exit.room = room_id;
if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) &&
(!painting["disable"] || !painting["disable"].as<bool>())) {
painting_exit.entrance = true;
if (painting["required_door"]) {
std::string rd_room = rooms_[room_id].name;
if (painting["required_door"]["room"]) {
rd_room = painting["required_door"]["room"].as<std::string>();
}
painting_exit.door = AddOrGetDoor(
rd_room, painting["required_door"]["door"].as<std::string>());
}
}
rooms_[room_id].paintings.push_back(painting_exit.id);
}
}
if (room_it.second["sunwarps"]) {
for (const auto &sunwarp : room_it.second["sunwarps"]) {
int index = sunwarp["dots"].as<int>() - 1;
if (sunwarp["direction"].as<std::string>() == "exit") {
index += 6;
}
rooms_[room_id].sunwarps.push_back(index);
room_by_sunwarp_[index] = room_id;
}
}
if (room_it.second["progression"]) {
for (const auto &progression_it : room_it.second["progression"]) {
std::string progressive_item_name =
progression_it.first.as<std::string>();
int progressive_item_id = -1;
if (ids_config["progression"] &&
ids_config["progression"][progressive_item_name]) {
progressive_item_id =
ids_config["progression"][progressive_item_name].as<int>();
} else {
TrackerLog(fmt::format("Missing AP item ID for progressive item {}",
progressive_item_name));
}
if (progression_it.second["doors"]) {
int index = 1;
for (const auto &stage : progression_it.second["doors"]) {
int door_id = -1;
if (stage.IsScalar()) {
door_id =
AddOrGetDoor(rooms_[room_id].name, stage.as<std::string>());
} else {
door_id = AddOrGetDoor(stage["room"].as<std::string>(),
stage["door"].as<std::string>());
}
doors_[door_id].progressives.push_back(
{.item_name = progressive_item_name,
.ap_item_id = progressive_item_id,
.quantity = index});
index++;
}
}
if (progression_it.second["panel_doors"]) {
int index = 1;
for (const auto &stage : progression_it.second["panel_doors"]) {
int panel_door_id = -1;
if (stage.IsScalar()) {
panel_door_id = AddOrGetPanelDoor(rooms_[room_id].name,
stage.as<std::string>());
} else {
panel_door_id =
AddOrGetPanelDoor(stage["room"].as<std::string>(),
stage["panel_door"].as<std::string>());
}
panel_doors_[panel_door_id].progressives.push_back(
{.item_name = progressive_item_name,
.ap_item_id = progressive_item_id,
.quantity = index});
index++;
}
}
}
}
}
map_areas_.reserve(areas_config.size());
std::map<std::string, int> fold_areas;
for (const auto &area_it : areas_config) {
if (area_it.second["map"]) {
int area_id = AddOrGetArea(area_it.first.as<std::string>());
MapArea &area_obj = map_areas_[area_id];
area_obj.map_x = area_it.second["map"][0].as<int>();
area_obj.map_y = area_it.second["map"][1].as<int>();
} else if (area_it.second["fold_into"]) {
fold_areas[area_it.first.as<std::string>()] =
AddOrGetArea(area_it.second["fold_into"].as<std::string>());
}
}
loaded_area_data_ = true;
// Only locations for the panels are kept here.
std::map<std::string, std::tuple<int, int>> locations_by_name;
for (const Panel &panel : panels_) {
int room_id = panel.room;
std::string room_name = rooms_[room_id].name;
std::string area_name = room_name;
std::string section_name = panel.name;
std::string location_name = room_name + " - " + panel.name;
if (!panel.location_name.empty()) {
location_name = panel.location_name;
size_t divider_pos = location_name.find(" - ");
if (divider_pos != std::string::npos) {
area_name = location_name.substr(0, divider_pos);
section_name = location_name.substr(divider_pos + 3);
}
}
if (fold_areas.count(area_name)) {
int fold_area_id = fold_areas[area_name];
area_name = map_areas_[fold_area_id].name;
}
int classification = kLOCATION_INSANITY;
if (panel.check) {
classification |= kLOCATION_NORMAL;
if (!panel.exclude_reduce) {
classification |= kLOCATION_REDUCED;
}
}
if (room_name == "Starting Room") {
classification |= kLOCATION_SMALL_SPHERE_ONE;
}
int area_id = AddOrGetArea(area_name);
MapArea &map_area = map_areas_[area_id];
// room field should be the original room ID
map_area.locations.push_back({.name = section_name,
.ap_location_name = location_name,
.ap_location_id = panel.ap_location_id,
.room = panel.room,
.panels = {panel.id},
.classification = classification,
.hunt = panel.hunt,
.single_panel = panel.id});
locations_by_name[location_name] = {area_id,
map_area.locations.size() - 1};
}
for (int door_id : door_definition_order_) {
const Door &door = doors_.at(door_id);
if (!door.skip_location) {
int classification = kLOCATION_NORMAL;
if (!door.exclude_reduce) {
classification |= kLOCATION_REDUCED;
}
if (locations_by_name.count(door.location_name)) {
auto [area_id, section_id] = locations_by_name[door.location_name];
map_areas_[area_id].locations[section_id].classification |=
classification;
} else {
int room_id = door.room;
std::string area_name = rooms_[room_id].name;
std::string section_name;
size_t divider_pos = door.location_name.find(" - ");
if (divider_pos == std::string::npos) {
section_name = door.location_name;
} else {
area_name = door.location_name.substr(0, divider_pos);
section_name = door.location_name.substr(divider_pos + 3);
}
if (fold_areas.count(area_name)) {
int fold_area_id = fold_areas[area_name];
area_name = map_areas_[fold_area_id].name;
}
int area_id = AddOrGetArea(area_name);
MapArea &map_area = map_areas_[area_id];
// room field should be the original room ID
map_area.locations.push_back({.name = section_name,
.ap_location_name = door.location_name,
.ap_location_id = door.ap_location_id,
.room = door.room,
.panels = door.panels,
.classification = classification});
}
}
}
for (MapArea &map_area : map_areas_) {
for (const Location &location : map_area.locations) {
map_area.classification |= location.classification;
map_area.hunt |= location.hunt;
map_area.has_single_panel |= location.single_panel.has_value();
}
}
for (const Room &room : rooms_) {
std::string area_name = room.name;
if (fold_areas.count(room.name)) {
int fold_area_id = fold_areas[room.name];
area_name = map_areas_[fold_area_id].name;
}
if (!room.paintings.empty()) {
int area_id = AddOrGetArea(area_name);
MapArea &map_area = map_areas_[area_id];
for (int painting_id : room.paintings) {
const PaintingExit &painting_obj = paintings_.at(painting_id);
if (painting_obj.entrance) {
map_area.paintings.push_back(painting_id);
}
}
}
}
// As a workaround for a generator bug in 0.5.1, we are going to remove the
// panel door requirement on panels that are defined earlier in the file than
// the panel door is. This results in logic that matches the generator, even
// if it is not true to how the game should work. This will be reverted once
// the logic bug is fixed and released.
// See: https://github.com/ArchipelagoMW/Archipelago/pull/4342
for (Panel& panel : panels_) {
if (panel.panel_door == -1) {
continue;
}
const PanelDoor &panel_door = panel_doors_[panel.panel_door];
for (int room_id : room_definition_order_) {
if (room_id == panel_door.room) {
// The panel door was defined first (or at the same time as the panel),
// so we're good.
break;
} else if (room_id == panel.room) {
// The panel was defined first, so we have to pretend the panel door is
// not required for this panel.
panel.panel_door = -1;
break;
}
}
}
// Report errors.
for (const std::string &area : malconfigured_areas_) {
TrackerLog(fmt::format("Area data not found for: {}", area));
}
// Read in subway items.
YAML::Node subway_config =
YAML::LoadFile(GetAbsolutePath("assets/subway.yaml"));
for (const auto &subway_it : subway_config) {
SubwayItem subway_item;
subway_item.id = subway_items_.size();
subway_item.x = subway_it["pos"][0].as<int>();
subway_item.y = subway_it["pos"][1].as<int>();
if (subway_it["door"]) {
subway_item.door = AddOrGetDoor(subway_it["room"].as<std::string>(),
subway_it["door"].as<std::string>());
}
if (subway_it["paintings"]) {
for (const auto &painting_it : subway_it["paintings"]) {
std::string painting_id = painting_it.as<std::string>();
subway_item.paintings.push_back(painting_id);
subway_item_by_painting_[painting_id] = subway_item.id;
}
}
if (subway_it["tags"]) {
for (const auto &tag_it : subway_it["tags"]) {
subway_item.tags.push_back(tag_it.as<std::string>());
}
}
if (subway_it["entrances"]) {
for (const auto &entrance_it : subway_it["entrances"]) {
subway_item.entrances.push_back(entrance_it.as<std::string>());
}
}
if (subway_it["exits"]) {
for (const auto &exit_it : subway_it["exits"]) {
subway_item.exits.push_back(exit_it.as<std::string>());
}
}
if (subway_it["sunwarp"]) {
SubwaySunwarp sunwarp;
sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>();
std::string sunwarp_type =
subway_it["sunwarp"]["type"].as<std::string>();
if (sunwarp_type == "final") {
sunwarp.type = SubwaySunwarpType::kFinal;
} else if (sunwarp_type == "exit") {
sunwarp.type = SubwaySunwarpType::kExit;
} else {
sunwarp.type = SubwaySunwarpType::kEnter;
}
subway_item.sunwarp = sunwarp;
subway_item_by_sunwarp_[sunwarp] = subway_item.id;
subway_item.door =
AddOrGetDoor("Sunwarps", std::to_string(sunwarp.dots) + " Sunwarp");
}
if (subway_it["special"]) {
subway_item.special = subway_it["special"].as<std::string>();
}
subway_items_.push_back(subway_item);
}
// Find singleton subway tags.
std::map<std::string, std::set<int>> subway_tags;
for (const SubwayItem &subway_item : subway_items_) {
for (const std::string &tag : subway_item.tags) {
subway_tags[tag].insert(subway_item.id);
}
}
for (const auto &[tag, items] : subway_tags) {
if (items.size() == 1) {
TrackerLog(fmt::format("Singleton subway item tag: {}", tag));
}
}
}
int AddOrGetRoom(std::string room) {
if (!room_by_id_.count(room)) {
room_by_id_[room] = rooms_.size();
rooms_.push_back({.name = room});
}
return room_by_id_[room];
}
int AddOrGetDoor(std::string room, std::string door) {
std::string full_name = room + " - " + door;
if (!door_by_id_.count(full_name)) {
int door_id = doors_.size();
door_by_id_[full_name] = doors_.size();
doors_.push_back(
{.id = door_id, .room = AddOrGetRoom(room), .name = door});
}
return door_by_id_[full_name];
}
int AddOrGetPanel(std::string room, std::string panel) {
std::string full_name = room + " - " + panel;
if (!panel_by_id_.count(full_name)) {
int panel_id = panels_.size();
panel_by_id_[full_name] = panel_id;
panels_.push_back(
{.id = panel_id, .room = AddOrGetRoom(room), .name = panel});
}
return panel_by_id_[full_name];
}
int AddOrGetPanelDoor(std::string room, std::string panel) {
std::string full_name = room + " - " + panel;
if (!panel_doors_by_id_.count(full_name)) {
int panel_door_id = panel_doors_.size();
panel_doors_by_id_[full_name] = panel_door_id;
panel_doors_.push_back({.room = AddOrGetRoom(room)});
}
return panel_doors_by_id_[full_name];
}
int AddOrGetArea(std::string area) {
if (!area_by_id_.count(area)) {
if (loaded_area_data_) {
malconfigured_areas_.insert(area);
}
int area_id = map_areas_.size();
area_by_id_[area] = area_id;
map_areas_.push_back({.id = area_id, .name = area});
}
return area_by_id_[area];
}
int AddOrGetPainting(std::string internal_id) {
if (!painting_by_id_.count(internal_id)) {
int painting_id = paintings_.size();
painting_by_id_[internal_id] = painting_id;
paintings_.push_back({.id = painting_id, .internal_id = internal_id});
}
return painting_by_id_[internal_id];
}
};
GameData &GetState() {
static GameData *instance = new GameData();
return *instance;
}
} // namespace
bool SubwayItem::HasWarps() const {
return !(this->tags.empty() && this->entrances.empty() &&
this->exits.empty());
}
bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const {
return std::tie(dots, type) < std::tie(rhs.dots, rhs.type);
}
const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; }
const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); }
int GD_GetRoomByName(const std::string &name) {
return GetState().room_by_id_.at(name);
}
const Room &GD_GetRoom(int room_id) { return GetState().rooms_.at(room_id); }
const std::vector<Door> &GD_GetDoors() { return GetState().doors_; }
const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); }
const PanelDoor &GD_GetPanelDoor(int panel_door_id) {
return GetState().panel_doors_.at(panel_door_id);
}
int GD_GetDoorByName(const std::string &name) {
return GetState().door_by_id_.at(name);
}
const Panel &GD_GetPanel(int panel_id) {
return GetState().panels_.at(panel_id);
}
const PaintingExit &GD_GetPaintingExit(int painting_id) {
return GetState().paintings_.at(painting_id);
}
int GD_GetPaintingByName(const std::string &name) {
return GetState().painting_by_id_.at(name);
}
const std::vector<int> &GD_GetAchievementPanels() {
return GetState().achievement_panels_;
}
int GD_GetItemIdForColor(LingoColor color) {
return GetState().ap_id_by_color_.at(color);
}
const std::vector<int> &GD_GetSunwarpDoors() {
return GetState().sunwarp_doors_;
}
int GD_GetRoomForSunwarp(int index) {
return GetState().room_by_sunwarp_.at(index);
}
const std::vector<SubwayItem> &GD_GetSubwayItems() {
return GetState().subway_items_;
}
const SubwayItem &GD_GetSubwayItem(int id) {
return GetState().subway_items_.at(id);
}
std::optional<int> GD_GetSubwayItemForPainting(const std::string &painting_id) {
if (GetState().subway_item_by_painting_.count(painting_id)) {
return GetState().subway_item_by_painting_.at(painting_id);
}
return std::nullopt;
}
int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) {
return GetState().subway_item_by_sunwarp_.at(sunwarp);
}