feat: craft/drops — 10 matériaux, 12 recettes, drop rate variable
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 32s

- 10 matériaux Égouts/Désert (Poil de Rat → Œil du Sphinx)
- 12 items craftables dont 1 legendary (Sceptre Prophétique)
- 12 recettes cross-zone avec ingrédients cohérents
- 15 monstres mappés à leur drop (tous les Égouts/Désert)
- Drop rate variable par difficulté relative (25-80%)
- Quantité drop variable (1-3 selon boss/difficulté)
This commit is contained in:
2026-03-24 20:07:18 +01:00
parent 6938eedcda
commit 47c90e4d55
2 changed files with 376 additions and 6 deletions

View File

@@ -19,6 +19,36 @@ import {
} from './combat.engine';
const COMBAT_ENDURANCE_COST = 5;
/**
* Drop rate variable basé sur la difficulté relative monstre vs joueur.
* Monstre facile = moins de drop, monstre difficile = plus de drop + quantité.
* Boss de zone (maxLevel ≥ 9 et spread ≥ 3) = 80% + 2-3 drops.
*/
function computeDropRate(
playerLevel: number,
monsterMinLevel: number,
monsterMaxLevel: number,
): { dropRate: number; dropQty: number } {
const monsterAvgLevel = (monsterMinLevel + monsterMaxLevel) / 2;
const diff = monsterAvgLevel - playerLevel;
const isBoss = (monsterMaxLevel - monsterMinLevel) >= 3 && monsterMaxLevel >= 9;
if (isBoss) {
return { dropRate: 0.80, dropQty: 2 + (Math.random() < 0.5 ? 1 : 0) }; // 2-3
}
if (diff >= 2) {
return { dropRate: 0.60, dropQty: 1 + (Math.random() < 0.3 ? 1 : 0) }; // 1-2
}
if (diff >= 0) {
return { dropRate: 0.50, dropQty: 1 + (Math.random() < 0.2 ? 1 : 0) }; // 1-2
}
if (diff >= -2) {
return { dropRate: 0.40, dropQty: 1 };
}
// Très facile (level >> monstre)
return { dropRate: 0.25, dropQty: 1 };
}
const DEFEAT_ENDURANCE_PENALTY = 25;
const DEFEAT_HP_RATIO = 0.2; // 20% hpMax à la défaite
const VICTORY_HP_REGEN_RATIO = 0.1; // +10% hpMax à la victoire
@@ -166,13 +196,16 @@ export class CombatService {
}
}
// Loot matériaux — 40% de chance après victoire
// Loot matériaux — drop rate variable par difficulté relative
let lootMaterial: { name: string; quantity: number } | null = null;
let lootedMaterialId: string | null = null;
if (result.winner === 'player' && monster.dropMaterialId && Math.random() < 0.4) {
await this.materialService.addMaterial(character.id, monster.dropMaterialId, 1);
lootMaterial = { name: 'matériau', quantity: 1 };
lootedMaterialId = monster.dropMaterialId;
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);
lootMaterial = { name: 'matériau', quantity: dropQty };
lootedMaterialId = monster.dropMaterialId;
}
}
// Persister le log
@@ -202,7 +235,7 @@ export class CombatService {
}
if (lootMaterial) {
summaryParts.push(`Loot : 1 matériau obtenu !`);
summaryParts.push(`Loot : ${lootMaterial.quantity} matériau${lootMaterial.quantity > 1 ? 'x' : ''} obtenu${lootMaterial.quantity > 1 ? 's' : ''} !`);
}
return {