Module:Crafting

-- -- Taken from: https://minecraft.gamepedia.com/Module:Crafting -- And modified for use in this wiki local loader = require('Module:Loader')

local string, table, yesno, slot, bazaar, element, arguments = loader.require('String', 'Table', 'Yesno', 'Inventory slot', 'Bazaar', 'Element', 'Arguments')

local getArgs, mergeArgsSyntax = arguments.getArgs, arguments.mergeArgsSyntax local curTitle = mw.title.getCurrentTitle -- local recipeTable = require('Module:Recipe table').table -- local recipeTable = recipeTable.table

local p = {}

-- local i18n = { -- 	colored = 'Colored', -- 	coloredDyes = { -- 		'Orange Dye', 'Magenta Dye', 'Light Blue Dye', 'Yellow Dye', 'Lime Dye', -- 		'Pink Dye', 'Gray Dye', 'Light Gray Dye', 'Cyan Dye', 'Purple Dye', -- 		'Lapis Lazuli', 'Cocoa Beans', 'Cactus Green', 'Red Dye', 'Ink Sack' -- 	}, -- 	categoryIngredientUsage = 'Category:Recipe using $1', -- 	categoryRecipeType = 'Category:$1 recipe', -- 	categoryUpcoming = 'Category:Upcoming', -- 	itemBlockOfQuartz = 'Block of Quartz', -- 	itemBoneMeal = 'Bone Meal', -- 	itemBrownMushroom = 'Brown Mushroom', -- 	itemCharcoal = 'Charcoal', -- 	itemCoal = 'Coal', -- 	itemColoredDye = 'Colored Dye', -- 	itemDye = 'Dye', -- 	itemMushroom = 'Mushroom', -- 	itemQuartzBlock = 'Quartz Block', -- 	itemRedMushroom = 'Red Mushroom', -- 	itemStone = 'Stone', -- 	stoneVariants = { 'Stone', 'Andesite', 'Granite', 'Diorite' }, -- 	type = 'Crafting', -- 	variantPages = { -- 		'Andesite', 'Banner', 'Bed', 'Diorite', 'Firework Star', 'Granite', -- 		'Pressure Plate', 'Sand', 'Sandstone', 'Shield', 'Slab', 'Stained Glass Pane', -- 		'Stained Glass', 'Stairs', 'Stone Bricks', 'Wood Planks', 'Wood', 'Wool', -- 	}, -- } -- p.i18n = i18n

local cArgVals = { 'A1', 'B1', 'C1', 'A2', 'B2', 'C2', 'A3', 'B3', 'C3' } local cArgValsT = { 'A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3' } p.cArgVals = cArgVals

local function namedkeys(t) local temp = {} for k, v in pairs(t) do		if type(k) ~= 'number' then table.push(temp, k)		end end return temp end

local controlKeys = {'id'} local function templateReplacements(values, default) local tb, allFields = {}, {} if type(values) ~= 'table' then values = { values } end table.merge(allFields, table.filter(namedkeys(default), function(v) return not table.findIndex(controlKeys, v)	end)) local g = { id = values.id } for _, v in ipairs(allFields) do g[v] = default[v] end for _, item in ipairs(values) do -- ipairs: won't read global settings local repLs = {} local function repl(str) local function _repl(args) for _, sym in ipairs(args) do					str = str:gsub(('{%s}'):format(sym), repLs[sym] or '') end return str end -- Apply all replacements local special, numeric = string.split('os', ''), {} local ret = _repl(special) -- special(1st) for i in ret:gmatch('{(%d+)}') do				table.push(numeric, i)			end local ret = _repl(numeric) -- numeric after special(1st) local ret = _repl(special) -- special(2nd) after numeric return ret end local loc = { default[1] } local raw, id, vars if type(item) == 'table' then raw = item[1] id = item.id or g.id or item[1] vars = table.slice(item, 2) for _, v in ipairs(table.merge({}, namedkeys(item), allFields)) do loc[v] = item[v] or g[v] end else raw, id = item, g.id or item for _, v in ipairs(allFields) do loc[v] = g[v] end end for i,v in ipairs(type(vars) == 'table' and vars or { vars }) do			repLs[tostring(i-1)] = tostring(v):gsub('%%','%%%%') -- so that it does not incorrectly remove '%'s		end repLs['o'], repLs['s'] = raw, raw id = repl(id) repLs['s'] = id		for k, v in pairs(loc) do			-- note: __NIL__ is to override a certain value with key existing in template with the nil value loc[k] = v ~= '__NIL__' and repl(v) or nil end tb[id] = loc end return tb end

local craftingData function p.getData if craftingData and #craftingData > 0 then return craftingData else local data, templates = loader.loadData('Crafting/Data', 'Crafting/Templates') data, templates = table.deepCopy(data, true), table.deepCopy(templates, true) local templatesTable = {} -- Process Data from Crafting/Templates -- local MAX_RUNS = 20 local allow_continue, made_change, currentProcess, templateStorage, installed, runs = true, false, 1, {}, {}, 0 while allow_continue and ((currentProcess == 1) or (table.length(templateStorage) > 0)) do			if (currentProcess > 1) and (not made_change) then allow_continue = false end runs, made_change = runs + 1, false if runs > MAX_RUNS then error('Too many iterations in processing templates') end for key, values in pairs(currentProcess == 1 and templates or templateStorage) do				local default = data[key] if not default then if allow_continue then templateStorage[key] = values else error('Key "'..key..'" no default') end else made_change = true table.push(installed, key) table.merge(templatesTable, templateReplacements(values, default)) end end for _, key in ipairs(installed) do				templateStorage[key] = nil end currentProcess, installed = currentProcess + 1, {} end craftingData = table.merge({}, templatesTable, data) return craftingData end end

-- function p.table( frame ) -- 	local args = getArgs(frame) -- 	-- Automatic shapeless positioning -- 	if args[1] then -- 		args.shapeless = 1 -- 		if args[7] then -- 			args.A1 = args[1] -- 			args.B1 = args[2] -- 			args.C1 = args[3] -- 			args.A2 = args[4] -- 			args.B2 = args[5] -- 			args.C2 = args[6] -- 			if args[8] then -- 				-- ◼◼◼     ◼◼◼ -- 				-- ◼◼◼  OR  ◼◼◼ -- 				-- ◼◼◼     ◼◼◻ -- 				args.A3 = args[7] -- 				args.B3 = args[8] -- 				args.C3 = args[9] -- 				if args[9] then -- 					local identical = true -- 					for i = 1, 8 do -- 						if args[i] ~= args[i + 1] then -- 							identical = false -- 							break -- 						end -- 					end -- 					if identical then -- 						args.shapeless = nil -- 					end -- 				end -- 			else -- 				-- ◼◼◼ -- 				-- ◼◼◼ -- 				-- ◻◼◻ -- 				args.B3 = args[7] -- 			end -- 		elseif args[2] then -- 			args.A2 = args[1] -- 			args.B2 = args[2] -- 			if args[5] then -- 				-- ◻◻◻     ◻◻◻ -- 				-- ◼◼◼  OR  ◼◼◼ -- 				-- ◼◼◼     ◼◼◻ -- 				args.C2 = args[3] -- 				args.A3 = args[4] -- 				args.B3 = args[5] -- 				args.C3 = args[6] -- 			elseif args[4] then -- 				-- ◻◻◻ -- 				-- ◼◼◻ -- 				-- ◼◼◻ -- 				args.A3 = args[3] -- 				args.B3 = args[4] -- 			else -- 				-- ◻◻◻     ◻◻◻ -- 				-- ◼◼◻  OR  ◼◼◻ -- 				-- ◻◼◻     ◻◻◻ -- 				args.B3 = args[3] -- 			end -- 		else -- 			-- ◻◻◻ -- 			-- ◻◼◻ -- 			-- ◻◻◻ -- 			args.B2 = args[1] -- 			args.shapeless = nil -- 		end -- 		for i = 1, 9 do -- 			args[i] = nil -- 		end -- 	end -- 	-- Create recipe table, and list of ingredients -- 	local out, ingredientSets = recipeTable( args, { -- 		uiFunc = 'craftingTable', -- 		type = i18n.type, -- 		ingredientArgs = cArgVals, -- 		outputArgs = { 'Output' }, -- 	} ) -- 	local title = mw.title.getCurrentTitle -- 	if args.nocat == '1' or title.namespace ~= 0 or title.isSubpage then -- 		return out -- 	end -- 	local categories = {} -- 	local cI = 1 -- 	if args.upcoming then -- 		categories[cI] =  .. i18n.categoryUpcoming ..  -- 		cI = cI + 1 -- 	end -- 	if args.type then -- 		categories[cI] =  .. i18n.categoryRecipeType:gsub( '%$1', args.type ) ..  -- 		cI = cI + 1 -- 	end -- 	if args.ignoreusage ~= '1' then -- 		-- Create ingredient categories for DPL -- 		local usedNames = {} -- 		for _, ingredientSet in pairs( ingredientSets ) do -- 			for _, ingredient in pairs( ingredientSet ) do -- 				local name = ingredient.name -- 				if not ingredient.mod and not usedNames[name] and name ~= title.text then -- 					-- List each dye individually as they have their own pages -- 					if -- 						name == slot.i18n.prefixes.any .. ' ' .. i18n.itemDye or -- 						name == slot.i18n.prefixes.matching .. ' ' .. i18n.itemDye or -- 						name == slot.i18n.prefixes.any .. ' ' .. i18n.itemColoredDye or -- 						name == slot.i18n.prefixes.matching .. ' ' .. i18n.itemColoredDye -- 					then -- 						if not name:find( i18n.colored ) then -- 							categories[cI] =  .. i18n.categoryIngredientUsage:gsub( '%$1', i18n.itemBoneMeal ) ..  -- 							cI = cI + 1 -- 							usedNames[i18n.itemBoneMeal] = true -- 						end -- 						for _, dye in pairs( i18n.coloredDyes ) do -- 							categories[cI] =  .. i18n.categoryIngredientUsage:gsub( '%$1', dye ) ..  -- 							cI = cI + 1 -- 							usedNames[dye] = true -- 						end -- 					-- List stone variants individually as they have their own pages -- 					elseif -- 						name == slot.i18n.prefixes.any .. ' ' .. i18n.itemStone or -- 						name == slot.i18n.prefixes.matching .. ' ' .. i18n.itemStone -- 					then -- 						for _, stone in pairs( i18n.stoneVariants ) do -- 							categories[cI] =  .. i18n.categoryIngredientUsage:gsub( '%$1', stone ) ..  -- 							cI = cI + 1 -- 							usedNames[stone] = true -- 						end -- 					else -- 						-- Merge item variants which use a single page -- 						if -- 							name == slot.i18n.prefixes.any .. ' ' .. i18n.itemMushroom or -- 							name == slot.i18n.prefixes.matching .. ' ' .. i18n.itemMushroom or -- 							name == i18n.itemRedMushroom or -- 							name == i18n.itemBrownMushroom -- 						then name = i18n.itemMushroom -- 						elseif name == i18n.itemCharcoal then name = i18n.itemCoal -- 						elseif name:find( ' ' .. i18n.itemQuartzBlock .. '$' ) then name = i18n.itemBlockOfQuartz -- 						else -- 							for _, variant in pairs( i18n.variantPages ) do -- 								if name:find( ' ' .. variant .. '$' ) then -- 									name = variant -- 									break -- 								end -- 							end -- 							-- Remove prefixes -- 							for _, prefix in pairs( slot.i18n.prefixes ) do -- 								if name:find( '^' .. prefix .. ' ' ) then -- 									name = name:gsub( '^' .. prefix .. ' ', '' ) -- 									break -- 								end -- 							end -- 						end -- 						if not usedNames[name] then -- 							categories[cI] =  .. i18n.categoryIngredientUsage:gsub( '%$1', name ) ..  -- 							cI = cI + 1 -- 							usedNames[name] = true -- 						end -- 					end -- 				end -- 			end -- 		end -- 	end -- 	return out, table.concat( categories, '' ) -- end

function p.addSlot(args, item, prefix, class, default) local none, nostacksize prefix = prefix or '' if #prefix == 0 then none = 'none' nostacksize = ((item ==  or type(item) == 'nil') and ) or (args and args[item] and args[item]:gsub('[,%d]', ) or ) end return slot.slot{ nostacksize or args[item], mod = args.Mod, link = none or args[prefix .. 'link'], title = none or args[prefix .. 'title'], class = class, default = default, parsed = args.parsed, forcenum = args.forcenum } end

function p.craftingGrid( frame ) local args = getArgs(frame) local replpttn = '[%s\'",;:\.]'	local collapse = yesno(args.collapse, false)	local out = args.Output or args['output']	local recipe = p.parseRecipe(args)	local grid = p._craftingGrid( recipe )	grid = (not collapse and out and string.wrapHtml(out, 'center') or ) ..		string.wrapHtml(grid, 'div', { class = "mcui mcui-Crafting_Table pixel-image" })	grid = string.wrapHtml(grid, 'div', { style = { display = 'inline-block' } })	local bzar	if yesno(args.bazaar, false) then		local t = {}		table.each(cArgVals, function(v)			if args[v] then table.push(t, args[v]) end		end)		bzar = ("Bazaar Material Cost: %s"):format(bazaar.calcMaterialBuyPrices(t))		bzar = string.wrapHtml(bzar, 'div')	end	grid = grid .. (bzar or )	if collapse then		local id = out and ('%s-table'):format(out:gsub(replpttn, '-'))		grid = ('%s\n%s'):format(element.collapsibleButton( {'▦ Recipe', id = id} ),			element.collapsible( {grid, id = id} ) )		grid = string.wrapHtml(grid, 'div')	end	if curTitle.namespace == 0 and (args.A1 or args.A2 or args.A3 or args.B1 or args.B2 or args.B3 or args.C1 or args.C2 or args.C3) then		return grid .. ''	end	return grid end

function p._craftingGrid(recipe) local grid = mw.html.create('table'):addClass('mcui-input') for num = 1, 3 do		local row = grid:tag('tr'):addClass('mcui-row') for _, letter in ipairs{ 'A', 'B', 'C' } do			local td = row:tag('td') td:wikitext(p.addSlot(recipe, letter .. num, 'I')) end end return tostring(grid) end

-- "Version change": performing a matrix transpose to the recipe table function p.vrchg(t) local temp = {} for i = 1, 9 do		temp[cArgVals[i]] = t[cArgValsT[i]] end for _, v in ipairs(cArgVals) do		t[v] = temp[v] end return t end

function p.parseRecipe(args) -- method 3 (lowest priority): from database; always at ver=2 syntax -- method 2: input using the Quick Recipe Syntax -- method 1 (highest priority): use normal args as override local function subprocess(query) local t = p.getData[query], true if not t then error('Crafting recipe not found: "' .. query .. '"') end t = type(t) == 'table' and t or { t } t = table.merge(t, p.vrchg(p._parseRecipe(t[1], query)), { Output = t['Output'] }) return t	end local ret, recipe = {}, {} local qrs = args.qrs and p._parseRecipe(args.qrs) or {} local toprocess = mergeArgsSyntax(args) if #toprocess > 1 then local alldata = table.map(toprocess, subprocess) -- only performs on positional arguments local todo = table.Set(table.merge({}, cArgVals, unpack(table.map(alldata, function(data) -- cArgVals must go before unpack return namedkeys(data) end)))):values for _, data in ipairs(alldata) do			for _, v in ipairs(todo) do				recipe[v] = table.push(recipe[v] or {}, data[v] or '') end end for k, v in pairs(recipe) do			recipe[k] = table.concat(v, ';') end elseif toprocess[1] and toprocess[1] ~= 'nil' then recipe = subprocess(toprocess[1]) end if tostring(args.ver) == '2' then -- for ver=1 (original): -- ROWS are indicated by '1' '2' '3' -- COLUMNS are indicated by 'A' 'B' 'C'			-- which can be less intuitive in some cases -- therefore, ver=2 is invented: -- ROWS are indicated by 'A' 'B' 'C'			-- COLUMNS are indicated by '1' '2' '3' -- if using ver=2 syntax, it must be explicitly specified, since here we revert it to ver1 for the table qrs, args = p.vrchg(qrs), p.vrchg(args) end local todo = table.Set(table.merge({}, namedkeys(args), namedkeys(qrs), namedkeys(recipe), cArgVals)):values for _, v in ipairs(todo) do		ret[v] = args[v] or qrs[v] or recipe[v] end return ret end

function p._parseRecipe(str, query) function err(reason) error(('Syntax error while trying to parse slot indicator "%s"%s'):format(str, reason and (' (%s)'):format(reason) or '')) end function parseSlot(str) -- returns unordered list of all slots indicated local args = string.split(str, '') local tb = {} local row = '' local flag = false for _, c in ipairs(args) do			if c == '*' then if row == '' or not flag then -- treat '*' as row selector row = 'ABC' flag = true else for _, r in ipairs(string.split(row, '')) do table.push(tb, r .. '1') table.push(tb, r .. '2') table.push(tb, r .. '3') end flag = false end elseif string.match(c, '[ABC]') then if flag then err('Unused column specifier') else row = c					flag = true end elseif string.match(c, '[123]') then if row == '' then err('Row number used before column specifier') else for _, r in ipairs(string.split(row, '')) do table.push(tb, r .. c)					end flag = false end else err('Illegal character') end end if flag then err('Unused column specifier') end return table.Set(tb):values end function each(substr) local args = string.matchAll(substr, '[ABC123*]+%s+".-"') local temp = {} for _, v in ipairs(args) do -- for each ingredient of that recipe v = v[1] local arg1, arg2 = string.match(v, '([ABC123*]+)%s+"(.-)"') for _, w in ipairs(parseSlot(arg1)) do -- for each slot placement of that ingredient temp[w] = arg2 end end return temp end local ret = {} local splited = string.split(str, '%s*//%s*') if table.length(splited) > 1 then for i, substr in ipairs(splited) do -- for each recipe (animated slot) local temp = each(substr) for _, v in ipairs(cArgVals) do				ret[v] = table.push(ret[v] or {}, temp[v] or '') end ret['Output'] = table.push(ret['Output'] or {}, query) end for k, v in pairs(ret) do			ret[k] = table.concat(v, ';') end else ret = each(str) ret['Output'] = query end return ret end

return p