diff --git a/soh/include/functions.h b/soh/include/functions.h index 1d9895523c6..497a41f2a77 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1565,6 +1565,7 @@ s32 func_800C0808(PlayState* play, s16 camId, Player* player, s16 arg3); s32 Play_CameraChangeSetting(PlayState* play, s16 camId, s16 arg2); void func_800C08AC(PlayState* play, s16 camId, s16 arg2); void Play_SaveSceneFlags(PlayState* play); +void Play_SetRespawnData(PlayState* play, s32 respawnMode, s16 entranceIndex, s32 roomIndex, s32 playerParams, Vec3f* pos, s16 yaw); void Play_SetupRespawnPoint(PlayState* play, s32 respawnMode, s32 playerParams); void Play_TriggerVoidOut(PlayState* play); void Play_TriggerRespawn(PlayState* play); diff --git a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp index c019facb4d2..1dc1ec41329 100644 --- a/soh/soh/Enhancements/debugger/debugSaveEditor.cpp +++ b/soh/soh/Enhancements/debugger/debugSaveEditor.cpp @@ -130,12 +130,36 @@ std::map itemMapping = { ITEM_MAP_ENTRY(ITEM_WALLET_GIANT), ITEM_MAP_ENTRY(ITEM_SEEDS), ITEM_MAP_ENTRY(ITEM_FISHING_POLE), + ITEM_MAP_ENTRY(ITEM_SONG_MINUET), + ITEM_MAP_ENTRY(ITEM_SONG_BOLERO), + ITEM_MAP_ENTRY(ITEM_SONG_SERENADE), + ITEM_MAP_ENTRY(ITEM_SONG_REQUIEM), + ITEM_MAP_ENTRY(ITEM_SONG_NOCTURNE), + ITEM_MAP_ENTRY(ITEM_SONG_PRELUDE), + ITEM_MAP_ENTRY(ITEM_SONG_LULLABY), + ITEM_MAP_ENTRY(ITEM_SONG_EPONA), + ITEM_MAP_ENTRY(ITEM_SONG_SARIA), + ITEM_MAP_ENTRY(ITEM_SONG_SUN), + ITEM_MAP_ENTRY(ITEM_SONG_TIME), + ITEM_MAP_ENTRY(ITEM_SONG_STORMS), + ITEM_MAP_ENTRY(ITEM_MEDALLION_FOREST), + ITEM_MAP_ENTRY(ITEM_MEDALLION_FIRE), + ITEM_MAP_ENTRY(ITEM_MEDALLION_WATER), + ITEM_MAP_ENTRY(ITEM_MEDALLION_SPIRIT), + ITEM_MAP_ENTRY(ITEM_MEDALLION_SHADOW), + ITEM_MAP_ENTRY(ITEM_MEDALLION_LIGHT), + ITEM_MAP_ENTRY(ITEM_KOKIRI_EMERALD), + ITEM_MAP_ENTRY(ITEM_GORON_RUBY), + ITEM_MAP_ENTRY(ITEM_ZORA_SAPPHIRE), + ITEM_MAP_ENTRY(ITEM_STONE_OF_AGONY), + ITEM_MAP_ENTRY(ITEM_GERUDO_CARD), + ITEM_MAP_ENTRY(ITEM_SKULL_TOKEN), + ITEM_MAP_ENTRY(ITEM_HEART_CONTAINER), + ITEM_MAP_ENTRY(ITEM_HEART_PIECE), ITEM_MAP_ENTRY(ITEM_KEY_BOSS), ITEM_MAP_ENTRY(ITEM_COMPASS), ITEM_MAP_ENTRY(ITEM_DUNGEON_MAP), ITEM_MAP_ENTRY(ITEM_KEY_SMALL), - ITEM_MAP_ENTRY(ITEM_HEART_CONTAINER), - ITEM_MAP_ENTRY(ITEM_HEART_PIECE), ITEM_MAP_ENTRY(ITEM_MAGIC_SMALL), ITEM_MAP_ENTRY(ITEM_MAGIC_LARGE) }; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp index f9afb1decd5..7ac7c709dc5 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp @@ -639,4 +639,17 @@ namespace GameInteractionEffect { void SlipperyFloor::_Remove() { GameInteractor::State::SlipperyFloorActive = 0; } + + // MARK: - GiveItem + GameInteractionEffectQueryResult GiveItem::CanBeApplied() { + if (!GameInteractor::IsSaveLoaded()) { + return GameInteractionEffectQueryResult::NotPossible; + } + + return GameInteractionEffectQueryResult::Possible; + } + + void GiveItem::_Apply() { + GameInteractor::RawAction::GiveItem(parameters[0], parameters[1]); + } } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h index ebc1b6fac22..b370a6754f2 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.h @@ -262,6 +262,11 @@ namespace GameInteractionEffect { void _Apply() override; void _Remove() override; }; + + class GiveItem: public GameInteractionEffectBase, public ParameterizedGameInteractionEffect { + GameInteractionEffectQueryResult CanBeApplied() override; + void _Apply() override; + }; } #endif /* __cplusplus */ diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor.h b/soh/soh/Enhancements/game-interactor/GameInteractor.h index 241eba36519..cb8cb57c224 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor.h @@ -9,6 +9,7 @@ typedef enum { GI_SCHEME_BUILT_IN, GI_SCHEME_CROWD_CONTROL, + GI_SCHEME_ANCHOR, } GIScheme; typedef enum { @@ -222,6 +223,7 @@ class GameInteractor { static void SetFlag(int16_t flagType, int16_t chestNum); static void UnsetFlag(int16_t flagType, int16_t chestNum); static void AddOrRemoveHealthContainers(int16_t amount); + static void GiveItem(uint16_t modId, uint16_t itemId); static void AddOrRemoveMagic(int8_t amount); static void HealOrDamagePlayer(int16_t hearts); static void SetPlayerHealth(int16_t hearts); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp new file mode 100644 index 00000000000..95fd8ef55c3 --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp @@ -0,0 +1,877 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#include "GameInteractor_Anchor.h" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include "macros.h" +#include "z64scene.h" +#include "z64actor.h" +#include "functions.h" +extern "C" s16 gEnPartnerId; +extern PlayState* gPlayState; +extern SaveContext gSaveContext; +} + +using json = nlohmann::json; + +void from_json(const json& j, Color_RGB8& color) { + j.at("r").get_to(color.r); + j.at("g").get_to(color.g); + j.at("b").get_to(color.b); +} + +void to_json(json& j, const Color_RGB8& color) { + j = json{ + {"r", color.r}, + {"g", color.g}, + {"b", color.b} + }; +} + +void to_json(json& j, const Vec3f& vec) { + j = json{ + {"x", vec.x}, + {"y", vec.y}, + {"z", vec.z} + }; +} + +void to_json(json& j, const Vec3s& vec) { + j = json{ + {"x", vec.x}, + {"y", vec.y}, + {"z", vec.z} + }; +} + +void from_json(const json& j, Vec3f& vec) { + j.at("x").get_to(vec.x); + j.at("y").get_to(vec.y); + j.at("z").get_to(vec.z); +} + +void from_json(const json& j, Vec3s& vec) { + j.at("x").get_to(vec.x); + j.at("y").get_to(vec.y); + j.at("z").get_to(vec.z); +} + +void to_json(json& j, const PosRot& posRot) { + j = json{ + {"pos", posRot.pos}, + {"rot", posRot.rot} + }; +} + +void from_json(const json& j, PosRot& posRot) { + j.at("pos").get_to(posRot.pos); + j.at("rot").get_to(posRot.rot); +} + +void from_json(const json& j, AnchorClient& client) { + j.contains("clientId") ? j.at("clientId").get_to(client.clientId) : client.clientId = 0; + j.contains("clientVersion") ? j.at("clientVersion").get_to(client.clientVersion) : client.clientVersion = "???"; + j.contains("name") ? j.at("name").get_to(client.name) : client.name = "???"; + j.contains("color") ? j.at("color").get_to(client.color) : client.color = {255, 255, 255}; + j.contains("seed") ? j.at("seed").get_to(client.seed) : client.seed = "???"; + j.contains("scene") ? j.at("scene").get_to(client.scene) : client.scene = SCENE_ID_MAX; + j.contains("roomIndex") ? j.at("roomIndex").get_to(client.roomIndex) : client.roomIndex = 0; + j.contains("entranceIndex") ? j.at("entranceIndex").get_to(client.entranceIndex) : client.entranceIndex = 0; + j.contains("posRot") ? j.at("posRot").get_to(client.posRot) : client.posRot = { -9999, -9999, -9999, 0, 0, 0 }; +} + +void to_json(json& j, const SavedSceneFlags& flags) { + j = json{ + {"chest", flags.chest}, + {"swch", flags.swch}, + {"clear", flags.clear}, + {"collect", flags.collect}, + }; +} + +void from_json(const json& j, SavedSceneFlags& flags) { + j.at("chest").get_to(flags.chest); + j.at("swch").get_to(flags.swch); + j.at("clear").get_to(flags.clear); + j.at("collect").get_to(flags.collect); +} + +void to_json(json& j, const Inventory& inventory) { + j = json{ + {"items", inventory.items}, + {"ammo", inventory.ammo}, + {"equipment", inventory.equipment}, + {"upgrades", inventory.upgrades}, + {"questItems", inventory.questItems}, + {"dungeonItems", inventory.dungeonItems}, + {"dungeonKeys", inventory.dungeonKeys}, + {"defenseHearts", inventory.defenseHearts}, + {"gsTokens", inventory.gsTokens} + }; +} + +void from_json(const json& j, Inventory& inventory) { + j.at("items").get_to(inventory.items); + j.at("ammo").get_to(inventory.ammo); + j.at("equipment").get_to(inventory.equipment); + j.at("upgrades").get_to(inventory.upgrades); + j.at("questItems").get_to(inventory.questItems); + j.at("dungeonItems").get_to(inventory.dungeonItems); + j.at("dungeonKeys").get_to(inventory.dungeonKeys); + j.at("defenseHearts").get_to(inventory.defenseHearts); + j.at("gsTokens").get_to(inventory.gsTokens); +} + +void to_json(json& j, const SohStats& sohStats) { + j = json{ + {"locationsSkipped", sohStats.locationsSkipped}, + {"fileCreatedAt", sohStats.fileCreatedAt}, + }; +} + +void from_json(const json& j, SohStats& sohStats) { + j.at("locationsSkipped").get_to(sohStats.locationsSkipped); + j.contains("fileCreatedAt") ? j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt) : gSaveContext.sohStats.fileCreatedAt; +} + +void to_json(json& j, const SaveContext& saveContext) { + j = json{ + {"healthCapacity", saveContext.healthCapacity}, + {"magicLevel", saveContext.magicLevel}, + {"magicCapacity", saveContext.magicCapacity}, + {"isMagicAcquired", saveContext.isMagicAcquired}, + {"isDoubleMagicAcquired", saveContext.isDoubleMagicAcquired}, + {"isDoubleDefenseAcquired", saveContext.isDoubleDefenseAcquired}, + {"bgsFlag", saveContext.bgsFlag}, + {"swordHealth", saveContext.swordHealth}, + {"sceneFlags", saveContext.sceneFlags}, + {"eventChkInf", saveContext.eventChkInf}, + {"itemGetInf", saveContext.itemGetInf}, + {"infTable", saveContext.infTable}, + {"randomizerInf", saveContext.randomizerInf}, + {"gsFlags", saveContext.gsFlags}, + {"inventory", saveContext.inventory}, + {"sohStats", saveContext.sohStats}, + {"adultTradeItems", saveContext.adultTradeItems}, + }; +} + +void from_json(const json& j, SaveContext& saveContext) { + j.at("healthCapacity").get_to(saveContext.healthCapacity); + j.at("magicLevel").get_to(saveContext.magicLevel); + j.at("magicCapacity").get_to(saveContext.magicCapacity); + j.at("isMagicAcquired").get_to(saveContext.isMagicAcquired); + j.at("isDoubleMagicAcquired").get_to(saveContext.isDoubleMagicAcquired); + j.at("isDoubleDefenseAcquired").get_to(saveContext.isDoubleDefenseAcquired); + j.at("bgsFlag").get_to(saveContext.bgsFlag); + j.at("swordHealth").get_to(saveContext.swordHealth); + j.at("sceneFlags").get_to(saveContext.sceneFlags); + j.at("eventChkInf").get_to(saveContext.eventChkInf); + j.at("itemGetInf").get_to(saveContext.itemGetInf); + j.at("infTable").get_to(saveContext.infTable); + j.at("randomizerInf").get_to(saveContext.randomizerInf); + j.at("gsFlags").get_to(saveContext.gsFlags); + j.at("inventory").get_to(saveContext.inventory); + j.at("sohStats").get_to(saveContext.sohStats); + j.at("adultTradeItems").get_to(saveContext.adultTradeItems); +} + +std::map GameInteractorAnchor::AnchorClients = {}; +std::vector GameInteractorAnchor::FairyIndexToClientId = {}; +std::string GameInteractorAnchor::clientVersion = "Anchor Build 9"; +std::string GameInteractorAnchor::seed = "00000"; +std::vector> receivedItems = {}; +std::vector anchorMessages = {}; +uint32_t notificationId = 0; + +void Anchor_DisplayMessage(AnchorMessage message = {}) { + message.id = notificationId++; + anchorMessages.push_back(message); +} + +void Anchor_SendClientData() { + GameInteractorAnchor::seed = std::accumulate(std::begin(gSaveContext.seedIcons), std::end(gSaveContext.seedIcons), std::string(), [](std::string a, int b) { + return a + std::to_string(b); + }); + + nlohmann::json payload; + payload["data"]["name"] = CVarGetString("gRemote.AnchorName", ""); + payload["data"]["color"] = CVarGetColor24("gRemote.AnchorColor", { 100, 255, 100 }); + payload["data"]["clientVersion"] = GameInteractorAnchor::clientVersion; + payload["data"]["seed"] = GameInteractorAnchor::seed; + payload["type"] = "UPDATE_CLIENT_DATA"; + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void GameInteractorAnchor::Enable() { + if (isEnabled) { + return; + } + + isEnabled = true; + GameInteractor::Instance->EnableRemoteInteractor(); + GameInteractor::Instance->RegisterRemoteJsonHandler([&](nlohmann::json payload) { + HandleRemoteJson(payload); + }); + GameInteractor::Instance->RegisterRemoteConnectedHandler([&]() { + Anchor_DisplayMessage({ .message = "Connected to Anchor" }); + Anchor_SendClientData(); + }); + GameInteractor::Instance->RegisterRemoteDisconnectedHandler([&]() { + Anchor_DisplayMessage({ .message = "Disconnected from Anchor" }); + }); +} + +void GameInteractorAnchor::Disable() { + if (!isEnabled) { + return; + } + + isEnabled = false; + GameInteractor::Instance->DisableRemoteInteractor(); + + GameInteractorAnchor::AnchorClients.clear(); + if (GameInteractor::IsSaveLoaded()) { + Anchor_SpawnClientFairies(); + } +} + +void GameInteractorAnchor::TransmitJsonToRemote(nlohmann::json payload) { + payload["roomId"] = CVarGetString("gRemote.AnchorRoomId", ""); + if (!payload.contains("quiet")) { + SPDLOG_INFO("Sending payload:\n{}", payload.dump()); + } + GameInteractor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_ParseSaveStateFromRemote(nlohmann::json payload); +void Anchor_PushSaveStateToRemote(); + +void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) { + if (!payload.contains("type")) { + return; + } + + if (!payload.contains("quiet")) { + SPDLOG_INFO("Received payload:\n{}", payload.dump()); + } + + if (payload["type"] == "GIVE_ITEM") { + auto effect = new GameInteractionEffect::GiveItem(); + effect->parameters[0] = payload["modId"].get(); + effect->parameters[1] = payload["getItemId"].get(); + CVarSetInteger("gFromGI", 1); + receivedItems.push_back({ payload["modId"].get(), payload["getItemId"].get() }); + if (effect->Apply() == Possible) { + GetItemEntry getItemEntry = ItemTableManager::Instance->RetrieveItemEntry(effect->parameters[0], effect->parameters[1]); + + AnchorClient anchorClient = GameInteractorAnchor::AnchorClients[payload["clientId"].get()]; + if (getItemEntry.getItemCategory != ITEM_CATEGORY_JUNK) { + if (getItemEntry.modIndex == MOD_NONE) { + Anchor_DisplayMessage({ + .itemIcon = SohUtils::GetIconNameFromItemID(getItemEntry.itemId), + .prefix = SohUtils::GetItemName(getItemEntry.itemId), + .message = "from", + .suffix = anchorClient.name + }); + } else if (getItemEntry.modIndex == MOD_RANDOMIZER) { + Anchor_DisplayMessage({ + .itemIcon = SohUtils::GetIconNameFromItemID(SohUtils::GetItemIdIconFromRandomizerGet(getItemEntry.getItemId)), + .prefix = SohUtils::GetRandomizerItemName(getItemEntry.getItemId), + .message = "from", + .suffix = anchorClient.name + }); + } + } + } + CVarClear("gFromGI"); + } + if (payload["type"] == "SET_SCENE_FLAG") { + auto effect = new GameInteractionEffect::SetSceneFlag(); + effect->parameters[0] = payload["sceneNum"].get(); + effect->parameters[1] = payload["flagType"].get(); + effect->parameters[2] = payload["flag"].get(); + effect->Apply(); + } + if (payload["type"] == "SET_FLAG") { + auto effect = new GameInteractionEffect::SetFlag(); + effect->parameters[0] = payload["flagType"].get(); + effect->parameters[1] = payload["flag"].get(); + effect->Apply(); + + // If mweep flag replace ruto's letter + if ( + payload["flagType"].get() == FLAG_EVENT_CHECK_INF && + payload["flag"].get() == EVENTCHKINF_KING_ZORA_MOVED && + Inventory_HasSpecificBottle(ITEM_LETTER_RUTO) + ) { + Inventory_ReplaceItem(gPlayState, ITEM_LETTER_RUTO, ITEM_BOTTLE); + } + } + if (payload["type"] == "UNSET_SCENE_FLAG") { + auto effect = new GameInteractionEffect::UnsetSceneFlag(); + effect->parameters[0] = payload["sceneNum"].get(); + effect->parameters[1] = payload["flagType"].get(); + effect->parameters[2] = payload["flag"].get(); + effect->Apply(); + } + if (payload["type"] == "UNSET_FLAG") { + auto effect = new GameInteractionEffect::UnsetFlag(); + effect->parameters[0] = payload["flagType"].get(); + effect->parameters[1] = payload["flag"].get(); + effect->Apply(); + } + if (payload["type"] == "CLIENT_UPDATE") { + uint32_t clientId = payload["clientId"].get(); + + if (GameInteractorAnchor::AnchorClients.contains(clientId)) { + GameInteractorAnchor::AnchorClients[clientId].scene = payload["sceneNum"].get(); + GameInteractorAnchor::AnchorClients[clientId].roomIndex = payload.contains("roomIndex") ? payload.at("roomIndex").get() : 0; + GameInteractorAnchor::AnchorClients[clientId].entranceIndex = payload.contains("entranceIndex") ? payload.at("entranceIndex").get() : 0; + GameInteractorAnchor::AnchorClients[clientId].posRot = payload["posRot"].get(); + } + } + if (payload["type"] == "PUSH_SAVE_STATE" && GameInteractor::IsSaveLoaded()) { + Anchor_ParseSaveStateFromRemote(payload); + } + if (payload["type"] == "REQUEST_SAVE_STATE" && GameInteractor::IsSaveLoaded()) { + Anchor_PushSaveStateToRemote(); + } + if (payload["type"] == "ALL_CLIENT_DATA") { + std::vector newClients = payload["clients"].get>(); + + // add new clients + for (auto& client : newClients) { + if (!GameInteractorAnchor::AnchorClients.contains(client.clientId)) { + GameInteractorAnchor::AnchorClients[client.clientId] = { + client.clientId, + client.clientVersion, + client.name, + client.color, + client.seed, + SCENE_ID_MAX, + 0, + 0, + { -9999, -9999, -9999, 0, 0, 0 } + }; + Anchor_DisplayMessage({ + .prefix = client.name, + .prefixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f), + .message = "connected" + }); + } + } + + // remove clients that are no longer in the list + std::vector clientsToRemove; + for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) { + if (std::find_if(newClients.begin(), newClients.end(), [clientId = clientId](AnchorClient& c) { return c.clientId == clientId; }) == newClients.end()) { + clientsToRemove.push_back(clientId); + } + } + for (auto& clientId : clientsToRemove) { + Anchor_DisplayMessage({ + .prefix = GameInteractorAnchor::AnchorClients[clientId].name, + .prefixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f), + .message = "connected" + }); + GameInteractorAnchor::AnchorClients.erase(clientId); + } + + if (GameInteractor::IsSaveLoaded()) { + Anchor_SpawnClientFairies(); + } + } + if (payload["type"] == "UPDATE_CLIENT_DATA") { + uint32_t clientId = payload["clientId"].get(); + if (GameInteractorAnchor::AnchorClients.contains(clientId)) { + AnchorClient client = payload["data"].get(); + GameInteractorAnchor::AnchorClients[clientId].clientVersion = client.clientVersion; + GameInteractorAnchor::AnchorClients[clientId].name = client.name; + GameInteractorAnchor::AnchorClients[clientId].color = client.color; + GameInteractorAnchor::AnchorClients[clientId].seed = client.seed; + } + } + if (payload["type"] == "SKIP_LOCATION" && GameInteractor::IsSaveLoaded()) { + gSaveContext.sohStats.locationsSkipped[payload["locationIndex"].get()] = payload["skipped"].get(); + } + if (payload["type"] == "UPDATE_BEANS_BOUGHT" && GameInteractor::IsSaveLoaded()) { + BEANS_BOUGHT = payload["amount"].get(); + } + if (payload["type"] == "UPDATE_BEANS_COUNT" && GameInteractor::IsSaveLoaded()) { + AMMO(ITEM_BEAN) = payload["amount"].get(); + } + if (payload["type"] == "CONSUME_ADULT_TRADE_ITEM" && GameInteractor::IsSaveLoaded()) { + uint8_t itemId = payload["itemId"].get(); + gSaveContext.adultTradeItems &= ~ADULT_TRADE_FLAG(itemId); + Inventory_ReplaceItem(gPlayState, itemId, Randomizer_GetNextAdultTradeItem()); + } + if (payload["type"] == "UPDATE_KEY_COUNT" && GameInteractor::IsSaveLoaded()) { + gSaveContext.inventory.dungeonKeys[payload["sceneNum"].get()] = payload["amount"].get(); + } + if (payload["type"] == "SERVER_MESSAGE") { + Anchor_DisplayMessage({ + .prefix = "Server:", + .prefixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f), + .message = payload["message"].get(), + }); + } + if (payload["type"] == "DISABLE_ANCHOR") { + GameInteractor::Instance->isRemoteInteractorEnabled = false; + GameInteractorAnchor::Instance->isEnabled = false; + } +} + +void Anchor_PushSaveStateToRemote() { + json payload = gSaveContext; + payload["type"] = "PUSH_SAVE_STATE"; + // manually update current scene flags + payload["sceneFlags"][gPlayState->sceneNum]["chest"] = gPlayState->actorCtx.flags.chest; + payload["sceneFlags"][gPlayState->sceneNum]["swch"] = gPlayState->actorCtx.flags.swch; + payload["sceneFlags"][gPlayState->sceneNum]["clear"] = gPlayState->actorCtx.flags.clear; + payload["sceneFlags"][gPlayState->sceneNum]["collect"] = gPlayState->actorCtx.flags.collect; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_RequestSaveStateFromRemote() { + nlohmann::json payload; + payload["type"] = "REQUEST_SAVE_STATE"; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_ParseSaveStateFromRemote(nlohmann::json payload) { + SaveContext loadedData = payload.get(); + + gSaveContext.healthCapacity = loadedData.healthCapacity; + gSaveContext.magicLevel = loadedData.magicLevel; + gSaveContext.magicCapacity = gSaveContext.magic = loadedData.magicCapacity; + gSaveContext.isMagicAcquired = loadedData.isMagicAcquired; + gSaveContext.isDoubleMagicAcquired = loadedData.isDoubleMagicAcquired; + gSaveContext.isDoubleDefenseAcquired = loadedData.isDoubleDefenseAcquired; + gSaveContext.bgsFlag = loadedData.bgsFlag; + gSaveContext.swordHealth = loadedData.swordHealth; + // TODO: Packet to live update this + gSaveContext.adultTradeItems = loadedData.adultTradeItems; + + for (int i = 0; i < 124; i++) { + gSaveContext.sceneFlags[i] = loadedData.sceneFlags[i]; + if (gPlayState->sceneNum == i) { + gPlayState->actorCtx.flags.chest = loadedData.sceneFlags[i].chest; + gPlayState->actorCtx.flags.swch = loadedData.sceneFlags[i].swch; + gPlayState->actorCtx.flags.clear = loadedData.sceneFlags[i].clear; + gPlayState->actorCtx.flags.collect = loadedData.sceneFlags[i].collect; + } + } + + for (int i = 0; i < 14; i++) { + gSaveContext.eventChkInf[i] = loadedData.eventChkInf[i]; + } + + for (int i = 0; i < 4; i++) { + gSaveContext.itemGetInf[i] = loadedData.itemGetInf[i]; + } + + // Skip last row of infTable, don't want to sync swordless flag + for (int i = 0; i < 29; i++) { + gSaveContext.infTable[i] = loadedData.infTable[i]; + } + + for (int i = 0; i < 9; i++) { + gSaveContext.randomizerInf[i] = loadedData.randomizerInf[i]; + } + + for (int i = 0; i < 6; i++) { + gSaveContext.gsFlags[i] = loadedData.gsFlags[i]; + } + + for (int i = 0; i < 746; i++) { + if (!gSaveContext.sohStats.locationsSkipped[i]) { + gSaveContext.sohStats.locationsSkipped[i] = loadedData.sohStats.locationsSkipped[i]; + } + } + + // Restore master sword state + u8 hasMasterSword = CHECK_OWNED_EQUIP(EQUIP_SWORD, 1); + if (hasMasterSword) { + loadedData.inventory.equipment |= 0x2; + } else { + loadedData.inventory.equipment &= ~0x2; + } + + // Restore bottle contents (unless it's ruto's letter) + for (int i = 0; i < 4; i++) { + if (gSaveContext.inventory.items[SLOT_BOTTLE_1 + i] != ITEM_NONE && gSaveContext.inventory.items[SLOT_BOTTLE_1 + i] != ITEM_LETTER_RUTO) { + loadedData.inventory.items[SLOT_BOTTLE_1 + i] = gSaveContext.inventory.items[SLOT_BOTTLE_1 + i]; + } + } + + // Restore ammo if it's non-zero, unless it's beans + for (int i = 0; i < ARRAY_COUNT(gSaveContext.inventory.ammo); i++) { + if (gSaveContext.inventory.ammo[i] != 0 && i != SLOT(ITEM_BEAN) && i != SLOT(ITEM_BEAN + 1)) { + loadedData.inventory.ammo[i] = gSaveContext.inventory.ammo[i]; + } + } + + gSaveContext.inventory = loadedData.inventory; + Anchor_DisplayMessage({ .message = "State loaded from remote!" }); +}; + +uint8_t Anchor_GetClientScene(uint32_t fairyIndex) { + uint32_t clientId = GameInteractorAnchor::FairyIndexToClientId[fairyIndex]; + if (GameInteractorAnchor::AnchorClients.find(clientId) == GameInteractorAnchor::AnchorClients.end()) { + return SCENE_ID_MAX; + } + + return GameInteractorAnchor::AnchorClients[clientId].scene; +} + +PosRot Anchor_GetClientPosition(uint32_t fairyIndex) { + uint32_t clientId = GameInteractorAnchor::FairyIndexToClientId[fairyIndex]; + if (GameInteractorAnchor::AnchorClients.find(clientId) == GameInteractorAnchor::AnchorClients.end()) { + return {-9999.0, -9999.0, -9999.0, 0, 0, 0}; + } + + return GameInteractorAnchor::AnchorClients[clientId].posRot; +} + +Color_RGB8 Anchor_GetClientColor(uint32_t fairyIndex) { + uint32_t clientId = GameInteractorAnchor::FairyIndexToClientId[fairyIndex]; + if (GameInteractorAnchor::AnchorClients.find(clientId) == GameInteractorAnchor::AnchorClients.end()) { + return {100, 255, 100}; + } + + return GameInteractorAnchor::AnchorClients[clientId].color; +} + +void Anchor_SpawnClientFairies() { + if (gPlayState == NULL) return; + Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ITEMACTION].head; + while (actor != NULL) { + if (gEnPartnerId == actor->id) { + Actor_Kill(actor); + } + actor = actor->next; + } + + GameInteractorAnchor::FairyIndexToClientId.clear(); + + uint32_t i = 0; + for (auto [clientId, client] : GameInteractorAnchor::AnchorClients) { + GameInteractorAnchor::FairyIndexToClientId.push_back(clientId); + auto fairy = Actor_Spawn(&gPlayState->actorCtx, gPlayState, gEnPartnerId, -9999.0, -9999.0, -9999.0, 0, 0, 0, 3 + i, false); + NameTag_RegisterForActor(fairy, client.name.c_str()); + i++; + } +} + +void Anchor_RegisterHooks() { + GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || gSaveContext.fileNum > 2) return; + + Anchor_SendClientData(); + Anchor_RequestSaveStateFromRemote(); + }); + GameInteractor::Instance->RegisterGameHook([](GetItemEntry itemEntry) { + if (itemEntry.modIndex == MOD_NONE && (itemEntry.itemId == ITEM_KEY_SMALL || itemEntry.itemId == ITEM_KEY_BOSS || itemEntry.itemId == ITEM_SWORD_MASTER)) { + return; + } + + // If the item exists in receivedItems, remove it from the list and don't emit the packet + auto it = std::find_if(receivedItems.begin(), receivedItems.end(), [itemEntry](std::pair pair) { + return pair.first == itemEntry.tableId && pair.second == itemEntry.getItemId; + }); + if (it != receivedItems.end()) { + receivedItems.erase(it); + return; + } + + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + + nlohmann::json payload; + + payload["type"] = "GIVE_ITEM"; + payload["modId"] = itemEntry.tableId; + payload["getItemId"] = itemEntry.getItemId; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum, int16_t flagType, int16_t flag) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + nlohmann::json payload; + + payload["type"] = "SET_SCENE_FLAG"; + payload["sceneNum"] = sceneNum; + payload["flagType"] = flagType; + payload["flag"] = flag; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t flagType, int16_t flag) { + if (flagType == FLAG_INF_TABLE && flag == INFTABLE_SWORDLESS) { + return; + } + + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + nlohmann::json payload; + + payload["type"] = "SET_FLAG"; + payload["flagType"] = flagType; + payload["flag"] = flag; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum, int16_t flagType, int16_t flag) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + nlohmann::json payload; + + payload["type"] = "UNSET_SCENE_FLAG"; + payload["sceneNum"] = sceneNum; + payload["flagType"] = flagType; + payload["flag"] = flag; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([](int16_t flagType, int16_t flag) { + if (flagType == FLAG_INF_TABLE && flag == INFTABLE_SWORDLESS) { + return; + } + + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + nlohmann::json payload; + + payload["type"] = "UNSET_FLAG"; + payload["flagType"] = flagType; + payload["flag"] = flag; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); + }); + GameInteractor::Instance->RegisterGameHook([]() { + static uint32_t lastPlayerCount = 0; + uint32_t currentPlayerCount = GameInteractorAnchor::AnchorClients.size(); + if (!GameInteractor::Instance->isRemoteInteractorConnected || gPlayState == NULL || !GameInteractor::Instance->IsSaveLoaded()) { + lastPlayerCount = currentPlayerCount; + return; + } + Player* player = GET_PLAYER(gPlayState); + nlohmann::json payload; + float currentPosition = player->actor.world.pos.x + player->actor.world.pos.y + player->actor.world.pos.z + player->actor.world.rot.y; + static float lastPosition = 0.0f; + + if (currentPosition == lastPosition && currentPlayerCount == lastPlayerCount) return; + + payload["type"] = "CLIENT_UPDATE"; + payload["sceneNum"] = gPlayState->sceneNum; + payload["roomIndex"] = gPlayState->roomCtx.curRoom.num; + payload["entranceIndex"] = gSaveContext.entranceIndex; + payload["posRot"] = player->actor.world; + payload["quiet"] = true; + + lastPosition = currentPosition; + lastPlayerCount = currentPlayerCount; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); + }); +} + +void Anchor_SkipLocation(uint32_t locationIndex, bool skipped) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + + nlohmann::json payload; + + payload["type"] = "SKIP_LOCATION"; + payload["locationIndex"] = locationIndex; + payload["skipped"] = skipped; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_UpdateBeansBought(uint8_t amount) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + + nlohmann::json payload; + + payload["type"] = "UPDATE_BEANS_BOUGHT"; + payload["amount"] = amount; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_UpdateBeansCount(uint8_t amount) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + + nlohmann::json payload; + + payload["type"] = "UPDATE_BEANS_COUNT"; + payload["amount"] = amount; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_ConsumeAdultTradeItem(uint8_t itemId) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + + nlohmann::json payload; + + payload["type"] = "CONSUME_ADULT_TRADE_ITEM"; + payload["itemId"] = itemId; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void Anchor_UpdateKeyCount(int16_t sceneNum, int8_t amount) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; + + nlohmann::json payload; + + payload["type"] = "UPDATE_KEY_COUNT"; + payload["sceneNum"] = sceneNum; + payload["amount"] = amount; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + +void AnchorPlayerLocationWindow::DrawElement() { + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::Begin("AnchorPlayerLocationWindow", &mIsVisible, + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoScrollbar + ); + + ImGui::Text("%s", CVarGetString("gRemote.AnchorName", "")); + if (gPlayState != NULL) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1), "%s", SohUtils::GetSceneName(gPlayState->sceneNum).c_str()); + } + for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) { + ImGui::PushID(clientId); + ImGui::Text("%s", client.name.c_str()); + if (client.scene < SCENE_ID_MAX) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1), "%s", SohUtils::GetSceneName(client.scene).c_str()); + if (gPlayState != NULL && client.scene != SCENE_KAKUSIANA && client.scene != SCENE_ID_MAX) { + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + if (ImGui::Button(ICON_FA_CHEVRON_RIGHT, ImVec2(ImGui::GetFontSize() * 1.0f, ImGui::GetFontSize() * 1.0f))) { + Play_SetRespawnData(gPlayState, RESPAWN_MODE_DOWN, client.entranceIndex, client.roomIndex, 0xDFF, &client.posRot.pos, client.posRot.rot.y); + Play_TriggerVoidOut(gPlayState); + } + ImGui::PopStyleVar(); + } + } + ImGui::PopID(); + } + + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); +} + +void AnchorLogWindow::DrawElement() { + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f); + ImGui::Begin("AnchorLogWindow", &mIsVisible, + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoScrollbar + ); + + // Options to stack notifications on top or bottom, and left or right + if (ImGui::Button(CVarGetInteger("gRemote.AnchorLogWindowX", 0) ? ICON_FA_CHEVRON_RIGHT : ICON_FA_CHEVRON_LEFT, ImVec2(20, 20))) { + CVarSetInteger("gRemote.AnchorLogWindowX", !CVarGetInteger("gRemote.AnchorLogWindowX", 0)); + } + ImGui::SameLine(); + if (ImGui::Button(CVarGetInteger("gRemote.AnchorLogWindowY", 0) ? ICON_FA_CHEVRON_DOWN : ICON_FA_CHEVRON_UP, ImVec2(20, 20))) { + CVarSetInteger("gRemote.AnchorLogWindowY", !CVarGetInteger("gRemote.AnchorLogWindowY", 0)); + } + + // Store x/y position of window + ImVec2 anchorPos = ImGui::GetWindowPos(); + ImVec2 anchorSize = ImGui::GetWindowSize(); + + for (int index = 0; index < anchorMessages.size(); ++index) { + auto& message = anchorMessages[index]; + int inverseIndex = -ABS(index - (anchorMessages.size() - 1)); + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + if (message.remainingTime < 4.0f) { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, (message.remainingTime - 1) / 3.0f); + } else { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); + } + ImGui::Begin(("anchorLog" + std::to_string(message.id)).c_str(), nullptr, + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoDocking | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoInputs | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar + ); + ImGui::SetWindowPos(ImVec2( + // X position should take into account both the alignment and the width of the message window + anchorPos.x + (CVarGetInteger("gRemote.AnchorLogWindowX", 0) ? 0 : -(ImGui::GetWindowSize().x - anchorSize.x)), + // Y Position should take into account the stack direction and index of the message + anchorPos.y + (CVarGetInteger("gRemote.AnchorLogWindowY", 0) ? (anchorSize.y + (ImGui::GetWindowSize().y * inverseIndex)) : -(ImGui::GetWindowSize().y * (inverseIndex + 1))) + )); + ImGui::SetWindowFontScale(1.8f); + + if (message.itemIcon != nullptr) { + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(message.itemIcon), ImVec2(24, 24)); + ImGui::SameLine(); + } + if (!message.prefix.empty()) { + ImGui::TextColored(message.prefixColor, "%s", message.prefix.c_str()); + ImGui::SameLine(); + } + ImGui::TextColored(message.messageColor, "%s", message.message.c_str()); + if (!message.suffix.empty()) { + ImGui::SameLine(); + ImGui::TextColored(message.suffixColor, "%s", message.suffix.c_str()); + } + ImGui::End(); + ImGui::PopStyleVar(); + + // decrement remainingTime + message.remainingTime -= ImGui::GetIO().DeltaTime; + + // remove message if it has expired + if (message.remainingTime <= 0) { + anchorMessages.erase(anchorMessages.begin() + index); + --index; + } + } + + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); +} + +#endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h new file mode 100644 index 00000000000..a2f040064dd --- /dev/null +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h @@ -0,0 +1,92 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#ifdef __cplusplus +#include + +#include "z64item.h" +#include "z64actor.h" +#include "./GameInteractor.h" + +typedef struct { + uint32_t clientId; + std::string clientVersion; + std::string name; + Color_RGB8 color; + std::string seed; + uint8_t scene; + uint8_t roomIndex; + uint32_t entranceIndex; + PosRot posRot; +} AnchorClient; + +class GameInteractorAnchor { + private: + bool isEnabled; + + void HandleRemoteJson(nlohmann::json payload); + public: + static GameInteractorAnchor* Instance; + static std::map AnchorClients; + static std::vector FairyIndexToClientId; + static std::string clientVersion; + static std::string seed; + + void Enable(); + void Disable(); + + void TransmitJsonToRemote(nlohmann::json payload); +}; + +class AnchorPlayerLocationWindow : public LUS::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override {}; + void DrawElement() override; + void UpdateElement() override {}; +}; + +struct AnchorMessage { + uint32_t id = 0; + const char* itemIcon = nullptr; + std::string prefix = ""; + ImVec4 prefixColor = ImVec4(0.5f, 0.5f, 1.0f, 1.0f); + std::string message = ""; + ImVec4 messageColor = ImVec4(0.7f, 0.7f, 0.7f, 1.0f); + std::string suffix = ""; + ImVec4 suffixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f); + float remainingTime = 10.0f; +}; + +class AnchorLogWindow : public LUS::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override {}; + void DrawElement() override; + void UpdateElement() override {}; +}; + +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void Anchor_RegisterHooks(); +void Anchor_PushSaveStateToRemote(); +void Anchor_RequestSaveStateFromRemote(); +uint8_t Anchor_GetClientScene(uint32_t fairyIndex); +PosRot Anchor_GetClientPosition(uint32_t fairyIndex); +Color_RGB8 Anchor_GetClientColor(uint32_t fairyIndex); +void Anchor_SpawnClientFairies(); +void Anchor_SkipLocation(uint32_t locationIndex, bool skipped); +void Anchor_UpdateBeansBought(uint8_t amount); +void Anchor_UpdateBeansCount(uint8_t amount); +void Anchor_ConsumeAdultTradeItem(uint8_t itemId); +void Anchor_UpdateKeyCount(int16_t sceneNum, int8_t amount); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp index 982a2f96f35..ce1f8b5b902 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp @@ -2,6 +2,7 @@ #include #include "soh/Enhancements/cosmetics/CosmeticsEditor.h" #include "soh/Enhancements/randomizer/3drando/random.hpp" +#include #include #include "soh/Enhancements/debugger/colViewer.h" @@ -122,6 +123,23 @@ void GameInteractor::RawAction::ElectrocutePlayer() { func_80837C0C(gPlayState, player, 4, 0, 0, 0, 0); } +void GameInteractor::RawAction::GiveItem(uint16_t modId, uint16_t itemId) { + GetItemEntry getItemEntry = ItemTableManager::Instance->RetrieveItemEntry(modId, itemId); + Player* player = GET_PLAYER(gPlayState); + if (getItemEntry.modIndex == MOD_NONE) { + if (getItemEntry.getItemId == GI_SWORD_BGS) { + gSaveContext.bgsFlag = true; + } + Item_Give(gPlayState, getItemEntry.itemId); + } else if (getItemEntry.modIndex == MOD_RANDOMIZER) { + if (getItemEntry.getItemId == RG_ICE_TRAP) { + gSaveContext.pendingIceTrapCount++; + } else { + Randomizer_Item_Give(gPlayState, getItemEntry); + } + } +}; + void GameInteractor::RawAction::KnockbackPlayer(float strength) { Player* player = GET_PLAYER(gPlayState); func_8002F71C(gPlayState, &player->actor, strength * 5, player->actor.world.rot.y + 0x8000, strength * 5); diff --git a/soh/soh/Enhancements/gameplaystats.cpp b/soh/soh/Enhancements/gameplaystats.cpp index 93482ffa5d5..4fa272319a3 100644 --- a/soh/soh/Enhancements/gameplaystats.cpp +++ b/soh/soh/Enhancements/gameplaystats.cpp @@ -13,6 +13,9 @@ extern "C" { #include #include #include "soh/Enhancements/enhancementTypes.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif extern "C" { #include @@ -443,7 +446,11 @@ void DrawGameplayStatsHeader() { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, { 4.0f, 4.0f }); ImGui::BeginTable("gameplayStatsHeader", 1, ImGuiTableFlags_BordersOuter); ImGui::TableSetupColumn("stat", ImGuiTableColumnFlags_WidthStretch); +#ifdef ENABLE_REMOTE_CONTROL + GameplayStatsRow("Build Version:", GameInteractorAnchor::clientVersion); +#else GameplayStatsRow("Build Version:", (char*) gBuildVersion); +#endif if (gSaveContext.sohStats.rtaTiming) { GameplayStatsRow("Total Time (RTA):", formatTimestampGameplayStat(GAMEPLAYSTAT_TOTAL_TIME), gSaveContext.sohStats.gameComplete ? COLOR_GREEN : COLOR_WHITE); } else { diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 078fbc2d007..0689a491fd9 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -8,6 +8,9 @@ #include "soh/Enhancements/randomizer/3drando/random.hpp" #include "soh/Enhancements/cosmetics/authenticGfxPatches.h" #include "soh/Enhancements/nametag.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif extern "C" { #include @@ -773,4 +776,7 @@ void InitMods() { RegisterMirrorModeHandler(); RegisterAltTrapTypes(); NameTag_RegisterHooks(); + #ifdef ENABLE_REMOTE_CONTROL + Anchor_RegisterHooks(); + #endif } diff --git a/soh/soh/Enhancements/randomizer/adult_trade_shuffle.c b/soh/soh/Enhancements/randomizer/adult_trade_shuffle.c index c1acc100b82..f5eb621ca9a 100644 --- a/soh/soh/Enhancements/randomizer/adult_trade_shuffle.c +++ b/soh/soh/Enhancements/randomizer/adult_trade_shuffle.c @@ -2,10 +2,16 @@ #include "functions.h" #include "variables.h" #include "macros.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif void Randomizer_ConsumeAdultTradeItem(PlayState* play, u8 itemId) { gSaveContext.adultTradeItems &= ~ADULT_TRADE_FLAG(itemId); Inventory_ReplaceItem(play, itemId, Randomizer_GetNextAdultTradeItem()); +#ifdef ENABLE_REMOTE_CONTROL + Anchor_ConsumeAdultTradeItem(itemId); +#endif } u8 Randomizer_GetNextAdultTradeItem() { diff --git a/soh/soh/Enhancements/randomizer/adult_trade_shuffle.h b/soh/soh/Enhancements/randomizer/adult_trade_shuffle.h index 13d905026c9..ec21b61c112 100644 --- a/soh/soh/Enhancements/randomizer/adult_trade_shuffle.h +++ b/soh/soh/Enhancements/randomizer/adult_trade_shuffle.h @@ -6,8 +6,16 @@ #define ADULT_TRADE_FLAG(itemId) (1 << (itemId - ITEM_POCKET_EGG)) #define PLAYER_HAS_SHUFFLED_ADULT_TRADE_ITEM(itemID) (gSaveContext.adultTradeItems & ADULT_TRADE_FLAG(itemID)) +#ifdef __cplusplus +extern "C" { +#endif + void Randomizer_ConsumeAdultTradeItem(PlayState* play, u8 itemId); u8 Randomizer_GetNextAdultTradeItem(); u8 Randomizer_GetPrevAdultTradeItem(); +#ifdef __cplusplus +} +#endif + #endif diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index a9da1bf9e2a..464eb89f7fe 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -9,6 +9,9 @@ #include "3drando/item_location.hpp" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "randomizerTypes.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif extern "C" { @@ -812,9 +815,15 @@ void DrawLocation(RandomizerCheckObject rcObj, RandomizerCheckShow* thisCheckSta if (skipped) { gSaveContext.sohStats.locationsSkipped[rcObj.rc] = 0; *thisCheckStatus = RCSHOW_UNCHECKED; +#ifdef ENABLE_REMOTE_CONTROL + Anchor_SkipLocation(rcObj.rc, false); +#endif } else { gSaveContext.sohStats.locationsSkipped[rcObj.rc] = 1; *thisCheckStatus = RCSHOW_SKIPPED; +#ifdef ENABLE_REMOTE_CONTROL + Anchor_SkipLocation(rcObj.rc, true); +#endif } } } else { diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp index d70ee26950b..17e2414e3d1 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker.cpp @@ -859,7 +859,7 @@ void EntranceTrackerWindow::DrawElement() { (override->reverseIndex == lastEntranceIndex && OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_DECOUPLED_ENTRANCES) == RO_GENERIC_OFF)) && CVarGetInteger("gEntranceTrackerHighlightPrevious", 0)) { color = COLOR_ORANGE; - } else if (LinkIsInArea(original) != -1) { + } else if (LinkIsInArea(destToggle ? override : original) != -1) { if (CVarGetInteger("gEntranceTrackerHighlightAvailable", 0)) { color = COLOR_GREEN; } @@ -877,16 +877,30 @@ void EntranceTrackerWindow::DrawElement() { // Use a non-breaking space to keep the arrow from wrapping to a newline by itself auto nbsp = u8"\u00A0"; if (original->srcGroup != ENTRANCE_GROUP_ONE_WAY) { - ImGui::TextWrapped("%s to %s%s->", origSrcName, origDstName, nbsp); + if (!destToggle) ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY); + ImGui::TextWrapped("%s", origSrcName); + if (!destToggle) ImGui::PopStyleColor(); + ImGui::SameLine(); + if (destToggle) ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY); + ImGui::TextWrapped("%s %s%s->", destToggle ? "from" : "to", origDstName, nbsp); + if (destToggle) ImGui::PopStyleColor(); } else { ImGui::TextWrapped("%s%s->", origSrcName, nbsp); } // Indent the destination - ImGui::SetCursorPosX(ImGui::GetCursorPosX() * 2); + // ImGui::SetCursorPosX(ImGui::GetCursorPosX() * 2); if (!showOverride || (showOverride && (!override->oneExit && override->srcGroup != ENTRANCE_GROUP_ONE_WAY))) { - ImGui::TextWrapped("%s from %s", rplcDstName, rplcSrcName); + ImGui::SameLine(); + if (destToggle) ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY); + ImGui::TextWrapped("%s", rplcDstName); + if (destToggle) ImGui::PopStyleColor(); + ImGui::SameLine(); + if (!destToggle) ImGui::PushStyleColor(ImGuiCol_Text, COLOR_GRAY); + ImGui::TextWrapped("%s %s", destToggle ? "to" : "from", rplcSrcName); + if (!destToggle) ImGui::PopStyleColor(); } else { + ImGui::SameLine(); ImGui::TextWrapped("%s", rplcDstName); } diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 94fb11da71a..e9b9296109c 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -78,8 +78,10 @@ #ifdef ENABLE_REMOTE_CONTROL #include "Enhancements/crowd-control/CrowdControl.h" #include "Enhancements/game-interactor/GameInteractor_BuiltIn.h" +#include "Enhancements/game-interactor/GameInteractor_Anchor.h" CrowdControl* CrowdControl::Instance; GameInteractorBuiltIn* GameInteractorBuiltIn::Instance; +GameInteractorAnchor* GameInteractorAnchor::Instance; #endif #include "Enhancements/mods.h" @@ -801,6 +803,7 @@ extern "C" void InitOTR() { #ifdef ENABLE_REMOTE_CONTROL CrowdControl::Instance = new CrowdControl(); GameInteractorBuiltIn::Instance = new GameInteractorBuiltIn(); + GameInteractorAnchor::Instance = new GameInteractorAnchor(); #endif clearMtx = (uintptr_t)&gMtxClear; @@ -832,6 +835,9 @@ extern "C" void InitOTR() { case GI_SCHEME_CROWD_CONTROL: CrowdControl::Instance->Enable(); break; + case GI_SCHEME_ANCHOR: + GameInteractorAnchor::Instance->Enable(); + break; } } #endif @@ -857,6 +863,9 @@ extern "C" void DeinitOTR() { case GI_SCHEME_CROWD_CONTROL: CrowdControl::Instance->Disable(); break; + case GI_SCHEME_ANCHOR: + GameInteractorAnchor::Instance->Disable(); + break; } } SDLNet_Quit(); diff --git a/soh/soh/SohGui.cpp b/soh/soh/SohGui.cpp index 77fe2f0a6dc..0979a4a3739 100644 --- a/soh/soh/SohGui.cpp +++ b/soh/soh/SohGui.cpp @@ -34,6 +34,7 @@ #ifdef ENABLE_REMOTE_CONTROL #include "Enhancements/crowd-control/CrowdControl.h" #include "Enhancements/game-interactor/GameInteractor_BuiltIn.h" +#include "Enhancements/game-interactor/GameInteractor_Anchor.h" #endif #include "Enhancements/game-interactor/GameInteractor.h" @@ -126,6 +127,10 @@ namespace SohGui { std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; std::shared_ptr mRandomizerSettingsWindow; +#ifdef ENABLE_REMOTE_CONTROL + std::shared_ptr mAnchorPlayerLocationWindow; + std::shared_ptr mAnchorLogWindow; +#endif void SetupGuiElements() { auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); @@ -184,6 +189,12 @@ namespace SohGui { gui->AddGuiWindow(mItemTrackerSettingsWindow); mRandomizerSettingsWindow = std::make_shared("gRandomizerSettingsEnabled", "Randomizer Settings"); gui->AddGuiWindow(mRandomizerSettingsWindow); +#ifdef ENABLE_REMOTE_CONTROL + mAnchorPlayerLocationWindow = std::make_shared("gRemote.AnchorPlayerLocationWindow", "Anchor Player Location Window"); + gui->AddGuiWindow(mAnchorPlayerLocationWindow); + mAnchorLogWindow = std::make_shared("gRemote.AnchorLogWindow", "Anchor Log"); + gui->AddGuiWindow(mAnchorLogWindow); +#endif } void Destroy() { @@ -205,5 +216,9 @@ namespace SohGui { mStatsWindow = nullptr; mConsoleWindow = nullptr; mSohMenuBar = nullptr; +#ifdef ENABLE_REMOTE_CONTROL + mAnchorPlayerLocationWindow = nullptr; + mAnchorLogWindow = nullptr; +#endif } } diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 746e44efef7..2be047dc7ec 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -14,6 +14,7 @@ #ifdef ENABLE_REMOTE_CONTROL #include "Enhancements/crowd-control/CrowdControl.h" #include "Enhancements/game-interactor/GameInteractor_BuiltIn.h" +#include "Enhancements/game-interactor/GameInteractor_Anchor.h" #endif @@ -1295,6 +1296,10 @@ extern std::shared_ptr mSaveEditorWindow; extern std::shared_ptr mColViewerWindow; extern std::shared_ptr mActorViewerWindow; extern std::shared_ptr mDLViewerWindow; +#ifdef ENABLE_REMOTE_CONTROL +extern std::shared_ptr mAnchorPlayerLocationWindow; +extern std::shared_ptr mAnchorLogWindow; +#endif void DrawDeveloperToolsMenu() { if (ImGui::BeginMenu("Developer Tools")) @@ -1381,9 +1386,17 @@ void DrawRemoteControlMenu() { if (ImGui::BeginMenu("Network")) { static std::string ip = CVarGetString("gRemote.IP", "127.0.0.1"); static uint16_t port = CVarGetInteger("gRemote.Port", 43384); - bool isFormValid = !isStringEmpty(CVarGetString("gRemote.IP", "127.0.0.1")) && port > 1024 && port < 65535; - - const char* remoteOptions[2] = { "Built-in", "Crowd Control"}; + static std::string AnchorName = CVarGetString("gRemote.AnchorName", ""); + static std::string anchorRoomId = CVarGetString("gRemote.AnchorRoomId", ""); + bool isFormValid = !isStringEmpty(CVarGetString("gRemote.IP", "127.0.0.1")) && port > 1024 && port < 65535 && ( + CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN) != GI_SCHEME_ANCHOR || + ( + !isStringEmpty(CVarGetString("gRemote.AnchorName", "")) && + !isStringEmpty(CVarGetString("gRemote.AnchorRoomId", "")) + ) + ); + + const char* remoteOptions[3] = { "Built-in", "Crowd Control", "Anchor" }; ImGui::BeginDisabled(GameInteractor::Instance->isRemoteInteractorEnabled); ImGui::Text("Remote Interaction Scheme"); @@ -1396,10 +1409,16 @@ void DrawRemoteControlMenu() { ip = "127.0.0.1"; port = 43384; break; + case GI_SCHEME_ANCHOR: + CVarSetString("gRemote.IP", "anchor.proxysaw.dev"); + CVarSetInteger("gRemote.Port", 43384); + ip = "anchor.proxysaw.dev"; + port = 43384; + break; } LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } - + if (CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN) != GI_SCHEME_ANCHOR) { ImGui::Text("Remote IP & Port"); if (ImGui::InputText("##gRemote.IP", (char*)ip.c_str(), ip.capacity() + 1)) { CVarSetString("gRemote.IP", ip.c_str()); @@ -1412,8 +1431,31 @@ void DrawRemoteControlMenu() { CVarSetInteger("gRemote.Port", port); LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); } - ImGui::PopItemWidth(); + } else { + ImGui::Text("Fairy Color & Name"); + static Color_RGBA8 color = CVarGetColor("gRemote.AnchorColor", { 100, 255, 100, 255 }); + static ImVec4 colorVec = ImVec4(color.r / 255.0, color.g / 255.0, color.b / 255.0, 1); + if (ImGui::ColorEdit3("##gRemote.AnchorColor", (float*)&colorVec, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) { + color.r = colorVec.x * 255.0; + color.g = colorVec.y * 255.0; + color.b = colorVec.z * 255.0; + + CVarSetColor("gRemote.AnchorColor", color); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputText("##gRemote.AnchorName", (char*)AnchorName.c_str(), AnchorName.capacity() + 1)) { + CVarSetString("gRemote.AnchorName", AnchorName.c_str()); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + ImGui::Text("Room ID"); + if (ImGui::InputText("##gRemote.AnchorRoomId", (char*)anchorRoomId.c_str(), anchorRoomId.capacity() + 1)) { + CVarSetString("gRemote.AnchorRoomId", anchorRoomId.c_str()); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + } + } ImGui::EndDisabled(); ImGui::Spacing(); @@ -1431,6 +1473,15 @@ void DrawRemoteControlMenu() { case GI_SCHEME_CROWD_CONTROL: CrowdControl::Instance->Disable(); break; + case GI_SCHEME_ANCHOR: + if (CVarGetInteger("gRemote.AnchorLogWindow", 0) && mAnchorLogWindow) { + mAnchorLogWindow->ToggleVisibility(); + } + if (CVarGetInteger("gRemote.AnchorPlayerLocationWindow", 0) && mAnchorPlayerLocationWindow) { + mAnchorPlayerLocationWindow->ToggleVisibility(); + } + GameInteractorAnchor::Instance->Disable(); + break; } } else { CVarSetInteger("gRemote.Enabled", 1); @@ -1442,6 +1493,11 @@ void DrawRemoteControlMenu() { case GI_SCHEME_CROWD_CONTROL: CrowdControl::Instance->Enable(); break; + case GI_SCHEME_ANCHOR: + if (mAnchorLogWindow) mAnchorLogWindow->ToggleVisibility(); + if (mAnchorPlayerLocationWindow) mAnchorPlayerLocationWindow->ToggleVisibility(); + GameInteractorAnchor::Instance->Enable(); + break; } } } @@ -1449,13 +1505,58 @@ void DrawRemoteControlMenu() { if (GameInteractor::Instance->isRemoteInteractorEnabled) { ImGui::Spacing(); - if (GameInteractor::Instance->isRemoteInteractorConnected) { - ImGui::Text("Connected"); - } else { + if (!GameInteractor::Instance->isRemoteInteractorConnected) { ImGui::Text("Connecting..."); } } + if (GameInteractor::Instance->isRemoteInteractorConnected && CVarGetInteger("gRemote.Scheme", GI_SCHEME_BUILT_IN) == GI_SCHEME_ANCHOR) { + if (ImGui::Button("Request State", ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) { + Anchor_RequestSaveStateFromRemote(); + } + + ImGui::Text("Players in Room:"); + ImGui::Text("%s", CVarGetString("gRemote.AnchorName", "")); + for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) { + ImGui::Text("%s", client.name.c_str()); + if (client.clientVersion != GameInteractorAnchor::clientVersion) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1, 1, 0, 1), ICON_FA_EXCLAMATION_TRIANGLE); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Client version mismatch (%s)", client.clientVersion.c_str()); + ImGui::EndTooltip(); + } + } + if (client.seed != GameInteractorAnchor::seed) { + ImGui::SameLine(); + ImGui::TextColored(ImVec4(1, 0, 0, 1), ICON_FA_EXCLAMATION_TRIANGLE); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Seed mismatch (%s)", client.seed.c_str()); + ImGui::EndTooltip(); + } + } + } + + ImGui::Spacing(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + if (mAnchorPlayerLocationWindow) { + if (ImGui::Button(GetWindowButtonText("Player Location", CVarGetInteger("gRemote.AnchorPlayerLocationWindow", 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + mAnchorPlayerLocationWindow->ToggleVisibility(); + } + } + if (mAnchorLogWindow) { + if (ImGui::Button(GetWindowButtonText("Anchor Log", CVarGetInteger("gRemote.AnchorLogWindow", 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + mAnchorLogWindow->ToggleVisibility(); + } + } + ImGui::PopStyleVar(3); + } + ImGui::Dummy(ImVec2(0.0f, 0.0f)); ImGui::EndMenu(); } diff --git a/soh/soh/util.cpp b/soh/soh/util.cpp index f1a555bde50..264c7b045fc 100644 --- a/soh/soh/util.cpp +++ b/soh/soh/util.cpp @@ -1,6 +1,9 @@ #include "util.h" +#include +#include "Enhancements/randomizer/randomizerTypes.h" #include +#include std::vector sceneNames = { "Inside the Deku Tree", @@ -26,7 +29,7 @@ std::vector sceneNames = { "Phantom Ganon's Lair", "Volvagia's Lair", "Morpha's Lair", - "Twinrova's Lair & Nabooru's Mini-Boss Room", + "Twinrova's Lair", "Bongo Bongo's Lair", "Ganondorf's Lair", "Tower Collapse Exterior", @@ -75,34 +78,34 @@ std::vector sceneNames = { "Castle Hedge Maze (Day)", "Castle Hedge Maze (Night)", "Cutscene Map", - "Damp�'s Grave & Windmill", + "Dampe's Grave & Windmill", "Fishing Pond", "Castle Courtyard", "Bombchu Bowling Alley", "Ranch House & Silo", "Guard House", "Granny's Potion Shop", - "Ganon's Tower Collapse & Battle Arena", + "Ganon's Tower Collapse & Arena", "House of Skulltula", - "Spot 00 - Hyrule Field", - "Spot 01 - Kakariko Village", - "Spot 02 - Graveyard", - "Spot 03 - Zora's River", - "Spot 04 - Kokiri Forest", - "Spot 05 - Sacred Forest Meadow", - "Spot 06 - Lake Hylia", - "Spot 07 - Zora's Domain", - "Spot 08 - Zora's Fountain", - "Spot 09 - Gerudo Valley", - "Spot 10 - Lost Woods", - "Spot 11 - Desert Colossus", - "Spot 12 - Gerudo's Fortress", - "Spot 13 - Haunted Wasteland", - "Spot 15 - Hyrule Castle", - "Spot 16 - Death Mountain Trail", - "Spot 17 - Death Mountain Crater", - "Spot 18 - Goron City", - "Spot 20 - Lon Lon Ranch", + "Hyrule Field", + "Kakariko Village", + "Graveyard", + "Zora's River", + "Kokiri Forest", + "Sacred Forest Meadow", + "Lake Hylia", + "Zora's Domain", + "Zora's Fountain", + "Gerudo Valley", + "Lost Woods", + "Desert Colossus", + "Gerudo's Fortress", + "Haunted Wasteland", + "Hyrule Castle", + "Death Mountain Trail", + "Death Mountain Crater", + "Goron City", + "Lon Lon Ranch", "Ganon's Castle Exterior", "Jungle Gym", "Ganondorf Test Room", @@ -499,6 +502,338 @@ std::vector questItemNames = { "Heart Piece", }; +#define ITEM_ICON(id) \ + { id, #id } + +// Maps items ids to info for use in ImGui +std::map itemIcons = { + ITEM_ICON(ITEM_STICK), + ITEM_ICON(ITEM_NUT), + ITEM_ICON(ITEM_BOMB), + ITEM_ICON(ITEM_BOW), + ITEM_ICON(ITEM_ARROW_FIRE), + ITEM_ICON(ITEM_DINS_FIRE), + ITEM_ICON(ITEM_SLINGSHOT), + ITEM_ICON(ITEM_OCARINA_FAIRY), + ITEM_ICON(ITEM_OCARINA_TIME), + ITEM_ICON(ITEM_BOMBCHU), + ITEM_ICON(ITEM_HOOKSHOT), + ITEM_ICON(ITEM_LONGSHOT), + ITEM_ICON(ITEM_ARROW_ICE), + ITEM_ICON(ITEM_FARORES_WIND), + ITEM_ICON(ITEM_BOOMERANG), + ITEM_ICON(ITEM_LENS), + ITEM_ICON(ITEM_BEAN), + ITEM_ICON(ITEM_HAMMER), + ITEM_ICON(ITEM_ARROW_LIGHT), + ITEM_ICON(ITEM_NAYRUS_LOVE), + ITEM_ICON(ITEM_BOTTLE), + ITEM_ICON(ITEM_POTION_RED), + ITEM_ICON(ITEM_POTION_GREEN), + ITEM_ICON(ITEM_POTION_BLUE), + ITEM_ICON(ITEM_FAIRY), + ITEM_ICON(ITEM_FISH), + ITEM_ICON(ITEM_MILK_BOTTLE), + ITEM_ICON(ITEM_LETTER_RUTO), + ITEM_ICON(ITEM_BLUE_FIRE), + ITEM_ICON(ITEM_BUG), + ITEM_ICON(ITEM_BIG_POE), + ITEM_ICON(ITEM_MILK_HALF), + ITEM_ICON(ITEM_POE), + ITEM_ICON(ITEM_WEIRD_EGG), + ITEM_ICON(ITEM_CHICKEN), + ITEM_ICON(ITEM_LETTER_ZELDA), + ITEM_ICON(ITEM_MASK_KEATON), + ITEM_ICON(ITEM_MASK_SKULL), + ITEM_ICON(ITEM_MASK_SPOOKY), + ITEM_ICON(ITEM_MASK_BUNNY), + ITEM_ICON(ITEM_MASK_GORON), + ITEM_ICON(ITEM_MASK_ZORA), + ITEM_ICON(ITEM_MASK_GERUDO), + ITEM_ICON(ITEM_MASK_TRUTH), + ITEM_ICON(ITEM_SOLD_OUT), + ITEM_ICON(ITEM_POCKET_EGG), + ITEM_ICON(ITEM_POCKET_CUCCO), + ITEM_ICON(ITEM_COJIRO), + ITEM_ICON(ITEM_ODD_MUSHROOM), + ITEM_ICON(ITEM_ODD_POTION), + ITEM_ICON(ITEM_SAW), + ITEM_ICON(ITEM_SWORD_BROKEN), + ITEM_ICON(ITEM_PRESCRIPTION), + ITEM_ICON(ITEM_FROG), + ITEM_ICON(ITEM_EYEDROPS), + ITEM_ICON(ITEM_CLAIM_CHECK), + ITEM_ICON(ITEM_BOW_ARROW_FIRE), + ITEM_ICON(ITEM_BOW_ARROW_ICE), + ITEM_ICON(ITEM_BOW_ARROW_LIGHT), + ITEM_ICON(ITEM_SWORD_KOKIRI), + ITEM_ICON(ITEM_SWORD_MASTER), + ITEM_ICON(ITEM_SWORD_BGS), + ITEM_ICON(ITEM_SHIELD_DEKU), + ITEM_ICON(ITEM_SHIELD_HYLIAN), + ITEM_ICON(ITEM_SHIELD_MIRROR), + ITEM_ICON(ITEM_TUNIC_KOKIRI), + ITEM_ICON(ITEM_TUNIC_GORON), + ITEM_ICON(ITEM_TUNIC_ZORA), + ITEM_ICON(ITEM_BOOTS_KOKIRI), + ITEM_ICON(ITEM_BOOTS_IRON), + ITEM_ICON(ITEM_BOOTS_HOVER), + ITEM_ICON(ITEM_BULLET_BAG_30), + ITEM_ICON(ITEM_BULLET_BAG_40), + ITEM_ICON(ITEM_BULLET_BAG_50), + ITEM_ICON(ITEM_QUIVER_30), + ITEM_ICON(ITEM_QUIVER_40), + ITEM_ICON(ITEM_QUIVER_50), + ITEM_ICON(ITEM_BOMB_BAG_20), + ITEM_ICON(ITEM_BOMB_BAG_30), + ITEM_ICON(ITEM_BOMB_BAG_40), + ITEM_ICON(ITEM_BRACELET), + ITEM_ICON(ITEM_GAUNTLETS_SILVER), + ITEM_ICON(ITEM_GAUNTLETS_GOLD), + ITEM_ICON(ITEM_SCALE_SILVER), + ITEM_ICON(ITEM_SCALE_GOLDEN), + ITEM_ICON(ITEM_SWORD_KNIFE), + ITEM_ICON(ITEM_WALLET_ADULT), + ITEM_ICON(ITEM_WALLET_GIANT), + ITEM_ICON(ITEM_SEEDS), + ITEM_ICON(ITEM_FISHING_POLE), + ITEM_ICON(ITEM_SONG_MINUET), + ITEM_ICON(ITEM_SONG_BOLERO), + ITEM_ICON(ITEM_SONG_SERENADE), + ITEM_ICON(ITEM_SONG_REQUIEM), + ITEM_ICON(ITEM_SONG_NOCTURNE), + ITEM_ICON(ITEM_SONG_PRELUDE), + ITEM_ICON(ITEM_SONG_LULLABY), + ITEM_ICON(ITEM_SONG_EPONA), + ITEM_ICON(ITEM_SONG_SARIA), + ITEM_ICON(ITEM_SONG_SUN), + ITEM_ICON(ITEM_SONG_TIME), + ITEM_ICON(ITEM_SONG_STORMS), + ITEM_ICON(ITEM_MEDALLION_FOREST), + ITEM_ICON(ITEM_MEDALLION_FIRE), + ITEM_ICON(ITEM_MEDALLION_WATER), + ITEM_ICON(ITEM_MEDALLION_SPIRIT), + ITEM_ICON(ITEM_MEDALLION_SHADOW), + ITEM_ICON(ITEM_MEDALLION_LIGHT), + ITEM_ICON(ITEM_KOKIRI_EMERALD), + ITEM_ICON(ITEM_GORON_RUBY), + ITEM_ICON(ITEM_ZORA_SAPPHIRE), + ITEM_ICON(ITEM_STONE_OF_AGONY), + ITEM_ICON(ITEM_GERUDO_CARD), + ITEM_ICON(ITEM_SKULL_TOKEN), + ITEM_ICON(ITEM_HEART_CONTAINER), + ITEM_ICON(ITEM_HEART_PIECE), + ITEM_ICON(ITEM_KEY_BOSS), + ITEM_ICON(ITEM_COMPASS), + ITEM_ICON(ITEM_DUNGEON_MAP), + ITEM_ICON(ITEM_KEY_SMALL), + ITEM_ICON(ITEM_MAGIC_SMALL), + ITEM_ICON(ITEM_MAGIC_LARGE) +}; + +std::map randomizerGetToItemIdIcon = { + { RG_NONE, ITEM_NONE }, + { RG_KOKIRI_SWORD, ITEM_SWORD_KOKIRI }, + { RG_GIANTS_KNIFE, ITEM_SWORD_KNIFE }, + { RG_BIGGORON_SWORD, ITEM_SWORD_BGS }, + { RG_DEKU_SHIELD, ITEM_SHIELD_DEKU }, + { RG_HYLIAN_SHIELD, ITEM_SHIELD_HYLIAN }, + { RG_MIRROR_SHIELD, ITEM_SHIELD_MIRROR }, + { RG_GORON_TUNIC, ITEM_TUNIC_GORON }, + { RG_ZORA_TUNIC, ITEM_TUNIC_ZORA }, + { RG_IRON_BOOTS, ITEM_BOOTS_IRON }, + { RG_HOVER_BOOTS, ITEM_BOOTS_HOVER }, + { RG_BOOMERANG, ITEM_BOOMERANG }, + { RG_LENS_OF_TRUTH, ITEM_LENS }, + { RG_MEGATON_HAMMER, ITEM_HAMMER }, + { RG_STONE_OF_AGONY, ITEM_STONE_OF_AGONY }, + { RG_DINS_FIRE, ITEM_DINS_FIRE }, + { RG_FARORES_WIND, ITEM_FARORES_WIND }, + { RG_NAYRUS_LOVE, ITEM_NAYRUS_LOVE }, + { RG_FIRE_ARROWS, ITEM_ARROW_FIRE }, + { RG_ICE_ARROWS, ITEM_ARROW_ICE }, + { RG_LIGHT_ARROWS, ITEM_ARROW_LIGHT }, + { RG_GERUDO_MEMBERSHIP_CARD, ITEM_GERUDO_CARD }, + { RG_MAGIC_BEAN, ITEM_BEAN }, + { RG_MAGIC_BEAN_PACK, ITEM_BEAN }, + { RG_DOUBLE_DEFENSE, ITEM_DOUBLE_DEFENSE }, + { RG_WEIRD_EGG, ITEM_WEIRD_EGG }, + { RG_ZELDAS_LETTER, ITEM_LETTER_ZELDA }, + { RG_POCKET_EGG, ITEM_POCKET_EGG }, + { RG_COJIRO, ITEM_COJIRO }, + { RG_ODD_MUSHROOM, ITEM_ODD_MUSHROOM }, + { RG_ODD_POTION, ITEM_ODD_POTION }, + { RG_POACHERS_SAW, ITEM_SAW }, + { RG_BROKEN_SWORD, ITEM_SWORD_BROKEN }, + { RG_PRESCRIPTION, ITEM_PRESCRIPTION }, + { RG_EYEBALL_FROG, ITEM_FROG }, + { RG_EYEDROPS, ITEM_EYEDROPS }, + { RG_CLAIM_CHECK, ITEM_CLAIM_CHECK }, + { RG_GOLD_SKULLTULA_TOKEN, ITEM_SKULL_TOKEN }, + { RG_PROGRESSIVE_HOOKSHOT, ITEM_HOOKSHOT }, + { RG_PROGRESSIVE_STRENGTH, ITEM_BRACELET }, + { RG_PROGRESSIVE_BOMB_BAG, ITEM_BOMB_BAG_20 }, + { RG_PROGRESSIVE_BOW, ITEM_BOW }, + { RG_PROGRESSIVE_SLINGSHOT, ITEM_SLINGSHOT }, + { RG_PROGRESSIVE_WALLET, ITEM_WALLET_ADULT }, + { RG_PROGRESSIVE_SCALE, ITEM_SCALE_SILVER }, + { RG_PROGRESSIVE_NUT_UPGRADE, ITEM_NUT }, + { RG_PROGRESSIVE_STICK_UPGRADE, ITEM_STICK }, + { RG_PROGRESSIVE_BOMBCHUS, ITEM_BOMBCHU }, + { RG_PROGRESSIVE_MAGIC_METER, ITEM_MAGIC_SMALL }, + { RG_MAGIC_SINGLE, ITEM_MAGIC_SMALL }, + { RG_MAGIC_DOUBLE, ITEM_MAGIC_LARGE }, + { RG_PROGRESSIVE_OCARINA, ITEM_OCARINA_FAIRY }, + { RG_PROGRESSIVE_GORONSWORD, ITEM_SWORD_BGS }, + { RG_EMPTY_BOTTLE, ITEM_BOTTLE }, + { RG_BOTTLE_WITH_MILK, ITEM_MILK_BOTTLE }, + { RG_BOTTLE_WITH_RED_POTION, ITEM_POTION_RED }, + { RG_BOTTLE_WITH_GREEN_POTION, ITEM_POTION_GREEN }, + { RG_BOTTLE_WITH_BLUE_POTION, ITEM_POTION_BLUE }, + { RG_BOTTLE_WITH_FAIRY, ITEM_FAIRY }, + { RG_BOTTLE_WITH_FISH, ITEM_FISH }, + { RG_BOTTLE_WITH_BLUE_FIRE, ITEM_BLUE_FIRE }, + { RG_BOTTLE_WITH_BUGS, ITEM_BUG }, + { RG_BOTTLE_WITH_POE, ITEM_POE }, + { RG_RUTOS_LETTER, ITEM_LETTER_RUTO }, + { RG_BOTTLE_WITH_BIG_POE, ITEM_BIG_POE }, + { RG_ZELDAS_LULLABY, ITEM_SONG_LULLABY }, + { RG_EPONAS_SONG, ITEM_SONG_EPONA }, + { RG_SARIAS_SONG, ITEM_SONG_SARIA }, + { RG_SUNS_SONG, ITEM_SONG_SUN }, + { RG_SONG_OF_TIME, ITEM_SONG_TIME }, + { RG_SONG_OF_STORMS, ITEM_SONG_STORMS }, + { RG_MINUET_OF_FOREST, ITEM_SONG_MINUET }, + { RG_BOLERO_OF_FIRE, ITEM_SONG_BOLERO }, + { RG_SERENADE_OF_WATER, ITEM_SONG_SERENADE }, + { RG_REQUIEM_OF_SPIRIT, ITEM_SONG_REQUIEM }, + { RG_NOCTURNE_OF_SHADOW, ITEM_SONG_NOCTURNE }, + { RG_PRELUDE_OF_LIGHT, ITEM_SONG_PRELUDE }, + { RG_DEKU_TREE_MAP, ITEM_DUNGEON_MAP }, + { RG_DODONGOS_CAVERN_MAP, ITEM_DUNGEON_MAP }, + { RG_JABU_JABUS_BELLY_MAP, ITEM_DUNGEON_MAP }, + { RG_FOREST_TEMPLE_MAP, ITEM_DUNGEON_MAP }, + { RG_FIRE_TEMPLE_MAP, ITEM_DUNGEON_MAP }, + { RG_WATER_TEMPLE_MAP, ITEM_DUNGEON_MAP }, + { RG_SPIRIT_TEMPLE_MAP, ITEM_DUNGEON_MAP }, + { RG_SHADOW_TEMPLE_MAP, ITEM_DUNGEON_MAP }, + { RG_BOTTOM_OF_THE_WELL_MAP, ITEM_DUNGEON_MAP }, + { RG_ICE_CAVERN_MAP, ITEM_DUNGEON_MAP }, + { RG_DEKU_TREE_COMPASS, ITEM_COMPASS }, + { RG_DODONGOS_CAVERN_COMPASS, ITEM_COMPASS }, + { RG_JABU_JABUS_BELLY_COMPASS, ITEM_COMPASS }, + { RG_FOREST_TEMPLE_COMPASS, ITEM_COMPASS }, + { RG_FIRE_TEMPLE_COMPASS, ITEM_COMPASS }, + { RG_WATER_TEMPLE_COMPASS, ITEM_COMPASS }, + { RG_SPIRIT_TEMPLE_COMPASS, ITEM_COMPASS }, + { RG_SHADOW_TEMPLE_COMPASS, ITEM_COMPASS }, + { RG_BOTTOM_OF_THE_WELL_COMPASS, ITEM_COMPASS }, + { RG_ICE_CAVERN_COMPASS, ITEM_COMPASS }, + { RG_FOREST_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS }, + { RG_FIRE_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS }, + { RG_WATER_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS }, + { RG_SPIRIT_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS }, + { RG_SHADOW_TEMPLE_BOSS_KEY, ITEM_KEY_BOSS }, + { RG_GANONS_CASTLE_BOSS_KEY, ITEM_KEY_BOSS }, + { RG_FOREST_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_FIRE_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_WATER_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_SPIRIT_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_SHADOW_TEMPLE_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_BOTTOM_OF_THE_WELL_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_GERUDO_TRAINING_GROUNDS_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_GERUDO_FORTRESS_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_GANONS_CASTLE_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_TREASURE_GAME_SMALL_KEY, ITEM_KEY_SMALL }, + { RG_FOREST_TEMPLE_KEY_RING, ITEM_KEY_SMALL }, + { RG_FIRE_TEMPLE_KEY_RING, ITEM_KEY_SMALL }, + { RG_WATER_TEMPLE_KEY_RING, ITEM_KEY_SMALL }, + { RG_SPIRIT_TEMPLE_KEY_RING, ITEM_KEY_SMALL }, + { RG_SHADOW_TEMPLE_KEY_RING, ITEM_KEY_SMALL }, + { RG_BOTTOM_OF_THE_WELL_KEY_RING, ITEM_KEY_SMALL }, + { RG_GERUDO_TRAINING_GROUNDS_KEY_RING, ITEM_KEY_SMALL }, + { RG_GERUDO_FORTRESS_KEY_RING, ITEM_KEY_SMALL }, + { RG_GANONS_CASTLE_KEY_RING, ITEM_KEY_SMALL }, + { RG_KOKIRI_EMERALD, ITEM_KOKIRI_EMERALD }, + { RG_GORON_RUBY, ITEM_GORON_RUBY }, + { RG_ZORA_SAPPHIRE, ITEM_ZORA_SAPPHIRE }, + { RG_FOREST_MEDALLION, ITEM_MEDALLION_FOREST }, + { RG_FIRE_MEDALLION, ITEM_MEDALLION_FIRE }, + { RG_WATER_MEDALLION, ITEM_MEDALLION_WATER }, + { RG_SPIRIT_MEDALLION, ITEM_MEDALLION_SPIRIT }, + { RG_SHADOW_MEDALLION, ITEM_MEDALLION_SHADOW }, + { RG_LIGHT_MEDALLION, ITEM_MEDALLION_LIGHT }, + { RG_RECOVERY_HEART, ITEM_HEART }, + { RG_GREEN_RUPEE, ITEM_RUPEE_GREEN }, + { RG_GREG_RUPEE, ITEM_RUPEE_GREEN }, + { RG_BLUE_RUPEE, ITEM_RUPEE_BLUE }, + { RG_RED_RUPEE, ITEM_RUPEE_RED }, + { RG_PURPLE_RUPEE, ITEM_RUPEE_PURPLE }, + { RG_HUGE_RUPEE, ITEM_RUPEE_GOLD }, + { RG_PIECE_OF_HEART, ITEM_HEART_PIECE }, + { RG_HEART_CONTAINER, ITEM_HEART_CONTAINER }, + { RG_ICE_TRAP, ITEM_NONE }, + { RG_MILK, ITEM_MILK_BOTTLE }, + { RG_BOMBS_5, ITEM_BOMB }, + { RG_BOMBS_10, ITEM_BOMB }, + { RG_BOMBS_20, ITEM_BOMB }, + { RG_BOMBCHU_5, ITEM_BOMBCHU }, + { RG_BOMBCHU_10, ITEM_BOMBCHU }, + { RG_BOMBCHU_20, ITEM_BOMBCHU }, + { RG_BOMBCHU_DROP, ITEM_BOMBCHU }, + { RG_ARROWS_5, ITEM_ARROWS_SMALL }, + { RG_ARROWS_10, ITEM_ARROWS_MEDIUM }, + { RG_ARROWS_30, ITEM_ARROWS_LARGE }, + { RG_DEKU_NUTS_5, ITEM_NUT }, + { RG_DEKU_NUTS_10, ITEM_NUT }, + { RG_DEKU_SEEDS_30, ITEM_SEEDS }, + { RG_DEKU_STICK_1, ITEM_STICK }, + { RG_RED_POTION_REFILL, ITEM_POTION_RED }, + { RG_GREEN_POTION_REFILL, ITEM_POTION_GREEN }, + { RG_BLUE_POTION_REFILL, ITEM_POTION_BLUE }, + { RG_TREASURE_GAME_HEART, ITEM_HEART_PIECE }, + { RG_TREASURE_GAME_GREEN_RUPEE, ITEM_RUPEE_GREEN }, + { RG_BUY_DEKU_NUT_5, ITEM_NUT }, + { RG_BUY_ARROWS_30, ITEM_ARROWS_MEDIUM }, + { RG_BUY_ARROWS_50, ITEM_ARROWS_LARGE }, + { RG_BUY_BOMBS_525, ITEM_BOMB }, + { RG_BUY_DEKU_NUT_10, ITEM_NUT }, + { RG_BUY_DEKU_STICK_1, ITEM_STICK }, + { RG_BUY_BOMBS_10, ITEM_BOMB }, + { RG_BUY_FISH, ITEM_FISH }, + { RG_BUY_RED_POTION_30, ITEM_POTION_RED }, + { RG_BUY_GREEN_POTION, ITEM_POTION_GREEN }, + { RG_BUY_BLUE_POTION, ITEM_POTION_BLUE }, + { RG_BUY_HYLIAN_SHIELD, ITEM_SHIELD_HYLIAN }, + { RG_BUY_DEKU_SHIELD, ITEM_SHIELD_DEKU }, + { RG_BUY_GORON_TUNIC, ITEM_TUNIC_GORON }, + { RG_BUY_ZORA_TUNIC, ITEM_TUNIC_ZORA }, + { RG_BUY_HEART, ITEM_HEART }, + { RG_BUY_BOMBCHU_10, ITEM_BOMBCHU }, + { RG_BUY_BOMBCHU_20, ITEM_BOMBCHU }, + { RG_BUY_DEKU_SEEDS_30, ITEM_SEEDS }, + { RG_SOLD_OUT, ITEM_SOLD_OUT }, + { RG_BUY_BLUE_FIRE, ITEM_BLUE_FIRE }, + { RG_BUY_BOTTLE_BUG, ITEM_BUG }, + { RG_BUY_POE, ITEM_POE }, + { RG_BUY_FAIRYS_SPIRIT, ITEM_FAIRY }, + { RG_BUY_ARROWS_10, ITEM_ARROWS_SMALL }, + { RG_BUY_BOMBS_20, ITEM_BOMB }, + { RG_BUY_BOMBS_30, ITEM_BOMB }, + { RG_BUY_BOMBS_535, ITEM_BOMB }, + { RG_BUY_RED_POTION_40, ITEM_POTION_RED }, + { RG_BUY_RED_POTION_50, ITEM_POTION_RED }, + { RG_TYCOON_WALLET, ITEM_WALLET_GIANT }, +}; + +int32_t SohUtils::GetItemIdIconFromRandomizerGet(int32_t randomizerGet) { + return randomizerGetToItemIdIcon.contains(randomizerGet) ? randomizerGetToItemIdIcon[randomizerGet] : ITEM_NONE; +} + +const char* SohUtils::GetIconNameFromItemID(int32_t itemId) { + return itemIcons.contains(itemId) ? itemIcons[itemId] : nullptr; +} + // To be used with SceneID enum (SCENE_ prefix) const std::string& SohUtils::GetSceneName(int32_t scene) { return sceneNames[scene]; diff --git a/soh/soh/util.h b/soh/soh/util.h index cf0265fd261..733bf0f3bda 100644 --- a/soh/soh/util.h +++ b/soh/soh/util.h @@ -3,6 +3,10 @@ #include namespace SohUtils { + int32_t GetItemIdIconFromRandomizerGet(int32_t randomizerGet); + + const char* GetIconNameFromItemID(int32_t itemId); + const std::string& GetSceneName(int32_t scene); const std::string& GetItemName(int32_t item); diff --git a/soh/src/code/z_en_item00.c b/soh/src/code/z_en_item00.c index cfe5a96d3aa..6d3782dd8f6 100644 --- a/soh/src/code/z_en_item00.c +++ b/soh/src/code/z_en_item00.c @@ -741,6 +741,13 @@ void EnItem00_Update(Actor* thisx, PlayState* play) { EnItem00* this = (EnItem00*)thisx; s32 pad; + // #region SOH [Co-op] + if (Flags_GetCollectible(play, this->collectibleFlag)) { + Actor_Kill(&this->actor); + return; + } + // #endregion + // OTRTODO: remove special case for bombchu when its 2D drop is implemented if (CVarGetInteger("gNewDrops", 0) || this->actor.params == ITEM00_BOMBCHU) { //set the rotation system on selected model only :) if ((this->actor.params == ITEM00_RUPEE_GREEN) || (this->actor.params == ITEM00_RUPEE_BLUE) || diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index af727f1d70c..b9737a0cee5 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -1741,6 +1741,11 @@ u8 Return_Item(u8 itemID, ModIndex modId, ItemID returnItem) { GetItemEntry gie = { ITEM_SOLD_OUT, 0, 0, 0, 0, 0, 0, 0, 0, false, ITEM_FROM_NPC, ITEM_CATEGORY_LESSER, NULL }; return Return_Item_Entry(gie, returnItem); } + // Master sword doesn't have an ItemTable entry, so pass custom entry instead (This will go away with master sword shuffle) + if (itemID == ITEM_SWORD_MASTER) { + GetItemEntry gie = { ITEM_SWORD_MASTER, 0, 0, 0, 0, 0, 0, 0, 0, false, ITEM_FROM_NPC, ITEM_CATEGORY_MAJOR, NULL }; + return Return_Item_Entry(gie, returnItem); + } GetItemID getItemID = GetGetItemIDFromItemID(itemID); if (getItemID != GI_MAX) { @@ -2194,6 +2199,14 @@ u8 Item_Give(PlayState* play, u8 item) { } else if ((item == ITEM_HEART_PIECE_2) || (item == ITEM_HEART_PIECE)) { gSaveContext.inventory.questItems += 1 << (QUEST_HEART_PIECE + 4); gSaveContext.sohStats.heartPieces++; + if (CVarGetInteger("gFromGI", 0)) { + gSaveContext.healthAccumulator = 0x140; + if ((s32)(gSaveContext.inventory.questItems & 0xF0000000) == 0x40000000) { + gSaveContext.inventory.questItems ^= 0x40000000; + gSaveContext.healthCapacity += 0x10; + gSaveContext.health += 0x10; + } + } return Return_Item(item, MOD_NONE, ITEM_NONE); } else if (item == ITEM_HEART_CONTAINER) { gSaveContext.healthCapacity += 0x10; diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 128ef309f75..ec50374d271 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -11,6 +11,9 @@ #include #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #include @@ -745,6 +748,13 @@ void Play_Init(GameState* thisx) { GET_PLAYER(play)->actor.world.pos.y + Player_GetHeight(GET_PLAYER(play)) + 5.0f, GET_PLAYER(play)->actor.world.pos.z, 0, 0, 0, 1, true); } +#ifdef ENABLE_REMOTE_CONTROL + // #region SOH [Co-op] + if (CVarGetInteger("gRemote.Scheme", 0) == GI_SCHEME_ANCHOR) { + Anchor_SpawnClientFairies(); + } + // #endregion +#endif } void Play_Update(PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_Bg_Breakwall/z_bg_breakwall.c b/soh/src/overlays/actors/ovl_Bg_Breakwall/z_bg_breakwall.c index 925a66ba253..b243a748e2f 100644 --- a/soh/src/overlays/actors/ovl_Bg_Breakwall/z_bg_breakwall.c +++ b/soh/src/overlays/actors/ovl_Bg_Breakwall/z_bg_breakwall.c @@ -275,7 +275,7 @@ void BgBreakwall_Wait(BgBreakwall* this, PlayState* play) { } // Break the floor immediately in Boss Rush so the player can jump in the hole immediately. - if (this->collider.base.acFlags & 2 || blueFireArrowHit || gSaveContext.isBossRush) { + if (this->collider.base.acFlags & 2 || blueFireArrowHit || gSaveContext.isBossRush || Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) { Vec3f effectPos; s32 wallType = ((this->dyna.actor.params >> 13) & 3) & 0xFF; diff --git a/soh/src/overlays/actors/ovl_Bg_Ydan_Sp/z_bg_ydan_sp.c b/soh/src/overlays/actors/ovl_Bg_Ydan_Sp/z_bg_ydan_sp.c index ad11f19fd97..de07d7a52b2 100644 --- a/soh/src/overlays/actors/ovl_Bg_Ydan_Sp/z_bg_ydan_sp.c +++ b/soh/src/overlays/actors/ovl_Bg_Ydan_Sp/z_bg_ydan_sp.c @@ -281,6 +281,13 @@ void BgYdanSp_FloorWebIdle(BgYdanSp* this, PlayState* play) { webPos.x = this->dyna.actor.world.pos.x; webPos.y = this->dyna.actor.world.pos.y - 50.0f; webPos.z = this->dyna.actor.world.pos.z; + + // #region SOH [Co-op] + if (Flags_GetSwitch(play, this->isDestroyedSwitchFlag)) { + BgYdanSp_BurnWeb(this, play); + return; + } + // #endregion if (Player_IsBurningStickInRange(play, &webPos, 70.0f, 50.0f) != 0) { this->dyna.actor.home.pos.x = player->meleeWeaponInfo[0].tip.x; this->dyna.actor.home.pos.z = player->meleeWeaponInfo[0].tip.z; @@ -410,6 +417,13 @@ void BgYdanSp_WallWebIdle(BgYdanSp* this, PlayState* play) { BgYdanSp_BurnWeb(this, play); } } + + // #region SOH [Co-op] + if (Flags_GetSwitch(play, this->isDestroyedSwitchFlag)) { + BgYdanSp_BurnWeb(this, play); + } + // #endregion + CollisionCheck_SetAC(play, &play->colChkCtx, &this->trisCollider.base); } diff --git a/soh/src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.c b/soh/src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.c index c5f05341aaf..b7ef2219c91 100644 --- a/soh/src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.c +++ b/soh/src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.c @@ -6,6 +6,9 @@ #include "z_door_gerudo.h" #include "objects/object_door_gerudo/object_door_gerudo.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #define FLAGS 0 @@ -101,6 +104,9 @@ void func_8099485C(DoorGerudo* this, PlayState* play) { if (this->unk_164 != 0) { this->actionFunc = func_8099496C; gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex] -= 1; +#ifdef ENABLE_REMOTE_CONTROL + Anchor_UpdateKeyCount(gSaveContext.mapIndex, gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]); +#endif Flags_SetSwitch(play, this->dyna.actor.params & 0x3F); Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_CHAIN_KEY_UNLOCK); } else { diff --git a/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.c b/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.c index f7556386f98..02b3569e191 100644 --- a/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.c +++ b/soh/src/overlays/actors/ovl_Door_Shutter/z_door_shutter.c @@ -23,6 +23,9 @@ #include "objects/object_menkuri_objects/object_menkuri_objects.h" #include "objects/object_demo_kekkai/object_demo_kekkai.h" #include "objects/object_ouke_haka/object_ouke_haka.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #define FLAGS ACTOR_FLAG_UPDATE_WHILE_CULLED @@ -375,6 +378,9 @@ void func_80996B0C(DoorShutter* this, PlayState* play) { Flags_SetSwitch(play, this->dyna.actor.params & 0x3F); if (this->doorType != SHUTTER_BOSS) { gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]--; +#ifdef ENABLE_REMOTE_CONTROL + Anchor_UpdateKeyCount(gSaveContext.mapIndex, gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]); +#endif Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_CHAIN_KEY_UNLOCK); } else { Audio_PlayActorSound2(&this->dyna.actor, NA_SE_EV_CHAIN_KEY_UNLOCK_B); @@ -640,6 +646,12 @@ void DoorShutter_Update(Actor* thisx, PlayState* play) { if (!(player->stateFlags1 & 0x100004C0) || (this->actionFunc == DoorShutter_SetupType)) { this->actionFunc(this, play); } + + // #region SOH [Co-op] + if (Flags_GetSwitch(play, this->dyna.actor.params & 0x3F)) { + DECR(this->unk_16E); + } + // #endregion } Gfx* func_80997838(PlayState* play, DoorShutter* this, Gfx* p) { diff --git a/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c b/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c index 35f58f63b28..e45d68a5b90 100644 --- a/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c +++ b/soh/src/overlays/actors/ovl_En_Daiku/z_en_daiku.c @@ -1,6 +1,8 @@ #include "z_en_daiku.h" #include "overlays/actors/ovl_En_GeldB/z_en_geldb.h" #include "objects/object_daiku/object_daiku.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_WHILE_CULLED) @@ -403,6 +405,7 @@ void EnDaiku_InitEscape(EnDaiku* this, PlayState* play) { this->stateFlags &= ~(ENDAIKU_STATEFLAG_1 | ENDAIKU_STATEFLAG_2); gSaveContext.eventChkInf[EVENTCHKINF_CARPENTERS_FREE_INDEX] |= EVENTCHKINF_CARPENTERS_FREE_MASK(this->actor.params & 3); + GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_CARPENTERS_FREE_INDEX << 4) + (int)log2(EVENTCHKINF_CARPENTERS_FREE_MASK(this->actor.params & 3))); this->actor.gravity = -1.0f; this->escapeSubCamTimer = sEscapeSubCamParams[this->actor.params & 3].maxFramesActive; diff --git a/soh/src/overlays/actors/ovl_En_Door/z_en_door.c b/soh/src/overlays/actors/ovl_En_Door/z_en_door.c index 685949b8189..57ca3b7e6b2 100644 --- a/soh/src/overlays/actors/ovl_En_Door/z_en_door.c +++ b/soh/src/overlays/actors/ovl_En_Door/z_en_door.c @@ -10,6 +10,9 @@ #include "objects/object_hidan_objects/object_hidan_objects.h" #include "objects/object_mizu_objects/object_mizu_objects.h" #include "objects/object_haka_door/object_haka_door.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #define FLAGS ACTOR_FLAG_UPDATE_WHILE_CULLED @@ -201,6 +204,9 @@ void EnDoor_Idle(EnDoor* this, PlayState* play) { (player->stateFlags1 & 0x8000000) ? 0.75f : 1.5f); if (this->lockTimer != 0) { gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]--; +#ifdef ENABLE_REMOTE_CONTROL + Anchor_UpdateKeyCount(gSaveContext.mapIndex, gSaveContext.inventory.dungeonKeys[gSaveContext.mapIndex]); +#endif Flags_SetSwitch(play, this->actor.params & 0x3F); Audio_PlayActorSound2(&this->actor, NA_SE_EV_CHAIN_KEY_UNLOCK); } @@ -230,6 +236,12 @@ void EnDoor_Idle(EnDoor* this, PlayState* play) { this->actionFunc = EnDoor_AjarOpen; } } + + // #region SOH [Co-op] + if (Flags_GetSwitch(play, this->actor.params & 0x3F)) { + DECR(this->lockTimer); + } + // #endregion } void EnDoor_WaitForCheck(EnDoor* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Fr/z_en_fr.c b/soh/src/overlays/actors/ovl_En_Fr/z_en_fr.c index 38c54471f75..3885195b9d8 100644 --- a/soh/src/overlays/actors/ovl_En_Fr/z_en_fr.c +++ b/soh/src/overlays/actors/ovl_En_Fr/z_en_fr.c @@ -3,6 +3,8 @@ #include "vt.h" #include "objects/object_fr/object_fr.h" #include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_NO_FREEZE_OCARINA) @@ -955,6 +957,7 @@ void EnFr_SetReward(EnFr* this, PlayState* play) { if ((songIndex >= FROG_ZL) && (songIndex <= FROG_SOT)) { if (!(gSaveContext.eventChkInf[13] & sSongIndex[songIndex])) { gSaveContext.eventChkInf[13] |= sSongIndex[songIndex]; + GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SONGS_FOR_FROGS_INDEX << 4) + (int)log2(sSongIndex[songIndex])); if (!gSaveContext.n64ddFlag) { this->reward = GI_RUPEE_PURPLE; } else { @@ -967,6 +970,7 @@ void EnFr_SetReward(EnFr* this, PlayState* play) { } else if (songIndex == FROG_STORMS) { if (!(gSaveContext.eventChkInf[13] & sSongIndex[songIndex])) { gSaveContext.eventChkInf[13] |= sSongIndex[songIndex]; + GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SONGS_FOR_FROGS_INDEX << 4) + (int)log2(sSongIndex[songIndex])); if (!gSaveContext.n64ddFlag) { this->reward = GI_HEART_PIECE; } else { @@ -979,6 +983,7 @@ void EnFr_SetReward(EnFr* this, PlayState* play) { } else if (songIndex == FROG_CHOIR_SONG) { if (!(gSaveContext.eventChkInf[13] & sSongIndex[songIndex])) { gSaveContext.eventChkInf[13] |= sSongIndex[songIndex]; + GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, (EVENTCHKINF_SONGS_FOR_FROGS_INDEX << 4) + (int)log2(sSongIndex[songIndex])); if (!gSaveContext.n64ddFlag) { this->reward = GI_HEART_PIECE; } else { diff --git a/soh/src/overlays/actors/ovl_En_Md/z_en_md.c b/soh/src/overlays/actors/ovl_En_Md/z_en_md.c index 90da4c01b4f..5d1fb9bcd74 100644 --- a/soh/src/overlays/actors/ovl_En_Md/z_en_md.c +++ b/soh/src/overlays/actors/ovl_En_Md/z_en_md.c @@ -785,6 +785,17 @@ void func_80AAB948(EnMd* this, PlayState* play) { player->stateFlags2 |= 0x800000; } } + + // #region SOH [Co-op] + if (Flags_GetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD) && this->interactInfo.talkState == NPC_TALK_STATE_IDLE && (play->sceneNum == SCENE_SPOT04)) { + func_80AAA92C(this, 3); + func_80AAA93C(this); + this->waypoint = 1; + this->interactInfo.talkState = NPC_TALK_STATE_IDLE; + this->actionFunc = func_80AABD0C; + this->actor.speedXZ = 1.5f; + } + // #endregion } void func_80AABC10(EnMd* this, PlayState* play) { diff --git a/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c b/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c index 039e7297f01..b098d289149 100644 --- a/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c +++ b/soh/src/overlays/actors/ovl_En_Ms/z_en_ms.c @@ -6,6 +6,9 @@ #include "z_en_ms.h" #include "objects/object_ms/object_ms.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_FRIENDLY) @@ -163,11 +166,17 @@ void EnMs_Sell(EnMs* this, PlayState* play) { gSaveContext.pendingSaleMod = itemEntry.modIndex; GiveItemEntryFromActor(&this->actor, play, itemEntry, 90.0f, 10.0f); BEANS_BOUGHT = 10; +#ifdef ENABLE_REMOTE_CONTROL + Anchor_UpdateBeansBought(BEANS_BOUGHT); +#endif } else { GetItemEntry entry = ItemTable_Retrieve(GI_BEAN); gSaveContext.pendingSaleMod = entry.modIndex; gSaveContext.pendingSale = entry.itemId; func_8002F434(&this->actor, play, GI_BEAN, 90.0f, 10.0f); +#ifdef ENABLE_REMOTE_CONTROL + Anchor_UpdateBeansBought(BEANS_BOUGHT); +#endif } } } diff --git a/soh/src/overlays/actors/ovl_En_Partner/z_en_partner.c b/soh/src/overlays/actors/ovl_En_Partner/z_en_partner.c index 60e0e8b48fd..0c2822e873a 100644 --- a/soh/src/overlays/actors/ovl_En_Partner/z_en_partner.c +++ b/soh/src/overlays/actors/ovl_En_Partner/z_en_partner.c @@ -11,6 +11,9 @@ #include #include #include +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #define FLAGS (ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED | ACTOR_FLAG_DRAGGED_BY_HOOKSHOT | ACTOR_FLAG_CAN_PRESS_SWITCH) @@ -74,12 +77,23 @@ void EnPartner_Init(Actor* thisx, PlayState* play) { this->innerColor.g = 255.0f; this->innerColor.b = 255.0f; this->innerColor.a = 255.0f; - this->outerColor.r = 0.0f; this->outerColor.g = 255.0f; this->outerColor.b = 0.0f; this->outerColor.a = 255.0f; +#ifdef ENABLE_REMOTE_CONTROL + if (this->actor.params >= 3) { + Color_RGB8 color = Anchor_GetClientColor(this->actor.params - 3); + this->outerColor.r = color.r; + this->outerColor.g = color.g; + this->outerColor.b = color.b; + this->innerColor.r = MIN(color.r + 100.0f, 255.0f); + this->innerColor.g = MIN(color.g + 100.0f, 255.0f); + this->innerColor.b = MIN(color.b + 100.0f, 255.0f); + } +#endif + this->usedItemButton = 0xFF; Collider_InitCylinder(play, &this->collider); @@ -131,11 +145,19 @@ void EnPartner_UpdateLights(EnPartner* this, PlayState* play) { Player* player; player = GET_PLAYER(play); - Lights_PointNoGlowSetInfo(&this->lightInfoNoGlow, player->actor.world.pos.x, (s16)(player->actor.world.pos.y) + 69, - player->actor.world.pos.z, 200, 255, 200, lightRadius); + if (this->actor.params >= 3) { + Lights_PointNoGlowSetInfo(&this->lightInfoNoGlow, player->actor.world.pos.x, (s16)(player->actor.world.pos.y) + 69, + player->actor.world.pos.z, 200, 200, 200, lightRadius); + + Lights_PointGlowSetInfo(&this->lightInfoGlow, this->actor.world.pos.x, this->actor.world.pos.y + 9, + this->actor.world.pos.z, 200, 200, 200, glowLightRadius); + } else { + Lights_PointNoGlowSetInfo(&this->lightInfoNoGlow, player->actor.world.pos.x, (s16)(player->actor.world.pos.y) + 69, + player->actor.world.pos.z, 200, 255, 200, lightRadius); - Lights_PointGlowSetInfo(&this->lightInfoGlow, this->actor.world.pos.x, this->actor.world.pos.y + 9, + Lights_PointGlowSetInfo(&this->lightInfoGlow, this->actor.world.pos.x, this->actor.world.pos.y + 9, this->actor.world.pos.z, 200, 255, 200, glowLightRadius); + } Actor_SetScale(&this->actor, this->actor.scale.x); } @@ -576,6 +598,40 @@ void EnPartner_Update(Actor* thisx, PlayState* play) { s32 pad; EnPartner* this = (EnPartner*)thisx; +#ifdef ENABLE_REMOTE_CONTROL + if (this->actor.params >= 3) { + if (Anchor_GetClientScene(this->actor.params - 3) == play->sceneNum) { + PosRot coopPlayerPos = Anchor_GetClientPosition(this->actor.params - 3); + // if hidden, immediately update position + if (this->actor.world.pos.y == -9999.0f) { + this->actor.world = coopPlayerPos; + this->actor.world.pos.y += Player_GetHeight(GET_PLAYER(play)); + this->actor.shape.rot = coopPlayerPos.rot; + // Otherwise smoothly update position + } else { + float dist = 0.0f; + dist += Math_SmoothStepToF(&this->actor.world.pos.x, coopPlayerPos.pos.x, 0.5f, 1000.0f, 0.0f); + dist += Math_SmoothStepToF(&this->actor.world.pos.y, coopPlayerPos.pos.y + Player_GetHeight(GET_PLAYER(play)), 0.5f, 1000.0f, 0.0f); + dist += Math_SmoothStepToF(&this->actor.world.pos.z, coopPlayerPos.pos.z, 0.5f, 1000.0f, 0.0f); + if (dist > 1.0f) { + EnPartner_SpawnSparkles(this, play, 12); + } + this->actor.world.rot = coopPlayerPos.rot; + this->actor.shape.rot = coopPlayerPos.rot; + } + } else { + this->actor.world.pos.x = -9999.0f; + this->actor.world.pos.y = -9999.0f; + this->actor.world.pos.z = -9999.0f; + } + + thisx->shape.shadowAlpha = 0xFF; + SkelAnime_Update(&this->skelAnime); + EnPartner_UpdateLights(this, play); + return; + } +#endif + Input sControlInput = play->state.input[this->actor.params]; f32 relX = sControlInput.cur.stick_x / 10.0f * (CVarGetInteger("gMirroredWorld", 0) ? -1 : 1); diff --git a/soh/src/overlays/actors/ovl_En_Si/z_en_si.c b/soh/src/overlays/actors/ovl_En_Si/z_en_si.c index cb64be23e46..1bd98656f79 100644 --- a/soh/src/overlays/actors/ovl_En_Si/z_en_si.c +++ b/soh/src/overlays/actors/ovl_En_Si/z_en_si.c @@ -189,6 +189,14 @@ void func_80AFB950(EnSi* this, PlayState* play) { void EnSi_Update(Actor* thisx, PlayState* play) { EnSi* this = (EnSi*)thisx; + // #region SOH [Co-op] + // Check to see if this gold skull token has already been retrieved. + if (GET_GS_FLAGS((thisx->params & 0x1F00) >> 8) & (thisx->params & 0xFF)) { + Actor_Kill(&this->actor); + return; + } + // #endregion + Actor_MoveForward(&this->actor); Actor_UpdateBgCheckInfo(play, &this->actor, 0.0f, 0.0f, 0.0f, 4); this->actionFunc(this, play); diff --git a/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c b/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c index 79a8d8bfbc3..ffb8946edb7 100644 --- a/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c +++ b/soh/src/overlays/actors/ovl_En_Sw/z_en_sw.c @@ -914,6 +914,14 @@ void func_80B0E9BC(EnSw* this, PlayState* play) { void EnSw_Update(Actor* thisx, PlayState* play) { EnSw* this = (EnSw*)thisx; + // #region SOH [Co-op] + // Check to see if this gold skull token has already been retrieved. + if (GET_GS_FLAGS((thisx->params & 0x1F00) >> 8) & (thisx->params & 0xFF)) { + Actor_Kill(&this->actor); + return; + } + // #endregion + SkelAnime_Update(&this->skelAnime); func_80B0C9F0(this, play); this->actionFunc(this, play); diff --git a/soh/src/overlays/actors/ovl_Item_B_Heart/z_item_b_heart.c b/soh/src/overlays/actors/ovl_Item_B_Heart/z_item_b_heart.c index 35f6d5457b7..3093f65b5fb 100644 --- a/soh/src/overlays/actors/ovl_Item_B_Heart/z_item_b_heart.c +++ b/soh/src/overlays/actors/ovl_Item_B_Heart/z_item_b_heart.c @@ -53,6 +53,13 @@ void ItemBHeart_Destroy(Actor* thisx, PlayState* play) { void ItemBHeart_Update(Actor* thisx, PlayState* play) { ItemBHeart* this = (ItemBHeart*)thisx; + // #region SOH [Co-op] + if (Flags_GetCollectible(play, 0x1F)) { + Actor_Kill(&this->actor); + return; + } + // #endregion + func_80B85264(this, play); Actor_UpdateBgCheckInfo(play, &this->actor, 0.0f, 0.0f, 0.0f, 4); if (Actor_HasParent(&this->actor, play)) { diff --git a/soh/src/overlays/actors/ovl_Obj_Bombiwa/z_obj_bombiwa.c b/soh/src/overlays/actors/ovl_Obj_Bombiwa/z_obj_bombiwa.c index b300b68534b..558b52bbd91 100644 --- a/soh/src/overlays/actors/ovl_Obj_Bombiwa/z_obj_bombiwa.c +++ b/soh/src/overlays/actors/ovl_Obj_Bombiwa/z_obj_bombiwa.c @@ -126,7 +126,7 @@ void ObjBombiwa_Update(Actor* thisx, PlayState* play) { s32 pad; if ((func_80033684(play, &this->actor) != NULL) || - ((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040))) { + ((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040)) || Flags_GetSwitch(play, this->actor.params & 0x3F)) { ObjBombiwa_Break(this, play); Flags_SetSwitch(play, this->actor.params & 0x3F); SoundSource_PlaySfxAtFixedWorldPos(play, &this->actor.world.pos, 80, NA_SE_EV_WALL_BROKEN); diff --git a/soh/src/overlays/actors/ovl_Obj_Hamishi/z_obj_hamishi.c b/soh/src/overlays/actors/ovl_Obj_Hamishi/z_obj_hamishi.c index e17cf0d8019..40bfc569103 100644 --- a/soh/src/overlays/actors/ovl_Obj_Hamishi/z_obj_hamishi.c +++ b/soh/src/overlays/actors/ovl_Obj_Hamishi/z_obj_hamishi.c @@ -171,10 +171,10 @@ void ObjHamishi_Update(Actor* thisx, PlayState* play) { ObjHamishi_Shake(this); - if ((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040)) { + if (((this->collider.base.acFlags & AC_HIT) && (this->collider.info.acHitInfo->toucher.dmgFlags & 0x40000040)) || Flags_GetSwitch(play, this->actor.params & 0x3F)) { this->collider.base.acFlags &= ~AC_HIT; this->hitCount++; - if (this->hitCount < 2) { + if (this->hitCount < 2 && !Flags_GetSwitch(play, this->actor.params & 0x3F)) { this->shakeFrames = 15; this->shakePosSize = 2.0f; this->shakeRotSize = 400.0f; 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 5b467f57547..e108a7e3329 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -29,6 +29,9 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/randomizer_grotto.h" #include "soh/frame_interpolation.h" +#ifdef ENABLE_REMOTE_CONTROL +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #include #include @@ -5110,6 +5113,9 @@ s32 func_8083B040(Player* this, PlayState* play) { ((this->exchangeItemId != EXCH_ITEM_BEAN) || (this->itemAction == PLAYER_IA_BEAN))) { if (this->exchangeItemId == EXCH_ITEM_BEAN) { Inventory_ChangeAmmo(ITEM_BEAN, -1); +#ifdef ENABLE_REMOTE_CONTROL + Anchor_UpdateBeansCount(AMMO(ITEM_BEAN)); +#endif func_80835DE4(play, this, func_8084279C, 0); this->stateFlags1 |= PLAYER_STATE1_IN_CUTSCENE; this->unk_850 = 0x50;