From ef12c92e308dbd09d809c33500265c6813fd9d22 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:15:14 -0500 Subject: [PATCH 01/20] First --- TombEngine/Game/Lara/lara_struct.h | 8 ++ .../flatbuffers/ten_savegame_generated.h | 130 ++++++++++++++++++ .../Specific/savegame/schema/ten_savegame.fbs | 7 + 3 files changed, 145 insertions(+) diff --git a/TombEngine/Game/Lara/lara_struct.h b/TombEngine/Game/Lara/lara_struct.h index 42a446380a..37ab1f98e7 100644 --- a/TombEngine/Game/Lara/lara_struct.h +++ b/TombEngine/Game/Lara/lara_struct.h @@ -1273,6 +1273,14 @@ struct PlayerEffectData std::array BubbleNodes = {}; }; +struct PlayerSkinData +{ + GAME_OBJECT_ID Skin = ID_LARA_SKIN; + GAME_OBJECT_ID SkinJoints = ID_LARA_SKIN_JOINTS; + GAME_OBJECT_ID HairPrimary = ID_HAIR_PRIMARY; + GAME_OBJECT_ID HairSecondary = ID_HAIR_SECONDARY; +}; + struct PlayerInventoryData { bool IsBusy = false; diff --git a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h index f256c91048..da541bc2f0 100644 --- a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h +++ b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h @@ -41,6 +41,10 @@ struct WeaponInfo; struct WeaponInfoBuilder; struct WeaponInfoT; +struct PlayerSkin; +struct PlayerSkinBuilder; +struct PlayerSkinT; + struct ArmInfo; struct ArmInfoBuilder; struct ArmInfoT; @@ -2008,6 +2012,97 @@ struct WeaponInfo::Traits { flatbuffers::Offset CreateWeaponInfo(flatbuffers::FlatBufferBuilder &_fbb, const WeaponInfoT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct PlayerSkinT : public flatbuffers::NativeTable { + typedef PlayerSkin TableType; + uint32_t skin = 0; + uint32_t skin_joints = 0; + uint32_t hair_primary = 0; + uint32_t hair_secondary = 0; +}; + +struct PlayerSkin FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef PlayerSkinT NativeTableType; + typedef PlayerSkinBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_SKIN = 4, + VT_SKIN_JOINTS = 6, + VT_HAIR_PRIMARY = 8, + VT_HAIR_SECONDARY = 10 + }; + uint32_t skin() const { + return GetField(VT_SKIN, 0); + } + uint32_t skin_joints() const { + return GetField(VT_SKIN_JOINTS, 0); + } + uint32_t hair_primary() const { + return GetField(VT_HAIR_PRIMARY, 0); + } + uint32_t hair_secondary() const { + return GetField(VT_HAIR_SECONDARY, 0); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_SKIN) && + VerifyField(verifier, VT_SKIN_JOINTS) && + VerifyField(verifier, VT_HAIR_PRIMARY) && + VerifyField(verifier, VT_HAIR_SECONDARY) && + verifier.EndTable(); + } + PlayerSkinT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(PlayerSkinT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct PlayerSkinBuilder { + typedef PlayerSkin Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_skin(uint32_t skin) { + fbb_.AddElement(PlayerSkin::VT_SKIN, skin, 0); + } + void add_skin_joints(uint32_t skin_joints) { + fbb_.AddElement(PlayerSkin::VT_SKIN_JOINTS, skin_joints, 0); + } + void add_hair_primary(uint32_t hair_primary) { + fbb_.AddElement(PlayerSkin::VT_HAIR_PRIMARY, hair_primary, 0); + } + void add_hair_secondary(uint32_t hair_secondary) { + fbb_.AddElement(PlayerSkin::VT_HAIR_SECONDARY, hair_secondary, 0); + } + explicit PlayerSkinBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreatePlayerSkin( + flatbuffers::FlatBufferBuilder &_fbb, + uint32_t skin = 0, + uint32_t skin_joints = 0, + uint32_t hair_primary = 0, + uint32_t hair_secondary = 0) { + PlayerSkinBuilder builder_(_fbb); + builder_.add_hair_secondary(hair_secondary); + builder_.add_hair_primary(hair_primary); + builder_.add_skin_joints(skin_joints); + builder_.add_skin(skin); + return builder_.Finish(); +} + +struct PlayerSkin::Traits { + using type = PlayerSkin; + static auto constexpr Create = CreatePlayerSkin; +}; + +flatbuffers::Offset CreatePlayerSkin(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct ArmInfoT : public flatbuffers::NativeTable { typedef ArmInfo TableType; int32_t anim_number = 0; @@ -8805,6 +8900,41 @@ inline flatbuffers::Offset CreateWeaponInfo(flatbuffers::FlatBufferB _target_state); } +inline PlayerSkinT *PlayerSkin::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void PlayerSkin::UnPackTo(PlayerSkinT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = skin(); _o->skin = _e; } + { auto _e = skin_joints(); _o->skin_joints = _e; } + { auto _e = hair_primary(); _o->hair_primary = _e; } + { auto _e = hair_secondary(); _o->hair_secondary = _e; } +} + +inline flatbuffers::Offset PlayerSkin::Pack(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreatePlayerSkin(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreatePlayerSkin(flatbuffers::FlatBufferBuilder &_fbb, const PlayerSkinT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const PlayerSkinT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _skin = _o->skin; + auto _skin_joints = _o->skin_joints; + auto _hair_primary = _o->hair_primary; + auto _hair_secondary = _o->hair_secondary; + return TEN::Save::CreatePlayerSkin( + _fbb, + _skin, + _skin_joints, + _hair_primary, + _hair_secondary); +} + inline ArmInfoT *ArmInfo::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = std::make_unique(); UnPackTo(_o.get(), _resolver); diff --git a/TombEngine/Specific/savegame/schema/ten_savegame.fbs b/TombEngine/Specific/savegame/schema/ten_savegame.fbs index 4b2000a373..cec827ce35 100644 --- a/TombEngine/Specific/savegame/schema/ten_savegame.fbs +++ b/TombEngine/Specific/savegame/schema/ten_savegame.fbs @@ -107,6 +107,13 @@ table WeaponInfo { target_state: uint32; } +table PlayerSkin { + skin: uint32; + skin_joints: uint32; + hair_primary: uint32; + hair_secondary: uint32; +} + table ArmInfo { anim_number: int32; frame_number: int32; From 8828fb16a7d7bf1bb544ae367a9238815ef0b8f8 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:34:25 -0500 Subject: [PATCH 02/20] Savegame --- TombEngine/Game/Lara/lara_struct.h | 1 + TombEngine/Game/savegame.cpp | 11 ++++++++ .../flatbuffers/ten_savegame_generated.h | 25 ++++++++++++++++--- .../Specific/savegame/schema/ten_savegame.fbs | 1 + 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/TombEngine/Game/Lara/lara_struct.h b/TombEngine/Game/Lara/lara_struct.h index 37ab1f98e7..c80e8425c5 100644 --- a/TombEngine/Game/Lara/lara_struct.h +++ b/TombEngine/Game/Lara/lara_struct.h @@ -1327,6 +1327,7 @@ struct LaraInfo PlayerStatusData Status = {}; PlayerEffectData Effect = {}; PlayerInventoryData Inventory = {}; + PlayerSkinData Skin = {}; // TODO: Move to PlayerControlData. FlareData Flare = {}; diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp index 6725159cd1..f3edca7a21 100644 --- a/TombEngine/Game/savegame.cpp +++ b/TombEngine/Game/savegame.cpp @@ -342,6 +342,13 @@ const std::vector SaveGame::Build() subsuitVelocity.push_back(Lara.Control.Subsuit.Velocity[1]); auto subsuitVelocityOffset = fbb.CreateVector(subsuitVelocity); + Save::PlayerSkinBuilder playerSkin{ fbb }; + playerSkin.add_skin((int)Lara.Skin.Skin); + playerSkin.add_skin_joints((int)Lara.Skin.SkinJoints); + playerSkin.add_hair_primary((int)Lara.Skin.HairPrimary); + playerSkin.add_hair_secondary((int)Lara.Skin.HairSecondary); + auto playerSkinOffset = playerSkin.Finish(); + Save::HolsterInfoBuilder holsterInfo{ fbb }; holsterInfo.add_back_holster((int)Lara.Control.Weapon.HolsterInfo.BackHolster); holsterInfo.add_left_holster((int)Lara.Control.Weapon.HolsterInfo.LeftHolster); @@ -2066,6 +2073,10 @@ static void ParsePlayer(const Save::SaveGame* s) Lara.Status.Stamina = s->lara()->status()->stamina(); Lara.TargetEntity = (s->lara()->target_entity_number() >= 0) ? &g_Level.Items[s->lara()->target_entity_number()] : nullptr; Lara.TargetArmOrient = ToEulerAngles(s->lara()->target_arm_orient()); + Lara.Skin.Skin = static_cast(s->lara()->skin()->skin()); + Lara.Skin.SkinJoints = static_cast(s->lara()->skin()->skin_joints()); + Lara.Skin.HairPrimary = static_cast(s->lara()->skin()->hair_primary()); + Lara.Skin.HairSecondary = static_cast(s->lara()->skin()->hair_secondary()); for (int i = 0; i < s->lara()->weapons()->size(); i++) { diff --git a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h index da541bc2f0..838579925d 100644 --- a/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h +++ b/TombEngine/Specific/savegame/flatbuffers/ten_savegame_generated.h @@ -4321,6 +4321,7 @@ struct LaraT : public flatbuffers::NativeTable { int32_t location_pad = 0; std::unique_ptr right_arm{}; std::unique_ptr status{}; + std::unique_ptr skin{}; std::unique_ptr target_arm_orient{}; int32_t target_entity_number = 0; std::unique_ptr torch{}; @@ -4349,10 +4350,11 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VT_LOCATION_PAD = 32, VT_RIGHT_ARM = 34, VT_STATUS = 36, - VT_TARGET_ARM_ORIENT = 38, - VT_TARGET_ENTITY_NUMBER = 40, - VT_TORCH = 42, - VT_WEAPONS = 44 + VT_SKIN = 38, + VT_TARGET_ARM_ORIENT = 40, + VT_TARGET_ENTITY_NUMBER = 42, + VT_TORCH = 44, + VT_WEAPONS = 46 }; const TEN::Save::PlayerContextData *context() const { return GetPointer(VT_CONTEXT); @@ -4405,6 +4407,9 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const TEN::Save::PlayerStatusData *status() const { return GetPointer(VT_STATUS); } + const TEN::Save::PlayerSkin *skin() const { + return GetPointer(VT_SKIN); + } const TEN::Save::EulerAngles *target_arm_orient() const { return GetStruct(VT_TARGET_ARM_ORIENT); } @@ -4445,6 +4450,8 @@ struct Lara FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { verifier.VerifyTable(right_arm()) && VerifyOffset(verifier, VT_STATUS) && verifier.VerifyTable(status()) && + VerifyOffset(verifier, VT_SKIN) && + verifier.VerifyTable(skin()) && VerifyField(verifier, VT_TARGET_ARM_ORIENT) && VerifyField(verifier, VT_TARGET_ENTITY_NUMBER) && VerifyOffset(verifier, VT_TORCH) && @@ -4514,6 +4521,9 @@ struct LaraBuilder { void add_status(flatbuffers::Offset status) { fbb_.AddOffset(Lara::VT_STATUS, status); } + void add_skin(flatbuffers::Offset skin) { + fbb_.AddOffset(Lara::VT_SKIN, skin); + } void add_target_arm_orient(const TEN::Save::EulerAngles *target_arm_orient) { fbb_.AddStruct(Lara::VT_TARGET_ARM_ORIENT, target_arm_orient); } @@ -4556,6 +4566,7 @@ inline flatbuffers::Offset CreateLara( int32_t location_pad = 0, flatbuffers::Offset right_arm = 0, flatbuffers::Offset status = 0, + flatbuffers::Offset skin = 0, const TEN::Save::EulerAngles *target_arm_orient = 0, int32_t target_entity_number = 0, flatbuffers::Offset torch = 0, @@ -4565,6 +4576,7 @@ inline flatbuffers::Offset CreateLara( builder_.add_torch(torch); builder_.add_target_entity_number(target_entity_number); builder_.add_target_arm_orient(target_arm_orient); + builder_.add_skin(skin); builder_.add_status(status); builder_.add_right_arm(right_arm); builder_.add_location_pad(location_pad); @@ -4609,6 +4621,7 @@ inline flatbuffers::Offset CreateLaraDirect( int32_t location_pad = 0, flatbuffers::Offset right_arm = 0, flatbuffers::Offset status = 0, + flatbuffers::Offset skin = 0, const TEN::Save::EulerAngles *target_arm_orient = 0, int32_t target_entity_number = 0, flatbuffers::Offset torch = 0, @@ -4633,6 +4646,7 @@ inline flatbuffers::Offset CreateLaraDirect( location_pad, right_arm, status, + skin, target_arm_orient, target_entity_number, torch, @@ -9681,6 +9695,7 @@ inline void Lara::UnPackTo(LaraT *_o, const flatbuffers::resolver_function_t *_r { auto _e = location_pad(); _o->location_pad = _e; } { auto _e = right_arm(); if (_e) _o->right_arm = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = status(); if (_e) _o->status = std::unique_ptr(_e->UnPack(_resolver)); } + { auto _e = skin(); if (_e) _o->skin = std::unique_ptr(_e->UnPack(_resolver)); } { auto _e = target_arm_orient(); if (_e) _o->target_arm_orient = std::unique_ptr(new TEN::Save::EulerAngles(*_e)); } { auto _e = target_entity_number(); _o->target_entity_number = _e; } { auto _e = torch(); if (_e) _o->torch = std::unique_ptr(_e->UnPack(_resolver)); } @@ -9712,6 +9727,7 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb auto _location_pad = _o->location_pad; auto _right_arm = _o->right_arm ? CreateArmInfo(_fbb, _o->right_arm.get(), _rehasher) : 0; auto _status = _o->status ? CreatePlayerStatusData(_fbb, _o->status.get(), _rehasher) : 0; + auto _skin = _o->skin ? CreatePlayerSkin(_fbb, _o->skin.get(), _rehasher) : 0; auto _target_arm_orient = _o->target_arm_orient ? _o->target_arm_orient.get() : 0; auto _target_entity_number = _o->target_entity_number; auto _torch = _o->torch ? CreateTorchData(_fbb, _o->torch.get(), _rehasher) : 0; @@ -9735,6 +9751,7 @@ inline flatbuffers::Offset CreateLara(flatbuffers::FlatBufferBuilder &_fbb _location_pad, _right_arm, _status, + _skin, _target_arm_orient, _target_entity_number, _torch, diff --git a/TombEngine/Specific/savegame/schema/ten_savegame.fbs b/TombEngine/Specific/savegame/schema/ten_savegame.fbs index cec827ce35..2c38f39aea 100644 --- a/TombEngine/Specific/savegame/schema/ten_savegame.fbs +++ b/TombEngine/Specific/savegame/schema/ten_savegame.fbs @@ -309,6 +309,7 @@ table Lara { location_pad: int32; right_arm: ArmInfo; status: PlayerStatusData; + skin: PlayerSkin; target_arm_orient: EulerAngles; target_entity_number: int32; torch: TorchData; From d362b496ae98de6d9716ca29b691cd2ef9e644cb Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:39:55 -0500 Subject: [PATCH 03/20] ScriptExport --- .../Internal/TEN/Objects/Lara/LaraObject.cpp | 17 +++++++++++++++++ .../Internal/TEN/Objects/Lara/LaraObject.h | 1 + 2 files changed, 18 insertions(+) diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp index bd88cd5261..af813baf25 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp @@ -390,6 +390,22 @@ bool LaraObject::TorchIsLit() const return lara->Torch.IsLit; } +/// Set Lara weapon type +// @function LaraObject:SetWeaponType +// @usage +// Lara:SetWeaponType(WeaponType.PISTOLS, false) +// @tparam Flow.WeaponType weaponType +// @tparam bool activate if `true`, also draw the weapons or set torch lit. If `false`, keep weapons holstered or leave torch unlit. +void LaraObject::SetSkin(GAME_OBJECT_ID skin, GAME_OBJECT_ID skinJoints, GAME_OBJECT_ID hair1, GAME_OBJECT_ID hair2) +{ + auto* lara = GetLaraInfo(_moveable); + + lara->Skin.Skin = skin; + lara->Skin.SkinJoints = skinJoints; + lara->Skin.HairPrimary = hair1; + lara->Skin.HairSecondary = hair2; +} + void LaraObject::Register(sol::table& parent) { parent.new_usertype(LUA_CLASS_NAME, @@ -414,6 +430,7 @@ void LaraObject::Register(sol::table& parent) ScriptReserved_GetTarget, &LaraObject::GetTarget, ScriptReserved_GetPlayerInteractedMoveable, &LaraObject::GetPlayerInteractedMoveable, ScriptReserved_TorchIsLit, &LaraObject::TorchIsLit, + "SetSkin", &LaraObject::SetSkin, sol::base_classes, sol::bases() ); } diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h index 4b519becef..39764a11fd 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.h @@ -31,4 +31,5 @@ class LaraObject : public Moveable void ThrowAwayTorch(); bool TorchIsLit() const; using Moveable::Moveable; + void SetSkin(GAME_OBJECT_ID skin, GAME_OBJECT_ID skinJoints, GAME_OBJECT_ID hair1, GAME_OBJECT_ID hair2); }; From 95c734be54174f60ef312445a5aff7f87e042d8d Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Thu, 6 Mar 2025 21:54:09 -0500 Subject: [PATCH 04/20] WIP --- TombEngine/Game/effects/hair.cpp | 2 +- TombEngine/Renderer/RendererCompatibility.cpp | 15 ++++++++------- TombEngine/Renderer/RendererLara.cpp | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/TombEngine/Game/effects/hair.cpp b/TombEngine/Game/effects/hair.cpp index 8e657c204c..ac44981532 100644 --- a/TombEngine/Game/effects/hair.cpp +++ b/TombEngine/Game/effects/hair.cpp @@ -349,7 +349,7 @@ namespace TEN::Effects::Hair { auto& unit = Units[i]; - auto objectID = (i == 0) ? ID_HAIR_PRIMARY : ID_HAIR_SECONDARY; + auto objectID = (i == 0) ? Lara.Skin.HairPrimary : Lara.Skin.HairSecondary; const auto& object = Objects[objectID]; unit.IsEnabled = (object.loaded && (i == 0 || (i == 1 && isYoung))); diff --git a/TombEngine/Renderer/RendererCompatibility.cpp b/TombEngine/Renderer/RendererCompatibility.cpp index e8cc461361..c9ba0cf63d 100644 --- a/TombEngine/Renderer/RendererCompatibility.cpp +++ b/TombEngine/Renderer/RendererCompatibility.cpp @@ -7,6 +7,7 @@ #include "Game/control/control.h" #include "Game/effects/Hair.h" +#include "Game/Lara/lara.h" #include "Game/Lara/lara_struct.h" #include "Game/savegame.h" #include "Game/Setup.h" @@ -530,8 +531,8 @@ namespace TEN::Renderer RendererMesh *mesh = GetRendererMeshFromTrMesh( &moveable, &g_Level.Meshes[obj->meshIndex + j], - j, MoveablesIds[i] == ID_LARA_SKIN_JOINTS, - MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY, &lastVertex, &lastIndex); + j, MoveablesIds[i] == Lara.Skin.SkinJoints, + MoveablesIds[i] == Lara.Skin.HairPrimary || MoveablesIds[i] == Lara.Skin.HairSecondary, &lastVertex, &lastIndex); moveable.ObjectMeshes.push_back(mesh); _meshes.push_back(mesh); @@ -634,12 +635,12 @@ namespace TEN::Renderer BuildHierarchy(&moveable); // Fix player skin joints and hair units. - if (MoveablesIds[i] == ID_LARA_SKIN_JOINTS) + if (MoveablesIds[i] == Lara.Skin.SkinJoints) { isSkinPresent = true; int bonesToCheck[2] = { 0, 0 }; - const auto& objSkin = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); + const auto& objSkin = GetRendererObject(Lara.Skin.Skin); for (int j = 1; j < obj->nmeshes; j++) { @@ -703,11 +704,11 @@ namespace TEN::Renderer } } } - else if ((MoveablesIds[i] == ID_HAIR_PRIMARY || MoveablesIds[i] == ID_HAIR_SECONDARY) && isSkinPresent) + else if ((MoveablesIds[i] == Lara.Skin.HairPrimary || MoveablesIds[i] == Lara.Skin.HairSecondary) && isSkinPresent) { bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); - bool isSecond = isYoung && MoveablesIds[i] == ID_HAIR_SECONDARY; - const auto& skinObj = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); + bool isSecond = isYoung && MoveablesIds[i] == Lara.Skin.HairSecondary; + const auto& skinObj = GetRendererObject(Lara.Skin.SkinJoints); const auto& settings = g_GameFlow->GetSettings()->Hair; for (int j = 0; j < obj->nmeshes; j++) diff --git a/TombEngine/Renderer/RendererLara.cpp b/TombEngine/Renderer/RendererLara.cpp index 6ed781f8b8..e6135a5c3f 100644 --- a/TombEngine/Renderer/RendererLara.cpp +++ b/TombEngine/Renderer/RendererLara.cpp @@ -303,7 +303,7 @@ void Renderer::DrawLara(RenderView& view, RendererPass rendererPass) _context->IASetIndexBuffer(_moveablesIndexBuffer.Buffer.Get(), DXGI_FORMAT_R32_UINT, 0); RendererObject& laraObj = *_moveableObjects[ID_LARA]; - RendererObject& laraSkin = GetRendererObject(GAME_OBJECT_ID::ID_LARA_SKIN); + RendererObject& laraSkin = GetRendererObject(Lara.Skin.Skin); RendererRoom* room = &_rooms[LaraItem->RoomNumber]; @@ -382,10 +382,10 @@ void Renderer::DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, Render void Renderer::DrawLaraJoints(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass) { - if (!_moveableObjects[ID_LARA_SKIN_JOINTS].has_value()) + if (!_moveableObjects[Lara.Skin.SkinJoints].has_value()) return; - RendererObject& laraSkinJoints = *_moveableObjects[ID_LARA_SKIN_JOINTS]; + RendererObject& laraSkinJoints = *_moveableObjects[Lara.Skin.SkinJoints]; for (int k = 1; k < laraSkinJoints.ObjectMeshes.size(); k++) { From 05c99cbb8b25313d2366e6e6543a6289cfaeebed Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Thu, 6 Mar 2025 22:06:49 -0500 Subject: [PATCH 05/20] Fix Savegame --- TombEngine/Game/Lara/lara_struct.h | 16 +++++++++------- TombEngine/Game/savegame.cpp | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/TombEngine/Game/Lara/lara_struct.h b/TombEngine/Game/Lara/lara_struct.h index c80e8425c5..59f888c316 100644 --- a/TombEngine/Game/Lara/lara_struct.h +++ b/TombEngine/Game/Lara/lara_struct.h @@ -1273,13 +1273,7 @@ struct PlayerEffectData std::array BubbleNodes = {}; }; -struct PlayerSkinData -{ - GAME_OBJECT_ID Skin = ID_LARA_SKIN; - GAME_OBJECT_ID SkinJoints = ID_LARA_SKIN_JOINTS; - GAME_OBJECT_ID HairPrimary = ID_HAIR_PRIMARY; - GAME_OBJECT_ID HairSecondary = ID_HAIR_SECONDARY; -}; + struct PlayerInventoryData { @@ -1318,6 +1312,14 @@ struct PlayerInventoryData int ExaminesCombo[NUM_EXAMINES * 2] = {}; }; +struct PlayerSkinData +{ + GAME_OBJECT_ID Skin = ID_LARA_SKIN; + GAME_OBJECT_ID SkinJoints = ID_LARA_SKIN_JOINTS; + GAME_OBJECT_ID HairPrimary = ID_HAIR_PRIMARY; + GAME_OBJECT_ID HairSecondary = ID_HAIR_SECONDARY; +}; + struct LaraInfo { static constexpr auto TARGET_COUNT_MAX = 16; diff --git a/TombEngine/Game/savegame.cpp b/TombEngine/Game/savegame.cpp index f3edca7a21..1e58a929f7 100644 --- a/TombEngine/Game/savegame.cpp +++ b/TombEngine/Game/savegame.cpp @@ -586,6 +586,7 @@ const std::vector SaveGame::Build() lara.add_location(Lara.Location); lara.add_location_pad(Lara.LocationPad); lara.add_right_arm(rightArmOffset); + lara.add_skin(playerSkinOffset); lara.add_status(statusOffset); lara.add_collision(collisionOffset); lara.add_target_arm_orient(&FromEulerAngles(Lara.TargetArmOrient)); From 35771369de0ff63cf6d682cf24864b08d0fbcb18 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Fri, 7 Mar 2025 08:33:18 -0500 Subject: [PATCH 06/20] LaraIntialize --- TombEngine/Game/Lara/lara_initialise.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TombEngine/Game/Lara/lara_initialise.cpp b/TombEngine/Game/Lara/lara_initialise.cpp index f39b8ab517..84c1fe456e 100644 --- a/TombEngine/Game/Lara/lara_initialise.cpp +++ b/TombEngine/Game/Lara/lara_initialise.cpp @@ -105,7 +105,7 @@ void InitializeLaraMeshes(ItemInfo* item) auto& player = GetLaraInfo(*item); // Override base mesh and mesh indices to player skin if it exists. - item->Model.BaseMesh = Objects[(Objects[ID_LARA_SKIN].loaded ? ID_LARA_SKIN : ID_LARA)].meshIndex; + item->Model.BaseMesh = Objects[(Objects[Lara.Skin.Skin].loaded ? Lara.Skin.Skin : ID_LARA)].meshIndex; for (int i = 0; i < NUM_LARA_MESHES; i++) item->Model.MeshIndex[i] = item->Model.BaseMesh + i; From 2b82e1405526f353173e106af8a4af371f8b2100 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Fri, 7 Mar 2025 08:45:13 -0500 Subject: [PATCH 07/20] Progress --- TombEngine/Game/Lara/lara_initialise.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TombEngine/Game/Lara/lara_initialise.cpp b/TombEngine/Game/Lara/lara_initialise.cpp index 84c1fe456e..875cad6822 100644 --- a/TombEngine/Game/Lara/lara_initialise.cpp +++ b/TombEngine/Game/Lara/lara_initialise.cpp @@ -104,8 +104,15 @@ void InitializeLaraMeshes(ItemInfo* item) { auto& player = GetLaraInfo(*item); + player.Skin.Skin = ID_LARA_SKIN; + player.Skin.SkinJoints = ID_LARA_SKIN_JOINTS; + player.Skin.HairPrimary = ID_HAIR_PRIMARY; + player.Skin.HairSecondary = ID_HAIR_SECONDARY; + + TENLog("Failed to create Lara Skin."+ Lara.Skin.Skin, LogLevel::Warning); + // Override base mesh and mesh indices to player skin if it exists. - item->Model.BaseMesh = Objects[(Objects[Lara.Skin.Skin].loaded ? Lara.Skin.Skin : ID_LARA)].meshIndex; + item->Model.BaseMesh = Objects[(Objects[player.Skin.Skin].loaded ? player.Skin.Skin : ID_LARA)].meshIndex; for (int i = 0; i < NUM_LARA_MESHES; i++) item->Model.MeshIndex[i] = item->Model.BaseMesh + i; From cbbb491ad4a3ab32676d3d9ab35863d04257e31e Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Fri, 7 Mar 2025 09:31:29 -0500 Subject: [PATCH 08/20] ReplaceSkinRefrences --- TombEngine/Game/Lara/Optics.cpp | 2 +- TombEngine/Game/effects/tomb4fx.cpp | 4 ++-- TombEngine/Renderer/RendererHelper.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/TombEngine/Game/Lara/Optics.cpp b/TombEngine/Game/Lara/Optics.cpp index d09e9ebcb1..595c9d29fa 100644 --- a/TombEngine/Game/Lara/Optics.cpp +++ b/TombEngine/Game/Lara/Optics.cpp @@ -123,7 +123,7 @@ static void HandlePlayerOpticAnimations(ItemInfo& item) player.TargetEntity = nullptr; } - int objNumber = Objects[ID_LARA_BINOCULARS_MESH].loaded ? ID_LARA_BINOCULARS_MESH : ID_LARA_SKIN; + int objNumber = Objects[ID_LARA_BINOCULARS_MESH].loaded ? ID_LARA_BINOCULARS_MESH : player.Skin.Skin; item.Model.MeshIndex[LM_RHAND] = Objects[objNumber].meshIndex + LM_RHAND; player.Control.HandStatus = HandStatus::Free; diff --git a/TombEngine/Game/effects/tomb4fx.cpp b/TombEngine/Game/effects/tomb4fx.cpp index 1f45448e98..7a4df59171 100644 --- a/TombEngine/Game/effects/tomb4fx.cpp +++ b/TombEngine/Game/effects/tomb4fx.cpp @@ -1178,8 +1178,8 @@ void ExplodingDeath(short itemNumber, short flags) ItemInfo* item = &g_Level.Items[itemNumber]; ObjectInfo* obj; - if (item->IsLara() && Objects[ID_LARA_SKIN].loaded) - obj = &Objects[ID_LARA_SKIN]; + if (item->IsLara() && Objects[Lara.Skin.Skin].loaded) + obj = &Objects[Lara.Skin.Skin]; else obj = &Objects[item->ObjectNumber]; diff --git a/TombEngine/Renderer/RendererHelper.cpp b/TombEngine/Renderer/RendererHelper.cpp index 671d41bda9..d863a7caf0 100644 --- a/TombEngine/Renderer/RendererHelper.cpp +++ b/TombEngine/Renderer/RendererHelper.cpp @@ -392,10 +392,10 @@ namespace TEN::Renderer RendererObject& Renderer::GetRendererObject(GAME_OBJECT_ID id) { - if (id == GAME_OBJECT_ID::ID_LARA || id == GAME_OBJECT_ID::ID_LARA_SKIN) + if (id == GAME_OBJECT_ID::ID_LARA || id == Lara.Skin.Skin) { - if (_moveableObjects[GAME_OBJECT_ID::ID_LARA_SKIN].has_value()) - return _moveableObjects[GAME_OBJECT_ID::ID_LARA_SKIN].value(); + if (_moveableObjects[Lara.Skin.Skin].has_value()) + return _moveableObjects[Lara.Skin.Skin].value(); else return _moveableObjects[GAME_OBJECT_ID::ID_LARA].value(); } From 33fdbd50ec4a6b40d9e5a9e459196118383db0ec Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Fri, 7 Mar 2025 09:52:10 -0500 Subject: [PATCH 09/20] Fix reference --- TombEngine/Game/Lara/lara_initialise.cpp | 9 +++++---- TombEngine/Game/Lara/lara_struct.h | 8 ++++---- TombEngine/Renderer/RendererCompatibility.cpp | 4 ++-- .../Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp | 1 + 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/TombEngine/Game/Lara/lara_initialise.cpp b/TombEngine/Game/Lara/lara_initialise.cpp index 875cad6822..453724a6b2 100644 --- a/TombEngine/Game/Lara/lara_initialise.cpp +++ b/TombEngine/Game/Lara/lara_initialise.cpp @@ -104,10 +104,11 @@ void InitializeLaraMeshes(ItemInfo* item) { auto& player = GetLaraInfo(*item); - player.Skin.Skin = ID_LARA_SKIN; - player.Skin.SkinJoints = ID_LARA_SKIN_JOINTS; - player.Skin.HairPrimary = ID_HAIR_PRIMARY; - player.Skin.HairSecondary = ID_HAIR_SECONDARY; + //Set Skin Defaults. + Lara.Skin.Skin = ID_LARA_SKIN; + Lara.Skin.SkinJoints = ID_LARA_SKIN_JOINTS; + Lara.Skin.HairPrimary = ID_HAIR_PRIMARY; + Lara.Skin.HairSecondary = ID_HAIR_SECONDARY; TENLog("Failed to create Lara Skin."+ Lara.Skin.Skin, LogLevel::Warning); diff --git a/TombEngine/Game/Lara/lara_struct.h b/TombEngine/Game/Lara/lara_struct.h index 59f888c316..18c105e25e 100644 --- a/TombEngine/Game/Lara/lara_struct.h +++ b/TombEngine/Game/Lara/lara_struct.h @@ -1314,10 +1314,10 @@ struct PlayerInventoryData struct PlayerSkinData { - GAME_OBJECT_ID Skin = ID_LARA_SKIN; - GAME_OBJECT_ID SkinJoints = ID_LARA_SKIN_JOINTS; - GAME_OBJECT_ID HairPrimary = ID_HAIR_PRIMARY; - GAME_OBJECT_ID HairSecondary = ID_HAIR_SECONDARY; + GAME_OBJECT_ID Skin; + GAME_OBJECT_ID SkinJoints; + GAME_OBJECT_ID HairPrimary; + GAME_OBJECT_ID HairSecondary; }; struct LaraInfo diff --git a/TombEngine/Renderer/RendererCompatibility.cpp b/TombEngine/Renderer/RendererCompatibility.cpp index c9ba0cf63d..702765fd8c 100644 --- a/TombEngine/Renderer/RendererCompatibility.cpp +++ b/TombEngine/Renderer/RendererCompatibility.cpp @@ -484,7 +484,7 @@ namespace TEN::Renderer ); TENLog("Preparing object data...", LogLevel::Info); - + bool isSkinPresent = false; totalVertices = 0; @@ -708,7 +708,7 @@ namespace TEN::Renderer { bool isYoung = (g_GameFlow->GetLevel(CurrentLevel)->GetLaraType() == LaraType::Young); bool isSecond = isYoung && MoveablesIds[i] == Lara.Skin.HairSecondary; - const auto& skinObj = GetRendererObject(Lara.Skin.SkinJoints); + const auto& skinObj = GetRendererObject(Lara.Skin.Skin); const auto& settings = g_GameFlow->GetSettings()->Hair; for (int j = 0; j < obj->nmeshes; j++) diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp index af813baf25..472cb2a261 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp @@ -404,6 +404,7 @@ void LaraObject::SetSkin(GAME_OBJECT_ID skin, GAME_OBJECT_ID skinJoints, GAME_OB lara->Skin.SkinJoints = skinJoints; lara->Skin.HairPrimary = hair1; lara->Skin.HairSecondary = hair2; + } void LaraObject::Register(sol::table& parent) From dd1aa3e972424b30df3124f29ea2fe4daa9335d9 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:51:59 -0500 Subject: [PATCH 10/20] WIP --- TombEngine/Game/Lara/lara_initialise.cpp | 2 - TombEngine/Renderer/Renderer.h | 1 + TombEngine/Renderer/RendererLara.cpp | 120 ++++++++++++++++++ .../Internal/TEN/Objects/Lara/LaraObject.cpp | 1 + 4 files changed, 122 insertions(+), 2 deletions(-) diff --git a/TombEngine/Game/Lara/lara_initialise.cpp b/TombEngine/Game/Lara/lara_initialise.cpp index 453724a6b2..e1a688e0b3 100644 --- a/TombEngine/Game/Lara/lara_initialise.cpp +++ b/TombEngine/Game/Lara/lara_initialise.cpp @@ -110,8 +110,6 @@ void InitializeLaraMeshes(ItemInfo* item) Lara.Skin.HairPrimary = ID_HAIR_PRIMARY; Lara.Skin.HairSecondary = ID_HAIR_SECONDARY; - TENLog("Failed to create Lara Skin."+ Lara.Skin.Skin, LogLevel::Warning); - // Override base mesh and mesh indices to player skin if it exists. item->Model.BaseMesh = Objects[(Objects[player.Skin.Skin].loaded ? player.Skin.Skin : ID_LARA)].meshIndex; diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h index 09939c63ec..4ed9740819 100644 --- a/TombEngine/Renderer/Renderer.h +++ b/TombEngine/Renderer/Renderer.h @@ -456,6 +456,7 @@ namespace TEN::Renderer void PrepareSparkParticles(RenderView& view); void PrepareExplosionParticles(RenderView& view); void DrawLaraHolsters(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass); + void SwapLaraSkin(); void DrawLaraJoints(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass); void DrawLaraHair(RendererItem* itemToDraw, RendererRoom* room, RenderView& view, RendererPass rendererPass); void DrawMoveableMesh(RendererItem* itemToDraw, RendererMesh* mesh, RendererRoom* room, int boneIndex, RenderView& view, RendererPass rendererPass); diff --git a/TombEngine/Renderer/RendererLara.cpp b/TombEngine/Renderer/RendererLara.cpp index e6135a5c3f..cacc1d4d51 100644 --- a/TombEngine/Renderer/RendererLara.cpp +++ b/TombEngine/Renderer/RendererLara.cpp @@ -15,6 +15,7 @@ #include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" #include "Scripting/Include/ScriptInterfaceLevel.h" #include "Specific/level.h" +#include using namespace TEN::Effects::Hair; using namespace TEN::Math; @@ -421,3 +422,122 @@ void Renderer::DrawLaraHolsters(RendererItem* itemToDraw, RendererRoom* room, Re DrawMoveableMesh(itemToDraw, mesh, room, LM_TORSO, view, rendererPass); } } + +void Renderer::SwapLaraSkin() +{ + + // Find the new skin joint object + ObjectInfo* newSkinJoint = &Objects[Lara.Skin.SkinJoints]; + if (!newSkinJoint || newSkinJoint->nmeshes <= 0) + { + TENLog("Invalid skin joint ID provided.", LogLevel::Error); + return; + } + + // Create and attach the new skin joints + _moveableObjects[Lara.Skin.SkinJoints] = RendererObject(); + RendererObject& moveable = *_moveableObjects[Lara.Skin.SkinJoints]; + moveable.Id = Lara.Skin.SkinJoints; + moveable.DoNotDraw = (newSkinJoint->drawRoutine == nullptr); + moveable.ShadowType = newSkinJoint->shadowType; + + int lastVertex = 0; + int lastIndex = 0; + + for (int j = 0; j < newSkinJoint->nmeshes; j++) + { + RendererMesh* mesh = GetRendererMeshFromTrMesh( + &moveable, + &g_Level.Meshes[newSkinJoint->meshIndex + j], + j, true, // This is a skin joint, so we override bone index + false, + &lastVertex, &lastIndex); + + moveable.ObjectMeshes.push_back(mesh); + _meshes.push_back(mesh); + } + + // Rebuild bone hierarchy + for (int j = 0; j < newSkinJoint->nmeshes; j++) + { + moveable.LinearizedBones.push_back(new RendererBone(j)); + moveable.AnimationTransforms.push_back(Matrix::Identity); + moveable.BindPoseTransforms.push_back(Matrix::Identity); + } + + if (newSkinJoint->nmeshes > 1) + { + int* bone = &g_Level.Bones[newSkinJoint->boneIndex]; + std::stack stack; + RendererBone* currentBone = moveable.LinearizedBones[0]; + + for (int mi = 0; mi < newSkinJoint->nmeshes - 1; mi++) + { + int j = mi + 1; + int opcode = *(bone++); + int linkX = *(bone++); + int linkY = *(bone++); + int linkZ = *(bone++); + byte flags = opcode & 0x1C; + + moveable.LinearizedBones[j]->ExtraRotationFlags = flags; + + switch (opcode & 0x03) + { + case 0: + moveable.LinearizedBones[j]->Parent = currentBone; + moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ); + currentBone->Children.push_back(moveable.LinearizedBones[j]); + currentBone = moveable.LinearizedBones[j]; + break; + + case 1: + if (!stack.empty()) + { + currentBone = stack.top(); + stack.pop(); + } + moveable.LinearizedBones[j]->Parent = currentBone; + moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ); + currentBone->Children.push_back(moveable.LinearizedBones[j]); + currentBone = moveable.LinearizedBones[j]; + break; + + case 2: + stack.push(currentBone); + moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ); + moveable.LinearizedBones[j]->Parent = currentBone; + currentBone->Children.push_back(moveable.LinearizedBones[j]); + currentBone = moveable.LinearizedBones[j]; + break; + + case 3: + if (!stack.empty()) + { + RendererBone* theBone = stack.top(); + stack.pop(); + moveable.LinearizedBones[j]->Translation = Vector3(linkX, linkY, linkZ); + moveable.LinearizedBones[j]->Parent = theBone; + theBone->Children.push_back(moveable.LinearizedBones[j]); + currentBone = moveable.LinearizedBones[j]; + stack.push(theBone); + } + break; + } + } +} + + // Finalize the transformation matrices + for (int n = 0; n < newSkinJoint->nmeshes; n++) + { + moveable.LinearizedBones[n]->Transform = Matrix::CreateTranslation( + moveable.LinearizedBones[n]->Translation.x, + moveable.LinearizedBones[n]->Translation.y, + moveable.LinearizedBones[n]->Translation.z); + } + + moveable.Skeleton = moveable.LinearizedBones[0]; + BuildHierarchy(&moveable); + + TENLog("Skin joints swapped successfully.", LogLevel::Info); +} diff --git a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp index 472cb2a261..3e9e82421b 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/Lara/LaraObject.cpp @@ -7,6 +7,7 @@ #include "Game/Lara/lara_fire.h" #include "Game/Lara/lara_helpers.h" #include "Game/Lara/lara_struct.h" +#include "Game/Setup.h" #include "Objects/Generic/Object/burning_torch.h" #include "Scripting/Internal/ReservedScriptNames.h" #include "Scripting/Internal/TEN/Objects/Lara/AmmoTypes.h" From 57176d3735272ea2ea81dee75cdee28165ff3df9 Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:48:11 +0200 Subject: [PATCH 11/20] Update version number --- Documentation/config.ld | 2 +- TombEngine/version.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/config.ld b/Documentation/config.ld index defc570992..21a5ec55c6 100644 --- a/Documentation/config.ld +++ b/Documentation/config.ld @@ -13,7 +13,7 @@ new_type("luautil", "6 Lua utility modules", true) not_luadoc = true -local version = "1.11" +local version = "2.0" project = " TombEngine" title = "TombEngine " .. version .. " Lua API" description = "TombEngine " .. version .. " scripting interface" diff --git a/TombEngine/version.h b/TombEngine/version.h index cd47a60fa1..ab21ba6a4f 100644 --- a/TombEngine/version.h +++ b/TombEngine/version.h @@ -1,9 +1,9 @@ #pragma once -#define TEN_MAJOR_VERSION 1 -#define TEN_MINOR_VERSION 11 -#define TEN_BUILD_NUMBER 1 -#define TEN_REVISION_NUMBER 1 +#define TEN_MAJOR_VERSION 2 +#define TEN_MINOR_VERSION 0 +#define TEN_BUILD_NUMBER 0 +#define TEN_REVISION_NUMBER 0 #define TEST_BUILD 0 From 141504165e93be942d7cacc5417d0c7d7dcf63cb Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:18:23 +0200 Subject: [PATCH 12/20] Add logo to the documentation table of contents --- Documentation/compile.bat | 8 ++++++++ Documentation/config.ld | 2 +- Documentation/ldoc.css | 14 ++++++++++++++ Documentation/ldoc.ltp | 3 ++- TEN logo.png | Bin 48102 -> 45296 bytes 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Documentation/compile.bat b/Documentation/compile.bat index 503b5f8ae7..47a6fa3267 100644 --- a/Documentation/compile.bat +++ b/Documentation/compile.bat @@ -32,5 +32,13 @@ if %ERRORLEVEL% neq 0 ( exit /b %ERRORLEVEL% ) +echo Copying TEN logo asset... +copy /Y "..\TEN logo.png" "%DOC_DIR%\TEN logo.png" >nul + +if %ERRORLEVEL% neq 0 ( + echo TEN logo copy failed with error code %ERRORLEVEL% + exit /b %ERRORLEVEL% +) + echo Documentation build completed successfully! exit /b 0 diff --git a/Documentation/config.ld b/Documentation/config.ld index 21a5ec55c6..f84f8301cf 100644 --- a/Documentation/config.ld +++ b/Documentation/config.ld @@ -14,7 +14,7 @@ new_type("luautil", "6 Lua utility modules", true) not_luadoc = true local version = "2.0" -project = " TombEngine" +project = "TombEngine" title = "TombEngine " .. version .. " Lua API" description = "TombEngine " .. version .. " scripting interface" full_description = [[Welcome to the TombEngine scripting API. diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css index b86524f8b8..de32fa8cdf 100644 --- a/Documentation/ldoc.css +++ b/Documentation/ldoc.css @@ -119,6 +119,20 @@ table.index td { text-align: left; vertical-align: top; } overflow: visible; } +#navigation .project-title { + align-items: center; + display: flex; + gap: 0.5rem; + margin: 1rem 0 1rem 0.5rem; +} + +#navigation .project-title-logo { + display: block; + flex: 0 0 auto; + height: 2rem; + width: auto; +} + #navigation h2 { background-color:#e7e7e7; font-size:1.1em; diff --git a/Documentation/ldoc.ltp b/Documentation/ldoc.ltp index 5c94f877d5..be77421b9a 100644 --- a/Documentation/ldoc.ltp +++ b/Documentation/ldoc.ltp @@ -28,12 +28,13 @@ # local iter = ldoc.modules.iter # local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end # local nowrap = ldoc.wrap and '' or 'nowrap' +# local logo_path = module and '../TEN%20logo.png' or 'TEN%20logo.png' + + + From efbeded85aff487f7d609e630514a8ef8b01f4f3 Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:42:27 +0200 Subject: [PATCH 14/20] Fix search popup style --- Documentation/docs-search.js | 77 ++++++++++++++++++++++++++++++++++-- Documentation/ldoc.css | 14 ++++++- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/Documentation/docs-search.js b/Documentation/docs-search.js index e5517d8b33..44d1107e8b 100644 --- a/Documentation/docs-search.js +++ b/Documentation/docs-search.js @@ -35,6 +35,67 @@ return element; } + function escapeHtml(text) { + return (text || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\"/g, """) + .replace(/'/g, "'"); + } + + function escapeRegExp(text) { + return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + + function createHighlightedElement(className, text, terms) { + var element = document.createElement("span"); + var uniqueTerms = []; + var seenTerms = {}; + + element.className = className; + + for (var termIndex = 0; termIndex < terms.length; termIndex++) { + var term = terms[termIndex]; + if (!term || seenTerms[term]) { + continue; + } + + seenTerms[term] = true; + uniqueTerms.push(term); + } + + uniqueTerms.sort(function (left, right) { + return right.length - left.length; + }); + + if (!uniqueTerms.length) { + element.textContent = text; + return element; + } + + var matchPattern = new RegExp("(" + uniqueTerms.map(escapeRegExp).join("|") + ")", "gi"); + var exactPattern = new RegExp("^(" + uniqueTerms.map(escapeRegExp).join("|") + ")$", "i"); + var fragments = String(text || "").split(matchPattern); + var html = ""; + + for (var fragmentIndex = 0; fragmentIndex < fragments.length; fragmentIndex++) { + var fragment = fragments[fragmentIndex]; + if (!fragment) { + continue; + } + + if (exactPattern.test(fragment)) { + html += '' + escapeHtml(fragment) + ""; + } else { + html += escapeHtml(fragment); + } + } + + element.innerHTML = html; + return element; + } + onReady(function () { var input = document.getElementById("doc-search-input"); var popup = document.getElementById("doc-search-popup"); @@ -85,11 +146,13 @@ var text = entry.text || ""; var lowerText = text.toLowerCase(); var matchIndex = query ? lowerText.indexOf(query) : -1; + var hasMatch = matchIndex >= 0; if (matchIndex < 0) { for (var termIndex = 0; termIndex < terms.length; termIndex++) { matchIndex = lowerText.indexOf(terms[termIndex]); if (matchIndex >= 0) { + hasMatch = true; break; } } @@ -111,7 +174,10 @@ excerpt += " ..."; } - return excerpt; + return { + hasMatch: hasMatch, + text: excerpt + }; } function search(query) { @@ -161,7 +227,8 @@ matches.push({ entry: candidate, score: score, - excerpt: buildExcerpt(candidate, normalizedQuery, terms) + excerpt: buildExcerpt(candidate, normalizedQuery, terms), + terms: normalizedQuery.indexOf(" ") >= 0 ? [normalizedQuery].concat(terms) : terms }); } @@ -205,7 +272,11 @@ link.appendChild(createTextElement("span", "doc-search-result-title", result.entry.title)); link.appendChild(createTextElement("span", "doc-search-result-meta", result.entry.section + " | " + result.entry.path)); - link.appendChild(createTextElement("span", "doc-search-result-excerpt", result.excerpt)); + if (result.excerpt.hasMatch) { + link.appendChild(createHighlightedElement("doc-search-result-excerpt", result.excerpt.text, result.terms)); + } else { + link.appendChild(createTextElement("span", "doc-search-result-excerpt", result.excerpt.text)); + } link.addEventListener("mouseenter", function () { activeIndex = indexInResults; diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css index 0d1cb2800d..b3c2e6aa90 100644 --- a/Documentation/ldoc.css +++ b/Documentation/ldoc.css @@ -184,10 +184,13 @@ table.index td { text-align: left; vertical-align: top; } overflow-y: auto; } -a.doc-search-result { +a.doc-search-result, +a.doc-search-result:link, +a.doc-search-result:visited { border-bottom: 1px solid #f0f0f0; color: #222222; display: block; + font-weight: normal; padding: 0.7rem 0.75rem; text-decoration: none; } @@ -214,16 +217,23 @@ a.doc-search-result.doc-search-result-active { color: #7a7a7a; display: block; font-size: 0.78em; + font-weight: normal; margin-bottom: 0.25rem; } .doc-search-result-excerpt { - color: #333333; + color: #666666; display: block; font-size: 0.84em; + font-weight: normal; line-height: 1.35; } +.doc-search-result-highlight { + color: #2b2b2b; + font-weight: bold; +} + #navigation h2 { background-color:#e7e7e7; font-size:1.1em; From 8d7e922c6d29307e500d4b4126621f2a0a3b90e6 Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:50:10 +0200 Subject: [PATCH 15/20] Add deep search --- Documentation/generate_search_index.ps1 | 124 +++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/Documentation/generate_search_index.ps1 b/Documentation/generate_search_index.ps1 index f00c346a43..4b4b6c36b1 100644 --- a/Documentation/generate_search_index.ps1 +++ b/Documentation/generate_search_index.ps1 @@ -70,12 +70,33 @@ function Get-DocumentContentHtml { return $Html } +function Get-NearestSectionName { + param( + [System.Collections.ArrayList]$Sections, + [int]$Position + ) + + $closestSection = "" + + foreach ($section in $Sections) { + if ($section.Position -gt $Position) { + break + } + + $closestSection = $section.Name + } + + return $closestSection +} + $resolvedDocRoot = (Resolve-Path $DocRoot).Path if (-not $OutputFile) { $OutputFile = Join-Path $resolvedDocRoot "search-index.js" } +$regexOptions = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline + $entries = Get-ChildItem -Path $resolvedDocRoot -Filter *.html -Recurse | Sort-Object FullName | ForEach-Object { @@ -100,13 +121,114 @@ $entries = Get-ChildItem -Path $resolvedDocRoot -Filter *.html -Recurse | $heading = [System.IO.Path]::GetFileNameWithoutExtension($_.Name) } - [ordered]@{ + $pageEntries = New-Object System.Collections.ArrayList + $null = $pageEntries.Add([ordered]@{ + kind = "page" path = $relativePath section = $section title = $heading pageTitle = $pageTitle text = $documentText + }) + + $sectionMatches = [regex]::Matches( + $contentHtml, + ']*class="section-header[^"]*"[^>]*>\s*(.*?)', + $regexOptions) + + $sections = New-Object System.Collections.ArrayList + foreach ($sectionMatch in $sectionMatches) { + $sectionName = Convert-HtmlToText -Html $sectionMatch.Groups[2].Value + if (-not [string]::IsNullOrWhiteSpace($sectionName)) { + $null = $sections.Add([ordered]@{ + Position = $sectionMatch.Index + Name = $sectionName + }) + } } + + $summaryMatches = [regex]::Matches( + $contentHtml, + '

(.*?)

\s*(.*?)
', + $regexOptions) + + $summaryByKey = @{} + foreach ($summaryMatch in $summaryMatches) { + $summarySection = Convert-HtmlToText -Html $summaryMatch.Groups[2].Value + $rowMatches = [regex]::Matches( + $summaryMatch.Groups[3].Value, + '\s*]*>(.*?)\s*(.*?)\s*', + $regexOptions) + + foreach ($rowMatch in $rowMatches) { + $rowAnchor = $rowMatch.Groups[1].Value + $rowSummary = Convert-HtmlToText -Html $rowMatch.Groups[3].Value + $summaryByKey["$summarySection|$rowAnchor"] = $rowSummary + } + } + + $detailMatches = [regex]::Matches( + $contentHtml, + '
\s*\s*(.*?).*?
\s*
(.*?)
', + $regexOptions) + + $anchorCounts = @{} + foreach ($detailMatch in $detailMatches) { + $anchorName = $detailMatch.Groups[1].Value + if ($anchorCounts.ContainsKey($anchorName)) { + $anchorCounts[$anchorName]++ + } else { + $anchorCounts[$anchorName] = 1 + } + } + + foreach ($detailMatch in $detailMatches) { + $anchorName = $detailMatch.Groups[1].Value + if ($anchorCounts[$anchorName] -ne 1) { + continue + } + + $detailTitle = Convert-HtmlToText -Html $detailMatch.Groups[2].Value + $detailBody = Convert-HtmlToText -Html $detailMatch.Groups[3].Value + $detailSection = Get-NearestSectionName -Sections $sections -Position $detailMatch.Index + $detailSummary = "" + + if (-not [string]::IsNullOrWhiteSpace($detailSection)) { + $summaryKey = "$detailSection|$anchorName" + if ($summaryByKey.ContainsKey($summaryKey)) { + $detailSummary = $summaryByKey[$summaryKey] + } + } + + $detailText = Normalize-Whitespace "$detailSummary $detailBody" + if ([string]::IsNullOrWhiteSpace($detailText)) { + $detailText = $detailSummary + } + + if ([string]::IsNullOrWhiteSpace($detailTitle)) { + continue + } + + $detailSectionLabel = $section + if (-not [string]::IsNullOrWhiteSpace($heading)) { + $detailSectionLabel += " / $heading" + } + + if (-not [string]::IsNullOrWhiteSpace($detailSection)) { + $detailSectionLabel += " / $detailSection" + } + + $null = $pageEntries.Add([ordered]@{ + kind = "anchor" + path = "$relativePath#$anchorName" + section = $detailSectionLabel + title = $detailTitle + pageTitle = $pageTitle + text = $detailText + }) + } + + $pageEntries } $json = $entries | ConvertTo-Json -Depth 4 -Compress From 6fef9a0878939676e4f9838480adaa8dbb477c9d Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:55:40 +0200 Subject: [PATCH 16/20] Make a TOC title a hyperlink to the index page --- Documentation/ldoc.css | 14 ++++++++++++++ Documentation/ldoc.ltp | 11 ++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css index b3c2e6aa90..38ba52dec6 100644 --- a/Documentation/ldoc.css +++ b/Documentation/ldoc.css @@ -126,6 +126,20 @@ table.index td { text-align: left; vertical-align: top; } margin: 1rem 0 1rem 0.5rem; } +#navigation .project-title-link, +#navigation .project-title-link:link, +#navigation .project-title-link:visited { + align-items: center; + color: inherit; + display: inline-flex; + gap: 0.5rem; + text-decoration: none; +} + +#navigation .project-title-link:hover { + text-decoration: none; +} + #navigation .project-title-logo { display: block; flex: 0 0 auto; diff --git a/Documentation/ldoc.ltp b/Documentation/ldoc.ltp index bea9b16a7c..c7c404ddf1 100644 --- a/Documentation/ldoc.ltp +++ b/Documentation/ldoc.ltp @@ -29,13 +29,14 @@ # local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end # local nowrap = ldoc.wrap and '' or 'nowrap' # local asset_root = module and '../' or '' +# local index_path = module and '../index.html' or 'index.html' # local logo_path = module and '../TEN%20logo.png' or 'TEN%20logo.png' -
    -# if not ldoc.single and module then -- reference back to project index -
  • Index
  • -# else -
  • Index
  • -# end -
- # if ldoc.no_summary and module and not ldoc.one then -- bang out the functions on the side # for kind, items in module.kinds() do

$(kind)

From fd1cefc5a3b1e628de1e7562013c433f9c19144e Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:03:54 +0200 Subject: [PATCH 17/20] Make documentation page variable-width --- Documentation/ldoc.css | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css index 38ba52dec6..f2c77fd297 100644 --- a/Documentation/ldoc.css +++ b/Documentation/ldoc.css @@ -91,9 +91,9 @@ table.index { border: 1px #00007f; } table.index td { text-align: left; vertical-align: top; } #container { - margin-left: 1em; - margin-right: 1em; background-color: #f0f0f0; + box-sizing: border-box; + width: 100%; } #product { @@ -109,10 +109,13 @@ table.index td { text-align: left; vertical-align: top; } #main { background-color: #f0f0f0; border-left: 2px solid #cccccc; - display: flex + display: flex; + width: 100%; } #navigation { + box-sizing: border-box; + flex: 0 0 18em; width: 18em; vertical-align: top; background-color: #f0f0f0; @@ -276,8 +279,10 @@ a.doc-search-result.doc-search-result-active { } #content { + box-sizing: border-box; + flex: 1 1 auto; + min-width: 0; padding: 2em; - width: 900px; border-left: 2px solid #cccccc; border-right: 2px solid #cccccc; background-color: #ffffff; @@ -302,12 +307,11 @@ a.doc-search-result.doc-search-result-active { } #container { - margin-left: 2%; - margin-right: 2%; background-color: #ffffff; } #content { + width: auto; padding: 1em; background-color: #ffffff; } From 5a8a4275250a3fb3ad555fc014b9ee3dd4f73717 Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Tue, 7 Apr 2026 01:07:05 +0200 Subject: [PATCH 18/20] Reduce TOC indentation --- Documentation/ldoc.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/ldoc.css b/Documentation/ldoc.css index f2c77fd297..c27489c11f 100644 --- a/Documentation/ldoc.css +++ b/Documentation/ldoc.css @@ -271,7 +271,7 @@ a.doc-search-result.doc-search-result-active { #navigation li { text-indent: -1em; display: block; - margin: 3px 0px 0px 22px; + margin: 3px 0px 0px 0px; } #navigation li li a { From f2ee61655683e07ea14f691834bdacfd6d307392 Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:25:33 +0200 Subject: [PATCH 19/20] Create AGENTS.md --- AGENTS.md | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..663f0acb6d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,132 @@ +## General Project Information + +- Language: **C++ (native)** with sol2-based **Lua API framework**. +- Domain: Real-time 3D engine and tooling for the classic-era Tomb Raider–style games. +- Architecture: Grid-based, room-based world structure. Rooms connected via portals (horizontal/vertical) strictly aligned to grid. +- World units: 1 sector = 1024 world units = roughly 2 meters. +- Codebase is **performance-critical** and low-level. Unnecessary abstractions should be avoided. + +## General Guidelines + +- Prefer simple, explicit, low-overhead C++, with a preference for C-styled patterns. +- Favor readability and predictability over abstraction. +- Minimize hidden behaviour and implicit costs. +- Prefer grouping all feature-related functionality within a self-contained module or modules. Avoid creating large code blocks over 10–15 lines in existing modules; instead, offload code to helper functions. +- Avoid duplicating and copypasting code. Implement helper methods instead, whenever similar code is used in several places within a given module, class or feature scope. + +## Casting Rules + +- Prefer C-style casts: + + ```cpp + int value = (int)someFloat; + ``` + +- Avoid these C++ styled casts, unless absolutely necessary by design: + + ```cpp + static_cast(someFloat); + reinterpret_cast<...>(); + const_cast<...>(); + ``` +## Types + +- Avoid types ending with `_t`, such as `size_t`, `uint32_t`, `int16_t` in local code. Use these types only when referencing external library methods or functions. +- Prefer verbose types, such as `char`, `unsigned char`, `short`, `unsigned short`, `int`, `unsigned int`. +- Prefer `unsigned char` and `char` over `unsigned byte` and `byte`. + +## Namespaces + +- Do not use anonymous namespaces: + + ```cpp + namespace + { + some code... + } + ``` + +## Includes + +- Local includes must use quotes `"`. +- External library or system includes must use angle brackets `<>`. +- Refer to `framework.h` file to know system or external library includes that shouldn't be explicitly added to modules. + +- **Includes should be grouped in this order**: + - Module's own `.h` include (for `.cpp` files). + - External library or system includes. + - Local project includes. + +- Every include group must be sorted in alphabetical order, unless any other specific order is necessary for successful build. +- Every include group must be separated from another group with a blank line. + +## Formatting + +- Indentation: 4 spaces (no tabs). +- Files must use Windows line endings. +- Only standard ASCII symbols are allowed; do not use Unicode symbols, even in comments. + +- **Braces**: + - Namespace declarations, type definitions, `if/while/for` blocks should place the opening curly brace `{` on a new line. + - Always use braces for multi-statement `if` blocks. + - Do not use braces for single-statement `if` blocks, unless the statement has multiple `else if` conditions with surrounding statements being multi-line. + +- **Line breaks and spacing**: + - A blank line separates logically distinct groups of members (fields, constructors, public methods, static helpers, etc.). + - Spaces around binary operators (`=`, `+`, `==`, etc.) and after commas. + - A single space follows keyword `if`/`for`/`while` before the opening parenthesis. + - Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability. + +- Do not collapse early exits or single-statement conditions into a single line: + + Bad example: + ```csharp + if (condition) return; + ``` + Do this instead: + ```csharp + if (condition) + return; + ``` + +## Naming + +- **PascalCase** for public types, methods, constants, properties and events. +- **camelCase** for private fields and local variables. Private fields should start with an underscore (`_itemIndex`, `_rendererFont`). Local variables should not start with an underscore. +- Constants use ALL_CAPS. +- `enum class` members use PascalCase. Old-styled `enum` members use ALL_CAPS. +- Methods and variables should use clear, descriptive names and generally avoid Hungarian notation. Avoid using short non-descriptive names, such as `s2`, `rwh`, `fmp`, unless underlying meaning is brief (e.g. X coordinate is `x`, counter is `i`). +- Class method and field names should not repeat words from a class name itself (e.g. `InputHandler::InitializeInputHandler` is a bad name, but `InputHandler::Initialize` is a good name). +- Interfaces are prefixed with `I` and use PascalCase (`IScaleable`). + +## Members and Access + +- `auto` type should be preferred where possible, when the right-hand type is evident from the initializer. Always use `*` and `&` with `auto` if underlying type is pointer or reference. +- `constexpr auto` type should be preferred for constants, unless it interferes with compiler's ability to make them static. +- Explicit typing should be only used when it is required by logic or compiler, or when type name is shorter than 6 symbols (e.g. `int`, `bool`, `float`). +- For floating-point numbers, always use `f` postfix and decimal, even if value is not fractional (e.g. `2.0f`). + +## Control Flow and Syntax + +- Avoid excessive condition nesting and use early exits / breaks where possible. +- Exception and error handling is done with `try`/`catch`, and caught exceptions are logged with using `TENLog` where appropriate. +- Warnings or safeguards must also be logged by `TENLog` where possible, if cause for the incorrect behaviour is user action. + +## Comments + +- When comments appear they are single-line `//`. Block comments (`/* ... */`) are rare. +- Comments are sparse. Code relies on meaningful names rather than inline documentation. +- If module or function implements complex functionality, a brief description (2-3 lines) may be added in front of it, separated by a blank line from the function body. +- All descriptive comments should end with a full stop (`.`). + +## Code Grouping + +- Large methods should group related actions together, separated by blank lines. +- Constants and static helpers that are used several times should appear at the top of a class or module. +- Constants that are used only within a scope of a method, should be declared within this method. +- One-liner lambdas may be grouped together, if they share similar meaning or functionality. + +## Performance + +- Prefer more performant approaches and locally cache frequently used data within the function scope whenever possible. +- Use `g_Parallel` for bulk operations to maximize performance, if parallelization overhead does not exceed the data length. Avoid using it in thread-unsafe contexts or when operating on serial data sets. \ No newline at end of file From d1907f1bef95ef19a5daf3d2aadf9b5d3973c1dd Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:35:00 +0200 Subject: [PATCH 20/20] Update AGENTS.md --- AGENTS.md | 113 +++++++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 663f0acb6d..61260450f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,18 +1,20 @@ ## General Project Information -- Language: **C++ (native)** with sol2-based **Lua API framework**. -- Domain: Real-time 3D engine and tooling for the classic-era Tomb Raider–style games. -- Architecture: Grid-based, room-based world structure. Rooms connected via portals (horizontal/vertical) strictly aligned to grid. +- Language: **C++ (native)** with sol2-based **Lua API framework**, targeting multiple platforms (Windows, Mac, Linux). +- Domain: Real-time 3D engine and tooling for classic-era Tomb Raider–style games. +- Architecture: Grid-based, room-based world structure. Rooms are connected via portals (horizontal/vertical) strictly aligned to the grid. - World units: 1 sector = 1024 world units = roughly 2 meters. - Codebase is **performance-critical** and low-level. Unnecessary abstractions should be avoided. ## General Guidelines -- Prefer simple, explicit, low-overhead C++, with a preference for C-styled patterns. +- Prefer simple, explicit, low-overhead C++, with a preference for C-style patterns. - Favor readability and predictability over abstraction. -- Minimize hidden behaviour and implicit costs. -- Prefer grouping all feature-related functionality within a self-contained module or modules. Avoid creating large code blocks over 10–15 lines in existing modules; instead, offload code to helper functions. -- Avoid duplicating and copypasting code. Implement helper methods instead, whenever similar code is used in several places within a given module, class or feature scope. +- Minimize hidden behavior and implicit costs. +- Prefer grouping all feature-related functionality within self-contained modules. Avoid creating large code blocks over 10–15 lines in existing modules; instead, offload code to helper functions. +- Before implementing your own helper methods for common math operations, refer to existing utility modules in the `/Math` subdirectory (`Math/Geometry.cpp`, `Math/Legacy.cpp`, `Math/Random.cpp`, `Math/Solvers.cpp`, etc.) and use them instead, if applicable. +- Avoid duplicating and copy-pasting code. Implement helper methods instead whenever similar code is used in several places within a given module, class, or feature scope. +- Avoid using Windows-specific or MSVC compiler-specific code patterns, data types or functions. Prefer universal patterns that are tolerated by all platform-specific compilers (MSVC, g++, Clang). ## Casting Rules @@ -21,43 +23,44 @@ ```cpp int value = (int)someFloat; ``` - -- Avoid these C++ styled casts, unless absolutely necessary by design: - ```cpp +- Avoid these C++-style casts unless absolutely necessary by design: + + ```cpp static_cast(someFloat); reinterpret_cast<...>(); const_cast<...>(); ``` + ## Types -- Avoid types ending with `_t`, such as `size_t`, `uint32_t`, `int16_t` in local code. Use these types only when referencing external library methods or functions. -- Prefer verbose types, such as `char`, `unsigned char`, `short`, `unsigned short`, `int`, `unsigned int`. +- Avoid types ending with `_t`, such as `size_t`, `uint32_t`, `int16_t`, in local code. Use these types only when referencing external library methods or functions. +- Prefer explicit types such as `char`, `unsigned char`, `short`, `unsigned short`, `int`, `unsigned int`. - Prefer `unsigned char` and `char` over `unsigned byte` and `byte`. ## Namespaces -- Do not use anonymous namespaces: +- Do not use anonymous namespaces, like in this example: ```cpp namespace { - some code... + // code... } ``` - + ## Includes -- Local includes must use quotes `"`. +- Local includes must use quotes `""`. - External library or system includes must use angle brackets `<>`. -- Refer to `framework.h` file to know system or external library includes that shouldn't be explicitly added to modules. +- Refer to the `framework.h` file to determine system or external library includes that should not be explicitly added to modules. - **Includes should be grouped in this order**: - Module's own `.h` include (for `.cpp` files). - External library or system includes. - Local project includes. - -- Every include group must be sorted in alphabetical order, unless any other specific order is necessary for successful build. + +- Every include group must be sorted in alphabetical order unless a specific order is required for a successful build. - Every include group must be separated from another group with a blank line. ## Formatting @@ -67,66 +70,70 @@ - Only standard ASCII symbols are allowed; do not use Unicode symbols, even in comments. - **Braces**: - - Namespace declarations, type definitions, `if/while/for` blocks should place the opening curly brace `{` on a new line. + - Namespace declarations, type definitions, and `if`/`while`/`for` blocks should place the opening curly brace `{` on a new line. - Always use braces for multi-statement `if` blocks. - - Do not use braces for single-statement `if` blocks, unless the statement has multiple `else if` conditions with surrounding statements being multi-line. + - Do not use braces for single-statement `if` blocks unless the statement is part of a multi-branch conditional (`else if`, etc.). - **Line breaks and spacing**: - A blank line separates logically distinct groups of members (fields, constructors, public methods, static helpers, etc.). - - Spaces around binary operators (`=`, `+`, `==`, etc.) and after commas. - - A single space follows keyword `if`/`for`/`while` before the opening parenthesis. + - Use spaces around binary operators (`=`, `+`, `==`, etc.) and after commas. + - A single space follows the keywords `if`/`for`/`while` before the opening parenthesis. - Expressions may be broken into multiple lines and aligned with the previous line's indentation level to improve readability. - -- Do not collapse early exits or single-statement conditions into a single line: - + +- Do not collapse early exits or single-statement conditions into a single line: + Bad example: - ```csharp - if (condition) return; - ``` + + ```cpp + if (condition) return; + ``` + Do this instead: - ```csharp - if (condition) - return; - ``` + + ```cpp + if (condition) + return; + ``` ## Naming -- **PascalCase** for public types, methods, constants, properties and events. +- **PascalCase** for public types, methods, constants, properties, and events. - **camelCase** for private fields and local variables. Private fields should start with an underscore (`_itemIndex`, `_rendererFont`). Local variables should not start with an underscore. - Constants use ALL_CAPS. -- `enum class` members use PascalCase. Old-styled `enum` members use ALL_CAPS. -- Methods and variables should use clear, descriptive names and generally avoid Hungarian notation. Avoid using short non-descriptive names, such as `s2`, `rwh`, `fmp`, unless underlying meaning is brief (e.g. X coordinate is `x`, counter is `i`). -- Class method and field names should not repeat words from a class name itself (e.g. `InputHandler::InitializeInputHandler` is a bad name, but `InputHandler::Initialize` is a good name). -- Interfaces are prefixed with `I` and use PascalCase (`IScaleable`). +- `enum class` members use PascalCase. Old-style `enum` members use ALL_CAPS. +- Methods and variables should use clear, descriptive names and generally avoid Hungarian notation. Avoid short, non-descriptive names such as `s2`, `rwh`, `fmp`, unless the underlying meaning is trivial (e.g., `x`, `i`). +- Do not use prefix-based method and field names, except `g_`, which indicates "global". +- Class method and field names should not repeat words from the class name itself (e.g., `InputHandler::InitializeInputHandler` is a bad name, but `InputHandler::Initialize` is a good name). +- Interfaces are prefixed with `I` and use PascalCase (`IScalable`). ## Members and Access -- `auto` type should be preferred where possible, when the right-hand type is evident from the initializer. Always use `*` and `&` with `auto` if underlying type is pointer or reference. -- `constexpr auto` type should be preferred for constants, unless it interferes with compiler's ability to make them static. -- Explicit typing should be only used when it is required by logic or compiler, or when type name is shorter than 6 symbols (e.g. `int`, `bool`, `float`). -- For floating-point numbers, always use `f` postfix and decimal, even if value is not fractional (e.g. `2.0f`). +- `auto` should be preferred where possible when the right-hand type is evident from the initializer. Always use `*` and `&` with `auto` if the underlying type is a pointer or reference. +- `constexpr auto` should be preferred for constants unless it interferes with the compiler's ability to make them static. +- Explicit typing should be used only when required by logic or the compiler, or when the type name is shorter than 6 characters (e.g., `int`, `bool`, `float`). +- For floating-point numbers, always use the `f` postfix and include a decimal, even if the value is not fractional (e.g., `2.0f`). ## Control Flow and Syntax -- Avoid excessive condition nesting and use early exits / breaks where possible. -- Exception and error handling is done with `try`/`catch`, and caught exceptions are logged with using `TENLog` where appropriate. -- Warnings or safeguards must also be logged by `TENLog` where possible, if cause for the incorrect behaviour is user action. +- Avoid excessive condition nesting and use early exits or breaks where possible. +- Exception and error handling is done with `try`/`catch`, and caught exceptions are logged using `TENLog` where appropriate. +- Warnings or safeguards must also be logged using `TENLog` where possible if incorrect behavior is caused by user action. ## Comments -- When comments appear they are single-line `//`. Block comments (`/* ... */`) are rare. -- Comments are sparse. Code relies on meaningful names rather than inline documentation. -- If module or function implements complex functionality, a brief description (2-3 lines) may be added in front of it, separated by a blank line from the function body. +- Use single-line comments (`//`). Block comments (`/* ... */`) are rare. +- Comments should be sparse. Code should rely on meaningful names rather than inline documentation. +- If a module or function implements complex functionality, a brief description (2–3 lines) may be added before it, separated by a blank line from the function body. - All descriptive comments should end with a full stop (`.`). ## Code Grouping - Large methods should group related actions together, separated by blank lines. -- Constants and static helpers that are used several times should appear at the top of a class or module. -- Constants that are used only within a scope of a method, should be declared within this method. -- One-liner lambdas may be grouped together, if they share similar meaning or functionality. +- Constants and static helpers used multiple times should appear at the top of a class or module. +- Constants used only within a method should be declared within that method. +- One-line lambdas may be grouped together if they share similar meaning or functionality. ## Performance -- Prefer more performant approaches and locally cache frequently used data within the function scope whenever possible. -- Use `g_Parallel` for bulk operations to maximize performance, if parallelization overhead does not exceed the data length. Avoid using it in thread-unsafe contexts or when operating on serial data sets. \ No newline at end of file +- Prefer performant approaches and locally cache frequently used data within the function scope whenever possible. +- Use `g_Parallel` for bulk operations to maximize performance if parallelization overhead does not exceed the data size. Avoid using it in thread-unsafe contexts or when operating on inherently serial data sets. \ No newline at end of file