![]() |
VOOZH | about |
This module implements {{inventory slot}}.
localp={} -- Internationalization data locali18n={ -- Name formats for pages and files filename='Invicon $1', legacyFilename='Grid $1', modLink='Mods/$1/$2', -- Dependencies moduleAliases=[[Module:Inventory slot/Aliases]], moduleRandom=[[Module:Random]], -- List of special prefixes which should be handled by -- other modules (such as being moved outside links) -- When localizing, you might want to use a separate list of patterns -- matching the prefixes’ grammatical forms depending on the language prefixes={ any='Any', matching='Matching', damaged='Damaged', unwaxed='Unwaxed', }, -- List of suffixes that are usually stripped from links and tooltips suffixes={ rev='Revision %d+', -- berev = 'BE%d+', -- jerev= 'JE%d+', be='BE', lce='LCE', sm='SM', damage='%d+' }, } p.i18n=i18n -- Global dependencies and constants localrandom=require(i18n.moduleRandom).random localaliases=mw.loadData(i18n.moduleAliases) localpageName=mw.title.getCurrentTitle().text localvanilla={v=1,vanilla=1,mc=1,minecraft=1} -- Auxilliary functions -- -- Performs a simple recursive clone of a table’s values. -- Probably exists due to mw.clone() being unusable on tables from mw.loadData() -- at the time (see the link to help.fandom.com above) localfunctioncloneTable(origTable) localnewTable={} fork,vinpairs(origTable)do iftype(v)=='table'then v=cloneTable(v) end newTable[k]=v end returnnewTable end -- Merges a list, or inserts a string or table into a table, -- depending on what the second argument happens to be localfunctionmergeList(parentTable,content) locali=#parentTable+1 ifcontent[1]then -- Merge list into table for_,vinipairs(content)do parentTable[i]=v i=i+1 end else -- Add strings or tables to table parentTable[i]=content end end -- Creates the HTML node for a given item. -- The actual icon file is found and added here localfunctionmakeItem(frame,args) localitem=(mw.html.create('span') :addClass('invslot-item') :addClass(args.imgclass) :cssText(args.imgstyle) ) if(frame.nameor'')==''then -- Empty frame, no icon to add returnitem end -- Frame parameters localtitle=frame.titleormw.text.trim(args.titleor'') localmod=frame.mod localname=frame.name localnum=frame.num localdescription=frame.text -- Split the extension out of the frame’s name localextension ifname:match('%.gif')orname:match('%.png')then extension=name:sub(-4) name=name:sub(0,-5) elseifname:match('%.webp')then extension='.webp' name=name:sub(0,-6) else extension='.png' end -- Determine the file name localimg ifmodthen -- Legacy mod support -- Comment out instead of deleting, as other wikis may find it useful img=i18n.legacyFilename:gsub('%$1',name..' ('..mod..')') else -- Fall back to an individual image if the sprite is lacking img=i18n.filename:gsub('%$1',name) end img=img..extension -- Strip suffixes out for_,suffixinpairs(i18n.suffixes)do name=name:gsub(' '..suffix..'$','') end -- Determine the link’s target locallink=args.linkor'' iflink==''then ifmodthen link=i18n.modLink:gsub('%$1',mod):gsub('%$2',name) else -- Strip the “Damaged” prefix out link=name:gsub('^'..i18n.prefixes.damaged..' ','') end elseiflink:lower()=='none'then -- Disable the link link=nil end iflinkandlink:gsub('^%l',string.upper)==pageNamethen link=nil end -- Tooltip titles. If JavaScript is not enabled, the slot will gracefully -- degrade to a simplified title without minetip formatting localformattedTitle localplainTitle iftitle==''then -- If the title is not set, default to the slot’s name plainTitle=name elseiftitle:lower()~='none'then -- Special character escapes plainTitle=title:gsub('\\\\','\'):gsub('\\&','&') -- The default title will have special formatting code stripped out localformatPatterns={'&[0-9a-jl-qs-vyzr]','&#%x%x%x%x%x%x','&$%x%x%x'} for_,formatPatterninipairs(formatPatterns)do ifplainTitle:match(formatPattern)then formattedTitle=title plainTitle=plainTitle:gsub(formatPattern,'') end end ifplainTitle==''then -- If the title field only has formatting code, the frame’s name -- is automatically used. For minetips it’s done by JavaScript -- by appending the plain title. plainTitle=name else -- Re-encode the plainTitle=plainTitle:gsub('\','\\'):gsub('&','&') end elseiflinkthen -- Disable the tooltip that will otherwise appear with a link formattedTitle='' end -- Minetips are controlled by custom HTML attributes. -- See [[MediaWiki:Common.js]] for implementation in JavaScript item:attr{ ['data-minetip-title']=formattedTitle, ['data-minetip-text']=description } -- & is re-escaped because mw.html treats attributes as plain text, -- but MediaWiki doesn’t. localescapedTitle=(plainTitleor''):gsub('&','&') -- Alt text localaltText=img..': Inventory sprite for '..name..' in Minecraft as shown in-game' iflinkthen altText=altText..' linking to '..link end ifformattedTitleorplainTitleorlinkthen altText=altText..' with description: '..(formattedTitleorplainTitleorlink) ifdescriptionthen altText=altText..' '..description:gsub('/',' ') end altText=altText:gsub('&[0-9a-jl-qs-wr]','') end -- Add the image item:addClass('invslot-item-image') :wikitext('[[File:',img,'|32x32px|link=',linkor'','|alt=',altText,'|',escapedTitle,']]') -- Add the stack number, if present and in 2-999 range ifnumandnum>1andnum<1000then iflinkthen item:wikitext('[[',link,'|') end localnumber=item :tag('span') :addClass('invslot-stacksize') :attr{title=plainTitle} :wikitext(num) ifargs.numstylethen number:cssText(args.numstyle) end iflinkthen item:wikitext(']]') end end -- The HTML node is now ready returnitem end -- Publicly available functions -- -- Main entry point: Creates the whole slot functionp.slot(f) -- Incoming arguments localargs=f.argsorf iff==mw.getCurrentFrame()andargs[1]==nilthen args=f:getParent().args end -- TODO: Add support for unexpanded frame sequences in table format ifnotargs.parsedthen -- Assumed to be a string, trim it args[1]=mw.text.trim(args[1]or'') end -- Legacy mod support. Comment out instead of deleting; might be useful -- for other wikis -- TODO: Support multiple mod alias tables at once (like on RuMCW) localmodData={ aliases=args.modaliasesor'', default=args.mod } ifmodData.aliases~=''then modData.aliases=mw.loadData('Module:'..modData.aliases) else modData.aliases=nil end ifargs.mod==''then modData.default=nil end -- Get the frame sequence in table format localframes ifargs.parsedthen -- Already parsed in some other module, such as Recipe table frames=args[1] elseifargs[1]~=''then -- Parse the frame string -- TODO: Make the “randomise” flag not hard-coded to invslot-large CSS class -- (ostensibly for output slots) as not all output slots are large localrandomise=args.class=='invslot-large'and'never'ornil frames=p.parseFrameText(args[1],randomise,false,modData) end -- Create the slot node and add applicable styles localbody=mw.html.create('span'):addClass('invslot'):css{['vertical-align']=args.align} -- Is the slot animated? localanimated=framesand#frames>1 ifanimatedthen body:addClass('animated') end -- Default background if(args.defaultor'')~=''then-- default background body:addClass('invslot-default-'..string.lower(args.default):gsub(' ','-')) end -- Custom styles body:addClass(args.class) body:cssText(args.style) --mw.logObject( frames ) ifnotframesor#frames==0then -- Empty slot returntostring(body) end -- We have frames, add them localactiveFrame=frames.randomise==trueandrandom(#frames)or1 fori,frameinipairs(frames)do localitem ifframe[1]then -- This is a subframe container. Each animation cycle of the slot -- will show a subframe, one at a time. -- Create a container node for subframes item=body:tag('span'):addClass('animated-subframe') localsubActiveFrame=frame.randomise==trueandrandom(#frame)or1 -- Add subframes to the note forsI,sFrameinipairs(frame)do localsItem=makeItem(sFrame,args) item:node(sItem) -- Set this subframe as active ifsI==subActiveFramethen sItem:addClass('animated-active') end end else -- A simple frame item=makeItem(frame,args) body:node(item) end ifi==activeFrameandanimatedthen -- Set this frame as active, if we have multiple of them item:addClass('animated-active') end end -- The slot is ready returntostring(body) end -- Splits a given text into fragments separated by semicolons that are not -- inside square brackets. Originally written by AttemptToCallNil for the -- Russian wiki. -- It processes the text byte-by-byte due to being written under a much stricter -- Lua runtime budget, with no LuaSandbox and mw.text.split being unperformant. -- See also https://help.fandom.com/wiki/Extension:Scribunto#Known_issues_and_solutions functionp.splitOnUnenclosedSemicolons(text) localsemicolon,lbrace,rbrace=(";[]"):byte(1,3) localnesting=false localsplitStart=1 localframeIndex=1 localframes={} forindex=1,text:len()do localbyte=text:byte(index) ifbyte==semicolonandnotnestingthen frames[frameIndex]=text:sub(splitStart,index-1) frameIndex=frameIndex+1 splitStart=index+1 elseifbyte==lbracethen assert(notnesting,"Excessive square brackets found") nesting=true elseifbyte==rbracethen assert(nesting,"Unbalanced square brackets found") nesting=false end end assert(notnesting,"Unbalanced square brackets found") frames[frameIndex]=text:sub(splitStart,text:len()) forindex=1,#framesdo frames[index]=(frames[index]:gsub("^%s+",""):gsub("%s+$",""))-- faster than mw.text.trim end returnframes end -- Parses the frame text into a table of frames and subframes, -- expanding aliases (and optionally retaining a reference), and -- deciding if the slot can be randomised. -- Alias references are used in [[Module:Recipe table]] to create links and -- lists of unique items. functionp.parseFrameText(framesText,randomise,aliasReference,modData) -- Frame sequences localframes={randomise=randomise} localsubframes={} -- Is the current frame a subframe? localsubframe -- The list of expanded aliases, will be added to the frame sequence -- if aliasReference is set to true AND if there are any aliases to expand. localexpandedAliases -- Split the frame string by semicolons (respecting square brackets) localsplitFrames=p.splitOnUnenclosedSemicolons(framesText) -- Iterate on frame fragments fori,frameTextinipairs(splitFrames)do -- Subframes are grouped by curly braces frameText=frameText:gsub('^%s*{%s*',function() subframe=true return'' end) ifsubframethen -- Closing brace found frameText=frameText:gsub('%s*}%s*$',function() subframe='last' return'' end) end -- Convert the frame text into table format, applying the default mod -- if needed. localframe=p.makeFrame(frameText,modDataandmodData.default) -- Alias processing -- TODO: Rework mod support to automatically load relevant alias tables, -- for use on other wikis that may want it. This will allow supporting -- multiple mod alias tables at once. Comment out instead of deleting! localnewFrame=frame ifaliasesormodData.aliasesthen localid=frame.name ifframe.modthen -- is this really needed? RuMCW doesn’t add mod prefixes in mod aliases id=frame.mod..':'..id end localalias=modDataandmodData.aliasesandmodData.aliases[id]or aliasesandaliases[id] ifaliasthen -- Alias found, expand it newFrame=p.getAlias(alias,frame) -- Save the alias references, if asked ifaliasReferencethen -- The alias data includes the original unexpanded frame -- and the number of frames it has expanded to. -- The alias reference table is not sequential — indices for -- each alias data object correspond to that alias’ first -- (or only) expanded frame. Which is not added to the frame -- sequence yet localcurFrame=#frames+1 localaliasData={frame=frame,length=#newFrame} ifsubframethen -- Subframe containers will have their own -- alias reference tables ifnotsubframes.aliasReferencethen subframes.aliasReference={} end subframes.aliasReference[#subframes+1]=aliasData else ifnotexpandedAliasesthen expandedAliases={} end expandedAliases[curFrame]=aliasData end end end end -- Alias processing ends here -- Add frames and control randomization ifsubframethen -- Add the frame to the current subframe container mergeList(subframes,newFrame) -- Randomise starting frame for "Any *" aliases, as long as the -- alias is the only subframe (and randomization is not disabled) ifframes.randomise~='never'andsubframes.randomise==niland frame.name:match('^'..i18n.prefixes.any..' ') then subframes.randomise=true else subframes.randomise=false end -- Disable randomization ifframes.randomise~='never'then frames.randomise=false end ifsubframe=='last'then if#subframes==1or#splitFrames==iand#frames==0then -- If the subframe container only has one expanded frame or -- is the only frame in the whole sequence, its contents are -- extracted into the main frame sequence locallastFrame=#frames mergeList(frames,subframes) -- Inherit the randomise flag if it’s the only frame if#splitFrames==1then frames.randomise=subframes.randomise end -- Append alias reference data, if present ifaliasReferenceandsubframes.aliasReferencethen ifnotexpandedAliasesthen expandedAliases={} end fori,aliasRefDatainpairs(subframes.aliasReference)do expandedAliases[lastFrame+i]=aliasRefData end end else -- Add the subframe container to the frame sequence table.insert(frames,subframes) end -- Finished processing this subframe container subframes={} subframe=nil end else -- Randomize starting frame for "Any *" aliases, as long as the alias is the only frame ifframes.randomise~='never'andframe.name:match('^'..i18n.prefixes.any..' ')then frames.randomise=true else frames.randomise=false end -- Add the expanded frame(s) to the frame sequence mergeList(frames,newFrame) end end -- Add the alias reference, if we’re compiling one frames.aliasReference=expandedAliases -- The frame sequence is ready returnframes end -- Applies parameters from the parent frame (such as title or text) -- to the alias’ expansion functionp.getAlias(aliasFrames,parentFrame) -- If alias is just a name, return the parent frame with the new name iftype(aliasFrames)=='string'then localexpandedFrame=mw.clone(parentFrame) expandedFrame.name=aliasFrames return{expandedFrame} end -- Single frame alias, put in list ifaliasFrames.namethen aliasFrames={aliasFrames} end -- Common case: group alias localexpandedFrames={} fori,aliasFrameinipairs(aliasFrames)do localexpandedFrame iftype(aliasFrame)=='string'then -- Simple expansion frame in string format expandedFrame={name=aliasFrame} else -- Expansion frame in table format -- As it’s loaded with mw.loadData, it must be cloned -- before changing expandedFrame=cloneTable(aliasFrame) end -- Apply the parent frame’s settings expandedFrame.title=parentFrame.titleorexpandedFrame.title expandedFrame.num=parentFrame.numorexpandedFrame.num expandedFrame.text=parentFrame.textorexpandedFrame.text -- Legacy mod support. Comment out instead of deleting -- TODO: invert the priority for mod parameter, to allow -- group mod aliases with vanilla items? expandedFrame.mod=parentFrame.modorexpandedFrame.mod expandedFrames[i]=expandedFrame end returnexpandedFrames end -- Convert the frame object back into string format functionp.stringifyFrame(frame) ifnotframe.namethen return'' end returnstring.format( '[%s]%s:%s,%s[%s]', frame.titleor'', frame.modor'Minecraft', frame.name, frame.numor'', frame.textor'' ) end -- Convert the frame sequence into string format functionp.stringifyFrames(frames) fori,frameinipairs(frames)do ifframe[1]then -- Subframe container -- As the format and the syntax are the same, process it recursively frames[i]='{'..p.stringifyFrames(frame)..'}' else frames[i]=p.stringifyFrame(frame) end end returntable.concat(frames,';') end -- Converts the frame text into a frame object -- Full syntax: [Title]Mod:Name,Number[Text] functionp.makeFrame(frameText,defaultMod) -- Simple frame with no parts ifnotframeText:match('[%[:,]')then return{ mod=defaultMod, name=mw.text.trim(frameText), } end -- Complex frame localframe={} -- Title localtitle,rest=frameText:match('^%s*%[([^%]]*)%]%s*(.*)') iftitlethen frame.title=title frameText=rest end -- Additional tooltip text localrest,text=frameText:match('([^%]]*)%s*%[([^%]]*)%]%s*$') iftextthen frame.text=text frameText=rest end -- Legacy mod support -- Comment out instead of deleting localmod,rest=frameText:match('^([^:]+):%s*(.*)') ifmodthen ifnotvanilla[mod:lower()]then frame.mod=mod end frameText=rest else frame.mod=defaultMod frameText=frameText:gsub('^:','') end -- Name and stack size -- The pattern will match the last comma, so you can use names with commas -- like so: “Potatiesh, Greatstaff of the Peasant,1” localname,num=frameText:match('(.*),%s*(%d+)') ifnumthen -- Number is set frame.name=mw.text.trim(name) frame.num=math.floor(num) ifframe.num<2then frame.num=nil end else -- No number frame.name=mw.text.trim(frameText) end -- The frame object is ready returnframe end -- This line should be the last one: returnp