From 8ca627a0477c1e07a2f42ad698dabc71ce334e55 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Sat, 13 Jul 2024 16:49:31 +0200 Subject: [PATCH] Feat : add Prepare token --- src/games/controllers/turn.controller.ts | 103 ++++++++++++++++++++++- src/games/dto/use-token-dto.ts | 4 + src/games/services/effect.functions.ts | 54 +++++++++++- src/games/services/turn.service.ts | 38 +++++++++ src/games/utils.ts | 12 +-- 5 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 src/games/dto/use-token-dto.ts diff --git a/src/games/controllers/turn.controller.ts b/src/games/controllers/turn.controller.ts index 788520e..c7fbbba 100644 --- a/src/games/controllers/turn.controller.ts +++ b/src/games/controllers/turn.controller.ts @@ -1,4 +1,5 @@ import { + Body, Controller, HttpException, HttpStatus, @@ -9,23 +10,27 @@ import { } from '@nestjs/common'; import { AuthGuard } from '../guards/auth.guard'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { Game } from '../schemas/game.schema'; +import { Game, Player } from '../schemas/game.schema'; import { Document, Model } from 'mongoose'; import { GameStartedPipe } from '../pipe/game.pipe'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { TurnService } from '../services/turn.service'; -import { getPlayer, getCurrentTeam, isInTeam } from '../utils'; +import { getPlayer, getCurrentTeam, isInTeam, findPlayer } from '../utils'; import { CardPipe } from '../pipe/card.pipe'; 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 { UseTokenDTO } from '../dto/use-token-dto'; @ApiTags('turn') @Controller('games/:gameId/turn') export class TurnController { constructor( private readonly turnService: TurnService, + private readonly gameService: GameService, private eventEmitter: EventEmitter2, ) {} @@ -88,6 +93,7 @@ export class TurnController { throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); } await this.turnService.buyGeneric(game, currentPlayer, card, game.market); + await this.gameService.fillMarket(game); } @Post('buyGem') @@ -152,4 +158,97 @@ export class TurnController { await this.turnService.lockEffect(game, currentPlayer, effect, card); } + + @Post('makeDiscard/:playerId') + @ApiOperation({ summary: 'Use a token to make discard a player' }) + @UseGuards(AuthGuard) + async makeDiscard( + @Param('gameId') _gameId: string, + @Param('playerId') _playerId: string, + @Param('gameId', GameStartedPipe) game: Document & Game, + @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, + ); + } + + const targetPlayer = findPlayer(_playerId, game); + if (!isInTeam(currentTeam, targetPlayer)) { + throw new HttpException( + 'You cannot target players in the same team as yours', + HttpStatus.FORBIDDEN, + ); + } + + const targetToken = game.currentTurn.tokens.find( + (t) => t.effect == CardEffects.MAKE_DISCARD, + ); + if (!targetToken) { + throw new HttpException('Token not found', HttpStatus.NOT_FOUND); + } + await this.turnService.makeDiscard(game, targetToken, targetPlayer); + } + + @Post('useToken/:tokenId') + @ApiOperation({ summary: 'Use a token' }) + @UseGuards(AuthGuard) + async useToken( + @Param('gameId') _gameId: string, + @Param('tokenId') _tokenId: string, + @Param('gameId', GameStartedPipe) game: Document & Game, + @Param('tokenId', EffectPipe) token: Document & Effect, + @Body() useTokenDto: UseTokenDTO, + @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 (!game.currentTurn.tokens.some((t) => t._id.equals(token._id))) { + throw new HttpException('Token not found', HttpStatus.NOT_FOUND); + } + + const targetPlayer = findPlayer(useTokenDto.playerId, game); + + const enemyTargetToken = [ + CardEffects.STUN, + CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE, + ]; + if (enemyTargetToken.includes(token.effect)) { + const 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) => + c._id.equals(useTokenDto.cardId), + ); + if (!targetCard) { + throw new HttpException('Card not found', HttpStatus.NOT_FOUND); + } + await this.turnService.useTokenOnEnemyBoard( + game, + currentPlayer, + token, + targetCard, + ); + } + } } diff --git a/src/games/dto/use-token-dto.ts b/src/games/dto/use-token-dto.ts new file mode 100644 index 0000000..a43e0ed --- /dev/null +++ b/src/games/dto/use-token-dto.ts @@ -0,0 +1,4 @@ +export class UseTokenDTO { + playerId: string; + cardId: string; +} diff --git a/src/games/services/effect.functions.ts b/src/games/services/effect.functions.ts index c1bef9c..6720ee9 100644 --- a/src/games/services/effect.functions.ts +++ b/src/games/services/effect.functions.ts @@ -2,7 +2,12 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { Game, Player } from '../schemas/game.schema'; import mongoose, { Document } from 'mongoose'; import { Card } from 'src/cards/schemas/cards.schema'; -import { CardEffects, EffectType, Per } from 'src/cards/schemas/cards.types'; +import { + CardEffects, + CardType, + EffectType, + Per, +} from 'src/cards/schemas/cards.types'; import { Effect, EffectCondition } from 'src/cards/schemas/effect.schema'; import { TurnService } from './turn.service'; import { GameService } from './games.service'; @@ -165,6 +170,53 @@ export const effectMappings = { ) => Promise >; +export const tokenMappings = { + [CardEffects.PREPARE]: async ( + game: Document & Game, + effect: Effect, + player: Player, + card: Card, + gameService: GameService, + eventEmitter: EventEmitter2, + _session: mongoose.mongo.ClientSession, + ) => { + if (card.cardType !== CardType.CHAMPION) { + throw new Error('Cannot Prepare : Card is not a champion'); + } + const targetEffects = card.effects.filter( + (e) => e.condition.effectId == EffectType.CHAMPION_ACTION, + ); + + if ( + !targetEffects.every((e) => + game.currentTurn.lockedEffects.some((ee) => ee._id.equals(e._id)), + ) + ) { + throw new Error('Cannot Prepare : champion effect not locked'); + } + + for (const e of targetEffects) { + const index = game.currentTurn.lockedEffects.findIndex((ee) => + ee._id.equals(e._id), + ); + game.currentTurn.lockedEffects.splice(index, 1); + eventEmitter.emit('sse.game.' + game._id.toHexString(), { + type: 'unlockEffect', + effect: effect._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, +}; + export const conditionsMappings = { [EffectType.CHAMPION_ACTION]: async () => true, [EffectType.FACTION_COMBO]: async ( diff --git a/src/games/services/turn.service.ts b/src/games/services/turn.service.ts index 63a1337..6ea2bea 100644 --- a/src/games/services/turn.service.ts +++ b/src/games/services/turn.service.ts @@ -223,4 +223,42 @@ export class TurnService { ); await game.save({ session }); } + + @WithTransaction + async makeDiscard( + game: Document & Game, + token: Effect, + target: Player, + session?: mongoose.mongo.ClientSession, + ) { + target.fuckUMarkers++; + const index = game.currentTurn.tokens.findIndex((t) => + t._id.equals(token._id), + ); + if (index == -1) { + throw new Error('Token not found'); + } + game.currentTurn.tokens.splice(index, 1); + + await game.save({ session }); + } + + @WithTransaction + async useToken( + game: Document & Game, + token: Effect, + target: Card, + session?: mongoose.mongo.ClientSession, + ) { + return; + } + + @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 fbb6a61..5f6ad3b 100644 --- a/src/games/utils.ts +++ b/src/games/utils.ts @@ -24,17 +24,19 @@ export function getPlayer(session: Record, game: Game) { if (!user) { throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED); } + + return findPlayer(user._id, game); +} + +export function findPlayer(playerId: string, game: Game) { for (const team of game.teams) { for (const player of team.players) { - if (player.user._id.equals(user._id)) { + if (player.user._id.equals(playerId)) { return player as Document & Player; } } } - throw new HttpException( - 'You are not part of this game', - HttpStatus.FORBIDDEN, - ); + throw new HttpException('Player not found in game', HttpStatus.FORBIDDEN); } export function isInTeam(team: Team, player: Player) {