TombEngine/TR5Main/Game/Lara/lara_helpers.cpp

509 lines
13 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"
2022-02-14 17:05:52 +11:00
#include "Game/Lara/lara_collide.h"
2022-02-08 01:26:59 +11:00
#include "Game/Lara/lara_fire.h"
2021-12-22 16:23:57 +03:00
#include "Game/Lara/lara_tests.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"
2022-02-08 01:26:59 +11:00
#include "Objects/TR2/Vehicles/snowmobile.h"
#include "Objects/TR3/Vehicles/biggun.h"
#include "Objects/TR3/Vehicles/kayak.h"
#include "Objects/TR3/Vehicles/minecart.h"
#include "Objects/TR3/Vehicles/quad.h"
#include "Objects/TR3/Vehicles/upv.h"
#include "Objects/TR4/Vehicles/jeep.h"
#include "Objects/TR4/Vehicles/motorbike.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->Velocity)
2021-12-23 18:41:37 +11:00
return;
2022-02-17 15:01:48 +11:00
rate = abs(rate);
2021-12-23 18:41:37 +11:00
int sign = copysign(1, maxAngle);
if (coll->CollisionType == CT_LEFT || coll->CollisionType == CT_RIGHT)
item->Position.zRot += std::min<short>(rate, abs((maxAngle * 3) / 5 - item->Position.zRot) / 3) * sign;
2021-12-23 18:41:37 +11:00
else
item->Position.zRot += std::min<short>(rate, abs(maxAngle - item->Position.zRot) / 3) * sign;
}
// Works, but disabled for the time being. @Sezz 2022.02.13
void ApproachLaraTargetAngle(ITEM_INFO* item, short targetAngle, float rate)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
if (!rate)
return;
2022-02-14 17:05:52 +11:00
rate = abs(rate);
if (abs((short)(targetAngle - item->Position.yRot)) > ANGLE(0.1f))
item->Position.yRot += (short)(targetAngle - item->Position.yRot) / rate;
else
item->Position.yRot = targetAngle;
2021-12-23 18:41:37 +11:00
}
void EaseOutLaraHeight(ITEM_INFO* item, int height)
{
if (height == NO_HEIGHT)
return;
// Translate Lara to new height.
// TODO: This approach may cause undesirable artefacts where an object pushes Lara rapidly up/down a slope or a platform rapidly ascends/descends.
static constexpr int rate = 50;
int threshold = std::max(abs(item->Velocity) / 3 * 2, STEP_SIZE / 16);
int sign = std::copysign(1, height);
if (TestEnvironment(ENV_FLAG_SWAMP, item) && height > 0)
item->Position.yPos += SWAMP_GRAVITY;
else if (abs(height) > (STEPUP_HEIGHT / 2)) // Outer range.
item->Position.yPos += rate * sign;
else if (abs(height) <= (STEPUP_HEIGHT / 2) && // Inner range.
abs(height) >= threshold)
{
item->Position.yPos += std::max<int>(abs(height / 2.75), threshold) * sign;
}
else
item->Position.yPos += height;
}
// 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)
{
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;
if (GetChange(item, &g_Level.Anims[item->AnimNumber]))
2021-12-04 14:01:41 +11:00
{
item->Position.yPos += coll->Middle.Floor;
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;
if (GetChange(item, &g_Level.Anims[item->AnimNumber]))
2021-12-04 14:01:41 +11:00
{
item->Position.yPos += coll->Middle.Floor;
2021-12-04 14:01:41 +11:00
return;
}
}
}
EaseOutLaraHeight(item, coll->Middle.Floor);
}
void DoLaraMonkeyStep(ITEM_INFO* item, COLL_INFO* coll)
{
EaseOutLaraHeight(item, 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)
{
coll->Setup.ForwardAngle = item->Position.yRot + ANGLE(180.0f);
2021-11-03 21:44:48 +11:00
GetCollisionInfo(coll, item);
SnapItemToLedge(item, coll);
MoveItem(item, item->Position.yRot, -LARA_RAD_CRAWL);
item->Position.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)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
if (!item->Velocity)
2021-12-14 14:35:42 +11:00
return;
int sign = copysign(1, maxAngle);
rate = copysign(rate, maxAngle);
info->ExtraTorsoRot.zRot += std::min(abs(rate), abs(maxAngle - info->ExtraTorsoRot.zRot) / 6) * sign;
if (!(TrInput & IN_LOOK) &&
item->ActiveState != LS_CRAWL_BACK)
{
info->ExtraHeadRot.zRot = info->ExtraTorsoRot.zRot / 2;
info->ExtraHeadRot.yRot = info->ExtraHeadRot.zRot;
}
}
2022-01-22 22:36:29 +11:00
void DoLaraFallDamage(ITEM_INFO* item)
{
// TODO: Demagic more of these numbers.
int landSpeed = item->VerticalVelocity - 140;
2022-01-22 22:36:29 +11:00
if (landSpeed > 0)
{
if (landSpeed <= 14)
item->HitPoints -= LARA_HEALTH_MAX * pow(landSpeed, 2) / 196;
2022-01-22 22:36:29 +11:00
else
item->HitPoints = 0;
2022-01-22 22:36:29 +11:00
}
}
LaraInfo*& GetLaraInfo(ITEM_INFO* item)
{
if (item->ObjectNumber != ID_LARA)
{
2022-02-14 17:05:52 +11:00
TENLog(std::string("Attempted to fetch LaraInfo data from item with object ID ") + std::to_string(item->ObjectNumber), LogLevel::Warning);
2022-02-14 17:05:52 +11:00
auto* firstLaraItem = FindItem(ID_LARA);
return (LaraInfo*&)firstLaraItem->Data;
}
return (LaraInfo*&)item->Data;
}
2022-01-23 18:16:29 +11:00
short GetLaraSlideDirection(ITEM_INFO* item, COLL_INFO* coll)
{
short direction = item->Position.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))
direction = 0;
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)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
if (TrInput & IN_FORWARD &&
2022-01-02 15:54:43 +11:00
TestLaraJumpForward(item, coll))
{
info->Control.JumpDirection = JumpDirection::Forward;
}
else if (TrInput & IN_BACK &&
2022-01-02 15:54:43 +11:00
TestLaraJumpBack(item, coll))
{
info->Control.JumpDirection = JumpDirection::Back;
}
else if (TrInput & IN_LEFT &&
2022-01-02 15:54:43 +11:00
TestLaraJumpLeft(item, coll))
{
info->Control.JumpDirection = JumpDirection::Left;
}
else if (TrInput & IN_RIGHT &&
2022-01-02 15:54:43 +11:00
TestLaraJumpRight(item, coll))
{
info->Control.JumpDirection = JumpDirection::Right;
}
2022-01-02 15:54:43 +11:00
else if (TestLaraJumpUp(item, coll)) [[likely]]
info->Control.JumpDirection = JumpDirection::Up;
else
info->Control.JumpDirection = JumpDirection::None;
}
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
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
2021-12-19 19:12:35 +11:00
int y = item->Position.yPos;
2022-01-22 00:22:24 +11:00
int dist = WALL_SIZE;
auto probe = GetCollisionResult(item, item->Position.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
{
2022-02-12 16:25:59 +11:00
info->Control.RunJumpQueued = IsRunJumpQueueableState((LaraState)item->TargetState);
2022-01-12 19:04:29 +11:00
}
2021-12-19 19:12:35 +11:00
else
info->Control.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)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
2022-02-05 23:13:31 +11:00
info->ProjectedFloorHeight = vaultResult.Height;
info->Control.HandStatus = vaultResult.SetBusyHands ? HandStatus::Busy : info->Control.HandStatus;
info->Control.TurnRate = 0;
// Disable smooth angle adjustment for now.
//info->Control.ApproachTargetAngle = vaultResult.ApproachLedgeAngle;
if (vaultResult.SnapToLedge)
SnapItemToLedge(item, coll, 0.2f);
2022-02-05 23:13:31 +11:00
}
2022-01-22 21:08:30 +11:00
void SetLaraLand(ITEM_INFO* item, COLL_INFO* coll)
{
item->Velocity = 0;
item->VerticalVelocity = 0;
//item->Airborne = false; // TODO: Removing this addresses an unusual landing bug Core had worked around in an obscure way. I'd like to find a proper solution someday. @Sezz 2022.02.18
2022-01-22 21:08:30 +11:00
LaraSnapToHeight(item, coll);
}
2021-12-10 12:30:23 +11:00
void SetLaraFallState(ITEM_INFO* item)
{
SetAnimation(item, LA_FALL_START);
item->VerticalVelocity = 0;
item->Airborne = true;
2021-12-10 12:30:23 +11:00
}
void SetLaraFallBackState(ITEM_INFO* item)
{
SetAnimation(item, LA_FALL_BACK);
item->VerticalVelocity = 0;
item->Airborne = true;
2021-12-10 12:30:23 +11:00
}
2022-01-12 17:31:03 +11:00
void SetLaraMonkeyFallState(ITEM_INFO* item)
{
2022-02-14 17:05:52 +11:00
// HACK: Disallow release during 180 turn action.
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)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
item->Velocity = 2;
item->VerticalVelocity = 1;
item->Airborne = true;
info->Control.HandStatus = HandStatus::Free;
2022-01-12 17:31:03 +11:00
}
2021-12-18 20:42:15 +11:00
void SetLaraSlideState(ITEM_INFO* item, COLL_INFO* coll)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
2021-12-18 20:42:15 +11:00
2022-01-23 18:16:29 +11:00
short direction = GetLaraSlideDirection(item, coll);
short delta = direction - item->Position.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->Position.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->Position.yRot = direction;
2021-12-18 20:42:15 +11:00
}
LaraSnapToHeight(item, coll);
info->Control.MoveAngle = direction;
oldAngle = direction;
2021-12-18 20:42:15 +11:00
}
void ResetLaraLean(ITEM_INFO* item, float rate, bool resetRoll, bool resetPitch)
{
2022-02-14 17:05:52 +11:00
if (!rate)
return;
2022-02-14 17:05:52 +11:00
rate = abs(rate);
if (resetPitch)
{
if (abs(item->Position.xRot) > ANGLE(0.1f))
item->Position.xRot += item->Position.xRot / -rate;
else
item->Position.xRot = 0;
}
2022-02-14 17:05:52 +11:00
if (resetRoll)
{
if (abs(item->Position.zRot) > ANGLE(0.1f))
item->Position.zRot += item->Position.zRot / -rate;
else
item->Position.zRot = 0;
}
}
2021-12-10 12:30:23 +11:00
void ResetLaraFlex(ITEM_INFO* item, float rate)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
if (!rate)
return;
2022-02-14 17:05:52 +11:00
rate = abs(rate);
// Reset head.
if (abs(info->ExtraHeadRot.xRot) > ANGLE(0.1f))
info->ExtraHeadRot.xRot += info->ExtraHeadRot.xRot / -rate;
else
info->ExtraHeadRot.xRot = 0;
if (abs(info->ExtraHeadRot.yRot) > ANGLE(0.1f))
info->ExtraHeadRot.yRot += info->ExtraHeadRot.yRot / -rate;
else
info->ExtraHeadRot.yRot = 0;
if (abs(info->ExtraHeadRot.zRot) > ANGLE(0.1f))
info->ExtraHeadRot.zRot += info->ExtraHeadRot.zRot / -rate;
else
info->ExtraHeadRot.zRot = 0;
// Reset torso.
if (abs(info->ExtraTorsoRot.xRot) > ANGLE(0.1f))
info->ExtraTorsoRot.xRot += info->ExtraTorsoRot.xRot / -rate;
else
info->ExtraTorsoRot.xRot = 0;
if (abs(info->ExtraTorsoRot.yRot) > ANGLE(0.1f))
info->ExtraTorsoRot.yRot += info->ExtraTorsoRot.yRot / -rate;
else
info->ExtraTorsoRot.yRot = 0;
if (abs(info->ExtraTorsoRot.zRot) > ANGLE(0.1f))
info->ExtraTorsoRot.zRot += info->ExtraTorsoRot.zRot / -rate;
else
info->ExtraTorsoRot.zRot = 0;
}
2021-12-10 12:30:23 +11:00
void HandleLaraMovementParameters(ITEM_INFO* item, COLL_INFO* coll)
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
2021-12-10 12:30:23 +11:00
// Reset running jump timer.
2022-02-12 16:25:59 +11:00
if (!IsRunJumpCountableState((LaraState)item->ActiveState))
info->Control.Count.RunJump = 0;
2022-02-04 22:18:55 +11:00
2022-01-03 17:01:07 +11:00
// Reset running jump action queue.
2022-02-12 16:25:59 +11:00
if (!IsRunJumpQueueableState((LaraState)item->ActiveState))
info->Control.RunJumpQueued = false;
2021-12-10 12:30:23 +11:00
// Increment/reset AFK pose timer.
if (info->Control.Count.Pose < LARA_POSE_TIME &&
2021-12-10 12:30:23 +11:00
TestLaraPose(item, coll) &&
!(TrInput & (IN_WAKE | IN_LOOK)) &&
g_GameFlow->Animations.Pose)
{
info->Control.Count.Pose++;
2021-12-10 12:30:23 +11:00
}
else
info->Control.Count.Pose = 0;
2021-12-10 12:30:23 +11:00
// Reset lean.
if (!info->Control.IsMoving || (info->Control.IsMoving && !(TrInput & (IN_LEFT | IN_RIGHT))))
ResetLaraLean(item, 6);
2021-12-10 12:30:23 +11:00
// Reset crawl flex.
if (!(TrInput & IN_LOOK) &&
coll->Setup.Height > LARA_HEIGHT - LARA_HEADROOM &&
(!item->Velocity || (item->Velocity && !(TrInput & (IN_LEFT | IN_RIGHT)))))
2021-12-10 12:30:23 +11:00
{
ResetLaraFlex(item, 12);
}
// Reset turn rate.
int sign = copysign(1, info->Control.TurnRate);
if (abs(info->Control.TurnRate) > ANGLE(2.0f))
info->Control.TurnRate -= ANGLE(2.0f) * sign;
else if (abs(info->Control.TurnRate) > ANGLE(0.5f))
info->Control.TurnRate -= ANGLE(0.5f) * sign;
2021-12-10 12:30:23 +11:00
else
info->Control.TurnRate = 0;
item->Position.yRot += info->Control.TurnRate;
}
2022-02-08 01:26:59 +11:00
void HandleLaraVehicle(ITEM_INFO* item, COLL_INFO* coll)
2022-02-08 01:26:59 +11:00
{
2022-02-14 17:05:52 +11:00
auto* info = GetLaraInfo(item);
2022-02-08 01:26:59 +11:00
if (info->Vehicle != NO_ITEM)
{
switch (g_Level.Items[info->Vehicle].ObjectNumber)
2022-02-08 01:26:59 +11:00
{
case ID_QUAD:
if (QuadBikeControl(item, coll))
return;
break;
case ID_JEEP:
if (JeepControl())
return;
break;
case ID_MOTORBIKE:
if (MotorbikeControl())
return;
break;
case ID_KAYAK:
if (KayakControl(item))
return;
break;
case ID_SNOWMOBILE:
if (SkidooControl(item, coll))
return;
break;
case ID_UPV:
if (SubControl(item, coll))
return;
break;
case ID_MINECART:
if (MineCartControl())
return;
break;
case ID_BIGGUN:
if (BigGunControl(item, coll))
return;
break;
// Boats are processed like normal items in loop.
default:
LaraGun(item);
return;
}
}
}