mirror of https://github.com/OpenMW/openmw.git
Dehardcode skill and level progression
parent
9350222e1a
commit
011d9d6493
@ -0,0 +1,6 @@
|
||||
Interface SkillProgression
|
||||
===================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/scripts_omw_skillhandlers.html
|
||||
|
@ -0,0 +1,283 @@
|
||||
local self = require('openmw.self')
|
||||
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
|
||||
|
||||
---
|
||||
-- Table of valid handler signatures
|
||||
-- @type HandlerSignatures
|
||||
--
|
||||
|
||||
--- Signature of the skillLevelUp handler
|
||||
-- @function [parent=#HandlerSignatures] skillLevelUpHandler
|
||||
-- @param #string skillid The ID of the skill being leveled up
|
||||
-- @param #SkillLevelUpSource source The source of the skill level up
|
||||
-- @param #table params Modifiable skill level up values as a table. 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.
|
||||
|
||||
--- Signature of the skillUsed handler
|
||||
-- @function [parent=#HandlerSignatures] skillUsedHandler
|
||||
-- @param #string skillid The ID of the skill being progressed
|
||||
-- @param #SkillUseType useType The use type the skill progression
|
||||
-- @param #table params Modifiable skill progression value. 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.
|
||||
|
||||
|
||||
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 getSkillGainValue(skillid, useType, scale)
|
||||
local skillRecord = Skill.record(skillid)
|
||||
|
||||
local skillGain = 0
|
||||
if useType == 0 then
|
||||
skillGain = skillRecord.skillGain1
|
||||
elseif useType == 1 then
|
||||
skillGain = skillRecord.skillGain2
|
||||
elseif useType == 2 then
|
||||
skillGain = skillRecord.skillGain3
|
||||
elseif useType == 3 then
|
||||
skillGain = skillRecord.skillGain4
|
||||
end
|
||||
|
||||
if scale ~= nil then skillGain = skillGain * scale end
|
||||
return skillGain
|
||||
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 skillGainUnorm = getSkillGainValue(skillid, useType, scale)
|
||||
local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid)
|
||||
local skillGain = skillGainUnorm / skillProgressRequirementUnorm
|
||||
|
||||
-- Put skill gain in a table so that handlers can modify it
|
||||
local parameters = {
|
||||
skillGain = skillGain,
|
||||
}
|
||||
|
||||
for i = #skillUsedHandlers, 1, -1 do
|
||||
if skillUsedHandlers[i](skillid, useType, parameters) == 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 parameters =
|
||||
{
|
||||
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, parameters) == 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
|
||||
-- @function [parent=#SkillProgression] addSkillLevelUpHandler
|
||||
-- @param #function handler The handler, see #skillLevelUpHandler.
|
||||
addSkillLevelUpHandler = function(handler)
|
||||
skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler
|
||||
end,
|
||||
|
||||
--- Add new skillUsed handler for this actor
|
||||
-- @function [parent=#SkillProgression] addSkillUsedHandler
|
||||
-- @param #function handler The handler.
|
||||
addSkillUsedHandler = function(handler)
|
||||
skillUsedHandlers[#skillUsedHandlers + 1] = handler
|
||||
end,
|
||||
|
||||
--- Register 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,
|
||||
},
|
||||
|
||||
--- Register a skill increase, activating relevant handlers
|
||||
-- @function [parent=#SkillProgression] skillUsed
|
||||
-- @param #string skillid The id of the skill to be increased.
|
||||
-- @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 = { _onSkillUse = skillUsed },
|
||||
}
|
Loading…
Reference in New Issue