From 0d4b8fc640b9a25c9cb6d9f1cc0ebfdc34968bea Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 15 Nov 2023 10:40:14 -0600 Subject: [PATCH] Anchor + Player Models 3 (alpha 5) Co-authored-by: PurpleHato Co-authored-by: MelonSpeedruns Co-authored-by: Sirius902 <10891979+Sirius902@users.noreply.github.com> --- soh/include/functions.h | 5 + soh/include/z64save.h | 43 +++ soh/soh/ActorDB.cpp | 21 + .../game-interactor/GameInteractor_Anchor.cpp | 361 +++++++++++++++++- .../game-interactor/GameInteractor_Anchor.h | 8 + soh/soh/Enhancements/nametag.cpp | 2 +- soh/soh/SohMenuBar.cpp | 8 +- soh/src/code/z_actor.c | 4 + soh/src/code/z_map_exp.c | 6 +- soh/src/code/z_play.c | 1 + soh/src/code/z_player_lib.c | 219 ++++++++++- .../z_bg_hidan_firewall.c | 4 +- .../actors/ovl_Link_Puppet/z_link_puppet.c | 238 ++++++++++++ .../actors/ovl_Link_Puppet/z_link_puppet.h | 45 +++ .../actors/ovl_player_actor/z_player.c | 8 + 15 files changed, 949 insertions(+), 24 deletions(-) create mode 100644 soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.c create mode 100644 soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.h diff --git a/soh/include/functions.h b/soh/include/functions.h index fb9346adccd..fdcd59820d0 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1124,6 +1124,7 @@ s32 func_8008EF44(PlayState* play, s32 ammo); s32 Player_IsBurningStickInRange(PlayState* play, Vec3f* pos, f32 radius, f32 arg3); s32 Player_GetStrength(void); u8 Player_GetMask(PlayState* play); +s32 Player_InflictDamage(PlayState* play, s32 damage); Player* Player_UnsetMask(PlayState* play); s32 Player_HasMirrorShieldEquipped(PlayState* play); s32 Player_HasMirrorShieldSetToDraw(PlayState* play); @@ -1148,6 +1149,10 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx** s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* data); s32 Player_OverrideLimbDrawGameplayFirstPerson(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* data); s32 Player_OverrideLimbDrawGameplayCrawling(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* data); +void DrawAnchorPuppet(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, s32 lod, s32 tunic, + s32 boots, s32 face, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, + void* this, PlayerData playerData, s32 anchorActorIndex); +s32 PuppetOverrideDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* data); u8 func_80090480(PlayState* play, ColliderQuad* collider, WeaponInfo* weaponDim, Vec3f* newTip, Vec3f* newBase); void Player_DrawGetItem(PlayState* play, Player* player); diff --git a/soh/include/z64save.h b/soh/include/z64save.h index c7bee045fc1..2a884310a31 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -166,6 +166,45 @@ typedef struct { u8 value; } RandoSetting; +typedef struct { + s32 playerAge; + u16 playerSound; + u8 sheathType; + u8 leftHandType; + u8 biggoron_broken; + u8 rightHandType; + u8 tunicType; + u8 bootsType; + s16 faceType; + u8 shieldType; + u8 damageEffect; + u8 damageValue; + s16 playerHealth; + s16 playerHealthCapacity; + s16 playerMagic; + s16 playerMagicCapacity; + s16 isPlayerMagicAcquired; + s16 isDoubleMagicAcquired; + s32 strengthValue; + f32 yOffset; + u8 currentMask; + u8 swordEquipped; + u32 playerStateFlags1; + u8 moveFlags; + f32 unk_6C4; + s16 unk_00; + s16 unk_02; + s16 unk_04; + s16 unk_06; + s16 unk_08; + f32 speedXZ; + s8 itemAction; + f32 unk_85C; + Vec3f stickWeaponTip; + s16 unk_860; + s8 unk_862; +} PlayerData; + typedef struct { /* 0x0000 */ s32 entranceIndex; // start of `save` substruct, originally called "memory" /* 0x0004 */ s32 linkAge; // 0: Adult; 1: Child (see enum `LinkAge`) @@ -325,6 +364,10 @@ typedef struct { /* */ u16 adultTradeItems; /* */ u8 triforcePiecesCollected; // #endregion + // #region SOH [Network] + // Upstream TODO: Move these to their own struct or name to more obviously specific to Network + /* */ PlayerData playerData; + // #endregion } SaveContext; // size = 0x1428 typedef enum { diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index a66048b322f..35efd4c68ec 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -604,8 +604,29 @@ static ActorDBInit EnPartnerInit = { }; extern "C" s16 gEnPartnerId; +#ifdef ENABLE_REMOTE_CONTROL +#include "src/overlays/actors/ovl_Link_Puppet/z_link_puppet.h" +static ActorDBInit EnLinkPuppetInit = { + "En_Link_Puppet", + "Puppet", + ACTORCAT_ITEMACTION, + (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED), + OBJECT_LINK_BOY, + sizeof(LinkPuppet), + (ActorFunc)LinkPuppet_Init, + (ActorFunc)LinkPuppet_Destroy, + (ActorFunc)LinkPuppet_Update, + (ActorFunc)LinkPuppet_Draw, + nullptr, +}; +#endif +extern "C" s16 gEnLinkPuppetId; + void ActorDB::AddBuiltInCustomActors() { gEnPartnerId = ActorDB::Instance->AddEntry(EnPartnerInit).entry.id; +#ifdef ENABLE_REMOTE_CONTROL + gEnLinkPuppetId = ActorDB::Instance->AddEntry(EnLinkPuppetInit).entry.id; +#endif } extern "C" ActorDBEntry* ActorDB_Retrieve(const int id) { diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp index 256de9b8d96..a80a71f0880 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -18,7 +19,7 @@ extern "C" { #include "z64scene.h" #include "z64actor.h" #include "functions.h" -extern "C" s16 gEnPartnerId; +extern "C" s16 gEnLinkPuppetId; extern PlayState* gPlayState; extern SaveContext gSaveContext; } @@ -39,6 +40,90 @@ void to_json(json& j, const Color_RGB8& color) { }; } +void from_json(const json& j, PlayerData& playerData) { + j.at("playerAge").get_to(playerData.playerAge); + j.at("playerSound").get_to(playerData.playerSound); + j.at("sheathType").get_to(playerData.sheathType); + j.at("leftHandType").get_to(playerData.leftHandType); + j.at("biggoron_broken").get_to(playerData.biggoron_broken); + j.at("rightHandType").get_to(playerData.rightHandType); + j.at("tunicType").get_to(playerData.tunicType); + j.at("bootsType").get_to(playerData.bootsType); + j.at("faceType").get_to(playerData.faceType); + j.at("shieldType").get_to(playerData.shieldType); + j.at("damageEffect").get_to(playerData.damageEffect); + j.at("damageValue").get_to(playerData.damageValue); + j.at("playerHealth").get_to(playerData.playerHealth); + j.at("playerHealthCapacity").get_to(playerData.playerHealthCapacity); + j.at("playerMagic").get_to(playerData.playerMagic); + j.at("playerMagicCapacity").get_to(playerData.playerMagicCapacity); + j.at("isPlayerMagicAcquired").get_to(playerData.isPlayerMagicAcquired); + j.at("isDoubleMagicAcquired").get_to(playerData.isDoubleMagicAcquired); + j.at("strengthValue").get_to(playerData.strengthValue); + j.at("yOffset").get_to(playerData.yOffset); + j.at("currentMask").get_to(playerData.currentMask); + j.at("swordEquipped").get_to(playerData.swordEquipped); + j.at("playerStateFlags1").get_to(playerData.playerStateFlags1); + j.at("moveFlags").get_to(playerData.moveFlags); + j.at("unk_6C4").get_to(playerData.unk_6C4); + j.at("unk_00").get_to(playerData.unk_00); + j.at("unk_02").get_to(playerData.unk_02); + j.at("unk_04").get_to(playerData.unk_04); + j.at("unk_06").get_to(playerData.unk_06); + j.at("unk_08").get_to(playerData.unk_08); + j.at("speedXZ").get_to(playerData.speedXZ); + j.at("itemAction").get_to(playerData.itemAction); + j.at("unk_85C").get_to(playerData.unk_85C); + j.at("stickWeaponTipX").get_to(playerData.stickWeaponTip.x); + j.at("stickWeaponTipY").get_to(playerData.stickWeaponTip.y); + j.at("stickWeaponTipZ").get_to(playerData.stickWeaponTip.z); + j.at("unk_860").get_to(playerData.unk_860); + j.at("unk_862").get_to(playerData.unk_862); +} + +void to_json(json& j, const PlayerData& playerData) { + j = json{ + { "playerAge", playerData.playerAge }, + { "playerSound", playerData.playerSound }, + { "sheathType", playerData.sheathType }, + { "leftHandType", playerData.leftHandType }, + { "biggoron_broken", playerData.biggoron_broken }, + { "rightHandType", playerData.rightHandType }, + { "tunicType", playerData.tunicType }, + { "bootsType", playerData.bootsType }, + { "faceType", playerData.faceType }, + { "shieldType", playerData.shieldType }, + { "damageEffect", playerData.damageEffect }, + { "damageValue", playerData.damageValue }, + { "playerHealth", playerData.playerHealth }, + { "playerHealthCapacity", playerData.playerHealthCapacity }, + { "playerMagic", playerData.playerMagic }, + { "playerMagicCapacity", playerData.playerMagicCapacity }, + { "isPlayerMagicAcquired", playerData.isPlayerMagicAcquired }, + { "isDoubleMagicAcquired", playerData.isDoubleMagicAcquired }, + { "strengthValue", playerData.strengthValue }, + { "yOffset", playerData.yOffset }, + { "currentMask", playerData.currentMask }, + { "swordEquipped", playerData.swordEquipped }, + { "playerStateFlags1", playerData.playerStateFlags1 }, + { "moveFlags", playerData.moveFlags }, + { "unk_6C4", playerData.unk_6C4 }, + { "unk_00", playerData.unk_00 }, + { "unk_02", playerData.unk_02 }, + { "unk_04", playerData.unk_04 }, + { "unk_06", playerData.unk_06 }, + { "unk_08", playerData.unk_08 }, + { "speedXZ", playerData.speedXZ }, + { "itemAction", playerData.itemAction }, + { "unk_85C", playerData.unk_85C }, + { "stickWeaponTipX", playerData.stickWeaponTip.x }, + { "stickWeaponTipY", playerData.stickWeaponTip.y }, + { "stickWeaponTipZ", playerData.stickWeaponTip.z }, + { "unk_860", playerData.unk_860 }, + { "unk_862", playerData.unk_862 }, + }; +} + void to_json(json& j, const Vec3f& vec) { j = json{ {"x", vec.x}, @@ -91,6 +176,7 @@ void from_json(const json& j, AnchorClient& client) { 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 }; + j.contains("playerData") ? j.at("playerData").get_to(client.playerData) : client.playerData = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; } void to_json(json& j, const SavedSceneFlags& flags) { @@ -195,7 +281,7 @@ void from_json(const json& j, SaveContext& saveContext) { std::map GameInteractorAnchor::AnchorClients = {}; std::vector GameInteractorAnchor::ActorIndexToClientId = {}; -std::string GameInteractorAnchor::clientVersion = "Anchor Build 12 (alpha 5)"; +std::string GameInteractorAnchor::clientVersion = "Anchor + Player Models 3 (alpha 5)"; std::vector> receivedItems = {}; std::vector discoveredEntrances = {}; std::vector anchorMessages = {}; @@ -263,6 +349,7 @@ void GameInteractorAnchor::Disable() { GameInteractor::Instance->DisableRemoteInteractor(); GameInteractorAnchor::AnchorClients.clear(); + // TODO: This crashes in player model build for some reason Anchor_RefreshClientActors(); } @@ -361,6 +448,42 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) { effect->parameters[1] = payload["flag"].get(); effect->Apply(); } + if (payload["type"] == "DAMAGE_PLAYER") { + if (payload["damageEffect"] > 0 && GET_PLAYER(gPlayState)->invincibilityTimer <= 0 && + !Player_InBlockingCsMode(gPlayState, GET_PLAYER(gPlayState))) { + if (payload["damageEffect"] == PUPPET_DMGEFF_NORMAL) { + u8 damage = payload["damageValue"]; + Player_InflictDamage(gPlayState, damage * -4); + func_80837C0C(gPlayState, GET_PLAYER(gPlayState), 0, 0, 0, 0, 0); + GET_PLAYER(gPlayState)->invincibilityTimer = 18; + GET_PLAYER(gPlayState)->actor.freezeTimer = 0; + } else if (payload["damageEffect"] == PUPPET_DMGEFF_ICE) { + GET_PLAYER(gPlayState)->stateFlags1 &= ~(PLAYER_STATE1_GETTING_ITEM | PLAYER_STATE1_ITEM_OVER_HEAD); + func_80837C0C(gPlayState, GET_PLAYER(gPlayState), 3, 0.0f, 0.0f, 0, 20); + GET_PLAYER(gPlayState)->invincibilityTimer = 18; + GET_PLAYER(gPlayState)->actor.freezeTimer = 0; + } else if (payload["damageEffect"] == PUPPET_DMGEFF_FIRE) { + for (int i = 0; i < 18; i++) { + GET_PLAYER(gPlayState)->flameTimers[i] = Rand_S16Offset(0, 200); + } + GET_PLAYER(gPlayState)->isBurning = true; + func_80837C0C(gPlayState, GET_PLAYER(gPlayState), 0, 0, 0, 0, 0); + GET_PLAYER(gPlayState)->invincibilityTimer = 18; + GET_PLAYER(gPlayState)->actor.freezeTimer = 0; + } else if (payload["damageEffect"] == PUPPET_DMGEFF_THUNDER) { + func_80837C0C(gPlayState, GET_PLAYER(gPlayState), 4, 0.0f, 0.0f, 0, 20); + GET_PLAYER(gPlayState)->invincibilityTimer = 18; + GET_PLAYER(gPlayState)->actor.freezeTimer = 0; + } else if (payload["damageEffect"] == PUPPET_DMGEFF_KNOCKBACK) { + func_8002F71C(gPlayState, &GET_PLAYER(gPlayState)->actor, 100.0f * 0.04f + 4.0f, GET_PLAYER(gPlayState)->actor.world.rot.y, 8.0f); + GET_PLAYER(gPlayState)->invincibilityTimer = 28; + GET_PLAYER(gPlayState)->actor.freezeTimer = 0; + } else if (payload["damageEffect"] == PUPPET_DMGEFF_STUN) { + GET_PLAYER(gPlayState)->actor.freezeTimer = 20; + Actor_SetColorFilter(&GET_PLAYER(gPlayState)->actor, 0, 0xFF, 0, 10); + } + } + } if (payload["type"] == "CLIENT_UPDATE") { uint32_t clientId = payload["clientId"].get(); @@ -369,6 +492,15 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) { 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.contains("playerData")) { + GameInteractorAnchor::AnchorClients[clientId].playerData = payload["playerData"].get(); + } + if (payload.contains("jointTable")) { + std::vector jointTable = payload["jointTable"].get>(); + for (int i = 0; i < 23; i++) { + GameInteractorAnchor::AnchorClients[clientId].jointTable[i] = jointTable[i]; + } + } } } if (payload["type"] == "PUSH_SAVE_STATE" && GameInteractor::IsSaveLoaded()) { @@ -394,7 +526,9 @@ void GameInteractorAnchor::HandleRemoteJson(nlohmann::json payload) { client.sceneNum, 0, client.entranceIndex, - { -9999, -9999, -9999, 0, 0, 0 } + { -9999, -9999, -9999, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + {}, }; Anchor_DisplayMessage({ .prefix = client.name, @@ -631,6 +765,27 @@ PosRot Anchor_GetClientPosition(uint32_t actorIndex) { return client->posRot; } +PlayerData Anchor_GetClientPlayerData(uint32_t actorIndex) { + AnchorClient* client = Anchor_GetClientByActorIndex(actorIndex); + if (client == nullptr) return { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + return client->playerData; +} + +Vec3s* Anchor_GetClientJointTable(uint32_t actorIndex) { + AnchorClient* client = Anchor_GetClientByActorIndex(actorIndex); + if (client == nullptr) return {}; + + return client->jointTable; +} + +const char* Anchor_GetClientName(uint32_t actorIndex) { + AnchorClient* client = Anchor_GetClientByActorIndex(actorIndex); + if (client == nullptr) return ""; + + return client->name.c_str(); +} + uint8_t Anchor_GetClientRoomIndex(uint32_t actorIndex) { AnchorClient* client = Anchor_GetClientByActorIndex(actorIndex); if (client == nullptr) return 0xFF; @@ -649,7 +804,7 @@ void Anchor_RefreshClientActors() { if (!GameInteractor::IsSaveLoaded()) return; Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ITEMACTION].head; while (actor != NULL) { - if (gEnPartnerId == actor->id) { + if (gEnLinkPuppetId == actor->id) { Actor_Kill(actor); } actor = actor->next; @@ -661,12 +816,13 @@ void Anchor_RefreshClientActors() { for (auto [clientId, client] : GameInteractorAnchor::AnchorClients) { GameInteractorAnchor::ActorIndexToClientId.push_back(clientId); auto fairy = Actor_Spawn( - &gPlayState->actorCtx, gPlayState, gEnPartnerId, + &gPlayState->actorCtx, gPlayState, gEnLinkPuppetId, client.posRot.pos.x, client.posRot.pos.y, client.posRot.pos.z, client.posRot.rot.x, client.posRot.rot.y, client.posRot.rot.z, 3 + i, false ); - NameTag_RegisterForActor(fairy, client.name.c_str()); + // Todo: This was removed in player models branch + // NameTag_RegisterForActor(fairy, client.name.c_str()); i++; } } @@ -779,7 +935,6 @@ void Anchor_RegisterHooks() { GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); }); GameInteractor::Instance->RegisterGameHook([]() { - static uint32_t lastPlayerCount = 0; uint32_t currentPlayerCount = 0; for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) { if (client.sceneNum == gPlayState->sceneNum) { @@ -787,25 +942,60 @@ void Anchor_RegisterHooks() { } } 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; + if (currentPlayerCount == 0) return; + + gSaveContext.playerData.bootsType = player->currentBoots; + gSaveContext.playerData.shieldType = player->currentShield; + gSaveContext.playerData.sheathType = player->sheathType; + gSaveContext.playerData.leftHandType = player->leftHandType; + gSaveContext.playerData.rightHandType = player->rightHandType; + gSaveContext.playerData.tunicType = player->currentTunic; + gSaveContext.playerData.faceType = player->actor.shape.face; + gSaveContext.playerData.biggoron_broken = gSaveContext.swordHealth <= 0 ? 1 : 0; + gSaveContext.playerData.playerAge = gSaveContext.linkAge; + gSaveContext.playerData.playerHealth = gSaveContext.health; + gSaveContext.playerData.playerHealthCapacity = gSaveContext.healthCapacity; + gSaveContext.playerData.playerMagic = gSaveContext.magic; + gSaveContext.playerData.playerMagicCapacity = gSaveContext.magicCapacity; + gSaveContext.playerData.isPlayerMagicAcquired = gSaveContext.isMagicAcquired; + gSaveContext.playerData.isDoubleMagicAcquired = gSaveContext.isDoubleMagicAcquired; + gSaveContext.playerData.strengthValue = CUR_UPG_VALUE(UPG_STRENGTH); + gSaveContext.playerData.yOffset = player->actor.shape.yOffset; + gSaveContext.playerData.currentMask = player->currentMask; + gSaveContext.playerData.swordEquipped = gSaveContext.equips.buttonItems[0]; + gSaveContext.playerData.playerStateFlags1 = player->stateFlags1; + gSaveContext.playerData.moveFlags = player->skelAnime.moveFlags; + gSaveContext.playerData.unk_6C4 = player->unk_6C4; + gSaveContext.playerData.speedXZ = player->actor.speedXZ; + gSaveContext.playerData.itemAction = player->itemAction; + gSaveContext.playerData.unk_85C = player->unk_85C; + gSaveContext.playerData.stickWeaponTip = player->meleeWeaponInfo[0].tip; + gSaveContext.playerData.unk_860 = player->unk_860; + gSaveContext.playerData.unk_862 = player->unk_862; + + payload["playerData"] = gSaveContext.playerData; 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; + PosRot playerPosRot; + playerPosRot.pos = player->actor.world.pos; + playerPosRot.rot = player->actor.shape.rot; + payload["posRot"] = playerPosRot; + + std::vector jointTable = {}; + for (int i = 0; i < 23; i++) { + jointTable.push_back(player->skelAnime.jointTable[i]); + } + + payload["jointTable"] = jointTable; + payload["quiet"] = true; for (auto& [clientId, client] : GameInteractorAnchor::AnchorClients) { if (client.sceneNum == gPlayState->sceneNum) { @@ -813,6 +1003,10 @@ void Anchor_RegisterHooks() { GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); } } + + gSaveContext.playerData.damageEffect = 0; + gSaveContext.playerData.damageValue = 0; + gSaveContext.playerData.playerSound = 0; }); } @@ -906,6 +1100,21 @@ void Anchor_GiveDungeonItem(int16_t sceneNum, uint16_t itemId) { GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); } +void Anchor_DamagePlayer(uint32_t actorIndex, u8 damageEffect, u8 damageValue) { + if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded() || + actorIndex >= GameInteractorAnchor::ActorIndexToClientId.size()) return; + + uint32_t clientId = GameInteractorAnchor::ActorIndexToClientId[actorIndex]; + nlohmann::json payload; + + payload["type"] = "DAMAGE_PLAYER"; + payload["damageEffect"] = damageEffect; + payload["damageValue"] = damageValue; + payload["targetClientId"] = clientId; + + GameInteractorAnchor::Instance->TransmitJsonToRemote(payload); +} + void Anchor_GameComplete() { if (!GameInteractor::Instance->isRemoteInteractorConnected || !GameInteractor::Instance->IsSaveLoaded()) return; @@ -947,6 +1156,123 @@ const ImVec4 GRAY = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); const ImVec4 WHITE = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); const ImVec4 GREEN = ImVec4(0.5f, 1.0f, 0.5f, 1.0f); +bool heartTexturesLoaded = false; + +bool IsValidSave() { + bool validSave = gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2; + return validSave; +} + +const char* heartTextureNames[16] = { + "Heart_Full", "Heart_One_Fourth", "Heart_One_Fourth", "Heart_One_Fourth", + "Heart_One_Fourth", "Heart_One_Fourth", "Heart_Half", "Heart_Half", + "Heart_Half", "Heart_Half", "Heart_Half", "Heart_Three_Fourths", + "Heart_Three_Fourths", "Heart_Three_Fourths", "Heart_Three_Fourths", "Heart_Three_Fourths", +}; + +void DisplayLifeMeter(AnchorClient& client) { + int currentHealth = client.playerData.playerHealth; + int maxHealth = client.playerData.playerHealthCapacity; + int currentMagic = client.playerData.playerMagic; + int maxMagic = client.playerData.playerMagicCapacity; + + int fullHearts = currentHealth / 16; + int partialHealth = currentHealth % 16; + + const ImVec4 normalHeartsColor = ImVec4(1, 0.275f, 0.118f, 1); + + int numMaxHearts = maxHealth / 16; + + int numLines = (numMaxHearts / 10) + 1; + + if (!heartTexturesLoaded) { + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture( + "Heart_Full", "textures/parameter_static/gHeartFullTex", normalHeartsColor); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture( + "Heart_Three_Fourths", "textures/parameter_static/gHeartThreeQuarterTex", normalHeartsColor); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture( + "Heart_Half", "textures/parameter_static/gHeartHalfTex", normalHeartsColor); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture( + "Heart_One_Fourth", "textures/parameter_static/gHeartQuarterTex", normalHeartsColor); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture( + "Heart_Empty", "textures/parameter_static/gHeartEmptyTex", normalHeartsColor); + heartTexturesLoaded = true; + } + + if (CVarGetInteger("gAnchorPlayerHealth", 0) == 1 || CVarGetInteger("gAnchorPlayerHealth", 0) == 3) { + std::string healthInfo = "Life: " + std::to_string(currentHealth) + " / " + std::to_string(maxHealth); + ImGui::Text(healthInfo.c_str()); + if (client.playerData.isPlayerMagicAcquired || client.playerData.isDoubleMagicAcquired) { + std::string magichInfo = " | Magic: " + std::to_string(currentMagic) + " / " + std::to_string(maxMagic); + ImGui::SameLine(); + ImGui::Text(magichInfo.c_str()); + } + if (CVarGetInteger("gAnchorPlayerHealth", 0) == 1) { + ImGui::Separator(); + } + } + + if (CVarGetInteger("gAnchorPlayerHealth", 0) == 2 || CVarGetInteger("gAnchorPlayerHealth", 0) == 3) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1, 0)); + + ImVec2 imageSize(16, 16); + + for (int line = 0; line < numLines; line++) { + for (int i = 0; i < 10; i++) { + int heartIndex = line * 10 + i; + + if (heartIndex >= numMaxHearts) { + break; + } + + if (i > 0) { + ImGui::SameLine(); + } + + if (heartIndex < fullHearts) { + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Heart_Full"), imageSize); + } else if (heartIndex == fullHearts) { + if (currentHealth == 0) { + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Heart_Empty"), imageSize); + } else { + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(heartTextureNames[partialHealth]), imageSize); + } + } else { + ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("Heart_Empty"), imageSize); + } + } + + if (line < numLines - 1) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2)); + } + } + + if (client.playerData.isPlayerMagicAcquired) { + ImGui::Spacing(); + ImVec4 magicBarColor = ImVec4(0, 1, 0, 1); + float magicBarItemWidth = 168.0f; + float magicBarItemHeight = 6.0f; + const char* label = ""; + + if (!client.playerData.isDoubleMagicAcquired) { + magicBarItemWidth /= 2; + } + + float currentMagicRatio = static_cast(currentMagic) / maxMagic; + + ImGui::BeginGroup(); + + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, magicBarColor); + ImGui::ProgressBar(currentMagicRatio, ImVec2(magicBarItemWidth, magicBarItemHeight), label); + ImGui::PopStyleColor(); + ImGui::EndGroup(); + } + + ImGui::PopStyleVar(); + ImGui::Separator(); + } +} + void AnchorPlayerLocationWindow::DrawElement() { ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0.5f)); @@ -1004,6 +1330,9 @@ void AnchorPlayerLocationWindow::DrawElement() { } ImGui::PopStyleVar(); } + if (IsValidSave() && CVarGetInteger("gAnchorPlayerHealth", 0) != 0) { + DisplayLifeMeter(client); + } } ImGui::PopID(); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h index 9be4ba62f52..5180c3741be 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Anchor.h @@ -1,10 +1,12 @@ #ifdef ENABLE_REMOTE_CONTROL #include "z64actor.h" +#include "z64save.h" #include #ifdef __cplusplus #include "z64item.h" +#include "src/overlays/actors/ovl_Link_Puppet/z_link_puppet.h" #include "./GameInteractor.h" typedef struct { @@ -19,6 +21,8 @@ typedef struct { uint8_t roomIndex; uint32_t entranceIndex; PosRot posRot; + PlayerData playerData; + Vec3s jointTable[23]; } AnchorClient; class GameInteractorAnchor { @@ -80,7 +84,10 @@ void Anchor_PushSaveStateToRemote(); void Anchor_RequestSaveStateFromRemote(); uint8_t Anchor_GetClientScene(uint32_t actorIndex); PosRot Anchor_GetClientPosition(uint32_t actorIndex); +const char* Anchor_GetClientName(uint32_t actorIndex); uint8_t Anchor_GetClientRoomIndex(uint32_t actorIndex); +Vec3s* Anchor_GetClientJointTable(uint32_t actorIndex); +PlayerData Anchor_GetClientPlayerData(uint32_t actorIndex); Color_RGB8 Anchor_GetClientColor(uint32_t actorIndex); void Anchor_RefreshClientActors(); void Anchor_EntranceDiscovered(uint16_t entranceIndex); @@ -90,6 +97,7 @@ void Anchor_UpdateBeansCount(uint8_t amount); void Anchor_ConsumeAdultTradeItem(uint8_t itemId); void Anchor_UpdateKeyCount(int16_t sceneNum, int8_t amount); void Anchor_GiveDungeonItem(int16_t sceneNum, uint16_t itemId); +void Anchor_DamagePlayer(uint32_t actorIndex, u8 damageEffect, u8 damageValue); void Anchor_GameComplete(); void Anchor_RequestTeleport(uint32_t clientId); void Anchor_TeleportToPlayer(uint32_t clientId); diff --git a/soh/soh/Enhancements/nametag.cpp b/soh/soh/Enhancements/nametag.cpp index 038ba4e1f65..429fd256fb6 100644 --- a/soh/soh/Enhancements/nametag.cpp +++ b/soh/soh/Enhancements/nametag.cpp @@ -59,7 +59,7 @@ void DrawNameTag(PlayState* play, const NameTag* nameTag) { alpha = (200000.0f - nameTag->actor->xyzDistToPlayerSq) / 40000.0f; } - float scale = 75.0f / 100.f; + float scale = 75.0f / 150.f; size_t numChar = nameTag->processedText.length(); // No text to render diff --git a/soh/soh/SohMenuBar.cpp b/soh/soh/SohMenuBar.cpp index 5dbb99e680c..c653e06963c 100644 --- a/soh/soh/SohMenuBar.cpp +++ b/soh/soh/SohMenuBar.cpp @@ -1494,6 +1494,8 @@ bool isStringEmpty(std::string str) { } #ifdef ENABLE_REMOTE_CONTROL +static const char* anchorPlayerHealth[4] = { "Disabled", "Numeric", "Hearts and Magic", "Numeric + Hearts and Magic" }; + void DrawRemoteControlMenu() { if (ImGui::BeginMenu("Network")) { static std::string ip = CVarGetString("gRemote.IP", "127.0.0.1"); @@ -1545,7 +1547,7 @@ void DrawRemoteControlMenu() { } ImGui::PopItemWidth(); } else { - ImGui::Text("Fairy Color & Name"); + ImGui::Text("Tunic 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)) { @@ -1572,6 +1574,10 @@ void DrawRemoteControlMenu() { } ImGui::EndDisabled(); + // Todo: Crashes / Spacing issues + // ImGui::Spacing(); + // ImGui::Text("Display Player Health"); + // UIWidgets::EnhancementCombobox("gAnchorPlayerHealth", anchorPlayerHealth, 0); ImGui::Spacing(); ImGui::BeginDisabled(!isFormValid); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 79a3c8ace21..ca938702cc2 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2181,6 +2181,10 @@ void func_8002F7A0(PlayState* play, Actor* actor, f32 arg2, s16 arg3, f32 arg4) } void Player_PlaySfx(Actor* actor, u16 sfxId) { + if (actor->id == ACTOR_PLAYER) { + gSaveContext.playerData.playerSound = sfxId; + } + if (actor->id != ACTOR_PLAYER || sfxId < NA_SE_VO_LI_SWORD_N || sfxId > NA_SE_VO_LI_ELECTRIC_SHOCK_LV_KID) { Audio_PlaySoundGeneral(sfxId, &actor->projectedPos, 4, &D_801333E0 , &D_801333E0, &D_801333E8); } else { diff --git a/soh/src/code/z_map_exp.c b/soh/src/code/z_map_exp.c index c581329a06f..944aeb33dd7 100644 --- a/soh/src/code/z_map_exp.c +++ b/soh/src/code/z_map_exp.c @@ -606,7 +606,7 @@ void Map_Init(PlayState* play) { } } -extern s16 gEnPartnerId; +extern s16 gEnLinkPuppetId; void Minimap_DrawCompassIcons(PlayState* play) { s32 pad; @@ -735,7 +735,9 @@ void Minimap_DrawCompassIcons(PlayState* play) { // Other Anchor Players Arrow Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ITEMACTION].head; while (actor != NULL) { - if (gEnPartnerId == actor->id && Anchor_GetClientRoomIndex(actor->params - 3) == gPlayState->roomCtx.curRoom.num) { + if (gEnLinkPuppetId == actor->id && + (Anchor_GetClientRoomIndex(actor->params - 3) == gPlayState->roomCtx.curRoom.num || + (play->sceneNum >= SCENE_HYRULE_FIELD && play->sceneNum <= SCENE_OUTSIDE_GANONS_CASTLE))) { if (actor->world.pos.x != -9999.0 && Anchor_GetClientScene(actor->params - 3) == gPlayState->sceneNum) { Color_RGB8 playerColor = Anchor_GetClientColor(actor->params - 3); diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 59a5d0619c1..18ce173f804 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -36,6 +36,7 @@ PlayState* gPlayState; s16 firstInit = 0; s16 gEnPartnerId; +s16 gEnLinkPuppetId; void OTRPlay_SpawnScene(PlayState* play, s32 sceneNum, s32 spawn); diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 820f2c67267..3a30b87ec39 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -5,9 +5,14 @@ #include "objects/object_link_child/object_link_child.h" #include "objects/object_triforce_spot/object_triforce_spot.h" #include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h" - -#include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/randomizer/draw.h" +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/frame_interpolation.h" + +#ifdef ENABLE_REMOTE_CONTROL +#include "overlays/actors/ovl_Link_Puppet/z_link_puppet.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#endif #include @@ -1083,6 +1088,135 @@ void Player_DrawImpl(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dL CLOSE_DISPS(play->state.gfxCtx); } +static Gfx* sMaskDlists[PLAYER_MASK_MAX - 1] = { + gLinkChildKeatonMaskDL, gLinkChildSkullMaskDL, gLinkChildSpookyMaskDL, gLinkChildBunnyHoodDL, + gLinkChildGoronMaskDL, gLinkChildZoraMaskDL, gLinkChildGerudoMaskDL, gLinkChildMaskOfTruthDL, +}; + +#ifdef ENABLE_REMOTE_CONTROL +void DrawAnchorPuppet(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dListCount, s32 lod, s32 tunic, + s32 boots, s32 face, OverrideLimbDrawOpa overrideLimbDraw, PostLimbDrawOpa postLimbDraw, + void* data, PlayerData playerData, s32 anchorActorIndex) { + LinkPuppet* puppet = (LinkPuppet*)data; + Color_RGB8* color; + + s32 eyeIndex = (jointTable[22].x & 0xF) - 1; + s32 mouthIndex = (jointTable[22].x >> 4) - 1; + + OPEN_DISPS(play->state.gfxCtx); + + if (eyeIndex < 0) { + eyeIndex = sEyeMouthIndexes[face][0]; + } + + if (eyeIndex > 7) + eyeIndex = 7; + +#if defined(MODDING) || defined(_MSC_VER) || defined(__GNUC__) + gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[playerData.playerAge][eyeIndex])); +#else + gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(sEyeTextures[eyeIndex])); +#endif + if (mouthIndex < 0) { + mouthIndex = sEyeMouthIndexes[face][1]; + } + + if (mouthIndex > 3) + mouthIndex = 3; + +#if defined(MODDING) || defined(_MSC_VER) || defined(__GNUC__) + gSPSegment(POLY_OPA_DISP++, 0x09, SEGMENTED_TO_VIRTUAL(sMouthTextures[playerData.playerAge][mouthIndex])); +#else + gSPSegment(POLY_OPA_DISP++, 0x09, SEGMENTED_TO_VIRTUAL(sMouthTextures[eyeIndex])); +#endif + + Color_RGB8 sTemp; + color = &sTunicColors[tunic]; + + Color_RGB8 clientColor = Anchor_GetClientColor(anchorActorIndex); + color = &clientColor; + + gDPSetEnvColor(POLY_OPA_DISP++, color->r, color->g, color->b, 0); + + Gfx_SetupDL_25Opa(play->state.gfxCtx); + + gSPSegment(POLY_OPA_DISP++, 0x0C, gCullBackDList); + + SkelAnime_DrawFlexLod(play, skeleton, jointTable, dListCount, overrideLimbDraw, postLimbDraw, data, lod); + + if (playerData.playerAge == LINK_AGE_ADULT) { + s32 strengthUpgrade = playerData.strengthValue; + + if (strengthUpgrade >= 2) { // silver or gold gauntlets + gDPPipeSync(POLY_OPA_DISP++); + + color = &sGauntletColors[strengthUpgrade - 2]; + if (strengthUpgrade == PLAYER_STR_SILVER_G && + CVarGetInteger("gCosmetics.Gloves_SilverGauntlets.Changed", 0)) { + sTemp = + CVarGetColor24("gCosmetics.Gloves_SilverGauntlets.Value", sGauntletColors[PLAYER_STR_SILVER_G - 2]); + color = &sTemp; + } else if (strengthUpgrade == PLAYER_STR_GOLD_G && + CVarGetInteger("gCosmetics.Gloves_GoldenGauntlets.Changed", 0)) { + sTemp = + CVarGetColor24("gCosmetics.Gloves_GoldenGauntlets.Value", sGauntletColors[PLAYER_STR_GOLD_G - 2]); + color = &sTemp; + } + gDPSetEnvColor(POLY_OPA_DISP++, color->r, color->g, color->b, 0); + + gSPDisplayList(POLY_OPA_DISP++, gLinkAdultLeftGauntletPlate1DL); + gSPDisplayList(POLY_OPA_DISP++, gLinkAdultRightGauntletPlate1DL); + gSPDisplayList(POLY_OPA_DISP++, + (playerData.leftHandType == 0) ? gLinkAdultLeftGauntletPlate2DL : gLinkAdultLeftGauntletPlate3DL); + gSPDisplayList(POLY_OPA_DISP++, + (playerData.rightHandType == 8) ? gLinkAdultRightGauntletPlate2DL : gLinkAdultRightGauntletPlate3DL); + } + + if (boots != 0) { + Gfx** bootDLists = sBootDListGroups[boots - 1]; + + gSPDisplayList(POLY_OPA_DISP++, bootDLists[0]); + gSPDisplayList(POLY_OPA_DISP++, bootDLists[1]); + } + } else { + if (playerData.strengthValue > PLAYER_STR_NONE) { + gSPDisplayList(POLY_OPA_DISP++, gLinkChildGoronBraceletDL); + } + } + + if (playerData.currentMask != PLAYER_MASK_NONE) { + if (playerData.currentMask == PLAYER_MASK_BUNNY) { + PlayerData playerData = Anchor_GetClientPlayerData(puppet->actor.params - 3); + + Mtx* sp70 = Graph_Alloc(play->state.gfxCtx, 2 * sizeof(Mtx)); + + Vec3s sp68; + + FrameInterpolation_RecordActorPosRotMatrix(); + gSPSegment(POLY_OPA_DISP++, 0x0B, sp70); + + sp68.x = playerData.unk_02 + 0x3E2; + sp68.y = playerData.unk_04 + 0xDBE; + sp68.z = playerData.unk_00 - 0x348A; + Matrix_SetTranslateRotateYXZ(97.0f, -1203.0f - CVarGetFloat("gCosmetics.BunnyHood_EarLength", 0.0f), + -240.0f - CVarGetFloat("gCosmetics.BunnyHood_EarSpread", 0.0f), &sp68); + MATRIX_TOMTX(sp70++); + + sp68.x = playerData.unk_02 - 0x3E2; + sp68.y = -0xDBE - playerData.unk_04; + sp68.z = playerData.unk_00 - 0x348A; + Matrix_SetTranslateRotateYXZ(97.0f, -1203.0f - CVarGetFloat("gCosmetics.BunnyHood_EarLength", 0.0f), + 240.0f + CVarGetFloat("gCosmetics.BunnyHood_EarSpread", 0.0f), &sp68); + MATRIX_TOMTX(sp70); + } + + gSPDisplayList(POLY_OPA_DISP++, sMaskDlists[playerData.currentMask - 1]); + } + + CLOSE_DISPS(play->state.gfxCtx); +} +#endif + Vec3f sZeroVec = { 0.0f, 0.0f, 0.0f }; Vec3f D_80126038[] = { @@ -1271,6 +1405,85 @@ s32 Player_OverrideLimbDrawGameplayCommon(PlayState* play, s32 limbIndex, Gfx** return false; } +#ifdef ENABLE_REMOTE_CONTROL +s32 PuppetOverrideDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx) { + LinkPuppet* this = (LinkPuppet*)thisx; + + static Vec3f headPosLocal = { 2000.0f, 0.0f, -300.0f }; + + PlayerData playerData = Anchor_GetClientPlayerData(this->actor.params - 3); + + if (limbIndex == PLAYER_LIMB_ROOT) { + if (playerData.playerAge != LINK_AGE_ADULT) { + if (!(playerData.moveFlags & 4) || (playerData.moveFlags & 1)) { + pos->x *= 0.64f; + pos->z *= 0.64f; + } + + if (!(playerData.moveFlags & 4) || (playerData.moveFlags & 2)) { + pos->y *= 0.64f; + } + } + + pos->y -= playerData.unk_6C4; + } else if (limbIndex == PLAYER_LIMB_HEAD) { + Matrix_MultVec3f(&headPosLocal, &this->actor.focus.pos); + } else if (limbIndex == PLAYER_LIMB_L_HAND) { + Matrix_MultVec3f(&headPosLocal, &this->leftHandPos); + + Gfx** dLists = &sPlayerDListGroups[playerData.leftHandType][(void)0, playerData.playerAge]; + + if ((playerData.leftHandType == 4) && (playerData.biggoron_broken == 1)) { + dLists += 4; + } else if ((playerData.leftHandType == 6) && (playerData.playerStateFlags1 & 0x2000000)) { + dLists = &gPlayerLeftHandOpenDLs[playerData.playerAge]; + playerData.leftHandType = 0; + } else if ((playerData.leftHandType == 0) && (playerData.speedXZ > 2.0f) && + !(playerData.playerStateFlags1 & 0x8000000)) { + dLists = &gPlayerLeftHandClosedDLs[playerData.playerAge]; + playerData.leftHandType = 1; + } + + *dList = ResourceMgr_LoadGfxByName(dLists[sDListsLodOffset]); + } else if (limbIndex == PLAYER_LIMB_R_HAND) { + if (playerData.rightHandType == 0xFF) return false; + Gfx** dLists = &sPlayerDListGroups[playerData.rightHandType][(void)0, playerData.playerAge]; + + if (playerData.rightHandType == 10) { + dLists += playerData.shieldType * 4; + } else if ((playerData.rightHandType == 8) && (playerData.speedXZ > 2.0f) && + !(playerData.playerStateFlags1 & 0x8000000)) { + dLists = &sPlayerRightHandClosedDLs[playerData.playerAge]; + playerData.rightHandType = 9; + } + + *dList = ResourceMgr_LoadGfxByName(dLists[sDListsLodOffset]); + } else if (limbIndex == PLAYER_LIMB_SHEATH) { + Gfx** dLists = &sPlayerDListGroups[playerData.sheathType][(void)0, playerData.playerAge]; + + if ((playerData.sheathType == 18) || (playerData.sheathType == 19)) { + dLists += playerData.shieldType * 4; + if (playerData.playerAge != LINK_AGE_ADULT && (playerData.shieldType < PLAYER_SHIELD_HYLIAN) && + (playerData.swordEquipped != ITEM_SWORD_KOKIRI)) { + dLists += 16; + } + } else if (playerData.playerAge != LINK_AGE_ADULT && + ((playerData.sheathType == 16) || (playerData.sheathType == 17)) && + (playerData.swordEquipped != ITEM_SWORD_KOKIRI)) { + dLists = &sSheathWithSwordDLs[16]; + } + + if (dLists[sDListsLodOffset] != NULL) { + *dList = ResourceMgr_LoadGfxByName(dLists[sDListsLodOffset]); + } else { + *dList = NULL; + } + } + + return false; +} +#endif + s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx) { Player* this = (Player*)thisx; @@ -1332,10 +1545,12 @@ s32 Player_OverrideLimbDrawGameplayDefault(PlayState* play, s32 limbIndex, Gfx** } } + #ifdef ENABLE_REMOTE_CONTROL if (GameInteractor_InvisibleLinkActive()) { this->actor.shape.shadowDraw = NULL; *dList = NULL; } + #endif return false; } diff --git a/soh/src/overlays/actors/ovl_Bg_Hidan_Firewall/z_bg_hidan_firewall.c b/soh/src/overlays/actors/ovl_Bg_Hidan_Firewall/z_bg_hidan_firewall.c index 3350ea560fe..ec0cb918b1f 100644 --- a/soh/src/overlays/actors/ovl_Bg_Hidan_Firewall/z_bg_hidan_firewall.c +++ b/soh/src/overlays/actors/ovl_Bg_Hidan_Firewall/z_bg_hidan_firewall.c @@ -25,7 +25,7 @@ void BgHidanFirewall_Erupt(BgHidanFirewall* this, PlayState* play); void BgHidanFirewall_Collide(BgHidanFirewall* this, PlayState* play); void BgHidanFirewall_ColliderFollowPlayer(BgHidanFirewall* this, PlayState* play); -extern s16 gEnPartnerId; +extern s16 gEnLinkPuppetId; const ActorInit Bg_Hidan_Firewall_InitVars = { ACTOR_BG_HIDAN_FIREWALL, @@ -97,7 +97,7 @@ s32 BgHidanFirewall_CheckProximity(BgHidanFirewall* this, PlayState* play) { #ifdef ENABLE_REMOTE_CONTROL Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_ITEMACTION].head; while (actor != NULL) { - if (gEnPartnerId == actor->id && Anchor_GetClientRoomIndex(actor->params - 3) == gPlayState->roomCtx.curRoom.num && Anchor_GetClientScene(actor->params - 3) == gPlayState->sceneNum) { + if (gEnLinkPuppetId == actor->id && Anchor_GetClientRoomIndex(actor->params - 3) == gPlayState->roomCtx.curRoom.num && Anchor_GetClientScene(actor->params - 3) == gPlayState->sceneNum) { Vec3f actorDistance; func_8002DBD0(&this->actor, &actorDistance, &actor->world.pos); if (fabsf(actorDistance.x) < 100.0f && fabsf(actorDistance.z) < 120.0f) { diff --git a/soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.c b/soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.c new file mode 100644 index 00000000000..204fdef8235 --- /dev/null +++ b/soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.c @@ -0,0 +1,238 @@ +#ifdef ENABLE_REMOTE_CONTROL + +#include "vt.h" +#include "z_link_puppet.h" +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Anchor.h" +#include +#include + +#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED) + +void LinkPuppet_Init(Actor* thisx, PlayState* play); +void LinkPuppet_Destroy(Actor* thisx, PlayState* play); +void LinkPuppet_Update(Actor* thisx, PlayState* play); +void LinkPuppet_Draw(Actor* thisx, PlayState* play); + +static ColliderCylinderInit sCylinderInit = { + { + COLTYPE_HIT5, + AT_NONE, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_ALL, + OC2_TYPE_1, + COLSHAPE_CYLINDER, + }, + { + ELEMTYPE_UNK1, + { 0xFFCFFFFF, 0x00, 0x00 }, + { 0xFFCFFFFF, 0x00, 0x00 }, + TOUCH_NONE, + BUMP_ON, + OCELEM_ON, + }, + { 15, 50, 0, { 0, 0, 0 } }, +}; + +static DamageTable sDamageTable[] = { + /* Deku nut */ DMG_ENTRY(0, PUPPET_DMGEFF_STUN), + /* Deku stick */ DMG_ENTRY(2, PUPPET_DMGEFF_NORMAL), + /* Slingshot */ DMG_ENTRY(1, PUPPET_DMGEFF_NORMAL), + /* Explosive */ DMG_ENTRY(2, PUPPET_DMGEFF_NORMAL), + /* Boomerang */ DMG_ENTRY(0, PUPPET_DMGEFF_STUN), + /* Normal arrow */ DMG_ENTRY(2, PUPPET_DMGEFF_NORMAL), + /* Hammer swing */ DMG_ENTRY(2, PUPPET_DMGEFF_KNOCKBACK), + /* Hookshot */ DMG_ENTRY(0, PUPPET_DMGEFF_STUN), + /* Kokiri sword */ DMG_ENTRY(1, PUPPET_DMGEFF_NORMAL), + /* Master sword */ DMG_ENTRY(2, PUPPET_DMGEFF_NORMAL), + /* Giant's Knife */ DMG_ENTRY(4, PUPPET_DMGEFF_NORMAL), + /* Fire arrow */ DMG_ENTRY(2, PUPPET_DMGEFF_FIRE), + /* Ice arrow */ DMG_ENTRY(2, PUPPET_DMGEFF_ICE), + /* Light arrow */ DMG_ENTRY(2, PUPPET_DMGEFF_THUNDER), + /* Unk arrow 1 */ DMG_ENTRY(2, PUPPET_DMGEFF_NONE), + /* Unk arrow 2 */ DMG_ENTRY(2, PUPPET_DMGEFF_NONE), + /* Unk arrow 3 */ DMG_ENTRY(2, PUPPET_DMGEFF_NONE), + /* Fire magic */ DMG_ENTRY(2, PUPPET_DMGEFF_FIRE), + /* Ice magic */ DMG_ENTRY(0, PUPPET_DMGEFF_ICE), + /* Light magic */ DMG_ENTRY(3, PUPPET_DMGEFF_THUNDER), + /* Shield */ DMG_ENTRY(0, PUPPET_DMGEFF_NONE), + /* Mirror Ray */ DMG_ENTRY(0, PUPPET_DMGEFF_NONE), + /* Kokiri spin */ DMG_ENTRY(1, PUPPET_DMGEFF_NORMAL), + /* Giant spin */ DMG_ENTRY(4, PUPPET_DMGEFF_NORMAL), + /* Master spin */ DMG_ENTRY(2, PUPPET_DMGEFF_NORMAL), + /* Kokiri jump */ DMG_ENTRY(2, PUPPET_DMGEFF_NORMAL), + /* Giant jump */ DMG_ENTRY(8, PUPPET_DMGEFF_NORMAL), + /* Master jump */ DMG_ENTRY(4, PUPPET_DMGEFF_NORMAL), + /* Unknown 1 */ DMG_ENTRY(0, PUPPET_DMGEFF_NONE), + /* Unblockable */ DMG_ENTRY(0, PUPPET_DMGEFF_NONE), + /* Hammer jump */ DMG_ENTRY(4, PUPPET_DMGEFF_KNOCKBACK), + /* Unknown 2 */ DMG_ENTRY(0, PUPPET_DMGEFF_NONE), +}; + +void LinkPuppet_Init(Actor* thisx, PlayState* play) { + LinkPuppet* this = (LinkPuppet*)thisx; + + this->actor.room = -1; + this->actor.targetMode = 1; + + s32 playerAge = Anchor_GetClientPlayerData(this->actor.params - 3).playerAge; + + this->puppetAge = playerAge; + + SkelAnime_InitLink(play, &this->linkSkeleton, gPlayerSkelHeaders[((void)0, playerAge)], + gPlayerAnim_link_normal_wait, 9, this->linkSkeleton.jointTable, this->linkSkeleton.morphTable, + PLAYER_LIMB_MAX); + + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawFeet, 90.0f); + + Collider_InitCylinder(play, &this->collider); + Collider_SetCylinder(play, &this->collider, &this->actor, &sCylinderInit); + + this->actor.colChkInfo.damageTable = sDamageTable; + + PlayerData playerData = Anchor_GetClientPlayerData(this->actor.params - 3); + + NameTag_RemoveAllForActor(thisx); + + Color_RGB8 clientColor = Anchor_GetClientColor(this->actor.params - 3); + const char* playerName = Anchor_GetClientName(this->actor.params - 3); + this->nameTagOptions.yOffset = 0; + NameTag_RegisterForActorWithOptions(&this->actor, playerName, this->nameTagOptions); +} + +void LinkPuppet_Destroy(Actor* thisx, PlayState* play) { + LinkPuppet* this = (LinkPuppet*)thisx; + + Collider_DestroyCylinder(play, &this->collider); +} + +void LinkPuppet_Update(Actor* thisx, PlayState* play) { + LinkPuppet* this = (LinkPuppet*)thisx; + + PlayerData playerData = Anchor_GetClientPlayerData(this->actor.params - 3); + + if (this->puppetAge != playerData.playerAge) { + LinkPuppet_Init(this, play); + return; + } + + this->actor.shape.yOffset = playerData.yOffset; + + if (this->damageTimer > 0) { + this->damageTimer--; + } + + if (this->collider.base.acFlags & AC_HIT && this->damageTimer <= 0) { + this->collider.base.acFlags &= ~AC_HIT; + gSaveContext.playerData.damageEffect = this->actor.colChkInfo.damageEffect; + gSaveContext.playerData.damageValue = this->actor.colChkInfo.damage; + Anchor_DamagePlayer(this->actor.params - 3, this->actor.colChkInfo.damageEffect, this->actor.colChkInfo.damage); + + if (gSaveContext.playerData.damageEffect == PUPPET_DMGEFF_STUN) { + Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_JR_FREEZE); + Actor_SetColorFilter(&this->actor, 0, 0xFF, 0, 40); + } else if (gSaveContext.playerData.damageEffect != PUPPET_DMGEFF_NONE) { + Actor_SetColorFilter(&this->actor, 0x4000, 255, 0, 24); + } + + this->damageTimer = 18; + } + + Collider_UpdateCylinder(thisx, &this->collider); + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); + + if (this->actor.params >= 3) { + if (Anchor_GetClientScene(this->actor.params - 3) == play->sceneNum) { + PosRot playerPosRot = Anchor_GetClientPosition(this->actor.params - 3); + this->actor.world.pos = playerPosRot.pos; + this->actor.shape.rot = playerPosRot.rot; + } else { + this->actor.world.pos.x = -9999.0f; + this->actor.world.pos.y = -9999.0f; + this->actor.world.pos.z = -9999.0f; + } + } + + Vec3s* jointTable = Anchor_GetClientJointTable(this->actor.params - 3); + + this->linkSkeleton.jointTable = jointTable; + + if (playerData.playerSound != 0) { + Audio_PlaySoundGeneral(playerData.playerSound, &this->actor.projectedPos, 4, &D_801333E0, &D_801333E0, + &D_801333E8); + } +} + +Vec3f FEET_POS[] = { + { 200.0f, 300.0f, 0.0f }, + { 200.0f, 200.0f, 0.0f }, +}; + +Vec3f D_808547A4 = { 0.0f, 0.5f, 0.0f }; +Vec3f D_808547B0 = { 0.0f, 0.5f, 0.0f }; + +Color_RGBA8 D_808547BC = { 255, 255, 100, 255 }; +Color_RGBA8 D_808547C0 = { 255, 50, 0, 0 }; + +void Puppet_PostLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) { + LinkPuppet* this = (LinkPuppet*)thisx; + + PlayerData playerData = Anchor_GetClientPlayerData(this->actor.params - 3); + + Vec3f* vec = &FEET_POS[((void)0, playerData.playerAge)]; + Actor_SetFeetPos(&this->actor, limbIndex, PLAYER_LIMB_L_FOOT, vec, PLAYER_LIMB_R_FOOT, vec); + + if (limbIndex == PLAYER_LIMB_L_HAND && playerData.itemAction == PLAYER_IA_DEKU_STICK) { + OPEN_DISPS(play->state.gfxCtx); + + Matrix_Translate(-428.26f, 267.2f, -33.82f, MTXMODE_APPLY); + Matrix_RotateZYX(-0x8000, 0, 0x4000, MTXMODE_APPLY); + Matrix_Scale(1.0f, playerData.unk_85C, 1.0f, MTXMODE_APPLY); + + gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_OPA_DISP++, gLinkChildLinkDekuStickDL); + + CLOSE_DISPS(play->state.gfxCtx); + + if (playerData.unk_860 != 0) { + f32 temp = 1.0f; + + if (playerData.unk_860 > 200) { + temp = (210 - playerData.unk_860) / 10.0f; + } else if (playerData.unk_860 < 20) { + temp = playerData.unk_860 / 20.0f; + } + + func_8002836C(play, &playerData.stickWeaponTip, &D_808547A4, &D_808547B0, &D_808547BC, &D_808547C0, + temp * 200.0f, 0, 8); + } + } +} + +void LinkPuppet_Draw(Actor* thisx, PlayState* play) { + LinkPuppet* this = (LinkPuppet*)thisx; + + PlayerData playerData = Anchor_GetClientPlayerData(this->actor.params - 3); + + if (this->puppetAge == playerData.playerAge) { + DrawAnchorPuppet(play, this->linkSkeleton.skeleton, this->linkSkeleton.jointTable, + this->linkSkeleton.dListCount, 0, playerData.tunicType, playerData.bootsType, + playerData.faceType, PuppetOverrideDraw, Puppet_PostLimbDraw, this, playerData, this->actor.params - 3); + + if ((playerData.unk_862 - 1) > -1) { + OPEN_DISPS(play->state.gfxCtx); + + Matrix_Translate(this->leftHandPos.x + (3.3f * Math_SinS(this->actor.shape.rot.y)), this->leftHandPos.y, + this->leftHandPos.z + ((3.3f + (IREG(90) / 10.0f)) * Math_CosS(this->actor.shape.rot.y)), + MTXMODE_NEW); + Matrix_RotateZYX(0, play->gameplayFrames * 1000, 0, MTXMODE_APPLY); + Matrix_Scale(0.2f, 0.2f, 0.2f, MTXMODE_APPLY); + + GetItem_Draw(play, ABS(playerData.unk_862 - 1)); + + CLOSE_DISPS(play->state.gfxCtx); + } + } +} +#endif \ No newline at end of file diff --git a/soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.h b/soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.h new file mode 100644 index 00000000000..caefcb1485e --- /dev/null +++ b/soh/src/overlays/actors/ovl_Link_Puppet/z_link_puppet.h @@ -0,0 +1,45 @@ +#ifdef ENABLE_REMOTE_CONTROL +#ifndef Z_LINK_PUPPET_H +#define Z_LINK_PUPPET_H + +#include +#include "soh/Enhancements/nametag.h" +#include "global.h" + +struct LinkPuppet; + +typedef struct LinkPuppet { + Actor actor; + SkelAnime linkSkeleton; + ColliderCylinder collider; + ColliderQuad swordQuads[2]; + ColliderQuad shieldQuad; + uint8_t damageTimer; + uint8_t puppetAge; + NameTagOptions nameTagOptions; + Vec3f leftHandPos; +} LinkPuppet; + +typedef enum { + PUPPET_DMGEFF_NONE, + PUPPET_DMGEFF_NORMAL, + PUPPET_DMGEFF_ICE, + PUPPET_DMGEFF_FIRE, + PUPPET_DMGEFF_THUNDER, + PUPPET_DMGEFF_KNOCKBACK, + PUPPET_DMGEFF_STUN, +} PuppetDamageEffect; + +#ifdef __cplusplus +extern "C" { +#endif +void LinkPuppet_Init(Actor* thisx, PlayState* play); +void LinkPuppet_Destroy(Actor* thisx, PlayState* play); +void LinkPuppet_Update(Actor* thisx, PlayState* play); +void LinkPuppet_Draw(Actor* thisx, PlayState* play); +#ifdef __cplusplus +} +#endif + +#endif +#endif 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 ad14cbcd260..365ba434fbc 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -13791,6 +13791,14 @@ void Player_UpdateBunnyEars(Player* this) { } else { D_80858AC8.unk_04 = 0; } + + #ifdef ENABLE_REMOTE_CONTROL + gSaveContext.playerData.unk_00 = D_80858AC8.unk_00; + gSaveContext.playerData.unk_02 = D_80858AC8.unk_02; + gSaveContext.playerData.unk_04 = D_80858AC8.unk_04; + gSaveContext.playerData.unk_06 = D_80858AC8.unk_06; + gSaveContext.playerData.unk_08 = D_80858AC8.unk_08; + #endif } s32 func_80850224(Player* this, PlayState* play) {