Module:Equipment

local p = {}

local equipment = mw.loadData('Module:Equipment/data')

local INVALID_EQUIP_STAT = "" local INVALID_CLASS_CAT = "" local INVALID_TYPE_CAT = "" local INVALID_SELL_VALUE_CAT = "" local UNKNOWN_EQUIP = "Some Unknown Equipment"

-- area order is based on number of cells in the 6x8 battle grid local area_order = { ["Area (3)"] = 490, -- 7*7 = 49 ["Cross (3x3)"] = 330, -- 3*8+3*3 = 33 ["Area (2)"] = 250, -- 5*5 = 25 ["3 Columns"] = 241, -- 3*8 = 24 ["Diamond (3)"] = 240, -- 1+3+5+6+5+3+1 = 24 ["3 Rows"] = 180, -- 3*6 = 18 ["Ring"] = 160, -- 5+5+3+3 = 16 ["Cross (All)"] = 132, -- 8+5 = 13 ["Diamond (2)"] = 131, -- 1+3+5+3+1 = 13 ["X (3)"] = 130, -- 3*4+1 = 13 ["Cross (2)"] = 91, -- 9 ["Area (1)"] = 90, -- 3*3 = 9 ["1 Column"] = 80, -- 8 ["Lateral (1) + Vertical (2)"] = 71, -- 5+2 = 7 ["Lateral (2) + Vertical (1)"] = 70, -- 5+2 = 7 ["1 Row"] = 60, -- 6 ["Cross (1)"] = 52, -- 5 ["Vertical (2)"] = 51, -- 5 ["Lateral (2)"] = 50, -- 5 ["Vertical (1)"] = 31, -- 3 ["Lateral (1)"] = 30, -- 3 ["None"] = 0, }

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

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

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

local function lookup(name) return equipment[paramstring(name)] end

function tablelength(t) local n = 0 for _ in pairs(t) do       n = n + 1 end return n 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 e = lookup(p.key(frame)) if e == nil then return fallback end return tostring(e[key]) end

-- Returns the stat at lv. -- x is the stats at level 1. -- maxx is the stats at level maxlv. local function stat_at_level(lv, maxlv, x, maxx) -- mw.log(string.format("stat_at_level %d, %d, %d, %d", lv, maxlv, x, maxx)) if lv > maxlv then lv = maxlv end if maxlv > 1 then return x + math.floor((maxx - x) * (lv-1) / (maxlv-1)) end return x end

local function hp_at_level(e, lv) return stat_at_level(lv, e.max_lv, e.hp, e.max_hp) end local function mp_at_level(e, lv) return stat_at_level(lv, e.max_lv, e.mp, e.max_mp) end local function atk_at_level(e, lv) return stat_at_level(lv, e.max_lv, e.atk, e.max_atk) end local function def_at_level(e, lv) return stat_at_level(lv, e.max_lv, e.def, e.max_def) end local function matk_at_level(e, lv) return stat_at_level(lv, e.max_lv, e.matk, e.max_matk) end local function mdef_at_level(e, lv) return stat_at_level(lv, e.max_lv, e.mdef, e.max_mdef) end

local function get_stat(frame, stat) local def = tonumber(paramstring(frame.args.default)) if def == nil then def = 0 end local e = lookup(p.key(frame)) if e == nil then return def end local lv = tonumber(frame.args.level) local x = e[stat] local maxx = e["max_" .. stat] local maxlv = e["max_lv"]

if lv == nil or x == nil or maxx == nil or maxlv == nil then return def end if lv < 1 or maxlv < 1 or x < 0 or maxx maxx then return def end local x = stat_at_level(lv, maxlv, x, maxx) if isempty(paramstring(frame.args.default)) then return x   end if x ~= def then return def .. " " .. INVALID_EQUIP_STAT end return x end

local function get_range_attr(name, attr, pos) local e = lookup(name) if e == nil or e.ranges == nil then return "" end local r = e.ranges[pos] if r == nil then return "" end return r[attr] end

local function pframe_range_table(name, pframe) local area = {} local level = {} local class = {} for i=1,8 do       local a = paramstring(pframe.args[string.format("range %d", i)]) if not isempty(a) then area[i] = a           level[i] = paramstring(pframe.args[string.format("range %d level", i)]) class[i] = paramstring(pframe.args[string.format("range %d class", i)]) end end if tablelength(area) == 0 then for i=1,8 do           local a = get_range_attr(name, "area", i)            if not isempty(a) then area[i] = a               level[i] = get_range_attr(name, "level", i)                class[i] = get_range_attr(name, "class", i)            end end end if tablelength(area) == 0 then return "" end s = {} table.insert(s, "== Range ==\n") table.insert(s, "{| class=\"wikitable\"\n") table.insert(s, "|-\n") table.insert(s, "! Level\n") table.insert(s, "! Range\n") for i=1,8 do       if not isempty(area[i]) then -- filenames can't have '+' character in them local file = string.format("",               string.gsub(area[i], "+", "&")) table.insert(s, "|-\n") table.insert(s, string.format("! %s\n", tostring(level[i]))) table.insert(s, string.format("| %s %s %s\n", file, area[i], class[i])) end end table.insert(s, "|}\n") return table.concat(s, "") end

function p.key(frame) local key = paramstring(frame.args[1]) if equipment[key] ~= nil then return key end key = mw.title.getCurrentTitle.text key, _ = key:gsub(" %(Equipment%)$", "") return key end

function p.hp(frame) return get_stat(frame, "hp") end function p.mp(frame) return get_stat(frame, "mp") end function p.atk(frame) return get_stat(frame, "atk") end function p.def(frame) return get_stat(frame, "def") end function p.matk(frame) return get_stat(frame, "matk") end function p.mdef(frame) return get_stat(frame, "mdef") end

function p.range_table(frame) return pframe_range_table(p.key(frame), mw.getCurrentFrame:getParent) end

function p.jpname(frame) return get_attr(frame, "jpname", "") end function p.class(frame) return cmp_default(frame, get_attr(frame, "class", ""), INVALID_CLASS_CAT) end function p.type(frame) return cmp_default(frame, get_attr(frame, "type", ""), INVALID_TYPE_CAT) end function p.sell_value(frame) return cmp_default(frame, get_attr(frame, "coin", ""), INVALID_SELL_VALUE_CAT) end

function p._check_cargo local result = mw.ext.cargo.query("equipment", "_pageName,name", {limit=500}) local incargo = {} for _, row in ipairs(result) do        local e = equipment[row._pageName] if e == nil then mw.log("only in cargo: ", row._pageName) end incargo[row._pageName] = true end for k, v in pairs(equipment) do       if not incargo[k] then mw.log("not in cargo: ", k)        end end end

local function max_range(e) if e.ranges == nil then return nil end local maxlv = 0 local maxr = nil for i, r in ipairs(e.ranges) do        if r.level > maxlv then maxlv = r.level maxr = r        end end return maxr 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 rclass_order = {Peta=1, Tera=2, Giga=3, Mega=4}

local keys = {} for k in pairs(equipment) 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 !! Type !! Max HP !! Max MP !! Max ATK !! Max DEF !! Max MATK !! Max MDEF !! Max Class !! Max Range') for _, key in ipairs(keys) do       local e = equipment[key] table.insert(s, '|-') local icon = entity_icon{args={image=string.format('Equipment_%s_icon.png', key)}} local class = string.format('data-sort-value=%d|%s', class_order[e.class] or 100, e.class) local type = string.format(' %s', e.type, e.type) local maxclass = 'data-sort-value=100| N/A' local maxrange = 'data-sort-value=999| N/A' local maxr = max_range(e) if maxr ~= nil then maxclass = string.format('data-sort-value=%d|%s', rclass_order[maxr.class], maxr.class) maxrange = string.format('data-sort-value=%d|',                   -(area_order[maxr.area] or 999), maxr.area:gsub("+", "&")) end table.insert(s, string.format('| %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s', icon, key, e.name, class, type, e.max_hp, e.max_mp, e.max_atk, e.max_def, e.max_matk, e.max_mdef, maxclass, maxrange)) end table.insert(s, '|}') return table.concat(s, "\n") end

function p.sortable_table_ranges 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 rclass_order = {Peta=1, Tera=2, Giga=3, Mega=4}

local keys = {} for k in pairs(equipment) 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 !! Type !! Level !! HP !! MP !! ATK !! DEF !! MATK !! MDEF !! Range Class !! Range') for _, key in ipairs(keys) do       local e = equipment[key] if e.ranges ~= nil then for _, maxr in ipairs(e.ranges) do               table.insert(s, '|-') local icon = entity_icon{args={image=string.format('Equipment_%s_icon.png', key)}} local class = string.format('data-sort-value=%d|%s', class_order[e.class] or 100, e.class) local type = string.format(' %s', e.type, e.type) local maxclass = string.format('data-sort-value=%d|%s', rclass_order[maxr.class], maxr.class) local maxrange = string.format('data-sort-value=%d|',                   -(area_order[maxr.area] or 999), maxr.area:gsub("+", "&")) table.insert(s, string.format('| %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s || %s', icon, key, e.name, class, type, maxr.level, hp_at_level(e, maxr.level), mp_at_level(e, maxr.level), atk_at_level(e, maxr.level), def_at_level(e, maxr.level), matk_at_level(e, maxr.level), mdef_at_level(e, maxr.level), maxclass, maxrange)) end end end table.insert(s, '|}') return table.concat(s, "\n") end

-- TEST FUNCTIONS

-- all stats are tested by this function function p._test_hp local testcases = { {name="Dagger", level=1, hp=0,   mp=0,  atk=14, def=0,  matk=14, mdef=0}, {name="Dagger", level=49, hp=0,  mp=0,  atk=18, def=0,  matk=18, mdef=0}, {name="Dagger", level=60, hp=0,  mp=0,  atk=20, def=0,  matk=20, mdef=0}, {name="Beret", level=1,  hp=3,   mp=5,  atk=0,  def=0,  matk=2,  mdef=9}, {name="Beret", level=49, hp=84,  mp=22, atk=0,  def=6,  matk=11, mdef=27}, {name="Beret", level=59, hp=101, mp=26, atk=0,  def=8,  matk=12, mdef=30}, {name="Beret", level=70, hp=120, mp=30, atk=0,  def=10, matk=15, mdef=35}, }   for _, tc in ipairs(testcases) do        for _, stat in ipairs{"hp", "mp", "atk", "def", "matk", "mdef"} do            local x = p[stat]{args={tc.name, level=tc.level}} if x ~= tc[stat] then mw.log(string.format("%s %s at level %d is %d; expected %d", tc.name, stat, tc.level, x, tc[stat])) end local x = p[stat]{args={tc.name, level=tc.level, default="123456"}} if x ~= "123456 " .. INVALID_EQUIP_STAT then mw.log(string.format("%s %s at level %d is %s; expected invalid category", tc.name, stat, tc.level, x)) end end end if p.atk{args={UNKNOWN_EQUIP, level=1}} ~= 0 then mw.log("invalid stat for unknown equipment") end if p.atk{args={UNKNOWN_EQUIP, level=1, default="123456"}} ~= 123456 then mw.log("default stat not used for unknown equipment") end end p._test_mp = function end p._test_atk = function end p._test_def = function end p._test_matk = function end p._test_mdef = function end

local function test_range_attrs local testcases = { {name="Yoichi Bow", pos=1, level=40, class="Tera", area="Cross (1)"}, {name="Yoichi Bow", pos=3, level=60, class="Tera", area="Diamond (3)"}, {name="Yoichi Bow", pos=6, level=90, class="Peta", area="Diamond (3)"}, {name="White Robe", pos=1, level="", class="",     area=""}, {name=UNKNOWN_EQUIP, pos=1, level="", class="",    area=""}, }   for _, tc in ipairs(testcases) do        for _, attr in ipairs{"level", "class", "area"} do            local x = get_range_attr(tc.name, attr, tc.pos) if x ~= tc[attr] then mw.log(string.format("%s for %s at pos %d is %d; expected %d", attr, tc.name, tc.pos, x, tc[attr])) end end end end

function p._test_range_table test_range_attrs for name, e in pairs(equipment) do       local tab = pframe_range_table(name, {args={}}) if e.ranges == nil and string.len(tab) ~= 0 then mw.log("range table is not empty for " .. name) end if e.ranges ~= nil and string.len(tab) < 10 then mw.log("range table is too short for " .. name) end end end

function p._test_class local testcases = { {           name="Achilles Armor", jpname="アキレウスの鎧", class="Z", type="Armor", coin="3000" },       {            name="  Achilles Armor  ", jpname="アキレウスの鎧", class="Z", type="Armor", coin="3000" },       {            name="Sun Blade", jpname = "サンブレード", class="A", type="Sword", coin="2000" },       {            name=UNKNOWN_EQUIP, jpname = "", class="", type="", coin=""}, {           name="Sun Blade", jpname = "サンブレード", class="123456 " .. INVALID_CLASS_CAT, type="123456 " .. INVALID_TYPE_CAT, coin="123456 " .. INVALID_SELL_VALUE_CAT, default="123456" },       {            name=UNKNOWN_EQUIP, jpname = "", class="123456", type="123456", coin="123456", default="123456" },   }    local func = {"jpname", "class", "type", "sell_value"} local attr = {"jpname", "class", "type", "coin"} for _, tc in ipairs(testcases) do       for k, attr in ipairs(attr) do            local x = p[func[k]]{args={tc.name}} if tc.default ~= nil then x = p[func[k]]{args={tc.name, default=tc.default}} end if x ~= tc[attr] then mw.log(string.format("%s for %s is %s; expected %s", attr, tc.name, x, tc[attr])) end end end end function p._test_type end function p._test_sell_value end function p._test_jpname end

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

function p._test_sortable_table_ranges if string.len(p.sortable_table_ranges) <= 500 then mw.log("sortable table is too short") end end

function p._test_area_order for k, e in pairs(equipment) do       if e.ranges ~= nil then for _, r in ipairs(e.ranges) do                if area_order[r.area] == nil then mw.log(string.format("unknown format for %s: %s", e.name, r.area)) end end end end end

function p._test_key for k, _ in pairs(equipment) do       if p.key{args={k}} ~= k then mw.log("bad key " .. k)       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 p._test_area_order end

return p