diff --git a/cards.js b/cards.js index 73badc6..3a383f5 100644 --- a/cards.js +++ b/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: 'Уничтожь вражеского миньона с наибольшей атакой.', diff --git a/public/game.js b/public/game.js index 1c7b731..6b4b86a 100644 --- a/public/game.js +++ b/public/game.js @@ -14,6 +14,7 @@ let spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' }; let heroAbilityMode = { active: false }; let stealCardsMode = { active: false, handIndex: -1, targetPlayerIndex: null, targetDeck: [], selectedIndices: [] }; + let battlecryTargetMode = { active: false, battlecryId: '', cardId: '', minionId: '', targetPlayerIndex: null, targetBoardIndex: null }; const seenMinions = new Set(); let lastHandLength = 0; let prevGameState = null; @@ -235,8 +236,28 @@ showStealCardsModal(gameState, data); }); + socket.on('battlecryTargetRequest', (data) => { + if (data.battlecryId === 'return_hand_enemy') { + // Показываем модальное окно для выбора противника и его миньона + showBattlecryTargetModal(gameState, data); + } + }); + return socket; } + + function showBattlecryTargetModal(state, data) { + battlecryTargetMode = { active: true, battlecryId: data.battlecryId, cardId: data.cardId, minionId: data.minionId }; + + // Активируем режим выбора цели + spellMode = { active: true, handIndex: -1, cardId: data.cardId, spellTarget: 'enemy_minion', battlecryMode: true, minionId: data.minionId }; + $('spell-mode')?.classList.remove('hidden'); + const spellModeText = $('spell-mode')?.querySelector('p'); + if (spellModeText) { + spellModeText.textContent = 'Выберите миньона противника для возврата в руку'; + } + renderGame(state); + } function renderGameEnded(state) { const badge = $('turn-badge'); @@ -274,6 +295,30 @@ modal.classList.remove('hidden'); } + function showTurnNotification(state) { + const notification = $('turn-notification'); + const notificationText = $('turn-notification-text'); + if (!notification || !notificationText) return; + + const currentPlayer = state.players[state.currentPlayerIndex]; + const isYourTurn = state.currentPlayerIndex === state.yourIndex; + const playerName = currentPlayer?.name || `Игрок ${state.currentPlayerIndex + 1}`; + + if (isYourTurn) { + notificationText.textContent = 'ВАШ ХОД'; + notificationText.parentElement.classList.add('your-turn'); + } else { + notificationText.textContent = `ХОД: ${playerName.toUpperCase()}`; + notificationText.parentElement.classList.remove('your-turn'); + } + + // Показываем нотификацию на 2 секунды + notification.classList.add('show'); + setTimeout(() => { + notification.classList.remove('show'); + }, 2000); + } + function renderBoards(state) { const you = state.players[yourIndex]; if (!you) return; @@ -284,17 +329,18 @@ .map((p, i) => { if (i === yourIndex) return ''; const isCurrent = state.currentPlayerIndex === i; + const isDead = p.health <= 0 || p.isDead; const name = p.name || 'Игрок ' + (i + 1); const minions = (p.board || []).map((m, j) => renderBoardMinion(m, i, j, state, true, false)); const heroBar = renderHeroTarget(i, state); const heroDrop = renderHeroDropZone(i, state); const canSteal = state.currentPlayerIndex === state.yourIndex && spellMode.active && spellMode.cardId && cardDb[spellMode.cardId]?.spellEffect === 'steal_cards'; return ` -
-
${escapeHtml(name)}
-
- ❤ ${p.health} - 🔵 ${p.mana}/${p.maxMana} +
+
${escapeHtml(name)}${isDead ? ' ' : ''}
+
+ ❤ ${p.health ?? 30} + 🔵 ${p.mana ?? 0}/${p.maxMana ?? 0} ${p.deck && p.deck.length > 0 ? `📚 ${p.deck.length}` : ''}
${heroDrop}${heroBar}${minions.join('')}
@@ -612,9 +658,19 @@ const you = state.players[yourIndex]; if (!you) return; - $('your-mana').textContent = you.mana; - $('your-max-mana').textContent = you.maxMana; - $('your-health').textContent = you.health; + $('your-mana').textContent = you.mana ?? 0; + $('your-max-mana').textContent = you.maxMana ?? 0; + const healthEl = $('your-health'); + if (healthEl) { + const newHealth = you.health ?? 30; + const oldHealth = parseInt(healthEl.textContent) || 30; + healthEl.textContent = newHealth; + // Добавляем визуальный эффект при изменении HP + if (newHealth !== oldHealth) { + healthEl.classList.add('health-changed'); + setTimeout(() => healthEl.classList.remove('health-changed'), 500); + } + } $('your-deck').textContent = state.yourDeckCount ?? you.deck?.length ?? 0; const lastLog = state.log && state.log.length ? state.log[state.log.length - 1] : null; const logEl = $('game-log'); @@ -660,6 +716,9 @@ badge.classList.toggle('your-turn', isYourTurn); badge.textContent = isYourTurn ? 'ВАШ ХОД' : 'Ход ' + (state.turn || 1); } + + // Показываем нотификацию о том, кто ходит + showTurnNotification(state); var skipBoardUpdate = false; if (lastLog?.type === 'attack' && lastLog.attackerMinionId && lastLog.targetMinionId && prevGameState && runCombatAnimation(state)) { @@ -810,11 +869,20 @@ const canAttack = !isOpponent && m.canAttack && isYourTurn && (!canAttackTwice || attacksUsed < 2); const attackable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && attackMode.attackerBoard === boardIndex; const targetable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && playerIndex !== state.yourIndex; + + // Учитываем бонусы синергий + const synergyAttack = m.synergyAttackBonus || 0; + const synergyHealth = m.synergyHealthBonus || 0; + const displayAttack = (m.attack || 0) + synergyAttack; + const displayHealth = (m.health || 0) + synergyHealth; + const hasSynergy = synergyAttack > 0 || synergyHealth > 0; + const cls = ['card-wrap']; if (canAttack && !attackMode.active) cls.push('attackable'); if (attackable) cls.push('attackable'); if (targetable) cls.push('targetable'); if (isOpponent && isYourTurn) cls.push('drop-target'); + if (hasSynergy) cls.push('has-synergy'); var spellTarget = false; if (spellMode.active && spellMode.spellTarget) { var st = spellMode.spellTarget; @@ -841,8 +909,8 @@ + '
' + escapeHtml(name) + '
' + textHtml + abilHtml + '
' - + '
Атака' + m.attack + '
' - + '
Здоровье' + m.health + '
' + + '
Атака' + displayAttack + (synergyAttack > 0 ? '+' + synergyAttack + '' : '') + '
' + + '
Здоровье' + displayHealth + (synergyHealth > 0 ? '+' + synergyHealth + '' : '') + '
' + '
' + '
' + '
'; @@ -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') { diff --git a/public/index.html b/public/index.html index 66f4450..65bd179 100644 --- a/public/index.html +++ b/public/index.html @@ -229,6 +229,13 @@ + + +