123
This commit is contained in:
42
cards.js
42
cards.js
@ -9,7 +9,7 @@ module.exports = {
|
|||||||
health: 5,
|
health: 5,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Jedi. Hope of the Rebellion.',
|
text: 'Jedi. Hope of the Rebellion. Даёт +1/+1 повстанцам и Лее.',
|
||||||
art: 'luke',
|
art: 'luke',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Нанеси 1 урона вражескому герою.',
|
battlecry: 'Нанеси 1 урона вражескому герою.',
|
||||||
@ -23,7 +23,7 @@ module.exports = {
|
|||||||
health: 8,
|
health: 8,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Sith Lord. Fear is his weapon.',
|
text: 'Sith Lord. Fear is his weapon. Даёт +1/+1 штурмовикам и клонам.',
|
||||||
art: 'vader',
|
art: 'vader',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
deathrattle: 'Нанеси 2 урона всем врагам.',
|
deathrattle: 'Нанеси 2 урона всем врагам.',
|
||||||
@ -37,7 +37,7 @@ module.exports = {
|
|||||||
health: 7,
|
health: 7,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Jedi Master. Size matters not.',
|
text: 'Jedi Master. Size matters not. Даёт +1/+1 джедаям.',
|
||||||
art: 'yoda',
|
art: 'yoda',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Дайте союзному миньону +1/+1.',
|
battlecry: 'Дайте союзному миньону +1/+1.',
|
||||||
@ -51,7 +51,7 @@ module.exports = {
|
|||||||
health: 4,
|
health: 4,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Leader. Diplomat. Hero.',
|
text: 'Leader. Diplomat. Hero. Даёт +1/+1 Люку.',
|
||||||
art: 'leia',
|
art: 'leia',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Лея Органа — принцесса Алдераана, сенатор Галактической Республики, дочь Энакина Скайуокера и Падме Амидалы, сестра-близнец Люка. Воспитана Бейлом и Брехой Органа. Лидер Альянса повстанцев, участвовала в уничтожении обеих «Звёзд Смерти». Обладала чувствительностью к Силе, обучалась у Люка. Генерал Сопротивления, мать Бена Соло (Кайло Рена). Погибла, использовав последние силы Силы для связи с сыном через галактику.',
|
bio: 'Лея Органа — принцесса Алдераана, сенатор Галактической Республики, дочь Энакина Скайуокера и Падме Амидалы, сестра-близнец Люка. Воспитана Бейлом и Брехой Органа. Лидер Альянса повстанцев, участвовала в уничтожении обеих «Звёзд Смерти». Обладала чувствительностью к Силе, обучалась у Люка. Генерал Сопротивления, мать Бена Соло (Кайло Рена). Погибла, использовав последние силы Силы для связи с сыном через галактику.',
|
||||||
@ -63,7 +63,7 @@ module.exports = {
|
|||||||
health: 4,
|
health: 4,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Smuggler. Shoot first.',
|
text: 'Smuggler. Shoot first. Даёт +1/+1 Чубакке.',
|
||||||
art: 'han',
|
art: 'han',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Хан Соло — контрабандист и пилот, владелец корабля «Тысячелетний сокол». Родился на Кореллии, служил в Имперском флоте, затем стал контрабандистом. Встретил Люка и Лею, присоединился к Альянсу повстанцев. Участвовал в уничтожении обеих «Звёзд Смерти». Муж Леи, отец Бена Соло. Был заморожен в карбоните Джаббой Хаттом, но выжил. Позже убит собственным сыном Кайло Реном, пытаясь вернуть его к свету. Его знаменитая фраза: «Стреляй первым».',
|
bio: 'Хан Соло — контрабандист и пилот, владелец корабля «Тысячелетний сокол». Родился на Кореллии, служил в Имперском флоте, затем стал контрабандистом. Встретил Люка и Лею, присоединился к Альянсу повстанцев. Участвовал в уничтожении обеих «Звёзд Смерти». Муж Леи, отец Бена Соло. Был заморожен в карбоните Джаббой Хаттом, но выжил. Позже убит собственным сыном Кайло Реном, пытаясь вернуть его к свету. Его знаменитая фраза: «Стреляй первым».',
|
||||||
@ -75,7 +75,7 @@ module.exports = {
|
|||||||
health: 5,
|
health: 5,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Wookiee. Loyal friend.',
|
text: 'Wookiee. Loyal friend. Даёт +1/+1 Хану Соло.',
|
||||||
art: 'chewie',
|
art: 'chewie',
|
||||||
bio: 'Чубакка — вуки с планеты Кашиик, первый помощник и лучший друг Хана Соло. Пережил рабство и Имперскую оккупацию родной планеты. Сражался в Войнах клонов и Гражданской войне. Долг жизни Люку Скайуокеру за спасение от рабства. Пилот «Тысячелетнего сокола», мастер-механик. Один из немногих, кто понимал язык вуки. Пережил все три трилогии, стал генералом Сопротивления.',
|
bio: 'Чубакка — вуки с планеты Кашиик, первый помощник и лучший друг Хана Соло. Пережил рабство и Имперскую оккупацию родной планеты. Сражался в Войнах клонов и Гражданской войне. Долг жизни Люку Скайуокеру за спасение от рабства. Пилот «Тысячелетнего сокола», мастер-механик. Один из немногих, кто понимал язык вуки. Пережил все три трилогии, стал генералом Сопротивления.',
|
||||||
},
|
},
|
||||||
@ -86,7 +86,7 @@ module.exports = {
|
|||||||
health: 3,
|
health: 3,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'neutral',
|
faction: 'neutral',
|
||||||
text: 'Astromech. Beep boop.',
|
text: 'Astromech. Beep boop. Даёт +1/+1 C-3PO.',
|
||||||
art: 'r2d2',
|
art: 'r2d2',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
deathrattle: 'Возьми карту.',
|
deathrattle: 'Возьми карту.',
|
||||||
@ -100,7 +100,7 @@ module.exports = {
|
|||||||
health: 4,
|
health: 4,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'neutral',
|
faction: 'neutral',
|
||||||
text: 'Protocol droid. Terrible at odds.',
|
text: 'Protocol droid. Terrible at odds. Даёт +1/+1 R2-D2.',
|
||||||
art: 'c3po',
|
art: 'c3po',
|
||||||
bio: 'C-3PO — протокольный дроид, созданный Энакином Скайуокером в детстве на Татуине из запчастей. Владеет более чем шестью миллионами форм общения. Служил Падме Амидале, затем Лее Органе. Вечный спутник и лучший друг R2-D2, хотя часто жалуется на его безрассудство. Труслив, но предан друзьям. Участвовал во всех важных событиях галактики. Его золотая обшивка стала символом надежды для повстанцев.',
|
bio: 'C-3PO — протокольный дроид, созданный Энакином Скайуокером в детстве на Татуине из запчастей. Владеет более чем шестью миллионами форм общения. Служил Падме Амидале, затем Лее Органе. Вечный спутник и лучший друг R2-D2, хотя часто жалуется на его безрассудство. Труслив, но предан друзьям. Участвовал во всех важных событиях галактики. Его золотая обшивка стала символом надежды для повстанцев.',
|
||||||
},
|
},
|
||||||
@ -111,7 +111,7 @@ module.exports = {
|
|||||||
health: 3,
|
health: 3,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Bounty Hunter. No disintegrations.',
|
text: 'Bounty Hunter. No disintegrations. Даёт +1/+1 Джанго Фетту.',
|
||||||
art: 'boba',
|
art: 'boba',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Боба Фетт — охотник за головами, генетическая копия (не клон) Джанго Фетта. Воспитан как сын Джанго, унаследовал его доспехи и корабль «Раб I». Один из самых опасных охотников за головами в галактике. Работал на Джаббу Хатта и Дарта Вейдера. Был проглочен сарлаком, но выжил благодаря доспехам. Позже стал лидером мандалорцев, приняв тёмный меч. Мастер тактики и выживания, уважаемый во всей галактике.',
|
bio: 'Боба Фетт — охотник за головами, генетическая копия (не клон) Джанго Фетта. Воспитан как сын Джанго, унаследовал его доспехи и корабль «Раб I». Один из самых опасных охотников за головами в галактике. Работал на Джаббу Хатта и Дарта Вейдера. Был проглочен сарлаком, но выжил благодаря доспехам. Позже стал лидером мандалорцев, приняв тёмный меч. Мастер тактики и выживания, уважаемый во всей галактике.',
|
||||||
@ -123,7 +123,7 @@ module.exports = {
|
|||||||
health: 6,
|
health: 6,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Sith. Unlimited power!',
|
text: 'Sith. Unlimited power! Даёт +1/+1 ситхам и имперцам.',
|
||||||
art: 'palpatine',
|
art: 'palpatine',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Нанеси 2 урона вражескому герою.',
|
battlecry: 'Нанеси 2 урона вражескому герою.',
|
||||||
@ -167,7 +167,7 @@ module.exports = {
|
|||||||
health: 6,
|
health: 6,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Jedi. Hello there.',
|
text: 'Jedi. Hello there. Даёт +1/+1 Энакину.',
|
||||||
art: 'obiwan',
|
art: 'obiwan',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Оби-Ван Кеноби — мастер-джедай, наставник Энакина Скайуокера и Люка Скайуокера. Ученик Квай-Гона Джинна, затем мастер Энакина. Участвовал в Войнах клонов, сражался с Дартом Молом, графом Дуку и генералом Гривусом. Победил Энакина на Мустафаре, но не смог убить его. Скрывался на Татуине, наблюдая за Люком. Погиб на «Звезде Смерти», став духом Силы и продолжив наставлять Люка. Его знаменитая фраза: «Привет там!» Его мудрость и преданность кодексу джедаев сделали его легендой.',
|
bio: 'Оби-Ван Кеноби — мастер-джедай, наставник Энакина Скайуокера и Люка Скайуокера. Ученик Квай-Гона Джинна, затем мастер Энакина. Участвовал в Войнах клонов, сражался с Дартом Молом, графом Дуку и генералом Гривусом. Победил Энакина на Мустафаре, но не смог убить его. Скрывался на Татуине, наблюдая за Люком. Погиб на «Звезде Смерти», став духом Силы и продолжив наставлять Люка. Его знаменитая фраза: «Привет там!» Его мудрость и преданность кодексу джедаев сделали его легендой.',
|
||||||
@ -179,7 +179,7 @@ module.exports = {
|
|||||||
health: 3,
|
health: 3,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Sith. Double-bladed saber.',
|
text: 'Sith. Double-bladed saber. Даёт +1/+1 Сэвиджу Оппрессу.',
|
||||||
art: 'maul',
|
art: 'maul',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Дарт Мол — тёмный лорд ситхов, зачак, ученик Дарта Сидиуса. Был известен своим двойным световым мечом и агрессивным стилем боя. Убил Квай-Гона Джинна на Набу, но был побеждён Оби-Ваном Кеноби и считался мёртвым. Выжил благодаря ненависти и был спасён братом Сэвиджем Оппрессом. Получил кибернетические ноги. Стал лидером преступного синдиката «Теней». Был убит Оби-Ваном на Татуине, защищая Люка. Его ненависть к Оби-Вану длилась десятилетия.',
|
bio: 'Дарт Мол — тёмный лорд ситхов, зачак, ученик Дарта Сидиуса. Был известен своим двойным световым мечом и агрессивным стилем боя. Убил Квай-Гона Джинна на Набу, но был побеждён Оби-Ваном Кеноби и считался мёртвым. Выжил благодаря ненависти и был спасён братом Сэвиджем Оппрессом. Получил кибернетические ноги. Стал лидером преступного синдиката «Теней». Был убит Оби-Ваном на Татуине, защищая Люка. Его ненависть к Оби-Вану длилась десятилетия.',
|
||||||
@ -366,7 +366,7 @@ module.exports = {
|
|||||||
health: 4,
|
health: 4,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Senator. Fighter for peace.',
|
text: 'Senator. Fighter for peace. Даёт +1/+1 Энакину.',
|
||||||
art: 'padme',
|
art: 'padme',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Падме Амидала — королева, затем сенатор Набу. Была избрана королевой в 14 лет. Встретила Энакина Скайуокера, когда он был мальчиком, затем вышла за него замуж тайно. Мать Люка и Леи. Была известна своей дипломатией и борьбой за мир. Участвовала в создании Альянса повстанцев. Умерла от разбитого сердца после того, как Энакин пал на тёмную сторону. Её смерть стала ключевым моментом в превращении Энакина в Вейдера. Её преданность демократии вдохновила многих.',
|
bio: 'Падме Амидала — королева, затем сенатор Набу. Была избрана королевой в 14 лет. Встретила Энакина Скайуокера, когда он был мальчиком, затем вышла за него замуж тайно. Мать Люка и Леи. Была известна своей дипломатией и борьбой за мир. Участвовала в создании Альянса повстанцев. Умерла от разбитого сердца после того, как Энакин пал на тёмную сторону. Её смерть стала ключевым моментом в превращении Энакина в Вейдера. Её преданность демократии вдохновила многих.',
|
||||||
@ -415,7 +415,7 @@ module.exports = {
|
|||||||
health: 3,
|
health: 3,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Bounty hunter. Clone template.',
|
text: 'Bounty hunter. Clone template. Даёт +1/+1 Бобе Фетту.',
|
||||||
art: 'jango',
|
art: 'jango',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Джанго Фетт — мандалорец, охотник за головами, один из лучших в галактике. Был выбран как генетический шаблон для армии клонов Республики. Получил в награду невидоизменённого клона, которого назвал Бобой и воспитал как сына. Участвовал в попытке убийства сенатора Падме Амидалы. Был убит Мейсом Винду на Джеонозисе, обезглавлен световым мечом. Его доспехи и корабль унаследовал Боба. Его навыки боя и тактики сделали его идеальным шаблоном для клонов.',
|
bio: 'Джанго Фетт — мандалорец, охотник за головами, один из лучших в галактике. Был выбран как генетический шаблон для армии клонов Республики. Получил в награду невидоизменённого клона, которого назвал Бобой и воспитал как сына. Участвовал в попытке убийства сенатора Падме Амидалы. Был убит Мейсом Винду на Джеонозисе, обезглавлен световым мечом. Его доспехи и корабль унаследовал Боба. Его навыки боя и тактики сделали его идеальным шаблоном для клонов.',
|
||||||
@ -578,7 +578,7 @@ module.exports = {
|
|||||||
health: 5,
|
health: 5,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Бывшая падаван Энакина. Белые клинки.',
|
text: 'Бывшая падаван Энакина. Белые клинки. Даёт +1/+1 Рексу.',
|
||||||
art: 'ahsoka',
|
art: 'ahsoka',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Нанеси 1 урона вражескому миньону.',
|
battlecry: 'Нанеси 1 урона вражескому миньону.',
|
||||||
@ -603,7 +603,7 @@ module.exports = {
|
|||||||
health: 4,
|
health: 4,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Клон-командир 501-го. Верен своим.',
|
text: 'Клон-командир 501-го. Верен своим. Даёт +1/+1 Эйсоке.',
|
||||||
art: 'rex',
|
art: 'rex',
|
||||||
bio: 'Капитан Рекс (CT-7567) — клон-командир, лидер 501-го легиона. Служил под командованием Энакина Скайуокера и Эйсоки Тано. Снял чип контроля после того, как Файвс раскрыл заговор. Выжил после Приказа 66 благодаря помощи Эйсоки. Присоединился к повстанцам и сражался на Эндоре. Его независимость и преданность товарищам делали его уникальным клоном. Дожил до старости, став символом того, что клоны были больше, чем просто солдаты. Его дружба с Эйсокой длилась десятилетия.',
|
bio: 'Капитан Рекс (CT-7567) — клон-командир, лидер 501-го легиона. Служил под командованием Энакина Скайуокера и Эйсоки Тано. Снял чип контроля после того, как Файвс раскрыл заговор. Выжил после Приказа 66 благодаря помощи Эйсоки. Присоединился к повстанцам и сражался на Эндоре. Его независимость и преданность товарищам делали его уникальным клоном. Дожил до старости, став символом того, что клоны были больше, чем просто солдаты. Его дружба с Эйсокой длилась десятилетия.',
|
||||||
},
|
},
|
||||||
@ -628,7 +628,7 @@ module.exports = {
|
|||||||
health: 5,
|
health: 5,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'neutral',
|
faction: 'neutral',
|
||||||
text: 'Мандалорец. Это путь.',
|
text: 'Мандалорец. Это путь. Даёт +1/+1 Грогу.',
|
||||||
art: 'mando',
|
art: 'mando',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
bio: 'Дин Джарин (Мандалорец) — охотник за головами, мандалорец из клана Джаринов. Воспитал Грогу (Малыша Йоду), став его приёмным отцом. Владеет тёмным мечом, легендарным оружием мандалорцев. Следовал кодексу мандалорцев: «Это путь». Его доспехи из бескара были семейной реликвией. Стал лидером мандалорцев, объединив различные кланы. Его преданность Грогу и кодексу мандалорцев сделали его легендой. Один из величайших мандалорцев своего времени.',
|
bio: 'Дин Джарин (Мандалорец) — охотник за головами, мандалорец из клана Джаринов. Воспитал Грогу (Малыша Йоду), став его приёмным отцом. Владеет тёмным мечом, легендарным оружием мандалорцев. Следовал кодексу мандалорцев: «Это путь». Его доспехи из бескара были семейной реликвией. Стал лидером мандалорцев, объединив различные кланы. Его преданность Грогу и кодексу мандалорцев сделали его легендой. Один из величайших мандалорцев своего времени.',
|
||||||
@ -640,7 +640,7 @@ module.exports = {
|
|||||||
health: 3,
|
health: 3,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'neutral',
|
faction: 'neutral',
|
||||||
text: 'Малыш Йода. Сила сильна в нём.',
|
text: 'Малыш Йода. Сила сильна в нём. Даёт +1/+1 Мандалорцу.',
|
||||||
art: 'grogu',
|
art: 'grogu',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
deathrattle: 'Восстанови 2 здоровья своему герою.',
|
deathrattle: 'Восстанови 2 здоровья своему герою.',
|
||||||
@ -1308,7 +1308,7 @@ module.exports = {
|
|||||||
health: 6,
|
health: 6,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'rebellion',
|
faction: 'rebellion',
|
||||||
text: 'Избранный. Может пасть на тёмную сторону.',
|
text: 'Избранный. Может пасть на тёмную сторону. Даёт +1/+1 Оби-Вану и Падме.',
|
||||||
art: 'anakin',
|
art: 'anakin',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Уничтожь вражеского миньона с атакой 4 или меньше.',
|
battlecry: 'Уничтожь вражеского миньона с атакой 4 или меньше.',
|
||||||
@ -1423,7 +1423,7 @@ module.exports = {
|
|||||||
health: 5,
|
health: 5,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Брат Мола. Сильный, но хрупкий.',
|
text: 'Брат Мола. Сильный, но хрупкий. Даёт +1/+1 Дарту Молу.',
|
||||||
art: 'savage',
|
art: 'savage',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Уничтожь вражеского миньона с атакой 2 или меньше.',
|
battlecry: 'Уничтожь вражеского миньона с атакой 2 или меньше.',
|
||||||
@ -1453,7 +1453,7 @@ module.exports = {
|
|||||||
health: 6,
|
health: 6,
|
||||||
type: 'minion',
|
type: 'minion',
|
||||||
faction: 'empire',
|
faction: 'empire',
|
||||||
text: 'Стратег. Тактик Империи.',
|
text: 'Стратег. Тактик Империи. Даёт +1/+1 имперцам.',
|
||||||
art: 'tarkin',
|
art: 'tarkin',
|
||||||
legendary: true,
|
legendary: true,
|
||||||
battlecry: 'Уничтожь вражеского миньона с наибольшей атакой.',
|
battlecry: 'Уничтожь вражеского миньона с наибольшей атакой.',
|
||||||
|
|||||||
110
public/game.js
110
public/game.js
@ -14,6 +14,7 @@
|
|||||||
let spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
|
let spellMode = { active: false, handIndex: -1, cardId: '', spellTarget: '' };
|
||||||
let heroAbilityMode = { active: false };
|
let heroAbilityMode = { active: false };
|
||||||
let stealCardsMode = { active: false, handIndex: -1, targetPlayerIndex: null, targetDeck: [], selectedIndices: [] };
|
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();
|
const seenMinions = new Set();
|
||||||
let lastHandLength = 0;
|
let lastHandLength = 0;
|
||||||
let prevGameState = null;
|
let prevGameState = null;
|
||||||
@ -235,8 +236,28 @@
|
|||||||
showStealCardsModal(gameState, data);
|
showStealCardsModal(gameState, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('battlecryTargetRequest', (data) => {
|
||||||
|
if (data.battlecryId === 'return_hand_enemy') {
|
||||||
|
// Показываем модальное окно для выбора противника и его миньона
|
||||||
|
showBattlecryTargetModal(gameState, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return socket;
|
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) {
|
function renderGameEnded(state) {
|
||||||
const badge = $('turn-badge');
|
const badge = $('turn-badge');
|
||||||
@ -274,6 +295,30 @@
|
|||||||
modal.classList.remove('hidden');
|
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) {
|
function renderBoards(state) {
|
||||||
const you = state.players[yourIndex];
|
const you = state.players[yourIndex];
|
||||||
if (!you) return;
|
if (!you) return;
|
||||||
@ -284,17 +329,18 @@
|
|||||||
.map((p, i) => {
|
.map((p, i) => {
|
||||||
if (i === yourIndex) return '';
|
if (i === yourIndex) return '';
|
||||||
const isCurrent = state.currentPlayerIndex === i;
|
const isCurrent = state.currentPlayerIndex === i;
|
||||||
|
const isDead = p.health <= 0 || p.isDead;
|
||||||
const name = p.name || 'Игрок ' + (i + 1);
|
const name = p.name || 'Игрок ' + (i + 1);
|
||||||
const minions = (p.board || []).map((m, j) => renderBoardMinion(m, i, j, state, true, false));
|
const minions = (p.board || []).map((m, j) => renderBoardMinion(m, i, j, state, true, false));
|
||||||
const heroBar = renderHeroTarget(i, state);
|
const heroBar = renderHeroTarget(i, state);
|
||||||
const heroDrop = renderHeroDropZone(i, state);
|
const heroDrop = renderHeroDropZone(i, state);
|
||||||
const canSteal = state.currentPlayerIndex === state.yourIndex && spellMode.active && spellMode.cardId && cardDb[spellMode.cardId]?.spellEffect === 'steal_cards';
|
const canSteal = state.currentPlayerIndex === state.yourIndex && spellMode.active && spellMode.cardId && cardDb[spellMode.cardId]?.spellEffect === 'steal_cards';
|
||||||
return `
|
return `
|
||||||
<div class="opponent-block ${isCurrent ? 'current-turn' : ''} ${canSteal ? 'steal-target' : ''}" data-opponent-index="${i}" data-player-index="${i}">
|
<div class="opponent-block ${isCurrent ? 'current-turn' : ''} ${canSteal ? 'steal-target' : ''} ${isDead ? 'defeated' : ''}" data-opponent-index="${i}" data-player-index="${i}">
|
||||||
<div class="opponent-name">${escapeHtml(name)}</div>
|
<div class="opponent-name ${isDead ? 'defeated-name' : ''}">${escapeHtml(name)}${isDead ? ' <span class="defeated-badge">✝</span>' : ''}</div>
|
||||||
<div class="opponent-stats">
|
<div class="opponent-stats ${isDead ? 'defeated-stats' : ''}">
|
||||||
<span>❤ ${p.health}</span>
|
<span class="opponent-health">❤ ${p.health ?? 30}</span>
|
||||||
<span>🔵 ${p.mana}/${p.maxMana}</span>
|
<span>🔵 ${p.mana ?? 0}/${p.maxMana ?? 0}</span>
|
||||||
${p.deck && p.deck.length > 0 ? `<span>📚 ${p.deck.length}</span>` : ''}
|
${p.deck && p.deck.length > 0 ? `<span>📚 ${p.deck.length}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="opponent-board">${heroDrop}${heroBar}${minions.join('')}</div>
|
<div class="opponent-board">${heroDrop}${heroBar}${minions.join('')}</div>
|
||||||
@ -612,9 +658,19 @@
|
|||||||
const you = state.players[yourIndex];
|
const you = state.players[yourIndex];
|
||||||
if (!you) return;
|
if (!you) return;
|
||||||
|
|
||||||
$('your-mana').textContent = you.mana;
|
$('your-mana').textContent = you.mana ?? 0;
|
||||||
$('your-max-mana').textContent = you.maxMana;
|
$('your-max-mana').textContent = you.maxMana ?? 0;
|
||||||
$('your-health').textContent = you.health;
|
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;
|
$('your-deck').textContent = state.yourDeckCount ?? you.deck?.length ?? 0;
|
||||||
const lastLog = state.log && state.log.length ? state.log[state.log.length - 1] : null;
|
const lastLog = state.log && state.log.length ? state.log[state.log.length - 1] : null;
|
||||||
const logEl = $('game-log');
|
const logEl = $('game-log');
|
||||||
@ -660,6 +716,9 @@
|
|||||||
badge.classList.toggle('your-turn', isYourTurn);
|
badge.classList.toggle('your-turn', isYourTurn);
|
||||||
badge.textContent = isYourTurn ? 'ВАШ ХОД' : 'Ход ' + (state.turn || 1);
|
badge.textContent = isYourTurn ? 'ВАШ ХОД' : 'Ход ' + (state.turn || 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Показываем нотификацию о том, кто ходит
|
||||||
|
showTurnNotification(state);
|
||||||
|
|
||||||
var skipBoardUpdate = false;
|
var skipBoardUpdate = false;
|
||||||
if (lastLog?.type === 'attack' && lastLog.attackerMinionId && lastLog.targetMinionId && prevGameState && runCombatAnimation(state)) {
|
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 canAttack = !isOpponent && m.canAttack && isYourTurn && (!canAttackTwice || attacksUsed < 2);
|
||||||
const attackable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && attackMode.attackerBoard === boardIndex;
|
const attackable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && attackMode.attackerBoard === boardIndex;
|
||||||
const targetable = attackMode.active && attackMode.attackerPlayer === state.yourIndex && playerIndex !== state.yourIndex;
|
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'];
|
const cls = ['card-wrap'];
|
||||||
if (canAttack && !attackMode.active) cls.push('attackable');
|
if (canAttack && !attackMode.active) cls.push('attackable');
|
||||||
if (attackable) cls.push('attackable');
|
if (attackable) cls.push('attackable');
|
||||||
if (targetable) cls.push('targetable');
|
if (targetable) cls.push('targetable');
|
||||||
if (isOpponent && isYourTurn) cls.push('drop-target');
|
if (isOpponent && isYourTurn) cls.push('drop-target');
|
||||||
|
if (hasSynergy) cls.push('has-synergy');
|
||||||
var spellTarget = false;
|
var spellTarget = false;
|
||||||
if (spellMode.active && spellMode.spellTarget) {
|
if (spellMode.active && spellMode.spellTarget) {
|
||||||
var st = spellMode.spellTarget;
|
var st = spellMode.spellTarget;
|
||||||
@ -841,8 +909,8 @@
|
|||||||
+ '<div class="card-name">' + escapeHtml(name) + '</div>'
|
+ '<div class="card-name">' + escapeHtml(name) + '</div>'
|
||||||
+ textHtml + abilHtml
|
+ textHtml + abilHtml
|
||||||
+ '<div class="card-stats">'
|
+ '<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-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">' + m.health + '</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>'
|
||||||
+ '<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 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>';
|
+ '</div></div></div>';
|
||||||
@ -1248,11 +1316,31 @@
|
|||||||
|
|
||||||
$all('.spell-target, .drop-target-hero.spell-target').forEach(function (el) {
|
$all('.spell-target, .drop-target-hero.spell-target').forEach(function (el) {
|
||||||
el.onclick = function (e) {
|
el.onclick = function (e) {
|
||||||
if (!spellMode.active) return;
|
if (!spellMode.active && !battlecryTargetMode.active) return;
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
var tp = parseInt(el.dataset.dropPlayer ?? el.dataset.playerIndex, 10);
|
var tp = parseInt(el.dataset.dropPlayer ?? el.dataset.playerIndex, 10);
|
||||||
var tb = parseInt(el.dataset.dropBoard ?? el.dataset.boardIndex, 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];
|
const spellCard = cardDb[spellMode.cardId];
|
||||||
if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') {
|
if (spellCard && spellCard.spellEffect === 'steal_cards' && spellCard.spellTarget === 'enemy_player') {
|
||||||
|
|||||||
@ -229,6 +229,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div id="attack-mode" class="attack-mode hidden">
|
||||||
<p>Выберите цель для атаки (враг или его существо)</p>
|
<p>Выберите цель для атаки (враг или его существо)</p>
|
||||||
<button type="button" id="btn-cancel-attack" class="btn btn-ghost">Отмена</button>
|
<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;
|
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 { font-size: 0.9rem; color: #94a3b8; }
|
||||||
.deck-count .swg.deck-icon { font-size: 1rem; margin-right: 0.2rem; color: var(--gold); }
|
.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); }
|
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); }
|
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 { 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 { 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; }
|
.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; }
|
.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 effect */
|
||||||
.frozen, .frozen-card {
|
.frozen, .frozen-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
filter: brightness(0.85) saturate(0.7);
|
filter: brightness(0.75) saturate(0.6);
|
||||||
border: 2px solid rgba(135,206,250,0.6) !important;
|
border: 3px solid rgba(135,206,250,0.8) !important;
|
||||||
box-shadow: 0 0 15px rgba(135,206,250,0.4), inset 0 0 20px rgba(135,206,250,0.2) !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 {
|
.frozen-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
font-size: 1.5rem;
|
font-size: 1.8rem;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
animation: frozenPulse 2s ease-in-out infinite;
|
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 {
|
@keyframes frozenPulse {
|
||||||
0%, 100% { opacity: 0.8; transform: scale(1); }
|
0%, 100% { opacity: 0.9; transform: scale(1); }
|
||||||
50% { opacity: 1; transform: scale(1.1); }
|
50% { opacity: 1; transform: scale(1.3); }
|
||||||
}
|
}
|
||||||
.frozen-card::before {
|
.frozen-card::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: -3px;
|
||||||
background: linear-gradient(135deg, rgba(135,206,250,0.1) 0%, rgba(70,130,180,0.1) 100%);
|
background: linear-gradient(135deg,
|
||||||
border-radius: 10px;
|
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;
|
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 */
|
/* Fictional card marker */
|
||||||
|
|||||||
475
server.js
475
server.js
@ -357,6 +357,10 @@ function endTurn(room) {
|
|||||||
}
|
}
|
||||||
gameState.turn++;
|
gameState.turn++;
|
||||||
gameState.log.push({ type: 'turn', from: prev, to: next });
|
gameState.log.push({ type: 'turn', from: prev, to: next });
|
||||||
|
|
||||||
|
// Применяем синергии в начале каждого хода
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
checkGameOver(room);
|
checkGameOver(room);
|
||||||
startTurnTimer(room);
|
startTurnTimer(room);
|
||||||
broadcastGameState(room);
|
broadcastGameState(room);
|
||||||
@ -514,16 +518,22 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
} else if (id === 'heal_hero_5') {
|
} else if (id === 'heal_hero_5') {
|
||||||
p.health = Math.min(30, p.health + 5);
|
p.health = Math.min(30, p.health + 5);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_5', playerIndex });
|
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_5', playerIndex });
|
||||||
} else if (id === 'return_hand_enemy' && enemies.length) {
|
} else if (id === 'return_hand_enemy') {
|
||||||
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
|
// Для Ezra Bridger требуется выбор конкретного игрока
|
||||||
const enemyIdx = gameState.players.indexOf(enemy);
|
// Отправляем запрос клиенту для выбора цели
|
||||||
if (enemy.board && enemy.board.length > 0) {
|
const socket = io.sockets.sockets.get(p.id);
|
||||||
const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
|
if (socket && enemies.length > 0) {
|
||||||
const boardIdx = enemy.board.indexOf(target);
|
socket.emit('battlecryTargetRequest', {
|
||||||
enemy.board.splice(boardIdx, 1);
|
battlecryId: 'return_hand_enemy',
|
||||||
enemy.hand.push(target.cardId);
|
cardId: card.id || Object.keys(cardDb).find(k => cardDb[k] === card),
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'return_hand_enemy', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
|
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) {
|
} else if (id === 'destroy_strongest_enemy' && enemies.length) {
|
||||||
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
|
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
const enemyIdx = gameState.players.indexOf(enemy);
|
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);
|
p.board.splice(typeof boardPos === 'number' ? boardPos : p.board.length, 0, minion);
|
||||||
gameState.log.push({ type: 'play', playerIndex: pi, cardId: cid, minionId: minion.id });
|
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);
|
checkGameOver(room);
|
||||||
broadcastGameState(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) {
|
function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardIndex) {
|
||||||
const gameState = room.gameState;
|
const gameState = room.gameState;
|
||||||
const pi = findPlayerIndex(room, socketId);
|
const pi = findPlayerIndex(room, socketId);
|
||||||
@ -944,6 +1318,9 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
|||||||
|
|
||||||
p.mana -= cost;
|
p.mana -= cost;
|
||||||
p.hand.splice(handIndex, 1);
|
p.hand.splice(handIndex, 1);
|
||||||
|
// Применяем синергии после размещения карты
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
checkGameOver(room);
|
checkGameOver(room);
|
||||||
broadcastGameState(room);
|
broadcastGameState(room);
|
||||||
}
|
}
|
||||||
@ -1003,6 +1380,15 @@ function stealCardsFromDeck(room, socketId, handIndex, targetPlayerIndex, cardIn
|
|||||||
stolenCount: stolenCards.length
|
stolenCount: stolenCards.length
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Применяем синергии после изменений на доске
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
|
// Применяем синергии после боя
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
|
// Применяем синергии после изменений на доске
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
checkGameOver(room);
|
checkGameOver(room);
|
||||||
broadcastGameState(room);
|
broadcastGameState(room);
|
||||||
}
|
}
|
||||||
@ -1038,6 +1424,12 @@ function heroAbility(room, socketId, targetPlayerIndex, targetBoardIndex) {
|
|||||||
gameState.players.forEach((pl) => {
|
gameState.players.forEach((pl) => {
|
||||||
pl.board = pl.board.filter((min) => min.health > 0);
|
pl.board = pl.board.filter((min) => min.health > 0);
|
||||||
});
|
});
|
||||||
|
// Применяем синергии после изменений на доске
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
|
// Применяем синергии после изменений на доске
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
checkGameOver(room);
|
checkGameOver(room);
|
||||||
broadcastGameState(room);
|
broadcastGameState(room);
|
||||||
}
|
}
|
||||||
@ -1054,8 +1446,12 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
|||||||
const targetPlayer = gameState.players[targetPlayerIndex];
|
const targetPlayer = gameState.players[targetPlayerIndex];
|
||||||
if (!targetPlayer) return;
|
if (!targetPlayer) return;
|
||||||
|
|
||||||
|
// Применяем синергии перед расчётом урона
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
if (targetBoardIndex === -1) {
|
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({
|
gameState.log.push({
|
||||||
type: 'attackHero',
|
type: 'attackHero',
|
||||||
fromPlayer: pi,
|
fromPlayer: pi,
|
||||||
@ -1063,12 +1459,23 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
|||||||
attackerMinionId: attacker.id,
|
attackerMinionId: attacker.id,
|
||||||
damage: attacker.attack,
|
damage: attacker.attack,
|
||||||
});
|
});
|
||||||
checkGameOver(room); // Проверяем после атаки по герою
|
// Применяем синергии перед расчётом урона
|
||||||
|
applySynergies(room);
|
||||||
|
|
||||||
|
checkGameOver(room); // Проверяем после атаки по герою
|
||||||
} else {
|
} else {
|
||||||
const target = targetPlayer.board[targetBoardIndex];
|
const target = targetPlayer.board[targetBoardIndex];
|
||||||
if (!target) return;
|
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 attackerDied = attacker.health <= 0;
|
||||||
const targetDied = target.health <= 0;
|
const targetDied = target.health <= 0;
|
||||||
gameState.log.push({
|
gameState.log.push({
|
||||||
@ -1250,6 +1657,46 @@ io.on('connection', (socket) => {
|
|||||||
if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
|
if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
|
||||||
stealCardsFromDeck(room, socket.id, data.handIndex, data.targetPlayerIndex, data.cardIndices);
|
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', () => {
|
socket.on('resetToLobby', () => {
|
||||||
const room = getRoomBySocket(socket.id);
|
const room = getRoomBySocket(socket.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user