bc/server/game/logic/aiLogic.js
svoboda200786@gmail.com d0241d6053
Some checks failed
Deploy Project BC / deploy (push) Failing after 33s
test
2025-06-10 00:23:43 +03:00

133 lines
10 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/aiLogic.js
// GAME_CONFIG и gameData (или dataUtils) будут передаваться в decideAiAction как параметры,
// но для удобства можно импортировать GAME_CONFIG здесь, если он нужен для внутренних констант AI,
// не зависящих от передаваемого конфига.
// const GAME_CONFIG_STATIC = require('../../core/config'); // Если нужно для чего-то внутреннего
/**
* Логика принятия решения для AI (Балард).
* @param {object} currentGameState - Текущее состояние игры.
* @param {object} dataUtils - Утилиты для доступа к данным игры (getCharacterData, getCharacterAbilities и т.д.).
* @param {object} configToUse - Конфигурационный объект игры (переданный GAME_CONFIG).
* @param {function} addToLogCallback - Функция для добавления лога (опционально, если AI должен логировать свои "мысли").
* @returns {object} Объект с действием AI ({ actionType: 'attack' | 'ability' | 'pass', ability?: object, logMessage?: {message, type} }).
*/
function decideAiAction(currentGameState, dataUtils, configToUse, addToLogCallback) {
const opponentState = currentGameState.opponent; // AI Балард всегда в слоте opponent
const playerState = currentGameState.player; // Игрок всегда в слоте player (в AI режиме)
// Убеждаемся, что это AI Балард и есть необходимые данные
if (opponentState.characterKey !== 'balard' || !dataUtils) {
console.warn("[AI Logic] decideAiAction called for non-Balard opponent or missing dataUtils. Passing turn.");
if (addToLogCallback) addToLogCallback(`${opponentState.name || 'AI'} пропускает ход из-за внутренней ошибки.`, configToUse.LOG_TYPE_SYSTEM);
return { actionType: 'pass', logMessage: { message: `${opponentState.name || 'AI'} пропускает ход.`, type: configToUse.LOG_TYPE_INFO } };
}
const balardCharacterData = dataUtils.getCharacterData('balard');
if (!balardCharacterData || !balardCharacterData.abilities) {
console.warn("[AI Logic] Failed to get Balard's character data or abilities. Passing turn.");
if (addToLogCallback) addToLogCallback(`AI Балард пропускает ход из-за ошибки загрузки данных.`, configToUse.LOG_TYPE_SYSTEM);
return { actionType: 'pass', logMessage: { message: `Балард пропускает ход.`, type: configToUse.LOG_TYPE_INFO } };
}
const balardAbilities = balardCharacterData.abilities;
// Проверка полного безмолвия Баларда (от Гипнотического Взгляда Елены и т.п.)
const isBalardFullySilenced = opponentState.activeEffects.some(
eff => eff.isFullSilence && eff.turnsLeft > 0
);
if (isBalardFullySilenced) {
// AI под полным безмолвием просто атакует.
// Лог о безмолвии добавляется в GameInstance перед вызовом этой функции или при обработке атаки.
// Здесь можно добавить лог о "вынужденной" атаке, если нужно.
if (addToLogCallback) {
// Проверяем, не был ли лог о безмолвии уже добавлен в этом ходу (чтобы не спамить)
// Это упрощенная проверка, в реальном приложении можно использовать флаги или более сложную логику.
// if (!currentGameState.logContainsThisTurn || !currentGameState.logContainsThisTurn.includes('под действием Безмолвия')) {
// addToLogCallback(`😵 ${opponentState.name} под действием Безмолвия! Атакует в смятении.`, configToUse.LOG_TYPE_EFFECT);
// if(currentGameState) currentGameState.logContainsThisTurn = (currentGameState.logContainsThisTurn || "") + 'под действием Безмолвия';
// }
}
return { actionType: 'attack' };
}
const availableActions = [];
// 1. Проверяем способность "Покровительство Тьмы" (Лечение)
const healAbility = balardAbilities.find(a => a.id === configToUse.ABILITY_ID_BALARD_HEAL);
if (healAbility && opponentState.currentResource >= healAbility.cost &&
(opponentState.abilityCooldowns?.[healAbility.id] || 0) <= 0 && // Общий КД
healAbility.condition(opponentState, playerState, currentGameState, configToUse)) {
availableActions.push({ weight: 80, type: 'ability', ability: healAbility, requiresSuccessCheck: true, successRate: healAbility.successRate });
}
// 2. Проверяем способность "Эхо Безмолвия"
const silenceAbility = balardAbilities.find(a => a.id === configToUse.ABILITY_ID_BALARD_SILENCE);
if (silenceAbility && opponentState.currentResource >= silenceAbility.cost &&
(opponentState.silenceCooldownTurns === undefined || opponentState.silenceCooldownTurns <= 0) && // Спец. КД
(opponentState.abilityCooldowns?.[silenceAbility.id] || 0) <= 0 && // Общий КД
silenceAbility.condition(opponentState, playerState, currentGameState, configToUse)) {
// Условие в silenceAbility.condition уже проверяет, что Елена не под безмолвием
availableActions.push({ weight: 60, type: 'ability', ability: silenceAbility, requiresSuccessCheck: true, successRate: configToUse.SILENCE_SUCCESS_RATE });
}
// 3. Проверяем способность "Похищение Света" (Вытягивание маны и урон)
const drainAbility = balardAbilities.find(a => a.id === configToUse.ABILITY_ID_BALARD_MANA_DRAIN);
if (drainAbility && opponentState.currentResource >= drainAbility.cost &&
(opponentState.manaDrainCooldownTurns === undefined || opponentState.manaDrainCooldownTurns <= 0) && // Спец. КД
(opponentState.abilityCooldowns?.[drainAbility.id] || 0) <= 0 && // Общий КД
drainAbility.condition(opponentState, playerState, currentGameState, configToUse)) {
availableActions.push({ weight: 50, type: 'ability', ability: drainAbility });
}
// 4. Базовая атака - всегда доступна как запасной вариант с низким весом
availableActions.push({ weight: 30, type: 'attack' });
if (availableActions.length === 0) {
// Этого не должно происходить, так как атака всегда добавляется
if (addToLogCallback) addToLogCallback(`${opponentState.name} не может совершить действие (нет доступных).`, configToUse.LOG_TYPE_INFO);
return { actionType: 'pass', logMessage: { message: `${opponentState.name} пропускает ход.`, type: configToUse.LOG_TYPE_INFO } };
}
// Сортируем действия по весу в порядке убывания (самые приоритетные в начале)
availableActions.sort((a, b) => b.weight - a.weight);
// console.log(`[AI Logic] Available actions for Balard, sorted by weight:`, JSON.stringify(availableActions.map(a => ({type: a.type, name: a.ability?.name, weight: a.weight})), null, 2));
// Перебираем действия в порядке приоритета и выбираем первое подходящее
for (const action of availableActions) {
if (action.type === 'ability') {
if (action.requiresSuccessCheck) {
// Для способностей с шансом успеха, "бросаем кубик"
if (Math.random() < action.successRate) {
if (addToLogCallback) addToLogCallback(`${opponentState.name} решает использовать "${action.ability.name}" (попытка успешна)...`, configToUse.LOG_TYPE_INFO);
return { actionType: action.type, ability: action.ability };
} else {
// Провал шанса, добавляем лог и ИИ переходит к следующему действию в списке (если есть)
if (addToLogCallback) addToLogCallback(`💨 ${opponentState.name} пытался использовать "${action.ability.name}", но шанс не сработал!`, configToUse.LOG_TYPE_INFO);
continue; // Пробуем следующее приоритетное действие
}
} else {
// Способность без проверки шанса (например, Похищение Света)
if (addToLogCallback) addToLogCallback(`${opponentState.name} решает использовать "${action.ability.name}"...`, configToUse.LOG_TYPE_INFO);
return { actionType: action.type, ability: action.ability };
}
} else if (action.type === 'attack') {
// Атака - если дошли до нее, значит, более приоритетные способности не были выбраны или провалили шанс
if (addToLogCallback) addToLogCallback(`🦶 ${opponentState.name} решает атаковать...`, configToUse.LOG_TYPE_INFO);
return { actionType: 'attack' };
}
}
// Фоллбэк, если по какой-то причине ни одно действие не было выбрано (не должно происходить, если атака всегда есть)
console.warn("[AI Logic] AI Balard failed to select any action after iterating. Defaulting to pass.");
if (addToLogCallback) addToLogCallback(`${opponentState.name} не смог выбрать подходящее действие. Пропускает ход.`, configToUse.LOG_TYPE_INFO);
return { actionType: 'pass', logMessage: { message: `${opponentState.name} пропускает ход.`, type: configToUse.LOG_TYPE_INFO } };
}
module.exports = {
decideAiAction
};