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 --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index a6f6342..99b15f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(lingo_ap_tracker "src/achievements_pane.cpp" "src/settings_dialog.cpp" "src/global.cpp" + "src/subway_map.cpp" "vendor/whereami/whereami.c" ) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) -- cgit 1.4.1 From e3fbcc9f29a1b1c83b23a4cef3819631fd3117d0 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 12 May 2024 18:45:21 -0400 Subject: Subway map hover detection with a quadtree --- CMakeLists.txt | 1 + src/subway_map.cpp | 37 ++++++ src/subway_map.h | 10 ++ vendor/quadtree/Box.h | 67 ++++++++++ vendor/quadtree/LICENSE | 21 +++ vendor/quadtree/Quadtree.h | 315 +++++++++++++++++++++++++++++++++++++++++++++ vendor/quadtree/Vector2.h | 47 +++++++ 7 files changed, 498 insertions(+) create mode 100644 vendor/quadtree/Box.h create mode 100644 vendor/quadtree/LICENSE create mode 100644 vendor/quadtree/Quadtree.h create mode 100644 vendor/quadtree/Vector2.h (limited to 'CMakeLists.txt') diff --git a/CMakeLists.txt b/CMakeLists.txt index 99b15f8..d6170a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ include_directories( ${yaml-cpp_INCLUDE_DIRS} ${OpenSSL_INCLUDE_DIRS} vendor/whereami + vendor ) find_path(SYSTEM_INCLUDE_DIR zlib.h) diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 0aa7df3..230a256 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -25,6 +25,13 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { return; } + tree_ = std::make_unique>( + quadtree::Box{0, 0, static_cast(map_image_.GetWidth()), + static_cast(map_image_.GetHeight())}); + for (const SubwayItem &subway_item : GD_GetSubwayItems()) { + tree_->add(subway_item.id); + } + Redraw(); Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); @@ -47,6 +54,30 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } void SubwayMap::OnMouseMove(wxMouseEvent &event) { + int mouse_x = std::clamp( + (event.GetX() - render_x_) * map_image_.GetWidth() / render_width_, + 0, map_image_.GetWidth() - 1); + int mouse_y = std::clamp( + (event.GetY() - render_y_) * map_image_.GetWidth() / render_width_, + 0, map_image_.GetHeight() - 1); + + std::vector hovered = tree_->query( + {static_cast(mouse_x), static_cast(mouse_y), 2, 2}); + std::optional new_hovered_item; + if (!hovered.empty()) { + new_hovered_item = hovered[0]; + } + + 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; + } + event.Skip(); } @@ -121,3 +152,9 @@ void SubwayMap::Redraw() { } } } + +quadtree::Box SubwayMap::GetItemBox::operator()(const int& id) const { + const SubwayItem &subway_item = GD_GetSubwayItem(id); + return {static_cast(subway_item.x), static_cast(subway_item.y), + AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; +} diff --git a/src/subway_map.h b/src/subway_map.h index e375750..6cb5c63 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -7,8 +7,12 @@ #include #endif +#include +#include #include +#include + class SubwayMap : public wxPanel { public: SubwayMap(wxWindow *parent); @@ -29,7 +33,13 @@ class SubwayMap : public wxPanel { int render_y_ = 0; int render_width_ = 0; int render_height_ = 0; + + struct GetItemBox { + quadtree::Box operator()(const int &id) const; + }; + std::unique_ptr> tree_; + std::optional hovered_item_; }; #endif /* end of include guard: SUBWAY_MAP_H_BD2D843E */ diff --git a/vendor/quadtree/Box.h b/vendor/quadtree/Box.h new file mode 100644 index 0000000..65182bf --- /dev/null +++ b/vendor/quadtree/Box.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Vector2.h" + +namespace quadtree +{ + +template +class Box +{ +public: + T left; + T top; + T width; // Must be positive + T height; // Must be positive + + constexpr Box(T Left = 0, T Top = 0, T Width = 0, T Height = 0) noexcept : + left(Left), top(Top), width(Width), height(Height) + { + + } + + constexpr Box(const Vector2& position, const Vector2& size) noexcept : + left(position.x), top(position.y), width(size.x), height(size.y) + { + + } + + constexpr T getRight() const noexcept + { + return left + width; + } + + constexpr T getBottom() const noexcept + { + return top + height; + } + + constexpr Vector2 getTopLeft() const noexcept + { + return Vector2(left, top); + } + + constexpr Vector2 getCenter() const noexcept + { + return Vector2(left + width / 2, top + height / 2); + } + + constexpr Vector2 getSize() const noexcept + { + return Vector2(width, height); + } + + constexpr bool contains(const Box& box) const noexcept + { + return left <= box.left && box.getRight() <= getRight() && + top <= box.top && box.getBottom() <= getBottom(); + } + + constexpr bool intersects(const Box& box) const noexcept + { + return !(left >= box.getRight() || getRight() <= box.left || + top >= box.getBottom() || getBottom() <= box.top); + } +}; + +} \ No newline at end of file diff --git a/vendor/quadtree/LICENSE b/vendor/quadtree/LICENSE new file mode 100644 index 0000000..1b226d3 --- /dev/null +++ b/vendor/quadtree/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Pierre Vigier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/quadtree/Quadtree.h b/vendor/quadtree/Quadtree.h new file mode 100644 index 0000000..06097c5 --- /dev/null +++ b/vendor/quadtree/Quadtree.h @@ -0,0 +1,315 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "Box.h" + +namespace quadtree +{ + +template, typename Float = float> +class Quadtree +{ + static_assert(std::is_convertible_v, Box>, + "GetBox must be a callable of signature Box(const T&)"); + static_assert(std::is_convertible_v, bool>, + "Equal must be a callable of signature bool(const T&, const T&)"); + static_assert(std::is_arithmetic_v); + +public: + Quadtree(const Box& box, const GetBox& getBox = GetBox(), + const Equal& equal = Equal()) : + mBox(box), mRoot(std::make_unique()), mGetBox(getBox), mEqual(equal) + { + + } + + void add(const T& value) + { + add(mRoot.get(), 0, mBox, value); + } + + void remove(const T& value) + { + remove(mRoot.get(), mBox, value); + } + + std::vector query(const Box& box) const + { + auto values = std::vector(); + query(mRoot.get(), mBox, box, values); + return values; + } + + std::vector> findAllIntersections() const + { + auto intersections = std::vector>(); + findAllIntersections(mRoot.get(), intersections); + return intersections; + } + + Box getBox() const + { + return mBox; + } + +private: + static constexpr auto Threshold = std::size_t(16); + static constexpr auto MaxDepth = std::size_t(8); + + struct Node + { + std::array, 4> children; + std::vector values; + }; + + Box mBox; + std::unique_ptr mRoot; + GetBox mGetBox; + Equal mEqual; + + bool isLeaf(const Node* node) const + { + return !static_cast(node->children[0]); + } + + Box computeBox(const Box& box, int i) const + { + auto origin = box.getTopLeft(); + auto childSize = box.getSize() / static_cast(2); + switch (i) + { + // North West + case 0: + return Box(origin, childSize); + // Norst East + case 1: + return Box(Vector2(origin.x + childSize.x, origin.y), childSize); + // South West + case 2: + return Box(Vector2(origin.x, origin.y + childSize.y), childSize); + // South East + case 3: + return Box(origin + childSize, childSize); + default: + assert(false && "Invalid child index"); + return Box(); + } + } + + int getQuadrant(const Box& nodeBox, const Box& valueBox) const + { + auto center = nodeBox.getCenter(); + // West + if (valueBox.getRight() < center.x) + { + // North West + if (valueBox.getBottom() < center.y) + return 0; + // South West + else if (valueBox.top >= center.y) + return 2; + // Not contained in any quadrant + else + return -1; + } + // East + else if (valueBox.left >= center.x) + { + // North East + if (valueBox.getBottom() < center.y) + return 1; + // South East + else if (valueBox.top >= center.y) + return 3; + // Not contained in any quadrant + else + return -1; + } + // Not contained in any quadrant + else + return -1; + } + + void add(Node* node, std::size_t depth, const Box& box, const T& value) + { + assert(node != nullptr); + assert(box.contains(mGetBox(value))); + if (isLeaf(node)) + { + // Insert the value in this node if possible + if (depth >= MaxDepth || node->values.size() < Threshold) + node->values.push_back(value); + // Otherwise, we split and we try again + else + { + split(node, box); + add(node, depth, box, value); + } + } + else + { + auto i = getQuadrant(box, mGetBox(value)); + // Add the value in a child if the value is entirely contained in it + if (i != -1) + add(node->children[static_cast(i)].get(), depth + 1, computeBox(box, i), value); + // Otherwise, we add the value in the current node + else + node->values.push_back(value); + } + } + + void split(Node* node, const Box& box) + { + assert(node != nullptr); + assert(isLeaf(node) && "Only leaves can be split"); + // Create children + for (auto& child : node->children) + child = std::make_unique(); + // Assign values to children + auto newValues = std::vector(); // New values for this node + for (const auto& value : node->values) + { + auto i = getQuadrant(box, mGetBox(value)); + if (i != -1) + node->children[static_cast(i)]->values.push_back(value); + else + newValues.push_back(value); + } + node->values = std::move(newValues); + } + + bool remove(Node* node, const Box& box, const T& value) + { + assert(node != nullptr); + assert(box.contains(mGetBox(value))); + if (isLeaf(node)) + { + // Remove the value from node + removeValue(node, value); + return true; + } + else + { + // Remove the value in a child if the value is entirely contained in it + auto i = getQuadrant(box, mGetBox(value)); + if (i != -1) + { + if (remove(node->children[static_cast(i)].get(), computeBox(box, i), value)) + return tryMerge(node); + } + // Otherwise, we remove the value from the current node + else + removeValue(node, value); + return false; + } + } + + void removeValue(Node* node, const T& value) + { + // Find the value in node->values + auto it = std::find_if(std::begin(node->values), std::end(node->values), + [this, &value](const auto& rhs){ return mEqual(value, rhs); }); + assert(it != std::end(node->values) && "Trying to remove a value that is not present in the node"); + // Swap with the last element and pop back + *it = std::move(node->values.back()); + node->values.pop_back(); + } + + bool tryMerge(Node* node) + { + assert(node != nullptr); + assert(!isLeaf(node) && "Only interior nodes can be merged"); + auto nbValues = node->values.size(); + for (const auto& child : node->children) + { + if (!isLeaf(child.get())) + return false; + nbValues += child->values.size(); + } + if (nbValues <= Threshold) + { + node->values.reserve(nbValues); + // Merge the values of all the children + for (const auto& child : node->children) + { + for (const auto& value : child->values) + node->values.push_back(value); + } + // Remove the children + for (auto& child : node->children) + child.reset(); + return true; + } + else + return false; + } + + void query(Node* node, const Box& box, const Box& queryBox, std::vector& values) const + { + assert(node != nullptr); + assert(queryBox.intersects(box)); + for (const auto& value : node->values) + { + if (queryBox.intersects(mGetBox(value))) + values.push_back(value); + } + if (!isLeaf(node)) + { + for (auto i = std::size_t(0); i < node->children.size(); ++i) + { + auto childBox = computeBox(box, static_cast(i)); + if (queryBox.intersects(childBox)) + query(node->children[i].get(), childBox, queryBox, values); + } + } + } + + void findAllIntersections(Node* node, std::vector>& intersections) const + { + // Find intersections between values stored in this node + // Make sure to not report the same intersection twice + for (auto i = std::size_t(0); i < node->values.size(); ++i) + { + for (auto j = std::size_t(0); j < i; ++j) + { + if (mGetBox(node->values[i]).intersects(mGetBox(node->values[j]))) + intersections.emplace_back(node->values[i], node->values[j]); + } + } + if (!isLeaf(node)) + { + // Values in this node can intersect values in descendants + for (const auto& child : node->children) + { + for (const auto& value : node->values) + findIntersectionsInDescendants(child.get(), value, intersections); + } + // Find intersections in children + for (const auto& child : node->children) + findAllIntersections(child.get(), intersections); + } + } + + void findIntersectionsInDescendants(Node* node, const T& value, std::vector>& intersections) const + { + // Test against the values stored in this node + for (const auto& other : node->values) + { + if (mGetBox(value).intersects(mGetBox(other))) + intersections.emplace_back(value, other); + } + // Test against values stored into descendants of this node + if (!isLeaf(node)) + { + for (const auto& child : node->children) + findIntersectionsInDescendants(child.get(), value, intersections); + } + } +}; + +} diff --git a/vendor/quadtree/Vector2.h b/vendor/quadtree/Vector2.h new file mode 100644 index 0000000..302d73e --- /dev/null +++ b/vendor/quadtree/Vector2.h @@ -0,0 +1,47 @@ +#pragma once + +namespace quadtree +{ + +template +class Vector2 +{ +public: + T x; + T y; + + constexpr Vector2(T X = 0, T Y = 0) noexcept : x(X), y(Y) + { + + } + + constexpr Vector2& operator+=(const Vector2& other) noexcept + { + x += other.x; + y += other.y; + return *this; + } + + constexpr Vector2& operator/=(T t) noexcept + { + x /= t; + y /= t; + return *this; + } +}; + +template +constexpr Vector2 operator+(Vector2 lhs, const Vector2& rhs) noexcept +{ + lhs += rhs; + return lhs; +} + +template +constexpr Vector2 operator/(Vector2 vec, T t) noexcept +{ + vec /= t; + return vec; +} + +} -- 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 'CMakeLists.txt') 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 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 'CMakeLists.txt') 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