This commit is contained in:
2026-01-27 01:38:53 +03:00
parent 1c231fe28d
commit a4479916f9
4 changed files with 682 additions and 458 deletions

View File

@ -179,6 +179,20 @@
if (data.warn && typeof window.Sounds !== 'undefined') window.Sounds.timerWarning();
if (data.ended && typeof window.Sounds !== 'undefined') window.Sounds.timerEnd();
});
socket.on('chatMessage', (data) => {
const chatMessagesEl = $('chat-messages');
if (chatMessagesEl && data) {
const time = new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
const messageEl = document.createElement('div');
messageEl.className = 'chat-message user';
messageEl.innerHTML = '<span class="chat-username">' + escapeHtml(data.playerName || 'Игрок') + ':</span> ' +
escapeHtml(data.message || '') +
' <span class="chat-time">' + escapeHtml(time) + '</span>';
chatMessagesEl.appendChild(messageEl);
chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;
}
});
socket.on('gameState', (state) => {
if (combatTimeout) {
clearTimeout(combatTimeout);
@ -192,6 +206,19 @@
}
prevGameState = gameState && state.phase !== 'ended' ? { ...gameState, players: gameState.players.map((p) => ({ ...p, board: (p.board || []).map((m) => ({ ...m })) })) } : null;
showScreen('game');
// Инициализация чата при первом запуске игры
if (wasLobby) {
const chatMessagesEl = $('chat-messages');
if (chatMessagesEl) {
chatMessagesEl.innerHTML = '';
const welcomeMsg = document.createElement('div');
welcomeMsg.className = 'chat-message system';
welcomeMsg.textContent = 'Игра началась!';
chatMessagesEl.appendChild(welcomeMsg);
}
}
gameState = state;
cardDb = state.cardDb || {};
yourIndex = state.yourIndex ?? 0;
@ -673,30 +700,53 @@
}
$('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');
if (logEl) {
function logLine(entry) {
if (!entry) return '';
var who = function (i) { return state.players[i]?.name || ('Игрок ' + (i + 1)); };
if (entry.type === 'play') {
var c = cardDb[entry.cardId];
return who(entry.playerIndex) + ' разыграл ' + (c?.name || entry.cardId);
}
if (entry.type === 'turn') return 'Ход: ' + who(entry.to);
if (entry.type === 'skipTurn') return who(entry.playerIndex) + ' пропускает ход';
if (entry.type === 'playerDefeated') return who(entry.playerIndex) + ' выбыл из игры';
if (entry.type === 'attackHero') return 'Атака по герою!';
if (entry.type === 'attack') return 'Бой!';
if (entry.type === 'draw') return who(entry.playerIndex) + ' вытянул карту';
if (entry.type === 'fatigue') return who(entry.playerIndex) + ' усталость (' + (entry.damage || 0) + ' урона)';
if (entry.type === 'spell') return who(entry.fromPlayer) + ' заклинание';
if (entry.type === 'heroAbility') return who(entry.fromPlayer) + ' — геройская способность';
if (entry.type === 'battlecry' || entry.type === 'deathrattle') return '';
return '';
// Добавляем новые логи в чат
if (state.log && prevGameState) {
const chatMessagesEl = $('chat-messages');
if (chatMessagesEl) {
const prevLogLength = prevGameState.log ? prevGameState.log.length : 0;
const newLogs = state.log.slice(prevLogLength);
newLogs.forEach(entry => {
if (!entry) return;
const who = (i) => state.players[i]?.name || ('Игрок ' + (i + 1));
let message = '';
if (entry.type === 'play') {
const c = cardDb[entry.cardId];
message = who(entry.playerIndex) + ' разыграл ' + (c?.name || entry.cardId);
} else if (entry.type === 'turn') {
message = 'Ход: ' + who(entry.to);
} else if (entry.type === 'skipTurn') {
message = who(entry.playerIndex) + ' пропускает ход';
} else if (entry.type === 'playerDefeated') {
message = who(entry.playerIndex) + ' выбыл из игры';
} else if (entry.type === 'attackHero') {
message = 'Атака по герою!';
} else if (entry.type === 'attack') {
message = 'Бой!';
} else if (entry.type === 'draw') {
message = who(entry.playerIndex) + ' вытянул карту';
} else if (entry.type === 'fatigue') {
message = who(entry.playerIndex) + ' усталость (' + (entry.damage || 0) + ' урона)';
} else if (entry.type === 'spell') {
message = who(entry.fromPlayer) + ' заклинание';
} else if (entry.type === 'heroAbility') {
message = who(entry.fromPlayer) + ' — геройская способность';
} else if (entry.type === 'battlecry' || entry.type === 'deathrattle') {
return; // Пропускаем эти типы
}
if (message) {
const time = new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
const messageEl = document.createElement('div');
messageEl.className = 'chat-message system';
messageEl.innerHTML = '<span class="chat-time">' + escapeHtml(time) + '</span> ' + escapeHtml(message);
chatMessagesEl.appendChild(messageEl);
chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;
}
});
}
var lines = (state.log || []).slice(-10).map(logLine).filter(Boolean);
logEl.innerHTML = lines.map(function (s) { return '<div class="game-log-line">' + escapeHtml(s) + '</div>'; }).join('');
logEl.scrollTop = logEl.scrollHeight;
}
if (lastLog?.type === 'attackHero' && typeof window.Sounds !== 'undefined') window.Sounds.attack();
@ -747,10 +797,18 @@
var meta = cardDb[cid];
var cost = meta?.cost ?? 0;
var playable = false;
if (meta?.type === 'minion') playable = you.mana >= cost && (you.board?.length ?? 0) < 7;
else if (meta?.type === 'spell') playable = you.mana >= cost && !attackMode.active && !spellMode.active && !heroAbilityMode.active;
var notEnoughMana = false;
if (isYourTurn) {
if (meta?.type === 'minion') {
playable = you.mana >= cost && (you.board?.length ?? 0) < 7;
notEnoughMana = you.mana < cost;
} else if (meta?.type === 'spell') {
playable = you.mana >= cost && !attackMode.active && !spellMode.active && !heroAbilityMode.active;
notEnoughMana = you.mana < cost;
}
}
var drawAnim = justDrew && i === hand.length - 1;
return renderHandCard(cid, i, hand.length, playable, drawAnim);
return renderHandCard(cid, i, hand.length, playable, drawAnim, notEnoughMana);
})
.join('');
}
@ -789,7 +847,7 @@
return '<div class="card-abilities">' + tags.join(' ') + '</div>';
}
function renderHandCard(cardId, handIndex, handSize, playable, drawFromDeck) {
function renderHandCard(cardId, handIndex, handSize, playable, drawFromDeck, notEnoughMana) {
var meta = cardDb[cardId];
if (!meta) return '';
var name = meta.name || cardId;
@ -808,7 +866,8 @@
var textHtml = meta.text ? '<div class="card-text">' + escapeHtml(meta.text) + '</div>' : '';
var abilHtml = abilityTags(meta);
var fictionalClass = meta.fictional ? ' fictional-card' : '';
return '<div class="card-wrap in-hand ' + (playable ? 'playable' : '') + ' ' + (drawFromDeck ? 'draw-from-deck' : '') + fictionalClass + '" data-hand-index="' + handIndex + '" data-card-id="' + cardId + '" style="--hand-rotate: ' + rotate + 'deg" title="' + escapeHtml(name) + (meta.text ? ': ' + meta.text : '') + (meta.fictional ? ' [ВЫМЫШЛЕННЫЙ]' : '') + '">'
var notEnoughManaClass = notEnoughMana ? ' not-enough-mana' : '';
return '<div class="card-wrap in-hand ' + (playable ? 'playable' : '') + ' ' + (drawFromDeck ? 'draw-from-deck' : '') + fictionalClass + notEnoughManaClass + '" data-hand-index="' + handIndex + '" data-card-id="' + cardId + '" style="--hand-rotate: ' + rotate + 'deg" title="' + escapeHtml(name) + (meta.text ? ': ' + meta.text : '') + (meta.fictional ? ' [ВЫМЫШЛЕННЫЙ]' : '') + (notEnoughMana ? ' (Недостаточно маны)' : '') + '">'
+ '<div class="card faction-' + faction + fictionalClass + '">'
+ '<div class="card-art">' + art + '</div>'
+ '<div class="card-info">'
@ -978,6 +1037,54 @@
const isYourTurn = state.currentPlayerIndex === state.yourIndex;
const sidebar = $('forge-sidebar');
const isForgeOpen = sidebar && !sidebar.classList.contains('hidden');
// Chat handlers
const chatContainer = $('chat-container');
const chatToggle = $('btn-chat-toggle');
const chatHeader = $('chat-header');
const chatInput = $('chat-input');
const chatSendBtn = $('btn-chat-send');
if (chatToggle) {
chatToggle.onclick = function() {
if (chatContainer) {
chatContainer.classList.toggle('collapsed');
chatToggle.textContent = chatContainer.classList.contains('collapsed') ? '▲' : '▼';
}
};
}
if (chatHeader) {
chatHeader.onclick = function() {
if (chatContainer) {
chatContainer.classList.toggle('collapsed');
if (chatToggle) {
chatToggle.textContent = chatContainer.classList.contains('collapsed') ? '▲' : '▼';
}
}
};
}
function sendChatMessage() {
if (!chatInput || !socket) return;
const message = chatInput.value.trim();
if (message.length === 0) return;
socket.emit('chatMessage', { message: message });
chatInput.value = '';
}
if (chatSendBtn) {
chatSendBtn.onclick = sendChatMessage;
}
if (chatInput) {
chatInput.onkeypress = function(e) {
if (e.key === 'Enter') {
sendChatMessage();
}
};
}
$all('.card-wrap.in-hand').forEach((wrap) => {
wrap.classList.toggle('disabled', !isYourTurn);
@ -1549,8 +1656,11 @@
const sidebar = $('forge-sidebar');
if (sidebar) {
sidebar.classList.remove('hidden');
forgeSelected = []; // Сбрасываем выбор при открытии
renderForgeHand(state);
renderForgeDeck(state);
setupForgeDragAndDrop(state);
renderForgeSelected();
updateForgeCraftButton();
}
};
} else if (forgeBtn) {
@ -1728,9 +1838,12 @@
yourBoardEl.ondrop = null;
}
// Настраиваем drag-and-drop для кузницы, если она открыта
// Обновляем кузницу, если она открыта
if (isForgeOpen) {
setTimeout(() => setupForgeDragAndDrop(state), 100);
renderForgeHand(state);
renderForgeDeck(state);
renderForgeSelected();
updateForgeCraftButton();
}
}
@ -2050,10 +2163,16 @@
sidebar.classList.add('hidden');
forgeSelected = [];
renderForgeSelected();
// Обновляем отображение руки и колоды
if (gameState) {
renderHand(gameState);
}
}
});
$('btn-forge-clear')?.addEventListener('click', () => {
forgeSelected = [];
renderForgeSelected();
updateForgeCraftButton();
if (gameState) {
renderForgeHand(gameState);
renderForgeDeck(gameState);
}
});
@ -2211,20 +2330,24 @@
function renderForgeSelected() {
const selectedEl = $('forge-selected');
const countEl = $('forge-selected-count');
if (!selectedEl) return;
if (countEl) {
countEl.textContent = forgeSelected.length;
}
if (forgeSelected.length === 0) {
selectedEl.innerHTML = '<div class="forge-selected-empty">Перетащите карты сюда</div>';
selectedEl.classList.remove('drag-over');
selectedEl.innerHTML = '<div class="forge-selected-empty">Кликните на карты чтобы выбрать</div>';
} else {
selectedEl.innerHTML = forgeSelected.map((cardId, idx) => {
const meta = cardDb[cardId];
if (!meta) return '';
return `<div class="card-wrap forge-selected-card" data-card-id="${cardId}" data-forge-index="${idx}" style="width: 80px; height: 112px; position: relative;">
return `<div class="card-wrap forge-selected-card" data-card-id="${cardId}" data-forge-index="${idx}" style="width: 90px; height: 126px; position: relative;">
<div class="card faction-${meta.faction || 'neutral'}">
<div class="card-art">${getCardArt(meta)}</div>
<div class="card-info">
<div class="card-name" style="font-size: 0.6rem;">${escapeHtml(meta.name)}</div>
<div class="card-name" style="font-size: 0.7rem;">${escapeHtml(meta.name)}</div>
${meta.type === 'minion' ? `<div class="card-stats"><span class="atk">${meta.attack || 0}</span><span class="hp">${meta.health || 0}</span></div>` : ''}
</div>
</div>
@ -2236,17 +2359,7 @@
card.onclick = function(e) {
e.stopPropagation();
const cardId = card.dataset.cardId;
const idx = forgeSelected.indexOf(cardId);
if (idx >= 0) {
forgeSelected.splice(idx, 1);
renderForgeSelected();
updateForgeCraftButton();
// Обновляем отображение в источнике
if (gameState) {
renderForgeDeck(gameState);
renderHand(gameState);
}
}
removeCardFromForge(cardId);
};
});
}
@ -2256,13 +2369,18 @@
const craftBtn = $('btn-forge-craft');
const you = gameState?.players?.[gameState?.yourIndex];
if (craftBtn && you) {
craftBtn.disabled = forgeSelected.length < 2 || forgeSelected.length > 3 || (you.mana || 0) < 2;
const canCraft = forgeSelected.length >= 2 && forgeSelected.length <= 3 && (you.mana || 0) >= 2;
craftBtn.disabled = !canCraft;
}
}
function addCardToForge(cardId, source) {
if (!cardId) return;
if (forgeSelected.includes(cardId)) return; // Уже добавлена
if (forgeSelected.includes(cardId)) {
// Если карта уже выбрана, убираем её
removeCardFromForge(cardId);
return;
}
if (forgeSelected.length >= 3) {
// Показываем сообщение, что максимум 3 карты
const selectedEl = $('forge-selected');
@ -2270,7 +2388,7 @@
selectedEl.style.animation = 'shake 0.5s';
setTimeout(() => selectedEl.style.animation = '', 500);
}
return; // Максимум 3 карты
return;
}
const meta = cardDb[cardId];
@ -2280,272 +2398,59 @@
renderForgeSelected();
updateForgeCraftButton();
// Обновляем отображение в источнике (рука или колода)
// Обновляем отображение в источниках
if (gameState) {
if (source === 'hand') {
renderHand(gameState);
} else if (source === 'deck') {
renderForgeHand(gameState);
renderForgeDeck(gameState);
}
}
function removeCardFromForge(cardId) {
const idx = forgeSelected.indexOf(cardId);
if (idx >= 0) {
forgeSelected.splice(idx, 1);
renderForgeSelected();
updateForgeCraftButton();
// Обновляем отображение в источниках
if (gameState) {
renderForgeHand(gameState);
renderForgeDeck(gameState);
}
}
// Настраиваем drag-and-drop заново после добавления
setTimeout(() => setupForgeDragAndDrop(gameState), 100);
}
function setupForgeDragAndDrop(state) {
const selectedEl = $('forge-selected');
const sidebar = $('forge-sidebar');
if (!selectedEl || !sidebar || sidebar.classList.contains('hidden')) return;
function renderForgeHand(state) {
const you = state.players[state.yourIndex];
const handList = $('forge-hand-list');
if (!handList) return;
// Обработка drop в область выбранных карт
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
selectedEl.classList.add('drag-over');
};
const hand = you.hand || state.yourHand || [];
const handleDragLeave = (e) => {
if (!selectedEl.contains(e.relatedTarget)) {
selectedEl.classList.remove('drag-over');
}
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
selectedEl.classList.remove('drag-over');
let cardId = null;
let source = null;
// Проверяем данные из dataTransfer
const data = e.dataTransfer.getData('text/plain');
if (data) {
if (data.startsWith('hand:')) {
const handIndex = parseInt(data.split(':')[1], 10);
const you = state.players[state.yourIndex];
if (you.hand && you.hand[handIndex]) {
cardId = you.hand[handIndex];
source = 'hand';
}
} else {
cardId = data;
source = e.dataTransfer.getData('source') || 'deck';
}
}
if (cardId) {
addCardToForge(cardId, source);
}
};
// Удаляем старые обработчики, если есть
selectedEl.removeEventListener('dragover', handleDragOver);
selectedEl.removeEventListener('dragleave', handleDragLeave);
selectedEl.removeEventListener('drop', handleDrop);
// Добавляем новые обработчики
selectedEl.addEventListener('dragover', handleDragOver);
selectedEl.addEventListener('dragleave', handleDragLeave);
selectedEl.addEventListener('drop', handleDrop);
// Поддержка touch для мобильных устройств
if (isTouchDevice()) {
let touchStartCard = null;
let touchStartSource = null;
let touchStartElement = null;
// Обработка touch для карт в руке - упрощённая версия: просто тап добавляет карту
$all('#your-hand .card-wrap').forEach(cardWrap => {
const cardId = cardWrap.dataset.cardId;
const meta = cardDb[cardId];
if (cardId && meta && meta.type === 'minion') {
// Удаляем старые обработчики
cardWrap.ontouchstart = null;
cardWrap.ontouchend = null;
cardWrap.ontouchstart = function(e) {
if (sidebar && !sidebar.classList.contains('hidden')) {
touchStartCard = cardId;
touchStartSource = 'hand';
touchStartElement = cardWrap;
cardWrap._touchStartTime = Date.now();
cardWrap.classList.add('touch-active');
e.preventDefault();
}
};
cardWrap.ontouchend = function(e) {
if (touchStartCard === cardId && sidebar && !sidebar.classList.contains('hidden')) {
const touch = e.changedTouches[0];
const timeDiff = Date.now() - (touchStartElement?._touchStartTime || Date.now());
// Если это быстрый тап (менее 300мс), просто добавляем карту
if (timeDiff < 300) {
addCardToForge(cardId, 'hand');
} else {
// Иначе проверяем, куда перетащили
const dropEl = document.elementFromPoint(touch.clientX, touch.clientY);
if (dropEl && (dropEl === selectedEl || selectedEl.contains(dropEl) ||
dropEl.closest('#forge-selected'))) {
addCardToForge(cardId, 'hand');
}
}
cardWrap.classList.remove('touch-active');
touchStartCard = null;
touchStartSource = null;
touchStartElement = null;
e.preventDefault();
}
};
cardWrap.ontouchcancel = function() {
if (touchStartCard === cardId) {
cardWrap.classList.remove('touch-active');
touchStartCard = null;
touchStartSource = null;
touchStartElement = null;
}
};
}
});
// Обработка touch для карт в колоде - упрощённая версия
$all('.forge-deck-card').forEach(cardWrap => {
const cardId = cardWrap.dataset.cardId;
if (cardId) {
// Удаляем старые обработчики
cardWrap.ontouchstart = null;
cardWrap.ontouchend = null;
cardWrap.ontouchstart = function(e) {
touchStartCard = cardId;
touchStartSource = 'deck';
touchStartElement = cardWrap;
cardWrap._touchStartTime = Date.now();
cardWrap.classList.add('touch-active');
e.preventDefault();
};
cardWrap.ontouchend = function(e) {
if (touchStartCard === cardId) {
const touch = e.changedTouches[0];
const timeDiff = Date.now() - (touchStartElement?._touchStartTime || Date.now());
// Если это быстрый тап (менее 300мс), просто добавляем карту
if (timeDiff < 300) {
addCardToForge(cardId, 'deck');
} else {
// Иначе проверяем, куда перетащили
const dropEl = document.elementFromPoint(touch.clientX, touch.clientY);
if (dropEl && (dropEl === selectedEl || selectedEl.contains(dropEl) ||
dropEl.closest('#forge-selected'))) {
addCardToForge(cardId, 'deck');
}
}
cardWrap.classList.remove('touch-active');
touchStartCard = null;
touchStartSource = null;
touchStartElement = null;
e.preventDefault();
}
};
cardWrap.ontouchcancel = function() {
if (touchStartCard === cardId) {
cardWrap.classList.remove('touch-active');
touchStartCard = null;
touchStartSource = null;
touchStartElement = null;
}
};
}
});
// Также добавляем обработчик на саму область выбранных карт для визуальной обратной связи
selectedEl.ontouchstart = function(e) {
selectedEl.classList.add('drag-over');
};
selectedEl.ontouchend = function(e) {
selectedEl.classList.remove('drag-over');
};
if (!hand || hand.length === 0) {
handList.innerHTML = '<div style="color: #94a3b8; text-align: center; padding: 2rem; font-size: 0.9rem;">Рука пуста</div>';
return;
}
// Делаем карты в руке перетаскиваемыми в кузницу
$all('#your-hand .card-wrap').forEach(cardWrap => {
const cardId = cardWrap.dataset.cardId;
// Фильтруем только миньонов
const minionCards = hand.filter(cardId => {
const meta = cardDb[cardId];
if (cardId && meta && meta.type === 'minion') {
// Сохраняем оригинальный draggable, если он был
const originalDraggable = cardWrap.draggable;
const originalOndragstart = cardWrap.ondragstart;
// Добавляем дополнительный обработчик для кузницы
const forgeDragStart = (e) => {
// Проверяем, открыта ли кузница
if (sidebar && !sidebar.classList.contains('hidden')) {
e.dataTransfer.setData('text/plain', cardId);
e.dataTransfer.setData('source', 'hand');
cardWrap.classList.add('dragging');
}
};
cardWrap.addEventListener('dragstart', forgeDragStart);
// Сохраняем ссылку для последующего удаления
cardWrap._forgeDragStart = forgeDragStart;
}
return meta && meta.type === 'minion';
});
// Делаем карты в колоде перетаскиваемыми
$all('.forge-deck-card').forEach(cardWrap => {
const cardId = cardWrap.dataset.cardId;
if (cardId) {
cardWrap.draggable = true;
const deckDragStart = (e) => {
e.dataTransfer.setData('text/plain', cardId);
e.dataTransfer.setData('source', 'deck');
cardWrap.classList.add('dragging');
};
const deckDragEnd = () => {
cardWrap.classList.remove('dragging');
};
// Удаляем старые обработчики
cardWrap.removeEventListener('dragstart', cardWrap._deckDragStart);
cardWrap.removeEventListener('dragend', cardWrap._deckDragEnd);
// Добавляем новые
cardWrap.addEventListener('dragstart', deckDragStart);
cardWrap.addEventListener('dragend', deckDragEnd);
// Сохраняем ссылки
cardWrap._deckDragStart = deckDragStart;
cardWrap._deckDragEnd = deckDragEnd;
}
});
}
function renderForgeDeck(state) {
const you = state.players[state.yourIndex];
const deckList = $('forge-deck-list');
if (!deckList) return;
if (minionCards.length === 0) {
handList.innerHTML = '<div style="color: #94a3b8; text-align: center; padding: 2rem; font-size: 0.9rem;">Нет миньонов в руке</div>';
return;
}
deckList.innerHTML = you.deck.map((cardId, idx) => {
handList.innerHTML = minionCards.map((cardId) => {
const meta = cardDb[cardId];
if (!meta || meta.type !== 'minion') return '';
if (!meta) return '';
const isSelected = forgeSelected.includes(cardId);
return `<div class="card-wrap forge-deck-card ${isSelected ? 'selected' : ''}" data-card-id="${cardId}" data-deck-index="${idx}" draggable="true" style="width: 90px; height: 126px; cursor: grab;">
return `<div class="card-wrap ${isSelected ? 'selected' : ''}" data-card-id="${cardId}" style="width: 100px; height: 140px; cursor: pointer;">
<div class="card faction-${meta.faction || 'neutral'}">
<div class="card-art">${getCardArt(meta)}</div>
<div class="card-info">
<div class="card-name" style="font-size: 0.65rem;">${escapeHtml(meta.name)}</div>
<div class="card-name" style="font-size: 0.7rem;">${escapeHtml(meta.name)}</div>
<div class="card-stats">
<span class="atk">${meta.attack || 0}</span>
<span class="hp">${meta.health || 0}</span>
@ -2555,22 +2460,67 @@
</div>`;
}).filter(Boolean).join('');
// Клик по карте в колоде тоже добавляет её (для десктопа)
// На мобильных это обрабатывается через touch события
if (!isTouchDevice()) {
$all('.forge-deck-card').forEach(card => {
card.onclick = function() {
const cardId = card.dataset.cardId;
addCardToForge(cardId, 'deck');
};
});
// Обработчики клика для карт из руки
$all('#forge-hand-list .card-wrap').forEach(card => {
card.onclick = function(e) {
e.stopPropagation();
const cardId = card.dataset.cardId;
if (cardId) {
addCardToForge(cardId, 'hand');
}
};
});
}
function renderForgeDeck(state) {
const deck = state.yourDeck || [];
const deckList = $('forge-deck-list');
if (!deckList) return;
if (!deck || deck.length === 0) {
deckList.innerHTML = '<div style="color: #94a3b8; text-align: center; padding: 2rem; font-size: 0.9rem;">Колода пуста</div>';
return;
}
renderForgeSelected();
updateForgeCraftButton();
// Фильтруем только миньонов
const minionCards = deck.filter(cardId => {
const meta = cardDb[cardId];
return meta && meta.type === 'minion';
});
// Настраиваем drag-and-drop после рендеринга
setTimeout(() => setupForgeDragAndDrop(state), 100);
if (minionCards.length === 0) {
deckList.innerHTML = '<div style="color: #94a3b8; text-align: center; padding: 2rem; font-size: 0.9rem;">Нет миньонов в колоде</div>';
return;
}
deckList.innerHTML = minionCards.map((cardId) => {
const meta = cardDb[cardId];
if (!meta) return '';
const isSelected = forgeSelected.includes(cardId);
return `<div class="card-wrap ${isSelected ? 'selected' : ''}" data-card-id="${cardId}" style="width: 100px; height: 140px; cursor: pointer;">
<div class="card faction-${meta.faction || 'neutral'}">
<div class="card-art">${getCardArt(meta)}</div>
<div class="card-info">
<div class="card-name" style="font-size: 0.7rem;">${escapeHtml(meta.name)}</div>
<div class="card-stats">
<span class="atk">${meta.attack || 0}</span>
<span class="hp">${meta.health || 0}</span>
</div>
</div>
</div>
</div>`;
}).filter(Boolean).join('');
// Обработчики клика для карт из колоды
$all('#forge-deck-list .card-wrap').forEach(card => {
card.onclick = function(e) {
e.stopPropagation();
const cardId = card.dataset.cardId;
if (cardId) {
addCardToForge(cardId, 'deck');
}
};
});
}
// Обновляем drag-and-drop при каждом обновлении руки, если кузница открыта
@ -2587,6 +2537,7 @@
socket.emit('forgeCard', { cardIds: forgeSelected });
forgeSelected = [];
renderForgeSelected();
updateForgeCraftButton();
const sidebar = $('forge-sidebar');
if (sidebar) {
sidebar.classList.add('hidden');