123
This commit is contained in:
194
public/game.js
194
public/game.js
@ -10,6 +10,8 @@
|
||||
let gameState = null;
|
||||
let cardDb = {};
|
||||
let yourIndex = -1;
|
||||
let forgePreviewRequestId = 0;
|
||||
let forgePreviewPendingIds = null;
|
||||
let attackMode = { active: false, attackerPlayer: -1, attackerBoard: -1 };
|
||||
let spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
|
||||
let heroAbilityMode = { active: false };
|
||||
@ -179,6 +181,39 @@
|
||||
if (data.warn && typeof window.Sounds !== 'undefined') window.Sounds.timerWarning();
|
||||
if (data.ended && typeof window.Sounds !== 'undefined') window.Sounds.timerEnd();
|
||||
});
|
||||
socket.on('forgePreviewResult', (data) => {
|
||||
if (!data) return;
|
||||
if (data.requestId != null && data.requestId !== forgePreviewRequestId) return;
|
||||
const preview = $('forge-preview');
|
||||
const wrap = $('forge-preview-card-wrap');
|
||||
const confirmBtn = $('btn-forge-confirm');
|
||||
if (!preview) return;
|
||||
if (data.error || !data.resultCardId) {
|
||||
preview.classList.add('hidden');
|
||||
if (wrap) wrap.innerHTML = '';
|
||||
if (confirmBtn) confirmBtn.disabled = true;
|
||||
forgePreviewPendingIds = null;
|
||||
return;
|
||||
}
|
||||
const meta = (gameState?.cardDb || cardDb)[data.resultCardId];
|
||||
if (!meta) {
|
||||
preview.classList.add('hidden');
|
||||
forgePreviewPendingIds = null;
|
||||
return;
|
||||
}
|
||||
const you = gameState?.players?.[gameState?.yourIndex];
|
||||
const canCraft = you && (you.mana || 0) >= 2;
|
||||
if (wrap) {
|
||||
const art = typeof getCardArt === 'function' ? getCardArt(meta) : '';
|
||||
wrap.innerHTML = '<div class="card-wrap forge-preview-card" style="width:110px;height:154px;"><div class="card faction-' + (meta.faction || 'neutral') + '">' +
|
||||
'<div class="card-art">' + art + '</div>' +
|
||||
'<div class="card-info"><div class="card-name" style="font-size:0.75rem">' + 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>';
|
||||
}
|
||||
if (confirmBtn) confirmBtn.disabled = !canCraft;
|
||||
preview.classList.remove('hidden');
|
||||
});
|
||||
|
||||
socket.on('chatMessage', (data) => {
|
||||
const chatMessagesEl = $('chat-messages');
|
||||
if (chatMessagesEl && data) {
|
||||
@ -399,7 +434,7 @@
|
||||
document.body.appendChild(damageEl);
|
||||
setTimeout(() => {
|
||||
damageEl.style.animation = 'damageFloat 1.5s ease-out forwards';
|
||||
setTimeout(() => damageEl.remove(), 1000); // Сократил с 1500 до 1000 мс
|
||||
setTimeout(() => damageEl.remove(), 1000);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
@ -1658,9 +1693,8 @@
|
||||
sidebar.classList.remove('hidden');
|
||||
forgeSelected = []; // Сбрасываем выбор при открытии
|
||||
renderForgeHand(state);
|
||||
renderForgeDeck(state);
|
||||
renderForgeSelected();
|
||||
updateForgeCraftButton();
|
||||
updateForgePreview();
|
||||
}
|
||||
};
|
||||
} else if (forgeBtn) {
|
||||
@ -1841,9 +1875,8 @@
|
||||
// Обновляем кузницу, если она открыта
|
||||
if (isForgeOpen) {
|
||||
renderForgeHand(state);
|
||||
renderForgeDeck(state);
|
||||
renderForgeSelected();
|
||||
updateForgeCraftButton();
|
||||
updateForgePreview();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2162,18 +2195,20 @@
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('hidden');
|
||||
forgeSelected = [];
|
||||
forgePreviewPendingIds = null;
|
||||
const preview = $('forge-preview');
|
||||
if (preview) preview.classList.add('hidden');
|
||||
renderForgeSelected();
|
||||
updateForgePreview();
|
||||
}
|
||||
});
|
||||
|
||||
$('btn-forge-clear')?.addEventListener('click', () => {
|
||||
forgeSelected = [];
|
||||
forgePreviewPendingIds = null;
|
||||
renderForgeSelected();
|
||||
updateForgeCraftButton();
|
||||
if (gameState) {
|
||||
renderForgeHand(gameState);
|
||||
renderForgeDeck(gameState);
|
||||
}
|
||||
updateForgePreview();
|
||||
if (gameState) renderForgeHand(gameState);
|
||||
});
|
||||
|
||||
$('btn-steal-close')?.addEventListener('click', () => {
|
||||
@ -2338,7 +2373,7 @@
|
||||
}
|
||||
|
||||
if (forgeSelected.length === 0) {
|
||||
selectedEl.innerHTML = '<div class="forge-selected-empty">Кликните на карты чтобы выбрать</div>';
|
||||
selectedEl.innerHTML = '<div class="forge-selected-empty">Кликните на карты из руки</div>';
|
||||
} else {
|
||||
selectedEl.innerHTML = forgeSelected.map((cardId, idx) => {
|
||||
const meta = cardDb[cardId];
|
||||
@ -2365,19 +2400,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateForgeCraftButton() {
|
||||
const craftBtn = $('btn-forge-craft');
|
||||
const you = gameState?.players?.[gameState?.yourIndex];
|
||||
if (craftBtn && you) {
|
||||
const canCraft = forgeSelected.length >= 2 && forgeSelected.length <= 3 && (you.mana || 0) >= 2;
|
||||
craftBtn.disabled = !canCraft;
|
||||
function updateForgePreview() {
|
||||
const preview = $('forge-preview');
|
||||
const wrap = $('forge-preview-card-wrap');
|
||||
const confirmBtn = $('btn-forge-confirm');
|
||||
if (!preview) return;
|
||||
if (forgeSelected.length < 2 || forgeSelected.length > 3) {
|
||||
preview.classList.add('hidden');
|
||||
if (wrap) wrap.innerHTML = '';
|
||||
if (confirmBtn) confirmBtn.disabled = true;
|
||||
forgePreviewPendingIds = null;
|
||||
return;
|
||||
}
|
||||
forgePreviewRequestId = Date.now();
|
||||
forgePreviewPendingIds = [...forgeSelected];
|
||||
if (wrap) wrap.innerHTML = '<p class="forge-preview-loading">Загрузка…</p>';
|
||||
if (confirmBtn) confirmBtn.disabled = true;
|
||||
preview.classList.remove('hidden');
|
||||
socket.emit('forgePreview', { cardIds: forgePreviewPendingIds, requestId: forgePreviewRequestId });
|
||||
}
|
||||
|
||||
|
||||
function addCardToForge(cardId, source) {
|
||||
if (!cardId) return;
|
||||
if (source !== 'hand') return; // только из руки
|
||||
if (forgeSelected.includes(cardId)) {
|
||||
// Если карта уже выбрана, убираем её
|
||||
removeCardFromForge(cardId);
|
||||
return;
|
||||
}
|
||||
@ -2396,26 +2442,17 @@
|
||||
|
||||
forgeSelected.push(cardId);
|
||||
renderForgeSelected();
|
||||
updateForgeCraftButton();
|
||||
|
||||
// Обновляем отображение в источниках
|
||||
if (gameState) {
|
||||
renderForgeHand(gameState);
|
||||
renderForgeDeck(gameState);
|
||||
}
|
||||
updateForgePreview();
|
||||
if (gameState) renderForgeHand(gameState);
|
||||
}
|
||||
|
||||
|
||||
function removeCardFromForge(cardId) {
|
||||
const idx = forgeSelected.indexOf(cardId);
|
||||
if (idx >= 0) {
|
||||
forgeSelected.splice(idx, 1);
|
||||
renderForgeSelected();
|
||||
updateForgeCraftButton();
|
||||
// Обновляем отображение в источниках
|
||||
if (gameState) {
|
||||
renderForgeHand(gameState);
|
||||
renderForgeDeck(gameState);
|
||||
}
|
||||
updateForgePreview();
|
||||
if (gameState) renderForgeHand(gameState);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2472,76 +2509,35 @@
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Фильтруем только миньонов
|
||||
const minionCards = deck.filter(cardId => {
|
||||
const meta = cardDb[cardId];
|
||||
return meta && meta.type === 'minion';
|
||||
});
|
||||
|
||||
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 при каждом обновлении руки, если кузница открыта
|
||||
// Это делается в bindGameEvents
|
||||
|
||||
// Обработчик создания улучшенной карты
|
||||
const craftBtn = $('btn-forge-craft');
|
||||
if (craftBtn) {
|
||||
craftBtn.onclick = function() {
|
||||
if (craftBtn.disabled) return;
|
||||
const confirmBtn = $('btn-forge-confirm');
|
||||
if (confirmBtn) {
|
||||
confirmBtn.onclick = function() {
|
||||
if (confirmBtn.disabled || !forgePreviewPendingIds || forgePreviewPendingIds.length < 2) return;
|
||||
const you = gameState?.players?.[gameState?.yourIndex];
|
||||
if (!you || forgeSelected.length < 2 || forgeSelected.length > 3 || (you.mana || 0) < 2) return;
|
||||
|
||||
socket.emit('forgeCard', { cardIds: forgeSelected });
|
||||
if (!you || (you.mana || 0) < 2) return;
|
||||
socket.emit('forgeCard', { cardIds: forgePreviewPendingIds });
|
||||
if (typeof window.Sounds !== 'undefined' && window.Sounds.forge) window.Sounds.forge();
|
||||
forgeSelected = [];
|
||||
forgePreviewPendingIds = null;
|
||||
const preview = $('forge-preview');
|
||||
if (preview) preview.classList.add('hidden');
|
||||
renderForgeSelected();
|
||||
updateForgeCraftButton();
|
||||
updateForgePreview();
|
||||
if (gameState) renderForgeHand(gameState);
|
||||
const sidebar = $('forge-sidebar');
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('hidden');
|
||||
}
|
||||
if (sidebar) sidebar.classList.add('hidden');
|
||||
};
|
||||
}
|
||||
const cancelPreviewBtn = $('btn-forge-cancel-preview');
|
||||
if (cancelPreviewBtn) {
|
||||
cancelPreviewBtn.onclick = function() {
|
||||
const preview = $('forge-preview');
|
||||
if (preview) preview.classList.add('hidden');
|
||||
forgePreviewPendingIds = null;
|
||||
const wrap = $('forge-preview-card-wrap');
|
||||
if (wrap) wrap.innerHTML = '';
|
||||
const cb = $('btn-forge-confirm');
|
||||
if (cb) cb.disabled = true;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -271,10 +271,11 @@
|
||||
</div>
|
||||
|
||||
<div class="forge-main-content">
|
||||
<div class="forge-columns">
|
||||
<div class="forge-columns forge-two-cols">
|
||||
<!-- Карты из руки -->
|
||||
<div class="forge-column">
|
||||
<h3 class="forge-column-title">📋 Из руки</h3>
|
||||
<p class="forge-hint-inline">Только миньоны из руки. Выберите 2–3 карты.</p>
|
||||
<div id="forge-hand-list" class="forge-cards-list"></div>
|
||||
</div>
|
||||
|
||||
@ -282,21 +283,22 @@
|
||||
<div class="forge-column forge-selected-column">
|
||||
<h3 class="forge-column-title">✨ Выбрано (<span id="forge-selected-count">0</span>/3)</h3>
|
||||
<div id="forge-selected" class="forge-selected">
|
||||
<div class="forge-selected-empty">Кликните на карты чтобы выбрать</div>
|
||||
<div class="forge-selected-empty">Кликните на карты из руки</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Карты из колоды -->
|
||||
<div class="forge-column">
|
||||
<h3 class="forge-column-title">📚 Из колоды</h3>
|
||||
<div id="forge-deck-list" class="forge-cards-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="forge-preview" class="forge-preview hidden">
|
||||
<p class="forge-preview-title">При слиянии вы получите:</p>
|
||||
<div id="forge-preview-card-wrap" class="forge-preview-card-wrap"></div>
|
||||
<div class="forge-preview-actions">
|
||||
<button type="button" id="btn-forge-confirm" class="btn btn-primary" disabled>⚒ Крафтить (2 маны)</button>
|
||||
<button type="button" id="btn-forge-cancel-preview" class="btn btn-ghost">Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="forge-actions">
|
||||
<button type="button" id="btn-forge-clear" class="btn btn-ghost">Очистить</button>
|
||||
<button type="button" id="btn-forge-craft" class="btn btn-primary" disabled>⚒ Создать улучшенную карту (2 маны)</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="card-info-overlay" class="modal-overlay hidden">
|
||||
|
||||
@ -115,6 +115,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
function forge() {
|
||||
const c = init();
|
||||
if (!c) return;
|
||||
const notes = [392, 523, 659, 784];
|
||||
notes.forEach((f, i) => {
|
||||
setTimeout(() => beep(f, 0.2, 'sine', 0.12), i * 90);
|
||||
});
|
||||
}
|
||||
|
||||
global.Sounds = {
|
||||
init,
|
||||
playCard,
|
||||
@ -126,5 +135,6 @@
|
||||
hoverCard,
|
||||
timerWarning,
|
||||
timerEnd,
|
||||
forge,
|
||||
};
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
|
||||
@ -1648,6 +1648,17 @@ html, body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.forge-columns.forge-two-cols {
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
}
|
||||
|
||||
.forge-hint-inline {
|
||||
font-size: 0.85rem;
|
||||
color: #94a3b8;
|
||||
margin: 0 0 0.5rem 0;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.forge-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1789,6 +1800,56 @@ html, body {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Forge preview (предпросмотр результата крафта) */
|
||||
.forge-preview {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid rgba(212,168,75,0.2);
|
||||
background: rgba(0,0,0,0.25);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.forge-preview.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.forge-preview-title {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.forge-preview-card-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 120px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.forge-preview-loading {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.forge-preview-card {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.forge-preview-card .card {
|
||||
border-radius: 8px;
|
||||
border: 2px solid rgba(212,168,75,0.5);
|
||||
box-shadow: 0 0 20px rgba(212,168,75,0.3);
|
||||
}
|
||||
|
||||
.forge-preview-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-10px); }
|
||||
|
||||
Reference in New Issue
Block a user