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

Нет описания правки
Нет описания правки
(не показаны 2 промежуточные версии этого же участника)
Строка 25: Строка 25:
--  {{#invoke:SpeciesStats|features|Arachnid|header=Особенности|notes=...}} — особенности (ручные строки + из данных)
--  {{#invoke:SpeciesStats|features|Arachnid|header=Особенности|notes=...}} — особенности (ручные строки + из данных)
--  {{#invoke:SpeciesStats|missing|Human, Arachnid, ...}} — расы проекта, которых нет в списке
--  {{#invoke:SpeciesStats|missing|Human, Arachnid, ...}} — расы проекта, которых нет в списке
--  {{#invoke:SpeciesStats|image|Arachnid}}  — имя файла картинки расы (для шторки)
--  {{#invoke:SpeciesStats|nav|Human:Люди, ...}}    — сетка иконок рас (сейчас на странице не используется)
--  {{#invoke:SpeciesStats|summary|Human:Люди, ...}} — сводная таблица рас (сейчас не используется)
--
--
-- КАК ПРАВИТЬ
-- КАК ПРАВИТЬ
Строка 39: Строка 42:
--  * Особенность, которой нет в данных, пишется не здесь, а на самой странице
--  * Особенность, которой нет в данных, пишется не здесь, а на самой странице
--    в параметре notes= у нужной расы.
--    в параметре notes= у нужной расы.
--  * Расцветка: всё сравнивается с человеком (BASELINE_SPECIES). Зелёный — лучше,
--  * Расцветка: зелёный — положительное (лечение, преимущества), красный —
--    красный — хуже, без цвета — как у человека. Для чисел используйте mark(),
--    отрицательное (урон, недостатки). Для урона используйте takenText(), для лечения —
--    для урона — takenText(), для лечения — healText(), для фраз — tone().
--    healText(), для фраз — tone(). Режим сравнения с человеком — COMPARE_WITH_HUMAN.
--  * Порядок групп урона и органов задают GROUP_ORDER и ORGAN_ORDER ниже.
--  * Порядок групп урона и органов задают GROUP_ORDER и ORGAN_ORDER ниже.
--    Состав групп и органов там не задаётся: его дают данные.
--    Состав групп и органов там не задаётся: его дают данные.
Строка 228: Строка 231:
return out
return out
end
end
-- Цвет значений, которые совпадают с человеческими (нейтральный жёлтый).
-- Расцветка страницы:
--  зелёный (class="positive") — положительное: лечение, преимущества, баффы;
--  красный (class="negative") — отрицательное: получаемый урон, недостатки, дебаффы.
--  жёлтый (SAME_STYLE) — остальные числа (здоровье, скорость, температура и т. п.).
-- COMPARE_WITH_HUMAN = true включает другой режим: числа красятся по сравнению с человеком
-- (лучше — зелёный, хуже — красный, так же — SAME_STYLE). Сейчас выключен.
local COMPARE_WITH_HUMAN = false
-- Нейтральный цвет чисел.
local SAME_STYLE = "color:#e6c07b;font-weight:bold"
-- true, пока выводится сам человек: сравнивать его с собой незачем, значения без цвета.
local isBaseline = false


-- Подсветка значения в сравнении с человеком — единая расцветка страницы:
-- Подсветка значения в сравнении с человеком — единая расцветка страницы:
--  зелёный (class="positive") — лучше, чем у человека;
--  зелёный (class="positive") — лучше, чем у человека;
--  красный (class="negative") — хуже, чем у человека;
--  красный (class="negative") — хуже, чем у человека;
--  без цвета — так же, как у человека, или сравнивать не с чем.
--  жёлтый (SAME_STYLE) — так же, как у человека;
--  без цвета — сравнивать не с чем.
-- При наведении на подсвеченное значение видно значение человека.
-- При наведении на подсвеченное значение видно значение человека.
-- higherBetter: true — чем больше, тем лучше (скорость, здоровье), false — наоборот (урон).
-- higherBetter: true — чем больше, тем лучше (скорость, здоровье), false — наоборот (урон).
-- fmt — как показать значение человека в подсказке (по умолчанию num).
-- fmt — как показать значение человека в подсказке (по умолчанию num).
local function mark(text, v, base, higherBetter, fmt)
local function mark(text, v, base, higherBetter, fmt)
if type(v) ~= "number" or type(base) ~= "number" or math.abs(v - base) < 1e-6 then
if not COMPARE_WITH_HUMAN then
-- Обычное число (не урон и не лечение): нейтральный цвет, для красоты.
if type(v) == "number" then
return '<span style="' .. SAME_STYLE .. '">' .. text .. '</span>'
end
return text
end
if type(v) ~= "number" or type(base) ~= "number" then
return text
end
if isBaseline then
return text
return text
end
if math.abs(v - base) < 1e-6 then
return '<span style="' .. SAME_STYLE .. '" title="Как у человека">' .. text .. '</span>'
end
end
local better = (v > base) == higherBetter
local better = (v > base) == higherBetter
Строка 258: Строка 288:
end
end
local function value(v, b)
local function value(v, b)
if base then
if base and COMPARE_WITH_HUMAN then
return mark(num(v), v, b or 0, higherBetter)
return mark(num(v), v, b or 0, higherBetter)
elseif base then
-- Без сравнения: урон, который получает раса, — красный, лечение — зелёный.
return tone(num(v), higherBetter)
end
end
return num(v)
return num(v)
Строка 793: Строка 826:
for _, k in ipairs(sortedNumKeys(th)) do
for _, k in ipairs(sortedNumKeys(th)) do
if not out[th[k]] then
if not out[th[k]] then
out[th[k]] = k
out[th[k]] = tonumber(k)
end
end
end
end
Строка 829: Строка 862:
for _, k in ipairs(sortedNumKeys(th)) do
for _, k in ipairs(sortedNumKeys(th)) do
local state = th[k]
local state = th[k]
local v = mark(num(k), k, base[state], true)
local v = mark(num(k), tonumber(k), base[state], true)
if state == "PreCritical" then
if state == "PreCritical" then
parts[#parts + 1] = "подкрит — с " .. v .. " урона"
parts[#parts + 1] = "подкрит — с " .. v .. " урона"
Строка 871: Строка 904:
local parts = {}
local parts = {}
for _, k in ipairs(sortedNumKeys(st)) do
for _, k in ipairs(sortedNumKeys(st)) do
local b
-- Множитель скорости человека при том же уроне: последний его порог не выше k
-- (до первого порога человек не замедлен, множитель 1).
local b, bestKey = 1, nil
for bk, bv in pairs(bst) do
for bk, bv in pairs(bst) do
if tonumber(bk) == tonumber(k) then
local n = tonumber(bk)
b = bv
if n and n <= tonumber(k) and (not bestKey or n > bestKey) then
b, bestKey = bv, n
end
end
end
end
Строка 1399: Строка 1435:
}
}
end
end
-- Подзаголовок раздела внутри блока расы. Это не заголовок вики (== ==),
-- а жирная строка: так в оглавление попадают только сами расы.
-- level= (например «===») возвращает обычные заголовки, если они нужны.
local function subHeading(title, level)
if level and level ~= "" then
return level .. " " .. title .. " " .. level
end
return '<div style="font-weight:bold;font-size:1.15em;margin:0.8em 0 0.3em;'
.. 'border-bottom:1px solid #3d3d4a;padding-bottom:2px">' .. title .. '</div>'
end
-- Разделы, которые по умолчанию свёрнуты (раскрываются по клику «показать»).
-- Сейчас пусто: раса и так лежит в шторке, вложенные шторки не нужны.
-- Пример: { ["Внутренние органы"] = true }.
local COLLAPSED = {}


function p.stats(frame)
function p.stats(frame)
local id, args = argId(frame)
local id, args = argId(frame)
isBaseline = (id == BASELINE_SPECIES)
local mob = speciesMob(id)
local mob = speciesMob(id)
if not mob then
if not mob then
return ""
return ""
end
end
local level = args.level or "==="
local orgs = organs(mob)
local orgs = organs(mob)
local out = {}
local out = {}
local function section(title, lines)
local function section(title, lines)
if #lines > 0 then
if #lines == 0 then
out[#out + 1] = level .. " " .. title .. " " .. level
return
end
if COLLAPSED[title] and not args.level then
-- Встроенное сворачивание MediaWiki: заголовок виден всегда, список — по клику.
out[#out + 1] = '<div class="mw-collapsible mw-collapsed" data-expandtext="показать" data-collapsetext="скрыть">'
.. subHeading(title)
out[#out + 1] = '<div class="mw-collapsible-content">'
out[#out + 1] = table.concat(lines, "\n")
out[#out + 1] = '</div></div>'
else
out[#out + 1] = subHeading(title, args.level)
out[#out + 1] = table.concat(lines, "\n")
out[#out + 1] = table.concat(lines, "\n")
end
end
Строка 1424: Строка 1486:
end
end


-- Список особенностей расы по её компонентам (без заголовка).
-- Список особенностей расы по её компонентам.
-- notes= — строки, написанные вручную (идут первыми); header= — заголовок раздела,
-- notes= — строки, написанные вручную (идут первыми); header= — подзаголовок раздела,
-- который выводится, только если есть хоть одна строка.
-- который выводится, только если есть хоть одна строка.
function p.features(frame)
function p.features(frame)
local id, args = argId(frame)
local id, args = argId(frame)
isBaseline = (id == BASELINE_SPECIES)
local mob = speciesMob(id)
local mob = speciesMob(id)
if not mob then
if not mob then
Строка 1447: Строка 1510:
local text = table.concat(lines, "\n")
local text = table.concat(lines, "\n")
if args.header and args.header ~= "" then
if args.header and args.header ~= "" then
local level = args.level or "==="
text = subHeading(args.header, args.level) .. "\n" .. text
text = level .. " " .. args.header .. " " .. level .. "\n" .. text
end
end
return text
return text
end
-- Разбор списка рас для nav и summary: «Human:Люди, Skeleton*:Скелеты, …».
-- ID — id прототипа расы, после двоеточия — заголовок её раздела на странице (на него
-- ведёт ссылка). Звёздочка после ID — показывать, даже если раса недоступна при создании
-- персонажа. Расы, которых нет в проекте, пропускаются.
local function raceList(text)
local out = {}
for part in mw.text.gsplit(text or "", ",", true) do
local idPart, title = part:match("^%s*([^:]+):(.-)%s*$")
if idPart then
local any = idPart:sub(-1) == "*"
local id = mw.text.trim(any and idPart:sub(1, -2) or idPart)
if (any and species(id)) or isPlayable(id) then
out[#out + 1] = { id = id, title = mw.text.trim(title) }
end
end
end
return out
end
-- Сетка иконок рас со ссылками на их разделы:
-- {{#invoke:SpeciesStats|nav|Human:Люди, Dwarf:Дворфы, …}}
function p.nav(frame)
local _, args = argId(frame)
local cards = {}
for _, r in ipairs(raceList(args[1])) do
local mob = speciesMob(r.id)
local file = mob and imageFor(mob)
cards[#cards + 1] = '<div style="width:112px;font-size:0.9em;text-align:center;background:#27272e;'
.. 'border:1px solid #3d3d4a;border-radius:6px;padding:4px">'
.. (file and ("[[Файл:" .. file .. "|64px|link=#" .. r.title .. "]]<br>") or "")
.. "[[#" .. r.title .. "|" .. r.title .. "]]</div>"
end
if #cards == 0 then
return ""
end
return '<div style="display:flex;flex-wrap:wrap;gap:8px;margin:0.5em 0 1em">'
.. table.concat(cards) .. '</div>'
end
-- Сводная таблица рас (сортируется по клику на заголовок столбца):
-- {{#invoke:SpeciesStats|summary|Human:Люди, Dwarf:Дворфы, …}}
-- Цвета — как на всей странице: сравнение с человеком.
function p.summary(frame)
local _, args = argId(frame)
local baseMob = speciesMob(BASELINE_SPECIES)
local function bc(name)
return baseMob and comp(name, baseMob)
end
local baseTh = thresholdsOf(baseMob)
local baseMs = bc("MovementSpeedModifier")
local _, baseVol = bloodOf(bc("Bloodstream"))
local bt = bc("TemperatureDamage") or bc("Temperature")
local rows = {
'{| class="wikitable sortable" style="text-align:center"',
"! Раса !! Крит !! Смерть !! Шаг !! Бег !! Кровь, ед. !! Дышит !! Темп. от !! Темп. до",
}
local function cell(sortValue, text)
if sortValue == nil then
return '| data-sort-value="-9999" | —'
end
return '| data-sort-value="' .. sortValue .. '" | ' .. text
end
for _, r in ipairs(raceList(args[1])) do
local mob = speciesMob(r.id)
if mob then
isBaseline = (r.id == BASELINE_SPECIES)
local th = thresholdsOf(mob)
local ms = comp("MovementSpeedModifier", mob)
local walk, run = ms and ms("baseWalkSpeed"), ms and ms("baseSprintSpeed")
local bsComp = comp("Bloodstream", mob)
local _, vol = bloodOf(bsComp)
if not bsComp then
vol = 0
end
local t = comp("TemperatureDamage", mob) or comp("Temperature", mob)
local cold = t and t("coldDamageThreshold")
local heat = t and t("heatDamageThreshold")
if cold == 0 then
cold = nil
end
local breathText = "не дышит"
if comp("Respirator", mob) then
local types = lungTypes(organs(mob))
local b = types and reagentList((gases(types))) or {}
breathText = #b > 0 and table.concat(b, ", ") or "—"
end
rows[#rows + 1] = "|-"
rows[#rows + 1] = "| style=\"text-align:left\" | [[#" .. r.title .. "|" .. r.title .. "]]"
rows[#rows + 1] = cell(th.Critical, th.Critical and mark(num(th.Critical), th.Critical, baseTh.Critical, true))
rows[#rows + 1] = cell(th.Dead, th.Dead and mark(num(th.Dead), th.Dead, baseTh.Dead, true))
rows[#rows + 1] = cell(walk, walk and mark(num(walk), walk, baseMs and baseMs("baseWalkSpeed"), true))
rows[#rows + 1] = cell(run, run and mark(num(run), run, baseMs and baseMs("baseSprintSpeed"), true))
rows[#rows + 1] = cell(vol, vol and (vol == 0 and "нет" or mark(num(vol), vol, baseVol, true)))
rows[#rows + 1] = "| " .. breathText
rows[#rows + 1] = cell(cold, cold and mark(kelvinToC(cold), cold, bt and bt("coldDamageThreshold"), false, kelvinToC))
rows[#rows + 1] = cell(heat, heat and mark(kelvinToC(heat), heat, bt and bt("heatDamageThreshold"), true, kelvinToC))
end
end
isBaseline = false
rows[#rows + 1] = "|}"
return table.concat(rows, "\n")
end
-- Имя файла картинки расы (спрайт бота) или file=, если задан; пусто, если картинки нет.
-- {{#invoke:SpeciesStats|image|Arachnid|file=Арахнид.png}}
function p.image(frame)
local id, args = argId(frame)
if args.file and args.file ~= "" then
return args.file
end
local mob = speciesMob(id)
return mob and imageFor(mob) or ""
end
end