// /public/js/ui.js
// Этот файл отвечает за обновление DOM на основе состояния игры,
// полученного от client.js (который, в свою очередь, получает его от сервера).
(function() {
// --- DOM Элементы ---
const uiElements = {
player: { // Панель для персонажа, которым управляет ЭТОТ клиент
panel: document.getElementById('player-panel'),
name: document.getElementById('player-name'),
avatar: document.getElementById('player-panel')?.querySelector('.player-avatar'),
hpFill: document.getElementById('player-hp-fill'), hpText: document.getElementById('player-hp-text'),
resourceFill: document.getElementById('player-resource-fill'), resourceText: document.getElementById('player-resource-text'),
status: document.getElementById('player-status'),
effectsContainer: document.getElementById('player-effects'),
buffsList: document.getElementById('player-effects')?.querySelector('.player-buffs'),
debuffsList: document.getElementById('player-effects')?.querySelector('.player-debuffs')
},
opponent: { // Панель для персонажа-противника ЭТОГО клиента
panel: document.getElementById('opponent-panel'),
name: document.getElementById('opponent-name'),
avatar: document.getElementById('opponent-panel')?.querySelector('.opponent-avatar'),
hpFill: document.getElementById('opponent-hp-fill'), hpText: document.getElementById('opponent-hp-text'),
resourceFill: document.getElementById('opponent-resource-fill'), resourceText: document.getElementById('opponent-resource-text'),
status: document.getElementById('opponent-status'),
effectsContainer: document.getElementById('opponent-effects'),
buffsList: document.getElementById('opponent-effects')?.querySelector('.opponent-buffs'),
debuffsList: document.getElementById('opponent-effects')?.querySelector('.opponent-debuffs')
},
controls: {
turnIndicator: document.getElementById('turn-indicator'),
buttonAttack: document.getElementById('button-attack'),
buttonBlock: document.getElementById('button-block'), // Защита пока не активна
abilitiesGrid: document.getElementById('abilities-grid'),
},
log: {
list: document.getElementById('log-list'),
},
gameOver: {
screen: document.getElementById('game-over-screen'),
message: document.getElementById('result-message'),
// restartButton: document.getElementById('restart-game-button'), // Старый ID, заменен
returnToMenuButton: document.getElementById('return-to-menu-button'), // Новый ID
modalContent: document.getElementById('game-over-screen')?.querySelector('.modal-content')
},
gameHeaderTitle: document.querySelector('.game-header h1'),
playerResourceTypeIcon: document.getElementById('player-resource-bar')?.closest('.stat-bar-container')?.querySelector('.bar-icon i'),
opponentResourceTypeIcon: document.getElementById('opponent-resource-bar')?.closest('.stat-bar-container')?.querySelector('.bar-icon i'),
playerResourceBarContainer: document.getElementById('player-resource-bar')?.closest('.stat-bar-container'),
opponentResourceBarContainer: document.getElementById('opponent-resource-bar')?.closest('.stat-bar-container'),
};
function addToLog(message, type = 'info') {
const logListElement = uiElements.log.list;
if (!logListElement) return;
const li = document.createElement('li');
li.textContent = message;
const config = window.GAME_CONFIG || {}; // Получаем конфиг из глобальной области
// Формируем класс для лога на основе типа
const logTypeClass = config[`LOG_TYPE_${type.toUpperCase()}`] ? `log-${config[`LOG_TYPE_${type.toUpperCase()}`]}` : `log-${type}`;
li.className = logTypeClass;
logListElement.appendChild(li);
// Прокрутка лога вниз
requestAnimationFrame(() => { logListElement.scrollTop = logListElement.scrollHeight; });
}
function updateFighterPanelUI(panelRole, fighterState, fighterBaseStats, isControlledByThisClient) {
const elements = uiElements[panelRole]; // 'player' или 'opponent'
const config = window.GAME_CONFIG || {};
if (!elements || !elements.hpFill || !elements.hpText || !elements.resourceFill || !elements.resourceText || !elements.status || !fighterState || !fighterBaseStats) {
// console.warn(`updateFighterPanelUI: Отсутствуют элементы UI, состояние бойца или базовые статы для панели ${panelRole}.`);
// Если панель должна быть видима, но нет данных, можно ее скрыть или показать плейсхолдер
if (elements && elements.panel && elements.panel.style.display !== 'none' && (!fighterState || !fighterBaseStats)) {
// console.warn(`updateFighterPanelUI: Нет данных для видимой панели ${panelRole}.`);
// elements.panel.style.opacity = '0.3'; // Пример: сделать полупрозрачной
}
return;
}
// Если панель была полупрозрачной (из-за отсутствия данных), а теперь данные есть, делаем ее полностью видимой
// if (elements.panel && elements.panel.style.opacity !== '1' && fighterState && fighterBaseStats) {
// elements.panel.style.opacity = '1';
// }
// Обновление имени и иконки персонажа
if (elements.name) {
let iconClass = 'fa-question'; // Иконка по умолчанию
let accentColor = 'var(--text-muted)'; // Цвет по умолчанию
const characterKey = fighterBaseStats.characterKey;
if (characterKey === 'elena') { iconClass = 'fa-hat-wizard icon-player'; accentColor = 'var(--accent-player)'; }
else if (characterKey === 'almagest') { iconClass = 'fa-staff-aesculapius icon-almagest'; accentColor = 'var(--accent-almagest)'; }
else if (characterKey === 'balard') { iconClass = 'fa-khanda icon-opponent'; accentColor = 'var(--accent-opponent)'; }
else { /* console.warn(`updateFighterPanelUI: Неизвестный characterKey "${characterKey}" для иконки/цвета имени.`); */ }
let nameHtml = ` ${fighterBaseStats.name || 'Неизвестно'}`;
if (isControlledByThisClient) nameHtml += " (Вы)";
elements.name.innerHTML = nameHtml;
elements.name.style.color = accentColor;
}
// Обновление аватара
if (elements.avatar && fighterBaseStats.avatarPath) elements.avatar.src = fighterBaseStats.avatarPath;
else if (elements.avatar) elements.avatar.src = 'images/default_avatar.png'; // Запасной аватар
// Обновление полос здоровья и ресурса
const maxHp = Math.max(1, fighterBaseStats.maxHp); // Избегаем деления на ноль
const maxRes = Math.max(1, fighterBaseStats.maxResource);
const currentHp = Math.max(0, fighterState.currentHp);
const currentRes = Math.max(0, fighterState.currentResource);
elements.hpFill.style.width = `${(currentHp / maxHp) * 100}%`;
elements.hpText.textContent = `${Math.round(currentHp)} / ${fighterBaseStats.maxHp}`;
elements.resourceFill.style.width = `${(currentRes / maxRes) * 100}%`;
elements.resourceText.textContent = `${Math.round(currentRes)} / ${fighterBaseStats.maxResource}`;
// Обновление типа ресурса и иконки (mana/stamina/dark-energy)
const resourceBarContainerToUpdate = (panelRole === 'player') ? uiElements.playerResourceBarContainer : uiElements.opponentResourceBarContainer;
const resourceIconElementToUpdate = (panelRole === 'player') ? uiElements.playerResourceTypeIcon : uiElements.opponentResourceTypeIcon;
if (resourceBarContainerToUpdate && resourceIconElementToUpdate) {
resourceBarContainerToUpdate.classList.remove('mana', 'stamina', 'dark-energy'); // Сначала удаляем все классы ресурсов
let resourceClass = 'mana'; let iconClass = 'fa-flask'; // Значения по умолчанию (для Елены)
if (fighterBaseStats.resourceName === 'Ярость') { resourceClass = 'stamina'; iconClass = 'fa-fire-alt'; }
else if (fighterBaseStats.resourceName === 'Темная Энергия') { resourceClass = 'dark-energy'; iconClass = 'fa-skull'; }
resourceBarContainerToUpdate.classList.add(resourceClass);
resourceIconElementToUpdate.className = `fas ${iconClass}`;
}
// Обновление статуса (Готов/Защищается)
const statusText = fighterState.isBlocking ? (config.STATUS_BLOCKING || 'Защищается') : (config.STATUS_READY || 'Готов(а)');
elements.status.textContent = statusText;
elements.status.classList.toggle(config.CSS_CLASS_BLOCKING || 'blocking', fighterState.isBlocking);
// Обновление подсветки и рамки панели
if (elements.panel) {
let glowColorVar = '--panel-glow-opponent';
let borderColorVar = 'var(--accent-opponent)'; // По умолчанию для оппонента
if (fighterBaseStats.characterKey === 'elena') { glowColorVar = '--panel-glow-player'; borderColorVar = 'var(--accent-player)'; }
else if (fighterBaseStats.characterKey === 'almagest') { glowColorVar = 'var(--panel-glow-opponent)'; borderColorVar = 'var(--accent-almagest)'; } // Для Альмагест используется ее цвет рамки
else if (fighterBaseStats.characterKey === 'balard') { glowColorVar = 'var(--panel-glow-opponent)'; borderColorVar = 'var(--accent-opponent)'; }
else { borderColorVar = 'var(--panel-border)'; } // Фоллбэк
elements.panel.style.borderColor = borderColorVar;
// Убедимся, что переменная glowColorVar существует, иначе тень может не примениться или вызвать ошибку
elements.panel.style.boxShadow = glowColorVar ? `0 0 15px ${glowColorVar}, inset 0 0 10px rgba(0, 0, 0, 0.3)` : `0 0 15px rgba(0,0,0,0.4), inset 0 0 10px rgba(0,0,0,0.3)`;
}
}
function generateEffectsHTML(effectsArray) {
const config = window.GAME_CONFIG || {};
if (!effectsArray || effectsArray.length === 0) return 'Нет';
return effectsArray.map(eff => {
let effectClasses = config.CSS_CLASS_EFFECT || 'effect';
const title = `${eff.name}${eff.description ? ` - ${eff.description}` : ''} (Осталось: ${eff.turnsLeft} х.)`;
const displayText = `${eff.name} (${eff.turnsLeft} х.)`;
if (eff.isFullSilence || eff.id.startsWith('playerSilencedOn_') || (eff.type === config.ACTION_TYPE_DISABLE && !eff.grantsBlock) ) {
effectClasses += ' effect-stun'; // Стан/безмолвие
} else if (eff.grantsBlock) {
effectClasses += ' effect-block'; // Эффект блока
} else if (eff.type === config.ACTION_TYPE_DEBUFF || (eff.power && eff.power < 0 && eff.type !== config.ACTION_TYPE_HEAL )) {
effectClasses += ' effect-debuff'; // Ослабления, DoT
} else { // ACTION_TYPE_BUFF или положительные эффекты (например, HoT)
effectClasses += ' effect-buff';
}
return `${displayText}`;
}).join(' ');
}
function updateEffectsUI(currentGameState) {
if (!currentGameState || !window.GAME_CONFIG) { return; }
const mySlotId = window.myPlayerId; // Технический ID слота этого клиента
if (!mySlotId) { return; }
const opponentSlotId = mySlotId === window.GAME_CONFIG.PLAYER_ID ? window.GAME_CONFIG.OPPONENT_ID : window.GAME_CONFIG.PLAYER_ID;
const myState = currentGameState[mySlotId]; // Состояние персонажа этого клиента
if (uiElements.player && uiElements.player.buffsList && uiElements.player.debuffsList && myState && myState.activeEffects) {
uiElements.player.buffsList.innerHTML = generateEffectsHTML(myState.activeEffects.filter(e => e.type === window.GAME_CONFIG.ACTION_TYPE_BUFF || e.grantsBlock || (e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
uiElements.player.debuffsList.innerHTML = generateEffectsHTML(myState.activeEffects.filter(e => e.type !== window.GAME_CONFIG.ACTION_TYPE_BUFF && !e.grantsBlock && !(e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
}
const opponentState = currentGameState[opponentSlotId]; // Состояние оппонента этого клиента
if (uiElements.opponent && uiElements.opponent.buffsList && uiElements.opponent.debuffsList && opponentState && opponentState.activeEffects) {
uiElements.opponent.buffsList.innerHTML = generateEffectsHTML(opponentState.activeEffects.filter(e => e.type === window.GAME_CONFIG.ACTION_TYPE_BUFF || e.grantsBlock || (e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
uiElements.opponent.debuffsList.innerHTML = generateEffectsHTML(opponentState.activeEffects.filter(e => e.type !== window.GAME_CONFIG.ACTION_TYPE_BUFF && !e.grantsBlock && !(e.type === window.GAME_CONFIG.ACTION_TYPE_HEAL && e.turnsLeft > 0) ));
}
}
function updateUI() {
const currentGameState = window.gameState; // Глобальное состояние игры
const gameDataGlobal = window.gameData; // Глобальные данные (статы, абилки) для этого клиента
const configGlobal = window.GAME_CONFIG; // Глобальный конфиг
const myActualPlayerId = window.myPlayerId; // Технический ID слота этого клиента
if (!currentGameState || !gameDataGlobal || !configGlobal || !myActualPlayerId) {
console.warn("updateUI: Отсутствуют глобальные gameState, gameData, GAME_CONFIG или myActualPlayerId.");
return;
}
if (!uiElements.player.panel || !uiElements.opponent.panel || !uiElements.controls.turnIndicator || !uiElements.controls.abilitiesGrid || !uiElements.log.list) {
console.warn("updateUI: Некоторые базовые uiElements не найдены.");
return;
}
// Определяем, чей сейчас ход по ID слота
const actorSlotWhoseTurnItIs = currentGameState.isPlayerTurn ? configGlobal.PLAYER_ID : configGlobal.OPPONENT_ID;
// Определяем ID слота оппонента для этого клиента
const opponentActualSlotId = myActualPlayerId === configGlobal.PLAYER_ID ? configGlobal.OPPONENT_ID : configGlobal.PLAYER_ID;
// Обновление панели "моего" персонажа
if (gameDataGlobal.playerBaseStats && currentGameState[myActualPlayerId]) {
if (uiElements.player.panel && uiElements.player.panel.style.opacity !== '1') uiElements.player.panel.style.opacity = '1';
updateFighterPanelUI('player', currentGameState[myActualPlayerId], gameDataGlobal.playerBaseStats, true);
} else {
if (uiElements.player.panel) uiElements.player.panel.style.opacity = '0.5';
}
// Обновление панели "моего оппонента"
if (gameDataGlobal.opponentBaseStats && currentGameState[opponentActualSlotId]) {
if (uiElements.opponent.panel && uiElements.opponent.panel.style.opacity !== '1' && currentGameState.isGameOver === false ) {
// Если панель оппонента была "затемнена" и игра не окончена, восстанавливаем видимость
uiElements.opponent.panel.style.opacity = '1';
}
// Перед обновлением, если игра НЕ окончена, а панель "тает", отменяем это
if (uiElements.opponent.panel && uiElements.opponent.panel.classList.contains('dissolving') && currentGameState.isGameOver === false) {
console.warn("[UI UPDATE DEBUG] Opponent panel has .dissolving but game is NOT over. Forcing visible.");
const panel = uiElements.opponent.panel;
panel.classList.remove('dissolving');
const originalTransition = panel.style.transition; panel.style.transition = 'none';
panel.style.opacity = '1'; panel.style.transform = 'scale(1) translateY(0)';
requestAnimationFrame(() => { panel.style.transition = originalTransition || ''; });
}
updateFighterPanelUI('opponent', currentGameState[opponentActualSlotId], gameDataGlobal.opponentBaseStats, false);
} else {
if (uiElements.opponent.panel) uiElements.opponent.panel.style.opacity = '0.5';
}
updateEffectsUI(currentGameState);
// Обновление заголовка игры (Имя1 vs Имя2)
if (uiElements.gameHeaderTitle && gameDataGlobal.playerBaseStats && gameDataGlobal.opponentBaseStats) {
const myName = gameDataGlobal.playerBaseStats.name; // Имя моего персонажа
const opponentName = gameDataGlobal.opponentBaseStats.name; // Имя моего оппонента
const myKey = gameDataGlobal.playerBaseStats.characterKey;
const opponentKey = gameDataGlobal.opponentBaseStats.characterKey;
let myClass = 'title-player';
if (myKey === 'elena') myClass = 'title-enchantress'; else if (myKey === 'almagest') myClass = 'title-sorceress';
let opponentClass = 'title-opponent';
if (opponentKey === 'elena') opponentClass = 'title-enchantress'; else if (opponentKey === 'almagest') opponentClass = 'title-sorceress'; else if (opponentKey === 'balard') opponentClass = 'title-knight';
uiElements.gameHeaderTitle.innerHTML = `${myName} ${opponentName}`;
}
// Обновление индикатора хода
if (uiElements.controls.turnIndicator) {
// Имя того, чей ход, берем из gameState по ID слота
const currentTurnActorState = currentGameState[actorSlotWhoseTurnItIs]; // 'player' или 'opponent'
const currentTurnName = currentTurnActorState?.name || 'Неизвестно';
uiElements.controls.turnIndicator.textContent = `Ход: ${currentTurnName}`;
const currentTurnCharacterKey = currentTurnActorState?.characterKey;
let turnColor = 'var(--turn-color)';
if (currentTurnCharacterKey === 'elena') turnColor = 'var(--accent-player)';
else if (currentTurnCharacterKey === 'almagest') turnColor = 'var(--accent-almagest)';
else if (currentTurnCharacterKey === 'balard') turnColor = 'var(--accent-opponent)';
uiElements.controls.turnIndicator.style.color = turnColor;
}
// Управление активностью кнопок
const canThisClientAct = actorSlotWhoseTurnItIs === myActualPlayerId; // Может ли ЭТОТ клиент ходить
const isGameActive = !currentGameState.isGameOver;
// Кнопка атаки
if (uiElements.controls.buttonAttack) {
uiElements.controls.buttonAttack.disabled = !(canThisClientAct && isGameActive);
const myCharKey = gameDataGlobal.playerBaseStats?.characterKey;
const myStateForAttackBuff = currentGameState[myActualPlayerId]; // Состояние моего персонажа
let attackBuffId = null;
if (myCharKey === 'elena') attackBuffId = configGlobal.ABILITY_ID_NATURE_STRENGTH;
else if (myCharKey === 'almagest') attackBuffId = configGlobal.ABILITY_ID_ALMAGEST_BUFF_ATTACK;
if (attackBuffId && myStateForAttackBuff && myStateForAttackBuff.activeEffects) {
const isAttackBuffReady = myStateForAttackBuff.activeEffects.some(eff => eff.id === attackBuffId && !eff.justCast);
uiElements.controls.buttonAttack.classList.toggle(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed', isAttackBuffReady && canThisClientAct && isGameActive);
} else {
uiElements.controls.buttonAttack.classList.remove(configGlobal.CSS_CLASS_ATTACK_BUFFED || 'attack-buffed');
}
}
if (uiElements.controls.buttonBlock) uiElements.controls.buttonBlock.disabled = true; // Пока не используется
// Кнопки способностей
const actingPlayerState = currentGameState[myActualPlayerId]; // Состояние моего персонажа
const actingPlayerAbilities = gameDataGlobal.playerAbilities; // Способности моего персонажа
const actingPlayerResourceName = gameDataGlobal.playerBaseStats?.resourceName;
uiElements.controls.abilitiesGrid?.querySelectorAll(`.${configGlobal.CSS_CLASS_ABILITY_BUTTON || 'ability-button'}`).forEach(button => {
if (!(button instanceof HTMLButtonElement) || !actingPlayerState || !actingPlayerAbilities || !actingPlayerResourceName) {
if(button instanceof HTMLButtonElement) button.disabled = true;
return;
}
const abilityId = button.dataset.abilityId;
const ability = actingPlayerAbilities.find(ab => ab.id === abilityId);
if (!ability) { button.disabled = true; return; }
const hasEnoughResource = actingPlayerState.currentResource >= ability.cost;
const isBuffAlreadyActive = ability.type === configGlobal.ACTION_TYPE_BUFF && actingPlayerState.activeEffects?.some(eff => eff.id === ability.id);
const isOnCooldown = (actingPlayerState.abilityCooldowns?.[ability.id] || 0) > 0;
const isGenerallySilenced = actingPlayerState.activeEffects?.some(eff => eff.isFullSilence && eff.turnsLeft > 0);
const specificSilenceEffect = actingPlayerState.disabledAbilities?.find(dis => dis.abilityId === abilityId && dis.turnsLeft > 0);
const isSpecificallySilenced = !!specificSilenceEffect;
const isSilenced = isGenerallySilenced || isSpecificallySilenced;
const silenceTurnsLeft = isGenerallySilenced
? (actingPlayerState.activeEffects.find(eff => eff.isFullSilence)?.turnsLeft || 0)
: (specificSilenceEffect?.turnsLeft || 0);
let isDisabledByDebuffOnTarget = false;
const opponentStateForDebuffCheck = currentGameState[opponentActualSlotId]; // Состояние моего оппонента
if (opponentStateForDebuffCheck && opponentStateForDebuffCheck.activeEffects &&
(ability.id === configGlobal.ABILITY_ID_SEAL_OF_WEAKNESS || ability.id === configGlobal.ABILITY_ID_ALMAGEST_DEBUFF)) {
const effectIdForDebuff = 'effect_' + ability.id;
isDisabledByDebuffOnTarget = opponentStateForDebuffCheck.activeEffects.some(e => e.id === effectIdForDebuff);
}
button.disabled = !(canThisClientAct && isGameActive) || !hasEnoughResource || isBuffAlreadyActive || isSilenced || isOnCooldown || isDisabledByDebuffOnTarget;
button.classList.remove(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced', configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN||'is-on-cooldown');
const cooldownDisplay = button.querySelector('.ability-cooldown-display');
if (isOnCooldown) {
button.classList.add(configGlobal.CSS_CLASS_ABILITY_ON_COOLDOWN||'is-on-cooldown');
if (cooldownDisplay) { cooldownDisplay.textContent = `КД: ${actingPlayerState.abilityCooldowns[ability.id]}`; cooldownDisplay.style.display = 'block'; }
} else if (isSilenced) {
button.classList.add(configGlobal.CSS_CLASS_ABILITY_SILENCED||'is-silenced');
if (cooldownDisplay) { cooldownDisplay.textContent = `Безм: ${silenceTurnsLeft}`; cooldownDisplay.style.display = 'block'; }
} else {
if (cooldownDisplay) cooldownDisplay.style.display = 'none';
button.classList.toggle(configGlobal.CSS_CLASS_NOT_ENOUGH_RESOURCE||'not-enough-resource', !hasEnoughResource && !isBuffAlreadyActive && !isDisabledByDebuffOnTarget);
button.classList.toggle(configGlobal.CSS_CLASS_BUFF_IS_ACTIVE||'buff-is-active', isBuffAlreadyActive && !isDisabledByDebuffOnTarget);
}
// Обновление title (всплывающей подсказки)
let titleText = `${ability.name} (${ability.cost} ${actingPlayerResourceName})`;
let descriptionText = ability.description;
if (typeof ability.descriptionFunction === 'function') {
descriptionText = ability.descriptionFunction(configGlobal, gameDataGlobal.opponentBaseStats); // Для описания используем статы оппонента этого клиента
}
if (descriptionText) titleText += ` - ${descriptionText}`;
let abilityBaseCooldown = ability.cooldown;
if (ability.internalCooldownFromConfig && configGlobal[ability.internalCooldownFromConfig]) abilityBaseCooldown = configGlobal[ability.internalCooldownFromConfig];
else if (ability.internalCooldownValue) abilityBaseCooldown = ability.internalCooldownValue;
if (abilityBaseCooldown) titleText += ` (КД: ${abilityBaseCooldown} х.)`;
if (isOnCooldown) titleText = `${ability.name} - На перезарядке! Осталось: ${actingPlayerState.abilityCooldowns[ability.id]} х.`;
else if (isSilenced) titleText = `Безмолвие! Осталось: ${silenceTurnsLeft} х.`;
else if (isBuffAlreadyActive) {
const activeEffect = actingPlayerState.activeEffects?.find(eff => eff.id === abilityId);
titleText = `Эффект "${ability.name}" уже активен${activeEffect ? ` (${activeEffect.turnsLeft} х.)` : ''}`;
} else if (isDisabledByDebuffOnTarget && opponentStateForDebuffCheck) {
const activeDebuff = opponentStateForDebuffCheck.activeEffects?.find(e => e.id === 'effect_' + ability.id);
titleText = `Эффект "${ability.name}" уже наложен на ${opponentStateForDebuffCheck.name}${activeDebuff ? ` (${activeDebuff.turnsLeft} х.)` : ''}`;
}
button.setAttribute('title', titleText);
});
}
function showGameOver(playerWon, reason = "", opponentCharacterKeyFromClient = null) {
const config = window.GAME_CONFIG || {};
// Используем gameData, сохраненное в client.js, так как оно отражает перспективу этого клиента
const clientSpecificGameData = window.gameData;
const currentActualGameState = window.gameState; // Самое актуальное состояние игры
const gameOverScreenElement = uiElements.gameOver.screen;
console.log(`[UI.JS DEBUG] showGameOver CALLED. PlayerWon: ${playerWon}, Reason: ${reason}`);
console.log(`[UI.JS DEBUG] Current window.gameState.isGameOver: ${currentActualGameState?.isGameOver}`);
console.log(`[UI.JS DEBUG] Opponent Character Key (from client via param): ${opponentCharacterKeyFromClient}`);
console.log(`[UI.JS DEBUG] My Character Name (from window.gameData): ${clientSpecificGameData?.playerBaseStats?.name}`);
console.log(`[UI.JS DEBUG] Opponent Character Name (from window.gameData): ${clientSpecificGameData?.opponentBaseStats?.name}`);
if (!gameOverScreenElement) {
console.warn("[UI.JS DEBUG] showGameOver: gameOverScreenElement not found.");
return;
}
const resultMsgElement = uiElements.gameOver.message;
// Имена для сообщения берем из clientSpecificGameData, т.к. они уже "перевернуты" для каждого клиента
const myNameForResult = clientSpecificGameData?.playerBaseStats?.name || "Игрок";
const opponentNameForResult = clientSpecificGameData?.opponentBaseStats?.name || "Противник";
if (resultMsgElement) {
let winText = `Победа! ${myNameForResult} празднует!`;
let loseText = `Поражение! ${opponentNameForResult} оказался(лась) сильнее!`;
if (reason === 'opponent_disconnected') {
winText = `${opponentNameForResult} покинул(а) игру. Победа присуждается вам!`;
// Если оппонент отключился, а мы проиграли (технически такое возможно, если сервер так решит)
// То текст поражения можно оставить стандартным или специфичным.
// Пока оставим стандартный, если playerWon = false и reason = opponent_disconnected.
}
resultMsgElement.textContent = playerWon ? winText : loseText;
resultMsgElement.style.color = playerWon ? 'var(--heal-color)' : 'var(--damage-color)';
}
const opponentPanelElement = uiElements.opponent.panel;
if (opponentPanelElement) {
opponentPanelElement.classList.remove('dissolving');
console.log(`[UI.JS DEBUG] Opponent panel classList after initial remove .dissolving: ${opponentPanelElement.className}`);
// Используем opponentCharacterKeyFromClient, так как это ключ реального персонажа оппонента
const keyForDissolveEffect = opponentCharacterKeyFromClient;
if (currentActualGameState && currentActualGameState.isGameOver === true && playerWon && reason !== 'opponent_disconnected') {
if (keyForDissolveEffect === 'balard' || keyForDissolveEffect === 'almagest') {
console.log(`[UI.JS DEBUG] ADDING .dissolving to opponent panel. Conditions: isGameOver=${currentActualGameState.isGameOver}, playerWon=${playerWon}, opponentKeyForEffect=${keyForDissolveEffect}`);
opponentPanelElement.classList.add('dissolving');
} else {
console.log(`[UI.JS DEBUG] NOT adding .dissolving (opponent key mismatch for dissolve effect). Key for effect: ${keyForDissolveEffect}`);
}
} else {
console.log(`[UI.JS DEBUG] NOT adding .dissolving. Conditions NOT met: isGameOver=${currentActualGameState?.isGameOver}, playerWon=${playerWon}, reason=${reason}`);
if (!currentActualGameState || currentActualGameState.isGameOver === false) {
console.log(`[UI.JS DEBUG] Ensuring opponent panel is visible because game is not 'isGameOver=true' or gameState missing.`);
const originalTransition = opponentPanelElement.style.transition;
opponentPanelElement.style.transition = 'none';
opponentPanelElement.style.opacity = '1';
opponentPanelElement.style.transform = 'scale(1) translateY(0)';
requestAnimationFrame(() => {
opponentPanelElement.style.transition = originalTransition || '';
});
}
}
console.log(`[UI.JS DEBUG] Opponent panel classList FINAL in showGameOver: ${opponentPanelElement.className}`);
}
setTimeout(() => {
if (window.gameState && window.gameState.isGameOver === true) { // Перепроверяем перед показом
console.log(`[UI.JS DEBUG] Showing gameOverScreen modal (isGameOver is true).`);
gameOverScreenElement.classList.remove(config.CSS_CLASS_HIDDEN || 'hidden');
requestAnimationFrame(() => {
gameOverScreenElement.style.opacity = '0';
setTimeout(() => {
gameOverScreenElement.style.opacity = '1';
if (uiElements.gameOver.modalContent) {
uiElements.gameOver.modalContent.style.transform = 'scale(1) translateY(0)';
uiElements.gameOver.modalContent.style.opacity = '1';
}
}, config.MODAL_TRANSITION_DELAY || 10);
});
} else {
console.log(`[UI.JS DEBUG] NOT showing gameOverScreen modal (isGameOver is now false or gameState missing).`);
}
}, config.DELAY_BEFORE_VICTORY_MODAL || 1500);
}
window.gameUI = { uiElements, addToLog, updateUI, showGameOver };
})();