MediaWiki:ScmcChemistry.js: различия между версиями
Страница интерфейса MediaWiki
Дополнительные действия
Defer (обсуждение | вклад) Нет описания правки |
Defer (обсуждение | вклад) Нет описания правки |
||
| Строка 32: | Строка 32: | ||
}; | }; | ||
var EFFECT_LABELS = { | var EFFECT_LABELS = { | ||
Anticorrosive: 'Противокислотное действие', | |||
Antihallucinogenic: 'Подавление галлюцинаций', | |||
Antitoxic: 'Антитоксическое действие', | |||
Biocidic: 'Восстановление клеток', | |||
Carcinogenic: 'Канцерогенное действие', | |||
ChemHealEyeDamage: 'Лечит повреждения глаз', | |||
ChemVomit: 'Вызывает рвоту', | ChemVomit: 'Вызывает рвоту', | ||
Corrosive: 'Разъедающее действие', | |||
CureZombieInfection: 'Лечит зомби-инфекцию', | |||
CauseZombieInfection: 'Заражает зомби-инфекцией', | |||
Drunk: 'Вызывает опьянение', | Drunk: 'Вызывает опьянение', | ||
Electrogenetic: 'Электрогенетическое действие', | |||
Emote: 'Вызывает | Emote: 'Вызывает непроизвольную реакцию', | ||
EqualHealthChange: 'Изменяет здоровье', | |||
FlammableReaction: 'Повышает воспламеняемость', | |||
Flowing: 'Изменяет текучесть крови', | |||
Focusing: 'Повышает концентрацию', | |||
Fueling: 'Топливное действие', | |||
GenericStatusEffect: 'Накладывает статусный эффект', | GenericStatusEffect: 'Накладывает статусный эффект', | ||
HealthChange: 'Изменяет здоровье', | HealthChange: 'Изменяет здоровье', | ||
Hemogenic: 'Восстанавливает кровь', | |||
Hypoxemic: 'Вызывает кислородное голодание', | |||
Jitter: 'Вызывает дрожь', | |||
Ketogenic: 'Снижает запас питательных веществ', | |||
ModifyStatusEffect: 'Изменяет статусный эффект', | |||
MovespeedModifier: 'Изменяет скорость движения', | MovespeedModifier: 'Изменяет скорость движения', | ||
Neogenetic: 'Восстановление тканей', | |||
Nutritious: 'Питательное действие', | |||
Oxidizing: 'Окисляющее действие', | |||
Oxygenating: 'Насыщает организм кислородом', | |||
PopupMessage: 'Показывает сообщение', | PopupMessage: 'Показывает сообщение', | ||
SatiateHunger: 'Утоляет голод', | SatiateHunger: 'Утоляет голод', | ||
SatiateThirst: 'Утоляет жажду', | SatiateThirst: 'Утоляет жажду', | ||
Thermostabilizing: 'Стабилизирует температуру тела', | |||
Toxic: 'Токсическое действие', | |||
Viscous: 'Замедляющее действие', | |||
AdjustReagent: 'Изменяет количество реагента' | |||
}; | |||
var DAMAGE_LABELS = { | |||
Airloss: 'кислородного голодания', | |||
Asphyxiation: 'кислородного голодания', | |||
Bloodloss: 'кровопотери', | |||
Blunt: 'тупого', Brute: 'механического', Burn: 'термического', | |||
Caustic: 'кислотного', Cellular: 'клеточного', Cold: 'холодового', | |||
Genetic: 'генетического', Heat: 'ожогового', Piercing: 'колотого', | |||
Poison: 'токсического', Radiation: 'радиационного', Slash: 'режущего', | |||
Toxin: 'токсического' | |||
}; | |||
var DAMAGE_CLASSES = { | |||
Airloss: 'oxygen', Asphyxiation: 'oxygen', Bloodloss: 'oxygen', | |||
Blunt: 'physical', Brute: 'physical', Piercing: 'physical', Slash: 'physical', | |||
Burn: 'burn', Heat: 'burn', Cold: 'cold', Poison: 'toxin', Toxin: 'toxin', | |||
Caustic: 'acid', Genetic: 'genetic', Cellular: 'cellular', Radiation: 'radiation' | |||
}; | |||
var STATUS_LABELS = { | |||
Drowsiness: 'сонливость', Jitter: 'дрожь', RMCUnconscious: 'потерю сознания', | |||
ScrambledAccent: 'невнятную речь', StaminaModifier: 'изменение выносливости', | |||
StatusEffectSeeingRainbow: 'галлюцинации', Unconscious: 'потерю сознания' | |||
}; | |||
var EMOTE_LABELS = { | |||
Cough: 'кашель', Gasp: 'судорожный вдох', Laugh: 'смех', | |||
Scream: 'крик', Sneeze: 'чихание', Yawn: 'зевоту' | |||
}; | |||
var PLANT_LABELS = { | |||
PlantAdjustGrowth: 'возраст растения', PlantAdjustHealth: 'здоровье растения', | |||
PlantAdjustMutationLevel: 'уровень мутации', PlantAdjustMutationMod: 'шанс мутации', | |||
PlantAdjustNutrition: 'питание растения', PlantAdjustPests: 'число вредителей', | |||
PlantAdjustPotency: 'потенцию растения', PlantAdjustToxins: 'уровень токсинов', | |||
PlantAdjustWater: 'уровень воды', PlantAdjustWeeds: 'число сорняков' | |||
}; | |||
var ORGAN_LABELS = { | |||
Animal: 'животного', Bloodsucker: 'кровососа', Human: 'человека', | |||
Rat: 'крысы', Slime: 'слайма', Vox: 'вокса', Xeno: 'ксеноморфа' | |||
}; | |||
var MOB_STATE_LABELS = { | |||
Alive: 'жив', Critical: 'критическое состояние', Dead: 'мёртв' | |||
}; | |||
var REAGENT_LABELS = { | |||
UncookedAnimalProteins: 'сырые животные белки' | |||
}; | }; | ||
var state = { | var state = { | ||
| Строка 120: | Строка 176: | ||
function getName(id) { | function getName(id) { | ||
var reagent = getReagent(id); | var reagent = getReagent(id); | ||
return capitalizeFirst(reagent && reagent.name ? reagent.name : id); | return capitalizeFirst(reagent && reagent.name ? reagent.name : REAGENT_LABELS[id] || id); | ||
} | } | ||
| Строка 304: | Строка 360: | ||
} | } | ||
function | function action(text, kind) { | ||
if (value === | return el('span', 'scmc-chem__effect-action scmc-chem__effect-action--' + kind, text); | ||
return ''; | } | ||
function damageType(type) { | |||
var className = DAMAGE_CLASSES[type] || 'physical'; | |||
return el('span', 'scmc-chem__damage-type scmc-chem__damage-type--' + className, DAMAGE_LABELS[type] || type); | |||
} | |||
function line() { | |||
var node = el('div', 'scmc-chem__effect-details'); | |||
Array.prototype.slice.call(arguments).forEach(function (part) { | |||
node.appendChild(part && part.nodeType ? part : document.createTextNode(String(part))); | |||
}); | |||
return node; | |||
} | |||
function probabilityText(value) { | |||
return value === undefined ? '' : ' · вероятность ' + formatNumber(value * 100) + '%'; | |||
} | |||
function damageEntries(value) { | |||
var damage = value && value.damage; | |||
var result = []; | |||
if (Array.isArray(damage)) { | |||
damage.forEach(function (item) { | |||
Object.keys(item || {}).forEach(function (type) { | |||
result.push({ type: type, amount: item[type] }); | |||
}); | |||
}); | |||
} else if (damage && typeof damage === 'object') { | |||
['groups', 'types'].forEach(function (section) { | |||
Object.keys(damage[section] || {}).forEach(function (type) { | |||
result.push({ type: type, amount: damage[section][type] }); | |||
}); | |||
}); | |||
if (!damage.groups && !damage.types) { | |||
Object.keys(damage).forEach(function (type) { | |||
if (typeof damage[type] === 'number') { | |||
result.push({ type: type, amount: damage[type] }); | |||
} | |||
}); | |||
} | |||
} | |||
return result; | |||
} | |||
function effectKind(type, value) { | |||
var beneficial = /^(Anticorrosive|Antihallucinogenic|Antitoxic|Biocidic|ChemHealEyeDamage|CureZombieInfection|Electrogenetic|Focusing|Hemogenic|Neogenetic|Nutritious|Oxygenating|SatiateHunger|SatiateThirst|Thermostabilizing)$/; | |||
var harmful = /^(Carcinogenic|CauseZombieInfection|ChemVomit|Corrosive|Fueling|Hypoxemic|Jitter|Ketogenic|Oxidizing|Toxic|Viscous)$/; | |||
if (type === 'HealthChange' || type === 'EqualHealthChange') { | |||
var damage = damageEntries(value); | |||
return damage.length && damage.every(function (item) { return item.amount <= 0; }) ? 'beneficial' : | |||
damage.length && damage.every(function (item) { return item.amount >= 0; }) ? 'harmful' : 'warning'; | |||
} | |||
if (beneficial.test(type)) { | |||
return 'beneficial'; | |||
} | |||
if (harmful.test(type)) { | |||
return 'harmful'; | |||
} | |||
return 'warning'; | |||
} | |||
function conditionText(condition, reagent, hideDoseCondition) { | |||
var type = effectType(condition); | |||
var value = condition.value || {}; | |||
if (type === 'ReagentThreshold') { | |||
if (hideDoseCondition) { | |||
return ''; | |||
} | |||
return 'при содержании ' + (value.reagent ? getName(value.reagent) + ' ' : '') + | |||
(value.min !== undefined ? 'от ' + formatAmount(value.min) : '') + | |||
(value.max !== undefined ? ' до ' + formatAmount(value.max) : ''); | |||
} | |||
if (type === 'Temperature') { | |||
return 'при температуре ' + | |||
(value.min !== undefined ? 'от ' + formatNumber(value.min) + ' K' : '') + | |||
(value.max !== undefined ? 'до ' + formatNumber(value.max) + ' K' : ''); | |||
} | |||
if (type === 'MobStateCondition') { | |||
var stateName = value.mobstate || value.state || 'особое'; | |||
return 'если цель находится в состоянии: ' + (MOB_STATE_LABELS[stateName] || stateName); | |||
} | |||
if (type === 'OrganType') { | |||
var organ = value.type || value.organType || 'подходящего типа'; | |||
return 'для организма ' + (ORGAN_LABELS[organ] || organ); | |||
} | } | ||
if ( | return 'при выполнении дополнительного условия'; | ||
} | |||
function renderEffect(effect, reagent, tier) { | |||
var type = effectType(effect); | |||
var value = effect.value && typeof effect.value === 'object' ? effect.value : {}; | |||
var kind = effectKind(type, value); | |||
var card = el('div', 'scmc-chem__effect scmc-chem__effect--' + kind); | |||
add(card, el('div', 'scmc-chem__effect-name', EFFECT_LABELS[type] || ('Эффект «' + type + '»'))); | |||
var damage = damageEntries(value); | |||
if (damage.length) { | |||
damage.forEach(function (item) { | |||
var healing = item.amount < 0; | |||
var oxygenLike = /^(Airloss|Asphyxiation|Bloodloss)$/.test(item.type); | |||
add(card, oxygenLike ? line( | |||
action(healing ? 'Лечит' : 'Наносит', healing ? 'beneficial' : 'harmful'), | |||
' ' + formatNumber(Math.abs(item.amount)) + ' ед. урона от ', damageType(item.type) | |||
) : line( | |||
action(healing ? 'Лечит' : 'Наносит', healing ? 'beneficial' : 'harmful'), | |||
' ' + formatNumber(Math.abs(item.amount)) + ' ед. ', damageType(item.type), ' урона' | |||
)); | |||
}); | |||
} else if (/^(Antitoxic|Toxic)$/.test(type)) { | |||
add(card, line(action(type === 'Antitoxic' ? 'Лечит' : 'Наносит', type === 'Antitoxic' ? 'beneficial' : 'harmful'), | |||
' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Toxin'), ' урона')); | |||
} else if (/^(Biocidic|Carcinogenic)$/.test(type)) { | |||
add(card, line(action(type === 'Biocidic' ? 'Лечит' : 'Наносит', type === 'Biocidic' ? 'beneficial' : 'harmful'), | |||
' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Cellular'), ' урона')); | |||
} else if (/^(Neogenetic|Electrogenetic)$/.test(type)) { | |||
add(card, line(action('Лечит', 'beneficial'), ' ' + formatNumber(value.potency || 1) + ' ед. ', damageType(type === 'Neogenetic' ? 'Brute' : 'Genetic'), ' урона')); | |||
} else if (/^(Anticorrosive|Corrosive)$/.test(type)) { | |||
add(card, line(action(type === 'Anticorrosive' ? 'Лечит' : 'Наносит', type === 'Anticorrosive' ? 'beneficial' : 'harmful'), | |||
' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Caustic'), ' урона')); | |||
} else if (/^(Oxygenating|Hypoxemic)$/.test(type)) { | |||
add(card, line(action(type === 'Oxygenating' ? 'Лечит' : 'Наносит', type === 'Oxygenating' ? 'beneficial' : 'harmful'), | |||
' ' + formatNumber(value.potency || 1) + ' ед. урона от ', damageType('Airloss'))); | |||
} else if (type === 'Oxidizing') { | |||
add(card, line(action('Наносит', 'harmful'), ' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Burn'), ' урона')); | |||
} else if (type === 'Hemogenic') { | |||
add(card, line(action('Восстанавливает', 'beneficial'), ' кровь · сила ' + formatNumber(value.potency || 1))); | |||
} else if (type === 'AdjustReagent') { | |||
var amount = Number(value.amount || 0); | |||
add(card, line(action(amount < 0 ? 'Удаляет' : 'Добавляет', amount < 0 ? 'beneficial' : 'utility'), | |||
' ' + formatAmount(Math.abs(amount)) + ' реагента «' + getName(value.reagent) + '»')); | |||
} else if (type === 'GenericStatusEffect' || type === 'ModifyStatusEffect') { | |||
var status = value.component || value.key || value.effectProto || 'статусный эффект'; | |||
add(card, line(action(value.type === 'Remove' ? 'Снимает' : 'Вызывает', value.type === 'Remove' ? 'beneficial' : 'harmful'), | |||
' ' + (STATUS_LABELS[status] || status) + (value.time !== undefined ? ' на ' + formatNumber(value.time) + ' с' : ''))); | |||
} else if (type === 'Emote') { | |||
add(card, line(action('Вызывает', 'warning'), ' ' + (EMOTE_LABELS[value.emote] || value.emote || 'непроизвольную реакцию') + probabilityText(value.probability))); | |||
} else if (type === 'Jitter') { | |||
add(card, line(action('Вызывает', 'harmful'), ' дрожь' + probabilityText(value.probability))); | |||
} else if (type === 'ChemVomit') { | |||
add(card, line(action('Вызывает', 'harmful'), ' рвоту' + probabilityText(value.probability))); | |||
} else if (type === 'SatiateThirst' || type === 'SatiateHunger') { | |||
add(card, line(action(type === 'SatiateThirst' ? 'Утоляет жажду' : 'Утоляет голод', 'beneficial'), | |||
value.factor !== undefined ? ' · коэффициент ' + formatNumber(value.factor) : '')); | |||
} else if (type === 'Drunk') { | |||
add(card, line(action('Опьяняет', 'warning'), value.boozePower !== undefined ? ' · сила ' + formatNumber(value.boozePower) : '')); | |||
} else if (type === 'MovespeedModifier') { | |||
add(card, line(action('Изменяет скорость', 'utility'), | |||
' · ходьба ×' + formatNumber(value.walkSpeedModifier || 1) + ' · бег ×' + formatNumber(value.sprintSpeedModifier || 1))); | |||
} else if (type === 'CureZombieInfection' || type === 'CauseZombieInfection') { | |||
add(card, line(action(type === 'CureZombieInfection' ? 'Лечит' : 'Заражает', type === 'CureZombieInfection' ? 'beneficial' : 'harmful'), | |||
(type === 'CureZombieInfection' ? ' зомби-инфекцию' : ' зомби-инфекцией') + (value.innoculate ? ' и создаёт иммунитет' : ''))); | |||
} else if (type === 'PopupMessage') { | |||
add(card, line(action('Показывает', 'utility'), ' игровое сообщение' + probabilityText(value.probability))); | |||
} else { | |||
var potency = value.potency !== undefined ? ' · сила ' + formatNumber(value.potency) : ''; | |||
var multiplier = value.multiplier !== undefined ? ' · множитель ' + formatNumber(value.multiplier) : ''; | |||
add(card, line(action(EFFECT_LABELS[type] || 'Воздействует', kind === 'beneficial' ? 'beneficial' : kind === 'harmful' ? 'harmful' : 'utility'), potency + multiplier + probabilityText(value.probability))); | |||
} | } | ||
(value.conditions || []).forEach(function (condition) { | |||
var text = conditionText(condition, reagent, tier !== 'normal' && effectType(condition) === 'ReagentThreshold'); | |||
var | if (text) { | ||
add(card, el('div', 'scmc-chem__effect-condition', 'Условие: ' + text)); | |||
} | } | ||
return | }); | ||
}). | return card; | ||
} | |||
function effectDose(effect, reagent) { | |||
var conditions = effect.value && effect.value.conditions || []; | |||
var mins = conditions.filter(function (condition) { | |||
var value = condition.value || {}; | |||
return effectType(condition) === 'ReagentThreshold' && | |||
(!value.reagent || value.reagent === reagent.id) && value.min !== undefined; | |||
}).map(function (condition) { return Number(condition.value.min); }); | |||
return mins.length ? Math.max.apply(Math, mins) : null; | |||
} | } | ||
function renderEffects(effects) { | function effectTier(effect, reagent) { | ||
var dose = effectDose(effect, reagent); | |||
var properties = reagent.properties || {}; | |||
if (dose !== null && properties.criticalOverdose !== undefined && dose >= properties.criticalOverdose) { | |||
return 'critical'; | |||
} | |||
if (dose !== null && properties.overdose !== undefined && dose >= properties.overdose) { | |||
return 'overdose'; | |||
} | |||
return 'normal'; | |||
} | |||
function tierTitle(tier, reagent) { | |||
var properties = reagent.properties || {}; | |||
if (tier === 'critical') { | |||
return 'Критическая передозировка' + (properties.criticalOverdose !== undefined ? ' · от ' + formatAmount(properties.criticalOverdose) : ''); | |||
} | |||
if (tier === 'overdose') { | |||
return 'Передозировка' + (properties.overdose !== undefined ? ' · от ' + formatAmount(properties.overdose) : ''); | |||
} | |||
return 'Обычная доза' + (properties.overdose !== undefined ? ' · до ' + formatAmount(properties.overdose) : ''); | |||
} | |||
function renderEffects(effects, reagent) { | |||
var list = el('div', 'scmc-chem__effect-list'); | var list = el('div', 'scmc-chem__effect-list'); | ||
if (!effects || !effects.length) { | if (!effects || !effects.length) { | ||
| Строка 328: | Строка 570: | ||
return list; | return list; | ||
} | } | ||
var tiers = { normal: [], overdose: [], critical: [] }; | |||
effects.forEach(function (effect) { | effects.forEach(function (effect) { | ||
tiers[effectTier(effect, reagent)].push(effect); | |||
}); | |||
['normal', 'overdose', 'critical'].forEach(function (tier) { | |||
if (!tiers[tier].length) { | |||
return; | |||
} | |||
var block = el('div', 'scmc-chem__effect-tier scmc-chem__effect-tier--' + tier); | |||
add(block, el('div', 'scmc-chem__effect-tier-title', tierTitle(tier, reagent))); | |||
tiers[tier].forEach(function (effect) { | |||
add(block, renderEffect(effect, reagent, tier)); | |||
}); | |||
add(list, block); | |||
}); | |||
return list; | |||
} | |||
function plantEffectKind(type, amount) { | |||
if (/Mutation/.test(type)) { | |||
return 'warning'; | |||
} | |||
var harmfulWhenPositive = /Toxins|Weeds|Pests/.test(type); | |||
return (harmfulWhenPositive ? amount <= 0 : amount >= 0) ? 'beneficial' : 'harmful'; | |||
} | |||
function renderPlantEffects(effects) { | |||
var list = el('div', 'scmc-chem__effect-list'); | |||
(effects || []).forEach(function (effect) { | |||
var type = effectType(effect); | var type = effectType(effect); | ||
var value = effect.value || {}; | var value = effect.value || {}; | ||
if (type === 'PlantCryoxadone') { | |||
var special = el('div', 'scmc-chem__effect scmc-chem__effect--beneficial'); | |||
add(special, el('div', 'scmc-chem__effect-name', 'Криогенное восстановление')); | |||
add(special, line(action('Омолаживает', 'beneficial'), ' растение при низкой температуре')); | |||
add(list, special); | |||
add( | return; | ||
} | } | ||
var amount = Number(value.amount || 0); | |||
var kind = plantEffectKind(type, amount); | |||
var card = el('div', 'scmc-chem__effect scmc-chem__effect--' + kind); | |||
add(card, el('div', 'scmc-chem__effect-name', capitalizeFirst(PLANT_LABELS[type] || ('Эффект «' + type + '»')))); | |||
add(card, line( | |||
action(amount >= 0 ? 'Повышает' : 'Снижает', kind === 'beneficial' ? 'beneficial' : kind === 'harmful' ? 'harmful' : 'warning'), | |||
' ' + (PLANT_LABELS[type] || 'показатель') + ' на ' + formatNumber(Math.abs(amount)) + | |||
probabilityText(value.probability) | |||
)); | |||
add(list, card); | add(list, card); | ||
}); | }); | ||
| Строка 359: | Строка 638: | ||
title += ' · ' + formatAmount(metabolism.metabolismRate) + '/с'; | title += ' · ' + formatAmount(metabolism.metabolismRate) + '/с'; | ||
} | } | ||
add(block, el('div', 'scmc-chem__metabolism-title', title), renderEffects(metabolism.effects)); | add(block, el('div', 'scmc-chem__metabolism-title', title), renderEffects(metabolism.effects, reagent)); | ||
add(wrapper, block); | add(wrapper, block); | ||
}); | }); | ||
return wrapper; | |||
} | |||
function renderPlantMetabolism(reagent) { | |||
var effects = reagent.properties && reagent.properties.plantMetabolism; | |||
if (!Array.isArray(effects) || !effects.length) { | |||
return null; | |||
} | |||
var wrapper = el('div', 'scmc-chem__card-section'); | |||
var block = el('div', 'scmc-chem__metabolism'); | |||
add(wrapper, el('h4', 'scmc-chem__card-subtitle', 'Воздействие на растения')); | |||
add(block, el('div', 'scmc-chem__metabolism-title', 'Метаболизм растений'), renderPlantEffects(effects)); | |||
add(wrapper, block); | |||
return wrapper; | return wrapper; | ||
} | } | ||
| Строка 409: | Строка 701: | ||
} | } | ||
add(body, renderMetabolisms(reagent)); | add(body, renderMetabolisms(reagent)); | ||
add(body, renderPlantMetabolism(reagent)); | |||
add(details, summary, body); | add(details, summary, body); | ||
return details; | return details; | ||
Версия от 16:37, 14 августа 2026
/**
* Интерактивный каталог химии SSMC.
* Страница: MC:Химия
* Данные: MediaWiki:ScmcChemCatalog.js
*/
(function (mw, $) {
'use strict';
var ROOT_ID = 'scmc-chemistry-app';
var CATALOG_PAGE = 'MediaWiki:ScmcChemCatalog.js';
var STYLE_PAGE = 'MediaWiki:ScmcChemistry.css';
var NUMBER = new Intl.NumberFormat('ru-RU', { maximumFractionDigits: 3 });
var TABS = [
{ id: 'ordnance', label: 'Боевая химия' },
{ id: 'medicine', label: 'Медицина' },
{ id: 'drinks', label: 'Напитки' },
{ id: 'elements', label: 'Элементы' },
{ id: 'other', label: 'Другие вещества' }
];
var ORIGIN_LABELS = {
rmc14: 'RMC14',
stories: 'Stories',
'upstream-reference': 'Upstream'
};
var METABOLISM_LABELS = {
Alcohol: 'Алкоголь',
Drink: 'Напиток',
Food: 'Пища',
Medicine: 'Лекарство',
Narcotic: 'Наркотик',
Poison: 'Яд'
};
var EFFECT_LABELS = {
Anticorrosive: 'Противокислотное действие',
Antihallucinogenic: 'Подавление галлюцинаций',
Antitoxic: 'Антитоксическое действие',
Biocidic: 'Восстановление клеток',
Carcinogenic: 'Канцерогенное действие',
ChemHealEyeDamage: 'Лечит повреждения глаз',
ChemVomit: 'Вызывает рвоту',
Corrosive: 'Разъедающее действие',
CureZombieInfection: 'Лечит зомби-инфекцию',
CauseZombieInfection: 'Заражает зомби-инфекцией',
Drunk: 'Вызывает опьянение',
Electrogenetic: 'Электрогенетическое действие',
Emote: 'Вызывает непроизвольную реакцию',
EqualHealthChange: 'Изменяет здоровье',
FlammableReaction: 'Повышает воспламеняемость',
Flowing: 'Изменяет текучесть крови',
Focusing: 'Повышает концентрацию',
Fueling: 'Топливное действие',
GenericStatusEffect: 'Накладывает статусный эффект',
HealthChange: 'Изменяет здоровье',
Hemogenic: 'Восстанавливает кровь',
Hypoxemic: 'Вызывает кислородное голодание',
Jitter: 'Вызывает дрожь',
Ketogenic: 'Снижает запас питательных веществ',
ModifyStatusEffect: 'Изменяет статусный эффект',
MovespeedModifier: 'Изменяет скорость движения',
Neogenetic: 'Восстановление тканей',
Nutritious: 'Питательное действие',
Oxidizing: 'Окисляющее действие',
Oxygenating: 'Насыщает организм кислородом',
PopupMessage: 'Показывает сообщение',
SatiateHunger: 'Утоляет голод',
SatiateThirst: 'Утоляет жажду',
Thermostabilizing: 'Стабилизирует температуру тела',
Toxic: 'Токсическое действие',
Viscous: 'Замедляющее действие',
AdjustReagent: 'Изменяет количество реагента'
};
var DAMAGE_LABELS = {
Airloss: 'кислородного голодания',
Asphyxiation: 'кислородного голодания',
Bloodloss: 'кровопотери',
Blunt: 'тупого', Brute: 'механического', Burn: 'термического',
Caustic: 'кислотного', Cellular: 'клеточного', Cold: 'холодового',
Genetic: 'генетического', Heat: 'ожогового', Piercing: 'колотого',
Poison: 'токсического', Radiation: 'радиационного', Slash: 'режущего',
Toxin: 'токсического'
};
var DAMAGE_CLASSES = {
Airloss: 'oxygen', Asphyxiation: 'oxygen', Bloodloss: 'oxygen',
Blunt: 'physical', Brute: 'physical', Piercing: 'physical', Slash: 'physical',
Burn: 'burn', Heat: 'burn', Cold: 'cold', Poison: 'toxin', Toxin: 'toxin',
Caustic: 'acid', Genetic: 'genetic', Cellular: 'cellular', Radiation: 'radiation'
};
var STATUS_LABELS = {
Drowsiness: 'сонливость', Jitter: 'дрожь', RMCUnconscious: 'потерю сознания',
ScrambledAccent: 'невнятную речь', StaminaModifier: 'изменение выносливости',
StatusEffectSeeingRainbow: 'галлюцинации', Unconscious: 'потерю сознания'
};
var EMOTE_LABELS = {
Cough: 'кашель', Gasp: 'судорожный вдох', Laugh: 'смех',
Scream: 'крик', Sneeze: 'чихание', Yawn: 'зевоту'
};
var PLANT_LABELS = {
PlantAdjustGrowth: 'возраст растения', PlantAdjustHealth: 'здоровье растения',
PlantAdjustMutationLevel: 'уровень мутации', PlantAdjustMutationMod: 'шанс мутации',
PlantAdjustNutrition: 'питание растения', PlantAdjustPests: 'число вредителей',
PlantAdjustPotency: 'потенцию растения', PlantAdjustToxins: 'уровень токсинов',
PlantAdjustWater: 'уровень воды', PlantAdjustWeeds: 'число сорняков'
};
var ORGAN_LABELS = {
Animal: 'животного', Bloodsucker: 'кровососа', Human: 'человека',
Rat: 'крысы', Slime: 'слайма', Vox: 'вокса', Xeno: 'ксеноморфа'
};
var MOB_STATE_LABELS = {
Alive: 'жив', Critical: 'критическое состояние', Dead: 'мёртв'
};
var REAGENT_LABELS = {
UncookedAnimalProteins: 'сырые животные белки'
};
var state = {
data: null,
activeTab: 'ordnance',
query: '',
productReactions: Object.create(null)
};
function el(tag, className, text) {
var node = document.createElement(tag);
if (className) {
node.className = className;
}
if (text !== undefined && text !== null) {
node.textContent = String(text);
}
return node;
}
function add(parent) {
Array.prototype.slice.call(arguments, 1).forEach(function (child) {
if (child) {
parent.appendChild(child);
}
});
return parent;
}
function formatNumber(value) {
return typeof value === 'number' ? NUMBER.format(value) : String(value);
}
function formatAmount(value) {
return formatNumber(value) + ' ед.';
}
function normalize(value) {
return String(value || '').toLocaleLowerCase('ru-RU').replace(/ё/g, 'е').trim();
}
function capitalizeFirst(value) {
var text = String(value || '').trim();
return text ? text.charAt(0).toLocaleUpperCase('ru-RU') + text.slice(1) : text;
}
function slug(value) {
return String(value || 'section')
.toLocaleLowerCase('ru-RU')
.replace(/[^a-zа-яё0-9]+/gi, '-')
.replace(/^-+|-+$/g, '');
}
function safeColor(value) {
var probe = document.createElement('span').style;
probe.color = '';
probe.color = String(value || '');
return probe.color ? value : '#7f8c98';
}
function getReagent(id) {
return state.data.reagents[id] || state.data.dependencies[id] || null;
}
function getName(id) {
var reagent = getReagent(id);
return capitalizeFirst(reagent && reagent.name ? reagent.name : REAGENT_LABELS[id] || id);
}
function getColor(id) {
var reagent = getReagent(id);
return safeColor(reagent && reagent.properties && reagent.properties.color);
}
function indexReactions() {
state.productReactions = Object.create(null);
Object.keys(state.data.reactions || {}).forEach(function (reactionId) {
var reaction = state.data.reactions[reactionId];
(reaction.products || []).forEach(function (product) {
if (!state.productReactions[product.id]) {
state.productReactions[product.id] = [];
}
state.productReactions[product.id].push(reaction);
});
});
}
function validateCatalog(data) {
return data && data.schemaVersion === 1 && data.catalogSections &&
data.reagents && data.dependencies && data.reactions &&
TABS.every(function (tab) {
return Array.isArray(data.catalogSections[tab.id]);
});
}
function loadStyle() {
mw.loader.load(mw.util.getUrl(STYLE_PAGE, {
action: 'raw',
ctype: 'text/css',
maxage: 0,
smaxage: 0
}), 'text/css');
}
function loadCatalog() {
window.ScmcChemCatalog = null;
return mw.loader.getScript(mw.util.getUrl(CATALOG_PAGE, {
action: 'raw',
ctype: 'text/javascript',
maxage: 0,
smaxage: 0
})).then(function () {
if (!validateCatalog(window.ScmcChemCatalog)) {
throw new Error('MediaWiki:ScmcChemCatalog.js не содержит поддерживаемый каталог');
}
return window.ScmcChemCatalog;
});
}
function renderLoading(root) {
root.textContent = '';
add(root, add(el('div', 'scmc-chem__status scmc-chem__status--loading'),
el('span', 'scmc-chem__spinner'),
el('span', '', 'Загружаю химический каталог…')
));
}
function renderError(root, error) {
root.textContent = '';
var block = add(el('div', 'scmc-chem__status scmc-chem__status--error'),
el('strong', '', 'Каталог не загрузился.'),
el('span', '', ' Проверьте MediaWiki:ScmcChemCatalog.js.')
);
var retry = el('button', 'scmc-chem__retry', 'Повторить');
retry.type = 'button';
retry.addEventListener('click', function () { start(root); });
add(block, retry);
add(root, block);
if (window.console && console.error) {
console.error('[ScmcChemistry]', error);
}
}
function createHero() {
var count = TABS.reduce(function (total, tab) {
return total + state.data.catalogSections[tab.id].length;
}, 0);
return add(el('header', 'scmc-chem__hero'),
add(el('div', 'scmc-chem__hero-copy'),
el('p', 'scmc-chem__eyebrow', 'SSMC · справочник'),
el('h1', 'scmc-chem__title', 'Химия'),
el('p', 'scmc-chem__lead', 'Реагенты, рецепты, дозировки и эффекты. Данные хранятся прямо на вики.')
),
add(el('div', 'scmc-chem__stats'),
el('span', 'scmc-chem__stat', count + ' записей'),
el('span', 'scmc-chem__stat', Object.keys(state.data.reactions).length + ' реакций')
)
);
}
function renderTabs(root) {
var tabs = root.querySelector('.scmc-chem__tabs');
tabs.textContent = '';
TABS.forEach(function (tab) {
var button = el('button', 'scmc-chem__tab');
button.type = 'button';
button.setAttribute('role', 'tab');
button.setAttribute('aria-selected', String(tab.id === state.activeTab));
add(button,
el('span', '', tab.label),
el('span', 'scmc-chem__tab-count', state.data.catalogSections[tab.id].length)
);
button.addEventListener('click', function () {
state.activeTab = tab.id;
renderTabs(root);
renderResults(root);
});
add(tabs, button);
});
}
function groupEntries(entries) {
var groups = Object.create(null);
entries.forEach(function (entry) {
var path = Array.isArray(entry.sectionPath) && entry.sectionPath.length ? entry.sectionPath : ['Без раздела'];
var key = path.join(' › ');
if (!groups[key]) {
groups[key] = { title: path[path.length - 1], path: path, entries: [] };
}
groups[key].entries.push(entry);
});
return Object.keys(groups).map(function (key) { return groups[key]; });
}
function entryMatches(entry) {
if (!state.query) {
return true;
}
var reagent = getReagent(entry.id) || {};
var haystack = [entry.id, entry.name, reagent.name, reagent.description]
.concat(entry.sectionPath || []).join(' ');
return normalize(haystack).indexOf(state.query) !== -1;
}
function renderSectionNav(root, groups) {
var nav = root.querySelector('.scmc-chem__section-nav');
nav.textContent = '';
nav.hidden = groups.length < 2;
groups.forEach(function (group, index) {
var link = el('a', 'scmc-chem__section-link');
link.href = '#scmc-chem-section-' + index + '-' + slug(group.title);
add(link,
el('span', '', capitalizeFirst(group.title)),
el('span', 'scmc-chem__section-link-count', group.entries.length)
);
add(nav, link);
});
}
function renderMixture(items, className) {
var list = el('div', className);
(items || []).forEach(function (item) {
add(list, add(el('div', 'scmc-chem__mixture-item'),
el('span', 'scmc-chem__mixture-dot'),
el('span', 'scmc-chem__mixture-name', capitalizeFirst(item.name || getName(item.id))),
el('span', 'scmc-chem__mixture-amount', formatAmount(item.amount))
));
list.lastChild.firstChild.style.backgroundColor = getColor(item.id);
});
return list;
}
function renderReaction(reaction) {
var box = el('div', 'scmc-chem__reaction');
var equation = add(el('div', 'scmc-chem__equation'),
renderMixture(reaction.reactants, 'scmc-chem__reactants'),
el('span', 'scmc-chem__arrow', '→'),
renderMixture(reaction.products, 'scmc-chem__products')
);
add(box, el('div', 'scmc-chem__reaction-label', 'Рецепт'), equation);
if (!reaction.products || !reaction.products.length) {
add(box, el('div', 'scmc-chem__reaction-warning', 'Реакция не создаёт реагент и может иметь опасный эффект.'));
}
return box;
}
function effectType(effect) {
return String(effect && effect.yamlTag || '').replace(/^!type:/, '') || 'Эффект';
}
function action(text, kind) {
return el('span', 'scmc-chem__effect-action scmc-chem__effect-action--' + kind, text);
}
function damageType(type) {
var className = DAMAGE_CLASSES[type] || 'physical';
return el('span', 'scmc-chem__damage-type scmc-chem__damage-type--' + className, DAMAGE_LABELS[type] || type);
}
function line() {
var node = el('div', 'scmc-chem__effect-details');
Array.prototype.slice.call(arguments).forEach(function (part) {
node.appendChild(part && part.nodeType ? part : document.createTextNode(String(part)));
});
return node;
}
function probabilityText(value) {
return value === undefined ? '' : ' · вероятность ' + formatNumber(value * 100) + '%';
}
function damageEntries(value) {
var damage = value && value.damage;
var result = [];
if (Array.isArray(damage)) {
damage.forEach(function (item) {
Object.keys(item || {}).forEach(function (type) {
result.push({ type: type, amount: item[type] });
});
});
} else if (damage && typeof damage === 'object') {
['groups', 'types'].forEach(function (section) {
Object.keys(damage[section] || {}).forEach(function (type) {
result.push({ type: type, amount: damage[section][type] });
});
});
if (!damage.groups && !damage.types) {
Object.keys(damage).forEach(function (type) {
if (typeof damage[type] === 'number') {
result.push({ type: type, amount: damage[type] });
}
});
}
}
return result;
}
function effectKind(type, value) {
var beneficial = /^(Anticorrosive|Antihallucinogenic|Antitoxic|Biocidic|ChemHealEyeDamage|CureZombieInfection|Electrogenetic|Focusing|Hemogenic|Neogenetic|Nutritious|Oxygenating|SatiateHunger|SatiateThirst|Thermostabilizing)$/;
var harmful = /^(Carcinogenic|CauseZombieInfection|ChemVomit|Corrosive|Fueling|Hypoxemic|Jitter|Ketogenic|Oxidizing|Toxic|Viscous)$/;
if (type === 'HealthChange' || type === 'EqualHealthChange') {
var damage = damageEntries(value);
return damage.length && damage.every(function (item) { return item.amount <= 0; }) ? 'beneficial' :
damage.length && damage.every(function (item) { return item.amount >= 0; }) ? 'harmful' : 'warning';
}
if (beneficial.test(type)) {
return 'beneficial';
}
if (harmful.test(type)) {
return 'harmful';
}
return 'warning';
}
function conditionText(condition, reagent, hideDoseCondition) {
var type = effectType(condition);
var value = condition.value || {};
if (type === 'ReagentThreshold') {
if (hideDoseCondition) {
return '';
}
return 'при содержании ' + (value.reagent ? getName(value.reagent) + ' ' : '') +
(value.min !== undefined ? 'от ' + formatAmount(value.min) : '') +
(value.max !== undefined ? ' до ' + formatAmount(value.max) : '');
}
if (type === 'Temperature') {
return 'при температуре ' +
(value.min !== undefined ? 'от ' + formatNumber(value.min) + ' K' : '') +
(value.max !== undefined ? 'до ' + formatNumber(value.max) + ' K' : '');
}
if (type === 'MobStateCondition') {
var stateName = value.mobstate || value.state || 'особое';
return 'если цель находится в состоянии: ' + (MOB_STATE_LABELS[stateName] || stateName);
}
if (type === 'OrganType') {
var organ = value.type || value.organType || 'подходящего типа';
return 'для организма ' + (ORGAN_LABELS[organ] || organ);
}
return 'при выполнении дополнительного условия';
}
function renderEffect(effect, reagent, tier) {
var type = effectType(effect);
var value = effect.value && typeof effect.value === 'object' ? effect.value : {};
var kind = effectKind(type, value);
var card = el('div', 'scmc-chem__effect scmc-chem__effect--' + kind);
add(card, el('div', 'scmc-chem__effect-name', EFFECT_LABELS[type] || ('Эффект «' + type + '»')));
var damage = damageEntries(value);
if (damage.length) {
damage.forEach(function (item) {
var healing = item.amount < 0;
var oxygenLike = /^(Airloss|Asphyxiation|Bloodloss)$/.test(item.type);
add(card, oxygenLike ? line(
action(healing ? 'Лечит' : 'Наносит', healing ? 'beneficial' : 'harmful'),
' ' + formatNumber(Math.abs(item.amount)) + ' ед. урона от ', damageType(item.type)
) : line(
action(healing ? 'Лечит' : 'Наносит', healing ? 'beneficial' : 'harmful'),
' ' + formatNumber(Math.abs(item.amount)) + ' ед. ', damageType(item.type), ' урона'
));
});
} else if (/^(Antitoxic|Toxic)$/.test(type)) {
add(card, line(action(type === 'Antitoxic' ? 'Лечит' : 'Наносит', type === 'Antitoxic' ? 'beneficial' : 'harmful'),
' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Toxin'), ' урона'));
} else if (/^(Biocidic|Carcinogenic)$/.test(type)) {
add(card, line(action(type === 'Biocidic' ? 'Лечит' : 'Наносит', type === 'Biocidic' ? 'beneficial' : 'harmful'),
' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Cellular'), ' урона'));
} else if (/^(Neogenetic|Electrogenetic)$/.test(type)) {
add(card, line(action('Лечит', 'beneficial'), ' ' + formatNumber(value.potency || 1) + ' ед. ', damageType(type === 'Neogenetic' ? 'Brute' : 'Genetic'), ' урона'));
} else if (/^(Anticorrosive|Corrosive)$/.test(type)) {
add(card, line(action(type === 'Anticorrosive' ? 'Лечит' : 'Наносит', type === 'Anticorrosive' ? 'beneficial' : 'harmful'),
' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Caustic'), ' урона'));
} else if (/^(Oxygenating|Hypoxemic)$/.test(type)) {
add(card, line(action(type === 'Oxygenating' ? 'Лечит' : 'Наносит', type === 'Oxygenating' ? 'beneficial' : 'harmful'),
' ' + formatNumber(value.potency || 1) + ' ед. урона от ', damageType('Airloss')));
} else if (type === 'Oxidizing') {
add(card, line(action('Наносит', 'harmful'), ' ' + formatNumber(value.potency || 1) + ' ед. ', damageType('Burn'), ' урона'));
} else if (type === 'Hemogenic') {
add(card, line(action('Восстанавливает', 'beneficial'), ' кровь · сила ' + formatNumber(value.potency || 1)));
} else if (type === 'AdjustReagent') {
var amount = Number(value.amount || 0);
add(card, line(action(amount < 0 ? 'Удаляет' : 'Добавляет', amount < 0 ? 'beneficial' : 'utility'),
' ' + formatAmount(Math.abs(amount)) + ' реагента «' + getName(value.reagent) + '»'));
} else if (type === 'GenericStatusEffect' || type === 'ModifyStatusEffect') {
var status = value.component || value.key || value.effectProto || 'статусный эффект';
add(card, line(action(value.type === 'Remove' ? 'Снимает' : 'Вызывает', value.type === 'Remove' ? 'beneficial' : 'harmful'),
' ' + (STATUS_LABELS[status] || status) + (value.time !== undefined ? ' на ' + formatNumber(value.time) + ' с' : '')));
} else if (type === 'Emote') {
add(card, line(action('Вызывает', 'warning'), ' ' + (EMOTE_LABELS[value.emote] || value.emote || 'непроизвольную реакцию') + probabilityText(value.probability)));
} else if (type === 'Jitter') {
add(card, line(action('Вызывает', 'harmful'), ' дрожь' + probabilityText(value.probability)));
} else if (type === 'ChemVomit') {
add(card, line(action('Вызывает', 'harmful'), ' рвоту' + probabilityText(value.probability)));
} else if (type === 'SatiateThirst' || type === 'SatiateHunger') {
add(card, line(action(type === 'SatiateThirst' ? 'Утоляет жажду' : 'Утоляет голод', 'beneficial'),
value.factor !== undefined ? ' · коэффициент ' + formatNumber(value.factor) : ''));
} else if (type === 'Drunk') {
add(card, line(action('Опьяняет', 'warning'), value.boozePower !== undefined ? ' · сила ' + formatNumber(value.boozePower) : ''));
} else if (type === 'MovespeedModifier') {
add(card, line(action('Изменяет скорость', 'utility'),
' · ходьба ×' + formatNumber(value.walkSpeedModifier || 1) + ' · бег ×' + formatNumber(value.sprintSpeedModifier || 1)));
} else if (type === 'CureZombieInfection' || type === 'CauseZombieInfection') {
add(card, line(action(type === 'CureZombieInfection' ? 'Лечит' : 'Заражает', type === 'CureZombieInfection' ? 'beneficial' : 'harmful'),
(type === 'CureZombieInfection' ? ' зомби-инфекцию' : ' зомби-инфекцией') + (value.innoculate ? ' и создаёт иммунитет' : '')));
} else if (type === 'PopupMessage') {
add(card, line(action('Показывает', 'utility'), ' игровое сообщение' + probabilityText(value.probability)));
} else {
var potency = value.potency !== undefined ? ' · сила ' + formatNumber(value.potency) : '';
var multiplier = value.multiplier !== undefined ? ' · множитель ' + formatNumber(value.multiplier) : '';
add(card, line(action(EFFECT_LABELS[type] || 'Воздействует', kind === 'beneficial' ? 'beneficial' : kind === 'harmful' ? 'harmful' : 'utility'), potency + multiplier + probabilityText(value.probability)));
}
(value.conditions || []).forEach(function (condition) {
var text = conditionText(condition, reagent, tier !== 'normal' && effectType(condition) === 'ReagentThreshold');
if (text) {
add(card, el('div', 'scmc-chem__effect-condition', 'Условие: ' + text));
}
});
return card;
}
function effectDose(effect, reagent) {
var conditions = effect.value && effect.value.conditions || [];
var mins = conditions.filter(function (condition) {
var value = condition.value || {};
return effectType(condition) === 'ReagentThreshold' &&
(!value.reagent || value.reagent === reagent.id) && value.min !== undefined;
}).map(function (condition) { return Number(condition.value.min); });
return mins.length ? Math.max.apply(Math, mins) : null;
}
function effectTier(effect, reagent) {
var dose = effectDose(effect, reagent);
var properties = reagent.properties || {};
if (dose !== null && properties.criticalOverdose !== undefined && dose >= properties.criticalOverdose) {
return 'critical';
}
if (dose !== null && properties.overdose !== undefined && dose >= properties.overdose) {
return 'overdose';
}
return 'normal';
}
function tierTitle(tier, reagent) {
var properties = reagent.properties || {};
if (tier === 'critical') {
return 'Критическая передозировка' + (properties.criticalOverdose !== undefined ? ' · от ' + formatAmount(properties.criticalOverdose) : '');
}
if (tier === 'overdose') {
return 'Передозировка' + (properties.overdose !== undefined ? ' · от ' + formatAmount(properties.overdose) : '');
}
return 'Обычная доза' + (properties.overdose !== undefined ? ' · до ' + formatAmount(properties.overdose) : '');
}
function renderEffects(effects, reagent) {
var list = el('div', 'scmc-chem__effect-list');
if (!effects || !effects.length) {
add(list, el('p', 'scmc-chem__empty', 'Эффекты не указаны.'));
return list;
}
var tiers = { normal: [], overdose: [], critical: [] };
effects.forEach(function (effect) {
tiers[effectTier(effect, reagent)].push(effect);
});
['normal', 'overdose', 'critical'].forEach(function (tier) {
if (!tiers[tier].length) {
return;
}
var block = el('div', 'scmc-chem__effect-tier scmc-chem__effect-tier--' + tier);
add(block, el('div', 'scmc-chem__effect-tier-title', tierTitle(tier, reagent)));
tiers[tier].forEach(function (effect) {
add(block, renderEffect(effect, reagent, tier));
});
add(list, block);
});
return list;
}
function plantEffectKind(type, amount) {
if (/Mutation/.test(type)) {
return 'warning';
}
var harmfulWhenPositive = /Toxins|Weeds|Pests/.test(type);
return (harmfulWhenPositive ? amount <= 0 : amount >= 0) ? 'beneficial' : 'harmful';
}
function renderPlantEffects(effects) {
var list = el('div', 'scmc-chem__effect-list');
(effects || []).forEach(function (effect) {
var type = effectType(effect);
var value = effect.value || {};
if (type === 'PlantCryoxadone') {
var special = el('div', 'scmc-chem__effect scmc-chem__effect--beneficial');
add(special, el('div', 'scmc-chem__effect-name', 'Криогенное восстановление'));
add(special, line(action('Омолаживает', 'beneficial'), ' растение при низкой температуре'));
add(list, special);
return;
}
var amount = Number(value.amount || 0);
var kind = plantEffectKind(type, amount);
var card = el('div', 'scmc-chem__effect scmc-chem__effect--' + kind);
add(card, el('div', 'scmc-chem__effect-name', capitalizeFirst(PLANT_LABELS[type] || ('Эффект «' + type + '»'))));
add(card, line(
action(amount >= 0 ? 'Повышает' : 'Снижает', kind === 'beneficial' ? 'beneficial' : kind === 'harmful' ? 'harmful' : 'warning'),
' ' + (PLANT_LABELS[type] || 'показатель') + ' на ' + formatNumber(Math.abs(amount)) +
probabilityText(value.probability)
));
add(list, card);
});
return list;
}
function renderMetabolisms(reagent) {
var wrapper = el('div', 'scmc-chem__card-section');
add(wrapper, el('h4', 'scmc-chem__card-subtitle', 'Воздействие'));
var metabolisms = reagent.properties && reagent.properties.metabolisms;
var keys = Object.keys(metabolisms || {});
if (!keys.length) {
add(wrapper, el('p', 'scmc-chem__empty', 'Метаболические эффекты не указаны.'));
return wrapper;
}
keys.forEach(function (key) {
var metabolism = metabolisms[key] || {};
var block = el('div', 'scmc-chem__metabolism');
var title = METABOLISM_LABELS[key] || key;
if (metabolism.metabolismRate !== undefined) {
title += ' · ' + formatAmount(metabolism.metabolismRate) + '/с';
}
add(block, el('div', 'scmc-chem__metabolism-title', title), renderEffects(metabolism.effects, reagent));
add(wrapper, block);
});
return wrapper;
}
function renderPlantMetabolism(reagent) {
var effects = reagent.properties && reagent.properties.plantMetabolism;
if (!Array.isArray(effects) || !effects.length) {
return null;
}
var wrapper = el('div', 'scmc-chem__card-section');
var block = el('div', 'scmc-chem__metabolism');
add(wrapper, el('h4', 'scmc-chem__card-subtitle', 'Воздействие на растения'));
add(block, el('div', 'scmc-chem__metabolism-title', 'Метаболизм растений'), renderPlantEffects(effects));
add(wrapper, block);
return wrapper;
}
function renderCard(entry) {
var reagent = getReagent(entry.id) || {
id: entry.id,
name: entry.name,
origin: entry.origin,
properties: {}
};
var color = safeColor(reagent.properties && reagent.properties.color);
var details = el('details', 'scmc-chem__card');
details.style.setProperty('--scmc-chem-color', color);
var summary = el('summary', 'scmc-chem__card-summary');
var heading = add(el('span', 'scmc-chem__card-heading'),
el('span', 'scmc-chem__card-name', capitalizeFirst(reagent.name || entry.name || entry.id)),
el('code', 'scmc-chem__card-id', entry.id)
);
add(summary,
el('span', 'scmc-chem__color'),
heading,
el('span', 'scmc-chem__origin scmc-chem__origin--' + (reagent.origin || entry.origin || 'upstream-reference'), ORIGIN_LABELS[reagent.origin || entry.origin] || reagent.origin || entry.origin),
el('span', 'scmc-chem__chevron')
);
summary.firstChild.style.backgroundColor = color;
var body = el('div', 'scmc-chem__card-body');
add(body, el('p', reagent.description ? 'scmc-chem__description' : 'scmc-chem__empty', reagent.description || 'Описание не указано.'));
var overdose = reagent.properties && reagent.properties.overdose;
var critical = reagent.properties && reagent.properties.criticalOverdose;
if (overdose !== undefined || critical !== undefined) {
var doses = el('div', 'scmc-chem__dose-row');
if (overdose !== undefined) {
add(doses, el('span', 'scmc-chem__dose scmc-chem__dose--warning', 'Передозировка: ' + formatAmount(overdose)));
}
if (critical !== undefined) {
add(doses, el('span', 'scmc-chem__dose scmc-chem__dose--danger', 'Критическая: ' + formatAmount(critical)));
}
add(body, doses);
}
var reactions = state.productReactions[entry.id] || [];
if (reactions.length) {
var recipeSection = el('div', 'scmc-chem__card-section');
add(recipeSection, el('h4', 'scmc-chem__card-subtitle', 'Получение'));
reactions.forEach(function (reaction) { add(recipeSection, renderReaction(reaction)); });
add(body, recipeSection);
}
add(body, renderMetabolisms(reagent));
add(body, renderPlantMetabolism(reagent));
add(details, summary, body);
return details;
}
function renderResults(root) {
var result = root.querySelector('.scmc-chem__results');
var counter = root.querySelector('.scmc-chem__result-count');
result.textContent = '';
var entries = state.data.catalogSections[state.activeTab].filter(entryMatches);
var groups = groupEntries(entries);
counter.textContent = entries.length + ' найдено';
renderSectionNav(root, groups);
if (!entries.length) {
add(result, el('div', 'scmc-chem__no-results', 'Ничего не найдено. Измените запрос.'));
return;
}
groups.forEach(function (group, index) {
var section = el('section', 'scmc-chem__section');
section.id = 'scmc-chem-section-' + index + '-' + slug(group.title);
var heading = add(el('div', 'scmc-chem__section-heading'),
el('h2', 'scmc-chem__section-title', capitalizeFirst(group.title)),
el('span', 'scmc-chem__breadcrumbs', group.path.map(capitalizeFirst).join(' › ')),
el('span', 'scmc-chem__section-count', group.entries.length)
);
var cards = el('div', 'scmc-chem__cards');
group.entries.forEach(function (entry) { add(cards, renderCard(entry)); });
add(section, heading, cards);
add(result, section);
});
}
function renderApp(root) {
root.textContent = '';
add(root, createHero());
var controls = el('div', 'scmc-chem__controls');
var search = el('label', 'scmc-chem__search');
var input = el('input', 'scmc-chem__search-input');
input.type = 'search';
input.placeholder = 'Название, ID или раздел…';
input.autocomplete = 'off';
input.setAttribute('aria-label', 'Поиск по химическому каталогу');
input.addEventListener('input', function () {
state.query = normalize(input.value);
renderResults(root);
});
add(search, el('span', 'scmc-chem__search-icon', '⌕'), input);
add(controls, search, el('span', 'scmc-chem__result-count'));
add(root, controls, el('div', 'scmc-chem__tabs'));
var nav = el('nav', 'scmc-chem__section-nav');
nav.setAttribute('aria-label', 'Подразделы каталога');
add(root, nav, el('main', 'scmc-chem__results'));
var source = state.data.source || {};
add(root, el('footer', 'scmc-chem__footer', 'Локальный каталог MediaWiki' + (source.commit ? ' · версия ' + String(source.commit).slice(0, 8) : '')));
renderTabs(root);
renderResults(root);
}
function start(root) {
renderLoading(root);
loadCatalog().then(function (data) {
state.data = data;
indexReactions();
renderApp(root);
}).catch(function (error) {
renderError(root, error);
});
}
function init() {
var root = document.getElementById(ROOT_ID);
if (!root) {
return;
}
loadStyle();
start(root);
}
$(init);
}(mediaWiki, jQuery));