#include "game_data.h" #include #include #include #include #include #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 rooms_; std::vector doors_; std::vector panels_; std::vector map_areas_; std::vector subway_items_; std::vector paintings_; std::map room_by_id_; std::map door_by_id_; std::map panel_by_id_; std::map area_by_id_; std::map painting_by_id_; std::vector door_definition_order_; std::map room_by_painting_; std::map room_by_sunwarp_; std::vector achievement_panels_; std::map ap_id_by_color_; std::vector sunwarp_doors_; std::map subway_item_by_painting_; std::map subway_item_by_sunwarp_; bool loaded_area_data_ = false; std::set 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(); } 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()); for (const auto &entrance_it : room_it.second["entrances"]) { int from_room_id = AddOrGetRoom(entrance_it.first.as()); 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(); } exit_obj.door = AddOrGetDoor(door_room, option["door"].as()); } if (option["painting"] && option["painting"].as()) { exit_obj.type = EntranceType::kPainting; } if (option["sunwarp"] && option["sunwarp"].as()) { exit_obj.type = EntranceType::kSunwarp; } if (option["warp"] && option["warp"].as()) { 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()); 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())); } else { for (const auto &color_node : panel_it.second["colors"]) { panels_[panel_id].colors.push_back( GetColorForString(color_node.as())); } } } 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())); } else { for (const auto &rr_node : panel_it.second["required_room"]) { panels_[panel_id].required_rooms.push_back( AddOrGetRoom(rr_node.as())); } } } 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(); } panels_[panel_id].required_doors.push_back(AddOrGetDoor( rd_room, panel_it.second["required_door"]["door"].as())); } 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(); }; panels_[panel_id].required_doors.push_back( AddOrGetDoor(rd_room, rr_node["door"].as())); } } } 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(); } int rp_id = AddOrGetPanel( rp_room, panel_it.second["required_panel"]["panel"].as()); panels_[panel_id].required_panels.push_back(rp_id);
#include "area_popup.h"

#include <wx/dcbuffer.h>

#include "ap_state.h"
#include "game_data.h"
#include "global.h"
#include "tracker_config.h"
#include "tracker_state.h"

AreaPopup::AreaPopup(wxWindow* parent, int area_id)
    : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) {
  SetBackgroundStyle(wxBG_STYLE_PAINT);

  unchecked_eye_ =
      wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(),
                       wxBITMAP_TYPE_PNG)
                   .Scale(32, 32));
  checked_eye_ = wxBitmap(
      wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG)
          .Scale(32, 32));

  SetScrollRate(5, 5);

  SetBackgroundColour(*wxBLACK);
  Hide();

  Bind(wxEVT_PAINT, &AreaPopup::OnPaint, this);

  UpdateIndicators();
}

void AreaPopup::UpdateIndicators() {
  const MapArea& map_area = GD_GetMapArea(area_id_);

  // Start calculating extents.
  wxMemoryDC mem_dc;
  mem_dc.SetFont(GetFont().Bold());
  wxSize header_extent = mem_dc.GetTextExtent(map_area.name);

  int acc_height = header_extent.GetHeight() + 20;
  int col_width = 0;

  mem_dc.SetFont(GetFont());

  std::vector<int> real_locations;

  for (int section_id = 0; section_id < map_area.locations.size();
       section_id++) {
    const Location& location = map_area.locations.at(section_id);

    if (!AP_IsLocationVisible(location.classification) &&
        !(location.hunt && GetTrackerConfig().show_hunt_panels)) {
      continue;
    }

    real_locations.push_back(section_id);

    wxSize item_extent = mem_dc.GetTextExtent(location.name);
    int item_height = std::max(32, item_extent.GetHeight()) + 10;
    acc_height += item_height;

    if (item_extent.GetWidth() > col_width) {
      col_width = item_extent.GetWidth();
    }
  }

  if (AP_IsPaintingShuffle()) {
    for (int painting_id : map_area.paintings) {
      const PaintingExit& painting = GD_GetPaintingExit(painting_id);
      wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id);  // TODO: Replace with a friendly name.
      int item_height = std::max(32, item_extent.GetHeight()) + 10;
      acc_height += item_height;

      if (item_extent.GetWidth() > col_width) {
        col_width = item_extent.GetWidth();
      }
    }
  }

  int item_width = col_width + 10 + 32;
  int full_width = std::max(header_extent.GetWidth(), item_width) + 20;

  Fit();
  SetVirtualSize(full_width, acc_height);

  rendered_ = wxBitmap(full_width, acc_height);
  mem_dc.SelectObject(rendered_);
  mem_dc.SetPen(*wxTRANSPARENT_PEN);
  mem_dc.SetBrush(*wxBLACK_BRUSH);
  mem_dc.DrawRectangle({0, 0}, {full_width, acc_height});

  mem_dc.SetFont(GetFont().Bold());
  mem_dc.SetTextForeground(*wxWHITE);
  mem_dc.DrawText(map_area.name,
                  {(full_width - header_extent.GetWidth()) / 2, 10});

  int cur_height = header_extent.GetHeight() + 20;

  mem_dc.SetFont(GetFont());

  for (int section_id : real_locations) {
    const Location& location = map_area.locations.at(section_id);

    bool checked =
        AP_HasCheckedGameLocation(location.ap_location_id) ||
        (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id)) ||
        (IsLocationWinCondition(location) && AP_HasReachedGoal());

    wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;

    mem_dc.DrawBitmap(*eye_ptr, {10, cur_height});

    bool reachable = IsLocationReachable(location.ap_location_id);
    const wxColour* text_color = reachable ? wxWHITE : wxRED;
    mem_dc.SetTextForeground(*text_color);

    wxSize item_extent = mem_dc.GetTextExtent(location.name);
    mem_dc.DrawText(
        location.name,
        {10 + 32 + 10, cur_height + (32 - mem_dc.GetFontMetrics().height) / 2});

    cur_height += 10 + 32;
  }

  if (AP_IsPaintingShuffle()) {
    for (int painting_id : map_area.paintings) {
      const PaintingExit& painting = GD_GetPaintingExit(painting_id);
      bool checked = AP_IsPaintingChecked(painting.internal_id);
      wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;

      mem_dc.DrawBitmap(*eye_ptr, {10, cur_height});

      bool reachable = IsPaintingReachable(painting_id);
      const wxColour* text_color = reachable ? wxWHITE : wxRED;
      mem_dc.SetTextForeground(*text_color);

      wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id);  // TODO: Replace with friendly name.
      mem_dc.DrawText(painting.internal_id,
                      {10 + 32 + 10,
                       cur_height + (32 - mem_dc.GetFontMetrics().height) / 2});

      cur_height += 10 + 32;
    }
  }
}

void AreaPopup::OnPaint(wxPaintEvent& event) {
  wxBufferedPaintDC dc(this);
  PrepareDC(dc);
  dc.DrawBitmap(rendered_, 0, 0);

  event.Skip();
}