Module:Page/Memento

From The Alchemist Code Wiki
Jump to: navigation, search

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>