The Alchemist Code Wiki

READ MORE

The Alchemist Code Wiki
((by SublimeText.Mediawiker))
(Hmm...)
(33 intermediate revisions by the same user not shown)
Line 1: Line 1:
  +
--<nowiki>
  +
local p = {}
  +
local h = {}
  +
  +
local cargo = require('Module:CargoUtil')
 
local render_item_icon = require('Module:Render/Item')._icon
 
local render_item_icon = require('Module:Render/Item')._icon
 
local render_gear_icon = require('Module:Render/Gear')._icon
 
local render_gear_icon = require('Module:Render/Gear')._icon
Line 4: Line 9:
 
local render_unit_icon = require('Module:Render/Unit')._icon
 
local render_unit_icon = require('Module:Render/Unit')._icon
 
local enums = require('Module:Data/Enums')
 
local enums = require('Module:Data/Enums')
  +
local yesno = require('Module:Yesno')
  +
enums.eCardType = {
  +
[0] = "None",
  +
[1] = "Equipment",
  +
[2] = "Enhance_exp",
  +
[3] = "Enhance_trust"
  +
}
  +
local typeMap = {
  +
[1] = 'Equipment',
  +
[2] = 'Enhance EXP',
  +
[3] = 'Enhance Trust',
  +
}
  +
 
local model = require('Module:Data').model
 
local model = require('Module:Data').model
 
local query = model.query
 
local query = model.query
Line 11: Line 29:
 
root:addClass('infobox')
 
root:addClass('infobox')
 
root:addClass(args.class)
 
root:addClass(args.class)
root:css{
 
['float'] = 'right',
 
['margin-left'] = '15px',
 
['width'] = '300px',
 
['border'] = '1px solid #aaa',
 
['background'] = '#eee',
 
['font-size'] = '12px',
 
}
 
 
local heading = root:tag('div')
 
local heading = root:tag('div')
 
heading:addClass('heading')
 
heading:addClass('heading')
heading:css{
 
['background'] = 'goldenrod',
 
['font-weight'] = 700,
 
['font-size'] = '140%',
 
['line-height'] = '1.25em',
 
['display'] = 'flex',
 
['flex-direction'] = 'row',
 
['justify-items'] = 'flex-start',
 
['text-align'] = 'center',
 
['min-height'] = '64px',
 
}
 
 
local icon = heading:tag('div')
 
local icon = heading:tag('div')
 
icon:addClass('infobox-icon')
 
icon:addClass('infobox-icon')
icon:css{
 
['flex'] = '0 0 64px',
 
['padding'] = '0.25rem',
 
}
 
 
icon:node(args.icon)
 
icon:node(args.icon)
 
local name = heading:tag('div')
 
local name = heading:tag('div')
 
name:addClass('name')
 
name:addClass('name')
name:css{
 
['flex'] = '1 1 100%',
 
['display'] = 'flex',
 
['text-align'] = 'center',
 
['align-items'] = 'center',
 
['justify-content'] = 'center',
 
['margin'] = '0',
 
['padding'] = '0.25rem',
 
}
 
 
name:wikitext(args.name)
 
name:wikitext(args.name)
 
local wrapper = root:tag('div')
 
local wrapper = root:tag('div')
 
wrapper:addClass('wrapper')
 
wrapper:addClass('wrapper')
wrapper:css{
 
['flex'] = '1 1 100%',
 
['overflow']='hidden',
 
}
 
 
local dl = wrapper:tag('dl')
 
local dl = wrapper:tag('dl')
dl:css{
 
['display'] = 'grid',
 
['grid-template-columns'] = '100px 1fr',
 
}
 
   
 
local function printRow(t, label, content)
 
local function printRow(t, label, content)
 
if content == nil then return end
 
if content == nil then return end
 
local dt = t:tag('dt')
 
local dt = t:tag('dt')
dt:css{
 
['grid-column-start'] = 1,
 
['padding'] = '2px 8px 2px 3px',
 
['text-align'] = 'right',
 
['border-right'] = '2px solid #aaa',
 
['margin'] = '0 -2px 0 0',
 
}
 
 
dt:wikitext(label)
 
dt:wikitext(label)
 
local dd = t:tag('dd')
 
local dd = t:tag('dd')
dd:css{
 
['grid-column-start'] = 2,
 
['margin'] = 0,
 
['padding'] = '2px 7px',
 
['border-left'] = '2px solid #aaa',
 
}
 
 
dd:wikitext(content)
 
dd:wikitext(content)
 
end
 
end
Line 91: Line 56:
 
end
 
end
   
local p = {}
+
local cats = {}
   
 
function p.getData(iname)
 
function p.getData(iname)
local datapage = mw.ext.cargo.query('ConceptCard', '_pageName', { where = 'iname = "'..iname..'"', groupBy = '_pageName, iname'})[1]
+
local datapage = cargo.query{
  +
tables = 'ConceptCard',
  +
fields = '_pageName',
  +
where = 'iname = "'..iname..'"',
  +
groupBy = '_pageName, iname',
  +
}[1]
 
local pagename = datapage and datapage._pageName or 'Data:Game/MasterParam/ConceptCard/'..iname
 
local pagename = datapage and datapage._pageName or 'Data:Game/MasterParam/ConceptCard/'..iname
 
local frame = mw.getCurrentFrame()
 
local frame = mw.getCurrentFrame()
Line 109: Line 79:
 
return args
 
return args
 
end
 
end
 
local typeMap = {
 
['1'] = 'Equipment',
 
['2'] = 'Enhance EXP',
 
['3'] = 'Enhance Trust',
 
}
 
   
 
local maxLevels = {1, 25, 30, 35, 40}
 
local maxLevels = {1, 25, 30, 35, 40}
Line 120: Line 84:
 
local function getVCR(data)
 
local function getVCR(data)
 
local t = {}
 
local t = {}
  +
mw.logObject(data, 'getVCR')
local f = function(s, render_icon)
 
  +
local function f(s, render_icon)
 
local counts = data['trust_'..s..'_counts']
 
local counts = data['trust_'..s..'_counts']
 
local names = data['trust_'..s..'_names']
 
local names = data['trust_'..s..'_names']
 
if not names then return end
 
if not names then return end
  +
if type(names) == 'string' then
  +
names = mw.text.split(names, '[%|,]')
  +
end
  +
if type(counts) == 'string' then
  +
counts = mw.text.split(counts, '[%|,]')
  +
end
 
for i, v in ipairs(names) do
 
for i, v in ipairs(names) do
 
-- table.insert(sb2, i)
 
-- table.insert(sb2, i)
 
-- table.insert(sb2, (counts[i] > 1) and counts[i] or nil)
 
-- table.insert(sb2, (counts[i] > 1) and counts[i] or nil)
table.insert(t, render_icon({v, name = 'none', count = counts[i] > 1 and counts[i] or nil}))
+
table.insert(t, render_icon({v, name = 'none', count = tonumber(counts[i]) > 1 and tonumber(counts[i]) or nil}))
 
end
 
end
 
end
 
end
Line 141: Line 112:
 
end
 
end
   
local function minQuery(tables, fields, args)
+
local function minQuery(args)
if type(fields) == 'table' then
+
if type(args.fields) == 'string' and args.fields == '*' then
  +
local subModel = mw.loadData('Module:Data/Model/'..args.tables)
fields = table.concat(fields, ',')
 
  +
local t = {'_pageName', 'server'}
elseif fields == '*' then
 
local subModel = mw.loadData('Module:Data/Model/'..tables)
 
local t = {'_pageName,server'}
 
 
for k,v in pairs(subModel) do
 
for k,v in pairs(subModel) do
 
t[#t+1] = k
 
t[#t+1] = k
 
end
 
end
fields = table.concat(t, ',')
+
args.fields = t
 
end
 
end
local row = query(tables, fields, args)[1]
+
local row = cargo.query(args)[1]
 
if not row then return end
 
if not row then return end
 
local obj = {}
 
local obj = {}
Line 166: Line 135:
 
local function queryBuff(iname)
 
local function queryBuff(iname)
 
if not iname then return end
 
if not iname then return end
return minQuery('Buff','*',{where='iname="'..iname..'" AND server="gl"'})
+
return minQuery{tables='Buff',fields='*', where='iname="'..iname..'" AND server="gl"'}
 
end
 
end
 
local function querySkill(iname)
 
local function querySkill(iname)
 
if not iname then return end
 
if not iname then return end
return minQuery('Skill','*',{
+
return minQuery{tables = 'Skill', fields = '*',
 
where = 'iname="'..iname..'" AND server="gl"'
 
where = 'iname="'..iname..'" AND server="gl"'
})
+
}
  +
end
  +
local function queryAbility(iname)
  +
if not iname then return end
  +
return minQuery{tables = 'Ability', fields = '*',
  +
where = 'iname="'..iname..'" AND server="gl"'
  +
}
 
end
 
end
   
Line 185: Line 160:
   
 
local t = {}
 
local t = {}
  +
local rows = cargo.query{
local rows = query('Unit', 'iname', {where = 'ai = "AI_PLAYER" AND (notsmn IS NULL OR hero = 1) AND server = "gl" AND birth_id IN ('..table.concat(ids, ',')..')'})
 
  +
tables = 'Unit',
  +
fields = 'iname',
  +
where = {
  +
'ai = "AI_PLAYER"',
  +
'(notsmn IS NULL OR hero = 1)',
  +
'server = "gl"',
  +
'birth_id IN ('..table.concat(ids, ',')..')',
  +
}
  +
}
 
for i, row in ipairs(rows) do
 
for i, row in ipairs(rows) do
 
t[i] = row.iname
 
t[i] = row.iname
Line 194: Line 178:
 
local function getUnitInamesByGender(sex)
 
local function getUnitInamesByGender(sex)
 
local t = {}
 
local t = {}
  +
local rows = cargo.query{
local rows = query('Unit', 'iname', {where='ai = "AI_PLAYER" AND (notsmn IS NULL OR hero = 1) AND server = "gl" AND sex = "'..sex..'"'})
 
  +
tables = 'Unit',
  +
fields = 'iname',
  +
where = {
  +
'ai = "AI_PLAYER"',
  +
'(notsmn IS NULL OR hero = 1)',
  +
'server = "gl"',
  +
'sex = "'..sex..'"',
  +
}
  +
}
 
for i, row in ipairs(rows) do
 
for i, row in ipairs(rows) do
 
t[i] = row.iname
 
t[i] = row.iname
Line 207: Line 200:
 
inames[i] = string.format('%q', v)
 
inames[i] = string.format('%q', v)
 
end
 
end
  +
if #inames == 0 then return t end
local rows = query('Job', 'iname, origin', {where = 'iname IN ('..table.concat(inames, ',')..') AND server = "gl"'})
 
  +
local rows = cargo.query{
  +
tables = 'Job',
  +
fields = 'iname, origin',
  +
where = 'iname IN ('..table.concat(inames, ',')..') AND server = "gl"'
  +
}
 
for i, row in ipairs(rows) do
 
for i, row in ipairs(rows) do
 
t[i] = (row.origin or '') ~= '' and row.origin or row.iname
 
t[i] = (row.origin or '') ~= '' and row.origin or row.iname
Line 220: Line 218:
 
local t, i = {}, 0
 
local t, i = {}, 0
 
for _, jg_iname in ipairs(job_groups) do
 
for _, jg_iname in ipairs(job_groups) do
local job_group = mw.loadData('Module:Data/MasterParam/JobGroup')[jg_iname]
+
local job_group = cargo.query{
  +
tables = 'JobGroup',
local inames = getOriginalJobInamesFromJobInames(job_group.jobs)
 
  +
fields = 'jobs',
for _, iname in ipairs(inames) do
 
  +
where = 'iname = "'..jg_iname..'" AND server = "gl"',
i = i + 1
 
t[i] = iname
+
}[1]
  +
if job_group then
  +
if type(job_group.jobs) == 'string' then
  +
job_group.jobs = mw.text.split(job_group.jobs, '[%|,]')
  +
end
  +
local inames = getOriginalJobInamesFromJobInames(job_group.jobs)
  +
for _, iname in ipairs(inames) do
  +
i = i + 1
  +
t[i] = iname
  +
end
 
end
 
end
 
end
 
end
Line 242: Line 249:
 
end
 
end
   
local h = {}
 
 
function h.printEffects(root, args)
 
function h.printEffects(root, args)
 
if not args then return end
 
if not args then return end
 
if not args.effects then return end
 
if not args.effects then return end
   
local statusup_skills = {}
+
local stat = nil
 
local card_skills = {}
 
local card_skills = {}
 
local abilities = {}
 
local abilities = {}
Line 253: Line 259:
 
for i, eff in ipairs(args.effects) do
 
for i, eff in ipairs(args.effects) do
 
if eff.statusup_skill then
 
if eff.statusup_skill then
statusup_skills[#statusup_skills+1] = eff
+
stat = eff
 
end
 
end
 
if eff.card_skill then
 
if eff.card_skill then
Line 264: Line 270:
 
 
 
local maxLevel = maxLevels[args.rare+1]
 
local maxLevel = maxLevels[args.rare+1]
  +
if stat then
for i, eff in ipairs(statusup_skills) do
 
 
root:tag('h2'):wikitext('Stats')
 
root:tag('h2'):wikitext('Stats')
h.printStatsTable(root, eff.statusup_skill, maxLevel)
+
h.printStatsTable(root, stat.statusup_skill, maxLevel)
 
end
 
end
   
Line 272: Line 278:
 
root:tag('h2'):wikitext('Group skills')
 
root:tag('h2'):wikitext('Group skills')
 
for i, eff in ipairs(card_skills) do
 
for i, eff in ipairs(card_skills) do
local skill = query('Skill, Loc', 'value', {
+
local skill = query('Skill', '_pageName', {
where = 'iname = "'..eff.card_skill..'" AND server = "gl" AND param = "name" AND lang = "english"',
+
where = 'iname = "'..eff.card_skill..'" AND server = "gl"',
join = 'Skill._pageName = Loc._pageName',
 
 
})[1]
 
})[1]
  +
local skillname = skill and model.getLoc(skill._pageName, 'name') or '???'
 
root:tag('h3'):wikitext(skill.value)
+
root:tag('h3'):wikitext(skillname)
 
h.printStatsTable(root, eff.card_skill, maxLevel)
 
h.printStatsTable(root, eff.card_skill, maxLevel)
 
if eff.add_card_skill_buff_awake then -- limit break buff
 
if eff.add_card_skill_buff_awake then -- limit break buff
Line 285: Line 290:
 
if eff.add_card_skill_buff_lvmax then
 
if eff.add_card_skill_buff_lvmax then
 
root:tag('h4'):wikitext('Max Limit Break')
 
root:tag('h4'):wikitext('Max Limit Break')
local buff = queryBuff(eff.add_card_skill_buff_lvmax)
+
local buffdetails = h.getBuffDetails(eff.add_card_skill_buff_lvmax) or {}
  +
if #buffdetails == 0 then
local tbl = root:tag('table'):addClass('wikitable')
 
  +
root:tag('div'):wikitext('Missing buff '..eff.add_card_skill_buff_lvmax..' from DB')
local tr = tbl:tag('tr')
 
  +
else
tr:tag('th'):wikitext('Type')
 
for lb=5,5 do tr:tag('th'):wikitext('LB'..lb) end
+
local tbl = root:tag('table'):addClass('wikitable')
for i=1,11 do
+
local tr = tbl:tag('tr')
tr = tbl:tag('tr')
+
tr:tag('th'):wikitext('Type')
  +
for lb=5,5 do tr:tag('th'):wikitext('LB'..lb) end
if tonumber(buff['type'..i] or 0) > 0 then
 
local tp = tostring(buff['type'..i])
+
for i, buff in ipairs(buffdetails) do
tr:tag('td'):wikitext(enums.statNameFromType(tp))
+
tr = tbl:tag('tr')
  +
if tonumber(buff.type or 0) > 0 then
local min, max = buff['vini'..i], buff['vmax'..i]
 
local per = (max - min) / 4
+
local statName = enums.statNameFromType(buff.type, buff.tktag)
  +
tr:tag('td'):wikitext(statName)
for lb=5,5 do
 
local value = math.floor((lb - 1) * per + min)
+
local min, max = buff.vini, buff.vmax
  +
local per = (max - min) / 4
tr:tag('td'):wikitext(string.format('%+d', value))
 
  +
for lb=5,5 do
  +
local value = math.floor((lb - 1) * per + min)
  +
tr:tag('td'):wikitext(string.format('%+d', value))
  +
end
 
end
 
end
 
end
 
end
Line 312: Line 321:
 
if #abilities > 0 then
 
if #abilities > 0 then
 
root:tag('h2'):wikitext('Vision abilities')
 
root:tag('h2'):wikitext('Vision abilities')
  +
for i, eff in ipairs(abilities) do
 
  +
local function printAbility(root, eff)
local ability = query('Ability, Loc', 'value', {
 
  +
if not eff then return end
where = 'iname = "'..eff.abil_iname..'" AND server = "gl" AND param = "name" AND lang = "english"',
 
  +
if not eff.abil_iname then return end
join = 'Ability._pageName = Loc._pageName',
 
  +
if not eff.abil_iname_lvmax then return end
})[1]
 
  +
root:tag('h3'):wikitext(ability.value)
 
  +
local ability = {
  +
base = queryAbility(eff.abil_iname),
  +
max = queryAbility(eff.abil_iname_lvmax),
  +
}
  +
  +
local baseName = model.getLoc(ability.base._pageName, 'name')
  +
local maxName = model.getLoc(ability.max._pageName, 'name')
  +
root:tag('h3'):wikitext(baseName)
  +
if maxName ~= baseName then
  +
error('Expected max ability name to be `'..baseName..'` but was `'..maxName..'`.')
  +
end
  +
  +
for _, key in ipairs{'base', 'max'} do
  +
root:tag('h4'):wikitext('Ability '..key)
  +
local ab = ability[key]
  +
mw.logObject(ab, 'ab')
  +
local abType = ab.type_detail and enums.EAbilityTypeDetail[tonumber(ab.type_detail)] or nil
  +
if abType ~= nil and abType ~= 'VisionAbility' then
  +
error('Expected ability type "VisionAbility", but was "'..tostring(abType)..'".')
  +
end
  +
local ul = root:tag('ul')
  +
if ab.slot then
  +
local slot = enums.EAbilitySlot[tonumber(ab.slot)]
  +
ul:tag('li'):wikitext('Slot: [[File:MasterAbilityIcon.png|20px|'..slot..'|link=]] '..slot)
  +
end
  +
if ab.skl1 then
  +
local skill = querySkill(ab.skl1)
  +
if tonumber(skill.atk_type or 0) > 0 then
  +
local atk_type = enums.AttackTypes[tonumber(skill.atk_type)]
  +
if atk_type == 'MagAttack' then
  +
atk_type = 'Magical'
  +
elseif atk_type == 'PhyAttack' then
  +
atk_type = 'Physical'
  +
end
  +
ul:tag('li'):wikitext('DMG Type: [[File:Attack Class '..atk_type..'.png|20px|'..atk_type..'|link=]] '..atk_type)
  +
end
  +
if tonumber(skill.atk_det or 0) > 0 then
  +
local atk_det = enums.attackDetailNameFromKey(skill.atk_det)
  +
if atk_det == 'Blow' then
  +
atk_det = 'Strike'
  +
end
  +
ul:tag('li'):wikitext('ATK Type: [[File:Attack Type '..atk_det..'.png|20px|'..atk_det..'|link=]] '..atk_det)
  +
end
  +
if tonumber(skill.elem or 0) > 0 then
  +
local elem = enums.elemFromKey(tonumber(skill.elem))
  +
ul:tag('li'):wikitext('Element: [[File:'..elem..'Element.png|20px|link=]] '..elem)
  +
end
  +
  +
-- Explanation
  +
local expr = model.getLoc(skill._pageName, 'expr')
  +
root:tag('p'):wikitext(expr)
  +
  +
-- Skill effect
  +
if tonumber(skill.eff_type or 0) > 0 and skill.eff_val_ini and skill.eff_val_max then
  +
local tbl = root:tag('table'):addClass('wikitable')
  +
tbl:tag('caption'):wikitext('Skill Effect')
  +
local tr = tbl:tag('tr')
  +
tr:tag('th'):wikitext('Type')
  +
tr:tag('th'):wikitext('Min')
  +
tr:tag('th'):wikitext('Max')
  +
tr = tbl:tag('tr')
  +
tr:tag('td'):wikitext(enums.SkillEffectTypes[tonumber(skill.eff_type)])
  +
tr:tag('td'):wikitext((100+skill.eff_val_ini)..'%')
  +
tr:tag('td'):wikitext((100+skill.eff_val_max)..'%')
  +
end
  +
  +
-- Scaling
  +
-- Jewel cost
  +
local ul = root:tag('ul')
  +
if tonumber(skill.count or 0) > 0 then
  +
ul:tag('li'):wikitext('Charges: ' .. skill.count)
  +
end
  +
if tonumber(skill.cost or 0) > 0 then
  +
ul:tag('li'):wikitext('Jewel Cost: ' .. skill.cost)
  +
end
  +
if tonumber(skill.eff_h or 0) > 0 then
  +
ul:tag('li'):wikitext('Height: ' .. skill.eff_h)
  +
end
  +
if skill.sran then
  +
ul:tag('li'):wikitext('Select Range: ' .. enums.ESelectType[tonumber(skill.sran)])
  +
ul:tag('li'):wikitext('Range: ' .. (skill.rangemin or 0) .. '-'.. (skill.rangemax or 0))
  +
end
  +
if skill.ssco then
  +
ul:tag('li'):wikitext('Select Scope: ' .. enums.ESelectType[tonumber(skill.ssco)])
  +
ul:tag('li'):wikitext('Scope: ' .. (skill.scope or 0))
  +
end
  +
end
  +
end
  +
end
  +
  +
for _, eff in ipairs(abilities) do
  +
printAbility(root, eff)
 
if eff.cnds_iname then
 
if eff.cnds_iname then
 
h.printEffect(root, eff)
 
h.printEffect(root, eff)
Line 364: Line 465:
 
local l = #t
 
local l = #t
 
local render_icon = require('Module:Render/'..tp)._icon
 
local render_icon = require('Module:Render/'..tp)._icon
local name = query(tp..',Loc', 'value', {where='iname="'..iname..'" AND param="name" AND lang="english" AND server="gl"', join = tp..'._pageName = Loc._pageName'})[1].value
+
local page = cargo.query{tables = 'Pages', fields = '_pageName', where = 'iname = "'..iname..'"'}[1]
  +
local name
  +
if page then
  +
name = page._pageName
  +
else
  +
page = cargo.query{
  +
tables = {tp, 'Loc'},
  +
fields = 'value',
  +
where = {
  +
'iname="'..iname..'"',
  +
'param = "name"',
  +
'lang IN ("english","japanese")',
  +
'server="gl"',
  +
},
  +
join = tp..'._pageName = Loc._pageName',
  +
}[1]
  +
if page then
  +
name = page.value
  +
else
  +
name = iname
  +
end
  +
end
 
t[l+1] = {iname=iname, name=name, icon=render_icon{iname, size='small'}}
 
t[l+1] = {iname=iname, name=name, icon=render_icon{iname, size='small'}}
 
end
 
end
Line 408: Line 530:
 
end
 
end
 
end
 
end
local heading = root:tag('h3')
+
local heading = root:tag('h4')
 
heading:wikitext('Unit and job restriction')
 
heading:wikitext('Unit and job restriction')
 
table.sort(objs, function(a, b)
 
table.sort(objs, function(a, b)
Line 419: Line 541:
 
end
 
end
 
elseif #icons.Trigger.Unit > 0 then
 
elseif #icons.Trigger.Unit > 0 then
local heading = root:tag('h3')
+
local heading = root:tag('h4')
 
heading:wikitext('Unit restriction')
 
heading:wikitext('Unit restriction')
 
for _, obj in ipairs(icons.Trigger.Unit) do
 
for _, obj in ipairs(icons.Trigger.Unit) do
Line 429: Line 551:
 
end
 
end
 
elseif #icons.Trigger.Job > 0 then
 
elseif #icons.Trigger.Job > 0 then
local heading = root:tag('h3')
+
local heading = root:tag('h4')
 
heading:wikitext('Job restriction')
 
heading:wikitext('Job restriction')
 
for _, obj in ipairs(icons.Trigger.Job) do
 
for _, obj in ipairs(icons.Trigger.Job) do
Line 495: Line 617:
 
local buff = queryBuff(buff_iname)
 
local buff = queryBuff(buff_iname)
 
if not buff then return end
 
if not buff then return end
  +
  +
if type(buff.custom_targets) == 'string' then
  +
buff.custom_targets = mw.text.split(buff.custom_targets, '[%|,]')
  +
end
   
 
local t = {}
 
local t = {}
Line 559: Line 685:
 
if not skill then return end
 
if not skill then return end
 
 
local t_buff = queryBuff(skill.t_buff)
+
local t_buff_details = h.getBuffDetails(skill.t_buff) or {}
local s_buff = queryBuff(skill.s_buff)
+
local s_buff_details = h.getBuffDetails(skill.s_buff) or {}
   
 
local stats = {
 
local stats = {
 
 
}
 
}
for i=1,11 do
+
for i, buff in ipairs(t_buff_details) do
if tonumber(t_buff['type'..i] or 0) > 0 then
+
if tonumber(buff.type or 0) > 0 then
local tp = tostring(t_buff['type'..i])
+
local tp = tostring(buff.type)
 
local values = {}
 
local values = {}
local min, max = t_buff['vini'..i], t_buff['vmax'..i]
+
local min, max = buff.vini, buff.vmax
 
local per = (max - min) / (maxLevel - 1)
 
local per = (max - min) / (maxLevel - 1)
 
for level=1,maxLevel do
 
for level=1,maxLevel do
Line 586: Line 711:
 
tr:tag('th'):wikitext('Lvl '..(maxLevel-10))
 
tr:tag('th'):wikitext('Lvl '..(maxLevel-10))
 
tr:tag('th'):wikitext('Lvl '..maxLevel)
 
tr:tag('th'):wikitext('Lvl '..maxLevel)
for i=1,11 do
+
for i, buff in ipairs(t_buff_details) do
if tonumber(t_buff['type'..i] or 0) > 0 then
+
if tonumber(buff.type or 0) > 0 then
 
local tr = simple:tag('tr')
 
local tr = simple:tag('tr')
local tp = tostring(t_buff['type'..i])
+
local tp = tostring(buff.type)
 
local values = stats[tp]
 
local values = stats[tp]
tr:tag('td'):wikitext(enums.statNameFromType(tp))
+
local statName = enums.statNameFromType(buff.type, buff.tktag)
  +
tr:tag('td'):wikitext(statName)
 
tr:tag('td'):wikitext(string.format('%+d', values[1]))
 
tr:tag('td'):wikitext(string.format('%+d', values[1]))
 
tr:tag('td'):wikitext(string.format('%+d', values[maxLevel-10]))
 
tr:tag('td'):wikitext(string.format('%+d', values[maxLevel-10]))
Line 619: Line 745:
 
local tr = wikitable:tag('tr')
 
local tr = wikitable:tag('tr')
 
tr:tag('th'):wikitext('Level')
 
tr:tag('th'):wikitext('Level')
for i = 1, 11 do
+
for i, buff in ipairs(t_buff_details) do
if tonumber(t_buff['type'..i] or 0) > 0 then
+
if tonumber(buff.type or 0) > 0 then
local tp = tostring(t_buff['type'..i])
+
local statName = enums.statNameFromType(buff.type, buff.tktag)
tr:tag('th'):wikitext(enums.statNameFromType(tp))
+
tr:tag('th'):wikitext(statName)
 
end
 
end
 
end
 
end
Line 629: Line 755:
 
local tr = wikitable:tag('tr')
 
local tr = wikitable:tag('tr')
 
tr:tag('td'):wikitext(level)
 
tr:tag('td'):wikitext(level)
for i=1,11 do
+
for i, buff in ipairs(t_buff_details) do
if tonumber(t_buff['type'..i] or 0) > 0 then
+
if tonumber(buff.type or 0) > 0 then
local tp = tostring(t_buff['type'..i])
+
local tp = tostring(buff.type)
 
tr:tag('td'):wikitext(string.format('%+d', stats[tp][level]))
 
tr:tag('td'):wikitext(string.format('%+d', stats[tp][level]))
 
end
 
end
Line 656: Line 782:
 
-- ["vmax1"] = 30,
 
-- ["vmax1"] = 30,
 
-- }
 
-- }
local buff = queryBuff(buff_iname)
+
local buff_details = h.getBuffDetails(buff_iname) or {}
  +
if #buff_details == 0 then
  +
root:tag('div'):wikitext('Missing buff ' .. buff_iname .. ' from DB')
  +
return
  +
end
 
-- Show limit break
 
-- Show limit break
 
local tbl = root:tag('table'):addClass('wikitable')
 
local tbl = root:tag('table'):addClass('wikitable')
Line 662: Line 792:
 
tr:tag('th'):wikitext('Type')
 
tr:tag('th'):wikitext('Type')
 
for lb=1,5 do tr:tag('th'):wikitext('LB'..lb) end
 
for lb=1,5 do tr:tag('th'):wikitext('LB'..lb) end
for i=1,11 do
+
for i, buff in ipairs(buff_details) do
 
tr = tbl:tag('tr')
 
tr = tbl:tag('tr')
if tonumber(buff['type'..i] or 0) > 0 then
+
if tonumber(buff.type or 0) > 0 then
local tp = tostring(buff['type'..i])
+
local statName = enums.statNameFromType(buff.type, buff.tktag)
tr:tag('td'):wikitext(enums.statNameFromType(tp))
+
tr:tag('td'):wikitext(statName)
local min, max = buff['vini'..i], buff['vmax'..i]
+
local min, max = buff.vini, buff.vmax
 
local per = (max - min) / 4
 
local per = (max - min) / 4
 
for lb=1,5 do
 
for lb=1,5 do
Line 677: Line 807:
 
end
 
end
   
local renderPage = function(iname)
+
function h.getBuffDetails(iname)
  +
if not iname then return nil end
local data = minQuery('ConceptCard', '*', {where='iname = "'..iname..'"'})
 
  +
return cargo.query{
  +
tables = 'BuffDetail',
  +
fields = {
  +
'idx',
  +
'calc',
  +
'tktag',
  +
'type',
  +
'vini',
  +
'vmax',
  +
'vone',
  +
},
  +
where = {
  +
'BuffDetail.server = "gl"',
  +
'BuffDetail.buff_iname = "'..iname..'"',
  +
},
  +
orderBy = 'idx',
  +
}
  +
end
  +
  +
local function renderPage(iname)
  +
local card_rows = cargo.query{
  +
tables = {
  +
'ConceptCard',
  +
'Loc = Names',
  +
'Loc = Flavors',
  +
},
  +
fields = {
  +
'ConceptCard.iname = iname',
  +
-- Graphics
  +
'ConceptCard.icon = icon',
  +
-- Type
  +
'ConceptCard.type = type', -- eCardType
  +
'ConceptCard.rare = rare',
  +
'ConceptCard.lvcap = lvcap', -- Default defined by rare
  +
'ConceptCard.birth_id = birth_id', -- The origin of the memento
  +
'ConceptCard.coin_item = coin_item', -- Not sure what this is used for, maybe related to the coin shop
  +
  +
-- Value
  +
'ConceptCard.sell = sell', -- Integer",
  +
'ConceptCard.not_sale = not_sale', -- Boolean",
  +
'ConceptCard.en_cost = en_cost', -- Integer",
  +
'ConceptCard.en_exp = en_exp', -- Integer",
  +
'ConceptCard.en_trust = en_trust', -- Integer",
  +
  +
-- Rewards
  +
'ConceptCard.trust_artifact_names = trust_artifact_names', -- {"String", list='|', dataRef="MasterParam/Artifact"},
  +
'ConceptCard.trust_artifact_counts = trust_artifact_counts', -- {"Integer", list='|'},
  +
'ConceptCard.trust_item_names = trust_item_names', -- {"String", list='|', dataRef="MasterParam/Item"},
  +
'ConceptCard.trust_item_counts = trust_item_counts', -- {"Integer", list='|'},
  +
  +
-- References
  +
'ConceptCard.first_get_unit = first_get_unit', -- {"String", dataRef="MasterParam/Unit"}, -- Unit acquired when first obtaining memento
  +
  +
-- Leader Skill
  +
'ConceptCard.concept_card_groups = concept_card_groups', -- {"String", list='|'},
  +
'ConceptCard.leader_skill = leader_skill', -- {"String", dataRef="MasterParam/Skill"},
  +
-- Labels
  +
'Names.value = name',
  +
'Flavors.value = flavor',
  +
},
  +
where = {
  +
'ConceptCard.iname = "'..iname..'"',
  +
'Names.param = "name" AND Names.lang = "english"',
  +
'Flavors.param = "flavor" AND Flavors.lang = "english"',
  +
},
  +
join = {
  +
'ConceptCard._pageName = Names._pageName',
  +
'ConceptCard._pageName = Flavors._pageName',
  +
},
  +
}
  +
local data = card_rows[1]
 
if not data then
 
if not data then
 
return '`'..iname..'` not found in ConceptCard table.'
 
return '`'..iname..'` not found in ConceptCard table.'
 
end
 
end
  +
cats[#cats+1] = 'Memento'
local data2 = p.getData(iname)
 
  +
local effects = cargo.query{
local getLoc = function(param) return model.getLoc(data._pageName, param) end
 
  +
tables = 'ConceptCardEffect',
local name = getLoc('name')
 
  +
fields = {
  +
'cc_iname',
  +
'abil_iname',
  +
'abil_iname_lvmax',
  +
'add_card_skill_buff_awake',
  +
'add_card_skill_buff_lvmax',
  +
'card_skill',
  +
'cnds_iname',
  +
'is_decrease_eff',
  +
'server',
  +
'skin',
  +
'statusup_skill'
  +
},
  +
where = {
  +
'cc_iname = "'..iname..'"',
  +
'server = "gl"',
  +
}
  +
}
  +
  +
local name = data.name
 
local icon = mw.html.create('div')
 
local icon = mw.html.create('div')
 
icon:addClass('item-icon')
 
icon:addClass('item-icon')
Line 695: Line 916:
 
}
 
}
 
img:wikitext('[[File:Game,ConceptCardIcon,',data.icon,'.png|57px]]')
 
img:wikitext('[[File:Game,ConceptCardIcon,',data.icon,'.png|57px]]')
local maxLevels = {1, 25, 30, 35, 40}
 
 
-- Memento extra data
 
-- Memento extra data
 
local extra = mw.loadData("Module:Data/Extra/Memento")[iname] or {}
 
local extra = mw.loadData("Module:Data/Extra/Memento")[iname] or {}
Line 706: Line 926:
 
end
 
end
 
end
 
end
  +
 
local cats = {
 
'Memento'
 
}
 
 
local root = mw.html.create()
 
local root = mw.html.create()
 
-- Info Box
 
-- Info Box
Line 730: Line 947:
 
},
 
},
 
rows = {
 
rows = {
['Type'] = typeMap[data.type],
+
['Type'] = typeMap[tonumber(data.type)],
 
['Rank'] = (data.rare + 1)..'★',
 
['Rank'] = (data.rare + 1)..'★',
 
['Max level'] = maxLevels[data.rare + 1],
 
['Max level'] = maxLevels[data.rare + 1],
Line 745: Line 962:
 
}
 
}
 
root:node(infobox)
 
root:node(infobox)
if data.type and typeMap[data.type] then
+
if tonumber(data.type) and typeMap[tonumber(data.type)] then
cats[#cats+1] = typeMap[data.type] .. ' memento'
+
cats[#cats+1] = typeMap[tonumber(data.type)] .. ' memento'
 
end
 
end
   
Line 754: Line 971:
 
:css{
 
:css{
 
['margin'] = 'auto',
 
['margin'] = 'auto',
['width'] = 'fit-content',
+
['display'] = 'flex',
  +
['flex-direction'] = 'column',
  +
['min-width'] = '300px',
 
}
 
}
  +
:tag('div')
:wikitext('[[File:Game,ConceptCard,', data.icon, '.png]]')
 
  +
:wikitext('[[File:Game,ConceptCard,', data.icon, '.png|512px]]')
  +
:done()
 
:tag('div'):addClass('flavor')
 
:tag('div'):addClass('flavor')
 
:css{
 
:css{
Line 762: Line 983:
 
['padding'] = '8px',
 
['padding'] = '8px',
 
}
 
}
:wikitext(getLoc('flavor'))
+
:wikitext(data.flavor)
  +
:done()
  +
  +
h.printLeaderSkill(root, data.leader_skill)
  +
  +
-- mw.logObject(effects, 'effects')
 
-- TODO: Stats + Effects
 
-- TODO: Stats + Effects
h.printEffects(root, data2.gl)
+
h.printEffects(root, {
  +
rare = data.rare,
  +
lvmax = data.lvmax,
  +
effects = effects,
  +
})
  +
 
-- Category
 
-- Category
 
for i, cat in ipairs(cats) do
 
for i, cat in ipairs(cats) do
Line 774: Line 1,005:
 
end
 
end
   
local renderFuncs = {}
+
local renderFuncs = {
renderFuncs.Icon = render_memento_icon
+
Icon = render_memento_icon
  +
}
   
local renderTransclude = function(iname, args)
+
local function renderTransclude(iname, args)
 
local type = mw.text.trim(args[1])
 
local type = mw.text.trim(args[1])
 
args[1] = iname
 
args[1] = iname
Line 796: Line 1,028:
 
local args = require('Module:Arguments').getArgs(frame, {parentFirst = true})
 
local args = require('Module:Arguments').getArgs(frame, {parentFirst = true})
 
return renderPage(args[1])
 
return renderPage(args[1])
  +
end
  +
  +
function h.printLeaderSkill(root, sk_iname)
  +
if sk_iname == nil then return end
  +
local skill = cargo.query{
  +
tables = {'Skill', 'Loc = SkillName', 'Loc = SkillExpr'},
  +
fields = {
  +
'Skill.iname = iname',
  +
'SkillName.value = name',
  +
'SkillExpr.value = expr',
  +
'Skill.type = type',
  +
'Skill.eff_type = eff_type',
  +
'Skill.target = target',
  +
'Skill.cond = cond',
  +
'Skill.timing = timing',
  +
'Skill.t_buff = t_buff',
  +
},
  +
where = {
  +
'Skill.server = "gl"',
  +
'Skill.iname = "'..sk_iname..'"',
  +
'SkillName.param = "name" AND SkillName.lang = "english"',
  +
'SkillExpr.param = "expr" AND SkillExpr.lang = "english"',
  +
},
  +
join = {'Skill._pageName = SkillName._pageName', 'Skill._pageName = SkillExpr._pageName'},
  +
}[1]
  +
if not skill then return nil end
  +
root:newline():tag('h2'):wikitext("Leader Skill")
  +
root:newline():tag('h3'):wikitext(skill.name)
  +
root:newline():tag('p'):wikitext(skill.expr)
  +
skill.type = enums.ESkillType[tonumber(skill.type)]
  +
skill.eff_type = enums.SkillEffectTypes[tonumber(skill.eff_type)]
  +
skill.target = enums.ESkillTarget[tonumber(skill.target)]
  +
skill.cond = enums.ESkillCondition[tonumber(skill.cond)]
  +
skill.timing = enums.ESkillTiming[tonumber(skill.timing)]
  +
  +
if skill.t_buff then
  +
local buff = cargo.query{
  +
tables = {'Buff'},
  +
fields = {
  +
'iname',
  +
},
  +
where = {
  +
'Buff.iname = "'..skill.t_buff..'"',
  +
'Buff.server = "gl"',
  +
},
  +
}[1]
  +
local buff_details = h.getBuffDetails(buff.iname) or {}
  +
-- mw.logObject(buff_details)
  +
if #buff_details > 0 then
  +
local tbl = root:newline():tag('table'):addClass('wikitable')
  +
tbl:newline():tag('caption'):wikitext('Target Buff ('..skill.target..') ('..skill.cond..')')
  +
tbl:newline():tag('tr'):tag('th'):attr('colspan', 3):wikitext('Stats')
  +
tbl:newline():tag('tr')
  +
:tag('th'):wikitext('Type'):done()
  +
:tag('th'):wikitext('Min'):done()
  +
:tag('th'):wikitext('Max'):done()
  +
for i, buff in ipairs(buff_details) do
  +
local tr = tbl:newline():tag('tr')
  +
local fmt = yesno(buff.calc) and '%+d%%' or '%+d'
  +
local statName = enums.statNameFromType(buff.type, buff.tktag)
  +
tr:tag('th'):wikitext(statName)
  +
tr:tag('td'):wikitext(string.format(fmt, tonumber(buff.vini)))
  +
tr:tag('td'):wikitext(string.format(fmt, tonumber(buff.vmax)))
  +
if buff.tktag then
  +
cats[#cats+1] = statName
  +
end
  +
end
  +
end
  +
local custom_targets = cargo.query{
  +
tables = {
  +
'Buff',
  +
'Buff__custom_targets',
  +
'CustomTarget',
  +
},
  +
fields = {
  +
'CustomTarget.birth_id = birth_id', -- Integer
  +
'CustomTarget.sex = sex', -- Integer
  +
'CustomTarget.dark = dark', -- Integer
  +
'CustomTarget.fire = fire', -- Integer
  +
'CustomTarget.shine = shine', -- Integer
  +
'CustomTarget.thunder = thunder', -- Integer
  +
'CustomTarget.water = water', -- Integer
  +
'CustomTarget.wind = wind', -- Integer
  +
  +
'CustomTarget.concept_card_groups = concept_card_groups', -- List of String
  +
'CustomTarget.job_groups = job_groups', -- List of String
  +
'CustomTarget.unit_groups = unit_groups', -- List of String
  +
'CustomTarget.units = units', -- List of String
  +
},
  +
where = {
  +
'Buff.iname = "'..skill.t_buff..'"',
  +
'Buff.server = "gl"',
  +
'CustomTarget.server = "gl"',
  +
},
  +
join = {
  +
'Buff._ID = Buff__custom_targets._rowID',
  +
'Buff__custom_targets._value = CustomTarget.iname',
  +
},
  +
}
  +
for _, custom_target in ipairs(custom_targets) do
  +
if custom_target.concept_card_groups then
  +
local cards = cargo.query{
  +
tables = {
  +
'ConceptCardGroup',
  +
'ConceptCardGroup__cards',
  +
'ConceptCard',
  +
'Pages',
  +
},
  +
fields = {
  +
-- 'ConceptCard.iname = iname',
  +
'ConceptCard.icon = icon',
  +
-- Type
  +
'ConceptCard.type = type', -- eCardType
  +
'ConceptCard.rare = rare',
  +
'Pages._pageName = PAGENAME',
  +
},
  +
where = {
  +
'ConceptCardGroup.iname = "'..custom_target.concept_card_groups..'"',
  +
'ConceptCardGroup.server = "gl"',
  +
'ConceptCard.server = "gl"',
  +
},
  +
join = {
  +
'ConceptCardGroup._ID = ConceptCardGroup__cards._rowID',
  +
'ConceptCardGroup__cards._value = ConceptCard.iname',
  +
'ConceptCard.iname = Pages.iname',
  +
}
  +
}
  +
root:newline():tag('h4'):wikitext('Memento Group')
  +
local ul = root:newline():tag('ul')
  +
for _, card in ipairs(cards) do
  +
mw.logObject(card, 'card')
  +
-- local icon = ''
  +
if card.PAGENAME then
  +
local icon = string.format(
  +
'[[File:Game,ConceptCardIcon,%s.png|32px|link=%s]] [[%s]]',
  +
card.icon, card.PAGENAME, card.PAGENAME)
  +
ul:newline():tag('li'):wikitext(icon)
  +
end
  +
end
  +
end
  +
end
  +
end
 
end
 
end
   
 
function p.test(frame)
 
function p.test(frame)
  +
-- local root = mw.html.create()
return p.main{'TS_LOST_ZWEI_01'}
 
  +
-- h.printLeaderSkill(root, 'SK_LS_TS_ENVYRIA_CLOE_01')
  +
-- mw.log(tostring(root))
  +
return
  +
-- p.renderPage('TS_SAGA_BIRGITTA_01') ..
  +
p.renderPage('TS_GL_SIBLINGS_01')
 
end
 
end
   
 
return p
 
return p
  +
--</nowiki>

Revision as of 07:29, 10 December 2020

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

--<nowiki>
local p = {}
local h = {}

local cargo = require('Module:CargoUtil')
local render_item_icon = require('Module:Render/Item')._icon
local render_gear_icon = require('Module:Render/Gear')._icon
local render_memento_icon = require('Module:Render/Memento')._icon
local render_unit_icon = require('Module:Render/Unit')._icon
local enums = require('Module:Data/Enums')
local yesno = require('Module:Yesno')
enums.eCardType = {
	[0] = "None",
	[1] = "Equipment",
	[2] = "Enhance_exp",
	[3] = "Enhance_trust"
}
local typeMap = {
	[1] = 'Equipment',
	[2] = 'Enhance EXP',
	[3] = 'Enhance Trust',
}

local model = require('Module:Data').model
local query = model.query

local function makeInfobox(args)
	local root = mw.html.create('div')
	root:addClass('infobox')
	root:addClass(args.class)
	local heading = root:tag('div')
	heading:addClass('heading')
	local icon = heading:tag('div')
	icon:addClass('infobox-icon')
	icon:node(args.icon)
	local name = heading:tag('div')
	name:addClass('name')
	name:wikitext(args.name)
	local wrapper = root:tag('div')
	wrapper:addClass('wrapper')
	local dl = wrapper:tag('dl')

	local function printRow(t, label, content)
		if content == nil then return end
		local dt = t:tag('dt')
		dt:wikitext(label)
		local dd = t:tag('dd')
		dd:wikitext(content)
	end
	
	for i, label in ipairs(args.labels) do
		printRow(dl, label, args.rows[label])
	end

	return tostring(root)
end

local cats = {}

function p.getData(iname)
	local datapage = cargo.query{
		tables = 'ConceptCard',
		fields = '_pageName',
		where = 'iname = "'..iname..'"',
		groupBy = '_pageName, iname',
	}[1]
	local pagename = datapage and datapage._pageName or 'Data:Game/MasterParam/ConceptCard/'..iname
	local frame = mw.getCurrentFrame()
	local title = mw.title.new(pagename)
	local content = title:getContent()
	local args = {}
	local params = content:match('^%s*%{%{#invoke:Data%|insert%|(.-)%}%}%s*$')
	for param in mw.text.gsplit(params, '|', true) do
		local k, v = param:match('([^=]+)=(.+)')
		local obj = mw.text.jsonDecode(frame:preprocess(v))
		if k == 'gl' or k == 'jp' then obj.server = k end
		args[k] = obj
	end
	return args
end

local maxLevels = {1, 25, 30, 35, 40}

local function getVCR(data)
	local t = {}
	mw.logObject(data, 'getVCR')
	local function f(s, render_icon)
		local counts = data['trust_'..s..'_counts']
		local names = data['trust_'..s..'_names']
		if not names then return end
		if type(names) == 'string' then
			names = mw.text.split(names, '[%|,]')
		end
		if type(counts) == 'string' then
			counts = mw.text.split(counts, '[%|,]')
		end
		for i, v in ipairs(names) do
			-- table.insert(sb2, i)
			-- table.insert(sb2, (counts[i] > 1) and counts[i] or nil)
			table.insert(t, render_icon({v, name = 'none', count = tonumber(counts[i]) > 1 and tonumber(counts[i]) or nil}))
		end
	end
	f('item', render_item_icon)
	f('artifact', render_gear_icon)
	return #t > 0 and table.concat(t) or nil
end

local function getUnitReward(data)
	local iname = data.first_get_unit or ''
	if iname == '' then return nil end
	return render_unit_icon{iname, size='small'}
end

local function minQuery(args)
	if type(args.fields) == 'string' and args.fields == '*' then
		local subModel = mw.loadData('Module:Data/Model/'..args.tables)
		local t = {'_pageName', 'server'}
		for k,v in pairs(subModel) do
			t[#t+1] = k
		end
		args.fields = t
	end
	local row = cargo.query(args)[1]
	if not row then return end
	local obj = {}
	for k,v in pairs(row) do
		if type(v) == 'table' then
			if #v > 0 then obj[k] = v end
		elseif v ~= '' then
			obj[k] = v
		end
	end
	return obj
end
local function queryBuff(iname)
	if not iname then return end
	return minQuery{tables='Buff',fields='*', where='iname="'..iname..'" AND server="gl"'}
end
local function querySkill(iname)
	if not iname then return end
	return minQuery{tables = 'Skill', fields = '*',
		where = 'iname="'..iname..'" AND server="gl"'
	}
end
local function queryAbility(iname)
	if not iname then return end
	return minQuery{tables = 'Ability', fields = '*',
		where = 'iname="'..iname..'" AND server="gl"'
	}
end

local function getUnitInamesByOrigins(birth_ids)
	if type(birth_ids) == 'number' then
		birth_ids = {birth_ids}
	end
	local ids = {}
	for i, birth_id in ipairs(birth_ids) do
		ids[i] = string.format('%q', birth_id)
	end

	local t = {}
	local rows = cargo.query{
		tables = 'Unit',
		fields = 'iname',
		where = {
			'ai = "AI_PLAYER"',
			'(notsmn IS NULL OR hero = 1)',
			'server = "gl"',
			'birth_id IN ('..table.concat(ids, ',')..')',
		}
	}
	for i, row in ipairs(rows) do
		t[i] = row.iname
	end
	return t
end

local function getUnitInamesByGender(sex)
	local t = {}
	local rows = cargo.query{
		tables = 'Unit',
		fields = 'iname',
		where = {
			'ai = "AI_PLAYER"',
			'(notsmn IS NULL OR hero = 1)',
			'server = "gl"',
			'sex = "'..sex..'"',
		}
	}
	for i, row in ipairs(rows) do
		t[i] = row.iname
	end
	return t
end

local function getOriginalJobInamesFromJobInames( jobs )
	local t = {}
	local inames = {}
	for i, v in ipairs(jobs) do
		inames[i] = string.format('%q', v)
	end
	if #inames == 0 then return t end
	local rows = cargo.query{
		tables = 'Job',
		fields = 'iname, origin',
		where = 'iname IN ('..table.concat(inames, ',')..') AND server = "gl"'
	}
	for i, row in ipairs(rows) do
		t[i] = (row.origin or '') ~= '' and row.origin or row.iname
	end
	return t
end

local function getOriginalJobInamesFromJobGroups( job_groups )
	if type(job_groups) == 'string' then
		job_groups = {job_groups}
	end
	local t, i = {}, 0
	for _, jg_iname in ipairs(job_groups) do
		local job_group = cargo.query{
			tables = 'JobGroup',
			fields = 'jobs',
			where = 'iname = "'..jg_iname..'" AND server = "gl"',
		}[1]
		if job_group then
			if type(job_group.jobs) == 'string' then
				job_group.jobs = mw.text.split(job_group.jobs, '[%|,]')
			end
			local inames = getOriginalJobInamesFromJobInames(job_group.jobs)
			for _, iname in ipairs(inames) do
				i = i + 1
				t[i] = iname
			end
		end
	end
	return t
end

local function getUnitInamesFromUnitGroups( unit_groups )
	local t, i = {}, 0
	for _, ug_iname in ipairs(unit_groups) do
		local units = mw.loadData('Module:Data/MasterParam/UnitGroup')[ug_iname].units
		for _, iname in ipairs(units) do
			i = i + 1
			t[i] = iname
		end
	end
	return t
end

function h.printEffects(root, args)
	if not args then return end
	if not args.effects then return end

	local stat = nil
	local card_skills = {}
	local abilities = {}

	for i, eff in ipairs(args.effects) do
		if eff.statusup_skill then
			stat = eff
		end
		if eff.card_skill then
			card_skills[#card_skills+1] = eff
		end
		if eff.abil_iname then
			abilities[#abilities+1] = eff
		end
	end
	
	local maxLevel = maxLevels[args.rare+1]
	if stat then
		root:tag('h2'):wikitext('Stats')
		h.printStatsTable(root, stat.statusup_skill, maxLevel)
	end

	if #card_skills > 0 then
		root:tag('h2'):wikitext('Group skills')
		for i, eff in ipairs(card_skills) do
			local skill = query('Skill', '_pageName', {
				where = 'iname = "'..eff.card_skill..'" AND server = "gl"',
			})[1]
			local skillname = skill and model.getLoc(skill._pageName, 'name') or '???'
			root:tag('h3'):wikitext(skillname)
			h.printStatsTable(root, eff.card_skill, maxLevel)
			if eff.add_card_skill_buff_awake then -- limit break buff
				root:tag('h4'):wikitext('Limit Break')
				h.printAddCardSkillBuffAwake(root, eff.add_card_skill_buff_awake)
			end
			if eff.add_card_skill_buff_lvmax then
				root:tag('h4'):wikitext('Max Limit Break')
				local buffdetails = h.getBuffDetails(eff.add_card_skill_buff_lvmax) or {}
				if #buffdetails == 0 then
					root:tag('div'):wikitext('Missing buff '..eff.add_card_skill_buff_lvmax..' from DB')
				else
					local tbl = root:tag('table'):addClass('wikitable')
					local tr = tbl:tag('tr')
					tr:tag('th'):wikitext('Type')
					for lb=5,5 do tr:tag('th'):wikitext('LB'..lb) end
					for i, buff in ipairs(buffdetails) do
						tr = tbl:tag('tr')
						if tonumber(buff.type or 0) > 0 then
							local statName = enums.statNameFromType(buff.type, buff.tktag)
							tr:tag('td'):wikitext(statName)
							local min, max = buff.vini, buff.vmax
							local per = (max - min) / 4
							for lb=5,5 do
								local value = math.floor((lb - 1) * per + min)
								tr:tag('td'):wikitext(string.format('%+d', value))
							end
						end
					end
				end
			end

			if eff.cnds_iname then
				h.printEffect(root, eff)
			end
		end
	end
	if #abilities > 0 then
		root:tag('h2'):wikitext('Vision abilities')

		local function printAbility(root, eff)
			if not eff then return end
			if not eff.abil_iname then return end
			if not eff.abil_iname_lvmax then return end

			local ability = {
				base = queryAbility(eff.abil_iname),
				max = queryAbility(eff.abil_iname_lvmax),
			}

			local baseName = model.getLoc(ability.base._pageName, 'name')
			local maxName = model.getLoc(ability.max._pageName, 'name')
			root:tag('h3'):wikitext(baseName)
			if maxName ~= baseName then
				error('Expected max ability name to be `'..baseName..'` but was `'..maxName..'`.')
			end

			for _, key in ipairs{'base', 'max'} do
				root:tag('h4'):wikitext('Ability '..key)
				local ab = ability[key]
				mw.logObject(ab, 'ab')
				local abType = ab.type_detail and enums.EAbilityTypeDetail[tonumber(ab.type_detail)] or nil
				if abType ~= nil and abType ~= 'VisionAbility' then
					error('Expected ability type "VisionAbility", but was "'..tostring(abType)..'".')
				end
				local ul = root:tag('ul')
				if ab.slot then
					local slot = enums.EAbilitySlot[tonumber(ab.slot)]
					ul:tag('li'):wikitext('Slot: [[File:MasterAbilityIcon.png|20px|'..slot..'|link=]] '..slot)
				end
				if ab.skl1 then
					local skill = querySkill(ab.skl1)
					if tonumber(skill.atk_type or 0) > 0 then
						local atk_type = enums.AttackTypes[tonumber(skill.atk_type)]
						if atk_type == 'MagAttack' then
							atk_type = 'Magical'
						elseif atk_type == 'PhyAttack' then
							atk_type = 'Physical'
						end
						ul:tag('li'):wikitext('DMG Type: [[File:Attack Class '..atk_type..'.png|20px|'..atk_type..'|link=]] '..atk_type)
					end
					if tonumber(skill.atk_det or 0) > 0 then
						local atk_det = enums.attackDetailNameFromKey(skill.atk_det)
						if atk_det == 'Blow' then
							atk_det = 'Strike'
						end
						ul:tag('li'):wikitext('ATK Type: [[File:Attack Type '..atk_det..'.png|20px|'..atk_det..'|link=]] '..atk_det)
					end
					if tonumber(skill.elem or 0) > 0 then
						local elem = enums.elemFromKey(tonumber(skill.elem))
						ul:tag('li'):wikitext('Element: [[File:'..elem..'Element.png|20px|link=]] '..elem)
					end

					-- Explanation
					local expr = model.getLoc(skill._pageName, 'expr')
					root:tag('p'):wikitext(expr)

					-- Skill effect
					if tonumber(skill.eff_type or 0) > 0 and skill.eff_val_ini and skill.eff_val_max then
						local tbl = root:tag('table'):addClass('wikitable')
						tbl:tag('caption'):wikitext('Skill Effect')
						local tr = tbl:tag('tr')
						tr:tag('th'):wikitext('Type')
						tr:tag('th'):wikitext('Min')
						tr:tag('th'):wikitext('Max')
						tr = tbl:tag('tr')
						tr:tag('td'):wikitext(enums.SkillEffectTypes[tonumber(skill.eff_type)])
						tr:tag('td'):wikitext((100+skill.eff_val_ini)..'%')
						tr:tag('td'):wikitext((100+skill.eff_val_max)..'%')
					end

					-- Scaling
					-- Jewel cost
					local ul = root:tag('ul')
					if tonumber(skill.count or 0) > 0 then
						ul:tag('li'):wikitext('Charges: ' .. skill.count)
					end
					if tonumber(skill.cost or 0) > 0 then
						ul:tag('li'):wikitext('Jewel Cost: ' .. skill.cost)
					end
					if tonumber(skill.eff_h or 0) > 0 then
						ul:tag('li'):wikitext('Height: ' .. skill.eff_h)
					end
					if skill.sran then
						ul:tag('li'):wikitext('Select Range: ' .. enums.ESelectType[tonumber(skill.sran)])
						ul:tag('li'):wikitext('Range: ' .. (skill.rangemin or 0) .. '-'.. (skill.rangemax or 0))
					end
					if skill.ssco then
						ul:tag('li'):wikitext('Select Scope: ' .. enums.ESelectType[tonumber(skill.ssco)])
						ul:tag('li'):wikitext('Scope: ' .. (skill.scope or 0))
					end
				end
			end
		end

		for _, eff in ipairs(abilities) do
			printAbility(root, eff)
			if eff.cnds_iname then
				h.printEffect(root, eff)
			end
		end
	end
end

function h.printEffect(root, args)
	-- Stats
	if args.statusup_skill then
		h.printStatsTable(root, args.statusup_skill, args.rare)
	end

	local inames = {}
	-- Condition groups
	for i, iname in ipairs(h.getTriggerGroups(args.cnds_iname) or {}) do
		inames[iname] = inames[iname] or {}
		inames[iname].hasConditions = true
	end
	-- Buff groups
	for i, iname in ipairs(h.getBuffGroups(args.add_card_skill_buff_awake) or {}) do
		inames[iname] = inames[iname] or {}
		inames[iname].hasBuff = true
	end

	local icons = {
		['Trigger + Buff'] = {Unit={},Job={}},
		['Trigger'] = {Unit={},Job={}},
		['Buff'] = {Unit={},Job={}},
		[''] = {Unit={},Job={}},
	}

	for iname, thing in pairs(inames) do
		local triggerbuff = {}
		if thing.hasConditions then
			triggerbuff[#triggerbuff+1] = 'Trigger'
		end
		if thing.hasBuff then
			triggerbuff[#triggerbuff+1] = 'Buff'
		end
		triggerbuff = table.concat(triggerbuff, ' + ')
		local prefix = iname:sub(1,3)
		local tp = prefix == 'UN_' and 'Unit' or prefix == 'JB_' and 'Job' or ''
		local t = icons[triggerbuff][tp]
		local l = #t
		local render_icon = require('Module:Render/'..tp)._icon
		local page = cargo.query{tables = 'Pages', fields = '_pageName', where = 'iname = "'..iname..'"'}[1]
		local name
		if page then
			name = page._pageName
		else
			page = cargo.query{
				tables = {tp, 'Loc'},
				fields = 'value',
				where = {
					'iname="'..iname..'"',
					'param = "name"',
					'lang IN ("english","japanese")',
					'server="gl"',
				},
				join = tp..'._pageName = Loc._pageName',
			}[1]
			if page then
				name = page.value
			else
				name = iname
			end
		end
		t[l+1] = {iname=iname, name=name, icon=render_icon{iname, size='small'}}
	end

	if args.add_card_skill_buff_awake then
		for _, triggerbuff in ipairs({'Trigger + Buff', 'Trigger', 'Buff', ''}) do
			for _, tp in ipairs({'Unit', 'Job'}) do
				local objs = icons[triggerbuff][tp]
				if #objs > 0 then
					local heading = root:tag('h4')
					heading:wikitext(tp, ' group')
					if triggerbuff ~= '' then
						heading:wikitext(' (', triggerbuff:lower(), ')')
					end
					table.sort(objs, function(a, b)
						return a.name < b.name
					end)
					for i, obj in ipairs(objs) do
						root:wikitext(obj.icon)
					end
				end
			end
		end
	elseif args.abil_iname then
		local objs = {}
		if #icons.Trigger.Unit > 0 and #icons.Trigger.Job > 0 then
			for _, unit in ipairs(icons.Trigger.Unit) do
				local jb_inames = {}
				local rows = query('Unit, Unit__jobsets, JobSet',
					'JobSet.job = job',
					{
						where = 'Unit.server = "gl" AND JobSet.server = "gl" AND Unit.iname = "'..unit.iname..'"',
						join = 'Unit._ID = Unit__jobsets._rowID, Unit__jobsets._value = JobSet.iname',

					})
				for _, row in ipairs(rows) do
					jb_inames[row.job] = true
				end
				for _, job in ipairs(icons.Trigger.Job) do
					if jb_inames[job.iname] then
						objs[#objs+1] = {unit=unit, job=job}
					end
				end
			end
			local heading = root:tag('h4')
			heading:wikitext('Unit and job restriction')
			table.sort(objs, function(a, b)
				return (a.unit.name < b.unit.name) and (a.job.name < b.job.name)
			end)
			local ul = root:tag('ul')
			for _, obj in ipairs(objs) do
				local li = ul:tag('li')
				li:wikitext(obj.unit.icon, ' + ', obj.job.icon)
			end
		elseif #icons.Trigger.Unit > 0 then
			local heading = root:tag('h4')
			heading:wikitext('Unit restriction')
			for _, obj in ipairs(icons.Trigger.Unit) do
				objs[#objs+1] = obj
			end
			table.sort(objs, function(a,b) return a.name < b.name end)
			for _, obj in ipairs(objs) do
				root:wikitext(obj.icon)
			end
		elseif #icons.Trigger.Job > 0 then
			local heading = root:tag('h4')
			heading:wikitext('Job restriction')
			for _, obj in ipairs(icons.Trigger.Job) do
				objs[#objs+1] = obj
			end
			table.sort(objs, function(a,b) return a.name < b.name end)
			for _, obj in ipairs(objs) do
				root:wikitext(obj.icon)
			end
		end
	end
end

function h.getTriggerGroups(cnds_iname)
	if not cnds_iname then return end
	-- Condition groups
	local conditions = mw.loadData('Module:Data/MasterParam/ConceptCardConditions')[cnds_iname]
	if not conditions then return end

	local t = {}
	if conditions.birth_id then
		for _, iname in ipairs(getUnitInamesByOrigins(conditions.birth_id)) do
			t[#t+1] = iname
		end
	end

	if conditions.job_group then
		for _, iname in ipairs(getOriginalJobInamesFromJobGroups(conditions.job_group)) do
			t[#t+1] = iname
		end
	end

	if conditions.sex then
		for _, iname in ipairs(getUnitInamesByGender(conditions.sex)) do
			t[#t+1] = iname
		end
	end

	if conditions.un_group then
		for _, iname in ipairs(mw.loadData('Module:Data/MasterParam/UnitGroup')[conditions.un_group].units) do
			t[#t+1] = iname
		end
	end

	if conditions.el_dark then
	end
	if conditions.el_fire then
	end
	if conditions.el_shine then
	end
	if conditions.el_thunder then
	end
	if conditions.el_water then
	end
	if conditions.el_wind then
	end
	if conditions.supported_units then
	end

	return t
end

function h.getBuffGroups(buff_iname)
	if not buff_iname then return end
	local buff = queryBuff(buff_iname)
	if not buff then return end
	
	if type(buff.custom_targets) == 'string' then
		buff.custom_targets = mw.text.split(buff.custom_targets, '[%|,]')
	end

	local t = {}
	for i, ct_iname in ipairs(buff.custom_targets or {}) do
		local custom_target = mw.loadData('Module:Data/MasterParam/CustomTarget')[ct_iname]
		if custom_target.birth_id then
			for _, iname in ipairs(getUnitInamesByOrigins(custom_target.birth_id)) do
				t[#t+1] = iname
			end
		end

		if custom_target.concept_card_groups then
			-- TODO
		end

		if custom_target.jobs then
			for _, iname in ipairs(getOriginalJobInamesFromJobInames(custom_target.jobs)) do
				t[#t+1] = iname
			end
		end
		if custom_target.job_groups then
			for _, iname in ipairs(getOriginalJobInamesFromJobGroups(custom_target.job_groups)) do
				t[#t+1] = iname
			end				
		end

		if custom_target.sex then
			for _, iname in ipairs(getUnitInamesByGender(custom_target.sex)) do
				t[#t+1] = iname
			end
		end
		if custom_target.units then
			for _, iname in ipairs(custom_target.units) do
				t[#t+1] = iname
			end
		end
		if custom_target.unit_groups then
			for _, iname in ipairs(getUnitInamesFromUnitGroups(custom_target.unit_groups)) do
				t[#t+1] = iname
			end
		end
		if custom_target.dark then
		end
		if custom_target.fire then
		end
		if custom_target.shine then
		end
		if custom_target.thunder then
		end
		if custom_target.water then
		end
		if custom_target.wind then
		end
	end
	local un_group = buff.un_group and mw.loadData('Module:Data/MasterParam/UnitGroup')[buff.un_group]
	for _, iname in ipairs(un_group or {}) do
		t[#t+1] = iname
	end
	return t
end

function h.printStatsTable(root, skill_iname, maxLevel)
	local skill = querySkill(skill_iname)
	if not skill then return end
	
	local t_buff_details = h.getBuffDetails(skill.t_buff) or {}
	local s_buff_details = h.getBuffDetails(skill.s_buff) or {}

	local stats = {
	}
	for i, buff in ipairs(t_buff_details) do
		if tonumber(buff.type or 0) > 0 then
			local tp = tostring(buff.type)
			local values = {}
			local min, max = buff.vini, buff.vmax
			local per = (max - min) / (maxLevel - 1)
			for level=1,maxLevel do
				values[level] = math.floor((level - 1) * per + min)
			end
			stats[tp] = values
		end
	end

	-- Render
	root:tag('h4'):wikitext('Simple stats')
	local simple = root:tag('table'):addClass('wikitable')
	local tr = simple:tag('tr')
	tr:tag('th'):wikitext('Type')
	tr:tag('th'):wikitext('Lvl 1')
	tr:tag('th'):wikitext('Lvl '..(maxLevel-10))
	tr:tag('th'):wikitext('Lvl '..maxLevel)
	for i, buff in ipairs(t_buff_details) do
		if tonumber(buff.type or 0) > 0 then
			local tr = simple:tag('tr')
			local tp = tostring(buff.type)
			local values = stats[tp]
			local statName = enums.statNameFromType(buff.type, buff.tktag)
			tr:tag('td'):wikitext(statName)
			tr:tag('td'):wikitext(string.format('%+d', values[1]))
			tr:tag('td'):wikitext(string.format('%+d', values[maxLevel-10]))
			tr:tag('td'):wikitext(string.format('%+d', values[maxLevel]))
		end
	end
	
	--[==[ Scrollable table?
		<div style="display: table">
		<div style="max-height: 240px; overflow-y: scroll; box-sizing: border-box; padding: 4px; border: 1px solid #131313">
		-- Long table here
		</div>
		</div>
	--]==]

	root:tag('h4'):wikitext('Detailed stats')
	local detailed = root:tag('div'):css{
		display = 'flex',
		['flex-wrap'] = 'wrap',
	}
	for col = 1, maxLevel/10 do
		local flexitem = detailed:tag('div'):css{
			flex = 0,
			background = col % 2 == 0 and 'orange' or 'salmon', -- yum ^_^
		}
		local wikitable = flexitem:tag('table'):addClass('wikitable')
		local tr = wikitable:tag('tr')
		tr:tag('th'):wikitext('Level')
		for i, buff in ipairs(t_buff_details) do
			if tonumber(buff.type or 0) > 0 then
				local statName = enums.statNameFromType(buff.type, buff.tktag)
				tr:tag('th'):wikitext(statName)
			end
		end
		for row = 1, 10 do
			local level = (col - 1) * 10 + row
			local tr = wikitable:tag('tr')
			tr:tag('td'):wikitext(level)
			for i, buff in ipairs(t_buff_details) do
				if tonumber(buff.type or 0) > 0 then
					local tp = tostring(buff.type)
					tr:tag('td'):wikitext(string.format('%+d', stats[tp][level]))
				end
			end
		end
	end
end

function h.printAddCardSkillBuffAwake(root, buff_iname)
	-- add_card_skill_buff_awake = table#1 {
	-- ["_pageName"] = "Data:Game/MasterParam/Buff/BUFF GS1 LOST ZWEI 01 BONUS 01",
	-- ["calc1"] = 0,
	-- ["chktgt"] = 0,
	-- ["cond"] = 4,
	-- ["custom_targets"] = table#2 {
	-- "CT_LOST",
	-- "CT_UROBOROS",
	-- },
	-- ["iname"] = "BUFF_GS1_LOST_ZWEI_01_BONUS_01",
	-- ["server"] = "gl",
	-- ["timing"] = 1,
	-- ["type1"] = 7,
	-- ["vini1"] = 8,
	-- ["vmax1"] = 30,
	-- }
	local buff_details = h.getBuffDetails(buff_iname) or {}
	if #buff_details == 0 then
		root:tag('div'):wikitext('Missing buff ' .. buff_iname .. ' from DB')
		return
	end
	-- Show limit break
	local tbl = root:tag('table'):addClass('wikitable')
	local tr = tbl:tag('tr')
	tr:tag('th'):wikitext('Type')
	for lb=1,5 do tr:tag('th'):wikitext('LB'..lb) end
	for i, buff in ipairs(buff_details) do
		tr = tbl:tag('tr')
		if tonumber(buff.type or 0) > 0 then
			local statName = enums.statNameFromType(buff.type, buff.tktag)
			tr:tag('td'):wikitext(statName)
			local min, max = buff.vini, buff.vmax
			local per = (max - min) / 4
			for lb=1,5 do
				local value = math.floor((lb - 1) * per + min)
				tr:tag('td'):wikitext(string.format('%+d', value))
			end
		end
	end
end

function h.getBuffDetails(iname)
	if not iname then return nil end
	return cargo.query{
		tables = 'BuffDetail',
		fields = {
			'idx',
			'calc',
			'tktag',
			'type',
			'vini',
			'vmax',
			'vone',
		},
		where = {
			'BuffDetail.server = "gl"',
			'BuffDetail.buff_iname = "'..iname..'"',
		},
		orderBy = 'idx',
	}
end

local function renderPage(iname)
	local card_rows = cargo.query{
		tables = {
			'ConceptCard',
			'Loc = Names',
			'Loc = Flavors',
		},
		fields = {
		  'ConceptCard.iname = iname',
		  -- Graphics
		  'ConceptCard.icon = icon',
		  -- Type
		  'ConceptCard.type = type', -- eCardType
		  'ConceptCard.rare = rare',
		  'ConceptCard.lvcap = lvcap', -- Default defined by rare
		  'ConceptCard.birth_id = birth_id', -- The origin of the memento
		  'ConceptCard.coin_item = coin_item', -- Not sure what this is used for, maybe related to the coin shop
		  
		  -- Value
		  'ConceptCard.sell = sell', -- Integer",
		  'ConceptCard.not_sale = not_sale', -- Boolean",
		  'ConceptCard.en_cost = en_cost', -- Integer",
		  'ConceptCard.en_exp = en_exp', -- Integer",
		  'ConceptCard.en_trust = en_trust', -- Integer",
		
		  -- Rewards
		  'ConceptCard.trust_artifact_names = trust_artifact_names', -- {"String", list='|', dataRef="MasterParam/Artifact"},
		  'ConceptCard.trust_artifact_counts = trust_artifact_counts', -- {"Integer", list='|'},
		  'ConceptCard.trust_item_names = trust_item_names', -- {"String", list='|', dataRef="MasterParam/Item"},
		  'ConceptCard.trust_item_counts = trust_item_counts', -- {"Integer", list='|'},
		
		  -- References
		  'ConceptCard.first_get_unit = first_get_unit', -- {"String", dataRef="MasterParam/Unit"}, -- Unit acquired when first obtaining memento
		  
		  -- Leader Skill
		  'ConceptCard.concept_card_groups = concept_card_groups', -- {"String", list='|'},
		  'ConceptCard.leader_skill = leader_skill', -- {"String", dataRef="MasterParam/Skill"},
		  -- Labels
		  'Names.value = name',
		  'Flavors.value = flavor',
		},
		where = {
			'ConceptCard.iname = "'..iname..'"',
			'Names.param = "name" AND Names.lang = "english"',
			'Flavors.param = "flavor" AND Flavors.lang = "english"',
		},
		join = {
			'ConceptCard._pageName = Names._pageName',
			'ConceptCard._pageName = Flavors._pageName',
		},
	}
	local data = card_rows[1]
	if not data then
		return '`'..iname..'` not found in ConceptCard table.'
	end
	cats[#cats+1] = 'Memento'
	local effects = cargo.query{
		tables = 'ConceptCardEffect',
		fields = {
			'cc_iname',
			'abil_iname',
			'abil_iname_lvmax',
			'add_card_skill_buff_awake',
			'add_card_skill_buff_lvmax',
			'card_skill',
			'cnds_iname',
			'is_decrease_eff',
			'server',
			'skin',
			'statusup_skill'
		},
		where = {
			'cc_iname = "'..iname..'"',
			'server = "gl"',
		}
	}

	local name = data.name
	local icon = mw.html.create('div')
	icon:addClass('item-icon')
	local img = icon:tag('div')
	img:addClass('img x-'..data.rare..' y-4')
	img:css{
		['width'] = '57px',
		['height'] = '57px',
		['padding'] = '3.5px',
	}
	img:wikitext('[[File:Game,ConceptCardIcon,',data.icon,'.png|57px]]')
	-- Memento extra data
	local extra = mw.loadData("Module:Data/Extra/Memento")[iname] or {}
	local releaseDates
	for _, region in ipairs{'jp', 'gl'} do
		local date = extra[region..'Date']
		if date then
			releaseDates = releaseDates or mw.html.create('ul')
			releaseDates:tag('li'):wikitext(region:upper(), ': ', date)
		end
	end
	
	local root = mw.html.create()
	-- Info Box
	local infobox = makeInfobox{
		name = name,
		icon = icon,
		labels = {
			'Type',
			'Rank',
			'Max level',
			'Enhancer cost',
			'Enhancer EXP',
			'Enhancer trust',
			'Vision Clear Reward',
			'Gives unit',
			'Source',
			'Release dates',
			'Global only',
			'External links',
		},
		rows = {
			['Type'] = typeMap[tonumber(data.type)],
			['Rank'] = (data.rare + 1)..'★',
			['Max level'] = maxLevels[data.rare + 1],
			['Enhancer cost'] = data.en_cost, -- zeni required to upgrade a memento with this memento
			['Enhancer EXP'] = data.en_exp, -- EXP given by this memento
			['Enhancer trust'] = data.en_trust,
			['Vision Clear Reward'] = getVCR(data),
			['Gives unit'] = getUnitReward(data),
			['Source'] = extra.source or 'Unreleased',
			['Release dates'] = releaseDates and tostring(releaseDates) or nil,
			['Global only'] = extra.glOnly and 'Yes' or nil,
			['External links'] = '[http://www.alchemistcodedb.com/card/'..iname:gsub('_','-'):lower()..' AlchemistCodeDB]',
		}
	}
	root:node(infobox)
	if tonumber(data.type) and typeMap[tonumber(data.type)] then
		cats[#cats+1] = typeMap[tonumber(data.type)] .. ' memento'
	end

	-- Body
	-- Card Image + Flavor
	root:tag('div'):addClass('responsive-img')
		:css{
			['margin'] = 'auto',
			['display'] = 'flex',
			['flex-direction'] = 'column',
			['min-width'] = '300px',
		}
		:tag('div')
			:wikitext('[[File:Game,ConceptCard,', data.icon, '.png|512px]]')
			:done()
		:tag('div'):addClass('flavor')
			:css{
				['max-width'] = '1024px',
				['padding'] = '8px',
			}
			:wikitext(data.flavor)
			:done()
			
	h.printLeaderSkill(root, data.leader_skill)

	-- mw.logObject(effects, 'effects')
	-- TODO: Stats + Effects
	h.printEffects(root, {
		rare = data.rare,
		lvmax = data.lvmax,
		effects = effects,
	})

	-- Category
	for i, cat in ipairs(cats) do
		root:wikitext('[[Category:', cat, ']]')
	end
	-- TODO Implement http://cdn.alchemistcodedb.com/assets/big-card-frames-b34ac394d6035fb657d491b956e2c4764836b51b9361fccc4b31e722a27df510.png
	
	return tostring(root)
end

local renderFuncs = {
	Icon = render_memento_icon
}

local function renderTransclude(iname, args)
	local type = mw.text.trim(args[1])
	args[1] = iname
	return (renderFuncs[type] or renderFuncs.Name)(args)
end

function p._main(iname, args)
	if not args or args.isPage then
		return renderPage(iname)
	else
		return renderTransclude(iname, args)
	end
end

p.renderPage = renderPage

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {parentFirst = true})
	return renderPage(args[1])
end

function h.printLeaderSkill(root, sk_iname)
	if sk_iname == nil then return end
	local skill = cargo.query{
		tables = {'Skill', 'Loc = SkillName', 'Loc = SkillExpr'},
		fields = {
			'Skill.iname = iname',
			'SkillName.value = name',
			'SkillExpr.value = expr',
			'Skill.type = type',
			'Skill.eff_type = eff_type',
			'Skill.target = target',
			'Skill.cond = cond',
			'Skill.timing = timing',
			'Skill.t_buff = t_buff',
		},
		where = {
			'Skill.server = "gl"',
			'Skill.iname = "'..sk_iname..'"',
			'SkillName.param = "name" AND SkillName.lang = "english"',
			'SkillExpr.param = "expr" AND SkillExpr.lang = "english"',
		},
		join = {'Skill._pageName = SkillName._pageName', 'Skill._pageName = SkillExpr._pageName'},
	}[1]
	if not skill then return nil end
	root:newline():tag('h2'):wikitext("Leader Skill")
	root:newline():tag('h3'):wikitext(skill.name)
	root:newline():tag('p'):wikitext(skill.expr)
	skill.type = enums.ESkillType[tonumber(skill.type)]
	skill.eff_type = enums.SkillEffectTypes[tonumber(skill.eff_type)]
	skill.target = enums.ESkillTarget[tonumber(skill.target)]
	skill.cond = enums.ESkillCondition[tonumber(skill.cond)]
	skill.timing = enums.ESkillTiming[tonumber(skill.timing)]

	if skill.t_buff then
		local buff = cargo.query{
			tables = {'Buff'},
			fields = {
				'iname',
			},
			where = {
				'Buff.iname = "'..skill.t_buff..'"',
				'Buff.server = "gl"',
			},
		}[1]
		local buff_details = h.getBuffDetails(buff.iname) or {}
		-- mw.logObject(buff_details)
		if #buff_details > 0 then
			local tbl = root:newline():tag('table'):addClass('wikitable')
			tbl:newline():tag('caption'):wikitext('Target Buff ('..skill.target..') ('..skill.cond..')')
			tbl:newline():tag('tr'):tag('th'):attr('colspan', 3):wikitext('Stats')
			tbl:newline():tag('tr')
				:tag('th'):wikitext('Type'):done()
				:tag('th'):wikitext('Min'):done()
				:tag('th'):wikitext('Max'):done()
			for i, buff in ipairs(buff_details) do
				local tr = tbl:newline():tag('tr')
				local fmt = yesno(buff.calc) and '%+d%%' or '%+d'
				local statName = enums.statNameFromType(buff.type, buff.tktag)
				tr:tag('th'):wikitext(statName)
				tr:tag('td'):wikitext(string.format(fmt, tonumber(buff.vini)))
				tr:tag('td'):wikitext(string.format(fmt, tonumber(buff.vmax)))
				if buff.tktag then
					cats[#cats+1] = statName
				end
			end
		end
		local custom_targets = cargo.query{
			tables = {
				'Buff',
				'Buff__custom_targets',
				'CustomTarget',
			},
			fields = {
				'CustomTarget.birth_id = birth_id', -- Integer
				'CustomTarget.sex = sex', -- Integer
				'CustomTarget.dark = dark', -- Integer
				'CustomTarget.fire = fire', -- Integer
				'CustomTarget.shine = shine', -- Integer
				'CustomTarget.thunder = thunder', -- Integer
				'CustomTarget.water = water', -- Integer
				'CustomTarget.wind = wind', -- Integer

				'CustomTarget.concept_card_groups = concept_card_groups', -- List of String
				'CustomTarget.job_groups = job_groups', -- List of String
				'CustomTarget.unit_groups = unit_groups', -- List of String
				'CustomTarget.units = units', -- List of String
			},
			where = {
				'Buff.iname = "'..skill.t_buff..'"',
				'Buff.server = "gl"',
				'CustomTarget.server = "gl"',
			},
			join = {
				'Buff._ID = Buff__custom_targets._rowID',
				'Buff__custom_targets._value = CustomTarget.iname',
			},
		}
		for _, custom_target in ipairs(custom_targets) do
			if custom_target.concept_card_groups then
				local cards = cargo.query{
					tables = {
						'ConceptCardGroup',
						'ConceptCardGroup__cards',
						'ConceptCard',
						'Pages',
					},
					fields = {
						-- 'ConceptCard.iname = iname',
						'ConceptCard.icon = icon',
						-- Type
						'ConceptCard.type = type', -- eCardType
						'ConceptCard.rare = rare',
						'Pages._pageName = PAGENAME',
					},
					where = {
						'ConceptCardGroup.iname = "'..custom_target.concept_card_groups..'"',
						'ConceptCardGroup.server = "gl"',
						'ConceptCard.server = "gl"',
					},
					join = {
						'ConceptCardGroup._ID = ConceptCardGroup__cards._rowID',
						'ConceptCardGroup__cards._value = ConceptCard.iname',
						'ConceptCard.iname = Pages.iname',
					}
				}
				root:newline():tag('h4'):wikitext('Memento Group')
				local ul = root:newline():tag('ul')
				for _, card in ipairs(cards) do
					mw.logObject(card, 'card')
					-- local icon = ''
					if card.PAGENAME then
						local icon = string.format(
							'[[File:Game,ConceptCardIcon,%s.png|32px|link=%s]] [[%s]]',
							card.icon, card.PAGENAME, card.PAGENAME)
						ul:newline():tag('li'):wikitext(icon)
					end
				end
			end
		end
	end
end

function p.test(frame)
	-- local root = mw.html.create()
	-- h.printLeaderSkill(root, 'SK_LS_TS_ENVYRIA_CLOE_01')
	-- mw.log(tostring(root))
	return
	-- p.renderPage('TS_SAGA_BIRGITTA_01') ..
	p.renderPage('TS_GL_SIBLINGS_01')
end

return p
--</nowiki>