From 50af166a84d7273a80b1c21ec4f4bce2735c2b87 Mon Sep 17 00:00:00 2001 From: aMannus Date: Fri, 4 Jul 2025 10:28:21 +0200 Subject: [PATCH] Daeth link done --- soh/include/z64save.h | 1 - .../GameInteractor_HookTable.h | 1 + .../game-interactor/GameInteractor_Hooks.cpp | 4 + .../game-interactor/GameInteractor_Hooks.h | 1 + soh/soh/Network/Archipelago/Archipelago.cpp | 121 +++++++++++++----- soh/soh/Network/Archipelago/Archipelago.h | 4 + .../Archipelago/ArchipelagoSettingsWindow.cpp | 7 + .../actors/ovl_player_actor/z_player.c | 1 + 8 files changed, 104 insertions(+), 36 deletions(-) diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 7f7433b0b..6a0e0b720 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -176,7 +176,6 @@ typedef struct ArchipelagoLocationData { typedef struct ShipArchipelagoSaveContextData { u8 isArchipelago; u32 lastReceivedItemIndex; - u8 deathLink; char roomHash[100]; char slotName[17]; ArchipelagoLocationData locations[RC_MAX]; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 89c539e58..29bac7f5e 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -23,6 +23,7 @@ DEFINE_HOOK(OnFlagSet, (int16_t flagType, int16_t flag)); DEFINE_HOOK(OnFlagUnset, (int16_t flagType, int16_t flag)); DEFINE_HOOK(OnSceneSpawnActors, ()); DEFINE_HOOK(OnPlayerUpdate, ()); +DEFINE_HOOK(OnPlayerDeath, ()); DEFINE_HOOK(OnSetDoAction, (uint16_t action)); DEFINE_HOOK(OnOcarinaSongAction, ()); DEFINE_HOOK(OnCuccoOrChickenHatch, ()); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 79ef8d8b9..6a58e003a 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -89,6 +89,10 @@ void GameInteractor_ExecuteOnPlayerUpdate() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnPlayerDeath() { + GameInteractor::Instance->ExecuteHooks(); +} + void GameInteractor_ExecuteOnSetDoAction(uint16_t action) { GameInteractor::Instance->ExecuteHooks(action); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index b7ccb0b65..488a65d36 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -26,6 +26,7 @@ void GameInteractor_ExecuteOnFlagSet(int16_t flagType, int16_t flag); void GameInteractor_ExecuteOnFlagUnset(int16_t flagType, int16_t flag); void GameInteractor_ExecuteOnSceneSpawnActors(); void GameInteractor_ExecuteOnPlayerUpdate(); +void GameInteractor_ExecuteOnPlayerDeath(); void GameInteractor_ExecuteOnSetDoAction(uint16_t action); void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnCuccoOrChickenHatch(); diff --git a/soh/soh/Network/Archipelago/Archipelago.cpp b/soh/soh/Network/Archipelago/Archipelago.cpp index 0edfbe7dd..fad097bfb 100644 --- a/soh/soh/Network/Archipelago/Archipelago.cpp +++ b/soh/soh/Network/Archipelago/Archipelago.cpp @@ -30,6 +30,7 @@ ArchipelagoClient::ArchipelagoClient() { gameWon = false; itemQueued = false; disconnecting = false; + isDeathLinkedDeath = false; } ArchipelagoClient& ArchipelagoClient::GetInstance() { @@ -63,7 +64,9 @@ bool ArchipelagoClient::StartClient() { apClient->set_room_info_handler([&]() { std::list tags; - // tags.push_back("DeathLink"); // todo, implement deathlink + if (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0)) { + tags.push_back("DeathLink"); + } apClient->ConnectSlot(CVarGetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), ""), CVarGetString(CVAR_REMOTE_ARCHIPELAGO("Password"), ""), 0b001, tags); }); @@ -208,6 +211,25 @@ bool ArchipelagoClient::StartClient() { ArchipelagoConsole_PrintJson(coloredNodes); }); + apClient->set_bounced_handler([&](const nlohmann::json data) { + std::list tags = data["tags"]; + bool deathLink = (std::find(tags.begin(), tags.end(), "DeathLink") != tags.end()); + + if (deathLink && data["data"]["source"] != apClient->get_slot()) { + if (GameInteractor::IsSaveLoaded()) { + gSaveContext.health = 0; + Notification::Emit({ .prefix = data["data"]["source"], + .message = "died. Cause:", + .suffix = data["data"]["cause"] }); + std::string deathLinkMessage = + "[LOG] Received death link from " + std::string(data["data"]["source"]) + ". Cause: " + std::string(data["data"]["cause"]); + ArchipelagoConsole_SendMessage(deathLinkMessage.c_str()); + + isDeathLinkedDeath = true; + } + } + }); + return true; } @@ -426,11 +448,67 @@ uint8_t ArchipelagoClient::GetConnectionStatus() { } } +void ArchipelagoClient::OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) { + if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) { + gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++; + ArchipelagoClient::GetInstance().itemQueued = false; + } else { + ArchipelagoClient::GetInstance().CheckLocation((RandomizerCheck)rc); + + if (isGiSkipped && gi.modIndex == MOD_RANDOMIZER && + (gi.getItemId == RG_ARCHIPELAGO_ITEM_PROGRESSIVE || gi.getItemId == RG_ARCHIPELAGO_ITEM_USEFUL || + gi.getItemId == RG_ARCHIPELAGO_ITEM_JUNK)) { + + const char* itemIcon = ""; + switch (gi.getItemId) { + case RG_ARCHIPELAGO_ITEM_PROGRESSIVE: + itemIcon = "Archipelago Progressive Icon"; + break; + case RG_ARCHIPELAGO_ITEM_USEFUL: + itemIcon = "Archipelago Useful Icon"; + break; + case RG_ARCHIPELAGO_ITEM_JUNK: + itemIcon = "Archipelago Junk Icon"; + break; + } + + Notification::Emit( + { .itemIcon = itemIcon, + .prefix = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName), + .message = " for ", + .suffix = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName) }); + } + } +} + +void ArchipelagoClient::SendDeathLink() { + if (apClient && CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0) && !isDeathLinkedDeath) { + nlohmann::json data{ + { "time", apClient->get_server_time() }, + { "cause", "Shipwrecked by King Harkinian." }, + { "source", apClient->get_slot() } + }; + apClient->Bounce(data, {}, {}, { "DeathLink" }); + + Notification::Emit({ .message = "Sending Death Link" }); + ArchipelagoConsole_SendMessage("[LOG] Died, sending death link."); + } + + isDeathLinkedDeath = false; +} + +void ArchipelagoClient::SetDeathLinkTag() { + std::list tags; + if (CVarGetInteger(CVAR_REMOTE_ARCHIPELAGO("DeathLink"), 0)) { + tags.push_back("DeathLink"); + } + apClient->ConnectUpdate(false, 1, true, tags); +} + extern "C" void Archipelago_InitSaveFile() { gSaveContext.ship.quest.data.archipelago.isArchipelago = 1; nlohmann::json slotData = ArchipelagoClient::GetInstance().GetSlotData(); - gSaveContext.ship.quest.data.archipelago.deathLink = slotData["death_link"]; std::vector scoutedItems = ArchipelagoClient::GetInstance().GetScoutedItems(); @@ -456,7 +534,6 @@ void LoadArchipelagoData() { SaveManager::Instance->LoadData("isArchipelago", gSaveContext.ship.quest.data.archipelago.isArchipelago); SaveManager::Instance->LoadData("lastReceivedItemIndex", gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex); - SaveManager::Instance->LoadData("deathLink", gSaveContext.ship.quest.data.archipelago.deathLink); SaveManager::Instance->LoadCharArray("roomHash", gSaveContext.ship.quest.data.archipelago.roomHash, ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash)); @@ -480,7 +557,6 @@ void SaveArchipelagoData(SaveContext* saveContext, int sectionID, bool fullSave) SaveManager::Instance->SaveData("isArchipelago", saveContext->ship.quest.data.archipelago.isArchipelago); SaveManager::Instance->SaveData("lastReceivedItemIndex", saveContext->ship.quest.data.archipelago.lastReceivedItemIndex); - SaveManager::Instance->SaveData("deathLink", saveContext->ship.quest.data.archipelago.deathLink); SaveManager::Instance->SaveData("roomHash", saveContext->ship.quest.data.archipelago.roomHash); SaveManager::Instance->SaveData("slotName", saveContext->ship.quest.data.archipelago.slotName); @@ -499,7 +575,6 @@ void SaveArchipelagoData(SaveContext* saveContext, int sectionID, bool fullSave) void InitArchipelagoData(bool isDebug) { gSaveContext.ship.quest.data.archipelago.isArchipelago = 0; gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex = 0; - gSaveContext.ship.quest.data.archipelago.deathLink = 0; SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, "", ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash)); @@ -519,42 +594,18 @@ void RegisterArchipelago() { ArchipelagoClient::GetInstance(); COND_HOOK(GameInteractor::OnGameFrameUpdate, true, []() { ArchipelagoClient::GetInstance().Poll(); }); + COND_HOOK(GameInteractor::PostLoadGame, 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_RECEIVED_ITEM) { - gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++; - ArchipelagoClient::GetInstance().itemQueued = false; - } else { - ArchipelagoClient::GetInstance().CheckLocation((RandomizerCheck)rc); - - if (isGiSkipped && gi.modIndex == MOD_RANDOMIZER && - (gi.getItemId == RG_ARCHIPELAGO_ITEM_PROGRESSIVE || gi.getItemId == RG_ARCHIPELAGO_ITEM_USEFUL || - gi.getItemId == RG_ARCHIPELAGO_ITEM_JUNK)) { - - const char* itemIcon = ""; - switch (gi.getItemId) { - case RG_ARCHIPELAGO_ITEM_PROGRESSIVE: - itemIcon = "Archipelago Progressive Icon"; - break; - case RG_ARCHIPELAGO_ITEM_USEFUL: - itemIcon = "Archipelago Useful Icon"; - break; - case RG_ARCHIPELAGO_ITEM_JUNK: - itemIcon = "Archipelago Junk Icon"; - break; - } - - Notification::Emit( - { .itemIcon = itemIcon, - .prefix = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName), - .message = " for ", - .suffix = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName) }); - } - } + ArchipelagoClient::GetInstance().OnItemGiven(rc, gi, isGiSkipped); }); + + COND_HOOK(GameInteractor::OnPlayerDeath, IS_ARCHIPELAGO, + []() { ArchipelagoClient::GetInstance().SendDeathLink(); }); } static RegisterShipInitFunc initFunc(RegisterArchipelago, { "IS_ARCHIPELAGO" }); diff --git a/soh/soh/Network/Archipelago/Archipelago.h b/soh/soh/Network/Archipelago/Archipelago.h index aa282e2a8..ed77fb5ee 100644 --- a/soh/soh/Network/Archipelago/Archipelago.h +++ b/soh/soh/Network/Archipelago/Archipelago.h @@ -48,6 +48,9 @@ class ArchipelagoClient { const std::string GetSlotName() const; uint8_t GetConnectionStatus(); + void OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped); + void SendDeathLink(); + void SetDeathLinkTag(); const nlohmann::json GetSlotData(); const std::vector& GetScoutedItems(); @@ -65,6 +68,7 @@ class ArchipelagoClient { std::unique_ptr apClient; bool itemQueued; bool disconnecting; + bool isDeathLinkedDeath; int retries; protected: diff --git a/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp index d44d908d4..1ed91f3d1 100644 --- a/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp +++ b/soh/soh/Network/Archipelago/ArchipelagoSettingsWindow.cpp @@ -69,6 +69,13 @@ void ArchipelagoSettingsWindow::DrawElement() { } ImGui::PopStyleColor(); + ImGui::SeparatorText("Additional Options"); + if (UIWidgets::CVarCheckbox("Death Link", CVAR_REMOTE_ARCHIPELAGO("DeathLink"), + UIWidgets::CheckboxOptions() + .Color(THEME_COLOR).Tooltip("You die, others die. Others die, you die!"))) { + apClient.SetDeathLinkTag(); + } + static bool sArchipelagoTexturesLoaded = false; if (!sArchipelagoTexturesLoaded) { Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage( diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 4ae6fb744..4375f016b 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -3555,6 +3555,7 @@ void func_80836448(PlayState* play, Player* this, LinkAnimationHeader* anim) { Audio_PlayFanfare(NA_BGM_GAME_OVER); gSaveContext.seqId = (u8)NA_BGM_DISABLED; gSaveContext.natureAmbienceId = NATURE_ID_DISABLED; + GameInteractor_ExecuteOnPlayerDeath(); } OnePointCutscene_Init(play, 9806, cond ? 120 : 60, &this->actor, MAIN_CAM);