about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
25 files changed, 315 insertions, 473 deletions
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