From Dark and Darker Wiki
AssassinOTP (talk | contribs) fix formatting for movement penalty |
AssassinOTP (talk | contribs) mostly copied weapons individual page functions to utility |
||
| (4 intermediate revisions by the same user not shown) | |||
| Line 94: | Line 94: | ||
local effect = "" | local effect = "" | ||
if utility.utilitydesc then effect = utility.utilitydesc end | if utility.utilitydesc then effect = utility.utilitydesc end | ||
local frame = mw.getCurrentFrame() | |||
local expand = frame:expandTemplate({ | |||
title = 'UtilityEffects', | |||
args = { string.gsub(utility.name, "%s+", "") } | |||
}) | |||
if expand ~= "" then effect = effect.." "..expand | |||
elseif effect == "" and expand ~= "" then effect = expand | |||
end | |||
return "<td>"..effect.."</td>" | return "<td>"..effect.."</td>" | ||
end | end | ||
| Line 162: | Line 170: | ||
else | else | ||
wt = wt.."<span style='color:#eee8'>Always: </span>x"..movement_multiplier | wt = wt.."<span style='color:#eee8'>Always: </span>x"..movement_multiplier | ||
end | |||
end | |||
return "<td>"..wt.."</td>" | |||
end | |||
---Formats a table cell containing hitslow for each attack | |||
local function slowdown_on_hit(utility,is_header) | |||
if is_header then return [[<th style="width:8%">Slowdown On Hit</th>]] end | |||
if not (utility.abilities.Primary or utility.abilities.Secondary or utility.abilities.Special) then return "<td></td>" end | |||
local ability_type = "Primary" | |||
-- if utility.slottype == "Off-Hand" then ability_type = "Secondary" end | |||
local wt = format("<span style='color:#eee8'>%s Hitslow</span><br>", ability_type) | |||
local duration = " for " | |||
for i,attack in ipairs(utility.abilities[ability_type].order) do | |||
if i~=1 then wt = wt.."/" end | |||
wt = wt..(utility.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Move Speed Add"] | |||
or utility.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Move Speed Bonus"] or "") | |||
end | |||
for i,attack in ipairs(utility.abilities[ability_type].order) do | |||
if i~=1 then duration = duration.."/" end | |||
duration = duration..(utility.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Duration"] or "") | |||
end | |||
return "<td>"..wt..duration.."s</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,["Move Speed"]=true} | |||
---Marks up a stat block with the stat name and colored values and appropriate margins and alignment | |||
local function inline_block(utility,stat) | |||
if utility.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(utility,utility.stats[(stat):lower()]).."</div>" | |||
end | |||
---Formats a table cell containing any stats not covered by the rest of the row | |||
local function stats(utility,is_header) | |||
if is_header then return [[<th style="width:12%">Stats</th>]] end | |||
local wt = "" | |||
for i,stat in ipairs(utility.stats.order) do | |||
if not exclude_stat[stat] then | |||
wt = wt..inline_block(utility,stat) | |||
end | end | ||
end | end | ||
| Line 180: | Line 232: | ||
["Consumable"] = {name, move_speed, effect, interaction_time, action_move_speed_penalty}, | ["Consumable"] = {name, move_speed, effect, interaction_time, action_move_speed_penalty}, | ||
["Installable"] = {name,class,move_speed,effect,interaction_time}, | ["Installable"] = {name,class,move_speed,effect,interaction_time}, | ||
["LightSource"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty}, | ["LightSource"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty, slowdown_on_hit}, | ||
["Throwable"] = {name, class, move_speed, damage_on_hit, effect, action_move_speed_penalty}, | ["Throwable"] = {name, class, move_speed, damage_on_hit, effect, action_move_speed_penalty, slowdown_on_hit}, | ||
["MusicalInstrument"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty}, | ["MusicalInstrument"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty, slowdown_on_hit}, | ||
["Mining"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty}, | ["Mining"] = {name, move_speed, damage_on_hit, stats, effect, action_move_speed_penalty, slowdown_on_hit}, | ||
["SoulHeart"] = {name, move_speed, effect}, | ["SoulHeart"] = {name, move_speed, effect}, | ||
["Food"] = {name, move_speed, effect}, | ["Food"] = {name, move_speed, effect}, | ||
["Tool"] = {name, move_speed, effect, action_move_speed_penalty}, | ["Tool"] = {name, move_speed, damage_on_hit, stats, effect, action_move_speed_penalty, slowdown_on_hit}, | ||
["Default"] = {name, move_speed, damage_on_hit, effect, action_time, action_move_speed_penalty} | ["Default"] = {name, move_speed, damage_on_hit, stats, effect, action_time, action_move_speed_penalty} | ||
} | } | ||
| Line 203: | Line 255: | ||
end | end | ||
-- Draw the table based on type (Drink, Consumable, etc.) | |||
function p.draw_table(frame) | function p.draw_table(frame) | ||
local wt = {} | local wt = {} | ||
| Line 215: | Line 268: | ||
wt[#wt+1] = "</table>" | wt[#wt+1] = "</table>" | ||
return concat(wt) | |||
end | |||
--[=[########################################################################### | |||
Functions for individual utility pages | |||
#############################################################################]=] | |||
function p_quest_information(wt, name) | |||
if UD.Utility[name].questinformation == "" then return | |||
else wt[#wt+1] = "<h2>Quest Information</h2><ul>"..UD.Utility[name].questinformation.."</ul>" end | |||
end | |||
local function p_projectile(wt,weapon_name) | |||
local weapon = UD.Utility[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 = UD.Utility[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] = mw.getCurrentFrame():callParserFunction({ name = "#widget:Chart", args= { uniqueId = weapon_name, data = weapon.damagefalloff, color = 'orange' }}) | |||
wt[#wt+1] = "</div><br>Learn more at [[Damage_Calculation#Projectile_Falloff|Projectile Falloff]]" | |||
end | |||
local function p_hitbox(wt,weapon_name) | |||
local weapon = UD.Utility[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(" ⤷ Recover: %s<br>", attack["Recover"]) end | |||
if attack["Alternate Recover"] then wt[#wt+1] = string.format(" ⤷ Alternate Recover: %s<br>", attack["Alternate Recover"]) end | |||
if attack["Finish"] then wt[#wt+1] = string.format(" ⤷ 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 = UD.Utility[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 = UD.Utility[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 = UD.Utility[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: </span>x" | |||
wt[#wt+1] = movement_multiplier | |||
wt[#wt+1] = "<br><span style='color:#eee8'>Otherwise: </span>x" | |||
wt[#wt+1] = prepare_movement_multiplier | |||
elseif #movement_multiplier > 1 then | |||
wt[#wt+1] = "<span style='color:#eee8'>Always: </span>x" | |||
wt[#wt+1] = movement_multiplier | |||
else | |||
wt[#wt+1] = "<span style='color:#eee8'>Always: </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 = UD.Utility[weapon_name] | |||
if not weapon.isartifact 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 | |||
-- Draw anything relevant to the item for its individual page (not infobox or crafts) | |||
function p.page(f) | |||
local page_name = f.args.name | |||
local wt = {} | |||
p_quest_information(wt, page_name) | |||
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) | return concat(wt) | ||
end | end | ||
| Line 231: | Line 513: | ||
-- mw.log(p.draw_table({args={"Food"}})) | -- mw.log(p.draw_table({args={"Food"}})) | ||
-- mw.log(p.draw_table({args={"Tool"}})) | -- mw.log(p.draw_table({args={"Tool"}})) | ||
-- mw.log(p.page({args={name="Ale"}})) | |||
-- mw.log(p.page({args={name="Francisca Axe"}})) | |||
Latest revision as of 07:12, 12 November 2026
Overview
Functions for making Utility tables. Data comes from Data:Utility.json.
Functions
draw_table
Creates a table of utilities
Parameters
- 1 - <Type>
draw_table examples
Drink
{{#invoke:Utility|draw_table|Drink}}
| Name | Movement Speed | Effect | Action Time | Action Movement Penalty |
|---|---|---|---|---|
| -10 | While active, gain 4 | 5 | 6 | 7 | 8 | 9 | 10 Strength and become drunk for 15 seconds. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Drink this to gain 5 to All Attributes for 10 seconds. In return, you also gain an unhealing debuff. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Recover 20 Magical Base Healing (50% Scaling) over 15 seconds. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Instantly restores 3 health and applies a festive effect. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Gain between 50 | 100 | 200 to 150 | 200 | 300 Luck. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Gain a shield that blocks 10 | 15 | 20 | 25 | 30 | 35 | 40 magical Damage for 24 seconds. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Gain 2 spell recovery every second for 20 | 40 | 60 | 80 | 100 | 120 | 140 seconds. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Recover 20 Magical Base Healing (50% Scaling) over 20 | 17.5 | 15 | 12.5 | 10 | 7.5 | 5 seconds. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Gain invisibility for 11 | 14 | 17 | 20 | 23 | 26 | 29 seconds. When you move, perform an action or take damage, the effect is removed. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Gain between 20 to 80 Luck. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Gain a shield that blocks 10 | 15 | 20 | 25 | 30 | 35 | 40 physical damage for 24 seconds. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Recover 20 | 30 | 40 | 50 | 60 | 70 | 80 Oxygen. | 3s 100% Scaling | Other Actions Always: x75% | |
| -10 | Recover 100 Magical Base Healing (50% Scaling) over 100 seconds. | 3s 100% Scaling | Other Actions Always: x75% |
Code
local p = {}
local UD = mw.loadJsonData("Data:Utility.json")
local concat = table.concat
local format = string.format
---Get the item's size in pixels
local function size(item)
return tostring(item.invwidth*45).."x"..tostring(item.invheight*45).."px"
end
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(utility, values)
if values == nil then return "" end
if type(values)=="string" or type(values)=="number" then
if count(utility.rarities) == 1 then return "<span class='cr"..utility.rarities[1].."'>"..values.."</span>" end
return tostring(values)
end
local wt = ""
local newline = ""
for i, key in ipairs(utility.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
---Returns a predetermined utility list
local function get_list(frame)
if not (frame.args and frame.args[1]) then return {} end
if not (UD[frame.args[1]] and UD[frame.args[1]]) then return {} end
return UD[frame.args[1]]
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 a table cell of classes
local function class(utility,is_header)
if is_header then return [[<th style="width:5%">Class</th>]] end
local wt = {}
wt[#wt+1] = "<td>"
for _, class in ipairs(utility.classes) do
wt[#wt+1] = class.."<br>"
end
wt[#wt+1] = "</td>"
return concat(wt)
end
---Formats a table cell of move speeds
local function move_speed(utility,is_header)
if is_header then return [[<th style="width:5%">Movement Speed</th>]] end
return "<td>"..color_values(utility,utility.stats["move speed"]).."</td>"
end
---Formats a table cell of damages on hit
local function damage_on_hit(utility,is_header)
if is_header then return [[<th style="width:5%">Damage on Hit</th>]] end
return "<td>"..color_values(utility,utility.stats["physical base weapon damage"]).."</td>"
end
---Formats a table cell of effects
local function effect(utility,is_header)
if is_header then return [[<th style="width:20%">Effect</th>]] end
local effect = ""
if utility.utilitydesc then effect = utility.utilitydesc end
local frame = mw.getCurrentFrame()
local expand = frame:expandTemplate({
title = 'UtilityEffects',
args = { string.gsub(utility.name, "%s+", "") }
})
if expand ~= "" then effect = effect.." "..expand
elseif effect == "" and expand ~= "" then effect = expand
end
return "<td>"..effect.."</td>"
end
---Formats a table cell of action times
local function action_time(utility,is_header)
if is_header then return [[<th style="width:10%">Action Time</th>]] end
local actiontime = utility.actiontime
local actiontimescaling = utility.actiontimescaling
local finalstring = "<td></td>"
if actiontime and actiontimescaling then finalstring = format("<td>%ss<br>%s Scaling</td>", actiontime, actiontimescaling) end
return finalstring
end
---Formats a table cell of interaction times (This is the little loading circle with the percent when interacting vs stuff like drinking a potion)
local function interaction_time(utility,is_header)
if is_header then return [[<th style="width:10%">Interaction Time</th>]] end
local interactiontimescaling = utility.interactiontimescaling
local interactiontimeformatted = utility.interactiontimeformatted
local finalstring = "<td></td>"
if interactiontimeformatted and interactiontimescaling then finalstring = format("<td>%s<br>%s Scaling</td>", interactiontimeformatted, interactiontimescaling) end
return finalstring
end
---Helper function: Collects move mult and prepare move mult for display
local function collect_multipliers(ability)
local move, prep_move = "",""
for _, attack_name in ipairs(ability.order) do
if ability[attack_name].movementmultiplier then
if move ~= "" then
move = move.."/"
prep_move = prep_move.."/"
end
move = move..(ability[attack_name].movementmultiplier or "")
prep_move = prep_move..(ability[attack_name].preparemovementmultiplier or "")
end
end
return move,prep_move
end
---Formats a table cell containing speed penalties for each action
local function action_move_speed_penalty(utility,is_header)
if is_header then return [=[<th style="width:10%">[[Weapons#Movement_Multiplier_Explanation|Action Movement Penalty]]</th>]=] end
-- The below was brought from the Weapons Module, it appears "Other" abilities were ignored
-- This seems to skip needed ones in utilities, maybe weapons also has some? this is a potential oversight
-- local order = {} --reconstruct order to exclude the other ability group
-- for _,ability in ipairs(utility.abilities.order) do
-- if ability ~= "Other" then order[#order+1] = ability end
-- end
local wt = ""
for i,ability_name in ipairs(utility.abilities.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(utility.abilities[ability_name])
if #prepare_movement_multiplier > 3 then
wt = wt.."<span style='color:#eee8'>Mid Attack: </span>x"..movement_multiplier
.."<br><span style='color:#eee8'>Otherwise: </span>x"..prepare_movement_multiplier
else
wt = wt.."<span style='color:#eee8'>Always: </span>x"..movement_multiplier
end
end
return "<td>"..wt.."</td>"
end
---Formats a table cell containing hitslow for each attack
local function slowdown_on_hit(utility,is_header)
if is_header then return [[<th style="width:8%">Slowdown On Hit</th>]] end
if not (utility.abilities.Primary or utility.abilities.Secondary or utility.abilities.Special) then return "<td></td>" end
local ability_type = "Primary"
-- if utility.slottype == "Off-Hand" then ability_type = "Secondary" end
local wt = format("<span style='color:#eee8'>%s Hitslow</span><br>", ability_type)
local duration = " for "
for i,attack in ipairs(utility.abilities[ability_type].order) do
if i~=1 then wt = wt.."/" end
wt = wt..(utility.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Move Speed Add"]
or utility.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Move Speed Bonus"] or "")
end
for i,attack in ipairs(utility.abilities[ability_type].order) do
if i~=1 then duration = duration.."/" end
duration = duration..(utility.abilities[ability_type][attack].effects.HitSlow.rarity.Global["Duration"] or "")
end
return "<td>"..wt..duration.."s</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,["Move Speed"]=true}
---Marks up a stat block with the stat name and colored values and appropriate margins and alignment
local function inline_block(utility,stat)
if utility.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(utility,utility.stats[(stat):lower()]).."</div>"
end
---Formats a table cell containing any stats not covered by the rest of the row
local function stats(utility,is_header)
if is_header then return [[<th style="width:12%">Stats</th>]] end
local wt = ""
for i,stat in ipairs(utility.stats.order) do
if not exclude_stat[stat] then
wt = wt..inline_block(utility,stat)
end
end
return "<td>"..wt.."</td>"
end
local function determine_table_type(utility)
if utility.args then
return utility.args[1]
elseif utility.utilitytype then
return utility.utilitytype
end
end
---Determines which table data goes into the table row
local row_type = {
["Drink"] = {name, move_speed, effect, action_time, action_move_speed_penalty},
["Consumable"] = {name, move_speed, effect, interaction_time, action_move_speed_penalty},
["Installable"] = {name,class,move_speed,effect,interaction_time},
["LightSource"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty, slowdown_on_hit},
["Throwable"] = {name, class, move_speed, damage_on_hit, effect, action_move_speed_penalty, slowdown_on_hit},
["MusicalInstrument"] = {name, move_speed, damage_on_hit, effect, action_move_speed_penalty, slowdown_on_hit},
["Mining"] = {name, move_speed, damage_on_hit, stats, effect, action_move_speed_penalty, slowdown_on_hit},
["SoulHeart"] = {name, move_speed, effect},
["Food"] = {name, move_speed, effect},
["Tool"] = {name, move_speed, damage_on_hit, stats, effect, action_move_speed_penalty, slowdown_on_hit},
["Default"] = {name, move_speed, damage_on_hit, stats, effect, action_time, action_move_speed_penalty}
}
---Return a table row of data cells containing utility information,
local function row(utility, is_header)
is_header = is_header or false
local wt = {}
wt[#wt+1] = "<tr>"
for _,content in ipairs(row_type[determine_table_type(utility)]) do
wt[#wt+1] = content(utility,is_header)
end
wt[#wt+1] = "</tr>"
return concat(wt)
end
-- Draw the table based on type (Drink, Consumable, etc.)
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(UD.Utility[item_name])
end
wt[#wt+1] = "</table>"
return concat(wt)
end
--[=[###########################################################################
Functions for individual utility pages
#############################################################################]=]
function p_quest_information(wt, name)
if UD.Utility[name].questinformation == "" then return
else wt[#wt+1] = "<h2>Quest Information</h2><ul>"..UD.Utility[name].questinformation.."</ul>" end
end
local function p_projectile(wt,weapon_name)
local weapon = UD.Utility[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 = UD.Utility[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] = mw.getCurrentFrame():callParserFunction({ name = "#widget:Chart", args= { uniqueId = weapon_name, data = weapon.damagefalloff, color = 'orange' }})
wt[#wt+1] = "</div><br>Learn more at [[Damage_Calculation#Projectile_Falloff|Projectile Falloff]]"
end
local function p_hitbox(wt,weapon_name)
local weapon = UD.Utility[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(" ⤷ Recover: %s<br>", attack["Recover"]) end
if attack["Alternate Recover"] then wt[#wt+1] = string.format(" ⤷ Alternate Recover: %s<br>", attack["Alternate Recover"]) end
if attack["Finish"] then wt[#wt+1] = string.format(" ⤷ 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 = UD.Utility[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 = UD.Utility[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 = UD.Utility[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: </span>x"
wt[#wt+1] = movement_multiplier
wt[#wt+1] = "<br><span style='color:#eee8'>Otherwise: </span>x"
wt[#wt+1] = prepare_movement_multiplier
elseif #movement_multiplier > 1 then
wt[#wt+1] = "<span style='color:#eee8'>Always: </span>x"
wt[#wt+1] = movement_multiplier
else
wt[#wt+1] = "<span style='color:#eee8'>Always: </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 = UD.Utility[weapon_name]
if not weapon.isartifact 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
-- Draw anything relevant to the item for its individual page (not infobox or crafts)
function p.page(f)
local page_name = f.args.name
local wt = {}
p_quest_information(wt, page_name)
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
-- Tests
-- mw.log(p.draw_table({args={"Drink"}}))
-- mw.log(p.draw_table({args={"Consumable"}}))
-- mw.log(p.draw_table({args={"Installable"}}))
-- mw.log(p.draw_table({args={"LightSource"}}))
-- mw.log(p.draw_table({args={"Throwable"}}))
-- mw.log(p.draw_table({args={"MusicalInstrument"}}))
-- mw.log(p.draw_table({args={"Mining"}}))
-- mw.log(p.draw_table({args={"SoulHeart"}}))
-- mw.log(p.draw_table({args={"Food"}}))
-- mw.log(p.draw_table({args={"Tool"}}))
-- mw.log(p.page({args={name="Ale"}}))
-- mw.log(p.page({args={name="Francisca Axe"}}))