Module:Recipes

local item_link = require('Module:Item').go local trim = mw.text.trim local cargo = mw.ext.cargo

local currentFrame -- global cache for current frame object.

local itemLink = function(name, args) local args = args or {} args[1] = name args['small'] = 'y'	return item_link(currentFrame, args) end

-- credit: http://richard.warburton.it -- this version is with trim. local explode = function(div,str) if (div=='') then return false end local pos,arr = 0,{} -- for each divider found for st,sp in function return string.find(str,div,pos,true) end do		table.insert(arr, trim(string.sub(str,pos,st-1))) -- Attach chars left of current divider pos = sp + 1 -- Jump past current divider end table.insert(arr, trim(string.sub(str,pos))) -- Attach chars right of last divider return arr end

-- retuan a array of itemname, split xxx/yyy to item1=xxx, item2=yyy. If it's something like "Lead/Iron Bar", it will normalize as item1 = Iron Bar, item2 = Lead Bar. local split = (function	local metals = {		['Copper/Tin'] = 1,		['Silver/Tungsten'] = 1,		['Gold/Platinum'] = 1,		['Iron/Lead'] = 1,		['Cobalt/Palladium'] = 1,		['Mythril/Orichalcum'] = 1,		['Adamantite/Titanium'] = 1,		['Tin/Copper'] = 2,		['Tungsten/Silver'] = 2,		['Platinum/Gold'] = 2,		['Lead/Iron'] = 2,		['Palladium/Cobalt'] = 2,		['Orichalcum/Mythril'] = 2,		['Titanium/Adamantite'] = 2,	}	return function(name)		local count = select(2, name:gsub("/", "/", 2))		if count == 0 then			-- only 1 item			return { trim(name) }		elseif count == 1 then			-- 2 items			local item1a, item1b, item2a, item2b = name:match("^%s*(%S+)%s*(.-)/%s*(%S+)%s*(.-)$")			local x = metals[item1a..'/'..item2a]			if tostring(item1b) == '' and x then				item1b = item2b			end			if x == 2 then				return {trim(item2a..' '..item2b), trim(item1a..' '..item1b)}			else				return {trim(item1a..' '..item1b), trim(item2a..' '..item2b)} end else -- 3 or more items return explode('/', name) end end end)

-- return 1 or 2 value(s), when input is name[note], return item, note. local itemname = function(str) local item, note = str:match("^(.-)(%b[])$") if item then return item, note else return str end end

-- normalize ingredient name input, Lead Bar=>¦Lead Bar¦, Iron/Lead Bar => ¦Iron Bar¦Lead Bar¦, Lead/Iron Bar => ¦Iron Bar¦Lead Bar¦ .... local normalize = function(name) local result = '¦' for k, v in ipairs(split(name)) do result = result .. itemname(v) .. '¦'	end return result end

local escape = function(str) return str:gsub("'", "\\'"):gsub("&#39;", "\\'") end local enclose = function(str) return "'" .. escape(str) .. "'" end

local getItemGroupName = function(item) if item == 'Wood' or item == 'Ebonwood' or item == 'Rich Mahogany' or item == 'Pearlwood' or item == 'Shadewood' or item == 'Spooky Wood' or item == 'Boreal Wood' or item == 'Palm Wood' then return 'Any Wood' elseif item == 'Iron Bar' or item == 'Lead Bar' then return 'Any Iron Bar' elseif item == 'Sand Block' or item == 'Pearlsand Block' or item == 'Crimsand Block' or item == 'Ebonsand Block' or item == 'Hardened Sand Block' then return 'Any Sand' elseif item == 'Red Pressure Plate' or item == 'Green Pressure Plate' or item == 'Gray Pressure Plate' or item == 'Brown Pressure Plate' or item == 'Blue Pressure Plate' or item == 'Yellow Pressure Plate' or item == 'Lihzahrd Pressure Plate' then return 'Any Pressure Plate' elseif item == 'Bird' or item == 'Blue Jay' or item == 'Cardinal' then return 'Any Bird' elseif item == 'Black Scorpion' or item == 'Scorpion' then return 'Any Scorpion' elseif item == 'Squirrel' or item == 'Red Squirrel' then return 'Any Squirrel' elseif item == 'Grubby' or item == 'Sluggy' or item == 'Buggy' then return 'Any Bug' elseif item == 'Mallard Duck' or item == 'Duck' then return 'Any Duck' elseif item == 'Sulphur Butterfly' or item == 'Julia Butterfly' or item == 'Monarch Butterfly' or item == 'Purple Emperor Butterfly' or item == 'Red Admiral Butterfly' or item == 'Tree Nymph Butterfly' or item == 'Ulysses Butterfly' or item == 'Zebra Swallowtail Butterfly' then return 'Any Butterfly' elseif item == 'Firefly' or item == 'Lightning Bug' then return 'Any Firefly' elseif item == 'Snail' or item == 'Glowing Snail' then return 'Any Snail' end end

local normalizeStation = function(station) if station == 'Altar' then station = 'Demon Altar' end return station end

local criStr = function(args) local constraints = {} -- station = ? and station != ? local _station = trim(args['station'] or '') local _stationnot = trim(args['stationnot'] or '') local str = '' if _station ~= '' then for _, v in ipairs(explode('/', _station)) do			if str ~= '' then str = str .. ' OR ' end str = str .. "station = " .. enclose(normalizeStation(v)) end end if _stationnot ~= '' then if str ~= '' then str = '(' .. str .. ')' end for _, v in ipairs(explode('/', _stationnot)) do			if str ~= '' then str = str .. ' AND ' end str = str .. 'station <> ' .. enclose(normalizeStation(v)) end end constraints['station'] = str -- result = ? and result != ? local _result = trim(args['result'] or '') local _resultnot = trim(args['resultnot'] or '') local str = '' if _result ~= '' then for _, v in ipairs(explode('/', _result)) do			if str ~= '' then str = str .. ' OR ' end if mw.ustring.sub(v, 1, 5) == 'LIKE ' then str = str .. "result LIKE " .. enclose(trim(mw.ustring.sub(v, 6))) else str = str .. 'result=' .. enclose(v) end end end if _resultnot ~= '' then if str ~= '' then str = '(' .. str .. ')' end for _, v in ipairs(explode('/', _resultnot)) do			if str ~= '' then str = str .. ' AND ' end if mw.ustring.sub(v, 1, 5) == 'LIKE ' then str = str .. "result NOT LIKE " .. enclose(trim(mw.ustring.sub(v, 6))) else str = str .. 'result <> ' .. enclose(v) end end end if str ~= '' then constraints['result'] = str end -- ingredient = ? local _ingredient = trim(args['ingredient'] or '') if _ingredient ~= '' then local str = '' for _, v in ipairs(explode('/', _ingredient)) do			if str ~= '' then str = str .. ' OR ' end if mw.ustring.sub(v, 1, 1) == '#' then str = str .. "ingredients HOLDS '¦" .. escape(mw.ustring.sub(v, 2)) .. "¦'"			elseif mw.ustring.sub(v, 1, 5) == 'LIKE ' then str = str .. "ingredients HOLDS LIKE '¦" .. escape(trim(mw.ustring.sub(v, 6))) .. "¦'"			else str = str .. "ingredients HOLDS '¦" .. escape(v) .. "¦'"				-- any xxx local group = getItemGroupName(v) if group then str = str .. " OR ingredients HOLDS '¦" .. escape(group) .. "¦'"				end end end constraints['ingredient'] = str end

local where = '' if constraints['station'] then where = constraints['station'] end if constraints['result'] then if where ~= '' then where = where .. ' AND ' end where = where .. '(' .. constraints['result'] .. ')' end if constraints['ingredient'] then if where ~= '' then where = where .. ' AND ' end where = where .. '(' .. constraints['ingredient'] .. ')' end return where end

local resultCell = function(row, showResultId) local result, resultid, resultimage, resulttext, amount, version = row['result'], row['resultid'], row['resultimage'], row['resulttext'], row['amount'], row['version'] local str = '' if version ~= '' then str = str .. '(' ..currentFrame:expandTemplate{ title = 'version icons', args = {version} }..') ' end local args = {} if showResultId then args['id'] = resultid end if resultimage then args['image'] = resultimage end if resulttext then args[2] = resulttext end str = str .. itemLink(result, args) if amount ~= '1' then str = str .. ' ('..amount..') ' end return str end

local ingredientsCell = function(args) local str = '' for _, v in ipairs(explode('^', args)) do str = str .. '' local item, amount = v:match('^(.-)¦(.-)$') local s		for _, itemname in ipairs(split(item)) do			if s then s = s .. " or " .. itemLink(itemname) else s = itemLink(itemname) end end str = str .. s		if amount ~= '1' then str = str .. ' ('..amount..') ' end str = str .. '' end str = str .. '' return str end

local stationCell = function(station) if station == 'By Hand' then return 'By Hand' elseif station == 'Furnace' or station == 'Work Bench' or station == 'Sawmill' or station == "Tinkerer's Workshop" or station == 'Dye Vat' or station == 'Loom' or station == 'Keg' or station == 'Hellforge' or station == 'Bookcase' or station == 'Imbuing Station' or station == 'Lava' or station == 'Honey' or station == 'Glass Kiln' or station == 'Flesh Cloning Vat' or station == 'Autohammer' or station == 'Crystal Ball' or station == 'Ice Machine' or station == 'Meat Grinder' or station == 'Living Loom' or station == 'Heavy Work Bench' or station == 'Sky Mill' or station == 'Solidifier' or station == 'Honey Dispenser' or station == 'Bone Welder' or station == 'Blend-O-Matic' or station == 'Steampunk Boiler' or station == 'Ancient Manipulator' or station == 'Lihzahrd Furnace' then return itemLink(station, {wrap = 'y'}) elseif station == 'Iron Anvil' then return itemLink('Iron Anvil') .. " or " .. itemLink('Lead Anvil') elseif station == 'Adamantite Forge' then return itemLink('Adamantite Forge') .. " or " .. itemLink('Titanium Forge') elseif station == 'Mythril Anvil' then return itemLink('Mythril Anvil') .. " or " .. itemLink('Orichalcum Anvil') elseif station == 'Demon Altar' then return itemLink('Demon Altar') .. " or " .. itemLink('Crimson Altar') elseif station == 'Cooking Pot' then return itemLink('Cooking Pot') .. " or " .. itemLink('Cauldron') elseif station == 'Placed Bottle' then return itemLink('Placed Bottle') .. " or " .. itemLink('Alchemy Table', {wrap = 'y'}) elseif station == 'Water' then return itemLink('Water') .. " or " .. itemLink('Sink', {wrap = 'y'}) elseif station == 'Table and Chair' then return itemLink('Table') .. " and " .. itemLink('Chair') elseif station == 'Work Bench and Chair' then return itemLink('Work Bench') .. " and " .. itemLink('Chair') elseif station == 'Crystal Ball and Lava' then return itemLink('Crystal Ball') .. " and " .. itemLink('Lava') elseif station == 'Crystal Ball and Honey' then return itemLink('Crystal Ball') .. " and " .. itemLink('Honey') elseif station == 'Crystal Ball and Water' then return itemLink('Crystal Ball') .. " and ".. ' ' .. itemLink('Water') .. " or " .. itemLink('Sink', {wrap = 'y'}) .. ' '	elseif station == 'Sky Mill and Water' then return itemLink('Sky Mill', {wrap = 'y'}) .. " and ".. ' ' .. itemLink('Water') .. " or " .. itemLink('Sink', {wrap = 'y'}) .. ' '	elseif station == 'Sky Mill and Snow Biome' then return itemLink('Sky Mill', {wrap = 'y'}) .. " and ".. 'Snow Biome' elseif station == 'Placed Bottle only' then return itemLink('Placed Bottle') else return station end end

local getCateFlag = function(args) local needCate = 1 local _cate = trim(args['cate'] or '') if _cate == 'force' or _cate == 'all' then needCate = 2 elseif _cate == 'n' or _cate == 'no' then needCate = nil end return needCate end

local addCate, cateStr = (function 	local cate = {		['Lead Anvil'] = 'Iron or Lead Anvil',		['Iron Anvil'] = 'Iron or Lead Anvil',		['Orichalcum Anvil'] = 'Mythril or Orichalcum Anvil',		['Mythril Anvil'] = 'Mythril or Orichalcum Anvil',		['Demon Altar'] = 'Demon or Crimson Altar',		['Crimson Altar'] = 'Demon or Crimson Altar',		['Altar'] = 'Demon or Crimson Altar',		['Titanium Forge'] = 'Adamantite or Titanium Forge',		['Adamantite Forge'] = 'Adamantite or Titanium Forge',		['Cauldron'] = 'Cooking Pot or Cauldron',		['Cooking Pot'] = 'Cooking Pot or Cauldron',		['Bottle only'] = 'Placed Bottle',		['Placed Bottle only'] = 'Placed Bottle',		['Bottle'] = 'Placed Bottle or Alchemy Table',		['Placed Bottle'] = 'Placed Bottle or Alchemy Table',		['Alchemy Table'] = 'Placed Bottle or Alchemy Table',		['Water'] = 'Water or Sink',		['Sink'] = 'Water or Sink',		['Crystal Ball and Water'] = 'Crystal Ball and Water or Sink', ['Sky Mill and Water'] = 'Sky Mill Ball and Water or Sink', }	local cateCache = {} local addCate = function(station) cateCache[station] = true end local cateStr = function local str = '' for station, _ in pairs(cateCache) do str = str .. ''		end if str ~= '' then str = '' .. str end return str end return addCate, cateStr end)

local tableStart = function(title, withStation) local str = '  total: '..rows_count..' row(s)   ' if expectedrows and rows_count ~= expectedrows then str = str .. ''	end if rows_count == 0 then str = str .. ''	end return str end

local tableWithoutStation = function(result, showResultId, needCate, rootpagename, title, expectedrows) local str = tableStart(title, false) local rows_count = 0 for _, row in ipairs(result) do			rows_count = rows_count + 1 -- table row: str = str .. ' '.. resultCell(row, showResultId).. ' ' .. ingredientsCell(row['args']).. ' '			-- cate: if needCate then if needCate == 2 or rootpagename == row['result'] then addCate(row['station']) end end end str = str .. tableEnd(rows_count, expectedrows) if needCate then str = str .. cateStr end return str end

local tableWithStation = function(result, showResultId, needCate, rootpagename, title, expectedrows) local str = tableStart(title, true) local current_station local station_count local rows_count = 0 for _, row in ipairs(result) do			rows_count = rows_count + 1 -- table row: str = str .. ' '.. resultCell(row, showResultId).. ' ' .. ingredientsCell(row['args']).. ' '			local station = row['station'] if current_station == station then -- is same group ?? station_count = station_count + 1 else --new group: -- rowspan value for prev group, if needed. if station_count then str = str:gsub("xxxrowspanxxx", tostring(station_count)) end -- begin this group current_station = station station_count = 1 str = str .. ''.. stationCell(station) ..' ' end str = str .. ' '			-- cate: if needCate then if needCate == 2 or rootpagename == row['result'] then addCate(row['station']) end end end -- rowspan value for last group if station_count then str = str:gsub("xxxrowspanxxx", tostring(station_count)) end str = str .. tableEnd(rows_count, expectedrows) if needCate then str = str .. cateStr end return str end -

local p = {}

-- for p.register = function(frame) local args = frame:getParent.args

-- 	local ingredients = {} -- list of {index, itemname, amount} for k, v in pairs(args) do		if(type(k) == 'number') then if k % 2 == 1 then -- 2n-1, nth item local index, item, amount = (k+1)/2, trim(v), trim(args[k+1]) ingredients[index] = {item, amount} end end end

local serialized = '' -- serialized ingredients list for _, v in ipairs(ingredients) do serialized = serialized .. '^' .. v[1] .. '¦' .. v[2] end serialized = mw.ustring.sub(serialized, 2)

table.sort(ingredients, function(a, b) return a[1] < b[1] end) -- sort by ingredient item name local ingredients_string = '' local ingredients_string_full = '' for _, v in ipairs(ingredients) do		local name, amount = unpack(v) local ingstr = normalize(name) ingredients_string = ingredients_string .. '^' .. ingstr ingredients_string_full = ingredients_string_full .. '^' .. ingstr .. amount end

--, normalize local _version = trim(args['version'] or ''):lower local version = '' if _version:find('desktop', 1, true) then version = version .. ' desktop' end if _version:find('console', 1, true) then version = version .. ' console' end if _version:find('old-gen', 1, true) then version = version .. ' old-gen' end if _version:find('japan', 1, true) then version = version .. ' japan' end if _version:find('mobile', 1, true) then version = version .. ' mobile' end if _version:find('3ds', 1, true) then version = version .. ' 3ds' end if version == ' desktop console old-gen mobile 3ds' then version = '' end

--store frame:callParserFunction('#cargo_store:_table=newRecipes',{		result = trim(args['result'] or ),		resultid = trim(args['resultid'] or ),		resultimage = trim(args['image'] or ),		resulttext = trim(args['text'] or ),		amount = trim(args['amount'] or ),		version = version,		station = normalizeStation(trim(args['station'] or )), 		ingredients = mw.ustring.sub(ingredients_string, 2),		ings = mw.ustring.sub(ingredients_string_full, 2),		args = serialized,	}) end -- p.register

-- for p.query = function(frame) currentFrame = frame -- global frame cache local args = frame:getParent.args

local where = trim(args['where'] or '') if where == '' then where = criStr(args) end

-- no constraint no result. if where == '' then return 'No constraint' end

-- format: local needCate = getCateFlag(args) local showResultId = false if trim(args['showresultid'] or ) ~=  then showResultId = true end local _title = trim(args['title'] or '') local _expectedrows = trim(args['expectedrows'] or '') if _expectedrows ~= '' then _expectedrows = tonumber(_expectedrows) else _expectedrows = nil end local rootpagename = mw.title.getCurrentTitle.rootText if trim(args['nostation'] or ) ~=  then -- no station -- query, still need contain station field for cate. local result = mw.ext.cargo.query('newRecipes', 'result, resultid, resultimage, resulttext, amount, version, station, args', {			where = where,			groupBy = "resultid, result, amount, version, ings",			orderBy = "result, amount DESC, version", -- Don't order by station			limit = 2000,		}) return tableWithoutStation(result, showResultId, needCate, rootpagename, _title, _expectedrows) else -- with station -- query local result = mw.ext.cargo.query('newRecipes', 'result, resultid, resultimage, resulttext, amount, version, station, args', {			where = where,			groupBy = "resultid, result, amount, ings, version",			orderBy = "station, result, amount DESC, version, ings", -- order by station first for station grouping.			limit = 2000,		}) return tableWithStation(result, showResultId, needCate, rootpagename, _title, _expectedrows) end end -- p.query

return p