Module:Battles

From Granblue Fantasy Wiki
Jump to navigation Jump to search

This module implements {{BattlePhase}} and {{BattleEnemy}}.


local p = {}
local getArgs = require('Module:Arguments').getArgs

-- Renders
function p.renderTemplate(frame)
	local result = {}
	-- prefix
	local prefix = 'phase'
	if frame.args.prefix ~= nil then
		prefix = frame.args.prefix
	end
	local mainTemplate = frame.args.mainTemplate
	local phaseTemplate = frame.args.phaseTemplate
	local edit_title = frame.args.edit_title
	local edit_link = frame.args.edit_link
	
	local frameTemplate = frame
	-- if we have a parent assume we're included from a template
	if frame:getParent() ~= nil then
		frameTemplate = frame:getParent()
	end
	--local edit_title = ''
	--local edit_title_debug = ''
	--local titleFrame = frameTemplate
	--while (titleFrame ~= nil) do
	--	edit_title = edit_title .. '--' .. titleFrame:getTitle()
	--	titleFrame = titleFrame:getParent()
	--end
	--while (titleFrame ~= nil) and ((string.find(titleFrame:getTitle(), 'Template:') ~= nil) or (string.find(titleFrame:getTitle(), 'Module:') ~= nil)) do
	--	edit_title_debug = edit_title_debug .. '--' .. titleFrame:getTitle()
	--	titleFrame = titleFrame:getParent()
	--end
	--if titleFrame ~= nil then
	--	edit_title = titleFrame:getTitle()
	--end
	
	local hasDescNotes = false
	local phases = {}
	for i=1,9 do
		--local phaseKey = '%%%%'..prefix..i..'%%%%'
		local phaseText = ''
		local phase = frameTemplate.args[i]
		if (phase ~= nil) and (string.len(phase) > 0) then
			local temp = ''
			if (frame.args.dumpPhases ~= nil) and (frame.args.dumpPhases == 'yes') then
				table.insert(result, phase)
			end
			if pcall(function() temp = mw.text.jsonDecode(phase) end) then
				phase = {}
				phase.phase = '??'
				if temp.phase ~= nil then
					phase.phase = temp.phase
				end
				for j=1,9 do
					for k=1,9 do
						if (temp.enemies[j] ~= nil) and (temp.enemies[j][k] ~= nil) then
							for ix,key in pairs({'name','row','lvl','element','hp','ct','od','ca_desc','icon_s','notes'}) do
								local enemy_count = 0
								if temp.enemies[j][k][key] ~= nil then
									local value = temp.enemies[j][k][key]
									enemy_count = enemy_count + 1
									if k == 1 then 
										phase['enemy'..j..'_'..key] = value
									end
									phase['enemy'..j..k..'_'..key] = value
									if ((key == 'ca_desc') or (key == 'notes')) and (value ~= nil) and (string.len(value) > 0) then
										hasDescNotes = true
									end
								end
								phase['enemy'..j..'_count'] = enemy_count
							end
						end
					end
				end
				
				phaseText = p.renderPhase(frame, phaseTemplate, phase, temp.enemies, frameTemplate.args)
			else
				phaseText = 'Failed to decode: ' .. phase
			end
		end
		phases[i] = phaseText
	end
	
	local args = {}
	--mw.log(frameTemplate.args[1])
	for key,value in pairs(frameTemplate.args) do
		args[key] = value
	end
	args['has_descnotes'] = tostring(hasDescNotes)
	args['edit_title'] = edit_title
	args['edit_link'] = edit_link
	--args['edit_title_debug'] = edit_title_debug
	local text = frame:expandTemplate{ title = mainTemplate, args = args }

	-- find parts we need to replace with phases
	local parts = {}
	for i=1,9 do
		local phaseKey = '%%%%'..prefix..i..'%%%%'
		local pos = string.find(text, phaseKey)
		if pos ~= nil then
			table.insert(parts, { phase = i, start = pos, len = string.len(phaseKey)-4 })
		end
	end

	-- split up the text into an array and insert phases
	local start_offset = 1
	for i=1,9 do
		local part_key = nil
		local offset = string.len(text)
		for key,part in pairs(parts) do
			if (part.start >= start_offset) and (part.start < offset) then
				part_key = key
				offset = part.start
			end
		end

		-- if we found a part replace it
		if part_key ~= nil then
			local part = parts[part_key]
			--mw.log('part ' .. part.phase .. ': ' .. part.start .. ',' .. part.len)
		
			-- add text between current position and start of part
			--mw.log('start_offset: '..start_offset..' to offset:'..offset)
			if offset > start_offset then
				local sub = string.sub(text, start_offset, offset-1)
				--mw.log('inserting: '..mw.text.jsonEncode(sub))
				table.insert(result, sub)
			end
			start_offset = offset + part.len
			-- add phase
			if phases[part.phase] ~= nil then
				local sub = phases[part.phase]
				--mw.log('inserting: '..mw.text.jsonEncode(sub))
				table.insert(result, phases[part.phase])
			end
		end
	end
	if start_offset < string.len(text) then
		local sub = string.sub(text, start_offset)
		--mw.log('appending: '..mw.text.jsonEncode(sub))
		table.insert(result, sub)
	end

	return table.concat(result,'')
end

function p.renderPhase(frame, template, phase, enemies, context)
	return frame:expandTemplate{ title = template, args = phase }
end

-- Enemy details as a JSON string for passing data between templates
function p.jsonEnemy(frame)
	if frame:getParent() ~= nil then
		frame = frame:getParent()
	end
	local result = {}
	local params = {'name','row','lvl','element','hp','ct','od','ca_desc','notes'}
	for key,value in pairs(params) do
		if frame.args[value] ~= nil then
			result[value] = frame.args[value]
		end
	end
	return mw.text.jsonEncode(result)
end

function p.jsonEnemy2(frame)
	local args = getArgs(frame, {
		trim = true,
		removeBlanks = true,
	})
	return p._jsonEnemy2(args)
end

function p.format_int(number)

  local i, j, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')

  -- reverse the int-string and append a comma to all blocks of 3 digits
  int = int:reverse():gsub("(%d%d%d)", "%1,")

  -- reverse the int-string back remove an optional comma and put the 
  -- optional minus and fractional part back
  return minus .. int:reverse():gsub("^,", "") .. fraction
end

function p._jsonEnemy2(args)
	local result = {}

	if args['id'] ~= nil then
		local qf = 'id,name,lvl,element,hp,ct,od,rare,icon_s'
		local qa = { limit = 1, where = 'id='..args['id'] }
		for _, row in ipairs(mw.ext.cargo.query('enemies', qf, qa)) do
			for key, value in pairs(row) do
				result[key] = value
			end
			result['hp'] = p.format_int(result['hp'])
			result['od'] = (result['od'] == '1') and 'yes' or 'no'
			result['rare'] = (result['rare'] == '1') and 'yes' or 'no'
		end
	end

	-- allow overriding values
	local params = {'name','row','lvl','element','hp','ct','od','ca_desc','notes'}
	for key,value in pairs(params) do
		if args[value] ~= nil then
			result[value] = args[value]
		end
	end

	return mw.text.jsonEncode(result)
end

-- Phase details as a JSON string for passing data between templates
function p.jsonPhase(frame)
	if frame:getParent() ~= nil then
		frame = frame:getParent()
	end
	return mw.text.jsonEncode(p.parsePhase(frame))
end

function p.parsePhase(frame)
	local result = {}
	
	local phase = '???'
	if frame.args.name ~= nil then
		phase = frame.args.name
	elseif frame.args.phase ~= nil then
		phase = frame.args.phase
	end
	
	for ix,name in pairs {'label','label1','label2','label3'} do
		if frame.args[name] ~= nil then
			result[name] = frame.args[name]
		end
	end
	if (result.label1 ~= nil) and (result.label == nil) then
		result.label = result.label1
	elseif (result.label ~= nil) and (result.label1 == nil) then
		result.label1 = result.label
	end

	-- Find unindexed enemies
	for i=1,9 do
		if frame.args[i] ~= nil then
			local enemy = mw.text.jsonDecode(frame.args[i])
			for key,value in pairs(enemy) do
				frame.args['enemy'..i..'_'..key] = value
			end
		end
	end
	
	-- Parse out all enemies and assign them arow
	local rows = {}
	local current_row = 0
	for i=1,9 do
		local key_name    = 'enemy' .. i .. '_name'
		local key_icon    = 'enemy' .. i .. '_icon'
		local key_icon_s  = 'enemy' .. i .. '_icon_s'
		local key_row     = 'enemy' .. i .. '_row'
		local key_lvl     = 'enemy' .. i .. '_lvl'
		local key_element = 'enemy' .. i .. '_element'
		local key_hp      = 'enemy' .. i .. '_hp'
		local key_ct      = 'enemy' .. i .. '_ct'
		local key_od      = 'enemy' .. i .. '_od'
		local key_ca_desc = 'enemy' .. i .. '_ca_desc'
		local key_notes   = 'enemy' .. i .. '_notes'
		
		if frame.args[key_name] ~= nil then
			local name = frame.args[key_name]
			
			local row = 0
			if frame.args[key_row] ~= nil then
				row = tonumber(frame.args[key_row])
			else
				current_row = current_row + 1
				row = current_row
			end

			local icon = ''
			if frame.args[key_icon] ~= nil then
				icon = frame.args[key_icon]
			end

			local icon_s = ''
			if frame.args[key_icon_s] ~= nil then
				icon_s = frame.args[key_icon_s]
			end
			
			local lvl = '??'
			if frame.args[key_lvl] ~= nil then
				lvl = frame.args[key_lvl]
			end
			
			local element = 'Unknown'
			if frame.args[key_element] ~= nil then
				element = string.lower(frame.args[key_element])
			end
			
			local hp = '???'
			if frame.args[key_hp] ~= nil then
				hp = frame.args[key_hp]
			end
			
			local ct = -1
			if frame.args[key_ct] ~= nil then
				ct = tonumber(frame.args[key_ct])
			end
			
			local od = false
			if frame.args[key_od] ~= nil then
				od = string.lower(frame.args[key_od])
				od = ((od == 'yes') or (od == 'true') or (od == 'y'))
			end
			
			local ca_desc = ''
			if frame.args[key_ca_desc] ~= nil then
				ca_desc = frame.args[key_ca_desc]
			end
			
			local notes = ''
			if frame.args[key_notes] ~= nil then
				notes = frame.args[key_notes]
			end

			table.insert(rows, { row=row, name=name, icon=icon, icon_s=icon_s, lvl=lvl, element=element, hp=hp, ct=ct, od=od, ca_desc=ca_desc, notes=notes })
		end
	end

	local enemies = {}
	for ix,value in pairs(rows) do
		if enemies[value.row] == nil then
			enemies[value.row] = {}
		end
		table.insert(enemies[value.row], value)
	end
	
	result.phase = phase
	result.enemies = enemies
	
	return result
end

function p.testParsePhase()
	frame = mw.getCurrentFrame()
	frame.args.phase = 'Wave 1'
	frame.args.enemy1_name = 'mob1'
	frame.args.enemy1_lvl = '123'
	frame.args.enemy1_element = 'DaRk'
	frame.args.enemy1_hp = '123,456,789'
	frame.args.enemy1_ct = '3'
	frame.args.enemy1_od = 'yes'
	frame.args.enemy1_icon_s = 'test.png'
	frame.args.enemy1_ca_desc = [[* Bonggg...]]
	frame.args.enemy1_notes = [[Does not attack in bell form except for charge attacks.]]
	frame.args.enemy2_name = 'mob2'
	frame.args[3] = mw.text.jsonEncode({name='mob3',lvl=222,element='wind'})
	
	return p.jsonPhase(frame)
end

function p.testRender()
	frame = mw.getCurrentFrame()
	
	frame.args.mainTemplate = 'BattleEventQuest/Table'
	frame.args.phaseTemplate = 'BattlePhase/Raid'
	frame.args['header'] = 'Nightmare'
	frame.args.name = 'Level 70 Maddie-Blast Form'
	frame.args.image = 'EventQuest Platinum Sky (Nightmare).jpg'
	frame.args.cost = '0 AP'
	frame.args.unlock = 'Randomly appears after hosting Very Hard or Extreme.'
	frame.args.loot_clear = '{{Itm|Blue Sky Crystal,1}}'
	frame.args.loot_honor = '25000'
	frame.args.loot_token = '24'
	frame.args.loot_loyalty = '100'
	frame.args.loot_wood = '{{ItmLst|Gold Badge$nolink}}'
	frame.args.loot_silver = '{{ItmLst|angel|Mechanic\'s Spanner$nolink}}'
	frame.args.loot_gold = ''
	frame.args[1] = '{"enemies":[[{"od":true,"row":1,"ct":2,"ca_desc":"* Bonggg...\n: Plain damage to all allies and inflict Status AttackDown.pngAttack DOWN for 2 turns. (1st form only?)\n* Decemvir N\n: Damage to one ally and inflict Status Death.pngCounting Down for 5 turns.\n* Let Go OD\n: Damage to all allies and inflict Status Confused.pngIntoxicated for 1 turn.\n* Purify Mind TR\n: 108-hit damage to random allies. (2nd form only)","lvl":"30","hp":"???","element":"earth","name":"Joya","notes":"* Does not attack in bell form except for charge attacks.\n* If ally has Status Sleep.pngSleep\n: Casts Purify Mind.\n* 75% HP Trigger\n: Transforms\n* 25% HP Trigger\n: True Power\n: Base diamonds reduced to ◇.\n* Status Confused.pngIntoxicated\n: Ally randomly attacks an ally or enemy with plain damage. If Intoxicated is not removed after 1 turn, ally gains Status Sleep.pngSleep for 2 to 4 turns. If an Intoxicated ally is hit, debuff is removed.\n* Status Death.pngCounting Down\n: Take 100% of max HP in plain damage when count reaches 0."}]],"phase":"Boss"}'

	return p.renderTemplate(frame)
end

function p.testRender2()
	frame = mw.getCurrentFrame()
	
	local testArgs = {}
	frame.args.mainTemplate = 'BattleFreeQuest/Row'
	frame.args.phaseTemplate = 'BattlePhase/Quest'
	frame.args.stages = 3
	frame.args.name = 'I Challenge You!'
	frame.args.jpname = '若き騎士達からの挑戦'
	frame.args.cost = '25 AP'
	frame.args.element = 'dark'
	frame.args.notes = '{{SR}} only'
	frame.args.loot_clear = '{{Itm|Crystal,50}}'
	frame.args.loot_side = '{{ItmLst|Spring Water Jug,2-2}}'
	frame.args.loot_wood = '{{ItmLst|Normal Weapon,0-1|Falcon Feather,2-3}}'
	frame.args.loot_silver = '{{ItmLst|Spring Water Jug,3-8|Vermilion Stone,2-3}}'
	frame.args.loot_gold = ''
	frame.args[1] = '{"enemies":[[{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Light Warrior","notes":""},{"od":false,"row":1,"ct":1,"ca_desc":"","lvl":"32","hp":"75,000","element":"light","name":"Prism Fly","notes":""},{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"80,000","element":"light","name":"Sabrewolf","notes":""}],[{"od":false,"row":2,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Goblin Knight","notes":""},{"od":false,"row":2,"ct":3,"ca_desc":"","lvl":"32","hp":"100,000","element":"light","name":"Mid Golem","notes":""}]],"label1":"1 of","phase":"Wave 1A","label":"1 of","label2":"1 of"}'
	frame.args[2] = '{"enemies":[[{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"80,000","element":"light","name":"Sabrewolf","notes":""},{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Goblin Knight","notes":""}],[{"od":false,"row":2,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Light Warrior","notes":""}]],"label1":"1 of","phase":"Wave 2A","label":"1 of","label2":"1 of"}'
	frame.args[3] = '{"enemies":[[{"od":true,"row":1,"ct":3,"ca_desc":"","lvl":"32","hp":"400,000","element":"light","name":"Tooty","notes":""}]],"phase":"Boss A"}'
	frame.args[4] = ''
	frame.args[5] = '{"enemies":[[{"od":false,"row":1,"ct":1,"ca_desc":"","lvl":"32","hp":"6,000","element":"light","name":"Wandering Goldslime","notes":""}]],"phase":"Wave 2B"}'
	frame.args[6] = '{"enemies":[[{"od":false,"row":1,"ct":3,"ca_desc":"","lvl":"32","hp":"380,000","element":"light","name":"Mantis Overlord","notes":""}]],"phase":"Boss B"}'
	testArgs.mainTemplate = 'BattleFreeQuest/Row'
	testArgs.phaseTemplate = 'BattlePhase/Quest'
	testArgs.stages = 3
	testArgs.name = 'I Challenge You!'
	testArgs.jpname = '若き騎士達からの挑戦'
	testArgs.cost = '25 AP'
	testArgs.element = 'dark'
	testArgs.notes = '{{SR}} only'
	testArgs.loot_clear = '{{Itm|Crystal,50}}'
	testArgs.loot_side = '{{ItmLst|Spring Water Jug,2-2}}'
	testArgs.loot_wood = '{{ItmLst|Normal Weapon,0-1|Falcon Feather,2-3}}'
	testArgs.loot_silver = '{{ItmLst|Spring Water Jug,3-8|Vermilion Stone,2-3}}'
	testArgs.loot_gold = ''
	testArgs[1] = '{"enemies":[[{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Light Warrior","notes":""},{"od":false,"row":1,"ct":1,"ca_desc":"","lvl":"32","hp":"75,000","element":"light","name":"Prism Fly","notes":""},{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"80,000","element":"light","name":"Sabrewolf","notes":""}],[{"od":false,"row":2,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Goblin Knight","notes":""},{"od":false,"row":2,"ct":3,"ca_desc":"","lvl":"32","hp":"100,000","element":"light","name":"Mid Golem","notes":""}]],"label1":"1 of","phase":"Wave 1A","label":"1 of","label2":"1 of"}'
	testArgs[2] = '{"enemies":[[{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"80,000","element":"light","name":"Sabrewolf","notes":""},{"od":false,"row":1,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Goblin Knight","notes":""}],[{"od":false,"row":2,"ct":2,"ca_desc":"","lvl":"32","hp":"120,000","element":"light","name":"Light Warrior","notes":""}]],"label1":"1 of","phase":"Wave 2A","label":"1 of","label2":"1 of"}'
	testArgs[3] = '{"enemies":[[{"od":true,"row":1,"ct":3,"ca_desc":"","lvl":"32","hp":"400,000","element":"light","name":"Tooty","notes":""}]],"phase":"Boss A"}'
	testArgs[4] = ''
	testArgs[5] = '{"enemies":[[{"od":false,"row":1,"ct":1,"ca_desc":"","lvl":"32","hp":"6,000","element":"light","name":"Wandering Goldslime","notes":""}]],"phase":"Wave 2B"}'
	testArgs[6] = '{"enemies":[[{"od":false,"row":1,"ct":3,"ca_desc":"","lvl":"32","hp":"380,000","element":"light","name":"Mantis Overlord","notes":""}]],"phase":"Boss B"}'
	
	return p.renderTemplate(frame, testArgs)
end

function p.testEnemy()
	return p._jsonEnemy2({id = '1009020'})
end

return p