about summary refs log blame commit diff stats
path: root/src/game_data.cpp
blob: 0ac77af6830533ba638bd6311f47b806c50c6c8a (plain) (tree)
1
2
3
4
5
6
7
8
9
                      
                     
                          
                          
                   
                  
                   
                   
 
           


















                                                      
                                                      
 


                             


                             
                                      
                                  
                                        
                                       
 

                                          
                                                
                                         
                                             
 
                                          
                                          
 
                                               
                                      
 
                                       
 
                                            
                                  

                                                       

                                             
              


                                                             
                                                                               







                                                                             
                                                                               










                            
 
                                            
 
                                                                  
                                                

                                                                             
 

                                                                     
                                                   





                                                               
                                                                              












                                                                        
                                                              
                                                                    
               

                                                             

                                            
                                                                                                             

                                     
                                                        
                  
           
                                                           
                                              
             



                                     
                                         
                                                                             
                  
           
       
 
                                                               

                                                                         

                                                       
                                                                   

                                                                        
                                                   
                                                                     
             
 
                                                              
                                                                      

                                                                            
                                                           
                                                             
             
           
 
                                                           
                                                         

                                                                               
               
                                                                      


                                                                               
                                                           
                                                              
                                                           
                                                                              
             
 
                                                            
                                                         
                                                              
                                                                                
               


                                                                                
                                                                             
                                                           


                                                              

                                                                               


               
                                         
                                                                          
           
 
                                               
                                                 
                                                                 
 
                                                    
           
 



                                                                   



                                                        
                                        
                                                                        
           
                                                  
                                              
                                                             
                                                
                                                           
           
 






                                                           
                  

                                                                              
           
         
 
                                                             
                                                                      
                                                    





                                                                   
                                                                       

                                                                           
                                               

                                                                        
           
 
                                                
                                                           
           
 
                                            
                                                                               
           
 
                                        

                                                                               
           
 
                                            
                                                              
                                                                                
                                                                    
           
 
                                                                         








                                                                               
                    

                                                                           

             
                                             
                                        
                                                               
                                            


                                                                        
                    
                                                                            
             

                                                
                                           


                                                                  
                                                                             
                                                                              
                                                               
             
 

                                                     
           
                                                                             








                                                                               
                    

                                                                               

             
                                                 
                                            
                                                             






                                                           
         
       
 



























                                                                                
                                                                               









                                                                      
                                                                              



             
                                                                  
                                                                     

                                                                
 
                                                                              
                                          
                                            
                                                         





                                                                                
           
                                                                
         
 










                                                                 


                                                                          
 




                                                                           
                                                                                
           


                                                                      
 





                                                                                
 



                                                      
             
           
 











                                                                             
 




                                                                 
           

         
 












                                                                        
 



                                                                  
                                        
                                                   
 
                                        













                                                                 
                                                  
 




                                              
       
 


                                                     

                                                  




                                                                           
                                                       
                                                               
                                                                         
     

                                                
                                


                                              
 


                                                                             
                









                                                                      
 


                                                      
 



                                                                               
                                                                              


                                                                           


                                          
                                                           
                                                           
                                       
                                                                       
       
 









                                                  
                                                


                                                                        


         























                                                                                 
                                                          
                                                                   
     
 














                                                                             


                                                                  







                                                               










                                                                         













                                                              



                                                                                






                                                                     









                                                         
                                                                      
       
     
   
 


                                        
     
 
                             
 
                                                        
 
                                        
                                  
                                             
                                                                     
     
 
                                  
 
                                                          
 



                                                                       
     
                                   
   
 




                                                              
                                                           



                                         
                                      


                                          




                                                          
   








                                                                            
  
 

                                             
 
               
 



                                                           
                                                               

                                                             

                                                                              
 
                                               
 
                                                                             
 
                                                                    
 
                                                                             
 


                                                     


                                               

                                         
 




                                                         
 
                                                   
 


                                              


                                              


                                               






                                                    
 

                                                                                
   
                      



                                                              
#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);
}