From ad7c3e616fdc6f13812ec52675d226a19351494a Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 17 Dec 2024 15:40:19 -0500 Subject: Added getting player position from IPC --- CMakeLists.txt | 1 + src/ap_state.cpp | 43 +++++++--- src/ap_state.h | 2 + src/ipc_state.cpp | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ipc_state.h | 20 +++++ src/tracker_frame.cpp | 49 ++++++++--- src/tracker_frame.h | 5 +- src/tracker_panel.cpp | 27 ++++-- 8 files changed, 349 insertions(+), 29 deletions(-) create mode 100644 src/ipc_state.cpp create mode 100644 src/ipc_state.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4eef9d8..fd943b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable(lingo_ap_tracker "src/network_set.cpp" "src/logger.cpp" "src/godot_variant.cpp" + "src/ipc_state.cpp" "vendor/whereami/whereami.c" ) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 32ae8f0..80d96a1 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -22,6 +22,7 @@ #include #include "game_data.h" +#include "ipc_state.h" #include "logger.h" #include "tracker_frame.h" #include "tracker_state.h" @@ -43,6 +44,7 @@ struct APState { TrackerFrame* tracker_frame = nullptr; bool client_active = false; + std::string status_message = "Not connected to Archipelago."; std::mutex client_mutex; bool connected = false; @@ -113,7 +115,10 @@ struct APState { initialized = true; } - tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); + { + std::lock_guard client_guard(client_mutex); + SetStatusMessage("Connecting to Archipelago server...."); + } TrackerLog(fmt::format("Connecting to Archipelago server ({})...", server)); { @@ -169,8 +174,7 @@ struct APState { "Connected to Archipelago server. Authenticating as {} {}", player, (password.empty() ? "without password" : "with password " + password))); - tracker_frame->SetStatusMessage( - "Connected to Archipelago server. Authenticating..."); + SetStatusMessage("Connected to Archipelago server. Authenticating..."); apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, {AP_MAJOR, AP_MINOR, AP_REVISION}); @@ -187,14 +191,14 @@ struct APState { }); apclient->set_slot_disconnected_handler([this]() { - tracker_frame->SetStatusMessage( + SetStatusMessage( "Disconnected from Archipelago. Attempting to reconnect..."); TrackerLog( "Slot disconnected from Archipelago. Attempting to reconnect..."); }); apclient->set_socket_disconnected_handler([this]() { - tracker_frame->SetStatusMessage( + SetStatusMessage( "Disconnected from Archipelago. Attempting to reconnect..."); TrackerLog( "Socket disconnected from Archipelago. Attempting to reconnect..."); @@ -229,10 +233,12 @@ struct APState { apclient->set_slot_connected_handler([this, player, server, &connection_mutex]( const nlohmann::json& slot_data) { - tracker_frame->SetStatusMessage( - fmt::format("Connected to Archipelago! ({}@{})", player, server)); + SetStatusMessage( + fmt::format("Connected to Archipelago! ({}@{}).", player, server)); TrackerLog("Connected to Archipelago!"); + IPC_SetTrackerSlot(server, player); + save_name = fmt::format("zzAP_{}_{}.save", apclient->get_seed(), apclient->get_player_number()); data_storage_prefix = @@ -328,7 +334,7 @@ struct APState { has_connection_result = true; } - tracker_frame->SetStatusMessage("Disconnected from Archipelago."); + SetStatusMessage("Disconnected from Archipelago."); std::vector error_messages; error_messages.push_back("Could not connect to Archipelago."); @@ -376,7 +382,10 @@ struct APState { if (interval == 0) { DestroyClient(); - tracker_frame->SetStatusMessage("Disconnected from Archipelago."); + { + std::lock_guard client_guard(client_mutex); + SetStatusMessage("Disconnected from Archipelago."); + } TrackerLog("Timeout while connecting to Archipelago server."); wxMessageBox("Timeout while connecting to Archipelago server.", "Connection failed", wxOK | wxICON_ERROR); @@ -400,6 +409,17 @@ struct APState { } } + std::string GetStatusMessage() { + std::lock_guard client_guard(client_mutex); + return status_message; + } + + void SetStatusMessage(std::string msg) { + status_message = std::move(msg); + + tracker_frame->UpdateStatusMessage(); + } + void HandleDataStorage(const std::string& key, const nlohmann::json& value) { if (value.is_boolean()) { data_storage[key] = value.get(); @@ -527,6 +547,8 @@ void AP_Connect(std::string server, std::string player, std::string password) { GetState().Connect(server, player, password); } +std::string AP_GetStatusMessage() { return GetState().GetStatusMessage(); } + std::string AP_GetSaveName() { return GetState().save_name; } bool AP_HasCheckedGameLocation(int location_id) { @@ -590,7 +612,8 @@ bool AP_IsLocationVisible(int classification) { return false; } - if (GetState().door_shuffle_mode == kDOORS_MODE && !GetState().early_color_hallways) { + if (GetState().door_shuffle_mode == kDOORS_MODE && + !GetState().early_color_hallways) { world_state |= kLOCATION_SMALL_SPHERE_ONE; } diff --git a/src/ap_state.h b/src/ap_state.h index e06d4ff..f310ee8 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -43,6 +43,8 @@ void AP_SetTrackerFrame(TrackerFrame* tracker_frame); void AP_Connect(std::string server, std::string player, std::string password); +std::string AP_GetStatusMessage(); + std::string AP_GetSaveName(); bool AP_HasCheckedGameLocation(int location_id); diff --git a/src/ipc_state.cpp b/src/ipc_state.cpp new file mode 100644 index 0000000..18f318f --- /dev/null +++ b/src/ipc_state.cpp @@ -0,0 +1,231 @@ +#include "ipc_state.h" + +#define _WEBSOCKETPP_CPP11_STRICT_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "tracker_frame.h" + +namespace { + +constexpr const char* kIpcAddress = "ws://127.0.0.1:41253"; + +struct IPCState { + std::mutex state_mutex; + TrackerFrame* tracker_frame = nullptr; + + // Protected state + std::optional status_message; + + bool slot_matches = false; + std::string tracker_ap_server; + std::string tracker_ap_user; + std::string game_ap_server; + std::string game_ap_user; + + std::optional> player_position; + + int backoff_amount = 0; + + // Thread state + std::unique_ptr ws; + bool connected = false; + + void Start(TrackerFrame* frame) { + tracker_frame = frame; + + std::thread([this]() { Thread(); }).detach(); + } + + std::optional GetStatusMessage() { + std::lock_guard state_guard(state_mutex); + + return status_message; + } + + void SetTrackerSlot(std::string server, std::string user) { + std::lock_guard state_guard(state_mutex); + + tracker_ap_server = std::move(server); + tracker_ap_user = std::move(user); + + CheckIfSlotMatches(); + + backoff_amount = 0; + } + + bool IsConnected() { + std::lock_guard state_guard(state_mutex); + + return slot_matches; + } + + std::optional> GetPlayerPosition() { + std::lock_guard state_guard(state_mutex); + + return player_position; + } + + private: + void Thread() { + for (;;) { + player_position = std::nullopt; + + TrackerLog("Looking for game over IPC..."); + + { + std::lock_guard state_guard(state_mutex); + backoff_amount = 0; + } + + while (!TryConnect() || !connected) { + int backoff_limit; + { + std::lock_guard state_guard(state_mutex); + backoff_limit = (backoff_amount + 1) * 10; + } + + for (int i = 0; i < backoff_limit && !connected; i++) { + ws->poll(); + + // Back off + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + { + std::lock_guard state_guard(state_mutex); + backoff_amount = std::min(9, backoff_amount + 1); + + if (!connected) { + TrackerLog(fmt::format("Retrying IPC in {} second(s)...", + backoff_amount + 1)); + } + } + } + + while (connected) { + ws->poll(); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + SetStatusMessage("Disconnected from game."); + } + } + + bool TryConnect() { + try { + ws = std::make_unique( + kIpcAddress, [this]() { OnConnect(); }, [this]() { OnClose(); }, + [this](const std::string& s) { OnMessage(s); }, + [this](const std::string& s) { OnError(s); }); + return true; + } catch (const std::exception& ex) { + ws.reset(); + return false; + } + } + + void OnConnect() { connected = true; } + + void OnClose() { + connected = false; + + { + std::lock_guard state_guard(state_mutex); + + slot_matches = false; + } + } + + void OnMessage(const std::string& s) { + TrackerLog(s); + + auto msg = nlohmann::json::parse(s); + + if (msg["cmd"] == "Connect") { + std::lock_guard state_guard(state_mutex); + + game_ap_server = msg["slot"]["server"]; + game_ap_user = msg["slot"]["player"]; + + CheckIfSlotMatches(); + } else if (msg["cmd"] == "UpdatePosition") { + std::lock_guard state_guard(state_mutex); + + player_position = + std::make_tuple(msg["position"]["x"], msg["position"]["z"]); + + tracker_frame->RedrawPosition(); + } + } + + void OnError(const std::string& s) {} + + void CheckIfSlotMatches() { + slot_matches = (tracker_ap_server == game_ap_server && + tracker_ap_user == game_ap_user); + + if (slot_matches) { + status_message = "Connected to game."; + + Sync(); + } else if (tracker_ap_server.empty()) { + status_message = std::nullopt; + } else if (connected) { + status_message = "Local game doesn't match AP slot."; + } + + tracker_frame->UpdateStatusMessage(); + } + + void SetStatusMessage(std::optional msg) { + { + std::lock_guard state_guard(state_mutex); + status_message = msg; + } + + tracker_frame->UpdateStatusMessage(); + } + + void Sync() { + nlohmann::json msg; + msg["cmd"] = "Sync"; + + ws->send_text(msg.dump()); + } +}; + +IPCState& GetState() { + static IPCState* instance = new IPCState(); + return *instance; +} + +} // namespace + +void IPC_Start(TrackerFrame* tracker_frame) { GetState().Start(tracker_frame); } + +std::optional IPC_GetStatusMessage() { + return GetState().GetStatusMessage(); +} + +void IPC_SetTrackerSlot(std::string server, std::string user) { + GetState().SetTrackerSlot(server, user); +} + +bool IPC_IsConnected() { return GetState().IsConnected(); } + +std::optional> IPC_GetPlayerPosition() { + return GetState().GetPlayerPosition(); +} diff --git a/src/ipc_state.h b/src/ipc_state.h new file mode 100644 index 0000000..be71673 --- /dev/null +++ b/src/ipc_state.h @@ -0,0 +1,20 @@ +#ifndef IPC_STATE_H_6B3B0958 +#define IPC_STATE_H_6B3B0958 + +#include +#include +#include + +class TrackerFrame; + +void IPC_Start(TrackerFrame* tracker_frame); + +std::optional IPC_GetStatusMessage(); + +void IPC_SetTrackerSlot(std::string server, std::string user); + +bool IPC_IsConnected(); + +std::optional> IPC_GetPlayerPosition(); + +#endif /* end of include guard: IPC_STATE_H_6B3B0958 */ diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index b9282f5..a06e46b 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -14,12 +14,29 @@ #include "achievements_pane.h" #include "ap_state.h" #include "connection_dialog.h" +#include "ipc_state.h" #include "settings_dialog.h" #include "subway_map.h" #include "tracker_config.h" #include "tracker_panel.h" #include "version.h" +namespace { + +std::string GetStatusMessage() { + std::string msg = AP_GetStatusMessage(); + + std::optional ipc_msg = IPC_GetStatusMessage(); + if (ipc_msg) { + msg += " "; + msg += *ipc_msg; + } + + return msg; +} + +} // namespace + enum TrackerFrameIds { ID_CONNECT = 1, ID_CHECK_FOR_UPDATES = 2, @@ -32,6 +49,7 @@ enum TrackerFrameIds { wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); +wxDEFINE_EVENT(REDRAW_POSITION, wxCommandEvent); TrackerFrame::TrackerFrame() : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition, @@ -65,7 +83,6 @@ TrackerFrame::TrackerFrame() SetMenuBar(menuBar); CreateStatusBar(); - SetStatusText("Not connected to Archipelago."); Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); @@ -80,6 +97,7 @@ TrackerFrame::TrackerFrame() Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); + Bind(REDRAW_POSITION, &TrackerFrame::OnRedrawPosition, this); wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); achievements_pane_ = new AchievementsPane(choicebook); @@ -115,13 +133,14 @@ TrackerFrame::TrackerFrame() if (GetTrackerConfig().should_check_for_updates) { CheckForUpdates(/*manual=*/false); } -} -void TrackerFrame::SetStatusMessage(std::string message) { - wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED); - event->SetString(message.c_str()); + IPC_Start(this); - QueueEvent(event); + SetStatusText(GetStatusMessage()); +} + +void TrackerFrame::UpdateStatusMessage() { + QueueEvent(new wxCommandEvent(STATUS_CHANGED)); } void TrackerFrame::ResetIndicators() { @@ -132,6 +151,10 @@ void TrackerFrame::UpdateIndicators() { QueueEvent(new wxCommandEvent(STATE_CHANGED)); } +void TrackerFrame::RedrawPosition() { + QueueEvent(new wxCommandEvent(REDRAW_POSITION)); +} + void TrackerFrame::OnAbout(wxCommandEvent &event) { wxAboutDialogInfo about_info; about_info.SetName("Lingo Archipelago Tracker"); @@ -200,7 +223,7 @@ void TrackerFrame::OnZoomIn(wxCommandEvent &event) { } } -void TrackerFrame::OnZoomOut(wxCommandEvent& event) { +void TrackerFrame::OnZoomOut(wxCommandEvent &event) { if (notebook_->GetSelection() == 1) { subway_map_->Zoom(false); } @@ -211,7 +234,7 @@ void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { zoom_out_menu_item_->Enable(event.GetSelection() == 1); } -void TrackerFrame::OnOpenFile(wxCommandEvent& event) { +void TrackerFrame::OnOpenFile(wxCommandEvent &event) { wxFileDialog open_file_dialog( this, "Open Lingo Save File", fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable", @@ -233,7 +256,7 @@ void TrackerFrame::OnOpenFile(wxCommandEvent& event) { panels_panel_->SetSavedataPath(savedata_path); } -void TrackerFrame::OnStateReset(wxCommandEvent& event) { +void TrackerFrame::OnStateReset(wxCommandEvent &event) { tracker_panel_->UpdateIndicators(); achievements_pane_->UpdateIndicators(); subway_map_->OnConnect(); @@ -255,7 +278,13 @@ void TrackerFrame::OnStateChanged(wxCommandEvent &event) { } void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { - SetStatusText(event.GetString()); + SetStatusText(GetStatusMessage()); +} + +void TrackerFrame::OnRedrawPosition(wxCommandEvent &event) { + if (notebook_->GetSelection() == 0) { + tracker_panel_->Refresh(); + } } void TrackerFrame::CheckForUpdates(bool manual) { diff --git a/src/tracker_frame.h b/src/tracker_frame.h index 19bd0b3..ab59ffb 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h @@ -16,15 +16,17 @@ class wxNotebook; wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); +wxDECLARE_EVENT(REDRAW_POSITION, wxCommandEvent); class TrackerFrame : public wxFrame { public: TrackerFrame(); - void SetStatusMessage(std::string message); + void UpdateStatusMessage(); void ResetIndicators(); void UpdateIndicators(); + void RedrawPosition(); private: void OnExit(wxCommandEvent &event); @@ -40,6 +42,7 @@ class TrackerFrame : public wxFrame { void OnStateReset(wxCommandEvent &event); void OnStateChanged(wxCommandEvent &event); void OnStatusChanged(wxCommandEvent &event); + void OnRedrawPosition(wxCommandEvent &event); void CheckForUpdates(bool manual); diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 27e825a..2f2d596 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -10,6 +10,7 @@ #include "game_data.h" #include "global.h" #include "godot_variant.h" +#include "ipc_state.h" #include "tracker_config.h" #include "tracker_state.h" @@ -59,7 +60,8 @@ void TrackerPanel::UpdateIndicators() { void TrackerPanel::SetSavedataPath(std::string savedata_path) { if (!panels_mode_) { - wxButton *refresh_button = new wxButton(this, wxID_ANY, "Refresh", {15, 15}); + wxButton *refresh_button = + new wxButton(this, wxID_ANY, "Refresh", {15, 15}); refresh_button->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this); } @@ -94,16 +96,25 @@ void TrackerPanel::OnPaint(wxPaintEvent &event) { wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); - if (AP_GetPlayerPosition().has_value()) { + std::optional> player_position; + if (IPC_IsConnected()) { + player_position = IPC_GetPlayerPosition(); + } else { + player_position = AP_GetPlayerPosition(); + } + + if (player_position.has_value()) { // 1588, 1194 // 14x14 -> 154x154 double intended_x = - 1588.0 + (std::get<0>(*AP_GetPlayerPosition()) * (154.0 / 14.0)); + 1588.0 + (std::get<0>(*player_position) * (154.0 / 14.0)); double intended_y = - 1194.0 + (std::get<1>(*AP_GetPlayerPosition()) * (154.0 / 14.0)); + 1194.0 + (std::get<1>(*player_position) * (154.0 / 14.0)); - int real_x = offset_x_ + scale_x_ * intended_x - scaled_player_.GetWidth() / 2; - int real_y = offset_y_ + scale_y_ * intended_y - scaled_player_.GetHeight() / 2; + int real_x = + offset_x_ + scale_x_ * intended_x - scaled_player_.GetWidth() / 2; + int real_y = + offset_y_ + scale_y_ * intended_y - scaled_player_.GetHeight() / 2; dc.DrawBitmap(scaled_player_, real_x, real_y); } @@ -182,8 +193,8 @@ void TrackerPanel::Redraw() { if (panels_mode_) { area.active = map_area.has_single_panel; } else if (!AP_IsLocationVisible(map_area.classification) && - !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && - !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { + !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && + !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { area.active = false; } else { area.active = true; -- cgit 1.4.1