133 lines
10 KiB
JavaScript
133 lines
10 KiB
JavaScript
// /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
|
||
}; |