Module:MapLayout

local cargo = mw.ext.cargo local Util = require 'Module:Util' local List = require 'Module:ListUtil' local Hash = require 'Module:HashUtil' local Tab = require 'Module:Tab' local escq = require 'Module:EscQ'.main1 local mf = require 'Module:MF'.main1 local toboolean = require 'Module:Bool'.toboolean

local makeStartingSpotIcon = function (side, num, size) return (''):format(side, num == '?' and 'Unknown' or tostring(num), size) end

local makeGenericIcon = function (side, page, mov, wep) local iconDiv = mw.html.create('div'):css('position', 'relative'):css('display', 'inline-block') iconDiv:tag('div'):css('position', 'relative'):css('top', '0px'):css('left', '0px'):css('z-index', '1') :wikitext((''):format(side)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '50%'):css('left', '50%'):css('z-index', '3') :css('transform', side == 'Ally' and 'translate(-50%, -50%)' or 'translate(-50%, -50%) scaleX(-1)') :wikitext((''):format(page)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '0px'):css('left', '0px'):css('z-index', '3') :wikitext((''):format(side)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '2px'):css('left', '41px'):css('z-index', '4') :wikitext((''):format(wep)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '38px'):css('left', '4px'):css('z-index', '4') :wikitext((''):format(mov)) return iconDiv end

local makeHeroIcon = function (side, page, mov, wep) local iconDiv = mw.html.create('div'):css('position', 'relative'):css('display', 'inline-block') iconDiv:tag('div'):css('position', 'relative'):css('top', '0px'):css('left', '0px'):css('z-index', '1') :wikitext((''):format(side)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '4px'):css('left', '4px'):css('z-index', '3')--:css('transform', 'scaleX(1)') :wikitext((''):format(mf(page))) iconDiv:tag('div'):css('position', 'absolute'):css('top', '0px'):css('left', '0px'):css('z-index', '3') :wikitext((''):format(side)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '2px'):css('left', '41px'):css('z-index', '4') :wikitext((''):format(wep)) iconDiv:tag('div'):css('position', 'absolute'):css('top', '38px'):css('left', '4px'):css('z-index', '4') :wikitext((''):format(mov)) return iconDiv end

-- also for Module:UnitData local makeUnitIcon_noCargo = function (query, side) return (query.isGeneric and makeGenericIcon or makeHeroIcon)(side, query.page, query.MoveType, query.WeaponType) end

local makeIcon = function (args, side) if args[1] and (tonumber(args[1]) or args[1] == '?') then return makeStartingSpotIcon(side, args[1], args.size or '60x60px') else local unit = args.generic or args.hero or args.unit or args[1] local query = cargo.query('Units', "MoveType,WeaponType,IF(Properties__full LIKE '%generic%','1','')=isGeneric", {			where = ("'%s' IN (_pageName,IFNULL(CONCAT(Name,': ',Title),Name))"):format(escq(unit)),			groupBy = '_pageName',		})[1] if query then query.page = unit query.isGeneric = query.isGeneric ~= '' return makeUnitIcon_noCargo(query, side) end end end

local ally = function (args) return makeIcon(args, 'Ally') end

local enemy = function (args) return makeIcon(args, 'Enemy') end

local getBaseMapInfo = function (filename, typ) --	return mw.title.makeTitle('File', filename).file -- expensive parser function if typ == 'HO' then return {exists = true, width = 540, height = 684} elseif typ == 'RD' then return {exists = true, width = 540, height = 675} elseif typ == 'MS2' then return {exists = true, width = 540, height = 765} else return {exists = true, width = 540, height = 720} end end

local makeBackdropDiv = function (filename, typ, width, height) return mw.html.create('div'):css('position', 'absolute'):css('top', '0px'):css('left', '0px'):css('z-index', '1') :wikitext(():format(typ == 'RD' and 'Rival Domains ' or , filename, width, height)) end

local makeBackgroundDiv = function (mapid, typ, width, height) local filename = ('Map %s.png'):format(mapid) local info = getBaseMapInfo(filename, typ) if info.exists then local div2 = mw.html.create('div'):css('position', 'absolute'):css('z-index', '2') local aspect = info.width / info.height if aspect > width / height + 1e-5 then local margin = (info.width / info.height * height - width) / 2 div2:css('clip', ('rect(0px,%.5fpx,%dpx,%.5fpx)'):format(width + margin, height, margin)):css('top', '0px'):css('left', ('%.5fpx'):format(-margin)) div2:wikitext((''):format(filename, height)) elseif aspect < width / height - 1e-5 then local margin = (info.height / info.width * width - height) / 2 div2:css('clip', ('rect(%.5fpx,%dpx,%.5fpx,0px)'):format(margin, width, height + margin)):css('top', ('%.5fpx'):format(-margin)):css('left', '0px') div2:wikitext((''):format(filename, width)) else div2:css('top', '0px'):css('left', '0px') div2:wikitext((''):format(filename, width, height)) end return div2 end end

local makeTDScrollDiv = function return mw.html.create('div'):css('position', 'absolute'):css('top', '360px'):css('left', '0px'):css('z-index', '3') :wikitext('') end

local makePos = function (x, y) return string.char(96 + x) .. tostring(y) end

local parsePos = function (pos) local x, y = mw.ustring.match(pos, '^([a-z])(%d+)$') if y then return mw.ustring.codepoint(x) - 96, tonumber(y) end end

local makeMapObjectDiv = function (pos, obj) if obj then local x, y = parsePos(pos) if x and y then return mw.html.create('div'):css('position', 'absolute'):css('z-index', '3') :css('bottom', ((y - 1) * 60) .. 'px'):css('left', ((x - 1) * 60) .. 'px') :wikitext(obj) end end end

local makeMap = function (args) local MAP_HEIGHT = (args.type == 'RD' or args.type == 'MS2') and 10 or 8 local MAP_WIDTH = (args.type == 'RD' or args.type == 'MS2') and 8 or 6 local HEIGHT = MAP_HEIGHT * 60 local WIDTH = MAP_WIDTH * 60

local mapDiv = mw.html.create('div'):css('position', 'relative'):css('display', 'inline-block') :css('height', HEIGHT .. 'px'):css('width', WIDTH .. 'px')

-- Backdrop (eg. Water, Lava) if args.backdrop then mapDiv:node(makeBackdropDiv(args.backdrop, args.type, WIDTH, HEIGHT)) end

-- Base Map if args.baseMap then mapDiv:node(makeBackgroundDiv(args.baseMap, args.type, WIDTH, HEIGHT)) end

-- Tactics Drills scroll if args.type == 'TD' then mapDiv:node(makeTDScrollDiv) end

-- Starting positions if args.allyPos then local i = 1 for pos in mw.text.gsplit(args.allyPos, '%s*,%s*') do			mapDiv:node(makeMapObjectDiv(pos, makeStartingSpotIcon('Ally', i, '60x60px'))) i = i + 1 end end if args.enemyPos then local i = 1 for pos in mw.text.gsplit(args.enemyPos, '%s*,%s*') do			mapDiv:node(makeMapObjectDiv(pos, makeStartingSpotIcon('Enemy', i, '60x60px'))) i = i + 1 end end

-- Map Objects for y = 1, MAP_HEIGHT do		for x = 1, MAP_WIDTH do			local pos = makePos(x, y)			mapDiv:node(makeMapObjectDiv(pos, args[pos])) end end

-- Initial units if args.init and args.initTab then local units = cargo.query(			'MapUnits,Units',			Pos,MoveType,WeaponType,Units._pageName=page,			 IF(MapUnits.Properties__full LIKE '%is_ally%','Ally','Enemy')=Side,IF(Units.Properties__full LIKE '%generic%','1','')=isGeneric, {			join = 'MapUnits.Unit=Units.WikiName',			where = ("MapUnits._pageName='%s' AND MapUnits.TabName='%s' AND Pos IS NOT NULL AND Spawn IS NULL"):format(escq(args.init), escq(args.initTab)),			groupBy = 'Pos',			limit = 100,		}) for _, v in ipairs(units) do			v.isGeneric = v.isGeneric ~= '' mapDiv:node(makeMapObjectDiv(v.Pos, tostring(makeUnitIcon_noCargo(v, v.Side)))) end end

return tostring(mapDiv) end

local makeRDmap = function (args) args.type = args.type or 'RD' return makeMap(args) end

local initTabber = function (args) local MAP_HEIGHT = (args.type == 'RD' or args.type == 'MS2') and 10 or 8 local MAP_WIDTH = (args.type == 'RD' or args.type == 'MS2') and 8 or 6 local HEIGHT = MAP_HEIGHT * 60 local WIDTH = MAP_WIDTH * 60

local mapDiv = mw.html.create('div'):css('position', 'relative'):css('display', 'inline-block') :css('height', HEIGHT .. 'px'):css('width', WIDTH .. 'px')

-- Backdrop (eg. Water, Lava) if args.backdrop then mapDiv:node(makeBackdropDiv(args.backdrop, args.type, WIDTH, HEIGHT)) end

-- Base Map if args.baseMap then mapDiv:node(makeBackgroundDiv(args.baseMap, args.type, WIDTH, HEIGHT)) end

-- Tactics Drills scroll if args.type == 'TD' then mapDiv:node(makeTDScrollDiv) end

-- Starting positions if args.allyPos then local i = 1 for pos in mw.text.gsplit(args.allyPos, '%s*,%s*') do			mapDiv:node(makeMapObjectDiv(pos, makeStartingSpotIcon('Ally', i, '60x60px'))) i = i + 1 end end if args.enemyPos then local i = 1 for pos in mw.text.gsplit(args.enemyPos, '%s*,%s*') do			mapDiv:node(makeMapObjectDiv(pos, makeStartingSpotIcon('Enemy', i, '60x60px'))) i = i + 1 end end

-- Map Objects for y = 1, MAP_HEIGHT do		for x = 1, MAP_WIDTH do			local pos = makePos(x, y)			mapDiv:node(makeMapObjectDiv(pos, args[pos])) end end

-- Initial units local units = cargo.query(		'MapUnits,Units',		Pos,MoveType,WeaponType,Units._pageName=page,TabName=tab,		 IF(MapUnits.Properties__full LIKE '%is_ally%','Ally','Enemy')=Side,IF(Units.Properties__full LIKE '%generic%','1','')=isGeneric, {		join = 'MapUnits.Unit=Units.WikiName',		where = ("MapUnits._pageName='%s' AND Pos IS NOT NULL AND Spawn IS NULL"):format( escq(args.page or mw.title.getCurrentTitle.fullText)),		groupBy = 'tab,Pos',		orderBy = 'tab,Pos',		limit = 100,	}) for _, v in ipairs(units) do		v.isGeneric = v.isGeneric ~= '' end if #units == 0 then return tostring(mapDiv) end

local unitsByTab = List.group_by(units, function (v) return v.tab end) local unitLayouts = Hash.map(unitsByTab, function (vs)		return table.concat(List.map(vs, function (v) return v.page .. '\0' .. v.Side .. '\0' .. v.Pos end), '\0')	end) local tabs = {} for dif, layout in Hash.sorted_pairs(unitLayouts, function (_, _, k1, k2) return Util.difficultySort(k1, k2) end) do		tabs[layout] = tabs[layout] or {} table.insert(tabs[layout], dif) end

local tabberArgs = {} for dif, vs in Hash.sorted_pairs(unitsByTab, function (_, _, k1, k2) return Util.difficultySort(k1, k2) end) do		local allDifs = Hash.remove(tabs, unitLayouts[dif]) if allDifs then -- clone the entire html object every time except for the last tab local tabDiv = next(tabs) and mw.clone(mapDiv) or mapDiv for _, v in ipairs(vs) do				tabDiv:node(makeMapObjectDiv(v.Pos, tostring(makeUnitIcon_noCargo(v, v.Side)))) end tabberArgs[#tabberArgs + 1] = {table.concat(allDifs, '/'), tostring(tabDiv)} end end if #tabberArgs == 1 then return tabberArgs[1][2] end return Tab.tabber(tabberArgs) end

local p = require 'Module:MakeMWModule'.makeMWModule { ally = ally, enemy = enemy, map = makeMap, RDmap = makeRDmap, initTabber = initTabber, } p.makeUnitIcon_noCargo = makeUnitIcon_noCargo return p