1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-11-30 20:04: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_MAJOR 0)
set(OPENMW_VERSION_MINOR 51) set(OPENMW_VERSION_MINOR 51)
set(OPENMW_VERSION_RELEASE 0) 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_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

View file

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

View file

@ -4,5 +4,5 @@ Interface Combat
.. include:: version.rst .. include:: version.rst
.. raw:: html .. 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 # Game-specific settings for calendar.lua
openmw_aux/calendarconfig.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}) foreach (f ${BUILTIN_DATA_MW_FILES})
copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/vfs-mw/${f}") copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/vfs-mw/${f}")
endforeach (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 core = require('openmw.core')
local I = require('openmw.interfaces') local I = require('openmw.interfaces')
local self = require('openmw.self') local self = require('openmw.self')
local storage = require('openmw.storage') local storage = require('openmw.storage')
local types = require('openmw.types') local types = require('openmw.types')
local util = require('openmw.util')
local auxUtil = require('openmw_aux.util')
local Actor = types.Actor local Actor = types.Actor
local Weapon = types.Weapon
local Player = types.Player local Player = types.Player
local Creature = types.Creature local Creature = types.Creature
local Armor = types.Armor local Armor = types.Armor
local auxUtil = require('openmw_aux.util')
local isPlayer = Player.objectIsInstance(self) local isPlayer = Player.objectIsInstance(self)
local godMode = function() return false end local godMode = function() return false end
@ -20,8 +16,6 @@ if isPlayer then
godMode = function() return require('openmw.debug').isGodMode() end godMode = function() return require('openmw.debug').isGodMode() end
end end
local onHitHandlers = {}
local settings = storage.globalSection('SettingsOMWCombat') local settings = storage.globalSection('SettingsOMWCombat')
local function getSkill(actor, skillId) local function getSkill(actor, skillId)
@ -271,16 +265,13 @@ local function spawnBloodEffect(position)
end end
local function onHit(data) local function onHit(data)
if auxUtil.callEventHandlers(onHitHandlers, data) then
return
end
if data.successful and not godMode() then if data.successful and not godMode() then
I.Combat.applyArmor(data) I.Combat.applyArmor(data)
I.Combat.adjustDamageForDifficulty(data) I.Combat.adjustDamageForDifficulty(data)
if getDamage(data, 'health') > 0 then if getDamage(data, 'health') > 0 then
core.sound.playSound3d('Health Damage', self) core.sound.playSound3d('Health Damage', self)
if data.hitPos then if data.hitPos then
spawnBloodEffect(data.hitPos) I.Combat.spawnBloodEffect(data.hitPos)
end end
end end
elseif data.attacker and Player.objectIsInstance(data.attacker) then elseif data.attacker and Player.objectIsInstance(data.attacker) then
@ -289,142 +280,20 @@ local function onHit(data)
Actor._onHit(self, data) Actor._onHit(self, data)
end end
--- I.Combat.addOnHitHandler(onHit)
-- Table of possible attack source types
-- @type AttackSourceType local interface = auxUtil.shallowCopy(I.Combat)
-- @field #string Magic interface.adjustDamageForArmor = function(damage, actor) return adjustDamageForArmor(damage, actor or self) end
-- @field #string Melee interface.adjustDamageForDifficulty = function(attack, defendant) return adjustDamageForDifficulty(attack, defendant or self) end
-- @field #string Ranged interface.applyArmor = applyArmor
-- @field #string Unspecified 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 { 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', interfaceName = 'Combat',
interface = { interface = 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,
},
} }

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/activationhandlers.lua
scripts/omw/ai.lua scripts/omw/ai.lua
scripts/omw/cellhandlers.lua
scripts/omw/camera/camera.lua scripts/omw/camera/camera.lua
scripts/omw/camera/head_bobbing.lua scripts/omw/camera/head_bobbing.lua
scripts/omw/camera/third_person.lua scripts/omw/camera/third_person.lua
scripts/omw/camera/settings.lua scripts/omw/camera/settings.lua
scripts/omw/camera/move360.lua scripts/omw/camera/move360.lua
scripts/omw/camera/first_person_auto_switch.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/global.lua
scripts/omw/console/local.lua scripts/omw/console/local.lua
scripts/omw/console/player.lua scripts/omw/console/player.lua
scripts/omw/console/menu.lua scripts/omw/console/menu.lua
scripts/omw/combat/interface.lua
scripts/omw/mechanics/actorcontroller.lua scripts/omw/mechanics/actorcontroller.lua
scripts/omw/mechanics/animationcontroller.lua scripts/omw/mechanics/animationcontroller.lua
scripts/omw/mechanics/globalcontroller.lua scripts/omw/mechanics/globalcontroller.lua
scripts/omw/mechanics/playercontroller.lua scripts/omw/mechanics/playercontroller.lua
scripts/omw/settings/menu.lua scripts/omw/settings/menu.lua
scripts/omw/music/actor.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/player.lua
scripts/omw/settings/global.lua scripts/omw/settings/global.lua
scripts/omw/settings/common.lua scripts/omw/settings/common.lua

View file

@ -8,7 +8,6 @@ GLOBAL: scripts/omw/settings/global.lua
# Mechanics # Mechanics
GLOBAL: scripts/omw/activationhandlers.lua GLOBAL: scripts/omw/activationhandlers.lua
GLOBAL: scripts/omw/cellhandlers.lua
GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/usehandlers.lua
GLOBAL: scripts/omw/worldeventhandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua
GLOBAL: scripts/omw/crimes.lua GLOBAL: scripts/omw/crimes.lua
@ -25,9 +24,7 @@ PLAYER: scripts/omw/input/gamepadcontrols.lua
NPC,CREATURE: scripts/omw/ai.lua NPC,CREATURE: scripts/omw/ai.lua
GLOBAL: scripts/omw/mechanics/globalcontroller.lua GLOBAL: scripts/omw/mechanics/globalcontroller.lua
CREATURE, NPC, PLAYER: scripts/omw/mechanics/actorcontroller.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/actorcontroller.lua
GLOBAL: scripts/omw/combat/global.lua NPC,CREATURE,PLAYER: scripts/omw/combat/interface.lua
MENU: scripts/omw/combat/menu.lua
NPC,CREATURE,PLAYER: scripts/omw/combat/local.lua
# User interface # User interface
PLAYER: scripts/omw/ui.lua PLAYER: scripts/omw/ui.lua
@ -39,6 +36,4 @@ GLOBAL: scripts/omw/console/global.lua
CUSTOM: scripts/omw/console/local.lua CUSTOM: scripts/omw/console/local.lua
# Music system # Music system
PLAYER: scripts/omw/music/music.lua
MENU: scripts/omw/music/settings.lua
NPC,CREATURE: scripts/omw/music/actor.lua NPC,CREATURE: scripts/omw/music/actor.lua

View file

@ -143,5 +143,18 @@ function aux_util.callMultipleEventHandlers(handlers, ...)
return false return false
end 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 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 core = require('openmw.core')
local Skill = core.stats.Skill
local I = require('openmw.interfaces')
local nearby = require('openmw.nearby') local nearby = require('openmw.nearby')
local self = require('openmw.self') local self = require('openmw.self')
local types = require('openmw.types') local types = require('openmw.types')
local NPC = types.NPC
local Actor = types.Actor
local ui = require('openmw.ui') local ui = require('openmw.ui')
local cell = nil local cell = nil
@ -37,114 +32,6 @@ local function processAutomaticDoors()
end end
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) local function onUpdate(dt)
if dt <= 0 then if dt <= 0 then
return return
@ -157,13 +44,9 @@ local function onUpdate(dt)
processAutomaticDoors() processAutomaticDoors()
end end
I.SkillProgression.addSkillUsedHandler(skillUsedHandler)
I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler)
return { return {
engineHandlers = { engineHandlers = {
onUpdate = onUpdate, onUpdate = onUpdate,
_onJailTimeServed = jailTimeServed,
}, },
eventHandlers = { eventHandlers = {

View file

@ -1,6 +1,5 @@
local self = require('openmw.self') local self = require('openmw.self')
local I = require('openmw.interfaces') local I = require('openmw.interfaces')
local types = require('openmw.types')
local core = require('openmw.core') local core = require('openmw.core')
local auxUtil = require('openmw_aux.util') local auxUtil = require('openmw_aux.util')
local NPC = require('openmw.types').NPC local NPC = require('openmw.types').NPC
@ -47,40 +46,6 @@ local Skill = core.stats.Skill
local skillUsedHandlers = {} local skillUsedHandlers = {}
local skillLevelUpHandlers = {} 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) local function skillUsed(skillid, options)
if #skillUsedHandlers == 0 then if #skillUsedHandlers == 0 then
-- If there are no handlers, then there won't be any effect, so skip calculations -- 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 end
-- Make a copy so we don't change the caller's table -- 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 -- Compute use value if it was not supplied directly
if not options.skillGain then 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 -- If there are no handlers, then there won't be any effect, so skip calculations
return return
end end
local options = I.SkillProgression.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
auxUtil.callEventHandlers(skillLevelUpHandlers, skillid, source, options) auxUtil.callEventHandlers(skillLevelUpHandlers, skillid, source, options)
end end
@ -181,7 +119,7 @@ return {
interface = { interface = {
--- Interface version --- Interface version
-- @field [parent=#SkillProgression] #number version -- @field [parent=#SkillProgression] #number version
version = 1, version = 2,
--- Add new skill level up handler for this actor. --- Add new skill level up handler for this actor.
-- For load order consistency, handlers should be added in the body if your script. -- For load order consistency, handlers should be added in the body if your script.
@ -269,6 +207,13 @@ return {
-- @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. -- @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, 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} --- @{#SkillLevelUpSource}
-- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES -- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES
SKILL_INCREASE_SOURCES = { 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. --- 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 -- @function [parent=#SkillProgression] getSkillProgressRequirement
-- @param #string skillid The id of the skill to compute skill progress requirement for -- @param #string skillid The id of the skill to compute skill progress requirement for
getSkillProgressRequirement = getSkillProgressRequirement getSkillProgressRequirement = function(skillid) return 1 end
}, },
engineHandlers = { engineHandlers = {
-- Use the interface in these handlers so any overrides will receive the calls. -- 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.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 -- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI