about summary refs log tree commit diff stats
path: root/src/area_popup.h
blob: 00c644d08d598107c0a99d99350b82f16b31318b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef AREA_POPUP_H_03FAC988
#define AREA_POPUP_H_03FAC988

#include <wx/wxprec.h>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

class AreaPopup : public wxScrolledCanvas {
 public:
  AreaPopup(wxWindow* parent, int area_id);

  void UpdateIndicators();

 private:
  void OnPaint(wxPaintEvent& event);

  int area_id_;

  wxBitmap unchecked_eye_;
  wxBitmap checked_eye_;
  wxBitmap rendered_;
};

#endif /* end of include guard: AREA_POPUP_H_03FAC988 */
='#n242'>242 243 244 245 246
#include "ap_state.h"

#include <hkutil/string.h>

#include <apuuid.hpp>
#include <chrono>
#include <exception>
#include <list>
#include <thread>

#include "game_data.h"
#include "tracker_state.h"

constexpr int AP_MAJOR = 0;
constexpr int AP_MINOR = 4;
constexpr int AP_REVISION = 0;

constexpr int ITEM_HANDLING = 7;  // <- all

APState::APState() {
  std::thread([this]() {
    for (;;) {
      {
        std::lock_guard client_guard(client_mutex_);
        if (apclient_) {
          apclient_->poll();
        }
      }

      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
  }).detach();
}

void APState::Connect(std::string server, std::string player,
                      std::string password) {
  tracker_frame_->SetStatusMessage("Connecting to Archipelago server....");

  {
    std::lock_guard client_guard(client_mutex_);

    if (apclient_) {
      apclient_->reset();
    }

    apclient_ = std::make_unique<APClient>(ap_get_uuid(""), "Lingo", server);
  }

  inventory_.clear();
  checked_locations_.clear();

  bool connected = false;
  bool has_connection_result = false;

  apclient_->set_room_info_handler([&]() {
    tracker_frame_->SetStatusMessage(
        "Connected to Archipelago server. Authenticating...");

    apclient_->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"},
                           {AP_MAJOR, AP_MINOR, AP_REVISION});
  });

  apclient_->set_location_checked_handler(
      [&](const std::list<int64_t>& locations) {
        for (const int64_t location_id : locations) {
          checked_locations_.insert(location_id);
          std::cout << "Location: " << location_id << std::endl;
        }

        RefreshTracker();
      });

  apclient_->set_slot_disconnected_handler([&]() {
    tracker_frame_->SetStatusMessage("Disconnected from Archipelago.");
  });

  apclient_->set_socket_disconnected_handler([&]() {
    tracker_frame_->SetStatusMessage("Disconnected from Archipelago.");
  });

  apclient_->set_items_received_handler(
      [&](const std::list<APClient::NetworkItem>& items) {
        for (const APClient::NetworkItem& item : items) {
          // TODO: Progressive items.

          inventory_.insert(item.item);
          std::cout << "Item: " << item.item << std::endl;
        }

        RefreshTracker();
      });

  apclient_->set_slot_connected_handler([&](const nlohmann::json& slot_data) {
    tracker_frame_->SetStatusMessage("Connected to Archipelago!");

    door_shuffle_mode_ = slot_data["shuffle_doors"].get<DoorShuffleMode>();
    color_shuffle_ = slot_data["shuffle_colors"].get<bool>();

    connected = true;
    has_connection_result = true;
  });

  apclient_->set_slot_refused_handler(
      [&](const std::list<std::string>& errors) {
        connected = false;
        has_connection_result = true;

        tracker_frame_->SetStatusMessage("Disconnected from Archipelago.");

        std::vector<std::string> error_messages;
        error_messages.push_back("Could not connect to Archipelago.");

        for (const std::string& error : errors) {
          if (error == "InvalidSlot") {
            error_messages.push_back("Invalid player name.");
          } else if (error == "InvalidGame") {
            error_messages.push_back(
                "The specified player is not playing Lingo.");
          } else if (error == "IncompatibleVersion") {
            error_messages.push_back(
                "The Archipelago server is not the correct version for this "
                "client.");
          } else if (error == "InvalidPassword") {
            error_messages.push_back("Incorrect password.");
          } else if (error == "InvalidItemsHandling") {
            error_messages.push_back(
                "Invalid item handling flag. This is a bug with the tracker. "
                "Please report it to the lingo-ap-tracker GitHub.");
          } else {
            error_messages.push_back("Unknown error.");
          }
        }

        std::string full_message = hatkirby::implode(error_messages, " ");

        wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR);
      });

  client_active_ = true;

  int timeout = 5000;  // 5 seconds
  int interval = 100;
  int remaining_loops = timeout / interval;
  while (!has_connection_result) {
    if (interval == 0) {
      connected = false;
      has_connection_result = true;

      tracker_frame_->SetStatusMessage("Disconnected from Archipelago.");

      wxMessageBox("Timeout while connecting to Archipelago server.",
                   "Connection failed", wxOK | wxICON_ERROR);
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    interval--;
  }

  if (connected) {
    for (const MapArea& map_area : GetGameData().GetMapAreas()) {
      for (int section_id = 0; section_id < map_area.locations.size();
           section_id++) {
        const Location& location = map_area.locations.at(section_id);

        int64_t ap_id = apclient_->get_location_id(location.ap_location_name);
        if (ap_id == APClient::INVALID_NAME_ID) {
          std::cout << "Could not find AP location ID for "
                    << location.ap_location_name << std::endl;
        } else {
          ap_id_by_location_id_[{map_area.id, section_id}] = ap_id;
        }
      }
    }

    for (const Door& door : GetGameData().GetDoors()) {
      if (!door.skip_item) {
        ap_id_by_item_name_[door.item_name] = GetItemId(door.item_name);

        if (!door.group_name.empty() &&
            !ap_id_by_item_name_.count(door.group_name)) {
          ap_id_by_item_name_[door.group_name] = GetItemId(door.group_name);
        }
      }
    }

    ap_id_by_color_[LingoColor::kBlack] = GetItemId("Black");
    ap_id_by_color_[LingoColor::kRed] = GetItemId("Red");
    ap_id_by_color_[LingoColor::kBlue] = GetItemId("Blue");
    ap_id_by_color_[LingoColor::kYellow] = GetItemId("Yellow");
    ap_id_by_color_[LingoColor::kPurple] = GetItemId("Purple");
    ap_id_by_color_[LingoColor::kOrange] = GetItemId("Orange");
    ap_id_by_color_[LingoColor::kGreen] = GetItemId("Green");
    ap_id_by_color_[LingoColor::kBrown] = GetItemId("Brown");
    ap_id_by_color_[LingoColor::kGray] = GetItemId("Gray");

    RefreshTracker();
  } else {
    client_active_ = false;
  }
}

bool APState::HasCheckedGameLocation(int area_id, int section_id) const {
  std::tuple<int, int> location_key = {area_id, section_id};

  if (ap_id_by_location_id_.count(location_key)) {
    return checked_locations_.count(ap_id_by_location_id_.at(location_key));
  } else {
    return false;
  }
}

bool APState::HasColorItem(LingoColor color) const {
  if (ap_id_by_color_.count(color)) {
    return inventory_.count(ap_id_by_color_.at(color));
  } else {
    return false;
  }
}

bool APState::HasItem(const std::string& item) const {
  if (ap_id_by_item_name_.count(item)) {
    return inventory_.count(ap_id_by_item_name_.at(item));
  } else {
    return false;
  }
}

void APState::RefreshTracker() {
  GetTrackerState().CalculateState();
  tracker_frame_->UpdateIndicators();
}

int64_t APState::GetItemId(const std::string& item_name) {
  int64_t ap_id = apclient_->get_item_id(item_name);
  if (ap_id == APClient::INVALID_NAME_ID) {
    std::cout << "Could not find AP item ID for " << item_name << std::endl;
  }

  return ap_id;
}

APState& GetAPState() {
  static APState* instance = new APState();
  return *instance;
}