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