123
This commit is contained in:
125
cards.js
125
cards.js
@ -2796,4 +2796,129 @@ module.exports = {
|
|||||||
battlecryId: 'deal_2_minion',
|
battlecryId: 'deal_2_minion',
|
||||||
bio: 'Висас Марр — мираллука, бывшая ученица Дарта Нихилуса. Была единственной выжившей после уничтожения её родной планеты Катамии Нихилусом. Была послана убить Изгнанника-джедая, но была спасена и вернулась к свету. Её слепота и связь с Силой делали её уникальной. Её искупление стало важной частью её истории. Её навыки боя на световых мечах были впечатляющими.',
|
bio: 'Висас Марр — мираллука, бывшая ученица Дарта Нихилуса. Была единственной выжившей после уничтожения её родной планеты Катамии Нихилусом. Была послана убить Изгнанника-джедая, но была спасена и вернулась к свету. Её слепота и связь с Силой делали её уникальной. Её искупление стало важной частью её истории. Её навыки боя на световых мечах были впечатляющими.',
|
||||||
},
|
},
|
||||||
|
// Карты с хилом
|
||||||
|
field_heal: {
|
||||||
|
name: 'Полевой медик',
|
||||||
|
cost: 2,
|
||||||
|
attack: 1,
|
||||||
|
health: 3,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'В конце каждого хода восстанавливает +1 HP герою.',
|
||||||
|
art: 'medic',
|
||||||
|
fieldEffect: 'heal_1_per_turn',
|
||||||
|
bio: 'Медик, который лечит раненых на поле боя.',
|
||||||
|
},
|
||||||
|
instant_heal_5: {
|
||||||
|
name: 'Быстрое лечение',
|
||||||
|
cost: 2,
|
||||||
|
type: 'spell',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'Восстанови 5 HP герою.',
|
||||||
|
art: 'heal',
|
||||||
|
spellEffect: 'heal_hero_5',
|
||||||
|
spellTarget: 'none',
|
||||||
|
bio: 'Мгновенное восстановление здоровья.',
|
||||||
|
},
|
||||||
|
instant_heal_30pct: {
|
||||||
|
name: 'Регенерация',
|
||||||
|
cost: 3,
|
||||||
|
type: 'spell',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'Восстанови 30% HP герою.',
|
||||||
|
art: 'regen',
|
||||||
|
spellEffect: 'heal_hero_30pct',
|
||||||
|
spellTarget: 'none',
|
||||||
|
bio: 'Восстановление части здоровья.',
|
||||||
|
},
|
||||||
|
soul_heal: {
|
||||||
|
name: 'Душевное исцеление',
|
||||||
|
cost: 4,
|
||||||
|
attack: 2,
|
||||||
|
health: 4,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'За каждую убитую карту противника восстанавливает +1 HP герою.',
|
||||||
|
art: 'soul',
|
||||||
|
onEnemyDeath: 'heal_1_per_kill',
|
||||||
|
bio: 'Черпает силу из смерти врагов.',
|
||||||
|
},
|
||||||
|
// Карты с броней
|
||||||
|
shield_card: {
|
||||||
|
name: 'Щит',
|
||||||
|
cost: 3,
|
||||||
|
type: 'spell',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'Даёт +10 брони игроку.',
|
||||||
|
art: 'shield',
|
||||||
|
spellEffect: 'add_armor_10',
|
||||||
|
spellTarget: 'none',
|
||||||
|
bio: 'Защитный щит для героя.',
|
||||||
|
},
|
||||||
|
energy_shield: {
|
||||||
|
name: 'Энергетический щит',
|
||||||
|
cost: 4,
|
||||||
|
attack: 0,
|
||||||
|
health: 3,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'Поглощает первый урон каждого хода.',
|
||||||
|
art: 'energy',
|
||||||
|
shieldEffect: 'absorb_first_damage',
|
||||||
|
bio: 'Щит, который поглощает первый удар.',
|
||||||
|
},
|
||||||
|
jedi_barrier: {
|
||||||
|
name: 'Барьер джедая',
|
||||||
|
cost: 5,
|
||||||
|
attack: 2,
|
||||||
|
health: 5,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'rebellion',
|
||||||
|
text: 'Пока на поле джедай — броня восстанавливается на +2 за ход.',
|
||||||
|
art: 'barrier',
|
||||||
|
fieldEffect: 'armor_regen_2_if_jedi',
|
||||||
|
bio: 'Защита джедаев восстанавливается.',
|
||||||
|
},
|
||||||
|
droid_armor: {
|
||||||
|
name: 'Броня дроида',
|
||||||
|
cost: 3,
|
||||||
|
attack: 1,
|
||||||
|
health: 4,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'Мех-юниты имеют удвоенную броню.',
|
||||||
|
art: 'droid',
|
||||||
|
aura: 'double_armor_mechs',
|
||||||
|
bio: 'Усиленная защита для механических существ.',
|
||||||
|
},
|
||||||
|
imperial_shield_generator: {
|
||||||
|
name: 'Имперский Щитогенератор',
|
||||||
|
cost: 6,
|
||||||
|
attack: 0,
|
||||||
|
health: 6,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'empire',
|
||||||
|
text: 'Даёт +15 брони. Каждые 2 хода +5 брони. Урон по герою -50%.',
|
||||||
|
art: 'generator',
|
||||||
|
battlecry: 'add_armor_15',
|
||||||
|
fieldEffect: 'armor_regen_5_every_2_turns',
|
||||||
|
damageReduction: 0.5,
|
||||||
|
bio: 'Мощный генератор щитов Империи.',
|
||||||
|
},
|
||||||
|
// Карта героя с двойной атакой
|
||||||
|
double_strike_hero: {
|
||||||
|
name: 'Мастер двойного удара',
|
||||||
|
cost: 4,
|
||||||
|
attack: 6,
|
||||||
|
health: 2,
|
||||||
|
type: 'minion',
|
||||||
|
faction: 'neutral',
|
||||||
|
text: 'Может атаковать дважды, но урон делится. После двух ударов умирает.',
|
||||||
|
art: 'striker',
|
||||||
|
canAttackTwice: true,
|
||||||
|
divideDamage: true,
|
||||||
|
diesAfterAttacks: 2,
|
||||||
|
legendary: true,
|
||||||
|
bio: 'Герой, который может нанести два удара, но каждый удар слабее.',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -444,6 +444,7 @@
|
|||||||
<div class="opponent-name ${isDead ? 'defeated-name' : ''}">${escapeHtml(name)}${isDead ? ' <span class="defeated-badge">✝</span>' : ''}</div>
|
<div class="opponent-name ${isDead ? 'defeated-name' : ''}">${escapeHtml(name)}${isDead ? ' <span class="defeated-badge">✝</span>' : ''}</div>
|
||||||
<div class="opponent-stats ${isDead ? 'defeated-stats' : ''}">
|
<div class="opponent-stats ${isDead ? 'defeated-stats' : ''}">
|
||||||
<span class="opponent-health">❤ ${p.health ?? 30}</span>
|
<span class="opponent-health">❤ ${p.health ?? 30}</span>
|
||||||
|
${(p.armor || 0) > 0 ? `<span class="opponent-armor">🛡 ${p.armor}</span>` : ''}
|
||||||
<span>🔵 ${p.mana ?? 0}/${p.maxMana ?? 0}</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>
|
||||||
@ -779,6 +780,18 @@
|
|||||||
setTimeout(() => healthEl.classList.remove('health-changed'), 500);
|
setTimeout(() => healthEl.classList.remove('health-changed'), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Отображение брони
|
||||||
|
const armorEl = $('your-armor');
|
||||||
|
const armorDisplayEl = $('armor-display');
|
||||||
|
if (armorEl && armorDisplayEl) {
|
||||||
|
const armor = state.isSpectator ? 0 : (state.yourArmor || you?.armor || 0);
|
||||||
|
armorEl.textContent = armor;
|
||||||
|
if (armor > 0) {
|
||||||
|
armorDisplayEl.style.display = 'inline-flex';
|
||||||
|
} else {
|
||||||
|
armorDisplayEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
$('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;
|
||||||
|
|
||||||
|
|||||||
@ -197,6 +197,10 @@
|
|||||||
<i class="swg swg-deathstar health-icon" aria-hidden="true"></i>
|
<i class="swg swg-deathstar health-icon" aria-hidden="true"></i>
|
||||||
<span id="your-health">30</span>
|
<span id="your-health">30</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="armor-display" id="armor-display" style="display: none;">
|
||||||
|
<i class="swg swg-deathstar armor-icon" aria-hidden="true"></i>
|
||||||
|
<span id="your-armor">0</span>
|
||||||
|
</span>
|
||||||
<span class="deck-count"><i class="swg swg-galrep deck-icon" aria-hidden="true"></i><span id="your-deck">0</span> в колоде</span>
|
<span class="deck-count"><i class="swg swg-galrep deck-icon" aria-hidden="true"></i><span id="your-deck">0</span> в колоде</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
|
|||||||
@ -735,6 +735,9 @@ html, body {
|
|||||||
z-index: 11;
|
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-armor { color: #5eb3e8; font-weight: 600; }
|
||||||
|
.armor-display { color: #5eb3e8; font-weight: 600; margin-left: 0.5rem; }
|
||||||
|
.armor-icon { color: #5eb3e8; }
|
||||||
.opponent-name.defeated-name {
|
.opponent-name.defeated-name {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: #666;
|
color: #666;
|
||||||
@ -2421,6 +2424,7 @@ html, body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2431,18 +2435,25 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
min-height: 44px;
|
min-height: 40px;
|
||||||
padding: 0.85rem 1.5rem;
|
padding: 0.6rem 1.2rem;
|
||||||
font-size: 1rem;
|
font-size: 0.9rem;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-end-turn {
|
.btn-end-turn {
|
||||||
min-height: 48px;
|
min-height: 40px;
|
||||||
padding: 0.7rem 1.6rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 1.05rem;
|
font-size: 0.9rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 120px;
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-forge, .btn-hero-ability, .btn-draw-card {
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 0.45rem 0.9rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
min-width: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-instructions, .btn-settings {
|
.btn-instructions, .btn-settings {
|
||||||
@ -2454,19 +2465,43 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-wrap {
|
.card-wrap {
|
||||||
width: 140px;
|
width: 120px;
|
||||||
height: 196px;
|
height: 168px;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board .card-wrap {
|
.board .card-wrap {
|
||||||
width: 100px;
|
width: 85px;
|
||||||
height: 140px;
|
height: 119px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opponents .card-wrap {
|
.opponents .card-wrap {
|
||||||
width: 100px;
|
width: 85px;
|
||||||
height: 140px;
|
height: 119px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем текст на картах для средних мобильных */
|
||||||
|
.card-name {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-cost {
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stats {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stats .atk, .card-stats .hp {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
-webkit-line-clamp: 3 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opponents {
|
.opponents {
|
||||||
@ -2491,7 +2526,7 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hand .card-wrap {
|
.hand .card-wrap {
|
||||||
margin-left: -30px;
|
margin-left: -25px;
|
||||||
scroll-snap-align: start;
|
scroll-snap-align: start;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@ -2511,19 +2546,19 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.deck-fan {
|
.deck-fan {
|
||||||
width: 90px;
|
width: 80px;
|
||||||
height: 126px;
|
height: 112px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deck-stack {
|
.deck-stack {
|
||||||
width: 85px;
|
width: 75px;
|
||||||
height: 120px;
|
height: 105px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deck-stack .deck-card {
|
.deck-stack .deck-card {
|
||||||
width: 85px;
|
width: 75px;
|
||||||
height: 120px;
|
height: 105px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board {
|
.board {
|
||||||
@ -2704,54 +2739,224 @@ html, body {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем карты */
|
||||||
.card-wrap {
|
.card-wrap {
|
||||||
width: 120px;
|
width: 100px;
|
||||||
height: 168px;
|
height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.board .card-wrap {
|
.board .card-wrap {
|
||||||
width: 85px;
|
width: 70px;
|
||||||
height: 119px;
|
height: 98px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opponents .card-wrap {
|
.opponents .card-wrap {
|
||||||
width: 85px;
|
width: 70px;
|
||||||
height: 119px;
|
height: 98px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hand .card-wrap {
|
.hand .card-wrap {
|
||||||
margin-left: -35px;
|
margin-left: -25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hand-container {
|
.hand-container {
|
||||||
min-height: 160px;
|
min-height: 140px;
|
||||||
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hand {
|
||||||
|
gap: 0.2rem;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем колоду */
|
||||||
|
.deck-fan {
|
||||||
|
width: 70px;
|
||||||
|
height: 98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-stack {
|
||||||
|
width: 65px;
|
||||||
|
height: 91px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deck-stack .deck-card {
|
||||||
|
width: 65px;
|
||||||
|
height: 91px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем заголовок игры */
|
||||||
.game-header {
|
.game-header {
|
||||||
padding: 0.5rem;
|
padding: 0.4rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.75rem;
|
||||||
|
gap: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-left, .header-center, .header-right {
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Маленькие кнопки */
|
||||||
.btn {
|
.btn {
|
||||||
padding: 0.75rem 1.2rem;
|
padding: 0.4rem 0.7rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.75rem;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-end-turn {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
min-height: 36px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-forge, .btn-hero-ability, .btn-draw-card {
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
min-height: 32px;
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-instructions, .btn-settings {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем текст на картах */
|
||||||
|
.card-name {
|
||||||
|
font-size: 0.6rem !important;
|
||||||
|
line-height: 1.1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-cost {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
width: 18px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stats {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
gap: 0.3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stats .atk, .card-stats .hp {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
font-size: 0.55rem !important;
|
||||||
|
line-height: 1.2 !important;
|
||||||
|
-webkit-line-clamp: 3 !important;
|
||||||
|
margin-bottom: 0.2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-info {
|
||||||
|
padding: 0.3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем доски */
|
||||||
|
.board {
|
||||||
|
min-height: 100px;
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.your-board {
|
||||||
|
min-height: 90px;
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-block {
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-name {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-stats {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем статистику игрока */
|
||||||
|
#your-mana, #your-max-mana, #your-health {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#your-name {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем анонсы */
|
||||||
.attack-announcement {
|
.attack-announcement {
|
||||||
font-size: 1.5rem;
|
font-size: 1.2rem;
|
||||||
padding: 1.5rem 2rem;
|
padding: 1rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем модальные окна */
|
||||||
.modal {
|
.modal {
|
||||||
padding: 1rem;
|
padding: 0.75rem;
|
||||||
|
max-width: 95vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal h2 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal p {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Галерея карт */
|
||||||
.cards-gallery-grid {
|
.cards-gallery-grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(85px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gallery-card {
|
.gallery-card {
|
||||||
width: 85px !important;
|
width: 70px !important;
|
||||||
height: 119px !important;
|
height: 98px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем чат */
|
||||||
|
.chat-container {
|
||||||
|
width: calc(100vw - 10px);
|
||||||
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
max-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-content {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем кузницу */
|
||||||
|
.forge-sidebar {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forge-cards-list .card-wrap {
|
||||||
|
width: 80px;
|
||||||
|
height: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем лог игры */
|
||||||
|
#game-log {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уменьшаем таймер */
|
||||||
|
.turn-timer {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.3rem 0.6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
215
server.js
215
server.js
@ -52,6 +52,32 @@ function cleanupEmptyRooms() {
|
|||||||
// Периодическая очистка пустых комнат
|
// Периодическая очистка пустых комнат
|
||||||
setInterval(cleanupEmptyRooms, 30000); // каждые 30 секунд
|
setInterval(cleanupEmptyRooms, 30000); // каждые 30 секунд
|
||||||
|
|
||||||
|
// Функция для применения урона с учетом брони
|
||||||
|
function applyDamageToPlayer(player, damage) {
|
||||||
|
if (damage <= 0) return;
|
||||||
|
|
||||||
|
// Проверка на Droid Armor - удваиваем броню для мех-юнитов
|
||||||
|
// (это обрабатывается при создании карты, но можно добавить проверку здесь)
|
||||||
|
|
||||||
|
// Сначала урон идет на броню
|
||||||
|
if (player.armor > 0) {
|
||||||
|
const armorDamage = Math.min(player.armor, damage);
|
||||||
|
player.armor -= armorDamage;
|
||||||
|
damage -= armorDamage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Оставшийся урон идет на HP
|
||||||
|
if (damage > 0) {
|
||||||
|
player.health = Math.max(0, player.health - damage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для лечения игрока
|
||||||
|
function healPlayer(player, amount) {
|
||||||
|
if (amount <= 0) return;
|
||||||
|
player.health = Math.min(30, player.health + amount);
|
||||||
|
}
|
||||||
|
|
||||||
function shuffle(a) {
|
function shuffle(a) {
|
||||||
for (let i = a.length - 1; i > 0; i--) {
|
for (let i = a.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
@ -113,6 +139,7 @@ function initGame(room) {
|
|||||||
mana: 0,
|
mana: 0,
|
||||||
maxMana: 0,
|
maxMana: 0,
|
||||||
health: 30,
|
health: 30,
|
||||||
|
armor: 0, // Система брони
|
||||||
hero: (p.hero || ['luke', 'vader', 'yoda', 'leia', 'rey', 'kylo', 'mace'][i % 7]),
|
hero: (p.hero || ['luke', 'vader', 'yoda', 'leia', 'rey', 'kylo', 'mace'][i % 7]),
|
||||||
manualDrawUsed: false,
|
manualDrawUsed: false,
|
||||||
fatigueCounter: 0,
|
fatigueCounter: 0,
|
||||||
@ -140,6 +167,7 @@ function initGame(room) {
|
|||||||
mana: 0,
|
mana: 0,
|
||||||
maxMana: 0,
|
maxMana: 0,
|
||||||
health: 30,
|
health: 30,
|
||||||
|
armor: 0, // Система брони
|
||||||
hero: 'vader',
|
hero: 'vader',
|
||||||
manualDrawUsed: false,
|
manualDrawUsed: false,
|
||||||
fatigueCounter: 0,
|
fatigueCounter: 0,
|
||||||
@ -473,7 +501,7 @@ function endTurn(room) {
|
|||||||
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
||||||
if (enemies.length) {
|
if (enemies.length) {
|
||||||
const target = enemies[Math.floor(Math.random() * enemies.length)];
|
const target = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
target.health = Math.max(0, target.health - 3);
|
applyDamageToPlayer(target, 3);
|
||||||
gameState.log.push({ type: 'structure', effect: 'death_star_damage', fromPlayer: prev, toPlayer: gameState.players.indexOf(target), damage: 3 });
|
gameState.log.push({ type: 'structure', effect: 'death_star_damage', fromPlayer: prev, toPlayer: gameState.players.indexOf(target), damage: 3 });
|
||||||
}
|
}
|
||||||
} else if (card.name === 'Кантина Мос-Эйсли') {
|
} else if (card.name === 'Кантина Мос-Эйсли') {
|
||||||
@ -506,7 +534,7 @@ function endTurn(room) {
|
|||||||
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
||||||
if (enemies.length) {
|
if (enemies.length) {
|
||||||
const target = enemies[Math.floor(Math.random() * enemies.length)];
|
const target = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
target.health = Math.max(0, target.health - 2);
|
applyDamageToPlayer(target, 2);
|
||||||
gameState.log.push({ type: 'planet', effect: 'korriban_damage', fromPlayer: prev, toPlayer: gameState.players.indexOf(target), damage: 2 });
|
gameState.log.push({ type: 'planet', effect: 'korriban_damage', fromPlayer: prev, toPlayer: gameState.players.indexOf(target), damage: 2 });
|
||||||
}
|
}
|
||||||
} else if (card.name === 'Tatooine') {
|
} else if (card.name === 'Tatooine') {
|
||||||
@ -525,7 +553,7 @@ function endTurn(room) {
|
|||||||
} else if (card.name === 'Zakuul') {
|
} else if (card.name === 'Zakuul') {
|
||||||
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
||||||
enemies.forEach((enemy) => {
|
enemies.forEach((enemy) => {
|
||||||
enemy.health = Math.max(0, enemy.health - 3);
|
applyDamageToPlayer(enemy, 3);
|
||||||
});
|
});
|
||||||
gameState.log.push({ type: 'planet', effect: 'zakuul_damage', fromPlayer: prev });
|
gameState.log.push({ type: 'planet', effect: 'zakuul_damage', fromPlayer: prev });
|
||||||
} else if (card.name === 'Kamino') {
|
} else if (card.name === 'Kamino') {
|
||||||
@ -536,7 +564,7 @@ function endTurn(room) {
|
|||||||
} else if (card.name === 'Mustafar') {
|
} else if (card.name === 'Mustafar') {
|
||||||
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
|
||||||
enemies.forEach((enemy) => {
|
enemies.forEach((enemy) => {
|
||||||
enemy.health = Math.max(0, enemy.health - 1);
|
applyDamageToPlayer(enemy, 1);
|
||||||
if (enemy.board) {
|
if (enemy.board) {
|
||||||
enemy.board.forEach((min) => {
|
enemy.board.forEach((min) => {
|
||||||
min.health = Math.max(0, min.health - 1);
|
min.health = Math.max(0, min.health - 1);
|
||||||
@ -546,8 +574,33 @@ function endTurn(room) {
|
|||||||
});
|
});
|
||||||
gameState.log.push({ type: 'planet', effect: 'mustafar_damage', fromPlayer: prev });
|
gameState.log.push({ type: 'planet', effect: 'mustafar_damage', fromPlayer: prev });
|
||||||
} else if (card.name === 'Naboo') {
|
} else if (card.name === 'Naboo') {
|
||||||
prevPlayer.health = Math.min(30, prevPlayer.health + 2);
|
healPlayer(prevPlayer, 2);
|
||||||
gameState.log.push({ type: 'planet', effect: 'naboo_heal', playerIndex: prev });
|
gameState.log.push({ type: 'planet', effect: 'naboo_heal', playerIndex: prev });
|
||||||
|
}
|
||||||
|
// Обработка эффектов карт с хилом и броней
|
||||||
|
else if (card.fieldEffect === 'heal_1_per_turn') {
|
||||||
|
healPlayer(prevPlayer, 1);
|
||||||
|
gameState.log.push({ type: 'fieldEffect', effect: 'heal_1_per_turn', playerIndex: prev });
|
||||||
|
} else if (card.fieldEffect === 'armor_regen_2_if_jedi') {
|
||||||
|
// Проверяем, есть ли джедай на поле
|
||||||
|
const hasJedi = prevPlayer.board.some(min => {
|
||||||
|
const minCard = getCard(room, min.cardId);
|
||||||
|
return minCard && (minCard.name?.includes('Jedi') ||
|
||||||
|
minCard.id === 'luke' || minCard.id === 'obiwan' ||
|
||||||
|
minCard.id === 'ahsoka' || minCard.id === 'mace' ||
|
||||||
|
minCard.id === 'yoda' || minCard.id === 'anakin');
|
||||||
|
});
|
||||||
|
if (hasJedi) {
|
||||||
|
prevPlayer.armor = (prevPlayer.armor || 0) + 2;
|
||||||
|
gameState.log.push({ type: 'fieldEffect', effect: 'armor_regen_2_if_jedi', playerIndex: prev });
|
||||||
|
}
|
||||||
|
} else if (card.fieldEffect === 'armor_regen_5_every_2_turns') {
|
||||||
|
// Каждые 2 хода восстанавливаем +5 брони
|
||||||
|
const turnCount = gameState.turn || 0;
|
||||||
|
if (turnCount % 2 === 0) {
|
||||||
|
prevPlayer.armor = (prevPlayer.armor || 0) + 5;
|
||||||
|
gameState.log.push({ type: 'fieldEffect', effect: 'armor_regen_5_every_2_turns', playerIndex: prev });
|
||||||
|
}
|
||||||
} else if (card.name === 'Endor') {
|
} else if (card.name === 'Endor') {
|
||||||
if (prevPlayer.hand.length < 10) {
|
if (prevPlayer.hand.length < 10) {
|
||||||
prevPlayer.hand.push('ewok');
|
prevPlayer.hand.push('ewok');
|
||||||
@ -582,12 +635,16 @@ function endTurn(room) {
|
|||||||
m.attacksUsed = 0; // Сбрасываем счётчик атак для двойной атаки
|
m.attacksUsed = 0; // Сбрасываем счётчик атак для двойной атаки
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Сбрасываем флаг использования Energy Shield
|
||||||
|
if (card && card.shieldEffect === 'absorb_first_damage') {
|
||||||
|
m.shieldUsedThisTurn = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
np.manualDrawUsed = false;
|
np.manualDrawUsed = false;
|
||||||
np.heroAbilityUsed = false;
|
np.heroAbilityUsed = false;
|
||||||
if (np.deck.length === 0) {
|
if (np.deck.length === 0) {
|
||||||
np.fatigueCounter = (np.fatigueCounter || 0) + 1;
|
np.fatigueCounter = (np.fatigueCounter || 0) + 1;
|
||||||
np.health = Math.max(0, np.health - np.fatigueCounter);
|
applyDamageToPlayer(np, np.fatigueCounter);
|
||||||
gameState.log.push({ type: 'fatigue', playerIndex: next, damage: np.fatigueCounter });
|
gameState.log.push({ type: 'fatigue', playerIndex: next, damage: np.fatigueCounter });
|
||||||
} else {
|
} else {
|
||||||
const drawn = drawCards(np.deck, 1);
|
const drawn = drawCards(np.deck, 1);
|
||||||
@ -623,7 +680,7 @@ function manualDraw(room, socketId) {
|
|||||||
p.manualDrawUsed = true;
|
p.manualDrawUsed = true;
|
||||||
p.fatigueCounter = (p.fatigueCounter || 0) + 1;
|
p.fatigueCounter = (p.fatigueCounter || 0) + 1;
|
||||||
const dmg = p.fatigueCounter;
|
const dmg = p.fatigueCounter;
|
||||||
p.health = Math.max(0, p.health - dmg);
|
applyDamageToPlayer(p, dmg);
|
||||||
gameState.log.push({ type: 'fatigue', playerIndex: pi, damage: dmg });
|
gameState.log.push({ type: 'fatigue', playerIndex: pi, damage: dmg });
|
||||||
checkGameOver(room);
|
checkGameOver(room);
|
||||||
broadcastGameState(room);
|
broadcastGameState(room);
|
||||||
@ -644,12 +701,12 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
if (id === 'deal_1_hero' && enemies.length) {
|
if (id === 'deal_1_hero' && enemies.length) {
|
||||||
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
const idx = gameState.players.indexOf(t);
|
const idx = gameState.players.indexOf(t);
|
||||||
t.health = Math.max(0, t.health - 1);
|
applyDamageToPlayer(t, 1);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'deal_1_hero', fromPlayer: playerIndex, toPlayer: idx, damage: 1 });
|
gameState.log.push({ type: 'battlecry', effect: 'deal_1_hero', fromPlayer: playerIndex, toPlayer: idx, damage: 1 });
|
||||||
} else if (id === 'deal_2_hero' && enemies.length) {
|
} else if (id === 'deal_2_hero' && enemies.length) {
|
||||||
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
const idx = gameState.players.indexOf(t);
|
const idx = gameState.players.indexOf(t);
|
||||||
t.health = Math.max(0, t.health - 2);
|
applyDamageToPlayer(t, 2);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'deal_2_hero', fromPlayer: playerIndex, toPlayer: idx, damage: 2 });
|
gameState.log.push({ type: 'battlecry', effect: 'deal_2_hero', fromPlayer: playerIndex, toPlayer: idx, damage: 2 });
|
||||||
} else if (id === 'draw_1') {
|
} else if (id === 'draw_1') {
|
||||||
const drawn = drawCards(p.deck, 1);
|
const drawn = drawCards(p.deck, 1);
|
||||||
@ -689,10 +746,10 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (id === 'heal_hero_2') {
|
} else if (id === 'heal_hero_2') {
|
||||||
p.health = Math.min(30, p.health + 2);
|
healPlayer(p, 2);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_2', playerIndex });
|
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_2', playerIndex });
|
||||||
} else if (id === 'heal_hero_3') {
|
} else if (id === 'heal_hero_3') {
|
||||||
p.health = Math.min(30, p.health + 3);
|
healPlayer(p, 3);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_3', playerIndex });
|
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_3', playerIndex });
|
||||||
} else if (id === 'buff_all_friendly_attack') {
|
} else if (id === 'buff_all_friendly_attack') {
|
||||||
p.board.forEach((m) => {
|
p.board.forEach((m) => {
|
||||||
@ -701,7 +758,7 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
gameState.log.push({ type: 'battlecry', effect: 'buff_all_friendly_attack', playerIndex });
|
gameState.log.push({ type: 'battlecry', effect: 'buff_all_friendly_attack', playerIndex });
|
||||||
} else if (id === 'deal_3_all_enemies') {
|
} else if (id === 'deal_3_all_enemies') {
|
||||||
enemies.forEach((e) => {
|
enemies.forEach((e) => {
|
||||||
e.health = Math.max(0, e.health - 3);
|
applyDamageToPlayer(e, 3);
|
||||||
});
|
});
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'deal_3_all_enemies', fromPlayer: playerIndex });
|
gameState.log.push({ type: 'battlecry', effect: 'deal_3_all_enemies', fromPlayer: playerIndex });
|
||||||
} else if (id === 'deal_3_any') {
|
} else if (id === 'deal_3_any') {
|
||||||
@ -718,7 +775,7 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
runDeathrattle(room, getCard(room, target.cardId), idx);
|
runDeathrattle(room, getCard(room, target.cardId), idx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.health = Math.max(0, t.health - 3);
|
applyDamageToPlayer(t, 3);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'deal_3_any', fromPlayer: playerIndex, toPlayer: idx, damage: 3 });
|
gameState.log.push({ type: 'battlecry', effect: 'deal_3_any', fromPlayer: playerIndex, toPlayer: idx, damage: 3 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -763,7 +820,7 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
gameState.log.push({ type: 'battlecry', effect: 'buff_2_2', playerIndex, toIdx: boardIdx });
|
gameState.log.push({ type: 'battlecry', effect: 'buff_2_2', playerIndex, toIdx: boardIdx });
|
||||||
}
|
}
|
||||||
} else if (id === 'heal_hero_5') {
|
} else if (id === 'heal_hero_5') {
|
||||||
p.health = Math.min(30, p.health + 5);
|
healPlayer(p, 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') {
|
} else if (id === 'return_hand_enemy') {
|
||||||
// Для Ezra Bridger требуется выбор конкретного игрока
|
// Для Ezra Bridger требуется выбор конкретного игрока
|
||||||
@ -808,8 +865,8 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
} else if (id === 'random_effect') {
|
} else if (id === 'random_effect') {
|
||||||
const effects = [
|
const effects = [
|
||||||
() => { const drawn = drawCards(p.deck, 1); if (drawn.length) p.hand.push(...drawn); gameState.log.push({ type: 'battlecry', effect: 'random_draw', playerIndex }); },
|
() => { const drawn = drawCards(p.deck, 1); if (drawn.length) p.hand.push(...drawn); gameState.log.push({ type: 'battlecry', effect: 'random_draw', playerIndex }); },
|
||||||
() => { if (enemies.length) { const t = enemies[Math.floor(Math.random() * enemies.length)]; t.health = Math.max(0, t.health - 1); gameState.log.push({ type: 'battlecry', effect: 'random_damage', fromPlayer: playerIndex, toPlayer: gameState.players.indexOf(t), damage: 1 }); } },
|
() => { if (enemies.length) { const t = enemies[Math.floor(Math.random() * enemies.length)]; applyDamageToPlayer(t, 1); gameState.log.push({ type: 'battlecry', effect: 'random_damage', fromPlayer: playerIndex, toPlayer: gameState.players.indexOf(t), damage: 1 }); } },
|
||||||
() => { p.health = Math.min(30, p.health + 1); gameState.log.push({ type: 'battlecry', effect: 'random_heal', playerIndex }); }
|
() => { healPlayer(p, 1); gameState.log.push({ type: 'battlecry', effect: 'random_heal', playerIndex }); }
|
||||||
];
|
];
|
||||||
const randomEffect = effects[Math.floor(Math.random() * effects.length)];
|
const randomEffect = effects[Math.floor(Math.random() * effects.length)];
|
||||||
randomEffect();
|
randomEffect();
|
||||||
@ -820,6 +877,7 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
|
const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
|
||||||
target.health = Math.max(0, target.health - 2);
|
target.health = Math.max(0, target.health - 2);
|
||||||
const boardIdx = enemy.board.indexOf(target);
|
const boardIdx = enemy.board.indexOf(target);
|
||||||
|
applyDamageToPlayer(enemy, 2);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'deal_2_or_4_hero', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx, damage: 2 });
|
gameState.log.push({ type: 'battlecry', effect: 'deal_2_or_4_hero', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx, damage: 2 });
|
||||||
if (target.health <= 0) {
|
if (target.health <= 0) {
|
||||||
enemy.board.splice(boardIdx, 1);
|
enemy.board.splice(boardIdx, 1);
|
||||||
@ -827,6 +885,7 @@ function runBattlecry(room, card, playerIndex) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
enemy.health = Math.max(0, enemy.health - 4);
|
enemy.health = Math.max(0, enemy.health - 4);
|
||||||
|
applyDamageToPlayer(enemy, 4);
|
||||||
gameState.log.push({ type: 'battlecry', effect: 'deal_2_or_4_hero', fromPlayer: playerIndex, toPlayer: enemyIdx, damage: 4 });
|
gameState.log.push({ type: 'battlecry', effect: 'deal_2_or_4_hero', fromPlayer: playerIndex, toPlayer: enemyIdx, damage: 4 });
|
||||||
}
|
}
|
||||||
} else if (id === 'deal_2_all_enemy_minions') {
|
} else if (id === 'deal_2_all_enemy_minions') {
|
||||||
@ -899,15 +958,15 @@ function runDeathrattle(room, card, ownerIndex) {
|
|||||||
gameState.log.push({ type: 'deathrattle', effect: 'draw_1', playerIndex: ownerIndex });
|
gameState.log.push({ type: 'deathrattle', effect: 'draw_1', playerIndex: ownerIndex });
|
||||||
} else if (id === 'deal_2_all_enemies') {
|
} else if (id === 'deal_2_all_enemies') {
|
||||||
enemies.forEach((e) => {
|
enemies.forEach((e) => {
|
||||||
e.health = Math.max(0, e.health - 2);
|
applyDamageToPlayer(e, 2);
|
||||||
});
|
});
|
||||||
gameState.log.push({ type: 'deathrattle', effect: 'deal_2_all_enemies', fromPlayer: ownerIndex });
|
gameState.log.push({ type: 'deathrattle', effect: 'deal_2_all_enemies', fromPlayer: ownerIndex });
|
||||||
} else if (id === 'deal_1_random_enemy' && enemies.length) {
|
} else if (id === 'deal_1_random_enemy' && enemies.length) {
|
||||||
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
t.health = Math.max(0, t.health - 1);
|
applyDamageToPlayer(t, 1);
|
||||||
gameState.log.push({ type: 'deathrattle', effect: 'deal_1_random_enemy', fromPlayer: ownerIndex });
|
gameState.log.push({ type: 'deathrattle', effect: 'deal_1_random_enemy', fromPlayer: ownerIndex });
|
||||||
} else if (id === 'heal_hero_2') {
|
} else if (id === 'heal_hero_2') {
|
||||||
owner.health = Math.min(30, owner.health + 2);
|
healPlayer(owner, 2);
|
||||||
gameState.log.push({ type: 'deathrattle', effect: 'heal_hero_2', playerIndex: ownerIndex });
|
gameState.log.push({ type: 'deathrattle', effect: 'heal_hero_2', playerIndex: ownerIndex });
|
||||||
} else if (id === 'draw_3') {
|
} else if (id === 'draw_3') {
|
||||||
const drawn = drawCards(owner.deck, 3);
|
const drawn = drawCards(owner.deck, 3);
|
||||||
@ -915,11 +974,11 @@ function runDeathrattle(room, card, ownerIndex) {
|
|||||||
gameState.log.push({ type: 'deathrattle', effect: 'draw_3', playerIndex: ownerIndex });
|
gameState.log.push({ type: 'deathrattle', effect: 'draw_3', playerIndex: ownerIndex });
|
||||||
} else if (id === 'deal_4_random' && enemies.length) {
|
} else if (id === 'deal_4_random' && enemies.length) {
|
||||||
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
t.health = Math.max(0, t.health - 4);
|
applyDamageToPlayer(t, 4);
|
||||||
gameState.log.push({ type: 'deathrattle', effect: 'deal_4_random', fromPlayer: ownerIndex, toPlayer: gameState.players.indexOf(t), damage: 4 });
|
gameState.log.push({ type: 'deathrattle', effect: 'deal_4_random', fromPlayer: ownerIndex, toPlayer: gameState.players.indexOf(t), damage: 4 });
|
||||||
} else if (id === 'deal_3_random' && enemies.length) {
|
} else if (id === 'deal_3_random' && enemies.length) {
|
||||||
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
const t = enemies[Math.floor(Math.random() * enemies.length)];
|
||||||
t.health = Math.max(0, t.health - 3);
|
applyDamageToPlayer(t, 3);
|
||||||
gameState.log.push({ type: 'deathrattle', effect: 'deal_3_random', fromPlayer: ownerIndex, toPlayer: gameState.players.indexOf(t), damage: 3 });
|
gameState.log.push({ type: 'deathrattle', effect: 'deal_3_random', fromPlayer: ownerIndex, toPlayer: gameState.players.indexOf(t), damage: 3 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1011,10 +1070,17 @@ function playCard(room, socketId, handIndex, boardPos) {
|
|||||||
health: card.health,
|
health: card.health,
|
||||||
maxHealth: card.health,
|
maxHealth: card.health,
|
||||||
canAttack: card.attack > 0,
|
canAttack: card.attack > 0,
|
||||||
|
attacksUsed: 0, // Для двойной атаки
|
||||||
};
|
};
|
||||||
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 });
|
||||||
|
|
||||||
|
// Обработка battlecry для карт с броней
|
||||||
|
if (card.battlecry === 'add_armor_15') {
|
||||||
|
p.armor = (p.armor || 0) + 15;
|
||||||
|
gameState.log.push({ type: 'battlecry', effect: 'add_armor_15', playerIndex: pi });
|
||||||
|
}
|
||||||
|
|
||||||
// Для battlecry, требующих выбора цели (Ezra Bridger), отправляем запрос клиенту
|
// Для battlecry, требующих выбора цели (Ezra Bridger), отправляем запрос клиенту
|
||||||
if (card.battlecryId === 'return_hand_enemy') {
|
if (card.battlecryId === 'return_hand_enemy') {
|
||||||
const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);
|
const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);
|
||||||
@ -1261,6 +1327,21 @@ function applySynergies(room) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Аура: Droid Armor - мех-юниты имеют удвоенную броню
|
||||||
|
if (card.aura === 'double_armor_mechs') {
|
||||||
|
// Удваиваем броню игрока, если есть мех-юниты на поле
|
||||||
|
const hasMechs = player.board.some(other => {
|
||||||
|
const otherCard = cardDb[other.cardId];
|
||||||
|
return otherCard && (otherCard.id === 'r2d2' || otherCard.id === 'c3po' ||
|
||||||
|
otherCard.id === 'bb8' || otherCard.id === 'droid' ||
|
||||||
|
otherCard.name?.includes('Droid') || otherCard.name?.includes('дроид'));
|
||||||
|
});
|
||||||
|
if (hasMechs) {
|
||||||
|
// Броня уже применена, но можно добавить бонус
|
||||||
|
// Это будет обрабатываться при применении урона
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Йода даёт +1/+1 всем джедаям
|
// Йода даёт +1/+1 всем джедаям
|
||||||
if (card.id === 'yoda' || card.name === 'Yoda') {
|
if (card.id === 'yoda' || card.name === 'Yoda') {
|
||||||
player.board.forEach((other, otherIdx) => {
|
player.board.forEach((other, otherIdx) => {
|
||||||
@ -1471,7 +1552,7 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
|||||||
if (targetBoardIndex === -1) {
|
if (targetBoardIndex === -1) {
|
||||||
const t = gameState.players[targetPlayerIndex];
|
const t = gameState.players[targetPlayerIndex];
|
||||||
if (!t || t.health <= 0) return;
|
if (!t || t.health <= 0) return;
|
||||||
t.health = Math.max(0, t.health - 2);
|
applyDamageToPlayer(t, 2);
|
||||||
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_2_hero', damage: 2 });
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_2_hero', damage: 2 });
|
||||||
} else {
|
} else {
|
||||||
const t = gameState.players[targetPlayerIndex];
|
const t = gameState.players[targetPlayerIndex];
|
||||||
@ -1498,7 +1579,7 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
|||||||
} else if (eff === 'deal_1_all_enemies') {
|
} else if (eff === 'deal_1_all_enemies') {
|
||||||
gameState.players.forEach((pl, i) => {
|
gameState.players.forEach((pl, i) => {
|
||||||
if (i === pi) return;
|
if (i === pi) return;
|
||||||
pl.health = Math.max(0, pl.health - 1);
|
applyDamageToPlayer(pl, 1);
|
||||||
pl.board.forEach((m) => {
|
pl.board.forEach((m) => {
|
||||||
m.health -= 1;
|
m.health -= 1;
|
||||||
if (m.health <= 0) {
|
if (m.health <= 0) {
|
||||||
@ -1526,7 +1607,7 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
|||||||
if (targetBoardIndex === -1) {
|
if (targetBoardIndex === -1) {
|
||||||
const t = gameState.players[targetPlayerIndex];
|
const t = gameState.players[targetPlayerIndex];
|
||||||
if (!t || t.health <= 0) return;
|
if (!t || t.health <= 0) return;
|
||||||
t.health = Math.max(0, t.health - 4);
|
applyDamageToPlayer(t, 4);
|
||||||
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_4_hero', damage: 4 });
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_4_hero', damage: 4 });
|
||||||
} else {
|
} else {
|
||||||
const t = gameState.players[targetPlayerIndex];
|
const t = gameState.players[targetPlayerIndex];
|
||||||
@ -1544,7 +1625,7 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
|||||||
if (targetBoardIndex === -1) {
|
if (targetBoardIndex === -1) {
|
||||||
const t = gameState.players[targetPlayerIndex];
|
const t = gameState.players[targetPlayerIndex];
|
||||||
if (!t) return;
|
if (!t) return;
|
||||||
t.health = Math.min(30, t.health + 4);
|
healPlayer(t, 4);
|
||||||
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'heal_4_hero' });
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'heal_4_hero' });
|
||||||
} else {
|
} else {
|
||||||
const t = gameState.players[targetPlayerIndex];
|
const t = gameState.players[targetPlayerIndex];
|
||||||
@ -1583,6 +1664,16 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
|
|||||||
m.maxHealth = (m.maxHealth || m.health) + 1;
|
m.maxHealth = (m.maxHealth || m.health) + 1;
|
||||||
});
|
});
|
||||||
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'buff_all_friendly' });
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'buff_all_friendly' });
|
||||||
|
} else if (eff === 'heal_hero_5') {
|
||||||
|
healPlayer(p, 5);
|
||||||
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'heal_hero_5' });
|
||||||
|
} else if (eff === 'heal_hero_30pct') {
|
||||||
|
const healAmount = Math.floor(p.health * 0.3);
|
||||||
|
healPlayer(p, healAmount);
|
||||||
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'heal_hero_30pct', amount: healAmount });
|
||||||
|
} else if (eff === 'add_armor_10') {
|
||||||
|
p.armor = (p.armor || 0) + 10;
|
||||||
|
gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'add_armor_10' });
|
||||||
} else if (eff === 'destroy_weak_2') {
|
} else if (eff === 'destroy_weak_2') {
|
||||||
enemies.forEach((enemy) => {
|
enemies.forEach((enemy) => {
|
||||||
if (enemy.board && enemy.board.length > 0) {
|
if (enemy.board && enemy.board.length > 0) {
|
||||||
@ -1763,7 +1854,7 @@ function heroAbility(room, socketId, targetPlayerIndex, targetBoardIndex) {
|
|||||||
const targetPlayer = gameState.players[targetPlayerIndex];
|
const targetPlayer = gameState.players[targetPlayerIndex];
|
||||||
if (!targetPlayer) return;
|
if (!targetPlayer) return;
|
||||||
if (targetBoardIndex === -1) {
|
if (targetBoardIndex === -1) {
|
||||||
targetPlayer.health = Math.max(0, targetPlayer.health - 1);
|
applyDamageToPlayer(targetPlayer, 1);
|
||||||
gameState.log.push({ type: 'heroAbility', fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_1_hero' });
|
gameState.log.push({ type: 'heroAbility', fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_1_hero' });
|
||||||
} else {
|
} else {
|
||||||
const m = targetPlayer.board[targetBoardIndex];
|
const m = targetPlayer.board[targetBoardIndex];
|
||||||
@ -1809,14 +1900,50 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
|||||||
applySynergies(room);
|
applySynergies(room);
|
||||||
|
|
||||||
if (targetBoardIndex === -1) {
|
if (targetBoardIndex === -1) {
|
||||||
const attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
|
let attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
|
||||||
targetPlayer.health = Math.max(0, targetPlayer.health - attackerAttack);
|
const attackerCard = getCard(room, attacker.cardId);
|
||||||
|
|
||||||
|
// Если карта делит урон при двойной атаке
|
||||||
|
if (attackerCard && attackerCard.divideDamage && attackerCard.canAttackTwice) {
|
||||||
|
const attacksUsed = attacker.attacksUsed || 0;
|
||||||
|
if (attacksUsed > 0) {
|
||||||
|
// Второй удар - урон делится
|
||||||
|
attackerAttack = Math.floor(attackerAttack / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на уменьшение урона (Имперский Щитогенератор)
|
||||||
|
const shieldGen = targetPlayer.board.find(m => {
|
||||||
|
const card = getCard(room, m.cardId);
|
||||||
|
return card && card.damageReduction;
|
||||||
|
});
|
||||||
|
if (shieldGen) {
|
||||||
|
const card = getCard(room, shieldGen.cardId);
|
||||||
|
if (card && card.damageReduction) {
|
||||||
|
attackerAttack = Math.floor(attackerAttack * (1 - card.damageReduction));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на Energy Shield - поглощает первый урон каждого хода
|
||||||
|
const energyShield = targetPlayer.board.find(m => {
|
||||||
|
const card = getCard(room, m.cardId);
|
||||||
|
return card && card.shieldEffect === 'absorb_first_damage';
|
||||||
|
});
|
||||||
|
if (energyShield && !energyShield.shieldUsedThisTurn) {
|
||||||
|
energyShield.shieldUsedThisTurn = true;
|
||||||
|
attackerAttack = 0; // Полностью поглощается
|
||||||
|
gameState.log.push({ type: 'shieldAbsorb', playerIndex: targetPlayerIndex, minionId: energyShield.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldArmor = targetPlayer.armor || 0;
|
||||||
|
applyDamageToPlayer(targetPlayer, attackerAttack);
|
||||||
gameState.log.push({
|
gameState.log.push({
|
||||||
type: 'attackHero',
|
type: 'attackHero',
|
||||||
fromPlayer: pi,
|
fromPlayer: pi,
|
||||||
toPlayer: targetPlayerIndex,
|
toPlayer: targetPlayerIndex,
|
||||||
attackerMinionId: attacker.id,
|
attackerMinionId: attacker.id,
|
||||||
damage: attacker.attack,
|
damage: attackerAttack,
|
||||||
|
armorBlocked: oldArmor - (targetPlayer.armor || 0),
|
||||||
});
|
});
|
||||||
// Применяем синергии перед расчётом урона
|
// Применяем синергии перед расчётом урона
|
||||||
applySynergies(room);
|
applySynergies(room);
|
||||||
@ -1852,9 +1979,22 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
|||||||
});
|
});
|
||||||
const targetCard = getCard(room, target.cardId);
|
const targetCard = getCard(room, target.cardId);
|
||||||
const attackerCard = getCard(room, attacker.cardId);
|
const attackerCard = getCard(room, attacker.cardId);
|
||||||
if (targetDied && targetCard && targetCard.deathrattleId) {
|
|
||||||
|
// Обработка Soul Heal - хил за каждую убитую карту противника
|
||||||
|
if (targetDied) {
|
||||||
|
// Проверяем, есть ли у атакующего игрока карта Soul Heal
|
||||||
|
p.board.forEach((minion) => {
|
||||||
|
const minionCard = getCard(room, minion.cardId);
|
||||||
|
if (minionCard && minionCard.onEnemyDeath === 'heal_1_per_kill') {
|
||||||
|
healPlayer(p, 1);
|
||||||
|
gameState.log.push({ type: 'soulHeal', playerIndex: pi, healed: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetCard && targetCard.deathrattleId) {
|
||||||
runDeathrattle(room, targetCard, targetPlayerIndex);
|
runDeathrattle(room, targetCard, targetPlayerIndex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (attackerDied && attackerCard && attackerCard.deathrattleId) {
|
if (attackerDied && attackerCard && attackerCard.deathrattleId) {
|
||||||
runDeathrattle(room, attackerCard, pi);
|
runDeathrattle(room, attackerCard, pi);
|
||||||
}
|
}
|
||||||
@ -1862,8 +2002,19 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
|
|||||||
|
|
||||||
// Механика двойной атаки
|
// Механика двойной атаки
|
||||||
const attackerCard = getCard(room, attacker.cardId);
|
const attackerCard = getCard(room, attacker.cardId);
|
||||||
if (attackerCard && attackerCard.canAttackTwice && !attacker.attacksUsed) {
|
if (attackerCard && attackerCard.canAttackTwice) {
|
||||||
attacker.attacksUsed = 1; // Использована одна атака
|
attacker.attacksUsed = (attacker.attacksUsed || 0) + 1;
|
||||||
|
// Если карта умирает после определенного количества атак
|
||||||
|
if (attackerCard.diesAfterAttacks && attacker.attacksUsed >= attackerCard.diesAfterAttacks) {
|
||||||
|
attacker.health = 0; // Убиваем карту
|
||||||
|
gameState.log.push({ type: 'minionDeath', minionId: attacker.id, reason: 'diesAfterAttacks' });
|
||||||
|
} else if (attacker.attacksUsed >= 2) {
|
||||||
|
attacker.canAttack = false;
|
||||||
|
attacker.attacksUsed = 0;
|
||||||
|
} else {
|
||||||
|
// Может атаковать еще раз
|
||||||
|
attacker.canAttack = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
attacker.canAttack = false;
|
attacker.canAttack = false;
|
||||||
attacker.attacksUsed = 0;
|
attacker.attacksUsed = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user