TombEngine/TR5Main/Game/Lara/lara_helpers.cpp

410 lines
11 KiB
C++
Raw Normal View History

#include "framework.h"
2021-12-24 03:32:19 +03:00
#include "Game/Lara/lara_helpers.h"
2021-12-22 16:23:57 +03:00
#include "Game/collision/collide_room.h"
#include "Game/control/control.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_tests.h"
#include "Game/Lara/lara_collide.h"
#include "Scripting/GameFlowScript.h"
2021-12-24 03:32:19 +03:00
#include "Specific/input.h"
#include "Specific/level.h"
#include "Specific/setup.h"
2021-10-18 20:15:53 +11:00
// -----------------------------
// HELPER FUNCTIONS
// For State Control & Collision
// -----------------------------
2021-12-23 18:41:37 +11:00
// TODO: Make lean rate proportional to the turn rate, allowing for nicer aesthetics with future analog stick input.
2022-01-15 22:48:35 +11:00
void DoLaraLean(ITEM_INFO* item, COLL_INFO* coll, short maxAngle, short rate)
2021-12-23 18:41:37 +11:00
{
if (!item->speed)
return;
int sign = copysign(1, maxAngle);
if (coll->CollisionType == CT_LEFT || coll->CollisionType == CT_RIGHT)
item->pos.zRot += std::min(rate, (short)(abs((maxAngle * 3) / 5 - item->pos.zRot) / 3)) * sign;
else
item->pos.zRot += std::min(rate, (short)(abs(maxAngle - item->pos.zRot) / 3)) * sign;
}
// TODO: Some states can't make the most of this function due to missing step up/down animations.
// Try implementing leg IK as a substitute to make step animations obsolete. @Sezz 2021.10.09
void DoLaraStep(ITEM_INFO* item, COLL_INFO* coll, int deltaHeight)
{
if (deltaHeight == NO_HEIGHT)
return;
2022-01-25 18:02:22 +11:00
if (!TestEnvironment(ENV_FLAG_SWAMP, item))
{
2021-12-04 14:01:41 +11:00
if (TestLaraStepUp(item, coll))
{
item->targetState = LS_STEP_UP;
2021-12-04 14:01:41 +11:00
if (GetChange(item, &g_Level.Anims[item->animNumber]))
{
item->pos.yPos += deltaHeight;
2021-12-04 14:01:41 +11:00
return;
}
}
2021-12-04 14:01:41 +11:00
else if (TestLaraStepDown(item, coll))
{
item->targetState = LS_STEP_DOWN;
2021-12-04 14:01:41 +11:00
if (GetChange(item, &g_Level.Anims[item->animNumber]))
{
item->pos.yPos += deltaHeight;
2021-12-04 14:01:41 +11:00
return;
}
}
}
// Height difference is below threshold for step dispatch OR step animation doesn't exist; translate Lara to new floor height.
2022-02-03 12:21:28 +11:00
// TODO: This approach may cause undesirable artefacts where an object pushes Lara rapidly up/down a slope or a platform rapidly ascends/descends.
2021-12-10 12:30:23 +11:00
constexpr int rate = 50;
2021-12-04 14:01:41 +11:00
int threshold = std::max(abs(item->speed) / 3 * 2, STEP_SIZE / 16);
int sign = std::copysign(1, deltaHeight);
if (TestEnvironment(ENV_FLAG_SWAMP, item) && deltaHeight > 0)
item->pos.yPos += SWAMP_GRAVITY;
else if (abs(deltaHeight) > (STEPUP_HEIGHT / 2)) // Outer range.
item->pos.yPos += rate * sign;
else if (abs(deltaHeight) <= (STEPUP_HEIGHT / 2) && // Inner range.
abs(deltaHeight) >= threshold)
{
item->pos.yPos += std::max<int>(abs(deltaHeight / 2.75), threshold) * sign;
}
else
item->pos.yPos += deltaHeight;
}
void DoLaraStep(ITEM_INFO* item, COLL_INFO* coll)
{
DoLaraStep(item, coll, coll->Middle.Floor);
}
void DoLaraMonkeyStep(ITEM_INFO* item, COLL_INFO* coll)
{
constexpr int rate = 50;
2022-01-27 22:26:05 +11:00
int threshold = std::max<int>(abs(item->speed) / 3 * 2, CLICK(1.25f) / 16);
int sign = std::copysign(1, coll->Middle.Ceiling);
if (abs(coll->Middle.Ceiling) > (CLICK(1.25f) / 2)) // Outer range.
item->pos.yPos += rate * sign;
else if (abs(coll->Middle.Ceiling) <= (CLICK(1.25f) / 2) && // Inner range.
abs(coll->Middle.Ceiling) >= threshold)
{
2022-01-27 22:26:05 +11:00
item->pos.yPos += std::max<int>(abs(coll->Middle.Ceiling / 2.75), threshold) * sign;
}
else
item->pos.yPos += coll->Middle.Ceiling;
}
2022-01-03 17:57:57 +11:00
// TODO: Doesn't always work on bridges.
void DoLaraCrawlToHangSnap(ITEM_INFO* item, COLL_INFO* coll)
{
2021-12-04 12:54:01 +11:00
coll->Setup.ForwardAngle = item->pos.yRot + ANGLE(180.0f);
2021-11-03 21:44:48 +11:00
GetCollisionInfo(coll, item);
SnapItemToLedge(item, coll);
2021-12-04 12:54:01 +11:00
MoveItem(item, item->pos.yRot, -LARA_RAD_CRAWL);
2021-11-19 22:48:47 +11:00
item->pos.yRot += ANGLE(180.0f);
2021-11-03 21:44:48 +11:00
LaraResetGravityStatus(item, coll);
}
void DoLaraCrawlFlex(ITEM_INFO* item, COLL_INFO* coll, short maxAngle, short rate)
{
LaraInfo*& info = item->data;
2021-12-14 14:35:42 +11:00
if (!item->speed)
return;
int sign = copysign(1, maxAngle);
rate = copysign(rate, maxAngle);
info->torsoZrot += std::min(abs(rate), abs(maxAngle - info->torsoZrot) / 6) * sign;
if (!(TrInput & IN_LOOK) &&
item->activeState != LS_CRAWL_BACK)
{
info->headZrot = info->torsoZrot / 2;
info->headYrot = info->headZrot;
}
}
2022-01-22 22:36:29 +11:00
void DoLaraFallDamage(ITEM_INFO* item)
{
// TODO: Demagic more of these numbers.
int landSpeed = item->fallspeed - 140;
if (landSpeed > 0)
{
if (landSpeed <= 14)
item->hitPoints -= LARA_HEALTH_MAX * pow(landSpeed, 2) / 196;
else
item->hitPoints = 0;
}
}
2022-01-23 18:16:29 +11:00
short GetLaraSlideDirection(ITEM_INFO* item, COLL_INFO* coll)
{
2022-01-23 18:16:29 +11:00
short direction = item->pos.yRot;
2022-01-04 01:39:02 +11:00
2022-01-22 21:08:30 +11:00
//if (g_GameFlow->Animations.SlideExtended)
//{
// // TODO: Get true slope direction.
//}
//else
{
if (coll->FloorTiltX > 2)
2022-01-23 18:16:29 +11:00
direction = -ANGLE(90.0f);
2022-01-22 21:08:30 +11:00
else if (coll->FloorTiltX < -2)
2022-01-23 18:16:29 +11:00
direction = ANGLE(90.0f);
2022-01-22 21:08:30 +11:00
if (coll->FloorTiltZ > 2 && coll->FloorTiltZ > abs(coll->FloorTiltX))
2022-01-23 18:16:29 +11:00
direction = ANGLE(180.0f);
2022-01-22 21:08:30 +11:00
else if (coll->FloorTiltZ < -2 && -coll->FloorTiltZ > abs(coll->FloorTiltX))
2022-01-23 18:16:29 +11:00
direction = ANGLE(0.0f);
2022-01-22 21:08:30 +11:00
}
2022-01-23 18:16:29 +11:00
return direction;
}
void SetLaraJumpDirection(ITEM_INFO* item, COLL_INFO* coll)
{
LaraInfo*& info = item->data;
if (TrInput & IN_FORWARD &&
2022-01-02 15:54:43 +11:00
TestLaraJumpForward(item, coll))
{
info->jumpDirection = JumpDirection::Forward;
}
else if (TrInput & IN_BACK &&
2022-01-02 15:54:43 +11:00
TestLaraJumpBack(item, coll))
{
info->jumpDirection = JumpDirection::Back;
}
else if (TrInput & IN_LEFT &&
2022-01-02 15:54:43 +11:00
TestLaraJumpLeft(item, coll))
{
info->jumpDirection = JumpDirection::Left;
}
else if (TrInput & IN_RIGHT &&
2022-01-02 15:54:43 +11:00
TestLaraJumpRight(item, coll))
{
info->jumpDirection = JumpDirection::Right;
}
2022-01-02 15:54:43 +11:00
else if (TestLaraJumpUp(item, coll)) [[likely]]
info->jumpDirection = JumpDirection::Up;
else
info->jumpDirection = JumpDirection::NoDirection;
}
2022-01-22 00:22:24 +11:00
// TODO: Add a timeout? Imagine a small, sad rain cloud with the properties of a ceiling following Lara overhead.
// runJumpQueued will never reset, and when the sad cloud flies away after an indefinite amount of time, Lara will jump. @Sezz 2022.01.22
2022-01-22 00:22:24 +11:00
void SetLaraRunJumpQueue(ITEM_INFO* item, COLL_INFO* coll)
2021-12-19 19:12:35 +11:00
{
LaraInfo*& info = item->data;
int y = item->pos.yPos;
2022-01-22 00:22:24 +11:00
int dist = WALL_SIZE;
2022-01-03 17:01:07 +11:00
auto probe = GetCollisionResult(item, item->pos.yRot, dist, -coll->Setup.Height);
2021-12-19 19:12:35 +11:00
2022-01-22 00:22:24 +11:00
if ((TestLaraRunJumpForward(item, coll) || // Area close ahead is permissive...
(probe.Position.Ceiling - y) < -(coll->Setup.Height + (LARA_HEADROOM * 0.8f)) || // OR ceiling height is permissive far ahead
(probe.Position.Floor - y) >= CLICK(0.5f)) && // OR there is a drop below far ahead.
2022-01-04 16:25:12 +11:00
probe.Position.Floor != NO_HEIGHT)
2022-01-12 19:04:29 +11:00
{
info->runJumpQueued = IsRunJumpQueueableState((LARA_STATE)item->targetState);
2022-01-12 19:04:29 +11:00
}
2021-12-19 19:12:35 +11:00
else
2022-01-03 17:01:07 +11:00
info->runJumpQueued = false;
2021-12-19 19:12:35 +11:00
}
2022-02-05 23:13:31 +11:00
void SetLaraVault(ITEM_INFO* item, COLL_INFO* coll, VaultTestResult vaultResult)
{
LaraInfo*& info = item->data;
info->projectedFloorHeight = vaultResult.Height;
info->gunStatus = LG_HANDS_BUSY;
info->turnRate = 0;
SnapItemToLedge(item, coll, 0.2f);
}
2022-01-22 21:08:30 +11:00
void SetLaraLand(ITEM_INFO* item, COLL_INFO* coll)
{
item->speed = 0;
item->fallspeed = 0;
item->airborne = false;
LaraSnapToHeight(item, coll);
}
2021-12-10 12:30:23 +11:00
void SetLaraFallState(ITEM_INFO* item)
{
SetAnimation(item, LA_FALL_START);
item->fallspeed = 0;
2022-01-21 23:23:10 +11:00
item->airborne = true;
2021-12-10 12:30:23 +11:00
}
void SetLaraFallBackState(ITEM_INFO* item)
{
SetAnimation(item, LA_FALL_BACK);
item->fallspeed = 0;
2022-01-21 23:23:10 +11:00
item->airborne = true;
2021-12-10 12:30:23 +11:00
}
2022-01-12 17:31:03 +11:00
void SetLaraMonkeyFallState(ITEM_INFO* item)
{
// Hack.
if (item->activeState == LS_MONKEY_TURN_180)
return;
2022-01-26 17:48:50 +11:00
SetAnimation(item, LA_MONKEY_TO_FREEFALL);
SetLaraMonkeyRelease(item);
}
void SetLaraMonkeyRelease(ITEM_INFO* item)
{
LaraInfo*& info = item->data;
info->gunStatus = LG_HANDS_FREE;
item->speed = 2;
item->fallspeed = 1;
2022-01-21 23:23:10 +11:00
item->airborne = true;
2022-01-12 17:31:03 +11:00
}
2021-12-18 20:42:15 +11:00
void SetLaraSlideState(ITEM_INFO* item, COLL_INFO* coll)
{
LaraInfo*& info = item->data;
2022-01-23 18:16:29 +11:00
short direction = GetLaraSlideDirection(item, coll);
short delta = direction - item->pos.yRot;
2021-12-18 20:42:15 +11:00
static short oldAngle = 1;
ShiftItem(item, coll);
if (delta < -ANGLE(90.0f) || delta > ANGLE(90.0f))
{
if (item->activeState == LS_SLIDE_BACK && oldAngle == direction)
2021-12-18 20:42:15 +11:00
return;
SetAnimation(item, LA_SLIDE_BACK_START);
item->pos.yRot = direction + ANGLE(180.0f);
2021-12-18 20:42:15 +11:00
}
else
{
if (item->activeState == LS_SLIDE_FORWARD && oldAngle == direction)
2021-12-18 20:42:15 +11:00
return;
SetAnimation(item, LA_SLIDE_FORWARD);
item->pos.yRot = direction;
2021-12-18 20:42:15 +11:00
}
LaraSnapToHeight(item, coll);
info->moveAngle = direction;
oldAngle = direction;
2021-12-18 20:42:15 +11:00
}
2021-12-10 12:30:23 +11:00
void ResetLaraFlex(ITEM_INFO* item, float rate)
{
LaraInfo*& info = item->data;
// Reset head.
if (abs(info->headXrot) > ANGLE(0.1f))
info->headXrot += info->headXrot / -rate;
else
info->headXrot = 0;
if (abs(info->headYrot) > ANGLE(0.1f))
info->headYrot += info->headYrot / -rate;
else
info->headYrot = 0;
if (abs(info->headZrot) > ANGLE(0.1f))
info->headZrot += info->headZrot / -rate;
else
info->headZrot = 0;
// Reset torso.
if (abs(info->torsoXrot) > ANGLE(0.1f))
info->torsoXrot += info->torsoXrot / -rate;
else
info->torsoXrot = 0;
if (abs(info->torsoYrot) > ANGLE(0.1f))
info->torsoYrot += info->torsoYrot / -rate;
else
info->torsoYrot = 0;
if (abs(info->torsoZrot) > ANGLE(0.1f))
info->torsoZrot += info->torsoZrot / -rate;
else
info->torsoZrot = 0;
}
2021-12-10 12:30:23 +11:00
void HandleLaraMovementParameters(ITEM_INFO* item, COLL_INFO* coll)
{
2021-12-10 12:30:23 +11:00
LaraInfo*& info = item->data;
2021-12-10 12:30:23 +11:00
// Reset running jump timer.
if (!IsRunJumpCountableState((LARA_STATE)item->activeState))
2022-01-03 17:01:07 +11:00
info->runJumpCount = 0;
2022-02-04 22:18:55 +11:00
2022-01-03 17:01:07 +11:00
// Reset running jump action queue.
if (!IsRunJumpQueueableState((LARA_STATE)item->activeState))
2022-01-03 17:01:07 +11:00
info->runJumpQueued = false;
2022-02-04 20:23:39 +11:00
// Reset projected height value used by step function.
2022-02-05 23:13:31 +11:00
//if (!IsVaultState((LARA_STATE)item->activeState))
// info->projectedFloorHeight = NO_HEIGHT;
// Reset calculated auto jump velocity.
//if (item->activeState != LS_AUTO_JUMP)
// info->calcJumpVelocity = 0;
2021-12-10 12:30:23 +11:00
// Increment/reset AFK pose timer.
if (info->poseCount < LARA_POSE_TIME &&
TestLaraPose(item, coll) &&
!(TrInput & (IN_WAKE | IN_LOOK)) &&
g_GameFlow->Animations.Pose)
{
info->poseCount++;
}
else
info->poseCount = 0;
// Reset lean.
if (!info->isMoving || (info->isMoving && !(TrInput & (IN_LEFT | IN_RIGHT))))
{
if (abs(item->pos.zRot) > ANGLE(0.1f))
item->pos.zRot += item->pos.zRot / -6;
else
item->pos.zRot = 0;
}
// Temp.
if (abs(item->pos.xRot) > ANGLE(0.1f))
item->pos.xRot += item->pos.xRot / -6;
else
item->pos.xRot = 0;
2021-12-10 12:30:23 +11:00
// Reset crawl flex.
if (!(TrInput & IN_LOOK) &&
coll->Setup.Height > LARA_HEIGHT - LARA_HEADROOM &&
2021-12-10 12:30:23 +11:00
(!item->speed || (item->speed && !(TrInput & (IN_LEFT | IN_RIGHT)))))
{
ResetLaraFlex(item, 12);
}
// Reset turn rate.
int sign = copysign(1, info->turnRate);
if (abs(info->turnRate) > ANGLE(2.0f))
info->turnRate -= ANGLE(2.0f) * sign;
else if (abs(info->turnRate) > ANGLE(0.5f))
info->turnRate -= ANGLE(0.5f) * sign;
else
info->turnRate = 0;
item->pos.yRot += info->turnRate;
}