Daeth link done

This commit is contained in:
aMannus
2025-07-04 10:28:21 +02:00
parent 427b5aa25c
commit 50af166a84
8 changed files with 104 additions and 36 deletions

View File

@@ -176,7 +176,6 @@ typedef struct ArchipelagoLocationData {
typedef struct ShipArchipelagoSaveContextData { typedef struct ShipArchipelagoSaveContextData {
u8 isArchipelago; u8 isArchipelago;
u32 lastReceivedItemIndex; u32 lastReceivedItemIndex;
u8 deathLink;
char roomHash[100]; char roomHash[100];
char slotName[17]; char slotName[17];
ArchipelagoLocationData locations[RC_MAX]; ArchipelagoLocationData locations[RC_MAX];

View File

@@ -23,6 +23,7 @@ DEFINE_HOOK(OnFlagSet, (int16_t flagType, int16_t flag));
DEFINE_HOOK(OnFlagUnset, (int16_t flagType, int16_t flag)); DEFINE_HOOK(OnFlagUnset, (int16_t flagType, int16_t flag));
DEFINE_HOOK(OnSceneSpawnActors, ()); DEFINE_HOOK(OnSceneSpawnActors, ());
DEFINE_HOOK(OnPlayerUpdate, ()); DEFINE_HOOK(OnPlayerUpdate, ());
DEFINE_HOOK(OnPlayerDeath, ());
DEFINE_HOOK(OnSetDoAction, (uint16_t action)); DEFINE_HOOK(OnSetDoAction, (uint16_t action));
DEFINE_HOOK(OnOcarinaSongAction, ()); DEFINE_HOOK(OnOcarinaSongAction, ());
DEFINE_HOOK(OnCuccoOrChickenHatch, ()); DEFINE_HOOK(OnCuccoOrChickenHatch, ());

View File

@@ -89,6 +89,10 @@ void GameInteractor_ExecuteOnPlayerUpdate() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerUpdate>(); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerUpdate>();
} }
void GameInteractor_ExecuteOnPlayerDeath() {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerDeath>();
}
void GameInteractor_ExecuteOnSetDoAction(uint16_t action) { void GameInteractor_ExecuteOnSetDoAction(uint16_t action) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetDoAction>(action); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnSetDoAction>(action);
} }

View File

@@ -26,6 +26,7 @@ void GameInteractor_ExecuteOnFlagSet(int16_t flagType, int16_t flag);
void GameInteractor_ExecuteOnFlagUnset(int16_t flagType, int16_t flag); void GameInteractor_ExecuteOnFlagUnset(int16_t flagType, int16_t flag);
void GameInteractor_ExecuteOnSceneSpawnActors(); void GameInteractor_ExecuteOnSceneSpawnActors();
void GameInteractor_ExecuteOnPlayerUpdate(); void GameInteractor_ExecuteOnPlayerUpdate();
void GameInteractor_ExecuteOnPlayerDeath();
void GameInteractor_ExecuteOnSetDoAction(uint16_t action); void GameInteractor_ExecuteOnSetDoAction(uint16_t action);
void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnOcarinaSongAction();
void GameInteractor_ExecuteOnCuccoOrChickenHatch(); void GameInteractor_ExecuteOnCuccoOrChickenHatch();

View File

@@ -30,6 +30,7 @@ ArchipelagoClient::ArchipelagoClient() {
gameWon = false; gameWon = false;
itemQueued = false; itemQueued = false;
disconnecting = false; disconnecting = false;
isDeathLinkedDeath = false;
} }
ArchipelagoClient& ArchipelagoClient::GetInstance() { ArchipelagoClient& ArchipelagoClient::GetInstance() {
@@ -63,7 +64,9 @@ bool ArchipelagoClient::StartClient() {
apClient->set_room_info_handler([&]() { apClient->set_room_info_handler([&]() {
std::list<std::string> tags; std::list<std::string> 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"), ""), apClient->ConnectSlot(CVarGetString(CVAR_REMOTE_ARCHIPELAGO("SlotName"), ""),
CVarGetString(CVAR_REMOTE_ARCHIPELAGO("Password"), ""), 0b001, tags); CVarGetString(CVAR_REMOTE_ARCHIPELAGO("Password"), ""), 0b001, tags);
}); });
@@ -208,6 +211,25 @@ bool ArchipelagoClient::StartClient() {
ArchipelagoConsole_PrintJson(coloredNodes); ArchipelagoConsole_PrintJson(coloredNodes);
}); });
apClient->set_bounced_handler([&](const nlohmann::json data) {
std::list<std::string> 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; return true;
} }
@@ -426,104 +448,7 @@ uint8_t ArchipelagoClient::GetConnectionStatus() {
} }
} }
extern "C" void Archipelago_InitSaveFile() { void ArchipelagoClient::OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped) {
gSaveContext.ship.quest.data.archipelago.isArchipelago = 1;
nlohmann::json slotData = ArchipelagoClient::GetInstance().GetSlotData();
gSaveContext.ship.quest.data.archipelago.deathLink = slotData["death_link"];
std::vector<ArchipelagoClient::ApItem> 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];
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName,
scoutedItems[i].itemName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName,
scoutedItems[i].playerName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName));
}
}
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));
SaveManager::Instance->LoadCharArray("slotName", gSaveContext.ship.quest.data.archipelago.slotName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
SaveManager::Instance->LoadArray(
"locations", ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations), [](size_t i) {
SaveManager::Instance->LoadStruct("", [&i]() {
SaveManager::Instance->LoadCharArray(
"itemName", gSaveContext.ship.quest.data.archipelago.locations[i].itemName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].itemName));
SaveManager::Instance->LoadCharArray(
"playerName", gSaveContext.ship.quest.data.archipelago.locations[i].playerName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].playerName));
});
});
}
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);
SaveManager::Instance->SaveArray(
"locations", ARRAY_COUNT(saveContext->ship.quest.data.archipelago.locations), [&](size_t i) {
SaveManager::Instance->SaveStruct("", [&]() {
SaveManager::Instance->SaveData("itemName",
saveContext->ship.quest.data.archipelago.locations[i].itemName);
SaveManager::Instance->SaveData("playerName",
saveContext->ship.quest.data.archipelago.locations[i].playerName);
});
});
}
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));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
for (uint32_t i = 0; i < ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations); i++) {
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].itemName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].itemName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].playerName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].playerName));
}
}
void RegisterArchipelago() {
// make sure the client is constructed
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) { if (rc == RC_ARCHIPELAGO_RECEIVED_ITEM) {
gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++; gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex++;
ArchipelagoClient::GetInstance().itemQueued = false; ArchipelagoClient::GetInstance().itemQueued = false;
@@ -554,7 +479,133 @@ void RegisterArchipelago() {
.suffix = std::string(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName) }); .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<std::string> 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();
std::vector<ArchipelagoClient::ApItem> 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];
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName,
scoutedItems[i].itemName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[rc].itemName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName,
scoutedItems[i].playerName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[rc].playerName));
}
}
void LoadArchipelagoData() {
SaveManager::Instance->LoadData("isArchipelago", gSaveContext.ship.quest.data.archipelago.isArchipelago);
SaveManager::Instance->LoadData("lastReceivedItemIndex",
gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex);
SaveManager::Instance->LoadCharArray("roomHash", gSaveContext.ship.quest.data.archipelago.roomHash,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash));
SaveManager::Instance->LoadCharArray("slotName", gSaveContext.ship.quest.data.archipelago.slotName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
SaveManager::Instance->LoadArray(
"locations", ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations), [](size_t i) {
SaveManager::Instance->LoadStruct("", [&i]() {
SaveManager::Instance->LoadCharArray(
"itemName", gSaveContext.ship.quest.data.archipelago.locations[i].itemName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].itemName));
SaveManager::Instance->LoadCharArray(
"playerName", gSaveContext.ship.quest.data.archipelago.locations[i].playerName,
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].playerName));
});
}); });
} }
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("roomHash", saveContext->ship.quest.data.archipelago.roomHash);
SaveManager::Instance->SaveData("slotName", saveContext->ship.quest.data.archipelago.slotName);
SaveManager::Instance->SaveArray(
"locations", ARRAY_COUNT(saveContext->ship.quest.data.archipelago.locations), [&](size_t i) {
SaveManager::Instance->SaveStruct("", [&]() {
SaveManager::Instance->SaveData("itemName",
saveContext->ship.quest.data.archipelago.locations[i].itemName);
SaveManager::Instance->SaveData("playerName",
saveContext->ship.quest.data.archipelago.locations[i].playerName);
});
});
}
void InitArchipelagoData(bool isDebug) {
gSaveContext.ship.quest.data.archipelago.isArchipelago = 0;
gSaveContext.ship.quest.data.archipelago.lastReceivedItemIndex = 0;
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.roomHash, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.roomHash));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.slotName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.slotName));
for (uint32_t i = 0; i < ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations); i++) {
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].itemName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].itemName));
SohUtils::CopyStringToCharArray(gSaveContext.ship.quest.data.archipelago.locations[i].playerName, "",
ARRAY_COUNT(gSaveContext.ship.quest.data.archipelago.locations[i].playerName));
}
}
void RegisterArchipelago() {
// make sure the client is constructed
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) {
ArchipelagoClient::GetInstance().OnItemGiven(rc, gi, isGiSkipped);
});
COND_HOOK(GameInteractor::OnPlayerDeath, IS_ARCHIPELAGO,
[]() { ArchipelagoClient::GetInstance().SendDeathLink(); });
}
static RegisterShipInitFunc initFunc(RegisterArchipelago, { "IS_ARCHIPELAGO" }); static RegisterShipInitFunc initFunc(RegisterArchipelago, { "IS_ARCHIPELAGO" });

View File

@@ -48,6 +48,9 @@ class ArchipelagoClient {
const std::string GetSlotName() const; const std::string GetSlotName() const;
uint8_t GetConnectionStatus(); uint8_t GetConnectionStatus();
void OnItemGiven(uint32_t rc, GetItemEntry gi, uint8_t isGiSkipped);
void SendDeathLink();
void SetDeathLinkTag();
const nlohmann::json GetSlotData(); const nlohmann::json GetSlotData();
const std::vector<ApItem>& GetScoutedItems(); const std::vector<ApItem>& GetScoutedItems();
@@ -65,6 +68,7 @@ class ArchipelagoClient {
std::unique_ptr<APClient> apClient; std::unique_ptr<APClient> apClient;
bool itemQueued; bool itemQueued;
bool disconnecting; bool disconnecting;
bool isDeathLinkedDeath;
int retries; int retries;
protected: protected:

View File

@@ -69,6 +69,13 @@ void ArchipelagoSettingsWindow::DrawElement() {
} }
ImGui::PopStyleColor(); 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; static bool sArchipelagoTexturesLoaded = false;
if (!sArchipelagoTexturesLoaded) { if (!sArchipelagoTexturesLoaded) {
Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage( Ship::Context::GetInstance()->GetWindow()->GetGui()->LoadTextureFromRawImage(

View File

@@ -3555,6 +3555,7 @@ void func_80836448(PlayState* play, Player* this, LinkAnimationHeader* anim) {
Audio_PlayFanfare(NA_BGM_GAME_OVER); Audio_PlayFanfare(NA_BGM_GAME_OVER);
gSaveContext.seqId = (u8)NA_BGM_DISABLED; gSaveContext.seqId = (u8)NA_BGM_DISABLED;
gSaveContext.natureAmbienceId = NATURE_ID_DISABLED; gSaveContext.natureAmbienceId = NATURE_ID_DISABLED;
GameInteractor_ExecuteOnPlayerDeath();
} }
OnePointCutscene_Init(play, 9806, cond ? 120 : 60, &this->actor, MAIN_CAM); OnePointCutscene_Init(play, 9806, cond ? 120 : 60, &this->actor, MAIN_CAM);