mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 11:26:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			269 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			269 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
local self = require('openmw.self')
 | 
						|
local I = require('openmw.interfaces')
 | 
						|
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
 | 
						|
-- @field #number Athletics_SwimOneSecond 0
 | 
						|
 | 
						|
---
 | 
						|
-- 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)
 | 
						|
    local skillRecord = Skill.record(skillid)
 | 
						|
    local skillGainUnorm = skillRecord.skillGain[useType + 1]
 | 
						|
    if scale then skillGainUnorm = skillGainUnorm * scale end
 | 
						|
    local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid)
 | 
						|
    local skillGain = skillGainUnorm / skillProgressRequirementUnorm
 | 
						|
 | 
						|
    -- Put skill gain in a table so that handlers can modify it
 | 
						|
    local options = { 
 | 
						|
        skillGain = skillGain,
 | 
						|
    }
 | 
						|
 | 
						|
    for i = #skillUsedHandlers, 1, -1 do
 | 
						|
        if skillUsedHandlers[i](skillid, useType, options) == false then
 | 
						|
            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
 | 
						|
 | 
						|
    local options = 
 | 
						|
    {
 | 
						|
        skillIncreaseValue = 1,
 | 
						|
        levelUpProgress = levelUpProgress,
 | 
						|
        levelUpAttribute = skillRecord.attribute,
 | 
						|
        levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue,
 | 
						|
        levelUpSpecialization = skillRecord.specialization,
 | 
						|
        levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'),
 | 
						|
    }
 | 
						|
 | 
						|
    for i = #skillLevelUpHandlers, 1, -1 do
 | 
						|
        if skillLevelUpHandlers[i](skillid, source, options) == false then
 | 
						|
            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
 | 
						|
        -- 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.
 | 
						|
        --
 | 
						|
        -- @function [parent=#SkillProgression] addSkillLevelUpHandler
 | 
						|
        -- @param #function handler The handler.
 | 
						|
        addSkillLevelUpHandler = function(handler)
 | 
						|
            skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler
 | 
						|
        end,
 | 
						|
 | 
						|
        --- Add new skillUsed handler for this actor
 | 
						|
        -- 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.
 | 
						|
        --
 | 
						|
        -- @function [parent=#SkillProgression] addSkillUsedHandler
 | 
						|
        -- @param #function handler The handler.
 | 
						|
        addSkillUsedHandler = function(handler)
 | 
						|
            skillUsedHandlers[#skillUsedHandlers + 1] = handler
 | 
						|
        end,
 | 
						|
        
 | 
						|
        --- Trigger a skill use, activating relevant handlers
 | 
						|
        -- @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,
 | 
						|
            Athletics_SwimOneSecond = 0,
 | 
						|
        },
 | 
						|
        
 | 
						|
        --- Trigger a skill level up, activating relevant handlers
 | 
						|
        -- @function [parent=#SkillProgression] skillLevelUp
 | 
						|
        -- @param #string skillid The id of the skill to level up.
 | 
						|
        -- @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',
 | 
						|
        },
 | 
						|
    },
 | 
						|
    engineHandlers = { 
 | 
						|
        -- Use the interface in these handlers so any overrides will receive the calls.
 | 
						|
        _onSkillUse = function (skillid, useType, scale)
 | 
						|
            I.SkillProgression.skillUsed(skillid, useType, scale)
 | 
						|
        end,
 | 
						|
        _onSkillLevelUp = function (skillid, source)
 | 
						|
            I.SkillProgression.skillLevelUp(skillid, source)
 | 
						|
        end,
 | 
						|
    },
 | 
						|
}
 |