Initial commit
This commit is contained in:
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -6,17 +6,23 @@ import { GamesModule } from './games/games.module';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from './users/schemas/user.entity';
|
||||
import { Game } from './games/schemas/game.schema';
|
||||
import { Topic } from './words/schemas/topic.schema';
|
||||
import { Word } from './words/schemas/word.schema';
|
||||
import { Player } from './games/schemas/player.schema';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
EventEmitterModule.forRoot(),
|
||||
ConfigModule.forRoot(),
|
||||
TypeOrmModule.forRoot({
|
||||
type:"postgres",
|
||||
type: 'postgres',
|
||||
url: process.env.PG_ENDPOINT,
|
||||
password: process.env.PG_PASSWORD,
|
||||
username: process.env.PG_USER,
|
||||
synchronize: process.env.DEV_MODE=="true"
|
||||
synchronize: process.env.DEV_MODE == 'true',
|
||||
entities: [User, Player, Game, Topic, Word],
|
||||
}),
|
||||
GamesModule,
|
||||
UsersModule,
|
||||
|
||||
@@ -9,20 +9,15 @@ import {
|
||||
Sse,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { LobbyService } from '../services/games.lobby.service';
|
||||
import { LobbyService } from '../services/lobby.service';
|
||||
import { AuthGuard } from '../guards/auth.guard';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { StartingDeckDto } from '../dto/update-game.dto';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Game } from '../schemas/game.schema';
|
||||
import mongoose, { Document } from 'mongoose';
|
||||
import { GamePipe, GameStartedPipe } from '../pipe/game.pipe';
|
||||
|
||||
import { Observable, fromEvent, map } from 'rxjs';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { GameService } from '../services/games.service';
|
||||
import { getCurrentTeam, getPlayer, isInTeam, WithTransaction } from '../utils';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
import { ReadTeamDto } from '../dto/read-games.dto';
|
||||
|
||||
@ApiTags('game')
|
||||
@Controller('games/:id')
|
||||
@@ -33,76 +28,57 @@ export class GamesController {
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
@Post('changeStartingDeck')
|
||||
@ApiOperation({ summary: 'Pick a different starting deck' })
|
||||
@Post('/start')
|
||||
@ApiOperation({ summary: 'Start game' })
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'Game successfully started.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad Request',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 403,
|
||||
description: 'You can only start games you own',
|
||||
})
|
||||
@UseGuards(AuthGuard)
|
||||
async changeStartingDeck(
|
||||
@Param('id') id: string,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
@Body() startingDeckDto: StartingDeckDto,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
if (game.started) {
|
||||
throw new HttpException(
|
||||
'You cannot do that after the game has started',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const player = getPlayer(session, game);
|
||||
await this.lobbyService.changeStartingDeck(startingDeckDto, player, game);
|
||||
}
|
||||
|
||||
@Post('endTurn')
|
||||
@UseGuards(AuthGuard)
|
||||
async endTurn(
|
||||
@Param('id') id: string,
|
||||
@Param('id', GameStartedPipe) game: Document<Game> & Game,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
const currentTeam = getCurrentTeam(game);
|
||||
const currentPlayer = getPlayer(session, game);
|
||||
|
||||
if (!isInTeam(currentTeam, currentPlayer)) {
|
||||
throw new HttpException(
|
||||
'It is not your turn to play',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) {
|
||||
throw new HttpException(
|
||||
'You must consume all your discard markers before ending your turn',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
await this.gameService.endTurn(game);
|
||||
}
|
||||
|
||||
@Post('leave')
|
||||
@ApiOperation({ summary: "Leave a game that hasn't already started" })
|
||||
@UseGuards(AuthGuard)
|
||||
async leave(
|
||||
async start(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
@Param('id', GamePipe) game: Game,
|
||||
) {
|
||||
if (game.started) {
|
||||
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return await this.gameService.leaveGame(game, session.user);
|
||||
if (game.players.length < 2 && !process.env.DEV_MODE) {
|
||||
throw new HttpException(
|
||||
'Not enough teams to start the game',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (game.owner.userId != session.user.userId) {
|
||||
throw new HttpException(
|
||||
'You can only start games you own',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
await this.gameService.start(game);
|
||||
return;
|
||||
}
|
||||
|
||||
@Post('surrender')
|
||||
@ApiOperation({ summary: "Leave a game that hasn't already started" })
|
||||
@ApiOperation({ summary: 'Leave a game that has already started' })
|
||||
@UseGuards(AuthGuard)
|
||||
async surrender(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
@Param('id', GamePipe) game: Game,
|
||||
) {
|
||||
if (!game.started) {
|
||||
throw new HttpException('Game has not started', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return await this.gameService.leaveGame(game, session.user);
|
||||
return await this.lobbyService.leaveGame(game, session.user);
|
||||
}
|
||||
|
||||
@Post('kick/:playerId')
|
||||
@@ -111,7 +87,7 @@ export class GamesController {
|
||||
async kick(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
@Param('id', GamePipe) game: Game,
|
||||
@Param('playerId') playerId: string,
|
||||
) {
|
||||
if (session.user.userId != game.owner.userId) {
|
||||
@@ -120,15 +96,11 @@ export class GamesController {
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
return await this.gameService.kickPlayer(game, session.user, playerId);
|
||||
return await this.lobbyService.kickPlayer(game, session.user, playerId);
|
||||
}
|
||||
|
||||
@Sse('/subscribe')
|
||||
subscribe(
|
||||
@Param('id') id: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@Param('id', GamePipe) _game: Document<Game> & Game,
|
||||
): Observable<{ data: string }> {
|
||||
subscribe(@Param('id') id: string): Observable<{ data: string }> {
|
||||
return fromEvent(this.eventEmitter, 'sse.game.' + id).pipe(
|
||||
map((payload: any) => {
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LobbyController } from './games.lobby.controller';
|
||||
import { LobbyService } from '../services/games.lobby.service';
|
||||
import { LobbyService } from '../services/lobby.service';
|
||||
|
||||
describe('GamesController', () => {
|
||||
let controller: LobbyController;
|
||||
|
||||
@@ -12,14 +12,11 @@ import {
|
||||
Body,
|
||||
Sse,
|
||||
} from '@nestjs/common';
|
||||
import { LobbyService } from '../services/games.lobby.service';
|
||||
import { LobbyService } from '../services/lobby.service';
|
||||
import { AuthGuard } from '../guards/auth.guard';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { ReadGameDto } from '../dto/read-games.dto';
|
||||
import { UpdateGameDto } from '../dto/update-game.dto';
|
||||
import { GamePipe } from '../pipe/game.pipe';
|
||||
import { Game } from '../schemas/game.schema';
|
||||
import { Document } from 'mongoose';
|
||||
import { Observable, fromEvent, map } from 'rxjs';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
@@ -36,7 +33,7 @@ export class LobbyController {
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'Game successfully created.',
|
||||
type: ReadGameDto,
|
||||
// type: ReadGameDto,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
@@ -45,7 +42,7 @@ export class LobbyController {
|
||||
@ApiResponse({ status: 403, description: 'Forbidden.' })
|
||||
@UseGuards(AuthGuard)
|
||||
async create(@Session() session: Record<string, any>) {
|
||||
const games = await this.lobbyService.findByOwner(session.user._id);
|
||||
const games = await this.lobbyService.findByOwner(session.user);
|
||||
if (games) {
|
||||
throw new HttpException(
|
||||
'You can only create one game at a time',
|
||||
@@ -76,25 +73,8 @@ export class LobbyController {
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get game information' })
|
||||
async findOne(@Param('id') id: string, @Param('id', GamePipe) game: Game) {
|
||||
return new ReadGameDto(game);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiOperation({ summary: 'Edit game settings' })
|
||||
@UseGuards(AuthGuard)
|
||||
async update(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Body() updateGameDto: UpdateGameDto,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
) {
|
||||
if (game.owner._id.toString() != session.user._id) {
|
||||
throw new HttpException(
|
||||
'You can only edit games you own',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
return this.lobbyService.update(game, updateGameDto);
|
||||
return game;
|
||||
// return new ReadGameDto(game);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@@ -103,57 +83,15 @@ export class LobbyController {
|
||||
async remove(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
@Param('id', GamePipe) game: Game,
|
||||
) {
|
||||
if (game.owner._id.toString() != session.user._id) {
|
||||
if (game.owner.userId == session.user.userId) {
|
||||
throw new HttpException(
|
||||
'You can only delete games you own',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
const deleted = await this.lobbyService.remove(game);
|
||||
if (deleted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Post(':id/start')
|
||||
@ApiOperation({ summary: 'Start game' })
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'Game successfully started.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Bad Request',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 403,
|
||||
description: 'You can only start games you own',
|
||||
})
|
||||
@UseGuards(AuthGuard)
|
||||
async start(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
) {
|
||||
if (game.started) {
|
||||
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (game.teams.length < 2 && !process.env.DEV_MODE) {
|
||||
throw new HttpException(
|
||||
'Not enough teams to start the game',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (game.owner._id.toString() != session.user._id) {
|
||||
throw new HttpException(
|
||||
'You can only start games you own',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
await this.lobbyService.start(game);
|
||||
return;
|
||||
await this.lobbyService.remove(game);
|
||||
}
|
||||
|
||||
@Post(':id/join')
|
||||
@@ -162,17 +100,13 @@ export class LobbyController {
|
||||
async join(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Document<Game> & Game,
|
||||
@Param('id', GamePipe) game: Game,
|
||||
) {
|
||||
if (game.started) {
|
||||
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (
|
||||
game.teams
|
||||
.flatMap((t) => t.players)
|
||||
.some((p) => p.user.userId == session.user.userId)
|
||||
) {
|
||||
if (game.players.some((p) => p.user.userId == session.user.userId)) {
|
||||
throw new HttpException(
|
||||
'You are already in this game',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
@@ -180,4 +114,18 @@ export class LobbyController {
|
||||
}
|
||||
return await this.lobbyService.joinGame(game, session.user);
|
||||
}
|
||||
|
||||
@Post(':id/leave')
|
||||
@ApiOperation({ summary: "Leave a game that hasn't already started" })
|
||||
@UseGuards(AuthGuard)
|
||||
async leave(
|
||||
@Param('id') id: string,
|
||||
@Session() session: Record<string, any>,
|
||||
@Param('id', GamePipe) game: Game,
|
||||
) {
|
||||
if (game.started) {
|
||||
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return await this.lobbyService.leaveGame(game, session.user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,461 +0,0 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Post,
|
||||
Session,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard } from '../guards/auth.guard';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
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,
|
||||
findUser,
|
||||
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, 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')
|
||||
export class TurnController {
|
||||
constructor(
|
||||
private readonly turnService: TurnService,
|
||||
private readonly gameService: GameService,
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
@Post('lockCard/:cardId')
|
||||
@ApiOperation({ summary: 'Lock a card' })
|
||||
@UseGuards(AuthGuard)
|
||||
async lockCard(
|
||||
@Param('gameId') _gameId: string,
|
||||
@Param('cardId') _cardId: string,
|
||||
@Param('gameId', GameStartedPipe) game: Document<Game> & Game,
|
||||
@Param('cardId', CardPipe) card: Document<Card> & Card,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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 (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) {
|
||||
throw new HttpException(
|
||||
'You cannot lock a card while having discard markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (game.currentTurn.cardsLocked.some((c) => c._id.equals(card._id))) {
|
||||
throw new HttpException('Card already locked', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (!currentPlayer.board.cards.some((c) => c._id.equals(card._id))) {
|
||||
throw new HttpException(
|
||||
'Card not found in player board',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.turnService.lockCard(game, currentPlayer, card);
|
||||
}
|
||||
|
||||
@Post('buyCard/:cardId')
|
||||
@ApiOperation({ summary: 'Buys a card from the market' })
|
||||
@UseGuards(AuthGuard)
|
||||
async buyCard(
|
||||
@Param('gameId') _gameId: string,
|
||||
@Param('cardId') _cardId: string,
|
||||
@Param('gameId', GameStartedPipe) game: Document<Game> & Game,
|
||||
@Param('cardId', CardPipe) card: Document<Card> & Card,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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 (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) {
|
||||
throw new HttpException(
|
||||
'You cannot buy a card while having discard markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (!game.market.cards.some((c) => c._id.equals(card._id))) {
|
||||
throw new HttpException(
|
||||
'Card not in current game market',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (game.currentTurn.gold < card.cost) {
|
||||
throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
await this.turnService.buyGeneric(
|
||||
game,
|
||||
card,
|
||||
game.market,
|
||||
currentPlayer.discard,
|
||||
);
|
||||
await this.gameService.fillMarket(game);
|
||||
}
|
||||
|
||||
@Post('buyGem')
|
||||
@ApiOperation({ summary: 'Buys a gem from the market' })
|
||||
@UseGuards(AuthGuard)
|
||||
async buyGem(
|
||||
@Param('gameId') _gameId: string,
|
||||
@Param('gameId', GameStartedPipe) game: Document<Game> & Game,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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 (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) {
|
||||
throw new HttpException(
|
||||
'You cannot buy a card while having discard markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (game.fireGems.cards.length < 1) {
|
||||
throw new HttpException('No Gems left', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const targetCard = game.fireGems.cards[0];
|
||||
await this.turnService.buyGeneric(
|
||||
game,
|
||||
targetCard,
|
||||
game.fireGems,
|
||||
currentPlayer.discard,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('lockEffect/:effectId')
|
||||
@ApiOperation({ summary: 'Lock an effect' })
|
||||
@UseGuards(AuthGuard)
|
||||
async lockEffect(
|
||||
@Param('gameId') _gameId: string,
|
||||
@Param('effectId') _effectId: string,
|
||||
@Param('gameId', GameStartedPipe) game: Document<Game> & Game,
|
||||
@Param('effectId', EffectPipe) effect: Document<Effect> & Effect,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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 (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) {
|
||||
throw new HttpException(
|
||||
'You cannot lock an effect while having discard markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (game.currentTurn.lockedEffects.some((e) => e._id.equals(effect._id))) {
|
||||
throw new HttpException('Effect already locked', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
const card = (await effect.populate('card')).card;
|
||||
if (!currentPlayer.board.cards.some((c) => c._id.equals(card._id))) {
|
||||
throw new HttpException(
|
||||
'Card not found in player board',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (!game.currentTurn.cardsLocked.some((c) => c._id.equals(card._id))) {
|
||||
throw new HttpException('Card not locked', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
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> & Game,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
if (
|
||||
targetPlayer.fuckUMarkers + targetPlayer.discardMarkers >=
|
||||
targetPlayer.hand.cards.length
|
||||
) {
|
||||
throw new HttpException(
|
||||
'You cannot add more discard markers to this player',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
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.addFuckUMarker(game, targetToken, targetPlayer);
|
||||
const index = game.currentTurn.tokens.findIndex((t) =>
|
||||
t._id.equals(targetToken._id),
|
||||
);
|
||||
game.currentTurn.tokens.splice(index, 1);
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.useToken',
|
||||
tokenId: targetToken._id.toHexString(),
|
||||
});
|
||||
}
|
||||
|
||||
@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> & Game,
|
||||
@Param('tokenId', EffectPipe) token: Document<Effect> & Effect,
|
||||
@Body() useTokenDto: UseTokenDTO,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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 enemyTargetTokens = [
|
||||
CardEffects.STUN,
|
||||
CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE,
|
||||
];
|
||||
|
||||
const marketTokens = [
|
||||
CardEffects.STACK_NEXT_ACTION_BOUGHT,
|
||||
CardEffects.STACK_NEXT_CARD_BOUGHT,
|
||||
CardEffects.PLAY_NEXT_CARD_BOUGHT,
|
||||
];
|
||||
|
||||
const discardTokens = [
|
||||
CardEffects.RESTACK_DISCARDED_ACTION,
|
||||
CardEffects.RESTACK_DISCARDED_CARD,
|
||||
CardEffects.RESTACK_DISCARDED_CHAMPION,
|
||||
CardEffects.SACRIFICE,
|
||||
];
|
||||
|
||||
let targetCard: Card;
|
||||
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 if (discardTokens.includes(token.effect)) {
|
||||
targetCard = targetPlayer.discard.cards.find((c) =>
|
||||
c._id.equals(useTokenDto.cardId),
|
||||
);
|
||||
} else {
|
||||
targetCard = currentPlayer.board.cards.find((c) =>
|
||||
c._id.equals(useTokenDto.cardId),
|
||||
);
|
||||
}
|
||||
|
||||
if (!targetCard && token.effect == CardEffects.SACRIFICE) {
|
||||
targetCard = currentPlayer.board.cards.find((c) =>
|
||||
c._id.equals(useTokenDto.cardId),
|
||||
);
|
||||
}
|
||||
|
||||
if (token.effect != CardEffects.DRAW_AND_DISCARD) {
|
||||
if (currentPlayer.discardMarkers || currentPlayer.fuckUMarkers) {
|
||||
throw new HttpException(
|
||||
'You cannot use a token while having discard markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
if (!targetCard) {
|
||||
throw new HttpException('Card not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
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> & Game,
|
||||
@Body() damagePlayerDTO: DamagePlayerDTO,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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);
|
||||
if (targetPlayer.board.cards.some((c) => c.guard)) {
|
||||
throw new HttpException(
|
||||
'You cannot target this player while there is a guard',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
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> & Game,
|
||||
@Param('cardId', CardPipe) card: Document<Card> & Card,
|
||||
@Body() damageChampion: DamageChampionDTO,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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.playerId, 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,
|
||||
);
|
||||
}
|
||||
|
||||
if (!card.guard) {
|
||||
if (targetPlayer.board.cards.some((c) => c.guard)) {
|
||||
throw new HttpException(
|
||||
'You cannot target this champion while there is a guard',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
await this.turnService.damageChampion(game, targetPlayer, card);
|
||||
}
|
||||
|
||||
@Post('discardCard/:cardId')
|
||||
@ApiOperation({
|
||||
summary: 'Discard a card if you have fuckU or discard markers',
|
||||
})
|
||||
@UseGuards(AuthGuard)
|
||||
async discardCard(
|
||||
@Param('gameId') _gameId: string,
|
||||
@Param('cardId') _cardId: string,
|
||||
@Param('gameId', GameStartedPipe) game: Document<Game> & Game,
|
||||
@Param('cardId', CardPipe) card: Document<Card> & Card,
|
||||
@Session() session: Record<string, string>,
|
||||
) {
|
||||
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 (currentPlayer.discardMarkers == 0 && currentPlayer.fuckUMarkers == 0) {
|
||||
throw new HttpException(
|
||||
'You do not have any discard or fuckU markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentPlayer.board.cards.some((c) => c._id.equals(_cardId))) {
|
||||
throw new HttpException('Card not found in board', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.turnService.discardCard(game, currentPlayer, card);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LobbyService } from './services/games.lobby.service';
|
||||
import { LobbyService } from './services/lobby.service';
|
||||
import { LobbyController } from './controllers/games.lobby.controller';
|
||||
import { Game, GameSchema } from './schemas/game.schema';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { Game } from './schemas/game.schema';
|
||||
import { UsersModule } from 'src/users/users.module';
|
||||
import { CardsModule } from 'src/cards/cards.module';
|
||||
import { GamesController } from './controllers/games.controller';
|
||||
import { GameService } from './services/games.service';
|
||||
import { TurnService } from './services/turn.service';
|
||||
import { TurnController } from './controllers/turn.controller';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CardsModule,
|
||||
UsersModule,
|
||||
MongooseModule.forFeature([{ name: Game.name, schema: GameSchema }]),
|
||||
],
|
||||
controllers: [LobbyController, GamesController, TurnController],
|
||||
providers: [LobbyService, GameService, TurnService],
|
||||
imports: [UsersModule, TypeOrmModule.forFeature([Game])],
|
||||
controllers: [LobbyController, GamesController],
|
||||
providers: [LobbyService, GameService],
|
||||
})
|
||||
export class GamesModule {}
|
||||
|
||||
@@ -11,10 +11,12 @@ import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class GamePipe implements PipeTransform {
|
||||
constructor(@InjectRepository(Game) private gameRepository: Repository<Game>) {}
|
||||
constructor(
|
||||
@InjectRepository(Game) private gameRepository: Repository<Game>,
|
||||
) {}
|
||||
|
||||
async transform(value: any) {
|
||||
const game = await this.gameRepository.findBy({id: value});
|
||||
const game = await this.gameRepository.findBy({ id: value });
|
||||
if (!game) {
|
||||
throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
@@ -24,10 +26,12 @@ export class GamePipe implements PipeTransform {
|
||||
|
||||
@Injectable()
|
||||
export class GameStartedPipe implements PipeTransform {
|
||||
constructor(@InjectRepository(Game) private gameRepository: Repository<Game>) {}
|
||||
constructor(
|
||||
@InjectRepository(Game) private gameRepository: Repository<Game>,
|
||||
) {}
|
||||
|
||||
async transform(value: any) {
|
||||
const game = await this.gameRepository.findBy({id: value});
|
||||
const game = await this.gameRepository.findOne(value);
|
||||
if (!game) {
|
||||
throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Player } from './player.schema';
|
||||
|
||||
@Entity()
|
||||
@Unique(['value'])
|
||||
export class Concept {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
value: string;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Concept } from './concept.schema';
|
||||
import { Game } from './game.schema';
|
||||
|
||||
@Entity()
|
||||
export class ConceptInTurn {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Concept)
|
||||
concept: Concept;
|
||||
|
||||
@Column({ default: 0 })
|
||||
order: number;
|
||||
|
||||
@Column({ default: 0 })
|
||||
subconcept: number;
|
||||
|
||||
@Column({ default: 1 })
|
||||
markers: number;
|
||||
|
||||
@ManyToOne(() => Game)
|
||||
game: Promise<Game>;
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
import { Column, Entity, JoinTable, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { Player } from './player.schema';
|
||||
import { ConceptInTurn } from './concept_in_turn.schema';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
|
||||
@Entity()
|
||||
export class Game {
|
||||
@@ -7,12 +17,24 @@ export class Game {
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
seed: string
|
||||
seed: string;
|
||||
|
||||
@OneToOne(type=>Player, {onDelete:"CASCADE"})
|
||||
owner: Player
|
||||
@ManyToOne(() => User)
|
||||
owner: User;
|
||||
|
||||
@OneToMany(type=>Player, player => player.game, {onDelete:"CASCADE"})
|
||||
@OneToMany(() => Player, (player) => player.game, { onDelete: 'CASCADE' })
|
||||
@JoinTable()
|
||||
players: Player[]
|
||||
players: Player[];
|
||||
|
||||
@OneToOne(() => Player)
|
||||
currentPlayer: Player;
|
||||
|
||||
@Column()
|
||||
currentWord: string;
|
||||
|
||||
@Column()
|
||||
started: boolean;
|
||||
|
||||
@OneToMany(() => ConceptInTurn, (cit) => cit.game)
|
||||
currentConcepts: ConceptInTurn[];
|
||||
}
|
||||
@@ -1,16 +1,28 @@
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Game } from './game.schema';
|
||||
|
||||
@Entity()
|
||||
@Unique(["user, game"])
|
||||
export class Player{
|
||||
@Unique(['user, game'])
|
||||
export class Player {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number
|
||||
id: number;
|
||||
|
||||
@ManyToOne(type=>User)
|
||||
user: User
|
||||
@ManyToOne((type) => User)
|
||||
user: User;
|
||||
|
||||
@ManyToOne(type=>Game)
|
||||
game: Game
|
||||
@ManyToOne((type) => Game)
|
||||
game: Promise<Game>;
|
||||
|
||||
@Column()
|
||||
score: number;
|
||||
}
|
||||
|
||||
@@ -1,506 +0,0 @@
|
||||
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,
|
||||
CardRole,
|
||||
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';
|
||||
import { ReadEffectDto } from 'src/cards/dto/effect.dto';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
|
||||
export function isAutoActivable(effect: Effect) {
|
||||
if (
|
||||
!effect.mutex &&
|
||||
(!effect.condition ||
|
||||
( effect.condition.effectId == EffectType.CHAMPION_ACTION && !effect.per))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function addToken(
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
_player: Player,
|
||||
_card: Card,
|
||||
_gameService: GameService,
|
||||
eventEmitter: EventEmitter2,
|
||||
_session: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
game.currentTurn.tokens.push(effect);
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.addToken',
|
||||
token: new ReadEffectDto(effect),
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeToken(
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
eventEmitter: EventEmitter2,
|
||||
) {
|
||||
game.currentTurn.tokens.splice(
|
||||
game.currentTurn.tokens.findIndex((t) => t._id.equals(effect._id)),
|
||||
1,
|
||||
);
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.removeToken',
|
||||
token: effect._id,
|
||||
});
|
||||
}
|
||||
|
||||
export const effectMappings = {
|
||||
[CardEffects.GOLD]: async (
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
_player: Player,
|
||||
_card: Card,
|
||||
_gameService: GameService,
|
||||
eventEmitter: EventEmitter2,
|
||||
_session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
game.currentTurn.gold += effect.amount;
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'consumeEffect',
|
||||
effect: effect._id.toHexString(),
|
||||
});
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.updateGold',
|
||||
gold: game.currentTurn.gold,
|
||||
operation: effect.amount,
|
||||
});
|
||||
},
|
||||
|
||||
[CardEffects.DAMAGE]: async (
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
_player: Player,
|
||||
_card: Card,
|
||||
_gameService: GameService,
|
||||
eventEmitter: EventEmitter2,
|
||||
_session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
game.currentTurn.damage += effect.amount;
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'consumeEffect',
|
||||
effect: effect._id.toHexString(),
|
||||
});
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.updateDamage',
|
||||
damage: game.currentTurn.damage,
|
||||
operation: effect.amount,
|
||||
});
|
||||
},
|
||||
|
||||
[CardEffects.HEAL]: async (
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
player: Player,
|
||||
_card: Card,
|
||||
_gameService: GameService,
|
||||
eventEmitter: EventEmitter2,
|
||||
_session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
const team = game.teams.find((t) =>
|
||||
t.players.some((p) => p._id.equals(player._id)),
|
||||
);
|
||||
team.health += effect.amount;
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'consumeEffect',
|
||||
effect: effect._id.toHexString(),
|
||||
});
|
||||
eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'team.updateHealth',
|
||||
team: team._id,
|
||||
health: team.health,
|
||||
operation: effect.amount,
|
||||
});
|
||||
},
|
||||
|
||||
[CardEffects.DRAW]: async (
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
player: Player,
|
||||
_card: Card,
|
||||
gameService: GameService,
|
||||
_eventEmitter: EventEmitter2,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
await gameService.drawToBoard(effect.amount, player, game, session);
|
||||
},
|
||||
[CardEffects.PREPARE]: addToken,
|
||||
[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.BUY_FOR_FREE]: addToken,
|
||||
// [CardEffects.DISCARD_X_AND_DRAW_X]: addToken,
|
||||
// [CardEffects.PREPARE_ANOTHER_CHAMPION]: addToken,
|
||||
// [CardEffects.CHEAPER_CHAMPION]: addToken,
|
||||
// [CardEffects.CHEAPER_CHAMPIONS_PASSIVE]: addToken,
|
||||
// [CardEffects.CHEAPER_ACTION]: addToken,
|
||||
// [CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE]: addToken,
|
||||
|
||||
// [CardEffects.RESTACK_DISCARDED_ACTION]: addToken,
|
||||
|
||||
// [CardEffects.KEEP_IN_HAND]: addToken,
|
||||
// [CardEffects.BUY_GEM_FOR_FREE]: addToken,
|
||||
// [CardEffects.CHEAPER_SKILLS_PASSIVE]: addToken,
|
||||
// [CardEffects.CHEAPER_CARD_IF_HIGHER_PRICE]: addToken,
|
||||
// [CardEffects.PICK_FACTION]: addToken,
|
||||
} as Record<
|
||||
CardEffects,
|
||||
(
|
||||
game: Game,
|
||||
effect: Effect,
|
||||
player: Player,
|
||||
card: Card,
|
||||
gameService: GameService,
|
||||
eventEmitter: EventEmitter2,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => Promise<void>
|
||||
>;
|
||||
|
||||
export const tokenMappings = {
|
||||
[CardEffects.PREPARE]: async (
|
||||
game: Document<Game> & 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 Prepare : Card is not a champion',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
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 HttpException(
|
||||
'Cannot Prepare : champion effect not locked',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
for (const e of targetEffects) {
|
||||
await turnService.unlockEffect(game, player, e, card, session);
|
||||
}
|
||||
},
|
||||
[CardEffects.STUN]: async (
|
||||
game: Document<Game> & 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> & 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,
|
||||
);
|
||||
}
|
||||
if (player.discard.cards.some((c) => c._id.equals(card._id))) {
|
||||
await gameService.destroyCard(card, player.discard, game, session);
|
||||
} else if (player.board.cards.some((c) => c._id.equals(card._id))) {
|
||||
await gameService.destroyCard(card, player.board, game, session);
|
||||
} else {
|
||||
throw new HttpException(
|
||||
'Card not found in Discard or Board',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
},
|
||||
[CardEffects.RESTACK_DISCARDED_CHAMPION]: async (
|
||||
game: Document<Game> & 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> & 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> & Game,
|
||||
effect: Effect,
|
||||
player: Player,
|
||||
card: Card,
|
||||
gameService: GameService,
|
||||
turnService: TurnService,
|
||||
eventEmitter: EventEmitter2,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
if (card.cardType != CardType.ACTION) {
|
||||
throw new HttpException(
|
||||
'Cannot stack : Card is not an action',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
await turnService.buyGeneric(
|
||||
game,
|
||||
card,
|
||||
game.market,
|
||||
player.stack,
|
||||
session,
|
||||
);
|
||||
await gameService.fillMarket(game, session);
|
||||
},
|
||||
[CardEffects.STACK_NEXT_CARD_BOUGHT]: async (
|
||||
game: Document<Game> & 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,
|
||||
);
|
||||
await gameService.fillMarket(game, session);
|
||||
},
|
||||
[CardEffects.PLAY_NEXT_CARD_BOUGHT]: async (
|
||||
game: Document<Game> & 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,
|
||||
);
|
||||
await gameService.fillMarket(game, session);
|
||||
},
|
||||
[CardEffects.DRAW_AND_DISCARD]: async (
|
||||
game: Document<Game> & Game,
|
||||
effect: Effect,
|
||||
player: Player,
|
||||
_card: Card,
|
||||
gameService: GameService,
|
||||
turnService: TurnService,
|
||||
eventEmitter: EventEmitter2,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
if (
|
||||
game.currentTurn.drawAndDiscardCurrentEffect &&
|
||||
!game.currentTurn.drawAndDiscardCurrentEffect._id.equals(effect._id)
|
||||
) {
|
||||
throw new HttpException(
|
||||
'You cannot use a token while having discard markers',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
await gameService.drawToBoard(1, player, game, session);
|
||||
await turnService.addDiscardMarker(game, player, session);
|
||||
game.currentTurn.drawAndDiscardCurrentAmount++;
|
||||
game.currentTurn.drawAndDiscardCurrentEffect = effect;
|
||||
},
|
||||
} as Record<
|
||||
CardEffects,
|
||||
(
|
||||
game: Game,
|
||||
effect: Effect,
|
||||
player: Player,
|
||||
card: Card,
|
||||
gameService: GameService,
|
||||
turnService: TurnService,
|
||||
eventEmitter: EventEmitter2,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => Promise<void>
|
||||
>;
|
||||
|
||||
export const conditionsMappings = {
|
||||
[EffectType.CHAMPION_ACTION]: async () => true,
|
||||
[EffectType.FACTION_COMBO]: async (
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
card: Card,
|
||||
condition: EffectCondition,
|
||||
) => {
|
||||
return game.currentTurn.cardsLocked.some(
|
||||
(c) => c.faction == condition.faction && !c._id.equals(card._id),
|
||||
);
|
||||
},
|
||||
[EffectType.SUICIDE]: async (
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
card: Card,
|
||||
condition: EffectCondition,
|
||||
gameService: GameService,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => {
|
||||
await gameService.destroyCard(card, player.board, game, session);
|
||||
return true;
|
||||
},
|
||||
// [EffectTypeSpecial.ACTION_AMOUNT]: {},
|
||||
// [EffectTypeSpecial.CARD_AMOUNT]: {},
|
||||
// [EffectTypeSpecial.CHAMPION_AMOUNT]: {},
|
||||
// [EffectTypeSpecial.TOTAL_DAMAGE]: {},
|
||||
} as Record<
|
||||
string,
|
||||
(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
card: Card,
|
||||
condition: EffectCondition,
|
||||
gameService: GameService,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) => Promise<boolean>
|
||||
>;
|
||||
|
||||
export const perMapping = {
|
||||
[Per.CARD_OF_SAME_FACTION]: async (
|
||||
game: Document<Game> & Game,
|
||||
card: Card,
|
||||
) => {
|
||||
return game.currentTurn.cardsLocked.filter((c) => c.faction == card.faction)
|
||||
.length;
|
||||
},
|
||||
[Per.OTHER_CARD_OF_SAME_FACTION]: async (
|
||||
game: Document<Game> & Game,
|
||||
card: Card,
|
||||
) => {
|
||||
return game.currentTurn.cardsLocked.filter(
|
||||
(c) => c.faction == card.faction && !c._id.equals(card._id),
|
||||
).length;
|
||||
},
|
||||
[Per.CHAMPION]: async (game: Document<Game> & Game) => {
|
||||
return game.currentTurn.cardsLocked.filter((c) => c.defense).length;
|
||||
},
|
||||
[Per.CHAMPION_OF_SAME_FACTION]: async (
|
||||
game: Document<Game> & Game,
|
||||
card: Card,
|
||||
) => {
|
||||
return game.currentTurn.cardsLocked.filter(
|
||||
(c) => c.faction == card.faction && c.defense,
|
||||
).length;
|
||||
},
|
||||
[Per.OTHER_CHAMPION]: async (game: Document<Game> & Game, card: Card) => {
|
||||
return game.currentTurn.cardsLocked.filter(
|
||||
(c) => !c._id.equals(card._id) && c.defense,
|
||||
).length;
|
||||
},
|
||||
[Per.OTHER_GUARD]: async (game: Document<Game> & Game, card: Card) => {
|
||||
return game.currentTurn.cardsLocked.filter(
|
||||
(c) => !c._id.equals(card._id) && c.guard,
|
||||
).length;
|
||||
},
|
||||
[Per.STUNNED_CHAMPION]: async () => {
|
||||
console.error('per_stunned_champion not implemented');
|
||||
return 0;
|
||||
},
|
||||
[Per.OTHER_KNIFE_PLAYED]: async (game: Document<Game> & Game, card: Card) => {
|
||||
return game.currentTurn.cardsLocked.filter(
|
||||
(c) => !c._id.equals(card._id) && c.cardId == 80,
|
||||
).length;
|
||||
},
|
||||
} as Record<
|
||||
string,
|
||||
(game: Document<Game> & Game, card: Card) => Promise<number>
|
||||
>;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LobbyService } from './games.lobby.service';
|
||||
import { LobbyService } from './lobby.service';
|
||||
|
||||
describe('GamesService', () => {
|
||||
let service: LobbyService;
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { DatabaseObjectDto } from '../dto/database-object.dto';
|
||||
import { InjectConnection, InjectModel } from '@nestjs/mongoose';
|
||||
import { Game, Player, Team } from '../schemas/game.schema';
|
||||
import mongoose, { Document, Model } from 'mongoose';
|
||||
import {
|
||||
ReadContainerDto,
|
||||
ReadGameDto,
|
||||
ReadPlayerDto,
|
||||
ReadTeamDto,
|
||||
} from '../dto/read-games.dto';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { CardRole } from 'src/cards/schemas/cards.types';
|
||||
import { StartingDeckDto, UpdateGameDto } from '../dto/update-game.dto';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { GameService } from './games.service';
|
||||
import { WithTransaction } from '../utils';
|
||||
import { platform } from 'os';
|
||||
|
||||
@Injectable()
|
||||
export class LobbyService {
|
||||
constructor(
|
||||
@InjectModel(Game.name) private gameModel: Model<Game>,
|
||||
@InjectModel(Card.name) private cardModel: Model<Card>,
|
||||
@InjectConnection() private readonly connection: mongoose.Connection,
|
||||
private readonly gamesService: GameService,
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
@WithTransaction
|
||||
async create(owner: User, session?: mongoose.mongo.ClientSession) {
|
||||
const createdGame = new this.gameModel();
|
||||
createdGame.owner = owner;
|
||||
createdGame.packs = [
|
||||
'pack1_base_deck',
|
||||
'pack1_base_necros',
|
||||
'pack1_base_wild',
|
||||
'pack1_base_imperial',
|
||||
'pack1_base_guild',
|
||||
];
|
||||
await this.joinGame(createdGame, owner, session);
|
||||
const dto = new ReadGameDto(createdGame);
|
||||
this.eventEmitter.emit('sse.lobby', { type: 'create', ...dto });
|
||||
return dto;
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async joinGame(
|
||||
game: Document<Game> & Game,
|
||||
user: User,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
game.teams.push({
|
||||
players: [{ user } as Player],
|
||||
} as Team);
|
||||
|
||||
const team = game.teams.at(-1);
|
||||
// this.eventEmitter.emit('sse.lobby', {
|
||||
// type: 'joinGame',
|
||||
// team: new ReadTeamDto(team),
|
||||
// });
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'joinGame',
|
||||
team: new ReadTeamDto(team),
|
||||
game: game._id.toHexString(),
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'joinGame',
|
||||
team: new ReadTeamDto(team),
|
||||
});
|
||||
await game.save({ session });
|
||||
|
||||
return 'true!';
|
||||
}
|
||||
|
||||
async findAll(): Promise<ReadGameDto[]> {
|
||||
const games = await this.gameModel.find().exec();
|
||||
return await Promise.all(
|
||||
games.map(async (e) => {
|
||||
return await new ReadGameDto(e);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async findByOwner(_id: string): Promise<ReadGameDto> {
|
||||
const game = await this.gameModel.findOne({ owner: _id }).exec();
|
||||
if (!game) {
|
||||
return null;
|
||||
}
|
||||
return new ReadGameDto(game);
|
||||
}
|
||||
|
||||
async update(game: Document<Game> & Game, updateGameDto: UpdateGameDto) {
|
||||
game.packs = updateGameDto.packs;
|
||||
const packs = await this.cardModel.distinct('pack');
|
||||
if (updateGameDto.packs.every((e) => packs.includes(e))) {
|
||||
await game.save();
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'update',
|
||||
_id: game._id,
|
||||
...updateGameDto,
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'update',
|
||||
...updateGameDto,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new HttpException('Pack not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
async remove(game: Document<Game> & Game) {
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'delete',
|
||||
_id: game._id,
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'delete',
|
||||
});
|
||||
return await game.deleteOne();
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async start(
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const random = Math.floor(Math.random() * game.teams.length);
|
||||
game.currentTurn.currentTeam = game.teams[random]._id.toHexString();
|
||||
await game.save({ session });
|
||||
game.started = true;
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'start',
|
||||
game: game._id,
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'start',
|
||||
game: new ReadGameDto(game),
|
||||
});
|
||||
|
||||
let playerIndex = 0;
|
||||
for (const team of game.teams) {
|
||||
for (const player of team.players) {
|
||||
player.stack.cards = await this.cardModel
|
||||
.find({
|
||||
pack: { $in: game.packs },
|
||||
role: { $in: [player.class, player.race] },
|
||||
playerIndex,
|
||||
})
|
||||
.exec();
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'populateContainer',
|
||||
container: new ReadContainerDto(player.stack, true),
|
||||
});
|
||||
await this.gamesService.shuffleContainer(player.stack, game, session);
|
||||
playerIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
game.fireGems.cards = await this.cardModel.find({
|
||||
pack: { $in: game.packs },
|
||||
role: CardRole.FIRE_GEM,
|
||||
});
|
||||
|
||||
game.marketStack.cards = await this.cardModel.find({
|
||||
pack: { $in: game.packs },
|
||||
role: CardRole.MARKET,
|
||||
});
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'populateContainer',
|
||||
container: new ReadContainerDto(game.fireGems),
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'populateContainer',
|
||||
container: new ReadContainerDto(game.marketStack, true),
|
||||
});
|
||||
|
||||
await this.gamesService.shuffleContainer(game.marketStack, game, session);
|
||||
await this.gamesService.fillMarket(game, session);
|
||||
for (const team of game.teams) {
|
||||
for (const player of team.players) {
|
||||
if (team._id.equals(game.currentTurn.currentTeam)) {
|
||||
await this.gamesService.drawToHand(3, player, game, session);
|
||||
} else {
|
||||
await this.gamesService.drawToHand(5, player, game, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const currentTeam = game.teams.find((t) =>
|
||||
t._id.equals(game.currentTurn.currentTeam),
|
||||
);
|
||||
|
||||
for (const player of currentTeam.players) {
|
||||
await this.gamesService.distributeCards(
|
||||
player.hand.cards,
|
||||
player.hand,
|
||||
player.board,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
async changeStartingDeck(
|
||||
data: StartingDeckDto,
|
||||
player: Document<Player> & Player,
|
||||
game: Document<Game> & Game,
|
||||
) {
|
||||
const packs = await this.cardModel
|
||||
.find({ pack: { $in: game.packs } })
|
||||
.distinct('role');
|
||||
|
||||
if (
|
||||
!Object.values<string>(CardRole).includes(data.class) ||
|
||||
!Object.values<string>(CardRole).includes(data.race) ||
|
||||
data.race == CardRole.FIRE_GEM ||
|
||||
data.race == CardRole.MARKET ||
|
||||
data.class == CardRole.FIRE_GEM ||
|
||||
data.class == CardRole.MARKET
|
||||
) {
|
||||
throw new HttpException('Invalid race or class', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
if (
|
||||
packs.includes(data.class as CardRole) &&
|
||||
packs.includes(data.race as CardRole)
|
||||
) {
|
||||
player.class = data.class;
|
||||
player.race = data.race;
|
||||
await game.save();
|
||||
this.eventEmitter.emit('sse.game.changeStartingDeck', {
|
||||
_id: player._id,
|
||||
...data,
|
||||
});
|
||||
return;
|
||||
}
|
||||
throw new HttpException('Pack not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
@@ -5,367 +5,29 @@ import {
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { InjectModel, InjectConnection } from '@nestjs/mongoose';
|
||||
import { Container, Game, Player, Team } from '../schemas/game.schema';
|
||||
import mongoose, { Document, Model } from 'mongoose';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Game } from '../schemas/game.schema';
|
||||
import { Repository } from 'typeorm';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { WithTransaction, getCurrentTeam, shuffle } from '../utils';
|
||||
import { TurnService } from './turn.service';
|
||||
import { CardRole } from 'src/cards/schemas/cards.types';
|
||||
import { Player } from '../schemas/player.schema';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
import { ReadTeamDto } from '../dto/read-games.dto';
|
||||
|
||||
@Injectable()
|
||||
export class GameService {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => TurnService))
|
||||
private readonly turnService: TurnService,
|
||||
@InjectModel(Game.name) private gameModel: Model<Game>,
|
||||
@InjectModel(Card.name) private cardModel: Model<Card>,
|
||||
@InjectConnection() private readonly connection: mongoose.Connection,
|
||||
@InjectRepository(Game) private gameRepository: Repository<Game>,
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
@WithTransaction
|
||||
async endTurn(
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
let currentTeam = getCurrentTeam(game);
|
||||
|
||||
for (const player of currentTeam.players) {
|
||||
if (player.discardMarkers > 0) {
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'player.removeDiscardMarker',
|
||||
operation: -player.discardMarkers,
|
||||
discardMarkers: 0,
|
||||
playerId: player._id.toHexString(),
|
||||
});
|
||||
player.discardMarkers = 0;
|
||||
}
|
||||
|
||||
if (player.fuckUMarkers > 0) {
|
||||
player.fuckUMarkers--;
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'player.removeFuckUMarker',
|
||||
operation: -player.fuckUMarkers,
|
||||
fuckUMarkers: 0,
|
||||
playerId: player._id.toHexString(),
|
||||
});
|
||||
player.fuckUMarkers = 0;
|
||||
}
|
||||
|
||||
await this.distributeCards(
|
||||
player.board.cards.filter((c) => !c.defense),
|
||||
player.board,
|
||||
player.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
await this.drawToHand(5, player, game, session);
|
||||
}
|
||||
|
||||
const index = game.teams.indexOf(currentTeam);
|
||||
|
||||
game.currentTurn.currentTeam =
|
||||
game.teams[(index + 1) % game.teams.length]._id.toHexString();
|
||||
|
||||
game.currentTurn.cardsLocked = [];
|
||||
game.currentTurn.lockedEffects = [];
|
||||
game.currentTurn.tokens = [];
|
||||
game.currentTurn.damage = 0;
|
||||
game.currentTurn.gold = 0;
|
||||
|
||||
await game.save({ session });
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'endTurn',
|
||||
currentTurn: game.currentTurn,
|
||||
});
|
||||
|
||||
currentTeam = getCurrentTeam(game);
|
||||
|
||||
for (const player of currentTeam.players) {
|
||||
for (const card of player.board.cards) {
|
||||
if (card.defense) {
|
||||
await this.turnService.lockCard(game, player, card, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const player of currentTeam.players) {
|
||||
await this.distributeCards(
|
||||
player.hand.cards,
|
||||
player.hand,
|
||||
player.board,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
await game.save({ session });
|
||||
async start(game: Game) {
|
||||
throw Error('Not Implemented');
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async drawToBoard(
|
||||
amount,
|
||||
player: Player,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
await this.drawCards(
|
||||
amount,
|
||||
player.stack,
|
||||
player.board,
|
||||
player.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
async end(game: Game, winner: Player) {
|
||||
throw Error('Not Implemented');
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async drawToHand(
|
||||
amount,
|
||||
player: Player,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
await this.drawCards(
|
||||
amount,
|
||||
player.stack,
|
||||
player.hand,
|
||||
player.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
private async drawCards(
|
||||
amount: number,
|
||||
from: Container,
|
||||
to: Container,
|
||||
discard: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const shallow = [...from.cards];
|
||||
shallow.reverse();
|
||||
if (amount <= from.cards.length) {
|
||||
await this.distributeCards(
|
||||
shallow.slice(0, amount),
|
||||
from,
|
||||
to,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
await game.save({ session });
|
||||
return;
|
||||
} else {
|
||||
const toDraw = from.cards.length;
|
||||
await this.distributeCards(
|
||||
shallow.slice(0, toDraw),
|
||||
from,
|
||||
to,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
if (discard.cards.length == 0) {
|
||||
await game.save({ session });
|
||||
return;
|
||||
}
|
||||
// restack all discarded
|
||||
await this.distributeCards(discard.cards, discard, from, game, session);
|
||||
await this.shuffleContainer(from, game, session);
|
||||
await this.drawCards(amount - toDraw, from, to, discard, game, session);
|
||||
}
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async fillMarket(
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (game.market.cards.length < 5) {
|
||||
const amount = Math.min(
|
||||
game.marketStack.cards.length,
|
||||
5 - game.market.cards.length,
|
||||
);
|
||||
|
||||
await this.distributeCards(
|
||||
game.marketStack.cards.slice(0, amount),
|
||||
game.marketStack,
|
||||
game.market,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
}
|
||||
const missingIndexes = game.market.cards.reduce(function (a, e, i) {
|
||||
if (e === null) a.push(i);
|
||||
return a;
|
||||
}, []);
|
||||
if (missingIndexes.length > 0) {
|
||||
console.error('not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async shuffleContainer(
|
||||
container: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
container.cards = shuffle(container.cards);
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'shuffleContainer',
|
||||
container: container._id.toHexString(),
|
||||
});
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async distributeCards(
|
||||
cards: Card[],
|
||||
from: Container,
|
||||
to: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (cards.some((c) => !from.cards.some((cc) => cc._id.equals(c._id)))) {
|
||||
throw new HttpException(
|
||||
'Card not found in container',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const shallow = [...cards];
|
||||
for (const c of shallow) {
|
||||
const index = from.cards.findIndex((cc) => cc?._id.equals(c._id));
|
||||
from.cards.splice(index, 1);
|
||||
to.cards.push(c);
|
||||
}
|
||||
await game.save({ session });
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'distributeCards',
|
||||
from: from._id.toHexString(),
|
||||
to: to._id.toHexString(),
|
||||
cards: shallow,
|
||||
});
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async destroyCard(
|
||||
card: Card,
|
||||
from: Container,
|
||||
game: Document<Game> & Game,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (card.role == CardRole.FIRE_GEM) {
|
||||
await this.distributeCards([card], from, game.fireGems, game, session);
|
||||
return;
|
||||
}
|
||||
const index = from.cards.findIndex((cc) => cc?._id.equals(card._id));
|
||||
from.cards.splice(index, 1);
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'destroyCard',
|
||||
from: from._id.toHexString(),
|
||||
card: card._id,
|
||||
});
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async killTeam(
|
||||
game: Document<Game> & Game,
|
||||
team: Team,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const index = game.teams.findIndex((t) => t._id.equals(team._id));
|
||||
game.teams.splice(index, 1);
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'killTeam',
|
||||
teamId: team._id,
|
||||
});
|
||||
await game.save({ session });
|
||||
if (game.teams.length <= 1) {
|
||||
await this.endGame(game, game.teams[0], session);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async leaveGame(
|
||||
game: Document<Game> & Game,
|
||||
user: User,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const team = game.teams.find((t) =>
|
||||
t.players.find((p) => p.user._id.equals(user._id)),
|
||||
);
|
||||
if (team == undefined) {
|
||||
throw new HttpException('User not in game', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
if (game.owner._id.equals(user._id)) {
|
||||
throw new HttpException(
|
||||
'You cannot leave a game you own',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
const player = team.players.find((p) => p.user._id.equals(user._id));
|
||||
await this.kickPlayer(game, game.owner, player._id.toHexString(), session);
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async kickPlayer(
|
||||
game: Document<Game> & Game,
|
||||
user: User,
|
||||
kickedPlayerId: string,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const team = game.teams.find((t) =>
|
||||
t.players.find((p) => p._id.equals(kickedPlayerId)),
|
||||
);
|
||||
if (team == undefined) {
|
||||
throw new HttpException('User not in game', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
const index = team.players.findIndex((p) => p.user._id.equals(user._id));
|
||||
team.players.splice(index, 1);
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'leaveTeam',
|
||||
playerId: kickedPlayerId,
|
||||
team: new ReadTeamDto(team),
|
||||
game: game._id.toHexString(),
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'leaveTeam',
|
||||
team: new ReadTeamDto(team),
|
||||
playerId: kickedPlayerId,
|
||||
});
|
||||
let gameExists = true;
|
||||
if (team.players.length == 0) {
|
||||
if (!game.started) {
|
||||
const teamIndex = game.teams.indexOf(team);
|
||||
game.teams.splice(teamIndex, 1);
|
||||
} else {
|
||||
gameExists = await this.killTeam(game, team, session);
|
||||
}
|
||||
}
|
||||
if (gameExists) {
|
||||
await game.save({ session });
|
||||
}
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async endGame(
|
||||
game: Document<Game> & Game,
|
||||
winner: Team,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'endGame',
|
||||
winner: winner._id.toHexString(),
|
||||
});
|
||||
await game.deleteOne({ session });
|
||||
async guessWord(game: Game, user: User, word: string) {
|
||||
throw Error('Not Implemented');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { GameService } from './games.service';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Game } from '../schemas/game.schema';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
import { Player } from '../schemas/player.schema';
|
||||
|
||||
@Injectable()
|
||||
export class LobbyService {
|
||||
constructor(
|
||||
@InjectRepository(Game) private gameRepository: Repository<Game>,
|
||||
@InjectRepository(Player) private playerRepository: Repository<Player>,
|
||||
private readonly gamesService: GameService,
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async create(owner: User) {
|
||||
const createdGame = this.gameRepository.create();
|
||||
createdGame.owner = owner;
|
||||
await this.joinGame(createdGame, owner);
|
||||
this.eventEmitter.emit('sse.lobby', { type: 'create', ...createdGame });
|
||||
return createdGame;
|
||||
}
|
||||
|
||||
async joinGame(game: Game, user: User) {
|
||||
const player = this.playerRepository.create();
|
||||
player.user = user;
|
||||
game.players.push();
|
||||
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'joinGame',
|
||||
game: game.id,
|
||||
player: player,
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game.id, {
|
||||
type: 'joinGame',
|
||||
player: player,
|
||||
});
|
||||
this.playerRepository.save(player);
|
||||
this.gameRepository.save(game);
|
||||
}
|
||||
|
||||
findByOwner(user: User) {
|
||||
return this.gameRepository.findBy({ owner: user });
|
||||
}
|
||||
|
||||
async leaveGame(game: Game, user: User) {
|
||||
throw Error('not implemented');
|
||||
}
|
||||
|
||||
async kickPlayer(game: Game, user: User, kickedPlayerId: string) {
|
||||
throw Error('not implemented');
|
||||
}
|
||||
|
||||
async findAll(): Promise<Game[]> {
|
||||
return this.gameRepository.find();
|
||||
}
|
||||
|
||||
async remove(game: Game) {
|
||||
this.eventEmitter.emit('sse.lobby', {
|
||||
type: 'delete',
|
||||
_id: game.id,
|
||||
});
|
||||
this.eventEmitter.emit('sse.game.' + game.id, {
|
||||
type: 'delete',
|
||||
});
|
||||
this.gameRepository.remove(game);
|
||||
}
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
import {
|
||||
forwardRef,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { Container, Game, Player } from '../schemas/game.schema';
|
||||
import mongoose, { Document, Model } from 'mongoose';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Effect } from 'src/cards/schemas/effect.schema';
|
||||
import { CardEffects } from 'src/cards/schemas/cards.types';
|
||||
import { getPlayerTeam, WithTransaction } from '../utils';
|
||||
import {
|
||||
conditionsMappings,
|
||||
effectMappings,
|
||||
isAutoActivable,
|
||||
perMapping,
|
||||
tokenMappings,
|
||||
} from './effect.functions';
|
||||
import { InjectConnection, InjectModel } from '@nestjs/mongoose';
|
||||
import { GameService } from './games.service';
|
||||
|
||||
@Injectable()
|
||||
export class TurnService {
|
||||
constructor(
|
||||
private eventEmitter: EventEmitter2,
|
||||
@Inject(forwardRef(() => GameService))
|
||||
private readonly gamesService: GameService,
|
||||
@InjectModel(Game.name) private gameModel: Model<Game>,
|
||||
@InjectConnection() private readonly connection: mongoose.Connection,
|
||||
) {}
|
||||
|
||||
@WithTransaction
|
||||
async lockCard(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
card: Card,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
game.currentTurn.cardsLocked.push(card);
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'lockCard',
|
||||
card: card._id.toHexString(),
|
||||
});
|
||||
for (const effect of card.effects) {
|
||||
if (isAutoActivable(effect)) {
|
||||
await this.lockEffect(game, player, effect, card, session);
|
||||
}
|
||||
}
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async unlockEffect(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
effect: Effect,
|
||||
card: Card,
|
||||
session: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const index = game.currentTurn.lockedEffects.findIndex((ee) =>
|
||||
ee._id.equals(effect._id),
|
||||
);
|
||||
game.currentTurn.lockedEffects.splice(index, 1);
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'unlockEffect',
|
||||
effect: effect._id.toHexString(),
|
||||
});
|
||||
|
||||
for (const sub of effect.subEffects) {
|
||||
await this.unlockEffect(game, player, sub, card, session);
|
||||
}
|
||||
|
||||
for (const e of [effect, ...effect.subEffects]) {
|
||||
if (isAutoActivable(e)) {
|
||||
await this.lockEffect(game, player, e, card, session);
|
||||
}
|
||||
}
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async lockEffect(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
effect: Effect,
|
||||
card: Card,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const effectRunner = effectMappings[effect.effect];
|
||||
if (!effectRunner) {
|
||||
throw new HttpException(
|
||||
`Effect ${effect.effect} not implemented`,
|
||||
HttpStatus.NOT_IMPLEMENTED,
|
||||
);
|
||||
}
|
||||
// Process effect conditions
|
||||
if (effect.condition) {
|
||||
const effectCondition = conditionsMappings[effect.condition.effectId];
|
||||
if (!effectCondition) {
|
||||
throw new HttpException(
|
||||
`Condition ${effect.condition.effectId} not implemented`,
|
||||
HttpStatus.NOT_IMPLEMENTED,
|
||||
);
|
||||
}
|
||||
const conditionSuccess = await effectCondition(
|
||||
game,
|
||||
player,
|
||||
card,
|
||||
effect.condition,
|
||||
this.gamesService,
|
||||
session,
|
||||
);
|
||||
|
||||
if (!conditionSuccess) {
|
||||
throw new HttpException(
|
||||
`Condition ${effect.condition.effectId} not met`,
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
const effectUUID = effect._id.toHexString();
|
||||
if (game.currentTurn.lockedEffects.some((e) => e._id.equals(effectUUID))) {
|
||||
console.error(`Effect ${effectUUID} already used this turn`);
|
||||
throw new HttpException(
|
||||
`Effect ${effectUUID} already used this turn`,
|
||||
HttpStatus.CONFLICT,
|
||||
);
|
||||
}
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'lockEffect',
|
||||
effect: effectUUID,
|
||||
});
|
||||
|
||||
// Process effect mutexes
|
||||
|
||||
if (effect.mutex) {
|
||||
for (const e of card.effects) {
|
||||
if (e.mutex == effect.mutex) {
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'lockEffect',
|
||||
effect: e._id.toHexString(),
|
||||
});
|
||||
game.currentTurn.lockedEffects.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process effect Per
|
||||
game.currentTurn.lockedEffects.push(effect);
|
||||
let times = 1;
|
||||
if (effect.per) {
|
||||
times = await perMapping[effect.per](game, card);
|
||||
}
|
||||
for (let t = 0; t < times; t++) {
|
||||
await effectRunner(
|
||||
game,
|
||||
effect,
|
||||
player,
|
||||
card,
|
||||
this.gamesService,
|
||||
this.eventEmitter,
|
||||
session,
|
||||
);
|
||||
}
|
||||
|
||||
// process subeffects
|
||||
if (effect.subEffects) {
|
||||
for (const subeffect of effect.subEffects) {
|
||||
await this.lockEffect(game, player, subeffect, effect.card, session);
|
||||
}
|
||||
}
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async buyGeneric(
|
||||
game: Document<Game> & Game,
|
||||
card: Card,
|
||||
fromContainer: Container,
|
||||
toContainer: Container,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (!card.cost) {
|
||||
throw new TypeError('Card cost is null or undefined');
|
||||
}
|
||||
// const targetChangingEffects = [CardEffects.STACK_NEXT_CARD_BOUGHT];
|
||||
// if (card.cardType == CardType.ACTION) {
|
||||
// targetChangingEffects.push(CardEffects.STACK_NEXT_ACTION_BOUGHT);
|
||||
// }
|
||||
// const targetChangingToken = game.currentTurn.tokens.find((e) =>
|
||||
// targetChangingEffects.includes(e.effect),
|
||||
// );
|
||||
|
||||
// const forFreeEffects = [CardEffects.BUY_FOR_FREE];
|
||||
// if (card.role == CardRole.FIRE_GEM) {
|
||||
// forFreeEffects.push(CardEffects.BUY_GEM_FOR_FREE);
|
||||
// }
|
||||
|
||||
// const forFreeToken = game.currentTurn.tokens.find((e) =>
|
||||
// forFreeEffects.includes(e.effect),
|
||||
// );
|
||||
|
||||
// const reducedPriceEffects = [];
|
||||
|
||||
// if (card.cardType == CardType.ACTION) {
|
||||
// reducedPriceEffects.push(CardEffects.CHEAPER_ACTION);
|
||||
// }
|
||||
// if (card.cardType == CardType.CHAMPION) {
|
||||
// reducedPriceEffects.push(CardEffects.CHEAPER_CHAMPION);
|
||||
// reducedPriceEffects.push(CardEffects.CHEAPER_CHAMPIONS_PASSIVE);
|
||||
// }
|
||||
// // if(card.cost)
|
||||
// CardEffects.CHEAPER_CARD_IF_HIGHER_PRICE
|
||||
|
||||
// const reducedPriceToken = game.currentTurn.tokens.find((e) =>
|
||||
// forFreeEffects.includes(e.effect),
|
||||
// );
|
||||
|
||||
const cost = card.cost;
|
||||
// if (forFreeToken) {
|
||||
// cost = 0;
|
||||
// removeToken(game, targetChangingToken, this.eventEmitter);
|
||||
// }
|
||||
|
||||
if (game.currentTurn.gold < cost) {
|
||||
throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 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(), {
|
||||
type: 'currentTurn.updateGold',
|
||||
gold: game.currentTurn.gold,
|
||||
operation: -cost,
|
||||
});
|
||||
}
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async addFuckUMarker(
|
||||
game: Document<Game> & Game,
|
||||
token: Effect,
|
||||
target: Player,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
if (
|
||||
target.hand.cards.length <=
|
||||
target.fuckUMarkers + target.discardMarkers
|
||||
) {
|
||||
throw new HttpException(
|
||||
"This player doesn't have enough cards in the hand",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
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);
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'player.addFuckUMarker',
|
||||
operation: 1,
|
||||
fuckUMarkers: target.fuckUMarkers,
|
||||
playerId: target._id.toHexString(),
|
||||
});
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async addDiscardMarker(
|
||||
game: Document<Game> & Game,
|
||||
target: Player,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
const unlockedCards = target.board.cards.filter((c) => !c.isLocked(game));
|
||||
if (
|
||||
target.hand.cards.length + unlockedCards.length <=
|
||||
target.fuckUMarkers + target.discardMarkers
|
||||
) {
|
||||
throw new HttpException(
|
||||
"This player doesn't have enough cards in the hand",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
target.discardMarkers++;
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'player.addDiscardMarker',
|
||||
operation: 1,
|
||||
discardMarkers: target.discardMarkers,
|
||||
playerId: target._id.toHexString(),
|
||||
});
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async useToken(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
token: Effect,
|
||||
target: Card,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
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,
|
||||
);
|
||||
if (
|
||||
token.effect != CardEffects.DRAW_AND_DISCARD ||
|
||||
(token.effect == CardEffects.DRAW_AND_DISCARD &&
|
||||
token.times <= game.currentTurn.drawAndDiscardCurrentAmount)
|
||||
) {
|
||||
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(),
|
||||
});
|
||||
}
|
||||
|
||||
// process subeffects
|
||||
if (token.subEffects) {
|
||||
for (const subeffect of token.subEffects) {
|
||||
await this.lockEffect(game, player, subeffect, token.card, session);
|
||||
}
|
||||
}
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async damagePlayer(
|
||||
game: Document<Game> & 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,
|
||||
});
|
||||
await game.save({ session });
|
||||
if (team.health <= 0) {
|
||||
await this.gamesService.killTeam(game, team, session);
|
||||
}
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async damageChampion(
|
||||
game: Document<Game> & Game,
|
||||
targetPlayer: Player,
|
||||
targetCard: Card,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
game.currentTurn.damage -= targetCard.defense;
|
||||
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.updateDamage',
|
||||
damage: game.currentTurn.damage,
|
||||
operation: -targetCard.defense,
|
||||
});
|
||||
|
||||
await this.gamesService.distributeCards(
|
||||
[targetCard],
|
||||
targetPlayer.board,
|
||||
targetPlayer.discard,
|
||||
game,
|
||||
session,
|
||||
);
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async discardCard(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
card: Card,
|
||||
session?: mongoose.mongo.ClientSession,
|
||||
) {
|
||||
await this.gamesService.distributeCards(
|
||||
[card],
|
||||
player.board,
|
||||
player.discard,
|
||||
game,
|
||||
);
|
||||
if (player.discardMarkers > 0) {
|
||||
player.discardMarkers--;
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'player.removeDiscardMarker',
|
||||
operation: -1,
|
||||
discardMarkers: player.discardMarkers,
|
||||
playerId: player._id.toHexString(),
|
||||
});
|
||||
} else if (player.fuckUMarkers > 0) {
|
||||
player.fuckUMarkers--;
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'player.removeFuckUMarker',
|
||||
operation: -1,
|
||||
fuckUMarkers: player.fuckUMarkers,
|
||||
playerId: player._id.toHexString(),
|
||||
});
|
||||
}
|
||||
|
||||
game.currentTurn.drawAndDiscardCurrentAmount = 0;
|
||||
if (game.currentTurn.drawAndDiscardCurrentEffect) {
|
||||
const tokenIndex = game.currentTurn.tokens.findIndex((t) =>
|
||||
t._id.equals(game.currentTurn.drawAndDiscardCurrentEffect._id),
|
||||
);
|
||||
game.currentTurn.tokens.splice(tokenIndex, 1);
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'currentTurn.useToken',
|
||||
tokenId: game.currentTurn.drawAndDiscardCurrentEffect._id.toHexString(),
|
||||
});
|
||||
game.currentTurn.drawAndDiscardCurrentEffect = null;
|
||||
}
|
||||
|
||||
await game.save({ session });
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { HttpStatus } from '@nestjs/common/enums';
|
||||
import { HttpException } from '@nestjs/common/exceptions';
|
||||
import mongoose, { Document } from 'mongoose';
|
||||
import { Game, Player, Team } from './schemas/game.schema';
|
||||
import { ReadUserDto } from './dto/read-games.dto';
|
||||
|
||||
export function shuffle(a) {
|
||||
let j, x, i;
|
||||
for (i = a.length - 1; i > 0; i--) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
x = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
export function getCurrentTeam(game: Document<Game> & Game) {
|
||||
return game.teams.find((t) => t._id.equals(game.currentTurn.currentTeam));
|
||||
}
|
||||
|
||||
export function getPlayer(session: Record<string, any>, game: Game) {
|
||||
const user = session.user as ReadUserDto;
|
||||
if (!user) {
|
||||
throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
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> & 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._id.equals(playerId)) {
|
||||
return player as Document<Player> & Player;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new HttpException('Player not found in game', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
export function isInTeam(team: Team, player: Player) {
|
||||
if (team.players.some((p) => p._id.equals(player._id))) {
|
||||
return true;
|
||||
}
|
||||
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,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
const originalFunc = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
if (args.at(-1) instanceof mongoose.mongo.ClientSession) {
|
||||
const data = await originalFunc.apply(this, args).catch((e) => {
|
||||
if (e instanceof HttpException) {
|
||||
throw e;
|
||||
}
|
||||
console.log(e);
|
||||
reject(
|
||||
new HttpException(
|
||||
'Another request is processing',
|
||||
HttpStatus.TOO_MANY_REQUESTS,
|
||||
),
|
||||
);
|
||||
return;
|
||||
});
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
Reference in New Issue
Block a user