From 06a70d364f4b270e11580e6adf9cd2166b538f14 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Fri, 8 Nov 2024 23:19:52 +0100
Subject: [PATCH 001/112] Fixed #1455
---
CHANGELOG.md | 3 ++-
TombEngine/Sound/sound.cpp | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b72ca25e4..a50609928 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,11 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
### Bug fixes
-* Fixed engine performance around multiple stacked bridges.
+* Fixed engine performance around bridges.
* Fixed engine performance if weather effects are active.
* Fixed snow particles not always melting on the ground.
* Fixed enemy pickups dropping on death sectors.
+* Fixed audio tracks placed in subfolders not restoring after loading savegame.
### Features/Amendments
diff --git a/TombEngine/Sound/sound.cpp b/TombEngine/Sound/sound.cpp
index fdd831cbc..5657764b1 100644
--- a/TombEngine/Sound/sound.cpp
+++ b/TombEngine/Sound/sound.cpp
@@ -695,7 +695,7 @@ std::pair GetSoundTrackNameAndPosition(SoundTrackType type)
return std::pair();
std::filesystem::path path = track.Track;
- return std::pair(path.stem().string(), BASS_ChannelGetPosition(track.Channel, BASS_POS_BYTE));
+ return std::pair(path.string(), BASS_ChannelGetPosition(track.Channel, BASS_POS_BYTE));
}
static void CALLBACK Sound_FinishOneshotTrack(HSYNC handle, DWORD channel, DWORD data, void* userData)
From 506ee0f088ef4067983b0ad6ae75bcc9dcd92e25 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Fri, 8 Nov 2024 23:26:15 +0100
Subject: [PATCH 002/112] Fixed #1459
---
CHANGELOG.md | 1 +
TombEngine/Game/control/control.cpp | 4 ++++
TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp | 1 -
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a50609928..08c0b8afa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed snow particles not always melting on the ground.
* Fixed enemy pickups dropping on death sectors.
* Fixed audio tracks placed in subfolders not restoring after loading savegame.
+* Fixed Lara's Home entry not working.
### Features/Amendments
diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp
index 43c3bd150..8f34aebd9 100644
--- a/TombEngine/Game/control/control.cpp
+++ b/TombEngine/Game/control/control.cpp
@@ -632,6 +632,10 @@ GameStatus HandleMenuCalls(bool isTitle)
case InventoryResult::NewGameSelectedLevel:
return GameStatus::NewGame;
+ case InventoryResult::HomeLevel:
+ return GameStatus::HomeLevel;
+ break;
+
case InventoryResult::LoadGame:
return GameStatus::LoadGame;
diff --git a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp
index af4c9a948..4b880af1d 100644
--- a/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp
+++ b/TombEngine/Scripting/Internal/TEN/Flow/FlowHandler.cpp
@@ -701,7 +701,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;
From 50a7fa3b17604eef56c5bbea1204097d1bca9184 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sat, 9 Nov 2024 00:06:40 +0100
Subject: [PATCH 003/112] Return false for IsPointInRoom for inactive flipped
rooms
---
TombEngine/Game/room.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/TombEngine/Game/room.cpp b/TombEngine/Game/room.cpp
index 2e8841634..dab20b9ab 100644
--- a/TombEngine/Game/room.cpp
+++ b/TombEngine/Game/room.cpp
@@ -219,6 +219,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)))
From ed434aaffea49f8789c3e5018ea38e0ae8906c0a Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sat, 9 Nov 2024 00:13:27 +0100
Subject: [PATCH 004/112] Update room.cpp
---
TombEngine/Game/room.cpp | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/TombEngine/Game/room.cpp b/TombEngine/Game/room.cpp
index dab20b9ab..b2f6bf583 100644
--- a/TombEngine/Game/room.cpp
+++ b/TombEngine/Game/room.cpp
@@ -240,21 +240,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;
From 718997336bab218ccbdb3d4fea4f0535445316b6 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sat, 9 Nov 2024 08:10:52 +0100
Subject: [PATCH 005/112] Fix git merge error
---
TombEngine/Game/control/control.cpp | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp
index 8f34aebd9..11c478b3a 100644
--- a/TombEngine/Game/control/control.cpp
+++ b/TombEngine/Game/control/control.cpp
@@ -157,6 +157,9 @@ 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);
+ // 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();
@@ -170,14 +173,6 @@ GameStatus ControlPhase(bool insideMenu)
// Smash shatters and clear stopper flags under them.
UpdateShatters();
- // 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();
From 2b5e1cf4486852c003c3b0280e55fd629881a418 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sat, 9 Nov 2024 08:23:28 +0100
Subject: [PATCH 006/112] Simplify action queue code, fix potential problem
with scripted keypresses
---
CHANGELOG.md | 1 +
TombEngine/Game/control/control.cpp | 10 ++++++++--
TombEngine/Specific/Input/Input.cpp | 6 ------
TombEngine/Specific/Input/Input.h | 1 -
4 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08c0b8afa..a28647532 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed snow particles not always melting on the ground.
* Fixed enemy pickups dropping on death sectors.
* Fixed audio tracks placed in subfolders not restoring after loading savegame.
+* Fixed scripted input events not registering on the same game frame.
* Fixed Lara's Home entry not working.
### Features/Amendments
diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp
index 11c478b3a..14c919c50 100644
--- a/TombEngine/Game/control/control.cpp
+++ b/TombEngine/Game/control/control.cpp
@@ -157,14 +157,20 @@ 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);
+ // Queued input actions are read again after OnLoop, so that remaining control loop can immediately register
+ // emulated keypresses from the script.
+ ApplyActionQueue();
+
// 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.
+ // 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);
diff --git a/TombEngine/Specific/Input/Input.cpp b/TombEngine/Specific/Input/Input.cpp
index 594992735..6a1779225 100644
--- a/TombEngine/Specific/Input/Input.cpp
+++ b/TombEngine/Specific/Input/Input.cpp
@@ -278,10 +278,7 @@ namespace TEN::Input
break;
}
}
- }
- void ClearActionQueue()
- {
for (auto& queue : ActionQueue)
queue = QueueState::None;
}
@@ -718,10 +715,7 @@ namespace TEN::Input
action.Update(Key((int)action.GetID()));
if (applyQueue)
- {
ApplyActionQueue();
- ClearActionQueue();
- }
// Additional handling.
HandleHotkeyActions();
diff --git a/TombEngine/Specific/Input/Input.h b/TombEngine/Specific/Input/Input.h
index 1c25c0099..9789a4da4 100644
--- a/TombEngine/Specific/Input/Input.h
+++ b/TombEngine/Specific/Input/Input.h
@@ -98,7 +98,6 @@ namespace TEN::Input
void DefaultConflict();
void UpdateInputActions(ItemInfo* item, bool applyQueue = false);
void ApplyActionQueue();
- void ClearActionQueue();
void ClearAllActions();
void Rumble(float power, float delayInSec = 0.3f, RumbleMode mode = RumbleMode::Both);
void StopRumble();
From b5502103c0cf4b2d3c38d97e4497bd8e6fabf8a6 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sat, 9 Nov 2024 18:59:22 +0100
Subject: [PATCH 007/112] Fix lensflare object
---
CHANGELOG.md | 1 +
TombEngine/Game/collision/collide_item.cpp | 2 +-
TombEngine/Objects/Effects/LensFlare.cpp | 2 +-
TombEngine/Objects/Effects/effect_objects.cpp | 3 +++
4 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a28647532..430aa94ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed audio tracks placed in subfolders not restoring after loading savegame.
* Fixed scripted input events not registering on the same game frame.
* Fixed Lara's Home entry not working.
+* Fixed Lens Flare object not functioning properly.
### Features/Amendments
diff --git a/TombEngine/Game/collision/collide_item.cpp b/TombEngine/Game/collision/collide_item.cpp
index eaaa9263b..2b906dbab 100644
--- a/TombEngine/Game/collision/collide_item.cpp
+++ b/TombEngine/Game/collision/collide_item.cpp
@@ -152,7 +152,7 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
// Ignore items not feasible for collision.
if (item.Index == collidingItem.Index ||
- item.Flags & IFLAG_KILLED || item.MeshBits == NO_JOINT_BITS ||
+ item.Flags & IFLAG_KILLED || item.MeshBits == NO_JOINT_BITS || !item.Collidable ||
(object.drawRoutine == nullptr && !item.IsLara()) ||
(object.collision == nullptr && !item.IsLara()))
{
diff --git a/TombEngine/Objects/Effects/LensFlare.cpp b/TombEngine/Objects/Effects/LensFlare.cpp
index 788b64c75..a9932cc65 100644
--- a/TombEngine/Objects/Effects/LensFlare.cpp
+++ b/TombEngine/Objects/Effects/LensFlare.cpp
@@ -100,7 +100,7 @@ namespace TEN::Entities::Effects
auto& item = g_Level.Items[itemNumber];
if (TriggerActive(&item))
- SetupLensFlare(item.Pose.Position.ToVector3(), item.RoomNumber, Color(), false, SPRITE_TYPES::SPR_LENS_FLARE_3);
+ SetupLensFlare(item.Pose.Position.ToVector3(), item.RoomNumber, item.Model.Color, false, SPRITE_TYPES::SPR_LENS_FLARE_3);
}
void ClearLensFlares()
diff --git a/TombEngine/Objects/Effects/effect_objects.cpp b/TombEngine/Objects/Effects/effect_objects.cpp
index 0b6b1f9ec..ef905c561 100644
--- a/TombEngine/Objects/Effects/effect_objects.cpp
+++ b/TombEngine/Objects/Effects/effect_objects.cpp
@@ -58,5 +58,8 @@ void InitializeEffectsObjects()
obj = &Objects[ID_LENS_FLARE];
if (obj->loaded)
+ {
+ obj->drawRoutine = nullptr;
obj->control = ControlLensFlare;
+ }
}
From 908fc612834ed1262b68fee2a26985b05563f12f Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sat, 9 Nov 2024 23:12:29 +0100
Subject: [PATCH 008/112] Fix inventory item cancel
---
TombEngine/Game/control/control.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/TombEngine/Game/control/control.cpp b/TombEngine/Game/control/control.cpp
index 14c919c50..0c9cdae63 100644
--- a/TombEngine/Game/control/control.cpp
+++ b/TombEngine/Game/control/control.cpp
@@ -161,9 +161,6 @@ GameStatus ControlPhase(bool insideMenu)
// emulated keypresses from the script.
ApplyActionQueue();
- // 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)
@@ -179,6 +176,9 @@ GameStatus ControlPhase(bool insideMenu)
// Smash shatters and clear stopper flags under them.
UpdateShatters();
+ // Clear last selected item in inventory (must be after on loop event handling, so they can detect that).
+ g_Gui.CancelInventorySelection();
+
// Update weather.
Weather.Update();
From f0577706965a8354eebc6583b5e14f002f7b8de3 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sun, 10 Nov 2024 08:07:18 +0100
Subject: [PATCH 009/112] Fixed incorrect object camera position
---
CHANGELOG.md | 1 +
TombEngine/Game/camera.cpp | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 430aa94ea..ce24af1f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed enemy pickups dropping on death sectors.
* Fixed audio tracks placed in subfolders not restoring after loading savegame.
* Fixed scripted input events not registering on the same game frame.
+* Fixed incorrect object camera position.
* Fixed Lara's Home entry not working.
* Fixed Lens Flare object not functioning properly.
diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp
index 0c55a0508..e5d0d8101 100644
--- a/TombEngine/Game/camera.cpp
+++ b/TombEngine/Game/camera.cpp
@@ -409,7 +409,7 @@ void ObjCamera(ItemInfo* camSlotId, int camMeshId, ItemInfo* targetItem, int tar
//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;
From 6eca0548c6c7408f1f1175e4ccc21abe0bcb53c8 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sun, 10 Nov 2024 17:23:50 +0100
Subject: [PATCH 010/112] Exclude some ToBoundingOrientedBox conversions
---
TombEngine/Game/camera.cpp | 27 +++++++++++++++-------
TombEngine/Game/collision/collide_item.cpp | 20 ++++++++--------
2 files changed, 29 insertions(+), 18 deletions(-)
diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp
index e5d0d8101..173534cd9 100644
--- a/TombEngine/Game/camera.cpp
+++ b/TombEngine/Game/camera.cpp
@@ -1331,8 +1331,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 +1512,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 +1538,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/collision/collide_item.cpp b/TombEngine/Game/collision/collide_item.cpp
index 2b906dbab..f94a9bc64 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{};
@@ -117,7 +117,7 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
convertedBounds.Extents = Vector3(customRadius);
// Quickly discard collision if colliding item bounds are below tolerance threshold.
- if (collidingSphere.Radius <= EXTENTS_LENGTH_MIN)
+ if (collidingSphere.Radius <= COLLIDABLE_BOUNDS_THRESHOLD)
return collObjects;
// Run through neighboring rooms.
@@ -172,7 +172,7 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
auto extents = bounds.GetExtents();
// If item bounding box extents is below tolerance threshold, discard object.
- if (extents.Length() <= EXTENTS_LENGTH_MIN)
+ if (extents.Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
continue;
// Test rough vertical distance to discard objects not intersecting vertically.
@@ -237,10 +237,10 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
continue;
// Skip if either bounding box has any zero extent (not a collidable volume).
- if (bounds.GetExtents().Length() <= EXTENTS_LENGTH_MIN)
+ if (bounds.GetExtents().Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
continue;
- if (collidingBounds.GetExtents().Length() <= EXTENTS_LENGTH_MIN)
+ if (collidingBounds.GetExtents().Length() <= COLLIDABLE_BOUNDS_THRESHOLD)
continue;
auto box = bounds.ToBoundingOrientedBox(staticObj.pos.Position);
@@ -971,13 +971,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 +1805,7 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll)
if (isPlayer)
{
- GetLaraInfo(*item).HitDirection = -1;
+ GetLaraInfo(*item).HitDirection = NO_VALUE;
if (item->HitPoints <= 0)
return;
From 3b252848c59337c4d39bb9663d7f771f121faa1f Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Sun, 10 Nov 2024 19:23:17 +0300
Subject: [PATCH 011/112] Displaystring alpha (#1453)
* Support for alpha in display strings
* Update CHANGELOG.md
* Fix alpha
* Potentially fix problems with alpha blend
---
CHANGELOG.md | 2 ++
TombEngine/Renderer/Renderer.h | 4 ++--
TombEngine/Renderer/RendererDraw.cpp | 3 ++-
TombEngine/Renderer/RendererString.cpp | 12 ++++++++----
.../Renderer/Structures/RendererStringToDraw.h | 2 +-
5 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce24af1f5..3115cfd42 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,8 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
### Lua API changes
+* Added support for transparency value in DisplayString class.
+
## [Version 1.5](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.7.2) - 2024-11-03
### Bug fixes
diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h
index ae794fc92..88b2c443f 100644
--- a/TombEngine/Renderer/Renderer.h
+++ b/TombEngine/Renderer/Renderer.h
@@ -202,8 +202,8 @@ namespace TEN::Renderer
// 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
diff --git a/TombEngine/Renderer/RendererDraw.cpp b/TombEngine/Renderer/RendererDraw.cpp
index f0e73c32a..40dbdc78f 100644
--- a/TombEngine/Renderer/RendererDraw.cpp
+++ b/TombEngine/Renderer/RendererDraw.cpp
@@ -1583,7 +1583,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;
diff --git a/TombEngine/Renderer/RendererString.cpp b/TombEngine/Renderer/RendererString.cpp
index 7ed550a34..ff52f8391 100644
--- a/TombEngine/Renderer/RendererString.cpp
+++ b/TombEngine/Renderer/RendererString.cpp
@@ -50,7 +50,7 @@ namespace TEN::Renderer
rString.Flags = flags;
rString.X = 0;
rString.Y = 0;
- rString.Color = color.ToVector3();
+ rString.Color = color;
rString.Scale = (uiScale * fontScale) * scale;
// Measure string.
@@ -89,8 +89,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 +105,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 +113,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/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;
};
}
From 9494c999e739d654daa65f213789d29ca1d60268 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 08:04:58 +0100
Subject: [PATCH 012/112] Fixed incorrect camera movement near walls after
leaving look mode
---
CHANGELOG.md | 1 +
TombEngine/Game/camera.cpp | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3115cfd42..bdf472101 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed audio tracks placed in subfolders not restoring after loading savegame.
* Fixed scripted input events not registering on the same game frame.
* Fixed incorrect object camera position.
+* Fixed incorrect camera movement near walls after leaving look mode.
* Fixed Lara's Home entry not working.
* Fixed Lens Flare object not functioning properly.
diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp
index 173534cd9..f987412ac 100644
--- a/TombEngine/Game/camera.cpp
+++ b/TombEngine/Game/camera.cpp
@@ -1260,8 +1260,8 @@ void CalculateCamera(const CollisionInfo& coll)
if (isFixedCamera == Camera.fixedCamera)
{
Camera.fixedCamera = false;
- if (Camera.speed != 1 &&
- !Lara.Control.Look.IsUsingBinoculars)
+
+ if (Camera.speed != 1 && Camera.oldType != CameraType::Look && !Lara.Control.Look.IsUsingBinoculars)
{
if (TargetSnaps <= 8)
{
From d562b9f433859cb23e7f364125d32e2aebb037d8 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 08:31:36 +0100
Subject: [PATCH 013/112] Less snappy behaviour when exiting look to chase
camera
---
TombEngine/Game/camera.cpp | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/TombEngine/Game/camera.cpp b/TombEngine/Game/camera.cpp
index f987412ac..070bac476 100644
--- a/TombEngine/Game/camera.cpp
+++ b/TombEngine/Game/camera.cpp
@@ -522,6 +522,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);
@@ -1261,7 +1268,7 @@ void CalculateCamera(const CollisionInfo& coll)
{
Camera.fixedCamera = false;
- if (Camera.speed != 1 && Camera.oldType != CameraType::Look && !Lara.Control.Look.IsUsingBinoculars)
+ if (Camera.speed != 1 && !Lara.Control.Look.IsUsingBinoculars)
{
if (TargetSnaps <= 8)
{
From 6c848dcfaedb4e9e3fbea3b8d3402bcadaae3801 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 08:50:53 +0100
Subject: [PATCH 014/112] Fixed #1467
---
CHANGELOG.md | 1 +
TombEngine/Game/spotcam.cpp | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bdf472101..b012e9f68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed scripted input events not registering on the same game frame.
* Fixed incorrect object camera position.
* Fixed incorrect camera movement near walls after leaving look mode.
+* Fixed binocular or lasersight camera not switching off correctly after flyby.
* Fixed Lara's Home entry not working.
* Fixed Lens Flare object not functioning properly.
diff --git a/TombEngine/Game/spotcam.cpp b/TombEngine/Game/spotcam.cpp
index 15baf58fd..ea78586e8 100644
--- a/TombEngine/Game/spotcam.cpp
+++ b/TombEngine/Game/spotcam.cpp
@@ -693,13 +693,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)
{
From abf69f120e6735b276f43b60d94c8aeeb9a3586a Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 09:10:54 +0100
Subject: [PATCH 015/112] Fixed #1468
---
CHANGELOG.md | 1 +
TombEngine/Game/savegame.cpp | 15 +++++++++------
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b012e9f68..1f0c08973 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed engine performance if weather effects are active.
* Fixed snow particles not always melting on the ground.
* Fixed enemy pickups dropping on death sectors.
+* Fixed vehicle transfer not happening for levels which were not previously visited.
* Fixed audio tracks placed in subfolders not restoring after loading savegame.
* Fixed scripted input events not registering on the same game frame.
* Fixed incorrect object camera position.
diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp
index 42a877e0e..2757870b0 100644
--- a/TombEngine/Game/savegame.cpp
+++ b/TombEngine/Game/savegame.cpp
@@ -1506,15 +1506,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);
}
From 78c0ba3ff054cb4009b36aba220ed02514eea640 Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 09:40:34 +0100
Subject: [PATCH 016/112] Add ricochet sound and increase amount of particles
---
CHANGELOG.md | 4 +++-
TombEngine/Game/control/los.cpp | 11 +++++------
TombEngine/Game/effects/effects.cpp | 9 ++++++---
TombEngine/Game/effects/effects.h | 2 +-
TombEngine/Game/items.cpp | 2 +-
TombEngine/Objects/TR2/Entity/tr2_spear_guardian.cpp | 2 +-
TombEngine/Objects/TR2/Entity/tr2_sword_guardian.cpp | 2 +-
TombEngine/Objects/TR3/Entity/Shiva.cpp | 4 ++--
TombEngine/Objects/TR3/Entity/Winston.cpp | 2 +-
TombEngine/Objects/TR4/Entity/tr4_baddy.cpp | 2 +-
TombEngine/Objects/TR5/Entity/AutoGun.cpp | 2 +-
TombEngine/Objects/TR5/Entity/HeavyGuard.cpp | 2 +-
TombEngine/Objects/TR5/Entity/tr5_gunship.cpp | 6 ++----
TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp | 2 +-
14 files changed, 27 insertions(+), 25 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f0c08973..df03b1024 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,7 +21,9 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed Lens Flare object not functioning properly.
### Features/Amendments
-
+
+* Added ricochet sounds and make the effect more prominent.
+
### Lua API changes
* Added support for transparency value in DisplayString class.
diff --git a/TombEngine/Game/control/los.cpp b/TombEngine/Game/control/los.cpp
index 003d04b30..c07d5ff74 100644
--- a/TombEngine/Game/control/los.cpp
+++ b/TombEngine/Game/control/los.cpp
@@ -296,8 +296,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
SoundEffect(GetShatterSound(mesh->staticNumber), (Pose*)mesh);
}
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
else
{
@@ -312,7 +311,7 @@ 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);
}
else
{
@@ -348,7 +347,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
{
// TR5
if (object->hitEffect == HitEffect::Richochet)
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
}
else
@@ -420,7 +419,7 @@ bool GetTargetOnLOS(GameVector* origin, GameVector* target, bool drawTarget, boo
}
}
- TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y, 3, 0);
+ TriggerRicochetSpark(target2, LaraItem->Pose.Orientation.y);
}
}
}
@@ -447,7 +446,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);
}
}
diff --git a/TombEngine/Game/effects/effects.cpp b/TombEngine/Game/effects/effects.cpp
index c64da1969..fa0bab9c6 100644
--- a/TombEngine/Game/effects/effects.cpp
+++ b/TombEngine/Game/effects/effects.cpp
@@ -450,9 +450,13 @@ 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);
+
+ if (sound && Random::TestProbability(1 / 3.0f))
+ SoundEffect(SFX_TR4_WEAPON_RICOCHET, &Pose(pos.ToVector3i()));
}
void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv)
@@ -1135,8 +1139,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..583d51f82 100644
--- a/TombEngine/Game/effects/effects.h
+++ b/TombEngine/Game/effects/effects.h
@@ -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/items.cpp b/TombEngine/Game/items.cpp
index 5f30fac06..19e382ba8 100644
--- a/TombEngine/Game/items.cpp
+++ b/TombEngine/Game/items.cpp
@@ -893,7 +893,7 @@ void DefaultItemHit(ItemInfo& target, ItemInfo& source, std::optionalstaticNumber), &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..3c36ce5c1 100644
--- a/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp
+++ b/TombEngine/Objects/TR5/Entity/tr5_roman_statue.cpp
@@ -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);
}
From 87b9c3caacf188d21d2dbffd52c4afb4ab4db1ba Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 10:13:48 +0100
Subject: [PATCH 017/112] Fixed #1451
---
CHANGELOG.md | 1 +
TombEngine/Objects/TR3/tr3_objects.cpp | 6 +++---
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df03b1024..f97ae01b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed incorrect camera movement near walls after leaving look mode.
* Fixed binocular or lasersight camera not switching off correctly after flyby.
* Fixed Lara's Home entry not working.
+* Fixed exploding TR3 bosses.
* Fixed Lens Flare object not functioning properly.
### Features/Amendments
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];
From b79d662b1341786ed64c97aaf87156b1f534cf1d Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 11:48:59 +0100
Subject: [PATCH 018/112] Fixed bubbles performance
---
CHANGELOG.md | 2 +-
TombEngine/Game/Lara/lara.h | 2 +-
TombEngine/Game/Lara/lara_helpers.cpp | 2 +-
TombEngine/Game/effects/bubble.cpp | 24 ++++++++++++++----------
4 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f97ae01b4..5958cd161 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
### Bug fixes
* Fixed engine performance around bridges.
-* Fixed engine performance if weather effects are active.
+* Fixed engine performance if weather or bubble effects are active.
* Fixed snow particles not always melting on the ground.
* Fixed enemy pickups dropping on death sectors.
* Fixed vehicle transfer not happening for levels which were not previously visited.
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_helpers.cpp b/TombEngine/Game/Lara/lara_helpers.cpp
index b307919d2..3efab248d 100644
--- a/TombEngine/Game/Lara/lara_helpers.cpp
+++ b/TombEngine/Game/Lara/lara_helpers.cpp
@@ -953,7 +953,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);
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;
From 784f957596a41e8e0b483e2e756300ebc0ca9bdb Mon Sep 17 00:00:00 2001
From: Lwmte <3331699+Lwmte@users.noreply.github.com>
Date: Mon, 11 Nov 2024 13:55:50 +0300
Subject: [PATCH 019/112] Fast reload (#1445)
* Initial commit
* Fix crash on title
* Update level.cpp
* Update CHANGELOG.md
* Do slight audiotrack fade-ins and fade-outs on leveljumps
* Implement hash checks
* Fixes
* Rename rapid to fast
* Fixed flipmaps and bridge reinit
* Bypass reinitializing renderer for fast reload
* Fix issue when title and last loaded level are the same files
* Update CHANGELOG.md
* Additional fixes
* Wrap savegame loading code into a try-catch
* Update CHANGELOG.md
* Remove door collision on fast reload, restore stopper flag
* Display log message if level file was not found
* Update CHANGELOG.md
* Clear blocked flag for boxes
* Implement fast reload setting
* Add defaults to settings
* Consistent naming
* Smooth level loading audio crossfades
* Stop non-ambience soundtracks early
* Minor formatting
* Update Settings.lua
---------
Co-authored-by: Jakub <80340234+Jakub768@users.noreply.github.com>
Co-authored-by: Sezz
---
CHANGELOG.md | 6 +-
.../doc/2 classes/Flow.Settings.html | 24 +
Scripts/Settings.lua | 1 +
TombEngine/Game/camera.h | 3 +-
TombEngine/Game/control/control.cpp | 8 +-
TombEngine/Game/control/control.h | 1 -
TombEngine/Game/room.cpp | 96 ++-
TombEngine/Game/room.h | 1 +
TombEngine/Game/savegame.cpp | 82 ++-
TombEngine/Game/savegame.h | 37 +-
.../Objects/Generic/Doors/generic_doors.cpp | 2 +-
TombEngine/Renderer/RendererCompatibility.cpp | 2 +
TombEngine/Renderer/RendererDrawMenu.cpp | 30 +-
TombEngine/Renderer/RendererSettings.cpp | 4 +-
.../Include/Flow/ScriptInterfaceFlowHandler.h | 3 +
.../Scripting/Internal/ReservedScriptNames.h | 1 +
.../Internal/TEN/Flow/Settings/Settings.cpp | 28 +-
.../Internal/TEN/Flow/Settings/Settings.h | 22 +-
TombEngine/Sound/sound.cpp | 13 +-
TombEngine/Sound/sound.h | 5 +-
TombEngine/Specific/level.cpp | 558 +++++++++++-------
TombEngine/Specific/level.h | 4 +-
.../flatbuffers/ten_savegame_generated.h | 30 +-
.../Specific/savegame/schema/ten_savegame.fbs | 1 +
TombEngine/framework.h | 1 +
25 files changed, 605 insertions(+), 358 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5958cd161..0feead175 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,6 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
## Version 1.6 - xxxx-xx-xx
### Bug fixes
-
* Fixed engine performance around bridges.
* Fixed engine performance if weather or bubble effects are active.
* Fixed snow particles not always melting on the ground.
@@ -22,7 +21,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed Lens Flare object not functioning properly.
### Features/Amendments
-
+* Added fast savegame reloading.
* Added ricochet sounds and make the effect more prominent.
### Lua API changes
@@ -32,7 +31,6 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
## [Version 1.5](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.7.2) - 2024-11-03
### Bug fixes
-
* Fixed original issue with classic switch off trigger incorrectly activating some trigger actions.
* Fixed moveable status after antitriggering.
* Fixed leveljump vehicle transfer.
@@ -71,7 +69,6 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed young Lara hair drawing. https://tombengine.com/docs/level-settings/#young_lara
### Features/Amendments
-
* Added high framerate mode (also known as 60 FPS mode).
* Added a customisable global lensflare effect. https://tombengine.com/docs/level-settings/#lensflare
* Added a customisable starry sky and meteor effect. https://tombengine.com/docs/level-settings/#stars
@@ -100,7 +97,6 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Removed original limit of 32 active Flame Emitters.
### Lua API changes
-
* Added Flow.EnableHomeLevel() function.
* Added Flow.IsStringPresent() function.
* Added Flow.LensFlare() and Flow.Starfield() classes.
diff --git a/Documentation/doc/2 classes/Flow.Settings.html b/Documentation/doc/2 classes/Flow.Settings.html
index 007e40a45..abe97b44c 100644
--- a/Documentation/doc/2 classes/Flow.Settings.html
+++ b/Documentation/doc/2 classes/Flow.Settings.html
@@ -114,6 +114,10 @@
errorMode |
How should the application respond to script errors? |
+
+ fastReload |
+ Can game utilize fast reload feature? |
+
@@ -129,6 +133,7 @@
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.
@@ -151,6 +156,25 @@ has an unrecoverable error, the game will close.
+
+
+
+ fastReload
+
+
+ Can game utilize fast reload feature?
+
+When set to True
, game will attempt to perform fast savegame reloading, if current level is the same as
+level loaded from the savegame. It will not work if level timestamp or checksum has changed (i.e. level was
+updated). If set to False
this functionality is turned off.
+
+
+
+
+
+
+
+
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/camera.h b/TombEngine/Game/camera.h
index 59db6e4f8..f4f4e6829 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;
@@ -67,7 +68,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/control/control.cpp b/TombEngine/Game/control/control.cpp
index 0c9cdae63..aebf8c6e8 100644
--- a/TombEngine/Game/control/control.cpp
+++ b/TombEngine/Game/control/control.cpp
@@ -105,8 +105,6 @@ int RequiredStartPos;
int CurrentLevel;
int NextLevel;
-int SystemNameHash = 0;
-
bool InItemControlLoop;
short ItemNewRoomNo;
short ItemNewRooms[MAX_ROOMS];
@@ -508,10 +506,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;
@@ -596,7 +592,7 @@ void EndGameLoop(int levelIndex, GameStatus reason)
DeInitializeScripting(levelIndex, reason);
StopAllSounds();
- StopSoundTracks();
+ StopSoundTracks(SOUND_XFADETIME_LEVELJUMP, true);
StopRumble();
}
diff --git a/TombEngine/Game/control/control.h b/TombEngine/Game/control/control.h
index 646808f27..9aa63dd1e 100644
--- a/TombEngine/Game/control/control.h
+++ b/TombEngine/Game/control/control.h
@@ -64,7 +64,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/room.cpp b/TombEngine/Game/room.cpp
index b2f6bf583..a2cd8564d 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;
@@ -73,6 +75,74 @@ static void RemoveRoomFlipItems(const ROOM_INFO& room)
}
}
+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);
}
}
diff --git a/TombEngine/Game/room.h b/TombEngine/Game/room.h
index fd228fb2d..673c028e2 100644
--- a/TombEngine/Game/room.h
+++ b/TombEngine/Game/room.h
@@ -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 2757870b0..4a2c07408 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);
@@ -1582,50 +1583,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)
@@ -2049,7 +2068,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.
@@ -2655,6 +2674,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/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/Renderer/RendererCompatibility.cpp b/TombEngine/Renderer/RendererCompatibility.cpp
index 2bf5f89eb..cd94dd6f3 100644
--- a/TombEngine/Renderer/RendererCompatibility.cpp
+++ b/TombEngine/Renderer/RendererCompatibility.cpp
@@ -22,6 +22,8 @@ namespace TEN::Renderer
bool Renderer::PrepareDataForTheRenderer()
{
+ TENLog("Preparing renderer...", LogLevel::Info);
+
_lastBlendMode = BlendMode::Unknown;
_lastCullMode = CullMode::Unknown;
_lastDepthState = DepthState::Unknown;
diff --git a/TombEngine/Renderer/RendererDrawMenu.cpp b/TombEngine/Renderer/RendererDrawMenu.cpp
index d0316cad4..7db09fa8d 100644
--- a/TombEngine/Renderer/RendererDrawMenu.cpp
+++ b/TombEngine/Renderer/RendererDrawMenu.cpp
@@ -1013,10 +1013,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 +1043,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:
@@ -1103,26 +1107,30 @@ namespace TEN::Renderer
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);
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/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h b/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h
index 4a3f67354..c67c32650 100644
--- a/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h
+++ b/TombEngine/Scripting/Include/Flow/ScriptInterfaceFlowHandler.h
@@ -1,5 +1,7 @@
#pragma once
+
#include "Game/control/control.h"
+#include "Scripting/Internal/TEN/Flow/Settings/Settings.h"
class ScriptInterfaceLevel;
@@ -25,6 +27,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/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h
index 42022655e..f4b2df397 100644
--- a/TombEngine/Scripting/Internal/ReservedScriptNames.h
+++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h
@@ -238,6 +238,7 @@ 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";
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/Sound/sound.cpp b/TombEngine/Sound/sound.cpp
index 5657764b1..f04695fbc 100644
--- a/TombEngine/Sound/sound.cpp
+++ b/TombEngine/Sound/sound.cpp
@@ -490,7 +490,7 @@ std::optional GetCurrentSubtitle()
return std::nullopt;
}
-void PlaySoundTrack(const std::string& track, SoundTrackType mode, QWORD position)
+void PlaySoundTrack(const std::string& track, SoundTrackType mode, QWORD position, int forceFadeInTime)
{
if (!g_Configuration.EnableSound)
return;
@@ -565,11 +565,11 @@ void PlaySoundTrack(const std::string& track, SoundTrackType mode, QWORD positio
// BGM tracks are crossfaded, and additionally shuffled a bit to make things more natural.
// Think everybody are fed up with same start-up sounds of Caves ambience...
- if (crossfade && BASS_ChannelIsActive(SoundtrackSlot[(int)SoundTrackType::BGM].Channel))
+ if (forceFadeInTime > 0 || (crossfade && BASS_ChannelIsActive(SoundtrackSlot[(int)SoundTrackType::BGM].Channel)))
{
// Crossfade...
BASS_ChannelSetAttribute(stream, BASS_ATTRIB_VOL, 0.0f);
- BASS_ChannelSlideAttribute(stream, BASS_ATTRIB_VOL, masterVolume, crossfadeTime);
+ BASS_ChannelSlideAttribute(stream, BASS_ATTRIB_VOL, masterVolume, (forceFadeInTime > 0) ? forceFadeInTime : crossfadeTime);
// Shuffle...
// Only activates if no custom position is passed as argument.
@@ -659,7 +659,7 @@ void PlaySoundTrack(int index, short mask)
PlaySoundTrack(SoundTracks[index].Name, SoundTracks[index].Mode);
}
-void StopSoundTracks(bool excludeAmbience)
+void StopSoundTracks(int fadeoutTime, bool excludeAmbience)
{
for (int i = 0; i < (int)SoundTrackType::Count; i++)
{
@@ -667,12 +667,15 @@ void StopSoundTracks(bool excludeAmbience)
if (excludeAmbience && type == SoundTrackType::BGM)
continue;
- StopSoundTrack(type, SOUND_XFADETIME_ONESHOT);
+ StopSoundTrack(type, fadeoutTime);
}
}
void StopSoundTrack(SoundTrackType mode, int fadeoutTime)
{
+ if (SoundtrackSlot[(int)mode].Channel == NULL)
+ return;
+
// Do fadeout.
BASS_ChannelSlideAttribute(SoundtrackSlot[(int)mode].Channel, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, -1.0f, fadeoutTime);
diff --git a/TombEngine/Sound/sound.h b/TombEngine/Sound/sound.h
index eb18997e9..bad970b6c 100644
--- a/TombEngine/Sound/sound.h
+++ b/TombEngine/Sound/sound.h
@@ -25,6 +25,7 @@ constexpr auto SOUND_MILLISECONDS_IN_SECOND = 1000.0f;
constexpr auto SOUND_XFADETIME_BGM = 5000;
constexpr auto SOUND_XFADETIME_BGM_START = 1500;
constexpr auto SOUND_XFADETIME_ONESHOT = 200;
+constexpr auto SOUND_XFADETIME_LEVELJUMP = 400;
constexpr auto SOUND_XFADETIME_CUTSOUND = 100;
constexpr auto SOUND_XFADETIME_HIJACKSOUND = 50;
constexpr auto SOUND_BGM_DAMP_COEFFICIENT = 0.5f;
@@ -164,11 +165,11 @@ void SayNo();
void PlaySoundSources();
int GetShatterSound(int shatterID);
-void PlaySoundTrack(const std::string& trackName, SoundTrackType mode, QWORD position = 0);
+void PlaySoundTrack(const std::string& trackName, SoundTrackType mode, QWORD position = 0, int forceFadeInTime = 0);
void PlaySoundTrack(const std::string& trackName, short mask = 0);
void PlaySoundTrack(int index, short mask = 0);
void StopSoundTrack(SoundTrackType mode, int fadeoutTime);
-void StopSoundTracks(bool excludeAmbience = false);
+void StopSoundTracks(int fadeoutTime = SOUND_XFADETIME_LEVELJUMP, bool excludeAmbience = false);
void ClearSoundTrackMasks();
void PlaySecretTrack();
void EnumerateLegacyTracks();
diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp
index 2d7388c6e..656235b74 100644
--- a/TombEngine/Specific/level.cpp
+++ b/TombEngine/Specific/level.cpp
@@ -77,44 +77,54 @@ const std::vector BRIDGE_OBJECT_IDS =
ID_BRIDGE_CUSTOM
};
-char* LevelDataPtr;
+LEVEL g_Level;
+
std::vector MoveablesIds;
std::vector StaticObjectsIds;
std::vector SpriteSequencesIds;
-LEVEL g_Level;
+
+char* DataPtr;
+char* CurrentDataPtr;
+
+bool FirstLevel = true;
+int SystemNameHash = 0;
+int LastLevelHash = 0;
+
+std::filesystem::file_time_type LastLevelTimestamp;
+std::string LastLevelFilePath;
unsigned char ReadUInt8()
{
- unsigned char value = *(unsigned char*)LevelDataPtr;
- LevelDataPtr += 1;
+ unsigned char value = *(unsigned char*)CurrentDataPtr;
+ CurrentDataPtr += 1;
return value;
}
short ReadInt16()
{
- short value = *(short*)LevelDataPtr;
- LevelDataPtr += 2;
+ short value = *(short*)CurrentDataPtr;
+ CurrentDataPtr += 2;
return value;
}
unsigned short ReadUInt16()
{
- unsigned short value = *(unsigned short*)LevelDataPtr;
- LevelDataPtr += 2;
+ unsigned short value = *(unsigned short*)CurrentDataPtr;
+ CurrentDataPtr += 2;
return value;
}
int ReadInt32()
{
- int value = *(int*)LevelDataPtr;
- LevelDataPtr += 4;
+ int value = *(int*)CurrentDataPtr;
+ CurrentDataPtr += 4;
return value;
}
float ReadFloat()
{
- float value = *(float*)LevelDataPtr;
- LevelDataPtr += 4;
+ float value = *(float*)CurrentDataPtr;
+ CurrentDataPtr += 4;
return value;
}
@@ -152,8 +162,8 @@ bool ReadBool()
void ReadBytes(void* dest, int count)
{
- memcpy(dest, LevelDataPtr, count);
- LevelDataPtr += count;
+ memcpy(dest, CurrentDataPtr, count);
+ CurrentDataPtr += count;
}
long long ReadLEB128(bool sign)
@@ -188,9 +198,9 @@ std::string ReadString()
return std::string();
else
{
- auto newPtr = LevelDataPtr + numBytes;
- auto result = std::string(LevelDataPtr, newPtr);
- LevelDataPtr = newPtr;
+ auto newPtr = CurrentDataPtr + numBytes;
+ auto result = std::string(CurrentDataPtr, newPtr);
+ CurrentDataPtr = newPtr;
return result;
}
}
@@ -205,54 +215,51 @@ void LoadItems()
InitializeItemArray(ITEM_COUNT_MAX);
- if (g_Level.NumItems > 0)
+ for (int i = 0; i < g_Level.NumItems; i++)
{
- for (int i = 0; i < g_Level.NumItems; i++)
- {
- auto* item = &g_Level.Items[i];
+ auto* item = &g_Level.Items[i];
- item->Data = ItemData{};
- item->ObjectNumber = from_underlying(ReadInt16());
- item->RoomNumber = ReadInt16();
- item->Pose.Position.x = ReadInt32();
- item->Pose.Position.y = ReadInt32();
- item->Pose.Position.z = ReadInt32();
- item->Pose.Orientation.y = ReadInt16();
- item->Pose.Orientation.x = ReadInt16();
- item->Pose.Orientation.z = ReadInt16();
- item->Model.Color = ReadVector4();
- item->TriggerFlags = ReadInt16();
- item->Flags = ReadInt16();
- item->Name = ReadString();
+ item->Data = ItemData{};
+ item->ObjectNumber = from_underlying(ReadInt16());
+ item->RoomNumber = ReadInt16();
+ item->Pose.Position.x = ReadInt32();
+ item->Pose.Position.y = ReadInt32();
+ item->Pose.Position.z = ReadInt32();
+ item->Pose.Orientation.y = ReadInt16();
+ item->Pose.Orientation.x = ReadInt16();
+ item->Pose.Orientation.z = ReadInt16();
+ item->Model.Color = ReadVector4();
+ item->TriggerFlags = ReadInt16();
+ item->Flags = ReadInt16();
+ item->Name = ReadString();
- g_GameScriptEntities->AddName(item->Name, (short)i);
- g_GameScriptEntities->TryAddColliding((short)i);
+ g_GameScriptEntities->AddName(item->Name, (short)i);
+ g_GameScriptEntities->TryAddColliding((short)i);
- memcpy(&item->StartPose, &item->Pose, sizeof(Pose));
- }
+ memcpy(&item->StartPose, &item->Pose, sizeof(Pose));
+ }
- // Initialize items.
- for (int i = 0; i <= 1; i++)
+ // Initialize items.
+ for (int i = 0; i <= 1; i++)
+ {
+ // HACK: Initialize bridges first. Required because other items need final floordata to init properly.
+ if (i == 0)
{
- // HACK: Initialize bridges first. Required because other items need final floordata to init properly.
- if (i == 0)
+ for (int j = 0; j < g_Level.NumItems; j++)
{
- for (int j = 0; j < g_Level.NumItems; j++)
- {
- const auto& item = g_Level.Items[j];
- if (Contains(BRIDGE_OBJECT_IDS, item.ObjectNumber))
- InitializeItem(j);
- }
+ const auto& item = g_Level.Items[j];
+ if (Contains(BRIDGE_OBJECT_IDS, item.ObjectNumber))
+ InitializeItem(j);
}
- // Initialize non-bridge items second.
- else if (i == 1)
+ }
+ // Initialize non-bridge items second.
+ else if (i == 1)
+ {
+ for (int j = 0; j < g_Level.NumItems; j++)
{
- for (int j = 0; j < g_Level.NumItems; j++)
- {
- const auto& item = g_Level.Items[j];
- if (!item.IsBridge())
- InitializeItem(j);
- }
+ const auto& item = g_Level.Items[j];
+ if (!item.IsBridge())
+ InitializeItem(j);
}
}
}
@@ -674,7 +681,95 @@ static Plane ConvertFakePlaneToPlane(const Vector3& fakePlane, bool isFloor)
return Plane(normal, dist);
}
-void ReadRooms()
+void LoadDynamicRoomData()
+{
+ int roomCount = ReadInt32();
+
+ if (g_Level.Rooms.size() != roomCount)
+ throw std::exception("Dynamic room data count is inconsistent with room count");
+
+ for (int i = 0; i < roomCount; i++)
+ {
+ auto& room = g_Level.Rooms[i];
+
+ room.Name = ReadString();
+
+ int tagCount = ReadInt32();
+ room.Tags.resize(0);
+ room.Tags.reserve(tagCount);
+
+ for (int j = 0; j < tagCount; j++)
+ room.Tags.push_back(ReadString());
+
+ room.ambient = ReadVector3();
+
+ room.flippedRoom = ReadInt32();
+ room.flags = ReadInt32();
+ room.meshEffect = ReadInt32();
+ room.reverbType = (ReverbType)ReadInt32();
+ room.flipNumber = ReadInt32();
+
+ int staticCount = ReadInt32();
+ room.mesh.resize(0);
+ room.mesh.reserve(staticCount);
+
+ for (int j = 0; j < staticCount; j++)
+ {
+ auto& mesh = room.mesh.emplace_back();
+
+ mesh.roomNumber = i;
+ mesh.pos.Position.x = ReadInt32();
+ mesh.pos.Position.y = ReadInt32();
+ mesh.pos.Position.z = ReadInt32();
+ mesh.pos.Orientation.y = ReadUInt16();
+ mesh.pos.Orientation.x = ReadUInt16();
+ mesh.pos.Orientation.z = ReadUInt16();
+ mesh.scale = ReadFloat();
+ mesh.flags = ReadUInt16();
+ mesh.color = ReadVector4();
+ mesh.staticNumber = ReadUInt16();
+ mesh.HitPoints = ReadInt16();
+ mesh.Name = ReadString();
+
+ g_GameScriptEntities->AddName(mesh.Name, mesh);
+ }
+
+ int triggerVolumeCount = ReadInt32();
+ room.TriggerVolumes.resize(0);
+ room.TriggerVolumes.reserve(triggerVolumeCount);
+
+ for (int j = 0; j < triggerVolumeCount; j++)
+ {
+ auto& volume = room.TriggerVolumes.emplace_back();
+
+ volume.Type = (VolumeType)ReadInt32();
+
+ auto pos = ReadVector3();
+ auto orient = ReadVector4();
+ auto scale = ReadVector3();
+
+ volume.Enabled = ReadBool();
+ volume.DetectInAdjacentRooms = ReadBool();
+
+ volume.Name = ReadString();
+ volume.EventSetIndex = ReadInt32();
+
+ volume.Box = BoundingOrientedBox(pos, scale, orient);
+ volume.Sphere = BoundingSphere(pos, scale.x);
+
+ volume.StateQueue.reserve(VOLUME_STATE_QUEUE_SIZE);
+
+ g_GameScriptEntities->AddName(volume.Name, volume);
+ }
+
+ g_GameScriptEntities->AddName(room.Name, room);
+
+ room.itemNumber = NO_VALUE;
+ room.fxNumber = NO_VALUE;
+ }
+}
+
+void LoadStaticRoomData()
{
constexpr auto ILLEGAL_FLOOR_SLOPE_ANGLE = ANGLE(36.0f);
constexpr auto ILLEGAL_CEILING_SLOPE_ANGLE = ANGLE(45.0f);
@@ -687,12 +782,6 @@ void ReadRooms()
{
auto& room = g_Level.Rooms.emplace_back();
- room.Name = ReadString();
-
- int tagCount = ReadInt32();
- for (int j = 0; j < tagCount; j++)
- room.Tags.push_back(ReadString());
-
room.Position.x = ReadInt32();
room.Position.y = 0;
room.Position.z = ReadInt32();
@@ -824,8 +913,6 @@ void ReadRooms()
}
}
- room.ambient = ReadVector3();
-
int lightCount = ReadInt32();
room.lights.reserve(lightCount);
for (int j = 0; j < lightCount; j++)
@@ -851,67 +938,8 @@ void ReadRooms()
room.lights.push_back(light);
}
-
- int staticCount = ReadInt32();
- room.mesh.reserve(staticCount);
- for (int j = 0; j < staticCount; j++)
- {
- auto& mesh = room.mesh.emplace_back();
- mesh.roomNumber = i;
- mesh.pos.Position.x = ReadInt32();
- mesh.pos.Position.y = ReadInt32();
- mesh.pos.Position.z = ReadInt32();
- mesh.pos.Orientation.y = ReadUInt16();
- mesh.pos.Orientation.x = ReadUInt16();
- mesh.pos.Orientation.z = ReadUInt16();
- mesh.scale = ReadFloat();
- mesh.flags = ReadUInt16();
- mesh.color = ReadVector4();
- mesh.staticNumber = ReadUInt16();
- mesh.HitPoints = ReadInt16();
- mesh.Name = ReadString();
-
- g_GameScriptEntities->AddName(mesh.Name, mesh);
- }
-
- int triggerVolumeCount = ReadInt32();
- room.TriggerVolumes.reserve(triggerVolumeCount);
- for (int j = 0; j < triggerVolumeCount; j++)
- {
- auto& volume = room.TriggerVolumes.emplace_back();
-
- volume.Type = (VolumeType)ReadInt32();
-
- auto pos = ReadVector3();
- auto orient = ReadVector4();
- auto scale = ReadVector3();
-
- volume.Enabled = ReadBool();
- volume.DetectInAdjacentRooms = ReadBool();
-
- volume.Name = ReadString();
- volume.EventSetIndex = ReadInt32();
-
- volume.Box = BoundingOrientedBox(pos, scale, orient);
- volume.Sphere = BoundingSphere(pos, scale.x);
-
- volume.StateQueue.reserve(VOLUME_STATE_QUEUE_SIZE);
-
- g_GameScriptEntities->AddName(volume.Name, volume);
- }
-
- room.flippedRoom = ReadInt32();
- room.flags = ReadInt32();
- room.meshEffect = ReadInt32();
- room.reverbType = (ReverbType)ReadInt32();
- room.flipNumber = ReadInt32();
-
- room.itemNumber = NO_VALUE;
- room.fxNumber = NO_VALUE;
room.RoomNumber = i;
-
- g_GameScriptEntities->AddName(room.Name, room);
}
}
@@ -921,7 +949,7 @@ void LoadRooms()
Wibble = 0;
- ReadRooms();
+ LoadStaticRoomData();
BuildOutsideRoomsTable();
int numFloorData = ReadInt32();
@@ -929,15 +957,39 @@ void LoadRooms()
ReadBytes(g_Level.FloorData.data(), numFloorData * sizeof(short));
}
-void FreeLevel()
+void FreeLevel(bool partial)
{
- static bool firstLevel = true;
- if (firstLevel)
+ if (FirstLevel)
{
- firstLevel = false;
+ FirstLevel = false;
return;
}
+ // Should happen before resetting items.
+ if (partial)
+ ResetRoomData();
+
+ g_Level.Items.resize(0);
+ g_Level.AIObjects.resize(0);
+ g_Level.Cameras.resize(0);
+ g_Level.Sinks.resize(0);
+ g_Level.SoundSources.resize(0);
+ g_Level.VolumeEventSets.resize(0);
+ g_Level.GlobalEventSets.resize(0);
+ g_Level.LoopedEventSetIndices.resize(0);
+
+ g_GameScript->FreeLevelScripts();
+ g_GameScriptEntities->FreeEntities();
+
+ if (partial)
+ return;
+
+ g_Renderer.FreeRendererData();
+
+ MoveablesIds.resize(0);
+ StaticObjectsIds.resize(0);
+ SpriteSequencesIds.resize(0);
+
g_Level.RoomTextures.resize(0);
g_Level.MoveablesTextures.resize(0);
g_Level.StaticsTextures.resize(0);
@@ -947,8 +999,6 @@ void FreeLevel()
g_Level.Rooms.resize(0);
g_Level.Bones.resize(0);
g_Level.Meshes.resize(0);
- MoveablesIds.resize(0);
- SpriteSequencesIds.resize(0);
g_Level.PathfindingBoxes.resize(0);
g_Level.Overlaps.resize(0);
g_Level.Anims.resize(0);
@@ -960,14 +1010,6 @@ void FreeLevel()
g_Level.SoundDetails.resize(0);
g_Level.SoundMap.resize(0);
g_Level.FloorData.resize(0);
- g_Level.Cameras.resize(0);
- g_Level.Sinks.resize(0);
- g_Level.SoundSources.resize(0);
- g_Level.AIObjects.resize(0);
- g_Level.VolumeEventSets.resize(0);
- g_Level.GlobalEventSets.resize(0);
- g_Level.LoopedEventSetIndices.resize(0);
- g_Level.Items.resize(0);
for (int i = 0; i < 2; i++)
{
@@ -975,10 +1017,6 @@ void FreeLevel()
g_Level.Zones[j][i].clear();
}
- g_Renderer.FreeRendererData();
- g_GameScript->FreeLevelScripts();
- g_GameScriptEntities->FreeEntities();
-
FreeSamples();
}
@@ -1161,46 +1199,88 @@ bool Decompress(byte* dest, byte* src, unsigned long compressedSize, unsigned lo
return false;
}
-bool LoadLevel(int levelIndex)
+bool ReadCompressedBlock(FILE* filePtr, bool skip)
{
- auto* level = g_GameFlow->GetLevel(levelIndex);
+ int compressedSize = 0;
+ int uncompressedSize = 0;
- auto assetDir = g_GameFlow->GetGameDir();
- auto levelPath = assetDir + level->FileName;
- TENLog("Loading level file: " + levelPath, LogLevel::Info);
+ ReadFileEx(&uncompressedSize, 1, 4, filePtr);
+ ReadFileEx(&compressedSize, 1, 4, filePtr);
- LevelDataPtr = nullptr;
+ if (skip)
+ {
+ fseek(filePtr, compressedSize, SEEK_CUR);
+ return false;
+ }
+
+ auto compressedBuffer = (char*)malloc(compressedSize);
+ ReadFileEx(compressedBuffer, compressedSize, 1, filePtr);
+ DataPtr = (char*)malloc(uncompressedSize);
+ Decompress((byte*)DataPtr, (byte*)compressedBuffer, compressedSize, uncompressedSize);
+ free(compressedBuffer);
+
+ CurrentDataPtr = DataPtr;
+
+ return true;
+}
+
+void FinalizeBlock()
+{
+ if (DataPtr == nullptr)
+ return;
+
+ free(DataPtr);
+ DataPtr = nullptr;
+ CurrentDataPtr = nullptr;
+}
+
+void UpdateProgress(float progress, bool skip = false)
+{
+ if (skip)
+ return;
+
+ g_Renderer.UpdateProgress(progress);
+}
+
+bool LoadLevel(std::string path, bool partial)
+{
FILE* filePtr = nullptr;
- char* dataPtr = nullptr;
- bool LoadedSuccessfully;
-
- auto loadingScreenPath = TEN::Utils::ToWString(assetDir + level->LoadScreenFileName);
- g_Renderer.SetLoadingScreen(loadingScreenPath);
-
- SetScreenFadeIn(FADE_SCREEN_SPEED, true);
- g_Renderer.UpdateProgress(0);
+ bool loadedSuccessfully = false;
try
{
- filePtr = FileOpen(levelPath.c_str());
+ filePtr = FileOpen(path.c_str());
if (!filePtr)
- throw std::exception{ (std::string{ "Unable to read level file: " } + levelPath).c_str() };
+ throw std::exception{ (std::string{ "Unable to read level file: " } + path).c_str() };
char header[4];
unsigned char version[4];
- int compressedSize;
- int uncompressedSize;
- int systemHash;
+ int systemHash = 0;
+ int levelHash = 0;
// Read file header
ReadFileEx(&header, 1, 4, filePtr);
ReadFileEx(&version, 1, 4, filePtr);
ReadFileEx(&systemHash, 1, 4, filePtr);
+ ReadFileEx(&levelHash, 1, 4, filePtr);
- // Check file header
+ // Check file header.
if (std::string(header) != "TEN")
throw std::invalid_argument("Level file header is not valid! Must be TEN. Probably old level version?");
+
+ // Check level file integrity to allow or disallow fast reload.
+ if (partial && levelHash != LastLevelHash)
+ {
+ TENLog("Level file has changed since the last load; fast reload is not possible.", LogLevel::Warning);
+ partial = false;
+ FreeLevel(false); // Erase all precached data.
+ }
+
+ // Store information about last loaded level file.
+ LastLevelFilePath = path;
+ LastLevelHash = levelHash;
+ LastLevelTimestamp = std::filesystem::last_write_time(path);
TENLog("Level compiler version: " + std::to_string(version[0]) + "." + std::to_string(version[1]) + "." + std::to_string(version[2]), LogLevel::Info);
@@ -1225,95 +1305,105 @@ bool LoadLevel(int levelIndex)
SystemNameHash = 0;
}
- // Read data sizes
- ReadFileEx(&uncompressedSize, 1, 4, filePtr);
- ReadFileEx(&compressedSize, 1, 4, filePtr);
+ if (partial)
+ {
+ TENLog("Loading same level. Skipping media and geometry data.", LogLevel::Info);
+ SetScreenFadeOut(FADE_SCREEN_SPEED * 2, true);
+ }
+ else
+ {
+ SetScreenFadeIn(FADE_SCREEN_SPEED, true);
+ }
- // The entire level is ZLIB compressed
- auto compressedBuffer = (char*)malloc(compressedSize);
- dataPtr = (char*)malloc(uncompressedSize);
- LevelDataPtr = dataPtr;
+ UpdateProgress(0);
- ReadFileEx(compressedBuffer, compressedSize, 1, filePtr);
- Decompress((byte*)LevelDataPtr, (byte*)compressedBuffer, compressedSize, uncompressedSize);
+ // Media block
+ if (ReadCompressedBlock(filePtr, partial))
+ {
+ LoadTextures();
+ UpdateProgress(30);
- // Now the entire level is decompressed, we can close it
- free(compressedBuffer);
- FileClose(filePtr);
- filePtr = nullptr;
+ LoadSamples();
+ UpdateProgress(40);
- LoadTextures();
+ FinalizeBlock();
+ }
- g_Renderer.UpdateProgress(20);
+ // Geometry block
+ if (ReadCompressedBlock(filePtr, partial))
+ {
+ LoadRooms();
+ UpdateProgress(50);
- LoadRooms();
- g_Renderer.UpdateProgress(40);
+ LoadObjects();
+ UpdateProgress(60);
- LoadObjects();
- g_Renderer.UpdateProgress(50);
+ LoadSprites();
+ LoadBoxes();
+ LoadAnimatedTextures();
+ UpdateProgress(70);
- LoadSprites();
- LoadCameras();
- LoadSoundSources();
- g_Renderer.UpdateProgress(60);
+ FinalizeBlock();
+ }
- LoadBoxes();
+ // Dynamic data block
+ if (ReadCompressedBlock(filePtr, false))
+ {
+ LoadDynamicRoomData();
+ LoadItems();
+ LoadAIObjects();
+ LoadCameras();
+ LoadSoundSources();
+ LoadEventSets();
+ UpdateProgress(80, partial);
- //InitializeLOTarray(true);
-
- LoadAnimatedTextures();
- g_Renderer.UpdateProgress(70);
-
- LoadItems();
- LoadAIObjects();
-
- LoadEventSets();
-
- LoadSamples();
- g_Renderer.UpdateProgress(80);
+ FinalizeBlock();
+ }
TENLog("Initializing level...", LogLevel::Info);
- // Initialize the game
+ // Initialize game.
InitializeGameFlags();
InitializeLara(!InitializeGame && CurrentLevel > 0);
InitializeNeighborRoomList();
GetCarriedItems();
GetAIPickups();
g_GameScriptEntities->AssignLara();
- g_Renderer.UpdateProgress(90);
+ UpdateProgress(90, partial);
- TENLog("Preparing renderer...", LogLevel::Info);
+ if (!partial)
+ {
+ g_Renderer.PrepareDataForTheRenderer();
+ SetScreenFadeOut(FADE_SCREEN_SPEED, true);
+ StopSoundTracks(SOUND_XFADETIME_BGM_START);
+ }
+ else
+ {
+ SetScreenFadeIn(FADE_SCREEN_SPEED, true);
+ StopSoundTracks(SOUND_XFADETIME_LEVELJUMP);
+ }
- g_Renderer.PrepareDataForTheRenderer();
+ UpdateProgress(100, partial);
TENLog("Level loading complete.", LogLevel::Info);
- SetScreenFadeOut(FADE_SCREEN_SPEED, true);
- g_Renderer.UpdateProgress(100);
-
- LoadedSuccessfully = true;
+ loadedSuccessfully = true;
}
catch (std::exception& ex)
{
- if (filePtr)
- {
- FileClose(filePtr);
- filePtr = nullptr;
- }
+ FinalizeBlock();
+ StopSoundTracks(SOUND_XFADETIME_LEVELJUMP);
TENLog("Error while loading level: " + std::string(ex.what()), LogLevel::Error);
- LoadedSuccessfully = false;
+ loadedSuccessfully = false;
SystemNameHash = 0;
}
- if (dataPtr)
- {
- free(dataPtr);
- dataPtr = LevelDataPtr = nullptr;
- }
+ // Now the entire level is decompressed, we can close it
+ FileClose(filePtr);
+ filePtr = nullptr;
- return LoadedSuccessfully;
+ return loadedSuccessfully;
}
void LoadSamples()
@@ -1386,7 +1476,7 @@ void LoadBoxes()
int excessiveZoneGroups = numZoneGroups - j + 1;
TENLog("Level file contains extra pathfinding data, number of excessive zone groups is " +
std::to_string(excessiveZoneGroups) + ". These zone groups will be ignored.", LogLevel::Warning);
- LevelDataPtr += numBoxes * sizeof(int);
+ CurrentDataPtr += numBoxes * sizeof(int);
}
else
{
@@ -1406,13 +1496,35 @@ void LoadBoxes()
bool LoadLevelFile(int levelIndex)
{
- TENLog("Loading level file...", LogLevel::Info);
+ const auto& level = *g_GameFlow->GetLevel(levelIndex);
+
+ auto assetDir = g_GameFlow->GetGameDir();
+ auto levelPath = assetDir + level.FileName;
+
+ if (!std::filesystem::is_regular_file(levelPath))
+ {
+ TENLog("Level file not found: " + levelPath, LogLevel::Error);
+ return false;
+ }
+
+ TENLog("Loading level file: " + levelPath, LogLevel::Info);
+
+ auto timestamp = std::filesystem::last_write_time(levelPath);
+ bool fastReload = (g_GameFlow->GetSettings()->FastReload && levelIndex == CurrentLevel && timestamp == LastLevelTimestamp && levelPath == LastLevelFilePath);
+
+ // Dumping game scene right after engine launch is impossible, as no scene exists.
+ if (!FirstLevel && fastReload)
+ g_Renderer.DumpGameScene();
+
+ auto loadingScreenPath = TEN::Utils::ToWString(assetDir + level.LoadScreenFileName);
+ g_Renderer.SetLoadingScreen(fastReload ? std::wstring{} : loadingScreenPath);
BackupLara();
+ StopAllSounds();
CleanUp();
- FreeLevel();
+ FreeLevel(fastReload);
- LevelLoadTask = std::async(std::launch::async, LoadLevel, levelIndex);
+ LevelLoadTask = std::async(std::launch::async, LoadLevel, levelPath, fastReload);
return LevelLoadTask.get();
}
diff --git a/TombEngine/Specific/level.h b/TombEngine/Specific/level.h
index 1c0c84853..ef6c260f8 100644
--- a/TombEngine/Specific/level.h
+++ b/TombEngine/Specific/level.h
@@ -140,6 +140,8 @@ extern std::vector MoveablesIds;
extern std::vector StaticObjectsIds;
extern std::vector SpriteSequencesIds;
extern LEVEL g_Level;
+extern int SystemNameHash;
+extern int LastLevelHash;
inline std::future LevelLoadTask;
@@ -149,7 +151,7 @@ void FileClose(FILE* ptr);
bool Decompress(byte* dest, byte* src, unsigned long compressedSize, unsigned long uncompressedSize);
bool LoadLevelFile(int levelIndex);
-void FreeLevel();
+void FreeLevel(bool partial);
void LoadTextures();
void LoadRooms();
diff --git a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h
index 3bbe2dae6..2cb2b7ffc 100644
--- a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h
+++ b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h
@@ -7017,6 +7017,7 @@ flatbuffers::Offset CreateUnionVec(flatbuffers::FlatBufferBuilder &_fb
struct SaveGameHeaderT : public flatbuffers::NativeTable {
typedef SaveGameHeader TableType;
std::string level_name{};
+ int32_t level_hash = 0;
int32_t days = 0;
int32_t hours = 0;
int32_t minutes = 0;
@@ -7032,17 +7033,21 @@ struct SaveGameHeader FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
struct Traits;
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
VT_LEVEL_NAME = 4,
- VT_DAYS = 6,
- VT_HOURS = 8,
- VT_MINUTES = 10,
- VT_SECONDS = 12,
- VT_LEVEL = 14,
- VT_TIMER = 16,
- VT_COUNT = 18
+ VT_LEVEL_HASH = 6,
+ VT_DAYS = 8,
+ VT_HOURS = 10,
+ VT_MINUTES = 12,
+ VT_SECONDS = 14,
+ VT_LEVEL = 16,
+ VT_TIMER = 18,
+ VT_COUNT = 20
};
const flatbuffers::String *level_name() const {
return GetPointer(VT_LEVEL_NAME);
}
+ int32_t level_hash() const {
+ return GetField(VT_LEVEL_HASH, 0);
+ }
int32_t days() const {
return GetField(VT_DAYS, 0);
}
@@ -7068,6 +7073,7 @@ struct SaveGameHeader FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
return VerifyTableStart(verifier) &&
VerifyOffset(verifier, VT_LEVEL_NAME) &&
verifier.VerifyString(level_name()) &&
+ VerifyField(verifier, VT_LEVEL_HASH) &&
VerifyField(verifier, VT_DAYS) &&
VerifyField(verifier, VT_HOURS) &&
VerifyField(verifier, VT_MINUTES) &&
@@ -7089,6 +7095,9 @@ struct SaveGameHeaderBuilder {
void add_level_name(flatbuffers::Offset level_name) {
fbb_.AddOffset(SaveGameHeader::VT_LEVEL_NAME, level_name);
}
+ void add_level_hash(int32_t level_hash) {
+ fbb_.AddElement(SaveGameHeader::VT_LEVEL_HASH, level_hash, 0);
+ }
void add_days(int32_t days) {
fbb_.AddElement(SaveGameHeader::VT_DAYS, days, 0);
}
@@ -7124,6 +7133,7 @@ struct SaveGameHeaderBuilder {
inline flatbuffers::Offset CreateSaveGameHeader(
flatbuffers::FlatBufferBuilder &_fbb,
flatbuffers::Offset level_name = 0,
+ int32_t level_hash = 0,
int32_t days = 0,
int32_t hours = 0,
int32_t minutes = 0,
@@ -7139,6 +7149,7 @@ inline flatbuffers::Offset CreateSaveGameHeader(
builder_.add_minutes(minutes);
builder_.add_hours(hours);
builder_.add_days(days);
+ builder_.add_level_hash(level_hash);
builder_.add_level_name(level_name);
return builder_.Finish();
}
@@ -7151,6 +7162,7 @@ struct SaveGameHeader::Traits {
inline flatbuffers::Offset CreateSaveGameHeaderDirect(
flatbuffers::FlatBufferBuilder &_fbb,
const char *level_name = nullptr,
+ int32_t level_hash = 0,
int32_t days = 0,
int32_t hours = 0,
int32_t minutes = 0,
@@ -7162,6 +7174,7 @@ inline flatbuffers::Offset CreateSaveGameHeaderDirect(
return TEN::Save::CreateSaveGameHeader(
_fbb,
level_name__,
+ level_hash,
days,
hours,
minutes,
@@ -10245,6 +10258,7 @@ inline void SaveGameHeader::UnPackTo(SaveGameHeaderT *_o, const flatbuffers::res
(void)_o;
(void)_resolver;
{ auto _e = level_name(); if (_e) _o->level_name = _e->str(); }
+ { auto _e = level_hash(); _o->level_hash = _e; }
{ auto _e = days(); _o->days = _e; }
{ auto _e = hours(); _o->hours = _e; }
{ auto _e = minutes(); _o->minutes = _e; }
@@ -10263,6 +10277,7 @@ inline flatbuffers::Offset CreateSaveGameHeader(flatbuffers::Fla
(void)_o;
struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const SaveGameHeaderT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va;
auto _level_name = _o->level_name.empty() ? _fbb.CreateSharedString("") : _fbb.CreateString(_o->level_name);
+ auto _level_hash = _o->level_hash;
auto _days = _o->days;
auto _hours = _o->hours;
auto _minutes = _o->minutes;
@@ -10273,6 +10288,7 @@ inline flatbuffers::Offset CreateSaveGameHeader(flatbuffers::Fla
return TEN::Save::CreateSaveGameHeader(
_fbb,
_level_name,
+ _level_hash,
_days,
_hours,
_minutes,
diff --git a/TombEngine/Specific/savegame/schema/ten_savegame.fbs b/TombEngine/Specific/savegame/schema/ten_savegame.fbs
index d7089ad40..634b2c9f8 100644
--- a/TombEngine/Specific/savegame/schema/ten_savegame.fbs
+++ b/TombEngine/Specific/savegame/schema/ten_savegame.fbs
@@ -504,6 +504,7 @@ table UnionVec {
table SaveGameHeader {
level_name: string;
+ level_hash: int32;
days: int32;
hours: int32;
minutes: int32;
diff --git a/TombEngine/framework.h b/TombEngine/framework.h
index 6740dadd0..663b28f38 100644
--- a/TombEngine/framework.h
+++ b/TombEngine/framework.h
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include
#include
#include