about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CHANGELOG.md56
-rw-r--r--CMakeLists.txt8
-rw-r--r--VERSION2
-rw-r--r--VERSION.yaml8
-rw-r--r--assets/subway.yaml18
-rw-r--r--src/achievements_pane.cpp13
-rw-r--r--src/achievements_pane.h6
-rw-r--r--src/ap_state.cpp89
-rw-r--r--src/ap_state.h11
-rw-r--r--src/area_popup.cpp32
-rw-r--r--src/game_data.cpp180
-rw-r--r--src/game_data.h7
-rw-r--r--src/godot_variant.cpp84
-rw-r--r--src/godot_variant.h28
-rw-r--r--src/ipc_state.cpp21
-rw-r--r--src/ipc_state.h2
-rw-r--r--src/main.cpp2
-rw-r--r--src/settings_dialog.cpp48
-rw-r--r--src/settings_dialog.h11
-rw-r--r--src/subway_map.cpp12
-rw-r--r--src/tracker_config.cpp8
-rw-r--r--src/tracker_config.h8
-rw-r--r--src/tracker_frame.cpp66
-rw-r--r--src/tracker_frame.h7
-rw-r--r--src/tracker_panel.cpp91
-rw-r--r--src/tracker_panel.h21
-rw-r--r--src/tracker_state.cpp5
-rw-r--r--src/updater.cpp33
-rw-r--r--src/updater.h1
-rw-r--r--src/version.h2
-rw-r--r--vcpkg.json1
m---------vendor/vcpkg0
m---------vendor/websocketpp0
34 files changed, 400 insertions, 484 deletions
diff --git a/.gitmodules b/.gitmodules index ebe016f..1a69477 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -16,3 +16,6 @@
16[submodule "vendor/vcpkg"] 16[submodule "vendor/vcpkg"]
17 path = vendor/vcpkg 17 path = vendor/vcpkg
18 url = https://github.com/Microsoft/vcpkg.git 18 url = https://github.com/Microsoft/vcpkg.git
19[submodule "vendor/websocketpp"]
20 path = vendor/websocketpp
21 url = https://github.com/zaphoyd/websocketpp
diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0fdc2..e2444b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,61 @@
1# lingo-ap-tracker Releases 1# lingo-ap-tracker Releases
2 2
3## v2.0.2 - 2025-05-24
4
5- Fixed issue connecting to the Archipelago 0.6.2 RC server.
6
7Download:
8[lingo-ap-tracker-v2.0.2-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.2-win64.zip)<br/>
9Source: [v2.0.2](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v2.0.2)
10
11## v2.0.1 - 2025-04-06
12
13- The tracker now assumes postgame shuffle is enabled when the flag is not
14 present in slot data (as is the case with Archipelago 0.6.1 and earlier).
15 Players who have postgame shuffle disabled will unfortunately see locations
16 that are not in their world, until this problem can be fixed.
17
18Download:
19[lingo-ap-tracker-v2.0.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.1-win64.zip)<br/>
20Source: [v2.0.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v2.0.1)
21
22## v2.0.0 - 2025-04-01
23
24- Compatibility update for Archipelago 0.6.0.
25
26Download:
27[lingo-ap-tracker-v2.0.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.0-win64.zip)<br/>
28Source: [v2.0.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v2.0.0)
29
30## v1.0.0 - 2025-03-21
31
32After almost two years of development, we have finally hit version 1!
33
34- The subway map now uses Kinrah's updated map image, including fully separated
35 paintings for places like The Wondrous and Orange Tower Sixth Floor, as well
36 as indicators for the color items.
37- Paintings have friendly names now instead of the internal game IDs. They are
38 also distinguished from regular checks by using an owl icon instead of an eye.
39- The tracker is now able to read your solved panel state from the multiworld
40 (requires v5.3.0 of the client). This obsoletes save file parsing and
41 receiving panel solves by connecting to the game.
42- Numerous optimizations to reachability detection and rendering.
43- Added a pane that shows all items received, similar to the web tracker.
44- Added a pane that shows your currently revealed painting mapping, as well as a
45 button that reveals it all immediately.
46- Added a pane that shows your slot options.
47- Postgame shuffle being disabled is handled properly now, and removed locations
48 are no longer shown.
49- Improved support for high-DPI screens. The tracker should no longer appear
50 "blurry" on a high-DPI screen, and should also be able to react properly to
51 being moved between screens with different DPIs.
52- The update checker is now able to download and install tracker updates in
53 place.
54
55Download:
56[lingo-ap-tracker-v1.0.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v1.0.0-win64.zip)<br/>
57Source: [v1.0.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v1.0.0)
58
3## v0.12.3 - 2025-03-03 59## v0.12.3 - 2025-03-03
4 60
5- Fixed issue with non-ASCII connection details. 61- Fixed issue with non-ASCII connection details.
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9432f2e..ef741fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required (VERSION 3.1) 1cmake_minimum_required (VERSION 3.20)
2project (lingo_ap_tracker) 2project (lingo_ap_tracker)
3 3
4if (MSVC) 4if (MSVC)
@@ -10,7 +10,6 @@ endif(MSVC)
10find_package(wxWidgets CONFIG REQUIRED) 10find_package(wxWidgets CONFIG REQUIRED)
11find_package(OpenSSL REQUIRED) 11find_package(OpenSSL REQUIRED)
12find_package(yaml-cpp REQUIRED) 12find_package(yaml-cpp REQUIRED)
13find_package(websocketpp REQUIRED)
14find_package(fmt REQUIRED) 13find_package(fmt REQUIRED)
15 14
16include_directories( 15include_directories(
@@ -19,7 +18,7 @@ include_directories(
19 vendor/asio/asio/include 18 vendor/asio/asio/include
20 vendor/nlohmann 19 vendor/nlohmann
21 vendor/valijson/include 20 vendor/valijson/include
22 ${websocketpp_INCLUDE_DIRS} 21 vendor/websocketpp
23 vendor/wswrap/include 22 vendor/wswrap/include
24 ${yaml-cpp_INCLUDE_DIRS} 23 ${yaml-cpp_INCLUDE_DIRS}
25 ${OpenSSL_INCLUDE_DIRS} 24 ${OpenSSL_INCLUDE_DIRS}
@@ -49,7 +48,6 @@ set(SOURCE_FILES
49 "src/subway_map.cpp" 48 "src/subway_map.cpp"
50 "src/network_set.cpp" 49 "src/network_set.cpp"
51 "src/logger.cpp" 50 "src/logger.cpp"
52 "src/godot_variant.cpp"
53 "src/ipc_state.cpp" 51 "src/ipc_state.cpp"
54 "src/ipc_dialog.cpp" 52 "src/ipc_dialog.cpp"
55 "src/report_popup.cpp" 53 "src/report_popup.cpp"
@@ -69,7 +67,7 @@ endif(MSVC)
69add_executable(lingo_ap_tracker ${SOURCE_FILES}) 67add_executable(lingo_ap_tracker ${SOURCE_FILES})
70set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) 68set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20)
71set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) 69set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON)
72target_link_libraries(lingo_ap_tracker PRIVATE fmt::fmt OpenSSL::SSL OpenSSL::Crypto websocketpp::websocketpp wx::core wx::base wx::net yaml-cpp::yaml-cpp) 70target_link_libraries(lingo_ap_tracker PRIVATE fmt::fmt OpenSSL::SSL OpenSSL::Crypto wx::core wx::base wx::net yaml-cpp::yaml-cpp)
73 71
74set(SRC_DIR "${CMAKE_SOURCE_DIR}/assets") 72set(SRC_DIR "${CMAKE_SOURCE_DIR}/assets")
75set(DST_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>/assets") 73set(DST_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>/assets")
diff --git a/VERSION b/VERSION index 1af39b3..b02d37b 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@
v0.12.3 \ No newline at end of file v2.0.2 \ No newline at end of file
diff --git a/VERSION.yaml b/VERSION.yaml index c4ad3ec..8f86a39 100644 --- a/VERSION.yaml +++ b/VERSION.yaml
@@ -1,8 +1,8 @@
1version: v0.12.3 1version: v2.0.2
2packages: 2packages:
3 win64: 3 win64:
4 url: "https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.12.3-win64.zip" 4 url: "https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.2-win64.zip"
5 checksum: "0c0d8310d1ecae4f1b8de933661ba14f63aac011cd2b22947aff7085d997f0db" 5 checksum: "dfb2fce2f5b14c09f4af7e7fcf6478e420a070db9fb23f9f9ad196f2db4b7518"
6 files: 6 files:
7 - fmt.dll 7 - fmt.dll
8 - jpeg62.dll 8 - jpeg62.dll
@@ -20,7 +20,7 @@ packages:
20 - zlib1.dll 20 - zlib1.dll
21 - assets/areas.yaml 21 - assets/areas.yaml
22 - assets/checked.png 22 - assets/checked.png
23 # TODO: next release will contain the checked owl 23 - assets/checked_owl.png
24 - assets/ids.yaml 24 - assets/ids.yaml
25 - assets/lingo_map.png 25 - assets/lingo_map.png
26 - assets/LL1.yaml 26 - assets/LL1.yaml
diff --git a/assets/subway.yaml b/assets/subway.yaml index 080a139..8c0df38 100644 --- a/assets/subway.yaml +++ b/assets/subway.yaml
@@ -1042,3 +1042,21 @@
1042- pos: [815, 1002] 1042- pos: [815, 1002]
1043 room: Challenge Room 1043 room: Challenge Room
1044 door: Welcome Door 1044 door: Welcome Door
1045- pos: [104, 1208]
1046 special: color_black
1047- pos: [104, 1249]
1048 special: color_red
1049- pos: [104, 1290]
1050 special: color_yellow
1051- pos: [104, 1330]
1052 special: color_blue
1053- pos: [104, 1371]
1054 special: color_purple
1055- pos: [104, 1411]
1056 special: color_orange
1057- pos: [104, 1451]
1058 special: color_green
1059- pos: [104, 1491]
1060 special: color_brown
1061- pos: [104, 1531]
1062 special: color_gray
diff --git a/src/achievements_pane.cpp b/src/achievements_pane.cpp index b255f3b..d23c434 100644 --- a/src/achievements_pane.cpp +++ b/src/achievements_pane.cpp
@@ -8,13 +8,14 @@ AchievementsPane::AchievementsPane(wxWindow* parent)
8 AppendColumn("Achievement"); 8 AppendColumn("Achievement");
9 9
10 for (int panel_id : GD_GetAchievementPanels()) { 10 for (int panel_id : GD_GetAchievementPanels()) {
11 achievement_names_.push_back(GD_GetPanel(panel_id).achievement_name); 11 const Panel& panel = GD_GetPanel(panel_id);
12 achievements_.emplace_back(panel.achievement_name, panel.solve_index);
12 } 13 }
13 14
14 std::sort(std::begin(achievement_names_), std::end(achievement_names_)); 15 std::sort(std::begin(achievements_), std::end(achievements_));
15 16
16 for (int i = 0; i < achievement_names_.size(); i++) { 17 for (int i = 0; i < achievements_.size(); i++) {
17 InsertItem(i, achievement_names_.at(i)); 18 InsertItem(i, std::get<0>(achievements_.at(i)));
18 } 19 }
19 20
20 SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER); 21 SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER);
@@ -23,8 +24,8 @@ AchievementsPane::AchievementsPane(wxWindow* parent)
23} 24}
24 25
25void AchievementsPane::UpdateIndicators() { 26void AchievementsPane::UpdateIndicators() {
26 for (int i = 0; i < achievement_names_.size(); i++) { 27 for (int i = 0; i < achievements_.size(); i++) {
27 if (AP_HasAchievement(achievement_names_.at(i))) { 28 if (AP_IsPanelSolved(std::get<1>(achievements_.at(i)))) {
28 SetItemTextColour(i, *wxBLACK); 29 SetItemTextColour(i, *wxBLACK);
29 } else { 30 } else {
30 SetItemTextColour(i, *wxRED); 31 SetItemTextColour(i, *wxRED);
diff --git a/src/achievements_pane.h b/src/achievements_pane.h index ac88cac..941b5e3 100644 --- a/src/achievements_pane.h +++ b/src/achievements_pane.h
@@ -9,6 +9,10 @@
9 9
10#include <wx/listctrl.h> 10#include <wx/listctrl.h>
11 11
12#include <string>
13#include <tuple>
14#include <vector>
15
12class AchievementsPane : public wxListView { 16class AchievementsPane : public wxListView {
13 public: 17 public:
14 explicit AchievementsPane(wxWindow* parent); 18 explicit AchievementsPane(wxWindow* parent);
@@ -16,7 +20,7 @@ class AchievementsPane : public wxListView {
16 void UpdateIndicators(); 20 void UpdateIndicators();
17 21
18 private: 22 private:
19 std::vector<std::string> achievement_names_; 23 std::vector<std::tuple<std::string, int>> achievements_; // name, solve index
20}; 24};
21 25
22#endif /* end of include guard: ACHIEVEMENTS_PANE_H_C320D0B8 */ \ No newline at end of file 26#endif /* end of include guard: ACHIEVEMENTS_PANE_H_C320D0B8 */ \ No newline at end of file
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 1e5621d..8438649 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -10,6 +10,7 @@
10#include <any> 10#include <any>
11#include <apclient.hpp> 11#include <apclient.hpp>
12#include <apuuid.hpp> 12#include <apuuid.hpp>
13#include <bitset>
13#include <chrono> 14#include <chrono>
14#include <exception> 15#include <exception>
15#include <filesystem> 16#include <filesystem>
@@ -28,8 +29,8 @@
28#include "tracker_state.h" 29#include "tracker_state.h"
29 30
30constexpr int AP_MAJOR = 0; 31constexpr int AP_MAJOR = 0;
31constexpr int AP_MINOR = 4; 32constexpr int AP_MINOR = 6;
32constexpr int AP_REVISION = 5; 33constexpr int AP_REVISION = 1;
33 34
34constexpr const char* CERT_STORE_PATH = "cacert.pem"; 35constexpr const char* CERT_STORE_PATH = "cacert.pem";
35constexpr int ITEM_HANDLING = 7; // <- all 36constexpr int ITEM_HANDLING = 7; // <- all
@@ -37,6 +38,10 @@ constexpr int ITEM_HANDLING = 7; // <- all
37constexpr int CONNECTION_TIMEOUT = 50000; // 50 seconds 38constexpr int CONNECTION_TIMEOUT = 50000; // 50 seconds
38constexpr int CONNECTION_BACKOFF_INTERVAL = 100; 39constexpr int CONNECTION_BACKOFF_INTERVAL = 100;
39 40
41constexpr int PANEL_COUNT = 803;
42constexpr int PANEL_BITFIELD_LENGTH = 48;
43constexpr int PANEL_BITFIELDS = 17;
44
40namespace { 45namespace {
41 46
42const std::set<long> kNonProgressionItems = { 47const std::set<long> kNonProgressionItems = {
@@ -79,6 +84,7 @@ struct APState {
79 std::set<int64_t> checked_locations; 84 std::set<int64_t> checked_locations;
80 std::map<std::string, std::any> data_storage; 85 std::map<std::string, std::any> data_storage;
81 std::optional<std::tuple<int, int>> player_pos; 86 std::optional<std::tuple<int, int>> player_pos;
87 std::bitset<PANEL_COUNT> solved_panels;
82 88
83 DoorShuffleMode door_shuffle_mode = kNO_DOORS; 89 DoorShuffleMode door_shuffle_mode = kNO_DOORS;
84 bool group_doors = false; 90 bool group_doors = false;
@@ -142,6 +148,7 @@ struct APState {
142 checked_locations.clear(); 148 checked_locations.clear();
143 data_storage.clear(); 149 data_storage.clear();
144 player_pos = std::nullopt; 150 player_pos = std::nullopt;
151 solved_panels.reset();
145 victory_data_storage_key.clear(); 152 victory_data_storage_key.clear();
146 door_shuffle_mode = kNO_DOORS; 153 door_shuffle_mode = kNO_DOORS;
147 group_doors = false; 154 group_doors = false;
@@ -216,24 +223,13 @@ struct APState {
216 return checked_locations.count(location_id); 223 return checked_locations.count(location_id);
217 } 224 }
218 225
219 bool HasCheckedHuntPanel(int location_id) {
220 std::lock_guard state_guard(state_mutex);
221
222 std::string key =
223 fmt::format("{}Hunt|{}", data_storage_prefix, location_id);
224 return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key));
225 }
226
227 bool HasItem(int item_id, int quantity) { 226 bool HasItem(int item_id, int quantity) {
228 return inventory.count(item_id) && inventory.at(item_id) >= quantity; 227 return inventory.count(item_id) && inventory.at(item_id) >= quantity;
229 } 228 }
230 229
231 bool HasAchievement(const std::string& name) { 230 bool HasItemSafe(int item_id, int quantity) {
232 std::lock_guard state_guard(state_mutex); 231 std::lock_guard state_guard(state_mutex);
233 232 return HasItem(item_id, quantity);
234 std::string key =
235 fmt::format("{}Achievement|{}", data_storage_prefix, name);
236 return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key));
237 } 233 }
238 234
239 const std::set<std::string>& GetCheckedPaintings() { 235 const std::set<std::string>& GetCheckedPaintings() {
@@ -257,8 +253,6 @@ struct APState {
257 checked_paintings.count(painting_mapping.at(painting_id))); 253 checked_paintings.count(painting_mapping.at(painting_id)));
258 } 254 }
259 255
260 std::string GetItemName(int id) { return apclient->get_item_name(id, "Lingo"); }
261
262 void RevealPaintings() { 256 void RevealPaintings() {
263 std::lock_guard state_guard(state_mutex); 257 std::lock_guard state_guard(state_mutex);
264 258
@@ -283,6 +277,12 @@ struct APState {
283 30; // CLIENT_GOAL 277 30; // CLIENT_GOAL
284 } 278 }
285 279
280 bool IsPanelSolved(int solve_index) {
281 std::lock_guard state_guard(state_mutex);
282
283 return solved_panels.test(solve_index);
284 }
285
286 private: 286 private:
287 void Initialize() { 287 void Initialize() {
288 if (!initialized) { 288 if (!initialized) {
@@ -290,16 +290,8 @@ struct APState {
290 290
291 std::thread([this]() { Thread(); }).detach(); 291 std::thread([this]() { Thread(); }).detach();
292 292
293 for (int panel_id : GD_GetAchievementPanels()) { 293 for (int i = 0; i < PANEL_BITFIELDS; i++) {
294 tracked_data_storage_keys.push_back(fmt::format( 294 tracked_data_storage_keys.push_back(fmt::format("Panels_{}", i));
295 "Achievement|{}", GD_GetPanel(panel_id).achievement_name));
296 }
297
298 for (const MapArea& map_area : GD_GetMapAreas()) {
299 for (const Location& location : map_area.locations) {
300 tracked_data_storage_keys.push_back(
301 fmt::format("Hunt|{}", location.ap_location_id));
302 }
303 } 295 }
304 296
305 tracked_data_storage_keys.push_back("PlayerPos"); 297 tracked_data_storage_keys.push_back("PlayerPos");
@@ -425,7 +417,7 @@ struct APState {
425 } 417 }
426 418
427 for (const auto& [item_id, item_index] : index_by_item) { 419 for (const auto& [item_id, item_index] : index_by_item) {
428 item_states.push_back(ItemState{.name = GetItemName(item_id), 420 item_states.push_back(ItemState{.name = GD_GetItemName(item_id),
429 .amount = inventory[item_id], 421 .amount = inventory[item_id],
430 .index = item_index}); 422 .index = item_index});
431 } 423 }
@@ -511,8 +503,9 @@ struct APState {
511 : kSUNWARP_ACCESS_NORMAL; 503 : kSUNWARP_ACCESS_NORMAL;
512 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") && 504 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") &&
513 slot_data["shuffle_sunwarps"].get<int>() == 1; 505 slot_data["shuffle_sunwarps"].get<int>() == 1;
514 postgame_shuffle = slot_data.contains("shuffle_postgame") && 506 postgame_shuffle = slot_data.contains("shuffle_postgame")
515 slot_data["shuffle_postgame"].get<int>() == 1; 507 ? (slot_data["shuffle_postgame"].get<int>() == 1)
508 : true;
516 509
517 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 510 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
518 painting_mapping.clear(); 511 painting_mapping.clear();
@@ -603,11 +596,6 @@ struct APState {
603 TrackerLog(fmt::format("Data storage {} retrieved as {}", key, 596 TrackerLog(fmt::format("Data storage {} retrieved as {}", key,
604 (value.get<bool>() ? "true" : "false"))); 597 (value.get<bool>() ? "true" : "false")));
605 598
606 if (key.find("Achievement|") != std::string::npos) {
607 state_update.achievements = true;
608 } else if (key.find("Hunt|") != std::string::npos) {
609 state_update.hunt_panels = true;
610 }
611 } else if (value.is_number()) { 599 } else if (value.is_number()) {
612 data_storage[key] = value.get<int>(); 600 data_storage[key] = value.get<int>();
613 TrackerLog(fmt::format("Data storage {} retrieved as {}", key, 601 TrackerLog(fmt::format("Data storage {} retrieved as {}", key,
@@ -615,6 +603,21 @@ struct APState {
615 603
616 if (key == victory_data_storage_key) { 604 if (key == victory_data_storage_key) {
617 state_update.cleared_locations = true; 605 state_update.cleared_locations = true;
606 } else if (key.find("Panels_") != std::string::npos) {
607 int bitfield_num =
608 std::stoi(key.substr(data_storage_prefix.size() + 7));
609 uint64_t bitfield_value = value.get<uint64_t>();
610 for (int i = 0; i < PANEL_BITFIELD_LENGTH; i++) {
611 if ((bitfield_value & (1LL << i)) != 0) {
612 int solve_index = bitfield_num * PANEL_BITFIELD_LENGTH + i;
613
614 if (!solved_panels.test(solve_index)) {
615 state_update.panels.insert(solve_index);
616 }
617
618 solved_panels.set(solve_index);
619 }
620 }
618 } 621 }
619 } else if (value.is_object()) { 622 } else if (value.is_object()) {
620 if (key.ends_with("PlayerPos")) { 623 if (key.ends_with("PlayerPos")) {
@@ -715,16 +718,12 @@ bool AP_HasCheckedGameLocation(int location_id) {
715 return GetState().HasCheckedGameLocation(location_id); 718 return GetState().HasCheckedGameLocation(location_id);
716} 719}
717 720
718bool AP_HasCheckedHuntPanel(int location_id) {
719 return GetState().HasCheckedHuntPanel(location_id);
720}
721
722bool AP_HasItem(int item_id, int quantity) { 721bool AP_HasItem(int item_id, int quantity) {
723 return GetState().HasItem(item_id, quantity); 722 return GetState().HasItem(item_id, quantity);
724} 723}
725 724
726std::string AP_GetItemName(int item_id) { 725bool AP_HasItemSafe(int item_id, int quantity) {
727 return GetState().GetItemName(item_id); 726 return GetState().HasItemSafe(item_id, quantity);
728} 727}
729 728
730DoorShuffleMode AP_GetDoorShuffleMode() { 729DoorShuffleMode AP_GetDoorShuffleMode() {
@@ -830,10 +829,6 @@ VictoryCondition AP_GetVictoryCondition() {
830 return GetState().victory_condition; 829 return GetState().victory_condition;
831} 830}
832 831
833bool AP_HasAchievement(const std::string& achievement_name) {
834 return GetState().HasAchievement(achievement_name);
835}
836
837bool AP_HasEarlyColorHallways() { 832bool AP_HasEarlyColorHallways() {
838 std::lock_guard state_guard(GetState().state_mutex); 833 std::lock_guard state_guard(GetState().state_mutex);
839 834
@@ -883,3 +878,7 @@ std::optional<std::tuple<int, int>> AP_GetPlayerPosition() {
883 878
884 return GetState().player_pos; 879 return GetState().player_pos;
885} 880}
881
882bool AP_IsPanelSolved(int solve_index) {
883 return GetState().IsPanelSolved(solve_index);
884}
diff --git a/src/ap_state.h b/src/ap_state.h index 298df8c..a757d89 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -57,16 +57,11 @@ std::string AP_GetSaveName();
57 57
58bool AP_HasCheckedGameLocation(int location_id); 58bool AP_HasCheckedGameLocation(int location_id);
59 59
60bool AP_HasCheckedHuntPanel(int location_id);
61
62// This doesn't lock the state mutex, for speed, so it must ONLY be called from 60// This doesn't lock the state mutex, for speed, so it must ONLY be called from
63// RecalculateReachability, which is only called from the APState thread anyway. 61// RecalculateReachability, which is only called from the APState thread anyway.
64bool AP_HasItem(int item_id, int quantity = 1); 62bool AP_HasItem(int item_id, int quantity = 1);
65 63
66// This doesn't lock the client mutex because it is ONLY to be called from 64bool AP_HasItemSafe(int item_id, int quantity = 1);
67// RecalculateReachability, which is only called from within a client callback
68// anyway.
69std::string AP_GetItemName(int item_id);
70 65
71DoorShuffleMode AP_GetDoorShuffleMode(); 66DoorShuffleMode AP_GetDoorShuffleMode();
72 67
@@ -98,8 +93,6 @@ PanelShuffleMode AP_GetPanelShuffleMode();
98 93
99VictoryCondition AP_GetVictoryCondition(); 94VictoryCondition AP_GetVictoryCondition();
100 95
101bool AP_HasAchievement(const std::string& achievement_name);
102
103bool AP_HasEarlyColorHallways(); 96bool AP_HasEarlyColorHallways();
104 97
105bool AP_IsPilgrimageEnabled(); 98bool AP_IsPilgrimageEnabled();
@@ -120,4 +113,6 @@ bool AP_HasReachedGoal();
120 113
121std::optional<std::tuple<int, int>> AP_GetPlayerPosition(); 114std::optional<std::tuple<int, int>> AP_GetPlayerPosition();
122 115
116bool AP_IsPanelSolved(int solve_index);
117
123#endif /* end of include guard: AP_STATE_H_664A4180 */ 118#endif /* end of include guard: AP_STATE_H_664A4180 */
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index a8e6004..c95e492 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp
@@ -50,21 +50,15 @@ void AreaPopup::ResetIndicators() {
50 for (int section_id = 0; section_id < map_area.locations.size(); 50 for (int section_id = 0; section_id < map_area.locations.size();
51 section_id++) { 51 section_id++) {
52 const Location& location = map_area.locations.at(section_id); 52 const Location& location = map_area.locations.at(section_id);
53 if (IsLocationPostgame(location.ap_location_id)) { 53 if ((!AP_IsLocationVisible(location.classification) ||
54 IsLocationPostgame(location.ap_location_id)) &&
55 !(location.hunt &&
56 GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) &&
57 !(location.single_panel &&
58 GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS)) {
54 continue; 59 continue;
55 } 60 }
56 61
57 if (tracker_panel->IsPanelsMode()) {
58 if (!location.single_panel) {
59 continue;
60 }
61 } else {
62 if (!AP_IsLocationVisible(location.classification) &&
63 !(location.hunt && GetTrackerConfig().show_hunt_panels)) {
64 continue;
65 }
66 }
67
68 indicators_.emplace_back(section_id, kLOCATION, acc_height); 62 indicators_.emplace_back(section_id, kLOCATION, acc_height);
69 63
70 wxSize item_extent = mem_dc.GetTextExtent(location.name); 64 wxSize item_extent = mem_dc.GetTextExtent(location.name);
@@ -77,7 +71,7 @@ void AreaPopup::ResetIndicators() {
77 } 71 }
78 } 72 }
79 73
80 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { 74 if (AP_IsPaintingShuffle()) {
81 for (int painting_id : map_area.paintings) { 75 for (int painting_id : map_area.paintings) {
82 if (IsPaintingPostgame(painting_id)) { 76 if (IsPaintingPostgame(painting_id)) {
83 continue; 77 continue;
@@ -135,17 +129,11 @@ void AreaPopup::UpdateIndicators() {
135 bool checked = false; 129 bool checked = false;
136 if (IsLocationWinCondition(location)) { 130 if (IsLocationWinCondition(location)) {
137 checked = AP_HasReachedGoal(); 131 checked = AP_HasReachedGoal();
138 } else if (tracker_panel->IsPanelsMode()) {
139 const Panel& panel = GD_GetPanel(*location.single_panel);
140 if (panel.non_counting) {
141 checked = AP_HasCheckedGameLocation(location.ap_location_id);
142 } else {
143 checked = tracker_panel->GetSolvedPanels().contains(panel.nodepath);
144 }
145 } else { 132 } else {
146 checked = AP_HasCheckedGameLocation(location.ap_location_id) || 133 checked = AP_HasCheckedGameLocation(location.ap_location_id) ||
147 (location.hunt && 134 (location.single_panel &&
148 AP_HasCheckedHuntPanel(location.ap_location_id)); 135 AP_IsPanelSolved(
136 GD_GetPanel(*location.single_panel).solve_index));
149 } 137 }
150 138
151 const wxBitmap* eye_ptr = checked ? checked_eye_ : unchecked_eye_; 139 const wxBitmap* eye_ptr = checked ? checked_eye_ : unchecked_eye_;
diff --git a/src/game_data.cpp b/src/game_data.cpp index a4a441d..94b9888 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -12,32 +12,6 @@
12 12
13namespace { 13namespace {
14 14
15LingoColor GetColorForString(const std::string &str) {
16 if (str == "black") {
17 return LingoColor::kBlack;
18 } else if (str == "red") {
19 return LingoColor::kRed;
20 } else if (str == "blue") {
21 return LingoColor::kBlue;
22 } else if (str == "yellow") {
23 return LingoColor::kYellow;
24 } else if (str == "orange") {
25 return LingoColor::kOrange;
26 } else if (str == "green") {
27 return LingoColor::kGreen;
28 } else if (str == "gray") {
29 return LingoColor::kGray;
30 } else if (str == "brown") {
31 return LingoColor::kBrown;
32 } else if (str == "purple") {
33 return LingoColor::kPurple;
34 } else {
35 TrackerLog(fmt::format("Invalid color: {}", str));
36
37 return LingoColor::kNone;
38 }
39}
40
41struct GameData { 15struct GameData {
42 std::vector<Room> rooms_; 16 std::vector<Room> rooms_;
43 std::vector<Door> doors_; 17 std::vector<Door> doors_;
@@ -55,10 +29,10 @@ struct GameData {
55 std::map<std::string, int> painting_by_id_; 29 std::map<std::string, int> painting_by_id_;
56 30
57 std::vector<int> door_definition_order_; 31 std::vector<int> door_definition_order_;
58 std::vector<int> room_definition_order_;
59 32
60 std::map<std::string, int> room_by_painting_; 33 std::map<std::string, int> room_by_painting_;
61 std::map<int, int> room_by_sunwarp_; 34 std::map<int, int> room_by_sunwarp_;
35 std::map<int, int> panel_by_solve_index_;
62 36
63 std::vector<int> achievement_panels_; 37 std::vector<int> achievement_panels_;
64 38
@@ -69,6 +43,8 @@ struct GameData {
69 std::map<std::string, int> subway_item_by_painting_; 43 std::map<std::string, int> subway_item_by_painting_;
70 std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; 44 std::map<SubwaySunwarp, int> subway_item_by_sunwarp_;
71 45
46 std::map<int, std::string> item_by_ap_id_;
47
72 bool loaded_area_data_ = false; 48 bool loaded_area_data_ = false;
73 std::set<std::string> malconfigured_areas_; 49 std::set<std::string> malconfigured_areas_;
74 50
@@ -84,7 +60,7 @@ struct GameData {
84 ids_config["special_items"][color_name]) { 60 ids_config["special_items"][color_name]) {
85 std::string input_name = color_name; 61 std::string input_name = color_name;
86 input_name[0] = std::tolower(input_name[0]); 62 input_name[0] = std::tolower(input_name[0]);
87 ap_id_by_color_[GetColorForString(input_name)] = 63 ap_id_by_color_[GetLingoColorForString(input_name)] =
88 ids_config["special_items"][color_name].as<int>(); 64 ids_config["special_items"][color_name].as<int>();
89 } else { 65 } else {
90 TrackerLog(fmt::format("Missing AP item ID for color {}", color_name)); 66 TrackerLog(fmt::format("Missing AP item ID for color {}", color_name));
@@ -101,11 +77,20 @@ struct GameData {
101 init_color_id("Brown"); 77 init_color_id("Brown");
102 init_color_id("Gray"); 78 init_color_id("Gray");
103 79
80 if (ids_config["special_items"]) {
81 for (const auto& special_item_it : ids_config["special_items"])
82 {
83 item_by_ap_id_[special_item_it.second.as<int>()] =
84 special_item_it.first.as<std::string>();
85 }
86 }
87
104 rooms_.reserve(lingo_config.size() * 2); 88 rooms_.reserve(lingo_config.size() * 2);
105 89
90 std::vector<int> panel_location_ids;
91
106 for (const auto &room_it : lingo_config) { 92 for (const auto &room_it : lingo_config) {
107 int room_id = AddOrGetRoom(room_it.first.as<std::string>()); 93 int room_id = AddOrGetRoom(room_it.first.as<std::string>());
108 room_definition_order_.push_back(room_id);
109 94
110 for (const auto &entrance_it : room_it.second["entrances"]) { 95 for (const auto &entrance_it : room_it.second["entrances"]) {
111 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); 96 int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>());
@@ -181,12 +166,12 @@ struct GameData {
181 166
182 if (panel_it.second["colors"]) { 167 if (panel_it.second["colors"]) {
183 if (panel_it.second["colors"].IsScalar()) { 168 if (panel_it.second["colors"].IsScalar()) {
184 panels_[panel_id].colors.push_back(GetColorForString( 169 panels_[panel_id].colors.push_back(GetLingoColorForString(
185 panel_it.second["colors"].as<std::string>())); 170 panel_it.second["colors"].as<std::string>()));
186 } else { 171 } else {
187 for (const auto &color_node : panel_it.second["colors"]) { 172 for (const auto &color_node : panel_it.second["colors"]) {
188 panels_[panel_id].colors.push_back( 173 panels_[panel_id].colors.push_back(
189 GetColorForString(color_node.as<std::string>())); 174 GetLingoColorForString(color_node.as<std::string>()));
190 } 175 }
191 } 176 }
192 } 177 }
@@ -292,10 +277,11 @@ struct GameData {
292 ids_config["panels"][rooms_[room_id].name] && 277 ids_config["panels"][rooms_[room_id].name] &&
293 ids_config["panels"][rooms_[room_id].name] 278 ids_config["panels"][rooms_[room_id].name]
294 [panels_[panel_id].name]) { 279 [panels_[panel_id].name]) {
295 panels_[panel_id].ap_location_id = 280 int location_id = ids_config["panels"][rooms_[room_id].name]
296 ids_config["panels"][rooms_[room_id].name] 281 [panels_[panel_id].name]
297 [panels_[panel_id].name] 282 .as<int>();
298 .as<int>(); 283 panels_[panel_id].ap_location_id = location_id;
284 panel_location_ids.push_back(location_id);
299 } else { 285 } else {
300 TrackerLog(fmt::format("Missing AP location ID for panel {} - {}", 286 TrackerLog(fmt::format("Missing AP location ID for panel {} - {}",
301 rooms_[room_id].name, 287 rooms_[room_id].name,
@@ -361,6 +347,9 @@ struct GameData {
361 ids_config["doors"][rooms_[room_id].name] 347 ids_config["doors"][rooms_[room_id].name]
362 [doors_[door_id].name]["item"] 348 [doors_[door_id].name]["item"]
363 .as<int>(); 349 .as<int>();
350
351 item_by_ap_id_[doors_[door_id].ap_item_id] =
352 doors_[door_id].item_name;
364 } else { 353 } else {
365 TrackerLog(fmt::format("Missing AP item ID for door {} - {}", 354 TrackerLog(fmt::format("Missing AP item ID for door {} - {}",
366 rooms_[room_id].name, 355 rooms_[room_id].name,
@@ -377,6 +366,9 @@ struct GameData {
377 doors_[door_id].group_ap_item_id = 366 doors_[door_id].group_ap_item_id =
378 ids_config["door_groups"][doors_[door_id].group_name] 367 ids_config["door_groups"][doors_[door_id].group_name]
379 .as<int>(); 368 .as<int>();
369
370 item_by_ap_id_[doors_[door_id].group_ap_item_id] =
371 doors_[door_id].group_name;
380 } else { 372 } else {
381 TrackerLog(fmt::format("Missing AP item ID for door group {}", 373 TrackerLog(fmt::format("Missing AP item ID for door group {}",
382 doors_[door_id].group_name)); 374 doors_[door_id].group_name));
@@ -440,21 +432,50 @@ struct GameData {
440 int panel_door_id = 432 int panel_door_id =
441 AddOrGetPanelDoor(rooms_[room_id].name, panel_door_name); 433 AddOrGetPanelDoor(rooms_[room_id].name, panel_door_name);
442 434
435 std::map<std::string, std::vector<std::string>> panel_per_room;
436 int num_panels = 0;
443 for (const auto &panel_node : panel_door_it.second["panels"]) { 437 for (const auto &panel_node : panel_door_it.second["panels"]) {
438 num_panels++;
439
444 int panel_id = -1; 440 int panel_id = -1;
445 441
446 if (panel_node.IsScalar()) { 442 if (panel_node.IsScalar()) {
447 panel_id = AddOrGetPanel(rooms_[room_id].name, 443 panel_id = AddOrGetPanel(rooms_[room_id].name,
448 panel_node.as<std::string>()); 444 panel_node.as<std::string>());
445
446 panel_per_room[rooms_[room_id].name].push_back(
447 panel_node.as<std::string>());
449 } else { 448 } else {
450 panel_id = AddOrGetPanel(panel_node["room"].as<std::string>(), 449 panel_id = AddOrGetPanel(panel_node["room"].as<std::string>(),
451 panel_node["panel"].as<std::string>()); 450 panel_node["panel"].as<std::string>());
451
452 panel_per_room[panel_node["room"].as<std::string>()].push_back(
453 panel_node["panel"].as<std::string>());
452 } 454 }
453 455
454 Panel &panel = panels_[panel_id]; 456 Panel &panel = panels_[panel_id];
455 panel.panel_door = panel_door_id; 457 panel.panel_door = panel_door_id;
456 } 458 }
457 459
460 if (panel_door_it.second["item_name"]) {
461 panel_doors_[panel_door_id].item_name =
462 panel_door_it.second["item_name"].as<std::string>();
463 } else {
464 std::vector<std::string> room_strs;
465 for (const auto &[room_str, panels_str] : panel_per_room) {
466 room_strs.push_back(fmt::format(
467 "{} - {}", room_str, hatkirby::implode(panels_str, ", ")));
468 }
469
470 if (num_panels == 1) {
471 panel_doors_[panel_door_id].item_name =
472 fmt::format("{} (Panel)", room_strs[0]);
473 } else {
474 panel_doors_[panel_door_id].item_name = fmt::format(
475 "{} (Panels)", hatkirby::implode(room_strs, " and "));
476 }
477 }
478
458 if (ids_config["panel_doors"] && 479 if (ids_config["panel_doors"] &&
459 ids_config["panel_doors"][rooms_[room_id].name] && 480 ids_config["panel_doors"][rooms_[room_id].name] &&
460 ids_config["panel_doors"][rooms_[room_id].name] 481 ids_config["panel_doors"][rooms_[room_id].name]
@@ -462,6 +483,9 @@ struct GameData {
462 panel_doors_[panel_door_id].ap_item_id = 483 panel_doors_[panel_door_id].ap_item_id =
463 ids_config["panel_doors"][rooms_[room_id].name][panel_door_name] 484 ids_config["panel_doors"][rooms_[room_id].name][panel_door_name]
464 .as<int>(); 485 .as<int>();
486
487 item_by_ap_id_[panel_doors_[panel_door_id].ap_item_id] =
488 panel_doors_[panel_door_id].item_name;
465 } else { 489 } else {
466 TrackerLog(fmt::format("Missing AP item ID for panel door {} - {}", 490 TrackerLog(fmt::format("Missing AP item ID for panel door {} - {}",
467 rooms_[room_id].name, panel_door_name)); 491 rooms_[room_id].name, panel_door_name));
@@ -475,6 +499,9 @@ struct GameData {
475 ids_config["panel_groups"][panel_group]) { 499 ids_config["panel_groups"][panel_group]) {
476 panel_doors_[panel_door_id].group_ap_item_id = 500 panel_doors_[panel_door_id].group_ap_item_id =
477 ids_config["panel_groups"][panel_group].as<int>(); 501 ids_config["panel_groups"][panel_group].as<int>();
502
503 item_by_ap_id_[panel_doors_[panel_door_id].group_ap_item_id] =
504 panel_group;
478 } else { 505 } else {
479 TrackerLog(fmt::format( 506 TrackerLog(fmt::format(
480 "Missing AP item ID for panel door group {}", panel_group)); 507 "Missing AP item ID for panel door group {}", panel_group));
@@ -538,6 +565,8 @@ struct GameData {
538 ids_config["progression"][progressive_item_name]) { 565 ids_config["progression"][progressive_item_name]) {
539 progressive_item_id = 566 progressive_item_id =
540 ids_config["progression"][progressive_item_name].as<int>(); 567 ids_config["progression"][progressive_item_name].as<int>();
568
569 item_by_ap_id_[progressive_item_id] = progressive_item_name;
541 } else { 570 } else {
542 TrackerLog(fmt::format("Missing AP item ID for progressive item {}", 571 TrackerLog(fmt::format("Missing AP item ID for progressive item {}",
543 progressive_item_name)); 572 progressive_item_name));
@@ -589,6 +618,21 @@ struct GameData {
589 } 618 }
590 } 619 }
591 620
621 // Determine the panel solve indices from the sorted location IDs.
622 std::sort(panel_location_ids.begin(), panel_location_ids.end());
623
624 std::map<int, int> solve_index_by_location_id;
625 for (int i = 0; i < panel_location_ids.size(); i++) {
626 solve_index_by_location_id[panel_location_ids[i]] = i;
627 }
628
629 for (Panel &panel : panels_) {
630 if (panel.ap_location_id != -1) {
631 panel.solve_index = solve_index_by_location_id[panel.ap_location_id];
632 panel_by_solve_index_[panel.solve_index] = panel.id;
633 }
634 }
635
592 map_areas_.reserve(areas_config.size()); 636 map_areas_.reserve(areas_config.size());
593 637
594 std::map<std::string, int> fold_areas; 638 std::map<std::string, int> fold_areas;
@@ -734,31 +778,6 @@ struct GameData {
734 } 778 }
735 } 779 }
736 780
737 // As a workaround for a generator bug in 0.5.1, we are going to remove the
738 // panel door requirement on panels that are defined earlier in the file than
739 // the panel door is. This results in logic that matches the generator, even
740 // if it is not true to how the game should work. This will be reverted once
741 // the logic bug is fixed and released.
742 // See: https://github.com/ArchipelagoMW/Archipelago/pull/4342
743 for (Panel& panel : panels_) {
744 if (panel.panel_door == -1) {
745 continue;
746 }
747 const PanelDoor &panel_door = panel_doors_[panel.panel_door];
748 for (int room_id : room_definition_order_) {
749 if (room_id == panel_door.room) {
750 // The panel door was defined first (or at the same time as the panel),
751 // so we're good.
752 break;
753 } else if (room_id == panel.room) {
754 // The panel was defined first, so we have to pretend the panel door is
755 // not required for this panel.
756 panel.panel_door = -1;
757 break;
758 }
759 }
760 }
761
762 // Report errors. 781 // Report errors.
763 for (const std::string &area : malconfigured_areas_) { 782 for (const std::string &area : malconfigured_areas_) {
764 TrackerLog(fmt::format("Area data not found for: {}", area)); 783 TrackerLog(fmt::format("Area data not found for: {}", area));
@@ -891,7 +910,7 @@ struct GameData {
891 if (!panel_doors_by_id_.count(full_name)) { 910 if (!panel_doors_by_id_.count(full_name)) {
892 int panel_door_id = panel_doors_.size(); 911 int panel_door_id = panel_doors_.size();
893 panel_doors_by_id_[full_name] = panel_door_id; 912 panel_doors_by_id_[full_name] = panel_door_id;
894 panel_doors_.push_back({.room = AddOrGetRoom(room)}); 913 panel_doors_.push_back({});
895 } 914 }
896 915
897 return panel_doors_by_id_[full_name]; 916 return panel_doors_by_id_[full_name];
@@ -964,6 +983,10 @@ const Panel &GD_GetPanel(int panel_id) {
964 return GetState().panels_.at(panel_id); 983 return GetState().panels_.at(panel_id);
965} 984}
966 985
986int GD_GetPanelBySolveIndex(int solve_index) {
987 return GetState().panel_by_solve_index_.at(solve_index);
988}
989
967const std::vector<PaintingExit> &GD_GetPaintings() { 990const std::vector<PaintingExit> &GD_GetPaintings() {
968 return GetState().paintings_; 991 return GetState().paintings_;
969} 992}
@@ -1010,3 +1033,38 @@ std::optional<int> GD_GetSubwayItemForPainting(const std::string &painting_id) {
1010int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { 1033int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) {
1011 return GetState().subway_item_by_sunwarp_.at(sunwarp); 1034 return GetState().subway_item_by_sunwarp_.at(sunwarp);
1012} 1035}
1036
1037std::string GD_GetItemName(int id) {
1038 auto it = GetState().item_by_ap_id_.find(id);
1039 if (it != GetState().item_by_ap_id_.end()) {
1040 return it->second;
1041 } else {
1042 return "Unknown";
1043 }
1044}
1045
1046LingoColor GetLingoColorForString(const std::string &str) {
1047 if (str == "black") {
1048 return LingoColor::kBlack;
1049 } else if (str == "red") {
1050 return LingoColor::kRed;
1051 } else if (str == "blue") {
1052 return LingoColor::kBlue;
1053 } else if (str == "yellow") {
1054 return LingoColor::kYellow;
1055 } else if (str == "orange") {
1056 return LingoColor::kOrange;
1057 } else if (str == "green") {
1058 return LingoColor::kGreen;
1059 } else if (str == "gray") {
1060 return LingoColor::kGray;
1061 } else if (str == "brown") {
1062 return LingoColor::kBrown;
1063 } else if (str == "purple") {
1064 return LingoColor::kPurple;
1065 } else {
1066 TrackerLog(fmt::format("Invalid color: {}", str));
1067
1068 return LingoColor::kNone;
1069 }
1070}
diff --git a/src/game_data.h b/src/game_data.h index 24760de..ac911e5 100644 --- a/src/game_data.h +++ b/src/game_data.h
@@ -57,6 +57,7 @@ struct Panel {
57 int ap_location_id = -1; 57 int ap_location_id = -1;
58 bool hunt = false; 58 bool hunt = false;
59 int panel_door = -1; 59 int panel_door = -1;
60 int solve_index = -1;
60}; 61};
61 62
62struct ProgressiveRequirement { 63struct ProgressiveRequirement {
@@ -85,10 +86,10 @@ struct Door {
85}; 86};
86 87
87struct PanelDoor { 88struct PanelDoor {
88 int room;
89 int ap_item_id = -1; 89 int ap_item_id = -1;
90 int group_ap_item_id = -1; 90 int group_ap_item_id = -1;
91 std::vector<ProgressiveRequirement> progressives; 91 std::vector<ProgressiveRequirement> progressives;
92 std::string item_name;
92}; 93};
93 94
94struct Exit { 95struct Exit {
@@ -176,6 +177,7 @@ const std::vector<Door>& GD_GetDoors();
176const Door& GD_GetDoor(int door_id); 177const Door& GD_GetDoor(int door_id);
177int GD_GetDoorByName(const std::string& name); 178int GD_GetDoorByName(const std::string& name);
178const Panel& GD_GetPanel(int panel_id); 179const Panel& GD_GetPanel(int panel_id);
180int GD_GetPanelBySolveIndex(int solve_index);
179const PanelDoor& GD_GetPanelDoor(int panel_door_id); 181const PanelDoor& GD_GetPanelDoor(int panel_door_id);
180const std::vector<PaintingExit>& GD_GetPaintings(); 182const std::vector<PaintingExit>& GD_GetPaintings();
181const PaintingExit& GD_GetPaintingExit(int painting_id); 183const PaintingExit& GD_GetPaintingExit(int painting_id);
@@ -188,5 +190,8 @@ const std::vector<SubwayItem>& GD_GetSubwayItems();
188const SubwayItem& GD_GetSubwayItem(int id); 190const SubwayItem& GD_GetSubwayItem(int id);
189std::optional<int> GD_GetSubwayItemForPainting(const std::string& painting_id); 191std::optional<int> GD_GetSubwayItemForPainting(const std::string& painting_id);
190int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); 192int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp);
193std::string GD_GetItemName(int id);
194
195LingoColor GetLingoColorForString(const std::string& str);
191 196
192#endif /* end of include guard: GAME_DATA_H_9C42AC51 */ 197#endif /* end of include guard: GAME_DATA_H_9C42AC51 */
diff --git a/src/godot_variant.cpp b/src/godot_variant.cpp deleted file mode 100644 index 152b9ef..0000000 --- a/src/godot_variant.cpp +++ /dev/null
@@ -1,84 +0,0 @@
1// Godot save decoder algorithm by Chris Souvey.
2
3#include "godot_variant.h"
4
5#include <algorithm>
6#include <charconv>
7#include <cstddef>
8#include <cstdint>
9#include <fstream>
10#include <string>
11#include <tuple>
12#include <variant>
13#include <vector>
14
15namespace {
16
17uint16_t ReadUint16(std::basic_istream<char>& stream) {
18 uint16_t result;
19 stream.read(reinterpret_cast<char*>(&result), 2);
20 return result;
21}
22
23uint32_t ReadUint32(std::basic_istream<char>& stream) {
24 uint32_t result;
25 stream.read(reinterpret_cast<char*>(&result), 4);
26 return result;
27}
28
29GodotVariant ParseVariant(std::basic_istream<char>& stream) {
30 uint16_t type = ReadUint16(stream);
31 stream.ignore(2);
32
33 switch (type) {
34 case 1: {
35 // bool
36 bool boolval = (ReadUint32(stream) == 1);
37 return {boolval};
38 }
39 case 15: {
40 // nodepath
41 uint32_t name_length = ReadUint32(stream) & 0x7fffffff;
42 uint32_t subname_length = ReadUint32(stream) & 0x7fffffff;
43 uint32_t flags = ReadUint32(stream);
44
45 std::vector<std::string> result;
46 for (size_t i = 0; i < name_length + subname_length; i++) {
47 uint32_t char_length = ReadUint32(stream);
48 uint32_t padded_length = (char_length % 4 == 0)
49 ? char_length
50 : (char_length + 4 - (char_length % 4));
51 std::vector<char> next_bytes(padded_length);
52 stream.read(next_bytes.data(), padded_length);
53 std::string next_piece;
54 std::copy(next_bytes.begin(),
55 std::next(next_bytes.begin(), char_length),
56 std::back_inserter(next_piece));
57 result.push_back(next_piece);
58 }
59
60 return {result};
61 }
62 case 19: {
63 // array
64 uint32_t length = ReadUint32(stream) & 0x7fffffff;
65 std::vector<GodotVariant> result;
66 for (size_t i = 0; i < length; i++) {
67 result.push_back(ParseVariant(stream));
68 }
69 return {result};
70 }
71 default: {
72 // eh
73 return {std::monostate{}};
74 }
75 }
76}
77
78} // namespace
79
80GodotVariant ParseGodotFile(std::string filename) {
81 std::ifstream file_stream(filename, std::ios_base::binary);
82 file_stream.ignore(4);
83 return ParseVariant(file_stream);
84}
diff --git a/src/godot_variant.h b/src/godot_variant.h deleted file mode 100644 index 620e569..0000000 --- a/src/godot_variant.h +++ /dev/null
@@ -1,28 +0,0 @@
1#ifndef GODOT_VARIANT_H_ED7F2EB6
2#define GODOT_VARIANT_H_ED7F2EB6
3
4#include <string>
5#include <variant>
6#include <vector>
7
8struct GodotVariant {
9 using value_type = std::variant<std::monostate, bool, std::vector<std::string>, std::vector<GodotVariant>>;
10
11 value_type value;
12
13 GodotVariant(value_type v) : value(v) {}
14
15 bool AsBool() const { return std::get<bool>(value); }
16
17 const std::vector<std::string>& AsNodePath() const {
18 return std::get<std::vector<std::string>>(value);
19 }
20
21 const std::vector<GodotVariant>& AsArray() const {
22 return std::get<std::vector<GodotVariant>>(value);
23 }
24};
25
26GodotVariant ParseGodotFile(std::string filename);
27
28#endif /* end of include guard: GODOT_VARIANT_H_ED7F2EB6 */
diff --git a/src/ipc_state.cpp b/src/ipc_state.cpp index a99fa89..6e2a440 100644 --- a/src/ipc_state.cpp +++ b/src/ipc_state.cpp
@@ -39,7 +39,6 @@ struct IPCState {
39 std::string game_ap_user; 39 std::string game_ap_user;
40 40
41 std::optional<std::tuple<int, int>> player_position; 41 std::optional<std::tuple<int, int>> player_position;
42 std::set<std::string> solved_panels;
43 42
44 // Thread state 43 // Thread state
45 std::unique_ptr<wswrap::WS> ws; 44 std::unique_ptr<wswrap::WS> ws;
@@ -103,12 +102,6 @@ struct IPCState {
103 return player_position; 102 return player_position;
104 } 103 }
105 104
106 std::set<std::string> GetSolvedPanels() {
107 std::lock_guard state_guard(state_mutex);
108
109 return solved_panels;
110 }
111
112 private: 105 private:
113 void Thread() { 106 void Thread() {
114 for (;;) { 107 for (;;) {
@@ -134,7 +127,6 @@ struct IPCState {
134 game_ap_user.clear(); 127 game_ap_user.clear();
135 128
136 player_position = std::nullopt; 129 player_position = std::nullopt;
137 solved_panels.clear();
138 130
139 if (address.empty()) { 131 if (address.empty()) {
140 initialized = false; 132 initialized = false;
@@ -273,7 +265,6 @@ struct IPCState {
273 265
274 slot_matches = false; 266 slot_matches = false;
275 player_position = std::nullopt; 267 player_position = std::nullopt;
276 solved_panels.clear();
277 } 268 }
278 } 269 }
279 270
@@ -314,14 +305,6 @@ struct IPCState {
314 std::make_tuple<int, int>(msg["position"]["x"], msg["position"]["z"]); 305 std::make_tuple<int, int>(msg["position"]["x"], msg["position"]["z"]);
315 306
316 tracker_frame->UpdateIndicators(StateUpdate{.player_position = true}); 307 tracker_frame->UpdateIndicators(StateUpdate{.player_position = true});
317 } else if (msg["cmd"] == "SolvePanels") {
318 std::lock_guard state_guard(state_mutex);
319
320 for (std::string panel : msg["panels"]) {
321 solved_panels.insert(std::move(panel));
322 }
323
324 tracker_frame->UpdateIndicators(StateUpdate{.open_panels_tab = true});
325 } 308 }
326 } 309 }
327 310
@@ -382,7 +365,3 @@ bool IPC_IsConnected() { return GetState().IsConnected(); }
382std::optional<std::tuple<int, int>> IPC_GetPlayerPosition() { 365std::optional<std::tuple<int, int>> IPC_GetPlayerPosition() {
383 return GetState().GetPlayerPosition(); 366 return GetState().GetPlayerPosition();
384} 367}
385
386std::set<std::string> IPC_GetSolvedPanels() {
387 return GetState().GetSolvedPanels();
388}
diff --git a/src/ipc_state.h b/src/ipc_state.h index 7c9d68d..0e6fa51 100644 --- a/src/ipc_state.h +++ b/src/ipc_state.h
@@ -20,6 +20,4 @@ bool IPC_IsConnected();
20 20
21std::optional<std::tuple<int, int>> IPC_GetPlayerPosition(); 21std::optional<std::tuple<int, int>> IPC_GetPlayerPosition();
22 22
23std::set<std::string> IPC_GetSolvedPanels();
24
25#endif /* end of include guard: IPC_STATE_H_6B3B0958 */ 23#endif /* end of include guard: IPC_STATE_H_6B3B0958 */
diff --git a/src/main.cpp b/src/main.cpp index 1d7cc9e..574b6df 100644 --- a/src/main.cpp +++ b/src/main.cpp
@@ -10,7 +10,7 @@
10 10
11class TrackerApp : public wxApp { 11class TrackerApp : public wxApp {
12 public: 12 public:
13 virtual bool OnInit() { 13 virtual bool OnInit() override {
14 GetTrackerConfig().Load(); 14 GetTrackerConfig().Load();
15 15
16 TrackerFrame *frame = new TrackerFrame(); 16 TrackerFrame *frame = new TrackerFrame();
diff --git a/src/settings_dialog.cpp b/src/settings_dialog.cpp index fa7a82f..95df577 100644 --- a/src/settings_dialog.cpp +++ b/src/settings_dialog.cpp
@@ -3,35 +3,43 @@
3#include "tracker_config.h" 3#include "tracker_config.h"
4 4
5SettingsDialog::SettingsDialog() : wxDialog(nullptr, wxID_ANY, "Settings") { 5SettingsDialog::SettingsDialog() : wxDialog(nullptr, wxID_ANY, "Settings") {
6 should_check_for_updates_box_ = new wxCheckBox( 6 wxStaticBoxSizer* main_box =
7 this, wxID_ANY, "Check for updates when the tracker opens"); 7 new wxStaticBoxSizer(wxVERTICAL, this, "General settings");
8
9 should_check_for_updates_box_ =
10 new wxCheckBox(main_box->GetStaticBox(), wxID_ANY,
11 "Check for updates when the tracker opens");
8 hybrid_areas_box_ = new wxCheckBox( 12 hybrid_areas_box_ = new wxCheckBox(
9 this, wxID_ANY, 13 main_box->GetStaticBox(), wxID_ANY,
10 "Use two colors to show that an area has partial availability"); 14 "Use two colors to show that an area has partial availability");
11 show_hunt_panels_box_ = new wxCheckBox(this, wxID_ANY, "Show hunt panels"); 15 track_position_box_ = new wxCheckBox(main_box->GetStaticBox(), wxID_ANY,
12 track_position_box_ = new wxCheckBox(this, wxID_ANY, "Track player position"); 16 "Track player position");
13 17
14 should_check_for_updates_box_->SetValue( 18 should_check_for_updates_box_->SetValue(
15 GetTrackerConfig().should_check_for_updates); 19 GetTrackerConfig().should_check_for_updates);
16 hybrid_areas_box_->SetValue(GetTrackerConfig().hybrid_areas); 20 hybrid_areas_box_->SetValue(GetTrackerConfig().hybrid_areas);
17 show_hunt_panels_box_->SetValue(GetTrackerConfig().show_hunt_panels);
18 track_position_box_->SetValue(GetTrackerConfig().track_position); 21 track_position_box_->SetValue(GetTrackerConfig().track_position);
19 22
20 wxBoxSizer* form_sizer = new wxBoxSizer(wxVERTICAL); 23 main_box->Add(should_check_for_updates_box_, wxSizerFlags().Border());
21 24 main_box->AddSpacer(2);
22 form_sizer->Add(should_check_for_updates_box_, wxSizerFlags().HorzBorder()); 25 main_box->Add(hybrid_areas_box_, wxSizerFlags().Border());
23 form_sizer->AddSpacer(2); 26 main_box->AddSpacer(2);
24 27 main_box->Add(track_position_box_, wxSizerFlags().Border());
25 form_sizer->Add(hybrid_areas_box_, wxSizerFlags().HorzBorder()); 28
26 form_sizer->AddSpacer(2); 29 const wxString visible_panels_choices[] = {"Only show locations",
30 "Show locations and hunt panels",
31 "Show all panels"};
32 visible_panels_box_ =
33 new wxRadioBox(this, wxID_ANY, "Visible panels", wxDefaultPosition,
34 wxDefaultSize, 3, visible_panels_choices, 1);
35 visible_panels_box_->SetSelection(
36 static_cast<int>(GetTrackerConfig().visible_panels));
27 37
28 form_sizer->Add(show_hunt_panels_box_, wxSizerFlags().HorzBorder()); 38 wxBoxSizer* form_sizer = new wxBoxSizer(wxVERTICAL);
29 form_sizer->AddSpacer(2); 39 form_sizer->Add(main_box, wxSizerFlags().Border().Expand());
30 40 form_sizer->Add(visible_panels_box_, wxSizerFlags().Border().Expand());
31 form_sizer->Add(track_position_box_, wxSizerFlags().HorzBorder()); 41 form_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL),
32 form_sizer->AddSpacer(2); 42 wxSizerFlags().Center().Border());
33
34 form_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Center());
35 43
36 SetSizerAndFit(form_sizer); 44 SetSizerAndFit(form_sizer);
37 45
diff --git a/src/settings_dialog.h b/src/settings_dialog.h index 12f2439..c4dacfa 100644 --- a/src/settings_dialog.h +++ b/src/settings_dialog.h
@@ -7,6 +7,10 @@
7#include <wx/wx.h> 7#include <wx/wx.h>
8#endif 8#endif
9 9
10#include <wx/radiobox.h>
11
12#include "tracker_config.h"
13
10class SettingsDialog : public wxDialog { 14class SettingsDialog : public wxDialog {
11 public: 15 public:
12 SettingsDialog(); 16 SettingsDialog();
@@ -15,13 +19,16 @@ class SettingsDialog : public wxDialog {
15 return should_check_for_updates_box_->GetValue(); 19 return should_check_for_updates_box_->GetValue();
16 } 20 }
17 bool GetHybridAreas() const { return hybrid_areas_box_->GetValue(); } 21 bool GetHybridAreas() const { return hybrid_areas_box_->GetValue(); }
18 bool GetShowHuntPanels() const { return show_hunt_panels_box_->GetValue(); } 22 TrackerConfig::VisiblePanels GetVisiblePanels() const {
23 return static_cast<TrackerConfig::VisiblePanels>(
24 visible_panels_box_->GetSelection());
25 }
19 bool GetTrackPosition() const { return track_position_box_->GetValue(); } 26 bool GetTrackPosition() const { return track_position_box_->GetValue(); }
20 27
21 private: 28 private:
22 wxCheckBox* should_check_for_updates_box_; 29 wxCheckBox* should_check_for_updates_box_;
23 wxCheckBox* hybrid_areas_box_; 30 wxCheckBox* hybrid_areas_box_;
24 wxCheckBox* show_hunt_panels_box_; 31 wxRadioBox* visible_panels_box_;
25 wxCheckBox* track_position_box_; 32 wxCheckBox* track_position_box_;
26}; 33};
27 34
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 94292fd..55ac411 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp
@@ -551,6 +551,18 @@ void SubwayMap::Redraw() {
551 brush_color = wxGREEN_BRUSH; 551 brush_color = wxGREEN_BRUSH;
552 } else if (subway_item.special == "starting_room_overhead") { 552 } else if (subway_item.special == "starting_room_overhead") {
553 // Do not draw. 553 // Do not draw.
554 } else if (AP_IsColorShuffle() && subway_item.special &&
555 subway_item.special->starts_with("color_")) {
556 std::string color_name = subway_item.special->substr(6);
557 LingoColor lingo_color = GetLingoColorForString(color_name);
558 int color_item_id = GD_GetItemIdForColor(lingo_color);
559
560 draw_type = ItemDrawType::kBox;
561 if (AP_HasItemSafe(color_item_id)) {
562 brush_color = wxGREEN_BRUSH;
563 } else {
564 brush_color = wxRED_BRUSH;
565 }
554 } else if (subway_item.special == "sun_painting") { 566 } else if (subway_item.special == "sun_painting") {
555 if (!AP_IsPilgrimageEnabled()) { 567 if (!AP_IsPilgrimageEnabled()) {
556 draw_type = ItemDrawType::kOwl; 568 draw_type = ItemDrawType::kOwl;
diff --git a/src/tracker_config.cpp b/src/tracker_config.cpp index aeff669..da5d60a 100644 --- a/src/tracker_config.cpp +++ b/src/tracker_config.cpp
@@ -16,7 +16,9 @@ void TrackerConfig::Load() {
16 asked_to_check_for_updates = file["asked_to_check_for_updates"].as<bool>(); 16 asked_to_check_for_updates = file["asked_to_check_for_updates"].as<bool>();
17 should_check_for_updates = file["should_check_for_updates"].as<bool>(); 17 should_check_for_updates = file["should_check_for_updates"].as<bool>();
18 hybrid_areas = file["hybrid_areas"].as<bool>(); 18 hybrid_areas = file["hybrid_areas"].as<bool>();
19 show_hunt_panels = file["show_hunt_panels"].as<bool>(); 19 if (file["show_hunt_panels"] && file["show_hunt_panels"].as<bool>()) {
20 visible_panels = kHUNT_PANELS;
21 }
20 22
21 if (file["connection_history"]) { 23 if (file["connection_history"]) {
22 for (const auto& connection : file["connection_history"]) { 24 for (const auto& connection : file["connection_history"]) {
@@ -30,6 +32,8 @@ void TrackerConfig::Load() {
30 32
31 ipc_address = file["ipc_address"].as<std::string>(); 33 ipc_address = file["ipc_address"].as<std::string>();
32 track_position = file["track_position"].as<bool>(); 34 track_position = file["track_position"].as<bool>();
35 visible_panels =
36 static_cast<VisiblePanels>(file["visible_panels"].as<int>());
33 } catch (const std::exception&) { 37 } catch (const std::exception&) {
34 // It's fine if the file can't be loaded. 38 // It's fine if the file can't be loaded.
35 } 39 }
@@ -43,7 +47,6 @@ void TrackerConfig::Save() {
43 output["asked_to_check_for_updates"] = asked_to_check_for_updates; 47 output["asked_to_check_for_updates"] = asked_to_check_for_updates;
44 output["should_check_for_updates"] = should_check_for_updates; 48 output["should_check_for_updates"] = should_check_for_updates;
45 output["hybrid_areas"] = hybrid_areas; 49 output["hybrid_areas"] = hybrid_areas;
46 output["show_hunt_panels"] = show_hunt_panels;
47 50
48 output.remove("connection_history"); 51 output.remove("connection_history");
49 for (const ConnectionDetails& details : connection_history) { 52 for (const ConnectionDetails& details : connection_history) {
@@ -57,6 +60,7 @@ void TrackerConfig::Save() {
57 60
58 output["ipc_address"] = ipc_address; 61 output["ipc_address"] = ipc_address;
59 output["track_position"] = track_position; 62 output["track_position"] = track_position;
63 output["visible_panels"] = static_cast<int>(visible_panels);
60 64
61 std::ofstream filewriter(filename_); 65 std::ofstream filewriter(filename_);
62 filewriter << output; 66 filewriter << output;
diff --git a/src/tracker_config.h b/src/tracker_config.h index 4e851dc..df4105d 100644 --- a/src/tracker_config.h +++ b/src/tracker_config.h
@@ -23,14 +23,20 @@ class TrackerConfig {
23 23
24 void Save(); 24 void Save();
25 25
26 enum VisiblePanels {
27 kLOCATIONS_ONLY,
28 kHUNT_PANELS,
29 kALL_PANELS,
30 };
31
26 ConnectionDetails connection_details; 32 ConnectionDetails connection_details;
27 bool asked_to_check_for_updates = false; 33 bool asked_to_check_for_updates = false;
28 bool should_check_for_updates = false; 34 bool should_check_for_updates = false;
29 bool hybrid_areas = false; 35 bool hybrid_areas = false;
30 bool show_hunt_panels = false;
31 std::deque<ConnectionDetails> connection_history; 36 std::deque<ConnectionDetails> connection_history;
32 std::string ipc_address; 37 std::string ipc_address;
33 bool track_position = true; 38 bool track_position = true;
39 VisiblePanels visible_panels = kLOCATIONS_ONLY;
34 40
35 private: 41 private:
36 std::string filename_; 42 std::string filename_;
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index fa68582..e8d7ef6 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp
@@ -9,6 +9,7 @@
9#include <wx/stdpaths.h> 9#include <wx/stdpaths.h>
10#include <wx/webrequest.h> 10#include <wx/webrequest.h>
11 11
12#include <algorithm>
12#include <nlohmann/json.hpp> 13#include <nlohmann/json.hpp>
13#include <sstream> 14#include <sstream>
14 15
@@ -50,7 +51,6 @@ enum TrackerFrameIds {
50 ID_SETTINGS = 3, 51 ID_SETTINGS = 3,
51 ID_ZOOM_IN = 4, 52 ID_ZOOM_IN = 4,
52 ID_ZOOM_OUT = 5, 53 ID_ZOOM_OUT = 5,
53 ID_OPEN_SAVE_FILE = 6,
54 ID_IPC_CONNECT = 7, 54 ID_IPC_CONNECT = 7,
55 ID_LOG_DIALOG = 8, 55 ID_LOG_DIALOG = 8,
56}; 56};
@@ -76,7 +76,6 @@ TrackerFrame::TrackerFrame()
76 wxMenu *menuFile = new wxMenu(); 76 wxMenu *menuFile = new wxMenu();
77 menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago"); 77 menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago");
78 menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo"); 78 menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo");
79 menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O");
80 menuFile->Append(ID_SETTINGS, "&Settings"); 79 menuFile->Append(ID_SETTINGS, "&Settings");
81 menuFile->Append(wxID_EXIT); 80 menuFile->Append(wxID_EXIT);
82 81
@@ -113,7 +112,6 @@ TrackerFrame::TrackerFrame()
113 Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); 112 Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT);
114 Bind(wxEVT_MENU, &TrackerFrame::OnOpenLogWindow, this, ID_LOG_DIALOG); 113 Bind(wxEVT_MENU, &TrackerFrame::OnOpenLogWindow, this, ID_LOG_DIALOG);
115 Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); 114 Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this);
116 Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE);
117 Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &TrackerFrame::OnSashPositionChanged, 115 Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &TrackerFrame::OnSashPositionChanged,
118 this); 116 this);
119 Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); 117 Bind(STATE_RESET, &TrackerFrame::OnStateReset, this);
@@ -251,7 +249,7 @@ void TrackerFrame::OnSettings(wxCommandEvent &event) {
251 GetTrackerConfig().should_check_for_updates = 249 GetTrackerConfig().should_check_for_updates =
252 dlg.GetShouldCheckForUpdates(); 250 dlg.GetShouldCheckForUpdates();
253 GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas(); 251 GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas();
254 GetTrackerConfig().show_hunt_panels = dlg.GetShowHuntPanels(); 252 GetTrackerConfig().visible_panels = dlg.GetVisiblePanels();
255 GetTrackerConfig().track_position = dlg.GetTrackPosition(); 253 GetTrackerConfig().track_position = dlg.GetTrackPosition();
256 GetTrackerConfig().Save(); 254 GetTrackerConfig().Save();
257 255
@@ -302,28 +300,6 @@ void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) {
302 zoom_out_menu_item_->Enable(event.GetSelection() == 1); 300 zoom_out_menu_item_->Enable(event.GetSelection() == 1);
303} 301}
304 302
305void TrackerFrame::OnOpenFile(wxCommandEvent &event) {
306 wxFileDialog open_file_dialog(
307 this, "Open Lingo Save File",
308 fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable",
309 wxStandardPaths::Get().GetUserConfigDir().ToStdString()),
310 AP_GetSaveName(), "Lingo save file (*.save)|*.save",
311 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
312 if (open_file_dialog.ShowModal() == wxID_CANCEL) {
313 return;
314 }
315
316 std::string savedata_path = open_file_dialog.GetPath().ToStdString();
317
318 if (panels_panel_ == nullptr) {
319 panels_panel_ = new TrackerPanel(notebook_);
320 notebook_->AddPage(panels_panel_, "Panels");
321 }
322
323 notebook_->SetSelection(notebook_->FindPage(panels_panel_));
324 panels_panel_->SetSavedataPath(savedata_path);
325}
326
327void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) { 303void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) {
328 notebook_->Refresh(); 304 notebook_->Refresh();
329} 305}
@@ -335,51 +311,41 @@ void TrackerFrame::OnStateReset(wxCommandEvent &event) {
335 options_pane_->OnConnect(); 311 options_pane_->OnConnect();
336 paintings_pane_->ResetIndicators(); 312 paintings_pane_->ResetIndicators();
337 subway_map_->OnConnect(); 313 subway_map_->OnConnect();
338 if (panels_panel_ != nullptr) {
339 notebook_->DeletePage(notebook_->FindPage(panels_panel_));
340 panels_panel_ = nullptr;
341 }
342 Refresh(); 314 Refresh();
343} 315}
344 316
345void TrackerFrame::OnStateChanged(StateChangedEvent &event) { 317void TrackerFrame::OnStateChanged(StateChangedEvent &event) {
346 const StateUpdate &state = event.GetState(); 318 const StateUpdate &state = event.GetState();
347 319
348 if (state.open_panels_tab) { 320 bool hunt_panels = false;
349 if (panels_panel_ == nullptr) { 321 if (GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) {
350 panels_panel_ = new TrackerPanel(notebook_); 322 hunt_panels = std::any_of(
351 panels_panel_->SetPanelsMode(); 323 state.panels.begin(), state.panels.end(), [](int solve_index) {
352 notebook_->AddPage(panels_panel_, "Panels"); 324 return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index)).hunt;
353 } 325 });
354 panels_panel_->UpdateIndicators(/*reset=*/false); 326 } else if (GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS) {
355 if (notebook_->GetSelection() == 2) { 327 hunt_panels = true;
356 Refresh();
357 }
358
359 return;
360 } 328 }
361 329
362 if (!state.items.empty() || !state.paintings.empty() || 330 if (!state.items.empty() || !state.paintings.empty() ||
363 state.cleared_locations || 331 state.cleared_locations || hunt_panels) {
364 (state.hunt_panels && GetTrackerConfig().show_hunt_panels)) {
365 // TODO: The only real reason to reset tracker_panel during an active 332 // TODO: The only real reason to reset tracker_panel during an active
366 // connection is if the hunt panels setting changes. If we remove hunt 333 // connection is if the hunt panels setting changes. If we remove hunt
367 // panels later, we can get rid of this. 334 // panels later, we can get rid of this.
368 tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings); 335 tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings);
369 subway_map_->UpdateIndicators(); 336 subway_map_->UpdateIndicators();
370 if (panels_panel_ != nullptr) {
371 panels_panel_->UpdateIndicators(/*reset=*/false);
372 }
373 Refresh(); 337 Refresh();
374 } else if (state.player_position && GetTrackerConfig().track_position) { 338 } else if (state.player_position && GetTrackerConfig().track_position) {
375 if (notebook_->GetSelection() == 0) { 339 if (notebook_->GetSelection() == 0) {
376 tracker_panel_->Refresh(); 340 tracker_panel_->Refresh();
377 } else if (notebook_->GetSelection() == 2) {
378 panels_panel_->Refresh();
379 } 341 }
380 } 342 }
381 343
382 if (state.achievements) { 344 if (std::any_of(state.panels.begin(), state.panels.end(),
345 [](int solve_index) {
346 return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index))
347 .achievement;
348 })) {
383 achievements_pane_->UpdateIndicators(); 349 achievements_pane_->UpdateIndicators();
384 } 350 }
385 351
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index 131c7b8..00bbe70 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h
@@ -8,6 +8,7 @@
8#endif 8#endif
9 9
10#include <memory> 10#include <memory>
11#include <set>
11 12
12#include "ap_state.h" 13#include "ap_state.h"
13#include "icons.h" 14#include "icons.h"
@@ -52,10 +53,8 @@ struct StateUpdate {
52 std::vector<ItemState> items; 53 std::vector<ItemState> items;
53 bool progression_items = false; 54 bool progression_items = false;
54 std::vector<std::string> paintings; 55 std::vector<std::string> paintings;
55 bool achievements = false;
56 bool open_panels_tab = false;
57 bool cleared_locations = false; 56 bool cleared_locations = false;
58 bool hunt_panels = false; 57 std::set<int> panels;
59 bool player_position = false; 58 bool player_position = false;
60 bool changed_settings = false; 59 bool changed_settings = false;
61}; 60};
@@ -100,7 +99,6 @@ class TrackerFrame : public wxFrame {
100 void OnOpenLogWindow(wxCommandEvent &event); 99 void OnOpenLogWindow(wxCommandEvent &event);
101 void OnCloseLogWindow(wxCloseEvent &event); 100 void OnCloseLogWindow(wxCloseEvent &event);
102 void OnChangePage(wxBookCtrlEvent &event); 101 void OnChangePage(wxBookCtrlEvent &event);
103 void OnOpenFile(wxCommandEvent &event);
104 void OnSashPositionChanged(wxSplitterEvent &event); 102 void OnSashPositionChanged(wxSplitterEvent &event);
105 103
106 void OnStateReset(wxCommandEvent &event); 104 void OnStateReset(wxCommandEvent &event);
@@ -118,7 +116,6 @@ class TrackerFrame : public wxFrame {
118 OptionsPane *options_pane_; 116 OptionsPane *options_pane_;
119 PaintingsPane *paintings_pane_; 117 PaintingsPane *paintings_pane_;
120 SubwayMap *subway_map_; 118 SubwayMap *subway_map_;
121 TrackerPanel *panels_panel_ = nullptr;
122 LogDialog *log_dialog_ = nullptr; 119 LogDialog *log_dialog_ = nullptr;
123 120
124 wxMenuItem *zoom_in_menu_item_; 121 wxMenuItem *zoom_in_menu_item_;
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 9adcb1f..ddb4df9 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp
@@ -9,7 +9,6 @@
9#include "area_popup.h" 9#include "area_popup.h"
10#include "game_data.h" 10#include "game_data.h"
11#include "global.h" 11#include "global.h"
12#include "godot_variant.h"
13#include "ipc_state.h" 12#include "ipc_state.h"
14#include "tracker_config.h" 13#include "tracker_config.h"
15#include "tracker_state.h" 14#include "tracker_state.h"
@@ -52,21 +51,17 @@ TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
52} 51}
53 52
54void TrackerPanel::UpdateIndicators(bool reset) { 53void TrackerPanel::UpdateIndicators(bool reset) {
55 if (panels_mode_ && !savedata_path_) {
56 solved_panels_ = IPC_GetSolvedPanels();
57 }
58
59 if (reset) { 54 if (reset) {
60 for (AreaIndicator &area : areas_) { 55 for (AreaIndicator &area : areas_) {
61 const MapArea &map_area = GD_GetMapArea(area.area_id); 56 const MapArea &map_area = GD_GetMapArea(area.area_id);
62 57
63 if (IsAreaPostgame(area.area_id)) { 58 if ((!AP_IsLocationVisible(map_area.classification) ||
64 area.active = false; 59 IsAreaPostgame(area.area_id)) &&
65 } else if (panels_mode_) { 60 !(map_area.hunt &&
66 area.active = map_area.has_single_panel; 61 GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) &&
67 } else if (!AP_IsLocationVisible(map_area.classification) && 62 !(map_area.has_single_panel &&
68 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && 63 GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS) &&
69 !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { 64 !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) {
70 area.active = false; 65 area.active = false;
71 } else { 66 } else {
72 area.active = true; 67 area.active = true;
@@ -85,47 +80,10 @@ void TrackerPanel::UpdateIndicators(bool reset) {
85 Redraw(); 80 Redraw();
86} 81}
87 82
88void TrackerPanel::SetPanelsMode() { panels_mode_ = true; }
89
90void TrackerPanel::SetSavedataPath(std::string savedata_path) {
91 if (!savedata_path_) {
92 refresh_button_ = new wxButton(this, wxID_ANY, "Refresh");
93 refresh_button_->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this);
94 SetUpRefreshButton();
95 }
96
97 savedata_path_ = savedata_path;
98 panels_mode_ = true;
99
100 UpdateIndicators(/*reset=*/true);
101 RefreshSavedata();
102}
103
104void TrackerPanel::RefreshSavedata() {
105 solved_panels_.clear();
106
107 GodotVariant godot_variant = ParseGodotFile(*savedata_path_);
108 for (const GodotVariant &panel_node : godot_variant.AsArray()) {
109 const std::vector<GodotVariant> &fields = panel_node.AsArray();
110 if (fields[1].AsBool()) {
111 const std::vector<std::string> &nodepath = fields[0].AsNodePath();
112 std::string key = fmt::format("{}/{}", nodepath[3], nodepath[4]);
113 solved_panels_.insert(key);
114 }
115 }
116
117 UpdateIndicators(/*reset=*/false);
118 Refresh();
119}
120
121void TrackerPanel::OnPaint(wxPaintEvent &event) { 83void TrackerPanel::OnPaint(wxPaintEvent &event) {
122 if (GetSize() != rendered_.GetSize()) { 84 if (GetSize() != rendered_.GetSize()) {
123 Resize(); 85 Resize();
124 Redraw(); 86 Redraw();
125
126 if (refresh_button_ != nullptr) {
127 SetUpRefreshButton();
128 }
129 } 87 }
130 88
131 wxBufferedPaintDC dc(this); 89 wxBufferedPaintDC dc(this);
@@ -174,10 +132,6 @@ void TrackerPanel::OnMouseMove(wxMouseEvent &event) {
174 event.Skip(); 132 event.Skip();
175} 133}
176 134
177void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) {
178 RefreshSavedata();
179}
180
181void TrackerPanel::Resize() { 135void TrackerPanel::Resize() {
182 wxSize panel_size = GetClientSize(); 136 wxSize panel_size = GetClientSize();
183 wxSize image_size = map_image_.GetSize(); 137 wxSize image_size = map_image_.GetSize();
@@ -277,23 +231,17 @@ void TrackerPanel::Redraw() {
277 bool has_unreachable_unchecked = false; 231 bool has_unreachable_unchecked = false;
278 for (const Location &section : map_area.locations) { 232 for (const Location &section : map_area.locations) {
279 bool has_unchecked = false; 233 bool has_unchecked = false;
280 if (IsLocationPostgame(section.ap_location_id)) { 234 if (IsLocationWinCondition(section)) {
281 // Nope.
282 } else if (IsLocationWinCondition(section)) {
283 has_unchecked = !AP_HasReachedGoal(); 235 has_unchecked = !AP_HasReachedGoal();
284 } else if (panels_mode_) { 236 } else if (AP_IsLocationVisible(section.classification) &&
285 if (section.single_panel) { 237 !IsLocationPostgame(section.ap_location_id)) {
286 const Panel &panel = GD_GetPanel(*section.single_panel);
287 if (panel.non_counting) {
288 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
289 } else {
290 has_unchecked = !GetSolvedPanels().contains(panel.nodepath);
291 }
292 }
293 } else if (AP_IsLocationVisible(section.classification)) {
294 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); 238 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
295 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { 239 } else if ((section.hunt && GetTrackerConfig().visible_panels ==
296 has_unchecked = !AP_HasCheckedHuntPanel(section.ap_location_id); 240 TrackerConfig::kHUNT_PANELS) ||
241 (section.single_panel && GetTrackerConfig().visible_panels ==
242 TrackerConfig::kALL_PANELS)) {
243 has_unchecked =
244 !AP_IsPanelSolved(GD_GetPanel(*section.single_panel).solve_index);
297 } 245 }
298 246
299 if (has_unchecked) { 247 if (has_unchecked) {
@@ -305,7 +253,7 @@ void TrackerPanel::Redraw() {
305 } 253 }
306 } 254 }
307 255
308 if (AP_IsPaintingShuffle() && !panels_mode_) { 256 if (AP_IsPaintingShuffle()) {
309 for (int painting_id : map_area.paintings) { 257 for (int painting_id : map_area.paintings) {
310 if (IsPaintingPostgame(painting_id)) { 258 if (IsPaintingPostgame(painting_id)) {
311 continue; 259 continue;
@@ -357,8 +305,3 @@ void TrackerPanel::Redraw() {
357 } 305 }
358 } 306 }
359} 307}
360
361void TrackerPanel::SetUpRefreshButton() {
362 refresh_button_->SetSize(FromDIP(15), FromDIP(15), wxDefaultCoord,
363 wxDefaultCoord, wxSIZE_AUTO);
364}
diff --git a/src/tracker_panel.h b/src/tracker_panel.h index abab1bf..6825843 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h
@@ -19,16 +19,6 @@ class TrackerPanel : public wxPanel {
19 19
20 void UpdateIndicators(bool reset); 20 void UpdateIndicators(bool reset);
21 21
22 void SetPanelsMode();
23
24 void SetSavedataPath(std::string savedata_path);
25
26 bool IsPanelsMode() const { return panels_mode_; }
27
28 const std::set<std::string> &GetSolvedPanels() const {
29 return solved_panels_;
30 }
31
32 private: 22 private:
33 struct AreaIndicator { 23 struct AreaIndicator {
34 int area_id = -1; 24 int area_id = -1;
@@ -42,23 +32,16 @@ class TrackerPanel : public wxPanel {
42 32
43 void OnPaint(wxPaintEvent &event); 33 void OnPaint(wxPaintEvent &event);
44 void OnMouseMove(wxMouseEvent &event); 34 void OnMouseMove(wxMouseEvent &event);
45 void OnRefreshSavedata(wxCommandEvent &event);
46 35
47 void Resize(); 36 void Resize();
48 void Redraw(); 37 void Redraw();
49 38
50 void RefreshSavedata();
51
52 void SetUpRefreshButton();
53
54 wxImage map_image_; 39 wxImage map_image_;
55 wxImage player_image_; 40 wxImage player_image_;
56 wxBitmap scaled_map_; 41 wxBitmap scaled_map_;
57 wxBitmap rendered_; 42 wxBitmap rendered_;
58 wxBitmap scaled_player_; 43 wxBitmap scaled_player_;
59 44
60 wxButton *refresh_button_ = nullptr;
61
62 int offset_x_ = 0; 45 int offset_x_ = 0;
63 int offset_y_ = 0; 46 int offset_y_ = 0;
64 double scale_x_ = 0; 47 double scale_x_ = 0;
@@ -66,10 +49,6 @@ class TrackerPanel : public wxPanel {
66 int real_area_size_ = 0; 49 int real_area_size_ = 0;
67 50
68 std::vector<AreaIndicator> areas_; 51 std::vector<AreaIndicator> areas_;
69
70 bool panels_mode_ = false;
71 std::optional<std::string> savedata_path_;
72 std::set<std::string> solved_panels_;
73}; 52};
74 53
75#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ 54#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 40ba6c4..bf2725a 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -473,8 +473,7 @@ class StateCalculator {
473 Decision decision = IsNonGroupedDoorReachable(panel_door_obj); 473 Decision decision = IsNonGroupedDoorReachable(panel_door_obj);
474 474
475 if (report) { 475 if (report) {
476 (*report)[AP_GetItemName(panel_door_obj.ap_item_id)] = 476 (*report)[panel_door_obj.item_name] = (decision == kYes);
477 (decision == kYes);
478 } 477 }
479 478
480 if (decision != kYes) { 479 if (decision != kYes) {
@@ -485,7 +484,7 @@ class StateCalculator {
485 for (int item_id : reqs.items) { 484 for (int item_id : reqs.items) {
486 bool has_item = AP_HasItem(item_id); 485 bool has_item = AP_HasItem(item_id);
487 if (report) { 486 if (report) {
488 (*report)[AP_GetItemName(item_id)] = has_item; 487 (*report)[GD_GetItemName(item_id)] = has_item;
489 } 488 }
490 489
491 if (!has_item) { 490 if (!has_item) {
diff --git a/src/updater.cpp b/src/updater.cpp index 67d5f31..2b05daf 100644 --- a/src/updater.cpp +++ b/src/updater.cpp
@@ -36,7 +36,7 @@ std::string CalculateStringSha256(const wxString& data) {
36 36
37 char output[65] = {0}; 37 char output[65] = {0};
38 for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { 38 for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
39 sprintf(output + (i * 2), "%02x", hash[i]); 39 snprintf(output + (i * 2), 3, "%02x", hash[i]);
40 } 40 }
41 41
42 return std::string(output); 42 return std::string(output);
@@ -172,26 +172,33 @@ void Updater::InstallUpdate(std::string url, std::string checksum,
172 return; 172 return;
173 } 173 }
174 174
175 package_path_.clear(); 175 bool download_issue = false;
176 package_path_.resize(L_tmpnam + 1);
177 tmpnam_s(package_path_.data(), L_tmpnam);
178 176
179 { 177 wxFileName package_path;
180 wxFileOutputStream writeOut(package_path_); 178 package_path.AssignTempFileName("");
179
180 if (!package_path.IsOk()) {
181 download_issue = true;
182 } else {
183 wxFileOutputStream writeOut(package_path.GetFullPath());
181 wxString fileData = packageRequest.GetResponse().AsString(); 184 wxString fileData = packageRequest.GetResponse().AsString();
182 writeOut.WriteAll(fileData.c_str(), fileData.length()); 185 writeOut.WriteAll(fileData.c_str(), fileData.length());
183 186
184 std::string downloadedChecksum = CalculateStringSha256(fileData); 187 std::string downloadedChecksum = CalculateStringSha256(fileData);
185 if (downloadedChecksum != checksum) { 188 if (downloadedChecksum != checksum) {
186 if (wxMessageBox("There was an issue downloading the update. Would you " 189 download_issue = true;
187 "like to manually download it instead?",
188 "Error", wxYES_NO | wxICON_ERROR) == wxID_YES) {
189 wxLaunchDefaultBrowser(kChangelogUrl);
190 }
191 return;
192 } 190 }
193 } 191 }
194 192
193 if (download_issue) {
194 if (wxMessageBox("There was an issue downloading the update. Would you "
195 "like to manually download it instead?",
196 "Error", wxYES_NO | wxICON_ERROR) == wxID_YES) {
197 wxLaunchDefaultBrowser(kChangelogUrl);
198 }
199 return;
200 }
201
195 std::filesystem::path newArea = GetExecutableDirectory(); 202 std::filesystem::path newArea = GetExecutableDirectory();
196 std::filesystem::path oldArea = newArea / "old"; 203 std::filesystem::path oldArea = newArea / "old";
197 std::set<std::filesystem::path> folders; 204 std::set<std::filesystem::path> folders;
@@ -241,7 +248,7 @@ void Updater::InstallUpdate(std::string url, std::string checksum,
241 } 248 }
242 } 249 }
243 250
244 wxFileInputStream fileInputStream(package_path_); 251 wxFileInputStream fileInputStream(package_path.GetFullPath());
245 wxZipInputStream zipStream(fileInputStream); 252 wxZipInputStream zipStream(fileInputStream);
246 std::unique_ptr<wxZipEntry> zipEntry; 253 std::unique_ptr<wxZipEntry> zipEntry;
247 while ((zipEntry = std::unique_ptr<wxZipEntry>(zipStream.GetNextEntry())) != 254 while ((zipEntry = std::unique_ptr<wxZipEntry>(zipStream.GetNextEntry())) !=
diff --git a/src/updater.h b/src/updater.h index 2d2f746..c604a49 100644 --- a/src/updater.h +++ b/src/updater.h
@@ -41,7 +41,6 @@ class Updater : public wxEvtHandler {
41 41
42 wxFrame* parent_; 42 wxFrame* parent_;
43 UpdateState update_state_ = UpdateState::GetVersionInvisible; 43 UpdateState update_state_ = UpdateState::GetVersionInvisible;
44 std::string package_path_;
45}; 44};
46 45
47#endif /* end of include guard: UPDATER_H_809E7381 */ 46#endif /* end of include guard: UPDATER_H_809E7381 */
diff --git a/src/version.h b/src/version.h index 3d678e5..3439fda 100644 --- a/src/version.h +++ b/src/version.h
@@ -36,6 +36,6 @@ struct Version {
36 } 36 }
37}; 37};
38 38
39constexpr const Version kTrackerVersion = Version(0, 12, 3); 39constexpr const Version kTrackerVersion = Version(2, 0, 2);
40 40
41#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file 41#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file
diff --git a/vcpkg.json b/vcpkg.json index e13d228..581a507 100644 --- a/vcpkg.json +++ b/vcpkg.json
@@ -1,6 +1,5 @@
1{ 1{
2 "dependencies": [ 2 "dependencies": [
3 "websocketpp",
4 "wxwidgets", 3 "wxwidgets",
5 "openssl", 4 "openssl",
6 "yaml-cpp", 5 "yaml-cpp",
diff --git a/vendor/vcpkg b/vendor/vcpkg
Subproject 3dd44b931481d7a8e9ba412621fa810232b6628 Subproject df8bfe519564ae001903e5cdd32af0999531ef7
diff --git a/vendor/websocketpp b/vendor/websocketpp new file mode 160000
Subproject 1b11fd301531e6df35a6107c1e8665b1e77a2d8