From 70f1c629a6e08e0f9c58707f0470e08c6ffeca34 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 2 May 2023 20:14:43 -0400 Subject: Added reachability checking (only no doors rn) --- CMakeLists.txt | 1 + ap_state.cpp | 41 +++++++++++++++- ap_state.h | 13 +++++ area_popup.cpp | 5 +- area_window.cpp | 16 ++++-- game_data.h | 10 ++++ tracker_frame.cpp | 22 +++++++-- tracker_frame.h | 5 ++ tracker_state.cpp | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tracker_state.h | 19 ++++++++ 10 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 tracker_state.cpp create mode 100644 tracker_state.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c346a9..399060b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable(lingo_ap_tracker ap_state.cpp connection_dialog.cpp eye_indicator.cpp + tracker_state.cpp ) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 17) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) diff --git a/ap_state.cpp b/ap_state.cpp index 4d7ddb7..9df487f 100644 --- a/ap_state.cpp +++ b/ap_state.cpp @@ -9,6 +9,7 @@ #include #include "game_data.h" +#include "tracker_state.h" constexpr int AP_MAJOR = 0; constexpr int AP_MINOR = 4; @@ -16,6 +17,11 @@ constexpr int AP_REVISION = 0; constexpr int ITEM_HANDLING = 7; // <- all +NLOHMANN_JSON_SERIALIZE_ENUM(DoorShuffleMode, + {{DoorShuffleMode::kNone, "none"}, + {DoorShuffleMode::kSimple, "simple"}, + {DoorShuffleMode::kComplex, "complex"}}); + APState::APState() { std::thread([this]() { for (;;) { @@ -92,6 +98,9 @@ void APState::Connect(std::string server, std::string player, 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(); + connected = true; has_connection_result = true; }); @@ -169,6 +178,16 @@ void APState::Connect(std::string server, std::string player, } } + 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; @@ -185,7 +204,27 @@ bool APState::HasCheckedGameLocation(int area_id, int section_id) const { } } -void APState::RefreshTracker() { tracker_frame_->UpdateIndicators(); } +bool APState::HasColorItem(LingoColor color) const { + if (ap_id_by_color_.count(color)) { + return inventory_.count(ap_id_by_color_.at(color)); + } else { + return false; + } +} + +void APState::RefreshTracker() { + GetTrackerState().CalculateState(); + tracker_frame_->UpdateIndicators(); +} + +int64_t APState::GetItemId(const std::string& item_name) { + int64_t ap_id = apclient_->get_item_id(item_name); + if (ap_id == APClient::INVALID_NAME_ID) { + std::cout << "Could not find AP item ID for " << item_name << std::endl; + } + + return ap_id; +} APState& GetAPState() { static APState* instance = new APState(); diff --git a/ap_state.h b/ap_state.h index b5a94e3..d818b40 100644 --- a/ap_state.h +++ b/ap_state.h @@ -11,6 +11,8 @@ #include "game_data.h" #include "tracker_frame.h" +enum class DoorShuffleMode { kNone, kSimple, kComplex }; + class APState { public: APState(); @@ -23,9 +25,17 @@ class APState { bool HasCheckedGameLocation(int area_id, int section_id) const; + bool HasColorItem(LingoColor color) const; + + DoorShuffleMode GetDoorShuffleMode() const { return door_shuffle_mode_; } + + bool IsColorShuffle() const { return color_shuffle_; } + private: void RefreshTracker(); + int64_t GetItemId(const std::string& item_name); + TrackerFrame* tracker_frame_; std::unique_ptr apclient_; @@ -39,6 +49,9 @@ class APState { std::map ap_id_by_door_id_; std::map ap_id_by_door_group_id_; std::map ap_id_by_color_; + + DoorShuffleMode door_shuffle_mode_ = DoorShuffleMode::kNone; + bool color_shuffle_ = false; }; APState& GetAPState(); diff --git a/area_popup.cpp b/area_popup.cpp index e46e4ec..4cc3c63 100644 --- a/area_popup.cpp +++ b/area_popup.cpp @@ -2,6 +2,7 @@ #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) { @@ -43,7 +44,9 @@ void AreaPopup::UpdateIndicators() { for (int section_id = 0; section_id < map_area.locations.size(); section_id++) { bool checked = GetAPState().HasCheckedGameLocation(area_id_, section_id); - const wxColour* text_color = checked ? wxWHITE : wxGREEN; + 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_window.cpp b/area_window.cpp index ca327b8..fded223 100644 --- a/area_window.cpp +++ b/area_window.cpp @@ -4,6 +4,7 @@ #include "ap_state.h" #include "game_data.h" +#include "tracker_state.h" AreaWindow::AreaWindow(wxWindow* parent, int area_id, AreaPopup* popup) : wxWindow(parent, wxID_ANY), area_id_(area_id), popup_(popup) { @@ -35,16 +36,25 @@ void AreaWindow::Redraw() { const wxBrush* brush_color = wxGREY_BRUSH; const MapArea& map_area = GetGameData().GetMapArea(area_id_); - int unchecked_sections = 0; + bool has_reachable_unchecked = false; + bool has_unreachable_unchecked = false; for (int section_id = 0; section_id < map_area.locations.size(); section_id++) { if (!GetAPState().HasCheckedGameLocation(area_id_, section_id)) { - unchecked_sections++; + if (GetTrackerState().IsLocationReachable(area_id_, section_id)) { + has_reachable_unchecked = true; + } else { + has_unreachable_unchecked = true; + } } } - if (unchecked_sections > 0) { + 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 actual_border_size = GetSize().GetWidth() * BORDER_SIZE / EFFECTIVE_SIZE; diff --git a/game_data.h b/game_data.h index 4f93d92..ec3e94d 100644 --- a/game_data.h +++ b/game_data.h @@ -73,6 +73,16 @@ class GameData { 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 Door& GetDoor(int door_id) const { return doors_.at(door_id); } + + const Panel& GetPanel(int panel_id) const { return panels_.at(panel_id); } + private: int AddOrGetRoom(std::string room); int AddOrGetDoor(std::string room, std::string door); diff --git a/tracker_frame.cpp b/tracker_frame.cpp index cd2060c..237433a 100644 --- a/tracker_frame.cpp +++ b/tracker_frame.cpp @@ -6,6 +6,9 @@ enum TrackerFrameIds { ID_CONNECT = 1 }; +wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); +wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); + TrackerFrame::TrackerFrame() : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker") { ::wxInitAllImageHandlers(); @@ -33,17 +36,21 @@ TrackerFrame::TrackerFrame() 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) { - SetStatusText(message); + wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED); + event->SetString(message.c_str()); + + QueueEvent(event); } void TrackerFrame::UpdateIndicators() { - tracker_panel_->UpdateIndicators(); - Refresh(); + QueueEvent(new wxCommandEvent(STATE_CHANGED)); } void TrackerFrame::OnAbout(wxCommandEvent &event) { @@ -61,3 +68,12 @@ void TrackerFrame::OnConnect(wxCommandEvent &event) { 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 index 082c12c..6f4e4fc 100644 --- a/tracker_frame.h +++ b/tracker_frame.h @@ -9,6 +9,9 @@ class TrackerPanel; +wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); +wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); + class TrackerFrame : public wxFrame { public: TrackerFrame(); @@ -21,6 +24,8 @@ class TrackerFrame : public wxFrame { void OnExit(wxCommandEvent &event); void OnAbout(wxCommandEvent &event); void OnConnect(wxCommandEvent &event); + void OnStateChanged(wxCommandEvent &event); + void OnStatusChanged(wxCommandEvent &event); TrackerPanel *tracker_panel_; }; diff --git a/tracker_state.cpp b/tracker_state.cpp new file mode 100644 index 0000000..62e4612 --- /dev/null +++ b/tracker_state.cpp @@ -0,0 +1,143 @@ +#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; + } + + 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 (GetAPState().IsColorShuffle()) { + for (LingoColor color : panel_obj.colors) { + if (!GetAPState().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); + + switch (GetAPState().GetDoorShuffleMode()) { + case DoorShuffleMode::kNone: { + 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; + } + case DoorShuffleMode::kSimple: { + break; + } + case DoorShuffleMode::kComplex: { + break; + } + } +} + +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 (GetAPState().GetDoorShuffleMode() == + DoorShuffleMode::kNone) { + 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) { + new_boundary.push_back(out_edge); + } + } + } + + 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 new file mode 100644 index 0000000..879e6f2 --- /dev/null +++ b/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 */ -- cgit 1.4.1