Skip to content

Commit

Permalink
styling
Browse files Browse the repository at this point in the history
  • Loading branch information
bmos committed Feb 24, 2024
1 parent f5a681e commit 4f9acf3
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 117 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -4,7 +4,7 @@
This extension automates changes to hitpoints based on an ability score.

# Compatibility and Instructions
This extension has been tested with [FantasyGrounds Unity](https://www.fantasygrounds.com/home/FantasyGroundsUnity.php) v4.4.9 (2023-12-18).
This extension has been tested with [FantasyGrounds Unity](https://www.fantasygrounds.com/home/FantasyGroundsUnity.php) v4.5.0 (2024-02-21).

It is designed and written for use with the Pathfinder 1st edition ruleset (PFRPG).

Expand Down
4 changes: 3 additions & 1 deletion campaign/scripts/npc_recalculate.lua
Expand Up @@ -5,7 +5,9 @@
-- luacheck: globals onValueChanged

function onValueChanged()
if super and super.onValueChanged then super.onValueChanged() end
if super and super.onValueChanged then
super.onValueChanged()
end
local rActor = ActorManager.resolveActor(window.getDatabaseNode())
NPCLiveHP.setHpTotal(rActor)
end
4 changes: 3 additions & 1 deletion campaign/scripts/pc_recalculate.lua
Expand Up @@ -5,7 +5,9 @@
-- luacheck: globals onValueChanged

function onValueChanged()
if super and super.onValueChanged then super.onValueChanged() end
if super and super.onValueChanged then
super.onValueChanged()
end
local rActor = ActorManager.resolveActor(window.getDatabaseNode())
PCLiveHP.setHpTotal(rActor)
end
6 changes: 4 additions & 2 deletions effect_builder/effects/scripts/MHP.lua
@@ -1,7 +1,9 @@
-- luacheck: globals createEffectString parentcontrol number_value effect_bonus_type
function createEffectString()
local effectString = parentcontrol.window.effect.getStringValue() .. ': ' .. number_value.getStringValue()
if not effect_bonus_type.isEmpty() then effectString = effectString .. ' ' .. effect_bonus_type.getValue() end
local effectString = parentcontrol.window.effect.getStringValue() .. ": " .. number_value.getStringValue()
if not effect_bonus_type.isEmpty() then
effectString = effectString .. " " .. effect_bonus_type.getValue()
end

return effectString
end
24 changes: 14 additions & 10 deletions scripts/livehitpoints.lua
Expand Up @@ -8,29 +8,33 @@
--- This function returns the change in maximum hitpoints from effects.
-- It checks for a hitpoint changes from "MHP: n" and reduces that by 5 for each negative level.
function getEffectHp(rActor)
local nNegLvlHP = (EffectManager35EDS.getEffectsBonus(rActor, 'NLVL', true) * 5) or 0
local nMhp = EffectManager35EDS.getEffectsBonus(rActor, { 'MHP' }, true) or 0
local nNegLvlHP = (EffectManager35EDS.getEffectsBonus(rActor, "NLVL", true) * 5) or 0
local nMhp = EffectManager35EDS.getEffectsBonus(rActor, { "MHP" }, true) or 0
return nMhp - nNegLvlHP
end

function calculateHp(nodeActor, rActor, nAbilityBonus, nFeatBonus)
if not nodeActor or not rActor or not nAbilityBonus or not nFeatBonus then return nil end
if not nodeActor or not rActor or not nAbilityBonus or not nFeatBonus then
return nil
end

local nRolledHp = DB.getValue(nodeActor, 'livehp.rolled', 0)
local nMiscHp = DB.getValue(nodeActor, 'livehp.misc', 0)
local nRolledHp = DB.getValue(nodeActor, "livehp.rolled", 0)
local nMiscHp = DB.getValue(nodeActor, "livehp.misc", 0)

local nEffectHp = getEffectHp(rActor) or 0
local nTotalHp = nRolledHp + nAbilityBonus + nFeatBonus + nEffectHp + nMiscHp

DB.setValue(nodeActor, 'livehp.ability', 'number', nAbilityBonus)
DB.setValue(nodeActor, 'livehp.feats', 'number', nFeatBonus)
DB.setValue(nodeActor, 'livehp.effects', 'number', nEffectHp)
DB.setValue(nodeActor, 'livehp.total', 'number', nTotalHp)
DB.setValue(nodeActor, "livehp.ability", "number", nAbilityBonus)
DB.setValue(nodeActor, "livehp.feats", "number", nFeatBonus)
DB.setValue(nodeActor, "livehp.effects", "number", nEffectHp)
DB.setValue(nodeActor, "livehp.total", "number", nTotalHp)

return nTotalHp
end

--- This function checks whether an effect should trigger recalculation.
-- It does this by checking the effect text for a series of three letters followed by a colon (as used in bonuses like CON: 4).
-- luacheck: globals checkEffectRelevance
function checkEffectRelevance(nodeEffect) return string.find(DB.getValue(nodeEffect, 'label', ''), '%a%a%a:') ~= nil end
function checkEffectRelevance(nodeEffect)
return string.find(DB.getValue(nodeEffect, "label", ""), "%a%a%a:") ~= nil
end
146 changes: 88 additions & 58 deletions scripts/livehitpoints_npc.lua
Expand Up @@ -7,18 +7,22 @@
--- This function checks NPCs for feats, traits, and/or special abilities.
-- luacheck: no unused args
local function hasSpecialAbility(nodeActor, sSearchString, bFeat, bTrait, bSpecialAbility)
if not nodeActor then return false end
if not nodeActor then
return false
end

local sLowerSpecAbil = string.lower(sSearchString)
local sSpecialQualities = string.lower(DB.getValue(nodeActor, '.specialqualities', ''))
local sSpecAtks = string.lower(DB.getValue(nodeActor, '.specialattacks', ''))
local sFeats = string.lower(DB.getValue(nodeActor, '.feats', ''))
local sSpecialQualities = string.lower(DB.getValue(nodeActor, ".specialqualities", ""))
local sSpecAtks = string.lower(DB.getValue(nodeActor, ".specialattacks", ""))
local sFeats = string.lower(DB.getValue(nodeActor, ".feats", ""))

if bFeat and sFeats:match(sLowerSpecAbil, 1) then
local nRank = tonumber(sFeats:match(sLowerSpecAbil .. ' (%d+)', 1))
local nRank = tonumber(sFeats:match(sLowerSpecAbil .. " (%d+)", 1))
return true, (nRank or 1)
elseif bSpecialAbility and (sSpecAtks:match(sLowerSpecAbil, 1) or sSpecialQualities:match(sLowerSpecAbil, 1)) then
local nRank = tonumber(sSpecAtks:match(sLowerSpecAbil .. ' (%d+)', 1) or sSpecialQualities:match(sLowerSpecAbil .. ' (%d+)', 1))
local nRank = tonumber(
sSpecAtks:match(sLowerSpecAbil .. " (%d+)", 1) or sSpecialQualities:match(sLowerSpecAbil .. " (%d+)", 1)
)
return true, (nRank or 1)
end

Expand All @@ -28,22 +32,24 @@ end
--- This function reports if the HD information is entered incorrectly.
-- It alerts the user and suggests that they report it on the bug report thread.
local function reportHdErrors(nodeNPC, sHd)
local sNpcName = DB.getValue(nodeNPC, 'name', '')
local sHdErrorEnd = sHd:find('planar', 1) or sHd:find('profane', 1) or sHd:find('sacred', 1)
if not sHdErrorEnd or DB.getValue(nodeNPC, 'erroralerted') == 1 or sNpcName == '' then return end
local sNpcName = DB.getValue(nodeNPC, "name", "")
local sHdErrorEnd = sHd:find("planar", 1) or sHd:find("profane", 1) or sHd:find("sacred", 1)
if not sHdErrorEnd or DB.getValue(nodeNPC, "erroralerted") == 1 or sNpcName == "" then
return
end

if DataCommon.isPFRPG() then
ChatManager.SystemMessage(string.format(Interface.getString('npc_hd_error_pf1e'), sNpcName))
ChatManager.SystemMessage(string.format(Interface.getString("npc_hd_error_pf1e"), sNpcName))
else
ChatManager.SystemMessage(string.format(Interface.getString('npc_hd_error_generic'), sNpcName))
ChatManager.SystemMessage(string.format(Interface.getString("npc_hd_error_generic"), sNpcName))
end
DB.setValue(nodeNPC, 'erroralerted', 'number', 1)
DB.setValue(nodeNPC, "erroralerted", "number", 1)
end

--- This function finds the total number of HD for the NPC.
-- luacheck: globals processHd
function processHd(nodeNPC)
local sHDField = DB.getValue(nodeNPC, "hd", ""):gsub('%d+%s-HD;', '')
local sHDField = DB.getValue(nodeNPC, "hd", ""):gsub("%d+%s-HD;", "")
local nHDFieldSemiColon = sHDField:find(";")
local sHd
if nHDFieldSemiColon then
Expand All @@ -54,25 +60,29 @@ function processHd(nodeNPC)

reportHdErrors(nodeNPC, sHd)

sHd = sHd .. '+' -- ending plus
sHd = sHd .. "+" -- ending plus
local tHd = {} -- table to collect fields
local fieldstart = 1
repeat
local nexti = string.find(sHd, '+', fieldstart)
local nexti = string.find(sHd, "+", fieldstart)
table.insert(tHd, string.sub(sHd, fieldstart, nexti - 1))
fieldstart = nexti + 1
until fieldstart > string.len(sHd)

local nAbilHp, nHdCount = 0, 0
if not tHd[1] or (tHd[1] == '') then return nAbilHp, nHdCount end
if not tHd[1] or (tHd[1] == "") then
return nAbilHp, nHdCount
end

for _, v in ipairs(tHd) do
if string.find(v, 'd', 1) then
local nHdEndPos = string.find(v, 'd', 1)
if string.find(v, "d", 1) then
local nHdEndPos = string.find(v, "d", 1)
local nHd = tonumber(string.sub(v, 1, nHdEndPos - 1))
if nHd then nHdCount = nHdCount + nHd end
elseif string.match(v, '%d+') then
nAbilHp = nAbilHp + tonumber(string.match(v, '(%d+)'))
if nHd then
nHdCount = nHdCount + nHd
end
elseif string.match(v, "%d+") then
nAbilHp = nAbilHp + tonumber(string.match(v, "(%d+)"))
end
end

Expand All @@ -82,29 +92,35 @@ end
local function getFeatBonusHp(nodeNPC, nLevel)
local nFeatBonus = 0
if DataCommon.isPFRPG() then
if hasSpecialAbility(nodeNPC, 'Toughness %(Mythic%)', true) then
if hasSpecialAbility(nodeNPC, "Toughness %(Mythic%)", true) then
nFeatBonus = nFeatBonus + (math.max(nLevel, 3)) * 2
elseif hasSpecialAbility(nodeNPC, 'Toughness', true) then
elseif hasSpecialAbility(nodeNPC, "Toughness", true) then
nFeatBonus = nFeatBonus + math.max(nLevel, 3)
end
else
if hasSpecialAbility(nodeNPC, 'Toughness', true) then nFeatBonus = nFeatBonus + 3 end
if hasSpecialAbility(nodeNPC, 'Improved Toughness', true) then nFeatBonus = nFeatBonus + nLevel end
if hasSpecialAbility(nodeNPC, "Toughness", true) then
nFeatBonus = nFeatBonus + 3
end
if hasSpecialAbility(nodeNPC, "Improved Toughness", true) then
nFeatBonus = nFeatBonus + nLevel
end
end
return nFeatBonus
end

local function getRolled(nodeNPC)
local nRolled = DB.getValue(nodeNPC, 'livehp.rolled')
local nRolled = DB.getValue(nodeNPC, "livehp.rolled")

local sHD = DB.getValue(nodeNPC, 'hd', ''):gsub('%d+%s-HD%;', ''):gsub(';.+', ''):gsub('[+-]%s*%d+', '')
local sHD = DB.getValue(nodeNPC, "hd", ""):gsub("%d+%s-HD%;", ""):gsub(";.+", ""):gsub("[+-]%s*%d+", "")
sHD = StringManager.trim(sHD)
if sHD == '' then return nRolled end
if sHD == "" then
return nRolled
end

local sOptHRNH = OptionsManager.getOption('HRNH')
if sOptHRNH == 'max' then
local sOptHRNH = OptionsManager.getOption("HRNH")
if sOptHRNH == "max" then
nRolled = DiceManager.evalDiceString(sHD, true, true)
elseif sOptHRNH == 'random' then
elseif sOptHRNH == "random" then
nRolled = math.max(DiceManager.evalDiceString(sHD, true), 1)
end

Expand All @@ -114,20 +130,22 @@ end
local function guessAbility(nodeNPC)
local nAbilModOverride = nil

local sAbility = DB.getValue(nodeNPC, 'livehp.abilitycycler', '')
if sAbility ~= '' then return sAbility, nAbilModOverride end
local sAbility = DB.getValue(nodeNPC, "livehp.abilitycycler", "")
if sAbility ~= "" then
return sAbility, nAbilModOverride
end

local sType = string.lower(DB.getValue(nodeNPC, 'type', ''))
if sType:match('undead') and DataCommon.isPFRPG() then
sAbility = 'charisma'
DB.setValue(nodeNPC, 'livehp.abilitycycler', 'string', sAbility)
elseif sType:match('construct') and DataCommon.isPFRPG() then
local sType = string.lower(DB.getValue(nodeNPC, "type", ""))
if sType:match("undead") and DataCommon.isPFRPG() then
sAbility = "charisma"
DB.setValue(nodeNPC, "livehp.abilitycycler", "string", sAbility)
elseif sType:match("construct") and DataCommon.isPFRPG() then
nAbilModOverride = 0
elseif sType ~= '' then
sAbility = 'constitution'
DB.setValue(nodeNPC, 'livehp.abilitycycler', 'string', sAbility)
elseif sType ~= "" then
sAbility = "constitution"
DB.setValue(nodeNPC, "livehp.abilitycycler", "string", sAbility)
else
sAbility = 'constitution'
sAbility = "constitution"
end

return sAbility, nAbilModOverride
Expand All @@ -139,19 +157,21 @@ local function getAbilityBonusUsed(rActor, nLevel)
local sAbility, nAbilModOverride = guessAbility(nodeNPC)
local nAbilityMod = ActorManager35E.getAbilityBonus(rActor, sAbility)
local nEffectBonus = ActorManager35E.getAbilityEffectsBonus(rActor, sAbility)
if nAbilModOverride then nAbilityMod = nAbilModOverride end
if nAbilModOverride then
nAbilityMod = nAbilModOverride
end

return ((nAbilityMod + nEffectBonus) * nLevel) or 0
end

local function upgradeNpc(rActor, nAbil, nCalcAbil, nLevel)
local nodeNPC = ActorManager.getCreatureNode(rActor)

local nRolledHp = DB.getValue(nodeNPC, 'hp', 0) - nAbil
DB.setValue(nodeNPC, 'livehp.rolled', 'number', nRolledHp)
local nRolledHp = DB.getValue(nodeNPC, "hp", 0) - nAbil
DB.setValue(nodeNPC, "livehp.rolled", "number", nRolledHp)

local nMiscMod = nAbil - nCalcAbil - getFeatBonusHp(nodeNPC, nLevel)
DB.setValue(nodeNPC, 'livehp.misc', 'number', nMiscMod)
DB.setValue(nodeNPC, "livehp.misc", "number", nMiscMod)
end

--
Expand All @@ -164,13 +184,17 @@ function setHpTotal(rActor, bOnAdd)
local nAbil, nLevel = processHd(nodeNPC)
local nCalcAbil = getAbilityBonusUsed(rActor, nLevel)

if DB.getValue(nodeNPC, 'livehp.total', 0) == 0 then upgradeNpc(rActor, nAbil, nCalcAbil, nLevel) end
if DB.getValue(nodeNPC, "livehp.total", 0) == 0 then
upgradeNpc(rActor, nAbil, nCalcAbil, nLevel)
end

-- reroll rolled hp if adding npc to combat
if bOnAdd then DB.setValue(nodeNPC, 'livehp.rolled', 'number', getRolled(nodeNPC)) end
if bOnAdd then
DB.setValue(nodeNPC, "livehp.rolled", "number", getRolled(nodeNPC))
end

local nTotalHp = LiveHP.calculateHp(nodeNPC, rActor, nCalcAbil, getFeatBonusHp(nodeNPC, nLevel))
DB.setValue(nodeNPC, 'hp', 'number', nTotalHp)
DB.setValue(nodeNPC, "hp", "number", nTotalHp)
end

--
Expand All @@ -184,7 +208,7 @@ local addNPC_old -- placeholder for original addNPC function
local function addNPC_new(tCustom, ...)
addNPC_old(tCustom, ...) -- call original function

setHpTotal(ActorManager.resolveActor(tCustom['nodeCT']), true)
setHpTotal(ActorManager.resolveActor(tCustom["nodeCT"]), true)
end

--
Expand All @@ -195,15 +219,19 @@ end
-- First, it makes sure the triggering actor is not a PC and that the effect is relevant to this extension.
-- Then, it calls the calculateHp function in LiveHP and provides it with nodeActor and rActor.
local function onEffectChanged(node)
local rActor = ActorManager.resolveActor(DB.getChild(node, '....'))
if not ActorManager.isPC(rActor) and LiveHP.checkEffectRelevance(DB.getChild(node, '..')) then setHpTotal(rActor) end
local rActor = ActorManager.resolveActor(DB.getChild(node, "...."))
if not ActorManager.isPC(rActor) and LiveHP.checkEffectRelevance(DB.getChild(node, "..")) then
setHpTotal(rActor)
end
end

--- This function is called when effects are removed.
-- It calls the calculateHp function in LiveHP and provides it with nodeActor and rActor.
local function onEffectRemoved(node)
local rActor = ActorManager.resolveActor(DB.getChild(node, '..'))
if not ActorManager.isPC(rActor) then setHpTotal(rActor) end
local rActor = ActorManager.resolveActor(DB.getChild(node, ".."))
if not ActorManager.isPC(rActor) then
setHpTotal(rActor)
end
end

--- This function watches for changes in the database and triggers various functions.
Expand All @@ -212,8 +240,10 @@ function onInit()
addNPC_old = CombatRecordManager.addNPC
CombatRecordManager.addNPC = addNPC_new

if not Session.IsHost then return end
DB.addHandler(DB.getPath(CombatManager.CT_COMBATANT_PATH .. '.effects.*.label'), 'onUpdate', onEffectChanged)
DB.addHandler(DB.getPath(CombatManager.CT_COMBATANT_PATH .. '.effects.*.isactive'), 'onUpdate', onEffectChanged)
DB.addHandler(DB.getPath(CombatManager.CT_COMBATANT_PATH .. '.effects'), 'onChildDeleted', onEffectRemoved)
if not Session.IsHost then
return
end
DB.addHandler(DB.getPath(CombatManager.CT_COMBATANT_PATH .. ".effects.*.label"), "onUpdate", onEffectChanged)
DB.addHandler(DB.getPath(CombatManager.CT_COMBATANT_PATH .. ".effects.*.isactive"), "onUpdate", onEffectChanged)
DB.addHandler(DB.getPath(CombatManager.CT_COMBATANT_PATH .. ".effects"), "onChildDeleted", onEffectRemoved)
end

0 comments on commit 4f9acf3

Please sign in to comment.