Mediawiki:scmcchemistry.js

Вернуться

Строка 30: Строка 30:
         Narcotic: 'Наркотик',
         Narcotic: 'Наркотик',
         Poison: 'Яд'
         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 = {
     var DAMAGE_LABELS = {
         Airloss: 'кислородного голодания',
         Airloss: 'урона от кислородного голодания',
         Asphyxiation: 'кислородного голодания',
         Asphyxiation: 'урона от кислородного голодания',
         Bloodloss: 'кровопотери',
         Bloodloss: 'урона от кровопотери',
         Blunt: 'тупого', Brute: 'механического', Burn: 'термического',
         Blunt: 'тупого урона',
         Caustic: 'кислотного', Cellular: 'клеточного', Cold: 'холодового',
        Brute: 'механического урона',
         Genetic: 'генетического', Heat: 'ожогового', Piercing: 'колотого',
        Burn: 'термического урона',
         Poison: 'токсического', Radiation: 'радиационного', Slash: 'режущего',
         Caustic: 'кислотного урона',
         Toxin: 'токсического'
        Cellular: 'клеточного урона',
        Cold: 'холодового урона',
         Genetic: 'генетического урона',
        Heat: 'ожогового урона',
        Piercing: 'колотого урона',
         Poison: 'токсического урона',
        Radiation: 'радиационного урона',
        Slash: 'режущего урона',
         Toxin: 'токсического урона'
     };
     };
     var DAMAGE_CLASSES = {
     var EFFECT_TOKEN_RE = /(урона от кислородного голодания|урона от кровопотери|полностью лечит|дополнительно наносит|восстанавливает жизнеспособность|создаёт иммунитет|восстанавливает|возвращает|омолаживает|сокращает|удаляет|лечит|наносит|вызывает|заражает|поджигает|уничтожает|расходует|замедляет|снижает|повышает|механического(?: урона)?|тупого(?: урона)?|колотого(?: урона)?|режущего(?: урона)?|термического(?: урона)?|ожогового(?: урона)?|холодового(?: урона)?|токсического(?: урона)?|кислотного(?: урона)?|генетического(?: урона)?|клеточного(?: урона)?|радиационного(?: урона)?)/gi;
         Airloss: 'oxygen', Asphyxiation: 'oxygen', Bloodloss: 'oxygen',
    var EFFECT_ACTION_CLASSES = {
         Blunt: 'physical', Brute: 'physical', Piercing: 'physical', Slash: 'physical',
         'полностью лечит': 'beneficial', 'лечит': 'beneficial',
         Burn: 'burn', Heat: 'burn', Cold: 'cold', Poison: 'toxin', Toxin: 'toxin',
        'восстанавливает': 'beneficial', 'восстанавливает жизнеспособность': 'beneficial',
         Caustic: 'acid', Genetic: 'genetic', Cellular: 'cellular', Radiation: 'radiation'
         'возвращает': 'beneficial', 'омолаживает': 'beneficial', 'сокращает': 'beneficial',
        'удаляет': 'beneficial', 'создаёт иммунитет': 'beneficial',
         'наносит': 'harmful', 'дополнительно наносит': 'harmful', 'вызывает': 'harmful',
        'заражает': 'harmful', 'поджигает': 'harmful', 'уничтожает': 'harmful',
         'расходует': 'warning', 'замедляет': 'warning',
        'снижает': 'utility', 'повышает': 'utility'
     };
     };
    var DAMAGE_TOKEN_CLASSES = [
        { pattern: /кислородного голодания|кровопотери/, className: 'oxygen' },
        { pattern: /механического|тупого|колотого|режущего/, className: 'physical' },
        { pattern: /термического|ожогового/, className: 'burn' },
        { pattern: /холодового/, className: 'cold' },
        { pattern: /токсического/, className: 'toxin' },
        { pattern: /кислотного/, className: 'acid' },
        { pattern: /генетического/, className: 'genetic' },
        { pattern: /клеточного/, className: 'cellular' },
        { pattern: /радиационного/, className: 'radiation' }
    ];
     var STATUS_LABELS = {
     var STATUS_LABELS = {
         Drowsiness: 'сонливость', Jitter: 'дрожь', RMCUnconscious: 'потерю сознания',
         Jitter: 'дрожь', RMCUnconscious: 'потерю сознания',
        ScrambledAccent: 'невнятную речь', StaminaModifier: 'изменение выносливости',
         StatusEffectSeeingRainbow: 'галлюцинации', Unconscious: 'потерю сознания'
         StatusEffectSeeingRainbow: 'галлюцинации', Unconscious: 'потерю сознания'
     };
     };
     var EMOTE_LABELS = {
     var EMOTE_LABELS = {
         Cough: 'кашель', Gasp: 'судорожный вдох', Laugh: 'смех',
         Gasp: 'судорожный вдох', Laugh: 'смех', Scream: 'крик', Yawn: 'зевоту'
        Scream: 'крик', Sneeze: 'чихание', Yawn: 'зевоту'
     };
     };
     var PLANT_LABELS = {
     var PLANT_ATTRIBUTE_LABELS = {
         PlantAdjustGrowth: 'возраст растения', PlantAdjustHealth: 'здоровье растения',
         PlantAdjustGrowth: 'возраст растения', PlantAdjustHealth: 'здоровье растения',
         PlantAdjustMutationLevel: 'уровень мутации', PlantAdjustMutationMod: 'шанс мутации',
         PlantAdjustMutationLevel: 'уровень мутации', PlantAdjustMutationMod: 'модификатор мутации',
         PlantAdjustNutrition: 'питание растения', PlantAdjustPests: 'число вредителей',
         PlantAdjustNutrition: 'уровень питания', PlantAdjustPests: 'уровень вредителей',
         PlantAdjustPotency: 'потенцию растения', PlantAdjustToxins: 'уровень токсинов',
         PlantAdjustPotency: 'потенцию растения', PlantAdjustToxins: 'уровень токсинов',
         PlantAdjustWater: 'уровень воды', PlantAdjustWeeds: 'число сорняков'
         PlantAdjustWater: 'уровень воды', PlantAdjustWeeds: 'уровень сорняков',
    };
         PlantAffectGrowth: 'возраст растения'
    var ORGAN_LABELS = {
        Animal: 'животного', Bloodsucker: 'кровососа', Human: 'человека',
        Rat: 'крысы', Slime: 'слайма', Vox: 'вокса', Xeno: 'ксеноморфа'
    };
    var MOB_STATE_LABELS = {
         Alive: 'жив', Critical: 'критическое состояние', Dead: 'мёртв'
     };
     };
     var REAGENT_LABELS = {
     var REAGENT_LABELS = {
Строка 154: Строка 131:
         var text = String(value || '').trim();
         var text = String(value || '').trim();
         return text ? text.charAt(0).toLocaleUpperCase('ru-RU') + text.slice(1) : text;
         return text ? text.charAt(0).toLocaleUpperCase('ru-RU') + text.slice(1) : text;
    }
    function lowerFirst(value) {
        var text = String(value || '');
        return text ? text.charAt(0).toLocaleLowerCase('ru-RU') + text.slice(1) : text;
    }
    function finishSentence(value) {
        var text = capitalizeFirst(String(value || '').trim());
        return !text || /[.!?]$/.test(text) ? text : text + '.';
    }
    function tagName(tag) {
        return String(tag || '').replace(/^!type:/, '');
     }
     }


Строка 172: Строка 163:
     function getReagent(id) {
     function getReagent(id) {
         return state.data.reagents[id] || state.data.dependencies[id] || null;
         return state.data.reagents[id] || state.data.dependencies[id] || null;
    }
    function reagentName(id) {
        var reagent = getReagent(id);
        return reagent && reagent.name ? reagent.name : REAGENT_LABELS[id] || id;
     }
     }


Строка 655: Строка 651:
         add(wrapper, block);
         add(wrapper, block);
         return wrapper;
         return wrapper;
    }
    /* Исходная реализация описаний из js.txt. */
    function originalEffectValue(effect) {
        var value = effect && effect.value;
        return value && typeof value === 'object' ? value : {};
    }
    function originalWithChance(text, probability) {
        if (typeof probability !== 'number' || probability >= 1) {
            return finishSentence(text);
        }
        return finishSentence('С шансом ' + formatNumber(probability * 100) + '% ' + lowerFirst(text));
    }
    function originalConditionText(condition) {
        var type = tagName(condition && condition.yamlTag);
        var value = originalEffectValue(condition);
        if (type === 'ReagentThreshold') {
            if (value.min !== undefined && value.max !== undefined) {
                return 'при количестве от ' + formatAmount(value.min) + ' до ' + formatAmount(value.max);
            }
            if (value.min !== undefined) {
                return 'при количестве от ' + formatAmount(value.min) + ' в организме';
            }
            if (value.max !== undefined) {
                return 'при количестве до ' + formatAmount(value.max) + ' в организме';
            }
        }
        if (type === 'Temperature') {
            if (value.min !== undefined && value.max !== undefined) {
                return 'при температуре от ' + formatNumber(value.min) + ' до ' + formatNumber(value.max) + ' K';
            }
            if (value.min !== undefined) {
                return 'при температуре не ниже ' + formatNumber(value.min) + ' K';
            }
            if (value.max !== undefined) {
                return 'при температуре не выше ' + formatNumber(value.max) + ' K';
            }
        }
        if (type === 'MobStateCondition') {
            return value.mobstate === 'Critical' ? 'в критическом состоянии' : 'в состоянии «' + (value.mobstate || 'неизвестно') + '»';
        }
        if (type === 'OrganType') {
            return (value.shouldHave === false ? 'кроме организмов типа ' : 'для организмов типа ') + (value.type || 'неизвестно');
        }
        return type ? 'при выполнении условия «' + type + '»' : '';
    }
    function originalThresholdMinimum(effect) {
        var conditions = originalEffectValue(effect).conditions;
        var minimum = null;
        if (!Array.isArray(conditions)) {
            return minimum;
        }
        conditions.forEach(function (condition) {
            if (tagName(condition && condition.yamlTag) !== 'ReagentThreshold') {
                return;
            }
            var value = originalEffectValue(condition);
            if (typeof value.min === 'number' && (minimum === null || value.min > minimum)) {
                minimum = value.min;
            }
        });
        return minimum;
    }
    function originalEffectTier(effect, reagent) {
        var minimum = originalThresholdMinimum(effect);
        var properties = reagent.properties || {};
        if (minimum === null) {
            return 'normal';
        }
        if (typeof properties.criticalOverdose === 'number' && minimum >= properties.criticalOverdose) {
            return 'critical';
        }
        if (typeof properties.overdose === 'number' && minimum >= properties.overdose) {
            return 'overdose';
        }
        return 'normal';
    }
    function originalRemainingConditions(effect, reagent) {
        var conditions = originalEffectValue(effect).conditions;
        if (!Array.isArray(conditions)) {
            return [];
        }
        return conditions.map(function (condition) {
            if (tagName(condition && condition.yamlTag) === 'ReagentThreshold' && originalEffectTier(effect, reagent) !== 'normal') {
                return '';
            }
            return originalConditionText(condition);
        }).filter(Boolean);
    }
    function originalDamageChanges(damage, equalGroups) {
        var entries = [];
        if (equalGroups && Array.isArray(damage)) {
            damage.forEach(function (entry) {
                var key = Object.keys(entry || {})[0];
                if (key) {
                    entries.push({ id: key, amount: entry[key] });
                }
            });
        } else if (damage && typeof damage === 'object') {
            ['groups', 'types'].forEach(function (key) {
                var values = damage[key];
                if (!values || typeof values !== 'object') {
                    return;
                }
                Object.keys(values).forEach(function (damageId) {
                    entries.push({ id: damageId, amount: values[damageId] });
                });
            });
        }
        return entries.map(function (entry) {
            var actionText = entry.amount < 0 ? 'Лечит' : 'Наносит';
            return actionText + ' ' + formatNumber(Math.abs(entry.amount)) + ' ед. ' +
                (DAMAGE_LABELS[entry.id] || ('урона типа «' + entry.id + '»')) + ' в секунду';
        });
    }
    function originalDoseDescription(tier, text) {
        return { tier: tier, text: finishSentence(text) };
    }
    function originalAddDoseEffects(result, reagent, overdoseText, criticalText) {
        var properties = reagent.properties || {};
        if (properties.overdose !== undefined && overdoseText) {
            result.push(originalDoseDescription('overdose', overdoseText));
        }
        if (properties.criticalOverdose !== undefined && criticalText) {
            result.push(originalDoseDescription('critical', criticalText));
        }
    }
    function originalRmcEffectDescriptions(type, value, reagent) {
        var result = [];
        var potency = Number(value.potency || 0);
        var actual = potency || 0.5;
        var perSecond = potency * 0.25;
        var healing;
        if (type === 'Neogenetic') {
            healing = perSecond * (actual >= 2 ? 1.5 : 1);
            result.push(originalDoseDescription('normal', 'Лечит ' + formatNumber(healing) + ' ед. механического урона в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. ожогового урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. ожогового и ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Anticorrosive') {
            healing = perSecond * (actual >= 2 ? 1.5 : 1);
            result.push(originalDoseDescription('normal', 'Лечит ' + formatNumber(healing) + ' ед. термического урона в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит по ' + formatNumber(perSecond) + ' ед. тупого и токсического урона в секунду',
                'Дополнительно наносит по ' + formatNumber(perSecond * 5) + ' ед. тупого и токсического урона в секунду');
            return result;
        }
        if (type === 'Biocidic') {
            result.push(originalDoseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. механического урона в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. механического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. механического урона в секунду');
            return result;
        }
        if (type === 'Antitoxic') {
            result.push(originalDoseDescription('normal', 'Лечит по ' + formatNumber(perSecond * 2) + ' ед. токсического и генетического урона в секунду; удаляет 0,125 ед. токсичных веществ из крови в секунду'));
            originalAddDoseEffects(result, reagent, '', 'Дополнительно даёт 5% шанс потерять сознание на 10 секунд');
            return result;
        }
        if (type === 'Oxygenating') {
            result.push(originalDoseDescription('normal', actual >= 3 ?
                'Полностью лечит урон от кислородного голодания и удаляет ' + formatAmount(perSecond) + ' Лексорина из крови в секунду' :
                'Лечит ' + formatNumber(perSecond) + ' ед. урона от кислородного голодания и удаляет ' + formatAmount(perSecond) + ' Лексорина из крови в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 0.5) + ' ед. токсического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond) + ' ед. тупого и ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Electrogenetic') {
            result.push(originalDoseDescription('normal', 'При дефибрилляции лечит по ' + formatNumber(potency * 10) + ' ед. механического, термического и токсического урона; расходует 1 ед. препарата'));
            return result;
        }
        if (type === 'Antihallucinogenic') {
            result.push(originalDoseDescription('normal', 'Удаляет по 2,5 ед. Токсина «Разрушитель разума» и космических наркотиков из крови; сокращает галлюцинации на ' + formatNumber(perSecond * 10) + ' сек. каждую секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду',
                'Дополнительно наносит по ' + formatNumber(perSecond) + ' ед. тупого и ожогового урона, а также ' + formatNumber(perSecond * 3) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Focusing') {
            result.push(originalDoseDescription('normal', 'Удаляет ' + formatAmount(perSecond) + ' алкоголя из крови и сокращает опьянение, заикание и дрожь на ' + formatNumber(perSecond * 2) + ' сек. каждую секунду' + (actual >= 3 ? '; также снимает немоту и слепоту' : '')));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 3) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Thermostabilizing') {
            result.push(originalDoseDescription('normal', 'Возвращает температуру тела к норме, изменяя её примерно на ' + formatNumber(perSecond * 60) + ' K в секунду'));
            originalAddDoseEffects(result, reagent, 'Вызывает потерю сознания на 40 секунд', 'Дополнительно даёт 5% шанс потерять сознание на 10 секунд');
            return result;
        }
        if (type === 'Hemogenic') {
            result.push(originalDoseDescription('normal', 'Если персонаж не голоден, восстанавливает ' + formatNumber(perSecond) + ' сл крови и расходует ' + formatNumber(perSecond) + ' ед. питания в секунду'));
            if (actual >= 3) {
                result.push(originalDoseDescription('normal', 'При избытке крови наносит ' + formatNumber(perSecond) + ' ед. тупого и ' + formatNumber(perSecond * 2) + ' ед. урона от кислородного голодания в секунду, а также замедляет'));
            }
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду',
                'Дополнительно расходует ' + formatNumber(perSecond * 5) + ' ед. питания в секунду');
            return result;
        }
        if (type === 'Ketogenic') {
            result.push(originalDoseDescription('normal', 'Расходует ' + formatNumber(perSecond * 5) + ' ед. питания в секунду и ускоряет выведение алкоголя'));
            originalAddDoseEffects(result, reagent,
                'Дополнительно расходует ' + formatNumber(perSecond * 5) + ' ед. питания, наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду и даёт ' + formatNumber(actual * 2.5) + '% шанс рвоты',
                'Вызывает потерю сознания на 40 секунд');
            return result;
        }
        if (type === 'Nutritious') {
            var nutrition = ((Number(value.nutFactor || 0) * Number(value.nutMetabolism || 0)) + potency) * actual;
            result.push(originalDoseDescription('normal', 'Восстанавливает ' + formatNumber(nutrition) + ' ед. питания за цикл метаболизма'));
            return result;
        }
        if (type === 'Toxic') {
            result.push(originalDoseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Corrosive') {
            result.push(originalDoseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. кислотного урона в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. кислотного урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. кислотного урона в секунду');
            return result;
        }
        if (type === 'Hypoxemic') {
            result.push(originalDoseDescription('normal', 'Наносит ' + formatNumber(perSecond * 2) + ' ед. урона от кислородного голодания в секунду и даёт 10% шанс судорожного вдоха'));
            originalAddDoseEffects(result, reagent,
                'Наносит по ' + formatNumber(perSecond) + ' ед. тупого и токсического урона, а также ' + formatNumber(perSecond * 5) + ' ед. урона от кислородного голодания в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. тупого и ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Carcinogenic') {
            result.push(originalDoseDescription('normal', 'Наносит ' + formatNumber(perSecond * 0.5) + ' ед. клеточного урона в секунду'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. клеточного урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 2) + ' ед. тупого урона в секунду');
            return result;
        }
        if (type === 'Oxidizing') {
            result.push(originalDoseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. ожогового урона в секунду; в огненной смеси повышает интенсивность, но уменьшает длительность и радиус'));
            originalAddDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. ожогового урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. ожогового урона в секунду');
            return result;
        }
        if (type === 'Fueling') {
            result.push(originalDoseDescription('normal', 'Повышает воспламеняемость тела на ' + formatNumber(perSecond) + ' ед. в секунду; в огненной смеси увеличивает длительность и радиус, но снижает интенсивность'));
            originalAddDoseEffects(result, reagent,
                'Повышает воспламеняемость тела на ' + formatNumber(perSecond * 2) + ' ед. в секунду',
                'Поджигает тело с высокой интенсивностью');
            return result;
        }
        if (type === 'Flowing') {
            result.push(originalDoseDescription('normal', 'Делает огненную смесь более текучей: увеличивает радиус, но снижает интенсивность и длительность горения'));
            return result;
        }
        if (type === 'Viscous') {
            result.push(originalDoseDescription('normal', 'Замедляет движение на ' + formatNumber(perSecond) + ' сек. за каждый цикл и уменьшает радиус огненной смеси'));
            originalAddDoseEffects(result, reagent,
                'Сильно замедляет движение на ' + formatNumber(perSecond * 2) + ' сек. за каждый цикл',
                'Практически останавливает движение на ' + formatNumber(perSecond * 3) + ' сек. за каждый цикл');
            return result;
        }
        return null;
    }
    function originalGenericEffectDescription(effect, reagent) {
        var type = tagName(effect && effect.yamlTag);
        var value = originalEffectValue(effect);
        var probability = value.probability;
        var tier = originalEffectTier(effect, reagent);
        var conditions = originalRemainingConditions(effect, reagent);
        var text = '';
        if (probability === 0) {
            return [];
        }
        var rmc = originalRmcEffectDescriptions(type, value, reagent);
        if (rmc) {
            return rmc;
        }
        if (type === 'HealthChange' || type === 'EqualHealthChange') {
            text = originalDamageChanges(value.damage, type === 'EqualHealthChange').join('; ');
        } else if (type === 'AdjustReagent' && value.reagent) {
            text = (value.amount >= 0 ? 'Добавляет ' : 'Удаляет ') + formatAmount(Math.abs(value.amount || 0)) + ' реагента «' + capitalizeFirst(reagentName(value.reagent)) + '» за цикл метаболизма';
        } else if (type === 'MovespeedModifier') {
            var walk = Math.round((Number(value.walkSpeedModifier || 1) - 1) * 100);
            var sprint = Math.round((Number(value.sprintSpeedModifier || 1) - 1) * 100);
            text = 'Изменяет скорость ходьбы на ' + (walk >= 0 ? '+' : '') + walk + '% и бега на ' + (sprint >= 0 ? '+' : '') + sprint + '%';
        } else if (type === 'GenericStatusEffect') {
            var status = STATUS_LABELS[value.key] || value.key || 'неизвестный статус';
            text = value.type === 'Remove' ? 'Сокращает ' + status + ' на ' + formatNumber(value.time || 2) + ' сек.' : 'Вызывает ' + status + ' на ' + formatNumber(value.time || 2) + ' сек.';
        } else if (type === 'ModifyStatusEffect') {
            var modifiedStatus = STATUS_LABELS[value.effectProto] || value.effectProto || 'неизвестный статус';
            text = value.type === 'Remove' ? 'Сокращает ' + modifiedStatus + ' на ' + formatNumber(value.time || 2) + ' сек.' : 'Вызывает ' + modifiedStatus + ' на ' + formatNumber(value.time || 2) + ' сек.';
        } else if (type === 'Jitter') {
            text = 'Вызывает дрожь на ' + formatNumber(value.time || 2) + ' сек.';
        } else if (type === 'Emote') {
            text = 'Вызывает ' + (EMOTE_LABELS[value.emote] || ('эмоцию «' + (value.emote || 'неизвестно') + '»'));
        } else if (type === 'ChemVomit') {
            text = 'Вызывает рвоту';
        } else if (type === 'Drunk') {
            text = 'Добавляет ' + formatNumber(value.boozePower || 3) + ' сек. опьянения за цикл метаболизма';
        } else if (type === 'SatiateHunger') {
            text = 'Восстанавливает ' + formatNumber(value.factor !== undefined ? value.factor : 3) + ' ед. насыщения за цикл метаболизма';
        } else if (type === 'SatiateThirst') {
            text = 'Восстанавливает ' + formatNumber(value.factor !== undefined ? value.factor : 3) + ' ед. жажды за цикл метаболизма';
        } else if (type === 'ChemHealEyeDamage') {
            text = 'Лечит повреждения глаз';
        } else if (type === 'CauseZombieInfection') {
            text = 'Заражает зомби-инфекцией';
        } else if (type === 'CureZombieInfection') {
            text = value.innoculate ? 'Лечит зомби-инфекцию и создаёт иммунитет' : 'Лечит зомби-инфекцию';
        } else if (type === 'FlammableReaction') {
            text = 'Покрывает цель горючим; интенсивность составляет ' + formatNumber(Number(value.multiplier || 1) * 100) + '% от стандартной';
        } else if (type === 'PopupMessage') {
            text = value.messages && value.messages.indexOf('rmc-body-stings') !== -1 ? 'Вызывает болезненное жжение' : 'Вызывает заметное субъективное ощущение действия препарата';
        } else {
            text = 'Дополнительный игровой эффект «' + (type || 'неизвестно') + '»';
        }
        if (conditions.length) {
            text += ' (' + conditions.join(', ') + ')';
        }
        return [originalDoseDescription(tier, originalWithChance(text, probability))];
    }
    function originalSemanticTokenClass(token) {
        var normalized = String(token || '').toLocaleLowerCase('ru-RU');
        var actionClass = EFFECT_ACTION_CLASSES[normalized];
        if (actionClass) {
            return 'scmc-chem__effect-action scmc-chem__effect-action--' + actionClass;
        }
        for (var index = 0; index < DAMAGE_TOKEN_CLASSES.length; index += 1) {
            DAMAGE_TOKEN_CLASSES[index].pattern.lastIndex = 0;
            if (DAMAGE_TOKEN_CLASSES[index].pattern.test(normalized)) {
                return 'scmc-chem__damage-type scmc-chem__damage-type--' + DAMAGE_TOKEN_CLASSES[index].className;
            }
        }
        return '';
    }
    function originalEffectTone(text) {
        var value = String(text || '').toLocaleLowerCase('ru-RU');
        var beneficial = value.search(/полностью лечит|лечит|восстанавливает|возвращает|омолаживает|сокращает|удаляет|создаёт иммунитет/);
        var harmful = value.search(/наносит|вызывает|заражает|поджигает|уничтожает/);
        var warning = value.search(/расходует|замедляет/);
        var candidates = [];
        if (beneficial !== -1) { candidates.push({ index: beneficial, tone: 'beneficial' }); }
        if (harmful !== -1) { candidates.push({ index: harmful, tone: 'harmful' }); }
        if (warning !== -1) { candidates.push({ index: warning, tone: 'warning' }); }
        candidates.sort(function (left, right) { return left.index - right.index; });
        return candidates.length ? candidates[0].tone : 'utility';
    }
    function originalAppendSemanticEffectText(parent, text) {
        var value = finishSentence(text);
        var lastIndex = 0;
        var match;
        EFFECT_TOKEN_RE.lastIndex = 0;
        while ((match = EFFECT_TOKEN_RE.exec(value)) !== null) {
            if (match.index > lastIndex) {
                parent.appendChild(document.createTextNode(value.slice(lastIndex, match.index)));
            }
            var className = originalSemanticTokenClass(match[0]);
            parent.appendChild(className ? el('span', className, match[0]) : document.createTextNode(match[0]));
            lastIndex = EFFECT_TOKEN_RE.lastIndex;
        }
        if (lastIndex < value.length) {
            parent.appendChild(document.createTextNode(value.slice(lastIndex)));
        }
    }
    function originalRenderEffectText(text) {
        var tone = originalEffectTone(text);
        var item = el('li', 'scmc-chem__effect scmc-chem__effect--' + tone);
        var content = el('span', 'scmc-chem__effect-name');
        originalAppendSemanticEffectText(content, text);
        item.appendChild(content);
        return item;
    }
    function originalTierTitle(tier, reagent) {
        var properties = reagent.properties || {};
        if (tier === 'overdose') {
            return 'Передозировка' + (properties.overdose !== undefined ? ' · от ' + formatAmount(properties.overdose) : '');
        }
        if (tier === 'critical') {
            return 'Критическая передозировка' + (properties.criticalOverdose !== undefined ? ' · от ' + formatAmount(properties.criticalOverdose) : '');
        }
        return 'Обычная доза';
    }
    function originalAppendEffectTier(group, tier, effects, reagent) {
        if (!effects.length) {
            return;
        }
        var tierBlock = el('div', 'scmc-chem__effect-tier scmc-chem__effect-tier--' + tier);
        tierBlock.appendChild(el('div', 'scmc-chem__effect-tier-title', originalTierTitle(tier, reagent)));
        var list = el('ul', 'scmc-chem__effect-list');
        effects.forEach(function (description) { list.appendChild(originalRenderEffectText(description.text)); });
        tierBlock.appendChild(list);
        group.appendChild(tierBlock);
    }
    function renderOriginalMetabolisms(reagent) {
        var metabolisms = reagent.properties && reagent.properties.metabolisms;
        if (!metabolisms || !Object.keys(metabolisms).length) {
            return null;
        }
        var block = el('div', 'scmc-chem__card-section');
        var hasContent = false;
        block.appendChild(el('h4', 'scmc-chem__card-subtitle', 'Воздействие'));
        Object.keys(metabolisms).forEach(function (metabolismId) {
            var metabolism = metabolisms[metabolismId] || {};
            var tiers = { normal: [], overdose: [], critical: [] };
            (metabolism.effects || []).forEach(function (effect) {
                originalGenericEffectDescription(effect, reagent).forEach(function (description) {
                    tiers[description.tier].push(description);
                });
            });
            if (!tiers.normal.length && !tiers.overdose.length && !tiers.critical.length) {
                return;
            }
            var group = el('div', 'scmc-chem__metabolism');
            var title = METABOLISM_LABELS[metabolismId] || metabolismId;
            if (metabolism.metabolismRate !== undefined) {
                title += ' · расход ' + formatAmount(metabolism.metabolismRate) + '/сек.';
            }
            group.appendChild(el('div', 'scmc-chem__metabolism-title', title));
            originalAppendEffectTier(group, 'normal', tiers.normal, reagent);
            originalAppendEffectTier(group, 'overdose', tiers.overdose, reagent);
            originalAppendEffectTier(group, 'critical', tiers.critical, reagent);
            block.appendChild(group);
            hasContent = true;
        });
        return hasContent ? block : null;
    }
    function originalPlantEffectText(effect) {
        var type = tagName(effect && effect.yamlTag);
        var value = originalEffectValue(effect);
        var probability = value.probability;
        var amount = Number(value.amount !== undefined ? value.amount : 1);
        var attribute = PLANT_ATTRIBUTE_LABELS[type];
        var text;
        if (probability === 0) {
            return '';
        }
        if (attribute) {
            if (type === 'PlantAdjustHealth') {
                text = amount >= 0 ? 'Восстанавливает здоровье растения на ' + formatNumber(amount) : 'Снижает здоровье растения на ' + formatNumber(Math.abs(amount));
            } else {
                text = (amount >= 0 ? 'Повышает ' : 'Снижает ') + attribute + ' на ' + formatNumber(Math.abs(amount));
            }
        } else if (type === 'PlantCryoxadone') {
            text = 'Омолаживает растение с учётом его возраста и времени роста';
        } else if (type === 'PlantPhalanximine') {
            text = 'Возвращает жизнеспособность растению, погибшему из-за мутации';
        } else if (type === 'PlantDiethylamine') {
            text = 'Повышает продолжительность жизни или базовое здоровье растения; шанс 10% на единицу реагента';
        } else if (type === 'PlantRestoreSeeds') {
            text = 'Восстанавливает семена растения';
        } else if (type === 'PlantDestroySeeds') {
            text = 'Уничтожает семена растения';
        } else {
            text = 'Дополнительный растительный эффект «' + (type || 'неизвестно') + '»';
        }
        return originalWithChance(text, probability);
    }
    function renderOriginalPlantMetabolism(reagent) {
        var effects = reagent.properties && reagent.properties.plantMetabolism;
        if (!Array.isArray(effects) || !effects.length) {
            return null;
        }
        var descriptions = effects.map(originalPlantEffectText).filter(Boolean);
        if (!descriptions.length) {
            return null;
        }
        var block = el('div', 'scmc-chem__card-section');
        block.appendChild(el('h4', 'scmc-chem__card-subtitle', 'Метаболизм растений'));
        var group = el('div', 'scmc-chem__metabolism');
        group.appendChild(el('div', 'scmc-chem__metabolism-title', '1 ед. реагента каждые 3 секунды (базово)'));
        var list = el('ul', 'scmc-chem__effect-list');
        descriptions.forEach(function (description) { list.appendChild(originalRenderEffectText(description)); });
        group.appendChild(list);
        block.appendChild(group);
        return block;
     }
     }


Строка 700: Строка 1198:
             add(body, recipeSection);
             add(body, recipeSection);
         }
         }
         add(body, renderMetabolisms(reagent));
         add(body, renderOriginalMetabolisms(reagent));
         add(body, renderPlantMetabolism(reagent));
         add(body, renderOriginalPlantMetabolism(reagent));
         add(details, summary, body);
         add(details, summary, body);
         return details;
         return details;