Модуль:Serialization/EntityTableSelector: различия между версиями

Материал из Space Station 14 Вики
Нет описания правки
Нет описания правки
Метка: отменено
Строка 190: Строка 190:
     end
     end


return "{{предмет|".. id .."|" .. amount .. " <span>" .. prob .. "</span>|wrapper=1|repository=1}}"
    return "{{предмет|" .. id .. "|" .. amount .. " <span>" .. prob .. "</span>|wrapper=1|repository=1}}"
end
end


local renderSelector
local renderSelector
local renderTableById
local renderTableById
local function getCollapsiblePrefix(depth)
    if (depth or 0) > 0 then
        return "{{LinkCard/Сollapsible|развернуть=1|"
    end
    return "{{LinkCard/Сollapsible|"
end


local function renderPlainEntries(entries)
local function renderPlainEntries(entries)
Строка 211: Строка 219:
end
end


local function renderChildren(children, visited)
local function renderChildren(children, visited, depth)
     if type(children) ~= "table" then
     if type(children) ~= "table" then
         return ""
         return ""
Строка 220: Строка 228:
         local selector = normalizeSelector(child)
         local selector = normalizeSelector(child)
         if selector then
         if selector then
             local rendered = renderSelector(selector, visited, true)
             local rendered = renderSelector(selector, visited, true, depth)
             if rendered ~= "" then
             if rendered ~= "" then
                 result[#result + 1] = rendered
                 result[#result + 1] = rendered
Строка 230: Строка 238:
end
end


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


local function renderNestedSelector(selector, visited, wrapped)
local function renderNestedSelector(selector, visited, wrapped, depth)
     if trim(selector.tableId) == "" then
     if trim(selector.tableId) == "" then
         return ""
         return ""
Строка 259: Строка 267:


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


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


     if wrapped and suffix ~= "" then
     if wrapped and suffix ~= "" then
Строка 272: Строка 280:
end
end


local function renderGroupSelector(selector, visited)
local function renderGroupSelector(selector, visited, depth)
     if type(selector.children) ~= "table" then
     if type(selector.children) ~= "table" then
         return ""
         return ""
Строка 281: Строка 289:


     if selector.weight ~= nil and selector.weight ~= "default" then
     if selector.weight ~= nil and selector.weight ~= "default" then
         wrapperStart = "{{LinkCard/Сollapsible|название=Группа предметов " .. normalizePercent(selector.weight, false) .. "|содержание="
         wrapperStart = getCollapsiblePrefix(depth) .. "название=Группа предметов " .. normalizePercent(selector.weight, false) .. "|содержание="
         wrapperEnd = "}}"
         wrapperEnd = "}}"
     elseif selector.weight == nil then
     elseif selector.weight == nil then
         wrapperStart = "{{LinkCard/Сollapsible|название=Может выпасть лишь один из:|содержание="
         wrapperStart = getCollapsiblePrefix(depth) .. "название=Может выпасть лишь один из:|содержание="
         wrapperEnd = "}}"
         wrapperEnd = "}}"
     end
     end
Строка 293: Строка 301:
         if childSelector then
         if childSelector then
             if childSelector["!type"] == "AllSelector" then
             if childSelector["!type"] == "AllSelector" then
                 local rendered = renderAllSelector(childSelector, visited)
                 local rendered = renderAllSelector(childSelector, visited, (depth or 0) + 1)
                 if rendered ~= "" then
                 if rendered ~= "" then
                     result[#result + 1] = "{{LinkCard/Сollapsible|название=Выпадают только вместе:|содержание=" .. rendered .. "}}"
                     result[#result + 1] = getCollapsiblePrefix((depth or 0) + 1) .. "название=Выпадают только вместе:|содержание=" .. rendered .. "}}"
                 end
                 end
             elseif childSelector["!type"] == "GroupSelector" then
             elseif childSelector["!type"] == "GroupSelector" then
                 result[#result + 1] = renderGroupSelector(childSelector, visited)
                 result[#result + 1] = renderGroupSelector(childSelector, visited, (depth or 0) + 1)
             elseif childSelector["!type"] == "NestedSelector" then
             elseif childSelector["!type"] == "NestedSelector" then
                 result[#result + 1] = renderNestedSelector(childSelector, visited, true)
                 result[#result + 1] = renderNestedSelector(childSelector, visited, true, (depth or 0) + 1)
             elseif childSelector["!type"] == "EntSelector" then
             elseif childSelector["!type"] == "EntSelector" then
                 result[#result + 1] = formatContent(childSelector)
                 result[#result + 1] = formatContent(childSelector)
Строка 310: Строка 318:
end
end


renderTableById = function(tableId, visited)
renderTableById = function(tableId, visited, depth)
     visited = visited or {}
     visited = visited or {}
     tableId = trim(tableId)
     tableId = trim(tableId)
Строка 332: Строка 340:
     end
     end


     local result = renderSelector(selector, visited, false)
     local result = renderSelector(selector, visited, false, depth)
     visited[tableId] = nil
     visited[tableId] = nil
     return result
     return result
end
end


renderSelector = function(selector, visited, wrapped)
renderSelector = function(selector, visited, wrapped, depth)
     selector = normalizeSelector(selector)
     selector = normalizeSelector(selector)
     if not selector then
     if not selector then
Строка 346: Строка 354:
         return formatContent(selector)
         return formatContent(selector)
     elseif selector["!type"] == "AllSelector" then
     elseif selector["!type"] == "AllSelector" then
         return renderAllSelector(selector, visited)
         return renderAllSelector(selector, visited, depth)
     elseif selector["!type"] == "GroupSelector" then
     elseif selector["!type"] == "GroupSelector" then
         return renderGroupSelector(selector, visited)
         return renderGroupSelector(selector, visited, depth)
     elseif selector["!type"] == "NestedSelector" then
     elseif selector["!type"] == "NestedSelector" then
         return renderNestedSelector(selector, visited, wrapped ~= false)
         return renderNestedSelector(selector, visited, wrapped ~= false, depth)
     end
     end


Строка 379: Строка 387:
     end
     end


     return frame:preprocess(renderSelector(selector, {}, false))
     return frame:preprocess(renderSelector(selector, {}, false, 0))
end
end


return p
return p

Версия от 07:01, 28 марта 2026

Для документации этого модуля может быть создана страница Модуль: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 "{{предмет|" .. id .. "|" .. amount .. " <span>" .. prob .. "</span>|wrapper=1|repository=1}}"
end

local renderSelector
local renderTableById

local function getCollapsiblePrefix(depth)
    if (depth or 0) > 0 then
        return "{{LinkCard/Сollapsible|развернуть=1|"
    end

    return "{{LinkCard/Сollapsible|"
end

local function renderPlainEntries(entries)
    if type(entries) ~= "table" then
        return ""
    end

    local result = {}
    for _, entry in ipairs(entries) do
        if type(entry) == "table" and trim(entry.id) ~= "" then
            result[#result + 1] = formatContent(entry)
        end
    end

    return table.concat(result)
end

local function renderChildren(children, visited, depth)
    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, true, depth)
            if rendered ~= "" then
                result[#result + 1] = rendered
            end
        end
    end

    return table.concat(result)
end

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

local function renderNestedSelector(selector, visited, wrapped, depth)
    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] = getCollapsiblePrefix(depth) .. "название=Группа предметов" .. suffix .. "|содержание="
        end
    end

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

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

    return table.concat(result)
end

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

    local wrapperStart = ""
    local wrapperEnd = ""

    if selector.weight ~= nil and selector.weight ~= "default" then
        wrapperStart = getCollapsiblePrefix(depth) .. "название=Группа предметов " .. normalizePercent(selector.weight, false) .. "|содержание="
        wrapperEnd = "}}"
    elseif selector.weight == nil then
        wrapperStart = getCollapsiblePrefix(depth) .. "название=Может выпасть лишь один из:|содержание="
        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, (depth or 0) + 1)
                if rendered ~= "" then
                    result[#result + 1] = getCollapsiblePrefix((depth or 0) + 1) .. "название=Выпадают только вместе:|содержание=" .. rendered .. "}}"
                end
            elseif childSelector["!type"] == "GroupSelector" then
                result[#result + 1] = renderGroupSelector(childSelector, visited, (depth or 0) + 1)
            elseif childSelector["!type"] == "NestedSelector" then
                result[#result + 1] = renderNestedSelector(childSelector, visited, true, (depth or 0) + 1)
            elseif childSelector["!type"] == "EntSelector" then
                result[#result + 1] = formatContent(childSelector)
            end
        end
    end

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

renderTableById = function(tableId, visited, depth)
    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
        visited[tableId] = nil
        return "Таблица не найдена."
    end

    local result = renderSelector(selector, visited, false, depth)
    visited[tableId] = nil
    return result
end

renderSelector = function(selector, visited, wrapped, depth)
    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, depth)
    elseif selector["!type"] == "GroupSelector" then
        return renderGroupSelector(selector, visited, depth)
    elseif selector["!type"] == "NestedSelector" then
        return renderNestedSelector(selector, visited, wrapped ~= false, depth)
    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 ""
    end

    local ok, data = pcall(mw.text.jsonDecode, jsonText)
    if not ok or type(data) ~= "table" then
        return ""
    end

    local selector = normalizeSelector(data)
    if not selector then
        local plainEntries = renderPlainEntries(data)
        if plainEntries ~= "" then
            return frame:preprocess(plainEntries)
        end

        return ""
    end

    return frame:preprocess(renderSelector(selector, {}, false, 0))
end

return p