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.cpp211
1 files changed, 151 insertions, 60 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 4ac0cce..8438649 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -10,6 +10,7 @@
10#include <any> 10#include <any>
11#include <apclient.hpp> 11#include <apclient.hpp>
12#include <apuuid.hpp> 12#include <apuuid.hpp>
13#include <bitset>
13#include <chrono> 14#include <chrono>
14#include <exception> 15#include <exception>
15#include <filesystem> 16#include <filesystem>
@@ -28,8 +29,8 @@
28#include "tracker_state.h" 29#include "tracker_state.h"
29 30
30constexpr int AP_MAJOR = 0; 31constexpr int AP_MAJOR = 0;
31constexpr int AP_MINOR = 4; 32constexpr int AP_MINOR = 6;
32constexpr int AP_REVISION = 5; 33constexpr int AP_REVISION = 1;
33 34
34constexpr const char* CERT_STORE_PATH = "cacert.pem"; 35constexpr const char* CERT_STORE_PATH = "cacert.pem";
35constexpr int ITEM_HANDLING = 7; // <- all 36constexpr int ITEM_HANDLING = 7; // <- all
@@ -37,8 +38,24 @@ constexpr int ITEM_HANDLING = 7; // <- all
37constexpr int CONNECTION_TIMEOUT = 50000; // 50 seconds 38constexpr int CONNECTION_TIMEOUT = 50000; // 50 seconds
38constexpr int CONNECTION_BACKOFF_INTERVAL = 100; 39constexpr int CONNECTION_BACKOFF_INTERVAL = 100;
39 40
41constexpr int PANEL_COUNT = 803;
42constexpr int PANEL_BITFIELD_LENGTH = 48;
43constexpr int PANEL_BITFIELDS = 17;
44
40namespace { 45namespace {
41 46
47const std::set<long> kNonProgressionItems = {
48 444409, // :)
49 444575, // The Feeling of Being Lost
50 444576, // Wanderlust
51 444577, // Empty White Hallways
52 444410, // Slowness Trap
53 444411, // Iceland Trap
54 444412, // Atbash Trap
55 444413, // Puzzle Skip
56 444680, // Speed Boost
57};
58
42struct APState { 59struct APState {
43 // Initialized on main thread 60 // Initialized on main thread
44 bool initialized = false; 61 bool initialized = false;
@@ -67,10 +84,12 @@ struct APState {
67 std::set<int64_t> checked_locations; 84 std::set<int64_t> checked_locations;
68 std::map<std::string, std::any> data_storage; 85 std::map<std::string, std::any> data_storage;
69 std::optional<std::tuple<int, int>> player_pos; 86 std::optional<std::tuple<int, int>> player_pos;
87 std::bitset<PANEL_COUNT> solved_panels;
70 88
71 DoorShuffleMode door_shuffle_mode = kNO_DOORS; 89 DoorShuffleMode door_shuffle_mode = kNO_DOORS;
72 bool group_doors = false; 90 bool group_doors = false;
73 bool color_shuffle = false; 91 bool color_shuffle = false;
92 PanelShuffleMode panel_shuffle_mode = kNO_PANELS;
74 bool painting_shuffle = false; 93 bool painting_shuffle = false;
75 int mastery_requirement = 21; 94 int mastery_requirement = 21;
76 int level_2_requirement = 223; 95 int level_2_requirement = 223;
@@ -82,6 +101,7 @@ struct APState {
82 bool pilgrimage_allows_paintings = false; 101 bool pilgrimage_allows_paintings = false;
83 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; 102 SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL;
84 bool sunwarp_shuffle = false; 103 bool sunwarp_shuffle = false;
104 bool postgame_shuffle = true;
85 105
86 std::map<std::string, std::string> painting_mapping; 106 std::map<std::string, std::string> painting_mapping;
87 std::set<std::string> painting_codomain; 107 std::set<std::string> painting_codomain;
@@ -128,10 +148,12 @@ struct APState {
128 checked_locations.clear(); 148 checked_locations.clear();
129 data_storage.clear(); 149 data_storage.clear();
130 player_pos = std::nullopt; 150 player_pos = std::nullopt;
151 solved_panels.reset();
131 victory_data_storage_key.clear(); 152 victory_data_storage_key.clear();
132 door_shuffle_mode = kNO_DOORS; 153 door_shuffle_mode = kNO_DOORS;
133 group_doors = false; 154 group_doors = false;
134 color_shuffle = false; 155 color_shuffle = false;
156 panel_shuffle_mode = kNO_PANELS;
135 painting_shuffle = false; 157 painting_shuffle = false;
136 painting_mapping.clear(); 158 painting_mapping.clear();
137 painting_codomain.clear(); 159 painting_codomain.clear();
@@ -146,6 +168,7 @@ struct APState {
146 sunwarp_access = kSUNWARP_ACCESS_NORMAL; 168 sunwarp_access = kSUNWARP_ACCESS_NORMAL;
147 sunwarp_shuffle = false; 169 sunwarp_shuffle = false;
148 sunwarp_mapping.clear(); 170 sunwarp_mapping.clear();
171 postgame_shuffle = true;
149 } 172 }
150 173
151 apclient->set_room_info_handler( 174 apclient->set_room_info_handler(
@@ -200,24 +223,13 @@ struct APState {
200 return checked_locations.count(location_id); 223 return checked_locations.count(location_id);
201 } 224 }
202 225
203 bool HasCheckedHuntPanel(int location_id) {
204 std::lock_guard state_guard(state_mutex);
205
206 std::string key =
207 fmt::format("{}Hunt|{}", data_storage_prefix, location_id);
208 return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key));
209 }
210
211 bool HasItem(int item_id, int quantity) { 226 bool HasItem(int item_id, int quantity) {
212 return inventory.count(item_id) && inventory.at(item_id) >= quantity; 227 return inventory.count(item_id) && inventory.at(item_id) >= quantity;
213 } 228 }
214 229
215 bool HasAchievement(const std::string& name) { 230 bool HasItemSafe(int item_id, int quantity) {
216 std::lock_guard state_guard(state_mutex); 231 std::lock_guard state_guard(state_mutex);
217 232 return HasItem(item_id, quantity);
218 std::string key =
219 fmt::format("{}Achievement|{}", data_storage_prefix, name);
220 return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key));
221 } 233 }
222 234
223 const std::set<std::string>& GetCheckedPaintings() { 235 const std::set<std::string>& GetCheckedPaintings() {
@@ -241,7 +253,21 @@ struct APState {
241 checked_paintings.count(painting_mapping.at(painting_id))); 253 checked_paintings.count(painting_mapping.at(painting_id)));
242 } 254 }
243 255
244 std::string GetItemName(int id) { return apclient->get_item_name(id); } 256 void RevealPaintings() {
257 std::lock_guard state_guard(state_mutex);
258
259 std::vector<std::string> paintings;
260 for (const PaintingExit& painting : GD_GetPaintings()) {
261 paintings.push_back(painting.internal_id);
262 }
263
264 APClient::DataStorageOperation operation;
265 operation.operation = "replace";
266 operation.value = paintings;
267
268 apclient->Set(fmt::format("{}Paintings", data_storage_prefix), "", true,
269 {operation});
270 }
245 271
246 bool HasReachedGoal() { 272 bool HasReachedGoal() {
247 std::lock_guard state_guard(state_mutex); 273 std::lock_guard state_guard(state_mutex);
@@ -251,6 +277,12 @@ struct APState {
251 30; // CLIENT_GOAL 277 30; // CLIENT_GOAL
252 } 278 }
253 279
280 bool IsPanelSolved(int solve_index) {
281 std::lock_guard state_guard(state_mutex);
282
283 return solved_panels.test(solve_index);
284 }
285
254 private: 286 private:
255 void Initialize() { 287 void Initialize() {
256 if (!initialized) { 288 if (!initialized) {
@@ -258,16 +290,8 @@ struct APState {
258 290
259 std::thread([this]() { Thread(); }).detach(); 291 std::thread([this]() { Thread(); }).detach();
260 292
261 for (int panel_id : GD_GetAchievementPanels()) { 293 for (int i = 0; i < PANEL_BITFIELDS; i++) {
262 tracked_data_storage_keys.push_back(fmt::format( 294 tracked_data_storage_keys.push_back(fmt::format("Panels_{}", i));
263 "Achievement|{}", GD_GetPanel(panel_id).achievement_name));
264 }
265
266 for (const MapArea& map_area : GD_GetMapAreas()) {
267 for (const Location& location : map_area.locations) {
268 tracked_data_storage_keys.push_back(
269 fmt::format("Hunt|{}", location.ap_location_id));
270 }
271 } 295 }
272 296
273 tracked_data_storage_keys.push_back("PlayerPos"); 297 tracked_data_storage_keys.push_back("PlayerPos");
@@ -351,7 +375,7 @@ struct APState {
351 } 375 }
352 } 376 }
353 377
354 RefreshTracker(false); 378 RefreshTracker(StateUpdate{.cleared_locations = true});
355 } 379 }
356 380
357 void OnSlotDisconnected() { 381 void OnSlotDisconnected() {
@@ -373,37 +397,59 @@ struct APState {
373 } 397 }
374 398
375 void OnItemsReceived(const std::list<APClient::NetworkItem>& items) { 399 void OnItemsReceived(const std::list<APClient::NetworkItem>& items) {
400 std::vector<ItemState> item_states;
401 bool progression_items = false;
402
376 { 403 {
377 std::lock_guard state_guard(state_mutex); 404 std::lock_guard state_guard(state_mutex);
378 405
406 std::map<int64_t, int> index_by_item;
407
379 for (const APClient::NetworkItem& item : items) { 408 for (const APClient::NetworkItem& item : items) {
380 inventory[item.item]++; 409 inventory[item.item]++;
381 TrackerLog(fmt::format("Item: {}", item.item)); 410 TrackerLog(fmt::format("Item: {}", item.item));
411
412 index_by_item[item.item] = item.index;
413
414 if (!kNonProgressionItems.count(item.item)) {
415 progression_items = true;
416 }
417 }
418
419 for (const auto& [item_id, item_index] : index_by_item) {
420 item_states.push_back(ItemState{.name = GD_GetItemName(item_id),
421 .amount = inventory[item_id],
422 .index = item_index});
382 } 423 }
383 } 424 }
384 425
385 RefreshTracker(false); 426 RefreshTracker(StateUpdate{.items = item_states,
427 .progression_items = progression_items});
386 } 428 }
387 429
388 void OnRetrieved(const std::map<std::string, nlohmann::json>& data) { 430 void OnRetrieved(const std::map<std::string, nlohmann::json>& data) {
431 StateUpdate state_update;
432
389 { 433 {
390 std::lock_guard state_guard(state_mutex); 434 std::lock_guard state_guard(state_mutex);
391 435
392 for (const auto& [key, value] : data) { 436 for (const auto& [key, value] : data) {
393 HandleDataStorage(key, value); 437 HandleDataStorage(key, value, state_update);
394 } 438 }
395 } 439 }
396 440
397 RefreshTracker(false); 441 RefreshTracker(state_update);
398 } 442 }
399 443
400 void OnSetReply(const std::string& key, const nlohmann::json& value) { 444 void OnSetReply(const std::string& key, const nlohmann::json& value) {
445 StateUpdate state_update;
446
401 { 447 {
402 std::lock_guard state_guard(state_mutex); 448 std::lock_guard state_guard(state_mutex);
403 HandleDataStorage(key, value); 449 HandleDataStorage(key, value, state_update);
404 } 450 }
405 451
406 RefreshTracker(false); 452 RefreshTracker(state_update);
407 } 453 }
408 454
409 void OnSlotConnected(std::string player, std::string server, 455 void OnSlotConnected(std::string player, std::string server,
@@ -435,6 +481,7 @@ struct APState {
435 } 481 }
436 } 482 }
437 color_shuffle = slot_data["shuffle_colors"].get<int>() == 1; 483 color_shuffle = slot_data["shuffle_colors"].get<int>() == 1;
484 panel_shuffle_mode = slot_data["shuffle_panels"].get<PanelShuffleMode>();
438 painting_shuffle = slot_data["shuffle_paintings"].get<int>() == 1; 485 painting_shuffle = slot_data["shuffle_paintings"].get<int>() == 1;
439 mastery_requirement = slot_data["mastery_achievements"].get<int>(); 486 mastery_requirement = slot_data["mastery_achievements"].get<int>();
440 level_2_requirement = slot_data["level_2_requirement"].get<int>(); 487 level_2_requirement = slot_data["level_2_requirement"].get<int>();
@@ -456,6 +503,9 @@ struct APState {
456 : kSUNWARP_ACCESS_NORMAL; 503 : kSUNWARP_ACCESS_NORMAL;
457 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") && 504 sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") &&
458 slot_data["shuffle_sunwarps"].get<int>() == 1; 505 slot_data["shuffle_sunwarps"].get<int>() == 1;
506 postgame_shuffle = slot_data.contains("shuffle_postgame")
507 ? (slot_data["shuffle_postgame"].get<int>() == 1)
508 : true;
459 509
460 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { 510 if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) {
461 painting_mapping.clear(); 511 painting_mapping.clear();
@@ -497,7 +547,7 @@ struct APState {
497 } 547 }
498 548
499 ResetReachabilityRequirements(); 549 ResetReachabilityRequirements();
500 RefreshTracker(true); 550 RefreshTracker(std::nullopt);
501 } 551 }
502 552
503 void OnSlotRefused(const std::list<std::string>& errors) { 553 void OnSlotRefused(const std::list<std::string>& errors) {
@@ -540,19 +590,40 @@ struct APState {
540 } 590 }
541 591
542 // Assumes state mutex is locked. 592 // Assumes state mutex is locked.
543 void HandleDataStorage(const std::string& key, const nlohmann::json& value) { 593 void HandleDataStorage(const std::string& key, const nlohmann::json& value, StateUpdate& state_update) {
544 if (value.is_boolean()) { 594 if (value.is_boolean()) {
545 data_storage[key] = value.get<bool>(); 595 data_storage[key] = value.get<bool>();
546 TrackerLog(fmt::format("Data storage {} retrieved as {}", key, 596 TrackerLog(fmt::format("Data storage {} retrieved as {}", key,
547 (value.get<bool>() ? "true" : "false"))); 597 (value.get<bool>() ? "true" : "false")));
598
548 } else if (value.is_number()) { 599 } else if (value.is_number()) {
549 data_storage[key] = value.get<int>(); 600 data_storage[key] = value.get<int>();
550 TrackerLog(fmt::format("Data storage {} retrieved as {}", key, 601 TrackerLog(fmt::format("Data storage {} retrieved as {}", key,
551 value.get<int>())); 602 value.get<int>()));
603
604 if (key == victory_data_storage_key) {
605 state_update.cleared_locations = true;
606 } else if (key.find("Panels_") != std::string::npos) {
607 int bitfield_num =
608 std::stoi(key.substr(data_storage_prefix.size() + 7));
609 uint64_t bitfield_value = value.get<uint64_t>();
610 for (int i = 0; i < PANEL_BITFIELD_LENGTH; i++) {
611 if ((bitfield_value & (1LL << i)) != 0) {
612 int solve_index = bitfield_num * PANEL_BITFIELD_LENGTH + i;
613
614 if (!solved_panels.test(solve_index)) {
615 state_update.panels.insert(solve_index);
616 }
617
618 solved_panels.set(solve_index);
619 }
620 }
621 }
552 } else if (value.is_object()) { 622 } else if (value.is_object()) {
553 if (key.ends_with("PlayerPos")) { 623 if (key.ends_with("PlayerPos")) {
554 auto map_value = value.get<std::map<std::string, int>>(); 624 auto map_value = value.get<std::map<std::string, int>>();
555 player_pos = std::tuple<int, int>(map_value["x"], map_value["z"]); 625 player_pos = std::tuple<int, int>(map_value["x"], map_value["z"]);
626 state_update.player_position = true;
556 } else { 627 } else {
557 data_storage[key] = value.get<std::map<std::string, int>>(); 628 data_storage[key] = value.get<std::map<std::string, int>>();
558 } 629 }
@@ -561,6 +632,7 @@ struct APState {
561 } else if (value.is_null()) { 632 } else if (value.is_null()) {
562 if (key.ends_with("PlayerPos")) { 633 if (key.ends_with("PlayerPos")) {
563 player_pos = std::nullopt; 634 player_pos = std::nullopt;
635 state_update.player_position = true;
564 } else { 636 } else {
565 data_storage.erase(key); 637 data_storage.erase(key);
566 } 638 }
@@ -572,6 +644,8 @@ struct APState {
572 if (key.ends_with("Paintings")) { 644 if (key.ends_with("Paintings")) {
573 data_storage[key] = 645 data_storage[key] =
574 std::set<std::string>(list_value.begin(), list_value.end()); 646 std::set<std::string>(list_value.begin(), list_value.end());
647 state_update.paintings =
648 std::vector<std::string>(list_value.begin(), list_value.end());
575 } else { 649 } else {
576 data_storage[key] = list_value; 650 data_storage[key] = list_value;
577 } 651 }
@@ -582,29 +656,34 @@ struct APState {
582 } 656 }
583 657
584 // State mutex should NOT be locked. 658 // State mutex should NOT be locked.
585 void RefreshTracker(bool reset) { 659 // nullopt state_update indicates a reset.
660 void RefreshTracker(std::optional<StateUpdate> state_update) {
586 TrackerLog("Refreshing display..."); 661 TrackerLog("Refreshing display...");
587 662
588 std::string prev_msg; 663 if (!state_update || state_update->progression_items ||
589 { 664 !state_update->paintings.empty()) {
590 std::lock_guard state_guard(state_mutex); 665 std::string prev_msg;
666 {
667 std::lock_guard state_guard(state_mutex);
591 668
592 prev_msg = status_message; 669 prev_msg = status_message;
593 SetStatusMessage(fmt::format("{} Recalculating...", status_message)); 670 SetStatusMessage(fmt::format("{} Recalculating...", status_message));
594 } 671 }
595 672
596 RecalculateReachability(); 673 RecalculateReachability();
597 674
598 if (reset) { 675 {
599 tracker_frame->ResetIndicators(); 676 std::lock_guard state_guard(state_mutex);
600 } else {
601 tracker_frame->UpdateIndicators();
602 }
603 677
604 { 678 SetStatusMessage(prev_msg);
605 std::lock_guard state_guard(state_mutex); 679 }
680 }
681
606 682
607 SetStatusMessage(prev_msg); 683 if (!state_update) {
684 tracker_frame->ResetIndicators();
685 } else {
686 tracker_frame->UpdateIndicators(*state_update);
608 } 687 }
609 } 688 }
610 689
@@ -639,16 +718,12 @@ bool AP_HasCheckedGameLocation(int location_id) {
639 return GetState().HasCheckedGameLocation(location_id); 718 return GetState().HasCheckedGameLocation(location_id);
640} 719}
641 720
642bool AP_HasCheckedHuntPanel(int location_id) {
643 return GetState().HasCheckedHuntPanel(location_id);
644}
645
646bool AP_HasItem(int item_id, int quantity) { 721bool AP_HasItem(int item_id, int quantity) {
647 return GetState().HasItem(item_id, quantity); 722 return GetState().HasItem(item_id, quantity);
648} 723}
649 724
650std::string AP_GetItemName(int item_id) { 725bool AP_HasItemSafe(int item_id, int quantity) {
651 return GetState().GetItemName(item_id); 726 return GetState().HasItemSafe(item_id, quantity);
652} 727}
653 728
654DoorShuffleMode AP_GetDoorShuffleMode() { 729DoorShuffleMode AP_GetDoorShuffleMode() {
@@ -695,6 +770,8 @@ bool AP_IsPaintingChecked(const std::string& painting_id) {
695 return GetState().IsPaintingChecked(painting_id); 770 return GetState().IsPaintingChecked(painting_id);
696} 771}
697 772
773void AP_RevealPaintings() { GetState().RevealPaintings(); }
774
698int AP_GetMasteryRequirement() { 775int AP_GetMasteryRequirement() {
699 std::lock_guard state_guard(GetState().state_mutex); 776 std::lock_guard state_guard(GetState().state_mutex);
700 777
@@ -707,6 +784,12 @@ int AP_GetLevel2Requirement() {
707 return GetState().level_2_requirement; 784 return GetState().level_2_requirement;
708} 785}
709 786
787LocationChecks AP_GetLocationsChecks() {
788 std::lock_guard state_guard(GetState().state_mutex);
789
790 return GetState().location_checks;
791}
792
710bool AP_IsLocationVisible(int classification) { 793bool AP_IsLocationVisible(int classification) {
711 std::lock_guard state_guard(GetState().state_mutex); 794 std::lock_guard state_guard(GetState().state_mutex);
712 795
@@ -734,14 +817,16 @@ bool AP_IsLocationVisible(int classification) {
734 return (world_state & classification); 817 return (world_state & classification);
735} 818}
736 819
737VictoryCondition AP_GetVictoryCondition() { 820PanelShuffleMode AP_GetPanelShuffleMode() {
738 std::lock_guard state_guard(GetState().state_mutex); 821 std::lock_guard state_guard(GetState().state_mutex);
739 822
740 return GetState().victory_condition; 823 return GetState().panel_shuffle_mode;
741} 824}
742 825
743bool AP_HasAchievement(const std::string& achievement_name) { 826VictoryCondition AP_GetVictoryCondition() {
744 return GetState().HasAchievement(achievement_name); 827 std::lock_guard state_guard(GetState().state_mutex);
828
829 return GetState().victory_condition;
745} 830}
746 831
747bool AP_HasEarlyColorHallways() { 832bool AP_HasEarlyColorHallways() {
@@ -784,6 +869,8 @@ std::map<int, SunwarpMapping> AP_GetSunwarpMapping() {
784 return GetState().sunwarp_mapping; 869 return GetState().sunwarp_mapping;
785} 870}
786 871
872bool AP_IsPostgameShuffle() { return GetState().postgame_shuffle; }
873
787bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } 874bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); }
788 875
789std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { 876std::optional<std::tuple<int, int>> AP_GetPlayerPosition() {
@@ -791,3 +878,7 @@ std::optional<std::tuple<int, int>> AP_GetPlayerPosition() {
791 878
792 return GetState().player_pos; 879 return GetState().player_pos;
793} 880}
881
882bool AP_IsPanelSolved(int solve_index) {
883 return GetState().IsPanelSolved(solve_index);
884}