#include "tracker_state.h" #include #include #include #include #include #include #include #include #include "ap_state.h" #include "game_data.h" #include "logger.h" namespace { struct Requirements { bool disabled = false; std::set doors; // non-grouped, handles progressive std::set panel_doors; // non-grouped, handles progressive std::set items; // all other items std::set rooms; // maybe bool mastery = false; // maybe bool panel_hunt = false; // maybe void Merge(const Requirements& rhs) { if (rhs.disabled) { return; } for (int id : rhs.doors) { doors.insert(id); } for (int id : rhs.panel_doors) { panel_doors.insert(id); } for (int id : rhs.items) { items.insert(id); } for (int id : rhs.rooms) { rooms.insert(id); } mastery = mastery || rhs.mastery; panel_hunt = panel_hunt || rhs.panel_hunt; } }; class RequirementCalculator { public: void Reset() { doors_.clear(); panels_.clear(); } const Requirements& GetDoor(int door_id) { if (!doors_.count(door_id)) { Requirements requirements; const Door& door_obj = GD_GetDoor(door_id); if (door_obj.type == DoorType::kSunPainting) { if (!AP_IsPilgrimageEnabled()) { requirements.items.insert(door_obj.ap_item_id); } else { requirements.disabled = true; } } else if (door_obj.type == DoorType::kSunwarp) { switch (AP_GetSunwarpAccess()) { case kSUNWARP_ACCESS_NORMAL: // Do nothing. break; case kSUNWARP_ACCESS_DISABLED: requirements.disabled = true; break; case kSUNWARP_ACCESS_UNLOCK: requirements.items.insert(door_obj.group_ap_item_id); break; case kSUNWARP_ACCESS_INDIVIDUAL: case kSUNWARP_ACCESS_PROGRESSIVE: requirements.doors.insert(door_obj.id); break; } } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { requirements.rooms.insert(door_obj.room); for (int panel_id : door_obj.panels) { const Requirements& panel_reqs = GetPanel(panel_id); requirements.Merge(panel_reqs); } } else if (AP_AreDoorsGrouped() && !door_obj.group_name.empty()) { requirements.items.insert(door_obj.group_ap_item_id); } else { requirements.doors.insert(door_obj.id); } doors_[door_id] = requirements; } return doors_[door_id]; } const Requirements& GetPanel(int panel_id) { if (!panels_.count(panel_id)) { Requirements requirements; const Panel& panel_obj = GD_GetPanel(panel_id); requirements.rooms.insert(panel_obj.room); if (panel_obj.name == "THE MASTER") { requirements.mastery = true; } if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && AP_GetLevel2Requirement() > 1) { requirements.panel_hunt = true; } for (int room_id : panel_obj.required_rooms) { requirements.rooms.insert(room_id); } for (int door_id : panel_obj.required_doors) { const Requirements& door_reqs = GetDoor(door_id); requirements.Merge(door_reqs); } for (int panel_id : panel_obj.required_panels) { const Requirements& panel_reqs = GetPanel(panel_id); requirements.Merge(panel_reqs); } if (AP_IsColorShuffle()) { for (LingoColor color : panel_obj.colors) { requirements.items.insert(GD_GetItemIdForColor(color)); } } if (panel_obj.panel_door != -1 && AP_GetDoorShuffleMode() == kPANELS_MODE) { const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_obj.panel_door); if (panel_door_obj.group_ap_item_id != -1 && AP_AreDoorsGrouped()) { requirements.items.insert(panel_door_obj.group_ap_item_id); } else { requirements.panel_doors.insert(panel_obj.panel_door); } } panels_[panel_id] = requirements; } return panels_[panel_id]; } private: std::map doors_; std::map panels_; }; struct TrackerState { std::map reachability; std::set reachable_doors; std::set reachable_paintings; std::mutex reachability_mutex; RequirementCalculator requirements; std::map> door_reports; bool pilgrimage_doable = false; }; enum Decision { kYes, kNo, kMaybe }; TrackerState& GetState() { static TrackerState* instance = new TrackerState(); return *instance; } class StateCalculator; struct StateCalculatorOptions { int start; bool pilgrimage = false; StateCalculator* parent = nullptr; }; class StateCalculator { public: StateCalculator() = default; explicit StateCalculator(StateCalculatorOptions options) : options_(options) {} void Calculate() { std::list panel_boundary; std::list painting_boundary; std::list flood_boundary; flood_boundary.push_back( {.source_room = -1, .destination_room = options_.start}); bool reachable_changed = true; while (reachable_changed) { reachable_changed = false; std::list new_boundary; std::list new_panel_boundary; for (int panel_id : panel_boundary) { if (solveable_panels_.count(panel_id)) { continue; } Decision panel_reachable = IsPanelReachable(panel_id); if (panel_reachable == kYes) { solveable_panels_.insert(panel_id); reachable_changed = true; } else if (panel_reachable == kMaybe) { new_panel_boundary.push_back(panel_id); } } std::list new_painting_boundary; for (int painting_id : painting_boundary) { if (reachable_paintings_.count(painting_id)) { continue; } Decision painting_reachable = IsPaintingReachable(painting_id); if (painting_reachable == kYes) { reachable_paintings_.insert(painting_id); reachable_changed = true; PaintingExit cur_painting = GD_GetPaintingExit(painting_id); if (AP_GetPaintingMapping().count(cur_painting.internal_id) && AP_GetCheckedPaintings().count(cur_painting.internal_id)) { Exit painting_exit; PaintingExit target_painting = GD_GetPaintingExit(GD_GetPaintingByName( AP_GetPaintingMapping().at(cur_painting.internal_id))); painting_exit.source_room = cur_painting.room; painting_exit.destination_room = target_painting.room; painting_exit.type = EntranceType::kPainting; new_boundary.push_back(painting_exit); } } else if (painting_reachable == kMaybe) { new_painting_boundary.push_back(painting_id); } } for (const Exit& room_exit : flood_boundary) { if (reachable_rooms_.count(room_exit.destination_room)) { continue; } bool valid_transition = false; Decision exit_usable = IsExitUsable(room_exit); if (exit_usable == kYes) { valid_transition = true; } else if (exit_usable == kMaybe) { new_boundary.push_back(room_exit); } if (valid_transition) { reachable_rooms_.insert(room_exit.destination_room); reachable_changed = true; #ifndef NDEBUG std::list room_path = paths_[room_exit.source_room]; room_path.push_back(room_exit.destination_room); paths_[room_exit.destination_room] = room_path; #endif const Room& room_obj = GD_GetRoom(room_exit.destination_room); for (const Exit& out_edge : room_obj.exits) { if (out_edge.type == EntranceType::kPainting && AP_IsPaintingShuffle()) { continue; } if (out_edge.type == EntranceType::kSunwarp && AP_IsSunwarpShuffle()) { continue; } new_boundary.push_back(out_edge); } if (AP_IsPaintingShuffle()) { for (int out_edge : room_obj.paintings) { new_painting_boundary.push_back(out_edge); } } if (AP_IsSunwarpShuffle()) { for (int index : room_obj.sunwarps) { if (AP_GetSunwarpMapping().count(index)) { const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomForSunwarp(sm.exit_index), .door = GD_GetSunwarpDoors().at(sm.dots - 1), .type = EntranceType::kSunwarp}); } } } if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomByName("Color Hallways"), .type = EntranceType::kPainting}); } if (AP_IsPilgrimageEnabled()) { int pilgrimage_start_id = GD_GetRoomByName("Hub Room"); if (AP_IsSunwarpShuffle()) { for (const auto& [start_index, mapping] : AP_GetSunwarpMapping()) { if (mapping.dots == 1) { pilgrimage_start_id = GD_GetRoomForSunwarp(start_index); } } } if (room_exit.destination_room == pilgrimage_start_id) { new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomByName("Pilgrim Antechamber"), .type = EntranceType::kPilgrimage}); } } else { if (room_obj.name == "Starting Room") { new_boundary.push_back( {.source_room = room_exit.destination_room, .destination_room = GD_GetRoomByName("Pilgrim Antechamber"), .
#include "godot_scene.h"

#include <fstream>
#include <sstream>
#include <string_view>
#include <variant>

namespace com::fourisland::lingo2_archipelago {

namespace {

struct Heading {
  std::string type;

  std::string id;
  std::string path;
  std::string resource_type;

  std::string name;
  std::string parent;
  GodotInstanceType instance_type;
};

Heading ParseTscnHeading(std::string_view line) {
  std::string original_line(line);
  Heading heading;

  if (line[0] != '[') {
    std::ostringstream errormsg;
    errormsg << "Heading must start with [." << std::endl
             << "Bad heading: " << original_line;
    throw std::invalid_argument(errormsg.str());
  }

  line.remove_prefix(1);
  int divider = line.find_first_of(" ]");
  if (divider == std::string_view::npos) {
    std::ostringstream errormsg;
    errormsg << "Malformatted heading: " << line << std::endl
             << "Original line: " << original_line;
    throw std::invalid_argument(errormsg.str());
  }

  heading.type = std::string(line.substr(0, divider));
  line.remove_prefix(divider + 1);

  while (!line.empty()) {
    divider = line.find_first_of("=");
    if (divider == std::string_view::npos) {
      std::ostringstream errormsg;
      errormsg << "Malformatted heading: " << line << std::endl
               << "Original line: " << original_line;
      throw std::invalid_argument(errormsg.str());
    }

    std::string key(line.substr(0, divider));
    line.remove_prefix(divider + 1);

    if (line[0] == '"') {
      line.remove_prefix(1);
      divider = line.find_first_of("\"");

      if (divider == std::string_view::npos) {
        std::ostringstream errormsg;
        errormsg << "Malformatted heading: " << line << std::endl
                 << "Original line: " << original_line;
        throw std::invalid_argument(errormsg.str());
      }

      std::string strval(line.substr(0, divider));
      line.remove_prefix(divider + 2);

      if (key == "name") {
        heading.name = strval;
      } else if (key == "parent") {
        heading.parent = strval;
      } else if (key == "path") {
        heading.path = strval;
      } else if (key == "type") {
        heading.resource_type = strval;
      } else if (key == "id") {
        heading.id = strval;
      }
    } else if (line[0] == 'S' || line[0] == 'E') {
      GodotInstanceType rrval;
      char internal = line[0];

      line.remove_prefix(13);  // SubResource("
      divider = line.find_first_of("\"");

      if (divider == std::string_view::npos) {
        std::ostringstream errormsg;
        errormsg << "Malformatted heading: " << line << std::endl
                 << "Original line: " << original_line;
        throw std::invalid_argument(errormsg.str());
      }

      std::string refid = std::string(line.substr(0, divider));
      line.remove_prefix(divider + 3);

      GodotInstanceType instance_type;
      if (internal == 'E') {
        instance_type = GodotExtResourceRef{.id = refid};
      } else {
        // SubResource is not supported right now.
      }

      if (key == "instance") {
        heading.instance_type = instance_type;
      } else {
        // Other keys aren't supported right now.
      }
    } else {
      divider = line.find_first_of(" ]");

      if (divider == std::string_view::npos) {
        std::ostringstream errormsg;
        errormsg << "Malformatted heading: " << line << std::endl
                 << "Original line: " << original_line;
        throw std::invalid_argument(errormsg.str());
      }

      int numval = std::atoi(line.substr(0, divider).data());
      line.remove_prefix(divider + 1);

      // keyvals_[key] = numval;
    }
  }

  return heading;
}

}  // namespace

std::string GodotNode::GetPath() const {
  if (parent.empty() || parent == ".") {
    return name;
  } else {
    return parent + "/" + name;
  }
}

GodotScene ReadGodotSceneFromFile(const std::string& path) {
  std::map<std::string, GodotExtResource> ext_resources;
  std::vector<GodotNode> nodes;

  std::ifstream input(path);

  std::string line;
  bool section_started = false;
  Heading cur_heading;
  std::ostringstream cur_value;
  bool value_started = false;
  auto handle_end_of_section = [&]() {
    section_started = false;
    value_started = false;

    if (cur_heading.type == "sub_resource") {
      // sub_resources_[std::get<int>(cur_heading.GetKeyval("id"))] =
      // {cur_heading, cur_value.str(), ""};
    } else {
      // other_.emplace_back(cur_heading, cur_value.str());
    }

    cur_value = {};
  };
  while (std::getline(input, line)) {
    if (section_started && (line.empty() || line[0] == '[')) {
      handle_end_of_section();
    }
    if (!line.empty() && line[0] == '[') {
      Heading heading = ParseTscnHeading(line);
      if (heading.type == "gd_scene") {
        // file_descriptor_ = heading;
      } else if (heading.type == "ext_resource") {
        GodotExtResource ext_resource;
        ext_resource.path = heading.path;
        ext_resource.type = heading.resource_type;

        ext_resources[heading.id] = ext_resource;
      } else if (heading.type == "node") {
        if (heading.parent != "") {
          nodes.push_back(GodotNode{.name = heading.name,
                                    .parent = heading.parent,
                                    .instance_type = heading.instance_type});
        }
      } else {
        cur_heading = heading;
        section_started = true;
      }
    } else if (!line.empty()) {
      if (value_started) {
        cur_value << std::endl;
      } else {
        value_started = true;
      }
      cur_value << line;
    }
  }
  if (section_started) {
    handle_end_of_section();
  }

  return GodotScene(std::move(ext_resources), std::move(nodes));
}

}  // namespace com::fourisland::lingo2_archipelago