Toggle menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Module:Crafting usage

From Mine in Abyss
Revision as of 14:51, 3 October 2024 by minecraft>ThermoF (Undo revision 2706568 by ThermoF (talk))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This module implements {{crafting usage}}.

Dependencies


cs:Modul:Crafting usage es:Módulo:Crafting usage fr:Module:Utilisation fabrication it:Modulo:Utilizzo nella fabbricazione ja:モジュール:Crafting usage ko:모듈:Crafting usage lzh:模組:Crafting usage nl:Module:Vervaardigingsgebruik pt:Módulo:Crafting usage ru:Модуль:Использование для крафта th:มอดูล:Crafting usage uk:Модуль:Використання для майстрування zh:Module:Crafting usage



--[[
Test page listing important test cases, used to preview before saving any changes.
Template:Crafting_usage/test_page
--]]

local p = {}
local titleText = mw.title.getCurrentTitle().text

local i18n = {
	emptyCategory = 'Empty crafting usage',
	moduleCrafting = [[Module:Crafting]],
	moduleSlot = [[Module:Inventory slot]],
	moduleAliases = [[Module:Inventory slot/Aliases]],
	moduleText = [[Module:Text]],
	templateCrafting = 'Crafting',
}
p.i18n = i18n

local text = require(i18n.moduleText)
local slot = require(i18n.moduleSlot)
local aliases = require(i18n.moduleAliases)
local crafting = require(i18n.moduleCrafting)

local function filterFrames(craftingArgs, pinnedItems)
	-- Create a lookup table to easily determine if a string is a member of pinned items.
	local pinnedItemsLookup = {}
	for _, entry in pairs(pinnedItems) do
		pinnedItemsLookup[entry] = true	
	end
	
	-- Find any frames in this string that need to be pinned and pin them.
	local outputPinnedFrameCount = {}
	local shouldPinOutput = false
	local function pinFrames(inputString, restrictToMatching, isOutput)
		if not inputString then return nil end
		isOutput = isOutput or false
		
		local pinFrames = {} -- Only frames that are pinned
		local pinnedFrameCount = 0
		local pinnedFramesIndex = {}
		local allFrames = {} -- All input frames
		local allFrameCount = 0
		local frameCount = 0 -- Count how many expanded frames we are in our iteration. Used for restricting output frames to match input frames
		
		for frameStr in string.gmatch(inputString, "[^;]+") do
			local shouldPin = false
			frameStr = mw.text.trim(frameStr)
			local originalFrameString = frameStr
			local frameObj = slot.makeFrame(frameStr, '')
			local name = (frameObj.name:gsub("[{}]", "")) -- If this name is a subframe, remove the braces for alias testing.
			local subframe = (frameObj.name:find("[{}]") ~= nil) -- If the current frame is a subframe, don't advance the frame count for aliases
			local expandedAlias = aliases[name]
			local isAlias = (expandedAlias and expandedAlias[1]) ~= nil
			local isDesiredItem = (pinnedItemsLookup[name] ~= nil) -- If this item is present in our list of items to pin
			
			-- Even if we have an exactly matching alias input, we have to step through each frame to log it as pinnable.
			if isAlias == true and isDesiredItem then
				shouldPin = true -- Set this frame as adding to the list of pinned frames
				if not subframe then
					for _,_ in pairs(expandedAlias) do
						frameCount = frameCount + 1
						pinnedFramesIndex[frameCount] = true
					end
				else
					frameCount = frameCount + 1
					pinnedFramesIndex[frameCount] = true
				end
			elseif isAlias == false or subframe == true then
				frameCount = frameCount + 1
			end
			
			if isAlias -- If this is an alias
			and not isDesiredItem -- and we don't want to keep the alias as is
			and (not isOutput or (isOutput and restrictToMatching)) then -- and we are either not output, or we are output that needs to be restricted.
				local replacementString = {}
				for _,candidate in pairs(expandedAlias) do -- Find all pinned items that are part of this alias
					if not subframe then -- Subframes only actually count as one frame
						frameCount = frameCount + 1
					end

					candidate = candidate.name or candidate -- Sometimes alias returns a table of tables
					if pinnedItemsLookup[candidate] or (restrictToMatching and outputPinnedFrameCount[frameCount]) then
						local candidateOut = candidate
						if frameObj.num then
							candidateOut = candidateOut..','..frameObj.num	
						end
						table.insert(replacementString, candidateOut)
						pinnedFramesIndex[frameCount] = true -- When we find a good entry, store the number for use by Output
					end
				end
				if #replacementString > 0 then -- If this alias doesn't have any pinned items in it, leave it alone.
					if name:find("^" .. slot.i18n.prefixes.any .. " ") then
						-- Simple in place randomization
						for i = #replacementString, 2, -1 do
							local j = math.random(i)
							replacementString[i], replacementString[j] = replacementString[j], replacementString[i]
						end
					else -- If we modify anything that isn't an "Any" alias, force pin the output.
						shouldPinOutput = true
					end
					replacementString = table.concat(replacementString, ';')
					-- The gsub is to put a % in front of every non alphanumeric character in name, this escapes the name so special characters don't act as a pattern match.
					local findString = '('..name:gsub("(%W)", "%%%1")..',?%d*)' -- Also capture the quantity, since the replacement string has the quantity attached.
					frameStr = frameStr:gsub(findString, replacementString, 1) -- Replace the alias name in the frame with the expanded and filtered string
					shouldPin = true -- If we match with an expanded alias then we have to pin it
				end
			end
			
			-- Save modified frame to proper location
			if isDesiredItem or shouldPin or (restrictToMatching and outputPinnedFrameCount[frameCount]) then
				table.insert(pinFrames, frameStr)
				pinnedFrameCount = pinnedFrameCount + frameCount
				-- Don't pin an alias, unless its an exact match
				if not isOutput and (not isAlias and isDesiredItem) then
					pinnedFramesIndex[frameCount] = true -- When we find a good entry, store the number for use by Output
				end
			end
			table.insert(allFrames, frameStr)
			allFrameCount = allFrameCount + frameCount
		end
		if pinnedFrameCount > 0 then
			if pinnedFrameCount < allFrameCount then
				shouldPinOutput = true
			end
			for k,v in pairs(pinnedFramesIndex) do
				outputPinnedFrameCount[k] = v
			end
			return table.concat(pinFrames, ';')
		else
			return table.concat(allFrames, ';')
		end
	end
	
	-- for A1, A2, A3, B1, B2, etc
	for _, arg in ipairs(crafting.cArgVals) do
		craftingArgs[arg] = pinFrames(craftingArgs[arg])
	end
	craftingArgs.Output = pinFrames(craftingArgs.Output, shouldPinOutput, true)
	
	return craftingArgs
end

--[[The main body, which retrieves the data, and returns the relevant
	crafting templates, sorted alphabetically
--]]
function p.dpl(f)
	local args = f
	if f == mw.getCurrentFrame() then
		args = f:getParent().args
	else
		f = mw.getCurrentFrame()
	end
	
	local startingIngredients = args[1] and text.split(args[1], '%s*,%s*') or {titleText}
	local seen = {}
	local ingredients = {}
	
	-- Loop through all defined ingredients, and expand any aliases.
	for _,entry in pairs(startingIngredients) do
		if not seen[entry] then
			table.insert(ingredients, entry)
			seen[entry] = true
			local expandedAlias = aliases[entry]

			if expandedAlias then
				for _,a in pairs(expandedAlias) do
					if not seen[a] then
						table.insert(ingredients, (a.name or a))
						seen[a] = true	
					end
				end
			end
		end
	end
	
	local showDescription
	local templates = {}
	
	local queryStrings = {}
	-- SMW can query up to 15 conditions at once, so group ingredients into 15
	local count = 1
	while count <= #ingredients do
		queryStrings[math.floor(count/15)+1] = table.concat(ingredients, '||', count, math.min(#ingredients, count + 14))
		count = count + 15
	end
	
	local seen = {}
	for _,str in ipairs(queryStrings) do
		local query = {
			'[[Crafting ingredient::'..str..']]',
			'?Crafting JSON',
			'?-Has subobject#-=Source page',
			limit = 500
		}
		local smwdata = mw.smw.ask(query)
		
		if smwdata then
			for _,v in ipairs(smwdata) do
				if v['Source page'] ~= titleText then -- Do not display results that came from the same page we are operating on, as they will be outdated.
					if type(v['Crafting JSON']) ~= "table" then --If a subobject name is not unique enough it will return as a table.
						if seen[v['Crafting JSON']] == nil then
							seen[v['Crafting JSON']] = 1
							local tArgs = mw.text.jsonDecode(v['Crafting JSON'])
							tArgs['ignoreusage'] = '1' -- Always set ignore usage for crafting invocations that are usage.
							if tArgs.description and tArgs.description ~= '' then
								showDescription = '1'
							end
							local newArgs = filterFrames(tArgs, startingIngredients)
							table.insert(templates,{args = newArgs,	sortKey = newArgs.Output or newArgs.name})
						end
					else
						mw.log("ERROR: query returned table.")
						mw.logObject(v['Crafting JSON'])
					end
				end
			end
		end
	end
	
	local templateCount = #templates
	if templateCount == 0 then
		if mw.title.getCurrentTitle().nsText == '' then
			return f:expandTemplate{title='Translation category', args={i18n.emptyCategory, project='0'}}
		end
		return ''
	end
	
	table.sort(templates, function(a, b)
		if not a then return b end
		if not b then return a end
		return a.sortKey < b.sortKey
	end)
	
	local initialArgs = templates[1].args
	initialArgs.head = '1'
	initialArgs.showname = '1'
	initialArgs.showdescription = showDescription
	if not args.continue then
		templates[templateCount].args.foot = '1'
	end
	
	local out = {}
	for i, template in ipairs(templates) do
		out[i] = crafting.table(template.args)
	end
	return table.concat(out, '\n')
end

return p