From Dark and Darker Wiki

Revision as of 22:09, 29 January 2026 by Raw Salad (talk | contribs) (Minor cleanup of variable names and code style. Added commented TODOs.)

Overview

Functions for making Monsters table. Data comes from Data:Monster.json.

Functions

draw_monster_table

Create's a table of monsters

Parameters

  • 1 - list key of monsters to use
  • 2 - is the list key a race?

Bosses


{{#invoke:Monster_Table|draw_table|bosses}}


Lua error at line 345: attempt to index field 'monsters' (a nil value).

Demons


{{#invoke:Monster_Table|draw_table|Demon|true}}


Lua error at line 345: attempt to index field 'monsters' (a nil value).


local p = {}
local MD = mw.loadJsonData("Data:Monster.json")
local suppressed_color = "#1A91C4"
local neutral_color = "#EEE8"

---Check existence and equality of common and elite and nightmare variants
---@param common any
---@param elite any
---@param nightmare any
---@return boolean
local function is_suppressed(common, elite, nightmare)
	if common and common == elite then
		if nightmare then
			if elite == nightmare then
				return true
			end
		else
			return true
		end
	end
	return false
end


---Create css/html wikitext for iconbox
---@param name string
---@return string
local function iconbox(name)
	return "<div class='iconbox'><div class='rarity-1 rounded relative'>[[File:"..name..".png|x80px|link="..name.."]]</div><br>[["..name.."|<b class=rarity-1>"..name.."</b>]]</div>"
end


---Create color coded string for common/elite/nightmare variants, if they exist.
---- If more than one variant exists, and they are all the same, color code as suppressed.
---- If only one variant exists, just return that variant with appopriate rarity color coding.
---@param common any
---@param elite any
---@param nightmare any
---@param unit any
---@return string
local function variant_coloring(common, elite, nightmare, unit)
	if common == nil  or common == "" then return "" end
	unit = unit or ""

	local output_str = ""
	if is_suppressed(common, elite, nightmare) then
		output_str = "<span style='color:"..suppressed_color.."'>"..tostring(common)..unit.."</span>"	
	else
		if common then
			output_str = "<span>"..tostring(common)..unit.."</span>"
		end
		if elite then
			if output_str ~= "" then output_str = output_str.."/" end
			output_str = output_str.."<span class='colorrarityElite'>"..tostring(elite)..unit.."</span>"
		end
		if nightmare then
			if output_str ~= "" then output_str = output_str.."/" end
			output_str = output_str.."<span class='colorrarityNightmare'>"..tostring(nightmare)..unit.."</span>"
		end
	end
	return output_str
end


---Create tooltip wikitext
---@param color string
---@param display_text string
---@param tooltip_text string
---@return string
local function tooltip(color, display_text, tooltip_text)
	if color == "" or color == nil then color = " style='color:"..color.."'" end
	return "<span class='tooltip'"..color.."><u>["..tostring(display_text).."]</u><span class='tooltiptext-right'>"..tooltip_text.."</span></span>"
end


---Create color coded string of damage reductions
---- Assumes policy text is invariant
---@param common table
---@param elite table
---@param nightmare table
--- @return string
local function damage_reductions(common, elite, nightmare)
	local wikitext = ""
	for k, v in pairs(common.damageReductions) do
		if wikitext ~= "" then wikitext = wikitext.."<br>" end

		-- TODO Currently using lots of text processing.  Consider data structure refactor to avoid this, it isn't critical though.

		-- color the key and strip suffixes for brevity
		wikitext = wikitext.."<span style='color:"..neutral_color.."'>"..k:gsub(" Damage Reduction",""):gsub(" Magical",""):gsub(" Physical","")..": </span>"

		-- Asterisk indicates presence of policy text, which needs a tooltip.
		if tostring(v):find("*") then
			local color = ""
			if is_suppressed(v, elite.damageReductions and elite.damageReductions[k], nightmare.damageReductions and nightmare.damageReductions[k]) then
				color = " style='color:"..suppressed_color.."'"
			end
			wikitext = wikitext
				..tooltip(
					color,
					variant_coloring(
						tostring(v):match("^(.*)%*"),
						tostring(elite.damageReductions and elite.damageReductions[k]):match("^(.*)%*"),
						tostring(nightmare.damageReductions and nightmare.damageReductions[k]):match("^(.*)%*"),"%"
					),
					tostring(v):match("%*(.*)$")
				)
		else
			wikitext = wikitext
				..variant_coloring(
					v,
					elite.damageReductions and elite.damageReductions[k],
					nightmare.damageReductions and nightmare.damageReductions[k],"%"
				)
		end
	end
	return wikitext
end


---Split a comma seperated string into a list of trimmed strings
--- @return table
local function string_to_list(string)
	local list = {}
	for token in string:gmatch("([^,]+)") do
		list[#list+1] = token:gsub("^%s*(.-)%s*$", "%1")
	end
	return list
end


---Convert a string of policy keys into tooltipped wikitext
---@param policies string
---@return string
local function policy_tooltips(policies)
	if policies == "" or policies == nil then return "" end

	local wikitext = ""
	for _,policy in pairs(string_to_list(policies)) do
		wikitext = wikitext
			..tooltip(
				"",
				MD.policies[policy],
				policy
			)
	end
	return wikitext
end


---TODO
---- color code variants
---- Refactor data structure to avoid text processing
---- Add variant coloring
---- Assume policies are invariant
local function statuses(common, elite, nightmare)
	local output_str = ""

	local highest_variant = common
	if next(nightmare) ~= nil then highest_variant = nightmare
	elseif next(elite) ~= nil then highest_variant = elite end

	for k,v in pairs(highest_variant.statuses) do
		if output_str ~= "" then output_str = output_str.."<br>" end
		local status_effect = v:match("^(.*)%*") or v
		local policy_text = policy_tooltips(v:match("%*(.*)$"))
		output_str = output_str.."<span style='color:"..neutral_color.."'>"..k..":&nbsp;</span>"..status_effect.." "..policy_text
	end
	return output_str
end


---Check for consistency across variants and format string of an ability's effects
---@param common table
---@param elite table
---@param nightmare table
---@return string
local function process_effect(common, elite, nightmare)
	-- check for consistency across variants
	if next(elite) and (common.impact ~= elite.impact) or (next(nightmare) and (common.impact ~= nightmare.impact)) then
		return "<span color='red'>Error: Impact power differs across variants.</span>"
	end

	local impact_string = ""
	if is_suppressed(common.impact, elite.impact, nightmare.impact) then
		impact_string = "<span style='color:"..suppressed_color.."'> ("..tostring(common.impact)..") </span> "
	elseif common.impact then
		impact_string =  " ("..tostring(common.impact)..") "
	end

	-- get values for each variant
	return variant_coloring(common.value, elite.value, nightmare.value)
		..impact_string
		..variant_coloring(common.text, elite.text, nightmare.text)
end


--- Assumptions
---- If the variants exists, lower variants have a subset of higher variant ability set.
---- An ability, if present, has identical names across variants. Strings associated with an Ability can vary. Order of abilities matters.
---- Important ability values that vary by variant type are followed by the "#" token.
---- Statuses applied by an ability are preceded by the "$" token.
---- Impact Power of an ability is invariant.
--- @param name string
--- @param common table
--- @param elite table
--- @param nightmare table
--- @param highest_variant table
--- @return string
local function process_ability(name, common, elite, nightmare, highest_variant)
	local wikitext = ""

	if highest_variant.abilities[name].effects then
		for k,_ in pairs(highest_variant.abilities[name].effects) do
			if wikitext ~= "" then wikitext = wikitext..",&nbsp;" end
			wikitext = wikitext
				..process_effect(
					common.abilities[name].effects[k],
					elite.abilities and elite.abilities[name] and elite.abilities[name].effects[k] or {},
					nightmare.abilities and nightmare.abilities[name] and nightmare.abilities[name].effects[k] or {}
				)
		end
	end

	if not highest_variant.abilities[name].status_effects then
		return wikitext
	end

	if wikitext ~= "" then wikitext = wikitext.."&nbsp;+&nbsp;" end
	for k,_ in pairs(highest_variant.abilities[name].status_effects) do
		if wikitext ~= "" and k ~= 1 then wikitext = wikitext..",&nbsp;" end
		wikitext = wikitext
			..variant_coloring(
				common.abilities[name].status_effects[k].name,
				elite.abilities and elite.abilities[name] and elite.abilities[name].status_effects[k].name,
				nightmare.abilities and nightmare.abilities[name] and nightmare.abilities[name].status_effects[k].name
			)
	end
	return wikitext
end

---Create color coded string of abilities
---@param common table
---@param elite table
---@param nightmare table
--- @return string
local function abilities(common, elite, nightmare)
	local highest_variant = common
	if next(nightmare) ~= nil then highest_variant = nightmare
	elseif next(elite) ~= nil then highest_variant = elite end

	local wikitext = ""

	for k, v in pairs(highest_variant.abilities) do
		if wikitext ~= "" then wikitext = wikitext.."<br>" end
		wikitext = wikitext.."<span style='color:"..neutral_color.."'>"..k..":&nbsp;</span>"
			..process_ability(k, common, elite, nightmare, highest_variant)
	end

	return wikitext
end


---Hyphen seperated list of hyperlinked races.  Assumes all variants same races.
---@param common table
---@return string
local function races(common)
	local wikitext = ""
	for k,v in pairs(common.races) do
		if wikitext ~= "" then wikitext = wikitext.."-" end
		wikitext = wikitext.."[["..k.."]]"
	end

	return wikitext
end


---@return table Elite
---@return table Nightmare
local function get_monster_variants(common)
	-- TODO Move highest_variant logic to this function
	local elite = MD.monsters.monster_localized_names[common.name].Elite
	local nightmare = MD.monsters.monster_localized_names[common.name].Nightmare
	return MD.monsters.monsters_ids[elite] or {}, MD.monsters.monsters_ids[nightmare] or {}
end


---Create a table row for a monster and its variants
---@param common table
---@return string
local function row(common)
	local elite, nightmare = get_monster_variants(common)

	-- TODO string concats like this aren't performant, consider table.concat.
	return "<tr><td>"
		..iconbox(common.name).."</td><td>"
		..variant_coloring(common.hp, elite.hp, nightmare.hp).."</td><td>"
		..variant_coloring(common.move, elite.move, nightmare.move).."</td><td>"
			.."<div style='margin:auto; height:50%;'>"..variant_coloring(common.xp, elite.xp, nightmare.xp) --TODO the div automargin doesn't work as intended. Need to center vertically while splitting the cell in half.
			.."</div><div style='margin:auto; height:50%;'>"..variant_coloring(common.ap, elite.ap, nightmare.ap).."</td><td>"
		..damage_reductions(common, elite, nightmare).."</td><td>"
		..abilities(common, elite, nightmare).."</td><td>"
		..statuses(common, elite, nightmare).."</td><td>"
		..races(common).."</td><td>"
		..variant_coloring(common.actionspeed, elite.actionspeed, nightmare.actionspeed,"%").."</td></tr>"
end


--- @param frame table: frame args
---- frame.args[1] string: {"bosses", "minibosses", "mimics", "normal"} or a race list like {"Demon", "Undead", ...
---- frame.args[2] bool or string: determines if the first key is a race list
--- @return table: ordered list of monster ids
local function get_monster_list(frame)
	local key = frame.args[1] ~= nil and frame.args[1] or "all"
	local is_race_list = frame.args[2] ~= nil and frame.args[2]

	if is_race_list then
		return MD.races[key]
	else
		return MD[key]
	end
end


---Create monster table wikitext
---- Test on wiki with: mw.log(p.draw_table({args={"bosses"}})) or mw.log(p.draw_table({args={"Demon","true"}}))
---@param frame table
--- @return string
function p.draw_table(frame)
	if not MD then return "<span style='color:red;'>Error: Could not load Monster data in [[Module:Monster Table]]</span>" end

	local wikitext = [===[<table cellspacing="0" class="wikitable sortable jquery-tablesorter" style="width:100%;color:#eee; background:transparent; text-align:center; vertical-align:middle;"><tr>
<th style="width:3%">Name</th>
<th style="width:3%">Health</th>
<th style="width:3%">Move Speed</th>
<th style="width:3%">Exp<br>AP</th>
<th style="width:3%">Damage Reductions</th>
<th style="width:17%">Abilities' Damage([[Impact Power]]) + Statuses</th>
<th style="width:22%">[[Statuses]]' Effects</th>
<th style="width:3%">Race</th>	
<th style="width:3%">Action&nbsp;Speed</th></tr>]===]

	local monster_list = get_monster_list(frame)
	for _, monster_key in pairs(monster_list) do
		wikitext = wikitext..row(MD.monsters.monsters_ids[monster_key])
	end

	return wikitext.."</table>"
end


return p