From ea16cff14ff4faf5782da8ff684a6ec412b7b6ac Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 12 May 2024 17:48:02 -0400 Subject: Started making subway map --- src/game_data.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) (limited to 'src/game_data.cpp') diff --git a/src/game_data.cpp b/src/game_data.cpp index c98f532..4348967 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -44,6 +44,7 @@ struct GameData { std::vector doors_; std::vector panels_; std::vector map_areas_; + std::vector subway_items_; std::map room_by_id_; std::map door_by_id_; @@ -606,6 +607,56 @@ struct GameData { errstr << "Area data not found for: " << area; TrackerLog(errstr.str()); } + + // Read in subway items. + YAML::Node subway_config = + YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); + for (const auto &subway_it : subway_config) { + SubwayItem subway_item; + subway_item.id = subway_items_.size(); + subway_item.x = subway_it["pos"][0].as(); + subway_item.y = subway_it["pos"][1].as(); + + if (subway_it["door"]) { + subway_item.door = AddOrGetDoor(subway_it["room"].as(), + subway_it["door"].as()); + } + + if (subway_it["paintings"]) { + for (const auto &painting_it : subway_it["paintings"]) { + subway_item.paintings.push_back(painting_it.as()); + } + } + + if (subway_it["tags"]) { + for (const auto &tag_it : subway_it["tags"]) { + subway_item.tags.push_back(tag_it.as()); + } + } + + if (subway_it["sunwarp"]) { + SubwaySunwarp sunwarp; + sunwarp.dots = subway_it["sunwarp"]["dots"].as(); + + std::string sunwarp_type = + subway_it["sunwarp"]["type"].as(); + if (sunwarp_type == "final") { + sunwarp.type = SubwaySunwarpType::kFinal; + } else if (sunwarp_type == "exit") { + sunwarp.type = SubwaySunwarpType::kExit; + } else { + sunwarp.type = SubwaySunwarpType::kEnter; + } + + subway_item.sunwarp = sunwarp; + } + + if (subway_it["special"]) { + subway_item.special = subway_it["special"].as(); + } + + subway_items_.push_back(subway_item); + } } int AddOrGetRoom(std::string room) { @@ -621,8 +672,9 @@ struct GameData { std::string full_name = room + " - " + door; if (!door_by_id_.count(full_name)) { + int door_id = doors_.size(); door_by_id_[full_name] = doors_.size(); - doors_.push_back({.room = AddOrGetRoom(room), .name = door}); + doors_.push_back({.id = door_id, .room = AddOrGetRoom(room), .name = door}); } return door_by_id_[full_name]; @@ -704,3 +756,11 @@ const std::vector &GD_GetSunwarpDoors() { int GD_GetRoomForSunwarp(int index) { return GetState().room_by_sunwarp_.at(index); } + +const std::vector &GD_GetSubwayItems() { + return GetState().subway_items_; +} + +const SubwayItem &GD_GetSubwayItem(int id) { + return GetState().subway_items_.at(id); +} -- cgit 1.4.1 From 7f4b6b4f0cb276a7e0868c7e97d862b1feb468d3 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 11:41:50 -0400 Subject: Hovered connections on subway map! --- CMakeLists.txt | 1 + src/ap_state.cpp | 21 ++++---- src/game_data.cpp | 25 +++++++++- src/game_data.h | 4 ++ src/network_set.cpp | 68 ++++++++++++++++++++++++++ src/network_set.h | 28 +++++++++++ src/subway_map.cpp | 129 +++++++++++++++++++++++++++++++++++++++++++++++--- src/subway_map.h | 12 +++++ src/tracker_frame.cpp | 13 +++++ src/tracker_frame.h | 3 ++ 10 files changed, 289 insertions(+), 15 deletions(-) create mode 100644 src/network_set.cpp create mode 100644 src/network_set.h (limited to 'src/game_data.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index d6170a1..6dc2f4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable(lingo_ap_tracker "src/settings_dialog.cpp" "src/global.cpp" "src/subway_map.cpp" + "src/network_set.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 8feb78b..b057beb 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -173,7 +173,7 @@ struct APState { TrackerLog("Location: " + std::to_string(location_id)); } - RefreshTracker(); + RefreshTracker(false); }); apclient->set_slot_disconnected_handler([this]() { @@ -197,7 +197,7 @@ struct APState { TrackerLog("Item: " + std::to_string(item.item)); } - RefreshTracker(); + RefreshTracker(false); }); apclient->set_retrieved_handler( @@ -206,14 +206,14 @@ struct APState { HandleDataStorage(key, value); } - RefreshTracker(); + RefreshTracker(false); }); apclient->set_set_reply_handler([this](const std::string& key, const nlohmann::json& value, const nlohmann::json&) { HandleDataStorage(key, value); - RefreshTracker(); + RefreshTracker(false); }); apclient->set_slot_connected_handler([this]( @@ -271,7 +271,7 @@ struct APState { connected = true; has_connection_result = true; - RefreshTracker(); + RefreshTracker(true); std::list corrected_keys; for (const std::string& key : tracked_data_storage_keys) { @@ -353,7 +353,7 @@ struct APState { } if (connected) { - RefreshTracker(); + RefreshTracker(false); } else { client_active = false; } @@ -407,11 +407,16 @@ struct APState { return data_storage.count(key) && std::any_cast(data_storage.at(key)); } - void RefreshTracker() { + void RefreshTracker(bool reset) { TrackerLog("Refreshing display..."); RecalculateReachability(); - tracker_frame->UpdateIndicators(); + + if (reset) { + tracker_frame->ResetIndicators(); + } else { + tracker_frame->UpdateIndicators(); + } } int64_t GetItemId(const std::string& item_name) { diff --git a/src/game_data.cpp b/src/game_data.cpp index 4348967..74f872c 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -62,6 +62,9 @@ struct GameData { std::vector sunwarp_doors_; + std::map subway_item_by_painting_; + std::map subway_item_by_sunwarp_; + bool loaded_area_data_ = false; std::set malconfigured_areas_; @@ -624,7 +627,10 @@ struct GameData { if (subway_it["paintings"]) { for (const auto &painting_it : subway_it["paintings"]) { - subway_item.paintings.push_back(painting_it.as()); + std::string painting_id = painting_it.as(); + + subway_item.paintings.push_back(painting_id); + subway_item_by_painting_[painting_id] = subway_item.id; } } @@ -649,6 +655,11 @@ struct GameData { } subway_item.sunwarp = sunwarp; + + subway_item_by_sunwarp_[sunwarp] = subway_item.id; + + subway_item.door = + AddOrGetDoor("Sunwarps", std::to_string(sunwarp.dots) + " Sunwarp"); } if (subway_it["special"]) { @@ -715,6 +726,10 @@ GameData &GetState() { } // namespace +bool SubwaySunwarp::operator<(const SubwaySunwarp& rhs) const { + return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); +} + const std::vector &GD_GetMapAreas() { return GetState().map_areas_; } const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } @@ -764,3 +779,11 @@ const std::vector &GD_GetSubwayItems() { const SubwayItem &GD_GetSubwayItem(int id) { return GetState().subway_items_.at(id); } + +int GD_GetSubwayItemForPainting(const std::string& painting_id) { + return GetState().subway_item_by_painting_.at(painting_id); +} + +int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { + return GetState().subway_item_by_sunwarp_.at(sunwarp); +} diff --git a/src/game_data.h b/src/game_data.h index 37d1eb3..3afaec3 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -128,6 +128,8 @@ enum class SubwaySunwarpType { struct SubwaySunwarp { int dots; SubwaySunwarpType type; + + bool operator<(const SubwaySunwarp& rhs) const; }; struct SubwayItem { @@ -156,5 +158,7 @@ const std::vector& GD_GetSunwarpDoors(); int GD_GetRoomForSunwarp(int index); const std::vector& GD_GetSubwayItems(); const SubwayItem& GD_GetSubwayItem(int id); +int GD_GetSubwayItemForPainting(const std::string& painting_id); +int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/src/network_set.cpp b/src/network_set.cpp new file mode 100644 index 0000000..3238dcd --- /dev/null +++ b/src/network_set.cpp @@ -0,0 +1,68 @@ +#include "network_set.h" + +void NetworkSet::Clear() { + networks_.clear(); + network_by_item_.clear(); +} + +int NetworkSet::AddLink(int id1, int id2) { + if (id2 > id1) { + // Make sure id1 < id2 + std::swap(id1, id2); + } + + if (network_by_item_.count(id1)) { + if (network_by_item_.count(id2)) { + int network_id1 = network_by_item_[id1]; + int network_id2 = network_by_item_[id2]; + + networks_[network_id1].emplace(id1, id2); + + if (network_id1 != network_id2) { + for (const auto& [other_id1, other_id2] : networks_[network_id2]) { + network_by_item_[other_id1] = network_id1; + network_by_item_[other_id2] = network_id1; + } + + networks_[network_id1].merge(networks_[network_id2]); + networks_[network_id2].clear(); + } + + return network_id1; + } else { + int network_id = network_by_item_[id1]; + network_by_item_[id2] = network_id; + networks_[network_id].emplace(id1, id2); + + return network_id; + } + } else { + if (network_by_item_.count(id2)) { + int network_id = network_by_item_[id2]; + network_by_item_[id1] = network_id; + networks_[network_id].emplace(id1, id2); + + return network_id; + } else { + int network_id = networks_.size(); + network_by_item_[id1] = network_id; + network_by_item_[id2] = network_id; + networks_.emplace_back(); + networks_[network_id] = {{id1, id2}}; + + return network_id; + } + } +} + +bool NetworkSet::IsItemInNetwork(int id) const { + return network_by_item_.count(id); +} + +int NetworkSet::GetNetworkWithItem(int id) const { + return network_by_item_.at(id); +} + +const std::set>& NetworkSet::GetNetworkGraph(int id) const { + return networks_.at(id); +} diff --git a/src/network_set.h b/src/network_set.h new file mode 100644 index 0000000..e0ef043 --- /dev/null +++ b/src/network_set.h @@ -0,0 +1,28 @@ +#ifndef NETWORK_SET_H_3036B8E3 +#define NETWORK_SET_H_3036B8E3 + +#include +#include +#include +#include +#include + +class NetworkSet { + public: + void Clear(); + + int AddLink(int id1, int id2); + + bool IsItemInNetwork(int id) const; + + int GetNetworkWithItem(int id) const; + + const std::set>& GetNetworkGraph(int id) const; + + private: + + std::vector>> networks_; + std::map network_by_item_; +}; + +#endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ diff --git a/src/subway_map.cpp b/src/subway_map.cpp index f857270..a03f0d8 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -2,6 +2,9 @@ #include +#include + +#include "ap_state.h" #include "game_data.h" #include "global.h" #include "tracker_state.h" @@ -42,10 +45,61 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); } +void SubwayMap::OnConnect() { + networks_.Clear(); + + std::map> tagged; + for (const SubwayItem &subway_item : GD_GetSubwayItems()) { + if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { + continue; + } + + for (const std::string &tag : subway_item.tags) { + tagged[tag].push_back(subway_item.id); + } + + if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { + std::ostringstream tag; + tag << "sunwarp" << subway_item.sunwarp->dots; + + tagged[tag.str()].push_back(subway_item.id); + } + } + + for (const auto &[tag, items] : tagged) { + // Pairwise connect all items with the same tag. + for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end(); + tag_it1++) { + for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); + tag_it2++) { + networks_.AddLink(*tag_it1, *tag_it2); + } + } + } + + checked_paintings_.clear(); +} + void SubwayMap::UpdateIndicators() { Redraw(); } +void SubwayMap::UpdatePainting(std::string from_painting_id, + std::optional to_painting_id) { + checked_paintings_.insert(from_painting_id); + + if (to_painting_id) { + networks_.AddLink(GD_GetSubwayItemForPainting(from_painting_id), + GD_GetSubwayItemForPainting(*to_painting_id)); + } +} + +void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, + SubwaySunwarp to_sunwarp) { + networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), + GD_GetSubwayItemForSunwarp(to_sunwarp)); +} + void SubwayMap::OnPaint(wxPaintEvent &event) { if (GetSize() != rendered_.GetSize()) { Redraw(); @@ -54,6 +108,73 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); + if (hovered_item_ && networks_.IsItemInNetwork(*hovered_item_)) { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + + int network_id = networks_.GetNetworkWithItem(*hovered_item_); + for (const auto &[item_id1, item_id2] : + networks_.GetNetworkGraph(network_id)) { + const SubwayItem &item1 = GD_GetSubwayItem(item_id1); + const SubwayItem &item2 = GD_GetSubwayItem(item_id2); + + int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; + int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; + + int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; + int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; + + int left = std::min(item1_x, item2_x); + int top = std::min(item1_y, item2_y); + int right = std::max(item1_x, item2_x); + int bottom = std::max(item1_y, item2_y); + + int halfwidth = right - left; + int halfheight = bottom - top; + + int ellipse_x; + int ellipse_y; + double start; + double end; + + if (item1_x > item2_x) { + ellipse_y = top; + + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; + + start = 0; + end = 90; + } else { + ellipse_x = left; + + start = 90; + end = 180; + } + } else { + ellipse_y = top - halfheight; + + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; + + start = 270; + end = 360; + } else { + ellipse_x = left; + + start = 180; + end = 270; + } + } + + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, + start, end); + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, + start, end); + } + } + event.Skip(); } @@ -73,13 +194,9 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { } if (new_hovered_item != hovered_item_) { - if (new_hovered_item) { - wxLogVerbose("Hovered: %d", *new_hovered_item); - } else { - wxLogVerbose("Un-hovered: %d", *hovered_item_); - } - hovered_item_ = new_hovered_item; + + Refresh(); } event.Skip(); diff --git a/src/subway_map.h b/src/subway_map.h index 6cb5c63..1637125 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -9,15 +9,24 @@ #include #include +#include +#include #include #include +#include "game_data.h" +#include "network_set.h" + class SubwayMap : public wxPanel { public: SubwayMap(wxWindow *parent); + void OnConnect(); void UpdateIndicators(); + void UpdatePainting(std::string from_painting_id, + std::optional to_painting_id); + void UpdateSunwarp(SubwaySunwarp from_sunwarp, SubwaySunwarp to_sunwarp); private: void OnPaint(wxPaintEvent &event); @@ -40,6 +49,9 @@ class SubwayMap : public wxPanel { std::unique_ptr> tree_; std::optional hovered_item_; + + NetworkSet networks_; + std::set checked_paintings_; }; #endif /* end of include guard: SUBWAY_MAP_H_BD2D843E */ diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 70fee2d..e944704 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -22,6 +22,7 @@ enum TrackerFrameIds { ID_SETTINGS = 3 }; +wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); @@ -56,6 +57,7 @@ TrackerFrame::TrackerFrame() Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS); Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, ID_CHECK_FOR_UPDATES); + Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); @@ -103,6 +105,10 @@ void TrackerFrame::SetStatusMessage(std::string message) { QueueEvent(event); } +void TrackerFrame::ResetIndicators() { + QueueEvent(new wxCommandEvent(STATE_RESET)); +} + void TrackerFrame::UpdateIndicators() { QueueEvent(new wxCommandEvent(STATE_CHANGED)); } @@ -168,6 +174,13 @@ void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { CheckForUpdates(/*manual=*/true); } +void TrackerFrame::OnStateReset(wxCommandEvent& event) { + tracker_panel_->UpdateIndicators(); + achievements_pane_->UpdateIndicators(); + subway_map_->OnConnect(); + Refresh(); +} + void TrackerFrame::OnStateChanged(wxCommandEvent &event) { tracker_panel_->UpdateIndicators(); achievements_pane_->UpdateIndicators(); diff --git a/src/tracker_frame.h b/src/tracker_frame.h index c7c6772..f1d7171 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h @@ -11,6 +11,7 @@ class AchievementsPane; class SubwayMap; class TrackerPanel; +wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); @@ -20,6 +21,7 @@ class TrackerFrame : public wxFrame { void SetStatusMessage(std::string message); + void ResetIndicators(); void UpdateIndicators(); private: @@ -29,6 +31,7 @@ class TrackerFrame : public wxFrame { void OnSettings(wxCommandEvent &event); void OnCheckForUpdates(wxCommandEvent &event); + void OnStateReset(wxCommandEvent &event); void OnStateChanged(wxCommandEvent &event); void OnStatusChanged(wxCommandEvent &event); -- cgit 1.4.1 From bee4194f9e12c9d2210a5ecba7249bdfe3f3deda Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 12:53:59 -0400 Subject: Switch to wx logging --- src/ap_state.cpp | 43 ++++++++++++++++++++-------------------- src/game_data.cpp | 59 ++++++++++++++++++++----------------------------------- src/logger.cpp | 39 ++++++++++++++++-------------------- src/logger.h | 25 +++++++++++++++++++++-- src/main.cpp | 3 +++ 5 files changed, 85 insertions(+), 84 deletions(-) (limited to 'src/game_data.cpp') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index b057beb..e5ff74d 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -75,7 +75,7 @@ struct APState { void Connect(std::string server, std::string player, std::string password) { if (!initialized) { - TrackerLog("Initializing APState..."); + wxLogMessage("Initializing APState..."); std::thread([this]() { for (;;) { @@ -108,10 +108,10 @@ struct APState { } tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); - TrackerLog("Connecting to Archipelago server (" + server + ")..."); + wxLogMessage("Connecting to Archipelago server (%s)...", server); { - TrackerLog("Destroying old AP client..."); + wxLogMessage("Destroying old AP client..."); std::lock_guard client_guard(client_mutex); @@ -155,10 +155,10 @@ struct APState { apclient->set_room_info_handler([this, player, password]() { inventory.clear(); - TrackerLog("Connected to Archipelago server. Authenticating as " + - player + - (password.empty() ? " without password" - : " with password " + password)); + wxLogMessage("Connected to Archipelago server. Authenticating as %s %s", + player, + (password.empty() ? " without password" + : " with password " + password)); tracker_frame->SetStatusMessage( "Connected to Archipelago server. Authenticating..."); @@ -170,7 +170,7 @@ struct APState { [this](const std::list& locations) { for (const int64_t location_id : locations) { checked_locations.insert(location_id); - TrackerLog("Location: " + std::to_string(location_id)); + wxLogMessage("Location: %lld", location_id); } RefreshTracker(false); @@ -179,14 +179,14 @@ struct APState { apclient->set_slot_disconnected_handler([this]() { tracker_frame->SetStatusMessage( "Disconnected from Archipelago. Attempting to reconnect..."); - TrackerLog( + wxLogMessage( "Slot disconnected from Archipelago. Attempting to reconnect..."); }); apclient->set_socket_disconnected_handler([this]() { tracker_frame->SetStatusMessage( "Disconnected from Archipelago. Attempting to reconnect..."); - TrackerLog( + wxLogMessage( "Socket disconnected from Archipelago. Attempting to reconnect..."); }); @@ -194,7 +194,7 @@ struct APState { [this](const std::list& items) { for (const APClient::NetworkItem& item : items) { inventory[item.item]++; - TrackerLog("Item: " + std::to_string(item.item)); + wxLogMessage("Item: %lld", item.item); } RefreshTracker(false); @@ -219,7 +219,7 @@ struct APState { apclient->set_slot_connected_handler([this]( const nlohmann::json& slot_data) { tracker_frame->SetStatusMessage("Connected to Archipelago!"); - TrackerLog("Connected to Archipelago!"); + wxLogMessage("Connected to Archipelago!"); data_storage_prefix = "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; @@ -323,7 +323,7 @@ struct APState { } std::string full_message = hatkirby::implode(error_messages, " "); - TrackerLog(full_message); + wxLogError(wxString(full_message)); wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); }); @@ -342,7 +342,7 @@ struct APState { tracker_frame->SetStatusMessage("Disconnected from Archipelago."); - TrackerLog("Timeout while connecting to Archipelago server."); + wxLogMessage("Timeout while connecting to Archipelago server."); wxMessageBox("Timeout while connecting to Archipelago server.", "Connection failed", wxOK | wxICON_ERROR); } @@ -362,12 +362,11 @@ struct APState { void HandleDataStorage(const std::string& key, const nlohmann::json& value) { if (value.is_boolean()) { data_storage[key] = value.get(); - TrackerLog("Data storage " + key + " retrieved as " + - (value.get() ? "true" : "false")); + wxLogMessage("Data storage %s retrieved as %s", key, + (value.get() ? "true" : "false")); } else if (value.is_number()) { data_storage[key] = value.get(); - TrackerLog("Data storage " + key + " retrieved as " + - std::to_string(value.get())); + wxLogMessage("Data storage %s retrieved as %d", key, value.get()); } else if (value.is_object()) { if (key.ends_with("PlayerPos")) { auto map_value = value.get>(); @@ -376,7 +375,7 @@ struct APState { data_storage[key] = value.get>(); } - TrackerLog("Data storage " + key + " retrieved as dictionary"); + wxLogMessage("Data storage %s retrieved as dictionary", key); } else if (value.is_null()) { if (key.ends_with("PlayerPos")) { player_pos = std::nullopt; @@ -384,7 +383,7 @@ struct APState { data_storage.erase(key); } - TrackerLog("Data storage " + key + " retrieved as null"); + wxLogMessage("Data storage %s retrieved as null", key); } } @@ -408,7 +407,7 @@ struct APState { } void RefreshTracker(bool reset) { - TrackerLog("Refreshing display..."); + wxLogMessage("Refreshing display..."); RecalculateReachability(); @@ -422,7 +421,7 @@ struct APState { 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) { - TrackerLog("Could not find AP item ID for " + item_name); + wxLogError("Could not find AP item ID for %s", item_name); } return ap_id; diff --git a/src/game_data.cpp b/src/game_data.cpp index 74f872c..7bc3134 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -31,9 +31,7 @@ LingoColor GetColorForString(const std::string &str) { } else if (str == "purple") { return LingoColor::kPurple; } else { - std::ostringstream errmsg; - errmsg << "Invalid color: " << str; - TrackerLog(errmsg.str()); + wxLogError("Invalid color: %s", str); return LingoColor::kNone; } @@ -83,9 +81,7 @@ struct GameData { ap_id_by_color_[GetColorForString(input_name)] = ids_config["special_items"][color_name].as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for color " << color_name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for color %s", color_name); } }; @@ -160,8 +156,9 @@ struct GameData { } default: { // This shouldn't happen. - std::cout << "Error reading game data: " << entrance_it - << std::endl; + std::ostringstream formatted; + formatted << entrance_it; + wxLogError("Error reading game data: %s", formatted.str()); break; } } @@ -281,10 +278,8 @@ struct GameData { [panels_[panel_id].name] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP location ID for panel " - << rooms_[room_id].name << " - " << panels_[panel_id].name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP location ID for panel %s - %s", + rooms_[room_id].name, panels_[panel_id].name); } } } @@ -347,10 +342,8 @@ struct GameData { [doors_[door_id].name]["item"] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for door " << rooms_[room_id].name - << " - " << doors_[door_id].name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for door %s - %s", + rooms_[room_id].name, doors_[door_id].name); } } @@ -364,10 +357,8 @@ struct GameData { ids_config["door_groups"][doors_[door_id].group_name] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for door group " - << doors_[door_id].group_name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for door group %s", + doors_[door_id].group_name); } } @@ -377,13 +368,11 @@ struct GameData { } else if (!door_it.second["skip_location"] && !door_it.second["event"]) { if (has_external_panels) { - std::ostringstream errmsg; - errmsg - << rooms_[room_id].name << " - " << doors_[door_id].name - << " has panels from other rooms but does not have an " - "explicit " - "location name and is not marked skip_location or event"; - TrackerLog(errmsg.str()); + wxLogError( + "%s - %s has panels from other rooms but does not have an " + "explicit location name and is not marked skip_location or " + "event", + rooms_[room_id].name, doors_[door_id].name); } doors_[door_id].location_name = @@ -403,10 +392,8 @@ struct GameData { [doors_[door_id].name]["location"] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP location ID for door " - << rooms_[room_id].name << " - " << doors_[door_id].name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP location ID for door %s - %s", + rooms_[room_id].name, doors_[door_id].name); } } @@ -472,10 +459,8 @@ struct GameData { progressive_item_id = ids_config["progression"][progressive_item_name].as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for progressive item " - << progressive_item_name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for progressive item %s", + progressive_item_name); } int index = 1; @@ -606,9 +591,7 @@ struct GameData { // Report errors. for (const std::string &area : malconfigured_areas_) { - std::ostringstream errstr; - errstr << "Area data not found for: " << area; - TrackerLog(errstr.str()); + wxLogError("Area data not found for: %s", area); } // Read in subway items. diff --git a/src/logger.cpp b/src/logger.cpp index 4b722c8..dddcc4a 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,32 +1,27 @@ #include "logger.h" -#include -#include -#include - #include "global.h" -namespace { +Logger::Logger() : logfile_(GetAbsolutePath("debug.log")) {} -class Logger { - public: - Logger() : logfile_(GetAbsolutePath("debug.log")) {} +void Logger::Flush() { + wxLog::Flush(); - void LogLine(const std::string& text) { - std::lock_guard guard(file_mutex_); - logfile_ << "[" << std::chrono::system_clock::now() << "] " << text - << std::endl; - logfile_.flush(); - } + std::lock_guard guard(file_mutex_); + logfile_.flush(); +} - private: - std::ofstream logfile_; - std::mutex file_mutex_; -}; +Logger::~Logger() { + std::lock_guard guard(file_mutex_); + logfile_.flush(); +} -} // namespace +void Logger::DoLogText(const wxString& msg) { +#ifdef _WIN64 + OutputDebugStringA(msg.c_str()); + OutputDebugStringA("\r\n"); +#endif -void TrackerLog(const std::string& text) { - static Logger* instance = new Logger(); - instance->LogLine(text); + std::lock_guard guard(file_mutex_); + logfile_ << msg << std::endl; } diff --git a/src/logger.h b/src/logger.h index db9bb49..b1a1d99 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,8 +1,29 @@ #ifndef LOGGER_H_6E7B9594 #define LOGGER_H_6E7B9594 -#include +#include -void TrackerLog(const std::string& text); +#ifndef WX_PRECOMP +#include +#endif + +#include +#include + +class Logger : public wxLog { + public: + Logger(); + + void Flush() override; + + ~Logger(); + + protected: + void DoLogText(const wxString& msg) override; + + private: + std::ofstream logfile_; + std::mutex file_mutex_; +}; #endif /* end of include guard: LOGGER_H_6E7B9594 */ diff --git a/src/main.cpp b/src/main.cpp index fe9aceb..db7653c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,12 +4,15 @@ #include #endif +#include "logger.h" #include "tracker_config.h" #include "tracker_frame.h" class TrackerApp : public wxApp { public: virtual bool OnInit() { + wxLog::SetActiveTarget(new Logger()); + GetTrackerConfig().Load(); TrackerFrame *frame = new TrackerFrame(); -- cgit 1.4.1 From c1de3fe969a686dbe1d17bdd3dfe7e9d4251f17b Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 13:02:13 -0400 Subject: Warn on singleton subway tag data --- src/game_data.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/game_data.cpp') diff --git a/src/game_data.cpp b/src/game_data.cpp index 7bc3134..8af57e5 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -651,6 +651,20 @@ struct GameData { subway_items_.push_back(subway_item); } + + // Find singleton subway tags. + std::map> subway_tags; + for (const SubwayItem &subway_item : subway_items_) { + for (const std::string &tag : subway_item.tags) { + subway_tags[tag].insert(subway_item.id); + } + } + + for (const auto &[tag, items] : subway_tags) { + if (items.size() == 1) { + wxLogWarning("Singleton subway item tag: %s", tag); + } + } } int AddOrGetRoom(std::string room) { -- cgit 1.4.1 From 0c7cb22f3da56fde00f981f9fefb971c541bc8f1 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 13:19:34 -0400 Subject: Logging changes --- CMakeLists.txt | 1 - src/ap_state.cpp | 56 +++++++++++++++++++++++++------------------------------ src/game_data.cpp | 7 ++++++- src/logger.cpp | 27 --------------------------- src/logger.h | 29 ---------------------------- src/main.cpp | 14 ++++++++++++-- 6 files changed, 43 insertions(+), 91 deletions(-) delete mode 100644 src/logger.cpp delete mode 100644 src/logger.h (limited to 'src/game_data.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dc2f4c..cd62c55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,6 @@ add_executable(lingo_ap_tracker "src/connection_dialog.cpp" "src/tracker_state.cpp" "src/tracker_config.cpp" - "src/logger.cpp" "src/achievements_pane.cpp" "src/settings_dialog.cpp" "src/global.cpp" diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 68a6902..4a15db0 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -21,7 +21,6 @@ #include #include "game_data.h" -#include "logger.h" #include "tracker_frame.h" #include "tracker_state.h" @@ -75,7 +74,7 @@ struct APState { void Connect(std::string server, std::string player, std::string password) { if (!initialized) { - wxLogMessage("Initializing APState..."); + wxLogVerbose("Initializing APState..."); std::thread([this]() { for (;;) { @@ -108,11 +107,10 @@ struct APState { initialized = true; } - tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); - wxLogMessage("Connecting to Archipelago server (%s)...", server); + wxLogStatus("Connecting to Archipelago server (%s)...", server); { - wxLogMessage("Destroying old AP client..."); + wxLogVerbose("Destroying old AP client..."); std::lock_guard client_guard(client_mutex); @@ -156,12 +154,11 @@ struct APState { apclient->set_room_info_handler([this, player, password]() { inventory.clear(); - wxLogMessage("Connected to Archipelago server. Authenticating as %s %s", - player, - (password.empty() ? " without password" - : " with password " + password)); - tracker_frame->SetStatusMessage( + wxLogStatus( "Connected to Archipelago server. Authenticating..."); + wxLogVerbose("Authenticating as %s %s", player, + (password.empty() ? "without password" + : "with password " + password)); apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, {AP_MAJOR, AP_MINOR, AP_REVISION}); @@ -171,23 +168,19 @@ struct APState { [this](const std::list& locations) { for (const int64_t location_id : locations) { checked_locations.insert(location_id); - wxLogMessage("Location: %lld", location_id); + wxLogVerbose("Location: %lld", location_id); } RefreshTracker(false); }); apclient->set_slot_disconnected_handler([this]() { - tracker_frame->SetStatusMessage( - "Disconnected from Archipelago. Attempting to reconnect..."); - wxLogMessage( + wxLogStatus( "Slot disconnected from Archipelago. Attempting to reconnect..."); }); apclient->set_socket_disconnected_handler([this]() { - tracker_frame->SetStatusMessage( - "Disconnected from Archipelago. Attempting to reconnect..."); - wxLogMessage( + wxLogStatus( "Socket disconnected from Archipelago. Attempting to reconnect..."); }); @@ -195,7 +188,7 @@ struct APState { [this](const std::list& items) { for (const APClient::NetworkItem& item : items) { inventory[item.item]++; - wxLogMessage("Item: %lld", item.item); + wxLogVerbose("Item: %lld", item.item); } RefreshTracker(false); @@ -219,8 +212,7 @@ struct APState { apclient->set_slot_connected_handler([this]( const nlohmann::json& slot_data) { - tracker_frame->SetStatusMessage("Connected to Archipelago!"); - wxLogMessage("Connected to Archipelago!"); + wxLogStatus("Connected to Archipelago!"); data_storage_prefix = "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; @@ -341,9 +333,7 @@ struct APState { DestroyClient(); - tracker_frame->SetStatusMessage("Disconnected from Archipelago."); - - wxLogMessage("Timeout while connecting to Archipelago server."); + wxLogStatus("Timeout while connecting to Archipelago server."); wxMessageBox("Timeout while connecting to Archipelago server.", "Connection failed", wxOK | wxICON_ERROR); } @@ -363,11 +353,11 @@ struct APState { void HandleDataStorage(const std::string& key, const nlohmann::json& value) { if (value.is_boolean()) { data_storage[key] = value.get(); - wxLogMessage("Data storage %s retrieved as %s", key, + wxLogVerbose("Data storage %s retrieved as %s", key, (value.get() ? "true" : "false")); } else if (value.is_number()) { data_storage[key] = value.get(); - wxLogMessage("Data storage %s retrieved as %d", key, value.get()); + wxLogVerbose("Data storage %s retrieved as %d", key, value.get()); } else if (value.is_object()) { if (key.ends_with("PlayerPos")) { auto map_value = value.get>(); @@ -376,7 +366,7 @@ struct APState { data_storage[key] = value.get>(); } - wxLogMessage("Data storage %s retrieved as dictionary", key); + wxLogVerbose("Data storage %s retrieved as dictionary", key); } else if (value.is_null()) { if (key.ends_with("PlayerPos")) { player_pos = std::nullopt; @@ -384,15 +374,19 @@ struct APState { data_storage.erase(key); } - wxLogMessage("Data storage %s retrieved as null", key); + wxLogVerbose("Data storage %s retrieved as null", key); } else if (value.is_array()) { + auto list_value = value.get>(); + if (key.ends_with("Paintings")) { - data_storage[key] = value.get>(); + data_storage[key] = + std::set(list_value.begin(), list_value.end()); } else { - data_storage[key] = value.get>(); + data_storage[key] = list_value; } - wxLogMessage("Data storage %s retrieved as list", key); + wxLogVerbose("Data storage %s retrieved as list: [%s]", key, + hatkirby::implode(list_value, ", ")); } } @@ -425,7 +419,7 @@ struct APState { } void RefreshTracker(bool reset) { - wxLogMessage("Refreshing display..."); + wxLogVerbose("Refreshing display..."); RecalculateReachability(); diff --git a/src/game_data.cpp b/src/game_data.cpp index 8af57e5..de47ba4 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -1,5 +1,11 @@ #include "game_data.h" +#include + +#ifndef WX_PRECOMP +#include +#endif + #include #include @@ -7,7 +13,6 @@ #include #include "global.h" -#include "logger.h" namespace { diff --git a/src/logger.cpp b/src/logger.cpp deleted file mode 100644 index dddcc4a..0000000 --- a/src/logger.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "logger.h" - -#include "global.h" - -Logger::Logger() : logfile_(GetAbsolutePath("debug.log")) {} - -void Logger::Flush() { - wxLog::Flush(); - - std::lock_guard guard(file_mutex_); - logfile_.flush(); -} - -Logger::~Logger() { - std::lock_guard guard(file_mutex_); - logfile_.flush(); -} - -void Logger::DoLogText(const wxString& msg) { -#ifdef _WIN64 - OutputDebugStringA(msg.c_str()); - OutputDebugStringA("\r\n"); -#endif - - std::lock_guard guard(file_mutex_); - logfile_ << msg << std::endl; -} diff --git a/src/logger.h b/src/logger.h deleted file mode 100644 index b1a1d99..0000000 --- a/src/logger.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef LOGGER_H_6E7B9594 -#define LOGGER_H_6E7B9594 - -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include -#include - -class Logger : public wxLog { - public: - Logger(); - - void Flush() override; - - ~Logger(); - - protected: - void DoLogText(const wxString& msg) override; - - private: - std::ofstream logfile_; - std::mutex file_mutex_; -}; - -#endif /* end of include guard: LOGGER_H_6E7B9594 */ diff --git a/src/main.cpp b/src/main.cpp index db7653c..5b036ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,14 +4,24 @@ #include #endif -#include "logger.h" +#include + +#include "global.h" #include "tracker_config.h" #include "tracker_frame.h" +static std::ofstream* logfile; + class TrackerApp : public wxApp { public: virtual bool OnInit() { - wxLog::SetActiveTarget(new Logger()); + logfile = new std::ofstream(GetAbsolutePath("debug.log")); + wxLog::SetActiveTarget(new wxLogStream(logfile)); + +#ifndef NDEBUG + wxLog::SetVerbose(true); + wxLog::SetActiveTarget(new wxLogWindow(nullptr, "Debug Log")); +#endif GetTrackerConfig().Load(); -- cgit 1.4.1 From 636d23430bddf8295afeb55381db8d1f65cf4b38 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 13:27:33 -0400 Subject: Fixed art gallery painting --- assets/subway.yaml | 3 ++- src/game_data.cpp | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src/game_data.cpp') diff --git a/assets/subway.yaml b/assets/subway.yaml index 704f120..8f13f09 100644 --- a/assets/subway.yaml +++ b/assets/subway.yaml @@ -688,7 +688,7 @@ - blueman_starting - pos: [60, 970] special: early_color_hallways -- pos: [402, 1071] +- pos: [402, 1012] room: Outside The Undeterred door: Green Painting paintings: @@ -975,6 +975,7 @@ paintings: - smile_painting_3 - flower_painting_2 + - scenery_painting_0a - map_painting - fruitbowl_painting4 tags: diff --git a/src/game_data.cpp b/src/game_data.cpp index de47ba4..7c9564b 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -783,6 +783,11 @@ const SubwayItem &GD_GetSubwayItem(int id) { } int GD_GetSubwayItemForPainting(const std::string& painting_id) { +#ifndef NDEBUG + if (!GetState().subway_item_by_painting_.count(painting_id)) { + wxLogError("No subway item for painting %s", painting_id); + } +#endif return GetState().subway_item_by_painting_.at(painting_id); } -- cgit 1.4.1 From 13d2a129f6972e6e752da9c9cb686a63d5550517 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 29 May 2024 12:56:29 -0400 Subject: Show unchecked paintings --- src/ap_state.cpp | 12 ++++++++++++ src/ap_state.h | 2 ++ src/area_popup.cpp | 32 ++++++++++++++++++++++++++++++++ src/game_data.cpp | 33 ++++++++++++++++++++++++++------- src/game_data.h | 1 + src/tracker_panel.cpp | 14 ++++++++++++++ src/tracker_state.cpp | 32 +++++++++++++++++--------------- 7 files changed, 104 insertions(+), 22 deletions(-) (limited to 'src/game_data.cpp') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 20245e5..0c75eae 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -427,6 +427,14 @@ struct APState { return std::any_cast&>(data_storage.at(key)); } + bool IsPaintingChecked(const std::string& painting_id) { + const auto& checked_paintings = GetCheckedPaintings(); + + return checked_paintings.count(painting_id) || + (painting_mapping.count(painting_id) && + checked_paintings.count(painting_mapping.at(painting_id))); + } + void RefreshTracker(bool reset) { wxLogVerbose("Refreshing display..."); @@ -506,6 +514,10 @@ const std::set& AP_GetCheckedPaintings() { return GetState().GetCheckedPaintings(); } +bool AP_IsPaintingChecked(const std::string& painting_id) { + return GetState().IsPaintingChecked(painting_id); +} + int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } diff --git a/src/ap_state.h b/src/ap_state.h index 0ae6a25..2769bb8 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -61,6 +61,8 @@ const std::map& AP_GetPaintingMapping(); const std::set& AP_GetCheckedPaintings(); +bool AP_IsPaintingChecked(const std::string& painting_id); + int AP_GetMasteryRequirement(); int AP_GetLevel2Requirement(); diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 6e70315..b5c1ccb 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -65,6 +65,18 @@ void AreaPopup::UpdateIndicators() { } } + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& painting : map_area.paintings) { + wxSize item_extent = mem_dc.GetTextExtent(painting.id); + int item_height = std::max(32, item_extent.GetHeight()) + 10; + acc_height += item_height; + + if (item_extent.GetWidth() > col_width) { + col_width = item_extent.GetWidth(); + } + } + } + int item_width = col_width + 10 + 32; int full_width = std::max(header_extent.GetWidth(), item_width) + 20; @@ -109,6 +121,26 @@ void AreaPopup::UpdateIndicators() { cur_height += 10 + 32; } + + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& painting : map_area.paintings) { + bool checked = AP_IsPaintingChecked(painting.id); + wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; + + mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); + + bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + const wxColour* text_color = reachable ? wxWHITE : wxRED; + mem_dc.SetTextForeground(*text_color); + + wxSize item_extent = mem_dc.GetTextExtent(painting.id); + mem_dc.DrawText(painting.id, + {10 + 32 + 10, + cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); + + cur_height += 10 + 32; + } + } } void AreaPopup::OnPaint(wxPaintEvent& event) { diff --git a/src/game_data.cpp b/src/game_data.cpp index 7c9564b..4dd69e2 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -284,7 +284,7 @@ struct GameData { .as(); } else { wxLogError("Missing AP location ID for panel %s - %s", - rooms_[room_id].name, panels_[panel_id].name); + rooms_[room_id].name, panels_[panel_id].name); } } } @@ -348,7 +348,7 @@ struct GameData { .as(); } else { wxLogError("Missing AP item ID for door %s - %s", - rooms_[room_id].name, doors_[door_id].name); + rooms_[room_id].name, doors_[door_id].name); } } @@ -422,7 +422,8 @@ struct GameData { std::string painting_id = painting["id"].as(); room_by_painting_[painting_id] = room_id; - if (!painting["exit_only"] || !painting["exit_only"].as()) { + if ((!painting["exit_only"] || !painting["exit_only"].as()) && + (!painting["disable"] || !painting["disable"].as())) { PaintingExit painting_exit; painting_exit.id = painting_id; @@ -594,11 +595,28 @@ struct GameData { } } + for (const Room &room : rooms_) { + 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; + } + + if (!room.paintings.empty()) { + int area_id = AddOrGetArea(area_name); + MapArea &map_area = map_areas_[area_id]; + + for (const PaintingExit &painting : room.paintings) { + map_area.paintings.push_back(painting); + } + } + } + // Report errors. for (const std::string &area : malconfigured_areas_) { wxLogError("Area data not found for: %s", area); } - + // Read in subway items. YAML::Node subway_config = YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); @@ -687,7 +705,8 @@ struct GameData { if (!door_by_id_.count(full_name)) { int door_id = doors_.size(); door_by_id_[full_name] = doors_.size(); - doors_.push_back({.id = door_id, .room = AddOrGetRoom(room), .name = door}); + doors_.push_back( + {.id = door_id, .room = AddOrGetRoom(room), .name = door}); } return door_by_id_[full_name]; @@ -728,7 +747,7 @@ GameData &GetState() { } // namespace -bool SubwaySunwarp::operator<(const SubwaySunwarp& rhs) const { +bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); } @@ -782,7 +801,7 @@ const SubwayItem &GD_GetSubwayItem(int id) { return GetState().subway_items_.at(id); } -int GD_GetSubwayItemForPainting(const std::string& painting_id) { +int GD_GetSubwayItemForPainting(const std::string &painting_id) { #ifndef NDEBUG if (!GetState().subway_item_by_painting_.count(painting_id)) { wxLogError("No subway item for painting %s", painting_id); diff --git a/src/game_data.h b/src/game_data.h index 3afaec3..68ba5e4 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -113,6 +113,7 @@ struct MapArea { int id; std::string name; std::vector locations; + std::vector paintings; int map_x; int map_y; int classification = 0; diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 66bce81..0385f89 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -171,6 +171,20 @@ void TrackerPanel::Redraw() { } } + if (AP_IsPaintingShuffle()) { + for (const PaintingExit &painting : map_area.paintings) { + if (!AP_IsPaintingChecked(painting.id)) { + bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + + if (reachable) { + has_reachable_unchecked = true; + } else { + has_unreachable_unchecked = true; + } + } + } + } + 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)) * diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 869f837..faf74cc 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -15,11 +15,11 @@ namespace { struct Requirements { bool disabled = false; - std::set doors; // non-grouped, handles progressive - std::set items; // all other items - std::set rooms; // maybe - bool mastery = false; // maybe - bool panel_hunt = false; // maybe + std::set doors; // non-grouped, handles progressive + std::set items; // all other items + std::set rooms; // maybe + bool mastery = false; // maybe + bool panel_hunt = false; // maybe void Merge(const Requirements& rhs) { if (rhs.disabled) { @@ -228,7 +228,8 @@ class StateCalculator { if (AP_IsPaintingShuffle()) { for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id)) { + if (AP_GetPaintingMapping().count(out_edge.id) && + AP_GetCheckedPaintings().count(out_edge.id)) { Exit painting_exit; painting_exit.destination_room = GD_GetRoomForPainting( AP_GetPaintingMapping().at(out_edge.id)); @@ -286,7 +287,8 @@ class StateCalculator { panel_boundary = new_panel_boundary; } - // Now that we know the full reachable area, let's make sure all doors are evaluated. + // Now that we know the full reachable area, let's make sure all doors are + // evaluated. for (const Door& door : GD_GetDoors()) { int discard = IsDoorReachable(door.id); } @@ -320,7 +322,8 @@ class StateCalculator { return has_item ? kYes : kNo; } - Decision AreRequirementsSatisfied(const Requirements& reqs, std::map* report = nullptr) { + Decision AreRequirementsSatisfied( + const Requirements& reqs, std::map* report = nullptr) { if (reqs.disabled) { return kNo; } @@ -339,7 +342,7 @@ class StateCalculator { final_decision = decision; } } - + for (int item_id : reqs.items) { bool has_item = AP_HasItem(item_id); if (report) { @@ -353,10 +356,9 @@ class StateCalculator { for (int room_id : reqs.rooms) { bool reachable = reachable_rooms_.count(room_id); - + if (report) { - std::string report_name = - "Reach \"" + GD_GetRoom(room_id).name + "\""; + std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\""; (*report)[report_name] = reachable; } @@ -364,7 +366,7 @@ class StateCalculator { final_decision = kMaybe; } } - + if (reqs.mastery) { int achievements_accessible = 0; @@ -407,7 +409,7 @@ class StateCalculator { std::to_string(AP_GetLevel2Requirement()) + " Panels"; (*report)[report_name] = can_level2; } - + if (can_level2 && final_decision != kNo) { final_decision = kMaybe; } @@ -422,7 +424,7 @@ class StateCalculator { } else { door_report_[door_id] = {}; } - + return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), &door_report_[door_id]); } -- cgit 1.4.1 From 67a2efe7be6f4872adca8d944ebf403046472a98 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 13:53:20 -0400 Subject: Proper painting reachability detection --- src/area_popup.cpp | 16 ++++++------ src/game_data.cpp | 34 +++++++++++++++++++------- src/game_data.h | 11 ++++++--- src/tracker_panel.cpp | 7 +++--- src/tracker_state.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++--------- src/tracker_state.h | 2 ++ 6 files changed, 103 insertions(+), 34 deletions(-) (limited to 'src/game_data.cpp') diff --git a/src/area_popup.cpp b/src/area_popup.cpp index b5c1ccb..58d8897 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -66,8 +66,9 @@ void AreaPopup::UpdateIndicators() { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit& painting : map_area.paintings) { - wxSize item_extent = mem_dc.GetTextExtent(painting.id); + for (int painting_id : map_area.paintings) { + const PaintingExit& painting = GD_GetPaintingExit(painting_id); + wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. int item_height = std::max(32, item_extent.GetHeight()) + 10; acc_height += item_height; @@ -123,18 +124,19 @@ void AreaPopup::UpdateIndicators() { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit& painting : map_area.paintings) { - bool checked = AP_IsPaintingChecked(painting.id); + for (int painting_id : map_area.paintings) { + const PaintingExit& painting = GD_GetPaintingExit(painting_id); + bool checked = AP_IsPaintingChecked(painting.internal_id); wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); - bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + bool reachable = IsPaintingReachable(painting_id); const wxColour* text_color = reachable ? wxWHITE : wxRED; mem_dc.SetTextForeground(*text_color); - wxSize item_extent = mem_dc.GetTextExtent(painting.id); - mem_dc.DrawText(painting.id, + wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. + mem_dc.DrawText(painting.internal_id, {10 + 32 + 10, cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); diff --git a/src/game_data.cpp b/src/game_data.cpp index 4dd69e2..71b8629 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -48,11 +48,13 @@ struct GameData { std::vector panels_; std::vector map_areas_; std::vector subway_items_; + std::vector paintings_; std::map room_by_id_; std::map door_by_id_; std::map panel_by_id_; std::map area_by_id_; + std::map painting_by_id_; std::vector door_definition_order_; @@ -419,13 +421,13 @@ struct GameData { 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; + std::string internal_id = painting["id"].as(); if ((!painting["exit_only"] || !painting["exit_only"].as()) && (!painting["disable"] || !painting["disable"].as())) { - PaintingExit painting_exit; - painting_exit.id = painting_id; + int painting_id = AddOrGetPainting(internal_id); + PaintingExit &painting_exit = paintings_[painting_id]; + painting_exit.room = room_id; if (painting["required_door"]) { std::string rd_room = rooms_[room_id].name; @@ -437,7 +439,7 @@ struct GameData { rd_room, painting["required_door"]["door"].as()); } - rooms_[room_id].paintings.push_back(painting_exit); + rooms_[room_id].paintings.push_back(painting_exit.id); } } } @@ -606,8 +608,8 @@ struct GameData { int area_id = AddOrGetArea(area_name); MapArea &map_area = map_areas_[area_id]; - for (const PaintingExit &painting : room.paintings) { - map_area.paintings.push_back(painting); + for (int painting_id : room.paintings) { + map_area.paintings.push_back(painting_id); } } } @@ -738,6 +740,16 @@ struct GameData { return area_by_id_[area]; } + + int AddOrGetPainting(std::string internal_id) { + if (!painting_by_id_.count(internal_id)) { + int painting_id = paintings_.size(); + painting_by_id_[internal_id] = painting_id; + paintings_.push_back({.id = painting_id, .internal_id = internal_id}); + } + + return painting_by_id_[internal_id]; + } }; GameData &GetState() { @@ -773,8 +785,12 @@ const Panel &GD_GetPanel(int panel_id) { return GetState().panels_.at(panel_id); } -int GD_GetRoomForPainting(const std::string &painting_id) { - return GetState().room_by_painting_.at(painting_id); +const PaintingExit &GD_GetPaintingExit(int painting_id) { + return GetState().paintings_.at(painting_id); +} + +int GD_GetPaintingByName(const std::string &name) { + return GetState().painting_by_id_.at(name); } const std::vector &GD_GetAchievementPanels() { diff --git a/src/game_data.h b/src/game_data.h index 68ba5e4..e0942f7 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -87,14 +87,16 @@ struct Exit { }; struct PaintingExit { - std::string id; + int id; + int room; + std::string internal_id; std::optional door; }; struct Room { std::string name; std::vector exits; - std::vector paintings; + std::vector paintings; std::vector sunwarps; std::vector panels; }; @@ -113,7 +115,7 @@ struct MapArea { int id; std::string name; std::vector locations; - std::vector paintings; + std::vector paintings; int map_x; int map_y; int classification = 0; @@ -152,7 +154,8 @@ const std::vector& GD_GetDoors(); const Door& GD_GetDoor(int door_id); int GD_GetDoorByName(const std::string& name); const Panel& GD_GetPanel(int panel_id); -int GD_GetRoomForPainting(const std::string& painting_id); +const PaintingExit& GD_GetPaintingExit(int painting_id); +int GD_GetPaintingByName(const std::string& name); const std::vector& GD_GetAchievementPanels(); int GD_GetItemIdForColor(LingoColor color); const std::vector& GD_GetSunwarpDoors(); diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 0385f89..f0810c9 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -172,9 +172,10 @@ void TrackerPanel::Redraw() { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit &painting : map_area.paintings) { - if (!AP_IsPaintingChecked(painting.id)) { - bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + for (int painting_id : map_area.paintings) { + const PaintingExit &painting = GD_GetPaintingExit(painting_id); + if (!AP_IsPaintingChecked(painting.internal_id)) { + bool reachable = IsPaintingReachable(painting_id); if (reachable) { has_reachable_unchecked = true; diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 187a4a8..46bdbec 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -141,6 +141,7 @@ class RequirementCalculator { struct TrackerState { std::map reachability; std::set reachable_doors; + std::set reachable_paintings; std::mutex reachability_mutex; RequirementCalculator requirements; std::map> door_reports; @@ -170,6 +171,7 @@ class StateCalculator { void Calculate() { std::list panel_boundary; + std::list painting_boundary; std::list flood_boundary; flood_boundary.push_back({.destination_room = options_.start}); @@ -177,6 +179,8 @@ class StateCalculator { while (reachable_changed) { reachable_changed = false; + std::list new_boundary; + std::list new_panel_boundary; for (int panel_id : panel_boundary) { if (solveable_panels_.count(panel_id)) { @@ -192,7 +196,33 @@ class StateCalculator { } } - std::list new_boundary; + std::list new_painting_boundary; + for (int painting_id : painting_boundary) { + if (reachable_paintings_.count(painting_id)) { + continue; + } + + Decision painting_reachable = IsPaintingReachable(painting_id); + if (painting_reachable == kYes) { + reachable_paintings_.insert(painting_id); + reachable_changed = true; + + PaintingExit cur_painting = GD_GetPaintingExit(painting_id); + if (AP_GetPaintingMapping().count(cur_painting.internal_id) && + AP_GetCheckedPaintings().count(cur_painting.internal_id)) { + Exit painting_exit; + PaintingExit target_painting = + GD_GetPaintingExit(GD_GetPaintingByName( + AP_GetPaintingMapping().at(cur_painting.internal_id))); + painting_exit.destination_room = target_painting.room; + + new_boundary.push_back(painting_exit); + } + } else if (painting_reachable == kMaybe) { + new_painting_boundary.push_back(painting_id); + } + } + for (const Exit& room_exit : flood_boundary) { if (reachable_rooms_.count(room_exit.destination_room)) { continue; @@ -227,16 +257,8 @@ class StateCalculator { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id) && - AP_GetCheckedPaintings().count(out_edge.id)) { - Exit painting_exit; - painting_exit.destination_room = GD_GetRoomForPainting( - AP_GetPaintingMapping().at(out_edge.id)); - painting_exit.door = out_edge.door; - - new_boundary.push_back(painting_exit); - } + for (int out_edge : room_obj.paintings) { + new_painting_boundary.push_back(out_edge); } } @@ -285,6 +307,7 @@ class StateCalculator { flood_boundary = new_boundary; panel_boundary = new_panel_boundary; + painting_boundary = new_painting_boundary; } // Now that we know the full reachable area, let's make sure all doors are @@ -302,6 +325,10 @@ class StateCalculator { const std::set& GetSolveablePanels() const { return solveable_panels_; } + const std::set& GetReachablePaintings() const { + return reachable_paintings_; + } + const std::map>& GetDoorReports() const { return door_report_; } @@ -450,6 +477,15 @@ class StateCalculator { return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id)); } + Decision IsPaintingReachable(int painting_id) { + const PaintingExit& painting = GD_GetPaintingExit(painting_id); + if (painting.door) { + return IsDoorReachable(*painting.door); + } + + return kYes; + } + Decision IsExitUsable(const Exit& room_exit) { if (room_exit.type == EntranceType::kPilgrimage) { if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) { @@ -533,6 +569,7 @@ class StateCalculator { std::set reachable_rooms_; std::map door_decisions_; std::set solveable_panels_; + std::set reachable_paintings_; std::map> door_report_; }; @@ -573,6 +610,7 @@ void RecalculateReachability() { } } + std::set reachable_paintings = state_calculator.GetReachablePaintings(); std::map> door_reports = state_calculator.GetDoorReports(); @@ -580,6 +618,7 @@ void RecalculateReachability() { std::lock_guard reachability_guard(GetState().reachability_mutex); std::swap(GetState().reachability, new_reachability); std::swap(GetState().reachable_doors, new_reachable_doors); + std::swap(GetState().reachable_paintings, reachable_paintings); std::swap(GetState().door_reports, door_reports); } } @@ -600,6 +639,12 @@ bool IsDoorOpen(int door_id) { return GetState().reachable_doors.count(door_id); } +bool IsPaintingReachable(int painting_id) { + std::lock_guard reachability_guard(GetState().reachability_mutex); + + return GetState().reachable_paintings.count(painting_id); +} + const std::map& GetDoorRequirements(int door_id) { std::lock_guard reachability_guard(GetState().reachability_mutex); diff --git a/src/tracker_state.h b/src/tracker_state.h index 7acb0f2..c7857a0 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h @@ -12,6 +12,8 @@ bool IsLocationReachable(int location_id); bool IsDoorOpen(int door_id); +bool IsPaintingReachable(int painting_id); + const std::map& GetDoorRequirements(int door_id); #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ -- cgit 1.4.1