This commit is contained in:
2026-01-26 23:33:33 +03:00
parent 937c39b73f
commit a597cb9de7
5 changed files with 732 additions and 57 deletions

View File

@ -14,6 +14,7 @@
let spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
let heroAbilityMode = { active: false };
let stealCardsMode = { active: false, handIndex: -1, targetPlayerIndex: null, targetDeck: [], selectedIndices: [] };
let battlecryTargetMode = { active: false, battlecryId: '', cardId: '', minionId: '', targetPlayerIndex: null, targetBoardIndex: null };
const seenMinions = new Set();
let lastHandLength = 0;
let prevGameState = null;
@ -235,8 +236,28 @@
showStealCardsModal(gameState, data);
});
socket.on('battlecryTargetRequest', (data) => {
if (data.battlecryId === 'return_hand_enemy') {
// Показываем модальное окно для выбора противника и его миньона
showBattlecryTargetModal(gameState, data);
}
});
return socket;
}
function showBattlecryTargetModal(state, data) {
battlecryTargetMode = { active: true, battlecryId: data.battlecryId, cardId: data.cardId, minionId: data.minionId };
// Активируем режим выбора цели
spellMode = { active: true, handIndex: -1, cardId: data.cardId, spellTarget: 'enemy_minion', battlecryMode: true, minionId: data.minionId };
$('spell-mode')?.classList.remove('hidden');
const spellModeText = $('spell-mode')?.querySelector('p');
if (spellModeText) {
spellModeText.textContent = 'Выберите миньона противника для возврата в руку';
}
renderGame(state);
}
function renderGameEnded(state) {
const badge = $('turn-badge');
@ -274,6 +295,30 @@
modal.classList.remove('hidden');
}
function showTurnNotification(state) {
const notification = $('turn-notification');
const notificationText = $('turn-notification-text');
if (!notification || !notificationText) return;
const currentPlayer = state.players[state.currentPlayerIndex];
const isYourTurn = state.currentPlayerIndex === state.yourIndex;
const playerName = currentPlayer?.name || `Игрок ${state.currentPlayerIndex + 1}`;
if (isYourTurn) {
notificationText.textContent = 'ВАШ ХОД';
notificationText.parentElement.classList.add('your-turn');
} else {
notificationText.textContent = `ХОД: ${playerName.toUpperCase()}`;
notificationText.parentElement.classList.remove('your-turn');
}
// Показываем нотификацию на 2 секунды
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 2000);
}
function renderBoards(state) {
const you = state.players[yourIndex];
if (!you) return;
@ -284,17 +329,18 @@
.map((p, i) => {
if (i === yourIndex) return '';
const isCurrent = state.currentPlayerIndex === i;
const isDead = p.health <= 0 || p.isDead;
const name = p.name || 'Игрок ' + (i + 1);
const minions = (p.board || []).map((m, j) => renderBoardMinion(m, i, j, state, true, false));
const heroBar = renderHeroTarget(i, state);
const heroDrop = renderHeroDropZone(i, state);
const canSteal = state.currentPlayerIndex === state.yourIndex && spellMode.active && spellMode.cardId && cardDb[spellMode.cardId]?.spellEffect === 'steal_cards';
return `
<div class="opponent-block ${isCurrent ? 'current-turn' : ''} ${canSteal ? 'steal-target' : ''}" data-opponent-index="${i}" data-player-index="${i}">
<div class="opponent-name">${escapeHtml(name)}</div>
<div class="opponent-stats">
<span>❤ ${p.health}</span>
<span>🔵 ${p.mana}/${p.maxMana}</span>
<div class="opponent-block ${isCurrent ? 'current-turn' : ''} ${canSteal ? 'steal-target' : ''} ${isDead ? 'defeated' : ''}" data-opponent-index="${i}" data-player-index="${i}">
<div class="opponent-name ${isDead ? 'defeated-name' : ''}">${escapeHtml(name)}${isDead ? ' <span class="defeated-badge">✝</span>' : ''}</div>
<div class="opponent-stats ${isDead ? 'defeated-stats' : ''}">
<span class="opponent-health">❤ ${p.health ?? 30}</span>
<span>🔵 ${p.mana ?? 0}/${p.maxMana ?? 0}</span>
${p.deck && p.deck.length > 0 ? `<span>📚 ${p.deck.length}</span>` : ''}
</div>
<div class="opponent-board">${heroDrop}${heroBar}${minions.join('')}</div>
@ -612,9 +658,19 @@
const you = state.players[yourIndex];
if (!you) return;
$('your-mana').textContent = you.mana;
$('your-max-mana').textContent = you.maxMana;
$('your-health').textContent = you.health;
$('your-mana').textContent = you.mana ?? 0;
$('your-max-mana').textContent = you.maxMana ?? 0;
const healthEl = $('your-health');
if (healthEl) {
const newHealth = you.health ?? 30;
const oldHealth = parseInt(healthEl.textContent) || 30;
healthEl.textContent = newHealth;
// Добавляем визуальный эффект при изменении HP
if (newHealth !== oldHealth) {
healthEl.classList.add('health-changed');
setTimeout(() => healthEl.classList.remove('health-changed'), 500);
}
}
$('your-deck').textContent = state.yourDeckCount ?? you.deck?.length ?? 0;
const lastLog = state.log && state.log.length ? state.log[state.log.length - 1] : null;
const logEl = $('game-log');
@ -660,6 +716,9 @@
badge.classList.toggle('your-turn', isYourTurn);
badge.textContent = isYourTurn ? 'ВАШ ХОД' : 'Ход ' + (state.turn || 1);
}
// Показываем нотификацию о том, кто ходит
showTurnNotification(state);
var skipBoardUpdate = false;
if (lastLog?.type === 'attack' && lastLog.attackerMinionId && lastLog.targetMinionId && prevGameState && runCombatAnimation(state)) {
@ -810,11 +869,20 @@
const canAttack = !isOpponent && m.canAttack && isYourTurn && (!canAttackTwice || attacksUsed < 2);
const attackable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && attackMode.attackerBoard === boardIndex;
const targetable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && playerIndex !== state.yourIndex;
// Учитываем бонусы синергий
const synergyAttack = m.synergyAttackBonus || 0;
const synergyHealth = m.synergyHealthBonus || 0;
const displayAttack = (m.attack || 0) + synergyAttack;
const displayHealth = (m.health || 0) + synergyHealth;
const hasSynergy = synergyAttack > 0 || synergyHealth > 0;
const cls = ['card-wrap'];
if (canAttack && !attackMode.active) cls.push('attackable');
if (attackable) cls.push('attackable');
if (targetable) cls.push('targetable');
if (isOpponent && isYourTurn) cls.push('drop-target');
if (hasSynergy) cls.push('has-synergy');
var spellTarget = false;
if (spellMode.active && spellMode.spellTarget) {
var st = spellMode.spellTarget;
@ -841,8 +909,8 @@
+ '<div class="card-name">' + escapeHtml(name) + '</div>'
+ textHtml + abilHtml
+ '<div class="card-stats">'
+ '<div class="card-atk-wrap"><span class="card-stat-label">Атака</span><span class="atk">' + m.attack + '</span></div>'
+ '<div class="card-hp-wrap"><span class="card-stat-label">Здоровье</span><span class="hp">' + m.health + '</span></div>'
+ '<div class="card-atk-wrap"><span class="card-stat-label">Атака</span><span class="atk">' + displayAttack + (synergyAttack > 0 ? '<span class="synergy-bonus">+' + synergyAttack + '</span>' : '') + '</span></div>'
+ '<div class="card-hp-wrap"><span class="card-stat-label">Здоровье</span><span class="hp">' + displayHealth + (synergyHealth > 0 ? '<span class="synergy-bonus">+' + synergyHealth + '</span>' : '') + '</span></div>'
+ '</div>'
+ '<div class="card-info-row"><span></span><button type="button" class="card-btn-info" data-card-id="' + escapeHtml(m.cardId) + '" title="Описание">i</button></div>'
+ '</div></div></div>';
@ -1248,11 +1316,31 @@
$all('.spell-target, .drop-target-hero.spell-target').forEach(function (el) {
el.onclick = function (e) {
if (!spellMode.active) return;
if (!spellMode.active && !battlecryTargetMode.active) return;
e.stopPropagation();
var tp = parseInt(el.dataset.dropPlayer ?? el.dataset.playerIndex, 10);
var tb = parseInt(el.dataset.dropBoard ?? el.dataset.boardIndex, 10);
// Обработка для battlecry (Ezra Bridger)
if (battlecryTargetMode.active && battlecryTargetMode.battlecryId === 'return_hand_enemy') {
if (tp !== state.yourIndex && tp >= 0 && tb >= 0) {
const targetPlayer = state.players[tp];
if (targetPlayer && targetPlayer.board && targetPlayer.board[tb]) {
socket.emit('battlecryTarget', {
battlecryId: 'return_hand_enemy',
targetPlayerIndex: tp,
targetBoardIndex: tb
});
battlecryTargetMode = { active: false, battlecryId: '', cardId: '', minionId: '', targetPlayerIndex: null, targetBoardIndex: null };
spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
$('spell-mode')?.classList.add('hidden');
renderGame(state);
return;
}
}
return;
}
// Специальная обработка для Грабежа - открываем модальное окно
const spellCard = cardDb[spellMode.cardId];
if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') {