diff --git a/src/games/controllers/turn.controller.ts b/src/games/controllers/turn.controller.ts index a240571..6d5439f 100644 --- a/src/games/controllers/turn.controller.ts +++ b/src/games/controllers/turn.controller.ts @@ -28,8 +28,11 @@ import { Card } from 'src/cards/schemas/cards.schema'; import { EffectPipe } from '../pipe/effect.pipe'; import { Effect } from 'src/cards/schemas/effect.schema'; import { GameService } from '../services/games.service'; -import { CardEffects } from 'src/cards/schemas/cards.types'; +import { CardEffects, CardType } from 'src/cards/schemas/cards.types'; import { UseTokenDTO } from '../dto/use-token-dto'; +import { DamagePlayerDTO } from '../dto/damage-player-dto'; +import { DamageChampionDTO } from '../dto/damage-champion-dto'; +import { equal } from 'assert'; @ApiTags('turn') @Controller('games/:gameId/turn') @@ -228,18 +231,27 @@ export class TurnController { throw new HttpException('Token not found', HttpStatus.NOT_FOUND); } - const targetPlayer = findPlayer(useTokenDto.playerId, game); - - const enemyTargetToken = [ + const enemyTargetTokens = [ CardEffects.STUN, CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE, ]; + const marketTokens = [ + CardEffects.STACK_NEXT_ACTION_BOUGHT, + CardEffects.STACK_NEXT_CARD_BOUGHT, + ]; + let targetCard: Card; - if (enemyTargetToken.includes(token.effect)) { + let targetPlayer = currentPlayer; + if (enemyTargetTokens.includes(token.effect)) { + targetPlayer = findPlayer(useTokenDto.playerId, game); targetCard = targetPlayer.board.cards.find((c) => c._id.equals(useTokenDto.cardId), ); + } else if (marketTokens.includes(token.effect)) { + targetCard = + game.market.cards.find((c) => c._id.equals(useTokenDto.cardId)) ?? + game.fireGems.cards.find((c) => c._id.equals(useTokenDto.cardId)); } else { targetCard = currentPlayer.board.cards.find((c) => c._id.equals(useTokenDto.cardId), @@ -249,6 +261,82 @@ export class TurnController { if (!targetCard) { throw new HttpException('Card not found', HttpStatus.NOT_FOUND); } - await this.turnService.useToken(game, currentPlayer, token, targetCard); + await this.turnService.useToken(game, targetPlayer, token, targetCard); + } + + @Post('damagePlayer/:playerId') + @ApiOperation({ summary: 'Consume damage points to damage a player' }) + @UseGuards(AuthGuard) + async damagePlayer( + @Param('gameId') _gameId: string, + @Param('playerId') playerId: string, + @Param('gameId', GameStartedPipe) game: Document & Game, + @Body() damagePlayerDTO: DamagePlayerDTO, + @Session() session: Record, + ) { + const currentPlayer = getPlayer(session, game); + const currentTeam = getCurrentTeam(game); + if (!isInTeam(currentTeam, currentPlayer)) { + throw new HttpException( + 'It is not your turn to play', + HttpStatus.FORBIDDEN, + ); + } + if (damagePlayerDTO.amount > game.currentTurn.damage) { + throw new HttpException( + 'You cannot deal more damage than what your current damage points', + HttpStatus.BAD_REQUEST, + ); + } + + const targetPlayer = findPlayer(playerId, game); + await this.turnService.damagePlayer( + game, + damagePlayerDTO.amount, + currentPlayer, + targetPlayer, + ); + } + + @Post('damageChampion/:cardId') + @ApiOperation({ summary: 'Consume damage points to stun a champion' }) + @UseGuards(AuthGuard) + async damageChampion( + @Param('gameId') _gameId: string, + @Param('cardId') _cardId: string, + @Param('gameId', GameStartedPipe) game: Document & Game, + @Param('cardId', CardPipe) card: Document & Card, + @Body() damageChampion: DamageChampionDTO, + @Session() session: Record, + ) { + const currentPlayer = getPlayer(session, game); + const currentTeam = getCurrentTeam(game); + if (!isInTeam(currentTeam, currentPlayer)) { + throw new HttpException( + 'It is not your turn to play', + HttpStatus.FORBIDDEN, + ); + } + if (card.defense > game.currentTurn.damage) { + throw new HttpException( + 'You cannot deal more damage than what your current damage points', + HttpStatus.BAD_REQUEST, + ); + } + + const targetPlayer = findPlayer(damageChampion.player, game); + if (!targetPlayer) { + throw new HttpException('Player not found', HttpStatus.NOT_FOUND); + } + if (card.cardType != CardType.CHAMPION) { + throw new HttpException('Card is not a champion', HttpStatus.BAD_REQUEST); + } + + if (!targetPlayer.board.cards.some((c) => c._id.equals(card._id))) { + throw new HttpException( + 'Champion not found in player board', + HttpStatus.NOT_FOUND, + ); + } } } diff --git a/src/games/dto/damage-champion-dto.ts b/src/games/dto/damage-champion-dto.ts new file mode 100644 index 0000000..fb2638d --- /dev/null +++ b/src/games/dto/damage-champion-dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DamageChampionDTO { + @ApiProperty() + player: string; +} diff --git a/src/games/dto/damage-player-dto.ts b/src/games/dto/damage-player-dto.ts new file mode 100644 index 0000000..6754470 --- /dev/null +++ b/src/games/dto/damage-player-dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DamagePlayerDTO { + @ApiProperty() + amount: number; +} diff --git a/src/games/services/effect.functions.ts b/src/games/services/effect.functions.ts index 1287605..ca6773f 100644 --- a/src/games/services/effect.functions.ts +++ b/src/games/services/effect.functions.ts @@ -309,7 +309,7 @@ export const tokenMappings = { eventEmitter: EventEmitter2, session: mongoose.mongo.ClientSession, ) => { - if (card.cardType != CardType.CHAMPION) { + if (card.cardType != CardType.ACTION) { throw new HttpException( 'Cannot stack : Card is not an action', HttpStatus.BAD_REQUEST, @@ -322,6 +322,7 @@ export const tokenMappings = { player.stack, session, ); + await gameService.fillMarket(game); }, [CardEffects.STACK_NEXT_CARD_BOUGHT]: async ( game: Document & Game, @@ -345,6 +346,7 @@ export const tokenMappings = { player.stack, session, ); + await gameService.fillMarket(game); }, [CardEffects.PLAY_NEXT_CARD_BOUGHT]: async ( game: Document & Game, @@ -368,6 +370,7 @@ export const tokenMappings = { player.board, session, ); + await gameService.fillMarket(game); }, [CardEffects.DRAW_AND_DISCARD]: async ( game: Document & Game, diff --git a/src/games/services/games.service.ts b/src/games/services/games.service.ts index 18e5d86..4da68e2 100644 --- a/src/games/services/games.service.ts +++ b/src/games/services/games.service.ts @@ -6,7 +6,7 @@ import { Injectable, } from '@nestjs/common'; import { InjectModel, InjectConnection } from '@nestjs/mongoose'; -import { Container, Game, Player } from '../schemas/game.schema'; +import { Container, Game, Player, Team } from '../schemas/game.schema'; import mongoose, { Document, Model } from 'mongoose'; import { Card } from 'src/cards/schemas/cards.schema'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -248,4 +248,33 @@ export class GameService { await game.save({ session }); } + + @WithTransaction + async killTeam( + game: Document & Game, + team: Team, + session?: mongoose.mongo.ClientSession, + ) { + const index = game.teams.findIndex((t) => t._id.equals(team._id)); + game.teams.splice(index, 1); + if (game.teams.length <= 1) { + await this.endGame(game, session); + } + this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'killTeam', + teamId: team._id, + }); + await game.save({ session }); + } + + @WithTransaction + async endGame( + game: Document & Game, + session?: mongoose.mongo.ClientSession, + ) { + this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'endGame', + }); + await game.deleteOne({ session }); + } } diff --git a/src/games/services/turn.service.ts b/src/games/services/turn.service.ts index c035842..053e695 100644 --- a/src/games/services/turn.service.ts +++ b/src/games/services/turn.service.ts @@ -5,7 +5,7 @@ import { Inject, Injectable, } from '@nestjs/common'; -import { Container, Game, Player } from '../schemas/game.schema'; +import { Container, Game, Player, Team } from '../schemas/game.schema'; import mongoose, { Document, Model } from 'mongoose'; import { Card } from 'src/cards/schemas/cards.schema'; import { EventEmitter2 } from '@nestjs/event-emitter'; @@ -16,7 +16,7 @@ import { CardType, EffectType, } from 'src/cards/schemas/cards.types'; -import { WithTransaction } from '../utils'; +import { getPlayerTeam, WithTransaction } from '../utils'; import { conditionsMappings, effectMappings, @@ -275,4 +275,36 @@ export class TurnService { }); await game.save({ session }); } + + @WithTransaction + async damagePlayer( + game: Document & Game, + amount: number, + player: Player, + targetPlayer: Player, + session?: mongoose.mongo.ClientSession, + ) { + const team = getPlayerTeam(game, targetPlayer); + team.health -= amount; + game.currentTurn.damage -= amount; + + this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'currentTurn.updateDamage', + damage: game.currentTurn.damage, + operation: -amount, + }); + + this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'team.updateHealth', + team: team._id, + health: team.health, + operation: -amount, + }); + + if (team.health <= 0) { + this.gamesService.killTeam(game, team, session); + } + + await game.save({ session }); + } } diff --git a/src/games/utils.ts b/src/games/utils.ts index abbba08..3566364 100644 --- a/src/games/utils.ts +++ b/src/games/utils.ts @@ -57,6 +57,11 @@ export function isInTeam(team: Team, player: Player) { return false; } +export function getPlayerTeam(game: Game, player: Player) { + return game.teams.find((t) => + t.players.some((p) => p._id.equals(player._id)), + ); +} export function WithTransaction( target: any, propertyKey: string, @@ -66,30 +71,11 @@ export function WithTransaction( descriptor.value = function (...args: any[]) { return new Promise(async (resolve, reject) => { - if (args.at(-1) instanceof mongoose.mongo.ClientSession) { - const data = await originalFunc.apply(this, args).catch((e) => { - if (e instanceof HttpException) { - throw e; - } - reject( - new HttpException( - 'Another request is processing', - HttpStatus.TOO_MANY_REQUESTS, - ), - ); - }); - resolve(data); - return data; - } else { - await this.connection - .transaction(async (session) => { - const data = await originalFunc.apply(this, [...args, session]); - resolve(data); - return data; - }) - .catch((e) => { + try { + if (args.at(-1) instanceof mongoose.mongo.ClientSession) { + const data = await originalFunc.apply(this, args).catch((e) => { if (e instanceof HttpException) { - reject(e); + throw e; } reject( new HttpException( @@ -98,6 +84,29 @@ export function WithTransaction( ), ); }); + resolve(data); + return data; + } else { + await this.connection + .transaction(async (session) => { + const data = await originalFunc.apply(this, [...args, session]); + resolve(data); + return data; + }) + .catch((e) => { + if (e instanceof HttpException) { + reject(e); + } + reject( + new HttpException( + 'Another request is processing', + HttpStatus.TOO_MANY_REQUESTS, + ), + ); + }); + } + } catch (e) { + reject(e); } }); };