Module:Mayor

local loader = require('Module:Loader') local getArgs = require('Module:Arguments').getArgs

local string, table, yesno, slotutils, _ = loader.require('String', 'Table', 'Yesno', 'Inventory slot/Utils', 'LibraryUtil') local mayorData, eDT = loader.loadData('Mayor/Data', 'Mayor/Elections')

local electionsData, uiSpecialCases, currentMayor, currentYear = eDT.elections, eDT.uiSpecialCases, eDT.currentMayor, eDT.currentYear

local p = {}

local curTitle = mw.title.getCurrentTitle local candidateColors = { 'c', 'a', 'b', 'e', 'd' } local crown = 'https://static.wikia.nocookie.net/mcchampionship/images/3/39/WinnerCrown.png/revision/latest/scale-to-width-down/18?cb=20210922151946' local numToEng = { 'one', 'two', 'three', 'four', 'five' } local arrowSymbol = '➤'

function p.getMayorLink(mayor) return (''):format(mayorData.mayors[mayor] and mayorData.mayors[mayor].link or mayor, mayor) end

-- For Module:Infobox/Mechanics function p._mayorPerksTable(mayor) local dt = table.deepCopy(mayorData.mayors[mayor], true) dt = dt.perks_listed or dt.perks for _, v in ipairs(dt) do		v[1] = mayorData.linkedPerkNames[v[1]] or v[1] end return dt end

-- For Template:Current Mayor function p.getCurrentMayor(frame) local args = getArgs(frame) local m = currentMayor local ret if args[1] == 'r' then ret = m	elseif args[1] == 'l' then ret = mayorData.mayors[m] and mayorData.mayors[m].link or m	elseif args[1] == 'p' then ret = ('\'\'\'Perks: \'\'\' %s'):format(p.generatePerks) elseif args[1] == 'e' then local pg = ('Mayor Election/Year %s'):format(currentYear + 1) return pageExists(pg) and (' %s See Current Election '):format(pg, arrowSymbol) or '' else local d = mayorData.mayors[m] ret = ('%s'):format(d and d.link or m, m)	end return mw.getCurrentFrame:preprocess(ret) end

-- For Template:MayorPerks function p.mayorPerks(frame) local args = getArgs(frame) local headtext = args.headText or args.headtext headtext = headtext == 'none' and '' or headtext local txt = table.concat { headtext or 'Mayors may run for election with any of, randomly selected. Each candidate can offer up to at once. All candidates start with during their first election, and have a chance to stack up to  if they are a returning candidate. ',		args[2] or '', }	return mw.getCurrentFrame:preprocess(p._mayorPerks(args[1] or curTitle.text, txt)) end

function p._mayorPerks(mayor, prependText) local function insertperks(t, perksTb, frontdent) for _, info in ipairs(perksTb) do			table.push(t, ('%s %s : %s'):format( frontdent, mayorData.linkedPerkNames[info[1]] or info[1], type(info[2]) == 'table' and '' or info[2] ))			if type(info[2]) == 'table' then for _, item in ipairs(info[2]) do					table.push(t, ('%s* %s'):format(frontdent, item)) end end end end local dt = mayorData.mayors[mayor] local lines_ = {} local frontdent = dt.perks_listed and '**#' or '*#' if dt.perks_listed then table.push(lines_, '**\'\'\'Listed Perks\'\'\':') insertperks(lines_, dt.perks_listed, frontdent) table.push(lines_, ('**\'\'\'Actual Perks\'\'\': \'\'(hidden until %s is elected)\'\''):format(mayor)) end insertperks(lines_, dt.perks, frontdent) return table.concat({		prependText or '',		('*\'\'\'%s\'\'\' %s'):format( p.getMayorLink(mayor), dt.aka or '' ),		unpack(lines_)	}, '\n') end

-- For Template:MayorList function p.mayorList(frame) local args = getArgs(frame) return mw.getCurrentFrame:preprocess(p._mayorList(args[1])) end

function p._mayorList(_type) local mayors = table.deepCopy(mayorData.mayors, true) if _type then mayors = table.filterNamed(mayors, function(k, v)			return v.type == string.lower(_type)		end) end local paragraphs = {} for k, v in table.sortedPairs(mayors) do		table.push(paragraphs, p._mayorPerks(k)) end return table.concat(paragraphs, '\n') end

-- For Template:ElectionTable function p.electionTable(frame) return mw.getCurrentFrame:preprocess(p._electionTable) end

local function getClosestYear for i = table.length(electionsData), 1, -1 do		local c = electionsData[i].control or '' mw.log(c) if not (c:match('in%-progress') or c:match('collapsedown')) then return i		end end end

function p.generatePerks(mayor, year) year = year or getClosestYear local function each(t, i, title, text) text = type(text) == 'table' and table.map(table.deepCopy(text, true), function(d)			return '— ' .. d		end) or { text } table.push(t, string.wrapHtml{			i,			' ', {				class = 'minetip gemstone-slot',				style = { ['color'] = 'white' },				['data-minetip-title'] = '&d' .. title,				['data-minetip-text'] = table.concat(table.map(text, function(tx)					return string.gsubAll(tx, '{', '&#123;', '}', '&#125;', '.*', '')				end), '/'),			}		}) end local stats, perks = p._getElectionStats(year), nil for _, v in ipairs(stats) do		if v.name == mayor then perks = v.perks end end mayor = mayor or stats[1].name perks = perks or stats[1].perks if not perks then error('No perks information for Mayor ' .. mayor .. ' in year ' .. year .. ' election') end local allperks, t = mayorData.mayors[mayor].perks, {} if perks:match('%*') then for i = 1, table.xlength(allperks, false) do			if not allperks[i] then error(('Perk %s of "%s" not found.'):format(i, mayor)) end if not allperks[i] then break end each(t, i, allperks[i][1], allperks[i][3] ~= '' and allperks[i][3] or allperks[i][2]) end else for _, v in ipairs(string.split(perks, '')) do			v = tonumber(v) each(t, v, allperks[v][1], allperks[v][3] ~= '' and allperks[v][3] or allperks[v][2]) end end return table.concat(t, ' ') end

-- Helper function -- Returns ordered election stats with processed information -- Returns empty table if no relevant information function p._getElectionStats(year) local election = electionsData[tonumber(year)] local ret = {} if election then local c = election.control or '' if election and not c:match('novote') and not c:match('collapsedown') then if election.result or election.data then local tot = 0 local result = election.result or table.mapNamed(election.data, function(k, v)					return v.votes				end) for _, v in pairs(result) do					tot = tot + v				end for k, v in table.sortedPairsByValue(result, function(a, b)					return a > b				end) do					table.push(ret, table.merge({}, (election.data and election.data[k] or {}), { name = k,						votes = v,						percent = v / tot * 100, }))				end ret.total_votes = tot end if election.result or election.mayor then ret[1] = ret[1] or {} ret[1].name = ret[1].name or election.mayor ret[1].votes = ret[1].votes or election.votes ret[1].perks = ret[1].perks or election.perks ret[1].percent = ret[1].percent or election.percent end end if c:match('novote') and election.mayor then ret[1] = { name = election.mayor, votes = election.votes, }		end end return ret end

function p._electionTable local dt, rows = electionsData, {} local _date, _year = {}, {} for i = table.length(dt), 1, -1 do		local item = dt[i] local c = item.control or '' if not c:match('in%-progress') then table.push(_date, item.date) table.push(_year, i)			if not c:match('collapsedown') then local y2, y1 = _year[1], _year[#_year] local y = y2 == y1 and y2 or ('%s-%s'):format(y1, y2) local button = item.ui and (' &#91;UI&#93; '):format(y1) or '' local stats = p._getElectionStats(i) local _mayor, _votes, _perks, _other if #stats > 0 then local mayorName = stats[1].name if not mayorName then error(i) end _mayor = p.getMayorLink(mayorName) _votes = stats[1].percent and ('%s%s'):format(						(''):format(stats[1].percent),						stats[1].votes and (' '):format(stats[1].votes)					) or nil _perks = stats[1].perks or item.perks if _perks then if _perks:match('%?') then _perks = '' elseif mayorName then _perks = p.generatePerks(mayorName, i)						else _perks = 'Perk ' .. table.concat(string.split(_perks, ''), ', ') end local caption = stats[1].note or item.note _perks = caption and ('%s %s '):format(_perks, caption) or _perks end _other = {} for j = 2, #stats do						table.push(_other, (' %s: %s %s '):format( p.getMayorLink(stats[j].name), (''):format(stats[j].percent), (''):format(stats[j].votes) ))					end _other = #_other > 0 and table.concat(_other, ' ') or nil end table.push(rows, {					date = table.concat(_date, ' '),					year = y .. button,					mayor = _mayor,					votes = _votes,					perks = _perks,					other = _other,					control = c,				}) _date, _year = {}, {} end end end local wikitable = mw.html.create('table'):addClass('article-table wikitable') wikitable:tag('tr'):tag('th'):wikitext('SkyBlock Year'):done :tag('th'):wikitext('Date*'):done :tag('th'):wikitext('Mayor'):done :tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Perks'):done :tag('th'):wikitext('Other Candidates'):done local function g(val, control) return val or (control:match('novote') and  or ) end for _, v in ipairs(rows) do		wikitable:tag('tr'):tag('th'):wikitext(v.year):done :tag('td'):wikitext(v.date):done :tag('td'):wikitext(g(v.mayor, v.control)):done :tag('td'):wikitext(g(v.votes, v.control)):done :tag('td'):wikitext(g(v.perks, v.control)):done :tag('td'):wikitext(g(v.other, v.control)):done end return tostring(wikitable) end

local function mayorTooltip(name, mode, opt) opt = opt or {} local nums, col, ago, note = opt.nums, opt.col or 'd', opt.ago, opt.note local m = mayorData.mayors[name] mode = ('%s %s'):format(m.type, mode or '') local info = {} local perksref = table.deepCopy(mode:match('candidate') and m.perks_listed or m.perks, true) for _, perk in ipairs(nums and table.map(string.split(tostring(nums), ''), function(i) return perksref[tonumber(i)] end) or perksref) do		local desc = perk[3] ~= '' and perk[3] or perk[2] desc = type(desc) == 'table' and table.map(desc, function(d)			return '— ' .. d		end) or { desc } desc = string.gsubAll(table.concat(desc, '/'), '{', '&#123;', '}', '&#125;', '%|', '&#124;', '.*', '') -- Foxy event name replacement on tooltip if name == 'Foxy' and note then local c = note:match('Fishing') and 'b' or '6' local repl = string.gsub(note, '(.-) (.*)', ('&%s%%1/&%s%%2'):format(c, c)) desc = string.gsub(desc, '&6&#60;Event/&6name&#62;', repl) end table.push(info, ('&%s%s/&r%s'):format( col, perk[1], desc ))	end if mode:match('special') and mode:match('candidate') then table.push(info, '&r&' .. col .. 'This is a SPECIAL candidate!/&7It rarely appears!') end if mode:match('candidate') and ago then table.push(info, '&r&7Last elected: &' .. col .. ago .. 'y ago') end return { name, col, m.link or name, table.concat(info, '//'), title = perksref.title, text = perksref.text } end

local function templateTemplate(id, mode) local ret = {} for name, dt in pairs(mayorData.mayors) do		table.push(ret, mayorTooltip(name, mode)) end ret.id = id	return ret end

function p.getMayorsTemplate return templateTemplate('Mayor {o}', 'mayor') end

function p.getMayorCandidatesTemplate return templateTemplate(nil, 'candidate') end

-- For Template:Mayor Election UI function p.mayorElectionUI( frame ) local args = getArgs(frame) local year = args.year or args[1] return (' Electing: Mayor of Year %s \n%s'):format(year, mw.getCurrentFrame:preprocess(string.wrapHtml(		p._mayorElectionUI(year), 'div', { class = 'sbw-ui-tabber' }	))) end

function p._mayorElectionUI(year) local function slotview(slot) local params = '' slot['image'] = nil for k, v in pairs(slot) do			params = ('%s|%s = %s'):format(params, k, v)		end return params end local function uiview(tb) local params = '' for k, v in pairs(tb) do			params = ('%s|%s = %s'):format(params, k, v)		end return (''):format(params) end local t = table.deepCopy(uiSpecialCases[tonumber(year)] or {}, true) if #t < 1 then local data = electionsData[tonumber(year)] if not data or type(data.data) ~= 'table' then return '\'\' UI is missing \'\'' end data = data.data for candidate, candidateData in table.sortedPairsByValue(data, function(a, b)			return a.order < b.order		end) do			table.push(t, { candidate, dt = candidateData }) end end for i, v in ipairs(t) do		local info = mayorTooltip(v[1], 'candidate', {			nums = v.dt.perks,			col = candidateColors[i],			ago = v.dt.last,			note = v.dt.note,		}) local normal_tt = slotutils._useTemplate(			{ table.merge({ id = 'THIS' }, info) }, 'T:Mayor Candidate'		)['THIS'] local voting_tt = slotutils._useTemplate(			{ table.merge({ id = 'THIS' }, info, 'Leading in votes!/') }, 'T:Mayor Candidate Voting'		)['THIS'] local normal_title = string.gsub(normal_tt.title, ',', '\\,') local normal_text = string.gsubAll(normal_tt.text, ',', '\\,', 'Year #', 'Year ' .. year) local voting_text = string.gsubAll(voting_tt.text, ',', '\\,', 'Year #', 'Year ' .. year) v.glasspane = ('Gray Stained Glass Pane, election-year-%s-candidate-%s; none, %s, %s'):format(year, numToEng[i], normal_title, voting_text) v.mayor2 = ('%s, election-year-%s-candidate-%s; none, %s, %s'):format(v[1], year, numToEng[i], normal_title, voting_text) v.mayor1 = ('%s, election-year-%s-candidate-%s; none, %s, %s'):format(v[1], year, numToEng[i], normal_title, normal_text) end local function repl(txt, order, curr) return (order == curr) and txt:gsub('/&eClick to vote [^/]-$', '/&aYou voted for this candidate!') or txt end return uiview { 'Election', ['2, 1'] = t[1].mayor1, ['2, 3'] = t[2].mayor1, ['2, 5'] = t[3].mayor1, ['2, 7'] = t[4].mayor1, ['2, 9'] = t[5].mayor1, id = 'election-page1-year-'..year, rows = 3, arrow = 'none', close = 'none', } .. table.concat(table.map(table.map(t, function(d)			return d[1]			end), function(can, i)		return uiview { ('Election, Year %s'):format(year), ['col 1'] = ('%s; 1, 5'):format(repl(t[1].glasspane, i, 1)), ['col 3'] = ('%s; 1, 5'):format(repl(t[2].glasspane, i, 2)), ['col 5'] = ('%s; 1, 5'):format(repl(t[3].glasspane, i, 3)), ['col 7'] = ('%s; 1, 5'):format(repl(t[4].glasspane, i, 4)), ['col 9'] = ('%s; 1, 5'):format(repl(t[5].glasspane, i, 5)), ['6, 1'] = repl(t[1].mayor2, i, 1), ['6, 3'] = repl(t[2].mayor2, i, 2), ['6, 5'] = repl(t[3].mayor2, i, 3), ['6, 7'] = repl(t[4].mayor2, i, 4), ['6, 9'] = repl(t[5].mayor2, i, 5), id = ('election-year-%s-candidate-%s'):format(year, numToEng[i]), hide = 'y', arrow = 'none', close = 'none', }	end)) end

-- For Template:MayorWins function p.getWins( frame ) local args = getArgs(frame) local mayor = args[1] local stats = p._getWins if not mayorData.mayors[mayor] then return nil end if mayor then return stats[mayor].wins else local wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 3):wikitext(crown .. ' Mayor Election Stats') wikitable:tag('tr'):tag('th'):wikitext('Candidate'):done :tag('th'):wikitext('Times Elected'):done :tag('th'):wikitext(string.makeTitle('Average Support On Elected', 'Average vote percentage for a mayor when they are elected.')):done for k, v in table.sortedPairsByValue(stats, function(a, b)			return a.wins > b.wins		end) do			wikitable:tag('tr'):tag('td'):wikitext(''):done :tag('td'):wikitext(v.wins):done :tag('td'):wikitext((''):format(v.percent)):done end return mw.getCurrentFrame:preprocess(tostring(wikitable)) end end

function p._getWins local dt, storage, percentages, ret = electionsData, {}, {}, {} for _, v in ipairs(table.keys(mayorData.mayors)) do		storage[v] = { tot_wins = 0, tot_perc = 0, perc_counted = 0 } end for i = table.length(dt), 1, -1 do		local item = dt[i] local stats = p._getElectionStats(i) if stats[1] then local m = stats[1].name if storage[m] and stats[1].percent then storage[m].tot_wins = storage[m].tot_wins + 1 storage[m].tot_perc = storage[m].tot_perc + (stats[1].percent or 0) storage[m].perc_counted = storage[m].perc_counted + 1 end end end for mayor, stored in pairs(storage) do		ret[mayor] = { wins = stored.tot_wins, percent = stored.perc_counted > 0 and (stored.tot_perc / stored.perc_counted) or 0 }	end return ret end

function p.mayorUI( frame ) local args = getArgs(frame) local year = args.year or args[1] return p._mayorUI(year) end

function p._mayorUI(year) local slot1, slot2, mayorName = p._elecResultSlot(year) if not slot1 then return '\'\' UI is missing \'\'' end return mw.getCurrentFrame:expandTemplate{ title = 'Mayor/' .. mayorName, args = { slot1, slot2 } } .. '\'\' Note: This UI is recreated based on election data. The interface may not be exact. \'\'' end

function p._elecResultSlot(year) local res = p._getElectionStats(year) if #res > 0 then local results = table.map(res, function(val, i)			if val.votes and val.percent and val.name and val.order then				return ('&%s%.2f%%&8 ○ &e%s &evotes&8 | &%s%s'):format( candidateColors[val.order], string._formatNum(val.percent), string._formatNum(val.votes), candidateColors[val.order], val.name )			end		end) if #results == 5 then local resultStr, mayorName = table.concat(results, '/'), res[1].name local info = mayorTooltip(mayorName, 'mayor', {				nums = res[1].perks,				col = candidateColors[res[1].order],				note = res[1].note,			}) local normal_tt = slotutils._useTemplate(				{ table.merge({ id = 'THIS' }, info) }, 'T:Mayor'			)['THIS'] local normal_title = string.gsub(normal_tt.title, ',', '\\,') local normal_text = string.gsub(normal_tt.text, ',', '\\,') .. (mayorName:match('Jerry') and '//&9Perkpocalypse://&7<#list of perkpocalypse perks>' or '') local slot1 = ('Mayor %s, none, %s, %s'):format(				mayorName,				normal_title,				normal_text			) local slot2 = ('Jukebox, none; none, &bMayor Election Results, &8Year %s//%s//&7These are the votes for the/&7last election in which/&7%s was elected.'):format(				year,				resultStr:gsub(',', '\\,'),				mayorName			) return slot1, slot2, mayorName end end end

return p