mirror of
https://github.com/OpenMW/openmw.git
synced 2025-11-29 20:34:30 +00:00
Move Morrowind mechanics to data-mw
This commit is contained in:
parent
2a63ec6af6
commit
7928930435
23 changed files with 605 additions and 492 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
50
files/data-mw/builtin.omwscripts
Normal file
50
files/data-mw/builtin.omwscripts
Normal file
|
|
@ -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
|
||||
37
files/data-mw/scripts/omw/combat/local.lua
Normal file
37
files/data-mw/scripts/omw/combat/local.lua
Normal file
|
|
@ -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)
|
||||
|
|
@ -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'
|
||||
local function getDamage(attack, what)
|
||||
if attack.damage then
|
||||
return attack.damage[what] or 0
|
||||
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'
|
||||
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
|
||||
return 'heavyarmor'
|
||||
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
|
||||
|
||||
local function getSkillAdjustedArmorRating(item, actor)
|
||||
local record = Armor.record(item)
|
||||
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)
|
||||
local skill = getSkill(actor, skillid)
|
||||
if record.weight == 0 then
|
||||
return record.baseArmor
|
||||
if I.SkillProgression then
|
||||
I.SkillProgression.skillUsed(skillid, {useType = I.SkillProgression.SKILL_USE_TYPES.Armor_HitByOpponent})
|
||||
end
|
||||
return record.baseArmor * skill / core.getGMST('iBaseArmorSkill')
|
||||
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
|
||||
|
||||
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)
|
||||
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,
|
||||
},
|
||||
pickRandomArmor = function(actor) return pickRandomArmor(actor or self) end
|
||||
}
|
||||
66
files/data-mw/scripts/omw/interfaces/skillfunctions.lua
Normal file
66
files/data-mw/scripts/omw/interfaces/skillfunctions.lua
Normal file
|
|
@ -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
|
||||
}
|
||||
0
files/data/scripts/omw/music/helpers.lua → files/data-mw/scripts/omw/music/helpers.lua
Executable file → Normal file
0
files/data/scripts/omw/music/helpers.lua → files/data-mw/scripts/omw/music/helpers.lua
Executable file → Normal file
0
files/data/scripts/omw/music/music.lua → files/data-mw/scripts/omw/music/music.lua
Executable file → Normal file
0
files/data/scripts/omw/music/music.lua → files/data-mw/scripts/omw/music/music.lua
Executable file → Normal file
0
files/data/scripts/omw/music/settings.lua → files/data-mw/scripts/omw/music/settings.lua
Executable file → Normal file
0
files/data/scripts/omw/music/settings.lua → files/data-mw/scripts/omw/music/settings.lua
Executable file → Normal file
126
files/data-mw/scripts/omw/playerskillhandlers.lua
Normal file
126
files/data-mw/scripts/omw/playerskillhandlers.lua
Normal file
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
145
files/data/scripts/omw/interfaces/combat.lua
Normal file
145
files/data/scripts/omw/interfaces/combat.lua
Normal file
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
11
files/data/scripts/omw/interfaces/combatfunctions.lua
Normal file
11
files/data/scripts/omw/interfaces/combatfunctions.lua
Normal file
|
|
@ -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
|
||||
}
|
||||
4
files/data/scripts/omw/interfaces/skillfunctions.lua
Normal file
4
files/data/scripts/omw/interfaces/skillfunctions.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
return {
|
||||
getSkillProgressRequirement = function(skillid) return 1 end,
|
||||
getSkillLevelUpOptions = function(skillid, source) return {} end
|
||||
}
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue