Модуль:Serialization/EntityTableSelector

Версия от 05:43, 28 марта 2026; Pok (обсуждение | вклад) (Новая страница: «local p = {} local getArgs = require('Module:Arguments').getArgs local JsonPaths = require('Module:JsonPaths') local entityTableDataCache local function trim(value) return mw.text.trim(value or "") end local function deepCopy(source) if type(source) ~= "table" then return source end local copy = {} for key, value in pairs(source) do copy[key] = deepCopy(value) end return copy end local function deepMerge(tar...»)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

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

local p = {}

local getArgs = require('Module:Arguments').getArgs
local JsonPaths = require('Module:JsonPaths')

local entityTableDataCache

local function trim(value)
    return mw.text.trim(value or "")
end

local function deepCopy(source)
    if type(source) ~= "table" then
        return source
    end

    local copy = {}
    for key, value in pairs(source) do
        copy[key] = deepCopy(value)
    end

    return copy
end

local function deepMerge(target, source)
    if type(target) ~= "table" or type(source) ~= "table" then
        return
    end

    for key, value in pairs(source) do
        if type(value) == "table" and type(target[key]) == "table" then
            deepMerge(target[key], value)
        else
            target[key] = deepCopy(value)
        end
    end
end

local function loadEntityTableData()
    if entityTableDataCache ~= nil then
        return entityTableDataCache
    end

    local moduleName = JsonPaths.get("prototype/entityTable.json")
    local ok, data = pcall(mw.loadData, moduleName)
    if not ok or type(data) ~= "table" then
        entityTableDataCache = {}
        return entityTableDataCache
    end

    entityTableDataCache = data
    return entityTableDataCache
end

local function resolveEntry(data, id)
    if type(data) ~= "table" or id == nil or id == "" then
        return nil
    end

    local base = type(data["default"]) == "table" and deepCopy(data["default"]) or nil
    local idsTable = type(data.id) == "table" and data.id or nil
    local specific = idsTable and idsTable[id] or data[id]

    if type(specific) ~= "table" then
        return base
    end

    if base then
        deepMerge(base, specific)
        return base
    end

    return deepCopy(specific)
end

local function cloneSelector(value)
    if type(value) ~= "table" then
        return nil
    end

    local selector = deepCopy(value)
    selector["!type"] = selector["!type"] or ""
    return selector
end

local function normalizeSelector(value)
    if type(value) ~= "table" then
        return nil
    end

    if type(value["!type"]) == "string" and value["!type"] ~= "" then
        return cloneSelector(value)
    end

    for key, inner in pairs(value) do
        if type(key) == "string" and string.sub(key, 1, 6) == "!type:" and type(inner) == "table" then
            local selector = deepCopy(inner)
            selector["!type"] = string.sub(key, 7)
            return selector
        end
    end

    if type(value.table) == "table" then
        return normalizeSelector(value.table)
    end

    return nil
end

local function normalizePercent(value, multiplyByHundred)
    if type(value) ~= "number" then
        return ""
    end

    local percent = multiplyByHundred and (value * 100) or value
    if percent == math.floor(percent) then
        percent = math.floor(percent)
    end

    return tostring(percent) .. "%"
end

local function parseRange(rangeText)
    if type(rangeText) ~= "string" then
        return nil
    end

    local min, max = rangeText:match("(%d+),%s*(%d+)")
    min = tonumber(min)
    max = tonumber(max)

    if not min or not max then
        return nil
    end

    return min + 1, max + 1
end

local function getSelectorValue(selector)
    selector = normalizeSelector(selector) or selector
    if type(selector) ~= "table" then
        return nil
    end

    if selector.value ~= nil then
        return selector.value
    end

    if type(selector.range) == "string" then
        local min, max = parseRange(selector.range)
        if min and max then
            return tostring(min) .. "-" .. tostring(max)
        end
    end

    return nil
end

local function formatAmount(amountSelector)
    local value = getSelectorValue(amountSelector)
    if value == nil or value == 1 or value == "1" then
        return ""
    end

    return " [" .. tostring(value) .. "]"
end

local function formatRolls(rollSelector)
    local value = getSelectorValue(rollSelector)
    if value == nil then
        return ""
    end

    return "[" .. tostring(value) .. "]"
end

local function formatContent(selector)
    if type(selector) ~= "table" or trim(selector.id) == "" then
        return "Ошибка: отсутствует id у элемента."
    end

    local id = selector.id
    local amount = formatAmount(selector.amount)
    local prob = ""

    if type(selector.prob) == "number" then
        prob = normalizePercent(selector.prob, true)
    elseif type(selector.weight) == "number" then
        prob = normalizePercent(selector.weight, false)
    end

    return "{{LinkCard|название={{#invoke:Entity Lookup|getname|" .. id .. "}} {{#invoke:Prototypes/Хранилище/Предмет|main|framing|stack|" .. id .. "}} " .. amount .. " <span>" .. prob .. "</span>|пин={{#invoke:Prototypes/Хранилище/Предмет|main|framing|contained|" .. id .. "}} {{#invoke:Prototypes/Хранилище/Предмет|main|framing|slot|" .. id .. "}} {{#invoke:Prototypes/Хранилище/Предмет|main|framing|chem|" .. id .. "}}|изображение=[[Файл:" .. id .. ".png|32px]]|горизонт_стиль=1}}"
end

local renderSelector
local renderTableById

local function renderChildren(children, visited)
    if type(children) ~= "table" then
        return ""
    end

    local result = {}
    for _, child in ipairs(children) do
        local selector = normalizeSelector(child)
        if selector then
            local rendered = renderSelector(selector, visited, false)
            if rendered ~= "" then
                result[#result + 1] = rendered
            end
        end
    end

    return table.concat(result)
end

local function renderAllSelector(selector, visited)
    return renderChildren(selector.children, visited)
end

local function renderNestedSelector(selector, visited, wrapped)
    if trim(selector.tableId) == "" then
        return ""
    end

    local result = {}
    local suffix = ""

    if wrapped then
        local rolls = formatRolls(selector.rolls)
        local prob = ""

        if rolls ~= "" then
            suffix = suffix .. ", максимум может выпасть: " .. rolls
        end

        if type(selector.prob) == "number" then
            prob = " <div>" .. normalizePercent(selector.prob, true) .. "</div>"
        elseif type(selector.weight) == "number" then
            prob = " <div>" .. normalizePercent(selector.weight, false) .. "</div>"
        end

        suffix = suffix .. prob

        if suffix ~= "" then
            result[#result + 1] = "{{LinkCard/Сollapsible|название=Группа предметов" .. suffix .. "|содержание="
        end
    end

    result[#result + 1] = renderTableById(selector.tableId, visited)

    if wrapped and suffix ~= "" then
        result[#result + 1] = "}}"
    end

    return table.concat(result)
end

local function renderGroupSelector(selector, visited)
    if type(selector.children) ~= "table" then
        return ""
    end

    local wrapperStart = ""
    local wrapperEnd = ""

    if selector.weight ~= nil and selector.weight ~= "default" then
        wrapperStart = "{{LinkCard/Сollapsible|название=Группа предметов " .. normalizePercent(selector.weight, false) .. "|содержание="
        wrapperEnd = "}}"
    elseif selector.weight == nil then
        wrapperStart = "{{LinkCard/Сollapsible|название=Может выпасть лишь один из:|содержание="
        wrapperEnd = "}}"
    end

    local result = {}
    for _, child in ipairs(selector.children) do
        local childSelector = normalizeSelector(child)
        if childSelector then
            if childSelector["!type"] == "AllSelector" then
                local rendered = renderAllSelector(childSelector, visited)
                if rendered ~= "" then
                    result[#result + 1] = "{{LinkCard/Сollapsible|название=Выпадают только вместе:|содержание=" .. rendered .. "}}"
                end
            elseif childSelector["!type"] == "GroupSelector" then
                result[#result + 1] = renderGroupSelector(childSelector, visited)
            elseif childSelector["!type"] == "NestedSelector" then
                result[#result + 1] = renderNestedSelector(childSelector, visited, true)
            elseif childSelector["!type"] == "EntSelector" then
                result[#result + 1] = formatContent(childSelector)
            end
        end
    end

    return wrapperStart .. table.concat(result) .. wrapperEnd
end

renderTableById = function(tableId, visited)
    visited = visited or {}
    tableId = trim(tableId)

    if tableId == "" then
        return ""
    end

    if visited[tableId] then
        return ""
    end
    visited[tableId] = true

    local data = loadEntityTableData()
    local entry = resolveEntry(data, tableId)
    local selector = entry and normalizeSelector(entry.table) or nil

    if not selector then
        return "Таблица не найдена."
    end

    return renderSelector(selector, visited, false)
end

renderSelector = function(selector, visited, wrapped)
    selector = normalizeSelector(selector)
    if not selector then
        return ""
    end

    if selector["!type"] == "EntSelector" then
        return formatContent(selector)
    elseif selector["!type"] == "AllSelector" then
        return renderAllSelector(selector, visited)
    elseif selector["!type"] == "GroupSelector" then
        return renderGroupSelector(selector, visited)
    elseif selector["!type"] == "NestedSelector" then
        return renderNestedSelector(selector, visited, wrapped ~= false)
    end

    return ""
end

function p.main(frame)
    local args = getArgs(frame, { removeBlanks = false })
    local jsonText = mw.text.unstripNoWiki(args[1] or args.json or "")

    if trim(jsonText) == "" then
        return "Не указан JSON."
    end

    local ok, data = pcall(mw.text.jsonDecode, jsonText)
    if not ok or type(data) ~= "table" then
        return "Некорректный JSON."
    end

    local selector = normalizeSelector(data)
    if not selector then
        return "Не удалось определить тип селектора."
    end

    return renderSelector(selector, {}, false)
end

return p