import { forwardRef, HttpException, HttpStatus, Inject, Injectable, } from '@nestjs/common'; import { Container, Game, Player, Team } 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 { getPlayerTeam, WithTransaction } from '../utils'; import { conditionsMappings, effectMappings, isAutoActivable, perMapping, removeToken, tokenMappings, } 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, card: Card, fromContainer: Container, toContainer: 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); } // if (targetChangingToken) { // targetContainer = player.stack; // removeToken(game, targetChangingToken, this.eventEmitter); // } await this.gamesService.distributeCards( [card], fromContainer, toContainer, game, session, ); 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 game.save({ session }); } @WithTransaction async makeDiscard( game: Document & Game, token: Effect, target: Player, session?: mongoose.mongo.ClientSession, ) { target.fuckUMarkers++; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'player.addFuckUMarker', operation: 1, fuckUMarkers: target.fuckUMarkers, playerId: target._id.toHexString(), }); 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, player: Player, token: Effect, target: Card, session?: mongoose.mongo.ClientSession, ) { 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, ); if ( token.effect != CardEffects.DRAW_AND_DISCARD || (token.effect == CardEffects.DRAW_AND_DISCARD && token.times <= game.currentTurn.drawAndDiscardCurrentAmount) ) { 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 damagePlayer( game: Document & Game, amount: number, player: Player, targetPlayer: Player, session?: mongoose.mongo.ClientSession, ) { const team = getPlayerTeam(game, targetPlayer); team.health -= amount; game.currentTurn.damage -= amount; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'currentTurn.updateDamage', damage: game.currentTurn.damage, operation: -amount, }); this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'team.updateHealth', team: team._id, health: team.health, operation: -amount, }); await game.save({ session }); if (team.health <= 0) { await this.gamesService.killTeam(game, team, session); } } @WithTransaction async damageChampion( game: Document & Game, targetPlayer: Player, targetCard: Card, session?: mongoose.mongo.ClientSession, ) { game.currentTurn.damage -= targetCard.defense; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'currentTurn.updateDamage', damage: game.currentTurn.damage, operation: -targetCard.defense, }); await this.gamesService.distributeCards( [targetCard], targetPlayer.board, targetPlayer.discard, game, session, ); await game.save({ session }); } @WithTransaction async discardCard( game: Document & Game, player: Player, card: Card, session?: mongoose.mongo.ClientSession, ) { await this.gamesService.distributeCards( [card], player.board, player.discard, game, ); if (player.discardMarkers > 0) { player.discardMarkers--; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'player.removeDiscardMarker', operation: -1, discardMarkers: player.discardMarkers, playerId: player._id.toHexString(), }); } else if (player.fuckUMarkers > 0) { player.fuckUMarkers--; this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'player.removeFuckUMarker', operation: -1, fuckUMarkers: player.fuckUMarkers, playerId: player._id.toHexString(), }); } game.currentTurn.drawAndDiscardCurrentAmount = 0; if (game.currentTurn.drawAndDiscardCurrentEffect) { const tokenIndex = game.currentTurn.tokens.findIndex((t) => t._id.equals(game.currentTurn.drawAndDiscardCurrentEffect._id), ); game.currentTurn.tokens.splice(tokenIndex, 1); this.eventEmitter.emit('sse.game.' + game._id.toHexString(), { type: 'currentTurn.useToken', tokenId: game.currentTurn.drawAndDiscardCurrentEffect._id.toHexString(), }); game.currentTurn.drawAndDiscardCurrentEffect = null; } await game.save({ session }); } }