Fix : various multiplayer related fixes

This commit is contained in:
2024-07-03 23:27:33 +02:00
parent dc9b00284b
commit 0283663102
7 changed files with 113 additions and 40 deletions
+4 -2
View File
@@ -1,7 +1,6 @@
version: '3.1' version: '3.1'
services: services:
mongo: mongo:
image: docker.io/mongo image: docker.io/mongo
restart: always restart: always
@@ -10,11 +9,14 @@ services:
MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_ROOT_PASSWORD: example
ports: ports:
- 27017:27017 - 27017:27017
# entrypoint: bash -c "chown 999:999 /opt/keyfile/mongodb-keyfile && chmod 400 /opt/keyfile/mongodb-keyfile && exec docker-entrypoint.sh $$@" # entrypoint: bash -c "chown 999:999 /opt/keyfile/mongodb-keyfile && chmod 400 /opt/keyfile/mongodb-keyfile && exec docker-entrypoint.sh $$@"
command: mongod --replSet rs0 --keyFile /opt/keyfile/mongodb-keyfile command: mongod --replSet rs0 --keyFile /opt/keyfile/mongodb-keyfile
volumes: volumes:
- ./:/opt/keyfile/ - ./:/opt/keyfile/
- mongo:/data/db
groups: groups:
- 999 - 999
volumes:
mongo:
# docker run --rm -it --network=host --name mongoContainer mongo:latest mongosh mongodb://127.0.0.1:27017 -u root -p example --eval "rs.initiate({'_id':'rs0', members: [{'_id':1, 'host':'127.0.0.1:27017'}]})" # docker run --rm -it --network=host --name mongoContainer mongo:latest mongosh mongodb://127.0.0.1:27017 -u root -p example --eval "rs.initiate({'_id':'rs0', members: [{'_id':1, 'host':'127.0.0.1:27017'}]})"
@@ -27,7 +27,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
@Controller('games') @Controller('games')
export class LobbyController { export class LobbyController {
constructor( constructor(
private readonly gamesService: LobbyService, private readonly lobbyService: LobbyService,
private eventEmitter: EventEmitter2, private eventEmitter: EventEmitter2,
) {} ) {}
@@ -45,20 +45,20 @@ export class LobbyController {
@ApiResponse({ status: 403, description: 'Forbidden.' }) @ApiResponse({ status: 403, description: 'Forbidden.' })
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
async create(@Session() session: Record<string, any>) { async create(@Session() session: Record<string, any>) {
const games = await this.gamesService.findByOwner(session.user._id); const games = await this.lobbyService.findByOwner(session.user._id);
if (games) { if (games) {
throw new HttpException( throw new HttpException(
'You can only create one game at a time', 'You can only create one game at a time',
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
return this.gamesService.create(session.user); return await this.lobbyService.create(session.user);
} }
@Get() @Get()
@ApiOperation({ summary: 'List all games' }) @ApiOperation({ summary: 'List all games' })
findAll() { findAll() {
const games = this.gamesService.findAll(); const games = this.lobbyService.findAll();
return games; return games;
} }
@@ -94,7 +94,7 @@ export class LobbyController {
HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN,
); );
} }
return this.gamesService.update(game, updateGameDto); return this.lobbyService.update(game, updateGameDto);
} }
@Delete(':id') @Delete(':id')
@@ -111,7 +111,7 @@ export class LobbyController {
HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN,
); );
} }
const deleted = await this.gamesService.remove(game); const deleted = await this.lobbyService.remove(game);
if (deleted) { if (deleted) {
return; return;
} }
@@ -152,7 +152,32 @@ export class LobbyController {
HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN,
); );
} }
this.gamesService.start(game); this.lobbyService.start(game);
return; return;
} }
@Post(':id/join')
@ApiOperation({ summary: 'Join game' })
@UseGuards(AuthGuard)
async join(
@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
.flatMap((t) => t.players)
.some((p) => p.user.userId == session.user.userId)
) {
throw new HttpException(
'You are already in this game',
HttpStatus.BAD_REQUEST,
);
}
return this.lobbyService.joinGame(game, session.user);
}
} }
+1 -1
View File
@@ -3,7 +3,7 @@ import { Types } from 'mongoose';
export class DatabaseObjectDto { export class DatabaseObjectDto {
constructor(data: { _id: Types.ObjectId }) { constructor(data: { _id: Types.ObjectId }) {
this._id = data._id.toString(); this._id = data._id.toHexString();
} }
@ApiProperty() @ApiProperty()
+6 -2
View File
@@ -15,6 +15,8 @@ export class ReadPlayerDto extends DatabaseObjectDto {
this.stack = new ReadContainerDto(data.stack, true); this.stack = new ReadContainerDto(data.stack, true);
this.race = data.race; this.race = data.race;
this.class = data.class; this.class = data.class;
this.discardMarkers = data.discardMarkers;
this.fuckUMarkers = data.fuckUMarkers;
} }
user: ReadUserDto; user: ReadUserDto;
@@ -28,6 +30,9 @@ export class ReadPlayerDto extends DatabaseObjectDto {
class: string; class: string;
race: string; race: string;
discardMarkers: number;
fuckUMarkers: number;
} }
export class ReadTeamDto extends DatabaseObjectDto { export class ReadTeamDto extends DatabaseObjectDto {
@@ -54,11 +59,10 @@ export class ReadContainerDto extends DatabaseObjectDto {
// if (hidden) { // if (hidden) {
// this.cards = data.cards.map(() => null); // this.cards = data.cards.map(() => null);
// } else { // } else {
this.id = data._id.toHexString();
this.cards = data.cards; this.cards = data.cards;
// } // }
} }
id: string;
cards: Card[]; cards: Card[];
} }
+6
View File
@@ -65,6 +65,12 @@ export class Player extends Types.ObjectId {
@Prop({ type: ContainerSchema, default: {}, autopopulate: true }) @Prop({ type: ContainerSchema, default: {}, autopopulate: true })
discard!: Container; discard!: Container;
@Prop({ type: Number, default: 0 })
discardMarkers: number;
@Prop({ type: Number, default: 0 })
fuckUMarkers: number;
} }
export const PlayerSchema = SchemaFactory.createForClass(Player); export const PlayerSchema = SchemaFactory.createForClass(Player);
+40 -10
View File
@@ -1,9 +1,13 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { DatabaseObjectDto } from '../dto/database-object.dto'; import { DatabaseObjectDto } from '../dto/database-object.dto';
import { InjectModel } from '@nestjs/mongoose'; import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { Game, Player, Team } from '../schemas/game.schema'; import { Game, Player, Team } from '../schemas/game.schema';
import mongoose, { Document, Model } from 'mongoose'; import mongoose, { Document, Model } from 'mongoose';
import { ReadContainerDto, ReadGameDto } from '../dto/read-games.dto'; import {
ReadContainerDto,
ReadGameDto,
ReadTeamDto,
} from '../dto/read-games.dto';
import { User } from 'src/users/schemas/user.entity'; import { User } from 'src/users/schemas/user.entity';
import { Card } from 'src/cards/schemas/cards.schema'; import { Card } from 'src/cards/schemas/cards.schema';
import { CardRole } from 'src/cards/schemas/cards.types'; import { CardRole } from 'src/cards/schemas/cards.types';
@@ -17,11 +21,13 @@ export class LobbyService {
constructor( constructor(
@InjectModel(Game.name) private gameModel: Model<Game>, @InjectModel(Game.name) private gameModel: Model<Game>,
@InjectModel(Card.name) private cardModel: Model<Card>, @InjectModel(Card.name) private cardModel: Model<Card>,
@InjectConnection() private readonly connection: mongoose.Connection,
private readonly gamesService: GameService, private readonly gamesService: GameService,
private eventEmitter: EventEmitter2, private eventEmitter: EventEmitter2,
) {} ) {}
async create(owner: User): Promise<DatabaseObjectDto> { @WithTransaction
async create(owner: User, session?: mongoose.mongo.ClientSession) {
const createdGame = new this.gameModel(); const createdGame = new this.gameModel();
createdGame.owner = owner; createdGame.owner = owner;
createdGame.packs = [ createdGame.packs = [
@@ -31,15 +37,39 @@ export class LobbyService {
'pack1_base_imperial', 'pack1_base_imperial',
'pack1_base_guild', 'pack1_base_guild',
]; ];
createdGame.teams.push({ await this.joinGame(createdGame, owner, session);
players: [{ user: owner } as Player], 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); } as Team);
const gamedata = await createdGame.save(); const team = game.teams.at(-1);
const dto = new ReadGameDto(gamedata); // this.eventEmitter.emit('sse.lobby', {
this.eventEmitter.emit('sse.lobby', { type: 'create', ...dto }); // 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 dto; return 'true!';
} }
async findAll(): Promise<ReadGameDto[]> { async findAll(): Promise<ReadGameDto[]> {
@@ -100,7 +130,7 @@ export class LobbyService {
game.started = true; game.started = true;
this.eventEmitter.emit('sse.lobby', { this.eventEmitter.emit('sse.lobby', {
type: 'start', type: 'start',
_id: game._id, game: game._id,
}); });
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'start', type: 'start',
+24 -18
View File
@@ -55,24 +55,30 @@ export function WithTransaction(
) { ) {
const originalFunc = descriptor.value; const originalFunc = descriptor.value;
descriptor.value = async function (...args: any[]) { descriptor.value = function (...args: any[]) {
if (args.at(-1) instanceof mongoose.mongo.ClientSession) { return new Promise(async (resolve) => {
return originalFunc.apply(this, args); if (args.at(-1) instanceof mongoose.mongo.ClientSession) {
} else { const data = await originalFunc.apply(this, args);
await this.connection resolve(data);
.transaction(async (session) => { return data;
return originalFunc.apply(this, [...args, session]); } else {
}) await this.connection
.catch((e) => { .transaction(async (session) => {
if (e instanceof HttpException) { const data = await originalFunc.apply(this, [...args, session]);
throw e; resolve(data);
} return data;
throw new HttpException( })
'Another request is processing', .catch((e) => {
HttpStatus.TOO_MANY_REQUESTS, if (e instanceof HttpException) {
); throw e;
}); }
} throw new HttpException(
'Another request is processing',
HttpStatus.TOO_MANY_REQUESTS,
);
});
}
});
}; };
return descriptor; return descriptor;