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

Module:Inventory slot: Difference between revisions

From Vault Hunters Official Wiki
Refactored module code to reduce expensive function calls.
Boost stack size to 1024
 
(6 intermediate revisions by the same user not shown)
Line 14: Line 14:
-- Dependencies
-- Dependencies
moduleAliases = [[Module:Inventory slot/Aliases]],
moduleAliases = [[Module:Inventory slot/Aliases]],
moduleModAliases = [[Module:Inventory slot/ModAliases]], -- New mod aliases module
moduleVHAliases = [[Module:Inventory slot/VHAliases]], -- New mod aliases module
moduleRandom = [[Module:Random]],
moduleRandom = [[Module:Random]],
Line 34: Line 34:


-- Lazy-loaded dependencies
-- Lazy-loaded dependencies
local random, aliases, modAliases
local random, aliases, VHAliases
local vanilla = { v = 1, vanilla = 1, mc = 1, minecraft = 1 }
local vanilla = { v = 1, vanilla = 1, mc = 1, minecraft = 1 }


Line 40: Line 40:
local function initDependencies()
local function initDependencies()
if not random then
if not random then
random = require(i18n.moduleRandom).random
local success, module = pcall(require, i18n.moduleRandom)
if success then
random = module.random
else
-- Fallback random function
random = function(max) return math.random(max) end
end
end
end
if not aliases then
if not aliases then
aliases = mw.loadData(i18n.moduleAliases)
local success, data = pcall(mw.loadData, i18n.moduleAliases)
if success then
aliases = data
else
aliases = {} -- Fallback empty table
end
end
end
end
 
if not VHAliases then
-- Load mod aliases only when needed
local success, data = pcall(mw.loadData, i18n.moduleVHAliases)
local function getModAliases()
if not modAliases then
local success, result = pcall(mw.loadData, i18n.moduleModAliases)
if success then
if success then
modAliases = result
VHAliases = data
else
else
modAliases = {} -- Empty table if module doesn't exist
VHAliases = {} -- Fallback empty table
end
end
end
end
return modAliases
end
end


Line 220: Line 228:
-- Add stack number
-- Add stack number
if num and num > 1 and num < 1000 then
if num and num > 1 and num < 1025 then
local numberSpan = item:tag('span')
local numberSpan = item:tag('span')
:addClass('invslot-stacksize')
:addClass('invslot-stacksize')
Line 239: Line 247:
end
end


-- Optimized alias lookup with caching
-- Fixed alias lookup - check both tables without distinguishing mod vs vanilla
local function getAliasFromCache(id, isModItem)
local function getAliasFromCache(id)
local cacheKey = (isModItem and 'mod:' or 'vanilla:') .. id
-- Simple cache key since we don't need to distinguish mod vs vanilla
if aliasCache[cacheKey] then
if aliasCache[id] ~= nil then
return aliasCache[cacheKey]
return aliasCache[id]
end
end
Line 249: Line 257:
local alias
local alias
if isModItem then
-- Check main aliases table first
alias = getModAliases()[id]
alias = aliases[id]
else
alias = aliases[id]
-- If not found, check VHAliases table
if not alias then
alias = VHAliases[id]
end
end
aliasCache[cacheKey] = alias or false -- Cache negative results too
aliasCache[id] = alias or false -- Cache negative results too
return alias
return alias
end
end
Line 266: Line 276:
end
end
-- Create cache key for identical calls
-- Prepare args and create cache key
local cacheKey = (args[1] or '') .. '|' .. (args.class or '') .. '|' .. (args.style or '') .. '|' .. (args.mod or '')
local frameText = ''
if not args.parsed then
frameText = (args[1] or ''):match("^%s*(.-)%s*$") -- trim
args[1] = frameText
else
-- For parsed args, create a simple cache key from table contents
if args[1] and type(args[1]) == 'table' then
frameText = 'parsed_' .. tostring(#args[1])
end
end
local cacheKey = frameText .. '|' .. (args.class or '') .. '|' .. (args.style or '') .. '|' .. (args.mod or '')
if frameCache[cacheKey] then
if frameCache[cacheKey] then
return frameCache[cacheKey]
return frameCache[cacheKey]
end
if not args.parsed then
args[1] = (args[1] or ''):match("^%s*(.-)%s*$") -- trim
end
end
Line 355: Line 372:
end
end


-- Optimized frame parsing
-- Fixed frame parsing with simplified alias lookup
function p.parseFrameText(framesText, randomise, aliasReference, modData)
function p.parseFrameText(framesText, randomise, aliasReference, modData)
local frames = { randomise = randomise }
local frames = { randomise = randomise }
Line 379: Line 396:
local frame = p.makeFrame(frameText, modData and modData.default)
local frame = p.makeFrame(frameText, modData and modData.default)
-- Alias processing (optimized)
-- Simplified alias processing - just check both tables
local newFrame = { frame }
local newFrame = { frame }
if frame.name and frame.name ~= '' then
if frame.name and frame.name ~= '' then
local isModItem = frame.mod and frame.mod ~= '' and not vanilla[frame.mod:lower()]
local alias = getAliasFromCache(frame.name)
local alias = getAliasFromCache(frame.name, isModItem)
if alias then
if alias then

Latest revision as of 03:17, 2 September 2025

This module implements {{Inventory slot}}.

Dependencies


local p = {}

-- Cache for this page load
local frameCache = {}
local aliasCache = {}
local fileExistsCache = {}

-- Internationalization data
local i18n = {
	filename = 'Invicon $1',
	legacyFilename = 'Grid $1',
	modLink = 'Mods/$1/$2',
	
	-- Dependencies
	moduleAliases = [[Module:Inventory slot/Aliases]],
	moduleVHAliases = [[Module:Inventory slot/VHAliases]], -- New mod aliases module
	moduleRandom = [[Module:Random]],
	
	prefixes = {
		any = 'Any',
		matching = 'Matching',
		damaged = 'Damaged',
		unwaxed = 'Unwaxed',
	},
	
	suffixes = {
		rev = 'Revision %d+',
		be = 'BE',
		lce = 'LCE',
		sm = 'SM',
	},
}
p.i18n = i18n

-- Lazy-loaded dependencies
local random, aliases, VHAliases
local vanilla = { v = 1, vanilla = 1, mc = 1, minecraft = 1 }

-- Initialize dependencies only when needed
local function initDependencies()
	if not random then
		local success, module = pcall(require, i18n.moduleRandom)
		if success then
			random = module.random
		else
			-- Fallback random function
			random = function(max) return math.random(max) end
		end
	end
	
	if not aliases then
		local success, data = pcall(mw.loadData, i18n.moduleAliases)
		if success then
			aliases = data
		else
			aliases = {} -- Fallback empty table
		end
	end
	
	if not VHAliases then
		local success, data = pcall(mw.loadData, i18n.moduleVHAliases)
		if success then
			VHAliases = data
		else
			VHAliases = {} -- Fallback empty table
		end
	end
end

-- Optimized file existence check with caching
local function fileExists(filename)
	if fileExistsCache[filename] ~= nil then
		return fileExistsCache[filename]
	end
	
	local title = mw.title.new(filename, 'File')
	local exists = title and title.fileExists
	fileExistsCache[filename] = exists
	return exists
end

-- Fast semicolon splitting (optimized version)
local function splitOnUnenclosedSemicolons(text)
	local semicolon, lbrace, rbrace = 59, 91, 93 -- ASCII values for ;[]
	local bracketDepth = 0
	local splitStart = 1
	local frames = {}
	local frameIndex = 1
	
	for index = 1, #text do
		local byte = text:byte(index)
		if byte == semicolon and bracketDepth == 0 then
			frames[frameIndex] = text:sub(splitStart, index - 1):match("^%s*(.-)%s*$")
			frameIndex = frameIndex + 1
			splitStart = index + 1
		elseif byte == lbrace then
			bracketDepth = bracketDepth + 1
		elseif byte == rbrace then
			bracketDepth = bracketDepth - 1
		end
	end
	frames[frameIndex] = text:sub(splitStart):match("^%s*(.-)%s*$")
	
	return frames
end

-- Optimized table merging
local function mergeList(parentTable, content)
	local parentLen = #parentTable
	if content[1] then
		-- Merge list into table
		for i, v in ipairs(content) do
			parentTable[parentLen + i] = v
		end
	else
		-- Add single item
		parentTable[parentLen + 1] = content
	end
end

-- Optimized item creation with reduced expensive calls
local function makeItem(frame, args)
	local item = mw.html.create('span')
		:addClass('invslot-item')
		:addClass(args.imgclass)
		:cssText(args.imgstyle)
	
	if not frame.name or frame.name == '' then
		return item
	end
	
	-- Frame parameters
	local title = frame.title or args.title or ''
	title = title:match("^%s*(.-)%s*$") -- trim
	local mod = frame.mod
	local name = frame.name
	local num = frame.num
	local description = frame.text
	
	-- Optimized file extension detection
	local img, extension
	if mod then
		img = i18n.legacyFilename:gsub('%$1', name .. ' (' .. mod .. ')')
		extension = '.png' -- Default for legacy
	else
		local baseName = name
		local baseImg = i18n.filename:gsub('%$1', baseName)
		
		-- Check extensions in order of preference, with caching
		local extensions = {'.png', '.gif', '.webp'}
		extension = '.png' -- default
		for _, ext in ipairs(extensions) do
			if fileExists(baseImg .. ext) then
				extension = ext
				break
			end
		end
		img = baseImg
	end
	img = img .. extension
	
	-- Strip suffixes (optimized)
	local cleanName = name
	for _, suffix in pairs(i18n.suffixes) do
		cleanName = cleanName:gsub(' ' .. suffix .. '$', '')
	end
	
	-- Determine link target
	local link = args.link or ''
	if link == '' then
		if mod then
			link = i18n.modLink:gsub('%$1', mod):gsub('%$2', cleanName)
		else
			link = cleanName:gsub('^' .. i18n.prefixes.damaged .. ' ', '')
		end
	elseif link:lower() == 'none' then
		link = nil
	end
	
	-- Avoid self-links
	if link then
		local pageName = mw.title.getCurrentTitle().text
		if link:gsub('^%l', string.upper) == pageName then
			link = nil
		end
	end
	
	-- Process title and tooltips
	local formattedTitle, plainTitle
	if title == '' then
		plainTitle = cleanName
	elseif title:lower() ~= 'none' then
		plainTitle = title:gsub('\\\\', '&#92;'):gsub('\\&', '&#38;')
		
		-- Check for formatting codes
		if plainTitle:find('&[0-9a-jl-qs-vyzr]') or plainTitle:find('&#%x%x%x%x%x%x') or plainTitle:find('&$%x%x%x') then
			formattedTitle = title
			plainTitle = plainTitle:gsub('&[0-9a-jl-qs-vyzr]', ''):gsub('&#%x%x%x%x%x%x', ''):gsub('&$%x%x%x', '')
		end
		
		if plainTitle == '' then
			plainTitle = cleanName
		else
			plainTitle = plainTitle:gsub('&#92;', '\\'):gsub('&#38;', '&')
		end
	elseif link then
		formattedTitle = ''
	end
	
	-- Set minetip attributes
	if formattedTitle or description then
		item:attr('data-minetip-title', formattedTitle)
		item:attr('data-minetip-text', description)
	end
	
	-- Escape title for HTML
	local escapedTitle = (plainTitle or ''):gsub('&', '&#38;')
	
	-- Alt text
	local altText = 'Inventory sprite for ' .. cleanName
	if link then
		altText = altText .. ' linking to ' .. link
	end
	
	-- Add the image
	item:addClass('invslot-item-image')
		:wikitext('[[File:', img, '|link=', link or '', '|alt=', altText, '|', escapedTitle, ']]')
	
	-- Add stack number
	if num and num > 1 and num < 1025 then
		local numberSpan = item:tag('span')
			:addClass('invslot-stacksize')
			:attr('title', plainTitle)
			:wikitext(num)
		
		if args.numstyle then
			numberSpan:cssText(args.numstyle)
		end
		
		if link then
			-- Wrap the number in a link
			item:wikitext('[[', link, '|'):node(numberSpan):wikitext(']]')
		end
	end
	
	return item
end

-- Fixed alias lookup - check both tables without distinguishing mod vs vanilla
local function getAliasFromCache(id)
	-- Simple cache key since we don't need to distinguish mod vs vanilla
	if aliasCache[id] ~= nil then
		return aliasCache[id]
	end
	
	initDependencies()
	local alias
	
	-- Check main aliases table first
	alias = aliases[id]
	
	-- If not found, check VHAliases table
	if not alias then
		alias = VHAliases[id]
	end
	
	aliasCache[id] = alias or false -- Cache negative results too
	return alias
end

-- Main slot function with caching
function p.slot(f)
	local args = f.args or f
	if f == mw.getCurrentFrame() and args[1] == nil then
		args = f:getParent().args
	end
	
	-- Prepare args and create cache key
	local frameText = ''
	if not args.parsed then
		frameText = (args[1] or ''):match("^%s*(.-)%s*$") -- trim
		args[1] = frameText
	else
		-- For parsed args, create a simple cache key from table contents
		if args[1] and type(args[1]) == 'table' then
			frameText = 'parsed_' .. tostring(#args[1])
		end
	end
	
	local cacheKey = frameText .. '|' .. (args.class or '') .. '|' .. (args.style or '') .. '|' .. (args.mod or '')
	if frameCache[cacheKey] then
		return frameCache[cacheKey]
	end
	
	-- Legacy mod support
	local modData = {
		default = args.mod ~= '' and args.mod or nil
	}
	
	-- Parse frames
	local frames
	if args.parsed then
		frames = args[1]
	elseif args[1] and args[1] ~= '' then
		local randomise = args.class == 'invslot-large' and 'never' or nil
		frames = p.parseFrameText(args[1], randomise, false, modData)
	end
	
	-- Create slot
	local body = mw.html.create('span')
		:addClass('invslot')
		:css('vertical-align', args.align)
	
	-- Handle animation
	if frames and #frames > 1 then
		body:addClass('animated')
	end
	
	-- Default background
	if args.default and args.default ~= '' then
		body:addClass('invslot-default-' .. args.default:lower():gsub(' ', '-'))
	end
	
	-- Custom styles
	if args.class then body:addClass(args.class) end
	if args.style then body:cssText(args.style) end
	
	if not frames or #frames == 0 then
		local result = tostring(body)
		frameCache[cacheKey] = result
		return result
	end
	
	-- Add frames
	local activeFrame = 1
	if frames.randomise == true then
		if not random then
			initDependencies()
		end
		activeFrame = random(#frames)
	end
	
	for i, frame in ipairs(frames) do
		local item
		if frame[1] then
			-- Subframe container
			item = body:tag('span'):addClass('animated-subframe')
			local subActiveFrame = frame.randomise == true and random(#frame) or 1
			
			for sI, sFrame in ipairs(frame) do
				local sItem = makeItem(sFrame, args)
				if sI == subActiveFrame then
					sItem:addClass('animated-active')
				end
				item:node(sItem)
			end
		else
			-- Simple frame
			item = makeItem(frame, args)
		end
		
		if i == activeFrame and #frames > 1 then
			item:addClass('animated-active')
		end
		
		body:node(item)
	end
	
	local result = tostring(body)
	frameCache[cacheKey] = result
	return result
end

-- Fixed frame parsing with simplified alias lookup
function p.parseFrameText(framesText, randomise, aliasReference, modData)
	local frames = { randomise = randomise }
	local subframes = {}
	local subframe = false
	local expandedAliases = aliasReference and {} or nil
	
	local splitFrames = splitOnUnenclosedSemicolons(framesText)
	
	for i, frameText in ipairs(splitFrames) do
		-- Handle subframes
		if frameText:find('^%s*{') then
			frameText = frameText:gsub('^%s*{%s*', '')
			subframe = true
		end
		
		if subframe and frameText:find('}%s*$') then
			frameText = frameText:gsub('%s*}%s*$', '')
			subframe = 'last'
		end
		
		-- Create frame
		local frame = p.makeFrame(frameText, modData and modData.default)
		
		-- Simplified alias processing - just check both tables
		local newFrame = { frame }
		if frame.name and frame.name ~= '' then
			local alias = getAliasFromCache(frame.name)
			
			if alias then
				newFrame = p.getAlias(alias, frame)
				
				if aliasReference then
					local curFrame = #frames + 1
					local aliasData = { frame = frame, length = #newFrame }
					if subframe then
						if not subframes.aliasReference then
							subframes.aliasReference = {}
						end
						subframes.aliasReference[#subframes + 1] = aliasData
					else
						expandedAliases[curFrame] = aliasData
					end
				end
			end
		end
		
		-- Add frames
		if subframe then
			mergeList(subframes, newFrame)
			
			-- Handle randomization
			if frames.randomise ~= 'never' and subframes.randomise == nil and
				frame.name and frame.name:find('^' .. i18n.prefixes.any .. ' ') then
				subframes.randomise = true
			else
				subframes.randomise = false
			end
			
			if frames.randomise ~= 'never' then
				frames.randomise = false
			end
			
			if subframe == 'last' then
				if #subframes == 1 or (#splitFrames == i and #frames == 0) then
					local lastFrame = #frames
					mergeList(frames, subframes)
					
					if #splitFrames == 1 then
						frames.randomise = subframes.randomise
					end
					
					if aliasReference and subframes.aliasReference then
						for j, aliasRefData in pairs(subframes.aliasReference) do
							expandedAliases[lastFrame + j] = aliasRefData
						end
					end
				else
					frames[#frames + 1] = subframes
				end
				
				subframes = {}
				subframe = false
			end
		else
			if frames.randomise ~= 'never' and frame.name and frame.name:find('^' .. i18n.prefixes.any .. ' ') then
				frames.randomise = true
			else
				frames.randomise = false
			end
			
			mergeList(frames, newFrame)
		end
	end
	
	frames.aliasReference = expandedAliases
	return frames
end

-- Optimized alias expansion
function p.getAlias(aliasFrames, parentFrame)
	if type(aliasFrames) == 'string' then
		local expandedFrame = {
			name = aliasFrames,
			title = parentFrame.title,
			num = parentFrame.num,
			text = parentFrame.text,
			mod = parentFrame.mod
		}
		return { expandedFrame }
	end
	
	if aliasFrames.name then
		aliasFrames = { aliasFrames }
	end
	
	local expandedFrames = {}
	for i, aliasFrame in ipairs(aliasFrames) do
		local expandedFrame
		if type(aliasFrame) == 'string' then
			expandedFrame = { name = aliasFrame }
		else
			-- Shallow clone for performance
			expandedFrame = {}
			for k, v in pairs(aliasFrame) do
				expandedFrame[k] = v
			end
		end
		
		-- Apply parent settings (only if not already set)
		expandedFrame.title = parentFrame.title or expandedFrame.title
		expandedFrame.num = parentFrame.num or expandedFrame.num  
		expandedFrame.text = parentFrame.text or expandedFrame.text
		expandedFrame.mod = parentFrame.mod or expandedFrame.mod
		
		expandedFrames[i] = expandedFrame
	end
	
	return expandedFrames
end

-- Optimized frame creation
function p.makeFrame(frameText, defaultMod)
	-- Fast path for simple frames
	if not frameText:find('[%[:,]') then
		return {
			mod = defaultMod,
			name = frameText:match("^%s*(.-)%s*$"),
		}
	end
	
	-- Complex frame parsing
	local frame = { mod = defaultMod }
	
	-- Title
	local title, rest = frameText:match('^%s*%[([^%]]*)%]%s*(.*)')
	if title then
		frame.title = title
		frameText = rest
	end
	
	-- Text
	rest, frame.text = frameText:match('([^%]]*)%s*%[([^%]]*)%]%s*$')
	if frame.text then
		frameText = rest
	end
	
	-- Mod
	local mod, rest = frameText:match('^([^:]+):%s*(.*)')
	if mod and not vanilla[mod:lower()] then
		frame.mod = mod
		frameText = rest
	elseif mod then
		frameText = rest
	else
		frameText = frameText:gsub('^:', '')
	end
	
	-- Name and number
	local name, num = frameText:match('(.*),%s*(%d+)')
	if num then
		frame.name = name:match("^%s*(.-)%s*$")
		frame.num = math.floor(tonumber(num))
		if frame.num < 2 then
			frame.num = nil
		end
	else
		frame.name = frameText:match("^%s*(.-)%s*$")
	end
	
	return frame
end

-- Utility functions (unchanged)
function p.stringifyFrame(frame)
	if not frame.name then
		return ''
	end
	return string.format(
		'[%s]%s:%s,%s[%s]',
		frame.title or '',
		frame.mod or 'Minecraft',
		frame.name,
		frame.num or '',
		frame.text or ''
	)
end

function p.stringifyFrames(frames)
	local result = {}
	for i, frame in ipairs(frames) do
		if frame[1] then
			result[i] = '{' .. p.stringifyFrames(frame) .. '}'
		else
			result[i] = p.stringifyFrame(frame)
		end
	end
	return table.concat(result, ';')
end

return p