#include "framework.h" #include "Game/animation.h" #include "Game/camera.h" #include "Game/control/flipeffect.h" #include "Game/collision/collide_room.h" #include "Game/items.h" #include "Game/Lara/lara.h" #include "Game/Lara/lara_helpers.h" #include "Objects/Generic/Object/rope.h" #include "Renderer/Renderer11.h" #include "Sound/sound.h" #include "Specific/level.h" #include "Specific/setup.h" using namespace TEN::Entities::Generic; using TEN::Renderer::g_Renderer; BOUNDING_BOX InterpolatedBounds; void AnimateLara(ITEM_INFO* item) { auto* lara = GetLaraInfo(item); item->Animation.FrameNumber++; auto* anim = &g_Level.Anims[item->Animation.AnimNumber]; if (anim->numberChanges > 0 && GetChange(item, anim)) { anim = &g_Level.Anims[item->Animation.AnimNumber]; item->Animation.ActiveState = anim->ActiveState; } if (item->Animation.FrameNumber > anim->frameEnd) { if (anim->numberCommands > 0) { short* cmd = &g_Level.Commands[anim->commandIndex]; for (int i = anim->numberCommands; i > 0; i--) { switch (*(cmd++)) { case COMMAND_MOVE_ORIGIN: TranslateItem(item, cmd[0], cmd[1], cmd[2]); UpdateItemRoom(item, -LARA_HEIGHT / 2, -cmd[0], -cmd[2]); cmd += 3; break; case COMMAND_JUMP_VELOCITY: item->Animation.VerticalVelocity = *(cmd++); item->Animation.Velocity = *(cmd++); item->Animation.Airborne = true; if (lara->Control.CalculatedJumpVelocity) { item->Animation.VerticalVelocity = lara->Control.CalculatedJumpVelocity; lara->Control.CalculatedJumpVelocity = 0; } break; case COMMAND_ATTACK_READY: if (lara->Control.HandStatus != HandStatus::Special) lara->Control.HandStatus = HandStatus::Free; break; case COMMAND_SOUND_FX: case COMMAND_EFFECT: cmd += 2; break; default: break; } } } item->Animation.AnimNumber = anim->jumpAnimNum; item->Animation.FrameNumber = anim->jumpFrameNum; anim = &g_Level.Anims[item->Animation.AnimNumber]; item->Animation.ActiveState = anim->ActiveState; } if (anim->numberCommands > 0) { short* cmd = &g_Level.Commands[anim->commandIndex]; int flags; int effectID = 0; for (int i = anim->numberCommands; i > 0; i--) { switch (*(cmd++)) { case COMMAND_MOVE_ORIGIN: cmd += 3; break; case COMMAND_JUMP_VELOCITY: cmd += 2; break; case COMMAND_SOUND_FX: if (item->Animation.FrameNumber != *cmd) { cmd += 2; break; } flags = cmd[1] & 0xC000; if (flags == (int)SOUND_PLAYCONDITION::LandAndWater || (flags == (int)SOUND_PLAYCONDITION::Land && (lara->WaterSurfaceDist >= 0 || lara->WaterSurfaceDist == NO_HEIGHT)) || (flags == (int)SOUND_PLAYCONDITION::Water && lara->WaterSurfaceDist < 0 && lara->WaterSurfaceDist != NO_HEIGHT && !TestEnvironment(ENV_FLAG_SWAMP, item))) { SoundEffect(cmd[1] & 0x3FFF, &item->Position, 2); } cmd += 2; break; case COMMAND_EFFECT: if (item->Animation.FrameNumber != *cmd) { cmd += 2; break; } effectID = cmd[1] & 0x3FFF; DoFlipEffect(effectID, item); cmd += 2; break; default: break; } } } int lateral = anim->Xvelocity; if (anim->Xacceleration) lateral += anim->Xacceleration * (item->Animation.FrameNumber - anim->frameBase); item->Animation.LateralVelocity = lateral >>= 16; if (item->Animation.Airborne) { if (TestEnvironment(ENV_FLAG_SWAMP, item)) { item->Animation.Velocity -= item->Animation.Velocity >> 3; if (abs(item->Animation.Velocity) < 8) { item->Animation.Velocity = 0; item->Animation.Airborne = false; } if (item->Animation.VerticalVelocity > 128) item->Animation.VerticalVelocity /= 2; item->Animation.VerticalVelocity -= item->Animation.VerticalVelocity / 4; if (item->Animation.VerticalVelocity < 4) item->Animation.VerticalVelocity = 4; item->Position.yPos += item->Animation.VerticalVelocity; } else { int velocity = (anim->velocity + anim->acceleration * (item->Animation.FrameNumber - anim->frameBase - 1)); item->Animation.Velocity -= velocity >> 16; item->Animation.Velocity += (velocity + anim->acceleration) >> 16; item->Animation.VerticalVelocity += (item->Animation.VerticalVelocity >= 128 ? 1 : GRAVITY); item->Position.yPos += item->Animation.VerticalVelocity; } } else { int velocity; if (lara->Control.WaterStatus == WaterStatus::Wade && TestEnvironment(ENV_FLAG_SWAMP, item)) { velocity = (anim->velocity >> 1); if (anim->acceleration) velocity += (anim->acceleration * (item->Animation.FrameNumber - anim->frameBase)) >> 2; } else { velocity = anim->velocity; if (anim->acceleration) velocity += anim->acceleration * (item->Animation.FrameNumber - anim->frameBase); } item->Animation.Velocity = velocity >> 16; item->Position.yPos += lara->ExtraVelocity.y; } if (lara->Control.Rope.Ptr != -1) DelAlignLaraToRope(item); if (!lara->Control.IsMoving) MoveItem(item, lara->Control.MoveAngle, item->Animation.Velocity + lara->ExtraVelocity.x, item->Animation.LateralVelocity + lara->ExtraVelocity.z); // Update matrices g_Renderer.updateLaraAnimations(true); } void AnimateItem(ITEM_INFO* item) { item->TouchBits = 0; item->HitStatus = false; item->Animation.FrameNumber++; auto* anim = &g_Level.Anims[item->Animation.AnimNumber]; if (anim->numberChanges > 0 && GetChange(item, anim)) { anim = &g_Level.Anims[item->Animation.AnimNumber]; item->Animation.ActiveState = anim->ActiveState; if (item->Animation.RequiredState == item->Animation.ActiveState) item->Animation.RequiredState = 0; } if (item->Animation.FrameNumber > anim->frameEnd) { if (anim->numberCommands > 0) { short* cmd = &g_Level.Commands[anim->commandIndex]; for (int i = anim->numberCommands; i > 0; i--) { switch (*(cmd++)) { case COMMAND_MOVE_ORIGIN: TranslateItem(item, cmd[0], cmd[1], cmd[2]); cmd += 3; break; case COMMAND_JUMP_VELOCITY: item->Animation.VerticalVelocity = *(cmd++); item->Animation.Velocity = *(cmd++); item->Animation.Airborne = true; break; case COMMAND_DEACTIVATE: if (Objects[item->ObjectNumber].intelligent && !item->AfterDeath) item->AfterDeath = 1; item->Status = ITEM_DEACTIVATED; break; case COMMAND_SOUND_FX: case COMMAND_EFFECT: cmd += 2; break; default: break; } } } item->Animation.AnimNumber = anim->jumpAnimNum; item->Animation.FrameNumber = anim->jumpFrameNum; anim = &g_Level.Anims[item->Animation.AnimNumber]; if (item->Animation.ActiveState != anim->ActiveState) { item->Animation.ActiveState = anim->ActiveState; item->Animation.TargetState = anim->ActiveState; } if (item->Animation.RequiredState == item->Animation.ActiveState) item->Animation.RequiredState = 0; } if (anim->numberCommands > 0) { short* cmd = &g_Level.Commands[anim->commandIndex]; int flags; int effectID = 0; for (int i = anim->numberCommands; i > 0; i--) { switch (*(cmd++)) { case COMMAND_MOVE_ORIGIN: cmd += 3; break; case COMMAND_JUMP_VELOCITY: cmd += 2; break; case COMMAND_SOUND_FX: if (item->Animation.FrameNumber != *cmd) { cmd += 2; break; } flags = cmd[1] & 0xC000; if (!Objects[item->ObjectNumber].waterCreature) { if (item->RoomNumber == NO_ROOM) { item->Position.xPos = LaraItem->Position.xPos; item->Position.yPos = LaraItem->Position.yPos - 762; item->Position.zPos = LaraItem->Position.zPos; SoundEffect(cmd[1] & 0x3FFF, &item->Position, 2); } else if (TestEnvironment(ENV_FLAG_WATER, item)) { if (!flags || flags == (int)SOUND_PLAYCONDITION::Water && (TestEnvironment(ENV_FLAG_WATER, Camera.pos.roomNumber) || Objects[item->ObjectNumber].intelligent)) SoundEffect(cmd[1] & 0x3FFF, &item->Position, 2); } else if (!flags || flags == (int)SOUND_PLAYCONDITION::Land && !TestEnvironment(ENV_FLAG_WATER, Camera.pos.roomNumber)) SoundEffect(cmd[1] & 0x3FFF, &item->Position, 2); } else { if (TestEnvironment(ENV_FLAG_WATER, item)) SoundEffect(cmd[1] & 0x3FFF, &item->Position, 1); else SoundEffect(cmd[1] & 0x3FFF, &item->Position, 0); } break; case COMMAND_EFFECT: if (item->Animation.FrameNumber != *cmd) { cmd += 2; break; } effectID = cmd[1] & 0x3FFF; DoFlipEffect(effectID, item); cmd += 2; break; default: break; } } } int lateral = 0; if (item->Animation.Airborne) { item->Animation.VerticalVelocity += (item->Animation.VerticalVelocity >= 128 ? 1 : 6); item->Position.yPos += item->Animation.VerticalVelocity; } else { int velocity = anim->velocity; if (anim->acceleration) velocity += anim->acceleration * (item->Animation.FrameNumber - anim->frameBase); item->Animation.Velocity = velocity >> 16; lateral = anim->Xvelocity; if (anim->Xacceleration) lateral += anim->Xacceleration * (item->Animation.FrameNumber - anim->frameBase); lateral >>= 16; } MoveItem(item, item->Position.yRot, item->Animation.Velocity, lateral); // Update matrices. short itemNumber = item - g_Level.Items.data(); g_Renderer.updateItemAnimations(itemNumber, true); } // Checks whether targetState can be dispatched from the current frame. bool HasChange(ITEM_INFO* item, int targetState) { auto* anim = &g_Level.Anims[item->Animation.AnimNumber]; if (anim->numberChanges <= 0) return false; if (targetState < 0) targetState = item->Animation.TargetState; // Iterate over possible state dispatches. for (int i = 0; i < anim->numberChanges; i++) { auto* change = &g_Level.Changes[anim->changeIndex + i]; if (change->TargetState == targetState) { // Iterate over frame range of state dispatch. for (int j = 0; j < change->numberRanges; j++) { auto* range = &g_Level.Ranges[change->rangeIndex + j]; if (item->Animation.FrameNumber >= range->startFrame && item->Animation.FrameNumber <= range->endFrame) return true; } } } return false; } bool TestLastFrame(ITEM_INFO* item, int animNumber) { if (animNumber < 0) animNumber = item->Animation.AnimNumber; if (item->Animation.AnimNumber != animNumber) return false; auto* anim = &g_Level.Anims[animNumber]; return (item->Animation.FrameNumber >= anim->frameEnd); } void TranslateItem(ITEM_INFO* item, int x, int y, int z) { float c = phd_cos(item->Position.yRot); float s = phd_sin(item->Position.yRot); item->Position.xPos += roundf(c * x + s * z); item->Position.yPos += y; item->Position.zPos += roundf(-s * x + c * z); } void SetAnimation(ITEM_INFO* item, int animIndex, int frameToStart) { auto index = Objects[item->ObjectNumber].animIndex + animIndex; if (index < 0 || index >= g_Level.Anims.size()) { TENLog(std::string("Attempt to set nonexistent animation ") + std::to_string(animIndex) + std::string(" for object ") + std::to_string(item->ObjectNumber), LogLevel::Warning); return; } if (item->Animation.AnimNumber == animIndex) return; item->Animation.AnimNumber = index; item->Animation.FrameNumber = g_Level.Anims[index].frameBase + frameToStart; item->Animation.ActiveState = g_Level.Anims[index].ActiveState; item->Animation.TargetState = item->Animation.ActiveState; } bool GetChange(ITEM_INFO* item, ANIM_STRUCT* anim) { if (item->Animation.ActiveState == item->Animation.TargetState) return false; if (anim->numberChanges <= 0) return false; // Iterate over possible state dispatches. for (int i = 0; i < anim->numberChanges; i++) { auto* change = &g_Level.Changes[anim->changeIndex + i]; if (change->TargetState == item->Animation.TargetState) { // Iterate over frame range of state dispatch. for (int j = 0; j < change->numberRanges; j++) { auto* range = &g_Level.Ranges[change->rangeIndex + j]; if (item->Animation.FrameNumber >= range->startFrame && item->Animation.FrameNumber <= range->endFrame) { item->Animation.AnimNumber = range->linkAnimNum; item->Animation.FrameNumber = range->linkFrameNum; return true; } } } } return false; } BOUNDING_BOX* GetBoundsAccurate(ITEM_INFO* item) { int rate = 0; ANIM_FRAME* framePtr[2]; int frac = GetFrame(item, framePtr, &rate); if (frac == 0) return &framePtr[0]->boundingBox; else { InterpolatedBounds.X1 = framePtr[0]->boundingBox.X1 + (framePtr[1]->boundingBox.X1 - framePtr[0]->boundingBox.X1) * frac / rate; InterpolatedBounds.X2 = framePtr[0]->boundingBox.X2 + (framePtr[1]->boundingBox.X2 - framePtr[0]->boundingBox.X2) * frac / rate; InterpolatedBounds.Y1 = framePtr[0]->boundingBox.Y1 + (framePtr[1]->boundingBox.Y1 - framePtr[0]->boundingBox.Y1) * frac / rate; InterpolatedBounds.Y2 = framePtr[0]->boundingBox.Y2 + (framePtr[1]->boundingBox.Y2 - framePtr[0]->boundingBox.Y2) * frac / rate; InterpolatedBounds.Z1 = framePtr[0]->boundingBox.Z1 + (framePtr[1]->boundingBox.Z1 - framePtr[0]->boundingBox.Z1) * frac / rate; InterpolatedBounds.Z2 = framePtr[0]->boundingBox.Z2 + (framePtr[1]->boundingBox.Z2 - framePtr[0]->boundingBox.Z2) * frac / rate; return &InterpolatedBounds; } } ANIM_FRAME* GetBestFrame(ITEM_INFO* item) { int rate = 0; ANIM_FRAME* framePtr[2]; int frac = GetFrame(item, framePtr, &rate); if (frac <= (rate >> 1)) return framePtr[0]; else return framePtr[1]; } int GetFrame(ITEM_INFO* item, ANIM_FRAME* framePtr[], int* rate) { int frame = item->Animation.FrameNumber; auto* anim = &g_Level.Anims[item->Animation.AnimNumber]; framePtr[0] = framePtr[1] = &g_Level.Frames[anim->framePtr]; int rate2 = *rate = anim->interpolation & 0x00ff; frame -= anim->frameBase; int first = frame / rate2; int interpolation = frame % rate2; framePtr[0] += first; // Get frame pointers... framePtr[1] = framePtr[0] + 1; // and store away. if (interpolation == 0) return 0; // Clamp key frame to end if need be. int second = first * rate2 + rate2; if (second > anim->frameEnd) *rate = anim->frameEnd - (second - rate2); return interpolation; } int GetCurrentRelativeFrameNumber(ITEM_INFO* item) { return item->Animation.FrameNumber - GetFrameNumber(item, 0); } int GetFrameNumber(ITEM_INFO* item, int frameToStart) { return GetFrameNumber(item->ObjectNumber, item->Animation.AnimNumber, frameToStart); } int GetFrameNumber(int objectID, int animNumber, int frameToStart) { return (g_Level.Anims[Objects[objectID].animIndex + animNumber].frameBase + frameToStart); } int GetFrameCount(int animNumber) { if (animNumber < 0 || g_Level.Anims.size() <= animNumber) return 0; int end = g_Level.Anims[animNumber].frameEnd; int base = g_Level.Anims[animNumber].frameBase; return (end - base); } int GetNextAnimState(ITEM_INFO* item) { return GetNextAnimState(item->ObjectNumber, item->Animation.AnimNumber); } int GetNextAnimState(int objectID, int animNumber) { auto nextAnim = g_Level.Anims[Objects[objectID].animIndex + animNumber].jumpAnimNum; return g_Level.Anims[Objects[objectID].animIndex + nextAnim].ActiveState; } void DrawAnimatingItem(ITEM_INFO* item) { // TODO: to refactor // Empty stub because actually we disable items drawing when drawRoutine pointer is NULL in OBJECT_INFO } void GetLaraJointPosition(PHD_VECTOR* pos, int laraMeshIndex) { if (laraMeshIndex >= NUM_LARA_MESHES) laraMeshIndex = LM_HEAD; auto pos2 = Vector3(pos->x, pos->y, pos->z); g_Renderer.getLaraAbsBonePosition(&pos2, laraMeshIndex); pos->x = pos2.x; pos->y = pos2.y; pos->z = pos2.z; } void ClampRotation(PHD_3DPOS* pos, short angle, short rotation) { if (angle <= rotation) { if (angle >= -rotation) pos->yRot += angle; else pos->yRot -= rotation; } else pos->yRot += rotation; }