-
TombEngine 1.5 scripting interface
+
TombEngine 1.6 scripting interface
Welcome to the TombEngine scripting API. This is a work in progress and some information might be wrong or outdated. Please also note that this is primarily a reference document, not a tutorial, so expect descriptions to be fairly sparse.
At the time of writing, there is a tutorial describing the basics of Lua, as well as a number of example scripts, on the TombEngine website.
@@ -122,7 +124,7 @@ local door = GetMoveableByName("door_type4_14")
Enjoy.
-
- squidshire
+
- squidshire and the TombEngine development team.
1 Modules
@@ -279,6 +281,10 @@ local door = GetMoveableByName("door_type4_14")
Effects.EffectID |
Constants for effect IDs. |
+
+ Flow.FreezeMode |
+ Constants for freeze modes. |
+
Flow.GameStatus |
Constants for game statuses. |
@@ -311,6 +317,10 @@ local door = GetMoveableByName("door_type4_14")
Sound.SoundTrackType |
Constants for the type of the audio tracks. |
+
+ Strings.DisplayStringOption |
+ Constants for Display String Options. |
+
Util.LogLevel |
Constants for LogLevel IDs. |
diff --git a/Documentation/doc/ldoc.css b/Documentation/doc/ldoc.css
index 9417d33c0..306dcd9f5 100644
--- a/Documentation/doc/ldoc.css
+++ b/Documentation/doc/ldoc.css
@@ -141,9 +141,9 @@ table.index td { text-align: left; vertical-align: top; }
}
#content {
- margin-left: 16em;
- padding: 1em;
- width: 700px;
+ margin-left: 18em;
+ padding: 2em;
+ width: 900px;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
@@ -220,7 +220,6 @@ table.function_list td.name { background-color: #f0f0f0; min-width: 250px; }
table.function_list td.summary { width: 100%; }
ul.nowrap {
- overflow:auto;
white-space:nowrap;
}
diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css
index 9417d33c0..306dcd9f5 100644
--- a/Documentation/ldoc.css
+++ b/Documentation/ldoc.css
@@ -141,9 +141,9 @@ table.index td { text-align: left; vertical-align: top; }
}
#content {
- margin-left: 16em;
- padding: 1em;
- width: 700px;
+ margin-left: 18em;
+ padding: 2em;
+ width: 900px;
border-left: 2px solid #cccccc;
border-right: 2px solid #cccccc;
background-color: #ffffff;
@@ -220,7 +220,6 @@ table.function_list td.name { background-color: #f0f0f0; min-width: 250px; }
table.function_list td.summary { width: 100%; }
ul.nowrap {
- overflow:auto;
white-space:nowrap;
}
diff --git a/Scripts/Settings.lua b/Scripts/Settings.lua
index f50976766..b6d230a98 100644
--- a/Scripts/Settings.lua
+++ b/Scripts/Settings.lua
@@ -5,6 +5,7 @@ local Flow = TEN.Flow
local settings = Flow.Settings.new()
settings.errorMode = Flow.ErrorMode.WARN
+settings.fastReload = true
Flow.SetSettings(settings)
local anims = Flow.Animations.new()
diff --git a/TombEngine/Game/Lara/lara.h b/TombEngine/Game/Lara/lara.h
index 5e35047bf..0d426f036 100644
--- a/TombEngine/Game/Lara/lara.h
+++ b/TombEngine/Game/Lara/lara.h
@@ -82,7 +82,7 @@ constexpr auto LARA_STAMINA_MIN = LARA_STAMINA_MAX / 10;
constexpr auto LARA_STAMINA_CRITICAL = LARA_STAMINA_MAX / 2;
constexpr auto PLAYER_DRIP_NODE_MAX = 64.0f;
-constexpr auto PLAYER_BUBBLE_NODE_MAX = 12.0f;
+constexpr auto PLAYER_BUBBLE_NODE_MAX = 8.0f;
constexpr auto STEPUP_HEIGHT = (int)CLICK(1.5f);
constexpr auto CRAWL_STEPUP_HEIGHT = CLICK(1) - 1;
diff --git a/TombEngine/Game/Lara/lara_fire.cpp b/TombEngine/Game/Lara/lara_fire.cpp
index 08a97f190..27e0e4d91 100644
--- a/TombEngine/Game/Lara/lara_fire.cpp
+++ b/TombEngine/Game/Lara/lara_fire.cpp
@@ -874,7 +874,7 @@ FireWeaponType FireWeapon(LaraWeaponType weaponType, ItemInfo& targetEntity, Ite
}
}
- if (closestJointIndex < 0)
+ if (closestJointIndex == NO_VALUE)
{
auto vTarget = GameVector(target);
GetTargetOnLOS(&vOrigin, &vTarget, false, true);
@@ -1109,6 +1109,9 @@ void HitTarget(ItemInfo* laraItem, ItemInfo* targetEntity, GameVector* hitPos, i
if (targetEntity->IsCreature())
GetCreatureInfo(targetEntity)->HurtByLara = true;
+ if (object.HitRoutine == nullptr)
+ return;
+
if (hitPos != nullptr)
{
hitPos->RoomNumber = targetEntity->RoomNumber;
diff --git a/TombEngine/Game/Lara/lara_flare.cpp b/TombEngine/Game/Lara/lara_flare.cpp
index 0cae11806..aae02115e 100644
--- a/TombEngine/Game/Lara/lara_flare.cpp
+++ b/TombEngine/Game/Lara/lara_flare.cpp
@@ -260,7 +260,7 @@ void DrawFlare(ItemInfo& laraItem)
SoundEffect(
SFX_TR4_FLARE_IGNITE_DRY,
&laraItem.Pose,
- TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::ShallowWater : SoundEnvironment::Land);
+ TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::Underwater : SoundEnvironment::Land);
}
DoFlareInHand(laraItem, player.Flare.Life);
diff --git a/TombEngine/Game/Lara/lara_helpers.cpp b/TombEngine/Game/Lara/lara_helpers.cpp
index b307919d2..48fee7e30 100644
--- a/TombEngine/Game/Lara/lara_helpers.cpp
+++ b/TombEngine/Game/Lara/lara_helpers.cpp
@@ -726,7 +726,7 @@ static void GivePlayerItemsCheat(ItemInfo& item)
player.Inventory.Puzzles[i] = true;
player.Inventory.PuzzlesCombo[2 * i] = false;
- player.Inventory.PuzzlesCombo[(92 * i) + 1] = false;
+ player.Inventory.PuzzlesCombo[(2 * i) + 1] = false;
}
for (int i = 0; i < 8; ++i)
@@ -895,7 +895,9 @@ void HandlePlayerFlyCheat(ItemInfo& item)
{
SetAnimation(item, LA_FLY_CHEAT);
ResetPlayerFlex(&item);
- item.Animation.IsAirborne = false;
+ item.Animation.Velocity = Vector3::Zero;
+ item.Animation.IsAirborne = true;
+ item.Pose.Position.y -= CLICK(0.5f);
item.HitPoints = LARA_HEALTH_MAX;
player.Control.WaterStatus = WaterStatus::FlyCheat;
@@ -953,7 +955,7 @@ void HandlePlayerWetnessDrips(ItemInfo& item)
void HandlePlayerDiveBubbles(ItemInfo& item)
{
- constexpr auto BUBBLE_COUNT_MULT = 6;
+ constexpr auto BUBBLE_COUNT_MULT = 3;
auto& player = *GetLaraInfo(&item);
@@ -990,7 +992,7 @@ void HandlePlayerAirBubbles(ItemInfo* item)
{
constexpr auto BUBBLE_COUNT_MAX = 3;
- SoundEffect(SFX_TR4_LARA_BUBBLES, &item->Pose, SoundEnvironment::ShallowWater);
+ SoundEffect(SFX_TR4_LARA_BUBBLES, &item->Pose, SoundEnvironment::Underwater);
const auto& level = *g_GameFlow->GetLevel(CurrentLevel);
@@ -1467,7 +1469,7 @@ void UpdateLaraSubsuitAngles(ItemInfo* item)
auto mul1 = (float)abs(lara->Control.Subsuit.Velocity[0]) / BLOCK(8);
auto mul2 = (float)abs(lara->Control.Subsuit.Velocity[1]) / BLOCK(8);
auto vol = ((mul1 + mul2) * 5.0f) + 0.5f;
- SoundEffect(SFX_TR5_VEHICLE_DIVESUIT_ENGINE, &item->Pose, SoundEnvironment::ShallowWater, 1.0f + (mul1 + mul2), vol);
+ SoundEffect(SFX_TR5_VEHICLE_DIVESUIT_ENGINE, &item->Pose, SoundEnvironment::Underwater, 1.0f + (mul1 + mul2), vol);
}
}
diff --git a/TombEngine/Game/Lara/lara_one_gun.cpp b/TombEngine/Game/Lara/lara_one_gun.cpp
index 4270b6c9b..d3771a6cb 100644
--- a/TombEngine/Game/Lara/lara_one_gun.cpp
+++ b/TombEngine/Game/Lara/lara_one_gun.cpp
@@ -428,7 +428,7 @@ void FireShotgun(ItemInfo& laraItem)
player.RightArm.GunFlash = Weapons[(int)LaraWeaponType::Shotgun].FlashTime;
- SoundEffect(SFX_TR4_EXPLOSION1, &laraItem.Pose, TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::ShallowWater : SoundEnvironment::Land);
+ SoundEffect(SFX_TR4_EXPLOSION1, &laraItem.Pose, TestEnvironment(ENV_FLAG_WATER, &laraItem) ? SoundEnvironment::Underwater : SoundEnvironment::Land);
SoundEffect(Weapons[(int)LaraWeaponType::Shotgun].SampleNum, &laraItem.Pose);
Rumble(0.5f, 0.2f);
@@ -1568,7 +1568,7 @@ void HandleProjectile(ItemInfo& projectile, ItemInfo& emitter, const Vector3i& p
hasHit = hasHitNotByEmitter = doShatter = true;
doExplosion = isExplosive;
- if (StaticObjects[staticPtr->staticNumber].shatterType == ShatterType::None)
+ if (Statics[staticPtr->staticNumber].shatterType == ShatterType::None)
continue;
staticPtr->HitPoints -= damage;
diff --git a/TombEngine/Game/Setup.cpp b/TombEngine/Game/Setup.cpp
index 0f5b82874..f69f01e5c 100644
--- a/TombEngine/Game/Setup.cpp
+++ b/TombEngine/Game/Setup.cpp
@@ -33,7 +33,7 @@ using namespace TEN::Entities;
using namespace TEN::Entities::Switches;
ObjectHandler Objects;
-StaticInfo StaticObjects[MAX_STATICS];
+StaticHandler Statics;
void ObjectHandler::Initialize()
{
@@ -76,6 +76,44 @@ ObjectInfo& ObjectHandler::GetFirstAvailableObject()
return _objects[0];
}
+void StaticHandler::Initialize()
+{
+ _lookupTable.resize(0);
+ _lookupTable.reserve(_defaultLUTSize);
+ _statics.resize(0);
+}
+
+int StaticHandler::GetIndex(int staticID)
+{
+ if (staticID < 0 || staticID >= _lookupTable.size())
+ {
+ TENLog("Attempt to get nonexistent static mesh ID slot index (" + std::to_string(staticID) + ")", LogLevel::Warning);
+ return _lookupTable.front();
+ }
+
+ return _lookupTable[staticID];
+}
+
+StaticInfo& StaticHandler::operator [](int staticID)
+{
+ if (staticID < 0)
+ {
+ TENLog("Attempt to access illegal static mesh ID slot info", LogLevel::Warning);
+ return _statics.front();
+ }
+
+ if (staticID >= _lookupTable.size())
+ _lookupTable.resize(staticID + 1, NO_VALUE);
+
+ if (_lookupTable[staticID] != NO_VALUE)
+ return _statics[_lookupTable[staticID]];
+
+ _statics.emplace_back();
+ _lookupTable[staticID] = (int)_statics.size() - 1;
+
+ return _statics.back();
+}
+
// NOTE: JointRotationFlags allows bones to be rotated with CreatureJoint().
void ObjectInfo::SetBoneRotationFlags(int boneID, int flags)
{
@@ -191,6 +229,7 @@ void InitializeObjects()
obj->hitEffect = HitEffect::None;
obj->explodableMeshbits = 0;
obj->intelligent = false;
+ obj->AlwaysActive = false;
obj->waterCreature = false;
obj->nonLot = false;
obj->usingDrawAnimatingItem = true;
diff --git a/TombEngine/Game/Setup.h b/TombEngine/Game/Setup.h
index 824426e10..ee0413b56 100644
--- a/TombEngine/Game/Setup.h
+++ b/TombEngine/Game/Setup.h
@@ -12,8 +12,6 @@ constexpr auto DEFAULT_RADIUS = 10;
constexpr auto GRAVITY = 6.0f;
constexpr auto SWAMP_GRAVITY = GRAVITY / 3.0f;
-constexpr auto MAX_STATICS = 1000;
-
enum JointRotationFlags
{
ROT_X = 1 << 2,
@@ -39,11 +37,10 @@ enum class LotType
HumanPlusJump,
HumanPlusJumpAndMonkey,
Flyer,
- Blockable, // For large creatures such as trex and shiva.
- Spider, // Only 2 block vault allowed.
- Ape, // Only 2 block vault allowed.
- SnowmobileGun, // Only 1 block vault allowed and 4 block drop max.
- EnemyJeep
+ Blockable, // For large creatures such as trex and shiva.
+ Spider, // Only 2 block vault allowed.
+ Ape, // Only 2 block vault allowed.
+ SnowmobileGun // Only 1 block vault allowed and 4 block drop max.
};
enum class HitEffect
@@ -70,6 +67,7 @@ enum class ShatterType
Explode
};
+// TODO: All fields to PascalCase.
struct ObjectInfo
{
bool loaded = false; // IsLoaded
@@ -89,13 +87,14 @@ struct ObjectInfo
int pivotLength;
int radius;
- int HitPoints;
- bool intelligent; // IsIntelligent
- bool waterCreature; // IsWaterCreature
- bool nonLot; // IsNonLot
- bool isPickup; // IsPickup
- bool isPuzzleHole; // IsReceptacle
- bool usingDrawAnimatingItem;
+ int HitPoints = 0;
+ bool AlwaysActive = false;
+ bool intelligent = false;
+ bool waterCreature = false;
+ bool nonLot = false;
+ bool isPickup = false;
+ bool isPuzzleHole = false;
+ bool usingDrawAnimatingItem = false;
DWORD explodableMeshbits;
@@ -115,15 +114,13 @@ class ObjectHandler
{
private:
ObjectInfo _objects[ID_NUMBER_OBJECTS];
+ ObjectInfo& GetFirstAvailableObject();
public:
void Initialize();
bool CheckID(GAME_OBJECT_ID objectID, bool isSilent = false);
ObjectInfo& operator [](int objectID);
-
-private:
- ObjectInfo& GetFirstAvailableObject();
};
struct StaticInfo
@@ -134,10 +131,34 @@ struct StaticInfo
GameBoundingBox collisionBox;
ShatterType shatterType;
int shatterSound;
+ int ObjectNumber;
+};
+
+class StaticHandler
+{
+private:
+ static const int _defaultLUTSize = 256;
+
+ std::vector _statics = {};
+ std::vector _lookupTable = {};
+
+public:
+ void Initialize();
+ int GetIndex(int staticID);
+
+ StaticInfo& operator [](int staticID);
+
+ // Iterators
+ auto begin() { return _statics.begin(); } // Non-const begin
+ auto end() { return _statics.end(); } // Non-const end
+ auto begin() const { return _statics.cbegin(); } // Const begin
+ auto end() const { return _statics.cend(); } // Const end
+ auto cbegin() const { return _statics.cbegin(); } // Explicit const begin
+ auto cend() const { return _statics.cend(); } // Explicit const end
};
extern ObjectHandler Objects;
-extern StaticInfo StaticObjects[MAX_STATICS];
+extern StaticHandler Statics;
void InitializeGameFlags();
void InitializeSpecialEffects();
diff --git a/TombEngine/Game/animation.cpp b/TombEngine/Game/animation.cpp
index f29ee8590..9949d097c 100644
--- a/TombEngine/Game/animation.cpp
+++ b/TombEngine/Game/animation.cpp
@@ -39,7 +39,7 @@ static void PerformAnimCommands(ItemInfo& item, bool isFrameBased)
return;
// Get command data pointer.
- short* commandDataPtr = &g_Level.Commands[anim.CommandIndex];
+ int* commandDataPtr = &g_Level.Commands[anim.CommandIndex];
for (int i = anim.NumCommands; i > 0; i--)
{
@@ -118,45 +118,11 @@ static void PerformAnimCommands(ItemInfo& item, bool isFrameBased)
int frameNumber = commandDataPtr[0];
if (isFrameBased && item.Animation.FrameNumber == frameNumber)
{
- // Get sound ID and sound environment flag from packed data.
- int soundID = commandDataPtr[1] & 0xFFF; // Exclude last 4 bits for sound ID.
- int soundEnvFlag = commandDataPtr[1] & 0xF000; // Keep only last 4 bits for sound environment flag.
+ // Get sound ID and sound environment flag.
+ int soundID = commandDataPtr[1];
+ auto requiredSoundEnv = (SoundEnvironment)commandDataPtr[2];
- // FAILSAFE.
- if (item.RoomNumber == NO_VALUE)
- {
- SoundEffect(soundID, &item.Pose, SoundEnvironment::Always);
- commandDataPtr += 2;
- break;
- }
-
- // Get required sound environment from flag.
- auto requiredSoundEnv = SoundEnvironment::Always;
- switch (soundEnvFlag)
- {
- default:
- case 0:
- requiredSoundEnv = SoundEnvironment::Always;
- break;
-
- case (1 << 14):
- requiredSoundEnv = SoundEnvironment::Land;
- break;
-
- case (1 << 15):
- requiredSoundEnv = SoundEnvironment::ShallowWater;
- break;
-
- case (1 << 12):
- requiredSoundEnv = SoundEnvironment::Swamp;
- break;
-
- case (1 << 13):
- requiredSoundEnv = SoundEnvironment::Underwater;
- break;
- }
-
- int roomNumberAtPos = GetPointCollision(item).GetRoomNumber();
+ int roomNumberAtPos = (item.RoomNumber == NO_VALUE) ? Camera.pos.RoomNumber : GetPointCollision(item).GetRoomNumber();
bool isWater = TestEnvironment(ENV_FLAG_WATER, roomNumberAtPos);
bool isSwamp = TestEnvironment(ENV_FLAG_SWAMP, roomNumberAtPos);
@@ -201,13 +167,13 @@ static void PerformAnimCommands(ItemInfo& item, bool isFrameBased)
SoundEffect(soundID, &item.Pose, *soundEnv);
}
- commandDataPtr += 2;
- }
+ commandDataPtr += 3;
break;
+ }
case AnimCommandType::Flipeffect:
if (isFrameBased && item.Animation.FrameNumber == commandDataPtr[0])
- DoFlipEffect((commandDataPtr[1] & 0x3FFF), &item);
+ DoFlipEffect(commandDataPtr[1], &item);
commandDataPtr += 2;
break;
@@ -319,10 +285,13 @@ void AnimateItem(ItemInfo* item)
}
else
{
- item->Animation.Velocity.y += GetEffectiveGravity(item->Animation.Velocity.y);
- item->Animation.Velocity.z += animAccel.z;
+ if (item->Animation.ActiveState != LS_FLY_CHEAT)
+ {
+ item->Animation.Velocity.y += GetEffectiveGravity(item->Animation.Velocity.y);
+ item->Animation.Velocity.z += animAccel.z;
- item->Pose.Position.y += item->Animation.Velocity.y;
+ item->Pose.Position.y += item->Animation.Velocity.y;
+ }
}
}
else
diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp
index 0c55a0508..1a79ecfc8 100644
--- a/TombEngine/Game/camera.cpp
+++ b/TombEngine/Game/camera.cpp
@@ -49,8 +49,8 @@ struct OLD_CAMERA
Vector3i target;
};
+bool ItemCameraOn;
GameVector LastTarget;
-
GameVector LastIdeal;
GameVector Ideals[5];
OLD_CAMERA OldCam;
@@ -60,7 +60,6 @@ GameVector LookCamPosition;
GameVector LookCamTarget;
Vector3i CamOldPos;
CAMERA_INFO Camera;
-ObjectCameraInfo ItemCamera;
GameVector ForcedFixedCamera;
int UseForcedFixedCamera;
@@ -403,13 +402,13 @@ void ObjCamera(ItemInfo* camSlotId, int camMeshId, ItemInfo* targetItem, int tar
{
//camSlotId and targetItem stay the same object until I know how to expand targetItem to another object.
//activates code below -> void CalculateCamera().
- ItemCamera.ItemCameraOn = cond;
+ ItemCameraOn = cond;
UpdateCameraElevation();
//get mesh 0 coordinates.
auto pos = GetJointPosition(camSlotId, 0, Vector3i::Zero);
- auto dest = Vector3(pos.x, pos.y, pos.z);
+ auto dest = Vector3(pos.x, pos.y, pos.z) + camSlotId->Pose.Position.ToVector3();
GameVector from = GameVector(dest, camSlotId->RoomNumber);
Camera.fixedCamera = true;
@@ -420,7 +419,7 @@ void ObjCamera(ItemInfo* camSlotId, int camMeshId, ItemInfo* targetItem, int tar
void ClearObjCamera()
{
- ItemCamera.ItemCameraOn = false;
+ ItemCameraOn = false;
}
void MoveObjCamera(GameVector* ideal, ItemInfo* camSlotId, int camMeshId, ItemInfo* targetItem, int targetMeshId)
@@ -483,17 +482,10 @@ void MoveObjCamera(GameVector* ideal, ItemInfo* camSlotId, int camMeshId, ItemIn
speed = 2;
}
- //actual movement of the target.
+ // Actual movement of the target.
Camera.target.x += (pos2.x - Camera.target.x) / speed;
Camera.target.y += (pos2.y - Camera.target.y) / speed;
Camera.target.z += (pos2.z - Camera.target.z) / speed;
-
- if (ItemCamera.LastAngle != position)
- {
- ItemCamera.LastAngle = Vector3i(ItemCamera.LastAngle.x = angle.x,
- ItemCamera.LastAngle.y = angle.y,
- ItemCamera.LastAngle.z = angle.z);
- }
}
void RefreshFixedCamera(short camNumber)
@@ -522,6 +514,13 @@ void ChaseCamera(ItemInfo* item)
else if (Camera.actualElevation < ANGLE(-85.0f))
Camera.actualElevation = ANGLE(-85.0f);
+ // Force item position after exiting look mode to avoid weird movements near walls.
+ if (Camera.oldType == CameraType::Look)
+ {
+ Camera.target.x = item->Pose.Position.x;
+ Camera.target.z = item->Pose.Position.z;
+ }
+
int distance = Camera.targetDistance * phd_cos(Camera.actualElevation);
auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber);
@@ -1073,8 +1072,7 @@ static bool CalculateDeathCamera(const ItemInfo& item)
return true;
// Special death animations.
- if (item.Animation.AnimNumber == LA_SPIKE_DEATH ||
- item.Animation.AnimNumber == LA_BOULDER_DEATH ||
+ if (item.Animation.AnimNumber == LA_SPIKE_DEATH ||
item.Animation.AnimNumber == LA_TRAIN_OVERBOARD_DEATH)
{
return true;
@@ -1095,10 +1093,8 @@ void CalculateCamera(const CollisionInfo& coll)
return;
}
- if (ItemCamera.ItemCameraOn)
- {
+ if (ItemCameraOn)
return;
- }
if (UseForcedFixedCamera != 0)
{
@@ -1260,7 +1256,8 @@ void CalculateCamera(const CollisionInfo& coll)
if (isFixedCamera == Camera.fixedCamera)
{
Camera.fixedCamera = false;
- if (Camera.speed != 1 &&
+
+ if (Camera.speed != 1 && Camera.oldType != CameraType::Look &&
!Lara.Control.Look.IsUsingBinoculars)
{
if (TargetSnaps <= 8)
@@ -1331,8 +1328,13 @@ void CalculateCamera(const CollisionInfo& coll)
bool TestBoundsCollideCamera(const GameBoundingBox& bounds, const Pose& pose, short radius)
{
- auto sphere = BoundingSphere(Camera.pos.ToVector3(), radius);
- return sphere.Intersects(bounds.ToBoundingOrientedBox(pose));
+ auto camSphere = BoundingSphere(Camera.pos.ToVector3(), radius);
+ auto boundsSphere = BoundingSphere(pose.Position.ToVector3(), bounds.GetExtents().Length());
+
+ if (!camSphere.Intersects(boundsSphere))
+ return false;
+
+ return camSphere.Intersects(bounds.ToBoundingOrientedBox(pose));
}
float GetParticleDistanceFade(const Vector3i& pos)
@@ -1507,9 +1509,12 @@ void ItemsCollideCamera()
if (TestBoundsCollideCamera(bounds, item->Pose, CAMERA_RADIUS))
ItemPushCamera(&bounds, &item->Pose, RADIUS);
- DrawDebugBox(
- bounds.ToBoundingOrientedBox(item->Pose),
- Vector4(1.0f, 0.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats);
+ if (DebugMode)
+ {
+ DrawDebugBox(
+ bounds.ToBoundingOrientedBox(item->Pose),
+ Vector4(1.0f, 0.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats);
+ }
}
// Done.
@@ -1530,9 +1535,12 @@ void ItemsCollideCamera()
if (TestBoundsCollideCamera(bounds, mesh->pos, CAMERA_RADIUS))
ItemPushCamera(&bounds, &mesh->pos, RADIUS);
- DrawDebugBox(
- bounds.ToBoundingOrientedBox(mesh->pos),
- Vector4(1.0f, 0.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats);
+ if (DebugMode)
+ {
+ DrawDebugBox(
+ bounds.ToBoundingOrientedBox(mesh->pos),
+ Vector4(1.0f, 0.0f, 0.0f, 1.0f), RendererDebugPage::CollisionStats);
+ }
}
// Done.
diff --git a/TombEngine/Game/camera.h b/TombEngine/Game/camera.h
index 59db6e4f8..6a6939c94 100644
--- a/TombEngine/Game/camera.h
+++ b/TombEngine/Game/camera.h
@@ -1,6 +1,7 @@
#pragma once
#include "Game/items.h"
#include "Math/Math.h"
+#include "Specific/Clock.h"
struct CollisionInfo;
@@ -53,12 +54,6 @@ struct CAMERA_INFO
bool DisableInterpolation = false;
};
-struct ObjectCameraInfo
-{
- GameVector LastAngle;
- bool ItemCameraOn;
-};
-
enum CAMERA_FLAGS
{
CF_NONE = 0,
@@ -67,7 +62,7 @@ enum CAMERA_FLAGS
CF_CHASE_OBJECT = 3,
};
-constexpr auto FADE_SCREEN_SPEED = 16.0f / 255.0f;
+constexpr auto FADE_SCREEN_SPEED = 2.0f / FPS;
constexpr auto DEFAULT_FOV = 80.0f;
extern CAMERA_INFO Camera;
diff --git a/TombEngine/Game/collision/Point.cpp b/TombEngine/Game/collision/Point.cpp
index 652421c8c..66806e075 100644
--- a/TombEngine/Game/collision/Point.cpp
+++ b/TombEngine/Game/collision/Point.cpp
@@ -408,7 +408,7 @@ namespace TEN::Collision::Point
short slopeAngle = Geometry::GetSurfaceSlopeAngle(GetFloorNormal());
short steepSlopeAngle = (GetFloorBridgeItemNumber() != NO_VALUE) ?
DEFAULT_STEEP_FLOOR_SLOPE_ANGLE :
- GetBottomSector().GetSurfaceIllegalSlopeAngle(_position.x, _position.z, true);
+ GetBottomSector().GetSurfaceSteepSlopeAngle(_position.x, _position.z, true);
return (abs(slopeAngle) >= steepSlopeAngle);
}
@@ -418,7 +418,7 @@ namespace TEN::Collision::Point
short slopeAngle = Geometry::GetSurfaceSlopeAngle(GetCeilingNormal(), -Vector3::UnitY);
short steepSlopeAngle = (GetCeilingBridgeItemNumber() != NO_VALUE) ?
DEFAULT_STEEP_CEILING_SLOPE_ANGLE :
- GetTopSector().GetSurfaceIllegalSlopeAngle(_position.x, _position.z, false);
+ GetTopSector().GetSurfaceSteepSlopeAngle(_position.x, _position.z, false);
return (abs(slopeAngle) >= steepSlopeAngle);
}
diff --git a/TombEngine/Game/collision/collide_item.cpp b/TombEngine/Game/collision/collide_item.cpp
index eaaa9263b..7970ec5dc 100644
--- a/TombEngine/Game/collision/collide_item.cpp
+++ b/TombEngine/Game/collision/collide_item.cpp
@@ -27,6 +27,7 @@ using namespace TEN::Collision::Sphere;
using namespace TEN::Math;
constexpr auto ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD = 6;
+constexpr auto COLLIDABLE_BOUNDS_THRESHOLD = 4;
// Globals
@@ -95,7 +96,6 @@ void GenericSphereBoxCollision(short itemNumber, ItemInfo* playerItem, Collision
CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible, bool ignorePlayer, float customRadius, ObjectCollectionMode mode)
{
- constexpr auto EXTENTS_LENGTH_MIN = 2.0f;
constexpr auto ROUGH_BOX_HEIGHT_MIN = BLOCK(1 / 8.0f);
auto collObjects = CollidedObjectData{};
@@ -105,20 +105,20 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
// Establish parameters of colliding item.
const auto& collidingBounds = GetBestFrame(collidingItem).BoundingBox;
- auto collidingExtents = collidingBounds.GetExtents();
- auto collidingSphere = BoundingSphere(collidingBounds.GetCenter() + collidingItem.Pose.Position.ToVector3(), collidingExtents.Length());
- auto collidingCircle = Vector3(collidingSphere.Center.x, collidingSphere.Center.z, (customRadius > 0.0f) ? customRadius : std::hypot(collidingExtents.x, collidingExtents.z));
+
+ // Quickly discard collision if colliding item bounds are below tolerance threshold.
+ if (!customRadius && collidingBounds.GetExtents().Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
+ return collObjects;
// Convert bounding box to DX bounds.
auto convertedBounds = collidingBounds.ToBoundingOrientedBox(collidingItem.Pose);
+ // Create conservative AABB for rough tests.
+ auto collidingAabb = collidingBounds.ToConservativeBoundingBox(collidingItem.Pose);
+
// Override extents if specified.
if (customRadius > 0.0f)
- convertedBounds.Extents = Vector3(customRadius);
-
- // Quickly discard collision if colliding item bounds are below tolerance threshold.
- if (collidingSphere.Radius <= EXTENTS_LENGTH_MIN)
- return collObjects;
+ collidingAabb = BoundingBox(collidingItem.Pose.Position.ToVector3(), Vector3(customRadius));
// Run through neighboring rooms.
const auto& room = g_Level.Rooms[collidingItem.RoomNumber];
@@ -151,13 +151,12 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
continue;
// Ignore items not feasible for collision.
- if (item.Index == collidingItem.Index ||
- item.Flags & IFLAG_KILLED || item.MeshBits == NO_JOINT_BITS ||
- (object.drawRoutine == nullptr && !item.IsLara()) ||
- (object.collision == nullptr && !item.IsLara()))
- {
+ if (item.Index == collidingItem.Index || item.Flags & IFLAG_KILLED || item.MeshBits == NO_JOINT_BITS)
+ continue;
+
+ // Ignore non-collidable non-player.
+ if (!item.IsLara() && (!item.Collidable || object.drawRoutine == nullptr || object.collision == nullptr))
continue;
- }
// HACK: Ignore UPV and big gun.
if ((item.ObjectNumber == ID_UPV || item.ObjectNumber == ID_BIGGUN) && item.HitPoints == 1)
@@ -168,34 +167,19 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
if (dist > COLLISION_CHECK_DISTANCE)
continue;
- const auto& bounds = GetBestFrame(item).BoundingBox;
- auto extents = bounds.GetExtents();
-
// If item bounding box extents is below tolerance threshold, discard object.
- if (extents.Length() <= EXTENTS_LENGTH_MIN)
+ const auto& bounds = GetBestFrame(item).BoundingBox;
+ if (bounds.GetExtents().Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
continue;
- // Test rough vertical distance to discard objects not intersecting vertically.
- if (((collidingItem.Pose.Position.y + collidingBounds.Y1) - ROUGH_BOX_HEIGHT_MIN) >
- ((item.Pose.Position.y + bounds.Y2) + ROUGH_BOX_HEIGHT_MIN))
- {
- continue;
- }
- if (((collidingItem.Pose.Position.y + collidingBounds.Y2) + ROUGH_BOX_HEIGHT_MIN) <
- ((item.Pose.Position.y + bounds.Y1) - ROUGH_BOX_HEIGHT_MIN))
- {
- continue;
- }
-
- // Test rough circle intersection to discard objects not intersecting horizontally.
- auto circle = Vector3(item.Pose.Position.x, item.Pose.Position.z, std::hypot(extents.x, extents.z));
- if (!Geometry::CircleIntersects(circle, collidingCircle))
+ // Test conservative AABB intersection.
+ auto aabb = bounds.ToConservativeBoundingBox(item.Pose);
+ if (!aabb.Intersects(collidingAabb))
continue;
- auto box = bounds.ToBoundingOrientedBox(item.Pose);
-
- // Test accurate box intersection.
- if (box.Intersects(convertedBounds))
+ // Test accurate OBB intersection.
+ auto obb = bounds.ToBoundingOrientedBox(item.Pose);
+ if (obb.Intersects(convertedBounds))
collObjects.Items.push_back(&item);
}
while (itemNumber != NO_VALUE);
@@ -217,36 +201,19 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
if (dist > COLLISION_CHECK_DISTANCE)
continue;
- const auto& bounds = GetBoundsAccurate(staticObj, false);
-
- // Test rough vertical distance to discard statics not intersecting vertically.
- if (((collidingItem.Pose.Position.y + collidingBounds.Y1) - ROUGH_BOX_HEIGHT_MIN) >
- ((staticObj.pos.Position.y + bounds.Y2) + ROUGH_BOX_HEIGHT_MIN))
- {
- continue;
- }
- if (((collidingItem.Pose.Position.y + collidingBounds.Y2) + ROUGH_BOX_HEIGHT_MIN) <
- ((staticObj.pos.Position.y + bounds.Y1) - ROUGH_BOX_HEIGHT_MIN))
- {
- continue;
- }
-
- // Test rough circle intersection to discard statics not intersecting horizontally.
- auto circle = Vector3(staticObj.pos.Position.x, staticObj.pos.Position.z, (bounds.GetExtents() * Vector3(1.0f, 0.0f, 1.0f)).Length());
- if (!Geometry::CircleIntersects(circle, collidingCircle))
- continue;
-
// Skip if either bounding box has any zero extent (not a collidable volume).
- if (bounds.GetExtents().Length() <= EXTENTS_LENGTH_MIN)
+ const auto& bounds = GetBoundsAccurate(staticObj, false);
+ if (bounds.GetExtents().Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
continue;
- if (collidingBounds.GetExtents().Length() <= EXTENTS_LENGTH_MIN)
+ // Test conservative AABB intersection.
+ auto aabb = bounds.ToConservativeBoundingBox(staticObj.pos);
+ if (!aabb.Intersects(collidingAabb))
continue;
- auto box = bounds.ToBoundingOrientedBox(staticObj.pos.Position);
-
- // Test accurate box intersection.
- if (box.Intersects(convertedBounds))
+ // Test accurate OBB intersection.
+ auto obb = bounds.ToBoundingOrientedBox(staticObj.pos.Position);
+ if (obb.Intersects(convertedBounds))
collObjects.Statics.push_back(&staticObj);
}
}
@@ -971,13 +938,13 @@ bool CollideSolidBounds(ItemInfo* item, const GameBoundingBox& box, const Pose&
{
bool result = false;
+ // Ignore processing null bounds.
+ if (box.GetExtents().Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
+ return false;
+
// Get DX static bounds in global coordinates.
auto staticBounds = box.ToBoundingOrientedBox(pose);
- // Ignore processing null bounds.
- if (Vector3(staticBounds.Extents) == Vector3::Zero)
- return false;
-
// Get local TR bounds and DX item bounds in global coordinates.
auto itemBBox = GameBoundingBox(item);
auto itemBounds = itemBBox.ToBoundingOrientedBox(item->Pose);
@@ -1805,7 +1772,7 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll)
if (isPlayer)
{
- GetLaraInfo(*item).HitDirection = -1;
+ GetLaraInfo(*item).HitDirection = NO_VALUE;
if (item->HitPoints <= 0)
return;
@@ -1874,10 +1841,9 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll)
if (linkItem.HitPoints <= 0 || linkItem.HitPoints == NOT_TARGETABLE)
continue;
- if (isHarmless || abs(item->Animation.Velocity.z) < VEHICLE_COLLISION_TERMINAL_VELOCITY ||
- object.damageType == DamageMode::None)
+ if (isHarmless || abs(item->Animation.Velocity.z) < VEHICLE_COLLISION_TERMINAL_VELOCITY)
{
- // If vehicle is harmless, enemy is non-damageable, or speed is too low, push enemy.
+ // If vehicle is harmless or speed is too low, just push enemy.
ItemPushItem(&linkItem, item, coll, false, 0);
continue;
}
@@ -1921,7 +1887,7 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll)
// HACK: Shatter statics only by harmful vehicles.
if (!isPlayer &&
!isHarmless && abs(item->Animation.Velocity.z) > VEHICLE_COLLISION_TERMINAL_VELOCITY &&
- StaticObjects[staticObject.staticNumber].shatterType != ShatterType::None)
+ Statics[staticObject.staticNumber].shatterType != ShatterType::None)
{
SoundEffect(GetShatterSound(staticObject.staticNumber), &staticObject.pos);
ShatterObject(nullptr, &staticObject, -128, item->RoomNumber, 0);
diff --git a/TombEngine/Game/collision/floordata.cpp b/TombEngine/Game/collision/floordata.cpp
index d35ce9d95..30af7c2c8 100644
--- a/TombEngine/Game/collision/floordata.cpp
+++ b/TombEngine/Game/collision/floordata.cpp
@@ -40,10 +40,17 @@ int FloorInfo::GetSurfaceTriangleID(int x, int z, bool isFloor) const
{
constexpr auto TRI_ID_0 = 0;
constexpr auto TRI_ID_1 = 1;
+
+ static const auto ROT_MATRIX_0 = Matrix::CreateRotationZ(TO_RAD(SectorSurfaceData::SPLIT_ANGLE_0));
+ static const auto ROT_MATRIX_1 = Matrix::CreateRotationZ(TO_RAD(SectorSurfaceData::SPLIT_ANGLE_1));
+ // Get matrix.
+ const auto& rotMatrix = isFloor ?
+ ((FloorSurface.SplitAngle == SectorSurfaceData::SPLIT_ANGLE_0) ? ROT_MATRIX_0 : ROT_MATRIX_1) :
+ ((CeilingSurface.SplitAngle == SectorSurfaceData::SPLIT_ANGLE_0) ? ROT_MATRIX_0 : ROT_MATRIX_1);
+
// Calculate bias.
auto sectorPoint = GetSectorPoint(x, z).ToVector2();
- auto rotMatrix = Matrix::CreateRotationZ(TO_RAD(isFloor ? FloorSurface.SplitAngle : CeilingSurface.SplitAngle));
float bias = Vector2::Transform(sectorPoint, rotMatrix).x;
// Return triangle ID according to bias.
@@ -76,7 +83,7 @@ Vector3 FloorInfo::GetSurfaceNormal(int x, int z, bool isFloor) const
return GetSurfaceNormal(triID, isFloor);
}
-short FloorInfo::GetSurfaceIllegalSlopeAngle(int x, int z, bool isFloor) const
+short FloorInfo::GetSurfaceSteepSlopeAngle(int x, int z, bool isFloor) const
{
const auto& tri = GetSurfaceTriangle(x, z, isFloor);
return tri.SteepSlopeAngle;
@@ -223,21 +230,16 @@ int FloorInfo::GetSurfaceHeight(const Vector3i& pos, bool isFloor) const
auto bridgeCeilingHeight = bridge.GetCeilingHeight(bridgeItem, pos);
// 2.1) Get bridge surface height.
- auto bridgeSurfaceHeight = isFloor ? bridge.GetFloorHeight(bridgeItem, pos) : bridge.GetCeilingHeight(bridgeItem, pos);
+ auto bridgeSurfaceHeight = isFloor ? bridgeFloorHeight : bridgeCeilingHeight;
if (!bridgeSurfaceHeight.has_value())
continue;
- // Use bridge midpoint to decide whether to return bridge height or room height, in case probe point
- // is located within the bridge. Without it, dynamic bridges may fail while Lara is standing on them.
- int thickness = bridge.GetCeilingBorder(bridgeItem) - bridge.GetFloorBorder(bridgeItem);
- int midpoint = bridgeItem.Pose.Position.y + thickness / 2;
-
// 2.2) Track closest floor or ceiling height.
if (isFloor)
{
// Test if bridge floor height is closer.
- if (midpoint >= pos.y && // Bridge midpoint is below position.
+ if (*bridgeCeilingHeight >= pos.y && // Bridge midpoint is below position.
*bridgeSurfaceHeight < floorHeight && // Bridge floor height is above current closest floor height.
*bridgeSurfaceHeight >= ceilingHeight) // Bridge ceiling height is below sector ceiling height.
{
@@ -247,7 +249,7 @@ int FloorInfo::GetSurfaceHeight(const Vector3i& pos, bool isFloor) const
else
{
// Test if bridge ceiling height is closer.
- if (midpoint <= pos.y && // Bridge midpoint is above position.
+ if (*bridgeFloorHeight <= pos.y && // Bridge midpoint is above position.
*bridgeSurfaceHeight > ceilingHeight && // Bridge ceiling height is below current closest ceiling height.
*bridgeSurfaceHeight <= floorHeight) // Bridge floor height is above sector floor height.
{
@@ -879,7 +881,7 @@ namespace TEN::Collision::Floordata
}
// Updates BridgeItem for all blocks which are enclosed by bridge bounds.
- void UpdateBridgeItem(const ItemInfo& item, bool forceRemoval)
+ void UpdateBridgeItem(const ItemInfo& item, BridgeUpdateType updateType)
{
constexpr auto SECTOR_EXTENTS = Vector3(BLOCK(0.5f));
@@ -891,7 +893,7 @@ namespace TEN::Collision::Floordata
// Force removal if item was killed.
if (item.Flags & IFLAG_KILLED)
- forceRemoval = true;
+ updateType = BridgeUpdateType::Remove;
// Get bridge OBB.
auto bridgeBox = GameBoundingBox(&item).ToBoundingOrientedBox(item.Pose);
@@ -919,10 +921,11 @@ namespace TEN::Collision::Floordata
float offZ = pZ - item.Pose.Position.z;
// Clean previous bridge state.
- RemoveBridge(item.Index, offX, offZ);
+ if (updateType != BridgeUpdateType::Initialize)
+ RemoveBridge(item.Index, offX, offZ);
- // In sweep mode; don't try readding to sector.
- if (forceRemoval)
+ // In removal mode; don't try re-adding to sector.
+ if (updateType == BridgeUpdateType::Remove)
continue;
// Sector is outside enclosed AABB space; ignore precise check.
diff --git a/TombEngine/Game/collision/floordata.h b/TombEngine/Game/collision/floordata.h
index 56dff4dec..19d577463 100644
--- a/TombEngine/Game/collision/floordata.h
+++ b/TombEngine/Game/collision/floordata.h
@@ -55,6 +55,13 @@ enum class ClimbDirectionFlags
West = 1 << 11
};
+enum class BridgeUpdateType
+{
+ Normal,
+ Initialize,
+ Remove
+};
+
// NOTE: Describes vertical room location.
class RoomVector
{
@@ -154,7 +161,7 @@ public:
const SectorSurfaceTriangleData& GetSurfaceTriangle(int x, int z, bool isFloor) const;
Vector3 GetSurfaceNormal(int triID, bool isFloor) const;
Vector3 GetSurfaceNormal(int x, int z, bool isFloor) const;
- short GetSurfaceIllegalSlopeAngle(int x, int z, bool isFloor) const;
+ short GetSurfaceSteepSlopeAngle(int x, int z, bool isFloor) const;
MaterialType GetSurfaceMaterial(int x, int z, bool isFloor) const;
std::optional GetNextRoomNumber(int x, int z, bool isBelow) const;
@@ -201,7 +208,7 @@ namespace TEN::Collision::Floordata
std::optional GetBridgeItemIntersect(const ItemInfo& item, const Vector3i& pos, bool useBottomHeight);
int GetBridgeBorder(const ItemInfo& item, bool isBottom);
- void UpdateBridgeItem(const ItemInfo& item, bool forceRemoval = false);
+ void UpdateBridgeItem(const ItemInfo& item, BridgeUpdateType updateType = BridgeUpdateType::Normal);
bool TestMaterial(MaterialType refMaterial, const std::vector& materials);
diff --git a/TombEngine/Game/control/box.cpp b/TombEngine/Game/control/box.cpp
index 998794061..91ecaf960 100644
--- a/TombEngine/Game/control/box.cpp
+++ b/TombEngine/Game/control/box.cpp
@@ -1297,15 +1297,11 @@ void GetAITarget(CreatureInfo* creature)
{
auto* enemy = creature->Enemy;
- int enemyObjectID = 0;
+ short enemyObjectNumber;
if (enemy)
- {
- enemyObjectID = enemy->ObjectNumber;
- }
+ enemyObjectNumber = enemy->ObjectNumber;
else
- {
- enemyObjectID = NO_VALUE;
- }
+ enemyObjectNumber = NO_VALUE;
auto* item = &g_Level.Items[creature->ItemNumber];
@@ -1333,12 +1329,12 @@ void GetAITarget(CreatureInfo* creature)
}
else if (!creature->Patrol)
{
- if (enemyObjectID != ID_AI_PATROL1)
- FindAITargetObject(*item, ID_AI_PATROL1);
+ if (enemyObjectNumber != ID_AI_PATROL1)
+ FindAITargetObject(creature, ID_AI_PATROL1);
}
- else if (enemyObjectID != ID_AI_PATROL2)
+ else if (enemyObjectNumber != ID_AI_PATROL2)
{
- FindAITargetObject(*item, ID_AI_PATROL2);
+ FindAITargetObject(creature, ID_AI_PATROL2);
}
else if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < REACHED_GOAL_RADIUS &&
abs(enemy->Pose.Position.y - item->Pose.Position.y) < REACHED_GOAL_RADIUS &&
@@ -1354,8 +1350,8 @@ void GetAITarget(CreatureInfo* creature)
// First if was removed probably after TR3 and was it used by monkeys?
/*if (!(item->aiBits & MODIFY) && !creature->hurtByLara)
creature->enemy = LaraItem;
- else*/ if (enemyObjectID != ID_AI_AMBUSH)
- FindAITargetObject(*item, ID_AI_AMBUSH);
+ else*/ if (enemyObjectNumber != ID_AI_AMBUSH)
+ FindAITargetObject(creature, ID_AI_AMBUSH);
/*else if (item->objectNumber == ID_MONKEY)
return;*/
else if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < REACHED_GOAL_RADIUS &&
@@ -1366,7 +1362,6 @@ void GetAITarget(CreatureInfo* creature)
creature->ReachedGoal = true;
creature->Enemy = LaraItem;
item->AIBits &= ~(AMBUSH /* | MODIFY*/);
-
if (item->AIBits != MODIFY)
{
item->AIBits |= GUARD;
@@ -1386,9 +1381,9 @@ void GetAITarget(CreatureInfo* creature)
{
item->AIBits &= ~FOLLOW;
}
- else if (enemyObjectID != ID_AI_FOLLOW)
+ else if (enemyObjectNumber != ID_AI_FOLLOW)
{
- FindAITargetObject(*item, ID_AI_FOLLOW);
+ FindAITargetObject(creature, ID_AI_FOLLOW);
}
else if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < REACHED_GOAL_RADIUS &&
abs(enemy->Pose.Position.y - item->Pose.Position.y) < REACHED_GOAL_RADIUS &&
@@ -1398,18 +1393,17 @@ void GetAITarget(CreatureInfo* creature)
item->AIBits &= ~FOLLOW;
}
}
-
- /*else if (item->ObjectNumber == ID_MONKEY && item->CarriedItem == NO_VALUE)
+ /*else if (item->objectNumber == ID_MONKEY && item->carriedItem == NO_VALUE)
{
- if (item->AIBits != MODIFY)
+ if (item->aiBits != MODIFY)
{
- if (enemyObjectID != ID_SMALLMEDI_ITEM)
- FindAITargetObject(*item, ID_SMALLMEDI_ITEM);
+ if (enemyObjectNumber != ID_SMALLMEDI_ITEM)
+ FindAITargetObject(creature, ID_SMALLMEDI_ITEM);
}
else
{
- if (enemyObjectID != ID_KEY_ITEM4)
- FindAITargetObject(*item, ID_KEY_ITEM4);
+ if (enemyObjectNumber != ID_KEY_ITEM4)
+ FindAITargetObject(creature, ID_KEY_ITEM4);
}
}*/
}
@@ -1438,101 +1432,69 @@ void FindAITarget(CreatureInfo* creature, short objectNumber)
}
}
-void FindAITargetObject(ItemInfo& item, GAME_OBJECT_ID objectID, std::optional ocb, std::optional checkSameZone)
+void FindAITargetObject(CreatureInfo* creature, int objectNumber)
{
- auto& creature = *GetCreatureInfo(&item);
+ const auto& item = g_Level.Items[creature->ItemNumber];
- auto data = AITargetData{};
- data.CheckDistance = false;
- data.CheckOcb = ocb.has_value();
- data.ObjectID = objectID;
- data.Ocb = ocb.value_or(item.ItemFlags[3]);
- data.CheckSameZone = checkSameZone.value_or(true);
-
- if (FindAITargetObject(item, data))
- {
- *creature.AITarget = data.FoundItem;
- creature.Enemy = creature.AITarget;
- }
+ FindAITargetObject(creature, objectNumber, item.ItemFlags[3], true);
}
-bool FindAITargetObject(ItemInfo& item, AITargetData& data)
+void FindAITargetObject(CreatureInfo* creature, int objectNumber, int ocb, bool checkSameZone)
{
+ auto& item = g_Level.Items[creature->ItemNumber];
+
if (g_Level.AIObjects.empty())
- return false;
+ return;
- auto& creature = *GetCreatureInfo(&item);
+ AI_OBJECT* foundObject = nullptr;
- const AI_OBJECT* foundAIObject = nullptr;
-
- for (const auto& aiObject : g_Level.AIObjects)
+ for (auto& aiObject : g_Level.AIObjects)
{
- // Check if object IDs match.
- if (aiObject.objectNumber != data.ObjectID)
- continue;
-
- // Check if room is valid.
- if (aiObject.roomNumber == NO_VALUE)
- continue;
-
- // Check if distance is valid.
- if (data.CheckDistance)
+ if (aiObject.objectNumber == objectNumber &&
+ aiObject.triggerFlags == ocb &&
+ aiObject.roomNumber != NO_VALUE)
{
- if (Vector3i::Distance(item.Pose.Position, aiObject.pos.Position) > data.DistanceMax)
- continue;
- }
-
- // Check if OCBs match (useful for pathfinding).
- if (data.CheckOcb)
- {
- if (aiObject.triggerFlags != data.Ocb)
- continue;
- }
-
- // Check if zone IDs match.
- if (data.CheckSameZone)
- {
- int* zone = g_Level.Zones[(int)creature.LOT.Zone][(int)FlipStatus].data();
+ int* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data();
auto* room = &g_Level.Rooms[item.RoomNumber];
- // NOTE: Avoid changing box ID of item or AI item so a local variable isn't required when searching for AI object near it.
- int boxID = GetSector(room, item.Pose.Position.x - room->Position.x, item.Pose.Position.z - room->Position.z)->PathfindingBoxID;
+ item.BoxNumber = GetSector(room, item.Pose.Position.x - room->Position.x, item.Pose.Position.z - room->Position.z)->PathfindingBoxID;
room = &g_Level.Rooms[aiObject.roomNumber];
- int aiBoxID = GetSector(room, aiObject.pos.Position.x - room->Position.x, aiObject.pos.Position.z - room->Position.z)->PathfindingBoxID;
+ aiObject.boxNumber = GetSector(room, aiObject.pos.Position.x - room->Position.x, aiObject.pos.Position.z - room->Position.z)->PathfindingBoxID;
- // Box is invalid or zones don't match; continue.
- if (boxID == NO_VALUE || aiBoxID == NO_VALUE)
- continue;
+ if (item.BoxNumber == NO_VALUE || aiObject.boxNumber == NO_VALUE)
+ return;
- // Zone is invalid; continue.
- if (zone[boxID] != zone[aiBoxID])
- continue;
+ if (checkSameZone && (zone[item.BoxNumber] != zone[aiObject.boxNumber]))
+ return;
+
+ // Don't check for same zone. Needed for Sophia Leigh.
+ foundObject = &aiObject;
}
-
- // HACK: Don't check for matching zone. Needed for Sophia Leigh.
- foundAIObject = &aiObject;
}
- if (foundAIObject == nullptr)
- return false;
+ if (foundObject == nullptr)
+ return;
- auto aiItem = ItemInfo{};
- aiItem.ObjectNumber = foundAIObject->objectNumber;
- aiItem.RoomNumber = foundAIObject->roomNumber;
- aiItem.Pose.Position = foundAIObject->pos.Position;
- aiItem.Pose.Orientation.y = foundAIObject->pos.Orientation.y;
- aiItem.Flags = foundAIObject->flags;
- aiItem.TriggerFlags = foundAIObject->triggerFlags;
- aiItem.BoxNumber = foundAIObject->boxNumber;
+ auto& aiItem = *creature->AITarget;
- if (!(aiItem.Flags & IFLAG_TRIGGERED))
+ creature->Enemy = &aiItem;
+
+ aiItem.ObjectNumber = foundObject->objectNumber;
+ aiItem.RoomNumber = foundObject->roomNumber;
+ aiItem.Pose.Position = foundObject->pos.Position;
+ aiItem.Pose.Orientation.y = foundObject->pos.Orientation.y;
+ aiItem.Flags = foundObject->flags;
+ aiItem.TriggerFlags = foundObject->triggerFlags;
+ aiItem.BoxNumber = foundObject->boxNumber;
+
+ if (!(creature->AITarget->Flags & ItemFlags::IFLAG_TRIGGERED))
{
- aiItem.Pose.Position.x += CLICK(1) * phd_sin(aiItem.Pose.Orientation.y);
- aiItem.Pose.Position.z += CLICK(1) * phd_cos(aiItem.Pose.Orientation.y);
- }
+ float sinY = phd_sin(creature->AITarget->Pose.Orientation.y);
+ float cosY = phd_cos(creature->AITarget->Pose.Orientation.y);
- data.FoundItem = aiItem;
- return true;
+ creature->AITarget->Pose.Position.x += CLICK(1) * sinY;
+ creature->AITarget->Pose.Position.z += CLICK(1) * cosY;
+ }
}
int TargetReachable(ItemInfo* item, ItemInfo* enemy)
@@ -2187,7 +2149,7 @@ void InitializeItemBoxData()
for (const auto& mesh : room.mesh)
{
long index = ((mesh.pos.Position.z - room.Position.z) / BLOCK(1)) + room.ZSize * ((mesh.pos.Position.x - room.Position.x) / BLOCK(1));
- if (index > room.Sectors.size())
+ if (index >= room.Sectors.size())
continue;
auto* floor = &room.Sectors[index];
diff --git a/TombEngine/Game/control/box.h b/TombEngine/Game/control/box.h
index 1152ec78b..25396411b 100644
--- a/TombEngine/Game/control/box.h
+++ b/TombEngine/Game/control/box.h
@@ -83,23 +83,10 @@ constexpr auto CLIP_ALL = (CLIP_LEFT | CLIP_RIGHT | CLIP_TOP | CLIP_BOTTOM);
constexpr auto CLIP_SECONDARY = 0x10;
-struct AITargetData
-{
- ItemInfo FoundItem = {};
- GAME_OBJECT_ID ObjectID = GAME_OBJECT_ID::ID_NO_OBJECT;
- float DistanceMax = 0.0f;
- int Ocb = NO_VALUE;
-
- bool CheckDistance = false;
- bool CheckSameZone = true;
- bool CheckOcb = false;
-};
-
void GetCreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent);
void CreatureMood(ItemInfo* item, AI_INFO* AI, bool isViolent);
-void FindAITargetObject(ItemInfo& item, GAME_OBJECT_ID objectID, std::optional ocb = std::nullopt, std::optional checkSameZone = std::nullopt);
-bool FindAITargetObject(ItemInfo& item, AITargetData& data);
-
+void FindAITargetObject(CreatureInfo* creature, int objectNumber);
+void FindAITargetObject(CreatureInfo* creature, int objectNumber, int ocb, bool checkSameZone = true);
void GetAITarget(CreatureInfo* creature);
int CreatureVault(short itemNumber, short angle, int vault, int shift);
bool MoveCreature3DPos(Pose* fromPose, Pose* toPose, int velocity, short angleDif, int angleAdd);
diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp
index 43c3bd150..2ba480a49 100644
--- a/TombEngine/Game/control/control.cpp
+++ b/TombEngine/Game/control/control.cpp
@@ -93,20 +93,18 @@ using namespace TEN::Entities::Effects;
constexpr auto DEATH_NO_INPUT_TIMEOUT = 10 * FPS;
constexpr auto DEATH_INPUT_TIMEOUT = 3 * FPS;
-int GameTimer = 0;
-int GlobalCounter = 0;
+int GameTimer = 0;
+int GlobalCounter = 0;
-bool InitializeGame;
-bool DoTheGame;
-bool JustLoaded;
-bool ThreadEnded;
+bool InitializeGame = false;
+bool DoTheGame = false;
+bool JustLoaded = false;
+bool ThreadEnded = false;
int RequiredStartPos;
int CurrentLevel;
int NextLevel;
-int SystemNameHash = 0;
-
bool InItemControlLoop;
short ItemNewRoomNo;
short ItemNewRooms[MAX_ROOMS];
@@ -123,15 +121,19 @@ void DrawPhase(bool isTitle, float interpolationFactor)
{
g_Renderer.RenderTitle(interpolationFactor);
}
- else
+ else if (g_GameFlow->CurrentFreezeMode == FreezeMode::None)
{
g_Renderer.Render(interpolationFactor);
}
+ else
+ {
+ g_Renderer.RenderFreezeMode(interpolationFactor, g_GameFlow->CurrentFreezeMode == FreezeMode::Full);
+ }
g_Renderer.Lock();
}
-GameStatus ControlPhase(bool insideMenu)
+GameStatus GamePhase(bool insideMenu)
{
auto time1 = std::chrono::high_resolution_clock::now();
bool isTitle = (CurrentLevel == 0);
@@ -157,11 +159,17 @@ GameStatus ControlPhase(bool insideMenu)
g_GameScript->OnLoop(DELTA_TIME, false); // TODO: Don't use DELTA_TIME constant with high framerate.
HandleAllGlobalEvents(EventType::Loop, (Activator)LaraItem->Index);
- // Control lock is processed after handling scripts because builder may want to process input externally while locking player from input.
+ // Queued input actions are read again after OnLoop, so that remaining control loop can immediately register
+ // emulated keypresses from the script.
+ ApplyActionQueue();
+
+ // Control lock is processed after handling scripts because builder may want to process input externally
+ // while locking player from input.
if (!isTitle && Lara.Control.IsLocked)
ClearAllActions();
- // Item update should happen before camera update, so potential flyby/track camera triggers are processed correctly.
+ // Item update should happen before camera update, so potential flyby/track camera triggers
+ // are processed correctly.
UpdateAllItems();
UpdateAllEffects();
UpdateLara(LaraItem, isTitle);
@@ -173,11 +181,6 @@ GameStatus ControlPhase(bool insideMenu)
// Clear last selected item in inventory (must be after on loop event handling, so they can detect that).
g_Gui.CancelInventorySelection();
- // Control lock is processed after handling scripts because builder may want to
- // process input externally while locking player from input.
- if (!isTitle && Lara.Control.IsLocked)
- ClearAllActions();
-
// Update weather.
Weather.Update();
@@ -224,24 +227,33 @@ GameStatus ControlPhase(bool insideMenu)
PlaySoundSources();
Sound_UpdateScene();
- // Handle inventory, pause, load, save screens.
+ auto gameStatus = GameStatus::Normal;
+
if (!insideMenu)
{
- auto result = HandleMenuCalls(isTitle);
- if (result != GameStatus::Normal)
- return result;
+ // Handle inventory, pause, load, save screens.
+ gameStatus = HandleMenuCalls(isTitle);
// Handle global input events.
- result = HandleGlobalInputEvents(isTitle);
- if (result != GameStatus::Normal)
- return result;
+ if (gameStatus == GameStatus::Normal)
+ gameStatus = HandleGlobalInputEvents(isTitle);
+ }
+
+ if (gameStatus != GameStatus::Normal)
+ {
+ // Call post-loop callbacks last time and end level.
+ g_GameScript->OnLoop(DELTA_TIME, true);
+ g_GameScript->OnEnd(gameStatus);
+ HandleAllGlobalEvents(EventType::End, (Activator)LaraItem->Index);
+ }
+ else
+ {
+ // Post-loop script and event handling.
+ g_GameScript->OnLoop(DELTA_TIME, true);
}
UpdateCamera();
- // Post-loop script and event handling.
- g_GameScript->OnLoop(DELTA_TIME, true);
-
// Clear savegame loaded flag.
JustLoaded = false;
@@ -252,9 +264,87 @@ GameStatus ControlPhase(bool insideMenu)
auto time2 = std::chrono::high_resolution_clock::now();
ControlPhaseTime = (std::chrono::duration_cast(time2 - time1)).count() / 1000000;
+ return gameStatus;
+}
+
+GameStatus FreezePhase()
+{
+ // If needed when first entering freeze mode, do initialization.
+ if (g_GameFlow->LastFreezeMode == FreezeMode::None)
+ {
+ // Capture the screen for drawing it as a background.
+ if (g_GameFlow->LastFreezeMode == FreezeMode::Full)
+ g_Renderer.DumpGameScene(SceneRenderMode::NoHud);
+
+ StopRumble();
+ }
+
+ // Update last freeze mode here, so that items won't update inside freeze loop.
+ g_GameFlow->LastFreezeMode = g_GameFlow->CurrentFreezeMode;
+
+ g_Renderer.PrepareScene();
+ g_Renderer.SaveOldState();
+
+ ClearLensFlares();
+ ClearAllDisplaySprites();
+
+ SetupInterpolation();
+ PrepareCamera();
+
+ g_GameStringsHandler->ProcessDisplayStrings(DELTA_TIME);
+
+ // Track previous player animation to queue hair update if needed.
+ int lastAnimNumber = LaraItem->Animation.AnimNumber;
+
+ // Poll controls and call scripting events.
+ HandleControls(false);
+ g_GameScript->OnFreeze();
+ HandleAllGlobalEvents(EventType::Freeze, (Activator)LaraItem->Index);
+
+ // Partially update scene if not using full freeze mode.
+ if (g_GameFlow->LastFreezeMode != FreezeMode::Full)
+ {
+ if (g_GameFlow->LastFreezeMode == FreezeMode::Player)
+ UpdateLara(LaraItem, false);
+
+ UpdateAllItems();
+ UpdateGlobalLensFlare();
+
+ UpdateCamera();
+
+ PlaySoundSources();
+ Sound_UpdateScene();
+ }
+
+ // HACK: Update player hair if animation was switched in spectator mode.
+ // Needed for photo mode and other similar functionality.
+ if (g_GameFlow->LastFreezeMode == FreezeMode::Spectator &&
+ lastAnimNumber != LaraItem->Animation.AnimNumber)
+ {
+ lastAnimNumber = LaraItem->Animation.AnimNumber;
+ for (int i = 0; i < FPS; i++)
+ HairEffect.Update(*LaraItem);
+ }
+
+ // Update last freeze mode again, as it may have been changed in a script.
+ g_GameFlow->LastFreezeMode = g_GameFlow->CurrentFreezeMode;
+
return GameStatus::Normal;
}
+GameStatus ControlPhase(bool insideMenu)
+{
+ // For safety, only allow to break game loop in non-title levels.
+ if (g_GameFlow->CurrentFreezeMode == FreezeMode::None || CurrentLevel == 0)
+ {
+ return GamePhase(insideMenu);
+ }
+ else
+ {
+ return FreezePhase();
+ }
+}
+
unsigned CALLBACK GameMain(void *)
{
TENLog("Starting GameMain()...", LogLevel::Info);
@@ -486,14 +576,11 @@ void InitializeScripting(int levelIndex, LevelLoadType type)
// Play default background music.
if (type != LevelLoadType::Load)
- PlaySoundTrack(level.GetAmbientTrack(), SoundTrackType::BGM);
+ PlaySoundTrack(level.GetAmbientTrack(), SoundTrackType::BGM, 0, SOUND_XFADETIME_LEVELJUMP);
}
void DeInitializeScripting(int levelIndex, GameStatus reason)
{
- g_GameScript->OnEnd(reason);
- HandleAllGlobalEvents(EventType::End, (Activator)LaraItem->Index);
-
g_GameScript->FreeLevelScripts();
g_GameScriptEntities->FreeEntities();
@@ -507,10 +594,8 @@ void InitializeOrLoadGame(bool loadGame)
g_Gui.SetEnterInventory(NO_VALUE);
// Restore game?
- if (loadGame)
+ if (loadGame && SaveGame::Load(g_GameFlow->SelectedSaveGame))
{
- SaveGame::Load(g_GameFlow->SelectedSaveGame);
-
InitializeGame = false;
g_GameFlow->SelectedSaveGame = 0;
@@ -548,9 +633,9 @@ GameStatus DoGameLoop(int levelIndex)
int frameCount = LOOP_FRAME_COUNT;
auto& status = g_GameFlow->LastGameStatus;
- // Before entering actual game loop, ControlPhase() must be
- // called once to sort out various runtime shenanigangs (e.g. hair).
- status = ControlPhase(false);
+ // Before entering actual game loop, GamePhase() must be called once to sort out
+ // various runtime shenanigangs (e.g. hair or freeze mode initialization).
+ status = GamePhase(false);
g_Synchronizer.Init();
bool legacy30FpsDoneDraw = false;
@@ -570,6 +655,9 @@ GameStatus DoGameLoop(int levelIndex)
if (status != GameStatus::Normal)
break;
+ if (g_GameFlow->LastFreezeMode != g_GameFlow->CurrentFreezeMode)
+ continue;
+
if (!g_Configuration.EnableHighFramerate)
{
if (!legacy30FpsDoneDraw)
@@ -591,11 +679,14 @@ GameStatus DoGameLoop(int levelIndex)
void EndGameLoop(int levelIndex, GameStatus reason)
{
+ // Save last screenshot for loading screen.
+ g_Renderer.DumpGameScene();
+
SaveGame::SaveHub(levelIndex);
DeInitializeScripting(levelIndex, reason);
StopAllSounds();
- StopSoundTracks();
+ StopSoundTracks(SOUND_XFADETIME_LEVELJUMP, true);
StopRumble();
}
@@ -632,6 +723,10 @@ GameStatus HandleMenuCalls(bool isTitle)
case InventoryResult::NewGameSelectedLevel:
return GameStatus::NewGame;
+ case InventoryResult::HomeLevel:
+ return GameStatus::HomeLevel;
+ break;
+
case InventoryResult::LoadGame:
return GameStatus::LoadGame;
@@ -660,11 +755,8 @@ GameStatus HandleMenuCalls(bool isTitle)
else if (doLoad && g_GameFlow->IsLoadSaveEnabled() && g_Gui.GetInventoryMode() != InventoryMode::Load)
{
SaveGame::LoadHeaders();
-
g_Gui.SetInventoryMode(InventoryMode::Load);
-
- if (g_Gui.CallInventory(LaraItem, false))
- gameStatus = GameStatus::LoadGame;
+ g_Gui.CallInventory(LaraItem, false);
}
else if (doPause && g_Gui.GetInventoryMode() != InventoryMode::Pause)
{
diff --git a/TombEngine/Game/control/control.h b/TombEngine/Game/control/control.h
index 646808f27..1a49c42e1 100644
--- a/TombEngine/Game/control/control.h
+++ b/TombEngine/Game/control/control.h
@@ -26,6 +26,14 @@ enum class GameStatus
LevelComplete
};
+enum class FreezeMode
+{
+ None,
+ Full,
+ Spectator,
+ Player
+};
+
enum class LevelLoadType
{
New,
@@ -64,7 +72,6 @@ extern bool ThreadEnded;
extern int RequiredStartPos;
extern int CurrentLevel;
extern int NextLevel;
-extern int SystemNameHash;
extern bool InItemControlLoop;
extern short ItemNewRoomNo;
diff --git a/TombEngine/Game/control/event.h b/TombEngine/Game/control/event.h
index 2b712efec..67cd532bc 100644
--- a/TombEngine/Game/control/event.h
+++ b/TombEngine/Game/control/event.h
@@ -6,8 +6,6 @@ struct MESH_INFO;
namespace TEN::Control::Volumes
{
- constexpr auto NO_CALL_COUNTER = -1;
-
using Activator = std::variant<
std::nullptr_t,
short,
@@ -42,6 +40,7 @@ namespace TEN::Control::Volumes
Start,
End,
UseItem,
+ Freeze,
Count
};
@@ -52,7 +51,8 @@ namespace TEN::Control::Volumes
std::string Function = {};
std::string Data = {};
- int CallCounter = NO_CALL_COUNTER;
+ bool Enabled = true;
+ int CallCounter = NO_VALUE;
};
struct EventSet
diff --git a/TombEngine/Game/control/flipeffect.cpp b/TombEngine/Game/control/flipeffect.cpp
index a47059395..e1b3af5d4 100644
--- a/TombEngine/Game/control/flipeffect.cpp
+++ b/TombEngine/Game/control/flipeffect.cpp
@@ -346,6 +346,6 @@ void VoidEffect(ItemInfo* item)
void DoFlipEffect(int number, ItemInfo* item)
{
- if (number != -1 && number < NUM_FLIPEFFECTS && effect_routines[number] != nullptr)
+ if (number != NO_VALUE && number < NUM_FLIPEFFECTS && effect_routines[number] != nullptr)
effect_routines[number](item);
}
diff --git a/TombEngine/Game/control/los.cpp b/TombEngine/Game/control/los.cpp
index 003d04b30..00db3ae6f 100644
--- a/TombEngine/Game/control/los.cpp
+++ b/TombEngine/Game/control/los.cpp
@@ -266,13 +266,14 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
SoundEffect(SFX_TR4_REVOLVER_FIRE, nullptr);
}
- bool hasHit = false;
+ bool hitProcessed = false;
MESH_INFO* mesh = nullptr;
auto vector = Vector3i::Zero;
int itemNumber = ObjectOnLOS2(origin, target, &vector, &mesh);
+ bool hasHit = itemNumber != NO_LOS_ITEM;
- if (itemNumber != NO_LOS_ITEM)
+ if (hasHit)
{
target2.x = vector.x - ((vector.x - origin->x) >> 5);
target2.y = vector.y - ((vector.y - origin->y) >> 5);
@@ -286,7 +287,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
{
if (itemNumber < 0)
{
- if (StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
+ if (Statics[mesh->staticNumber].shatterType != ShatterType::None)
{
const auto& weapon = Weapons[(int)Lara.Control.Weapon.GunType];
mesh->HitPoints -= weapon.Damage;
@@ -294,10 +295,10 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
ShatterImpactData.impactLocation = Vector3(mesh->pos.Position.x, mesh->pos.Position.y, mesh->pos.Position.z);
ShatterObject(nullptr, mesh, 128, target2.RoomNumber, 0);
SoundEffect(GetShatterSound(mesh->staticNumber), (Pose*)mesh);
+ hitProcessed = true;
}
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
else
{
@@ -312,14 +313,15 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
ShatterImpactData.impactDirection = dir;
ShatterImpactData.impactLocation = ShatterItem.sphere.Center;
ShatterObject(&ShatterItem, 0, 128, target2.RoomNumber, 0);
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, false);
+ hitProcessed = true;
}
else
{
auto* object = &Objects[item->ObjectNumber];
if (drawTarget && (Lara.Control.Weapon.GunType == LaraWeaponType::Revolver ||
- Lara.Control.Weapon.GunType == LaraWeaponType::HK))
+ Lara.Control.Weapon.GunType == LaraWeaponType::HK))
{
if (object->intelligent || object->HitRoutine)
{
@@ -342,33 +344,21 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
}
}
}
+
HitTarget(LaraItem, item, &target2, Weapons[(int)Lara.Control.Weapon.GunType].Damage, false, bestJointIndex);
+ hitProcessed = true;
}
else
{
// TR5
if (object->hitEffect == HitEffect::Richochet)
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
}
- else
+ else if (item->ObjectNumber >= ID_SMASH_OBJECT1 && item->ObjectNumber <= ID_SMASH_OBJECT8)
{
- if (item->ObjectNumber >= ID_SMASH_OBJECT1 && item->ObjectNumber <= ID_SMASH_OBJECT8)
- {
- SmashObject(itemNumber);
- }
- else
- {
- const auto& weapon = Weapons[(int)Lara.Control.Weapon.GunType];
- if (object->HitRoutine != nullptr)
- {
- object->HitRoutine(*item, *LaraItem, target2, weapon.Damage, false, NO_VALUE);
- }
- else
- {
- DefaultItemHit(*item, *LaraItem, target2, weapon.Damage, false, NO_VALUE);
- }
- }
+ SmashObject(itemNumber);
+ hitProcessed = true;
}
}
}
@@ -418,9 +408,11 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
item->Status = ITEM_ACTIVE;
item->Flags |= IFLAG_ACTIVATION_MASK | 0x40;
}
+
+ hitProcessed = true;
}
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
}
}
@@ -430,8 +422,6 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
FireCrossBowFromLaserSight(*LaraItem, origin, &target2);
}
}
-
- hasHit = true;
}
else
{
@@ -447,7 +437,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
target2.z -= (target2.z - origin->z) >> 5;
if (isFiring && !result)
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 8, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
}
@@ -460,7 +450,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
LaserSightZ = target2.z;
}
- return hasHit;
+ return hitProcessed;
}
static bool DoRayBox(const GameVector& origin, const GameVector& target, const GameBoundingBox& bounds,
@@ -602,7 +592,7 @@ int ObjectOnLOS2(GameVector* origin, GameVector* target, Vector3i* vec, MESH_INF
if (priorityObjectID != GAME_OBJECT_ID::ID_NO_OBJECT && item.ObjectNumber != priorityObjectID)
continue;
- if (item.ObjectNumber != ID_LARA && Objects[item.ObjectNumber].collision == nullptr)
+ if (item.ObjectNumber != ID_LARA && (Objects[item.ObjectNumber].collision == nullptr || !item.Collidable))
continue;
if (item.ObjectNumber == ID_LARA && priorityObjectID != ID_LARA)
diff --git a/TombEngine/Game/control/lot.cpp b/TombEngine/Game/control/lot.cpp
index b1168618d..5983a9b57 100644
--- a/TombEngine/Game/control/lot.cpp
+++ b/TombEngine/Game/control/lot.cpp
@@ -197,13 +197,6 @@ void InitializeSlot(short itemNumber, bool makeTarget)
creature->LOT.Drop = -BLOCK(1);
creature->LOT.Zone = ZoneType::Human;
break;
-
- case LotType::EnemyJeep:
- creature->LOT.Step = BLOCK(4);
- creature->LOT.Drop = -BLOCK(4);
- creature->LOT.CanJump = true;
- creature->LOT.Zone = ZoneType::Human;
- break;
}
ClearLOT(&creature->LOT);
diff --git a/TombEngine/Game/control/trigger.cpp b/TombEngine/Game/control/trigger.cpp
index adbe0b74c..44e8c9119 100644
--- a/TombEngine/Game/control/trigger.cpp
+++ b/TombEngine/Game/control/trigger.cpp
@@ -414,6 +414,9 @@ void Trigger(short const value, short const flags)
void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bool heavy, int heavyFlags)
{
+ if (g_GameFlow->CurrentFreezeMode != FreezeMode::None)
+ return;
+
bool switchOff = false;
bool flipAvailable = false;
int flip = NO_VALUE;
@@ -592,14 +595,6 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
&& (item->Flags & ONESHOT))
break;
- if (triggerType != TRIGGER_TYPES::ANTIPAD
- && triggerType != TRIGGER_TYPES::ANTITRIGGER
- && triggerType != TRIGGER_TYPES::HEAVYANTITRIGGER)
- {
- if (item->ObjectNumber == ID_DART_EMITTER && item->Active)
- break;
- }
-
item->Timer = timer;
if (timer != 1)
item->Timer = FPS * timer;
@@ -609,10 +604,6 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
{
if (heavyFlags >= 0)
{
- //if (switchFlag)
- //item->Flags |= (flags & CODE_BITS);
- //else
-
item->Flags ^= (flags & CODE_BITS);
if (flags & ONESHOT)
@@ -867,6 +858,9 @@ void TestTriggers(int x, int y, int z, short roomNumber, bool heavy, int heavyFl
void ProcessSectorFlags(ItemInfo* item)
{
+ if (g_GameFlow->CurrentFreezeMode != FreezeMode::None)
+ return;
+
bool isPlayer = item->IsLara();
// HACK: because of L-shaped portal configurations, we need to fetch room number from Location struct for player.
diff --git a/TombEngine/Game/control/volume.cpp b/TombEngine/Game/control/volume.cpp
index 5968dd206..0f0188132 100644
--- a/TombEngine/Game/control/volume.cpp
+++ b/TombEngine/Game/control/volume.cpp
@@ -87,11 +87,11 @@ namespace TEN::Control::Volumes
bool HandleEvent(Event& event, Activator& activator)
{
- if (event.Function.empty() || event.CallCounter == 0 || event.CallCounter < NO_CALL_COUNTER)
+ if (!event.Enabled || event.CallCounter == 0 || event.Function.empty())
return false;
g_GameScript->ExecuteFunction(event.Function, activator, event.Data);
- if (event.CallCounter != NO_CALL_COUNTER)
+ if (event.CallCounter != NO_VALUE)
event.CallCounter--;
return true;
@@ -124,18 +124,16 @@ namespace TEN::Control::Volumes
if (eventSet == nullptr)
return false;
- auto& event = eventSet->Events[(int)eventType];
- bool disabled = eventSet->Events[(int)eventType].CallCounter < NO_CALL_COUNTER;
-
- // Flip the call counter to indicate that it is currently disabled.
- if ((enabled && disabled) || (!enabled && !disabled))
- eventSet->Events[(int)eventType].CallCounter += enabled ? EVENT_STATE_MASK : -EVENT_STATE_MASK;
+ eventSet->Events[(int)eventType].Enabled = enabled;
return true;
}
void TestVolumes(short roomNumber, const BoundingOrientedBox& box, ActivatorFlags activatorFlag, Activator activator)
{
+ if (g_GameFlow->CurrentFreezeMode != FreezeMode::None)
+ return;
+
if (roomNumber == NO_VALUE)
return;
diff --git a/TombEngine/Game/effects/bubble.cpp b/TombEngine/Game/effects/bubble.cpp
index 7dc9f676f..06feb93b7 100644
--- a/TombEngine/Game/effects/bubble.cpp
+++ b/TombEngine/Game/effects/bubble.cpp
@@ -136,8 +136,9 @@ namespace TEN::Effects::Bubble
void UpdateBubbles()
{
- constexpr auto LIFE_FULL_SCALE = std::max(BUBBLE_LIFE_MAX - 0.25f, 0.0f);
- constexpr auto LIFE_START_FADING = std::min(1.0f, BUBBLE_LIFE_MAX);
+ constexpr auto LIFE_FULL_SCALE = std::max(BUBBLE_LIFE_MAX - 0.25f, 0.0f);
+ constexpr auto LIFE_START_FADING = std::min(1.0f, BUBBLE_LIFE_MAX);
+ constexpr int ROOM_UPDATE_INTERVAL = 10;
if (Bubbles.empty())
return;
@@ -149,19 +150,23 @@ namespace TEN::Effects::Bubble
bubble.StoreInterpolationData();
- // Update room number. TODO: Should use GetPointCollision(), but calling it for each bubble is very inefficient.
- auto roomVector = RoomVector(bubble.RoomNumber, int(bubble.Position.y - bubble.Gravity));
- int roomNumber = GetRoomVector(roomVector, Vector3i(bubble.Position.x, bubble.Position.y - bubble.Gravity, bubble.Position.z)).RoomNumber;
int prevRoomNumber = bubble.RoomNumber;
- bubble.RoomNumber = roomNumber;
+
+ int updateOffset = (int)(bubble.Position.x + bubble.Position.y + bubble.Position.z) % ROOM_UPDATE_INTERVAL;
+ if (GlobalCounter % ROOM_UPDATE_INTERVAL == updateOffset)
+ {
+ auto roomVector = RoomVector(bubble.RoomNumber, int(bubble.Position.y - bubble.Gravity));
+ auto testPosition = Vector3i(bubble.Position.x, bubble.Position.y - bubble.Gravity, bubble.Position.z);
+ bubble.RoomNumber = GetRoomVector(roomVector, testPosition).RoomNumber;;
+ }
// Out of water.
- if (!TestEnvironment(ENV_FLAG_WATER, roomNumber))
+ if (bubble.RoomNumber != prevRoomNumber && !TestEnvironment(ENV_FLAG_WATER, bubble.RoomNumber))
{
// Hit water surface; spawn ripple.
SpawnRipple(
Vector3(bubble.Position.x, g_Level.Rooms[prevRoomNumber].TopHeight, bubble.Position.z),
- roomNumber,
+ bubble.RoomNumber,
((bubble.SizeMax.x + bubble.SizeMax.y) / 2) * 0.5f,
(int)RippleFlags::SlowFade);
@@ -169,8 +174,7 @@ namespace TEN::Effects::Bubble
continue;
}
// Hit ceiling. NOTE: This is a hacky check. New collision fetching should provide fast info on a need-to-know basis.
- else if (bubble.RoomNumber == prevRoomNumber &&
- bubble.Position.y <= g_Level.Rooms[prevRoomNumber].TopHeight)
+ else if (bubble.RoomNumber == prevRoomNumber && bubble.Position.y <= g_Level.Rooms[prevRoomNumber].TopHeight)
{
bubble.Life = 0.0f;
continue;
diff --git a/TombEngine/Game/effects/debris.cpp b/TombEngine/Game/effects/debris.cpp
index 560bc9000..d83b51b32 100644
--- a/TombEngine/Game/effects/debris.cpp
+++ b/TombEngine/Game/effects/debris.cpp
@@ -70,7 +70,7 @@ void ShatterObject(SHATTER_ITEM* item, MESH_INFO* mesh, int num, short roomNumbe
return;
isStatic = true;
- meshIndex = StaticObjects[mesh->staticNumber].meshNumber;
+ meshIndex = Statics[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;
diff --git a/TombEngine/Game/effects/effects.cpp b/TombEngine/Game/effects/effects.cpp
index c64da1969..9a47a93cb 100644
--- a/TombEngine/Game/effects/effects.cpp
+++ b/TombEngine/Game/effects/effects.cpp
@@ -47,7 +47,7 @@ constexpr int WIBBLE_MAX = UCHAR_MAX - WIBBLE_SPEED + 1;
Particle Particles[MAX_PARTICLES];
ParticleDynamic ParticleDynamics[MAX_PARTICLE_DYNAMICS];
-FX_INFO EffectList[NUM_EFFECTS];
+FX_INFO EffectList[MAX_SPAWNED_ITEM_COUNT];
GameBoundingBox DeadlyBounds;
SPLASH_SETUP SplashSetup;
@@ -450,9 +450,11 @@ void UpdateSparks()
}
}
-void TriggerRicochetSpark(const GameVector& pos, short angle, int count, int unk)
+void TriggerRicochetSpark(const GameVector& pos, short angle, bool sound)
{
+ int count = Random::GenerateInt(3, 8);
TriggerRicochetSpark(pos, angle, count);
+ SoundEffect(SFX_TR4_WEAPON_RICOCHET, &Pose(pos.ToVector3i()));
}
void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv)
@@ -1135,8 +1137,7 @@ void Ricochet(Pose& pose)
{
short angle = Geometry::GetOrientToPoint(pose.Position.ToVector3(), LaraItem->Pose.Position.ToVector3()).y;
auto target = GameVector(pose.Position);
- TriggerRicochetSpark(target, angle / 16, 3, 0);
- SoundEffect(SFX_TR4_WEAPON_RICOCHET, &pose);
+ TriggerRicochetSpark(target, angle / 16);
}
void ControlWaterfallMist(short itemNumber)
diff --git a/TombEngine/Game/effects/effects.h b/TombEngine/Game/effects/effects.h
index aaa5031a9..e980a8d22 100644
--- a/TombEngine/Game/effects/effects.h
+++ b/TombEngine/Game/effects/effects.h
@@ -1,5 +1,6 @@
#pragma once
#include "Math/Math.h"
+#include "Game/Items.h"
#include "Renderer/RendererEnums.h"
enum class LaraWeaponType;
@@ -13,7 +14,6 @@ constexpr auto SD_UWEXPLOSION = 2;
constexpr auto MAX_NODE = 23;
constexpr auto MAX_DYNAMICS = 64;
constexpr auto MAX_SPLASHES = 8;
-constexpr auto NUM_EFFECTS = 256;
constexpr auto MAX_PARTICLES = 1024;
constexpr auto MAX_PARTICLE_DYNAMICS = 8;
@@ -239,7 +239,7 @@ extern SPLASH_STRUCT Splashes[MAX_SPLASHES];
extern Vector3i NodeVectors[ParticleNodeOffsetIDs::NodeMax];
extern NODEOFFSET_INFO NodeOffsets[ParticleNodeOffsetIDs::NodeMax];
-extern FX_INFO EffectList[NUM_EFFECTS];
+extern FX_INFO EffectList[MAX_SPAWNED_ITEM_COUNT];
template
TEffect& GetNewEffect(std::vector& effects, unsigned int countMax)
@@ -284,7 +284,7 @@ void SetSpriteSequence(Particle& particle, GAME_OBJECT_ID objectID);
void DetatchSpark(int num, SpriteEnumFlag type);
void UpdateSparks();
-void TriggerRicochetSpark(const GameVector& pos, short angle, int count, int unk);
+void TriggerRicochetSpark(const GameVector& pos, short angle, bool sound = true);
void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv);
void TriggerExplosionSparks(int x, int y, int z, int extraTrig, int dynamic, int uw, int roomNumber, const Vector3& mainColor = Vector3::Zero, const Vector3& secondColor = Vector3::Zero);
void TriggerExplosionSmokeEnd(int x, int y, int z, int uw);
diff --git a/TombEngine/Game/effects/hair.cpp b/TombEngine/Game/effects/hair.cpp
index 32ae15b55..a574ac31c 100644
--- a/TombEngine/Game/effects/hair.cpp
+++ b/TombEngine/Game/effects/hair.cpp
@@ -176,6 +176,11 @@ namespace TEN::Effects::Hair
// Calculate absolute orientation.
auto absDir = target - origin;
absDir.Normalize();
+
+ // FAILSAFE: Handle case with zero normal (can happen if 2 hair segments have same offset).
+ if (absDir == Vector3::Zero)
+ return Quaternion::Identity;
+
auto absOrient = Geometry::ConvertDirectionToQuat(absDir);
// Calculate relative twist rotation.
diff --git a/TombEngine/Game/effects/tomb4fx.cpp b/TombEngine/Game/effects/tomb4fx.cpp
index cf83fe7bf..ef50c0418 100644
--- a/TombEngine/Game/effects/tomb4fx.cpp
+++ b/TombEngine/Game/effects/tomb4fx.cpp
@@ -1474,93 +1474,6 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
}
}
-/*void TriggerExplosionSmokeEnd(int x, int y, int z, int unk)
-{
- auto* spark = GetFreeParticle();
-
- spark->on = 1;
- if (unk)
- {
- spark->sR = 0;
- spark->sG = 0;
- spark->sB = 0;
- spark->dR = 192;
- spark->dG = 192;
- spark->dB = 208;
- }
- else
- {
- spark->dR = 64;
- spark->sR = 144;
- spark->sG = 144;
- spark->sB = 144;
- spark->dG = 64;
- spark->dB = 64;
- }
-
- spark->colFadeSpeed = 8;
- spark->fadeToBlack = 64;
- spark->life = spark->sLife = (GetRandomControl() & 0x1F) + 96;
-
- if (unk)
- spark->blendMode = BlendMode::Additive;
- else
- spark->blendMode = 3;
-
- spark->x = (GetRandomControl() & 0x1F) + x - 16;
- spark->y = (GetRandomControl() & 0x1F) + y - 16;
- spark->z = (GetRandomControl() & 0x1F) + z - 16;
- spark->xVel = ((GetRandomControl() & 0xFFF) - 2048) >> 2;
- spark->yVel = (GetRandomControl() & 0xFF) - 128;
- spark->zVel = ((GetRandomControl() & 0xFFF) - 2048) >> 2;
-
- if (unk)
- {
- spark->friction = 20;
- spark->yVel >>= 4;
- spark->y += 32;
- }
- else
- spark->friction = 6;
-
- spark->flags = 538;
- spark->rotAng = GetRandomControl() & 0xFFF;
-
- if (GetRandomControl() & 1)
- spark->rotAdd = -((GetRandomControl() & 0xF) + 16);
- else
- spark->rotAdd = (GetRandomControl() & 0xF) + 16;
- spark->scalar = 3;
-
- if (unk)
- {
- spark->maxYvel = 0;
- spark->gravity = 0;
- }
- else
- {
- spark->gravity = -3 - (GetRandomControl() & 3);
- spark->maxYvel = -4 - (GetRandomControl() & 3);
- }
-
- int size = (GetRandomControl() & 0x1F) + 128;
- spark->dSize = size;
- spark->sSize = size >> 2;
- spark->size = size >> 2;
-}
-*/
-/*void DrawLensFlares(ItemInfo* item)
-{
- GameVector pos;
-
- pos.x = item->pos.Position.x;
- pos.y = item->pos.Position.y;
- pos.z = item->pos.Position.z;
- pos.roomNumber = item->roomNumber;
-
- SetUpLensFlare(0, 0, 0, &pos);
-}*/
-
void TriggerFenceSparks(int x, int y, int z, int kill, int crane)
{
auto* spark = GetFreeParticle();
diff --git a/TombEngine/Game/gui.cpp b/TombEngine/Game/gui.cpp
index 949634a88..0979ea6bc 100644
--- a/TombEngine/Game/gui.cpp
+++ b/TombEngine/Game/gui.cpp
@@ -3168,9 +3168,7 @@ namespace TEN::Gui
bool GuiController::CallPause()
{
- ClearAllDisplaySprites();
- g_Renderer.PrepareScene();
- g_Renderer.DumpGameScene();
+ g_Renderer.DumpGameScene(SceneRenderMode::NoHud);
PauseAllSounds(SoundPauseMode::Pause);
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
@@ -3251,9 +3249,7 @@ namespace TEN::Gui
player.Inventory.OldBusy = player.Inventory.IsBusy;
- ClearAllDisplaySprites();
- g_Renderer.PrepareScene();
- g_Renderer.DumpGameScene();
+ g_Renderer.DumpGameScene(SceneRenderMode::NoHud);
PauseAllSounds(SoundPauseMode::Inventory);
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
@@ -3506,7 +3502,7 @@ namespace TEN::Gui
LoadResult GuiController::DoLoad()
{
- constexpr auto DEATH_NO_INPUT_LOAD_TIMEOUT = 1 * FPS;
+ constexpr auto DEATH_NO_INPUT_LOAD_TIMEOUT = FPS / 2;
bool canLoop = g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::SaveLoadOnly ||
g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus;
@@ -3524,7 +3520,7 @@ namespace TEN::Gui
else
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
- g_GameFlow->SelectedSaveGame = SelectedSaveSlot;
+ NextLevel = -(SelectedSaveSlot + 1);
return LoadResult::Load;
}
}
diff --git a/TombEngine/Game/items.cpp b/TombEngine/Game/items.cpp
index 5f30fac06..75bdbed15 100644
--- a/TombEngine/Game/items.cpp
+++ b/TombEngine/Game/items.cpp
@@ -276,7 +276,7 @@ void KillItem(short const itemNumber)
// AI target generation uses a hack with making a dummy item without ObjectNumber.
// Therefore, a check should be done here to prevent access violation.
if (item->ObjectNumber != GAME_OBJECT_ID::ID_NO_OBJECT && item->IsBridge())
- UpdateBridgeItem(*item, true);
+ UpdateBridgeItem(*item, BridgeUpdateType::Remove);
GameScriptHandleKilled(itemNumber, true);
@@ -479,13 +479,13 @@ void InitializeFXArray()
NextFxActive = NO_VALUE;
NextFxFree = 0;
- for (int i = 0; i < NUM_EFFECTS; i++)
+ for (int i = 0; i < MAX_SPAWNED_ITEM_COUNT; i++)
{
auto* fx = &EffectList[i];
fx->nextFx = i + 1;
}
- EffectList[NUM_EFFECTS - 1].nextFx = NO_VALUE;
+ EffectList[MAX_SPAWNED_ITEM_COUNT - 1].nextFx = NO_VALUE;
}
void RemoveDrawnItem(short itemNumber)
@@ -763,28 +763,30 @@ void UpdateAllItems()
while (itemNumber != NO_VALUE)
{
auto* item = &g_Level.Items[itemNumber];
- short nextItem = item->NextActive;
+ itemNumber = item->NextActive;
if (!Objects.CheckID(item->ObjectNumber))
continue;
+ if (g_GameFlow->LastFreezeMode != FreezeMode::None && !Objects[item->ObjectNumber].AlwaysActive)
+ continue;
+
if (item->AfterDeath <= ITEM_DEATH_TIMEOUT)
{
if (Objects[item->ObjectNumber].control)
- Objects[item->ObjectNumber].control(itemNumber);
+ Objects[item->ObjectNumber].control(item->Index);
- TestVolumes(itemNumber);
+ TestVolumes(item->Index);
ProcessEffects(item);
if (item->AfterDeath > 0 && item->AfterDeath < ITEM_DEATH_TIMEOUT && !(Wibble & 3))
item->AfterDeath++;
if (item->AfterDeath == ITEM_DEATH_TIMEOUT)
- KillItem(itemNumber);
+ KillItem(item->Index);
}
else
- KillItem(itemNumber);
+ KillItem(item->Index);
- itemNumber = nextItem;
}
InItemControlLoop = false;
@@ -893,7 +895,7 @@ void DefaultItemHit(ItemInfo& target, ItemInfo& source, std::optional ANGLE(-67.5f))
diff --git a/TombEngine/Game/pickup/pickup.cpp b/TombEngine/Game/pickup/pickup.cpp
index 84f6f982d..b81a1f4d0 100644
--- a/TombEngine/Game/pickup/pickup.cpp
+++ b/TombEngine/Game/pickup/pickup.cpp
@@ -203,23 +203,6 @@ void RemoveObjectFromInventory(GAME_OBJECT_ID objectID, std::optional count
}
}
-void CollectCarriedItems(ItemInfo* item)
-{
- short pickupNumber = item->CarriedItem;
- while (pickupNumber != NO_VALUE)
- {
- auto& pickupItem = g_Level.Items[pickupNumber];
-
- PickedUpObject(pickupItem);
- g_Hud.PickupSummary.AddDisplayPickup(pickupItem);
- KillItem(pickupNumber);
-
- pickupNumber = pickupItem.CarriedItem;
- }
-
- item->CarriedItem = NO_VALUE;
-}
-
static void HideOrDisablePickup(ItemInfo& pickupItem)
{
if (pickupItem.TriggerFlags & 0xC0)
@@ -234,6 +217,23 @@ static void HideOrDisablePickup(ItemInfo& pickupItem)
}
}
+void CollectCarriedItems(ItemInfo* item)
+{
+ short pickupNumber = item->CarriedItem;
+ while (pickupNumber != NO_VALUE)
+ {
+ auto& pickupItem = g_Level.Items[pickupNumber];
+
+ PickedUpObject(pickupItem);
+ g_Hud.PickupSummary.AddDisplayPickup(pickupItem);
+ HideOrDisablePickup(pickupItem);
+
+ pickupNumber = pickupItem.CarriedItem;
+ }
+
+ item->CarriedItem = NO_VALUE;
+}
+
void CollectMultiplePickups(int itemNumber)
{
auto& firstItem = g_Level.Items[itemNumber];
@@ -903,7 +903,7 @@ void DropPickups(ItemInfo* item)
for (auto* staticPtr : collObjects.Statics)
{
- auto& object = StaticObjects[staticPtr->staticNumber];
+ auto& object = Statics[staticPtr->staticNumber];
auto box = object.collisionBox.ToBoundingOrientedBox(staticPtr->pos);
if (box.Intersects(sphere))
@@ -1270,7 +1270,7 @@ void SearchObjectControl(short itemNumber)
{
PickedUpObject(*item2);
g_Hud.PickupSummary.AddDisplayPickup(*item2);
- KillItem(item->ItemFlags[1]);
+ HideOrDisablePickup(*item2);
}
else
{
diff --git a/TombEngine/Game/room.cpp b/TombEngine/Game/room.cpp
index 2e8841634..4acf9ee30 100644
--- a/TombEngine/Game/room.cpp
+++ b/TombEngine/Game/room.cpp
@@ -7,14 +7,16 @@
#include "Game/control/lot.h"
#include "Game/control/volume.h"
#include "Game/items.h"
-#include "Renderer/Renderer.h"
#include "Math/Math.h"
#include "Objects/game_object_ids.h"
+#include "Objects/Generic/Doors/generic_doors.h"
+#include "Renderer/Renderer.h"
#include "Specific/trutils.h"
using namespace TEN::Math;
using namespace TEN::Collision::Floordata;
using namespace TEN::Collision::Point;
+using namespace TEN::Entities::Doors;
using namespace TEN::Renderer;
using namespace TEN::Utils;
@@ -69,10 +71,78 @@ static void RemoveRoomFlipItems(const ROOM_INFO& room)
// Clear bridge.
if (item.IsBridge())
- UpdateBridgeItem(item, true);
+ UpdateBridgeItem(item, BridgeUpdateType::Remove);
}
}
+static void FlipRooms(int roomNumber, ROOM_INFO& activeRoom, ROOM_INFO& flippedRoom)
+{
+ RemoveRoomFlipItems(activeRoom);
+
+ // Swap rooms.
+ std::swap(activeRoom, flippedRoom);
+ activeRoom.flippedRoom = flippedRoom.flippedRoom;
+ flippedRoom.flippedRoom = NO_VALUE;
+ activeRoom.itemNumber = flippedRoom.itemNumber;
+ activeRoom.fxNumber = flippedRoom.fxNumber;
+
+ AddRoomFlipItems(activeRoom);
+
+ // Update active room sectors.
+ for (auto& sector : activeRoom.Sectors)
+ sector.RoomNumber = roomNumber;
+
+ // Update flipped room sectors.
+ for (auto& sector : flippedRoom.Sectors)
+ sector.RoomNumber = activeRoom.flippedRoom;
+
+ // Update renderer data.
+ g_Renderer.FlipRooms(roomNumber, activeRoom.flippedRoom);
+}
+
+void ResetRoomData()
+{
+ // Remove all door collisions.
+ for (const auto& item : g_Level.Items)
+ {
+ if (item.ObjectNumber == NO_VALUE || !item.Data.is())
+ continue;
+
+ auto& doorItem = g_Level.Items[item.Index];
+ auto& door = *(DOOR_DATA*)doorItem.Data;
+
+ if (door.opened)
+ continue;
+
+ OpenThatDoor(&door.d1, &door);
+ OpenThatDoor(&door.d2, &door);
+ OpenThatDoor(&door.d1flip, &door);
+ OpenThatDoor(&door.d2flip, &door);
+ door.opened = true;
+ }
+
+ // Unflip all rooms and remove all bridges and stopper flags.
+ for (int roomNumber = 0; roomNumber < g_Level.Rooms.size(); roomNumber++)
+ {
+ auto& room = g_Level.Rooms[roomNumber];
+ if (room.flippedRoom != NO_VALUE && room.flipNumber != NO_VALUE && FlipStats[room.flipNumber])
+ {
+ auto& flippedRoom = g_Level.Rooms[room.flippedRoom];
+ FlipRooms(roomNumber, room, flippedRoom);
+ }
+
+ for (auto& sector : room.Sectors)
+ {
+ sector.Stopper = false;
+ sector.BridgeItemNumbers.clear();
+ }
+ }
+
+ // Make sure no pathfinding boxes are blocked (either by doors or by other door-like objects).
+ for (int pathfindingBoxID = 0; pathfindingBoxID < g_Level.PathfindingBoxes.size(); pathfindingBoxID++)
+ g_Level.PathfindingBoxes[pathfindingBoxID].flags &= ~BLOCKED;
+}
+
void DoFlipMap(int group)
{
if (group >= MAX_FLIPMAP)
@@ -87,30 +157,10 @@ void DoFlipMap(int group)
auto& room = g_Level.Rooms[roomNumber];
// Handle flipmap.
- if (room.flippedRoom >= 0 && room.flipNumber == group)
+ if (room.flippedRoom != NO_VALUE && room.flipNumber == group)
{
auto& flippedRoom = g_Level.Rooms[room.flippedRoom];
-
- RemoveRoomFlipItems(room);
-
- // Swap rooms.
- std::swap(room, flippedRoom);
- room.flippedRoom = flippedRoom.flippedRoom;
- flippedRoom.flippedRoom = NO_VALUE;
- room.itemNumber = flippedRoom.itemNumber;
- room.fxNumber = flippedRoom.fxNumber;
-
- AddRoomFlipItems(room);
-
- g_Renderer.FlipRooms(roomNumber, room.flippedRoom);
-
- // Update active room sectors.
- for (auto& sector : room.Sectors)
- sector.RoomNumber = roomNumber;
-
- // Update flipped room sectors.
- for (auto& sector : flippedRoom.Sectors)
- sector.RoomNumber = room.flippedRoom;
+ FlipRooms(roomNumber, room, flippedRoom);
}
}
@@ -205,11 +255,11 @@ GameBoundingBox& GetBoundsAccurate(const MESH_INFO& mesh, bool getVisibilityBox)
if (getVisibilityBox)
{
- bounds = StaticObjects[mesh.staticNumber].visibilityBox * mesh.scale;
+ bounds = Statics[mesh.staticNumber].visibilityBox * mesh.scale;
}
else
{
- bounds = StaticObjects[mesh.staticNumber].collisionBox * mesh.scale;
+ bounds = Statics[mesh.staticNumber].collisionBox * mesh.scale;
}
return bounds;
@@ -219,6 +269,9 @@ bool IsPointInRoom(const Vector3i& pos, int roomNumber)
{
const auto& room = g_Level.Rooms[roomNumber];
+ if (!room.Active())
+ return false;
+
if (pos.z >= (room.Position.z + BLOCK(1)) && pos.z <= (room.Position.z + BLOCK(room.ZSize - 1)) &&
pos.y <= room.BottomHeight && pos.y > room.TopHeight &&
pos.x >= (room.Position.x + BLOCK(1)) && pos.x <= (room.Position.x + BLOCK(room.XSize - 1)))
@@ -237,21 +290,20 @@ int FindRoomNumber(const Vector3i& pos, int startRoomNumber, bool onlyNeighbors)
for (int neighborRoomNumber : room.NeighborRoomNumbers)
{
const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];
- if (neighborRoomNumber != startRoomNumber && neighborRoom.Active() &&
- IsPointInRoom(pos, neighborRoomNumber))
+ if (neighborRoomNumber != startRoomNumber && IsPointInRoom(pos, neighborRoomNumber))
{
return neighborRoomNumber;
}
}
}
- if (onlyNeighbors)
- return startRoomNumber;
-
- for (int roomNumber = 0; roomNumber < g_Level.Rooms.size(); roomNumber++)
+ if (!onlyNeighbors)
{
- if (IsPointInRoom(pos, roomNumber) && g_Level.Rooms[roomNumber].Active())
- return roomNumber;
+ for (int roomNumber = 0; roomNumber < g_Level.Rooms.size(); roomNumber++)
+ {
+ if (IsPointInRoom(pos, roomNumber))
+ return roomNumber;
+ }
}
return (startRoomNumber != NO_VALUE) ? startRoomNumber : 0;
diff --git a/TombEngine/Game/room.h b/TombEngine/Game/room.h
index fd228fb2d..eec49f807 100644
--- a/TombEngine/Game/room.h
+++ b/TombEngine/Game/room.h
@@ -21,18 +21,18 @@ extern int FlipMap[MAX_FLIPMAP];
enum RoomEnvFlags
{
- ENV_FLAG_WATER = (1 << 0),
- ENV_FLAG_SWAMP = (1 << 2),
- ENV_FLAG_OUTSIDE = (1 << 3),
- ENV_FLAG_DYNAMIC_LIT = (1 << 4),
- ENV_FLAG_WIND = (1 << 5),
- ENV_FLAG_NOT_NEAR_OUTSIDE = (1 << 6),
- ENV_FLAG_NO_LENSFLARE = (1 << 7),
- ENV_FLAG_MIST = (1 << 8),
- ENV_FLAG_CAUSTICS = (1 << 9),
- ENV_FLAG_UNKNOWN3 = (1 << 10),
- ENV_FLAG_DAMAGE = (1 << 11),
- ENV_FLAG_COLD = (1 << 12)
+ ENV_FLAG_WATER = (1 << 0),
+ ENV_FLAG_SWAMP = (1 << 2),
+ ENV_FLAG_SKYBOX = (1 << 3),
+ ENV_FLAG_DYNAMIC_LIT = (1 << 4),
+ ENV_FLAG_WIND = (1 << 5),
+ ENV_FLAG_NOT_NEAR_SKYBOX = (1 << 6),
+ ENV_FLAG_NO_LENSFLARE = (1 << 7),
+ ENV_FLAG_MIST = (1 << 8),
+ ENV_FLAG_CAUSTICS = (1 << 9),
+ ENV_FLAG_UNKNOWN3 = (1 << 10),
+ ENV_FLAG_DAMAGE = (1 << 11),
+ ENV_FLAG_COLD = (1 << 12)
};
enum StaticMeshFlags : short
@@ -125,6 +125,7 @@ struct ROOM_INFO
};
void DoFlipMap(int group);
+void ResetRoomData();
bool IsObjectInRoom(int roomNumber, GAME_OBJECT_ID objectID);
bool IsPointInRoom(const Vector3i& pos, int roomNumber);
int FindRoomNumber(const Vector3i& pos, int startRoomNumber = NO_VALUE, bool onlyNeighbors = false);
diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp
index 42a877e0e..872beacc7 100644
--- a/TombEngine/Game/savegame.cpp
+++ b/TombEngine/Game/savegame.cpp
@@ -247,6 +247,7 @@ const std::vector SaveGame::Build()
Save::SaveGameHeaderBuilder sghb{ fbb };
sghb.add_level_name(levelNameOffset);
+ sghb.add_level_hash(LastLevelHash);
auto gameTime = GetGameTime(GameTimer);
sghb.add_days(gameTime.Days);
@@ -1074,17 +1075,23 @@ const std::vector SaveGame::Build()
std::vector> globalEventSets{};
for (int j = 0; j < g_Level.GlobalEventSets.size(); j++)
{
+ std::vector statuses = {};
std::vector callCounters = {};
for (int k = 0; k < g_Level.GlobalEventSets[j].Events.size(); k++)
+ {
+ statuses.push_back(g_Level.GlobalEventSets[j].Events[k].Enabled);
callCounters.push_back(g_Level.GlobalEventSets[j].Events[k].CallCounter);
+ }
- auto vec = fbb.CreateVector(callCounters);
+ auto vecStatuses = fbb.CreateVector(statuses);
+ auto vecCounters = fbb.CreateVector(callCounters);
Save::EventSetBuilder eventSet{ fbb };
eventSet.add_index(j);
- eventSet.add_call_counters(vec);
+ eventSet.add_statuses(vecStatuses);
+ eventSet.add_call_counters(vecCounters);
globalEventSets.push_back(eventSet.Finish());
}
@@ -1094,17 +1101,23 @@ const std::vector SaveGame::Build()
std::vector> volumeEventSets{};
for (int j = 0; j < g_Level.VolumeEventSets.size(); j++)
{
+ std::vector statuses = {};
std::vector callCounters = {};
for (int k = 0; k < g_Level.VolumeEventSets[j].Events.size(); k++)
+ {
+ statuses.push_back(g_Level.VolumeEventSets[j].Events[k].Enabled);
callCounters.push_back(g_Level.VolumeEventSets[j].Events[k].CallCounter);
+ }
- auto vec = fbb.CreateVector(callCounters);
+ auto vecStatuses = fbb.CreateVector(statuses);
+ auto vecCounters = fbb.CreateVector(callCounters);
Save::EventSetBuilder eventSet{ fbb };
eventSet.add_index(j);
- eventSet.add_call_counters(vec);
+ eventSet.add_statuses(vecStatuses);
+ eventSet.add_call_counters(vecCounters);
volumeEventSets.push_back(eventSet.Finish());
}
@@ -1395,6 +1408,12 @@ const std::vector SaveGame::Build()
std::vector callbackVecPreLoop;
std::vector callbackVecPostLoop;
+ std::vector callbackVecPreUseItem;
+ std::vector callbackVecPostUseItem;
+
+ std::vector callbackVecPreFreeze;
+ std::vector callbackVecPostFreeze;
+
g_GameScript->GetCallbackStrings(
callbackVecPreStart,
callbackVecPostStart,
@@ -1405,7 +1424,11 @@ const std::vector SaveGame::Build()
callbackVecPreLoad,
callbackVecPostLoad,
callbackVecPreLoop,
- callbackVecPostLoop);
+ callbackVecPostLoop,
+ callbackVecPreUseItem,
+ callbackVecPostUseItem,
+ callbackVecPreFreeze,
+ callbackVecPostFreeze);
auto stringsCallbackPreStart = fbb.CreateVectorOfStrings(callbackVecPreStart);
auto stringsCallbackPostStart = fbb.CreateVectorOfStrings(callbackVecPostStart);
@@ -1417,6 +1440,10 @@ const std::vector SaveGame::Build()
auto stringsCallbackPostLoad = fbb.CreateVectorOfStrings(callbackVecPostLoad);
auto stringsCallbackPreLoop = fbb.CreateVectorOfStrings(callbackVecPreLoop);
auto stringsCallbackPostLoop = fbb.CreateVectorOfStrings(callbackVecPostLoop);
+ auto stringsCallbackPreUseItem = fbb.CreateVectorOfStrings(callbackVecPreUseItem);
+ auto stringsCallbackPostUseItem = fbb.CreateVectorOfStrings(callbackVecPostUseItem);
+ auto stringsCallbackPreFreeze = fbb.CreateVectorOfStrings(callbackVecPreFreeze);
+ auto stringsCallbackPostFreeze = fbb.CreateVectorOfStrings(callbackVecPostFreeze);
Save::SaveGameBuilder sgb{ fbb };
@@ -1483,6 +1510,12 @@ const std::vector SaveGame::Build()
sgb.add_callbacks_pre_loop(stringsCallbackPreLoop);
sgb.add_callbacks_post_loop(stringsCallbackPostLoop);
+ sgb.add_callbacks_pre_useitem(stringsCallbackPreUseItem);
+ sgb.add_callbacks_post_useitem(stringsCallbackPostUseItem);
+
+ sgb.add_callbacks_pre_freeze(stringsCallbackPreFreeze);
+ sgb.add_callbacks_post_freeze(stringsCallbackPostFreeze);
+
auto sg = sgb.Finish();
fbb.Finish(sg);
@@ -1506,15 +1539,18 @@ void SaveGame::SaveHub(int index)
void SaveGame::LoadHub(int index)
{
- // Don't attempt to load hub data if it doesn't exist, or level is a title level.
- if (index == 0 || !IsOnHub(index))
+ // Don't attempt to load hub data if level is a title level.
+ if (index == 0)
return;
- // Load hub data.
- TENLog("Loading hub data for level #" + std::to_string(index), LogLevel::Info);
- Parse(Hub[index], true);
+ if (IsOnHub(index))
+ {
+ // Load hub data.
+ TENLog("Loading hub data for level #" + std::to_string(index), LogLevel::Info);
+ Parse(Hub[index], true);
+ }
- // Restore vehicle.
+ // Restore vehicle (also for cases when no hub data yet exists).
InitializePlayerVehicle(*LaraItem);
}
@@ -1579,50 +1615,68 @@ bool SaveGame::Save(int slot)
bool SaveGame::Load(int slot)
{
if (!IsSaveGameSlotValid(slot))
+ {
+ TENLog("Savegame slot " + std::to_string(slot) + " is invalid, load is impossible.", LogLevel::Error);
return false;
+ }
if (!DoesSaveGameExist(slot))
+ {
+ TENLog("Savegame in slot " + std::to_string(slot) + " does not exist.", LogLevel::Error);
return false;
+ }
auto fileName = GetSavegameFilename(slot);
TENLog("Loading from savegame: " + fileName, LogLevel::Info);
- std::ifstream file;
- file.open(fileName, std::ios_base::app | std::ios_base::binary);
-
- int size;
- file.read(reinterpret_cast(&size), sizeof(size));
-
- // Read current level save data.
- std::vector saveData(size);
- file.read(reinterpret_cast(saveData.data()), size);
-
- // Reset hub data, as it's about to be replaced with saved one.
- ResetHub();
-
- // Read hub data from savegame.
- int hubCount;
- file.read(reinterpret_cast(&hubCount), sizeof(hubCount));
-
- TENLog("Hub count: " + std::to_string(hubCount), LogLevel::Info);
-
- for (int i = 0; i < hubCount; i++)
+ auto file = std::ifstream();
+ try
{
- int index;
- file.read(reinterpret_cast(&index), sizeof(index));
+ file.open(fileName, std::ios_base::app | std::ios_base::binary);
+ int size = 0;
file.read(reinterpret_cast(&size), sizeof(size));
- std::vector hubBuffer(size);
- file.read(reinterpret_cast(hubBuffer.data()), size);
- Hub[index] = hubBuffer;
+ // Read current level save data.
+ auto saveData = std::vector(size);
+ file.read(reinterpret_cast(saveData.data()), size);
+
+ // Reset hub data, as it's about to be replaced with saved one.
+ ResetHub();
+
+ // Read hub data from savegame.
+ int hubCount = 0;
+ file.read(reinterpret_cast(&hubCount), sizeof(hubCount));
+
+ TENLog("Hub count: " + std::to_string(hubCount), LogLevel::Info);
+
+ for (int i = 0; i < hubCount; i++)
+ {
+ int index = 0;
+ file.read(reinterpret_cast(&index), sizeof(index));
+
+ file.read(reinterpret_cast(&size), sizeof(size));
+ auto hubBuffer = std::vector(size);
+ file.read(reinterpret_cast(hubBuffer.data()), size);
+
+ Hub[index] = hubBuffer;
+ }
+
+ file.close();
+
+ // Load save data for current level.
+ Parse(saveData, false);
+ return true;
+ }
+ catch (std::exception& ex)
+ {
+ TENLog("Error while loading savegame: " + std::string(ex.what()), LogLevel::Error);
+
+ if (file.is_open())
+ file.close();
}
- file.close();
-
- // Load save data for current level.
- Parse(saveData, false);
- return true;
+ return false;
}
static void ParseStatistics(const Save::SaveGame* s, bool isHub)
@@ -1660,7 +1714,10 @@ static void ParseLua(const Save::SaveGame* s)
{
auto setSaved = s->volume_event_sets()->Get(i);
for (int j = 0; j < setSaved->call_counters()->size(); ++j)
+ {
+ g_Level.VolumeEventSets[setSaved->index()].Events[j].Enabled = setSaved->statuses()->Get(j);
g_Level.VolumeEventSets[setSaved->index()].Events[j].CallCounter = setSaved->call_counters()->Get(j);
+ }
}
}
@@ -1670,7 +1727,10 @@ static void ParseLua(const Save::SaveGame* s)
{
auto setSaved = s->global_event_sets()->Get(i);
for (int j = 0; j < setSaved->call_counters()->size(); ++j)
+ {
+ g_Level.GlobalEventSets[setSaved->index()].Events[j].Enabled = setSaved->statuses()->Get(j);
g_Level.GlobalEventSets[setSaved->index()].Events[j].CallCounter = setSaved->call_counters()->Get(j);
+ }
}
}
@@ -1776,6 +1836,12 @@ static void ParseLua(const Save::SaveGame* s)
auto callbacksPreLoopVec = populateCallbackVecs(&Save::SaveGame::callbacks_pre_loop);
auto callbacksPostLoopVec = populateCallbackVecs(&Save::SaveGame::callbacks_post_loop);
+ auto callbacksPreUseItemVec = populateCallbackVecs(&Save::SaveGame::callbacks_pre_useitem);
+ auto callbacksPostUseItemVec = populateCallbackVecs(&Save::SaveGame::callbacks_post_useitem);
+
+ auto callbacksPreFreezeVec = populateCallbackVecs(&Save::SaveGame::callbacks_pre_freeze);
+ auto callbacksPostFreezeVec = populateCallbackVecs(&Save::SaveGame::callbacks_post_freeze);
+
g_GameScript->SetCallbackStrings(
callbacksPreStartVec,
callbacksPostStartVec,
@@ -1786,7 +1852,11 @@ static void ParseLua(const Save::SaveGame* s)
callbacksPreLoadVec,
callbacksPostLoadVec,
callbacksPreLoopVec,
- callbacksPostLoopVec);
+ callbacksPostLoopVec,
+ callbacksPreUseItemVec,
+ callbacksPostUseItemVec,
+ callbacksPreFreezeVec,
+ callbacksPostFreezeVec);
}
static void ParsePlayer(const Save::SaveGame* s)
@@ -2046,7 +2116,7 @@ static void ParseEffects(const Save::SaveGame* s)
TENAssert(i < (int)SoundTrackType::Count, "Soundtrack type count was changed");
auto track = s->soundtracks()->Get(i);
- PlaySoundTrack(track->name()->str(), (SoundTrackType)i, track->position());
+ PlaySoundTrack(track->name()->str(), (SoundTrackType)i, track->position(), SOUND_XFADETIME_LEVELJUMP);
}
// Load fish swarm.
@@ -2207,6 +2277,8 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode)
room->mesh[number].flags = staticMesh->flags();
room->mesh[number].HitPoints = staticMesh->hit_points();
+
+ room->mesh[number].Dirty = true;
if (!room->mesh[number].flags)
{
@@ -2348,6 +2420,10 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode)
continue;
}
+ // If object is bridge - remove it from existing sectors.
+ if (item->IsBridge())
+ UpdateBridgeItem(g_Level.Items[i], BridgeUpdateType::Remove);
+
// Position
item->Pose = ToPose(*savedItem->pose());
item->RoomNumber = savedItem->room_number();
@@ -2415,8 +2491,9 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode)
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + savedItem->anim_number();
}
+ // Re-add bridges at new position.
if (item->IsBridge())
- UpdateBridgeItem(g_Level.Items[i]);
+ UpdateBridgeItem(g_Level.Items[i], BridgeUpdateType::Initialize);
// Creature data for intelligent items.
if (item->ObjectNumber != ID_LARA && item->Status == ITEM_ACTIVE && obj->intelligent)
@@ -2652,6 +2729,7 @@ bool SaveGame::LoadHeader(int slot, SaveGameHeader* header)
header->Level = s->header()->level();
header->LevelName = s->header()->level_name()->str();
+ header->LevelHash = s->header()->level_hash();
header->Days = s->header()->days();
header->Hours = s->header()->hours();
header->Minutes = s->header()->minutes();
diff --git a/TombEngine/Game/savegame.h b/TombEngine/Game/savegame.h
index 61a8d922e..3b09ac92d 100644
--- a/TombEngine/Game/savegame.h
+++ b/TombEngine/Game/savegame.h
@@ -11,32 +11,33 @@ constexpr auto SAVEGAME_MAX = 16;
struct Stats
{
- unsigned int Timer;
- unsigned int Distance;
- unsigned int AmmoHits;
- unsigned int AmmoUsed;
- unsigned int HealthUsed;
- unsigned int Kills;
- unsigned int Secrets;
+ unsigned int Timer = 0;
+ unsigned int Distance = 0;
+ unsigned int AmmoHits = 0;
+ unsigned int AmmoUsed = 0;
+ unsigned int HealthUsed = 0;
+ unsigned int Kills = 0;
+ unsigned int Secrets = 0;
};
struct GameStats
{
- Stats Game;
- Stats Level;
+ Stats Game = {};
+ Stats Level = {};
};
struct SaveGameHeader
{
- std::string LevelName;
- int Days;
- int Hours;
- int Minutes;
- int Seconds;
- int Level;
- int Timer;
- int Count;
- bool Present;
+ std::string LevelName = {};
+ int LevelHash = 0;
+ int Days = 0;
+ int Hours = 0;
+ int Minutes = 0;
+ int Seconds = 0;
+ int Level = 0;
+ int Timer = 0;
+ int Count = 0;
+ bool Present = false;
};
class SaveGame
diff --git a/TombEngine/Game/spotcam.cpp b/TombEngine/Game/spotcam.cpp
index 15baf58fd..e39ba51a0 100644
--- a/TombEngine/Game/spotcam.cpp
+++ b/TombEngine/Game/spotcam.cpp
@@ -659,10 +659,7 @@ void CalculateSpotCameras()
SpotcamPaused = 0;
if (LastCamera >= CurrentSplineCamera)
- {
- Camera.DisableInterpolation = true;
return;
- }
if (s->flags & SCF_LOOP_SEQUENCE)
{
@@ -693,13 +690,14 @@ void CalculateSpotCameras()
SetCinematicBars(0.0f, SPOTCAM_CINEMATIC_BARS_SPEED);
- Camera.DisableInterpolation = true;
UseSpotCam = false;
- Lara.Control.IsLocked = false;
CheckTrigger = false;
+ Lara.Control.IsLocked = false;
+ Lara.Control.Look.IsUsingBinoculars = false;
Camera.oldType = CameraType::Fixed;
Camera.type = CameraType::Chase;
Camera.speed = 1;
+ Camera.DisableInterpolation = true;
if (s->flags & SCF_CUT_TO_LARA_CAM)
{
diff --git a/TombEngine/Math/Objects/GameBoundingBox.cpp b/TombEngine/Math/Objects/GameBoundingBox.cpp
index d69209967..60177a6da 100644
--- a/TombEngine/Math/Objects/GameBoundingBox.cpp
+++ b/TombEngine/Math/Objects/GameBoundingBox.cpp
@@ -85,6 +85,36 @@
Z2 = (int)round(boxMax.z);
}
+ BoundingSphere GameBoundingBox::ToLocalBoundingSphere() const
+ {
+ return BoundingSphere(GetCenter(), GetExtents().Length());
+ }
+
+ BoundingBox GameBoundingBox::ToConservativeBoundingBox(const Pose& pose) const
+ {
+ // Calculate conservative radius in XZ plane for Y-axis rotation.
+ auto extents = GetExtents();
+ float xzRadius = sqrt(extents.x * extents.x + extents.z * extents.z);
+
+ // Extents remain unchanged along Y-axis.
+ auto conservativeExtents = Vector3(xzRadius, extents.y, xzRadius);
+
+ // Manual 2D rotation for Y-axis only.
+ auto center = GetCenter();
+ float cos = phd_cos(pose.Orientation.y);
+ float sin = phd_sin(pose.Orientation.y);
+
+ // Rotate centerOffset in XZ plane (ignore Y rotation for height).
+ float rotatedX = (cos * center.x) + (sin * center.z);
+ float rotatedZ = -(sin * center.x) + (cos * center.z);
+
+ // Box center is now position plus rotated offset.
+ auto rotatedCenter = pose.Position.ToVector3() + Vector3(rotatedX, center.y, rotatedZ);
+
+ // Build and return conservative AABB.
+ return BoundingBox(rotatedCenter, conservativeExtents);
+ }
+
BoundingOrientedBox GameBoundingBox::ToBoundingOrientedBox(const Pose& pose) const
{
return ToBoundingOrientedBox(pose.Position.ToVector3(), pose.Orientation.ToQuaternion());
diff --git a/TombEngine/Math/Objects/GameBoundingBox.h b/TombEngine/Math/Objects/GameBoundingBox.h
index 17569391d..cba6910cd 100644
--- a/TombEngine/Math/Objects/GameBoundingBox.h
+++ b/TombEngine/Math/Objects/GameBoundingBox.h
@@ -45,8 +45,10 @@ struct ObjectInfo;
// Converters
+ BoundingSphere ToLocalBoundingSphere() const;
+ BoundingBox ToConservativeBoundingBox(const Pose& pose) const; // TODO: item.GetAabb() method.
BoundingOrientedBox ToBoundingOrientedBox(const Pose& pose) const;
- BoundingOrientedBox ToBoundingOrientedBox(const Vector3& pos, const Quaternion& orient) const;
+ BoundingOrientedBox ToBoundingOrientedBox(const Vector3& pos, const Quaternion& orient) const; // TODO: item.GetObb() method.
// Operators
diff --git a/TombEngine/Math/Objects/Vector2i.cpp b/TombEngine/Math/Objects/Vector2i.cpp
index e8bc8fe61..89b093120 100644
--- a/TombEngine/Math/Objects/Vector2i.cpp
+++ b/TombEngine/Math/Objects/Vector2i.cpp
@@ -13,12 +13,12 @@ namespace TEN::Math
float Vector2i::Distance(const Vector2i& origin, const Vector2i& target)
{
- return Vector2::Distance(origin.ToVector2(), target.ToVector2());
+ return std::sqrt(DistanceSquared(origin, target));
}
float Vector2i::DistanceSquared(const Vector2i& origin, const Vector2i& target)
{
- return Vector2::DistanceSquared(origin.ToVector2(), target.ToVector2());
+ return (SQUARE(target.x - origin.x) + SQUARE(target.y - origin.y));
}
Vector2 Vector2i::ToVector2() const
diff --git a/TombEngine/Math/Objects/Vector3i.cpp b/TombEngine/Math/Objects/Vector3i.cpp
index b45e626bc..3b171c9d6 100644
--- a/TombEngine/Math/Objects/Vector3i.cpp
+++ b/TombEngine/Math/Objects/Vector3i.cpp
@@ -14,12 +14,12 @@
float Vector3i::Distance(const Vector3i& origin, const Vector3i& target)
{
- return Vector3::Distance(origin.ToVector3(), target.ToVector3());
+ return std::sqrt(DistanceSquared(origin, target));
}
-
+
float Vector3i::DistanceSquared(const Vector3i& origin, const Vector3i& target)
{
- return Vector3::DistanceSquared(origin.ToVector3(), target.ToVector3());
+ return (SQUARE(target.x - origin.x) + SQUARE(target.y - origin.y) + SQUARE(target.z - origin.z));
}
void Vector3i::Lerp(const Vector3i& target, float alpha)
diff --git a/TombEngine/Math/Random.cpp b/TombEngine/Math/Random.cpp
index 0131624dc..a7459051f 100644
--- a/TombEngine/Math/Random.cpp
+++ b/TombEngine/Math/Random.cpp
@@ -8,21 +8,33 @@
namespace TEN::Math::Random
{
- static std::mt19937 Engine;
+ static auto Generator = std::mt19937();
- int GenerateInt(int low, int high)
+ int GenerateInt(int min, int max)
{
- return (Engine() / (Engine.max() / (high - low + 1) + 1) + low);
+ if (min >= max)
+ {
+ TENLog("Attempted to generate integer with minimum value greater than maximum value.", LogLevel::Warning);
+ return min;
+ }
+
+ return (((Generator() / (Generator.max()) / (max - min + 1)) + 1) + min);
}
- float GenerateFloat(float low, float high)
+ float GenerateFloat(float min, float max)
{
- return ((high - low) * Engine() / Engine.max() + low);
+ if (min >= max)
+ {
+ TENLog("Attempted to generate float with minimum value greater than maximum value.", LogLevel::Warning);
+ return min;
+ }
+
+ return ((((max - min) * Generator()) / Generator.max()) + min);
}
- short GenerateAngle(short low, short high)
+ short GenerateAngle(short min, short max)
{
- return (short)GenerateInt(low, high);
+ return (short)GenerateInt(min, min);
}
Vector2 GenerateDirection2D()
diff --git a/TombEngine/Math/Random.h b/TombEngine/Math/Random.h
index 97b783993..ef197f7c5 100644
--- a/TombEngine/Math/Random.h
+++ b/TombEngine/Math/Random.h
@@ -6,9 +6,9 @@ namespace TEN::Math::Random
{
// Value generation
- int GenerateInt(int low = 0, int high = SHRT_MAX);
- float GenerateFloat(float low = 0.0f, float high = 1.0f);
- short GenerateAngle(short low = SHRT_MIN, short high = SHRT_MAX);
+ int GenerateInt(int min = 0, int max = SHRT_MAX);
+ float GenerateFloat(float min = 0.0f, float max = 1.0f);
+ short GenerateAngle(short min = SHRT_MIN, short max = SHRT_MAX);
// 2D geometric generation
diff --git a/TombEngine/Objects/Effects/LensFlare.cpp b/TombEngine/Objects/Effects/LensFlare.cpp
index 788b64c75..733e2a27a 100644
--- a/TombEngine/Objects/Effects/LensFlare.cpp
+++ b/TombEngine/Objects/Effects/LensFlare.cpp
@@ -12,27 +12,69 @@ using namespace TEN::Math;
namespace TEN::Entities::Effects
{
- std::vector LensFlares;
+ constexpr float MAX_INTENSITY = 1.0f; // Maximum intensity
+ constexpr float FADE_SPEED = 0.10f; // Speed of fade-in/out per frame
+ constexpr float SHIMMER_STRENGTH = 0.15f; // Max shimmer amplitude
+ constexpr float DEFAULT_FALLOFF_RADIUS = BLOCK(64);
- static void SetupLensFlare(const Vector3& pos, int roomNumber, const Color& color, bool isGlobal, int spriteID)
+ float GlobalLensFlareIntensity = 0;
+
+ std::vector LensFlares;
+
+ static void UpdateLensFlareIntensity(bool isVisible, float& intensity)
{
- auto lensFlarePos = Vector3::Zero;
+ // Target intensity based on visibility.
+ float targetIntensity = isVisible ? MAX_INTENSITY : 0.0f;
+
+ // Fade-in or fade-out.
+ if (intensity < targetIntensity)
+ intensity = std::min(intensity + FADE_SPEED, targetIntensity);
+ else if (intensity > targetIntensity)
+ intensity = std::max(intensity - FADE_SPEED, targetIntensity);
+ }
+
+ static void AdjustLensflarePosition(Vector3& lensFlarePos, const Vector3& targetPos, float divisor, float threshold)
+ {
+ // Gradually narrow lensflare vector down to target vector, until threshold is met.
+ auto delta = Vector3(FLT_MAX);
+ while (delta.Length() > threshold)
+ {
+ delta = lensFlarePos - targetPos;
+ lensFlarePos -= delta / divisor;
+ }
+ }
+
+ static void SetupLensFlare(const Vector3& pos, int roomNumber, const Color& color, float& intensity, int spriteID)
+ {
+ auto cameraPos = Camera.pos.ToVector3();
+ auto cameraTarget = Camera.target.ToVector3();
+
+ auto forward = (cameraTarget - cameraPos);
+ forward.Normalize();
+
+ // Discard lensflares behind camera.
+ if (forward.Dot(pos - cameraPos) < 0.0f)
+ return;
+
+ bool isGlobal = roomNumber == NO_VALUE;
+ bool isVisible = true;
+
+ // Don't draw global lensflare if camera is in a room with no lensflare flag set.
+ if (isGlobal && TestEnvironment(ENV_FLAG_NO_LENSFLARE, Camera.pos.RoomNumber))
+ isVisible = false;
+
+ auto lensFlarePos = pos;
+
if (isGlobal)
{
- if (TestEnvironment(ENV_FLAG_NO_LENSFLARE, Camera.pos.RoomNumber))
- return;
+ // Gradually move lensflare position to a nearest point within closest room.
+ AdjustLensflarePosition(lensFlarePos, cameraPos, 8.0f, BLOCK(256));
+ AdjustLensflarePosition(lensFlarePos, cameraPos, 4.0f, BLOCK(32));
- lensFlarePos = pos;
- auto delta = (lensFlarePos - Camera.pos.ToVector3()) / 16.0f;
- while (abs(delta.x) > BLOCK(200) || abs(delta.y) > BLOCK(200) || abs(delta.z) > BLOCK(200))
- lensFlarePos -= delta;
+ // FAILSAFE: Break while loop if room can't be found (e.g. camera is in the void).
+ int narrowingCycleCount = 0;
- delta = (lensFlarePos - Camera.pos.ToVector3()) / 16.0f;
- while (abs(delta.x) > BLOCK(32) || abs(delta.y) > BLOCK(32) || abs(delta.z) > BLOCK(32))
- lensFlarePos -= delta;
-
- delta = (lensFlarePos - Camera.pos.ToVector3()) / 16.0f;
- for (int i = 0; i < 16; i++)
+ while (roomNumber == NO_VALUE && narrowingCycleCount < 16)
{
int foundRoomNumber = IsRoomOutside(lensFlarePos.x, lensFlarePos.y, lensFlarePos.z);
if (foundRoomNumber != NO_VALUE)
@@ -41,37 +83,55 @@ namespace TEN::Entities::Effects
break;
}
- lensFlarePos -= delta;
+ AdjustLensflarePosition(lensFlarePos, cameraPos, 2.0f, BLOCK(32));
+ narrowingCycleCount++;
}
+
+ // Don't draw global lensflare, if not in room or in rooms where skybox is not visible.
+ if (roomNumber == NO_VALUE || TestEnvironment(ENV_FLAG_NOT_NEAR_SKYBOX, roomNumber))
+ isVisible = false;
}
- else
+
+ // Do occlusion tests only if lensflare passed the previous checks.
+ if (isVisible)
{
- float dist = Vector3::Distance(pos, Camera.pos.ToVector3());
- if (dist > BLOCK(32))
- return;
+ auto origin = GameVector(lensFlarePos, roomNumber);
+ auto target = Camera.pos;
+ auto distance = Vector3::Distance(origin.ToVector3(), target.ToVector3());
- lensFlarePos = pos;
+ // Check room occlusion.
+ isVisible = LOS(&origin, &target);
+
+ MESH_INFO* mesh = nullptr;
+ auto pointOfContact = Vector3i();
+
+ // Check occlusion for all static meshes and moveables but player.
+ bool collided = isVisible && ObjectOnLOS2(&origin, &target, &pointOfContact, &mesh) != NO_LOS_ITEM;
+ if (collided && Vector3::Distance(pointOfContact.ToVector3(), origin.ToVector3()) < distance)
+ isVisible = false;
+
+ // Check occlusion only for player.
+ collided = isVisible && ObjectOnLOS2(&origin, &target, &pointOfContact, nullptr, ID_LARA) != NO_LOS_ITEM;
+ if (collided && Vector3::Distance(pointOfContact.ToVector3(), origin.ToVector3()) < distance)
+ isVisible = false;
}
- bool isVisible = false;
- if (roomNumber != NO_VALUE)
- {
- if (TestEnvironment(ENV_FLAG_NOT_NEAR_OUTSIDE, roomNumber) || !isGlobal)
- {
- auto origin = Camera.pos;
- auto target = GameVector(lensFlarePos, roomNumber);
- isVisible = LOS(&origin, &target);
- }
- }
+ // Fade in/out lensflares depending on their visibility.
+ UpdateLensFlareIntensity(isVisible, intensity);
- if (!isVisible && !isGlobal)
+ // Lensflare is completely invisible.
+ if (!isVisible && intensity == 0.0f)
return;
+ // Generate slight shimmer.
+ float shimmer = Random::GenerateFloat(-SHIMMER_STRENGTH, SHIMMER_STRENGTH);
+ float finalIntensity = std::clamp(Smoothstep(intensity) + shimmer * intensity, 0.0f, MAX_INTENSITY);
+
auto lensFlare = LensFlare{};
lensFlare.Position = pos;
lensFlare.RoomNumber = roomNumber;
lensFlare.IsGlobal = isGlobal;
- lensFlare.Color = color;
+ lensFlare.Color = color * Smoothstep(finalIntensity);
lensFlare.SpriteID = spriteID;
LensFlares.push_back(lensFlare);
@@ -92,15 +152,37 @@ namespace TEN::Entities::Effects
auto rotMatrix = orient.ToRotationMatrix();
pos += Vector3::Transform(BASE_POS, rotMatrix);
- SetupLensFlare(pos, NO_VALUE, color, true, spriteID);
+ SetupLensFlare(pos, NO_VALUE, color, GlobalLensFlareIntensity, spriteID);
}
void ControlLensFlare(int itemNumber)
{
auto& item = g_Level.Items[itemNumber];
- if (TriggerActive(&item))
- SetupLensFlare(item.Pose.Position.ToVector3(), item.RoomNumber, Color(), false, SPRITE_TYPES::SPR_LENS_FLARE_3);
+ if (!TriggerActive(&item))
+ return;
+
+ auto color = item.Model.Color;
+
+ // If OCB is set, it specifies radius in blocks, after which flare starts to fadeout.
+ float radius = (item.TriggerFlags > 0) ? (float)(item.TriggerFlags * BLOCK(1)) : DEFAULT_FALLOFF_RADIUS;
+ float distance = Vector3i::Distance(item.Pose.Position, Camera.pos.ToVector3i());
+
+ if (distance > radius)
+ {
+ float fadeMultiplier = std::max((1.0f - ((distance - radius) / radius)), 0.0f);
+
+ // Discard flare, if it is out of falloff sphere radius.
+ if (fadeMultiplier <= 0.0f)
+ return;
+
+ color *= fadeMultiplier;
+ }
+
+ // Intensity value can be modified inside lensflare setup function.
+ float currentIntensity = (float)item.ItemFlags[0] / 100.0f;
+ SetupLensFlare(item.Pose.Position.ToVector3(), item.RoomNumber, color, currentIntensity, SPRITE_TYPES::SPR_LENS_FLARE_3);
+ item.ItemFlags[0] = (short)(currentIntensity * 100.0f);
}
void ClearLensFlares()
diff --git a/TombEngine/Objects/Effects/effect_objects.cpp b/TombEngine/Objects/Effects/effect_objects.cpp
index 0b6b1f9ec..f157af040 100644
--- a/TombEngine/Objects/Effects/effect_objects.cpp
+++ b/TombEngine/Objects/Effects/effect_objects.cpp
@@ -58,5 +58,9 @@ void InitializeEffectsObjects()
obj = &Objects[ID_LENS_FLARE];
if (obj->loaded)
+ {
+ obj->drawRoutine = nullptr;
obj->control = ControlLensFlare;
+ obj->AlwaysActive = true;
+ }
}
diff --git a/TombEngine/Objects/Generic/Doors/generic_doors.cpp b/TombEngine/Objects/Generic/Doors/generic_doors.cpp
index 756dfd0c2..583570724 100644
--- a/TombEngine/Objects/Generic/Doors/generic_doors.cpp
+++ b/TombEngine/Objects/Generic/Doors/generic_doors.cpp
@@ -92,7 +92,7 @@ namespace TEN::Entities::Doors
doorData->d1.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE;
doorData->d1.data = *doorData->d1.floor;
- if (r->flippedRoom != -1)
+ if (r->flippedRoom != NO_VALUE)
{
r = &g_Level.Rooms[r->flippedRoom];
doorData->d1flip.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x + xOffset, doorItem->Pose.Position.z - r->Position.z + zOffset);
diff --git a/TombEngine/Objects/Generic/Object/generic_bridge.cpp b/TombEngine/Objects/Generic/Object/generic_bridge.cpp
index 81abfa392..c8ccd680f 100644
--- a/TombEngine/Objects/Generic/Object/generic_bridge.cpp
+++ b/TombEngine/Objects/Generic/Object/generic_bridge.cpp
@@ -104,6 +104,6 @@ namespace TEN::Entities::Generic
break;
}
- UpdateBridgeItem(bridgeItem);
+ UpdateBridgeItem(bridgeItem, BridgeUpdateType::Initialize);
}
}
diff --git a/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp b/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp
index c8d31ee1d..b68d8d044 100644
--- a/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp
+++ b/TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp
@@ -540,7 +540,7 @@ namespace TEN::Entities::Creatures::TR2
if (target.ItemFlags[1] == 1)
{
if (pos.has_value())
- TriggerRicochetSpark(pos.value(), source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(pos.value(), source.Pose.Orientation.y);
return;
}
diff --git a/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp b/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp
index e3fb2e420..5b5e246e4 100644
--- a/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp
+++ b/TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp
@@ -375,7 +375,7 @@ namespace TEN::Entities::Creatures::TR2
if (target.ItemFlags[1] == 1)
{
if (pos.has_value())
- TriggerRicochetSpark(pos.value(), source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(pos.value(), source.Pose.Orientation.y);
return;
}
diff --git a/TombEngine/Objects/TR3/Entity/FishSwarm.cpp b/TombEngine/Objects/TR3/Entity/FishSwarm.cpp
index ae67854f0..7adb8940a 100644
--- a/TombEngine/Objects/TR3/Entity/FishSwarm.cpp
+++ b/TombEngine/Objects/TR3/Entity/FishSwarm.cpp
@@ -184,7 +184,7 @@ namespace TEN::Entities::Creatures::TR3
// Follow path.
if (item.AIBits && !item.ItemFlags[4])
{
- FindAITargetObject(item, ID_AI_FOLLOW, item.ItemFlags[3] + item.ItemFlags[2], false);
+ FindAITargetObject(&creature, ID_AI_FOLLOW, item.ItemFlags[3] + item.ItemFlags[2], false);
if (creature.AITarget->TriggerFlags == (item.ItemFlags[3] + item.ItemFlags[2]) &&
creature.AITarget->ObjectNumber == ID_AI_FOLLOW)
diff --git a/TombEngine/Objects/TR3/Entity/Shiva.cpp b/TombEngine/Objects/TR3/Entity/Shiva.cpp
index e93810050..fbe57eb44 100644
--- a/TombEngine/Objects/TR3/Entity/Shiva.cpp
+++ b/TombEngine/Objects/TR3/Entity/Shiva.cpp
@@ -537,7 +537,7 @@ namespace TEN::Entities::Creatures::TR3
if (target.ItemFlags[1] != 0)
{
SoundEffect(SFX_TR4_WEAPON_RICOCHET, &target.Pose);
- TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(*pos, source.Pose.Orientation.y, false);
return;
}
@@ -546,7 +546,7 @@ namespace TEN::Entities::Creatures::TR3
target.Animation.ActiveState == SHIVA_STATE_GUARD_IDLE)
{
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
- TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(*pos, source.Pose.Orientation.y, false);
return;
}
}
diff --git a/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp b/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp
index d4ae15027..ea752dae8 100644
--- a/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp
+++ b/TombEngine/Objects/TR3/Entity/SophiaLeigh.cpp
@@ -289,7 +289,7 @@ namespace TEN::Entities::Creatures::TR3
// Check the previous and next position of AI object to
// allow Sophia to go up or down based on enemy's vertical position.
- FindAITargetObject(item, ID_AI_X1, creature->LocationAI, false);
+ FindAITargetObject(creature, ID_AI_X1, creature->LocationAI, false);
if (Vector3i::Distance(item.Pose.Position, creature->Enemy->Pose.Position) < SOPHIALEIGH_REACHED_GOAL_RANGE)
{
diff --git a/TombEngine/Objects/TR3/Entity/Winston.cpp b/TombEngine/Objects/TR3/Entity/Winston.cpp
index 484d99524..daf3b3ae4 100644
--- a/TombEngine/Objects/TR3/Entity/Winston.cpp
+++ b/TombEngine/Objects/TR3/Entity/Winston.cpp
@@ -315,7 +315,7 @@ namespace TEN::Entities::Creatures::TR3
if (object.hitEffect == HitEffect::Richochet)
{
- TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(*pos, source.Pose.Orientation.y, false);
SoundEffect(SFX_TR3_WINSTON_CUPS, &target.Pose);
}
}
diff --git a/TombEngine/Objects/TR3/tr3_objects.cpp b/TombEngine/Objects/TR3/tr3_objects.cpp
index 950c4fb7d..3e9d42952 100644
--- a/TombEngine/Objects/TR3/tr3_objects.cpp
+++ b/TombEngine/Objects/TR3/tr3_objects.cpp
@@ -73,7 +73,7 @@ static void StartEntity(ObjectInfo* obj)
obj->nonLot = true; // NOTE: Doesn't move to reach the player, only throws projectiles.
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y);
- obj->SetHitEffect();
+ obj->SetHitEffect(HitEffect::NonExplosive);
}
obj = &Objects[ID_TIGER];
@@ -300,7 +300,7 @@ static void StartEntity(ObjectInfo* obj)
obj->LotType = LotType::Human;
obj->SetBoneRotationFlags(6, ROT_X | ROT_Y);
obj->SetBoneRotationFlags(13, ROT_Y);
- obj->SetHitEffect();
+ obj->SetHitEffect(HitEffect::NonExplosive);
}
obj = &Objects[ID_CIVVY];
@@ -365,7 +365,7 @@ static void StartEntity(ObjectInfo* obj)
obj->pivotLength = 50;
obj->SetBoneRotationFlags(4, ROT_Y); // Puna quest object.
obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); // Head.
- obj->SetHitEffect();
+ obj->SetHitEffect(HitEffect::NonExplosive);
}
obj = &Objects[ID_WASP_MUTANT];
diff --git a/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp b/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp
index 80c1036ef..2b7d6bb87 100644
--- a/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp
+++ b/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp
@@ -345,7 +345,7 @@ namespace TEN::Entities::TR4
if (item->TriggerFlags % 1000 > 100)
{
item->ItemFlags[0] = -80;
- FindAITargetObject(*item, ID_AI_X1);
+ FindAITargetObject(creature, ID_AI_X1);
}
item->TriggerFlags = 1000 * (item->TriggerFlags / 1000);
@@ -1315,7 +1315,7 @@ namespace TEN::Entities::TR4
{
// Baddy2 bullet deflection with sword.
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
- TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(*pos, source.Pose.Orientation.y, false);
return;
}
else if (object.hitEffect == HitEffect::Blood)
diff --git a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp
index 3930f6872..98791d0f5 100644
--- a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp
+++ b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.cpp
@@ -8,441 +8,375 @@
#include "Game/control/lot.h"
#include "Game/control/trigger.h"
#include "Game/effects/effects.h"
-#include "Game/effects/smoke.h"
#include "Game/effects/tomb4fx.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
-#include "Game/Lara/lara_one_gun.h"
#include "Game/misc.h"
-#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
-#include "Renderer/Renderer.h"
#include "Sound/sound.h"
#include "Specific/level.h"
using namespace TEN::Collision::Point;
-using namespace TEN::Effects::Smoke;
using namespace TEN::Math;
-using namespace TEN::Renderer;
-
-/// item.ItemFlags[1] = AI_X2 behaviour
-/// item.ItemFlags[2] = Wheel rotation
-/// item.ItemFlags[3] = Grenade cooldown
-/// item.ItemFlags[4] = Behaviour when idle
-/// item.ItemFlags[5] = Wait radius
namespace TEN::Entities::TR4
{
- enum EnemyJeepAnim
+ void EnemyJeepLaunchGrenade(ItemInfo* item)
{
- ENEMY_JEEP_ANIM_MOVE_START = 0,
- ENEMY_JEEP_ANIM_MOVE_STOP = 1,
- ENEMY_JEEP_ANIM_MOVE = 2,
- ENEMY_JEEP_ANIM_TURN_LEFT_START = 3,
- ENEMY_JEEP_ANIM_TURN_LEFT = 4,
- ENEMY_JEEP_ANIM_TURN_LEFT_END = 5,
- ENEMY_JEEP_ANIM_FALL_END = 6,
- ENEMY_JEEP_ANIM_FALL_2_STEPS = 7,
- ENEMY_JEEP_ANIM_JUMP_2_STEP_PIT = 8,
- ENEMY_JEEP_ANIM_IDLE = 9,
- ENEMY_JEEP_ANIM_TURN_RIGHT_START = 10,
- ENEMY_JEEP_ANIM_TURN_RIGHT = 11,
- ENEMY_JEEP_ANIM_TURN_RIGHT_END = 12
- };
+ short grenadeItemNumber = CreateItem();
- enum EnemyJeepState
- {
- ENEMY_JEEP_STATE_IDLE = 0,
- ENEMY_JEEP_STATE_MOVE = 1,
- ENEMY_JEEP_STATE_STOP = 2,
- ENEMY_JEEP_STATE_TURN_LEFT = 3,
- ENEMY_JEEP_STATE_TURN_RIGHT = 4,
- ENEMY_JEEP_STATE_DROP_LAND = 5,
-
- // States to allow customization.
-
- ENEMY_JEEP_STATE_DROP = 6,
- ENEMY_JEEP_STATE_JUMP_PIT = 7,
- ENEMY_JEEP_STATE_CUSTOM_DROP = 8,
- ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT = 9
- };
-
- enum EnemyJeepOcb
- {
- EJ_NO_PLAYER_VEHICLE_REQUIRED = 1 // Starts immediately instead of waiting for player to enter vehicle.
- };
-
- enum EnemyJeepX2Ocb
- {
- X2_DROP_GRENADE = 1,
- X2_DROP = 2,
- X2_JUMP_PIT = 3,
- // Need ocb 4 + block distance
- // Example: 4 + 1024 = 4 block distance + wait behaviour.
- X2_WAIT_UNTIL_PLAYER_NEAR = 4,
- X2_DISAPPEAR = 5,
- X2_ACTIVATE_HEAVY_TRIGGER = 6,
- X2_CUSTOM_DROP = 7, // Another drop step for customization.
- X2_CUSTOM_JUMP_PIT = 8, // Another jump steps for customization.
- };
-
- constexpr auto ENEMY_JEEP_GRENADE_VELOCITY = 32.0f;
- constexpr auto ENEMY_JEEP_GRENADE_TIMER = 150;
-
- constexpr auto ENEMY_JEEP_RIGHT_LIGHT_MESHBITS = 15;
- constexpr auto ENEMY_JEEP_LEFT_LIGHT_MESHBITS = 17;
- constexpr auto ENEMY_JEEP_GRENADE_COOLDOWN_TIME = 15;
- constexpr auto ENEMY_JEEP_PLAYER_IS_NEAR = BLOCK(6.0f);
- constexpr auto ENEMY_JEEP_NEAR_X1_NODE_DISTANCE = BLOCK(1);
- constexpr auto ENEMY_JEEP_NEAR_X2_NODE_DISTANCE = BLOCK(0.3f);
- constexpr auto ENEMY_JEEP_PITCH_MAX = 120.0f;
- constexpr auto ENEMY_JEEP_PITCH_WHEEL_SPEED_MULTIPLIER = 12.0f;
-
- constexpr auto ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM = ANGLE(12.0f);
-
- constexpr auto ENEMY_JEEP_CENTER_MESH = 11;
-
- const auto EnemyJeepGrenadeBite = CreatureBiteInfo(Vector3(0.0f, -640.0f, -768.0f), ENEMY_JEEP_CENTER_MESH);
- const auto EnemyJeepRightLightBite = CreatureBiteInfo(Vector3(200.0f, -144.0f, -768.0f), ENEMY_JEEP_CENTER_MESH);
- const auto EnemyJeepLeftLightBite = CreatureBiteInfo(Vector3(-200.0f, -144.0f, -768.0f), ENEMY_JEEP_CENTER_MESH);
-
- static void DrawEnemyJeepLightMesh(ItemInfo& item, bool isBraking)
- {
- if (isBraking)
+ if (grenadeItemNumber != NO_VALUE)
{
- item.MeshBits.Set(ENEMY_JEEP_RIGHT_LIGHT_MESHBITS);
- item.MeshBits.Set(ENEMY_JEEP_LEFT_LIGHT_MESHBITS);
- }
- else
- {
- item.MeshBits.Clear(ENEMY_JEEP_RIGHT_LIGHT_MESHBITS);
- item.MeshBits.Clear(ENEMY_JEEP_LEFT_LIGHT_MESHBITS);
- }
- }
+ auto* grenadeItem = &g_Level.Items[grenadeItemNumber];
- static void SpawnEnemyJeepBrakeLights(const ItemInfo& item)
- {
- constexpr auto COLOR = Color(0.25f, 0.0f, 0.0f);
- constexpr auto FALLOFF = 0.04f;
+ grenadeItem->Model.Color = Vector4(0.5f, 0.5f, 0.5f, 1.0f);
+ grenadeItem->ObjectNumber = ID_GRENADE;
+ grenadeItem->RoomNumber = item->RoomNumber;
- auto leftPos = GetJointPosition(item, EnemyJeepLeftLightBite).ToVector3();
- TriggerDynamicLight(leftPos, COLOR, FALLOFF);
+ InitializeItem(grenadeItemNumber);
- auto rightPos = GetJointPosition(item, EnemyJeepRightLightBite).ToVector3();
- TriggerDynamicLight(rightPos, COLOR, FALLOFF);
- }
+ grenadeItem->Pose.Orientation.x = item->Pose.Orientation.x;
+ grenadeItem->Pose.Orientation.y = item->Pose.Orientation.y - ANGLE(180.0f);
+ grenadeItem->Pose.Orientation.z = 0;
- static void SpawnEnemyJeepGrenade(ItemInfo& item)
- {
- int grenadeItemNumber = CreateItem();
- if (grenadeItemNumber == NO_VALUE || item.ItemFlags[3] > 0)
- return;
+ grenadeItem->Pose.Position.x = item->Pose.Position.x + BLOCK(1) * phd_sin(grenadeItem->Pose.Orientation.y);
+ grenadeItem->Pose.Position.y = item->Pose.Position.y - CLICK(3);
+ grenadeItem->Pose.Position.z = item->Pose.Position.x + BLOCK(1) * phd_cos(grenadeItem->Pose.Orientation.y);
- auto& grenadeItem = g_Level.Items[grenadeItemNumber];
+ for (int i = 0; i < 5; i++)
+ TriggerGunSmoke(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, 0, 0, 0, 1, LaraWeaponType::GrenadeLauncher, 32);
- grenadeItem.ObjectNumber = ID_GRENADE;
- grenadeItem.RoomNumber = item.RoomNumber;
- grenadeItem.Model.Color = Color(0.5f, 0.5f, 0.5f, 1.0f);
+ if (Random::TestProbability(0.75f))
+ grenadeItem->ItemFlags[0] = 1;
+ else
+ grenadeItem->ItemFlags[0] = 2;
- auto grenadePos = GetJointPosition(item, EnemyJeepGrenadeBite);
- auto grenadeposF = Vector3(grenadePos.x, grenadePos.y, grenadePos.z);
+ grenadeItem->Animation.ActiveState = grenadeItem->Pose.Orientation.x;
+ grenadeItem->Animation.TargetState = grenadeItem->Pose.Orientation.y;
+ grenadeItem->Animation.RequiredState = NO_VALUE;
+ grenadeItem->Animation.Velocity.z = 32;
+ grenadeItem->Animation.Velocity.y = -32 * phd_sin(grenadeItem->Pose.Orientation.x);
+ grenadeItem->HitPoints = 120;
- grenadeItem.Pose.Orientation = EulerAngles(item.Pose.Orientation.x, item.Pose.Orientation.y + ANGLE(180.0f), 0);
- grenadeItem.Pose.Position = grenadePos + Vector3i(BLOCK(0.1f) * phd_sin(grenadeItem.Pose.Orientation.y), 0, BLOCK(0.1f) * phd_cos(grenadeItem.Pose.Orientation.y));
-
- InitializeItem(grenadeItemNumber);
-
- for (int i = 0; i < 9; i++)
- SpawnGunSmokeParticles(grenadeposF, Vector3(0, 0, 1), item.RoomNumber, 1, LaraWeaponType::RocketLauncher, 32);
-
- if (GetRandomControl() & 3)
- {
- grenadeItem.ItemFlags[0] = (int)ProjectileType::Grenade;
- }
- else
- {
- grenadeItem.ItemFlags[0] = (int)ProjectileType::FragGrenade;
- }
-
- grenadeItem.Animation.Velocity.z = ENEMY_JEEP_GRENADE_VELOCITY;
- grenadeItem.Animation.Velocity.y = CLICK(1) * phd_sin(grenadeItem.Pose.Orientation.x);
- grenadeItem.Animation.ActiveState = grenadeItem.Pose.Orientation.x;
- grenadeItem.Animation.TargetState = grenadeItem.Pose.Orientation.y;
- grenadeItem.Animation.RequiredState = NO_VALUE;
- grenadeItem.HitPoints = ENEMY_JEEP_GRENADE_TIMER;
-
- item.ItemFlags[3] = ENEMY_JEEP_GRENADE_COOLDOWN_TIME;
-
- AddActiveItem(grenadeItemNumber);
- SoundEffect(SFX_TR4_GRENADEGUN_FIRE, &item.Pose);
-; }
-
- static void RotateTowardTarget(ItemInfo& item, const short angle, short turnRate)
- {
- if (abs(angle) < turnRate)
- {
- item.Pose.Orientation.y += angle;
- }
- else if (angle < 0)
- {
- item.Pose.Orientation.y -= turnRate;
- }
- else
- {
- item.Pose.Orientation.y += turnRate;
- }
- }
-
- // Check for X1 and X2 AI object and do a path based on X1 ocb, check behaviour with X2 like throw grenade or stop and wait for X sec...
- static void DoNodePath(ItemInfo& item)
- {
- auto& creature = *GetCreatureInfo(&item);
-
- // Use it to setup the path
- FindAITargetObject(item, ID_AI_X1, creature.LocationAI, false);
- if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= ENEMY_JEEP_NEAR_X1_NODE_DISTANCE)
- creature.LocationAI++;
-
- // Use it to get behaviour if you arrive on X2 ai without modifing the creature.Enemy.
- auto data = AITargetData{};
- data.CheckDistance = true;
- data.CheckOcb = false;
- data.ObjectID = ID_AI_X2;
- data.Ocb = NO_VALUE;
- data.CheckSameZone = false;
- data.DistanceMax = ENEMY_JEEP_NEAR_X2_NODE_DISTANCE;
-
- if (FindAITargetObject(item, data))
- {
- DrawDebugSphere(data.FoundItem.Pose.Position.ToVector3(), 128.0f, Color(1, 1, 0), RendererDebugPage::WireframeMode, true);
- item.ItemFlags[1] = data.FoundItem.TriggerFlags;
- }
- }
-
- // Process the AI_X2 ocb and do any required query, like drop grenade or jump pit.
- static void ProcessBehaviour(ItemInfo& item)
- {
- switch (item.ItemFlags[1])
- {
- // Drop grenade.
- case X2_DROP_GRENADE:
- SpawnEnemyJeepGrenade(item);
- break;
-
- // Drop 2 step or more.
- case X2_DROP:
- item.Animation.TargetState = ENEMY_JEEP_STATE_DROP;
- break;
-
- // Jump 2 step pit.
- case X2_JUMP_PIT:
- item.Animation.TargetState = ENEMY_JEEP_STATE_JUMP_PIT;
- break;
-
- // Make the entity disappear/kill itself.
- case X2_DISAPPEAR:
- item.Status = ITEM_INVISIBLE;
- item.Flags |= IFLAG_INVISIBLE;
- RemoveActiveItem(item.Index);
- DisableEntityAI(item.Index);
- break;
-
- // Make the entity start heavy trigger below it.
- case X2_ACTIVATE_HEAVY_TRIGGER:
- TestTriggers(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, item.RoomNumber, true, 0);
- break;
-
- case X2_CUSTOM_DROP:
- item.Animation.TargetState = ENEMY_JEEP_STATE_CUSTOM_DROP;
- break;
-
- case X2_CUSTOM_JUMP_PIT:
- item.Animation.TargetState = ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT;
- break;
-
- default:
- bool waitBehaviour = (item.ItemFlags[1] & X2_WAIT_UNTIL_PLAYER_NEAR) != 0;
- if (waitBehaviour)
- {
- item.Animation.TargetState = ENEMY_JEEP_STATE_STOP;
- item.ItemFlags[4] = 1;
- item.ItemFlags[5] = item.ItemFlags[1] - X2_WAIT_UNTIL_PLAYER_NEAR;
- }
-
- break;
- }
-
- item.ItemFlags[1] = 0; // Reset X2 flags to avoid behaviour loop.
- }
-
- static bool IsJeepIdle(int activeState)
- {
- return (activeState == ENEMY_JEEP_STATE_IDLE ||
- activeState == ENEMY_JEEP_STATE_STOP);
- }
-
- static bool IsJeepMoving(int activeState)
- {
- return (activeState == ENEMY_JEEP_STATE_MOVE ||
- activeState == ENEMY_JEEP_STATE_TURN_LEFT ||
- activeState == ENEMY_JEEP_STATE_TURN_RIGHT);
- }
-
- static bool IsJeepJumpingOrDropping(int activeState, bool onlyJump = false)
- {
- if (onlyJump)
- {
- return (activeState == ENEMY_JEEP_STATE_JUMP_PIT ||
- activeState == ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT);
- }
- else
- {
- return (activeState == ENEMY_JEEP_STATE_JUMP_PIT ||
- activeState == ENEMY_JEEP_STATE_DROP ||
- activeState == ENEMY_JEEP_STATE_CUSTOM_DROP ||
- activeState == ENEMY_JEEP_STATE_CUSTOM_JUMP_PIT);
+ AddActiveItem(grenadeItemNumber);
}
}
void InitializeEnemyJeep(short itemNumber)
{
- auto& item = g_Level.Items[itemNumber];
- item.ItemFlags[2] = 0;
- item.ItemFlags[3] = 0;
- item.ItemFlags[4] = NO_VALUE;
- item.ItemFlags[5] = NO_VALUE;
+ auto* item = &g_Level.Items[itemNumber];
- InitializeCreature(itemNumber);
- SetAnimation(item, ENEMY_JEEP_ANIM_IDLE);
- DrawEnemyJeepLightMesh(item, true);
+ item->ItemFlags[0] = -80;
+
+ if (g_Level.NumItems > 0)
+ {
+ for (int i = 0; i < g_Level.NumItems; i++)
+ {
+ auto* other = &g_Level.Items[i];
+
+ if (other == item || other->TriggerFlags != item->TriggerFlags)
+ continue;
+
+ item->ItemFlags[1] = i;
+ other->ItemFlags[0] = -80;
+ other->Pose.Position.y = item->Pose.Position.y - BLOCK(1);
+ }
+ }
}
- void ControlEnemyJeep(short itemNumber)
+ void EnemyJeepControl(short itemNumber)
{
- if (!CreatureActive(itemNumber))
- return;
-
- auto& item = g_Level.Items[itemNumber];
- auto& creature = *GetCreatureInfo(&item);
- auto& object = Objects[item.ObjectNumber];
-
- AI_INFO ai = {};
-
- if (item.ItemFlags[3] > 0)
- item.ItemFlags[3]--;
- item.ItemFlags[3] = std::clamp(item.ItemFlags[3], 0, ENEMY_JEEP_GRENADE_COOLDOWN_TIME);
-
- if (item.HitPoints <= 0)
+ if (CreatureActive(itemNumber))
{
+ auto* item = &g_Level.Items[itemNumber];
+ auto* creature = GetCreatureInfo(item);
- }
- else
- {
- CreatureAIInfo(&item, &ai);
+ int x = item->Pose.Position.x;
+ int y = item->Pose.Position.y;
+ int z = item->Pose.Position.z;
- // Manage light mesh and dynamic light based on state.
- if (IsJeepIdle(item.Animation.ActiveState))
+ int dx = 682 * phd_sin(item->Pose.Orientation.y);
+ int dz = 682 * phd_cos(item->Pose.Orientation.y);
+
+ int height1 = GetPointCollision(Vector3i(x - dz, y, z - dx), item->RoomNumber).GetFloorHeight();
+ if (abs(item->Pose.Position.y - height1) > CLICK(3))
{
- DrawEnemyJeepLightMesh(item, true);
- SpawnEnemyJeepBrakeLights(item);
+ item->Pose.Position.x += dz / 64;
+ item->Pose.Position.z += dx / 64;
+ item->Pose.Orientation.y += ANGLE(2.0f);
+ height1 = y;
+ }
+
+ int height2 = GetPointCollision(Vector3i(x + dz, y, z - dx), item->RoomNumber).GetFloorHeight();
+ if (abs(item->Pose.Position.y - height2) > CLICK(3))
+ {
+ item->Pose.Orientation.y -= ANGLE(2.0f);
+ item->Pose.Position.x -= dz / 64;
+ item->Pose.Position.z += dx / 64;
+ height2 = y;
+ }
+
+ short zRot = phd_atan(1364, height2 - height1);
+
+ int height3 = GetPointCollision(Vector3i(x + dx, y, z + dz), item->RoomNumber).GetFloorHeight();
+ if (abs(y - height3) > CLICK(3))
+ height3 = y;
+
+ int height4 = GetPointCollision(Vector3i(x - dx, y, z - dz), item->RoomNumber).GetFloorHeight();
+ if (abs(y - height4) > CLICK(3))
+ height4 = y;
+
+ short xRot = phd_atan(1364, height4 - height3);
+
+ AI_INFO AI;
+ CreatureAIInfo(item, &AI);
+
+ creature->Enemy = creature->AITarget;
+
+ auto* target = creature->AITarget;
+
+ dx = LaraItem->Pose.Position.x - item->Pose.Position.x;
+ dz = LaraItem->Pose.Position.z - item->Pose.Position.z;
+ short angle = phd_atan(dz, dx) - item->Pose.Orientation.y;
+
+ int distance;
+ if (dx > BLOCK(31.25f) || dx < -BLOCK(31.25f) ||
+ dz > BLOCK(31.25f) || dz < -BLOCK(31.25f))
+ {
+ distance = INT_MAX;
+ }
+ else
+ distance = pow(dx, 2) + pow(dz, 2);
+
+ auto pos = Vector3i::Zero;
+ switch (item->Animation.ActiveState)
+ {
+ case 0:
+ case 2:
+ item->ItemFlags[0] -= 128;
+ item->MeshBits = -98305;
+
+ pos = GetJointPosition(item, 11, Vector3i(0, -144, -1024));
+ TriggerDynamicLight(pos.x, pos.y, pos.z, 10, 64, 0, 0);
+
+ if (item->ItemFlags[0] < 0)
+ item->ItemFlags[0] = 0;
+
+ if (item->Animation.RequiredState != NO_VALUE)
+ item->Animation.TargetState = item->Animation.RequiredState;
+ else if (AI.distance > pow(BLOCK(1), 2) || Lara.Location >= item->ItemFlags[3])
+ item->Animation.TargetState = 1;
+
+ break;
+
+ case 1:
+ item->ItemFlags[0] += 37;
+ item->MeshBits = 0xFFFDBFFF;
+ creature->MaxTurn = item->ItemFlags[0] / 16;
+
+ if (item->ItemFlags[0] > 8704)
+ item->ItemFlags[0] = 8704;
+
+ if (AI.angle <= ANGLE(1.4f))
+ {
+ if (AI.angle < -ANGLE(1.4f))
+ item->Animation.TargetState = 3;
+ }
+ else
+ item->Animation.TargetState = 4;
+
+ break;
+
+ case 3:
+ case 4:
+ item->Animation.TargetState = 1;
+ item->ItemFlags[0] += 18;
+
+ if (item->ItemFlags[0] > 8704)
+ item->ItemFlags[0] = 8704;
+
+ break;
+
+ case 5:
+ if (item->ItemFlags[0] < 1184)
+ item->ItemFlags[0] = 1184;
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (height3 <= (item->Floor + CLICK(2)))
+ {
+ if (height4 > (item->Floor + CLICK(2)) && item->Animation.ActiveState != 5)
+ {
+ item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 8;
+ item->Animation.FrameNumber = GetAnimData(item).frameBase;
+ item->Animation.ActiveState = 5;
+ item->Animation.TargetState = 1;
+ item->ItemFlags[1] = 0;
+ }
}
else
{
- DrawEnemyJeepLightMesh(item, false);
- }
+ creature->LOT.RequiredBox |= 8;
- // This jump/drop check need to be there or else the jeep could miss a AI_X1
- // and potentially could break, anyway it's still weird
- // to see it going back to valid the missed AI.
- if (IsJeepMoving(item.Animation.ActiveState) ||
- IsJeepJumpingOrDropping(item.Animation.ActiveState))
- {
- DoNodePath(item);
- }
-
- if (IsJeepMoving(item.Animation.ActiveState))
- RotateTowardTarget(item, ai.angle, ANGLE(5.0f));
-
- switch (item.Animation.ActiveState)
- {
- case ENEMY_JEEP_STATE_IDLE:
- switch (item.ItemFlags[4])
+ if (item->ItemFlags[1] > 0)
{
- // Wait for player to enter a vehicle.
- default:
- case 0:
- if ((item.TriggerFlags & EJ_NO_PLAYER_VEHICLE_REQUIRED) != 0 ||
- Lara.Context.Vehicle != NO_VALUE)
- item.Animation.TargetState = ENEMY_JEEP_STATE_MOVE;
- break;
+ item->ItemFlags[1] -= 8;
+ item->Pose.Position.y += item->ItemFlags[1] / 64;
- // Wait until player is near.
- case 1:
- if (item.ItemFlags[5] != NO_VALUE &&
- Vector3i::Distance(LaraItem->Pose.Position, item.Pose.Position) <= item.ItemFlags[5])
+ if (item->ItemFlags[1] < 0)
+ creature->LOT.RequiredBox &= ~8;
+ }
+ else
+ {
+ item->ItemFlags[1] = 2 * xRot;
+ creature->LOT.RequiredBox |= 8u;
+ }
+
+ if (creature->LOT.RequiredBox & 8)
+ {
+ item->Animation.TargetState = 1;
+ creature->MaxTurn = 0;
+ }
+ }
+
+ if (AI.distance < pow(BLOCK(1.5f), 2) || item->ItemFlags[3] == -2)
+ creature->ReachedGoal = true;
+
+ if (creature->ReachedGoal)
+ {
+ TestTriggers(target, true);
+
+ if (Lara.Location < item->ItemFlags[3] && item->Animation.ActiveState != 2 && item->Animation.TargetState != 2)
+ {
+ item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 1;
+ item->Animation.FrameNumber = GetAnimData(item).frameBase;
+ item->Animation.TargetState = 2;
+ item->Animation.ActiveState = 2;
+
+ if (target->Flags & 4)
{
- item.Animation.TargetState = ENEMY_JEEP_STATE_MOVE;
- item.ItemFlags[4] = NO_VALUE; // Remove state.
- item.ItemFlags[5] = NO_VALUE; // Remove radius.
+ item->Pose = target->Pose;
+
+ if (item->RoomNumber != target->RoomNumber)
+ ItemNewRoom(itemNumber, target->RoomNumber);
+ }
+ }
+
+ if (distance > pow(BLOCK(2), 2) &&
+ distance < pow(BLOCK(10), 2) &&
+ !item->ItemFlags[2] &&
+ (angle < -ANGLE(112.5f) || angle > ANGLE(112.5f)))
+ {
+ EnemyJeepLaunchGrenade(item);
+ item->ItemFlags[2] = 150;
+ }
+
+ if (target->Flags == 62)
+ {
+ item->Status = ITEM_INVISIBLE;
+ RemoveActiveItem(itemNumber);
+ DisableEntityAI(itemNumber);
+ }
+
+ if (Lara.Location >= item->ItemFlags[3] || !(target->Flags & 4))
+ {
+ creature->ReachedGoal = false;
+ item->ItemFlags[3]++;
+
+ creature->Enemy = nullptr;
+ AI_OBJECT* aiObject = nullptr;
+
+ for (int i = 0; i < g_Level.AIObjects.size(); i++)
+ {
+ aiObject = &g_Level.AIObjects[i];
+
+ if (g_Level.AIObjects[i].triggerFlags == item->ItemFlags[3] && g_Level.AIObjects[i].roomNumber != NO_VALUE)
+ {
+ aiObject = &g_Level.AIObjects[i];
+ break;
+ }
}
- break;
+ if (aiObject != nullptr)
+ {
+ creature->Enemy = nullptr;
+ target->ObjectNumber = aiObject->objectNumber;
+ target->RoomNumber = aiObject->roomNumber;
+ target->Pose.Position = aiObject->pos.Position;
+ target->Pose.Orientation.y = aiObject->pos.Orientation.y;
+ target->Flags = aiObject->flags;
+ target->TriggerFlags = aiObject->triggerFlags;
+ target->BoxNumber = aiObject->boxNumber;
+
+ if (!(aiObject->flags & 0x20))
+ {
+ target->Pose.Position.x += CLICK(1) * phd_sin(target->Pose.Orientation.y);
+ target->Pose.Position.z += CLICK(1) * phd_cos(target->Pose.Orientation.y);
+ }
+ }
}
-
- break;
-
- case ENEMY_JEEP_STATE_MOVE:
- if (ai.angle < -ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM)
- {
- item.Animation.TargetState = ENEMY_JEEP_STATE_TURN_LEFT;
- }
- else if (ai.angle > ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM)
- {
- item.Animation.TargetState = ENEMY_JEEP_STATE_TURN_RIGHT;
- }
-
- break;
-
- case ENEMY_JEEP_STATE_TURN_LEFT:
- case ENEMY_JEEP_STATE_TURN_RIGHT:
- if (abs(ai.angle) <= ENEMY_JEEP_WHEEL_LEFTRIGHT_TURN_MINIMUM)
- item.Animation.TargetState = ENEMY_JEEP_STATE_MOVE;
-
- break;
}
- }
- ProcessBehaviour(item);
- CreatureAnimation(itemNumber, 0, 0);
+ item->ItemFlags[2]--;
+ if (item->ItemFlags[2] < 0)
+ item->ItemFlags[2] = 0;
- if (IsJeepJumpingOrDropping(item.Animation.ActiveState, true))
- {
- // Required, else the entity will go back to previous position (before the jump)
- creature.LOT.IsJumping = true;
- }
- else
- {
- creature.LOT.IsJumping = false;
- AlignEntityToSurface(&item, Vector2(object.radius / 3, (object.radius / 3) * 1.33f), 0.8f);
- }
+ if (abs(xRot - item->Pose.Orientation.x) < ANGLE(1.4f))
+ item->Pose.Orientation.x = xRot;
+ else if (xRot < item->Pose.Orientation.x)
+ item->Pose.Orientation.x -= ANGLE(1.4f);
+ else
+ item->Pose.Orientation.x += ANGLE(1.4f);
+
+ if (abs(zRot - item->Pose.Orientation.z) < ANGLE(1.4f))
+ item->Pose.Orientation.z = zRot;
+ else if (zRot < item->Pose.Orientation.z)
+ item->Pose.Orientation.z -= ANGLE(1.4f);
+ else
+ item->Pose.Orientation.z += ANGLE(1.4f);
+
+ item->ItemFlags[0] += -2 - xRot / 512;
+ if (item->ItemFlags[0] < 0)
+ item->ItemFlags[0] = 0;
+
+ dx = item->ItemFlags[0] * phd_sin(-2 - xRot / 512);
+ dz = item->ItemFlags[0] * phd_cos(-2 - xRot / 512);
+
+ item->Pose.Position.x += dx / 64;
+ item->Pose.Position.z += dz / 64;
- // Didn't use Move there because we need the move sound when jump/drop and rotating left/right.
- if (!IsJeepIdle(item.Animation.ActiveState))
- {
- float pitch = std::clamp(0.4f + (float)abs(item.Animation.Velocity.z) / (float)ENEMY_JEEP_PITCH_MAX, 0.6f, 1.4f);
for (int i = 0; i < 4; i++)
- creature.JointRotation[i] -= ANGLE(pitch * ENEMY_JEEP_PITCH_WHEEL_SPEED_MULTIPLIER);
+ creature->JointRotation[i] -= item->ItemFlags[0];
- SoundEffect(SFX_TR4_VEHICLE_JEEP_MOVING, &item.Pose, SoundEnvironment::Land, pitch, 1.5f);
- }
- else
- {
- for (int i = 0; i < 4; i++)
- creature.JointRotation[i] = 0;
+ if (!creature->ReachedGoal)
+ ClampRotation(item->Pose, AI.angle, item->ItemFlags[0] / 16);
- SoundEffect(SFX_TR4_VEHICLE_JEEP_IDLE, &item.Pose);
+ creature->MaxTurn = 0;
+ AnimateItem(item);
+
+ auto probe = GetPointCollision(*item);
+ item->Floor = probe.GetFloorHeight();
+ if (item->RoomNumber != probe.GetRoomNumber())
+ ItemNewRoom(itemNumber, probe.GetRoomNumber());
+
+ if (item->Pose.Position.y < item->Floor)
+ {
+ item->Animation.IsAirborne = true;
+ }
+ else
+ {
+ item->Pose.Position.y = item->Floor;
+ item->Animation.IsAirborne = false;
+ item->Animation.Velocity.y = 0;
+ }
+
+ SoundEffect(SFX_TR4_VEHICLE_JEEP_MOVING, &item->Pose, SoundEnvironment::Land, 1.0f + (float)item->ItemFlags[0] / BLOCK(8)); // TODO: Check actual sound!
}
}
}
diff --git a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h
index 53e7ad1ae..c2a2d0706 100644
--- a/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h
+++ b/TombEngine/Objects/TR4/Entity/tr4_enemy_jeep.h
@@ -1,9 +1,8 @@
#pragma once
-
#include "Game/items.h"
namespace TEN::Entities::TR4
{
void InitializeEnemyJeep(short itemNumber);
- void ControlEnemyJeep(short itemNumber);
+ void EnemyJeepControl(short itemNumber);
}
diff --git a/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp b/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp
index 3ed121e7b..aa9940f78 100644
--- a/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp
+++ b/TombEngine/Objects/TR4/Entity/tr4_knight_templar.cpp
@@ -194,7 +194,7 @@ namespace TEN::Entities::TR4
{
if (abs(pos.x - mesh.pos.Position.x) < BLOCK(1) &&
abs(pos.z - mesh.pos.Position.z) < BLOCK(1) &&
- StaticObjects[mesh.staticNumber].shatterType == ShatterType::None)
+ Statics[mesh.staticNumber].shatterType == ShatterType::None)
{
ShatterObject(nullptr, &mesh, -64, LaraItem->RoomNumber, 0);
SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose);
diff --git a/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp b/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp
index df8eaf7d8..9438ef83c 100644
--- a/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp
+++ b/TombEngine/Objects/TR4/Entity/tr4_skeleton.cpp
@@ -652,7 +652,7 @@ namespace TEN::Entities::TR4
if (abs(pos.x - staticMesh->pos.Position.x) < BLOCK(1) &&
abs(pos.z - staticMesh->pos.Position.z) < BLOCK(1) &&
- StaticObjects[staticMesh->staticNumber].shatterType != ShatterType::None)
+ Statics[staticMesh->staticNumber].shatterType != ShatterType::None)
{
ShatterObject(0, staticMesh, -128, LaraItem->RoomNumber, 0);
SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose);
diff --git a/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp b/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp
index e6d194981..3c3c37309 100644
--- a/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp
+++ b/TombEngine/Objects/TR4/Entity/tr4_sphinx.cpp
@@ -95,7 +95,7 @@ namespace TEN::Entities::TR4
if (((mesh->pos.Position.z / BLOCK(1)) == (z / BLOCK(1))) &&
((mesh->pos.Position.x / BLOCK(1)) == (x / BLOCK(1))) &&
- StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
+ Statics[mesh->staticNumber].shatterType != ShatterType::None)
{
ShatterObject(nullptr, mesh, -64, item->RoomNumber, 0);
SoundEffect(SFX_TR4_SMASH_ROCK, &item->Pose);
diff --git a/TombEngine/Objects/TR4/Trap/SpikyWall.cpp b/TombEngine/Objects/TR4/Trap/SpikyWall.cpp
index 225b5efe1..fc0a070cd 100644
--- a/TombEngine/Objects/TR4/Trap/SpikyWall.cpp
+++ b/TombEngine/Objects/TR4/Trap/SpikyWall.cpp
@@ -91,7 +91,7 @@ namespace TEN::Entities::Traps
abs(pointColl0.GetPosition().z - mesh.pos.Position.z) < BLOCK(1)) ||
abs(pointColl1.GetPosition().x - mesh.pos.Position.x) < BLOCK(1) &&
abs(pointColl1.GetPosition().z - mesh.pos.Position.z) < BLOCK(1) &&
- StaticObjects[mesh.staticNumber].shatterType != ShatterType::None)
+ Statics[mesh.staticNumber].shatterType != ShatterType::None)
{
if (mesh.HitPoints != 0)
continue;
diff --git a/TombEngine/Objects/TR4/tr4_objects.cpp b/TombEngine/Objects/TR4/tr4_objects.cpp
index 9ce44ab8d..f17c818de 100644
--- a/TombEngine/Objects/TR4/tr4_objects.cpp
+++ b/TombEngine/Objects/TR4/tr4_objects.cpp
@@ -11,7 +11,7 @@
#include "Specific/level.h"
// Creatures
-#include "Objects/TR4/Entity/Wraith.h"
+#include "Objects/TR4/Entity/Wraith.h" // OFF
#include "Objects/TR4/Entity/tr4_enemy_jeep.h"
#include "Objects/TR4/Entity/tr4_ahmet.h" // OK
#include "Objects/TR4/Entity/tr4_baddy.h" // OK
@@ -626,7 +626,7 @@ namespace TEN::Entities
if (obj->loaded)
{
obj->Initialize = InitializeEnemyJeep;
- obj->control = ControlEnemyJeep;
+ obj->control = EnemyJeepControl;
obj->collision = CreatureCollision;
obj->shadowType = ShadowMode::All;
obj->HitPoints = 40;
@@ -634,8 +634,8 @@ namespace TEN::Entities
obj->radius = 512;
obj->intelligent = true;
obj->damageType = DamageMode::None; // NOTE: Prevents enemy jeep from being killed with skidoo gun or something like that.
- obj->LotType = LotType::EnemyJeep;
- obj->SetBoneRotationFlags(8, ROT_X); // Wheel rotation.
+ obj->LotType = LotType::HumanPlusJumpAndMonkey;
+ obj->SetBoneRotationFlags(8, ROT_X);
obj->SetBoneRotationFlags(9, ROT_X);
obj->SetBoneRotationFlags(11, ROT_X);
obj->SetBoneRotationFlags(12, ROT_X);
diff --git a/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp b/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp
index 5a432a1e7..2b54f9584 100644
--- a/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp
+++ b/TombEngine/Objects/TR5/Emitter/tr5_smoke_emitter.cpp
@@ -212,7 +212,7 @@ namespace TEN::Effects::SmokeEmitter
part.friction = 3;
part.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
- if (TestEnvironment(RoomEnvFlags::ENV_FLAG_OUTSIDE, item.RoomNumber))
+ if (TestEnvironment(RoomEnvFlags::ENV_FLAG_SKYBOX, item.RoomNumber))
part.flags |= SP_WIND;
part.rotAng = Random::GenerateInt(0, 4095);
diff --git a/TombEngine/Objects/TR5/Entity/AutoGun.cpp b/TombEngine/Objects/TR5/Entity/AutoGun.cpp
index 7379eeb67..32a7d6ebc 100644
--- a/TombEngine/Objects/TR5/Entity/AutoGun.cpp
+++ b/TombEngine/Objects/TR5/Entity/AutoGun.cpp
@@ -168,7 +168,7 @@ namespace TEN::Entities::Creatures::TR5
pos.z += dz + GetRandomControl() - 128;
if (!LOS(&origin, &pos))
- TriggerRicochetSpark(pos, Random::GenerateAngle(), 3, 0);
+ TriggerRicochetSpark(pos, Random::GenerateAngle());
}
}
else
diff --git a/TombEngine/Objects/TR5/Entity/HeavyGuard.cpp b/TombEngine/Objects/TR5/Entity/HeavyGuard.cpp
index 57b954bbb..3c35dc53a 100644
--- a/TombEngine/Objects/TR5/Entity/HeavyGuard.cpp
+++ b/TombEngine/Objects/TR5/Entity/HeavyGuard.cpp
@@ -532,7 +532,7 @@ namespace TEN::Entities::Creatures::TR5
else
{
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose);
- TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(*pos, source.Pose.Orientation.y, false);
}
}
}
diff --git a/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp b/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp
index cd8759512..28c7b46b1 100644
--- a/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp
+++ b/TombEngine/Objects/TR5/Entity/tr5_gladiator.cpp
@@ -348,7 +348,7 @@ namespace TEN::Entities::Creatures::TR5
{
if (!((pos.x ^ mesh->pos.Position.x) & 0xFFFFFC00))
{
- if (StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
+ if (Statics[mesh->staticNumber].shatterType != ShatterType::None)
{
ShatterObject(0, mesh, -64, LaraItem->RoomNumber, 0);
//SoundEffect(ShatterSounds[gfCurrentLevel - 5][*(v28 + 18)], v28);
diff --git a/TombEngine/Objects/TR5/Entity/tr5_gunship.cpp b/TombEngine/Objects/TR5/Entity/tr5_gunship.cpp
index 38f4c2601..193d21cde 100644
--- a/TombEngine/Objects/TR5/Entity/tr5_gunship.cpp
+++ b/TombEngine/Objects/TR5/Entity/tr5_gunship.cpp
@@ -104,21 +104,19 @@ namespace TEN::Entities::Creatures::TR5
if (!los2)
{
- TriggerRicochetSpark(target, 2 * GetRandomControl(), 3, 0);
- TriggerRicochetSpark(target, 2 * GetRandomControl(), 3, 0);
+ TriggerRicochetSpark(target, 2 * GetRandomControl());
}
if (objOnLos < 0 && GetRandomControl() & 1)
{
- if (StaticObjects[hitMesh->staticNumber].shatterType != ShatterType::None)
+ if (Statics[hitMesh->staticNumber].shatterType != ShatterType::None)
{
ShatterObject(0, hitMesh, 64, target.RoomNumber, 0);
TestTriggers(hitMesh->pos.Position.x, hitMesh->pos.Position.y, hitMesh->pos.Position.z, target.RoomNumber, true);
SoundEffect(GetShatterSound(hitMesh->staticNumber), &hitMesh->pos);
}
- TriggerRicochetSpark(GameVector(hitPos), 2 * GetRandomControl(), 3, 0);
- TriggerRicochetSpark(GameVector(hitPos), 2 * GetRandomControl(), 3, 0);
+ TriggerRicochetSpark(GameVector(hitPos), 2 * GetRandomControl());
}
}
else
diff --git a/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp b/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp
index bdb8dbcfc..397276556 100644
--- a/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp
+++ b/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp
@@ -558,7 +558,7 @@ namespace TEN::Entities::Creatures::TR5
if (!((mesh->pos.Position.z ^ pos.z) & 0xFFFFFC00) && !((mesh->pos.Position.x ^ pos.x) & 0xFFFFFC00))
{
- if (StaticObjects[mesh->staticNumber].shatterType != ShatterType::None)
+ if (Statics[mesh->staticNumber].shatterType != ShatterType::None)
{
ShatterObject(0, mesh, -64, LaraItem->RoomNumber, 0);
SoundEffect(GetShatterSound(mesh->staticNumber), (Pose*)mesh);
@@ -882,7 +882,7 @@ namespace TEN::Entities::Creatures::TR5
if (object.hitEffect == HitEffect::Richochet && pos.has_value())
{
- TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(*pos, source.Pose.Orientation.y, false);
SoundEffect(SFX_TR5_SWORD_GOD_HIT_METAL, &target.Pose);
}
diff --git a/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp b/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp
index 99bfeee01..2972b8d5a 100644
--- a/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp
+++ b/TombEngine/Objects/TR5/Object/tr5_twoblockplatform.cpp
@@ -92,7 +92,7 @@ namespace TEN::Entities::Generic
if (probe.GetRoomNumber() != item->RoomNumber)
{
- UpdateBridgeItem(*item, true);
+ UpdateBridgeItem(*item, BridgeUpdateType::Remove);
ItemNewRoom(itemNumber, probe.GetRoomNumber());
UpdateBridgeItem(*item);
}
diff --git a/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp b/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp
index b80b0de18..09bf564f0 100644
--- a/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp
+++ b/TombEngine/Objects/TR5/Trap/tr5_explosion.cpp
@@ -163,7 +163,7 @@ namespace TEN::Entities::Traps
for (auto* staticPtr : collObjects.Statics)
{
- if (StaticObjects[staticPtr->staticNumber].shatterType != ShatterType::None)
+ if (Statics[staticPtr->staticNumber].shatterType != ShatterType::None)
{
TriggerExplosionSparks(staticPtr->pos.Position.x, staticPtr->pos.Position.y, staticPtr->pos.Position.z, 3, -2, 0, item.RoomNumber);
staticPtr->pos.Position.y -= 128;
diff --git a/TombEngine/Renderer/Renderer.cpp b/TombEngine/Renderer/Renderer.cpp
index e251bc09e..bb54ef41e 100644
--- a/TombEngine/Renderer/Renderer.cpp
+++ b/TombEngine/Renderer/Renderer.cpp
@@ -34,8 +34,10 @@ namespace TEN::Renderer
{
_shadowLight = nullptr;
+ _items.resize(0);
+ _effects.resize(0);
_moveableObjects.resize(0);
- _staticObjects.resize(0);
+ _staticObjects.clear();
_sprites.resize(0);
_rooms.resize(0);
_roomTextures.resize(0);
@@ -48,15 +50,6 @@ namespace TEN::Renderer
for (auto& mesh : _meshes)
delete mesh;
_meshes.resize(0);
-
- for (auto& item : _items)
- {
- item.DisableInterpolation = true;
- item.PrevRoomNumber = NO_VALUE;
- item.RoomNumber = NO_VALUE;
- item.ItemNumber = NO_VALUE;
- item.LightsToDraw.clear();
- }
}
void Renderer::Lock()
diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h
index ae794fc92..02a5f9a2f 100644
--- a/TombEngine/Renderer/Renderer.h
+++ b/TombEngine/Renderer/Renderer.h
@@ -6,6 +6,7 @@
#include
#include
#include
+
#include "Math/Math.h"
#include "Game/control/box.h"
#include "Game/items.h"
@@ -15,11 +16,13 @@
#include "Game/Hud/PickupSummary.h"
#include "Game/effects/effects.h"
#include "Game/effects/Electricity.h"
+#include "Game/Setup.h"
#include "Specific/level.h"
#include "Specific/fast_vector.h"
+#include "Renderer/Frustum.h"
#include "Renderer/RendererEnums.h"
-#include "Renderer/Structures/RendererLight.h"
#include "Renderer/RenderView.h"
+#include "Renderer/Structures/RendererLight.h"
#include "Renderer/ConstantBuffers/StaticBuffer.h"
#include "Renderer/ConstantBuffers/LightBuffer.h"
#include "Renderer/ConstantBuffers/HUDBarBuffer.h"
@@ -33,26 +36,23 @@
#include "Renderer/ConstantBuffers/SpriteBuffer.h"
#include "Renderer/ConstantBuffers/InstancedStaticBuffer.h"
#include "Renderer/ConstantBuffers/InstancedSpriteBuffer.h"
-#include "Frustum.h"
-#include "Specific/level.h"
-#include "ConstantBuffers/ConstantBuffer.h"
-#include "Specific/fast_vector.h"
-#include "Renderer/ConstantBuffers/InstancedSpriteBuffer.h"
+#include "Renderer/ConstantBuffers/ConstantBuffer.h"
#include "Renderer/ConstantBuffers/PostProcessBuffer.h"
+#include "Renderer/ConstantBuffers/SMAABuffer.h"
#include "Renderer/Structures/RendererBone.h"
+#include "Renderer/Structures/RendererDoor.h"
#include "Renderer/Structures/RendererStringToDraw.h"
#include "Renderer/Structures/RendererRoom.h"
#include "Renderer/Structures/RendererSprite.h"
-#include "Renderer/Graphics/VertexBuffer.h"
-#include "Renderer/Structures/RendererDoor.h"
-#include "Structures/RendererAnimatedTexture.h"
-#include "Structures/RendererAnimatedTextureSet.h"
-#include "Structures/RendererRoom.h"
+#include "Renderer/Structures/RendererAnimatedTexture.h"
+#include "Renderer/Structures/RendererAnimatedTextureSet.h"
#include "Renderer/Graphics/Texture2D.h"
#include "Renderer/Graphics/IndexBuffer.h"
#include "Renderer/Graphics/RenderTarget2D.h"
#include "Renderer/Graphics/RenderTargetCube.h"
#include "Renderer/Graphics/Texture2DArray.h"
+#include "Renderer/Graphics/VertexBuffer.h"
+#include "Renderer/Graphics/Vertices/PostProcessVertex.h"
#include "Renderer/Structures/RendererItem.h"
#include "Renderer/Structures/RendererEffect.h"
#include "Renderer/Structures/RendererLine3D.h"
@@ -63,9 +63,7 @@
#include "Renderer/Structures/RendererLine2D.h"
#include "Renderer/Structures/RendererHudBar.h"
#include "Renderer/Structures/RendererRoomAmbientMap.h"
-#include "Renderer/ConstantBuffers/SMAABuffer.h"
#include "Renderer/Structures/RendererObject.h"
-#include "Graphics/Vertices/PostProcessVertex.h"
#include "Renderer/Structures/RendererStar.h"
enum GAME_OBJECT_ID : short;
@@ -90,6 +88,7 @@ namespace TEN::Renderer
{
private:
// Core DX11 objects
+
ComPtr _device = nullptr;
ComPtr _context = nullptr;
ComPtr _swapChain = nullptr;
@@ -109,6 +108,7 @@ namespace TEN::Renderer
Viewport _viewportToolkit;
// Render targets
+
RenderTarget2D _normalsRenderTarget;
RenderTarget2D _depthRenderTarget;
RenderTarget2D _backBuffer;
@@ -123,6 +123,7 @@ namespace TEN::Renderer
Texture2DArray _shadowMap;
// Shaders
+
ComPtr _vsRooms;
ComPtr _vsRoomsAnimatedTextures;
ComPtr _psRooms;
@@ -162,6 +163,7 @@ namespace TEN::Renderer
ComPtr _psRoomAmbient;
// Constant buffers
+
RenderView _gameCamera;
RenderView _oldGameCamera;
RenderView _currentGameCamera;
@@ -196,17 +198,20 @@ namespace TEN::Renderer
ConstantBuffer _cbSMAABuffer;
// Primitive batches
+
std::unique_ptr _spriteBatch;
std::unique_ptr> _primitiveBatch;
// Text
+
std::unique_ptr _gameFont;
std::vector _stringsToDraw;
- float _blinkColorValue = 0.0f;
- float _blinkTime = 0.0f;
+ Vector4 _blinkColorValue = Vector4::Zero;
+ float _blinkTime = 0.0f;
float _oldBlinkTime = 0.0f;
// Graphics resources
+
Texture2D _logo;
Texture2D _skyTexture;
Texture2D _whiteTexture;
@@ -233,40 +238,47 @@ namespace TEN::Renderer
std::vector _staticsIndices;
// Rooms and collector
+
std::vector _rooms;
bool _invalidateCache;
std::vector _visitedRoomsStack;
// Lights
+
std::vector _dynamicLights;
RendererLight* _shadowLight;
// Lines
+
std::vector _lines2DToDraw = {};
std::vector _lines3DToDraw = {};
std::vector _triangles3DToDraw = {};
// Textures, objects and sprites
- std::vector> _moveableObjects;
- std::vector> _staticObjects;
- std::vector _sprites;
- std::vector _spriteSequences;
- Matrix _laraWorldMatrix;
- std::vector _animatedTextureSets;
- std::vector _meshes;
- std::vector _roomTextures;
- std::vector _animatedTextures;
- std::vector _moveablesTextures;
- std::vector _staticTextures;
- std::vector _spritesTextures;
- // Preallocated pools of objects for avoiding new/delete
+ std::vector> _moveableObjects;
+ std::vector> _staticObjects; // Key = static ID, value = renderer object.
+ std::vector _sprites;
+ std::vector _spriteSequences;
+ std::vector _animatedTextureSets;
+ std::vector _meshes;
+ std::vector _roomTextures;
+ std::vector _animatedTextures;
+ std::vector _moveablesTextures;
+ std::vector _staticTextures;
+ std::vector _spritesTextures;
+
+ Matrix _laraWorldMatrix;
+
+ // Preallocated pools of objects for avoiding new/delete.
// Items and effects are safe (can't be more than 1024 items in TR),
// lights should be oversized (eventually ignore lights more than MAX_LIGHTS)
- RendererItem _items[ITEM_COUNT_MAX];
- RendererEffect _effects[ITEM_COUNT_MAX];
+
+ std::vector _items;
+ std::vector _effects;
// Debug variables
+
int _numDrawCalls = 0;
int _numRoomsDrawCalls = 0;
@@ -298,6 +310,7 @@ namespace TEN::Renderer
RendererDebugPage _debugPage = RendererDebugPage::None;
// Times for debug
+
int _timeUpdate;
int _timeRoomsCollector;
int _timeDraw;
@@ -306,6 +319,7 @@ namespace TEN::Renderer
int _currentCausticsFrame;
// Screen settings
+
int _screenWidth;
int _screenHeight;
int _refreshRate;
@@ -313,9 +327,11 @@ namespace TEN::Renderer
float _farView = DEFAULT_FAR_VIEW;
// A flag to prevent extra renderer object additions
+
bool _isLocked = false;
// Caching state changes
+
TextureBase* _lastTexture;
BlendMode _lastBlendMode;
DepthState _lastDepthState;
@@ -326,6 +342,7 @@ namespace TEN::Renderer
ComPtr _shadowSampler;
// Antialiasing
+
Texture2D _SMAAAreaTexture;
Texture2D _SMAASearchTexture;
RenderTarget2D _SMAASceneRenderTarget;
@@ -346,7 +363,8 @@ namespace TEN::Renderer
ComPtr _vsFXAA;
ComPtr _psFXAA;
- // Post process
+ // Post-process
+
PostProcessMode _postProcessMode = PostProcessMode::None;
float _postProcessStrength = 1.0f;
Vector3 _postProcessTint = Vector3::One;
@@ -364,6 +382,7 @@ namespace TEN::Renderer
bool _doingFullscreenPass = false;
// SSAO
+
ComPtr _vsSSAO;
ComPtr _psSSAO;
ComPtr _psSSAOBlur;
@@ -372,21 +391,29 @@ namespace TEN::Renderer
RenderTarget2D _SSAOBlurredRenderTarget;
std::vector _SSAOKernel;
+ // New ambient light techinque
+ RenderTarget2D _roomAmbientMapFront;
+ RenderTarget2D _roomAmbientMapBack;
+
// Special effects
+
std::vector _causticTextures;
// Transparency
+
fast_vector _sortedPolygonsVertices;
fast_vector _sortedPolygonsIndices;
VertexBuffer _sortedPolygonsVertexBuffer;
IndexBuffer _sortedPolygonsIndexBuffer;
// High framerate.
+
float _interpolationFactor = 0.0f;
bool _graphicsSettingsChanged = false;
// Private functions
+
void ApplySMAA(RenderTarget2D* renderTarget, RenderView& view);
void ApplyFXAA(RenderTarget2D* renderTarget, RenderView& view);
void BindTexture(TextureRegister registerType, TextureBase* texture, SamplerStateRegister samplerType);
@@ -487,7 +514,7 @@ namespace TEN::Renderer
void PrepareStreamers(RenderView& view);
void PrepareFootprints(RenderView& view);
void DrawLoadingBar(float percent);
- void DrawPostprocess(RenderTarget2D* renderTarget, RenderView& view);
+ void DrawPostprocess(RenderTarget2D* renderTarget, RenderView& view, SceneRenderMode renderMode);
void RenderInventoryScene(RenderTarget2D* renderTarget, TextureBase* background, float backgroundFade);
void RenderTitleMenu(Menu menu);
void RenderPauseMenu(Menu menu);
@@ -585,6 +612,11 @@ namespace TEN::Renderer
blendMode == BlendMode::FastAlphaBlend);
}
+ inline RendererObject& GetStaticRendererObject(short objectNumber)
+ {
+ return _staticObjects[Statics.GetIndex(objectNumber)].value();
+ }
+
public:
Renderer();
~Renderer();
@@ -599,9 +631,9 @@ namespace TEN::Renderer
bool PrepareDataForTheRenderer();
void UpdateCameraMatrices(CAMERA_INFO* cam, float farView);
void RenderSimpleSceneToParaboloid(RenderTarget2D* renderTarget, Vector3 position, int emisphere);
- void DumpGameScene();
+ void DumpGameScene(SceneRenderMode renderMode = SceneRenderMode::Full);
void RenderInventory();
- void RenderScene(RenderTarget2D* renderTarget, bool doAntialiasing, RenderView& view);
+ void RenderScene(RenderTarget2D* renderTarget, RenderView& view, SceneRenderMode renderMode = SceneRenderMode::Full);
void PrepareScene();
void ClearScene();
void SaveScreenshot();
@@ -613,6 +645,7 @@ namespace TEN::Renderer
void FreeRendererData();
void AddDynamicLight(int x, int y, int z, short falloff, byte r, byte g, byte b);
void RenderLoadingScreen(float percentage);
+ void RenderFreezeMode(float interpFactor, bool staticBackground);
void UpdateProgress(float value);
void ToggleFullScreen(bool force = false);
void SetFullScreen();
@@ -652,7 +685,7 @@ namespace TEN::Renderer
void SaveOldState();
float GetFramerateMultiplier() const;
- float GetInterpolationFactor() const;
+ float GetInterpolationFactor(bool forceRawValue = false) const;
Vector2i GetScreenResolution() const;
int GetScreenRefreshRate() const;
std::optional Get2DPosition(const Vector3& pos) const;
diff --git a/TombEngine/Renderer/RendererCompatibility.cpp b/TombEngine/Renderer/RendererCompatibility.cpp
index 2bf5f89eb..5f5be02ef 100644
--- a/TombEngine/Renderer/RendererCompatibility.cpp
+++ b/TombEngine/Renderer/RendererCompatibility.cpp
@@ -22,17 +22,26 @@ namespace TEN::Renderer
bool Renderer::PrepareDataForTheRenderer()
{
+ TENLog("Preparing renderer...", LogLevel::Info);
+
_lastBlendMode = BlendMode::Unknown;
_lastCullMode = CullMode::Unknown;
_lastDepthState = DepthState::Unknown;
_moveableObjects.resize(ID_NUMBER_OBJECTS);
_spriteSequences.resize(ID_NUMBER_OBJECTS);
- _staticObjects.resize(MAX_STATICS);
_rooms.resize(g_Level.Rooms.size());
_meshes.clear();
+ int allocatedItemSize = (int)g_Level.Items.size() + MAX_SPAWNED_ITEM_COUNT;
+
+ auto item = RendererItem();
+ _items = std::vector(allocatedItemSize, item);
+
+ auto effect = RendererEffect();
+ _effects = std::vector(allocatedItemSize, effect);
+
TENLog("Allocated renderer object memory.", LogLevel::Info);
_animatedTextures.resize(g_Level.AnimatedTextures.size());
@@ -263,7 +272,7 @@ namespace TEN::Renderer
staticInfo->AmbientLight = r->AmbientLight;
staticInfo->Pose = oldMesh->pos;
staticInfo->Scale = oldMesh->scale;
- staticInfo->OriginalVisibilityBox = StaticObjects[staticInfo->ObjectNumber].visibilityBox;
+ staticInfo->OriginalSphere = Statics[staticInfo->ObjectNumber].visibilityBox.ToLocalBoundingSphere();
staticInfo->IndexInRoom = l;
staticInfo->Update();
@@ -868,16 +877,13 @@ namespace TEN::Renderer
totalVertices = 0;
totalIndices = 0;
- for (int i = 0; i < StaticObjectsIds.size(); i++)
+ for (const auto& staticObj : Statics)
{
- int objNum = StaticObjectsIds[i];
- StaticInfo* obj = &StaticObjects[objNum];
- MESH* mesh = &g_Level.Meshes[obj->meshNumber];
-
- for (auto& bucket : mesh->buckets)
+ const auto& mesh = g_Level.Meshes[staticObj.meshNumber];
+ for (const auto& bucket : mesh.buckets)
{
- totalVertices += bucket.numQuads * 4 + bucket.numTriangles * 3;
- totalIndices += bucket.numQuads * 6 + bucket.numTriangles * 3;
+ totalVertices += (bucket.numQuads * 4) + (bucket.numTriangles * 3);
+ totalIndices += (bucket.numQuads * 6) + (bucket.numTriangles * 3);
}
}
@@ -886,20 +892,18 @@ namespace TEN::Renderer
lastVertex = 0;
lastIndex = 0;
- for (int i = 0; i < StaticObjectsIds.size(); i++)
+ for (const auto& staticObj : Statics)
{
- StaticInfo*obj = &StaticObjects[StaticObjectsIds[i]];
- _staticObjects[StaticObjectsIds[i]] = RendererObject();
- RendererObject &staticObject = *_staticObjects[StaticObjectsIds[i]];
- staticObject.Type = 1;
- staticObject.Id = StaticObjectsIds[i];
+ auto newStaticObj = RendererObject();
+ newStaticObj.Type = 1;
+ newStaticObj.Id = staticObj.ObjectNumber;
- RendererMesh *mesh = GetRendererMeshFromTrMesh(&staticObject, &g_Level.Meshes[obj->meshNumber], 0, false, false, &lastVertex, &lastIndex);
+ auto& mesh = *GetRendererMeshFromTrMesh(&newStaticObj, &g_Level.Meshes[staticObj.meshNumber], 0, false, false, &lastVertex, &lastIndex);
- staticObject.ObjectMeshes.push_back(mesh);
- _meshes.push_back(mesh);
+ newStaticObj.ObjectMeshes.push_back(&mesh);
+ _meshes.push_back(&mesh);
- _staticObjects[StaticObjectsIds[i]] = staticObject;
+ _staticObjects.push_back(newStaticObj);
}
_staticsVertexBuffer = VertexBuffer(_device.Get(), (int)_staticsVertices.size(), _staticsVertices.data());
diff --git a/TombEngine/Renderer/RendererDraw.cpp b/TombEngine/Renderer/RendererDraw.cpp
index f0e73c32a..019b44ab3 100644
--- a/TombEngine/Renderer/RendererDraw.cpp
+++ b/TombEngine/Renderer/RendererDraw.cpp
@@ -280,7 +280,7 @@ namespace TEN::Renderer
auto prevRotMatrix = gunshell->oldPos.Orientation.ToRotationMatrix();
auto prevWorldMatrix = prevRotMatrix * prevTranslation;
- worldMatrix = Matrix::Lerp(prevWorldMatrix, worldMatrix, _interpolationFactor);
+ worldMatrix = Matrix::Lerp(prevWorldMatrix, worldMatrix, GetInterpolationFactor());
_stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].World = worldMatrix;
_stInstancedStaticMeshBuffer.StaticMeshes[gunShellCount].Ambient = room.AmbientLight;
@@ -346,7 +346,7 @@ namespace TEN::Renderer
relPos = Vector3(segment->x >> FP_SHIFT, segment->y >> FP_SHIFT, segment->z >> FP_SHIFT);
auto currentOutput = Vector3::Transform(relPos, translationMatrix);
- auto absolutePos = Vector3::Lerp(prevOutput, currentOutput, _interpolationFactor);
+ auto absolutePos = Vector3::Lerp(prevOutput, currentOutput, GetInterpolationFactor());
absolutePoints[i] = absolutePos;
}
@@ -628,7 +628,7 @@ namespace TEN::Renderer
for (auto& poly : bucket.Polygons)
{
- auto worldMatrix = Matrix::Lerp(fish.PrevTransform, fish.Transform, _interpolationFactor);
+ auto worldMatrix = Matrix::Lerp(fish.PrevTransform, fish.Transform, GetInterpolationFactor());
auto center = Vector3::Transform(poly.Centre, worldMatrix);
float dist = Vector3::Distance(center, view.Camera.WorldPosition);
@@ -689,7 +689,7 @@ namespace TEN::Renderer
const auto& mesh = *GetMesh(Objects[ID_FISH_EMITTER].meshIndex + fish.MeshIndex);
- _stStatic.World = Matrix::Lerp(fish.PrevTransform, fish.Transform, _interpolationFactor);
+ _stStatic.World = Matrix::Lerp(fish.PrevTransform, fish.Transform, GetInterpolationFactor());
_stStatic.Color = Vector4::One;
_stStatic.AmbientLight = _rooms[fish.RoomNumber].AmbientLight;
@@ -743,7 +743,7 @@ namespace TEN::Renderer
for (int p = 0; p < bucket.Polygons.size(); p++)
{
- auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, _interpolationFactor);
+ auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, GetInterpolationFactor());
auto centre = Vector3::Transform(bucket.Polygons[p].Centre, transformMatrix);
float dist = (centre - view.Camera.WorldPosition).Length();
@@ -773,7 +773,7 @@ namespace TEN::Renderer
{
auto& room = _rooms[bat.RoomNumber];
- auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, _interpolationFactor);
+ auto transformMatrix = Matrix::Lerp(bat.PrevTransform, bat.Transform, GetInterpolationFactor());
_stInstancedStaticMeshBuffer.StaticMeshes[batCount].World = transformMatrix;
_stInstancedStaticMeshBuffer.StaticMeshes[batCount].Ambient = room.AmbientLight;
@@ -848,7 +848,7 @@ namespace TEN::Renderer
if (!beetle.On)
continue;
- auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, _interpolationFactor);
+ auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, GetInterpolationFactor());
for (auto& bucket : mesh.Buckets)
{
@@ -886,7 +886,7 @@ namespace TEN::Renderer
{
auto& room = _rooms[beetle.RoomNumber];
- auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, _interpolationFactor);
+ auto transformMatrix = Matrix::Lerp(beetle.PrevTransform, beetle.Transform, GetInterpolationFactor());
_stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].World = transformMatrix;
_stInstancedStaticMeshBuffer.StaticMeshes[beetleCount].Ambient = room.AmbientLight;
@@ -977,7 +977,7 @@ namespace TEN::Renderer
for (int p = 0; p < bucket.Polygons.size(); p++)
{
- auto transformMatrix = Matrix::Lerp(locust.PrevTransform, locust.Transform, _interpolationFactor);
+ auto transformMatrix = Matrix::Lerp(locust.PrevTransform, locust.Transform, GetInterpolationFactor());
auto centre = Vector3::Transform(bucket.Polygons[p].Centre, transformMatrix);
float dist = (centre - view.Camera.WorldPosition).Length();
@@ -1039,7 +1039,7 @@ namespace TEN::Renderer
auto& mesh = *GetMesh(Objects[ID_LOCUSTS].meshIndex + (-locust.counter & 3));
- _stStatic.World = Matrix::Lerp(locust.PrevTransform, locust.Transform, _interpolationFactor);
+ _stStatic.World = Matrix::Lerp(locust.PrevTransform, locust.Transform, GetInterpolationFactor());
_stStatic.Color = Vector4::One;
_stStatic.AmbientLight = _rooms[locust.roomNumber].AmbientLight;
_cbStatic.UpdateData(_stStatic, _context.Get());
@@ -1538,7 +1538,7 @@ namespace TEN::Renderer
void Renderer::AddDynamicLight(int x, int y, int z, short falloff, byte r, byte g, byte b)
{
- if (_isLocked)
+ if (_isLocked || g_GameFlow->LastFreezeMode != FreezeMode::None)
return;
RendererLight dynamicLight = {};
@@ -1569,7 +1569,9 @@ namespace TEN::Renderer
void Renderer::PrepareScene()
{
- _dynamicLights.clear();
+ if (g_GameFlow->CurrentFreezeMode == FreezeMode::None)
+ _dynamicLights.clear();
+
_lines2DToDraw.clear();
_lines3DToDraw.clear();
_triangles3DToDraw.clear();
@@ -1583,7 +1585,8 @@ namespace TEN::Renderer
constexpr auto BLINK_TIME_STEP = 0.2f;
// Calculate blink increment based on sine wave.
- _blinkColorValue = ((sin(_blinkTime) + BLINK_VALUE_MAX) * 0.5f) + BLINK_VALUE_MIN;
+ float blink = ((sin(_blinkTime) + BLINK_VALUE_MAX) * 0.5f) + BLINK_VALUE_MIN;
+ _blinkColorValue = Vector4(blink, blink, blink, 1.0f);
// Update blink time.
_blinkTime += BLINK_TIME_STEP;
@@ -1604,7 +1607,7 @@ namespace TEN::Renderer
ClearShadowMap();
}
- void Renderer::RenderScene(RenderTarget2D* renderTarget, bool doAntialiasing, RenderView& view)
+ void Renderer::RenderScene(RenderTarget2D* renderTarget, RenderView& view, SceneRenderMode renderMode)
{
using ns = std::chrono::nanoseconds;
using get_time = std::chrono::steady_clock;
@@ -1697,8 +1700,8 @@ namespace TEN::Renderer
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// TODO: Needs improvements before enabling it.
- //RenderSimpleSceneToParaboloid(&_roomAmbientMapsCache[ambientMapCacheIndex].Front, LaraItem->Pose.Position.ToVector3(), 1);
- //RenderSimpleSceneToParaboloid(&_roomAmbientMapsCache[ambientMapCacheIndex].Back, LaraItem->Pose.Position.ToVector3(), -1);
+ // RenderSimpleSceneToParaboloid(&_roomAmbientMapFront, LaraItem->Pose.Position.ToVector3(), 1);
+ // RenderSimpleSceneToParaboloid(&_roomAmbientMapBack, LaraItem->Pose.Position.ToVector3(), -1);
// Bind and clear render target.
_context->ClearRenderTargetView(_renderTarget.RenderTargetView.Get(), _debugPage == RendererDebugPage::WireframeMode ? Colors::DimGray : Colors::Black);
@@ -1849,13 +1852,16 @@ namespace TEN::Renderer
ClearDrawPhaseDisplaySprites();
_context->ClearDepthStencilView(_renderTarget.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
- g_Hud.Draw(*LaraItem);
-
- _doingFullscreenPass = true;
- // Apply antialiasing.
- if (doAntialiasing)
+ // HUD must be drawn before post-processing to be antialiased.
+ if (renderMode == SceneRenderMode::Full)
+ g_Hud.Draw(*LaraItem);
+
+ if (renderMode != SceneRenderMode::NoPostprocess)
{
+ _doingFullscreenPass = true;
+
+ // Apply antialiasing.
switch (g_Configuration.AntialiasingMode)
{
case AntialiasingMode::None:
@@ -1870,30 +1876,33 @@ namespace TEN::Renderer
ApplySMAA(&_renderTarget, view);
break;
}
+
+ // Draw post-process effects (cinematic bars, fade, flash, HDR, tone mapping, etc.).
+ DrawPostprocess(renderTarget, view, renderMode);
+
+ _doingFullscreenPass = false;
+
+ // Draw binoculars or lasersight overlay.
+ DrawOverlays(view);
+
+ // Draw 2D debug lines.
+ DrawLines2D();
}
- // Draw post-process effects (cinematic bars, fade, flash, HDR, tone mapping, etc.).
- DrawPostprocess(renderTarget, view);
+ if (renderMode == SceneRenderMode::Full)
+ {
+ // Draw display sprites sorted by priority.
+ CollectDisplaySprites(view);
+ DrawDisplaySprites(view);
- _doingFullscreenPass = false;
-
- // Draw 2D debug lines.
- DrawLines2D();
-
- // Draw display sprites sorted by priority.
- CollectDisplaySprites(view);
- DrawDisplaySprites(view);
-
- // Draw binoculars or lasersight overlay.
- DrawOverlays(view);
+ DrawDebugInfo(view);
+ DrawAllStrings();
+ }
time2 = std::chrono::high_resolution_clock::now();
_timeFrame = (std::chrono::duration_cast(time2 - time1)).count() / 1000000;
time1 = time2;
- DrawDebugInfo(view);
- DrawAllStrings();
-
ClearScene();
CalculateFrameRate();
}
@@ -2056,7 +2065,7 @@ namespace TEN::Renderer
// final pixel colors
if (Vector3::Distance(room->BoundingBox.Center, LaraItem->Pose.Position.ToVector3()) >= BLOCK(40))
{
- continue;
+ //continue;
}
cameraConstantBuffer.CameraUnderwater = g_Level.Rooms[_rooms[i].RoomNumber].flags & ENV_FLAG_WATER;
@@ -2152,11 +2161,9 @@ namespace TEN::Renderer
SetBlendMode(BlendMode::Opaque, true);*/
}
- void Renderer::DumpGameScene()
+ void Renderer::DumpGameScene(SceneRenderMode renderMode)
{
- RenderScene(&_dumpScreenRenderTarget, false, _gameCamera);
-
- _graphicsSettingsChanged = false;
+ RenderScene(&_dumpScreenRenderTarget, _gameCamera, renderMode);
}
void Renderer::DrawItems(RenderView& view, RendererPass rendererPass)
@@ -2405,7 +2412,7 @@ namespace TEN::Renderer
std::vector statics = it->second;
RendererStatic* refStatic = statics[0];
- RendererObject& refStaticObj = *_staticObjects[refStatic->ObjectNumber];
+ RendererObject& refStaticObj = GetStaticRendererObject(refStatic->ObjectNumber);
if (refStaticObj.ObjectMeshes.size() == 0)
continue;
@@ -2483,7 +2490,7 @@ namespace TEN::Renderer
std::vector statics = it->second;
RendererStatic* refStatic = statics[0];
- RendererObject& refStaticObj = *_staticObjects[refStatic->ObjectNumber];
+ RendererObject& refStaticObj = GetStaticRendererObject(refStatic->ObjectNumber);
if (refStaticObj.ObjectMeshes.size() == 0)
continue;
@@ -2723,7 +2730,7 @@ namespace TEN::Renderer
for (int k = 0; k < renderView.RoomsToDraw.size(); k++)
{
const auto& nativeRoom = g_Level.Rooms[renderView.RoomsToDraw[k]->RoomNumber];
- if (nativeRoom.flags & ENV_FLAG_OUTSIDE)
+ if (nativeRoom.flags & ENV_FLAG_SKYBOX)
{
anyOutsideRooms = true;
break;
@@ -2876,7 +2883,7 @@ namespace TEN::Renderer
rDrawSprite.Type = SpriteType::CustomBillboard;
rDrawSprite.pos =
renderView.Camera.WorldPosition +
- Vector3::Lerp(meteor.PrevPosition, meteor.Position, _interpolationFactor);
+ Vector3::Lerp(meteor.PrevPosition, meteor.Position, GetInterpolationFactor());
rDrawSprite.Rotation = 0;
rDrawSprite.Scale = 1;
rDrawSprite.Width = 2;
@@ -2888,7 +2895,7 @@ namespace TEN::Renderer
meteor.Color.x,
meteor.Color.y,
meteor.Color.z,
- Lerp(meteor.PrevFade, meteor.Fade, _interpolationFactor));
+ Lerp(meteor.PrevFade, meteor.Fade, GetInterpolationFactor()));
_stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1;
_stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = 0;
@@ -3025,7 +3032,7 @@ namespace TEN::Renderer
void Renderer::Render(float interpFactor)
{
InterpolateCamera(interpFactor);
- RenderScene(&_backBuffer, true, _gameCamera);
+ RenderScene(&_backBuffer, _gameCamera);
_context->ClearState();
_swapChain->Present(1, 0);
@@ -3529,7 +3536,7 @@ namespace TEN::Renderer
_stStatic.World = objectInfo->Static->World;
_stStatic.Color = objectInfo->Static->Color;
_stStatic.AmbientLight = objectInfo->Room->AmbientLight;
- _stStatic.LightMode = (int)_staticObjects[objectInfo->Static->ObjectNumber]->ObjectMeshes[0]->LightMode;
+ _stStatic.LightMode = (int)GetStaticRendererObject(objectInfo->Static->ObjectNumber).ObjectMeshes[0]->LightMode;
BindStaticLights(objectInfo->Static->LightsToDraw);
_cbStatic.UpdateData(_stStatic, _context.Get());
diff --git a/TombEngine/Renderer/RendererDrawEffect.cpp b/TombEngine/Renderer/RendererDrawEffect.cpp
index bd031cee0..ef7b775fc 100644
--- a/TombEngine/Renderer/RendererDrawEffect.cpp
+++ b/TombEngine/Renderer/RendererDrawEffect.cpp
@@ -86,22 +86,22 @@ namespace TEN::Renderer
{
bool isLastSubdivision = (i == (LaserBeamEffect::SUBDIVISION_COUNT - 1));
- auto color = Color::Lerp(beam.OldColor, beam.Color, _interpolationFactor);
+ auto color = Color::Lerp(beam.OldColor, beam.Color, GetInterpolationFactor());
AddColoredQuad(
- Vector3::Lerp(beam.OldVertices[i], beam.Vertices[i], _interpolationFactor),
+ Vector3::Lerp(beam.OldVertices[i], beam.Vertices[i], GetInterpolationFactor()),
Vector3::Lerp(
beam.OldVertices[isLastSubdivision ? 0 : (i + 1)],
beam.Vertices[isLastSubdivision ? 0 : (i + 1)],
- _interpolationFactor),
+ GetInterpolationFactor()),
Vector3::Lerp(
beam.OldVertices[LaserBeamEffect::SUBDIVISION_COUNT + (isLastSubdivision ? 0 : (i + 1))],
beam.Vertices[LaserBeamEffect::SUBDIVISION_COUNT + (isLastSubdivision ? 0 : (i + 1))],
- _interpolationFactor),
+ GetInterpolationFactor()),
Vector3::Lerp(
beam.OldVertices[LaserBeamEffect::SUBDIVISION_COUNT + i],
beam.Vertices[LaserBeamEffect::SUBDIVISION_COUNT + i],
- _interpolationFactor),
+ GetInterpolationFactor()),
color, color, color, color,
BlendMode::Additive, view, SpriteRenderType::LaserBeam);
}
@@ -134,40 +134,40 @@ namespace TEN::Renderer
if (segment.Flags & (int)StreamerFlags::FadeLeft)
{
AddColoredQuad(
- Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], _interpolationFactor),
- Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], _interpolationFactor),
- Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], _interpolationFactor),
- Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], _interpolationFactor),
+ Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], GetInterpolationFactor()),
+ Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], GetInterpolationFactor()),
+ Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], GetInterpolationFactor()),
+ Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], GetInterpolationFactor()),
Vector4::Zero,
- Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor),
- Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor),
+ Vector4::Lerp(segment.PrevColor, segment.Color, GetInterpolationFactor()),
+ Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, GetInterpolationFactor()),
Vector4::Zero,
blendMode, view);
}
else if (segment.Flags & (int)StreamerFlags::FadeRight)
{
AddColoredQuad(
- Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], _interpolationFactor),
- Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], _interpolationFactor),
- Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], _interpolationFactor),
- Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], _interpolationFactor),
- Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor),
+ Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], GetInterpolationFactor()),
+ Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], GetInterpolationFactor()),
+ Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], GetInterpolationFactor()),
+ Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], GetInterpolationFactor()),
+ Vector4::Lerp(segment.PrevColor, segment.Color, GetInterpolationFactor()),
Vector4::Zero,
Vector4::Zero,
- Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor),
+ Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, GetInterpolationFactor()),
blendMode, view);
}
else
{
AddColoredQuad(
- Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], _interpolationFactor),
- Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], _interpolationFactor),
- Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], _interpolationFactor),
- Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], _interpolationFactor),
- Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor),
- Vector4::Lerp(segment.PrevColor, segment.Color, _interpolationFactor),
- Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor),
- Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, _interpolationFactor),
+ Vector3::Lerp(segment.PrevVertices[0], segment.Vertices[0], GetInterpolationFactor()),
+ Vector3::Lerp(segment.PrevVertices[1], segment.Vertices[1], GetInterpolationFactor()),
+ Vector3::Lerp(prevSegment.PrevVertices[1], prevSegment.Vertices[1], GetInterpolationFactor()),
+ Vector3::Lerp(prevSegment.PrevVertices[0], prevSegment.Vertices[0], GetInterpolationFactor()),
+ Vector4::Lerp(segment.PrevColor, segment.Color, GetInterpolationFactor()),
+ Vector4::Lerp(segment.PrevColor, segment.Color, GetInterpolationFactor()),
+ Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, GetInterpolationFactor()),
+ Vector4::Lerp(prevSegment.PrevColor, prevSegment.Color, GetInterpolationFactor()),
blendMode, view);
}
}
@@ -189,13 +189,13 @@ namespace TEN::Renderer
if (laser.Life <= 0.0f)
continue;
- auto color = Vector4::Lerp(laser.PrevColor, laser.Color, _interpolationFactor);
- color.w = Lerp(laser.PrevOpacity, laser.Opacity, _interpolationFactor);
+ auto color = Vector4::Lerp(laser.PrevColor, laser.Color, GetInterpolationFactor());
+ color.w = Lerp(laser.PrevOpacity, laser.Opacity, GetInterpolationFactor());
- auto laserTarget = Vector3::Lerp(laser.PrevTarget, laser.Target, _interpolationFactor);
+ auto laserTarget = Vector3::Lerp(laser.PrevTarget, laser.Target, GetInterpolationFactor());
ElectricityKnots[0] = laserTarget;
- ElectricityKnots[1] = Vector3::Lerp(laser.PrevOrigin, laser.Origin, _interpolationFactor);
+ ElectricityKnots[1] = Vector3::Lerp(laser.PrevOrigin, laser.Origin, GetInterpolationFactor());
for (int j = 0; j < 2; j++)
ElectricityKnots[j] -= laserTarget;
@@ -241,12 +241,12 @@ namespace TEN::Renderer
if (arc.life <= 0)
continue;
- ElectricityKnots[0] = Vector3::Lerp(arc.PrevPos1, arc.pos1, _interpolationFactor);
- ElectricityKnots[1] = Vector3::Lerp(arc.PrevPos1, arc.pos1, _interpolationFactor);
- ElectricityKnots[2] = Vector3::Lerp(arc.PrevPos2, arc.pos2, _interpolationFactor);
- ElectricityKnots[3] = Vector3::Lerp(arc.PrevPos3, arc.pos3, _interpolationFactor);
- ElectricityKnots[4] = Vector3::Lerp(arc.PrevPos4, arc.pos4, _interpolationFactor);
- ElectricityKnots[5] = Vector3::Lerp(arc.PrevPos4, arc.pos4, _interpolationFactor);
+ ElectricityKnots[0] = Vector3::Lerp(arc.PrevPos1, arc.pos1, GetInterpolationFactor());
+ ElectricityKnots[1] = Vector3::Lerp(arc.PrevPos1, arc.pos1, GetInterpolationFactor());
+ ElectricityKnots[2] = Vector3::Lerp(arc.PrevPos2, arc.pos2, GetInterpolationFactor());
+ ElectricityKnots[3] = Vector3::Lerp(arc.PrevPos3, arc.pos3, GetInterpolationFactor());
+ ElectricityKnots[4] = Vector3::Lerp(arc.PrevPos4, arc.pos4, GetInterpolationFactor());
+ ElectricityKnots[5] = Vector3::Lerp(arc.PrevPos4, arc.pos4, GetInterpolationFactor());
for (int j = 0; j < ElectricityKnots.size(); j++)
ElectricityKnots[j] -= LaraItem->Pose.Position.ToVector3();
@@ -300,9 +300,9 @@ namespace TEN::Renderer
oldB = (arc.PrevLife * arc.PrevB) / 16;
}
- r = (byte)Lerp(oldR, r, _interpolationFactor);
- g = (byte)Lerp(oldG, g, _interpolationFactor);
- b = (byte)Lerp(oldB, b, _interpolationFactor);
+ r = (byte)Lerp(oldR, r, GetInterpolationFactor());
+ g = (byte)Lerp(oldG, g, GetInterpolationFactor());
+ b = (byte)Lerp(oldB, b, GetInterpolationFactor());
AddSpriteBillboardConstrained(
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LIGHTHING],
@@ -326,16 +326,16 @@ namespace TEN::Renderer
Vector3::Lerp(
Vector3(smoke.oldPosition.x, smoke.oldPosition.y, smoke.oldPosition.z),
Vector3(smoke.position.x, smoke.position.y, smoke.position.z),
- _interpolationFactor),
+ GetInterpolationFactor()),
Vector4::Lerp(
Vector4(smoke.oldShade / 255.0f, smoke.oldShade / 255.0f, smoke.oldShade / 255.0f, 1.0f),
Vector4(smoke.shade / 255.0f, smoke.shade / 255.0f, smoke.shade / 255.0f, 1.0f),
- _interpolationFactor),
- TO_RAD(Lerp(smoke.oldRotAng << 4, smoke.rotAng << 4, _interpolationFactor)),
- Lerp(smoke.oldScalar, smoke.scalar, _interpolationFactor),
+ GetInterpolationFactor()),
+ TO_RAD(Lerp(smoke.oldRotAng << 4, smoke.rotAng << 4, GetInterpolationFactor())),
+ Lerp(smoke.oldScalar, smoke.scalar, GetInterpolationFactor()),
{
- Lerp(smoke.oldSize, smoke.size, _interpolationFactor) * 4.0f,
- Lerp(smoke.oldSize, smoke.size, _interpolationFactor) * 4.0f
+ Lerp(smoke.oldSize, smoke.size, GetInterpolationFactor()) * 4.0f,
+ Lerp(smoke.oldSize, smoke.size, GetInterpolationFactor()) * 4.0f
},
BlendMode::Additive, true, view);
}
@@ -347,7 +347,7 @@ namespace TEN::Renderer
{
auto oldFade = fire.oldFade == 1 ? 1.0f : (float)(255 - fire.oldFade) / 255.0f;
auto fade = fire.fade == 1 ? 1.0f : (float)(255 - fire.fade) / 255.0f;
- fade = Lerp(oldFade, fade, _interpolationFactor);
+ fade = Lerp(oldFade, fade, GetInterpolationFactor());
for (int i = 0; i < MAX_SPARKS_FIRE; i++)
{
@@ -365,7 +365,7 @@ namespace TEN::Renderer
fire.position.x + spark->position.x * fire.size / 2,
fire.position.y + spark->position.y * fire.size / 2,
fire.position.z + spark->position.z * fire.size / 2),
- _interpolationFactor),
+ GetInterpolationFactor()),
Vector4::Lerp(
Vector4(
spark->oldColor.x / 255.0f * fade,
@@ -377,13 +377,13 @@ namespace TEN::Renderer
spark->color.y / 255.0f * fade,
spark->color.z / 255.0f * fade,
1.0f),
- _interpolationFactor),
- TO_RAD(Lerp(spark->oldRotAng << 4, spark->rotAng << 4, _interpolationFactor)),
- Lerp(spark->oldScalar, spark->scalar, _interpolationFactor),
+ GetInterpolationFactor()),
+ TO_RAD(Lerp(spark->oldRotAng << 4, spark->rotAng << 4, GetInterpolationFactor())),
+ Lerp(spark->oldScalar, spark->scalar, GetInterpolationFactor()),
Vector2::Lerp(
Vector2(fire.oldSize * spark->oldSize, fire.oldSize * spark->oldSize),
Vector2(fire.size * spark->size, fire.size * spark->size),
- _interpolationFactor),
+ GetInterpolationFactor()),
BlendMode::Additive, true, view);
}
}
@@ -405,7 +405,7 @@ namespace TEN::Renderer
auto pos = Vector3::Lerp(
Vector3(particle.PrevX, particle.PrevY, particle.PrevZ),
Vector3(particle.x, particle.y, particle.z),
- _interpolationFactor);
+ GetInterpolationFactor());
if (particle.flags & SP_FX)
{
@@ -421,11 +421,11 @@ namespace TEN::Renderer
newEffect.RoomNumber = fx.roomNumber;
newEffect.Position = fx.pos.Position.ToVector3();
- newEffect.InterpolatedPosition = Vector3::Lerp(newEffect.PrevPosition, newEffect.Position, _interpolationFactor);
- newEffect.InterpolatedTranslation = Matrix::Lerp(newEffect.PrevTranslation, newEffect.Translation, _interpolationFactor);
- newEffect.InterpolatedRotation = Matrix::Lerp(newEffect.InterpolatedRotation, newEffect.Rotation, _interpolationFactor);
- newEffect.InterpolatedWorld = Matrix::Lerp(newEffect.PrevWorld, newEffect.World, _interpolationFactor);
- newEffect.InterpolatedScale = Matrix::Lerp(newEffect.PrevScale, newEffect.Scale, _interpolationFactor);
+ newEffect.InterpolatedPosition = Vector3::Lerp(newEffect.PrevPosition, newEffect.Position, GetInterpolationFactor());
+ newEffect.InterpolatedTranslation = Matrix::Lerp(newEffect.PrevTranslation, newEffect.Translation, GetInterpolationFactor());
+ newEffect.InterpolatedRotation = Matrix::Lerp(newEffect.InterpolatedRotation, newEffect.Rotation, GetInterpolationFactor());
+ newEffect.InterpolatedWorld = Matrix::Lerp(newEffect.PrevWorld, newEffect.World, GetInterpolationFactor());
+ newEffect.InterpolatedScale = Matrix::Lerp(newEffect.PrevScale, newEffect.Scale, GetInterpolationFactor());
pos += newEffect.InterpolatedPosition;
@@ -514,7 +514,7 @@ namespace TEN::Renderer
auto pos = Vector3::Lerp(
Vector3(particle.PrevX, particle.PrevY, particle.PrevZ),
Vector3(particle.x, particle.y, particle.z),
- _interpolationFactor);
+ GetInterpolationFactor());
auto axis = Vector3(particle.xVel, particle.yVel, particle.zVel);
axis.Normalize();
@@ -567,7 +567,7 @@ namespace TEN::Renderer
}
}
- color = (byte)Lerp(prevColor, color, _interpolationFactor);
+ color = (byte)Lerp(prevColor, color, GetInterpolationFactor());
float xInner;
float zInner;
@@ -580,8 +580,8 @@ namespace TEN::Renderer
float yInner = splash.y;
float yOuter = splash.y - splash.height;
- float innerRadius = Lerp(splash.PrevInnerRad, splash.innerRad, _interpolationFactor);
- float outerRadius = Lerp(splash.PrevOuterRad, splash.outerRad, _interpolationFactor);
+ float innerRadius = Lerp(splash.PrevInnerRad, splash.innerRad, GetInterpolationFactor());
+ float outerRadius = Lerp(splash.PrevOuterRad, splash.outerRad, GetInterpolationFactor());
for (int i = 0; i < NUM_POINTS; i++)
{
@@ -630,11 +630,11 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + bubble.SpriteIndex],
- Vector3::Lerp(bubble.PrevPosition, bubble.Position, _interpolationFactor),
- Vector4::Lerp(bubble.PrevColor, bubble.Color, _interpolationFactor),
+ Vector3::Lerp(bubble.PrevPosition, bubble.Position, GetInterpolationFactor()),
+ Vector4::Lerp(bubble.PrevColor, bubble.Color, GetInterpolationFactor()),
0.0f,
1.0f,
- Vector2::Lerp(bubble.PrevSize, bubble.Size, _interpolationFactor) / 2,
+ Vector2::Lerp(bubble.PrevSize, bubble.Size, GetInterpolationFactor()) / 2,
BlendMode::Additive, true, view);
}
}
@@ -660,10 +660,10 @@ namespace TEN::Renderer
AddSpriteBillboardConstrained(
&_sprites[Objects[ID_DRIP_SPRITE].meshIndex],
- Vector3::Lerp(drip.PrevPosition, drip.Position, _interpolationFactor),
- Vector4::Lerp(drip.PrevColor, drip.Color, _interpolationFactor),
- 0.0f, 1.0f, Vector2::Lerp(drip.PrevSize, drip.Size, _interpolationFactor),
- BlendMode::Additive, -Vector3::Lerp(prevAxis, axis, _interpolationFactor), false, view);
+ Vector3::Lerp(drip.PrevPosition, drip.Position, GetInterpolationFactor()),
+ Vector4::Lerp(drip.PrevColor, drip.Color, GetInterpolationFactor()),
+ 0.0f, 1.0f, Vector2::Lerp(drip.PrevSize, drip.Size, GetInterpolationFactor()),
+ BlendMode::Additive, -Vector3::Lerp(prevAxis, axis, GetInterpolationFactor()), false, view);
}
}
@@ -687,9 +687,9 @@ namespace TEN::Renderer
AddSpriteBillboardConstrainedLookAt(
&_sprites[ripple.SpriteIndex],
- Vector3::Lerp(ripple.PrevPosition, ripple.Position, _interpolationFactor),
- Vector4::Lerp(oldColor, color, _interpolationFactor),
- 0.0f, 1.0f, Vector2(Lerp(ripple.PrevSize, ripple.Size, _interpolationFactor) * 2),
+ Vector3::Lerp(ripple.PrevPosition, ripple.Position, GetInterpolationFactor()),
+ Vector4::Lerp(oldColor, color, GetInterpolationFactor()),
+ 0.0f, 1.0f, Vector2(Lerp(ripple.PrevSize, ripple.Size, GetInterpolationFactor()) * 2),
BlendMode::Additive, ripple.Normal, true, view);
}
}
@@ -732,12 +732,12 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[uwBlood.SpriteIndex],
- Vector3::Lerp(uwBlood.PrevPosition, uwBlood.Position, _interpolationFactor),
- Vector4::Lerp(oldColor, color, _interpolationFactor),
+ Vector3::Lerp(uwBlood.PrevPosition, uwBlood.Position, GetInterpolationFactor()),
+ Vector4::Lerp(oldColor, color, GetInterpolationFactor()),
0.0f, 1.0f,
Vector2(
- Lerp(uwBlood.PrevSize, uwBlood.Size, _interpolationFactor),
- Lerp(uwBlood.PrevSize, uwBlood.Size, _interpolationFactor)) * 2,
+ Lerp(uwBlood.PrevSize, uwBlood.Size, GetInterpolationFactor()),
+ Lerp(uwBlood.PrevSize, uwBlood.Size, GetInterpolationFactor())) * 2,
BlendMode::Additive, true, view);
}
}
@@ -772,8 +772,8 @@ namespace TEN::Renderer
auto pos = Vector3(shockwave->x, shockwave->y, shockwave->z);
- float innerRadius = Lerp(shockwave->oldInnerRad, shockwave->innerRad, _interpolationFactor);
- float outerRadius = Lerp(shockwave->oldOuterRad, shockwave->outerRad, _interpolationFactor);
+ float innerRadius = Lerp(shockwave->oldInnerRad, shockwave->innerRad, GetInterpolationFactor());
+ float outerRadius = Lerp(shockwave->oldOuterRad, shockwave->outerRad, GetInterpolationFactor());
// Inner circle
if (shockwave->style == (int)ShockwaveStyle::Normal)
@@ -934,16 +934,16 @@ namespace TEN::Renderer
Vector3::Lerp(
Vector3(blood->oldX, blood->oldY, blood->oldZ),
Vector3(blood->x, blood->y, blood->z),
- _interpolationFactor),
+ GetInterpolationFactor()),
Vector4::Lerp(
Vector4(blood->oldShade / 255.0f, blood->oldShade * 0, blood->oldShade * 0, 1.0f),
Vector4(blood->shade / 255.0f, blood->shade * 0, blood->shade * 0, 1.0f),
- _interpolationFactor),
- TO_RAD(Lerp(blood->oldRotAng << 4, blood->rotAng << 4, _interpolationFactor)),
+ GetInterpolationFactor()),
+ TO_RAD(Lerp(blood->oldRotAng << 4, blood->rotAng << 4, GetInterpolationFactor())),
1.0f,
Vector2(
- Lerp(blood->oldSize, blood->size, _interpolationFactor) * 8.0f,
- Lerp(blood->oldSize, blood->size, _interpolationFactor) * 8.0f),
+ Lerp(blood->oldSize, blood->size, GetInterpolationFactor()) * 8.0f,
+ Lerp(blood->oldSize, blood->size, GetInterpolationFactor()) * 8.0f),
BlendMode::Additive, true, view);
}
}
@@ -967,9 +967,9 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST],
- Vector3::Lerp(part.PrevPosition, part.Position, _interpolationFactor),
+ Vector3::Lerp(part.PrevPosition, part.Position, GetInterpolationFactor()),
Color(1.0f, 1.0f, 1.0f, part.Transparency()),
- 0.0f, 1.0f, Vector2(Lerp(part.PrevSize, part.Size, _interpolationFactor)),
+ 0.0f, 1.0f, Vector2(Lerp(part.PrevSize, part.Size, GetInterpolationFactor())),
BlendMode::Additive, true, view);
break;
@@ -981,9 +981,9 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST],
- Vector3::Lerp(part.PrevPosition, part.Position, _interpolationFactor),
+ Vector3::Lerp(part.PrevPosition, part.Position, GetInterpolationFactor()),
Color(1.0f, 1.0f, 1.0f, part.Transparency()),
- 0.0f, 1.0f, Vector2(Lerp(part.PrevSize, part.Size, _interpolationFactor)),
+ 0.0f, 1.0f, Vector2(Lerp(part.PrevSize, part.Size, GetInterpolationFactor())),
BlendMode::Additive, true, view);
break;
@@ -998,10 +998,10 @@ namespace TEN::Renderer
AddSpriteBillboardConstrained(
&_sprites[Objects[ID_DRIP_SPRITE].meshIndex],
- Vector3::Lerp(part.PrevPosition, part.Position, _interpolationFactor),
+ Vector3::Lerp(part.PrevPosition, part.Position, GetInterpolationFactor()),
Color(0.8f, 1.0f, 1.0f, part.Transparency()),
0.0f, 1.0f,
- Vector2(RAIN_WIDTH, Lerp(part.PrevSize, part.Size, _interpolationFactor)),
+ Vector2(RAIN_WIDTH, Lerp(part.PrevSize, part.Size, GetInterpolationFactor())),
BlendMode::Additive, -v, true, view);
break;
@@ -1403,7 +1403,7 @@ namespace TEN::Renderer
BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[deb.mesh.tex]), SamplerStateRegister::LinearClamp);
}
- _stStatic.World = Matrix::Lerp(deb.PrevTransform, deb.Transform, _interpolationFactor);
+ _stStatic.World = Matrix::Lerp(deb.PrevTransform, deb.Transform, GetInterpolationFactor());
_stStatic.Color = deb.color;
_stStatic.AmbientLight = _rooms[deb.roomNumber].AmbientLight;
_stStatic.LightMode = (int)deb.lightMode;
@@ -1462,13 +1462,13 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[Objects[ID_SMOKE_SPRITES].meshIndex + smoke.sprite],
- Vector3::Lerp(smoke.PrevPosition, smoke.position, _interpolationFactor),
- Vector4::Lerp(smoke.PrevColor, smoke.color, _interpolationFactor),
- Lerp(smoke.PrevRotation, smoke.rotation, _interpolationFactor),
+ Vector3::Lerp(smoke.PrevPosition, smoke.position, GetInterpolationFactor()),
+ Vector4::Lerp(smoke.PrevColor, smoke.color, GetInterpolationFactor()),
+ Lerp(smoke.PrevRotation, smoke.rotation, GetInterpolationFactor()),
1.0f,
Vector2(
- Lerp(smoke.PrevSize, smoke.size, _interpolationFactor),
- Lerp(smoke.PrevSize, smoke.size, _interpolationFactor)),
+ Lerp(smoke.PrevSize, smoke.size, GetInterpolationFactor()),
+ Lerp(smoke.PrevSize, smoke.size, GetInterpolationFactor())),
BlendMode::AlphaBlend, true, view);
}
}
@@ -1493,7 +1493,7 @@ namespace TEN::Renderer
s.PrevVelocity.Normalize(prevVelocity);
s.velocity.Normalize(velocity);
- velocity = Vector3::Lerp(prevVelocity, velocity, _interpolationFactor);
+ velocity = Vector3::Lerp(prevVelocity, velocity, GetInterpolationFactor());
velocity.Normalize();
float normalizedLife = s.age / s.life;
@@ -1502,7 +1502,7 @@ namespace TEN::Renderer
AddSpriteBillboardConstrained(
&_sprites[Objects[ID_SPARK_SPRITE].meshIndex],
- Vector3::Lerp(s.PrevPosition, s.pos, _interpolationFactor),
+ Vector3::Lerp(s.PrevPosition, s.pos, GetInterpolationFactor()),
color,
0, 1,
Vector2(
@@ -1527,13 +1527,13 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[Objects[ID_EXPLOSION_SPRITES].meshIndex + exp.sprite],
- Vector3::Lerp(exp.oldPos, exp.pos, _interpolationFactor),
- Vector4::Lerp(exp.oldTint, exp.tint, _interpolationFactor),
- Lerp(exp.oldRotation, exp.rotation, _interpolationFactor),
+ Vector3::Lerp(exp.oldPos, exp.pos, GetInterpolationFactor()),
+ Vector4::Lerp(exp.oldTint, exp.tint, GetInterpolationFactor()),
+ Lerp(exp.oldRotation, exp.rotation, GetInterpolationFactor()),
1.0f,
Vector2(
- Lerp(exp.oldSize, exp.size, _interpolationFactor),
- Lerp(exp.oldSize, exp.size, _interpolationFactor)),
+ Lerp(exp.oldSize, exp.size, GetInterpolationFactor()),
+ Lerp(exp.oldSize, exp.size, GetInterpolationFactor())),
BlendMode::Additive, true, view);
}
}
@@ -1552,11 +1552,11 @@ namespace TEN::Renderer
AddSpriteBillboard(
&_sprites[Objects[part.sequence].meshIndex + part.sprite],
- Vector3::Lerp(part.PrevWorldPosition, part.worldPosition, _interpolationFactor),
+ Vector3::Lerp(part.PrevWorldPosition, part.worldPosition, GetInterpolationFactor()),
Color(1.0f, 1.0f, 1.0f), 0, 1.0f,
Vector2(
- Lerp(part.PrevSize, part.size, _interpolationFactor),
- Lerp(part.PrevSize, part.size, _interpolationFactor) / 2),
+ Lerp(part.PrevSize, part.size, GetInterpolationFactor()),
+ Lerp(part.PrevSize, part.size, GetInterpolationFactor()) / 2),
BlendMode::AlphaBlend, true, view);
}
}
diff --git a/TombEngine/Renderer/RendererDrawMenu.cpp b/TombEngine/Renderer/RendererDrawMenu.cpp
index d0316cad4..6e8685634 100644
--- a/TombEngine/Renderer/RendererDrawMenu.cpp
+++ b/TombEngine/Renderer/RendererDrawMenu.cpp
@@ -15,6 +15,7 @@
#include "Specific/level.h"
#include "Specific/trutils.h"
#include "Specific/winmain.h"
+#include "Version.h"
using namespace TEN::Gui;
using namespace TEN::Hud;
@@ -745,10 +746,10 @@ namespace TEN::Renderer
constexpr auto COUNT_STRING_INF = "Inf";
constexpr auto COUNT_STRING_OFFSET = Vector2(DISPLAY_SPACE_RES.x / 40, 0.0f);
- auto pos = Vector2::Lerp(pickup.PrevPosition, pickup.Position, _interpolationFactor);
- auto orient = EulerAngles::Lerp(pickup.PrevOrientation, pickup.Orientation, _interpolationFactor);
- float scale = Lerp(pickup.PrevScale, pickup.Scale, _interpolationFactor);
- float opacity = Lerp(pickup.PrevOpacity, pickup.Opacity, _interpolationFactor);
+ auto pos = Vector2::Lerp(pickup.PrevPosition, pickup.Position, GetInterpolationFactor());
+ auto orient = EulerAngles::Lerp(pickup.PrevOrientation, pickup.Orientation, GetInterpolationFactor());
+ float scale = Lerp(pickup.PrevScale, pickup.Scale, GetInterpolationFactor());
+ float opacity = Lerp(pickup.PrevOpacity, pickup.Opacity, GetInterpolationFactor());
// Draw display pickup.
DrawObjectIn2DSpace(pickup.ObjectID, pos, orient, scale);
@@ -1013,10 +1014,6 @@ namespace TEN::Renderer
_context->VSSetShader(_vsInventory.Get(), nullptr, 0);
_context->PSSetShader(_psInventory.Get(), nullptr, 0);
- // Set texture
- BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[0]), SamplerStateRegister::AnisotropicClamp);
- BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[0]), SamplerStateRegister::AnisotropicClamp);
-
if (CurrentLevel == 0)
{
auto titleMenu = g_Gui.GetMenuToDisplay();
@@ -1047,6 +1044,14 @@ namespace TEN::Renderer
}
else
{
+ if (g_Gui.GetInventoryMode() == InventoryMode::InGame ||
+ g_Gui.GetInventoryMode() == InventoryMode::Examine)
+ {
+ // Set texture.
+ BindTexture(TextureRegister::ColorMap, &std::get<0>(_moveablesTextures[0]), SamplerStateRegister::AnisotropicClamp);
+ BindTexture(TextureRegister::NormalMap, &std::get<1>(_moveablesTextures[0]), SamplerStateRegister::AnisotropicClamp);
+ }
+
switch (g_Gui.GetInventoryMode())
{
case InventoryMode::Load:
@@ -1101,28 +1106,72 @@ namespace TEN::Renderer
SetTextureOrDefault(_loadingScreenTexture, fileName);
}
+ void Renderer::RenderFreezeMode(float interpFactor, bool staticBackground)
+ {
+ if (staticBackground)
+ {
+ // Set basic render states.
+ SetBlendMode(BlendMode::Opaque);
+ SetCullMode(CullMode::CounterClockwise);
+
+ // Clear screen
+ _context->ClearRenderTargetView(_backBuffer.RenderTargetView.Get(), Colors::Black);
+ _context->ClearDepthStencilView(_backBuffer.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
+
+ // Bind back buffer.
+ _context->OMSetRenderTargets(1, _backBuffer.RenderTargetView.GetAddressOf(), _backBuffer.DepthStencilView.Get());
+ _context->RSSetViewports(1, &_viewport);
+ ResetScissor();
+
+ // Draw full screen background.
+ DrawFullScreenQuad(_dumpScreenRenderTarget.ShaderResourceView.Get(), Vector3::One);
+ }
+ else
+ {
+ InterpolateCamera(interpFactor);
+ RenderScene(&_backBuffer, _gameCamera, SceneRenderMode::NoHud);
+ }
+
+ // TODO: Put 3D object drawing management here (don't forget about interpolation!)
+ // Draw3DObjectsIn2DSpace(_gameCamera);
+
+ // Draw display sprites sorted by priority.
+ CollectDisplaySprites(_gameCamera);
+ DrawDisplaySprites(_gameCamera);
+ DrawAllStrings();
+
+ ClearScene();
+
+ _context->ClearState();
+ _swapChain->Present(1, 0);
+ }
+
void Renderer::RenderLoadingScreen(float percentage)
{
- // Set basic render states
+ // Set basic render states.
SetBlendMode(BlendMode::Opaque);
SetCullMode(CullMode::CounterClockwise);
do
{
- // Clear screen
+ // Clear screen.
_context->ClearRenderTargetView(_backBuffer.RenderTargetView.Get(), Colors::Black);
_context->ClearDepthStencilView(_backBuffer.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
- // Bind the back buffer
+ // Bind back buffer.
_context->OMSetRenderTargets(1, _backBuffer.RenderTargetView.GetAddressOf(), _backBuffer.DepthStencilView.Get());
_context->RSSetViewports(1, &_viewport);
ResetScissor();
- // Draw the full screen background
+ // Draw fullscreen background. If unavailable, draw last dumped game scene.
if (_loadingScreenTexture.Texture)
- DrawFullScreenQuad(
- _loadingScreenTexture.ShaderResourceView.Get(),
- Vector3(ScreenFadeCurrent, ScreenFadeCurrent, ScreenFadeCurrent));
+ {
+ DrawFullScreenQuad(_loadingScreenTexture.ShaderResourceView.Get(), Vector3(ScreenFadeCurrent, ScreenFadeCurrent, ScreenFadeCurrent));
+ }
+ else if (_dumpScreenRenderTarget.Texture)
+ {
+ DrawFullScreenQuad(_dumpScreenRenderTarget.ShaderResourceView.Get(), Vector3(ScreenFadeCurrent, ScreenFadeCurrent, ScreenFadeCurrent));
+ }
if (ScreenFadeCurrent && percentage > 0.0f && percentage < 100.0f)
DrawLoadingBar(percentage);
@@ -1143,6 +1192,7 @@ namespace TEN::Renderer
UpdateCameraMatrices(&Camera, BLOCK(g_GameFlow->GetLevel(CurrentLevel)->GetFarView()));
Camera.DisableInterpolation = true;
DumpGameScene();
+ _graphicsSettingsChanged = false;
}
_context->ClearDepthStencilView(_backBuffer.DepthStencilView.Get(), D3D11_CLEAR_STENCIL | D3D11_CLEAR_DEPTH, 1.0f, 0);
@@ -1178,6 +1228,14 @@ namespace TEN::Renderer
void Renderer::DrawDebugInfo(RenderView& view)
{
+#ifdef TEST_BUILD
+ if (CurrentLevel == 0)
+ {
+ AddString("TombEngine " + std::string(TEN_VERSION_STRING) + " test build - not for distribution",
+ Vector2(20, 560), Vector4(1.0f, 0, 0, 0.5f), 0.7f, 0);
+ }
+#endif
+
if (!DebugMode || CurrentLevel == 0)
return;
@@ -1186,7 +1244,7 @@ namespace TEN::Renderer
const auto& room = g_Level.Rooms[LaraItem->RoomNumber];
float aspectRatio = _screenWidth / (float)_screenHeight;
- int thumbWidth = _screenWidth / 6;
+ int thumbWidth = _screenWidth / 8;
auto rect = RECT{};
int thumbY = 0;
@@ -1262,6 +1320,22 @@ namespace TEN::Renderer
thumbY += thumbWidth / aspectRatio;
}
+ rect.left = _screenWidth - thumbWidth;
+ rect.top = thumbY;
+ rect.right = rect.left + thumbWidth;
+ rect.bottom = rect.top + thumbWidth;
+
+ _spriteBatch->Draw(_roomAmbientMapFront.ShaderResourceView.Get(), rect);
+ thumbY += thumbWidth;
+
+ rect.left = _screenWidth - thumbWidth;
+ rect.top = thumbY;
+ rect.right = rect.left + thumbWidth;
+ rect.bottom = rect.top + thumbWidth;
+
+ _spriteBatch->Draw(_roomAmbientMapBack.ShaderResourceView.Get(), rect);
+ thumbY += thumbWidth;
+
_spriteBatch->End();
break;
diff --git a/TombEngine/Renderer/RendererEnums.h b/TombEngine/Renderer/RendererEnums.h
index c159c1c42..23df7a369 100644
--- a/TombEngine/Renderer/RendererEnums.h
+++ b/TombEngine/Renderer/RendererEnums.h
@@ -58,15 +58,13 @@ constexpr auto SKY_INDICES_COUNT = 6 * SKY_TILES_COUNT * SKY_TILES_COUNT;
constexpr auto SKY_TRIANGLES_COUNT = 2 * SKY_TILES_COUNT * SKY_TILES_COUNT;
constexpr auto MAX_ROOMS_DRAW = 256;
-constexpr auto MAX_STATICS_DRAW = 128;
-constexpr auto MAX_EFFECTS_DRAW = 16;
constexpr auto MAX_ITEMS_DRAW = 128;
constexpr auto MAX_LIGHTS_DRAW = 48;
constexpr auto MAX_FOG_BULBS_DRAW = 32;
constexpr auto MAX_SPRITES_DRAW = 512;
constexpr auto MAX_LENS_FLARES_DRAW = 8;
-constexpr auto ROOM_AMBIENT_MAP_SIZE = 32;
+constexpr auto ROOM_AMBIENT_MAP_SIZE = 64;
constexpr auto MAX_ROOM_AMBIENT_MAPS = 10;
enum class LightType
@@ -231,6 +229,13 @@ enum class RendererPass
RoomAmbient
};
+enum class SceneRenderMode
+{
+ Full,
+ NoHud,
+ NoPostprocess
+};
+
enum class SpriteRenderType
{
Default,
diff --git a/TombEngine/Renderer/RendererFrame.cpp b/TombEngine/Renderer/RendererFrame.cpp
index 64d6bea6e..d40516df1 100644
--- a/TombEngine/Renderer/RendererFrame.cpp
+++ b/TombEngine/Renderer/RendererFrame.cpp
@@ -436,13 +436,16 @@ namespace TEN::Renderer
newItem->PrevAnimTransforms[j] = newItem->AnimTransforms[j];
}
- newItem->InterpolatedPosition = Vector3::Lerp(newItem->PrevPosition, newItem->Position, _interpolationFactor);
- newItem->InterpolatedTranslation = Matrix::Lerp(newItem->PrevTranslation, newItem->Translation, _interpolationFactor);
- newItem->InterpolatedRotation = Matrix::Lerp(newItem->InterpolatedRotation, newItem->Rotation, _interpolationFactor);
- newItem->InterpolatedWorld = Matrix::Lerp(newItem->PrevWorld, newItem->World, _interpolationFactor);
+ // Force interpolation only for Lara in player freeze mode.
+ bool forceValue = g_GameFlow->CurrentFreezeMode == FreezeMode::Player && item->ObjectNumber == ID_LARA;
+
+ newItem->InterpolatedPosition = Vector3::Lerp(newItem->PrevPosition, newItem->Position, GetInterpolationFactor(forceValue));
+ newItem->InterpolatedTranslation = Matrix::Lerp(newItem->PrevTranslation, newItem->Translation, GetInterpolationFactor(forceValue));
+ newItem->InterpolatedRotation = Matrix::Lerp(newItem->InterpolatedRotation, newItem->Rotation, GetInterpolationFactor(forceValue));
+ newItem->InterpolatedWorld = Matrix::Lerp(newItem->PrevWorld, newItem->World, GetInterpolationFactor(forceValue));
for (int j = 0; j < MAX_BONES; j++)
- newItem->InterpolatedAnimTransforms[j] = Matrix::Lerp(newItem->PrevAnimTransforms[j], newItem->AnimTransforms[j], _interpolationFactor);
+ newItem->InterpolatedAnimTransforms[j] = Matrix::Lerp(newItem->PrevAnimTransforms[j], newItem->AnimTransforms[j], GetInterpolationFactor(forceValue));
CalculateLightFades(newItem);
CollectLightsForItem(newItem);
@@ -471,7 +474,7 @@ namespace TEN::Renderer
{
mesh->ObjectNumber = nativeMesh->staticNumber;
mesh->Color = nativeMesh->color;
- mesh->OriginalVisibilityBox = StaticObjects[mesh->ObjectNumber].visibilityBox;
+ mesh->OriginalSphere = Statics[mesh->ObjectNumber].visibilityBox.ToLocalBoundingSphere();
mesh->Pose = nativeMesh->pos;
mesh->Scale = nativeMesh->scale;
mesh->Update();
@@ -482,15 +485,15 @@ namespace TEN::Renderer
if (!(nativeMesh->flags & StaticMeshFlags::SM_VISIBLE))
continue;
- if (!_staticObjects[mesh->ObjectNumber].has_value())
+ if (!_staticObjects[Statics.GetIndex(mesh->ObjectNumber)].has_value())
continue;
- auto& obj = *_staticObjects[mesh->ObjectNumber];
+ auto& obj = GetStaticRendererObject(mesh->ObjectNumber);
if (obj.ObjectMeshes.empty())
continue;
- if (!renderView.Camera.Frustum.SphereInFrustum(mesh->VisibilityBox.Center, mesh->VisibilitySphereRadius))
+ if (!renderView.Camera.Frustum.SphereInFrustum(mesh->Sphere.Center, mesh->Sphere.Radius))
continue;
// Collect the lights
@@ -854,11 +857,11 @@ namespace TEN::Renderer
newEffect->PrevScale = newEffect->Scale;
}
- newEffect->InterpolatedPosition = Vector3::Lerp(newEffect->PrevPosition, newEffect->Position, _interpolationFactor);
- newEffect->InterpolatedTranslation = Matrix::Lerp(newEffect->PrevTranslation, newEffect->Translation, _interpolationFactor);
- newEffect->InterpolatedRotation = Matrix::Lerp(newEffect->InterpolatedRotation, newEffect->Rotation, _interpolationFactor);
- newEffect->InterpolatedWorld = Matrix::Lerp(newEffect->PrevWorld, newEffect->World, _interpolationFactor);
- newEffect->InterpolatedScale = Matrix::Lerp(newEffect->PrevScale, newEffect->Scale, _interpolationFactor);
+ newEffect->InterpolatedPosition = Vector3::Lerp(newEffect->PrevPosition, newEffect->Position, GetInterpolationFactor());
+ newEffect->InterpolatedTranslation = Matrix::Lerp(newEffect->PrevTranslation, newEffect->Translation, GetInterpolationFactor());
+ newEffect->InterpolatedRotation = Matrix::Lerp(newEffect->InterpolatedRotation, newEffect->Rotation, GetInterpolationFactor());
+ newEffect->InterpolatedWorld = Matrix::Lerp(newEffect->PrevWorld, newEffect->World, GetInterpolationFactor());
+ newEffect->InterpolatedScale = Matrix::Lerp(newEffect->PrevScale, newEffect->Scale, GetInterpolationFactor());
CollectLightsForEffect(fx->roomNumber, newEffect);
@@ -868,31 +871,31 @@ namespace TEN::Renderer
void Renderer::ResetItems()
{
- for (int i = 0; i < ITEM_COUNT_MAX; i++)
- _items[i].DoneAnimations = false;
+ for (auto& item : _items)
+ item.DoneAnimations = false;
}
void Renderer::SaveOldState()
{
- for (int i = 0; i < g_Level.Items.size(); i++)
+ for (auto& item : _items)
{
- _items[i].PrevPosition = _items[i].Position;
- _items[i].PrevWorld = _items[i].World;
- _items[i].PrevTranslation = _items[i].Translation;
- _items[i].PrevRotation = _items[i].Rotation;
- _items[i].PrevScale = _items[i].Scale;
+ item.PrevPosition = item.Position;
+ item.PrevWorld = item.World;
+ item.PrevTranslation = item.Translation;
+ item.PrevRotation = item.Rotation;
+ item.PrevScale = item.Scale;
for (int j = 0; j < MAX_BONES; j++)
- _items[i].PrevAnimTransforms[j] = _items[i].AnimTransforms[j];
+ item.PrevAnimTransforms[j] = item.AnimTransforms[j];
}
- for (int i = 0; i < ITEM_COUNT_MAX; i++)
+ for (auto& effect : _effects)
{
- _effects[i].PrevPosition = _effects[i].Position;
- _effects[i].PrevWorld = _effects[i].World;
- _effects[i].PrevTranslation = _effects[i].Translation;
- _effects[i].PrevRotation = _effects[i].Rotation;
- _effects[i].PrevScale = _effects[i].Scale;
+ effect.PrevPosition = effect.Position;
+ effect.PrevWorld = effect.World;
+ effect.PrevTranslation = effect.Translation;
+ effect.PrevRotation = effect.Rotation;
+ effect.PrevScale = effect.Scale;
}
}
}
diff --git a/TombEngine/Renderer/RendererHelper.cpp b/TombEngine/Renderer/RendererHelper.cpp
index 7a2544541..2e6a96965 100644
--- a/TombEngine/Renderer/RendererHelper.cpp
+++ b/TombEngine/Renderer/RendererHelper.cpp
@@ -313,9 +313,6 @@ namespace TEN::Renderer
auto frameData = GetFrameInterpData(*nativeItem);
UpdateAnimation(itemToDraw, moveableObj, frameData, UINT_MAX);
-
- for (int m = 0; m < obj->nmeshes; m++)
- itemToDraw->AnimTransforms[m] = itemToDraw->AnimTransforms[m];
}
void Renderer::UpdateItemAnimations(RenderView& view)
@@ -513,9 +510,9 @@ namespace TEN::Renderer
return g_Configuration.EnableHighFramerate ? (g_Renderer.GetScreenRefreshRate() / (float)FPS) : 1.0f;
}
- float Renderer::GetInterpolationFactor() const
+ float Renderer::GetInterpolationFactor(bool forceRawValue) const
{
- return _interpolationFactor;
+ return (forceRawValue || g_GameFlow->CurrentFreezeMode == FreezeMode::None) ? _interpolationFactor : 0.0f;
}
Vector2i Renderer::GetScreenResolution() const
diff --git a/TombEngine/Renderer/RendererInit.cpp b/TombEngine/Renderer/RendererInit.cpp
index 86fb7ced3..44ab3ef55 100644
--- a/TombEngine/Renderer/RendererInit.cpp
+++ b/TombEngine/Renderer/RendererInit.cpp
@@ -131,11 +131,11 @@ namespace TEN::Renderer
_lines3DToDraw = createVector(MAX_LINES_3D);
_triangles3DToDraw = createVector(MAX_TRIANGLES_3D);
- for (int i = 0; i < ITEM_COUNT_MAX; i++)
- {
- _items[i].LightsToDraw = createVector(MAX_LIGHTS_PER_ITEM);
- _effects[i].LightsToDraw = createVector(MAX_LIGHTS_PER_ITEM);
- }
+ for (auto& item : _items)
+ item.LightsToDraw = createVector(MAX_LIGHTS_PER_ITEM);
+
+ for (auto& effect : _effects)
+ effect.LightsToDraw = createVector(MAX_LIGHTS_PER_ITEM);
D3D11_BLEND_DESC blendStateDesc{};
blendStateDesc.AlphaToCoverageEnable = false;
@@ -257,7 +257,7 @@ namespace TEN::Renderer
//_tempRoomAmbientRenderTarget2 = RenderTarget2D(_device.Get(), ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE, DXGI_FORMAT_R8G8B8A8_UNORM, false, DXGI_FORMAT_D24_UNORM_S8_UINT);
//_tempRoomAmbientRenderTarget3 = RenderTarget2D(_device.Get(), ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE, DXGI_FORMAT_R8G8B8A8_UNORM, false, DXGI_FORMAT_D24_UNORM_S8_UINT);
//_tempRoomAmbientRenderTarget4 = RenderTarget2D(_device.Get(), ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE, DXGI_FORMAT_R8G8B8A8_UNORM, false, DXGI_FORMAT_D24_UNORM_S8_UINT);
-
+
_SMAAAreaTexture = Texture2D(_device.Get(), AREATEX_WIDTH, AREATEX_HEIGHT, DXGI_FORMAT_R8G8_UNORM, AREATEX_PITCH, areaTexBytes);
_SMAASearchTexture = Texture2D(_device.Get(), SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT, DXGI_FORMAT_R8_UNORM, SEARCHTEX_PITCH, searchTexBytes);
@@ -267,6 +267,9 @@ namespace TEN::Renderer
InitializeSpriteQuad();
InitializeSky();
+ _roomAmbientMapFront = RenderTarget2D(_device.Get(), ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE, DXGI_FORMAT_R8G8B8A8_UNORM, false, DXGI_FORMAT_D32_FLOAT);
+ _roomAmbientMapBack = RenderTarget2D(_device.Get(), ROOM_AMBIENT_MAP_SIZE, ROOM_AMBIENT_MAP_SIZE, DXGI_FORMAT_R8G8B8A8_UNORM, false, DXGI_FORMAT_D32_FLOAT);
+
_sortedPolygonsVertices.reserve(MAX_TRANSPARENT_VERTICES);
_sortedPolygonsIndices.reserve(MAX_TRANSPARENT_VERTICES);
_sortedPolygonsVertexBuffer = VertexBuffer(_device.Get(), MAX_TRANSPARENT_VERTICES, _sortedPolygonsVertices);
diff --git a/TombEngine/Renderer/RendererLara.cpp b/TombEngine/Renderer/RendererLara.cpp
index b1e29ebe5..75dc4e55b 100644
--- a/TombEngine/Renderer/RendererLara.cpp
+++ b/TombEngine/Renderer/RendererLara.cpp
@@ -331,6 +331,8 @@ void TEN::Renderer::Renderer::DrawLara(RenderView& view, RendererPass rendererPa
void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass)
{
+ bool forceValue = g_GameFlow->CurrentFreezeMode == FreezeMode::Player;
+
for (int i = 0; i < HairEffect.Units.size(); i++)
{
const auto& unit = HairEffect.Units[i];
@@ -351,9 +353,9 @@ void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, Render
const auto& segment = unit.Segments[i];
auto worldMatrix =
Matrix::CreateFromQuaternion(
- Quaternion::Lerp(segment.PrevOrientation, segment.Orientation, _interpolationFactor)) *
+ Quaternion::Lerp(segment.PrevOrientation, segment.Orientation, GetInterpolationFactor(forceValue))) *
Matrix::CreateTranslation(
- Vector3::Lerp(segment.PrevPosition, segment.Position, _interpolationFactor));
+ Vector3::Lerp(segment.PrevPosition, segment.Position, GetInterpolationFactor(forceValue)));
_stItem.BonesMatrices[i + 1] = worldMatrix;
_stItem.BoneLightModes[i] = (int)LightMode::Dynamic;
diff --git a/TombEngine/Renderer/RendererPostProcess.cpp b/TombEngine/Renderer/RendererPostProcess.cpp
index 1c00be4a8..656a6e90b 100644
--- a/TombEngine/Renderer/RendererPostProcess.cpp
+++ b/TombEngine/Renderer/RendererPostProcess.cpp
@@ -4,7 +4,7 @@
namespace TEN::Renderer
{
- void Renderer::DrawPostprocess(RenderTarget2D* renderTarget, RenderView& view)
+ void Renderer::DrawPostprocess(RenderTarget2D* renderTarget, RenderView& view, SceneRenderMode renderMode)
{
SetBlendMode(BlendMode::Opaque);
SetCullMode(CullMode::CounterClockwise);
@@ -12,10 +12,13 @@ namespace TEN::Renderer
_context->RSSetViewports(1, &view.Viewport);
ResetScissor();
+ float screenFadeFactor = renderMode == SceneRenderMode::Full ? ScreenFadeCurrent : 1.0f;
+ float cinematicBarsHeight = renderMode == SceneRenderMode::Full ? Smoothstep(CinematicBarsHeight) * SPOTCAM_CINEMATIC_BARS_HEIGHT : 0.0f;
+
+ _stPostProcessBuffer.ScreenFadeFactor = screenFadeFactor;
+ _stPostProcessBuffer.CinematicBarsHeight = cinematicBarsHeight;
_stPostProcessBuffer.ViewportWidth = _screenWidth;
_stPostProcessBuffer.ViewportHeight = _screenHeight;
- _stPostProcessBuffer.ScreenFadeFactor = ScreenFadeCurrent;
- _stPostProcessBuffer.CinematicBarsHeight = Smoothstep(CinematicBarsHeight) * SPOTCAM_CINEMATIC_BARS_HEIGHT;
_stPostProcessBuffer.EffectStrength = _postProcessStrength;
_stPostProcessBuffer.Tint = _postProcessTint;
_cbPostProcessBuffer.UpdateData(_stPostProcessBuffer, _context.Get());
diff --git a/TombEngine/Renderer/RendererSettings.cpp b/TombEngine/Renderer/RendererSettings.cpp
index cad687213..1f137f9a1 100644
--- a/TombEngine/Renderer/RendererSettings.cpp
+++ b/TombEngine/Renderer/RendererSettings.cpp
@@ -64,8 +64,10 @@ namespace TEN::Renderer
texture = Texture2D();
if (std::filesystem::is_regular_file(path))
+ {
texture = Texture2D(_device.Get(), path);
- else
+ }
+ else if (!path.empty()) // Loading default texture without path may be intentional.
{
std::wstring_convert, wchar_t> converter;
TENLog("Texture file not found: " + converter.to_bytes(path), LogLevel::Warning);
diff --git a/TombEngine/Renderer/RendererString.cpp b/TombEngine/Renderer/RendererString.cpp
index 7ed550a34..41bc05c41 100644
--- a/TombEngine/Renderer/RendererString.cpp
+++ b/TombEngine/Renderer/RendererString.cpp
@@ -40,21 +40,22 @@ namespace TEN::Renderer
float fontSpacing = _gameFont->GetLineSpacing();
float fontScale = REFERENCE_FONT_SIZE / fontSpacing;
- auto stringLines = SplitString(string);
+ auto stringLines = SplitString(TEN::Utils::ToWString(string));
float yOffset = 0.0f;
for (const auto& line : stringLines)
{
// Prepare structure for renderer.
RendererStringToDraw rString;
- rString.String = TEN::Utils::ToWString(line);
+ rString.String = line;
rString.Flags = flags;
rString.X = 0;
rString.Y = 0;
- rString.Color = color.ToVector3();
+ rString.Color = color;
rString.Scale = (uiScale * fontScale) * scale;
// Measure string.
- auto size = Vector2(_gameFont->MeasureString(rString.String.c_str())) * rString.Scale;
+ auto size = line.empty() ? Vector2(0, fontSpacing * rString.Scale) : Vector2(_gameFont->MeasureString(line.c_str())) * rString.Scale;
+
if (flags & (int)PrintStringFlags::Center)
{
rString.X = (pos.x * factor.x) - (size.x / 2.0f);
@@ -66,7 +67,7 @@ namespace TEN::Renderer
else
{
// Calculate indentation to account for string scaling.
- auto indent = _gameFont->FindGlyph(line.at(0))->XAdvance * rString.Scale;
+ auto indent = line.empty() ? 0 : _gameFont->FindGlyph(line.at(0))->XAdvance * rString.Scale;
rString.X = pos.x * factor.x + indent;
}
@@ -89,8 +90,12 @@ namespace TEN::Renderer
void Renderer::DrawAllStrings()
{
- float shadowOffset = 1.5f / (REFERENCE_FONT_SIZE / _gameFont->GetLineSpacing());
+ if (_stringsToDraw.empty())
+ return;
+ SetBlendMode(BlendMode::AlphaBlend);
+
+ float shadowOffset = 1.5f / (REFERENCE_FONT_SIZE / _gameFont->GetLineSpacing());
_spriteBatch->Begin();
for (const auto& rString : _stringsToDraw)
@@ -101,7 +106,7 @@ namespace TEN::Renderer
_gameFont->DrawString(
_spriteBatch.get(), rString.String.c_str(),
Vector2(rString.X + shadowOffset * rString.Scale, rString.Y + shadowOffset * rString.Scale),
- Vector4(0.0f, 0.0f, 0.0f, 1.0f) * ScreenFadeCurrent,
+ Vector4(0.0f, 0.0f, 0.0f, rString.Color.w) * ScreenFadeCurrent,
0.0f, Vector4::Zero, rString.Scale);
}
@@ -109,7 +114,7 @@ namespace TEN::Renderer
_gameFont->DrawString(
_spriteBatch.get(), rString.String.c_str(),
Vector2(rString.X, rString.Y),
- Vector4(rString.Color.x, rString.Color.y, rString.Color.z, 1.0f) * ScreenFadeCurrent,
+ (rString.Color * rString.Color.w) * ScreenFadeCurrent,
0.0f, Vector4::Zero, rString.Scale);
}
diff --git a/TombEngine/Renderer/Structures/RendererItem.h b/TombEngine/Renderer/Structures/RendererItem.h
index 0924ec4bb..0c9774d69 100644
--- a/TombEngine/Renderer/Structures/RendererItem.h
+++ b/TombEngine/Renderer/Structures/RendererItem.h
@@ -8,8 +8,8 @@ namespace TEN::Renderer::Structures
{
struct RendererItem
{
- int ItemNumber = 0;
- int ObjectID = 0;
+ int ItemNumber = NO_VALUE;
+ int ObjectID = NO_VALUE;
Vector3 Position = Vector3::Zero;
int RoomNumber = NO_VALUE;
diff --git a/TombEngine/Renderer/Structures/RendererStatic.h b/TombEngine/Renderer/Structures/RendererStatic.h
index 5e7e4f57c..03ae26f0f 100644
--- a/TombEngine/Renderer/Structures/RendererStatic.h
+++ b/TombEngine/Renderer/Structures/RendererStatic.h
@@ -22,19 +22,17 @@ namespace TEN::Renderer::Structures
std::vector LightsToDraw;
std::vector CachedRoomLights;
bool CacheLights;
- GameBoundingBox OriginalVisibilityBox;
- BoundingOrientedBox VisibilityBox;
+ BoundingSphere OriginalSphere;
+ BoundingSphere Sphere;
float Scale;
- float VisibilitySphereRadius;
void Update()
{
World = (Pose.Orientation.ToRotationMatrix() *
Matrix::CreateScale(Scale) *
Matrix::CreateTranslation(Pose.Position.x, Pose.Position.y, Pose.Position.z));
+ Sphere = BoundingSphere(Vector3::Transform(OriginalSphere.Center, World), OriginalSphere.Radius * Scale);
CacheLights = true;
- VisibilityBox = OriginalVisibilityBox.ToBoundingOrientedBox(Pose);
- VisibilitySphereRadius = Vector3(VisibilityBox.Extents).Length() * Scale;
}
};
}
diff --git a/TombEngine/Renderer/Structures/RendererStringToDraw.h b/TombEngine/Renderer/Structures/RendererStringToDraw.h
index b04aad266..cfe278480 100644
--- a/TombEngine/Renderer/Structures/RendererStringToDraw.h
+++ b/TombEngine/Renderer/Structures/RendererStringToDraw.h
@@ -11,7 +11,7 @@ namespace TEN::Renderer::Structures
float Y;
int Flags;
std::wstring String;
- Vector3 Color;
+ Vector4 Color;
float Scale;
};
}
diff --git a/TombEngine/Resources.rc b/TombEngine/Resources.rc
index 908593e12..0fa0b9c9e 100644
--- a/TombEngine/Resources.rc
+++ b/TombEngine/Resources.rc
@@ -3,6 +3,7 @@
#pragma code_page(65001)
#include "resource.h"
+#include "Version.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
@@ -26,8 +27,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,5,0,2
- PRODUCTVERSION 1,7,2,0
+ FILEVERSION TEN_MAJOR_VERSION,TEN_MINOR_VERSION,TEN_BUILD_NUMBER,TEN_REVISION_NUMBER
+ PRODUCTVERSION TE_MAJOR_VERSION,TE_MINOR_VERSION,TE_BUILD_NUMBER,TE_REVISION_NUMBER
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,12 +44,12 @@ BEGIN
BLOCK "000904b0"
BEGIN
VALUE "CompanyName", "Tomb Engine Development Community"
- VALUE "FileVersion", "1.5.0.2"
VALUE "InternalName", "TombEngine.exe"
VALUE "LegalCopyright", "Copyright (c) 2024"
VALUE "OriginalFilename", "TombEngine.exe"
VALUE "ProductName", "Tomb Engine"
- VALUE "ProductVersion", "1.7.2.0"
+ VALUE "FileVersion", TEN_VERSION_STRING
+ VALUE "ProductVersion", TE_VERSION_STRING
END
END
BLOCK "VarFileInfo"
@@ -64,7 +65,8 @@ END
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
-IDI_ICON1 ICON "ten.ico"
+IDI_ICON1 ICON "Resources\\Ten.ico"
+IDR_TITLELEVEL BIN "Resources\\Title.bin"
/////////////////////////////////////////////////////////////////////////////
diff --git a/TombEngine/ten.ico b/TombEngine/Resources/ten.ico
similarity index 100%
rename from TombEngine/ten.ico
rename to TombEngine/Resources/ten.ico
diff --git a/TombEngine/Resources/title.bin b/TombEngine/Resources/title.bin
new file mode 100644
index 000000000..9475b7db0
Binary files /dev/null and b/TombEngine/Resources/title.bin differ
diff --git a/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h b/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h
index 4a3f67354..e09b79037 100644
--- a/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h
+++ b/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h
@@ -1,19 +1,17 @@
#pragma once
+
#include "Game/control/control.h"
+#include "Scripting/Internal/TEN/Flow/Settings/Settings.h"
class ScriptInterfaceLevel;
-enum class TitleType
-{
- Flyby,
- Background
-};
-
class ScriptInterfaceFlowHandler
{
public:
- GameStatus LastGameStatus = GameStatus::Normal;
- TitleType TitleType = TitleType::Flyby;
+ GameStatus LastGameStatus = GameStatus::Normal;
+ FreezeMode LastFreezeMode = FreezeMode::None;
+ FreezeMode CurrentFreezeMode = FreezeMode::None;
+
std::string IntroImagePath = {};
std::string TitleScreenImagePath = {};
@@ -25,6 +23,7 @@ public:
virtual ~ScriptInterfaceFlowHandler() = default;
virtual void LoadFlowScript() = 0;
+ virtual Settings* GetSettings() = 0;
virtual void SetGameDir(const std::string& assetDir) = 0;
virtual std::string GetGameDir() = 0;
diff --git a/TombEngine/Scripting/Include/ScriptInterfaceGame.h b/TombEngine/Scripting/Include/ScriptInterfaceGame.h
index 81958aaaf..75510183a 100644
--- a/TombEngine/Scripting/Include/ScriptInterfaceGame.h
+++ b/TombEngine/Scripting/Include/ScriptInterfaceGame.h
@@ -58,6 +58,7 @@ public:
virtual void OnSave() = 0;
virtual void OnEnd(GameStatus reason) = 0;
virtual void OnUseItem(GAME_OBJECT_ID objectNumber) = 0;
+ virtual void OnFreeze() = 0;
virtual void ShortenTENCalls() = 0;
virtual void FreeLevelScripts() = 0;
@@ -80,7 +81,11 @@ public:
std::vector& preLoad,
std::vector& postLoad,
std::vector& preLoop,
- std::vector& postLoop) const = 0;
+ std::vector& postLoop,
+ std::vector& preUseItem,
+ std::vector& postUseItem,
+ std::vector& preBreak,
+ std::vector& postBreak) const = 0;
virtual void SetCallbackStrings(
const std::vector& preStart,
@@ -92,7 +97,11 @@ public:
const std::vector& preLoad,
const std::vector& postLoad,
const std::vector& preLoop,
- const std::vector& postLoop) = 0;
+ const std::vector& postLoop,
+ const std::vector& preUseItem,
+ const std::vector& postUseItem,
+ const std::vector& preBreak,
+ const std::vector& postBreak) = 0;
};
extern ScriptInterfaceGame* g_GameScript;
diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h
index 42022655e..dcf278189 100644
--- a/TombEngine/Scripting/Internal/ReservedScriptNames.h
+++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h
@@ -66,10 +66,14 @@ static constexpr char ScriptReserved_PreSave[] = "PRESAVE";
static constexpr char ScriptReserved_PostSave[] = "POSTSAVE";
static constexpr char ScriptReserved_PreLoad[] = "PRELOAD";
static constexpr char ScriptReserved_PostLoad[] = "POSTLOAD";
+static constexpr char ScriptReserved_PreControlPhase[] = "PRECONTROLPHASE"; // DEPRECATED
+static constexpr char ScriptReserved_PostControlPhase[] = "POSTCONTROLPHASE"; // DEPRECATED
static constexpr char ScriptReserved_PreLoop[] = "PRELOOP";
static constexpr char ScriptReserved_PostLoop[] = "POSTLOOP";
-static constexpr char ScriptReserved_PreControlPhase[] = "PRECONTROLPHASE";
-static constexpr char ScriptReserved_PostControlPhase[] = "POSTCONTROLPHASE";
+static constexpr char ScriptReserved_PreUseItem[] = "PREUSEITEM";
+static constexpr char ScriptReserved_PostUseItem[] = "POSTUSEITEM";
+static constexpr char ScriptReserved_PreFreeze[] = "PREFREEZE";
+static constexpr char ScriptReserved_PostFreeze[] = "POSTFREEZE";
// Built-in LevelFuncs
static constexpr char ScriptReserved_OnStart[] = "OnStart";
@@ -79,6 +83,7 @@ static constexpr char ScriptReserved_OnControlPhase[] = "OnControlPhase"; // DEP
static constexpr char ScriptReserved_OnSave[] = "OnSave";
static constexpr char ScriptReserved_OnEnd[] = "OnEnd";
static constexpr char ScriptReserved_OnUseItem[] = "OnUseItem";
+static constexpr char ScriptReserved_OnFreeze[] = "OnFreeze";
// Event types (volume events + global events)
static constexpr char ScriptReserved_EventOnEnter[] = "ENTER";
@@ -90,12 +95,15 @@ static constexpr char ScriptReserved_EventOnLoop[] = "LOOP";
static constexpr char ScriptReserved_EventOnSave[] = "SAVE";
static constexpr char ScriptReserved_EventOnEnd[] = "END";
static constexpr char ScriptReserved_EventOnUseItem[] = "USEITEM";
+static constexpr char ScriptReserved_EventOnFreeze[] = "FREEZE";
// Member functions
static constexpr char ScriptReserved_New[] = "New";
static constexpr char ScriptReserved_Init[] = "Init";
static constexpr char ScriptReserved_Enable[] = "Enable";
static constexpr char ScriptReserved_Disable[] = "Disable";
+static constexpr char ScriptReserved_GetCollidable[] = "GetCollidable";
+static constexpr char ScriptReserved_SetCollidable[] = "SetCollidable";
static constexpr char ScriptReserved_MakeInvisible[] = "MakeInvisible";
static constexpr char ScriptReserved_SetVisible[] = "SetVisible";
static constexpr char ScriptReserved_Explode[] = "Explode";
@@ -203,6 +211,7 @@ static constexpr char ScriptReserved_IsTagPresent[] = "IsTagPresent";
static constexpr char ScriptReserved_AddLevel[] = "AddLevel";
static constexpr char ScriptReserved_GetLevel[] = "GetLevel";
static constexpr char ScriptReserved_GetCurrentLevel[] = "GetCurrentLevel";
+static constexpr char ScriptReserved_GetNextLevel[] = "GetNextLevel";
static constexpr char ScriptReserved_SetIntroImagePath[] = "SetIntroImagePath";
static constexpr char ScriptReserved_SetTitleScreenImagePath[] = "SetTitleScreenImagePath";
static constexpr char ScriptReserved_SetFarView[] = "SetFarView";
@@ -210,6 +219,8 @@ static constexpr char ScriptReserved_SetSettings[] = "SetSettings";
static constexpr char ScriptReserved_SetAnimations[] = "SetAnimations";
static constexpr char ScriptReserved_EndLevel[] = "EndLevel";
static constexpr char ScriptReserved_GetGameStatus[] = "GetGameStatus";
+static constexpr char ScriptReserved_SetFreezeMode[] = "SetFreezeMode";
+static constexpr char ScriptReserved_GetFreezeMode[] = "GetFreezeMode";
static constexpr char ScriptReserved_SaveGame[] = "SaveGame";
static constexpr char ScriptReserved_LoadGame[] = "LoadGame";
static constexpr char ScriptReserved_DeleteSaveGame[] = "DeleteSaveGame";
@@ -238,11 +249,13 @@ static constexpr char ScriptReserved_LaraType[] = "LaraType";
static constexpr char ScriptReserved_RotationAxis[] = "RotationAxis";
static constexpr char ScriptReserved_ItemAction[] = "ItemAction";
static constexpr char ScriptReserved_ErrorMode[] = "ErrorMode";
+static constexpr char ScriptReserved_FastReload[] = "FastReload";
static constexpr char ScriptReserved_InventoryItem[] = "InventoryItem";
static constexpr char ScriptReserved_LaraWeaponType[] = "LaraWeaponType";
static constexpr char ScriptReserved_PlayerAmmoType[] = "PlayerAmmoType";
static constexpr char ScriptReserved_HandStatus[] = "HandStatus";
static constexpr char ScriptReserved_GameStatus[] = "GameStatus";
+static constexpr char ScriptReserved_FreezeMode[] = "FreezeMode";
// Functions
static constexpr char ScriptReserved_ShowString[] = "ShowString";
@@ -297,6 +310,7 @@ static constexpr char ScriptReserved_EmitBlood[] = "EmitBlood";
static constexpr char ScriptReserved_EmitFire[] = "EmitFire";
static constexpr char ScriptReserved_MakeExplosion[] = "MakeExplosion";
static constexpr char ScriptReserved_MakeEarthquake[] = "MakeEarthquake";
+static constexpr char ScriptReserved_GetWind[] = "GetWind";
static constexpr char ScriptReserved_Vibrate[] = "Vibrate";
static constexpr char ScriptReserved_FlashScreen[] = "FlashScreen";
static constexpr char ScriptReserved_FadeIn[] = "FadeIn";
diff --git a/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp b/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp
index 9514e38e8..e3404b00e 100644
--- a/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp
+++ b/TombEngine/Scripting/Internal/TEN/Effects/EffectsFunctions.cpp
@@ -10,6 +10,7 @@
#include "Game/effects/explosion.h"
#include "Game/effects/spark.h"
#include "Game/effects/tomb4fx.h"
+#include "Game/effects/weather.h"
#include "Game/Setup.h"
#include "Objects/Utils/object_helper.h"
#include "Scripting/Internal/LuaHandler.h"
@@ -31,6 +32,7 @@ Functions to generate effects.
using namespace TEN::Effects::DisplaySprite;
using namespace TEN::Effects::Electricity;
+using namespace TEN::Effects::Environment;
using namespace TEN::Effects::Explosion;
using namespace TEN::Effects::Spark;
@@ -308,6 +310,15 @@ namespace TEN::Scripting::Effects
Camera.bounce = -str;
}
+ /// Get the wind vector for the current game frame.
+ // This represents the 3D displacement applied by the engine on things like particles affected by wind.
+ // @function GetWind()
+ // @treturn Vec3 Wind vector.
+ static Vec3 GetWind()
+ {
+ return Vec3(Weather.Wind());
+ }
+
void Register(sol::state* state, sol::table& parent)
{
auto tableEffects = sol::table(state->lua_state(), sol::create);
@@ -321,6 +332,7 @@ namespace TEN::Scripting::Effects
tableEffects.set_function(ScriptReserved_MakeExplosion, &MakeExplosion);
tableEffects.set_function(ScriptReserved_EmitFire, &EmitFire);
tableEffects.set_function(ScriptReserved_MakeEarthquake, &Earthquake);
+ tableEffects.set_function(ScriptReserved_GetWind, &GetWind);
auto handler = LuaHandler{ state };
handler.MakeReadOnlyTable(tableEffects, ScriptReserved_BlendID, BLEND_IDS);
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Enums/FreezeModes.h b/TombEngine/Scripting/Internal/TEN/Flow/Enums/FreezeModes.h
new file mode 100644
index 000000000..368993ce8
--- /dev/null
+++ b/TombEngine/Scripting/Internal/TEN/Flow/Enums/FreezeModes.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "Game/control/control.h"
+
+/***
+Constants for freeze modes.
+@enum Flow.FreezeMode
+@pragma nostrip
+*/
+
+/*** Flow.FreezeMode constants.
+
+The following constants are inside Flow.FreezeMode.
+
+ NONE - Normal in-game operation.
+ FULL - Game is completely frozen, as in pause or inventory menus.
+ SPECTATOR - Game is completely frozen, but with ability to control camera.
+ PLAYER - Game is completely frozen, but with ability to control player. Experimental.
+
+@section Flow.FreezeMode
+*/
+
+/*** Table of freeze modes.
+@table CONSTANT_STRING_HERE
+*/
+
+static const auto FREEZE_MODES = std::unordered_map
+{
+ { "NONE", FreezeMode::None },
+ { "FULL", FreezeMode::Full },
+ { "SPECTATOR", FreezeMode::Spectator },
+ { "PLAYER", FreezeMode::Player }
+};
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/GameStatuses.h b/TombEngine/Scripting/Internal/TEN/Flow/Enums/GameStatuses.h
similarity index 100%
rename from TombEngine/Scripting/Internal/TEN/Flow/GameStatuses.h
rename to TombEngine/Scripting/Internal/TEN/Flow/Enums/GameStatuses.h
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp
index af4c9a948..70bc3f86d 100644
--- a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp
+++ b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp
@@ -8,7 +8,8 @@
#include "Scripting/Include/Objects/ScriptInterfaceObjectsHandler.h"
#include "Scripting/Include/Strings/ScriptInterfaceStringsHandler.h"
#include "Scripting/Internal/ReservedScriptNames.h"
-#include "Scripting/Internal/TEN/Flow/GameStatuses.h"
+#include "Scripting/Internal/TEN/Flow/Enums/FreezeModes.h"
+#include "Scripting/Internal/TEN/Flow/Enums/GameStatuses.h"
#include "Scripting/Internal/TEN/Flow/InventoryItem/InventoryItem.h"
#include "Scripting/Internal/TEN/Logic/LevelFunc.h"
#include "Scripting/Internal/TEN/Vec2/Vec2.h"
@@ -125,6 +126,11 @@ have an ID of 0, the second an ID of 1, and so on.
*/
tableFlow.set_function(ScriptReserved_GetCurrentLevel, &FlowHandler::GetCurrentLevel, this);
+ /// Returns the level that is about to load. If no new level is about to load, returns current level.
+ // @function GetNextLevel
+ // @treturn Flow.Level incoming new level or current level, if no new level is loading
+ tableFlow.set_function(ScriptReserved_GetNextLevel, &FlowHandler::GetNextLevel, this);
+
/***
Finishes the current level, with optional level index and start position index provided.
If level index is not provided or is zero, jumps to next level. If level index is more than
@@ -143,6 +149,22 @@ Get current game status, such as normal game loop, exiting to title, etc.
*/
tableFlow.set_function(ScriptReserved_GetGameStatus, &FlowHandler::GetGameStatus, this);
+/***
+Get current freeze mode, such as none, full, spectator or player.
+@function GetFreezeMode
+@treturn Flow.FreezeMode the current freeze mode
+*/
+ tableFlow.set_function(ScriptReserved_GetFreezeMode, &FlowHandler::GetFreezeMode, this);
+
+/***
+Set current freeze mode, such as none, full, spectator or player.
+Freeze mode specifies whether game is in normal mode or paused in a particular way to allow
+custom menu creation, photo mode or time freeze.
+@function SetFreezeMode
+@tparam Flow.FreezeMode new freeze mode to set.
+*/
+ tableFlow.set_function(ScriptReserved_SetFreezeMode, &FlowHandler::SetFreezeMode, this);
+
/***
Save the game to a savegame slot.
@function SaveGame
@@ -281,6 +303,7 @@ Specify which translations in the strings table correspond to which languages.
_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_ItemAction, ITEM_MENU_ACTIONS);
_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_ErrorMode, ERROR_MODES);
_handler.MakeReadOnlyTable(tableFlow, ScriptReserved_GameStatus, GAME_STATUSES);
+ _handler.MakeReadOnlyTable(tableFlow, ScriptReserved_FreezeMode, FREEZE_MODES);
}
FlowHandler::~FlowHandler()
@@ -399,6 +422,15 @@ Level* FlowHandler::GetCurrentLevel()
return Levels[CurrentLevel];
}
+Level* FlowHandler::GetNextLevel()
+{
+ if (NextLevel == CurrentLevel)
+ return Levels[CurrentLevel];
+
+ // NOTE: Negative value indicates incoming savegame.
+ return Levels[abs(NextLevel)];
+}
+
int FlowHandler::GetNumLevels() const
{
return (int)Levels.size();
@@ -466,6 +498,16 @@ GameStatus FlowHandler::GetGameStatus()
return this->LastGameStatus;
}
+FreezeMode FlowHandler::GetFreezeMode()
+{
+ return this->CurrentFreezeMode;
+}
+
+void FlowHandler::SetFreezeMode(FreezeMode mode)
+{
+ this->CurrentFreezeMode = mode;
+}
+
void FlowHandler::FlipMap(int group)
{
DoFlipMap(group);
@@ -701,7 +743,6 @@ bool FlowHandler::DoFlow()
case GameStatus::NewGame:
// NOTE: 0 reserved for title level and 1 reserved for home level.
CurrentLevel = (SelectedLevelForNewGame != 0) ? SelectedLevelForNewGame : (IsHomeLevelEnabled() ? 2 : 1);
-
RequiredStartPos = 0;
SelectedLevelForNewGame = 0;
InitializeGame = true;
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h
index 55092b97d..06f881412 100644
--- a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h
+++ b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.h
@@ -59,10 +59,13 @@ public:
Settings* GetSettings();
Level* GetLevel(int id);
Level* GetCurrentLevel();
+ Level* GetNextLevel();
int GetLevelNumber(const std::string& flieName);
int GetNumLevels() const;
void EndLevel(std::optional nextLevel, std::optional startPosIndex);
GameStatus GetGameStatus();
+ FreezeMode GetFreezeMode();
+ void SetFreezeMode(FreezeMode mode);
void FlipMap(int group);
bool GetFlipMapStatus(std::optional group);
void SaveGame(int slot);
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp b/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp
index ed5fe3145..77517676c 100644
--- a/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp
+++ b/TombEngine/Scripting/Internal/TEN/Flow/Level/FlowLevel.cpp
@@ -25,7 +25,8 @@ void Level::Register(sol::table& parent)
sol::constructors(),
sol::call_constructor, sol::constructors(),
- // Corresponds to an entry in strings.lua.
+/// (string) string key for the level's (localised) name.
+// Corresponds to an entry in strings.lua.
//@mem nameKey
"nameKey", &Level::NameStringKey,
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp
index bcb0e5de2..af8846404 100644
--- a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp
+++ b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp
@@ -1,19 +1,19 @@
#include "framework.h"
-#include "Settings.h"
+#include "Scripting/Internal/TEN/Flow/Settings/Settings.h"
-/***
-Settings that will be run on game startup.
-@tenclass Flow.Settings
-@pragma nostrip
-*/
+/// Settings that will be run on game startup.
+// @tenclass Flow.Settings
+// @pragma nostrip
-void Settings::Register(sol::table & parent)
+void Settings::Register(sol::table& parent)
{
- parent.new_usertype("Settings",
+ parent.new_usertype(
+ "Settings",
sol::constructors(),
sol::call_constructor, sol::constructors(),
/*** How should the application respond to script errors?
+
Must be one of the following:
`ErrorMode.TERMINATE` - print to the log file and return to the title level when any script error is hit.
This is the one you will want to go for if you want to know IMMEDIATELY if something has gone wrong.
@@ -31,6 +31,14 @@ has an unrecoverable error, the game will close.
@mem errorMode
*/
- "errorMode", &Settings::ErrorMode
- );
+ "errorMode", &Settings::ErrorMode,
+
+/// Can the game utilize the fast reload feature?
+//
+// When set to `true`, the game will attempt to perform fast savegame reloading if current level is the same as
+// the level loaded from the savegame. It will not work if the level timestamp or checksum has changed
+// (i.e. level was updated). If set to `false`, this functionality is turned off.
+//
+// @mem fastReload
+ "fastReload", &Settings::FastReload);
}
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h
index e1988f09f..62dc474a8 100644
--- a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h
+++ b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h
@@ -1,22 +1,20 @@
#pragma once
#include "Scripting/Internal/ScriptAssert.h"
-#include
-static const std::unordered_map ERROR_MODES {
- {"SILENT", ErrorMode::Silent},
- {"WARN", ErrorMode::Warn},
- {"TERMINATE", ErrorMode::Terminate}
+namespace sol { class state; }
+
+static const std::unordered_map ERROR_MODES
+{
+ { "SILENT", ErrorMode::Silent },
+ { "WARN", ErrorMode::Warn },
+ { "TERMINATE", ErrorMode::Terminate }
};
-namespace sol {
- class state;
-}
-
struct Settings
{
- ErrorMode ErrorMode;
+ ErrorMode ErrorMode = ErrorMode::Warn;
+ bool FastReload = true;
- static void Register(sol::table & parent);
+ static void Register(sol::table& parent);
};
-
diff --git a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp
index 9b96d12d7..bda6d6213 100644
--- a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp
+++ b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.cpp
@@ -36,7 +36,11 @@ enum class CallbackPoint
PreSave,
PostSave,
PreEnd,
- PostEnd
+ PostEnd,
+ PreUseItem,
+ PostUseItem,
+ PreFreeze,
+ PostFreeze
};
static const std::unordered_map CALLBACK_POINTS
@@ -49,11 +53,14 @@ static const std::unordered_map CALLBACK_POINTS
{ ScriptReserved_PostLoop, CallbackPoint::PostLoop },
{ ScriptReserved_PreControlPhase, CallbackPoint::PreLoop }, // DEPRECATED
{ ScriptReserved_PostControlPhase, CallbackPoint::PostLoop }, // DEPRECATED
- { ScriptReserved_PostSave, CallbackPoint::PostSave },
- { ScriptReserved_PostSave, CallbackPoint::PostSave },
{ ScriptReserved_PreSave, CallbackPoint::PreSave },
+ { ScriptReserved_PostSave, CallbackPoint::PostSave },
{ ScriptReserved_PreEnd, CallbackPoint::PreEnd },
- { ScriptReserved_PostEnd, CallbackPoint::PostEnd }
+ { ScriptReserved_PostEnd, CallbackPoint::PostEnd },
+ { ScriptReserved_PreUseItem, CallbackPoint::PreUseItem },
+ { ScriptReserved_PostUseItem, CallbackPoint::PostUseItem },
+ { ScriptReserved_PreFreeze, CallbackPoint::PreFreeze },
+ { ScriptReserved_PostFreeze, CallbackPoint::PostFreeze }
};
static const std::unordered_map EVENT_TYPES
@@ -66,7 +73,8 @@ static const std::unordered_map EVENT_TYPES
{ ScriptReserved_EventOnSave, EventType::Save },
{ ScriptReserved_EventOnStart, EventType::Start },
{ ScriptReserved_EventOnEnd, EventType::End },
- { ScriptReserved_EventOnUseItem, EventType::UseItem }
+ { ScriptReserved_EventOnUseItem, EventType::UseItem },
+ { ScriptReserved_EventOnFreeze, EventType::Freeze }
};
enum class LevelEndReason
@@ -190,6 +198,10 @@ LogicHandler::LogicHandler(sol::state* lua, sol::table & parent) : m_handler{ lu
m_callbacks.insert(std::make_pair(CallbackPoint::PostSave, &m_callbacksPostSave));
m_callbacks.insert(std::make_pair(CallbackPoint::PreEnd, &m_callbacksPreEnd));
m_callbacks.insert(std::make_pair(CallbackPoint::PostEnd, &m_callbacksPostEnd));
+ m_callbacks.insert(std::make_pair(CallbackPoint::PreUseItem, &m_callbacksPreUseItem));
+ m_callbacks.insert(std::make_pair(CallbackPoint::PostUseItem, &m_callbacksPostUseItem));
+ m_callbacks.insert(std::make_pair(CallbackPoint::PreFreeze, &m_callbacksPreFreeze));
+ m_callbacks.insert(std::make_pair(CallbackPoint::PostFreeze, &m_callbacksPostFreeze));
LevelFunc::Register(tableLogic);
@@ -221,6 +233,9 @@ Possible values for `point`:
PRELOAD -- will be called immediately before OnLoad
POSTLOAD -- will be called immediately after OnLoad
+ PREFREEZE -- will be called before entering freeze mode
+ POSTFREEZE -- will be called immediately after exiting freeze mode
+
-- These take a LevelEndReason arg, like OnEnd
PREEND -- will be called immediately before OnEnd
POSTEND -- will be called immediately after OnEnd
@@ -229,6 +244,10 @@ Possible values for `point`:
PRELOOP -- will be called in the beginning of game loop
POSTLOOP -- will be called at the end of game loop
+ -- These take functions which accepts an objectNumber argument, like OnUseItem
+ PREUSEITEM -- will be called immediately before OnUseItem
+ POSTUSEITEM -- will be called immediately after OnUseItem
+
The order in which two functions with the same CallbackPoint are called is undefined.
i.e. if you register `MyFunc` and `MyFunc2` with `PRELOOP`, both will be called in the beginning of game loop, but there is no guarantee that `MyFunc` will be called before `MyFunc2`, or vice-versa.
@@ -295,6 +314,7 @@ Possible event type values:
END
LOOP
USEITEM
+ MENU
@function HandleEvent
@tparam string name Name of the event set to find.
@@ -460,6 +480,7 @@ void LogicHandler::FreeLevelScripts()
m_onSave = sol::nil;
m_onEnd = sol::nil;
m_onUseItem = sol::nil;
+ m_onBreak = sol::nil;
m_handler.GetState()->collect_garbage();
}
@@ -778,7 +799,11 @@ void LogicHandler::GetCallbackStrings(
std::vector& preLoad,
std::vector& postLoad,
std::vector& preLoop,
- std::vector& postLoop) const
+ std::vector& postLoop,
+ std::vector& preUseItem,
+ std::vector& postUseItem,
+ std::vector& preBreak,
+ std::vector& postBreak) const
{
auto populateWith = [](std::vector& dest, const std::unordered_set& src)
{
@@ -800,6 +825,12 @@ void LogicHandler::GetCallbackStrings(
populateWith(preLoop, m_callbacksPreLoop);
populateWith(postLoop, m_callbacksPostLoop);
+
+ populateWith(preUseItem, m_callbacksPreUseItem);
+ populateWith(postUseItem, m_callbacksPostUseItem);
+
+ populateWith(preBreak, m_callbacksPreFreeze);
+ populateWith(postBreak, m_callbacksPostFreeze);
}
void LogicHandler::SetCallbackStrings(
@@ -812,7 +843,11 @@ void LogicHandler::SetCallbackStrings(
const std::vector& preLoad,
const std::vector& postLoad,
const std::vector& preLoop,
- const std::vector& postLoop)
+ const std::vector& postLoop,
+ const std::vector& preUseItem,
+ const std::vector& postUseItem,
+ const std::vector& preBreak,
+ const std::vector& postBreak)
{
auto populateWith = [](std::unordered_set& dest, const std::vector& src)
{
@@ -834,6 +869,12 @@ void LogicHandler::SetCallbackStrings(
populateWith(m_callbacksPreLoop, preLoop);
populateWith(m_callbacksPostLoop, postLoop);
+
+ populateWith(m_callbacksPreUseItem, preUseItem);
+ populateWith(m_callbacksPostUseItem, postUseItem);
+
+ populateWith(m_callbacksPreFreeze, preBreak);
+ populateWith(m_callbacksPostFreeze, postBreak);
}
template
@@ -896,7 +937,7 @@ void LogicHandler::ExecuteString(const std::string& command)
// These wind up calling CallLevelFunc, which is where all error checking is.
void LogicHandler::ExecuteFunction(const std::string& name, short idOne, short idTwo)
{
- sol::protected_function func = m_levelFuncs_luaFunctions[name];
+ auto func = m_levelFuncs_luaFunctions[name];
func(std::make_unique(idOne), std::make_unique(idTwo));
}
@@ -914,28 +955,27 @@ void LogicHandler::ExecuteFunction(const std::string& name, TEN::Control::Volume
}
}
-
void LogicHandler::OnStart()
{
- for (auto& name : m_callbacksPreStart)
+ for (const auto& name : m_callbacksPreStart)
CallLevelFuncByName(name);
if (m_onStart.valid())
CallLevelFunc(m_onStart);
- for (auto& name : m_callbacksPostStart)
+ for (const auto& name : m_callbacksPostStart)
CallLevelFuncByName(name);
}
void LogicHandler::OnLoad()
{
- for (auto& name : m_callbacksPreLoad)
+ for (const auto& name : m_callbacksPreLoad)
CallLevelFuncByName(name);
if (m_onLoad.valid())
CallLevelFunc(m_onLoad);
- for (auto& name : m_callbacksPostLoad)
+ for (const auto& name : m_callbacksPostLoad)
CallLevelFuncByName(name);
}
@@ -943,7 +983,7 @@ void LogicHandler::OnLoop(float deltaTime, bool postLoop)
{
if (!postLoop)
{
- for (auto& name : m_callbacksPreLoop)
+ for (const auto& name : m_callbacksPreLoop)
CallLevelFuncByName(name, deltaTime);
lua_gc(m_handler.GetState()->lua_state(), LUA_GCCOLLECT, 0);
@@ -952,27 +992,26 @@ void LogicHandler::OnLoop(float deltaTime, bool postLoop)
}
else
{
- for (auto& name : m_callbacksPostLoop)
+ for (const auto& name : m_callbacksPostLoop)
CallLevelFuncByName(name, deltaTime);
}
}
void LogicHandler::OnSave()
{
- for (auto& name : m_callbacksPreSave)
+ for (const auto& name : m_callbacksPreSave)
CallLevelFuncByName(name);
if (m_onSave.valid())
CallLevelFunc(m_onSave);
- for (auto& name : m_callbacksPostSave)
+ for (const auto& name : m_callbacksPostSave)
CallLevelFuncByName(name);
}
void LogicHandler::OnEnd(GameStatus reason)
{
- auto endReason{LevelEndReason::Other};
-
+ auto endReason = LevelEndReason::Other;
switch (reason)
{
case GameStatus::LaraDead:
@@ -992,20 +1031,38 @@ void LogicHandler::OnEnd(GameStatus reason)
break;
}
- for (auto& name : m_callbacksPreEnd)
+ for (const auto& name : m_callbacksPreEnd)
CallLevelFuncByName(name, endReason);
- if(m_onEnd.valid())
+ if (m_onEnd.valid())
CallLevelFunc(m_onEnd, endReason);
- for (auto& name : m_callbacksPostEnd)
+ for (const auto& name : m_callbacksPostEnd)
CallLevelFuncByName(name, endReason);
}
void LogicHandler::OnUseItem(GAME_OBJECT_ID objectNumber)
{
+ for (const auto& name : m_callbacksPreUseItem)
+ CallLevelFuncByName(name, objectNumber);
+
if (m_onUseItem.valid())
CallLevelFunc(m_onUseItem, objectNumber);
+
+ for (const auto& name : m_callbacksPostUseItem)
+ CallLevelFuncByName(name, objectNumber);
+}
+
+void LogicHandler::OnFreeze()
+{
+ for (const auto& name : m_callbacksPreFreeze)
+ CallLevelFuncByName(name);
+
+ if (m_onBreak.valid())
+ CallLevelFunc(m_onBreak);
+
+ for (const auto& name : m_callbacksPostFreeze)
+ CallLevelFuncByName(name);
}
/*** Special tables
@@ -1152,4 +1209,5 @@ void LogicHandler::InitCallbacks()
assignCB(m_onSave, ScriptReserved_OnSave);
assignCB(m_onEnd, ScriptReserved_OnEnd);
assignCB(m_onUseItem, ScriptReserved_OnUseItem);
+ assignCB(m_onBreak, ScriptReserved_OnFreeze);
}
diff --git a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h
index c1f4f2108..51fc08a40 100644
--- a/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h
+++ b/TombEngine/Scripting/Internal/TEN/Logic/LogicHandler.h
@@ -51,23 +51,28 @@ private:
// "LevelFuncs.MyLevel.CoolFuncs"
std::unordered_map> m_levelFuncs_tablesOfNames{};
+ std::unordered_set m_callbacksPreStart;
+ std::unordered_set m_callbacksPostStart;
+ std::unordered_set m_callbacksPreLoop;
+ std::unordered_set m_callbacksPostLoop;
+ std::unordered_set m_callbacksPreLoad;
+ std::unordered_set m_callbacksPostLoad;
+ std::unordered_set m_callbacksPreSave;
+ std::unordered_set m_callbacksPostSave;
+ std::unordered_set m_callbacksPreEnd;
+ std::unordered_set m_callbacksPostEnd;
+ std::unordered_set m_callbacksPreUseItem;
+ std::unordered_set m_callbacksPostUseItem;
+ std::unordered_set m_callbacksPreFreeze;
+ std::unordered_set m_callbacksPostFreeze;
+
sol::protected_function m_onStart{};
- sol::protected_function m_onLoad{};
sol::protected_function m_onLoop{};
+ sol::protected_function m_onLoad{};
sol::protected_function m_onSave{};
sol::protected_function m_onEnd{};
sol::protected_function m_onUseItem{};
-
- std::unordered_set m_callbacksPreSave;
- std::unordered_set m_callbacksPostSave;
- std::unordered_set m_callbacksPreLoad;
- std::unordered_set m_callbacksPostLoad;
- std::unordered_set m_callbacksPreStart;
- std::unordered_set m_callbacksPostStart;
- std::unordered_set m_callbacksPreEnd;
- std::unordered_set m_callbacksPostEnd;
- std::unordered_set