Модуль:Serialization/EntityTableSelector: различия между версиями
Материал из Space Station 14 Вики
Pok (обсуждение | вклад) Нет описания правки |
Pok (обсуждение | вклад) Нет описания правки Метки: ручная отмена отменено |
||
| (не показано 7 промежуточных версий этого же участника) | |||
| Строка 190: | Строка 190: | ||
end | end | ||
return "{{предмет|" .. id .. "|" .. amount .. " <span>" .. prob .. "</span>|wrapper=1|repository=1}}" | |||
end | end | ||
| Строка 196: | Строка 196: | ||
local renderTableById | local renderTableById | ||
local function renderChildren(children, visited) | 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 | if type(children) ~= "table" then | ||
return "" | return "" | ||
| Строка 205: | Строка 228: | ||
local selector = normalizeSelector(child) | local selector = normalizeSelector(child) | ||
if selector then | if selector then | ||
local rendered = renderSelector(selector, visited, | local rendered = renderSelector(selector, visited, true, depth) | ||
if rendered ~= "" then | if rendered ~= "" then | ||
result[#result + 1] = rendered | result[#result + 1] = rendered | ||
| Строка 215: | Строка 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 "" | ||
| Строка 244: | Строка 267: | ||
if suffix ~= "" then | if suffix ~= "" then | ||
result[#result + 1] = " | 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 | ||
| Строка 257: | Строка 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 "" | ||
| Строка 266: | Строка 289: | ||
if selector.weight ~= nil and selector.weight ~= "default" then | if selector.weight ~= nil and selector.weight ~= "default" then | ||
wrapperStart = " | wrapperStart = getCollapsiblePrefix(depth) .. "название=Группа предметов " .. normalizePercent(selector.weight, false) .. "|содержание=" | ||
wrapperEnd = "}}" | wrapperEnd = "}}" | ||
elseif selector.weight == nil then | elseif selector.weight == nil then | ||
wrapperStart = " | wrapperStart = getCollapsiblePrefix(depth) .. "название=Может выпасть лишь один из:|содержание=" | ||
wrapperEnd = "}}" | wrapperEnd = "}}" | ||
end | end | ||
| Строка 278: | Строка 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] = " | 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) | ||
| Строка 295: | Строка 318: | ||
end | end | ||
renderTableById = function(tableId, visited) | renderTableById = function(tableId, visited, depth) | ||
visited = visited or {} | visited = visited or {} | ||
tableId = trim(tableId) | tableId = trim(tableId) | ||
| Строка 313: | Строка 336: | ||
if not selector then | if not selector then | ||
visited[tableId] = nil | |||
return "Таблица не найдена." | return "Таблица не найдена." | ||
end | end | ||
local result = renderSelector(selector, visited, false, depth) | |||
visited[tableId] = nil | |||
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 | ||
| Строка 328: | Строка 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 | ||
| Строка 343: | Строка 369: | ||
if trim(jsonText) == "" then | if trim(jsonText) == "" then | ||
return " | return "" | ||
end | end | ||
local ok, data = pcall(mw.text.jsonDecode, jsonText) | local ok, data = pcall(mw.text.jsonDecode, jsonText) | ||
if not ok or type(data) ~= "table" then | if not ok or type(data) ~= "table" then | ||
return " | return "" | ||
end | end | ||
local selector = normalizeSelector(data) | local selector = normalizeSelector(data) | ||
if not selector then | if not selector then | ||
return " | local plainEntries = renderPlainEntries(data) | ||
if plainEntries ~= "" then | |||
return frame:preprocess(plainEntries) | |||
end | |||
return "" | |||
end | end | ||
return frame:preprocess(renderSelector(selector, {}, false)) | return frame:preprocess(renderSelector(selector, {}, false, 0)) | ||
end | end | ||
return p | return p | ||
Версия от 07:13, 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