// /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 }; })();