Feat : add ability to use tokens

This commit is contained in:
2024-07-13 21:37:59 +02:00
parent 8ca627a047
commit 22836f7611
5 changed files with 267 additions and 59 deletions
+23 -23
View File
@@ -16,7 +16,13 @@ import { GameStartedPipe } from '../pipe/game.pipe';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { TurnService } from '../services/turn.service'; import { TurnService } from '../services/turn.service';
import { getPlayer, getCurrentTeam, isInTeam, findPlayer } from '../utils'; import {
getPlayer,
getCurrentTeam,
isInTeam,
findUser,
findPlayer,
} from '../utils';
import { CardPipe } from '../pipe/card.pipe'; import { CardPipe } from '../pipe/card.pipe';
import { Card } from 'src/cards/schemas/cards.schema'; import { Card } from 'src/cards/schemas/cards.schema';
import { EffectPipe } from '../pipe/effect.pipe'; import { EffectPipe } from '../pipe/effect.pipe';
@@ -92,7 +98,12 @@ export class TurnController {
if (game.currentTurn.gold < card.cost) { if (game.currentTurn.gold < card.cost) {
throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST);
} }
await this.turnService.buyGeneric(game, currentPlayer, card, game.market); await this.turnService.buyGeneric(
game,
card,
game.market,
currentPlayer.discard,
);
await this.gameService.fillMarket(game); await this.gameService.fillMarket(game);
} }
@@ -118,9 +129,9 @@ export class TurnController {
const targetCard = game.fireGems.cards[0]; const targetCard = game.fireGems.cards[0];
await this.turnService.buyGeneric( await this.turnService.buyGeneric(
game, game,
currentPlayer,
targetCard, targetCard,
game.fireGems, game.fireGems,
currentPlayer.discard,
); );
} }
@@ -223,32 +234,21 @@ export class TurnController {
CardEffects.STUN, CardEffects.STUN,
CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE, CardEffects.CONTROL_OPPOSING_CHAMPION_PASSIVE,
]; ];
let targetCard: Card;
if (enemyTargetToken.includes(token.effect)) { if (enemyTargetToken.includes(token.effect)) {
const targetCard = targetPlayer.board.cards.find((c) => targetCard = targetPlayer.board.cards.find((c) =>
c._id.equals(useTokenDto.cardId), c._id.equals(useTokenDto.cardId),
); );
if (!targetCard) {
throw new HttpException('Card not found', HttpStatus.NOT_FOUND);
}
await this.turnService.useTokenOnEnemyBoard(
game,
targetPlayer,
token,
targetCard,
);
} else { } else {
const targetCard = currentPlayer.board.cards.find((c) => targetCard = currentPlayer.board.cards.find((c) =>
c._id.equals(useTokenDto.cardId), c._id.equals(useTokenDto.cardId),
); );
if (!targetCard) {
throw new HttpException('Card not found', HttpStatus.NOT_FOUND);
}
await this.turnService.useTokenOnEnemyBoard(
game,
currentPlayer,
token,
targetCard,
);
} }
if (!targetCard) {
throw new HttpException('Card not found', HttpStatus.NOT_FOUND);
}
await this.turnService.useToken(game, currentPlayer, token, targetCard);
} }
} }
+5 -1
View File
@@ -1,4 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
export class UseTokenDTO { export class UseTokenDTO {
playerId: string; @ApiProperty()
playerId?: string;
@ApiProperty()
cardId: string; cardId: string;
} }
+192 -13
View File
@@ -4,6 +4,7 @@ import mongoose, { Document } from 'mongoose';
import { Card } from 'src/cards/schemas/cards.schema'; import { Card } from 'src/cards/schemas/cards.schema';
import { import {
CardEffects, CardEffects,
CardRole,
CardType, CardType,
EffectType, EffectType,
Per, Per,
@@ -12,6 +13,7 @@ import { Effect, EffectCondition } from 'src/cards/schemas/effect.schema';
import { TurnService } from './turn.service'; import { TurnService } from './turn.service';
import { GameService } from './games.service'; import { GameService } from './games.service';
import { ReadEffectDto } from 'src/cards/dto/effect.dto'; import { ReadEffectDto } from 'src/cards/dto/effect.dto';
import { HttpException, HttpStatus } from '@nestjs/common';
export function isAutoActivable(effect: Effect) { export function isAutoActivable(effect: Effect) {
if ( if (
@@ -177,11 +179,15 @@ export const tokenMappings = {
player: Player, player: Player,
card: Card, card: Card,
gameService: GameService, gameService: GameService,
turnService: TurnService,
eventEmitter: EventEmitter2, eventEmitter: EventEmitter2,
_session: mongoose.mongo.ClientSession, _session: mongoose.mongo.ClientSession,
) => { ) => {
if (card.cardType !== CardType.CHAMPION) { if (card.cardType !== CardType.CHAMPION) {
throw new Error('Cannot Prepare : Card is not a champion'); throw new HttpException(
'Cannot Prepare : Card is not a champion',
HttpStatus.BAD_REQUEST,
);
} }
const targetEffects = card.effects.filter( const targetEffects = card.effects.filter(
(e) => e.condition.effectId == EffectType.CHAMPION_ACTION, (e) => e.condition.effectId == EffectType.CHAMPION_ACTION,
@@ -192,7 +198,10 @@ export const tokenMappings = {
game.currentTurn.lockedEffects.some((ee) => ee._id.equals(e._id)), game.currentTurn.lockedEffects.some((ee) => ee._id.equals(e._id)),
) )
) { ) {
throw new Error('Cannot Prepare : champion effect not locked'); throw new HttpException(
'Cannot Prepare : champion effect not locked',
HttpStatus.BAD_REQUEST,
);
} }
for (const e of targetEffects) { for (const e of targetEffects) {
@@ -202,20 +211,190 @@ export const tokenMappings = {
game.currentTurn.lockedEffects.splice(index, 1); game.currentTurn.lockedEffects.splice(index, 1);
eventEmitter.emit('sse.game.' + game._id.toHexString(), { eventEmitter.emit('sse.game.' + game._id.toHexString(), {
type: 'unlockEffect', type: 'unlockEffect',
effect: effect._id.toHexString(), effect: e._id.toHexString(),
}); });
} }
}, },
[CardEffects.STUN]: addToken, [CardEffects.STUN]: async (
[CardEffects.SACRIFICE]: addToken, game: Document<Game> & Game,
[CardEffects.DRAW_AND_DISCARD]: addToken, effect: Effect,
[CardEffects.MAKE_DISCARD]: addToken, player: Player,
[CardEffects.RESTACK_DISCARDED_CHAMPION]: addToken, card: Card,
[CardEffects.RESTACK_DISCARDED_CARD]: addToken, gameService: GameService,
[CardEffects.STACK_NEXT_ACTION_BOUGHT]: addToken, turnService: TurnService,
[CardEffects.STACK_NEXT_CARD_BOUGHT]: addToken, eventEmitter: EventEmitter2,
[CardEffects.PLAY_NEXT_CARD_BOUGHT]: addToken, 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,
);
}
await gameService.destroyCard(card, player.board, game, session);
},
[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.CHAMPION) {
throw new HttpException(
'Cannot stack : Card is not an action',
HttpStatus.BAD_REQUEST,
);
}
await turnService.buyGeneric(
game,
card,
game.market,
player.stack,
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,
);
},
[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,
);
},
[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,
) => {
await gameService.drawToBoard(1, player, game, session);
await player.discardMarkers++;
},
} 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 = { export const conditionsMappings = {
[EffectType.CHAMPION_ACTION]: async () => true, [EffectType.CHAMPION_ACTION]: async () => true,
+34 -20
View File
@@ -23,6 +23,7 @@ import {
isAutoActivable, isAutoActivable,
perMapping, perMapping,
removeToken, removeToken,
tokenMappings,
} from './effect.functions'; } from './effect.functions';
import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { GameService } from './games.service'; import { GameService } from './games.service';
@@ -148,9 +149,9 @@ export class TurnService {
@WithTransaction @WithTransaction
async buyGeneric( async buyGeneric(
game: Document<Game> & Game, game: Document<Game> & Game,
player: Player,
card: Card, card: Card,
fromContainer: Container, fromContainer: Container,
toContainer: Container,
session?: mongoose.mongo.ClientSession, session?: mongoose.mongo.ClientSession,
) { ) {
if (!card.cost) { if (!card.cost) {
@@ -198,13 +199,19 @@ export class TurnService {
if (game.currentTurn.gold < cost) { if (game.currentTurn.gold < cost) {
throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST);
} }
const targetContainer = player.discard;
// if (targetChangingToken) { // if (targetChangingToken) {
// targetContainer = player.stack; // targetContainer = player.stack;
// removeToken(game, targetChangingToken, this.eventEmitter); // removeToken(game, targetChangingToken, this.eventEmitter);
// } // }
await this.gamesService.distributeCards(
[card],
fromContainer,
toContainer,
game,
session,
);
if (cost != 0) { if (cost != 0) {
game.currentTurn.gold -= cost; game.currentTurn.gold -= cost;
this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { this.eventEmitter.emit('sse.game.' + game._id.toHexString(), {
@@ -214,13 +221,6 @@ export class TurnService {
}); });
} }
await this.gamesService.distributeCards(
[card],
fromContainer,
targetContainer,
game,
session,
);
await game.save({ session }); await game.save({ session });
} }
@@ -246,19 +246,33 @@ export class TurnService {
@WithTransaction @WithTransaction
async useToken( async useToken(
game: Document<Game> & Game, game: Document<Game> & Game,
player: Player,
token: Effect, token: Effect,
target: Card, target: Card,
session?: mongoose.mongo.ClientSession, session?: mongoose.mongo.ClientSession,
) { ) {
return; 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,
);
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(),
});
await game.save({ session });
} }
@WithTransaction
async useTokenOnEnemyBoard(
game: Document<Game> & Game,
targetPlayer: Player,
token: Effect,
target: Card,
session?: mongoose.mongo.ClientSession,
) {}
} }
+13 -2
View File
@@ -25,13 +25,24 @@ export function getPlayer(session: Record<string, any>, game: Game) {
throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED); throw new HttpException('Please log in', HttpStatus.UNAUTHORIZED);
} }
return findPlayer(user._id, game); 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) { export function findPlayer(playerId: string, game: Game) {
for (const team of game.teams) { for (const team of game.teams) {
for (const player of team.players) { for (const player of team.players) {
if (player.user._id.equals(playerId)) { if (player._id.equals(playerId)) {
return player as Document<Player> & Player; return player as Document<Player> & Player;
} }
} }