about summary refs log tree commit diff stats
path: root/src/ap_state.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ap_state.cpp')
-rw-r--r--src/ap_state.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp new file mode 100644 index 0000000..22a51ee --- /dev/null +++ b/src/ap_state.cpp
@@ -0,0 +1,330 @@
1#include "ap_state.h"
2
3#define HAS_STD_FILESYSTEM
4#define _WEBSOCKETPP_CPP11_STRICT_
5#pragma comment(lib, "crypt32")
6
7#include <hkutil/string.h>
8
9#include <apclient.hpp>
10#include <apuuid.hpp>
11#include <chrono>
12#include <exception>
13#include <list>
14#include <memory>
15#include <mutex>
16#include <set>
17#include <thread>
18#include <tuple>
19
20#include "game_data.h"
21#include "tracker_frame.h"
22#include "tracker_state.h"
23
24constexpr int AP_MAJOR = 0;
25constexpr int AP_MINOR = 4;
26constexpr int AP_REVISION = 0;
27
28constexpr int ITEM_HANDLING = 7; // <- all
29
30namespace {
31
32APClient* apclient = nullptr;
33
34bool initialized = false;
35
36TrackerFrame* tracker_frame;
37
38bool client_active = false;
39std::mutex client_mutex;
40
41bool connected = false;
42bool has_connection_result = false;
43
44std::map<int64_t, int> inventory;
45std::set<int64_t> checked_locations;
46
47std::map<std::tuple<int, int>, int64_t> ap_id_by_location_id;
48std::map<std::string, int64_t> ap_id_by_item_name;
49std::map<LingoColor, int64_t> ap_id_by_color;
50std::map<int64_t, std::string> progressive_item_by_ap_id;
51
52DoorShuffleMode door_shuffle_mode = kNO_DOORS;
53bool color_shuffle = false;
54bool painting_shuffle = false;
55int mastery_requirement = 21;
56
57std::map<std::string, std::string> painting_mapping;
58
59void RefreshTracker() {
60 GetTrackerState().CalculateState();
61 tracker_frame->UpdateIndicators();
62}
63
64int64_t GetItemId(const std::string& item_name) {
65 int64_t ap_id = apclient->get_item_id(item_name);
66 if (ap_id == APClient::INVALID_NAME_ID) {
67 std::cout << "Could not find AP item ID for " << item_name << std::endl;
68 }
69
70 return ap_id;
71}
72
73void DestroyClient() {
74 client_active = false;
75 apclient->reset();
76 delete apclient;
77 apclient = nullptr;
78}
79
80} // namespace
81
82void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; }
83
84void AP_Connect(std::string server, std::string player, std::string password) {
85 if (!initialized) {
86 std::thread([]() {
87 for (;;) {
88 {
89 std::lock_guard client_guard(client_mutex);
90 if (apclient) {
91 apclient->poll();
92 }
93 }
94
95 std::this_thread::sleep_for(std::chrono::milliseconds(100));
96 }
97 }).detach();
98
99 initialized = true;
100 }
101
102 tracker_frame->SetStatusMessage("Connecting to Archipelago server....");
103
104 {
105 std::lock_guard client_guard(client_mutex);
106
107 if (apclient) {
108 DestroyClient();
109 }
110
111 apclient = new APClient(ap_get_uuid(""), "Lingo", server);
112 }
113
114 inventory.clear();
115 checked_locations.clear();
116 door_shuffle_mode = kNO_DOORS;
117 color_shuffle = false;
118 painting_shuffle = false;
119 painting_mapping.clear();
120 mastery_requirement = 21;
121
122 connected = false;
123 has_connection_result = false;
124
125 apclient->set_room_info_handler([player, password]() {
126 inventory.clear();
127
128 tracker_frame->SetStatusMessage(
129 "Connected to Archipelago server. Authenticating...");
130
131 apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"},
132 {AP_MAJOR, AP_MINOR, AP_REVISION});
133 });
134
135 apclient->set_location_checked_handler(
136 [](const std::list<int64_t>& locations) {
137 for (const int64_t location_id : locations) {
138 checked_locations.insert(location_id);
139 std::cout << "Location: " << location_id << std::endl;
140 }
141
142 RefreshTracker();
143 });
144
145 apclient->set_slot_disconnected_handler([]() {
146 tracker_frame->SetStatusMessage(
147 "Disconnected from Archipelago. Attempting to reconnect...");
148 });
149
150 apclient->set_socket_disconnected_handler([]() {
151 tracker_frame->SetStatusMessage(
152 "Disconnected from Archipelago. Attempting to reconnect...");
153 });
154
155 apclient->set_items_received_handler(
156 [](const std::list<APClient::NetworkItem>& items) {
157 for (const APClient::NetworkItem& item : items) {
158 inventory[item.item]++;
159 std::cout << "Item: " << item.item << std::endl;
160 }
161
162 RefreshTracker();
163 });
164
165 apclient->set_slot_connected_handler([](const nlohmann::json& slot_data) {
166 tracker_frame->SetStatusMessage("Connected to Archipelago!");
167
168 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>();
169 color_shuffle = slot_data["shuffle_colors"].get<bool>();
170 painting_shuffle = slot_data["shuffle_paintings"].get<bool>();
171 mastery_requirement = slot_data["mastery_achievements"].get<int>();
172
173 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
174 painting_mapping.clear();
175
176 for (const auto& mapping_it :
177 slot_data["painting_entrance_to_exit"].items()) {
178 painting_mapping[mapping_it.key()] = mapping_it.value();
179 }
180 }
181
182 connected = true;
183 has_connection_result = true;
184
185 RefreshTracker();
186 });
187
188 apclient->set_slot_refused_handler([](const std::list<std::string>& errors) {
189 connected = false;
190 has_connection_result = true;
191
192 tracker_frame->SetStatusMessage("Disconnected from Archipelago.");
193
194 std::vector<std::string> error_messages;
195 error_messages.push_back("Could not connect to Archipelago.");
196
197 for (const std::string& error : errors) {
198 if (error == "InvalidSlot") {
199 error_messages.push_back("Invalid player name.");
200 } else if (error == "InvalidGame") {
201 error_messages.push_back("The specified player is not playing Lingo.");
202 } else if (error == "IncompatibleVersion") {
203 error_messages.push_back(
204 "The Archipelago server is not the correct version for this "
205 "client.");
206 } else if (error == "InvalidPassword") {
207 error_messages.push_back("Incorrect password.");
208 } else if (error == "InvalidItemsHandling") {
209 error_messages.push_back(
210 "Invalid item handling flag. This is a bug with the tracker. "
211 "Please report it to the lingo-ap-tracker GitHub.");
212 } else {
213 error_messages.push_back("Unknown error.");
214 }
215 }
216
217 std::string full_message = hatkirby::implode(error_messages, " ");
218
219 wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR);
220 });
221
222 client_active = true;
223
224 int timeout = 5000; // 5 seconds
225 int interval = 100;
226 int remaining_loops = timeout / interval;
227 while (!has_connection_result) {
228 if (interval == 0) {
229 connected = false;
230 has_connection_result = true;
231
232 DestroyClient();
233
234 tracker_frame->SetStatusMessage("Disconnected from Archipelago.");
235
236 wxMessageBox("Timeout while connecting to Archipelago server.",
237 "Connection failed", wxOK | wxICON_ERROR);
238 }
239
240 std::this_thread::sleep_for(std::chrono::milliseconds(100));
241
242 interval--;
243 }
244
245 if (connected) {
246 for (const MapArea& map_area : GetGameData().GetMapAreas()) {
247 for (int section_id = 0; section_id < map_area.locations.size();
248 section_id++) {
249 const Location& location = map_area.locations.at(section_id);
250
251 int64_t ap_id = apclient->get_location_id(location.ap_location_name);
252 if (ap_id == APClient::INVALID_NAME_ID) {
253 std::cout << "Could not find AP location ID for "
254 << location.ap_location_name << std::endl;
255 } else {
256 ap_id_by_location_id[{map_area.id, section_id}] = ap_id;
257 }
258 }
259 }
260
261 for (const Door& door : GetGameData().GetDoors()) {
262 if (!door.skip_item) {
263 ap_id_by_item_name[door.item_name] = GetItemId(door.item_name);
264
265 if (!door.group_name.empty() &&
266 !ap_id_by_item_name.count(door.group_name)) {
267 ap_id_by_item_name[door.group_name] = GetItemId(door.group_name);
268 }
269
270 for (const ProgressiveRequirement& prog_req : door.progressives) {
271 ap_id_by_item_name[prog_req.item_name] =
272 GetItemId(prog_req.item_name);
273 }
274 }
275 }
276
277 ap_id_by_color[LingoColor::kBlack] = GetItemId("Black");
278 ap_id_by_color[LingoColor::kRed] = GetItemId("Red");
279 ap_id_by_color[LingoColor::kBlue] = GetItemId("Blue");
280 ap_id_by_color[LingoColor::kYellow] = GetItemId("Yellow");
281 ap_id_by_color[LingoColor::kPurple] = GetItemId("Purple");
282 ap_id_by_color[LingoColor::kOrange] = GetItemId("Orange");
283 ap_id_by_color[LingoColor::kGreen] = GetItemId("Green");
284 ap_id_by_color[LingoColor::kBrown] = GetItemId("Brown");
285 ap_id_by_color[LingoColor::kGray] = GetItemId("Gray");
286
287 RefreshTracker();
288 } else {
289 client_active = false;
290 }
291}
292
293bool AP_HasCheckedGameLocation(int area_id, int section_id) {
294 std::tuple<int, int> location_key = {area_id, section_id};
295
296 if (ap_id_by_location_id.count(location_key)) {
297 return checked_locations.count(ap_id_by_location_id.at(location_key));
298 } else {
299 return false;
300 }
301}
302
303bool AP_HasColorItem(LingoColor color) {
304 if (ap_id_by_color.count(color)) {
305 return inventory.count(ap_id_by_color.at(color));
306 } else {
307 return false;
308 }
309}
310
311bool AP_HasItem(const std::string& item, int quantity) {
312 if (ap_id_by_item_name.count(item)) {
313 int64_t ap_id = ap_id_by_item_name.at(item);
314 return inventory.count(ap_id) && inventory.at(ap_id) >= quantity;
315 } else {
316 return false;
317 }
318}
319
320DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; }
321
322bool AP_IsColorShuffle() { return color_shuffle; }
323
324bool AP_IsPaintingShuffle() { return painting_shuffle; }
325
326const std::map<std::string, std::string> AP_GetPaintingMapping() {
327 return painting_mapping;
328}
329
330int AP_GetMasteryRequirement() { return mastery_requirement; }