Fix : various multiplayer related fixes
This commit is contained in:
+4
-2
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user