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
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:
@@ -19,6 +19,36 @@ import {
|
|||||||
} from './combat.engine';
|
} from './combat.engine';
|
||||||
|
|
||||||
const COMBAT_ENDURANCE_COST = 5;
|
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_ENDURANCE_PENALTY = 25;
|
||||||
const DEFEAT_HP_RATIO = 0.2; // 20% hpMax à la défaite
|
const DEFEAT_HP_RATIO = 0.2; // 20% hpMax à la défaite
|
||||||
const VICTORY_HP_REGEN_RATIO = 0.1; // +10% hpMax à la victoire
|
const VICTORY_HP_REGEN_RATIO = 0.1; // +10% hpMax à la victoire
|
||||||
@@ -166,14 +196,17 @@ 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 lootMaterial: { name: string; quantity: number } | null = null;
|
||||||
let lootedMaterialId: string | null = null;
|
let lootedMaterialId: string | null = null;
|
||||||
if (result.winner === 'player' && monster.dropMaterialId && Math.random() < 0.4) {
|
if (result.winner === 'player' && monster.dropMaterialId) {
|
||||||
await this.materialService.addMaterial(character.id, monster.dropMaterialId, 1);
|
const { dropRate, dropQty } = computeDropRate(character.level, monster.minLevel, monster.maxLevel);
|
||||||
lootMaterial = { name: 'matériau', quantity: 1 };
|
if (Math.random() < dropRate) {
|
||||||
|
await this.materialService.addMaterial(character.id, monster.dropMaterialId, dropQty);
|
||||||
|
lootMaterial = { name: 'matériau', quantity: dropQty };
|
||||||
lootedMaterialId = monster.dropMaterialId;
|
lootedMaterialId = monster.dropMaterialId;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Persister le log
|
// Persister le log
|
||||||
const combatLog = this.combatLogRepository.create({
|
const combatLog = this.combatLogRepository.create({
|
||||||
@@ -202,7 +235,7 @@ export class CombatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lootMaterial) {
|
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 {
|
return {
|
||||||
|
|||||||
337
src/database/seed-craft-drops.ts
Normal file
337
src/database/seed-craft-drops.ts
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
|
import { AppDataSource } from './data-source';
|
||||||
|
import { Material } from '../material/material.entity';
|
||||||
|
import { Item } from '../item/item.entity';
|
||||||
|
import { Recipe } from '../craft/recipe.entity';
|
||||||
|
import { Monster } from '../monster/monster.entity';
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
// MATÉRIAUX — 10 nouveaux (Égouts + Désert)
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
const NEW_MATERIALS: Partial<Material>[] = [
|
||||||
|
// Égouts
|
||||||
|
{ name: 'Poil de Rat', description: 'Poil rêche arraché à un rat d\'égout. Sert de rembourrage.', rarity: 'common' },
|
||||||
|
{ name: 'Gelée Toxique', description: 'Substance corrosive récupérée sur un slime.', rarity: 'common' },
|
||||||
|
{ name: 'Fil de Soie Géant', description: 'Fil incroyablement résistant tissé par une araignée géante.', rarity: 'rare' },
|
||||||
|
{ name: 'Cuir de Croco', description: 'Cuir épais et écailleux, presque impénétrable.', rarity: 'rare' },
|
||||||
|
{ name: 'Couronne du Roi', description: 'Couronne tordue portée par le Roi des Rats. Irradie de pouvoir.', rarity: 'epic' },
|
||||||
|
// Désert
|
||||||
|
{ name: 'Dard de Scorpion', description: 'Dard venimeux encore suintant.', rarity: 'common' },
|
||||||
|
{ name: 'Plume de Vautour', description: 'Plume noire et résistante, légère comme le vent.', rarity: 'common' },
|
||||||
|
{ name: 'Bandelette Maudite', description: 'Tissu ancien imprégné de magie noire.', rarity: 'rare' },
|
||||||
|
{ name: 'Sable Cristallisé', description: 'Grain de sable fusionné en cristal par la chaleur du désert.', rarity: 'rare' },
|
||||||
|
{ name: 'Œil du Sphinx', description: 'Gemme mystique arrachée au Sphinx. Pulse d\'énergie ancienne.', rarity: 'epic' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
// ITEMS CRAFTABLES — 12 nouveaux
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
const NEW_ITEMS: Partial<Item>[] = [
|
||||||
|
// Marais (2 nouveaux)
|
||||||
|
{ name: 'Potion Antipoison', type: 'consumable', rarity: 'common', attackBonus: 0, defenseBonus: 0, forceBonus: 20, buyPrice: 0, minLevel: 1, zone: null, description: 'Neutralise les poisons et restaure 20 endurance.' },
|
||||||
|
{ name: 'Bouclier de Boue', type: 'armor', rarity: 'common', attackBonus: 0, defenseBonus: 4, buyPrice: 0, minLevel: 2, zone: 'marais', description: 'Forgé dans la boue cristallisée des golems.' },
|
||||||
|
// Égouts (5 nouveaux)
|
||||||
|
{ name: 'Lame Empoisonnée', type: 'weapon', rarity: 'rare', attackBonus: 11, defenseBonus: 0, buyPrice: 0, minLevel: 5, zone: 'egouts', description: 'Chaque coup inflige un poison insidieux.' },
|
||||||
|
{ name: 'Armure de Soie', type: 'armor', rarity: 'rare', attackBonus: 0, defenseBonus: 8, agiliteBonus: 2, buyPrice: 0, minLevel: 5, zone: 'egouts', description: 'Légère et résistante, tissée par des araignées géantes.' },
|
||||||
|
{ name: 'Plastron de Croco', type: 'armor', rarity: 'rare', attackBonus: 0, defenseBonus: 11, buyPrice: 0, minLevel: 7, zone: 'egouts', description: 'Cuir de crocodile assemblé en plastron imposant.' },
|
||||||
|
{ name: 'Grande Potion de Soin', type: 'consumable', rarity: 'rare', attackBonus: 0, defenseBonus: 0, forceBonus: 0, buyPrice: 0, minLevel: 4, zone: null, description: 'Restaure 80% des PV.' },
|
||||||
|
{ name: 'Épée du Roi', type: 'weapon', rarity: 'epic', attackBonus: 16, defenseBonus: 0, buyPrice: 0, minLevel: 8, zone: 'egouts', description: 'Forgée avec la couronne du Roi des Rats.' },
|
||||||
|
// Désert (5 nouveaux)
|
||||||
|
{ name: 'Dague du Scorpion', type: 'weapon', rarity: 'rare', attackBonus: 14, defenseBonus: 0, buyPrice: 0, minLevel: 9, zone: 'desert', description: 'Le venin du scorpion suinte encore de la lame.' },
|
||||||
|
{ name: 'Cape du Vautour', type: 'armor', rarity: 'rare', attackBonus: 0, defenseBonus: 12, agiliteBonus: 3, buyPrice: 0, minLevel: 9, zone: 'desert', description: 'Cape de plumes noires, silencieuse et protectrice.' },
|
||||||
|
{ name: 'Armure de la Momie', type: 'armor', rarity: 'epic', attackBonus: 0, defenseBonus: 16, buyPrice: 0, minLevel: 11, zone: 'desert', description: 'Bandelettes enchantées, dures comme le roc.' },
|
||||||
|
{ name: 'Lame du Désert', type: 'weapon', rarity: 'epic', attackBonus: 22, defenseBonus: 0, buyPrice: 0, minLevel: 11, zone: 'desert', description: 'Cristal de sable fusionné en lame tranchante.' },
|
||||||
|
{ name: 'Sceptre Prophétique', type: 'weapon', rarity: 'legendary', attackBonus: 30, defenseBonus: 0, intelligenceBonus: 5, buyPrice: 0, minLevel: 13, zone: 'desert', description: 'Seul artefact capable de canaliser la vision du Sphinx.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
// MONSTER → MATERIAL LOOT MAPPING
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
const MONSTER_LOOT: Record<string, string> = {
|
||||||
|
// Égouts
|
||||||
|
'Rat d\'Égout': 'Poil de Rat',
|
||||||
|
'Slime Toxique': 'Gelée Toxique',
|
||||||
|
'Araignée Géante': 'Fil de Soie Géant',
|
||||||
|
'Crocodile': 'Cuir de Croco',
|
||||||
|
'Roi des Rats': 'Couronne du Roi',
|
||||||
|
// Désert
|
||||||
|
'Scorpion': 'Dard de Scorpion',
|
||||||
|
'Vautour': 'Plume de Vautour',
|
||||||
|
'Momie': 'Bandelette Maudite',
|
||||||
|
'Ver des Sables': 'Sable Cristallisé',
|
||||||
|
'Sphinx': 'Œil du Sphinx',
|
||||||
|
};
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
// RECETTES — 12 nouvelles (nom ingrédient → résolu en runtime)
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
interface RecipeDef {
|
||||||
|
name: string;
|
||||||
|
resultItemName: string;
|
||||||
|
craftDurationSeconds: number;
|
||||||
|
enduranceCost: number;
|
||||||
|
ingredientNames: { materialName: string; quantity: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const NEW_RECIPES: RecipeDef[] = [
|
||||||
|
// Marais (2)
|
||||||
|
{
|
||||||
|
name: 'Craft Potion Antipoison',
|
||||||
|
resultItemName: 'Potion Antipoison',
|
||||||
|
craftDurationSeconds: 10,
|
||||||
|
enduranceCost: 5,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Spores Vénéneuses', quantity: 2 },
|
||||||
|
{ materialName: 'Venin de Serpent', quantity: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Bouclier de Boue',
|
||||||
|
resultItemName: 'Bouclier de Boue',
|
||||||
|
craftDurationSeconds: 25,
|
||||||
|
enduranceCost: 10,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Fragment de Boue', quantity: 4 },
|
||||||
|
{ materialName: 'Écailles de Grenouille', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Égouts (5)
|
||||||
|
{
|
||||||
|
name: 'Craft Lame Empoisonnée',
|
||||||
|
resultItemName: 'Lame Empoisonnée',
|
||||||
|
craftDurationSeconds: 45,
|
||||||
|
enduranceCost: 12,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Gelée Toxique', quantity: 2 },
|
||||||
|
{ materialName: 'Venin de Serpent', quantity: 1 },
|
||||||
|
{ materialName: 'Poil de Rat', quantity: 3 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Armure de Soie',
|
||||||
|
resultItemName: 'Armure de Soie',
|
||||||
|
craftDurationSeconds: 60,
|
||||||
|
enduranceCost: 14,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Fil de Soie Géant', quantity: 4 },
|
||||||
|
{ materialName: 'Poil de Rat', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Plastron de Croco',
|
||||||
|
resultItemName: 'Plastron de Croco',
|
||||||
|
craftDurationSeconds: 90,
|
||||||
|
enduranceCost: 16,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Cuir de Croco', quantity: 3 },
|
||||||
|
{ materialName: 'Fragment de Boue', quantity: 2 },
|
||||||
|
{ materialName: 'Fil de Soie Géant', quantity: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Grande Potion de Soin',
|
||||||
|
resultItemName: 'Grande Potion de Soin',
|
||||||
|
craftDurationSeconds: 20,
|
||||||
|
enduranceCost: 8,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Gelée Toxique', quantity: 2 },
|
||||||
|
{ materialName: 'Spores Vénéneuses', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Épée du Roi',
|
||||||
|
resultItemName: 'Épée du Roi',
|
||||||
|
craftDurationSeconds: 120,
|
||||||
|
enduranceCost: 20,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Couronne du Roi', quantity: 1 },
|
||||||
|
{ materialName: 'Cuir de Croco', quantity: 3 },
|
||||||
|
{ materialName: 'Gelée Toxique', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Désert (5)
|
||||||
|
{
|
||||||
|
name: 'Craft Dague du Scorpion',
|
||||||
|
resultItemName: 'Dague du Scorpion',
|
||||||
|
craftDurationSeconds: 60,
|
||||||
|
enduranceCost: 14,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Dard de Scorpion', quantity: 3 },
|
||||||
|
{ materialName: 'Plume de Vautour', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Cape du Vautour',
|
||||||
|
resultItemName: 'Cape du Vautour',
|
||||||
|
craftDurationSeconds: 90,
|
||||||
|
enduranceCost: 16,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Plume de Vautour', quantity: 4 },
|
||||||
|
{ materialName: 'Bandelette Maudite', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Armure de la Momie',
|
||||||
|
resultItemName: 'Armure de la Momie',
|
||||||
|
craftDurationSeconds: 150,
|
||||||
|
enduranceCost: 20,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Bandelette Maudite', quantity: 3 },
|
||||||
|
{ materialName: 'Sable Cristallisé', quantity: 2 },
|
||||||
|
{ materialName: 'Cuir de Croco', quantity: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Lame du Désert',
|
||||||
|
resultItemName: 'Lame du Désert',
|
||||||
|
craftDurationSeconds: 180,
|
||||||
|
enduranceCost: 22,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Sable Cristallisé', quantity: 3 },
|
||||||
|
{ materialName: 'Dard de Scorpion', quantity: 2 },
|
||||||
|
{ materialName: 'Bandelette Maudite', quantity: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Craft Sceptre Prophétique',
|
||||||
|
resultItemName: 'Sceptre Prophétique',
|
||||||
|
craftDurationSeconds: 300,
|
||||||
|
enduranceCost: 25,
|
||||||
|
ingredientNames: [
|
||||||
|
{ materialName: 'Œil du Sphinx', quantity: 1 },
|
||||||
|
{ materialName: 'Sable Cristallisé', quantity: 3 },
|
||||||
|
{ materialName: 'Bandelette Maudite', quantity: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
// SEED RUNNER
|
||||||
|
// ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function seed() {
|
||||||
|
await AppDataSource.initialize();
|
||||||
|
console.log('DB connectée (MySQL)');
|
||||||
|
|
||||||
|
const materialRepo = AppDataSource.getRepository(Material);
|
||||||
|
const itemRepo = AppDataSource.getRepository(Item);
|
||||||
|
const recipeRepo = AppDataSource.getRepository(Recipe);
|
||||||
|
const monsterRepo = AppDataSource.getRepository(Monster);
|
||||||
|
|
||||||
|
// 1. Seed matériaux
|
||||||
|
console.log('\n── Matériaux ──');
|
||||||
|
const materialMap: Record<string, string> = {};
|
||||||
|
|
||||||
|
// Charger les matériaux existants dans le map
|
||||||
|
const existingMats = await materialRepo.find();
|
||||||
|
for (const mat of existingMats) {
|
||||||
|
materialMap[mat.name] = mat.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const data of NEW_MATERIALS) {
|
||||||
|
if (materialMap[data.name!]) {
|
||||||
|
console.log(`⏭ ${data.name} (déjà présent)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const mat = await materialRepo.save(materialRepo.create(data));
|
||||||
|
materialMap[mat.name] = mat.id;
|
||||||
|
console.log(`✅ ${data.name} (${data.rarity})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Seed items craftables
|
||||||
|
console.log('\n── Items craftables ──');
|
||||||
|
const itemMap: Record<string, string> = {};
|
||||||
|
|
||||||
|
const existingItems = await itemRepo.find();
|
||||||
|
for (const item of existingItems) {
|
||||||
|
itemMap[item.name] = item.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const data of NEW_ITEMS) {
|
||||||
|
if (itemMap[data.name!]) {
|
||||||
|
console.log(`⏭ ${data.name} (déjà présent)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const item = await itemRepo.save(itemRepo.create(data));
|
||||||
|
itemMap[item.name] = item.id;
|
||||||
|
console.log(`✅ ${data.name} (${data.rarity}, ${data.type})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Mapper les drops des monstres Égouts/Désert
|
||||||
|
console.log('\n── Monster drops ──');
|
||||||
|
for (const [monsterName, materialName] of Object.entries(MONSTER_LOOT)) {
|
||||||
|
const materialId = materialMap[materialName];
|
||||||
|
if (!materialId) {
|
||||||
|
console.error(`❌ Matériau "${materialName}" introuvable pour ${monsterName}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const monster = await monsterRepo.findOne({ where: { name: monsterName } });
|
||||||
|
if (!monster) {
|
||||||
|
console.error(`❌ Monstre "${monsterName}" introuvable`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (monster.dropMaterialId === materialId) {
|
||||||
|
console.log(`⏭ ${monsterName} → ${materialName} (déjà mappé)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
monster.dropMaterialId = materialId;
|
||||||
|
await monsterRepo.save(monster);
|
||||||
|
console.log(`✅ ${monsterName} → ${materialName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Seed recettes
|
||||||
|
console.log('\n── Recettes ──');
|
||||||
|
for (const recipeDef of NEW_RECIPES) {
|
||||||
|
const exists = await recipeRepo.findOne({ where: { name: recipeDef.name } });
|
||||||
|
if (exists) {
|
||||||
|
console.log(`⏭ ${recipeDef.name} (déjà présente)`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultItemId = itemMap[recipeDef.resultItemName];
|
||||||
|
if (!resultItemId) {
|
||||||
|
console.error(`❌ Item "${recipeDef.resultItemName}" introuvable pour recette ${recipeDef.name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ingredients = recipeDef.ingredientNames.map((ing) => {
|
||||||
|
const matId = materialMap[ing.materialName];
|
||||||
|
if (!matId) {
|
||||||
|
throw new Error(`Matériau "${ing.materialName}" introuvable pour recette ${recipeDef.name}`);
|
||||||
|
}
|
||||||
|
return { materialId: matId, quantity: ing.quantity };
|
||||||
|
});
|
||||||
|
|
||||||
|
await recipeRepo.save(
|
||||||
|
recipeRepo.create({
|
||||||
|
name: recipeDef.name,
|
||||||
|
resultItemId,
|
||||||
|
craftDurationSeconds: recipeDef.craftDurationSeconds,
|
||||||
|
enduranceCost: recipeDef.enduranceCost,
|
||||||
|
ingredients,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
console.log(`✅ ${recipeDef.name} (${recipeDef.ingredientNames.length} ingrédients, ${recipeDef.craftDurationSeconds}s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Résumé
|
||||||
|
const totalMats = await materialRepo.count();
|
||||||
|
const totalItems = await itemRepo.count();
|
||||||
|
const totalRecipes = await recipeRepo.count();
|
||||||
|
const monstersWithDrop = await monsterRepo.count({ where: { dropMaterialId: undefined } as any });
|
||||||
|
const totalMonsters = await monsterRepo.count();
|
||||||
|
|
||||||
|
console.log(`\n✅ Seed craft/drops terminé`);
|
||||||
|
console.log(` Matériaux: ${totalMats} | Items: ${totalItems} | Recettes: ${totalRecipes} | Monstres: ${totalMonsters}`);
|
||||||
|
|
||||||
|
await AppDataSource.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
seed().catch((err) => {
|
||||||
|
console.error('Seed craft/drops échoué :', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user