Module:Damage display: Difference between revisions
Jump to navigation
Jump to search
Refactored damage instance parsing in preparation for handling special "weapon" damage value |
mNo edit summary |
||
Line 115: | Line 115: | ||
-- Format the damage values | -- Format the damage values | ||
local function damage_format( | local function damage_format(frame, args) | ||
) | |||
-- Text to insert in place of modifiers whose value could not be evaluated | -- Text to insert in place of modifiers whose value could not be evaluated | ||
local unevaluated_modifiers = { | local unevaluated_modifiers = { | ||
Line 148: | Line 144: | ||
-- Left div element containing the damage dice images | -- Left div element containing the damage dice images | ||
local dice_size = args["dice width"] or 30 | |||
result = result .. damage_dice_format(frame, damage_dice, dice_size) | result = result .. damage_dice_format(frame, damage_dice, dice_size) | ||
Revision as of 18:58, 28 January 2025

This module renders damage information in a format designed to replicate the in-game view.
Parameters
Parameter | Meaning |
---|---|
damage n
|
The damage string in the simple format
Expr → Term + Expr | Term For example, "2d8 + 1d6 + 4". The "prof" term is a special value for Proficiency bonus that is occasionally used. |
damage n type
|
The type of the damage which may be any of the damage types in the game or one of the special values: weapon (for damage type that is inherited from the weapon), Physical (for an unspecified physical damage type), or Healing (for healing which is displayed separately from damage).
|
damage n modifier
|
The modifier added to the damage. It may be a specific ability score such as Strength or Charisma or it may be a special value such as melee , ranged , finesse , or spell .
|
damage n save
|
The saving throw used to avoid or reduce this damage if applicable. |
damage n save dc
|
The DC of the saving throw for this damage instance. |
damage n save effect
|
The effect of a successful saving throw on this damage. Values can be negate or halve .
|
damage n per
|
Specify if the damage is dealt repeatedly for example per turn or per distance moved. |
damage n conditional
|
Specify that this damage requires some special conditions to apply. |
damage n delayed
|
Specify that this damage is delayed rather than applying immediately. |
damage n self
|
Specify if this damage is dealt to the user rather than the target. |
str |
Strength ability score used for evaluating modifiers |
dex |
Dexterity ability score used for evaluating modifiers |
con |
Constitution ability score used for evaluating modifiers |
int |
Intelligence ability score used for evaluating modifiers |
wis |
Wisdom ability score used for evaluating modifiers |
cha |
Charisma ability score used for evaluating modifiers |
casting ability |
The ability score used for casting. Determines how to evaluate the spell special modifier value.
|
weapon |
Specify the weapon used in order to evaluate generic "Normal weapon damage" values. |
dice size |
Specify the size of the dice images. Setting it to 0 removes them entirely. |
level |
Specify the level which is needed to evaluate "Proficiency bonus" damage modifiers. |
Examples
Example | Markup | Renders as |
---|---|---|
Unspecified ability scores | {{#invoke: Damage display | main | damage 1 = 1d6 + 2 | damage 1 type = Piercing | damage 1 modifier = finesse | damage 2 = 1d6 | damage 2 type = Fire }} |
Damage: 4~14
![]() ![]() 1d6 + 2 + Strength or Dexterity modifierPiercing + 1d6Fire |
Specified ability scores | {{#invoke: Damage display | main | damage 1 = 1d6 + 2 | damage 1 type = Piercing | damage 1 modifier = finesse | damage 2 = 1d6 | damage 2 type = Fire | damage 3 = 2d8 | damage 3 type = Radiant | str = 9 | dex = 17 }} | |
Specified casting ability | {{#invoke: Damage display | main | damage 1 = 1d10 | damage 1 type = Force | damage 1 modifier = spell | damage 2 = 1d10 | damage 2 type = Force | damage 2 modifier = spell | damage 3 = 1d10 | damage 3 type = Force | damage 3 modifier = spell | wis = 10 | int = 8 | cha = 17 | casting ability = cha }} | |
Unspecified weapon | {{#invoke: Damage display | main | damage 1 = weapon | damage 2 = 1d6 | damage 2 type = Necrotic }} |
Lua error at line 218: attempt to perform arithmetic on a nil value. |
Specified weapon | {{#invoke: Damage display | main | damage 1 = weapon | damage 2 = 1d6 | damage 2 type = Necrotic | weapon = Spear +1 }} |
Lua error at line 218: attempt to perform arithmetic on a nil value. |
Specified weapon and abilities | {{#invoke: Damage display | main | damage 1 = weapon | damage 2 = 1d6 | damage 2 type = Necrotic | weapon = Spear +1 | str = 17 | dex = 12 }} |
Lua error at line 218: attempt to perform arithmetic on a nil value. |
Big dice | {{#invoke: Damage display | main | damage 1 = 1d12 | damage 1 type = Cold | damage 2 = 1d10 | damage 2 type = Lightning | damage 3 = 2d8 | damage 3 type = Psychic | damage 4 = 1d4 | damage 4 type = Force | damage 5 = 2d6 | damage 5 type = Bludgeoning | dice size = 45 }} |
Damage: 7~54
![]() ![]() ![]() ![]() ![]() |
No dice | {{#invoke: Damage display | main | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = 1d6 | damage 2 type = Poison | dice size = 0 }} | |
Proficiency bonus | {{#invoke: Damage display | main | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = prof | damage 2 type = Radiant }} |
Lua error at line 218: attempt to perform arithmetic on a nil value. |
Proficiency bonus w/ level | {{#invoke: Damage display | main | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = prof | damage 2 type = Radiant | level = 8 }} |
Lua error at line 218: attempt to perform arithmetic on a nil value. |
Saving throw | {{#invoke: Damage display | main | damage 1 = 8d6 | damage 1 type = Fire | damage 1 save = dex | damage 1 save dc = 16 | damage 1 save effect = halve }} |
Damage: 8~48
![]() 8d6Fire |
Miscellaneous properties | {{#invoke: Damage display | main | damage 1 = 1d6 | damage 1 type = Fire | damage 1 per = turn | damage 2 = 2d4 | damage 2 type = Acid | damage 2 delayed = yes | damage 3 = 1d6 | damage 3 type = Piercing | damage 3 self = yes | damage 4 = 1d10 | damage 4 type = Poison | damage 4 conditional = yes }} | |
Healing | {{#invoke: Damage display | main | damage 1 = 1d6 | damage 1 type = Healing | damage 1 modifier = Wisdom | wis = 19 }} |
Damage: 5~10
![]() 1d6 + 4Healing |
local getArgs = require("Module:Arguments").getArgs
local p = {}
-- These variables will be populated by the parser function
local damage_dice = {} -- List of damage dice images to render
local damage_instances = {} -- List of damage instances to render
local min_roll = 0 -- Value to show as the upper bound of damage
local max_roll = 0 -- Value to show as the upper bound of damage
-- Given a modifier type such as "melee" or "caster", evaluate the specific
-- value based on provided ability scores
local function evaluate_modifer(modifier_type, args)
if not modifier_type then
return nil
end
local modifiers = {}
local evaluated_modifier = nil
for _, ability in ipairs({"str", "dex", "con", "wis", "int", "cha"}) do
modifiers[ability] = args[ability] and math.floor((args[ability] - 10)/2) or nil
end
-- Handle melee, ranged, and finesse special modifier values
if modifier_type == "melee" and modifiers["str"] then
evaluated_modifier = modifiers["str"]
elseif modifier_type == "ranged" and modifiers["dex"] then
evaluated_modifier = modifiers["dex"]
elseif modifier_type == "finesse" and modifiers["dex"] and modifiers["str"] then
if modifiers["dex"] > modifiers["str"] then
evaluated_modifier = modifiers["dex"]
else
evaluated_modifier = modifiers["str"]
end
end
-- Handle spell special modifier value
if modifier_type == "spell" and args["casting ability"] then
evaluated_modifier = modifiers[args["casting ability"]]
end
-- Handle case where modifier is a specific ability score
local modifier_abbr = string.sub(string.lower(modifier_type), 1, 3)
if modifiers[modifier_abbr] then
evaluated_modifier = modifiers[modifier_abbr]
end
return evaluated_modifier
end
-- Render the scattered damage dice to replicate how it looks in-game
local function damage_dice_format(frame, damage_dice, width)
local n_dice = #damage_dice
-- Determine width of overall element which is dependent on number of dice
local elem_width = width
if n_dice >= 2 then
elem_width = elem_width * 1.4
end
-- Determine padding which is dependent on number of dice
local left_padding = 0
if n_dice >= 3 then
left_padding = width * 0.4
end
local top_padding = 0
if n_dice >= 2 and n_dice < 4 then
top_padding = width * 0.3
elseif n_dice >= 4 then
top_padding = width * 0.7
end
local element = string.format([[<span style="
display: block;
position: relative;
width: %s;
height: %s;
padding-left: %s;
padding-top: %s;
margin-right: 10px;
">]],
elem_width .. "px",
width .. "px",
left_padding .. "px",
top_padding .. "px"
)
-- Translation and rotation to position each dice in a way that replicates
-- the in-game damage preview
local image_transform = {
[1] = "translate( 0%, 0%)",
[2] = "translate( 40%, -30%) rotate(20deg)",
[3] = "translate(-35%, -25%) rotate(40deg)",
[4] = "translate( 40%, -70%) rotate(25deg)",
[5] = "translate(-40%, -68%) rotate(40deg)",
}
for i, dice in ipairs(damage_dice) do
if i > #image_transform then
break
end
element = element .. string.format(
"<span style=\"z-index: %d; position: absolute; transform: %s\">",
n_dice - i,
image_transform[i]
)
element = element .. string.format(
"[[File:%s %s.png|link= |x%s]]</span>",
dice["value"],
dice["type"],
width .. "px"
)
end
return element .. "</span>"
end
-- Format the damage values
local function damage_format(frame, args)
-- Text to insert in place of modifiers whose value could not be evaluated
local unevaluated_modifiers = {
melee = "[[Strength#Strength_modifier_chart|Strength modifier]]",
ranged = "[[Dexterity#Dexterity_modifier_chart|Dexterity modifier]]",
finesse = "[[Finesse|Strength or Dexterity modifier]]",
spell = "[[Spells#Spellcasting_ability|Spellcasting modifier]]",
strength = "[[Strength#Strength_modifier_chart|Strength modifier]]",
dexterity = "[[Dexterity#Dexterity_modifier_chart|Dexterity modifier]]",
constitution = "[[Constitution#Constitution_modifier_chart|Constitution modifier]]",
wisdom = "[[Wisdom#Wisdom_modifier_chart|Wisdom modifier]]",
intelligence = "[[Intelligence#Intelligence_modifier_chart|Intelligence modifier]]",
charisma = "[[Charisma#Charisma_modifier_chart|Charisma modifier]]",
}
-- Outermost div container for the entire element
local result = "<div class=\"bg3wiki-info-blob\" style=\"font-family: unset\">"
-- Damage range preview
result = result .. "<b>Damage: " .. min_roll .. "~" .. max_roll .. "</b>"
-- Flexbox that holds the dice images on the left and damage values on the right
result = result .. [[<div style="
display: flex;
align-items: center;
width: fit-content;
">]]
-- Left div element containing the damage dice images
local dice_size = args["dice width"] or 30
result = result .. damage_dice_format(frame, damage_dice, dice_size)
-- Right div element containing the damage instance list
result = result .. "<div>"
for i, damage in ipairs(damage_instances) do
local value = damage["value"]
if damage["modifier"] and damage["modifier"] ~= "" then
-- Damage instance has an unevaluated modifier
local modifier = string.lower(damage["modifier"])
if unevaluated_modifiers[modifier] then
value = value .. " + " .. unevaluated_modifiers[modifier]
else
-- Copy the modifier field verbatim as a fallback
value = value .. " + " .. damage["modifier"]
end
end
result = result .. "<div>"
if i > 1 then
result = result .. " + "
end
result = result .. frame:expandTemplate{
title = "DamageColor",
args = { damage["type"], value }
} .. frame:expandTemplate{
title = "DamageType",
args = { damage["type"] }
} .. "</div>"
end
result = result .. "</div>" -- End right flexbox element
result = result .. "</div>" -- End flexbox
result = result .. "</div>" -- End outermost div
return result
end
-- Parse a damage instance
-- Writes result to the global variables damage_instances, damage_dice, min_roll, and max_roll
local function damage_parse(args, damage, damage_type, damage_modifier)
local evaluated_modifier = evaluate_modifer(damage_modifier, args)
if evaluated_modifier or not damage_modifier then
damage_modifier = ""
end
local evaluated_damage = ""
local flat_bonus = 0
-- Parse the damage string which (for now) assumes a very simple format of
-- Expr = Term + Expr | Term
-- Term = Dice | Integer
-- Dice = Integer d Integer
for term in string.gmatch(damage, "[^+%s]+") do
if string.find(term, "d") then
-- Dice term
local count = string.sub(term, 0, string.find(term, "d") - 1)
local dice = string.sub(term, string.find(term, "d") + 1, -1)
-- Track damage dice for when we render the dice image
table.insert(damage_dice, {
["value"] = "d" .. dice,
["count"] = count,
["type"] = damage_type
})
evaluated_damage = evaluated_damage .. " + " .. term
-- Track the low/high values for the overall damage range preview
min_roll = min_roll + count
max_roll = max_roll + count*dice
else
-- Constant term
flat_bonus = flat_bonus + tonumber(term)
end
end
-- If enough information is provided to assign a numerical value to the
-- modifier, combine it with other flat bonuses
if evaluated_modifier then
flat_bonus = flat_bonus + evaluated_modifier
end
min_roll = min_roll + flat_bonus
max_roll = max_roll + flat_bonus
-- Re-add the updated flat bonus to the damage string
if flat_bonus > 0 then
evaluated_damage = evaluated_damage .. " + " .. flat_bonus
elseif flat_bonus < 0 then
evaluated_damage = evaluated_damage .. " - " .. -flat_bonus
end
-- Strip leading " + "
evaluated_damage = string.sub(evaluated_damage, 4, -1)
table.insert(damage_instances, {
["value"] = evaluated_damage,
["type"] = damage_type,
["modifier"] = damage_modifier
})
end
function p.main(frame)
local args = getArgs(frame)
local i = 1
while args["damage " .. i] do
local damage = args["damage " .. i]
local damage_type = args["damage " .. i .. " type"]
local damage_modifier = args["damage " .. i .. " modifier"]
damage_parse(args, damage, damage_type, damage_modifier)
i = i + 1
end
return damage_format(frame, args)
end
return p