Module:Damage display/format

From bg3.wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Damage display/format/doc

local formatting = {}

-- Special damage values, mostly a legacy of {{Damage info}}
local special_values = {
	["weapon"] = function(frame)
		return "Normal weapon damage"
	end,
	["half weapon"] = function(frame) 
		return "1/2 Normal weapon damage"
	end,
	["superiority die"] = function(frame)
		return frame:expandTemplate{
			title = "SmallIcon",
			args = { "Superiority Die d8 Icon.png" },
		} .. "[[Battlemaster#Superiority_dice|Superiority Die]]"
	end,
	["unarmed"] = function(frame)
		return frame:expandTemplate{
			title = "DamageColor",
			args = { "Bludgeoning", "Unarmed" }
		} .. frame:expandTemplate{
			title = "DamageType",
			args = { "Bludgeoning" }
		}
	end,
}

-- Translation and rotation to position each dice image in a way that replicates
-- the in-game damage preview
local dice_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)",
}

-- Render the scattered damage dice to replicate how it looks in-game
function formatting.damage_dice(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 = mw.html.create("span")
		:css("display",     "block")
		:css("position",    "relative")
		:css("width",       elem_width .. "px")
		:css("height",      width .. "px")
		:css("margin-left", left_padding .. "px")
		:css("padding-top", top_padding .. "px")

	for i, dice in ipairs(damage_dice) do
		if i > #dice_image_transform then
			break
		end
		element:tag("span")
			:css("z-index", n_dice - i)
			:css("position", "absolute")
			:css("transform", dice_image_transform[i])
			:wikitext(string.format(
				"[[File:%s %s.png|link=|alt=|x%s]]",
				dice["value"],
				dice["type"],
				width .. "px"
			))
	end
	return element
end

-- Format the damage values in a list format
function formatting.damage(frame, args, data, header)
	-- Flexbox that holds the dice images on the left and damage values on the right
	local element = mw.html.create("div")
		:css("display",     "flex")
		:css("align-items", "center")
		:css("width",       "fit-content")

	-- Left div element containing the damage dice images
	local dice_size = tonumber(args["dice size"] or args["dice width"] or "30")
	if dice_size > 0 and #data.dice  > 0 then
		element:node(formatting.damage_dice(frame, data.dice, dice_size))
	end

	-- Right div element containing the damage instance list
	local damage_div = element:tag("div")
	for i, damage in ipairs(data.instances) do
		local damage_text = ""
		if i > 1 then
			damage_text = damage_text .. " + "
		end
		local value = damage["value"]
		if value == "weapon" then
			damage_text = damage_text .. "Normal weapon damage"
		elseif special_values[value] then
			-- Handle some special values
			damage_text = damage_text .. special_values[value](frame)
		else
			damage_text = damage_text .. frame:expandTemplate{
				title = "DamageColor",
				args = { damage["type"], value }
			} .. frame:expandTemplate{
				title = "DamageType",
				args = { damage["type"] }
			}
		end

		-- Extra info field to add free-form annotation to the damage instance
		if damage["info"] then
			damage_text = damage_text .. " (" .. damage["info"] .. ")"
		end
		
		damage_div:tag("div")
			:css("white-space", "nowrap")
			:wikitext(damage_text)
	end
	
	if args["format"] ~= "nosummary" then
		local header_text = header .. ": "
		-- Damage range preview
		if data.min_roll and data.max_roll then
			if data.min_roll ~= data.max_roll then
				header_text = header_text .. data.min_roll .. "~" .. data.max_roll
			elseif data.max_roll > 0 then
				header_text = header_text .. data.max_roll
			end
			if data.uneval_mods and data.max_roll > 0 then
				header_text = header_text .. " + modifiers"
			end
		end
		local header = mw.html.create("dt"):wikitext(header_text)
		
		return tostring(header) .. tostring(element)
	else
		return tostring(element)
	end
end

-- Format the damage values in a compact inline format with less details
function formatting.damage_inline(frame, args, data)
	local result = ""
    mw.log(data)
	for _, kind in pairs(data) do
		mw.log(kind)
		for i, damage in ipairs(kind.instances) do
			local value = damage["value"]
			if i > 1 then
				result = result .. " + "
			end
			if special_values[value] then
				-- Handle some legacy special values
				result = result .. special_values[value](frame)
			else
				result = result .. frame:expandTemplate{
					title = "DamageColor",
					args = { damage["type"], value }
				} .. frame:expandTemplate{
					title = "DamageType",
					args = { damage["type"] }
				}
			end
		end
	end

	return result
end

return formatting