import { AttackType } from '../monster/monster.entity'; // ---------- Types ---------- export interface CombatantStats { name: string; hpCurrent: number; hpMax: number; force: number; agilite: number; intelligence: number; chance: number; attack: number; // arme (0 Sprint 2) pour joueur, flat pour monstre defense: number; // 0 joueur Sprint 2 attackType: AttackType; } export interface AttackResult { damage: number; isCrit: boolean; isDodged: boolean; log: string; } export interface RoundLog { round: number; playerAttack: AttackResult; monsterAttack: AttackResult; playerHp: number; monsterHp: number; log: string[]; } export interface CombatResult { winner: 'player' | 'monster'; rounds: RoundLog[]; xpEarned: number; goldEarned: number; totalRounds: number; } // ---------- Formules (déterministes pour les tests, mais utilisent Math.random) ---------- const MAX_ROUNDS = 30; // sécurité anti-boucle infinie function statForAttackType(stats: CombatantStats): number { switch (stats.attackType) { case 'melee': return stats.force; case 'ranged': return stats.agilite; case 'magic': return stats.intelligence; } } export function calcPlayerDamage(player: CombatantStats, monsterDefense: number): number { const stat = statForAttackType(player); const raw = player.attack + Math.floor(stat * 1.5); return Math.max(1, raw - monsterDefense); } export function calcMonsterDamage(monster: CombatantStats, playerDefense: number): number { return Math.max(1, monster.attack - playerDefense); } export function rollCrit(chance: number): boolean { const rate = 0.05 + chance * 0.002; return Math.random() < rate; } export function rollDodge(chance: number): boolean { const rate = 0.05 + chance * 0.001; return Math.random() < rate; } function resolvePlayerAttack( player: CombatantStats, monster: CombatantStats, ): AttackResult { // Les monstres ne dodgent pas en Sprint 2 const baseDamage = calcPlayerDamage(player, monster.defense); const isCrit = rollCrit(player.chance); const damage = isCrit ? Math.floor(baseDamage * 1.5) : baseDamage; const critText = isCrit ? ' (CRITIQUE !)' : ''; const log = `${player.name} attaque ${monster.name} pour ${damage} dégâts${critText}`; return { damage, isCrit, isDodged: false, log }; } function resolveMonsterAttack( monster: CombatantStats, player: CombatantStats, ): AttackResult { const isDodged = rollDodge(player.chance); if (isDodged) { return { damage: 0, isCrit: false, isDodged: true, log: `${player.name} esquive l'attaque de ${monster.name} !`, }; } const damage = calcMonsterDamage(monster, player.defense); return { damage, isCrit: false, isDodged: false, log: `${monster.name} attaque ${player.name} pour ${damage} dégâts.`, }; } // ---------- Résolution complète ---------- export function resolveCombat( player: CombatantStats, monster: CombatantStats, xpReward: number, goldMin: number, goldMax: number, ): CombatResult { let playerHp = player.hpCurrent; let monsterHp = monster.hpCurrent; const rounds: RoundLog[] = []; for (let round = 1; round <= MAX_ROUNDS; round++) { // -- Tour joueur -- const playerAttack = resolvePlayerAttack(player, monster); monsterHp = Math.max(0, monsterHp - playerAttack.damage); // -- Tour monstre (seulement si encore vivant) -- let monsterAttack: AttackResult; if (monsterHp > 0) { monsterAttack = resolveMonsterAttack(monster, player); playerHp = Math.max(0, playerHp - monsterAttack.damage); } else { monsterAttack = { damage: 0, isCrit: false, isDodged: false, log: '' }; } rounds.push({ round, playerAttack, monsterAttack, playerHp, monsterHp, log: [ playerAttack.log, ...(monsterAttack.log ? [monsterAttack.log] : []), `HP — ${player.name}: ${playerHp}/${player.hpMax} | ${monster.name}: ${monsterHp}/${monster.hpCurrent}`, ], }); if (monsterHp <= 0) { const goldEarned = goldMin + Math.floor(Math.random() * (goldMax - goldMin + 1)); return { winner: 'player', rounds, xpEarned: xpReward, goldEarned, totalRounds: round }; } if (playerHp <= 0) { return { winner: 'monster', rounds, xpEarned: 0, goldEarned: 0, totalRounds: round }; } } // Timeout (ne devrait pas arriver avec des valeurs équilibrées) return { winner: 'monster', rounds, xpEarned: 0, goldEarned: 0, totalRounds: MAX_ROUNDS }; } // ---------- Level up ---------- export function xpRequiredForLevel(level: number): number { return Math.round(100 * Math.pow(level, 1.5)); } export interface LevelUpResult { newLevel: number; newXp: number; statPointsGained: number; levelsGained: number; } export function applyXpGain(currentLevel: number, currentXp: number, xpEarned: number): LevelUpResult { let level = currentLevel; let xp = currentXp + xpEarned; let statPointsGained = 0; // Chaîne de level up while (level < 100) { const required = xpRequiredForLevel(level + 1); if (xp >= required) { xp -= required; level++; statPointsGained += 5; } else { break; } } return { newLevel: level, newXp: xp, statPointsGained, levelsGained: level - currentLevel, }; }