about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--ap_state.cpp41
-rw-r--r--ap_state.h13
-rw-r--r--area_popup.cpp5
-rw-r--r--area_window.cpp16
-rw-r--r--game_data.h10
-rw-r--r--tracker_frame.cpp22
-rw-r--r--tracker_frame.h5
-rw-r--r--tracker_state.cpp143
-rw-r--r--tracker_state.h19
10 files changed, 267 insertions, 8 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c346a9..399060b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -33,6 +33,7 @@ add_executable(lingo_ap_tracker
33 ap_state.cpp 33 ap_state.cpp
34 connection_dialog.cpp 34 connection_dialog.cpp
35 eye_indicator.cpp 35 eye_indicator.cpp
36 tracker_state.cpp
36) 37)
37set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 17) 38set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 17)
38set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) 39set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON)
diff --git a/ap_state.cpp b/ap_state.cpp index 4d7ddb7..9df487f 100644 --- a/ap_state.cpp +++ b/ap_state.cpp
@@ -9,6 +9,7 @@
9#include <thread> 9#include <thread>
10 10
11#include "game_data.h" 11#include "game_data.h"
12#include "tracker_state.h"
12 13
13constexpr int AP_MAJOR = 0; 14constexpr int AP_MAJOR = 0;
14constexpr int AP_MINOR = 4; 15constexpr int AP_MINOR = 4;
@@ -16,6 +17,11 @@ constexpr int AP_REVISION = 0;
16 17
17constexpr int ITEM_HANDLING = 7; // <- all 18constexpr int ITEM_HANDLING = 7; // <- all
18 19
20NLOHMANN_JSON_SERIALIZE_ENUM(DoorShuffleMode,
21 {{DoorShuffleMode::kNone, "none"},
22 {DoorShuffleMode::kSimple, "simple"},
23 {DoorShuffleMode::kComplex, "complex"}});
24
19APState::APState() { 25APState::APState() {
20 std::thread([this]() { 26 std::thread([this]() {
21 for (;;) { 27 for (;;) {
@@ -92,6 +98,9 @@ void APState::Connect(std::string server, std::string player,
92 apclient_->set_slot_connected_handler([&](const nlohmann::json& slot_data) { 98 apclient_->set_slot_connected_handler([&](const nlohmann::json& slot_data) {
93 tracker_frame_->SetStatusMessage("Connected to Archipelago!"); 99 tracker_frame_->SetStatusMessage("Connected to Archipelago!");
94 100
101 door_shuffle_mode_ = slot_data["shuffle_doors"].get<DoorShuffleMode>();
102 color_shuffle_ = slot_data["shuffle_colors"].get<bool>();
103
95 connected = true; 104 connected = true;
96 has_connection_result = true; 105 has_connection_result = true;
97 }); 106 });
@@ -169,6 +178,16 @@ void APState::Connect(std::string server, std::string player,
169 } 178 }
170 } 179 }
171 180
181 ap_id_by_color_[LingoColor::kBlack] = GetItemId("Black");
182 ap_id_by_color_[LingoColor::kRed] = GetItemId("Red");
183 ap_id_by_color_[LingoColor::kBlue] = GetItemId("Blue");
184 ap_id_by_color_[LingoColor::kYellow] = GetItemId("Yellow");
185 ap_id_by_color_[LingoColor::kPurple] = GetItemId("Purple");
186 ap_id_by_color_[LingoColor::kOrange] = GetItemId("Orange");
187 ap_id_by_color_[LingoColor::kGreen] = GetItemId("Green");
188 ap_id_by_color_[LingoColor::kBrown] = GetItemId("Brown");
189 ap_id_by_color_[LingoColor::kGray] = GetItemId("Gray");
190
172 RefreshTracker(); 191 RefreshTracker();
173 } else { 192 } else {
174 client_active_ = false; 193 client_active_ = false;
@@ -185,7 +204,27 @@ bool APState::HasCheckedGameLocation(int area_id, int section_id) const {
185 } 204 }
186} 205}
187 206
188void APState::RefreshTracker() { tracker_frame_->UpdateIndicators(); } 207bool APState::HasColorItem(LingoColor color) const {
208 if (ap_id_by_color_.count(color)) {
209 return inventory_.count(ap_id_by_color_.at(color));
210 } else {
211 return false;
212 }
213}
214
215void APState::RefreshTracker() {
216 GetTrackerState().CalculateState();
217 tracker_frame_->UpdateIndicators();
218}
219
220int64_t APState::GetItemId(const std::string& item_name) {
221 int64_t ap_id = apclient_->get_item_id(item_name);
222 if (ap_id == APClient::INVALID_NAME_ID) {
223 std::cout << "Could not find AP item ID for " << item_name << std::endl;
224 }
225
226 return ap_id;
227}
189 228
190APState& GetAPState() { 229APState& GetAPState() {
191 static APState* instance = new APState(); 230 static APState* instance = new APState();
diff --git a/ap_state.h b/ap_state.h index b5a94e3..d818b40 100644 --- a/ap_state.h +++ b/ap_state.h
@@ -11,6 +11,8 @@
11#include "game_data.h" 11#include "game_data.h"
12#include "tracker_frame.h" 12#include "tracker_frame.h"
13 13
14enum class DoorShuffleMode { kNone, kSimple, kComplex };
15
14class APState { 16class APState {
15 public: 17 public:
16 APState(); 18 APState();
@@ -23,9 +25,17 @@ class APState {
23 25
24 bool HasCheckedGameLocation(int area_id, int section_id) const; 26 bool HasCheckedGameLocation(int area_id, int section_id) const;
25 27
28 bool HasColorItem(LingoColor color) const;
29
30 DoorShuffleMode GetDoorShuffleMode() const { return door_shuffle_mode_; }
31
32 bool IsColorShuffle() const { return color_shuffle_; }
33
26 private: 34 private:
27 void RefreshTracker(); 35 void RefreshTracker();
28 36
37 int64_t GetItemId(const std::string& item_name);
38
29 TrackerFrame* tracker_frame_; 39 TrackerFrame* tracker_frame_;
30 40
31 std::unique_ptr<APClient> apclient_; 41 std::unique_ptr<APClient> apclient_;
@@ -39,6 +49,9 @@ class APState {
39 std::map<int, int64_t> ap_id_by_door_id_; 49 std::map<int, int64_t> ap_id_by_door_id_;
40 std::map<int, int64_t> ap_id_by_door_group_id_; 50 std::map<int, int64_t> ap_id_by_door_group_id_;
41 std::map<LingoColor, int64_t> ap_id_by_color_; 51 std::map<LingoColor, int64_t> ap_id_by_color_;
52
53 DoorShuffleMode door_shuffle_mode_ = DoorShuffleMode::kNone;
54 bool color_shuffle_ = false;
42}; 55};
43 56
44APState& GetAPState(); 57APState& GetAPState();
diff --git a/area_popup.cpp b/area_popup.cpp index e46e4ec..4cc3c63 100644 --- a/area_popup.cpp +++ b/area_popup.cpp
@@ -2,6 +2,7 @@
2 2
3#include "ap_state.h" 3#include "ap_state.h"
4#include "game_data.h" 4#include "game_data.h"
5#include "tracker_state.h"
5 6
6AreaPopup::AreaPopup(wxWindow* parent, int area_id) 7AreaPopup::AreaPopup(wxWindow* parent, int area_id)
7 : wxPanel(parent, wxID_ANY), area_id_(area_id) { 8 : wxPanel(parent, wxID_ANY), area_id_(area_id) {
@@ -43,7 +44,9 @@ void AreaPopup::UpdateIndicators() {
43 for (int section_id = 0; section_id < map_area.locations.size(); 44 for (int section_id = 0; section_id < map_area.locations.size();
44 section_id++) { 45 section_id++) {
45 bool checked = GetAPState().HasCheckedGameLocation(area_id_, section_id); 46 bool checked = GetAPState().HasCheckedGameLocation(area_id_, section_id);
46 const wxColour* text_color = checked ? wxWHITE : wxGREEN; 47 bool reachable =
48 GetTrackerState().IsLocationReachable(area_id_, section_id);
49 const wxColour* text_color = reachable ? wxWHITE : wxRED;
47 50
48 section_labels_[section_id]->SetForegroundColour(*text_color); 51 section_labels_[section_id]->SetForegroundColour(*text_color);
49 eye_indicators_[section_id]->SetChecked(checked); 52 eye_indicators_[section_id]->SetChecked(checked);
diff --git a/area_window.cpp b/area_window.cpp index ca327b8..fded223 100644 --- a/area_window.cpp +++ b/area_window.cpp
@@ -4,6 +4,7 @@
4 4
5#include "ap_state.h" 5#include "ap_state.h"
6#include "game_data.h" 6#include "game_data.h"
7#include "tracker_state.h"
7 8
8AreaWindow::AreaWindow(wxWindow* parent, int area_id, AreaPopup* popup) 9AreaWindow::AreaWindow(wxWindow* parent, int area_id, AreaPopup* popup)
9 : wxWindow(parent, wxID_ANY), area_id_(area_id), popup_(popup) { 10 : wxWindow(parent, wxID_ANY), area_id_(area_id), popup_(popup) {
@@ -35,16 +36,25 @@ void AreaWindow::Redraw() {
35 const wxBrush* brush_color = wxGREY_BRUSH; 36 const wxBrush* brush_color = wxGREY_BRUSH;
36 37
37 const MapArea& map_area = GetGameData().GetMapArea(area_id_); 38 const MapArea& map_area = GetGameData().GetMapArea(area_id_);
38 int unchecked_sections = 0; 39 bool has_reachable_unchecked = false;
40 bool has_unreachable_unchecked = false;
39 for (int section_id = 0; section_id < map_area.locations.size(); 41 for (int section_id = 0; section_id < map_area.locations.size();
40 section_id++) { 42 section_id++) {
41 if (!GetAPState().HasCheckedGameLocation(area_id_, section_id)) { 43 if (!GetAPState().HasCheckedGameLocation(area_id_, section_id)) {
42 unchecked_sections++; 44 if (GetTrackerState().IsLocationReachable(area_id_, section_id)) {
45 has_reachable_unchecked = true;
46 } else {
47 has_unreachable_unchecked = true;
48 }
43 } 49 }
44 } 50 }
45 51
46 if (unchecked_sections > 0) { 52 if (has_reachable_unchecked && has_unreachable_unchecked) {
53 brush_color = wxYELLOW_BRUSH;
54 } else if (has_reachable_unchecked) {
47 brush_color = wxGREEN_BRUSH; 55 brush_color = wxGREEN_BRUSH;
56 } else if (has_unreachable_unchecked) {
57 brush_color = wxRED_BRUSH;
48 } 58 }
49 59
50 int actual_border_size = GetSize().GetWidth() * BORDER_SIZE / EFFECTIVE_SIZE; 60 int actual_border_size = GetSize().GetWidth() * BORDER_SIZE / EFFECTIVE_SIZE;
diff --git a/game_data.h b/game_data.h index 4f93d92..ec3e94d 100644 --- a/game_data.h +++ b/game_data.h
@@ -73,6 +73,16 @@ class GameData {
73 73
74 const MapArea& GetMapArea(int id) const { return map_areas_.at(id); } 74 const MapArea& GetMapArea(int id) const { return map_areas_.at(id); }
75 75
76 int GetRoomByName(const std::string& name) const {
77 return room_by_id_.at(name);
78 }
79
80 const Room& GetRoom(int room_id) const { return rooms_.at(room_id); }
81
82 const Door& GetDoor(int door_id) const { return doors_.at(door_id); }
83
84 const Panel& GetPanel(int panel_id) const { return panels_.at(panel_id); }
85
76 private: 86 private:
77 int AddOrGetRoom(std::string room); 87 int AddOrGetRoom(std::string room);
78 int AddOrGetDoor(std::string room, std::string door); 88 int AddOrGetDoor(std::string room, std::string door);
diff --git a/tracker_frame.cpp b/tracker_frame.cpp index cd2060c..237433a 100644 --- a/tracker_frame.cpp +++ b/tracker_frame.cpp
@@ -6,6 +6,9 @@
6 6
7enum TrackerFrameIds { ID_CONNECT = 1 }; 7enum TrackerFrameIds { ID_CONNECT = 1 };
8 8
9wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent);
10wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent);
11
9TrackerFrame::TrackerFrame() 12TrackerFrame::TrackerFrame()
10 : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker") { 13 : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker") {
11 ::wxInitAllImageHandlers(); 14 ::wxInitAllImageHandlers();
@@ -33,17 +36,21 @@ TrackerFrame::TrackerFrame()
33 Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); 36 Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT);
34 Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); 37 Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT);
35 Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT); 38 Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT);
39 Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this);
40 Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this);
36 41
37 tracker_panel_ = new TrackerPanel(this); 42 tracker_panel_ = new TrackerPanel(this);
38} 43}
39 44
40void TrackerFrame::SetStatusMessage(std::string message) { 45void TrackerFrame::SetStatusMessage(std::string message) {
41 SetStatusText(message); 46 wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED);
47 event->SetString(message.c_str());
48
49 QueueEvent(event);
42} 50}
43 51
44void TrackerFrame::UpdateIndicators() { 52void TrackerFrame::UpdateIndicators() {
45 tracker_panel_->UpdateIndicators(); 53 QueueEvent(new wxCommandEvent(STATE_CHANGED));
46 Refresh();
47} 54}
48 55
49void TrackerFrame::OnAbout(wxCommandEvent &event) { 56void TrackerFrame::OnAbout(wxCommandEvent &event) {
@@ -61,3 +68,12 @@ void TrackerFrame::OnConnect(wxCommandEvent &event) {
61 dlg.GetPasswordValue()); 68 dlg.GetPasswordValue());
62 } 69 }
63} 70}
71
72void TrackerFrame::OnStateChanged(wxCommandEvent &event) {
73 tracker_panel_->UpdateIndicators();
74 Refresh();
75}
76
77void TrackerFrame::OnStatusChanged(wxCommandEvent &event) {
78 SetStatusText(event.GetString());
79}
diff --git a/tracker_frame.h b/tracker_frame.h index 082c12c..6f4e4fc 100644 --- a/tracker_frame.h +++ b/tracker_frame.h
@@ -9,6 +9,9 @@
9 9
10class TrackerPanel; 10class TrackerPanel;
11 11
12wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent);
13wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent);
14
12class TrackerFrame : public wxFrame { 15class TrackerFrame : public wxFrame {
13 public: 16 public:
14 TrackerFrame(); 17 TrackerFrame();
@@ -21,6 +24,8 @@ class TrackerFrame : public wxFrame {
21 void OnExit(wxCommandEvent &event); 24 void OnExit(wxCommandEvent &event);
22 void OnAbout(wxCommandEvent &event); 25 void OnAbout(wxCommandEvent &event);
23 void OnConnect(wxCommandEvent &event); 26 void OnConnect(wxCommandEvent &event);
27 void OnStateChanged(wxCommandEvent &event);
28 void OnStatusChanged(wxCommandEvent &event);
24 29
25 TrackerPanel *tracker_panel_; 30 TrackerPanel *tracker_panel_;
26}; 31};
diff --git a/tracker_state.cpp b/tracker_state.cpp new file mode 100644 index 0000000..62e4612 --- /dev/null +++ b/tracker_state.cpp
@@ -0,0 +1,143 @@
1#include "tracker_state.h"
2
3#include <list>
4#include <set>
5
6#include "ap_state.h"
7#include "game_data.h"
8
9bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms);
10
11bool IsPanelReachable_Helper(int panel_id,
12 const std::set<int>& reachable_rooms) {
13 const Panel& panel_obj = GetGameData().GetPanel(panel_id);
14
15 if (!reachable_rooms.count(panel_obj.room)) {
16 return false;
17 }
18
19 for (int room_id : panel_obj.required_rooms) {
20 if (!reachable_rooms.count(room_id)) {
21 return false;
22 }
23 }
24
25 for (int door_id : panel_obj.required_doors) {
26 if (!IsDoorReachable_Helper(door_id, reachable_rooms)) {
27 return false;
28 }
29 }
30
31 if (GetAPState().IsColorShuffle()) {
32 for (LingoColor color : panel_obj.colors) {
33 if (!GetAPState().HasColorItem(color)) {
34 return false;
35 }
36 }
37 }
38
39 return true;
40}
41
42bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms) {
43 const Door& door_obj = GetGameData().GetDoor(door_id);
44
45 switch (GetAPState().GetDoorShuffleMode()) {
46 case DoorShuffleMode::kNone: {
47 if (!reachable_rooms.count(door_obj.room)) {
48 return false;
49 }
50
51 for (int panel_id : door_obj.panels) {
52 if (!IsPanelReachable_Helper(panel_id, reachable_rooms)) {
53 return false;
54 }
55 }
56
57 return true;
58 }
59 case DoorShuffleMode::kSimple: {
60 break;
61 }
62 case DoorShuffleMode::kComplex: {
63 break;
64 }
65 }
66}
67
68void TrackerState::CalculateState() {
69 reachability_.clear();
70
71 std::set<int> reachable_rooms;
72
73 std::list<Exit> flood_boundary;
74 flood_boundary.push_back(
75 {.destination_room = GetGameData().GetRoomByName("Menu")});
76
77 bool reachable_changed = true;
78 while (reachable_changed) {
79 reachable_changed = false;
80
81 std::list<Exit> new_boundary;
82 for (const Exit& room_exit : flood_boundary) {
83 if (reachable_rooms.count(room_exit.destination_room)) {
84 continue;
85 }
86
87 bool valid_transition = false;
88 if (room_exit.door.has_value()) {
89 if (IsDoorReachable_Helper(*room_exit.door, reachable_rooms)) {
90 valid_transition = true;
91 } else if (GetAPState().GetDoorShuffleMode() ==
92 DoorShuffleMode::kNone) {
93 new_boundary.push_back(room_exit);
94 }
95 } else {
96 valid_transition = true;
97 }
98
99 if (valid_transition) {
100 reachable_rooms.insert(room_exit.destination_room);
101 reachable_changed = true;
102
103 const Room& room_obj =
104 GetGameData().GetRoom(room_exit.destination_room);
105 for (const Exit& out_edge : room_obj.exits) {
106 new_boundary.push_back(out_edge);
107 }
108 }
109 }
110
111 flood_boundary = new_boundary;
112 }
113
114 for (const MapArea& map_area : GetGameData().GetMapAreas()) {
115 for (int section_id = 0; section_id < map_area.locations.size();
116 section_id++) {
117 const Location& location_section = map_area.locations.at(section_id);
118 bool reachable = reachable_rooms.count(location_section.room);
119 if (reachable) {
120 for (int panel_id : location_section.panels) {
121 reachable &= IsPanelReachable_Helper(panel_id, reachable_rooms);
122 }
123 }
124
125 reachability_[{map_area.id, section_id}] = reachable;
126 }
127 }
128}
129
130bool TrackerState::IsLocationReachable(int area_id, int section_id) {
131 std::tuple<int, int> key = {area_id, section_id};
132
133 if (reachability_.count(key)) {
134 return reachability_.at(key);
135 } else {
136 return false;
137 }
138}
139
140TrackerState& GetTrackerState() {
141 static TrackerState* instance = new TrackerState();
142 return *instance;
143}
diff --git a/tracker_state.h b/tracker_state.h new file mode 100644 index 0000000..879e6f2 --- /dev/null +++ b/tracker_state.h
@@ -0,0 +1,19 @@
1#ifndef TRACKER_STATE_H_8639BC90
2#define TRACKER_STATE_H_8639BC90
3
4#include <map>
5#include <tuple>
6
7class TrackerState {
8 public:
9 void CalculateState();
10
11 bool IsLocationReachable(int area_id, int section_id);
12
13 private:
14 std::map<std::tuple<int, int>, bool> reachability_;
15};
16
17TrackerState& GetTrackerState();
18
19#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */