fix: cooldown serveur 2s/8s + loot dans transaction (élimine deadlock)
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable, BadRequestException, ConflictException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { DataSource, EntityManager, Repository } from 'typeorm';
|
||||
import { CharacterMaterial } from '../material/character-material.entity';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Character } from '../character/entities/character.entity';
|
||||
import { Monster } from '../monster/monster.entity';
|
||||
@@ -54,8 +55,25 @@ const DEFEAT_HP_RATIO = 0.2; // 20% hpMax à la défaite
|
||||
const VICTORY_HP_REGEN_RATIO = 0.1; // +10% hpMax à la victoire
|
||||
const DEFEAT_GOLD_LOSS_RATIO = 0.05; // perte 5% or à la défaite
|
||||
|
||||
/** Ajouter un matériau dans la transaction courante (pas de connexion séparée). */
|
||||
async function addMaterialInTx(manager: EntityManager, characterId: string, materialId: string, quantity: number) {
|
||||
const repo = manager.getRepository(CharacterMaterial);
|
||||
let entry = await repo.findOne({ where: { characterId, materialId } });
|
||||
if (entry) {
|
||||
entry.quantity += quantity;
|
||||
} else {
|
||||
entry = repo.create({ characterId, materialId, quantity });
|
||||
}
|
||||
await repo.save(entry);
|
||||
}
|
||||
|
||||
const COOLDOWN_SINGLE_MS = 2_000;
|
||||
const COOLDOWN_MULTI_MS = 8_000;
|
||||
|
||||
@Injectable()
|
||||
export class CombatService {
|
||||
private readonly cooldowns = new Map<string, number>(); // userId → timestamp
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Character)
|
||||
private readonly characterRepository: Repository<Character>,
|
||||
@@ -69,7 +87,23 @@ export class CombatService {
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
private checkCooldown(userId: string): void {
|
||||
const lastCombat = this.cooldowns.get(userId) ?? 0;
|
||||
const remaining = lastCombat - Date.now();
|
||||
if (remaining > 0) {
|
||||
throw new BadRequestException(
|
||||
`Cooldown actif — attendez ${Math.ceil(remaining / 1000)}s`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private setCooldown(userId: string, durationMs: number): void {
|
||||
this.cooldowns.set(userId, Date.now() + durationMs);
|
||||
}
|
||||
|
||||
async startCombat(dto: StartCombatDto, user: User) {
|
||||
this.checkCooldown(user.id);
|
||||
|
||||
// Charger le monstre (hors transaction — lecture seule)
|
||||
const monster = await this.monsterService.findOne(dto.monsterId);
|
||||
|
||||
@@ -202,7 +236,7 @@ export class CombatService {
|
||||
if (result.winner === 'player' && monster.dropMaterialId) {
|
||||
const { dropRate, dropQty } = computeDropRate(character.level, monster.minLevel, monster.maxLevel);
|
||||
if (Math.random() < dropRate) {
|
||||
await this.materialService.addMaterial(character.id, monster.dropMaterialId, dropQty);
|
||||
await addMaterialInTx(manager, character.id, monster.dropMaterialId, dropQty);
|
||||
lootMaterial = { name: 'matériau', quantity: dropQty };
|
||||
lootedMaterialId = monster.dropMaterialId;
|
||||
}
|
||||
@@ -284,10 +318,12 @@ export class CombatService {
|
||||
}
|
||||
}
|
||||
|
||||
this.setCooldown(user.id, COOLDOWN_SINGLE_MS);
|
||||
return txResult.response;
|
||||
}
|
||||
|
||||
async startMultiCombat(dto: StartCombatDto, user: User, count: number) {
|
||||
this.checkCooldown(user.id);
|
||||
const monster = await this.monsterService.findOne(dto.monsterId);
|
||||
|
||||
const txResult = await this.dataSource.transaction(async (manager) => {
|
||||
@@ -368,7 +404,7 @@ export class CombatService {
|
||||
if (monster.dropMaterialId) {
|
||||
const { dropRate, dropQty } = computeDropRate(character.level, monster.minLevel, monster.maxLevel);
|
||||
if (Math.random() < dropRate) {
|
||||
await this.materialService.addMaterial(character.id, monster.dropMaterialId, dropQty);
|
||||
await addMaterialInTx(manager, character.id, monster.dropMaterialId, dropQty);
|
||||
totals.loot.push({ name: 'matériau', quantity: dropQty });
|
||||
lootedMaterialIds.push(monster.dropMaterialId);
|
||||
}
|
||||
@@ -428,6 +464,7 @@ export class CombatService {
|
||||
}
|
||||
}
|
||||
|
||||
this.setCooldown(user.id, COOLDOWN_MULTI_MS);
|
||||
return {
|
||||
mode: 'multi',
|
||||
count: txResult.combatsDone,
|
||||
|
||||
Reference in New Issue
Block a user