From a3907716cddcfb5a199bb74dcac3620cd7dc7261 Mon Sep 17 00:00:00 2001 From: legonzaur Date: Sun, 7 Jul 2024 18:26:47 +0200 Subject: [PATCH] Feat : implement per for effects --- src/cards/dto/effect.dto.ts | 37 ++++++++++ src/cards/schemas/cards.types.ts | 4 +- src/cards/schemas/effect.schema.ts | 12 +--- src/games/services/effect.functions.ts | 97 +++++++++++++++++++++++++- src/games/services/games.service.ts | 24 +++++++ src/games/services/turn.service.ts | 66 ++++++++++++++---- 6 files changed, 212 insertions(+), 28 deletions(-) create mode 100644 src/cards/dto/effect.dto.ts diff --git a/src/cards/dto/effect.dto.ts b/src/cards/dto/effect.dto.ts new file mode 100644 index 0000000..b395a05 --- /dev/null +++ b/src/cards/dto/effect.dto.ts @@ -0,0 +1,37 @@ +import { DatabaseObjectDto } from 'src/games/dto/database-object.dto'; +import { Effect, EffectCondition } from '../schemas/effect.schema'; +import { CardEffects, CardFaction, Per } from '../schemas/cards.types'; +import { Card } from '../schemas/cards.schema'; + +export class ReadEffectDto extends DatabaseObjectDto { + constructor(effect: Effect) { + super(effect); + this.effect = effect.effect; + this.amount = effect.amount; + this.times = effect.times; + this.subEffects = effect.subEffects; + this.condition = effect.condition; + this.per = effect.per; + this.mutex = effect.mutex; + this.card = effect.card._id.toHexString(); + this.faction = effect.faction; + } + + effect: CardEffects; + + amount: number; + + times: number; + + subEffects: Effect[]; + + condition?: EffectCondition; + + per?: Per; + + mutex?: string; + + card: string; + + faction?: CardFaction; +} diff --git a/src/cards/schemas/cards.types.ts b/src/cards/schemas/cards.types.ts index fb2ca9c..6906732 100644 --- a/src/cards/schemas/cards.types.ts +++ b/src/cards/schemas/cards.types.ts @@ -108,9 +108,7 @@ export enum EffectType { CHAMPION_ACTION = 'champion_action', SUICIDE = 'suicide', FACTION_COMBO = 'faction_combo', -} -export enum EffectTypeSpecial { CHAMPION_AMOUNT = 'champion_amount', TOTAL_DAMAGE = 'total_damage', CARD_AMOUNT = 'card_amount', @@ -118,7 +116,7 @@ export enum EffectTypeSpecial { } export type EffectTypeVerbose = { - id: EffectType | EffectTypeSpecial; + id: EffectType; faction?: string; amount?: number; card_id?: number; diff --git a/src/cards/schemas/effect.schema.ts b/src/cards/schemas/effect.schema.ts index eb12288..a4fc17d 100644 --- a/src/cards/schemas/effect.schema.ts +++ b/src/cards/schemas/effect.schema.ts @@ -1,18 +1,12 @@ import mongoose, { Types } from 'mongoose'; -import { - CardEffects, - CardFaction, - EffectType, - EffectTypeSpecial, - Per, -} from './cards.types'; +import { CardEffects, CardFaction, EffectType, Per } from './cards.types'; import { Card } from './cards.schema'; import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; // @Schema() export class EffectCondition { - @Prop({ type: String, enum: { ...EffectType, ...EffectTypeSpecial } }) - effectId: EffectType | EffectTypeSpecial; + @Prop({ type: String, enum: EffectType }) + effectId: EffectType; @Prop({ type: Number }) amount: number; diff --git a/src/games/services/effect.functions.ts b/src/games/services/effect.functions.ts index 74c6dd2..6a79f0e 100644 --- a/src/games/services/effect.functions.ts +++ b/src/games/services/effect.functions.ts @@ -2,10 +2,11 @@ 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, EffectType } from 'src/cards/schemas/cards.types'; -import { Effect } from 'src/cards/schemas/effect.schema'; +import { CardEffects, 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'; export function isAutoActivable(effect: Effect) { if ( @@ -30,7 +31,7 @@ async function addToken( game.currentTurn.tokens.push(effect); eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'currentTurn.addToken', - token: effect, + token: new ReadEffectDto(effect), }); } @@ -151,3 +152,93 @@ export const effectMappings = { session: mongoose.mongo.ClientSession, ) => Promise >; + +export const conditionsMappings = { + [EffectType.CHAMPION_ACTION]: async () => true, + [EffectType.FACTION_COMBO]: async ( + game: Document & 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, + 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, + player: Player, + card: Card, + condition: EffectCondition, + gameService: GameService, + session: mongoose.mongo.ClientSession, + ) => Promise +>; + +export const perMapping = { + [Per.CARD_OF_SAME_FACTION]: async ( + game: Document & Game, + card: Card, + ) => { + return game.currentTurn.cardsLocked.filter((c) => c.faction == card.faction) + .length; + }, + [Per.OTHER_CARD_OF_SAME_FACTION]: async ( + game: Document & 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) => { + return game.currentTurn.cardsLocked.filter((c) => c.defense).length; + }, + [Per.CHAMPION_OF_SAME_FACTION]: async ( + game: Document & Game, + card: Card, + ) => { + return game.currentTurn.cardsLocked.filter( + (c) => c.faction == card.faction && c.defense, + ).length; + }, + [Per.OTHER_CHAMPION]: async (game: Document & Game, card: Card) => { + return game.currentTurn.cardsLocked.filter( + (c) => !c._id.equals(card._id) && c.defense, + ).length; + }, + [Per.OTHER_GUARD]: async (game: Document & 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, card: Card) => { + return game.currentTurn.cardsLocked.filter( + (c) => !c._id.equals(card._id) && c.cardId == 80, + ).length; + }, +} as Record< + string, + (game: Document & Game, card: Card) => Promise +>; diff --git a/src/games/services/games.service.ts b/src/games/services/games.service.ts index 694849f..18e5d86 100644 --- a/src/games/services/games.service.ts +++ b/src/games/services/games.service.ts @@ -12,6 +12,7 @@ import { Card } from 'src/cards/schemas/cards.schema'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { WithTransaction, getCurrentTeam, shuffle } from '../utils'; import { TurnService } from './turn.service'; +import { CardRole, CardType } from 'src/cards/schemas/cards.types'; @Injectable() export class GameService { @@ -224,4 +225,27 @@ export class GameService { cards: shallow, }); } + + @WithTransaction + async destroyCard( + card: Card, + from: Container, + game: Document & 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 }); + } } diff --git a/src/games/services/turn.service.ts b/src/games/services/turn.service.ts index e825ec9..c38bee2 100644 --- a/src/games/services/turn.service.ts +++ b/src/games/services/turn.service.ts @@ -12,7 +12,12 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { Effect } from 'src/cards/schemas/effect.schema'; import { CardEffects, EffectType } from 'src/cards/schemas/cards.types'; import { WithTransaction } from '../utils'; -import { effectMappings, isAutoActivable } from './effect.functions'; +import { + conditionsMappings, + effectMappings, + isAutoActivable, + perMapping, +} from './effect.functions'; import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { GameService } from './games.service'; @@ -55,14 +60,37 @@ export class TurnService { session?: mongoose.mongo.ClientSession, ) { const effectRunner = effectMappings[effect.effect]; - if (!effectMappings[effect.effect]) { - console.warn(`Effect ${effect.effect} not implemented`); - return; + 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`); @@ -75,6 +103,9 @@ export class TurnService { type: 'lockEffect', effect: effectUUID, }); + + // Process effect mutexes + if (effect.mutex) { for (const e of card.effects) { if (e.mutex == effect.mutex) { @@ -86,16 +117,25 @@ export class TurnService { } } } + + // Process effect Per game.currentTurn.lockedEffects.push(effect); - await effectRunner( - game, - effect, - player, - card, - this.gamesService, - this.eventEmitter, - session, - ); + 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, + ); + } + await game.save({ session }); }