Files
Shiip-of-Hakinian-Espanol/soh/soh/Network/Anchor/DummyPlayer.cpp
Garrett Cox 9cd31099e2 Additional Anchor functionality: (#5999)
- Returned support for custom tunic colors
- Ocarina playback now audible
- Fixed movement translation issue when climbing or going through crawlspaces
- Fixed issue preventing some items from being visible in Dummy hands (namely ocarina)
- Fixed stick length not correctly syncing
2025-11-30 18:17:00 -07:00

247 lines
9.8 KiB
C++

#include "Anchor.h"
#include "soh/Enhancements/nametag.h"
#include "soh/frame_interpolation.h"
extern "C" {
#include "macros.h"
#include "variables.h"
#include "functions.h"
extern PlayState* gPlayState;
void Player_UseItem(PlayState* play, Player* player, s32 item);
void Player_Draw(Actor* actor, PlayState* play);
}
static DamageTable DummyPlayerDamageTable = {
/* Deku nut */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_STUN),
/* Deku stick */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Slingshot */ DMG_ENTRY(1, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Explosive */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Boomerang */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_STUN),
/* Normal arrow */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Hammer swing */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_KNOCKBACK_LARGE),
/* Hookshot */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_STUN),
/* Kokiri sword */ DMG_ENTRY(1, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Master sword */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Giant's Knife */ DMG_ENTRY(4, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Fire arrow */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_FIRE),
/* Ice arrow */ DMG_ENTRY(4, PLAYER_HIT_RESPONSE_ICE_TRAP),
/* Light arrow */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_ELECTRIC_SHOCK),
/* Unk arrow 1 */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_NONE),
/* Unk arrow 2 */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_NONE),
/* Unk arrow 3 */ DMG_ENTRY(2, PLAYER_HIT_RESPONSE_NONE),
/* Fire magic */ DMG_ENTRY(0, DUMMY_PLAYER_HIT_RESPONSE_FIRE),
/* Ice magic */ DMG_ENTRY(3, PLAYER_HIT_RESPONSE_ICE_TRAP),
/* Light magic */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_ELECTRIC_SHOCK),
/* Shield */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
/* Mirror Ray */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
/* Kokiri spin */ DMG_ENTRY(1, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Giant spin */ DMG_ENTRY(4, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Master spin */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Kokiri jump */ DMG_ENTRY(2, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Giant jump */ DMG_ENTRY(8, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Master jump */ DMG_ENTRY(4, DUMMY_PLAYER_HIT_RESPONSE_NORMAL),
/* Unknown 1 */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
/* Unblockable */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
/* Hammer jump */ DMG_ENTRY(4, PLAYER_HIT_RESPONSE_KNOCKBACK_LARGE),
/* Unknown 2 */ DMG_ENTRY(0, PLAYER_HIT_RESPONSE_NONE),
};
void DummyPlayer_Init(Actor* actor, PlayState* play) {
Player* player = (Player*)actor;
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (!Anchor::Instance->clients.contains(clientId)) {
Actor_Kill(actor);
return;
}
AnchorClient& client = Anchor::Instance->clients[clientId];
// Hack to account for usage of gSaveContext in Player_Init
s32 originalAge = gSaveContext.linkAge;
gSaveContext.linkAge = client.linkAge;
// #region modeled after EnTorch2_Init and Player_Init
actor->room = -1;
player->itemAction = player->heldItemAction = -1;
player->heldItemId = ITEM_NONE;
Player_UseItem(play, player, ITEM_NONE);
Player_SetModelGroup(player, Player_ActionToModelGroup(player, player->heldItemAction));
play->playerInit(player, play, gPlayerSkelHeaders[client.linkAge]);
play->func_11D54(player, play);
// #endregion
player->cylinder.base.acFlags = AC_ON | AC_TYPE_PLAYER;
player->cylinder.base.ocFlags2 = OC2_TYPE_1;
player->cylinder.info.bumperFlags = BUMP_ON | BUMP_HOOKABLE | BUMP_NO_HITMARK;
player->actor.flags |= ACTOR_FLAG_HOOKSHOT_PULLS_PLAYER;
player->cylinder.dim.radius = 30;
player->actor.colChkInfo.damageTable = &DummyPlayerDamageTable;
gSaveContext.linkAge = originalAge;
bool isGlobalRoom = (std::string("soh-global") == CVarGetString(CVAR_REMOTE_ANCHOR("RoomId"), ""));
if (!isGlobalRoom) {
NameTag_RegisterForActorWithOptions(actor, client.name.c_str(), {});
}
}
void Math_Vec3s_Copy(Vec3s* dest, Vec3s* src) {
dest->x = src->x;
dest->y = src->y;
dest->z = src->z;
}
// Update the actor with new data from the client
void DummyPlayer_Update(Actor* actor, PlayState* play) {
Player* player = (Player*)actor;
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (!Anchor::Instance->clients.contains(clientId)) {
Actor_Kill(actor);
return;
}
AnchorClient& client = Anchor::Instance->clients[clientId];
if (client.sceneNum != gPlayState->sceneNum || !client.online || !client.isSaveLoaded) {
actor->world.pos.x = -9999.0f;
actor->world.pos.y = -9999.0f;
actor->world.pos.z = -9999.0f;
actor->shape.shadowAlpha = 0;
return;
}
actor->shape.shadowAlpha = 255;
Math_Vec3s_Copy(&player->upperLimbRot, &client.upperLimbRot);
Math_Vec3s_Copy(&actor->shape.rot, &client.posRot.rot);
Math_Vec3f_Copy(&actor->world.pos, &client.posRot.pos);
player->skelAnime.jointTable = client.jointTable;
player->skelAnime.movementFlags = client.movementFlags;
Math_Vec3s_Copy(&player->skelAnime.prevTransl, &client.prevTransl);
player->currentBoots = client.currentBoots;
player->currentShield = client.currentShield;
player->currentTunic = client.currentTunic;
player->stateFlags1 = client.stateFlags1;
player->stateFlags2 = client.stateFlags2;
player->itemAction = client.itemAction;
player->heldItemAction = client.heldItemAction;
player->invincibilityTimer = client.invincibilityTimer;
player->unk_862 = client.unk_862;
player->unk_85C = client.unk_85C;
player->av1.actionVar1 = client.actionVar1;
// Apply animation movement (Copied from Player_ApplyAnimMovementScaledByAge)
Vec3f diff;
SkelAnime_UpdateTranslation(&player->skelAnime, &diff, player->actor.shape.rot.y);
if (player->skelAnime.movementFlags & 1) {
if (!LINK_IS_ADULT) {
diff.x *= 0.64f;
diff.z *= 0.64f;
}
player->actor.world.pos.x += diff.x * player->actor.scale.x;
player->actor.world.pos.z += diff.z * player->actor.scale.z;
}
if (player->skelAnime.movementFlags & 2) {
if (!(player->skelAnime.movementFlags & 4)) {
diff.y *= player->ageProperties->unk_08;
}
player->actor.world.pos.y += diff.y * player->actor.scale.y;
}
if (player->modelGroup != Player_ActionToModelGroup(player, player->itemAction)) {
// Hack to account for usage of gSaveContext
s32 originalAge = gSaveContext.linkAge;
gSaveContext.linkAge = client.linkAge;
u8 originalButtonItem0 = gSaveContext.equips.buttonItems[0];
gSaveContext.equips.buttonItems[0] = client.buttonItem0;
Player_SetModelGroup(player, Player_ActionToModelGroup(player, player->itemAction));
gSaveContext.linkAge = originalAge;
gSaveContext.equips.buttonItems[0] = originalButtonItem0;
}
if (Anchor::Instance->roomState.pvpMode == 0 ||
(Anchor::Instance->roomState.pvpMode == 1 &&
client.teamId == CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default"))) {
actor->flags |= ACTOR_FLAG_LOCK_ON_DISABLED;
return;
}
actor->flags &= ~ACTOR_FLAG_LOCK_ON_DISABLED;
if (player->cylinder.base.acFlags & AC_HIT && player->invincibilityTimer == 0) {
Anchor::Instance->SendPacket_DamagePlayer(client.clientId, player->actor.colChkInfo.damageEffect,
player->actor.colChkInfo.damage);
if (player->actor.colChkInfo.damageEffect == DUMMY_PLAYER_HIT_RESPONSE_STUN) {
Actor_SetColorFilter(&player->actor, 0, 0xFF, 0, 24);
} else {
player->invincibilityTimer = 20;
}
}
Collider_UpdateCylinder(&player->actor, &player->cylinder);
if (!(player->stateFlags2 & PLAYER_STATE2_FROZEN)) {
if (!(player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_HANGING_OFF_LEDGE |
PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_ON_HORSE))) {
CollisionCheck_SetOC(play, &play->colChkCtx, &player->cylinder.base);
}
if (!(player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_DAMAGED)) &&
(player->invincibilityTimer <= 0)) {
CollisionCheck_SetAC(play, &play->colChkCtx, &player->cylinder.base);
if (player->invincibilityTimer < 0) {
CollisionCheck_SetAT(play, &play->colChkCtx, &player->cylinder.base);
}
}
}
if (player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS | PLAYER_STATE1_IN_CUTSCENE)) {
player->actor.colChkInfo.mass = MASS_IMMOVABLE;
} else {
player->actor.colChkInfo.mass = 50;
}
Collider_ResetCylinderAC(play, &player->cylinder.base);
}
void DummyPlayer_Draw(Actor* actor, PlayState* play) {
Player* player = (Player*)actor;
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (!Anchor::Instance->clients.contains(clientId)) {
Actor_Kill(actor);
return;
}
AnchorClient& client = Anchor::Instance->clients[clientId];
if (client.sceneNum != gPlayState->sceneNum || !client.online || !client.isSaveLoaded) {
return;
}
// Hack to account for usage of gSaveContext in Player_Draw
s32 originalAge = gSaveContext.linkAge;
gSaveContext.linkAge = client.linkAge;
u8 originalButtonItem0 = gSaveContext.equips.buttonItems[0];
gSaveContext.equips.buttonItems[0] = client.buttonItem0;
Player_Draw((Actor*)player, play);
gSaveContext.linkAge = originalAge;
gSaveContext.equips.buttonItems[0] = originalButtonItem0;
}
void DummyPlayer_Destroy(Actor* actor, PlayState* play) {
}