Module:Page/Memento/sandbox

-- local p = {} local h = {}

bit32 = require('bit32') local cargo = require('Module:CargoUtil') local util_table = require('Module:TableUtil') local util_vars = require('Module:VarsUtil') -- local model = require('Module:Data').model local render_item_icon = require('Module:Render/Item')._icon2 local render_gear_icon = require('Module:Render/Gear')._icon2 local render_memento_icon = require('Module:Render/Memento')._icon2 local render_unit_icon = require('Module:Render/Unit')._icon2 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', }

h.flags = { NONE = 0x0, TRIGGER = 0x1, BUFF = 0x2, TRIGGER_BUFF = 0x3, }

local function setdefault(dict, key, value) dict[key] = dict[key] or value return dict[key] end

function h.quote( s ) if not s then return end return ('%q'):format(s) end

function h.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}

function h.getVCR(data) local t = {} local rows = cargo.query{ tables = { 'ConceptCard=CC', 'ConceptCard__trust_item_names=CC_IT_names', 'Item=IT', 'ItemLoc=ITL', 'Pages=ITP', 'ConceptCard__trust_item_counts=CC_IT_counts', 'ConceptCard__trust_artifact_names=CC_AF_names', 'Artifact=AF', 'ArtifactLoc=AFL', 'Pages=AFP', 'ConceptCard__trust_artifact_counts=CC_AF_counts', },		join = { 'CC._ID=CC_IT_counts._rowID', 'CC._ID=CC_IT_names._rowID', 'CC_IT_names._value=IT.iname', 'IT.iname=ITL.iname', 'IT.iname=ITP.iname', 'CC._ID=CC_AF_counts._rowID', 'CC._ID=CC_AF_names._rowID', 'CC_AF_names._value=AF.iname', 'AF.iname=AFL.iname', 'AF.iname=AFP.iname', },		where = { 'CC.iname="'..data.iname..'"', 'CC.server="gl"', 'COALESCE(IT.server,CC.server)=CC.server', 'COALESCE(ITL.lang,"english")="english"', 'COALESCE(CC_IT_names._position,0)=COALESCE(CC_IT_counts._position,0)', 'COALESCE(AF.server,CC.server)=CC.server', 'COALESCE(AFL.lang,"english")="english"', 'COALESCE(CC_AF_names._position,0)=COALESCE(CC_AF_counts._position,0)', },		fields = { 'CC_IT_names._position=CC_IT_idx', 'IT.iname=IT_iname', 'IT.icon=IT_icon', 'ITL.name=IT_name', 'IT.rare=IT_rare', 'IT.type=IT_type', 'CC_IT_counts._value=IT_count', 'CC_AF_names._position=CC_AF_idx', 'AF.iname=AF_iname', 'AF.icon=AF_icon', 'AFL.name=AF_name', 'AF.rini=AF_rare', 'AF.type=AF_type', 'CC_AF_counts._value=AF_count', }	}	local items = {} local gears = {} for i, row in ipairs(rows) do		local idx = tonumber(row.CC_IT_idx) if idx then items[idx] = items[idx] or render_item_icon{data={ iname = row.IT_iname, icon = row.IT_icon, name = row.IT_name, rare = tonumber(row.IT_rare), type = tonumber(row.IT_type), }, name='none', count=(tonumber(row.IT_count) or 0) > 1 and tonumber(row.IT_count)} end local idx = tonumber(row.CC_AF_idx) if idx then items[idx] = items[idx] or render_gear_icon{data={ iname = row.AF_iname, icon = row.AF_icon, name = row.AF_name, rare = tonumber(row.AF_rare), type = tonumber(row.AF_type), }, name='none', count=(tonumber(row.AF_count) or 0) > 1 and tonumber(row.AF_count)} end end local t = {} t[#t+1] = table.concat(items) t[#t+1] = table.concat(gears)

return #t > 0 and table.concat(t) or nil end

function h.getUnitReward(data) local iname = data.first_get_unit or '' if iname == '' then return nil end local row = cargo.query{ tables = {'Unit=UN', 'UnitLoc=UNL', 'Pages=P'}, join = {'UN.iname = UNL.iname', 'UN.iname = P.iname'}, fields = { 'UN.iname = iname', 'UNL.name = name', 'P._pageName = _pageName', 'UN.rare = rare', 'UN.img = icon', },		where = { 'UN.iname = "'..iname..'"', 'UN.server = "gl"', 'UNL.lang = "english"', },	}[1]	return render_unit_icon{data=row, size='small'} end

function h.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

function h.queryBuff(iname) if not iname then return end return h.minQuery{tables='Buff',fields='*', where='iname="'..iname..'" AND server="gl"'} 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 = cargo.query{ tables = {'Skill', 'SkillLoc'}, join = 'Skill.iname = SkillLoc.iname', fields = {'Skill._pageName', 'name'}, where = { 'Skill.iname = "'..eff.card_skill..'"', 'Skill.server = "gl"', 'SkillLoc.lang = "english"', },			}[1]			local skillname = skill and skill.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') local buff_details = cargo.query{ tables = 'BuffDetail', fields = { 'idx', 'calc', 'tktag', 'type', 'vini', 'vmax', 'vone', },					where = { 'BuffDetail.server = "gl"', 'BuffDetail.buff_iname = "'..eff.add_card_skill_buff_awake..'"', },					orderBy = 'idx', }				if #buff_details == 0 then root:tag('div'):wikitext('Missing buff ' .. eff.add_card_skill_buff_awake .. ' 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

if eff.add_card_skill_buff_lvmax then root:tag('h4'):wikitext('Max Limit Break') local buffdetails = cargo.query{ tables = 'BuffDetail', fields = { 'idx', 'calc', 'tktag', 'type', 'vini', 'vmax', 'vone', },					where = { 'BuffDetail.server = "gl"', 'BuffDetail.buff_iname = "'..eff.add_card_skill_buff_lvmax..'"', },					orderBy = 'idx', }				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')

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

function h.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 = {} local ids = { eff.abil_iname, eff.abil_iname_lvmax, }

local t = {} local rows = cargo.query{ tables = { 'Ability = AB', 'AbilityLoc = ABL', 'Skill = SK', 'SkillLoc = SKL', },		join = { 'AB.iname = ABL.iname', 'AB.skl1 = SK.iname', 'SK.iname = SKL.iname', },		fields = { 'AB.iname = iname', 'AB.type_detail = type_detail', 'AB.slot = slot', 'AB.skl1 = skl1', 'ABL.name = ab_name',

'SKL.name = sk_name', 'SKL.expr = sk_expr', 'SK.atk_type = sk_atk_type', 'SK.atk_det = sk_atk_det', 'SK.elem = sk_elem', 'SK.eff_type = sk_eff_type', 'SK.eff_val_ini = sk_eff_val_ini', 'SK.eff_val_max = sk_eff_val_max', 'SK.count = sk_count', 'SK.cost = sk_cost', 'SK.eff_h = sk_eff_h', 'SK.sran = sk_sran', 'SK.rangemin = sk_rangemin', 'SK.rangemax = sk_rangemax', 'SK.ssco = sk_ssco', 'SK.scope = sk_scope', },		where = { 'AB.iname IN ('..util_table.concat(ids, ',', h.quote)..')', 'AB.server="gl"', 'ABL.lang = "english"', 'SK.server IS NULL OR SK.server = "gl" AND SKL.lang = "english"', },	}	for i, row in ipairs(rows) do		t[row.iname] = t[row.iname] or { name = row.ab_name, type_detail = row.type_detail, slot = tonumber(row.slot), }		if row.skl1 then t[row.iname].skl1 = t[row.iname].skl1 or { name = row.sk_name, expr = row.sk_expr, atk_type = tonumber(row.sk_atk_type), atk_det = tonumber(row.sk_atk_det), elem = tonumber(row.sk_elem), eff_type = tonumber(row.sk_eff_type), eff_val_ini = tonumber(row.sk_eff_val_ini), eff_val_max = tonumber(row.sk_eff_val_max), count = tonumber(row.sk_count), cost = tonumber(row.sk_cost), eff_h = tonumber(row.sk_eff_h), sran = tonumber(row.sk_sran), rangemin = tonumber(row.sk_rangemin), rangemax = tonumber(row.sk_rangemax), ssco = tonumber(row.sk_ssco), scope = tonumber(row.sk_scope), }		end end ability.base = t[eff.abil_iname] ability.max = t[eff.abil_iname_lvmax]

local baseName = ability.base.name local maxName = ability.max.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] if ab.type_detail then local abType = enums.EAbilityTypeDetail[ab.type_detail] if abType and abType ~= 'VisionAbility' then error('Expected ability type "VisionAbility", but was "'..tostring(abType)..'".') end end local ul = root:tag('ul') if ab.slot then local slot = enums.EAbilitySlot[ab.slot] ul:tag('li'):wikitext('Slot: '..slot) end local skill = ab.skl1 if skill then if (skill.atk_type or 0) > 0 then local atk_type = enums.AttackTypes[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: '..atk_type) end if (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: '..atk_det) end if (skill.elem or 0) > 0 then local elem = enums.elemFromKey(skill.elem) ul:tag('li'):wikitext('Element: '..elem) end

-- Explanation root:tag('p'):wikitext(skill.expr)

-- Skill effect if (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[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 (skill.count or 0) > 0 then ul:tag('li'):wikitext('Charges: ' .. skill.count) end if (skill.cost or 0) > 0 then ul:tag('li'):wikitext('Jewel Cost: ' .. skill.cost) end if (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[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

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 iname, flag in pairs(h.getTriggerGroups(args.cnds_iname) or {}) do		inames[iname] = inames[iname] or {} inames[iname].hasConditions = true end -- Buff groups for iname, flag in pairs(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={}}, }

local ids = util_table.concat(util_table.getKeys(inames), ',', h.quote) for i, row in ipairs(cargo.query{		tables = {			'Pages',			'Unit', 'UnitLoc',			'Job', 'JobLoc',		},		join = {			'Pages.iname = Unit.iname',			'Unit.iname = UnitLoc.iname',			'Pages.iname = Job.iname',			'Job.iname = JobLoc.iname',		},		fields = {			'Pages.iname = iname',			'Pages._pageName = _pageName',			'Pages.type = entityType',			'COALESCE(Job.ac2d, Job.mdl, Unit.img) = icon',			'COALESCE(JobLoc.name, UnitLoc.name) = name',		},		where = {			'Pages.iname IN ('..ids..')',			'Job.server IS NULL OR Job.server = "gl" AND JobLoc.lang = "english"',			'Unit.server IS NULL OR Unit.server = "gl" AND UnitLoc.lang = "english"',		},	}) do		local thing = inames[row.iname] 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 tp = row.entityType local render_icon = require('Module:Render/'..tp)._icon2 local t = icons[triggerbuff][tp] t[row.iname] = { iname = row.iname, name = row.name, icon = render_icon({data=row, 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 = {} for iname, obj in pairs(icons[triggerbuff][tp]) do					objs[#objs+1] = obj end if next(objs) 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 next(icons.Trigger.Unit) and next(icons.Trigger.Job) then local unit_inames = util_table.getKeys(icons.Trigger.Unit) local job_inames = util_table.getKeys(icons.Trigger.Job) local un_ids = util_table.concat(unit_inames, ',', h.quote) local jb_ids = util_table.concat(job_inames, ',', h.quote)

for _, row in ipairs(cargo.query{				tables = {					'Unit',					'Unit__jobsets',					'JobSet',				},				fields = {					'Unit.iname = un_iname',					'JobSet.job = jb_iname',				},				where = {					'Unit.server = "gl"',					'JobSet.server = "gl"',					'Unit.iname IN ('..un_ids..')',					'JobSet.job IN ('..jb_ids..')',				},				join = {					'Unit._ID = Unit__jobsets._rowID',					'Unit__jobsets._value = JobSet.iname',				},			}) do				objs[#objs+1] = { unit=icons.Trigger.Unit[row.un_iname], job=icons.Trigger.Job[row.jb_iname], }			end local heading = root:tag('h4') heading:wikitext('Unit and job restriction') table.sort(objs, function(a, b)			  if a.unit.name ~= b.unit.name then			   	return a.unit.name < b.unit.name			   end				return 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 next(icons.Trigger.Unit) then local heading = root:tag('h4') heading:wikitext('Unit restriction') for iname, obj in pairs(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 next(icons.Trigger.Job) then local heading = root:tag('h4') heading:wikitext('Job restriction') for iname, obj in pairs(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) -- Condition groups if not cnds_iname then return end

local rows = cargo.query{ tables = { 'ConceptCardConditions = CCC', 'ConceptCardConditions__birth_id = CCC__UN2', 'JobGroup = JG1', 'JobGroup__jobs = JG1__JB1', 'Job = JB1', 'UnitGroup = UG1', 'UnitGroup__units = UG1__UN1', 'Unit = UN1', 'Unit = UN2', 'Unit = UN3', },		join = { 'CCC.job_group = JG1.iname', 'JG1._ID = JG1__JB1._rowID', 'JG1__JB1._value = JB1.iname', 'CCC.un_group = UG1.iname', 'UG1._ID = UG1__UN1._rowID', 'UG1__UN1._value = UN1.iname', 'CCC._ID = CCC__UN2._rowID', 'CCC__UN2._value = UN2.birth_id', 'CCC.sex = UN3.sex', },		fields = { 'COALESCE(JB1.origin, JB1.iname) = jb_iname1', 'UN1.iname = un_iname1', 'UN2.iname = un_iname2', 'UN3.iname = un_iname3', },		where = { 'CCC.iname = "'..cnds_iname..'"', 'CCC.server = "gl"', 'JG1.server IS NULL OR JB1.server IS NULL OR JG1.server = "gl" AND JB1.server = "gl"', 'UG1.server IS NULL OR UN1.server IS NULL OR UG1.server = "gl" AND UN1.server = "gl" AND UN1.ai = "AI_PLAYER" AND (UN1.notsmn IS NULL OR UN1.hero = 1)', 'UN2.server IS NULL OR UN2.server = "gl" AND UN2.ai = "AI_PLAYER" AND (UN2.notsmn IS NULL OR UN2.hero = 1)', 'UN3.server IS NULL OR UN3.server = "gl" AND UN3.ai = "AI_PLAYER" AND (UN3.notsmn IS NULL OR UN3.hero = 1)', },	}	local t = {} for _, row in ipairs(rows) do		for k, v in pairs(row) do			t[v] = h.flags.TRIGGER end end return t end

function h.getBuffGroups(buff_iname) if not buff_iname then return end local rows = cargo.query{ tables = { 'Buff = BF', -- buff 'UnitGroup = UG0', -- buff.un_group 'UnitGroup__units = UG0__UN0', 'Unit = UN0', -- buff.un_group.units 'Buff__custom_targets = BF__CT', 'CustomTarget = CT', -- buff.custom_targets 'CustomTarget__unit_groups = CT__UG1', 'UnitGroup = UG1', -- buff.custom_targets.unit_groups 'UnitGroup__units = UG1__UN1', 'Unit = UN1', -- buff.custom_targets.unit_groups.units 'CustomTarget__units = CT__UN2', 'Unit = UN2', -- buff.custom_targets.units 'Unit = UN3', -- buff.custom_targets.birth_id 'Unit = UN4', -- buff.custom_targets.sex 'CustomTarget__job_groups = CT__JG1', 'JobGroup = JG1', -- buff.custom_targets.job_groups 'JobGroup__jobs = JG1__JB1', 'Job = JB1', -- buff.custom_targets.job_groups.jobs 'CustomTarget__jobs = CT__JB2', 'Job = JB2', -- buff.custom_targets.jobs },		join = { 'BF.un_group = UG0.iname', 'BF._ID = BF__CT._rowID', 'BF__CT._value = CT.iname', 'UG0._ID = UG0__UN0._rowID', 'UG0__UN0._value = UN0.iname', 'CT._ID = CT__UG1._rowID', 'CT__UG1._value = UG1.iname', 'UG1._ID = UG1__UN1._rowID', 'UG1__UN1._value = UN1.iname', 'CT._ID = CT__UN2._rowID', 'CT__UN2._value = UN2.iname', 'CT.birth_id = UN3.birth_id', 'CT.sex = UN4.sex', 'CT._ID = CT__JG1._rowID', 'CT__JG1._value = JG1.iname', 'JG1._ID = JG1__JB1._rowID', 'JG1__JB1._value = JB1.iname', 'CT._ID = CT__JB2._rowID', 'CT__JB2._value = JB2.iname', },		fields = { 'UN0.iname = un_iname0', 'UN1.iname = un_iname1', 'UN2.iname = un_iname2', 'UN3.iname = un_iname3', 'UN4.iname = un_iname4', 'COALESCE(JB1.origin, JB1.iname) = jb_iname1', 'COALESCE(JB2.origin, JB2.iname) = jb_iname2', },		where = { 'BF.iname = "'..buff_iname..'"', 'BF.server = "gl"', 'CT.server = "gl"', 'UG0.server IS NULL OR UN0.server IS NULL OR UG0.server = "gl" AND UN0.server = "gl" AND UN0.ai = "AI_PLAYER" AND (UN0.notsmn IS NULL OR UN0.hero = 1)', 'UG1.server IS NULL OR UN1.server IS NULL OR UG1.server = "gl" AND UN1.server = "gl" AND UN1.ai = "AI_PLAYER" AND (UN1.notsmn IS NULL OR UN1.hero = 1)', 'UN2.server IS NULL OR UN2.server = "gl" AND UN2.ai = "AI_PLAYER" AND (UN2.notsmn IS NULL OR UN2.hero = 1)', 'UN3.server IS NULL OR UN3.server = "gl" AND UN3.ai = "AI_PLAYER" AND (UN3.notsmn IS NULL OR UN3.hero = 1)', 'UN4.server IS NULL OR UN4.server = "gl" AND UN4.ai = "AI_PLAYER" AND (UN4.notsmn IS NULL OR UN4.hero = 1)', 'JG1.server IS NULL OR JB1.server IS NULL OR JG1.server = "gl" AND JB1.server = "gl"', 'JB2.server IS NULL OR JB2.server = "gl"', },	}	local t = {} for _, row in ipairs(rows) do		for k, v in pairs(row) do			t[v] = h.flags.BUFF end end return t end

function h.printStatsTable(root, skill_iname, maxLevel) if not skill_iname then return end local rows = cargo.query{ tables = { 'Skill = SK', 'BuffDetail = TBD', 'BuffDetail = SBD', },		join = { 'SK.t_buff = TBD.buff_iname', 'SK.s_buff = SBD.buff_iname', },		fields = { 'TBD.idx = t_idx', 'TBD.calc = t_calc', 'TBD.tktag = t_tktag', 'TBD.type = t_type', 'TBD.vini = t_vini', 'TBD.vmax = t_vmax', 'TBD.vone = t_vone', 'SBD.idx = s_idx', 'SBD.calc = s_calc', 'SBD.tktag = s_tktag', 'SBD.type = s_type', 'SBD.vini = s_vini', 'SBD.vmax = s_vmax', 'SBD.vone = s_vone', },		where = { 'SK.iname="'..skill_iname..'"', 'SK.server="gl"', 'COALESCE(TBD.server,"gl")="gl"', 'COALESCE(SBD.server,"gl")="gl"', },	}	if #rows == 0 then return end local t_buff_details = {} local s_buff_details = {} for i, row in ipairs(rows) do		local idx = tonumber(row.t_idx) if idx then t_buff_details[idx] = { idx = row.t_idx, calc = row.t_calc, tktag = row.t_tktag, type = row.t_type, vini = row.t_vini, vmax = row.t_vmax, vone = row.t_vone, }		end local idx = tonumber(row.s_idx) if idx then s_buff_details[idx] = { idx = row.s_idx, calc = row.s_calc, tktag = row.s_tktag, type = row.s_type, vini = row.s_vini, vmax = row.s_vmax, vone = row.s_vone, }		end end 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?  -- Long table here --]==]

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

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('') -- 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 = h.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'] = h.getVCR(data), ['Gives unit'] = h.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'] = '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('') :done :tag('div'):addClass('flavor') :css{ ['max-width'] = '1024px', ['padding'] = '8px', }			:wikitext(data.flavor) :done h.printLeaderSkill(root, data.leader_skill)

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

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

local function renderTransclude(iname, args) local type = mw.text.trim(args[1]) args[1] = iname if type == 'Icon' then args.data = cargo.query{ tables = {'ConceptCard=CC', 'ConceptCardLoc=CCLoc', 'Pages'}, join = { 'CC.iname = CCLoc.iname', 'CC.iname = Pages.iname', },			fields = { 'Pages._pageName = _pageName', 'CC.iname = iname', 'CC.server = server', 'CC.rare = rare', 'CC.icon = icon', 'CCLoc.name = name', },			where = { ('"%s" IN (CC.iname, Pages._pageName)'):format(iname), ('CC.server = "%s"'):format(server), ('CCLoc.lang = "%s"'):format(lang), 'Pages._pageName IS NULL OR Pages.type = "Memento"', },		}[1]		return render_memento_icon(args) end return (cargo.query{			tables = 'ConceptCardLoc',			fields = 'name',			where = 'iname = "'..iname..'" AND lang = "english"',		}[1] or {}).name end

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

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

function h.printLeaderSkill(root, sk_iname) if sk_iname == nil then return end local skill local rows = cargo.query{ tables = { 'Skill = SK', 'SkillLoc = SK_LOC', 'Buff = BF', 'BuffDetail = BD', 'Buff__custom_targets = BF_CT', 'CustomTarget = CT', 'CustomTarget__concept_card_groups = CT_CCG', 'ConceptCardGroup = CCG', 'ConceptCardGroup__cards = CCG_CC', 'ConceptCard = CC', 'Pages = CC_PAGES', },		join = { 'SK.iname = SK_LOC.iname', 'SK.t_buff = BF.iname', 'BF.iname = BD.buff_iname', 'BF._ID = BF_CT._rowID, BF_CT._value = CT.iname', 'CT._ID = CT_CCG._rowID, CT_CCG._value = CCG.iname', 'CCG._ID = CCG_CC._rowID, CCG_CC._value = CC.iname', 'CC.iname = CC_PAGES.iname', },		fields = { 'SK.iname = iname', 'SK.type = type', 'SK.eff_type = eff_type', 'SK.target = target', 'SK.cond = cond', 'SK.timing = timing', 'SK_LOC.name = name', 'SK_LOC.expr = expr',

'BF.iname = t_buff_iname',

'BD.idx = BD_idx', 'BD.calc = calc', 'BD.tktag = tktag', 'BD.type = type', 'BD.vini = vini', 'BD.vmax = vmax', 'BD.vone = vone',

'BF_CT._position = BF_CT_idx', 'CT.birth_id = birth_id', -- Integer 'CT.sex = sex', -- Integer 'CT.dark = dark', -- Integer 'CT.fire = fire', -- Integer 'CT.shine = shine', -- Integer 'CT.thunder = thunder', -- Integer 'CT.water = water', -- Integer 'CT.wind = wind', -- Integer -- 'CT.concept_card_groups = concept_card_groups', -- List of String 'CT.job_groups = job_groups', -- List of String 'CT.unit_groups = unit_groups', -- List of String 'CT.units = units', -- List of String

'CT_CCG._position = CT_CCG_idx', 'CCG.iname = CCG_iname', 'CCG_CC._position = CCG_CC_idx', 'CC.iname = CC_iname', 'CC.icon = CC_icon', 'CC.type = CC_type', 'CC.rare = CC_rare', 'CC_PAGES._pageName = CC_PAGENAME', },		where = { 'SK.iname = "'..sk_iname..'"', 'SK.server = "gl"', 'SK_LOC.lang = "english"', 'COALESCE(SK.t_buff, "") = "" OR BF.server = SK.server AND BD.server = SK.server', 'COALESCE(BF.custom_targets__full, "") = "" OR CT.server = SK.server',

'CCG.server = "gl"', 'CC.server = "gl"', },		orderBy = 'idx', }

if #rows == 0 then return end for _, row in ipairs(rows) do		skill = skill or { iname = row.iname, name = row.name, expr = row.expr, type = enums.ESkillType[tonumber(row.type)], eff_type = enums.SkillEffectTypes[tonumber(row.eff_type)], target = enums.ESkillTarget[tonumber(row.target)], cond = enums.ESkillCondition[tonumber(row.cond)], timing = enums.ESkillTiming[tonumber(row.timing)], t_buff = { iname = row.t_buff_iname, custom_targets = {}, details = {}, },		}		setdefault(skill.t_buff.details, tonumber(row.BD_idx), {			calc = row.calc,			tktag = row.tktag,			type = tonumber(row.type),			vini = tonumber(row.vini),			vmax = tonumber(row.vmax),			vone = tonumber(row.vone),		}) local custom_target = setdefault(skill.t_buff.custom_targets, tonumber(row.BF_CT_idx), {			birth_id = tonumber(row.birth_id),			sex = tonumber(row.sex),			dark = tonumber(row.dark),			fire = tonumber(row.fire),			shine = tonumber(row.shine),			thunder = tonumber(row.thunder),			water = tonumber(row.water),			wind = tonumber(row.wind),			concept_card_groups = {},			job_groups = row.job_groups,			unit_groups = row.unit_groups,			units = row.units,		}) local concept_card_group = setdefault(custom_target.concept_card_groups, tonumber(row.CT_CCG_idx), {			iname = row.CCG_iname,			concept_cards = {},		}) local concept_card = setdefault(concept_card_group.concept_cards, tonumber(row.CCG_CC_idx), {			iname = row.CC_iname,			icon = row.CC_icon,			type = row.CC_type,			rare = row.CC_rare,			PAGENAME = row.CC_PAGENAME,		}) end root:newline:tag('h2'):wikitext("Leader Skill") root:newline:tag('h3'):wikitext(skill.name) root:newline:tag('p'):wikitext(skill.expr)

if #skill.t_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(skill.t_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 for _, custom_target in ipairs(skill.t_buff.custom_targets or {}) do		for _, cc_group in ipairs(custom_target.concept_card_groups or {}) do			local cards = cc_group.concept_cards root:newline:tag('h4'):wikitext('Memento Group') local ul = root:newline:tag('ul') for _, card in ipairs(cards) do				-- local icon = '' if card.PAGENAME then local icon = string.format(						' %s',						card.icon, card.PAGENAME, card.PAGENAME) ul:newline:tag('li'):wikitext(icon) end end end end end

local DebugHelper = {} function DebugHelper.deepCompare(t1, t2, ignoreMetatable) local type1 = type(t1) local type2 = type(t2)

if type1 ~= type2 then return false end if type1 ~= 'table' then return t1 == t2   end

local metatable = getmetatable(t1) if not ignoreMetatable and metatable and metatable.__eq then return t1 == t2   end

for k1, v1 in pairs(t1) do       local v2 = t2[k1] if v2 == nil or not DebugHelper.deepCompare(v1, v2) then return false end end for k2, v2 in pairs(t2) do       if t1[k2] == nil then return false end end

return true end

function p.testPrintAbilities util_vars.setVar('cargo query count', 0) local iname = 'TS_BF_02'

local that = require('Module:Page/Memento') local this = p

local effects = cargo.query{ tables = 'ConceptCardEffect', fields = { 'abil_iname', 'abil_iname_lvmax', 'add_card_skill_buff_awake', 'add_card_skill_buff_lvmax', 'card_skill', 'cnds_iname', 'is_decrease_eff', 'skin', 'statusup_skill', },		where = 'cc_iname="'..iname..'" and server="gl" AND abil_iname IS NOT NULL', }

for i, eff in ipairs(effects) do		local root = mw.html.create that.h.printEffect(root, eff) local a = tostring(root)

local root = mw.html.create this.h.printEffect(root, eff) local b = tostring(root)

if a == b then mw.log(i, a == b)			mw.log(a) else mw.log(i, 'that = ', a)			mw.log(i, 'this = ', b)		end end end

function p.test(frame) util_vars.setVar('cargo query count', 0) p.testPrintAbilities

local iname = 'TS_BF_02'

local that = require('Module:Page/Memento') local this = p

local expected_func = that.renderPage local actual_func = this.renderPage

local inames = { iname, }	for i, iname in ipairs(inames) do		local args = iname

local start = util_vars.getVar('cargo query count', 0) local expected = expected_func(args) local expected_count = util_vars.getVar('cargo query count', 0) - start

local start = util_vars.setVar('cargo query count', 0) local actual = actual_func(args) local actual_count = util_vars.getVar('cargo query count') - start

local eq = DebugHelper.deepCompare(expected, actual) if not eq then mw.logObject(expected, 'expected') mw.logObject(actual, 'actual') end mw.log('expected == actual?', eq) mw.log('cargo.query counts (lower is better):\n'			.. 'Expect ' .. expected_count .. ', '			.. 'Actual ' .. actual_count) end end

p.h = h -- for testing purposes >_<

return p --