Данная страница входит в проект Обновление модулей. |
Черновик разрабатываемой новой версии модуля Инвентарный слот.
Смотрите также: Minecraft Wiki:Проекты/Обновление модулей/Инвентарный слот
-------------------------------------------------------------------
--- Модуль для отображения инвентарных слотов в Minecraft Wiki.
--- Форк [[:en:Special:Permalink/1326065]]
-------------------------------------------------------------------
-- <nowiki>
local p = {}
local slot_utils = require("Модуль:Инвентарный слот2/Utils")
local i18n = {
filename = 'Invicon $1',
legacyFilename = 'Grid $1.png',
modLink = '$1/$2',
moduleAliases = [[Модуль:Инвентарный слот2/Псевдонимы]],
moduleInvData = [[Модуль:ИнвСпрайт]],
moduleRandom = [[Модуль:Случайные числа]],
moduleSprite = [[Модуль:Спрайт]],
moduleProcessArgs = [[Модуль:ProcessArgs]],
-- Список особых префиксов, которые должны быть обработаны другими модулями
-- (в том числе убраны из ссылок)
prefixes = {
any = {'Любой', 'Любая', 'Любое', 'Любые'},
matching = {'Соответствующий', 'Соответствующая', 'Соответствующее', 'Соответствующие'},
damaged = {'Повреждённый', 'Повреждённая', 'Повреждённое', 'Повреждённые'},
},
-- Проверяет строку name на соответствие какой-либо из строк, заданных выше
-- в prefixes с ключом prefixName. Возвращает true/false.
matchesPrefix = function(name, prefixName)
local prefixData = p.i18n.prefixes[prefixName]
assert(prefixData, ("недопустимый ID префикса: %s"):format(prefixName))
for _, prefix in ipairs(prefixData) do
if name:find(prefix) == 1 then
return true
end
end
return false
end,
stripPrefix = function(name, prefixName)
local prefixData = p.i18n.prefixes[prefixName]
assert(prefixData, ("недопустимый ID префикса: %s"):format(prefixName))
for _, prefix in ipairs(prefixData) do
local name, matched = name:gsub("^" .. prefix .. " ", "")
if matched == 1 then
return name
end
end
return name
end,
suffixes = {
be = 'BE',
lce = 'LCE',
},
templateFileUrl = 'FileUrl',
moduleModData = "Модуль:ИнвСпрайт/$1"
}
p.i18n = i18n
local random = require( i18n.moduleRandom ).random
local sprite = require( i18n.moduleSprite ).sprite
local processArgs = require( i18n.moduleProcessArgs )
local modNameAliases = mw.loadData("Модуль:Модификации")
local aliases = mw.loadData( i18n.moduleAliases )
local ids = mw.loadData( i18n.moduleInvData )["IDы"]
local pageName = mw.title.getCurrentTitle().text
local vanillaPrefixes = { v = 1, vanilla = 1, mc = 1, minecraft = 1 }
function p.loadModData(mod)
return slot_utils.loadMaybeData(("Модуль:ИнвСпрайт/%s"):format(mod))
end
-- Создаёт HTML-код предмета.
--
-- frame: структура фрейма, содержащая данные фрейма.
-- i: индекс фрейма или подфрейма
-- args: обработанные аргументы модуля
local function makeItem( frame, i, args )
local item = mw.html.create( 'span' )
:addClass( 'invslot-item' )
:addClass( args.imageClass ) -- no-op when given nil
:cssText( args.imageStyle ) -- no-op when given nil
if frame.name == '' then
return item
end
local category
local title = frame.title or ((args.title or ''):gsub("^%s+", ""):gsub("%s+$", ""))
local mod = frame.mod
local name = frame.name or ''
local num = frame.num
local description = frame.text
local img, idData, modData, enName, imageOverride
if mod then
local modResults = p.resolveModData(mod, name)
modData = modResults.modData
idData = modResults.idData
img = modResults.img
enName = modResults.enName
imageOverride = modResults.imageOverride
elseif ids[name] then
idData = ids[name]
elseif name:match( '\.gif$' ) or name:match( '\.png$' ) then
img = i18n.filename:gsub( '%$1', name )
-- Remove file extension from name
name = name:sub( 0, -5 )
else
img = i18n.legacyFilename:gsub( '%$1', name )
end
local link = args.link or ''
if link == '' then
if mod then
link = i18n.modLink:gsub( '%$1', mod ):gsub( '%$2', name )
else
link = i18n.stripPrefix(name, "damaged")
for _, suffix in pairs( i18n.suffixes ) do
link = link:gsub( ' ' .. suffix .. '$', '' )
end
end
elseif link:lower() == 'нет' then
link = nil
end
if link == pageName then
link = nil
end
local formattedTitle
local plainTitle
if title == '' then
plainTitle = name
elseif title:lower() ~= 'нет' then
plainTitle = title:gsub( '\\\\', '\' ):gsub( '\\&', '&' )
local formatPattern = '&[0-9a-fk-or]'
if plainTitle:match( formatPattern ) then
formattedTitle = title
plainTitle = plainTitle:gsub( formatPattern, '' )
end
if plainTitle == '' then
plainTitle = name
else
plainTitle = plainTitle:gsub( '\', '\\' ):gsub( '&', '&' )
end
elseif link then
if img then
formattedTitle = ''
else
plainTitle = ''
end
end
description = p.resolveMultipage(mod, idData, description)
item:attr{
['data-minetip-title'] = formattedTitle,
['data-minetip-text'] = description,
['data-modinfo-text'] = mod,
['data-minetip-lowtitle'] = enName
}
if img then
-- & is re-escaped because mw.html treats attributes
-- as plain text, but MediaWiki doesn't
local escapedTitle = ( plainTitle or '' ):gsub( '&', '&' )
item:addClass( 'invslot-item-image' )
:wikitext( '[[File:', img, '|32x32px|link=', link or '', '|', escapedTitle, ']]' )
else
local image
local data = 'ИнвСпрайт'
if mod then
image = args.stylesheet or imageOverride
data = data .. '/' .. mod
end
if link then
item:wikitext( '[[', link, '|' )
end
local image, spriteCat = sprite{
["масштаб"] = args.scale, ["данныеID"] = idData, ["назв"] = plainTitle,
["изобр"] = image, ["данные"] = data,
nourl = args.nourl,
}
item:node( image )
category = spriteCat
end
if num and num > 1 and num < 1000 then
if img and link then
item:wikitext( '[[', link, '|' )
end
local number = item
:tag( 'span' )
:addClass( 'invslot-stacksize' )
:attr{ title = plainTitle }
:wikitext( num )
:cssText( args.numberStyle ) -- no-op when given nil
if img and link then
item:wikitext( ']]' )
end
end
if idData and link then
item:wikitext( ']]' )
end
item:wikitext( category )
return item
end
---- Функции для операций над псевдонимами. ------------------------------------
--[[Returns a new table with the parts of the parent frame
added to the alias
--]]
function p.getAlias( aliasFrames, parentFrame )
-- If alias is just a name, return the parent frame with the new name
if type( aliasFrames ) == 'string' then
local expandedFrame = mw.clone( parentFrame )
expandedFrame.name = aliasFrames
return { expandedFrame }
end
-- Single frame alias, put in list
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
expandedFrame = slot_utils.cloneTable( aliasFrame )
end
expandedFrame.title = parentFrame.title or expandedFrame.title
expandedFrame.mod = parentFrame.mod or expandedFrame.mod
expandedFrame.num = parentFrame.num or expandedFrame.num
expandedFrame.text = parentFrame.text or expandedFrame.text
expandedFrames[i] = expandedFrame
end
return expandedFrames
end
function p.expandAlias( parentFrame, alias )
error("Вызвана устаревшая функция p.expandAlias из модуля Инвентарный слот!")
-- return p.getAlias( alias, parentFrame )
end
---- Функции для операций, связанных с модификациями. --------------------------
--[[
Возвращает окончательное название модификации из приведённого.
Задействует псевдонимы для обозначения модификаций и оригинальной игры.
Параметры:
• mod (строка) — название модификации, введённое пользователем.
Возвращает каноническое название модификации (строку) или nil, если название
указывает на оригинальную игру.
]]
function p.resolveMod(mod)
if (not mod)
or (vanillaPrefixes[mw.ustring.lower(mod)])
or (mod == '') then
-- mw.log("mod = " .. (tostring(mod) or "?") .. ", type = " .. type(mod) .. "; rejecting" )
return nil
else
local mod = modNameAliases[mod] or mod
return (mod:gsub('_', ' '))
end
end
function p.resolveModData(mod, name)
local results = {}
results.modData = p.loadModData(mod)
local idListOverride = results.modData['настройки']['списокID']
results.imageOverride = results.modData['настройки']['изобр']
if idListOverride then
results.modData = slot_utils.loadMaybeData('Модуль:' .. idListOverride)
end
-- River said: "wtb .?"; I agree
results.modData = results.modData and results.modData["IDы"]
results.idData = results.modData and results.modData[name]
results.enName = results.idData and results.idData.en
if not (results.modData and results.idData) then
results.img = i18n.legacyFilename:gsub( '%$1', name .. ' (' .. mod .. ')' )
end
return results
end
function p.loadModAliases(mod)
return slot_utils.loadMaybeData(("Модуль:ИнвСпрайт/%s/Псевдонимы2"):format(mod))
end
function p.resolveMultipage(mod, idData, description)
if mod == 'GregTech' and idData and idData["страница"] then
local mat_info = slot_utils.loadMaybeData('Модуль:Обработка/Материалы/' .. idData["страница"])
if mat_info then
local formula = mat_info["Формула"]
local melt = mat_info["Плавление"]
local boil = mat_info["Испарение"]
return (description or '') .. '/&7Формула: &e' .. formula .. '/&6t° плавления: ' .. melt .. 'K/&ct° испарения: ' .. boil .. 'K'
end
end
return description
end
---- Функции для операций над фреймами. ----------------------------------------
-- Фрейм — структура, описывающая один кадр анимации инвентарного слота.
--
-- Поля фрейма:
-- • title (строка): заглавие всплывающей подсказки. Если не задано, подсказки нет.
-- • mod (строка): название модификации. Если не задано, предмет берётся из основной игры.
-- • name (строка): название предмета. Должно быть задано.
-- • num (число): количество предметов в ячейке. Равно nil, если не задано.
-- • text (строка): текст всплывающей подсказки.
----
--[[
Возвращает строковое представление структуры фрейма frame.
]]
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
--[[
Берёт последовательность структур фреймов frames и заменяет в этой
последовательности каждую структуру её строковым представлением. Затем
возвращает одну строку, объединяющую все фреймы.
]]
function p.stringifyFrames( frames )
for i, frame in ipairs( frames ) do
frames[i] = p.stringifyFrame( frame )
end
return table.concat( frames, ';' )
end
--[[
Берёт текстовое представление фрейма (frameText) и переопределение модификации
(mod). Возвращает структуру фрейма, соответствующую тексту frameText. Если во
frameText не задана модификация, используется значение mod.
]]
function p.makeFrame( frameText, mod )
-- Simple frame with no parts
if not frameText:match( '[%[:,]' ) then
return {
mod = p.resolveMod(mod),
name = (frameText:gsub("^%s+", ""):gsub("%s+$", "")),
}
end
local frame = {}
-- Эти две функции должны быть вызваны первыми, чтобы gsub ниже не портил подсказки.
frameText, frame.title = slot_utils.removePrefix(frameText, '%s*%[%s*(.-)%s*%]%s*')
frameText, frame.text = slot_utils.removeSuffix(frameText, '%s*%[%s*(.-)%s*%]%s*')
-- Убирает пробелы, окружающие особые символы (квадратные скобки, двоеточия,
-- запятые и точки с запятой).
frameText = frameText:gsub( '%s*([%[%]:,;])%s*', '%1' )
frameText, frame.mod = slot_utils.removePrefix(frameText, '([^:,;%[%]]+):', mod)
frame.mod = p.resolveMod(frame.mod)
frameText, frame.name = slot_utils.removePrefix(frameText, '[^,;%[]+')
frameText, frame.num = slot_utils.removePrefix(frameText, ',(%d+)')
frame.num = math.floor( frame.num or 0 )
if frame.num == 0 then
frame.num = nil
end
return frame
end
function p.getParts( frameText, mod )
error("Вызвана устаревшая функция p.getParts из модуля Инвентарный слот!")
-- return p.makeFrame( frameText, mod )
end
---- Парсер списка фреймов. ----------------------------------------------------
--[[
Разбивает список фреймов на строки, описывающие конкретные фреймы.
Учитывает, что разделитель фреймов (точка с запятой) может находиться ещё и в
подсказке.
Выдаёт ошибку при некорректном использовании квадратных скобок.
Возвращает последовательность строковых представлений фреймов. Начальные и
конечные пробельные символы в каждой строке уже убраны.
]]
local function splitOnUnenclosedSemicolons(text)
local semicolon, lbrace, rbrace = (";[]"):byte(1, 3)
local nesting = false
local splitStart = 1
local frameIndex = 1
local frames = {}
for index = 1, text:len() do
local byte = text:byte(index)
if byte == semicolon and not nesting then
frames[frameIndex] = text:sub(splitStart, index - 1)
frameIndex = frameIndex + 1
splitStart = index + 1
elseif byte == lbrace then
assert(not nesting, "Ошибка синтаксиса: чрезмерные квадратные скобки")
nesting = true
elseif byte == rbrace then
assert(nesting, "Ошибка синтаксиса: несбалансированные квадратные скобки")
nesting = false
end
end
assert(not nesting, "Ошибка синтаксиса: несбалансированные квадратные скобки")
frames[frameIndex] = text:sub(splitStart, text:len())
for index = 1, #frames do
frames[index] = (frames[index]:gsub("^%s+", ""):gsub("%s+$", "")) -- faster mw.text.trim
end
return frames
end
--[[
Удаляет синтаксис подфреймов, выдавая ошибку при его некорректном использовании.
Принимает текст фрейма (frameText) и состояние наличия подфрейма (subframe).
Возвращает новые значения frameText и subframe
]]
local function setSubframe(frameText, subframe)
local matchedStart, matchedEnd
frameText, matchedStart = frameText:gsub('^{%s*', '')
if matchedStart == 1 then
subframe = true
end
frameText, matchedEnd = frameText:gsub('}%s*$', '')
assert(subframe or matchedEnd == 0, "Неожиданная закрывающая фигурная скобка при отсутствии подфреймов.")
if matchedEnd == 1 then
subframe = "last"
end
return frameText, subframe
end
--[[
Таблица parserArgs должна содержать следующие значения:
• frames: строка со списком фреймов;
• randomize: булево значение; если ложно, воспроизведение в случайном порядке
не допускается;
• defaultMod: название модификации по умолчанию.
]]
function p.parseFrameText(parserArgs)
local frames = { randomise = parserArgs.randomise }
local subframes = {}
local subframe
local expandedAliases
local defaultMod = parserArgs.defaultMod
local splitFrames = splitOnUnenclosedSemicolons(parserArgs.frames)
for _, frameText in ipairs( splitFrames ) do
frameText, subframe = setSubframe(frameText, subframe)
local frame = p.makeFrame( frameText, defaultMod )
local modAliases = frame.mod and p.loadModAliases(frame.mod)
local newFrame = frame
if aliases or modAliases then
local id = frame.name
if frame.mod then
id = frame.mod .. ':' .. id
end
local alias = modAliases and modAliases[id] or
aliases and aliases[id]
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
if not expandedAliases then
expandedAliases = {}
end
expandedAliases[curFrame] = aliasData
end
end
end
end
if subframe then
slot_utils.mergeList( subframes, newFrame )
-- Randomise starting frame for "Any *" aliases, as long as the alias is the only subframe
if frames.randomise ~= 'never' and subframes.randomise == nil and
i18n.matchesPrefix(frame.name, "any") then
subframes.randomise = true
else
subframes.randomise = false
end
if frames.randomise ~= 'never' then
frames.randomise = false
end
if subframe == 'last' then
-- No point having a subframe containing a single frame,
-- or the subframe being the only frame
if #subframes == 1 or #splitFrames == i and #frames == 0 then
slot_utils.mergeList( frames, subframes )
else
table.insert( frames, subframes )
end
subframes = {}
subframe = nil
end
else
-- Randomise starting frame for "Any *" aliases, as long as the alias is the only frame
if frames.randomise == nil and i18n.matchesPrefix(frame.name, "any") then
frames.randomise = true
elseif frames.randomise ~= 'never' then
frames.randomise = false
end
slot_utils.mergeList( frames, newFrame )
end
end
frames.aliasReference = expandedAliases
return frames
end
---- Основная функция. ---------------------------------------------------------
function p.slot_main(pargs)
local frames = pargs.frames
local animated = frames and #frames > 1
local imgClass = pargs.imageClass
local body = mw.html.create( 'span' )
:addClass( 'invslot' )
:addClass( pargs.class ) -- no-op when given nil
:css{ ['vertical-align'] = pargs.align }
:cssText( pargs.style ) -- no-op when given nil
if animated then
body:addClass( 'animated2' )
end
if pargs.default then
body:css( 'background-image', mw.getCurrentFrame():expandTemplate{ title = i18n.templateFileUrl, args = { pargs.default .. '.png' } } )
end
local scale = pargs.scale
if scale ~= 1 then
if scale == 0.5 then
body:css('top', '-1px')
end
local size = scale * 32
body:css{
width = size .. 'px',
height = size .. 'px'
}
end
--mw.logObject( frames )
if not frames then
return tostring( body )
end
local activeFrame = frames.randomise == true and random( #frames ) or 1
for i, frame in ipairs( frames ) do
local item
-- Table is a list, must contain subframes
if frame[1] then
item = body:tag( 'span' ):addClass( 'animated2-subframe' )
local subActiveFrame = frame.randomise and random( #frame ) or 1
for sI, sFrame in ipairs( frame ) do
local sItem = makeItem( sFrame, sI, pargs )
item:node( sItem )
if sI == subActiveFrame then
sItem:addClass( 'animated2-active' )
end
end
else
item = makeItem( frame, i, pargs )
body:node( item )
end
if i == activeFrame and animated then
item:addClass( 'animated2-active' )
end
end
return tostring( body )
end
---- Основная точка входа для Шаблон:Инвентарный слот. -------------------------
-- Другие модули должны вызывать p.slot_main.
----
--[[
Параметры фрейма.
Формат: Локализованное название (оригинальное название): описание
Совместимые с оригинальными:
• первый параметр (содержащий данные слота);
• мод (mod): модификация по умолчанию;
• классизобр (imgclass): CSS-класс изображения;
• стильцифр (numstyle):
• ссылка (link)
• выравн (align)
• класс (class)
• стиль (style)
• умолчание (default)
• назв (title)
• таблспрайтов (spritesheet)
• nourl
НЕ СОВМЕСТИМЫЕ с оригинальными:
• parsed: используйте p.slot_main() вместо parsed.
• модпсевдонимы (modaliases): псевдонимы не глобальны для всех модификаций, а локальны для каждой модификации.
Введённые помимо оригинальных:
• стильизобр
• масштаб
• умолчаниеCSS
• Версия
]]
function p.slot( f )
local args = require(i18n.moduleProcessArgs).merge(true)
local pargs = {}
assert(not args.parsed, "Флаг parsed несовместим с переводной версией. Используйте p.slot_main вместо него.")
local defaultMod = args["мод"]
pargs.stylesheet = args["таблспрайтов"]
pargs.class = args["класс"]
pargs.imageClass = args["классизобр"]
pargs.style = args["стиль"]
pargs.imageStyle = args["стильизобр"]
pargs.numberStyle = args["стильцифр"]
pargs.align = args["выравн"]
pargs.scale = tonumber(args["масштаб"] or 1)
pargs.default = args["умолчание"]
pargs.cssDefault = args["умолчаниеCSS"]
pargs.defaultVersion = args["Версия"]
pargs.link = args["ссылка"]
pargs.title = args["назв"]
pargs.frames = p.parseFrameText({
frames = args[1],
randomize = pargs.class == 'invslot-large' and 'never' or nil,
defaultMod = defaultMod
})
return p.slot_main(pargs)
end
return p
-- </nowiki>