about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-07-24 09:40:22 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2024-07-24 09:40:22 -0400
commitc443acfd0b25b3e4f3446f795777b8dd18b00e2b (patch)
tree86340c00f1723310cbab02f0ebc496b25a780278
parent378766bcee3cad04256ada937f96b232aba85cf3 (diff)
parentab5206255603f6401d9c216ffce26607da16ad33 (diff)
downloadlingo-ap-tracker-c443acfd0b25b3e4f3446f795777b8dd18b00e2b.tar.gz
lingo-ap-tracker-c443acfd0b25b3e4f3446f795777b8dd18b00e2b.tar.bz2
lingo-ap-tracker-c443acfd0b25b3e4f3446f795777b8dd18b00e2b.zip
Merge branch 'main' into panels
-rw-r--r--CHANGELOG.md21
-rw-r--r--CMakeLists.txt1
-rw-r--r--VERSION2
-rw-r--r--src/ap_state.cpp7
-rw-r--r--src/ap_state.h2
-rw-r--r--src/area_popup.cpp48
-rw-r--r--src/game_data.cpp9
-rw-r--r--src/game_data.h3
-rw-r--r--src/godot_variant.cpp83
-rw-r--r--src/godot_variant.h28
-rw-r--r--src/subway_map.cpp7
-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
-rw-r--r--src/tracker_state.cpp14
-rw-r--r--src/version.h2
17 files changed, 307 insertions, 30 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e32ac..a9ce76d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
1# lingo-ap-tracker Releases 1# lingo-ap-tracker Releases
2 2
3## v0.11.0 - 2024-07-19
4
5- Added a savedata analyzer. When connected to a world, the user can open up the
6 Lingo save file associated with the connected world, and a new tab will open
7 up showing unsolved panels that are accessible, even if the world is not a
8 panelsanity world.
9
10Download:
11[lingo-ap-tracker-v0.11.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.11.0-win64.zip)<br/>
12Source: [v0.11.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.11.0)
13
14## v0.10.7 - 2024-07-17
15
16- Fixed issue with pilgrimage detection when sunwarps are shuffled where it
17 would expect you to use sunwarps mid-pilgrimage.
18- Fixed unreachable paintings sometimes being shown as already checked.
19
20Download:
21[lingo-ap-tracker-v0.10.7-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.10.7-win64.zip)<br/>
22Source: [v0.10.7](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.10.7)
23
3## v0.10.6 - 2024-07-16 24## v0.10.6 - 2024-07-16
4 25
5- The status bar now shows the name and server for the connected slot. 26- The status bar now shows the name and server for the connected slot.
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/VERSION b/VERSION index 7b63f4b..e88c34f 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@
v0.10.6 \ No newline at end of file v0.11.0 \ No newline at end of file
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index d501e81..fbd8d12 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;
@@ -132,6 +134,7 @@ struct APState {
132 cert_store); 134 cert_store);
133 } 135 }
134 136
137 save_name.clear();
135 inventory.clear(); 138 inventory.clear();
136 checked_locations.clear(); 139 checked_locations.clear();
137 data_storage.clear(); 140 data_storage.clear();
@@ -230,6 +233,8 @@ struct APState {
230 fmt::format("Connected to Archipelago! ({}@{})", player, server)); 233 fmt::format("Connected to Archipelago! ({}@{})", player, server));
231 TrackerLog("Connected to Archipelago!"); 234 TrackerLog("Connected to Archipelago!");
232 235
236 save_name = fmt::format("zzAP_{}_{}.save", apclient->get_seed(),
237 apclient->get_player_number());
233 data_storage_prefix = 238 data_storage_prefix =
234 fmt::format("Lingo_{}_", apclient->get_player_number()); 239 fmt::format("Lingo_{}_", apclient->get_player_number());
235 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); 240 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>();
@@ -522,6 +527,8 @@ void AP_Connect(std::string server, std::string player, std::string password) {
522 GetState().Connect(server, player, password); 527 GetState().Connect(server, player, password);
523} 528}
524 529
530std::string AP_GetSaveName() { return GetState().save_name; }
531
525bool AP_HasCheckedGameLocation(int location_id) { 532bool AP_HasCheckedGameLocation(int location_id) {
526 return GetState().HasCheckedGameLocation(location_id); 533 return GetState().HasCheckedGameLocation(location_id);
527} 534}
diff --git a/src/ap_state.h b/src/ap_state.h index 190b21f..e06d4ff 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 58d8897..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,18 +145,18 @@ 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 bool checked = AP_IsPaintingChecked(painting.internal_id);
130 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;
131
132 mem_dc.DrawBitmap(*eye_ptr, {10, cur_height});
133 151
134 bool reachable = IsPaintingReachable(painting_id); 152 bool reachable = IsPaintingReachable(painting_id);
135 const wxColour* text_color = reachable ? wxWHITE : wxRED; 153 const wxColour* text_color = reachable ? wxWHITE : wxRED;
136 mem_dc.SetTextForeground(*text_color); 154 mem_dc.SetTextForeground(*text_color);
137 155
156 bool checked = reachable && AP_IsPaintingChecked(painting.internal_id);
157 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;
158 mem_dc.DrawBitmap(*eye_ptr, {10, cur_height});
159
138 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. 160 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name.
139 mem_dc.DrawText(painting.internal_id, 161 mem_dc.DrawText(painting.internal_id,
140 {10 + 32 + 10, 162 {10 + 32 + 10,
diff --git a/src/game_data.cpp b/src/game_data.cpp index ec8d8f5..0786edb 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -267,6 +267,11 @@ struct GameData {
267 panel_it.second["location_name"].as<std::string>(); 267 panel_it.second["location_name"].as<std::string>();
268 } 268 }
269 269
270 if (panel_it.second["id"]) {
271 panels_[panel_id].nodepath =
272 panel_it.second["id"].as<std::string>();
273 }
274
270 if (panel_it.second["hunt"]) { 275 if (panel_it.second["hunt"]) {
271 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); 276 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>();
272 } 277 }
@@ -639,7 +644,8 @@ struct GameData {
639 .room = panel.room, 644 .room = panel.room,
640 .panels = {panel.id}, 645 .panels = {panel.id},
641 .classification = classification, 646 .classification = classification,
642 .hunt = panel.hunt}); 647 .hunt = panel.hunt,
648 .panel = true});
643 locations_by_name[location_name] = {area_id, 649 locations_by_name[location_name] = {area_id,
644 map_area.locations.size() - 1}; 650 map_area.locations.size() - 1};
645 } 651 }
@@ -692,6 +698,7 @@ struct GameData {
692 for (const Location &location : map_area.locations) { 698 for (const Location &location : map_area.locations) {
693 map_area.classification |= location.classification; 699 map_area.classification |= location.classification;
694 map_area.hunt |= location.hunt; 700 map_area.hunt |= location.hunt;
701 map_area.panel |= location.panel;
695 } 702 }
696 } 703 }
697 704
diff --git a/src/game_data.h b/src/game_data.h index 197585c..6f287cf 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;
@@ -120,6 +121,7 @@ struct Location {
120 std::vector<int> panels; 121 std::vector<int> panels;
121 int classification = 0; 122 int classification = 0;
122 bool hunt = false; 123 bool hunt = false;
124 bool panel = false;
123}; 125};
124 126
125struct MapArea { 127struct MapArea {
@@ -131,6 +133,7 @@ struct MapArea {
131 int map_y; 133 int map_y;
132 int classification = 0; 134 int classification = 0;
133 bool hunt = false; 135 bool hunt = false;
136 bool panel = false;
134}; 137};
135 138
136enum class SubwaySunwarpType { 139enum class SubwaySunwarpType {
diff --git a/src/godot_variant.cpp b/src/godot_variant.cpp new file mode 100644 index 0000000..1bc906f --- /dev/null +++ b/src/godot_variant.cpp
@@ -0,0 +1,83 @@
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 return {std::monostate{}};
73 }
74 }
75}
76
77} // namespace
78
79GodotVariant ParseGodotFile(std::string filename) {
80 std::ifstream file_stream(filename, std::ios_base::binary);
81 file_stream.ignore(4);
82 return ParseVariant(file_stream);
83}
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/subway_map.cpp b/src/subway_map.cpp index 044e6fa..9bfedf9 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp
@@ -19,7 +19,6 @@ enum class ItemDrawType { kNone, kBox, kOwl };
19namespace { 19namespace {
20 20
21std::optional<int> GetRealSubwayDoor(const SubwayItem subway_item) { 21std::optional<int> GetRealSubwayDoor(const SubwayItem subway_item) {
22 std::optional<int> subway_door = subway_item.door;
23 if (AP_IsSunwarpShuffle() && subway_item.sunwarp && 22 if (AP_IsSunwarpShuffle() && subway_item.sunwarp &&
24 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { 23 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) {
25 int sunwarp_index = subway_item.sunwarp->dots - 1; 24 int sunwarp_index = subway_item.sunwarp->dots - 1;
@@ -29,12 +28,12 @@ std::optional<int> GetRealSubwayDoor(const SubwayItem subway_item) {
29 28
30 for (const auto &[start_index, mapping] : AP_GetSunwarpMapping()) { 29 for (const auto &[start_index, mapping] : AP_GetSunwarpMapping()) {
31 if (start_index == sunwarp_index || mapping.exit_index == sunwarp_index) { 30 if (start_index == sunwarp_index || mapping.exit_index == sunwarp_index) {
32 subway_door = GD_GetSunwarpDoors().at(mapping.dots - 1); 31 return GD_GetSunwarpDoors().at(mapping.dots - 1);
33 } 32 }
34 } 33 }
35
36 return subway_door;
37 } 34 }
35
36 return subway_item.door;
38} 37}
39 38
40} // namespace 39} // namespace
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 */
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 66a9f94..18bb499 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -194,7 +194,8 @@ class StateCalculator {
194 std::list<int> panel_boundary; 194 std::list<int> panel_boundary;
195 std::list<int> painting_boundary; 195 std::list<int> painting_boundary;
196 std::list<Exit> flood_boundary; 196 std::list<Exit> flood_boundary;
197 flood_boundary.push_back({.destination_room = options_.start}); 197 flood_boundary.push_back(
198 {.source_room = -1, .destination_room = options_.start});
198 199
199 bool reachable_changed = true; 200 bool reachable_changed = true;
200 while (reachable_changed) { 201 while (reachable_changed) {
@@ -296,12 +297,11 @@ class StateCalculator {
296 if (AP_GetSunwarpMapping().count(index)) { 297 if (AP_GetSunwarpMapping().count(index)) {
297 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); 298 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index);
298 299
299 Exit sunwarp_exit; 300 new_boundary.push_back(
300 sunwarp_exit.destination_room = 301 {.source_room = room_exit.destination_room,
301 GD_GetRoomForSunwarp(sm.exit_index); 302 .destination_room = GD_GetRoomForSunwarp(sm.exit_index),
302 sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1); 303 .door = GD_GetSunwarpDoors().at(sm.dots - 1),
303 304 .type = EntranceType::kSunwarp});
304 new_boundary.push_back(sunwarp_exit);
305 } 305 }
306 } 306 }
307 } 307 }
diff --git a/src/version.h b/src/version.h index 8f93e18..ed5e97c 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, 10, 6); 39constexpr const Version kTrackerVersion = Version(0, 11, 0);
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