- 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
247 lines
9.8 KiB
C++
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) {
|
|
}
|