Merge branch 'develop' into DisplayString_Scale

This commit is contained in:
Lwmte 2023-11-05 07:26:38 +01:00
commit b4cf85af77
13 changed files with 403 additions and 308 deletions

View file

@ -16,11 +16,16 @@ Version 1.2
* Fix double drawing additive faces.
* Fix savegame count not properly increasing.
* Fix regeneration of non-ammo pickups with OCB 128.
* Overhaul Pushable blocks:
- Separate climbable and normal pushable blocks slots.
- Add new pushable OCB to manipulate pushable block properties.
- Add new Lara animations for pushing over the block off the edge (both TR1-3 and TR4-5 versions).
- Fix pushables not working with raising block.
* Fix vault bug preventing the player from climbing onto ledges out of deeper sections of wade-depth water.
* Fix cold exposure status not recovering in non-cold wade-depth water.
* Fix non-elevated combat camera.
* Fix camera snap when disengaging the look-around mode.
* Improve head-on wall collision.
* Overhaul pushables:
- Separate climbable and non-climbable pushable object slots.
- Add new pushable OCB to manipulate pushable properties.
- Add new animations for pushing pushables off edgees (TR1-3 and TR4-5 versions).
- Fix pushables not working with raising blocks.
- Fix miscellaneous pushable bugs.
* Overhaul look-around feature:
- Allow for more consistent and wider viewing angles while crawling, crouching, and hanging.
@ -42,14 +47,14 @@ Lua API changes:
* Add DisplaySprite object.
* Add Flow:EnableLoadSave() function to disable savegames.
* Add Flow:EnablePointFilter() function to disable bilinear filtering.
* Add InputGetCursorDisplayPosition() function to get the position of the cursor.
* Add Lara:GetAmmoType() function to read the ammo that Lara is using.
* Add Lara:GetControlLock() and Lara:SetControlLock() functions to handle controls locking.
* Add Logic.HandleEvent() function to call node events.
* Add Input.GetCursorDisplayPosition() function to get the position of the cursor.
* Add functions to load, save, delete and check existence of savegames.
* Add DisplayStringOption.RIGHT and DisplayStringOption.BLINK flags for DisplayString.
* Make Vec2 and Vec3 objects float-based instead of integer-based.
* Split and organise functions in `Misc` namespace to appropriate new namespaces.
* Split and organize functions in `Misc` namespace to appropriate new namespaces.
* Add Vec2/Vec3 arithmetic for division with a number and multiplication with another Vec2/Vec3.
* Add various Vec2/Vec3 operations such as Normalize() or Lerp().
* Add log messages warnings to functions AddCallback and RemoveCallback.

View file

@ -450,102 +450,80 @@ auto PlayerStateCollisionRoutines = std::array<PlayerStateRoutine, NUM_LARA_STAT
void LaraControl(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
auto& player = GetLaraInfo(*item);
if (lara->Control.Weapon.HasFired)
// Alert nearby creatures.
if (player.Control.Weapon.HasFired)
{
AlertNearbyGuards(item);
lara->Control.Weapon.HasFired = false;
player.Control.Weapon.HasFired = false;
}
if (lara->Status.Poison)
// Handle object interation adjustment parameters.
if (player.Control.IsMoving)
{
if (lara->Status.Poison > LARA_POISON_MAX)
lara->Status.Poison = LARA_POISON_MAX;
if (!(Wibble & 0xFF))
item->HitPoints -= lara->Status.Poison;
}
if (lara->Control.IsMoving)
{
if (lara->Control.Count.PositionAdjust > LARA_POSITION_ADJUST_MAX_TIME)
if (player.Control.Count.PositionAdjust > LARA_POSITION_ADJUST_MAX_TIME)
{
lara->Control.IsMoving = false;
lara->Control.HandStatus = HandStatus::Free;
player.Control.IsMoving = false;
player.Control.HandStatus = HandStatus::Free;
}
++lara->Control.Count.PositionAdjust;
++player.Control.Count.PositionAdjust;
}
else
{
lara->Control.Count.PositionAdjust = 0;
player.Control.Count.PositionAdjust = 0;
}
if (!lara->Control.Locked)
lara->LocationPad = -1;
if (!player.Control.Locked)
player.LocationPad = -1;
auto prevPos = item->Pose.Position;
if (lara->Control.HandStatus == HandStatus::Busy &&
item->Animation.AnimNumber == LA_STAND_IDLE &&
// FAILSAFE: Force hand status reset.
if (item->Animation.AnimNumber == LA_STAND_IDLE &&
item->Animation.ActiveState == LS_IDLE &&
item->Animation.TargetState == LS_IDLE &&
!item->Animation.IsAirborne)
!item->Animation.IsAirborne &&
player.Control.HandStatus == HandStatus::Busy)
{
lara->Control.HandStatus = HandStatus::Free;
player.Control.HandStatus = HandStatus::Free;
}
if (lara->Status.Stamina < LARA_STAMINA_MAX && item->Animation.ActiveState != LS_SPRINT)
lara->Status.Stamina++;
HandlePlayerQuickActions(*item);
RumbleLaraHealthCondition(item);
bool isWater = TestEnvironment(ENV_FLAG_WATER, item);
bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, item);
bool isCold = TestEnvironment(ENV_FLAG_COLD, item);
auto water = GetPlayerWaterData(*item);
player.Context.WaterSurfaceDist = -water.HeightFromWater;
if (player.Context.Vehicle == NO_ITEM)
SpawnPlayerSplash(*item, water.WaterHeight, water.WaterDepth);
bool isWaterOnHeadspace = false;
int waterDepth = GetWaterDepth(item);
int waterHeight = GetWaterHeight(item);
int heightFromWater;
if (waterHeight != NO_HEIGHT)
heightFromWater = item->Pose.Position.y - waterHeight;
else
heightFromWater = NO_HEIGHT;
lara->Context.WaterSurfaceDist = -heightFromWater;
if (lara->Context.Vehicle == NO_ITEM)
WadeSplash(item, waterHeight, waterDepth);
if (lara->Context.Vehicle == NO_ITEM && lara->ExtraAnim == NO_ITEM)
if (player.Context.Vehicle == NO_ITEM && player.ExtraAnim == NO_ITEM)
{
switch (lara->Control.WaterStatus)
switch (player.Control.WaterStatus)
{
case WaterStatus::Dry:
for (int i = 0; i < NUM_LARA_MESHES; i++)
lara->Effect.BubbleNodes[i] = 0.0f;
player.Effect.BubbleNodes[i] = 0.0f;
if (heightFromWater == NO_HEIGHT || heightFromWater < WADE_DEPTH)
if (water.HeightFromWater == NO_HEIGHT || water.HeightFromWater < WADE_WATER_DEPTH)
break;
Camera.targetElevation = ANGLE(-22.0f);
// Water is deep enough to swim; dispatch dive.
if (waterDepth >= SWIM_DEPTH && !isSwamp)
// Water is at swim depth; dispatch dive.
if (water.WaterDepth >= SWIM_WATER_DEPTH && !water.IsSwamp)
{
if (isWater)
if (water.IsWater)
{
item->Pose.Position.y += CLICK(0.5f) - 28;
item->Pose.Position.y += CLICK(0.5f) - 28; // TODO: Demagic.
item->Animation.IsAirborne = false;
lara->Control.WaterStatus = WaterStatus::Underwater;
lara->Status.Air = LARA_AIR_MAX;
player.Control.WaterStatus = WaterStatus::Underwater;
player.Status.Air = LARA_AIR_MAX;
for (int i = 0; i < NUM_LARA_MESHES; i++)
lara->Effect.BubbleNodes[i] = PLAYER_BUBBLE_NODE_MAX;
player.Effect.BubbleNodes[i] = PLAYER_BUBBLE_NODE_MAX;
UpdateLaraRoom(item, 0);
StopSoundEffect(SFX_TR4_LARA_FALL);
@ -553,22 +531,22 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
if (item->Animation.ActiveState == LS_SWAN_DIVE)
{
SetAnimation(item, LA_SWANDIVE_DIVE);
item->Animation.Velocity.y /= 2.0f;
item->Pose.Orientation.x = -ANGLE(45.0f);
lara->Control.HandStatus = HandStatus::Free;
item->Animation.Velocity.y /= 2;
item->Pose.Orientation.x = ANGLE(-45.0f);
player.Control.HandStatus = HandStatus::Free;
}
else if (item->Animation.ActiveState == LS_FREEFALL_DIVE)
{
SetAnimation(item, LA_SWANDIVE_DIVE);
item->Animation.Velocity.y /= 2.0f;
item->Pose.Orientation.x = -ANGLE(85.0f);
lara->Control.HandStatus = HandStatus::Free;
item->Animation.Velocity.y /= 2;
item->Pose.Orientation.x = ANGLE(-85.0f);
player.Control.HandStatus = HandStatus::Free;
}
else
{
SetAnimation(item, LA_FREEFALL_DIVE);
item->Animation.Velocity.y = (item->Animation.Velocity.y / 8.0f) * 3.0f;
item->Pose.Orientation.x = -ANGLE(45.0f);
item->Animation.Velocity.y = (item->Animation.Velocity.y / 8) * 3;
item->Pose.Orientation.x = ANGLE(-45.0f);
}
ResetPlayerFlex(item);
@ -576,13 +554,13 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
}
}
// Water is at wade depth; update water status and do special handling.
else if (heightFromWater >= WADE_DEPTH)
else if (water.HeightFromWater >= WADE_WATER_DEPTH)
{
lara->Control.WaterStatus = WaterStatus::Wade;
player.Control.WaterStatus = WaterStatus::Wade;
// Make splash ONLY within this particular threshold before swim depth while airborne (WadeSplash() above interferes otherwise).
if (waterDepth > (SWIM_DEPTH - CLICK(1)) &&
item->Animation.IsAirborne && !isSwamp)
// Make splash ONLY within this particular threshold before swim depth while airborne (SpawnPlayerSplash() above interferes otherwise).
if (water.WaterDepth > (SWIM_WATER_DEPTH - CLICK(1)) &&
item->Animation.IsAirborne && !water.IsSwamp)
{
item->Animation.TargetState = LS_IDLE;
Splash(item);
@ -592,12 +570,12 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
{
item->Animation.TargetState = LS_IDLE;
}
else if (isSwamp)
else if (water.IsSwamp)
{
if (item->Animation.ActiveState == LS_SWAN_DIVE ||
item->Animation.ActiveState == LS_FREEFALL_DIVE)
{
item->Pose.Position.y = waterHeight + (BLOCK(1) - 24);
item->Pose.Position.y = water.WaterHeight + (BLOCK(1) - 24); // TODO: Demagic.
}
SetAnimation(item, LA_WADE);
@ -607,25 +585,23 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
break;
case WaterStatus::Underwater:
// Disable potential Lara resurfacing if her health is zero or below.
// For some reason, originals worked without this condition, but TEN does not. -- Lwmte, 11.08.22
// Disable potential player resurfacing if health is <= 0.
// Originals worked without this condition, but TEN does not. -- Lwmte, 11.08.22
if (item->HitPoints <= 0)
break;
// Determine if Lara's head is above water surface. This is needed to prevent
// pre-TR5 bug where Lara would keep submerged until her root mesh (butt) is above water level.
// Determine if player's head is above water surface. Needed to prevent
// pre-TR5 bug where player would keep submerged until root mesh was above water level.
isWaterOnHeadspace = TestEnvironment(
ENV_FLAG_WATER, item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z,
GetCollision(item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z, item->RoomNumber).RoomNumber);
isWaterOnHeadspace = TestEnvironment(ENV_FLAG_WATER, item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z,
GetCollision(item->Pose.Position.x, item->Pose.Position.y - CLICK(1), item->Pose.Position.z, item->RoomNumber).RoomNumber);
if (waterDepth == NO_HEIGHT || abs(heightFromWater) >= CLICK(1) || isWaterOnHeadspace ||
if (water.WaterDepth == NO_HEIGHT || abs(water.HeightFromWater) >= CLICK(1) || isWaterOnHeadspace ||
item->Animation.AnimNumber == LA_UNDERWATER_RESURFACE || item->Animation.AnimNumber == LA_ONWATER_DIVE)
{
if (!isWater)
if (!water.IsWater)
{
if (waterDepth == NO_HEIGHT || abs(heightFromWater) >= CLICK(1))
if (water.WaterDepth == NO_HEIGHT || abs(water.HeightFromWater) >= CLICK(1))
{
SetAnimation(item, LA_FALL_START);
ResetPlayerLean(item);
@ -633,7 +609,7 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
item->Animation.IsAirborne = true;
item->Animation.Velocity.z = item->Animation.Velocity.y;
item->Animation.Velocity.y = 0.0f;
lara->Control.WaterStatus = WaterStatus::Dry;
player.Control.WaterStatus = WaterStatus::Dry;
}
else
{
@ -641,8 +617,8 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
ResetPlayerLean(item);
ResetPlayerFlex(item);
item->Animation.Velocity.y = 0.0f;
item->Pose.Position.y = waterHeight;
lara->Control.WaterStatus = WaterStatus::TreadWater;
item->Pose.Position.y = water.WaterHeight;
player.Control.WaterStatus = WaterStatus::TreadWater;
UpdateLaraRoom(item, -(STEPUP_HEIGHT - 3));
}
@ -654,8 +630,8 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
ResetPlayerLean(item);
ResetPlayerFlex(item);
item->Animation.Velocity.y = 0.0f;
item->Pose.Position.y = waterHeight + 1;
lara->Control.WaterStatus = WaterStatus::TreadWater;
item->Pose.Position.y = water.WaterHeight + 1;
player.Control.WaterStatus = WaterStatus::TreadWater;
UpdateLaraRoom(item, 0);
}
@ -663,19 +639,19 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
break;
case WaterStatus::TreadWater:
if (!isWater)
if (!water.IsWater)
{
if (heightFromWater <= WADE_DEPTH)
if (water.HeightFromWater <= WADE_WATER_DEPTH)
{
SetAnimation(item, LA_FALL_START);
item->Animation.IsAirborne = true;
item->Animation.Velocity.z = item->Animation.Velocity.y;
lara->Control.WaterStatus = WaterStatus::Dry;
player.Control.WaterStatus = WaterStatus::Dry;
}
else
{
SetAnimation(item, LA_STAND_IDLE);
lara->Control.WaterStatus = WaterStatus::Wade;
player.Control.WaterStatus = WaterStatus::Wade;
}
ResetPlayerLean(item);
@ -686,26 +662,26 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
break;
case WaterStatus::Wade:
Camera.targetElevation = -ANGLE(22.0f);
Camera.targetElevation = ANGLE(-22.0f);
if (heightFromWater >= WADE_DEPTH)
if (water.HeightFromWater >= WADE_WATER_DEPTH)
{
if (heightFromWater > SWIM_DEPTH && !isSwamp)
if (water.HeightFromWater > SWIM_WATER_DEPTH && !water.IsSwamp)
{
SetAnimation(item, LA_ONWATER_IDLE);
ResetPlayerLean(item);
ResetPlayerFlex(item);
item->Animation.IsAirborne = false;
item->Animation.Velocity.y = 0.0f;
item->Pose.Position.y += 1 - heightFromWater;
lara->Control.WaterStatus = WaterStatus::TreadWater;
item->Pose.Position.y += 1 - water.HeightFromWater;
player.Control.WaterStatus = WaterStatus::TreadWater;
UpdateLaraRoom(item, 0);
}
}
else
{
lara->Control.WaterStatus = WaterStatus::Dry;
player.Control.WaterStatus = WaterStatus::Dry;
if (item->Animation.ActiveState == LS_WADE_FORWARD)
item->Animation.TargetState = LS_RUN_FORWARD;
@ -715,150 +691,22 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
}
}
if (TestEnvironment(ENV_FLAG_DAMAGE, item) && item->HitPoints > 0)
item->HitPoints--;
HandlePlayerStatusEffects(*item, player.Control.WaterStatus, water);
if (item->HitPoints <= 0)
{
item->HitPoints = -1;
auto prevPos = item->Pose.Position;
if (lara->Control.Count.Death == 0)
StopSoundTracks(true);
lara->Control.Count.Death++;
if ((item->Flags & IFLAG_INVISIBLE))
{
lara->Control.Count.Death++;
return;
}
}
switch (lara->Control.WaterStatus)
switch (player.Control.WaterStatus)
{
case WaterStatus::Dry:
case WaterStatus::Wade:
if (isSwamp && lara->Context.WaterSurfaceDist < -(LARA_HEIGHT + 8)) // TODO: Find best height. @Sezz 2021.11.10
{
if (item->HitPoints >= 0)
{
lara->Status.Air -= 6;
if (lara->Status.Air < 0)
{
lara->Status.Air = -1;
item->HitPoints -= 10;
}
}
}
else if (lara->Status.Air < LARA_AIR_MAX && item->HitPoints >= 0)
{
// HACK: Special case for UPV.
if (lara->Context.Vehicle == NO_ITEM)
{
lara->Status.Air += 10;
if (lara->Status.Air > LARA_AIR_MAX)
lara->Status.Air = LARA_AIR_MAX;
}
}
if (item->HitPoints >= 0)
{
if (lara->Control.WaterStatus == WaterStatus::Dry)
{
// HACK: Special case for UPV.
if (lara->Context.Vehicle != NO_ITEM)
{
auto& vehicleItem = g_Level.Items[lara->Context.Vehicle];
if (vehicleItem.ObjectNumber == ID_UPV)
{
auto pointColl = GetCollision(item, 0, 0, CLICK(1));
isCold = isCold || TestEnvironment(ENV_FLAG_COLD, pointColl.RoomNumber);
if (isCold)
{
lara->Status.Exposure--;
if (lara->Status.Exposure <= 0)
{
lara->Status.Exposure = 0;
item->HitPoints -= 10;
}
}
}
}
else
{
lara->Status.Exposure++;
if (lara->Status.Exposure >= LARA_EXPOSURE_MAX)
lara->Status.Exposure = LARA_EXPOSURE_MAX;
}
}
else
{
if (isCold)
{
lara->Status.Exposure--;
if (lara->Status.Exposure <= 0)
{
lara->Status.Exposure = 0;
item->HitPoints -= 10;
}
}
}
}
LaraAboveWater(item, coll);
break;
case WaterStatus::Underwater:
if (item->HitPoints >= 0)
{
auto level = g_GameFlow->GetLevel(CurrentLevel);
if (level->GetLaraType() != LaraType::Divesuit)
lara->Status.Air--;
if (lara->Status.Air < 0)
{
item->HitPoints -= 5;
lara->Status.Air = -1;
}
if (isCold)
{
lara->Status.Exposure -= 2;
if (lara->Status.Exposure <= 0)
{
lara->Status.Exposure = 0;
item->HitPoints -= 10;
}
}
else
{
lara->Status.Exposure++;
if (lara->Status.Exposure >= LARA_EXPOSURE_MAX)
lara->Status.Exposure = LARA_EXPOSURE_MAX;
}
}
LaraUnderwater(item, coll);
break;
case WaterStatus::TreadWater:
if (item->HitPoints >= 0)
{
lara->Status.Air += 10;
if (lara->Status.Air > LARA_AIR_MAX)
lara->Status.Air = LARA_AIR_MAX;
if (isCold)
{
lara->Status.Exposure -= 2;
if (lara->Status.Exposure <= 0)
{
lara->Status.Exposure = 0;
item->HitPoints -= 10;
}
}
}
LaraWaterSurface(item, coll);
break;

View file

@ -87,8 +87,8 @@ constexpr auto PLAYER_BUBBLE_NODE_MAX = 12.0f;
constexpr auto STEPUP_HEIGHT = (int)CLICK(3 / 2.0f);
constexpr auto BAD_JUMP_CEILING = (int)CLICK(6 / 8.0f);
constexpr auto SHALLOW_WATER_DEPTH = (int)CLICK(1 / 2.0f);
constexpr auto WADE_DEPTH = STEPUP_HEIGHT;
constexpr auto SWIM_DEPTH = CLICK(3) - 38;
constexpr auto WADE_WATER_DEPTH = STEPUP_HEIGHT;
constexpr auto SWIM_WATER_DEPTH = CLICK(2.75f);
constexpr auto SLOPE_DIFFERENCE = 60;
extern LaraInfo Lara;

View file

@ -460,9 +460,9 @@ void LaraJumpCollision(ItemInfo* item, CollisionInfo* coll, short moveAngle)
void LaraSurfaceCollision(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
const auto& player = GetLaraInfo(*item);
coll->Setup.ForwardAngle = lara->Control.MoveAngle;
coll->Setup.ForwardAngle = player.Control.MoveAngle;
GetCollisionInfo(coll, item, Vector3i(0, LARA_HEIGHT_TREAD, 0));
ShiftItem(item, coll);
@ -474,14 +474,19 @@ void LaraSurfaceCollision(ItemInfo* item, CollisionInfo* coll)
item->Pose.Position = coll->Setup.PrevPosition;
}
else if (coll->CollisionType == CT_LEFT)
{
item->Pose.Orientation.y += ANGLE(5.0f);
}
else if (coll->CollisionType == CT_RIGHT)
{
item->Pose.Orientation.y -= ANGLE(5.0f);
}
if (GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber) - item->Pose.Position.y > -100)
TestLaraWaterStepOut(item, coll);
else
SetLaraSwimDiveAnimation(item);
auto pointColl = GetCollision(item);
int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
if ((pointColl.Position.Floor - item->Pose.Position.y) < SWIM_WATER_DEPTH)
TestPlayerWaterStepOut(item, coll);
}
void LaraSwimCollision(ItemInfo* item, CollisionInfo* coll)

View file

@ -92,6 +92,184 @@ void HandleLaraMovementParameters(ItemInfo* item, CollisionInfo* coll)
lara->Control.IsMonkeySwinging = false;
}
void HandlePlayerStatusEffects(ItemInfo& item, WaterStatus waterStatus, PlayerWaterData& water)
{
auto& player = GetLaraInfo(item);
// Update health status.
if (TestEnvironment(ENV_FLAG_DAMAGE, &item) && item.HitPoints > 0)
item.HitPoints--;
// Update poison status.
if (player.Status.Poison)
{
if (player.Status.Poison > LARA_POISON_MAX)
player.Status.Poison = LARA_POISON_MAX;
if (!(Wibble & 0xFF))
item.HitPoints -= player.Status.Poison;
}
// Update stamina status.
if (player.Status.Stamina < LARA_STAMINA_MAX && item.Animation.ActiveState != LS_SPRINT)
player.Status.Stamina++;
// TODO: Dehardcode values and make cleaner implementation.
// Handle environmental status effects.
switch (waterStatus)
{
case WaterStatus::Dry:
case WaterStatus::Wade:
// TODO: Find best height. -- Sezz 2021.11.10
if (water.IsSwamp && player.Context.WaterSurfaceDist < -(LARA_HEIGHT + 8))
{
if (item.HitPoints >= 0)
{
player.Status.Air -= 6;
if (player.Status.Air < 0)
{
player.Status.Air = -1;
item.HitPoints -= 10;
}
}
}
else if (player.Status.Air < LARA_AIR_MAX && item.HitPoints >= 0)
{
// HACK: Special case for UPV.
if (player.Context.Vehicle == NO_ITEM)
{
player.Status.Air += 10;
if (player.Status.Air > LARA_AIR_MAX)
player.Status.Air = LARA_AIR_MAX;
}
}
if (item.HitPoints >= 0)
{
if (player.Control.WaterStatus == WaterStatus::Dry)
{
// HACK: Special case for UPV.
if (player.Context.Vehicle != NO_ITEM)
{
const auto& vehicleItem = g_Level.Items[player.Context.Vehicle];
if (vehicleItem.ObjectNumber == ID_UPV)
{
auto pointColl = GetCollision(&item, 0, 0, CLICK(1));
water.IsCold = (water.IsCold || TestEnvironment(ENV_FLAG_COLD, pointColl.RoomNumber));
if (water.IsCold)
{
player.Status.Exposure--;
if (player.Status.Exposure <= 0)
{
player.Status.Exposure = 0;
item.HitPoints -= 10;
}
}
}
}
else
{
player.Status.Exposure++;
if (player.Status.Exposure >= LARA_EXPOSURE_MAX)
player.Status.Exposure = LARA_EXPOSURE_MAX;
}
}
else
{
if (water.IsCold)
{
player.Status.Exposure--;
if (player.Status.Exposure <= 0)
{
player.Status.Exposure = 0;
item.HitPoints -= 10;
}
}
else
{
player.Status.Exposure++;
if (player.Status.Exposure >= LARA_EXPOSURE_MAX)
player.Status.Exposure = LARA_EXPOSURE_MAX;
}
}
}
break;
case WaterStatus::Underwater:
if (item.HitPoints >= 0)
{
const auto& level = *g_GameFlow->GetLevel(CurrentLevel);
if (level.GetLaraType() != LaraType::Divesuit)
player.Status.Air--;
if (player.Status.Air < 0)
{
item.HitPoints -= 5;
player.Status.Air = -1;
}
if (water.IsCold)
{
player.Status.Exposure -= 2;
if (player.Status.Exposure <= 0)
{
player.Status.Exposure = 0;
item.HitPoints -= 10;
}
}
else
{
player.Status.Exposure++;
if (player.Status.Exposure >= LARA_EXPOSURE_MAX)
player.Status.Exposure = LARA_EXPOSURE_MAX;
}
}
break;
case WaterStatus::TreadWater:
if (item.HitPoints >= 0)
{
player.Status.Air += 10;
if (player.Status.Air > LARA_AIR_MAX)
player.Status.Air = LARA_AIR_MAX;
if (water.IsCold)
{
player.Status.Exposure -= 2;
if (player.Status.Exposure <= 0)
{
player.Status.Exposure = 0;
item.HitPoints -= 10;
}
}
}
break;
default:
break;
}
// Update death counter.
if (item.HitPoints <= 0)
{
item.HitPoints = -1;
if (player.Control.Count.Death == 0)
StopSoundTracks(true);
player.Control.Count.Death++;
if ((item.Flags & IFLAG_INVISIBLE))
{
player.Control.Count.Death++;
return;
}
}
}
static void UsePlayerMedipack(ItemInfo& item)
{
auto& player = GetLaraInfo(item);
@ -825,6 +1003,25 @@ LaraInfo*& GetLaraInfo(ItemInfo* item)
return (LaraInfo*&)firstPlayerItem.Data;
}
PlayerWaterData GetPlayerWaterData(ItemInfo& item)
{
bool isWater = TestEnvironment(ENV_FLAG_WATER, &item);
bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, &item);
bool isCold = TestEnvironment(ENV_FLAG_COLD, &item);
int waterDepth = GetWaterDepth(&item);
int waterHeight = GetWaterHeight(&item);
auto pointColl = GetCollision(item);
int heightFromWater = (waterHeight == NO_HEIGHT) ? NO_HEIGHT : (std::min(item.Pose.Position.y, pointColl.Position.Floor) - waterHeight);
return PlayerWaterData
{
isWater, isSwamp, isCold,
waterDepth, waterHeight, heightFromWater
};
}
short GetLaraSlideDirection(ItemInfo* item, CollisionInfo* coll)
{
short headingAngle = coll->Setup.ForwardAngle;

View file

@ -1,17 +1,30 @@
#pragma once
#include "Game/collision/collide_room.h"
enum class WaterStatus;
struct ItemInfo;
struct CollisionInfo;
struct LaraInfo;
struct VaultTestResult;
struct PlayerWaterData
{
bool IsWater = false;
bool IsSwamp = false;
bool IsCold = false;
int WaterDepth = 0;
int WaterHeight = 0;
int HeightFromWater = 0;
};
// -----------------------------
// HELPER FUNCTIONS
// For State Control & Collision
// -----------------------------
void HandleLaraMovementParameters(ItemInfo* item, CollisionInfo* coll);
void HandlePlayerStatusEffects(ItemInfo& item, WaterStatus waterStatus, PlayerWaterData& water);
void HandlePlayerQuickActions(ItemInfo& item);
bool CanPlayerLookAround(const ItemInfo& item); // TODO: Move to context file. -- Sezz 2023.08.22
void HandlePlayerLookAround(ItemInfo& item, bool invertXAxis = true);
@ -33,6 +46,7 @@ LaraInfo& GetLaraInfo(ItemInfo& item);
const LaraInfo& GetLaraInfo(const ItemInfo& item);
LaraInfo*& GetLaraInfo(ItemInfo* item);
PlayerWaterData GetPlayerWaterData(ItemInfo& item);
short GetLaraSlideDirection(ItemInfo* item, CollisionInfo* coll);
short ModulateLaraTurnRate(short turnRate, short accelRate, short minTurnRate, short maxTurnRate, float axisCoeff, bool invert);

View file

@ -877,34 +877,39 @@ CollisionResult LaraCeilingCollisionFront(ItemInfo* item, short angle, int dista
return probe;
}
bool TestLaraWaterStepOut(ItemInfo* item, CollisionInfo* coll)
bool TestPlayerWaterStepOut(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
auto& player = GetLaraInfo(*item);
// Get point collision.
auto pointColl = GetCollision(item);
int vPos = item->Pose.Position.y;
if (coll->CollisionType == CT_FRONT ||
coll->Middle.FloorSlope ||
coll->Middle.Floor >= 0)
pointColl.Position.FloorSlope ||
(pointColl.Position.Floor - vPos) <= 0)
{
return false;
}
if (coll->Middle.Floor >= -CLICK(0.5f))
if ((pointColl.Position.Floor - vPos) >= -CLICK(0.5f))
{
SetAnimation(item, LA_STAND_IDLE);
}
else
{
SetAnimation(item, LA_ONWATER_TO_WADE_1_STEP);
item->Animation.TargetState = LS_IDLE;
}
item->Pose.Position.y += coll->Middle.Floor + CLICK(2.75f) - 9;
item->Pose.Position.y = pointColl.Position.Floor;
UpdateLaraRoom(item, -(STEPUP_HEIGHT - 3));
item->Pose.Orientation.x = 0;
item->Pose.Orientation.z = 0;
item->Animation.Velocity.z = 0;
item->Animation.Velocity.y = 0;
ResetPlayerLean(item);
item->Animation.Velocity.y = 0.0f;
item->Animation.Velocity.z = 0.0f;
item->Animation.IsAirborne = false;
lara->Control.WaterStatus = WaterStatus::Wade;
player.Control.WaterStatus = WaterStatus::Wade;
return true;
}
@ -933,11 +938,12 @@ bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll)
return false;
}
//Do extra check if it's a bridge collider:
// Extra bridge check.
if (coll->Front.Bridge != NO_ITEM)
{
auto distancePointLara = GetBridgeBorder(coll->Front.Bridge, false) - LaraItem->Pose.Position.y;
frontFloor = distancePointLara - 128;
int bridgeBorder = GetBridgeBorder(coll->Front.Bridge, false) - item->Pose.Position.y;
frontFloor = bridgeBorder - CLICK(0.5f);
if (frontFloor <= -CLICK(2) ||
frontFloor > CLICK(1.25f) - 4)
{
@ -1084,29 +1090,27 @@ bool TestLaraLadderClimbOut(ItemInfo* item, CollisionInfo* coll) // NEW function
void TestLaraWaterDepth(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
auto& player = GetLaraInfo(*item);
auto probe = GetCollision(item);
int waterDepth = GetWaterDepth(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, probe.RoomNumber);
auto pointColl = GetCollision(item);
int waterDepth = GetWaterDepth(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, pointColl.RoomNumber);
if (waterDepth == NO_HEIGHT)
{
item->Animation.Velocity.y = 0;
item->Animation.Velocity.y = 0.0f;
item->Pose.Position = coll->Setup.PrevPosition;
}
// 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)
else if (waterDepth <= (LARA_HEIGHT - (LARA_HEADROOM / 2)))
{
SetAnimation(item, LA_UNDERWATER_TO_STAND);
ResetPlayerLean(item);
item->Animation.TargetState = LS_IDLE;
item->Pose.Position.y = probe.Position.Floor;
item->Pose.Orientation.x = 0;
item->Pose.Orientation.z = 0;
item->Pose.Position.y = pointColl.Position.Floor;
item->Animation.IsAirborne = false;
item->Animation.Velocity.z = 0;
item->Animation.Velocity.y = 0;
lara->Control.WaterStatus = WaterStatus::Wade;
item->Animation.Velocity.y = 0.0f;
item->Animation.Velocity.z = 0.0f;
player.Control.WaterStatus = WaterStatus::Wade;
}
}

View file

@ -37,7 +37,7 @@ int LaraCeilingFront(ItemInfo* item, short angle, int distance, int height);
CollisionResult LaraCollisionFront(ItemInfo* item, short angle, int distance);
CollisionResult LaraCeilingCollisionFront(ItemInfo* item, short angle, int distance, int height);
bool TestLaraWaterStepOut(ItemInfo* item, CollisionInfo* coll);
bool TestPlayerWaterStepOut(ItemInfo* item, CollisionInfo* coll);
bool TestLaraWaterClimbOut(ItemInfo* item, CollisionInfo* coll);
bool TestLaraLadderClimbOut(ItemInfo* item, CollisionInfo* coll);
void TestLaraWaterDepth(ItemInfo* item, CollisionInfo* coll);

View file

@ -1182,6 +1182,8 @@ void CalculateCamera(const CollisionInfo& coll)
LastTarget.y = Camera.target.y;
LastTarget.z = Camera.target.z;
LastTarget.RoomNumber = Camera.target.RoomNumber;
y -= CLICK(0.25f);
}
Camera.target.RoomNumber = item->RoomNumber;
@ -1193,8 +1195,8 @@ void CalculateCamera(const CollisionInfo& coll)
}
else
{
Camera.target.y += (y - Camera.target.y) >> 2;
Camera.speed = Camera.type != CameraType::Look ? 8 : 4;
Camera.target.y += (y - Camera.target.y) / 4;
Camera.speed = (Camera.type != CameraType::Look) ? 8 : 4;
}
Camera.fixedCamera = false;
@ -1240,20 +1242,22 @@ void CalculateCamera(const CollisionInfo& coll)
{
Camera.fixedCamera = false;
if (Camera.speed != 1 &&
Camera.oldType != CameraType::Look &&
!Lara.Control.Look.IsUsingBinoculars)
{
if (TargetSnaps <= 8)
{
x = LastTarget.x + ((x - LastTarget.x) >> 2);
x = LastTarget.x + ((x - LastTarget.x) / 4);
y = LastTarget.y + ((y - LastTarget.y) / 4);
z = LastTarget.z + ((z - LastTarget.z) / 4);
Camera.target.x = x;
y = LastTarget.y + ((y - LastTarget.y) >> 2);
Camera.target.y = y;
z = LastTarget.z + ((z - LastTarget.z) >> 2);
Camera.target.z = z;
}
else
{
TargetSnaps = 0;
}
}
}
else
@ -1274,9 +1278,13 @@ void CalculateCamera(const CollisionInfo& coll)
}
if (Camera.type != CameraType::Chase && Camera.flags != CF_CHASE_OBJECT)
{
FixedCamera(item);
}
else
{
ChaseCamera(item);
}
}
Camera.fixedCamera = isFixedCamera;
@ -1304,14 +1312,13 @@ bool TestBoundsCollideCamera(const GameBoundingBox& bounds, const Pose& pose, sh
return sphere.Intersects(bounds.ToBoundingOrientedBox(pose));
}
float GetParticleDistanceFade(Vector3i position)
float GetParticleDistanceFade(const Vector3i& pos)
{
float distance = Vector3::Distance(Camera.pos.ToVector3(), position.ToVector3());
if (distance <= PARTICLE_FADE_THRESHOLD)
float dist = Vector3::Distance(Camera.pos.ToVector3(), pos.ToVector3());
if (dist <= PARTICLE_FADE_THRESHOLD)
return 1.0f;
return std::clamp(1.0f - ((distance - PARTICLE_FADE_THRESHOLD) / COLL_CHECK_THRESHOLD), 0.0f, 1.0f);
return std::clamp(1.0f - ((dist - PARTICLE_FADE_THRESHOLD) / COLL_CHECK_THRESHOLD), 0.0f, 1.0f);
}
void ItemPushCamera(GameBoundingBox* bounds, Pose* pos, short radius)

View file

@ -115,4 +115,4 @@ void UpdateFadeScreenAndCinematicBars();
void UpdateMikePos(const ItemInfo& item);
void ClearObjCamera();
float GetParticleDistanceFade(Vector3i position);
float GetParticleDistanceFade(const Vector3i& pos);

View file

@ -1236,43 +1236,58 @@ void TriggerDynamicLight(int x, int y, int z, short falloff, byte r, byte g, byt
g_Renderer.AddDynamicLight(x, y, z, falloff, r, g, b);
}
void WadeSplash(ItemInfo* item, int wh, int wd)
// TODO: Better implementation.
void SpawnPlayerSplash(const ItemInfo& item, int waterHeight, int waterDepth)
{
auto probe1 = GetCollision(item);
auto probe2 = GetCollision(probe1.Block, item->Pose.Position.x, probe1.Position.Ceiling, item->Pose.Position.z);
// Get point collision.
auto pointColl0 = GetCollision(item);
auto pointColl1 = GetCollision(pointColl0.Block, item.Pose.Position.x, pointColl0.Position.Ceiling, item.Pose.Position.z);
if (!TestEnvironment(ENV_FLAG_WATER, probe1.RoomNumber) ||
TestEnvironment(ENV_FLAG_WATER, probe1.RoomNumber) == TestEnvironment(ENV_FLAG_WATER, probe2.RoomNumber))
if (!TestEnvironment(ENV_FLAG_WATER, pointColl0.RoomNumber) ||
TestEnvironment(ENV_FLAG_WATER, pointColl0.RoomNumber) == TestEnvironment(ENV_FLAG_WATER, pointColl1.RoomNumber))
{
return;
}
const auto& bounds = GetBestFrame(item).BoundingBox;
if (item.Pose.Position.y + bounds.Y1 > waterHeight)
return;
const auto& bounds = GetBestFrame(*item).BoundingBox;
if (item->Pose.Position.y + bounds.Y1 > wh)
if (item.Pose.Position.y + bounds.Y2 < waterHeight)
return;
if (item->Pose.Position.y + bounds.Y2 < wh)
return;
if (item->Animation.Velocity.y <= 0.0f || wd >= 474 || SplashCount != 0)
if (item.Animation.Velocity.y <= 0.0f || waterDepth >= 474 || SplashCount != 0)
{
if (!(Wibble & 0xF))
{
if (!(GetRandomControl() & 0xF) || item->Animation.ActiveState != LS_IDLE)
if (!(GetRandomControl() & 0xF) || item.Animation.ActiveState != LS_IDLE)
{
if (item->Animation.ActiveState != LS_IDLE)
SpawnRipple(Vector3(item->Pose.Position.x, wh - 1, item->Pose.Position.z), item->RoomNumber, 112 + (GetRandomControl() & 15), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity);
if (item.Animation.ActiveState != LS_IDLE)
{
SpawnRipple(
Vector3(item.Pose.Position.x, waterHeight - 1, item.Pose.Position.z),
item.RoomNumber, 112 + (GetRandomControl() & 15),
(int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity);
}
else
SpawnRipple(Vector3(item->Pose.Position.x, wh - 1, item->Pose.Position.z), item->RoomNumber, 112 + (GetRandomControl() & 15), (int)RippleFlags::LowOpacity);
{
SpawnRipple(
Vector3(item.Pose.Position.x, waterHeight - 1, item.Pose.Position.z),
item.RoomNumber, 112 + (GetRandomControl() & 15),
(int)RippleFlags::LowOpacity);
}
}
}
}
else
{
SplashSetup.y = wh - 1;
SplashSetup.x = item->Pose.Position.x;
SplashSetup.z = item->Pose.Position.z;
SplashSetup.x = item.Pose.Position.x;
SplashSetup.y = waterHeight - 1;
SplashSetup.z = item.Pose.Position.z;
SplashSetup.innerRadius = 16;
SplashSetup.splashPower = item->Animation.Velocity.z;
SetupSplash(&SplashSetup, probe1.RoomNumber);
SplashSetup.splashPower = item.Animation.Velocity.z;
SetupSplash(&SplashSetup, pointColl0.RoomNumber);
SplashCount = 16;
}
}

View file

@ -273,7 +273,7 @@ void TriggerFlashSmoke(int x, int y, int z, short roomNumber);
void TriggerMetalSparks(int x, int y, int z, int xv, int yv, int zv, const Vector3& color, int additional);
void SpawnCorpseEffect(const Vector3& pos);
void TriggerAttackFlame(const Vector3i& pos, const Vector3& color, int scale);
void WadeSplash(ItemInfo* item, int wh, int wd);
void SpawnPlayerSplash(const ItemInfo& item, int waterHeight, int waterDepth);
void Splash(ItemInfo* item);
void TriggerRocketFire(int x, int y, int z);
void TriggerExplosionBubbles(int x, int y, int z, short roomNumber);

View file

@ -631,11 +631,11 @@ void StopSoundTracks(bool excludeAmbience)
{
for (int i = 0; i < (int)SoundTrackType::Count; i++)
{
auto mode = (SoundTrackType)i;
if (excludeAmbience && mode == SoundTrackType::BGM)
auto type = (SoundTrackType)i;
if (excludeAmbience && type == SoundTrackType::BGM)
continue;
StopSoundTrack((SoundTrackType)i, SOUND_XFADETIME_ONESHOT);
StopSoundTrack(type, SOUND_XFADETIME_ONESHOT);
}
}