From Dark and Darker Wiki

(Added firearms to ranged type tables.)
(Fixed bug in page blocking section. The existence check wasn't catching all cases.)
 
(13 intermediate revisions by the same user not shown)
Line 2: Line 2:
local WD = mw.loadJsonData("Data:Weapon.json") --Global var holding tables from Data:Weapon.json
local WD = mw.loadJsonData("Data:Weapon.json") --Global var holding tables from Data:Weapon.json
local concat = table.concat
local concat = table.concat


---Get the item's size in pixels
---Get the item's size in pixels
--- @param item table
--- @return string
local function size(item)
local function size(item)
return tostring(item.invwidth*45).."x"..tostring(item.invheight*45).."px"
return tostring(item.invwidth*45).."x"..tostring(item.invheight*45).."px"
end
end


---Counts number of keys. Tables from Weapon.json don't work with next() or the # operator
---Counts number of keys. Tables from Weapon.json don't work with next() or the # operator
--- @param t table
--- @return number
local function count(t)
local function count(t)
local c = 0
local c = 0
Line 20: Line 14:
return c
return c
end
end


---Marks up a list of values with color based on rarity
---Marks up a list of values with color based on rarity
--- @param values string|table
--- @param weapon table
--- @return string
local function color_values(weapon, values)
local function color_values(weapon, values)
if values == nil then return "" end
if values == nil then return "" end
Line 41: Line 31:
return wt
return wt
end
end


---Marks up a stat block with the stat name and colored values and appropriate margins and alignment
---Marks up a stat block with the stat name and colored values and appropriate margins and alignment
--- @param weapon table
--- @param stat string
--- @return string
local function inline_block(weapon,stat)
local function inline_block(weapon,stat)
if weapon.stats[(stat):lower()] == nil then return "" end
if weapon.stats[(stat):lower()] == nil then return "" end
return "<div style='display:inline-block;vertical-align:top;margin:0 15px 0 15px'><b style='color:#eee8'>"..stat.."</b><br>"..color_values(weapon,weapon.stats[(stat):lower()]).."</div>"
return "<div style='display:inline-block;vertical-align:top;margin:0 15px 0 15px'><b style='color:#eee8'>"..stat.."</b><br>"..color_values(weapon,weapon.stats[(stat):lower()]).."</div>"
end
end


---Formats a table cell for the iconbox
---Formats a table cell for the iconbox
---@param item table
---@return string
local function name(item, is_header)
local function name(item, is_header)
if is_header then return [[<th style="width:5%">Name</th>]] end
if is_header then return [[<th style="width:5%">Name</th>]] end
Line 71: Line 54:
end
end


return "<td><div class='iconbox'><div class='rarity"..color.." rounded relative'>[[File:"..item.name..".png|"..size(item).."|link="..item.name.."]]"
return "<td><div class='iconbox' style='width:max-content;align-items:center'><div class='rarity"..color.." rounded relative'>[[File:"..item.name..".png|"..size(item).."|link="..item.name.."]]"
..icon_amount
..icon_amount
.."</div><br>[["..item.name..bold_link.."</b>]]</div></td>"
.."</div><br>[["..item.name..bold_link.."</b>]]</div></td>"
end
end


---Formats the weapon's list of classes into a table cell of classes separated by linebreaks
---Formats the weapon's list of classes into a table cell of classes separated by linebreaks
--- @param weapon table
--- @param is_header boolean
--- @return string
local function classes(weapon,is_header)
local function classes(weapon,is_header)
if is_header then return [[<th style="width:5%">Class</th>]] end
if is_header then return [[<th style="width:5%">Class</th>]] end
Line 92: Line 71:
return "<td>"..wt.."</td>"
return "<td>"..wt.."</td>"
end
end


---Formats a table cell of slottype and handtype
---Formats a table cell of slottype and handtype
--- @param weapon table
--- @param is_header boolean
--- @return string
local function slot_type(weapon,is_header)
local function slot_type(weapon,is_header)
if is_header then return [[<th style="width:5%">Slot</th>]] end
if is_header then return [[<th style="width:5%">Slot</th>]] end
Line 103: Line 78:
return "<td>"..weapon.slottype.."<br>"..weapon.handtype.."</td>"
return "<td>"..weapon.slottype.."<br>"..weapon.handtype.."</td>"
end
end


---Formats a table cell of move speeds
---Formats a table cell of move speeds
--- @param weapon table
--- @param is_header boolean
--- @return string
local function move_speed(weapon,is_header)
local function move_speed(weapon,is_header)
if is_header then return [[<th style="width:5%">Movement Speed</th>]] end
if is_header then return [[<th style="width:5%">Movement Speed</th>]] end
Line 114: Line 85:
return "<td>"..color_values(weapon,weapon.stats["move speed"]).."</td>"
return "<td>"..color_values(weapon,weapon.stats["move speed"]).."</td>"
end
end


---Formats a table cell of Armor rating and Magical Resistance
---Formats a table cell of Armor rating and Magical Resistance
--- @param weapon table
--- @param is_header boolean
--- @return string
local function armor_rating(weapon,is_header)
local function armor_rating(weapon,is_header)
if is_header then return [[<th style="width:15%">Armor Rating</th>]] end
if is_header then return [[<th style="width:15%">Armor Rating</th>]] end
Line 125: Line 92:
return "<td>"..inline_block(weapon,"Armor Rating")..inline_block(weapon,"Magical Resistance").."</td>"
return "<td>"..inline_block(weapon,"Armor Rating")..inline_block(weapon,"Magical Resistance").."</td>"
end
end


local function impact_zones(weapon,is_header)
local function impact_zones(weapon,is_header)
Line 135: Line 101:
for j,attack in ipairs(weapon.abilities[ability].order) do
for j,attack in ipairs(weapon.abilities[ability].order) do
if weapon.abilities[ability][attack].impactpower and weapon.abilities[ability][attack].impactzones then
if weapon.abilities[ability][attack].impactpower and weapon.abilities[ability][attack].impactzones then
wt = wt.."<br><span style='color:#eee8'>"..ability.." "..attack.."</span><br>"
wt = wt.."<span style='color:#eee8'>"..ability.." "..attack.."</span><br>"
for k,impact_zone in ipairs(weapon.abilities[ability][attack].impactzones) do
for k,impact_zone in ipairs(weapon.abilities[ability][attack].impactzones) do
if k~=1 then wt = wt.."/" end
if k~=1 then wt = wt.."/" end
wt = wt..impact_zone
wt = wt..impact_zone
end
end
wt = wt.." + "..weapon.abilities[ability][attack].impactpower.."<br>"
wt = wt.." + "..weapon.abilities[ability][attack].impactpower.."<br><br>"
end
end
end
end
end
end
end
end
return "<td>"..wt.."<br></td>"
if weapon.impactresistance~="" then
wt = wt.."<span style='color:#eee8'>Impact Resist</span><br>"..weapon.impactresistance
end
return "<td>"..wt.."</td>"
end
end


---Helper function: Collects move mult and prepare move mult for display
---Helper function: Collects move mult and prepare move mult for display
---@param ability table
---@return string
---@return string
local function collect_multipliers(ability)
local function collect_multipliers(ability)
local move, prep_move = "",""
local move, prep_move = "",""
Line 169: Line 134:
return move,prep_move
return move,prep_move
end
end


---Formats a table cell containing speed penalties for each action
---Formats a table cell containing speed penalties for each action
---@param weapon table
---@param is_header boolean
---@return string
local function action_move_speed_penalty(weapon,is_header)
local function action_move_speed_penalty(weapon,is_header)
if is_header then return [=[<th style="width:10%">[[Weapons#Movement_Multiplier_Explanation|Action Movement Penalty]]</th>]=] end
if is_header then return [=[<th style="width:10%">[[Weapons#Movement_Multiplier_Explanation|Action Movement Penalty]]</th>]=] end
local order = {} --reconstruct order to exclude the other ability group
for _,ability in ipairs(weapon.abilities.order) do
if ability ~= "Other" then order[#order+1] = ability end
end


local wt = ""
local wt = ""
for i,ability_name in ipairs(weapon.abilities.order) do
for i,ability_name in ipairs(order) do
if i~=1 then wt = wt.."<br><br>" end
if i~=1 then wt = wt.."<br><br>" end


Line 196: Line 162:
return "<td>"..wt.."</td>"
return "<td>"..wt.."</td>"
end
end


---Formats a table cell containing base Physical and Magical damage, if present.
---Formats a table cell containing base Physical and Magical damage, if present.
---@param weapon any
---@param is_header any
---@return string
local function damage_on_hit(weapon,is_header)
local function damage_on_hit(weapon,is_header)
if is_header then return [[<th style="width:25%">Damage on Hit</th>]] end
if is_header then return [[<th style="width:25%">Damage on Hit</th>]] end
Line 210: Line 172:
return "<td>"..wt.."</td>"
return "<td>"..wt.."</td>"
end
end


---These stats get their own table cells, as such they are excluded from the stats cell to avoid needless redundancy
---These stats get their own table cells, as such they are excluded from the stats cell to avoid needless redundancy
local exclude_stat = {["Physical Base Weapon Damage"]=true,["Magical Base Weapon Damage"]=true,["Armor Rating"]=true,["Magical Resistance"]=true,["Move Speed"]=true}
local exclude_stat = {["Physical Base Weapon Damage"]=true,["Magical Base Weapon Damage"]=true,["Armor Rating"]=true,["Magical Resistance"]=true,["Move Speed"]=true}


---Formats a table cell containing any stats not covered by the rest of the row
---Formats a table cell containing any stats not covered by the rest of the row
---@param weapon table
---@param is_header boolean
---@return string
local function stats(weapon,is_header)
local function stats(weapon,is_header)
if is_header then return [[<th style="width:12%">Stats</th>]] end
if is_header then return [[<th style="width:12%">Stats</th>]] end
Line 231: Line 188:
return "<td>"..wt.."</td>"
return "<td>"..wt.."</td>"
end
end


---Formats a table cell containing animation times for ranges weapons
---Formats a table cell containing animation times for ranges weapons
---@param weapon table
---@param is_header boolean
---@return string
local function animation_time(weapon,is_header)
local function animation_time(weapon,is_header)
if is_header then return [[<th style="width:10%">Animation Times</th>]] end
if is_header then return [[<th style="width:10%">Animation Times</th>]] end
Line 263: Line 216:
end
end
end
end


---Formats a table cell containing hitslow for each attack
---Formats a table cell containing hitslow for each attack
---@param weapon table
---@param is_header boolean
---@return string
local function slowdown_on_hit(weapon,is_header)
local function slowdown_on_hit(weapon,is_header)
if is_header then return [[<th style="width:8%">Slowdown On Hit</th>]] end
if is_header then return [[<th style="width:8%">Slowdown On Hit</th>]] end
Line 291: Line 240:


---Formats a table cell containing the projectile's initial speed
---Formats a table cell containing the projectile's initial speed
---@param weapon table
---@param is_header boolean
---@return string
local function initial_speed(weapon,is_header)
local function initial_speed(weapon,is_header)
if is_header then return [[<th style="width:5%">Initial Projectile Speed (m/s)</th>]] end
if is_header then return [[<th style="width:5%">Initial Projectile Speed (m/s)</th>]] end
Line 299: Line 245:
return "<td>"..weapon.initialspeed.."</td>"
return "<td>"..weapon.initialspeed.."</td>"
end
end


---Formats a table cell containing the hitbox image resized by inventory size
---Formats a table cell containing the hitbox image resized by inventory size
---@param weapon table
---@param is_header boolean
---@return string
local function hitbox(weapon,is_header)
local function hitbox(weapon,is_header)
if is_header then return [[<th style="width:10%">Hitbox + Impact Resist</th>]] end
if is_header then return [[<th style="width:10%">Hitbox + Impact Resist</th>]] end


if weapon.impactresistance~="" then
return "<td>[[File:"..weapon.name.." Hitbox.png|link=|"..size(weapon).."]]</td>"
return "<td>[[File:"..weapon.name.." Hitbox.png|link=|"..size(weapon).."]]<br><span style='color:#eee8'>Impact Resist</span><br>"..weapon.impactresistance.."</td>"
else
return "<td>[[File:"..weapon.name.." Hitbox.png|link=|"..size(weapon).."]]</td>"
end
end
end


---Helps format a string containing either Combo Damage or damagetype values
---Helps format a string containing either Combo Damage or damagetype values
---@param weapon table
---@param ability string
---@param value string
---@return string
local function format_combo(weapon,ability,value)
local function format_combo(weapon,ability,value)
local wt = ""
local wt = ""
Line 329: Line 262:
return wt
return wt
end
end


---Formats a table cell containing ability combo values per attack
---Formats a table cell containing ability combo values per attack
---@param weapon table
---@param is_header boolean
---@return string
local function combo(weapon,is_header)
local function combo(weapon,is_header)
if is_header then return [[<th style="width:8%">Combo</th>]] end
if is_header then return [[<th style="width:8%">Combo</th>]] end
Line 351: Line 280:
end
end
return "<td>"..wt.."</td>"
return "<td>"..wt.."</td>"
end
local function determine_table_type(weapon)
if weapon.args then
if weapon.args[1] == "Shield" then return "Shield"
elseif (weapon.args[1]):lower():match("bow") or weapon.args[1] == "Firearm" or weapon.args[1] == "Throwable" then return "Ranged" end
elseif weapon.types then
if weapon.types.Shield then return "Shield"
elseif weapon.types.Bow or weapon.types.Crossbow or weapon.types.Firearm or weapon.types.Throwable then return "Ranged" end
end
return "Default"
end
end


Line 357: Line 298:
local row_type = {
local row_type = {
["Shield"] = {name,classes,slot_type,move_speed,armor_rating,impact_zones,action_move_speed_penalty},
["Shield"] = {name,classes,slot_type,move_speed,armor_rating,impact_zones,action_move_speed_penalty},
["Ranged"] = {name,classes,slot_type,move_speed,damage_on_hit,stats,impact_zones,animation_time,action_move_speed_penalty,slowdown_on_hit,initial_speed},
["Ranged"] = {name,classes,slot_type,move_speed,damage_on_hit,stats,impact_zones,action_move_speed_penalty,slowdown_on_hit,initial_speed},
["Default"] = {name,classes,slot_type,move_speed,damage_on_hit,stats,hitbox,impact_zones,combo,action_move_speed_penalty,slowdown_on_hit}
["Default"] = {name,classes,slot_type,move_speed,damage_on_hit,stats,hitbox,impact_zones,combo,action_move_speed_penalty,slowdown_on_hit}
}
}
Line 363: Line 304:


---Return a table row of data cells containing weapon information,
---Return a table row of data cells containing weapon information,
--- @param weapon table
--- @return string
local function row(weapon, is_header)
local function row(weapon, is_header)
is_header = is_header or false
is_header = is_header or false
local key = "Default"
if is_header then
if weapon.args[1] == "Shield" then key = "Shield" elseif (weapon.args[1]):lower():match("bow") or weapon.args[1]=="Firearm" then key = "Ranged" end
else
if weapon.types.Shield then key = "Shield" elseif weapon.types.Bow or weapon.types.Crossbow or weapon.types.Firearm then key = "Ranged" end
end


local wt = {}
local wt = {}
wt[#wt+1] = "<tr>"
wt[#wt+1] = "<tr>"
for _,content in ipairs(row_type[key]) do
for _,content in ipairs(row_type[determine_table_type(weapon)]) do
wt[#wt+1] = content(weapon,is_header)
wt[#wt+1] = content(weapon,is_header)
end
end
Line 386: Line 318:


---Returns a predetermined weapon list
---Returns a predetermined weapon list
---@param frame table contains args, first key is determined by types, second key must be {Uncraftable, Craftable, Artifact}
---@return table
local function get_list(frame)
local function get_list(frame)
if not (frame.args and frame.args[1] and frame.args[2]) then return {} end
if not (frame.args and frame.args[1] and frame.args[2]) then return {} end
Line 395: Line 325:
end
end


--- @param frame any
--- @return string
function p.draw_table(frame)
function p.draw_table(frame)
local wt = {}
local wt = {}
local item_list = get_list(frame)
if count(item_list) == 0 then return "<span style='font-size:24pt'>There are currently no items of this type.</span>" end


wt[#wt+1] = [[<table cellspacing="0" class="wikitable sortable jquery-tablesorter" style="text-align:center; vertical-align:middle;">]]
wt[#wt+1] = [[<table cellspacing="0" class="wikitable sortable jquery-tablesorter" style="text-align:center; vertical-align:middle;">]]
wt[#wt+1] = row(frame, true)
wt[#wt+1] = row(frame, true)
for _,item_name in ipairs(get_list(frame)) do
for _,item_name in ipairs(item_list) do
wt[#wt+1] = row(WD.Weapon[item_name])
wt[#wt+1] = row(WD.Weapon[item_name])
end
end
Line 411: Line 340:
end
end


--[=[###########################################################################
                Functions for individual weapon pages
#############################################################################]=]
local function p_projectile(wt,weapon_name)
local weapon = WD.Weapon[weapon_name]
if weapon.initialspeed ~= "" then
wt[#wt+1] = string.format("<h2>Projectile</h2>Initial Speed: %s<br>", weapon.initialspeed)
end
if weapon.piercecount ~= "" then
wt[#wt+1] = string.format("Pierce: %s<br>", weapon.piercecount)
end
end
local function p_damage_falloff(wt,weapon_name)
local weapon = WD.Weapon[weapon_name]
if weapon.damagefalloff == "" then return end
wt[#wt+1] = string.format("<h2>Damage Falloff</h2>%s has damage falloff depending on airtime of the projectile.<br><br>Projectile falloff damage chart:<br>X: Air time (seconds)<br>Y: %% Damage",weapon_name,weapon.initialspeed)
wt[#wt+1] = "<div style='width:500px'>"
wt[#wt+1] = weapon.damagefalloff
wt[#wt+1] = "</div><br>Learn more at [[Damage_Calculation#Projectile_Falloff|Projectile Falloff]]"
end
local function p_hitbox(wt,weapon_name)
local weapon = WD.Weapon[weapon_name]
if not weapon.hitbox or not weapon.hitbox.height then return end
wt[#wt+1] = "<h2>Hitbox</h2>"
wt[#wt+1] = "Height: "..weapon.hitbox.height
wt[#wt+1] = "<br>Width: "..weapon.hitbox.width
wt[#wt+1] = "<br>Depth: "..weapon.hitbox.depth
local primary_attack = weapon.abilities.Primary and weapon.abilities.Primary["Attack 1"] or {}
local secondary_attack = weapon.abilities.Secondary and weapon.abilities.Secondary["Attack 1"] or {}
if #primary_attack == 0 and #secondary_attack == 0 then return end
-- If is not ranged, and has primary or secondary attacks
if not (weapon.types["Bow"] or weapon.types["Crossow"] or weapon.types["ThrowableStuff"])
and (primary_attack.impactzones or secondary_attack.impactzones) then
local impact_zones = primary_attack.impactzones or secondary_attack.impactzones
wt[#wt+1] = "<br><br>The weapon's hitbox doesn't necessarily determine a weapon's reach due to different arm extension during attack animations. Thus, shorter weapons can have better reach than longer weapons.<br>[[Impact Zones]] of "
wt[#wt+1] = weapon.name
wt[#wt+1] = ":<div style='display:flex;align-items:center'><div>[[File:"
wt[#wt+1] = weapon.name
wt[#wt+1] = " Hitbox.png|x300px]]</div><div><span style='color:green'>Green Zone deals "
wt[#wt+1] = impact_zones[1]
wt[#wt+1] = " damage</span>"
if impact_zones[2] then
wt[#wt+1] = "<br><br><span style='color:orange'>Orange Zone deals "
wt[#wt+1] = impact_zones[2]
wt[#wt+1] = " damage</span>"
end
if impact_zones[3] then
wt[#wt+1] = "<br><br><span style='color:red'>Red Zone deals "
wt[#wt+1] = impact_zones[3]
wt[#wt+1] = " damage</span>"
end
wt[#wt+1] = "</div></div><p><br>'''Disclaimer''': The impact zone's locations are an approximation. The real hitboxes may be slightly larger, closer together, and/or follow the weapon's shape more closely. Image is not able to be updated automatically. If it is outdated, please report it to the [https://discord.gg/KbsxgACkFS Wiki Discord].</p>"
end
if primary_attack.impactpower or secondary_attack.impactpower then
wt[#wt+1] = "<br>'''Impact Power''' of "
wt[#wt+1] = weapon.name
wt[#wt+1] = ": "
wt[#wt+1] = tostring(primary_attack.impactpower or secondary_attack.impactpower)
wt[#wt+1] = "<br>Impact power helps with breaking crates, barricades or any other breakables in game. The higher the impact power the faster something breaks.<br>Impact power also affects shield blocking. If the attacking weapon's impact power is higher than shield's impact power, the shield holder staggers, and vice versa."
end
end
local function list_to_string(l,separator)
local ret = ""
for i,v in ipairs(l) do
if i~=1 then ret = ret..separator end
ret = ret..v
end
return ret
end
local function combo_format(wt,attacks,type)
if type == "Other" or type == "Block" then return end
local first = ""
wt[#wt+1] = "<span style='color:#eee8'>"
wt[#wt+1] = type
wt[#wt+1] = " Attack: </span>"
for i,name in ipairs(attacks.order) do
if i ~= 1 then wt[#wt+1] = "/" else first = name end
wt[#wt+1] = attacks[name].damagetype
end
wt[#wt+1] = " dealing "
for i,name in ipairs(attacks.order) do
if i ~= 1 then wt[#wt+1] = "/" end
wt[#wt+1] = attacks[name].combodamage
end
wt[#wt+1] = " damage on each swing, with impact zone: "
wt[#wt+1] = list_to_string(attacks[first].impactzones, "/")
wt[#wt+1] = "<br>"
end
local function attack_animation_times(wt,weapon,ability_name,attack_name)
local attack = weapon.animationtimes[ability_name][attack_name]
if attack["Windup"]            then wt[#wt+1] = string.format("Windup: %s<br>",                          attack["Windup"]) end
if attack["Hit"]              then wt[#wt+1] = string.format("Hit: %s<br>",                              attack["Hit"]) end
if attack["Second Windup"]    then wt[#wt+1] = string.format("Second Windup: %s<br>",                    attack["Second Windup"]) end
if attack["Second Hit"]        then wt[#wt+1] = string.format("Second Hit: %s<br>",                      attack["Second Hit"]) end
if attack["Recover"]          then wt[#wt+1] = string.format("&nbsp;&#10551; Recover: %s<br>",          attack["Recover"]) end
if attack["Alternate Recover"] then wt[#wt+1] = string.format("&nbsp;&#10551; Alternate Recover: %s<br>", attack["Alternate Recover"]) end
if attack["Finish"]            then wt[#wt+1] = string.format("&nbsp;&#10551; Finish: %s<br>",            attack["Finish"]) end
end
local function animation_format(wt, weapon, ability)
if ability == "Other" or ability == "Block" then return end
for i, ability_name in ipairs(weapon.abilities[ability].order) do
wt[#wt+1] = ability.." Combo: "..ability_name.."<br><div style='display:inline-block;margin-left:20px'>"
attack_animation_times(wt,weapon,ability,ability_name)
wt[#wt+1] = "</div><br><br>"
end
end
local function p_combo(wt, page_name)
local weapon = WD.Weapon[page_name]
if not (weapon.abilities.Primary or weapon.abilities.Secondary or weapon.abilities.Special) then return end
-- If is not ranged, and isn't shield, or at least isn't a Lantern Shield
if not (weapon.types.Bow or weapon.types.Crossow or weapon.types.ThrowableStuff)
and (not weapon.types.Shield or (weapon.name):match("Lantern Shield")) then
wt[#wt+1] = "<h2>Attack Animation Time</h2>"
for _,name in ipairs(weapon.abilities.order) do
combo_format(wt,weapon.abilities[name],name)
end
if weapon.abilities.Primary or weapon.abilities.Secondary or weapon.abilities.Special then
wt[#wt+1] = "<br><b>Attack Times</b><br><div style='display:inline-block;margin-left:20px'>"
for _,name in ipairs(weapon.abilities.order) do
animation_format(wt,weapon,name)
end
wt[#wt+1] = "</div>"
end
wt[#wt+1] = "Reference [[Attack Times]] for a glossary of the terms used above.<br>"
end
end
local function p_block(wt,weapon_name)
local weapon = WD.Weapon[weapon_name]
if not weapon.impactresistance or weapon.impactresistance == "" then return end
wt[#wt+1] = string.format("<h2>Block</h2>%s has a block ability with an impact resistance of <b>%s</b>.<br>To learn more about impact resistance see [[Impact Power]].", weapon.name, weapon.impactresistance)
end
local function p_action_move_slow_penalty(wt,weapon_name)
local weapon = WD.Weapon[weapon_name]
wt[#wt+1] = "<h2>Action Movement Slow</h2>When a player performs certain actions, such as attacking or defending themselves, they move slower."
wt[#wt+1] = "<div style='display:inline-block;margin-left:20px'>"
for _,ability in ipairs(weapon.abilities.order) do
for _,attack in ipairs(weapon.abilities[ability].order) do
-- There's a bug where Riposte values are recorded within "Other" abilities
-- Since ripose is covered in Special abilities, we will skip it here
if attack == "Riposte" then break end
local movespeedadd = weapon.abilities[ability][attack].effects
and weapon.abilities[ability][attack].effects.ItemActivateState
and weapon.abilities[ability][attack].effects.ItemActivateState.rarity.Global["Move Speed Add"]
or ""
local movement_multiplier = weapon.abilities[ability][attack].movementmultiplier or ""
local prepare_movement_multiplier = weapon.abilities[ability][attack].preparemovementmultiplier or ""
wt[#wt+1] = "<span style='color:#eee8; font-size:110%; font-weight:bold'>"
if ability == "Other" then wt[#wt+1] = attack else wt[#wt+1] = ability end
wt[#wt+1] = "</span><br>"
wt[#wt+1] = "<div style='display:inline-block;margin-left:20px'>"
if #prepare_movement_multiplier > 1 then
wt[#wt+1] = "<span style='color:#eee8'>Mid Attack:&nbsp;</span>x"
wt[#wt+1] = movement_multiplier
wt[#wt+1] = "<br><span style='color:#eee8'>Otherwise:&nbsp;</span>x"
wt[#wt+1] = prepare_movement_multiplier
elseif #movement_multiplier > 1 then
wt[#wt+1] = "<span style='color:#eee8'>Always:&nbsp;</span>x"
wt[#wt+1] = movement_multiplier
else
wt[#wt+1] = "<span style='color:#eee8'>Always:&nbsp;</span>"
wt[#wt+1] = movespeedadd
end
wt[#wt+1] = "</div>"
wt[#wt+1] = "<br><br>"
-- Everything except attacks within Other has invariant move mults
-- So after processessing the first attack, we break the loop
if ability ~= "Other" then break end
end
end
wt[#wt+1] = "</div>"
end
local function p_artifact(wt,weapon_name)
local weapon = WD.Weapon[weapon_name]
if weapon.isartifact or weapon.artifactname == "" then return end
wt[#wt+1] = string.format("<h2>Artifact</h2><p>%s has a powerful [[Artifacts|Artifact]] named [[%s]].</p>", weapon.name, weapon.artifactname)
end
local function tab_toggle(title,content,active,tabid)
local class = ""
if active then
class = " class='selected-tab tab-toggle tab'"
else
class = " class='tab-toggle tab'"
end
return string.format("<div style='display:inline-block' %s data-tabid='%s' data-tab='%s' title='%s'>%s</div>",class,tabid,title,title,content)
end
local function reference_table(wt,table_type)
local types_order = {"Sword","Dagger","Mace","Polearm","Axe","Throwable","Bow","Crossbow","Firearm","MagicStuff","Shield","LightSource"}
local localization = {
["Sword"]        = "Swords",
["Dagger"]      = "Daggers",
["Mace"]        = "Maces",
["Polearm"]      = "Polearms",
["Axe"]          = "Axes",
["Throwable"]    = "Throwables",
["Bow"]          = "Bows",
["Crossbow"]    = "Crossbows",
["Firearm"]      = "Firearms",
["MagicStuff"]  = "Magic Stuff",
["Shield"]      = "Shields",
["LightSource"]  = "Light Sources"
}
local separator = "]] • [["
wt[#wt+1] = "<table cellspacing='0' class='wikitable sortable jquery-tablesorter' style='width:100%;text-align:center;vertical-align:middle;margin-top:15px'>"
for _,weapon_type in ipairs(types_order) do
wt[#wt+1] = string.format("<tr><td style='font-weight:bold; background-color:rgb(220,220,220,0.2); width:10%%'>[[Weapons#%s|%s]]</td><td style='text-align-last:left;padding:10px 25px'>",localization[weapon_type],localization[weapon_type])
if WD[weapon_type][table_type] then
wt[#wt+1] = "[["
for i,v in ipairs(WD[weapon_type][table_type]) do
if i~=1 then wt[#wt+1] = separator end
wt[#wt+1] = v
end
wt[#wt+1] = "]]"
end
wt[#wt+1] = "</td></tr>"
end
wt[#wt+1] = "</table>"
end
function p.references()
local table_types = {"Uncraftable","Craftable","Artifact"}
local wt = {}
wt[#wt+1] = "<br><h2>References</h2>"
wt[#wt+1] = "<div style='display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;margin-top:15px'>"
for i,table_type in ipairs(table_types) do
wt[#wt+1] = tab_toggle(table_type,table_type.." Weapons",i==1,"")
end
wt[#wt+1] = "</div>"
for i,table_type in ipairs(table_types) do
wt[#wt+1] = "<div class='"..table_type.."-data'"
if i==1 then wt[#wt+1] = ">" else wt[#wt+1] = "style='display:none'>" end
reference_table(wt,table_type)
wt[#wt+1] = "</div>"
end
return table.concat(wt)
end
function p.page(f)
local page_name = f.args.name
local wt = {}
p_projectile(wt,page_name)
p_damage_falloff(wt,page_name)
p_hitbox(wt,page_name)
p_combo(wt,page_name)
p_block(wt,page_name)
p_action_move_slow_penalty(wt,page_name)
p_artifact(wt,page_name)
return concat(wt)
end


return p
return p
---- Test on wiki withmw.log(p.draw_table({args={"Crossbow","Uncraftable"}}))
 
---- Test on wiki with:  mw.log(p.draw_table({args={"Shield","Craftable"}}))
 
---- Test on wiki with:  mw.log(p.draw_table({args={"Polearm","Artifact"}}))
-- Test on wiki with
-- mw.log(p.draw_table({args={"Crossbow","Uncraftable"}}))
-- mw.log(p.draw_table({args={"Shield","Craftable"}}))
-- mw.log(p.draw_table({args={"Polearm","Artifact"}}))
 
 
-- mw.log(p.page({args={name="Rapier"}}))

Latest revision as of 21:32, 15 April 2026

Overview

Functions for making Weapon table. Data comes from Data:Weapon.json.

Functions

draw_table

Creates a table of weapons

Parameters

  • 1 - <Type>
  • 2 - Craftable or Uncraftable or Artifact


draw_table examples

Sword, Craftable


{{#invoke:Weapon|draw_table|Sword|Craftable}}


NameClassSlotMovement SpeedDamage on HitStatsHitbox + Impact ResistImpact Zones + Impact PowerComboAction Movement PenaltySlowdown On Hit
Fighter
Warlock
Sorcerer
Main-Hand
Two Handed
-30
Physical Base Weapon Damage
38
Undead Race Damage Bonus
15%
Blade of Righteousness Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Special Riposte Attack 2
100%/90% + 4

Impact Resist
3
Primary Attacks
Pierce/Slash/Slash
100%/105%/110%

Special Attacks
Slash/Slash
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-30/-30/-30 for 0.35/0.35/0.35s
Ranger
Rogue
Bard
Main-Hand
One Handed
-15
Physical Base Weapon Damage
26
Magical Base Weapon Damage
1
Demon's Glee Hitbox.pngPrimary Attack 1
100%/90% + 2

Primary Attack 2
100%/90% + 2

Primary Attack 3
100%/90% + 2

Primary Attack 4
100%/90% + 2

Special Riposte Attack 1
100%/90% + 2

Special Riposte Attack 2
100%/90% + 2

Impact Resist
3
Primary Attacks
Pierce/Pierce/Pierce/Pierce
100%/105%/110%/115%

Special Attacks
Slash/Pierce
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-15/-15/-15/-15 for 0.2/0.2/0.2/0.2s
Fighter
Warlock
Sorcerer
Main-Hand
Two Handed
-30
Physical Base Weapon Damage
39
Undead Race Damage Bonus
15%
Divine Blade Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Special Riposte Attack 2
100%/90% + 4

Impact Resist
3
Primary Attacks
Pierce/Slash/Slash
100%/105%/110%

Special Attacks
Slash/Slash
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-30/-30/-30 for 0.35/0.35/0.35s
Fighter
Rogue
Ranger
Bard
Off-Hand
One Handed
-15
Physical Base Weapon Damage
26
Undead Race Damage Bonus
15%
Divine Short Sword Hitbox.pngSecondary Attack 1
100%/90% + 2

Secondary Attack 2
100%/90% + 2

Secondary Attack 3
100%/90% + 2

Special Riposte Attack 1
100%/90% + 2

Special Riposte Attack 2
100%/90% + 2

Impact Resist
3
Secondary Attacks
Slash/Slash/Pierce
100%/105%/110%

Special Attacks
Slash/Pierce
150%/150%
Secondary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-15/-15/-30 for 0.2/0.2/0.2s
Fighter
Bard
Warlock
Sorcerer
Main-Hand
One Handed
-25
Physical Base Weapon Damage
37
Armor Penetration
2~3%
Falchion of Honor Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Impact Resist
3
Primary Attacks
Slash/Slash/Slash
100%/105%/110%

Special Attacks
Slash
150%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%
Hitslow
-25/-25/-25 for 0.35/0.35/0.35s
Wizard
Warlock
Sorcerer
Main-Hand
Two Handed
-25
Physical Base Weapon Damage
13
Magical Base Weapon Damage
18
Action Speed
2%
Frostlight Crystal Sword Hitbox.pngPrimary Attack 1
100%/90% + 3

Primary Attack 2
100%/90% + 3

Primary Attack 3
100%/90% + 3

Special Riposte Attack 1
100%/90% + 3

Impact Resist
3
Primary Attacks
Slash/Slash/Slash
100%/105%/110%

Special Attacks
Slash
150%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x85%

Special Attacks
Always: x85%
Hitslow
-25/-25/-25 for 0.35/0.35/0.35s
Fighter
Barbarian
Main-Hand
One Handed
-20
Physical Base Weapon Damage
33
Luck
10
Magical Damage Reduction
2%
Golden Viking Sword Hitbox.pngPrimary Attack 1
100%/90% + 3

Primary Attack 2
100%/90% + 3

Primary Attack 3
100%/90% + 3

Special Riposte Attack 1
100%/90% + 3

Impact Resist
3
Primary Attacks
Slash/Slash/Pierce
100%/105%/110%

Special Attacks
Slash
150%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%
Hitslow
-20/-20/-20 for 0.35/0.35/0.35s
Fighter
Barbarian
Warlock
Main-Hand
Two Handed
-40
Physical Base Weapon Damage
45~46
Physical Power
5
Undead Race Damage Bonus
30%
Grimslayer Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Impact Resist
3
Primary Attacks
Slash/Slash/Slash
100%/105%/110%

Special Attacks
Slash
125%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x85%

Special Attacks
Always: x85%
Hitslow
-40/-40/-40 for 0.35/0.35/0.35s
Fighter
Bard
Warlock
Sorcerer
Main-Hand
One Handed
-23
Physical Base Weapon Damage
33
Projectile Damage Reduction
2%
File:Obsidian Cutlass Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Primary Attack 4
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Special Riposte Attack 2
100%/90% + 4

Impact Resist
3
Primary Attacks
Slash/Slash/Slash/Slash
100%/105%/110%/115%

Special Attacks
Slash/Slash
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-25/-25/-25/-25 for 0.35/0.35/0.35/0.35s
Fighter
Rogue
Ranger
Bard
Off-Hand
One Handed
-15
Physical Base Weapon Damage
25
Undead Race Damage Bonus
15%
Short Sword of Righteousness Hitbox.pngSecondary Attack 1
100%/90% + 2

Secondary Attack 2
100%/90% + 2

Secondary Attack 3
100%/90% + 2

Special Riposte Attack 1
100%/90% + 2

Special Riposte Attack 2
100%/90% + 2

Impact Resist
3
Secondary Attacks
Slash/Slash/Pierce
100%/105%/110%

Special Attacks
Slash/Pierce
150%/150%
Secondary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-15/-15/-30 for 0.2/0.2/0.2s
Wizard
Warlock
Sorcerer
Main-Hand
Two Handed
-25
Physical Base Weapon Damage
13
Magical Base Weapon Damage
18
Outgoing Magical Healing Add
2
Move Speed Bonus
2%
Sovereign's Ghostblade Hitbox.pngPrimary Attack 1
100%/90% + 3

Primary Attack 2
100%/90% + 3

Primary Attack 3
100%/90% + 3

Special Riposte Attack 1
100%/90% + 3

Impact Resist
3
Primary Attacks
Slash/Slash/Slash
100%/105%/110%

Special Attacks
Slash
150%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x85%

Special Attacks
Always: x85%
Hitslow
-25/-25/-25 for 0.35/0.35/0.35s
Fighter
Warlock
Sorcerer
Main-Hand
Two Handed
-30
Physical Base Weapon Damage
40
Action Speed
5%
Headshot Damage Modifier
5%
File:Spectral Blade Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Special Riposte Attack 2
100%/90% + 4

Impact Resist
3
Primary Attacks
Pierce/Slash/Slash
100%/105%/110%

Special Attacks
Slash/Slash
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-30/-30/-30 for 0.35/0.35/0.35s
Fighter
Warlock
Sorcerer
Main-Hand
Two Handed
-30
Physical Base Weapon Damage
37
Undead Race Damage Bonus
15%
Sterling Blade Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Special Riposte Attack 2
100%/90% + 4

Impact Resist
3
Primary Attacks
Pierce/Slash/Slash
100%/105%/110%

Special Attacks
Slash/Slash
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-30/-30/-30 for 0.35/0.35/0.35s
Fighter
Rogue
Ranger
Bard
Off-Hand
One Handed
-15
Physical Base Weapon Damage
24
Undead Race Damage Bonus
15%
Sterling Short Sword Hitbox.pngSecondary Attack 1
100%/90% + 2

Secondary Attack 2
100%/90% + 2

Secondary Attack 3
100%/90% + 2

Special Riposte Attack 1
100%/90% + 2

Special Riposte Attack 2
100%/90% + 2

Impact Resist
3
Secondary Attacks
Slash/Slash/Pierce
100%/105%/110%

Special Attacks
Slash/Pierce
150%/150%
Secondary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-15/-15/-30 for 0.2/0.2/0.2s
Fighter
Bard
Warlock
Sorcerer
Main-Hand
One Handed
-25
Physical Base Weapon Damage
37
Debuff Duration Bonus
1.5%
File:Tidal Falchion Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Impact Resist
3
Primary Attacks
Slash/Slash/Slash
100%/105%/110%

Special Attacks
Slash
150%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%
Hitslow
-25/-25/-25 for 0.35/0.35/0.35s
Fighter
Warlock
Sorcerer
Main-Hand
Two Handed
-30
Physical Base Weapon Damage
41
Magical Base Weapon Damage
2
True Magical Damage
2
Void Blade Hitbox.pngPrimary Attack 1
100%/90% + 4

Primary Attack 2
100%/90% + 4

Primary Attack 3
100%/90% + 4

Special Riposte Attack 1
100%/90% + 4

Special Riposte Attack 2
100%/90% + 4

Impact Resist
3
Primary Attacks
Pierce/Slash/Slash
100%/105%/110%

Special Attacks
Slash/Slash
135%/135%
Primary Attacks
Mid Attack: x60%/60%/60%
Otherwise: x92.5%/92.5%/92.5%

Block Actions
Always: x97%

Special Attacks
Always: x85%/85%
Hitslow
-30/-30/-30 for 0.35/0.35/0.35s


Firearm, Uncraftable


{{#invoke:Weapon|draw_table|Firearm|Uncraftable}}

Code

NameClassSlotMovement SpeedDamage on HitStatsImpact Zones + Impact PowerAction Movement PenaltySlowdown On HitInitial Projectile Speed (m/s)
Fighter
Ranger
Main-Hand
Two Handed
-30
Physical Base Weapon Damage
45~46
47~48
49~50
51~52
53~54
55~56
57~58
True Physical Damage
10
Primary Attack 1
100% + 6

Impact Resist
3
Primary Attacks
Always: x50%

Block Actions
Always: x85%
Hitslow
-135 for 1.5s
38

local p = {}
local WD = mw.loadJsonData("Data:Weapon.json") --Global var holding tables from Data:Weapon.json
local concat = table.concat

---Get the item's size in pixels
local function size(item)
	return tostring(item.invwidth*45).."x"..tostring(item.invheight*45).."px"
end

---Counts number of keys. Tables from Weapon.json don't work with next() or the # operator
local function count(t)
	local c = 0
	for _ in pairs(t) do c = c + 1 end
	return c
end

---Marks up a list of values with color based on rarity
local function color_values(weapon, values)
	if values == nil then return "" end
	if type(values)=="string" or type(values)=="number" then
		if count(weapon.rarities) == 1 then return "<span class='cr"..weapon.rarities[1].."'>"..values.."</span>" end
		return tostring(values)
	end

	local wt = ""
	local newline = ""
	for i, key in ipairs(weapon.rarities) do
		wt = wt..newline.."<span class='cr"..key.."'>"..tostring(values[tonumber(key)] or "").."</span>"
		if i == 1 then newline = "<br>" end
	end
	return wt
end

---Marks up a stat block with the stat name and colored values and appropriate margins and alignment
local function inline_block(weapon,stat)
	if weapon.stats[(stat):lower()] == nil then return "" end
	return "<div style='display:inline-block;vertical-align:top;margin:0 15px 0 15px'><b style='color:#eee8'>"..stat.."</b><br>"..color_values(weapon,weapon.stats[(stat):lower()]).."</div>"
end

---Formats a table cell for the iconbox
local function name(item, is_header)
	if is_header then return [[<th style="width:5%">Name</th>]] end

	local color = "2"
	local bold_link = "|<b>"..item.name
	if count(item.rarities) == 1 then
		color = item.rarities[1]
		bold_link = "|<b class=cr"..color..">"..item.name
	end

	local icon_amount = ""
	if item.maxammocount > 0 then
		icon_amount = "<span class='iconamount' style='pointer-events:none;color:#EEEA;font-size:16px'>"..item.maxammocount.."</span>"
	end

	return "<td><div class='iconbox' style='width:max-content;align-items:center'><div class='rarity"..color.." rounded relative'>[[File:"..item.name..".png|"..size(item).."|link="..item.name.."]]"
				..icon_amount
			.."</div><br>[["..item.name..bold_link.."</b>]]</div></td>"
end

---Formats the weapon's list of classes into a table cell of classes separated by linebreaks
local function classes(weapon,is_header)
	if is_header then return [[<th style="width:5%">Class</th>]] end

	local wt = ""
	local newline = ""
	for i, class in ipairs(weapon.classes) do
		wt = wt..newline..class
		if i == 1 then newline = "<br>" end -- Subsequent loops will prefix a linebreak
	end
	return "<td>"..wt.."</td>"
end

---Formats a table cell of slottype and handtype
local function slot_type(weapon,is_header)
	if is_header then return [[<th style="width:5%">Slot</th>]] end

	return "<td>"..weapon.slottype.."<br>"..weapon.handtype.."</td>"
end

---Formats a table cell of move speeds
local function move_speed(weapon,is_header)
	if is_header then return [[<th style="width:5%">Movement Speed</th>]] end

	return "<td>"..color_values(weapon,weapon.stats["move speed"]).."</td>"
end

---Formats a table cell of Armor rating and Magical Resistance
local function armor_rating(weapon,is_header)
	if is_header then return [[<th style="width:15%">Armor Rating</th>]] end

	return "<td>"..inline_block(weapon,"Armor Rating")..inline_block(weapon,"Magical Resistance").."</td>"
end

local function impact_zones(weapon,is_header)
	if is_header then return [[<th style="width:10%">Impact Zones + Impact Power</th>]] end

	local wt= ""
	for i,ability in ipairs(weapon.abilities.order) do
		if ability ~= "Other" then
			for j,attack in ipairs(weapon.abilities[ability].order) do
				if weapon.abilities[ability][attack].impactpower and weapon.abilities[ability][attack].impactzones then
					wt = wt.."<span style='color:#eee8'>"..ability.." "..attack.."</span><br>"
					for k,impact_zone in ipairs(weapon.abilities[ability][attack].impactzones) do
						if k~=1 then wt = wt.."/" end
						wt = wt..impact_zone
					end
					wt = wt.." + "..weapon.abilities[ability][attack].impactpower.."<br><br>"
				end
			end
		end
	end
	if weapon.impactresistance~="" then
		wt = wt.."<span style='color:#eee8'>Impact Resist</span><br>"..weapon.impactresistance
	end
	return "<td>"..wt.."</td>"
end

---Helper function: Collects move mult and prepare move mult for display
local function collect_multipliers(ability)
	local move, prep_move = "",""
	for i,attack_name in ipairs(ability.order) do
		if ability[attack_name].movementmultiplier then
			if i ~= 1 then
				move = move.."/"
				prep_move = prep_move.."/"
			end
			move = move..(ability[attack_name].movementmultiplier or "")
			prep_move = prep_move..(ability[attack_name].preparemovementmultiplier or "")
			i = i + 1
		end
	end

	return move,prep_move
end

---Formats a table cell containing speed penalties for each action
local function action_move_speed_penalty(weapon,is_header)
	if is_header then return [=[<th style="width:10%">[[Weapons#Movement_Multiplier_Explanation|Action Movement Penalty]]</th>]=] end

	local order = {} --reconstruct order to exclude the other ability group
	for _,ability in ipairs(weapon.abilities.order) do
		if ability ~= "Other" then order[#order+1] = ability end
	end

	local wt = ""
	for i,ability_name in ipairs(order) do
		if i~=1 then wt = wt.."<br><br>" end

		wt = wt.."<span style='color:#eee8; font-size:110%; font-weight:bold'>"
		if ability_name=="Other" or ability_name=="Block" then wt = wt..ability_name.." Actions" else wt = wt..ability_name.." Attacks" end
		wt = wt.."</span><br>"

		local movement_multiplier,prepare_movement_multiplier = collect_multipliers(weapon.abilities[ability_name])
		if #prepare_movement_multiplier > 3 then
			wt = wt.."<span style='color:#eee8'>Mid Attack:&nbsp;</span>x"..movement_multiplier
			.."<br><span style='color:#eee8'>Otherwise:&nbsp;</span>x"..prepare_movement_multiplier
		else
			wt = wt.."<span style='color:#eee8'>Always:&nbsp;</span>x"..movement_multiplier
		end
	end
	return "<td>"..wt.."</td>"
end

---Formats a table cell containing base Physical and Magical damage, if present.
local function damage_on_hit(weapon,is_header)
	if is_header then return [[<th style="width:25%">Damage on Hit</th>]] end

	local wt = ""
	wt = wt..inline_block(weapon,"Physical Base Weapon Damage")
			..inline_block(weapon,"Magical Base Weapon Damage")
	return "<td>"..wt.."</td>"
end

---These stats get their own table cells, as such they are excluded from the stats cell to avoid needless redundancy
local exclude_stat = {["Physical Base Weapon Damage"]=true,["Magical Base Weapon Damage"]=true,["Armor Rating"]=true,["Magical Resistance"]=true,["Move Speed"]=true}

---Formats a table cell containing any stats not covered by the rest of the row
local function stats(weapon,is_header)
	if is_header then return [[<th style="width:12%">Stats</th>]] end

	local wt = ""
	for i,stat in ipairs(weapon.stats.order) do
		if not exclude_stat[stat] then
			wt = wt..inline_block(weapon,stat)
		end
	end
	return "<td>"..wt.."</td>"
end

---Formats a table cell containing animation times for ranges weapons
local function animation_time(weapon,is_header)
	if is_header then return [[<th style="width:10%">Animation Times</th>]] end

	local windup = "<br>Windup:"
	local finish = "<br>Finish:"
	local reload = "<br><br><span style='color:#eee8'>Reload</span><br>"

	if weapon.animationtimes["Attack 1"] then
		-- windup = windup..weapon.animationtimes["Attack 1"].Windup
		-- finish = finish..weapon.animationtimes["Attack 1"].Finish
	else
		windup = ""
		finish = ""
	end
	if weapon.animationtimes["Other"] then
		-- reload = reload..weapon.animationtimes["Other"].Reload
	else
		reload = ""
	end

	if #windup == 0 and #finish == 0 and #reload == 0 then
		return ""
	else
		return "<td><span style='color:#eee8'>Primary Attack</span>"..windup..finish..reload.."</td>"
	end
end

---Formats a table cell containing hitslow for each attack
local function slowdown_on_hit(weapon,is_header)
	if is_header then return [[<th style="width:8%">Slowdown On Hit</th>]] end
	if not (weapon.abilities.Primary or weapon.abilities.Secondary or weapon.abilities.Special) then return "<td></td>" end

	local ability_type = "Primary"
	if weapon.slottype == "Off-Hand" then ability_type = "Secondary" end

	local wt = "<span style='color:#eee8'>Hitslow</span><br>"
	local duration = " for "
	for i,attack in ipairs(weapon.abilities[ability_type].order) do
		if i~=1 then wt = wt.."/" end
		wt = wt..(weapon.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Move Speed Add"]
				or weapon.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Move Speed Bonus"] or "")
	end
	for i,attack in ipairs(weapon.abilities[ability_type].order) do
		if i~=1 then duration = duration.."/" end
		duration = duration..(weapon.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Duration"] or "")
	end
	return "<td>"..wt..duration.."s</td>"
end

---Formats a table cell containing the projectile's initial speed
local function initial_speed(weapon,is_header)
	if is_header then return [[<th style="width:5%">Initial Projectile Speed (m/s)</th>]] end

	return "<td>"..weapon.initialspeed.."</td>"
end

---Formats a table cell containing the hitbox image resized by inventory size
local function hitbox(weapon,is_header)
	if is_header then return [[<th style="width:10%">Hitbox + Impact Resist</th>]] end

	return "<td>[[File:"..weapon.name.." Hitbox.png|link=|"..size(weapon).."]]</td>"
end

---Helps format a string containing either Combo Damage or damagetype values
local function format_combo(weapon,ability,value)
	local wt = ""
	for i,attack in ipairs(weapon.abilities[ability].order) do
		if i~=1 then wt = wt.."/" end
		wt = wt..(weapon.abilities[ability][attack][value] or "")
	end
	return wt
end

---Formats a table cell containing ability combo values per attack
local function combo(weapon,is_header)
	if is_header then return [[<th style="width:8%">Combo</th>]] end

	local wt = ""
	if weapon.abilities["Primary"] then
		wt = wt.."<span style='color:#eee8'>Primary Attacks</span><br>"..format_combo(weapon,"Primary","damagetype").."<br>"..format_combo(weapon,"Primary","combodamage")
	end
	if weapon.abilities["Secondary"] then
		if #wt ~= 0 then wt = wt.."<br><br>" end
		wt = wt.."<span style='color:#eee8'>Secondary Attacks</span><br>"..format_combo(weapon,"Secondary","damagetype").."<br>"..format_combo(weapon,"Secondary","combodamage")
	end
	if weapon.abilities["Special"] then
		if wt:match("Primary") or wt:match("Secondary") then wt = wt.."<br><br>" end
		wt = wt.."<span style='color:#eee8'>Special Attacks</span><br>"..format_combo(weapon,"Special","damagetype").."<br>"..format_combo(weapon,"Special","combodamage")
	end
	return "<td>"..wt.."</td>"
end

local function determine_table_type(weapon)
	if weapon.args then
		if weapon.args[1] == "Shield" then return "Shield"
		elseif (weapon.args[1]):lower():match("bow") or weapon.args[1] == "Firearm" or weapon.args[1] == "Throwable" then return "Ranged" end
	elseif weapon.types then
		if weapon.types.Shield then return "Shield"
		elseif weapon.types.Bow or weapon.types.Crossbow or weapon.types.Firearm or weapon.types.Throwable then return "Ranged" end
	end

	return "Default"
end


---Determines which table data goes into the table row
local row_type = {
	["Shield"] = {name,classes,slot_type,move_speed,armor_rating,impact_zones,action_move_speed_penalty},
	["Ranged"] = {name,classes,slot_type,move_speed,damage_on_hit,stats,impact_zones,action_move_speed_penalty,slowdown_on_hit,initial_speed},
	["Default"] = {name,classes,slot_type,move_speed,damage_on_hit,stats,hitbox,impact_zones,combo,action_move_speed_penalty,slowdown_on_hit}
}


---Return a table row of data cells containing weapon information,
local function row(weapon, is_header)
	is_header = is_header or false

	local wt = {}
	wt[#wt+1] = "<tr>"
	for _,content in ipairs(row_type[determine_table_type(weapon)]) do
		wt[#wt+1] = content(weapon,is_header)
	end
	wt[#wt+1] = "</tr>"

	return concat(wt)
end

---Returns a predetermined weapon list
local function get_list(frame)
	if not (frame.args and frame.args[1] and frame.args[2]) then return {} end
	if not (WD[frame.args[1]] and WD[frame.args[1]][frame.args[2]]) then return {} end

	return WD[frame.args[1]][frame.args[2]]
end

function p.draw_table(frame)
	local wt = {}
	local item_list = get_list(frame)
	if count(item_list) == 0 then return "<span style='font-size:24pt'>There are currently no items of this type.</span>" end

	wt[#wt+1] = [[<table cellspacing="0" class="wikitable sortable jquery-tablesorter" style="text-align:center; vertical-align:middle;">]]
	wt[#wt+1] = row(frame, true)
	for _,item_name in ipairs(item_list) do
		wt[#wt+1] = row(WD.Weapon[item_name])
	end
	wt[#wt+1] = "</table>"

	return concat(wt)
end



--[=[###########################################################################
                Functions for individual weapon pages
#############################################################################]=]
local function p_projectile(wt,weapon_name)
	local weapon = WD.Weapon[weapon_name]
	if weapon.initialspeed ~= "" then 
		wt[#wt+1] = string.format("<h2>Projectile</h2>Initial Speed: %s<br>", weapon.initialspeed)
	end
	if weapon.piercecount ~= "" then
		wt[#wt+1] = string.format("Pierce: %s<br>", weapon.piercecount)
	end
end

local function p_damage_falloff(wt,weapon_name)
	local weapon = WD.Weapon[weapon_name]
	if weapon.damagefalloff == "" then return end

	wt[#wt+1] = string.format("<h2>Damage Falloff</h2>%s has damage falloff depending on airtime of the projectile.<br><br>Projectile falloff damage chart:<br>X: Air time (seconds)<br>Y: %% Damage",weapon_name,weapon.initialspeed)
	wt[#wt+1] = "<div style='width:500px'>"
	wt[#wt+1] = weapon.damagefalloff
	wt[#wt+1] = "</div><br>Learn more at [[Damage_Calculation#Projectile_Falloff|Projectile Falloff]]"
end

local function p_hitbox(wt,weapon_name)
	local weapon = WD.Weapon[weapon_name]
	if not weapon.hitbox or not weapon.hitbox.height then return end

	wt[#wt+1] = "<h2>Hitbox</h2>"
	wt[#wt+1] = "Height: "..weapon.hitbox.height
	wt[#wt+1] = "<br>Width: "..weapon.hitbox.width
	wt[#wt+1] = "<br>Depth: "..weapon.hitbox.depth

	local primary_attack = weapon.abilities.Primary and weapon.abilities.Primary["Attack 1"] or {}
	local secondary_attack = weapon.abilities.Secondary and weapon.abilities.Secondary["Attack 1"] or {}
	if #primary_attack == 0 and #secondary_attack == 0 then return end

	-- If is not ranged, and has primary or secondary attacks
	if not (weapon.types["Bow"] or weapon.types["Crossow"] or weapon.types["ThrowableStuff"])
			and (primary_attack.impactzones or secondary_attack.impactzones) then
		local impact_zones = primary_attack.impactzones or secondary_attack.impactzones
		wt[#wt+1] = "<br><br>The weapon's hitbox doesn't necessarily determine a weapon's reach due to different arm extension during attack animations. Thus, shorter weapons can have better reach than longer weapons.<br>[[Impact Zones]] of "
		wt[#wt+1] = weapon.name
		wt[#wt+1] = ":<div style='display:flex;align-items:center'><div>[[File:"
		wt[#wt+1] = weapon.name
		wt[#wt+1] = " Hitbox.png|x300px]]</div><div><span style='color:green'>Green Zone deals "
		wt[#wt+1] = impact_zones[1]
		wt[#wt+1] = " damage</span>"
		if impact_zones[2] then
			wt[#wt+1] = "<br><br><span style='color:orange'>Orange Zone deals "
			wt[#wt+1] = impact_zones[2]
			wt[#wt+1] = " damage</span>"
		end
		if impact_zones[3] then
			wt[#wt+1] = "<br><br><span style='color:red'>Red Zone deals "
			wt[#wt+1] = impact_zones[3]
			wt[#wt+1] = " damage</span>"
		end
		wt[#wt+1] = "</div></div><p><br>'''Disclaimer''': The impact zone's locations are an approximation. The real hitboxes may be slightly larger, closer together, and/or follow the weapon's shape more closely. Image is not able to be updated automatically. If it is outdated, please report it to the [https://discord.gg/KbsxgACkFS Wiki Discord].</p>"
	end

	if primary_attack.impactpower or secondary_attack.impactpower then
		wt[#wt+1] = "<br>'''Impact Power''' of "
		wt[#wt+1] = weapon.name
		wt[#wt+1] = ": "
		wt[#wt+1] = tostring(primary_attack.impactpower or secondary_attack.impactpower)
		wt[#wt+1] = "<br>Impact power helps with breaking crates, barricades or any other breakables in game. The higher the impact power the faster something breaks.<br>Impact power also affects shield blocking. If the attacking weapon's impact power is higher than shield's impact power, the shield holder staggers, and vice versa."
	end
end

local function list_to_string(l,separator)
	local ret = ""
	for i,v in ipairs(l) do
		if i~=1 then ret = ret..separator end
		ret = ret..v
	end
	return ret
end

local function combo_format(wt,attacks,type)
	if type == "Other" or type == "Block" then return end
	local first = ""

	wt[#wt+1] = "<span style='color:#eee8'>"
	wt[#wt+1] = type
	wt[#wt+1] = " Attack: </span>"

	for i,name in ipairs(attacks.order) do
		if i ~= 1 then wt[#wt+1] = "/" else first = name end
		wt[#wt+1] = attacks[name].damagetype
	end

	wt[#wt+1] = " dealing "

	for i,name in ipairs(attacks.order) do
		if i ~= 1 then wt[#wt+1] = "/" end
		wt[#wt+1] = attacks[name].combodamage
	end

	wt[#wt+1] = " damage on each swing, with impact zone: "
	wt[#wt+1] = list_to_string(attacks[first].impactzones, "/")
	wt[#wt+1] = "<br>"
end

local function attack_animation_times(wt,weapon,ability_name,attack_name)
	local attack = weapon.animationtimes[ability_name][attack_name]
	if attack["Windup"]            then wt[#wt+1] = string.format("Windup: %s<br>",                           attack["Windup"]) end
	if attack["Hit"]               then wt[#wt+1] = string.format("Hit: %s<br>",                              attack["Hit"]) end
	if attack["Second Windup"]     then wt[#wt+1] = string.format("Second Windup: %s<br>",                    attack["Second Windup"]) end
	if attack["Second Hit"]        then wt[#wt+1] = string.format("Second Hit: %s<br>",                       attack["Second Hit"]) end
	if attack["Recover"]           then wt[#wt+1] = string.format("&nbsp;&#10551; Recover: %s<br>",           attack["Recover"]) end
	if attack["Alternate Recover"] then wt[#wt+1] = string.format("&nbsp;&#10551; Alternate Recover: %s<br>", attack["Alternate Recover"]) end
	if attack["Finish"]            then wt[#wt+1] = string.format("&nbsp;&#10551; Finish: %s<br>",            attack["Finish"]) end
end

local function animation_format(wt, weapon, ability)
	if ability == "Other" or ability == "Block" then return end

	for i, ability_name in ipairs(weapon.abilities[ability].order) do
		wt[#wt+1] = ability.." Combo: "..ability_name.."<br><div style='display:inline-block;margin-left:20px'>"
		attack_animation_times(wt,weapon,ability,ability_name)
		wt[#wt+1] = "</div><br><br>"
	end
end

local function p_combo(wt, page_name)
	local weapon = WD.Weapon[page_name]
	if not (weapon.abilities.Primary or weapon.abilities.Secondary or weapon.abilities.Special) then return end

	-- If is not ranged, and isn't shield, or at least isn't a Lantern Shield
	if not (weapon.types.Bow or weapon.types.Crossow or weapon.types.ThrowableStuff)
		and (not weapon.types.Shield or (weapon.name):match("Lantern Shield")) then

		wt[#wt+1] = "<h2>Attack Animation Time</h2>"

		for _,name in ipairs(weapon.abilities.order) do
			combo_format(wt,weapon.abilities[name],name)
		end

		if weapon.abilities.Primary or weapon.abilities.Secondary or weapon.abilities.Special then
			wt[#wt+1] = "<br><b>Attack Times</b><br><div style='display:inline-block;margin-left:20px'>"
			for _,name in ipairs(weapon.abilities.order) do
				animation_format(wt,weapon,name)
			end
			wt[#wt+1] = "</div>"
		end
		wt[#wt+1] = "Reference [[Attack Times]] for a glossary of the terms used above.<br>"
	end
end

local function p_block(wt,weapon_name)
	local weapon = WD.Weapon[weapon_name]
	if not weapon.impactresistance or weapon.impactresistance == "" then return end

	wt[#wt+1] = string.format("<h2>Block</h2>%s has a block ability with an impact resistance of <b>%s</b>.<br>To learn more about impact resistance see [[Impact Power]].", weapon.name, weapon.impactresistance)
end

local function p_action_move_slow_penalty(wt,weapon_name)
	local weapon = WD.Weapon[weapon_name]

	wt[#wt+1] = "<h2>Action Movement Slow</h2>When a player performs certain actions, such as attacking or defending themselves, they move slower."
	wt[#wt+1] = "<div style='display:inline-block;margin-left:20px'>"
	for _,ability in ipairs(weapon.abilities.order) do
		for _,attack in ipairs(weapon.abilities[ability].order) do
			-- There's a bug where Riposte values are recorded within "Other" abilities
			-- Since ripose is covered in Special abilities, we will skip it here
			if attack == "Riposte" then break end

			local movespeedadd = weapon.abilities[ability][attack].effects
								and weapon.abilities[ability][attack].effects.ItemActivateState
								and weapon.abilities[ability][attack].effects.ItemActivateState.rarity.Global["Move Speed Add"]
								or ""
			local movement_multiplier = weapon.abilities[ability][attack].movementmultiplier or ""
			local prepare_movement_multiplier = weapon.abilities[ability][attack].preparemovementmultiplier or ""

			wt[#wt+1] = "<span style='color:#eee8; font-size:110%; font-weight:bold'>"
			if ability == "Other" then wt[#wt+1] = attack else wt[#wt+1] = ability end
			wt[#wt+1] = "</span><br>"
			wt[#wt+1] = "<div style='display:inline-block;margin-left:20px'>"
			if #prepare_movement_multiplier > 1 then
				wt[#wt+1] = "<span style='color:#eee8'>Mid Attack:&nbsp;</span>x"
				wt[#wt+1] = movement_multiplier
				wt[#wt+1] = "<br><span style='color:#eee8'>Otherwise:&nbsp;</span>x"
				wt[#wt+1] = prepare_movement_multiplier
			elseif #movement_multiplier > 1 then
				wt[#wt+1] = "<span style='color:#eee8'>Always:&nbsp;</span>x"
				wt[#wt+1] = movement_multiplier
			else
				wt[#wt+1] = "<span style='color:#eee8'>Always:&nbsp;</span>"
				wt[#wt+1] = movespeedadd
			end
			wt[#wt+1] = "</div>"
			wt[#wt+1] = "<br><br>"

			-- Everything except attacks within Other has invariant move mults
			-- So after processessing the first attack, we break the loop
			if ability ~= "Other" then break end
		end
	end
	wt[#wt+1] = "</div>"
end

local function p_artifact(wt,weapon_name)
	local weapon = WD.Weapon[weapon_name]
	if weapon.isartifact or weapon.artifactname == "" then return end

	wt[#wt+1] = string.format("<h2>Artifact</h2><p>%s has a powerful [[Artifacts|Artifact]] named [[%s]].</p>", weapon.name, weapon.artifactname)
end

local function tab_toggle(title,content,active,tabid)
	local class = ""
	if active then
		class = " class='selected-tab tab-toggle tab'"
	else
		class = " class='tab-toggle tab'"
	end
	return string.format("<div style='display:inline-block' %s data-tabid='%s' data-tab='%s' title='%s'>%s</div>",class,tabid,title,title,content)
end

local function reference_table(wt,table_type)
	local types_order = {"Sword","Dagger","Mace","Polearm","Axe","Throwable","Bow","Crossbow","Firearm","MagicStuff","Shield","LightSource"}
	local localization = {
		["Sword"]        = "Swords",
		["Dagger"]       = "Daggers",
		["Mace"]         = "Maces",
		["Polearm"]      = "Polearms",
		["Axe"]          = "Axes",
		["Throwable"]    = "Throwables",
		["Bow"]          = "Bows",
		["Crossbow"]     = "Crossbows",
		["Firearm"]      = "Firearms",
		["MagicStuff"]   = "Magic Stuff",
		["Shield"]       = "Shields",
		["LightSource"]  = "Light Sources"
	}
	local separator = "]] • [["

	wt[#wt+1] = "<table cellspacing='0' class='wikitable sortable jquery-tablesorter' style='width:100%;text-align:center;vertical-align:middle;margin-top:15px'>"

	for _,weapon_type in ipairs(types_order) do
		wt[#wt+1] = string.format("<tr><td style='font-weight:bold; background-color:rgb(220,220,220,0.2); width:10%%'>[[Weapons#%s|%s]]</td><td style='text-align-last:left;padding:10px 25px'>",localization[weapon_type],localization[weapon_type])
		if WD[weapon_type][table_type] then
			wt[#wt+1] = "[["
			for i,v in ipairs(WD[weapon_type][table_type]) do
				if i~=1 then wt[#wt+1] = separator end
				wt[#wt+1] = v
			end
			wt[#wt+1] = "]]"
		end
		wt[#wt+1] = "</td></tr>"
	end
	wt[#wt+1] = "</table>"
end

function p.references()
	local table_types = {"Uncraftable","Craftable","Artifact"}

	local wt = {}
	wt[#wt+1] = "<br><h2>References</h2>"
	wt[#wt+1] = "<div style='display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;margin-top:15px'>"
	for i,table_type in ipairs(table_types) do
		wt[#wt+1] = tab_toggle(table_type,table_type.." Weapons",i==1,"")
	end
	wt[#wt+1] = "</div>"
	for i,table_type in ipairs(table_types) do
		wt[#wt+1] = "<div class='"..table_type.."-data'"
		if i==1 then wt[#wt+1] = ">" else wt[#wt+1] = "style='display:none'>" end
		reference_table(wt,table_type)
		wt[#wt+1] = "</div>"
	end
	return table.concat(wt)
end

function p.page(f)
	local page_name = f.args.name

	local wt = {}
	p_projectile(wt,page_name)
	p_damage_falloff(wt,page_name)
	p_hitbox(wt,page_name)
	p_combo(wt,page_name)
	p_block(wt,page_name)
	p_action_move_slow_penalty(wt,page_name)
	p_artifact(wt,page_name)
	return concat(wt)
end

return p


-- Test on wiki with
-- mw.log(p.draw_table({args={"Crossbow","Uncraftable"}}))
-- mw.log(p.draw_table({args={"Shield","Craftable"}}))
-- mw.log(p.draw_table({args={"Polearm","Artifact"}}))


-- mw.log(p.page({args={name="Rapier"}}))