From 149e7c0836927e14a926a952bd1a7f0d1b49e779 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Fri, 5 May 2023 15:46:58 -0400 Subject: Organised repo --- CMakeLists.txt | 20 +-- ap_state.cpp | 330 ------------------------------------- ap_state.h | 33 ---- area_popup.cpp | 54 ------ area_popup.h | 25 --- connection_dialog.cpp | 40 ----- connection_dialog.h | 30 ---- eye_indicator.cpp | 49 ------ eye_indicator.h | 30 ---- game_data.cpp | 406 ---------------------------------------------- game_data.h | 135 --------------- main.cpp | 21 --- src/ap_state.cpp | 330 +++++++++++++++++++++++++++++++++++++ src/ap_state.h | 33 ++++ src/area_popup.cpp | 54 ++++++ src/area_popup.h | 25 +++ src/connection_dialog.cpp | 40 +++++ src/connection_dialog.h | 30 ++++ src/eye_indicator.cpp | 49 ++++++ src/eye_indicator.h | 30 ++++ src/game_data.cpp | 406 ++++++++++++++++++++++++++++++++++++++++++++++ src/game_data.h | 135 +++++++++++++++ src/main.cpp | 21 +++ src/tracker_config.cpp | 33 ++++ src/tracker_config.h | 19 +++ src/tracker_frame.cpp | 86 ++++++++++ src/tracker_frame.h | 33 ++++ src/tracker_panel.cpp | 149 +++++++++++++++++ src/tracker_panel.h | 39 +++++ src/tracker_state.cpp | 181 +++++++++++++++++++++ src/tracker_state.h | 19 +++ tracker_config.cpp | 33 ---- tracker_config.h | 19 --- tracker_frame.cpp | 86 ---------- tracker_frame.h | 33 ---- tracker_panel.cpp | 149 ----------------- tracker_panel.h | 39 ----- tracker_state.cpp | 181 --------------------- tracker_state.h | 19 --- 39 files changed, 1722 insertions(+), 1722 deletions(-) delete mode 100644 ap_state.cpp delete mode 100644 ap_state.h delete mode 100644 area_popup.cpp delete mode 100644 area_popup.h delete mode 100644 connection_dialog.cpp delete mode 100644 connection_dialog.h delete mode 100644 eye_indicator.cpp delete mode 100644 eye_indicator.h delete mode 100644 game_data.cpp delete mode 100644 game_data.h delete mode 100644 main.cpp create mode 100644 src/ap_state.cpp create mode 100644 src/ap_state.h create mode 100644 src/area_popup.cpp create mode 100644 src/area_popup.h create mode 100644 src/connection_dialog.cpp create mode 100644 src/connection_dialog.h create mode 100644 src/eye_indicator.cpp create mode 100644 src/eye_indicator.h create mode 100644 src/game_data.cpp create mode 100644 src/game_data.h create mode 100644 src/main.cpp create mode 100644 src/tracker_config.cpp create mode 100644 src/tracker_config.h create mode 100644 src/tracker_frame.cpp create mode 100644 src/tracker_frame.h create mode 100644 src/tracker_panel.cpp create mode 100644 src/tracker_panel.h create mode 100644 src/tracker_state.cpp create mode 100644 src/tracker_state.h delete mode 100644 tracker_config.cpp delete mode 100644 tracker_config.h delete mode 100644 tracker_frame.cpp delete mode 100644 tracker_frame.h delete mode 100644 tracker_panel.cpp delete mode 100644 tracker_panel.h delete mode 100644 tracker_state.cpp delete mode 100644 tracker_state.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e29d18..af3b0ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,16 +30,16 @@ include_directories(${SYSTEM_INCLUDE_DIR}) link_directories(${openssl_LIBRARY_DIRS}) add_executable(lingo_ap_tracker - main.cpp - tracker_frame.cpp - tracker_panel.cpp - game_data.cpp - area_popup.cpp - ap_state.cpp - connection_dialog.cpp - eye_indicator.cpp - tracker_state.cpp - tracker_config.cpp + "src/main.cpp" + "src/tracker_frame.cpp" + "src/tracker_panel.cpp" + "src/game_data.cpp" + "src/area_popup.cpp" + "src/ap_state.cpp" + "src/connection_dialog.cpp" + "src/eye_indicator.cpp" + "src/tracker_state.cpp" + "src/tracker_config.cpp" ) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) diff --git a/ap_state.cpp b/ap_state.cpp deleted file mode 100644 index 22a51ee..0000000 --- a/ap_state.cpp +++ /dev/null @@ -1,330 +0,0 @@ -#include "ap_state.h" - -#define HAS_STD_FILESYSTEM -#define _WEBSOCKETPP_CPP11_STRICT_ -#pragma comment(lib, "crypt32") - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "game_data.h" -#include "tracker_frame.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 - -namespace { - -APClient* apclient = nullptr; - -bool initialized = false; - -TrackerFrame* tracker_frame; - -bool client_active = false; -std::mutex client_mutex; - -bool connected = false; -bool has_connection_result = false; - -std::map inventory; -std::set checked_locations; - -std::map, int64_t> ap_id_by_location_id; -std::map ap_id_by_item_name; -std::map ap_id_by_color; -std::map progressive_item_by_ap_id; - -DoorShuffleMode door_shuffle_mode = kNO_DOORS; -bool color_shuffle = false; -bool painting_shuffle = false; -int mastery_requirement = 21; - -std::map painting_mapping; - -void RefreshTracker() { - GetTrackerState().CalculateState(); - tracker_frame->UpdateIndicators(); -} - -int64_t 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; -} - -void DestroyClient() { - client_active = false; - apclient->reset(); - delete apclient; - apclient = nullptr; -} - -} // namespace - -void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; } - -void AP_Connect(std::string server, std::string player, std::string password) { - if (!initialized) { - std::thread([]() { - for (;;) { - { - std::lock_guard client_guard(client_mutex); - if (apclient) { - apclient->poll(); - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - }).detach(); - - initialized = true; - } - - tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); - - { - std::lock_guard client_guard(client_mutex); - - if (apclient) { - DestroyClient(); - } - - apclient = new APClient(ap_get_uuid(""), "Lingo", server); - } - - inventory.clear(); - checked_locations.clear(); - door_shuffle_mode = kNO_DOORS; - color_shuffle = false; - painting_shuffle = false; - painting_mapping.clear(); - mastery_requirement = 21; - - connected = false; - has_connection_result = false; - - apclient->set_room_info_handler([player, password]() { - inventory.clear(); - - 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& 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. Attempting to reconnect..."); - }); - - apclient->set_socket_disconnected_handler([]() { - tracker_frame->SetStatusMessage( - "Disconnected from Archipelago. Attempting to reconnect..."); - }); - - apclient->set_items_received_handler( - [](const std::list& items) { - for (const APClient::NetworkItem& item : items) { - inventory[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(); - color_shuffle = slot_data["shuffle_colors"].get(); - painting_shuffle = slot_data["shuffle_paintings"].get(); - mastery_requirement = slot_data["mastery_achievements"].get(); - - if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { - painting_mapping.clear(); - - for (const auto& mapping_it : - slot_data["painting_entrance_to_exit"].items()) { - painting_mapping[mapping_it.key()] = mapping_it.value(); - } - } - - connected = true; - has_connection_result = true; - - RefreshTracker(); - }); - - apclient->set_slot_refused_handler([](const std::list& errors) { - connected = false; - has_connection_result = true; - - tracker_frame->SetStatusMessage("Disconnected from Archipelago."); - - std::vector 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; - - DestroyClient(); - - 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); - } - - for (const ProgressiveRequirement& prog_req : door.progressives) { - ap_id_by_item_name[prog_req.item_name] = - GetItemId(prog_req.item_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 AP_HasCheckedGameLocation(int area_id, int section_id) { - std::tuple 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 AP_HasColorItem(LingoColor color) { - if (ap_id_by_color.count(color)) { - return inventory.count(ap_id_by_color.at(color)); - } else { - return false; - } -} - -bool AP_HasItem(const std::string& item, int quantity) { - if (ap_id_by_item_name.count(item)) { - int64_t ap_id = ap_id_by_item_name.at(item); - return inventory.count(ap_id) && inventory.at(ap_id) >= quantity; - } else { - return false; - } -} - -DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; } - -bool AP_IsColorShuffle() { return color_shuffle; } - -bool AP_IsPaintingShuffle() { return painting_shuffle; } - -const std::map AP_GetPaintingMapping() { - return painting_mapping; -} - -int AP_GetMasteryRequirement() { return mastery_requirement; } diff --git a/ap_state.h b/ap_state.h deleted file mode 100644 index d880c71..0000000 --- a/ap_state.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef AP_STATE_H_664A4180 -#define AP_STATE_H_664A4180 - -#include -#include - -#include "game_data.h" - -class TrackerFrame; - -enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 }; - -void AP_SetTrackerFrame(TrackerFrame* tracker_frame); - -void AP_Connect(std::string server, std::string player, std::string password); - -bool AP_HasCheckedGameLocation(int area_id, int section_id); - -bool AP_HasColorItem(LingoColor color); - -bool AP_HasItem(const std::string& item, int quantity = 1); - -DoorShuffleMode AP_GetDoorShuffleMode(); - -bool AP_IsColorShuffle(); - -bool AP_IsPaintingShuffle(); - -const std::map AP_GetPaintingMapping(); - -int AP_GetMasteryRequirement(); - -#endif /* end of include guard: AP_STATE_H_664A4180 */ diff --git a/area_popup.cpp b/area_popup.cpp deleted file mode 100644 index a8ff612..0000000 --- a/area_popup.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "area_popup.h" - -#include "ap_state.h" -#include "game_data.h" -#include "tracker_state.h" - -AreaPopup::AreaPopup(wxWindow* parent, int area_id) - : wxPanel(parent, wxID_ANY), area_id_(area_id) { - const MapArea& map_area = GetGameData().GetMapArea(area_id); - - wxFlexGridSizer* section_sizer = new wxFlexGridSizer(2, 10, 10); - - for (const Location& location : map_area.locations) { - EyeIndicator* eye_indicator = new EyeIndicator(this); - section_sizer->Add(eye_indicator, wxSizerFlags().Expand()); - eye_indicators_.push_back(eye_indicator); - - wxStaticText* section_label = new wxStaticText(this, -1, location.name); - section_label->SetForegroundColour(*wxWHITE); - section_sizer->Add( - section_label, - wxSizerFlags().Align(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL)); - section_labels_.push_back(section_label); - } - - wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); - - wxStaticText* top_label = new wxStaticText(this, -1, map_area.name); - top_label->SetForegroundColour(*wxWHITE); - top_label->SetFont(top_label->GetFont().Bold()); - top_sizer->Add(top_label, - wxSizerFlags().Center().DoubleBorder(wxUP | wxLEFT | wxRIGHT)); - - top_sizer->Add(section_sizer, wxSizerFlags().DoubleBorder(wxALL).Expand()); - - SetSizerAndFit(top_sizer); - - SetBackgroundColour(*wxBLACK); - Hide(); -} - -void AreaPopup::UpdateIndicators() { - const MapArea& map_area = GetGameData().GetMapArea(area_id_); - for (int section_id = 0; section_id < map_area.locations.size(); - section_id++) { - bool checked = AP_HasCheckedGameLocation(area_id_, section_id); - bool reachable = - GetTrackerState().IsLocationReachable(area_id_, section_id); - const wxColour* text_color = reachable ? wxWHITE : wxRED; - - section_labels_[section_id]->SetForegroundColour(*text_color); - eye_indicators_[section_id]->SetChecked(checked); - } -} diff --git a/area_popup.h b/area_popup.h deleted file mode 100644 index b602b63..0000000 --- a/area_popup.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef AREA_POPUP_H_03FAC988 -#define AREA_POPUP_H_03FAC988 - -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include "eye_indicator.h" - -class AreaPopup : public wxPanel { - public: - AreaPopup(wxWindow* parent, int area_id); - - void UpdateIndicators(); - - private: - int area_id_; - - std::vector section_labels_; - std::vector eye_indicators_; -}; - -#endif /* end of include guard: AREA_POPUP_H_03FAC988 */ diff --git a/connection_dialog.cpp b/connection_dialog.cpp deleted file mode 100644 index 9dd9984..0000000 --- a/connection_dialog.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "connection_dialog.h" - -#include "tracker_config.h" - -ConnectionDialog::ConnectionDialog() - : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { - server_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_server, wxDefaultPosition, - {300, -1}); - player_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_player, wxDefaultPosition, - {300, -1}); - password_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_password, - wxDefaultPosition, {300, -1}); - - wxFlexGridSizer* form_sizer = new wxFlexGridSizer(2, 10, 10); - - form_sizer->Add( - new wxStaticText(this, -1, "Server:"), - wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); - form_sizer->Add(server_box_, wxSizerFlags().Expand()); - form_sizer->Add( - new wxStaticText(this, -1, "Player:"), - wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); - form_sizer->Add(player_box_, wxSizerFlags().Expand()); - form_sizer->Add( - new wxStaticText(this, -1, "Password:"), - wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); - form_sizer->Add(password_box_, wxSizerFlags().Expand()); - - wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); - top_sizer->Add(new wxStaticText( - this, -1, "Enter the details to connect to Archipelago."), - wxSizerFlags().Align(wxALIGN_LEFT).DoubleBorder()); - top_sizer->Add(form_sizer, wxSizerFlags().DoubleBorder().Expand()); - top_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Center()); - - SetSizerAndFit(top_sizer); - - Center(); - server_box_->SetFocus(); -} diff --git a/connection_dialog.h b/connection_dialog.h deleted file mode 100644 index 5d2703d..0000000 --- a/connection_dialog.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef CONNECTION_DIALOG_H_E526D0E7 -#define CONNECTION_DIALOG_H_E526D0E7 - -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include - -class ConnectionDialog : public wxDialog { - public: - ConnectionDialog(); - - std::string GetServerValue() { return server_box_->GetValue().ToStdString(); } - - std::string GetPlayerValue() { return player_box_->GetValue().ToStdString(); } - - std::string GetPasswordValue() { - return password_box_->GetValue().ToStdString(); - } - - private: - wxTextCtrl* server_box_; - wxTextCtrl* player_box_; - wxTextCtrl* password_box_; -}; - -#endif /* end of include guard: CONNECTION_DIALOG_H_E526D0E7 */ diff --git a/eye_indicator.cpp b/eye_indicator.cpp deleted file mode 100644 index 03e234e..0000000 --- a/eye_indicator.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "eye_indicator.h" - -EyeIndicator::EyeIndicator(wxWindow* parent) : wxWindow(parent, wxID_ANY) { - SetMinSize({32, 32}); - - Redraw(); - - Bind(wxEVT_PAINT, &EyeIndicator::OnPaint, this); -} - -void EyeIndicator::SetChecked(bool checked) { - if (intended_checked_ != checked) { - intended_checked_ = checked; - - Redraw(); - } -} - -const wxImage& EyeIndicator::GetUncheckedImage() { - static wxImage* unchecked_image = - new wxImage("assets/unchecked.png", wxBITMAP_TYPE_PNG); - return *unchecked_image; -} - -const wxImage& EyeIndicator::GetCheckedImage() { - static wxImage* checked_image = - new wxImage("assets/checked.png", wxBITMAP_TYPE_PNG); - return *checked_image; -} - -void EyeIndicator::OnPaint(wxPaintEvent& event) { - if (GetSize() != rendered_.GetSize() || - intended_checked_ != rendered_checked_) { - Redraw(); - } - - wxPaintDC dc(this); - dc.DrawBitmap(rendered_, 0, 0); - - event.Skip(); -} - -void EyeIndicator::Redraw() { - rendered_ = - wxBitmap((intended_checked_ ? GetCheckedImage() : GetUncheckedImage()) - .Scale(GetSize().GetWidth(), GetSize().GetHeight(), - wxIMAGE_QUALITY_NORMAL)); - rendered_checked_ = intended_checked_; -} diff --git a/eye_indicator.h b/eye_indicator.h deleted file mode 100644 index e8fd890..0000000 --- a/eye_indicator.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef EYE_INDICATOR_H_778150F2 -#define EYE_INDICATOR_H_778150F2 - -#include - -#ifndef WX_PRECOMP -#include -#endif - -class EyeIndicator : public wxWindow { - public: - EyeIndicator(wxWindow* parent); - - void SetChecked(bool checked); - - private: - static const wxImage& GetUncheckedImage(); - static const wxImage& GetCheckedImage(); - - void OnPaint(wxPaintEvent& event); - - void Redraw(); - - bool intended_checked_ = false; - - wxBitmap rendered_; - bool rendered_checked_ = false; -}; - -#endif /* end of include guard: EYE_INDICATOR_H_778150F2 */ diff --git a/game_data.cpp b/game_data.cpp deleted file mode 100644 index e15847e..0000000 --- a/game_data.cpp +++ /dev/null @@ -1,406 +0,0 @@ -#include "game_data.h" - -#include -#include - -#include - -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 { - std::cout << "Invalid color: " << str << std::endl; - return LingoColor::kNone; - } -} - -GameData::GameData() { - YAML::Node lingo_config = YAML::LoadFile("assets/LL1.yaml"); - YAML::Node areas_config = YAML::LoadFile("assets/areas.yaml"); - - rooms_.reserve(lingo_config.size() * 2); - - for (const auto &room_it : lingo_config) { - int room_id = AddOrGetRoom(room_it.first.as()); - Room &room_obj = rooms_[room_id]; - - for (const auto &entrance_it : room_it.second["entrances"]) { - int from_room_id = AddOrGetRoom(entrance_it.first.as()); - Room &from_room_obj = rooms_[from_room_id]; - - switch (entrance_it.second.Type()) { - case YAML::NodeType::Scalar: { - // This is just "true". - from_room_obj.exits.push_back({.destination_room = room_id}); - break; - } - case YAML::NodeType::Map: { - Exit exit_obj; - exit_obj.destination_room = room_id; - - if (entrance_it.second["door"]) { - std::string door_room = room_obj.name; - if (entrance_it.second["room"]) { - door_room = entrance_it.second["room"].as(); - } - exit_obj.door = AddOrGetDoor( - door_room, entrance_it.second["door"].as()); - } - - if (entrance_it.second["painting"]) { - exit_obj.painting = entrance_it.second["painting"].as(); - } - - from_room_obj.exits.push_back(exit_obj); - break; - } - case YAML::NodeType::Sequence: { - for (const auto &option : entrance_it.second) { - Exit exit_obj; - exit_obj.destination_room = room_id; - - std::string door_room = room_obj.name; - if (option["room"]) { - door_room = option["room"].as(); - } - exit_obj.door = - AddOrGetDoor(door_room, option["door"].as()); - - if (option["painting"]) { - exit_obj.painting = option["painting"].as(); - } - - from_room_obj.exits.push_back(exit_obj); - } - - break; - } - default: { - // This shouldn't happen. - std::cout << "Error reading game data: " << entrance_it << std::endl; - break; - } - } - } - - if (room_it.second["panels"]) { - for (const auto &panel_it : room_it.second["panels"]) { - int panel_id = - AddOrGetPanel(room_obj.name, panel_it.first.as()); - Panel &panel_obj = panels_[panel_id]; - - if (panel_it.second["colors"]) { - if (panel_it.second["colors"].IsScalar()) { - panel_obj.colors.push_back( - GetColorForString(panel_it.second["colors"].as())); - } else { - for (const auto &color_node : panel_it.second["colors"]) { - panel_obj.colors.push_back( - GetColorForString(color_node.as())); - } - } - } - - if (panel_it.second["required_room"]) { - if (panel_it.second["required_room"].IsScalar()) { - panel_obj.required_rooms.push_back(AddOrGetRoom( - panel_it.second["required_room"].as())); - } else { - for (const auto &rr_node : panel_it.second["required_room"]) { - panel_obj.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 = room_obj.name; - if (panel_it.second["required_door"]["room"]) { - rd_room = - panel_it.second["required_door"]["room"].as(); - } - - panel_obj.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 = room_obj.name; - if (rr_node["room"]) { - rd_room = rr_node["room"].as(); - } - - panel_obj.required_doors.push_back( - AddOrGetDoor(rd_room, rr_node["door"].as())); - } - } - } - - if (panel_it.second["check"]) { - panel_obj.check = panel_it.second["check"].as(); - } - - if (panel_it.second["achievement"]) { - panel_obj.achievement = panel_it.second["achievement"].as(); - - if (panel_obj.achievement) { - achievement_panels_.push_back(panel_id); - } - } - - if (panel_it.second["exclude_reduce"]) { - panel_obj.exclude_reduce = - panel_it.second["exclude_reduce"].as(); - } - } - } - - if (room_it.second["doors"]) { - for (const auto &door_it : room_it.second["doors"]) { - int door_id = - AddOrGetDoor(room_obj.name, door_it.first.as()); - Door &door_obj = doors_[door_id]; - - bool has_external_panels = false; - std::vector panel_names; - - for (const auto &panel_node : door_it.second["panels"]) { - if (panel_node.IsScalar()) { - panel_names.push_back(panel_node.as()); - door_obj.panels.push_back( - AddOrGetPanel(room_obj.name, panel_node.as())); - } else { - has_external_panels = true; - panel_names.push_back(panel_node["panel"].as()); - door_obj.panels.push_back( - AddOrGetPanel(panel_node["room"].as(), - panel_node["panel"].as())); - } - } - - if (door_it.second["skip_location"]) { - door_obj.skip_location = door_it.second["skip_location"].as(); - } - - if (door_it.second["skip_item"]) { - door_obj.skip_item = door_it.second["skip_item"].as(); - } - - if (door_it.second["event"]) { - door_obj.skip_location = door_it.second["event"].as(); - door_obj.skip_item = door_it.second["event"].as(); - } - - if (door_it.second["item_name"]) { - door_obj.item_name = door_it.second["item_name"].as(); - } else if (!door_it.second["skip_item"] && !door_it.second["event"]) { - door_obj.item_name = room_obj.name + " - " + door_obj.name; - } - - if (door_it.second["group"]) { - door_obj.group_name = door_it.second["group"].as(); - } - - if (door_it.second["location_name"]) { - door_obj.location_name = - door_it.second["location_name"].as(); - } else if (!door_it.second["skip_location"] && - !door_it.second["event"]) { - if (has_external_panels) { - std::cout - << room_obj.name << " - " << door_obj.name - << " has panels from other rooms but does not have an explicit " - "location name and is not marked skip_location or event" - << std::endl; - } - - door_obj.location_name = - room_obj.name + " - " + hatkirby::implode(panel_names, ", "); - } - - if (door_it.second["include_reduce"]) { - door_obj.exclude_reduce = - !door_it.second["include_reduce"].as(); - } - } - } - - if (room_it.second["paintings"]) { - for (const auto &painting : room_it.second["paintings"]) { - std::string painting_id = painting["id"].as(); - room_by_painting_[painting_id] = room_id; - - if (!painting["exit_only"] || !painting["exit_only"].as()) { - PaintingExit painting_exit; - painting_exit.id = painting_id; - - if (painting["required_door"]) { - std::string rd_room = room_obj.name; - if (painting["required_door"]["room"]) { - rd_room = painting["required_door"]["room"].as(); - } - - painting_exit.door = AddOrGetDoor( - rd_room, painting["required_door"]["door"].as()); - } - - room_obj.paintings.push_back(painting_exit); - } - } - } - - if (room_it.second["progression"]) { - for (const auto &progression_it : room_it.second["progression"]) { - std::string progressive_item_name = - progression_it.first.as(); - - int index = 1; - for (const auto &stage : progression_it.second) { - int door_id = -1; - - if (stage.IsScalar()) { - door_id = AddOrGetDoor(room_obj.name, stage.as()); - } else { - door_id = AddOrGetDoor(stage["room"].as(), - stage["door"].as()); - } - - doors_[door_id].progressives.push_back( - {.item_name = progressive_item_name, .quantity = index}); - index++; - } - } - } - } - - map_areas_.reserve(areas_config.size()); - - std::map fold_areas; - for (const auto &area_it : areas_config) { - if (area_it.second["map"]) { - int area_id = AddOrGetArea(area_it.first.as()); - MapArea &area_obj = map_areas_[area_id]; - area_obj.map_x = area_it.second["map"][0].as(); - area_obj.map_y = area_it.second["map"][1].as(); - } else if (area_it.second["fold_into"]) { - fold_areas[area_it.first.as()] = - AddOrGetArea(area_it.second["fold_into"].as()); - } - } - - for (const Panel &panel : panels_) { - if (panel.check) { - int room_id = panel.room; - std::string room_name = rooms_[room_id].name; - - 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; - } - - 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 = panel.name, - .ap_location_name = room_name + " - " + panel.name, - .room = panel.room, - .panels = {panel.id}}); - } - } - - for (const Door &door : doors_) { - if (!door.skip_location) { - 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, - .room = door.room, - .panels = door.panels}); - } - } -} - -int GameData::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 GameData::AddOrGetDoor(std::string room, std::string door) { - std::string full_name = room + " - " + door; - - if (!door_by_id_.count(full_name)) { - door_by_id_[full_name] = doors_.size(); - doors_.push_back({.room = AddOrGetRoom(room), .name = door}); - } - - return door_by_id_[full_name]; -} - -int GameData::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 GameData::AddOrGetArea(std::string area) { - if (!area_by_id_.count(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]; -} - -const GameData &GetGameData() { - static GameData *instance = new GameData(); - return *instance; -} diff --git a/game_data.h b/game_data.h deleted file mode 100644 index 0cc7a7b..0000000 --- a/game_data.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef GAME_DATA_H_9C42AC51 -#define GAME_DATA_H_9C42AC51 - -#include -#include -#include -#include - -enum class LingoColor { - kNone, - kBlack, - kRed, - kBlue, - kYellow, - kGreen, - kOrange, - kPurple, - kBrown, - kGray -}; - -struct Panel { - int id; - int room; - std::string name; - std::vector colors; - std::vector required_rooms; - std::vector required_doors; - bool check = false; - bool exclude_reduce = false; - bool achievement = false; -}; - -struct ProgressiveRequirement { - std::string item_name; - int quantity = 0; -}; - -struct Door { - int room; - std::string name; - std::string location_name; - std::string item_name; - std::string group_name; - bool skip_location = false; - bool skip_item = false; - std::vector panels; - bool exclude_reduce = true; - std::vector progressives; -}; - -struct Exit { - int destination_room; - std::optional door; - bool painting = false; -}; - -struct PaintingExit { - std::string id; - std::optional door; -}; - -struct Room { - std::string name; - std::vector exits; - std::vector paintings; -}; - -struct Location { - std::string name; - std::string ap_location_name; - int room; - std::vector panels; -}; - -struct MapArea { - int id; - std::string name; - std::vector locations; - int map_x; - int map_y; -}; - -class GameData { - public: - GameData(); - - const std::vector& GetMapAreas() const { return map_areas_; } - - const MapArea& GetMapArea(int id) const { return map_areas_.at(id); } - - int GetRoomByName(const std::string& name) const { - return room_by_id_.at(name); - } - - const Room& GetRoom(int room_id) const { return rooms_.at(room_id); } - - const std::vector& GetDoors() const { return doors_; } - - const Door& GetDoor(int door_id) const { return doors_.at(door_id); } - - const Panel& GetPanel(int panel_id) const { return panels_.at(panel_id); } - - int GetRoomForPainting(const std::string& painting_id) const { - return room_by_painting_.at(painting_id); - } - - const std::vector& GetAchievementPanels() const { - return achievement_panels_; - } - - private: - int AddOrGetRoom(std::string room); - int AddOrGetDoor(std::string room, std::string door); - int AddOrGetPanel(std::string room, std::string panel); - int AddOrGetArea(std::string area); - - std::vector rooms_; - std::vector doors_; - std::vector panels_; - std::vector map_areas_; - - std::map room_by_id_; - std::map door_by_id_; - std::map panel_by_id_; - std::map area_by_id_; - - std::map room_by_painting_; - - std::vector achievement_panels_; -}; - -const GameData& GetGameData(); - -#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/main.cpp b/main.cpp deleted file mode 100644 index fe9aceb..0000000 --- a/main.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include "tracker_config.h" -#include "tracker_frame.h" - -class TrackerApp : public wxApp { - public: - virtual bool OnInit() { - GetTrackerConfig().Load(); - - TrackerFrame *frame = new TrackerFrame(); - frame->Show(true); - return true; - } -}; - -wxIMPLEMENT_APP(TrackerApp); diff --git a/src/ap_state.cpp b/src/ap_state.cpp new file mode 100644 index 0000000..22a51ee --- /dev/null +++ b/src/ap_state.cpp @@ -0,0 +1,330 @@ +#include "ap_state.h" + +#define HAS_STD_FILESYSTEM +#define _WEBSOCKETPP_CPP11_STRICT_ +#pragma comment(lib, "crypt32") + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "game_data.h" +#include "tracker_frame.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 + +namespace { + +APClient* apclient = nullptr; + +bool initialized = false; + +TrackerFrame* tracker_frame; + +bool client_active = false; +std::mutex client_mutex; + +bool connected = false; +bool has_connection_result = false; + +std::map inventory; +std::set checked_locations; + +std::map, int64_t> ap_id_by_location_id; +std::map ap_id_by_item_name; +std::map ap_id_by_color; +std::map progressive_item_by_ap_id; + +DoorShuffleMode door_shuffle_mode = kNO_DOORS; +bool color_shuffle = false; +bool painting_shuffle = false; +int mastery_requirement = 21; + +std::map painting_mapping; + +void RefreshTracker() { + GetTrackerState().CalculateState(); + tracker_frame->UpdateIndicators(); +} + +int64_t 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; +} + +void DestroyClient() { + client_active = false; + apclient->reset(); + delete apclient; + apclient = nullptr; +} + +} // namespace + +void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; } + +void AP_Connect(std::string server, std::string player, std::string password) { + if (!initialized) { + std::thread([]() { + for (;;) { + { + std::lock_guard client_guard(client_mutex); + if (apclient) { + apclient->poll(); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }).detach(); + + initialized = true; + } + + tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); + + { + std::lock_guard client_guard(client_mutex); + + if (apclient) { + DestroyClient(); + } + + apclient = new APClient(ap_get_uuid(""), "Lingo", server); + } + + inventory.clear(); + checked_locations.clear(); + door_shuffle_mode = kNO_DOORS; + color_shuffle = false; + painting_shuffle = false; + painting_mapping.clear(); + mastery_requirement = 21; + + connected = false; + has_connection_result = false; + + apclient->set_room_info_handler([player, password]() { + inventory.clear(); + + 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& 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. Attempting to reconnect..."); + }); + + apclient->set_socket_disconnected_handler([]() { + tracker_frame->SetStatusMessage( + "Disconnected from Archipelago. Attempting to reconnect..."); + }); + + apclient->set_items_received_handler( + [](const std::list& items) { + for (const APClient::NetworkItem& item : items) { + inventory[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(); + color_shuffle = slot_data["shuffle_colors"].get(); + painting_shuffle = slot_data["shuffle_paintings"].get(); + mastery_requirement = slot_data["mastery_achievements"].get(); + + if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { + painting_mapping.clear(); + + for (const auto& mapping_it : + slot_data["painting_entrance_to_exit"].items()) { + painting_mapping[mapping_it.key()] = mapping_it.value(); + } + } + + connected = true; + has_connection_result = true; + + RefreshTracker(); + }); + + apclient->set_slot_refused_handler([](const std::list& errors) { + connected = false; + has_connection_result = true; + + tracker_frame->SetStatusMessage("Disconnected from Archipelago."); + + std::vector 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; + + DestroyClient(); + + 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); + } + + for (const ProgressiveRequirement& prog_req : door.progressives) { + ap_id_by_item_name[prog_req.item_name] = + GetItemId(prog_req.item_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 AP_HasCheckedGameLocation(int area_id, int section_id) { + std::tuple 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 AP_HasColorItem(LingoColor color) { + if (ap_id_by_color.count(color)) { + return inventory.count(ap_id_by_color.at(color)); + } else { + return false; + } +} + +bool AP_HasItem(const std::string& item, int quantity) { + if (ap_id_by_item_name.count(item)) { + int64_t ap_id = ap_id_by_item_name.at(item); + return inventory.count(ap_id) && inventory.at(ap_id) >= quantity; + } else { + return false; + } +} + +DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; } + +bool AP_IsColorShuffle() { return color_shuffle; } + +bool AP_IsPaintingShuffle() { return painting_shuffle; } + +const std::map AP_GetPaintingMapping() { + return painting_mapping; +} + +int AP_GetMasteryRequirement() { return mastery_requirement; } diff --git a/src/ap_state.h b/src/ap_state.h new file mode 100644 index 0000000..d880c71 --- /dev/null +++ b/src/ap_state.h @@ -0,0 +1,33 @@ +#ifndef AP_STATE_H_664A4180 +#define AP_STATE_H_664A4180 + +#include +#include + +#include "game_data.h" + +class TrackerFrame; + +enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 }; + +void AP_SetTrackerFrame(TrackerFrame* tracker_frame); + +void AP_Connect(std::string server, std::string player, std::string password); + +bool AP_HasCheckedGameLocation(int area_id, int section_id); + +bool AP_HasColorItem(LingoColor color); + +bool AP_HasItem(const std::string& item, int quantity = 1); + +DoorShuffleMode AP_GetDoorShuffleMode(); + +bool AP_IsColorShuffle(); + +bool AP_IsPaintingShuffle(); + +const std::map AP_GetPaintingMapping(); + +int AP_GetMasteryRequirement(); + +#endif /* end of include guard: AP_STATE_H_664A4180 */ diff --git a/src/area_popup.cpp b/src/area_popup.cpp new file mode 100644 index 0000000..a8ff612 --- /dev/null +++ b/src/area_popup.cpp @@ -0,0 +1,54 @@ +#include "area_popup.h" + +#include "ap_state.h" +#include "game_data.h" +#include "tracker_state.h" + +AreaPopup::AreaPopup(wxWindow* parent, int area_id) + : wxPanel(parent, wxID_ANY), area_id_(area_id) { + const MapArea& map_area = GetGameData().GetMapArea(area_id); + + wxFlexGridSizer* section_sizer = new wxFlexGridSizer(2, 10, 10); + + for (const Location& location : map_area.locations) { + EyeIndicator* eye_indicator = new EyeIndicator(this); + section_sizer->Add(eye_indicator, wxSizerFlags().Expand()); + eye_indicators_.push_back(eye_indicator); + + wxStaticText* section_label = new wxStaticText(this, -1, location.name); + section_label->SetForegroundColour(*wxWHITE); + section_sizer->Add( + section_label, + wxSizerFlags().Align(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL)); + section_labels_.push_back(section_label); + } + + wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); + + wxStaticText* top_label = new wxStaticText(this, -1, map_area.name); + top_label->SetForegroundColour(*wxWHITE); + top_label->SetFont(top_label->GetFont().Bold()); + top_sizer->Add(top_label, + wxSizerFlags().Center().DoubleBorder(wxUP | wxLEFT | wxRIGHT)); + + top_sizer->Add(section_sizer, wxSizerFlags().DoubleBorder(wxALL).Expand()); + + SetSizerAndFit(top_sizer); + + SetBackgroundColour(*wxBLACK); + Hide(); +} + +void AreaPopup::UpdateIndicators() { + const MapArea& map_area = GetGameData().GetMapArea(area_id_); + for (int section_id = 0; section_id < map_area.locations.size(); + section_id++) { + bool checked = AP_HasCheckedGameLocation(area_id_, section_id); + bool reachable = + GetTrackerState().IsLocationReachable(area_id_, section_id); + const wxColour* text_color = reachable ? wxWHITE : wxRED; + + section_labels_[section_id]->SetForegroundColour(*text_color); + eye_indicators_[section_id]->SetChecked(checked); + } +} diff --git a/src/area_popup.h b/src/area_popup.h new file mode 100644 index 0000000..b602b63 --- /dev/null +++ b/src/area_popup.h @@ -0,0 +1,25 @@ +#ifndef AREA_POPUP_H_03FAC988 +#define AREA_POPUP_H_03FAC988 + +#include + +#ifndef WX_PRECOMP +#include +#endif + +#include "eye_indicator.h" + +class AreaPopup : public wxPanel { + public: + AreaPopup(wxWindow* parent, int area_id); + + void UpdateIndicators(); + + private: + int area_id_; + + std::vector section_labels_; + std::vector eye_indicators_; +}; + +#endif /* end of include guard: AREA_POPUP_H_03FAC988 */ diff --git a/src/connection_dialog.cpp b/src/connection_dialog.cpp new file mode 100644 index 0000000..9dd9984 --- /dev/null +++ b/src/connection_dialog.cpp @@ -0,0 +1,40 @@ +#include "connection_dialog.h" + +#include "tracker_config.h" + +ConnectionDialog::ConnectionDialog() + : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { + server_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_server, wxDefaultPosition, + {300, -1}); + player_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_player, wxDefaultPosition, + {300, -1}); + password_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_password, + wxDefaultPosition, {300, -1}); + + wxFlexGridSizer* form_sizer = new wxFlexGridSizer(2, 10, 10); + + form_sizer->Add( + new wxStaticText(this, -1, "Server:"), + wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + form_sizer->Add(server_box_, wxSizerFlags().Expand()); + form_sizer->Add( + new wxStaticText(this, -1, "Player:"), + wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + form_sizer->Add(player_box_, wxSizerFlags().Expand()); + form_sizer->Add( + new wxStaticText(this, -1, "Password:"), + wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); + form_sizer->Add(password_box_, wxSizerFlags().Expand()); + + wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); + top_sizer->Add(new wxStaticText( + this, -1, "Enter the details to connect to Archipelago."), + wxSizerFlags().Align(wxALIGN_LEFT).DoubleBorder()); + top_sizer->Add(form_sizer, wxSizerFlags().DoubleBorder().Expand()); + top_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Center()); + + SetSizerAndFit(top_sizer); + + Center(); + server_box_->SetFocus(); +} diff --git a/src/connection_dialog.h b/src/connection_dialog.h new file mode 100644 index 0000000..5d2703d --- /dev/null +++ b/src/connection_dialog.h @@ -0,0 +1,30 @@ +#ifndef CONNECTION_DIALOG_H_E526D0E7 +#define CONNECTION_DIALOG_H_E526D0E7 + +#include + +#ifndef WX_PRECOMP +#include +#endif + +#include + +class ConnectionDialog : public wxDialog { + public: + ConnectionDialog(); + + std::string GetServerValue() { return server_box_->GetValue().ToStdString(); } + + std::string GetPlayerValue() { return player_box_->GetValue().ToStdString(); } + + std::string GetPasswordValue() { + return password_box_->GetValue().ToStdString(); + } + + private: + wxTextCtrl* server_box_; + wxTextCtrl* player_box_; + wxTextCtrl* password_box_; +}; + +#endif /* end of include guard: CONNECTION_DIALOG_H_E526D0E7 */ diff --git a/src/eye_indicator.cpp b/src/eye_indicator.cpp new file mode 100644 index 0000000..03e234e --- /dev/null +++ b/src/eye_indicator.cpp @@ -0,0 +1,49 @@ +#include "eye_indicator.h" + +EyeIndicator::EyeIndicator(wxWindow* parent) : wxWindow(parent, wxID_ANY) { + SetMinSize({32, 32}); + + Redraw(); + + Bind(wxEVT_PAINT, &EyeIndicator::OnPaint, this); +} + +void EyeIndicator::SetChecked(bool checked) { + if (intended_checked_ != checked) { + intended_checked_ = checked; + + Redraw(); + } +} + +const wxImage& EyeIndicator::GetUncheckedImage() { + static wxImage* unchecked_image = + new wxImage("assets/unchecked.png", wxBITMAP_TYPE_PNG); + return *unchecked_image; +} + +const wxImage& EyeIndicator::GetCheckedImage() { + static wxImage* checked_image = + new wxImage("assets/checked.png", wxBITMAP_TYPE_PNG); + return *checked_image; +} + +void EyeIndicator::OnPaint(wxPaintEvent& event) { + if (GetSize() != rendered_.GetSize() || + intended_checked_ != rendered_checked_) { + Redraw(); + } + + wxPaintDC dc(this); + dc.DrawBitmap(rendered_, 0, 0); + + event.Skip(); +} + +void EyeIndicator::Redraw() { + rendered_ = + wxBitmap((intended_checked_ ? GetCheckedImage() : GetUncheckedImage()) + .Scale(GetSize().GetWidth(), GetSize().GetHeight(), + wxIMAGE_QUALITY_NORMAL)); + rendered_checked_ = intended_checked_; +} diff --git a/src/eye_indicator.h b/src/eye_indicator.h new file mode 100644 index 0000000..e8fd890 --- /dev/null +++ b/src/eye_indicator.h @@ -0,0 +1,30 @@ +#ifndef EYE_INDICATOR_H_778150F2 +#define EYE_INDICATOR_H_778150F2 + +#include + +#ifndef WX_PRECOMP +#include +#endif + +class EyeIndicator : public wxWindow { + public: + EyeIndicator(wxWindow* parent); + + void SetChecked(bool checked); + + private: + static const wxImage& GetUncheckedImage(); + static const wxImage& GetCheckedImage(); + + void OnPaint(wxPaintEvent& event); + + void Redraw(); + + bool intended_checked_ = false; + + wxBitmap rendered_; + bool rendered_checked_ = false; +}; + +#endif /* end of include guard: EYE_INDICATOR_H_778150F2 */ diff --git a/src/game_data.cpp b/src/game_data.cpp new file mode 100644 index 0000000..e15847e --- /dev/null +++ b/src/game_data.cpp @@ -0,0 +1,406 @@ +#include "game_data.h" + +#include +#include + +#include + +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 { + std::cout << "Invalid color: " << str << std::endl; + return LingoColor::kNone; + } +} + +GameData::GameData() { + YAML::Node lingo_config = YAML::LoadFile("assets/LL1.yaml"); + YAML::Node areas_config = YAML::LoadFile("assets/areas.yaml"); + + rooms_.reserve(lingo_config.size() * 2); + + for (const auto &room_it : lingo_config) { + int room_id = AddOrGetRoom(room_it.first.as()); + Room &room_obj = rooms_[room_id]; + + for (const auto &entrance_it : room_it.second["entrances"]) { + int from_room_id = AddOrGetRoom(entrance_it.first.as()); + Room &from_room_obj = rooms_[from_room_id]; + + switch (entrance_it.second.Type()) { + case YAML::NodeType::Scalar: { + // This is just "true". + from_room_obj.exits.push_back({.destination_room = room_id}); + break; + } + case YAML::NodeType::Map: { + Exit exit_obj; + exit_obj.destination_room = room_id; + + if (entrance_it.second["door"]) { + std::string door_room = room_obj.name; + if (entrance_it.second["room"]) { + door_room = entrance_it.second["room"].as(); + } + exit_obj.door = AddOrGetDoor( + door_room, entrance_it.second["door"].as()); + } + + if (entrance_it.second["painting"]) { + exit_obj.painting = entrance_it.second["painting"].as(); + } + + from_room_obj.exits.push_back(exit_obj); + break; + } + case YAML::NodeType::Sequence: { + for (const auto &option : entrance_it.second) { + Exit exit_obj; + exit_obj.destination_room = room_id; + + std::string door_room = room_obj.name; + if (option["room"]) { + door_room = option["room"].as(); + } + exit_obj.door = + AddOrGetDoor(door_room, option["door"].as()); + + if (option["painting"]) { + exit_obj.painting = option["painting"].as(); + } + + from_room_obj.exits.push_back(exit_obj); + } + + break; + } + default: { + // This shouldn't happen. + std::cout << "Error reading game data: " << entrance_it << std::endl; + break; + } + } + } + + if (room_it.second["panels"]) { + for (const auto &panel_it : room_it.second["panels"]) { + int panel_id = + AddOrGetPanel(room_obj.name, panel_it.first.as()); + Panel &panel_obj = panels_[panel_id]; + + if (panel_it.second["colors"]) { + if (panel_it.second["colors"].IsScalar()) { + panel_obj.colors.push_back( + GetColorForString(panel_it.second["colors"].as())); + } else { + for (const auto &color_node : panel_it.second["colors"]) { + panel_obj.colors.push_back( + GetColorForString(color_node.as())); + } + } + } + + if (panel_it.second["required_room"]) { + if (panel_it.second["required_room"].IsScalar()) { + panel_obj.required_rooms.push_back(AddOrGetRoom( + panel_it.second["required_room"].as())); + } else { + for (const auto &rr_node : panel_it.second["required_room"]) { + panel_obj.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 = room_obj.name; + if (panel_it.second["required_door"]["room"]) { + rd_room = + panel_it.second["required_door"]["room"].as(); + } + + panel_obj.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 = room_obj.name; + if (rr_node["room"]) { + rd_room = rr_node["room"].as(); + } + + panel_obj.required_doors.push_back( + AddOrGetDoor(rd_room, rr_node["door"].as())); + } + } + } + + if (panel_it.second["check"]) { + panel_obj.check = panel_it.second["check"].as(); + } + + if (panel_it.second["achievement"]) { + panel_obj.achievement = panel_it.second["achievement"].as(); + + if (panel_obj.achievement) { + achievement_panels_.push_back(panel_id); + } + } + + if (panel_it.second["exclude_reduce"]) { + panel_obj.exclude_reduce = + panel_it.second["exclude_reduce"].as(); + } + } + } + + if (room_it.second["doors"]) { + for (const auto &door_it : room_it.second["doors"]) { + int door_id = + AddOrGetDoor(room_obj.name, door_it.first.as()); + Door &door_obj = doors_[door_id]; + + bool has_external_panels = false; + std::vector panel_names; + + for (const auto &panel_node : door_it.second["panels"]) { + if (panel_node.IsScalar()) { + panel_names.push_back(panel_node.as()); + door_obj.panels.push_back( + AddOrGetPanel(room_obj.name, panel_node.as())); + } else { + has_external_panels = true; + panel_names.push_back(panel_node["panel"].as()); + door_obj.panels.push_back( + AddOrGetPanel(panel_node["room"].as(), + panel_node["panel"].as())); + } + } + + if (door_it.second["skip_location"]) { + door_obj.skip_location = door_it.second["skip_location"].as(); + } + + if (door_it.second["skip_item"]) { + door_obj.skip_item = door_it.second["skip_item"].as(); + } + + if (door_it.second["event"]) { + door_obj.skip_location = door_it.second["event"].as(); + door_obj.skip_item = door_it.second["event"].as(); + } + + if (door_it.second["item_name"]) { + door_obj.item_name = door_it.second["item_name"].as(); + } else if (!door_it.second["skip_item"] && !door_it.second["event"]) { + door_obj.item_name = room_obj.name + " - " + door_obj.name; + } + + if (door_it.second["group"]) { + door_obj.group_name = door_it.second["group"].as(); + } + + if (door_it.second["location_name"]) { + door_obj.location_name = + door_it.second["location_name"].as(); + } else if (!door_it.second["skip_location"] && + !door_it.second["event"]) { + if (has_external_panels) { + std::cout + << room_obj.name << " - " << door_obj.name + << " has panels from other rooms but does not have an explicit " + "location name and is not marked skip_location or event" + << std::endl; + } + + door_obj.location_name = + room_obj.name + " - " + hatkirby::implode(panel_names, ", "); + } + + if (door_it.second["include_reduce"]) { + door_obj.exclude_reduce = + !door_it.second["include_reduce"].as(); + } + } + } + + if (room_it.second["paintings"]) { + for (const auto &painting : room_it.second["paintings"]) { + std::string painting_id = painting["id"].as(); + room_by_painting_[painting_id] = room_id; + + if (!painting["exit_only"] || !painting["exit_only"].as()) { + PaintingExit painting_exit; + painting_exit.id = painting_id; + + if (painting["required_door"]) { + std::string rd_room = room_obj.name; + if (painting["required_door"]["room"]) { + rd_room = painting["required_door"]["room"].as(); + } + + painting_exit.door = AddOrGetDoor( + rd_room, painting["required_door"]["door"].as()); + } + + room_obj.paintings.push_back(painting_exit); + } + } + } + + if (room_it.second["progression"]) { + for (const auto &progression_it : room_it.second["progression"]) { + std::string progressive_item_name = + progression_it.first.as(); + + int index = 1; + for (const auto &stage : progression_it.second) { + int door_id = -1; + + if (stage.IsScalar()) { + door_id = AddOrGetDoor(room_obj.name, stage.as()); + } else { + door_id = AddOrGetDoor(stage["room"].as(), + stage["door"].as()); + } + + doors_[door_id].progressives.push_back( + {.item_name = progressive_item_name, .quantity = index}); + index++; + } + } + } + } + + map_areas_.reserve(areas_config.size()); + + std::map fold_areas; + for (const auto &area_it : areas_config) { + if (area_it.second["map"]) { + int area_id = AddOrGetArea(area_it.first.as()); + MapArea &area_obj = map_areas_[area_id]; + area_obj.map_x = area_it.second["map"][0].as(); + area_obj.map_y = area_it.second["map"][1].as(); + } else if (area_it.second["fold_into"]) { + fold_areas[area_it.first.as()] = + AddOrGetArea(area_it.second["fold_into"].as()); + } + } + + for (const Panel &panel : panels_) { + if (panel.check) { + int room_id = panel.room; + std::string room_name = rooms_[room_id].name; + + 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; + } + + 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 = panel.name, + .ap_location_name = room_name + " - " + panel.name, + .room = panel.room, + .panels = {panel.id}}); + } + } + + for (const Door &door : doors_) { + if (!door.skip_location) { + 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, + .room = door.room, + .panels = door.panels}); + } + } +} + +int GameData::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 GameData::AddOrGetDoor(std::string room, std::string door) { + std::string full_name = room + " - " + door; + + if (!door_by_id_.count(full_name)) { + door_by_id_[full_name] = doors_.size(); + doors_.push_back({.room = AddOrGetRoom(room), .name = door}); + } + + return door_by_id_[full_name]; +} + +int GameData::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 GameData::AddOrGetArea(std::string area) { + if (!area_by_id_.count(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]; +} + +const GameData &GetGameData() { + static GameData *instance = new GameData(); + return *instance; +} diff --git a/src/game_data.h b/src/game_data.h new file mode 100644 index 0000000..0cc7a7b --- /dev/null +++ b/src/game_data.h @@ -0,0 +1,135 @@ +#ifndef GAME_DATA_H_9C42AC51 +#define GAME_DATA_H_9C42AC51 + +#include +#include +#include +#include + +enum class LingoColor { + kNone, + kBlack, + kRed, + kBlue, + kYellow, + kGreen, + kOrange, + kPurple, + kBrown, + kGray +}; + +struct Panel { + int id; + int room; + std::string name; + std::vector colors; + std::vector required_rooms; + std::vector required_doors; + bool check = false; + bool exclude_reduce = false; + bool achievement = false; +}; + +struct ProgressiveRequirement { + std::string item_name; + int quantity = 0; +}; + +struct Door { + int room; + std::string name; + std::string location_name; + std::string item_name; + std::string group_name; + bool skip_location = false; + bool skip_item = false; + std::vector panels; + bool exclude_reduce = true; + std::vector progressives; +}; + +struct Exit { + int destination_room; + std::optional door; + bool painting = false; +}; + +struct PaintingExit { + std::string id; + std::optional door; +}; + +struct Room { + std::string name; + std::vector exits; + std::vector paintings; +}; + +struct Location { + std::string name; + std::string ap_location_name; + int room; + std::vector panels; +}; + +struct MapArea { + int id; + std::string name; + std::vector locations; + int map_x; + int map_y; +}; + +class GameData { + public: + GameData(); + + const std::vector& GetMapAreas() const { return map_areas_; } + + const MapArea& GetMapArea(int id) const { return map_areas_.at(id); } + + int GetRoomByName(const std::string& name) const { + return room_by_id_.at(name); + } + + const Room& GetRoom(int room_id) const { return rooms_.at(room_id); } + + const std::vector& GetDoors() const { return doors_; } + + const Door& GetDoor(int door_id) const { return doors_.at(door_id); } + + const Panel& GetPanel(int panel_id) const { return panels_.at(panel_id); } + + int GetRoomForPainting(const std::string& painting_id) const { + return room_by_painting_.at(painting_id); + } + + const std::vector& GetAchievementPanels() const { + return achievement_panels_; + } + + private: + int AddOrGetRoom(std::string room); + int AddOrGetDoor(std::string room, std::string door); + int AddOrGetPanel(std::string room, std::string panel); + int AddOrGetArea(std::string area); + + std::vector rooms_; + std::vector doors_; + std::vector panels_; + std::vector map_areas_; + + std::map room_by_id_; + std::map door_by_id_; + std::map panel_by_id_; + std::map area_by_id_; + + std::map room_by_painting_; + + std::vector achievement_panels_; +}; + +const GameData& GetGameData(); + +#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fe9aceb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,21 @@ +#include + +#ifndef WX_PRECOMP +#include +#endif + +#include "tracker_config.h" +#include "tracker_frame.h" + +class TrackerApp : public wxApp { + public: + virtual bool OnInit() { + GetTrackerConfig().Load(); + + TrackerFrame *frame = new TrackerFrame(); + frame->Show(true); + return true; + } +}; + +wxIMPLEMENT_APP(TrackerApp); diff --git a/src/tracker_config.cpp b/src/tracker_config.cpp new file mode 100644 index 0000000..96bb60a --- /dev/null +++ b/src/tracker_config.cpp @@ -0,0 +1,33 @@ +#include "tracker_config.h" + +#include +#include + +constexpr const char* CONFIG_FILE_NAME = "config.yaml"; + +void TrackerConfig::Load() { + try { + YAML::Node file = YAML::LoadFile(CONFIG_FILE_NAME); + + ap_server = file["ap_server"].as(); + ap_player = file["ap_player"].as(); + ap_password = file["ap_password"].as(); + } catch (const std::exception&) { + // It's fine if the file can't be loaded. + } +} + +void TrackerConfig::Save() { + YAML::Node output; + output["ap_server"] = ap_server; + output["ap_player"] = ap_player; + output["ap_password"] = ap_password; + + std::ofstream filewriter(CONFIG_FILE_NAME); + filewriter << output; +} + +TrackerConfig& GetTrackerConfig() { + static TrackerConfig* instance = new TrackerConfig(); + return *instance; +} diff --git a/src/tracker_config.h b/src/tracker_config.h new file mode 100644 index 0000000..e2e6cd7 --- /dev/null +++ b/src/tracker_config.h @@ -0,0 +1,19 @@ +#ifndef TRACKER_CONFIG_H_36CDD648 +#define TRACKER_CONFIG_H_36CDD648 + +#include + +class TrackerConfig { + public: + void Load(); + + void Save(); + + std::string ap_server; + std::string ap_player; + std::string ap_password; +}; + +TrackerConfig& GetTrackerConfig(); + +#endif /* end of include guard: TRACKER_CONFIG_H_36CDD648 */ diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp new file mode 100644 index 0000000..2a862a5 --- /dev/null +++ b/src/tracker_frame.cpp @@ -0,0 +1,86 @@ +#include "tracker_frame.h" + +#include "ap_state.h" +#include "connection_dialog.h" +#include "tracker_config.h" +#include "tracker_panel.h" + +enum TrackerFrameIds { ID_CONNECT = 1 }; + +wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); +wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); + +TrackerFrame::TrackerFrame() + : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition, + wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) { + ::wxInitAllImageHandlers(); + + AP_SetTrackerFrame(this); + + SetSize(1280, 728); + + wxMenu *menuFile = new wxMenu(); + menuFile->Append(ID_CONNECT, "&Connect"); + menuFile->Append(wxID_EXIT); + + wxMenu *menuHelp = new wxMenu(); + menuHelp->Append(wxID_ABOUT); + + wxMenuBar *menuBar = new wxMenuBar(); + menuBar->Append(menuFile, "&File"); + menuBar->Append(menuHelp, "&Help"); + + SetMenuBar(menuBar); + + CreateStatusBar(); + SetStatusText("Not connected to Archipelago."); + + Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); + Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); + Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT); + Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); + Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); + + tracker_panel_ = new TrackerPanel(this); +} + +void TrackerFrame::SetStatusMessage(std::string message) { + wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED); + event->SetString(message.c_str()); + + QueueEvent(event); +} + +void TrackerFrame::UpdateIndicators() { + QueueEvent(new wxCommandEvent(STATE_CHANGED)); +} + +void TrackerFrame::OnAbout(wxCommandEvent &event) { + wxMessageBox("Lingo Archipelago Tracker by hatkirby", + "About lingo-ap-tracker", wxOK | wxICON_INFORMATION); +} + +void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } + +void TrackerFrame::OnConnect(wxCommandEvent &event) { + ConnectionDialog dlg; + + if (dlg.ShowModal() == wxID_OK) { + GetTrackerConfig().ap_server = dlg.GetServerValue(); + GetTrackerConfig().ap_player = dlg.GetPlayerValue(); + GetTrackerConfig().ap_password = dlg.GetPasswordValue(); + GetTrackerConfig().Save(); + + AP_Connect(dlg.GetServerValue(), dlg.GetPlayerValue(), + dlg.GetPasswordValue()); + } +} + +void TrackerFrame::OnStateChanged(wxCommandEvent &event) { + tracker_panel_->UpdateIndicators(); + Refresh(); +} + +void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { + SetStatusText(event.GetString()); +} diff --git a/src/tracker_frame.h b/src/tracker_frame.h new file mode 100644 index 0000000..6f4e4fc --- /dev/null +++ b/src/tracker_frame.h @@ -0,0 +1,33 @@ +#ifndef TRACKER_FRAME_H_86BD8DFB +#define TRACKER_FRAME_H_86BD8DFB + +#include + +#ifndef WX_PRECOMP +#include +#endif + +class TrackerPanel; + +wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); +wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); + +class TrackerFrame : public wxFrame { + public: + TrackerFrame(); + + void SetStatusMessage(std::string message); + + void UpdateIndicators(); + + private: + void OnExit(wxCommandEvent &event); + void OnAbout(wxCommandEvent &event); + void OnConnect(wxCommandEvent &event); + void OnStateChanged(wxCommandEvent &event); + void OnStatusChanged(wxCommandEvent &event); + + TrackerPanel *tracker_panel_; +}; + +#endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp new file mode 100644 index 0000000..0e0569b --- /dev/null +++ b/src/tracker_panel.cpp @@ -0,0 +1,149 @@ +#include "tracker_panel.h" + +#include "ap_state.h" +#include "area_popup.h" +#include "game_data.h" +#include "tracker_state.h" + +constexpr int AREA_ACTUAL_SIZE = 64; +constexpr int AREA_BORDER_SIZE = 5; +constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; + +TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { + map_image_ = wxImage("assets/lingo_map.png", wxBITMAP_TYPE_PNG); + if (!map_image_.IsOk()) { + return; + } + + for (const MapArea &map_area : GetGameData().GetMapAreas()) { + AreaIndicator area; + area.area_id = map_area.id; + + area.popup = new AreaPopup(this, map_area.id); + area.popup->SetPosition({0, 0}); + + areas_.push_back(area); + } + + Redraw(); + + Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); + Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); +} + +void TrackerPanel::UpdateIndicators() { + Redraw(); + + for (AreaIndicator &area : areas_) { + area.popup->UpdateIndicators(); + } +} + +void TrackerPanel::OnPaint(wxPaintEvent &event) { + if (GetSize() != rendered_.GetSize()) { + Redraw(); + } + + wxPaintDC dc(this); + dc.DrawBitmap(rendered_, 0, 0); + + event.Skip(); +} + +void TrackerPanel::OnMouseMove(wxMouseEvent &event) { + for (AreaIndicator &area : areas_) { + if (area.real_x1 <= event.GetX() && event.GetX() < area.real_x2 && + area.real_y1 <= event.GetY() && event.GetY() < area.real_y2) { + area.popup->Show(); + } else { + area.popup->Hide(); + } + } + + event.Skip(); +} + +void TrackerPanel::Redraw() { + wxSize panel_size = GetSize(); + wxSize image_size = map_image_.GetSize(); + + int final_x = 0; + int final_y = 0; + int final_width = panel_size.GetWidth(); + int final_height = panel_size.GetHeight(); + + if (image_size.GetWidth() * panel_size.GetHeight() > + panel_size.GetWidth() * image_size.GetHeight()) { + final_height = (panel_size.GetWidth() * image_size.GetHeight()) / + image_size.GetWidth(); + final_y = (panel_size.GetHeight() - final_height) / 2; + } else { + final_width = (image_size.GetWidth() * panel_size.GetHeight()) / + image_size.GetHeight(); + final_x = (panel_size.GetWidth() - final_width) / 2; + } + + rendered_ = wxBitmap( + map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) + .Size(panel_size, {final_x, final_y}, 0, 0, 0)); + + wxMemoryDC dc; + dc.SelectObject(rendered_); + + for (AreaIndicator &area : areas_) { + const wxBrush *brush_color = wxGREY_BRUSH; + + const MapArea &map_area = GetGameData().GetMapArea(area.area_id); + bool has_reachable_unchecked = false; + bool has_unreachable_unchecked = false; + for (int section_id = 0; section_id < map_area.locations.size(); + section_id++) { + if (!AP_HasCheckedGameLocation(area.area_id, section_id)) { + if (GetTrackerState().IsLocationReachable(area.area_id, section_id)) { + has_reachable_unchecked = true; + } else { + has_unreachable_unchecked = true; + } + } + } + + if (has_reachable_unchecked && has_unreachable_unchecked) { + brush_color = wxYELLOW_BRUSH; + } else if (has_reachable_unchecked) { + brush_color = wxGREEN_BRUSH; + } else if (has_unreachable_unchecked) { + brush_color = wxRED_BRUSH; + } + + int real_area_size = + final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); + int actual_border_size = + real_area_size * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; + int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * + final_width / image_size.GetWidth(); + int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * + final_width / image_size.GetWidth(); + + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); + dc.SetBrush(*brush_color); + dc.DrawRectangle({real_area_x, real_area_y}, + {real_area_size, real_area_size}); + + area.real_x1 = real_area_x; + area.real_x2 = real_area_x + real_area_size; + area.real_y1 = real_area_y; + area.real_y2 = real_area_y + real_area_size; + + int popup_x = + final_x + map_area.map_x * final_width / image_size.GetWidth(); + int popup_y = + final_y + map_area.map_y * final_width / image_size.GetWidth(); + if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { + popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); + } + if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { + popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); + } + area.popup->SetPosition({popup_x, popup_y}); + } +} diff --git a/src/tracker_panel.h b/src/tracker_panel.h new file mode 100644 index 0000000..a871f90 --- /dev/null +++ b/src/tracker_panel.h @@ -0,0 +1,39 @@ +#ifndef TRACKER_PANEL_H_D675A54D +#define TRACKER_PANEL_H_D675A54D + +#include + +#ifndef WX_PRECOMP +#include +#endif + +class AreaPopup; + +class TrackerPanel : public wxPanel { + public: + TrackerPanel(wxWindow *parent); + + void UpdateIndicators(); + + private: + struct AreaIndicator { + int area_id = -1; + AreaPopup *popup = nullptr; + int real_x1 = 0; + int real_y1 = 0; + int real_x2 = 0; + int real_y2 = 0; + }; + + void OnPaint(wxPaintEvent &event); + void OnMouseMove(wxMouseEvent &event); + + void Redraw(); + + wxImage map_image_; + wxBitmap rendered_; + + std::vector areas_; +}; + +#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp new file mode 100644 index 0000000..858ec3e --- /dev/null +++ b/src/tracker_state.cpp @@ -0,0 +1,181 @@ +#include "tracker_state.h" + +#include +#include + +#include "ap_state.h" +#include "game_data.h" + +bool IsDoorReachable_Helper(int door_id, const std::set& reachable_rooms); + +bool IsPanelReachable_Helper(int panel_id, + const std::set& reachable_rooms) { + const Panel& panel_obj = GetGameData().GetPanel(panel_id); + + if (!reachable_rooms.count(panel_obj.room)) { + return false; + } + + if (panel_obj.name == "THE MASTER") { + int achievements_accessible = 0; + + for (int achieve_id : GetGameData().GetAchievementPanels()) { + if (IsPanelReachable_Helper(achieve_id, reachable_rooms)) { + achievements_accessible++; + + if (achievements_accessible >= AP_GetMasteryRequirement()) { + break; + } + } + } + + return (achievements_accessible >= AP_GetMasteryRequirement()); + } + + for (int room_id : panel_obj.required_rooms) { + if (!reachable_rooms.count(room_id)) { + return false; + } + } + + for (int door_id : panel_obj.required_doors) { + if (!IsDoorReachable_Helper(door_id, reachable_rooms)) { + return false; + } + } + + if (AP_IsColorShuffle()) { + for (LingoColor color : panel_obj.colors) { + if (!AP_HasColorItem(color)) { + return false; + } + } + } + + return true; +} + +bool IsDoorReachable_Helper(int door_id, const std::set& reachable_rooms) { + const Door& door_obj = GetGameData().GetDoor(door_id); + + if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { + if (!reachable_rooms.count(door_obj.room)) { + return false; + } + + for (int panel_id : door_obj.panels) { + if (!IsPanelReachable_Helper(panel_id, reachable_rooms)) { + return false; + } + } + + return true; + } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && + !door_obj.group_name.empty()) { + return AP_HasItem(door_obj.group_name); + } else { + bool has_item = AP_HasItem(door_obj.item_name); + + if (!has_item) { + for (const ProgressiveRequirement& prog_req : door_obj.progressives) { + if (AP_HasItem(prog_req.item_name, prog_req.quantity)) { + has_item = true; + break; + } + } + } + + return has_item; + } +} + +void TrackerState::CalculateState() { + reachability_.clear(); + + std::set reachable_rooms; + + std::list flood_boundary; + flood_boundary.push_back( + {.destination_room = GetGameData().GetRoomByName("Menu")}); + + bool reachable_changed = true; + while (reachable_changed) { + reachable_changed = false; + + std::list new_boundary; + for (const Exit& room_exit : flood_boundary) { + if (reachable_rooms.count(room_exit.destination_room)) { + continue; + } + + bool valid_transition = false; + if (room_exit.door.has_value()) { + if (IsDoorReachable_Helper(*room_exit.door, reachable_rooms)) { + valid_transition = true; + } else if (AP_GetDoorShuffleMode() == kNO_DOORS) { + new_boundary.push_back(room_exit); + } + } else { + valid_transition = true; + } + + if (valid_transition) { + reachable_rooms.insert(room_exit.destination_room); + reachable_changed = true; + + const Room& room_obj = + GetGameData().GetRoom(room_exit.destination_room); + for (const Exit& out_edge : room_obj.exits) { + if (!out_edge.painting || !AP_IsPaintingShuffle()) { + new_boundary.push_back(out_edge); + } + } + + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& out_edge : room_obj.paintings) { + if (AP_GetPaintingMapping().count(out_edge.id)) { + Exit painting_exit; + painting_exit.destination_room = GetGameData().GetRoomForPainting( + AP_GetPaintingMapping().at(out_edge.id)); + painting_exit.door = out_edge.door; + + new_boundary.push_back(painting_exit); + } + } + } + } + } + + flood_boundary = new_boundary; + } + + for (const MapArea& map_area : GetGameData().GetMapAreas()) { + for (int section_id = 0; section_id < map_area.locations.size(); + section_id++) { + const Location& location_section = map_area.locations.at(section_id); + bool reachable = reachable_rooms.count(location_section.room); + if (reachable) { + for (int panel_id : location_section.panels) { + reachable &= IsPanelReachable_Helper(panel_id, reachable_rooms); + } + } + + reachability_[{map_area.id, section_id}] = reachable; + } + } +} + +bool TrackerState::IsLocationReachable(int area_id, int section_id) { + std::tuple key = {area_id, section_id}; + + if (reachability_.count(key)) { + return reachability_.at(key); + } else { + return false; + } +} + +TrackerState& GetTrackerState() { + static TrackerState* instance = new TrackerState(); + return *instance; +} diff --git a/src/tracker_state.h b/src/tracker_state.h new file mode 100644 index 0000000..879e6f2 --- /dev/null +++ b/src/tracker_state.h @@ -0,0 +1,19 @@ +#ifndef TRACKER_STATE_H_8639BC90 +#define TRACKER_STATE_H_8639BC90 + +#include +#include + +class TrackerState { + public: + void CalculateState(); + + bool IsLocationReachable(int area_id, int section_id); + + private: + std::map, bool> reachability_; +}; + +TrackerState& GetTrackerState(); + +#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ diff --git a/tracker_config.cpp b/tracker_config.cpp deleted file mode 100644 index 96bb60a..0000000 --- a/tracker_config.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "tracker_config.h" - -#include -#include - -constexpr const char* CONFIG_FILE_NAME = "config.yaml"; - -void TrackerConfig::Load() { - try { - YAML::Node file = YAML::LoadFile(CONFIG_FILE_NAME); - - ap_server = file["ap_server"].as(); - ap_player = file["ap_player"].as(); - ap_password = file["ap_password"].as(); - } catch (const std::exception&) { - // It's fine if the file can't be loaded. - } -} - -void TrackerConfig::Save() { - YAML::Node output; - output["ap_server"] = ap_server; - output["ap_player"] = ap_player; - output["ap_password"] = ap_password; - - std::ofstream filewriter(CONFIG_FILE_NAME); - filewriter << output; -} - -TrackerConfig& GetTrackerConfig() { - static TrackerConfig* instance = new TrackerConfig(); - return *instance; -} diff --git a/tracker_config.h b/tracker_config.h deleted file mode 100644 index e2e6cd7..0000000 --- a/tracker_config.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TRACKER_CONFIG_H_36CDD648 -#define TRACKER_CONFIG_H_36CDD648 - -#include - -class TrackerConfig { - public: - void Load(); - - void Save(); - - std::string ap_server; - std::string ap_player; - std::string ap_password; -}; - -TrackerConfig& GetTrackerConfig(); - -#endif /* end of include guard: TRACKER_CONFIG_H_36CDD648 */ diff --git a/tracker_frame.cpp b/tracker_frame.cpp deleted file mode 100644 index 2a862a5..0000000 --- a/tracker_frame.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "tracker_frame.h" - -#include "ap_state.h" -#include "connection_dialog.h" -#include "tracker_config.h" -#include "tracker_panel.h" - -enum TrackerFrameIds { ID_CONNECT = 1 }; - -wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); -wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); - -TrackerFrame::TrackerFrame() - : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition, - wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) { - ::wxInitAllImageHandlers(); - - AP_SetTrackerFrame(this); - - SetSize(1280, 728); - - wxMenu *menuFile = new wxMenu(); - menuFile->Append(ID_CONNECT, "&Connect"); - menuFile->Append(wxID_EXIT); - - wxMenu *menuHelp = new wxMenu(); - menuHelp->Append(wxID_ABOUT); - - wxMenuBar *menuBar = new wxMenuBar(); - menuBar->Append(menuFile, "&File"); - menuBar->Append(menuHelp, "&Help"); - - SetMenuBar(menuBar); - - CreateStatusBar(); - SetStatusText("Not connected to Archipelago."); - - Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); - Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); - Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT); - Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); - Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); - - tracker_panel_ = new TrackerPanel(this); -} - -void TrackerFrame::SetStatusMessage(std::string message) { - wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED); - event->SetString(message.c_str()); - - QueueEvent(event); -} - -void TrackerFrame::UpdateIndicators() { - QueueEvent(new wxCommandEvent(STATE_CHANGED)); -} - -void TrackerFrame::OnAbout(wxCommandEvent &event) { - wxMessageBox("Lingo Archipelago Tracker by hatkirby", - "About lingo-ap-tracker", wxOK | wxICON_INFORMATION); -} - -void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } - -void TrackerFrame::OnConnect(wxCommandEvent &event) { - ConnectionDialog dlg; - - if (dlg.ShowModal() == wxID_OK) { - GetTrackerConfig().ap_server = dlg.GetServerValue(); - GetTrackerConfig().ap_player = dlg.GetPlayerValue(); - GetTrackerConfig().ap_password = dlg.GetPasswordValue(); - GetTrackerConfig().Save(); - - AP_Connect(dlg.GetServerValue(), dlg.GetPlayerValue(), - dlg.GetPasswordValue()); - } -} - -void TrackerFrame::OnStateChanged(wxCommandEvent &event) { - tracker_panel_->UpdateIndicators(); - Refresh(); -} - -void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { - SetStatusText(event.GetString()); -} diff --git a/tracker_frame.h b/tracker_frame.h deleted file mode 100644 index 6f4e4fc..0000000 --- a/tracker_frame.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef TRACKER_FRAME_H_86BD8DFB -#define TRACKER_FRAME_H_86BD8DFB - -#include - -#ifndef WX_PRECOMP -#include -#endif - -class TrackerPanel; - -wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); -wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); - -class TrackerFrame : public wxFrame { - public: - TrackerFrame(); - - void SetStatusMessage(std::string message); - - void UpdateIndicators(); - - private: - void OnExit(wxCommandEvent &event); - void OnAbout(wxCommandEvent &event); - void OnConnect(wxCommandEvent &event); - void OnStateChanged(wxCommandEvent &event); - void OnStatusChanged(wxCommandEvent &event); - - TrackerPanel *tracker_panel_; -}; - -#endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ diff --git a/tracker_panel.cpp b/tracker_panel.cpp deleted file mode 100644 index 0e0569b..0000000 --- a/tracker_panel.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "tracker_panel.h" - -#include "ap_state.h" -#include "area_popup.h" -#include "game_data.h" -#include "tracker_state.h" - -constexpr int AREA_ACTUAL_SIZE = 64; -constexpr int AREA_BORDER_SIZE = 5; -constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; - -TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { - map_image_ = wxImage("assets/lingo_map.png", wxBITMAP_TYPE_PNG); - if (!map_image_.IsOk()) { - return; - } - - for (const MapArea &map_area : GetGameData().GetMapAreas()) { - AreaIndicator area; - area.area_id = map_area.id; - - area.popup = new AreaPopup(this, map_area.id); - area.popup->SetPosition({0, 0}); - - areas_.push_back(area); - } - - Redraw(); - - Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); - Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); -} - -void TrackerPanel::UpdateIndicators() { - Redraw(); - - for (AreaIndicator &area : areas_) { - area.popup->UpdateIndicators(); - } -} - -void TrackerPanel::OnPaint(wxPaintEvent &event) { - if (GetSize() != rendered_.GetSize()) { - Redraw(); - } - - wxPaintDC dc(this); - dc.DrawBitmap(rendered_, 0, 0); - - event.Skip(); -} - -void TrackerPanel::OnMouseMove(wxMouseEvent &event) { - for (AreaIndicator &area : areas_) { - if (area.real_x1 <= event.GetX() && event.GetX() < area.real_x2 && - area.real_y1 <= event.GetY() && event.GetY() < area.real_y2) { - area.popup->Show(); - } else { - area.popup->Hide(); - } - } - - event.Skip(); -} - -void TrackerPanel::Redraw() { - wxSize panel_size = GetSize(); - wxSize image_size = map_image_.GetSize(); - - int final_x = 0; - int final_y = 0; - int final_width = panel_size.GetWidth(); - int final_height = panel_size.GetHeight(); - - if (image_size.GetWidth() * panel_size.GetHeight() > - panel_size.GetWidth() * image_size.GetHeight()) { - final_height = (panel_size.GetWidth() * image_size.GetHeight()) / - image_size.GetWidth(); - final_y = (panel_size.GetHeight() - final_height) / 2; - } else { - final_width = (image_size.GetWidth() * panel_size.GetHeight()) / - image_size.GetHeight(); - final_x = (panel_size.GetWidth() - final_width) / 2; - } - - rendered_ = wxBitmap( - map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) - .Size(panel_size, {final_x, final_y}, 0, 0, 0)); - - wxMemoryDC dc; - dc.SelectObject(rendered_); - - for (AreaIndicator &area : areas_) { - const wxBrush *brush_color = wxGREY_BRUSH; - - const MapArea &map_area = GetGameData().GetMapArea(area.area_id); - bool has_reachable_unchecked = false; - bool has_unreachable_unchecked = false; - for (int section_id = 0; section_id < map_area.locations.size(); - section_id++) { - if (!AP_HasCheckedGameLocation(area.area_id, section_id)) { - if (GetTrackerState().IsLocationReachable(area.area_id, section_id)) { - has_reachable_unchecked = true; - } else { - has_unreachable_unchecked = true; - } - } - } - - if (has_reachable_unchecked && has_unreachable_unchecked) { - brush_color = wxYELLOW_BRUSH; - } else if (has_reachable_unchecked) { - brush_color = wxGREEN_BRUSH; - } else if (has_unreachable_unchecked) { - brush_color = wxRED_BRUSH; - } - - int real_area_size = - final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); - int actual_border_size = - real_area_size * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; - int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * - final_width / image_size.GetWidth(); - int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * - final_width / image_size.GetWidth(); - - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); - dc.SetBrush(*brush_color); - dc.DrawRectangle({real_area_x, real_area_y}, - {real_area_size, real_area_size}); - - area.real_x1 = real_area_x; - area.real_x2 = real_area_x + real_area_size; - area.real_y1 = real_area_y; - area.real_y2 = real_area_y + real_area_size; - - int popup_x = - final_x + map_area.map_x * final_width / image_size.GetWidth(); - int popup_y = - final_y + map_area.map_y * final_width / image_size.GetWidth(); - if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { - popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); - } - if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { - popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); - } - area.popup->SetPosition({popup_x, popup_y}); - } -} diff --git a/tracker_panel.h b/tracker_panel.h deleted file mode 100644 index a871f90..0000000 --- a/tracker_panel.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TRACKER_PANEL_H_D675A54D -#define TRACKER_PANEL_H_D675A54D - -#include - -#ifndef WX_PRECOMP -#include -#endif - -class AreaPopup; - -class TrackerPanel : public wxPanel { - public: - TrackerPanel(wxWindow *parent); - - void UpdateIndicators(); - - private: - struct AreaIndicator { - int area_id = -1; - AreaPopup *popup = nullptr; - int real_x1 = 0; - int real_y1 = 0; - int real_x2 = 0; - int real_y2 = 0; - }; - - void OnPaint(wxPaintEvent &event); - void OnMouseMove(wxMouseEvent &event); - - void Redraw(); - - wxImage map_image_; - wxBitmap rendered_; - - std::vector areas_; -}; - -#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ diff --git a/tracker_state.cpp b/tracker_state.cpp deleted file mode 100644 index 858ec3e..0000000 --- a/tracker_state.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "tracker_state.h" - -#include -#include - -#include "ap_state.h" -#include "game_data.h" - -bool IsDoorReachable_Helper(int door_id, const std::set& reachable_rooms); - -bool IsPanelReachable_Helper(int panel_id, - const std::set& reachable_rooms) { - const Panel& panel_obj = GetGameData().GetPanel(panel_id); - - if (!reachable_rooms.count(panel_obj.room)) { - return false; - } - - if (panel_obj.name == "THE MASTER") { - int achievements_accessible = 0; - - for (int achieve_id : GetGameData().GetAchievementPanels()) { - if (IsPanelReachable_Helper(achieve_id, reachable_rooms)) { - achievements_accessible++; - - if (achievements_accessible >= AP_GetMasteryRequirement()) { - break; - } - } - } - - return (achievements_accessible >= AP_GetMasteryRequirement()); - } - - for (int room_id : panel_obj.required_rooms) { - if (!reachable_rooms.count(room_id)) { - return false; - } - } - - for (int door_id : panel_obj.required_doors) { - if (!IsDoorReachable_Helper(door_id, reachable_rooms)) { - return false; - } - } - - if (AP_IsColorShuffle()) { - for (LingoColor color : panel_obj.colors) { - if (!AP_HasColorItem(color)) { - return false; - } - } - } - - return true; -} - -bool IsDoorReachable_Helper(int door_id, const std::set& reachable_rooms) { - const Door& door_obj = GetGameData().GetDoor(door_id); - - if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { - if (!reachable_rooms.count(door_obj.room)) { - return false; - } - - for (int panel_id : door_obj.panels) { - if (!IsPanelReachable_Helper(panel_id, reachable_rooms)) { - return false; - } - } - - return true; - } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && - !door_obj.group_name.empty()) { - return AP_HasItem(door_obj.group_name); - } else { - bool has_item = AP_HasItem(door_obj.item_name); - - if (!has_item) { - for (const ProgressiveRequirement& prog_req : door_obj.progressives) { - if (AP_HasItem(prog_req.item_name, prog_req.quantity)) { - has_item = true; - break; - } - } - } - - return has_item; - } -} - -void TrackerState::CalculateState() { - reachability_.clear(); - - std::set reachable_rooms; - - std::list flood_boundary; - flood_boundary.push_back( - {.destination_room = GetGameData().GetRoomByName("Menu")}); - - bool reachable_changed = true; - while (reachable_changed) { - reachable_changed = false; - - std::list new_boundary; - for (const Exit& room_exit : flood_boundary) { - if (reachable_rooms.count(room_exit.destination_room)) { - continue; - } - - bool valid_transition = false; - if (room_exit.door.has_value()) { - if (IsDoorReachable_Helper(*room_exit.door, reachable_rooms)) { - valid_transition = true; - } else if (AP_GetDoorShuffleMode() == kNO_DOORS) { - new_boundary.push_back(room_exit); - } - } else { - valid_transition = true; - } - - if (valid_transition) { - reachable_rooms.insert(room_exit.destination_room); - reachable_changed = true; - - const Room& room_obj = - GetGameData().GetRoom(room_exit.destination_room); - for (const Exit& out_edge : room_obj.exits) { - if (!out_edge.painting || !AP_IsPaintingShuffle()) { - new_boundary.push_back(out_edge); - } - } - - if (AP_IsPaintingShuffle()) { - for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id)) { - Exit painting_exit; - painting_exit.destination_room = GetGameData().GetRoomForPainting( - AP_GetPaintingMapping().at(out_edge.id)); - painting_exit.door = out_edge.door; - - new_boundary.push_back(painting_exit); - } - } - } - } - } - - flood_boundary = new_boundary; - } - - for (const MapArea& map_area : GetGameData().GetMapAreas()) { - for (int section_id = 0; section_id < map_area.locations.size(); - section_id++) { - const Location& location_section = map_area.locations.at(section_id); - bool reachable = reachable_rooms.count(location_section.room); - if (reachable) { - for (int panel_id : location_section.panels) { - reachable &= IsPanelReachable_Helper(panel_id, reachable_rooms); - } - } - - reachability_[{map_area.id, section_id}] = reachable; - } - } -} - -bool TrackerState::IsLocationReachable(int area_id, int section_id) { - std::tuple key = {area_id, section_id}; - - if (reachability_.count(key)) { - return reachability_.at(key); - } else { - return false; - } -} - -TrackerState& GetTrackerState() { - static TrackerState* instance = new TrackerState(); - return *instance; -} diff --git a/tracker_state.h b/tracker_state.h deleted file mode 100644 index 879e6f2..0000000 --- a/tracker_state.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TRACKER_STATE_H_8639BC90 -#define TRACKER_STATE_H_8639BC90 - -#include -#include - -class TrackerState { - public: - void CalculateState(); - - bool IsLocationReachable(int area_id, int section_id); - - private: - std::map, bool> reachability_; -}; - -TrackerState& GetTrackerState(); - -#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ -- cgit 1.4.1