123
This commit is contained in:
110
public/game.js
110
public/game.js
@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user