Модуль:Serialization/EntityTableSelector: различия между версиями
Материал из Space Station 14 Вики
Pok (обсуждение | вклад) Нет описания правки |
Pok (обсуждение | вклад) Нет описания правки |
||
| (не показаны 2 промежуточные версии этого же участника) | |||
| Строка 138: | Строка 138: | ||
local function getSelectorValue(selector) | local function getSelectorValue(selector) | ||
selector = normalizeSelector(selector) or selector | selector = normalizeSelector(selector) or selector | ||
if type(selector) ~= "table" then | if type(selector) ~= "table" then | ||
| Строка 234: | Строка 230: | ||
return table.concat(result) | return table.concat(result) | ||
end | |||
local function renderPlainEntry(entry) | |||
if type(entry) ~= "table" or trim(entry.id) == "" then | |||
return "" | |||
end | |||
return formatContent(entry) | |||
end | end | ||
| Строка 246: | Строка 250: | ||
if selector then | if selector then | ||
local rendered = renderSelector(selector, visited, true, depth) | local rendered = renderSelector(selector, visited, true, depth) | ||
if rendered ~= "" then | |||
result[#result + 1] = rendered | |||
end | |||
else | |||
local rendered = renderPlainEntry(child) | |||
if rendered ~= "" then | if rendered ~= "" then | ||
result[#result + 1] = rendered | result[#result + 1] = rendered | ||
| Строка 283: | Строка 292: | ||
end | end | ||
local content = renderTableById(selector.tableId, visited, | local content = renderTableById(selector.tableId, visited, depth) | ||
if wrapped and suffix ~= "" then | if wrapped and suffix ~= "" then | ||
return wrapCollapsible("Группа предметов" .. suffix, content, depth) | return wrapCollapsible("Группа предметов" .. suffix, content, depth) | ||
| Строка 297: | Строка 306: | ||
local title = "" | local title = "" | ||
local hasWeight = rawget(selector, "weight") ~= nil | |||
if | if hasWeight and selector.weight ~= "default" then | ||
title = "Группа предметов " .. normalizePercent(selector.weight, false) | title = "Группа предметов " .. normalizePercent(selector.weight, false) | ||
elseif | elseif not hasWeight then | ||
title = "Может выпасть лишь один из:" | title = "Может выпасть лишь один из:" | ||
end | end | ||
| Строка 387: | Строка 397: | ||
local selector = normalizeSelector(data) | local selector = normalizeSelector(data) | ||
if not selector then | if not selector then | ||
local plainEntry = renderPlainEntry(data) | |||
if plainEntry ~= "" then | |||
return frame:preprocess(plainEntry) | |||
end | |||
local plainEntries = renderPlainEntries(data) | local plainEntries = renderPlainEntries(data) | ||
if plainEntries ~= "" then | if plainEntries ~= "" then | ||
Версия от 07:56, 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 hasNestedCollapsible(content)
if type(content) ~= "string" or content == "" then
return false
end
return string.find(content, "{{LinkCard/Сollapsible|", 1, true) ~= nil
end
local function wrapCollapsible(title, content, depth)
if trim(title) == "" then
return content or ""
end
local prefix = "{{LinkCard/Сollapsible|"
if (depth or 0) > 0 and not hasNestedCollapsible(content) then
prefix = prefix .. "развернуть=1|"
end
return prefix .. "название=" .. title .. "|содержание=" .. (content or "") .. "}}"
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 renderPlainEntry(entry)
if type(entry) ~= "table" or trim(entry.id) == "" then
return ""
end
return formatContent(entry)
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
else
local rendered = renderPlainEntry(child)
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 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
end
local content = renderTableById(selector.tableId, visited, depth)
if wrapped and suffix ~= "" then
return wrapCollapsible("Группа предметов" .. suffix, content, depth)
end
return content
end
local function renderGroupSelector(selector, visited, depth)
if type(selector.children) ~= "table" then
return ""
end
local title = ""
local hasWeight = rawget(selector, "weight") ~= nil
if hasWeight and selector.weight ~= "default" then
title = "Группа предметов " .. normalizePercent(selector.weight, false)
elseif not hasWeight then
title = "Может выпасть лишь один из:"
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] = wrapCollapsible("Выпадают только вместе:", rendered, (depth or 0) + 1)
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 wrapCollapsible(title, table.concat(result), depth)
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 plainEntry = renderPlainEntry(data)
if plainEntry ~= "" then
return frame:preprocess(plainEntry)
end
local plainEntries = renderPlainEntries(data)
if plainEntries ~= "" then
return frame:preprocess(plainEntries)
end
return ""
end
return frame:preprocess(renderSelector(selector, {}, false, 0))
end
return p