Module:Recipe Tree Search/Sandbox

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to navigation Jump to search
Module documentation
This documentation is transcluded from Module:Recipe Tree Search/Sandbox/doc. [edit] [history] [purge]
Module:Recipe Tree Search/Sandbox's function main is invoked by Template:Recipe Tree Search/Sandbox.
Module:Recipe Tree Search/Sandbox requires .
Function list
L 11 — p.main
L 16 — p._main
L 39 — check_item
L 56 — parse_list
L 72 — p.parse_args
L 95 — p.output_args
L 124 — p.fill_cache
L 173 — p.build_tree
L 202 — p.build_subtree
L 272 — p.create_checklist
L 325 — p.create_sublist
L 376 — p.create_baselist

A sandbox copy of Module:Recipe Tree Search for prototyping changes before implementing them.


require("strict")
require('Module:Mw.html extension')
local hc = require('Module:Paramtest').has_content
local yn = require('Module:Yesno')
local tt = require('Module:TableTools')
local plink = require("Module:Plink")._plink
local lang = mw.getContentLanguage()

local p = {}

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

function p._main(args)
	
	local params = p.parse_args(args)
	
	if not hc(params.item) then
		return 'Invalid item specified.'
	end
	
	-- looks through every recipe in the tree in a non-recursive manner to fill 
	-- the cache of recipe JSON data, ''before'' it tried to construct the tree 
	-- itself
	local cache = p.fill_cache(params)

	-- build the tree structure from the cached data
	local tree = p.build_tree(params,cache)
	
	local list = p.create_checklist(params,tree,args)
	
	return list
	
end

-- takes a case-insensitive name of an item, and returns the case-sensitive name
local function check_item(item) 
	local query = bucket('infobox_item')
		.where(bucket.Or({'page_name', item },{'page_name_sub', item },{'item_name', item }))
		.select('page_name','image')
		.limit(1)
		.run()
	local result = query and type(query)=='table' and query[1] and type(query[1])=='table' and query[1] or {}
	local page_name = result.page_name
	local image = result.image and type(result.image)=='table' and result.image[1] or result.image
	if type(image)=='string' then
		image = image:gsub('^File:',''):gsub("%.png$", "")
	end
	return page_name, image
end

-- takes a list of comma-separated item names, and returns a table where each 
-- item name is a key and the value at each key is {}.
local function parse_list(list)
	if not (type(list)=='string' and hc(list)) then
		return {}
	end
	local parts = mw.text.split(list,',')
	local out = {}
	for _,part in ipairs(parts) do
		part = part:gsub("^%s*(.-)%s*$", "%1")
		if hc(part) then
			out[part] = {}
		end
	end
	return out
end

-- parses the input args, either from the calculator or from a template use
function p.parse_args(args)
	
	local params = {}
	
	params.item = (args[1] or args.item) or ''
	params.page, params.image = check_item(params.item)
	if not hc(params.page) then
		params.item = nil
	end
	params.page = params.item
	params.qty = tonumber(args[2]) or tonumber(args.qty) or 1
	params.force_base = parse_list(args.force_base)
	params.force_intermediate = parse_list(args.force_intermediate)
	params.exclude_intermediate = parse_list(args.exclude_intermediate)
	params.transmute = yn(args.transmute)
	params.wikiout = yn(args.wikiout)
	
	return params
	
end

-- the inverse function, takes the params that were used and reproduces the 
-- input args
function p.output_args(args)
	
	local out = '<pre>{{Recipe Tree Search'
	if hc(args.item) then
		out = out  .. '<br>|item=' .. args.item
	end
	if hc(args.qty) and tonumber(args.qty)~=1 then
		out = out  .. '<br>|qty=' .. args.qty
	end
	if args.force_base then
		out = out  .. '<br>|force_base=' .. args.force_base
	end
	if args.force_intermediate then
		out = out  .. '<br>|force_intermediate=' .. args.force_intermediate
	end
	if args.exclude_intermediate then
		out = out  .. '<br>|exclude_intermediate=' .. args.exclude_intermediate
	end
	if yn(args.transmute) then
		out = out  .. '<br>|transmute=yes'
	end
	out = out .. '<br>}}</pre>'
	
	return out
	
end

-- non-recursively traverses the recipe tree, extracting the production_json 
-- tables for each recipe it finds in the tree
function p.fill_cache(params)
	
	local item = params.item
	
	if type('item')~='string' then
		return nil
	end
	
	-- achieves this non-recursively by creating a to do list of recipes search
	-- for, adding the jsons to the cache as it goes, and not re-searching a
	-- recipe that is already in the cache.
	local to_do_list = {item}
	local cache = {}
	for cache_item,_ in pairs(params.force_base) do
		cache[cache_item] = {}
	end
	
	while #to_do_list>0 do
		item = table.remove(to_do_list)
		
		if not cache[item] then 
			cache[item] = {}
			local query = bucket('recipe')
				.where({'production_main_output',item})
				.select('uses_material','production_json')
				
			local results = query.run()
			
			for _,result in ipairs(results) do
				local json = result.production_json and type(result.production_json)=='string' and mw.text.jsonDecode(result.production_json) or {}
				if params.transmute or not string.find(json.method,'transmutation') then
					cache[item] = json
					local materials = result.uses_material or {}
					for i,material in ipairs(materials) do
						table.insert(to_do_list,material)
					end
					break
				end
			end
			cache[item].intermediate = params.force_intermediate[item] and true
		else
			cache[item].intermediate = not params.exclude_intermediate[item]
		end
	end
	
	return cache
end

-- recursively builds the tree structure for the recipe
function p.build_tree(params,cache)
	
	-- the main tree table will have an entry called "main" that is the top level
	local tree = {
		main={
			item = params.item,
			qty = params.qty,
			page = params.page,
			image = params.image
		}
	}
	
	-- this first call finds all intermediates within the recipe tree, and a 
	-- list of base items which gets added to with each recursive call
	local _, intermediates, bases = p.build_subtree(tree.main,cache,{},{},{},false)
	
	-- for each intermediate, repeat the recursive search and save it under the 
	-- key of the name of the intermediate item
	for i,intermediate in pairs(intermediates) do
		tree[i],_,bases = p.build_subtree(intermediate,cache,{},{},bases,true)
	end
	
	-- the base materials
	tree.base = bases
		
	return tree
	
end

function p.build_subtree(tree,cache,branch,intermediates,bases,show_intermediate)
	
	intermediates = intermediates or {}
	bases = bases or {}
	
	local item = tree.item
	local qty = tree.qty
	local page = tree.page
	local image = tree.image
	local intermediate = tree.intermediate
	
	local new_branch = tt.deepCopy(branch)
	table.insert(new_branch,item)
	for _,branch_item in ipairs(branch) do
		if branch_item==item then
			local subtree = {
				item = 'Warning: Recipe loop detected in branch: ' .. table.concat(new_branch,'<') .. '. Force one of these items to be a base material to break the loop',
				qty = 1,
				page = '',
				image = '',
			}
			tree[1] = subtree
			return tree, cache, branch
		end
	end
	
	local item_cache = cache[page]
	if item_cache==nil or type(item_cache.materials)~='table' then
		bases[item] = bases[item] or {}
		bases[item].item = item
		bases[item].qty = bases[item].qty or 0
		bases[item].qty = qty and bases[item].qty + qty
		bases[item].page = page
		bases[item].image = image
		return tree, intermediates, bases
	end
	if item_cache.intermediate and not show_intermediate then
		intermediates[item] = intermediates[item] or {}
		intermediates[item].item = item
		intermediates[item].qty = intermediates[item].qty or 0
		intermediates[item].qty = qty and intermediates[item].qty + qty
		intermediates[item].page = page
		intermediates[item].image = image
		tree.intermediates = true
		return tree, intermediates, bases
	end
	
	local output_quantity = 1
	for _,out in ipairs(item_cache.outputs) do
		if out.name==item then
			output_quantity = out.quantity
		end
	end
	
	local recipe_qty = qty / output_quantity
	
	for i,mat in ipairs(item_cache.materials) do
		local subtree = {
			item = mat.name,
			qty = mat.quantity * recipe_qty,
			page = mat.page,
			image = mat.image,
		}
		tree[i] = p.build_subtree(subtree,cache,new_branch,intermediates,bases,show_intermediate)
	end
	
	return tree, intermediates, bases

end

function p.create_checklist(params,tree,args)
	
	local out = mw.html.create('div')
	
	if params.wikiout then
		out:tag('span')
			:node(p.output_args(args))
	end
	
	local first_header = out:tag('h4')
		:wikitext('Main recipe tree')

	
	local first_list = out:tag('div')
		:addClass('lighttable checklist')
		:tag('ul')
			:css('list-style', 'none')
			:node(p.create_sublist(tree.main))

	local first_material=true
	for k,v in pairs(tree) do
		if k~='main' and k~='base' then
			if first_material then
				first_material = false
				local int_header = out:tag('h4')
					:wikitext('Intermediate materials')
			end
			
			local int_list = out:tag('div')
				:addClass('lighttable checklist')
				:tag('ul')
					:css('list-style', 'none')
					:node(p.create_sublist(v))
				:done()
			
		end
	end
	
	local base_header = out:tag('h4')
		:wikitext('Base materials')
		:done()
		
	local base_list = out:tag('div')
		:addClass('lighttable checklist')
		:tag('ul')
			:css('list-style', 'none')
			:node(p.create_baselist(tree.base))
		:done()
		
	return out
	
end

function p.create_sublist(tree)
	
	local li = mw.html.create('li')
	
	if type(tree)=='table' then
		
		local item = tree.item
		local qty = tree.qty
		local qtystring = lang:formatNum(tonumber(('%.3f'):format(qty)))
		local page = tree.page or item
		local image = tree.image:gsub("%.png$", "") or item
		local intermediates = tree.intermediates
		
		if tree[1] then
			li:addClass('mw-collapsible coloured-collapse')
				:attr('data-expandtext', '⊕')
				:attr('data-collapsetext', '⊖')
				
			local header = li:tag('div')
				:addClass('mw-collapsible-toggle')
				:tag('span')
					:addClass('mw-collapsible-text')
					:wikitext('⊖')
				:done()
				:wikitextIf(page~='',('&nbsp;&nbsp;&nbsp; %s × %s'):format(qtystring, plink(page, {pic = image, txt = item})))
				:wikitextIf(page=='',('&nbsp;&nbsp;&nbsp; %s'):format(item))
			
			local content = li:tag('div')
				:addClass('mw-collapsible-content')
			
			local ul = content:tag('ul')
				:css('list-style', 'none')
			for _,subtree in ipairs(tree) do
				ul:node(p.create_sublist(subtree))
			end
		else
			local header = li:tag('div')
				:tag('span')
					:css('margin-left','0.2em')
					:wikitextIf(intermediates,'⊗')
					:wikitextIf(not intermediates,'⊙')
				:done()
				:wikitextIf(page~='',('&nbsp;&nbsp;&nbsp; %s × %s'):format(qtystring, plink(page, {pic = image, txt = item})))
				:wikitextIf(page=='',('&nbsp;&nbsp;&nbsp; %s'):format(item))
				:wikitextIf(intermediates,' - [Intermediate item]')
		end
	end
	
	return li
end

function p.create_baselist(tree)
	
	local out = mw.html.create('div')
		:addClass('lighttable checklist')
	
	if type(tree)=='table' then
		
		local keys = tt.keysToList(tree)
		
		for _,key in ipairs(keys) do
			
			local item = tree[key].item
			local qty = tree[key].qty
			local qtystring = lang:formatNum(tonumber(('%.3f'):format(qty)))
			local page = tree[key].page or item
			local image = tree[key].image:gsub("%.png$", "") or item
			
			out:tag('li')
				:tag('span')
					:css('margin-left','0.2em')
					:wikitext('⊙')
				:done()
				:wikitext(('&nbsp;&nbsp;&nbsp; %s × %s'):format(qtystring, plink(page, {pic = image, txt = item})))
			:done()
			
		end
	end
	
	return out
end

return p