Модуль:Component
Материал из Space Station 14 Вики
Для документации этого модуля может быть создана страница Модуль:Component/doc
-- Модуль:Component
-- Расчёты для шаблонов компонентов Шаблон:Component/<Компонент>.
--
-- Как устроено:
-- {{Сущность}} для каждого компонента сущности вызывает Шаблон:Component/<Компонент>.
-- Шаблон, как и остальные шаблоны Component/…, берёт простые поля напрямую ({{{поле}}},
-- {{serialization/damage}}, Модуль:GetField). Этот модуль нужен только там, где данных
-- одного компонента мало: органы, газы, особые вещества, кровь, сравнение с человеком.
-- Список функций — в разделе «Функции для шаблонов» в конце модуля.
-- Используемые данные бота:
-- component/*.json — поля компонентов
-- prototype/Species.json — моб человека для сравнения
-- prototype/DamageType.json, prototype/DamageGroup.json — названия типов урона
-- prototype/Reagent.json — кровь, газы, особые вещества
-- prototype/Satiation.json — голод и жажда (новая схема)
-- prototype/Body.json — органы (старая схема тела, Goob)
-- prototype/MetabolismStage.json, prototype/MetabolismGroup.json, prototype/MetabolizerType.json — органы
-- loc.json — русская локализация
--
-- КАК ПРАВИТЬ
-- * Поменять формулировку — правьте шаблон Шаблон:Component/<Компонент>, а не модуль.
-- * Расцветка: зелёный (class="positive") — хорошее (лечение, преимущества),
-- красный (class="negative") — плохое (урон, недостатки), жёлтый — прочие числа.
-- Жёлтый задан в NUMBER_STYLE.
-- * С кем сравнивать (ratio, satiation, gases, reagents) — BASELINE_SPECIES, по умолчанию человек.
-- * Порядок групп урона и органов — GROUP_ORDER и ORGAN_ORDER.
-- * После правки проверьте страницу во всех проектах через переключатель проекта:
-- у Goob другая схема тела.
-- * Вспомогательные функции скопированы из Модуль:SpeciesStats. Когда все расы
-- переедут на шаблоны, SpeciesStats оставит себе только карточку расы и списки рас,
-- а расчёты будут только здесь.
local p = {}
local getArgs = require('Module:Arguments').getArgs
local JsonPaths = require('Module:JsonPaths')
-- Модуль:GetField — общий модуль вики для чтения выгрузки бота. Через него
-- грузятся все файлы данных, чтобы путь к данным проекта выбирался одинаково
-- на всех страницах (Corvax / Goob / Wega / WL).
local GetField = require('Module:GetField')
local lang = mw.getContentLanguage()
-- Моб, с которым сравниваются голод, жажда и запас воздуха.
local BASELINE_SPECIES = "Human"
-- Порядок групп урона при выводе (только порядок, состав групп — из данных бота).
local GROUP_ORDER = { "Brute", "Burn", "Toxin", "Airloss", "Genetic", "Metaphysical", "Electronic" }
-- Порядок органов при выводе (новая схема тела: ключи InitialBody.organs).
local ORGAN_ORDER = { "Brain", "Heart", "Lungs", "Stomach", "Liver", "Kidneys" }
---------------------------------------------------------------------------
-- Загрузка данных (mw.loadData кэширует страницу на всё время рендера)
---------------------------------------------------------------------------
local cache = {}
local function load(rel)
if cache[rel] == nil then
-- GetField.loadCachedData сам выбирает проект (через JsonPaths)
-- и возвращает nil, если файла нет.
cache[rel] = GetField.loadCachedData(rel) or {}
end
return cache[rel]
end
-- Пустая ли таблица. next и # для таблиц из mw.loadData не работают
-- (это прокси только для чтения), поэтому проверяем через pairs.
local function isEmpty(t)
if type(t) ~= "table" then
return true
end
for _ in pairs(t) do
return false
end
return true
end
-- Запись прототипа/компонента из файла формата { default = {...}, id = { Id = {...} } }.
-- Возвращает функцию-геттер поля с подстановкой default или nil, если записи нет.
local function getEntry(rel, id)
if not id then
return nil
end
local data = load(rel)
local entry = (data.id or {})[id]
if entry == nil then
return nil
end
local default = data.default or {}
return function(field)
local v = entry[field]
-- бот иногда выгружает переопределение пустой таблицей — тогда берём default
if v == nil or (type(v) == "table" and isEmpty(v)) then
v = default[field]
end
return v
end
end
local function loc(key)
if not key then
return nil
end
local v = load("loc.json")[key]
if type(v) == "table" then
v = v._value
end
return v
end
local function ucfirst(s)
return s and lang:ucfirst(s) or s
end
local function list(t)
local out = {}
if type(t) == "table" then
for _, v in ipairs(t) do
out[#out + 1] = v
end
end
return out
end
---------------------------------------------------------------------------
-- Названия
---------------------------------------------------------------------------
local function entityName(id)
return ucfirst(loc("ent-" .. id)) or id
end
local function typeName(typeId)
local dt = getEntry("prototype/DamageType.json", typeId)
return ucfirst(loc(dt and dt("name"))) or typeId
end
local function reagentName(id)
local r = getEntry("prototype/Reagent.json", id)
return ucfirst(loc(r and r("name"))) or id
end
local function metabolizerName(id)
local m = getEntry("prototype/MetabolizerType.json", id)
return ucfirst(loc(m and m("name"))) or id
end
local function stageName(id)
local s = getEntry("prototype/MetabolismStage.json", id) or getEntry("prototype/MetabolismGroup.json", id)
return loc(s and s("name")) or id
end
---------------------------------------------------------------------------
-- Форматирование
---------------------------------------------------------------------------
-- Число с округлением до сотых и десятичной запятой.
local function num(x)
x = tonumber(x) or 0
local r = math.floor(math.abs(x) * 100 + 0.5) / 100
if x < 0 then
r = -r
end
return lang:formatNum(r)
end
-- Процент изменения урона: 1.3 → «+30%», 0.6 → «−40%».
local function percent(coef)
local d = math.floor((coef - 1) * 100 + 0.5)
if d > 0 then
return '<span class="negative">+' .. d .. '%</span>'
end
return '<span class="positive">−' .. (-d) .. '%</span>'
end
-- Ключи числового словаря (пороги урона), отсортированные по возрастанию.
local function sortedNumKeys(t)
local keys = {}
for k in pairs(t or {}) do
if tonumber(k) then
keys[#keys + 1] = k
end
end
table.sort(keys, function(a, b) return tonumber(a) < tonumber(b) end)
return keys
end
-- Типы урона в порядке групп; возвращает список { id, value }.
local function orderedTypes(map)
local out, seen = {}, {}
for _, g in ipairs(GROUP_ORDER) do
local dg = getEntry("prototype/DamageGroup.json", g)
for _, t in ipairs(list(dg and dg("damageTypes"))) do
if map[t] ~= nil and not seen[t] then
out[#out + 1] = { t, map[t] }
seen[t] = true
end
end
end
-- типы, которых нет ни в одной известной группе
local rest = {}
for t, v in pairs(map) do
if not seen[t] then
rest[#rest + 1] = { t, v }
end
end
table.sort(rest, function(a, b) return a[1] < b[1] end)
for _, e in ipairs(rest) do
out[#out + 1] = e
end
return out
end
-- Расцветка страницы (сравнения цветом с человеком нет):
-- зелёный (class="positive") — положительное: лечение, преимущества, баффы;
-- красный (class="negative") — отрицательное: получаемый урон, недостатки, дебаффы;
-- жёлтый (NUMBER_STYLE) — остальные числа (здоровье, скорость, температура и т. п.).
-- Чтобы поменять жёлтый, правьте NUMBER_STYLE.
local NUMBER_STYLE = "color:#e6c07b;font-weight:bold"
-- Обычное число (не урон и не лечение) — жёлтым. v — само число: если это не число,
-- текст выводится без цвета.
local function mark(text, v)
if type(v) == "number" then
return '<span style="' .. NUMBER_STYLE .. '">' .. text .. '</span>'
end
return text
end
-- Температура: в кельвинах, как в игре, а °C — справочно в скобках: «285 K (11,85 °C)».
-- Число в кельвинах выделяется жёлтым (функция mark выше).
local function temperature(k)
return mark(num(k) .. " K", k) .. " (" .. num(k - 273.15) .. " °C)"
end
-- Подсветка целой фразы: good = true — зелёная, false — красная.
local function tone(text, good)
return '<span class="' .. (good and "positive" or "negative") .. '">' .. text .. '</span>'
end
-- DamageSpecifier ({ types = {...}, groups = {...} }) → «уколы 5, ушибы 2».
-- good: true — числа зелёные (лечение), false — красные (урон), nil — без цвета.
local function damageText(spec, good)
if type(spec) ~= "table" then
return nil
end
local function value(v)
if good == nil then
return num(v)
end
return tone(num(v), good)
end
local parts = {}
for _, e in ipairs(orderedTypes(spec.types or {})) do
if e[2] ~= 0 then
parts[#parts + 1] = mw.ustring.lower(typeName(e[1])) .. " " .. value(e[2])
end
end
for gid, v in pairs(spec.groups or {}) do
if v ~= 0 then
local dg = getEntry("prototype/DamageGroup.json", gid)
parts[#parts + 1] = mw.ustring.lower(loc(dg and dg("name")) or gid) .. " " .. value(v)
end
end
if #parts == 0 then
return nil
end
return table.concat(parts, ", ")
end
-- Урон, который получает раса: числа красные.
local function takenText(spec)
return damageText(spec, false)
end
-- Сумма урона в DamageSpecifier (для проверок «есть ли урон вообще»).
local function damageSum(spec)
local s = 0
if type(spec) == "table" then
for _, v in pairs(spec.types or {}) do
s = s + math.abs(v)
end
for _, v in pairs(spec.groups or {}) do
s = s + math.abs(v)
end
end
return s
end
-- Отрицательные значения DamageSpecifier как лечение: «ушибы 0,05», числа зелёные.
local function healText(spec)
local neg = { types = {}, groups = {} }
local any = false
if type(spec) == "table" then
for t, v in pairs(spec.types or {}) do
if v < 0 then
neg.types[t] = -v
any = true
end
end
for g, v in pairs(spec.groups or {}) do
if v < 0 then
neg.groups[g] = -v
any = true
end
end
end
if not any then
return nil
end
return damageText(neg, true)
end
-- Строка интервала: бот отдаёт TimeSpan как число секунд или «0:00:05».
local function seconds(v)
if type(v) == "number" then
return v
end
if type(v) == "string" then
local h, m, s = v:match("^(%d+):(%d+):([%d%.]+)$")
if h then
return tonumber(h) * 3600 + tonumber(m) * 60 + tonumber(s)
end
return tonumber((v:gsub("s$", "")))
end
return nil
end
---------------------------------------------------------------------------
-- Раса и её моб
---------------------------------------------------------------------------
local function species(id)
return getEntry("prototype/Species.json", id)
end
local function speciesMob(id)
local s = species(id)
return s and s("prototype")
end
local function isPlayable(id)
local s = species(id)
return s ~= nil and s("roundStart") == true
end
local function comp(name, mob)
return getEntry("component/" .. name .. ".json", mob)
end
---------------------------------------------------------------------------
-- Органы: новая схема (InitialBody + органы-сущности) и старая (Body prototype, Goob)
---------------------------------------------------------------------------
-- Возвращает список органов с метаболизмом:
-- { { id = "OrganHumanHeart", slot = "Heart", max = 2, types = {...}, stages = {...} }, ... }
local function organs(mob)
local out = {}
local function add(slot, organId)
local m = getEntry("component/Metabolizer.json", organId)
if not m then
return
end
local stages = {}
for _, st in ipairs(list(m("stages"))) do
stages[#stages + 1] = st
end
for _, g in ipairs(list(m("groups"))) do
stages[#stages + 1] = type(g) == "table" and g.id or g
end
out[#out + 1] = {
id = organId,
slot = slot,
max = m("maxReagents") or m("maxPoisonsProcessable"),
types = list(m("metabolizerTypes")),
stages = stages,
interval = seconds(m("updateInterval")),
}
end
local ib = comp("InitialBody", mob)
local initial = ib and ib("organs")
if not isEmpty(initial) then
local used = {}
for _, slot in ipairs(ORGAN_ORDER) do
if initial[slot] then
add(slot, initial[slot])
used[slot] = true
end
end
local rest = {}
for slot, organId in pairs(initial) do
if not used[slot] then
rest[#rest + 1] = slot
end
end
table.sort(rest)
for _, slot in ipairs(rest) do
add(slot, initial[slot])
end
return out
end
local body = comp("Body", mob)
local bp = getEntry("prototype/Body.json", body and body("prototype"))
if bp then
local slots = bp("slots") or {}
local slotNames = {}
for name in pairs(slots) do
slotNames[#slotNames + 1] = name
end
table.sort(slotNames)
-- органы из всех частей тела, в порядке ORGAN_ORDER (ключи в Body — строчными)
local found = {}
for _, name in ipairs(slotNames) do
for k, organId in pairs(slots[name].organs or {}) do
found[#found + 1] = { k, organId }
end
end
local rank = {}
for i, k in ipairs(ORGAN_ORDER) do
rank[mw.ustring.lower(k)] = i
end
table.sort(found, function(a, b)
local ra, rb = rank[a[1]] or 99, rank[b[1]] or 99
if ra ~= rb then
return ra < rb
end
return a[1] < b[1]
end)
for _, e in ipairs(found) do
add(e[1], e[2])
end
end
return out
end
-- Подходит ли условие эффекта реагента под типы метаболизма органа.
-- Понимает MetabolizerTypeCondition (новая схема) и OrganType (старая схема).
local function conditionsMatch(conditions, types)
local has = {}
for _, t in ipairs(types) do
has[t] = true
end
for _, c in ipairs(list(conditions)) do
for ctype, data in pairs(c) do
if ctype == "!type:MetabolizerTypeCondition" then
local hit = false
for _, t in ipairs(list(data.type)) do
if has[t] then
hit = true
end
end
if data.inverted then
hit = not hit
end
if not hit then
return false
end
elseif ctype == "!type:OrganType" then
local hit = has[data.type] == true
if data.shouldHave == false then
hit = not hit
end
if not hit then
return false
end
end
end
end
return true
end
-- Газы при вдохе. Возвращает два множества ID реагентов:
-- breath — эффект Oxygenate (без отрицательного множителя) подходит под тип лёгких;
-- harm — эффект HealthChange с уроном в группе дыхания (Respiration / Gas).
local gasCache = {}
local function gases(types)
local key = table.concat(types, ",")
if gasCache[key] then
return gasCache[key][1], gasCache[key][2]
end
local breath, harm = {}, {}
local reagents = load("prototype/Reagent.json").id or {}
for rid, r in pairs(reagents) do
local metabolisms = r.metabolisms
if type(metabolisms) == "table" then
local ok, bad = false, false
for group, entry in pairs(metabolisms) do
for _, eff in ipairs(list(type(entry) == "table" and entry.effects)) do
local ox = eff["!type:Oxygenate"]
if ox and (ox.factor == nil or ox.factor > 0) and conditionsMatch(ox.conditions, types) then
ok = true
end
local hc = eff["!type:HealthChange"]
if hc and (group == "Respiration" or group == "Gas") and conditionsMatch(hc.conditions, types) then
local d = hc.damage or {}
local sum = 0
for _, v in pairs(d.types or {}) do
sum = sum + v
end
for _, v in pairs(d.groups or {}) do
sum = sum + v
end
if sum > 0 then
bad = true
end
end
end
end
if ok then
breath[rid] = true
elseif bad then
harm[rid] = true
end
end
end
gasCache[key] = { breath, harm }
return breath, harm
end
-- Типы метаболизма лёгких из списка органов.
local function lungTypes(orgs)
for _, o in ipairs(orgs) do
for _, st in ipairs(o.stages) do
if st == "Respiration" or st == "Gas" then
return o.types
end
end
end
return nil
end
-- Названия реагентов из множества (кроме тех, что есть в except и except2), по алфавиту, строчными.
local function reagentList(set, except, except2)
local out = {}
for rid in pairs(set) do
if not (except and except[rid]) and not (except2 and except2[rid]) then
out[#out + 1] = mw.ustring.lower(reagentName(rid))
end
end
table.sort(out)
return out
end
-- Вещества, которые действуют на расу не так, как на человека.
-- Для каждого вещества складываются все эффекты HealthChange/EvenHealthChange,
-- которые сработают у расы и у человека (по условиям MetabolizerTypeCondition
-- и OrganType; пороги вроде «от 15 ед.» не учитываются). Итог за тик: меньше 0 — лечит,
-- больше 0 — вредит. Сравнение с человеком даёт четыре списка:
-- heal — расу лечит, а человека нет; harm — расе вредит, а человеку нет;
-- blocked — человека лечит, а расу нет; safe — человеку вредит, а расе нет.
local function specialReagents(types, baseTypes)
local function set(list_)
local t = {}
for _, v in ipairs(list_) do
t[v] = true
end
return t
end
local race, base = set(types), set(baseTypes)
-- Сработает ли эффект у тела с типами метаболизма has.
local function applies(conditions, has)
for _, c in ipairs(list(conditions)) do
for ctype, data in pairs(c) do
if ctype == "!type:MetabolizerTypeCondition" then
local hit = false
for _, t in ipairs(list(data.type)) do
if has[t] then
hit = true
end
end
if hit == (data.inverted == true) then
return false
end
elseif ctype == "!type:OrganType" then
if (has[data.type] == true) ~= (data.shouldHave ~= false) then
return false
end
end
end
end
return true
end
-- Упоминает ли эффект тип метаболизма (иначе он одинаков для всех рас).
local function typed(conditions)
for _, c in ipairs(list(conditions)) do
if c["!type:MetabolizerTypeCondition"] or c["!type:OrganType"] then
return true
end
end
return false
end
local function total(t)
local sum = 0
for _, v in pairs(t or {}) do
if type(v) == "number" then
sum = sum + v
end
end
return sum
end
local heal, harm, blocked, safe = {}, {}, {}, {}
for rid, r in pairs(load("prototype/Reagent.json").id or {}) do
if type(r.metabolisms) == "table" then
local raceSum, baseSum, special = 0, 0, false
for group, entry in pairs(r.metabolisms) do
if group ~= "Respiration" and group ~= "Gas" and type(entry) == "table" then
for _, eff in ipairs(list(entry.effects)) do
local amount, conditions
local hc, ehc = eff["!type:HealthChange"], eff["!type:EvenHealthChange"]
if hc then
local d = hc.damage or {}
amount, conditions = total(d.types) + total(d.groups), hc.conditions
elseif ehc then
amount, conditions = total(ehc.damage), ehc.conditions
end
if amount and amount ~= 0 and typed(conditions) then
special = true
if applies(conditions, race) then
raceSum = raceSum + amount
end
if applies(conditions, base) then
baseSum = baseSum + amount
end
end
end
end
end
if special then
if raceSum < 0 and baseSum >= 0 then
heal[rid] = true
elseif raceSum > 0 and baseSum <= 0 then
harm[rid] = true
elseif baseSum < 0 and raceSum >= 0 then
blocked[rid] = true
elseif baseSum > 0 and raceSum <= 0 then
safe[rid] = true
end
end
end
end
return heal, harm, blocked, safe
end
-- Типы метаболизма всех органов, кроме лёгких.
local function bodyTypes(orgs)
local seen, out = {}, {}
for _, o in ipairs(orgs) do
local lung = false
for _, st in ipairs(o.stages) do
if st == "Respiration" or st == "Gas" then
lung = true
end
end
if not lung then
for _, t in ipairs(o.types) do
if not seen[t] then
seen[t] = true
out[#out + 1] = t
end
end
end
end
return out
end
---------------------------------------------------------------------------
-- Голод и жажда: скорость относительно человека
---------------------------------------------------------------------------
-- Скорость убывания (положительное число) для Hunger/Thirst моба или nil.
local function satiationRate(mob, kind)
local sat = comp("Satiation", mob)
local sats = sat and sat("satiations")
if not isEmpty(sats) and sats[kind] then
local proto = getEntry("prototype/Satiation.json", sats[kind].prototype)
local rate = proto and proto("baseChangeRate")
return rate and math.abs(rate) or nil
end
local c = comp(kind, mob)
local rate = c and c("baseDecayRate")
return rate and math.abs(rate) or nil
end
local function rateCompare(rate, base)
if not rate or not base or base == 0 then
return nil
end
local ratio = rate / base
if math.abs(ratio - 1) < 0.01 then
return nil
end
if ratio > 1 then
return "в " .. num(ratio) .. " раза быстрее, чем у человека"
end
if ratio == 0 then
return "не убывает"
end
return "в " .. num(1 / ratio) .. " раза медленнее, чем у человека"
end
-- Пороги состояний моба: словарь «урон → состояние» ({ ["100"] = "Critical", ... }).
local function thresholdsOf(mob)
local thr = mob and comp("MobThresholds", mob)
return thr and thr("thresholds")
end
-- Объём крови моба (новая и старая схема Bloodstream).
local function bloodOf(bs)
if not bs then
return nil, nil
end
local ref = bs("bloodReferenceSolution")
if type(ref) == "table" and not isEmpty(ref.reagents) then
local r = ref.reagents[1]
return r.ReagentId, r.Quantity
end
return bs("bloodReagent"), bs("bloodMaxVolume")
end
-- Название органа. Если у органа нет своего названия (например, у органов дворфа
-- в Corvax в локализации стоит общее «орган»), берём название базового органа слота.
local function organName(o)
local own = loc("ent-" .. o.id)
local generic = loc("ent-OrganBase")
if own and own ~= generic then
return ucfirst(own)
end
local base = o.slot and loc("ent-OrganBase" .. lang:ucfirst(o.slot))
return ucfirst(base or own) or o.id
end
-- Не больше limit элементов списка, остальное — «и ещё N».
local function capped(items, limit)
if #items <= limit then
return table.concat(items, ", ")
end
local head = {}
for i = 1, limit do
head[i] = items[i]
end
return table.concat(head, ", ") .. " и ещё " .. (#items - limit)
end
---------------------------------------------------------------------------
-- Функции для шаблонов Шаблон:Component/<Компонент>
---------------------------------------------------------------------------
-- Здесь только то, что нельзя достать из полей компонента напрямую через {{{поле}}}:
-- данные из нескольких выгрузок сразу и сравнение с человеком.
-- Вызов как у Модуль:GetField — первым параметром ID сущности:
-- {{#invoke:Component|organs|{{{id}}}}}
-- Если данных нет, функция возвращает пустую строку: в шаблоне это проверяется
-- через {{#vardefine:…}} и {{#if:{{#var:…}}|…}}.
--
-- blood|{{{id}}} — название крови («Кровь»)
-- bloodVolume|{{{id}}} — объём крови («600»)
-- satiation|{{{id}}}|Hunger — голод (Thirst — жажда) в сравнении с человеком:
-- «в 2 раза быстрее, чем у человека», «не испытывает»
-- ratio|{{{id}}}|Компонент|поле — во сколько раз поле больше, чем у человека («1.5»);
-- пусто, если так же
-- gases|{{{id}}}|breath — чем дышит; harm — вредные газы, которые не вредны
-- человеку; safe — вредные человеку, но не ей
-- reagents|{{{id}}}|heal — вещества, которые действуют не так, как на человека:
-- heal (лечат), harm (вредят), blocked (не лечат),
-- safe (не вредят); больше 20 — «и ещё N»
-- organs|{{{id}}} — список органов, по пункту «* » на орган
-- hasDamage|{{{id}}}|Компонент|поле — «1», если суммарный урон в поле больше 0 (иначе пусто)
-- flammable|{{{id}}} — горение по сравнению с человеком: «сильнее», «слабее»,
-- «нет» (огонь не наносит урона) или пусто (так же / не моб)
local function idArg(frame)
local args = getArgs(frame)
return mw.text.trim(args[1] or ""), args
end
-- Моб человека, с которым идёт сравнение.
local function baseMob()
return speciesMob(BASELINE_SPECIES)
end
-- Число с точкой, до сотых: как {{{поле}}} в остальных шаблонах вики.
local function plain(x)
return tostring(math.floor(x * 100 + 0.5) / 100)
end
-- Новая схема крови (bloodReferenceSolution) и старая (bloodReagent, bloodMaxVolume).
function p.blood(frame)
local id = idArg(frame)
local reagent = bloodOf(id ~= "" and comp("Bloodstream", id))
return reagent and reagentName(reagent) or ""
end
function p.bloodVolume(frame)
local id = idArg(frame)
local _, volume = bloodOf(id ~= "" and comp("Bloodstream", id))
volume = tonumber(volume)
return volume and plain(volume) or ""
end
function p.satiation(frame)
local id, args = idArg(frame)
if id == "" then
return ""
end
local kind = args[2] == "Thirst" and "Thirst" or "Hunger"
local base = baseMob()
local rate, baseRate = satiationRate(id, kind), satiationRate(base, kind)
if rate == nil then
return (baseRate and id ~= base) and "не испытывает" or ""
end
return rateCompare(rate, baseRate) or ""
end
function p.ratio(frame)
local id, args = idArg(frame)
local c = id ~= "" and comp(args[2] or "", id)
local b = comp(args[2] or "", baseMob())
local v = c and tonumber(c(args[3] or ""))
local base = b and tonumber(b(args[3] or ""))
if not v or not base or base == 0 or math.abs(v / base - 1) <= 0.01 then
return ""
end
return plain(v / base)
end
-- Газы определяются по типу метаболизма лёгких сущности.
function p.gases(frame)
local id, args = idArg(frame)
local types = id ~= "" and lungTypes(organs(id))
if not types then
return ""
end
local breath, harm = gases(types)
local mode = args[2] or "breath"
local out
if mode == "breath" then
out = reagentList(breath)
else
local baseTypes = lungTypes(organs(baseMob()))
if not baseTypes then
return ""
end
local _, baseHarm = gases(baseTypes)
if mode == "harm" then
out = reagentList(harm, baseHarm)
else
out = reagentList(baseHarm, harm, breath)
end
end
return table.concat(out, ", ")
end
function p.reagents(frame)
local id, args = idArg(frame)
local types = id ~= "" and bodyTypes(organs(id)) or {}
if #types == 0 then
return ""
end
local base = baseMob()
local heal, harm, blocked, safe = specialReagents(types, base and bodyTypes(organs(base)) or {})
local set = ({ heal = heal, harm = harm, blocked = blocked, safe = safe })[args[2] or ""]
if not set then
return ""
end
local items = reagentList(set)
return #items > 0 and capped(items, 20) or ""
end
-- Новая схема тела (InitialBody) и старая (Body, Goob).
function p.organs(frame)
local id = idArg(frame)
if id == "" then
return ""
end
local lines = {}
for _, o in ipairs(organs(id)) do
local stages = {}
for _, st in ipairs(o.stages) do
stages[#stages + 1] = mw.ustring.lower(stageName(st))
end
local types = {}
for _, t in ipairs(o.types) do
types[#types + 1] = metabolizerName(t)
end
local line = "* '''" .. organName(o) .. "'''"
if #stages > 0 then
line = line .. " (" .. table.concat(stages, ", ") .. ")"
end
local details = {}
if o.max and not (#o.stages == 1 and (o.stages[1] == "Respiration" or o.stages[1] == "Gas")) then
details[#details + 1] = "одновременно обрабатывает веществ: '''" .. plain(o.max) .. "'''"
end
if #types > 0 then
details[#details + 1] = "тип метаболизма: " .. table.concat(types, ", ")
end
if o.interval and o.interval ~= 1 then
details[#details + 1] = "срабатывает раз в '''" .. plain(o.interval) .. "''' сек."
end
if #details > 0 then
line = line .. " — " .. table.concat(details, "; ")
end
lines[#lines + 1] = line .. "."
end
return table.concat(lines, "\n")
end
-- Только для мобов (есть MobState): у предметов сравнение с человеком не имеет смысла.
function p.flammable(frame)
local id = idArg(frame)
local f = id ~= "" and comp("MobState", id) and comp("Flammable", id)
local base = comp("Flammable", baseMob())
if not f or not base or id == baseMob() then
return ""
end
local a, b = damageSum(f("damage")), damageSum(base("damage"))
if b == 0 or math.abs(a / b - 1) < 0.01 then
return ""
end
if a == 0 then
return "нет"
end
return a > b and "сильнее" or "слабее"
end
-- Урон вида {types = {...}} бывает «пустым» (все значения 0): тогда строку выводить не нужно.
function p.hasDamage(frame)
local id, args = idArg(frame)
local c = id ~= "" and comp(args[2] or "", id)
return (c and damageSum(c(args[3] or "")) > 0) and "1" or ""
end
return p