1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-11-30 06:34:33 +00:00

Merge branch 'data-mw' into 'master'

Move Morrowind mechanics to data-mw

See merge request OpenMW/openmw!4937
This commit is contained in:
Alexei Kotov 2025-11-24 21:24:10 +03:00
commit 0d4bff8ad6
21 changed files with 411 additions and 347 deletions

View file

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 51)
set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 102)
set(OPENMW_LUA_API_REVISION 103)
set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "")

View file

@ -2,7 +2,7 @@ paths=(
openmw_aux/*lua
scripts/omw/activationhandlers.lua
scripts/omw/ai.lua
scripts/omw/combat/local.lua
scripts/omw/combat/interface.lua
scripts/omw/input/playercontrols.lua
scripts/omw/mechanics/animationcontroller.lua
scripts/omw/input/gamepadcontrols.lua

View file

@ -4,5 +4,5 @@ Interface Combat
.. include:: version.rst
.. raw:: html
:file: generated_html/scripts_omw_combat_local.html
:file: generated_html/scripts_omw_combat_interface.html

View file

@ -20,8 +20,29 @@ set(BUILTIN_DATA_MW_FILES
# Game-specific settings for calendar.lua
openmw_aux/calendarconfig.lua
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/music/helpers.lua
scripts/omw/music/music.lua
scripts/omw/music/settings.lua
scripts/omw/playerskillhandlers.lua
)
foreach (f ${BUILTIN_DATA_MW_FILES})
copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/vfs-mw/${f}")
endforeach (f)
# Concat data/builtin.omwscripts and data-mw/builtin.omwscripts.in to create vfs-mw/builtin.omwscripts
set(builtinBase "${CMAKE_CURRENT_SOURCE_DIR}/../data/builtin.omwscripts")
# https://gitlab.kitware.com/cmake/cmake/-/issues/20181
if (NOT CMAKE_GENERATOR MATCHES "Ninja")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${builtinBase}")
endif()
file(READ "${builtinBase}" BUILTIN_SCRIPTS)
configure_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/builtin.omwscripts.in" "${OPENMW_RESOURCES_ROOT}" "resources/vfs-mw/builtin.omwscripts")

View file

@ -0,0 +1,10 @@
@BUILTIN_SCRIPTS@
# Game specific scripts to append to builtin.omwscripts
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

View file

@ -1,17 +1,13 @@
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 auxUtil = require('openmw_aux.util')
local isPlayer = Player.objectIsInstance(self)
local godMode = function() return false end
@ -20,8 +16,6 @@ if isPlayer then
godMode = function() return require('openmw.debug').isGodMode() end
end
local onHitHandlers = {}
local settings = storage.globalSection('SettingsOMWCombat')
local function getSkill(actor, skillId)
@ -271,16 +265,13 @@ local function spawnBloodEffect(position)
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)
I.Combat.spawnBloodEffect(data.hitPos)
end
end
elseif data.attacker and Player.objectIsInstance(data.attacker) then
@ -289,142 +280,20 @@ local function onHit(data)
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
I.Combat.addOnHitHandler(onHit)
local interface = auxUtil.shallowCopy(I.Combat)
interface.adjustDamageForArmor = function(damage, actor) return adjustDamageForArmor(damage, actor or self) end
interface.adjustDamageForDifficulty = function(attack, defendant) return adjustDamageForDifficulty(attack, defendant or self) end
interface.applyArmor = applyArmor
interface.getArmorRating = function(actor) return getArmorRating(actor or self) end
interface.getArmorSkill = getArmorSkill
interface.getSkillAdjustedArmorRating = function(item, actor) return getSkillAdjustedArmorRating(item, actor or self) end
interface.getEffectiveArmorRating = function(item, actor) return getEffectiveArmorRating(item, actor or self) end
interface.spawnBloodEffect = spawnBloodEffect
interface.pickRandomArmor = function(actor) return pickRandomArmor(actor or self) end
---
-- @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,
},
interface = interface
}

View file

@ -0,0 +1,190 @@
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 auxUtil = require('openmw_aux.util')
local function tableHasValue(table, value)
for _, v in pairs(table) do
if v == value then return true end
end
return false
end
local function getSkillProgressRequirement(skillid)
local npcRecord = NPC.record(self)
local class = NPC.classes.record(npcRecord.class)
local skillStat = NPC.stats.skills[skillid](self)
local skillRecord = Skill.record(skillid)
local factor = core.getGMST('fMiscSkillBonus')
if tableHasValue(class.majorSkills, skillid) then
factor = core.getGMST('fMajorSkillBonus')
elseif tableHasValue(class.minorSkills, skillid) then
factor = core.getGMST('fMinorSkillBonus')
end
if skillRecord.specialization == class.specialization then
factor = factor * core.getGMST('fSpecialSkillBonus')
end
return (skillStat.base + 1) * factor
end
local function getSkillLevelUpOptions(skillid, source)
local skillRecord = Skill.record(skillid)
local npcRecord = NPC.record(self)
local class = NPC.classes.record(npcRecord.class)
local levelUpProgress = 0
local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte')
if tableHasValue(class.minorSkills, skillid) then
levelUpProgress = core.getGMST('iLevelUpMinorMult')
levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute')
elseif tableHasValue(class.majorSkills, skillid) then
levelUpProgress = core.getGMST('iLevelUpMajorMult')
levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute')
end
local options = {}
if source == 'jail' and not (skillid == 'security' or skillid == 'sneak') then
options.skillIncreaseValue = -1
else
options.skillIncreaseValue = 1
options.levelUpProgress = levelUpProgress
options.levelUpAttribute = skillRecord.attribute
options.levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue
options.levelUpSpecialization = skillRecord.specialization
options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization')
end
return options
end
local function skillLevelUpHandler(skillid, source, params)
local skillStat = NPC.stats.skills[skillid](self)
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)
local interface = auxUtil.shallowCopy(I.SkillProgression)
interface.getSkillProgressRequirement = getSkillProgressRequirement
interface.getSkillLevelUpOptions = getSkillLevelUpOptions
return {
engineHandlers = {
_onJailTimeServed = jailTimeServed,
},
interfaceName = 'SkillProgression',
interface = interface
}

View file

@ -125,30 +125,23 @@ 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/combat/interface.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

View file

@ -8,7 +8,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 +24,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/combat/interface.lua
# User interface
PLAYER: scripts/omw/ui.lua
@ -39,6 +36,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

View file

@ -143,5 +143,18 @@ function aux_util.callMultipleEventHandlers(handlers, ...)
return false
end
---
-- Copies all key-value pairs from the input table to a new table.
-- @function [parent=#util] shallowCopy
-- @param #table table The table to copy
-- @return #table A shallow copy of the input table
function aux_util.shallowCopy(table)
local copy = {}
for key, value in pairs(table) do
copy[key] = value
end
return copy
end
return aux_util

View file

@ -0,0 +1,145 @@
local I = require('openmw.interfaces')
local util = require('openmw.util')
local auxUtil = require('openmw_aux.util')
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 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 local I = require('openmw.interfaces')
--
--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 damage 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) 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 = function(attack) end,
--- 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 0 end,
--- Computes this character's armor rating.
-- You can override this to return any skill you wish (including non-armor skills, if you so wish).
-- 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 instance of @{openmw.types#Armor}. Can return nil if unimplemented.
getArmorSkill = function(item) return nil end,
--- Computes the armor rating of a single piece of @{openmw.types#Armor}, adjusted for skill
-- Note that this interface function is read by the engine to update the UI.
-- 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 0 end,
--- Computes the effective armor rating of a single piece of @{openmw.types#Armor}, adjusted for skill and item condition
-- @function [parent=#Combat] getEffectiveArmorRating
-- @param openmw.core#GameObject item The item
-- @param openmw.core#GameObject actor (Optional) The actor, defaults to self
-- @return #number
getEffectiveArmorRating = function(item, actor) return 0 end,
--- Spawns a random blood effect at the given position
-- @function [parent=#Combat] spawnBloodEffect
-- @param openmw.util#Vector3 position
spawnBloodEffect = function(position) end,
--- 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 = function(actor) return nil end,
--- @{#AttackSourceType}
-- @field [parent=#Combat] #AttackSourceType ATTACK_SOURCE_TYPES Available attack source types
ATTACK_SOURCE_TYPES = util.makeStrictReadOnly({
Magic = 'magic',
Melee = 'melee',
Ranged = 'ranged',
Unspecified = 'unspecified',
}),
},
eventHandlers = {
Hit = function(data) I.Combat.onHit(data) end,
},
}

View file

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

View file

@ -1,6 +1,5 @@
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
@ -47,40 +46,6 @@ 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
@ -88,7 +53,7 @@ local function skillUsed(skillid, options)
end
-- Make a copy so we don't change the caller's table
options = shallowCopy(options)
options = auxUtil.shallowCopy(options)
-- Compute use value if it was not supplied directly
if not options.skillGain then
@ -113,34 +78,7 @@ local function skillLevelUp(skillid, source)
-- If there are no handlers, then there won't be any effect, so skip calculations
return
end
local 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 = I.SkillProgression.getSkillLevelUpOptions(skillid, source)
auxUtil.callEventHandlers(skillLevelUpHandlers, skillid, source, options)
end
@ -181,7 +119,7 @@ return {
interface = {
--- Interface version
-- @field [parent=#SkillProgression] #number version
version = 1,
version = 2,
--- Add new skill level up handler for this actor.
-- For load order consistency, handlers should be added in the body if your script.
@ -268,6 +206,13 @@ return {
-- @param #string skillid The id of the skill to level up.
-- @param #SkillLevelUpSource source The source of the skill increase. Note that passing a value of @{#SkillLevelUpSource.Jail} will cause a skill decrease for all skills except sneak and security.
skillLevelUp = skillLevelUp,
--- Construct a table of skill level up options
-- @function [parent=#SkillProgression] getSkillLevelUpOptions
-- @param #string skillid The id of the skill to level up
-- @param #SkillLevelUpSource source The source of the skill increase
-- @return #table The options to pass to the skill level up handlers
getSkillLevelUpOptions = function(skillid, source) return {} end,
--- @{#SkillLevelUpSource}
-- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES
@ -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 = function(skillid) return 1 end
},
engineHandlers = {
-- Use the interface in these handlers so any overrides will receive the calls.

View file

@ -16,7 +16,7 @@
-- @field [parent=#interfaces] scripts.omw.camera.camera#scripts.omw.camera.camera Camera
---
-- @field [parent=#interfaces] scripts.omw.combat.local#scripts.omw.combat.local Combat
-- @field [parent=#interfaces] scripts.omw.combat.interface#scripts.omw.combat.interface Combat
---
-- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI