diff --git a/src/games/controllers/turn.controller.ts b/src/games/controllers/turn.controller.ts index c7fbbba..a240571 100644 --- a/src/games/controllers/turn.controller.ts +++ b/src/games/controllers/turn.controller.ts @@ -16,7 +16,13 @@ import { GameStartedPipe } from '../pipe/game.pipe'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { TurnService } from '../services/turn.service'; -import { getPlayer, getCurrentTeam, isInTeam, findPlayer } from '../utils'; +import { + getPlayer, + getCurrentTeam, + isInTeam, + findUser, + findPlayer, +} from '../utils'; import { CardPipe } from '../pipe/card.pipe'; import { Card } from 'src/cards/schemas/cards.schema'; import { EffectPipe } from '../pipe/effect.pipe'; @@ -92,7 +98,12 @@ export class TurnController { if (game.currentTurn.gold < card.cost) { throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); } - await this.turnService.buyGeneric(game, currentPlayer, card, game.market); + await this.turnService.buyGeneric( + game, + card, + game.market, + currentPlayer.discard, + ); await this.gameService.fillMarket(game); } @@ -118,9 +129,9 @@ export class TurnController { const targetCard = game.fireGems.cards[0]; await this.turnService.buyGeneric( game, - currentPlayer, targetCard, game.fireGems, + currentPlayer.discard, ); } @@ -223,32 +234,21 @@ export class TurnController { CardEffects.STUN, CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE, ]; + + let targetCard: Card; if (enemyTargetToken.includes(token.effect)) { - const targetCard = targetPlayer.board.cards.find((c) => + targetCard = targetPlayer.board.cards.find((c) => c._id.equals(useTokenDto.cardId), ); - if (!targetCard) { - throw new HttpException('Card not found', HttpStatus.NOT_FOUND); - } - await this.turnService.useTokenOnEnemyBoard( - game, - targetPlayer, - token, - targetCard, - ); } else { - const targetCard = currentPlayer.board.cards.find((c) => + targetCard = currentPlayer.board.cards.find((c) => c._id.equals(useTokenDto.cardId), ); - if (!targetCard) { - throw new HttpException('Card not found', HttpStatus.NOT_FOUND); - } - await this.turnService.useTokenOnEnemyBoard( - game, - currentPlayer, - token, - targetCard, - ); } + + if (!targetCard) { + throw new HttpException('Card not found', HttpStatus.NOT_FOUND); + } + await this.turnService.useToken(game, currentPlayer, token, targetCard); } } diff --git a/src/games/dto/use-token-dto.ts b/src/games/dto/use-token-dto.ts index a43e0ed..2e39792 100644 --- a/src/games/dto/use-token-dto.ts +++ b/src/games/dto/use-token-dto.ts @@ -1,4 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; + export class UseTokenDTO { - playerId: string; + @ApiProperty() + playerId?: string; + @ApiProperty() cardId: string; } diff --git a/src/games/services/effect.functions.ts b/src/games/services/effect.functions.ts index 6720ee9..1287605 100644 --- a/src/games/services/effect.functions.ts +++ b/src/games/services/effect.functions.ts @@ -4,6 +4,7 @@ import mongoose, { Document } from 'mongoose'; import { Card } from 'src/cards/schemas/cards.schema'; import { CardEffects, + CardRole, CardType, EffectType, Per, @@ -12,6 +13,7 @@ import { Effect, EffectCondition } from 'src/cards/schemas/effect.schema'; import { TurnService } from './turn.service'; import { GameService } from './games.service'; import { ReadEffectDto } from 'src/cards/dto/effect.dto'; +import { HttpException, HttpStatus } from '@nestjs/common'; export function isAutoActivable(effect: Effect) { if ( @@ -177,11 +179,15 @@ export const tokenMappings = { player: Player, card: Card, gameService: GameService, + turnService: TurnService, eventEmitter: EventEmitter2, _session: mongoose.mongo.ClientSession, ) => { if (card.cardType !== CardType.CHAMPION) { - throw new Error('Cannot Prepare : Card is not a champion'); + throw new HttpException( + 'Cannot Prepare : Card is not a champion', + HttpStatus.BAD_REQUEST, + ); } const targetEffects = card.effects.filter( (e) => e.condition.effectId == EffectType.CHAMPION_ACTION, @@ -192,7 +198,10 @@ export const tokenMappings = { game.currentTurn.lockedEffects.some((ee) => ee._id.equals(e._id)), ) ) { - throw new Error('Cannot Prepare : champion effect not locked'); + throw new HttpException( + 'Cannot Prepare : champion effect not locked', + HttpStatus.BAD_REQUEST, + ); } for (const e of targetEffects) { @@ -202,20 +211,190 @@ export const tokenMappings = { game.currentTurn.lockedEffects.splice(index, 1); eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'unlockEffect', - effect: effect._id.toHexString(), + effect: e._id.toHexString(), }); } }, - [CardEffects.STUN]: addToken, - [CardEffects.SACRIFICE]: addToken, - [CardEffects.DRAW_AND_DISCARD]: addToken, - [CardEffects.MAKE_DISCARD]: addToken, - [CardEffects.RESTACK_DISCARDED_CHAMPION]: addToken, - [CardEffects.RESTACK_DISCARDED_CARD]: addToken, - [CardEffects.STACK_NEXT_ACTION_BOUGHT]: addToken, - [CardEffects.STACK_NEXT_CARD_BOUGHT]: addToken, - [CardEffects.PLAY_NEXT_CARD_BOUGHT]: addToken, -}; + [CardEffects.STUN]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + if (card.cardType !== CardType.CHAMPION) { + throw new HttpException( + 'Cannot Stun : Card is not a champion', + HttpStatus.BAD_REQUEST, + ); + } + await gameService.distributeCards( + [card], + player.board, + player.discard, + game, + session, + ); + }, + [CardEffects.SACRIFICE]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + if (game.currentTurn.cardsLocked.some((c) => c._id.equals(card._id))) { + throw new HttpException( + 'Cannot destroy : Card is locked', + HttpStatus.BAD_REQUEST, + ); + } + await gameService.destroyCard(card, player.board, game, session); + }, + [CardEffects.RESTACK_DISCARDED_CHAMPION]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + if (card.cardType != CardType.CHAMPION) { + throw new HttpException( + 'Cannot Restack : Card is not a champion', + HttpStatus.BAD_REQUEST, + ); + } + await gameService.distributeCards( + [card], + player.discard, + player.stack, + game, + session, + ); + }, + [CardEffects.RESTACK_DISCARDED_CARD]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + await gameService.distributeCards( + [card], + player.discard, + player.stack, + game, + session, + ); + }, + [CardEffects.STACK_NEXT_ACTION_BOUGHT]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + if (card.cardType != CardType.CHAMPION) { + throw new HttpException( + 'Cannot stack : Card is not an action', + HttpStatus.BAD_REQUEST, + ); + } + await turnService.buyGeneric( + game, + card, + game.market, + player.stack, + session, + ); + }, + [CardEffects.STACK_NEXT_CARD_BOUGHT]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + let fromContainer = game.market; + if (game.fireGems.cards.some((c) => c._id.equals(card._id))) { + fromContainer = game.fireGems; + } + + await turnService.buyGeneric( + game, + card, + fromContainer, + player.stack, + session, + ); + }, + [CardEffects.PLAY_NEXT_CARD_BOUGHT]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + let fromContainer = game.market; + if (game.fireGems.cards.some((c) => c._id.equals(card._id))) { + fromContainer = game.fireGems; + } + + await turnService.buyGeneric( + game, + card, + fromContainer, + player.board, + session, + ); + }, + [CardEffects.DRAW_AND_DISCARD]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => { + await gameService.drawToBoard(1, player, game, session); + await player.discardMarkers++; + }, +} as Record< + CardEffects, + ( + game: Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + turnService: TurnService, + eventEmitter: EventEmitter2, + session: mongoose.mongo.ClientSession, + ) => Promise +>; export const conditionsMappings = { [EffectType.CHAMPION_ACTION]: async () => true, diff --git a/src/games/services/turn.service.ts b/src/games/services/turn.service.ts index 6ea2bea..c035842 100644 --- a/src/games/services/turn.service.ts +++ b/src/games/services/turn.service.ts @@ -23,6 +23,7 @@ import { isAutoActivable, perMapping, removeToken, + tokenMappings, } from './effect.functions'; import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { GameService } from './games.service'; @@ -148,9 +149,9 @@ export class TurnService { @WithTransaction async buyGeneric( game: Document & Game, - player: Player, card: Card, fromContainer: Container, + toContainer: Container, session?: mongoose.mongo.ClientSession, ) { if (!card.cost) { @@ -198,13 +199,19 @@ export class TurnService { if (game.currentTurn.gold < cost) { throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); } - - const targetContainer = player.discard; // if (targetChangingToken) { // targetContainer = player.stack; // removeToken(game, targetChangingToken, this.eventEmitter); // } + await this.gamesService.distributeCards( + [card], + fromContainer, + toContainer, + game, + session, + ); + if (cost != 0) { game.currentTurn.gold -= cost; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { @@ -214,13 +221,6 @@ export class TurnService { }); } - await this.gamesService.distributeCards( - [card], - fromContainer, - targetContainer, - game, - session, - ); await game.save({ session }); } @@ -246,19 +246,33 @@ export class TurnService { @WithTransaction async useToken( game: Document & Game, + player: Player, token: Effect, target: Card, session?: mongoose.mongo.ClientSession, ) { - return; + const handler = tokenMappings[token.effect]; + if (!handler) { + throw new Error('Effect not implemented'); + } + await handler( + game, + token, + player, + target, + this.gamesService, + this, + this.eventEmitter, + session, + ); + const index = game.currentTurn.tokens.findIndex((t) => + t._id.equals(token._id), + ); + game.currentTurn.tokens.splice(index, 1); + this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'currentTurn.useToken', + tokenId: token._id.toHexString(), + }); + await game.save({ session }); } - - @WithTransaction - async useTokenOnEnemyBoard( - game: Document & Game, - targetPlayer: Player, - token: Effect, - target: Card, - session?: mongoose.mongo.ClientSession, - ) {} } diff --git a/src/games/utils.ts b/src/games/utils.ts index 5f6ad3b..abbba08 100644 --- a/src/games/utils.ts +++ b/src/games/utils.ts @@ -25,13 +25,24 @@ export function getPlayer(session: Record, game: Game) { throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED); } - return findPlayer(user._id, game); + return findUser(user._id, game); +} + +export function findUser(playerId: string, game: Game) { + for (const team of game.teams) { + for (const player of team.players) { + if (player.user._id.equals(playerId)) { + return player as Document & Player; + } + } + } + throw new HttpException('User not found in game', HttpStatus.FORBIDDEN); } export function findPlayer(playerId: string, game: Game) { for (const team of game.teams) { for (const player of team.players) { - if (player.user._id.equals(playerId)) { + if (player._id.equals(playerId)) { return player as Document & Player; } }