TombEngine/TR5Main/Game/Lara/lara_tests.cpp

2460 lines
72 KiB
C++
Raw Normal View History

#include "framework.h"
2021-12-22 16:23:57 +03:00
#include "Game/Lara/lara_tests.h"
2021-12-24 03:32:19 +03:00
2021-12-22 16:23:57 +03:00
#include "Game/animation.h"
2021-12-24 03:32:19 +03:00
#include "Game/collision/collide_room.h"
#include "Game/collision/collide_item.h"
2021-12-22 16:23:57 +03:00
#include "Game/control/control.h"
#include "Game/control/los.h"
#include "Game/items.h"
2021-12-24 03:32:19 +03:00
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_climb.h"
#include "Game/Lara/lara_collide.h"
#include "Game/Lara/lara_flare.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/Lara/lara_monkey.h"
2021-12-22 16:23:57 +03:00
#include "Renderer/Renderer11.h"
2021-12-01 15:18:47 +03:00
#include "Scripting/GameFlowScript.h"
2021-12-24 03:32:19 +03:00
#include "Specific/input.h"
#include "Specific/level.h"
2021-09-25 11:27:47 +02:00
2021-10-08 00:54:00 +03:00
using namespace TEN::Renderer;
2021-08-30 18:03:21 +03:00
using namespace TEN::Floordata;
2021-01-17 15:56:48 -03:00
2021-11-03 21:44:48 +11:00
// -----------------------------
// TEST FUNCTIONS
// For State Control & Collision
// -----------------------------
2021-10-07 16:45:26 +03:00
// Test if a ledge in front of item is valid to climb.
bool TestValidLedge(ITEM_INFO* item, CollisionInfo* coll, bool ignoreHeadroom, bool heightLimit)
{
// Determine probe base left/right points
int xl = phd_sin(coll->NearestLedgeAngle - ANGLE(90.0f)) * coll->Setup.Radius;
int zl = phd_cos(coll->NearestLedgeAngle - ANGLE(90.0f)) * coll->Setup.Radius;
int xr = phd_sin(coll->NearestLedgeAngle + ANGLE(90.0f)) * coll->Setup.Radius;
int zr = phd_cos(coll->NearestLedgeAngle + ANGLE(90.0f)) * coll->Setup.Radius;
2021-10-08 00:54:00 +03:00
// Determine probe top point
int y = item->Position.yPos - coll->Setup.Height;
// Get frontal collision data
auto frontLeft = GetCollision(item->Position.xPos + xl, y, item->Position.zPos + zl, GetRoom(item->Location, item->Position.xPos, y, item->Position.zPos).roomNumber);
auto frontRight = GetCollision(item->Position.xPos + xr, y, item->Position.zPos + zr, GetRoom(item->Location, item->Position.xPos, y, item->Position.zPos).roomNumber);
// If any of the frontal collision results intersects item bounds, return false, because there is material intersection.
// This check helps to filter out cases when Lara is formally facing corner but ledge check returns true because probe distance is fixed.
if (frontLeft.Position.Floor < (item->Position.yPos - CLICK(0.5f)) || frontRight.Position.Floor < (item->Position.yPos - CLICK(0.5f)))
return false;
if (frontLeft.Position.Ceiling > (item->Position.yPos - coll->Setup.Height) || frontRight.Position.Ceiling > (item->Position.yPos - coll->Setup.Height))
return false;
2021-10-08 00:54:00 +03:00
//g_Renderer.addDebugSphere(Vector3(item->pos.xPos + xl, left, item->pos.zPos + zl), 64, Vector4::One, RENDERER_DEBUG_PAGE::LOGIC_STATS);
//g_Renderer.addDebugSphere(Vector3(item->pos.xPos + xr, right, item->pos.zPos + zr), 64, Vector4::One, RENDERER_DEBUG_PAGE::LOGIC_STATS);
// Determine ledge probe embed offset.
// We use 0.2f radius extents here for two purposes. First - we can't guarantee that shifts weren't already applied
// and misfire may occur. Second - it guarantees that Lara won't land on a very thin edge of diagonal geometry.
int xf = phd_sin(coll->NearestLedgeAngle) * (coll->Setup.Radius * 1.2f);
int zf = phd_cos(coll->NearestLedgeAngle) * (coll->Setup.Radius * 1.2f);
2021-10-07 16:45:26 +03:00
// Get floor heights at both points
auto left = GetCollision(item->Position.xPos + xf + xl, y, item->Position.zPos + zf + zl, GetRoom(item->Location, item->Position.xPos, y, item->Position.zPos).roomNumber).Position.Floor;
auto right = GetCollision(item->Position.xPos + xf + xr, y, item->Position.zPos + zf + zr, GetRoom(item->Location, item->Position.xPos, y, item->Position.zPos).roomNumber).Position.Floor;
2021-10-07 16:45:26 +03:00
2021-10-29 05:13:32 +03:00
// If specified, limit vertical search zone only to nearest height
2021-12-12 21:11:27 +11:00
if (heightLimit && (abs(left - y) > CLICK(0.5f) || abs(right - y) > CLICK(0.5f)))
2021-10-29 05:13:32 +03:00
return false;
// Determine allowed slope difference for a given collision radius
auto slopeDelta = ((float)STEPUP_HEIGHT / (float)SECTOR(1)) * (coll->Setup.Radius * 2);
2021-10-07 19:49:59 +03:00
// Discard if there is a slope beyond tolerance delta
if (abs(left - right) >= slopeDelta)
2021-10-07 16:45:26 +03:00
return false;
2021-11-01 05:26:15 +03:00
// Discard if ledge is not within distance threshold
2022-01-27 13:24:49 +11:00
if (abs(coll->NearestLedgeDistance) > OFFSET_RADIUS(coll->Setup.Radius))
2021-11-01 05:26:15 +03:00
return false;
// Discard if ledge is not within angle threshold
if (!TestValidLedgeAngle(item, coll))
2021-12-25 15:39:34 +11:00
return false;
2021-10-23 12:53:55 +03:00
if (!ignoreHeadroom)
{
auto headroom = (coll->Front.Floor + coll->Setup.Height) - coll->Middle.Ceiling;
2021-12-12 21:11:27 +11:00
if (headroom < CLICK(1))
2021-10-23 12:53:55 +03:00
return false;
}
2021-10-07 19:49:59 +03:00
return true;
2021-10-07 16:45:26 +03:00
}
bool TestValidLedgeAngle(ITEM_INFO* item, CollisionInfo* coll)
2021-10-27 01:23:59 +03:00
{
return (abs((short)(coll->NearestLedgeAngle - item->Position.yRot)) <= LARA_GRAB_THRESHOLD);
}
bool TestLaraHang(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2022-02-28 21:02:19 +11:00
auto angle = lara->Control.MoveAngle;
2021-09-14 00:03:24 +03:00
auto climbShift = 0;
2022-02-28 21:02:19 +11:00
if (lara->Control.MoveAngle == (short)(item->Position.yRot - ANGLE(90.0f)))
climbShift = -coll->Setup.Radius;
2022-02-28 21:02:19 +11:00
else if (lara->Control.MoveAngle == (short)(item->Position.yRot + ANGLE(90.0f)))
climbShift = coll->Setup.Radius;
2021-09-14 00:03:24 +03:00
// Temporarily move item a bit closer to the wall to get more precise coll results
auto oldPos = item->Position;
item->Position.xPos += phd_sin(item->Position.yRot) * coll->Setup.Radius * 0.5f;
item->Position.zPos += phd_cos(item->Position.yRot) * coll->Setup.Radius * 0.5f;
2021-11-29 20:45:22 +03:00
// Get height difference with side spaces (left or right, depending on movement direction)
2022-02-28 21:02:19 +11:00
auto hdif = LaraFloorFront(item, lara->Control.MoveAngle, coll->Setup.Radius * 1.4f);
// Set stopped flag, if floor height is above footspace which is step size
2021-12-12 21:11:27 +11:00
auto stopped = hdif < CLICK(0.5f);
// Set stopped flag, if ceiling height is below headspace which is step size
2022-02-28 21:02:19 +11:00
if (LaraCeilingFront(item, lara->Control.MoveAngle, coll->Setup.Radius * 1.5f, 0) > -950)
stopped = true;
2021-11-30 02:51:19 +03:00
// Backup item pos to restore it after coll tests
item->Position = oldPos;
2021-09-14 00:03:24 +03:00
2022-02-28 21:02:19 +11:00
// Setup coll lara
lara->Control.MoveAngle = item->Position.yRot;
coll->Setup.LowerFloorBound = NO_LOWER_BOUND;
coll->Setup.UpperFloorBound = -STEPUP_HEIGHT;
coll->Setup.LowerCeilingBound = 0;
2022-02-28 21:02:19 +11:00
coll->Setup.ForwardAngle = lara->Control.MoveAngle;
2021-09-14 00:03:24 +03:00
2021-10-24 05:33:32 +03:00
// When Lara is about to move, use larger embed offset for stabilizing diagonal shimmying)
auto embedOffset = 4;
2021-11-10 02:01:43 +11:00
if (TrInput & (IN_LEFT | IN_RIGHT))
2021-10-24 05:33:32 +03:00
embedOffset = 16;
item->Position.xPos += phd_sin(item->Position.yRot) * embedOffset;
item->Position.zPos += phd_cos(item->Position.yRot) * embedOffset;
2021-09-14 00:03:24 +03:00
GetCollisionInfo(coll, item);
2021-09-14 00:03:24 +03:00
2021-09-16 01:56:02 +03:00
bool result = false;
2022-02-28 21:02:19 +11:00
if (lara->Control.CanClimbLadder) // Ladder case
{
if (TrInput & IN_ACTION && item->HitPoints > 0)
{
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = angle;
2021-09-14 00:03:24 +03:00
2022-03-16 21:56:25 +11:00
if (!TestLaraHangOnClimbableWall(item, coll))
{
if (item->Animation.AnimNumber != LA_LADDER_TO_HANG_RIGHT &&
item->Animation.AnimNumber != LA_LADDER_TO_HANG_LEFT)
{
LaraSnapToEdgeOfBlock(item, coll, GetQuadrant(item->Position.yRot));
item->Position.yPos = coll->Setup.OldPosition.y;
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_REACH_TO_HANG, 21);
}
2021-11-10 02:01:43 +11:00
2021-09-14 00:03:24 +03:00
result = true;
}
else
{
if (item->Animation.AnimNumber == LA_REACH_TO_HANG && item->Animation.FrameNumber == GetFrameNumber(item, 21) &&
2022-03-16 21:56:25 +11:00
TestLaraClimbIdle(item, coll))
2021-11-10 02:01:43 +11:00
{
item->Animation.TargetState = LS_LADDER_IDLE;
2021-11-10 02:01:43 +11:00
}
}
}
2021-11-30 02:51:19 +03:00
else // Death or action release
{
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_FALL_START);
item->Position.yPos += CLICK(1);
item->Animation.Airborne = true;
item->Animation.Velocity = 2;
item->Animation.VerticalVelocity = 1;
2022-02-28 21:02:19 +11:00
lara->Control.HandStatus = HandStatus::Free;
}
}
2021-11-30 02:51:19 +03:00
else // Normal case
{
if (TrInput & IN_ACTION && item->HitPoints > 0 && coll->Front.Floor <= 0)
{
if (stopped && hdif > 0 && climbShift != 0 && (climbShift > 0 == coll->MiddleLeft.Floor > coll->MiddleRight.Floor))
stopped = false;
2021-09-14 00:03:24 +03:00
auto verticalShift = coll->Front.Floor - GetBoundsAccurate(item)->Y1;
auto x = item->Position.xPos;
auto z = item->Position.zPos;
2021-09-14 00:03:24 +03:00
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = angle;
if (climbShift != 0)
{
2022-02-28 21:02:19 +11:00
auto s = phd_sin(lara->Control.MoveAngle);
auto c = phd_cos(lara->Control.MoveAngle);
auto testShift = Vector2(s * climbShift, c * climbShift);
2021-10-23 15:35:59 +03:00
x += testShift.x;
z += testShift.y;
}
2021-09-14 00:03:24 +03:00
if ((256 << GetQuadrant(item->Position.yRot)) & GetClimbFlags(x, item->Position.yPos, z, item->RoomNumber))
{
2022-03-16 21:56:25 +11:00
if (!TestLaraHangOnClimbableWall(item, coll))
2021-11-30 02:51:19 +03:00
verticalShift = 0; // Ignore vertical shift if ladder is encountered next block
}
2021-10-23 15:35:59 +03:00
else if (!TestValidLedge(item, coll, true))
{
2022-03-16 21:56:25 +11:00
if ((climbShift < 0 && coll->FrontLeft.Floor != coll->Front.Floor) ||
(climbShift > 0 && coll->FrontRight.Floor != coll->Front.Floor))
2022-01-29 07:54:04 +11:00
{
stopped = true;
2022-01-29 07:54:04 +11:00
}
}
2021-09-14 00:03:24 +03:00
2022-03-16 21:56:25 +11:00
if (!stopped &&
coll->Middle.Ceiling < 0 && coll->CollisionType == CT_FRONT && !coll->HitStatic &&
abs(verticalShift) < SLOPE_DIFFERENCE && TestValidLedgeAngle(item, coll))
{
if (item->Animation.Velocity != 0)
2021-10-23 15:35:59 +03:00
SnapItemToLedge(item, coll);
2021-09-14 00:03:24 +03:00
item->Position.yPos += verticalShift;
}
else
{
item->Position.xPos = coll->Setup.OldPosition.x;
item->Position.yPos = coll->Setup.OldPosition.y;
item->Position.zPos = coll->Setup.OldPosition.z;
2021-09-14 00:03:24 +03:00
2022-03-16 21:56:25 +11:00
if (item->Animation.ActiveState == LS_SHIMMY_LEFT ||
item->Animation.ActiveState == LS_SHIMMY_RIGHT)
{
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_REACH_TO_HANG, 21);
}
2021-09-14 00:03:24 +03:00
result = true;
}
}
2022-01-29 07:54:04 +11:00
else // Death, incorrect ledge or ACTION release
{
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_JUMP_UP, 9);
item->Position.xPos += coll->Shift.x;
item->Position.yPos += GetBoundsAccurate(item)->Y2 * 1.8f;
item->Position.zPos += coll->Shift.z;
item->Animation.Airborne = true;
item->Animation.Velocity = 2;
item->Animation.VerticalVelocity = 1;
2022-02-28 21:02:19 +11:00
lara->Control.HandStatus = HandStatus::Free;
}
}
2021-09-14 00:03:24 +03:00
return result;
}
2022-03-16 21:56:25 +11:00
bool TestLaraHangJump(ITEM_INFO* item, CollisionInfo* coll)
2022-01-14 19:25:56 +03:00
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2022-03-16 21:56:25 +11:00
if (!(TrInput & IN_ACTION) || lara->Control.HandStatus != HandStatus::Free || coll->HitStatic)
return false;
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (TestLaraMonkeyGrab(item, coll))
{
SetAnimation(item, LA_REACH_TO_MONKEY);
ResetLaraFlex(item);
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
item->Position.yPos += coll->Middle.Ceiling + (LARA_HEIGHT_MONKEY - coll->Setup.Height);
lara->Control.HandStatus = HandStatus::Busy;
return true;
}
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (coll->Middle.Ceiling > -STEPUP_HEIGHT ||
coll->Middle.Floor < 200 ||
coll->CollisionType != CT_FRONT)
{
return false;
}
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
int edge;
auto edgeCatch = TestLaraEdgeCatch(item, coll, &edge);
if (!edgeCatch)
return false;
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
bool ladder = TestLaraHangOnClimbableWall(item, coll);
if (!(ladder && edgeCatch) &&
!(TestValidLedge(item, coll, true, true) && edgeCatch > 0))
2022-01-14 19:25:56 +03:00
{
2022-03-16 21:56:25 +11:00
return false;
}
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (TestHangSwingIn(item, coll))
{
SetAnimation(item, LA_REACH_TO_HANG_OSCILLATE);
ResetLaraFlex(item);
}
else
SetAnimation(item, LA_REACH_TO_HANG);
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
auto bounds = GetBoundsAccurate(item);
if (edgeCatch <= 0)
{
item->Position.yPos = edge - bounds->Y1 - 20;
item->Position.yRot = coll->NearestLedgeAngle;
}
else
item->Position.yPos += coll->Front.Floor - bounds->Y1 - 20;
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (ladder)
SnapItemToGrid(item, coll); // HACK: until fragile ladder code is refactored, we must exactly snap to grid.
else
SnapItemToLedge(item, coll, 0.2f);
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
item->Animation.Velocity = 2;
item->Animation.VerticalVelocity = 1;
item->Animation.Airborne = true;
lara->Control.HandStatus = HandStatus::Busy;
return true;
}
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
bool TestLaraHangJumpUp(ITEM_INFO* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (!(TrInput & IN_ACTION) || lara->Control.HandStatus != HandStatus::Free || coll->HitStatic)
return false;
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (TestLaraMonkeyGrab(item, coll))
{
SetAnimation(item, LA_JUMP_UP_TO_MONKEY);
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
item->Position.yPos += coll->Middle.Ceiling + (LARA_HEIGHT_MONKEY - coll->Setup.Height);
lara->Control.HandStatus = HandStatus::Busy;
return true;
}
2022-01-14 19:25:56 +03:00
2022-03-16 21:56:25 +11:00
if (coll->Middle.Ceiling > -STEPUP_HEIGHT || coll->CollisionType != CT_FRONT)
return false;
int edge;
auto edgeCatch = TestLaraEdgeCatch(item, coll, &edge);
if (!edgeCatch)
return false;
bool ladder = TestLaraHangOnClimbableWall(item, coll);
if (!(ladder && edgeCatch) &&
!(TestValidLedge(item, coll, true, true) && edgeCatch > 0))
{
return false;
}
SetAnimation(item, LA_REACH_TO_HANG, 12);
auto bounds = GetBoundsAccurate(item);
if (edgeCatch <= 0)
item->Position.yPos = edge - bounds->Y1 + 4;
else
item->Position.yPos += coll->Front.Floor - bounds->Y1;
if (ladder)
SnapItemToGrid(item, coll); // HACK: until fragile ladder code is refactored, we must exactly snap to grid.
else
SnapItemToLedge(item, coll);
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
lara->Control.HandStatus = HandStatus::Busy;
lara->ExtraTorsoRot = PHD_3DPOS();
return true;
}
int TestLaraEdgeCatch(ITEM_INFO* item, CollisionInfo* coll, int* edge)
{
BOUNDING_BOX* bounds = GetBoundsAccurate(item);
int hdif = coll->Front.Floor - bounds->Y1;
if (hdif < 0 == hdif + item->Animation.VerticalVelocity < 0)
{
hdif = item->Position.yPos + bounds->Y1;
if ((hdif + item->Animation.VerticalVelocity & 0xFFFFFF00) != (hdif & 0xFFFFFF00))
2022-01-14 19:25:56 +03:00
{
2022-03-16 21:56:25 +11:00
if (item->Animation.VerticalVelocity > 0)
*edge = (hdif + item->Animation.VerticalVelocity) & 0xFFFFFF00;
else
*edge = hdif & 0xFFFFFF00;
return -1;
2022-01-14 19:25:56 +03:00
}
2022-03-16 21:56:25 +11:00
return 0;
2022-01-14 19:25:56 +03:00
}
2022-03-16 21:56:25 +11:00
if (!TestValidLedge(item, coll, true))
return 0;
return 1;
2022-01-14 19:25:56 +03:00
}
2022-03-16 21:56:25 +11:00
bool TestLaraClimbIdle(ITEM_INFO* item, CollisionInfo* coll)
{
int shiftRight, shiftLeft;
if (LaraTestClimbPos(item, coll->Setup.Radius, coll->Setup.Radius + CLICK(0.5f), -700, CLICK(2), &shiftRight) != 1)
return false;
if (LaraTestClimbPos(item, coll->Setup.Radius, -(coll->Setup.Radius + CLICK(0.5f)), -700, CLICK(2), &shiftLeft) != 1)
return false;
if (shiftRight)
{
if (shiftLeft)
{
if (shiftRight < 0 != shiftLeft < 0)
return false;
if ((shiftRight < 0 && shiftLeft < shiftRight) ||
(shiftRight > 0 && shiftLeft > shiftRight))
{
item->Position.yPos += shiftLeft;
return true;
}
}
item->Position.yPos += shiftRight;
}
else if (shiftLeft)
item->Position.yPos += shiftLeft;
return true;
}
bool TestLaraHangOnClimbableWall(ITEM_INFO* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
int shift, result;
if (!lara->Control.CanClimbLadder)
return false;
if (item->Animation.VerticalVelocity < 0)
return false;
// HACK: Climb wall tests are highly fragile and depend on quadrant shifts.
// Until climb wall tests are fully refactored, we need to recalculate COLL_INFO.
auto coll2 = *coll;
coll2.Setup.Mode = CollisionProbeMode::Quadrants;
GetCollisionInfo(&coll2, item);
switch (GetQuadrant(item->Position.yRot))
{
case NORTH:
case SOUTH:
item->Position.zPos += coll2.Shift.z;
break;
case EAST:
case WEST:
item->Position.xPos += coll2.Shift.x;
break;
default:
break;
}
auto bounds = GetBoundsAccurate(item);
if (lara->Control.MoveAngle != item->Position.yRot)
{
short l = LaraCeilingFront(item, item->Position.yRot, 0, 0);
short r = LaraCeilingFront(item, lara->Control.MoveAngle, CLICK(0.5f), 0);
if (abs(l - r) > SLOPE_DIFFERENCE)
return false;
}
if (LaraTestClimbPos(item, LARA_RAD, LARA_RAD, bounds->Y1, bounds->Y2 - bounds->Y1, &shift) &&
LaraTestClimbPos(item, LARA_RAD, -LARA_RAD, bounds->Y1, bounds->Y2 - bounds->Y1, &shift))
{
result = LaraTestClimbPos(item, LARA_RAD, 0, bounds->Y1, bounds->Y2 - bounds->Y1, &shift);
if (result)
{
if (result != 1)
item->Position.yPos += shift;
return true;
}
}
return false;
}
bool TestLaraValidHangPosition(ITEM_INFO* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
// Get incoming ledge height and own Lara's upper bound.
// First one will be negative while first one is positive.
// Difference between two indicates difference in height between ledges.
2022-03-17 01:19:21 +11:00
auto frontFloor = GetCollision(item, lara->Control.MoveAngle, coll->Setup.Radius + CLICK(0.5f), -LARA_HEIGHT).Position.Floor;
2022-03-16 21:56:25 +11:00
auto laraUpperBound = item->Position.yPos - coll->Setup.Height;
// If difference is above 1/2 click, return false (ledge is out of reach).
if (abs(frontFloor - laraUpperBound) > CLICK(0.5f))
return false;
// Embed Lara into wall to make collision test succeed
item->Position.xPos += phd_sin(item->Position.yRot) * 8;
item->Position.zPos += phd_cos(item->Position.yRot) * 8;
// Setup new GCI call
lara->Control.MoveAngle = item->Position.yRot;
coll->Setup.LowerFloorBound = NO_LOWER_BOUND;
coll->Setup.UpperFloorBound = -CLICK(2);
coll->Setup.LowerCeilingBound = 0;
coll->Setup.Mode = CollisionProbeMode::FreeFlat;
coll->Setup.ForwardAngle = lara->Control.MoveAngle;
GetCollisionInfo(coll, item);
// Filter out narrow ceiling spaces, no collision cases and statics in front.
if (coll->Middle.Ceiling >= 0 || coll->CollisionType != CT_FRONT || coll->HitStatic)
return false;
// Finally, do ordinary ledge checks (slope difference etc.)
return TestValidLedge(item, coll);
}
CornerType TestLaraHangCorner(ITEM_INFO* item, CollisionInfo* coll, float testAngle)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2021-10-26 12:50:26 +03:00
// Lara isn't in stop state yet, bypass test
if (item->Animation.AnimNumber != LA_REACH_TO_HANG && item->Animation.AnimNumber != LA_HANG_IDLE)
2022-03-16 21:56:25 +11:00
return CornerType::None;
2021-10-26 12:50:26 +03:00
// Static is in the way, bypass test
2021-09-10 00:18:47 +03:00
if (coll->HitStatic)
2022-03-16 21:56:25 +11:00
return CornerType::None;
2021-10-26 12:50:26 +03:00
// INNER CORNER TESTS
2021-10-26 12:50:26 +03:00
// Backup old Lara position and frontal collision
auto oldPos = item->Position;
2022-02-28 21:02:19 +11:00
auto oldMoveAngle = lara->Control.MoveAngle;
2022-01-14 19:25:56 +03:00
auto cornerResult = TestItemAtNextCornerPosition(item, coll, testAngle, false);
// Do further testing only if test angle is equal to resulting edge angle
2022-01-14 19:25:56 +03:00
if (cornerResult.Success)
{
2021-11-29 18:09:34 +03:00
// Get bounding box height for further ledge height calculations
auto bounds = GetBoundsAccurate(item);
// Store next position
item->Position = cornerResult.RealPositionResult;
2022-02-28 21:02:19 +11:00
lara->NextCornerPos.xPos = item->Position.xPos;
2022-03-17 01:19:21 +11:00
lara->NextCornerPos.yPos = GetCollision(item, item->Position.yRot, coll->Setup.Radius * 2, -(abs(bounds->Y1) + LARA_HEADROOM)).Position.Floor + abs(bounds->Y1);
2022-02-28 21:02:19 +11:00
lara->NextCornerPos.zPos = item->Position.zPos;
lara->NextCornerPos.yRot = item->Position.yRot;
lara->Control.MoveAngle = item->Position.yRot;
2022-03-16 21:56:25 +11:00
item->Position = cornerResult.ProbeResult;
2022-03-16 21:56:25 +11:00
auto result = TestLaraValidHangPosition(item, coll);
2021-10-26 12:50:26 +03:00
2021-11-29 18:21:50 +03:00
// Restore original item positions
item->Position = oldPos;
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = oldMoveAngle;
2021-10-27 02:24:55 +03:00
if (result)
2022-03-16 21:56:25 +11:00
return CornerType::Inner;
2021-10-27 02:24:55 +03:00
2022-02-28 21:02:19 +11:00
if (lara->Control.CanClimbLadder)
{
2021-12-12 11:10:08 +03:00
auto& angleSet = testAngle > 0 ? LeftExtRightIntTab : LeftIntRightExtTab;
2022-02-28 21:02:19 +11:00
if (GetClimbFlags(lara->NextCornerPos.xPos, item->Position.yPos, lara->NextCornerPos.zPos, item->RoomNumber) & (short)angleSet[GetQuadrant(item->Position.yRot)])
2021-12-12 11:39:35 +03:00
{
2022-02-28 21:02:19 +11:00
lara->NextCornerPos.yPos = item->Position.yPos; // Restore original Y pos for ladder tests because we don't snap to ledge height in such case.
2022-03-16 21:56:25 +11:00
return CornerType::Inner;
2021-12-12 11:39:35 +03:00
}
}
}
2021-10-27 02:24:55 +03:00
// Restore original item positions
item->Position = oldPos;
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = oldMoveAngle;
2021-10-26 12:50:26 +03:00
// OUTER CORNER TESTS
2021-10-26 13:39:42 +03:00
// Test if there's a material obstacles blocking outer corner pathway
if ((LaraFloorFront(item, item->Position.yRot + ANGLE(testAngle), coll->Setup.Radius + CLICK(1)) < 0) ||
(LaraCeilingFront(item, item->Position.yRot + ANGLE(testAngle), coll->Setup.Radius + CLICK(1), coll->Setup.Height) > 0))
2022-03-16 21:56:25 +11:00
return CornerType::None;
2021-10-28 01:13:28 +03:00
// Last chance for possible diagonal vs. non-diagonal cases: ray test
2022-03-04 21:30:09 +11:00
if (!LaraPositionOnLOS(item, item->Position.yRot + ANGLE(testAngle), coll->Setup.Radius + CLICK(1)))
2022-03-16 21:56:25 +11:00
return CornerType::None;
2022-01-14 19:25:56 +03:00
cornerResult = TestItemAtNextCornerPosition(item, coll, testAngle, true);
2021-11-29 18:21:50 +03:00
// Additional test if there's a material obstacles blocking outer corner pathway
if ((LaraFloorFront(item, item->Position.yRot, 0) < 0) ||
(LaraCeilingFront(item, item->Position.yRot, 0, coll->Setup.Height) > 0))
2022-01-14 19:25:56 +03:00
cornerResult.Success = false;
2021-11-29 18:21:50 +03:00
// Do further testing only if test angle is equal to resulting edge angle
2022-01-14 19:25:56 +03:00
if (cornerResult.Success)
{
2021-11-29 18:09:34 +03:00
// Get bounding box height for further ledge height calculations
auto bounds = GetBoundsAccurate(item);
// Store next position
item->Position = cornerResult.RealPositionResult;
2022-02-28 21:02:19 +11:00
lara->NextCornerPos.xPos = item->Position.xPos;
2022-03-17 01:19:21 +11:00
lara->NextCornerPos.yPos = GetCollision(item, item->Position.yRot, coll->Setup.Radius * 2, -(abs(bounds->Y1) + LARA_HEADROOM)).Position.Floor + abs(bounds->Y1);
2022-02-28 21:02:19 +11:00
lara->NextCornerPos.zPos = item->Position.zPos;
lara->NextCornerPos.yRot = item->Position.yRot;
lara->Control.MoveAngle = item->Position.yRot;
item->Position = cornerResult.ProbeResult;
2022-03-16 21:56:25 +11:00
auto result = TestLaraValidHangPosition(item, coll);
2021-11-29 18:21:50 +03:00
// Restore original item positions
item->Position = oldPos;
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = oldMoveAngle;
2021-11-29 18:21:50 +03:00
if (result)
2022-03-16 21:56:25 +11:00
return CornerType::Outer;
2022-02-28 21:02:19 +11:00
if (lara->Control.CanClimbLadder)
{
2021-12-12 11:10:08 +03:00
auto& angleSet = testAngle > 0 ? LeftIntRightExtTab : LeftExtRightIntTab;
2022-02-28 21:02:19 +11:00
if (GetClimbFlags(lara->NextCornerPos.xPos, item->Position.yPos, lara->NextCornerPos.zPos, item->RoomNumber) & (short)angleSet[GetQuadrant(item->Position.yRot)])
2021-12-12 11:39:35 +03:00
{
2022-02-28 21:02:19 +11:00
lara->NextCornerPos.yPos = item->Position.yPos; // Restore original Y pos for ladder tests because we don't snap to ledge height in such case.
2022-03-16 21:56:25 +11:00
return CornerType::Outer;
2021-12-12 11:39:35 +03:00
}
}
}
2021-10-27 02:24:55 +03:00
// Restore original item positions
item->Position = oldPos;
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = oldMoveAngle;
2022-03-16 21:56:25 +11:00
return CornerType::None;
}
2022-03-16 21:56:25 +11:00
CornerTestResult TestItemAtNextCornerPosition(ITEM_INFO* item, CollisionInfo* coll, float angle, bool outer)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2022-03-16 21:56:25 +11:00
auto result = CornerTestResult();
2022-03-16 21:56:25 +11:00
// Determine real turning angle
auto turnAngle = outer ? angle : -angle;
2022-03-16 21:56:25 +11:00
// Backup previous position into array
PHD_3DPOS pos[3] = { item->Position, item->Position, item->Position };
2022-03-16 21:56:25 +11:00
// Do a two-step rotation check. First step is real resulting position, and second step is probing
// position. We need this because checking at exact ending position does not always return
// correct results with nearest ledge angle.
2022-03-16 21:56:25 +11:00
for (int i = 0; i < 2; i++)
{
2022-03-16 21:56:25 +11:00
// Determine collision box anchor point and rotate collision box around this anchor point.
// Then determine new test position from centerpoint of new collision box position.
2022-03-16 21:56:25 +11:00
// Push back item a bit to compensate for possible edge ledge cases
pos[i].xPos -= round((coll->Setup.Radius * (outer ? -0.2f : 0.2f)) * phd_sin(pos[i].yRot));
pos[i].zPos -= round((coll->Setup.Radius * (outer ? -0.2f : 0.2f)) * phd_cos(pos[i].yRot));
2022-03-16 21:56:25 +11:00
// Move item at the distance of full collision diameter plus half-radius margin to movement direction
pos[i].xPos += round((coll->Setup.Radius * (i == 0 ? 2.0f : 2.5f)) * phd_sin(lara->Control.MoveAngle));
pos[i].zPos += round((coll->Setup.Radius * (i == 0 ? 2.0f : 2.5f)) * phd_cos(lara->Control.MoveAngle));
2022-03-16 21:56:25 +11:00
// Determine anchor point
auto cX = pos[i].xPos + round(coll->Setup.Radius * phd_sin(pos[i].yRot));
auto cZ = pos[i].zPos + round(coll->Setup.Radius * phd_cos(pos[i].yRot));
cX += (coll->Setup.Radius * phd_sin(pos[i].yRot + ANGLE(90.0f * -std::copysign(1.0f, angle))));
cZ += (coll->Setup.Radius * phd_cos(pos[i].yRot + ANGLE(90.0f * -std::copysign(1.0f, angle))));
2022-03-16 21:56:25 +11:00
// Determine distance from anchor point to new item position
auto dist = Vector2(pos[i].xPos, pos[i].zPos) - Vector2(cX, cZ);
auto s = phd_sin(ANGLE(turnAngle));
auto c = phd_cos(ANGLE(turnAngle));
2021-11-10 02:01:43 +11:00
2022-03-16 21:56:25 +11:00
// Shift item to a new anchor point
pos[i].xPos = dist.x * c - dist.y * s + cX;
pos[i].zPos = dist.x * s + dist.y * c + cZ;
2022-03-16 21:56:25 +11:00
// Virtually rotate item to new angle
short newAngle = pos[i].yRot - ANGLE(turnAngle);
pos[i].yRot = newAngle;
2022-03-16 21:56:25 +11:00
// Snap to nearest ledge, if any.
item->Position = pos[i];
SnapItemToLedge(item, coll, item->Position.yRot);
2022-03-16 21:56:25 +11:00
// Copy resulting position to an array and restore original item position.
pos[i] = item->Position;
item->Position = pos[2];
2022-03-16 21:56:25 +11:00
if (i == 1) // Both passes finished, construct the result.
{
2022-03-16 21:56:25 +11:00
result.RealPositionResult = pos[0];
result.ProbeResult = pos[1];
result.Success = newAngle == pos[i].yRot;
}
}
2022-03-16 21:56:25 +11:00
return result;
}
bool TestHangSwingIn(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
auto probe = GetCollision(item, item->Position.yRot, OFFSET_RADIUS(coll->Setup.Radius));
if ((probe.Position.Floor - y) > 0 &&
(probe.Position.Ceiling - y) < -CLICK(1.6f) &&
probe.Position.Floor != NO_HEIGHT)
{
return true;
}
2021-09-13 09:12:46 +03:00
return false;
2021-07-09 19:41:56 -05:00
}
bool TestLaraHangSideways(ITEM_INFO* item, CollisionInfo* coll, short angle)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2021-12-30 22:17:36 +11:00
auto oldPos = item->Position;
2022-02-28 21:02:19 +11:00
lara->Control.MoveAngle = item->Position.yRot + angle;
2021-12-15 15:21:39 +03:00
2021-12-01 11:59:24 +03:00
static constexpr auto sidewayTestDistance = 16;
2022-02-28 21:02:19 +11:00
item->Position.xPos += phd_sin(lara->Control.MoveAngle) * sidewayTestDistance;
item->Position.zPos += phd_cos(lara->Control.MoveAngle) * sidewayTestDistance;
coll->Setup.OldPosition.y = item->Position.yPos;
2021-12-30 22:17:36 +11:00
bool res = TestLaraHang(item, coll);
item->Position = oldPos;
return !res;
}
2022-03-16 21:56:25 +11:00
bool TestLaraWall(ITEM_INFO* item, int distance, int height, int side)
{
float s = phd_sin(item->Position.yRot);
float c = phd_cos(item->Position.yRot);
auto start = GAME_VECTOR(
item->Position.xPos + (side * c),
item->Position.yPos + height,
item->Position.zPos + (-side * s),
item->RoomNumber);
auto end = GAME_VECTOR(
item->Position.xPos + (distance * s) + (side * c),
item->Position.yPos + height,
item->Position.zPos + (distance * c) + (-side * s),
item->RoomNumber);
return !LOS(&start, &end);
}
bool TestLaraFacingCorner(ITEM_INFO* item, short angle, int distance)
{
short angleLeft = angle - ANGLE(15.0f);
short angleRight = angle + ANGLE(15.0f);
2022-02-07 23:07:40 +11:00
auto start = GAME_VECTOR(
item->Position.xPos,
item->Position.yPos - STEPUP_HEIGHT,
item->Position.zPos,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end1 = GAME_VECTOR(
item->Position.xPos + distance * phd_sin(angleLeft),
item->Position.yPos - STEPUP_HEIGHT,
item->Position.zPos + distance * phd_cos(angleLeft),
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end2 = GAME_VECTOR(
item->Position.xPos + distance * phd_sin(angleRight),
item->Position.yPos - STEPUP_HEIGHT,
item->Position.zPos + distance * phd_cos(angleRight),
item->RoomNumber);
2021-12-28 16:09:05 +11:00
bool result1 = LOS(&start, &end1);
bool result2 = LOS(&start, &end2);
return (!result1 && !result2);
}
bool LaraPositionOnLOS(ITEM_INFO* item, short angle, int distance)
{
2022-02-07 23:07:40 +11:00
auto start1 = GAME_VECTOR(
item->Position.xPos,
item->Position.yPos - LARA_HEADROOM,
item->Position.zPos,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto start2 = GAME_VECTOR(
item->Position.xPos,
item->Position.yPos - LARA_HEIGHT + LARA_HEADROOM,
item->Position.zPos,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end1 = GAME_VECTOR(
item->Position.xPos + distance * phd_sin(angle),
item->Position.yPos - LARA_HEADROOM,
item->Position.zPos + distance * phd_cos(angle),
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end2 = GAME_VECTOR(
item->Position.xPos + distance * phd_sin(angle),
item->Position.yPos - LARA_HEIGHT + LARA_HEADROOM,
item->Position.zPos + distance * phd_cos(angle),
item->RoomNumber);
2022-01-29 07:54:04 +11:00
auto result1 = LOS(&start1, &end1);
auto result2 = LOS(&start2, &end2);
return (result1 && result2);
}
int LaraFloorFront(ITEM_INFO* item, short angle, int distance)
{
return LaraCollisionFront(item, angle, distance).Position.Floor;
}
2022-03-16 21:56:25 +11:00
int LaraCeilingFront(ITEM_INFO* item, short angle, int distance, int height)
{
return LaraCeilingCollisionFront(item, angle, distance, height).Position.Ceiling;
}
CollisionResult LaraCollisionFront(ITEM_INFO* item, short angle, int distance)
{
auto probe = GetCollision(item, angle, distance, -LARA_HEIGHT);
if (probe.Position.Floor != NO_HEIGHT)
probe.Position.Floor -= item->Position.yPos;
return probe;
}
CollisionResult LaraCeilingCollisionFront(ITEM_INFO* item, short angle, int distance, int height)
{
auto probe = GetCollision(item, angle, distance, -height);
if (probe.Position.Ceiling != NO_HEIGHT)
probe.Position.Ceiling += height - item->Position.yPos;
return probe;
}
bool TestLaraWaterStepOut(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
if (coll->CollisionType == CT_FRONT ||
2022-01-18 16:52:52 +11:00
coll->Middle.FloorSlope ||
coll->Middle.Floor >= 0)
{
return false;
}
2021-12-12 21:11:27 +11:00
if (coll->Middle.Floor >= -CLICK(0.5f))
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_STAND_IDLE);
else
{
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_WADE_1_STEP);
item->Animation.TargetState = LS_IDLE;
}
item->Position.yPos += coll->Middle.Floor + CLICK(2.75f) - 9;
UpdateItemRoom(item, -(STEPUP_HEIGHT - 3));
item->Position.xRot = 0;
2022-02-28 21:02:19 +11:00
item->Position.zRot = 0;
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
2022-02-28 21:02:19 +11:00
lara->Control.WaterStatus = WaterStatus::Wade;
return true;
}
bool TestLaraWaterClimbOut(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2021-11-21 13:47:55 +11:00
if (coll->CollisionType != CT_FRONT || !(TrInput & IN_ACTION))
return false;
2022-02-28 21:02:19 +11:00
if (lara->Control.HandStatus != HandStatus::Free &&
(lara->Control.HandStatus != HandStatus::WeaponReady || lara->Control.Weapon.GunType != LaraWeaponType::Flare))
{
return false;
}
if (coll->Middle.Ceiling > -STEPUP_HEIGHT)
return false;
int frontFloor = coll->Front.Floor + LARA_HEIGHT_SURFSWIM;
2021-12-12 21:11:27 +11:00
if (frontFloor <= -CLICK(2) ||
frontFloor > CLICK(1.25f) - 4)
{
return false;
}
if (!TestValidLedge(item, coll))
return false;
2022-03-17 01:19:21 +11:00
auto probe = GetCollision(item, coll->Setup.ForwardAngle, CLICK(2), -CLICK(1));
int headroom = probe.Position.Floor - probe.Position.Ceiling;
2021-12-12 21:11:27 +11:00
if (frontFloor <= -CLICK(1))
{
if (headroom < LARA_HEIGHT)
{
2022-03-04 15:51:53 +11:00
if (g_GameFlow->Animations.HasCrawlExtended)
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_CROUCH_1_STEP);
else
return false;
}
else
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_STAND_1_STEP);
}
2021-12-12 21:11:27 +11:00
else if (frontFloor > CLICK(0.5f))
{
if (headroom < LARA_HEIGHT)
{
2022-03-04 15:51:53 +11:00
if (g_GameFlow->Animations.HasCrawlExtended)
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_CROUCH_M1_STEP);
else
return false;
}
else
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_STAND_M1_STEP);
}
else
{
if (headroom < LARA_HEIGHT)
{
2022-03-04 15:51:53 +11:00
if (g_GameFlow->Animations.HasCrawlExtended)
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_CROUCH_0_STEP);
else
return false;
}
else
2022-02-18 23:19:46 +11:00
SetAnimation(item, LA_ONWATER_TO_STAND_0_STEP);
}
UpdateItemRoom(item, -LARA_HEIGHT / 2);
SnapItemToLedge(item, coll, 1.7f);
item->Position.yPos += frontFloor - 5;
item->Animation.ActiveState = LS_ONWATER_EXIT;
item->Animation.Airborne = false;
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
2022-02-28 21:02:19 +11:00
lara->Control.HandStatus = HandStatus::Busy;
lara->Control.WaterStatus = WaterStatus::Dry;
return true;
}
bool TestLaraLadderClimbOut(ITEM_INFO* item, CollisionInfo* coll) // NEW function for water to ladder move
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
if (!(TrInput & IN_ACTION) ||
2022-02-28 21:02:19 +11:00
!lara->Control.CanClimbLadder ||
coll->CollisionType != CT_FRONT)
{
return false;
}
2022-02-28 21:02:19 +11:00
if (lara->Control.HandStatus != HandStatus::Free &&
(lara->Control.HandStatus != HandStatus::WeaponReady || lara->Control.Weapon.GunType != LaraWeaponType::Flare))
{
return false;
}
2022-03-16 21:56:25 +11:00
if (!TestLaraClimbIdle(item, coll))
return false;
short rot = item->Position.yRot;
if (rot >= -ANGLE(35.0f) && rot <= ANGLE(35.0f))
rot = 0;
else if (rot >= ANGLE(55.0f) && rot <= ANGLE(125.0f))
rot = ANGLE(90.0f);
else if (rot >= ANGLE(145.0f) || rot <= -ANGLE(145.0f))
rot = ANGLE(180.0f);
else if (rot >= -ANGLE(125.0f) && rot <= -ANGLE(55.0f))
rot = -ANGLE(90.0f);
if (rot & 0x3FFF)
return false;
switch ((unsigned short)rot / ANGLE(90.0f))
{
case NORTH:
item->Position.zPos = (item->Position.zPos | (SECTOR(1) - 1)) - LARA_RAD - 1;
break;
case EAST:
item->Position.xPos = (item->Position.xPos | (SECTOR(1) - 1)) - LARA_RAD - 1;
break;
case SOUTH:
item->Position.zPos = (item->Position.zPos & -SECTOR(1)) + LARA_RAD + 1;
break;
case WEST:
item->Position.xPos = (item->Position.xPos & -SECTOR(1)) + LARA_RAD + 1;
break;
}
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_ONWATER_IDLE);
item->Animation.TargetState = LS_LADDER_IDLE;
AnimateLara(item);
item->Position.yRot = rot;
item->Position.yPos -= 10;//otherwise she falls back into the water
item->Position.xRot = 0;
item->Position.zRot = 0;
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
2022-02-28 21:02:19 +11:00
lara->Control.TurnRate = 0;
lara->Control.HandStatus = HandStatus::Busy;
lara->Control.WaterStatus = WaterStatus::Dry;
return true;
}
void TestLaraWaterDepth(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
short roomNum = item->RoomNumber;
FLOOR_INFO* floor = GetFloor(item->Position.xPos, item->Position.yPos, item->Position.zPos, &roomNum);
int waterDepth = GetWaterDepth(item->Position.xPos, item->Position.yPos, item->Position.zPos, roomNum);
if (waterDepth == NO_HEIGHT)
{
item->Animation.VerticalVelocity = 0;
item->Position.xPos = coll->Setup.OldPosition.x;
item->Position.yPos = coll->Setup.OldPosition.y;
item->Position.zPos = coll->Setup.OldPosition.z;
}
2021-12-12 21:11:27 +11:00
// Height check was at CLICK(2) before but changed to this
// because now Lara surfaces on a head level, not mid-body level.
else if (waterDepth <= LARA_HEIGHT - LARA_HEADROOM / 2)
{
2021-11-10 02:41:59 +03:00
SetAnimation(item, LA_UNDERWATER_TO_STAND);
item->Animation.TargetState = LS_IDLE;
item->Position.zRot = 0;
item->Position.xRot = 0;
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
item->Position.yPos = GetFloorHeight(floor, item->Position.xPos, item->Position.yPos, item->Position.zPos);
2022-02-28 21:02:19 +11:00
lara->Control.WaterStatus = WaterStatus::Wade;
}
}
#ifndef NEW_TIGHTROPE
void GetTightropeFallOff(ITEM_INFO* item, int regularity)
2022-01-27 22:26:05 +11:00
{
2022-03-16 21:56:25 +11:00
auto* lara = GetLaraInfo(item);
if (item->HitPoints <= 0 || item->HitStatus)
SetAnimation(item, LA_TIGHTROPE_FALL_LEFT);
2022-03-16 21:56:25 +11:00
if (!lara->Control.Tightrope.Fall && !(GetRandomControl() & regularity))
lara->Control.Tightrope.Fall = 2 - ((GetRandomControl() & 0xF) != 0);
}
#endif
2022-03-16 21:56:25 +11:00
bool IsStandingWeapon(LaraWeaponType weaponType)
{
if (weaponType == LaraWeaponType::Shotgun ||
weaponType == LaraWeaponType::HK ||
weaponType == LaraWeaponType::Crossbow ||
weaponType == LaraWeaponType::Torch ||
weaponType == LaraWeaponType::GrenadeLauncher ||
weaponType == LaraWeaponType::HarpoonGun ||
weaponType == LaraWeaponType::RocketLauncher||
weaponType == LaraWeaponType::Snowmobile)
{
return true;
}
return false;
}
bool IsVaultState(LaraState state)
{
2022-03-16 21:56:25 +11:00
if (state == LS_VAULT ||
state == LS_VAULT_2_STEPS ||
state == LS_VAULT_3_STEPS ||
state == LS_VAULT_1_STEP_CROUCH ||
state == LS_VAULT_2_STEPS_CROUCH ||
state == LS_VAULT_3_STEPS_CROUCH ||
state == LS_AUTO_JUMP)
{
return true;
}
return false;
}
2022-02-12 16:25:59 +11:00
bool IsJumpState(LaraState state)
2022-02-02 23:11:57 +11:00
{
if (state == LS_JUMP_FORWARD ||
state == LS_JUMP_BACK ||
state == LS_JUMP_LEFT ||
state == LS_JUMP_RIGHT ||
state == LS_JUMP_UP ||
state == LS_FALL_BACK ||
2022-02-03 12:21:28 +11:00
state == LS_REACH ||
state == LS_SWAN_DIVE ||
state == LS_FREEFALL_DIVE ||
state == LS_FREEFALL)
2022-02-02 23:11:57 +11:00
{
return true;
}
return false;
}
2022-02-12 16:25:59 +11:00
bool IsRunJumpQueueableState(LaraState state)
{
if (state == LS_RUN_FORWARD ||
state == LS_STEP_UP ||
state == LS_STEP_DOWN)
{
return true;
}
return false;
}
2022-02-12 16:25:59 +11:00
bool IsRunJumpCountableState(LaraState state)
2022-02-04 22:18:55 +11:00
{
if (state == LS_RUN_FORWARD ||
state == LS_WALK_FORWARD ||
state == LS_JUMP_FORWARD ||
state == LS_SPRINT ||
state == LS_SPRINT_DIVE)
{
return true;
}
return false;
}
2022-03-16 21:56:25 +11:00
bool TestLaraPose(ITEM_INFO* item, CollisionInfo* coll)
2022-02-05 23:13:31 +11:00
{
2022-03-16 21:56:25 +11:00
auto* lara = GetLaraInfo(item);
if (TestEnvironment(ENV_FLAG_SWAMP, item))
return false;
if (!(TrInput & (IN_FLARE | IN_DRAW)) && // Avoid unsightly concurrent actions.
lara->Control.HandStatus == HandStatus::Free && // Hands are free.
(lara->Control.Weapon.GunType != LaraWeaponType::Flare || // Flare is not being handled. TODO: Will she pose with weapons drawn?
lara->Flare.Life) &&
lara->Vehicle == NO_ITEM) // Not in a vehicle.
2022-02-05 23:13:31 +11:00
{
return true;
}
return false;
}
2022-03-16 21:56:25 +11:00
bool TestLaraKeepLow(ITEM_INFO* item, CollisionInfo* coll)
2022-01-25 18:02:22 +11:00
{
2022-03-16 21:56:25 +11:00
// HACK: coll->Setup.Radius is currently only set to
// LARA_RAD_CRAWL in the collision function, then reset by LaraAboveWater().
// For tests called in control functions, then, it will store the wrong radius. @Sezz 2021.11.05
int radius = (item->Animation.ActiveState == LS_CROUCH_IDLE ||
item->Animation.ActiveState == LS_CROUCH_TURN_LEFT ||
item->Animation.ActiveState == LS_CROUCH_TURN_RIGHT)
? LARA_RAD : LARA_RAD_CRAWL;
2022-03-16 21:56:25 +11:00
auto probeFront = GetCollision(item, item->Position.yRot, radius, -coll->Setup.Height);
auto probeBack = GetCollision(item, item->Position.yRot + ANGLE(180.0f), radius, -coll->Setup.Height);
auto probeMiddle = GetCollision(item);
2022-01-25 18:02:22 +11:00
2022-03-16 21:56:25 +11:00
if (abs(probeFront.Position.Ceiling - probeFront.Position.Floor) < LARA_HEIGHT || // Front is not a clamp.
abs(probeBack.Position.Ceiling - probeBack.Position.Floor) < LARA_HEIGHT || // Back is not a clamp.
abs(probeMiddle.Position.Ceiling - probeMiddle.Position.Floor) < LARA_HEIGHT) // Middle is not a clamp.
{
return true;
}
2022-01-25 18:02:22 +11:00
2022-03-16 21:56:25 +11:00
return false;
2022-01-25 18:02:22 +11:00
}
2022-03-16 21:56:25 +11:00
bool TestLaraSlide(ITEM_INFO* item, CollisionInfo* coll)
{
int y = item->Position.yPos;
auto probe = GetCollision(item);
if (abs(probe.Position.Floor - y) <= STEPUP_HEIGHT &&
probe.Position.FloorSlope &&
!TestEnvironment(ENV_FLAG_SWAMP, item))
{
return true;
}
return false;
}
bool TestLaraLand(ITEM_INFO* item, CollisionInfo* coll)
{
int heightFromFloor = GetCollision(item).Position.Floor - item->Position.yPos;
if (item->Animation.Airborne && item->Animation.VerticalVelocity >= 0 &&
(heightFromFloor <= item->Animation.VerticalVelocity || TestEnvironment(ENV_FLAG_SWAMP, item)))
{
return true;
}
return false;
}
bool TestLaraFall(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2022-03-16 21:56:25 +11:00
if (coll->Middle.Floor <= STEPUP_HEIGHT ||
lara->Control.WaterStatus == WaterStatus::Wade) // TODO: This causes a legacy floor snap bug when Lara wades off a ledge into a dry room. @Sezz 2021.09.26
{
2022-02-12 16:25:59 +11:00
return false;
2022-03-16 21:56:25 +11:00
}
2022-02-12 16:25:59 +11:00
2022-03-16 21:56:25 +11:00
return true;
}
bool TestLaraMonkeyGrab(ITEM_INFO* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
if (lara->Control.CanMonkeySwing && coll->Middle.Ceiling <= CLICK(0.5f) &&
(coll->Middle.Ceiling >= 0 || coll->CollisionType == CT_TOP || coll->CollisionType == CT_TOP_FRONT) &&
abs(coll->Middle.Ceiling + coll->Middle.Floor + coll->Setup.Height) > LARA_HEIGHT_MONKEY)
{
return true;
}
return false;
}
bool TestLaraMonkeyFall(ITEM_INFO* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos - LARA_HEIGHT_MONKEY;
auto probe = GetCollision(item);
if (!lara->Control.CanMonkeySwing || // No monkey sector.
(probe.Position.Ceiling - y) > CLICK(1.25f) || // Outside lower bound.
(probe.Position.Ceiling - y) < -CLICK(1.25f) || // Outside upper bound.
probe.Position.CeilingSlope || // Is ceiling slope.
probe.Position.Ceiling == NO_HEIGHT)
{
return true;
}
return false;
}
bool TestLaraStep(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2021-11-28 22:45:08 +11:00
if (abs(coll->Middle.Floor) > 0 &&
2022-02-12 16:25:59 +11:00
(coll->Middle.Floor <= STEPUP_HEIGHT || // Within lower floor bound...
2022-02-28 21:02:19 +11:00
lara->Control.WaterStatus == WaterStatus::Wade) && // OR Lara is wading.
2022-02-12 16:25:59 +11:00
coll->Middle.Floor >= -STEPUP_HEIGHT && // Within upper floor bound.
2021-11-28 22:45:08 +11:00
coll->Middle.Floor != NO_HEIGHT)
{
return true;
}
return false;
}
bool TestLaraStepUp(ITEM_INFO* item, CollisionInfo* coll)
{
2022-01-30 15:17:57 +11:00
if (coll->Middle.Floor < -CLICK(0.5f) && // Within lower floor bound.
coll->Middle.Floor >= -STEPUP_HEIGHT) // Within upper floor bound.
{
return true;
}
return false;
}
bool TestLaraStepDown(ITEM_INFO* item, CollisionInfo* coll)
{
2022-01-30 15:17:57 +11:00
if (coll->Middle.Floor <= STEPUP_HEIGHT && // Within lower floor bound.
coll->Middle.Floor > CLICK(0.5f)) // Within upper floor bound.
{
return true;
}
return false;
}
bool TestLaraMonkeyStep(ITEM_INFO* item, CollisionInfo* coll)
{
int y = item->Position.yPos - LARA_HEIGHT_MONKEY;
auto probe = GetCollision(item);
2022-01-30 15:17:57 +11:00
if ((probe.Position.Ceiling - y) <= CLICK(1.25f) && // Within lower ceiling bound.
(probe.Position.Ceiling - y) >= -CLICK(1.25f) && // Within upper ceiling bound.
probe.Position.Ceiling != NO_HEIGHT)
{
return true;
}
return false;
2020-12-21 13:16:29 -03:00
}
// TODO: This function and its clone TestLaraCrawlMoveTolerance() should become obsolete with more accurate and accessible collision detection in the future.
// For now, it supersedes old probes and is used alongside COLL_INFO. @Sezz 2021.10.24
bool TestLaraMoveTolerance(ITEM_INFO* item, CollisionInfo* coll, MoveTestSetup testSetup, bool useCrawlSetup)
{
// HACK: coll->Setup.Radius and coll->Setup.Height are set in
// lara_col functions, then reset by LaraAboveWater() to defaults.
// This means they store the wrong values for move tests called in crawl lara_as functions.
// When states become objects, collision setup should occur at the beginning of each state, eliminating the need
// for the useCrawlSetup argument. @Sezz 2022.03.16
int laraRadius = useCrawlSetup ? LARA_RAD_CRAWL : coll->Setup.Radius;
int laraHeight = useCrawlSetup ? LARA_HEIGHT_CRAWL : coll->Setup.Height;
int y = item->Position.yPos;
int distance = OFFSET_RADIUS(laraRadius);
auto probe = GetCollision(item, testSetup.Angle, distance, -laraHeight);
2022-01-28 00:02:48 +11:00
bool isSlopeDown = testSetup.CheckSlopeDown ? (probe.Position.FloorSlope && probe.Position.Floor > y) : false;
bool isSlopeUp = testSetup.CheckSlopeUp ? (probe.Position.FloorSlope && probe.Position.Floor < y) : false;
bool isDeath = testSetup.CheckDeath ? probe.Block->Flags.Death : false;
2022-02-07 23:07:40 +11:00
auto start1 = GAME_VECTOR(
item->Position.xPos,
2022-02-07 23:07:40 +11:00
y + testSetup.UpperFloorBound - 1,
item->Position.zPos,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end1 = GAME_VECTOR(
probe.Coordinates.x,
y + testSetup.UpperFloorBound - 1,
probe.Coordinates.z,
item->RoomNumber);
2022-01-28 00:02:48 +11:00
2022-02-07 23:07:40 +11:00
auto start2 = GAME_VECTOR(
item->Position.xPos,
y - laraHeight + 1,
item->Position.zPos,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end2 = GAME_VECTOR(
probe.Coordinates.x,
probe.Coordinates.y + 1,
probe.Coordinates.z,
item->RoomNumber);
2022-01-28 00:02:48 +11:00
2022-02-07 23:07:40 +11:00
// Discard walls.
if (probe.Position.Floor == NO_HEIGHT)
return false;
2022-02-08 01:26:59 +11:00
// Check for slope or death sector (if applicable).
2022-02-07 23:07:40 +11:00
if (isSlopeDown || isSlopeUp || isDeath)
return false;
// Conduct ray test at upper floor bound and lowest ceiling bound.
2022-02-07 23:07:40 +11:00
if (!LOS(&start1, &end1) || !LOS(&start2, &end2))
2022-01-28 00:02:48 +11:00
return false;
// Assess move feasibility to location ahead.
if ((probe.Position.Floor - y) <= testSetup.LowerFloorBound && // Within lower floor bound.
(probe.Position.Floor - y) >= testSetup.UpperFloorBound && // Within upper floor bound.
(probe.Position.Ceiling - y) < -laraHeight && // Within lowest ceiling bound.
abs(probe.Position.Ceiling - probe.Position.Floor) > laraHeight) // Space is not a clamp.
{
return true;
}
return false;
}
bool TestLaraRunForward(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperFloorBound defined in run state collision function.
MoveTestSetup testSetup
{
item->Position.yRot,
NO_LOWER_BOUND, -STEPUP_HEIGHT,
false, true, false
};
return TestLaraMoveTolerance(item, coll, testSetup);
}
bool TestLaraWalkForward(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperFloorBound defined in walk state collision function.
MoveTestSetup testSetup
{
item->Position.yRot,
STEPUP_HEIGHT, -STEPUP_HEIGHT
};
return TestLaraMoveTolerance(item, coll, testSetup);
}
bool TestLaraWalkBack(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperFloorBound defined in walk back state collision function.
MoveTestSetup testSetup
{
item->Position.yRot + ANGLE(180.0f),
STEPUP_HEIGHT, -STEPUP_HEIGHT
};
return TestLaraMoveTolerance(item, coll, testSetup);
}
bool TestLaraRunBack(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperFloorBound defined in hop back state collision function.
MoveTestSetup testSetup
{
item->Position.yRot + ANGLE(180.0f),
NO_LOWER_BOUND, -STEPUP_HEIGHT,
false, false, false
};
return TestLaraMoveTolerance(item, coll, testSetup);
}
bool TestLaraStepLeft(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperFloorBound defined in step left state collision function.
MoveTestSetup testSetup
{
item->Position.yRot - ANGLE(90.0f),
CLICK(0.8f), -CLICK(0.8f)
};
return TestLaraMoveTolerance(item, coll, testSetup);
}
bool TestLaraStepRight(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperFloorBound defined in step right state collision function.
MoveTestSetup testSetup
{
item->Position.yRot + ANGLE(90.0f),
CLICK(0.8f), -CLICK(0.8f)
};
return TestLaraMoveTolerance(item, coll, testSetup);
}
bool TestLaraWadeForwardSwamp(ITEM_INFO* item, CollisionInfo* coll)
2022-01-02 21:31:18 +11:00
{
// Using Lower/UpperFloorBound defined in wade forward state collision function.
2022-01-02 21:31:18 +11:00
MoveTestSetup testSetup
{
item->Position.yRot,
NO_LOWER_BOUND, -STEPUP_HEIGHT,
false, false, false
};
2022-01-02 21:31:18 +11:00
return TestLaraMoveTolerance(item, coll, testSetup);
2021-10-24 20:43:02 +11:00
}
bool TestLaraWalkBackSwamp(ITEM_INFO* item, CollisionInfo* coll)
2021-11-07 21:55:38 +11:00
{
// Using UpperFloorBound defined in walk back state collision function.
MoveTestSetup testSetup
2022-01-02 21:31:18 +11:00
{
item->Position.yRot + ANGLE(180.0f),
NO_LOWER_BOUND, -STEPUP_HEIGHT,
false, false, false
};
2022-01-02 21:31:18 +11:00
return TestLaraMoveTolerance(item, coll, testSetup);
2021-11-07 21:55:38 +11:00
}
bool TestLaraStepLeftSwamp(ITEM_INFO* item, CollisionInfo* coll)
2021-11-07 21:55:38 +11:00
{
// Using UpperFloorBound defined in step left state collision function.
2022-01-03 01:29:32 +11:00
MoveTestSetup testSetup
{
item->Position.yRot - ANGLE(90.0f),
NO_LOWER_BOUND, -CLICK(0.8f),
false, false, false
};
2022-01-03 13:52:07 +11:00
return TestLaraMoveTolerance(item, coll, testSetup);
2021-11-07 21:55:38 +11:00
}
bool TestLaraStepRightSwamp(ITEM_INFO* item, CollisionInfo* coll)
2021-11-07 21:55:38 +11:00
{
// Using UpperFloorBound defined in step right state collision function.
MoveTestSetup testSetup
2022-01-03 01:29:32 +11:00
{
item->Position.yRot + ANGLE(90.0f),
NO_LOWER_BOUND, -CLICK(0.8f),
false, false, false
};
2022-01-03 01:29:32 +11:00
return TestLaraMoveTolerance(item, coll, testSetup);
2021-11-07 21:55:38 +11:00
}
bool TestLaraCrawlForward(ITEM_INFO* item, CollisionInfo* coll)
2021-10-24 20:43:02 +11:00
{
// Using Lower/UpperFloorBound defined in crawl state collision functions.
2022-01-03 01:29:32 +11:00
MoveTestSetup testSetup
{
item->Position.yRot,
CLICK(1) - 1, -(CLICK(1) - 1)
};
2022-01-03 01:29:32 +11:00
return TestLaraMoveTolerance(item, coll, testSetup, true);
2021-10-24 20:43:02 +11:00
}
bool TestLaraCrawlBack(ITEM_INFO* item, CollisionInfo* coll)
2021-10-24 20:43:02 +11:00
{
// Using Lower/UpperFloorBound defined in crawl state collision functions.
MoveTestSetup testSetup
{
item->Position.yRot + ANGLE(180.0f),
CLICK(1) - 1, -(CLICK(1) - 1)
};
return TestLaraMoveTolerance(item, coll, testSetup, true);
}
bool TestLaraCrouchRoll(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
int distance = CLICK(3);
auto probe = GetCollision(item, item->Position.yRot, distance, -LARA_HEIGHT_CRAWL);
if (!(TrInput & (IN_FLARE | IN_DRAW)) && // Avoid unsightly concurrent actions.
(probe.Position.Floor - y) <= (CLICK(1) - 1) && // Within lower floor bound.
(probe.Position.Floor - y) >= -(CLICK(1) - 1) && // Within upper floor bound.
(probe.Position.Ceiling - y) < -LARA_HEIGHT_CRAWL && // Within lowest ceiling bound.
!probe.Position.FloorSlope && // Not a slope.
lara->WaterSurfaceDist >= -CLICK(1)) // Water depth is optically permissive.
{
return true;
}
return false;
}
bool TestLaraCrouchToCrawl(ITEM_INFO* item)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
if (!(TrInput & (IN_FLARE | IN_DRAW)) && // Avoid unsightly concurrent actions.
lara->Control.HandStatus == HandStatus::Free && // Hands are free.
(lara->Control.Weapon.GunType != LaraWeaponType::Flare || // Not handling flare. TODO: Should be allowed, but the flare animation bugs out right now. @Sezz 2022.03.18
2022-02-28 21:02:19 +11:00
lara->Flare.Life))
{
return true;
}
return false;
}
bool TestLaraMonkeyMoveTolerance(ITEM_INFO* item, CollisionInfo* coll, MonkeyMoveTestSetup testSetup)
{
int y = item->Position.yPos - LARA_HEIGHT_MONKEY;
int distance = OFFSET_RADIUS(coll->Setup.Radius);
auto probe = GetCollision(item, testSetup.Angle, distance);
2022-01-28 00:02:48 +11:00
2022-02-07 23:07:40 +11:00
auto start1 = GAME_VECTOR(
item->Position.xPos,
2022-02-07 23:07:40 +11:00
y + testSetup.LowerCeilingBound + 1,
item->Position.zPos,
item->RoomNumber);
2022-01-28 00:02:48 +11:00
2022-02-07 23:07:40 +11:00
auto end1 = GAME_VECTOR(
probe.Coordinates.x,
probe.Coordinates.y - LARA_HEIGHT_MONKEY + testSetup.LowerCeilingBound + 1,
probe.Coordinates.z,
item->RoomNumber);
2022-01-28 00:02:48 +11:00
2022-02-07 23:07:40 +11:00
auto start2 = GAME_VECTOR(
item->Position.xPos,
2022-02-07 23:07:40 +11:00
y + LARA_HEIGHT_MONKEY - 1,
item->Position.zPos,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
auto end2 = GAME_VECTOR(
probe.Coordinates.x,
probe.Coordinates.y - 1,
probe.Coordinates.z,
item->RoomNumber);
2022-02-07 23:07:40 +11:00
// Discard walls.
if (probe.Position.Ceiling == NO_HEIGHT)
return false;
2022-02-08 01:26:59 +11:00
// Check for ceiling slope.
2022-02-07 23:07:40 +11:00
if (probe.Position.CeilingSlope)
return false;
// Conduct ray test at lower ceiling bound and highest floor bound.
2022-02-07 23:07:40 +11:00
if (!LOS(&start1, &end1) || !LOS(&start2, &end2))
2022-01-28 00:02:48 +11:00
return false;
2022-01-15 22:48:35 +11:00
2022-01-28 00:02:48 +11:00
// Assess move feasibility to location ahead.
2022-01-22 22:36:29 +11:00
if (probe.BottomBlock->Flags.Monkeyswing && // Is monkey sector.
(probe.Position.Floor - y) > LARA_HEIGHT_MONKEY && // Within highest floor bound.
(probe.Position.Ceiling - y) <= testSetup.LowerCeilingBound && // Within lower ceiling bound.
(probe.Position.Ceiling - y) >= testSetup.UpperCeilingBound && // Within upper ceiling bound.
2022-02-07 23:07:40 +11:00
abs(probe.Position.Ceiling - probe.Position.Floor) > LARA_HEIGHT_MONKEY) // Space is not a clamp.
{
return true;
}
return false;
}
bool TestLaraMonkeyForward(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperCeilingBound defined in monkey forward collision function.
MonkeyMoveTestSetup testSetup
2022-01-08 16:50:23 +11:00
{
item->Position.yRot,
CLICK(1.25f), -CLICK(1.25f)
2022-01-08 16:50:23 +11:00
};
return TestLaraMonkeyMoveTolerance(item, coll, testSetup);
}
bool TestLaraMonkeyBack(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperCeilingBound defined in monkey back collision function.
MonkeyMoveTestSetup testSetup
2022-01-08 16:50:23 +11:00
{
item->Position.yRot + ANGLE(180.0f),
CLICK(1.25f), -CLICK(1.25f)
2022-01-08 16:50:23 +11:00
};
return TestLaraMonkeyMoveTolerance(item, coll, testSetup);
}
bool TestLaraMonkeyShimmyLeft(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperCeilingBound defined in monkey shimmy left collision function.
MonkeyMoveTestSetup testSetup
2022-01-08 16:50:23 +11:00
{
item->Position.yRot - ANGLE(90.0f),
CLICK(0.5f), -CLICK(0.5f)
2022-01-08 16:50:23 +11:00
};
return TestLaraMonkeyMoveTolerance(item, coll, testSetup);
}
bool TestLaraMonkeyShimmyRight(ITEM_INFO* item, CollisionInfo* coll)
{
// Using Lower/UpperCeilingBound defined in monkey shimmy right collision function.
MonkeyMoveTestSetup testSetup
2022-01-08 16:50:23 +11:00
{
item->Position.yRot + ANGLE(90.0f),
CLICK(0.5f), -CLICK(0.5f)
2022-01-08 16:50:23 +11:00
};
return TestLaraMonkeyMoveTolerance(item, coll, testSetup);
}
VaultTestResult TestLaraVaultTolerance(ITEM_INFO* item, CollisionInfo* coll, VaultTestSetup testSetup)
2021-12-10 12:30:23 +11:00
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
int distance = OFFSET_RADIUS(coll->Setup.Radius);
auto probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, -coll->Setup.Height);
auto probeMiddle = GetCollision(item);
2022-01-30 15:17:57 +11:00
2022-02-28 21:02:19 +11:00
bool swampTooDeep = testSetup.CheckSwampDepth ? (TestEnvironment(ENV_FLAG_SWAMP, item) && lara->WaterSurfaceDist < -CLICK(3)) : TestEnvironment(ENV_FLAG_SWAMP, item);
2022-02-07 23:07:40 +11:00
2022-02-08 01:26:59 +11:00
// Check swamp depth (if applicable).
2022-02-07 23:07:40 +11:00
if (swampTooDeep)
return VaultTestResult{ false };
2021-12-10 12:30:23 +11:00
// HACK: Where the probe finds that the wall in front is formed by a ceiling or the space between the floor and ceiling is a clamp,
// any climbable floor in a room above will be missed.
// Raise y position of probe point by increments of CLICK(0.5f) to find this potential vault candidate location.
int yOffset = testSetup.LowerCeilingBound;
while (((probeFront.Position.Ceiling - y) > -coll->Setup.Height || // Ceiling is below Lara's height...
abs(probeFront.Position.Ceiling - probeFront.Position.Floor) <= testSetup.ClampMin || // OR clamp is too small
abs(probeFront.Position.Ceiling - probeFront.Position.Floor) > testSetup.ClampMax) && // OR clamp is too large (future-proofing; not possible right now).
yOffset > (testSetup.UpperCeilingBound - coll->Setup.Height)) // Offset is not too high.
2021-12-10 12:30:23 +11:00
{
probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, yOffset);
2022-01-27 22:26:05 +11:00
yOffset -= std::max<int>(CLICK(0.5f), testSetup.ClampMin);
}
2022-02-07 23:07:40 +11:00
// Discard walls.
if (probeFront.Position.Floor == NO_HEIGHT)
return VaultTestResult{ false };
// Assess vault candidate location.
if ((probeFront.Position.Floor - y) < testSetup.LowerCeilingBound && // Within lower floor bound.
(probeFront.Position.Floor - y) >= testSetup.UpperCeilingBound && // Within upper floor bound.
abs(probeFront.Position.Ceiling - probeFront.Position.Floor) > testSetup.ClampMin && // Within clamp min.
abs(probeFront.Position.Ceiling - probeFront.Position.Floor) <= testSetup.ClampMax && // Within clamp max.
2022-02-07 23:07:40 +11:00
abs(probeMiddle.Position.Ceiling - probeFront.Position.Floor) >= testSetup.GapMin) // Gap is optically permissive.
{
return VaultTestResult{ true, probeFront.Position.Floor };
}
return VaultTestResult{ false };
}
VaultTestResult TestLaraVault2Steps(ITEM_INFO* item, CollisionInfo* coll)
{
// Floor range: (-STEPUP_HEIGHT, -CLICK(2.5f)]
// Clamp range: (-LARA_HEIGHT, -MAX_HEIGHT]
VaultTestSetup testSetup
{
-STEPUP_HEIGHT, -CLICK(2.5f),
LARA_HEIGHT, -MAX_HEIGHT,
2022-01-14 13:15:16 +11:00
CLICK(1)
};
auto testResult = TestLaraVaultTolerance(item, coll, testSetup);
testResult.Height += CLICK(2);
testResult.SetBusyHands = true;
testResult.SnapToLedge = true;
2022-02-27 23:51:00 +11:00
testResult.SetJumpVelocity = false;
return testResult;
}
VaultTestResult TestLaraVault3Steps(ITEM_INFO* item, CollisionInfo* coll)
{
// Floor range: (-CLICK(2.5f), -CLICK(3.5f)]
// Clamp range: (-LARA_HEIGHT, -MAX_HEIGHT]
VaultTestSetup testSetup
{
-CLICK(2.5f), -CLICK(3.5f),
LARA_HEIGHT, -MAX_HEIGHT,
CLICK(1),
};
auto testResult = TestLaraVaultTolerance(item, coll, testSetup);
testResult.Height += CLICK(3);
testResult.SetBusyHands = true;
testResult.SnapToLedge = true;
2022-02-27 23:51:00 +11:00
testResult.SetJumpVelocity = false;
return testResult;
2021-10-24 20:43:02 +11:00
}
VaultTestResult TestLaraVault1StepToCrouch(ITEM_INFO* item, CollisionInfo* coll)
2021-11-07 21:55:38 +11:00
{
// Floor range: (0, -STEPUP_HEIGHT]
// Clamp range: (-LARA_HEIGHT_CRAWL, -LARA_HEIGHT]
VaultTestSetup testSetup
{
0, -STEPUP_HEIGHT,
LARA_HEIGHT_CRAWL, LARA_HEIGHT,
CLICK(1),
};
auto testResult = TestLaraVaultTolerance(item, coll, testSetup);
testResult.Height += CLICK(1);
testResult.SetBusyHands = true;
testResult.SnapToLedge = true;
2022-02-27 23:51:00 +11:00
testResult.SetJumpVelocity = false;
return testResult;
2021-11-07 21:55:38 +11:00
}
VaultTestResult TestLaraVault2StepsToCrouch(ITEM_INFO* item, CollisionInfo* coll)
2021-11-07 21:55:38 +11:00
{
// Floor range: (-STEPUP_HEIGHT, -CLICK(2.5f)]
// Clamp range: (-LARA_HEIGHT_CRAWL, -LARA_HEIGHT]
VaultTestSetup testSetup
{
-STEPUP_HEIGHT, -CLICK(2.5f),
LARA_HEIGHT_CRAWL, LARA_HEIGHT,
CLICK(1),
};
auto testResult = TestLaraVaultTolerance(item, coll, testSetup);
testResult.Height += CLICK(2);
testResult.SetBusyHands = true;
testResult.SnapToLedge = true;
2022-02-27 23:51:00 +11:00
testResult.SetJumpVelocity = false;
return testResult;
2021-11-07 21:55:38 +11:00
}
VaultTestResult TestLaraVault3StepsToCrouch(ITEM_INFO* item, CollisionInfo* coll)
2021-10-24 20:43:02 +11:00
{
// Floor range: (-CLICK(2.5f), -CLICK(3.5f)]
// Clamp range: (-LARA_HEIGHT_CRAWL, -LARA_HEIGHT]
VaultTestSetup testSetup
{
-CLICK(2.5f), -CLICK(3.5f),
LARA_HEIGHT_CRAWL, LARA_HEIGHT,
CLICK(1),
};
auto testResult = TestLaraVaultTolerance(item, coll, testSetup);
testResult.Height += CLICK(3);
testResult.SetBusyHands = true;
testResult.SnapToLedge = true;
2022-02-27 23:51:00 +11:00
testResult.SetJumpVelocity = false;
return testResult;
2021-10-24 20:43:02 +11:00
}
VaultTestResult TestLaraLedgeAutoJump(ITEM_INFO* item, CollisionInfo* coll)
2022-02-05 23:13:31 +11:00
{
// Floor range: (-CLICK(3.5f), -CLICK(7.5f)]
// Clamp range: (-CLICK(0.1f), -MAX_HEIGHT]
VaultTestSetup testSetup
{
-CLICK(3.5f), -CLICK(7.5f),
CLICK(0.1f)/* TODO: Is this enough hand room?*/,-MAX_HEIGHT,
CLICK(0.1f),
false
};
auto testResult = TestLaraVaultTolerance(item, coll, testSetup);
testResult.SetBusyHands = false;
testResult.SnapToLedge = true;
2022-02-27 23:51:00 +11:00
testResult.SetJumpVelocity = true;
return testResult;
2022-02-05 23:13:31 +11:00
}
VaultTestResult TestLaraLadderAutoJump(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
int distance = OFFSET_RADIUS(coll->Setup.Radius);
auto probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, -coll->Setup.Height);
auto probeMiddle = GetCollision(item);
if (TestValidLedgeAngle(item, coll) && // Appropriate angle difference from ladder.
2022-01-25 18:02:22 +11:00
!TestEnvironment(ENV_FLAG_SWAMP, item) && // No swamp.
2022-02-28 21:02:19 +11:00
lara->Control.CanClimbLadder && // Ladder sector flag set.
(probeMiddle.Position.Ceiling - y) <= -CLICK(6.5f) && // Within lowest middle ceiling bound. (Synced with TestLaraLadderMount())
coll->NearestLedgeDistance <= coll->Setup.Radius) // Appropriate distance from wall (tentative).
{
return VaultTestResult{ true, probeMiddle.Position.Ceiling, false, true, true };
}
return VaultTestResult{ false };
}
VaultTestResult TestLaraLadderMount(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
int distance = OFFSET_RADIUS(coll->Setup.Radius);
auto probeFront = GetCollision(item, coll->NearestLedgeAngle, distance, -coll->Setup.Height);
auto probeMiddle = GetCollision(item);
if (TestValidLedgeAngle(item, coll) &&
2022-02-28 21:02:19 +11:00
lara->Control.CanClimbLadder && // Ladder sector flag set.
(probeMiddle.Position.Ceiling - y) <= -CLICK(4.5f) && // Within lower middle ceiling bound.
(probeMiddle.Position.Floor - y) > -CLICK(6.5f) && // Within upper middle floor bound. (Synced with TestLaraAutoJump())
(probeFront.Position.Ceiling - y) <= -CLICK(4.5f) && // Within lowest front ceiling bound.
2022-01-26 14:51:50 +11:00
coll->NearestLedgeDistance <= coll->Setup.Radius) // Appropriate distance from wall.
{
return VaultTestResult{ true, NO_HEIGHT, true, true, false };
}
return VaultTestResult{ false };
}
VaultTestResult TestLaraMonkeyAutoJump(ITEM_INFO* item, CollisionInfo* coll)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
auto probe = GetCollision(item);
2022-01-25 18:02:22 +11:00
if (!TestEnvironment(ENV_FLAG_SWAMP, item) && // No swamp.
2022-02-28 21:02:19 +11:00
lara->Control.CanMonkeySwing && // Monkey swing sector flag set.
(probe.Position.Ceiling - y) < -LARA_HEIGHT_MONKEY && // Within lower ceiling bound.
(probe.Position.Ceiling - y) >= -CLICK(7)) // Within upper ceiling bound.
{
return VaultTestResult{ true, probe.Position.Ceiling, false, false, true };
2022-02-05 23:13:31 +11:00
}
return VaultTestResult{ false };
2022-02-05 23:13:31 +11:00
}
VaultTestResult TestLaraVault(ITEM_INFO* item, CollisionInfo* coll)
2022-02-05 23:13:31 +11:00
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2022-02-05 23:13:31 +11:00
2022-02-28 21:02:19 +11:00
if (!(TrInput & IN_ACTION) || lara->Control.HandStatus != HandStatus::Free)
return VaultTestResult{ false };
2022-02-05 23:13:31 +11:00
2022-02-28 21:02:19 +11:00
if (TestEnvironment(ENV_FLAG_SWAMP, item) && lara->WaterSurfaceDist < -CLICK(3))
return VaultTestResult{ false };
2022-02-05 23:13:31 +11:00
VaultTestResult vaultResult;
// Attempt ledge vault.
if (TestValidLedge(item, coll))
{
// Vault to crouch up one step.
vaultResult = TestLaraVault1StepToCrouch(item, coll);
if (vaultResult.Success)
{
vaultResult.TargetState = LS_VAULT_1_STEP_CROUCH;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
// Vault to stand up two steps.
vaultResult = TestLaraVault2Steps(item, coll);
if (vaultResult.Success)
{
vaultResult.TargetState = LS_VAULT_2_STEPS;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
// Vault to crouch up two steps.
vaultResult = TestLaraVault2StepsToCrouch(item, coll);
if (vaultResult.Success &&
2022-03-04 15:51:53 +11:00
g_GameFlow->Animations.HasCrawlExtended)
2022-02-05 23:13:31 +11:00
{
vaultResult.TargetState = LS_VAULT_2_STEPS_CROUCH;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
// Vault to stand up three steps.
vaultResult = TestLaraVault3Steps(item, coll);
if (vaultResult.Success)
{
vaultResult.TargetState = LS_VAULT_3_STEPS;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
// Vault to crouch up three steps.
vaultResult = TestLaraVault3StepsToCrouch(item, coll);
if (vaultResult.Success &&
2022-03-04 15:51:53 +11:00
g_GameFlow->Animations.HasCrawlExtended)
2022-02-05 23:13:31 +11:00
{
vaultResult.TargetState = LS_VAULT_3_STEPS_CROUCH;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
// Auto jump to ledge.
vaultResult = TestLaraLedgeAutoJump(item, coll);
2022-02-05 23:13:31 +11:00
if (vaultResult.Success)
{
vaultResult.TargetState = LS_AUTO_JUMP;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
}
// TODO: Move ladder checks here when ladders are less prone to breaking.
// In this case, they fail due to a reliance on ShiftItem(). @Sezz 2021.02.05
// Auto jump to monkey swing.
vaultResult = TestLaraMonkeyAutoJump(item, coll);
if (vaultResult.Success &&
2022-03-04 15:51:53 +11:00
g_GameFlow->Animations.HasMonkeyAutoJump)
2022-02-05 23:13:31 +11:00
{
vaultResult.TargetState = LS_AUTO_JUMP;
vaultResult.Success = HasChange(item, vaultResult.TargetState);
2022-02-05 23:13:31 +11:00
return vaultResult;
}
return VaultTestResult{ false };
2022-02-05 23:13:31 +11:00
}
2022-03-17 01:19:21 +11:00
// Temporary solution to ladder mounts until ladders stop breaking whenever anyone tries to do anything with them. @Sezz 2022.02.05
bool TestAndDoLaraLadderClimb(ITEM_INFO* item, CollisionInfo* coll)
2022-02-05 23:13:31 +11:00
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
2022-02-05 23:13:31 +11:00
2022-02-28 21:02:19 +11:00
if (!(TrInput & IN_ACTION) || !(TrInput & IN_FORWARD) || lara->Control.HandStatus != HandStatus::Free)
2022-02-05 23:13:31 +11:00
return false;
2022-02-28 21:02:19 +11:00
if (TestEnvironment(ENV_FLAG_SWAMP, item) && lara->WaterSurfaceDist < -CLICK(3))
2022-02-05 23:13:31 +11:00
return false;
// Auto jump to ladder.
auto vaultResult = TestLaraLadderAutoJump(item, coll);
if (vaultResult.Success)
{
// TODO: Somehow harmonise CalculatedJumpVelocity to work for both ledge and ladder auto jumps, because otherwise there will be a need for an odd workaround in the future.
2022-02-28 21:02:19 +11:00
lara->Control.CalculatedJumpVelocity = -3 - sqrt(-9600 - 12 * std::max((vaultResult.Height - item->Position.yPos + CLICK(0.2f)), -CLICK(7.1f)));
item->Animation.AnimNumber = LA_STAND_SOLID;
item->Animation.FrameNumber = GetFrameNumber(item, 0);
item->Animation.TargetState = LS_JUMP_UP;
item->Animation.ActiveState = LS_IDLE;
2022-02-28 21:02:19 +11:00
lara->Control.HandStatus = HandStatus::Busy;
lara->Control.TurnRate = 0;
2022-02-05 23:13:31 +11:00
ShiftItem(item, coll);
SnapItemToGrid(item, coll); // HACK: until fragile ladder code is refactored, we must exactly snap to grid.
lara->TargetFacingAngle = item->Position.yRot;
2022-02-05 23:13:31 +11:00
AnimateLara(item);
return true;
}
2022-02-05 23:13:31 +11:00
// Mount ladder.
vaultResult = TestLaraLadderMount(item, coll);
if (vaultResult.Success &&
2022-03-16 21:56:25 +11:00
TestLaraClimbIdle(item, coll))
2022-02-05 23:13:31 +11:00
{
item->Animation.AnimNumber = LA_STAND_SOLID;
item->Animation.FrameNumber = GetFrameNumber(item, 0);
item->Animation.TargetState = LS_LADDER_IDLE;
item->Animation.ActiveState = LS_IDLE;
2022-02-28 21:02:19 +11:00
lara->Control.HandStatus = HandStatus::Busy;
lara->Control.TurnRate = 0;
2022-02-05 23:13:31 +11:00
ShiftItem(item, coll);
SnapItemToGrid(item, coll); // HACK: until fragile ladder code is refactored, we must exactly snap to grid.
AnimateLara(item);
return true;
}
return false;
}
2022-03-15 20:42:35 +11:00
CrawlVaultTestResult TestLaraCrawlVaultTolerance(ITEM_INFO* item, CollisionInfo* coll, CrawlVaultTestSetup testSetup)
{
int y = item->Position.yPos;
auto probeA = GetCollision(item, item->Position.yRot, testSetup.CrossDist, -LARA_HEIGHT_CRAWL); // Crossing.
auto probeB = GetCollision(item, item->Position.yRot, testSetup.DestDist, -LARA_HEIGHT_CRAWL); // Approximate destination.
auto probeMiddle = GetCollision(item);
2022-01-30 15:17:57 +11:00
bool isSlope = testSetup.CheckSlope ? probeB.Position.FloorSlope : false;
bool isDeath = testSetup.CheckDeath ? probeB.Block->Flags.Death : false;
2022-02-07 23:07:40 +11:00
// Discard walls.
if (probeA.Position.Floor == NO_HEIGHT || probeB.Position.Floor == NO_HEIGHT)
2022-03-15 20:42:35 +11:00
return CrawlVaultTestResult{ false };
2022-02-07 23:07:40 +11:00
2022-02-08 01:26:59 +11:00
// Check for slope or death sector (if applicable).
2022-02-07 23:07:40 +11:00
if (isSlope || isDeath)
2022-03-15 20:42:35 +11:00
return CrawlVaultTestResult{ false };
2022-02-07 23:07:40 +11:00
// Assess crawl vault feasibility to location ahead.
if ((probeA.Position.Floor - y) <= testSetup.LowerFloorBound && // Within lower floor bound.
(probeA.Position.Floor - y) >= testSetup.UpperFloorBound && // Within upper floor bound.
abs(probeA.Position.Ceiling - probeA.Position.Floor) > testSetup.ClampMin && // Crossing clamp limit.
abs(probeB.Position.Ceiling - probeB.Position.Floor) > testSetup.ClampMin && // Destination clamp limit.
abs(probeMiddle.Position.Ceiling - probeA.Position.Floor) >= testSetup.GapMin && // Gap is optically permissive (going up).
abs(probeA.Position.Ceiling - probeMiddle.Position.Floor) >= testSetup.GapMin && // Gap is optically permissive (going down).
2022-01-30 15:17:57 +11:00
abs(probeA.Position.Floor - probeB.Position.Floor) <= testSetup.MaxProbeHeightDif && // Crossing/destination floor height difference suggests continuous crawl surface.
2022-02-07 23:07:40 +11:00
(probeA.Position.Ceiling - y) < -testSetup.GapMin) // Ceiling height is permissive.
{
2022-03-15 20:42:35 +11:00
return CrawlVaultTestResult{ true };
}
2022-03-15 20:42:35 +11:00
return CrawlVaultTestResult{ false };
}
2022-03-15 20:42:35 +11:00
CrawlVaultTestResult TestLaraCrawlUpStep(ITEM_INFO* item, CollisionInfo* coll)
{
// Floor range: [-CLICK(1), -STEPUP_HEIGHT]
CrawlVaultTestSetup testSetup
{
-CLICK(1), -STEPUP_HEIGHT,
LARA_HEIGHT_CRAWL,
CLICK(0.6f),
CLICK(1.2f),
CLICK(2),
CLICK(1) - 1
};
return TestLaraCrawlVaultTolerance(item, coll, testSetup);
}
2022-03-15 20:42:35 +11:00
CrawlVaultTestResult TestLaraCrawlDownStep(ITEM_INFO* item, CollisionInfo* coll)
{
// Floor range: [STEPUP_HEIGHT, CLICK(1)]
CrawlVaultTestSetup testSetup
{
STEPUP_HEIGHT, CLICK(1),
LARA_HEIGHT_CRAWL,
CLICK(0.6f),
CLICK(1.2f),
CLICK(2),
CLICK(1) - 1
};
return TestLaraCrawlVaultTolerance(item, coll, testSetup);
}
2022-03-15 20:42:35 +11:00
CrawlVaultTestResult TestLaraCrawlExitDownStep(ITEM_INFO* item, CollisionInfo* coll)
{
// Floor range: [STEPUP_HEIGHT, CLICK(1)]
CrawlVaultTestSetup testSetup
{
STEPUP_HEIGHT, CLICK(1),
LARA_HEIGHT,
CLICK(1.25f),
CLICK(1.2f),
CLICK(1.5f),
-MAX_HEIGHT,
false, false
};
return TestLaraCrawlVaultTolerance(item, coll, testSetup);
}
2022-03-15 20:42:35 +11:00
CrawlVaultTestResult TestLaraCrawlExitJump(ITEM_INFO* item, CollisionInfo* coll)
{
// Floor range: [NO_LOWER_BOUND, STEPUP_HEIGHT)
CrawlVaultTestSetup testSetup
{
NO_LOWER_BOUND, STEPUP_HEIGHT + 1,
LARA_HEIGHT,
CLICK(1.25f),
CLICK(1.2f),
CLICK(1.5f),
NO_LOWER_BOUND,
false, false
};
return TestLaraCrawlVaultTolerance(item, coll, testSetup);
}
CrawlVaultTestResult TestLaraCrawlVault(ITEM_INFO* item, CollisionInfo* coll)
2022-02-04 20:23:39 +11:00
{
if (!(TrInput & (IN_ACTION | IN_JUMP)))
2022-02-07 23:07:40 +11:00
return CrawlVaultTestResult{ false };
2022-02-04 20:23:39 +11:00
2022-03-15 20:42:35 +11:00
// Crawl vault exit down 1 step.
auto crawlVaultResult = TestLaraCrawlExitDownStep(item, coll);
if (crawlVaultResult.Success)
2022-02-04 20:23:39 +11:00
{
2022-03-15 20:42:35 +11:00
if (TrInput & IN_CROUCH && TestLaraCrawlDownStep(item, coll).Success)
crawlVaultResult.TargetState = LS_CRAWL_STEP_DOWN;
2022-02-04 20:23:39 +11:00
else [[likely]]
2022-03-15 20:42:35 +11:00
crawlVaultResult.TargetState = LS_CRAWL_EXIT_STEP_DOWN;
2022-03-17 01:19:21 +11:00
crawlVaultResult.Success = HasChange(item, crawlVaultResult.TargetState);
2022-03-15 20:42:35 +11:00
return crawlVaultResult;
2022-02-04 20:23:39 +11:00
}
2022-03-15 20:42:35 +11:00
// Crawl vault exit jump.
crawlVaultResult = TestLaraCrawlExitJump(item, coll);
if (crawlVaultResult.Success)
2022-02-04 20:23:39 +11:00
{
if (TrInput & IN_WALK)
2022-03-15 20:42:35 +11:00
crawlVaultResult.TargetState = LS_CRAWL_EXIT_FLIP;
2022-02-04 20:23:39 +11:00
else [[likely]]
2022-03-15 20:42:35 +11:00
crawlVaultResult.TargetState = LS_CRAWL_EXIT_JUMP;
2022-03-17 01:19:21 +11:00
crawlVaultResult.Success = HasChange(item, crawlVaultResult.TargetState);
2022-03-15 20:42:35 +11:00
return crawlVaultResult;
2022-02-04 20:23:39 +11:00
}
2022-03-15 20:42:35 +11:00
// Crawl vault up 1 step.
crawlVaultResult = TestLaraCrawlUpStep(item, coll);
if (crawlVaultResult.Success)
{
crawlVaultResult.TargetState = LS_CRAWL_STEP_UP;
2022-03-17 01:19:21 +11:00
crawlVaultResult.Success = HasChange(item, crawlVaultResult.TargetState);
2022-03-15 20:42:35 +11:00
return crawlVaultResult;
}
2022-02-04 20:23:39 +11:00
2022-03-15 20:42:35 +11:00
// Crawl vault down 1 step.
crawlVaultResult = TestLaraCrawlDownStep(item, coll);
if (crawlVaultResult.Success)
{
crawlVaultResult.TargetState = LS_CRAWL_STEP_DOWN;
2022-03-17 01:19:21 +11:00
crawlVaultResult.Success = HasChange(item, crawlVaultResult.TargetState);
2022-03-15 20:42:35 +11:00
return crawlVaultResult;
}
2022-02-04 20:23:39 +11:00
2022-02-07 23:07:40 +11:00
return CrawlVaultTestResult{ false };
2022-02-04 20:23:39 +11:00
}
2022-03-15 20:42:35 +11:00
bool TestLaraCrawlToHang(ITEM_INFO* item, CollisionInfo* coll)
{
int y = item->Position.yPos;
2022-03-19 22:55:23 +11:00
int distance = CLICK(1.2f);
auto probe = GetCollision(item, item->Position.yRot + ANGLE(180.0f), distance, -LARA_HEIGHT_CRAWL);
2022-03-15 20:42:35 +11:00
bool objectCollided = TestLaraObjectCollision(item, item->Position.yRot + ANGLE(180.0f), CLICK(1.2f), -LARA_HEIGHT_CRAWL);
if (!objectCollided && // No obstruction.
(probe.Position.Floor - y) >= LARA_HEIGHT_STRETCH && // Highest floor bound.
(probe.Position.Ceiling - y) <= -CLICK(0.75f) && // Gap is optically permissive.
probe.Position.Floor != NO_HEIGHT)
{
return true;
}
return false;
}
bool TestLaraJumpTolerance(ITEM_INFO* item, CollisionInfo* coll, JumpTestSetup testSetup)
{
2022-02-28 21:02:19 +11:00
auto* lara = GetLaraInfo(item);
int y = item->Position.yPos;
auto probe = GetCollision(item, testSetup.Angle, testSetup.Distance, -coll->Setup.Height);
2022-01-30 15:17:57 +11:00
2022-02-07 23:07:40 +11:00
bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, item);
2022-02-28 21:02:19 +11:00
bool isWading = testSetup.CheckWadeStatus ? (lara->Control.WaterStatus == WaterStatus::Wade) : false;
2022-02-07 23:07:40 +11:00
// Discard walls.
if (probe.Position.Floor == NO_HEIGHT)
return false;
2022-02-08 01:26:59 +11:00
// Check for swamp or wade status (if applicable).
2022-02-07 23:07:40 +11:00
if (isSwamp || isWading)
return false;
// Assess jump feasibility toward location ahead.
if (!TestLaraFacingCorner(item, testSetup.Angle, testSetup.Distance) && // Avoid jumping through corners.
(probe.Position.Floor - y) >= -STEPUP_HEIGHT && // Within highest floor bound.
((probe.Position.Ceiling - y) < -(coll->Setup.Height + (LARA_HEADROOM * 0.8f)) || // Within lowest ceiling bound...
((probe.Position.Ceiling - y) < -coll->Setup.Height && // OR ceiling is level with Lara's head
2022-02-07 23:07:40 +11:00
(probe.Position.Floor - y) >= CLICK(0.5f)))) // AND there is a drop below.
{
return true;
}
return false;
}
bool TestLaraRunJumpForward(ITEM_INFO* item, CollisionInfo* coll)
{
JumpTestSetup testSetup
{
item->Position.yRot,
CLICK(1.5f)
};
return TestLaraJumpTolerance(item, coll, testSetup);
}
bool TestLaraJumpForward(ITEM_INFO* item, CollisionInfo* coll)
{
JumpTestSetup testSetup
{
item->Position.yRot
};
return TestLaraJumpTolerance(item, coll, testSetup);
}
bool TestLaraJumpBack(ITEM_INFO* item, CollisionInfo* coll)
{
JumpTestSetup testSetup
{
item->Position.yRot + ANGLE(180.0f)
};
return TestLaraJumpTolerance(item, coll, testSetup);
}
bool TestLaraJumpLeft(ITEM_INFO* item, CollisionInfo* coll)
{
JumpTestSetup testSetup
{
item->Position.yRot - ANGLE(90.0f)
};
return TestLaraJumpTolerance(item, coll, testSetup);
}
bool TestLaraJumpRight(ITEM_INFO* item, CollisionInfo* coll)
{
JumpTestSetup testSetup
{
item->Position.yRot + ANGLE(90.0f)
};
return TestLaraJumpTolerance(item, coll, testSetup);
}
bool TestLaraJumpUp(ITEM_INFO* item, CollisionInfo* coll)
{
JumpTestSetup testSetup
{
0,
0,
false
};
return TestLaraJumpTolerance(item, coll, testSetup);
}
bool TestLaraSlideJump(ITEM_INFO* item, CollisionInfo* coll)
2022-03-09 13:03:21 +11:00
{
// TODO: Broken on diagonal slides?
if (g_GameFlow->Animations.HasSlideExtended)
{
auto probe = GetCollision(item);
2022-03-09 13:03:21 +11:00
short direction = GetLaraSlideDirection(item, coll);
short steepness = GetSurfaceSteepnessAngle(probe.FloorTilt.x, probe.FloorTilt.y);
return (abs((short)(coll->Setup.ForwardAngle - direction)) <= abs(steepness));
}
return true;
}
bool TestLaraCrawlspaceDive(ITEM_INFO* item, CollisionInfo* coll)
{
auto probe = GetCollision(item, coll->Setup.ForwardAngle, coll->Setup.Radius, -coll->Setup.Height);
if (abs(probe.Position.Ceiling - probe.Position.Floor) < LARA_HEIGHT ||
TestLaraKeepLow(item, coll))
{
return true;
}
return false;
}
bool TestLaraTightropeDismount(ITEM_INFO* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
auto probe = GetCollision(item);
if (probe.Position.Floor == item->Position.yPos &&
lara->Control.Tightrope.CanDismount)
{
return true;
}
return false;
}
bool TestLaraPoleCollision(ITEM_INFO* item, CollisionInfo* coll, bool up, float offset)
2021-12-02 13:07:00 +11:00
{
static constexpr auto poleProbeCollRadius = 16.0f;
bool atLeastOnePoleCollided = false;
if (GetCollidedObjects(item, SECTOR(1), true, CollidedItems, nullptr, 0) && CollidedItems[0])
2021-12-02 13:07:00 +11:00
{
auto laraBox = TO_DX_BBOX(item->Position, GetBoundsAccurate(item));
2021-12-02 13:07:00 +11:00
// HACK: because Core implemented upward pole movement as SetPosition command, we can't precisely
// check her position. So we add a fixed height offset.
auto sphere = BoundingSphere(laraBox.Center + Vector3(0, (laraBox.Extents.y + poleProbeCollRadius + offset) * (up ? -1 : 1), 0), poleProbeCollRadius);
2021-12-09 12:48:54 +03:00
//g_Renderer.addDebugSphere(sphere.Center, 16.0f, Vector4(1, 0, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
2021-12-02 13:07:00 +11:00
int i = 0;
while (CollidedItems[i] != NULL)
{
auto& obj = CollidedItems[i];
i++;
if (obj->ObjectNumber != ID_POLEROPE)
2021-12-02 13:07:00 +11:00
continue;
auto poleBox = TO_DX_BBOX(obj->Position, GetBoundsAccurate(obj));
2021-12-02 13:07:00 +11:00
poleBox.Extents = poleBox.Extents + Vector3(coll->Setup.Radius, 0, coll->Setup.Radius);
2021-12-09 12:48:54 +03:00
//g_Renderer.addDebugBox(poleBox, Vector4(0, 0, 1, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
2021-12-02 13:07:00 +11:00
if (poleBox.Intersects(sphere))
2021-12-09 12:48:54 +03:00
{
2021-12-02 13:07:00 +11:00
atLeastOnePoleCollided = true;
2021-12-09 12:48:54 +03:00
break;
}
2021-12-02 13:07:00 +11:00
}
}
return atLeastOnePoleCollided;
}
bool TestLaraPoleUp(ITEM_INFO* item, CollisionInfo* coll)
{
2021-12-12 21:11:27 +11:00
if (!TestLaraPoleCollision(item, coll, true, CLICK(1)))
2021-12-02 13:07:00 +11:00
return false;
2021-12-12 21:11:27 +11:00
return (coll->Middle.Ceiling < -CLICK(1));
}
bool TestLaraPoleDown(ITEM_INFO* item, CollisionInfo* coll)
{
2021-12-02 13:07:00 +11:00
if (!TestLaraPoleCollision(item, coll, false))
return false;
return (coll->Middle.Floor > 0);
}