about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ap_state.cpp13
-rw-r--r--src/ap_state.h2
-rw-r--r--src/area_popup.cpp48
-rw-r--r--src/game_data.cpp29
-rw-r--r--src/game_data.h10
-rw-r--r--src/godot_variant.cpp83
-rw-r--r--src/godot_variant.h28
-rw-r--r--src/network_set.cpp34
-rw-r--r--src/network_set.h16
-rw-r--r--src/subway_map.cpp136
-rw-r--r--src/tracker_frame.cpp36
-rw-r--r--src/tracker_frame.h2
-rw-r--r--src/tracker_panel.cpp62
-rw-r--r--src/tracker_panel.h19
-rw-r--r--src/tracker_state.cpp75
-rw-r--r--src/tracker_state.h2
-rw-r--r--src/version.h2
17 files changed, 527 insertions, 70 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 876fdd8..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();
@@ -221,11 +224,15 @@ struct APState {
221 RefreshTracker(false); 224 RefreshTracker(false);
222 }); 225 });
223 226
224 apclient->set_slot_connected_handler([this, &connection_mutex]( 227 apclient->set_slot_connected_handler([this, player, server,
228 &connection_mutex](
225 const nlohmann::json& slot_data) { 229 const nlohmann::json& slot_data) {
226 tracker_frame->SetStatusMessage("Connected to Archipelago!"); 230 tracker_frame->SetStatusMessage(
231 fmt::format("Connected to Archipelago! ({}@{})", player, server));
227 TrackerLog("Connected to Archipelago!"); 232 TrackerLog("Connected to Archipelago!");
228 233
234 save_name = fmt::format("zzAP_{}_{}.save", apclient->get_seed(),
235 apclient->get_player_number());
229 data_storage_prefix = 236 data_storage_prefix =
230 fmt::format("Lingo_{}_", apclient->get_player_number()); 237 fmt::format("Lingo_{}_", apclient->get_player_number());
231 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); 238 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>();
@@ -507,6 +514,8 @@ void AP_Connect(std::string server, std::string player, std::string password) {
507 GetState().Connect(server, player, password); 514 GetState().Connect(server, player, password);
508} 515}
509 516
517std::string AP_GetSaveName() { return GetState().save_name; }
518
510bool AP_HasCheckedGameLocation(int location_id) { 519bool AP_HasCheckedGameLocation(int location_id) {
511 return GetState().HasCheckedGameLocation(location_id); 520 return GetState().HasCheckedGameLocation(location_id);
512} 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 58d8897..8d6487e 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.single_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 const Panel& panel = GD_GetPanel(*location.single_panel);
121 if (panel.non_counting) {
122 checked = AP_HasCheckedGameLocation(location.ap_location_id);
123 } else {
124 checked = 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 e75170e..7b805df 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -109,6 +109,7 @@ struct GameData {
109 auto process_single_entrance = 109 auto process_single_entrance =
110 [this, room_id, from_room_id](const YAML::Node &option) { 110 [this, room_id, from_room_id](const YAML::Node &option) {
111 Exit exit_obj; 111 Exit exit_obj;
112 exit_obj.source_room = from_room_id;
112 exit_obj.destination_room = room_id; 113 exit_obj.destination_room = room_id;
113 114
114 if (option["door"]) { 115 if (option["door"]) {
@@ -143,7 +144,7 @@ struct GameData {
143 switch (entrance_it.second.Type()) { 144 switch (entrance_it.second.Type()) {
144 case YAML::NodeType::Scalar: { 145 case YAML::NodeType::Scalar: {
145 // This is just "true". 146 // This is just "true".
146 rooms_[from_room_id].exits.push_back({.destination_room = room_id}); 147 rooms_[from_room_id].exits.push_back({.source_room = from_room_id, .destination_room = room_id});
147 break; 148 break;
148 } 149 }
149 case YAML::NodeType::Map: { 150 case YAML::NodeType::Map: {
@@ -264,6 +265,11 @@ struct GameData {
264 panel_it.second["location_name"].as<std::string>(); 265 panel_it.second["location_name"].as<std::string>();
265 } 266 }
266 267
268 if (panel_it.second["id"]) {
269 panels_[panel_id].nodepath =
270 panel_it.second["id"].as<std::string>();
271 }
272
267 if (panel_it.second["hunt"]) { 273 if (panel_it.second["hunt"]) {
268 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); 274 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>();
269 } 275 }
@@ -563,7 +569,8 @@ struct GameData {
563 .room = panel.room, 569 .room = panel.room,
564 .panels = {panel.id}, 570 .panels = {panel.id},
565 .classification = classification, 571 .classification = classification,
566 .hunt = panel.hunt}); 572 .hunt = panel.hunt,
573 .single_panel = panel.id});
567 locations_by_name[location_name] = {area_id, 574 locations_by_name[location_name] = {area_id,
568 map_area.locations.size() - 1}; 575 map_area.locations.size() - 1};
569 } 576 }
@@ -616,6 +623,7 @@ struct GameData {
616 for (const Location &location : map_area.locations) { 623 for (const Location &location : map_area.locations) {
617 map_area.classification |= location.classification; 624 map_area.classification |= location.classification;
618 map_area.hunt |= location.hunt; 625 map_area.hunt |= location.hunt;
626 map_area.has_single_panel |= location.single_panel.has_value();
619 } 627 }
620 } 628 }
621 629
@@ -673,6 +681,18 @@ struct GameData {
673 } 681 }
674 } 682 }
675 683
684 if (subway_it["entrances"]) {
685 for (const auto &entrance_it : subway_it["entrances"]) {
686 subway_item.entrances.push_back(entrance_it.as<std::string>());
687 }
688 }
689
690 if (subway_it["exits"]) {
691 for (const auto &exit_it : subway_it["exits"]) {
692 subway_item.exits.push_back(exit_it.as<std::string>());
693 }
694 }
695
676 if (subway_it["sunwarp"]) { 696 if (subway_it["sunwarp"]) {
677 SubwaySunwarp sunwarp; 697 SubwaySunwarp sunwarp;
678 sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); 698 sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>();
@@ -784,6 +804,11 @@ GameData &GetState() {
784 804
785} // namespace 805} // namespace
786 806
807bool SubwayItem::HasWarps() const {
808 return !(this->tags.empty() && this->entrances.empty() &&
809 this->exits.empty());
810}
811
787bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { 812bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const {
788 return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); 813 return std::tie(dots, type) < std::tie(rhs.dots, rhs.type);
789} 814}
diff --git a/src/game_data.h b/src/game_data.h index b787e6f..1f6d247 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;
@@ -83,6 +84,7 @@ struct Door {
83}; 84};
84 85
85struct Exit { 86struct Exit {
87 int source_room;
86 int destination_room; 88 int destination_room;
87 std::optional<int> door; 89 std::optional<int> door;
88 EntranceType type = EntranceType::kNormal; 90 EntranceType type = EntranceType::kNormal;
@@ -112,6 +114,7 @@ struct Location {
112 std::vector<int> panels; 114 std::vector<int> panels;
113 int classification = 0; 115 int classification = 0;
114 bool hunt = false; 116 bool hunt = false;
117 std::optional<int> single_panel;
115}; 118};
116 119
117struct MapArea { 120struct MapArea {
@@ -123,6 +126,7 @@ struct MapArea {
123 int map_y; 126 int map_y;
124 int classification = 0; 127 int classification = 0;
125 bool hunt = false; 128 bool hunt = false;
129 bool has_single_panel = false;
126}; 130};
127 131
128enum class SubwaySunwarpType { 132enum class SubwaySunwarpType {
@@ -144,9 +148,13 @@ struct SubwayItem {
144 int y; 148 int y;
145 std::optional<int> door; 149 std::optional<int> door;
146 std::vector<std::string> paintings; 150 std::vector<std::string> paintings;
147 std::vector<std::string> tags; 151 std::vector<std::string> tags; // 2-way teleports
152 std::vector<std::string> entrances; // teleport entrances
153 std::vector<std::string> exits; // teleport exits
148 std::optional<SubwaySunwarp> sunwarp; 154 std::optional<SubwaySunwarp> sunwarp;
149 std::optional<std::string> special; 155 std::optional<std::string> special;
156
157 bool HasWarps() const;
150}; 158};
151 159
152const std::vector<MapArea>& GD_GetMapAreas(); 160const std::vector<MapArea>& GD_GetMapAreas();
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/network_set.cpp b/src/network_set.cpp index 6d2a098..45911e3 100644 --- a/src/network_set.cpp +++ b/src/network_set.cpp
@@ -4,9 +4,8 @@ void NetworkSet::Clear() {
4 network_by_item_.clear(); 4 network_by_item_.clear();
5} 5}
6 6
7void NetworkSet::AddLink(int id1, int id2) { 7void NetworkSet::AddLink(int id1, int id2, bool two_way) {
8 if (id2 > id1) { 8 if (two_way && id2 > id1) {
9 // Make sure id1 < id2
10 std::swap(id1, id2); 9 std::swap(id1, id2);
11 } 10 }
12 11
@@ -17,14 +16,37 @@ void NetworkSet::AddLink(int id1, int id2) {
17 network_by_item_[id2] = {}; 16 network_by_item_[id2] = {};
18 } 17 }
19 18
20 network_by_item_[id1].insert({id1, id2}); 19 NetworkNode node = {id1, id2, two_way};
21 network_by_item_[id2].insert({id1, id2}); 20
21 network_by_item_[id1].insert(node);
22 network_by_item_[id2].insert(node);
23}
24
25void NetworkSet::AddLinkToNetwork(int network_id, int id1, int id2, bool two_way) {
26 if (two_way && id2 > id1) {
27 std::swap(id1, id2);
28 }
29
30 if (!network_by_item_.count(network_id)) {
31 network_by_item_[network_id] = {};
32 }
33
34 NetworkNode node = {id1, id2, two_way};
35
36 network_by_item_[network_id].insert(node);
22} 37}
23 38
24bool NetworkSet::IsItemInNetwork(int id) const { 39bool NetworkSet::IsItemInNetwork(int id) const {
25 return network_by_item_.count(id); 40 return network_by_item_.count(id);
26} 41}
27 42
28const std::set<std::pair<int, int>>& NetworkSet::GetNetworkGraph(int id) const { 43const std::set<NetworkNode>& NetworkSet::GetNetworkGraph(int id) const {
29 return network_by_item_.at(id); 44 return network_by_item_.at(id);
30} 45}
46
47bool NetworkNode::operator<(const NetworkNode& rhs) const {
48 if (entry != rhs.entry) return entry < rhs.entry;
49 if (exit != rhs.exit) return exit < rhs.exit;
50 if (two_way != rhs.two_way) return two_way < rhs.two_way;
51 return false;
52}
diff --git a/src/network_set.h b/src/network_set.h index e6f0c07..0f72052 100644 --- a/src/network_set.h +++ b/src/network_set.h
@@ -7,19 +7,29 @@
7#include <utility> 7#include <utility>
8#include <vector> 8#include <vector>
9 9
10struct NetworkNode {
11 int entry;
12 int exit;
13 bool two_way;
14
15 bool operator<(const NetworkNode& rhs) const;
16};
17
10class NetworkSet { 18class NetworkSet {
11 public: 19 public:
12 void Clear(); 20 void Clear();
13 21
14 void AddLink(int id1, int id2); 22 void AddLink(int id1, int id2, bool two_way);
23
24 void AddLinkToNetwork(int network_id, int id1, int id2, bool two_way);
15 25
16 bool IsItemInNetwork(int id) const; 26 bool IsItemInNetwork(int id) const;
17 27
18 const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; 28 const std::set<NetworkNode>& GetNetworkGraph(int id) const;
19 29
20 private: 30 private:
21 31
22 std::map<int, std::set<std::pair<int, int>>> network_by_item_; 32 std::map<int, std::set<NetworkNode>> network_by_item_;
23}; 33};
24 34
25#endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ 35#endif /* end of include guard: NETWORK_SET_H_3036B8E3 */
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index e3b844d..f896693 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp
@@ -16,6 +16,28 @@ constexpr int OWL_ACTUAL_SIZE = 32;
16 16
17enum class ItemDrawType { kNone, kBox, kOwl }; 17enum class ItemDrawType { kNone, kBox, kOwl };
18 18
19namespace {
20
21std::optional<int> GetRealSubwayDoor(const SubwayItem subway_item) {
22 if (AP_IsSunwarpShuffle() && subway_item.sunwarp &&
23 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) {
24 int sunwarp_index = subway_item.sunwarp->dots - 1;
25 if (subway_item.sunwarp->type == SubwaySunwarpType::kExit) {
26 sunwarp_index += 6;
27 }
28
29 for (const auto &[start_index, mapping] : AP_GetSunwarpMapping()) {
30 if (start_index == sunwarp_index || mapping.exit_index == sunwarp_index) {
31 return GD_GetSunwarpDoors().at(mapping.dots - 1);
32 }
33 }
34 }
35
36 return subway_item.door;
37}
38
39} // namespace
40
19SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { 41SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
20 SetBackgroundStyle(wxBG_STYLE_PAINT); 42 SetBackgroundStyle(wxBG_STYLE_PAINT);
21 43
@@ -69,10 +91,12 @@ void SubwayMap::OnConnect() {
69 networks_.Clear(); 91 networks_.Clear();
70 92
71 std::map<std::string, std::vector<int>> tagged; 93 std::map<std::string, std::vector<int>> tagged;
94 std::map<std::string, std::vector<int>> entrances;
95 std::map<std::string, std::vector<int>> exits;
72 for (const SubwayItem &subway_item : GD_GetSubwayItems()) { 96 for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
73 if (AP_HasEarlyColorHallways() && 97 if (AP_HasEarlyColorHallways() &&
74 subway_item.special == "starting_room_paintings") { 98 subway_item.special == "starting_room_paintings") {
75 tagged["early_ch"].push_back(subway_item.id); 99 entrances["early_ch"].push_back(subway_item.id);
76 } 100 }
77 101
78 if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { 102 if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) {
@@ -82,21 +106,40 @@ void SubwayMap::OnConnect() {
82 for (const std::string &tag : subway_item.tags) { 106 for (const std::string &tag : subway_item.tags) {
83 tagged[tag].push_back(subway_item.id); 107 tagged[tag].push_back(subway_item.id);
84 } 108 }
109 for (const std::string &tag : subway_item.entrances) {
110 entrances[tag].push_back(subway_item.id);
111 }
112 for (const std::string &tag : subway_item.exits) {
113 exits[tag].push_back(subway_item.id);
114 }
85 115
86 if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && 116 if (!AP_IsSunwarpShuffle() && subway_item.sunwarp) {
87 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { 117 std::string tag = fmt::format("sunwarp{}", subway_item.sunwarp->dots);
88 std::string tag = fmt::format("subway{}", subway_item.sunwarp->dots); 118 switch (subway_item.sunwarp->type) {
89 tagged[tag].push_back(subway_item.id); 119 case SubwaySunwarpType::kEnter:
120 entrances[tag].push_back(subway_item.id);
121 break;
122 case SubwaySunwarpType::kExit:
123 exits[tag].push_back(subway_item.id);
124 break;
125 default:
126 break;
127 }
90 } 128 }
91 129
92 if (!AP_IsPilgrimageEnabled() && 130 if (!AP_IsPilgrimageEnabled()) {
93 (subway_item.special == "sun_painting" || 131 if (subway_item.special == "sun_painting") {
94 subway_item.special == "sun_painting_exit")) { 132 entrances["sun_painting"].push_back(subway_item.id);
95 tagged["sun_painting"].push_back(subway_item.id); 133 } else if (subway_item.special == "sun_painting_exit") {
134 exits["sun_painting"].push_back(subway_item.id);
135 }
96 } 136 }
97 } 137 }
98 138
99 if (AP_IsSunwarpShuffle()) { 139 if (AP_IsSunwarpShuffle()) {
140 SubwaySunwarp final_sunwarp{.dots = 6, .type = SubwaySunwarpType::kFinal};
141 int final_sunwarp_item = GD_GetSubwayItemForSunwarp(final_sunwarp);
142
100 for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { 143 for (const auto &[index, mapping] : AP_GetSunwarpMapping()) {
101 std::string tag = fmt::format("sunwarp{}", mapping.dots); 144 std::string tag = fmt::format("sunwarp{}", mapping.dots);
102 145
@@ -118,8 +161,14 @@ void SubwayMap::OnConnect() {
118 toWarp.type = SubwaySunwarpType::kExit; 161 toWarp.type = SubwaySunwarpType::kExit;
119 } 162 }
120 163
121 tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); 164 entrances[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp));
122 tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); 165 exits[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp));
166
167 networks_.AddLinkToNetwork(
168 final_sunwarp_item, GD_GetSubwayItemForSunwarp(fromWarp),
169 mapping.dots == 6 ? final_sunwarp_item
170 : GD_GetSubwayItemForSunwarp(toWarp),
171 false);
123 } 172 }
124 } 173 }
125 174
@@ -129,7 +178,17 @@ void SubwayMap::OnConnect() {
129 tag_it1++) { 178 tag_it1++) {
130 for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); 179 for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end();
131 tag_it2++) { 180 tag_it2++) {
132 networks_.AddLink(*tag_it1, *tag_it2); 181 // two links because tags are bi-directional
182 networks_.AddLink(*tag_it1, *tag_it2, true);
183 }
184 }
185 }
186
187 for (const auto &[tag, items] : entrances) {
188 if (!exits.contains(tag)) continue;
189 for (auto exit : exits[tag]) {
190 for (auto entrance : items) {
191 networks_.AddLink(entrance, exit, false);
133 } 192 }
134 } 193 }
135 } 194 }
@@ -149,7 +208,7 @@ void SubwayMap::UpdateIndicators() {
149 AP_GetPaintingMapping().at(painting_id)); 208 AP_GetPaintingMapping().at(painting_id));
150 209
151 if (from_id && to_id) { 210 if (from_id && to_id) {
152 networks_.AddLink(*from_id, *to_id); 211 networks_.AddLink(*from_id, *to_id, false);
153 } 212 }
154 } 213 }
155 } 214 }
@@ -162,7 +221,7 @@ void SubwayMap::UpdateIndicators() {
162void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, 221void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp,
163 SubwaySunwarp to_sunwarp) { 222 SubwaySunwarp to_sunwarp) {
164 networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), 223 networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp),
165 GD_GetSubwayItemForSunwarp(to_sunwarp)); 224 GD_GetSubwayItemForSunwarp(to_sunwarp), false);
166} 225}
167 226
168void SubwayMap::Zoom(bool in) { 227void SubwayMap::Zoom(bool in) {
@@ -267,9 +326,11 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
267 // Note that these requirements are duplicated on OnMouseClick so that it 326 // Note that these requirements are duplicated on OnMouseClick so that it
268 // knows when an item has a hover effect. 327 // knows when an item has a hover effect.
269 const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); 328 const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_);
270 if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { 329 std::optional<int> subway_door = GetRealSubwayDoor(subway_item);
330
331 if (subway_door && !GetDoorRequirements(*subway_door).empty()) {
271 const std::map<std::string, bool> &report = 332 const std::map<std::string, bool> &report =
272 GetDoorRequirements(*subway_item.door); 333 GetDoorRequirements(*subway_door);
273 334
274 int acc_height = 10; 335 int acc_height = 10;
275 int col_width = 0; 336 int col_width = 0;
@@ -326,10 +387,9 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
326 if (networks_.IsItemInNetwork(*hovered_item_)) { 387 if (networks_.IsItemInNetwork(*hovered_item_)) {
327 dc.SetBrush(*wxTRANSPARENT_BRUSH); 388 dc.SetBrush(*wxTRANSPARENT_BRUSH);
328 389
329 for (const auto &[item_id1, item_id2] : 390 for (const auto node : networks_.GetNetworkGraph(*hovered_item_)) {
330 networks_.GetNetworkGraph(*hovered_item_)) { 391 const SubwayItem &item1 = GD_GetSubwayItem(node.entry);
331 const SubwayItem &item1 = GD_GetSubwayItem(item_id1); 392 const SubwayItem &item2 = GD_GetSubwayItem(node.exit);
332 const SubwayItem &item2 = GD_GetSubwayItem(item_id2);
333 393
334 wxPoint item1_pos = MapPosToRenderPos( 394 wxPoint item1_pos = MapPosToRenderPos(
335 {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); 395 {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2});
@@ -349,6 +409,12 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
349 dc.DrawLine(item1_pos, item2_pos); 409 dc.DrawLine(item1_pos, item2_pos);
350 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); 410 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
351 dc.DrawLine(item1_pos, item2_pos); 411 dc.DrawLine(item1_pos, item2_pos);
412 if (!node.two_way) {
413 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2));
414 dc.SetBrush(*wxCYAN_BRUSH);
415 dc.DrawCircle(item2_pos, 4);
416 dc.SetBrush(*wxTRANSPARENT_BRUSH);
417 }
352 } else { 418 } else {
353 int ellipse_x; 419 int ellipse_x;
354 int ellipse_y; 420 int ellipse_y;
@@ -391,6 +457,12 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
391 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); 457 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
392 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, 458 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2,
393 halfheight * 2, start, end); 459 halfheight * 2, start, end);
460 if (!node.two_way) {
461 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2));
462 dc.SetBrush(*wxCYAN_BRUSH);
463 dc.DrawCircle(item2_pos, 4);
464 dc.SetBrush(*wxTRANSPARENT_BRUSH);
465 }
394 } 466 }
395 } 467 }
396 } 468 }
@@ -450,7 +522,9 @@ void SubwayMap::OnMouseClick(wxMouseEvent &event) {
450 522
451 if (actual_hover_) { 523 if (actual_hover_) {
452 const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); 524 const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_);
453 if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || 525 std::optional<int> subway_door = GetRealSubwayDoor(subway_item);
526
527 if ((subway_door && !GetDoorRequirements(*subway_door).empty()) ||
454 networks_.IsItemInNetwork(*hovered_item_)) { 528 networks_.IsItemInNetwork(*hovered_item_)) {
455 if (actual_hover_ != hovered_item_) { 529 if (actual_hover_ != hovered_item_) {
456 hovered_item_ = actual_hover_; 530 hovered_item_ = actual_hover_;
@@ -509,7 +583,8 @@ void SubwayMap::OnClickHelp(wxCommandEvent &event) {
509 "corner.\nClick on a side of the screen to start panning. It will follow " 583 "corner.\nClick on a side of the screen to start panning. It will follow "
510 "your mouse. Click again to stop.\nHover over a door to see the " 584 "your mouse. Click again to stop.\nHover over a door to see the "
511 "requirements to open it.\nHover over a warp or active painting to see " 585 "requirements to open it.\nHover over a warp or active painting to see "
512 "what it is connected to.\nIn painting shuffle, paintings that have not " 586 "what it is connected to.\nFor one-way connections, there will be a "
587 "circle at the exit.\nIn painting shuffle, paintings that have not "
513 "yet been checked will not show their connections.\nA green shaded owl " 588 "yet been checked will not show their connections.\nA green shaded owl "
514 "means that there is a painting entrance there.\nA red shaded owl means " 589 "means that there is a painting entrance there.\nA red shaded owl means "
515 "that there are only painting exits there.\nClick on a door or " 590 "that there are only painting exits there.\nClick on a door or "
@@ -529,6 +604,7 @@ void SubwayMap::Redraw() {
529 ItemDrawType draw_type = ItemDrawType::kNone; 604 ItemDrawType draw_type = ItemDrawType::kNone;
530 const wxBrush *brush_color = wxGREY_BRUSH; 605 const wxBrush *brush_color = wxGREY_BRUSH;
531 std::optional<wxColour> shade_color; 606 std::optional<wxColour> shade_color;
607 std::optional<int> subway_door = GetRealSubwayDoor(subway_item);
532 608
533 if (AP_HasEarlyColorHallways() && 609 if (AP_HasEarlyColorHallways() &&
534 subway_item.special == "starting_room_paintings") { 610 subway_item.special == "starting_room_paintings") {
@@ -544,6 +620,16 @@ void SubwayMap::Redraw() {
544 brush_color = wxRED_BRUSH; 620 brush_color = wxRED_BRUSH;
545 } 621 }
546 } 622 }
623 } else if (subway_item.sunwarp &&
624 subway_item.sunwarp->type == SubwaySunwarpType::kFinal &&
625 AP_IsPilgrimageEnabled()) {
626 draw_type = ItemDrawType::kBox;
627
628 if (IsPilgrimageDoable()) {
629 brush_color = wxGREEN_BRUSH;
630 } else {
631 brush_color = wxRED_BRUSH;
632 }
547 } else if (!subway_item.paintings.empty()) { 633 } else if (!subway_item.paintings.empty()) {
548 if (AP_IsPaintingShuffle()) { 634 if (AP_IsPaintingShuffle()) {
549 bool has_checked_painting = false; 635 bool has_checked_painting = false;
@@ -577,13 +663,13 @@ void SubwayMap::Redraw() {
577 } 663 }
578 } 664 }
579 } 665 }
580 } else if (!subway_item.tags.empty()) { 666 } else if (subway_item.HasWarps()) {
581 draw_type = ItemDrawType::kOwl; 667 draw_type = ItemDrawType::kOwl;
582 } 668 }
583 } else if (subway_item.door) { 669 } else if (subway_door) {
584 draw_type = ItemDrawType::kBox; 670 draw_type = ItemDrawType::kBox;
585 671
586 if (IsDoorOpen(*subway_item.door)) { 672 if (IsDoorOpen(*subway_door)) {
587 brush_color = wxGREEN_BRUSH; 673 brush_color = wxGREEN_BRUSH;
588 } else { 674 } else {
589 brush_color = wxRED_BRUSH; 675 brush_color = wxRED_BRUSH;
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 80fd137..b9282f5 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);
@@ -131,6 +137,7 @@ void TrackerFrame::OnAbout(wxCommandEvent &event) {
131 about_info.SetName("Lingo Archipelago Tracker"); 137 about_info.SetName("Lingo Archipelago Tracker");
132 about_info.SetVersion(kTrackerVersion.ToString()); 138 about_info.SetVersion(kTrackerVersion.ToString());
133 about_info.AddDeveloper("hatkirby"); 139 about_info.AddDeveloper("hatkirby");
140 about_info.AddDeveloper("art0007i");
134 about_info.AddArtist("Brenton Wildes"); 141 about_info.AddArtist("Brenton Wildes");
135 about_info.AddArtist("kinrah"); 142 about_info.AddArtist("kinrah");
136 143
@@ -204,10 +211,36 @@ void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) {
204 zoom_out_menu_item_->Enable(event.GetSelection() == 1); 211 zoom_out_menu_item_->Enable(event.GetSelection() == 1);
205} 212}
206 213
214void TrackerFrame::OnOpenFile(wxCommandEvent& event) {
215 wxFileDialog open_file_dialog(
216 this, "Open Lingo Save File",
217 fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable",
218 wxStandardPaths::Get().GetUserConfigDir().ToStdString()),
219 AP_GetSaveName(), "Lingo save file (*.save)|*.save",
220 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
221 if (open_file_dialog.ShowModal() == wxID_CANCEL) {
222 return;
223 }
224
225 std::string savedata_path = open_file_dialog.GetPath().ToStdString();
226
227 if (panels_panel_ == nullptr) {
228 panels_panel_ = new TrackerPanel(notebook_);
229 notebook_->AddPage(panels_panel_, "Panels");
230 }
231
232 notebook_->SetSelection(notebook_->FindPage(panels_panel_));
233 panels_panel_->SetSavedataPath(savedata_path);
234}
235
207void TrackerFrame::OnStateReset(wxCommandEvent& event) { 236void TrackerFrame::OnStateReset(wxCommandEvent& event) {
208 tracker_panel_->UpdateIndicators(); 237 tracker_panel_->UpdateIndicators();
209 achievements_pane_->UpdateIndicators(); 238 achievements_pane_->UpdateIndicators();
210 subway_map_->OnConnect(); 239 subway_map_->OnConnect();
240 if (panels_panel_ != nullptr) {
241 notebook_->DeletePage(notebook_->FindPage(panels_panel_));
242 panels_panel_ = nullptr;
243 }
211 Refresh(); 244 Refresh();
212} 245}
213 246
@@ -215,6 +248,9 @@ void TrackerFrame::OnStateChanged(wxCommandEvent &event) {
215 tracker_panel_->UpdateIndicators(); 248 tracker_panel_->UpdateIndicators();
216 achievements_pane_->UpdateIndicators(); 249 achievements_pane_->UpdateIndicators();
217 subway_map_->UpdateIndicators(); 250 subway_map_->UpdateIndicators();
251 if (panels_panel_ != nullptr) {
252 panels_panel_->UpdateIndicators();
253 }
218 Refresh(); 254 Refresh();
219} 255}
220 256
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..27e825a 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,35 @@ 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.has_single_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 if (section.single_panel) {
204 const Panel &panel = GD_GetPanel(*section.single_panel);
205 if (panel.non_counting) {
206 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
207 } else {
208 has_unchecked = !solved_panels_.contains(panel.nodepath);
209 }
210 }
160 } else if (AP_IsLocationVisible(section.classification)) { 211 } else if (AP_IsLocationVisible(section.classification)) {
161 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); 212 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
162 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { 213 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) {
@@ -172,12 +223,11 @@ void TrackerPanel::Redraw() {
172 } 223 }
173 } 224 }
174 225
175 if (AP_IsPaintingShuffle()) { 226 if (AP_IsPaintingShuffle() && !panels_mode_) {
176 for (int painting_id : map_area.paintings) { 227 for (int painting_id : map_area.paintings) {
177 const PaintingExit &painting = GD_GetPaintingExit(painting_id); 228 const PaintingExit &painting = GD_GetPaintingExit(painting_id);
178 if (!AP_IsPaintingChecked(painting.internal_id)) { 229 bool reachable = IsPaintingReachable(painting_id);
179 bool reachable = IsPaintingReachable(painting_id); 230 if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) {
180
181 if (reachable) { 231 if (reachable) {
182 has_reachable_unchecked = true; 232 has_reachable_unchecked = true;
183 } else { 233 } else {
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 1a2d116..8d5d904 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -1,5 +1,8 @@
1#include "tracker_state.h" 1#include "tracker_state.h"
2 2
3#include <fmt/core.h>
4#include <hkutil/string.h>
5
3#include <list> 6#include <list>
4#include <map> 7#include <map>
5#include <mutex> 8#include <mutex>
@@ -9,6 +12,7 @@
9 12
10#include "ap_state.h" 13#include "ap_state.h"
11#include "game_data.h" 14#include "game_data.h"
15#include "logger.h"
12 16
13namespace { 17namespace {
14 18
@@ -148,6 +152,7 @@ struct TrackerState {
148 std::mutex reachability_mutex; 152 std::mutex reachability_mutex;
149 RequirementCalculator requirements; 153 RequirementCalculator requirements;
150 std::map<int, std::map<std::string, bool>> door_reports; 154 std::map<int, std::map<std::string, bool>> door_reports;
155 bool pilgrimage_doable = false;
151}; 156};
152 157
153enum Decision { kYes, kNo, kMaybe }; 158enum Decision { kYes, kNo, kMaybe };
@@ -176,7 +181,8 @@ class StateCalculator {
176 std::list<int> panel_boundary; 181 std::list<int> panel_boundary;
177 std::list<int> painting_boundary; 182 std::list<int> painting_boundary;
178 std::list<Exit> flood_boundary; 183 std::list<Exit> flood_boundary;
179 flood_boundary.push_back({.destination_room = options_.start}); 184 flood_boundary.push_back(
185 {.source_room = -1, .destination_room = options_.start});
180 186
181 bool reachable_changed = true; 187 bool reachable_changed = true;
182 while (reachable_changed) { 188 while (reachable_changed) {
@@ -217,7 +223,9 @@ class StateCalculator {
217 PaintingExit target_painting = 223 PaintingExit target_painting =
218 GD_GetPaintingExit(GD_GetPaintingByName( 224 GD_GetPaintingExit(GD_GetPaintingByName(
219 AP_GetPaintingMapping().at(cur_painting.internal_id))); 225 AP_GetPaintingMapping().at(cur_painting.internal_id)));
226 painting_exit.source_room = cur_painting.room;
220 painting_exit.destination_room = target_painting.room; 227 painting_exit.destination_room = target_painting.room;
228 painting_exit.type = EntranceType::kPainting;
221 229
222 new_boundary.push_back(painting_exit); 230 new_boundary.push_back(painting_exit);
223 } 231 }
@@ -244,6 +252,12 @@ class StateCalculator {
244 reachable_rooms_.insert(room_exit.destination_room); 252 reachable_rooms_.insert(room_exit.destination_room);
245 reachable_changed = true; 253 reachable_changed = true;
246 254
255#ifndef NDEBUG
256 std::list<int> room_path = paths_[room_exit.source_room];
257 room_path.push_back(room_exit.destination_room);
258 paths_[room_exit.destination_room] = room_path;
259#endif
260
247 const Room& room_obj = GD_GetRoom(room_exit.destination_room); 261 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
248 for (const Exit& out_edge : room_obj.exits) { 262 for (const Exit& out_edge : room_obj.exits) {
249 if (out_edge.type == EntranceType::kPainting && 263 if (out_edge.type == EntranceType::kPainting &&
@@ -270,32 +284,44 @@ class StateCalculator {
270 if (AP_GetSunwarpMapping().count(index)) { 284 if (AP_GetSunwarpMapping().count(index)) {
271 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); 285 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index);
272 286
273 Exit sunwarp_exit; 287 new_boundary.push_back(
274 sunwarp_exit.destination_room = 288 {.source_room = room_exit.destination_room,
275 GD_GetRoomForSunwarp(sm.exit_index); 289 .destination_room = GD_GetRoomForSunwarp(sm.exit_index),
276 sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1); 290 .door = GD_GetSunwarpDoors().at(sm.dots - 1),
277 291 .type = EntranceType::kSunwarp});
278 new_boundary.push_back(sunwarp_exit);
279 } 292 }
280 } 293 }
281 } 294 }
282 295
283 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { 296 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") {
284 new_boundary.push_back( 297 new_boundary.push_back(
285 {.destination_room = GD_GetRoomByName("Color Hallways"), 298 {.source_room = room_exit.destination_room,
299 .destination_room = GD_GetRoomByName("Color Hallways"),
286 .type = EntranceType::kPainting}); 300 .type = EntranceType::kPainting});
287 } 301 }
288 302
289 if (AP_IsPilgrimageEnabled()) { 303 if (AP_IsPilgrimageEnabled()) {
290 if (room_obj.name == "Hub Room") { 304 int pilgrimage_start_id = GD_GetRoomByName("Hub Room");
305 if (AP_IsSunwarpShuffle()) {
306 for (const auto& [start_index, mapping] :
307 AP_GetSunwarpMapping()) {
308 if (mapping.dots == 1) {
309 pilgrimage_start_id = GD_GetRoomForSunwarp(start_index);
310 }
311 }
312 }
313
314 if (room_exit.destination_room == pilgrimage_start_id) {
291 new_boundary.push_back( 315 new_boundary.push_back(
292 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), 316 {.source_room = room_exit.destination_room,
317 .destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
293 .type = EntranceType::kPilgrimage}); 318 .type = EntranceType::kPilgrimage});
294 } 319 }
295 } else { 320 } else {
296 if (room_obj.name == "Starting Room") { 321 if (room_obj.name == "Starting Room") {
297 new_boundary.push_back( 322 new_boundary.push_back(
298 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), 323 {.source_room = room_exit.destination_room,
324 .destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
299 .door = 325 .door =
300 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), 326 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"),
301 .type = EntranceType::kPainting}); 327 .type = EntranceType::kPainting});
@@ -336,6 +362,21 @@ class StateCalculator {
336 return door_report_; 362 return door_report_;
337 } 363 }
338 364
365 bool IsPilgrimageDoable() const { return pilgrimage_doable_; }
366
367 std::string GetPathToRoom(int room_id) const {
368 if (!paths_.count(room_id)) {
369 return "";
370 }
371
372 const std::list<int>& path = paths_.at(room_id);
373 std::vector<std::string> room_names;
374 for (int room_id : path) {
375 room_names.push_back(GD_GetRoom(room_id).name);
376 }
377 return hatkirby::implode(room_names, " -> ");
378 }
379
339 private: 380 private:
340 Decision IsNonGroupedDoorReachable(const Door& door_obj) { 381 Decision IsNonGroupedDoorReachable(const Door& door_obj) {
341 bool has_item = AP_HasItem(door_obj.ap_item_id); 382 bool has_item = AP_HasItem(door_obj.ap_item_id);
@@ -534,6 +575,8 @@ class StateCalculator {
534 } 575 }
535 } 576 }
536 577
578 pilgrimage_doable_ = true;
579
537 return kYes; 580 return kYes;
538 } 581 }
539 582
@@ -574,6 +617,9 @@ class StateCalculator {
574 std::set<int> solveable_panels_; 617 std::set<int> solveable_panels_;
575 std::set<int> reachable_paintings_; 618 std::set<int> reachable_paintings_;
576 std::map<int, std::map<std::string, bool>> door_report_; 619 std::map<int, std::map<std::string, bool>> door_report_;
620 bool pilgrimage_doable_ = false;
621
622 std::map<int, std::list<int>> paths_;
577}; 623};
578 624
579} // namespace 625} // namespace
@@ -623,6 +669,7 @@ void RecalculateReachability() {
623 std::swap(GetState().reachable_doors, new_reachable_doors); 669 std::swap(GetState().reachable_doors, new_reachable_doors);
624 std::swap(GetState().reachable_paintings, reachable_paintings); 670 std::swap(GetState().reachable_paintings, reachable_paintings);
625 std::swap(GetState().door_reports, door_reports); 671 std::swap(GetState().door_reports, door_reports);
672 GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable();
626} 673}
627 674
628bool IsLocationReachable(int location_id) { 675bool IsLocationReachable(int location_id) {
@@ -652,3 +699,9 @@ const std::map<std::string, bool>& GetDoorRequirements(int door_id) {
652 699
653 return GetState().door_reports[door_id]; 700 return GetState().door_reports[door_id];
654} 701}
702
703bool IsPilgrimageDoable() {
704 std::lock_guard reachability_guard(GetState().reachability_mutex);
705
706 return GetState().pilgrimage_doable;
707}
diff --git a/src/tracker_state.h b/src/tracker_state.h index c7857a0..a8f155d 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h
@@ -16,4 +16,6 @@ bool IsPaintingReachable(int painting_id);
16 16
17const std::map<std::string, bool>& GetDoorRequirements(int door_id); 17const std::map<std::string, bool>& GetDoorRequirements(int door_id);
18 18
19bool IsPilgrimageDoable();
20
19#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ 21#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */
diff --git a/src/version.h b/src/version.h index b688e27..ec52f44 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, 5); 39constexpr const Version kTrackerVersion = Version(0, 11, 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