about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-07-19 03:51:23 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2024-07-19 03:51:23 -0400
commit8c5b719469bc61e33a451d9b3aeb66c7b0a6d68e (patch)
tree1f452dc8a601630a1fd50f4ee3f8ea25aed7b315
parentb80e1b888a7203312119e5bfad9e26c2c17d9b9f (diff)
downloadlingo-ap-tracker-8c5b719469bc61e33a451d9b3aeb66c7b0a6d68e.tar.gz
lingo-ap-tracker-8c5b719469bc61e33a451d9b3aeb66c7b0a6d68e.tar.bz2
lingo-ap-tracker-8c5b719469bc61e33a451d9b3aeb66c7b0a6d68e.zip
Added savedata analyzer
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/ap_state.cpp7
-rw-r--r--src/ap_state.h2
-rw-r--r--src/area_popup.cpp40
-rw-r--r--src/game_data.cpp9
-rw-r--r--src/game_data.h3
-rw-r--r--src/godot_variant.cpp82
-rw-r--r--src/godot_variant.h28
-rw-r--r--src/tracker_frame.cpp35
-rw-r--r--src/tracker_frame.h2
-rw-r--r--src/tracker_panel.cpp54
-rw-r--r--src/tracker_panel.h19
12 files changed, 269 insertions, 13 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f9f1117..e1cb7f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -48,6 +48,7 @@ add_executable(lingo_ap_tracker
48 "src/subway_map.cpp" 48 "src/subway_map.cpp"
49 "src/network_set.cpp" 49 "src/network_set.cpp"
50 "src/logger.cpp" 50 "src/logger.cpp"
51 "src/godot_variant.cpp"
51 "vendor/whereami/whereami.c" 52 "vendor/whereami/whereami.c"
52) 53)
53set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) 54set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20)
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index ebc5fc9..f8d4ee0 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -52,6 +52,8 @@ struct APState {
52 std::list<std::string> tracked_data_storage_keys; 52 std::list<std::string> tracked_data_storage_keys;
53 std::string victory_data_storage_key; 53 std::string victory_data_storage_key;
54 54
55 std::string save_name;
56
55 std::map<int64_t, int> inventory; 57 std::map<int64_t, int> inventory;
56 std::set<int64_t> checked_locations; 58 std::set<int64_t> checked_locations;
57 std::map<std::string, std::any> data_storage; 59 std::map<std::string, std::any> data_storage;
@@ -131,6 +133,7 @@ struct APState {
131 cert_store); 133 cert_store);
132 } 134 }
133 135
136 save_name.clear();
134 inventory.clear(); 137 inventory.clear();
135 checked_locations.clear(); 138 checked_locations.clear();
136 data_storage.clear(); 139 data_storage.clear();
@@ -228,6 +231,8 @@ struct APState {
228 fmt::format("Connected to Archipelago! ({}@{})", player, server)); 231 fmt::format("Connected to Archipelago! ({}@{})", player, server));
229 TrackerLog("Connected to Archipelago!"); 232 TrackerLog("Connected to Archipelago!");
230 233
234 save_name = fmt::format("zzAP_{}_{}.save", apclient->get_seed(),
235 apclient->get_player_number());
231 data_storage_prefix = 236 data_storage_prefix =
232 fmt::format("Lingo_{}_", apclient->get_player_number()); 237 fmt::format("Lingo_{}_", apclient->get_player_number());
233 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); 238 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>();
@@ -509,6 +514,8 @@ void AP_Connect(std::string server, std::string player, std::string password) {
509 GetState().Connect(server, player, password); 514 GetState().Connect(server, player, password);
510} 515}
511 516
517std::string AP_GetSaveName() { return GetState().save_name; }
518
512bool AP_HasCheckedGameLocation(int location_id) { 519bool AP_HasCheckedGameLocation(int location_id) {
513 return GetState().HasCheckedGameLocation(location_id); 520 return GetState().HasCheckedGameLocation(location_id);
514} 521}
diff --git a/src/ap_state.h b/src/ap_state.h index 7af7395..f8936e5 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -43,6 +43,8 @@ void AP_SetTrackerFrame(TrackerFrame* tracker_frame);
43 43
44void AP_Connect(std::string server, std::string player, std::string password); 44void AP_Connect(std::string server, std::string player, std::string password);
45 45
46std::string AP_GetSaveName();
47
46bool AP_HasCheckedGameLocation(int location_id); 48bool AP_HasCheckedGameLocation(int location_id);
47 49
48bool AP_HasCheckedHuntPanel(int location_id); 50bool AP_HasCheckedHuntPanel(int location_id);
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index ca3b352..b18ba62 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp
@@ -2,10 +2,13 @@
2 2
3#include <wx/dcbuffer.h> 3#include <wx/dcbuffer.h>
4 4
5#include <algorithm>
6
5#include "ap_state.h" 7#include "ap_state.h"
6#include "game_data.h" 8#include "game_data.h"
7#include "global.h" 9#include "global.h"
8#include "tracker_config.h" 10#include "tracker_config.h"
11#include "tracker_panel.h"
9#include "tracker_state.h" 12#include "tracker_state.h"
10 13
11AreaPopup::AreaPopup(wxWindow* parent, int area_id) 14AreaPopup::AreaPopup(wxWindow* parent, int area_id)
@@ -43,15 +46,23 @@ void AreaPopup::UpdateIndicators() {
43 46
44 mem_dc.SetFont(GetFont()); 47 mem_dc.SetFont(GetFont());
45 48
49 TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent());
50
46 std::vector<int> real_locations; 51 std::vector<int> real_locations;
47 52
48 for (int section_id = 0; section_id < map_area.locations.size(); 53 for (int section_id = 0; section_id < map_area.locations.size();
49 section_id++) { 54 section_id++) {
50 const Location& location = map_area.locations.at(section_id); 55 const Location& location = map_area.locations.at(section_id);
51 56
52 if (!AP_IsLocationVisible(location.classification) && 57 if (tracker_panel->IsPanelsMode()) {
53 !(location.hunt && GetTrackerConfig().show_hunt_panels)) { 58 if (!location.panel) {
54 continue; 59 continue;
60 }
61 } else {
62 if (!AP_IsLocationVisible(location.classification) &&
63 !(location.hunt && GetTrackerConfig().show_hunt_panels)) {
64 continue;
65 }
55 } 66 }
56 67
57 real_locations.push_back(section_id); 68 real_locations.push_back(section_id);
@@ -65,7 +76,7 @@ void AreaPopup::UpdateIndicators() {
65 } 76 }
66 } 77 }
67 78
68 if (AP_IsPaintingShuffle()) { 79 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) {
69 for (int painting_id : map_area.paintings) { 80 for (int painting_id : map_area.paintings) {
70 const PaintingExit& painting = GD_GetPaintingExit(painting_id); 81 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
71 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. 82 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name.
@@ -102,10 +113,21 @@ void AreaPopup::UpdateIndicators() {
102 for (int section_id : real_locations) { 113 for (int section_id : real_locations) {
103 const Location& location = map_area.locations.at(section_id); 114 const Location& location = map_area.locations.at(section_id);
104 115
105 bool checked = 116 bool checked = false;
106 AP_HasCheckedGameLocation(location.ap_location_id) || 117 if (IsLocationWinCondition(location)) {
107 (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id)) || 118 checked = AP_HasReachedGoal();
108 (IsLocationWinCondition(location) && AP_HasReachedGoal()); 119 } else if (tracker_panel->IsPanelsMode()) {
120 checked = location.panel && std::any_of(
121 location.panels.begin(), location.panels.end(),
122 [tracker_panel](int panel_id) {
123 const Panel& panel = GD_GetPanel(panel_id);
124 return tracker_panel->GetSolvedPanels().contains(panel.nodepath);
125 });
126 } else {
127 checked =
128 AP_HasCheckedGameLocation(location.ap_location_id) ||
129 (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id));
130 }
109 131
110 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; 132 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;
111 133
@@ -123,7 +145,7 @@ void AreaPopup::UpdateIndicators() {
123 cur_height += 10 + 32; 145 cur_height += 10 + 32;
124 } 146 }
125 147
126 if (AP_IsPaintingShuffle()) { 148 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) {
127 for (int painting_id : map_area.paintings) { 149 for (int painting_id : map_area.paintings) {
128 const PaintingExit& painting = GD_GetPaintingExit(painting_id); 150 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
129 151
diff --git a/src/game_data.cpp b/src/game_data.cpp index 4c0104f..b8e1386 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -265,6 +265,11 @@ struct GameData {
265 panel_it.second["location_name"].as<std::string>(); 265 panel_it.second["location_name"].as<std::string>();
266 } 266 }
267 267
268 if (panel_it.second["id"]) {
269 panels_[panel_id].nodepath =
270 panel_it.second["id"].as<std::string>();
271 }
272
268 if (panel_it.second["hunt"]) { 273 if (panel_it.second["hunt"]) {
269 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); 274 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>();
270 } 275 }
@@ -564,7 +569,8 @@ struct GameData {
564 .room = panel.room, 569 .room = panel.room,
565 .panels = {panel.id}, 570 .panels = {panel.id},
566 .classification = classification, 571 .classification = classification,
567 .hunt = panel.hunt}); 572 .hunt = panel.hunt,
573 .panel = true});
568 locations_by_name[location_name] = {area_id, 574 locations_by_name[location_name] = {area_id,
569 map_area.locations.size() - 1}; 575 map_area.locations.size() - 1};
570 } 576 }
@@ -617,6 +623,7 @@ struct GameData {
617 for (const Location &location : map_area.locations) { 623 for (const Location &location : map_area.locations) {
618 map_area.classification |= location.classification; 624 map_area.classification |= location.classification;
619 map_area.hunt |= location.hunt; 625 map_area.hunt |= location.hunt;
626 map_area.panel |= location.panel;
620 } 627 }
621 } 628 }
622 629
diff --git a/src/game_data.h b/src/game_data.h index 23f7b3a..71bc533 100644 --- a/src/game_data.h +++ b/src/game_data.h
@@ -43,6 +43,7 @@ struct Panel {
43 int id; 43 int id;
44 int room; 44 int room;
45 std::string name; 45 std::string name;
46 std::string nodepath;
46 std::vector<LingoColor> colors; 47 std::vector<LingoColor> colors;
47 std::vector<int> required_rooms; 48 std::vector<int> required_rooms;
48 std::vector<int> required_doors; 49 std::vector<int> required_doors;
@@ -113,6 +114,7 @@ struct Location {
113 std::vector<int> panels; 114 std::vector<int> panels;
114 int classification = 0; 115 int classification = 0;
115 bool hunt = false; 116 bool hunt = false;
117 bool panel = false;
116}; 118};
117 119
118struct MapArea { 120struct MapArea {
@@ -124,6 +126,7 @@ struct MapArea {
124 int map_y; 126 int map_y;
125 int classification = 0; 127 int classification = 0;
126 bool hunt = false; 128 bool hunt = false;
129 bool panel = false;
127}; 130};
128 131
129enum class SubwaySunwarpType { 132enum class SubwaySunwarpType {
diff --git a/src/godot_variant.cpp b/src/godot_variant.cpp new file mode 100644 index 0000000..152408f --- /dev/null +++ b/src/godot_variant.cpp
@@ -0,0 +1,82 @@
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 <fstream>
9#include <string>
10#include <tuple>
11#include <variant>
12#include <vector>
13
14namespace {
15
16uint16_t ReadUint16(std::basic_istream<char>& stream) {
17 uint16_t result;
18 stream.read(reinterpret_cast<char*>(&result), 2);
19 return result;
20}
21
22uint32_t ReadUint32(std::basic_istream<char>& stream) {
23 uint32_t result;
24 stream.read(reinterpret_cast<char*>(&result), 4);
25 return result;
26}
27
28GodotVariant ParseVariant(std::basic_istream<char>& stream) {
29 uint16_t type = ReadUint16(stream);
30 stream.ignore(2);
31
32 switch (type) {
33 case 1: {
34 // bool
35 bool boolval = (ReadUint32(stream) == 1);
36 return {boolval};
37 }
38 case 15: {
39 // nodepath
40 uint32_t name_length = ReadUint32(stream) & 0x7fffffff;
41 uint32_t subname_length = ReadUint32(stream) & 0x7fffffff;
42 uint32_t flags = ReadUint32(stream);
43
44 std::vector<std::string> result;
45 for (size_t i = 0; i < name_length + subname_length; i++) {
46 uint32_t char_length = ReadUint32(stream);
47 uint32_t padded_length = (char_length % 4 == 0)
48 ? char_length
49 : (char_length + 4 - (char_length % 4));
50 std::vector<char> next_bytes(padded_length);
51 stream.read(next_bytes.data(), padded_length);
52 std::string next_piece;
53 std::copy(next_bytes.begin(),
54 std::next(next_bytes.begin(), char_length),
55 std::back_inserter(next_piece));
56 result.push_back(next_piece);
57 }
58
59 return {result};
60 }
61 case 19: {
62 // array
63 uint32_t length = ReadUint32(stream) & 0x7fffffff;
64 std::vector<GodotVariant> result;
65 for (size_t i = 0; i < length; i++) {
66 result.push_back(ParseVariant(stream));
67 }
68 return {result};
69 }
70 default: {
71 // eh
72 }
73 }
74}
75
76} // namespace
77
78GodotVariant ParseGodotFile(std::string filename) {
79 std::ifstream file_stream(filename, std::ios_base::binary);
80 file_stream.ignore(4);
81 return ParseVariant(file_stream);
82}
diff --git a/src/godot_variant.h b/src/godot_variant.h new file mode 100644 index 0000000..620e569 --- /dev/null +++ b/src/godot_variant.h
@@ -0,0 +1,28 @@
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/tracker_frame.cpp b/src/tracker_frame.cpp index 80fd137..3b6beda 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp
@@ -2,9 +2,12 @@
2 2
3#include <wx/aboutdlg.h> 3#include <wx/aboutdlg.h>
4#include <wx/choicebk.h> 4#include <wx/choicebk.h>
5#include <wx/filedlg.h>
5#include <wx/notebook.h> 6#include <wx/notebook.h>
7#include <wx/stdpaths.h>
6#include <wx/webrequest.h> 8#include <wx/webrequest.h>
7 9
10#include <fmt/core.h>
8#include <nlohmann/json.hpp> 11#include <nlohmann/json.hpp>
9#include <sstream> 12#include <sstream>
10 13
@@ -23,6 +26,7 @@ enum TrackerFrameIds {
23 ID_SETTINGS = 3, 26 ID_SETTINGS = 3,
24 ID_ZOOM_IN = 4, 27 ID_ZOOM_IN = 4,
25 ID_ZOOM_OUT = 5, 28 ID_ZOOM_OUT = 5,
29 ID_OPEN_SAVE_FILE = 6,
26}; 30};
27 31
28wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); 32wxDEFINE_EVENT(STATE_RESET, wxCommandEvent);
@@ -38,6 +42,7 @@ TrackerFrame::TrackerFrame()
38 42
39 wxMenu *menuFile = new wxMenu(); 43 wxMenu *menuFile = new wxMenu();
40 menuFile->Append(ID_CONNECT, "&Connect"); 44 menuFile->Append(ID_CONNECT, "&Connect");
45 menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O");
41 menuFile->Append(ID_SETTINGS, "&Settings"); 46 menuFile->Append(ID_SETTINGS, "&Settings");
42 menuFile->Append(wxID_EXIT); 47 menuFile->Append(wxID_EXIT);
43 48
@@ -71,6 +76,7 @@ TrackerFrame::TrackerFrame()
71 Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); 76 Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN);
72 Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); 77 Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT);
73 Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); 78 Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this);
79 Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE);
74 Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); 80 Bind(STATE_RESET, &TrackerFrame::OnStateReset, this);
75 Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); 81 Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this);
76 Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); 82 Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this);
@@ -204,10 +210,36 @@ void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) {
204 zoom_out_menu_item_->Enable(event.GetSelection() == 1); 210 zoom_out_menu_item_->Enable(event.GetSelection() == 1);
205} 211}
206 212
213void TrackerFrame::OnOpenFile(wxCommandEvent& event) {
214 wxFileDialog open_file_dialog(
215 this, "Open Lingo Save File",
216 fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable",
217 wxStandardPaths::Get().GetUserConfigDir().ToStdString()),
218 AP_GetSaveName(), "Lingo save file (*.save)|*.save",
219 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
220 if (open_file_dialog.ShowModal() == wxID_CANCEL) {
221 return;
222 }
223
224 std::string savedata_path = open_file_dialog.GetPath().ToStdString();
225
226 if (panels_panel_ == nullptr) {
227 panels_panel_ = new TrackerPanel(notebook_);
228 notebook_->AddPage(panels_panel_, "Panels");
229 }
230
231 notebook_->SetSelection(notebook_->FindPage(panels_panel_));
232 panels_panel_->SetSavedataPath(savedata_path);
233}
234
207void TrackerFrame::OnStateReset(wxCommandEvent& event) { 235void TrackerFrame::OnStateReset(wxCommandEvent& event) {
208 tracker_panel_->UpdateIndicators(); 236 tracker_panel_->UpdateIndicators();
209 achievements_pane_->UpdateIndicators(); 237 achievements_pane_->UpdateIndicators();
210 subway_map_->OnConnect(); 238 subway_map_->OnConnect();
239 if (panels_panel_ != nullptr) {
240 notebook_->DeletePage(notebook_->FindPage(panels_panel_));
241 panels_panel_ = nullptr;
242 }
211 Refresh(); 243 Refresh();
212} 244}
213 245
@@ -215,6 +247,9 @@ void TrackerFrame::OnStateChanged(wxCommandEvent &event) {
215 tracker_panel_->UpdateIndicators(); 247 tracker_panel_->UpdateIndicators();
216 achievements_pane_->UpdateIndicators(); 248 achievements_pane_->UpdateIndicators();
217 subway_map_->UpdateIndicators(); 249 subway_map_->UpdateIndicators();
250 if (panels_panel_ != nullptr) {
251 panels_panel_->UpdateIndicators();
252 }
218 Refresh(); 253 Refresh();
219} 254}
220 255
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index f7cb3f2..19bd0b3 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h
@@ -35,6 +35,7 @@ class TrackerFrame : public wxFrame {
35 void OnZoomIn(wxCommandEvent &event); 35 void OnZoomIn(wxCommandEvent &event);
36 void OnZoomOut(wxCommandEvent &event); 36 void OnZoomOut(wxCommandEvent &event);
37 void OnChangePage(wxBookCtrlEvent &event); 37 void OnChangePage(wxBookCtrlEvent &event);
38 void OnOpenFile(wxCommandEvent &event);
38 39
39 void OnStateReset(wxCommandEvent &event); 40 void OnStateReset(wxCommandEvent &event);
40 void OnStateChanged(wxCommandEvent &event); 41 void OnStateChanged(wxCommandEvent &event);
@@ -46,6 +47,7 @@ class TrackerFrame : public wxFrame {
46 TrackerPanel *tracker_panel_; 47 TrackerPanel *tracker_panel_;
47 AchievementsPane *achievements_pane_; 48 AchievementsPane *achievements_pane_;
48 SubwayMap *subway_map_; 49 SubwayMap *subway_map_;
50 TrackerPanel *panels_panel_ = nullptr;
49 51
50 wxMenuItem *zoom_in_menu_item_; 52 wxMenuItem *zoom_in_menu_item_;
51 wxMenuItem *zoom_out_menu_item_; 53 wxMenuItem *zoom_out_menu_item_;
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index d60c1b6..2e1497b 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp
@@ -1,11 +1,15 @@
1#include "tracker_panel.h" 1#include "tracker_panel.h"
2 2
3#include <fmt/core.h>
3#include <wx/dcbuffer.h> 4#include <wx/dcbuffer.h>
4 5
6#include <algorithm>
7
5#include "ap_state.h" 8#include "ap_state.h"
6#include "area_popup.h" 9#include "area_popup.h"
7#include "game_data.h" 10#include "game_data.h"
8#include "global.h" 11#include "global.h"
12#include "godot_variant.h"
9#include "tracker_config.h" 13#include "tracker_config.h"
10#include "tracker_state.h" 14#include "tracker_state.h"
11 15
@@ -53,6 +57,35 @@ void TrackerPanel::UpdateIndicators() {
53 Redraw(); 57 Redraw();
54} 58}
55 59
60void TrackerPanel::SetSavedataPath(std::string savedata_path) {
61 if (!panels_mode_) {
62 wxButton *refresh_button = new wxButton(this, wxID_ANY, "Refresh", {15, 15});
63 refresh_button->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this);
64 }
65
66 savedata_path_ = savedata_path;
67 panels_mode_ = true;
68
69 RefreshSavedata();
70}
71
72void TrackerPanel::RefreshSavedata() {
73 solved_panels_.clear();
74
75 GodotVariant godot_variant = ParseGodotFile(*savedata_path_);
76 for (const GodotVariant &panel_node : godot_variant.AsArray()) {
77 const std::vector<GodotVariant> &fields = panel_node.AsArray();
78 if (fields[1].AsBool()) {
79 const std::vector<std::string> &nodepath = fields[0].AsNodePath();
80 std::string key = fmt::format("{}/{}", nodepath[3], nodepath[4]);
81 solved_panels_.insert(key);
82 }
83 }
84
85 UpdateIndicators();
86 Refresh();
87}
88
56void TrackerPanel::OnPaint(wxPaintEvent &event) { 89void TrackerPanel::OnPaint(wxPaintEvent &event) {
57 if (GetSize() != rendered_.GetSize()) { 90 if (GetSize() != rendered_.GetSize()) {
58 Redraw(); 91 Redraw();
@@ -92,6 +125,10 @@ void TrackerPanel::OnMouseMove(wxMouseEvent &event) {
92 event.Skip(); 125 event.Skip();
93} 126}
94 127
128void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) {
129 RefreshSavedata();
130}
131
95void TrackerPanel::Redraw() { 132void TrackerPanel::Redraw() {
96 wxSize panel_size = GetSize(); 133 wxSize panel_size = GetSize();
97 wxSize image_size = map_image_.GetSize(); 134 wxSize image_size = map_image_.GetSize();
@@ -142,21 +179,32 @@ void TrackerPanel::Redraw() {
142 179
143 for (AreaIndicator &area : areas_) { 180 for (AreaIndicator &area : areas_) {
144 const MapArea &map_area = GD_GetMapArea(area.area_id); 181 const MapArea &map_area = GD_GetMapArea(area.area_id);
145 if (!AP_IsLocationVisible(map_area.classification) && 182 if (panels_mode_) {
183 area.active = map_area.panel;
184 } else if (!AP_IsLocationVisible(map_area.classification) &&
146 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && 185 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) &&
147 !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { 186 !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) {
148 area.active = false; 187 area.active = false;
149 continue;
150 } else { 188 } else {
151 area.active = true; 189 area.active = true;
152 } 190 }
153 191
192 if (!area.active) {
193 continue;
194 }
195
154 bool has_reachable_unchecked = false; 196 bool has_reachable_unchecked = false;
155 bool has_unreachable_unchecked = false; 197 bool has_unreachable_unchecked = false;
156 for (const Location &section : map_area.locations) { 198 for (const Location &section : map_area.locations) {
157 bool has_unchecked = false; 199 bool has_unchecked = false;
158 if (IsLocationWinCondition(section)) { 200 if (IsLocationWinCondition(section)) {
159 has_unchecked = !AP_HasReachedGoal(); 201 has_unchecked = !AP_HasReachedGoal();
202 } else if (panels_mode_) {
203 has_unchecked = section.panel && std::any_of(
204 section.panels.begin(), section.panels.end(), [this](int panel_id) {
205 const Panel &panel = GD_GetPanel(panel_id);
206 return !solved_panels_.contains(panel.nodepath);
207 });
160 } else if (AP_IsLocationVisible(section.classification)) { 208 } else if (AP_IsLocationVisible(section.classification)) {
161 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); 209 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
162 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { 210 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) {
@@ -172,7 +220,7 @@ void TrackerPanel::Redraw() {
172 } 220 }
173 } 221 }
174 222
175 if (AP_IsPaintingShuffle()) { 223 if (AP_IsPaintingShuffle() && !panels_mode_) {
176 for (int painting_id : map_area.paintings) { 224 for (int painting_id : map_area.paintings) {
177 const PaintingExit &painting = GD_GetPaintingExit(painting_id); 225 const PaintingExit &painting = GD_GetPaintingExit(painting_id);
178 if (!AP_IsPaintingChecked(painting.internal_id)) { 226 if (!AP_IsPaintingChecked(painting.internal_id)) {
diff --git a/src/tracker_panel.h b/src/tracker_panel.h index 06ec7a0..e1f515d 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h
@@ -7,6 +7,10 @@
7#include <wx/wx.h> 7#include <wx/wx.h>
8#endif 8#endif
9 9
10#include <optional>
11#include <set>
12#include <string>
13
10class AreaPopup; 14class AreaPopup;
11 15
12class TrackerPanel : public wxPanel { 16class TrackerPanel : public wxPanel {
@@ -15,6 +19,14 @@ class TrackerPanel : public wxPanel {
15 19
16 void UpdateIndicators(); 20 void UpdateIndicators();
17 21
22 void SetSavedataPath(std::string savedata_path);
23
24 bool IsPanelsMode() const { return panels_mode_; }
25
26 const std::set<std::string> &GetSolvedPanels() const {
27 return solved_panels_;
28 }
29
18 private: 30 private:
19 struct AreaIndicator { 31 struct AreaIndicator {
20 int area_id = -1; 32 int area_id = -1;
@@ -28,9 +40,12 @@ class TrackerPanel : public wxPanel {
28 40
29 void OnPaint(wxPaintEvent &event); 41 void OnPaint(wxPaintEvent &event);
30 void OnMouseMove(wxMouseEvent &event); 42 void OnMouseMove(wxMouseEvent &event);
43 void OnRefreshSavedata(wxCommandEvent &event);
31 44
32 void Redraw(); 45 void Redraw();
33 46
47 void RefreshSavedata();
48
34 wxImage map_image_; 49 wxImage map_image_;
35 wxImage player_image_; 50 wxImage player_image_;
36 wxBitmap rendered_; 51 wxBitmap rendered_;
@@ -42,6 +57,10 @@ class TrackerPanel : public wxPanel {
42 double scale_y_ = 0; 57 double scale_y_ = 0;
43 58
44 std::vector<AreaIndicator> areas_; 59 std::vector<AreaIndicator> areas_;
60
61 bool panels_mode_ = false;
62 std::optional<std::string> savedata_path_;
63 std::set<std::string> solved_panels_;
45}; 64};
46 65
47#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ 66#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */