Terra Battle 2 Wiki
Advertisement

Documentation for this module may be created at Module:Guardian/doc

local p = {}

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

local INVALID_SKILL_CAT = "[[Category:Guardians with invalid skill info]]"
local INVALID_PRICE_CAT = "[[Category:Guardians with invalid sell price]]"
local INVALID_JAPANESE_CAT = "[[Category:Guardians with invalid Japanese name]]"
local INVALID_UNION_CAT = "[[Category:Guardians with invalid Union rewards]]"
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('[[File:Skill_icon_%s.png|20x20px]] %s', g.race, g.race)
            local job = string.format('[[File:Skill_icon_%s.png|20x20px]] %s', g.job, g.job)
            local elem = "N/A"
            if not isempty(g.element) then
                elem = string.format('[[File:Skill_icon_%s.png|20x20px]] %s', g.element, g.element)
            end
            table.insert(s, string.format('| %s || [[%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("[[File:Skill_icon_%s.png|20x20px]]", 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 <i>"%s"</i>', style, skill.desc)
            else
                s:fmt('| %s %s<br /><i>"%s"</i>', 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
Advertisement