Модуль:Entity Sprite/all: различия между версиями

Материал из Space Station 14 Вики
мНет описания правки
Нет описания правки
 
(не показано 66 промежуточных версий этого же участника)
Строка 1: Строка 1:
local p = {}
local p = {}
local JsonPaths = require('Module:JsonPaths')


-- Загрузка данных
local function normalizeSpritePath(path)
local function loadData(filePath)
if path == nil then
    local page = mw.title.new(filePath)
return nil
    local content = page and page:getContent()
end
    return content and mw.text.jsonDecode(content) or nil
 
path = mw.text.trim(tostring(path))
path = path:gsub("^/Textures/?", "")
 
return path
end
end


-- Проверка равенства двух таблиц
local function normalizeData(data)
local function deepEqual(t1, t2)
if type(data) ~= "table" then
    if t1 == t2 then return true end
return nil
    if type(t1) ~= "table" or type(t2) ~= "table" then return false end
end


    local function isArray(t)
if type(data.id) == "table" then
        local count = 0
return data.id
        for k in pairs(t) do
end
            if type(k) ~= "number" then return false end
            count = count + 1
        end
        return count == #t
    end


    if isArray(t1) and isArray(t2) then
return data
        if #t1 ~= #t2 then return false end
end
        for i = 1, #t1 do
            if not deepEqual(t1[i], t2[i]) then
                return false
            end
        end
        return true
    end


    for k, v in pairs(t1) do
local function buildEntryKey(entry)
        if t2[k] == nil or not deepEqual(v, t2[k]) then
local parts = {}
            return false
        end
    end


    for k, v in pairs(t2) do
local function addField(name, value)
        if t1[k] == nil or not deepEqual(v, t1[k]) then
if value ~= nil then
            return false
table.insert(parts, name .. "=" .. tostring(value))
        end
end
    end
end


    return true
addField("sprite", normalizeSpritePath(entry.sprite))
addField("state", entry.state)
addField("scale", entry.scale)
addField("enableOverrideDir", entry.enableOverrideDir)
addField("overrideDir", entry.overrideDir)
 
if entry.layers and type(entry.layers) == "table" then
local layers = {}
 
for _, layer in ipairs(entry.layers) do
local layerParts = {}
 
for k, v in pairs(layer) do
if type(v) ~= "table" then
layerParts[#layerParts + 1] = k .. "=" .. tostring(v)
end
end
 
table.sort(layerParts)
table.insert(layers, table.concat(layerParts, ","))
end
 
table.sort(layers)
table.insert(parts, "layers=" .. table.concat(layers, "|"))
end
 
table.sort(parts)
return table.concat(parts, ";")
end
end


-- Вспомогательная функция для получения поля без учёта регистра
local function getSpriteStates(entry)
local function getInsensitiveKey(t, target)
local result = {}
    target = target:lower()
 
    for k, v in pairs(t) do
if entry.layers and type(entry.layers) == "table" then
        if type(k) == "string" and k:lower() == target then
for _, layer in ipairs(entry.layers) do
            return v
if layer.visible ~= false and layer.state then
        end
table.insert(result, {
    end
state = tostring(layer.state),
    return nil
sprite = normalizeSpritePath(layer.sprite or entry.sprite)
})
end
end
elseif entry.state and entry.sprite then
table.insert(result, {
state = tostring(entry.state),
sprite = normalizeSpritePath(entry.sprite)
})
end
 
return (#result > 0) and result or nil
end
end


-- Получение пути спрайта
local function getSpritePath(entry)
local function getSpritePath(entry)
    local spriteField = getInsensitiveKey(entry, "Sprite")
if entry.sprite then
    local iconField = getInsensitiveKey(entry, "Icon")
return normalizeSpritePath(entry.sprite)
end
 
if entry.layers and type(entry.layers) == "table" then
for _, layer in ipairs(entry.layers) do
if layer.sprite then
return normalizeSpritePath(layer.sprite)
end
end
end
 
return nil
end
 
local function splitCsv(value)
local result = {}
 
if value == nil then
return nil
end
 
value = mw.text.trim(tostring(value))
if value == "" then
return nil
end
 
for part in mw.text.gsplit(value, ",", true) do
local item = mw.text.trim(part)
if item ~= "" then
table.insert(result, item)
end
end
 
return (#result > 0) and result or nil
end
 
local function buildSet(value)
local list = splitCsv(value)
if not list then
return nil
end
 
local set = {}
for _, item in ipairs(list) do
set[item] = true
end
 
return set
end
 
local function getParents(parentData, id)
if not parentData or type(parentData) ~= "table" or not id then
return nil
end
 
local parents = parentData[id]
if type(parents) ~= "table" then
return nil
end
 
return parents
end
 
local function hasAnyParent(parents, set)
if not parents or not set then
return false
end
 
for _, parent in ipairs(parents) do
if set[parent] then
return true
end
end
 
return false
end
 
local function shouldIncludeEntry(id, whitelistSet, blacklistSet, parentData)
local parents = getParents(parentData, id)
 
if whitelistSet and not hasAnyParent(parents, whitelistSet) then
return false
end
 
if blacklistSet and hasAnyParent(parents, blacklistSet) then
return false
end


    if spriteField and spriteField.sprite then
return true
        return spriteField.sprite
    elseif iconField and iconField.sprite then
        return iconField.sprite
    elseif spriteField and spriteField.layers then
        for _, layer in pairs(spriteField.layers) do
            if layer.sprite then
                return layer.sprite
            end
        end
    end
    return nil
end
end


-- Генерация шаблона repeat
local function filterSpriteData(spriteData, parentData, whitelistSet, blacklistSet, project)
local function generateRepeatTemplate(data)
local result = {}
    local spriteGroups = {}
 
for id, entry in pairs(spriteData) do
if shouldIncludeEntry(id, whitelistSet, blacklistSet, parentData) then
if project == "" or JsonPaths.has(id, project) then
result[id] = entry
end
end
end
 
return result
end
 
local function buildFileLink(id, project)
return "[[:Файл:" .. JsonPaths.prefixFile(id) .. ".png]]"
end
 
local function buildSpriteUrl(baseUrl, spritePath)
return baseUrl .. spritePath
end
 
local function buildSpritePathLink(baseUrl, spritePath)
local url = buildSpriteUrl(baseUrl, spritePath)
return "[" .. url .. " " .. spritePath .. "]"
end
 
local function buildStateLink(baseUrl, spritePath, state)
local url = buildSpriteUrl(baseUrl, spritePath) .. "/" .. state .. ".png"
return "[" .. url .. " " .. state .. "]"
end
 
local function buildSpriteStateLinks(baseUrl, states)
if not states or #states == 0 then
return ""
end
 
local links = {}
 
for _, item in ipairs(states) do
if item.sprite and item.state then
table.insert(links, buildStateLink(baseUrl, item.sprite, item.state))
end
end
 
return table.concat(links, ", ")
end


    for _, entry in pairs(data) do
local function buildRepeatSpriteValue(baseUrl, entry)
        local found = false
local spritePath = getSpritePath(entry)
        for _, group in pairs(spriteGroups) do
if not spritePath then
            if deepEqual(getInsensitiveKey(entry, "Sprite"), getInsensitiveKey(group[1], "Sprite")) and
return ""
              deepEqual(entry.EntityStorageVisuals, group[1].EntityStorageVisuals) and
end
              deepEqual(getInsensitiveKey(entry, "Icon"), getInsensitiveKey(group[1], "Icon")) then
                table.insert(group, entry)
                found = true
                break
            end
        end


        if not found then
local pathLink = buildSpritePathLink(baseUrl, spritePath)
            table.insert(spriteGroups, {entry})
local states = getSpriteStates(entry)
        end
    end


    local result = {}
if states and #states > 0 and states[1].state and states[1].state ~= "" then
    for _, group in pairs(spriteGroups) do
return pathLink .. " (" .. buildStateLink(baseUrl, spritePath, states[1].state) .. ")"
        if #group > 1 then
end
            local idLinks = {}
            for _, entry in pairs(group) do
                table.insert(idLinks, "[[:Файл:" .. entry.id .. ".png]]")
            end
            table.insert(result, mw.getCurrentFrame():preprocess("{{Entity Sprite/Repeat|" .. table.concat(idLinks, " ") .. "|" .. group[1].id .. "}}"))
        end
    end


    return table.concat(result, "\n")
return pathLink
end
end


-- Функция генерации шаблона по записи
local function generateRepeatTemplate(data, project, baseUrl)
local function generateTemplate(entry, param, data)
local spriteGroups = {}
    local spritePath = getSpritePath(entry)
 
    if not entry.id or not spritePath then
for id, entry in pairs(data) do
        return nil
local key = buildEntryKey(entry)
    end


    local spriteField = getInsensitiveKey(entry, "Sprite")
spriteGroups[key] = spriteGroups[key] or {}
table.insert(spriteGroups[key], { id = id, entry = entry })
end


    -- Определяем state
local result = {}
    local state = nil
    if spriteField and spriteField.state then
        state = spriteField.state
    elseif spriteField and spriteField.layers then
        for _, layer in pairs(spriteField.layers) do
            if layer.state then
                state = layer.state
                break
            end
        end
    end


    if param == "image" then
for _, group in pairs(spriteGroups) do
        local stateStr = ""
if #group > 1 then
        if state then
local idLinks = {}
            stateStr = " (state: " .. state .. ")"
        end
        return mw.getCurrentFrame():preprocess("{{Entity Sprite/Image|" .. entry.id .. "|https://github.com/space-syndicate/space-station-14/blob/master/Resources/Textures/" .. spritePath .. stateStr .. "}}")
    end


    return nil
for _, obj in ipairs(group) do
table.insert(idLinks, buildFileLink(obj.id, project))
end
 
local firstId = group[1].id
local firstEntry = group[1].entry
local spriteValue = buildRepeatSpriteValue(baseUrl, firstEntry)
 
table.insert(result, mw.getCurrentFrame():preprocess(
"{{Entity Sprite/Repeat|файлы=" .. table.concat(idLinks, " ") ..
"|перенаправление=" .. JsonPaths.prefixFile(firstId) ..
"|id=" .. firstId ..
"|спрайт=" .. spriteValue ..
"}}"
))
end
end
 
return table.concat(result, "\n")
end
 
local function generateTemplate(id, entry, baseUrl, project)
local spritePath = getSpritePath(entry)
if not id or not spritePath then
return nil
end
 
local states = getSpriteStates(entry)
local stateStr = buildSpriteStateLinks(baseUrl, states)
 
return mw.getCurrentFrame():preprocess(
"{{Entity Sprite/Image|файл=" .. JsonPaths.prefixFile(id) ..
"|id=" .. id ..
"|путь=" .. buildSpriteUrl(baseUrl, spritePath) ..
"|state=" .. stateStr ..
"}}"
)
end
end


function p.main(frame)
function p.main(frame)
    local param = frame.args[1]
local action = frame.args[1]
local json = frame.args.json or "component/Sprite.json"
 
local project = JsonPaths.project()
local baseUrl = JsonPaths.git() .. "/Resources/Textures/"
 
local dataPage = JsonPaths.get(json)
local parentPage = JsonPaths.get("entity parent.json")
 
local spriteDataRaw = mw.loadData(dataPage)
local parentDataRaw = mw.loadData(parentPage)
 
local spriteData = normalizeData(spriteDataRaw)
local parentData = normalizeData(parentDataRaw)
 
if not spriteData or type(spriteData) ~= "table" then
return "Ошибка загрузки JSON: " .. dataPage
end
 
if not parentData or type(parentData) ~= "table" then
return "Ошибка загрузки JSON: " .. parentPage
end
 
local whitelistSet = buildSet(frame.args.whitelistParent)
local blacklistSet = buildSet(frame.args.blacklistParent)
local filteredData = filterSpriteData(spriteData, parentData, whitelistSet, blacklistSet, project)
 
if action == "repeat" then
return generateRepeatTemplate(filteredData, project, baseUrl)
elseif action == "image" then
local result = {}
 
for id, entry in pairs(filteredData) do
local t = generateTemplate(id, entry, baseUrl, project)
if t then
table.insert(result, t)
end
end


    local data = loadData('User:IanComradeBot/prototypes/entity sprite.json')
return table.concat(result, "\n")
    if not data or type(data) ~= 'table' then
end
        return 'Ошибка: Невозможно загрузить данные из JSON.'
    end


    if param == "repeat" then
return nil
        return generateRepeatTemplate(data)
    elseif param == "image" or param == "path" then
        local result = {}
        for _, entry in pairs(data) do
            local template = generateTemplate(entry, param, data)
            if template then
                table.insert(result, template)
            end
        end
        return table.concat(result, "\n")
    else
        return nil
    end
end
end


return p
return p

Текущая версия от 15:31, 23 июня 2026

Для документации этого модуля может быть создана страница Модуль:Entity Sprite/all/doc

local p = {}
local JsonPaths = require('Module:JsonPaths')

local function normalizeSpritePath(path)
	if path == nil then
		return nil
	end

	path = mw.text.trim(tostring(path))
	path = path:gsub("^/Textures/?", "")

	return path
end

local function normalizeData(data)
	if type(data) ~= "table" then
		return nil
	end

	if type(data.id) == "table" then
		return data.id
	end

	return data
end

local function buildEntryKey(entry)
	local parts = {}

	local function addField(name, value)
		if value ~= nil then
			table.insert(parts, name .. "=" .. tostring(value))
		end
	end

	addField("sprite", normalizeSpritePath(entry.sprite))
	addField("state", entry.state)
	addField("scale", entry.scale)
	addField("enableOverrideDir", entry.enableOverrideDir)
	addField("overrideDir", entry.overrideDir)

	if entry.layers and type(entry.layers) == "table" then
		local layers = {}

		for _, layer in ipairs(entry.layers) do
			local layerParts = {}

			for k, v in pairs(layer) do
				if type(v) ~= "table" then
					layerParts[#layerParts + 1] = k .. "=" .. tostring(v)
				end
			end

			table.sort(layerParts)
			table.insert(layers, table.concat(layerParts, ","))
		end

		table.sort(layers)
		table.insert(parts, "layers=" .. table.concat(layers, "|"))
	end

	table.sort(parts)
	return table.concat(parts, ";")
end

local function getSpriteStates(entry)
	local result = {}

	if entry.layers and type(entry.layers) == "table" then
		for _, layer in ipairs(entry.layers) do
			if layer.visible ~= false and layer.state then
				table.insert(result, {
					state = tostring(layer.state),
					sprite = normalizeSpritePath(layer.sprite or entry.sprite)
				})
			end
		end
	elseif entry.state and entry.sprite then
		table.insert(result, {
			state = tostring(entry.state),
			sprite = normalizeSpritePath(entry.sprite)
		})
	end

	return (#result > 0) and result or nil
end

local function getSpritePath(entry)
	if entry.sprite then
		return normalizeSpritePath(entry.sprite)
	end

	if entry.layers and type(entry.layers) == "table" then
		for _, layer in ipairs(entry.layers) do
			if layer.sprite then
				return normalizeSpritePath(layer.sprite)
			end
		end
	end

	return nil
end

local function splitCsv(value)
	local result = {}

	if value == nil then
		return nil
	end

	value = mw.text.trim(tostring(value))
	if value == "" then
		return nil
	end

	for part in mw.text.gsplit(value, ",", true) do
		local item = mw.text.trim(part)
		if item ~= "" then
			table.insert(result, item)
		end
	end

	return (#result > 0) and result or nil
end

local function buildSet(value)
	local list = splitCsv(value)
	if not list then
		return nil
	end

	local set = {}
	for _, item in ipairs(list) do
		set[item] = true
	end

	return set
end

local function getParents(parentData, id)
	if not parentData or type(parentData) ~= "table" or not id then
		return nil
	end

	local parents = parentData[id]
	if type(parents) ~= "table" then
		return nil
	end

	return parents
end

local function hasAnyParent(parents, set)
	if not parents or not set then
		return false
	end

	for _, parent in ipairs(parents) do
		if set[parent] then
			return true
		end
	end

	return false
end

local function shouldIncludeEntry(id, whitelistSet, blacklistSet, parentData)
	local parents = getParents(parentData, id)

	if whitelistSet and not hasAnyParent(parents, whitelistSet) then
		return false
	end

	if blacklistSet and hasAnyParent(parents, blacklistSet) then
		return false
	end

	return true
end

local function filterSpriteData(spriteData, parentData, whitelistSet, blacklistSet, project)
	local result = {}

	for id, entry in pairs(spriteData) do
		if shouldIncludeEntry(id, whitelistSet, blacklistSet, parentData) then
			if project == "" or JsonPaths.has(id, project) then
				result[id] = entry
			end
		end
	end

	return result
end

local function buildFileLink(id, project)
	return "[[:Файл:" .. JsonPaths.prefixFile(id) .. ".png]]"
end

local function buildSpriteUrl(baseUrl, spritePath)
	return baseUrl .. spritePath
end

local function buildSpritePathLink(baseUrl, spritePath)
	local url = buildSpriteUrl(baseUrl, spritePath)
	return "[" .. url .. " " .. spritePath .. "]"
end

local function buildStateLink(baseUrl, spritePath, state)
	local url = buildSpriteUrl(baseUrl, spritePath) .. "/" .. state .. ".png"
	return "[" .. url .. " " .. state .. "]"
end

local function buildSpriteStateLinks(baseUrl, states)
	if not states or #states == 0 then
		return ""
	end

	local links = {}

	for _, item in ipairs(states) do
		if item.sprite and item.state then
			table.insert(links, buildStateLink(baseUrl, item.sprite, item.state))
		end
	end

	return table.concat(links, ", ")
end

local function buildRepeatSpriteValue(baseUrl, entry)
	local spritePath = getSpritePath(entry)
	if not spritePath then
		return ""
	end

	local pathLink = buildSpritePathLink(baseUrl, spritePath)
	local states = getSpriteStates(entry)

	if states and #states > 0 and states[1].state and states[1].state ~= "" then
		return pathLink .. " (" .. buildStateLink(baseUrl, spritePath, states[1].state) .. ")"
	end

	return pathLink
end

local function generateRepeatTemplate(data, project, baseUrl)
	local spriteGroups = {}

	for id, entry in pairs(data) do
		local key = buildEntryKey(entry)

		spriteGroups[key] = spriteGroups[key] or {}
		table.insert(spriteGroups[key], { id = id, entry = entry })
	end

	local result = {}

	for _, group in pairs(spriteGroups) do
		if #group > 1 then
			local idLinks = {}

			for _, obj in ipairs(group) do
				table.insert(idLinks, buildFileLink(obj.id, project))
			end

			local firstId = group[1].id
			local firstEntry = group[1].entry
			local spriteValue = buildRepeatSpriteValue(baseUrl, firstEntry)

			table.insert(result, mw.getCurrentFrame():preprocess(
				"{{Entity Sprite/Repeat|файлы=" .. table.concat(idLinks, " ") ..
				"|перенаправление=" .. JsonPaths.prefixFile(firstId) ..
				"|id=" .. firstId ..
				"|спрайт=" .. spriteValue ..
				"}}"
			))
		end
	end

	return table.concat(result, "\n")
end

local function generateTemplate(id, entry, baseUrl, project)
	local spritePath = getSpritePath(entry)
	if not id or not spritePath then
		return nil
	end

	local states = getSpriteStates(entry)
	local stateStr = buildSpriteStateLinks(baseUrl, states)

	return mw.getCurrentFrame():preprocess(
		"{{Entity Sprite/Image|файл=" .. JsonPaths.prefixFile(id) ..
		"|id=" .. id ..
		"|путь=" .. buildSpriteUrl(baseUrl, spritePath) ..
		"|state=" .. stateStr ..
		"}}"
	)
end

function p.main(frame)
	local action = frame.args[1]
	local json = frame.args.json or "component/Sprite.json"

	local project = JsonPaths.project()
	local baseUrl = JsonPaths.git() .. "/Resources/Textures/"

	local dataPage = JsonPaths.get(json)
	local parentPage = JsonPaths.get("entity parent.json")

	local spriteDataRaw = mw.loadData(dataPage)
	local parentDataRaw = mw.loadData(parentPage)

	local spriteData = normalizeData(spriteDataRaw)
	local parentData = normalizeData(parentDataRaw)

	if not spriteData or type(spriteData) ~= "table" then
		return "Ошибка загрузки JSON: " .. dataPage
	end

	if not parentData or type(parentData) ~= "table" then
		return "Ошибка загрузки JSON: " .. parentPage
	end

	local whitelistSet = buildSet(frame.args.whitelistParent)
	local blacklistSet = buildSet(frame.args.blacklistParent)
	local filteredData = filterSpriteData(spriteData, parentData, whitelistSet, blacklistSet, project)

	if action == "repeat" then
		return generateRepeatTemplate(filteredData, project, baseUrl)
	elseif action == "image" then
		local result = {}

		for id, entry in pairs(filteredData) do
			local t = generateTemplate(id, entry, baseUrl, project)
			if t then
				table.insert(result, t)
			end
		end

		return table.concat(result, "\n")
	end

	return nil
end

return p