Module:TradeTable

From Tensura: Reincarnated Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:TradeTable/doc

local p = {}
local namespace = mw.title.getCurrentTitle().nsText
local sprite = require('Module:SpriteFile')
local rowspans = {} -- Count of rowspans for each level
local rowspans_slot = {} -- count of rowspans for each slot
local outside_html -- html outside of table
local html -- html table
local lastLevel = ''
local lastSlot
local show_java
local show_mult_and_xp
local profession = {'Armorer', 'Butcher', 'Battlewill Trainer', 'Cartographer', 'Cleric', 'Farmer', 'Fisherman', 'Fletcher', 'Leatherworker', 'Librarian', 'Lumberjack', 'Magic Trainer', 'Mason', 'Merchant', 'Miner', 'Shepherd', 'Toolsmith', 'Weaponsmith'}
local refUsed = false
local level_trades = {} -- How many different trades are available in the pool per level
local given_note_list = {} -- A table of note names -> note text
local wanted_note_list = {} -- A table of note names -> note text

local colors = {'White', 'Light Gray', 'Gray', 'Black', 'Brown', 'Red', 'Orange', 'Yellow', 'Lime', 'Green', 'Cyan', 'Light Blue', 'Blue', 'Purple', 'Magenta', 'Pink'}

-- Maps alternate input param names to a standardized name
local function normalizeInput(line)
	return {
		level = line["level"] or line["lvl"],
		trade_slot = line["slot"] or '—',
		slot_draws = line["slotDraws"] or '-',
		wanted_item = line["want"] or line["want1"] or 'Emerald',
		wanted_quant = line["wantQuant"] or line["wantQuant1"] or '1',
		wanted_sprite = line["wantSprite"] or line["wantSprite1"],
		wanted_item_2 = line["want2"],
		wanted_sprite_2 = line["wantSprite2"],
		wanted_quant_2 = line["wantQuant2"] or '1',
		wanted_note = line["wantNote"],
		wanted_note_text = line["wantNoteText"],
		price_multiplier = line["multi"],
		given_item = line["give"] or 'Emerald',
		given_sprite = line["giveSprite"],
		given_quant = line["giveQuant"] or '1',
		given_note = line["giveNote"],
		given_note_text = line["giveNoteText"],
		max_trades = line["maxTrades"],
		villager_xp_gain = line["xpGain"],
		weight = line["weight"] or 1,
		override_probability_je = line['overrideProbability'] or line['overrideProbabilityJE'] or nil,
		override_probability_be = line['overrideProbability'] or line['overrideProbabilityBE'] or nil
	}
end

-- Takes all args and puts them into a json string
function p.serialize(frame)
	return mw.text.jsonEncode(frame:getParent().args)
end

-- Undo the serialization for parsing
local function deserialize(args)
	local e
	local out = {}
	for k, v in pairs(args) do
		if type(k) == "number" then -- only deserialize unnamed params
			e, out[k] = pcall( mw.text.jsonDecode, v )
			if not e then
				out[k] = { '', 'unknown', v..' <strong class="error">Lua error: '..out[k].. ' Input:'.. v ..'</strong>' }
			else
				out[k] = normalizeInput(out[k])
			end
		end
	end
	return out
end

local function printHeader(headerText)
	outside_html = mw.html.create('div')
	html = outside_html:tag('table')
		:addClass('wikitable')
		:cssText('text-align:center')
	
	local colspan = 6
	if show_java then
		colspan = colspan + 1
	end
	
	if headerText ~= '' then
		html:tag('th')
			:attr('colspan', colspan)
			:attr('data-description', headerText)
			:wikitext(sprite.link({profession, name='EntitySprite', headerText}))
	end
	
	local tr = html:tag('tr')
	tr:tag('th'):wikitext('Level'):attr('rowspan', 2)
	
	if show_java then
		tr:tag('th'):wikitext("''[[Java Edition]]''")
	end

	tr:tag('th'):wikitext('Villager wants'):attr('rowspan', 2)
		:tag('th'):wikitext('Player receives'):attr('rowspan', 2)
		:tag('th'):wikitext('Trades in<br>stock'):attr('rowspan', 2)
	
	if show_mult_and_xp then
		tr:tag('th'):wikitext('Villager<br>experience'):attr('rowspan', 2)
			:tag('th'):wikitext('Price<br>multiplier'):attr('rowspan', 2)
	end
	
	tr = tr:tag('tr')
	if show_java then
		tr:tag('th'):wikitext('Probability')
	end

end

function p.formatItemOutput(item, quant, item2, quant2, _sprite, _sprite2)
	local text, qtyText = '', ''
	
	local item_display_text = item:gsub(' %(item%)',''):gsub('Any color','')
	
	_sprite = _sprite or item
	if quant ~= '1' then
		quant = quant:gsub('-', '&ndash;')
		qtyText = quant..' × '
	end
	if item:find('Enchanted') then
		text = text..qtyText..'[[File:'.._sprite..' (item).gif|16x16px|link='..item..']] [['..item..']]'
	elseif item:find('Any color') then
		-- Show all color sprites
		local base_item = _sprite:gsub('Any color', '')
		for i, color in pairs(colors) do
			text = text..sprite.link({text='',id=color..' '..base_item,name='InvSprite',keepcase=1})
			if i == 8 then
				text = text..'<br>'
			end
		end
		text = text..'<br>'..qtyText..'[['..item_display_text..'|'..item..']]'
	else
		text = text..qtyText..sprite.link({text=item_display_text,id=_sprite,link=item,name="InvSprite", keepcase=1})
	end
	
	if quant2 and item2 then
		text = text .. '<br>+ ' .. p.formatItemOutput(item2, quant2, nil, nil, _sprite2)
	end
	return text
end

-- from WP
local function choose(n, k)
	if k < 0 or k > n then
		return 0
	end
	if k == 0 or k == n then
		return 1
	end
	k = math.min(k, n-k) -- symmetry
	local c = 1
	for i = 0, k-1 do
		c = c * (n-i) / (k - i)
	end
	return c
end

local function formatProbability(line, use_slot)
	local weight = line.weight
	local group_weight = level_trades[line.level]
	local draws = 2
	
	if group_weight - weight == 0 then
		return 100
	end
	
	local probability = (1 - choose(group_weight - weight, draws) / choose(group_weight, draws)) * 100
	return math.floor(probability+0.5)
end

local function printLine(line)
	local tr = html:tag('tr')
	local cssText = ''
	if line.level ~= lastLevel then
		-- Don't do thick border for first level
		if lastLevel ~= '' then
			cssText = 'border-top-width:2px'
		end
		
	end
	lastLevel = line.level
	
	if show_java then
		local java_probability = formatProbability(line, false) .. '%'
		tr:tag('td'):cssText(cssText):wikitext(line.override_probability_je or java_probability) -- Java probability
	end
	

	local wanted = tr:tag('td'):cssText(cssText):wikitext(p.formatItemOutput(line.wanted_item, line.wanted_quant, line.wanted_item_2, line.wanted_quant_2, line.wanted_sprite, line.wanted_sprite_2))
	if line.wanted_note_text ~= nil or line.wanted_note ~= nil then
		wanted:wikitext(mw.getCurrentFrame():extensionTag{ name='ref', content=line.wanted_note_text, args = {name=line.wanted_note, group='t'}})
		refUsed = true -- Used to set references at footer
	end
	local given = tr:tag('td'):cssText(cssText):wikitext(p.formatItemOutput(line.given_item, line.given_quant, nil, nil, line.given_sprite)):wikitext()
	if line.given_note_text ~= nil or line.given_note ~= nil then
		given:wikitext(mw.getCurrentFrame():extensionTag{ name='ref', content=line.given_note_text, args = {name=line.given_note, group='t'}})
		refUsed = true -- Used to set references at footer
	end
	tr:tag('td'):cssText(cssText):wikitext(line.max_trades)
	
	if show_mult_and_xp then
		tr:tag('td'):cssText(cssText):wikitext(line.villager_xp_gain or '—')

		local price_multiplier_text = line.price_multiplier
		
		tr:tag('td'):cssText(cssText):wikitext(price_multiplier_text or '—')
	end
end

local function storeLine(line)
	-- Put data into Bucket
	-- The only top level params that are needed is stuff that would be used as selectors
	-- everything else is put into a json blob for flexibility
	if not (namespace == '' or namespace == 'Minecraft_Wiki') then
		 return -- only store data if we are in main or Minecraft_Wiki namespaces
	end
	
	local bucket_json = {
		['profession'] = profession,
		['level'] = line.level,
		['java_probability'] = line.override_probability_je or formatProbability(line, false) .. '%',
		['given_item'] = line.given_item,
		['given_quant'] = line.given_quant,
		['given_sprite'] = line.given_sprite,
		['wanted_item'] = line.wanted_item,
		['wanted_quant'] = line.wanted_quant,
		['wanted_sprite'] = line.wanted_sprite,
		['wanted_item_2'] = line.wanted_item_2,
		['wanted_quant_2'] = line.wanted_quant_2,
		['wanted_sprite_2'] = line.wanted_sprite_2,
		['given_note'] = line.given_note,
		['given_note_text'] = given_note_list[line.given_note] or line.given_note_text,
		['wanted_note'] = line.wanted_note,
		['wanted_note_text'] = wanted_note_list[line.wanted_note] or line.wanted_note_text,
		['price_multiplier'] = line.price_multiplier,
		['max_trades'] = line.max_trades,
		['villager_xp_gain'] = line.villager_xp_gain
	}

	local bucket_data = { -- the actual bucket data
		['profession'] = profession,
		['given_item'] = line.given_item,
		['wanted_item'] = {line.wanted_item, line.wanted_item_2},
		['json'] = mw.text.jsonEncode(bucket_json)
	}
	bucket('trade').sub(line.level).put(bucket_data)
end

function p.main(frame)
	return p._main(frame:getParent().args)
end

function p._main(args)
	local headerText = args.title or ''
	local ignoreUsage = args.ignoreUsage or nil
	local lines = deserialize(args)
	
	
	local edition = args.edition 
	show_java = (edition == nil or edition == '' or edition == 'both' or edition == 'java')

	-- First loop counts the rowspans for column 1
	for i,line in ipairs(lines) do
		level_trades[line.level] = (level_trades[line.level] or 0) + line.weight
		rowspans[line.level] = (rowspans[line.level] or 0) + 1
		rowspans_slot[line.trade_slot] = (rowspans_slot[line.trade_slot] or 0) + 1
		slot_trades[line.trade_slot] = (slot_trades[line.trade_slot] or 0) + line.weight
		
		--Save the note text so it can be used by Bucket to display the notes on other pages
		if line.given_note and line.given_note_text and given_note_list[line.given_note] == nil then
			given_note_list[line.given_note] = line.given_note_text
		end
		if line.wanted_note and line.wanted_note_text and wanted_note_list[line.wanted_note] == nil then
			wanted_note_list[line.wanted_note] = line.wanted_note_text
		end
	end
	profession = args.profession or headerText
	
	
	rowspans_slot['—'] = 1 -- Special case missing slot to not combine
	
	for i, line in ipairs(lines) do
		printLine(line)
		if not ignoreUsage then
			storeLine(line)
		end
	end
	
	if refUsed then
		outside_html:wikitext(mw.getCurrentFrame():extensionTag{ name='references', args = {group='t'}})
	end
	
	return outside_html
end

return p