From Dark and Darker Wiki

m (data load moved to global scope)
(Minor cleanup of variable names and code style. Added commented TODOs.)
 
Line 1: Line 1:
local p = {}
local p = {}
local monster_data = mw.loadJsonData("Data:Monster.json")
local MD = mw.loadJsonData("Data:Monster.json")
local suppressed_color = "#1A91C4"
local suppressed_color = "#1A91C4"
local neutral_color = "#EEE8"
local neutral_color = "#EEE8"


---Check existence and equality of elite and nightmare variants to common
---Check existence and equality of common and elite and nightmare variants
---@param common any
---@param common any
---@param elite any
---@param elite any
Line 23: Line 23:




--- Create css/html wikitext for iconbox
---Create css/html wikitext for iconbox
--- @param name string
---@param name string
--- @return string
---@return string
local function draw_iconbox(name)
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>"
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
end




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


unit = unit or ""
local output_str = ""
local output_str = ""
if is_suppressed(common, elite, nightmare) then
if is_suppressed(common, elite, nightmare) then
Line 57: Line 59:
end
end
end
end
return output_str
return output_str
end
end




--- Create tooltip wikitext
---Create tooltip wikitext
--- @param color string
---@param color string
--- @param display_text string
---@param display_text string
--- @param tooltip_text string
---@param tooltip_text string
--- @return string
---@return string
local function tooltip(color, display_text, tooltip_text)
local function tooltip(color, display_text, tooltip_text)
if color == "" or color == nil then color = " style='color:"..color.."'" end
if color == "" or color == nil then color = " style='color:"..color.."'" end
Line 74: Line 75:


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


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


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




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




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


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




--TODO
---TODO
local function statuses(MD, common, elite, nightmare)
---- 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 output_str = ""


Line 146: Line 164:
if output_str ~= "" then output_str = output_str.."<br>" end
if output_str ~= "" then output_str = output_str.."<br>" end
local status_effect = v:match("^(.*)%*") or v
local status_effect = v:match("^(.*)%*") or v
local policy_text = policy_tooltips(MD, v:match("%*(.*)$"))
local policy_text = policy_tooltips(v:match("%*(.*)$"))
output_str = output_str.."<span style='color:"..neutral_color.."'>"..k..":&nbsp;</span>"..status_effect.." "..policy_text
output_str = output_str.."<span style='color:"..neutral_color.."'>"..k..":&nbsp;</span>"..status_effect.." "..policy_text
end
end
Line 172: Line 190:


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




-- Assumptions:
--- Assumptions
---- If the variants exists, lower variants have a subset of higher variant ability set.
---- 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.
---- An ability, if present, has identical names across variants. Strings associated with an Ability can vary. Order of abilities matters.
Line 191: Line 209:
--- @return string
--- @return string
local function process_ability(name, common, elite, nightmare, highest_variant)
local function process_ability(name, common, elite, nightmare, highest_variant)
local output_str = ""
local wikitext = ""


if highest_variant.abilities[name].effects then
if highest_variant.abilities[name].effects then
for k,_ in pairs(highest_variant.abilities[name].effects) do
for k,_ in pairs(highest_variant.abilities[name].effects) do
if output_str ~= "" then output_str = output_str..",&nbsp;" end
if wikitext ~= "" then wikitext = wikitext..",&nbsp;" end
output_str = output_str..process_effect(
wikitext = wikitext
common.abilities[name].effects[k],
..process_effect(
elite.abilities and elite.abilities[name] and elite.abilities[name].effects[k] or {},
common.abilities[name].effects[k],
nightmare.abilities and nightmare.abilities[name] and nightmare.abilities[name].effects[k] or {})
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
end
end


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


if output_str ~= "" then output_str = output_str.."&nbsp;+&nbsp;" end
if wikitext ~= "" then wikitext = wikitext.."&nbsp;+&nbsp;" end
if highest_variant.abilities[name].status_effects then
for k,_ in pairs(highest_variant.abilities[name].status_effects) do
for k,v in pairs(highest_variant.abilities[name].status_effects) do
if wikitext ~= "" and k ~= 1 then wikitext = wikitext..",&nbsp;" end
if output_str ~= "" and k ~= 1 then output_str = output_str..",&nbsp;" end
wikitext = wikitext
output_str = output_str..variant_color_coding(
..variant_coloring(
common.abilities[name].status_effects[k].name,
common.abilities[name].status_effects[k].name,
elite.abilities and elite.abilities[name] and elite.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)
nightmare.abilities and nightmare.abilities[name] and nightmare.abilities[name].status_effects[k].name
end
)
end
end
 
return wikitext
return output_str
end
end


Line 225: Line 244:
---@param elite table
---@param elite table
---@param nightmare table
---@param nightmare table
---@return string
--- @return string
local function abilities(common, elite, nightmare)
local function abilities(common, elite, nightmare)
local highest_variant = common
local highest_variant = common
Line 231: Line 250:
elseif next(elite) ~= nil then highest_variant = elite end
elseif next(elite) ~= nil then highest_variant = elite end


local output_str = ""
local wikitext = ""


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


return output_str
return wikitext
end
end




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


return output_str
return wikitext
end
end




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




local function draw_row(MD, monster)
---Create a table row for a monster and its variants
local monster_elite, monster_nightmare = get_monster_variants(MD, monster)
---@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>"
return "<tr><td>"
..draw_iconbox(monster.name).."</td><td>"
..iconbox(common.name).."</td><td>"
..variant_color_coding(monster.hp, monster_elite.hp, monster_nightmare.hp).."</td><td>"
..variant_coloring(common.hp, elite.hp, nightmare.hp).."</td><td>"
..variant_color_coding(monster.move, monster_elite.move, monster_nightmare.move).."</td><td>"
..variant_coloring(common.move, elite.move, nightmare.move).."</td><td>"
.."<div style='margin:auto; height:50%;'>"..variant_color_coding(monster.xp, monster_elite.xp, monster_nightmare.xp)
.."<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_color_coding(monster.ap, monster_elite.ap, monster_nightmare.ap).."</td><td>"
.."</div><div style='margin:auto; height:50%;'>"..variant_coloring(common.ap, elite.ap, nightmare.ap).."</td><td>"
..damage_reductions(monster, monster_elite, monster_nightmare).."</td><td>"
..damage_reductions(common, elite, nightmare).."</td><td>"
..abilities(monster, monster_elite, monster_nightmare).."</td><td>"
..abilities(common, elite, nightmare).."</td><td>"
..statuses(MD, monster, monster_elite, monster_nightmare).."</td><td>"
..statuses(common, elite, nightmare).."</td><td>"
..races(monster).."</td><td>"
..races(common).."</td><td>"
..variant_color_coding(monster.actionspeed, monster_elite.actionspeed, monster_nightmare.actionspeed,"%").."</td></tr>"
..variant_coloring(common.actionspeed, elite.actionspeed, nightmare.actionspeed,"%").."</td></tr>"
end
end




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


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


local output_str = [===[<table cellspacing="0" class="wikitable sortable jquery-tablesorter" style="width:100%;color:#eee; background:transparent; text-align:center; vertical-align:middle;"><tr>
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%">Name</th>
<th style="width:3%">Health</th>
<th style="width:3%">Health</th>
Line 317: Line 341:
<th style="width:3%">Action&nbsp;Speed</th></tr>]===]
<th style="width:3%">Action&nbsp;Speed</th></tr>]===]


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


return output_str.."</table>"
return wikitext.."</table>"
end
end




return p
return p

Latest revision as of 22:09, 29 January 2026

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}}


Name Health Move Speed Exp
AP
Damage Reductions Abilities' Damage(Impact Power) + Statuses Statuses' Effects Race Action Speed
2656/3646200/270
15/20
100
Projectile: 25%
Divine: -100%
Evil: 50%
Magical: 36.8%
Physical: -22%
Curse Of Chilling: Curse Of Chilling
Earth Quake: 120.0/60.0 (6) Physical/Magical + Curse Of Chilling
Scream: -45% (4) True Max Health + Curse Of Chilling
Eating Snare: Eating
Normal Attack: 30 (7) Magical + Curse Of Chilling
Teleport: 20 (5) Magical + Curse Of Chilling
Normal Attack Return: Curse Of Chilling
Eating Apply And Remove Shield: Eating Shield
Curse Of Chilling: -1.6% Move Speed for 8s [AgT]Stacking Type: Aggregates by Target[30x]Maximum Stacks: 30[Rmv]Removable
Eating Shield: 1000 Shield [AgT]Stacking Type: Aggregates by Target
Eating: -95% Max Health, Unable to Move over 15s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Ref]Refresh: Resets on Successful Application[Rmv]Removable
Undead-Ghost0%
4115/7115400
15/20
50
Spirit: -50%
Magical: 36.8%
Lightning: 50%
Physical: -22%
Swim L: 79.8/126.0 (6) Physical + Obessed
Short Dash: 34.2/54.0 (6) Physical
Tail Slash High: 72.2/114.0 (5) Physical68.4/108.0 (5) Physical + Tail Poison
Water Arrow1: 60.8/96.0 (6) Physical
Swim R1500: 79.8/126.0 (6) Physical + Obessed
Turning Water Arrow2 L: 60.8/96.0 (6) Physical
Lightning Bubble: Lightning Bubble Dmg
Water Arrow2: 49.4/78.0 (6) Physical
Rush Lightning: 34.2/54.0Physical
Divine Judgment: 114.0/180.0 (6) Physical
Turning Water Arrow R: 60.8/96.0 (6) Physical
Rush: 64.6/102.0 (4) Physical + Obessed
Move Spawn Location Start: 22.8/36.0 (4) Physical
Swim R: 79.8/126.0 (6) Physical + Obessed
Tail Slash Down: 68.4/108.0 (5) Physical68.4/108.0 (5) Physical + Tail Poison
Dash End: 22.8/36.0 (4) Physical
Swim L1500: 79.8/126.0 (6) Physical + Obessed
Pop Out: 45.6/72.0 (6) Physical
Lightning Nova: 304.0/480.0 (6) Physical
Tail Attack Up: 72.2/114.0 (5) Physical72.2/114.0 (5) Physical + Tail Poison
Dash: 22.8/36.0 (4) Physical + Obessed
Tail Attack Down: 76.0/120.0 (5) Physical76.0/120.0 (5) Physical + Tail Poison
Turning Water Arrow2 R: 60.8/96.0 (6) Physical
Turning Water Arrow L: 60.8/96.0 (6) Physical
Obessed: ? for 30s [1x]Maximum Stacks: 1
Tail Poison: 20 Magical Damage over 30s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Ref]Refresh: Resets on Successful Application
Lightning Bubble Dmg: 7 Magical Damage
Beast-Aquatic-12%/0%
2215/3115250/300
15
50
Spirit: -100%
Light: -100%
Fire: 50%
Projectile: -25%
Ice: 50%
Magical: 7.8%
Physical: [3%]0 Armor Rating and 25% Other Physical Damage Reduction
Bombing Spawn Fog Missile: 50.4/67.2 (6) Magical
Scream Sentence: 10.8/14.4 (6) Magical
Fog Rush: 57.6/76.8 (7) Magical + Decrease Heal
Fog Attack2: 79.2/105.6 (7) Magical + Decrease Heal
Hide Attack: 64.8/86.4 (7) Magical + Decrease Heal
Pull: 54.0/72.0 (6) Magical + Decrease Heal
Fog Attack1: 50.4/67.2 (6) Magical + Decrease Heal
Decrease Heal: ? for 7s [AgT]Stacking Type: Aggregates by Target[10x]Maximum Stacks: 10[Exp]Expiration: Removes Single Stack and Refreshes Duration[Rmv]RemovableUndead-Ghost0%
2990/3980/4515250/280/300
12/15/18
50
Projectile: 40%
Fire: 50%
Earth: -100%
Dark: -200%
Evil: 50%
Magical: 7.8%
Physical: -22%
Attack Ground Combo: 100.0/110.0/120.0 (7) Physical70.0/77.0/84.0 (7) Physical Radius + Slow
Attack Ground: 100.0/110.0/120.0 (7) Physical70.0/77.0/84.0 (7) Physical Radius + Slow
Attack Punch Down: 80.0/88.0/96.0 (7) Physical17.0/18.7/20.4 (7) Physical Radius + Slow
Combo Head Up: 180.0/198.0/216.0 (7) Physical + Slow
Attack Spinning Low: 200.0/220.0/240.0 (7) Physical + Slow
Combo Right To Left: 110.0/121.0/132.0 (7) Physical + Slow
Combo Punch Down: 80.0/88.0/96.0 (7) Physical17.0/18.7/20.4 (7) Physical Radius + Slow
Attack Spinning High: 200.0/220.0/240.0 (7) Physical + Slow
Regeneration: Regeneration
Debuff Shouting: SlowWhirl
Add Impact Power:  (999)
Combo Left To Right: 150.0/165.0/180.0 (7) Physical + Slow
Combo Slap: 145.0/159.5/174.0 (7) Physical20.0/22.0/24.0 (7) Physical Radius + Slow
Slow: -35% Move Speed for 4s [Rmv]Removable
Whirl: ? for 4s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Regeneration: 2 Physical Heal per 1s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1
Troll-Humanoid0%
2115/3015200/250
15/20
100
Projectile: 25%
Divine: -25%
Evil: 50%
Magical: 36.8%
Physical: -22%
Curse Of Gathering Mark: -50% (4) True Max Health-15% (4) True Max Health-95% (4) True Max Health-5% (4) True Max Health-2% (4) True Max Health + , CoG Heal5CoG Heal1CoG Heal2CoG Mark IconCoG Heal3CoG Heal4
Close Attack: 70 (5) Magical
Normal Attack: 100 (5) Magical
Getting Soul: Soul Shield
Curse Of Isolation Mark: -70% (4) True Max Health-90% (4) True Max Health-50% (4) True Max Health-150% (4) True Max Health-250% (4) True Max Health + , CoI Heal5CoI Heal1CoI Heal2CoI Mark IconCoI Heal3CoI Heal4
Strong Attack: 200 (7) Magical
CoI Heal1: 35% Max Health over 20s [Rmv]Removable
CoG Heal3: 10% Max Health over 20s [Rmv]Removable
Soul Shield: 250 Shield [AgT]Stacking Type: Aggregates by Target[Rmv]Removable
CoI Heal3: 60% Max Health over 20s [Rmv]Removable
CoI Mark Icon: ? for 5s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
CoI Heal4: 100% Max Health over 20s [Rmv]Removable
CoG Mark Icon: ? for 5s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
CoG Heal1: 65% Max Health over 20s [Rmv]Removable
CoG Heal5: 2% Max Health over 20s [Rmv]Removable
CoG Heal2: 35% Max Health over 20s [Rmv]Removable
CoI Heal2: 50% Max Health over 20s [Rmv]Removable
CoI Heal5: 170% Max Health over 20s [Rmv]Removable
CoG Heal4: 4% Max Health over 20s [Rmv]Removable
Undead-Lich0%
233180
0
1
Projectile: 15%
Fire: -250%
Earth: -300%
Divine: -200%
Evil: 25%
Magical: -17.2%
Physical: -22%
Back Step: 67.5 (4) Physical
Throwing Knife: 43.2 (3) Physical
Soul Pass: Skeleton Warlord Speed Up
Bombing: 108.0 (5) Physical
Attack: 43.2 (3) Physical
Skeleton Warlord Speed Up: 4% Move Speed, 1% Action Speed, 2% Physical Resistance, 1% Magical Resistance [AgT]Stacking Type: Aggregates by Target[25x]Maximum Stacks: 25Undead-Skeleton0%
4251/6308250/280
12/15
50
Light: -200%
Fire: 50%
Projectile: 30%
Dark: -25%
Evil: 50%
Magical: 7.8%
Physical: -22%
Run And Swing: 88.0/106.0 (7) Physical
Stomp: 193.6/233.2 (7) Physical + Slow
Sprinkle Soil: 149.6/180.2 (7) Physical + Blinker
Combo2: 140.8/169.6 (7) Physical
Combo3: 132.0/159.0 (7) Physical + Blinker
Combo1: 96.8/116.6 (7) Physical
Riot: 158.4/190.8 (7) Physical
Spawn Falling Rock: 17.6/21.2 (4) Physical
Blinker: Blinded by dirt for 10s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Slow: -50% Move Speed for 8s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Cyclops-Humanoid-20%
?100
0
0
Projectile: 15%
Magical: 7.8%
Physical: -22%
Beam Attack: 6.0 (7) Magical0%
2715/3615260/280
15/20
50
Spirit: -20%
Projectile: 30%
Fire: 50%
Magical: 7.8%
Physical: -22%
Slash Low: 88.2/112.0 (7) Physical
Attack Fire Sparks In: 100.8/128.0 (7) Physical
Ballista Shockwave: 50.4/64.0 (7) Physical
Thrust: 88.2/112.0 (7) Physical
Upper Slash: 81.9/104.0 (7) Physical
Slash High Short: 88.2/112.0 (7) Physical
Cannon Fire Long: 100.8/128.0 (7) Physical
Reflect Melee: 94.5/120.0 (7) Physical
Rush Attack: 88.2/112.0 (7) Physical
Ballista Falling: 100.8/128.0 (7) Physical
Rush End: 88.2/112.0 (7) Physical
Attack Fire Sparks Out: 100.8/128.0 (7) Physical
Blade Down: 69.3/88.0 (2) Physical
Blade Slash Short: 69.3/88.0 (2) Physical
Slash R: 81.9/104.0 (7) Physical
Blade Slash: 69.3/88.0 (2) Physical
Rush Slash High: 88.2/112.0 (7) Physical
Upper Slash Cannon: 81.9/104.0 (7) Physical
Blade Down Short: 69.3/88.0 (2) Physical
Slash High: 88.2/112.0 (7) Physical
Attack Down Slash: 81.9/104.0 (7) Physical
Blade Vertical Slash: 69.3/88.0 (2) Physical
Attack Overhead Slash: 63.0/80.0 (7) Physical44.1/56.0 (7) Physical
Slash L: 81.9/104.0 (7) Physical
Rush Slash Low: 88.2/112.0 (7) Physical
Cannon Fire: 100.8/128.0 (7) Physical
Human-Humanoid10%/15%
3615/7115500
20/25
125
Projectile: 50%
Fire: -25%
Air: -200%
Dark: -25%
Ice: 50%
Magical: 36.8%
Physical: -22%
Buff Movespeed: 1000Move Speed
Special Leap Attack3 To Center: 83.6/132.0 (7) Physical + Melee Attack Slow
Combo Tail Attack2: 68.4/108.0 (7) Physical + Melee Attack Slow
Special Pushing:  (999)
Special Breath Ground: Extreme Cold DmgExtreme Cold SlowFrost Shield To Breath
Special Breath Air To Side: 60.8/96.0 (7) Physical45.6/72.0 (6) Physical Radius
Combo Forward Attack L: 60.8/96.0 (7) Physical + Melee Attack Slow
Combo Two Hand Attack: 76.0/120.0 (7) Physical + Melee Attack Slow
Combo Leap Attack3: 83.6/132.0 (7) Physical + Melee Attack Slow
Combo Breath Ground: 60.8/96.0 (7) Physical45.6/72.0 (6) Physical Radius
Combo Leap Attack2: 60.8/96.0 (7) Physical45.6/72.0 (6) Physical Radius
Combo Breath Air: 60.8/96.0 (7) Physical45.6/72.0 (6) Physical Radius
Buff Movespeed Special: 2500Move Speed
Combo Tail Attack1: 57.0/90.0 (6) Physical + Melee Attack Slow
Special Breath Air: Extreme Cold DmgExtreme Cold Slow
Combo Forward Attack R: 60.8/96.0 (7) Physical + Melee Attack Slow
Special Leap Attack1 To Center: 53.2/84.0 (7) Physical
Special Summon Elemental: Frost Shield To Elemental
Combo Leap Attack1: 53.2/84.0 (7) Physical + Melee Attack Slow
Melee Attack Slow: -15% Action Speed for 11s [AgT]Stacking Type: Aggregates by Target[3x]Maximum Stacks: 3[Exp]Expiration: Removes Single Stack and Refreshes Duration[Rmv]Removable
Extreme Cold Slow: -20% Move Speed, -20% Action Speed for 8s [AgT]Stacking Type: Aggregates by Target[3x]Maximum Stacks: 3[Exp]Expiration: Removes Single Stack and Refreshes Duration[Rmv]Removable
Frost Shield To Elemental: Immune to Damage
Extreme Cold Dmg: 50 Physical Damage, 50 Magical Damage over 2s [0.2s]Tick interval: 0.2s[AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Frost Shield To Breath: Immune to Damage
Dragon-Wyvern0%
2515/3315320/360
12/15
50
Spirit: -100%
Projectile: 30%
Dark: -100%
Magical: 7.8%
Physical: -22%
Attack Slash12: 75.6/96.0 (6) Physical
Attack Soul Slash Down: 201.6/256.0 (7) Physical
Attack Rush Thrust: 107.1/136.0 (7) Physical
Attack Long Range Vertically: 100.8/128.0 (7) Physical
Attack Rush Thrust For Soul Slash: 107.1/136.0 (7) Physical
Debuff Move Speed: -70Move Speed
Attack Front Thrust: 81.9/104.0 (6) Physical
Attack Soul Slash Up: 201.6/256.0 (7) Physical
Attack Soul Shockwave: 138.6/176.0 (7) Physical + Dead Rift DmgDead Rift Move Slow
Attack Slash11: 75.6/96.0 (6) Physical
Attack Long Range Transverse: 100.8/128.0 (7) Physical
Attack Shoulder Charge: 44.1/56.0 (6) Physical
Attack Front Slash: 94.5/120.0 (7) Physical
Dead Rift Dmg: 5 Magical Damage [0.3s]Tick interval: 0.3s[AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Dead Rift Move Slow: -30% Move Speed [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Undead-Ghost0%/10%
2959/4359250/280
15/20
100
Projectile: 25%
Earth: -100%
Evil: 50%
Magical: 36.8%
Physical: -22%
Throwing Knives: 60.8/91.2 (7) Magical + Paralyze
Combo4: 60.8/91.2 (7) Magical
Shield: Shield
Combo1: 48.0/72.0 (7) Magical
Throwing Knives2: 25.6/38.4 (3) Magical + Paralyze
Combo2: 57.6/86.4 (7) Magical
Bone Prison: Dominated Player
Realm Of Domination: Dominated Player
Combo5: 60.8/91.2 (7) Magical
Combo3: 70.4/105.6 (7) Magical
Shield: Immune to Damage [Inf]Duration: Infinite
Dominated Player: ? for 6s [AgT]Stacking Type: Aggregates by Target[6x]Maximum Stacks: 6[Exp]Expiration: Removes Single Stack and Refreshes Duration
Paralyze: -15% Move Speed for 15s [AgT]Stacking Type: Aggregates by Target[5x]Maximum Stacks: 5[Rmv]Removable
Undead-Skeleton0%


Demons


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


Name Health Move Speed Exp
AP
Damage Reductions Abilities' Damage(Impact Power) + Statuses Statuses' Effects Race Action Speed
135/165/235100/125/150
3/4/5
1/2/3
Fire: 50%
Air: -400%
Projectile: -25%
Light: -200%
Divine: -150%
Arcane: -200%
Magical: -17.2%
Ice: -200%
Lightning: -200%
Physical: -22%
Demon Fly Run State: 200Move Speed
Demon Fly Self Destruction: 25.5/30.0/34.5Physical + Demon Fly Poison SlowDemon Fly Poison DmgDemon Fly Aggro Marker
Demon Fly Poison Powder: Demon Fly Poison Powder Dmg
Demon Fly Self Destruction2: 25.5/30.0/34.5Physical + Demon Fly Poison SlowDemon Fly Poison DmgDemon Fly Aggro Marker
Demon Fly Poison Powder Dmg: 6 Physical Damage [0.5s]Tick interval: 0.5s[AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Ref]Refresh: Resets on Successful Application[Rmv]Removable
Demon Fly Poison Dmg: 30 Physical Damage over 5s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Ref]Refresh: Resets on Successful Application[Rmv]Removable
Demon Fly Aggro Marker: ? for 15s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1
Demon Fly Poison Slow: -30% Move Speed for 5s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Demon0%/5%/10%
1015/1215/1415200/220/250
8/9/10
50
Fire: 25%
Dark: 50%
Evil: 50%
Light: -200%
Projectile: 20%
Divine: -200%
Ice: -300%
Magical: 36.8%
Physical: -22%
Attack2 Heavy Swing Down: 45.0/49.5/54.0 (6) Physical
Attack1 Heavy Swing Up: 45.0/49.5/54.0 (6) Physical
Attack5 Destruction: 48.0/52.8/57.6 (6) Physical
Attack4 Blood Vomiting: 12.0/13.2/14.4 (3) Physical6.0/6.6/7.2 (3) Physical Radius + Vomiting Blinker
Attack3 Greedy Rush: 60.0/66.0/72.0 (6) Physical
Vomiting Blinker: Blinded by dirt for 7s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]RemovableDemon0%/10%/20%
185/235/295250/290/340
4/5/6
2/3/4
Projectile: -25%
Light: -300%
Divine: -200%
Air: -400%
Evil: 25%
Ice: -300%
Magical: -17.2%
Physical: -22%
Attack: 27.0/36.0/54.0 (4) PhysicalDemon0%/10%/40%
915/1215/1415200/230/260
8/9/10
50
Fire: 25%
Dark: 50%
Evil: 50%
Light: -200%
Projectile: 20%
Divine: -200%
Ice: -300%
Magical: 36.8%
Physical: -22%
Attack Low Sweep: 50.4/75.6/75.6 (6) Physical
Sprint Attack Pierce: 44.8/67.2/67.2 (6) Physical
Sprint Phase: 150Move Speed
Attack1: 42.0/63.0/63.0 (6) Physical
Sprint Attack: 61.6/92.4/92.4 (7) Physical
Attack2: 50.4/75.6/75.6 (6) Physical
Demon0%/10%/15%
815/1015/121575/120/150
6/7/8
50
Projectile: 20%
Light: -200%
Fire: -200%
Evil: 25%
Divine: -200%
Ice: 50%
Magical: 36.8%
Physical: -22%
Attack01: 37.5/45.0/57.0 (3) Magical + Frostbite
Blizzard Fire Spell: 22.5/27.0/34.2 (3) Magical + Frostbite
Run State: 100Move Speed
Frostbite: -1% Move Speed, -3% Action Speed for 6s [AgT]Stacking Type: Aggregates by Target[3x]Maximum Stacks: 3[Exp]Expiration: Removes Single Stack and Refreshes Duration[Rmv]RemovableDemon0%/0%/20%
215/265/315100/150/200
3/3/5
2/3/4
Projectile: 15%
Fire: -300%
Divine: -200%
Ice: 50%
Magical: -17.2%
Physical: -22%
Run State: 150Move Speed
Attack: 31.5/39.0/52.5 (3) Physical + FrostbiteFreezing
Frostbite: -1% Move Speed, -3% Action Speed for 6s [AgT]Stacking Type: Aggregates by Target[3x]Maximum Stacks: 3[Exp]Expiration: Removes Single Stack and Refreshes Duration[Rmv]Removable
Freezing: Unable to Move, Unable to Attack for 1s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Demon0%
175/225/280350
4/5/6
2/3/4
Light: -300%
Divine: -200%
Projectile: 15%
Evil: 25%
Ice: -300%
Magical: -17.2%
Physical: -22%
Dash Attack: 35.2/48.4/61.6 (5) Physical
Attack: 24.0/33.0/42.0 (4) Physical
Demon0%/10%/20%
415100
7
15
Projectile: 15%
Fire: -300%
Divine: -200%
Ice: 50%
Magical: -17.2%
Physical: -22%
Frost Imp Run State: 150Move Speed
Frost Imp Attack: 31.5 (3) Physical + FrostbiteFrost Imp Freezing
Frostbite: -1% Move Speed, -3% Action Speed for 6s [AgT]Stacking Type: Aggregates by Target[3x]Maximum Stacks: 3[Exp]Expiration: Removes Single Stack and Refreshes Duration[Rmv]Removable
Frost Imp Freezing: Unable to Move, Unable to Attack for 1s [AgT]Stacking Type: Aggregates by Target[1x]Maximum Stacks: 1[Rmv]Removable
Demon0%
915/1165/1315200/260/320
8/9/10
50
Fire: 25%
Dark: 50%
Evil: 50%
Light: -200%
Projectile: 15%
Divine: -200%
Ice: -300%
Magical: 7.8%
Physical: -22%
Attack1: 36.8/49.6/62.4 (6) Physical
Attack2: 36.8/49.6/62.4 (6) Physical
Demon-10%
155/205/25570/90/110
3/4/5
1/2/3
Light: -300%
Divine: -200%
Projectile: 15%
Evil: 25%
Ice: -300%
Magical: -17.2%
Physical: -22%
Casting Heal: HealHeal: 5% Max Health Demon0%/5%/10%

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