diff options
| author | Star Rauchenberger <fefferburbia@gmail.com> | 2024-06-09 22:43:20 -0400 |
|---|---|---|
| committer | Star Rauchenberger <fefferburbia@gmail.com> | 2024-06-09 22:43:20 -0400 |
| commit | 475b7a38f66071ad5713f6f00a49c4e1399e0613 (patch) | |
| tree | 4dcb76d5bb9e1dbabe19dcbd0cc9676c31f715e6 /src/ap_state.cpp | |
| parent | 829bb6ba7fdbef5c4e6fb9e4eabc0c2f962325ae (diff) | |
| parent | 14d075e02007aeb53dbadd6c629564ee467cd7b2 (diff) | |
| download | lingo-ap-tracker-475b7a38f66071ad5713f6f00a49c4e1399e0613.tar.gz lingo-ap-tracker-475b7a38f66071ad5713f6f00a49c4e1399e0613.tar.bz2 lingo-ap-tracker-475b7a38f66071ad5713f6f00a49c4e1399e0613.zip | |
Merge branch 'main' into panels
Diffstat (limited to 'src/ap_state.cpp')
| -rw-r--r-- | src/ap_state.cpp | 163 |
1 files changed, 118 insertions, 45 deletions
| diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 4fd241a..a7565cf 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp | |||
| @@ -21,7 +21,6 @@ | |||
| 21 | #include <tuple> | 21 | #include <tuple> |
| 22 | 22 | ||
| 23 | #include "game_data.h" | 23 | #include "game_data.h" |
| 24 | #include "logger.h" | ||
| 25 | #include "tracker_frame.h" | 24 | #include "tracker_frame.h" |
| 26 | #include "tracker_state.h" | 25 | #include "tracker_state.h" |
| 27 | 26 | ||
| @@ -72,11 +71,12 @@ struct APState { | |||
| 72 | bool sunwarp_shuffle = false; | 71 | bool sunwarp_shuffle = false; |
| 73 | 72 | ||
| 74 | std::map<std::string, std::string> painting_mapping; | 73 | std::map<std::string, std::string> painting_mapping; |
| 74 | std::set<std::string> painting_codomain; | ||
| 75 | std::map<int, SunwarpMapping> sunwarp_mapping; | 75 | std::map<int, SunwarpMapping> sunwarp_mapping; |
| 76 | 76 | ||
| 77 | void Connect(std::string server, std::string player, std::string password) { | 77 | void Connect(std::string server, std::string player, std::string password) { |
| 78 | if (!initialized) { | 78 | if (!initialized) { |
| 79 | TrackerLog("Initializing APState..."); | 79 | wxLogVerbose("Initializing APState..."); |
| 80 | 80 | ||
| 81 | std::thread([this]() { | 81 | std::thread([this]() { |
| 82 | for (;;) { | 82 | for (;;) { |
| @@ -104,15 +104,16 @@ struct APState { | |||
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | tracked_data_storage_keys.push_back("PlayerPos"); | 106 | tracked_data_storage_keys.push_back("PlayerPos"); |
| 107 | tracked_data_storage_keys.push_back("Paintings"); | ||
| 107 | 108 | ||
| 108 | initialized = true; | 109 | initialized = true; |
| 109 | } | 110 | } |
| 110 | 111 | ||
| 111 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | 112 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); |
| 112 | TrackerLog("Connecting to Archipelago server (" + server + ")..."); | 113 | wxLogStatus("Connecting to Archipelago server (%s)...", server); |
| 113 | 114 | ||
| 114 | { | 115 | { |
| 115 | TrackerLog("Destroying old AP client..."); | 116 | wxLogVerbose("Destroying old AP client..."); |
| 116 | 117 | ||
| 117 | std::lock_guard client_guard(client_mutex); | 118 | std::lock_guard client_guard(client_mutex); |
| 118 | 119 | ||
| @@ -139,6 +140,7 @@ struct APState { | |||
| 139 | color_shuffle = false; | 140 | color_shuffle = false; |
| 140 | painting_shuffle = false; | 141 | painting_shuffle = false; |
| 141 | painting_mapping.clear(); | 142 | painting_mapping.clear(); |
| 143 | painting_codomain.clear(); | ||
| 142 | mastery_requirement = 21; | 144 | mastery_requirement = 21; |
| 143 | level_2_requirement = 223; | 145 | level_2_requirement = 223; |
| 144 | location_checks = kNORMAL_LOCATIONS; | 146 | location_checks = kNORMAL_LOCATIONS; |
| @@ -151,16 +153,17 @@ struct APState { | |||
| 151 | sunwarp_shuffle = false; | 153 | sunwarp_shuffle = false; |
| 152 | sunwarp_mapping.clear(); | 154 | sunwarp_mapping.clear(); |
| 153 | 155 | ||
| 156 | std::mutex connection_mutex; | ||
| 154 | connected = false; | 157 | connected = false; |
| 155 | has_connection_result = false; | 158 | has_connection_result = false; |
| 156 | 159 | ||
| 157 | apclient->set_room_info_handler([this, player, password]() { | 160 | apclient->set_room_info_handler([this, player, password]() { |
| 158 | inventory.clear(); | 161 | inventory.clear(); |
| 159 | 162 | ||
| 160 | TrackerLog("Connected to Archipelago server. Authenticating as " + | 163 | wxLogStatus("Connected to Archipelago server. Authenticating as %s %s", |
| 161 | player + | 164 | player, |
| 162 | (password.empty() ? " without password" | 165 | (password.empty() ? "without password" |
| 163 | : " with password " + password)); | 166 | : "with password " + password)); |
| 164 | tracker_frame->SetStatusMessage( | 167 | tracker_frame->SetStatusMessage( |
| 165 | "Connected to Archipelago server. Authenticating..."); | 168 | "Connected to Archipelago server. Authenticating..."); |
| 166 | 169 | ||
| @@ -172,23 +175,23 @@ struct APState { | |||
| 172 | [this](const std::list<int64_t>& locations) { | 175 | [this](const std::list<int64_t>& locations) { |
| 173 | for (const int64_t location_id : locations) { | 176 | for (const int64_t location_id : locations) { |
| 174 | checked_locations.insert(location_id); | 177 | checked_locations.insert(location_id); |
| 175 | TrackerLog("Location: " + std::to_string(location_id)); | 178 | wxLogVerbose("Location: %lld", location_id); |
| 176 | } | 179 | } |
| 177 | 180 | ||
| 178 | RefreshTracker(); | 181 | RefreshTracker(false); |
| 179 | }); | 182 | }); |
| 180 | 183 | ||
| 181 | apclient->set_slot_disconnected_handler([this]() { | 184 | apclient->set_slot_disconnected_handler([this]() { |
| 182 | tracker_frame->SetStatusMessage( | 185 | tracker_frame->SetStatusMessage( |
| 183 | "Disconnected from Archipelago. Attempting to reconnect..."); | 186 | "Disconnected from Archipelago. Attempting to reconnect..."); |
| 184 | TrackerLog( | 187 | wxLogStatus( |
| 185 | "Slot disconnected from Archipelago. Attempting to reconnect..."); | 188 | "Slot disconnected from Archipelago. Attempting to reconnect..."); |
| 186 | }); | 189 | }); |
| 187 | 190 | ||
| 188 | apclient->set_socket_disconnected_handler([this]() { | 191 | apclient->set_socket_disconnected_handler([this]() { |
| 189 | tracker_frame->SetStatusMessage( | 192 | tracker_frame->SetStatusMessage( |
| 190 | "Disconnected from Archipelago. Attempting to reconnect..."); | 193 | "Disconnected from Archipelago. Attempting to reconnect..."); |
| 191 | TrackerLog( | 194 | wxLogStatus( |
| 192 | "Socket disconnected from Archipelago. Attempting to reconnect..."); | 195 | "Socket disconnected from Archipelago. Attempting to reconnect..."); |
| 193 | }); | 196 | }); |
| 194 | 197 | ||
| @@ -196,10 +199,10 @@ struct APState { | |||
| 196 | [this](const std::list<APClient::NetworkItem>& items) { | 199 | [this](const std::list<APClient::NetworkItem>& items) { |
| 197 | for (const APClient::NetworkItem& item : items) { | 200 | for (const APClient::NetworkItem& item : items) { |
| 198 | inventory[item.item]++; | 201 | inventory[item.item]++; |
| 199 | TrackerLog("Item: " + std::to_string(item.item)); | 202 | wxLogVerbose("Item: %lld", item.item); |
| 200 | } | 203 | } |
| 201 | 204 | ||
| 202 | RefreshTracker(); | 205 | RefreshTracker(false); |
| 203 | }); | 206 | }); |
| 204 | 207 | ||
| 205 | apclient->set_retrieved_handler( | 208 | apclient->set_retrieved_handler( |
| @@ -208,20 +211,20 @@ struct APState { | |||
| 208 | HandleDataStorage(key, value); | 211 | HandleDataStorage(key, value); |
| 209 | } | 212 | } |
| 210 | 213 | ||
| 211 | RefreshTracker(); | 214 | RefreshTracker(false); |
| 212 | }); | 215 | }); |
| 213 | 216 | ||
| 214 | apclient->set_set_reply_handler([this](const std::string& key, | 217 | apclient->set_set_reply_handler([this](const std::string& key, |
| 215 | const nlohmann::json& value, | 218 | const nlohmann::json& value, |
| 216 | const nlohmann::json&) { | 219 | const nlohmann::json&) { |
| 217 | HandleDataStorage(key, value); | 220 | HandleDataStorage(key, value); |
| 218 | RefreshTracker(); | 221 | RefreshTracker(false); |
| 219 | }); | 222 | }); |
| 220 | 223 | ||
| 221 | apclient->set_slot_connected_handler([this]( | 224 | apclient->set_slot_connected_handler([this, &connection_mutex]( |
| 222 | const nlohmann::json& slot_data) { | 225 | const nlohmann::json& slot_data) { |
| 223 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | 226 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); |
| 224 | TrackerLog("Connected to Archipelago!"); | 227 | wxLogStatus("Connected to Archipelago!"); |
| 225 | 228 | ||
| 226 | data_storage_prefix = | 229 | data_storage_prefix = |
| 227 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; | 230 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; |
| @@ -266,6 +269,7 @@ struct APState { | |||
| 266 | for (const auto& mapping_it : | 269 | for (const auto& mapping_it : |
| 267 | slot_data["painting_entrance_to_exit"].items()) { | 270 | slot_data["painting_entrance_to_exit"].items()) { |
| 268 | painting_mapping[mapping_it.key()] = mapping_it.value(); | 271 | painting_mapping[mapping_it.key()] = mapping_it.value(); |
| 272 | painting_codomain.insert(mapping_it.value()); | ||
| 269 | } | 273 | } |
| 270 | } | 274 | } |
| 271 | 275 | ||
| @@ -281,11 +285,6 @@ struct APState { | |||
| 281 | } | 285 | } |
| 282 | } | 286 | } |
| 283 | 287 | ||
| 284 | connected = true; | ||
| 285 | has_connection_result = true; | ||
| 286 | |||
| 287 | RefreshTracker(); | ||
| 288 | |||
| 289 | std::list<std::string> corrected_keys; | 288 | std::list<std::string> corrected_keys; |
| 290 | for (const std::string& key : tracked_data_storage_keys) { | 289 | for (const std::string& key : tracked_data_storage_keys) { |
| 291 | corrected_keys.push_back(data_storage_prefix + key); | 290 | corrected_keys.push_back(data_storage_prefix + key); |
| @@ -302,12 +301,23 @@ struct APState { | |||
| 302 | 301 | ||
| 303 | apclient->Get(corrected_keys); | 302 | apclient->Get(corrected_keys); |
| 304 | apclient->SetNotify(corrected_keys); | 303 | apclient->SetNotify(corrected_keys); |
| 304 | |||
| 305 | { | ||
| 306 | std::lock_guard connection_lock(connection_mutex); | ||
| 307 | if (!has_connection_result) { | ||
| 308 | connected = true; | ||
| 309 | has_connection_result = true; | ||
| 310 | } | ||
| 311 | } | ||
| 305 | }); | 312 | }); |
| 306 | 313 | ||
| 307 | apclient->set_slot_refused_handler( | 314 | apclient->set_slot_refused_handler( |
| 308 | [this](const std::list<std::string>& errors) { | 315 | [this, &connection_mutex](const std::list<std::string>& errors) { |
| 309 | connected = false; | 316 | { |
| 310 | has_connection_result = true; | 317 | std::lock_guard connection_lock(connection_mutex); |
| 318 | connected = false; | ||
| 319 | has_connection_result = true; | ||
| 320 | } | ||
| 311 | 321 | ||
| 312 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 322 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
| 313 | 323 | ||
| @@ -336,7 +346,7 @@ struct APState { | |||
| 336 | } | 346 | } |
| 337 | 347 | ||
| 338 | std::string full_message = hatkirby::implode(error_messages, " "); | 348 | std::string full_message = hatkirby::implode(error_messages, " "); |
| 339 | TrackerLog(full_message); | 349 | wxLogError(wxString(full_message)); |
| 340 | 350 | ||
| 341 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); | 351 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); |
| 342 | }); | 352 | }); |
| @@ -346,18 +356,29 @@ struct APState { | |||
| 346 | int timeout = 5000; // 5 seconds | 356 | int timeout = 5000; // 5 seconds |
| 347 | int interval = 100; | 357 | int interval = 100; |
| 348 | int remaining_loops = timeout / interval; | 358 | int remaining_loops = timeout / interval; |
| 349 | while (!has_connection_result) { | 359 | while (true) { |
| 350 | if (interval == 0) { | 360 | { |
| 351 | connected = false; | 361 | std::lock_guard connection_lock(connection_mutex); |
| 352 | has_connection_result = true; | 362 | if (has_connection_result) { |
| 363 | break; | ||
| 364 | } | ||
| 365 | } | ||
| 353 | 366 | ||
| 367 | if (interval == 0) { | ||
| 354 | DestroyClient(); | 368 | DestroyClient(); |
| 355 | 369 | ||
| 356 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 370 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
| 357 | 371 | wxLogStatus("Timeout while connecting to Archipelago server."); | |
| 358 | TrackerLog("Timeout while connecting to Archipelago server."); | ||
| 359 | wxMessageBox("Timeout while connecting to Archipelago server.", | 372 | wxMessageBox("Timeout while connecting to Archipelago server.", |
| 360 | "Connection failed", wxOK | wxICON_ERROR); | 373 | "Connection failed", wxOK | wxICON_ERROR); |
| 374 | |||
| 375 | { | ||
| 376 | std::lock_guard connection_lock(connection_mutex); | ||
| 377 | connected = false; | ||
| 378 | has_connection_result = true; | ||
| 379 | } | ||
| 380 | |||
| 381 | break; | ||
| 361 | } | 382 | } |
| 362 | 383 | ||
| 363 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | 384 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| @@ -366,7 +387,8 @@ struct APState { | |||
| 366 | } | 387 | } |
| 367 | 388 | ||
| 368 | if (connected) { | 389 | if (connected) { |
| 369 | RefreshTracker(); | 390 | ResetReachabilityRequirements(); |
| 391 | RefreshTracker(true); | ||
| 370 | } else { | 392 | } else { |
| 371 | client_active = false; | 393 | client_active = false; |
| 372 | } | 394 | } |
| @@ -375,12 +397,11 @@ struct APState { | |||
| 375 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { | 397 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { |
| 376 | if (value.is_boolean()) { | 398 | if (value.is_boolean()) { |
| 377 | data_storage[key] = value.get<bool>(); | 399 | data_storage[key] = value.get<bool>(); |
| 378 | TrackerLog("Data storage " + key + " retrieved as " + | 400 | wxLogVerbose("Data storage %s retrieved as %s", key, |
| 379 | (value.get<bool>() ? "true" : "false")); | 401 | (value.get<bool>() ? "true" : "false")); |
| 380 | } else if (value.is_number()) { | 402 | } else if (value.is_number()) { |
| 381 | data_storage[key] = value.get<int>(); | 403 | data_storage[key] = value.get<int>(); |
| 382 | TrackerLog("Data storage " + key + " retrieved as " + | 404 | wxLogVerbose("Data storage %s retrieved as %d", key, value.get<int>()); |
| 383 | std::to_string(value.get<int>())); | ||
| 384 | } else if (value.is_object()) { | 405 | } else if (value.is_object()) { |
| 385 | if (key.ends_with("PlayerPos")) { | 406 | if (key.ends_with("PlayerPos")) { |
| 386 | auto map_value = value.get<std::map<std::string, int>>(); | 407 | auto map_value = value.get<std::map<std::string, int>>(); |
| @@ -389,7 +410,7 @@ struct APState { | |||
| 389 | data_storage[key] = value.get<std::map<std::string, int>>(); | 410 | data_storage[key] = value.get<std::map<std::string, int>>(); |
| 390 | } | 411 | } |
| 391 | 412 | ||
| 392 | TrackerLog("Data storage " + key + " retrieved as dictionary"); | 413 | wxLogVerbose("Data storage %s retrieved as dictionary", key); |
| 393 | } else if (value.is_null()) { | 414 | } else if (value.is_null()) { |
| 394 | if (key.ends_with("PlayerPos")) { | 415 | if (key.ends_with("PlayerPos")) { |
| 395 | player_pos = std::nullopt; | 416 | player_pos = std::nullopt; |
| @@ -397,7 +418,19 @@ struct APState { | |||
| 397 | data_storage.erase(key); | 418 | data_storage.erase(key); |
| 398 | } | 419 | } |
| 399 | 420 | ||
| 400 | TrackerLog("Data storage " + key + " retrieved as null"); | 421 | wxLogVerbose("Data storage %s retrieved as null", key); |
| 422 | } else if (value.is_array()) { | ||
| 423 | auto list_value = value.get<std::vector<std::string>>(); | ||
| 424 | |||
| 425 | if (key.ends_with("Paintings")) { | ||
| 426 | data_storage[key] = | ||
| 427 | std::set<std::string>(list_value.begin(), list_value.end()); | ||
| 428 | } else { | ||
| 429 | data_storage[key] = list_value; | ||
| 430 | } | ||
| 431 | |||
| 432 | wxLogVerbose("Data storage %s retrieved as list: [%s]", key, | ||
| 433 | hatkirby::implode(list_value, ", ")); | ||
| 401 | } | 434 | } |
| 402 | } | 435 | } |
| 403 | 436 | ||
| @@ -420,22 +453,46 @@ struct APState { | |||
| 420 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | 453 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); |
| 421 | } | 454 | } |
| 422 | 455 | ||
| 423 | void RefreshTracker() { | 456 | const std::set<std::string>& GetCheckedPaintings() { |
| 424 | TrackerLog("Refreshing display..."); | 457 | std::string key = data_storage_prefix + "Paintings"; |
| 458 | if (!data_storage.count(key)) { | ||
| 459 | data_storage[key] = std::set<std::string>(); | ||
| 460 | } | ||
| 461 | |||
| 462 | return std::any_cast<const std::set<std::string>&>(data_storage.at(key)); | ||
| 463 | } | ||
| 464 | |||
| 465 | bool IsPaintingChecked(const std::string& painting_id) { | ||
| 466 | const auto& checked_paintings = GetCheckedPaintings(); | ||
| 467 | |||
| 468 | return checked_paintings.count(painting_id) || | ||
| 469 | (painting_mapping.count(painting_id) && | ||
| 470 | checked_paintings.count(painting_mapping.at(painting_id))); | ||
| 471 | } | ||
| 472 | |||
| 473 | void RefreshTracker(bool reset) { | ||
| 474 | wxLogVerbose("Refreshing display..."); | ||
| 425 | 475 | ||
| 426 | RecalculateReachability(); | 476 | RecalculateReachability(); |
| 427 | tracker_frame->UpdateIndicators(); | 477 | |
| 478 | if (reset) { | ||
| 479 | tracker_frame->ResetIndicators(); | ||
| 480 | } else { | ||
| 481 | tracker_frame->UpdateIndicators(); | ||
| 482 | } | ||
| 428 | } | 483 | } |
| 429 | 484 | ||
| 430 | int64_t GetItemId(const std::string& item_name) { | 485 | int64_t GetItemId(const std::string& item_name) { |
| 431 | int64_t ap_id = apclient->get_item_id(item_name); | 486 | int64_t ap_id = apclient->get_item_id(item_name); |
| 432 | if (ap_id == APClient::INVALID_NAME_ID) { | 487 | if (ap_id == APClient::INVALID_NAME_ID) { |
| 433 | TrackerLog("Could not find AP item ID for " + item_name); | 488 | wxLogError("Could not find AP item ID for %s", item_name); |
| 434 | } | 489 | } |
| 435 | 490 | ||
| 436 | return ap_id; | 491 | return ap_id; |
| 437 | } | 492 | } |
| 438 | 493 | ||
| 494 | std::string GetItemName(int id) { return apclient->get_item_name(id); } | ||
| 495 | |||
| 439 | bool HasReachedGoal() { | 496 | bool HasReachedGoal() { |
| 440 | return data_storage.count(victory_data_storage_key) && | 497 | return data_storage.count(victory_data_storage_key) && |
| 441 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == | 498 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == |
| @@ -474,6 +531,10 @@ bool AP_HasItem(int item_id, int quantity) { | |||
| 474 | return GetState().HasItem(item_id, quantity); | 531 | return GetState().HasItem(item_id, quantity); |
| 475 | } | 532 | } |
| 476 | 533 | ||
| 534 | std::string AP_GetItemName(int item_id) { | ||
| 535 | return GetState().GetItemName(item_id); | ||
| 536 | } | ||
| 537 | |||
| 477 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } | 538 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } |
| 478 | 539 | ||
| 479 | bool AP_AreDoorsGrouped() { return GetState().group_doors; } | 540 | bool AP_AreDoorsGrouped() { return GetState().group_doors; } |
| @@ -482,10 +543,22 @@ bool AP_IsColorShuffle() { return GetState().color_shuffle; } | |||
| 482 | 543 | ||
| 483 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } | 544 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } |
| 484 | 545 | ||
| 485 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | 546 | const std::map<std::string, std::string>& AP_GetPaintingMapping() { |
| 486 | return GetState().painting_mapping; | 547 | return GetState().painting_mapping; |
| 487 | } | 548 | } |
| 488 | 549 | ||
| 550 | bool AP_IsPaintingMappedTo(const std::string& painting_id) { | ||
| 551 | return GetState().painting_codomain.count(painting_id); | ||
| 552 | } | ||
| 553 | |||
| 554 | const std::set<std::string>& AP_GetCheckedPaintings() { | ||
| 555 | return GetState().GetCheckedPaintings(); | ||
| 556 | } | ||
| 557 | |||
| 558 | bool AP_IsPaintingChecked(const std::string& painting_id) { | ||
| 559 | return GetState().IsPaintingChecked(painting_id); | ||
| 560 | } | ||
| 561 | |||
| 489 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } | 562 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } |
| 490 | 563 | ||
| 491 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } | 564 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } |
