import { forwardRef, HttpException, HttpStatus, Inject, Injectable, } from '@nestjs/common'; import { Container, Game, Player } from '../schemas/game.schema'; import mongoose, { Document, Model } from 'mongoose'; import { Card } from 'src/cards/schemas/cards.schema'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Effect } from 'src/cards/schemas/effect.schema'; import { CardEffects, CardRole, CardType, EffectType, } from 'src/cards/schemas/cards.types'; import { WithTransaction } from '../utils'; import { conditionsMappings, effectMappings, isAutoActivable, perMapping, removeToken, } from './effect.functions'; import { InjectConnection, InjectModel } from '@nestjs/mongoose'; import { GameService } from './games.service'; @Injectable() export class TurnService { constructor( private eventEmitter: EventEmitter2, @Inject(forwardRef(() => GameService)) private readonly gamesService: GameService, @InjectModel(Game.name) private gameModel: Model, @InjectConnection() private readonly connection: mongoose.Connection, ) {} @WithTransaction async lockCard( game: Document & Game, player: Player, card: Card, session?: mongoose.mongo.ClientSession, ) { game.currentTurn.cardsLocked.push(card); this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'lockCard', card: card._id.toHexString(), }); for (const effect of card.effects) { if (isAutoActivable(effect)) { await this.lockEffect(game, player, effect, card, session); } } await game.save({ session }); } @WithTransaction async lockEffect( game: Document & Game, player: Player, effect: Effect, card: Card, session?: mongoose.mongo.ClientSession, ) { const effectRunner = effectMappings[effect.effect]; 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`); throw new HttpException( `Effect ${effectUUID} already used this turn`, HttpStatus.CONFLICT, ); } this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'lockEffect', effect: effectUUID, }); // Process effect mutexes 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); } } } // Process effect Per game.currentTurn.lockedEffects.push(effect); 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 }); } @WithTransaction async buyGeneric( game: Document & Game, player: Player, card: Card, fromContainer: Container, session?: mongoose.mongo.ClientSession, ) { if (!card.cost) { throw new TypeError('Card cost is null or undefined'); } // const targetChangingEffects = [CardEffects.STACK_NEXT_CARD_BOUGHT]; // if (card.cardType == CardType.ACTION) { // targetChangingEffects.push(CardEffects.STACK_NEXT_ACTION_BOUGHT); // } // const targetChangingToken = game.currentTurn.tokens.find((e) => // targetChangingEffects.includes(e.effect), // ); // const forFreeEffects = [CardEffects.BUY_FOR_FREE]; // if (card.role == CardRole.FIRE_GEM) { // forFreeEffects.push(CardEffects.BUY_GEM_FOR_FREE); // } // const forFreeToken = game.currentTurn.tokens.find((e) => // forFreeEffects.includes(e.effect), // ); // const reducedPriceEffects = []; // if (card.cardType == CardType.ACTION) { // reducedPriceEffects.push(CardEffects.CHEAPER_ACTION); // } // if (card.cardType == CardType.CHAMPION) { // reducedPriceEffects.push(CardEffects.CHEAPER_CHAMPION); // reducedPriceEffects.push(CardEffects.CHEAPER_CHAMPIONS_PASSIVE); // } // // if(card.cost) // CardEffects.CHEAPER_CARD_IF_HIGHER_PRICE // const reducedPriceToken = game.currentTurn.tokens.find((e) => // forFreeEffects.includes(e.effect), // ); const cost = card.cost; // if (forFreeToken) { // cost = 0; // removeToken(game, targetChangingToken, this.eventEmitter); // } if (game.currentTurn.gold < cost) { throw new HttpException('Not enough gold', HttpStatus.BAD_REQUEST); } const targetContainer = player.discard; // if (targetChangingToken) { // targetContainer = player.stack; // removeToken(game, targetChangingToken, this.eventEmitter); // } if (cost != 0) { game.currentTurn.gold -= cost; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'currentTurn.updateGold', gold: game.currentTurn.gold, operation: -cost, }); } await this.gamesService.distributeCards( [card], fromContainer, targetContainer, game, session, ); await game.save({ session }); } @WithTransaction async makeDiscard( game: Document & Game, token: Effect, target: Player, session?: mongoose.mongo.ClientSession, ) { target.fuckUMarkers++; const index = game.currentTurn.tokens.findIndex((t) => t._id.equals(token._id), ); if (index == -1) { throw new Error('Token not found'); } game.currentTurn.tokens.splice(index, 1); await game.save({ session }); } @WithTransaction async useToken( game: Document & Game, token: Effect, target: Card, session?: mongoose.mongo.ClientSession, ) { return; } @WithTransaction async useTokenOnEnemyBoard( game: Document & Game, targetPlayer: Player, token: Effect, target: Card, session?: mongoose.mongo.ClientSession, ) {} }