Module:Products: Difference between revisions

From Brighter Shores Wiki
Jump to navigation Jump to search
Content added Content deleted
No edit summary
(extend module to work on data from Profession info as well as Infobox Recipe)
Line 34: Line 34:
limit = 500
limit = 500
end
end

-- Query for data
-- Query for data
local smw_data = mw.smw.ask{
local smw_data = mw.smw.ask{
'[[Uses item::' .. item .. ']]',
'[[Uses item::' .. item .. ']] OR [[Activity input::' .. item .. ']] OR [[Activity container::' .. item .. ']]',
'?Uses item',
'?Recipe JSON',
'?Recipe JSON',
'?Activity JSON',
showValues and '?Value',
showValues and '?Value',
limit = limit
limit = limit
Line 52: Line 52:
local produced_items = {}
local produced_items = {}
for _, product in ipairs(smw_data) do
for _, product in ipairs(smw_data) do
local jsons = product['Recipe JSON']
local jsons = product['Recipe JSON'] or product['Activity JSON']
if type(jsons) == 'string' then
if type(jsons) == 'string' then
jsons = { jsons }
jsons = { jsons }
Line 126: Line 126:
:tag('th')
:tag('th')
:attr{ colspan = '3' }
:attr{ colspan = '3' }
:wikitext('Recipe')
:wikitext('Product')
:done()
:done()
:tag('th'):wikitext('Level'):done()
:tag('th'):wikitext('Level'):done()
Line 132: Line 132:
:tag('th'):wikitext('Value'):done()
:tag('th'):wikitext('Value'):done()
:END()
:END()
:tag('th'):wikitext('Ingredients'):done()
:tag('th'):wikitext('Inputs'):done()
:IF(showPrices)
:IF(showPrices)
:tag('th'):wikitext('Price'):done()
:tag('th'):wikitext('Price'):done()

Revision as of 02:18, 16 December 2024

Module documentation
This documentation is transcluded from Module:Products/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:Products/doc. [edit]
Module:Products's function main is invoked by Template:Products.
Module:Products requires Module:Array.
Module:Products requires Module:Currency.
Module:Products requires Module:Mw.html extension.
Module:Products requires Module:Purge.
Module:Products requires Module:Yesno.

require('Module:Mw.html extension')
local Array = require('Module:Array')
local currency = require('Module:Currency').parse
local yesno = require('Module:Yesno')
local purge = require('Module:Purge')._purge

local p = {}

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

local function recipe_sort(recipe_a, recipe_b)
	-- Sort unknown levels to the end
	if (recipe_a.level == nil) ~= (recipe_b.level == nil) then
		return recipe_b.level == nil
	end

	if recipe_a.level ~= nil then
		return recipe_a.level < recipe_b.level
	end

	-- Sort by name if same level
	return recipe_a.output[1].name < recipe_b.output[1].name
end

function p._main(args)
	args = args or {}
	local item = args[1] or mw.title.getCurrentTitle().text
	local showPrices = yesno(args.showPrices)
	local showValues = yesno(args.showValues)
	local limit = tonumber(args.limit or 0) or 0
	if limit <= 0 then
		limit = 500
	end
	
	-- Query for data
	local smw_data = mw.smw.ask{
		'[[Uses item::' .. item .. ']] OR [[Activity input::' .. item .. ']] OR [[Activity container::' .. item .. ']]',
		'?Recipe JSON',
		'?Activity JSON',
		showValues and '?Value',
		limit = limit
	}
	if not smw_data then
		return ":''No products found. To force an update, click "
				..purge('dml-'..mw.uri.anchorEncode(item), 'here', 'span')
				..".''[[Category:Empty products lists]]"
	end

	-- Create a list of all recipes, grouped by output item (to keep them together in the sort)
	local produced_items = {}
	for _, product in ipairs(smw_data) do
		local jsons = product['Recipe JSON'] or product['Activity JSON'] 
		if type(jsons) == 'string' then
			jsons = { jsons }
		end
		local parsed = {}
		for _, json in ipairs(jsons) do
			local json = mw.text.jsonDecode(json)
			json.Value = product.Value
			-- Filter out when this item isn't actually used (Will happen on pages with multiple recipes)
			if Array.any(json.materials, function(mat)
				return mat.name == item
			end) then
				table.insert(parsed, json)
			end
		end
		table.sort(parsed, recipe_sort)
		table.insert(produced_items, parsed)
	end

	-- Sort by the smallest recipe in the group
	table.sort(produced_items, function(item1, item2)
		local first1 = item1[1]
		local first2 = item2[1]
		if (first1 == nil) ~= (first2 == nil) then
			return first2 == nil
		end
		if first1 == nil then
			return false  -- Both empty, equivalent
		end

		return recipe_sort(first1, first2)
	end)

	-- Flatten into a single list
	local recipes = {}
	for _, product in ipairs(produced_items) do
		for _, json in ipairs(product) do
			table.insert(recipes, json)
		end
	end

	-- Calculate shop prices
	if showPrices then
		local price_cache = {}
		for _, recipe in ipairs(recipes) do
			for _, item in ipairs(recipe.materials) do
				-- Extract item name and quantity from the "item,#" format

				-- Query for the shop buy price of the item
				local shopPriceResult = price_cache[item.name] or mw.smw.ask{
					'[[Sold item::' .. item.name .. ']]',
					'?Shop buy price'
				} or {}
				price_cache[item.name] = shopPriceResult
				local shopPrice

				if shopPriceResult[1] then
					shopPrice = tonumber(shopPriceResult[1]['Shop buy price'] or 0) or 0
				end

				-- Update the product with the total price
				if shopPrice ~= nil then
					item.price = shopPrice * item.quantity
				end
			end
		end
	end

	-- Create table
	local out = mw.html.create('table')
		:addClass('wikitable align-right-1 sortable')
		:tag('tr')
			:tag('th')
				:attr{ colspan = '3' }
				:wikitext('Product')
			:done()
			:tag('th'):wikitext('Level'):done()
			:IF(showValues)
				:tag('th'):wikitext('Value'):done()
			:END()
			:tag('th'):wikitext('Inputs'):done()
			:IF(showPrices)
				:tag('th'):wikitext('Price'):done()
			:END()
		:done()

	for _, recipe in ipairs(recipes) do
		local row = out:tag('tr')
			:tag('td')
				:css{ ['border-right'] = '0', ['padding-right'] = '0' }
				:attr{ ['data-sort-value'] = recipe.output[1].name }
				:wikitext(recipe.output[1].quantity .. ' &times;')
			:done()
			:tag('td')
				:addClass('plinkt-image no-border')
				:css{ ['border-left'] = '0', ['padding-left'] = '0' }
				:wikitext('[[File:' .. recipe.output[1].name .. '.png|link=' .. recipe.output[1].name .. '|30px]]')
			:done()
			:tag('td')
				:addClass('plinkt-link no-border')
				:wikitext('[[' .. recipe.output[1].name .. ']]')
			:done()
			:tag('td')
				:IF(recipe.profession)
					:wikitext(('[[File:%s small icon.png|15px|link=%s]] %s'):format(recipe.profession, recipe.profession, recipe.level or 'Unknown'))
				:ELSE()
					:wikitext(('[[FileUnknown profession small icon.png|15px|link=Professions]] %s'):format(recipe.level or 'Unknown'))
				:END()
			:done()

		if showValues then
			if recipe.Value then
				row:tag('td')
					:wikitext(currency(recipe.Value))
				:done()
			else
				row:tag('td')
					:attr{ ['data-sort-value'] = '' }
					:wikitext('Unknown')
				:done()
			end
		end

		local ingredients = row:tag('td')
			:attr{ ['data-sort-value'] = table.concat(Array.map(recipe.materials, function(item) return item.name end), '\0') }
			:tag('ul')
				:css{ ['list-style'] = 'none', ['margin'] = '0', ['padding-left'] = '0' }

		for _, item in ipairs(recipe.materials) do
			ingredients:tag('li')
				:wikitext(('%s &times; [[File:%s.png|link=%s|18px]] [[%s]]'):format(item.quantity, item.name, item.name, item.name))
			:done()
		end

		if showPrices then
			local prices = row:tag('td')
				:tag('ul')
					:css{ ['list-style'] = 'none', ['margin'] = '0', ['padding-left'] = '0' }

			for _, item in ipairs(recipe.materials) do
				if item.price then
					prices:tag('li'):wikitext(currency(item.price)):done()
				else
					prices:tag('li'):wikitext('Unknown'):done()
				end
			end
		end

	end

	return out
end

return p