From 22d990697871d2aeb82799159ee13b4455573e87 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Thu, 20 Jun 2024 16:58:24 +0200 Subject: [PATCH 01/35] An ability to specify attack type in controls.use value --- apps/openmw/mwmechanics/actors.cpp | 18 +++++++++++++++++- apps/openmw/mwmechanics/character.cpp | 12 +++++++++++- apps/openmw/mwmechanics/character.hpp | 2 ++ apps/openmw/mwmechanics/creaturestats.cpp | 1 + apps/openmw/mwmechanics/creaturestats.hpp | 4 ++++ 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 525044d55c..1a6f224c0a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -239,6 +239,18 @@ namespace MWMechanics namespace { + std::string_view attackTypeName(int attackTypeNum) + { + if (attackTypeNum == 1) + return "chop"; + else if (attackTypeNum == 2) + return "slash"; + else if (attackTypeNum == 3) + return "thrust"; + else + return ""; + } + float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) { @@ -363,7 +375,11 @@ namespace MWMechanics mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length(); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); - stats.setAttackingOrSpell((controls.mUse & 1) == 1); + + int attackTypeNum = controls.mUse & 3; + stats.setAttackingOrSpell(attackTypeNum != 0); + stats.setAttackType(attackTypeName(attackTypeNum)); + controls.mChanged = false; } // For the player we don't need to copy these values to Lua because mwinput doesn't change them. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 287e5d035c..1a396c9ce9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1674,7 +1674,12 @@ namespace MWMechanics } } else if (aiInactive) - mAttackType = getRandomAttackType(); + { + mAttackType = getDesiredAttackType(); + if (mAttackType == "") + mAttackType = getRandomAttackType(); + } + // else if (mPtr != getPlayer()) use mAttackType set by AiCombat startKey = mAttackType + ' ' + startKey; stopKey = mAttackType + " max attack"; @@ -3002,6 +3007,11 @@ namespace MWMechanics return mPtr.getClass().getCreatureStats(mPtr).getAttackingOrSpell(); } + std::string_view CharacterController::getDesiredAttackType() const + { + return mPtr.getClass().getCreatureStats(mPtr).getAttackType(); + } + void CharacterController::setActive(int active) const { mAnimation->setActive(active); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 8ead23f659..f043419a81 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -247,6 +247,8 @@ namespace MWMechanics bool getAttackingOrSpell() const; void setAttackingOrSpell(bool attackingOrSpell) const; + std::string_view getDesiredAttackType() const; + void prepareHit(); public: diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 98bfa59c89..e3fed90d58 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -45,6 +45,7 @@ namespace MWMechanics , mSideMovementAngle(0) , mLevel(0) , mAttackingOrSpell(false) + , mAttackType("") { for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) { diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 7989357634..3f7c57094c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -97,6 +97,7 @@ namespace MWMechanics protected: int mLevel; bool mAttackingOrSpell; + std::string mAttackType; public: CreatureStats(); @@ -130,6 +131,7 @@ namespace MWMechanics const MagicEffects& getMagicEffects() const; bool getAttackingOrSpell() const { return mAttackingOrSpell; } + std::string_view getAttackType() const { return mAttackType; } int getLevel() const; @@ -156,6 +158,8 @@ namespace MWMechanics void setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; } + void setAttackType(std::string_view attackType) { mAttackType = attackType; } + void setLevel(int level); void setAiSetting(AiSetting index, Stat value); From 70c822d0363f429c02ff86b8019c081ecb393500 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Thu, 20 Jun 2024 21:22:06 +0200 Subject: [PATCH 02/35] Updated lua docs description --- files/lua_api/openmw/self.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 6123c36ae6..60b5f563a5 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -37,7 +37,7 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump --- @field [parent=#ActorControls] #number use if 1 - activates the readied weapon/spell. For weapons, keeping at 1 will charge the attack until set to 0. +-- @field [parent=#ActorControls] #number use Possible values: 0 - 3. If above 0 - activates the readied weapon/spell. For weapons, keeping at a non-zero value will charge the attack until set to 0. For melee weapons on AI-disabled actors - values 1 - 3 determine an attack type: 1 - chop, 2 - slash, 3 - thrust. --- -- Enables or disables standard AI (enabled by default). From 7d659ae64cb9fb8e7993c4a00058a351f0e95f24 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Fri, 21 Jun 2024 00:26:48 +0200 Subject: [PATCH 03/35] Added lua enum for attacktypes --- apps/openmw/mwlua/localscripts.cpp | 2 ++ apps/openmw/mwmechanics/creaturestats.cpp | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 3b0d44a984..e9c3ea3662 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -63,6 +63,8 @@ namespace MWLua selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; + selfAPI["attackType"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( + { { "NoAttack", 0 }, { "Chop", 1 }, { "Slash", 2 }, { "Thrust", 3 } })); using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index e3fed90d58..98bfa59c89 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -45,7 +45,6 @@ namespace MWMechanics , mSideMovementAngle(0) , mLevel(0) , mAttackingOrSpell(false) - , mAttackType("") { for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) { From 2ef0568dda0e87b2419460c1351264a76d3ff970 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Fri, 21 Jun 2024 16:53:11 +0200 Subject: [PATCH 04/35] Changed lua enum name, tweaked docs --- apps/openmw/mwlua/localscripts.cpp | 4 ++-- files/lua_api/openmw/self.lua | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index e9c3ea3662..d2ef661bc7 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -63,8 +63,8 @@ namespace MWLua selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; - selfAPI["attackType"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( - { { "NoAttack", 0 }, { "Chop", 1 }, { "Slash", 2 }, { "Thrust", 3 } })); + selfAPI["AttackTYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( + { { "NoAttack", 0 }, { "Attack", 1 }, { "Chop", 1 }, { "Slash", 2 }, { "Thrust", 3 } })); using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 60b5f563a5..9f13f6a8bf 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -37,7 +37,14 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump --- @field [parent=#ActorControls] #number use Possible values: 0 - 3. If above 0 - activates the readied weapon/spell. For weapons, keeping at a non-zero value will charge the attack until set to 0. For melee weapons on AI-disabled actors - values 1 - 3 determine an attack type: 1 - chop, 2 - slash, 3 - thrust. +-- @field [parent=#ActorControls] #number Accepts an @{#AttackTYPE} value. Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#AttackTYPE.NoAttack}. + +-- @type AttackTYPE +-- @field #number NoAttack +-- @field #number Attack +-- @field #number Chop +-- @field #number Swing +-- @field #number Thrust --- -- Enables or disables standard AI (enabled by default). From b01b76b81ea6e6c87887bbdc70ce513bf430a9c3 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Fri, 21 Jun 2024 23:18:13 +0200 Subject: [PATCH 05/35] Introduced attacktype enum, fixed lua docs --- apps/openmw/mwlua/localscripts.cpp | 9 +++++++-- apps/openmw/mwmechanics/actors.cpp | 17 ++++++++++------- apps/openmw/mwmechanics/attacktype.hpp | 16 ++++++++++++++++ files/lua_api/openmw/self.lua | 5 +++-- 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 apps/openmw/mwmechanics/attacktype.hpp diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index d2ef661bc7..966444b173 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -1,6 +1,7 @@ #include "localscripts.hpp" #include +#include #include #include "../mwbase/environment.hpp" @@ -13,6 +14,7 @@ #include "../mwmechanics/aisequence.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" +#include "../mwmechanics/attacktype.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" @@ -63,8 +65,11 @@ namespace MWLua selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; - selfAPI["AttackTYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( - { { "NoAttack", 0 }, { "Attack", 1 }, { "Chop", 1 }, { "Slash", 2 }, { "Thrust", 3 } })); + selfAPI["AttackTYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( + { { "NoAttack", MWMechanics::AttackType::NoAttack }, { "Any", MWMechanics::AttackType::Any }, + { "Chop", MWMechanics::AttackType::Chop }, { "Slash", MWMechanics::AttackType::Slash }, + { "Thrust", MWMechanics::AttackType::Thrust } })); using AiPackage = MWMechanics::AiPackage; sol::usertype aiPackage = context.mLua->sol().new_usertype("AiPackage"); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 1a6f224c0a..300e2316ba 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -47,6 +47,7 @@ #include "aifollow.hpp" #include "aipursue.hpp" #include "aiwander.hpp" +#include "attacktype.hpp" #include "character.hpp" #include "creaturestats.hpp" #include "movement.hpp" @@ -239,13 +240,13 @@ namespace MWMechanics namespace { - std::string_view attackTypeName(int attackTypeNum) + std::string_view attackTypeName(AttackType attackType) { - if (attackTypeNum == 1) + if (attackType == AttackType::Chop) return "chop"; - else if (attackTypeNum == 2) + else if (attackType == AttackType::Slash) return "slash"; - else if (attackTypeNum == 3) + else if (attackType == AttackType::Thrust) return "thrust"; else return ""; @@ -376,9 +377,11 @@ namespace MWMechanics stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); - int attackTypeNum = controls.mUse & 3; - stats.setAttackingOrSpell(attackTypeNum != 0); - stats.setAttackType(attackTypeName(attackTypeNum)); + // Same as mUse % max AttackType int value + AttackType attackType = static_cast(controls.mUse % static_cast(AttackType::Thrust)); + + stats.setAttackingOrSpell(attackType != AttackType::NoAttack); + stats.setAttackType(attackTypeName(attackType)); controls.mChanged = false; } diff --git a/apps/openmw/mwmechanics/attacktype.hpp b/apps/openmw/mwmechanics/attacktype.hpp new file mode 100644 index 0000000000..3824f5bbe7 --- /dev/null +++ b/apps/openmw/mwmechanics/attacktype.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_MWMECHANICS_ATTACKTYPE_H +#define OPENMW_MWMECHANICS_ATTACKTYPE_H + +namespace MWMechanics +{ + enum class AttackType + { + NoAttack, + Any, + Chop, + Slash, + Thrust + }; +} + +#endif diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 9f13f6a8bf..acb83a3cf4 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -37,11 +37,12 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump --- @field [parent=#ActorControls] #number Accepts an @{#AttackTYPE} value. Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#AttackTYPE.NoAttack}. +-- @field [parent=#ActorControls] #number use Accepts an @{#AttackTYPE} value. Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#AttackTYPE.NoAttack}. If an @{#AttackTYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#AttackTYPE} will be used instead. +--- -- @type AttackTYPE -- @field #number NoAttack --- @field #number Attack +-- @field #number Any -- @field #number Chop -- @field #number Swing -- @field #number Thrust From 88e4ec198d1b51e6d2efd17143cc76cf1fd7294f Mon Sep 17 00:00:00 2001 From: Max Yari Date: Sat, 22 Jun 2024 00:15:19 +0200 Subject: [PATCH 06/35] Changed value type in the docs --- files/lua_api/openmw/self.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index acb83a3cf4..e57e83b952 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -37,7 +37,7 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump --- @field [parent=#ActorControls] #number use Accepts an @{#AttackTYPE} value. Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#AttackTYPE.NoAttack}. If an @{#AttackTYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#AttackTYPE} will be used instead. +-- @field [parent=#ActorControls] #AttackTYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#AttackTYPE.NoAttack}. If an @{#AttackTYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#AttackTYPE} will be used instead. --- -- @type AttackTYPE From 40dee3059669834702b7088a6559a8475b5aafb7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Jun 2024 02:58:30 +0200 Subject: [PATCH 07/35] Add more integration tests for rotation Some tests might fail due to wrong implementation of the functions. --- .../integration_tests/test_lua_api/player.lua | 92 ++++++++++++++++--- .../integration_tests/test_lua_api/test.lua | 24 ++++- 2 files changed, 102 insertions(+), 14 deletions(-) diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 544bf5adc0..07e9a7967b 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -13,18 +13,88 @@ types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.Magic, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.VanityMode, false) types.Player.setControlSwitch(self, types.Player.CONTROL_SWITCH.ViewMode, false) -testing.registerLocalTest('playerRotation', - function() - local endTime = core.getSimulationTime() + 1 - while core.getSimulationTime() < endTime do - self.controls.jump = false - self.controls.run = true - self.controls.movement = 0 - self.controls.sideMovement = 0 - self.controls.yawChange = util.normalizeAngle(math.rad(90) - self.rotation:getYaw()) * 0.5 - coroutine.yield() +local function rotate(object, targetPitch, targetYaw) + local endTime = core.getSimulationTime() + 1 + while core.getSimulationTime() < endTime do + object.controls.jump = false + object.controls.run = true + object.controls.movement = 0 + object.controls.sideMovement = 0 + if targetPitch ~= nil then + object.controls.pitchChange = util.normalizeAngle(targetPitch - object.rotation:getPitch()) * 0.5 + end + if targetYaw ~= nil then + object.controls.yawChange = util.normalizeAngle(targetYaw - object.rotation:getYaw()) * 0.5 end - testing.expectEqualWithDelta(self.rotation:getYaw(), math.rad(90), 0.05, 'Incorrect rotation') + coroutine.yield() + end +end + +local function rotateByYaw(object, target) + rotate(object, nil, target) +end + +local function rotateByPitch(object, target) + rotate(object, target, nil) +end + +testing.registerLocalTest('playerYawRotation', + function() + local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ() + local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX() + + local targetYaw = math.rad(90) + rotateByYaw(self, targetYaw) + + testing.expectEqualWithDelta(self.rotation:getYaw(), targetYaw, 0.05, 'Incorrect yaw rotation') + + local alpha1, gamma1 = self.rotation:getAnglesXZ() + testing.expectEqualWithDelta(alpha1, initialAlphaXZ, 0.05, 'Alpha rotation in XZ convention should not change') + testing.expectEqualWithDelta(gamma1, targetYaw, 0.05, 'Incorrect gamma rotation in XZ convention') + + local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX() + testing.expectEqualWithDelta(alpha2, targetYaw, 0.05, 'Incorrect alpha rotation in ZYX convention') + testing.expectEqualWithDelta(beta2, initialBetaZYX, 0.05, 'Beta rotation in ZYX convention should not change') + testing.expectEqualWithDelta(gamma2, initialGammaZYX, 0.05, 'Gamma rotation in ZYX convention should not change') + end) + +testing.registerLocalTest('playerPitchRotation', + function() + local initialAlphaXZ, initialGammaXZ = self.rotation:getAnglesXZ() + local initialAlphaZYX, initialBetaZYX, initialGammaZYX = self.rotation:getAnglesZYX() + + local targetPitch = math.rad(90) + rotateByPitch(self, targetPitch) + + testing.expectEqualWithDelta(self.rotation:getPitch(), targetPitch, 0.05, 'Incorrect pitch rotation') + + local alpha1, gamma1 = self.rotation:getAnglesXZ() + testing.expectEqualWithDelta(alpha1, targetPitch, 0.05, 'Incorrect alpha rotation in XZ convention') + testing.expectEqualWithDelta(gamma1, initialGammaXZ, 0.05, 'Gamma rotation in XZ convention should not change') + + local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX() + testing.expectEqualWithDelta(alpha2, initialAlphaZYX, 0.05, 'Alpha rotation in ZYX convention should not change') + testing.expectEqualWithDelta(beta2, initialBetaZYX, 0.05, 'Beta rotation in ZYX convention should not change') + testing.expectEqualWithDelta(gamma2, targetPitch, 0.05, 'Incorrect gamma rotation in ZYX convention') + end) + +testing.registerLocalTest('playerPitchAndYawRotation', + function() + local targetPitch = math.rad(-30) + local targetYaw = math.rad(-60) + rotate(self, targetPitch, targetYaw) + + testing.expectEqualWithDelta(self.rotation:getPitch(), targetPitch, 0.05, 'Incorrect pitch rotation') + testing.expectEqualWithDelta(self.rotation:getYaw(), targetYaw, 0.05, 'Incorrect yaw rotation') + + local alpha1, gamma1 = self.rotation:getAnglesXZ() + testing.expectEqualWithDelta(alpha1, targetPitch, 0.05, 'Incorrect alpha rotation in XZ convention') + testing.expectEqualWithDelta(gamma1, targetYaw, 0.05, 'Incorrect gamma rotation in XZ convention') + + local alpha2, beta2, gamma2 = self.rotation:getAnglesZYX() + testing.expectEqualWithDelta(alpha2, math.rad(-56), 0.05, 'Incorrect alpha rotation in ZYX convention') + testing.expectEqualWithDelta(beta2, math.rad(-25), 0.05, 'Incorrect beta rotation in ZYX convention') + testing.expectEqualWithDelta(gamma2, math.rad(-16), 0.05, 'Incorrect gamma rotation in ZYX convention') end) testing.registerLocalTest('playerForwardRunning', diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 24d19601f8..9acb4e1fc3 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -44,7 +44,17 @@ local function testTeleport() testing.expectEqualWithDelta(player.position.x, 100, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.y, 50, 1, 'incorrect position after teleporting') testing.expectEqualWithDelta(player.position.z, 500, 1, 'incorrect position after teleporting') - testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(90), 0.05, 'incorrect rotation after teleporting') + testing.expectEqualWithDelta(player.rotation:getYaw(), math.rad(90), 0.05, 'incorrect yaw rotation after teleporting') + testing.expectEqualWithDelta(player.rotation:getPitch(), math.rad(0), 0.05, 'incorrect pitch rotation after teleporting') + + local rotationX1, rotationZ1 = player.rotation:getAnglesXZ() + testing.expectEqualWithDelta(rotationX1, math.rad(0), 0.05, 'incorrect x rotation from getAnglesXZ after teleporting') + testing.expectEqualWithDelta(rotationZ1, math.rad(90), 0.05, 'incorrect z rotation from getAnglesXZ after teleporting') + + local rotationZ2, rotationY2, rotationX2 = player.rotation:getAnglesZYX() + testing.expectEqualWithDelta(rotationZ2, math.rad(90), 0.05, 'incorrect z rotation from getAnglesZYX after teleporting') + testing.expectEqualWithDelta(rotationY2, math.rad(0), 0.05, 'incorrect y rotation from getAnglesZYX after teleporting') + testing.expectEqualWithDelta(rotationX2, math.rad(0), 0.05, 'incorrect x rotation from getAnglesZYX after teleporting') player:teleport('', player.position, {rotation=util.transform.rotateZ(math.rad(-90)), onGround=true}) coroutine.yield() @@ -193,9 +203,17 @@ end tests = { {'timers', testTimers}, - {'playerRotation', function() + {'rotating player with controls.yawChange should change rotation', function() + initPlayer() + testing.runLocalTest(player, 'playerYawRotation') + end}, + {'rotating player with controls.pitchChange should change rotation', function() + initPlayer() + testing.runLocalTest(player, 'playerPitchRotation') + end}, + {'rotating player with controls.pitchChange and controls.yawChange should change rotation', function() initPlayer() - testing.runLocalTest(player, 'playerRotation') + testing.runLocalTest(player, 'playerPitchAndYawRotation') end}, {'playerForwardRunning', function() initPlayer() From 89a30532b7b7d0d41c5fc72375b2c63c3c8cb800 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Jun 2024 03:38:03 +0200 Subject: [PATCH 08/35] Fix getAnglesZYX implementation --- components/lua/utilpackage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index e9b8e886d2..a93262bb03 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -240,7 +240,7 @@ namespace LuaUtil return std::make_tuple(angles.x(), angles.z()); }; transMType["getAnglesZYX"] = [](const TransformM& m) { - osg::Vec3f angles = Misc::toEulerAnglesXZ(m.mM); + osg::Vec3f angles = Misc::toEulerAnglesZYX(m.mM); return std::make_tuple(angles.z(), angles.y(), angles.x()); }; @@ -276,7 +276,7 @@ namespace LuaUtil return std::make_tuple(angles.x(), angles.z()); }; transQType["getAnglesZYX"] = [](const TransformQ& q) { - osg::Vec3f angles = Misc::toEulerAnglesXZ(q.mQ); + osg::Vec3f angles = Misc::toEulerAnglesZYX(q.mQ); return std::make_tuple(angles.z(), angles.y(), angles.x()); }; From 13918e3b7042a66a8babf2f2f60f39633e72c988 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Mon, 24 Jun 2024 01:15:43 +0200 Subject: [PATCH 09/35] AttackType -> ATTACK_TYPE --- apps/openmw/mwlua/localscripts.cpp | 2 +- files/lua_api/openmw/self.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 966444b173..4e7b171ff0 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -65,7 +65,7 @@ namespace MWLua selfAPI["controls"] = sol::readonly_property([](SelfObject& self) { return &self.mControls; }); selfAPI["isActive"] = [](SelfObject& self) { return &self.mIsActive; }; selfAPI["enableAI"] = [](SelfObject& self, bool v) { self.mControls.mDisableAI = !v; }; - selfAPI["AttackTYPE"] + selfAPI["ATTACK_TYPE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs( { { "NoAttack", MWMechanics::AttackType::NoAttack }, { "Any", MWMechanics::AttackType::Any }, { "Chop", MWMechanics::AttackType::Chop }, { "Slash", MWMechanics::AttackType::Slash }, diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index e57e83b952..1af7fe99a3 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -37,10 +37,10 @@ -- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump --- @field [parent=#ActorControls] #AttackTYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#AttackTYPE.NoAttack}. If an @{#AttackTYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#AttackTYPE} will be used instead. +-- @field [parent=#ActorControls] #ATTACK_TYPE use Activates the readied weapon/spell according to a provided value. For weapons, keeping this value modified will charge the attack until set to @{#ATTACK_TYPE.NoAttack}. If an @{#ATTACK_TYPE} not appropriate for a currently equipped weapon provided - an appropriate @{#ATTACK_TYPE} will be used instead. --- --- @type AttackTYPE +-- @type ATTACK_TYPE -- @field #number NoAttack -- @field #number Any -- @field #number Chop From 152073a42e7be6ab443e4ee66cfe624183f4b7f3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 24 Jun 2024 12:47:24 +0200 Subject: [PATCH 10/35] Don't trigger onNewExterior while loading saves --- apps/openmw/mwworld/store.cpp | 6 +++ apps/openmw/mwworld/worldimp.cpp | 2 +- apps/openmw/mwworld/worldmodel.cpp | 60 ++++++++++++++++++------------ apps/openmw/mwworld/worldmodel.hpp | 2 + 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 1e92df85ec..b0684b1ab4 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -630,6 +630,12 @@ namespace MWWorld } void Store::clearDynamic() { + for (const auto& [_, cell] : mDynamicExt) + mCells.erase(cell->mId); + mDynamicExt.clear(); + for (const auto& [_, cell] : mDynamicInt) + mCells.erase(cell->mId); + mDynamicInt.clear(); setUp(); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 77bf039276..0bd686ce7f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -473,8 +473,8 @@ namespace MWWorld mStore.write(writer, progress); // dynamic Store must be written (and read) before Cells, so that // references to custom made records will be recognized + mWorldModel.write(writer, progress); // the player's cell needs to be loaded before the player mPlayer->write(writer, progress); - mWorldModel.write(writer, progress); mGlobalVariables.write(writer, progress); mWeatherManager->write(writer, progress); mProjectileManager->write(writer, progress); diff --git a/apps/openmw/mwworld/worldmodel.cpp b/apps/openmw/mwworld/worldmodel.cpp index b9187f59c4..3a1c486f0e 100644 --- a/apps/openmw/mwworld/worldmodel.cpp +++ b/apps/openmw/mwworld/worldmodel.cpp @@ -101,6 +101,24 @@ namespace MWWorld return Cell(*cell); return std::nullopt; } + + CellStore* getOrCreateExterior(const ESM::ExteriorCellLocation& location, + std::map& exteriors, ESMStore& store, + ESM::ReadersCache& readers, std::unordered_map& cells, bool triggerEvent) + { + if (const auto it = exteriors.find(location); it != exteriors.end()) + { + assert(it->second != nullptr); + return it->second; + } + auto [cell, created] = createExteriorCell(location, store); + const ESM::RefId id = cell.getId(); + CellStore* const cellStore = &emplaceCellStore(id, std::move(cell), store, readers, cells); + exteriors.emplace(location, cellStore); + if (created && triggerEvent) + MWBase::Environment::get().getLuaManager()->exteriorCreated(*cellStore); + return cellStore; + } } } @@ -178,23 +196,7 @@ namespace MWWorld { CellStore& WorldModel::getExterior(ESM::ExteriorCellLocation location, bool forceLoad) const { - const auto it = mExteriors.find(location); - CellStore* cellStore = nullptr; - - if (it == mExteriors.end()) - { - auto [cell, created] = createExteriorCell(location, mStore); - const ESM::RefId id = cell.getId(); - cellStore = &emplaceCellStore(id, std::move(cell), mStore, mReaders, mCells); - mExteriors.emplace(location, cellStore); - if (created) - MWBase::Environment::get().getLuaManager()->exteriorCreated(*cellStore); - } - else - { - assert(it->second != nullptr); - cellStore = it->second; - } + CellStore* cellStore = getOrCreateExterior(location, mExteriors, mStore, mReaders, mCells, true); if (forceLoad && cellStore->getState() != CellStore::State_Loaded) cellStore->load(); @@ -447,17 +449,26 @@ void MWWorld::WorldModel::write(ESM::ESMWriter& writer, Loading::Listener& progr } } -struct GetCellStoreCallback : public MWWorld::CellStore::GetCellStoreCallback +struct MWWorld::WorldModel::GetCellStoreCallback : public CellStore::GetCellStoreCallback { public: - GetCellStoreCallback(MWWorld::WorldModel& worldModel) + GetCellStoreCallback(WorldModel& worldModel) : mWorldModel(worldModel) { } - MWWorld::WorldModel& mWorldModel; + WorldModel& mWorldModel; - MWWorld::CellStore* getCellStore(const ESM::RefId& cellId) override { return mWorldModel.findCell(cellId); } + CellStore* getCellStore(const ESM::RefId& cellId) override + { + if (const auto* exteriorId = cellId.getIf()) + { + ESM::ExteriorCellLocation location(exteriorId->getX(), exteriorId->getY(), ESM::Cell::sDefaultWorldspaceId); + return getOrCreateExterior( + location, mWorldModel.mExteriors, mWorldModel.mStore, mWorldModel.mReaders, mWorldModel.mCells, false); + } + return mWorldModel.findCell(cellId); + } }; bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type) @@ -467,7 +478,10 @@ bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type) ESM::CellState state; state.mId = reader.getCellId(); - CellStore* const cellStore = findCell(state.mId); + GetCellStoreCallback callback(*this); + + CellStore* const cellStore = callback.getCellStore(state.mId); + if (cellStore == nullptr) { Log(Debug::Warning) << "Dropping state for cell " << state.mId << " (cell no longer exists)"; @@ -484,8 +498,6 @@ bool MWWorld::WorldModel::readRecord(ESM::ESMReader& reader, uint32_t type) if (cellStore->getState() != CellStore::State_Loaded) cellStore->load(); - GetCellStoreCallback callback(*this); - cellStore->readReferences(reader, &callback); return true; diff --git a/apps/openmw/mwworld/worldmodel.hpp b/apps/openmw/mwworld/worldmodel.hpp index 904dd27411..4c39d866de 100644 --- a/apps/openmw/mwworld/worldmodel.hpp +++ b/apps/openmw/mwworld/worldmodel.hpp @@ -104,6 +104,8 @@ namespace MWWorld bool readRecord(ESM::ESMReader& reader, uint32_t type); private: + struct GetCellStoreCallback; + PtrRegistry mPtrRegistry; // defined before mCells because during destruction it should be the last MWWorld::ESMStore& mStore; From 3eadb841426b5c38e71b9667bd8dae7681b37040 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Wed, 26 Jun 2024 01:25:25 +0200 Subject: [PATCH 11/35] Fixed wrong mUse wrapping --- apps/openmw/mwmechanics/actors.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 300e2316ba..e10de7122f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -378,7 +378,8 @@ namespace MWMechanics stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); // Same as mUse % max AttackType int value - AttackType attackType = static_cast(controls.mUse % static_cast(AttackType::Thrust)); + AttackType attackType + = static_cast(controls.mUse % (static_cast(AttackType::Thrust) + 1)); stats.setAttackingOrSpell(attackType != AttackType::NoAttack); stats.setAttackType(attackTypeName(attackType)); From 1497dae4fa8d040bdebd7a0fbc090677e4aae738 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Fri, 28 Jun 2024 10:00:04 +0200 Subject: [PATCH 12/35] Better mUse out-of-range handling --- apps/openmw/mwmechanics/actors.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e10de7122f..191ad86733 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -242,14 +242,19 @@ namespace MWMechanics { std::string_view attackTypeName(AttackType attackType) { - if (attackType == AttackType::Chop) - return "chop"; - else if (attackType == AttackType::Slash) - return "slash"; - else if (attackType == AttackType::Thrust) - return "thrust"; - else - return ""; + switch (attackType) + { + case AttackType::NoAttack: + case AttackType::Any: + return {}; + case AttackType::Chop: + return "chop"; + case AttackType::Slash: + return "slash"; + case AttackType::Thrust: + return "thrust"; + } + throw std::logic_error("Invalid attack type value: " + std::to_string(static_cast(attackType))); } float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, @@ -377,10 +382,7 @@ namespace MWMechanics stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); - // Same as mUse % max AttackType int value - AttackType attackType - = static_cast(controls.mUse % (static_cast(AttackType::Thrust) + 1)); - + AttackType attackType = static_cast(controls.mUse); stats.setAttackingOrSpell(attackType != AttackType::NoAttack); stats.setAttackType(attackTypeName(attackType)); From 8a022651f767c127e96ba1ab6d9a8f9e63dafccb Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 28 Jun 2024 17:23:51 +0200 Subject: [PATCH 13/35] Move index from ActiveEffect to ActiveSpellEffect --- files/lua_api/openmw/core.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index b75f22748b..3813ebd4f1 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -349,6 +349,7 @@ --- -- @type ActiveSpellEffect +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #string id Magic effect id @@ -729,7 +730,6 @@ -- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires. -- @field #number magnitudeBase -- @field #number magnitudeModifier --- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- @{#Sound}: Sounds and Speech -- @field [parent=#core] #Sound sound From d3e30830061c5ee553292c225242e2bd47d0ad5b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jul 2024 16:57:40 +0200 Subject: [PATCH 14/35] CI match script names and remove dead code --- components/compiler/fileparser.cpp | 11 ++++------- components/compiler/fileparser.hpp | 3 --- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index d699f54ca4..ac0f28de10 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -1,5 +1,7 @@ #include "fileparser.hpp" +#include + #include "scanner.hpp" #include "tokenloc.hpp" @@ -12,11 +14,6 @@ namespace Compiler { } - std::string FileParser::getName() const - { - return mName; - } - Interpreter::Program FileParser::getProgram() const { return mScriptParser.getProgram(); @@ -39,7 +36,7 @@ namespace Compiler if (mState == EndNameState) { // optional repeated name after end statement - if (mName != name) + if (!Misc::StringUtils::ciEqual(mName, name)) reportWarning("Names for script " + mName + " do not match", loc); mState = EndCompleteState; @@ -79,7 +76,7 @@ namespace Compiler if (mState == EndNameState) { // optional repeated name after end statement - if (mName != loc.mLiteral) + if (!Misc::StringUtils::ciEqual(mName, loc.mLiteral)) reportWarning("Names for script " + mName + " do not match", loc); mState = EndCompleteState; diff --git a/components/compiler/fileparser.hpp b/components/compiler/fileparser.hpp index 19e82344fc..e2a7675330 100644 --- a/components/compiler/fileparser.hpp +++ b/components/compiler/fileparser.hpp @@ -28,9 +28,6 @@ namespace Compiler public: FileParser(ErrorHandler& errorHandler, Context& context); - std::string getName() const; - ///< Return script name. - Interpreter::Program getProgram() const; const Locals& getLocals() const; From a9108a743d6fef964738b2e7d31fb6b26a5e889f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 1 Jul 2024 20:50:25 +0400 Subject: [PATCH 15/35] Do not make redundant copy --- apps/opencs/view/world/datadisplaydelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/datadisplaydelegate.cpp b/apps/opencs/view/world/datadisplaydelegate.cpp index 713fedb4ba..c642a05e2b 100644 --- a/apps/opencs/view/world/datadisplaydelegate.cpp +++ b/apps/opencs/view/world/datadisplaydelegate.cpp @@ -71,7 +71,7 @@ bool CSVWorld::DataDisplayDelegate::eventFilter(QObject* target, QEvent* event) QColor themeColor = QApplication::palette().text().color(); if (themeColor != mPixmapsColor) { - mPixmapsColor = themeColor; + mPixmapsColor = std::move(themeColor); buildPixmaps(); } From 723c64a79b9abdc29f0450b3de1111456123444f Mon Sep 17 00:00:00 2001 From: Max Yari Date: Mon, 1 Jul 2024 19:58:33 +0200 Subject: [PATCH 16/35] Lua API version increment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4102a7040..c50d8a7c62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 62) +set(OPENMW_LUA_API_REVISION 63) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") From 45362e0ede98915e18e2208fc87a1db306c80b1e Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 3 Jul 2024 13:51:57 -0700 Subject: [PATCH 17/35] fix distortion breaking in first person meshes --- components/sceneutil/extradata.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 5e91830bba..e616880697 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -37,7 +37,8 @@ namespace SceneUtil osg::StateSet* stateset = node.getOrCreateStateSet(); - stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + stateset->setNestRenderBins(false); + stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); stateset->addUniform(new osg::Uniform("distortionStrength", distortionStrength)); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); From dc7407a34c44dcb0d2229ac7403d4817672f26a0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 1 Jul 2024 20:50:58 +0400 Subject: [PATCH 18/35] Use signed variables to match an API --- apps/opencs/view/world/scriptsubview.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index 793f2e58c1..aa6890904d 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -212,7 +212,7 @@ void CSVWorld::ScriptSubView::useHint(const std::string& hint) if (hint.empty()) return; - unsigned line = 0, column = 0; + int line = 0, column = 0; char c; std::istringstream stream(hint.c_str() + 1); switch (hint[0]) @@ -222,8 +222,8 @@ void CSVWorld::ScriptSubView::useHint(const std::string& hint) { QModelIndex index = mModel->getModelIndex(getUniversalId().getId(), mColumn); QString source = mModel->data(index).toString(); - unsigned stringSize = source.length(); - unsigned pos, dummy; + int stringSize = static_cast(source.length()); + int pos, dummy; if (!(stream >> c >> dummy >> pos)) return; @@ -234,7 +234,7 @@ void CSVWorld::ScriptSubView::useHint(const std::string& hint) pos = stringSize; } - for (unsigned i = 0; i <= pos; ++i) + for (int i = 0; i <= pos; ++i) { if (source[i] == '\n') { From 871263d436e9137e62724a6c27909f697f43c588 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 1 Jul 2024 20:51:44 +0400 Subject: [PATCH 19/35] Check if file is successfully opened --- components/misc/scalableicon.cpp | 7 ++++++- components/platform/application.cpp | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/components/misc/scalableicon.cpp b/components/misc/scalableicon.cpp index 0b30eaf2ab..b3f4304557 100644 --- a/components/misc/scalableicon.cpp +++ b/components/misc/scalableicon.cpp @@ -36,7 +36,12 @@ namespace Misc return QIcon(); QFile iconFile(fileName); - iconFile.open(QIODevice::ReadOnly); + if (!iconFile.open(QIODevice::ReadOnly)) + { + qDebug() << "Failed to open icon file:" << fileName; + return QIcon(); + } + auto content = iconFile.readAll(); if (!content.startsWith(" Date: Thu, 4 Jul 2024 21:07:40 -0700 Subject: [PATCH 20/35] ensure distortion is only rendered once --- apps/openmw/mwrender/distortion.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwrender/distortion.cpp b/apps/openmw/mwrender/distortion.cpp index 2ca2ace65b..ed90944245 100644 --- a/apps/openmw/mwrender/distortion.cpp +++ b/apps/openmw/mwrender/distortion.cpp @@ -2,6 +2,8 @@ #include +#include "postprocessor.hpp" + namespace MWRender { void DistortionCallback::drawImplementation( @@ -10,6 +12,11 @@ namespace MWRender osg::State* state = renderInfo.getState(); size_t frameId = state->getFrameStamp()->getFrameNumber() % 2; + PostProcessor* postProcessor = dynamic_cast(renderInfo.getCurrentCamera()->getUserData()); + + if (!postProcessor || bin->getStage()->getFrameBufferObject() != postProcessor->getPrimaryFbo(frameId)) + return; + mFBO[frameId]->apply(*state); const osg::Texture* tex From 888abc009792332ae47e216d8b6b03d166a9a399 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 26 Jun 2024 05:03:25 +0300 Subject: [PATCH 21/35] Remove time played setting --- apps/launcher/settingspage.cpp | 2 -- apps/launcher/ui/settingspage.ui | 10 ---------- apps/openmw/mwgui/savegamedialog.cpp | 2 +- components/settings/categories/saves.hpp | 1 - docs/source/reference/modding/settings/saves.rst | 13 ------------- files/lang/launcher_de.ts | 8 -------- files/lang/launcher_en.ts | 8 -------- files/lang/launcher_fr.ts | 8 -------- files/lang/launcher_ru.ts | 8 -------- files/lang/launcher_sv.ts | 8 -------- files/settings-default.cfg | 3 --- 11 files changed, 1 insertion(+), 70 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index d336ab4f17..b7f97d24fb 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -319,7 +319,6 @@ bool Launcher::SettingsPage::loadSettings() // Miscellaneous { // Saves - loadSettingBool(Settings::saves().mTimeplayed, *timePlayedCheckbox); loadSettingInt(Settings::saves().mMaxQuicksaves, *maximumQuicksavesComboBox); // Other Settings @@ -512,7 +511,6 @@ void Launcher::SettingsPage::saveSettings() // Miscellaneous { // Saves Settings - saveSettingBool(*timePlayedCheckbox, Settings::saves().mTimeplayed); saveSettingInt(*maximumQuicksavesComboBox, Settings::saves().mMaxQuicksaves); // Other Settings diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 758e08481e..9362920daf 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1430,16 +1430,6 @@ Saves - - - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - - - Add "Time Played" to Saves - - - diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index f83250dfd0..94f25e118b 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -435,7 +435,7 @@ namespace MWGui mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}"); - if (Settings::saves().mTimeplayed) + if (mCurrentSlot->mProfile.mTimePlayed > 0) { text << "\n" << "#{OMWEngine:TimePlayed}: " << formatTimeplayed(mCurrentSlot->mProfile.mTimePlayed); diff --git a/components/settings/categories/saves.hpp b/components/settings/categories/saves.hpp index 3a64785412..4861c68ba9 100644 --- a/components/settings/categories/saves.hpp +++ b/components/settings/categories/saves.hpp @@ -20,7 +20,6 @@ namespace Settings SettingValue mCharacter{ mIndex, "Saves", "character" }; SettingValue mAutosave{ mIndex, "Saves", "autosave" }; - SettingValue mTimeplayed{ mIndex, "Saves", "timeplayed" }; SettingValue mMaxQuicksaves{ mIndex, "Saves", "max quicksaves", makeMaxSanitizerInt(1) }; }; } diff --git a/docs/source/reference/modding/settings/saves.rst b/docs/source/reference/modding/settings/saves.rst index 0e58d66d84..88aa48fc56 100644 --- a/docs/source/reference/modding/settings/saves.rst +++ b/docs/source/reference/modding/settings/saves.rst @@ -21,19 +21,6 @@ This setting determines whether the game will be automatically saved when the ch This setting can be toggled in game with the Auto-Save when Rest button in the Prefs panel of the Options menu. -timeplayed ----------- - -:Type: boolean -:Range: True/False -:Default: False - -This setting determines whether the amount of the time the player has spent playing will be displayed -for each saved game in the Load menu. Currently, the counter includes time spent in menus, including the pause menu, -but does not include time spent with the game window minimized. - -This setting can only be configured by editing the settings configuration file. - max quicksaves -------------- diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index a39a3de4fd..9532fcc8ec 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -920,10 +920,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Saves - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - - JPG @@ -1415,10 +1411,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Can Zoom on Maps - - Add "Time Played" to Saves - - Notify on Saved Screenshot diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index de20f743e8..36506bfb50 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1363,14 +1363,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Saves - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - - - - Add "Time Played" to Saves - - Maximum Quicksaves diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 688726cf96..3cb015db67 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -920,10 +920,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Saves Sauvegardes - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - <html><body><p>Cette option affiche le temps de jeu de chaque sauvegarde dans leur menu de sélection.</p></body></html> - JPG JPG @@ -1418,10 +1414,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Can Zoom on Maps Permettre le zoom sur la carte - - Add "Time Played" to Saves - Ajoute le temps de jeu aux sauvegardes - Notify on Saved Screenshot Notifier l'enregistrement des captures d'écran diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index a64eae38a3..09b2fdf0e9 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1140,14 +1140,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Saves Сохранения - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - <html><head/><body><p>Эта настройка определяет, будет ли отображаться время с начала новой игры для выбранного сохранения в меню загрузки.</p></body></html> - - - Add "Time Played" to Saves - Выводить "Время в игре" в сохранениях - JPG JPG diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index 59d076b79c..2d590fea11 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -933,10 +933,6 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Saves Sparfiler - - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - <html><head/><body><p>Denna inställning avgör huruvida mängden tid spelaren har spenderat i spelet kommer visas för varje sparat spel i Ladda spel-menyn.</p></body></html> - JPG JPG @@ -1434,10 +1430,6 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Can Zoom on Maps Kan zooma på kartor - - Add "Time Played" to Saves - Lägg till spelad tid i sparfiler - Notify on Saved Screenshot Ge notis vid sparad skärmdump diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 26722268a0..325bc57618 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -566,9 +566,6 @@ character = # Automatically save the game whenever the player rests. autosave = true -# Display the time played on each save file in the load menu. -timeplayed = false - # The maximum number of quick (or auto) save slots to have. # If all slots are used, the oldest save is reused max quicksaves = 1 From 045e6d8c4f7e176d336570e223f0b58d3b53331b Mon Sep 17 00:00:00 2001 From: Kindi Date: Fri, 14 Jun 2024 13:31:29 +0800 Subject: [PATCH 22/35] move enchant charge and soul to itemdata --- apps/openmw/mwlua/itemdata.cpp | 113 ++++++++++++++++++++++++------- apps/openmw/mwlua/types/item.cpp | 1 + apps/openmw/mwlua/types/misc.cpp | 2 + files/lua_api/openmw/types.lua | 14 ++-- 4 files changed, 101 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index 3e2b755af8..9ae9f57b34 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -1,22 +1,28 @@ #include "itemdata.hpp" +#include +#include + #include "context.hpp" #include "luamanagerimp.hpp" #include "objectvariant.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/spellutil.hpp" + #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" namespace { using SelfObject = MWLua::SelfObject; using Index = const SelfObject::CachedStat::Index&; - constexpr std::array properties = { "condition", /*"enchantmentCharge", "soul", "owner", etc..*/ }; + constexpr std::array properties = { "condition", "enchantmentCharge", "soul" }; - void invalidPropErr(std::string_view prop, const MWWorld::Ptr& ptr) + void valueErr(std::string_view prop, std::string type) { - throw std::runtime_error("'" + std::string(prop) + "'" + " property does not exist for item " - + std::string(ptr.getClass().getName(ptr)) + "(" + std::string(ptr.getTypeDescription()) + ")"); + throw std::logic_error("'" + std::string(prop) + "'" + " received invalid value type (" + type + ")"); } } @@ -54,26 +60,56 @@ namespace MWLua if (it != self->mStatsCache.end()) return it->second; } - return sol::make_object(context.mLua->sol(), getValue(context, prop)); + return sol::make_object(context.mLua->sol(), getValue(context, prop, mObject.ptr())); } void set(const Context& context, std::string_view prop, const sol::object& value) const { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; + if (mObject.isGObject()) + setValue({}, prop, mObject.ptr(), value); + else if (mObject.isSelfObject()) + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; + } + else + throw std::runtime_error("Only global or self scripts can set the value"); } - sol::object getValue(const Context& context, std::string_view prop) const + static sol::object getValue(const Context& context, std::string_view prop, const MWWorld::Ptr& ptr) { if (prop == "condition") { - MWWorld::Ptr o = mObject.ptr(); - if (o.mRef->getType() == ESM::REC_LIGH) - return sol::make_object(context.mLua->sol(), o.getClass().getRemainingUsageTime(o)); - else if (o.getClass().hasItemHealth(o)) - return sol::make_object( - context.mLua->sol(), o.getClass().getItemHealth(o) + o.getCellRef().getChargeIntRemainder()); + if (ptr.mRef->getType() == ESM::REC_LIGH) + return sol::make_object(context.mLua->sol(), ptr.getClass().getRemainingUsageTime(ptr)); + else if (ptr.getClass().hasItemHealth(ptr)) + return sol::make_object(context.mLua->sol(), + ptr.getClass().getItemHealth(ptr) + ptr.getCellRef().getChargeIntRemainder()); + } + else if (prop == "enchantmentCharge") + { + const ESM::RefId& enchantmentName = ptr.getClass().getEnchantment(ptr); + + if (enchantmentName.empty()) + return sol::lua_nil; + + float charge = ptr.getCellRef().getEnchantmentCharge(); + const auto& store = MWBase::Environment::get().getESMStore(); + const auto* enchantment = store->get().find(enchantmentName); + + if (charge == -1) // return the full charge + return sol::make_object(context.mLua->sol(), MWMechanics::getEnchantmentCharge(*enchantment)); + + return sol::make_object(context.mLua->sol(), charge); + } + else if (prop == "soul") + { + ESM::RefId soul = ptr.getCellRef().getSoul(); + if (soul.empty()) + return sol::lua_nil; + + return sol::make_object(context.mLua->sol(), soul.serializeText()); } return sol::lua_nil; @@ -83,17 +119,48 @@ namespace MWLua { if (prop == "condition") { - float cond = LuaUtil::cast(value); - if (ptr.mRef->getType() == ESM::REC_LIGH) - ptr.getClass().setRemainingUsageTime(ptr, cond); - else if (ptr.getClass().hasItemHealth(ptr)) + if (value.get_type() == sol::type::number) + { + float cond = LuaUtil::cast(value); + if (ptr.mRef->getType() == ESM::REC_LIGH) + ptr.getClass().setRemainingUsageTime(ptr, cond); + else if (ptr.getClass().hasItemHealth(ptr)) + { + // if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 + ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); + ptr.getCellRef().setCharge(std::max(0.f, cond)); + } + } + else + valueErr(prop, sol::type_name(value.lua_state(), value.get_type())); + } + else if (prop == "enchantmentCharge") + { + if (value.get_type() == sol::type::lua_nil) + ptr.getCellRef().setEnchantmentCharge(-1); + else if (value.get_type() == sol::type::number) + ptr.getCellRef().setEnchantmentCharge(std::max(0.0f, LuaUtil::cast(value))); + else + valueErr(prop, sol::type_name(value.lua_state(), value.get_type())); + } + else if (prop == "soul") + { + if (value.get_type() == sol::type::lua_nil) + ptr.getCellRef().setSoul(ESM::RefId{}); + else if (value.get_type() == sol::type::string) { - // if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 - ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); - ptr.getCellRef().setCharge(std::max(0.f, cond)); + std::string_view souldId = LuaUtil::cast(value); + ESM::RefId creature = ESM::RefId::deserializeText(souldId); + const auto& store = *MWBase::Environment::get().getESMStore(); + + // TODO: Add Support for NPC Souls + if (store.get().search(creature)) + ptr.getCellRef().setSoul(creature); + else + throw std::runtime_error("Cannot use non-existent creature as a soul: " + std::string(souldId)); } else - invalidPropErr(prop, ptr); + valueErr(prop, sol::type_name(value.lua_state(), value.get_type())); } } }; diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 8f05ce8e93..ba995a9808 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -11,6 +11,7 @@ namespace MWLua { void addItemBindings(sol::table item, const Context& context) { + // Deprecated. Moved to itemData; should be removed later item["getEnchantmentCharge"] = [](const Object& object) -> sol::optional { float charge = object.ptr().getCellRef().getEnchantmentCharge(); if (charge == -1) diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index f83864477f..8fc9406cfd 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -55,6 +55,7 @@ namespace MWLua addRecordFunctionBinding(miscellaneous, context); miscellaneous["createRecordDraft"] = tableToMisc; + // Deprecated. Moved to itemData; should be removed later miscellaneous["setSoul"] = [](const GObject& object, std::string_view soulId) { ESM::RefId creature = ESM::RefId::deserializeText(soulId); const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -75,6 +76,7 @@ namespace MWLua return soul.serializeText(); }; miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Miscellaneous"); record[sol::meta_function::to_string] diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 1e0b486524..f9aeaf97af 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -750,7 +750,7 @@ -- @return #boolean --- --- Get this item's current enchantment charge. +-- (DEPRECATED, use itemData(item).enchantmentCharge) Get this item's current enchantment charge. -- @function [parent=#Item] getEnchantmentCharge -- @param openmw.core#GameObject item -- @return #number The charge remaining. `nil` if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of `nil`. @@ -763,7 +763,7 @@ -- @return #boolean --- --- Set this item's enchantment charge. +-- (DEPRECATED, use itemData(item).enchantmentCharge) Set this item's enchantment charge. -- @function [parent=#Item] setEnchantmentCharge -- @param openmw.core#GameObject item -- @param #number charge Can be `nil` to reset the unused state / full @@ -777,14 +777,16 @@ -- @return #boolean --- --- Set of properties that differentiates one item from another of the same record type. +-- Set of properties that differentiates one item from another of the same record type; can be used by any script, but only global and self scripts can change values. -- @function [parent=#Item] itemData -- @param openmw.core#GameObject item -- @return #ItemData --- -- @type ItemData --- @field #number condition The item's current condition. Time remaining for lights. Uses left for lockpicks and probes. Current health for weapons and armor. +-- @field #number condition The item's current condition. Time remaining for lights. Uses left for repairs, lockpicks and probes. Current health for weapons and armor. +-- @field #number enchantmentCharge The item's current enchantment charge. Unenchanted items will always return a value of `nil`. Setting this to `nil` will reset the charge of the item. +-- @field #string soul The recordId of the item's current soul. Items without soul will always return a value of `nil`. Setting this to `nil` will remove the soul from the item. -------------------------------------------------------------------------------- -- @{#Creature} functions @@ -1689,7 +1691,7 @@ -- @return #MiscellaneousRecord --- --- Returns the read-only soul of a miscellaneous item +-- (DEPRECATED, use itemData(item).soul) Returns the read-only soul of a miscellaneous item -- @function [parent=#Miscellaneous] getSoul -- @param openmw.core#GameObject object -- @return #string @@ -1702,7 +1704,7 @@ -- @return #MiscellaneousRecord A strongly typed Miscellaneous record. --- --- Sets the soul of a miscellaneous item, intended for soul gem objects; Must be used in a global script. +-- (DEPRECATED, use itemData(item).soul) Sets the soul of a miscellaneous item, intended for soul gem objects; Must be used in a global script. -- @function [parent=#Miscellaneous] setSoul -- @param openmw.core#GameObject object -- @param #string soulId Record ID for the soul of the creature to use From bf9f5dc2eff24d4dc0ff1162c83df5f45d684796 Mon Sep 17 00:00:00 2001 From: Kindi Date: Tue, 2 Jul 2024 01:07:24 +0800 Subject: [PATCH 23/35] dont return empty string for absent value --- apps/openmw/mwlua/types/armor.cpp | 7 +++++-- apps/openmw/mwlua/types/book.cpp | 7 +++++-- apps/openmw/mwlua/types/clothing.cpp | 7 +++++-- apps/openmw/mwlua/types/weapon.cpp | 7 +++++-- files/lua_api/openmw/types.lua | 8 ++++---- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index 91a4c05d8b..8c741a4684 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -98,8 +98,11 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["enchant"] - = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["enchant"] = sol::readonly_property([](const ESM::Armor& rec) -> sol::optional { + if (rec.mEnchant.empty()) + return sol::nullopt; + return rec.mEnchant.serializeText(); + }); record["mwscript"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mWeight; }); diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index ce2138127d..92bea13748 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -110,8 +110,11 @@ namespace MWLua return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; }); - record["enchant"] - = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["enchant"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional { + if (rec.mEnchant.empty()) + return sol::nullopt; + return rec.mEnchant.serializeText(); + }); record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; }); record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; }); diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 894748946d..8679ce0b67 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -93,8 +93,11 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["enchant"] = sol::readonly_property( - [](const ESM::Clothing& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["enchant"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional { + if (rec.mEnchant.empty()) + return sol::nullopt; + return rec.mEnchant.serializeText(); + }); record["mwscript"] = sol::readonly_property( [](const ESM::Clothing& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mWeight; }); diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index f09cd96dad..71fab2e0f5 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -131,8 +131,11 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["enchant"] = sol::readonly_property( - [](const ESM::Weapon& rec) -> std::string { return rec.mEnchant.serializeText(); }); + record["enchant"] = sol::readonly_property([](const ESM::Weapon& rec) -> sol::optional { + if (rec.mEnchant.empty()) + return sol::nullopt; + return rec.mEnchant.serializeText(); + }); record["mwscript"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript.serializeText(); }); record["isMagical"] = sol::readonly_property( diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 1e0b486524..53120b1733 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1327,7 +1327,7 @@ -- @field #string model VFS path to the model -- @field #string mwscript MWScript on this armor (can be empty) -- @field #string icon VFS path to the icon --- @field #string enchant The enchantment ID of this armor (can be empty) +-- @field #string enchant The enchantment ID of this armor (can be nil) -- @field #number weight -- @field #number value -- @field #number type See @{#Armor.TYPE} @@ -1416,7 +1416,7 @@ -- @field #string model VFS path to the model -- @field #string mwscript MWScript on this book (can be empty) -- @field #string icon VFS path to the icon --- @field #string enchant The enchantment ID of this book (can be empty) +-- @field #string enchant The enchantment ID of this book (can be nil) -- @field #string text The text content of the book -- @field #number weight -- @field #number value @@ -1494,7 +1494,7 @@ -- @field #string model VFS path to the model -- @field #string mwscript MWScript on this clothing (can be empty) -- @field #string icon VFS path to the icon --- @field #string enchant The enchantment ID of this clothing (can be empty) +-- @field #string enchant The enchantment ID of this clothing (can be nil) -- @field #number weight -- @field #number value -- @field #number type See @{#Clothing.TYPE} @@ -1819,7 +1819,7 @@ -- @field #string model VFS path to the model -- @field #string mwscript MWScript on this weapon (can be empty) -- @field #string icon VFS path to the icon --- @field #string enchant +-- @field #string enchant The enchantment ID of this weapon (can be nil) -- @field #boolean isMagical -- @field #boolean isSilver -- @field #number weight From ee653eb2b87a7013803cbd6548000c195015d4c7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Jul 2024 13:58:34 +0200 Subject: [PATCH 24/35] Make mwscript return nil for records that don't have a script --- CMakeLists.txt | 2 +- apps/openmw/mwlua/types/activator.cpp | 6 +++-- apps/openmw/mwlua/types/apparatus.cpp | 6 +++-- apps/openmw/mwlua/types/armor.cpp | 12 ++++----- apps/openmw/mwlua/types/book.cpp | 16 +++++------- apps/openmw/mwlua/types/clothing.cpp | 10 ++++---- apps/openmw/mwlua/types/container.cpp | 6 +++-- apps/openmw/mwlua/types/creature.cpp | 6 +++-- apps/openmw/mwlua/types/door.cpp | 5 ++-- apps/openmw/mwlua/types/ingredient.cpp | 5 ++-- apps/openmw/mwlua/types/light.cpp | 5 ++-- apps/openmw/mwlua/types/lockpick.cpp | 6 +++-- apps/openmw/mwlua/types/misc.cpp | 11 ++++----- apps/openmw/mwlua/types/npc.cpp | 4 +-- apps/openmw/mwlua/types/potion.cpp | 4 +-- apps/openmw/mwlua/types/probe.cpp | 5 ++-- apps/openmw/mwlua/types/repair.cpp | 5 ++-- apps/openmw/mwlua/types/weapon.cpp | 12 ++++----- components/lua/util.hpp | 12 +++++++++ files/lua_api/openmw/types.lua | 34 +++++++++++++------------- 20 files changed, 95 insertions(+), 77 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43a645ed14..83d81999ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 63) +set(OPENMW_LUA_API_REVISION 64) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index 43e03952f7..3531a476ad 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -51,7 +52,8 @@ namespace MWLua record["model"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Activator& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Activator& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); } } diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp index 282dd0669d..025cba6550 100644 --- a/apps/openmw/mwlua/types/apparatus.cpp +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -39,8 +40,9 @@ namespace MWLua record["model"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Apparatus& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Apparatus& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); record["icon"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index 8c741a4684..3d1250af13 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -98,13 +99,10 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["enchant"] = sol::readonly_property([](const ESM::Armor& rec) -> sol::optional { - if (rec.mEnchant.empty()) - return sol::nullopt; - return rec.mEnchant.serializeText(); - }); - record["mwscript"] - = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mScript.serializeText(); }); + record["enchant"] = sol::readonly_property( + [](const ESM::Armor& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mEnchant); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Armor& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["weight"] = sol::readonly_property([](const ESM::Armor& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mValue; }); record["type"] = sol::readonly_property([](const ESM::Armor& rec) -> int { return rec.mData.mType; }); diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 92bea13748..0733d89914 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -104,17 +105,14 @@ namespace MWLua record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property( [](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] - = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Book& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["text"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mText; }); - record["enchant"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional { - if (rec.mEnchant.empty()) - return sol::nullopt; - return rec.mEnchant.serializeText(); - }); + record["enchant"] = sol::readonly_property( + [](const ESM::Book& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mEnchant); }); record["isScroll"] = sol::readonly_property([](const ESM::Book& rec) -> bool { return rec.mData.mIsScroll; }); record["value"] = sol::readonly_property([](const ESM::Book& rec) -> int { return rec.mData.mValue; }); record["weight"] = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mWeight; }); @@ -122,9 +120,7 @@ namespace MWLua = sol::readonly_property([](const ESM::Book& rec) -> float { return rec.mData.mEnchant * 0.1f; }); record["skill"] = sol::readonly_property([](const ESM::Book& rec) -> sol::optional { ESM::RefId skill = ESM::Skill::indexToRefId(rec.mData.mSkillId); - if (!skill.empty()) - return skill.serializeText(); - return sol::nullopt; + return LuaUtil::serializeRefId(skill); }); } } diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 8679ce0b67..733cf5ba11 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -94,12 +95,11 @@ namespace MWLua return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); record["enchant"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional { - if (rec.mEnchant.empty()) - return sol::nullopt; - return rec.mEnchant.serializeText(); + return LuaUtil::serializeRefId(rec.mEnchant); + }); + record["mwscript"] = sol::readonly_property([](const ESM::Clothing& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Clothing& rec) -> std::string { return rec.mScript.serializeText(); }); record["weight"] = sol::readonly_property([](const ESM::Clothing& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mValue; }); record["type"] = sol::readonly_property([](const ESM::Clothing& rec) -> int { return rec.mData.mType; }); diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index 55f3e4a10e..b5d9c082bd 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -51,8 +52,9 @@ namespace MWLua record["model"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Container& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Container& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); record["weight"] = sol::readonly_property([](const ESM::Container& rec) -> float { return rec.mWeight; }); record["isOrganic"] = sol::readonly_property( [](const ESM::Container& rec) -> bool { return rec.mFlags & ESM::Container::Organic; }); diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index dd4b1bd67b..61ddc3f4fa 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -36,8 +37,9 @@ namespace MWLua record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property( [](const ESM::Creature& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Creature& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Creature& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); record["baseCreature"] = sol::readonly_property( [](const ESM::Creature& rec) -> std::string { return rec.mOriginal.serializeText(); }); record["soulValue"] = sol::readonly_property([](const ESM::Creature& rec) -> int { return rec.mData.mSoul; }); diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index df1d10015a..1c0cc07b00 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -64,8 +65,8 @@ namespace MWLua record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property( [](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] - = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Door& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["openSound"] = sol::readonly_property( [](const ESM::Door& rec) -> std::string { return rec.mOpenSound.serializeText(); }); record["closeSound"] = sol::readonly_property( diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index a76869308b..0df31d1e85 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -34,8 +34,9 @@ namespace MWLua record["model"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Ingredient& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Ingredient& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); record["icon"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index 2784e5a770..ab758c22ec 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -96,8 +97,8 @@ namespace MWLua }); record["sound"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mSound.serializeText(); }); - record["mwscript"] - = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Light& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["weight"] = sol::readonly_property([](const ESM::Light& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mValue; }); record["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; }); diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp index 373de4b24d..4186f44378 100644 --- a/apps/openmw/mwlua/types/lockpick.cpp +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -31,8 +32,9 @@ namespace MWLua record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property( [](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Lockpick& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Lockpick& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index 8fc9406cfd..85eb03524c 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -70,10 +71,7 @@ namespace MWLua }; miscellaneous["getSoul"] = [](const Object& object) -> sol::optional { ESM::RefId soul = object.ptr().getCellRef().getSoul(); - if (soul.empty()) - return sol::nullopt; - else - return soul.serializeText(); + return LuaUtil::serializeRefId(soul); }; miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later @@ -87,8 +85,9 @@ namespace MWLua record["model"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] = sol::readonly_property( - [](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> sol::optional { + return LuaUtil::serializeRefId(rec.mScript); + }); record["icon"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index f8a1a82e54..0f5c82f1dd 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -85,8 +85,8 @@ namespace MWLua = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mRace.serializeText(); }); record["class"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mClass.serializeText(); }); - record["mwscript"] - = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::NPC& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["hair"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.serializeText(); }); record["baseDisposition"] diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index cc4dd5700c..a3dab55224 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -80,8 +80,8 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["mwscript"] - = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Potion& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["weight"] = sol::readonly_property([](const ESM::Potion& rec) -> float { return rec.mData.mWeight; }); record["value"] = sol::readonly_property([](const ESM::Potion& rec) -> int { return rec.mData.mValue; }); record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table { diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp index 30c84326a5..5db8141a23 100644 --- a/apps/openmw/mwlua/types/probe.cpp +++ b/apps/openmw/mwlua/types/probe.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -31,8 +32,8 @@ namespace MWLua record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property( [](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] - = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Probe& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp index 880a74d131..4d35a55fbe 100644 --- a/apps/openmw/mwlua/types/repair.cpp +++ b/apps/openmw/mwlua/types/repair.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -31,8 +32,8 @@ namespace MWLua record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); record["model"] = sol::readonly_property( [](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); - record["mwscript"] - = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript.serializeText(); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Repair& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 71fab2e0f5..386b81c843 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -131,13 +132,10 @@ namespace MWLua record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); - record["enchant"] = sol::readonly_property([](const ESM::Weapon& rec) -> sol::optional { - if (rec.mEnchant.empty()) - return sol::nullopt; - return rec.mEnchant.serializeText(); - }); - record["mwscript"] - = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mScript.serializeText(); }); + record["enchant"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mEnchant); }); + record["mwscript"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> sol::optional { return LuaUtil::serializeRefId(rec.mScript); }); record["isMagical"] = sol::readonly_property( [](const ESM::Weapon& rec) -> bool { return rec.mData.mFlags & ESM::Weapon::Magical; }); record["isSilver"] = sol::readonly_property( diff --git a/components/lua/util.hpp b/components/lua/util.hpp index 640b5eeeb0..2b84d9d60c 100644 --- a/components/lua/util.hpp +++ b/components/lua/util.hpp @@ -2,6 +2,11 @@ #define COMPONENTS_LUA_UTIL_H #include +#include + +#include + +#include namespace LuaUtil { @@ -15,6 +20,13 @@ namespace LuaUtil { return i + 1; } + + inline sol::optional serializeRefId(ESM::RefId id) + { + if (id.empty()) + return sol::nullopt; + return id.serializeText(); + } } #endif diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d686df144a..7606015c15 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -837,7 +837,7 @@ -- @field #string name -- @field #string baseCreature Record id of a base creature, which was modified to create this one -- @field #string model VFS path to the creature's model --- @field #string mwscript +-- @field #string mwscript MWScript on this creature (can be nil) -- @field #number soulValue The soul value of the creature record -- @field #number type The @{#Creature.TYPE} of the creature -- @field #number baseGold The base barter gold of the creature @@ -1117,7 +1117,7 @@ -- @field #string race -- @field #string class Name of the NPC's class (e. g. Acrobat) -- @field #string model Path to the model associated with this NPC, used for animations. --- @field #string mwscript MWScript that is attached to this NPC +-- @field #string mwscript MWScript on this NPC (can be nil) -- @field #string hair Path to the hair body part model -- @field #string head Path to the head body part model -- @field #number baseGold The base barter gold of the NPC @@ -1327,7 +1327,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this armor (can be empty) +-- @field #string mwscript MWScript on this armor (can be nil) -- @field #string icon VFS path to the icon -- @field #string enchant The enchantment ID of this armor (can be nil) -- @field #number weight @@ -1416,7 +1416,7 @@ -- @field #string id The record ID of the book -- @field #string name Name of the book -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this book (can be empty) +-- @field #string mwscript MWScript on this book (can be nil) -- @field #string icon VFS path to the icon -- @field #string enchant The enchantment ID of this book (can be nil) -- @field #string text The text content of the book @@ -1494,7 +1494,7 @@ -- @field #string id Record id -- @field #string name Name of the clothing -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this clothing (can be empty) +-- @field #string mwscript MWScript on this clothing (can be nil) -- @field #string icon VFS path to the icon -- @field #string enchant The enchantment ID of this clothing (can be nil) -- @field #number weight @@ -1537,7 +1537,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this potion (can be empty) +-- @field #string mwscript MWScript on this potion (can be nil) -- @field #string icon VFS path to the icon -- @field #number weight -- @field #number value @@ -1643,7 +1643,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this light (can be empty) +-- @field #string mwscript MWScript on this light (can be nil) -- @field #string icon VFS path to the icon -- @field #string sound VFS path to the sound -- @field #number weight @@ -1714,7 +1714,7 @@ -- @field #string id The record ID of the miscellaneous item -- @field #string name The name of the miscellaneous item -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this miscellaneous item (can be empty) +-- @field #string mwscript MWScript on this miscellaneous item (can be nil) -- @field #string icon VFS path to the icon -- @field #number weight -- @field #number value @@ -1759,7 +1759,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this potion (can be empty) +-- @field #string mwscript MWScript on this potion (can be nil) -- @field #string icon VFS path to the icon -- @field #number weight -- @field #number value @@ -1819,7 +1819,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this weapon (can be empty) +-- @field #string mwscript MWScript on this weapon (can be nil) -- @field #string icon VFS path to the icon -- @field #string enchant The enchantment ID of this weapon (can be nil) -- @field #boolean isMagical @@ -1888,7 +1888,7 @@ -- @field #string id The record ID of the apparatus -- @field #string name The name of the apparatus -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this apparatus (can be empty) +-- @field #string mwscript MWScript on this apparatus (can be nil) -- @field #string icon VFS path to the icon -- @field #number type The type of apparatus. See @{#Apparatus.TYPE} -- @field #number weight @@ -1927,7 +1927,7 @@ -- @field #string id The record ID of the lockpick -- @field #string name The name of the lockpick -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this lockpick (can be empty) +-- @field #string mwscript MWScript on this lockpick (can be nil) -- @field #string icon VFS path to the icon -- @field #number maxCondition The maximum number of uses of this lockpick -- @field #number weight @@ -1966,7 +1966,7 @@ -- @field #string id The record ID of the probe -- @field #string name The name of the probe -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this probe (can be empty) +-- @field #string mwscript MWScript on this probe (can be nil) -- @field #string icon VFS path to the icon -- @field #number maxCondition The maximum number of uses of this probe -- @field #number weight @@ -2005,7 +2005,7 @@ -- @field #string id The record ID of the repair tool -- @field #string name The name of the repair tool -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this repair tool (can be empty) +-- @field #string mwscript MWScript on this repair tool (can be nil) -- @field #string icon VFS path to the icon -- @field #number maxCondition The maximum number of uses of this repair tool -- @field #number weight @@ -2042,7 +2042,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this activator (can be empty) +-- @field #string mwscript MWScript on this activator (can be nil) --- -- Creates a @{#ActivatorRecord} without adding it to the world database. @@ -2109,7 +2109,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this container (can be empty) +-- @field #string mwscript MWScript on this container (can be nil) -- @field #number weight capacity of this container -- @field #boolean isOrganic Whether items can be placed in the container -- @field #boolean isRespawning Whether the container respawns its contents @@ -2171,7 +2171,7 @@ -- @field #string id Record id -- @field #string name Human-readable name -- @field #string model VFS path to the model --- @field #string mwscript MWScript on this door (can be empty) +-- @field #string mwscript MWScript on this door (can be nil) -- @field #string openSound VFS path to the sound of opening -- @field #string closeSound VFS path to the sound of closing From 4bacf0d55afbf5fd6129d33be414a4125368f2f7 Mon Sep 17 00:00:00 2001 From: Kuyondo Date: Sun, 7 Jul 2024 21:03:43 +0800 Subject: [PATCH 25/35] Its sound id not sound path --- files/lua_api/openmw/types.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d686df144a..8d54cf14df 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -2172,8 +2172,8 @@ -- @field #string name Human-readable name -- @field #string model VFS path to the model -- @field #string mwscript MWScript on this door (can be empty) --- @field #string openSound VFS path to the sound of opening --- @field #string closeSound VFS path to the sound of closing +-- @field #string openSound The sound id for door opening +-- @field #string closeSound The sound id for door closing From 0342a299cc8bff91e4a79094b98ebfe41e8f6760 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Jul 2024 15:05:19 +0200 Subject: [PATCH 26/35] Require Pursue packages to target a player --- files/data/scripts/omw/ai.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/data/scripts/omw/ai.lua b/files/data/scripts/omw/ai.lua index 59455dc8cb..e875e9f616 100644 --- a/files/data/scripts/omw/ai.lua +++ b/files/data/scripts/omw/ai.lua @@ -1,5 +1,6 @@ local self = require('openmw.self') local interfaces = require('openmw.interfaces') +local types = require('openmw.types') local util = require('openmw.util') local function startPackage(args) @@ -10,6 +11,7 @@ local function startPackage(args) self:_startAiCombat(args.target, cancelOther) elseif args.type == 'Pursue' then if not args.target then error("target required") end + if not types.Player.objectIsInstance(args.target) then error("target must be a player") end self:_startAiPursue(args.target, cancelOther) elseif args.type == 'Follow' then if not args.target then error("target required") end From 299ee1a6b117397f96ed563204a286cc82328892 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jul 2024 17:07:52 +0200 Subject: [PATCH 27/35] Keep running scripts until the menu pauses the game --- apps/openmw/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 10c0ba5ab0..ca1b0f577d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -245,7 +245,7 @@ bool OMW::Engine::frame(unsigned frameNumber, float frametime) if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - if (!mWindowManager->containsMode(MWGui::GM_MainMenu)) + if (!mWindowManager->containsMode(MWGui::GM_MainMenu) || !paused) { if (mWorld->getScriptsEnabled()) { From 134c099df9ca166d5728b8cf4876db54fdaa6a6e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jul 2024 19:59:50 +0200 Subject: [PATCH 28/35] Attempt to select an AI action directly instead of waiting for the next frame --- apps/openmw/mwmechanics/aiwander.cpp | 46 +++++++++++++++++----------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 38466c0c4c..3c299c1490 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -455,27 +455,37 @@ namespace MWMechanics void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, MWWorld::MovementDirectionFlags supportedMovementDirections, AiWanderStorage& storage) { - switch (storage.mState) + // Attempt to fast forward to the next state instead of remaining in an intermediate state for a frame + for (int i = 0; i < 2; ++i) { - case AiWanderStorage::Wander_IdleNow: - onIdleStatePerFrameActions(actor, duration, storage); - break; - - case AiWanderStorage::Wander_Walking: - onWalkingStatePerFrameActions(actor, duration, supportedMovementDirections, storage); - break; - - case AiWanderStorage::Wander_ChooseAction: - onChooseActionStatePerFrameActions(actor, storage); - break; + switch (storage.mState) + { + case AiWanderStorage::Wander_IdleNow: + { + onIdleStatePerFrameActions(actor, duration, storage); + if (storage.mState != AiWanderStorage::Wander_ChooseAction) + return; + continue; + } + case AiWanderStorage::Wander_Walking: + onWalkingStatePerFrameActions(actor, duration, supportedMovementDirections, storage); + return; - case AiWanderStorage::Wander_MoveNow: - break; // nothing to do + case AiWanderStorage::Wander_ChooseAction: + { + onChooseActionStatePerFrameActions(actor, storage); + if (storage.mState != AiWanderStorage::Wander_IdleNow) + return; + continue; + } + case AiWanderStorage::Wander_MoveNow: + return; // nothing to do - default: - // should never get here - assert(false); - break; + default: + // should never get here + assert(false); + return; + } } } From 35058a49d17901f25bb36be08ae010053aad922a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jul 2024 21:06:15 +0200 Subject: [PATCH 29/35] Update deps for RTD build --- docs/requirements.txt | 6 +++--- docs/source/conf.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 2fa165c836..b4c67b2548 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ parse_cmake -sphinx==1.8.5 -docutils==0.17.1 +sphinx==7.1.2 +docutils==0.18.1 jinja2==3.1.4 -sphinx_rtd_theme +sphinx_rtd_theme==1.3.0 diff --git a/docs/source/conf.py b/docs/source/conf.py index 1dca7374e5..e57b8859b3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -55,7 +55,7 @@ master_doc = 'index' # General information about the project. project = u'OpenMW' -copyright = u'2023, OpenMW Team' +copyright = u'2024, OpenMW Team' # The version info for the project you're documenting, acts as replacement for From dbbcbaa4c5a56cbfab36d36f3263a7d1ad4bf9a9 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 8 Jul 2024 21:41:31 +0200 Subject: [PATCH 30/35] Clean up a debug log --- files/data/scripts/omw/settings/menu.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 4e6971a516..8ae227e0f1 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -448,7 +448,6 @@ local function resetPlayerGroups() if not menuGroups[groupKey] then if groupElements[pageKey][groupKey] then groupElements[pageKey][groupKey]:destroy() - print(string.format('destroyed group element %s %s', pageKey, groupKey)) groupElements[pageKey][groupKey] = nil end page[groupKey] = nil From d4e6dd9f8c0ac590c07fc066f3d902a6086fa63d Mon Sep 17 00:00:00 2001 From: trav5 Date: Mon, 8 Jul 2024 23:30:05 +0200 Subject: [PATCH 31/35] Add Camera.isZoomEnabled check to move360 zooming Early return in the processZoom3rdPerson function at move360.lua extended with a check for disabled zoom in the Camera interface. This fixes #8064 - prevents from switching between 1st and 3rd person camera by pressing zoom in/out inputs when zooming is disabled. --- CHANGELOG.md | 1 + files/data/scripts/omw/camera/move360.lua | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0421a594b0..f418da977e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -184,6 +184,7 @@ Bug #8005: F3 stats bars are sorted not according to their place in the timeline Bug #8018: Potion effects should never explode and always apply on self Bug #8021: Player's scale doesn't reset when starting a new game + Bug #8064: Lua move360 script doesn't respect the enableZoom/disableZoom Camera interface setting Feature #1415: Infinite fall failsafe Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/files/data/scripts/omw/camera/move360.lua b/files/data/scripts/omw/camera/move360.lua index a80019f3a0..aec171d102 100644 --- a/files/data/scripts/omw/camera/move360.lua +++ b/files/data/scripts/omw/camera/move360.lua @@ -35,7 +35,8 @@ local function processZoom3rdPerson() not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or input.getBooleanActionValue('TogglePOV') or - not I.Camera.isModeControlEnabled() + not I.Camera.isModeControlEnabled() or + not I.Camera.isZoomEnabled() then return end From f258f9ec68c0c9809fe593849a96f2007ec220be Mon Sep 17 00:00:00 2001 From: Sam Kaufman Date: Wed, 10 Jul 2024 07:34:08 +0000 Subject: [PATCH 32/35] Set macOS app. category to role-playing game. --- CHANGELOG.md | 1 + files/mac/openmw-Info.plist.in | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0421a594b0..2791bf1020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -249,6 +249,7 @@ Feature #7971: Make save's Time Played value display hours instead of days Feature #7985: Support dark mode on Windows Feature #8034: (Lua) Containers should have respawning/organic flags + Feature #8067: Support Game Mode on macOS Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/files/mac/openmw-Info.plist.in b/files/mac/openmw-Info.plist.in index 20dc36afa5..c0282c0284 100644 --- a/files/mac/openmw-Info.plist.in +++ b/files/mac/openmw-Info.plist.in @@ -8,8 +8,8 @@ English CFBundleExecutable openmw-launcher - CFBundleIdentifier - org.openmw.openmw + CFBundleIdentifier + org.openmw.openmw CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString @@ -20,14 +20,16 @@ APPL CFBundleSignature ???? - CFBundleShortVersionString - ${OPENMW_VERSION} + CFBundleShortVersionString + ${OPENMW_VERSION} CFBundleVersion ${OPENMW_VERSION} CSResourcesFileMapped LSRequiresCarbon + LSApplicationCategoryType + public.app-category.role-playing-games NSHumanReadableCopyright NSHighResolutionCapable From b3cee84787b035cf5e2145bb406e175ce1975270 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 1 Jul 2024 12:59:10 -0500 Subject: [PATCH 33/35] c h a n g e l o g --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a7e49d37..0558186b3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -184,6 +184,7 @@ Bug #8005: F3 stats bars are sorted not according to their place in the timeline Bug #8018: Potion effects should never explode and always apply on self Bug #8021: Player's scale doesn't reset when starting a new game + Bug #8048: Actors can generate negative collision extents and have no collision Bug #8064: Lua move360 script doesn't respect the enableZoom/disableZoom Camera interface setting Feature #1415: Infinite fall failsafe Feature #2566: Handle NAM9 records for manual cell references From 5bca2919c25eb7193b2d68a8d47f2202f0a22c55 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 4 Jul 2024 21:16:52 -0500 Subject: [PATCH 34/35] CLEANUP: Don't assign invalid extents when loading bounding boxes --- components/nifbullet/bulletnifloader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index a57e8b3c06..bcda7efe70 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -85,7 +85,8 @@ namespace NifBullet { if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box")) { - if (node.mBounds.mType == Nif::BoundingVolume::Type::BOX_BV) + if (node.mBounds.mType == Nif::BoundingVolume::Type::BOX_BV + && std::ranges::all_of(node.mBounds.mBox.mExtents._v, [](float extent) { return extent > 0.f; })) { mShape->mCollisionBox.mExtents = node.mBounds.mBox.mExtents; mShape->mCollisionBox.mCenter = node.mBounds.mBox.mCenter; From 4cc956fdd7011bba7867abcf6282e813ba028284 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 4 Jul 2024 21:31:25 -0500 Subject: [PATCH 35/35] TEST: Add a test to ensure invalid box extents are not assigned --- .../nifloader/testbulletnifloader.cpp | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/components_tests/nifloader/testbulletnifloader.cpp b/apps/components_tests/nifloader/testbulletnifloader.cpp index d44561a68b..f7ee559578 100644 --- a/apps/components_tests/nifloader/testbulletnifloader.cpp +++ b/apps/components_tests/nifloader/testbulletnifloader.cpp @@ -1364,4 +1364,28 @@ namespace EXPECT_EQ(*result, expected); } + + TEST_F(TestBulletNifLoader, dont_assign_invalid_bounding_box_extents) + { + copy(mTransform, mNiTriShape.mTransform); + mNiTriShape.mTransform.mScale = 10; + mNiTriShape.mParents.push_back(&mNiNode); + + mNiTriShape2.mName = "Bounding Box"; + mNiTriShape2.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; + mNiTriShape2.mBounds.mBox.mExtents = osg::Vec3f(-1, -2, -3); + mNiTriShape2.mParents.push_back(&mNiNode); + + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape), Nif::NiAVObjectPtr(&mNiTriShape2) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + + const auto result = mLoader.load(file); + + const bool extentsUnassigned + = std::ranges::all_of(result->mCollisionBox.mExtents._v, [](float extent) { return extent == 0.f; }); + + EXPECT_EQ(extentsUnassigned, true); + } }