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') local mayorData, electionsData = loader.loadData('Mayor/Data', 'Mayor/Elections')

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'

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 = mayorData.currentMayor local ret if args[1] == 'r' then ret = m	elseif args[1] == 'p' then ret = ('\'\'\'Perks: \'\'\' %s'):format(p.generatePerks) 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 txt = table.concat { '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

function p.generatePerks(mayor, year) year = year or table.length(electionsData) 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[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 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(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 repl = string.gsub(note, '(.-) (.*)', '&6%1/&6%2') 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 data = electionsData[tonumber(year)] if not data or type(data.data) ~= 'table' then return '\'\' UI is missing \'\'' end data = data.data local t = {} for candidate, candidateData in table.sortedPairsByValue(data, function(a, b)		return a.order < b.order	end) do		table.push(t, { candidate, dt = candidateData }) 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.gsub(normal_tt.text, ',', '\\,') local voting_text = string.gsub(voting_tt.text, ',', '\\,') v.glasspane = ('Gray Stained Glass Pane, election-year-%s-candidate-%s; none, %s, %s'):format(year, v[1], normal_title, voting_text) v.mayor2 = ('%s, election-year-%s-candidate-%s; none, %s, %s'):format(v[1], year, v[1], normal_title, voting_text) v.mayor1 = ('%s, election-year-%s-candidate-%s; none, %s, %s'):format(v[1], year, v[1], normal_title, normal_text) end local function repl(txt, can) return txt:match(can) 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.keys(data), function(can) return uiview { ('Election, Year %s'):format(year), ['col 1'] = ('%s; 1, 5'):format(repl(t[1].glasspane, can)), ['col 3'] = ('%s; 1, 5'):format(repl(t[2].glasspane, can)), ['col 5'] = ('%s; 1, 5'):format(repl(t[3].glasspane, can)), ['col 7'] = ('%s; 1, 5'):format(repl(t[4].glasspane, can)), ['col 9'] = ('%s; 1, 5'):format(repl(t[5].glasspane, can)), ['6, 1'] = repl(t[1].mayor2, can), ['6, 3'] = repl(t[2].mayor2, can), ['6, 5'] = repl(t[3].mayor2, can), ['6, 7'] = repl(t[4].mayor2, can), ['6, 9'] = repl(t[5].mayor2, can), id = ('election-year-%s-candidate-%s'):format(year, can), 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 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 > 0 then local m = stats[1].name storage[m].tot_wins = storage[m].tot_wins + 1 if stats[1].percent then 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

return p