From e978c230dc754ddf0733ecaf21c63312520a7159 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 5 Oct 2025 17:52:46 +0200 Subject: [PATCH] Override functions by shallow copying the interface instead of overriding files --- docs/source/luadoc_data_paths.sh | 2 +- .../lua-scripting/interface_combat.rst | 2 +- files/data-mw/CMakeLists.txt | 2 - files/data-mw/builtin.omwscripts | 2 +- files/data-mw/scripts/omw/combat/local.lua | 262 +++++++++++++++++ .../omw/interfaces/combatfunctions.lua | 270 ------------------ .../scripts/omw/interfaces/skillfunctions.lua | 66 ----- .../scripts/omw/playerskillhandlers.lua | 66 ++++- files/data/CMakeLists.txt | 4 +- files/data/builtin.omwscripts | 2 +- files/data/openmw_aux/util.lua | 13 + .../combat.lua => combat/interface.lua} | 21 +- .../omw/interfaces/combatfunctions.lua | 11 - .../scripts/omw/interfaces/skillfunctions.lua | 4 - files/data/scripts/omw/skillhandlers.lua | 22 +- files/lua_api/openmw/interfaces.lua | 2 +- 16 files changed, 367 insertions(+), 384 deletions(-) delete mode 100644 files/data-mw/scripts/omw/interfaces/combatfunctions.lua delete mode 100644 files/data-mw/scripts/omw/interfaces/skillfunctions.lua rename files/data/scripts/omw/{interfaces/combat.lua => combat/interface.lua} (92%) delete mode 100644 files/data/scripts/omw/interfaces/combatfunctions.lua delete mode 100644 files/data/scripts/omw/interfaces/skillfunctions.lua diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 025485e5cf..765ccf274a 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -2,7 +2,7 @@ paths=( openmw_aux/*lua scripts/omw/activationhandlers.lua scripts/omw/ai.lua - scripts/omw/interfaces/combat.lua + scripts/omw/combat/interface.lua scripts/omw/input/playercontrols.lua scripts/omw/mechanics/animationcontroller.lua scripts/omw/input/gamepadcontrols.lua diff --git a/docs/source/reference/lua-scripting/interface_combat.rst b/docs/source/reference/lua-scripting/interface_combat.rst index 5f8271d12c..617cd9dbdf 100644 --- a/docs/source/reference/lua-scripting/interface_combat.rst +++ b/docs/source/reference/lua-scripting/interface_combat.rst @@ -4,5 +4,5 @@ Interface Combat .. include:: version.rst .. raw:: html - :file: generated_html/scripts_omw_interfaces_combat.html + :file: generated_html/scripts_omw_combat_interface.html diff --git a/files/data-mw/CMakeLists.txt b/files/data-mw/CMakeLists.txt index 60c8bb32aa..75ffe703d3 100644 --- a/files/data-mw/CMakeLists.txt +++ b/files/data-mw/CMakeLists.txt @@ -28,8 +28,6 @@ set(BUILTIN_DATA_MW_FILES scripts/omw/combat/global.lua scripts/omw/combat/local.lua scripts/omw/combat/menu.lua - scripts/omw/interfaces/combatfunctions.lua - scripts/omw/interfaces/skillfunctions.lua scripts/omw/music/helpers.lua scripts/omw/music/music.lua scripts/omw/music/settings.lua diff --git a/files/data-mw/builtin.omwscripts b/files/data-mw/builtin.omwscripts index de6c79fe8a..217bac5509 100644 --- a/files/data-mw/builtin.omwscripts +++ b/files/data-mw/builtin.omwscripts @@ -26,7 +26,7 @@ PLAYER: scripts/omw/input/gamepadcontrols.lua NPC,CREATURE: scripts/omw/ai.lua GLOBAL: scripts/omw/mechanics/globalcontroller.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/actorcontroller.lua -NPC,CREATURE,PLAYER: scripts/omw/interfaces/combat.lua +NPC,CREATURE,PLAYER: scripts/omw/combat/interface.lua # User interface PLAYER: scripts/omw/ui.lua diff --git a/files/data-mw/scripts/omw/combat/local.lua b/files/data-mw/scripts/omw/combat/local.lua index 6c90d73b5a..8db8d87301 100644 --- a/files/data-mw/scripts/omw/combat/local.lua +++ b/files/data-mw/scripts/omw/combat/local.lua @@ -1,9 +1,13 @@ local core = require('openmw.core') local I = require('openmw.interfaces') local self = require('openmw.self') +local storage = require('openmw.storage') local types = require('openmw.types') local Actor = types.Actor local Player = types.Player +local Creature = types.Creature +local Armor = types.Armor +local auxUtil = require('openmw_aux.util') local isPlayer = Player.objectIsInstance(self) local godMode = function() return false end @@ -12,12 +16,254 @@ if isPlayer then godMode = function() return require('openmw.debug').isGodMode() end end +local settings = storage.globalSection('SettingsOMWCombat') + +local function getSkill(actor, skillId) + if Creature.objectIsInstance(actor) then + local specialization = core.stats.Skill.record(skillId).specialization + local creatureRecord = Creature.record(actor) + return creatureRecord[specialization..'Skill'] + else + return types.NPC.stats.skills[skillId](actor).modified + end +end + +local armorTypeGmst = { + [Armor.TYPE.Boots] = core.getGMST('iBootsWeight'), + [Armor.TYPE.Cuirass] = core.getGMST('iCuirassWeight'), + [Armor.TYPE.Greaves] = core.getGMST('iGreavesWeight'), + [Armor.TYPE.Helmet] = core.getGMST('iHelmWeight'), + [Armor.TYPE.LBracer] = core.getGMST('iGauntletWeight'), + [Armor.TYPE.LGauntlet] = core.getGMST('iGauntletWeight'), + [Armor.TYPE.LPauldron] = core.getGMST('iPauldronWeight'), + [Armor.TYPE.RBracer] = core.getGMST('iGauntletWeight'), + [Armor.TYPE.RGauntlet] = core.getGMST('iGauntletWeight'), + [Armor.TYPE.RPauldron] = core.getGMST('iPauldronWeight'), + [Armor.TYPE.Shield] = core.getGMST('iShieldWeight'), +} + +local armorSlots = { + Actor.EQUIPMENT_SLOT.Boots, + Actor.EQUIPMENT_SLOT.Cuirass, + Actor.EQUIPMENT_SLOT.Greaves, + Actor.EQUIPMENT_SLOT.Helmet, + Actor.EQUIPMENT_SLOT.LeftGauntlet, + Actor.EQUIPMENT_SLOT.LeftPauldron, + Actor.EQUIPMENT_SLOT.RightGauntlet, + Actor.EQUIPMENT_SLOT.RightPauldron, + Actor.EQUIPMENT_SLOT.CarriedLeft, +} + local function getDamage(attack, what) if attack.damage then return attack.damage[what] or 0 end end +local function setDamage(attack, what, damage) + attack.damage = attack.damage or {} + attack.damage[what] = damage +end + +local function adjustDamageForArmor(damage, actor) + local armor = I.Combat.getArmorRating(actor) + local x = damage / (damage + armor) + return damage * math.max(x, core.getGMST('fCombatArmorMinMult')) +end + +local function adjustDamageForDifficulty(attack, defendant) + local attackerIsPlayer = attack.attacker and Player.objectIsInstance(attack.attacker) + -- The interface guarantees defendant is never nil + local defendantIsPlayer = Player.objectIsInstance(defendant) + -- If both characters are NPCs or both characters are players then + -- difficulty settings do not apply + if attackerIsPlayer == defendantIsPlayer then return end + + local fDifficultyMult = core.getGMST('fDifficultyMult') + local difficultyTerm = core.getGameDifficulty() * 0.01 + local x = 0 + + if defendantIsPlayer then + -- Defending actor is a player + if difficultyTerm > 0 then + x = difficultyTerm * fDifficultyMult + else + x = difficultyTerm / fDifficultyMult + end + elseif attackerIsPlayer then + -- Attacking actor is a player + if difficultyTerm > 0 then + x = -difficultyTerm / fDifficultyMult + else + x = -difficultyTerm * fDifficultyMult + end + end + + setDamage(attack, 'health', getDamage(attack, 'health') * (1 + x)) +end + +local function applyArmor(attack) + local healthDamage = getDamage(attack, 'health') + if healthDamage > 0 then + local healthDamageAdjusted = I.Combat.adjustDamageForArmor(healthDamage) + local diff = math.floor(healthDamageAdjusted - healthDamage) + setDamage(attack, 'health', math.max(healthDamageAdjusted, 1)) + local item = I.Combat.pickRandomArmor() + local skillid = I.Combat.getArmorSkill(item) + if I.SkillProgression then + I.SkillProgression.skillUsed(skillid, {useType = I.SkillProgression.SKILL_USE_TYPES.Armor_HitByOpponent}) + end + if item and Armor.objectIsInstance(item) then + local attackerIsUnarmedCreature = attack.attacker and not attack.weapon and not attack.ammo and Creature.objectIsInstance(attack.attacker) + if settings:get('unarmedCreatureAttacksDamageArmor') or not attackerIsUnarmedCreature then + core.sendGlobalEvent('ModifyItemCondition', { actor = self, item = item, amount = diff }) + end + + if skillid == 'lightarmor' then + core.sound.playSound3d('Light Armor Hit', self) + elseif skillid == 'mediumarmor' then + core.sound.playSound3d('Medium Armor Hit', self) + elseif skillid == 'heavyarmor' then + core.sound.playSound3d('Heavy Armor Hit', self) + else + core.sound.playSound3d('Hand To Hand Hit', self) + end + end + end +end + +local function getArmorRating(actor) + local magicShield = Actor.activeEffects(actor):getEffect(core.magic.EFFECT_TYPE.Shield).magnitude + + if Creature.objectIsInstance(actor) then + return magicShield + end + + local equipment = Actor.getEquipment(actor) + local ratings = {} + local unarmored = getSkill(actor, 'unarmored') + local fUnarmoredBase1 = core.getGMST('fUnarmoredBase1') + local fUnarmoredBase2 = core.getGMST('fUnarmoredBase2') + + for _, v in pairs(armorSlots) do + if equipment[v] and Armor.objectIsInstance(equipment[v]) then + ratings[v] = I.Combat.getEffectiveArmorRating(equipment[v], actor) + else + -- Unarmored + ratings[v] = (fUnarmoredBase1 * unarmored) * (fUnarmoredBase2 * unarmored) + end + end + + return ratings[Actor.EQUIPMENT_SLOT.Cuirass] * 0.3 + + ratings[Actor.EQUIPMENT_SLOT.CarriedLeft] * 0.1 + + ratings[Actor.EQUIPMENT_SLOT.Helmet] * 0.1 + + ratings[Actor.EQUIPMENT_SLOT.Greaves] * 0.1 + + ratings[Actor.EQUIPMENT_SLOT.Boots] * 0.1 + + ratings[Actor.EQUIPMENT_SLOT.LeftPauldron] * 0.1 + + ratings[Actor.EQUIPMENT_SLOT.RightPauldron] * 0.1 + + ratings[Actor.EQUIPMENT_SLOT.LeftGauntlet] * 0.05 + + ratings[Actor.EQUIPMENT_SLOT.RightGauntlet] * 0.05 + + magicShield +end + +local function getArmorSkill(item) + if not item or not Armor.objectIsInstance(item) then + return 'unarmored' + end + local record = Armor.record(item) + local weightGmst = armorTypeGmst[record.type] + local epsilon = 0.0005 + if record.weight <= weightGmst * core.getGMST('fLightMaxMod') + epsilon then + return 'lightarmor' + elseif record.weight <= weightGmst * core.getGMST('fMedMaxMod') + epsilon then + return 'mediumarmor' + else + return 'heavyarmor' + end +end + +local function getSkillAdjustedArmorRating(item, actor) + local record = Armor.record(item) + local skillid = I.Combat.getArmorSkill(item) + local skill = getSkill(actor, skillid) + if record.weight == 0 then + return record.baseArmor + end + return record.baseArmor * skill / core.getGMST('iBaseArmorSkill') +end + +local function getEffectiveArmorRating(item, actor) + local record = Armor.record(item) + local rating = getSkillAdjustedArmorRating(item, actor) + if record.health and record.health ~= 0 then + rating = rating * (types.Item.itemData(item).condition / record.health) + end + return rating +end + +local function spawnBloodEffect(position) + if isPlayer and not settings:get('spawnBloodEffectsOnPlayer') then + return + end + + local bloodEffectModel = string.format('Blood_Model_%d', math.random(0, 2)) -- randIntUniformClosed(0, 2) + + -- TODO: implement a Misc::correctMeshPath equivalent instead? + -- All it ever does it append 'meshes\\' though + bloodEffectModel = 'meshes/'..core.getGMST(bloodEffectModel) + + local record = self.object.type.record(self.object) + local bloodTexture = string.format('Blood_Texture_%d', record.bloodType) + bloodTexture = core.getGMST(bloodTexture) + if not bloodTexture or bloodTexture == '' then + bloodTexture = core.getGMST('Blood_Texture_0') + end + core.sendGlobalEvent('SpawnVfx', { + model = bloodEffectModel, + position = position, + options = { + mwMagicVfx = false, + particleTextureOverride = bloodTexture, + useAmbientLight = false, + }, + }) +end + +local function pickRandomArmor(actor) + local slot = nil + local roll = math.random(0, 99) -- randIntUniform(0, 100) + if roll >= 90 then + slot = Actor.EQUIPMENT_SLOT.CarriedLeft + local item = Actor.getEquipment(actor, slot) + local haveShield = item and Armor.objectIsInstance(item) + if settings:get('redistributeShieldHitsWhenNotWearingShield') and not haveShield then + if roll >= 95 then + slot = Actor.EQUIPMENT_SLOT.Cuirass + else + slot = Actor.EQUIPMENT_SLOT.LeftPauldron + end + end + elseif roll >= 85 then + slot = Actor.EQUIPMENT_SLOT.RightGauntlet + elseif roll >= 80 then + slot = Actor.EQUIPMENT_SLOT.LeftGauntlet + elseif roll >= 70 then + slot = Actor.EQUIPMENT_SLOT.RightPauldron + elseif roll >= 60 then + slot = Actor.EQUIPMENT_SLOT.LeftPauldron + elseif roll >= 50 then + slot = Actor.EQUIPMENT_SLOT.Boots + elseif roll >= 40 then + slot = Actor.EQUIPMENT_SLOT.Greaves + elseif roll >= 30 then + slot = Actor.EQUIPMENT_SLOT.Helmet + else + slot = Actor.EQUIPMENT_SLOT.Cuirass + end + + return Actor.getEquipment(actor, slot) +end + local function onHit(data) if data.successful and not godMode() then I.Combat.applyArmor(data) @@ -35,3 +281,19 @@ local function onHit(data) end I.Combat.addOnHitHandler(onHit) + +local interface = auxUtil.shallowCopy(I.Combat) +interface.adjustDamageForArmor = function(damage, actor) return adjustDamageForArmor(damage, actor or self) end +interface.adjustDamageForDifficulty = function(attack, defendant) return adjustDamageForDifficulty(attack, defendant or self) end +interface.applyArmor = applyArmor +interface.getArmorRating = function(actor) return getArmorRating(actor or self) end +interface.getArmorSkill = getArmorSkill +interface.getSkillAdjustedArmorRating = function(item, actor) return getSkillAdjustedArmorRating(item, actor or self) end +interface.getEffectiveArmorRating = function(item, actor) return getEffectiveArmorRating(item, actor or self) end +interface.spawnBloodEffect = spawnBloodEffect +interface.pickRandomArmor = function(actor) return pickRandomArmor(actor or self) end + +return { + interfaceName = 'Combat', + interface = interface +} diff --git a/files/data-mw/scripts/omw/interfaces/combatfunctions.lua b/files/data-mw/scripts/omw/interfaces/combatfunctions.lua deleted file mode 100644 index 398c510bf4..0000000000 --- a/files/data-mw/scripts/omw/interfaces/combatfunctions.lua +++ /dev/null @@ -1,270 +0,0 @@ -local core = require('openmw.core') -local I = require('openmw.interfaces') -local self = require('openmw.self') -local storage = require('openmw.storage') -local types = require('openmw.types') -local Actor = types.Actor -local Player = types.Player -local Creature = types.Creature -local Armor = types.Armor -local isPlayer = Player.objectIsInstance(self) - -local settings = storage.globalSection('SettingsOMWCombat') - -local function getSkill(actor, skillId) - if Creature.objectIsInstance(actor) then - local specialization = core.stats.Skill.record(skillId).specialization - local creatureRecord = Creature.record(actor) - return creatureRecord[specialization..'Skill'] - else - return types.NPC.stats.skills[skillId](actor).modified - end -end - -local armorTypeGmst = { - [Armor.TYPE.Boots] = core.getGMST('iBootsWeight'), - [Armor.TYPE.Cuirass] = core.getGMST('iCuirassWeight'), - [Armor.TYPE.Greaves] = core.getGMST('iGreavesWeight'), - [Armor.TYPE.Helmet] = core.getGMST('iHelmWeight'), - [Armor.TYPE.LBracer] = core.getGMST('iGauntletWeight'), - [Armor.TYPE.LGauntlet] = core.getGMST('iGauntletWeight'), - [Armor.TYPE.LPauldron] = core.getGMST('iPauldronWeight'), - [Armor.TYPE.RBracer] = core.getGMST('iGauntletWeight'), - [Armor.TYPE.RGauntlet] = core.getGMST('iGauntletWeight'), - [Armor.TYPE.RPauldron] = core.getGMST('iPauldronWeight'), - [Armor.TYPE.Shield] = core.getGMST('iShieldWeight'), -} - -local armorSlots = { - Actor.EQUIPMENT_SLOT.Boots, - Actor.EQUIPMENT_SLOT.Cuirass, - Actor.EQUIPMENT_SLOT.Greaves, - Actor.EQUIPMENT_SLOT.Helmet, - Actor.EQUIPMENT_SLOT.LeftGauntlet, - Actor.EQUIPMENT_SLOT.LeftPauldron, - Actor.EQUIPMENT_SLOT.RightGauntlet, - Actor.EQUIPMENT_SLOT.RightPauldron, - Actor.EQUIPMENT_SLOT.CarriedLeft, -} - -local function getDamage(attack, what) - if attack.damage then - return attack.damage[what] or 0 - end -end - -local function setDamage(attack, what, damage) - attack.damage = attack.damage or {} - attack.damage[what] = damage -end - -local function adjustDamageForArmor(damage, actor) - local armor = I.Combat.getArmorRating(actor) - local x = damage / (damage + armor) - return damage * math.max(x, core.getGMST('fCombatArmorMinMult')) -end - -local function adjustDamageForDifficulty(attack, defendant) - local attackerIsPlayer = attack.attacker and Player.objectIsInstance(attack.attacker) - -- The interface guarantees defendant is never nil - local defendantIsPlayer = Player.objectIsInstance(defendant) - -- If both characters are NPCs or both characters are players then - -- difficulty settings do not apply - if attackerIsPlayer == defendantIsPlayer then return end - - local fDifficultyMult = core.getGMST('fDifficultyMult') - local difficultyTerm = core.getGameDifficulty() * 0.01 - local x = 0 - - if defendantIsPlayer then - -- Defending actor is a player - if difficultyTerm > 0 then - x = difficultyTerm * fDifficultyMult - else - x = difficultyTerm / fDifficultyMult - end - elseif attackerIsPlayer then - -- Attacking actor is a player - if difficultyTerm > 0 then - x = -difficultyTerm / fDifficultyMult - else - x = -difficultyTerm * fDifficultyMult - end - end - - setDamage(attack, 'health', getDamage(attack, 'health') * (1 + x)) -end - -local function applyArmor(attack) - local healthDamage = getDamage(attack, 'health') - if healthDamage > 0 then - local healthDamageAdjusted = I.Combat.adjustDamageForArmor(healthDamage) - local diff = math.floor(healthDamageAdjusted - healthDamage) - setDamage(attack, 'health', math.max(healthDamageAdjusted, 1)) - local item = I.Combat.pickRandomArmor() - local skillid = I.Combat.getArmorSkill(item) - if I.SkillProgression then - I.SkillProgression.skillUsed(skillid, {useType = I.SkillProgression.SKILL_USE_TYPES.Armor_HitByOpponent}) - end - if item and Armor.objectIsInstance(item) then - local attackerIsUnarmedCreature = attack.attacker and not attack.weapon and not attack.ammo and Creature.objectIsInstance(attack.attacker) - if settings:get('unarmedCreatureAttacksDamageArmor') or not attackerIsUnarmedCreature then - core.sendGlobalEvent('ModifyItemCondition', { actor = self, item = item, amount = diff }) - end - - if skillid == 'lightarmor' then - core.sound.playSound3d('Light Armor Hit', self) - elseif skillid == 'mediumarmor' then - core.sound.playSound3d('Medium Armor Hit', self) - elseif skillid == 'heavyarmor' then - core.sound.playSound3d('Heavy Armor Hit', self) - else - core.sound.playSound3d('Hand To Hand Hit', self) - end - end - end -end - -local function getArmorRating(actor) - local magicShield = Actor.activeEffects(actor):getEffect(core.magic.EFFECT_TYPE.Shield).magnitude - - if Creature.objectIsInstance(actor) then - return magicShield - end - - local equipment = Actor.getEquipment(actor) - local ratings = {} - local unarmored = getSkill(actor, 'unarmored') - local fUnarmoredBase1 = core.getGMST('fUnarmoredBase1') - local fUnarmoredBase2 = core.getGMST('fUnarmoredBase2') - - for _, v in pairs(armorSlots) do - if equipment[v] and Armor.objectIsInstance(equipment[v]) then - ratings[v] = I.Combat.getEffectiveArmorRating(equipment[v], actor) - else - -- Unarmored - ratings[v] = (fUnarmoredBase1 * unarmored) * (fUnarmoredBase2 * unarmored) - end - end - - return ratings[Actor.EQUIPMENT_SLOT.Cuirass] * 0.3 - + ratings[Actor.EQUIPMENT_SLOT.CarriedLeft] * 0.1 - + ratings[Actor.EQUIPMENT_SLOT.Helmet] * 0.1 - + ratings[Actor.EQUIPMENT_SLOT.Greaves] * 0.1 - + ratings[Actor.EQUIPMENT_SLOT.Boots] * 0.1 - + ratings[Actor.EQUIPMENT_SLOT.LeftPauldron] * 0.1 - + ratings[Actor.EQUIPMENT_SLOT.RightPauldron] * 0.1 - + ratings[Actor.EQUIPMENT_SLOT.LeftGauntlet] * 0.05 - + ratings[Actor.EQUIPMENT_SLOT.RightGauntlet] * 0.05 - + magicShield -end - -local function getArmorSkill(item) - if not item or not Armor.objectIsInstance(item) then - return 'unarmored' - end - local record = Armor.record(item) - local weightGmst = armorTypeGmst[record.type] - local epsilon = 0.0005 - if record.weight <= weightGmst * core.getGMST('fLightMaxMod') + epsilon then - return 'lightarmor' - elseif record.weight <= weightGmst * core.getGMST('fMedMaxMod') + epsilon then - return 'mediumarmor' - else - return 'heavyarmor' - end -end - -local function getSkillAdjustedArmorRating(item, actor) - local record = Armor.record(item) - local skillid = I.Combat.getArmorSkill(item) - local skill = getSkill(actor, skillid) - if record.weight == 0 then - return record.baseArmor - end - return record.baseArmor * skill / core.getGMST('iBaseArmorSkill') -end - -local function getEffectiveArmorRating(item, actor) - local record = Armor.record(item) - local rating = getSkillAdjustedArmorRating(item, actor) - if record.health and record.health ~= 0 then - rating = rating * (types.Item.itemData(item).condition / record.health) - end - return rating -end - -local function spawnBloodEffect(position) - if isPlayer and not settings:get('spawnBloodEffectsOnPlayer') then - return - end - - local bloodEffectModel = string.format('Blood_Model_%d', math.random(0, 2)) -- randIntUniformClosed(0, 2) - - -- TODO: implement a Misc::correctMeshPath equivalent instead? - -- All it ever does it append 'meshes\\' though - bloodEffectModel = 'meshes/'..core.getGMST(bloodEffectModel) - - local record = self.object.type.record(self.object) - local bloodTexture = string.format('Blood_Texture_%d', record.bloodType) - bloodTexture = core.getGMST(bloodTexture) - if not bloodTexture or bloodTexture == '' then - bloodTexture = core.getGMST('Blood_Texture_0') - end - core.sendGlobalEvent('SpawnVfx', { - model = bloodEffectModel, - position = position, - options = { - mwMagicVfx = false, - particleTextureOverride = bloodTexture, - useAmbientLight = false, - }, - }) -end - -local function pickRandomArmor(actor) - local slot = nil - local roll = math.random(0, 99) -- randIntUniform(0, 100) - if roll >= 90 then - slot = Actor.EQUIPMENT_SLOT.CarriedLeft - local item = Actor.getEquipment(actor, slot) - local haveShield = item and Armor.objectIsInstance(item) - if settings:get('redistributeShieldHitsWhenNotWearingShield') and not haveShield then - if roll >= 95 then - slot = Actor.EQUIPMENT_SLOT.Cuirass - else - slot = Actor.EQUIPMENT_SLOT.LeftPauldron - end - end - elseif roll >= 85 then - slot = Actor.EQUIPMENT_SLOT.RightGauntlet - elseif roll >= 80 then - slot = Actor.EQUIPMENT_SLOT.LeftGauntlet - elseif roll >= 70 then - slot = Actor.EQUIPMENT_SLOT.RightPauldron - elseif roll >= 60 then - slot = Actor.EQUIPMENT_SLOT.LeftPauldron - elseif roll >= 50 then - slot = Actor.EQUIPMENT_SLOT.Boots - elseif roll >= 40 then - slot = Actor.EQUIPMENT_SLOT.Greaves - elseif roll >= 30 then - slot = Actor.EQUIPMENT_SLOT.Helmet - else - slot = Actor.EQUIPMENT_SLOT.Cuirass - end - - return Actor.getEquipment(actor, slot) -end - -return { - adjustDamageForArmor = function(damage, actor) return adjustDamageForArmor(damage, actor or self) end, - adjustDamageForDifficulty = function(attack, defendant) return adjustDamageForDifficulty(attack, defendant or self) end, - applyArmor = applyArmor, - getArmorRating = function(actor) return getArmorRating(actor or self) end, - getArmorSkill = getArmorSkill, - getSkillAdjustedArmorRating = function(item, actor) return getSkillAdjustedArmorRating(item, actor or self) end, - getEffectiveArmorRating = function(item, actor) return getEffectiveArmorRating(item, actor or self) end, - spawnBloodEffect = spawnBloodEffect, - pickRandomArmor = function(actor) return pickRandomArmor(actor or self) end -} diff --git a/files/data-mw/scripts/omw/interfaces/skillfunctions.lua b/files/data-mw/scripts/omw/interfaces/skillfunctions.lua deleted file mode 100644 index 0cb671d1bf..0000000000 --- a/files/data-mw/scripts/omw/interfaces/skillfunctions.lua +++ /dev/null @@ -1,66 +0,0 @@ -local self = require('openmw.self') -local core = require('openmw.core') -local NPC = require('openmw.types').NPC -local Skill = core.stats.Skill - -local function tableHasValue(table, value) - for _, v in pairs(table) do - if v == value then return true end - end - return false -end - -local function getSkillProgressRequirement(skillid) - local npcRecord = NPC.record(self) - local class = NPC.classes.record(npcRecord.class) - local skillStat = NPC.stats.skills[skillid](self) - local skillRecord = Skill.record(skillid) - - local factor = core.getGMST('fMiscSkillBonus') - if tableHasValue(class.majorSkills, skillid) then - factor = core.getGMST('fMajorSkillBonus') - elseif tableHasValue(class.minorSkills, skillid) then - factor = core.getGMST('fMinorSkillBonus') - end - - if skillRecord.specialization == class.specialization then - factor = factor * core.getGMST('fSpecialSkillBonus') - end - - return (skillStat.base + 1) * factor -end - -local function getSkillLevelUpOptions(skillid, source) - local skillRecord = Skill.record(skillid) - local npcRecord = NPC.record(self) - local class = NPC.classes.record(npcRecord.class) - - local levelUpProgress = 0 - local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte') - - if tableHasValue(class.minorSkills, skillid) then - levelUpProgress = core.getGMST('iLevelUpMinorMult') - levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute') - elseif tableHasValue(class.majorSkills, skillid) then - levelUpProgress = core.getGMST('iLevelUpMajorMult') - levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') - end - - local options = {} - if source == 'jail' and not (skillid == 'security' or skillid == 'sneak') then - options.skillIncreaseValue = -1 - else - options.skillIncreaseValue = 1 - options.levelUpProgress = levelUpProgress - options.levelUpAttribute = skillRecord.attribute - options.levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue - options.levelUpSpecialization = skillRecord.specialization - options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization') - end - return options -end - -return { - getSkillProgressRequirement = getSkillProgressRequirement, - getSkillLevelUpOptions = getSkillLevelUpOptions -} diff --git a/files/data-mw/scripts/omw/playerskillhandlers.lua b/files/data-mw/scripts/omw/playerskillhandlers.lua index b6485bba8e..1146e26551 100644 --- a/files/data-mw/scripts/omw/playerskillhandlers.lua +++ b/files/data-mw/scripts/omw/playerskillhandlers.lua @@ -7,6 +7,64 @@ local types = require('openmw.types') local NPC = types.NPC local Actor = types.Actor local ui = require('openmw.ui') +local auxUtil = require('openmw_aux.util') + +local function tableHasValue(table, value) + for _, v in pairs(table) do + if v == value then return true end + end + return false +end + +local function getSkillProgressRequirement(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + local skillStat = NPC.stats.skills[skillid](self) + local skillRecord = Skill.record(skillid) + + local factor = core.getGMST('fMiscSkillBonus') + if tableHasValue(class.majorSkills, skillid) then + factor = core.getGMST('fMajorSkillBonus') + elseif tableHasValue(class.minorSkills, skillid) then + factor = core.getGMST('fMinorSkillBonus') + end + + if skillRecord.specialization == class.specialization then + factor = factor * core.getGMST('fSpecialSkillBonus') + end + + return (skillStat.base + 1) * factor +end + +local function getSkillLevelUpOptions(skillid, source) + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + local levelUpProgress = 0 + local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte') + + if tableHasValue(class.minorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMinorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute') + elseif tableHasValue(class.majorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMajorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') + end + + local options = {} + if source == 'jail' and not (skillid == 'security' or skillid == 'sneak') then + options.skillIncreaseValue = -1 + else + options.skillIncreaseValue = 1 + options.levelUpProgress = levelUpProgress + options.levelUpAttribute = skillRecord.attribute + options.levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue + options.levelUpSpecialization = skillRecord.specialization + options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization') + end + return options +end local function skillLevelUpHandler(skillid, source, params) local skillStat = NPC.stats.skills[skillid](self) @@ -119,8 +177,14 @@ end I.SkillProgression.addSkillUsedHandler(skillUsedHandler) I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) +local interface = auxUtil.shallowCopy(I.SkillProgression) +interface.getSkillProgressRequirement = getSkillProgressRequirement +interface.getSkillLevelUpOptions = getSkillLevelUpOptions + return { engineHandlers = { _onJailTimeServed = jailTimeServed, - } + }, + interfaceName = 'SkillProgression', + interface = interface } diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index cb17f23426..4b1b9e01ca 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -135,9 +135,7 @@ set(BUILTIN_DATA_FILES scripts/omw/console/local.lua scripts/omw/console/player.lua scripts/omw/console/menu.lua - scripts/omw/interfaces/combat.lua - scripts/omw/interfaces/combatfunctions.lua - scripts/omw/interfaces/skillfunctions.lua + scripts/omw/combat/interface.lua scripts/omw/mechanics/actorcontroller.lua scripts/omw/mechanics/animationcontroller.lua scripts/omw/mechanics/globalcontroller.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 682536ec39..f61182c033 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -26,7 +26,7 @@ PLAYER: scripts/omw/input/gamepadcontrols.lua NPC,CREATURE: scripts/omw/ai.lua GLOBAL: scripts/omw/mechanics/globalcontroller.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/actorcontroller.lua -NPC,CREATURE,PLAYER: scripts/omw/interfaces/combat.lua +NPC,CREATURE,PLAYER: scripts/omw/combat/interface.lua # User interface PLAYER: scripts/omw/ui.lua diff --git a/files/data/openmw_aux/util.lua b/files/data/openmw_aux/util.lua index 00dd974427..9535c979c8 100644 --- a/files/data/openmw_aux/util.lua +++ b/files/data/openmw_aux/util.lua @@ -143,5 +143,18 @@ function aux_util.callMultipleEventHandlers(handlers, ...) return false end +--- +-- Copies all key-value pairs from the input table to a new table. +-- @function [parent=#util] shallowCopy +-- @param #table table The table to copy +-- @return #table A shallow copy of the input table +function aux_util.shallowCopy(table) + local copy = {} + for key, value in pairs(table) do + copy[key] = value + end + return copy +end + return aux_util diff --git a/files/data/scripts/omw/interfaces/combat.lua b/files/data/scripts/omw/combat/interface.lua similarity index 92% rename from files/data/scripts/omw/interfaces/combat.lua rename to files/data/scripts/omw/combat/interface.lua index f3330dcd25..e96c60ee2b 100644 --- a/files/data/scripts/omw/interfaces/combat.lua +++ b/files/data/scripts/omw/combat/interface.lua @@ -1,6 +1,5 @@ local I = require('openmw.interfaces') local auxUtil = require('openmw_aux.util') -local functions = require('scripts.omw.interfaces.combatfunctions') local onHitHandlers = {} @@ -26,7 +25,7 @@ local onHitHandlers = {} return { --- Basic combat interface -- @module Combat - -- @usage require('openmw.interfaces').Combat + -- @usage local I = require('openmw.interfaces') -- --I.Combat.addOnHitHandler(function(attack) -- -- Adds fatigue loss when hit by draining fatigue when taking health damage @@ -62,7 +61,7 @@ return { -- @param #number Damage The numeric damage to adjust -- @param openmw.core#GameObject actor (Optional) The actor to calculate the armor rating for. Defaults to self. -- @return #number Damage adjusted for armor - adjustDamageForArmor = functions.adjustDamageForArmor, + adjustDamageForArmor = function(damage, actor) return damage end, --- Calculates a difficulty multiplier based on current difficulty settings -- and adjusts damage accordingly. Has no effect if both this actor and the @@ -70,14 +69,14 @@ return { -- @function [parent=#Combat] adjustDamageForDifficulty -- @param #Attack attack The attack to adjust -- @param openmw.core#GameObject defendant (Optional) The defendant to make the difficulty adjustment for. Defaults to self. - adjustDamageForDifficulty = functions.adjustDamageForDifficulty, + adjustDamageForDifficulty = function(attack, defendant) end, --- Applies this character's armor to the attack. Adjusts damage, reduces item -- condition accordingly, progresses armor skill, and plays the armor appropriate -- hit sound. -- @function [parent=#Combat] applyArmor -- @param #Attack attack - applyArmor = functions.applyArmor, + applyArmor = function(attack) end, --- Computes this character's armor rating. -- Note that this interface function is read by the engine to update the UI. @@ -85,7 +84,7 @@ return { -- @function [parent=#Combat] getArmorRating -- @param openmw.core#GameObject actor (Optional) The actor to calculate the armor rating for. Defaults to self. -- @return #number - getArmorRating = functions.getArmorRating, + getArmorRating = function(actor) return 0 end, --- Computes this character's armor rating. -- You can override this to return any skill you wish (including non-armor skills, if you so wish). @@ -94,7 +93,7 @@ return { -- @function [parent=#Combat] getArmorSkill -- @param openmw.core#GameObject item The item -- @return #string The armor skill identifier, or unarmored if the item was nil or not an instace of @{openmw.types#Armor} - getArmorSkill = functions.getArmorSkill, + getArmorSkill = function(item) return nil end, --- Computes the armor rating of a single piece of @{openmw.types#Armor}, adjusted for skill -- Note that this interface function is read by the engine to update the UI. @@ -103,19 +102,19 @@ return { -- @param openmw.core#GameObject item The item -- @param openmw.core#GameObject actor (Optional) The actor, defaults to self -- @return #number - getSkillAdjustedArmorRating = functions.getSkillAdjustedArmorRating, + getSkillAdjustedArmorRating = function(item, actor) return 0 end, --- Computes the effective armor rating of a single piece of @{openmw.types#Armor}, adjusted for skill and item condition -- @function [parent=#Combat] getEffectiveArmorRating -- @param openmw.core#GameObject item The item -- @param openmw.core#GameObject actor (Optional) The actor, defaults to self -- @return #number - getEffectiveArmorRating = functions.getEffectiveArmorRating, + getEffectiveArmorRating = function(item, actor) return 0 end, --- Spawns a random blood effect at the given position -- @function [parent=#Combat] spawnBloodEffect -- @param openmw.util#Vector3 position - spawnBloodEffect = functions.spawnBloodEffect, + spawnBloodEffect = function(position) end, --- Hit this actor. Normally called as Hit event from the attacking actor, with the same parameters. -- @function [parent=#Combat] onHit @@ -127,7 +126,7 @@ return { -- @function [parent=#Combat] pickRandomArmor -- @param openmw.core#GameObject actor (Optional) The actor to pick armor from, defaults to self -- @return openmw.core#GameObject The armor equipped in the chosen slot. nil if nothing was equipped in that slot. - pickRandomArmor = functions.pickRandomArmor, + pickRandomArmor = function(actor) return nil end, --- @{#AttackSourceType} -- @field [parent=#Combat] #AttackSourceType ATTACK_SOURCE_TYPES Available attack source types diff --git a/files/data/scripts/omw/interfaces/combatfunctions.lua b/files/data/scripts/omw/interfaces/combatfunctions.lua deleted file mode 100644 index eb2c6d229e..0000000000 --- a/files/data/scripts/omw/interfaces/combatfunctions.lua +++ /dev/null @@ -1,11 +0,0 @@ -return { - adjustDamageForArmor = function(damage, actor) return damage end, - adjustDamageForDifficulty = function(attack, defendant) end, - applyArmor = function(attack) end, - getArmorRating = function(actor) return 0 end, - getArmorSkill = function(item) return nil end, - getSkillAdjustedArmorRating = function(item, actor) return 0 end, - getEffectiveArmorRating = function(item, actor) return 0 end, - spawnBloodEffect = function(position) end, - pickRandomArmor = function(actor) return nil end -} diff --git a/files/data/scripts/omw/interfaces/skillfunctions.lua b/files/data/scripts/omw/interfaces/skillfunctions.lua deleted file mode 100644 index 93ea1836b6..0000000000 --- a/files/data/scripts/omw/interfaces/skillfunctions.lua +++ /dev/null @@ -1,4 +0,0 @@ -return { - getSkillProgressRequirement = function(skillid) return 1 end, - getSkillLevelUpOptions = function(skillid, source) return {} end -} diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 4ce211b31a..2e19e8cfeb 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -4,7 +4,6 @@ local core = require('openmw.core') local auxUtil = require('openmw_aux.util') local NPC = require('openmw.types').NPC local Skill = core.stats.Skill -local functions = require('scripts.omw.interfaces.skillfunctions') --- -- Table of skill use types defined by Morrowind. @@ -47,12 +46,6 @@ local functions = require('scripts.omw.interfaces.skillfunctions') local skillUsedHandlers = {} local skillLevelUpHandlers = {} -local function shallowCopy(t1) - local t2 = {} - for key, value in pairs(t1) do t2[key] = value end - return t2 -end - local function skillUsed(skillid, options) if #skillUsedHandlers == 0 then -- If there are no handlers, then there won't be any effect, so skip calculations @@ -60,7 +53,7 @@ local function skillUsed(skillid, options) end -- Make a copy so we don't change the caller's table - options = shallowCopy(options) + options = auxUtil.shallowCopy(options) -- Compute use value if it was not supplied directly if not options.skillGain then @@ -85,7 +78,7 @@ local function skillLevelUp(skillid, source) -- If there are no handlers, then there won't be any effect, so skip calculations return end - local options = functions.getSkillLevelUpOptions(skillid, source) + local options = I.SkillProgression.getSkillLevelUpOptions(skillid, source) auxUtil.callEventHandlers(skillLevelUpHandlers, skillid, source, options) end @@ -126,7 +119,7 @@ return { interface = { --- Interface version -- @field [parent=#SkillProgression] #number version - version = 1, + version = 2, --- Add new skill level up handler for this actor. -- For load order consistency, handlers should be added in the body if your script. @@ -213,6 +206,13 @@ return { -- @param #string skillid The id of the skill to level up. -- @param #SkillLevelUpSource source The source of the skill increase. Note that passing a value of @{#SkillLevelUpSource.Jail} will cause a skill decrease for all skills except sneak and security. skillLevelUp = skillLevelUp, + + --- Construct a table of skill level up options + -- @function [parent=#SkillProgression] getSkillLevelUpOptions + -- @param #string skillid The id of the skill to level up + -- @param #SkillLevelUpSource source The source of the skill increase + -- @return #table The options to pass to the skill level up handlers + getSkillLevelUpOptions = function(skillid, source) return {} end, --- @{#SkillLevelUpSource} -- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES @@ -226,7 +226,7 @@ return { --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. -- @function [parent=#SkillProgression] getSkillProgressRequirement -- @param #string skillid The id of the skill to compute skill progress requirement for - getSkillProgressRequirement = functions.getSkillProgressRequirement + getSkillProgressRequirement = function(skillid) return 1 end }, engineHandlers = { -- Use the interface in these handlers so any overrides will receive the calls. diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index cd7139cbab..140b484382 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -16,7 +16,7 @@ -- @field [parent=#interfaces] scripts.omw.camera.camera#scripts.omw.camera.camera Camera --- --- @field [parent=#interfaces] scripts.omw.interfaces.combat#scripts.omw.interfaces.combat Combat +-- @field [parent=#interfaces] scripts.omw.combat.interface#scripts.omw.combat.interface Combat --- -- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI