diff --git a/cards.js b/cards.js
index 2e9bd58..389b557 100644
--- a/cards.js
+++ b/cards.js
@@ -1012,11 +1012,11 @@ module.exports = {
cost: 3,
type: 'spell',
faction: 'pirates',
- text: 'Возьми 2 карты.',
+ text: 'Укради 2 карты из колоды противника.',
art: 'plunder',
- spellEffect: 'draw_2',
- spellTarget: 'none',
- bio: 'Пираты грабят и получают добычу.',
+ spellEffect: 'steal_cards',
+ spellTarget: 'enemy_player',
+ bio: 'Пираты грабят и крадут карты у противников.',
},
mandalorian_rage: {
name: 'Ярость мандалорца',
diff --git a/public/game.js b/public/game.js
index 4b939d1..2e30b1d 100644
--- a/public/game.js
+++ b/public/game.js
@@ -13,6 +13,7 @@
let attackMode = { active: false, attackerPlayer: -1, attackerBoard: -1 };
let spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
let heroAbilityMode = { active: false };
+ let stealCardsMode = { active: false, handIndex: -1, targetPlayerIndex: null, targetDeck: [], selectedIndices: [] };
const seenMinions = new Set();
let lastHandLength = 0;
let prevGameState = null;
@@ -200,6 +201,40 @@
}
renderGame(state);
});
+
+ socket.on('stealCardsRequest', (data) => {
+ if (!gameState) return;
+ // Находим handIndex заклинания "Грабеж" в текущей руке
+ const you = gameState.players[gameState.yourIndex];
+ if (!you || !you.hand) return;
+
+ // Ищем заклинание "Грабеж" в руке
+ let handIndex = -1;
+ for (let i = 0; i < you.hand.length; i++) {
+ const card = cardDb[you.hand[i]];
+ if (card && card.spellEffect === 'steal_cards') {
+ handIndex = i;
+ break;
+ }
+ }
+
+ if (handIndex < 0) return;
+
+ stealCardsMode.active = true;
+ stealCardsMode.handIndex = handIndex;
+ stealCardsMode.targetPlayerIndex = data.targetPlayerIndex;
+ stealCardsMode.targetDeck = [];
+ stealCardsMode.selectedIndices = [];
+
+ // Получаем колоду противника из gameState
+ const targetPlayer = gameState.players[data.targetPlayerIndex];
+ if (targetPlayer && targetPlayer.deck) {
+ stealCardsMode.targetDeck = [...targetPlayer.deck];
+ }
+
+ showStealCardsModal(gameState, data);
+ });
+
return socket;
}
@@ -253,12 +288,14 @@
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 `
-
+
${escapeHtml(name)}
❤ ${p.health}
🔵 ${p.mana}/${p.maxMana}
+ ${p.deck && p.deck.length > 0 ? `📚 ${p.deck.length}` : ''}
${heroDrop}${heroBar}${minions.join('')}
`;
@@ -888,14 +925,32 @@
return;
}
if (meta.type === 'spell') {
- if (spellMode.active || heroAbilityMode.active) return;
+ if (spellMode.active || heroAbilityMode.active || stealCardsMode.active) return;
var needTarget = meta.spellTarget && meta.spellTarget !== 'none';
+
+ // Специальная обработка для Грабежа
+ if (meta.spellEffect === 'steal_cards') {
+ if (meta.spellTarget === 'enemy_player') {
+ // Открываем модальное окно для выбора противника
+ spellMode = { active: true, handIndex: handIndex, cardId: wrap.dataset.cardId, spellTarget: meta.spellTarget };
+ $('spell-mode')?.classList.remove('hidden');
+ renderGame(state);
+ return;
+ }
+ }
+
if (!needTarget) {
if (typeof window.Sounds !== 'undefined') window.Sounds.playCard();
socket.emit('playSpell', { handIndex: handIndex });
return;
}
spellMode = { active: true, handIndex: handIndex, cardId: wrap.dataset.cardId, spellTarget: meta.spellTarget };
+ const spellModeText = $('spell-mode')?.querySelector('p');
+ if (spellModeText && meta.spellEffect === 'steal_cards') {
+ spellModeText.textContent = 'Выберите противника для грабежа (кликните на блок противника)';
+ } else if (spellModeText) {
+ spellModeText.textContent = 'Выберите цель для заклинания';
+ }
$('spell-mode')?.classList.remove('hidden');
renderGame(state);
return;
@@ -1184,6 +1239,34 @@
e.stopPropagation();
var tp = parseInt(el.dataset.dropPlayer ?? el.dataset.playerIndex, 10);
var tb = parseInt(el.dataset.dropBoard ?? el.dataset.boardIndex, 10);
+
+ // Специальная обработка для Грабежа - открываем модальное окно
+ const spellCard = cardDb[spellMode.cardId];
+ if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') {
+ // Выбираем противника (tp должен быть индексом противника, tb игнорируем для enemy_player)
+ if (tp !== state.yourIndex && tp >= 0) {
+ const targetPlayer = state.players[tp];
+ if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) {
+ stealCardsMode.active = true;
+ stealCardsMode.handIndex = spellMode.handIndex;
+ stealCardsMode.targetPlayerIndex = tp;
+ // Получаем актуальную колоду из gameState
+ stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : [];
+ stealCardsMode.selectedIndices = [];
+ showStealCardsModal(state, {
+ targetPlayerIndex: tp,
+ targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`,
+ targetDeckSize: targetPlayer.deck.length,
+ maxCards: Math.min(2, targetPlayer.deck.length)
+ });
+ spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
+ $('spell-mode')?.classList.add('hidden');
+ return;
+ }
+ }
+ return;
+ }
+
if (typeof window.Sounds !== 'undefined') window.Sounds.playCard();
socket.emit('playSpell', { handIndex: spellMode.handIndex, targetPlayerIndex: tp, targetBoardIndex: tb });
spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
@@ -1199,6 +1282,33 @@
e.stopPropagation();
var tp = parseInt(el.dataset.dropPlayer ?? el.dataset.playerIndex, 10);
var tb = parseInt(el.dataset.dropBoard ?? el.dataset.boardIndex, 10);
+
+ // Специальная обработка для Грабежа
+ const spellCard = cardDb[spellMode.cardId];
+ if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') {
+ if (tp !== state.yourIndex && tp >= 0) {
+ const targetPlayer = state.players[tp];
+ if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) {
+ stealCardsMode.active = true;
+ stealCardsMode.handIndex = spellMode.handIndex;
+ stealCardsMode.targetPlayerIndex = tp;
+ // Получаем актуальную колоду из gameState
+ stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : [];
+ stealCardsMode.selectedIndices = [];
+ showStealCardsModal(state, {
+ targetPlayerIndex: tp,
+ targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`,
+ targetDeckSize: targetPlayer.deck.length,
+ maxCards: Math.min(2, targetPlayer.deck.length)
+ });
+ spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
+ $('spell-mode')?.classList.add('hidden');
+ return;
+ }
+ }
+ return;
+ }
+
if (typeof window.Sounds !== 'undefined') window.Sounds.playCard();
socket.emit('playSpell', { handIndex: spellMode.handIndex, targetPlayerIndex: tp, targetBoardIndex: tb });
spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
@@ -1237,6 +1347,65 @@
};
}
});
+
+ // Обработка выбора противника для Грабежа
+ $all('.opponent-block.steal-target').forEach(function (el) {
+ el.onclick = function (e) {
+ if (!spellMode.active) return;
+ const spellCard = cardDb[spellMode.cardId];
+ if (!spellCard || spellCard.spellEffect !== 'steal_cards' || spellCard.spellTarget !== 'enemy_player') return;
+ e.stopPropagation();
+ var tp = parseInt(el.dataset.playerIndex ?? el.dataset.opponentIndex, 10);
+ if (tp === state.yourIndex || tp < 0) return;
+ const targetPlayer = state.players[tp];
+ if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) {
+ stealCardsMode.active = true;
+ stealCardsMode.handIndex = spellMode.handIndex;
+ stealCardsMode.targetPlayerIndex = tp;
+ // Получаем актуальную колоду из gameState
+ stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : [];
+ stealCardsMode.selectedIndices = [];
+ showStealCardsModal(state, {
+ targetPlayerIndex: tp,
+ targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`,
+ targetDeckSize: targetPlayer.deck.length,
+ maxCards: Math.min(2, targetPlayer.deck.length)
+ });
+ spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
+ $('spell-mode')?.classList.add('hidden');
+ }
+ };
+
+ // Touch support
+ if (isTouchDevice()) {
+ el.ontouchstart = function (e) {
+ if (!spellMode.active) return;
+ const spellCard = cardDb[spellMode.cardId];
+ if (!spellCard || spellCard.spellEffect !== 'steal_cards' || spellCard.spellTarget !== 'enemy_player') return;
+ e.preventDefault();
+ e.stopPropagation();
+ var tp = parseInt(el.dataset.playerIndex ?? el.dataset.opponentIndex, 10);
+ if (tp === state.yourIndex || tp < 0) return;
+ const targetPlayer = state.players[tp];
+ if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) {
+ stealCardsMode.active = true;
+ stealCardsMode.handIndex = spellMode.handIndex;
+ stealCardsMode.targetPlayerIndex = tp;
+ // Получаем актуальную колоду из gameState
+ stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : [];
+ stealCardsMode.selectedIndices = [];
+ showStealCardsModal(state, {
+ targetPlayerIndex: tp,
+ targetPlayerName: targetPlayer.name || `Игрок ${tp + 1}`,
+ targetDeckSize: targetPlayer.deck.length,
+ maxCards: Math.min(2, targetPlayer.deck.length)
+ });
+ spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
+ $('spell-mode')?.classList.add('hidden');
+ }
+ };
+ }
+ });
var heroAbilityBtn = $('btn-hero-ability');
if (heroAbilityBtn) heroAbilityBtn.onclick = function () {
@@ -1263,9 +1432,11 @@
spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
attackMode = { active: false, attackerPlayer: -1, attackerBoard: -1 };
heroAbilityMode = { active: false };
+ stealCardsMode = { active: false, handIndex: -1, targetPlayerIndex: null, targetDeck: [], selectedIndices: [] };
$('spell-mode')?.classList.add('hidden');
$('attack-mode')?.classList.add('hidden');
$('hero-ability-mode')?.classList.add('hidden');
+ $('steal-cards-overlay')?.classList.add('hidden');
renderGame(state);
};
@@ -1282,9 +1453,11 @@
attackMode = { active: false, attackerPlayer: -1, attackerBoard: -1 };
spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
heroAbilityMode = { active: false };
+ stealCardsMode = { active: false, handIndex: -1, targetPlayerIndex: null, targetDeck: [], selectedIndices: [] };
$('attack-mode')?.classList.add('hidden');
$('spell-mode')?.classList.add('hidden');
$('hero-ability-mode')?.classList.add('hidden');
+ $('steal-cards-overlay')?.classList.add('hidden');
renderGame(state);
};
}
@@ -1700,6 +1873,156 @@
}
});
+ $('btn-steal-close')?.addEventListener('click', () => {
+ $('steal-cards-overlay')?.classList.add('hidden');
+ stealCardsMode.active = false;
+ stealCardsMode.handIndex = -1;
+ stealCardsMode.targetPlayerIndex = null;
+ stealCardsMode.targetDeck = [];
+ stealCardsMode.selectedIndices = [];
+ });
+
+ $('steal-cards-overlay')?.addEventListener('click', (e) => {
+ if (e.target.id === 'steal-cards-overlay') {
+ $('steal-cards-overlay')?.classList.add('hidden');
+ stealCardsMode.active = false;
+ stealCardsMode.handIndex = -1;
+ stealCardsMode.targetPlayerIndex = null;
+ stealCardsMode.targetDeck = [];
+ stealCardsMode.selectedIndices = [];
+ }
+ });
+
+ function showStealCardsModal(state, data) {
+ const deckList = $('steal-deck-list');
+ const selectedEl = $('steal-selected');
+ const confirmBtn = $('btn-steal-confirm');
+ const hintEl = $('steal-cards-hint');
+ const targetSelect = $('steal-target-select');
+
+ if (!deckList || !selectedEl || !confirmBtn) return;
+
+ // Если противник уже выбран, показываем его колоду
+ if (stealCardsMode.targetPlayerIndex !== null) {
+ // Обновляем колоду из актуального gameState
+ const targetPlayer = state.players[stealCardsMode.targetPlayerIndex];
+ if (targetPlayer && targetPlayer.deck) {
+ stealCardsMode.targetDeck = [...targetPlayer.deck];
+ }
+
+ if (stealCardsMode.targetDeck.length === 0) {
+ // Колода пуста, закрываем модальное окно
+ $('steal-cards-overlay')?.classList.add('hidden');
+ stealCardsMode.active = false;
+ return;
+ }
+
+ targetSelect.classList.add('hidden');
+ deckList.classList.remove('hidden');
+
+ if (hintEl) {
+ hintEl.textContent = `Выберите до 2 карт из колоды ${targetPlayer?.name || `Игрока ${stealCardsMode.targetPlayerIndex + 1}`} (${stealCardsMode.targetDeck.length} карт)`;
+ }
+
+ deckList.innerHTML = stealCardsMode.targetDeck.map((cardId, idx) => {
+ const meta = cardDb[cardId];
+ if (!meta) return '';
+ const isSelected = stealCardsMode.selectedIndices.includes(idx);
+ const cost = meta.cost || 0;
+ const attack = meta.attack !== undefined ? meta.attack : '';
+ const health = meta.health !== undefined ? meta.health : '';
+ const stats = meta.type === 'minion' ? `
${attack}${health}
` : '';
+ const costDisplay = `
${cost}
`;
+ const art = getCardArt ? getCardArt(meta) : '✦';
+
+ return `
`;
+ }).filter(Boolean).join('');
+
+ $all('.steal-deck-card').forEach(card => {
+ card.onclick = function() {
+ const idx = parseInt(card.dataset.deckIndex, 10);
+ const selectedIdx = stealCardsMode.selectedIndices.indexOf(idx);
+ if (selectedIdx >= 0) {
+ stealCardsMode.selectedIndices.splice(selectedIdx, 1);
+ } else if (stealCardsMode.selectedIndices.length < 2) {
+ stealCardsMode.selectedIndices.push(idx);
+ }
+ showStealCardsModal(state, data);
+ };
+ });
+
+ selectedEl.innerHTML = stealCardsMode.selectedIndices.length ? stealCardsMode.selectedIndices.map(idx => {
+ const cardId = stealCardsMode.targetDeck[idx];
+ const meta = cardDb[cardId];
+ if (!meta) return '';
+ return `
`;
+ }).join('') : '
Выберите до 2 карт
';
+
+ confirmBtn.disabled = stealCardsMode.selectedIndices.length === 0;
+ confirmBtn.onclick = function() {
+ if (confirmBtn.disabled || stealCardsMode.selectedIndices.length === 0) return;
+ if (typeof window.Sounds !== 'undefined') window.Sounds.playCard();
+ socket.emit('stealCards', {
+ handIndex: stealCardsMode.handIndex,
+ targetPlayerIndex: stealCardsMode.targetPlayerIndex,
+ cardIndices: stealCardsMode.selectedIndices
+ });
+ stealCardsMode.active = false;
+ stealCardsMode.handIndex = -1;
+ stealCardsMode.targetPlayerIndex = null;
+ stealCardsMode.targetDeck = [];
+ stealCardsMode.selectedIndices = [];
+ $('steal-cards-overlay')?.classList.add('hidden');
+ };
+ } else {
+ // Показываем выбор противника (если еще не выбран)
+ targetSelect.classList.remove('hidden');
+ deckList.classList.add('hidden');
+
+ if (hintEl) {
+ hintEl.textContent = 'Выберите противника для грабежа';
+ }
+
+ const enemies = state.players.filter((p, i) => i !== state.yourIndex && p.health > 0 && p.deck && p.deck.length > 0);
+ targetSelect.innerHTML = enemies.map((enemy, idx) => {
+ const enemyIdx = state.players.indexOf(enemy);
+ return `
+
${escapeHtml(enemy.name || `Игрок ${enemyIdx + 1}`)}
+
Колода: ${enemy.deck.length} карт
+
`;
+ }).join('');
+
+ $all('.steal-target-option').forEach(option => {
+ option.onclick = function() {
+ const playerIdx = parseInt(option.dataset.playerIndex, 10);
+ const targetPlayer = state.players[playerIdx];
+ if (targetPlayer && targetPlayer.deck && targetPlayer.deck.length > 0) {
+ stealCardsMode.targetPlayerIndex = playerIdx;
+ // Получаем актуальную колоду из gameState
+ stealCardsMode.targetDeck = targetPlayer.deck ? [...targetPlayer.deck] : [];
+ showStealCardsModal(state, {
+ targetPlayerIndex: playerIdx,
+ targetPlayerName: targetPlayer.name || `Игрок ${playerIdx + 1}`,
+ targetDeckSize: targetPlayer.deck.length,
+ maxCards: Math.min(2, targetPlayer.deck.length)
+ });
+ }
+ };
+ });
+ }
+
+ $('steal-cards-overlay')?.classList.remove('hidden');
+ }
+
let forgeSelected = [];
function renderForgeDeck(state) {
const you = state.players[state.yourIndex];
diff --git a/public/index.html b/public/index.html
index 18df195..ad5881b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -263,6 +263,19 @@
+
+
+
⚔ Грабеж
+
Выберите противника и до 2 карт из его колоды
+
+
+
+
+
+
+
+
+
Настройки
diff --git a/public/styles.css b/public/styles.css
index bf2749b..e036242 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -504,6 +504,21 @@ html, body {
border-color: rgba(255,180,0,0.6);
box-shadow: 0 0 24px rgba(255,180,0,0.2);
}
+.opponent-block.steal-target {
+ border-color: rgba(255,204,0,0.6);
+ box-shadow: 0 0 20px rgba(255,204,0,0.4);
+ cursor: pointer;
+ animation: stealTargetPulse 1.5s ease-in-out infinite;
+}
+.opponent-block.steal-target:hover {
+ border-color: rgba(255,204,0,0.9);
+ box-shadow: 0 0 30px rgba(255,204,0,0.6);
+ transform: scale(1.02);
+}
+@keyframes stealTargetPulse {
+ 0%, 100% { box-shadow: 0 0 20px rgba(255,204,0,0.4); }
+ 50% { box-shadow: 0 0 30px rgba(255,204,0,0.7); }
+}
.opponent-name { font-weight: 700; color: var(--cyan); margin-bottom: 0.35rem; font-size: 0.95rem; }
.opponent-stats { display: flex; gap: 0.75rem; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; }
.opponent-board { display: flex; flex-wrap: wrap; gap: 0.5rem; min-height: 80px; }
@@ -1435,6 +1450,67 @@ html, body {
white-space: nowrap;
}
.settings-modal { max-width: 400px; text-align: left; }
+.steal-cards-modal {
+ max-width: 800px;
+ max-height: 90vh;
+ text-align: left;
+ overflow-y: auto;
+}
+.steal-target-select {
+ margin-bottom: 1rem;
+}
+.steal-target-option:hover {
+ background: rgba(0,180,255,0.2) !important;
+ border-color: var(--cyan) !important;
+ transform: scale(1.02);
+}
+.steal-deck-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
+ gap: 0.75rem;
+ max-height: 400px;
+ overflow-y: auto;
+ margin-bottom: 1rem;
+ padding: 0.75rem;
+ background: rgba(0,0,0,0.3);
+ border-radius: 8px;
+}
+.steal-deck-card {
+ cursor: pointer;
+ transition: transform 0.2s, box-shadow 0.2s;
+ border: 2px solid transparent;
+}
+.steal-deck-card:hover {
+ transform: scale(1.1);
+ box-shadow: 0 0 15px rgba(0,180,255,0.5);
+}
+.steal-deck-card.selected {
+ border-color: var(--cyan);
+ box-shadow: 0 0 20px rgba(0,180,255,0.7);
+}
+.steal-selected {
+ min-height: 80px;
+ padding: 0.5rem;
+ background: rgba(255,204,0,0.1);
+ border: 2px dashed rgba(255,204,0,0.3);
+ border-radius: 8px;
+ margin-bottom: 1rem;
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ align-items: center;
+}
+.steal-selected-empty {
+ color: #94a3b8;
+ font-size: 0.9rem;
+ width: 100%;
+ text-align: center;
+}
+.steal-actions {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: center;
+}
.settings-content { margin: 1.5rem 0; }
.setting-group {
margin-bottom: 1.5rem;
diff --git a/server.js b/server.js
index 98259c7..defbc52 100644
--- a/server.js
+++ b/server.js
@@ -777,8 +777,29 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
const eff = card.spellEffect;
const needTarget = card.spellTarget && card.spellTarget !== 'none';
+
+ // Специальная обработка для steal_cards - требует только выбор противника (не требует targetBoardIndex)
+ if (eff === 'steal_cards') {
+ if (targetPlayerIndex == null || targetPlayerIndex === pi) return;
+ const targetPlayer = gameState.players[targetPlayerIndex];
+ if (!targetPlayer || targetPlayer.health <= 0 || !targetPlayer.deck || targetPlayer.deck.length === 0) return;
+
+ // Отправляем запрос на выбор карт для кражи
+ const socket = io.sockets.sockets.get(p.id);
+ if (socket) {
+ socket.emit('stealCardsRequest', {
+ targetPlayerIndex: targetPlayerIndex,
+ targetPlayerName: targetPlayer.name || `Игрок ${targetPlayerIndex + 1}`,
+ targetDeckSize: targetPlayer.deck.length,
+ maxCards: Math.min(2, targetPlayer.deck.length)
+ });
+ }
+ return; // Не тратим ману и не удаляем карту пока - это сделаем после выбора
+ }
- if (needTarget && (targetPlayerIndex == null || targetBoardIndex == null)) return;
+ // Для других заклинаний проверяем targetBoardIndex только если это не выбор игрока
+ if (needTarget && card.spellTarget !== 'enemy_player' && (targetPlayerIndex == null || targetBoardIndex == null)) return;
+ if (needTarget && card.spellTarget === 'enemy_player' && targetPlayerIndex == null) return;
const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);
if (eff === 'deal_2') {
@@ -927,6 +948,65 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
broadcastGameState(room);
}
+function stealCardsFromDeck(room, socketId, handIndex, targetPlayerIndex, cardIndices) {
+ const gameState = room.gameState;
+ const pi = findPlayerIndex(room, socketId);
+ if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
+ const p = gameState.players[pi];
+ if (p.health <= 0 || p.isDead) return;
+
+ const cid = p.hand[handIndex];
+ if (!cid) return;
+ const card = cardDb[cid];
+ if (!card || card.type !== 'spell' || card.spellEffect !== 'steal_cards') return;
+ const cost = card.cost || 0;
+ if (p.mana < cost) return;
+
+ const targetPlayer = gameState.players[targetPlayerIndex];
+ if (!targetPlayer || targetPlayerIndex === pi || targetPlayer.health <= 0) return;
+ if (!targetPlayer.deck || targetPlayer.deck.length === 0) return;
+
+ // Проверяем индексы карт
+ if (!Array.isArray(cardIndices) || cardIndices.length === 0 || cardIndices.length > 2) return;
+ const validIndices = cardIndices.filter(idx => idx >= 0 && idx < targetPlayer.deck.length);
+ if (validIndices.length === 0) return;
+
+ // Убираем дубликаты и сортируем по убыванию (чтобы удалять с конца)
+ const uniqueIndices = [...new Set(validIndices)].sort((a, b) => b - a);
+
+ // Крадём карты
+ const stolenCards = [];
+ uniqueIndices.forEach(idx => {
+ if (idx >= 0 && idx < targetPlayer.deck.length) {
+ stolenCards.push(targetPlayer.deck[idx]);
+ targetPlayer.deck.splice(idx, 1);
+ }
+ });
+
+ // Добавляем украденные карты в руку игрока
+ stolenCards.forEach(cardId => {
+ if (p.hand.length < 10) {
+ p.hand.push(cardId);
+ }
+ });
+
+ // Тратим ману и удаляем заклинание
+ p.mana -= cost;
+ p.hand.splice(handIndex, 1);
+
+ gameState.log.push({
+ type: 'spell',
+ spell: cid,
+ fromPlayer: pi,
+ toPlayer: targetPlayerIndex,
+ effect: 'steal_cards',
+ stolenCount: stolenCards.length
+ });
+
+ checkGameOver(room);
+ broadcastGameState(room);
+}
+
function heroAbility(room, socketId, targetPlayerIndex, targetBoardIndex) {
const gameState = room.gameState;
const pi = findPlayerIndex(room, socketId);
@@ -1165,6 +1245,12 @@ io.on('connection', (socket) => {
forgeCard(room, socket.id, data.cardIds);
});
+ socket.on('stealCards', (data) => {
+ const room = getRoomBySocket(socket.id);
+ if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
+ stealCardsFromDeck(room, socket.id, data.handIndex, data.targetPlayerIndex, data.cardIndices);
+ });
+
socket.on('resetToLobby', () => {
const room = getRoomBySocket(socket.id);
if (!room || !room.gameState || !room.gameState.players?.length) return;