1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-11-29 19:34:32 +00:00

Override functions by shallow copying the interface instead of overriding files

This commit is contained in:
Evil Eye 2025-10-05 17:52:46 +02:00
parent 7928930435
commit e978c230dc
16 changed files with 367 additions and 384 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -1,4 +0,0 @@
return {
getSkillProgressRequirement = function(skillid) return 1 end,
getSkillLevelUpOptions = function(skillid, source) return {} end
}

View file

@ -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.

View file

@ -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