feat(sprint3): items + forge + craft + loot — équipement, artisanat lazy-calc, forge risque GDD

This commit is contained in:
2026-03-15 08:22:20 +01:00
parent 6d1230d16a
commit 23f7dd0f3c
25 changed files with 1169 additions and 2 deletions

144
src/database/items-seed.ts Normal file
View File

@@ -0,0 +1,144 @@
import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { Item } from '../item/item.entity';
import { Material } from '../material/material.entity';
import { Recipe } from '../craft/recipe.entity';
import { Monster } from '../monster/monster.entity';
const dataSource = new DataSource({
type: 'postgres',
url: process.env.DATABASE_URL ?? 'postgresql://tetardpg:password@localhost:5432/tetardpg',
entities: [Item, Material, Recipe, Monster],
synchronize: false,
});
const ITEMS = [
{ name: 'Bâton de Roseau', description: 'Un bâton taillé dans un roseau des marais.', type: 'weapon' as const, rarity: 'common' as const, attackBonus: 3 },
{ name: 'Dague Rouillée', description: 'Une dague usée mais encore tranchante.', type: 'weapon' as const, rarity: 'common' as const, attackBonus: 5 },
{ name: 'Épée Courte', description: 'Une épée courte bien équilibrée.', type: 'weapon' as const, rarity: 'rare' as const, attackBonus: 9 },
{ name: 'Gilet de Cuir', description: 'Un gilet de cuir tanné offrant une protection basique.', type: 'armor' as const, rarity: 'common' as const, defenseBonus: 3 },
{ name: 'Cotte de Mailles', description: 'Une cotte de mailles robuste.', type: 'armor' as const, rarity: 'rare' as const, defenseBonus: 7 },
];
const MATERIALS = [
{ name: 'Bave de Têtard', description: 'Substance visqueuse sécrétée par les têtards vases.', rarity: 'common' as const },
{ name: 'Écailles de Grenouille', description: 'Écailles dures et brillantes de grenouilles boueuses.', rarity: 'common' as const },
{ name: 'Venin de Serpent', description: 'Venin concentré extrait d\'un serpent des marais.', rarity: 'rare' as const },
{ name: 'Spores Vénéneuses', description: 'Spores toxiques récoltées sur les champi vénéneux.', rarity: 'rare' as const },
{ name: 'Fragment de Boue', description: 'Éclat de boue cristallisée prélevé sur un golem.', rarity: 'common' as const },
];
// Loot mapping : monster name → material name
const MONSTER_LOOT: Record<string, string> = {
'Têtard Vase': 'Bave de Têtard',
'Grenouille Boueuse': 'Écailles de Grenouille',
'Serpent des Marais': 'Venin de Serpent',
'Champi Vénéneux': 'Spores Vénéneuses',
'Golem de Boue': 'Fragment de Boue',
};
async function seed() {
await dataSource.initialize();
console.log('DB connectée');
const itemRepo = dataSource.getRepository(Item);
const materialRepo = dataSource.getRepository(Material);
const recipeRepo = dataSource.getRepository(Recipe);
const monsterRepo = dataSource.getRepository(Monster);
// Seed matériaux
const materialMap: Record<string, string> = {};
for (const data of MATERIALS) {
let mat = await materialRepo.findOne({ where: { name: data.name } });
if (!mat) {
mat = await materialRepo.save(materialRepo.create(data));
console.log(`✅ Matériau "${data.name}" seedé`);
} else {
console.log(`⏭ Matériau "${data.name}" déjà présent`);
}
materialMap[data.name] = mat.id;
}
// Seed items
const itemMap: Record<string, string> = {};
for (const data of ITEMS) {
let item = await itemRepo.findOne({ where: { name: data.name } });
if (!item) {
item = await itemRepo.save(itemRepo.create(data));
console.log(`✅ Item "${data.name}" seedé`);
} else {
console.log(`⏭ Item "${data.name}" déjà présent`);
}
itemMap[data.name] = item.id;
}
// Seed recettes
const RECIPES = [
{
name: 'Craft Dague Rouillée',
resultItemName: 'Dague Rouillée',
craftDurationSeconds: 15,
enduranceCost: 8,
ingredients: [
{ materialId: materialMap['Bave de Têtard'], quantity: 3 },
{ materialId: materialMap['Écailles de Grenouille'], quantity: 1 },
],
},
{
name: 'Craft Gilet de Cuir',
resultItemName: 'Gilet de Cuir',
craftDurationSeconds: 30,
enduranceCost: 10,
ingredients: [
{ materialId: materialMap['Écailles de Grenouille'], quantity: 3 },
{ materialId: materialMap['Fragment de Boue'], quantity: 2 },
],
},
{
name: 'Craft Épée Courte',
resultItemName: 'Épée Courte',
craftDurationSeconds: 60,
enduranceCost: 15,
ingredients: [
{ materialId: materialMap['Venin de Serpent'], quantity: 2 },
{ materialId: materialMap['Fragment de Boue'], quantity: 3 },
{ materialId: materialMap['Écailles de Grenouille'], quantity: 2 },
],
},
];
for (const data of RECIPES) {
const exists = await recipeRepo.findOne({ where: { name: data.name } });
if (!exists) {
await recipeRepo.save(recipeRepo.create({
name: data.name,
resultItemId: itemMap[data.resultItemName],
craftDurationSeconds: data.craftDurationSeconds,
enduranceCost: data.enduranceCost,
ingredients: data.ingredients,
}));
console.log(`✅ Recette "${data.name}" seedée`);
} else {
console.log(`⏭ Recette "${data.name}" déjà présente`);
}
}
// Mise à jour monsters avec leur drop_material_id
for (const [monsterName, materialName] of Object.entries(MONSTER_LOOT)) {
const monster = await monsterRepo.findOne({ where: { name: monsterName } });
const materialId = materialMap[materialName];
if (monster && materialId && monster.dropMaterialId !== materialId) {
monster.dropMaterialId = materialId;
await monsterRepo.save(monster);
console.log(`✅ Monstre "${monsterName}" → drop "${materialName}" mis à jour`);
}
}
console.log('✅ Seed Sprint 3 terminé');
await dataSource.destroy();
}
seed().catch((err) => {
console.error('Seed Sprint 3 échoué :', err);
process.exit(1);
});