diff options
Diffstat (limited to 'src/ap_state.cpp')
| -rw-r--r-- | src/ap_state.cpp | 172 |
1 files changed, 123 insertions, 49 deletions
| diff --git a/src/ap_state.cpp b/src/ap_state.cpp index b0b4f0b..876fdd8 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #define _WEBSOCKETPP_CPP11_STRICT_ | 4 | #define _WEBSOCKETPP_CPP11_STRICT_ |
| 5 | #pragma comment(lib, "crypt32") | 5 | #pragma comment(lib, "crypt32") |
| 6 | 6 | ||
| 7 | #include <fmt/core.h> | ||
| 7 | #include <hkutil/string.h> | 8 | #include <hkutil/string.h> |
| 8 | 9 | ||
| 9 | #include <any> | 10 | #include <any> |
| @@ -71,6 +72,7 @@ struct APState { | |||
| 71 | bool sunwarp_shuffle = false; | 72 | bool sunwarp_shuffle = false; |
| 72 | 73 | ||
| 73 | std::map<std::string, std::string> painting_mapping; | 74 | std::map<std::string, std::string> painting_mapping; |
| 75 | std::set<std::string> painting_codomain; | ||
| 74 | std::map<int, SunwarpMapping> sunwarp_mapping; | 76 | std::map<int, SunwarpMapping> sunwarp_mapping; |
| 75 | 77 | ||
| 76 | void Connect(std::string server, std::string player, std::string password) { | 78 | void Connect(std::string server, std::string player, std::string password) { |
| @@ -91,24 +93,25 @@ struct APState { | |||
| 91 | }).detach(); | 93 | }).detach(); |
| 92 | 94 | ||
| 93 | for (int panel_id : GD_GetAchievementPanels()) { | 95 | for (int panel_id : GD_GetAchievementPanels()) { |
| 94 | tracked_data_storage_keys.push_back( | 96 | tracked_data_storage_keys.push_back(fmt::format( |
| 95 | "Achievement|" + GD_GetPanel(panel_id).achievement_name); | 97 | "Achievement|{}", GD_GetPanel(panel_id).achievement_name)); |
| 96 | } | 98 | } |
| 97 | 99 | ||
| 98 | for (const MapArea& map_area : GD_GetMapAreas()) { | 100 | for (const MapArea& map_area : GD_GetMapAreas()) { |
| 99 | for (const Location& location : map_area.locations) { | 101 | for (const Location& location : map_area.locations) { |
| 100 | tracked_data_storage_keys.push_back( | 102 | tracked_data_storage_keys.push_back( |
| 101 | "Hunt|" + std::to_string(location.ap_location_id)); | 103 | fmt::format("Hunt|{}", location.ap_location_id)); |
| 102 | } | 104 | } |
| 103 | } | 105 | } |
| 104 | 106 | ||
| 105 | tracked_data_storage_keys.push_back("PlayerPos"); | 107 | tracked_data_storage_keys.push_back("PlayerPos"); |
| 108 | tracked_data_storage_keys.push_back("Paintings"); | ||
| 106 | 109 | ||
| 107 | initialized = true; | 110 | initialized = true; |
| 108 | } | 111 | } |
| 109 | 112 | ||
| 110 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | 113 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); |
| 111 | TrackerLog("Connecting to Archipelago server (" + server + ")..."); | 114 | TrackerLog(fmt::format("Connecting to Archipelago server ({})...", server)); |
| 112 | 115 | ||
| 113 | { | 116 | { |
| 114 | TrackerLog("Destroying old AP client..."); | 117 | TrackerLog("Destroying old AP client..."); |
| @@ -137,6 +140,7 @@ struct APState { | |||
| 137 | color_shuffle = false; | 140 | color_shuffle = false; |
| 138 | painting_shuffle = false; | 141 | painting_shuffle = false; |
| 139 | painting_mapping.clear(); | 142 | painting_mapping.clear(); |
| 143 | painting_codomain.clear(); | ||
| 140 | mastery_requirement = 21; | 144 | mastery_requirement = 21; |
| 141 | level_2_requirement = 223; | 145 | level_2_requirement = 223; |
| 142 | location_checks = kNORMAL_LOCATIONS; | 146 | location_checks = kNORMAL_LOCATIONS; |
| @@ -149,16 +153,17 @@ struct APState { | |||
| 149 | sunwarp_shuffle = false; | 153 | sunwarp_shuffle = false; |
| 150 | sunwarp_mapping.clear(); | 154 | sunwarp_mapping.clear(); |
| 151 | 155 | ||
| 156 | std::mutex connection_mutex; | ||
| 152 | connected = false; | 157 | connected = false; |
| 153 | has_connection_result = false; | 158 | has_connection_result = false; |
| 154 | 159 | ||
| 155 | apclient->set_room_info_handler([this, player, password]() { | 160 | apclient->set_room_info_handler([this, player, password]() { |
| 156 | inventory.clear(); | 161 | inventory.clear(); |
| 157 | 162 | ||
| 158 | TrackerLog("Connected to Archipelago server. Authenticating as " + | 163 | TrackerLog(fmt::format( |
| 159 | player + | 164 | "Connected to Archipelago server. Authenticating as {} {}", player, |
| 160 | (password.empty() ? " without password" | 165 | (password.empty() ? "without password" |
| 161 | : " with password " + password)); | 166 | : "with password " + password))); |
| 162 | tracker_frame->SetStatusMessage( | 167 | tracker_frame->SetStatusMessage( |
| 163 | "Connected to Archipelago server. Authenticating..."); | 168 | "Connected to Archipelago server. Authenticating..."); |
| 164 | 169 | ||
| @@ -170,10 +175,10 @@ struct APState { | |||
| 170 | [this](const std::list<int64_t>& locations) { | 175 | [this](const std::list<int64_t>& locations) { |
| 171 | for (const int64_t location_id : locations) { | 176 | for (const int64_t location_id : locations) { |
| 172 | checked_locations.insert(location_id); | 177 | checked_locations.insert(location_id); |
| 173 | TrackerLog("Location: " + std::to_string(location_id)); | 178 | TrackerLog(fmt::format("Location: {}", location_id)); |
| 174 | } | 179 | } |
| 175 | 180 | ||
| 176 | RefreshTracker(); | 181 | RefreshTracker(false); |
| 177 | }); | 182 | }); |
| 178 | 183 | ||
| 179 | apclient->set_slot_disconnected_handler([this]() { | 184 | apclient->set_slot_disconnected_handler([this]() { |
| @@ -194,10 +199,10 @@ struct APState { | |||
| 194 | [this](const std::list<APClient::NetworkItem>& items) { | 199 | [this](const std::list<APClient::NetworkItem>& items) { |
| 195 | for (const APClient::NetworkItem& item : items) { | 200 | for (const APClient::NetworkItem& item : items) { |
| 196 | inventory[item.item]++; | 201 | inventory[item.item]++; |
| 197 | TrackerLog("Item: " + std::to_string(item.item)); | 202 | TrackerLog(fmt::format("Item: {}", item.item)); |
| 198 | } | 203 | } |
| 199 | 204 | ||
| 200 | RefreshTracker(); | 205 | RefreshTracker(false); |
| 201 | }); | 206 | }); |
| 202 | 207 | ||
| 203 | apclient->set_retrieved_handler( | 208 | apclient->set_retrieved_handler( |
| @@ -206,23 +211,23 @@ struct APState { | |||
| 206 | HandleDataStorage(key, value); | 211 | HandleDataStorage(key, value); |
| 207 | } | 212 | } |
| 208 | 213 | ||
| 209 | RefreshTracker(); | 214 | RefreshTracker(false); |
| 210 | }); | 215 | }); |
| 211 | 216 | ||
| 212 | apclient->set_set_reply_handler([this](const std::string& key, | 217 | apclient->set_set_reply_handler([this](const std::string& key, |
| 213 | const nlohmann::json& value, | 218 | const nlohmann::json& value, |
| 214 | const nlohmann::json&) { | 219 | const nlohmann::json&) { |
| 215 | HandleDataStorage(key, value); | 220 | HandleDataStorage(key, value); |
| 216 | RefreshTracker(); | 221 | RefreshTracker(false); |
| 217 | }); | 222 | }); |
| 218 | 223 | ||
| 219 | apclient->set_slot_connected_handler([this]( | 224 | apclient->set_slot_connected_handler([this, &connection_mutex]( |
| 220 | const nlohmann::json& slot_data) { | 225 | const nlohmann::json& slot_data) { |
| 221 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | 226 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); |
| 222 | TrackerLog("Connected to Archipelago!"); | 227 | TrackerLog("Connected to Archipelago!"); |
| 223 | 228 | ||
| 224 | data_storage_prefix = | 229 | data_storage_prefix = |
| 225 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; | 230 | fmt::format("Lingo_{}_", apclient->get_player_number()); |
| 226 | door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); | 231 | door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); |
| 227 | color_shuffle = slot_data["shuffle_colors"].get<int>() == 1; | 232 | color_shuffle = slot_data["shuffle_colors"].get<int>() == 1; |
| 228 | painting_shuffle = slot_data["shuffle_paintings"].get<int>() == 1; | 233 | painting_shuffle = slot_data["shuffle_paintings"].get<int>() == 1; |
| @@ -253,6 +258,7 @@ struct APState { | |||
| 253 | for (const auto& mapping_it : | 258 | for (const auto& mapping_it : |
| 254 | slot_data["painting_entrance_to_exit"].items()) { | 259 | slot_data["painting_entrance_to_exit"].items()) { |
| 255 | painting_mapping[mapping_it.key()] = mapping_it.value(); | 260 | painting_mapping[mapping_it.key()] = mapping_it.value(); |
| 261 | painting_codomain.insert(mapping_it.value()); | ||
| 256 | } | 262 | } |
| 257 | } | 263 | } |
| 258 | 264 | ||
| @@ -268,33 +274,39 @@ struct APState { | |||
| 268 | } | 274 | } |
| 269 | } | 275 | } |
| 270 | 276 | ||
| 271 | connected = true; | ||
| 272 | has_connection_result = true; | ||
| 273 | |||
| 274 | RefreshTracker(); | ||
| 275 | |||
| 276 | std::list<std::string> corrected_keys; | 277 | std::list<std::string> corrected_keys; |
| 277 | for (const std::string& key : tracked_data_storage_keys) { | 278 | for (const std::string& key : tracked_data_storage_keys) { |
| 278 | corrected_keys.push_back(data_storage_prefix + key); | 279 | corrected_keys.push_back(data_storage_prefix + key); |
| 279 | } | 280 | } |
| 280 | 281 | ||
| 281 | { | 282 | victory_data_storage_key = |
| 282 | std::ostringstream vdsks; | 283 | fmt::format("_read_client_status_{}_{}", apclient->get_team_number(), |
| 283 | vdsks << "_read_client_status_" << apclient->get_team_number() << "_" | 284 | apclient->get_player_number()); |
| 284 | << apclient->get_player_number(); | ||
| 285 | victory_data_storage_key = vdsks.str(); | ||
| 286 | } | ||
| 287 | 285 | ||
| 288 | corrected_keys.push_back(victory_data_storage_key); | 286 | corrected_keys.push_back(victory_data_storage_key); |
| 289 | 287 | ||
| 290 | apclient->Get(corrected_keys); | 288 | apclient->Get(corrected_keys); |
| 291 | apclient->SetNotify(corrected_keys); | 289 | apclient->SetNotify(corrected_keys); |
| 290 | |||
| 291 | ResetReachabilityRequirements(); | ||
| 292 | RefreshTracker(true); | ||
| 293 | |||
| 294 | { | ||
| 295 | std::lock_guard connection_lock(connection_mutex); | ||
| 296 | if (!has_connection_result) { | ||
| 297 | connected = true; | ||
| 298 | has_connection_result = true; | ||
| 299 | } | ||
| 300 | } | ||
| 292 | }); | 301 | }); |
| 293 | 302 | ||
| 294 | apclient->set_slot_refused_handler( | 303 | apclient->set_slot_refused_handler( |
| 295 | [this](const std::list<std::string>& errors) { | 304 | [this, &connection_mutex](const std::list<std::string>& errors) { |
| 296 | connected = false; | 305 | { |
| 297 | has_connection_result = true; | 306 | std::lock_guard connection_lock(connection_mutex); |
| 307 | connected = false; | ||
| 308 | has_connection_result = true; | ||
| 309 | } | ||
| 298 | 310 | ||
| 299 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 311 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
| 300 | 312 | ||
| @@ -333,18 +345,29 @@ struct APState { | |||
| 333 | int timeout = 5000; // 5 seconds | 345 | int timeout = 5000; // 5 seconds |
| 334 | int interval = 100; | 346 | int interval = 100; |
| 335 | int remaining_loops = timeout / interval; | 347 | int remaining_loops = timeout / interval; |
| 336 | while (!has_connection_result) { | 348 | while (true) { |
| 337 | if (interval == 0) { | 349 | { |
| 338 | connected = false; | 350 | std::lock_guard connection_lock(connection_mutex); |
| 339 | has_connection_result = true; | 351 | if (has_connection_result) { |
| 352 | break; | ||
| 353 | } | ||
| 354 | } | ||
| 340 | 355 | ||
| 356 | if (interval == 0) { | ||
| 341 | DestroyClient(); | 357 | DestroyClient(); |
| 342 | 358 | ||
| 343 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 359 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
| 344 | |||
| 345 | TrackerLog("Timeout while connecting to Archipelago server."); | 360 | TrackerLog("Timeout while connecting to Archipelago server."); |
| 346 | wxMessageBox("Timeout while connecting to Archipelago server.", | 361 | wxMessageBox("Timeout while connecting to Archipelago server.", |
| 347 | "Connection failed", wxOK | wxICON_ERROR); | 362 | "Connection failed", wxOK | wxICON_ERROR); |
| 363 | |||
| 364 | { | ||
| 365 | std::lock_guard connection_lock(connection_mutex); | ||
| 366 | connected = false; | ||
| 367 | has_connection_result = true; | ||
| 368 | } | ||
| 369 | |||
| 370 | break; | ||
| 348 | } | 371 | } |
| 349 | 372 | ||
| 350 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | 373 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| @@ -353,8 +376,6 @@ struct APState { | |||
| 353 | } | 376 | } |
| 354 | 377 | ||
| 355 | if (connected) { | 378 | if (connected) { |
| 356 | RefreshTracker(); | ||
| 357 | } else { | ||
| 358 | client_active = false; | 379 | client_active = false; |
| 359 | } | 380 | } |
| 360 | } | 381 | } |
| @@ -362,12 +383,12 @@ struct APState { | |||
| 362 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { | 383 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { |
| 363 | if (value.is_boolean()) { | 384 | if (value.is_boolean()) { |
| 364 | data_storage[key] = value.get<bool>(); | 385 | data_storage[key] = value.get<bool>(); |
| 365 | TrackerLog("Data storage " + key + " retrieved as " + | 386 | TrackerLog(fmt::format("Data storage {} retrieved as {}", key, |
| 366 | (value.get<bool>() ? "true" : "false")); | 387 | (value.get<bool>() ? "true" : "false"))); |
| 367 | } else if (value.is_number()) { | 388 | } else if (value.is_number()) { |
| 368 | data_storage[key] = value.get<int>(); | 389 | data_storage[key] = value.get<int>(); |
| 369 | TrackerLog("Data storage " + key + " retrieved as " + | 390 | TrackerLog(fmt::format("Data storage {} retrieved as {}", key, |
| 370 | std::to_string(value.get<int>())); | 391 | value.get<int>())); |
| 371 | } else if (value.is_object()) { | 392 | } else if (value.is_object()) { |
| 372 | if (key.ends_with("PlayerPos")) { | 393 | if (key.ends_with("PlayerPos")) { |
| 373 | auto map_value = value.get<std::map<std::string, int>>(); | 394 | auto map_value = value.get<std::map<std::string, int>>(); |
| @@ -376,7 +397,7 @@ struct APState { | |||
| 376 | data_storage[key] = value.get<std::map<std::string, int>>(); | 397 | data_storage[key] = value.get<std::map<std::string, int>>(); |
| 377 | } | 398 | } |
| 378 | 399 | ||
| 379 | TrackerLog("Data storage " + key + " retrieved as dictionary"); | 400 | TrackerLog(fmt::format("Data storage {} retrieved as dictionary", key)); |
| 380 | } else if (value.is_null()) { | 401 | } else if (value.is_null()) { |
| 381 | if (key.ends_with("PlayerPos")) { | 402 | if (key.ends_with("PlayerPos")) { |
| 382 | player_pos = std::nullopt; | 403 | player_pos = std::nullopt; |
| @@ -384,7 +405,19 @@ struct APState { | |||
| 384 | data_storage.erase(key); | 405 | data_storage.erase(key); |
| 385 | } | 406 | } |
| 386 | 407 | ||
| 387 | TrackerLog("Data storage " + key + " retrieved as null"); | 408 | TrackerLog(fmt::format("Data storage {} retrieved as null", key)); |
| 409 | } else if (value.is_array()) { | ||
| 410 | auto list_value = value.get<std::vector<std::string>>(); | ||
| 411 | |||
| 412 | if (key.ends_with("Paintings")) { | ||
| 413 | data_storage[key] = | ||
| 414 | std::set<std::string>(list_value.begin(), list_value.end()); | ||
| 415 | } else { | ||
| 416 | data_storage[key] = list_value; | ||
| 417 | } | ||
| 418 | |||
| 419 | TrackerLog(fmt::format("Data storage {} retrieved as list: [{}]", key, | ||
| 420 | hatkirby::implode(list_value, ", "))); | ||
| 388 | } | 421 | } |
| 389 | } | 422 | } |
| 390 | 423 | ||
| @@ -394,7 +427,7 @@ struct APState { | |||
| 394 | 427 | ||
| 395 | bool HasCheckedHuntPanel(int location_id) { | 428 | bool HasCheckedHuntPanel(int location_id) { |
| 396 | std::string key = | 429 | std::string key = |
| 397 | data_storage_prefix + "Hunt|" + std::to_string(location_id); | 430 | fmt::format("{}Hunt|{}", data_storage_prefix, location_id); |
| 398 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | 431 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); |
| 399 | } | 432 | } |
| 400 | 433 | ||
| @@ -403,26 +436,51 @@ struct APState { | |||
| 403 | } | 436 | } |
| 404 | 437 | ||
| 405 | bool HasAchievement(const std::string& name) { | 438 | bool HasAchievement(const std::string& name) { |
| 406 | std::string key = data_storage_prefix + "Achievement|" + name; | 439 | std::string key = |
| 440 | fmt::format("{}Achievement|{}", data_storage_prefix, name); | ||
| 407 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | 441 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); |
| 408 | } | 442 | } |
| 409 | 443 | ||
| 410 | void RefreshTracker() { | 444 | const std::set<std::string>& GetCheckedPaintings() { |
| 445 | std::string key = fmt::format("{}Paintings", data_storage_prefix); | ||
| 446 | if (!data_storage.count(key)) { | ||
| 447 | data_storage[key] = std::set<std::string>(); | ||
| 448 | } | ||
| 449 | |||
| 450 | return std::any_cast<const std::set<std::string>&>(data_storage.at(key)); | ||
| 451 | } | ||
| 452 | |||
| 453 | bool IsPaintingChecked(const std::string& painting_id) { | ||
| 454 | const auto& checked_paintings = GetCheckedPaintings(); | ||
| 455 | |||
| 456 | return checked_paintings.count(painting_id) || | ||
| 457 | (painting_mapping.count(painting_id) && | ||
| 458 | checked_paintings.count(painting_mapping.at(painting_id))); | ||
| 459 | } | ||
| 460 | |||
| 461 | void RefreshTracker(bool reset) { | ||
| 411 | TrackerLog("Refreshing display..."); | 462 | TrackerLog("Refreshing display..."); |
| 412 | 463 | ||
| 413 | RecalculateReachability(); | 464 | RecalculateReachability(); |
| 414 | tracker_frame->UpdateIndicators(); | 465 | |
| 466 | if (reset) { | ||
| 467 | tracker_frame->ResetIndicators(); | ||
| 468 | } else { | ||
| 469 | tracker_frame->UpdateIndicators(); | ||
| 470 | } | ||
| 415 | } | 471 | } |
| 416 | 472 | ||
| 417 | int64_t GetItemId(const std::string& item_name) { | 473 | int64_t GetItemId(const std::string& item_name) { |
| 418 | int64_t ap_id = apclient->get_item_id(item_name); | 474 | int64_t ap_id = apclient->get_item_id(item_name); |
| 419 | if (ap_id == APClient::INVALID_NAME_ID) { | 475 | if (ap_id == APClient::INVALID_NAME_ID) { |
| 420 | TrackerLog("Could not find AP item ID for " + item_name); | 476 | TrackerLog(fmt::format("Could not find AP item ID for {}", item_name)); |
| 421 | } | 477 | } |
| 422 | 478 | ||
| 423 | return ap_id; | 479 | return ap_id; |
| 424 | } | 480 | } |
| 425 | 481 | ||
| 482 | std::string GetItemName(int id) { return apclient->get_item_name(id); } | ||
| 483 | |||
| 426 | bool HasReachedGoal() { | 484 | bool HasReachedGoal() { |
| 427 | return data_storage.count(victory_data_storage_key) && | 485 | return data_storage.count(victory_data_storage_key) && |
| 428 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == | 486 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == |
| @@ -461,16 +519,32 @@ bool AP_HasItem(int item_id, int quantity) { | |||
| 461 | return GetState().HasItem(item_id, quantity); | 519 | return GetState().HasItem(item_id, quantity); |
| 462 | } | 520 | } |
| 463 | 521 | ||
| 522 | std::string AP_GetItemName(int item_id) { | ||
| 523 | return GetState().GetItemName(item_id); | ||
| 524 | } | ||
| 525 | |||
| 464 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } | 526 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } |
| 465 | 527 | ||
| 466 | bool AP_IsColorShuffle() { return GetState().color_shuffle; } | 528 | bool AP_IsColorShuffle() { return GetState().color_shuffle; } |
| 467 | 529 | ||
| 468 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } | 530 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } |
| 469 | 531 | ||
| 470 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | 532 | const std::map<std::string, std::string>& AP_GetPaintingMapping() { |
| 471 | return GetState().painting_mapping; | 533 | return GetState().painting_mapping; |
| 472 | } | 534 | } |
| 473 | 535 | ||
| 536 | bool AP_IsPaintingMappedTo(const std::string& painting_id) { | ||
| 537 | return GetState().painting_codomain.count(painting_id); | ||
| 538 | } | ||
| 539 | |||
| 540 | const std::set<std::string>& AP_GetCheckedPaintings() { | ||
| 541 | return GetState().GetCheckedPaintings(); | ||
| 542 | } | ||
| 543 | |||
| 544 | bool AP_IsPaintingChecked(const std::string& painting_id) { | ||
| 545 | return GetState().IsPaintingChecked(painting_id); | ||
| 546 | } | ||
| 547 | |||
| 474 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } | 548 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } |
| 475 | 549 | ||
| 476 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } | 550 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } |
