diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 6ad2ec90cc..025485e5cf 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/combat/local.lua + scripts/omw/interfaces/combat.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 e3175ea27c..5f8271d12c 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_combat_local.html + :file: generated_html/scripts_omw_interfaces_combat.html diff --git a/files/data-mw/CMakeLists.txt b/files/data-mw/CMakeLists.txt index 48e818cfb1..60c8bb32aa 100644 --- a/files/data-mw/CMakeLists.txt +++ b/files/data-mw/CMakeLists.txt @@ -20,6 +20,20 @@ set(BUILTIN_DATA_MW_FILES # Game-specific settings for calendar.lua openmw_aux/calendarconfig.lua + + builtin.omwscripts + + scripts/omw/cellhandlers.lua + scripts/omw/combat/common.lua + 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 + scripts/omw/playerskillhandlers.lua ) foreach (f ${BUILTIN_DATA_MW_FILES}) diff --git a/files/data-mw/builtin.omwscripts b/files/data-mw/builtin.omwscripts new file mode 100644 index 0000000000..de6c79fe8a --- /dev/null +++ b/files/data-mw/builtin.omwscripts @@ -0,0 +1,50 @@ +# NB: This file is a superset of the one in data! + +# UI framework +MENU,PLAYER: scripts/omw/mwui/init.lua + +# Settings framework +MENU: scripts/omw/settings/menu.lua +PLAYER: scripts/omw/settings/player.lua +GLOBAL: scripts/omw/settings/global.lua + +# Mechanics +GLOBAL: scripts/omw/activationhandlers.lua +GLOBAL: scripts/omw/usehandlers.lua +GLOBAL: scripts/omw/worldeventhandlers.lua +GLOBAL: scripts/omw/crimes.lua +CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua +PLAYER: scripts/omw/skillhandlers.lua +PLAYER: scripts/omw/mechanics/playercontroller.lua +MENU: scripts/omw/camera/settings.lua +MENU: scripts/omw/input/settings.lua +PLAYER: scripts/omw/input/playercontrols.lua +PLAYER: scripts/omw/camera/camera.lua +PLAYER: scripts/omw/input/actionbindings.lua +PLAYER: scripts/omw/input/smoothmovement.lua +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 + +# User interface +PLAYER: scripts/omw/ui.lua + +# Lua console +MENU: scripts/omw/console/menu.lua +PLAYER: scripts/omw/console/player.lua +GLOBAL: scripts/omw/console/global.lua +CUSTOM: scripts/omw/console/local.lua + +# Music system +NPC,CREATURE: scripts/omw/music/actor.lua + +# Game specific +GLOBAL: scripts/omw/cellhandlers.lua +GLOBAL: scripts/omw/combat/global.lua +MENU: scripts/omw/combat/menu.lua +NPC,CREATURE,PLAYER: scripts/omw/combat/local.lua +PLAYER: scripts/omw/music/music.lua +MENU: scripts/omw/music/settings.lua +PLAYER: scripts/omw/playerskillhandlers.lua diff --git a/files/data/scripts/omw/cellhandlers.lua b/files/data-mw/scripts/omw/cellhandlers.lua similarity index 100% rename from files/data/scripts/omw/cellhandlers.lua rename to files/data-mw/scripts/omw/cellhandlers.lua diff --git a/files/data/scripts/omw/combat/common.lua b/files/data-mw/scripts/omw/combat/common.lua similarity index 100% rename from files/data/scripts/omw/combat/common.lua rename to files/data-mw/scripts/omw/combat/common.lua diff --git a/files/data/scripts/omw/combat/global.lua b/files/data-mw/scripts/omw/combat/global.lua similarity index 100% rename from files/data/scripts/omw/combat/global.lua rename to files/data-mw/scripts/omw/combat/global.lua diff --git a/files/data-mw/scripts/omw/combat/local.lua b/files/data-mw/scripts/omw/combat/local.lua new file mode 100644 index 0000000000..6c90d73b5a --- /dev/null +++ b/files/data-mw/scripts/omw/combat/local.lua @@ -0,0 +1,37 @@ +local core = require('openmw.core') +local I = require('openmw.interfaces') +local self = require('openmw.self') +local types = require('openmw.types') +local Actor = types.Actor +local Player = types.Player +local isPlayer = Player.objectIsInstance(self) + +local godMode = function() return false end +if isPlayer then + -- openmw.debug is only allowed on player scripts + godMode = function() return require('openmw.debug').isGodMode() end +end + +local function getDamage(attack, what) + if attack.damage then + return attack.damage[what] or 0 + end +end + +local function onHit(data) + if data.successful and not godMode() then + I.Combat.applyArmor(data) + I.Combat.adjustDamageForDifficulty(data) + if getDamage(data, 'health') > 0 then + core.sound.playSound3d('Health Damage', self) + if data.hitPos then + I.Combat.spawnBloodEffect(data.hitPos) + end + end + elseif data.attacker and Player.objectIsInstance(data.attacker) then + core.sound.playSound3d('miss', self) + end + Actor._onHit(self, data) +end + +I.Combat.addOnHitHandler(onHit) diff --git a/files/data/scripts/omw/combat/menu.lua b/files/data-mw/scripts/omw/combat/menu.lua similarity index 100% rename from files/data/scripts/omw/combat/menu.lua rename to files/data-mw/scripts/omw/combat/menu.lua diff --git a/files/data/scripts/omw/combat/local.lua b/files/data-mw/scripts/omw/interfaces/combatfunctions.lua similarity index 51% rename from files/data/scripts/omw/combat/local.lua rename to files/data-mw/scripts/omw/interfaces/combatfunctions.lua index a046614986..398c510bf4 100644 --- a/files/data/scripts/omw/combat/local.lua +++ b/files/data-mw/scripts/omw/interfaces/combatfunctions.lua @@ -1,27 +1,14 @@ -local animation = require('openmw.animation') -local async = require('openmw.async') 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 util = require('openmw.util') -local auxUtil = require('openmw_aux.util') local Actor = types.Actor -local Weapon = types.Weapon local Player = types.Player local Creature = types.Creature local Armor = types.Armor local isPlayer = Player.objectIsInstance(self) -local godMode = function() return false end -if isPlayer then - -- openmw.debug is only allowed on player scripts - godMode = function() return require('openmw.debug').isGodMode() end -end - -local onHitHandlers = {} - local settings = storage.globalSection('SettingsOMWCombat') local function getSkill(actor, skillId) @@ -60,39 +47,82 @@ local armorSlots = { Actor.EQUIPMENT_SLOT.CarriedLeft, } -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' +local function getDamage(attack, what) + if attack.damage then + return attack.damage[what] or 0 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') +local function setDamage(attack, what, damage) + attack.damage = attack.damage or {} + attack.damage[what] = damage 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) +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 - return rating end local function getArmorRating(actor) @@ -129,10 +159,67 @@ local function getArmorRating(actor) + magicShield 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')) +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) @@ -170,261 +257,14 @@ local function pickRandomArmor(actor) return Actor.getEquipment(actor, slot) end -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 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 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 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 onHit(data) - if auxUtil.callEventHandlers(onHitHandlers, data) then - return - end - if data.successful and not godMode() then - I.Combat.applyArmor(data) - I.Combat.adjustDamageForDifficulty(data) - if getDamage(data, 'health') > 0 then - core.sound.playSound3d('Health Damage', self) - if data.hitPos then - spawnBloodEffect(data.hitPos) - end - end - elseif data.attacker and Player.objectIsInstance(data.attacker) then - core.sound.playSound3d('miss', self) - end - Actor._onHit(self, data) -end - ---- --- Table of possible attack source types --- @type AttackSourceType --- @field #string Magic --- @field #string Melee --- @field #string Ranged --- @field #string Unspecified - ---- --- @type AttackInfo --- @field [parent=#AttackInfo] #table damage A table mapping a stat name (health, fatigue, or magicka) to a number. For example, {health = 50, fatigue = 10} will cause 50 damage to health and 10 to fatigue (before adjusting for armor and difficulty). This field is ignored for failed attacks. --- @field [parent=#AttackInfo] #number strength A number between 0 and 1 representing the attack strength. This field is ignored for failed attacks. --- @field [parent=#AttackInfo] #boolean successful Whether the attack was successful or not. --- @field [parent=#AttackInfo] #AttackSourceType sourceType What class of attack this is. --- @field [parent=#AttackInfo] openmw.self#ATTACK_TYPE type (Optional) Attack variant if applicable. For melee attacks this represents chop vs thrust vs slash. For unarmed creatures this implies which of its 3 possible attacks were used. For other attacks this field can be ignored. --- @field [parent=#AttackInfo] openmw.types#Actor attacker (Optional) Attacking actor --- @field [parent=#AttackInfo] openmw.types#Weapon weapon (Optional) Attacking weapon --- @field [parent=#AttackInfo] #string ammo (Optional) Ammo record ID --- @field [parent=#AttackInfo] openmw.util#Vector3 hitPos (Optional) Where on the victim the attack is landing. Used to spawn blood effects. Blood effects are skipped if nil. return { - --- Basic combat interface - -- @module Combat - -- @usage require('openmw.interfaces').Combat - -- - --I.Combat.addOnHitHandler(function(attack) - -- -- Adds fatigue loss when hit by draining fatigue when taking health damage - -- if attack.damage.health and not attack.damage.fatigue then - -- local strengthFactor = Actor.stats.attributes.strength(self).modified / 100 * 0.66 - -- local enduranceFactor = Actor.stats.attributes.endurance(self).modified / 100 * 0.34 - -- local factor = 1 - math.min(strengthFactor + enduranceFactor, 1) - -- if factor > 0 then - -- attack.damage.fatigue = attack.damage.health * factor - -- end - -- end - --end) - - interfaceName = 'Combat', - interface = { - --- Interface version - -- @field [parent=#Combat] #number version - version = 1, - - --- Add new onHit handler for this actor - -- If `handler(attack)` returns false, other handlers for - -- the call will be skipped. Where attack is the same @{#AttackInfo} passed to #Combat.onHit - -- @function [parent=#Combat] addOnHitHandler - -- @param #function handler The handler. - addOnHitHandler = function(handler) - onHitHandlers[#onHitHandlers + 1] = handler - end, - - --- Calculates the character's armor rating and adjusts damage accordingly. - -- Note that this function only adjusts the number, use #Combat.applyArmor - -- to include other side effects. - -- @function [parent=#Combat] adjustDamageForArmor - -- @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 = function(damage, actor) return adjustDamageForArmor(damage, actor or self) end, - - --- Calculates a difficulty multiplier based on the current difficulty settings - -- and adjusts damage accordingly. Has no effect if both this actor and the - -- attacker are NPCs, or if both are Players. - -- @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 = function(attack, defendant) return adjustDamageForDifficulty(attack, defendant or self) 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 = applyArmor, - - --- Computes this character's armor rating. - -- Note that this interface function is read by the engine to update the UI. - -- This function can still be overridden same as any other interface, but must not call any functions or interfaces that modify anything. - -- @function [parent=#Combat] getArmorRating - -- @param openmw.core#GameObject actor (Optional) The actor to calculate the armor rating for. Defaults to self. - -- @return #number - getArmorRating = function(actor) return getArmorRating(actor or self) 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). - -- Note that this interface function is read by the engine to update the UI. - -- This function can still be overridden same as any other interface, but must not call any functions or interfaces that modify anything. - -- @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 = getArmorSkill, - - --- 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. - -- This function can still be overridden same as any other interface, but must not call any functions or interfaces that modify anything. - -- @function [parent=#Combat] getSkillAdjustedArmorRating - -- @param openmw.core#GameObject item The item - -- @param openmw.core#GameObject actor (Optional) The actor, defaults to self - -- @return #number - getSkillAdjustedArmorRating = function(item, actor) return getSkillAdjustedArmorRating(item, actor or self) 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 = function(item, actor) return getEffectiveArmorRating(item, actor or self) end, - - --- Spawns a random blood effect at the given position - -- @function [parent=#Combat] spawnBloodEffect - -- @param openmw.util#Vector3 position - spawnBloodEffect = spawnBloodEffect, - - --- Hit this actor. Normally called as Hit event from the attacking actor, with the same parameters. - -- @function [parent=#Combat] onHit - -- @param #AttackInfo attackInfo - onHit = onHit, - - --- Picks a random armor slot and returns the item equipped in that slot. - -- Used to pick which armor to damage / skill to increase when hit during combat. - -- @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 = function(actor) return pickRandomArmor(actor or self) end, - - --- @{#AttackSourceType} - -- @field [parent=#Combat] #AttackSourceType ATTACK_SOURCE_TYPES Available attack source types - ATTACK_SOURCE_TYPES = { - Magic = 'magic', - Melee = 'melee', - Ranged = 'ranged', - Unspecified = 'unspecified', - }, - }, - - eventHandlers = { - Hit = function(data) I.Combat.onHit(data) end, - }, + 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 new file mode 100644 index 0000000000..0cb671d1bf --- /dev/null +++ b/files/data-mw/scripts/omw/interfaces/skillfunctions.lua @@ -0,0 +1,66 @@ +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/scripts/omw/music/helpers.lua b/files/data-mw/scripts/omw/music/helpers.lua old mode 100755 new mode 100644 similarity index 100% rename from files/data/scripts/omw/music/helpers.lua rename to files/data-mw/scripts/omw/music/helpers.lua diff --git a/files/data/scripts/omw/music/music.lua b/files/data-mw/scripts/omw/music/music.lua old mode 100755 new mode 100644 similarity index 100% rename from files/data/scripts/omw/music/music.lua rename to files/data-mw/scripts/omw/music/music.lua diff --git a/files/data/scripts/omw/music/settings.lua b/files/data-mw/scripts/omw/music/settings.lua old mode 100755 new mode 100644 similarity index 100% rename from files/data/scripts/omw/music/settings.lua rename to files/data-mw/scripts/omw/music/settings.lua diff --git a/files/data-mw/scripts/omw/playerskillhandlers.lua b/files/data-mw/scripts/omw/playerskillhandlers.lua new file mode 100644 index 0000000000..b6485bba8e --- /dev/null +++ b/files/data-mw/scripts/omw/playerskillhandlers.lua @@ -0,0 +1,126 @@ +local ambient = require('openmw.ambient') +local core = require('openmw.core') +local Skill = core.stats.Skill +local I = require('openmw.interfaces') +local self = require('openmw.self') +local types = require('openmw.types') +local NPC = types.NPC +local Actor = types.Actor +local ui = require('openmw.ui') + +local function skillLevelUpHandler(skillid, source, params) + local skillStat = NPC.stats.skills[skillid](self) + if (skillStat.base >= 100 and params.skillIncreaseValue > 0) or + (skillStat.base <= 0 and params.skillIncreaseValue < 0) then + return false + end + + if params.skillIncreaseValue then + skillStat.base = skillStat.base + params.skillIncreaseValue + end + + local levelStat = Actor.stats.level(self) + if params.levelUpProgress then + levelStat.progress = levelStat.progress + params.levelUpProgress + end + + if params.levelUpAttribute and params.levelUpAttributeIncreaseValue then + levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + = levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + params.levelUpAttributeIncreaseValue + end + + if params.levelUpSpecialization and params.levelUpSpecializationIncreaseValue then + levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; + end + + if source ~= 'jail' then + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + ambient.playSound("skillraise") + + local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) + + if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then + message = '#{sBookSkillMessage}\n'..message + end + + ui.showMessage(message, { showInDialogue = false }) + + if levelStat.progress >= core.getGMST('iLevelUpTotal') then + ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) + end + + if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end + end +end + +local function jailTimeServed(days) + if not days or days <= 0 then + return + end + + local oldSkillLevels = {} + local skillByNumber = {} + for skillid, skillStat in pairs(NPC.stats.skills) do + oldSkillLevels[skillid] = skillStat(self).base + skillByNumber[#skillByNumber+1] = skillid + end + + math.randomseed(core.getSimulationTime()) + for day=1,days do + local skillid = skillByNumber[math.random(#skillByNumber)] + -- skillLevelUp() handles skill-based increase/decrease + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Jail) + end + + local message = '' + if days == 1 then + message = string.format(core.getGMST('sNotifyMessage42'), days) + else + message = string.format(core.getGMST('sNotifyMessage43'), days) + end + for skillid, skillStat in pairs(NPC.stats.skills) do + local diff = skillStat(self).base - oldSkillLevels[skillid] + if diff ~= 0 then + local skillMsg = core.getGMST('sNotifyMessage39') + if diff < 0 then + skillMsg = core.getGMST('sNotifyMessage44') + end + local skillRecord = Skill.record(skillid) + message = message..'\n'..string.format(skillMsg, skillRecord.name, skillStat(self).base) + end + end + + I.UI.showInteractiveMessage(message) +end + +local function skillUsedHandler(skillid, params) + if NPC.isWerewolf(self) then + return false + end + + local skillStat = NPC.stats.skills[skillid](self) + + if (skillStat.base >= 100 and params.skillGain > 0) or + (skillStat.base <= 0 and params.skillGain < 0) then + return false + end + + skillStat.progress = skillStat.progress + params.skillGain / I.SkillProgression.getSkillProgressRequirement(skillid) + + if skillStat.progress >= 1 then + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) + end +end + +I.SkillProgression.addSkillUsedHandler(skillUsedHandler) +I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) + +return { + engineHandlers = { + _onJailTimeServed = jailTimeServed, + } +} diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index f9cc1df16e..cb17f23426 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -125,30 +125,25 @@ set(BUILTIN_DATA_FILES scripts/omw/activationhandlers.lua scripts/omw/ai.lua - scripts/omw/cellhandlers.lua scripts/omw/camera/camera.lua scripts/omw/camera/head_bobbing.lua scripts/omw/camera/third_person.lua scripts/omw/camera/settings.lua scripts/omw/camera/move360.lua scripts/omw/camera/first_person_auto_switch.lua - scripts/omw/combat/common.lua - scripts/omw/combat/global.lua - scripts/omw/combat/local.lua - scripts/omw/combat/menu.lua scripts/omw/console/global.lua 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/mechanics/actorcontroller.lua scripts/omw/mechanics/animationcontroller.lua scripts/omw/mechanics/globalcontroller.lua scripts/omw/mechanics/playercontroller.lua scripts/omw/settings/menu.lua scripts/omw/music/actor.lua - scripts/omw/music/helpers.lua - scripts/omw/music/music.lua - scripts/omw/music/settings.lua scripts/omw/settings/player.lua scripts/omw/settings/global.lua scripts/omw/settings/common.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 0d0d2eb8a7..682536ec39 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -1,3 +1,5 @@ +# NB: data-mw overrides this file! + # UI framework MENU,PLAYER: scripts/omw/mwui/init.lua @@ -8,7 +10,6 @@ GLOBAL: scripts/omw/settings/global.lua # Mechanics GLOBAL: scripts/omw/activationhandlers.lua -GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua GLOBAL: scripts/omw/crimes.lua @@ -25,9 +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 -GLOBAL: scripts/omw/combat/global.lua -MENU: scripts/omw/combat/menu.lua -NPC,CREATURE,PLAYER: scripts/omw/combat/local.lua +NPC,CREATURE,PLAYER: scripts/omw/interfaces/combat.lua # User interface PLAYER: scripts/omw/ui.lua @@ -39,6 +38,4 @@ GLOBAL: scripts/omw/console/global.lua CUSTOM: scripts/omw/console/local.lua # Music system -PLAYER: scripts/omw/music/music.lua -MENU: scripts/omw/music/settings.lua NPC,CREATURE: scripts/omw/music/actor.lua diff --git a/files/data/scripts/omw/interfaces/combat.lua b/files/data/scripts/omw/interfaces/combat.lua new file mode 100644 index 0000000000..f3330dcd25 --- /dev/null +++ b/files/data/scripts/omw/interfaces/combat.lua @@ -0,0 +1,145 @@ +local I = require('openmw.interfaces') +local auxUtil = require('openmw_aux.util') +local functions = require('scripts.omw.interfaces.combatfunctions') + +local onHitHandlers = {} + +--- +-- Table of possible attack source types +-- @type AttackSourceType +-- @field #string Magic +-- @field #string Melee +-- @field #string Ranged +-- @field #string Unspecified + +--- +-- @type AttackInfo +-- @field [parent=#AttackInfo] #table damage A table mapping stat name (health, fatigue, or magicka) to number. For example, {health = 50, fatigue = 10} will cause 50 damage to health and 10 to fatigue (before adjusting for armor and difficulty). This field is ignored for failed attacks. +-- @field [parent=#AttackInfo] #number strength A number between 0 and 1 representing the attack strength. This field is ignored for failed attacks. +-- @field [parent=#AttackInfo] #boolean successful Whether the attack was successful or not. +-- @field [parent=#AttackInfo] #AttackSourceType sourceType What class of attack this is. +-- @field [parent=#AttackInfo] openmw.self#ATTACK_TYPE type (Optional) Attack variant if applicable. For melee attacks this represents chop vs thrust vs slash. For unarmed creatures this implies which of its 3 possible attacks were used. For other attacks this field can be ignored. +-- @field [parent=#AttackInfo] openmw.types#Actor attacker (Optional) Attacking actor +-- @field [parent=#AttackInfo] openmw.types#Weapon weapon (Optional) Attacking weapon +-- @field [parent=#AttackInfo] #string ammo (Optional) Ammo record ID +-- @field [parent=#AttackInfo] openmw.util#Vector3 hitPos (Optional) Where on the victim the attack is landing. Used to spawn blood effects. Blood effects are skipped if nil. +return { + --- Basic combat interface + -- @module Combat + -- @usage require('openmw.interfaces').Combat + -- + --I.Combat.addOnHitHandler(function(attack) + -- -- Adds fatigue loss when hit by draining fatigue when taking health damage + -- if attack.damage.health and not attack.damage.fatigue then + -- local strengthFactor = Actor.stats.attributes.strength(self).modified / 100 * 0.66 + -- local enduranceFactor = Actor.stats.attributes.endurance(self).modified / 100 * 0.34 + -- local factor = 1 - math.min(strengthFactor + enduranceFactor, 1) + -- if factor > 0 then + -- attack.damage.fatigue = attack.damage.health * factor + -- end + -- end + --end) + + interfaceName = 'Combat', + interface = { + --- Interface version + -- @field [parent=#Combat] #number version + version = 1, + + --- Add new onHit handler for this actor + -- If `handler(attack)` returns false, other handlers for + -- the call will be skipped. where attack is the same @{#AttackInfo} passed to #Combat.onHit + -- @function [parent=#Combat] addOnHitHandler + -- @param #function handler The handler. + addOnHitHandler = function(handler) + onHitHandlers[#onHitHandlers + 1] = handler + end, + + --- Calculates the character's armor rating and adjusts damage accordingly. + -- Note that this function only adjusts the number, use #Combat.applyArmor + -- to include other side effects. + -- @function [parent=#Combat] adjustDamageForArmor + -- @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, + + --- Calculates a difficulty multiplier based on current difficulty settings + -- and adjusts damage accordingly. Has no effect if both this actor and the + -- attacker are NPCs, or if both are Players. + -- @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, + + --- 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, + + --- Computes this character's armor rating. + -- Note that this interface function is read by the engine to update the UI. + -- This function can still be overridden same as any other interface, but must not call any functions or interfaces that modify anything. + -- @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, + + --- Computes this character's armor rating. + -- You can override this to return any skill you wish (including non-armor skills, if you so wish). + -- Note that this interface function is read by the engine to update the UI. + -- This function can still be overridden same as any other interface, but must not call any functions or interfaces that modify anything. + -- @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, + + --- 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. + -- This function can still be overridden same as any other interface, but must not call any functions or interfaces that modify anything. + -- @function [parent=#Combat] getSkillAdjustedArmorRating + -- @param openmw.core#GameObject item The item + -- @param openmw.core#GameObject actor (Optional) The actor, defaults to self + -- @return #number + getSkillAdjustedArmorRating = functions.getSkillAdjustedArmorRating, + + --- 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, + + --- Spawns a random blood effect at the given position + -- @function [parent=#Combat] spawnBloodEffect + -- @param openmw.util#Vector3 position + spawnBloodEffect = functions.spawnBloodEffect, + + --- Hit this actor. Normally called as Hit event from the attacking actor, with the same parameters. + -- @function [parent=#Combat] onHit + -- @param #AttackInfo attackInfo + onHit = function(attackInfo) auxUtil.callEventHandlers(onHitHandlers, attackInfo) end, + + --- Picks a random armor slot and returns the item equipped in that slot. + -- Used to pick which armor to damage / skill to increase when hit during combat. + -- @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, + + --- @{#AttackSourceType} + -- @field [parent=#Combat] #AttackSourceType ATTACK_SOURCE_TYPES Available attack source types + ATTACK_SOURCE_TYPES = { + Magic = 'magic', + Melee = 'melee', + Ranged = 'ranged', + Unspecified = 'unspecified', + }, + }, + + eventHandlers = { + Hit = function(data) I.Combat.onHit(data) end, + }, +} \ No newline at end of file diff --git a/files/data/scripts/omw/interfaces/combatfunctions.lua b/files/data/scripts/omw/interfaces/combatfunctions.lua new file mode 100644 index 0000000000..eb2c6d229e --- /dev/null +++ b/files/data/scripts/omw/interfaces/combatfunctions.lua @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000000..93ea1836b6 --- /dev/null +++ b/files/data/scripts/omw/interfaces/skillfunctions.lua @@ -0,0 +1,4 @@ +return { + getSkillProgressRequirement = function(skillid) return 1 end, + getSkillLevelUpOptions = function(skillid, source) return {} end +} diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 8345ed2166..a8963f86d1 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -1,12 +1,7 @@ -local ambient = require('openmw.ambient') local core = require('openmw.core') -local Skill = core.stats.Skill -local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local types = require('openmw.types') -local NPC = types.NPC -local Actor = types.Actor local ui = require('openmw.ui') local cell = nil @@ -37,114 +32,6 @@ local function processAutomaticDoors() end end -local function skillLevelUpHandler(skillid, source, params) - local skillStat = NPC.stats.skills[skillid](self) - if (skillStat.base >= 100 and params.skillIncreaseValue > 0) or - (skillStat.base <= 0 and params.skillIncreaseValue < 0) then - return false - end - - if params.skillIncreaseValue then - skillStat.base = skillStat.base + params.skillIncreaseValue - end - - local levelStat = Actor.stats.level(self) - if params.levelUpProgress then - levelStat.progress = levelStat.progress + params.levelUpProgress - end - - if params.levelUpAttribute and params.levelUpAttributeIncreaseValue then - levelStat.skillIncreasesForAttribute[params.levelUpAttribute] - = levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + params.levelUpAttributeIncreaseValue - end - - if params.levelUpSpecialization and params.levelUpSpecializationIncreaseValue then - levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] - = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; - end - - if source ~= 'jail' then - local skillRecord = Skill.record(skillid) - local npcRecord = NPC.record(self) - local class = NPC.classes.record(npcRecord.class) - - ambient.playSound("skillraise") - - local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) - - if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then - message = '#{sBookSkillMessage}\n'..message - end - - ui.showMessage(message, { showInDialogue = false }) - - if levelStat.progress >= core.getGMST('iLevelUpTotal') then - ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) - end - - if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end - end -end - -local function jailTimeServed(days) - if not days or days <= 0 then - return - end - - local oldSkillLevels = {} - local skillByNumber = {} - for skillid, skillStat in pairs(NPC.stats.skills) do - oldSkillLevels[skillid] = skillStat(self).base - skillByNumber[#skillByNumber+1] = skillid - end - - math.randomseed(core.getSimulationTime()) - for day=1,days do - local skillid = skillByNumber[math.random(#skillByNumber)] - -- skillLevelUp() handles skill-based increase/decrease - I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Jail) - end - - local message = '' - if days == 1 then - message = string.format(core.getGMST('sNotifyMessage42'), days) - else - message = string.format(core.getGMST('sNotifyMessage43'), days) - end - for skillid, skillStat in pairs(NPC.stats.skills) do - local diff = skillStat(self).base - oldSkillLevels[skillid] - if diff ~= 0 then - local skillMsg = core.getGMST('sNotifyMessage39') - if diff < 0 then - skillMsg = core.getGMST('sNotifyMessage44') - end - local skillRecord = Skill.record(skillid) - message = message..'\n'..string.format(skillMsg, skillRecord.name, skillStat(self).base) - end - end - - I.UI.showInteractiveMessage(message) -end - -local function skillUsedHandler(skillid, params) - if NPC.isWerewolf(self) then - return false - end - - local skillStat = NPC.stats.skills[skillid](self) - - if (skillStat.base >= 100 and params.skillGain > 0) or - (skillStat.base <= 0 and params.skillGain < 0) then - return false - end - - skillStat.progress = skillStat.progress + params.skillGain / I.SkillProgression.getSkillProgressRequirement(skillid) - - if skillStat.progress >= 1 then - I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) - end -end - local function onUpdate(dt) if dt <= 0 then return @@ -157,13 +44,9 @@ local function onUpdate(dt) processAutomaticDoors() end -I.SkillProgression.addSkillUsedHandler(skillUsedHandler) -I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) - return { engineHandlers = { onUpdate = onUpdate, - _onJailTimeServed = jailTimeServed, }, eventHandlers = { diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 70036db857..4ce211b31a 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -1,10 +1,10 @@ local self = require('openmw.self') local I = require('openmw.interfaces') -local types = require('openmw.types') 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,40 +47,12 @@ local Skill = core.stats.Skill local skillUsedHandlers = {} local skillLevelUpHandlers = {} -local function tableHasValue(table, value) - for _, v in pairs(table) do - if v == value then return true end - end - return false -end - local function shallowCopy(t1) local t2 = {} for key, value in pairs(t1) do t2[key] = value end return t2 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 skillUsed(skillid, options) if #skillUsedHandlers == 0 then -- If there are no handlers, then there won't be any effect, so skip calculations @@ -113,34 +85,7 @@ local function skillLevelUp(skillid, source) -- If there are no handlers, then there won't be any effect, so skip calculations return end - - 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 - + local options = functions.getSkillLevelUpOptions(skillid, source) auxUtil.callEventHandlers(skillLevelUpHandlers, skillid, source, options) end @@ -281,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 = getSkillProgressRequirement + getSkillProgressRequirement = functions.getSkillProgressRequirement }, 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 cfdf4728d1..cd7139cbab 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.combat.local#scripts.omw.combat.local Combat +-- @field [parent=#interfaces] scripts.omw.interfaces.combat#scripts.omw.interfaces.combat Combat --- -- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI