This commit is contained in:
2026-01-26 23:33:33 +03:00
parent 937c39b73f
commit a597cb9de7
5 changed files with 732 additions and 57 deletions

475
server.js
View File

@ -357,6 +357,10 @@ function endTurn(room) {
}
gameState.turn++;
gameState.log.push({ type: 'turn', from: prev, to: next });
// Применяем синергии в начале каждого хода
applySynergies(room);
checkGameOver(room);
startTurnTimer(room);
broadcastGameState(room);
@ -514,16 +518,22 @@ function runBattlecry(room, card, playerIndex) {
} else if (id === 'heal_hero_5') {
p.health = Math.min(30, p.health + 5);
gameState.log.push({ type: 'battlecry', effect: 'heal_hero_5', playerIndex });
} else if (id === 'return_hand_enemy' && enemies.length) {
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
const enemyIdx = gameState.players.indexOf(enemy);
if (enemy.board && enemy.board.length > 0) {
const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
const boardIdx = enemy.board.indexOf(target);
enemy.board.splice(boardIdx, 1);
enemy.hand.push(target.cardId);
gameState.log.push({ type: 'battlecry', effect: 'return_hand_enemy', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
} else if (id === 'return_hand_enemy') {
// Для Ezra Bridger требуется выбор конкретного игрока
// Отправляем запрос клиенту для выбора цели
const socket = io.sockets.sockets.get(p.id);
if (socket && enemies.length > 0) {
socket.emit('battlecryTargetRequest', {
battlecryId: 'return_hand_enemy',
cardId: card.id || Object.keys(cardDb).find(k => cardDb[k] === card),
availableTargets: enemies.map((e, idx) => ({
playerIndex: gameState.players.indexOf(e),
playerName: e.name || `Игрок ${gameState.players.indexOf(e) + 1}`,
hasMinions: e.board && e.board.length > 0
}))
});
}
return; // Не выполняем сразу, ждём выбора цели
} else if (id === 'destroy_strongest_enemy' && enemies.length) {
const enemy = enemies[Math.floor(Math.random() * enemies.length)];
const enemyIdx = gameState.players.indexOf(enemy);
@ -757,11 +767,375 @@ function playCard(room, socketId, handIndex, boardPos) {
};
p.board.splice(typeof boardPos === 'number' ? boardPos : p.board.length, 0, minion);
gameState.log.push({ type: 'play', playerIndex: pi, cardId: cid, minionId: minion.id });
if (card.battlecryId) runBattlecry(room, card, pi);
// Для battlecry, требующих выбора цели (Ezra Bridger), отправляем запрос клиенту
if (card.battlecryId === 'return_hand_enemy') {
const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);
if (enemies.length > 0) {
const socket = io.sockets.sockets.get(p.id);
if (socket) {
// Сохраняем информацию о миньоне для последующего выполнения battlecry
minion.pendingBattlecry = { battlecryId: card.battlecryId, cardId: cid };
socket.emit('battlecryTargetRequest', {
battlecryId: 'return_hand_enemy',
cardId: cid,
minionId: minion.id,
availableTargets: enemies.flatMap(enemy => {
const enemyIdx = gameState.players.indexOf(enemy);
if (!enemy.board || enemy.board.length === 0) return [];
return enemy.board.map((m, boardIdx) => ({
playerIndex: enemyIdx,
boardIndex: boardIdx,
playerName: enemy.name || `Игрок ${enemyIdx + 1}`,
minionName: cardDb[m.cardId]?.name || m.cardId
}));
})
});
broadcastGameState(room);
return; // Не применяем синергии пока, ждём выбора цели
}
}
} else if (card.battlecryId) {
runBattlecry(room, card, pi);
}
// Применяем синергии после размещения карты
applySynergies(room);
checkGameOver(room);
broadcastGameState(room);
}
// Функция для применения синергий между картами
function applySynergies(room) {
const gameState = room.gameState;
const cardDb = require('./cards.js');
gameState.players.forEach((player, playerIndex) => {
if (!player.board || player.board.length === 0) return;
// Сбрасываем все бонусы синергий перед пересчётом
player.board.forEach(m => {
m.synergyAttackBonus = 0;
m.synergyHealthBonus = 0;
});
player.board.forEach((minion, idx) => {
const card = cardDb[minion.cardId];
if (!card) return;
// Дарт Вейдер даёт +1/+1 всем штурмовикам и клонам
if (card.id === 'vader' || card.name === 'Darth Vader') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'stormtrooper' || otherCard.name === 'Stormtrooper' ||
otherCard.id === 'clone_trooper' || otherCard.name === 'Clone Trooper' ||
otherCard.id === 'clone_commando' || otherCard.name === 'Clone Commando' ||
otherCard.id === 'arc_trooper' || otherCard.name === 'ARC Trooper')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Люк Скайуокер даёт +1/+1 всем повстанцам
if (card.id === 'luke' || card.name === 'Luke Skywalker') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && otherCard.faction === 'rebellion' && otherCard.type === 'minion') {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Император Палпатин даёт +1/+1 всем ситхам и имперским картам
if (card.id === 'palpatine' || card.name === 'Emperor Palpatine') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.faction === 'empire' ||
otherCard.name?.includes('Darth') || otherCard.id === 'maul' ||
otherCard.id === 'vader' || otherCard.id === 'dooku' || otherCard.id === 'kylo')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Хан Соло и Чубакка дают друг другу +1/+1
if (card.id === 'han' || card.name === 'Han Solo') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'chewie' || otherCard.name === 'Chewbacca')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
if (card.id === 'chewie' || card.name === 'Chewbacca') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'han' || otherCard.name === 'Han Solo')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// R2-D2 и C-3PO дают друг другу +1/+1
if (card.id === 'r2d2' || card.name === 'R2-D2') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'c3po' || otherCard.name === 'C-3PO')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
if (card.id === 'c3po' || card.name === 'C-3PO') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'r2d2' || otherCard.name === 'R2-D2')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Лея даёт +1/+1 Люку
if (card.id === 'leia' || card.name === 'Princess Leia') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'luke' || otherCard.name === 'Luke Skywalker')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Люк даёт +1/+1 Лее
if (card.id === 'luke' || card.name === 'Luke Skywalker') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'leia' || otherCard.name === 'Princess Leia')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Оби-Ван даёт +1/+1 Энакину/Анакину
if (card.id === 'obiwan' || card.name === 'Obi-Wan Kenobi') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'anakin' || otherCard.name === 'Anakin Skywalker')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Энакин даёт +1/+1 Оби-Вану
if (card.id === 'anakin' || card.name === 'Anakin Skywalker') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'obiwan' || otherCard.name === 'Obi-Wan Kenobi')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Йода даёт +1/+1 всем джедаям
if (card.id === 'yoda' || card.name === 'Yoda') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && otherCard.faction === 'rebellion' &&
(otherCard.name?.includes('Jedi') || otherCard.id === 'luke' ||
otherCard.id === 'obiwan' || otherCard.id === 'anakin' ||
otherCard.id === 'ahsoka' || otherCard.id === 'mace' ||
otherCard.id === 'quigon' || otherCard.id === 'plo_koon' ||
otherCard.id === 'ki_adi' || otherCard.id === 'aayla' ||
otherCard.id === 'shaak_ti' || otherCard.id === 'kanan' ||
otherCard.id === 'ezra' || otherCard.id === 'cal_kestis')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Боба Фетт даёт +1/+1 Джанго Фетту
if (card.id === 'boba' || card.name === 'Boba Fett') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'jango' || otherCard.name === 'Jango Fett')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Джанго Фетт даёт +1/+1 Бобе Фетту
if (card.id === 'jango' || card.name === 'Jango Fett') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'boba' || otherCard.name === 'Boba Fett')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Эйсока даёт +1/+1 Рексу
if (card.id === 'ahsoka' || card.name === 'Ahsoka Tano') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'rex' || otherCard.name === 'Captain Rex')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Рекс даёт +1/+1 Эйсоке
if (card.id === 'rex' || card.name === 'Captain Rex') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'ahsoka' || otherCard.name === 'Ahsoka Tano')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Мандалорец даёт +1/+1 Грогу
if (card.id === 'mando' || card.name === 'Din Djarin') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'grogu' || otherCard.name === 'Grogu')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Грогу даёт +1/+1 Мандалорцу
if (card.id === 'grogu' || card.name === 'Grogu') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'mando' || otherCard.name === 'Din Djarin')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Таркин даёт +1/+1 имперским картам
if (card.id === 'tarkin' || card.name === 'Grand Moff Tarkin') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && otherCard.faction === 'empire' && otherCard.type === 'minion') {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Дарт Мол даёт +1/+1 Сэвиджу Оппрессу
if (card.id === 'maul' || card.name === 'Darth Maul') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'savage' || otherCard.name === 'Savage Opress')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Сэвидж Оппресс даёт +1/+1 Дарту Молу
if (card.id === 'savage' || card.name === 'Savage Opress') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'maul' || otherCard.name === 'Darth Maul')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Падме даёт +1/+1 Энакину
if (card.id === 'padme' || card.name === 'Padmé Amidala') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'anakin' || otherCard.name === 'Anakin Skywalker')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
// Энакин даёт +1/+1 Падме
if (card.id === 'anakin' || card.name === 'Anakin Skywalker') {
player.board.forEach((other, otherIdx) => {
if (idx !== otherIdx) {
const otherCard = cardDb[other.cardId];
if (otherCard && (otherCard.id === 'padme' || otherCard.name === 'Padmé Amidala')) {
other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
}
}
});
}
});
});
}
function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardIndex) {
const gameState = room.gameState;
const pi = findPlayerIndex(room, socketId);
@ -944,6 +1318,9 @@ function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardInde
p.mana -= cost;
p.hand.splice(handIndex, 1);
// Применяем синергии после размещения карты
applySynergies(room);
checkGameOver(room);
broadcastGameState(room);
}
@ -1003,6 +1380,15 @@ function stealCardsFromDeck(room, socketId, handIndex, targetPlayerIndex, cardIn
stolenCount: stolenCards.length
});
// Применяем синергии после изменений на доске
applySynergies(room);
// Применяем синергии после боя
applySynergies(room);
// Применяем синергии после изменений на доске
applySynergies(room);
checkGameOver(room);
broadcastGameState(room);
}
@ -1038,6 +1424,12 @@ function heroAbility(room, socketId, targetPlayerIndex, targetBoardIndex) {
gameState.players.forEach((pl) => {
pl.board = pl.board.filter((min) => min.health > 0);
});
// Применяем синергии после изменений на доске
applySynergies(room);
// Применяем синергии после изменений на доске
applySynergies(room);
checkGameOver(room);
broadcastGameState(room);
}
@ -1054,8 +1446,12 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
const targetPlayer = gameState.players[targetPlayerIndex];
if (!targetPlayer) return;
// Применяем синергии перед расчётом урона
applySynergies(room);
if (targetBoardIndex === -1) {
targetPlayer.health = Math.max(0, targetPlayer.health - attacker.attack);
const attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
targetPlayer.health = Math.max(0, targetPlayer.health - attackerAttack);
gameState.log.push({
type: 'attackHero',
fromPlayer: pi,
@ -1063,12 +1459,23 @@ function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoa
attackerMinionId: attacker.id,
damage: attacker.attack,
});
checkGameOver(room); // Проверяем после атаки по герою
// Применяем синергии перед расчётом урона
applySynergies(room);
checkGameOver(room); // Проверяем после атаки по герою
} else {
const target = targetPlayer.board[targetBoardIndex];
if (!target) return;
target.health -= attacker.attack;
attacker.health -= target.attack;
// Применяем синергии перед расчётом урона
applySynergies(room);
// Учитываем бонусы синергий при расчёте урона (бонусы уже применены в applySynergies)
const attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
const targetAttack = target.attack + (target.synergyAttackBonus || 0);
target.health -= attackerAttack;
attacker.health -= targetAttack;
const attackerDied = attacker.health <= 0;
const targetDied = target.health <= 0;
gameState.log.push({
@ -1250,6 +1657,46 @@ io.on('connection', (socket) => {
if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
stealCardsFromDeck(room, socket.id, data.handIndex, data.targetPlayerIndex, data.cardIndices);
});
socket.on('battlecryTarget', (data) => {
const room = getRoomBySocket(socket.id);
if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
const gameState = room.gameState;
const pi = findPlayerIndex(room, socket.id);
if (pi < 0) return;
const p = gameState.players[pi];
if (p.health <= 0 || p.isDead) return;
if (data.battlecryId === 'return_hand_enemy') {
const targetPlayerIndex = data.targetPlayerIndex;
const targetBoardIndex = data.targetBoardIndex;
const targetPlayer = gameState.players[targetPlayerIndex];
if (!targetPlayer || targetPlayerIndex === pi || targetPlayer.health <= 0) return;
if (!targetPlayer.board || targetPlayer.board.length === 0) return;
if (targetBoardIndex == null || targetBoardIndex < 0 || targetBoardIndex >= targetPlayer.board.length) return;
const target = targetPlayer.board[targetBoardIndex];
targetPlayer.board.splice(targetBoardIndex, 1);
if (targetPlayer.hand.length < 10) {
targetPlayer.hand.push(target.cardId);
}
// Удаляем pendingBattlecry с миньона
const playedMinion = p.board?.find(m => m.pendingBattlecry && m.pendingBattlecry.battlecryId === 'return_hand_enemy');
if (playedMinion) {
delete playedMinion.pendingBattlecry;
}
gameState.log.push({ type: 'battlecry', effect: 'return_hand_enemy', fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex });
// Применяем синергии после изменений на доске
applySynergies(room);
checkGameOver(room);
broadcastGameState(room);
}
});
socket.on('resetToLobby', () => {
const room = getRoomBySocket(socket.id);