From 5f7069d480022b115bee585724d41ff827f80f2f Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 20 Apr 2024 12:36:22 -0400 Subject: Replaced about box --- src/tracker_frame.cpp | 24 +++++++++++++++--------- src/version.h | 12 +++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index d64e0d3..8a0c764 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -1,5 +1,6 @@ #include "tracker_frame.h" +#include #include #include @@ -101,12 +102,14 @@ void TrackerFrame::UpdateIndicators() { } void TrackerFrame::OnAbout(wxCommandEvent &event) { - std::ostringstream message_text; - message_text << "Lingo Archipelago Tracker " << kTrackerVersion - << " by hatkirby"; - - wxMessageBox(message_text.str(), "About lingo-ap-tracker", - wxOK | wxICON_INFORMATION); + wxAboutDialogInfo about_info; + about_info.SetName("Lingo Archipelago Tracker"); + about_info.SetVersion(kTrackerVersion.ToString()); + about_info.AddDeveloper("hatkirby"); + about_info.AddArtist("Brenton Wildes"); + about_info.AddArtist("kinrah"); + + wxAboutBox(about_info); } void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } @@ -122,7 +125,8 @@ void TrackerFrame::OnConnect(wxCommandEvent &event) { std::deque new_history; new_history.push_back(GetTrackerConfig().connection_details); - for (const ConnectionDetails& details : GetTrackerConfig().connection_history) { + for (const ConnectionDetails &details : + GetTrackerConfig().connection_history) { if (details != GetTrackerConfig().connection_details) { new_history.push_back(details); } @@ -192,8 +196,10 @@ void TrackerFrame::CheckForUpdates(bool manual) { std::ostringstream message_text; message_text << "There is a newer version of Lingo AP Tracker " "available. You have " - << kTrackerVersion << ", and the latest version is " - << latest_version << ". Would you like to update?"; + << kTrackerVersion.ToString() + << ", and the latest version is " + << latest_version.ToString() + << ". Would you like to update?"; if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == wxYES) { diff --git a/src/version.h b/src/version.h index 8c567e9..7b9aebc 100644 --- a/src/version.h +++ b/src/version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H_C757E53C #define VERSION_H_C757E53C -#include +#include #include struct Version { @@ -23,6 +23,12 @@ struct Version { } } + std::string ToString() const { + std::ostringstream output; + output << "v" << major << "." << minor << "." << revision; + return output.str(); + } + bool operator<(const Version& rhs) const { return (major < rhs.major) || (major == rhs.major && @@ -31,10 +37,6 @@ struct Version { } }; -std::ostream& operator<<(std::ostream& out, const Version& ver) { - return out << "v" << ver.major << "." << ver.minor << "." << ver.revision; -} - constexpr const Version kTrackerVersion = Version(0, 8, 0); #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file -- cgit 1.4.1 From ea16cff14ff4faf5782da8ff684a6ec412b7b6ac Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 12 May 2024 17:48:02 -0400 Subject: Started making subway map --- CMakeLists.txt | 1 + assets/owl.png | Bin 0 -> 439 bytes assets/subway.png | Bin 0 -> 184533 bytes assets/subway.yaml | 614 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/game_data.cpp | 62 ++++- src/game_data.h | 25 ++ src/subway_map.cpp | 89 ++++++++ src/subway_map.h | 35 +++ src/tracker_frame.cpp | 13 +- src/tracker_frame.h | 2 + src/tracker_state.cpp | 20 ++ src/tracker_state.h | 2 + 12 files changed, 859 insertions(+), 4 deletions(-) create mode 100644 assets/owl.png create mode 100644 assets/subway.png create mode 100644 assets/subway.yaml create mode 100644 src/subway_map.cpp create mode 100644 src/subway_map.h (limited to 'src') diff --git a/CMakeLists.txt b/CMakeLists.txt index a6f6342..99b15f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(lingo_ap_tracker "src/achievements_pane.cpp" "src/settings_dialog.cpp" "src/global.cpp" + "src/subway_map.cpp" "vendor/whereami/whereami.c" ) set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) diff --git a/assets/owl.png b/assets/owl.png new file mode 100644 index 0000000..0210303 Binary files /dev/null and b/assets/owl.png differ diff --git a/assets/subway.png b/assets/subway.png new file mode 100644 index 0000000..3860d2c Binary files /dev/null and b/assets/subway.png differ diff --git a/assets/subway.yaml b/assets/subway.yaml new file mode 100644 index 0000000..b0d9c84 --- /dev/null +++ b/assets/subway.yaml @@ -0,0 +1,614 @@ +--- +- pos: [1050, 954] + room: Starting Room + door: Back Right Door +- pos: [986, 1034] + room: Starting Room + door: Rhyme Room Entrance +- pos: [990, 956] + special: starting_room_paintings # Early Color Hallways painting is a hardcoded special case + paintings: + - arrows_painting +- pos: [905, 841] + room: Hedge Maze + door: Painting Shortcut + paintings: + - garden_painting_tower2 + tags: + - garden_starting +- pos: [1066, 841] + room: Courtyard + door: Painting Shortcut + paintings: + - flower_painting_8 + tags: + - flower_starting +- pos: [905, 895] + room: The Wondrous (Doorknob) + door: Painting Shortcut + paintings: + - symmetry_painting_a_starter + tags: + - symmetry_starting +- pos: [1066, 868] + room: Outside The Bold + door: Painting Shortcut + paintings: + - pencil_painting6 + tags: + - pencil_starting +- pos: [1066, 895] + room: Outside The Undeterred + door: Painting Shortcut + paintings: + - blueman_painting_3 + tags: + - blueman_starting +- pos: [905, 868] + room: Outside The Agreeable + door: Painting Shortcut + paintings: + - eyes_yellow_painting2 + tags: + - street_starting +- pos: [1211, 879] + room: Hidden Room + door: Dead End Door +- pos: [1291, 906] + room: Hidden Room + door: Knight Night Entrance +- pos: [1103, 980] + room: Hidden Room + door: Seeker Entrance +- pos: [1173, 980] + room: Hidden Room + door: Rhyme Room Entrance +- pos: [1116, 939] + paintings: + - owl_painting + tags: + - owl_hidden +- pos: [986, 793] + room: Second Room + door: Exit Door +- pos: [798, 584] + room: Hub Room + door: Crossroads Entrance +- pos: [932, 665] + room: Hub Room + door: Tenacious Entrance +- pos: [1361, 578] + room: Hub Room + door: Shortcut to Hedge Maze +- pos: [1312, 841] + room: Hub Room + door: Near RAT Door +- pos: [1371, 729] + room: Hub Room + door: Traveled Entrance +- pos: [1313, 686] + paintings: + - maze_painting + tags: + - green_owl + - green_numbers +- pos: [1172, 760] + sunwarp: + dots: 1 + type: enter +- pos: [1302, 638] + room: Outside The Undeterred + door: Fours +- pos: [1243, 819] + room: Outside The Undeterred + door: Fours +- pos: [1276, 819] + room: Outside The Undeterred + door: Eights +- pos: [1263, 867] + paintings: + - smile_painting_6 + tags: + - smiley_deadend +- pos: [1012, 1086] + sunwarp: + dots: 6 + type: final +- pos: [938, 1002] + room: Pilgrim Antechamber + door: Sun Painting + special: sun_painting +- pos: [1053, 1090] + invisible: true + special: sun_painting_exit +- pos: [1077, 1061] + room: Pilgrim Room + door: Shortcut to The Seeker +- pos: [713, 359] + room: Number Hunt + door: Eights +- pos: [932, 348] + room: Crossroads + door: Hollow Hallway +- pos: [798, 290] + room: Crossroads + door: Tower Entrance +- pos: [932, 477] + room: Crossroads + door: Tenacious Entrance +- pos: [638, 477] + room: Crossroads + door: Discerning Entrance +- pos: [905, 290] + room: Crossroads + door: Tower Back Entrance +- pos: [894, 423] + room: Crossroads + door: Words Sword Door +- pos: [632, 643] + room: Crossroads + door: Eye Wall +- pos: [638, 520] + room: Crossroads + door: Roof Access +- pos: [756, 400] + paintings: + - smile_painting_4 + tags: + - smiley_crossroads +- pos: [878, 509] + sunwarp: + dots: 1 + type: exit +- pos: [1056, 344] + room: Lost Area + door: Exit +- pos: [954, 290] + room: Lost Area + door: Exit +- pos: [986, 290] + room: Number Hunt + door: Eights +- pos: [954, 247] + room: Amen Name Area + door: Exit +- pos: [954, 222] + paintings: + - west_afar +- pos: [986, 697] + room: The Tenacious + door: Shortcut to Hub Room +- pos: [1173, 665] + room: Near Far Area + door: Door +- pos: [1173, 622] + room: Warts Straw Area + door: Door +- pos: [1173, 579] + room: Leaf Feel Area + door: Door +- pos: [1173, 333] + room: Outside The Agreeable + door: Purple Barrier +- pos: [1088, 289] + room: Outside The Undeterred + door: Fives +- pos: [1088, 418] + room: Outside The Undeterred + door: Fives +- pos: [1039, 477] + room: Outside The Agreeable + door: Tenacious Entrance +- pos: [1147, 525] + room: Outside The Agreeable + door: Black Door +- pos: [1216, 525] + room: Outside The Agreeable + door: Agreeable Entrance +- pos: [1138, 287] + paintings: + - eyes_yellow_painting + tags: + - street_starting +- pos: [1088, 385] + sunwarp: + dots: 6 + type: enter +- pos: [1195, 450] + room: Compass Room + door: Lookout Entrance +- pos: [1214, 457] + paintings: + - pencil_painting7 + tags: + - pencil_compass +- pos: [1196, 417] + invisible: true + tags: + - agreeable_to_lookout +- pos: [1657, 1392] + room: Room Room + door: Excavation +- pos: [1725, 1441] + invisible: true + tags: + - agreeable_to_lookout +- pos: [1040, 665] + room: Dread Hallway + door: Tenacious Entrance +- pos: [1324, 525] + room: The Agreeable + door: Shortcut to Hedge Maze +- pos: [1484, 392] + room: Hedge Maze + door: Perceptive Entrance +- pos: [1441, 241] + room: Hedge Maze + door: Observant Entrance +- pos: [1714, 434] + room: Hedge Maze + door: Observant Entrance +- pos: [1477, 343] + paintings: + - garden_painting_tower + tags: + - garden_starting +- pos: [1565, 311] + room: The Fearless (First Floor) + door: Second Floor +- pos: [1597, 279] + room: The Fearless (Second Floor) + door: Third Floor +- pos: [1414, 209] + room: The Observant + door: Backside Door +- pos: [1624, 188] + room: The Observant + door: Stairs +- pos: [1667, 686] + room: The Incomparable + door: Eight Door +- pos: [1784, 569] + paintings: + - crown_painting + tags: + - crown_tower6 +- pos: [1653, 717] + paintings: + - eight_painting2 + tags: + - eight_alcove +- pos: [1653, 662] + paintings: + - eight_painting + tags: + - eight_alcove +- pos: [697, 1471] + room: Orange Tower + door: Second Floor +- pos: [633, 1406] + room: Orange Tower + door: Third Floor +- pos: [570, 1343] + room: Orange Tower + door: Fourth Floor +- pos: [504, 1279] + room: Orange Tower + door: Fifth Floor +- pos: [440, 1215] + room: Orange Tower + door: Sixth Floor +- pos: [379, 1153] + room: Orange Tower + door: Seventh Floor +- pos: [905, 793] + room: Orange Tower First Floor + door: Shortcut to Hub Room +- pos: [686, 820] + room: Orange Tower First Floor + door: Salt Pepper Door +- pos: [755, 787] + sunwarp: + dots: 4 + type: enter +- pos: [719, 846] + tags: + - tower1_tower1 +- pos: [681, 1506] + tags: + - tower1_tower1 +- pos: [722, 1439] + tags: + - tower2_undeterred +- pos: [533, 1375] + tags: + - tower3_tower3 +- pos: [662, 1375] + tags: + - tower3_gallery +- pos: [483, 1307] + tags: + - tower4_room +- pos: [598, 1307] + tags: + - tower4_tower4 +- pos: [598, 1287] + tags: + - tower4_courtyard +- pos: [533, 1245] + tags: + - tower5_welcome +- pos: [419, 1245] + tags: + - tower5_cellar +- pos: [419, 1267] + tags: + - tower5_quadruple +- pos: [203, 1014] + tags: + - tower2_undeterred +- pos: [1325, 1191] + tags: + - tower3_tower3 +- pos: [1700, 1021] + tags: + - tower3_gallery +- pos: [1653, 1318] + tags: + - tower4_room +- pos: [918, 222] + tags: + - tower4_tower4 +- pos: [806, 222] + tags: + - tower4_courtyard +- pos: [652, 951] + tags: + - tower5_welcome +- pos: [1553, 1440] + tags: + - tower5_cellar +- pos: [1459, 1119] + tags: + - tower5_quadruple +- pos: [1216, 1280] + room: Orange Tower Third Floor + door: Red Barrier +- pos: [1173, 1248] + room: Orange Tower Third Floor + door: Rhyme Room Entrance +- pos: [1270, 1231] + paintings: + - arrows_painting_6 + - flower_painting_5 +- pos: [1216, 1216] + sunwarp: + dots: 2 + type: exit +- pos: [1253, 1172] + sunwarp: + dots: 3 + type: enter +- pos: [852, 198] + room: Orange Tower Fourth Floor + door: Hot Crusts Door +- pos: [830, 289] + sunwarp: + dots: 5 + type: enter +- pos: [877, 155] + room: Number Hunt + door: Eights +- pos: [844, 134] + paintings: + - smile_painting_8 + tags: + - smiley_hotcrusts +- pos: [797, 155] + sunwarp: + dots: 2 + type: enter +- pos: [679, 985] + room: Number Hunt + door: Nines +- pos: [723, 953] + room: Orange Tower Fifth Floor + door: Welcome Back +- pos: [683, 944] + paintings: + - east_afar +- pos: [548, 1221] + paintings: + - hi_solved_painting3 +- pos: [1574, 1425] + paintings: + - hi_solved_painting2 +- pos: [411, 1186] + paintings: + - arrows_painting_10 + - owl_painting_3 + - clock_painting + - scenery_painting_5d_2 + - symmetry_painting_b_7 + - panda_painting_2 + - crown_painting2 + - colors_painting2 + - cherry_painting2 + - hi_solved_painting + tags: + - owl_tower6 + - wise_tower6 + - panda_tower6 + - crown_tower6 + - apple_tower6 + - hi_scientific +- pos: [349, 1124] + paintings: + - map_painting2 +- pos: [436, 1159] + room: Orange Tower Seventh Floor + door: Mastery +- pos: [544, 1159] + paintings: + - arrows_painting_11 +- pos: [498, 284] + room: Courtyard + door: Green Barrier +- pos: [556, 233] + paintings: + - flower_painting_7 + tags: + - flower_starting + - flower_arrow +- pos: [600, 332] + room: Number Hunt + door: Nines +- pos: [579, 350] + paintings: + - blueman_painting + tags: + - blueman_courtyard +- pos: [530, 310] + room: First Second Third Fourth + door: Backside Door +- pos: [584, 107] + room: The Colorful (White) + door: Progress Door +- pos: [622, 107] + room: The Colorful (Black) + door: Progress Door +- pos: [659, 107] + room: The Colorful (Red) + door: Progress Door +- pos: [697, 107] + room: The Colorful (Yellow) + door: Progress Door +- pos: [734, 107] + room: The Colorful (Blue) + door: Progress Door +- pos: [772, 107] + room: The Colorful (Purple) + door: Progress Door +- pos: [809, 107] + room: The Colorful (Orange) + door: Progress Door +- pos: [847, 107] + room: The Colorful (Green) + door: Progress Door +- pos: [884, 107] + room: The Colorful (Brown) + door: Progress Door +- pos: [922, 107] + room: The Colorful (Gray) + door: Progress Door +- pos: [967, 107] + paintings: + - arrows_painting_12 +- pos: [878, 954] + room: Welcome Back Area + door: Shortcut to Starting Room +- pos: [773, 954] + tags: + - hub_wb + - wondrous_wb + - undeterred_wb + - agreeable_wb + - wanderer_wb + - observant_wb + - gallery_wb + - scientific_wb + - cellar_wb +- pos: [1107, 749] + tags: + - hub_wb +- pos: [408, 817] + tags: + - wondrous_wb +- pos: [281, 1017] + tags: + - undeterred_wb +- pos: [1017, 289] + tags: + - agreeable_wb +- pos: [907, 1385] + tags: + - wanderer_wb +- pos: [1737, 1053] + tags: + - gallery_wb +- pos: [1690, 268] + tags: + - observant_wb +- pos: [250, 604] + tags: + - scientific_wb +- pos: [1553, 1467] + tags: + - cellar_wb +- pos: [1478, 498] + room: Owl Hallway + door: Shortcut to Hedge Maze +- pos: [1480, 551] + paintings: + - arrows_painting_8 + - maze_painting_2 + - owl_painting_2 + - clock_painting_4 + tags: + - green_owl + - owl_hidden + - owl_tower6 +- pos: [1478, 938] + room: Number Hunt + door: Sevens +- pos: [1580, 853] + room: Number Hunt + door: Sevens +- pos: [1478, 905] + room: Number Hunt + door: Eights +- pos: [1452, 841] + room: Number Hunt + door: Nines +- pos: [1420, 841] + room: Outside The Initiated + door: Blue Barrier +- pos: [1479, 1018] + room: Outside The Initiated + door: Orange Barrier +- pos: [1360, 847] + room: Outside The Initiated + door: Shortcut to Hub Room +- pos: [1511, 841] + room: Outside The Initiated + door: Initiated Entrance +- pos: [1141, 1441] + room: Orange Tower Third Floor + door: Orange Barrier +- pos: [1173, 1441] + room: Outside The Initiated + door: Green Barrier +- pos: [1206, 1441] + room: Outside The Initiated + door: Purple Barrier +- pos: [1189, 1355] + room: Outside The Initiated + door: Entrance +- pos: [1580, 729] + room: Outside The Initiated + door: Eight Door +- pos: [1530, 938] + paintings: + - clock_painting_5 + tags: + - clock_initiated +- pos: [1546, 938] + paintings: + - clock_painting_2 + tags: + - clock_tower6 + - clock_initiated +- pos: [1579, 813] + sunwarp: + dots: 3 + type: exit diff --git a/src/game_data.cpp b/src/game_data.cpp index c98f532..4348967 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -44,6 +44,7 @@ struct GameData { std::vector doors_; std::vector panels_; std::vector map_areas_; + std::vector subway_items_; std::map room_by_id_; std::map door_by_id_; @@ -606,6 +607,56 @@ struct GameData { errstr << "Area data not found for: " << area; TrackerLog(errstr.str()); } + + // Read in subway items. + YAML::Node subway_config = + YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); + for (const auto &subway_it : subway_config) { + SubwayItem subway_item; + subway_item.id = subway_items_.size(); + subway_item.x = subway_it["pos"][0].as(); + subway_item.y = subway_it["pos"][1].as(); + + if (subway_it["door"]) { + subway_item.door = AddOrGetDoor(subway_it["room"].as(), + subway_it["door"].as()); + } + + if (subway_it["paintings"]) { + for (const auto &painting_it : subway_it["paintings"]) { + subway_item.paintings.push_back(painting_it.as()); + } + } + + if (subway_it["tags"]) { + for (const auto &tag_it : subway_it["tags"]) { + subway_item.tags.push_back(tag_it.as()); + } + } + + if (subway_it["sunwarp"]) { + SubwaySunwarp sunwarp; + sunwarp.dots = subway_it["sunwarp"]["dots"].as(); + + std::string sunwarp_type = + subway_it["sunwarp"]["type"].as(); + if (sunwarp_type == "final") { + sunwarp.type = SubwaySunwarpType::kFinal; + } else if (sunwarp_type == "exit") { + sunwarp.type = SubwaySunwarpType::kExit; + } else { + sunwarp.type = SubwaySunwarpType::kEnter; + } + + subway_item.sunwarp = sunwarp; + } + + if (subway_it["special"]) { + subway_item.special = subway_it["special"].as(); + } + + subway_items_.push_back(subway_item); + } } int AddOrGetRoom(std::string room) { @@ -621,8 +672,9 @@ struct GameData { std::string full_name = room + " - " + door; if (!door_by_id_.count(full_name)) { + int door_id = doors_.size(); door_by_id_[full_name] = doors_.size(); - doors_.push_back({.room = AddOrGetRoom(room), .name = door}); + doors_.push_back({.id = door_id, .room = AddOrGetRoom(room), .name = door}); } return door_by_id_[full_name]; @@ -704,3 +756,11 @@ const std::vector &GD_GetSunwarpDoors() { int GD_GetRoomForSunwarp(int index) { return GetState().room_by_sunwarp_.at(index); } + +const std::vector &GD_GetSubwayItems() { + return GetState().subway_items_; +} + +const SubwayItem &GD_GetSubwayItem(int id) { + return GetState().subway_items_.at(id); +} diff --git a/src/game_data.h b/src/game_data.h index cd09627..37d1eb3 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -62,6 +62,7 @@ struct ProgressiveRequirement { }; struct Door { + int id; int room; std::string name; std::string location_name; @@ -118,6 +119,28 @@ struct MapArea { bool hunt = false; }; +enum class SubwaySunwarpType { + kEnter, + kExit, + kFinal +}; + +struct SubwaySunwarp { + int dots; + SubwaySunwarpType type; +}; + +struct SubwayItem { + int id; + int x; + int y; + std::optional door; + std::vector paintings; + std::vector tags; + std::optional sunwarp; + std::optional special; +}; + const std::vector& GD_GetMapAreas(); const MapArea& GD_GetMapArea(int id); int GD_GetRoomByName(const std::string& name); @@ -131,5 +154,7 @@ const std::vector& GD_GetAchievementPanels(); int GD_GetItemIdForColor(LingoColor color); const std::vector& GD_GetSunwarpDoors(); int GD_GetRoomForSunwarp(int index); +const std::vector& GD_GetSubwayItems(); +const SubwayItem& GD_GetSubwayItem(int id); #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/src/subway_map.cpp b/src/subway_map.cpp new file mode 100644 index 0000000..c58b2d1 --- /dev/null +++ b/src/subway_map.cpp @@ -0,0 +1,89 @@ +#include "subway_map.h" + +#include "game_data.h" +#include "global.h" +#include "tracker_state.h" + +constexpr int AREA_ACTUAL_SIZE = 21; + +SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { + map_image_ = + wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG); + if (!map_image_.IsOk()) { + return; + } + + Redraw(); + Resize(); + + Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); + Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); +} + +void SubwayMap::UpdateIndicators() { + Redraw(); + Resize(); +} + +void SubwayMap::OnPaint(wxPaintEvent &event) { + if (GetSize() != resized_.GetSize()) { + Resize(); + } + + wxPaintDC dc(this); + dc.DrawBitmap(resized_, 0, 0); + + event.Skip(); +} + +void SubwayMap::OnMouseMove(wxMouseEvent &event) { + event.Skip(); +} + +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(); + + render_x_ = 0; + render_y_ = 0; + render_width_ = panel_size.GetWidth(); + render_height_ = panel_size.GetHeight(); + + if (image_size.GetWidth() * panel_size.GetHeight() > + panel_size.GetWidth() * image_size.GetHeight()) { + render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / + image_size.GetWidth(); + render_y_ = (panel_size.GetHeight() - render_height_) / 2; + } else { + render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / + image_size.GetHeight(); + render_x_ = (panel_size.GetWidth() - render_width_) / 2; + } + + resized_ = wxBitmap(rendered_.ConvertToImage() + .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR) + .Size(panel_size, {render_x_, render_y_}, 0, 0, 0)); +} 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 */ diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 8a0c764..70fee2d 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -11,6 +11,7 @@ #include "ap_state.h" #include "connection_dialog.h" #include "settings_dialog.h" +#include "subway_map.h" #include "tracker_config.h" #include "tracker_panel.h" #include "version.h" @@ -58,15 +59,20 @@ TrackerFrame::TrackerFrame() Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); - wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); achievements_pane_ = new AchievementsPane(this); + + wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); choicebook->AddPage(achievements_pane_, "Achievements"); - tracker_panel_ = new TrackerPanel(this); + 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"); wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); - top_sizer->Add(tracker_panel_, wxSizerFlags().Expand().Proportion(3)); + top_sizer->Add(rightpane, wxSizerFlags().Expand().Proportion(3)); SetSizerAndFit(top_sizer); SetSize(1280, 728); @@ -165,6 +171,7 @@ void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { void TrackerFrame::OnStateChanged(wxCommandEvent &event) { tracker_panel_->UpdateIndicators(); achievements_pane_->UpdateIndicators(); + subway_map_->UpdateIndicators(); Refresh(); } diff --git a/src/tracker_frame.h b/src/tracker_frame.h index e5bf97e..c7c6772 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h @@ -8,6 +8,7 @@ #endif class AchievementsPane; +class SubwayMap; class TrackerPanel; wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); @@ -35,6 +36,7 @@ class TrackerFrame : public wxFrame { TrackerPanel *tracker_panel_; AchievementsPane *achievements_pane_; + SubwayMap *subway_map_; }; #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 640a159..5588c7f 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -14,6 +14,7 @@ namespace { struct TrackerState { std::map reachability; + std::set reachable_doors; std::mutex reachability_mutex; }; @@ -156,6 +157,11 @@ class StateCalculator { flood_boundary = new_boundary; panel_boundary = new_panel_boundary; } + + // Now that we know the full reachable area, let's make sure all doors are evaluated. + for (const Door& door : GD_GetDoors()) { + int discard = IsDoorReachable(door.id); + } } const std::set& GetReachableRooms() const { return reachable_rooms_; } @@ -422,9 +428,17 @@ void RecalculateReachability() { } } + std::set new_reachable_doors; + for (const auto& [door_id, decision] : state_calculator.GetDoorDecisions()) { + if (decision == kYes) { + new_reachable_doors.insert(door_id); + } + } + { std::lock_guard reachability_guard(GetState().reachability_mutex); std::swap(GetState().reachability, new_reachability); + std::swap(GetState().reachable_doors, new_reachable_doors); } } @@ -437,3 +451,9 @@ bool IsLocationReachable(int location_id) { return false; } } + +bool IsDoorOpen(int door_id) { + std::lock_guard reachability_guard(GetState().reachability_mutex); + + return GetState().reachable_doors.count(door_id); +} diff --git a/src/tracker_state.h b/src/tracker_state.h index e73607f..119b3b5 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h @@ -5,4 +5,6 @@ void RecalculateReachability(); bool IsLocationReachable(int location_id); +bool IsDoorOpen(int door_id); + #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ -- 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') 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') 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 2bab4f7ae28d0d0b836f00e073816bc920e62d55 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 12 May 2024 20:06:05 -0400 Subject: White background for subway map --- src/subway_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 230a256..7d9786d 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -104,7 +104,7 @@ void SubwayMap::Redraw() { rendered_ = wxBitmap( map_image_ .Scale(render_width_, render_height_, wxIMAGE_QUALITY_BILINEAR) - .Size(panel_size, {render_x_, render_y_}, 0, 0, 0)); + .Size(panel_size, {render_x_, render_y_}, 255, 255, 255)); wxMemoryDC dc; dc.SelectObject(rendered_); -- cgit 1.4.1 From 34133b1e330a7d3c2a3e6a6bcd36deb5f95e8f13 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 11:40:58 -0400 Subject: Double buffered painting looks better --- src/area_popup.cpp | 6 +++++- src/subway_map.cpp | 6 +++++- src/tracker_panel.cpp | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 3b5d8d4..6e70315 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -1,5 +1,7 @@ #include "area_popup.h" +#include + #include "ap_state.h" #include "game_data.h" #include "global.h" @@ -8,6 +10,8 @@ AreaPopup::AreaPopup(wxWindow* parent, int area_id) : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { + SetBackgroundStyle(wxBG_STYLE_PAINT); + unchecked_eye_ = wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), wxBITMAP_TYPE_PNG) @@ -108,7 +112,7 @@ void AreaPopup::UpdateIndicators() { } void AreaPopup::OnPaint(wxPaintEvent& event) { - wxPaintDC dc(this); + wxBufferedPaintDC dc(this); PrepareDC(dc); dc.DrawBitmap(rendered_, 0, 0); diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 7d9786d..f857270 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -1,5 +1,7 @@ #include "subway_map.h" +#include + #include "game_data.h" #include "global.h" #include "tracker_state.h" @@ -13,6 +15,8 @@ enum class ItemDrawType { }; SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { + SetBackgroundStyle(wxBG_STYLE_PAINT); + map_image_ = wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG); if (!map_image_.IsOk()) { @@ -47,7 +51,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { Redraw(); } - wxPaintDC dc(this); + wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); event.Skip(); diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 5f9f8ea..66bce81 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -1,5 +1,7 @@ #include "tracker_panel.h" +#include + #include "ap_state.h" #include "area_popup.h" #include "game_data.h" @@ -13,6 +15,8 @@ constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; constexpr int PLAYER_SIZE = 96; TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { + SetBackgroundStyle(wxBG_STYLE_PAINT); + map_image_ = wxImage(GetAbsolutePath("assets/lingo_map.png").c_str(), wxBITMAP_TYPE_PNG); if (!map_image_.IsOk()) { @@ -54,7 +58,7 @@ void TrackerPanel::OnPaint(wxPaintEvent &event) { Redraw(); } - wxPaintDC dc(this); + wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); if (AP_GetPlayerPosition().has_value()) { -- 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') 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 dff1d7382f2af91e50561bfdb9eb83773ac7c010 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 12:07:31 -0400 Subject: Hide vanilla paintings that go nowhere There's some shading stuff for painting shuffle owls but that's not anything at the moment. --- src/subway_map.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index a03f0d8..d331b7d 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -10,6 +10,7 @@ #include "tracker_state.h" constexpr int AREA_ACTUAL_SIZE = 21; +constexpr int OWL_ACTUAL_SIZE = 32; enum class ItemDrawType { kNone, @@ -230,17 +231,10 @@ void SubwayMap::Redraw() { 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; + std::optional shade_color; if (subway_item.door) { draw_type = ItemDrawType::kBox; @@ -255,7 +249,37 @@ void SubwayMap::Redraw() { brush_color = wxRED_BRUSH; } } else if (!subway_item.paintings.empty()) { - draw_type = ItemDrawType::kOwl; + if (AP_IsPaintingShuffle()) { + bool has_checked_painting = false; + bool has_unchecked_painting = false; + bool has_mapped_painting = false; + + for (const std::string &painting_id : subway_item.paintings) { + if (checked_paintings_.count(painting_id)) { + has_checked_painting = true; + + if (AP_GetPaintingMapping().count(painting_id)) { + has_mapped_painting = true; + } + } else { + has_unchecked_painting = true; + } + } + + if (has_unchecked_painting || has_mapped_painting) { + draw_type = ItemDrawType::kOwl; + + if (has_unchecked_painting) { + if (has_checked_painting) { + shade_color = wxColour(255, 255, 0, 100); + } else { + shade_color = wxColour(100, 100, 100, 100); + } + } + } + } else if (!subway_item.tags.empty()) { + draw_type = ItemDrawType::kOwl; + } } int real_area_x = @@ -263,12 +287,23 @@ void SubwayMap::Redraw() { int real_area_y = render_y_ + subway_item.y * render_width_ / image_size.GetWidth(); + int real_area_size = + render_width_ * + (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE) / + image_size.GetWidth(); + if (real_area_size == 0) { + real_area_size = 1; + } + 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) { + 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}); } } -- cgit 1.4.1 From bee4194f9e12c9d2210a5ecba7249bdfe3f3deda Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 12:53:59 -0400 Subject: Switch to wx logging --- src/ap_state.cpp | 43 ++++++++++++++++++++-------------------- src/game_data.cpp | 59 ++++++++++++++++++++----------------------------------- src/logger.cpp | 39 ++++++++++++++++-------------------- src/logger.h | 25 +++++++++++++++++++++-- src/main.cpp | 3 +++ 5 files changed, 85 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index b057beb..e5ff74d 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -75,7 +75,7 @@ struct APState { void Connect(std::string server, std::string player, std::string password) { if (!initialized) { - TrackerLog("Initializing APState..."); + wxLogMessage("Initializing APState..."); std::thread([this]() { for (;;) { @@ -108,10 +108,10 @@ struct APState { } tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); - TrackerLog("Connecting to Archipelago server (" + server + ")..."); + wxLogMessage("Connecting to Archipelago server (%s)...", server); { - TrackerLog("Destroying old AP client..."); + wxLogMessage("Destroying old AP client..."); std::lock_guard client_guard(client_mutex); @@ -155,10 +155,10 @@ struct APState { apclient->set_room_info_handler([this, player, password]() { inventory.clear(); - TrackerLog("Connected to Archipelago server. Authenticating as " + - player + - (password.empty() ? " without password" - : " with password " + password)); + wxLogMessage("Connected to Archipelago server. Authenticating as %s %s", + player, + (password.empty() ? " without password" + : " with password " + password)); tracker_frame->SetStatusMessage( "Connected to Archipelago server. Authenticating..."); @@ -170,7 +170,7 @@ struct APState { [this](const std::list& locations) { for (const int64_t location_id : locations) { checked_locations.insert(location_id); - TrackerLog("Location: " + std::to_string(location_id)); + wxLogMessage("Location: %lld", location_id); } RefreshTracker(false); @@ -179,14 +179,14 @@ struct APState { apclient->set_slot_disconnected_handler([this]() { tracker_frame->SetStatusMessage( "Disconnected from Archipelago. Attempting to reconnect..."); - TrackerLog( + wxLogMessage( "Slot disconnected from Archipelago. Attempting to reconnect..."); }); apclient->set_socket_disconnected_handler([this]() { tracker_frame->SetStatusMessage( "Disconnected from Archipelago. Attempting to reconnect..."); - TrackerLog( + wxLogMessage( "Socket disconnected from Archipelago. Attempting to reconnect..."); }); @@ -194,7 +194,7 @@ struct APState { [this](const std::list& items) { for (const APClient::NetworkItem& item : items) { inventory[item.item]++; - TrackerLog("Item: " + std::to_string(item.item)); + wxLogMessage("Item: %lld", item.item); } RefreshTracker(false); @@ -219,7 +219,7 @@ struct APState { apclient->set_slot_connected_handler([this]( const nlohmann::json& slot_data) { tracker_frame->SetStatusMessage("Connected to Archipelago!"); - TrackerLog("Connected to Archipelago!"); + wxLogMessage("Connected to Archipelago!"); data_storage_prefix = "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; @@ -323,7 +323,7 @@ struct APState { } std::string full_message = hatkirby::implode(error_messages, " "); - TrackerLog(full_message); + wxLogError(wxString(full_message)); wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); }); @@ -342,7 +342,7 @@ struct APState { tracker_frame->SetStatusMessage("Disconnected from Archipelago."); - TrackerLog("Timeout while connecting to Archipelago server."); + wxLogMessage("Timeout while connecting to Archipelago server."); wxMessageBox("Timeout while connecting to Archipelago server.", "Connection failed", wxOK | wxICON_ERROR); } @@ -362,12 +362,11 @@ struct APState { void HandleDataStorage(const std::string& key, const nlohmann::json& value) { if (value.is_boolean()) { data_storage[key] = value.get(); - TrackerLog("Data storage " + key + " retrieved as " + - (value.get() ? "true" : "false")); + wxLogMessage("Data storage %s retrieved as %s", key, + (value.get() ? "true" : "false")); } else if (value.is_number()) { data_storage[key] = value.get(); - TrackerLog("Data storage " + key + " retrieved as " + - std::to_string(value.get())); + wxLogMessage("Data storage %s retrieved as %d", key, value.get()); } else if (value.is_object()) { if (key.ends_with("PlayerPos")) { auto map_value = value.get>(); @@ -376,7 +375,7 @@ struct APState { data_storage[key] = value.get>(); } - TrackerLog("Data storage " + key + " retrieved as dictionary"); + wxLogMessage("Data storage %s retrieved as dictionary", key); } else if (value.is_null()) { if (key.ends_with("PlayerPos")) { player_pos = std::nullopt; @@ -384,7 +383,7 @@ struct APState { data_storage.erase(key); } - TrackerLog("Data storage " + key + " retrieved as null"); + wxLogMessage("Data storage %s retrieved as null", key); } } @@ -408,7 +407,7 @@ struct APState { } void RefreshTracker(bool reset) { - TrackerLog("Refreshing display..."); + wxLogMessage("Refreshing display..."); RecalculateReachability(); @@ -422,7 +421,7 @@ struct APState { int64_t GetItemId(const std::string& item_name) { int64_t ap_id = apclient->get_item_id(item_name); if (ap_id == APClient::INVALID_NAME_ID) { - TrackerLog("Could not find AP item ID for " + item_name); + wxLogError("Could not find AP item ID for %s", item_name); } return ap_id; diff --git a/src/game_data.cpp b/src/game_data.cpp index 74f872c..7bc3134 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -31,9 +31,7 @@ LingoColor GetColorForString(const std::string &str) { } else if (str == "purple") { return LingoColor::kPurple; } else { - std::ostringstream errmsg; - errmsg << "Invalid color: " << str; - TrackerLog(errmsg.str()); + wxLogError("Invalid color: %s", str); return LingoColor::kNone; } @@ -83,9 +81,7 @@ struct GameData { ap_id_by_color_[GetColorForString(input_name)] = ids_config["special_items"][color_name].as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for color " << color_name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for color %s", color_name); } }; @@ -160,8 +156,9 @@ struct GameData { } default: { // This shouldn't happen. - std::cout << "Error reading game data: " << entrance_it - << std::endl; + std::ostringstream formatted; + formatted << entrance_it; + wxLogError("Error reading game data: %s", formatted.str()); break; } } @@ -281,10 +278,8 @@ struct GameData { [panels_[panel_id].name] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP location ID for panel " - << rooms_[room_id].name << " - " << panels_[panel_id].name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP location ID for panel %s - %s", + rooms_[room_id].name, panels_[panel_id].name); } } } @@ -347,10 +342,8 @@ struct GameData { [doors_[door_id].name]["item"] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for door " << rooms_[room_id].name - << " - " << doors_[door_id].name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for door %s - %s", + rooms_[room_id].name, doors_[door_id].name); } } @@ -364,10 +357,8 @@ struct GameData { ids_config["door_groups"][doors_[door_id].group_name] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for door group " - << doors_[door_id].group_name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for door group %s", + doors_[door_id].group_name); } } @@ -377,13 +368,11 @@ struct GameData { } else if (!door_it.second["skip_location"] && !door_it.second["event"]) { if (has_external_panels) { - std::ostringstream errmsg; - errmsg - << rooms_[room_id].name << " - " << doors_[door_id].name - << " has panels from other rooms but does not have an " - "explicit " - "location name and is not marked skip_location or event"; - TrackerLog(errmsg.str()); + wxLogError( + "%s - %s has panels from other rooms but does not have an " + "explicit location name and is not marked skip_location or " + "event", + rooms_[room_id].name, doors_[door_id].name); } doors_[door_id].location_name = @@ -403,10 +392,8 @@ struct GameData { [doors_[door_id].name]["location"] .as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP location ID for door " - << rooms_[room_id].name << " - " << doors_[door_id].name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP location ID for door %s - %s", + rooms_[room_id].name, doors_[door_id].name); } } @@ -472,10 +459,8 @@ struct GameData { progressive_item_id = ids_config["progression"][progressive_item_name].as(); } else { - std::ostringstream errmsg; - errmsg << "Missing AP item ID for progressive item " - << progressive_item_name; - TrackerLog(errmsg.str()); + wxLogError("Missing AP item ID for progressive item %s", + progressive_item_name); } int index = 1; @@ -606,9 +591,7 @@ struct GameData { // Report errors. for (const std::string &area : malconfigured_areas_) { - std::ostringstream errstr; - errstr << "Area data not found for: " << area; - TrackerLog(errstr.str()); + wxLogError("Area data not found for: %s", area); } // Read in subway items. diff --git a/src/logger.cpp b/src/logger.cpp index 4b722c8..dddcc4a 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,32 +1,27 @@ #include "logger.h" -#include -#include -#include - #include "global.h" -namespace { +Logger::Logger() : logfile_(GetAbsolutePath("debug.log")) {} -class Logger { - public: - Logger() : logfile_(GetAbsolutePath("debug.log")) {} +void Logger::Flush() { + wxLog::Flush(); - void LogLine(const std::string& text) { - std::lock_guard guard(file_mutex_); - logfile_ << "[" << std::chrono::system_clock::now() << "] " << text - << std::endl; - logfile_.flush(); - } + std::lock_guard guard(file_mutex_); + logfile_.flush(); +} - private: - std::ofstream logfile_; - std::mutex file_mutex_; -}; +Logger::~Logger() { + std::lock_guard guard(file_mutex_); + logfile_.flush(); +} -} // namespace +void Logger::DoLogText(const wxString& msg) { +#ifdef _WIN64 + OutputDebugStringA(msg.c_str()); + OutputDebugStringA("\r\n"); +#endif -void TrackerLog(const std::string& text) { - static Logger* instance = new Logger(); - instance->LogLine(text); + std::lock_guard guard(file_mutex_); + logfile_ << msg << std::endl; } diff --git a/src/logger.h b/src/logger.h index db9bb49..b1a1d99 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,8 +1,29 @@ #ifndef LOGGER_H_6E7B9594 #define LOGGER_H_6E7B9594 -#include +#include -void TrackerLog(const std::string& text); +#ifndef WX_PRECOMP +#include +#endif + +#include +#include + +class Logger : public wxLog { + public: + Logger(); + + void Flush() override; + + ~Logger(); + + protected: + void DoLogText(const wxString& msg) override; + + private: + std::ofstream logfile_; + std::mutex file_mutex_; +}; #endif /* end of include guard: LOGGER_H_6E7B9594 */ diff --git a/src/main.cpp b/src/main.cpp index fe9aceb..db7653c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,12 +4,15 @@ #include #endif +#include "logger.h" #include "tracker_config.h" #include "tracker_frame.h" class TrackerApp : public wxApp { public: virtual bool OnInit() { + wxLog::SetActiveTarget(new Logger()); + GetTrackerConfig().Load(); TrackerFrame *frame = new TrackerFrame(); -- cgit 1.4.1 From c1de3fe969a686dbe1d17bdd3dfe7e9d4251f17b Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Tue, 14 May 2024 13:02:13 -0400 Subject: Warn on singleton subway tag data --- src/game_data.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src') diff --git a/src/game_data.cpp b/src/game_data.cpp index 7bc3134..8af57e5 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -651,6 +651,20 @@ struct GameData { subway_items_.push_back(subway_item); } + + // Find singleton subway tags. + std::map> subway_tags; + for (const SubwayItem &subway_item : subway_items_) { + for (const std::string &tag : subway_item.tags) { + subway_tags[tag].insert(subway_item.id); + } + } + + for (const auto &[tag, items] : subway_tags) { + if (items.size() == 1) { + wxLogWarning("Singleton subway item tag: %s", tag); + } + } } int AddOrGetRoom(std::string room) { -- cgit 1.4.1 From a5a6c1b8b902c960f8204f8d814ce579dfd5fa50 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 11:09:23 -0400 Subject: Hovering over a connection only shows nearest hops --- src/network_set.cpp | 58 +++++++++-------------------------------------------- src/network_set.h | 7 ++----- src/subway_map.cpp | 3 +-- 3 files changed, 13 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/network_set.cpp b/src/network_set.cpp index 3238dcd..6d2a098 100644 --- a/src/network_set.cpp +++ b/src/network_set.cpp @@ -1,68 +1,30 @@ #include "network_set.h" void NetworkSet::Clear() { - networks_.clear(); network_by_item_.clear(); } -int NetworkSet::AddLink(int id1, int id2) { +void 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; - } + if (!network_by_item_.count(id1)) { + network_by_item_[id1] = {}; } + if (!network_by_item_.count(id2)) { + network_by_item_[id2] = {}; + } + + network_by_item_[id1].insert({id1, id2}); + network_by_item_[id2].insert({id1, id2}); } 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); + return network_by_item_.at(id); } diff --git a/src/network_set.h b/src/network_set.h index e0ef043..e6f0c07 100644 --- a/src/network_set.h +++ b/src/network_set.h @@ -11,18 +11,15 @@ class NetworkSet { public: void Clear(); - int AddLink(int id1, int id2); + void 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_; + 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 d331b7d..99638aa 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -112,9 +112,8 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { 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)) { + networks_.GetNetworkGraph(*hovered_item_)) { const SubwayItem &item1 = GD_GetSubwayItem(item_id1); const SubwayItem &item2 = GD_GetSubwayItem(item_id2); -- 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') 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 0c7cb22f3da56fde00f981f9fefb971c541bc8f1 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 13:19:34 -0400 Subject: Logging changes --- CMakeLists.txt | 1 - src/ap_state.cpp | 56 +++++++++++++++++++++++++------------------------------ src/game_data.cpp | 7 ++++++- src/logger.cpp | 27 --------------------------- src/logger.h | 29 ---------------------------- src/main.cpp | 14 ++++++++++++-- 6 files changed, 43 insertions(+), 91 deletions(-) delete mode 100644 src/logger.cpp delete mode 100644 src/logger.h (limited to 'src') diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dc2f4c..cd62c55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,6 @@ add_executable(lingo_ap_tracker "src/connection_dialog.cpp" "src/tracker_state.cpp" "src/tracker_config.cpp" - "src/logger.cpp" "src/achievements_pane.cpp" "src/settings_dialog.cpp" "src/global.cpp" diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 68a6902..4a15db0 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -21,7 +21,6 @@ #include #include "game_data.h" -#include "logger.h" #include "tracker_frame.h" #include "tracker_state.h" @@ -75,7 +74,7 @@ struct APState { void Connect(std::string server, std::string player, std::string password) { if (!initialized) { - wxLogMessage("Initializing APState..."); + wxLogVerbose("Initializing APState..."); std::thread([this]() { for (;;) { @@ -108,11 +107,10 @@ struct APState { initialized = true; } - tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); - wxLogMessage("Connecting to Archipelago server (%s)...", server); + wxLogStatus("Connecting to Archipelago server (%s)...", server); { - wxLogMessage("Destroying old AP client..."); + wxLogVerbose("Destroying old AP client..."); std::lock_guard client_guard(client_mutex); @@ -156,12 +154,11 @@ struct APState { apclient->set_room_info_handler([this, player, password]() { inventory.clear(); - wxLogMessage("Connected to Archipelago server. Authenticating as %s %s", - player, - (password.empty() ? " without password" - : " with password " + password)); - tracker_frame->SetStatusMessage( + wxLogStatus( "Connected to Archipelago server. Authenticating..."); + wxLogVerbose("Authenticating as %s %s", player, + (password.empty() ? "without password" + : "with password " + password)); apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, {AP_MAJOR, AP_MINOR, AP_REVISION}); @@ -171,23 +168,19 @@ struct APState { [this](const std::list& locations) { for (const int64_t location_id : locations) { checked_locations.insert(location_id); - wxLogMessage("Location: %lld", location_id); + wxLogVerbose("Location: %lld", location_id); } RefreshTracker(false); }); apclient->set_slot_disconnected_handler([this]() { - tracker_frame->SetStatusMessage( - "Disconnected from Archipelago. Attempting to reconnect..."); - wxLogMessage( + wxLogStatus( "Slot disconnected from Archipelago. Attempting to reconnect..."); }); apclient->set_socket_disconnected_handler([this]() { - tracker_frame->SetStatusMessage( - "Disconnected from Archipelago. Attempting to reconnect..."); - wxLogMessage( + wxLogStatus( "Socket disconnected from Archipelago. Attempting to reconnect..."); }); @@ -195,7 +188,7 @@ struct APState { [this](const std::list& items) { for (const APClient::NetworkItem& item : items) { inventory[item.item]++; - wxLogMessage("Item: %lld", item.item); + wxLogVerbose("Item: %lld", item.item); } RefreshTracker(false); @@ -219,8 +212,7 @@ struct APState { apclient->set_slot_connected_handler([this]( const nlohmann::json& slot_data) { - tracker_frame->SetStatusMessage("Connected to Archipelago!"); - wxLogMessage("Connected to Archipelago!"); + wxLogStatus("Connected to Archipelago!"); data_storage_prefix = "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; @@ -341,9 +333,7 @@ struct APState { DestroyClient(); - tracker_frame->SetStatusMessage("Disconnected from Archipelago."); - - wxLogMessage("Timeout while connecting to Archipelago server."); + wxLogStatus("Timeout while connecting to Archipelago server."); wxMessageBox("Timeout while connecting to Archipelago server.", "Connection failed", wxOK | wxICON_ERROR); } @@ -363,11 +353,11 @@ struct APState { void HandleDataStorage(const std::string& key, const nlohmann::json& value) { if (value.is_boolean()) { data_storage[key] = value.get(); - wxLogMessage("Data storage %s retrieved as %s", key, + wxLogVerbose("Data storage %s retrieved as %s", key, (value.get() ? "true" : "false")); } else if (value.is_number()) { data_storage[key] = value.get(); - wxLogMessage("Data storage %s retrieved as %d", key, value.get()); + wxLogVerbose("Data storage %s retrieved as %d", key, value.get()); } else if (value.is_object()) { if (key.ends_with("PlayerPos")) { auto map_value = value.get>(); @@ -376,7 +366,7 @@ struct APState { data_storage[key] = value.get>(); } - wxLogMessage("Data storage %s retrieved as dictionary", key); + wxLogVerbose("Data storage %s retrieved as dictionary", key); } else if (value.is_null()) { if (key.ends_with("PlayerPos")) { player_pos = std::nullopt; @@ -384,15 +374,19 @@ struct APState { data_storage.erase(key); } - wxLogMessage("Data storage %s retrieved as null", key); + wxLogVerbose("Data storage %s retrieved as null", key); } else if (value.is_array()) { + auto list_value = value.get>(); + if (key.ends_with("Paintings")) { - data_storage[key] = value.get>(); + data_storage[key] = + std::set(list_value.begin(), list_value.end()); } else { - data_storage[key] = value.get>(); + data_storage[key] = list_value; } - wxLogMessage("Data storage %s retrieved as list", key); + wxLogVerbose("Data storage %s retrieved as list: [%s]", key, + hatkirby::implode(list_value, ", ")); } } @@ -425,7 +419,7 @@ struct APState { } void RefreshTracker(bool reset) { - wxLogMessage("Refreshing display..."); + wxLogVerbose("Refreshing display..."); RecalculateReachability(); diff --git a/src/game_data.cpp b/src/game_data.cpp index 8af57e5..de47ba4 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -1,5 +1,11 @@ #include "game_data.h" +#include + +#ifndef WX_PRECOMP +#include +#endif + #include #include @@ -7,7 +13,6 @@ #include #include "global.h" -#include "logger.h" namespace { diff --git a/src/logger.cpp b/src/logger.cpp deleted file mode 100644 index dddcc4a..0000000 --- a/src/logger.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "logger.h" - -#include "global.h" - -Logger::Logger() : logfile_(GetAbsolutePath("debug.log")) {} - -void Logger::Flush() { - wxLog::Flush(); - - std::lock_guard guard(file_mutex_); - logfile_.flush(); -} - -Logger::~Logger() { - std::lock_guard guard(file_mutex_); - logfile_.flush(); -} - -void Logger::DoLogText(const wxString& msg) { -#ifdef _WIN64 - OutputDebugStringA(msg.c_str()); - OutputDebugStringA("\r\n"); -#endif - - std::lock_guard guard(file_mutex_); - logfile_ << msg << std::endl; -} diff --git a/src/logger.h b/src/logger.h deleted file mode 100644 index b1a1d99..0000000 --- a/src/logger.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef LOGGER_H_6E7B9594 -#define LOGGER_H_6E7B9594 - -#include - -#ifndef WX_PRECOMP -#include -#endif - -#include -#include - -class Logger : public wxLog { - public: - Logger(); - - void Flush() override; - - ~Logger(); - - protected: - void DoLogText(const wxString& msg) override; - - private: - std::ofstream logfile_; - std::mutex file_mutex_; -}; - -#endif /* end of include guard: LOGGER_H_6E7B9594 */ diff --git a/src/main.cpp b/src/main.cpp index db7653c..5b036ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,14 +4,24 @@ #include #endif -#include "logger.h" +#include + +#include "global.h" #include "tracker_config.h" #include "tracker_frame.h" +static std::ofstream* logfile; + class TrackerApp : public wxApp { public: virtual bool OnInit() { - wxLog::SetActiveTarget(new Logger()); + logfile = new std::ofstream(GetAbsolutePath("debug.log")); + wxLog::SetActiveTarget(new wxLogStream(logfile)); + +#ifndef NDEBUG + wxLog::SetVerbose(true); + wxLog::SetActiveTarget(new wxLogWindow(nullptr, "Debug Log")); +#endif GetTrackerConfig().Load(); -- cgit 1.4.1 From 636d23430bddf8295afeb55381db8d1f65cf4b38 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 13:27:33 -0400 Subject: Fixed art gallery painting --- assets/subway.yaml | 3 ++- src/game_data.cpp | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/assets/subway.yaml b/assets/subway.yaml index 704f120..8f13f09 100644 --- a/assets/subway.yaml +++ b/assets/subway.yaml @@ -688,7 +688,7 @@ - blueman_starting - pos: [60, 970] special: early_color_hallways -- pos: [402, 1071] +- pos: [402, 1012] room: Outside The Undeterred door: Green Painting paintings: @@ -975,6 +975,7 @@ paintings: - smile_painting_3 - flower_painting_2 + - scenery_painting_0a - map_painting - fruitbowl_painting4 tags: diff --git a/src/game_data.cpp b/src/game_data.cpp index de47ba4..7c9564b 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -783,6 +783,11 @@ const SubwayItem &GD_GetSubwayItem(int id) { } int GD_GetSubwayItemForPainting(const std::string& painting_id) { +#ifndef NDEBUG + if (!GetState().subway_item_by_painting_.count(painting_id)) { + wxLogError("No subway item for painting %s", painting_id); + } +#endif return GetState().subway_item_by_painting_.at(painting_id); } -- cgit 1.4.1 From b83d91790f0fe57a88373f7a6f7cd8dd037342be Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 13:31:37 -0400 Subject: Draw a straight line when the ellipse would be invisible --- src/subway_map.cpp | 71 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 460532c..6070fd5 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -135,47 +135,54 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { int halfwidth = right - left; int halfheight = bottom - top; - int ellipse_x; - int ellipse_y; - double start; - double end; + 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; - if (item1_x > item2_x) { - ellipse_y = top; + if (item1_x > item2_x) { + ellipse_y = top; - if (item1_y > item2_y) { - ellipse_x = left - halfwidth; + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; - start = 0; - end = 90; - } else { - ellipse_x = left; + start = 0; + end = 90; + } else { + ellipse_x = left; - start = 90; - end = 180; - } - } else { - ellipse_y = top - halfheight; + start = 90; + end = 180; + } + } else { + ellipse_y = top - halfheight; - if (item1_y > item2_y) { - ellipse_x = left - halfwidth; + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; - start = 270; - end = 360; - } else { - ellipse_x = left; + start = 270; + end = 360; + } else { + ellipse_x = left; - start = 180; - end = 270; + 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); + } } } -- cgit 1.4.1 From 249817743c12b453338c6d0a355180bf5084c73c Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 16 May 2024 09:41:02 -0400 Subject: Added status bar stuff back --- src/ap_state.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 4a15db0..f245c2b 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -107,6 +107,7 @@ struct APState { initialized = true; } + tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); wxLogStatus("Connecting to Archipelago server (%s)...", server); { @@ -154,11 +155,12 @@ struct APState { apclient->set_room_info_handler([this, player, password]() { inventory.clear(); - wxLogStatus( + wxLogStatus("Connected to Archipelago server. Authenticating as %s %s", + player, + (password.empty() ? "without password" + : "with password " + password)); + tracker_frame->SetStatusMessage( "Connected to Archipelago server. Authenticating..."); - wxLogVerbose("Authenticating as %s %s", player, - (password.empty() ? "without password" - : "with password " + password)); apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, {AP_MAJOR, AP_MINOR, AP_REVISION}); @@ -175,11 +177,15 @@ struct APState { }); apclient->set_slot_disconnected_handler([this]() { + tracker_frame->SetStatusMessage( + "Disconnected from Archipelago. Attempting to reconnect..."); wxLogStatus( "Slot disconnected from Archipelago. Attempting to reconnect..."); }); apclient->set_socket_disconnected_handler([this]() { + tracker_frame->SetStatusMessage( + "Disconnected from Archipelago. Attempting to reconnect..."); wxLogStatus( "Socket disconnected from Archipelago. Attempting to reconnect..."); }); @@ -212,6 +218,7 @@ struct APState { apclient->set_slot_connected_handler([this]( const nlohmann::json& slot_data) { + tracker_frame->SetStatusMessage("Connected to Archipelago!"); wxLogStatus("Connected to Archipelago!"); data_storage_prefix = @@ -333,6 +340,7 @@ struct APState { DestroyClient(); + tracker_frame->SetStatusMessage("Disconnected from Archipelago."); wxLogStatus("Timeout while connecting to Archipelago server."); wxMessageBox("Timeout while connecting to Archipelago server.", "Connection failed", wxOK | wxICON_ERROR); -- 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') 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') 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 71124d81e8a254360c12d3c9a2a687c77b864132 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 18 May 2024 15:34:45 -0400 Subject: Don't crash on subway map before connection --- src/tracker_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index ccb3fbd..869f837 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -601,5 +601,5 @@ bool IsDoorOpen(int door_id) { const std::map& GetDoorRequirements(int door_id) { std::lock_guard reachability_guard(GetState().reachability_mutex); - return GetState().door_reports.at(door_id); + return GetState().door_reports[door_id]; } -- cgit 1.4.1 From 4e95da4dbb105042bbaf41b7384c73506c86a61e Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 18 May 2024 15:34:52 -0400 Subject: Log fatal exceptions --- src/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 5b036ea..abe6626 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,16 @@ class TrackerApp : public wxApp { frame->Show(true); return true; } + + bool OnExceptionInMainLoop() override { + try { + throw; + } catch (const std::exception& ex) { + wxLogError(ex.what()); + } + + return false; + } }; wxIMPLEMENT_APP(TrackerApp); -- 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') 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 8980bf2a124f647dbdece9f90b86b2f37c4f3fa5 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 19 May 2024 11:21:24 -0400 Subject: Faster subway map rendering Image quality suffered a bit though. --- src/subway_map.cpp | 103 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index b02c616..51f17c5 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -117,13 +117,77 @@ void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, void SubwayMap::OnPaint(wxPaintEvent &event) { if (GetSize() != rendered_.GetSize()) { - Redraw(); + wxSize panel_size = GetSize(); + wxSize image_size = map_image_.GetSize(); + + render_x_ = 0; + render_y_ = 0; + render_width_ = panel_size.GetWidth(); + render_height_ = panel_size.GetHeight(); + + if (image_size.GetWidth() * panel_size.GetHeight() > + panel_size.GetWidth() * image_size.GetHeight()) { + render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / + image_size.GetWidth(); + render_y_ = (panel_size.GetHeight() - render_height_) / 2; + } else { + render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / + image_size.GetHeight(); + render_x_ = (panel_size.GetWidth() - render_width_) / 2; + } + + SetZoomPos({zoom_x_, zoom_y_}); } wxBufferedPaintDC dc(this); dc.SetBackground(*wxWHITE_BRUSH); dc.Clear(); - dc.DrawBitmap(rendered_, zoom_x_, zoom_y_); + + { + wxMemoryDC rendered_dc; + rendered_dc.SelectObject(rendered_); + + int dst_x; + int dst_y; + int dst_w; + int dst_h; + int src_x; + int src_y; + int src_w; + int src_h; + + int zoomed_width = render_width_ * zoom_; + int zoomed_height = render_height_ * zoom_; + + if (zoomed_width <= GetSize().GetWidth()) { + dst_x = (GetSize().GetWidth() - zoomed_width) / 2; + dst_w = zoomed_width; + src_x = 0; + src_w = map_image_.GetWidth(); + } else { + dst_x = 0; + dst_w = GetSize().GetWidth(); + src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_; + src_w = + GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_; + } + + if (zoomed_height <= GetSize().GetHeight()) { + dst_y = (GetSize().GetHeight() - zoomed_height) / 2; + dst_h = zoomed_height; + src_y = 0; + src_h = map_image_.GetHeight(); + } else { + dst_y = 0; + dst_h = GetSize().GetHeight(); + src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_; + src_h = + GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; + } + + dc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, + src_w, src_h); + } if (hovered_item_) { const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); @@ -320,7 +384,6 @@ void SubwayMap::OnMouseScroll(wxMouseEvent &event) { wxPoint virtual_pos = MapPosToVirtualPos(map_pos); SetZoomPos(-(virtual_pos - event.GetPosition())); - Redraw(); Refresh(); } @@ -335,29 +398,7 @@ void SubwayMap::OnTimer(wxTimerEvent &event) { } void SubwayMap::Redraw() { - wxSize panel_size = GetSize(); - wxSize image_size = map_image_.GetSize(); - - render_x_ = 0; - render_y_ = 0; - render_width_ = panel_size.GetWidth(); - render_height_ = panel_size.GetHeight(); - - if (image_size.GetWidth() * panel_size.GetHeight() > - panel_size.GetWidth() * image_size.GetHeight()) { - render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / - image_size.GetWidth(); - render_y_ = (panel_size.GetHeight() - render_height_) / 2; - } else { - render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / - image_size.GetHeight(); - render_x_ = (panel_size.GetWidth() - render_width_) / 2; - } - - rendered_ = wxBitmap(map_image_.Scale( - render_width_ * zoom_, render_height_ * zoom_, wxIMAGE_QUALITY_BILINEAR)); - - SetZoomPos({zoom_x_, zoom_y_}); + rendered_ = wxBitmap(map_image_); wxMemoryDC dc; dc.SelectObject(rendered_); @@ -413,15 +454,9 @@ void SubwayMap::Redraw() { } } - wxPoint real_area_pos = MapPosToVirtualPos({subway_item.x, subway_item.y}); + wxPoint real_area_pos = {subway_item.x, subway_item.y}; - int real_area_size = - render_width_ * zoom_ * - (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE) / - image_size.GetWidth(); - if (real_area_size == 0) { - real_area_size = 1; - } + 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)); -- cgit 1.4.1 From e50107d0339000c48a955caab8b56d0d4fc56e84 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 19 May 2024 11:26:24 -0400 Subject: Higher quality while still fast --- src/subway_map.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 51f17c5..47ebfdf 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -1,6 +1,7 @@ #include "subway_map.h" #include +#include #include @@ -185,8 +186,10 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; } - dc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, - src_w, src_h); + wxGCDC gcdc(dc); + gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD); + gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, + src_w, src_h); } if (hovered_item_) { -- 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') 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') 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 d3e2d9518403eb89eb150fa2158966c3483d5339 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sun, 19 May 2024 12:38:34 -0400 Subject: Swap checked/unchecked eye for subway map --- src/subway_map.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 69bf51b..9175514 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -257,7 +257,7 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { int cur_height = 10; for (const auto &[text, obtained] : report) { - wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_; + wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_; dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); -- cgit 1.4.1 From 13d2a129f6972e6e752da9c9cb686a63d5550517 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 29 May 2024 12:56:29 -0400 Subject: Show unchecked paintings --- src/ap_state.cpp | 12 ++++++++++++ src/ap_state.h | 2 ++ src/area_popup.cpp | 32 ++++++++++++++++++++++++++++++++ src/game_data.cpp | 33 ++++++++++++++++++++++++++------- src/game_data.h | 1 + src/tracker_panel.cpp | 14 ++++++++++++++ src/tracker_state.cpp | 32 +++++++++++++++++--------------- 7 files changed, 104 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 20245e5..0c75eae 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -427,6 +427,14 @@ struct APState { return std::any_cast&>(data_storage.at(key)); } + bool IsPaintingChecked(const std::string& painting_id) { + const auto& checked_paintings = GetCheckedPaintings(); + + return checked_paintings.count(painting_id) || + (painting_mapping.count(painting_id) && + checked_paintings.count(painting_mapping.at(painting_id))); + } + void RefreshTracker(bool reset) { wxLogVerbose("Refreshing display..."); @@ -506,6 +514,10 @@ const std::set& AP_GetCheckedPaintings() { return GetState().GetCheckedPaintings(); } +bool AP_IsPaintingChecked(const std::string& painting_id) { + return GetState().IsPaintingChecked(painting_id); +} + int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } diff --git a/src/ap_state.h b/src/ap_state.h index 0ae6a25..2769bb8 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -61,6 +61,8 @@ const std::map& AP_GetPaintingMapping(); const std::set& AP_GetCheckedPaintings(); +bool AP_IsPaintingChecked(const std::string& painting_id); + int AP_GetMasteryRequirement(); int AP_GetLevel2Requirement(); diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 6e70315..b5c1ccb 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -65,6 +65,18 @@ void AreaPopup::UpdateIndicators() { } } + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& painting : map_area.paintings) { + wxSize item_extent = mem_dc.GetTextExtent(painting.id); + int item_height = std::max(32, item_extent.GetHeight()) + 10; + acc_height += item_height; + + if (item_extent.GetWidth() > col_width) { + col_width = item_extent.GetWidth(); + } + } + } + int item_width = col_width + 10 + 32; int full_width = std::max(header_extent.GetWidth(), item_width) + 20; @@ -109,6 +121,26 @@ void AreaPopup::UpdateIndicators() { cur_height += 10 + 32; } + + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& painting : map_area.paintings) { + bool checked = AP_IsPaintingChecked(painting.id); + wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; + + mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); + + bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + const wxColour* text_color = reachable ? wxWHITE : wxRED; + mem_dc.SetTextForeground(*text_color); + + wxSize item_extent = mem_dc.GetTextExtent(painting.id); + mem_dc.DrawText(painting.id, + {10 + 32 + 10, + cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); + + cur_height += 10 + 32; + } + } } void AreaPopup::OnPaint(wxPaintEvent& event) { diff --git a/src/game_data.cpp b/src/game_data.cpp index 7c9564b..4dd69e2 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -284,7 +284,7 @@ struct GameData { .as(); } else { wxLogError("Missing AP location ID for panel %s - %s", - rooms_[room_id].name, panels_[panel_id].name); + rooms_[room_id].name, panels_[panel_id].name); } } } @@ -348,7 +348,7 @@ struct GameData { .as(); } else { wxLogError("Missing AP item ID for door %s - %s", - rooms_[room_id].name, doors_[door_id].name); + rooms_[room_id].name, doors_[door_id].name); } } @@ -422,7 +422,8 @@ struct GameData { std::string painting_id = painting["id"].as(); room_by_painting_[painting_id] = room_id; - if (!painting["exit_only"] || !painting["exit_only"].as()) { + if ((!painting["exit_only"] || !painting["exit_only"].as()) && + (!painting["disable"] || !painting["disable"].as())) { PaintingExit painting_exit; painting_exit.id = painting_id; @@ -594,11 +595,28 @@ struct GameData { } } + for (const Room &room : rooms_) { + std::string area_name = room.name; + if (fold_areas.count(room.name)) { + int fold_area_id = fold_areas[room.name]; + area_name = map_areas_[fold_area_id].name; + } + + if (!room.paintings.empty()) { + int area_id = AddOrGetArea(area_name); + MapArea &map_area = map_areas_[area_id]; + + for (const PaintingExit &painting : room.paintings) { + map_area.paintings.push_back(painting); + } + } + } + // Report errors. for (const std::string &area : malconfigured_areas_) { wxLogError("Area data not found for: %s", area); } - + // Read in subway items. YAML::Node subway_config = YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); @@ -687,7 +705,8 @@ struct GameData { if (!door_by_id_.count(full_name)) { int door_id = doors_.size(); door_by_id_[full_name] = doors_.size(); - doors_.push_back({.id = door_id, .room = AddOrGetRoom(room), .name = door}); + doors_.push_back( + {.id = door_id, .room = AddOrGetRoom(room), .name = door}); } return door_by_id_[full_name]; @@ -728,7 +747,7 @@ GameData &GetState() { } // namespace -bool SubwaySunwarp::operator<(const SubwaySunwarp& rhs) const { +bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); } @@ -782,7 +801,7 @@ const SubwayItem &GD_GetSubwayItem(int id) { return GetState().subway_items_.at(id); } -int GD_GetSubwayItemForPainting(const std::string& painting_id) { +int GD_GetSubwayItemForPainting(const std::string &painting_id) { #ifndef NDEBUG if (!GetState().subway_item_by_painting_.count(painting_id)) { wxLogError("No subway item for painting %s", painting_id); diff --git a/src/game_data.h b/src/game_data.h index 3afaec3..68ba5e4 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -113,6 +113,7 @@ struct MapArea { int id; std::string name; std::vector locations; + std::vector paintings; int map_x; int map_y; int classification = 0; diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 66bce81..0385f89 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -171,6 +171,20 @@ void TrackerPanel::Redraw() { } } + if (AP_IsPaintingShuffle()) { + for (const PaintingExit &painting : map_area.paintings) { + if (!AP_IsPaintingChecked(painting.id)) { + bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + + if (reachable) { + has_reachable_unchecked = true; + } else { + has_unreachable_unchecked = true; + } + } + } + } + int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * final_width / image_size.GetWidth(); int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 869f837..faf74cc 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -15,11 +15,11 @@ namespace { struct Requirements { bool disabled = false; - std::set doors; // non-grouped, handles progressive - std::set items; // all other items - std::set rooms; // maybe - bool mastery = false; // maybe - bool panel_hunt = false; // maybe + std::set doors; // non-grouped, handles progressive + std::set items; // all other items + std::set rooms; // maybe + bool mastery = false; // maybe + bool panel_hunt = false; // maybe void Merge(const Requirements& rhs) { if (rhs.disabled) { @@ -228,7 +228,8 @@ class StateCalculator { if (AP_IsPaintingShuffle()) { for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id)) { + if (AP_GetPaintingMapping().count(out_edge.id) && + AP_GetCheckedPaintings().count(out_edge.id)) { Exit painting_exit; painting_exit.destination_room = GD_GetRoomForPainting( AP_GetPaintingMapping().at(out_edge.id)); @@ -286,7 +287,8 @@ class StateCalculator { panel_boundary = new_panel_boundary; } - // Now that we know the full reachable area, let's make sure all doors are evaluated. + // Now that we know the full reachable area, let's make sure all doors are + // evaluated. for (const Door& door : GD_GetDoors()) { int discard = IsDoorReachable(door.id); } @@ -320,7 +322,8 @@ class StateCalculator { return has_item ? kYes : kNo; } - Decision AreRequirementsSatisfied(const Requirements& reqs, std::map* report = nullptr) { + Decision AreRequirementsSatisfied( + const Requirements& reqs, std::map* report = nullptr) { if (reqs.disabled) { return kNo; } @@ -339,7 +342,7 @@ class StateCalculator { final_decision = decision; } } - + for (int item_id : reqs.items) { bool has_item = AP_HasItem(item_id); if (report) { @@ -353,10 +356,9 @@ class StateCalculator { for (int room_id : reqs.rooms) { bool reachable = reachable_rooms_.count(room_id); - + if (report) { - std::string report_name = - "Reach \"" + GD_GetRoom(room_id).name + "\""; + std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\""; (*report)[report_name] = reachable; } @@ -364,7 +366,7 @@ class StateCalculator { final_decision = kMaybe; } } - + if (reqs.mastery) { int achievements_accessible = 0; @@ -407,7 +409,7 @@ class StateCalculator { std::to_string(AP_GetLevel2Requirement()) + " Panels"; (*report)[report_name] = can_level2; } - + if (can_level2 && final_decision != kNo) { final_decision = kMaybe; } @@ -422,7 +424,7 @@ class StateCalculator { } else { door_report_[door_id] = {}; } - + return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), &door_report_[door_id]); } -- cgit 1.4.1 From 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') 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 78ac9905e222c26758e95b098d2e3a3e74a48839 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 13:52:11 -0400 Subject: Fixed LEVEL 2 and THE MASTER calculation --- src/tracker_state.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index faf74cc..187a4a8 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -386,7 +386,7 @@ class StateCalculator { (*report)["Mastery"] = can_mastery; } - if (can_mastery && final_decision != kNo) { + if (!can_mastery && final_decision != kNo) { final_decision = kMaybe; } } @@ -410,7 +410,7 @@ class StateCalculator { (*report)[report_name] = can_level2; } - if (can_level2 && final_decision != kNo) { + if (!can_level2 && final_decision != kNo) { final_decision = kMaybe; } } -- cgit 1.4.1 From 67a2efe7be6f4872adca8d944ebf403046472a98 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 13:53:20 -0400 Subject: Proper painting reachability detection --- src/area_popup.cpp | 16 ++++++------ src/game_data.cpp | 34 +++++++++++++++++++------- src/game_data.h | 11 ++++++--- src/tracker_panel.cpp | 7 +++--- src/tracker_state.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++--------- src/tracker_state.h | 2 ++ 6 files changed, 103 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/area_popup.cpp b/src/area_popup.cpp index b5c1ccb..58d8897 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -66,8 +66,9 @@ void AreaPopup::UpdateIndicators() { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit& painting : map_area.paintings) { - wxSize item_extent = mem_dc.GetTextExtent(painting.id); + for (int painting_id : map_area.paintings) { + const PaintingExit& painting = GD_GetPaintingExit(painting_id); + wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. int item_height = std::max(32, item_extent.GetHeight()) + 10; acc_height += item_height; @@ -123,18 +124,19 @@ void AreaPopup::UpdateIndicators() { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit& painting : map_area.paintings) { - bool checked = AP_IsPaintingChecked(painting.id); + for (int painting_id : map_area.paintings) { + const PaintingExit& painting = GD_GetPaintingExit(painting_id); + bool checked = AP_IsPaintingChecked(painting.internal_id); wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); - bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + bool reachable = IsPaintingReachable(painting_id); const wxColour* text_color = reachable ? wxWHITE : wxRED; mem_dc.SetTextForeground(*text_color); - wxSize item_extent = mem_dc.GetTextExtent(painting.id); - mem_dc.DrawText(painting.id, + wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. + mem_dc.DrawText(painting.internal_id, {10 + 32 + 10, cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); diff --git a/src/game_data.cpp b/src/game_data.cpp index 4dd69e2..71b8629 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -48,11 +48,13 @@ struct GameData { std::vector panels_; std::vector map_areas_; std::vector subway_items_; + std::vector paintings_; std::map room_by_id_; std::map door_by_id_; std::map panel_by_id_; std::map area_by_id_; + std::map painting_by_id_; std::vector door_definition_order_; @@ -419,13 +421,13 @@ struct GameData { if (room_it.second["paintings"]) { for (const auto &painting : room_it.second["paintings"]) { - std::string painting_id = painting["id"].as(); - room_by_painting_[painting_id] = room_id; + std::string internal_id = painting["id"].as(); if ((!painting["exit_only"] || !painting["exit_only"].as()) && (!painting["disable"] || !painting["disable"].as())) { - PaintingExit painting_exit; - painting_exit.id = painting_id; + int painting_id = AddOrGetPainting(internal_id); + PaintingExit &painting_exit = paintings_[painting_id]; + painting_exit.room = room_id; if (painting["required_door"]) { std::string rd_room = rooms_[room_id].name; @@ -437,7 +439,7 @@ struct GameData { rd_room, painting["required_door"]["door"].as()); } - rooms_[room_id].paintings.push_back(painting_exit); + rooms_[room_id].paintings.push_back(painting_exit.id); } } } @@ -606,8 +608,8 @@ struct GameData { int area_id = AddOrGetArea(area_name); MapArea &map_area = map_areas_[area_id]; - for (const PaintingExit &painting : room.paintings) { - map_area.paintings.push_back(painting); + for (int painting_id : room.paintings) { + map_area.paintings.push_back(painting_id); } } } @@ -738,6 +740,16 @@ struct GameData { return area_by_id_[area]; } + + int AddOrGetPainting(std::string internal_id) { + if (!painting_by_id_.count(internal_id)) { + int painting_id = paintings_.size(); + painting_by_id_[internal_id] = painting_id; + paintings_.push_back({.id = painting_id, .internal_id = internal_id}); + } + + return painting_by_id_[internal_id]; + } }; GameData &GetState() { @@ -773,8 +785,12 @@ const Panel &GD_GetPanel(int panel_id) { return GetState().panels_.at(panel_id); } -int GD_GetRoomForPainting(const std::string &painting_id) { - return GetState().room_by_painting_.at(painting_id); +const PaintingExit &GD_GetPaintingExit(int painting_id) { + return GetState().paintings_.at(painting_id); +} + +int GD_GetPaintingByName(const std::string &name) { + return GetState().painting_by_id_.at(name); } const std::vector &GD_GetAchievementPanels() { diff --git a/src/game_data.h b/src/game_data.h index 68ba5e4..e0942f7 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -87,14 +87,16 @@ struct Exit { }; struct PaintingExit { - std::string id; + int id; + int room; + std::string internal_id; std::optional door; }; struct Room { std::string name; std::vector exits; - std::vector paintings; + std::vector paintings; std::vector sunwarps; std::vector panels; }; @@ -113,7 +115,7 @@ struct MapArea { int id; std::string name; std::vector locations; - std::vector paintings; + std::vector paintings; int map_x; int map_y; int classification = 0; @@ -152,7 +154,8 @@ const std::vector& GD_GetDoors(); const Door& GD_GetDoor(int door_id); int GD_GetDoorByName(const std::string& name); const Panel& GD_GetPanel(int panel_id); -int GD_GetRoomForPainting(const std::string& painting_id); +const PaintingExit& GD_GetPaintingExit(int painting_id); +int GD_GetPaintingByName(const std::string& name); const std::vector& GD_GetAchievementPanels(); int GD_GetItemIdForColor(LingoColor color); const std::vector& GD_GetSunwarpDoors(); diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 0385f89..f0810c9 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -172,9 +172,10 @@ void TrackerPanel::Redraw() { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit &painting : map_area.paintings) { - if (!AP_IsPaintingChecked(painting.id)) { - bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + for (int painting_id : map_area.paintings) { + const PaintingExit &painting = GD_GetPaintingExit(painting_id); + if (!AP_IsPaintingChecked(painting.internal_id)) { + bool reachable = IsPaintingReachable(painting_id); if (reachable) { has_reachable_unchecked = true; diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 187a4a8..46bdbec 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -141,6 +141,7 @@ class RequirementCalculator { struct TrackerState { std::map reachability; std::set reachable_doors; + std::set reachable_paintings; std::mutex reachability_mutex; RequirementCalculator requirements; std::map> door_reports; @@ -170,6 +171,7 @@ class StateCalculator { void Calculate() { std::list panel_boundary; + std::list painting_boundary; std::list flood_boundary; flood_boundary.push_back({.destination_room = options_.start}); @@ -177,6 +179,8 @@ class StateCalculator { while (reachable_changed) { reachable_changed = false; + std::list new_boundary; + std::list new_panel_boundary; for (int panel_id : panel_boundary) { if (solveable_panels_.count(panel_id)) { @@ -192,7 +196,33 @@ class StateCalculator { } } - std::list new_boundary; + std::list new_painting_boundary; + for (int painting_id : painting_boundary) { + if (reachable_paintings_.count(painting_id)) { + continue; + } + + Decision painting_reachable = IsPaintingReachable(painting_id); + if (painting_reachable == kYes) { + reachable_paintings_.insert(painting_id); + reachable_changed = true; + + PaintingExit cur_painting = GD_GetPaintingExit(painting_id); + if (AP_GetPaintingMapping().count(cur_painting.internal_id) && + AP_GetCheckedPaintings().count(cur_painting.internal_id)) { + Exit painting_exit; + PaintingExit target_painting = + GD_GetPaintingExit(GD_GetPaintingByName( + AP_GetPaintingMapping().at(cur_painting.internal_id))); + painting_exit.destination_room = target_painting.room; + + new_boundary.push_back(painting_exit); + } + } else if (painting_reachable == kMaybe) { + new_painting_boundary.push_back(painting_id); + } + } + for (const Exit& room_exit : flood_boundary) { if (reachable_rooms_.count(room_exit.destination_room)) { continue; @@ -227,16 +257,8 @@ class StateCalculator { } if (AP_IsPaintingShuffle()) { - for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id) && - AP_GetCheckedPaintings().count(out_edge.id)) { - Exit painting_exit; - painting_exit.destination_room = GD_GetRoomForPainting( - AP_GetPaintingMapping().at(out_edge.id)); - painting_exit.door = out_edge.door; - - new_boundary.push_back(painting_exit); - } + for (int out_edge : room_obj.paintings) { + new_painting_boundary.push_back(out_edge); } } @@ -285,6 +307,7 @@ class StateCalculator { flood_boundary = new_boundary; panel_boundary = new_panel_boundary; + painting_boundary = new_painting_boundary; } // Now that we know the full reachable area, let's make sure all doors are @@ -302,6 +325,10 @@ class StateCalculator { const std::set& GetSolveablePanels() const { return solveable_panels_; } + const std::set& GetReachablePaintings() const { + return reachable_paintings_; + } + const std::map>& GetDoorReports() const { return door_report_; } @@ -450,6 +477,15 @@ class StateCalculator { return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id)); } + Decision IsPaintingReachable(int painting_id) { + const PaintingExit& painting = GD_GetPaintingExit(painting_id); + if (painting.door) { + return IsDoorReachable(*painting.door); + } + + return kYes; + } + Decision IsExitUsable(const Exit& room_exit) { if (room_exit.type == EntranceType::kPilgrimage) { if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) { @@ -533,6 +569,7 @@ class StateCalculator { std::set reachable_rooms_; std::map door_decisions_; std::set solveable_panels_; + std::set reachable_paintings_; std::map> door_report_; }; @@ -573,6 +610,7 @@ void RecalculateReachability() { } } + std::set reachable_paintings = state_calculator.GetReachablePaintings(); std::map> door_reports = state_calculator.GetDoorReports(); @@ -580,6 +618,7 @@ void RecalculateReachability() { std::lock_guard reachability_guard(GetState().reachability_mutex); std::swap(GetState().reachability, new_reachability); std::swap(GetState().reachable_doors, new_reachable_doors); + std::swap(GetState().reachable_paintings, reachable_paintings); std::swap(GetState().door_reports, door_reports); } } @@ -600,6 +639,12 @@ bool IsDoorOpen(int door_id) { return GetState().reachable_doors.count(door_id); } +bool IsPaintingReachable(int painting_id) { + std::lock_guard reachability_guard(GetState().reachability_mutex); + + return GetState().reachable_paintings.count(painting_id); +} + const std::map& GetDoorRequirements(int door_id) { std::lock_guard reachability_guard(GetState().reachability_mutex); diff --git a/src/tracker_state.h b/src/tracker_state.h index 7acb0f2..c7857a0 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h @@ -12,6 +12,8 @@ bool IsLocationReachable(int location_id); bool IsDoorOpen(int door_id); +bool IsPaintingReachable(int painting_id); + const std::map& GetDoorRequirements(int door_id); #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ -- cgit 1.4.1 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') 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 3d7d4b216d630ec65bc008cd9d7979f8de191825 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:15:14 -0400 Subject: Added remaining painting areas --- assets/areas.yaml | 12 ++++++++++-- src/tracker_panel.cpp | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/assets/areas.yaml b/assets/areas.yaml index d38ceb8..cbcf23a 100755 --- a/assets/areas.yaml +++ b/assets/areas.yaml @@ -67,6 +67,8 @@ map: [2642, 872] Eight Room: fold_into: The Incomparable + Eight Alcove: + fold_into: The Incomparable Orange Tower First Floor: map: [1285, 928] Color Hunt: @@ -83,8 +85,10 @@ map: [1252, 1259] Orange Tower Seventh Floor: map: [1587, 1900] - Orange Tower Basement: + Orange Tower Sixth Floor: map: [1587, 2000] + Orange Tower Basement: + map: [1587, 2100] Courtyard: map: [863, 387] First Second Third Fourth: @@ -224,11 +228,15 @@ The Eyes They See: map: [955, 933] Far Window: - fold_into: The Eyes They See + fold_into: The Eyes They See + Wondrous Lobby: + fold_into: The Eyes They See Outside The Wondrous: map: [691, 524] The Wondrous: map: [648, 338] + The Wondrous (Doorknob): + fold_into: The Wondrous The Wondrous (Bookcase): fold_into: The Wondrous The Wondrous (Chandelier): diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index f0810c9..d60c1b6 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -143,7 +143,8 @@ void TrackerPanel::Redraw() { for (AreaIndicator &area : areas_) { const MapArea &map_area = GD_GetMapArea(area.area_id); if (!AP_IsLocationVisible(map_area.classification) && - !(map_area.hunt && GetTrackerConfig().show_hunt_panels)) { + !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && + !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { area.active = false; continue; } else { -- cgit 1.4.1 From 973179a841e98c55a56a977634891ba592d2e2be Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:18:22 -0400 Subject: Added crosshair cursor while scrolling subway map --- src/subway_map.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 100d351..0beef76 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -423,6 +423,8 @@ void SubwayMap::OnMouseClick(wxMouseEvent &event) { scroll_mode_ = false; SetScrollSpeed(0, 0); + + SetCursor(wxCURSOR_ARROW); } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || event.GetPosition().y < GetSize().GetHeight() / 6 || @@ -430,6 +432,8 @@ void SubwayMap::OnMouseClick(wxMouseEvent &event) { scroll_mode_ = true; EvaluateScroll(event.GetPosition()); + + SetCursor(wxCURSOR_CROSS); } } -- cgit 1.4.1 From b84a5401359a442b2dff14599f80d47626290fa1 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:35:57 -0400 Subject: Shade owls to indicate entrance/exit --- src/ap_state.cpp | 7 +++++++ src/ap_state.h | 2 ++ src/subway_map.cpp | 54 +++++++++++++++++++++++++++++++----------------------- 3 files changed, 40 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 0c75eae..0ce4582 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -70,6 +70,7 @@ struct APState { bool sunwarp_shuffle = false; std::map painting_mapping; + std::set painting_codomain; std::map sunwarp_mapping; void Connect(std::string server, std::string player, std::string password) { @@ -137,6 +138,7 @@ struct APState { color_shuffle = false; painting_shuffle = false; painting_mapping.clear(); + painting_codomain.clear(); mastery_requirement = 21; level_2_requirement = 223; location_checks = kNORMAL_LOCATIONS; @@ -253,6 +255,7 @@ struct APState { for (const auto& mapping_it : slot_data["painting_entrance_to_exit"].items()) { painting_mapping[mapping_it.key()] = mapping_it.value(); + painting_codomain.insert(mapping_it.value()); } } @@ -510,6 +513,10 @@ const std::map& AP_GetPaintingMapping() { return GetState().painting_mapping; } +bool AP_IsPaintingMappedTo(const std::string& painting_id) { + return GetState().painting_codomain.count(painting_id); +} + const std::set& AP_GetCheckedPaintings() { return GetState().GetCheckedPaintings(); } diff --git a/src/ap_state.h b/src/ap_state.h index 2769bb8..7af7395 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -59,6 +59,8 @@ bool AP_IsPaintingShuffle(); const std::map& AP_GetPaintingMapping(); +bool AP_IsPaintingMappedTo(const std::string& painting_id); + const std::set& AP_GetCheckedPaintings(); bool AP_IsPaintingChecked(const std::string& painting_id); diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 0beef76..dde817b 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -457,7 +457,9 @@ void SubwayMap::OnClickHelp(wxCommandEvent &event) { "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 " + "yet been checked will not show their connections.\nA green shaded owl " + "means that there is a painting entrance there.\nA red shaded owl means " + "that there are only painting exits there.\nClick on a door or " "warp to make the popup stick until you click again.", "Subway Map Help"); } @@ -468,28 +470,19 @@ void SubwayMap::Redraw() { wxMemoryDC dc; dc.SelectObject(rendered_); + wxGCDC gcdc(dc); + for (const SubwayItem &subway_item : GD_GetSubwayItems()) { ItemDrawType draw_type = ItemDrawType::kNone; const wxBrush *brush_color = wxGREY_BRUSH; std::optional shade_color; - 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()) { + if (!subway_item.paintings.empty()) { if (AP_IsPaintingShuffle()) { bool has_checked_painting = false; bool has_unchecked_painting = false; bool has_mapped_painting = false; + bool has_codomain_painting = false; for (const std::string &painting_id : subway_item.paintings) { if (checked_paintings_.count(painting_id)) { @@ -497,26 +490,36 @@ void SubwayMap::Redraw() { if (AP_GetPaintingMapping().count(painting_id)) { has_mapped_painting = true; + } else if (AP_IsPaintingMappedTo(painting_id)) { + has_codomain_painting = true; } } else { has_unchecked_painting = true; } } - if (has_unchecked_painting || has_mapped_painting) { + if (has_unchecked_painting || has_mapped_painting || has_codomain_painting) { draw_type = ItemDrawType::kOwl; - if (has_unchecked_painting) { - if (has_checked_painting) { - shade_color = wxColour(255, 255, 0, 100); + if (has_checked_painting) { + if (has_mapped_painting) { + shade_color = wxColour(0, 255, 0, 128); } else { - shade_color = wxColour(100, 100, 100, 100); + shade_color = wxColour(255, 0, 0, 128); } } } } else if (!subway_item.tags.empty()) { draw_type = ItemDrawType::kOwl; } + } else if (subway_item.door) { + draw_type = ItemDrawType::kBox; + + if (IsDoorOpen(*subway_item.door)) { + brush_color = wxGREEN_BRUSH; + } else { + brush_color = wxRED_BRUSH; + } } wxPoint real_area_pos = {subway_item.x, subway_item.y}; @@ -525,13 +528,18 @@ void SubwayMap::Redraw() { (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); 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}); + gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); + gcdc.SetBrush(*brush_color); + gcdc.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_pos); + gcdc.DrawBitmap(owl_bitmap, real_area_pos); + + if (shade_color) { + gcdc.SetBrush(wxBrush(*shade_color)); + gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); + } } } } -- 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') 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 From e7b85c60546341dd842efe4dc06718854be9376c Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:58:08 -0400 Subject: Allow scrolling while sticky hovering --- src/subway_map.cpp | 54 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index dde817b..b32c362 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -405,35 +405,47 @@ void SubwayMap::OnMouseLeave(wxMouseEvent &event) { } void SubwayMap::OnMouseClick(wxMouseEvent &event) { - if (sticky_hover_) { - sticky_hover_ = false; + bool finished = false; - if (actual_hover_ != hovered_item_) { - hovered_item_ = actual_hover_; - - Refresh(); - } - } else if (hovered_item_) { - const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); + if (actual_hover_) { + const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || networks_.IsItemInNetwork(*hovered_item_)) { - sticky_hover_ = true; + if (actual_hover_ != hovered_item_) { + hovered_item_ = actual_hover_; + + if (!hovered_item_) { + sticky_hover_ = false; + } + + Refresh(); + } else { + sticky_hover_ = !sticky_hover_; + } + + finished = true; } - } else if (scroll_mode_) { - scroll_mode_ = false; + } - SetScrollSpeed(0, 0); + if (!finished) { + if (scroll_mode_) { + scroll_mode_ = false; - SetCursor(wxCURSOR_ARROW); - } 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; + SetScrollSpeed(0, 0); - EvaluateScroll(event.GetPosition()); + SetCursor(wxCURSOR_ARROW); + } 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; - SetCursor(wxCURSOR_CROSS); + EvaluateScroll(event.GetPosition()); + + SetCursor(wxCURSOR_CROSS); + } else { + sticky_hover_ = false; + } } } -- cgit 1.4.1 From cb880c618ce031c95009b848cdf62dd61751c858 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 15:06:09 -0400 Subject: Fixed achievements pane not refreshing properly --- src/tracker_frame.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index a15a6b4..107ae49 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp @@ -75,9 +75,8 @@ TrackerFrame::TrackerFrame() Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); - achievements_pane_ = new AchievementsPane(this); - wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); + achievements_pane_ = new AchievementsPane(choicebook); choicebook->AddPage(achievements_pane_, "Achievements"); notebook_ = new wxNotebook(this, wxID_ANY); -- cgit 1.4.1 From 6f5287b3921c843a6b322ccbdfcbef00a8f16980 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 15:51:32 -0400 Subject: Handle special cases (ECH + Sun Painting) --- src/subway_map.cpp | 34 +++++++++++++++++++++++++++++++++- src/tracker_state.cpp | 9 ++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/subway_map.cpp b/src/subway_map.cpp index b32c362..8364714 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -69,6 +69,12 @@ void SubwayMap::OnConnect() { std::map> tagged; for (const SubwayItem &subway_item : GD_GetSubwayItems()) { + if (AP_HasEarlyColorHallways() && + (subway_item.special == "starting_room_paintings" || + subway_item.special == "early_color_hallways")) { + tagged["early_color_hallways"].push_back(subway_item.id); + } + if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { continue; } @@ -84,6 +90,12 @@ void SubwayMap::OnConnect() { tagged[tag.str()].push_back(subway_item.id); } + + if (!AP_IsPilgrimageEnabled() && + (subway_item.special == "sun_painting" || + subway_item.special == "sun_painting_exit")) { + tagged["sun_painting"].push_back(subway_item.id); + } } for (const auto &[tag, items] : tagged) { @@ -489,7 +501,27 @@ void SubwayMap::Redraw() { const wxBrush *brush_color = wxGREY_BRUSH; std::optional shade_color; - if (!subway_item.paintings.empty()) { + if (AP_HasEarlyColorHallways() && + (subway_item.special == "starting_room_paintings" || + subway_item.special == "early_color_hallways")) { + draw_type = ItemDrawType::kOwl; + + if (subway_item.special == "starting_room_paintings") { + shade_color = wxColour(0, 255, 0, 128); + } else { + shade_color = wxColour(255, 0, 0, 128); + } + } else if (subway_item.special == "sun_painting") { + if (!AP_IsPilgrimageEnabled()) { + if (IsDoorOpen(*subway_item.door)) { + draw_type = ItemDrawType::kOwl; + shade_color = wxColour(0, 255, 0, 128); + } else { + draw_type = ItemDrawType::kBox; + brush_color = wxRED_BRUSH; + } + } + } else if (!subway_item.paintings.empty()) { if (AP_IsPaintingShuffle()) { bool has_checked_painting = false; bool has_unchecked_painting = false; diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 46bdbec..66e7751 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -52,9 +52,12 @@ class RequirementCalculator { 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); + if (door_obj.type == DoorType::kSunPainting) { + if (!AP_IsPilgrimageEnabled()) { + requirements.items.insert(door_obj.ap_item_id); + } else { + requirements.disabled = true; + } } else if (door_obj.type == DoorType::kSunwarp) { switch (AP_GetSunwarpAccess()) { case kSUNWARP_ACCESS_NORMAL: -- cgit 1.4.1