2024-01-14 19:33:23 +00:00
local self = require ( ' openmw.self ' )
2024-01-15 20:24:40 +00:00
local I = require ( ' openmw.interfaces ' )
2024-01-14 19:33:23 +00:00
local types = require ( ' openmw.types ' )
local core = require ( ' openmw.core ' )
local NPC = require ( ' openmw.types ' ) . NPC
local Skill = core.stats . Skill
---
-- Table of skill use types defined by morrowind.
-- Each entry corresponds to an index into the available skill gain values
-- of a @{openmw.types#SkillRecord}
-- @type SkillUseType
-- @field #number Armor_HitByOpponent 0
-- @field #number Block_Success 0
-- @field #number Spellcast_Success 0
-- @field #number Weapon_SuccessfulHit 0
-- @field #number Alchemy_CreatePotion 0
-- @field #number Alchemy_UseIngredient 1
-- @field #number Enchant_Recharge 0
-- @field #number Enchant_UseMagicItem 1
-- @field #number Enchant_CreateMagicItem 2
-- @field #number Enchant_CastOnStrike 3
-- @field #number Acrobatics_Jump 0
-- @field #number Acrobatics_Fall 1
-- @field #number Mercantile_Success 0
-- @field #number Mercantile_Bribe 1
-- @field #number Security_DisarmTrap 0
-- @field #number Security_PickLock 1
-- @field #number Sneak_AvoidNotice 0
-- @field #number Sneak_PickPocket 1
-- @field #number Speechcraft_Success 0
-- @field #number Speechcraft_Fail 1
-- @field #number Armorer_Repair 0
-- @field #number Athletics_RunOneSecond 0
2024-02-27 18:28:51 +00:00
-- @field #number Athletics_SwimOneSecond 1
2024-01-14 19:33:23 +00:00
---
-- Table of valid sources for skill increases
-- @type SkillLevelUpSource
-- @field #string Book book
-- @field #string Trainer trainer
-- @field #string Usage usage
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 getSkillProgressRequirementUnorm ( npc , skillid )
local npcRecord = NPC.record ( npc )
local class = NPC.classes . record ( npcRecord.class )
local skillStat = NPC.stats . skills [ skillid ] ( npc )
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 , useType , scale )
if # skillUsedHandlers == 0 then
-- If there are no handlers, then there won't be any effect, so skip calculations
return
end
if useType > 3 or useType < 0 then
print ( ' Error: Unknown useType: ' .. tostring ( useType ) )
return
end
-- Compute skill gain
local skillStat = NPC.stats . skills [ skillid ] ( self )
2024-01-15 19:40:38 +00:00
local skillRecord = Skill.record ( skillid )
local skillGainUnorm = skillRecord.skillGain [ useType + 1 ]
if scale then skillGainUnorm = skillGainUnorm * scale end
2024-01-14 19:33:23 +00:00
local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm ( self , skillid )
local skillGain = skillGainUnorm / skillProgressRequirementUnorm
-- Put skill gain in a table so that handlers can modify it
2024-02-02 19:10:25 +00:00
local options = {
2024-01-14 19:33:23 +00:00
skillGain = skillGain ,
}
for i = # skillUsedHandlers , 1 , - 1 do
2024-02-02 19:10:25 +00:00
if skillUsedHandlers [ i ] ( skillid , useType , options ) == false then
2024-01-14 19:33:23 +00:00
return
end
end
end
local function skillLevelUp ( skillid , source )
if # skillLevelUpHandlers == 0 then
-- 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
2024-02-02 19:10:25 +00:00
local options =
2024-01-14 19:33:23 +00:00
{
skillIncreaseValue = 1 ,
levelUpProgress = levelUpProgress ,
levelUpAttribute = skillRecord.attribute ,
levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue ,
levelUpSpecialization = skillRecord.specialization ,
levelUpSpecializationIncreaseValue = core.getGMST ( ' iLevelupSpecialization ' ) ,
}
for i = # skillLevelUpHandlers , 1 , - 1 do
2024-02-02 19:10:25 +00:00
if skillLevelUpHandlers [ i ] ( skillid , source , options ) == false then
2024-01-14 19:33:23 +00:00
return
end
end
end
return {
interfaceName = ' SkillProgression ' ,
---
-- Allows to extend or override built-in skill progression mechanics.
-- @module SkillProgression
-- @usage local I = require('openmw.interfaces')
--
-- -- Forbid increasing destruction skill past 50
-- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options)
-- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then
-- return false
-- end
-- end)
--
-- -- Scale sneak skill progression based on active invisibility effects
-- I.SkillProgression.addSkillUsedHandler(function(skillid, useType, params)
-- if skillid == 'sneak' and useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then
-- local activeEffects = Actor.activeEffects(self)
-- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100
-- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude
-- visibility = 1 - math.min(1, math.max(0, visibility))
-- local oldSkillGain = params.skillGain
-- params.skillGain = oldSkillGain * visibility
-- end
-- end
--
interface = {
--- Interface version
-- @field [parent=#SkillProgression] #number version
version = 0 ,
--- Add new skill level up handler for this actor
2024-02-02 19:10:25 +00:00
-- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler)
-- will be skipped. Where skillid and source are the parameters passed to @{SkillProgression#skillLevelUp}, and options is
-- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers.
-- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values:
--
-- * `skillIncreaseValue` - The numeric amount of skill levels gained.
-- * `levelUpProgress` - The numeric amount of level up progress gained.
-- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up.
-- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up.
-- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up.
-- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up.
--
2024-01-14 19:33:23 +00:00
-- @function [parent=#SkillProgression] addSkillLevelUpHandler
2024-02-02 19:10:25 +00:00
-- @param #function handler The handler.
2024-01-14 19:33:23 +00:00
addSkillLevelUpHandler = function ( handler )
skillLevelUpHandlers [ # skillLevelUpHandlers + 1 ] = handler
end ,
--- Add new skillUsed handler for this actor
2024-02-02 19:10:25 +00:00
-- If `handler(skillid, useType, options)` returns false, other handlers (including the default skill progress handler)
-- will be skipped. Where skillid and useType are the parameters passed to @{SkillProgression#skillUsed},
-- and options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers.
-- By default contains the single value:
--
-- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level.
--
2024-01-14 19:33:23 +00:00
-- @function [parent=#SkillProgression] addSkillUsedHandler
-- @param #function handler The handler.
addSkillUsedHandler = function ( handler )
skillUsedHandlers [ # skillUsedHandlers + 1 ] = handler
end ,
2024-01-31 22:01:16 +00:00
--- Trigger a skill use, activating relevant handlers
2024-01-14 19:33:23 +00:00
-- @function [parent=#SkillProgression] skillUsed
-- @param #string skillid The if of the skill that was used
-- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType}
-- @param #number scale A number that linearly scales the skill progress received from this use. Defaults to 1.
skillUsed = skillUsed ,
--- @{#SkillUseType}
-- @field [parent=#SkillProgression] #SkillUseType SKILL_USE_TYPES Available skill usage types
SKILL_USE_TYPES = {
-- These are shared by multiple skills
Armor_HitByOpponent = 0 ,
Block_Success = 0 ,
Spellcast_Success = 0 ,
Weapon_SuccessfulHit = 0 ,
-- Skill-specific use types
Alchemy_CreatePotion = 0 ,
Alchemy_UseIngredient = 1 ,
Enchant_Recharge = 0 ,
Enchant_UseMagicItem = 1 ,
Enchant_CreateMagicItem = 2 ,
Enchant_CastOnStrike = 3 ,
Acrobatics_Jump = 0 ,
Acrobatics_Fall = 1 ,
Mercantile_Success = 0 ,
Mercantile_Bribe = 1 , -- Note: This is bugged in vanilla and is not actually in use.
Security_DisarmTrap = 0 ,
Security_PickLock = 1 ,
Sneak_AvoidNotice = 0 ,
Sneak_PickPocket = 1 ,
Speechcraft_Success = 0 ,
Speechcraft_Fail = 1 ,
Armorer_Repair = 0 ,
Athletics_RunOneSecond = 0 ,
2024-02-27 18:28:51 +00:00
Athletics_SwimOneSecond = 1 ,
2024-01-14 19:33:23 +00:00
} ,
2024-01-31 22:01:16 +00:00
--- Trigger a skill level up, activating relevant handlers
-- @function [parent=#SkillProgression] skillLevelUp
-- @param #string skillid The id of the skill to level up.
2024-01-14 19:33:23 +00:00
-- @param #SkillLevelUpSource source The source of the skill increase.
skillLevelUp = skillLevelUp ,
--- @{#SkillLevelUpSource}
-- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES
SKILL_INCREASE_SOURCES = {
Book = ' book ' ,
Usage = ' usage ' ,
Trainer = ' trainer ' ,
} ,
} ,
2024-01-15 20:24:40 +00:00
engineHandlers = {
2024-01-15 20:48:19 +00:00
-- Use the interface in these handlers so any overrides will receive the calls.
2024-01-15 20:24:40 +00:00
_onSkillUse = function ( skillid , useType , scale )
I.SkillProgression . skillUsed ( skillid , useType , scale )
end ,
2024-01-15 20:48:19 +00:00
_onSkillLevelUp = function ( skillid , source )
I.SkillProgression . skillLevelUp ( skillid , source )
end ,
2024-01-15 20:24:40 +00:00
} ,
2024-01-14 19:33:23 +00:00
}