bc/server/game/logic/combatLogic.js
svoboda200786@gmail.com 86aada4e2d
Some checks failed
Deploy Project BC / deploy (push) Failing after 32s
test4
2025-06-10 08:14:09 +03:00

472 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /server/game/logic/combatLogic.js
// GAME_CONFIG и dataUtils будут передаваться в функции как параметры.
// effectsLogic может потребоваться для импорта, если updateBlockingStatus используется здесь напрямую,
// но в вашем GameInstance.js он вызывается отдельно.
// const effectsLogic = require('./effectsLogic'); // Если нужно
/**
* Обрабатывает базовую атаку одного бойца по другому.
* @param {object} attackerState - Состояние атакующего бойца из gameState.
* @param {object} defenderState - Состояние защищающегося бойца из gameState.
* @param {object} attackerBaseStats - Базовые статы атакующего (из dataUtils.getCharacterBaseStats).
* @param {object} defenderBaseStats - Базовые статы защищающегося (из dataUtils.getCharacterBaseStats).
* @param {object} currentGameState - Текущее полное состояние игры.
* @param {function} addToLogCallback - Функция для добавления сообщений в лог игры.
* @param {object} configToUse - Конфигурационный объект игры (GAME_CONFIG).
* @param {object} dataUtils - Утилиты для доступа к данным игры.
* @param {function} getRandomTauntFunction - Функция gameLogic.getRandomTaunt, переданная для использования.
*/
function performAttack(
attackerState,
defenderState,
attackerBaseStats,
defenderBaseStats,
currentGameState,
addToLogCallback,
configToUse,
dataUtils,
getRandomTauntFunction
) {
// Расчет базового урона с вариацией
let damage = Math.floor(
attackerBaseStats.attackPower *
(configToUse.DAMAGE_VARIATION_MIN + Math.random() * configToUse.DAMAGE_VARIATION_RANGE)
);
let wasBlocked = false;
let attackBonusesLog = []; // Для сбора информации о бонусах к атаке
// --- ПРОВЕРКА И ПРИМЕНЕНИЕ БОНУСА ОТ ОТЛОЖЕННОГО БАФФА АТАКИ ---
const delayedAttackBuff = attackerState.activeEffects.find(eff =>
eff.isDelayed &&
(eff.id === configToUse.ABILITY_ID_NATURE_STRENGTH || eff.id === configToUse.ABILITY_ID_ALMAGEST_BUFF_ATTACK) &&
eff.turnsLeft > 0 &&
!eff.justCast
);
if (delayedAttackBuff) {
console.log(`[CombatLogic performAttack] Found active delayed buff: ${delayedAttackBuff.name} for ${attackerState.name}`);
let damageBonus = 0;
// Если бы были прямые бонусы к урону атаки от этих баффов, они бы рассчитывались здесь
// Например:
// if (delayedAttackBuff.id === configToUse.ABILITY_ID_NATURE_STRENGTH && configToUse.NATURE_STRENGTH_ATTACK_DAMAGE_BONUS) {
// damageBonus = configToUse.NATURE_STRENGTH_ATTACK_DAMAGE_BONUS;
// } else if (delayedAttackBuff.id === configToUse.ABILITY_ID_ALMAGEST_BUFF_ATTACK && configToUse.ALMAGEST_ATTACK_BUFF_DAMAGE_BONUS) {
// damageBonus = configToUse.ALMAGEST_ATTACK_BUFF_DAMAGE_BONUS;
// }
if (damageBonus > 0) {
damage += damageBonus;
attackBonusesLog.push(`урон +${damageBonus} от "${delayedAttackBuff.name}"`);
}
let resourceRegenConfigKey = null;
if (delayedAttackBuff.id === configToUse.ABILITY_ID_NATURE_STRENGTH) {
resourceRegenConfigKey = 'NATURE_STRENGTH_MANA_REGEN';
} else if (delayedAttackBuff.id === configToUse.ABILITY_ID_ALMAGEST_BUFF_ATTACK) {
resourceRegenConfigKey = 'ALMAGEST_DARK_ENERGY_REGEN'; // Предположительный ключ
}
if (resourceRegenConfigKey && configToUse[resourceRegenConfigKey]) {
const regenAmount = configToUse[resourceRegenConfigKey];
const actualRegen = Math.min(regenAmount, attackerBaseStats.maxResource - attackerState.currentResource);
if (actualRegen > 0) {
attackerState.currentResource = Math.round(attackerState.currentResource + actualRegen);
if (addToLogCallback) {
addToLogCallback(
`🌿 ${attackerState.name} восстанавливает ${actualRegen} ${attackerState.resourceName} от "${delayedAttackBuff.name}"!`,
configToUse.LOG_TYPE_HEAL
);
}
}
}
}
// --- КОНЕЦ ПРОВЕРКИ И ПРИМЕНЕНИЯ ОТЛОЖЕННОГО БАФФА АТАКИ ---
// Проверка на блок
if (defenderState.isBlocking) {
const initialDamage = damage;
damage = Math.floor(damage * configToUse.BLOCK_DAMAGE_REDUCTION);
wasBlocked = true;
if (addToLogCallback) {
let blockLogMsg = `🛡️ ${defenderBaseStats.name} блокирует атаку ${attackerBaseStats.name}! Урон снижен (${initialDamage} -> ${damage}).`;
if (attackBonusesLog.length > 0) {
blockLogMsg += ` (${attackBonusesLog.join(', ')})`;
}
addToLogCallback(blockLogMsg, configToUse.LOG_TYPE_BLOCK);
}
} else {
if (addToLogCallback) {
let hitLogMsg = `${attackerBaseStats.name} атакует ${defenderBaseStats.name}! Наносит ${damage} урона.`;
if (attackBonusesLog.length > 0) {
hitLogMsg += ` (${attackBonusesLog.join(', ')})`;
}
addToLogCallback(hitLogMsg, configToUse.LOG_TYPE_DAMAGE);
}
}
// Применяем урон, убеждаемся, что HP не ниже нуля
const actualDamageDealtToHp = Math.min(defenderState.currentHp, damage); // Сколько HP реально отнято (не может быть больше текущего HP)
defenderState.currentHp = Math.max(0, Math.round(defenderState.currentHp - damage));
// --- Насмешка от защищающегося (defenderState) в ответ на атаку ---
if (getRandomTauntFunction && dataUtils) {
let subTriggerForTaunt = null;
if (wasBlocked) {
subTriggerForTaunt = 'attackBlocked';
} else if (actualDamageDealtToHp > 0) { // Если не было блока, но был нанесен урон
subTriggerForTaunt = 'attackHits';
}
// Можно добавить еще условие для промаха, если урон = 0 и не было блока (и actualDamageDealtToHp === 0)
// else if (damage <= 0 && !wasBlocked) { subTriggerForTaunt = 'attackMissed'; } // Если есть такой триггер
if (subTriggerForTaunt) {
const attackerFullDataForTaunt = dataUtils.getCharacterData(attackerState.characterKey);
if (attackerFullDataForTaunt) {
const reactionTaunt = getRandomTauntFunction(
defenderState.characterKey, // Говорящий (защитник)
'onOpponentAction', // Главный триггер
subTriggerForTaunt, // Подтриггер: 'attackBlocked' или 'attackHits'
configToUse,
attackerFullDataForTaunt, // Оппонент (атакующий) для говорящего
currentGameState
);
if (reactionTaunt && reactionTaunt !== "(Молчание)") {
addToLogCallback(`${defenderState.name}: "${reactionTaunt}"`, configToUse.LOG_TYPE_INFO);
}
}
}
}
}
/**
* применяет111 эффект способности.
* @param {object} ability - Объект способности.
* @param {object} casterState - Состояние бойца, применившего способность.
* @param {object} targetState - Состояние цели способности.
* @param {object} casterBaseStats - Базовые статы кастера.
* @param {object} targetBaseStats - Базовые статы цели.
* @param {object} currentGameState - Текущее полное состояние игры.
* @param {function} addToLogCallback - Функция для добавления лога.
* @param {object} configToUse - Конфигурация игры.
* @param {object} dataUtils - Утилиты для доступа к данным игры.
* @param {function} getRandomTauntFunction - Функция gameLogic.getRandomTaunt.
* @param {function|null} checkIfActionWasSuccessfulFunction - (Опционально) Функция для проверки успеха действия для контекстных насмешек.
*/
function applyAbilityEffect(
ability,
casterState,
targetState,
casterBaseStats,
targetBaseStats,
currentGameState,
addToLogCallback,
configToUse,
dataUtils,
getRandomTauntFunction,
checkIfActionWasSuccessfulFunction // Пока не используется активно, outcome определяется внутри
) {
let abilityApplicationSucceeded = true; // Флаг общего успеха применения способности
let actionOutcomeForTaunt = null; // 'success' или 'fail' для специфичных насмешек (например, Безмолвие Баларда)
switch (ability.type) {
case configToUse.ACTION_TYPE_HEAL:
const healAmount = Math.floor(ability.power * (configToUse.HEAL_VARIATION_MIN + Math.random() * configToUse.HEAL_VARIATION_RANGE));
const actualHeal = Math.min(healAmount, casterBaseStats.maxHp - casterState.currentHp);
if (actualHeal > 0) {
casterState.currentHp = Math.round(casterState.currentHp + actualHeal);
if (addToLogCallback) addToLogCallback(`💚 ${casterBaseStats.name} применяет111 "${ability.name}" и восстанавливает ${actualHeal} HP!`, configToUse.LOG_TYPE_HEAL);
actionOutcomeForTaunt = 'success'; // Для реакции оппонента, если таковая есть на хил
} else {
if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} применяет111 "${ability.name}", но не получает лечения (HP уже полное или хил = 0).`, configToUse.LOG_TYPE_INFO);
abilityApplicationSucceeded = false;
actionOutcomeForTaunt = 'fail';
}
break;
case configToUse.ACTION_TYPE_DAMAGE:
let damage = Math.floor(ability.power * (configToUse.DAMAGE_VARIATION_MIN + Math.random() * configToUse.DAMAGE_VARIATION_RANGE));
let wasAbilityBlocked = false;
let actualDamageDealtByAbility = 0;
if (targetState.isBlocking) {
const initialDamage = damage;
damage = Math.floor(damage * configToUse.BLOCK_DAMAGE_REDUCTION);
wasAbilityBlocked = true;
if (addToLogCallback) addToLogCallback(`🛡️ ${targetBaseStats.name} блокирует "${ability.name}" от ${casterBaseStats.name}! Урон снижен (${initialDamage} -> ${damage}).`, configToUse.LOG_TYPE_BLOCK);
}
actualDamageDealtByAbility = Math.min(targetState.currentHp, damage);
targetState.currentHp = Math.max(0, Math.round(targetState.currentHp - damage));
if (addToLogCallback && !wasAbilityBlocked) {
addToLogCallback(`💥 ${casterBaseStats.name} применяет111 "${ability.name}" на ${targetBaseStats.name}, нанося ${damage} урона!`, configToUse.LOG_TYPE_DAMAGE);
}
if (damage <= 0 && !wasAbilityBlocked) { // Если урон нулевой и не было блока (например, из-за резистов, которых пока нет)
abilityApplicationSucceeded = false;
actionOutcomeForTaunt = 'fail';
} else if (wasAbilityBlocked) {
actionOutcomeForTaunt = 'blocked'; // Специальный исход для реакции на блок способности
} else if (actualDamageDealtByAbility > 0) {
actionOutcomeForTaunt = 'hit'; // Специальный исход для реакции на попадание способностью
} else {
actionOutcomeForTaunt = 'fail'; // Если урон 0 и не было блока (например цель уже мертва и 0 хп)
}
break;
case configToUse.ACTION_TYPE_BUFF:
let effectDescriptionBuff = ability.description;
if (typeof ability.descriptionFunction === 'function') {
effectDescriptionBuff = ability.descriptionFunction(configToUse, targetBaseStats); // targetBaseStats здесь может быть casterBaseStats, если бафф на себя
}
// Обычно баффы накладываются на кастера
casterState.activeEffects.push({
id: ability.id, name: ability.name, description: effectDescriptionBuff,
type: ability.type, duration: ability.duration,
turnsLeft: ability.duration,
grantsBlock: !!ability.grantsBlock,
isDelayed: !!ability.isDelayed,
justCast: true
});
if (ability.grantsBlock && casterState.activeEffects.find(e => e.id === ability.id && e.grantsBlock)) {
// Требуется effectsLogic.updateBlockingStatus(casterState);
// но GameInstance вызывает его в switchTurn, так что здесь можно не дублировать, если эффект не мгновенный
}
if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} накладывает эффект "${ability.name}"!`, configToUse.LOG_TYPE_EFFECT);
actionOutcomeForTaunt = 'success'; // Для реакции оппонента, если бафф на себя
break;
case configToUse.ACTION_TYPE_DISABLE:
// Общее "полное безмолвие" от Елены или Альмагест
if (ability.id === configToUse.ABILITY_ID_HYPNOTIC_GAZE || ability.id === configToUse.ABILITY_ID_ALMAGEST_DISABLE) {
const effectIdFullSilence = ability.id === configToUse.ABILITY_ID_HYPNOTIC_GAZE ? 'fullSilenceByElena' : 'fullSilenceByAlmagest';
if (!targetState.activeEffects.some(e => e.id === effectIdFullSilence)) {
targetState.activeEffects.push({
id: effectIdFullSilence, name: ability.name, description: ability.description,
type: ability.type, duration: ability.effectDuration, turnsLeft: ability.effectDuration,
power: ability.power, isFullSilence: true, justCast: true
});
if (addToLogCallback) addToLogCallback(`🌀 ${casterBaseStats.name} применяет111 "${ability.name}" на ${targetBaseStats.name}! Способности цели заблокированы на ${ability.effectDuration} хода!`, configToUse.LOG_TYPE_EFFECT);
actionOutcomeForTaunt = 'success';
} else {
if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} пытается применить "${ability.name}", но эффект уже активен на ${targetState.name}!`, configToUse.LOG_TYPE_INFO);
abilityApplicationSucceeded = false;
actionOutcomeForTaunt = 'fail';
}
}
// Специальное Безмолвие Баларда
else if (ability.id === configToUse.ABILITY_ID_BALARD_SILENCE && casterState.characterKey === 'balard') {
const success = Math.random() < configToUse.SILENCE_SUCCESS_RATE;
actionOutcomeForTaunt = success ? 'success' : 'fail'; // Этот outcome используется в tauntLogic
if (success) {
const targetAbilitiesList = dataUtils.getCharacterAbilities(targetState.characterKey);
const availableAbilitiesToSilence = targetAbilitiesList.filter(pa =>
!targetState.disabledAbilities?.some(d => d.abilityId === pa.id) &&
!targetState.activeEffects?.some(eff => eff.id === `playerSilencedOn_${pa.id}`) &&
pa.id !== configToUse.ABILITY_ID_NONE // Исключаем "пустую" абилку, если она есть
);
if (availableAbilitiesToSilence.length > 0) {
const abilityToSilence = availableAbilitiesToSilence[Math.floor(Math.random() * availableAbilitiesToSilence.length)];
const turns = configToUse.SILENCE_DURATION;
targetState.disabledAbilities.push({ abilityId: abilityToSilence.id, turnsLeft: turns + 1 });
targetState.activeEffects.push({
id: `playerSilencedOn_${abilityToSilence.id}`, name: `Безмолвие: ${abilityToSilence.name}`,
description: `Способность "${abilityToSilence.name}" временно недоступна.`,
type: configToUse.ACTION_TYPE_DISABLE, sourceAbilityId: ability.id,
duration: turns, turnsLeft: turns + 1, justCast: true
});
if (addToLogCallback) addToLogCallback(`🔇 Эхо Безмолвия! "${abilityToSilence.name}" у ${targetBaseStats.name} заблокировано на ${turns} хода!`, configToUse.LOG_TYPE_EFFECT);
} else {
if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} пытается наложить Безмолвие, но у ${targetBaseStats.name} нечего глушить!`, configToUse.LOG_TYPE_INFO);
actionOutcomeForTaunt = 'fail'; // Переопределяем, т.к. нечего было глушить
}
} else {
if (addToLogCallback) addToLogCallback(`💨 Попытка ${casterBaseStats.name} наложить Безмолвие на ${targetBaseStats.name} провалилась!`, configToUse.LOG_TYPE_INFO);
}
}
break;
case configToUse.ACTION_TYPE_DEBUFF:
const effectIdDebuff = 'effect_' + ability.id; // Уникальный ID для дебаффа на цели
if (!targetState.activeEffects.some(e => e.id === effectIdDebuff)) {
let effectDescriptionDebuff = ability.description;
if (typeof ability.descriptionFunction === 'function') {
effectDescriptionDebuff = ability.descriptionFunction(configToUse, targetBaseStats);
}
targetState.activeEffects.push({
id: effectIdDebuff, name: ability.name, description: effectDescriptionDebuff,
type: configToUse.ACTION_TYPE_DEBUFF, sourceAbilityId: ability.id,
duration: ability.effectDuration, turnsLeft: ability.effectDuration,
power: ability.power, justCast: true
});
if (addToLogCallback) addToLogCallback(`📉 ${casterBaseStats.name} накладывает "${ability.name}" на ${targetBaseStats.name}! Эффект продлится ${ability.effectDuration} хода.`, configToUse.LOG_TYPE_EFFECT);
actionOutcomeForTaunt = 'success';
} else {
if (addToLogCallback) addToLogCallback(`${casterBaseStats.name} пытается применить "${ability.name}", но эффект уже активен на ${targetState.name}!`, configToUse.LOG_TYPE_INFO);
abilityApplicationSucceeded = false;
actionOutcomeForTaunt = 'fail';
}
break;
case configToUse.ACTION_TYPE_DRAIN: // Пример для Манадрейна Баларда
if (casterState.characterKey === 'balard' && ability.id === configToUse.ABILITY_ID_BALARD_MANA_DRAIN) {
let manaDrained = 0; let healthGained = 0; let damageDealtDrain = 0;
if (ability.powerDamage > 0) {
let baseDamageDrain = ability.powerDamage;
if (targetState.isBlocking) { // Маловероятно, что дрейны блокируются, но для полноты
baseDamageDrain = Math.floor(baseDamageDrain * configToUse.BLOCK_DAMAGE_REDUCTION);
}
damageDealtDrain = Math.max(0, baseDamageDrain);
targetState.currentHp = Math.max(0, Math.round(targetState.currentHp - damageDealtDrain));
}
const potentialDrain = ability.powerManaDrain;
const actualDrain = Math.min(potentialDrain, targetState.currentResource);
if (actualDrain > 0) {
targetState.currentResource = Math.max(0, Math.round(targetState.currentResource - actualDrain));
manaDrained = actualDrain;
const potentialHeal = Math.floor(manaDrained * (ability.powerHealthGainFactor || 0)); // Убедимся, что фактор есть
const actualHealGain = Math.min(potentialHeal, casterBaseStats.maxHp - casterState.currentHp);
if (actualHealGain > 0) {
casterState.currentHp = Math.round(casterState.currentHp + actualHealGain);
healthGained = actualHealGain;
}
}
let logMsgDrain = `${casterBaseStats.name} применяет1111111 "${ability.name}"! `;
if (damageDealtDrain > 0) logMsgDrain += `Наносит ${damageDealtDrain} урона ${targetBaseStats.name}. `;
if (manaDrained > 0) {
logMsgDrain += `Вытягивает ${manaDrained} ${targetBaseStats.resourceName} у ${targetBaseStats.name}`;
if(healthGained > 0) logMsgDrain += ` и исцеляется на ${healthGained} HP!`; else logMsgDrain += `!`;
} else if (damageDealtDrain > 0) {
logMsgDrain += `${targetBaseStats.name} не имеет ${targetBaseStats.resourceName} для похищения.`;
} else {
logMsgDrain += `Не удалось ничего похитить у ${targetBaseStats.name}.`;
}
if (addToLogCallback) addToLogCallback(logMsgDrain, (manaDrained > 0 || damageDealtDrain > 0) ? configToUse.LOG_TYPE_DAMAGE : configToUse.LOG_TYPE_INFO);
if (manaDrained <= 0 && damageDealtDrain <= 0 && healthGained <= 0) {
abilityApplicationSucceeded = false;
actionOutcomeForTaunt = 'fail';
} else {
actionOutcomeForTaunt = 'success';
}
}
break;
default:
if (addToLogCallback) addToLogCallback(`Неизвестный тип способности: ${ability?.type} для "${ability?.name}"`, configToUse.LOG_TYPE_SYSTEM);
console.warn(`applyAbilityEffect: Неизвестный тип способности: ${ability?.type} для способности ${ability?.id}`);
abilityApplicationSucceeded = false;
actionOutcomeForTaunt = 'fail';
}
// --- Насмешка от цели (targetState) в ответ на применение способности оппонентом (casterState) ---
// Вызываем только если способность не была нацелена на самого себя И есть функция насмешек
if (getRandomTauntFunction && dataUtils && casterState.id !== targetState.id) {
const casterFullDataForTaunt = dataUtils.getCharacterData(casterState.characterKey);
if (casterFullDataForTaunt) {
let tauntContext = { abilityId: ability.id };
// Если для этой способности был определен исход (например, для безмолвия Баларда, или попадание/блок урона)
// Используем actionOutcomeForTaunt, который мы установили в switch-case выше
if (actionOutcomeForTaunt === 'success' || actionOutcomeForTaunt === 'fail' || actionOutcomeForTaunt === 'blocked' || actionOutcomeForTaunt === 'hit') {
tauntContext.outcome = actionOutcomeForTaunt;
}
// Для способностей типа DAMAGE, 'blocked' и 'hit' будут ключами в taunts.js (например, Elena onOpponentAction -> ABILITY_ID_ALMAGEST_DAMAGE -> blocked: [...])
// Это не стандартные 'attackBlocked' и 'attackHits', а специфичные для реакции на *способность*
// Если вы хотите использовать общие 'attackBlocked'/'attackHits' и для способностей, вам нужно будет изменить логику в taunts.js
// или передавать здесь другие subTrigger'ы, если способность заблокирована/попала.
const reactionTaunt = getRandomTauntFunction(
targetState.characterKey, // Кто говорит (цель способности)
'onOpponentAction', // Триггер
tauntContext, // Контекст: ID способности кастера (оппонента) и, возможно, outcome
configToUse,
casterFullDataForTaunt, // Оппонент для говорящего - это кастер
currentGameState
);
if (reactionTaunt && reactionTaunt !== "(Молчание)") {
addToLogCallback(`${targetState.name}: "${reactionTaunt}"`, configToUse.LOG_TYPE_INFO);
}
}
}
}
/**
* Проверяет валидность использования способности.
* @param {object} ability - Объект способности.
* @param {object} casterState - Состояние бойца, который пытается применить способность.
* @param {object} targetState - Состояние цели (может быть тем же, что и casterState).
* @param {object} configToUse - Конфигурационный объект игры (GAME_CONFIG).
* @returns {object} - { isValid: boolean, reason: string|null }
*/
function checkAbilityValidity(ability, casterState, targetState, configToUse) {
if (!ability) return { isValid: false, reason: "Способность не найдена." };
if (casterState.currentResource < ability.cost) {
return { isValid: false, reason: `${casterState.name} пытается применить "${ability.name}", но не хватает ${casterState.resourceName} (${casterState.currentResource}/${ability.cost})!` };
}
if ((casterState.abilityCooldowns?.[ability.id] || 0) > 0) {
return { isValid: false, reason: `"${ability.name}" еще на перезарядке (${casterState.abilityCooldowns[ability.id]} х.).` };
}
// Специальные кулдауны для Баларда
if (casterState.characterKey === 'balard') {
if (ability.id === configToUse.ABILITY_ID_BALARD_SILENCE && (casterState.silenceCooldownTurns || 0) > 0) {
return { isValid: false, reason: `"${ability.name}" (спец. КД) еще на перезарядке (${casterState.silenceCooldownTurns} х.).` };
}
if (ability.id === configToUse.ABILITY_ID_BALARD_MANA_DRAIN && (casterState.manaDrainCooldownTurns || 0) > 0) {
return { isValid: false, reason: `"${ability.name}" (спец. КД) еще на перезарядке (${casterState.manaDrainCooldownTurns} х.).` };
}
}
// Проверка на безмолвие
const isCasterFullySilenced = casterState.activeEffects.some(eff => eff.isFullSilence && eff.turnsLeft > 0);
const isAbilitySpecificallySilenced = casterState.disabledAbilities?.some(dis => dis.abilityId === ability.id && dis.turnsLeft > 0);
if (isCasterFullySilenced) {
return { isValid: false, reason: `${casterState.name} не может использовать способности из-за полного безмолвия!` };
}
if (isAbilitySpecificallySilenced) {
const specificSilenceEffect = casterState.disabledAbilities.find(dis => dis.abilityId === ability.id);
return { isValid: false, reason: `Способность "${ability.name}" у ${casterState.name} временно заблокирована (${specificSilenceEffect.turnsLeft} х.)!` };
}
// Проверка наложения баффа, который уже активен (кроме обновляемых)
if (ability.type === configToUse.ACTION_TYPE_BUFF && casterState.activeEffects.some(e => e.id === ability.id)) {
// Исключение для "отложенных" баффов, которые можно обновлять (например, Сила Природы)
if (!ability.isDelayed) { // Если isDelayed не true, то нельзя обновлять.
return { isValid: false, reason: `Эффект "${ability.name}" уже активен у ${casterState.name}!` };
}
}
// Проверка наложения дебаффа, который уже активен на цели
const isTargetedDebuff = ability.type === configToUse.ACTION_TYPE_DEBUFF ||
(ability.type === configToUse.ACTION_TYPE_DISABLE && ability.id !== configToUse.ABILITY_ID_BALARD_SILENCE); // Безмолвие Баларда может пытаться наложиться повторно (и провалиться)
if (isTargetedDebuff && targetState.id !== casterState.id) { // Убедимся, что это не бафф на себя, проверяемый как дебафф
const effectIdToCheck = (ability.type === configToUse.ACTION_TYPE_DISABLE && ability.id !== configToUse.ABILITY_ID_BALARD_SILENCE) ?
(ability.id === configToUse.ABILITY_ID_HYPNOTIC_GAZE ? 'fullSilenceByElena' : 'fullSilenceByAlmagest') :
('effect_' + ability.id);
if (targetState.activeEffects.some(e => e.id === effectIdToCheck)) {
return { isValid: false, reason: `Эффект "${ability.name}" уже наложен на ${targetState.name}!` };
}
}
return { isValid: true, reason: null };
}
module.exports = {
performAttack,
applyAbilityEffect,
checkAbilityValidity
};