Feat : add tokens, handle mutex effects
This commit is contained in:
Vendored
+23
-26
@@ -1,27 +1,24 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch via NPM",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start:dev",
|
||||
"-b",
|
||||
"swc"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"console": "internalConsole",
|
||||
"outputCapture": "std",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
}
|
||||
]
|
||||
}
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch via NPM",
|
||||
"request": "launch",
|
||||
"runtimeArgs": ["run", "start:dev", "-b", "swc"],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"${workspaceFolder}/node_modules/**/*.js",
|
||||
"${workspaceFolder}/lib/**/*.js"
|
||||
],
|
||||
"type": "node",
|
||||
"console": "internalConsole",
|
||||
"outputCapture": "std",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"envFile": "${workspaceFolder}/.env"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { Card, CardSchema } from './schemas/cards.schema';
|
||||
import { CardsService } from './cards.service';
|
||||
import { Effect, EffectSchema } from './schemas/effect.schema';
|
||||
// import {
|
||||
// Effect,
|
||||
// EffectCondition,
|
||||
@@ -12,7 +13,7 @@ import { CardsService } from './cards.service';
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
// { name: Effect.name, schema: EffectSchema },
|
||||
{ name: Effect.name, schema: EffectSchema },
|
||||
// { name: EffectCondition.name, schema: EffectConditionSchema },
|
||||
{ name: Card.name, schema: CardSchema },
|
||||
]),
|
||||
|
||||
@@ -17,7 +17,10 @@ import { EffectCondition } from './schemas/effect.schema';
|
||||
|
||||
@Injectable()
|
||||
export class CardsService {
|
||||
constructor(@InjectModel(Card.name) private cardModel: Model<Card>) {}
|
||||
constructor(
|
||||
@InjectModel(Card.name) private cardModel: Model<Card>,
|
||||
@InjectModel(Effect.name) private effectModel: Model<Effect>,
|
||||
) {}
|
||||
|
||||
async loadCards(): Promise<void> {
|
||||
const cardPath = path.join(path.resolve(), 'src', 'json');
|
||||
@@ -88,6 +91,9 @@ export class CardsService {
|
||||
): Promise<Effect[]> {
|
||||
return await Promise.all(
|
||||
json.map(async (e) => {
|
||||
if (e.mutex) {
|
||||
console.log(e.mutex);
|
||||
}
|
||||
const createdEffect = {
|
||||
card: card,
|
||||
effect: e.effect,
|
||||
@@ -112,7 +118,7 @@ export class CardsService {
|
||||
card,
|
||||
);
|
||||
}
|
||||
return createdEffect;
|
||||
return await this.effectModel.create(createdEffect);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import { CardFaction, CardRole, CardType } from './cards.types';
|
||||
import { Types } from 'mongoose';
|
||||
import mongoose, { Types } from 'mongoose';
|
||||
import { Effect } from './effect.schema';
|
||||
|
||||
@Schema()
|
||||
@@ -32,7 +32,11 @@ export class Card extends Types.ObjectId {
|
||||
@Prop()
|
||||
guard?: boolean;
|
||||
|
||||
@Prop({ type: [Effect], default: [], autopopulate: true })
|
||||
@Prop({
|
||||
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Effect' }],
|
||||
autopopulate: true,
|
||||
default: [],
|
||||
})
|
||||
effects: Effect[];
|
||||
|
||||
@Prop()
|
||||
|
||||
@@ -38,7 +38,11 @@ export class Effect extends Types.ObjectId {
|
||||
@Prop({ type: Number, default: 1 })
|
||||
times!: number;
|
||||
|
||||
// @Prop({ type: [Effect], default: [], autopopulate: true })
|
||||
@Prop({
|
||||
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Effect' }],
|
||||
autopopulate: true,
|
||||
default: [],
|
||||
})
|
||||
subEffects: Effect[];
|
||||
|
||||
@Prop({ type: EffectCondition, autopopulate: true })
|
||||
@@ -56,6 +60,7 @@ export class Effect extends Types.ObjectId {
|
||||
// },
|
||||
// ],
|
||||
// })
|
||||
@Prop({ type: String })
|
||||
mutex?: string;
|
||||
|
||||
// @Prop({
|
||||
@@ -74,6 +79,6 @@ EffectSchema.add({
|
||||
card: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Card',
|
||||
autopopulate: true,
|
||||
autopopulate: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -18,6 +18,8 @@ import { TurnService } from '../services/turn.service';
|
||||
import { getPlayer, getCurrentTeam, isInTeam } 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';
|
||||
|
||||
@ApiTags('turn')
|
||||
@Controller('games/:gameId/turn')
|
||||
@@ -87,4 +89,39 @@ export class TurnController {
|
||||
}
|
||||
await this.turnService.buyCard(game, currentPlayer, card);
|
||||
}
|
||||
|
||||
@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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
import { DatabaseObjectDto } from './database-object.dto';
|
||||
import { Game, Player, PlayingTurn, Team } from '../schemas/game.schema';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { Effect } from 'src/cards/schemas/effect.schema';
|
||||
|
||||
export class ReadPlayerDto extends DatabaseObjectDto {
|
||||
constructor(
|
||||
@@ -53,14 +54,14 @@ export class ReadContainerDto extends DatabaseObjectDto {
|
||||
typeof DatabaseObjectDto
|
||||
>[0],
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_hidden = false,
|
||||
hidden = false,
|
||||
) {
|
||||
super(data);
|
||||
// if (hidden) {
|
||||
// this.cards = data.cards.map(() => null);
|
||||
// } else {
|
||||
this.cards = data.cards;
|
||||
// }
|
||||
if (hidden) {
|
||||
this.cards = data.cards.map(() => null);
|
||||
} else {
|
||||
this.cards = data.cards;
|
||||
}
|
||||
}
|
||||
|
||||
cards: Card[];
|
||||
@@ -95,9 +96,12 @@ class ReadPlayingTeamDto {
|
||||
constructor(currentTurn: PlayingTurn) {
|
||||
this.currentTeam = currentTurn.currentTeam;
|
||||
this.cardsLocked = currentTurn.cardsLocked.map((c) => c._id.toHexString());
|
||||
this.effectsUsed = currentTurn.effectsUsed;
|
||||
this.lockedEffects = currentTurn.lockedEffects.map((e) =>
|
||||
e._id.toHexString(),
|
||||
);
|
||||
this.damage = currentTurn.damage;
|
||||
this.gold = currentTurn.gold;
|
||||
this.tokens = currentTurn.tokens;
|
||||
}
|
||||
|
||||
@ApiProperty()
|
||||
@@ -107,7 +111,10 @@ class ReadPlayingTeamDto {
|
||||
cardsLocked: string[];
|
||||
|
||||
@ApiProperty()
|
||||
effectsUsed: string[];
|
||||
lockedEffects: string[];
|
||||
|
||||
@ApiProperty()
|
||||
tokens: Effect[];
|
||||
|
||||
@ApiProperty()
|
||||
damage: number;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
PipeTransform,
|
||||
Injectable,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
import { Effect } from 'src/cards/schemas/effect.schema';
|
||||
|
||||
@Injectable()
|
||||
export class EffectPipe implements PipeTransform {
|
||||
constructor(@InjectModel(Effect.name) private effectmodel: Model<Effect>) {}
|
||||
|
||||
async transform(value: any) {
|
||||
const effect = await this.effectmodel.findById(value);
|
||||
if (!effect) {
|
||||
throw new HttpException('Effect not found', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import mongoose, { HydratedDocument, Types } from 'mongoose';
|
||||
import { Card } from 'src/cards/schemas/cards.schema';
|
||||
import { CardRole } from 'src/cards/schemas/cards.types';
|
||||
import { Effect } from 'src/cards/schemas/effect.schema';
|
||||
import { User } from 'src/users/schemas/user.entity';
|
||||
|
||||
export type GameDocument = HydratedDocument<Game>;
|
||||
@@ -29,8 +30,19 @@ export class PlayingTurn extends Types.ObjectId {
|
||||
})
|
||||
cardsLocked: Card[];
|
||||
|
||||
@Prop({ type: [String], default: [] })
|
||||
effectsUsed: string[];
|
||||
@Prop({
|
||||
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Effect' }],
|
||||
autopopulate: false,
|
||||
default: [],
|
||||
})
|
||||
lockedEffects: Effect[];
|
||||
|
||||
@Prop({
|
||||
type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Effect' }],
|
||||
autopopulate: true,
|
||||
default: [],
|
||||
})
|
||||
tokens: Effect[];
|
||||
|
||||
@Prop({ default: 0 })
|
||||
damage: number;
|
||||
|
||||
@@ -18,6 +18,22 @@ export function isAutoActivable(effect: Effect) {
|
||||
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: effect,
|
||||
});
|
||||
}
|
||||
|
||||
export const effectMappings = {
|
||||
[CardEffects.GOLD]: async (
|
||||
game: Document<Game> & Game,
|
||||
@@ -29,6 +45,10 @@ export const effectMappings = {
|
||||
_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,
|
||||
@@ -46,6 +66,10 @@ export const effectMappings = {
|
||||
_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,
|
||||
@@ -66,6 +90,10 @@ export const effectMappings = {
|
||||
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,
|
||||
@@ -85,6 +113,32 @@ export const effectMappings = {
|
||||
) => {
|
||||
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,
|
||||
(
|
||||
|
||||
@@ -48,7 +48,8 @@ export class GameService {
|
||||
game.teams[(index + 1) % game.teams.length]._id.toHexString();
|
||||
|
||||
game.currentTurn.cardsLocked = [];
|
||||
game.currentTurn.effectsUsed = [];
|
||||
game.currentTurn.lockedEffects = [];
|
||||
game.currentTurn.tokens = [];
|
||||
game.currentTurn.damage = 0;
|
||||
game.currentTurn.gold = 0;
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@ export class TurnService {
|
||||
});
|
||||
for (const effect of card.effects) {
|
||||
if (isAutoActivable(effect)) {
|
||||
await this.useEffect(game, player, effect, card, session);
|
||||
await this.lockEffect(game, player, effect, card, session);
|
||||
}
|
||||
}
|
||||
await game.save({ session });
|
||||
}
|
||||
|
||||
@WithTransaction
|
||||
async useEffect(
|
||||
async lockEffect(
|
||||
game: Document<Game> & Game,
|
||||
player: Player,
|
||||
effect: Effect,
|
||||
@@ -64,7 +64,7 @@ export class TurnService {
|
||||
);
|
||||
}
|
||||
const effectUUID = effect._id.toHexString();
|
||||
if (game.currentTurn.effectsUsed.includes(effectUUID)) {
|
||||
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`,
|
||||
@@ -72,10 +72,21 @@ export class TurnService {
|
||||
);
|
||||
}
|
||||
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
|
||||
type: 'useEffect',
|
||||
type: 'lockEffect',
|
||||
effect: effectUUID,
|
||||
});
|
||||
game.currentTurn.effectsUsed.push(effectUUID);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
game.currentTurn.lockedEffects.push(effect);
|
||||
await effectRunner(
|
||||
game,
|
||||
effect,
|
||||
|
||||
Reference in New Issue
Block a user