Module:Resource

From bg3.wiki
Jump to navigation Jump to search

This module handles displaying resource costs. It can:

  • Handle multiple resources at once. The input is a comma separated list of resource names.
  • Handle auto-categorisation for actions based on resource consumption via the categories function. This is not done implicitly so page generators must call this additional function.
  • Display in multiple formats suitable for inline usage or for compact table columns.

Parameters

Parameter Meaning
Unnamed parameter A comma separated list of resources with an optional count parameter separated by :

ListTerm | Term, Term
TermResource | Resource:Count
Resource → (see below for valid values)

force plural Boolean flag to force icon names to always use their plural forms. Default is no.
icons only Boolean flag to show only the icon names. Full resource names are visible in hover-over tooltips. Default is no.
show links Boolean flag to make resource names into links to the appropriate pages. Default is no.
icon size Adjust the size of the resource icons. Default is 20 and for inline text, larger icons should not be used.

Resource types

The available resource types are as follows:

List of resources
Resource Name and aliases
Actionsaction
Bonus Actionsbonus, ba, bonus action
Reactionsreaction
Movement Speedmovement, movement m, m
Movement Speed (Half cost)half movement, 1/2 movement
Level 1 Spell Slotsspell1, s1, level1, l1
Level 2 Spell Slotsspell2, s2, level2, l2
Level 3 Spell Slotsspell3, s3, level3, l3
Level 4 Spell Slotsspell4, s4, level4, l4
Level 5 Spell Slotsspell5, s5, level5, l5
Level 6 Spell Slotsspell6, s6, level6, l6
Arcane Arrowsarcarr, aa, arcane arrow
Arcane Recovery Chargesarcrec, ar, arcane recovery
Bardic Inspiration Chargesbi, bardin, bardic inspiration
Bladesong Powerbladesong, bs, bsp, bladesong power
Channel Divinity Chargescd, chadiv, channel divinity
Channel Oath Chargesco, oath, chao, chaoat, channel oath
Cosmic Omenscos, cosmic, omen, cosmic omen
Eyestalk actionses, eyestalk, eyestalk action
Fungal Infestation Chargesfi, fnginf, fungal infestation
Ki Pointski
Lay on Hands Chargesloh, lh, layonh, lay on hands
Luck Pointsluck, lp, lukpnt, luck point
Natural Recovery Chargesnatrec, nr, natural recovery
Rage Chargesrage, rg
Level 3 Shadow Spell Slotsshadowspell3, shadow spell, ss3, shadowlevel3, sl3
Sorcery Pointssp, srcpnt, sorcery, sorcery point
Star Mapsstarmap, sm, star map
Superiority Dicesupdie, sd, superiority die
Tides of Chaos Chargestoc, tides, tides of chaos
Writhing Currentswc, writhing, writhing current
Wild Shape Chargesws, wldshp, wildshape, wild shape
War Priest Chargeswp, warpri, war priest

Examples

Example Markup Renders as
Basic usage
{{#invoke: Resource | main | action}}
 Action
Optional count specified
{{#invoke: Resource | main | ki:3 }}
 3 Ki Points
Multiple resources
{{#invoke: Resource | main | action, bonus, spell1}}
 Action +  Bonus Action +  Level 1 Spell Slot
Movement speed
{{#invoke: Resource | main | bonus, m:3 }}
 Bonus Action +  3 m / 10 ft Movement Speed
Icons only
{{#invoke: Resource | main 
| action, bonus, spell1 
| format = icons only
}}
 Action +  Bonus Action +  Level 1 Spell Slot

local getArgs = require("Module:Arguments").getArgs
local p = {}

-- Non-breaking thin space character
local nbts = " "

-- Table of reaction resources
-- Required values
--   name: Formatted wikitext to display for this reaction
--   icon: Icon file associated with the resource
-- Optional values:
--   plural: If the plural form of the resource name is not just appending "s" to the end, specify it here
--   aliases: Any additional aliases that can be used to refer to this resource
--   category: Category for actions that consume this resource
--   cropping: Crop this percentage from each side of the icon. This is for icons with excessive whitespace
local resources = {
	-- Common resources
	{
		aliases  = {"action"},
		name     = "Action",
		link     = "Actions#Resources",
		icon     = "Action Icon.png",
		cropping = 15,
		category = "Actions"
	},
	{
		aliases  = {"bonus", "ba", "bonus action"},
		name     = "Bonus Action",
		link     = "Actions#Resources",
		icon     = "Bonus Action Icon.png",
		cropping = 15,
		category = "Bonus actions"
	},
	{
		aliases  = {"reaction"},
		name     = "Reaction",
		link     = "Actions#Reactions",
		icon     = "Reaction Icon.png",
		cropping = 15,
		category = "Reactions"
	},
	{
		aliases = {"movement", "movement m", "m"},
		name     = "Movement Speed",
		plural   = "Movement Speed",
		link     = "Resources#Movement speed",
		icon     = "Movement Speed Icon.png",
		cropping = 15,
		category = "Movement-expending actions"
	},
	{
		aliases  = {"half movement", "1/2 movement"},
		name     = "Movement Speed (Half cost)",
		plural   = "Movement Speed (Half cost)",
		link     = "Resources#Movement speed",
		icon     = "Movement Speed Icon.png",
		cropping = 15,
		category = "Movement-expending actions"
	},
	{
		aliases  = {"spell1", "s1", "level1", "l1"},
		name     = "Level 1 Spell Slot",
		link     = "Spells#Spell slots",
		icon     = "Spell Slot Icon.png",
	},
	{
		aliases  = {"spell2", "s2", "level2", "l2"},
		name     = "Level 2 Spell Slot",
		link     = "Spells#Spell slots",
		icon     = "Spell Slot Icon.png",
	},
	{
		aliases  = {"spell3", "s3", "level3", "l3"},
		name     = "Level 3 Spell Slot",
		link     = "Spells#Spell slots",
		icon     = "Spell Slot Icon.png",
	},
	{
		aliases  = {"spell4", "s4", "level4", "l4"},
		name     = "Level 4 Spell Slot",
		link     = "Spells#Spell slots",
		icon     = "Spell Slot Icon.png",
	},
	{
		aliases  = {"spell5", "s5", "level5", "l5"},
		name     = "Level 5 Spell Slot",
		link     = "Spells#Spell slots",
		icon     = "Spell Slot Icon.png",
	},
	{
		aliases  = {"spell6", "s6", "level6", "l6"},
		name     = "Level 6 Spell Slot",
		link     = "Spells#Spell slots",
		icon     = "Spell Slot Icon.png",
	},

	-- Class resources
	{
		aliases  = {"arcarr", "aa", "arcane arrow"},
		name     = "Arcane Arrow",
		link     = "Arcane Archer#Arcane Arrow",
		icon     = "Arcane Arrow Resource Icon.png",
	},
	{
		aliases  = {"arcrec", "ar", "arcane recovery"},
		name     = "Arcane Recovery Charge",
		link     = "Arcane Recovery",
		icon     = "Arcane Recovery Charges Icons.png",
	},
	{
		aliases  = {"bi", "bardin", "bardic inspiration"},
		name     = "Bardic Inspiration Charge",
		link     = "Bard#Level 1",
		icon     = "Bardic Inspiration Resource Icon.png",
		category = "Bardic Inspiration actions"
	},
	{
		aliases  = {"bladesong", "bs", "bsp", "bladesong power"},
		name     = "Bladesong Power",
		plural   = "Bladesong Power",
		link     = "Bladesinging#Level 2",
		icon     = "Bladesong Resource Icon.png",
	},
	{
		aliases  = {"cd", "chadiv", "channel divinity"},
		name     = "Channel Divinity Charge",
		link     = "Channel Divinity",
		icon     = "Channel Divinity Charges Icons.png",
		category = "Channel Divinity actions",
	},
	{
		aliases  = {"co", "oath", "chao", "chaoat", "channel oath"},
		name     = "Channel Oath Charge",
		link     = "Channel Oath",
		icon     = "Channel Oath Icon.png",
		category = "Channel Oath actions"
	},
	{
		aliases  = {"cos", "cosmic", "omen", "cosmic omen"},
		name     = "Cosmic Omen",
		link     = "Circle of the Stars#Level 6",
		icon     = "Cosmic Omen Resource Icon.png",
	},
	{
		aliases  = {"es", "eyestalk", "eyestalk action"},
		name     = "Eyestalk action",
		link     = "Spectator#Combat",
		icon     = "Eyestalk Action Icon.png",
	},
	{
		aliases  = {"fi", "fnginf", "fungal infestation"},
		name     = "Fungal Infestation Charge",
		link     = "Circle of the Spores#Level 6",
		icon     = "Fungal Infestation Charge Icon.png",
	}, 
	{
		aliases  = {"ki"},
		name     = "Ki Point",
		icon     = "Monk Ki Icon.png",
		link     = "Monk#Level 1",
		category = "Ki actions"
	},
	{
		aliases  = {"loh", "lh", "layonh", "lay on hands"},
		name     = "Lay on Hands Charge",
		link     = "Paladin#Level 1",
		icon     = "Lay on Hands Resource Icon.png",
	},
	{
		aliases  = {"luck", "lp", "lukpnt", "luck point"},
		name     = "Luck Point",
		link     = "Lucky",
		icon     = "Luck Point Resource Icon.png",
	},
	{
		aliases  = {"natrec", "nr", "natural recovery"},
		name     = "Natural Recovery Charge",
		link     = "Natural Recovery",
		icon     = "Natural Recovery Charges Icon.png",
	},
	{
		aliases  = {"rage", "rg"},
		name     = "Rage Charge",
		link     = "Barbarian#Level 1",
		icon     = "Rage Charges Icons.png",
		category = "Rage actions"
	},
	{
		aliases  = {"shadowspell3", "shadow spell", "ss3", "shadowlevel3", "sl3"},
		name     = "Level 3 Shadow Spell Slot",
		link     = "Permanent bonuses#Consumed Shadow Weave",
		icon     = "Shadow Spell Slot Icon.png",
	},
	{
		aliases  = {"sp", "srcpnt", "sorcery", "sorcery point"},
		name     = "Sorcery Point",
		link     = "Sorcerer#Level 2",
		icon     = "Sorcery Points Icons.png",
		category = "Sorcery actions"
	},
	{
		aliases  = {"starmap", "sm", "star map"},
		name     = "Star Map",
		link     = "Circle of the Stars#Level 2",
		icon     = "Star Map Resource Icon.png",
	},
	{
		aliases  = {"supdie", "sd", "superiority die"},
		name     = "Superiority Die",
		plural   = "Superiority Dice",
		link     = "Battle Master#Level 3",
		icon     = "Superiority Die d8 Icon.png",
		category = "Combat manoeuvres"
	},
	{
		aliases  = {"toc", "tides", "tides of chaos"},
		name     = "Tides of Chaos Charge",
		link     = "Tides of Chaos",
		icon     = "Tides of Chaos Resource Icon.png",
	},
	{
		aliases  = {"wc", "writhing", "writhing current"},
		name     = "Writhing Current",
		link     = "Swarmkeeper#Level 6",
		icon     = "Writhing Current Resource Icon.png",
	}, 
	{
		aliases  = {"ws", "wldshp", "wildshape", "wild shape"},
		name     = "Wild Shape Charge",
		link     = "Druid#Level 2",
		icon     = "Wild Shape Charges Icon.png",
		category = "Wild Shape actions"
	},
	{
		aliases  = {"wp", "warpri", "war priest"},
		name     = "War Priest Charge",
		link     = "War Domain#Level 1",
		icon     = "War Priest Charges Icon.png",
	},
}

local aliases = {}
local alias_warnings = {}
for idx, resource in ipairs(resources) do
	for _, alias in ipairs(resource.aliases or {}) do
		if aliases[alias] ~= nil then
			table.insert(
				alias_warnings, 
					string.format("Alias \"%s\" defined for \"%s\" and \"%s\".",
						alias,
						resources[aliases[alias]].name,
						resource.name
					)
				)
			end
		aliases[alias] = idx
	end
end

local function flag_is_set(args, flag)
	-- Standardise string by trimming whitespace and converting to lowercase
	local value = string.lower(string.match(args[flag] or "", "^%s*(.-)%s*$"))
	return value == "y" or value == "yes" or value == "1" or value == "t" or value == "true"
end

local function get_resource(name)
	-- Standardise string by trimming whitespace and converting to lowercase
	local name = string.lower(string.match(name, "^%s*(.-)%s*$"))

	return resources[aliases[name]] or {
		name     = "Unknown",
		plural   = "Unknown",
		icon     = "Alert Icon.png",
		category = "Pages with unknown resources"
	}
end

local function resource_icon(resource, args)
	local size = args["icon size"] or 20

	-- Image alt text should be blank unless _only_ the icon is displayed
	-- If this not blank, copy-pasting or TextExtract of something like "costs an {{r|action}"
	-- will give "costs an Action Icon.png action"
	local alt = ""
	if flag_is_set(args, "icons only") then
		alt = resource.name
	end

	if resource.cropping then
		-- Crop the icon. This is used for some icons which have large amounts of whitespace
		return string.format(
			"<span style=\"display: inline-block; height: %dpx; width: %dpx; margin-left: %dpx; margin-top: %dpx\">",
			size,
			size,
			-size * (resource.cropping)/100,
			-size * (resource.cropping)/100
		) .. string.format(
			"[[File:%s|link=|x%dpx|alt=%s|class=bg3wiki-icon]]",
			resource.icon,
			size * (100 + resource.cropping)/100,
			alt,
			resource.name
		) .. "</span>"
	else
		return string.format(
			"[[File:%s|link=|x%dpx|alt=%s|class=bg3wiki-icon]]",
			resource.icon,
			size,
			alt,
			resource.name
		)
	end
end

local function print_resource(resource, count, args)
	local name = resource.name
	local count = count:match("^%s*(.-)%s*$")
	local count_num = tonumber(count)
	if ((count ~= "") and (count_num ~= 1)) or flag_is_set(args, "force plural") then
		name = resource.plural or resource.name .. "s"
	end

	if flag_is_set(args, "show links") and resource.link then
		name = string.format("[[%s|%s]]", resource.link, name)
	end
	
	local amount = ""
	-- Movement has a special format when count is specified. It is displayed is both m and ft
	if count_num ~= nil and resource.name:find("Movement") ~= nil then
		local m = count_num
		if m >= 2 then
			amount = string.format("%.0f%sm / %.0f%sft ", m, nbts, (m * 10)/3, nbts)
		else
			amount = string.format("%.1f%sm / %.0f%sft ", m, nbts, (m * 10)/3, nbts)
		end
	elseif count ~= "" and count_num ~= 1 then
		amount = count .. " "
	end
	
	local icon = resource_icon(resource, args)

	if flag_is_set(args, "icons only") then
		local tooltip = string.format([[<span aria-label="%s">%s</span>]], resource.name, icon)
		if count ~= "" and count_num ~= 1 and resource.name:find("Movement") == nil then
			return count .. " " .. tooltip
		else
			return tooltip
		end
	else
		return icon .. nbts .. amount .. name
	end
end

-- Print the resource list
function p.main(frame)
	local args = getArgs(frame)
	local terms = args[1] or "Unknown"
	local result = ""
	-- Parse comma separated list of resource names
	for term in terms:gmatch("[^,]+") do
		-- Split on ":"" if the optional count is specified
		local sep_idx = term:find(":") or 1000
		local name  = term:sub(0, sep_idx - 1)
		local count = term:sub(sep_idx + 1, -1)

		result = result .. print_resource(get_resource(name), count, args) .. " &plus; "
	end
	result = result:sub(0, -9)
	return result
end

-- Add a page to the relevant categories for actions that consume these resources
function p.categories(frame)
	local args = getArgs(frame)
	local terms = args[1]
	
	if terms == nil or terms == "" then
		return ""
	end
	
	local result = ""
	-- Parse comma separated list of resource names
	for term in terms:gmatch("[^,]+") do
		-- Split on ":"" if the optional count is specified
		local sep_idx = term:find(":") or 0
		local name = term:sub(0, sep_idx - 1)

		local category = get_resource(name).category
		if category then
			result = result .. string.format("[[Category:%s]]\n", category)
		end
	end
	return result
end

-- Print a formatted list of all the resources for documentation pages
function p.resource_table(frame)
	local args = getArgs(frame)
	local result = ""
	for _, warning in ipairs(alias_warnings) do
		result = result .. frame:expandTemplate{
			title = "Warning",
			args = { warning }
		} .. "<br>"
	end
	
	result = result .. [[<table class="wikitable">
	<caption>List of resources</caption>
	<tr>
	<th>Resource</th>
	<th>Name and aliases</th>
	</tr>
	]]
	for _, resource in ipairs(resources) do
		local names = ""
		for _, alias in ipairs(resource.aliases or {}) do
			names = names .. string.format("<code>%s</code>, ", alias)
		end
		names = names:sub(0,-3)
		result = result .. string.format(
			"<tr><td>%s</td><td>%s</td></tr>\n",
			print_resource(resource, "", args),
			names
		)
	end
	result = result .. "</table>"
	return result
end

return p