diff options
Diffstat (limited to 'src/ap_state.cpp')
-rw-r--r-- | src/ap_state.cpp | 330 |
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 | |||
24 | constexpr int AP_MAJOR = 0; | ||
25 | constexpr int AP_MINOR = 4; | ||
26 | constexpr int AP_REVISION = 0; | ||
27 | |||
28 | constexpr int ITEM_HANDLING = 7; // <- all | ||
29 | |||
30 | namespace { | ||
31 | |||
32 | APClient* apclient = nullptr; | ||
33 | |||
34 | bool initialized = false; | ||
35 | |||
36 | TrackerFrame* tracker_frame; | ||
37 | |||
38 | bool client_active = false; | ||
39 | std::mutex client_mutex; | ||
40 | |||
41 | bool connected = false; | ||
42 | bool has_connection_result = false; | ||
43 | |||
44 | std::map<int64_t, int> inventory; | ||
45 | std::set<int64_t> checked_locations; | ||
46 | |||
47 | std::map<std::tuple<int, int>, int64_t> ap_id_by_location_id; | ||
48 | std::map<std::string, int64_t> ap_id_by_item_name; | ||
49 | std::map<LingoColor, int64_t> ap_id_by_color; | ||
50 | std::map<int64_t, std::string> progressive_item_by_ap_id; | ||
51 | |||
52 | DoorShuffleMode door_shuffle_mode = kNO_DOORS; | ||
53 | bool color_shuffle = false; | ||
54 | bool painting_shuffle = false; | ||
55 | int mastery_requirement = 21; | ||
56 | |||
57 | std::map<std::string, std::string> painting_mapping; | ||
58 | |||
59 | void RefreshTracker() { | ||
60 | GetTrackerState().CalculateState(); | ||
61 | tracker_frame->UpdateIndicators(); | ||
62 | } | ||
63 | |||
64 | int64_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 | |||
73 | void DestroyClient() { | ||
74 | client_active = false; | ||
75 | apclient->reset(); | ||
76 | delete apclient; | ||
77 | apclient = nullptr; | ||
78 | } | ||
79 | |||
80 | } // namespace | ||
81 | |||
82 | void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; } | ||
83 | |||
84 | void 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 | |||
293 | bool 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 | |||
303 | bool 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 | |||
311 | bool 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 | |||
320 | DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; } | ||
321 | |||
322 | bool AP_IsColorShuffle() { return color_shuffle; } | ||
323 | |||
324 | bool AP_IsPaintingShuffle() { return painting_shuffle; } | ||
325 | |||
326 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | ||
327 | return painting_mapping; | ||
328 | } | ||
329 | |||
330 | int AP_GetMasteryRequirement() { return mastery_requirement; } | ||