Module:Damage display
This module renders damage information in a format designed to replicate the in-game view.
End users may invoke this module through the wrapper templates:
- {{Damage display}} - For the full feature set of the module
- {{Damage inline}} - For an inline-only version with simplified syntax and features
Parameters
Parameter | Meaning |
---|---|
format
|
Format of the damage display, either list (default), nosummary to hide the header, or inline for a compact inline display.
|
damage n
|
The damage string in the simple format
Expr → Term + Expr | Term Example: |
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 info
|
Free-form field for adding additional information about a damage instance. For example, "per ray " for Scorching Ray damage, "if the target is a Fiend or Undead " for extra Divine Smite damage, or "delayed " for Melf's Acid Arrow damage.
|
damage n modifier
|
The modifier added to the damage. It may be a specific ability score such as |
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 + finesse mod | damage 1 type = Piercing | damage 2 = 1d6 | damage 2 type = Fire }} | ![]() ![]() 1d6 + 2 + Strength or Dexterity modifier + 1d6 |
Specified ability scores |
{{#invoke: Damage display | main | damage 1 = 1d6 + 2 + finesse mod | damage 1 type = Piercing | damage 2 = 1d6 | damage 2 type = Fire | damage 3 = 2d8 | damage 3 type = Radiant | str = 9 | dex = 17 }} | ![]() ![]() ![]() 1d6 + 5 + 1d6 + 2d8 |
Specified casting ability |
{{#invoke: Damage display | main | damage 1 = 1d10 + spell + spell | damage 1 type = Force | damage 2 = 1d10 + spell + spell | damage 2 type = Force | damage 3 = 1d10 + spell + spell | damage 3 type = Force | wis = 10 | int = 8 | cha = 17 | casting ability = cha }} | ![]() ![]() ![]() 1d10 + 6 + 1d10 + 6 + 1d10 + 6 |
Unspecified weapon |
{{#invoke: Damage display | main | damage 1 = weapon | damage 2 = 1d6 | damage 2 type = Necrotic }} | ![]() Normal weapon damage + 1d6 |
Specified weapon |
{{#invoke: Damage display | main | damage 1 = weapon | damage 2 = 1d6 | damage 2 type = Necrotic | weapon = Spear +1 }} | ![]() ![]() 1d6 + 1 + Strength modifier + 1d6 |
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 }} | ![]() ![]() 1d6 + 4 + 1d6 |
Healing |
{{#invoke: Damage display | main | damage 1 = 1d6 + wis | damage 1 type = Healing | wis = 19 }} | ![]() 1d6 + 4 |
Proficiency bonus |
{{#invoke: Damage display | main | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = prof | damage 2 type = Radiant }} | ![]() 1d12 + 2 |
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 }} | ![]() 1d12 + 2 + 3 |
Info field |
{{#invoke: Damage display | main | damage 1 = 2d4 | damage 1 type = Acid | damage 1 info = Delayed | damage 2 = 2d8 | damage 2 type = Radiant | damage 2 info = If the target is a Fiend or Undead | damage 3 = 2d6 | damage 3 type = Fire | damage 3 info = per ray | damage 4 = 1d6 | damage 4 type = Piercing | damage 4 info = to self }} | ![]() ![]() ![]() ![]() 2d4 (Delayed) + 2d8 (If the target is a Fiend or Undead) + 2d6 (per ray) + 1d6 (to self) |
Freeform damage input |
{{#invoke: Damage display | main | damage 1 = (Sorcerer level)/2 | damage 1 type = Lightning | damage 2 = 5 + 2 x (Cleric level) | damage 2 type = Necrotic }} | (Sorcerer level)/2 + 5 + 2 x (Cleric level) |
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 }} | ![]() ![]() ![]() ![]() ![]() 1d12 + 1d10 + 2d8 + 1d4 + 2d6 |
No dice |
{{#invoke: Damage display | main | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = 1d6 | damage 2 type = Poison | dice size = 0 }} | 1d12 + 2 + 1d6 |
Inline output |
This format can be used inline: {{#invoke: Damage display | main | format = inline | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = 1d6 | damage 2 type = Poison }}. It is simple and compact. | This format can be used inline: 1d12 + 2 + 1d6 . It is simple and compact. |
No summary |
{{#invoke: Damage display | main | format = nosummary | damage 1 = 1d12 + 2 | damage 1 type = Slashing | damage 2 = 1d6 | damage 2 type = Poison }} | ![]() ![]() 1d12 + 2 + 1d6 |
local getArgs = require("Module:Arguments").getArgs
local formatting = require("Module:Damage display/format")
local p = {}
-- 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]]",
proficiency = "[[Proficiency bonus]]"
}
-- Aliases for modifiers since they are not used consistently in every place
local modifier_aliases = {
spellcasting = "spell",
spellcaster = "spell",
casting = "spell",
caster = "spell",
melee = "strength",
ranged = "dexterity",
str = "strength",
dex = "dexterity",
con = "constitution",
wis = "wisdom",
int = "intelligence",
cha = "charisma",
prof = "proficiency",
}
-- These variables will be populated by the parser function
local parsed_data = {
damage = {
dice = {},
instances = {},
min_roll = 0,
max_roll = 0,
uneval_mods = false,
},
healing = {
dice = {},
instances = {},
min_roll = 0,
max_roll = 0,
uneval_mods = false,
},
}
-- Parse and try to evaluate a term containing an ability score modifier
-- The first return value is the value of the modifier it can be evaluated
-- The second return value is the unevaluated modifier in a format ready to be rendered
local function parse_modifier(term, args)
-- Some basic parsing to match strings of the form "<ability> mod" or "<ability> modifier" or "<ability>"
local term = string.lower(term)
local words = {}
for word in string.gmatch(term, "[^ ]+") do
table.insert(words, word)
end
if #words >= 3 then
return nil
end
if words[2] and words[2] ~= "mod" and words[2] ~= "modifier" and words[2] ~= "bonus" then
return nil
end
local modifier_name = modifier_aliases[words[1]] or words[1]
-- Function to calculate a given ability score from the provided information
local calc_modifier = function(args, ability)
return function()
local ability_score = args[ability] or args[string.sub(ability, 1 ,3)]
return ability_score and math.floor((ability_score - 10)/2) or nil
end
end
-- Functions to calculate the modifier of the specific type
local modifiers = {
strength = calc_modifier(args, "strength"),
dexterity = calc_modifier(args, "dexterity"),
constitution = calc_modifier(args, "constitution"),
wisdom = calc_modifier(args, "wisdom"),
intelligence = calc_modifier(args, "intelligence"),
charisma = calc_modifier(args, "charisma"),
proficiency = function()
local level = tonumber(args.level)
if level then
return math.floor((level + 7)/4)
end
end,
finesse = function()
local str = calc_modifier(args, "strength")()
local dex = calc_modifier(args, "dexterity")()
if str and dex then
return math.max(str, dex)
end
end,
spell = function()
local casting_ability = args["casting ability"] or modifier_aliases[args["casting ability"]]
if casting_ability then
return calc_modifier(args, casting_ability)()
end
end
}
-- Return the modifier evaluated or the standardized modifier name if it could not be
if modifiers[modifier_name] then
local modifier = modifiers[modifier_name]()
if modifier then
return modifier, nil
else
return nil, unevaluated_modifiers[modifier_name]
end
end
end
-- Parse a damage term
-- The position of the return value indicates the type of term
-- The first return value is a dice term that should be displayed first
-- The second return value is an integer that should be summed with all other integer terms.
-- It should be displayed after the dice.
-- The third return value is an unevaluated modifier. It should be displayed last.
function parse_term(term, damage_type, args, data)
-- Strip whitespace
local term = string.match(term, "^%s*(.-)%s*$")
-- Try to parse as a dice (e.g. 6d8)
local d_idx = string.find(term, "d")
if d_idx then
local count = tonumber(string.sub(term, 0, string.find(term, "d") - 1))
local dice = tonumber(string.sub(term, string.find(term, "d") + 1, -1))
-- Treat missing first number as 1. E.g. "d8" is the same as "1d8"
if d_idx == 1 then
count = 1
end
if count and dice then
-- Track the dice type for displaying the dice icons
table.insert(data.dice, {
["value"] = "d" .. dice,
["count"] = count,
["type"] = damage_type
})
-- Track the low/high values for the overall damage range preview
data.min_roll = data.min_roll + count
data.max_roll = data.max_roll + count*dice
return term, nil, nil
end
end
-- Try to parse as a flat integer
local i = tonumber(term)
if i then
return nil, i, nil
end
-- Try to parse as a special value
local name, value = parse_modifier(term, args)
if name or value then
return nil, name, value
end
-- Catchall for any other term
return nil, nil, term
end
-- Parse a damage instance in the simple format of
-- Expr = Term + Expr | Term
-- Term = Dice | Integer | Modifier
-- Dice = Integer d Integer
-- Modifier = Ability mod | Ability modifier | Ability | prof | proficiency bonus
-- Ability = Strength | str | Dexterity | dex | ...
--
-- Writes result to the global variable parsed_data
local function damage_parse(args, damage_instance)
local damage = damage_instance["value"]
local damage_type = damage_instance["type"]
-- Determine whether this instance is damage or healing
local data = parsed_data.damage
if string.lower(damage_type or "") == "healing" then
data = parsed_data.healing
end
local parsed = ""
local unevaluated_terms = ""
local bonus = 0
for term in string.gmatch(damage, "[^+]+") do
local dice, value, modifier = parse_term(term, damage_type, args, data)
if dice then parsed = parsed .. " + " .. dice end
if value then bonus = bonus + value end
if modifier then unevaluated_terms = unevaluated_terms .. " + " .. modifier end
end
data.min_roll = data.min_roll + bonus
data.max_roll = data.max_roll + bonus
-- Re-add the updated flat bonus to the damage string
if bonus > 0 then
parsed = parsed .. " + " .. bonus
elseif bonus < 0 then
parsed = parsed .. " - " .. -bonus
end
-- Re-add the unevaluated modifiers to the end of the string
parsed = parsed .. unevaluated_terms
if unevaluated_terms ~= "" then
data.uneval_mods = true
end
-- Strip leading " + "
parsed = string.sub(parsed, 4, -1)
table.insert(data.instances, {
["value"] = parsed,
["type"] = damage_type,
["info"] = damage_instance["info"],
})
end
-- Parse the special "weapon" damage value which involves a cargo query into the
-- weapons table for the specific damage values
local function weapon_parse(args)
local weapon_name = args["weapon"]
if not weapon_name then
table.insert(parsed_data["damage"].instances, { ["value"] = "weapon" })
return
end
-- Fields stored in the weapons table. These are liable to change.
local fields = [[
name,
damage,
damage_type,
extra_damage,
extra_damage_type,
extra_damage_2,
extra_damage_2_type,
melee_or_ranged,
finesse
]]
local query = mw.ext.cargo.query('weapons', fields, {
where = "name=\"" .. weapon_name .. "\""
})
if #query > 0 then
weapon = query[1]
-- Apply the weapon modifier to the main weapon damage
-- TODO: Weapons with special modifiers like Sylvan Scimitar do not
-- have their modifier stored in the table correctly.
local modifier = "melee"
if weapon["melee_or_ranged"] == "ranged" then
modifier = "ranged"
elseif weapon["finesse"] == "1" then
modifier = "finesse"
end
weapon["damage"] = weapon["damage"] .. " + " .. modifier
for i, damage_field in ipairs({"damage", "extra_damage", "extra_damage_2"}) do
if weapon[damage_field] then
damage_parse(args, {
["value"] = weapon[damage_field],
["type"] = weapon[damage_field .. "_type"],
})
end
end
else
table.insert(parsed_data["damage"].instances, { ["value"] = "weapon" })
end
end
function p.main(frame, args)
local args = args or getArgs(frame)
-- Use parent frames arguments if arguments are not supplied when calling {{#invoke}}.
-- This makes it unnecessary to explicitly pass parameters into the function
-- when calling it from a page generator template for instance.
if args["damage"] == nil and args["damage 1"] == nil then
args = getArgs(frame:getParent())
end
-- Alias for damage 1. Omitting the 1 is acceptable.
args["damage 1"] = args["damage 1"] or args["damage"]
args["damage 1 type"] = args["damage 1 type"] or args["damage type"]
args["damage 1 info"] = args["damage 1 info"] or args["damage info"]
local i = 1
while args["damage " .. i] do
local damage = args["damage " .. i]
if damage == "weapon" then
weapon_parse(args)
else
-- Handle deprecated "damage modifier" field
if args["damage " .. i .. " modifier"] then
damage = damage .. " + " .. args["damage " .. i .. " modifier"]
end
damage_parse(args, {
["value"] = damage,
["type"] = args["damage " .. i .. " type"],
["info"] = args["damage " .. i .. " info"],
})
end
i = i + 1
end
if args["format"] == "inline" then
return formatting.damage_inline(frame, args, parsed_data)
else
local result = ""
-- Damage and healing instances are tracked and displayed separately
if #parsed_data.damage.instances > 0 then
result = result .. formatting.damage(frame, args, parsed_data.damage, "Damage")
end
if #parsed_data.healing.instances > 0 then
result = result .. formatting.damage(frame, args, parsed_data.healing, "Healing")
end
return result
end
end
-- Display table of examples. This can be invoked from the debug console with
-- =p.display_examples()
-- to preview changes before saving.
function p.display_examples(frame)
return require("Module:Damage display/examples").display_examples(frame)
end
return p