Documentation for this module may be created at Module:XIVAPI/Sandbox/doc
-- <nowiki>
local p = {}
local getArgs = require("Dev:Arguments").getArgs
local disciplineIcons = mw.loadData("Module:Icon/data/FFXIV/Discipline")
local materiaDb = mw.loadData("Module:XIVAPI/Materia")
-- Locations of files on wiki
local DATA_DIRECTORY = "Module:XIVAPI/Item/"
local ICON_DATA_DIRECTORY = "Module:XIVAPI/Item Icon/"
-- Stat names.
local HQ_ICON = "[[File:FFXIV HQ Icon.png|HQ]]"
local BASE_STATS = {
{"Block", "Block strength"},
{"BlockRate", "Block rate"},
{"DamagePhys", "Physical damage"},
{"DamageMag", "Magic damage"},
{"DefensePhys", "Physical defense"},
{"DefenseMag", "Magic defense"},
{"DelayMs", "Attack delay", "ms"},
{"CooldownS", "Cooldown", "s"}
}
local PRIMARY_STATS = {
"Strength",
"Dexterity",
"Vitality",
"Intelligence",
"Mind"
}
local SUB_STATS = {
{"CriticalHit", "Critical Hit"},
{"Determination", "Determination"},
{"DirectHitRate", "Direct Hit"},
{"SkillSpeed", "Skill Speed"},
{"SpellSpeed", "Spell Speed"},
{"Tenacity", "Tenacity"},
{"Piety", "Piety"},
{"Craftsmanship", "Craftsmanship"},
{"Control", "Control"},
{"Gathering", "Gathering"},
{"Perception", "Perception"},
{"CP", "CP"},
{"GP", "GP"}
}
local BOOLS = {
{"IsDyeable", "Is dyeable."},
{"AetherialReduce", "Can be aetherially reduced."},
{"Desynth", "Can be desynthesized."},
{"EquipRestriction", "Gender/race locked."},
{"ItemAction", "Can be used."},
{"CanBeHq", "Can be " .. HQ_ICON .. "."},
{"IsCollectable", "Collectable."},
{"IsUnique", "Unique."},
{"IsUntradable", "Untradable."},
{"IsCrestWorthy", "Can be given FC crest."},
{"IsIndisposable", "Cannot be discarded."},
{"AlwaysCollectable", "Can only be crafted as collectable."}
}
local RECIPE_STATS = {
{"ID", "Recipe ID"},
{"Difficulty", "Difficulty"},
{"Durability", "Durability"},
{"Quality", "Quality"},
{"AmountResult", "Amount produced"},
{"SecretRecipeBook", "Recipe book"},
{"RequiredCraftsmanship", "Required Craftsmanship"},
{"RequiredControl", "Required Control"},
{"SuggestedCraftsmanship", "Suggested Craftsmanship"},
{"SuggestedControl", "Suggested Control"},
}
local RECIPE_BOOLS = {
{"CanHq", "Can be made " .. HQ_ICON .. "."},
{"CanQuickSynth", "Can be made using Quick Synthesis."},
{"IsExpert", "Is an Expert recipe."},
{"IsSecondary", "Is a secondary recipe."},
{"IsSpecializationRequired", "Specialization required."},
}
local ROMAN_NUMERALS = {
["I"] = 1,
["II"] = 2,
["III"] = 3,
["IV"] = 4,
["V"] = 5,
["VI"] = 6,
["VII"] = 7,
["VIII"] = 8,
["IX"] = 9,
["X"] = 10,
["XI"] = 11,
["XII"] = 12,
["XIII"] = 13,
["XIV"] = 14
}
-- Placeholder stuff
local PLACEHOLDER_ICON = "Image Placeholder.png|128px"
local EMPTY_ITEM = {
["Name"] = "",
["ID"] = 0,
["IconID"] = 0,
["Link"] = "Final Fantasy XIV items"
}
local function subdir(name)
-- Return the three-character subdirectory for the item
if #name >= 3 then
return name:sub(1, 3):lower()
else
return "*"
end
end
local function lookup_item(name)
-- Return the data table for the item
local b, v = pcall(mw.loadData, DATA_DIRECTORY .. subdir(name))
return (b and v[name]) or EMPTY_ITEM
end
local function lookup_item_icon(id)
-- Return the data table for the item icon
if (id == 0) then
return PLACEHOLDER_ICON
end
local b, v = pcall(mw.loadData, ICON_DATA_DIRECTORY .. subdir(tostring(id)))
return (b and v[id]) or PLACEHOLDER_ICON
end
local function create_li(str)
-- Return an <li> object with wikitext the string
return mw.html.create("li"):wikitext(str)
end
local function create_class_icon(class)
-- Return an image and link to a class
-- Takes in the name of the class
class = disciplineIcons[class]
return "[[File:" .. class.file .. "|" .. class.name .. "|link=" .. class.link .. "|20px]]"
end
local function create_item_icon(item)
-- Return an item icon, takes in an item table.
item = item or EMPTY_ITEM
return "[[File:" ..
lookup_item_icon(item.IconID) .. "|" .. item.Name .. "|20px]] [[" .. item.Link .. "|" .. item.Name .. "]]"
end
local function create_trs(item, showTypeRow)
-- Return a list of <tr> objects for an item
local tr = {}
tr[1] = mw.html.create("tr")
if showTypeRow then
tr[2] = mw.html.create("tr")
end
-- Left cell
local name_cell_wt = (item.Name or "")
if not (item.IconID == 0) then
name_cell_wt = name_cell_wt .. "[[File:" .. lookup_item_icon(item.IconID) .. "]]"
end
-- Level row
local level_cell = mw.html.create("ul")
if item.LevelEquip or item.ClassJobCategory then
-- item.ClassJobCategory should be formatted as {'arcanist', etc.}
local s = "Equip to: "
for _, class in ipairs(item.ClassJobCategory or {}) do
s = s .. create_class_icon(class)
end
s = s .. " " .. tostring(item.LevelEquip or 0)
level_cell:node(create_li(s))
end
level_cell:node(create_li("Item level: " .. tostring(item.LevelItem or 0)))
level_cell:node(create_li("Stack size: " .. tostring(item.StackSize or 0)))
level_cell:node(create_li("ID: " .. tostring(item.ID or 0)))
-- Stats row
local stats_cell = mw.html.create("ul")
for _, s in ipairs(BASE_STATS) do
local code, name, unit = unpack(s)
unit = unit or ""
local li = name
if item[code] then
li = li .. ": " .. tostring(item[code]) .. unit
end
if item.CanBeHq and item["Hq" .. code] then
-- TODO: Make the xivapi parser generate Hq$baseStat$
if li == name then
li = li .. ": 0" .. unit
end
li = li .. " (" .. HQ_ICON .. " " .. tostring(item["Hq" .. code]) .. unit .. ")"
end
if not (li == name) then
stats_cell:node(create_li(li))
end
end
if item.Stats then
for _, s in ipairs(PRIMARY_STATS) do
if item.Stats[s] then
local li = s .. ": " .. tostring(item.Stats[s].NQ)
if item.canBeHq and item.Stats[s].HQ then
li = li .. " (" .. HQ_ICON .. " " .. tostring(item.Stats[s].HQ) .. " )"
end
stats_cell:node(create_li(li))
end
end
for _, s in ipairs(SUB_STATS) do
local code, name = unpack(s)
if item.Stats[code] then
local li = name .. ": " .. tostring(item.Stats[code].NQ)
if item.canBeHq and item.Stats[code].HQ then
li = li .. " (" .. HQ_ICON .. " " .. tostring(item.Stats[code].HQ) .. " )"
end
stats_cell:node(create_li(li))
end
end
end
if not (tostring(stats_cell) == "<ul></ul>") then
table.insert(
tr,
mw.html.create("tr"):
tag("td"):
tag("div"):
attr("class", "columns"):
node(stats_cell):
allDone()
)
end
-- Repair row
local repair_cell = mw.html.create("ul")
if item.ClassJobRepair then
repair_cell:node(
create_li(
"Repair job: " ..
create_class_icon(item.ClassJobRepair) .. " " .. tostring(math.max((item.LevelEquip or 0) - 10, 1))
)
)
end
if item.ItemRepair then
repair_cell:node(create_li("Catalyst: " .. create_item_icon(lookup_item(item.ItemRepair))))
end
if item.MateriaSlotCount then
local s = "Materia slots: " .. tostring(item.MateriaSlotCount)
if item.IsAdvancedMeldingPermitted then
s = s .. " (can overmeld)"
end
repair_cell:node(create_li(s))
end
if not (tostring(repair_cell) == "<ul></ul>") then
table.insert(
tr,
mw.html.create("tr"):
tag("td"):
tag("div"):
attr("class", "columns"):
node(repair_cell):
allDone()
)
end
-- Materia row
-- Materia information isn't any easier to recover from XIVAPI than it is from the item's name,
-- so we just determine if the item is a materia from its name.
-- See Module:XIVAPI/Materia for data
local materiaStart, materiaEnd = string.find(item.Name, " Materia ")
if materiaStart then
local materiaType = materiaDb[string.sub(item.Name, 1, materiaStart - 1)]
local strength = ROMAN_NUMERALS[string.sub(item.Name, materiaEnd + 1)]
if materiaType and strength then
table.insert(
tr,
mw.html.create("tr"):
tag("td"):
wikitext(
"This materia increases " .. materiaType[1] .. " by " .. tostring(materiaType[2][strength]) .. "."
):allDone()
)
end
end
-- Recipe rows
for i, recipe in ipairs(item.Recipes) do
local stats_ul = mw.html.create("ul")
stats_ul:node(create_li("Crafted by: " .. create_class_icon(recipe.ClassJob) .. " " .. tostring(recipe.ClassJobLevel or 0)))
for _, stat in ipairs(RECIPE_STATS) do
local code, str = unpack(stat)
if recipe[code] then
stats_ul:node(create_li(str .. ": " .. tostring(recipe[code])))
end
end
if recipe.Stars then
stats_ul:node(create_li(string.rep("[[File:MFF 1-Star Icon.png|star|16px]]", recipe.Stars) .. " recipe"))
end
local bool_output = "<center>Properties:</center><br/>"
for _, bool in ipairs(RECIPE_BOOLS) do
local code, str = unpack(bool)
if recipe[code] then
bool_output = bool_output .. str .. " "
end
end
if bool_output == "<center>Properties:</center><br/>" then
bool_output = ""
end
local ingredients_ul = mw.html.create("ul")
for _, ingredient in ipairs(recipe.Ingredients) do
local name, amount = unpack(ingredient)
ingredients_ul:node(create_li(create_item_icon(lookup_item(item.ItemRepair)) .. " x" .. tostring(amount)))
end
local recipe_cell = mw.html.create("td"):
wikitext("<center>Recipe " .. tostring(i) .. ":</center>"):
tag("div"):
attr("class", "columns"):
node(stats_ul):
allDone():
wikitext("<br><center>Ingredients</center>:"):
tag("div"):
attr("class", "columns"):
node(ingredients_ul):
allDone():
wikitext(bool_output)
table.insert(
tr,
mw.html.create("tr"):
node(recipe_cell):
allDone()
)
end
-- TODO: Used to make
-- Description row
local descr_string = (item.Description and (item.Description .. "<br/>")) or ""
for _, bool in ipairs(BOOLS) do
local bool_name, bool_descr = unpack(bool)
if item[bool_name] then
descr_string = descr_string .. bool_descr .. " "
end
end
if item.ItemSeries then
descr_string = descr_string .. "Part of the " .. item.ItemSeries .. " collection. "
end
if item.GrandCompany then
descr_string = descr_string .. item.GrandCompany .. " uniform."
end
if not (descr_string == "") then
table.insert(
tr,
mw.html.create("tr"):
tag("td"):
wikitext(descr_string):
done()
)
end
-- Generate left and top cells
if showTypeRow then
tr[1]:
tag("th"):
wikitext(name_cell_wt):
attr("rowspan", #tr):
done():
tag("th"):
wikitext((item.ItemKind or "—") .. " (" .. (item.ItemUICategory or "—") .. ")")
tr[2]:
tag("td"):
tag("div"):
attr("class", "columns"):
node(level_cell)
else
tr[1]:
tag("th"):
wikitext(name_cell_wt):
attr("rowspan", #tr):
done():
tag("td"):
tag("div"):
attr("class", "columns"):
node(level_cell)
end
return tr
end
local function table_header()
-- Return a basic table header
return mw.html.create("table"):attr("class", "full-width article-table ffxiv-item-table"):tag("tr"):tag("th"):attr(
"style",
"width:128px;"
):wikitext("Item"):done():tag("th"):wikitext("Description"):allDone()
end
function p.Test()
item = {}
item.Name = "Savage Aim Materia VI"
item.ID = 12345
item.Link = "Project:Sandsea"
item.ClassJobCategory = {"arcanist", "scholar"}
item.LevelEquip = 26
item.LevelItem = 45
item.ItemRepair = "grade 5 dark matter"
item.IsIndisposable = 1
item.IconID = 10000
item.ClassJobRepair = "weaver"
item.Description = "A big weapon."
item.IsCrestWorthy = 1
item.StackSize = 16
item.BlockRate = 100
item.HqBlockRate = 120
item.HqCooldownS = 19
item.MateriaSlotCount = 2
item.ItemKind = "Medicines & Meals"
item.ItemUICategory = "Gladiator's Arm"
item.IsAdvancedMeldingPermitted = 1
item.AlwaysCollectable = 1
item.CanBeHq = true
item.DamageMag = 15
item.Stats = {
["Strength"] = {["NQ"] = 16, ["HQ"] = 26},
["DirectHitRate"] = {["NQ"] = 19}
}
item.Recipes = {
{
["CanHq"] = true,
["ID"] = 199,
["ClassJob"] = "blacksmith",
["ClassJobLevel"] = 100,
["IsExpert"] = false,
["AmountResult"] = 15,
["Stars"] = 3,
["SuggestedControl"] = 1,
["RequiredControl"] = 2,
["Ingredients"] = {
{"Grade 5 Dark Matter", 15},
}
},
}
local t = table_header()
local trs = create_trs(item, false)
for i, tr in ipairs(trs) do
t:node(tr)
end
return t
end
return p