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, recipeTable, bazaar, element, arguments = loader.require('String', 'Table', 'Yesno', 'Inventory slot', 'Recipe table', 'Bazaar', 'Element', 'Arguments') local craftingData = loader.loadData('Crafting/Data')

local getArgs, mergeArgsSyntax = arguments.getArgs, arguments.mergeArgsSyntax local recipeTable = recipeTable.table local curTitle = mw.title.getCurrentTitle

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

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

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 = table.deepCopy(craftingData[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