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