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/subway_map.h | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/subway_map.h (limited to 'src/subway_map.h') diff --git a/src/subway_map.h b/src/subway_map.h new file mode 100644 index 0000000..dc67867 --- /dev/null +++ b/src/subway_map.h @@ -0,0 +1,35 @@ +#ifndef SUBWAY_MAP_H_BD2D843E +#define SUBWAY_MAP_H_BD2D843E + +#include + +#ifndef WX_PRECOMP +#include +#endif + +#include + +class SubwayMap : public wxPanel { + public: + SubwayMap(wxWindow *parent); + + void UpdateIndicators(); + + private: + void OnPaint(wxPaintEvent &event); + void OnMouseMove(wxMouseEvent &event); + + void Redraw(); + void Resize(); + + wxImage map_image_; + wxBitmap rendered_; + wxBitmap resized_; + int render_x_ = 0; + int render_y_ = 0; + int render_width_ = 0; + int render_height_ = 0; + +}; + +#endif /* end of include guard: SUBWAY_MAP_H_BD2D843E */ -- cgit 1.4.1 From 00658b38bd65e2cb81a502bd72e98ad9c411a7b4 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 12 May 2024 18:08:12 -0400 Subject: Higher quality + owl pictures --- src/subway_map.cpp | 98 ++++++++++++++++++++++++++++++++++++------------------ src/subway_map.h | 4 +-- 2 files changed, 68 insertions(+), 34 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index c58b2d1..0aa7df3 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -6,6 +6,12 @@ constexpr int AREA_ACTUAL_SIZE = 21; +enum class ItemDrawType { + kNone, + kBox, + kOwl +}; + SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { map_image_ = wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG); @@ -13,8 +19,13 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { return; } + owl_image_ = + wxImage(GetAbsolutePath("assets/owl.png").c_str(), wxBITMAP_TYPE_PNG); + if (!owl_image_.IsOk()) { + return; + } + Redraw(); - Resize(); Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); @@ -22,16 +33,15 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { void SubwayMap::UpdateIndicators() { Redraw(); - Resize(); } void SubwayMap::OnPaint(wxPaintEvent &event) { - if (GetSize() != resized_.GetSize()) { - Resize(); + if (GetSize() != rendered_.GetSize()) { + Redraw(); } wxPaintDC dc(this); - dc.DrawBitmap(resized_, 0, 0); + dc.DrawBitmap(rendered_, 0, 0); event.Skip(); } @@ -41,31 +51,8 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { } void SubwayMap::Redraw() { - rendered_ = wxBitmap(map_image_); - - wxMemoryDC dc; - dc.SelectObject(rendered_); - - for (const SubwayItem &subway_item : GD_GetSubwayItems()) { - const wxBrush *brush_color = wxGREY_BRUSH; - if (subway_item.door) { - if (IsDoorOpen(*subway_item.door)) { - brush_color = wxGREEN_BRUSH; - } else { - brush_color = wxRED_BRUSH; - } - } - - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); - dc.SetBrush(*brush_color); - dc.DrawRectangle({subway_item.x, subway_item.y}, - {AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}); - } -} - -void SubwayMap::Resize() { wxSize panel_size = GetSize(); - wxSize image_size = rendered_.GetSize(); + wxSize image_size = map_image_.GetSize(); render_x_ = 0; render_y_ = 0; @@ -75,15 +62,62 @@ void SubwayMap::Resize() { if (image_size.GetWidth() * panel_size.GetHeight() > panel_size.GetWidth() * image_size.GetHeight()) { render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / - image_size.GetWidth(); + image_size.GetWidth(); render_y_ = (panel_size.GetHeight() - render_height_) / 2; } else { render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / - image_size.GetHeight(); + image_size.GetHeight(); render_x_ = (panel_size.GetWidth() - render_width_) / 2; } - resized_ = wxBitmap(rendered_.ConvertToImage() + rendered_ = wxBitmap( + map_image_ .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR) .Size(panel_size, {render_x_, render_y_}, 0, 0, 0)); + + wxMemoryDC dc; + dc.SelectObject(rendered_); + + int real_area_size = + render_width_ * AREA_ACTUAL_SIZE / image_size.GetWidth(); + if (real_area_size == 0) { + real_area_size = 1; + } + wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( + real_area_size * 1.25, real_area_size * 1.25, wxIMAGE_QUALITY_BILINEAR)); + + for (const SubwayItem &subway_item : GD_GetSubwayItems()) { + ItemDrawType draw_type = ItemDrawType::kNone; + const wxBrush *brush_color = wxGREY_BRUSH; + + if (subway_item.door) { + draw_type = ItemDrawType::kBox; + + if (IsDoorOpen(*subway_item.door)) { + if (!subway_item.paintings.empty()) { + draw_type = ItemDrawType::kOwl; + } else { + brush_color = wxGREEN_BRUSH; + } + } else { + brush_color = wxRED_BRUSH; + } + } else if (!subway_item.paintings.empty()) { + draw_type = ItemDrawType::kOwl; + } + + int real_area_x = + render_x_ + subway_item.x * render_width_ / image_size.GetWidth(); + int real_area_y = + render_y_ + subway_item.y * render_width_ / image_size.GetWidth(); + + if (draw_type == ItemDrawType::kBox) { + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); + dc.SetBrush(*brush_color); + dc.DrawRectangle({real_area_x, real_area_y}, + {real_area_size, real_area_size}); + } else if (draw_type == ItemDrawType::kOwl) { + dc.DrawBitmap(owl_bitmap, {real_area_x, real_area_y}); + } + } } diff --git a/src/subway_map.h b/src/subway_map.h index dc67867..e375750 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -20,11 +20,11 @@ class SubwayMap : public wxPanel { void OnMouseMove(wxMouseEvent &event); void Redraw(); - void Resize(); wxImage map_image_; + wxImage owl_image_; + wxBitmap rendered_; - wxBitmap resized_; int render_x_ = 0; int render_y_ = 0; int render_width_ = 0; -- 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 'src/subway_map.h') 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 'src/subway_map.h') 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 3151ac6274e796f54f2d9269186f1fd2e69f90c3 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 12:11:00 -0400 Subject: Get checked paintings from server --- src/ap_state.cpp | 24 +++++++++++++++++++++++- src/ap_state.h | 5 ++++- src/subway_map.cpp | 24 ++++++++++++++---------- src/subway_map.h | 2 -- 4 files changed, 41 insertions(+), 14 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index e5ff74d..68a6902 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -103,6 +103,7 @@ struct APState { } tracked_data_storage_keys.push_back("PlayerPos"); + tracked_data_storage_keys.push_back("Paintings"); initialized = true; } @@ -384,6 +385,14 @@ struct APState { } wxLogMessage("Data storage %s retrieved as null", key); + } else if (value.is_array()) { + if (key.ends_with("Paintings")) { + data_storage[key] = value.get>(); + } else { + data_storage[key] = value.get>(); + } + + wxLogMessage("Data storage %s retrieved as list", key); } } @@ -406,6 +415,15 @@ struct APState { return data_storage.count(key) && std::any_cast(data_storage.at(key)); } + const std::set& GetCheckedPaintings() { + std::string key = data_storage_prefix + "Paintings"; + if (!data_storage.count(key)) { + data_storage[key] = std::set(); + } + + return std::any_cast&>(data_storage.at(key)); + } + void RefreshTracker(bool reset) { wxLogMessage("Refreshing display..."); @@ -471,10 +489,14 @@ bool AP_IsColorShuffle() { return GetState().color_shuffle; } bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } -const std::map AP_GetPaintingMapping() { +const std::map& AP_GetPaintingMapping() { return GetState().painting_mapping; } +const std::set& AP_GetCheckedPaintings() { + return GetState().GetCheckedPaintings(); +} + 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 6667e0d..5fbb720 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -54,7 +55,9 @@ bool AP_IsColorShuffle(); bool AP_IsPaintingShuffle(); -const std::map AP_GetPaintingMapping(); +const std::map& AP_GetPaintingMapping(); + +const std::set& AP_GetCheckedPaintings(); int AP_GetMasteryRequirement(); diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 99638aa..460532c 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -82,17 +82,21 @@ void SubwayMap::OnConnect() { } 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)); + if (AP_IsPaintingShuffle()) { + for (const std::string &painting_id : AP_GetCheckedPaintings()) { + if (!checked_paintings_.count(painting_id)) { + checked_paintings_.insert(painting_id); + + if (AP_GetPaintingMapping().count(painting_id)) { + networks_.AddLink(GD_GetSubwayItemForPainting(painting_id), + GD_GetSubwayItemForPainting( + AP_GetPaintingMapping().at(painting_id))); + } + } + } } + + Redraw(); } void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, diff --git a/src/subway_map.h b/src/subway_map.h index 1637125..e5f0bf6 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -24,8 +24,6 @@ class SubwayMap : public wxPanel { 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: -- cgit 1.4.1 From 4d8e36245e8ce43eef9b687a9108fd4c353f756f Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 16 May 2024 17:06:33 -0400 Subject: Added door popups that report requirements --- src/ap_state.cpp | 7 ++ src/ap_state.h | 2 + src/subway_map.cpp | 181 ++++++++++++++++++++--------- src/subway_map.h | 2 + src/tracker_state.cpp | 314 ++++++++++++++++++++++++++++++++++++-------------- src/tracker_state.h | 7 ++ 6 files changed, 377 insertions(+), 136 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index f245c2b..20245e5 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -271,6 +271,7 @@ struct APState { connected = true; has_connection_result = true; + ResetReachabilityRequirements(); RefreshTracker(true); std::list corrected_keys; @@ -447,6 +448,8 @@ struct APState { return ap_id; } + std::string GetItemName(int id) { return apclient->get_item_name(id); } + bool HasReachedGoal() { return data_storage.count(victory_data_storage_key) && std::any_cast(data_storage.at(victory_data_storage_key)) == @@ -485,6 +488,10 @@ bool AP_HasItem(int item_id, int quantity) { return GetState().HasItem(item_id, quantity); } +std::string AP_GetItemName(int item_id) { + return GetState().GetItemName(item_id); +} + DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } bool AP_IsColorShuffle() { return GetState().color_shuffle; } diff --git a/src/ap_state.h b/src/ap_state.h index 5fbb720..0ae6a25 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -49,6 +49,8 @@ bool AP_HasCheckedHuntPanel(int location_id); bool AP_HasItem(int item_id, int quantity = 1); +std::string AP_GetItemName(int item_id); + DoorShuffleMode AP_GetDoorShuffleMode(); bool AP_IsColorShuffle(); diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 6070fd5..2e7b36f 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -33,6 +33,14 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { return; } + unchecked_eye_ = + wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), + wxBITMAP_TYPE_PNG) + .Scale(32, 32)); + checked_eye_ = wxBitmap( + wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) + .Scale(32, 32)); + tree_ = std::make_unique>( quadtree::Box{0, 0, static_cast(map_image_.GetWidth()), static_cast(map_image_.GetHeight())}); @@ -113,75 +121,144 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); - if (hovered_item_ && networks_.IsItemInNetwork(*hovered_item_)) { - dc.SetBrush(*wxTRANSPARENT_BRUSH); + if (hovered_item_) { + const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); + if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { + const std::map &report = + GetDoorRequirements(*subway_item.door); - for (const auto &[item_id1, item_id2] : - networks_.GetNetworkGraph(*hovered_item_)) { - const SubwayItem &item1 = GD_GetSubwayItem(item_id1); - const SubwayItem &item2 = GD_GetSubwayItem(item_id2); + int acc_height = 10; + int col_width = 0; - 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_; + for (const auto &[text, obtained] : report) { + wxSize item_extent = dc.GetTextExtent(text); + int item_height = std::max(32, item_extent.GetHeight()) + 10; + acc_height += item_height; - 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_; + if (item_extent.GetWidth() > col_width) { + col_width = item_extent.GetWidth(); + } + } + + int item_width = col_width + 10 + 32; + int full_width = item_width + 20; + + int popup_x = (subway_item.x + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_x_; + int popup_y = (subway_item.y + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_y_; + + if (popup_x + full_width > GetSize().GetWidth()) { + popup_x = GetSize().GetWidth() - full_width; + } + if (popup_y + acc_height > GetSize().GetHeight()) { + popup_y = GetSize().GetHeight() - acc_height; + } - 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); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(*wxBLACK_BRUSH); + dc.DrawRectangle({popup_x, popup_y}, {full_width, acc_height}); - int halfwidth = right - left; - int halfheight = bottom - top; + dc.SetFont(GetFont()); - if (halfwidth < 4 || halfheight < 4) { - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); - dc.DrawLine(item1_x, item1_y, item2_x, item2_y); - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); - dc.DrawLine(item1_x, item1_y, item2_x, item2_y); - } else { - int ellipse_x; - int ellipse_y; - double start; - double end; + int cur_height = 10; - if (item1_x > item2_x) { - ellipse_y = top; + for (const auto& [text, obtained] : report) { + wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_; - if (item1_y > item2_y) { - ellipse_x = left - halfwidth; + dc.DrawBitmap(*eye_ptr, {popup_x + 10, popup_y + cur_height}); - start = 0; - end = 90; - } else { - ellipse_x = left; + dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); + wxSize item_extent = dc.GetTextExtent(text); + dc.DrawText( + text, + {popup_x + 10 + 32 + 10, + popup_y + cur_height + (32 - dc.GetFontMetrics().height) / 2}); - start = 90; - end = 180; - } + cur_height += 10 + 32; + } + } + + if (networks_.IsItemInNetwork(*hovered_item_)) { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + + for (const auto &[item_id1, item_id2] : + networks_.GetNetworkGraph(*hovered_item_)) { + 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; + + if (halfwidth < 4 || halfheight < 4) { + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); + dc.DrawLine(item1_x, item1_y, item2_x, item2_y); + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); + dc.DrawLine(item1_x, item1_y, item2_x, item2_y); } else { - ellipse_y = top - halfheight; + int ellipse_x; + int ellipse_y; + double start; + double end; - if (item1_y > item2_y) { - ellipse_x = left - halfwidth; + if (item1_x > item2_x) { + ellipse_y = top; - start = 270; - end = 360; + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; + + start = 0; + end = 90; + } else { + ellipse_x = left; + + start = 90; + end = 180; + } } else { - ellipse_x = left; + ellipse_y = top - halfheight; + + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; - start = 180; - end = 270; + 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); + 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); + } } } } diff --git a/src/subway_map.h b/src/subway_map.h index e5f0bf6..52d07b8 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -34,6 +34,8 @@ class SubwayMap : public wxPanel { wxImage map_image_; wxImage owl_image_; + wxBitmap unchecked_eye_; + wxBitmap checked_eye_; wxBitmap rendered_; int render_x_ = 0; diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 5588c7f..ccb3fbd 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -12,10 +12,138 @@ 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 + + void Merge(const Requirements& rhs) { + if (rhs.disabled) { + return; + } + + for (int id : rhs.doors) { + doors.insert(id); + } + for (int id : rhs.items) { + items.insert(id); + } + for (int id : rhs.rooms) { + rooms.insert(id); + } + mastery = mastery || rhs.mastery; + panel_hunt = panel_hunt || rhs.panel_hunt; + } +}; + +class RequirementCalculator { + public: + void Reset() { + doors_.clear(); + panels_.clear(); + } + + const Requirements& GetDoor(int door_id) { + if (!doors_.count(door_id)) { + Requirements requirements; + const Door& door_obj = GD_GetDoor(door_id); + + if (!AP_IsPilgrimageEnabled() && + door_obj.type == DoorType::kSunPainting) { + requirements.items.insert(door_obj.ap_item_id); + } else if (door_obj.type == DoorType::kSunwarp) { + switch (AP_GetSunwarpAccess()) { + case kSUNWARP_ACCESS_NORMAL: + // Do nothing. + break; + case kSUNWARP_ACCESS_DISABLED: + requirements.disabled = true; + break; + case kSUNWARP_ACCESS_UNLOCK: + requirements.items.insert(door_obj.group_ap_item_id); + break; + case kSUNWARP_ACCESS_INDIVIDUAL: + case kSUNWARP_ACCESS_PROGRESSIVE: + requirements.doors.insert(door_obj.id); + break; + } + } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { + requirements.rooms.insert(door_obj.room); + + for (int panel_id : door_obj.panels) { + const Requirements& panel_reqs = GetPanel(panel_id); + requirements.Merge(panel_reqs); + } + } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && + !door_obj.group_name.empty()) { + requirements.items.insert(door_obj.group_ap_item_id); + } else { + requirements.doors.insert(door_obj.id); + } + + doors_[door_id] = requirements; + } + + return doors_[door_id]; + } + + const Requirements& GetPanel(int panel_id) { + if (!panels_.count(panel_id)) { + Requirements requirements; + const Panel& panel_obj = GD_GetPanel(panel_id); + + requirements.rooms.insert(panel_obj.room); + + if (panel_obj.name == "THE MASTER") { + requirements.mastery = true; + } + + if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && + AP_GetLevel2Requirement() > 1) { + requirements.panel_hunt = true; + } + + for (int room_id : panel_obj.required_rooms) { + requirements.rooms.insert(room_id); + } + + for (int door_id : panel_obj.required_doors) { + const Requirements& door_reqs = GetDoor(door_id); + requirements.Merge(door_reqs); + } + + for (int panel_id : panel_obj.required_panels) { + const Requirements& panel_reqs = GetPanel(panel_id); + requirements.Merge(panel_reqs); + } + + if (AP_IsColorShuffle()) { + for (LingoColor color : panel_obj.colors) { + requirements.items.insert(GD_GetItemIdForColor(color)); + } + } + + panels_[panel_id] = requirements; + } + + return panels_[panel_id]; + } + + private: + std::map doors_; + std::map panels_; +}; + struct TrackerState { std::map reachability; std::set reachable_doors; std::mutex reachability_mutex; + RequirementCalculator requirements; + std::map> door_reports; }; enum Decision { kYes, kNo, kMaybe }; @@ -172,6 +300,10 @@ class StateCalculator { const std::set& GetSolveablePanels() const { return solveable_panels_; } + const std::map>& GetDoorReports() const { + return door_report_; + } + private: Decision IsNonGroupedDoorReachable(const Door& door_obj) { bool has_item = AP_HasItem(door_obj.ap_item_id); @@ -188,68 +320,52 @@ class StateCalculator { return has_item ? kYes : kNo; } - Decision IsDoorReachable_Helper(int door_id) { - const Door& door_obj = GD_GetDoor(door_id); - - if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { - return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; - } else if (door_obj.type == DoorType::kSunwarp) { - switch (AP_GetSunwarpAccess()) { - case kSUNWARP_ACCESS_NORMAL: - return kYes; - case kSUNWARP_ACCESS_DISABLED: - return kNo; - case kSUNWARP_ACCESS_UNLOCK: - return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; - case kSUNWARP_ACCESS_INDIVIDUAL: - case kSUNWARP_ACCESS_PROGRESSIVE: - return IsNonGroupedDoorReachable(door_obj); - } - } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { - if (!reachable_rooms_.count(door_obj.room)) { - return kMaybe; - } + Decision AreRequirementsSatisfied(const Requirements& reqs, std::map* report = nullptr) { + if (reqs.disabled) { + return kNo; + } - for (int panel_id : door_obj.panels) { - if (!solveable_panels_.count(panel_id)) { - return kMaybe; - } - } + Decision final_decision = kYes; - return kYes; - } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && - !door_obj.group_name.empty()) { - return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; - } else { - return IsNonGroupedDoorReachable(door_obj); - } - } + for (int door_id : reqs.doors) { + const Door& door_obj = GD_GetDoor(door_id); + Decision decision = IsNonGroupedDoorReachable(door_obj); - Decision IsDoorReachable(int door_id) { - if (options_.parent) { - return options_.parent->IsDoorReachable(door_id); - } + if (report) { + (*report)[door_obj.item_name] = (decision == kYes); + } - if (door_decisions_.count(door_id)) { - return door_decisions_.at(door_id); + if (decision != kYes) { + final_decision = decision; + } } + + for (int item_id : reqs.items) { + bool has_item = AP_HasItem(item_id); + if (report) { + (*report)[AP_GetItemName(item_id)] = has_item; + } - Decision result = IsDoorReachable_Helper(door_id); - if (result != kMaybe) { - door_decisions_[door_id] = result; + if (!has_item) { + final_decision = kNo; + } } - return result; - } - - Decision IsPanelReachable(int panel_id) { - const Panel& panel_obj = GD_GetPanel(panel_id); + 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 + "\""; + (*report)[report_name] = reachable; + } - if (!reachable_rooms_.count(panel_obj.room)) { - return kMaybe; + if (!reachable && final_decision != kNo) { + final_decision = kMaybe; + } } - - if (panel_obj.name == "THE MASTER") { + + if (reqs.mastery) { int achievements_accessible = 0; for (int achieve_id : GD_GetAchievementPanels()) { @@ -262,12 +378,18 @@ class StateCalculator { } } - return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes - : kMaybe; + bool can_mastery = + (achievements_accessible >= AP_GetMasteryRequirement()); + if (report) { + (*report)["Mastery"] = can_mastery; + } + + if (can_mastery && final_decision != kNo) { + final_decision = kMaybe; + } } - if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && - AP_GetLevel2Requirement() > 1) { + if (reqs.panel_hunt) { int counting_panels_accessible = 0; for (int solved_panel_id : solveable_panels_) { @@ -278,44 +400,52 @@ class StateCalculator { } } - return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) - ? kYes - : kMaybe; + bool can_level2 = + (counting_panels_accessible >= AP_GetLevel2Requirement() - 1); + if (report) { + std::string report_name = + std::to_string(AP_GetLevel2Requirement()) + " Panels"; + (*report)[report_name] = can_level2; + } + + if (can_level2 && final_decision != kNo) { + final_decision = kMaybe; + } } - for (int room_id : panel_obj.required_rooms) { - if (!reachable_rooms_.count(room_id)) { - return kMaybe; - } + return final_decision; + } + + Decision IsDoorReachable_Helper(int door_id) { + if (door_report_.count(door_id)) { + door_report_[door_id].clear(); + } else { + door_report_[door_id] = {}; } + + return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), + &door_report_[door_id]); + } - for (int door_id : panel_obj.required_doors) { - Decision door_reachable = IsDoorReachable(door_id); - if (door_reachable == kNo) { - const Door& door_obj = GD_GetDoor(door_id); - return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) - ? kMaybe - : kNo; - } else if (door_reachable == kMaybe) { - return kMaybe; - } + Decision IsDoorReachable(int door_id) { + if (options_.parent) { + return options_.parent->IsDoorReachable(door_id); } - for (int panel_id : panel_obj.required_panels) { - if (!solveable_panels_.count(panel_id)) { - return kMaybe; - } + if (door_decisions_.count(door_id)) { + return door_decisions_.at(door_id); } - if (AP_IsColorShuffle()) { - for (LingoColor color : panel_obj.colors) { - if (!AP_HasItem(GD_GetItemIdForColor(color))) { - return kNo; - } - } + Decision result = IsDoorReachable_Helper(door_id); + if (result != kMaybe) { + door_decisions_[door_id] = result; } - return kYes; + return result; + } + + Decision IsPanelReachable(int panel_id) { + return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id)); } Decision IsExitUsable(const Exit& room_exit) { @@ -401,10 +531,16 @@ class StateCalculator { std::set reachable_rooms_; std::map door_decisions_; std::set solveable_panels_; + std::map> door_report_; }; } // namespace +void ResetReachabilityRequirements() { + std::lock_guard reachability_guard(GetState().reachability_mutex); + GetState().requirements.Reset(); +} + void RecalculateReachability() { StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); state_calculator.Calculate(); @@ -435,10 +571,14 @@ void RecalculateReachability() { } } + std::map> door_reports = + state_calculator.GetDoorReports(); + { 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().door_reports, door_reports); } } @@ -457,3 +597,9 @@ bool IsDoorOpen(int door_id) { return GetState().reachable_doors.count(door_id); } + +const std::map& GetDoorRequirements(int door_id) { + std::lock_guard reachability_guard(GetState().reachability_mutex); + + return GetState().door_reports.at(door_id); +} diff --git a/src/tracker_state.h b/src/tracker_state.h index 119b3b5..7acb0f2 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h @@ -1,10 +1,17 @@ #ifndef TRACKER_STATE_H_8639BC90 #define TRACKER_STATE_H_8639BC90 +#include +#include + +void ResetReachabilityRequirements(); + void RecalculateReachability(); bool IsLocationReachable(int location_id); bool IsDoorOpen(int door_id); +const std::map& GetDoorRequirements(int door_id); + #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ -- cgit 1.4.1 From a2b67cf27dbd41bcfa75835e36c605adfc040e40 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 18 May 2024 15:33:30 -0400 Subject: Coordinate transformation functions --- src/subway_map.cpp | 99 ++++++++++++++++++++++++++---------------------------- src/subway_map.h | 3 ++ 2 files changed, 50 insertions(+), 52 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 2e7b36f..5a4be4b 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -143,23 +143,20 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { int item_width = col_width + 10 + 32; int full_width = item_width + 20; - int popup_x = (subway_item.x + AREA_ACTUAL_SIZE / 2) * render_width_ / - map_image_.GetWidth() + - render_x_; - int popup_y = (subway_item.y + AREA_ACTUAL_SIZE / 2) * render_width_ / - map_image_.GetWidth() + - render_y_; - - if (popup_x + full_width > GetSize().GetWidth()) { - popup_x = GetSize().GetWidth() - full_width; + wxPoint popup_pos = + MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, + subway_item.y + AREA_ACTUAL_SIZE / 2}); + + if (popup_pos.x + full_width > GetSize().GetWidth()) { + popup_pos.x = GetSize().GetWidth() - full_width; } - if (popup_y + acc_height > GetSize().GetHeight()) { - popup_y = GetSize().GetHeight() - acc_height; + if (popup_pos.y + acc_height > GetSize().GetHeight()) { + popup_pos.y = GetSize().GetHeight() - acc_height; } dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(*wxBLACK_BRUSH); - dc.DrawRectangle({popup_x, popup_y}, {full_width, acc_height}); + dc.DrawRectangle(popup_pos, {full_width, acc_height}); dc.SetFont(GetFont()); @@ -168,14 +165,15 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { for (const auto& [text, obtained] : report) { wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_; - dc.DrawBitmap(*eye_ptr, {popup_x + 10, popup_y + cur_height}); + dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); wxSize item_extent = dc.GetTextExtent(text); dc.DrawText( text, - {popup_x + 10 + 32 + 10, - popup_y + cur_height + (32 - dc.GetFontMetrics().height) / 2}); + popup_pos + + wxPoint{10 + 32 + 10, + cur_height + (32 - dc.GetFontMetrics().height) / 2}); cur_height += 10 + 32; } @@ -189,43 +187,34 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { 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); + wxPoint item1_pos = MapPosToRenderPos( + {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); + wxPoint item2_pos = MapPosToRenderPos( + {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); + + int left = std::min(item1_pos.x, item2_pos.x); + int top = std::min(item1_pos.y, item2_pos.y); + int right = std::max(item1_pos.x, item2_pos.x); + int bottom = std::max(item1_pos.y, item2_pos.y); int halfwidth = right - left; int halfheight = bottom - top; if (halfwidth < 4 || halfheight < 4) { dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); - dc.DrawLine(item1_x, item1_y, item2_x, item2_y); + dc.DrawLine(item1_pos, item2_pos); dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); - dc.DrawLine(item1_x, item1_y, item2_x, item2_y); + dc.DrawLine(item1_pos, item2_pos); } else { int ellipse_x; int ellipse_y; double start; double end; - if (item1_x > item2_x) { + if (item1_pos.x > item2_pos.x) { ellipse_y = top; - if (item1_y > item2_y) { + if (item1_pos.y > item2_pos.y) { ellipse_x = left - halfwidth; start = 0; @@ -239,7 +228,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } else { ellipse_y = top - halfheight; - if (item1_y > item2_y) { + if (item1_pos.y > item2_pos.y) { ellipse_x = left - halfwidth; start = 270; @@ -267,15 +256,10 @@ 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); - + wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); + std::vector hovered = tree_->query( - {static_cast(mouse_x), static_cast(mouse_y), 2, 2}); + {static_cast(mouse_pos.x), static_cast(mouse_pos.y), 2, 2}); std::optional new_hovered_item; if (!hovered.empty()) { new_hovered_item = hovered[0]; @@ -369,10 +353,7 @@ void SubwayMap::Redraw() { } } - int real_area_x = - render_x_ + subway_item.x * render_width_ / image_size.GetWidth(); - int real_area_y = - render_y_ + subway_item.y * render_width_ / image_size.GetWidth(); + wxPoint real_area_pos = MapPosToRenderPos({subway_item.x, subway_item.y}); int real_area_size = render_width_ * @@ -385,17 +366,31 @@ void SubwayMap::Redraw() { if (draw_type == ItemDrawType::kBox) { dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); dc.SetBrush(*brush_color); - dc.DrawRectangle({real_area_x, real_area_y}, + dc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); } else if (draw_type == ItemDrawType::kOwl) { wxBitmap owl_bitmap = wxBitmap( owl_image_.Scale(real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); - dc.DrawBitmap(owl_bitmap, {real_area_x, real_area_y}); + dc.DrawBitmap(owl_bitmap, real_area_pos); } } } + +wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { + return {pos.x * render_width_ / map_image_.GetSize().GetWidth() + render_x_, + pos.y * render_width_ / map_image_.GetSize().GetWidth() + render_y_}; +} + +wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { + return { + std::clamp((pos.x - render_x_) * map_image_.GetWidth() / render_width_, 0, + map_image_.GetWidth() - 1), + std::clamp((pos.y - render_y_) * map_image_.GetWidth() / render_width_, 0, + map_image_.GetHeight() - 1)}; +} + 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), diff --git a/src/subway_map.h b/src/subway_map.h index 52d07b8..0d26d0b 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -32,6 +32,9 @@ class SubwayMap : public wxPanel { void Redraw(); + wxPoint MapPosToRenderPos(wxPoint pos) const; + wxPoint RenderPosToMapPos(wxPoint pos) const; + wxImage map_image_; wxImage owl_image_; wxBitmap unchecked_eye_; -- cgit 1.4.1 From c87281384f716c8ca2f57bb8ea65d6d01bb06fae Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 18 May 2024 20:33:16 -0400 Subject: Zoom in and scroll subway map --- src/subway_map.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++----------- src/subway_map.h | 15 +++++ 2 files changed, 151 insertions(+), 35 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 5a4be4b..b02c616 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -12,11 +12,7 @@ constexpr int AREA_ACTUAL_SIZE = 21; constexpr int OWL_ACTUAL_SIZE = 32; -enum class ItemDrawType { - kNone, - kBox, - kOwl -}; +enum class ItemDrawType { kNone, kBox, kOwl }; SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { SetBackgroundStyle(wxBG_STYLE_PAINT); @@ -50,8 +46,13 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { Redraw(); + scroll_timer_ = new wxTimer(this); + Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); + Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); + Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); + Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); } void SubwayMap::OnConnect() { @@ -67,7 +68,8 @@ void SubwayMap::OnConnect() { tagged[tag].push_back(subway_item.id); } - if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { + if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && + subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { std::ostringstream tag; tag << "sunwarp" << subway_item.sunwarp->dots; @@ -108,7 +110,7 @@ void SubwayMap::UpdateIndicators() { } void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, - SubwaySunwarp to_sunwarp) { + SubwaySunwarp to_sunwarp) { networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), GD_GetSubwayItemForSunwarp(to_sunwarp)); } @@ -119,7 +121,9 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } wxBufferedPaintDC dc(this); - dc.DrawBitmap(rendered_, 0, 0); + dc.SetBackground(*wxWHITE_BRUSH); + dc.Clear(); + dc.DrawBitmap(rendered_, zoom_x_, zoom_y_); if (hovered_item_) { const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); @@ -139,7 +143,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { col_width = item_extent.GetWidth(); } } - + int item_width = col_width + 10 + 32; int full_width = item_width + 20; @@ -162,7 +166,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { int cur_height = 10; - for (const auto& [text, obtained] : report) { + for (const auto &[text, obtained] : report) { wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_; dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); @@ -191,7 +195,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); wxPoint item2_pos = MapPosToRenderPos( {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); - + int left = std::min(item1_pos.x, item2_pos.x); int top = std::min(item1_pos.y, item2_pos.y); int right = std::max(item1_pos.x, item2_pos.x); @@ -242,11 +246,11 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); - dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, - start, end); + 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); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, + halfheight * 2, start, end); } } } @@ -257,7 +261,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { void SubwayMap::OnMouseMove(wxMouseEvent &event) { wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); - + std::vector hovered = tree_->query( {static_cast(mouse_pos.x), static_cast(mouse_pos.y), 2, 2}); std::optional new_hovered_item; @@ -271,9 +275,65 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { Refresh(); } + int scroll_x; + int scroll_y; + if (event.GetPosition().x < GetSize().GetWidth() / 9) { + scroll_x = 20; + } else if (event.GetPosition().x < GetSize().GetWidth() / 6) { + scroll_x = 5; + } else if (event.GetPosition().x > 8 * GetSize().GetWidth() / 9) { + scroll_x = -20; + } else if (event.GetPosition().x > 5 * GetSize().GetWidth() / 6) { + scroll_x = -5; + } else { + scroll_x = 0; + } + if (event.GetPosition().y < GetSize().GetHeight() / 9) { + scroll_y = 20; + } else if (event.GetPosition().y < GetSize().GetHeight() / 6) { + scroll_y = 5; + } else if (event.GetPosition().y > 8 * GetSize().GetHeight() / 9) { + scroll_y = -20; + } else if (event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { + scroll_y = -5; + } else { + scroll_y = 0; + } + + SetScrollSpeed(scroll_x, scroll_y); + event.Skip(); } +void SubwayMap::OnMouseScroll(wxMouseEvent &event) { + double new_zoom = zoom_; + if (event.GetWheelRotation() > 0) { + new_zoom = std::min(3.0, zoom_ + 0.25); + } else { + new_zoom = std::max(1.0, zoom_ - 0.25); + } + + if (zoom_ != new_zoom) { + wxPoint map_pos = RenderPosToMapPos(event.GetPosition()); + zoom_ = new_zoom; + + wxPoint virtual_pos = MapPosToVirtualPos(map_pos); + SetZoomPos(-(virtual_pos - event.GetPosition())); + + Redraw(); + Refresh(); + } + + event.Skip(); +} + +void SubwayMap::OnMouseLeave(wxMouseEvent &event) { SetScrollSpeed(0, 0); } + +void SubwayMap::OnTimer(wxTimerEvent &event) { + SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); + Refresh(); +} + void SubwayMap::Redraw() { wxSize panel_size = GetSize(); wxSize image_size = map_image_.GetSize(); @@ -294,10 +354,10 @@ void SubwayMap::Redraw() { render_x_ = (panel_size.GetWidth() - render_width_) / 2; } - rendered_ = wxBitmap( - map_image_ - .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR) - .Size(panel_size, {render_x_, render_y_}, 255, 255, 255)); + rendered_ = wxBitmap(map_image_.Scale( + render_width_ * zoom_, render_height_ * zoom_, wxIMAGE_QUALITY_BILINEAR)); + + SetZoomPos({zoom_x_, zoom_y_}); wxMemoryDC dc; dc.SelectObject(rendered_); @@ -353,10 +413,10 @@ void SubwayMap::Redraw() { } } - wxPoint real_area_pos = MapPosToRenderPos({subway_item.x, subway_item.y}); + wxPoint real_area_pos = MapPosToVirtualPos({subway_item.x, subway_item.y}); int real_area_size = - render_width_ * + render_width_ * zoom_ * (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE) / image_size.GetWidth(); if (real_area_size == 0) { @@ -366,32 +426,73 @@ void SubwayMap::Redraw() { if (draw_type == ItemDrawType::kBox) { dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); dc.SetBrush(*brush_color); - dc.DrawRectangle(real_area_pos, - {real_area_size, real_area_size}); + dc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); } else if (draw_type == ItemDrawType::kOwl) { - wxBitmap owl_bitmap = wxBitmap( - owl_image_.Scale(real_area_size, real_area_size, - wxIMAGE_QUALITY_BILINEAR)); + wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( + real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); dc.DrawBitmap(owl_bitmap, real_area_pos); } } } - wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { - return {pos.x * render_width_ / map_image_.GetSize().GetWidth() + render_x_, - pos.y * render_width_ / map_image_.GetSize().GetWidth() + render_y_}; + return {static_cast(pos.x * render_width_ * zoom_ / + map_image_.GetSize().GetWidth() + + zoom_x_), + static_cast(pos.y * render_width_ * zoom_ / + map_image_.GetSize().GetWidth() + + zoom_y_)}; +} + +wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { + return {static_cast(pos.x * render_width_ * zoom_ / + map_image_.GetSize().GetWidth()), + static_cast(pos.y * render_width_ * zoom_ / + map_image_.GetSize().GetWidth())}; } wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { return { - std::clamp((pos.x - render_x_) * map_image_.GetWidth() / render_width_, 0, - map_image_.GetWidth() - 1), - std::clamp((pos.y - render_y_) * map_image_.GetWidth() / render_width_, 0, - map_image_.GetHeight() - 1)}; + std::clamp(static_cast((pos.x - zoom_x_) * map_image_.GetWidth() / + render_width_ / zoom_), + 0, map_image_.GetWidth() - 1), + std::clamp(static_cast((pos.y - zoom_y_) * map_image_.GetWidth() / + render_width_ / zoom_), + 0, map_image_.GetHeight() - 1)}; +} + +void SubwayMap::SetZoomPos(wxPoint pos) { + if (render_width_ * zoom_ <= GetSize().GetWidth()) { + zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; + } else { + zoom_x_ = std::clamp( + pos.x, GetSize().GetWidth() - static_cast(render_width_ * zoom_), + 0); + } + if (render_height_ * zoom_ <= GetSize().GetHeight()) { + zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; + } else { + zoom_y_ = std::clamp( + pos.y, GetSize().GetHeight() - static_cast(render_height_ * zoom_), + 0); + } +} + +void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { + bool should_timer = (scroll_x != 0 || scroll_y != 0); + if (should_timer != scroll_timer_->IsRunning()) { + if (should_timer) { + scroll_timer_->Start(1000 / 60); + } else { + scroll_timer_->Stop(); + } + } + + scroll_x_ = scroll_x; + scroll_y_ = scroll_y; } -quadtree::Box SubwayMap::GetItemBox::operator()(const int& id) const { +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 0d26d0b..9b0b43f 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -29,12 +29,19 @@ class SubwayMap : public wxPanel { private: void OnPaint(wxPaintEvent &event); void OnMouseMove(wxMouseEvent &event); + void OnMouseScroll(wxMouseEvent &event); + void OnMouseLeave(wxMouseEvent &event); + void OnTimer(wxTimerEvent &event); void Redraw(); wxPoint MapPosToRenderPos(wxPoint pos) const; + wxPoint MapPosToVirtualPos(wxPoint pos) const; wxPoint RenderPosToMapPos(wxPoint pos) const; + void SetZoomPos(wxPoint pos); + void SetScrollSpeed(int scroll_x, int scroll_y); + wxImage map_image_; wxImage owl_image_; wxBitmap unchecked_eye_; @@ -46,6 +53,14 @@ class SubwayMap : public wxPanel { int render_width_ = 0; int render_height_ = 0; + double zoom_ = 1.0; + int zoom_x_ = 0; // in render space + int zoom_y_ = 0; + + wxTimer* scroll_timer_; + int scroll_x_ = 0; + int scroll_y_ = 0; + struct GetItemBox { quadtree::Box operator()(const int &id) const; }; -- cgit 1.4.1 From eaa21c53b96b945d8809dc5f4a9353ecaaacc266 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 19 May 2024 11:48:34 -0400 Subject: Zoom slider --- src/subway_map.cpp | 31 ++++++++++++++++++++++++------- src/subway_map.h | 4 ++++ 2 files changed, 28 insertions(+), 7 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 47ebfdf..98d544c 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -54,6 +54,9 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); + + zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); + zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); } void SubwayMap::OnConnect() { @@ -381,13 +384,7 @@ void SubwayMap::OnMouseScroll(wxMouseEvent &event) { } if (zoom_ != new_zoom) { - wxPoint map_pos = RenderPosToMapPos(event.GetPosition()); - zoom_ = new_zoom; - - wxPoint virtual_pos = MapPosToVirtualPos(map_pos); - SetZoomPos(-(virtual_pos - event.GetPosition())); - - Refresh(); + SetZoom(new_zoom, event.GetPosition()); } event.Skip(); @@ -400,6 +397,14 @@ void SubwayMap::OnTimer(wxTimerEvent &event) { Refresh(); } +void SubwayMap::OnZoomSlide(wxCommandEvent &event) { + double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue(); + + if (new_zoom != zoom_) { + SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}); + } +} + void SubwayMap::Redraw() { rendered_ = wxBitmap(map_image_); @@ -530,6 +535,18 @@ void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { scroll_y_ = scroll_y; } +void SubwayMap::SetZoom(double zoom, wxPoint static_point) { + wxPoint map_pos = RenderPosToMapPos(static_point); + zoom_ = zoom; + + wxPoint virtual_pos = MapPosToVirtualPos(map_pos); + SetZoomPos(-(virtual_pos - static_point)); + + Refresh(); + + zoom_slider_->SetValue((zoom - 1.0) / 0.25); +} + 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), diff --git a/src/subway_map.h b/src/subway_map.h index 9b0b43f..e891058 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -32,6 +32,7 @@ class SubwayMap : public wxPanel { void OnMouseScroll(wxMouseEvent &event); void OnMouseLeave(wxMouseEvent &event); void OnTimer(wxTimerEvent &event); + void OnZoomSlide(wxCommandEvent &event); void Redraw(); @@ -41,6 +42,7 @@ class SubwayMap : public wxPanel { void SetZoomPos(wxPoint pos); void SetScrollSpeed(int scroll_x, int scroll_y); + void SetZoom(double zoom, wxPoint static_point); wxImage map_image_; wxImage owl_image_; @@ -61,6 +63,8 @@ class SubwayMap : public wxPanel { int scroll_x_ = 0; int scroll_y_ = 0; + wxSlider *zoom_slider_; + struct GetItemBox { quadtree::Box operator()(const int &id) const; }; -- cgit 1.4.1 From 983f01cb8a2eaecd162e5734de88c461ef197b34 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 19 May 2024 12:24:00 -0400 Subject: Zoom in/out menu items with keyboard shortcuts --- src/subway_map.cpp | 30 ++++++++++++++++++++++++++++-- src/subway_map.h | 3 +++ src/tracker_frame.cpp | 45 ++++++++++++++++++++++++++++++++++++++------- src/tracker_frame.h | 9 +++++++++ 4 files changed, 78 insertions(+), 9 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 98d544c..69bf51b 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -119,6 +119,26 @@ void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, GD_GetSubwayItemForSunwarp(to_sunwarp)); } +void SubwayMap::Zoom(bool in) { + wxPoint focus_point; + + if (mouse_position_) { + focus_point = *mouse_position_; + } else { + focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}; + } + + if (in) { + if (zoom_ < 3.0) { + SetZoom(zoom_ + 0.25, focus_point); + } + } else { + if (zoom_ > 1.0) { + SetZoom(zoom_ - 0.25, focus_point); + } + } +} + void SubwayMap::OnPaint(wxPaintEvent &event) { if (GetSize() != rendered_.GetSize()) { wxSize panel_size = GetSize(); @@ -372,6 +392,8 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { SetScrollSpeed(scroll_x, scroll_y); + mouse_position_ = event.GetPosition(); + event.Skip(); } @@ -390,7 +412,10 @@ void SubwayMap::OnMouseScroll(wxMouseEvent &event) { event.Skip(); } -void SubwayMap::OnMouseLeave(wxMouseEvent &event) { SetScrollSpeed(0, 0); } +void SubwayMap::OnMouseLeave(wxMouseEvent &event) { + SetScrollSpeed(0, 0); + mouse_position_ = std::nullopt; +} void SubwayMap::OnTimer(wxTimerEvent &event) { SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); @@ -464,7 +489,8 @@ void SubwayMap::Redraw() { wxPoint real_area_pos = {subway_item.x, subway_item.y}; - int real_area_size = (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); + int real_area_size = + (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); if (draw_type == ItemDrawType::kBox) { dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); diff --git a/src/subway_map.h b/src/subway_map.h index e891058..986998a 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -25,6 +25,7 @@ class SubwayMap : public wxPanel { void OnConnect(); void UpdateIndicators(); void UpdateSunwarp(SubwaySunwarp from_sunwarp, SubwaySunwarp to_sunwarp); + void Zoom(bool in); private: void OnPaint(wxPaintEvent &event); @@ -65,6 +66,8 @@ class SubwayMap : public wxPanel { wxSlider *zoom_slider_; + std::optional mouse_position_; + struct GetItemBox { quadtree::Box operator()(const int &id) const; }; diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index e944704..a15a6b4 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -19,7 +20,9 @@ enum TrackerFrameIds { ID_CONNECT = 1, ID_CHECK_FOR_UPDATES = 2, - ID_SETTINGS = 3 + ID_SETTINGS = 3, + ID_ZOOM_IN = 4, + ID_ZOOM_OUT = 5, }; wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); @@ -38,12 +41,20 @@ TrackerFrame::TrackerFrame() menuFile->Append(ID_SETTINGS, "&Settings"); menuFile->Append(wxID_EXIT); + wxMenu *menuView = new wxMenu(); + zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); + zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); + + zoom_in_menu_item_->Enable(false); + zoom_out_menu_item_->Enable(false); + wxMenu *menuHelp = new wxMenu(); menuHelp->Append(wxID_ABOUT); menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates"); wxMenuBar *menuBar = new wxMenuBar(); menuBar->Append(menuFile, "&File"); + menuBar->Append(menuView, "&View"); menuBar->Append(menuHelp, "&Help"); SetMenuBar(menuBar); @@ -57,6 +68,9 @@ TrackerFrame::TrackerFrame() Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS); Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, ID_CHECK_FOR_UPDATES); + Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); + Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); + Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); @@ -66,15 +80,15 @@ TrackerFrame::TrackerFrame() wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); choicebook->AddPage(achievements_pane_, "Achievements"); - wxNotebook *rightpane = new wxNotebook(this, wxID_ANY); - tracker_panel_ = new TrackerPanel(rightpane); - subway_map_ = new SubwayMap(rightpane); - rightpane->AddPage(tracker_panel_, "Map"); - rightpane->AddPage(subway_map_, "Subway"); + notebook_ = new wxNotebook(this, wxID_ANY); + tracker_panel_ = new TrackerPanel(notebook_); + subway_map_ = new SubwayMap(notebook_); + notebook_->AddPage(tracker_panel_, "Map"); + notebook_->AddPage(subway_map_, "Subway"); wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); - top_sizer->Add(rightpane, wxSizerFlags().Expand().Proportion(3)); + top_sizer->Add(notebook_, wxSizerFlags().Expand().Proportion(3)); SetSizerAndFit(top_sizer); SetSize(1280, 728); @@ -174,6 +188,23 @@ void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { CheckForUpdates(/*manual=*/true); } +void TrackerFrame::OnZoomIn(wxCommandEvent &event) { + if (notebook_->GetSelection() == 1) { + subway_map_->Zoom(true); + } +} + +void TrackerFrame::OnZoomOut(wxCommandEvent& event) { + if (notebook_->GetSelection() == 1) { + subway_map_->Zoom(false); + } +} + +void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { + zoom_in_menu_item_->Enable(event.GetSelection() == 1); + zoom_out_menu_item_->Enable(event.GetSelection() == 1); +} + void TrackerFrame::OnStateReset(wxCommandEvent& event) { tracker_panel_->UpdateIndicators(); achievements_pane_->UpdateIndicators(); diff --git a/src/tracker_frame.h b/src/tracker_frame.h index f1d7171..f7cb3f2 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h @@ -10,6 +10,8 @@ class AchievementsPane; class SubwayMap; class TrackerPanel; +class wxBookCtrlEvent; +class wxNotebook; wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); @@ -30,6 +32,9 @@ class TrackerFrame : public wxFrame { void OnConnect(wxCommandEvent &event); void OnSettings(wxCommandEvent &event); void OnCheckForUpdates(wxCommandEvent &event); + void OnZoomIn(wxCommandEvent &event); + void OnZoomOut(wxCommandEvent &event); + void OnChangePage(wxBookCtrlEvent &event); void OnStateReset(wxCommandEvent &event); void OnStateChanged(wxCommandEvent &event); @@ -37,9 +42,13 @@ class TrackerFrame : public wxFrame { void CheckForUpdates(bool manual); + wxNotebook *notebook_; TrackerPanel *tracker_panel_; AchievementsPane *achievements_pane_; SubwayMap *subway_map_; + + wxMenuItem *zoom_in_menu_item_; + wxMenuItem *zoom_out_menu_item_; }; #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ -- cgit 1.4.1 From f9d209b21596e5aac09a7743385065803749f6a0 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 11:09:15 -0400 Subject: Subway: Click to activate scroll / sticky popup --- src/subway_map.cpp | 97 ++++++++++++++++++++++++++++++++++++++---------------- src/subway_map.h | 6 ++++ 2 files changed, 74 insertions(+), 29 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 9175514..77f6ae6 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -53,6 +53,7 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); + Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); @@ -216,6 +217,8 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } if (hovered_item_) { + // Note that these requirements are duplicated on OnMouseClick so that it + // knows when an item has a hover effect. const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { const std::map &report = @@ -354,44 +357,22 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { std::vector hovered = tree_->query( {static_cast(mouse_pos.x), static_cast(mouse_pos.y), 2, 2}); - std::optional new_hovered_item; if (!hovered.empty()) { - new_hovered_item = hovered[0]; + actual_hover_= hovered[0]; + } else { + actual_hover_ = std::nullopt; } - if (new_hovered_item != hovered_item_) { - hovered_item_ = new_hovered_item; + if (!sticky_hover_ && actual_hover_ != hovered_item_) { + hovered_item_ = actual_hover_; Refresh(); } - int scroll_x; - int scroll_y; - if (event.GetPosition().x < GetSize().GetWidth() / 9) { - scroll_x = 20; - } else if (event.GetPosition().x < GetSize().GetWidth() / 6) { - scroll_x = 5; - } else if (event.GetPosition().x > 8 * GetSize().GetWidth() / 9) { - scroll_x = -20; - } else if (event.GetPosition().x > 5 * GetSize().GetWidth() / 6) { - scroll_x = -5; - } else { - scroll_x = 0; - } - if (event.GetPosition().y < GetSize().GetHeight() / 9) { - scroll_y = 20; - } else if (event.GetPosition().y < GetSize().GetHeight() / 6) { - scroll_y = 5; - } else if (event.GetPosition().y > 8 * GetSize().GetHeight() / 9) { - scroll_y = -20; - } else if (event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { - scroll_y = -5; - } else { - scroll_y = 0; + if (scroll_mode_) { + EvaluateScroll(event.GetPosition()); } - SetScrollSpeed(scroll_x, scroll_y); - mouse_position_ = event.GetPosition(); event.Skip(); @@ -417,6 +398,35 @@ void SubwayMap::OnMouseLeave(wxMouseEvent &event) { mouse_position_ = std::nullopt; } +void SubwayMap::OnMouseClick(wxMouseEvent &event) { + if (sticky_hover_) { + sticky_hover_ = false; + + if (actual_hover_ != hovered_item_) { + hovered_item_ = actual_hover_; + + Refresh(); + } + } else if (hovered_item_) { + const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); + if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || + networks_.IsItemInNetwork(*hovered_item_)) { + sticky_hover_ = true; + } + } else if (scroll_mode_) { + scroll_mode_ = false; + + SetScrollSpeed(0, 0); + } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || + event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || + event.GetPosition().y < GetSize().GetHeight() / 6 || + event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { + scroll_mode_ = true; + + EvaluateScroll(event.GetPosition()); + } +} + void SubwayMap::OnTimer(wxTimerEvent &event) { SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); Refresh(); @@ -504,6 +514,35 @@ void SubwayMap::Redraw() { } } +void SubwayMap::EvaluateScroll(wxPoint pos) { + int scroll_x; + int scroll_y; + if (pos.x < GetSize().GetWidth() / 9) { + scroll_x = 20; + } else if (pos.x < GetSize().GetWidth() / 6) { + scroll_x = 5; + } else if (pos.x > 8 * GetSize().GetWidth() / 9) { + scroll_x = -20; + } else if (pos.x > 5 * GetSize().GetWidth() / 6) { + scroll_x = -5; + } else { + scroll_x = 0; + } + if (pos.y < GetSize().GetHeight() / 9) { + scroll_y = 20; + } else if (pos.y < GetSize().GetHeight() / 6) { + scroll_y = 5; + } else if (pos.y > 8 * GetSize().GetHeight() / 9) { + scroll_y = -20; + } else if (pos.y > 5 * GetSize().GetHeight() / 6) { + scroll_y = -5; + } else { + scroll_y = 0; + } + + SetScrollSpeed(scroll_x, scroll_y); +} + wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { return {static_cast(pos.x * render_width_ * zoom_ / map_image_.GetSize().GetWidth() + diff --git a/src/subway_map.h b/src/subway_map.h index 986998a..8b8c6a6 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -32,6 +32,7 @@ class SubwayMap : public wxPanel { void OnMouseMove(wxMouseEvent &event); void OnMouseScroll(wxMouseEvent &event); void OnMouseLeave(wxMouseEvent &event); + void OnMouseClick(wxMouseEvent &event); void OnTimer(wxTimerEvent &event); void OnZoomSlide(wxCommandEvent &event); @@ -41,6 +42,8 @@ class SubwayMap : public wxPanel { wxPoint MapPosToVirtualPos(wxPoint pos) const; wxPoint RenderPosToMapPos(wxPoint pos) const; + void EvaluateScroll(wxPoint pos); + void SetZoomPos(wxPoint pos); void SetScrollSpeed(int scroll_x, int scroll_y); void SetZoom(double zoom, wxPoint static_point); @@ -60,6 +63,7 @@ class SubwayMap : public wxPanel { int zoom_x_ = 0; // in render space int zoom_y_ = 0; + bool scroll_mode_ = false; wxTimer* scroll_timer_; int scroll_x_ = 0; int scroll_y_ = 0; @@ -74,6 +78,8 @@ class SubwayMap : public wxPanel { std::unique_ptr> tree_; std::optional hovered_item_; + std::optional actual_hover_; + bool sticky_hover_ = false; NetworkSet networks_; std::set checked_paintings_; -- cgit 1.4.1 From b78a7361c9f9f4a9edc30b45a90a3ff94e919b19 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:07:51 -0400 Subject: Added help button for subway map --- src/subway_map.cpp | 25 +++++++++++++++++++++++++ src/subway_map.h | 4 ++++ 2 files changed, 29 insertions(+) (limited to 'src/subway_map.h') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 77f6ae6..100d351 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -58,6 +58,10 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); + + help_button_ = new wxButton(this, wxID_ANY, "Help"); + help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); + SetUpHelpButton(); } void SubwayMap::OnConnect() { @@ -162,6 +166,8 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { } SetZoomPos({zoom_x_, zoom_y_}); + + SetUpHelpButton(); } wxBufferedPaintDC dc(this); @@ -440,6 +446,18 @@ void SubwayMap::OnZoomSlide(wxCommandEvent &event) { } } +void SubwayMap::OnClickHelp(wxCommandEvent &event) { + wxMessageBox( + "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the " + "corner.\nClick on a side of the screen to start panning. It will follow " + "your mouse. Click again to stop.\nHover over a door to see the " + "requirements to open it.\nHover over a warp or active painting to see " + "what it is connected to.\nIn painting shuffle, paintings that have not " + "yet been checked will not show their connections.\nClick on a door or " + "warp to make the popup stick until you click again.", + "Subway Map Help"); +} + void SubwayMap::Redraw() { rendered_ = wxBitmap(map_image_); @@ -514,6 +532,13 @@ void SubwayMap::Redraw() { } } +void SubwayMap::SetUpHelpButton() { + help_button_->SetPosition({ + GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, + 15, + }); +} + void SubwayMap::EvaluateScroll(wxPoint pos) { int scroll_x; int scroll_y; diff --git a/src/subway_map.h b/src/subway_map.h index 8b8c6a6..1108cd4 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -35,8 +35,10 @@ class SubwayMap : public wxPanel { void OnMouseClick(wxMouseEvent &event); void OnTimer(wxTimerEvent &event); void OnZoomSlide(wxCommandEvent &event); + void OnClickHelp(wxCommandEvent &event); void Redraw(); + void SetUpHelpButton(); wxPoint MapPosToRenderPos(wxPoint pos) const; wxPoint MapPosToVirtualPos(wxPoint pos) const; @@ -70,6 +72,8 @@ class SubwayMap : public wxPanel { wxSlider *zoom_slider_; + wxButton *help_button_; + std::optional mouse_position_; struct GetItemBox { -- cgit 1.4.1 From 52dd0b92f4cf82dee0f638bdf4ba304090863423 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:57:50 -0400 Subject: Prevent crash from opening subway map too quickly --- src/subway_map.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/subway_map.h') diff --git a/src/subway_map.h b/src/subway_map.h index 1108cd4..feee8ff 100644 --- a/src/subway_map.h +++ b/src/subway_map.h @@ -58,8 +58,8 @@ class SubwayMap : public wxPanel { wxBitmap rendered_; int render_x_ = 0; int render_y_ = 0; - int render_width_ = 0; - int render_height_ = 0; + int render_width_ = 1; + int render_height_ = 1; double zoom_ = 1.0; int zoom_x_ = 0; // in render space -- cgit 1.4.1