Merge branch 'master' into lua_1.0.2

This commit is contained in:
hispidence 2022-09-03 16:54:42 +01:00
commit e16a98a8aa
246 changed files with 16974 additions and 15499 deletions

View file

@ -6,36 +6,56 @@ Version 1.0.2
- GiveItem, TakeItem, and SetItemCount have been reworked (e.g. SetItemCount with a value of -1 can give infinite ammo/consumables).
- Removing Pistols with TakeItem and SetItemCount now works correctly.
- Vec3s can now be saved and loaded in LevelVars and GameVars.
* Adjust max turn rate of idle state.
* Align Lara on slopes when crouching, crawling, and dying.
* Better slope alignment for large, flat enemies (i.e. big scorpion and crocodile).
* Lock turn rate when automatically aligning Lara to objects.
* Don't play Lara alignment animations if the interacted object is too close.
* Fix incorrect pole grabbing.
* Fix zeroed forward velocity upon landing.
* Fix 1-frame turn rate delays.
* Fix occasional leave event calls when moving closer to volumes.
* Fix incorrect viewport size in windowed mode.
* Fix Lara's subway train death so that she no longer slides slowly after the animation has finished.
Version 1.0.1
=============
- Added antialiasing support.
- Fix some issues with shimmying between diagonal ledges and walls.
- Fix rope transparency.
- Fix objects disappearing under certain angles at the edges of the screen.
- Fix incorrect polerope and jumpswitch grabbing.
- Fix camera behaviour with pushable blocks.
- Fix minecart unduck on inclines.
- Fix quadbike dismount with jump key and allow to shoot big gun with action key.
- Fix static meshes having wrong colors on savegame reload.
- Fix rollingball incorrectly killing Lara in water and in jump.
- Fix resurfacing on underwater death.
- Fix ripples not appearing on water connections higher than room bottom.
- Fix several problems with ropes (stumbling, rope length, etc).
- Fix several problems with teeth spikes.
- Fix crashes when loading image files are missing.
- Disable trigger check for puzzle holes.
- Clear locusts and other swarm enemies on level reload.
- Prevent title music audio from starting in a random place.
- Update harpoon speed on room change.
- Enable second sky layer rendering.
- Preserve inventory and flare on level jumps.
- Timer.Create now lets you choose the units to display remaining time.
- Fatal script errors now boot you to the title (it will crash if the title itself has these errors).
- SetFarView has been removed, and Flow.Level.farView is now uncapped.
- DisplayString text will now be cleared when a level is exited or reloaded.
- EventSequence.lua has been added and documented.
* Added antialiasing support.
* Added static mesh scaling support.
* Added free rotation for teeth spikes instead of using OCB codes.
* Fix some issues with shimmying between diagonal ledges and walls.
* Fix rope transparency.
* Fix objects disappearing under certain angles at the edges of the screen.
* Fix incorrect polerope and jumpswitch grabbing.
* Fix camera behaviour with pushable blocks.
* Fix minecart unduck on inclines.
* Fix quadbike dismount with jump key and allow to shoot big gun with action key.
* Fix static meshes having wrong colors on savegame reload.
* Fix rollingball incorrectly killing Lara in water and in jump.
* Fix resurfacing on underwater death.
* Fix water to ladder animation not activating in all cases.
* Fix ripples not appearing on water connections higher than room bottom.
* Fix several problems with ropes (stumbling, rope length, etc).
* Fix several problems with teeth spikes.
* Fix falling through twoblock platform on room number change.
* Fix falling block breaking too early if placed on a vertical portal.
* Fix crashes when loading image files are missing.
* Disable trigger check for puzzle holes.
* Clear locusts and other swarm enemies on level reload.
* Enhance cobra AI and fix targeting.
* Fully decompile HAMMER object from TR4.
* Prevent title music audio from starting in a random place.
* Update harpoon speed on room change.
* Enable second sky layer rendering.
* Preserve inventory and flare on level jumps.
* Timer.Create now lets you choose the units to display remaining time.
* Fatal script errors now boot you to the title (it will crash if the title itself has these errors).
* SetFarView has been removed, and Flow.Level.farView is now uncapped.
* DisplayString text will now be cleared when a level is exited or reloaded.
* EventSequence.lua has been added and documented.
Version 1.0
===========

View file

@ -12,9 +12,14 @@ local InvID = Flow.InvID
local RotationAxis = Flow.RotationAxis
local ItemAction = Flow.ItemAction
-- These variables are unused for now.
-- Intro image is a splash screen which appears before actual loading screen.
-- If you don't want it to appear, just remove this line.
Flow.SetIntroImagePath("Screens\\main.jpg")
-- This image should be used for static title screen background (as in TR1-TR3).
-- For now it is not implemented.
Flow.SetTitleScreenImagePath("Screens\\main.jpg")
@ -38,7 +43,7 @@ Flow.AddLevel(title)
test = Level.new()
test.nameKey = "level_test"
test.scriptFile = "Scripts\\TestLevel.lua"
test.scriptFile = "Scripts\\New_Level.lua"
test.ambientTrack = "108"
test.levelFile = "Data\\TestLevel.ten"
test.loadScreenFile = "Screens\\rome.jpg"

30
Scripts/New_Level.lua Normal file
View file

@ -0,0 +1,30 @@
-- New level script file.
-- To include other script files, you can use require("filename") command.
local Util = require("Util")
Util.ShortenTENCalls()
-- Called when entering a level, either after leveljump, new game or loading game
LevelFuncs.OnStart = function() end
-- Called after loading from a save
LevelFuncs.OnLoad = function() end
-- Called after saving game
LevelFuncs.OnSave = function() end
-- Called on every frame of the game
-- dt stands for "delta time", and holds the time in seconds since the last call to OnControlPhase
LevelFuncs.OnControlPhase = function(dt) end
-- Called when level is ended, either after leveljump, quitting to title or loading game
LevelFuncs.OnEnd = function() end
-- An example function which prints a string and leaves it on screen for 1 second.
-- Argument should be typed in TE trigger manager window's argument text field.
LevelFuncs.PrintText = function(Triggerer, Argument)
local TestText = DisplayString(Argument, 100, 100, Color.new(250,250,250))
ShowString(TestText, 1)
end

View file

@ -457,8 +457,6 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
RumbleLaraHealthCondition(item);
lara->Control.IsLow = false;
bool isWater = TestEnvironment(ENV_FLAG_WATER, item);
bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, item);
@ -785,11 +783,9 @@ void LaraAboveWater(ItemInfo* item, CollisionInfo* coll)
if (HandleLaraVehicle(item, coll))
return;
HandleLaraMovementParameters(item, coll);
// Handle current Lara status.
lara_control_routines[item->Animation.ActiveState](item, coll);
HandleLaraMovementParameters(item, coll);
AnimateLara(item);
if (lara->ExtraAnim == NO_ITEM)
@ -820,6 +816,8 @@ void LaraWaterSurface(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
lara->Control.IsLow = false;
Camera.targetElevation = -ANGLE(22.0f);
coll->Setup.Mode = CollisionProbeMode::FreeForward;
@ -890,6 +888,8 @@ void LaraUnderwater(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
lara->Control.IsLow = false;
coll->Setup.Mode = CollisionProbeMode::Quadrants;
coll->Setup.Radius = LARA_RADIUS_UNDERWATER;
coll->Setup.Height = LARA_HEIGHT;

View file

@ -50,7 +50,7 @@ constexpr int LARA_RADIUS = 100;
constexpr int LARA_RADIUS_CRAWL = 200;
constexpr int LARA_RADIUS_UNDERWATER = 300;
constexpr int LARA_RADIUS_DEATH = 400;
constexpr int LARA_VELOCITY = 12; // TODO: Float.
constexpr int LARA_ALIGN_VELOCITY = 12; // TODO: Float.
constexpr auto LARA_FREEFALL_VELOCITY = 131.0f;
constexpr auto LARA_DAMAGE_VELOCITY = 141.0f;

View file

@ -139,9 +139,12 @@ void lara_as_walk_forward(ItemInfo* item, CollisionInfo* coll)
return;
}
// TODO: Implement item alignment properly someday. @Sezz 2021.11.01
// TODO: Implement item alignment properly someday. -- Sezz 2021.11.01
if (lara->Control.IsMoving)
{
ModulateLaraTurnRateY(item, 0, 0, 0);
return;
}
if (TrInput & (IN_LEFT | IN_RIGHT))
{
@ -410,7 +413,7 @@ void lara_as_idle(ItemInfo* item, CollisionInfo* coll)
(TrInput & IN_RIGHT &&
!(TrInput & IN_RSTEP || (TrInput & IN_WALK && TrInput & IN_RIGHT))))
{
ModulateLaraTurnRateY(item, LARA_TURN_RATE_ACCEL, 0, LARA_SLOW_TURN_RATE_MAX);
ModulateLaraTurnRateY(item, LARA_TURN_RATE_ACCEL, 0, LARA_SLOW_MED_TURN_RATE_MAX);
}
}
@ -1325,13 +1328,13 @@ void lara_col_turn_left_slow(ItemInfo* item, CollisionInfo* coll)
void lara_as_death(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
auto* bounds = GetBoundsAccurate(item);
item->Animation.Velocity.z = 0.0f;
lara->Control.CanLook = false;
coll->Setup.EnableObjectPush = false;
coll->Setup.EnableSpasm = false;
ModulateLaraTurnRateY(item, 0, 0, 0);
if (BinocularRange)
{
BinocularRange = 0;
@ -1340,6 +1343,11 @@ void lara_as_death(ItemInfo* item, CollisionInfo* coll)
item->MeshBits = ALL_JOINT_BITS;
lara->Inventory.IsBusy = false;
}
if (bounds->Height() <= (LARA_HEIGHT * 0.75f))
AlignLaraToSurface(item);
ModulateLaraTurnRateY(item, 0, 0, 0);
}
// State: LS_DEATH (8)
@ -1421,7 +1429,10 @@ void lara_as_walk_back(ItemInfo* item, CollisionInfo* coll)
}
if (lara->Control.IsMoving)
{
ModulateLaraTurnRateY(item, 0, 0, 0);
return;
}
if (isSwamp && lara->Control.WaterStatus == WaterStatus::Wade)
{
@ -1781,7 +1792,10 @@ void lara_as_step_right(ItemInfo* item, CollisionInfo* coll)
}
if (lara->Control.IsMoving)
{
ModulateLaraTurnRateY(item, 0, 0, 0);
return;
}
if (TrInput & IN_WALK) // WALK locks orientation.
ModulateLaraTurnRateY(item, 0, 0, 0);
@ -1872,7 +1886,10 @@ void lara_as_step_left(ItemInfo* item, CollisionInfo* coll)
}
if (lara->Control.IsMoving)
{
ModulateLaraTurnRateY(item, 0, 0, 0);
return;
}
if (TrInput & IN_WALK) // WALK locks orientation.
ModulateLaraTurnRateY(item, 0, 0, 0);

View file

@ -42,6 +42,8 @@ void lara_as_crouch_idle(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
// TODO: Dispatch pickups from within states.
if (item->Animation.TargetState == LS_PICKUP)
return;
@ -156,6 +158,8 @@ void lara_as_crouch_roll(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (TrInput & (IN_LEFT | IN_RIGHT))
{
ModulateLaraTurnRateY(item, LARA_TURN_RATE_ACCEL, 0, LARA_CROUCH_ROLL_TURN_RATE_MAX);
@ -227,6 +231,8 @@ void lara_as_crouch_turn_left(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (item->HitPoints <= 0)
{
item->Animation.TargetState = LS_DEATH;
@ -282,6 +288,8 @@ void lara_as_crouch_turn_right(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (item->HitPoints <= 0)
{
item->Animation.TargetState = LS_DEATH;
@ -337,6 +345,8 @@ void lara_as_crouch_turn_180(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if ((TrInput & IN_CROUCH || lara->Control.KeepLow) &&
lara->Control.WaterStatus != WaterStatus::Wade)
{
@ -375,6 +385,8 @@ void lara_as_crawl_idle(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
// TODO: Dispatch pickups from within states.
if (item->Animation.TargetState == LS_PICKUP)
return;
@ -520,6 +532,8 @@ void lara_as_crawl_forward(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (item->HitPoints <= 0)
{
item->Animation.TargetState = LS_DEATH;
@ -614,6 +628,8 @@ void lara_as_crawl_back(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (item->HitPoints <= 0)
{
item->Animation.TargetState = LS_DEATH;
@ -699,6 +715,8 @@ void lara_as_crawl_turn_left(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (item->HitPoints <= 0)
{
item->Animation.TargetState = LS_DEATH;
@ -758,6 +776,8 @@ void lara_as_crawl_turn_right(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if (item->HitPoints <= 0)
{
item->Animation.TargetState = LS_DEATH;
@ -815,6 +835,8 @@ void lara_as_crawl_turn_180(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = false;
Camera.targetDistance = SECTOR(1);
AlignLaraToSurface(item);
if ((TrInput & IN_CROUCH || lara->Control.KeepLow) &&
lara->Control.WaterStatus != WaterStatus::Wade)
{
@ -842,6 +864,8 @@ void lara_col_crawl_to_hang(ItemInfo* item, CollisionInfo* coll)
Camera.targetAngle = 0;
Camera.targetDistance = SECTOR(1);
ResetLaraLean(item, 6.0f);
if (item->Animation.AnimNumber == LA_CRAWL_TO_HANG_END)
{
lara->Control.MoveAngle = item->Pose.Orientation.y;

View file

@ -938,7 +938,7 @@ void FindTargetPoint(ItemInfo* item, GameVector* target)
{
auto* bounds = (BOUNDING_BOX*)GetBestFrame(item);
int x = (int)(bounds->X1 + bounds->X2) / 2;
int y = (int) bounds->Y1 + (bounds->Y2 - bounds->Y1) / 3;
int y = (int) bounds->Y1 + bounds->Height() / 3;
int z = (int)(bounds->Z1 + bounds->Z2) / 2;
float c = phd_cos(item->Pose.Orientation.y);

View file

@ -1,13 +1,13 @@
#include "framework.h"
#include "Game/Lara/lara_helpers.h"
#include "Flow/ScriptInterfaceFlowHandler.h"
#include "Game/collision/collide_room.h"
#include "Game/control/control.h"
#include "Game/control/volume.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_collide.h"
#include "Flow/ScriptInterfaceFlowHandler.h"
#include "Game/Lara/lara_fire.h"
#include "Game/Lara/lara_tests.h"
#include "Renderer/Renderer11.h"
@ -57,8 +57,11 @@ void HandleLaraMovementParameters(ItemInfo* item, CollisionInfo* coll)
lara->Control.RunJumpQueued = false;
// Reset lean.
if (!lara->Control.IsMoving || (lara->Control.IsMoving && !(TrInput & (IN_LEFT | IN_RIGHT))))
if ((!lara->Control.IsMoving || (lara->Control.IsMoving && !(TrInput & (IN_LEFT | IN_RIGHT)))) &&
(!lara->Control.IsLow && item->Animation.ActiveState != LS_DEATH)) // HACK: Don't interfere with surface alignment in crouch, crawl, and death states.
{
ResetLaraLean(item, 6.0f);
}
// Reset crawl flex.
if (!(TrInput & IN_LOOK) && coll->Setup.Height > LARA_HEIGHT - LARA_HEADROOM && // HACK
@ -71,6 +74,8 @@ void HandleLaraMovementParameters(ItemInfo* item, CollisionInfo* coll)
item->Pose.Orientation.y += lara->Control.TurnRate;
if (!(TrInput & (IN_LEFT | IN_RIGHT)))
lara->Control.TurnRate = 0;
lara->Control.IsLow = false;
}
bool HandleLaraVehicle(ItemInfo* item, CollisionInfo* coll)
@ -332,7 +337,7 @@ short GetLaraSlideDirection(ItemInfo* item, CollisionInfo* coll)
// Get either:
// a) the surface aspect angle (extended slides), or
// b) the derived nearest cardinal direction from it (original slides).
headingAngle = GetSurfaceAspectAngle(probe.FloorTilt.x, probe.FloorTilt.y);
headingAngle = GetSurfaceAspectAngle(probe.FloorTilt);
if (g_GameFlow->HasSlideExtended())
return headingAngle;
else
@ -539,8 +544,8 @@ void ModulateLaraSlideVelocity(ItemInfo* item, CollisionInfo* coll)
{
auto probe = GetCollision(item);
short minSlideAngle = ANGLE(33.75f);
short steepness = GetSurfaceSteepnessAngle(probe.FloorTilt.x, probe.FloorTilt.y);
short direction = GetSurfaceAspectAngle(probe.FloorTilt.x, probe.FloorTilt.y);
short steepness = GetSurfaceSteepnessAngle(probe.FloorTilt);
short direction = GetSurfaceAspectAngle(probe.FloorTilt);
float velocityMultiplier = 1 / (float)ANGLE(33.75f);
int slideVelocity = std::min<int>(minVelocity + 10 * (steepness * velocityMultiplier), LARA_TERMINAL_VELOCITY);
@ -555,7 +560,25 @@ void ModulateLaraSlideVelocity(ItemInfo* item, CollisionInfo* coll)
//lara->ExtraVelocity.x += minVelocity;
}
void SetLaraJumpDirection(ItemInfo* item, CollisionInfo* coll)
void AlignLaraToSurface(ItemInfo* item, float alpha)
{
auto floorTilt = GetCollision(item).FloorTilt;
short aspectAngle = GetSurfaceAspectAngle(floorTilt);
short steepnessAngle = std::min(GetSurfaceSteepnessAngle(floorTilt), ANGLE(70.0f));
short deltaAngle = GetShortestAngularDistance(item->Pose.Orientation.y, aspectAngle);
float sinDeltaAngle = phd_sin(deltaAngle);
float cosDeltaAngle = phd_cos(deltaAngle);
auto extraRot = Vector3Shrt(
-steepnessAngle * cosDeltaAngle,
0,
steepnessAngle * sinDeltaAngle
) - Vector3Shrt(item->Pose.Orientation.x, 0, item->Pose.Orientation.z);
item->Pose.Orientation += extraRot * alpha;
}
void SetLaraJumpDirection(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
@ -634,10 +657,8 @@ void SetLaraVault(ItemInfo* item, CollisionInfo* coll, VaultTestResult vaultResu
void SetLaraLand(ItemInfo* item, CollisionInfo* coll)
{
//item->IsAirborne = false; // TODO: Removing this avoids an unusual landing bug Core had worked around in an obscure way. I hope to find a proper solution. @Sezz 2022.02.18
item->Animation.Velocity.z = 0;
item->Animation.Velocity.y = 0;
//item->IsAirborne = false; // TODO: Removing this avoids an unusual landing bug. I hope to find a proper solution later. -- Sezz 2022.02.18
item->Animation.Velocity.y = 0.0f;
LaraSnapToHeight(item, coll);
}

View file

@ -35,6 +35,7 @@ void UpdateLaraSubsuitAngles(ItemInfo* item);
void ModulateLaraLean(ItemInfo* item, CollisionInfo* coll, short baseRate, short maxAngle);
void ModulateLaraCrawlFlex(ItemInfo* item, short baseRate, short maxAngle);
void ModulateLaraSlideVelocity(ItemInfo* item, CollisionInfo* coll);
void AlignLaraToSurface(ItemInfo* item, float alpha = 0.15f);
void SetLaraJumpDirection(ItemInfo* item, CollisionInfo* coll);
void SetLaraRunJumpQueue(ItemInfo* item, CollisionInfo* coll);

View file

@ -780,7 +780,7 @@ void lara_col_swan_dive(ItemInfo* item, CollisionInfo* coll)
auto* lara = GetLaraInfo(item);
auto* bounds = GetBoundsAccurate(item);
int realHeight = g_GameFlow->HasCrawlspaceSwandive() ? ((bounds->Y2 - bounds->Y1) * 0.7f) : LARA_HEIGHT;
int realHeight = g_GameFlow->HasCrawlspaceSwandive() ? (bounds->Height() * 0.7f) : LARA_HEIGHT;
lara->Control.MoveAngle = item->Pose.Orientation.y;
coll->Setup.Height = std::max(LARA_HEIGHT_CRAWL, realHeight);

View file

@ -495,10 +495,10 @@ bool TestLaraHangOnClimbableWall(ItemInfo* item, CollisionInfo* coll)
return false;
}
if (LaraTestClimbPos(item, LARA_RADIUS, LARA_RADIUS, bounds->Y1, bounds->Y2 - bounds->Y1, &shift) &&
LaraTestClimbPos(item, LARA_RADIUS, -LARA_RADIUS, bounds->Y1, bounds->Y2 - bounds->Y1, &shift))
if (LaraTestClimbPos(item, LARA_RADIUS, LARA_RADIUS, bounds->Y1, bounds->Height(), &shift) &&
LaraTestClimbPos(item, LARA_RADIUS, -LARA_RADIUS, bounds->Y1, bounds->Height(), &shift))
{
result = LaraTestClimbPos(item, LARA_RADIUS, 0, bounds->Y1, bounds->Y2 - bounds->Y1, &shift);
result = LaraTestClimbPos(item, LARA_RADIUS, 0, bounds->Y1, bounds->Height(), &shift);
if (result)
{
if (result != 1)
@ -1004,9 +1004,7 @@ bool TestLaraLadderClimbOut(ItemInfo* item, CollisionInfo* coll) // NEW function
{
auto* lara = GetLaraInfo(item);
if (!(TrInput & IN_ACTION) ||
!lara->Control.CanClimbLadder ||
coll->CollisionType != CT_FRONT)
if (!(TrInput & IN_ACTION) || !lara->Control.CanClimbLadder || coll->CollisionType != CT_FRONT)
{
return false;
}
@ -1017,6 +1015,9 @@ bool TestLaraLadderClimbOut(ItemInfo* item, CollisionInfo* coll) // NEW function
return false;
}
// HACK: Reduce probe radius, because free forward probe mode makes ladder tests to fail in some cases.
coll->Setup.Radius *= 0.8f;
if (!TestLaraClimbIdle(item, coll))
return false;
@ -1112,22 +1113,22 @@ void GetTightropeFallOff(ItemInfo* item, int regularity)
#endif
// TODO: Organise all of this properly. -- Sezz 2022.07.28
bool CheckLaraState(LaraState state, std::vector<LaraState> stateList)
bool CheckLaraState(LaraState referenceState, const std::vector<LaraState>& stateList)
{
for (auto listedState : stateList)
for (auto& state : stateList)
{
if (state == listedState)
if (state == referenceState)
return true;
}
return false;
}
bool CheckLaraWeaponType(LaraWeaponType weaponType, std::vector<LaraWeaponType> weaponTypeList)
bool CheckLaraWeaponType(LaraWeaponType referenceWeaponType, const std::vector<LaraWeaponType>& weaponTypeList)
{
for (auto listedWeaponType : weaponTypeList)
for (auto& weaponType : weaponTypeList)
{
if (weaponType == listedWeaponType)
if (weaponType == referenceWeaponType)
return true;
}
@ -1295,8 +1296,7 @@ bool TestLaraLand(ItemInfo* item, CollisionInfo* coll)
{
int heightFromFloor = GetCollision(item).Position.Floor - item->Pose.Position.y;
if (item->Animation.IsAirborne &&
item->Animation.Velocity.y >= 0 &&
if (item->Animation.IsAirborne && item->Animation.Velocity.y >= 0 &&
(heightFromFloor <= item->Animation.Velocity.y ||
TestEnvironment(ENV_FLAG_SWAMP, item)))
{
@ -2484,7 +2484,7 @@ bool TestLaraSlideJump(ItemInfo* item, CollisionInfo* coll)
auto probe = GetCollision(item);
short direction = GetLaraSlideDirection(item, coll);
short steepness = GetSurfaceSteepnessAngle(probe.FloorTilt.x, probe.FloorTilt.y);
short steepness = GetSurfaceSteepnessAngle(probe.FloorTilt);
return (abs((short)(coll->Setup.ForwardAngle - direction)) <= abs(steepness));
}
@ -2519,25 +2519,33 @@ bool TestLaraTightropeDismount(ItemInfo* item, CollisionInfo* coll)
return false;
}
bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool up, float offset)
bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool goingUp, float offset)
{
static constexpr auto poleProbeCollRadius = 16.0f;
bool atLeastOnePoleCollided = false;
if (GetCollidedObjects(item, SECTOR(1), true, CollidedItems, nullptr, 0) && CollidedItems[0])
if (GetCollidedObjects(item, SECTOR(1), true, CollidedItems, nullptr, false) &&
CollidedItems[0] != nullptr)
{
auto laraBox = TO_DX_BBOX(item->Pose, GetBoundsAccurate(item));
// HACK: because Core implemented upward pole movement as SetPosition command, we can't precisely
// HACK: Because Core implemented upward pole movement as a 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);
// Offset a sphere when jumping toward pole.
auto sphereOffset2D = Vector3::Zero;
sphereOffset2D = TranslateVector(sphereOffset2D, item->Pose.Orientation.y, coll->Setup.Radius + item->Animation.Velocity.z);
auto spherePos = laraBox.Center + Vector3(0.0f, (laraBox.Extents.y + poleProbeCollRadius + offset) * (goingUp ? -1 : 1), 0.0f);
auto sphere = BoundingSphere(spherePos, poleProbeCollRadius);
auto offsetSphere = BoundingSphere(spherePos + sphereOffset2D, poleProbeCollRadius);
//g_Renderer.AddDebugSphere(sphere.Center, 16.0f, Vector4(1, 0, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
int i = 0;
while (CollidedItems[i] != NULL)
while (CollidedItems[i] != nullptr)
{
auto*& object = CollidedItems[i];
i++;
@ -2546,11 +2554,11 @@ bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool up, float o
continue;
auto poleBox = TO_DX_BBOX(object->Pose, GetBoundsAccurate(object));
poleBox.Extents = poleBox.Extents + Vector3(coll->Setup.Radius, 0, coll->Setup.Radius);
poleBox.Extents = poleBox.Extents + Vector3(coll->Setup.Radius, 0.0f, coll->Setup.Radius);
//g_Renderer.AddDebugBox(poleBox, Vector4(0, 0, 1, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
if (poleBox.Intersects(sphere))
if (poleBox.Intersects(sphere) || poleBox.Intersects(offsetSphere))
{
atLeastOnePoleCollided = true;
break;

View file

@ -46,7 +46,8 @@ void TestLaraWaterDepth(ItemInfo* item, CollisionInfo* coll);
void GetTightropeFallOff(ItemInfo* item, int regularity);
#endif
bool CheckLaraState(LaraState state, std::vector<LaraState> stateList);
bool CheckLaraState(LaraState referenceState, const std::vector<LaraState>& stateList);
bool CheckLaraWeaponType(LaraWeaponType referenceWeaponType, const std::vector<LaraWeaponType>& weaponTypeList);
bool IsStandingWeapon(ItemInfo* item, LaraWeaponType weaponType);
bool IsVaultState(LaraState state);
bool IsJumpState(LaraState state);
@ -123,6 +124,6 @@ bool TestLaraCrawlspaceDive(ItemInfo* item, CollisionInfo* coll);
bool TestLaraTightropeDismount(ItemInfo* item, CollisionInfo* coll);
bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool up, float offset = 0.0f);
bool TestLaraPoleCollision(ItemInfo* item, CollisionInfo* coll, bool goingUp, float offset = 0.0f);
bool TestLaraPoleUp(ItemInfo* item, CollisionInfo* coll);
bool TestLaraPoleDown(ItemInfo* item, CollisionInfo* coll);

View file

@ -412,17 +412,16 @@ void TranslateItem(ItemInfo* item, Vector3 direction, float distance)
void SetAnimation(ItemInfo* item, int animIndex, int frameToStart)
{
int index = Objects[item->ObjectNumber].animIndex + animIndex;
if (item->Animation.AnimNumber == animIndex)
return;
int index = Objects[item->ObjectNumber].animIndex + animIndex;
if (index < 0 || index >= g_Level.Anims.size())
{
TENLog(std::string("Attempted to set nonexistent animation ") + std::to_string(animIndex) + std::string(" for object ") + std::to_string(item->ObjectNumber), LogLevel::Warning);
return;
}
if (item->Animation.AnimNumber == animIndex)
return;
item->Animation.AnimNumber = index;
item->Animation.FrameNumber = g_Level.Anims[index].frameBase + frameToStart;
item->Animation.ActiveState = g_Level.Anims[index].ActiveState;

View file

@ -1461,7 +1461,7 @@ void CalculateCamera()
auto* bounds = GetBoundsAccurate(item);
int x;
int y = ((bounds->Y1 + bounds->Y2) / 2) + item->Pose.Position.y - CLICK(1);
int y = item->Pose.Position.y + bounds->Y2 + (3 * (bounds->Y1 - bounds->Y2) / 4);
int z;
if (Camera.item)
@ -1854,10 +1854,10 @@ static bool CheckStaticCollideCamera(MESH_INFO* mesh)
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
return false;
auto stat = &StaticObjects[mesh->staticNumber];
auto extents = Vector3(abs(stat->collisionBox.X1 - stat->collisionBox.X2),
abs(stat->collisionBox.Y1 - stat->collisionBox.Y2),
abs(stat->collisionBox.Z1 - stat->collisionBox.Z2));
auto bounds = GetBoundsAccurate(mesh, false);
auto extents = Vector3(abs(bounds->X1 - bounds->X2),
abs(bounds->Y1 - bounds->Y2),
abs(bounds->Z1 - bounds->Z2));
// Check extents, if any 2 bounds are smaller than threshold, discard.
if ((abs(extents.x) < COLL_DISCARD_THRESHOLD && abs(extents.y) < COLL_DISCARD_THRESHOLD) ||
@ -1929,9 +1929,7 @@ void ItemsCollideCamera()
for (int i = 0; i < staticList.size(); i++)
{
auto mesh = staticList[i];
auto stat = &StaticObjects[mesh->staticNumber];
if (!mesh || !stat)
if (!mesh)
return;
auto dx = abs(LaraItem->Pose.Position.x - mesh->pos.Position.x);
@ -1941,7 +1939,7 @@ void ItemsCollideCamera()
if (dx > COLL_CANCEL_THRESHOLD || dz > COLL_CANCEL_THRESHOLD || dy > COLL_CANCEL_THRESHOLD)
continue;
auto bounds = &stat->collisionBox;
auto bounds = GetBoundsAccurate(mesh, false);
if (TestBoundsCollideCamera(bounds, &mesh->pos, CAMERA_RADIUS))
ItemPushCamera(bounds, &mesh->pos, rad);

View file

@ -3,22 +3,23 @@
#include "Game/animation.h"
#include "Game/control/los.h"
#include "Game/collision/sphere.h"
#include "Game/collision/collide_room.h"
#include "Game/collision/sphere.h"
#include "Game/effects/debris.h"
#include "Game/effects/effects.h"
#include "Game/effects/simple_particle.h"
#include "Game/effects/tomb4fx.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/items.h"
#include "Game/effects/effects.h"
#include "Game/effects/debris.h"
#include "Game/effects/tomb4fx.h"
#include "Game/effects/simple_particle.h"
#include "Game/pickup/pickup.h"
#include "Game/room.h"
#include "Renderer/Renderer11.h"
#include "Sound/sound.h"
#include "Specific/trmath.h"
#include "Specific/prng.h"
#include "ScriptInterfaceGame.h"
#include "Sound/sound.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
#include "Specific/trmath.h"
using namespace TEN::Math::Random;
using namespace TEN::Renderer;
@ -27,6 +28,8 @@ BOUNDING_BOX GlobalCollisionBounds;
ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS];
MESH_INFO* CollidedMeshes[MAX_COLLIDED_OBJECTS];
constexpr auto ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD = 6;
void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
auto* item = &g_Level.Items[itemNumber];
@ -38,11 +41,11 @@ void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionIn
int collidedBits = TestCollision(item, laraItem);
if (collidedBits)
{
short oldYOrient = item->Pose.Orientation.y;
short prevYOrient = item->Pose.Orientation.y;
item->Pose.Orientation.y = 0;
GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity);
item->Pose.Orientation.y = oldYOrient;
item->Pose.Orientation.y = prevYOrient;
int deadlyBits = *((int*)&item->ItemFlags[0]);
auto* sphere = &CreatureSpheres[0];
@ -112,26 +115,27 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
for (int j = 0; j < room->mesh.size(); j++)
{
auto* mesh = &room->mesh[j];
auto* staticMesh = &StaticObjects[mesh->staticNumber];
auto* bbox = GetBoundsAccurate(mesh, false);
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
continue;
if (collidingItem->Pose.Position.y + radius + CLICK(0.5f) < mesh->pos.Position.y + staticMesh->collisionBox.Y1)
if ((collidingItem->Pose.Position.y + radius + CLICK(0.5f)) < (mesh->pos.Position.y + bbox->Y1))
continue;
if (collidingItem->Pose.Position.y > mesh->pos.Position.y + staticMesh->collisionBox.Y2)
if (collidingItem->Pose.Position.y > (mesh->pos.Position.y + bbox->Y2))
continue;
float s = phd_sin(mesh->pos.Orientation.y);
float c = phd_cos(mesh->pos.Orientation.y);
float rx = (collidingItem->Pose.Position.x - mesh->pos.Position.x) * c - s * (collidingItem->Pose.Position.z - mesh->pos.Position.z);
float rz = (collidingItem->Pose.Position.z - mesh->pos.Position.z) * c + s * (collidingItem->Pose.Position.x - mesh->pos.Position.x);
float sinY = phd_sin(mesh->pos.Orientation.y);
float cosY = phd_cos(mesh->pos.Orientation.y);
if (radius + rx + CLICK(0.5f) < staticMesh->collisionBox.X1 || rx - radius - CLICK(0.5f) > staticMesh->collisionBox.X2)
float rx = ((collidingItem->Pose.Position.x - mesh->pos.Position.x) * cosY) - ((collidingItem->Pose.Position.z - mesh->pos.Position.z) * sinY);
float rz = ((collidingItem->Pose.Position.z - mesh->pos.Position.z) * cosY) + ((collidingItem->Pose.Position.x - mesh->pos.Position.x) * sinY);
if ((radius + rx + CLICK(0.5f) < bbox->X1) || (rx - radius - CLICK(0.5f) > bbox->X2))
continue;
if (radius + rz + CLICK(0.5f) < staticMesh->collisionBox.Z1 || rz - radius - CLICK(0.5f) > staticMesh->collisionBox.Z2)
if ((radius + rz + CLICK(0.5f) < bbox->Z1) || (rz - radius - CLICK(0.5f) > bbox->Z2))
continue;
collidedMeshes[numMeshes++] = mesh;
@ -159,8 +163,8 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
item->ObjectNumber == ID_LARA && ignoreLara ||
item->Flags & 0x8000 ||
item->MeshBits == NO_JOINT_BITS ||
(Objects[item->ObjectNumber].drawRoutine == NULL && item->ObjectNumber != ID_LARA) ||
(Objects[item->ObjectNumber].collision == NULL && item->ObjectNumber != ID_LARA) ||
(Objects[item->ObjectNumber].drawRoutine == nullptr && item->ObjectNumber != ID_LARA) ||
(Objects[item->ObjectNumber].collision == nullptr && item->ObjectNumber != ID_LARA) ||
onlyVisible && item->Status == ITEM_INVISIBLE ||
item->ObjectNumber == ID_BURNING_FLOOR)
{
@ -190,14 +194,14 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
if (dx >= -SECTOR(2) && dx <= SECTOR(2) &&
dy >= -SECTOR(2) && dy <= SECTOR(2) &&
dz >= -SECTOR(2) && dz <= SECTOR(2) &&
collidingItem->Pose.Position.y + radius + CLICK(0.5f) >= item->Pose.Position.y + framePtr->boundingBox.Y1 &&
collidingItem->Pose.Position.y - radius - CLICK(0.5f) <= item->Pose.Position.y + framePtr->boundingBox.Y2)
(collidingItem->Pose.Position.y + radius + CLICK(0.5f)) >= (item->Pose.Position.y + framePtr->boundingBox.Y1) &&
(collidingItem->Pose.Position.y - radius - CLICK(0.5f)) <= (item->Pose.Position.y + framePtr->boundingBox.Y2))
{
float s = phd_sin(item->Pose.Orientation.y);
float c = phd_cos(item->Pose.Orientation.y);
float sinY = phd_sin(item->Pose.Orientation.y);
float cosY = phd_cos(item->Pose.Orientation.y);
int rx = dx * c - s * dz;
int rz = dz * c + s * dx;
int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY);
if (item->ObjectNumber == ID_TURN_SWITCH)
{
@ -207,36 +211,39 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
framePtr->boundingBox.Z1 = CLICK(1);
}
if (radius + rx + CLICK(0.5f) >= framePtr->boundingBox.X1 && rx - radius - CLICK(0.5f) <= framePtr->boundingBox.X2)
if ((radius + rx + CLICK(0.5f)) >= framePtr->boundingBox.X1 &&
(rx - radius - CLICK(0.5f)) <= framePtr->boundingBox.X2)
{
if (radius + rz + CLICK(0.5f) >= framePtr->boundingBox.Z1 && rz - radius - CLICK(0.5f) <= framePtr->boundingBox.Z2)
if ((radius + rz + CLICK(0.5f)) >= framePtr->boundingBox.Z1 &&
(rz - radius - CLICK(0.5f)) <= framePtr->boundingBox.Z2)
{
collidedItems[numItems++] = item;
}
}
else
{
if (collidingItem->Pose.Position.y + radius + 128 >= item->Pose.Position.y + framePtr->boundingBox.Y1 &&
collidingItem->Pose.Position.y - radius - 128 <= item->Pose.Position.y + framePtr->boundingBox.Y2)
if ((collidingItem->Pose.Position.y + radius + CLICK(0.5f)) >= (item->Pose.Position.y + framePtr->boundingBox.Y1) &&
(collidingItem->Pose.Position.y - radius - CLICK(0.5f)) <= (item->Pose.Position.y + framePtr->boundingBox.Y2))
{
float s = phd_sin(item->Pose.Orientation.y);
float c = phd_cos(item->Pose.Orientation.y);
float sinY = phd_sin(item->Pose.Orientation.y);
float cosY = phd_cos(item->Pose.Orientation.y);
int rx = dx * c - s * dz;
int rz = dz * c + s * dx;
int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY);
if (item->ObjectNumber == ID_TURN_SWITCH)
{
framePtr->boundingBox.X1 = -256;
framePtr->boundingBox.X2 = 256;
framePtr->boundingBox.Z1 = -256;
framePtr->boundingBox.Z1 = 256;
framePtr->boundingBox.X1 = -CLICK(1);
framePtr->boundingBox.X2 = CLICK(1);
framePtr->boundingBox.Z1 = -CLICK(1);
framePtr->boundingBox.Z1 = CLICK(1);
}
if (radius + rx + 128 >= framePtr->boundingBox.X1 && rx - radius - 128 <= framePtr->boundingBox.X2)
if ((radius + rx + CLICK(0.5f)) >= framePtr->boundingBox.X1 &&
(rx - radius - CLICK(0.5f)) <= framePtr->boundingBox.X2)
{
if (radius + rz + 128 >= framePtr->boundingBox.Z1 && rz - radius - 128 <= framePtr->boundingBox.Z2)
if ((radius + rz + CLICK(0.5f)) >= framePtr->boundingBox.Z1 &&
(rz - radius - CLICK(0.5f)) <= framePtr->boundingBox.Z2)
{
collidedItems[numItems++] = item;
@ -249,10 +256,11 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
}
itemNumber = item->NextItem;
} while (itemNumber != NO_ITEM);
}
while (itemNumber != NO_ITEM);
}
collidedItems[numItems] = NULL;
collidedItems[numItems] = nullptr;
}
}
@ -269,20 +277,22 @@ bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, Collision
if (item->Pose.Position.y + GlobalCollisionBounds.Y1 >= framePtr->boundingBox.Y2)
return false;
float s = phd_sin(item->Pose.Orientation.y);
float c = phd_cos(item->Pose.Orientation.y);
float sinY = phd_sin(item->Pose.Orientation.y);
float cosY = phd_cos(item->Pose.Orientation.y);
int dx = laraItem->Pose.Position.x - item->Pose.Position.x;
int dz = laraItem->Pose.Position.z - item->Pose.Position.z;
int x = c * dx - s * dz;
int z = c * dz + s * dx;
int x = (dx * cosY) - (dz * sinY);
int z = (dz * cosY) + (dx * sinY);
if (x < GlobalCollisionBounds.X1 - coll->Setup.Radius ||
x > GlobalCollisionBounds.X2 + coll->Setup.Radius ||
z < GlobalCollisionBounds.Z1 - coll->Setup.Radius ||
z > GlobalCollisionBounds.Z2 + coll->Setup.Radius)
{
return false;
}
return true;
}
@ -294,15 +304,15 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
for (int i = 0; i < 3; i++)
{
auto s = (i != 1) ? phd_sin(coll->Setup.ForwardAngle + ANGLE((i * 90) - 90)) : 0;
auto c = (i != 1) ? phd_cos(coll->Setup.ForwardAngle + ANGLE((i * 90) - 90)) : 0;
auto sinForwardAngle = (i != 1) ? (phd_sin(coll->Setup.ForwardAngle + ANGLE((i * 90.0f) - 90.0f))) : 0;
auto cosForwardAngle = (i != 1) ? (phd_cos(coll->Setup.ForwardAngle + ANGLE((i * 90.0f) - 90.0f))) : 0;
auto x = item->Pose.Position.x + (s * (coll->Setup.Radius));
auto y = item->Pose.Position.y - height - STEP_SIZE;
auto z = item->Pose.Position.z + (c * (coll->Setup.Radius));
auto x = item->Pose.Position.x + (sinForwardAngle * (coll->Setup.Radius));
auto y = item->Pose.Position.y - height - CLICK(1);
auto z = item->Pose.Position.z + (cosForwardAngle * (coll->Setup.Radius));
auto origin = Vector3(x, y, z);
auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(coll->Setup.ForwardAngle), 0, 0);
auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(coll->Setup.ForwardAngle), 0.0f, 0.0f);
auto direction = (Matrix::CreateTranslation(Vector3::UnitZ) * mxR).Translation();
// g_Renderer.AddDebugSphere(origin, 16, Vector4::One, RENDERER_DEBUG_PAGE::DIMENSION_STATS);
@ -312,10 +322,10 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
short itemNumber = g_Level.Rooms[i].itemNumber;
while (itemNumber != NO_ITEM)
{
auto item2 = &g_Level.Items[itemNumber];
auto obj = &Objects[item2->ObjectNumber];
auto* item2 = &g_Level.Items[itemNumber];
auto* object = &Objects[item2->ObjectNumber];
if (obj->isPickup || obj->collision == nullptr || !item2->Collidable || item2->Status == ITEM_INVISIBLE)
if (object->isPickup || object->collision == nullptr || !item2->Collidable || item2->Status == ITEM_INVISIBLE)
{
itemNumber = item2->NextItem;
continue;
@ -338,17 +348,17 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
for (int j = 0; j < g_Level.Rooms[i].mesh.size(); j++)
{
auto mesh = &g_Level.Rooms[i].mesh[j];
auto* mesh = &g_Level.Rooms[i].mesh[j];
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
continue;
if (phd_Distance(&item->Pose, &mesh->pos) < COLLISION_CHECK_DISTANCE)
{
auto box = TO_DX_BBOX(mesh->pos, &StaticObjects[mesh->staticNumber].collisionBox);
float dist;
auto box = TO_DX_BBOX(mesh->pos, GetBoundsAccurate(mesh, false));
float distance;
if (box.Intersects(origin, direction, dist) && dist < coll->Setup.Radius * 2)
if (box.Intersects(origin, direction, distance) && distance < coll->Setup.Radius * 2)
{
coll->HitStatic = true;
return;
@ -361,29 +371,29 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
bool TestLaraPosition(OBJECT_COLLISION_BOUNDS* bounds, ItemInfo* item, ItemInfo* laraItem)
{
auto rotRel = laraItem->Pose.Orientation - item->Pose.Orientation;
auto deltaOrient = laraItem->Pose.Orientation - item->Pose.Orientation;
if (rotRel.x < bounds->rotX1)
if (deltaOrient.x < bounds->rotX1)
return false;
if (rotRel.x > bounds->rotX2)
if (deltaOrient.x > bounds->rotX2)
return false;
if (rotRel.y < bounds->rotY1)
if (deltaOrient.y < bounds->rotY1)
return false;
if (rotRel.y > bounds->rotY2)
if (deltaOrient.y > bounds->rotY2)
return false;
if (rotRel.z < bounds->rotZ1)
if (deltaOrient.z < bounds->rotZ1)
return false;
if (rotRel.z > bounds->rotZ2)
if (deltaOrient.z > bounds->rotZ2)
return false;
auto pos = (laraItem->Pose.Position - item->Pose.Position).ToVector3();
Matrix matrix = Matrix::CreateFromYawPitchRoll(
auto matrix = Matrix::CreateFromYawPitchRoll(
TO_RAD(item->Pose.Orientation.y),
TO_RAD(item->Pose.Orientation.x),
TO_RAD(item->Pose.Orientation.z)
@ -401,34 +411,35 @@ bool TestLaraPosition(OBJECT_COLLISION_BOUNDS* bounds, ItemInfo* item, ItemInfo*
if (pos.x < bounds->boundingBox.X1 || pos.x > bounds->boundingBox.X2 ||
pos.y < bounds->boundingBox.Y1 || pos.y > bounds->boundingBox.Y2 ||
pos.z < bounds->boundingBox.Z1 || pos.z > bounds->boundingBox.Z2)
{
return false;
}
return true;
}
bool AlignLaraPosition(Vector3Int* vec, ItemInfo* item, ItemInfo* laraItem)
{
auto* lara = GetLaraInfo(laraItem);
laraItem->Pose.Orientation = item->Pose.Orientation;
Matrix matrix = Matrix::CreateFromYawPitchRoll(
auto matrix = Matrix::CreateFromYawPitchRoll(
TO_RAD(item->Pose.Orientation.y),
TO_RAD(item->Pose.Orientation.x),
TO_RAD(item->Pose.Orientation.z)
);
Vector3 pos = Vector3::Transform(Vector3(vec->x, vec->y, vec->z), matrix);
Vector3 newPos = item->Pose.Position.ToVector3() + pos;
auto pos = Vector3::Transform(vec->ToVector3(), matrix);
auto newPos = item->Pose.Position.ToVector3() + pos;
int height = GetCollision(newPos.x, newPos.y, newPos.z, laraItem->RoomNumber).Position.Floor;
if ((laraItem->Pose.Position.y - height) <= CLICK(2))
{
laraItem->Pose.Position.x = newPos.x;
laraItem->Pose.Position.y = newPos.y;
laraItem->Pose.Position.z = newPos.z;
laraItem->Pose.Position = Vector3Int(newPos);
return true;
}
auto* lara = GetLaraInfo(laraItem);
if (lara->Control.IsMoving)
{
lara->Control.IsMoving = false;
@ -442,9 +453,8 @@ bool MoveLaraPosition(Vector3Int* vec, ItemInfo* item, ItemInfo* laraItem)
{
auto* lara = GetLaraInfo(laraItem);
auto dest = PHD_3DPOS(item->Pose.Orientation);
Vector3 pos = Vector3(vec->x, vec->y, vec->z);
auto target = PHD_3DPOS(item->Pose.Orientation);
auto pos = vec->ToVector3();
Matrix matrix = Matrix::CreateFromYawPitchRoll(
TO_RAD(item->Pose.Orientation.y),
@ -453,24 +463,23 @@ bool MoveLaraPosition(Vector3Int* vec, ItemInfo* item, ItemInfo* laraItem)
);
pos = Vector3::Transform(pos, matrix);
target.Position = item->Pose.Position + Vector3Int(pos);
dest.Position.x = item->Pose.Position.x + pos.x;
dest.Position.y = item->Pose.Position.y + pos.y;
dest.Position.z = item->Pose.Position.z + pos.z;
if (!Objects[item->ObjectNumber].isPickup)
return Move3DPosTo3DPos(&laraItem->Pose, &target, LARA_ALIGN_VELOCITY, ANGLE(2.0f));
if (item->ObjectNumber != ID_FLARE_ITEM && item->ObjectNumber != ID_BURNING_TORCH_ITEM)
return Move3DPosTo3DPos(&laraItem->Pose, &dest, LARA_VELOCITY, ANGLE(2.0f));
// Prevent picking up items which can result in so called "flare pickup bug"
int height = GetCollision(dest.Position.x, dest.Position.y, dest.Position.z, laraItem->RoomNumber).Position.Floor;
int height = GetCollision(target.Position.x, target.Position.y, target.Position.z, laraItem->RoomNumber).Position.Floor;
if (abs(height - laraItem->Pose.Position.y) <= CLICK(2))
{
auto direction = dest.Position - laraItem->Pose.Position;
auto direction = target.Position - laraItem->Pose.Position;
float distance = sqrt(pow(direction.x, 2) + pow(direction.y, 2) + pow(direction.z, 2));
float distance = sqrt(SQUARE(direction.x) + SQUARE(direction.y) + SQUARE(direction.z));
if (distance < CLICK(0.5f))
return true;
return Move3DPosTo3DPos(&laraItem->Pose, &dest, LARA_VELOCITY, ANGLE(2.0f));
return Move3DPosTo3DPos(&laraItem->Pose, &target, LARA_ALIGN_VELOCITY, ANGLE(2.0f));
}
if (lara->Control.IsMoving)
@ -484,20 +493,21 @@ bool MoveLaraPosition(Vector3Int* vec, ItemInfo* item, ItemInfo* laraItem)
static bool ItemCollide(int value, int radius)
{
return value >= -radius && value <= radius;
return (value >= -radius && value <= radius);
}
static bool ItemInRange(int x, int z, int radius)
{
return (pow(x, 2) + pow(z, 2)) <= pow(radius, 2);
return ((SQUARE(x) + SQUARE(z)) <= SQUARE(radius));
}
bool ItemNearLara(PHD_3DPOS* pos, int radius)
{
GameVector target;
target.x = pos->Position.x - LaraItem->Pose.Position.x;
target.y = pos->Position.y - LaraItem->Pose.Position.y;
target.z = pos->Position.z - LaraItem->Pose.Position.z;
auto target = GameVector(
pos->Position.x - LaraItem->Pose.Position.x,
pos->Position.y - LaraItem->Pose.Position.y,
pos->Position.z - LaraItem->Pose.Position.z
);
if (!ItemCollide(target.y, ITEM_RADIUS_YMAX))
return false;
@ -535,22 +545,24 @@ bool ItemNearTarget(PHD_3DPOS* src, ItemInfo* target, int radius)
return false;
}
bool Move3DPosTo3DPos(PHD_3DPOS* src, PHD_3DPOS* dest, int velocity, short angleAdd)
bool Move3DPosTo3DPos(PHD_3DPOS* origin, PHD_3DPOS* target, int velocity, short angleAdd)
{
auto direction = dest->Position - src->Position;
int distance = sqrt(pow(direction.x, 2) + pow(direction.y, 2) + pow(direction.z, 2));
auto direction = target->Position - origin->Position;
int distance = sqrt(SQUARE(direction.x) + SQUARE(direction.y) + SQUARE(direction.z));
if (velocity < distance)
src->Position += direction * velocity / distance;
origin->Position += direction * velocity / distance;
else
src->Position = dest->Position;
origin->Position = target->Position;
if (!Lara.Control.IsMoving)
{
if (Lara.Control.WaterStatus != WaterStatus::Underwater)
bool shouldAnimate = (distance - velocity) > (velocity * ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD);
if (shouldAnimate && Lara.Control.WaterStatus != WaterStatus::Underwater)
{
int angle = mGetAngle(dest->Position.x, dest->Position.z, src->Position.x, src->Position.z);
int direction = (GetQuadrant(angle) - GetQuadrant(dest->Orientation.y)) & 3;
int angle = mGetAngle(target->Position.x, target->Position.z, origin->Position.x, origin->Position.z);
int direction = (GetQuadrant(angle) - GetQuadrant(target->Orientation.y)) & 3;
switch (direction)
{
@ -581,42 +593,42 @@ bool Move3DPosTo3DPos(PHD_3DPOS* src, PHD_3DPOS* dest, int velocity, short angle
Lara.Control.Count.PositionAdjust = 0;
}
short deltaAngle = dest->Orientation.x - src->Orientation.x;
short deltaAngle = target->Orientation.x - origin->Orientation.x;
if (deltaAngle > angleAdd)
src->Orientation.x += angleAdd;
origin->Orientation.x += angleAdd;
else if (deltaAngle < -angleAdd)
src->Orientation.x -= angleAdd;
origin->Orientation.x -= angleAdd;
else
src->Orientation.x = dest->Orientation.x;
origin->Orientation.x = target->Orientation.x;
deltaAngle = dest->Orientation.y - src->Orientation.y;
deltaAngle = target->Orientation.y - origin->Orientation.y;
if (deltaAngle > angleAdd)
src->Orientation.y += angleAdd;
origin->Orientation.y += angleAdd;
else if (deltaAngle < -angleAdd)
src->Orientation.y -= angleAdd;
origin->Orientation.y -= angleAdd;
else
src->Orientation.y = dest->Orientation.y;
origin->Orientation.y = target->Orientation.y;
deltaAngle = dest->Orientation.z - src->Orientation.z;
deltaAngle = target->Orientation.z - origin->Orientation.z;
if (deltaAngle > angleAdd)
src->Orientation.z += angleAdd;
origin->Orientation.z += angleAdd;
else if (deltaAngle < -angleAdd)
src->Orientation.z -= angleAdd;
origin->Orientation.z -= angleAdd;
else
src->Orientation.z = dest->Orientation.z;
origin->Orientation.z = target->Orientation.z;
return (src->Position == dest->Position && src->Orientation == dest->Orientation);
return (origin->Position == target->Position && origin->Orientation == target->Orientation);
}
bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius)
{
auto bounds = (BOUNDING_BOX*)GetBestFrame(item);
auto laraBounds = (BOUNDING_BOX*)GetBestFrame(laraItem);
auto* bounds = (BOUNDING_BOX*)GetBestFrame(item);
auto* laraBounds = (BOUNDING_BOX*)GetBestFrame(laraItem);
if (item->Pose.Position.y + bounds->Y2 <= laraItem->Pose.Position.y + laraBounds->Y1)
if ((item->Pose.Position.y + bounds->Y2) <= (laraItem->Pose.Position.y + laraBounds->Y1))
return false;
if (item->Pose.Position.y + bounds->Y1 >= laraItem->Pose.Position.y + laraBounds->Y2)
if ((item->Pose.Position.y + bounds->Y1) >= (laraItem->Pose.Position.y + laraBounds->Y2))
return false;
float sinY = phd_sin(item->Pose.Orientation.y);
@ -640,16 +652,16 @@ bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius)
bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius)
{
auto bounds = StaticObjects[mesh->staticNumber].collisionBox;
auto bounds = GetBoundsAccurate(mesh, false);
if (!(bounds.Z2 != 0 || bounds.Z1 != 0 || bounds.X1 != 0 || bounds.X2 != 0 || bounds.Y1 != 0 || bounds.Y2 != 0))
if (!(bounds->Z2 != 0 || bounds->Z1 != 0 || bounds->X1 != 0 || bounds->X2 != 0 || bounds->Y1 != 0 || bounds->Y2 != 0))
return false;
auto* frame = GetBestFrame(item);
if (mesh->pos.Position.y + bounds.Y2 <= item->Pose.Position.y + frame->boundingBox.Y1)
if (mesh->pos.Position.y + bounds->Y2 <= item->Pose.Position.y + frame->boundingBox.Y1)
return false;
if (mesh->pos.Position.y + bounds.Y1 >= item->Pose.Position.y + frame->boundingBox.Y2)
if (mesh->pos.Position.y + bounds->Y1 >= item->Pose.Position.y + frame->boundingBox.Y2)
return false;
float sinY = phd_sin(mesh->pos.Orientation.y);
@ -660,10 +672,10 @@ bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius)
int dx = cosY * x - sinY * z;
int dz = cosY * z + sinY * x;
if (dx <= radius + bounds.X2 &&
dx >= bounds.X1 - radius &&
dz <= radius + bounds.Z2 &&
dz >= bounds.Z1 - radius)
if (dx <= radius + bounds->X2 &&
dx >= bounds->X1 - radius &&
dz <= radius + bounds->Z2 &&
dz >= bounds->Z1 - radius)
{
return true;
}
@ -671,19 +683,20 @@ bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius)
return false;
}
bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spasmEnabled, char bigPush) // previously ItemPushLara
// NOTE: Previously ItemPushLara().
bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spasmEnabled, char bigPush)
{
// Get item's rotation
// Get item's rotation.
float sinY = phd_sin(item->Pose.Orientation.y);
float cosY = phd_cos(item->Pose.Orientation.y);
// Get vector from item to Lara
// Get vector from item to Lara.
int dx = item2->Pose.Position.x - item->Pose.Position.x;
int dz = item2->Pose.Position.z - item->Pose.Position.z;
// Rotate Lara vector into item frame
int rx = cosY * dx - sinY * dz;
int rz = cosY * dz + sinY * dx;
// Rotate Lara vector into item frame.
int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY);
BOUNDING_BOX* bounds;
if (bigPush & 2)
@ -731,13 +744,13 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
auto* lara = item2->IsLara() ? GetLaraInfo(item2) : nullptr;
if (lara != nullptr && spasmEnabled && bounds->Y2 - bounds->Y1 > CLICK(1))
if (lara != nullptr && spasmEnabled && (bounds->Y2 - bounds->Y1) > CLICK(1))
{
rx = (bounds->X1 + bounds->X2) / 2;
rz = (bounds->Z1 + bounds->Z2) / 2;
dx -= cosY * rx + sinY * rz;
dz -= cosY * rz - sinY * rx;
dx -= (rx * cosY) + (rz * sinY);
dz -= (rz * cosY) - (rx * sinY);
lara->HitDirection = (item2->Pose.Orientation.y - phd_atan(dz, dz) - ANGLE(135.0f)) / ANGLE(90.0f);
DoDamage(item2, 0); // Dummy hurt call. Only for ooh sound!
@ -784,7 +797,7 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
bool ItemPushStatic(ItemInfo* item, MESH_INFO* mesh, CollisionInfo* coll) // previously ItemPushLaraStatic
{
auto bounds = StaticObjects[mesh->staticNumber].collisionBox;
auto bounds = GetBoundsAccurate(mesh, false);
float sinY = phd_sin(mesh->pos.Orientation.y);
float cosY = phd_cos(mesh->pos.Orientation.y);
@ -793,10 +806,10 @@ bool ItemPushStatic(ItemInfo* item, MESH_INFO* mesh, CollisionInfo* coll) // pre
auto dz = item->Pose.Position.z - mesh->pos.Position.z;
auto rx = cosY * dx - sinY * dz;
auto rz = cosY * dz + sinY * dx;
auto minX = bounds.X1 - coll->Setup.Radius;
auto maxX = bounds.X2 + coll->Setup.Radius;
auto minZ = bounds.Z1 - coll->Setup.Radius;
auto maxZ = bounds.Z2 + coll->Setup.Radius;
auto minX = bounds->X1 - coll->Setup.Radius;
auto maxX = bounds->X2 + coll->Setup.Radius;
auto minZ = bounds->Z1 - coll->Setup.Radius;
auto maxZ = bounds->Z2 + coll->Setup.Radius;
if (abs(dx) > SECTOR(4.5f) || abs(dz) > SECTOR(4.5f) ||
rx <= minX || rx >= maxX ||
@ -862,7 +875,7 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
{
for (int j = 0; j < g_Level.Rooms[i].mesh.size(); j++)
{
auto mesh = &g_Level.Rooms[i].mesh[j];
auto* mesh = &g_Level.Rooms[i].mesh[j];
// Only process meshes which are visible and solid
if ((mesh->flags & StaticMeshFlags::SM_VISIBLE) && (mesh->flags & StaticMeshFlags::SM_SOLID))
@ -870,7 +883,7 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
if (phd_Distance(&item->Pose, &mesh->pos) < COLLISION_CHECK_DISTANCE)
{
auto staticInfo = &StaticObjects[mesh->staticNumber];
if (CollideSolidBounds(item, staticInfo->collisionBox, mesh->pos, coll))
if (CollideSolidBounds(item, GetBoundsAccurate(mesh, false), mesh->pos, coll))
coll->HitStatic = true;
}
}
@ -878,12 +891,12 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
}
}
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, CollisionInfo* coll)
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, CollisionInfo* coll)
{
bool result = false;
// Get DX static bounds in global coords
auto staticBounds = TO_DX_BBOX(pos, &box);
auto staticBounds = TO_DX_BBOX(pos, box);
// Get local TR bounds and DX item bounds in global coords
auto itemBBox = GetBoundsAccurate(item);
@ -911,7 +924,7 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, Collisi
// Calculate vertical item coll bounds according to either height (land mode) or precise bounds (water mode).
// Water mode needs special processing because height calc in original engines is inconsistent in such cases.
if (g_Level.Rooms[item->RoomNumber].flags & ENV_FLAG_WATER)
if (TestEnvironment(ENV_FLAG_WATER, item))
{
collBox.Y1 = itemBBox->Y1;
collBox.Y2 = itemBBox->Y2;
@ -945,7 +958,7 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, Collisi
};
// Determine collision box vertical dimensions
auto height = collBox.Y2 - collBox.Y1;
auto height = collBox.Height();
auto center = item->Pose.Position.y - height / 2;
// Do a series of angular tests with 90 degree steps to determine top/bottom collision.
@ -1022,21 +1035,21 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, Collisi
// Determine identity rotation/distance
auto distance = Vector3(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z);
auto c = phd_cos(pos.Orientation.y);
auto s = phd_sin(pos.Orientation.y);
auto sinY = phd_sin(pos.Orientation.y);
auto cosY = phd_cos(pos.Orientation.y);
// Rotate item to collision bounds identity
auto x = round(distance.x * c - distance.z * s) + pos.Position.x;
auto x = round(distance.x * cosY - distance.z * sinY) + pos.Position.x;
auto y = item->Pose.Position.y;
auto z = round(distance.x * s + distance.z * c) + pos.Position.z;
auto z = round(distance.x * sinY + distance.z * cosY) + pos.Position.z;
// Determine identity static collision bounds
auto XMin = pos.Position.x + box.X1;
auto XMax = pos.Position.x + box.X2;
auto YMin = pos.Position.y + box.Y1;
auto YMax = pos.Position.y + box.Y2;
auto ZMin = pos.Position.z + box.Z1;
auto ZMax = pos.Position.z + box.Z2;
auto XMin = pos.Position.x + box->X1;
auto XMax = pos.Position.x + box->X2;
auto YMin = pos.Position.y + box->Y1;
auto YMax = pos.Position.y + box->Y2;
auto ZMin = pos.Position.z + box->Z1;
auto ZMax = pos.Position.z + box->Z2;
// Determine item collision bounds
auto inXMin = x + collBox.X1;
@ -1074,8 +1087,8 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, Collisi
// Rotate previous collision position to identity
distance = Vector3(coll->Setup.OldPosition.x, coll->Setup.OldPosition.y, coll->Setup.OldPosition.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z);
auto ox = round(distance.x * c - distance.z * s) + pos.Position.x;
auto oz = round(distance.x * s + distance.z * c) + pos.Position.z;
auto ox = round(distance.x * cosY - distance.z * sinY) + pos.Position.x;
auto oz = round(distance.x * sinY + distance.z * cosY) + pos.Position.z;
// Calculate collisison type based on identity rotation
switch (GetQuadrant(coll->Setup.ForwardAngle - pos.Orientation.y))
@ -1171,12 +1184,12 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, Collisi
// Determine final shifts rotation/distance
distance = Vector3(x + coll->Shift.x, y, z + coll->Shift.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z);
c = phd_cos(-pos.Orientation.y);
s = phd_sin(-pos.Orientation.y);
sinY = phd_sin(-pos.Orientation.y);
cosY = phd_cos(-pos.Orientation.y);
// Calculate final shifts rotation/distance
coll->Shift.x = (round(distance.x * c - distance.z * s) + pos.Position.x) - item->Pose.Position.x;
coll->Shift.z = (round(distance.x * s + distance.z * c) + pos.Position.z) - item->Pose.Position.z;
coll->Shift.x = (round(distance.x * cosY - distance.z * sinY) + pos.Position.x) - item->Pose.Position.x;
coll->Shift.z = (round(distance.x * sinY + distance.z * cosY) + pos.Position.z) - item->Pose.Position.z;
if (coll->Shift.x == 0 && coll->Shift.z == 0)
coll->CollisionType = CT_NONE; // Paranoid
@ -1188,27 +1201,27 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, Collisi
return true;
}
void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, int zv) // previously DoProperDetection
// NOTE: Previously DoProperDetection().
void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv, int zv)
{
int bs, yAngle;
auto* item = &g_Level.Items[itemNumber];
auto oldCollResult = GetCollision(x, y, z, item->RoomNumber);
auto prevCollResult = GetCollision(x, y, z, item->RoomNumber);
auto collResult = GetCollision(item);
auto* bounds = GetBoundsAccurate(item);
int radius = abs(bounds->Y2 - bounds->Y1);
int radius = bounds->Height();
item->Pose.Position.y += radius;
if (item->Pose.Position.y >= collResult.Position.Floor)
{
bs = 0;
int bs = 0;
if (collResult.Position.FloorSlope && oldCollResult.Position.Floor < collResult.Position.Floor)
if (collResult.Position.FloorSlope && prevCollResult.Position.Floor < collResult.Position.Floor)
{
yAngle = (long)((unsigned short)item->Pose.Orientation.y);
int yAngle = (long)((unsigned short)item->Pose.Orientation.y);
if (collResult.FloorTilt.x < 0)
{
if (yAngle >= ANGLE(180.0f))
@ -1601,7 +1614,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{
if (yv >= 0)
{
oldCollResult = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber);
prevCollResult = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber);
collResult = GetCollision(item);
// Bounce off floor.
@ -1610,7 +1623,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
// was always set to 0 by GetHeight() function which was called before the check.
// Possibly a mistake or unfinished feature by Core? -- Lwmte, 27.08.21
if (item->Pose.Position.y >= oldCollResult.Position.Floor)
if (item->Pose.Position.y >= prevCollResult.Position.Floor)
{
// Hit the floor; bounce and slow down.
if (item->Animation.Velocity.y > 0)
@ -1644,7 +1657,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
}
}
item->Pose.Position.y = oldCollResult.Position.Floor;
item->Pose.Position.y = prevCollResult.Position.Floor;
}
}
// else
@ -1709,10 +1722,10 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
laraItem->HitStatus = false;
coll->HitStatic = false;
bool playerCollision = laraItem->IsLara();
bool harmless = !playerCollision && (laraItem->Data.is<KayakInfo>() || laraItem->Data.is<UPVInfo>());
bool doPlayerCollision = laraItem->IsLara();
bool harmless = !doPlayerCollision && (laraItem->Data.is<KayakInfo>() || laraItem->Data.is<UPVInfo>());
if (playerCollision)
if (doPlayerCollision)
{
GetLaraInfo(laraItem)->HitDirection = -1;
@ -1751,7 +1764,7 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
if (phd_Distance(&item->Pose, &laraItem->Pose) >= COLLISION_CHECK_DISTANCE)
continue;
if (playerCollision)
if (doPlayerCollision)
{
// Objects' own collision routines were almost universally written only for
// managing collisions with Lara and nothing else. Until all of these routines
@ -1796,7 +1809,7 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
item->RoomNumber, 3);
}
}
else
else if (coll->Setup.EnableObjectPush)
ItemPushItem(item, laraItem, coll, false, 1);
}
}
@ -1810,7 +1823,7 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
// For Lara, solid static mesh collisions are directly managed by GetCollisionInfo,
// so we bypass them here to avoid interference.
if (playerCollision && (mesh->flags & StaticMeshFlags::SM_SOLID))
if (doPlayerCollision && (mesh->flags & StaticMeshFlags::SM_SOLID))
continue;
if (phd_Distance(&mesh->pos, &laraItem->Pose) >= COLLISION_CHECK_DISTANCE)
@ -1822,7 +1835,7 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
coll->HitStatic = true;
// HACK: Shatter statics only by non-harmless vehicles.
if (!playerCollision &&
if (!doPlayerCollision &&
!harmless && abs(laraItem->Animation.Velocity.z) > VEHICLE_COLLISION_TERMINAL_VELOCITY &&
StaticObjects[mesh->staticNumber].shatterType != SHT_NONE)
{
@ -1840,7 +1853,7 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
}
}
if (playerCollision)
if (doPlayerCollision)
{
auto* lara = GetLaraInfo(laraItem);
if (lara->HitDirection == -1)
@ -1880,14 +1893,12 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
if (!TestCollision(item, laraItem))
return;
bool playerCollision = laraItem->IsLara();
bool waterPlayerCollision = playerCollision && GetLaraInfo(laraItem)->Control.WaterStatus >= WaterStatus::TreadWater;
bool doPlayerCollision = laraItem->IsLara();
bool waterPlayerCollision = doPlayerCollision && GetLaraInfo(laraItem)->Control.WaterStatus >= WaterStatus::TreadWater;
if (waterPlayerCollision || coll->Setup.EnableObjectPush)
{
ItemPushItem(item, laraItem, coll, coll->Setup.EnableSpasm, 0);
}
else if (playerCollision && coll->Setup.EnableSpasm)
else if (doPlayerCollision && coll->Setup.EnableSpasm)
{
int x = laraItem->Pose.Position.x - item->Pose.Position.x;
int z = laraItem->Pose.Position.z - item->Pose.Position.z;
@ -1899,12 +1910,12 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
int rx = (frame->boundingBox.X1 + frame->boundingBox.X2) / 2;
int rz = (frame->boundingBox.X2 + frame->boundingBox.Z2) / 2;
if (frame->boundingBox.Y2 - frame->boundingBox.Y1 > STEP_SIZE)
if (frame->boundingBox.Height() > STEP_SIZE)
{
int angle = (laraItem->Pose.Orientation.y - phd_atan(z - cosY * rx - sinY * rz, x - cosY * rx + sinY * rz) - ANGLE(135.0f)) / ANGLE(90.0f);
auto* lara = GetLaraInfo(laraItem);
int angle = (laraItem->Pose.Orientation.y - phd_atan(z - cosY * rx - sinY * rz, x - cosY * rx + sinY * rz) - ANGLE(135.0f)) / ANGLE(90.0f);
lara->HitDirection = (short)angle;
// TODO: check if a second Lara.hitFrame++; is required there !

View file

@ -46,7 +46,7 @@ bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius);
bool ItemPushItem(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll, bool spasmEnabled, char bigPush);
bool ItemPushStatic(ItemInfo* laraItem, MESH_INFO* mesh, CollisionInfo* coll);
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX box, PHD_3DPOS pos, CollisionInfo* coll);
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PHD_3DPOS pos, CollisionInfo* coll);
void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll);
void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);

View file

@ -219,78 +219,72 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
{
// Player collision has several more precise checks for bridge collisions.
// Therefore, we should differentiate these code paths.
bool playerCollision = item->IsLara();
bool doPlayerCollision = item->IsLara();
// Reset collision parameters.
coll->CollisionType = CT_NONE;
coll->Shift.x = 0;
coll->Shift.y = 0;
coll->Shift.z = 0;
coll->CollisionType = CollisionType::CT_NONE;
coll->Shift = Vector3Int::Zero;
// Offset base probe position by provided offset, if any.
int xPos = item->Pose.Position.x + offset.x;
int yPos = item->Pose.Position.y + offset.y;
int zPos = item->Pose.Position.z + offset.z;
auto entityPos = item->Pose.Position + offset;
// Specify base probe position, Y position being bounds top side.
int x = xPos;
int y = yPos - coll->Setup.Height;
int z = zPos;
// Specify base probe position, with Y position being bounds top side.
auto probePos = Vector3Int(entityPos.x, entityPos.y - coll->Setup.Height, entityPos.z);
// Define side probe offsets.
int xfront, xright, xleft, zfront, zright, zleft;
// Declare side probe offsets.
int xFront, zFront, xRight, zRight, xLeft, zLeft;
// Get nearest 90-degree snapped angle (quadrant).
auto quadrant = GetQuadrant(coll->Setup.ForwardAngle);
// Get side probe offsets depending on quadrant.
// If unconstrained mode is specified, don't use quadrant.
switch (coll->Setup.Mode == CollisionProbeMode::Quadrants ? quadrant : -1)
switch ((coll->Setup.Mode == CollisionProbeMode::Quadrants) ? quadrant : -1)
{
case 0:
xfront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zfront = coll->Setup.Radius;
xleft = -coll->Setup.Radius;
zleft = coll->Setup.Radius;
xright = coll->Setup.Radius;
zright = coll->Setup.Radius;
xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zFront = coll->Setup.Radius;
xLeft = -coll->Setup.Radius;
zLeft = coll->Setup.Radius;
xRight = coll->Setup.Radius;
zRight = coll->Setup.Radius;
break;
case 1:
xfront = coll->Setup.Radius;
zfront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xleft = coll->Setup.Radius;
zleft = coll->Setup.Radius;
xright = coll->Setup.Radius;
zright = -coll->Setup.Radius;
xFront = coll->Setup.Radius;
zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xLeft = coll->Setup.Radius;
zLeft = coll->Setup.Radius;
xRight = coll->Setup.Radius;
zRight = -coll->Setup.Radius;
break;
case 2:
xfront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zfront = -coll->Setup.Radius;
xleft = coll->Setup.Radius;
zleft = -coll->Setup.Radius;
xright = -coll->Setup.Radius;
zright = -coll->Setup.Radius;
xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zFront = -coll->Setup.Radius;
xLeft = coll->Setup.Radius;
zLeft = -coll->Setup.Radius;
xRight = -coll->Setup.Radius;
zRight = -coll->Setup.Radius;
break;
case 3:
xfront = -coll->Setup.Radius;
zfront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xleft = -coll->Setup.Radius;
zleft = -coll->Setup.Radius;
xright = -coll->Setup.Radius;
zright = coll->Setup.Radius;
xFront = -coll->Setup.Radius;
zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xLeft = -coll->Setup.Radius;
zLeft = -coll->Setup.Radius;
xRight = -coll->Setup.Radius;
zRight = coll->Setup.Radius;
break;
// No valid quadrant; return true probe offsets from object rotation.
default:
// No valid quadrant, return true probe offsets from object rotation.
xfront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zfront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xleft = (xfront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_sin(coll->Setup.ForwardAngle - ANGLE(90)) * coll->Setup.Radius;
zleft = (zfront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_cos(coll->Setup.ForwardAngle - ANGLE(90)) * coll->Setup.Radius;
xright = (xfront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_sin(coll->Setup.ForwardAngle + ANGLE(90)) * coll->Setup.Radius;
zright = (zfront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_cos(coll->Setup.ForwardAngle + ANGLE(90)) * coll->Setup.Radius;
xFront = phd_sin(coll->Setup.ForwardAngle) * coll->Setup.Radius;
zFront = phd_cos(coll->Setup.ForwardAngle) * coll->Setup.Radius;
xLeft = (xFront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_sin(coll->Setup.ForwardAngle - ANGLE(90.0f)) * coll->Setup.Radius;
zLeft = (zFront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_cos(coll->Setup.ForwardAngle - ANGLE(90.0f)) * coll->Setup.Radius;
xRight = (xFront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_sin(coll->Setup.ForwardAngle + ANGLE(90.0f)) * coll->Setup.Radius;
zRight = (zFront * (coll->Setup.Mode == CollisionProbeMode::FreeForward ? 0.5f : 1.0f)) + phd_cos(coll->Setup.ForwardAngle + ANGLE(90.0f)) * coll->Setup.Radius;
break;
}
@ -302,7 +296,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
// TEST 1: TILT AND NEAREST LEDGE CALCULATION
auto collResult = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber);
auto collResult = GetCollision(probePos.x, item->Pose.Position.y, probePos.z, item->RoomNumber);
coll->FloorTilt = collResult.FloorTilt;
coll->CeilingTilt = collResult.CeilingTilt;
coll->NearestLedgeAngle = GetNearestLedgeAngle(item, coll, coll->NearestLedgeDistance);
@ -313,24 +307,28 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
// TEST 2: CENTERPOINT PROBE
collResult = GetCollision(x, y, z, item->RoomNumber);
auto topRoomNumber = collResult.RoomNumber; // Keep top room number as we need it to re-probe from origin room
if (playerCollision)
collResult = GetCollision(probePos.x, probePos.y, probePos.z, item->RoomNumber);
auto topRoomNumber = collResult.RoomNumber; // Keep top room number as we need it to re-probe from origin room.
if (doPlayerCollision)
{
tfLocation = GetRoom(item->Location, x, y, z);
height = GetFloorHeight(tfLocation, x, z).value_or(NO_HEIGHT);
tfLocation = GetRoom(item->Location, probePos.x, probePos.y, probePos.z);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(item->Location, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeilingHeight(tcLocation, x, z).value_or(NO_HEIGHT);
tcLocation = GetRoom(item->Location, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
}
else
{
height = collResult.Position.Floor;
ceiling = GetCeiling(collResult.Block, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
if (ceiling != NO_HEIGHT) ceiling -= y;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (ceiling != NO_HEIGHT)
ceiling -= probePos.y;
coll->Middle = collResult.Position;
coll->Middle.Floor = height;
@ -338,14 +336,14 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
// TEST 3: FRONTAL PROBE
x = xPos + xfront;
z = zPos + zfront;
probePos.x = entityPos.x + xFront;
probePos.z = entityPos.z + zFront;
g_Renderer.AddDebugSphere(Vector3(x, y, z), 32, Vector4(1, 0, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
g_Renderer.AddDebugSphere(probePos.ToVector3(), 32, Vector4(1, 0, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
collResult = GetCollision(x, y, z, topRoomNumber);
collResult = GetCollision(probePos.x, probePos.y, probePos.z, topRoomNumber);
if (playerCollision)
if (doPlayerCollision)
{
if (resetRoom)
{
@ -354,34 +352,38 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
topRoomNumber = item->RoomNumber;
}
tfLocation = GetRoom(tfLocation, x, y, z);
height = GetFloorHeight(tfLocation, x, z).value_or(NO_HEIGHT);
tfLocation = GetRoom(tfLocation, probePos.x, probePos.y, probePos.z);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeilingHeight(tcLocation, x, z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
}
else
{
height = collResult.Position.Floor;
ceiling = GetCeiling(collResult.Block, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
if (ceiling != NO_HEIGHT) ceiling -= y;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (ceiling != NO_HEIGHT)
ceiling -= probePos.y;
coll->Front = collResult.Position;
coll->Front.Floor = height;
coll->Front.Ceiling = ceiling;
if (playerCollision)
if (doPlayerCollision)
{
tfLocation = GetRoom(tfLocation, x + xfront, y, z + zfront);
height = GetFloorHeight(tfLocation, x + xfront, z + zfront).value_or(NO_HEIGHT);
tfLocation = GetRoom(tfLocation, probePos.x + xFront, probePos.y, probePos.z + zFront);
height = GetFloorHeight(tfLocation, probePos.x + xFront, probePos.z + zFront).value_or(NO_HEIGHT);
}
else
{
height = GetCollision(x + xfront, y, z + zfront, topRoomNumber).Position.Floor;
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
height = GetCollision(probePos.x + xFront, probePos.y, probePos.z + zFront, topRoomNumber).Position.Floor;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (coll->Setup.BlockFloorSlopeUp &&
coll->Front.FloorSlope &&
@ -409,35 +411,39 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
coll->Front.Floor = STOP_SIZE;
}
else if (coll->Setup.BlockMonkeySwingEdge &&
!GetCollision(x, y + coll->Setup.Height, z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
!GetCollision(probePos.x, probePos.y + coll->Setup.Height, probePos.z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
{
coll->Front.Floor = MAX_HEIGHT;
}
// TEST 4: MIDDLE-LEFT PROBE
x = xPos + xleft;
z = zPos + zleft;
probePos.x = entityPos.x + xLeft;
probePos.z = entityPos.z + zLeft;
g_Renderer.AddDebugSphere(Vector3(x, y, z), 32, Vector4(0, 0, 1, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
g_Renderer.AddDebugSphere(probePos.ToVector3(), 32, Vector4(0, 0, 1, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
collResult = GetCollision(x, y, z, item->RoomNumber);
collResult = GetCollision(probePos.x, probePos.y, probePos.z, item->RoomNumber);
if (playerCollision)
if (doPlayerCollision)
{
lrfLocation = GetRoom(item->Location, x, y, z);
height = GetFloorHeight(lrfLocation, x, z).value_or(NO_HEIGHT);
lrfLocation = GetRoom(item->Location, probePos.x, probePos.y, probePos.z);
height = GetFloorHeight(lrfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
lrcLocation = GetRoom(item->Location, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeilingHeight(lrcLocation, x, z).value_or(NO_HEIGHT);
lrcLocation = GetRoom(item->Location, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
ceiling = GetCeilingHeight(lrcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
}
else
{
height = collResult.Position.Floor;
ceiling = GetCeiling(collResult.Block, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
if (ceiling != NO_HEIGHT) ceiling -= y;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (ceiling != NO_HEIGHT)
ceiling -= probePos.y;
coll->MiddleLeft = collResult.Position;
coll->MiddleLeft.Floor = height;
@ -467,30 +473,34 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
coll->MiddleLeft.Floor = STOP_SIZE;
}
else if (coll->Setup.BlockMonkeySwingEdge &&
!GetCollision(x, y + coll->Setup.Height, z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
!GetCollision(probePos.x, probePos.y + coll->Setup.Height, probePos.z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
{
coll->MiddleLeft.Floor = MAX_HEIGHT;
}
// TEST 5: FRONT-LEFT PROBE
collResult = GetCollision(x, y, z, topRoomNumber); // We use plain x/z values here, proposed by Choco
collResult = GetCollision(probePos.x, probePos.y, probePos.z, topRoomNumber); // Use plain X/Z values here as proposed by Choco.
if (playerCollision)
if (doPlayerCollision)
{
tfLocation = GetRoom(tfLocation, x, y, z);
height = GetFloorHeight(tfLocation, x, z).value_or(NO_HEIGHT);
tfLocation = GetRoom(tfLocation, probePos.x, probePos.y, probePos.z);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeilingHeight(tcLocation, x, z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
}
else
{
height = collResult.Position.Floor;
ceiling = GetCeiling(collResult.Block, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
if (ceiling != NO_HEIGHT) ceiling -= y;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (ceiling != NO_HEIGHT)
ceiling -= probePos.y;
coll->FrontLeft = collResult.Position;
coll->FrontLeft.Floor = height;
@ -520,35 +530,39 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
coll->FrontLeft.Floor = STOP_SIZE;
}
else if (coll->Setup.BlockMonkeySwingEdge &&
!GetCollision(x, y + coll->Setup.Height, z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
!GetCollision(probePos.x, probePos.y + coll->Setup.Height, probePos.z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
{
coll->FrontLeft.Floor = MAX_HEIGHT;
}
// TEST 6: MIDDLE-RIGHT PROBE
x = xPos + xright;
z = zPos + zright;
probePos.x = entityPos.x + xRight;
probePos.z = entityPos.z + zRight;
g_Renderer.AddDebugSphere(Vector3(x, y, z), 32, Vector4(0, 1, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
g_Renderer.AddDebugSphere(probePos.ToVector3(), 32, Vector4(0, 1, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
collResult = GetCollision(x, y, z, item->RoomNumber);
collResult = GetCollision(probePos.x, probePos.y, probePos.z, item->RoomNumber);
if (playerCollision)
if (doPlayerCollision)
{
lrfLocation = GetRoom(item->Location, x, y, z);
height = GetFloorHeight(lrfLocation, x, z).value_or(NO_HEIGHT);
lrfLocation = GetRoom(item->Location, probePos.x, probePos.y, probePos.z);
height = GetFloorHeight(lrfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
lrcLocation = GetRoom(item->Location, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeilingHeight(lrcLocation, x, z).value_or(NO_HEIGHT);
lrcLocation = GetRoom(item->Location, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
ceiling = GetCeilingHeight(lrcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
}
else
{
height = collResult.Position.Floor;
ceiling = GetCeiling(collResult.Block, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
if (ceiling != NO_HEIGHT) ceiling -= y;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (ceiling != NO_HEIGHT)
ceiling -= probePos.y;
coll->MiddleRight = collResult.Position;
coll->MiddleRight.Floor = height;
@ -578,30 +592,34 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
coll->MiddleRight.Floor = STOP_SIZE;
}
else if (coll->Setup.BlockMonkeySwingEdge &&
!GetCollision(x, y + coll->Setup.Height, z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
!GetCollision(probePos.x, probePos.y + coll->Setup.Height, probePos.z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
{
coll->MiddleRight.Floor = MAX_HEIGHT;
}
// TEST 7: FRONT-RIGHT PROBE
collResult = GetCollision(x, y, z, topRoomNumber);
collResult = GetCollision(probePos.x, probePos.y, probePos.z, topRoomNumber);
if (playerCollision)
if (doPlayerCollision)
{
tfLocation = GetRoom(tfLocation, x, y, z);
height = GetFloorHeight(tfLocation, x, z).value_or(NO_HEIGHT);
tfLocation = GetRoom(tfLocation, probePos.x, probePos.y, probePos.z);
height = GetFloorHeight(tfLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeilingHeight(tcLocation, x, z).value_or(NO_HEIGHT);
tcLocation = GetRoom(tcLocation, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
ceiling = GetCeilingHeight(tcLocation, probePos.x, probePos.z).value_or(NO_HEIGHT);
}
else
{
height = collResult.Position.Floor;
ceiling = GetCeiling(collResult.Block, x, y - item->Animation.Velocity.y, z);
ceiling = GetCeiling(collResult.Block, probePos.x, probePos.y - item->Animation.Velocity.y, probePos.z);
}
if (height != NO_HEIGHT) height -= (playerCollision ? yPos : y);
if (ceiling != NO_HEIGHT) ceiling -= y;
if (height != NO_HEIGHT)
height -= (doPlayerCollision ? entityPos.y : probePos.y);
if (ceiling != NO_HEIGHT)
ceiling -= probePos.y;
coll->FrontRight = collResult.Position;
coll->FrontRight.Floor = height;
@ -631,7 +649,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
coll->FrontRight.Floor = STOP_SIZE;
}
else if (coll->Setup.BlockMonkeySwingEdge &&
!GetCollision(x, y + coll->Setup.Height, z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
!GetCollision(probePos.x, probePos.y + coll->Setup.Height, probePos.z, item->RoomNumber).BottomBlock->Flags.Monkeyswing)
{
coll->FrontRight.Floor = MAX_HEIGHT;
}
@ -645,18 +663,14 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
if (coll->Middle.Floor == NO_HEIGHT)
{
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.y = coll->Setup.OldPosition.y - yPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift = coll->Setup.OldPosition - entityPos;
coll->CollisionType = CT_FRONT;
return;
}
if (coll->Middle.Floor - coll->Middle.Ceiling <= 0)
{
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.y = coll->Setup.OldPosition.y - yPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift = coll->Setup.OldPosition - entityPos;
coll->CollisionType = CT_CLAMP;
return;
}
@ -675,8 +689,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
{
if (coll->Front.HasDiagonalSplit())
{
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift.x = coll->Setup.OldPosition.x - entityPos.x;
coll->Shift.z = coll->Setup.OldPosition.z - entityPos.z;
}
else
{
@ -684,28 +698,26 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
{
case 0:
case 2:
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = FindGridShift(zPos + zfront, zPos);
coll->Shift.x = coll->Setup.OldPosition.x - entityPos.x;
coll->Shift.z = FindGridShift(entityPos.z + zFront, entityPos.z);
break;
case 1:
case 3:
coll->Shift.x = FindGridShift(xPos + xfront, xPos);
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift.x = FindGridShift(entityPos.x + xFront, entityPos.x);
coll->Shift.z = coll->Setup.OldPosition.z - entityPos.z;
break;
}
}
coll->CollisionType = (coll->CollisionType == CT_TOP ? CT_TOP_FRONT : CT_FRONT);
coll->CollisionType = ((coll->CollisionType == CT_TOP) ? CT_TOP_FRONT : CT_FRONT);
return;
}
if (coll->Front.Ceiling > coll->Setup.LowerCeilingBound ||
coll->Front.Ceiling < coll->Setup.UpperCeilingBound)
{
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.y = coll->Setup.OldPosition.y - yPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift = coll->Setup.OldPosition - entityPos;
coll->CollisionType = CT_TOP_FRONT;
return;
}
@ -721,8 +733,8 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
// HACK: Force slight push-out to the left side to avoid stucking
TranslateItem(item, coll->Setup.ForwardAngle + ANGLE(8.0f), item->Animation.Velocity.z);
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift.x = coll->Setup.OldPosition.x - entityPos.x;
coll->Shift.z = coll->Setup.OldPosition.z - entityPos.z;
}
else
{
@ -730,34 +742,28 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
{
case 0:
case 2:
coll->Shift.x = FindGridShift(xPos + xleft, xPos + xfront);
coll->Shift.x = FindGridShift(entityPos.x + xLeft, entityPos.x + xFront);
break;
case 1:
case 3:
coll->Shift.z = FindGridShift(zPos + zleft, zPos + zfront);
coll->Shift.z = FindGridShift(entityPos.z + zLeft, entityPos.z + zFront);
break;
}
}
if (coll->DiagonalStepAtLeft())
{
int quarter = (unsigned short)(coll->Setup.ForwardAngle) / ANGLE(90); // different from quadrant!
int quarter = (unsigned short)(coll->Setup.ForwardAngle) / ANGLE(90.0f); // NOTE: Different from quadrant!
quarter %= 2;
if (coll->MiddleLeft.HasFlippedDiagonalSplit())
{
if (quarter) coll->CollisionType = CT_LEFT;
}
else
{
if (!quarter) coll->CollisionType = CT_LEFT;
}
}
else
{
coll->CollisionType = CT_LEFT;
}
return;
}
@ -770,11 +776,11 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
{
if (coll->TriangleAtRight() && !coll->MiddleRight.FloorSlope)
{
// HACK: Force slight push-out to the right side to avoid stucking
// HACK: Force slight push out to the right side to avoid getting stuck.
TranslateItem(item, coll->Setup.ForwardAngle - ANGLE(8.0f), item->Animation.Velocity.z);
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
coll->Shift.x = coll->Setup.OldPosition.x - entityPos.x;
coll->Shift.z = coll->Setup.OldPosition.z - entityPos.z;
}
else
{
@ -782,85 +788,64 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
{
case 0:
case 2:
coll->Shift.x = FindGridShift(xPos + xright, xPos + xfront);
coll->Shift.x = FindGridShift(entityPos.x + xRight, entityPos.x + xFront);
break;
case 1:
case 3:
coll->Shift.z = FindGridShift(zPos + zright, zPos + zfront);
coll->Shift.z = FindGridShift(entityPos.z + zRight, entityPos.z + zFront);
break;
}
}
if (coll->DiagonalStepAtRight())
{
int quarter = (unsigned short)(coll->Setup.ForwardAngle) / ANGLE(90); // different from quadrant!
int quarter = (unsigned short)(coll->Setup.ForwardAngle) / ANGLE(90.0f); // NOTE: Different from quadrant!
quarter %= 2;
if (coll->MiddleRight.HasFlippedDiagonalSplit())
{
if (quarter) coll->CollisionType = CT_RIGHT;
}
else
{
if (!quarter) coll->CollisionType = CT_RIGHT;
}
}
else
{
coll->CollisionType = CT_RIGHT;
}
return;
}
}
// New function for rotating item along XZ slopes.
// (int radiusDivide) is for radiusZ, else the MaxZ is too high and cause rotation problem !
// Dont need to set a value in radiusDivisor if you dont need it (radiusDivisor is set to 1 by default).
// Warning: dont set it to 0 !!!!
void CalculateItemRotationToSurface(ItemInfo* item, float radiusDivisor, short xOffset, short zOffset)
void AlignEntityToSurface(ItemInfo* item, Vector2 radius, float tiltConstraintAngle, Vector3Shrt tiltOffset)
{
if (!radiusDivisor)
{
TENLog(std::string("CalculateItemRotationToSurface() attempted division by zero!"), LogLevel::Warning);
return;
}
// Reduce probe radii for stability.
auto halvedRadius = radius / 2;
auto pos = GameVector(
item->Pose.Position.x,
item->Pose.Position.y,
item->Pose.Position.z,
item->RoomNumber
// Probe heights at points around the entity.
int frontHeight = GetCollision(item, item->Pose.Orientation.y, halvedRadius.y).Position.Floor;
int backHeight = GetCollision(item, item->Pose.Orientation.y + ANGLE(180.0f), halvedRadius.y).Position.Floor;
int leftHeight = GetCollision(item, item->Pose.Orientation.y - ANGLE(90.0f), halvedRadius.x).Position.Floor;
int rightHeight = GetCollision(item, item->Pose.Orientation.y + ANGLE(90.0f), halvedRadius.x).Position.Floor;
// Calculate height differences.
int forwardHeightDif = backHeight - frontHeight;
int lateralHeightDif = rightHeight - leftHeight;
// Don't align if height differences are too significant.
if ((abs(forwardHeightDif) > STEPUP_HEIGHT) || (abs(lateralHeightDif) > STEPUP_HEIGHT))
return;
// Calculate and apply tilts.
auto tiltedOrient = Vector3Shrt(
phd_atan(radius.y * 2, forwardHeightDif) + tiltOffset.x,
0,
phd_atan(radius.x * 2, lateralHeightDif) + tiltOffset.z
);
auto* bounds = GetBoundsAccurate(item);
auto radiusX = bounds->X2;
auto radiusZ = bounds->Z2 / radiusDivisor; // Need divide in any case else it's too much !
if (abs(tiltedOrient.x) <= ANGLE(tiltConstraintAngle))
item->Pose.Orientation.x = tiltedOrient.x;
auto ratioXZ = radiusZ / radiusX;
auto frontX = phd_sin(item->Pose.Orientation.y) * radiusZ;
auto frontZ = phd_cos(item->Pose.Orientation.y) * radiusZ;
auto leftX = -frontZ * ratioXZ;
auto leftZ = frontX * ratioXZ;
auto rightX = frontZ * ratioXZ;
auto rightZ = -frontX * ratioXZ;
auto frontHeight = GetCollision(pos.x + frontX, pos.y, pos.z + frontZ, pos.roomNumber).Position.Floor;
auto backHeight = GetCollision(pos.x - frontX, pos.y, pos.z - frontZ, pos.roomNumber).Position.Floor;
auto leftHeight = GetCollision(pos.x + leftX, pos.y, pos.z + leftZ, pos.roomNumber).Position.Floor;
auto rightHeight = GetCollision(pos.x + rightX, pos.y, pos.z + rightZ, pos.roomNumber).Position.Floor;
auto frontHDif = backHeight - frontHeight;
auto sideHDif = rightHeight - leftHeight;
// Don't align if height differences are too large
if ((abs(frontHDif) > STEPUP_HEIGHT) || (abs(sideHDif) > STEPUP_HEIGHT))
return;
// NOTE: float(atan2()) is required, else warning about double !
item->Pose.Orientation.x = ANGLE(float(atan2(frontHDif, 2 * radiusZ)) / RADIAN) + xOffset;
item->Pose.Orientation.z = ANGLE(float(atan2(sideHDif, 2 * radiusX)) / RADIAN) + zOffset;
if (abs(tiltedOrient.z) <= ANGLE(tiltConstraintAngle))
item->Pose.Orientation.z = tiltedOrient.z;
}
int GetQuadrant(short angle)
@ -890,7 +875,7 @@ short GetNearestLedgeAngle(ItemInfo* item, CollisionInfo* coll, float& distance)
// Determine two Y points to test (lower and higher).
// 1/10 headroom crop is needed to avoid possible issues with tight diagonal headrooms.
int headroom = abs(bounds->Y2 - bounds->Y1) / 20.0f;
int headroom = bounds->Height() / 20.0f;
int yPoints[2] = { item->Pose.Position.y + bounds->Y1 + headroom,
item->Pose.Position.y + bounds->Y2 - headroom };
@ -1453,15 +1438,15 @@ int GetWaterHeight(ItemInfo* item)
return GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
}
short GetSurfaceSteepnessAngle(float xTilt, float zTilt)
short GetSurfaceSteepnessAngle(Vector2 tilt)
{
short qtrBlockAngleIncrement = ANGLE(45.0f) / 4;
return (short)sqrt(pow(xTilt * qtrBlockAngleIncrement, 2) + pow(zTilt * qtrBlockAngleIncrement, 2));
return (short)sqrt(pow(tilt.x * qtrBlockAngleIncrement, 2) + pow(tilt.y * qtrBlockAngleIncrement, 2));
}
short GetSurfaceAspectAngle(float xTilt, float zTilt)
short GetSurfaceAspectAngle(Vector2 tilt)
{
return (short)phd_atan(-zTilt, -xTilt);
return (short)phd_atan(-tilt.y, -tilt.x);
}
bool TestEnvironment(RoomEnvFlags environmentType, int x, int y, int z, int roomNumber)

View file

@ -152,10 +152,10 @@ void SnapItemToLedge(ItemInfo* item, CollisionInfo* coll, float offsetMultiplier
void SnapItemToLedge(ItemInfo* item, CollisionInfo* coll, short angle, float offsetMultiplier = 0.0f);
void SnapItemToGrid(ItemInfo* item, CollisionInfo* coll);
void CalculateItemRotationToSurface(ItemInfo* item, float radiusDivisor = 1.0f, short xOffset = 0, short zOffset = 0);
void AlignEntityToSurface(ItemInfo* item, Vector2 radius, float tiltConstraintAngle = 45.0f, Vector3Shrt tiltOffset = Vector3Shrt::Zero);
short GetSurfaceAspectAngle(float xTilt, float zTilt);
short GetSurfaceSteepnessAngle(float xTilt, float zTilt);
short GetSurfaceAspectAngle(Vector2 tilt);
short GetSurfaceSteepnessAngle(Vector2 tilt);
bool TestEnvironment(RoomEnvFlags environmentType, int x, int y, int z, int roomNumber);
bool TestEnvironment(RoomEnvFlags environmentType, ItemInfo* item);

View file

@ -8,14 +8,15 @@
#include "Game/control/control.h"
#include "Game/control/lot.h"
#include "Game/effects/tomb4fx.h"
#include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/room.h"
#include "Specific/setup.h"
#include "Specific/trmath.h"
#include "Objects/objectslist.h"
#include "Game/itemdata/creature_info.h"
#include "Objects/TR5/Object/tr5_pushableblock.h"
#include "Renderer/Renderer11.h"
@ -296,19 +297,17 @@ void CreatureKill(ItemInfo* item, int killAnim, int killState, int laraKillState
*/
}
short CreatureEffect2(ItemInfo* item, BITE_INFO* bite, short damage, short angle, std::function<CreatureEffectFunction> func)
short CreatureEffect2(ItemInfo* item, BiteInfo bite, short velocity, short angle, std::function<CreatureEffectFunction> func)
{
auto pos = Vector3Int(bite->x, bite->y, bite->z);
GetJointAbsPosition(item, &pos, bite->meshNum);
return func(pos.x, pos.y, pos.z, damage, angle, item->RoomNumber);
auto pos = Vector3Int(bite.Position);
GetJointAbsPosition(item, &pos, bite.meshNum);
return func(pos.x, pos.y, pos.z, velocity, angle, item->RoomNumber);
}
short CreatureEffect(ItemInfo* item, BITE_INFO* bite, std::function<CreatureEffectFunction> func)
short CreatureEffect(ItemInfo* item, BiteInfo bite, std::function<CreatureEffectFunction> func)
{
auto pos = Vector3Int(bite->x, bite->y, bite->z);
GetJointAbsPosition(item, &pos, bite->meshNum);
auto pos = Vector3Int(bite.Position);
GetJointAbsPosition(item, &pos, bite.meshNum);
return func(pos.x, pos.y, pos.z, item->Animation.Velocity.z, item->Pose.Orientation.y, item->RoomNumber);
}
@ -1390,7 +1389,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box;
room = &g_Level.Rooms[aiObject->roomNumber];
aiObject->boxNumber = GetSector(room, aiObject->x - room->x, aiObject->z - room->z)->Box;
aiObject->boxNumber = GetSector(room, aiObject->pos.Position.x - room->x, aiObject->pos.Position.z - room->z)->Box;
if (item->BoxNumber == NO_BOX || aiObject->boxNumber == NO_BOX)
return;
@ -1411,10 +1410,10 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
aiItem->ObjectNumber = foundObject->objectNumber;
aiItem->RoomNumber = foundObject->roomNumber;
aiItem->Pose.Position.x = foundObject->x;
aiItem->Pose.Position.y = foundObject->y;
aiItem->Pose.Position.z = foundObject->z;
aiItem->Pose.Orientation.y = foundObject->yRot;
aiItem->Pose.Position.x = foundObject->pos.Position.x;
aiItem->Pose.Position.y = foundObject->pos.Position.y;
aiItem->Pose.Position.z = foundObject->pos.Position.z;
aiItem->Pose.Orientation.y = foundObject->pos.Orientation.y;
aiItem->Flags = foundObject->flags;
aiItem->TriggerFlags = foundObject->triggerFlags;
aiItem->BoxNumber = foundObject->boxNumber;
@ -1436,8 +1435,9 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
auto* creature = GetCreatureInfo(item);
auto* object = &Objects[item->ObjectNumber];
// TODO: Deal with LaraItem global.
auto* enemy = creature->Enemy;
if (!enemy)
if (enemy == nullptr)
{
enemy = LaraItem;
creature->Enemy = LaraItem;
@ -1475,7 +1475,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
auto probe = GetCollision(floor, enemy->Pose.Position.x, enemy->Pose.Position.y, enemy->Pose.Position.z);
auto bounds = GetBoundsAccurate(item);
reachable = abs(enemy->Pose.Position.y - probe.Position.Floor) < abs(bounds->Y2 - bounds->Y1);
reachable = abs(enemy->Pose.Position.y - probe.Position.Floor) < bounds->Height();
}
if (floor && reachable)
@ -1532,8 +1532,11 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
vector.z = abs(vector.z);
// Makes Lara smaller.
if (enemy == LaraItem && ((LaraInfo*)enemy)->Control.IsLow)
vector.y -= STEPUP_HEIGHT;
if (enemy->IsLara())
{
if (GetLaraInfo(enemy)->Control.IsLow)
vector.y -= STEPUP_HEIGHT;
}
if (vector.x > vector.z)
AI->xAngle = phd_atan(vector.x + (vector.z >> 1), vector.y);
@ -1544,7 +1547,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
AI->bite = (AI->ahead && enemy->HitPoints > 0 && abs(enemy->Pose.Position.y - item->Pose.Position.y) <= CLICK(2));
}
void CreatureMood(ItemInfo* item, AI_INFO* AI, int violent)
void CreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent)
{
if (!item->Data)
return;
@ -1592,7 +1595,7 @@ void CreatureMood(ItemInfo* item, AI_INFO* AI, int violent)
{
if (EscapeBox(item, enemy, boxNumber))
TargetBox(LOT, boxNumber);
else if (AI->zoneNumber == AI->enemyZone && StalkBox(item, enemy, boxNumber) && !violent)
else if (AI->zoneNumber == AI->enemyZone && StalkBox(item, enemy, boxNumber) && !isViolent)
{
TargetBox(LOT, boxNumber);
creature->Mood = MoodType::Stalk;
@ -1694,7 +1697,7 @@ void CreatureMood(ItemInfo* item, AI_INFO* AI, int violent)
}
}
void GetCreatureMood(ItemInfo* item, AI_INFO* AI, int isViolent)
void GetCreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent)
{
if (!item->Data)
return;
@ -2082,15 +2085,13 @@ void InitialiseItemBoxData()
if (!(g_Level.Boxes[floor->Box].flags & BLOCKED))
{
int floorHeight = floor->FloorHeight(mesh.pos.Position.x, mesh.pos.Position.z);
auto* staticInfo = &StaticObjects[mesh.staticNumber];
auto bbox = GetBoundsAccurate(&mesh, false);
if (floorHeight <= mesh.pos.Position.y - staticInfo->collisionBox.Y2 + CLICK(2) &&
floorHeight < mesh.pos.Position.y - staticInfo->collisionBox.Y1)
if (floorHeight <= mesh.pos.Position.y - bbox->Y2 + CLICK(2) &&
floorHeight < mesh.pos.Position.y - bbox->Y1)
{
if (staticInfo->collisionBox.X1 == 0 || staticInfo->collisionBox.X2 == 0 ||
staticInfo->collisionBox.Z1 == 0 || staticInfo->collisionBox.Z2 == 0 ||
((staticInfo->collisionBox.X1 < 0) ^ (staticInfo->collisionBox.X2 < 0)) &&
((staticInfo->collisionBox.Z1 < 0) ^ (staticInfo->collisionBox.Z2 < 0)))
if (bbox->X1 == 0 || bbox->X2 == 0 || bbox->Z1 == 0 || bbox->Z2 == 0 ||
((bbox->X1 < 0) ^ (bbox->X2 < 0)) && ((bbox->Z1 < 0) ^ (bbox->Z2 < 0)))
{
floor->Stopper = true;
}

View file

@ -1,10 +1,10 @@
#pragma once
#include "Specific/phd_global.h"
#include "Specific/level.h"
#include "Specific/phd_global.h"
struct ItemInfo;
struct BITE_INFO;
struct BiteInfo;
struct CreatureInfo;
struct ItemInfo;
struct LOTInfo;
enum TARGET_TYPE
@ -93,26 +93,26 @@ struct OVERLAP
int flags;
};
struct BITE_INFO
struct BiteInfo
{
int x;
int y;
int z;
int meshNum;
Vector3 Position = Vector3::Zero;
int meshNum = 0;
BITE_INFO()
BiteInfo()
{
this->x = 0;
this->y = 0;
this->z = 0;
this->Position = Vector3::Zero;
this->meshNum = 0;
}
BITE_INFO(int xpos, int ypos, int zpos, int meshNumber)
BiteInfo(Vector3 pos, int meshNumber)
{
this->x = xpos;
this->y = ypos;
this->z = zpos;
this->Position = pos;
this->meshNum = meshNumber;
}
BiteInfo(float xPos, float yPos, float zPos, int meshNumber)
{
this->Position = Vector3(xPos, yPos, zPos);
this->meshNum = meshNumber;
}
};
@ -121,8 +121,8 @@ struct BITE_INFO
constexpr auto BOX_BLOCKED = (1 << 14); // unpassable for other enemies, always set for movable blocks & closed doors
constexpr auto BOX_LAST = (1 << 15); // unpassable by large enemies (T-Rex, Centaur, etc), always set behind doors
constexpr auto TIMID = 0;
constexpr auto VIOLENT = 1;
constexpr auto REVERSE = 0x4000;
constexpr auto BLOCKABLE = 0x8000;
constexpr auto BLOCKED = 0x4000;
@ -146,8 +146,8 @@ constexpr auto CLIP_BOTTOM = 0x8;
constexpr auto SECONDARY_CLIP = 0x10;
constexpr auto ALL_CLIP = (CLIP_LEFT | CLIP_RIGHT | CLIP_TOP | CLIP_BOTTOM);
void GetCreatureMood(ItemInfo* item, AI_INFO* AI, int violent);
void CreatureMood(ItemInfo* item, AI_INFO* AI, int violent);
void GetCreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent);
void CreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent);
void FindAITargetObject(CreatureInfo* creature, short objectNumber);
void GetAITarget(CreatureInfo* creature);
int CreatureVault(short itemNumber, short angle, int vault, int shift);
@ -160,8 +160,8 @@ short AIGuard(CreatureInfo* creature);
void AlertNearbyGuards(ItemInfo* item);
void AlertAllGuards(short itemNumber);
void CreatureKill(ItemInfo* item, int killAnim, int killState, int laraKillState);
short CreatureEffect2(ItemInfo* item, BITE_INFO* bite, short damage, short angle, std::function<CreatureEffectFunction> func);
short CreatureEffect(ItemInfo* item, BITE_INFO* bite, std::function<CreatureEffectFunction> func);
short CreatureEffect2(ItemInfo* item, BiteInfo bite, short velocity, short angle, std::function<CreatureEffectFunction> func);
short CreatureEffect(ItemInfo* item, BiteInfo bite, std::function<CreatureEffectFunction> func);
void CreatureUnderwater(ItemInfo* item, int depth);
void CreatureFloat(short itemNumber);
void CreatureJoint(ItemInfo* item, short joint, short required);
@ -185,6 +185,5 @@ void CreatureSwitchRoom(short itemNumber);
void AdjustStopperFlag(ItemInfo* item, int direction, bool set);
void InitialiseItemBoxData();
void DrawBox(int boxIndex, Vector3 color);
void DrawNearbyPathfinding(int boxIndex);
void DrawNearbyPathfinding(int boxIndex);

View file

@ -325,8 +325,9 @@ void FloorShake(ItemInfo* item)
void Turn180(ItemInfo* item)
{
item->Pose.Orientation.y -= ANGLE(180.0f);
item->Pose.Orientation.x = -item->Pose.Orientation.x;
item->Pose.Orientation.y += ANGLE(180.0f);
item->Pose.Orientation.z = -item->Pose.Orientation.z;
}
void FinishLevel(ItemInfo* item)

View file

@ -315,7 +315,7 @@ int ObjectOnLOS2(GameVector* start, GameVector* end, Vector3Int* vec, MESH_INFO*
pos.Position.z = meshp->pos.Position.z;
pos.Orientation.y = meshp->pos.Orientation.y;
if (DoRayBox(start, end, &StaticObjects[meshp->staticNumber].collisionBox, &pos, vec, -1 - meshp->staticNumber))
if (DoRayBox(start, end, GetBoundsAccurate(meshp, false), &pos, vec, -1 - meshp->staticNumber))
{
*mesh = meshp;
end->roomNumber = LosRooms[r];
@ -333,7 +333,7 @@ int ObjectOnLOS2(GameVector* start, GameVector* end, Vector3Int* vec, MESH_INFO*
if ((priorityObject != GAME_OBJECT_ID::ID_NO_OBJECT) && (item->ObjectNumber != priorityObject))
continue;
if ((item->ObjectNumber != ID_LARA) && (Objects[item->ObjectNumber].collision == NULL))
if ((item->ObjectNumber != ID_LARA) && (Objects[item->ObjectNumber].collision == nullptr))
continue;
if ((item->ObjectNumber == ID_LARA) && (priorityObject != ID_LARA))

View file

@ -5,6 +5,7 @@
#include "Game/camera.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/Lara/lara.h"
#include "Specific/level.h"
#include "Specific/setup.h"
@ -18,7 +19,7 @@ std::vector<CreatureInfo*> ActiveCreatures;
void InitialiseLOTarray(int itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = (CreatureInfo*)item->Data;
auto* creature = GetCreatureInfo(item);
if(!creature->LOT.Initialised)
{
@ -31,7 +32,7 @@ int EnableEntityAI(short itemNum, int always, bool makeTarget)
{
ItemInfo* item = &g_Level.Items[itemNum];
if (item->Data.is<CreatureInfo>())
if (item->IsCreature())
return true;
/*
@ -99,12 +100,12 @@ int EnableEntityAI(short itemNum, int always, bool makeTarget)
void DisableEntityAI(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (!item->IsCreature())
return;
auto* creature = (CreatureInfo*)item->Data;
auto* creature = GetCreatureInfo(item);
creature->ItemNumber = NO_ITEM;
KillItem(creature->AITargetNumber);
ActiveCreatures.erase(std::find(ActiveCreatures.begin(), ActiveCreatures.end(), creature));
@ -113,11 +114,11 @@ void DisableEntityAI(short itemNumber)
void InitialiseSlot(short itemNum, short slot, bool makeTarget)
{
ItemInfo* item = &g_Level.Items[itemNum];
ObjectInfo* obj = &Objects[item->ObjectNumber];
auto* item = &g_Level.Items[itemNum];
auto* obj = &Objects[item->ObjectNumber];
item->Data = CreatureInfo();
CreatureInfo* creature = item->Data;
auto* creature = GetCreatureInfo(item);
InitialiseLOTarray(itemNum);
creature->ItemNumber = itemNum;
creature->Mood = MoodType::Bored;
@ -158,22 +159,22 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
{
default:
case ZONE_NULL:
creature->LOT.Step = SECTOR(1) - CLICK(3);
creature->LOT.Drop = -(SECTOR(1) - CLICK(3));
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
obj->zoneType = ZONE_BASIC; // only entity that use CreatureActive() will reach InitialiseSlot() !
break;
case ZONE_SKELLY:
// Can jump
creature->LOT.Step = SECTOR(1) - CLICK(3);
creature->LOT.Drop = -(SECTOR(1) - CLICK(3));
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.CanJump = true;
creature->LOT.Zone = ZONE_SKELLY;
break;
case ZONE_BASIC:
creature->LOT.Step = SECTOR(1) - CLICK(3);
creature->LOT.Drop = -(SECTOR(1) - CLICK(3));
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Zone = ZONE_BASIC;
break;
@ -195,7 +196,6 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
{
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED / 2; // is more slow than the other underwater entity
creature->LOT.IsAmphibious = true; // crocodile can walk and swim.
creature->LOT.Zone = ZONE_FLYER;
}
else if (item->ObjectNumber == ID_BIG_RAT)
{
@ -206,7 +206,6 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
{
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED;
}
break;
case ZONE_HUMAN_CLASSIC:
@ -235,8 +234,8 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
case ZONE_HUMAN_LONGJUMP_AND_MONKEY:
// Can climb, jump, monkey, long jump
creature->LOT.Step = 1792;
creature->LOT.Drop = -1792;
creature->LOT.Step = SECTOR(1) + CLICK(3);
creature->LOT.Drop = -(SECTOR(1) + CLICK(3));
creature->LOT.CanJump = true;
creature->LOT.CanMonkey = true;
creature->LOT.Zone = ZONE_VON_CROY;
@ -253,8 +252,13 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
creature->LOT.Zone = ZONE_BASIC;
break;
case ZONE_APE:
creature->LOT.Step = CLICK(2);
creature->LOT.Drop = -SECTOR(1);
break;
case ZONE_SOPHIALEE:
creature->LOT.Step = CLICK(4);
creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -CLICK(3);
creature->LOT.Zone = ZONE_HUMAN_CLASSIC;
break;
@ -269,9 +273,8 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
void SetBaddyTarget(short itemNum, short target)
{
ItemInfo* item = &g_Level.Items[itemNum];
CreatureInfo* creature = item->Data;
auto* item = &g_Level.Items[itemNum];
auto* creature = GetCreatureInfo(item);
creature->AITargetNumber = target;
@ -289,7 +292,7 @@ void ClearLOT(LOTInfo* LOT)
LOT->TargetBox = NO_BOX;
LOT->RequiredBox = NO_BOX;
BOX_NODE* node = LOT->Node.data();
auto* node = LOT->Node.data();
for(auto& node : LOT->Node)
{
node.exitBox = NO_BOX;
@ -300,8 +303,8 @@ void ClearLOT(LOTInfo* LOT)
void CreateZone(ItemInfo* item)
{
CreatureInfo* creature = (CreatureInfo*)item->Data;
ROOM_INFO* r = &g_Level.Rooms[item->RoomNumber];
auto* creature = GetCreatureInfo(item);
auto* r = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(r, item->Pose.Position.x - r->x, item->Pose.Position.z - r->z)->Box;
@ -325,7 +328,7 @@ void CreateZone(ItemInfo* item)
int zoneNumber = zone[item->BoxNumber];
int flippedZoneNumber = flippedZone[item->BoxNumber];
BOX_NODE* node = creature->LOT.Node.data();
auto* node = creature->LOT.Node.data();
creature->LOT.ZoneCount = 0;
for (int i = 0; i < g_Level.Boxes.size(); i++)

View file

@ -100,13 +100,19 @@ namespace TEN::Control::Volumes
{
if (volume->Status == TriggerStatus::Inside)
{
volume->Triggerer = nullptr;
volume->Status = TriggerStatus::Leaving;
if (!set->OnLeave.Function.empty() && set->OnLeave.CallCounter != 0)
// Only fire leave event when a certain timeout has passed.
// This helps to filter out borderline cases when moving around volumes.
if (GameTimer - volume->Timeout > VOLUME_LEAVE_TIMEOUT)
{
g_GameScript->ExecuteFunction(set->OnLeave.Function, triggerer, set->OnLeave.Argument);
if (set->OnLeave.CallCounter != NO_CALL_COUNTER)
set->OnLeave.CallCounter--;
volume->Triggerer = nullptr;
volume->Status = TriggerStatus::Leaving;
if (!set->OnLeave.Function.empty() && set->OnLeave.CallCounter != 0)
{
g_GameScript->ExecuteFunction(set->OnLeave.Function, triggerer, set->OnLeave.Argument);
if (set->OnLeave.CallCounter != NO_CALL_COUNTER)
set->OnLeave.CallCounter--;
}
}
}
else
@ -129,8 +135,7 @@ namespace TEN::Control::Volumes
void TestVolumes(short roomNumber, MESH_INFO* mesh)
{
auto* staticInfo = &StaticObjects[mesh->staticNumber];
auto bbox = TO_DX_BBOX(mesh->pos, &staticInfo->collisionBox);
auto bbox = TO_DX_BBOX(mesh->pos, GetBoundsAccurate(mesh, false));
TestVolumes(roomNumber, bbox, TriggerVolumeActivators::Static, mesh);
}

View file

@ -8,6 +8,7 @@ constexpr auto NO_EVENT_SET = -1;
constexpr auto NO_CALL_COUNTER = -1;
constexpr auto VOLUME_BUSY_TIMEOUT = 10;
constexpr auto VOLUME_LEAVE_TIMEOUT = 5;
enum class TriggerStatus
{

View file

@ -63,6 +63,7 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe
{
int meshIndex = 0;
short yRot = 0;
float scale;
Vector3 pos;
bool isStatic;
@ -72,6 +73,7 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe
meshIndex = StaticObjects[mesh->staticNumber].meshNumber;
yRot = mesh->pos.Orientation.y;
pos = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z);
scale = mesh->scale;
}
else
{
@ -79,6 +81,7 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe
meshIndex = item->meshIndex;
yRot = item->yRot;
pos = Vector3(item->sphere.x, item->sphere.y, item->sphere.z);
scale = 1.0f;
}
auto fragmentsMesh = &g_Level.Meshes[meshIndex];
@ -118,9 +121,9 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe
Matrix rotationMatrix = Matrix::CreateFromYawPitchRoll(TO_RAD(yRot), 0, 0);
Vector3 pos1 = fragmentsMesh->positions[poly->indices[indices[j * 3 + 0]]];
Vector3 pos2 = fragmentsMesh->positions[poly->indices[indices[j * 3 + 1]]];
Vector3 pos3 = fragmentsMesh->positions[poly->indices[indices[j * 3 + 2]]];
Vector3 pos1 = fragmentsMesh->positions[poly->indices[indices[j * 3 + 0]]] * scale;
Vector3 pos2 = fragmentsMesh->positions[poly->indices[indices[j * 3 + 1]]] * scale;
Vector3 pos3 = fragmentsMesh->positions[poly->indices[indices[j * 3 + 2]]] * scale;
Vector2 uv1 = poly->textureCoordinates[indices[j * 3 + 0]];
Vector2 uv2 = poly->textureCoordinates[indices[j * 3 + 1]];

View file

@ -1452,8 +1452,6 @@ void ExplodingDeath(short itemNumber, short flags)
obj = &Objects[ID_LARA_SKIN];
else
obj = &Objects[item->ObjectNumber];
ANIM_FRAME* frame = GetBestFrame(item);
Matrix world = Matrix::CreateFromYawPitchRoll(
TO_RAD(item->Pose.Orientation.y),

View file

@ -1,22 +1,23 @@
#pragma once
#include <variant>
#include <functional>
#include <cstddef>
#include <functional>
#include <stdexcept>
#include <variant>
#include "Game/itemdata/creature_info.h"
#include "Game/itemdata/door_data.h"
#include "Game/Lara/lara_struct.h"
#include "Objects/TR2/Vehicles/speedboat_info.h"
#include "Objects/TR2/Vehicles/skidoo_info.h"
#include "Objects/TR3/Vehicles/minecart_info.h"
#include "Objects/TR2/Vehicles/speedboat_info.h"
#include "Objects/TR3/Vehicles/big_gun_info.h"
#include "Objects/TR3/Vehicles/kayak_info.h"
#include "Objects/TR3/Vehicles/minecart_info.h"
#include "Objects/TR3/Vehicles/quad_bike_info.h"
#include "Objects/TR3/Vehicles/rubber_boat_info.h"
#include "Objects/TR3/Vehicles/upv_info.h"
#include "Objects/TR4/Entity/tr4_wraith_info.h"
#include "Objects/TR4/Vehicles/jeep_info.h"
#include "Objects/TR4/Vehicles/motorbike_info.h"
#include "Objects/TR4/Entity/tr4_wraith_info.h"
#include "Objects/TR5/Entity/tr5_laserhead_info.h"
#include "Objects/TR5/Object/tr5_pushableblock_info.h"
#include "Specific/phd_global.h"
@ -25,6 +26,7 @@ template<class... Ts> struct visitor : Ts... { using Ts::operator()...; };
template<class... Ts> visitor(Ts...)->visitor<Ts...>; // line not needed in C++20...
using namespace TEN::Entities::TR4;
using namespace TEN::Entities::TR5;
using namespace TEN::Entities::Vehicles;
struct ItemInfo;

View file

@ -17,65 +17,9 @@ using namespace TEN::Floordata;
using namespace TEN::Input;
using namespace TEN::Math::Random;
void ItemInfo::SetBits(JointBitType type, std::vector<int> jointIndices)
{
for (int i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = unsigned int(1) << jointIndices[i];
switch (type)
{
case JointBitType::Touch:
this->TouchBits |= jointBit;
break;
case JointBitType::Mesh:
this->MeshBits |= jointBit;
break;
case JointBitType::MeshSwap:
this->MeshSwapBits |= jointBit;
break;
}
}
}
void ItemInfo::SetBits(JointBitType type, int jointIndex)
{
return SetBits(type, std::vector{ jointIndex });
}
void ItemInfo::ClearBits(JointBitType type, std::vector<int> jointIndices)
{
for (int i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = unsigned int(1) << jointIndices[i];
switch (type)
{
case JointBitType::Touch:
this->TouchBits &= ~jointBit;
break;
case JointBitType::Mesh:
this->MeshBits &= ~jointBit;
break;
case JointBitType::MeshSwap:
this->MeshSwapBits &= ~jointBit;
break;
}
}
}
void ItemInfo::ClearBits(JointBitType type, int jointIndex)
{
return ClearBits(type, std::vector{ jointIndex });
}
bool ItemInfo::TestBits(JointBitType type, std::vector<int> jointIndices)
{
for (int i = 0; i < jointIndices.size(); i++)
for (size_t i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = unsigned int(1) << jointIndices[i];
@ -109,6 +53,93 @@ bool ItemInfo::TestBits(JointBitType type, int jointIndex)
return TestBits(type, std::vector{ jointIndex });
}
void ItemInfo::SetBits(JointBitType type, std::vector<int> jointIndices)
{
for (size_t i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = unsigned int(1) << jointIndices[i];
switch (type)
{
case JointBitType::Touch:
this->TouchBits |= jointBit;
break;
case JointBitType::Mesh:
this->MeshBits |= jointBit;
break;
case JointBitType::MeshSwap:
this->MeshSwapBits |= jointBit;
break;
}
}
}
void ItemInfo::SetBits(JointBitType type, int jointIndex)
{
return SetBits(type, std::vector{ jointIndex });
}
void ItemInfo::ClearBits(JointBitType type, std::vector<int> jointIndices)
{
for (size_t i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = unsigned int(1) << jointIndices[i];
switch (type)
{
case JointBitType::Touch:
this->TouchBits &= ~jointBit;
break;
case JointBitType::Mesh:
this->MeshBits &= ~jointBit;
break;
case JointBitType::MeshSwap:
this->MeshSwapBits &= ~jointBit;
break;
}
}
}
void ItemInfo::ClearBits(JointBitType type, int jointIndex)
{
return ClearBits(type, std::vector{ jointIndex });
}
bool ItemInfo::TestOcb(short ocbFlags)
{
return ((TriggerFlags & ocbFlags) == ocbFlags);
}
void ItemInfo::RemoveOcb(short ocbFlags)
{
TriggerFlags &= ~ocbFlags;
}
void ItemInfo::ClearAllOcb()
{
TriggerFlags = NULL;
}
bool ItemInfo::TestFlags(short id, short value)
{
if (id < 0 || id > 7)
return false;
return (ItemFlags[id] == value);
}
void ItemInfo::SetFlags(short id, short value)
{
if (id < 0 || id > 7)
return;
ItemFlags[id] = value;
}
bool ItemInfo::IsLara()
{
return this->Data.is<LaraInfo*>();
@ -122,8 +153,6 @@ bool ItemInfo::IsCreature()
void ClearItem(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* room = &g_Level.Rooms[item->RoomNumber];
item->Collidable = true;
item->Data = nullptr;
item->StartPose = item->Pose;
@ -330,11 +359,11 @@ void KillEffect(short fxNumber)
NextFxActive = fx->nextActive;
else
{
for (short linknum = NextFxActive; linknum != NO_ITEM; linknum = EffectList[linknum].nextActive)
for (short linkNumber = NextFxActive; linkNumber != NO_ITEM; linkNumber = EffectList[linkNumber].nextActive)
{
if (EffectList[linknum].nextActive == fxNumber)
if (EffectList[linkNumber].nextActive == fxNumber)
{
EffectList[linknum].nextActive = fx->nextActive;
EffectList[linkNumber].nextActive = fx->nextActive;
break;
}
}
@ -344,11 +373,11 @@ void KillEffect(short fxNumber)
g_Level.Rooms[fx->roomNumber].fxNumber = fx->nextFx;
else
{
for (short linknum = g_Level.Rooms[fx->roomNumber].fxNumber; linknum != NO_ITEM; linknum = EffectList[linknum].nextFx)
for (short linkNumber = g_Level.Rooms[fx->roomNumber].fxNumber; linkNumber != NO_ITEM; linkNumber = EffectList[linkNumber].nextFx)
{
if (EffectList[linknum].nextFx == fxNumber)
if (EffectList[linkNumber].nextFx == fxNumber)
{
EffectList[linknum].nextFx = fx->nextFx;
EffectList[linkNumber].nextFx = fx->nextFx;
break;
}
}
@ -359,7 +388,7 @@ void KillEffect(short fxNumber)
}
}
short CreateNewEffect(short roomNum)
short CreateNewEffect(short roomNumber)
{
short fxNumber = NextFxFree;
@ -368,8 +397,9 @@ short CreateNewEffect(short roomNum)
auto* fx = &EffectList[NextFxFree];
NextFxFree = fx->nextFx;
auto* room = &g_Level.Rooms[roomNum];
fx->roomNumber = roomNum;
auto* room = &g_Level.Rooms[roomNumber];
fx->roomNumber = roomNumber;
fx->nextFx = room->fxNumber;
room->fxNumber = fxNumber;
fx->nextActive = NextFxActive;
@ -456,9 +486,6 @@ void InitialiseItem(short itemNumber)
item->Animation.TargetState = g_Level.Anims[item->Animation.AnimNumber].ActiveState;
item->Animation.ActiveState = g_Level.Anims[item->Animation.AnimNumber].ActiveState;
item->Pose.Orientation.z = 0;
item->Pose.Orientation.x = 0;
item->Animation.Velocity.y = 0;
item->Animation.Velocity.z = 0;

View file

@ -114,12 +114,19 @@ struct ItemInfo
std::string LuaCallbackOnCollidedWithObjectName;
std::string LuaCallbackOnCollidedWithRoomName;
bool TestBits(JointBitType type, std::vector<int> jointIndices);
bool TestBits(JointBitType type, int jointIndex);
void SetBits(JointBitType type, std::vector<int> jointIndices);
void SetBits(JointBitType type, int jointIndex);
void ClearBits(JointBitType type, std::vector<int> jointIndices);
void ClearBits(JointBitType type, int jointIndex);
bool TestBits(JointBitType type, std::vector<int> jointIndices);
bool TestBits(JointBitType type, int jointIndex);
bool TestOcb(short ocbFlags);
void RemoveOcb(short ocbFlags);
void ClearAllOcb();
bool TestFlags(short id, short value);
void SetFlags(short id, short value);
bool IsLara();
bool IsCreature();

View file

@ -12,7 +12,7 @@ using std::vector;
CreatureInfo* GetCreatureInfo(ItemInfo* item)
{
return (CreatureInfo*)item->Data;
return static_cast<CreatureInfo*>(item->Data);
}
void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature)
@ -33,7 +33,7 @@ void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature)
int y = target->Pose.Position.y - item->Pose.Position.y;
int z = target->Pose.Position.z - item->Pose.Position.z;
int distance = pow(x, 2) + pow(y, 2) + pow(z, 2);
int distance = SQUARE(x) + SQUARE(y) + SQUARE(z);
if (distance < bestDistance)
{
creature->Enemy = target;

View file

@ -4,20 +4,22 @@
#include "Game/animation.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/collide_room.h"
#include "Game/effects/tomb4fx.h"
#include "Game/effects/effects.h"
#include "Game/effects/explosion.h"
#include "Game/effects/bubble.h"
#include "Game/Lara/lara.h"
#include "Game/items.h"
#include "Sound/sound.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#define SHARD_DAMAGE 30
#define ROCKET_DAMAGE 100
#define DIVER_HARPOON_DAMAGE 50
using namespace TEN::Effects::Explosion;
#define SHARD_VELOCITY 250
#define ROCKET_VELOCITY 220
#define NATLA_GUN_VELOCITY 400
#define MUTANT_SHARD_DAMAGE 30
#define MUTANT_BOMB_DAMAGE 100
#define DIVER_HARPOON_DAMAGE 50
#define KNIFE_DAMAGE 50
void ShootAtLara(FX_INFO *fx)
{
@ -33,91 +35,97 @@ void ShootAtLara(FX_INFO *fx)
fx->pos.Orientation.y = phd_atan(z, x);
// Random scatter (only a little bit else it's too hard to avoid).
fx->pos.Orientation.x += (GetRandomControl() - 0x4000) / 0x40;
fx->pos.Orientation.y += (GetRandomControl() - 0x4000) / 0x40;
fx->pos.Orientation.x += (GetRandomControl() - ANGLE(90.0f)) / 64;
fx->pos.Orientation.y += (GetRandomControl() - ANGLE(90.0f)) / 64;
}
void ControlMissile(short fxNumber)
{
auto* fx = &EffectList[fxNumber];
auto* fx = &EffectList[fxNumber]; // TODO: add fx->target (ItemInfo* target) to get the actual target (if it was really it)
auto isUnderwater = TestEnvironment(ENV_FLAG_WATER, fx->roomNumber);
auto soundFxType = isUnderwater ? SoundEnvironment::Water : SoundEnvironment::Land;
if (fx->objectNumber == ID_SCUBA_HARPOON && !TestEnvironment(ENV_FLAG_WATER, fx->roomNumber) && fx->pos.Orientation.x > -0x3000)
if (fx->objectNumber == ID_SCUBA_HARPOON && isUnderwater
&& fx->pos.Orientation.x > ANGLE(-67.5f))
{
fx->pos.Orientation.x -= ANGLE(1.0f);
}
fx->pos.Position.y += fx->speed * phd_sin(-fx->pos.Orientation.x);
int velocity = fx->speed * phd_cos(fx->pos.Orientation.x);
fx->pos.Position.z += velocity * phd_cos(fx->pos.Orientation.y);
fx->pos.Position.y += fx->speed * phd_sin(-fx->pos.Orientation.x);
fx->pos.Position.x += velocity * phd_sin(fx->pos.Orientation.y);
auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
auto hitLara = ItemNearLara(&fx->pos, 200);
// Check for hitting something.
if (fx->pos.Position.y >= probe.Position.Floor ||
fx->pos.Position.y <= probe.Position.Ceiling)
fx->pos.Position.y <= probe.Position.Ceiling ||
hitLara)
{
if (/*fx->objectNumber == KNIFE ||*/ fx->objectNumber == ID_SCUBA_HARPOON)
if (fx->objectNumber == ID_KNIFETHROWER_KNIFE ||
fx->objectNumber == ID_SCUBA_HARPOON ||
fx->objectNumber == ID_PROJ_SHARD)
{
// Change shard into ricochet.
// fx->speed = 0;
// fx->frameNumber = -GetRandomControl()/11000;
// fx->counter = 6;
// fx->objectNumber = RICOCHET1;
SoundEffect((fx->objectNumber == ID_SCUBA_HARPOON) ? SFX_TR4_WEAPON_RICOCHET : SFX_TR2_CIRCLE_BLADE_HIT, &fx->pos);
SoundEffect((fx->objectNumber == ID_SCUBA_HARPOON) ? SFX_TR4_WEAPON_RICOCHET : SFX_TR2_CIRCLE_BLADE_HIT, &fx->pos, soundFxType);
}
/*else if (fx->objectNumber == DRAGON_FIRE)
else if (fx->objectNumber == ID_PROJ_BOMB)
{
AddDynamicLight(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, 14, 11);
KillEffect(fx_number);
}*/
return;
SoundEffect(SFX_TR1_ATLANTEAN_EXPLODE, &fx->pos, soundFxType);
TriggerExplosionSparks(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, NULL, false, isUnderwater, fx->roomNumber);
}
if (hitLara)
{
if (fx->objectNumber == ID_KNIFETHROWER_KNIFE)
{
DoDamage(LaraItem, KNIFE_DAMAGE); // TODO: Make ControlMissile() not use LaraItem global. -- TokyoSU, 5/8/2022
KillEffect(fxNumber);
}
else if (fx->objectNumber == ID_SCUBA_HARPOON)
{
DoDamage(LaraItem, DIVER_HARPOON_DAMAGE); // TODO: Make ControlMissile() not use LaraItem global. -- TokyoSU, 5/8/2022
KillEffect(fxNumber);
}
else if (fx->objectNumber == ID_PROJ_BOMB)
{
DoDamage(LaraItem, MUTANT_BOMB_DAMAGE); // TODO: Make ControlMissile() not use LaraItem global. -- TokyoSU, 5/8/2022
KillEffect(fxNumber);
}
else if (fx->objectNumber == ID_PROJ_SHARD)
{
TriggerBlood(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, NULL, 10);
SoundEffect(SFX_TR4_BLOOD_LOOP, &fx->pos, soundFxType);
DoDamage(LaraItem, MUTANT_SHARD_DAMAGE);
KillEffect(fxNumber);
}
LaraItem->HitStatus = 1; // TODO: Make ControlMissile() not use LaraItem global. -- TokyoSU, 5/8/2022
fx->pos.Orientation.y = LaraItem->Pose.Orientation.y;
fx->speed = LaraItem->Animation.Velocity.z;
fx->frameNumber = fx->counter = 0;
}
}
if (probe.RoomNumber != fx->roomNumber)
EffectNewRoom(fxNumber, probe.RoomNumber);
// Check for hitting Lara.
/*if (fx->objectNumber == DRAGON_FIRE)
{
if (ItemNearLara(&fx->pos, 350))
{
DoDamage(LaraItem, 3);
LaraBurn(LaraItem);
return;
}
}*/
else if (ItemNearLara(&fx->pos, 200))
{
/*if (fx->objectNumber == KNIFE)
{
DoDamage(LaraItem, KNIFE_DAMAGE);
KillEffect(fx_number);
}
else*/ if (fx->objectNumber == ID_SCUBA_HARPOON)
{
DoDamage(LaraItem, DIVER_HARPOON_DAMAGE);
KillEffect(fxNumber);
}
if (fx->objectNumber == ID_KNIFETHROWER_KNIFE)
fx->pos.Orientation.z += ANGLE(3.0f); // update knife rotation overtime
LaraItem->HitStatus = 1;
fx->pos.Orientation.y = LaraItem->Pose.Orientation.y;
fx->speed = LaraItem->Animation.Velocity.z;
fx->frameNumber = fx->counter = 0;
switch (fx->objectNumber)
{
case ID_SCUBA_HARPOON:
if (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, fx->roomNumber))
CreateBubble(&fx->pos.Position, fx->roomNumber, 1, 0, 0, 0, 0, 0);
break;
case ID_PROJ_BOMB:
TriggerDynamicLight(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, 14, 180, 100, 0);
break;
}
// Create bubbles in wake of harpoon bolt.
//if (fx->objectNumber == ID_SCUBA_HARPOON && g_Level.Rooms[fx->roomNumber].flags & 1)
// CreateBubble(&fx->pos, fx->roomNumber, 1, 0);
/*else if (fx->objectNumber == DRAGON_FIRE && !fx->counter--)
{
AddDynamicLight(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, 14, 11);
SoundEffect(305, &fx->pos);
KillEffect(fx_number);
}
else if (fx->objectNumber == KNIFE)
fx->pos.Orientation.z += 30 * ONE_DEGREE;*/
}
void ControlNatlaGun(short fxNumber)
@ -175,7 +183,7 @@ short ShardGun(int x, int y, int z, short velocity, short yRot, short roomNumber
fx->roomNumber = roomNumber;
fx->pos.Orientation.x = fx->pos.Orientation.z = 0;
fx->pos.Orientation.y = yRot;
fx->speed = SHARD_VELOCITY;
fx->speed = velocity;
fx->frameNumber = 0;
fx->objectNumber = ID_PROJ_SHARD;
fx->color = Vector4::One;
@ -198,7 +206,7 @@ short BombGun(int x, int y, int z, short velocity, short yRot, short roomNumber)
fx->roomNumber = roomNumber;
fx->pos.Orientation.x = fx->pos.Orientation.z = 0;
fx->pos.Orientation.y = yRot;
fx->speed = ROCKET_VELOCITY;
fx->speed = velocity;
fx->frameNumber = 0;
fx->objectNumber = ID_PROJ_BOMB;
fx->color = Vector4::One;
@ -221,7 +229,7 @@ short NatlaGun(int x, int y, int z, short velocity, short yRot, short roomNumber
fx->roomNumber = roomNumber;
fx->pos.Orientation.x = fx->pos.Orientation.z = 0;
fx->pos.Orientation.y = yRot;
fx->speed = NATLA_GUN_VELOCITY;
fx->speed = velocity;
fx->frameNumber = 0;
fx->objectNumber = ID_PROJ_NATLA;
fx->color = Vector4::One;

View file

@ -6,5 +6,5 @@ void ControlMissile(short fxNumber);
void ControlNatlaGun(short fxNumber);
short ShardGun(int x, int y, int z, short velocity, short yRot, short roomNumber);
short BombGun(int x, int y, int z, short velocity, short yRot, short roomNumber); // RocketGun = BombGun
short BombGun(int x, int y, int z, short velocity, short yRot, short roomNumber);
short NatlaGun(int x, int y, int z, short velocity, short yRot, short roomNumber);

View file

@ -12,7 +12,7 @@
#include "Game/misc.h"
#include "Sound/sound.h"
bool ShotLara(ItemInfo* item, AI_INFO* AI, BITE_INFO* gun, short extraRotation, int damage)
bool ShotLara(ItemInfo* item, AI_INFO* AI, BiteInfo gun, short extraRotation, int damage)
{
auto* creature = GetCreatureInfo(item);
auto* enemy = creature->Enemy;
@ -42,12 +42,12 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BITE_INFO* gun, short extraRotation,
if (damage)
{
if (enemy == LaraItem)
if (enemy->IsLara())
{
if (hit)
{
DoDamage(enemy, damage);
CreatureEffect(item, gun, &GunHit);
DoDamage(LaraItem, damage);
}
else if (targetable)
CreatureEffect(item, gun, &GunMiss);
@ -64,8 +64,9 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BITE_INFO* gun, short extraRotation,
if (random > 14)
random = 0;
Vector3Int pos = { 0, 0, 0 };
auto pos = Vector3Int::Zero;
GetJointAbsPosition(enemy, &pos, random);
DoBloodSplat(pos.x, pos.y, pos.z, (GetRandomControl() & 3) + 4, enemy->Pose.Orientation.y, enemy->RoomNumber);
}
}
@ -78,21 +79,22 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BITE_INFO* gun, short extraRotation,
short GunMiss(int x, int y, int z, short velocity, short yRot, short roomNumber)
{
GameVector pos;
pos.x = LaraItem->Pose.Position.x + ((GetRandomControl() - 0x4000) << 9) / 0x7FFF;
pos.y = LaraItem->Floor;
pos.z = LaraItem->Pose.Position.z + ((GetRandomControl() - 0x4000) << 9) / 0x7FFF;
pos.roomNumber = LaraItem->RoomNumber;
auto pos = GameVector(
LaraItem->Pose.Position.x + ((GetRandomControl() - 0x4000) << 9) / 0x7FFF,
LaraItem->Floor,
LaraItem->Pose.Position.z + ((GetRandomControl() - 0x4000) << 9) / 0x7FFF,
LaraItem->RoomNumber
);
Richochet((PHD_3DPOS*)&pos);
return GunShot(x, y, z, velocity, yRot, roomNumber);
}
short GunHit(int x, int y, int z, short velocity, short yRot, short roomNumber)
{
Vector3Int pos = { 0, 0, 0 };
auto pos = Vector3Int::Zero;
GetLaraJointPosition(&pos, (25 * GetRandomControl()) >> 15);
DoBloodSplat(pos.x, pos.y, pos.z, (GetRandomControl() & 3) + 3, LaraItem->Pose.Orientation.y, LaraItem->RoomNumber);
return GunShot(x, y, z, velocity, yRot, roomNumber);
}
@ -102,59 +104,73 @@ short GunShot(int x, int y, int z, short velocity, short yRot, short roomNumber)
return -1;
}
bool Targetable(ItemInfo* item, AI_INFO* AI)
bool Targetable(ItemInfo* item, AI_INFO* AI)
{
// Discard it entity is not a creature (only creatures can use Targetable())
// or if the target is not visible.
if (!item->IsCreature() || !AI->ahead || AI->distance >= SQUARE(MAX_VISIBILITY_DISTANCE))
return false;
auto* creature = GetCreatureInfo(item);
auto* enemy = creature->Enemy;
if (enemy == NULL || enemy->HitPoints <= 0 || !AI->ahead || AI->distance >= pow(MAX_VISIBILITY_DISTANCE, 2))
if (creature->Enemy == nullptr)
return false;
if (!enemy->Data.is<CreatureInfo>() && !enemy->IsLara())
// Only Lara or a creature may be targeted.
if ((!enemy->IsCreature() && !enemy->IsLara()) || enemy->HitPoints <= 0)
return false;
auto* bounds = (BOUNDING_BOX*)GetBestFrame(item);
auto& bounds = GetBestFrame(item)->boundingBox;
auto& boundsTarget = GetBestFrame(enemy)->boundingBox;
GameVector start;
start.x = item->Pose.Position.x;
start.y = (item->ObjectNumber == ID_SNIPER) ? (item->Pose.Position.y - CLICK(3)) : (item->Pose.Position.y + ((bounds->Y2 + 3 * bounds->Y1) / 4));
start.z = item->Pose.Position.z;
start.roomNumber = item->RoomNumber;
bounds = (BOUNDING_BOX*)GetBestFrame(enemy);
GameVector target;
target.x = enemy->Pose.Position.x;
target.y = enemy->Pose.Position.y + ((bounds->Y2 + 3 * bounds->Y1) / 4);
target.z = enemy->Pose.Position.z;
return LOS(&start, &target);
auto origin = GameVector(
item->Pose.Position.x,
(item->ObjectNumber == ID_SNIPER) ? (item->Pose.Position.y - CLICK(3)) : (item->Pose.Position.y + ((bounds.Y2 + 3 * bounds.Y1) / 4)),
item->Pose.Position.z,
item->RoomNumber
);
auto target = GameVector(
enemy->Pose.Position.x,
enemy->Pose.Position.y + ((boundsTarget.Y2 + 3 * boundsTarget.Y1) / 4),
enemy->Pose.Position.z,
enemy->RoomNumber // TODO: Check why this line didn't exist in the first place. -- TokyoSU 2022.08.05
);
return LOS(&origin, &target);
}
bool TargetVisible(ItemInfo* item, AI_INFO* AI)
bool TargetVisible(ItemInfo* item, AI_INFO* AI, float maxAngle)
{
auto* creature = GetCreatureInfo(item);
auto* enemy = creature->Enemy;
if (!item->IsCreature() || AI->distance >= SQUARE(MAX_VISIBILITY_DISTANCE))
return false;
if (enemy != NULL)
// Check just in case.
auto* creatureInfo = GetCreatureInfo(item);
if (creatureInfo == nullptr)
return false;
auto* enemy = creatureInfo->Enemy;
if (enemy == nullptr || enemy->HitPoints == 0)
return false;
short angle = AI->angle - creatureInfo->JointRotation[2];
if (angle > -ANGLE(maxAngle) && angle < ANGLE(maxAngle))
{
short angle = AI->angle - creature->JointRotation[2];
if (enemy->HitPoints != 0 && angle > -ANGLE(45.0f) && angle < ANGLE(45.0f) && AI->distance < pow(MAX_VISIBILITY_DISTANCE, 2))
{
GameVector start;
start.x = item->Pose.Position.x;
start.y = item->Pose.Position.y - CLICK(3);
start.z = item->Pose.Position.z;
start.roomNumber = item->RoomNumber;
GameVector start;
GameVector target;
auto& bounds = GetBestFrame(enemy)->boundingBox;
GameVector target;
target.x = enemy->Pose.Position.x;
auto* bounds = (BOUNDING_BOX*)GetBestFrame(enemy);
target.y = enemy->Pose.Position.y + ((((bounds->Y1 * 2) + bounds->Y1) + bounds->Y2) / 4);
target.z = enemy->Pose.Position.z;
start.x = item->Pose.Position.x;
start.y = item->Pose.Position.y - CLICK(3);
start.z = item->Pose.Position.z;
start.roomNumber = item->RoomNumber;
return LOS(&start, &target);
}
target.x = enemy->Pose.Position.x;
target.y = enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4);
target.z = enemy->Pose.Position.z;
target.roomNumber = enemy->RoomNumber; // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022
return LOS(&start, &target);
}
return false;

View file

@ -3,9 +3,9 @@
constexpr auto MAX_VISIBILITY_DISTANCE = SECTOR(8);
bool ShotLara(ItemInfo* item, AI_INFO* AI, BITE_INFO* bite, short extraRotation, int damage);
bool ShotLara(ItemInfo* item, AI_INFO* AI, BiteInfo gun, short extraRotation, int damage);
short GunMiss(int x, int y, int z, short velocity, short yRot, short roomNumber);
short GunHit(int x, int y, int z, short velocity, short yRot, short roomNumber);
short GunShot(int x, int y, int z, short velocity, short yRot, short roomNumber);
bool Targetable(ItemInfo* item, AI_INFO* AI);
bool TargetVisible(ItemInfo* item, AI_INFO* AI);
bool TargetVisible(ItemInfo* item, AI_INFO* AI, float maxAngle = 45.0f);

View file

@ -825,36 +825,27 @@ BOUNDING_BOX* FindPlinth(ItemInfo* item)
{
auto* room = &g_Level.Rooms[item->RoomNumber];
int found = -1;
for (int i = 0; i < room->mesh.size(); i++)
{
auto* mesh = &room->mesh[i];
if (mesh->flags & StaticMeshFlags::SM_VISIBLE)
{
if (item->Pose.Position.x == mesh->pos.Position.x && item->Pose.Position.z == mesh->pos.Position.z)
{
auto* frame = (BOUNDING_BOX*)GetBestFrame(item);
auto* staticInfo = &StaticObjects[mesh->staticNumber];
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
continue;
if (frame->X1 <= staticInfo->collisionBox.X2 &&
frame->X2 >= staticInfo->collisionBox.X1 &&
frame->Z1 <= staticInfo->collisionBox.Z2 &&
frame->Z2 >= staticInfo->collisionBox.Z1 &&
(staticInfo->collisionBox.X1 || staticInfo->collisionBox.X2))
{
found = mesh->staticNumber;
break;
}
}
}
if (item->Pose.Position.x != mesh->pos.Position.x || item->Pose.Position.z != mesh->pos.Position.z)
continue;
auto* frame = (BOUNDING_BOX*)GetBestFrame(item);
auto* bbox = GetBoundsAccurate(mesh, false);
if (frame->X1 <= bbox->X2 && frame->X2 >= bbox->X1 &&
frame->Z1 <= bbox->Z2 && frame->Z2 >= bbox->Z1 &&
(bbox->X1 || bbox->X2))
return bbox;
}
if (found != -1)
return &StaticObjects[found].collisionBox;
if (room->itemNumber == NO_ITEM)
return NULL;
return nullptr;
short itemNumber = room->itemNumber;
for (itemNumber = room->itemNumber; itemNumber != NO_ITEM; itemNumber = g_Level.Items[itemNumber].NextItem)
@ -873,7 +864,7 @@ BOUNDING_BOX* FindPlinth(ItemInfo* item)
}
if (itemNumber == NO_ITEM)
return NULL;
return nullptr;
else
return (BOUNDING_BOX*)GetBestFrame(&g_Level.Items[itemNumber]);
}

View file

@ -8,8 +8,8 @@
#include "Game/items.h"
#include "Renderer/Renderer11.h"
using namespace TEN::Renderer;
using namespace TEN::Floordata;
using namespace TEN::Renderer;
byte FlipStatus = 0;
int FlipStats[MAX_FLIPMAP];
@ -142,7 +142,13 @@ int IsRoomOutside(int x, int y, int z)
if (y < probe.Position.Ceiling)
return NO_ROOM;
return ((room->flags & (ENV_FLAG_WIND | ENV_FLAG_WATER)) != 0 ? probe.RoomNumber : NO_ROOM);
if (TestEnvironmentFlags(ENV_FLAG_WATER, room->flags) ||
TestEnvironmentFlags(ENV_FLAG_WIND, room->flags))
{
return probe.RoomNumber;
}
return NO_ROOM;
}
}
@ -161,18 +167,28 @@ FloorInfo* GetSector(ROOM_INFO* room, int x, int z)
return &room->floor[index];
}
BOUNDING_BOX* GetBoundsAccurate(const MESH_INFO* mesh, bool visibility)
{
static BOUNDING_BOX result;
if (visibility)
result = StaticObjects[mesh->staticNumber].visibilityBox * mesh->scale;
else
result = StaticObjects[mesh->staticNumber].collisionBox * mesh->scale;
return &result;
}
bool IsPointInRoom(Vector3Int pos, int roomNumber)
{
int x = pos.x;
int y = pos.y;
int z = pos.z;
auto* room = &g_Level.Rooms[roomNumber];
int xSector = (x - room->x) / SECTOR(1);
int zSector = (z - room->z) / SECTOR(1);
if ((xSector >= 0 && xSector <= room->xSize - 1) &&
(zSector >= 0 && zSector <= room->zSize - 1) &&
(y <= room->minfloor && y >= room->maxceiling)) // up is negative y axis, hence y should be "less" than floor
int xSector = (pos.x - room->x) / SECTOR(1);
int zSector = (pos.z - room->z) / SECTOR(1);
if ((xSector >= 0 && xSector <= (room->xSize - 1)) &&
(zSector >= 0 && zSector <= (room->zSize - 1)) &&
(pos.y <= room->minfloor && pos.y >= room->maxceiling)) // Up is -y, hence y should be "less" than floor.
{
return true;
}
@ -192,39 +208,40 @@ int FindRoomNumber(Vector3Int position)
Vector3Int GetRoomCenter(int roomNumber)
{
auto* room = &g_Level.Rooms[roomNumber];
auto halfLength = SECTOR(room->xSize) / 2;
auto halfDepth = SECTOR(room->zSize) / 2;
auto halfHeight = (room->maxceiling - room->minfloor) / 2;
Vector3Int center;
center.x = room->x + halfLength;
center.y = room->minfloor + halfHeight;
center.z = room->z + halfDepth;
auto center = Vector3Int(
room->x + halfLength,
room->minfloor + halfHeight,
room->z + halfDepth
);
return center;
}
std::set<int> GetRoomList(int roomNumber)
{
std::set<int> result;
std::set<int> roomNumberList;
if (g_Level.Rooms.size() <= roomNumber)
return result;
return roomNumberList;
result.insert(roomNumber);
roomNumberList.insert(roomNumber);
auto* room = &g_Level.Rooms[roomNumber];
for (int i = 0; i < room->doors.size(); i++)
result.insert(room->doors[i].room);
for (size_t i = 0; i < room->doors.size(); i++)
roomNumberList.insert(room->doors[i].room);
for (auto i : result)
for (int roomNumber : roomNumberList)
{
room = &g_Level.Rooms[i];
for (int j = 0; j < room->doors.size(); j++)
result.insert(room->doors[j].room);
room = &g_Level.Rooms[roomNumber];
for (size_t j = 0; j < room->doors.size(); j++)
roomNumberList.insert(room->doors[j].room);
}
return result;
return roomNumberList;
}
void InitializeNeighborRoomList()
@ -235,8 +252,8 @@ void InitializeNeighborRoomList()
room->neighbors.clear();
auto roomList = GetRoomList(i);
for (int n : roomList)
room->neighbors.push_back(n);
auto roomNumberList = GetRoomList(i);
for (int roomNumber : roomNumberList)
room->neighbors.push_back(roomNumber);
}
}
}

View file

@ -42,6 +42,7 @@ struct ROOM_LIGHT
struct MESH_INFO
{
PHD_3DPOS pos;
float scale;
short staticNumber;
short flags;
Vector4 color;
@ -109,6 +110,9 @@ struct ROOM_INFO
short fxNumber;
bool boundActive;
std::string name;
std::vector<std::string> tags;
std::vector<FloorInfo> floor;
std::vector<ROOM_LIGHT> lights;
std::vector<MESH_INFO> mesh;
@ -138,10 +142,11 @@ void AddRoomFlipItems(ROOM_INFO* room);
void RemoveRoomFlipItems(ROOM_INFO* room);
bool IsObjectInRoom(short roomNumber, short objectNumber);
bool IsPointInRoom(Vector3Int pos, int roomNumber);
int FindRoomNumber(Vector3Int position);
int FindRoomNumber(Vector3Int pos);
Vector3Int GetRoomCenter(int roomNumber);
int IsRoomOutside(int x, int y, int z);
std::set<int> GetRoomList(int roomNumber);
void InitializeNeighborRoomList();
BOUNDING_BOX* GetBoundsAccurate(const MESH_INFO* mesh, bool visibility);
FloorInfo* GetSector(ROOM_INFO* room, int x, int z);

View file

@ -813,6 +813,7 @@ bool SaveGame::Save(int slot)
Save::StaticMeshInfoBuilder staticMesh{ fbb };
staticMesh.add_pose(&FromPHD(room->mesh[j].pos));
staticMesh.add_scale(room->mesh[j].scale);
staticMesh.add_color(&FromVector4(room->mesh[j].color));
staticMesh.add_flags(room->mesh[j].flags);
@ -1244,6 +1245,7 @@ bool SaveGame::Load(int slot)
int number = staticMesh->number();
room->mesh[number].pos = ToPHD(staticMesh->pose());
room->mesh[number].scale = staticMesh->scale();
room->mesh[number].color = ToVector4(staticMesh->color());
room->mesh[number].flags = staticMesh->flags();

View file

@ -70,9 +70,9 @@ namespace TEN::Entities::TR4
locust->pos.Orientation.y = (GetRandomControl() & 0x7FF) + angles.y - ANGLE(5.6f);
locust->roomNumber = item->RoomNumber;
locust->randomRotation = (GetRandomControl() & 0x1F) + 0x10;
locust->escapeYrot = (GetRandomControl() & 0x1FF);
locust->escapeXrot = ((GetRandomControl() & 0x7) + 0xF) * 20;
locust->counter = 0;
locust->escapeYrot = GetRandomControl() & 0x1FF;
locust->escapeXrot = (GetRandomControl() & 0x1F) + 16;
locust->counter = 20 * ((GetRandomControl() & 0x7) + 0xF);
}
}
@ -120,15 +120,10 @@ namespace TEN::Entities::TR4
if (locust->on)
{
// NOTE: not present in original TR4 code
//if (LaraItem == nullptr)
// LaraItem = LaraItem;
if ((Lara.Control.KeepLow || LaraItem->HitPoints <= 0) &&
locust->counter >= 90 &&
!(GetRandomControl() & 7))
{
//if (locust->target == nullptr)
// locust->target = LaraItem;
if ((Lara.Burn || LaraItem->HitPoints <= 0) && locust->counter >= 90 && !(GetRandomControl() & 7))
locust->counter = 90;
}
locust->counter--;
if (locust->counter == 0)
@ -149,7 +144,7 @@ namespace TEN::Entities::TR4
LaraItem->Pose.Position.y - locust->escapeYrot - locust->pos.Position.y,
LaraItem->Pose.Position.z + 8 * locust->escapeZrot - locust->pos.Position.z);
int distance = pow(LaraItem->Pose.Position.z - locust->pos.Position.z, 2) + pow(LaraItem->Pose.Position.x - locust->pos.Position.x, 2);
int distance = SQUARE(LaraItem->Pose.Position.z - locust->pos.Position.z) + SQUARE(LaraItem->Pose.Position.x - locust->pos.Position.x);
int square = int(sqrt(distance)) / 8;
if (square <= 128)
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Doors
{

View file

@ -1,16 +1,16 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ROOM_INFO;
struct DOORPOS_DATA;
struct DOOR_DATA;
struct ItemInfo;
struct ROOM_INFO;
namespace TEN::Entities::Doors
{
void InitialiseDoor(short itemNumber);
void DoorCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
void DoorControl(short itemNumber);
void OpenThatDoor(DOORPOS_DATA* doorPos, DOOR_DATA* dd);
void ShutThatDoor(DOORPOS_DATA* doorPos, DOOR_DATA* dd);
void OpenThatDoor(DOORPOS_DATA* doorPos, DOOR_DATA* door);
void ShutThatDoor(DOORPOS_DATA* doorPos, DOOR_DATA* door);
}

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Doors
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Doors
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Doors
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Doors
{

View file

@ -1,122 +1,157 @@
#include "framework.h"
#include "Specific/level.h"
#include "Game/control/control.h"
#include "Objects/Generic/Object/polerope.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/sphere.h"
#include "Game/control/box.h"
#include "Game/items.h"
#include "Game/control/control.h"
#include "Game/control/lot.h"
#include "Specific/input.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/Lara/lara_struct.h"
#include "Game/Lara/lara_tests.h"
#include "Game/Lara/lara.h"
#include "Specific/input.h"
#include "Specific/level.h"
#include "Specific/trmath.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/sphere.h"
using namespace TEN::Input;
using std::vector;
namespace TEN::Entities::Generic
{
Vector3Int PolePos = { 0, 0, -208 };
Vector3Int PolePosR = { 0, 0, 0 };
OBJECT_COLLISION_BOUNDS PoleBounds =
const vector<LaraState> VPoleMountedStates =
{
-256, 256,
LS_POLE_IDLE,
LS_POLE_UP,
LS_POLE_DOWN,
LS_POLE_TURN_CLOCKWISE,
LS_POLE_TURN_COUNTER_CLOCKWISE
};
const vector<LaraState> VPoleGroundedMountStates =
{
LS_IDLE,
LS_TURN_LEFT_SLOW,
LS_TURN_RIGHT_SLOW,
LS_TURN_LEFT_FAST,
LS_TURN_RIGHT_FAST,
LS_WALK_FORWARD,
LS_RUN_FORWARD
};
const vector<LaraState> VPoleAirborneMountStates =
{
LS_REACH,
LS_JUMP_UP
};
// TODO: These might be interfering with the SetPosition command. -- Sezz 2022.08.29
auto VPolePos = Vector3Int(0, 0, -208);
auto VPolePosR = Vector3Int::Zero;
OBJECT_COLLISION_BOUNDS VPoleBounds =
{
-CLICK(1), CLICK(1),
0, 0,
-512, 512,
-ANGLE(10.0f), ANGLE(10.0f),
-ANGLE(30.0f), ANGLE(30.0f),
-ANGLE(10.0f), ANGLE(10.0f)
-CLICK(2), CLICK(2),
ANGLE(-10.0f), ANGLE(10.0f),
ANGLE(-30.0f), ANGLE(30.0f),
ANGLE(-10.0f), ANGLE(10.0f)
};
void PoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
auto* laraInfo = GetLaraInfo(laraItem);
auto* poleItem = &g_Level.Items[itemNumber];
auto* lara = GetLaraInfo(laraItem);
bool isLara = !poleItem->IsLara();
bool isFacingPole = IsPointInFront(laraItem->Pose, poleItem->Pose.Position.ToVector3());
if (TrInput & IN_ACTION && isLara &&
laraInfo->Control.HandStatus == HandStatus::Free &&
laraItem->Animation.ActiveState == LS_IDLE &&
laraItem->Animation.AnimNumber == LA_STAND_IDLE || laraInfo->Control.IsMoving &&
laraInfo->InteractedItem == itemNumber)
// Mount while grounded.
if (TrInput & IN_ACTION && isFacingPole &&
CheckLaraState((LaraState)laraItem->Animation.ActiveState, VPoleGroundedMountStates) &&
lara->Control.HandStatus == HandStatus::Free ||
(lara->Control.IsMoving && lara->InteractedItem == itemNumber))
{
short rot = poleItem->Pose.Orientation.y;
// Temporarily reorient pole.
short yOrient = poleItem->Pose.Orientation.y;
poleItem->Pose.Orientation.y = laraItem->Pose.Orientation.y;
if (TestLaraPosition(&PoleBounds, poleItem, laraItem))
if (TestLaraPosition(&VPoleBounds, poleItem, laraItem))
{
if (MoveLaraPosition(&PolePos, poleItem, laraItem))
if (MoveLaraPosition(&VPolePos, poleItem, laraItem))
{
laraItem->Animation.AnimNumber = LA_STAND_TO_POLE;
laraItem->Animation.ActiveState = LS_POLE_IDLE;
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
laraInfo->Control.IsMoving = false;
laraInfo->Control.HandStatus = HandStatus::Busy;
SetAnimation(laraItem, LA_STAND_TO_POLE);
lara->Control.IsMoving = false;
lara->Control.HandStatus = HandStatus::Busy;
}
else
laraInfo->InteractedItem = itemNumber;
lara->InteractedItem = itemNumber;
poleItem->Pose.Orientation.y = rot;
poleItem->Pose.Orientation.y = yOrient;
}
else
{
if (laraInfo->Control.IsMoving && laraInfo->InteractedItem == itemNumber)
if (lara->Control.IsMoving && lara->InteractedItem == itemNumber)
{
laraInfo->Control.IsMoving = false;
laraInfo->Control.HandStatus = HandStatus::Free;
lara->Control.IsMoving = false;
lara->Control.HandStatus = HandStatus::Free;
}
poleItem->Pose.Orientation.y = rot;
poleItem->Pose.Orientation.y = yOrient;
}
return;
}
else if (TrInput & IN_ACTION && isLara &&
laraInfo->Control.HandStatus == HandStatus::Free &&
laraItem->Animation.IsAirborne &&
laraItem->Animation.Velocity.y > (int)laraInfo->Control.HandStatus && // ?????
laraItem->Animation.ActiveState == LS_REACH || laraItem->Animation.ActiveState == LS_JUMP_UP)
// Mount while airborne.
if (TrInput & IN_ACTION && isFacingPole &&
CheckLaraState((LaraState)laraItem->Animation.ActiveState, VPoleAirborneMountStates) &&
laraItem->Animation.IsAirborne &&
laraItem->Animation.Velocity.y > 0.0f &&
lara->Control.HandStatus == HandStatus::Free)
{
if (TestBoundsCollide(poleItem, laraItem, 100) &&
TestLaraPoleCollision(laraItem, coll, true, -CLICK(1)) &&
TestLaraPoleCollision(laraItem, coll, false))
// Test sphere collision.
if (!TestLaraPoleCollision(laraItem, coll, true, -CLICK(1)) || !TestLaraPoleCollision(laraItem, coll, false))
return;
// Test bounds collision.
if (TestBoundsCollide(poleItem, laraItem, LARA_RADIUS + (int)round(abs(laraItem->Animation.Velocity.z))))
{
if (TestCollision(poleItem, laraItem))
{
short rot = poleItem->Pose.Orientation.y;
// Temporarily reorient pole.
short yOrient = poleItem->Pose.Orientation.y;
poleItem->Pose.Orientation.y = laraItem->Pose.Orientation.y;
// Reaching.
if (laraItem->Animation.ActiveState == LS_REACH)
{
PolePosR.y = laraItem->Pose.Position.y - poleItem->Pose.Position.y + 10;
AlignLaraPosition(&PolePosR, poleItem, laraItem);
laraItem->Animation.AnimNumber = LA_REACH_TO_POLE;
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
VPolePosR.y = laraItem->Pose.Position.y - poleItem->Pose.Position.y + 10;
AlignLaraPosition(&VPolePosR, poleItem, laraItem);
SetAnimation(laraItem, LA_REACH_TO_POLE);
}
// Jumping up.
else
{
PolePosR.y = laraItem->Pose.Position.y - poleItem->Pose.Position.y + 66;
AlignLaraPosition(&PolePosR, poleItem, laraItem);
laraItem->Animation.AnimNumber = LA_JUMP_UP_TO_POLE;
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
VPolePosR.y = laraItem->Pose.Position.y - poleItem->Pose.Position.y + 66;
AlignLaraPosition(&VPolePosR, poleItem, laraItem);
SetAnimation(laraItem, LA_JUMP_UP_TO_POLE);
}
laraItem->Animation.ActiveState = LS_POLE_IDLE;
laraItem->Animation.Velocity.y = 0;
laraItem->Animation.IsAirborne = false;
laraInfo->Control.HandStatus = HandStatus::Busy;
poleItem->Pose.Orientation.y = rot;
laraItem->Animation.Velocity.y = 0.0f;
lara->Control.HandStatus = HandStatus::Busy;
poleItem->Pose.Orientation.y = yOrient;
}
}
return;
}
else
// Player is not interacting with vertical pole; do regular object collision.
if (!CheckLaraState((LaraState)laraItem->Animation.ActiveState, VPoleMountedStates) &&
laraItem->Animation.ActiveState != LS_JUMP_BACK)
{
if (!isLara || ((laraItem->Animation.ActiveState < LS_POLE_IDLE ||
laraItem->Animation.ActiveState > LS_POLE_TURN_COUNTER_CLOCKWISE) &&
laraItem->Animation.ActiveState != LS_JUMP_BACK))
{
ObjectCollision(itemNumber, laraItem, coll);
}
ObjectCollision(itemNumber, laraItem, coll);
}
}
}

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Generic
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
namespace TEN::Entities::Switches
{

View file

@ -1,12 +1,13 @@
#include "framework.h"
#include "Objects/Generic/Traps/dart_emitter.h"
#include "Specific/level.h"
#include "Game/collision/collide_room.h"
#include "Game/Lara/lara.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Sound/sound.h"
#include "Game/Lara/lara.h"
#include "Renderer/Renderer11Enums.h"
#include "Sound/sound.h"
#include "Specific/level.h"
namespace TEN::Entities::Traps
{
@ -228,4 +229,4 @@ namespace TEN::Entities::Traps
spark->dSize = size;
}
}
}

View file

@ -5,4 +5,4 @@ namespace TEN::Entities::Traps
void DartControl(short itemNumber);
void DartEmitterControl(short itemNumber);
void TriggerDartSmoke(int x, int y, int z, int xv, int zv, bool hit);
}
}

View file

@ -1,30 +1,32 @@
#include "framework.h"
#include "Game/effects/debris.h"
#include "Game/collision/collide_room.h"
#include "Game/camera.h"
#include "Objects/Generic/Traps/falling_block.h"
#include "Game/animation.h"
#include "Game/camera.h"
#include "Game/collision/collide_room.h"
#include "Game/collision/floordata.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "Game/effects/debris.h"
#include "Game/room.h"
#include "Sound/sound.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
using namespace TEN::Floordata;
using namespace TEN::Math::Random;
constexpr auto FALLINGBLOCK_INITIAL_SPEED = 10;
constexpr auto FALLINGBLOCK_MAX_SPEED = 100;
constexpr auto FALLINGBLOCK_FALL_VELOCITY = 4;
constexpr auto FALLINGBLOCK_INITIAL_SPEED = 10;
constexpr auto FALLINGBLOCK_MAX_SPEED = 100;
constexpr auto FALLINGBLOCK_FALL_VELOCITY = 4;
constexpr auto FALLINGBLOCK_FALL_ROTATION_SPEED = 1;
constexpr auto FALLINGBLOCK_DELAY = 52;
constexpr auto FALLINGBLOCK_WIBBLE = 3;
constexpr auto FALLINGBLOCK_HEIGHT_TOLERANCE = 8;
constexpr auto FALLINGBLOCK_CRUMBLE_DELAY = 100;
constexpr auto FALLINGBLOCK_DELAY = 52;
constexpr auto FALLINGBLOCK_WIBBLE = 3;
constexpr auto FALLINGBLOCK_HEIGHT_TOLERANCE = 8;
constexpr auto FALLINGBLOCK_CRUMBLE_DELAY = 100;
void InitialiseFallingBlock(short itemNumber)
{
auto item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
g_Level.Items[itemNumber].MeshBits = 1;
TEN::Floordata::UpdateBridgeItem(itemNumber);
@ -34,15 +36,17 @@ void InitialiseFallingBlock(short itemNumber)
item->Animation.Mutator[i].Rotation = Vector3::Zero;
}
void FallingBlockCollision(short itemNum, ItemInfo* l, CollisionInfo* coll)
void FallingBlockCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
ItemInfo* item = &g_Level.Items[itemNum];
if (!item->ItemFlags[0] && !item->TriggerFlags && abs(item->Pose.Position.y - l->Pose.Position.y) < FALLINGBLOCK_HEIGHT_TOLERANCE)
auto* item = &g_Level.Items[itemNumber];
if (!item->ItemFlags[0] && !item->TriggerFlags &&
abs(item->Pose.Position.y - laraItem->Pose.Position.y) < FALLINGBLOCK_HEIGHT_TOLERANCE)
{
if (!((item->Pose.Position.x ^ l->Pose.Position.x) & 0xFFFFFC00) && !((l->Pose.Position.z ^ item->Pose.Position.z) & 0xFFFFFC00))
if (!((item->Pose.Position.x ^ laraItem->Pose.Position.x) & 0xFFFFFC00) && !((laraItem->Pose.Position.z ^ item->Pose.Position.z) & 0xFFFFFC00))
{
SoundEffect(SFX_TR4_ROCK_FALL_CRUMBLE, &item->Pose);
AddActiveItem(itemNum);
AddActiveItem(itemNumber);
item->ItemFlags[0] = 0;
item->Status = ITEM_ACTIVE;
@ -53,7 +57,7 @@ void FallingBlockCollision(short itemNum, ItemInfo* l, CollisionInfo* coll)
void FallingBlockControl(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (item->TriggerFlags)
{
@ -65,7 +69,7 @@ void FallingBlockControl(short itemNumber)
{
if (item->ItemFlags[0] < FALLINGBLOCK_DELAY)
{
// Subtly shake all meshes separately
// Subtly shake all meshes separately.
for (int i = 0; i < item->Animation.Mutator.size(); i++)
{
item->Animation.Mutator[i].Rotation.x = RADIAN * GenerateFloat(-FALLINGBLOCK_WIBBLE, FALLINGBLOCK_WIBBLE);
@ -75,7 +79,7 @@ void FallingBlockControl(short itemNumber)
}
else
{
// Make rotational falling movement with some random seed
// Make rotational falling movement with some random seed.
for (int i = 0; i < item->Animation.Mutator.size(); i++)
{
auto rotSpeed = i % 2 ? FALLINGBLOCK_FALL_ROTATION_SPEED : -FALLINGBLOCK_FALL_ROTATION_SPEED;
@ -97,31 +101,31 @@ void FallingBlockControl(short itemNumber)
item->Pose.Position.y += item->ItemFlags[1];
}
if (GetDistanceToFloor(itemNumber) >= 0)
{
// If crumbled before actual delay (e.g. too low position), force delay to be correct
if (item->ItemFlags[0] < FALLINGBLOCK_DELAY)
item->ItemFlags[0] = FALLINGBLOCK_DELAY;
// Convert object to shatter item
ShatterItem.yRot = item->Pose.Orientation.y;
ShatterItem.meshIndex = Objects[item->ObjectNumber].meshIndex;
ShatterItem.color = item->Color;
ShatterItem.sphere.x = item->Pose.Position.x;
ShatterItem.sphere.y = item->Pose.Position.y - STEP_SIZE; // So debris won't spawn below floor
ShatterItem.sphere.z = item->Pose.Position.z;
ShatterItem.bit = 0;
ShatterImpactData.impactDirection = Vector3(0, -(float)item->ItemFlags[1] / (float)FALLINGBLOCK_MAX_SPEED, 0);
ShatterImpactData.impactLocation = { (float)ShatterItem.sphere.x, (float)ShatterItem.sphere.y, (float)ShatterItem.sphere.z };
ShatterObject(&ShatterItem, nullptr, 0, item->RoomNumber, false);
SoundEffect(SFX_TR4_ROCK_FALL_LAND, &item->Pose);
KillItem(itemNumber);
}
}
item->ItemFlags[0]++;
if (GetDistanceToFloor(itemNumber) >= 0)
{
// If crumbled before actual delay (e.g. too low position), force delay to be correct
if (item->ItemFlags[0] < FALLINGBLOCK_DELAY)
item->ItemFlags[0] = FALLINGBLOCK_DELAY;
// Convert object to shatter item
ShatterItem.yRot = item->Pose.Orientation.y;
ShatterItem.meshIndex = Objects[item->ObjectNumber].meshIndex;
ShatterItem.color = item->Color;
ShatterItem.sphere.x = item->Pose.Position.x;
ShatterItem.sphere.y = item->Pose.Position.y - STEP_SIZE; // So debris won't spawn below floor
ShatterItem.sphere.z = item->Pose.Position.z;
ShatterItem.bit = 0;
ShatterImpactData.impactDirection = Vector3(0, -(float)item->ItemFlags[1] / (float)FALLINGBLOCK_MAX_SPEED, 0);
ShatterImpactData.impactLocation = { (float)ShatterItem.sphere.x, (float)ShatterItem.sphere.y, (float)ShatterItem.sphere.z };
ShatterObject(&ShatterItem, nullptr, 0, item->RoomNumber, false);
SoundEffect(SFX_TR4_ROCK_FALL_LAND, &item->Pose);
KillItem(itemNumber);
}
}
else
{

View file

@ -4,9 +4,9 @@
struct CollisionInfo;
struct ItemInfo;
void FallingBlockCollision(short itemNum, ItemInfo* l, CollisionInfo* coll);
void FallingBlockCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
void FallingBlockControl(short itemNumber);
std::optional<int> FallingBlockFloor(short itemNumber, int x, int y, int z);
std::optional<int> FallingBlockCeiling(short itemNumber, int x, int y, int z);
int FallingBlockFloorBorder(short itemNumber);
int FallingBlockCeilingBorder(short itemNumber);
int FallingBlockCeilingBorder(short itemNumber);

View file

@ -461,6 +461,7 @@ void StartTraps()
object->saveFlags = true;
object->savePosition = true;
object->usingDrawAnimatingItem = true;
object->isPickup = true;
}
}

View file

@ -1,7 +1,7 @@
#pragma once
struct ItemInfo;
struct CollisionInfo;
struct ItemInfo;
// Puzzles
void PuzzleHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);

View file

@ -5,33 +5,35 @@
#include "Game/control/control.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Game/Lara/lara.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector;
namespace TEN::Entities::TR1
{
BITE_INFO ApeBite = { 0, -19, 75, 15 };
const vector<int> ApeAttackJoints = { 8, 9, 10, 11, 12, 13, 14, 15 };
constexpr auto APE_ATTACK_DAMAGE = 200;
constexpr auto APE_ATTACK_RANGE = SECTOR(0.42f);
constexpr auto APE_PANIC_RANGE = SECTOR(2);
constexpr auto APE_ATTACK_RANGE = SQUARE(SECTOR(0.42f));
constexpr auto APE_PANIC_RANGE = SQUARE(SECTOR(2));
constexpr auto APE_JUMP_CHANCE = 0xa0;
constexpr auto APE_POUND_CHEST_CHANCE = APE_JUMP_CHANCE + 0xA0;
constexpr auto APE_JUMP_CHANCE = 0xA0;
constexpr auto APE_POUND_CHEST_CHANCE = APE_JUMP_CHANCE + 0xA0;
constexpr auto APE_POUND_GROUND_CHANCE = APE_POUND_CHEST_CHANCE + 0xA0;
constexpr auto APE_RUN_LEFT_CHANCE = APE_POUND_GROUND_CHANCE + 0xA0;
constexpr auto APE_RUN_LEFT_CHANCE = APE_POUND_GROUND_CHANCE + 0xA0;
constexpr auto SHIFT = 75;
#define APE_RUN_TURN_RATE_MAX ANGLE(5.0f)
#define APE_DISPLAY_ANGLE ANGLE(45.0f)
#define APE_DISPLAY_ANGLE ANGLE(45.0f)
const auto ApeBite = BiteInfo(Vector3(0.0f, -19.0f, 75.0f), 15);
const vector<int> ApeAttackJoints = { 8, 9, 10, 11, 12, 13, 14, 15 };
enum ApeState
{
@ -73,10 +75,12 @@ namespace TEN::Entities::TR1
APE_ANIM_VAULT = 19
};
const std::array ApeDeathAnims = { APE_ANIM_DEATH_1, APE_ANIM_DEATH_2 };
enum ApeFlags
{
APE_FLAG_ATTACK = (1 << 0),
APE_FLAG_TURN_LEFT = (1 << 1),
APE_FLAG_ATTACK = (1 << 0),
APE_FLAG_TURN_LEFT = (1 << 1),
APE_FLAG_TURN_RIGHT = (1 << 2)
};
@ -88,25 +92,25 @@ namespace TEN::Entities::TR1
if (creature->Flags & APE_FLAG_TURN_LEFT)
{
item->Pose.Orientation.y -= ANGLE(90.0f);
creature->Flags -= APE_FLAG_TURN_LEFT;
creature->Flags &= ~APE_FLAG_TURN_LEFT;
}
else if (item->Flags & APE_FLAG_TURN_RIGHT)
{
item->Pose.Orientation.y += ANGLE(90.0f);
creature->Flags -= APE_FLAG_TURN_RIGHT;
creature->Flags &= ~APE_FLAG_TURN_RIGHT;
}
long long xx = item->Pose.Position.z / SECTOR(1);
long long yy = item->Pose.Position.x / SECTOR(1);
long long y = item->Pose.Position.y;
int xx = item->Pose.Position.z / SECTOR(1);
int yy = item->Pose.Position.x / SECTOR(1);
int y = item->Pose.Position.y;
CreatureAnimation(itemNumber, angle, 0);
if (item->Pose.Position.y > (y - CLICK(1.5f)))
return;
long long xFloor = item->Pose.Position.z / SECTOR(1);
long long yFloor = item->Pose.Position.x / SECTOR(1);
int xFloor = item->Pose.Position.z / SECTOR(1);
int yFloor = item->Pose.Position.x / SECTOR(1);
if (xx == xFloor)
{
if (yy == yFloor)
@ -141,17 +145,10 @@ namespace TEN::Entities::TR1
// diagonal
}
switch (CreatureVault(itemNumber, angle, 2, SHIFT))
if (CreatureVault(itemNumber, angle, 2, SHIFT) == 2)
{
case 2:
item->Pose.Position.y = y;
item->Animation.AnimNumber = Objects[ID_APE].animIndex + APE_ANIM_VAULT;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = APE_STATE_VAULT;
break;
default:
return;
SetAnimation(item, APE_ANIM_VAULT);
}
}
@ -169,11 +166,7 @@ namespace TEN::Entities::TR1
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != APE_STATE_DEATH)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + APE_ANIM_DEATH_1 + (short)(GetRandomControl() / 0x4000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = APE_STATE_DEATH;
}
SetAnimation(item, ApeDeathAnims[GenerateInt(0, ApeDeathAnims.size() - 1)]);
}
else
{
@ -183,12 +176,12 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false);
angle = CreatureTurn(item, creatureInfo->MaxTurn);
if (item->HitStatus || AI.distance < pow(APE_PANIC_RANGE, 2))
if (item->HitStatus || AI.distance < APE_PANIC_RANGE)
creatureInfo->Flags |= APE_FLAG_ATTACK;
short random;
@ -203,13 +196,13 @@ namespace TEN::Entities::TR1
}
else if (item->Flags & APE_FLAG_TURN_RIGHT)
{
item->Pose.Orientation.y += ANGLE(90);
item->Pose.Orientation.y += ANGLE(90.0f);
creatureInfo->Flags -= APE_FLAG_TURN_RIGHT;
}
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < pow(APE_ATTACK_RANGE, 2))
else if (AI.bite && AI.distance < APE_ATTACK_RANGE)
item->Animation.TargetState = APE_STATE_ATTACK;
else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) &&
AI.zoneNumber == AI.enemyZone && AI.ahead)
@ -240,7 +233,7 @@ namespace TEN::Entities::TR1
case APE_STATE_RUN_FORWARD:
creatureInfo->MaxTurn = APE_RUN_TURN_RATE_MAX;
if (creatureInfo->Flags == 0 &&
if (!creatureInfo->Flags &&
AI.angle > -APE_DISPLAY_ANGLE &&
AI.angle < APE_DISPLAY_ANGLE)
{
@ -297,9 +290,9 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, ApeAttackJoints))
{
CreatureEffect(item, &ApeBite, DoBloodSplat);
item->Animation.RequiredState = APE_STATE_IDLE;
DoDamage(creatureInfo->Enemy, APE_ATTACK_DAMAGE);
CreatureEffect(item, ApeBite, DoBloodSplat);
}
break;

View file

@ -15,25 +15,25 @@ using std::vector;
namespace TEN::Entities::TR1
{
BITE_INFO BearBite = { 0, 96, 335, 14 };
const vector<int> BearAttackJoints = { 2, 3, 5, 6, 14, 17 };
constexpr auto BEAR_RUN_DAMAGE = 3;
constexpr auto BEAR_RUN_DAMAGE = 3;
constexpr auto BEAR_ATTACK_DAMAGE = 200;
constexpr auto BEAR_SLAM_DAMAGE = 200;
constexpr auto BEAR_PAT_DAMAGE = 400;
constexpr auto BEAR_SLAM_DAMAGE = 200;
constexpr auto BEAR_PAT_DAMAGE = 400;
constexpr auto BEAR_ATTACK_RANGE = SECTOR(1);
constexpr auto BEAR_REAR_RANGE = SECTOR(2);
constexpr auto BEAR_ATTACK_RANGE = SECTOR(1);
constexpr auto BEAR_REAR_RANGE = SECTOR(2);
constexpr auto BEAR_REAR_SWIPE_ATTACK_RANGE = SECTOR(0.6f);
constexpr auto BEAR_EAT_RANGE = CLICK(3);
constexpr auto BEAR_EAT_RANGE = CLICK(3);
constexpr auto BEAR_ROAR_CHANCE = 0x50;
constexpr auto BEAR_REAR_CHANCE = 0x300;
constexpr auto BEAR_DROP_CHANCE = 0x600;
#define BEAR_WALK_TURN_RATE_MAX ANGLE(2.0f)
#define BEAR_RUN_TURN_RATE_MAX ANGLE(5.0f)
#define BEAR_RUN_TURN_RATE_MAX ANGLE(5.0f)
const auto BearBite = BiteInfo(Vector3(0.0f, 96.0f, 335.0f), 14);
const vector<int> BearAttackJoints = { 2, 3, 5, 6, 14, 17 };
enum BearState
{
@ -133,8 +133,8 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
@ -194,7 +194,9 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.ahead && !item->Animation.RequiredState)
{
if (!creature->Flags && AI.distance < pow(BEAR_REAR_RANGE, 2) && GetRandomControl() < BEAR_REAR_CHANCE)
if (AI.distance < pow(BEAR_REAR_RANGE, 2) &&
GetRandomControl() < BEAR_REAR_CHANCE &&
!creature->Flags)
{
item->Animation.RequiredState = BEAR_STATE_REAR;
item->Animation.TargetState = BEAR_STATE_IDLE;
@ -262,7 +264,7 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, BearAttackJoints))
{
CreatureEffect(item, &BearBite, DoBloodSplat);
CreatureEffect(item, BearBite, DoBloodSplat);
DoDamage(creature->Enemy, BEAR_ATTACK_DAMAGE);
item->Animation.RequiredState = BEAR_STATE_IDLE;
}

View file

@ -10,107 +10,100 @@
#include "Game/misc.h"
#include "Game/people.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector;
namespace TEN::Entities::TR1
{
BITE_INFO BigRatBite = { 0, -11, 108, 3 };
const vector<int> BigRatAttackJoints = { 0, 1, 2, 3, 7, 8, 24, 25 };
constexpr auto BIG_RAT_BITE_ATTACK_DAMAGE = 20;
constexpr auto BIG_RAT_POUNCE_ATTACK_DAMAGE = 25;
constexpr auto BIG_RAT_BITE_DAMAGE = 20;
constexpr auto BIG_RAT_CHARGE_DAMAGE = 25;
constexpr auto BIG_RAT_ALERT_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto BIG_RAT_VISIBILITY_RANGE = SQUARE(SECTOR(5));
constexpr auto BIG_RAT_LAND_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.34f));
constexpr auto BIG_RAT_POUNCE_ATTACK_RANGE = SQUARE(SECTOR(0.5f));
constexpr auto BIG_RAT_WATER_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.3f));
constexpr auto DEFAULT_SWIM_UPDOWN_SPEED = 32;
constexpr auto BIG_RAT_REAR_POSE_CHANCE = 0.008f;
constexpr auto BIG_RAT_SWIM_UP_DOWN_SPEED = 32;
constexpr auto BIG_RAT_WATER_SURFACE_OFFSET = 10;
constexpr auto BIG_RAT_ALERT_RANGE = SECTOR(1.5f);
constexpr auto BIG_RAT_VISIBILITY_RANGE = SECTOR(5);
constexpr auto BIG_RAT_BITE_RANGE = SECTOR(0.34f);
constexpr auto BIG_RAT_CHARGE_RANGE = SECTOR(0.5f);
constexpr auto BIG_RAT_WATER_BITE_RANGE = SECTOR(0.3f);
#define BIG_RAT_RUN_TURN_RATE_MAX ANGLE(6.0f)
#define BIG_RAT_SWIM_TURN_RATE_MAX ANGLE(3.0f)
constexpr auto BIG_RAT_POSE_CHANCE = 0x100;
#define BIG_RAT_RUN_TURN_ANGLE ANGLE(6.0f)
#define BIG_RAT_SWIM_TURN_ANGLE ANGLE(3.0f)
const auto BigRatBite = BiteInfo(Vector3(0.0f, -11.0f, 108.0f), 3);
enum BigRatState
{
BIG_RAT_STATE_EMPTY = 0,
BIG_RAT_STATE_NONE = 0,
BIG_RAT_STATE_IDLE = 1,
BIG_RAT_STATE_CHARGE_ATTACK = 2,
BIG_RAT_STATE_RUN = 3,
BIG_RAT_STATE_BITE_ATTACK = 4,
BIG_RAT_STATE_POUNCE_ATTACK = 2,
BIG_RAT_STATE_RUN_FORWARD = 3,
BIG_RAT_STATE_LAND_BITE_ATTACK = 4,
BIG_RAT_STATE_LAND_DEATH = 5,
BIG_RAT_STATE_POSE = 6,
BIG_RAT_STATE_REAR_POSE = 6,
BIG_RAT_STATE_SWIM = 7,
BIG_RAT_STATE_SWIM_ATTACK = 8,
BIG_RAT_STATE_SWIM_BITE_ATTACK = 8,
BIG_RAT_STATE_WATER_DEATH = 9
};
// TODO
enum BigRatAnim
{
BIG_RAT_ANIM_EMPTY = 0,
BIG_RAT_ANIM_STOP_TO_RUN = 1,
BIG_RAT_ANIM_RUN = 2,
BIG_RAT_ANIM_RUN_TO_STOP = 3,
BIG_RAT_ANIM_POSE = 4,
BIG_RAT_ANIM_POSE_TO_STOP = 5,
BIG_RAT_ANIM_IDLE = 0,
BIG_RAT_ANIM_IDLE_TO_RUN_FORWARD = 1,
BIG_RAT_ANIM_RUN_FORWARD = 2,
BIG_RAT_ANIM_RUN_FORWARD_TO_IDLE = 3,
BIG_RAT_ANIM_REAR_POSE = 4,
BIG_RAT_ANIM_REAR_POSE_TO_IDLE = 5,
BIG_RAT_ANIM_LAND_BITE_ATTACK = 6,
BIG_RAT_ANIM_CHARGE_ATTACK = 7,
BIG_RAT_ANIM_POUNCE_ATTACK = 7,
BIG_RAT_ANIM_LAND_DEATH = 8,
BIG_RAT_ANIM_SWIM = 9,
BIG_RAT_ANIM_WATER_BITE = 10,
BIG_RAT_ANIM_WATER_BITE_ATTACK = 10,
BIG_RAT_ANIM_WATER_DEATH = 11,
BIG_RAT_ANIM_RUN_TO_SWIM = 12,
BIG_RAT_ANIM_SWIM_TO_RUN = 13
// NOTE: These animations don't exist for the TR2 rat. -- TokyoSU 2022.08.10
BIG_RAT_ANIM_RUN_FORWARD_TO_SWIM = 12,
BIG_RAT_ANIM_SWIM_TO_RUN_FORWARD = 13
};
void InitialiseBigRat(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
InitialiseCreature(itemNumber);
if (TestEnvironment(ENV_FLAG_WATER, item))
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_SWIM;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_SWIM;
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
}
SetAnimation(item, BIG_RAT_ANIM_SWIM);
else
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_EMPTY;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_IDLE;
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
}
SetAnimation(item, BIG_RAT_ANIM_IDLE);
}
static bool RatIsInWater(ItemInfo* item)
int GetRatWaterHeight(ItemInfo* item)
{
auto* creature = GetCreatureInfo(item);
short roomNumber = item->RoomNumber;
GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber);
int waterDepth = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, roomNumber);
auto probe = GetCollision(item);
int waterDepth = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, probe.RoomNumber);
if (waterDepth != NO_HEIGHT)
{
creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED;
return true;
creature->LOT.Fly = BIG_RAT_SWIM_UP_DOWN_SPEED;
return waterDepth;
}
else
{
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
return false;
}
return NO_HEIGHT;
}
void BigRatControl(short itemNumber)
@ -120,34 +113,24 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
auto* objectInfo = &Objects[item->ObjectNumber];
int waterHeight = GetRatWaterHeight(item);
short head = 0;
short angle = 0;
int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
bool isOnWater = waterHeight != NO_HEIGHT;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH &&
item->Animation.ActiveState != BIG_RAT_STATE_WATER_DEATH)
{
if (TestEnvironment(ENV_FLAG_WATER, item))
{
item->Animation.AnimNumber = objectInfo->animIndex + BIG_RAT_ANIM_WATER_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_WATER_DEATH;
item->Animation.TargetState = BIG_RAT_STATE_WATER_DEATH;
}
if (isOnWater)
SetAnimation(item, BIG_RAT_ANIM_WATER_DEATH);
else
{
item->Animation.AnimNumber = objectInfo->animIndex + BIG_RAT_ANIM_LAND_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_LAND_DEATH;
item->Animation.TargetState = BIG_RAT_STATE_LAND_DEATH;
}
SetAnimation(item, BIG_RAT_ANIM_LAND_DEATH);
}
if (TestEnvironment(ENV_FLAG_WATER, item))
if (isOnWater)
CreatureFloat(itemNumber);
}
else
@ -158,110 +141,94 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false);
angle = CreatureTurn(item, creature->MaxTurn);
if (item->AIBits & ALL_AIOBJ)
GetAITarget(creature);
else if (creature->HurtByLara)
creature->Enemy = LaraItem;
if ((item->HitStatus || AI.distance < pow(BIG_RAT_ALERT_RANGE, 2)) ||
(TargetVisible(item, &AI) && AI.distance < pow(BIG_RAT_VISIBILITY_RANGE, 2)))
{
if (!creature->Alerted)
creature->Alerted = true;
AlertAllGuards(itemNumber);
}
switch (item->Animation.ActiveState)
{
case BIG_RAT_STATE_IDLE:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < pow(BIG_RAT_BITE_RANGE, 2))
item->Animation.TargetState = BIG_RAT_STATE_BITE_ATTACK;
else if (AI.bite && AI.distance < BIG_RAT_LAND_BITE_ATTACK_RANGE)
item->Animation.TargetState = BIG_RAT_STATE_LAND_BITE_ATTACK;
else
item->Animation.TargetState = BIG_RAT_STATE_RUN;
item->Animation.TargetState = BIG_RAT_STATE_RUN_FORWARD;
break;
case BIG_RAT_STATE_RUN:
creature->MaxTurn = BIG_RAT_RUN_TURN_ANGLE;
case BIG_RAT_STATE_RUN_FORWARD:
creature->MaxTurn = BIG_RAT_RUN_TURN_RATE_MAX;
if (RatIsInWater(item))
TENLog("WaterHeight: " + std::to_string(waterHeight));
if (isOnWater)
{
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
item->Animation.RequiredState = BIG_RAT_STATE_SWIM;
SetAnimation(item, BIG_RAT_ANIM_SWIM);
break;
}
if (AI.ahead && item->TestBits(JointBitType::Touch, BigRatAttackJoints))
if (AI.ahead && item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
else if (AI.bite && AI.distance < pow(BIG_RAT_CHARGE_RANGE, 2))
item->Animation.TargetState = BIG_RAT_STATE_CHARGE_ATTACK;
else if (AI.ahead && GetRandomControl() < BIG_RAT_POSE_CHANCE)
else if (AI.bite && AI.distance < BIG_RAT_POUNCE_ATTACK_RANGE)
item->Animation.TargetState = BIG_RAT_STATE_POUNCE_ATTACK;
else if (AI.ahead && TestProbability(BIG_RAT_REAR_POSE_CHANCE))
{
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
item->Animation.RequiredState = BIG_RAT_STATE_POSE;
item->Animation.RequiredState = BIG_RAT_STATE_REAR_POSE;
}
break;
case BIG_RAT_STATE_BITE_ATTACK:
case BIG_RAT_STATE_LAND_BITE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatAttackJoints))
item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
DoDamage(creature->Enemy, BIG_RAT_BITE_DAMAGE);
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
DoDamage(creature->Enemy, BIG_RAT_BITE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat);
}
break;
case BIG_RAT_STATE_CHARGE_ATTACK:
case BIG_RAT_STATE_POUNCE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatAttackJoints))
item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
DoDamage(creature->Enemy, BIG_RAT_CHARGE_DAMAGE);
item->Animation.RequiredState = BIG_RAT_STATE_RUN;
item->Animation.RequiredState = BIG_RAT_STATE_RUN_FORWARD;
DoDamage(creature->Enemy, BIG_RAT_POUNCE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat);
}
break;
case BIG_RAT_STATE_POSE:
if (creature->Mood != MoodType::Bored || GetRandomControl() < BIG_RAT_POSE_CHANCE)
case BIG_RAT_STATE_REAR_POSE:
if (creature->Mood != MoodType::Bored || TestProbability(BIG_RAT_REAR_POSE_CHANCE))
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
break;
case BIG_RAT_STATE_SWIM:
creature->MaxTurn = BIG_RAT_SWIM_TURN_ANGLE;
creature->MaxTurn = BIG_RAT_SWIM_TURN_RATE_MAX;
if (!RatIsInWater(item))
if (!isOnWater)
{
item->Animation.TargetState = BIG_RAT_STATE_RUN;
item->Animation.RequiredState = BIG_RAT_STATE_RUN;
SetAnimation(item, BIG_RAT_ANIM_RUN_FORWARD);
break;
}
if (AI.ahead && item->TestBits(JointBitType::Touch, BigRatAttackJoints))
item->Animation.TargetState = BIG_RAT_STATE_SWIM_ATTACK;
if (AI.ahead && item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
item->Animation.TargetState = BIG_RAT_STATE_SWIM_BITE_ATTACK;
break;
case BIG_RAT_STATE_SWIM_ATTACK:
case BIG_RAT_STATE_SWIM_BITE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatAttackJoints))
item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
DoDamage(creature->Enemy, BIG_RAT_BITE_DAMAGE);
DoDamage(creature->Enemy, BIG_RAT_BITE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat);
}
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
break;
}
@ -270,10 +237,10 @@ namespace TEN::Entities::TR1
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
if (RatIsInWater(item))
if (isOnWater)
{
CreatureUnderwater(item, 0);
item->Pose.Position.y = waterHeight;
item->Pose.Position.y = waterHeight - BIG_RAT_WATER_SURFACE_OFFSET;
}
else
item->Pose.Position.y = item->Floor;

View file

@ -11,6 +11,7 @@
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_one_gun.h"
#include "Game/misc.h"
#include "Game/missile.h"
#include "Game/people.h"
#include "Sound/sound.h"
#include "Specific/level.h"
@ -20,20 +21,17 @@ using std::vector;
namespace TEN::Entities::TR1
{
BITE_INFO CentaurRocketBite = { 11, 415, 41, 13 };
BITE_INFO CentaurRearBite = { 50, 30, 0, 5 };
constexpr auto CENTAUR_REAR_DAMAGE = 200;
constexpr auto CENTAUR_REAR_RANGE = SECTOR(1.5f);
constexpr auto CENTAUR_REAR_CHANCE = 0x60;
constexpr auto CENTAUR_BOMB_VELOCITY = 20;
#define CENTAUR_TURN_RATE_MAX ANGLE(4.0f)
const auto CentaurRocketBite = BiteInfo(Vector3(11.0f, 415.0f, 41.0f), 13);
const auto CentaurRearBite = BiteInfo(Vector3(50.0f, 30.0f, 0.0f), 5);
const vector<int> CentaurAttackJoints = { 0, 3, 4, 7, 8, 16, 17 };
constexpr auto CENTAUR_REAR_DAMAGE = 200;
constexpr auto CENTAUR_PROJECTILE_SPEED = CLICK(1);
constexpr auto CENTAUR_REAR_RANGE = SECTOR(1.5f);
constexpr auto CENTAUR_REAR_CHANCE = 0x60;
#define CENTAUR_TURN_ANGLE ANGLE(4.0f)
enum CentaurState
{
CENTAUR_STATE_NONE = 0,
@ -51,135 +49,13 @@ namespace TEN::Entities::TR1
CENTAUR_ANIM_DEATH = 8,
};
void ControlCentaurBomb(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
bool aboveWater = false;
auto oldPos = item->Pose.Position;
item->Pose.Orientation.z += ANGLE(35.0f);
if (!TestEnvironment(ENV_FLAG_WATER, item->RoomNumber))
{
item->Pose.Orientation.x -= ANGLE(1.0f);
if (item->Pose.Orientation.x < -ANGLE(90.0f))
item->Pose.Orientation.x = -ANGLE(90.0f);
item->Animation.Velocity.z = CENTAUR_PROJECTILE_SPEED * phd_cos(item->Pose.Orientation.x);
item->Animation.Velocity.y = -CENTAUR_PROJECTILE_SPEED * phd_sin(item->Pose.Orientation.x);
aboveWater = true;
}
else
{
item->Animation.Velocity.y += 3;
aboveWater = true;
if (item->Animation.Velocity.z)
{
item->Pose.Orientation.z += ((item->Animation.Velocity.z / 4) + 7) * ANGLE(1.0f);
if (item->Animation.RequiredState)
item->Pose.Orientation.y += ((item->Animation.Velocity.z / 2) + 7) * ANGLE(1.0f);
else
item->Pose.Orientation.x += ((item->Animation.Velocity.z / 2) + 7) * ANGLE(1.0f);
}
}
TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity.z);
auto probe = GetCollision(item);
if (probe.Position.Floor < item->Pose.Position.y ||
probe.Position.Ceiling > item->Pose.Position.y)
{
item->Pose.Position = oldPos;
if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber))
TriggerUnderwaterExplosion(item, 0);
else
{
item->Pose.Position.y -= CLICK(0.5f);
TriggerShockwave(&item->Pose, 48, 304, 96, 0, 96, 128, 24, 0, 0);
TriggerExplosionSparks(oldPos.x, oldPos.y, oldPos.z, 3, -2, 0, item->RoomNumber);
for (int x = 0; x < 2; x++)
TriggerExplosionSparks(oldPos.x, oldPos.y, oldPos.z, 3, -1, 0, item->RoomNumber);
}
return;
}
if (item->RoomNumber != probe.RoomNumber)
ItemNewRoom(itemNumber, probe.RoomNumber);
if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber) && aboveWater)
SetupRipple(item->Pose.Position.x, g_Level.Rooms[item->RoomNumber].minfloor, item->Pose.Position.z, (GetRandomControl() & 7) + 8, 0);
GetCollidedObjects(item, HARPOON_HIT_RADIUS, true, &CollidedItems[0], &CollidedMeshes[0], 0);
if (!CollidedItems[0] && !CollidedMeshes[0])
return;
if (CollidedItems[0])
{
auto* currentItem = CollidedItems[0];
int k = 0;
do
{
auto* currentObject = &Objects[currentItem->ObjectNumber];
if (currentItem->Status == ITEM_ACTIVE &&
currentObject->intelligent && !currentObject->undead &&
currentObject->collision)
{
DoExplosiveDamageOnBaddy(LaraItem, currentItem, item, LaraWeaponType::Crossbow);
}
k++;
currentItem = CollidedItems[k];
} while (currentItem);
}
}
static void RocketGun(ItemInfo* centaurItem)
{
short itemNumber;
itemNumber = CreateItem();
if (itemNumber != NO_ITEM)
{
auto* projectileItem = &g_Level.Items[itemNumber];
projectileItem->ObjectNumber = ID_PROJ_BOMB;
projectileItem->Color = Vector4(0.5f, 0.5f, 0.5f, 1.0f);
projectileItem->RoomNumber = centaurItem->RoomNumber;
auto pos = Vector3Int(11, 415, 41);
GetJointAbsPosition(centaurItem, &pos, 13);
projectileItem->Pose.Position = pos;
InitialiseItem(itemNumber);
projectileItem->Pose.Orientation = Vector3Shrt(0, centaurItem->Pose.Orientation.y, 0 );
projectileItem->Animation.Velocity.z = CENTAUR_PROJECTILE_SPEED * phd_cos(projectileItem->Pose.Orientation.x);
projectileItem->Animation.Velocity.y = -CENTAUR_PROJECTILE_SPEED * phd_cos(projectileItem->Pose.Orientation.x);
projectileItem->ItemFlags[0] = 1;
AddActiveItem(itemNumber);
}
}
void CentaurControl(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
@ -200,9 +76,9 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
CreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, CENTAUR_TURN_ANGLE);
angle = CreatureTurn(item, CENTAUR_TURN_RATE_MAX);
switch (item->Animation.ActiveState)
{
@ -252,7 +128,7 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState)
{
item->Animation.RequiredState = CENTAUR_STATE_AIM;
RocketGun(item);
CreatureEffect2(item, CentaurRocketBite, CENTAUR_BOMB_VELOCITY, head, BombGun);
}
break;
@ -261,7 +137,7 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, CentaurAttackJoints))
{
CreatureEffect(item, &CentaurRearBite, DoBloodSplat);
CreatureEffect(item, CentaurRearBite, DoBloodSplat);
DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE);
item->Animation.RequiredState = CENTAUR_STATE_IDLE;
}

View file

@ -2,6 +2,8 @@
namespace TEN::Entities::TR1
{
constexpr auto SHARD_VELOCITY = 250;
constexpr auto BOMB_VELOCITY = 220;
void CentaurControl(short itemNumber);
void ControlCentaurBomb(short itemNumber);
}

View file

@ -39,7 +39,7 @@ namespace TEN::Entities::TR1
if (!found)
itemNumber = NO_ITEM;
return (itemNumber == NO_ITEM ? NULL : &g_Level.Items[itemNumber]);
return (itemNumber == NO_ITEM ? nullptr : &g_Level.Items[itemNumber]);
}
static short GetWeaponDamage(LaraWeaponType weaponType)
@ -59,7 +59,7 @@ namespace TEN::Entities::TR1
auto* reference = FindReference(item, ID_BACON_REFERENCE);
if (item->Data == NULL)
if (!item->Data)
{
Vector3Int pos;
if (reference == nullptr)

View file

@ -14,21 +14,27 @@
#include "Specific/level.h"
#include "Specific/setup.h"
using std::vector;
namespace TEN::Entities::TR1
{
const std::vector<int> MutantAttackJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
const std::vector<int> MutantAttackLeftJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
const std::vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
constexpr auto MUTANT_ATTACK_DAMAGE = 500;
constexpr auto MUTANT_CONTACT_DAMAGE = 6;
constexpr auto MUTANT_ATTACK_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto MUTANT_CLOSE_RANGE = SQUARE(SECTOR(2.2f));
constexpr auto MUTANT_ATTACK_1_CHANCE = 0x2AF8;
constexpr auto MUTANT_ATTACK_2_CHANCE = 0x55F0;
#define MUTANT_NEED_TURN ANGLE(45.0f)
#define MUTANT_TURN ANGLE(3.0f)
#define MUTANT_ATTACK_RANGE pow(2600, 2)
#define MUTANT_CLOSE_RANGE pow(2250, 2)
#define MUTANT_ATTACK_1_CHANCE 11000
#define MUTANT_ATTACK_2_CHANCE 22000
#define MUTANT_ATTACK_DAMAGE 500
#define MUTANT_TOUCH_DAMAGE 5
#define LARA_GIANT_MUTANT_DEATH 6
#define LARA_GIANT_MUTANT_DEATH 6 // TODO: Not 13? Check this.
const vector<int> MutantAttackJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
const vector<int> MutantAttackLeftJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
const vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
enum GiantMutantState
{
@ -47,7 +53,7 @@ namespace TEN::Entities::TR1
};
// TODO
enum GianMutantAnim
enum GiantMutantAnim
{
MUTANT_ANIM_DEATH = 13,
};
@ -80,14 +86,14 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = (short)phd_atan(creature->Target.z - item->Pose.Position.z, creature->Target.x - item->Pose.Position.x) - item->Pose.Orientation.y;
if (item->TouchBits)
{
DoDamage(creature->Enemy, MUTANT_TOUCH_DAMAGE);
DoDamage(creature->Enemy, MUTANT_CONTACT_DAMAGE);
}
switch (item->Animation.ActiveState)

View file

@ -2,8 +2,8 @@
#include "Objects/TR1/Entity/tr1_natla.h"
#include "Game/control/box.h"
#include "Game/itemdata/creature_info.h"
#include "Game/effects/effects.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/missile.h"
@ -14,17 +14,21 @@
namespace TEN::Entities::TR1
{
BITE_INFO NatlaGunBite = { 5, 220, 7, 4 };
// TODO: Organise.
constexpr auto NATLA_SHOT_DAMAGE = 100;
constexpr auto NATLA_NEAR_DEATH = 200;
constexpr auto NATLA_DEATH_TIME = (FPS * 16); // 16 seconds.
constexpr auto NATLA_FLYMODE = 0x8000;
constexpr auto NATLA_TIMER = 0x7FFF;
constexpr auto NATLA_LAND_CHANCE = 0x100;
constexpr auto NATLA_GUN_VELOCITY = 400;
#define NATLA_NEAR_DEATH 200
#define NATLA_FLYMODE 0x8000
#define NATLA_TIMER 0x7fff
#define NATLA_FIRE_ARC ANGLE(30.0f)
#define NATLA_FLY_TURN ANGLE(5.0f)
#define NATLA_RUN_TURN ANGLE(6.0f)
#define NATLA_LAND_CHANCE 0x100
#define NATLA_DEATH_TIME (FPS * 16) // 16 seconds.
#define NATLA_SHOT_DAMAGE 100
#define NATLA_TURN_NEAR_DEATH_SPEED ANGLE(6.0f)
#define NATLA_TURN_SPEED ANGLE(5.0f)
#define NATLA_FLY_ANGLE_SPEED ANGLE(5.0f)
#define NATLA_SHOOT_ANGLE ANGLE(30.0f)
const auto NatlaGunBite = BiteInfo(Vector3(5.0f, 220.0f, 7.0f), 4);
enum NatlaState
{
@ -71,11 +75,11 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, NATLA_RUN_TURN);
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC&& Targetable(item, &AI));
angle = CreatureTurn(item, NATLA_TURN_NEAR_DEATH_SPEED);
shoot = (AI.angle > -NATLA_SHOOT_ANGLE && AI.angle < NATLA_SHOOT_ANGLE&& Targetable(item, &AI));
if (facing)
{
@ -107,7 +111,7 @@ namespace TEN::Entities::TR1
if (timer >= 20)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, ShardGun);
short FXNumber = CreatureEffect(item, NatlaGunBite, ShardGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
@ -125,7 +129,7 @@ namespace TEN::Entities::TR1
if (timer >= 20)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, ShardGun);
short FXNumber = CreatureEffect(item, NatlaGunBite, ShardGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
@ -175,7 +179,7 @@ namespace TEN::Entities::TR1
creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC&& Targetable(item, &AI));
shoot = (AI.angle > -NATLA_SHOOT_ANGLE && AI.angle < NATLA_SHOOT_ANGLE&& Targetable(item, &AI));
if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE))
{
@ -183,7 +187,7 @@ namespace TEN::Entities::TR1
creature->Flags -= NATLA_FLYMODE;
if (!(creature->Flags & NATLA_FLYMODE))
CreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, true);
creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20);
@ -198,17 +202,17 @@ namespace TEN::Entities::TR1
head = AI.angle;
if (item->Animation.ActiveState != NATLA_STATE_FLY || (creature->Flags & NATLA_FLYMODE))
CreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, false);
item->Pose.Orientation.y -= facing;
angle = CreatureTurn(item, NATLA_FLY_TURN);
angle = CreatureTurn(item, NATLA_TURN_SPEED);
if (item->Animation.ActiveState == NATLA_STATE_FLY)
{
if (AI.angle > NATLA_FLY_TURN)
facing += NATLA_FLY_TURN;
else if (AI.angle < -NATLA_FLY_TURN)
facing -= NATLA_FLY_TURN;
if (AI.angle > NATLA_FLY_ANGLE_SPEED)
facing += NATLA_FLY_ANGLE_SPEED;
else if (AI.angle < -NATLA_FLY_ANGLE_SPEED)
facing -= NATLA_FLY_ANGLE_SPEED;
else
facing += AI.angle;
@ -238,7 +242,7 @@ namespace TEN::Entities::TR1
if (timer >= 30)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
short FXNumber = CreatureEffect(item, NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
@ -264,15 +268,15 @@ namespace TEN::Entities::TR1
case NATLA_STATE_SHOOT:
if (!item->Animation.RequiredState)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
short FXNumber = CreatureEffect(item, NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
gun = EffectList[FXNumber].pos.Orientation.x;
FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
FXNumber = CreatureEffect(item, NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
EffectList[FXNumber].pos.Orientation.y += (short)((GetRandomControl() - 0x4000) / 4);
FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
FXNumber = CreatureEffect(item, NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
EffectList[FXNumber].pos.Orientation.y += (short)((GetRandomControl() - 0x4000) / 4);

View file

@ -0,0 +1,486 @@
#include "framework.h"
#include "Objects/TR1/Entity/tr1_winged_mutant.h"
#include "Game/collision/collide_room.h"
#include "Game/control/box.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/missile.h"
#include "Game/people.h"
#include "Sound/sound.h"
#include "Specific/level.h"
#include "Specific/trmath.h"
using std::vector;
namespace TEN::Entities::TR1
{
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150;
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100;
constexpr auto WINGED_MUTANT_SWIPE_ATTACK_DAMAGE = 200;
constexpr auto WINGED_MUTANT_WALK_RANGE = SQUARE(SECTOR(4.5f));
constexpr auto WINGED_MUTANT_SWIPE_ATTACK_RANGE = SQUARE(CLICK(1.17f));
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE = SQUARE(CLICK(2.34f));
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto WINGED_MUTANT_ATTACK_RANGE = SQUARE(SECTOR(3.75f));
constexpr auto WINGED_MUTANT_POSE_CHANCE = 85;
constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 200;
constexpr auto WINGED_MUTANT_FLY_VELOCITY = CLICK(1) / 8;
constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250;
constexpr auto WINGED_MUTANT_BOMB_VELOCITY = 220;
#define WINGED_MUTANT_WALK_FORWARD_TURN_RATE_MAX ANGLE(2.0f)
#define WINGED_MUTANT_RUN_FORWARD_TURN_RATE_MAX ANGLE(6.0f)
const auto WingedMutantBite = BiteInfo(Vector3(-27.0f, 98.0f, 0.0f), 10);
const auto WingedMutantRocketBite = BiteInfo(Vector3(51.0f, 213.0f, 0.0f), 14);
const auto WingedMutantShardBite = BiteInfo(Vector3(-35.0f, 269.0f, 0.0f), 9);
const vector<int> WingedMutantJoints = { WingedMutantShardBite.meshNum, WingedMutantBite.meshNum, WingedMutantRocketBite.meshNum };
enum WingedMutantState
{
WMUTANT_STATE_NONE = 0,
WMUTANT_STATE_IDLE = 1,
WMUTANT_STATE_WALK_FORWARD = 2,
WMUTANT_STATE_RUN_FORWARD = 3,
WMUTANT_STATE_IDLE_JUMP_ATTACK = 4,
WMUTANT_STATE_DEATH = 5,
WMUTANT_STATE_POSE = 6,
WMUTANT_STATE_RUN_JUMP_ATTACK = 7,
WMUTANT_STATE_SWIPE_ATTACK = 8,
WMUTANT_STATE_AIM_DART = 9,
WMUTANT_STATE_AIM_BOMB = 10,
WMUTANT_STATE_SHOOT = 11,
WMUTANT_STATE_INACTIVE = 12,
WMUTANT_STATE_FLY = 13,
};
enum WingedMutantAnim
{
WMUTANT_ANIM_INACTIVE = 0,
WMUTANT_ANIM_INACTIVE_TO_IDLE = 1,
WMUTANT_ANIM_IDLE = 2,
WMUTANT_ANIM_IDLE_TO_RUN = 3,
WMUTANT_ANIM_RUN_FORWARD = 4,
WMUTANT_ANIM_IDLE_JUMP_ATTACK_START = 5,
WMUTANT_ANIM_IDLE_JUMP_ATTACK_END = 6,
WMUTANT_ANIM_IDLE_TO_POSE = 7,
WMUTANT_ANIM_POSE = 8,
WMUTANT_ANIM_POSE_TO_IDLE = 9,
WMUTANT_ANIM_POSE_TO_WALK_FORWARD = 10,
WMUTANT_ANIM_WALK_FORWARD = 11,
WMUTANT_ANIM_WALK_FORWARD_TO_IDLE = 12,
WMUTANT_ANIM_WALK_FORWARD_TO_POSE = 13,
WMUTANT_ANIM_RUN_JUMP_ATTACK = 14,
WMUTANT_ANIM_IDLE_TO_AIM_1 = 15,
WMUTANT_ANIM_AIM_DART = 16,
WMUTANT_ANIM_SHOOT_DART = 17,
WMUTANT_ANIM_AIM_DART_TO_IDLE = 18,
WMUTANT_ANIM_IDLE_TO_AIM_BOMB = 19,
WMUTANT_ANIM_SHOOT_BOMB = 20,
WMUTANT_ANIM_RUN_FORWARD_TO_IDLE = 21,
WMUTANT_ANIM_AIM_BOMB_TO_IDLE = 22,
WMUTANT_ANIM_IDLE_TO_FLY = 23,
WMUTANT_ANIM_FLY = 24,
WMUTANT_ANIM_FLY_TO_IDLE = 25,
WMUTANT_ANIM_SWIPE_ATTACK = 26
};
enum WingedMutantPathFinding
{
WMUTANT_PATH_GROUND = 1,
WMUTANT_PATH_AERIAL = 2
};
// NOTE: Originally, winged mutants did not have OCBs. -- TokyoSU 5/8/2022
enum WingedMutantOcb
{
WMUTANT_OCB_START_AERIAL = (1 << 0),
WMUTANT_OCB_START_INACTIVE = (1 << 1),
WMUTANT_OCB_START_POSE = (1 << 2),
WMUTANT_OCB_NO_WINGS = (1 << 3),
WMUTANT_OCB_DISABLE_DART_WEAPON = (1 << 4),
WMUTANT_OCB_DISABLE_BOMB_WEAPON = (1 << 5)
};
enum WingedMutantProjectileType
{
WMUTANT_PROJ_NONE,
WMUTANT_PROJ_DART,
WMUTANT_PROJ_BOMB
};
enum WingedMutantConfig
{
WMUTANT_CONF_CAN_FLY,
WMUTANT_CONF_PATHFINDING_MODE,
WMUTANT_CONF_PROJECTILE_MODE,
WMUTANT_CONF_NO_WINGS,
WMUTANT_CONF_DISABLE_DART_WEAPON,
WMUTANT_CONF_DISABLE_BOMB_WEAPON
};
void SwitchPathfinding(CreatureInfo* creature, WingedMutantPathFinding path)
{
switch (path)
{
case WMUTANT_PATH_GROUND:
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
break;
case WMUTANT_PATH_AERIAL:
creature->LOT.Step = SECTOR(30);
creature->LOT.Drop = -SECTOR(30);
creature->LOT.Fly = WINGED_MUTANT_FLY_VELOCITY;
break;
}
}
WingedMutantProjectileType CanTargetLara(ItemInfo* item, CreatureInfo* creature, AI_INFO* AI)
{
if (Targetable(item, AI) && (AI->zoneNumber != AI->enemyZone || AI->distance > WINGED_MUTANT_ATTACK_RANGE))
{
if ((AI->angle > 0 && AI->angle < ANGLE(45.0f)) &&
item->TestFlags(WMUTANT_OCB_DISABLE_DART_WEAPON, false))
{
return WMUTANT_PROJ_DART;
}
else if ((AI->angle < 0 && AI->angle > -ANGLE(45.0f)) &&
item->TestFlags(WMUTANT_OCB_DISABLE_BOMB_WEAPON, false))
{
return WMUTANT_PROJ_BOMB;
}
}
// Cannot be targeted.
return WMUTANT_PROJ_NONE;
}
void WingedInitOCB(ItemInfo* item, CreatureInfo* creature)
{
if (item->TestOcb(WMUTANT_OCB_START_AERIAL))
{
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
SetAnimation(item, WMUTANT_ANIM_FLY);
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
}
else if (item->TestOcb(WMUTANT_OCB_START_INACTIVE))
{
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
else if (item->TestOcb(WMUTANT_OCB_START_POSE))
{
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
// Remove OCBs since we don't need them anymore.
if (item->TestOcb(WMUTANT_OCB_START_AERIAL))
item->RemoveOcb(WMUTANT_OCB_START_AERIAL);
if (item->TestOcb(WMUTANT_OCB_START_INACTIVE))
item->RemoveOcb(WMUTANT_OCB_START_INACTIVE);
if (item->TestOcb(WMUTANT_OCB_START_POSE))
item->RemoveOcb(WMUTANT_OCB_START_POSE);
}
// NOTE: Doesn't exist in the original game. -- TokyoSU 5/8/2022
void InitialiseWingedMutant(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
InitialiseCreature(itemNumber);
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
if (item->TestOcb(WMUTANT_OCB_NO_WINGS))
{
item->SetFlags(WMUTANT_CONF_CAN_FLY, false);
item->MeshBits = 0xFFE07FFF;
}
else
item->SetFlags(WMUTANT_CONF_CAN_FLY, true);
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
item->SetFlags(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true);
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
item->SetFlags(WMUTANT_CONF_DISABLE_DART_WEAPON, true);
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
item->RemoveOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON);
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
item->RemoveOcb(WMUTANT_OCB_DISABLE_DART_WEAPON);
if (item->TestOcb(WMUTANT_OCB_NO_WINGS))
item->RemoveOcb(WMUTANT_OCB_NO_WINGS);
}
void WingedMutantControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short torso = 0; // Only when shooting.
short angle = 0;
bool flyEnabled = item->TestFlags(WMUTANT_CONF_CAN_FLY, true);
bool flyStatus = item->TestFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
WingedInitOCB(item, creature);
if (item->HitPoints <= 0)
{
CreatureDie(itemNumber, true);
SoundEffect(SFX_TR1_ATLANTEAN_EXPLODE, &item->Pose);
return;
}
else
{
AI_INFO AI;
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
else
{
head = 0;
torso = 0;
}
GetCreatureMood(item, &AI, flyStatus);
CreatureMood(item, &AI, flyStatus);
angle = CreatureTurn(item, creature->MaxTurn);
auto shootType = CanTargetLara(item, creature, &AI);
if (flyEnabled)
{
if (item->Animation.ActiveState == WMUTANT_STATE_FLY)
{
if (flyStatus && creature->Mood != MoodType::Escape &&
AI.zoneNumber == AI.enemyZone)
{
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
CreatureAIInfo(item, &AI);
}
else if ((AI.zoneNumber != AI.enemyZone &&
!flyStatus && shootType == WMUTANT_PROJ_NONE &&
(!AI.ahead || creature->Mood == MoodType::Bored)) ||
creature->Mood == MoodType::Escape)
{
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
}
}
switch (item->Animation.ActiveState)
{
case WMUTANT_STATE_INACTIVE:
creature->MaxTurn = 0;
if (TargetVisible(item, &AI) || creature->HurtByLara)
item->Animation.TargetState = WMUTANT_STATE_IDLE;
break;
case WMUTANT_STATE_IDLE:
torso = 0;
creature->MaxTurn = 0;
item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE);
if (flyStatus && flyEnabled)
item->Animation.TargetState = WMUTANT_STATE_FLY;
else if (item->TestBits(JointBitType::Touch, WingedMutantJoints[1]))
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
else if (AI.bite && AI.distance < WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE)
item->Animation.TargetState = WMUTANT_STATE_IDLE_JUMP_ATTACK;
else if (AI.bite && AI.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
else if (shootType == WMUTANT_PROJ_DART)
item->Animation.TargetState = WMUTANT_STATE_AIM_DART;
else if (shootType == WMUTANT_PROJ_BOMB)
item->Animation.TargetState = WMUTANT_STATE_AIM_BOMB;
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && AI.distance < WINGED_MUTANT_WALK_RANGE))
{
item->Animation.TargetState = WMUTANT_STATE_POSE;
}
else
item->Animation.TargetState = WMUTANT_STATE_RUN_FORWARD;
break;
case WMUTANT_STATE_POSE:
head = 0; // Pose has an animation for the head.
creature->MaxTurn = 0;
if (shootType != WMUTANT_PROJ_NONE || (flyStatus && flyEnabled))
item->Animation.TargetState = WMUTANT_STATE_IDLE;
else if (creature->Mood == MoodType::Stalk)
{
if (AI.distance < WINGED_MUTANT_WALK_RANGE)
{
if (AI.zoneNumber == AI.enemyZone ||
GetRandomControl() < WINGED_MUTANT_UNPOSE_CHANCE)
{
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
}
}
else
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature->Mood == MoodType::Bored && GetRandomControl() < WINGED_MUTANT_UNPOSE_CHANCE)
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
else if (creature->Mood == MoodType::Attack ||
creature->Mood == MoodType::Escape)
item->Animation.TargetState = WMUTANT_STATE_IDLE;
break;
case WMUTANT_STATE_WALK_FORWARD:
creature->MaxTurn = WINGED_MUTANT_WALK_FORWARD_TURN_RATE_MAX;
if (shootType != WMUTANT_PROJ_NONE || (flyStatus && flyEnabled))
item->Animation.TargetState = WMUTANT_STATE_IDLE;
else if (creature->Mood == MoodType::Attack || creature->Mood == MoodType::Escape)
item->Animation.TargetState = WMUTANT_STATE_IDLE;
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && AI.zoneNumber != AI.enemyZone))
{
if (GetRandomControl() < WINGED_MUTANT_POSE_CHANCE)
item->Animation.TargetState = WMUTANT_STATE_POSE;
}
else if (creature->Mood == MoodType::Stalk &&
AI.distance > WINGED_MUTANT_WALK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_RUN_FORWARD:
creature->MaxTurn = WINGED_MUTANT_RUN_FORWARD_TURN_RATE_MAX;
if (flyStatus && flyEnabled)
item->Animation.TargetState = WMUTANT_STATE_IDLE;
else if (item->TestBits(JointBitType::Touch, WingedMutantJoints[1]))
item->Animation.TargetState = WMUTANT_STATE_IDLE;
else if (AI.bite && AI.distance < WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE)
item->Animation.TargetState = WMUTANT_STATE_RUN_JUMP_ATTACK;
else if (AI.bite && AI.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
else if (AI.ahead && AI.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
else if (shootType != WMUTANT_PROJ_NONE)
item->Animation.TargetState = WMUTANT_STATE_IDLE;
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && AI.distance < WINGED_MUTANT_WALK_RANGE))
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_IDLE_JUMP_ATTACK:
if (item->Animation.RequiredState == WMUTANT_STATE_NONE &&
item->TestBits(JointBitType::Touch, WingedMutantJoints[1]))
{
DoDamage(creature->Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE);
CreatureEffect(item, WingedMutantBite, DoBloodSplat);
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_RUN_JUMP_ATTACK:
if (item->Animation.RequiredState == WMUTANT_STATE_NONE &&
item->TestBits(JointBitType::Touch, WingedMutantJoints[1]))
{
DoDamage(creature->Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE);
CreatureEffect(item, WingedMutantBite, DoBloodSplat);
item->Animation.TargetState = WMUTANT_STATE_RUN_FORWARD;
}
break;
case WMUTANT_STATE_SWIPE_ATTACK:
if (item->Animation.RequiredState == WMUTANT_STATE_NONE &&
item->TestBits(JointBitType::Touch, WingedMutantJoints[1]))
{
DoDamage(creature->Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE);
CreatureEffect(item, WingedMutantBite, DoBloodSplat);
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_AIM_DART:
torso = AI.angle / 2;
creature->MaxTurn = 0;
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
if (shootType == WMUTANT_PROJ_DART)
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
else
item->Animation.TargetState = WMUTANT_STATE_IDLE;
break;
case WMUTANT_STATE_AIM_BOMB:
torso = AI.angle / 2;
creature->MaxTurn = 0;
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
if (shootType == WMUTANT_PROJ_BOMB)
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
else
item->Animation.TargetState = WMUTANT_STATE_IDLE;
break;
case WMUTANT_STATE_SHOOT:
{
torso = AI.angle / 2;
creature->MaxTurn = 0;
bool isDart = item->TestFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
bool isBomb = item->TestFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
if (isDart)
CreatureEffect2(item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torso, ShardGun);
else if (isBomb)
CreatureEffect2(item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torso, BombGun);
item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
break;
}
case WMUTANT_STATE_FLY:
if (!flyStatus && item->Pose.Position.y == item->Floor)
item->Animation.TargetState = WMUTANT_STATE_IDLE; // Switch to ground mode.
break;
}
}
CreatureJoint(item, 0, torso);
CreatureJoint(item, 1, head);
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

@ -0,0 +1,8 @@
#pragma once
namespace TEN::Entities::TR1
{
void InitialiseWingedMutant(short itemNumber);
void WingedMutantControl(short itemNumber);
}

View file

@ -15,24 +15,24 @@ using std::vector;
namespace TEN::Entities::TR1
{
BITE_INFO WolfBite = { 0, -14, 174, 6 };
const vector<int> WolfAttackJoints = { 0, 1, 2, 3, 6, 8, 9, 10, 12, 13, 14 };
constexpr auto WOLF_BITE_DAMAGE = 100;
constexpr auto WOLF_BITE_DAMAGE = 100;
constexpr auto WOLF_LUNGE_DAMAGE = 50;
constexpr auto WOLF_ATTACK_RANGE = SECTOR(1.5f);
constexpr auto WOLF_STALK_RANGE = SECTOR(2);
constexpr auto WOLF_ATTACK_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto WOLF_STALK_RANGE = SQUARE(SECTOR(2));
constexpr auto WOLF_WAKE_CHANCE = 0x20;
constexpr auto WOLF_WAKE_CHANCE = 0x20;
constexpr auto WOLF_SLEEP_CHANCE = 0x20;
constexpr auto WOLF_HOWL_CHANCE = 0x180;
constexpr auto WOLF_HOWL_CHANCE = 0x180;
constexpr auto WOLF_SLEEP_FRAME = 96;
#define WOLF_WALK_TURN_ANGLE ANGLE(2.0f)
#define WOLF_RUN_TURN_ANGLE ANGLE(5.0f)
#define WOLF_STALK_TURN_ANGLE ANGLE(2.0f)
#define WOLF_WALK_TURN_RATE_MAX ANGLE(2.0f)
#define WOLF_RUN_TURN_RATE_MAX ANGLE(5.0f)
#define WOLF_STALK_TURN_RATE_MAX ANGLE(2.0f)
const auto WolfBite = BiteInfo(Vector3(0.0f, -14.0f, 174.0f), 6);
const vector<int> WolfAttackJoints = { 0, 1, 2, 3, 6, 8, 9, 10, 12, 13, 14 };
enum WolfState
{
@ -94,8 +94,8 @@ namespace TEN::Entities::TR1
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false);
if (item->Animation.ActiveState != WOLF_STATE_SLEEP)
angle = CreatureTurn(item, creature->MaxTurn);
@ -126,7 +126,7 @@ namespace TEN::Entities::TR1
break;
case WOLF_STATE_WALK:
creature->MaxTurn = WOLF_WALK_TURN_ANGLE;
creature->MaxTurn = WOLF_WALK_TURN_RATE_MAX;
if (creature->Mood != MoodType::Bored)
{
@ -158,7 +158,7 @@ namespace TEN::Entities::TR1
break;
case WOLF_STATE_STALK:
creature->MaxTurn = WOLF_STALK_TURN_ANGLE;
creature->MaxTurn = WOLF_STALK_TURN_RATE_MAX;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = WOLF_STATE_RUN;
@ -185,12 +185,12 @@ namespace TEN::Entities::TR1
break;
case WOLF_STATE_RUN:
creature->MaxTurn = WOLF_RUN_TURN_ANGLE;
creature->MaxTurn = WOLF_RUN_TURN_RATE_MAX;
tilt = angle;
if (AI.ahead && AI.distance < pow(WOLF_ATTACK_RANGE, 2))
if (AI.ahead && AI.distance < WOLF_ATTACK_RANGE)
{
if (AI.distance > (pow(WOLF_ATTACK_RANGE, 2) / 2) &&
if (AI.distance > (WOLF_ATTACK_RANGE / 2) &&
(AI.enemyFacing > FRONT_ARC || AI.enemyFacing < -FRONT_ARC))
{
item->Animation.RequiredState = WOLF_STATE_STALK;
@ -202,7 +202,8 @@ namespace TEN::Entities::TR1
item->Animation.RequiredState = WOLF_STATE_NONE;
}
}
else if (creature->Mood == MoodType::Stalk && AI.distance < pow(WOLF_STALK_RANGE, 2))
else if (creature->Mood == MoodType::Stalk &&
AI.distance < WOLF_STALK_RANGE)
{
item->Animation.RequiredState = WOLF_STATE_STALK;
item->Animation.TargetState = WOLF_STATE_CROUCH;
@ -215,11 +216,12 @@ namespace TEN::Entities::TR1
case WOLF_STATE_ATTACK:
tilt = angle;
if (!item->Animation.RequiredState && item->TestBits(JointBitType::Touch, WolfAttackJoints))
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, WolfAttackJoints))
{
CreatureEffect(item, &WolfBite, DoBloodSplat);
DoDamage(creature->Enemy, WOLF_LUNGE_DAMAGE);
item->Animation.RequiredState = WOLF_STATE_RUN;
DoDamage(creature->Enemy, WOLF_LUNGE_DAMAGE);
CreatureEffect(item, WolfBite, DoBloodSplat);
}
item->Animation.TargetState = WOLF_STATE_RUN;
@ -229,9 +231,9 @@ namespace TEN::Entities::TR1
if (AI.ahead && !item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, WolfAttackJoints))
{
CreatureEffect(item, &WolfBite, DoBloodSplat);
DoDamage(creature->Enemy, WOLF_BITE_DAMAGE);
item->Animation.RequiredState = WOLF_STATE_CROUCH;
DoDamage(creature->Enemy, WOLF_BITE_DAMAGE);
CreatureEffect(item, WolfBite, DoBloodSplat);
}
break;

View file

@ -5,6 +5,7 @@
#include "Game/control/box.h"
#include "Game/collision/collide_item.h"
#include "Game/itemdata/creature_info.h"
#include "Game/missile.h"
#include "Specific/setup.h"
#include "Specific/level.h"
@ -17,6 +18,7 @@
#include "Objects/TR1/Entity/tr1_wolf.h" // OK
#include "Objects/TR1/Entity/tr1_big_rat.h" // OK
#include "Objects/TR1/Entity/tr1_centaur.h"
#include "Objects/TR1/Entity/tr1_winged_mutant.h"
#include "Objects/Utils/object_helper.h"
using namespace TEN::Entities::TR1;
@ -39,8 +41,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true;
obj->saveAnim = true;
obj->saveFlags = true;
g_Level.Bones[obj->boneIndex + 2 * 4] |= ROT_Y;
obj->SetBoneRotation(2, ROT_Y); // head
}
obj = &Objects[ID_BEAR];
@ -59,8 +60,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true;
obj->saveAnim = true;
obj->saveFlags = true;
g_Level.Bones[obj->boneIndex + 13 * 4] |= ROT_Y;
obj->SetBoneRotation(13, ROT_Y); // head
}
obj = &Objects[ID_APE];
@ -99,7 +99,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveFlags = true;
obj->waterCreature = true;
obj->zoneType = ZONE_WATER;
g_Level.Bones[obj->boneIndex + 4] |= ROT_Y;
obj->SetBoneRotation(1, ROT_Y); // head
}
obj = &Objects[ID_NATLA];
@ -107,17 +107,17 @@ static void StartEntity(ObjectInfo* obj)
{
obj->initialise = InitialiseCreature;
obj->collision = CreatureCollision;
obj->hitEffect = HIT_BLOOD;
obj->control = NatlaControl;
obj->shadowType = ShadowMode::All;
obj->HitPoints = 400;
obj->radius = 204;
obj->hitEffect = HIT_BLOOD;
obj->intelligent = true;
obj->saveAnim = true;
obj->saveFlags = true;
obj->savePosition = true;
obj->saveHitpoints = true;
g_Level.Bones[obj->boneIndex + 2 * 4] |= (ROT_Z | ROT_X);
obj->SetBoneRotation(2, ROT_X | ROT_Z);
}
obj = &Objects[ID_GIANT_MUTANT];
@ -135,7 +135,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveFlags = true;
obj->savePosition = true;
obj->saveHitpoints = true;
g_Level.Bones[obj->boneIndex + 1 * 4] |= ROT_Y;
obj->SetBoneRotation(1, ROT_Y);
}
obj = &Objects[ID_LARA_DOPPELGANGER];
@ -169,10 +169,36 @@ static void StartEntity(ObjectInfo* obj)
obj->hitEffect = HIT_BLOOD;
obj->pivotLength = 400;
obj->radius = WALL_SIZE / 3;
obj->intelligent = 1;
obj->savePosition = obj->saveHitpoints = obj->saveAnim = obj->saveFlags = 1;
g_Level.Bones[obj->boneIndex + 10 * 4] |= ROT_Y | ROT_X;
obj->intelligent = true;
obj->savePosition = true;
obj->saveHitpoints = true;
obj->saveAnim = true;
obj->saveFlags = true;
obj->zoneType = ZONE_BLOCKABLE;
obj->SetBoneRotation(10, ROT_X | ROT_Y);
}
obj = &Objects[ID_WINGED_MUMMY];
if (obj->loaded)
{
obj->initialise = InitialiseWingedMutant;
obj->control = WingedMutantControl;
obj->collision = CreatureCollision;
obj->shadowType = ShadowMode::All;
obj->hitEffect = HIT_BLOOD;
obj->pivotLength = 150;
obj->radius = WALL_SIZE / 3;
obj->HitPoints = 50;
obj->intelligent = true;
obj->saveAnim = true;
obj->saveFlags = true;
obj->saveHitpoints = true;
obj->savePosition = true;
obj->zoneType = ZONE_FLYER;
obj->SetBoneRotation(1, ROT_Y); // torso
obj->SetBoneRotation(2, ROT_Y); // head
}
}
static void StartObject(ObjectInfo* obj)
@ -193,7 +219,9 @@ static void StartTrap(ObjectInfo* obj)
static void StartProjectiles(ObjectInfo* obj)
{
InitProjectile(obj, ControlCentaurBomb, ID_PROJ_BOMB);
InitProjectile(obj, ControlMissile, ID_PROJ_SHARD);
InitProjectile(obj, ControlMissile, ID_PROJ_NATLA);
InitProjectile(obj, ControlMissile, ID_PROJ_BOMB);
}
static ObjectInfo* objToInit;

View file

@ -10,113 +10,147 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO BarracudaBite = { 2, -60, 121, 7 };
using std::vector;
// TODO
enum BarracudaState
namespace TEN::Entities::TR2
{
constexpr auto BARRACUDA_ATTACK_DAMAGE = 100;
constexpr auto BARRACUDA_IDLE_ATTACK_RANGE = SQUARE(SECTOR(0.67f));
constexpr auto BARRACUDA_SWIM_FAST_ATTACK_RANGE = SQUARE(SECTOR(0.34f));
};
const auto BarracudaBite = BiteInfo(Vector3(2.0f, -60.0f, 121.0f), 7);
const vector<int> BarracudaAttackJoints = { 5, 6, 7 };
// TODO
enum BarracudaAnim
{
};
void BarracudaControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
enum BarracudaState
{
if (item->Animation.ActiveState != 6)
{
item->Animation.AnimNumber = Objects[ID_BARRACUDA].animIndex + 6;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 6;
}
BARRACUDA_STATE_NONE = 0,
BARRACUDA_STATE_IDLE = 1,
BARRACUDA_STATE_SWIM_SLOW = 2,
BARRACUDA_STATE_SWIM_FAST = 3,
BARRACUDA_STATE_IDLE_ATTACK = 4,
BARRACUDA_STATE_SWIM_FAST_ATTACK = 5,
BARRACUDA_STATE_DEATH = 6,
};
CreatureFloat(itemNumber);
return;
}
else
enum BarracudaAnim
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
BARRACUDA_ANIM_SWIM_FAST_ATTACK_LEFT_END = 0,
BARRACUDA_ANIM_SWIM_FAST_ATTACK_RIGHT_END = 1,
BARRACUDA_ANIM_IDLE_ATTACK_END = 2,
BARRACUDA_ANIM_IDLE_ATTACK_CONTINUE = 3,
BARRACUDA_ANIM_SWIM_FAST_ATTACK_LEFT_CONTINUE = 4,
BARRACUDA_ANIM_SWIM_FAST_ATTACK_RIGHT_CONTINUE = 5,
BARRACUDA_ANIM_DEATH_START = 6,
BARRACUDA_ANIM_DEATH_END = 7,
BARRACUDA_ANIM_IDLE_ATTACK_START = 8,
BARRACUDA_ANIM_IDLE_TO_SWIM_SLOW = 9,
BARRACUDA_ANIM_IDLE_TO_SWIM_FAST = 10,
BARRACUDA_ANIM_IDLE = 11,
BARRACUDA_ANIM_SWIM_SLOW = 12,
BARRACUDA_ANIM_SWIM_FAST = 13,
BARRACUDA_ANIM_SWIM_SLOW_TO_IDLE = 13,
BARRACUDA_ANIM_SWIM_SLOW_TO_FAST = 14,
BARRACUDA_ANIM_SWIM_FAST_ATTACK_LEFT_START = 16,
BARRACUDA_ANIM_SWIM_FAST_ATTACK_RIGHT_START = 17,
BARRACUDA_ANIM_SWIM_FAST_TO_IDLE = 18,
BARRACUDA_ANIM_SWIM_FAST_TO_SLOW = 19
};
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
void BarracudaControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
angle = CreatureTurn(item, creature->MaxTurn);
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
switch (item->Animation.ActiveState)
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
{
case 1:
creature->Flags = 0;
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (AI.ahead && AI.distance < pow(680, 2))
item->Animation.TargetState = 4;
else if (creature->Mood == MoodType::Stalk)
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 3;
break;
case 2:
creature->MaxTurn = ANGLE(2.0f);
if (creature->Mood == MoodType::Bored)
break;
else if (AI.ahead && (item->TouchBits & 0xE0))
item->Animation.TargetState = 1;
else if (creature->Mood != MoodType::Stalk)
item->Animation.TargetState = 3;
break;
case 3:
creature->MaxTurn = ANGLE(4.0f);
creature->Flags = 0;
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (AI.ahead && AI.distance < pow(340, 2))
item->Animation.TargetState = 5;
else if (AI.ahead && AI.distance < pow(680, 2))
item->Animation.TargetState = 1;
else if (creature->Mood == MoodType::Stalk)
item->Animation.TargetState = 2;
break;
case 4:
case 5:
if (AI.ahead)
head = AI.angle;
if (!creature->Flags && (item->TouchBits & 0xE0))
if (item->Animation.ActiveState != BARRACUDA_STATE_DEATH)
{
CreatureEffect(item, &BarracudaBite, DoBloodSplat);
DoDamage(creature->Enemy, 100);
creature->Flags = 1;
item->Animation.AnimNumber = Objects[ID_BARRACUDA].animIndex + BARRACUDA_ANIM_DEATH_START;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BARRACUDA_STATE_DEATH;
}
break;
CreatureFloat(itemNumber);
return;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureUnderwater(item, CLICK(1));
GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case BARRACUDA_STATE_IDLE:
creature->Flags = 0;
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = BARRACUDA_STATE_SWIM_SLOW;
else if (AI.ahead && AI.distance < BARRACUDA_IDLE_ATTACK_RANGE)
item->Animation.TargetState = BARRACUDA_STATE_IDLE_ATTACK;
else if (creature->Mood == MoodType::Stalk)
item->Animation.TargetState = BARRACUDA_STATE_SWIM_SLOW;
else
item->Animation.TargetState = BARRACUDA_STATE_SWIM_FAST;
break;
case BARRACUDA_STATE_SWIM_SLOW:
creature->MaxTurn = ANGLE(2.0f);
if (creature->Mood == MoodType::Bored)
break;
else if (AI.ahead && item->TestBits(JointBitType::Touch, BarracudaAttackJoints))
item->Animation.TargetState = BARRACUDA_STATE_IDLE;
else if (creature->Mood != MoodType::Stalk)
item->Animation.TargetState = BARRACUDA_STATE_SWIM_FAST;
break;
case BARRACUDA_STATE_SWIM_FAST:
creature->MaxTurn = ANGLE(4.0f);
creature->Flags = 0;
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = BARRACUDA_STATE_SWIM_SLOW;
else if (AI.ahead && AI.distance < BARRACUDA_SWIM_FAST_ATTACK_RANGE)
item->Animation.TargetState = BARRACUDA_STATE_SWIM_FAST_ATTACK;
else if (AI.ahead && AI.distance < BARRACUDA_IDLE_ATTACK_RANGE)
item->Animation.TargetState = BARRACUDA_STATE_IDLE;
else if (creature->Mood == MoodType::Stalk)
item->Animation.TargetState = BARRACUDA_STATE_SWIM_SLOW;
break;
case BARRACUDA_STATE_IDLE_ATTACK:
case BARRACUDA_STATE_SWIM_FAST_ATTACK:
if (AI.ahead)
head = AI.angle;
if (item->TestBits(JointBitType::Touch, BarracudaAttackJoints) &&
!creature->Flags)
{
DoDamage(creature->Enemy, BARRACUDA_ATTACK_DAMAGE);
CreatureEffect(item, BarracudaBite, DoBloodSplat);
creature->Flags = 1;
}
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureUnderwater(item, CLICK(1));
}
}

View file

@ -1,3 +1,6 @@
#pragma once
void BarracudaControl(short itemNumber);
namespace TEN::Entities::TR2
{
void BarracudaControl(short itemNumber);
}

View file

@ -0,0 +1,206 @@
#include "framework.h"
#include "Objects/TR2/Entity/tr2_bird_monster.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector;
namespace TEN::Entities::TR2
{
constexpr auto BIRD_MONSTER_ATTACK_DAMAGE = 200;
constexpr auto BIRD_MONSTER_SLAM_CRUSH_ATTACK_RANGE = SQUARE(SECTOR(1));
constexpr auto BIRD_MONSTER_PUNCH_ATTACK_RANGE = SQUARE(SECTOR(2));
#define BIRD_MONSTER_WALK_TURN_RATE_MAX ANGLE(4.0f)
const auto BirdMonsterBiteLeft = BiteInfo(Vector3(0.0f, 224.0f, 0.0f), 19);
const auto BirdMonsterBiteRight = BiteInfo(Vector3(0.0f, 224.0f, 0.0f), 22);
const vector<int> BirdMonsterAttackLeftJoints = { 18, 19 };
const vector<int> BirdMonsterAttackRightJoints = { 21, 22 };
enum BirdMonsterState
{
BMONSTER_STATE_NONE = 0,
BMONSTER_STATE_IDLE = 1,
BMONSTER_STATE_WALK_FORWARD = 2,
BMONSTER_STATE_SLAM_ATTACK_START = 3,
BMONSTER_STATE_SLAM_ATTACK_CONTINUE = 4,
BMONSTER_STATE_PUNCH_ATTACK_START = 5,
BMONSTER_STATE_PUNCH_ATTACK_RIGHT_CONTINUE = 6,
BMONSTER_STATE_PUNCH_ATTACK_LEFT_CONTINUE = 7,
BMONSTER_STATE_ROAR = 8,
BMONSTER_STATE_DEATH = 9,
BMONSTER_STATE_CRUSH_ATTACK_START = 10,
BMONSTER_STATE_CRUSH_ATTACK_CONTINUE = 11
};
enum BirdMonsterAnim
{
BMONSTER_ANIM_IDLE = 0,
BMONSTER_ANIM_IDLE_TO_WALK_FORWARD = 1,
BMONSTER_ANIM_WALK_FORWARD = 2,
BMONSTER_ANIM_WALK_FORWARD_TO_IDLE_LEFT = 3,
BMONSTER_ANIM_WALK_FORWARD_TO_IDLE_RIGHT = 4,
BMONSTER_ANIM_SLAM_ATTACK_START = 5,
BMONSTER_ANIM_SLAM_ATTACK_CANCEL = 6,
BMONSTER_ANIM_SLAM_ATTACK_CONTINUE = 7,
BMONSTER_ANIM_SLAM_ATTACK_END = 8,
BMONSTER_ANIM_PUNCH_ATTACK_RIGHT_START = 9,
BMONSTER_ANIM_PUNCH_ATTACK_RIGHT_CANCEL = 10,
BMONSTER_ANIM_PUNCH_ATTACK_RIGHT_CONTINUE = 11,
BMONSTER_ANIM_PUNCH_ATTACK_RIGHT_END = 12,
BMONSTER_ANIM_PUNCH_ATTACK_LEFT_START = 13,
BMONSTER_ANIM_PUNCH_ATTACK_LEFT_CANCEL = 14,
BMONSTER_ANIM_PUNCH_ATTACK_LEFT_CONTINUE = 15,
BMONSTER_ANIM_PUNCH_ATTACK_LEFT_END = 16,
BMONSTER_ANIM_ROAR_START = 17,
BMONSTER_ANIM_ROAR_CONTINUE = 18,
BMONSTER_ANIM_ROAR_END = 19,
BMONSTER_ANIM_DEATH = 20,
BMONSTER_ANIM_CRUSH_ATTACK_START = 21,
BMONSTER_ANIM_CRUSH_ATTACK_CANCEL = 22,
BMONSTER_ANIM_CRUSH_ATTACK_CONTINUE = 23,
BMONSTER_ANIM_CRUSH_ATTACK_END = 24
};
void BirdMonsterControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != BMONSTER_STATE_DEATH)
SetAnimation(item, BMONSTER_ANIM_DEATH);
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case BMONSTER_STATE_IDLE:
creature->MaxTurn = 0;
if (AI.ahead && AI.distance < BIRD_MONSTER_SLAM_CRUSH_ATTACK_RANGE)
{
if (TestProbability(0.5f))
item->Animation.TargetState = BMONSTER_STATE_SLAM_ATTACK_START;
else
item->Animation.TargetState = BMONSTER_STATE_CRUSH_ATTACK_START;
}
else if (AI.ahead &&
(creature->Mood == MoodType::Bored || creature->Mood == MoodType::Stalk))
{
if (AI.zoneNumber != AI.enemyZone)
{
item->Animation.TargetState = BMONSTER_STATE_WALK_FORWARD;
creature->Mood = MoodType::Escape;
}
else
item->Animation.TargetState = BMONSTER_STATE_ROAR;
}
else
item->Animation.TargetState = BMONSTER_STATE_WALK_FORWARD;
break;
case BMONSTER_STATE_ROAR:
creature->MaxTurn = 0;
if (creature->Mood != MoodType::Bored || !AI.ahead)
item->Animation.TargetState = BMONSTER_STATE_IDLE;
break;
case BMONSTER_STATE_WALK_FORWARD:
creature->MaxTurn = BIRD_MONSTER_WALK_TURN_RATE_MAX;
if (AI.ahead && AI.distance < BIRD_MONSTER_PUNCH_ATTACK_RANGE)
item->Animation.TargetState = BMONSTER_STATE_PUNCH_ATTACK_START;
else if ((creature->Mood == MoodType::Bored || creature->Mood == MoodType::Stalk) && AI.ahead)
item->Animation.TargetState = BMONSTER_STATE_IDLE;
break;
case BMONSTER_STATE_SLAM_ATTACK_START:
creature->Flags = 0;
if (AI.ahead && AI.distance < BIRD_MONSTER_SLAM_CRUSH_ATTACK_RANGE)
item->Animation.TargetState = BMONSTER_STATE_SLAM_ATTACK_CONTINUE;
else
item->Animation.TargetState = BMONSTER_STATE_IDLE;
break;
case BMONSTER_STATE_PUNCH_ATTACK_START:
creature->Flags = 0;
if (AI.ahead && AI.distance < BIRD_MONSTER_PUNCH_ATTACK_RANGE)
item->Animation.TargetState = BMONSTER_STATE_PUNCH_ATTACK_RIGHT_CONTINUE;
else
item->Animation.TargetState = BMONSTER_STATE_IDLE;
break;
case BMONSTER_STATE_CRUSH_ATTACK_START:
creature->Flags = 0;
if (AI.ahead && AI.distance < BIRD_MONSTER_SLAM_CRUSH_ATTACK_RANGE)
item->Animation.TargetState = BMONSTER_STATE_CRUSH_ATTACK_CONTINUE;
else
item->Animation.TargetState = BMONSTER_STATE_IDLE;
break;
case BMONSTER_STATE_SLAM_ATTACK_CONTINUE:
case BMONSTER_STATE_PUNCH_ATTACK_RIGHT_CONTINUE:
case BMONSTER_STATE_CRUSH_ATTACK_CONTINUE:
case BMONSTER_STATE_PUNCH_ATTACK_LEFT_CONTINUE:
if (!(creature->Flags & 1) &&
item->TestBits(JointBitType::Touch, BirdMonsterAttackRightJoints))
{
DoDamage(creature->Enemy, BIRD_MONSTER_ATTACK_DAMAGE);
CreatureEffect(item, BirdMonsterBiteRight, DoBloodSplat);
creature->Flags |= 1;
}
if (!(creature->Flags & 2) &&
item->TestBits(JointBitType::Touch, BirdMonsterAttackLeftJoints))
{
DoDamage(creature->Enemy, BIRD_MONSTER_ATTACK_DAMAGE);
CreatureEffect(item, BirdMonsterBiteLeft, DoBloodSplat);
creature->Flags |= 2;
}
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

@ -0,0 +1,6 @@
#pragma once
namespace TEN::Entities::TR2
{
void BirdMonsterControl(short itemNumber);
}

View file

@ -1,159 +0,0 @@
#include "framework.h"
#include "Objects/TR2/Entity/tr2_birdmonster.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO BirdMonsterBiteLeft = { 0, 224, 0, 19 };
BITE_INFO BirdMonsterBiteRight = { 0, 224, 0, 22 };
// TODO
enum BirdMonsterState
{
};
// TODO
enum BirdMonsterAnim
{
};
void BirdMonsterControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != 9)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 20;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 9;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case 1:
creature->MaxTurn = 0;
if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
{
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 3;
else
item->Animation.TargetState = 10;
}
else if (AI.ahead && (creature->Mood == MoodType::Bored || creature->Mood == MoodType::Stalk))
{
if (AI.zoneNumber != AI.enemyZone)
{
item->Animation.TargetState = 2;
creature->Mood = MoodType::Escape;
}
else
item->Animation.TargetState = 8;
}
else
item->Animation.TargetState = 2;
break;
case 8:
creature->MaxTurn = 0;
if (creature->Mood != MoodType::Bored || !AI.ahead)
item->Animation.TargetState = 1;
break;
case 2:
creature->MaxTurn = ANGLE(4.0f);
if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 5;
else if ((creature->Mood == MoodType::Bored || creature->Mood == MoodType::Stalk) && AI.ahead)
item->Animation.TargetState = 1;
break;
case 3:
creature->Flags = 0;
if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
item->Animation.TargetState = 4;
else
item->Animation.TargetState = 1;
break;
case 5:
creature->Flags = 0;
if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 6;
else
item->Animation.TargetState = 1;
break;
case 10:
creature->Flags = 0;
if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
item->Animation.TargetState = 11;
else
item->Animation.TargetState = 1;
break;
case 4:
case 6:
case 11:
case 7:
if (!(creature->Flags & 1) && item->TouchBits & 0x600000)
{
CreatureEffect(item, &BirdMonsterBiteRight, DoBloodSplat);
DoDamage(creature->Enemy, 200);
creature->Flags |= 1;
}
if (!(creature->Flags & 2) && item->TouchBits & 0x0C0000)
{
CreatureEffect(item, &BirdMonsterBiteLeft, DoBloodSplat);
DoDamage(creature->Enemy, 200);
creature->Flags |= 2;
}
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
}

View file

@ -1,3 +0,0 @@
#pragma once
void BirdMonsterControl(short itemNumber);

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,10 @@
#include "Game/collision/collide_room.h"
#include "Game/items.h"
void DragonCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
void DragonControl(short backNumber);
void InitialiseBartoli(short itemNumber);
void BartoliControl(short itemNumber);
namespace TEN::Entities::TR2
{
void DragonCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
void DragonControl(short backNumber);
void InitialiseBartoli(short itemNumber);
void BartoliControl(short itemNumber);
}

View file

@ -10,155 +10,158 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO EagleBite = { 15, 46, 21, 6 };
BITE_INFO CrowBite = { 2, 10, 60, 14 };
// TODO
enum EagleState
namespace TEN::Entities::TR2
{
const auto EagleBite = BiteInfo(Vector3(15.0f, 46.0f, 21.0f), 6);
const auto CrowBite = BiteInfo(Vector3(2.0f, 10.0f, 60.0f), 14);
};
// TODO
enum EagleAnim
{
};
void InitialiseEagle(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
ClearItem(itemNumber);
if (item->ObjectNumber == ID_CROW)
// TODO
enum EagleState
{
item->Animation.AnimNumber = Objects[ID_CROW].animIndex + 14;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = item->Animation.TargetState = 7;
}
else
};
// TODO
enum EagleAnim
{
item->Animation.AnimNumber = Objects[ID_EAGLE].animIndex + 5;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = item->Animation.TargetState = 2;
}
}
void EagleControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
};
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
if (item->HitPoints <= 0)
void InitialiseEagle(short itemNumber)
{
switch (item->Animation.ActiveState)
auto* item = &g_Level.Items[itemNumber];
ClearItem(itemNumber);
if (item->ObjectNumber == ID_CROW)
{
case 4:
if (item->Pose.Position.y > item->Floor)
{
item->Pose.Position.y = item->Floor;
item->Animation.Velocity.y = 0;
item->Animation.IsAirborne = false;
item->Animation.TargetState = 5;
}
break;
case 5:
item->Pose.Position.y = item->Floor;
break;
default:
if (item->ObjectNumber == ID_CROW)
item->Animation.AnimNumber = Objects[ID_CROW].animIndex + 1;
else
item->Animation.AnimNumber = Objects[ID_EAGLE].animIndex + 8;
item->Animation.AnimNumber = Objects[ID_CROW].animIndex + 14;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 4;
item->Animation.Velocity.z = 0;
item->Animation.IsAirborne = true;
break;
item->Animation.ActiveState = item->Animation.TargetState = 7;
}
item->Pose.Orientation.x = 0;
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, ANGLE(3.0f));
switch (item->Animation.ActiveState)
else
{
case 7:
item->Pose.Position.y = item->Floor;
if (creature->Mood != MoodType::Bored)
item->Animation.TargetState = 1;
break;
case 2:
item->Pose.Position.y = item->Floor;
if (creature->Mood == MoodType::Bored)
break;
else
item->Animation.TargetState = 1;
break;
case 1:
creature->Flags = 0;
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
item->Animation.TargetState = 6;
else
item->Animation.TargetState = 3;
break;
case 3:
if (creature->Mood == MoodType::Bored)
{
item->Animation.RequiredState = 2;
item->Animation.TargetState = 1;
}
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
item->Animation.TargetState = 6;
break;
case 6:
if (!creature->Flags && item->TouchBits)
{
DoDamage(creature->Enemy, 20);
if (item->ObjectNumber == ID_CROW)
CreatureEffect(item, &CrowBite, DoBloodSplat);
else
CreatureEffect(item, &EagleBite, DoBloodSplat);
creature->Flags = 1;
}
break;
item->Animation.AnimNumber = Objects[ID_EAGLE].animIndex + 5;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = item->Animation.TargetState = 2;
}
}
CreatureAnimation(itemNumber, angle, 0);
void EagleControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
if (item->HitPoints <= 0)
{
switch (item->Animation.ActiveState)
{
case 4:
if (item->Pose.Position.y > item->Floor)
{
item->Pose.Position.y = item->Floor;
item->Animation.Velocity.y = 0;
item->Animation.IsAirborne = false;
item->Animation.TargetState = 5;
}
break;
case 5:
item->Pose.Position.y = item->Floor;
break;
default:
if (item->ObjectNumber == ID_CROW)
item->Animation.AnimNumber = Objects[ID_CROW].animIndex + 1;
else
item->Animation.AnimNumber = Objects[ID_EAGLE].animIndex + 8;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 4;
item->Animation.Velocity.z = 0;
item->Animation.IsAirborne = true;
break;
}
item->Pose.Orientation.x = 0;
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, false);
angle = CreatureTurn(item, ANGLE(3.0f));
switch (item->Animation.ActiveState)
{
case 7:
item->Pose.Position.y = item->Floor;
if (creature->Mood != MoodType::Bored)
item->Animation.TargetState = 1;
break;
case 2:
item->Pose.Position.y = item->Floor;
if (creature->Mood == MoodType::Bored)
break;
else
item->Animation.TargetState = 1;
break;
case 1:
creature->Flags = 0;
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
item->Animation.TargetState = 6;
else
item->Animation.TargetState = 3;
break;
case 3:
if (creature->Mood == MoodType::Bored)
{
item->Animation.RequiredState = 2;
item->Animation.TargetState = 1;
}
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
item->Animation.TargetState = 6;
break;
case 6:
if (!creature->Flags && item->TouchBits)
{
DoDamage(creature->Enemy, 20);
if (item->ObjectNumber == ID_CROW)
CreatureEffect(item, CrowBite, DoBloodSplat);
else
CreatureEffect(item, EagleBite, DoBloodSplat);
creature->Flags = 1;
}
break;
}
}
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

@ -1,4 +1,7 @@
#pragma once
void InitialiseEagle(short itemNumber);
void EagleControl(short itemNumber);
namespace TEN::Entities::TR2
{
void InitialiseEagle(short itemNumber);
void EagleControl(short itemNumber);
}

View file

@ -0,0 +1,305 @@
#include "framework.h"
#include "Objects/TR2/Entity/tr2_knife_thrower.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/collide_room.h"
#include "Game/collision/floordata.h"
#include "Game/control/box.h"
#include "Game/effects/effects.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Sound/sound.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
using namespace TEN::Math::Random;
namespace TEN::Entities::TR2
{
constexpr auto KNIFE_PROJECTILE_DAMAGE = 50;
// TODO: Ranges.
const auto KnifeBiteLeft = BiteInfo(Vector3::Zero, 5);
const auto KnifeBiteRight = BiteInfo(Vector3::Zero, 8);
enum KnifeThrowerState
{
KTHROWER_STATE_NONE = 0,
KTHROWER_STATE_IDLE = 1,
KTHROWER_STATE_WALK_FORWARD = 2,
KTHROWER_STATE_RUN_FORWARD = 3,
KTHROWER_STATE_WALK_KNIFE_ATTACK_LEFT_START = 4,
KTHROWER_STATE_WALK_KNIFE_ATTACK_LEFT_CONTINUE = 5,
KTHROWER_STATE_WALK_KNIFE_ATTACK_RIGHT_START = 6,
KTHROWER_STATE_WALK_KNIFE_ATTACK_RIGHT_CONTINUE = 7,
KTHROWER_STATE_IDLE_KNIFE_ATTACK_START = 8,
KTHROWER_STATE_IDLE_KNIFE_ATTACK_CONTINUE = 9,
KTHROWER_STATE_DEATH = 10
};
enum KnifeThrowerAnim
{
KTHROWER_ANIM_IDLE = 0,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_LEFT_START = 1,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_LEFT_CANCEL = 2,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_RIGHT_START = 3,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_RIGHT_CANCEL = 4,
KTHROWER_ANIM_IDLE_KNIFE_ATTACK_START = 5,
KTHROWER_ANIM_RUN_FORWARD_TO_IDLE = 6,
KTHROWER_ANIM_RUN_FORWARD_TO_WALK_FORWARD = 7,
KTHROWER_ANIM_RUN_FORWARD = 8,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_LEFT_CONTINUE = 9,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_RIGHT_CONTINUE = 10,
KTHROWER_ANIM_IDLE_KNIFE_ATTACK_CONTINUE = 11,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_LEFT_END = 12,
KTHROWER_ANIM_IDLE_KNIFE_ATTACK_END_TO_IDLE = 13,
KTHROWER_ANIM_IDLE_KNIFE_ATTACK_END_TO_START = 14,
KTHROWER_ANIM_IDLE_TO_RUN_FORWARD = 15,
KTHROWER_ANIM_IDLE_TO_WALK_FORWARD = 16,
KTHROWER_ANIM_WALK_FORWARD = 17,
KTHROWER_ANIM_WALK_FORWARD_TO_RUN_FORWARD = 18,
KTHROWER_ANIM_WALK_FORWARD_TO_IDLE_START = 19,
KTHROWER_ANIM_WALK_FORWARD_TO_IDLE_END = 20,
KTHROWER_ANIM_WALK_KNIFE_ATTACK_RIGHT_END = 21,
KTHROWER_ANIM_IDLE_KNIFE_ATTACK_CANCEL = 22,
KTHROWER_ANIM_DEATH = 23
};
void KnifeControl(short fxNumber)
{
auto* fx = &EffectList[fxNumber];
if (fx->counter <= 0)
{
KillEffect(fxNumber);
return;
}
else
fx->counter--;
int speed = fx->speed * phd_cos(fx->pos.Orientation.x);
fx->pos.Position.z += speed * phd_cos(fx->pos.Orientation.y);
fx->pos.Position.x += speed * phd_sin(fx->pos.Orientation.y);
fx->pos.Position.y += fx->speed * phd_sin(-fx->pos.Orientation.x);
auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
if (fx->pos.Position.y >= probe.Position.Floor ||
fx->pos.Position.y <= probe.Position.Ceiling)
{
KillEffect(fxNumber);
return;
}
if (probe.RoomNumber != fx->roomNumber)
EffectNewRoom(fxNumber, probe.RoomNumber);
fx->pos.Orientation.z += ANGLE(30.0f);
if (ItemNearLara(&fx->pos, 200))
{
DoDamage(LaraItem, KNIFE_PROJECTILE_DAMAGE);
fx->pos.Orientation.y = LaraItem->Pose.Orientation.y;
fx->speed = LaraItem->Animation.Velocity.z;
fx->frameNumber = fx->counter = 0;
DoBloodSplat(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, 80, fx->pos.Orientation.y, fx->roomNumber);
SoundEffect(SFX_TR2_CRUNCH2, &fx->pos);
KillEffect(fxNumber);
}
}
short ThrowKnife(int x, int y, int z, short velocity, short yRot, short roomNumber)
{
short fxNumber = 0;
// TODO: add fx parameters
return fxNumber;
}
void KnifeThrowerControl(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short torso = 0;
short head = 0;
short tilt = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != KTHROWER_STATE_DEATH)
SetAnimation(item, KTHROWER_ANIM_DEATH);
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case KTHROWER_STATE_IDLE:
creature->MaxTurn = 0;
if (AI.ahead)
head = AI.angle;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = KTHROWER_STATE_RUN_FORWARD;
else if (Targetable(item, &AI))
item->Animation.TargetState = KTHROWER_STATE_IDLE_KNIFE_ATTACK_START;
else if (creature->Mood == MoodType::Bored)
{
if (!AI.ahead || AI.distance > pow(SECTOR(6), 2))
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
}
else if (AI.ahead && AI.distance < pow(SECTOR(4), 2))
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
else
item->Animation.TargetState = KTHROWER_STATE_RUN_FORWARD;
break;
case KTHROWER_STATE_WALK_FORWARD:
creature->MaxTurn = ANGLE(3.0f);
if (AI.ahead)
head = AI.angle;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = KTHROWER_STATE_RUN_FORWARD;
else if (Targetable(item, &AI))
{
if (AI.distance < pow(SECTOR(2.5f), 2) || AI.zoneNumber != AI.enemyZone)
item->Animation.TargetState = KTHROWER_STATE_IDLE;
else if (TestProbability(0.5f))
item->Animation.TargetState = KTHROWER_STATE_WALK_KNIFE_ATTACK_LEFT_START;
else
item->Animation.TargetState = KTHROWER_STATE_WALK_KNIFE_ATTACK_RIGHT_START;
}
else if (creature->Mood == MoodType::Bored)
{
if (AI.ahead && AI.distance < pow(SECTOR(6), 2))
item->Animation.TargetState = KTHROWER_STATE_IDLE;
}
else if (!AI.ahead || AI.distance > pow(SECTOR(4), 2))
item->Animation.TargetState = KTHROWER_STATE_RUN_FORWARD;
break;
case KTHROWER_STATE_RUN_FORWARD:
tilt = angle / 3;
creature->MaxTurn = ANGLE(6.0f);
if (AI.ahead)
head = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
else if (creature->Mood == MoodType::Bored)
{
if (AI.ahead && AI.distance < pow(SECTOR(6), 2))
item->Animation.TargetState = KTHROWER_STATE_IDLE;
else
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
}
else if (AI.ahead && AI.distance < pow(SECTOR(4), 2))
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
break;
case KTHROWER_STATE_WALK_KNIFE_ATTACK_LEFT_START:
creature->Flags = 0;
if (AI.ahead)
torso = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = KTHROWER_STATE_WALK_KNIFE_ATTACK_LEFT_CONTINUE;
else
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
break;
case KTHROWER_STATE_WALK_KNIFE_ATTACK_RIGHT_START:
creature->Flags = 0;
if (AI.ahead)
torso = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = KTHROWER_STATE_WALK_KNIFE_ATTACK_RIGHT_CONTINUE;
else
item->Animation.TargetState = KTHROWER_STATE_WALK_FORWARD;
break;
case KTHROWER_STATE_IDLE_KNIFE_ATTACK_START:
creature->Flags = 0;
if (AI.ahead)
torso = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = KTHROWER_STATE_IDLE_KNIFE_ATTACK_CONTINUE;
else
item->Animation.TargetState = KTHROWER_STATE_IDLE;
break;
case KTHROWER_STATE_WALK_KNIFE_ATTACK_LEFT_CONTINUE:
if (AI.ahead)
torso = AI.angle;
if (!creature->Flags)
{
CreatureEffect(item, KnifeBiteLeft, ThrowKnife);
creature->Flags = 1;
}
break;
case KTHROWER_STATE_WALK_KNIFE_ATTACK_RIGHT_CONTINUE:
if (AI.ahead)
torso = AI.angle;
if (!creature->Flags)
{
CreatureEffect(item, KnifeBiteRight, ThrowKnife);
creature->Flags = 1;
}
break;
case KTHROWER_STATE_IDLE_KNIFE_ATTACK_CONTINUE:
if (AI.ahead)
torso = AI.angle;
if (!creature->Flags)
{
CreatureEffect(item, KnifeBiteLeft, ThrowKnife);
CreatureEffect(item, KnifeBiteRight, ThrowKnife);
creature->Flags = 1;
}
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torso);
CreatureJoint(item, 1, head);
CreatureAnimation(itemNumber, angle, tilt);
}
}

View file

@ -0,0 +1,7 @@
#pragma once
namespace TEN::Entities::TR2
{
void KnifeControl(short fxNumber);
void KnifeThrowerControl(short itemNumber);
}

View file

@ -1,270 +0,0 @@
#include "framework.h"
#include "Objects/TR2/Entity/tr2_knifethrower.h"
#include "Game/collision/floordata.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/collide_room.h"
#include "Game/control/box.h"
#include "Game/effects/effects.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "Sound/sound.h"
BITE_INFO KnifeBiteLeft = { 0, 0, 0, 5 };
BITE_INFO KnifeBiteRight = { 0, 0, 0, 8 };
// TODO
enum KnifeThrowerState
{
};
// TODO
enum KnifeThrowerAnim
{
};
void KnifeControl(short fxNumber)
{
auto* fx = &EffectList[fxNumber];
if (fx->counter <= 0)
{
KillEffect(fxNumber);
return;
}
else
fx->counter--;
int speed = fx->speed * phd_cos(fx->pos.Orientation.x);
fx->pos.Position.z += speed * phd_cos(fx->pos.Orientation.y);
fx->pos.Position.x += speed * phd_sin(fx->pos.Orientation.y);
fx->pos.Position.y += fx->speed * phd_sin(-fx->pos.Orientation.x);
auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
if (fx->pos.Position.y >= probe.Position.Floor ||
fx->pos.Position.y <= probe.Position.Ceiling)
{
KillEffect(fxNumber);
return;
}
if (probe.RoomNumber != fx->roomNumber)
EffectNewRoom(fxNumber, probe.RoomNumber);
fx->pos.Orientation.z += ANGLE(30.0f);
if (ItemNearLara(&fx->pos, 200))
{
DoDamage(LaraItem, 50);
fx->pos.Orientation.y = LaraItem->Pose.Orientation.y;
fx->speed = LaraItem->Animation.Velocity.z;
fx->frameNumber = fx->counter = 0;
SoundEffect(SFX_TR2_CRUNCH2, &fx->pos);
DoBloodSplat(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, 80, fx->pos.Orientation.y, fx->roomNumber);
KillEffect(fxNumber);
}
}
static short ThrowKnife(int x, int y, int z, short velocity, short yRot, short roomNumber)
{
short fxNumber = 0;
// TODO: add fx parameters
return fxNumber;
}
void KnifeThrowerControl(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short torso = 0;
short head = 0;
short tilt = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != 10)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 23;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 10;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case 1:
creature->MaxTurn = 0;
if (AI.ahead)
head = AI.angle;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (Targetable(item, &AI))
item->Animation.TargetState = 8;
else if (creature->Mood == MoodType::Bored)
{
if (!AI.ahead || AI.distance > pow(SECTOR(6), 2))
item->Animation.TargetState = 2;
}
else if (AI.ahead && AI.distance < pow(SECTOR(4), 2))
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 3;
break;
case 2:
creature->MaxTurn = ANGLE(3.0f);
if (AI.ahead)
head = AI.angle;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (Targetable(item, &AI))
{
if (AI.distance < pow(SECTOR(2.5f), 2) || AI.zoneNumber != AI.enemyZone)
item->Animation.TargetState = 1;
else if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 4;
else
item->Animation.TargetState = 6;
}
else if (creature->Mood == MoodType::Bored)
{
if (AI.ahead && AI.distance < pow(SECTOR(6), 2))
item->Animation.TargetState = 1;
}
else if (!AI.ahead || AI.distance > pow(SECTOR(4), 2))
item->Animation.TargetState = 3;
break;
case 3:
tilt = angle / 3;
creature->MaxTurn = ANGLE(6.0f);
if (AI.ahead)
head = AI.angle;
if (Targetable(item, &AI))
{
item->Animation.TargetState = 2;
}
else if (creature->Mood == MoodType::Bored)
{
if (AI.ahead && AI.distance < pow(SECTOR(6), 2))
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 2;
}
else if (AI.ahead && AI.distance < pow(SECTOR(4), 2))
item->Animation.TargetState = 2;
break;
case 4:
creature->Flags = 0;
if (AI.ahead)
torso = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = 5;
else
item->Animation.TargetState = 2;
break;
case 6:
creature->Flags = 0;
if (AI.ahead)
torso = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = 7;
else
item->Animation.TargetState = 2;
break;
case 8:
creature->Flags = 0;
if (AI.ahead)
torso = AI.angle;
if (Targetable(item, &AI))
item->Animation.TargetState = 9;
else
item->Animation.TargetState = 1;
break;
case 5:
if (AI.ahead)
torso = AI.angle;
if (!creature->Flags)
{
CreatureEffect(item, &KnifeBiteLeft, ThrowKnife);
creature->Flags = 1;
}
break;
case 7:
if (AI.ahead)
torso = AI.angle;
if (!creature->Flags)
{
CreatureEffect(item, &KnifeBiteRight, ThrowKnife);
creature->Flags = 1;
}
break;
case 9:
if (AI.ahead)
torso = AI.angle;
if (!creature->Flags)
{
CreatureEffect(item, &KnifeBiteLeft, ThrowKnife);
CreatureEffect(item, &KnifeBiteRight, ThrowKnife);
creature->Flags = 1;
}
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torso);
CreatureJoint(item, 1, head);
CreatureAnimation(itemNumber, angle, tilt);
}

View file

@ -1,4 +0,0 @@
#pragma once
void KnifeControl(short fxNumber);
void KnifeThrowerControl(short itemNumber);

View file

@ -11,407 +11,410 @@
#include "Specific/setup.h"
#include "Specific/trmath.h"
BITE_INFO MercenaryUziBite = { 0, 150, 19, 17 };
BITE_INFO MercenaryAutoPistolBite = { 0, 230, 9, 17 };
// TODO
enum MercenaryState
namespace TEN::Entities::TR2
{
const auto MercenaryUziBite = BiteInfo(Vector3(0.0f, 150.0f, 19.0f), 17);
const auto MercenaryAutoPistolBite = BiteInfo(Vector3(0.0f, 230.0f, 9.0f), 17);
};
// TODO
enum MercenaryAnim
{
};
void MercenaryUziControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short tilt = 0;
short headX = 0;
short headY = 0;
short torsoX = 0;
short torsoY = 0;
if (item->HitPoints <= 0)
// TODO
enum MercenaryState
{
if (item->Animation.ActiveState != 13)
};
// TODO
enum MercenaryAnim
{
};
void MercenaryUziControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short tilt = 0;
short headX = 0;
short headY = 0;
short torsoX = 0;
short torsoY = 0;
if (item->HitPoints <= 0)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 14;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 13;
if (item->Animation.ActiveState != 13)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 14;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 13;
}
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
else
{
case 1:
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
creature->MaxTurn = 0;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 2;
else if (Targetable(item, &AI))
{
if (AI.distance > pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
if (GetRandomControl() >= 0x2000)
{
if (GetRandomControl() >= 0x4000)
item->Animation.TargetState = 11;
else
item->Animation.TargetState = 7;
}
else
item->Animation.TargetState = 5;
}
else
{
if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = 3;
else if (!AI.ahead)
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 1;
}
break;
case 2:
creature->MaxTurn = ANGLE(7.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (Targetable(item, &AI))
{
if (AI.distance <= pow(SECTOR(2), 2) || AI.zoneNumber != AI.enemyZone)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 12;
}
else if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = 3;
else
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case 1:
if (AI.ahead)
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 1;
}
break;
case 3:
tilt = angle / 3;
creature->MaxTurn = ANGLE(10.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood != MoodType::Escape)
{
if (Targetable(item, &AI))
item->Animation.TargetState = 1;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
}
break;
case 5:
case 7:
case 8:
case 9:
creature->MaxTurn = 0;
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
if (!ShotLara(item, &AI, &MercenaryUziBite, torsoY, 8))
item->Animation.TargetState = 1;
if (AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 1;
break;
case 10:
case 14:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
if (!ShotLara(item, &AI, &MercenaryUziBite, torsoY, 8))
item->Animation.TargetState = 1;
if (AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY);
CreatureJoint(item, 1, torsoX);
CreatureJoint(item, 2, headY);
CreatureJoint(item, 3, headX);
CreatureAnimation(itemNumber, angle, tilt);
}
void MercenaryAutoPistolControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short tilt = 0;
short headX = 0;
short headY = 0;
short torsoX = 0;
short torsoY = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != 11)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 9;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 11;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case 2:
creature->MaxTurn = 0;
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4;
else if (Targetable(item, &AI))
{
if (AI.distance <= pow(SECTOR(2), 2))
{
headX = AI.xAngle;
headY = AI.angle;
}
creature->MaxTurn = 0;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 2;
else if (Targetable(item, &AI))
{
if (AI.distance > pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
if (GetRandomControl() >= 0x2000)
{
if (GetRandomControl() >= 0x4000)
item->Animation.TargetState = 5;
item->Animation.TargetState = 11;
else
item->Animation.TargetState = 8;
item->Animation.TargetState = 7;
}
else
item->Animation.TargetState = 7;
item->Animation.TargetState = 5;
}
else
item->Animation.TargetState = 3;
}
else
{
if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = 4;
if (!AI.ahead || GetRandomControl() < 0x100)
item->Animation.TargetState = 3;
}
break;
case 3:
creature->MaxTurn = ANGLE(7.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4;
else if (Targetable(item, &AI))
{
if (AI.distance < pow(SECTOR(2), 2) || AI.zoneNumber == AI.enemyZone || GetRandomControl() < 1024)
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 1;
}
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4;
else if (AI.ahead && GetRandomControl() < 1024)
item->Animation.TargetState = 2;
break;
case 4:
tilt = angle / 3;
creature->MaxTurn = ANGLE(10.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood != MoodType::Escape && (creature->Mood == MoodType::Escape || Targetable(item, &AI)))
item->Animation.TargetState = 2;
break;
case 1:
case 5:
case 6:
creature->Flags = 0;
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
break;
case 7:
case 8:
case 13:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
if (!creature->Flags)
{
if (GetRandomControl() < 0x2000)
if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = 3;
else if (!AI.ahead)
item->Animation.TargetState = 2;
ShotLara(item, &AI, &MercenaryAutoPistolBite, torsoY, 50);
creature->Flags = 1;
else
item->Animation.TargetState = 1;
}
}
else
item->Animation.TargetState = 2;
break;
case 9:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
break;
case 2:
creature->MaxTurn = ANGLE(7.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (Targetable(item, &AI))
{
if (AI.distance <= pow(SECTOR(2), 2) || AI.zoneNumber != AI.enemyZone)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 12;
}
else if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = 3;
else
{
if (AI.ahead)
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 1;
}
break;
case 3:
tilt = angle / 3;
creature->MaxTurn = ANGLE(10.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood != MoodType::Escape)
{
if (Targetable(item, &AI))
item->Animation.TargetState = 1;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
}
break;
case 5:
case 7:
case 8:
case 9:
creature->MaxTurn = 0;
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
if (!ShotLara(item, &AI, MercenaryUziBite, torsoY, 8))
item->Animation.TargetState = 1;
if (AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 3;
item->Animation.TargetState = 1;
if (creature->Flags != 1)
break;
case 10:
case 14:
if (AI.ahead)
{
if (!ShotLara(item, &AI, &MercenaryAutoPistolBite, torsoY, 50))
item->Animation.TargetState = 3;
creature->Flags = 1;
torsoX = AI.xAngle;
torsoY = AI.angle;
}
}
else
item->Animation.TargetState = 3;
break;
case 12:
creature->Flags = 0;
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
if (Targetable(item, &AI))
item->Animation.TargetState = 13;
else
item->Animation.TargetState = 2;
break;
case 10:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
if (!ShotLara(item, &AI, MercenaryUziBite, torsoY, 8))
item->Animation.TargetState = 1;
if (AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 3;
item->Animation.TargetState = 2;
if (creature->Flags != 2)
{
if (!ShotLara(item, &AI, &MercenaryAutoPistolBite, torsoY, 50))
item->Animation.TargetState = 3;
creature->Flags = 2;
}
break;
}
else
item->Animation.TargetState = 3;
break;
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY);
CreatureJoint(item, 1, torsoX);
CreatureJoint(item, 2, headY);
CreatureJoint(item, 3, headX);
CreatureAnimation(itemNumber, angle, tilt);
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY);
CreatureJoint(item, 1, torsoX);
CreatureJoint(item, 2, headY);
CreatureJoint(item, 3, headX);
CreatureAnimation(itemNumber, angle, tilt);
void MercenaryAutoPistolControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short tilt = 0;
short headX = 0;
short headY = 0;
short torsoX = 0;
short torsoY = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != 11)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 9;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 11;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case 2:
creature->MaxTurn = 0;
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4;
else if (Targetable(item, &AI))
{
if (AI.distance <= pow(SECTOR(2), 2))
{
if (GetRandomControl() >= 0x2000)
{
if (GetRandomControl() >= 0x4000)
item->Animation.TargetState = 5;
else
item->Animation.TargetState = 8;
}
else
item->Animation.TargetState = 7;
}
else
item->Animation.TargetState = 3;
}
else
{
if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = 4;
if (!AI.ahead || GetRandomControl() < 0x100)
item->Animation.TargetState = 3;
}
break;
case 3:
creature->MaxTurn = ANGLE(7.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4;
else if (Targetable(item, &AI))
{
if (AI.distance < pow(SECTOR(2), 2) || AI.zoneNumber == AI.enemyZone || GetRandomControl() < 1024)
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 1;
}
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4;
else if (AI.ahead && GetRandomControl() < 1024)
item->Animation.TargetState = 2;
break;
case 4:
tilt = angle / 3;
creature->MaxTurn = ANGLE(10.0f);
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
}
if (creature->Mood != MoodType::Escape && (creature->Mood == MoodType::Escape || Targetable(item, &AI)))
item->Animation.TargetState = 2;
break;
case 1:
case 5:
case 6:
creature->Flags = 0;
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
break;
case 7:
case 8:
case 13:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
if (!creature->Flags)
{
if (GetRandomControl() < 0x2000)
item->Animation.TargetState = 2;
ShotLara(item, &AI, MercenaryAutoPistolBite, torsoY, 50);
creature->Flags = 1;
}
}
else
item->Animation.TargetState = 2;
break;
case 9:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
if (AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 3;
if (creature->Flags != 1)
{
if (!ShotLara(item, &AI, MercenaryAutoPistolBite, torsoY, 50))
item->Animation.TargetState = 3;
creature->Flags = 1;
}
}
else
item->Animation.TargetState = 3;
break;
case 12:
creature->Flags = 0;
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
}
if (Targetable(item, &AI))
item->Animation.TargetState = 13;
else
item->Animation.TargetState = 2;
break;
case 10:
if (AI.ahead)
{
torsoX = AI.xAngle;
torsoY = AI.angle;
if (AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 3;
if (creature->Flags != 2)
{
if (!ShotLara(item, &AI, MercenaryAutoPistolBite, torsoY, 50))
item->Animation.TargetState = 3;
creature->Flags = 2;
}
}
else
item->Animation.TargetState = 3;
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY);
CreatureJoint(item, 1, torsoX);
CreatureJoint(item, 2, headY);
CreatureJoint(item, 3, headX);
CreatureAnimation(itemNumber, angle, tilt);
}
}

View file

@ -1,4 +1,7 @@
#pragma once
void MercenaryUziControl(short itemNumber);
void MercenaryAutoPistolControl(short itemNumber);
namespace TEN::Entities::TR2
{
void MercenaryUziControl(short itemNumber);
void MercenaryAutoPistolControl(short itemNumber);
}

View file

@ -11,216 +11,219 @@
#include "Specific/level.h"
#include "Specific/setup.h"
bool MonksAttackLara;
BITE_INFO MonkBite = { -23,16,265, 14 };
// TODO
enum MonkState
namespace TEN::Entities::TR2
{
const auto MonkBite = BiteInfo(Vector3(-23.0f, 16.0f, 265.0f), 14);
};
bool MonksAttackLara;
// TODO
enum MonkAnim
{
};
void MonkControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short torso = 0;
short angle = 0;
short tilt = 0;
if (item->HitPoints <= 0)
// TODO
enum MonkState
{
if (item->Animation.ActiveState != 9)
};
// TODO
enum MonkAnim
{
};
void MonkControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short torso = 0;
short angle = 0;
short tilt = 0;
if (item->HitPoints <= 0)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 20 + (GetRandomControl() / 0x4000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 9;
if (item->Animation.ActiveState != 9)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 20 + (GetRandomControl() / 0x4000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 9;
}
}
}
else
{
if (MonksAttackLara)
creature->Enemy = LaraItem;
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (!MonksAttackLara && creature->Enemy == LaraItem)
creature->Enemy = NULL;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, creature->MaxTurn);
if (AI.ahead)
torso = AI.angle;
switch (item->Animation.ActiveState)
else
{
case 1:
creature->Flags &= 0x0FFF;
if (MonksAttackLara)
creature->Enemy = LaraItem;
if (!MonksAttackLara && AI.ahead && Lara.TargetEntity == item)
break;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (!MonksAttackLara && creature->Enemy == LaraItem)
creature->Enemy = nullptr;
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, creature->MaxTurn);
if (AI.ahead)
torso = AI.angle;
switch (item->Animation.ActiveState)
{
if (GetRandomControl() < 0x7000)
item->Animation.TargetState = 4;
else
item->Animation.TargetState = 11;
}
else if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
item->Animation.TargetState = 7;
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 3;
break;
case 1:
creature->Flags &= 0x0FFF;
case 11:
creature->Flags &= 0x0FFF;
if (!MonksAttackLara && AI.ahead && Lara.TargetEntity == item)
break;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
auto random = GetRandomControl();
if (random < 0x3000)
item->Animation.TargetState = 5;
else if (random < 0x6000)
item->Animation.TargetState = 8;
else
item->Animation.TargetState = 1;
}
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 3;
break;
case 2:
creature->MaxTurn = ANGLE(3.0f);
if (creature->Mood == MoodType::Bored)
{
if (!MonksAttackLara && AI.ahead && Lara.TargetEntity == item)
break;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
if (GetRandomControl() < 0x7000)
item->Animation.TargetState = 4;
else
item->Animation.TargetState = 11;
}
else if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
item->Animation.TargetState = 7;
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 3;
break;
case 11:
creature->Flags &= 0x0FFF;
if (!MonksAttackLara && AI.ahead && Lara.TargetEntity == item)
break;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 2;
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
auto random = GetRandomControl();
if (random < 0x3000)
item->Animation.TargetState = 5;
else if (random < 0x6000)
item->Animation.TargetState = 8;
else
item->Animation.TargetState = 1;
}
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 2;
else
item->Animation.TargetState = 3;
break;
case 2:
creature->MaxTurn = ANGLE(3.0f);
if (creature->Mood == MoodType::Bored)
{
if (!MonksAttackLara && AI.ahead && Lara.TargetEntity == item)
{
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
}
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
}
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
else if (!AI.ahead || AI.distance > pow(SECTOR(2), 2))
item->Animation.TargetState = 3;
break;
else if (!AI.ahead || AI.distance > pow(SECTOR(2), 2))
item->Animation.TargetState = 3;
case 3:
tilt = angle / 4;
creature->MaxTurn = ANGLE(4.0f);
creature->Flags &= 0x0FFF;
if (MonksAttackLara)
creature->MaxTurn += ANGLE(1.0f);
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 1;
else if (creature->Mood == MoodType::Escape)
break;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
if (GetRandomControl() < 0x4000)
case 3:
tilt = angle / 4;
creature->MaxTurn = ANGLE(4.0f);
creature->Flags &= 0x0FFF;
if (MonksAttackLara)
creature->MaxTurn += ANGLE(1.0f);
if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
else if (AI.ahead && AI.distance < pow(SECTOR(3), 2))
item->Animation.TargetState = 10;
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
{
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
break;
case 8:
if (!AI.ahead || AI.distance > pow(SECTOR(0.5f), 2))
item->Animation.TargetState = 11;
else
item->Animation.TargetState = 6;
break;
case 4:
case 5:
case 6:
case 7:
case 10:
auto* enemy = creature->Enemy;
if (enemy == LaraItem)
{
if (!(creature->Flags & 0xF000) && item->TouchBits & 0x4000)
else if (creature->Mood == MoodType::Escape)
break;
else if (AI.ahead && AI.distance < pow(SECTOR(0.5f), 2))
{
creature->Flags |= 0x1000;
SoundEffect(SFX_TR2_CRUNCH1, &item->Pose);
CreatureEffect(item, &MonkBite, DoBloodSplat);
DoDamage(enemy, 150);
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
}
else
{
if (!(creature->Flags & 0xf000) && enemy)
else if (AI.ahead && AI.distance < pow(SECTOR(3), 2))
item->Animation.TargetState = 10;
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
{
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < CLICK(2) &&
abs(enemy->Pose.Position.y - item->Pose.Position.y) < CLICK(2) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < CLICK(2))
if (GetRandomControl() < 0x4000)
item->Animation.TargetState = 1;
else
item->Animation.TargetState = 11;
}
break;
case 8:
if (!AI.ahead || AI.distance > pow(SECTOR(0.5f), 2))
item->Animation.TargetState = 11;
else
item->Animation.TargetState = 6;
break;
case 4:
case 5:
case 6:
case 7:
case 10:
auto * enemy = creature->Enemy;
if (enemy == LaraItem)
{
if (!(creature->Flags & 0xF000) && item->TouchBits & 0x4000)
{
creature->Flags |= 0x1000;
DoDamage(enemy, 150);
CreatureEffect(item, MonkBite, DoBloodSplat);
SoundEffect(SFX_TR2_CRUNCH1, &item->Pose);
DoDamage(enemy, 5);
}
}
else
{
if (!(creature->Flags & 0xf000) && enemy)
{
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < CLICK(2) &&
abs(enemy->Pose.Position.y - item->Pose.Position.y) < CLICK(2) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < CLICK(2))
{
creature->Flags |= 0x1000;
DoDamage(enemy, 5);
SoundEffect(SFX_TR2_CRUNCH1, &item->Pose);
}
}
}
break;
}
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torso);
CreatureAnimation(itemNumber, angle, tilt);
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torso);
CreatureAnimation(itemNumber, angle, tilt);
}
}

View file

@ -1,3 +1,6 @@
#pragma once
void MonkControl(short itemNumber);
namespace TEN::Entities::TR2
{
void MonkControl(short itemNumber);
}

View file

@ -9,122 +9,148 @@
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
BITE_INFO RatBite = { 0, 0, 57, 2 };
using namespace TEN::Math::Random;
// TODO
enum RatState
namespace TEN::Entities::TR2
{
constexpr auto RAT_ATTACK_DAMAGE = 20;
constexpr auto RAT_ATTACK_RANGE = SQUARE(CLICK(0.7f));
};
constexpr auto RAT_SQUEAK_CHANCE = 0.04f;
constexpr auto RAT_IDLE_CHANCE = 0.08f;
constexpr auto RAT_WALK_CHANCE = 0.92f;
// TODO
enum RatAnim
{
#define RAT_TURN_RATE_MAX ANGLE(6.0f)
};
const auto RatBite = BiteInfo(Vector3(0.0f, 0.0f, 57.0f), 2);
void RatControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item);
short head = 0;
short angle = 0;
short random = 0;
if (item->HitPoints <= 0)
enum RatState
{
if (item->Animation.ActiveState != 6)
RAT_STATE_NONE = 0,
RAT_STATE_WALK_FORWARD = 1,
RAT_STATE_IDLE = 2,
RAT_STATE_SQUEAK = 3,
RAT_STATE_BITE_ATTACK = 4,
RAT_STATE_POUNCE_ATTACK = 5,
RAT_STATE_DEATH = 6
};
enum RatAnim
{
RAT_ANIM_IDLE = 0,
RAT_ANIM_IDLE_TO_WALK_FORWARD = 1,
RAT_ANIM_WALK_FORWARD = 2,
RAT_ANIM_WALK_FORWARD_TO_IDLE = 3,
RAT_ANIM_SQUEAK = 4,
RAT_ANIM_BITE_ATTACK_START = 5,
RAT_ANIM_BITE_ATTACK_CONTINUE = 6,
RAT_ANIM_BITE_ATTACK_END = 7,
RAT_ANIM_POUNCE_ATTACK = 8,
RAT_ANIM_DEATH = 9
};
void RatControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 9;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 6;
if (item->Animation.ActiveState != RAT_STATE_DEATH)
SetAnimation(item, RAT_ANIM_DEATH);
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, ANGLE(6.0f));
switch (item->Animation.ActiveState)
else
{
case 4:
if (info->Mood == MoodType::Bored || info->Mood == MoodType::Stalk)
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false);
angle = CreatureTurn(item, RAT_TURN_RATE_MAX);
switch (item->Animation.ActiveState)
{
short random = (short)GetRandomControl();
if (random < 0x500)
item->Animation.RequiredState = 3;
else if (random > 0xA00)
item->Animation.RequiredState = 1;
}
else if (AI.distance < pow(340, 2))
item->Animation.RequiredState = 5;
else
item->Animation.RequiredState = 1;
if (item->Animation.RequiredState)
item->Animation.TargetState = 2;
break;
case 2:
info->MaxTurn = 0;
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
break;
case 1:
info->MaxTurn = ANGLE(6.0f);
if (info->Mood == MoodType::Bored || info->Mood == MoodType::Stalk)
{
random = (short)GetRandomControl();
if (random < 0x500)
case RAT_STATE_BITE_ATTACK:
if (creature->Mood == MoodType::Bored ||
creature->Mood == MoodType::Stalk)
{
item->Animation.RequiredState = 3;
item->Animation.TargetState = 2;
if (TestProbability(RAT_SQUEAK_CHANCE))
item->Animation.RequiredState = RAT_STATE_SQUEAK;
else if (TestProbability(RAT_WALK_CHANCE))
item->Animation.RequiredState = RAT_STATE_WALK_FORWARD;
}
else if (random < 0xA00)
item->Animation.TargetState = 2;
else if (AI.distance < RAT_ATTACK_RANGE)
item->Animation.RequiredState = RAT_STATE_POUNCE_ATTACK;
else
item->Animation.RequiredState = RAT_STATE_WALK_FORWARD;
if (item->Animation.RequiredState)
item->Animation.TargetState = RAT_STATE_IDLE;
break;
case RAT_STATE_IDLE:
creature->MaxTurn = 0;
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
break;
case RAT_STATE_WALK_FORWARD:
creature->MaxTurn = RAT_TURN_RATE_MAX;
if (creature->Mood == MoodType::Bored ||
creature->Mood == MoodType::Stalk)
{
if (TestProbability(RAT_SQUEAK_CHANCE))
{
item->Animation.TargetState = RAT_STATE_IDLE;
item->Animation.RequiredState = RAT_STATE_SQUEAK;
}
else if (TestProbability(RAT_IDLE_CHANCE))
item->Animation.TargetState = RAT_STATE_IDLE;
}
else if (AI.ahead && AI.distance < RAT_ATTACK_RANGE)
item->Animation.TargetState = RAT_STATE_IDLE;
break;
case RAT_STATE_POUNCE_ATTACK:
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, RatBite.meshNum))
{
item->Animation.RequiredState = RAT_STATE_IDLE;
DoDamage(creature->Enemy, RAT_ATTACK_DAMAGE);
CreatureEffect(item, RatBite, DoBloodSplat);
}
break;
case RAT_STATE_SQUEAK:
creature->MaxTurn = 0;
if (TestProbability(RAT_SQUEAK_CHANCE))
item->Animation.TargetState = RAT_STATE_IDLE;
break;
}
else if (AI.ahead && AI.distance < pow(340, 2))
item->Animation.TargetState = 2;
break;
case 5:
if (!item->Animation.RequiredState && item->TouchBits & 0x7F)
{
CreatureEffect(item, &RatBite, DoBloodSplat);
DoDamage(info->Enemy, 20);
item->Animation.RequiredState = 2;
}
break;
case 3:
if (GetRandomControl() < 0x500)
item->Animation.TargetState = 2;
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

@ -1,3 +1,6 @@
#pragma once
void RatControl(short itemNumber);
namespace TEN::Entities::TR2
{
void RatControl(short itemNumber);
}

View file

@ -11,105 +11,108 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO SharkBite = { 17, -22, 344, 12 };
void SharkControl(short itemNumber)
namespace TEN::Entities::TR2
{
if (!CreatureActive(itemNumber))
return;
const auto SharkBite = BiteInfo(Vector3(17.0f, -22.0f, 344.0f), 12);
auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item);
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
void SharkControl(short itemNumber)
{
if (item->Animation.ActiveState != 5)
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item);
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
{
item->Animation.AnimNumber = Objects[ID_SHARK].animIndex + 4;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 5;
}
CreatureFloat(itemNumber);
return;
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, info->MaxTurn);
switch (item->Animation.ActiveState)
{
case 0:
info->Flags = 0;
info->MaxTurn = 0;
if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2) && AI.zoneNumber == AI.enemyZone)
item->Animation.TargetState = 3;
else
item->Animation.TargetState = 1;
break;
case 1:
info->MaxTurn = ANGLE(0.5f);
if (info->Mood == MoodType::Bored)
break;
else if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2))
item->Animation.TargetState = 0;
else if (info->Mood == MoodType::Escape || AI.distance > pow(SECTOR(3), 2) || !AI.ahead)
item->Animation.TargetState = 2;
break;
case 2:
info->MaxTurn = ANGLE(2.0f);
info->Flags = 0;
if (info->Mood == MoodType::Bored)
item->Animation.TargetState = 1;
else if (info->Mood == MoodType::Escape)
break;
else if (AI.ahead && AI.distance < pow(1365, 2) && AI.zoneNumber == AI.enemyZone)
if (item->Animation.ActiveState != 5)
{
if (GetRandomControl() < 0x800)
item->Animation.AnimNumber = Objects[ID_SHARK].animIndex + 4;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 5;
}
CreatureFloat(itemNumber);
return;
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, info->MaxTurn);
switch (item->Animation.ActiveState)
{
case 0:
info->Flags = 0;
info->MaxTurn = 0;
if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2) && AI.zoneNumber == AI.enemyZone)
item->Animation.TargetState = 3;
else
item->Animation.TargetState = 1;
break;
case 1:
info->MaxTurn = ANGLE(0.5f);
if (info->Mood == MoodType::Bored)
break;
else if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2))
item->Animation.TargetState = 0;
else if (AI.distance < pow(SECTOR(0.75f), 2))
item->Animation.TargetState = 4;
else if (info->Mood == MoodType::Escape || AI.distance > pow(SECTOR(3), 2) || !AI.ahead)
item->Animation.TargetState = 2;
break;
case 2:
info->MaxTurn = ANGLE(2.0f);
info->Flags = 0;
if (info->Mood == MoodType::Bored)
item->Animation.TargetState = 1;
else if (info->Mood == MoodType::Escape)
break;
else if (AI.ahead && AI.distance < pow(1365, 2) && AI.zoneNumber == AI.enemyZone)
{
if (GetRandomControl() < 0x800)
item->Animation.TargetState = 0;
else if (AI.distance < pow(SECTOR(0.75f), 2))
item->Animation.TargetState = 4;
}
break;
case 3:
case 4:
if (AI.ahead)
head = AI.angle;
if (!info->Flags && item->TouchBits & 0x3400)
{
CreatureEffect(item, SharkBite, DoBloodSplat);
DoDamage(info->Enemy, 400);
info->Flags = 1;
}
break;
}
break;
case 3:
case 4:
if (AI.ahead)
head = AI.angle;
if (!info->Flags && item->TouchBits & 0x3400)
{
CreatureEffect(item, &SharkBite, DoBloodSplat);
DoDamage(info->Enemy, 400);
info->Flags = 1;
}
break;
}
}
if (item->Animation.ActiveState != 6)
{
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureUnderwater(item, 340);
if (item->Animation.ActiveState != 6)
{
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureUnderwater(item, 340);
}
else
AnimateItem(item);
}
else
AnimateItem(item);
}

View file

@ -1,3 +1,6 @@
#pragma once
void SharkControl(short itemNumber);
namespace TEN::Entities::TR2
{
void SharkControl(short itemNumber);
}

View file

@ -10,101 +10,93 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO SilencerGunBite = { 3, 331, 56, 10 };
// TODO
enum SilencerState
namespace TEN::Entities::TR2
{
const auto SilencerGunBite = BiteInfo(Vector3(3.0f, 331.0f, 56.0f), 10);
};
// TODO
enum SilencerAnim
{
};
void SilencerControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item);
short angle = 0;
short torsoX = 0;
short torsoY = 0;
short head = 0;
short tilt = 0;
if (item->HitPoints <= 0)
// TODO
enum SilencerState
{
if (item->Animation.ActiveState != 12 && item->Animation.ActiveState != 13)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 20;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 13;
}
}
else
};
// TODO
enum SilencerAnim
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
};
angle = CreatureTurn(item, info->MaxTurn);
void SilencerControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
switch (item->Animation.ActiveState)
auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item);
short angle = 0;
short torsoX = 0;
short torsoY = 0;
short head = 0;
short tilt = 0;
if (item->HitPoints <= 0)
{
case 3:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = 0;
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
break;
case 4:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = 0;
if (info->Mood == MoodType::Escape)
if (item->Animation.ActiveState != 12 && item->Animation.ActiveState != 13)
{
item->Animation.RequiredState = 2;
item->Animation.TargetState = 3;
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 20;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 13;
}
else
{
if (Targetable(item, &AI))
{
item->Animation.RequiredState = (GetRandomControl() >= 0x4000 ? 10 : 6);
item->Animation.TargetState = 3;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (info->Mood == MoodType::Attack || !AI.ahead)
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, info->MaxTurn);
switch (item->Animation.ActiveState)
{
case 3:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = 0;
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
break;
case 4:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = 0;
if (info->Mood == MoodType::Escape)
{
if (AI.distance >= pow(SECTOR(2), 2))
{
item->Animation.RequiredState = 2;
item->Animation.TargetState = 3;
}
else
{
item->Animation.RequiredState = 1;
item->Animation.TargetState = 3;
}
item->Animation.RequiredState = 2;
item->Animation.TargetState = 3;
}
else
{
if (GetRandomControl() >= 1280)
if (Targetable(item, &AI))
{
if (GetRandomControl() < 2560)
item->Animation.RequiredState = (GetRandomControl() >= 0x4000 ? 10 : 6);
item->Animation.TargetState = 3;
}
if (info->Mood == MoodType::Attack || !AI.ahead)
{
if (AI.distance >= pow(SECTOR(2), 2))
{
item->Animation.RequiredState = 2;
item->Animation.TargetState = 3;
}
else
{
item->Animation.RequiredState = 1;
item->Animation.TargetState = 3;
@ -112,157 +104,168 @@ void SilencerControl(short itemNumber)
}
else
{
item->Animation.RequiredState = 5;
item->Animation.TargetState = 3;
if (GetRandomControl() >= 1280)
{
if (GetRandomControl() < 2560)
{
item->Animation.RequiredState = 1;
item->Animation.TargetState = 3;
}
}
else
{
item->Animation.RequiredState = 5;
item->Animation.TargetState = 3;
}
}
}
}
break;
break;
case 1:
if (AI.ahead)
head = AI.angle;
case 1:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = 910;
info->MaxTurn = 910;
if (info->Mood == MoodType::Escape)
item->Animation.TargetState = 2;
else if (Targetable(item, &AI))
{
item->Animation.RequiredState = (GetRandomControl() >= 0x4000 ? 10 : 6);
item->Animation.TargetState = 3;
}
else
{
if (AI.distance > pow(SECTOR(2), 2) || !AI.ahead)
if (info->Mood == MoodType::Escape)
item->Animation.TargetState = 2;
if (info->Mood == MoodType::Bored && GetRandomControl() < 0x300)
else if (Targetable(item, &AI))
{
item->Animation.RequiredState = (GetRandomControl() >= 0x4000 ? 10 : 6);
item->Animation.TargetState = 3;
}
}
else
{
if (AI.distance > pow(SECTOR(2), 2) || !AI.ahead)
item->Animation.TargetState = 2;
if (info->Mood == MoodType::Bored && GetRandomControl() < 0x300)
item->Animation.TargetState = 3;
}
break;
break;
case 2:
if (AI.ahead)
head = AI.angle;
case 2:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = ANGLE(5.0f);
info->Flags = 0;
tilt = angle / 4;
info->MaxTurn = ANGLE(5.0f);
info->Flags = 0;
tilt = angle / 4;
if (info->Mood == MoodType::Escape)
{
if (Targetable(item, &AI))
item->Animation.TargetState = 9;
break;
}
if (info->Mood == MoodType::Escape)
{
if (Targetable(item, &AI))
item->Animation.TargetState = 9;
{
if (AI.distance >= pow(SECTOR(2), 2) && AI.zoneNumber == AI.enemyZone)
item->Animation.TargetState = 9;
break;
}
else if (info->Mood == MoodType::Attack)
item->Animation.TargetState = (GetRandomControl() >= 0x4000) ? 3 : 2;
else
item->Animation.TargetState = 3;
break;
}
case 5:
if (AI.ahead)
head = AI.angle;
if (Targetable(item, &AI))
{
if (AI.distance >= pow(SECTOR(2), 2) && AI.zoneNumber == AI.enemyZone)
item->Animation.TargetState = 9;
info->MaxTurn = 0;
if (Targetable(item, &AI))
{
item->Animation.RequiredState = 6;
item->Animation.TargetState = 3;
}
else
{
if (info->Mood == MoodType::Attack || GetRandomControl() < 0x100)
item->Animation.TargetState = 3;
if (!AI.ahead)
item->Animation.TargetState = 3;
}
break;
case 6:
case 10:
info->MaxTurn = 0;
info->Flags = 0;
if (AI.ahead)
{
torsoY = AI.angle;
torsoX = AI.xAngle;
}
else
head = AI.angle;
if (info->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (Targetable(item, &AI))
item->Animation.TargetState = item->Animation.ActiveState != 6 ? 11 : 7;
else
item->Animation.TargetState = 3;
break;
case 7:
case 11:
info->MaxTurn = 0;
if (AI.ahead)
{
torsoY = AI.angle;
torsoX = AI.xAngle;
}
else
head = AI.angle;
if (!info->Flags)
{
ShotLara(item, &AI, SilencerGunBite, torsoY, 50);
info->Flags = 1;
}
break;
case 9:
info->MaxTurn = ANGLE(5.0f);
if (AI.ahead)
{
torsoY = AI.angle;
torsoX = AI.xAngle;
}
else
head = AI.angle;
if (!item->Animation.RequiredState)
{
if (!ShotLara(item, &AI, SilencerGunBite, torsoY, 50))
item->Animation.TargetState = 2;
item->Animation.RequiredState = 9;
}
break;
}
else if (info->Mood == MoodType::Attack)
item->Animation.TargetState = (GetRandomControl() >= 0x4000) ? 3 : 2;
else
item->Animation.TargetState = 3;
break;
case 5:
if (AI.ahead)
head = AI.angle;
info->MaxTurn = 0;
if (Targetable(item, &AI))
{
item->Animation.RequiredState = 6;
item->Animation.TargetState = 3;
}
else
{
if (info->Mood == MoodType::Attack || GetRandomControl() < 0x100)
item->Animation.TargetState = 3;
if (!AI.ahead)
item->Animation.TargetState = 3;
}
break;
case 6:
case 10:
info->MaxTurn = 0;
info->Flags = 0;
if (AI.ahead)
{
torsoY = AI.angle;
torsoX = AI.xAngle;
}
else
head = AI.angle;
if (info->Mood == MoodType::Escape)
item->Animation.TargetState = 3;
else if (Targetable(item, &AI))
item->Animation.TargetState = item->Animation.ActiveState != 6 ? 11 : 7;
else
item->Animation.TargetState = 3;
break;
case 7:
case 11:
info->MaxTurn = 0;
if (AI.ahead)
{
torsoY = AI.angle;
torsoX = AI.xAngle;
}
else
head = AI.angle;
if (!info->Flags)
{
ShotLara(item, &AI, &SilencerGunBite, torsoY, 50);
info->Flags = 1;
}
break;
case 9:
info->MaxTurn = ANGLE(5.0f);
if (AI.ahead)
{
torsoY = AI.angle;
torsoX = AI.xAngle;
}
else
head = AI.angle;
if (!item->Animation.RequiredState)
{
if (!ShotLara(item, &AI, &SilencerGunBite, torsoY, 50))
item->Animation.TargetState = 2;
item->Animation.RequiredState = 9;
}
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY);
CreatureJoint(item, 1, torsoX);
CreatureJoint(item, 2, head);
CreatureAnimation(itemNumber, angle, tilt);
CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY);
CreatureJoint(item, 1, torsoX);
CreatureJoint(item, 2, head);
CreatureAnimation(itemNumber, angle, tilt);
}
}

View file

@ -1,3 +1,6 @@
#pragma once
void SilencerControl(short itemNumber);
namespace TEN::Entities::TR2
{
void SilencerControl(short itemNumber);
}

Some files were not shown because too many files have changed in this diff Show more