Mediawiki:scmcchemistry.js

Вернуться

Строка 2: Строка 2:
  * Интерактивный каталог химии SSMC.
  * Интерактивный каталог химии SSMC.
  * Страница: MC:Химия
  * Страница: MC:Химия
  * Данные: https://github.com/DeferW/ssmc-wiki-data
  * Данные: DeferW/ssmc-wiki-data
  */
  */
(function () {
(function () {
Строка 20: Строка 20:
     ];
     ];


     var EFFECT_LABELS = {
     var DAMAGE_LABELS = {
         AdjustReagent: 'Создаёт другой реагент',
         Airloss: 'урона от кислородного голодания',
         Anticorrosive: 'Лечит ожоги и кислотные повреждения',
         Asphyxiation: 'урона от кислородного голодания',
         Antihallucinogenic: 'Подавляет галлюцинации',
         Bloodloss: 'урона от кровопотери',
         Antitoxic: 'Выводит токсины',
         Blunt: 'тупого урона',
         Biocidic: 'Лечит клеточные повреждения',
         Brute: 'механического урона',
         Carcinogenic: 'Вызывает генетические повреждения',
         Burn: 'термического урона',
         CauseZombieInfection: 'Вызывает зомби-инфекцию',
         Caustic: 'кислотного урона',
         ChemHealEyeDamage: 'Лечит повреждения глаз',
         Cellular: 'клеточного урона',
         ChemVomit: 'Вызывает рвоту',
         Cold: 'холодового урона',
         Corrosive: 'Наносит кислотные повреждения',
         Genetic: 'генетического урона',
         CureZombieInfection: 'Лечит зомби-инфекцию',
         Heat: 'ожогового урона',
         Drunk: 'Вызывает опьянение',
         Piercing: 'колотого урона',
         Electrogenetic: 'Стимулирует сердечную деятельность',
         Poison: 'токсического урона',
         Emote: 'Вызывает непроизвольную реакцию',
         Radiation: 'радиационного урона',
         EqualHealthChange: 'Равномерно изменяет здоровье',
         Slash: 'режущего урона',
         FlammableReaction: 'Повышает горючесть',
         Toxin: 'токсического урона'
        Flowing: 'Повышает текучесть',
        Focusing: 'Повышает концентрацию',
        Fueling: 'Повышает горючесть смеси',
        GenericStatusEffect: 'Изменяет состояние организма',
        HealthChange: 'Изменяет здоровье',
        Hemogenic: 'Восстанавливает кровь',
        Hypoxemic: 'Вызывает кислородное голодание',
        Jitter: 'Вызывает дрожь',
        Ketogenic: 'Ускоряет расход питательных веществ',
        ModifyStatusEffect: 'Изменяет статусный эффект',
        MovespeedModifier: 'Изменяет скорость передвижения',
        Neogenetic: 'Лечит механические повреждения',
        Nutritious: 'Восстанавливает питание',
        Oxidizing: 'Усиливает горение',
        Oxygenating: 'Восстанавливает кислород',
        PopupMessage: 'Вызывает ощущение или сообщение',
        SatiateHunger: 'Утоляет голод',
        SatiateThirst: 'Утоляет жажду',
        Thermostabilizing: 'Стабилизирует температуру тела',
        Toxic: 'Наносит токсические повреждения',
        Viscous: 'Повышает вязкость'
     };
     };


     var DAMAGE_LABELS = {
     var STATUS_LABELS = {
         Asphyxiation: 'удушье',
         Jitter: 'дрожь',
         Bloodloss: 'кровопотеря',
         RMCUnconscious: 'потерю сознания',
         Blunt: 'ушибы',
         StatusEffectSeeingRainbow: 'галлюцинации',
         Brute: 'механические',
         Unconscious: 'потерю сознания'
         Burn: 'ожоги',
    };
         Caustic: 'кислота',
 
         Cellular: 'клеточные',
    var EMOTE_LABELS = {
         Cold: 'холод',
        Gasp: 'судорожный вдох',
         Genetic: 'генетические',
        Laugh: 'смех',
         Heat: 'тепло',
        Scream: 'крик',
         Piercing: 'колотые',
         Yawn: 'зевоту'
         Poison: 'яд',
    };
         Radiation: 'радиация',
 
         Slash: 'порезы',
    var PLANT_ATTRIBUTE_LABELS = {
         Toxin: 'токсины'
        PlantAdjustGrowth: 'возраст растения',
         PlantAdjustHealth: 'здоровье растения',
         PlantAdjustMutationLevel: 'уровень мутации',
         PlantAdjustMutationMod: 'модификатор мутации',
         PlantAdjustNutrition: 'уровень питания',
         PlantAdjustPests: 'уровень вредителей',
         PlantAdjustPotency: 'потенцию растения',
         PlantAdjustToxins: 'уровень токсинов',
         PlantAdjustWater: 'уровень воды',
         PlantAdjustWeeds: 'уровень сорняков',
         PlantAffectGrowth: 'возраст растения'
     };
     };


Строка 126: Строка 115:
     function formatAmount(value) {
     function formatAmount(value) {
         return formatNumber(value) + ' ед.';
         return formatNumber(value) + ' ед.';
    }
    function capitalizeFirst(value) {
        var text = String(value || '');
        if (!text) {
            return text;
        }
        return text.charAt(0).toLocaleUpperCase('ru-RU') + text.slice(1);
    }
    function finishSentence(value) {
        var text = capitalizeFirst(String(value || '').trim());
        if (!text || /[.!?]$/.test(text)) {
            return text;
        }
        return text + '.';
     }
     }


Строка 203: Строка 212:


         return state.data.guides[tabId] || [];
         return state.data.guides[tabId] || [];
    }
    function effectValue(effect) {
        var value = effect && effect.value;
        return value && typeof value === 'object' ? value : {};
    }
    function lowerFirst(value) {
        var text = String(value || '');
        return text ? text.charAt(0).toLocaleLowerCase('ru-RU') + text.slice(1) : text;
    }
    function withChance(text, probability) {
        if (typeof probability !== 'number' || probability >= 1) {
            return finishSentence(text);
        }
        return finishSentence('С шансом ' + formatNumber(probability * 100) + '% ' + lowerFirst(text));
     }
     }


     function conditionText(condition) {
     function conditionText(condition) {
         var type = tagName(condition && condition.yamlTag);
         var type = tagName(condition && condition.yamlTag);
         var value = condition && condition.value;
         var value = effectValue(condition);
        value = value && typeof value === 'object' ? value : {};


         if (type === 'ReagentThreshold') {
         if (type === 'ReagentThreshold') {
Строка 215: Строка 241:
             }
             }
             if (value.min !== undefined) {
             if (value.min !== undefined) {
                 return 'от ' + formatAmount(value.min) + ' в организме';
                 return 'при количестве от ' + formatAmount(value.min) + ' в организме';
             }
             }
             if (value.max !== undefined) {
             if (value.max !== undefined) {
                 return 'до ' + formatAmount(value.max) + ' в организме';
                 return 'при количестве до ' + formatAmount(value.max) + ' в организме';
             }
             }
         }
         }


         if (type === 'Temperature') {
         if (type === 'Temperature') {
            if (value.min !== undefined && value.max !== undefined) {
                return 'при температуре от ' + formatNumber(value.min) + ' до ' + formatNumber(value.max) + ' K';
            }
             if (value.min !== undefined) {
             if (value.min !== undefined) {
                 return 'при температуре не ниже ' + formatNumber(value.min) + ' K';
                 return 'при температуре не ниже ' + formatNumber(value.min) + ' K';
Строка 232: Строка 261:


         if (type === 'MobStateCondition') {
         if (type === 'MobStateCondition') {
             return value.mobstate === 'Critical' ? 'в критическом состоянии' : 'состояние: ' + value.mobstate;
             return value.mobstate === 'Critical' ? 'в критическом состоянии' : 'в состоянии «' + (value.mobstate || 'неизвестно') + '»';
         }
         }


         if (type === 'OrganType') {
         if (type === 'OrganType') {
             return (value.shouldHave === false ? 'кроме типа ' : 'для типа ') + (value.type || 'неизвестно');
             return (value.shouldHave === false ? 'кроме организмов типа ' : 'для организмов типа ') + (value.type || 'неизвестно');
         }
         }


         return type ? 'условие: ' + type : '';
         return type ? 'при выполнении условия «' + type + '»' : '';
    }
 
    function thresholdMinimum(effect) {
        var conditions = effectValue(effect).conditions;
        var minimum = null;
 
        if (!Array.isArray(conditions)) {
            return minimum;
        }
 
        conditions.forEach(function (condition) {
            if (tagName(condition && condition.yamlTag) !== 'ReagentThreshold') {
                return;
            }
 
            var value = effectValue(condition);
            if (typeof value.min === 'number' && (minimum === null || value.min > minimum)) {
                minimum = value.min;
            }
        });
 
        return minimum;
     }
     }


     function damageText(damage) {
     function effectTier(effect, reagent) {
         var parts = [];
         var minimum = thresholdMinimum(effect);
        var properties = reagent.properties || {};


         if (!damage || typeof damage !== 'object') {
         if (minimum === null) {
             return '';
            return 'normal';
        }
        if (typeof properties.criticalOverdose === 'number' && minimum >= properties.criticalOverdose) {
            return 'critical';
        }
        if (typeof properties.overdose === 'number' && minimum >= properties.overdose) {
             return 'overdose';
         }
         }


         ['groups', 'types'].forEach(function (key) {
         return 'normal';
             var values = damage[key];
    }
 
    function remainingConditions(effect, reagent) {
        var conditions = effectValue(effect).conditions;
 
        if (!Array.isArray(conditions)) {
             return [];
        }


             if (!values || typeof values !== 'object') {
        return conditions.map(function (condition) {
                 return;
             if (tagName(condition && condition.yamlTag) === 'ReagentThreshold' && effectTier(effect, reagent) !== 'normal') {
                 return '';
             }
             }
            return conditionText(condition);
        }).filter(Boolean);
    }
    function damageChanges(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) {
                Object.keys(values).forEach(function (damageId) {
                var amount = values[damageId];
                    entries.push({ id: damageId, amount: values[damageId] });
                 var action = amount < 0 ? 'лечит' : 'наносит';
                 });
                parts.push(action + ' ' + formatNumber(Math.abs(amount)) + ' (' + (DAMAGE_LABELS[damageId] || damageId) + ')');
             });
             });
        }
        return entries.map(function (entry) {
            var action = entry.amount < 0 ? 'Лечит' : 'Наносит';
            return action + ' ' + formatNumber(Math.abs(entry.amount)) + ' ед. ' + (DAMAGE_LABELS[entry.id] || ('урона типа «' + entry.id + '»')) + ' в секунду';
         });
         });
    }


         return parts.join(', ');
    function doseDescription(tier, text) {
         return { tier: tier, text: finishSentence(text) };
     }
     }


     function equalDamageText(damage) {
     function addDoseEffects(result, reagent, overdoseText, criticalText) {
         if (!Array.isArray(damage)) {
        var properties = reagent.properties || {};
             return '';
 
         if (properties.overdose !== undefined && overdoseText) {
            result.push(doseDescription('overdose', overdoseText));
        }
        if (properties.criticalOverdose !== undefined && criticalText) {
             result.push(doseDescription('critical', criticalText));
         }
         }
    }


        return damage.map(function (entry) {
    function rmcEffectDescriptions(type, value, reagent) {
            var key = Object.keys(entry || {})[0];
        var result = [];
            var amount = key ? entry[key] : 0;
        var potency = Number(value.potency || 0);
            var action = amount < 0 ? 'лечит' : 'наносит';
        var actual = potency * 0.5;
             return action + ' ' + formatNumber(Math.abs(amount)) + ' (' + (DAMAGE_LABELS[key] || key) + ')';
        var perSecond = potency * 0.25;
        }).join(', ');
        var healing;
    }
 
        if (type === 'Neogenetic') {
            healing = perSecond * (actual > 2 ? 1.5 : 1);
             result.push(doseDescription('normal', 'Лечит ' + formatNumber(healing) + ' ед. механического урона в секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. ожогового урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. ожогового и ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду');
            return result;
        }


    function effectDetails(type, value) {
        if (type === 'Anticorrosive') {
        var details = [];
            healing = perSecond * (actual > 2 ? 1.5 : 1);
        value = value && typeof value === 'object' ? value : {};
            result.push(doseDescription('normal', 'Лечит ' + formatNumber(healing) + ' ед. термического урона в секунду'));
            addDoseEffects(result, reagent,
                'Наносит по ' + formatNumber(perSecond) + ' ед. тупого и токсического урона в секунду',
                'Дополнительно наносит по ' + formatNumber(perSecond * 5) + ' ед. тупого и токсического урона в секунду');
            return result;
        }


         if (value.potency !== undefined) {
         if (type === 'Biocidic') {
             details.push('сила ' + formatNumber(value.potency));
             result.push(doseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. механического урона в секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. механического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. механического урона в секунду');
            return result;
         }
         }
         if (value.factor !== undefined) {
 
             details.push('коэффициент ' + formatNumber(value.factor));
         if (type === 'Antitoxic') {
             result.push(doseDescription('normal', 'Лечит по ' + formatNumber(perSecond * 2) + ' ед. токсического и генетического урона в секунду; удаляет 0,125 ед. токсичных веществ из крови в секунду'));
            addDoseEffects(result, reagent, '', 'Дополнительно даёт 5% шанс потерять сознание на 10 секунд');
            return result;
         }
         }
         if (value.probability !== undefined && value.probability > 0) {
 
             details.push('шанс ' + formatNumber(value.probability * 100) + '%');
         if (type === 'Oxygenating') {
             result.push(doseDescription('normal', actual >= 3
                ? 'Полностью лечит урон от кислородного голодания и удаляет ' + formatAmount(perSecond) + ' Лексорина из крови в секунду'
                : 'Лечит ' + formatNumber(perSecond) + ' ед. урона от кислородного голодания и удаляет ' + formatAmount(perSecond) + ' Лексорина из крови в секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 0.5) + ' ед. токсического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond) + ' ед. тупого и ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду');
            return result;
         }
         }
         if (value.boozePower !== undefined) {
 
             details.push('крепость ' + formatNumber(value.boozePower));
         if (type === 'Electrogenetic') {
             result.push(doseDescription('normal', 'При дефибрилляции лечит по ' + formatNumber(potency * 10) + ' ед. механического, термического и токсического урона; расходует 1 ед. препарата'));
            return result;
         }
         }
         if (value.time !== undefined) {
 
             details.push('длительность ' + formatNumber(value.time) + ' сек.');
         if (type === 'Antihallucinogenic') {
             result.push(doseDescription('normal', 'Удаляет по 2,5 ед. Токсина «Разрушитель разума» и космических наркотиков из крови; сокращает галлюцинации на ' + formatNumber(perSecond * 10) + ' сек. каждую секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду',
                'Дополнительно наносит по ' + formatNumber(perSecond) + ' ед. тупого и ожогового урона, а также ' + formatNumber(perSecond * 3) + ' ед. токсического урона в секунду');
            return result;
         }
         }
         if (value.damage) {
 
             details.push(type === 'EqualHealthChange' ? equalDamageText(value.damage) : damageText(value.damage));
         if (type === 'Focusing') {
             result.push(doseDescription('normal', 'Удаляет ' + formatAmount(perSecond) + ' алкоголя из крови и сокращает опьянение, заикание и дрожь на ' + formatNumber(perSecond * 2) + ' сек. каждую секунду' + (actual >= 3 ? '; также снимает немоту и слепоту' : '')));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 3) + ' ед. токсического урона в секунду');
            return result;
         }
         }
         if (type === 'AdjustReagent' && value.reagent) {
 
             details.push((value.amount >= 0 ? 'добавляет ' : 'удаляет ') + formatAmount(Math.abs(value.amount || 0)) + ' «' + reagentName(value.reagent) + '»');
         if (type === 'Thermostabilizing') {
             result.push(doseDescription('normal', 'Возвращает температуру тела к норме, изменяя её примерно на ' + formatNumber(perSecond * 60) + ' K в секунду'));
            addDoseEffects(result, reagent,
                'Вызывает потерю сознания на 40 секунд',
                'Дополнительно даёт 5% шанс потерять сознание на 10 секунд');
            return result;
         }
         }
         if (type === 'MovespeedModifier') {
 
             if (value.walkSpeedModifier !== undefined) {
         if (type === 'Hemogenic') {
                details.push('ходьба ×' + formatNumber(value.walkSpeedModifier));
             result.push(doseDescription('normal', 'Если персонаж не голоден, восстанавливает ' + formatNumber(perSecond) + ' сл крови и расходует ' + formatNumber(perSecond) + ' ед. питания в секунду'));
            }
             if (actual > 3) {
             if (value.sprintSpeedModifier !== undefined) {
                 result.push(doseDescription('normal', 'При избытке крови наносит ' + formatNumber(perSecond) + ' ед. тупого и ' + formatNumber(perSecond * 2) + ' ед. урона от кислородного голодания в секунду, а также замедляет'));
                 details.push('бег ×' + formatNumber(value.sprintSpeedModifier));
             }
             }
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду',
                'Дополнительно расходует ' + formatNumber(perSecond * 5) + ' ед. питания в секунду');
            return result;
        }
        if (type === 'Ketogenic') {
            result.push(doseDescription('normal', 'Расходует ' + formatNumber(perSecond * 5) + ' ед. питания в секунду и ускоряет выведение алкоголя'));
            addDoseEffects(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(doseDescription('normal', 'Восстанавливает ' + formatNumber(nutrition) + ' ед. питания за цикл метаболизма'));
            return result;
        }
        if (type === 'Toxic') {
            result.push(doseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. токсического урона в секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Corrosive') {
            result.push(doseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. кислотного урона в секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. кислотного урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. кислотного урона в секунду');
            return result;
        }
        if (type === 'Hypoxemic') {
            result.push(doseDescription('normal', 'Наносит ' + formatNumber(perSecond * 2) + ' ед. урона от кислородного голодания в секунду и даёт 10% шанс судорожного вдоха'));
            addDoseEffects(result, reagent,
                'Наносит по ' + formatNumber(perSecond) + ' ед. тупого и токсического урона, а также ' + formatNumber(perSecond * 5) + ' ед. урона от кислородного голодания в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. тупого и ' + formatNumber(perSecond * 2) + ' ед. токсического урона в секунду');
            return result;
        }
        if (type === 'Carcinogenic') {
            result.push(doseDescription('normal', 'Наносит ' + formatNumber(perSecond * 0.5) + ' ед. клеточного урона в секунду'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. клеточного урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 2) + ' ед. тупого урона в секунду');
            return result;
        }
        if (type === 'Oxidizing') {
            result.push(doseDescription('normal', 'Наносит ' + formatNumber(perSecond) + ' ед. ожогового урона в секунду; в огненной смеси повышает интенсивность, но уменьшает длительность и радиус'));
            addDoseEffects(result, reagent,
                'Наносит ' + formatNumber(perSecond * 2) + ' ед. ожогового урона в секунду',
                'Дополнительно наносит ' + formatNumber(perSecond * 5) + ' ед. ожогового урона в секунду');
            return result;
         }
         }
         if (type === 'GenericStatusEffect' && value.key) {
 
             details.push('статус ' + value.key);
         if (type === 'Fueling') {
             result.push(doseDescription('normal', 'Повышает воспламеняемость тела на ' + formatNumber(perSecond) + ' ед. в секунду; в огненной смеси увеличивает длительность и радиус, но снижает интенсивность'));
            addDoseEffects(result, reagent,
                'Повышает воспламеняемость тела на ' + formatNumber(perSecond * 2) + ' ед. в секунду',
                'Поджигает тело с высокой интенсивностью');
            return result;
         }
         }
         if (type === 'ModifyStatusEffect' && value.effectProto) {
 
             details.push('статус ' + value.effectProto);
         if (type === 'Flowing') {
             result.push(doseDescription('normal', 'Делает огненную смесь более текучей: увеличивает радиус, но снижает интенсивность и длительность горения'));
            return result;
         }
         }
         if (type === 'Emote' && value.emote) {
 
             details.push('реакция ' + value.emote);
         if (type === 'Viscous') {
             result.push(doseDescription('normal', 'Замедляет движение на ' + formatNumber(perSecond) + ' сек. за каждый цикл и уменьшает радиус огненной смеси'));
            addDoseEffects(result, reagent,
                'Сильно замедляет движение на ' + formatNumber(perSecond * 2) + ' сек. за каждый цикл',
                'Практически останавливает движение на ' + formatNumber(perSecond * 3) + ' сек. за каждый цикл');
            return result;
         }
         }


         return details.filter(Boolean).join('; ');
         return null;
     }
     }


     function renderEffect(effect) {
     function genericEffectDescription(effect, reagent) {
         var type = tagName(effect && effect.yamlTag);
         var type = tagName(effect && effect.yamlTag);
         var value = effect && effect.value;
         var value = effectValue(effect);
         var item = element('li', 'scmc-chem__effect');
         var probability = value.probability;
         var title = element('span', 'scmc-chem__effect-name', EFFECT_LABELS[type] || type || 'Неизвестный эффект');
         var tier = effectTier(effect, reagent);
         var details = effectDetails(type, value);
         var conditions = remainingConditions(effect, reagent);
         var conditions = value && Array.isArray(value.conditions) ? value.conditions.map(conditionText).filter(Boolean) : [];
         var text = '';
 
        if (probability === 0) {
            return [];
        }


         item.appendChild(title);
         var rmc = rmcEffectDescriptions(type, value, reagent);
        if (rmc) {
            return rmc;
        }


         if (details) {
         if (type === 'HealthChange' || type === 'EqualHealthChange') {
             item.appendChild(element('span', 'scmc-chem__effect-details', ' ' + details));
            text = damageChanges(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) {
         if (conditions.length) {
             item.appendChild(element('span', 'scmc-chem__effect-condition', ' (' + conditions.join(', ') + ')'));
             text += ' (' + conditions.join(', ') + ')';
         }
         }


        return [doseDescription(tier, withChance(text, probability))];
    }
    function renderEffectText(text) {
        var item = element('li', 'scmc-chem__effect');
        item.appendChild(element('span', 'scmc-chem__effect-name', finishSentence(text)));
         return item;
         return item;
    }
    function tierTitle(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 appendEffectTier(group, tier, effects, reagent) {
        if (!effects.length) {
            return;
        }
        var tierBlock = element('div', 'scmc-chem__effect-tier scmc-chem__effect-tier--' + tier);
        tierBlock.appendChild(element('div', 'scmc-chem__effect-tier-title', tierTitle(tier, reagent)));
        var list = element('ul', 'scmc-chem__effect-list');
        effects.forEach(function (description) {
            list.appendChild(renderEffectText(description.text));
        });
        tierBlock.appendChild(list);
        group.appendChild(tierBlock);
     }
     }


Строка 354: Строка 649:


         var block = element('div', 'scmc-chem__card-section');
         var block = element('div', 'scmc-chem__card-section');
        var hasContent = false;
         block.appendChild(element('h4', 'scmc-chem__card-subtitle', 'Воздействие'));
         block.appendChild(element('h4', 'scmc-chem__card-subtitle', 'Воздействие'));


         Object.keys(metabolisms).forEach(function (metabolismId) {
         Object.keys(metabolisms).forEach(function (metabolismId) {
             var metabolism = metabolisms[metabolismId] || {};
             var metabolism = metabolisms[metabolismId] || {};
            var tiers = { normal: [], overdose: [], critical: [] };
            (metabolism.effects || []).forEach(function (effect) {
                genericEffectDescription(effect, reagent).forEach(function (description) {
                    tiers[description.tier].push(description);
                });
            });
            if (!tiers.normal.length && !tiers.overdose.length && !tiers.critical.length) {
                return;
            }
             var group = element('div', 'scmc-chem__metabolism');
             var group = element('div', 'scmc-chem__metabolism');
             var title = METABOLISM_LABELS[metabolismId] || metabolismId;
             var title = METABOLISM_LABELS[metabolismId] || metabolismId;


             if (metabolism.metabolismRate !== undefined) {
             if (metabolism.metabolismRate !== undefined) {
                 title += ' · ' + formatAmount(metabolism.metabolismRate) + '/сек.';
                 title += ' · расход ' + formatAmount(metabolism.metabolismRate) + '/сек.';
             }
             }


             group.appendChild(element('div', 'scmc-chem__metabolism-title', title));
             group.appendChild(element('div', 'scmc-chem__metabolism-title', title));
            appendEffectTier(group, 'normal', tiers.normal, reagent);
            appendEffectTier(group, 'overdose', tiers.overdose, reagent);
            appendEffectTier(group, 'critical', tiers.critical, reagent);
            block.appendChild(group);
            hasContent = true;
        });
        return hasContent ? block : null;
    }
    function plantEffectText(effect) {
        var type = tagName(effect && effect.yamlTag);
        var value = effectValue(effect);
        var probability = value.probability;
        var amount = Number(value.amount !== undefined ? value.amount : 1);
        var attribute = PLANT_ATTRIBUTE_LABELS[type];
        var text;


             if (Array.isArray(metabolism.effects) && metabolism.effects.length) {
        if (probability === 0) {
                var list = element('ul', 'scmc-chem__effect-list');
             return '';
                 metabolism.effects.forEach(function (effect) {
        }
                     list.appendChild(renderEffect(effect));
 
                });
        if (attribute) {
                 group.appendChild(list);
            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 withChance(text, probability);
    }
    function renderPlantMetabolism(reagent) {
        var effects = reagent.properties && reagent.properties.plantMetabolism;


             block.appendChild(group);
        if (!Array.isArray(effects) || !effects.length) {
            return null;
        }
 
        var descriptions = effects.map(plantEffectText).filter(Boolean);
        if (!descriptions.length) {
             return null;
        }
 
        var block = element('div', 'scmc-chem__card-section');
        block.appendChild(element('h4', 'scmc-chem__card-subtitle', 'Метаболизм растений'));
        var group = element('div', 'scmc-chem__metabolism');
        group.appendChild(element('div', 'scmc-chem__metabolism-title', '1 ед. реагента каждые 3 секунды (базово)'));
        var list = element('ul', 'scmc-chem__effect-list');
        descriptions.forEach(function (description) {
            list.appendChild(renderEffectText(description));
         });
         });
 
        group.appendChild(list);
        block.appendChild(group);
         return block;
         return block;
     }
     }
Строка 393: Строка 758:
                 row,
                 row,
                 dot,
                 dot,
                 element('span', 'scmc-chem__mixture-name', item.name || reagentName(item.id)),
                 element('span', 'scmc-chem__mixture-name', capitalizeFirst(item.name || reagentName(item.id))),
                 element('span', 'scmc-chem__mixture-amount', formatAmount(item.amount))
                 element('span', 'scmc-chem__mixture-amount', formatAmount(item.amount))
             );
             );
Строка 493: Строка 858:
         append(
         append(
             heading,
             heading,
             element('span', 'scmc-chem__card-name', entry.name || (reagent && reagent.name) || entry.id),
             element('span', 'scmc-chem__card-name', capitalizeFirst(entry.name || (reagent && reagent.name) || entry.id)),
             element('code', 'scmc-chem__card-id', entry.id)
             element('code', 'scmc-chem__card-id', entry.id)
         );
         );
Строка 514: Строка 879:


         if (reagent.description) {
         if (reagent.description) {
             body.appendChild(element('p', 'scmc-chem__description', reagent.description));
             body.appendChild(element('p', 'scmc-chem__description', capitalizeFirst(reagent.description)));
         }
         }


         append(body, renderDoseBadges(reagent), renderRecipes(entry.id), renderMetabolisms(reagent));
         append(body, renderDoseBadges(reagent), renderRecipes(entry.id), renderMetabolisms(reagent), renderPlantMetabolism(reagent));


         if (!body.childNodes.length) {
         if (!body.childNodes.length) {
Строка 551: Строка 916:
     function renderSection(group) {
     function renderSection(group) {
         var section = element('section', 'scmc-chem__section');
         var section = element('section', 'scmc-chem__section');
         var title = group.path[group.path.length - 1] || 'Без раздела';
         var title = capitalizeFirst(group.path[group.path.length - 1] || 'Без раздела');
         var heading = element('div', 'scmc-chem__section-heading');
         var heading = element('div', 'scmc-chem__section-heading');


Строка 557: Строка 922:


         if (group.path.length > 1) {
         if (group.path.length > 1) {
             heading.appendChild(element('div', 'scmc-chem__breadcrumbs', group.path.slice(0, -1).join(' › ')));
             heading.appendChild(element('div', 'scmc-chem__breadcrumbs', group.path.slice(0, -1).map(capitalizeFirst).join(' › ')));
         }
         }