Module:Guardian

local p = {}

local guardians = mw.loadData('Module:Guardian/data')

local INVALID_SKILL_CAT = "" local INVALID_PRICE_CAT = "" local INVALID_JAPANESE_CAT = "" local INVALID_UNION_CAT = "" local UNKNOWN_GUARDIAN = "Some Unknown Guardian"

local function StringBuilder return { s = {}, fmt = function(self, format, ...) return table.insert(self.s, string.format(format, unpack({...}))) end, concat = function(self, sep) return table.concat(self.s, sep) end, } end

-- Toarray converts a table into an array. -- This is sometimes necessary to get table.concat to work. local function toarray(tbl) local a = {} for _, x in ipairs(tbl) do       table.insert(a, x)    end return a end

local function isempty(s) return s == nil or s == '' end

local function trim(s) return s:gsub("^%s*(.-)%s*$", "%1") end

local function paramstring(s) if s == nil then return "" end return trim(tostring(s)) end

local function lookup(name) return guardians[name] end

-- Compare val against default value. -- Give priority to default. local function cmp_default(frame, val, category) local def = paramstring(frame.args.default) if isempty(val) then return def end if isempty(def) then return val end if val ~= def then return def .. " " .. category end return def end

local function get_attr(frame, key, fallback) local g = lookup(frame.args[1]) if g == nil then return fallback end return tostring(g[key]) end

local function get_slot(frame) g = lookup(frame.args[1]) if g == nil then return nil end return g.skills[tonumber(frame.args.slot)] end

local function get_slot_attr(frame, key, fallback) local slot = get_slot(frame) if slot == nil then return fallback end if slot[1] == nil then return fallback end local val = slot[1][key] if val == nil then return fallback end return tostring(val) end

function p.exists(frame) if lookup(frame.args[1]) ~= nil then return "yes" end return "" end

function p.class(frame) return get_attr(frame, "class", "") end

function p.race(frame) return get_attr(frame, "race", "") end

function p.element(frame) return get_attr(frame, "element", "") end

function p.job(frame) return get_attr(frame, "job", "") end

function p.profile(frame) return get_attr(frame, "profile", "No profile.") end

function p.sell_price(frame) return cmp_default(frame, get_attr(frame, "coin", ""), INVALID_PRICE_CAT) end

function p.jpname(frame) return cmp_default(frame, get_attr(frame, "jpname", ""), INVALID_JAPANESE_CAT) end

function p.union50(frame) return cmp_default(frame, get_attr(frame, "union50", ""), INVALID_UNION_CAT) end

function p.union100(frame) return cmp_default(frame, get_attr(frame, "union100", ""), INVALID_UNION_CAT) end

function p.skill_name(frame) return get_slot_attr(frame, "name", "") end

function p.skill_level(frame) return cmp_default(frame, get_slot_attr(frame, "level", ""), INVALID_SKILL_CAT) end

function p.skill_icon(frame) return get_slot_attr(frame, "icon", "") end

function p.skill_class(frame) return cmp_default(frame, get_slot_attr(frame, "class", ""), INVALID_SKILL_CAT) end

function p.skill_desc(frame) local slot = get_slot(frame) if slot == nil then return "" end local desc = {} if slot[1] ~= nil then table.insert(desc, slot[1].desc .. "\n") end local i = 2 while slot[i] ~= nil do       local skill = slot[i] table.insert(desc, string.format("* Upgrades to %s", skill.name)) if skill.class ~= nil then table.insert(desc, string.format(" (%s)", skill.class)) end table.insert(desc, string.format(" at Lv %s.\n", skill.level)) i = i + 1 end return table.concat(desc, "") end

function p.union_sources(frame) local q = paramstring(frame.args[1]) if isempty(q) then return "" end local src = {} for name, g in pairs(guardians) do       if g.evolution == 1 then if g.union50 == q then table.insert(src, {name=name,union=50}) end if g.union100 == q then table.insert(src, {name=name, union=100}) end end end if #src == 0 then return "" end local icon = require("Module:Icon").icon if #src == 1 then return string.format("* %s, %d%% Union Reward\n", icon{args={src[1].name}}, src[1].union) end table.sort(src, function(a, b) return a.name < b.name end) s = {} table.insert(s, "* Union Reward from the following Guardians:\n") for _, a in ipairs(src) do       table.insert(s, string.format("** %s (%d%% Union)\n", icon{args={a.name}}, a.union)) end return table.concat(s, "") end

function p.sortable_table local entity_icon = require("Module:Icon").entity_icon local class_order = {Z=1, SS=2, S=3, A=4, B=5, C=6, D=7}

local keys = {} for k in pairs(guardians) do       table.insert(keys, k)    end table.sort(keys)

local s = {} table.insert(s, '{| class="wikitable sortable"') table.insert(s, '! class="unsortable"|Icon !! Name !! Class !! Race !! Job !! Element') for _, key in ipairs(keys) do       local g = guardians[key] if g.evolution == 1 then table.insert(s, '|-') local icon = entity_icon{args={image=string.format('Guardian_%s_icon.png', key)}} local class = string.format('data-sort-value=%d|%s', class_order[g.class] or 100, g.class) local race = string.format(' %s', g.race, g.race) local job = string.format(' %s', g.job, g.job) local elem = "N/A" if not isempty(g.element) then elem = string.format(' %s', g.element, g.element) end table.insert(s, string.format('| %s || %s || %s || %s || %s || %s', icon, key, g.name, class, race, job, elem)) end end table.insert(s, '|}') return table.concat(s, "\n") end

function p.skills_table(frame) local name = paramstring(frame.args[1]) if isempty(name) then return "" end g = guardians[name] if g == nil then return "" end local s = StringBuilder s:fmt('{| class="wikitable"') s:fmt('|+ %s', name) s:fmt('|-') s:fmt('! class="unsortable"| Lv !! Skill !! Effects !! Target !! Condition !! Class') for slot, skills in ipairs(g.skills) do       local style = "" if slot%2 == 0 then style = 'style="background-color:#eeeeee33;" |' end for _, skill in ipairs(skills) do           local icon = string.format("", skill.icon) local effects = table.concat(toarray(skill.effects or {}), " ") s:fmt('|-') s:fmt('| %s %s', style, skill.level) s:fmt('| %s %s %s', style, icon, skill.short_name or "") if effects == "" then s:fmt('| %s "%s"', style, skill.desc) else s:fmt('| %s %s "%s"', style, effects, skill.desc) end s:fmt('| %s %s', style, skill.target or "") if skill.emit ~= nil then s:fmt('| %s %s (%d%%)', style, skill.condition or "", skill.emit) else s:fmt('| %s %s', style, skill.condition or "") end s:fmt('| %s %s', style, skill.class or "") end end s:fmt('|}') return s:concat("\n") end

-- TEST FUNCTIONS

function p._test_exists local testcases = { {name="Djagos", out="yes"}, {name="Djagos DNA", out="yes"}, {name="Djagos RNA", out="yes"}, {name=UNKNOWN_GUARDIAN, out=""}, }   for _, tc in ipairs(testcases) do        local out = p.exists{args={tc.name}} if out ~= tc.out then mw.log(string.format("exists: output for %s is %s; expected %s", tc.name, out, tc.out)) end end end

function p._test_class local valid_classes = {D=true, C=true, B=true, A=true, S=true, SS=true, Z=true} for name, _ in pairs(guardians) do       if not valid_classes[p.class{args={name}}] then mw.log("invalid class for " .. name) end end if p.class{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("missing class should be empty") end end

function p._test_race local valid_races = {Human=true, Beastfolk=true, Lizardfolk=true, Golem=true, Machine=true, Fairy=true, Animal=true, Monster=true, Celestial=true, Soul=true} for name, _ in pairs(guardians) do       if not valid_races[p.race{args={name}}] then mw.log("invalid race for " .. name) end end if p.race{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("missing race should be empty") end end

function p._test_element local valid_elements = {Fire=true, Ice=true, Earth=true, Wind=true, Lightning=true, Darkness=true, Photon=true, Graviton=true, ["Non-Elemental"]=true, [""]=true} for name, _ in pairs(guardians) do       if not valid_elements[p.element{args={name}}] then mw.log("invalid element for " .. name) end end if p.element{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("missing element should be empty") end end

function p._test_job local valid_jobs = {Warrior=true, Spellblade=true, Mage=true, Healer=true} for name, _ in pairs(guardians) do       if not valid_jobs[p.job{args={name}}] then mw.log("invalid job for " .. name) end end if p.job{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("missing job should be empty") end end

function p._test_profile for name, _ in pairs(guardians) do       if string.len(p.profile{args={name}}) < 20 then mw.log("profile len too short for " .. name) end end if p.profile{args={UNKNOWN_GUARDIAN}} ~= "No profile." then mw.log("invalid message for missing profile") end end

function p._test_sell_price for name, _ in pairs(guardians) do       if tonumber(p.sell_price{args={name}}) == nil then mw.log("nil sell price for " .. name) end local price = p.sell_price{args={name, default="123456"}} if price ~= "123456 " .. INVALID_PRICE_CAT then mw.log(string.format("default sell price not used for %s (price=%s)", name, price)) end end if p.sell_price{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("invalid message for missing sell price") end if p.sell_price{args={UNKNOWN_GUARDIAN, default="123456"}} ~= "123456" then mw.log("default sell price not used for missing guardian") end end

function p._test_union50 for name, _ in pairs(guardians) do       if string.len(p.union50{args={name}}) == 0 then mw.log("empty 50% union for " .. name) end local union = p.union50{args={name, default="Wrong Union"}} if union ~= "Wrong Union " .. INVALID_UNION_CAT then mw.log(string.format("default union 50 not used for %s (union 50=%s)", name, union)) end end if p.union50{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("invalid 50% union for missing guardian") end end

function p._test_union100 for name, _ in pairs(guardians) do       if string.len(p.union100{args={name}}) == 0 then mw.log("empty 100% union for " .. name) end local union = p.union100{args={name, default="Wrong Union"}} if union ~= "Wrong Union " .. INVALID_UNION_CAT then mw.log(string.format("default union 100 not used for %s (union 100=%s)", name, union)) end end if p.union100{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("invalid 100% union for missing guardian") end end

function p._test_jpname for name, _ in pairs(guardians) do       if string.len(p.jpname{args={name}}) == 0 then mw.log("empty jpname for " .. name) end end end

function p._test_skill_name for name, g in pairs(guardians) do       if g.skills[1] ~= nil then if string.len(p.skill_name{args={name, slot=1}}) == 0 then mw.log("empty skill name for " .. name) end end end end

function p._test_skill_level for name, g in pairs(guardians) do       if g.skills[1] ~= nil then if tonumber(p.skill_level{args={name, slot=1}}) ~= 1 then mw.log("skill level for first slot is not 1 for " .. name) end end end end

function p._test_skill_icon for name, g in pairs(guardians) do       if g.skills[1] ~= nil then if tonumber(p.skill_icon{args={name, slot=1}}) == nil then mw.log("nil skill icon for " .. name) end end end end

function p._test_skill_class for name, _ in pairs(guardians) do       for slot=1,6 do            local cl = p.skill_class{args={name, slot=slot}} if cl == tostring(nil) then -- note: tostring(nil) ~= nil mw.log("nil skill class for " .. name) end end end end

function p._test_skill_desc for name, g in pairs(guardians) do       if g.skills[1] ~= nil then local desc = p.skill_desc{args={name, slot=1}} if string.len(desc) < 10 then mw.log("short skill desc for " .. name) end end end if p.skill_desc{args={UNKNOWN_GUARDIAN}} ~= "" then mw.log("invalid message for missing skill description") end end

function p._test_union_sources local testcases = { "ATK Earrings 5", "Scaly +10%", }   for _, tc in ipairs(testcases) do        local out = p.union_sources{args={tc}} if string.len(out) <= 10 then mw.log(string.format("union sources for %s is too short", tc)) end end if string.len(p.union_sources{args={"INVALID UNION REWARD"}}) ~= 0 then mw.log("non-empty union sources for invalid union reward") end if string.len(p.union_sources{args={""}}) ~= 0 then mw.log("non-empty union sources for empty union reward") end end

function p._test_sortable_table if string.len(p.sortable_table) <= 100 then mw.log("sortable table is too short") end end

function p._test_skills_table for k, _ in pairs(guardians) do       if string.len(p.skills_table{args={k}}) <= 100 then mw.log("skills table is too short") end end end

-- Run this before saving function p._test for key, _ in pairs(p) do       if string.sub(key, 1, 1) ~= "_" then mw.log("running _test_" .. key) p["_test_" .. key] end end end

return p