123
This commit is contained in:
42
cards.js
42
cards.js
@ -9,7 +9,7 @@ module.exports = {
|
||||
health: 5,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Jedi. Hope of the Rebellion.',
|
||||
text: 'Jedi. Hope of the Rebellion. Даёт +1/+1 повстанцам и Лее.',
|
||||
art: 'luke',
|
||||
legendary: true,
|
||||
battlecry: 'Нанеси 1 урона вражескому герою.',
|
||||
@ -23,7 +23,7 @@ module.exports = {
|
||||
health: 8,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Sith Lord. Fear is his weapon.',
|
||||
text: 'Sith Lord. Fear is his weapon. Даёт +1/+1 штурмовикам и клонам.',
|
||||
art: 'vader',
|
||||
legendary: true,
|
||||
deathrattle: 'Нанеси 2 урона всем врагам.',
|
||||
@ -37,7 +37,7 @@ module.exports = {
|
||||
health: 7,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Jedi Master. Size matters not.',
|
||||
text: 'Jedi Master. Size matters not. Даёт +1/+1 джедаям.',
|
||||
art: 'yoda',
|
||||
legendary: true,
|
||||
battlecry: 'Дайте союзному миньону +1/+1.',
|
||||
@ -51,7 +51,7 @@ module.exports = {
|
||||
health: 4,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Leader. Diplomat. Hero.',
|
||||
text: 'Leader. Diplomat. Hero. Даёт +1/+1 Люку.',
|
||||
art: 'leia',
|
||||
legendary: true,
|
||||
bio: 'Лея Органа — принцесса Алдераана, сенатор Галактической Республики, дочь Энакина Скайуокера и Падме Амидалы, сестра-близнец Люка. Воспитана Бейлом и Брехой Органа. Лидер Альянса повстанцев, участвовала в уничтожении обеих «Звёзд Смерти». Обладала чувствительностью к Силе, обучалась у Люка. Генерал Сопротивления, мать Бена Соло (Кайло Рена). Погибла, использовав последние силы Силы для связи с сыном через галактику.',
|
||||
@ -63,7 +63,7 @@ module.exports = {
|
||||
health: 4,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Smuggler. Shoot first.',
|
||||
text: 'Smuggler. Shoot first. Даёт +1/+1 Чубакке.',
|
||||
art: 'han',
|
||||
legendary: true,
|
||||
bio: 'Хан Соло — контрабандист и пилот, владелец корабля «Тысячелетний сокол». Родился на Кореллии, служил в Имперском флоте, затем стал контрабандистом. Встретил Люка и Лею, присоединился к Альянсу повстанцев. Участвовал в уничтожении обеих «Звёзд Смерти». Муж Леи, отец Бена Соло. Был заморожен в карбоните Джаббой Хаттом, но выжил. Позже убит собственным сыном Кайло Реном, пытаясь вернуть его к свету. Его знаменитая фраза: «Стреляй первым».',
|
||||
@ -75,7 +75,7 @@ module.exports = {
|
||||
health: 5,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Wookiee. Loyal friend.',
|
||||
text: 'Wookiee. Loyal friend. Даёт +1/+1 Хану Соло.',
|
||||
art: 'chewie',
|
||||
bio: 'Чубакка — вуки с планеты Кашиик, первый помощник и лучший друг Хана Соло. Пережил рабство и Имперскую оккупацию родной планеты. Сражался в Войнах клонов и Гражданской войне. Долг жизни Люку Скайуокеру за спасение от рабства. Пилот «Тысячелетнего сокола», мастер-механик. Один из немногих, кто понимал язык вуки. Пережил все три трилогии, стал генералом Сопротивления.',
|
||||
},
|
||||
@ -86,7 +86,7 @@ module.exports = {
|
||||
health: 3,
|
||||
type: 'minion',
|
||||
faction: 'neutral',
|
||||
text: 'Astromech. Beep boop.',
|
||||
text: 'Astromech. Beep boop. Даёт +1/+1 C-3PO.',
|
||||
art: 'r2d2',
|
||||
legendary: true,
|
||||
deathrattle: 'Возьми карту.',
|
||||
@ -100,7 +100,7 @@ module.exports = {
|
||||
health: 4,
|
||||
type: 'minion',
|
||||
faction: 'neutral',
|
||||
text: 'Protocol droid. Terrible at odds.',
|
||||
text: 'Protocol droid. Terrible at odds. Даёт +1/+1 R2-D2.',
|
||||
art: 'c3po',
|
||||
bio: 'C-3PO — протокольный дроид, созданный Энакином Скайуокером в детстве на Татуине из запчастей. Владеет более чем шестью миллионами форм общения. Служил Падме Амидале, затем Лее Органе. Вечный спутник и лучший друг R2-D2, хотя часто жалуется на его безрассудство. Труслив, но предан друзьям. Участвовал во всех важных событиях галактики. Его золотая обшивка стала символом надежды для повстанцев.',
|
||||
},
|
||||
@ -111,7 +111,7 @@ module.exports = {
|
||||
health: 3,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Bounty Hunter. No disintegrations.',
|
||||
text: 'Bounty Hunter. No disintegrations. Даёт +1/+1 Джанго Фетту.',
|
||||
art: 'boba',
|
||||
legendary: true,
|
||||
bio: 'Боба Фетт — охотник за головами, генетическая копия (не клон) Джанго Фетта. Воспитан как сын Джанго, унаследовал его доспехи и корабль «Раб I». Один из самых опасных охотников за головами в галактике. Работал на Джаббу Хатта и Дарта Вейдера. Был проглочен сарлаком, но выжил благодаря доспехам. Позже стал лидером мандалорцев, приняв тёмный меч. Мастер тактики и выживания, уважаемый во всей галактике.',
|
||||
@ -123,7 +123,7 @@ module.exports = {
|
||||
health: 6,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Sith. Unlimited power!',
|
||||
text: 'Sith. Unlimited power! Даёт +1/+1 ситхам и имперцам.',
|
||||
art: 'palpatine',
|
||||
legendary: true,
|
||||
battlecry: 'Нанеси 2 урона вражескому герою.',
|
||||
@ -167,7 +167,7 @@ module.exports = {
|
||||
health: 6,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Jedi. Hello there.',
|
||||
text: 'Jedi. Hello there. Даёт +1/+1 Энакину.',
|
||||
art: 'obiwan',
|
||||
legendary: true,
|
||||
bio: 'Оби-Ван Кеноби — мастер-джедай, наставник Энакина Скайуокера и Люка Скайуокера. Ученик Квай-Гона Джинна, затем мастер Энакина. Участвовал в Войнах клонов, сражался с Дартом Молом, графом Дуку и генералом Гривусом. Победил Энакина на Мустафаре, но не смог убить его. Скрывался на Татуине, наблюдая за Люком. Погиб на «Звезде Смерти», став духом Силы и продолжив наставлять Люка. Его знаменитая фраза: «Привет там!» Его мудрость и преданность кодексу джедаев сделали его легендой.',
|
||||
@ -179,7 +179,7 @@ module.exports = {
|
||||
health: 3,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Sith. Double-bladed saber.',
|
||||
text: 'Sith. Double-bladed saber. Даёт +1/+1 Сэвиджу Оппрессу.',
|
||||
art: 'maul',
|
||||
legendary: true,
|
||||
bio: 'Дарт Мол — тёмный лорд ситхов, зачак, ученик Дарта Сидиуса. Был известен своим двойным световым мечом и агрессивным стилем боя. Убил Квай-Гона Джинна на Набу, но был побеждён Оби-Ваном Кеноби и считался мёртвым. Выжил благодаря ненависти и был спасён братом Сэвиджем Оппрессом. Получил кибернетические ноги. Стал лидером преступного синдиката «Теней». Был убит Оби-Ваном на Татуине, защищая Люка. Его ненависть к Оби-Вану длилась десятилетия.',
|
||||
@ -366,7 +366,7 @@ module.exports = {
|
||||
health: 4,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Senator. Fighter for peace.',
|
||||
text: 'Senator. Fighter for peace. Даёт +1/+1 Энакину.',
|
||||
art: 'padme',
|
||||
legendary: true,
|
||||
bio: 'Падме Амидала — королева, затем сенатор Набу. Была избрана королевой в 14 лет. Встретила Энакина Скайуокера, когда он был мальчиком, затем вышла за него замуж тайно. Мать Люка и Леи. Была известна своей дипломатией и борьбой за мир. Участвовала в создании Альянса повстанцев. Умерла от разбитого сердца после того, как Энакин пал на тёмную сторону. Её смерть стала ключевым моментом в превращении Энакина в Вейдера. Её преданность демократии вдохновила многих.',
|
||||
@ -415,7 +415,7 @@ module.exports = {
|
||||
health: 3,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Bounty hunter. Clone template.',
|
||||
text: 'Bounty hunter. Clone template. Даёт +1/+1 Бобе Фетту.',
|
||||
art: 'jango',
|
||||
legendary: true,
|
||||
bio: 'Джанго Фетт — мандалорец, охотник за головами, один из лучших в галактике. Был выбран как генетический шаблон для армии клонов Республики. Получил в награду невидоизменённого клона, которого назвал Бобой и воспитал как сына. Участвовал в попытке убийства сенатора Падме Амидалы. Был убит Мейсом Винду на Джеонозисе, обезглавлен световым мечом. Его доспехи и корабль унаследовал Боба. Его навыки боя и тактики сделали его идеальным шаблоном для клонов.',
|
||||
@ -578,7 +578,7 @@ module.exports = {
|
||||
health: 5,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Бывшая падаван Энакина. Белые клинки.',
|
||||
text: 'Бывшая падаван Энакина. Белые клинки. Даёт +1/+1 Рексу.',
|
||||
art: 'ahsoka',
|
||||
legendary: true,
|
||||
battlecry: 'Нанеси 1 урона вражескому миньону.',
|
||||
@ -603,7 +603,7 @@ module.exports = {
|
||||
health: 4,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Клон-командир 501-го. Верен своим.',
|
||||
text: 'Клон-командир 501-го. Верен своим. Даёт +1/+1 Эйсоке.',
|
||||
art: 'rex',
|
||||
bio: 'Капитан Рекс (CT-7567) — клон-командир, лидер 501-го легиона. Служил под командованием Энакина Скайуокера и Эйсоки Тано. Снял чип контроля после того, как Файвс раскрыл заговор. Выжил после Приказа 66 благодаря помощи Эйсоки. Присоединился к повстанцам и сражался на Эндоре. Его независимость и преданность товарищам делали его уникальным клоном. Дожил до старости, став символом того, что клоны были больше, чем просто солдаты. Его дружба с Эйсокой длилась десятилетия.',
|
||||
},
|
||||
@ -628,7 +628,7 @@ module.exports = {
|
||||
health: 5,
|
||||
type: 'minion',
|
||||
faction: 'neutral',
|
||||
text: 'Мандалорец. Это путь.',
|
||||
text: 'Мандалорец. Это путь. Даёт +1/+1 Грогу.',
|
||||
art: 'mando',
|
||||
legendary: true,
|
||||
bio: 'Дин Джарин (Мандалорец) — охотник за головами, мандалорец из клана Джаринов. Воспитал Грогу (Малыша Йоду), став его приёмным отцом. Владеет тёмным мечом, легендарным оружием мандалорцев. Следовал кодексу мандалорцев: «Это путь». Его доспехи из бескара были семейной реликвией. Стал лидером мандалорцев, объединив различные кланы. Его преданность Грогу и кодексу мандалорцев сделали его легендой. Один из величайших мандалорцев своего времени.',
|
||||
@ -640,7 +640,7 @@ module.exports = {
|
||||
health: 3,
|
||||
type: 'minion',
|
||||
faction: 'neutral',
|
||||
text: 'Малыш Йода. Сила сильна в нём.',
|
||||
text: 'Малыш Йода. Сила сильна в нём. Даёт +1/+1 Мандалорцу.',
|
||||
art: 'grogu',
|
||||
legendary: true,
|
||||
deathrattle: 'Восстанови 2 здоровья своему герою.',
|
||||
@ -1308,7 +1308,7 @@ module.exports = {
|
||||
health: 6,
|
||||
type: 'minion',
|
||||
faction: 'rebellion',
|
||||
text: 'Избранный. Может пасть на тёмную сторону.',
|
||||
text: 'Избранный. Может пасть на тёмную сторону. Даёт +1/+1 Оби-Вану и Падме.',
|
||||
art: 'anakin',
|
||||
legendary: true,
|
||||
battlecry: 'Уничтожь вражеского миньона с атакой 4 или меньше.',
|
||||
@ -1423,7 +1423,7 @@ module.exports = {
|
||||
health: 5,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Брат Мола. Сильный, но хрупкий.',
|
||||
text: 'Брат Мола. Сильный, но хрупкий. Даёт +1/+1 Дарту Молу.',
|
||||
art: 'savage',
|
||||
legendary: true,
|
||||
battlecry: 'Уничтожь вражеского миньона с атакой 2 или меньше.',
|
||||
@ -1453,7 +1453,7 @@ module.exports = {
|
||||
health: 6,
|
||||
type: 'minion',
|
||||
faction: 'empire',
|
||||
text: 'Стратег. Тактик Империи.',
|
||||
text: 'Стратег. Тактик Империи. Даёт +1/+1 имперцам.',
|
||||
art: 'tarkin',
|
||||
legendary: true,
|
||||
battlecry: 'Уничтожь вражеского миньона с наибольшей атакой.',
|
||||
|
||||
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,9 +236,29 @@
|
||||
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');
|
||||
if (badge) badge.textContent = 'Игра окончена';
|
||||
@ -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');
|
||||
@ -661,6 +717,9 @@
|
||||
badge.textContent = isYourTurn ? 'ВАШ ХОД' : 'Ход ' + (state.turn || 1);
|
||||
}
|
||||
|
||||
// Показываем нотификацию о том, кто ходит
|
||||
showTurnNotification(state);
|
||||
|
||||
var skipBoardUpdate = false;
|
||||
if (lastLog?.type === 'attack' && lastLog.attackerMinionId && lastLog.targetMinionId && prevGameState && runCombatAnimation(state)) {
|
||||
skipBoardUpdate = true;
|
||||
@ -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') {
|
||||
|
||||
@ -229,6 +229,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Turn Notification -->
|
||||
<div id="turn-notification" class="turn-notification hidden">
|
||||
<div class="turn-notification-content">
|
||||
<h2 id="turn-notification-text">Ход игрока</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="attack-mode" class="attack-mode hidden">
|
||||
<p>Выберите цель для атаки (враг или его существо)</p>
|
||||
<button type="button" id="btn-cancel-attack" class="btn btn-ghost">Отмена</button>
|
||||
|
||||
@ -345,6 +345,26 @@ html, body {
|
||||
color: #ff6b6b; display: inline-flex; align-items: center;
|
||||
}
|
||||
|
||||
.health-display #your-health.health-changed {
|
||||
animation: healthChange 0.5s ease-out;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@keyframes healthChange {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
color: var(--red);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.3);
|
||||
color: var(--amber-bright);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
color: var(--red);
|
||||
}
|
||||
}
|
||||
|
||||
.deck-count { font-size: 0.9rem; color: #94a3b8; }
|
||||
.deck-count .swg.deck-icon { font-size: 1rem; margin-right: 0.2rem; color: var(--gold); }
|
||||
|
||||
@ -519,8 +539,50 @@ html, body {
|
||||
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-block.defeated {
|
||||
opacity: 0.5;
|
||||
position: relative;
|
||||
}
|
||||
.opponent-block.defeated::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, transparent 45%, rgba(255,70,70,0.3) 48%, rgba(255,70,70,0.3) 52%, transparent 55%);
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
.opponent-block.defeated::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
height: 3px;
|
||||
background: rgba(255,70,70,0.8);
|
||||
transform: translateY(-50%) rotate(-5deg);
|
||||
box-shadow: 0 0 10px rgba(255,70,70,0.6);
|
||||
pointer-events: none;
|
||||
z-index: 11;
|
||||
}
|
||||
.opponent-name { font-weight: 700; color: var(--cyan); margin-bottom: 0.35rem; font-size: 0.95rem; }
|
||||
.opponent-name.defeated-name {
|
||||
text-decoration: line-through;
|
||||
color: #666;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.defeated-badge {
|
||||
color: var(--red);
|
||||
font-size: 1.2em;
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
.opponent-stats { display: flex; gap: 0.75rem; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; }
|
||||
.opponent-stats.defeated-stats {
|
||||
opacity: 0.5;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.opponent-board { display: flex; flex-wrap: wrap; gap: 0.5rem; min-height: 80px; }
|
||||
|
||||
.battlefield { flex: 1; display: flex; flex-direction: column; justify-content: center; position: relative; z-index: 1; }
|
||||
@ -1739,31 +1801,102 @@ html, body {
|
||||
/* Frozen effect */
|
||||
.frozen, .frozen-card {
|
||||
position: relative;
|
||||
filter: brightness(0.85) saturate(0.7);
|
||||
border: 2px solid rgba(135,206,250,0.6) !important;
|
||||
box-shadow: 0 0 15px rgba(135,206,250,0.4), inset 0 0 20px rgba(135,206,250,0.2) !important;
|
||||
filter: brightness(0.75) saturate(0.6);
|
||||
border: 3px solid rgba(135,206,250,0.8) !important;
|
||||
box-shadow: 0 0 20px rgba(135,206,250,0.6),
|
||||
0 0 40px rgba(135,206,250,0.4),
|
||||
inset 0 0 30px rgba(135,206,250,0.3) !important;
|
||||
animation: frozenShimmer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes frozenShimmer {
|
||||
0%, 100% {
|
||||
filter: brightness(0.75) saturate(0.6);
|
||||
box-shadow: 0 0 20px rgba(135,206,250,0.6),
|
||||
0 0 40px rgba(135,206,250,0.4),
|
||||
inset 0 0 30px rgba(135,206,250,0.3);
|
||||
}
|
||||
50% {
|
||||
filter: brightness(0.85) saturate(0.7);
|
||||
box-shadow: 0 0 30px rgba(135,206,250,0.8),
|
||||
0 0 60px rgba(135,206,250,0.6),
|
||||
inset 0 0 40px rgba(135,206,250,0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.frozen-icon {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.8rem;
|
||||
z-index: 10;
|
||||
animation: frozenPulse 2s ease-in-out infinite;
|
||||
text-shadow: 0 0 10px rgba(135,206,250,0.8);
|
||||
text-shadow: 0 0 10px rgba(135,206,250,0.8),
|
||||
0 0 20px rgba(135,206,250,0.6);
|
||||
}
|
||||
@keyframes frozenPulse {
|
||||
0%, 100% { opacity: 0.8; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.1); }
|
||||
0%, 100% { opacity: 0.9; transform: scale(1); }
|
||||
50% { opacity: 1; transform: scale(1.3); }
|
||||
}
|
||||
.frozen-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(135,206,250,0.1) 0%, rgba(70,130,180,0.1) 100%);
|
||||
border-radius: 10px;
|
||||
inset: -3px;
|
||||
background: linear-gradient(135deg,
|
||||
rgba(135,206,250,0.2) 0%,
|
||||
rgba(70,130,180,0.3) 25%,
|
||||
rgba(135,206,250,0.2) 50%,
|
||||
rgba(70,130,180,0.3) 75%,
|
||||
rgba(135,206,250,0.2) 100%);
|
||||
background-size: 200% 200%;
|
||||
border-radius: 12px;
|
||||
animation: frozenGradient 3s linear infinite;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes frozenGradient {
|
||||
0% {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 200%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Synergy bonuses */
|
||||
.has-synergy {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.has-synergy::after {
|
||||
content: '✨';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
font-size: 1.2rem;
|
||||
z-index: 15;
|
||||
animation: synergyGlow 2s ease-in-out infinite;
|
||||
filter: drop-shadow(0 0 5px rgba(255, 204, 0, 0.8));
|
||||
}
|
||||
|
||||
@keyframes synergyGlow {
|
||||
0%, 100% {
|
||||
opacity: 0.7;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.synergy-bonus {
|
||||
color: var(--amber-bright);
|
||||
font-size: 0.7em;
|
||||
font-weight: bold;
|
||||
margin-left: 2px;
|
||||
text-shadow: 0 0 5px rgba(255, 204, 0, 0.8);
|
||||
}
|
||||
|
||||
/* Fictional card marker */
|
||||
|
||||
473
server.js
473
server.js
@ -357,6 +357,10 @@ function endTurn(room) {
|
||||
}
|
||||
gameState.turn++;
|
||||
gameState.log.push({ type: 'turn', from: prev, to: next });
|
||||
|
||||
// Применяем синергии в начале каждого хода
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room);
|
||||
startTurnTimer(room);
|
||||
broadcastGameState(room);
|
||||
@ -514,16 +518,22 @@ function runBattlecry(room, card, playerIndex) {
|
||||
} else if (id === 'heal_hero_5') {
|
||||
p.health = Math.min(30, p.health + 5);
|
||||
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_5', playerIndex });
|
||||
} else if (id === 'return_hand_enemy' && enemies.length) {
|
||||
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
|
||||
const enemyIdx = gameState.players.indexOf(enemy);
|
||||
if (enemy.board && enemy.board.length > 0) {
|
||||
const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
|
||||
const boardIdx = enemy.board.indexOf(target);
|
||||
enemy.board.splice(boardIdx, 1);
|
||||
enemy.hand.push(target.cardId);
|
||||
gameState.log.push({ type: 'battlecry', effect: 'return_hand_enemy', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
|
||||
} else if (id === 'return_hand_enemy') {
|
||||
// Для Ezra Bridger требуется выбор конкретного игрока
|
||||
// Отправляем запрос клиенту для выбора цели
|
||||
const socket = io.sockets.sockets.get(p.id);
|
||||
if (socket && enemies.length > 0) {
|
||||
socket.emit('battlecryTargetRequest', {
|
||||
battlecryId: 'return_hand_enemy',
|
||||
cardId: card.id || Object.keys(cardDb).find(k => cardDb[k] === card),
|
||||
availableTargets: enemies.map((e, idx) => ({
|
||||
playerIndex: gameState.players.indexOf(e),
|
||||
playerName: e.name || `Игрок ${gameState.players.indexOf(e) + 1}`,
|
||||
hasMinions: e.board && e.board.length > 0
|
||||
}))
|
||||
});
|
||||
}
|
||||
return; // Не выполняем сразу, ждём выбора цели
|
||||
} else if (id === 'destroy_strongest_enemy' && enemies.length) {
|
||||
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
|
||||
const enemyIdx = gameState.players.indexOf(enemy);
|
||||
@ -757,11 +767,375 @@ function playCard(room, socketId, handIndex, boardPos) {
|
||||
};
|
||||
p.board.splice(typeof boardPos === 'number' ? boardPos : p.board.length, 0, minion);
|
||||
gameState.log.push({ type: 'play', playerIndex: pi, cardId: cid, minionId: minion.id });
|
||||
if (card.battlecryId) runBattlecry(room, card, pi);
|
||||
|
||||
// Для battlecry, требующих выбора цели (Ezra Bridger), отправляем запрос клиенту
|
||||
if (card.battlecryId === 'return_hand_enemy') {
|
||||
const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);
|
||||
if (enemies.length > 0) {
|
||||
const socket = io.sockets.sockets.get(p.id);
|
||||
if (socket) {
|
||||
// Сохраняем информацию о миньоне для последующего выполнения battlecry
|
||||
minion.pendingBattlecry = { battlecryId: card.battlecryId, cardId: cid };
|
||||
socket.emit('battlecryTargetRequest', {
|
||||
battlecryId: 'return_hand_enemy',
|
||||
cardId: cid,
|
||||
minionId: minion.id,
|
||||
availableTargets: enemies.flatMap(enemy => {
|
||||
const enemyIdx = gameState.players.indexOf(enemy);
|
||||
if (!enemy.board || enemy.board.length === 0) return [];
|
||||
return enemy.board.map((m, boardIdx) => ({
|
||||
playerIndex: enemyIdx,
|
||||
boardIndex: boardIdx,
|
||||
playerName: enemy.name || `Игрок ${enemyIdx + 1}`,
|
||||
minionName: cardDb[m.cardId]?.name || m.cardId
|
||||
}));
|
||||
})
|
||||
});
|
||||
broadcastGameState(room);
|
||||
return; // Не применяем синергии пока, ждём выбора цели
|
||||
}
|
||||
}
|
||||
} else if (card.battlecryId) {
|
||||
runBattlecry(room, card, pi);
|
||||
}
|
||||
|
||||
// Применяем синергии после размещения карты
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room);
|
||||
broadcastGameState(room);
|
||||
}
|
||||
|
||||
// Функция для применения синергий между картами
|
||||
function applySynergies(room) {
|
||||
const gameState = room.gameState;
|
||||
const cardDb = require('./cards.js');
|
||||
|
||||
gameState.players.forEach((player, playerIndex) => {
|
||||
if (!player.board || player.board.length === 0) return;
|
||||
|
||||
// Сбрасываем все бонусы синергий перед пересчётом
|
||||
player.board.forEach(m => {
|
||||
m.synergyAttackBonus = 0;
|
||||
m.synergyHealthBonus = 0;
|
||||
});
|
||||
|
||||
player.board.forEach((minion, idx) => {
|
||||
const card = cardDb[minion.cardId];
|
||||
if (!card) return;
|
||||
|
||||
// Дарт Вейдер даёт +1/+1 всем штурмовикам и клонам
|
||||
if (card.id === 'vader' || card.name === 'Darth Vader') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'stormtrooper' || otherCard.name === 'Stormtrooper' ||
|
||||
otherCard.id === 'clone_trooper' || otherCard.name === 'Clone Trooper' ||
|
||||
otherCard.id === 'clone_commando' || otherCard.name === 'Clone Commando' ||
|
||||
otherCard.id === 'arc_trooper' || otherCard.name === 'ARC Trooper')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Люк Скайуокер даёт +1/+1 всем повстанцам
|
||||
if (card.id === 'luke' || card.name === 'Luke Skywalker') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && otherCard.faction === 'rebellion' && otherCard.type === 'minion') {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Император Палпатин даёт +1/+1 всем ситхам и имперским картам
|
||||
if (card.id === 'palpatine' || card.name === 'Emperor Palpatine') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.faction === 'empire' ||
|
||||
otherCard.name?.includes('Darth') || otherCard.id === 'maul' ||
|
||||
otherCard.id === 'vader' || otherCard.id === 'dooku' || otherCard.id === 'kylo')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Хан Соло и Чубакка дают друг другу +1/+1
|
||||
if (card.id === 'han' || card.name === 'Han Solo') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'chewie' || otherCard.name === 'Chewbacca')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (card.id === 'chewie' || card.name === 'Chewbacca') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'han' || otherCard.name === 'Han Solo')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// R2-D2 и C-3PO дают друг другу +1/+1
|
||||
if (card.id === 'r2d2' || card.name === 'R2-D2') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'c3po' || otherCard.name === 'C-3PO')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (card.id === 'c3po' || card.name === 'C-3PO') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'r2d2' || otherCard.name === 'R2-D2')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Лея даёт +1/+1 Люку
|
||||
if (card.id === 'leia' || card.name === 'Princess Leia') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'luke' || otherCard.name === 'Luke Skywalker')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Люк даёт +1/+1 Лее
|
||||
if (card.id === 'luke' || card.name === 'Luke Skywalker') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'leia' || otherCard.name === 'Princess Leia')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Оби-Ван даёт +1/+1 Энакину/Анакину
|
||||
if (card.id === 'obiwan' || card.name === 'Obi-Wan Kenobi') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'anakin' || otherCard.name === 'Anakin Skywalker')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Энакин даёт +1/+1 Оби-Вану
|
||||
if (card.id === 'anakin' || card.name === 'Anakin Skywalker') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'obiwan' || otherCard.name === 'Obi-Wan Kenobi')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Йода даёт +1/+1 всем джедаям
|
||||
if (card.id === 'yoda' || card.name === 'Yoda') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && otherCard.faction === 'rebellion' &&
|
||||
(otherCard.name?.includes('Jedi') || otherCard.id === 'luke' ||
|
||||
otherCard.id === 'obiwan' || otherCard.id === 'anakin' ||
|
||||
otherCard.id === 'ahsoka' || otherCard.id === 'mace' ||
|
||||
otherCard.id === 'quigon' || otherCard.id === 'plo_koon' ||
|
||||
otherCard.id === 'ki_adi' || otherCard.id === 'aayla' ||
|
||||
otherCard.id === 'shaak_ti' || otherCard.id === 'kanan' ||
|
||||
otherCard.id === 'ezra' || otherCard.id === 'cal_kestis')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Боба Фетт даёт +1/+1 Джанго Фетту
|
||||
if (card.id === 'boba' || card.name === 'Boba Fett') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'jango' || otherCard.name === 'Jango Fett')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Джанго Фетт даёт +1/+1 Бобе Фетту
|
||||
if (card.id === 'jango' || card.name === 'Jango Fett') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'boba' || otherCard.name === 'Boba Fett')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Эйсока даёт +1/+1 Рексу
|
||||
if (card.id === 'ahsoka' || card.name === 'Ahsoka Tano') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'rex' || otherCard.name === 'Captain Rex')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Рекс даёт +1/+1 Эйсоке
|
||||
if (card.id === 'rex' || card.name === 'Captain Rex') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'ahsoka' || otherCard.name === 'Ahsoka Tano')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Мандалорец даёт +1/+1 Грогу
|
||||
if (card.id === 'mando' || card.name === 'Din Djarin') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'grogu' || otherCard.name === 'Grogu')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Грогу даёт +1/+1 Мандалорцу
|
||||
if (card.id === 'grogu' || card.name === 'Grogu') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'mando' || otherCard.name === 'Din Djarin')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Таркин даёт +1/+1 имперским картам
|
||||
if (card.id === 'tarkin' || card.name === 'Grand Moff Tarkin') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && otherCard.faction === 'empire' && otherCard.type === 'minion') {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Дарт Мол даёт +1/+1 Сэвиджу Оппрессу
|
||||
if (card.id === 'maul' || card.name === 'Darth Maul') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'savage' || otherCard.name === 'Savage Opress')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Сэвидж Оппресс даёт +1/+1 Дарту Молу
|
||||
if (card.id === 'savage' || card.name === 'Savage Opress') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'maul' || otherCard.name === 'Darth Maul')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Падме даёт +1/+1 Энакину
|
||||
if (card.id === 'padme' || card.name === 'Padmé Amidala') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'anakin' || otherCard.name === 'Anakin Skywalker')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Энакин даёт +1/+1 Падме
|
||||
if (card.id === 'anakin' || card.name === 'Anakin Skywalker') {
|
||||
player.board.forEach((other, otherIdx) => {
|
||||
if (idx !== otherIdx) {
|
||||
const otherCard = cardDb[other.cardId];
|
||||
if (otherCard && (otherCard.id === 'padme' || otherCard.name === 'Padmé Amidala')) {
|
||||
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
|
||||
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardIndex) {
|
||||
const gameState = room.gameState;
|
||||
const pi = findPlayerIndex(room, socketId);
|
||||
@ -944,6 +1318,9 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
||||
|
||||
p.mana -= cost;
|
||||
p.hand.splice(handIndex, 1);
|
||||
// Применяем синергии после размещения карты
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room);
|
||||
broadcastGameState(room);
|
||||
}
|
||||
@ -1003,6 +1380,15 @@ function stealCardsFromDeck(room, socketId, handIndex, targetPlayerIndex, cardIn
|
||||
stolenCount: stolenCards.length
|
||||
});
|
||||
|
||||
// Применяем синергии после изменений на доске
|
||||
applySynergies(room);
|
||||
|
||||
// Применяем синергии после боя
|
||||
applySynergies(room);
|
||||
|
||||
// Применяем синергии после изменений на доске
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room);
|
||||
broadcastGameState(room);
|
||||
}
|
||||
@ -1038,6 +1424,12 @@ function heroAbility(room, socketId, targetPlayerIndex, targetBoardIndex) {
|
||||
gameState.players.forEach((pl) => {
|
||||
pl.board = pl.board.filter((min) => min.health > 0);
|
||||
});
|
||||
// Применяем синергии после изменений на доске
|
||||
applySynergies(room);
|
||||
|
||||
// Применяем синергии после изменений на доске
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room);
|
||||
broadcastGameState(room);
|
||||
}
|
||||
@ -1054,8 +1446,12 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
||||
const targetPlayer = gameState.players[targetPlayerIndex];
|
||||
if (!targetPlayer) return;
|
||||
|
||||
// Применяем синергии перед расчётом урона
|
||||
applySynergies(room);
|
||||
|
||||
if (targetBoardIndex === -1) {
|
||||
targetPlayer.health = Math.max(0, targetPlayer.health - attacker.attack);
|
||||
const attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
|
||||
targetPlayer.health = Math.max(0, targetPlayer.health - attackerAttack);
|
||||
gameState.log.push({
|
||||
type: 'attackHero',
|
||||
fromPlayer: pi,
|
||||
@ -1063,12 +1459,23 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
||||
attackerMinionId: attacker.id,
|
||||
damage: attacker.attack,
|
||||
});
|
||||
// Применяем синергии перед расчётом урона
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room); // Проверяем после атаки по герою
|
||||
} else {
|
||||
const target = targetPlayer.board[targetBoardIndex];
|
||||
if (!target) return;
|
||||
target.health -= attacker.attack;
|
||||
attacker.health -= target.attack;
|
||||
|
||||
// Применяем синергии перед расчётом урона
|
||||
applySynergies(room);
|
||||
|
||||
// Учитываем бонусы синергий при расчёте урона (бонусы уже применены в applySynergies)
|
||||
const attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
|
||||
const targetAttack = target.attack + (target.synergyAttackBonus || 0);
|
||||
|
||||
target.health -= attackerAttack;
|
||||
attacker.health -= targetAttack;
|
||||
const attackerDied = attacker.health <= 0;
|
||||
const targetDied = target.health <= 0;
|
||||
gameState.log.push({
|
||||
@ -1251,6 +1658,46 @@ io.on('connection', (socket) => {
|
||||
stealCardsFromDeck(room, socket.id, data.handIndex, data.targetPlayerIndex, data.cardIndices);
|
||||
});
|
||||
|
||||
socket.on('battlecryTarget', (data) => {
|
||||
const room = getRoomBySocket(socket.id);
|
||||
if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
|
||||
const gameState = room.gameState;
|
||||
const pi = findPlayerIndex(room, socket.id);
|
||||
if (pi < 0) return;
|
||||
const p = gameState.players[pi];
|
||||
if (p.health <= 0 || p.isDead) return;
|
||||
|
||||
if (data.battlecryId === 'return_hand_enemy') {
|
||||
const targetPlayerIndex = data.targetPlayerIndex;
|
||||
const targetBoardIndex = data.targetBoardIndex;
|
||||
const targetPlayer = gameState.players[targetPlayerIndex];
|
||||
|
||||
if (!targetPlayer || targetPlayerIndex === pi || targetPlayer.health <= 0) return;
|
||||
if (!targetPlayer.board || targetPlayer.board.length === 0) return;
|
||||
if (targetBoardIndex == null || targetBoardIndex < 0 || targetBoardIndex >= targetPlayer.board.length) return;
|
||||
|
||||
const target = targetPlayer.board[targetBoardIndex];
|
||||
targetPlayer.board.splice(targetBoardIndex, 1);
|
||||
if (targetPlayer.hand.length < 10) {
|
||||
targetPlayer.hand.push(target.cardId);
|
||||
}
|
||||
|
||||
// Удаляем pendingBattlecry с миньона
|
||||
const playedMinion = p.board?.find(m => m.pendingBattlecry && m.pendingBattlecry.battlecryId === 'return_hand_enemy');
|
||||
if (playedMinion) {
|
||||
delete playedMinion.pendingBattlecry;
|
||||
}
|
||||
|
||||
gameState.log.push({ type: 'battlecry', effect: 'return_hand_enemy', fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex });
|
||||
|
||||
// Применяем синергии после изменений на доске
|
||||
applySynergies(room);
|
||||
|
||||
checkGameOver(room);
|
||||
broadcastGameState(room);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('resetToLobby', () => {
|
||||
const room = getRoomBySocket(socket.id);
|
||||
if (!room || !room.gameState || !room.gameState.players?.length) return;
|
||||
|
||||
Reference in New Issue
Block a user