diff --git a/README.md b/README.md index edae15c6b..6fa826854 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ You can currently connect to the multiworld server, scout the items. If you have no randomizer genereted (the `Randomizer` folder is empty) `Connect` to the multiworld from the main screen, click the `Scout` button to load all of the item locations and finally press the `Link up` button, you'll be able to start a randomizer save file. with the items populated with the location from the server. -Sending and Recieving should be implemented, Saving the game doesn't save the current item index localy (the library is a bit anoying with that). +Sending and Receiving should be implemented, Saving the game doesn't save the current item index localy (the library is a bit anoying with that). Not all checks have been mapped, and some may be mapped incorrectly. The victory condition should be implemented but has largely gone untested. diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 73e447999..69118b5ab 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -71,5 +71,5 @@ DEFINE_HOOK(OnAssetAltChange, ()); DEFINE_HOOK(OnKaleidoUpdate, ()); DEFINE_HOOK(OnRandomizerItemGivenHooks, (uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped)); -DEFINE_HOOK(OnArchipelagoItemRecieved, (uint32_t rg)); +DEFINE_HOOK(OnArchipelagoItemReceived, (uint32_t rg)); DEFINE_HOOK(OnRandomizerExternalCheck, (uint32_t rc)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 238cff3b0..b1f4bb447 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -309,8 +309,8 @@ void GameInteractor_ExecuteOnRandomizerItemGivenHooks(uint32_t rc, GetItemEntry } // MARK: Archipelago -void GameInteractor_ExecuteOnArchipelagoItemRecieved(uint32_t rg) { - GameInteractor::Instance->ExecuteHooks(rg); +void GameInteractor_ExecuteOnArchipelagoItemReceived(uint32_t rg) { + GameInteractor::Instance->ExecuteHooks(rg); } void GameInteractor_ExecuteOnRandomizerExternalCheck(uint32_t rc) { diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 17de14e5f..0306dc38d 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -85,7 +85,7 @@ void GameInteractor_ExecuteOnKaleidoUpdate(); void GameInteractor_ExecuteOnRandomizerItemGivenHooks(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped); // Mark: - Archipelago -void GameInteractor_ExecuteOnArchipelagoItemRecieved(uint32_t rg); +void GameInteractor_ExecuteOnArchipelagoItemReceived(uint32_t rg); void GameInteractor_ExecuteOnRandomizerExternalCheck(uint32_t rc); #ifdef __cplusplus diff --git a/soh/soh/Enhancements/randomizer/context.cpp b/soh/soh/Enhancements/randomizer/context.cpp index 149e656a5..af2d403db 100644 --- a/soh/soh/Enhancements/randomizer/context.cpp +++ b/soh/soh/Enhancements/randomizer/context.cpp @@ -346,27 +346,27 @@ void Context::SetSpoilerLoaded(const bool spoilerLoaded) { mSpoilerLoaded = spoilerLoaded; } -void Context::AddRecievedArchipelagoItem(const RandomizerGet item) { - mAPrecieveQueue.emplace(item); +void Context::AddReceivedArchipelagoItem(const RandomizerGet item) { + mAPreceiveQueue.emplace(item); std::string logMessage = "[LOG] Item Pushed: " + item; ArchipelagoConsole_SendMessage(logMessage.c_str(), true); } GetItemEntry Context::GetArchipelagoGIEntry() { ArchipelagoConsole_SendMessage("[LOG] Trying to get Item Entry", true); - if(mAPrecieveQueue.empty()) { + if(mAPreceiveQueue.empty()) { // something must have gone wrong here, just give a rupee return ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, GI_HEART); } // get the first item from the archipelago queue - RandomizerGet item_id = mAPrecieveQueue.front(); + RandomizerGet item_id = mAPreceiveQueue.front(); assert(item_id != RG_NONE); Item& item = StaticData::RetrieveItem(item_id); SPDLOG_TRACE("Found item! {}, {}", item.GetName().GetEnglish(), (int)item_id); GetItemEntry item_entry = item.GetGIEntry_Copy(); - mAPrecieveQueue.pop(); + mAPreceiveQueue.pop(); return item_entry; // todo: add custom text maybe? } diff --git a/soh/soh/Enhancements/randomizer/context.h b/soh/soh/Enhancements/randomizer/context.h index f97b21287..648b9c731 100644 --- a/soh/soh/Enhancements/randomizer/context.h +++ b/soh/soh/Enhancements/randomizer/context.h @@ -107,7 +107,7 @@ class Context { */ RandoOptionLACSCondition LACSCondition() const; GetItemEntry GetFinalGIEntry(RandomizerCheck rc, bool checkObtainability = true, GetItemID ogItemId = GI_NONE); - void AddRecievedArchipelagoItem(const RandomizerGet item); + void AddReceivedArchipelagoItem(const RandomizerGet item); GetItemEntry GetArchipelagoGIEntry(); void ParseSpoiler(const char* spoilerFileName); void ParseHashIconIndexesJson(nlohmann::json spoilerFileJson); @@ -189,6 +189,6 @@ class Context { std::string mHash; std::string mSeedString; uint32_t mFinalSeed = 0; - std::queue mAPrecieveQueue = {}; + std::queue mAPreceiveQueue = {}; }; } // namespace Rando \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 601b2be0d..d1e41bceb 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -222,11 +222,11 @@ static std::queue randomizerQueuedChecks; static RandomizerCheck randomizerQueuedCheck = RC_UNKNOWN_CHECK; static GetItemEntry randomizerQueuedItemEntry = GET_ITEM_NONE; -void ArchipelagoOnRecieveItem(const int32_t item) { +void ArchipelagoOnReceiveItem(const int32_t item) { std::string logMessage = "[LOG] Receive item handler called: " + item; ArchipelagoConsole_SendMessage(logMessage.c_str(), true); - randomizerQueuedChecks.push(RC_ARCHIPELAGO_RECIEVED_ITEM); - Rando::Context::GetInstance()->AddRecievedArchipelagoItem(static_cast(item)); + randomizerQueuedChecks.push(RC_ARCHIPELAGO_RECEIVED_ITEM); + Rando::Context::GetInstance()->AddReceivedArchipelagoItem(static_cast(item)); } void RandomizerOnFlagSetHandler(int16_t flagType, int16_t flag) { @@ -313,7 +313,7 @@ void RandomizerOnPlayerUpdateForRCQueueHandler() { auto loc = Rando::Context::GetInstance()->GetItemLocation(rc); uint8_t isGiSkipped = 0; - if (rc == RC_ARCHIPELAGO_RECIEVED_ITEM) { + if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) { getItemEntry = Rando::Context::GetInstance()->GetArchipelagoGIEntry(); } else { RandomizerGet vanillaRandomizerGet = Rando::StaticData::GetLocation(rc)->GetVanillaItem(); @@ -2540,7 +2540,7 @@ void RandomizerRegisterHooks() { onCuccoOrChickenHatchHook = GameInteractor::Instance->RegisterGameHook( RandomizerOnCuccoOrChickenHatch); - COND_HOOK(GameInteractor::OnArchipelagoItemRecieved, IS_ARCHIPELAGO, ArchipelagoOnRecieveItem); + COND_HOOK(GameInteractor::OnArchipelagoItemReceived, IS_ARCHIPELAGO, ArchipelagoOnReceiveItem); COND_HOOK(GameInteractor::OnRandomizerExternalCheck, IS_ARCHIPELAGO, RandomizerOnExternalCheckHandler) if (RAND_GET_OPTION(RSK_FISHSANITY) != RO_FISHSANITY_OFF) { diff --git a/soh/soh/Enhancements/randomizer/item_location.cpp b/soh/soh/Enhancements/randomizer/item_location.cpp index b31914548..3269287a2 100644 --- a/soh/soh/Enhancements/randomizer/item_location.cpp +++ b/soh/soh/Enhancements/randomizer/item_location.cpp @@ -133,7 +133,7 @@ bool ItemLocation::HasObtained() const { } void ItemLocation::SetCheckStatus(RandomizerCheckStatus status_) { - if(rc == RC_ARCHIPELAGO_RECIEVED_ITEM) // never count the AP recieve trigger as 'collected' + if(rc == RC_ARCHIPELAGO_RECEIVED_ITEM) // never count the AP receive trigger as 'collected' return; status = status_; } diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 959e78f87..fe79f7c92 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -3472,7 +3472,7 @@ typedef enum { RC_DEKU_TREE_QUEEN_GOHMA_GRASS_8, // End Grass - RC_ARCHIPELAGO_RECIEVED_ITEM, + RC_ARCHIPELAGO_RECEIVED_ITEM, RC_MAX } RandomizerCheck; diff --git a/soh/soh/Network/Archipelago/Archipelago.cpp b/soh/soh/Network/Archipelago/Archipelago.cpp index 07263f213..c6e495608 100644 --- a/soh/soh/Network/Archipelago/Archipelago.cpp +++ b/soh/soh/Network/Archipelago/Archipelago.cpp @@ -29,10 +29,7 @@ ArchipelagoClient::ArchipelagoClient() { gameWon = false; itemQueued = false; - - // call poll every frame - COND_HOOK(GameInteractor::OnGameFrameUpdate, true, [](){ArchipelagoClient::GetInstance().Poll();}); - COND_HOOK(GameInteractor::OnLoadGame, true, [](int32_t file_id){ArchipelagoClient::GetInstance().GameLoaded();}); + disconnecting = false; } ArchipelagoClient& ArchipelagoClient::GetInstance() { @@ -45,6 +42,7 @@ bool ArchipelagoClient::StartClient() { apClient.reset(); } + disconnecting = false; apClient = std::unique_ptr( new APClient(uuid, AP_Client_consts::AP_GAME_NAME, CVarGetString(CVAR_REMOTE_ARCHIPELAGO("ServerAddress"), "localhost:38281"), "cacert.pem")); @@ -66,12 +64,22 @@ bool ArchipelagoClient::StartClient() { // if we are already in game when we connect // we won't have to request an itemSynch if(GameInteractor::IsSaveLoaded(true)) { + if(!isRightSaveLoaded()) { + disconnecting = true; + ArchipelagoConsole_SendMessage("[ERROR] Connected to incorrect slot, disconnecting..."); + return; + } + SynchSentLocations(); - SynchRecievedLocations(); + SynchReceivedLocations(); } }); apClient->set_items_received_handler([&](const std::list& items) { + if(disconnecting) { + return; + } + for(const APClient::NetworkItem& item : items) { ApItem apItem; const std::string game = apClient->get_player_game(item.player); @@ -85,6 +93,10 @@ bool ArchipelagoClient::StartClient() { }); apClient->set_location_info_handler([&](const std::list& items) { + if(disconnecting) { + return; + } + scoutedItems.clear(); for(const APClient::NetworkItem& item: items) { @@ -108,25 +120,71 @@ bool ArchipelagoClient::StartClient() { }); // todo maybe move these functions to a lambda, since they don't have to be static anymore apClient->set_location_checked_handler([&](const std::list locations) { + if(disconnecting) { + return; + } + for(const int64_t apLoc : locations) { QueueExternalCheck(apLoc); } }); apClient->set_print_json_handler([&](const APClient::PrintJSONArgs& arg) { - std::string tag = "[" + arg.type + "] "; - - const int slot = apClient->get_player_number(); - if(arg.type == "ItemSend") { - if((*arg.item).player == slot) { - tag = "[Found] "; - } else if (*arg.receiving == slot ) { - tag = "[Received] "; - } + if(disconnecting) { + return; } - std::string text = tag + apClient->render_json(arg.data, APClient::RenderFormat::TEXT); - ArchipelagoConsole_SendMessage(text.c_str(), false); + std::vector coloredNodes; + + for(const APClient::TextNode& node : arg.data) { + APClient* client = apClient.get(); + std::string color; + std::string text; + + if(node.type == "player_id") { + int id = std::stoi(node.text); + if (color.empty() && id == client->get_player_number()) color = "magenta"; + else if(color.empty()) color = "yellow"; + text = client->get_player_alias(id); + } else if (node.type == "item_id") { + int64_t id = std::stoll(node.text); + if(color.empty()) { + if (node.flags & APClient::ItemFlags::FLAG_ADVANCEMENT) color = "plum"; + else if (node.flags & APClient::ItemFlags::FLAG_NEVER_EXCLUDE) color = "slateblue"; + else if (node.flags & APClient::ItemFlags::FLAG_TRAP) color = "salmon"; + else color = "cyan"; + } + text = client->get_item_name(id, client->get_player_game(node.player)); + } else if (node.type == "location_id") { + int64_t id = std::stoll(node.text); + if (color.empty()) color = "blue"; + text = client->get_location_name(id, client->get_player_game(node.player)); + } else if (node.type == "hint_status") { + text = node.text; + if (node.hintStatus == APClient::HINT_FOUND) color = "green"; + else if (node.hintStatus == APClient::HINT_UNSPECIFIED) color = "grey"; + else if (node.hintStatus == APClient::HINT_NO_PRIORITY) color = "slateblue"; + else if (node.hintStatus == APClient::HINT_AVOID) color = "salmon"; + else if (node.hintStatus == APClient::HINT_PRIORITY) color = "plum"; + else color = "red"; // unknown status -> red + } else if (node.type == "ERROR") { + color = "ERROR"; + text = node.text; + } else if (node.type == "LOG") { + color = "LOG"; + text = node.text; + } else { + color = "white"; + text = node.text; + } + + ColoredTextNode Colornode; + Colornode.color = color; + Colornode.text = text; + coloredNodes.push_back(Colornode); + } + + ArchipelagoConsole_PrintJson(coloredNodes); }); return true; @@ -139,7 +197,14 @@ void ArchipelagoClient::GameLoaded() { // if its not an AP save, disconnect if(!IS_ARCHIPELAGO) { - apClient->reset(); + ArchipelagoConsole_SendMessage("[ERROR] Loaded save is not not an archipelago save, disconnecting..."); + disconnecting = true; + return; + } + + if(!isRightSaveLoaded()) { + ArchipelagoConsole_SendMessage("[ERROR] Loaded save is not associated with connected slot, disconnecting..."); + disconnecting = true; return; } @@ -147,7 +212,7 @@ void ArchipelagoClient::GameLoaded() { SynchItems(); SynchSentLocations(); - SynchRecievedLocations(); + SynchReceivedLocations(); } void ArchipelagoClient::StartLocationScouts() { @@ -185,7 +250,7 @@ void ArchipelagoClient::SynchSentLocations() { apClient->LocationChecks(checkedLocations); } -void ArchipelagoClient::SynchRecievedLocations() { +void ArchipelagoClient::SynchReceivedLocations() { // Open checks that have been found previously but went unsaved for(const int64_t apLoc : apClient->get_checked_locations()) { QueueExternalCheck(apLoc); @@ -196,12 +261,17 @@ void ArchipelagoClient::QueueExternalCheck(const int64_t apLocation) { const std::string checkName = apClient->get_location_name(apLocation, AP_Client_consts::AP_GAME_NAME); const uint32_t RC = static_cast(Rando::StaticData::locationNameToEnum[checkName]); + if(RC == RC_UNKNOWN_CHECK) { + ArchipelagoConsole_SendMessage("[ERROR] Attempting to queue RC_UNKOWN_CHECK, skipping", false); + return; + } + // Don't queue checks we already have if(Rando::Context::GetInstance()->GetItemLocation(RC)->HasObtained()) { return; } - std::string locationLog = "[LOG] Externaly checking" + checkName; + std::string locationLog = "[LOG] Externaly checking: " + checkName; ArchipelagoConsole_SendMessage(locationLog.c_str(), true); GameInteractor_ExecuteOnRandomizerExternalCheck(RC); @@ -212,12 +282,17 @@ bool ArchipelagoClient::IsConnected() { } void ArchipelagoClient::CheckLocation(RandomizerCheck sohCheckId) { + if(sohCheckId == RC_UNKNOWN_CHECK) { + ArchipelagoConsole_SendMessage("[ERROR] trying to send RC_UNKNOWN_CHECK, skipping", false); + return; + } + std::string apName = Rando::StaticData::GetLocation(sohCheckId)->GetName(); if (apName.empty()) { return; } - int64_t apItemId = apClient->get_location_id(std::string(apName)); + int64_t apItemId = apClient->get_location_id(std::string(apName)); std::string logMessage = "[LOG] Checked: " + apName + "(" + std::to_string(apItemId) + "), sending to AP server"; ArchipelagoConsole_SendMessage(logMessage.c_str(), true); @@ -234,21 +309,21 @@ void ArchipelagoClient::OnItemReceived(const ApItem apItem) { return; } - std::string logMessage = "[LOG] Recieved " + apItem.itemName; + std::string logMessage = "[LOG] Received " + apItem.itemName; ArchipelagoConsole_SendMessage(logMessage.c_str(), true); - // add item to the queue - recieveQueue.push(apItem); -} - -void ArchipelagoClient::QueueItem(const ApItem item) { - if(item.index < gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex) { + if(apItem.index < gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex) { // Skip queueing any items we already have - std::string logMessage = "[LOG] Skipping giving " + item.itemName + ". We recieved this previously."; + std::string logMessage = "[LOG] Skipping giving " + apItem.itemName + ". We received this previously."; ArchipelagoConsole_SendMessage(logMessage.c_str(), true); return; } + // add item to the queue + receiveQueue.push(apItem); +} + +void ArchipelagoClient::QueueItem(const ApItem item) { std::string logMessage = "[LOG] Giving " + item.itemName; ArchipelagoConsole_SendMessage(logMessage.c_str(), true); const RandomizerGet RG = Rando::StaticData::itemNameToEnum[item.itemName]; @@ -257,7 +332,7 @@ void ArchipelagoClient::QueueItem(const ApItem item) { } itemQueued = true; - GameInteractor_ExecuteOnArchipelagoItemRecieved(static_cast(RG)); + GameInteractor_ExecuteOnArchipelagoItemReceived(static_cast(RG)); } void ArchipelagoClient::SendGameWon() { @@ -267,22 +342,49 @@ void ArchipelagoClient::SendGameWon() { } } +void ArchipelagoClient::SendMessage(const std::string message) { + // local commands not implemented yet + if(message.starts_with("/")) { + ArchipelagoConsole_SendMessage("Ship of Harkinian does not have any local commands yet.\nUse !help\" to see server commands instead", false); + return; + } + + if(apClient == nullptr) { + ArchipelagoConsole_SendMessage("[ERROR] Could not send message. Please Connect to your slot.", false); + return; + } + + apClient->Say(message); +} + void ArchipelagoClient::Poll() { if(apClient == nullptr) { return; } - // queue another item to be recieved - if(!itemQueued && recieveQueue.size() > 0) { + if(disconnecting) { + apClient->reset(); + apClient = nullptr; + return; + } + + // queue another item to be received + if(!itemQueued && receiveQueue.size() > 0) { - const ApItem item = recieveQueue.front(); - recieveQueue.pop(); + const ApItem item = receiveQueue.front(); + receiveQueue.pop(); QueueItem(item); } apClient->poll(); } +bool ArchipelagoClient::isRightSaveLoaded() const { + const bool seedMatch = apClient->get_seed().compare(gSaveContext.ship.quest.data.archipelago.roomHash) == 0; + const bool slotMatch = GetSlotName().compare(gSaveContext.ship.quest.data.archipelago.slotName) == 0; + return seedMatch && slotMatch; +} + const std::string ArchipelagoClient::GetSlotName() const { if(apClient == NULL) { return ""; @@ -301,7 +403,7 @@ const std::vector& ArchipelagoClient::GetScoutedItems const char* ArchipelagoClient::GetConnectionStatus() { if (!apClient) { - return ""; + return "Disconnected!"; } APClient::State clientStatus = apClient->get_state(); @@ -317,7 +419,7 @@ const char* ArchipelagoClient::GetConnectionStatus() { return "Socket Connected!"; } case APClient::State::ROOM_INFO: { - return "Room info Recieved!"; + return "Room info Received!"; } case APClient::State::SLOT_CONNECTED: { return "Slot Connected!"; @@ -335,6 +437,12 @@ extern "C" void Archipelago_InitSaveFile() { std::vector scoutedItems = ArchipelagoClient::GetInstance().GetScoutedItems(); + ArchipelagoClient& client = ArchipelagoClient::GetInstance(); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, client.apClient->get_seed(), + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash)); + SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, client.apClient->get_slot(), + ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName)); + for (uint32_t i = 0; i < scoutedItems.size(); i++) { RandomizerCheck rc = Rando::StaticData::locationNameToEnum[scoutedItems[i].locationName]; @@ -411,11 +519,16 @@ void InitArchipelagoData(bool isDebug) { } void RegisterArchipelago() { + // make sure the client is constructed + ArchipelagoClient::GetInstance(); + CVarSetInteger(CVAR_REMOTE_ARCHIPELAGO("Connected"), 0); + COND_HOOK(GameInteractor::OnGameFrameUpdate, true, [](){ArchipelagoClient::GetInstance().Poll();}); + COND_HOOK(GameInteractor::OnLoadGame, true, [](int32_t file_id){ArchipelagoClient::GetInstance().GameLoaded();}); COND_HOOK(GameInteractor::OnRandomizerItemGivenHooks, IS_ARCHIPELAGO, [](uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) { - if (rc == RC_ARCHIPELAGO_RECIEVED_ITEM) { + if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) { gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++; ArchipelagoClient::GetInstance().itemQueued = false; } else { diff --git a/soh/soh/Network/Archipelago/Archipelago.h b/soh/soh/Network/Archipelago/Archipelago.h index 314c0a34a..fecb6038a 100644 --- a/soh/soh/Network/Archipelago/Archipelago.h +++ b/soh/soh/Network/Archipelago/Archipelago.h @@ -27,6 +27,11 @@ class ArchipelagoClient{ uint64_t index; }; + struct ColoredTextNode { + std::string text; + std::string color; + }; + static ArchipelagoClient& GetInstance(); bool StartClient(); @@ -36,7 +41,7 @@ class ArchipelagoClient{ void StartLocationScouts(); void SynchItems(); void SynchSentLocations(); - void SynchRecievedLocations(); + void SynchReceivedLocations(); // getters const std::string GetSlotName() const; @@ -53,11 +58,12 @@ class ArchipelagoClient{ void QueueExternalCheck(int64_t apLocation); void SendGameWon(); - + void SendMessage(const std::string message); void Poll(); std::unique_ptr apClient; bool itemQueued; + bool disconnecting; protected: ArchipelagoClient(); @@ -65,9 +71,12 @@ class ArchipelagoClient{ private: ArchipelagoClient(ArchipelagoClient &) = delete; void operator=(const ArchipelagoClient &) = delete; + + bool isRightSaveLoaded() const; + std::string uuid; - static std::shared_ptr instance; // is this even used? + static std::shared_ptr instance; static bool initialized; bool gameWon; @@ -75,7 +84,7 @@ class ArchipelagoClient{ nlohmann::json slotData; std::set locations; std::vector scoutedItems; - std::queue recieveQueue; + std::queue receiveQueue; }; void LoadArchipelagoData(); diff --git a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp index 2817b112e..b1580711f 100644 --- a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp +++ b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.cpp @@ -4,7 +4,7 @@ #include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" -ImVector Items; +std::vector> Items; bool autoScroll = true; using namespace UIWidgets; @@ -19,7 +19,21 @@ void ArchipelagoConsole_SendMessage(const char* fmt, bool debugMessage, ...) { vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); buf[IM_ARRAYSIZE(buf) - 1] = 0; va_end(args); - Items.push_back(strdup(buf)); + ArchipelagoClient::ColoredTextNode node; + node.text = std::string(buf); + node.color = "white"; + if (strstr(buf, "[ERROR]")) { + node.color = "ERROR"; + } else if (strstr(buf, "[LOG]")) { + node.color = "LOG"; + } + std::vector line; + line.push_back(node); + Items.push_back(line); +} + +void ArchipelagoConsole_PrintJson(const std::vector nodes) { + Items.push_back(nodes); } void ArchipelagoConsoleWindow::DrawElement() { @@ -28,57 +42,19 @@ void ArchipelagoConsoleWindow::DrawElement() { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.15f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15.0f, 12.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 1.0f)); if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 400), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_HorizontalScrollbar)) { - for (int i = 0; i < Items.Size; i++) { - const char* item = Items[i]; - ImVec4 color; - bool hasColor = false; - if (strstr(item, "[ERROR]")) { - color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); - hasColor = true; - } else if (strstr(item, "[LOG]")) { - color = ImVec4(0.7f, 0.7f, 1.0f, 1.0f); - hasColor = true; - } else if (strstr(item, "[Found]")) { - color = ImVec4(0.24f, 0.64f, 0.69f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Received]")) { - color = ImVec4(0.18f, 0.76f, 0.42f, 1.00f); - hasColor = true; - } else if (strstr(item, "[ItemCheat]")) { - color = ImVec4(0.97f, 0.26f, 0.26f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Hint]")) { - color = ImVec4(0.89f, 0.45f, 1.00f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Join]")) { - color = ImVec4(0.00f, 0.80f, 0.11f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Part]")) { - color = ImVec4(1.00f, 0.01f, 0.01f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Goal]")) { - color = ImVec4(0.00f, 0.70f, 0.21f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Release]")) { - color = ImVec4(0.51f, 0.21f, 0.61f, 1.00f); - hasColor = true; - } else if (strstr(item, "[Collect]")) { - color = ImVec4(0.51f, 0.21f, 0.61f, 1.00f); - hasColor = true; - } - if (hasColor) { - ImGui::PushStyleColor(ImGuiCol_Text, color); - } - - ImGui::TextUnformatted(item); - - if (hasColor) { + for(const std::vector& line : Items) { + for(const ArchipelagoClient::ColoredTextNode& node : line) { + ImGui::PushStyleColor(ImGuiCol_Text, getColorVal(node.color)); + ImGui::TextUnformatted(node.text.c_str()); + ImGui::SameLine(); ImGui::PopStyleColor(); } + ImGui::NewLine(); } // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. @@ -89,5 +65,58 @@ void ArchipelagoConsoleWindow::DrawElement() { } ImGui::EndChild(); ImGui::PopStyleColor(); - ImGui::PopStyleVar(2); + ImGui::PopStyleVar(3); + + static char textEntryBuf[1024]; + static bool keepFocus = false; + + if(keepFocus) { + ImGui::SetKeyboardFocusHere(); + keepFocus = false; + } + if(ImGui::InputText("##AP_MessageField", textEntryBuf, 1023, ImGuiInputTextFlags_EnterReturnsTrue)) { + ArchipelagoClient::GetInstance().SendMessage(std::string(textEntryBuf)); + textEntryBuf[0] = '\0'; + keepFocus = true; + } + //keepFocus = ImGui::IsItemActive(); + ImGui::SameLine(); + if(ImGui::Button("Send")) { + ArchipelagoClient::GetInstance().SendMessage(std::string(textEntryBuf)); + textEntryBuf[0] = '\0'; + keepFocus = true; + } }; + +ImVec4 getColorVal(const std::string& color) { // TODO change color strings to an enum + if (color == "ERROR") { + return ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + } else if(color =="LOG") { + return ImVec4(0.7f, 0.7f, 1.0f, 1.0f); + } else if(color == "black") { + return ImVec4(0.000f, 0.000f, 0.000f, 1.00f); + } else if(color == "red") { + return ImVec4(0.933f, 0.000f, 0.000f, 1.00f); + } else if(color == "green") { + return ImVec4(0.000f, 1.000f, 0.498f, 1.00f); + } else if(color == "yellow") { + return ImVec4(0.980f, 0.980f, 0.824f, 1.00f); + } else if(color == "blue") { + return ImVec4(0.392f, 0.584f, 0.929f, 1.00f); + } else if(color == "cyan") { + return ImVec4(0.000f, 0.933f, 0.933f, 1.00f); + } else if(color == "magenta") { + return ImVec4(0.933f, 0.000f, 0.933f, 1.00f); + } else if(color == "slateblue") { + return ImVec4(0.427f, 0.545f, 0.910f, 1.00f); + } else if(color == "plum") { + return ImVec4(0.686f, 0.600f, 0.937f, 1.00f); + } else if(color == "salmon") { + return ImVec4(0.980f, 0.502f, 0.447f, 1.00f); + } else if(color == "white") { + return ImVec4(0.93f, 0.93f, 0.93f, 1.00f); + } else if(color == "orange") { + return ImVec4(1.000, 0.467f, 0.000f, 1.000f); + } + return ImVec4(0.93f, 0.93f, 0.93f, 1.00f); +} \ No newline at end of file diff --git a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h index 46cc3332e..07597c515 100644 --- a/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h +++ b/soh/soh/Network/Archipelago/ArchipelagoConsoleWindow.h @@ -3,6 +3,9 @@ #define ARCHIPELAGO_CONSOLE_WINDOW_H #include +#include "Archipelago.h" +#include +#include class ArchipelagoConsoleWindow final : public Ship::GuiWindow { public: @@ -16,5 +19,7 @@ class ArchipelagoConsoleWindow final : public Ship::GuiWindow { }; void ArchipelagoConsole_SendMessage(const char* fmt, bool debugMessage = false, ...); +void ArchipelagoConsole_PrintJson(const std::vector nodes); +ImVec4 getColorVal(const std::string& color); #endif // ARCHIPELAGO_CONSOLE_WINDOW_H \ No newline at end of file