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

View File

@@ -0,0 +1,32 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Character } from '../character/entities/character.entity';
import { Material } from './material.entity';
@Entity('character_materials')
export class CharacterMaterial {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'character_id' })
characterId: string;
@ManyToOne(() => Character)
@JoinColumn({ name: 'character_id' })
character: Character;
@Column({ name: 'material_id' })
materialId: string;
@ManyToOne(() => Material, { eager: true })
@JoinColumn({ name: 'material_id' })
material: Material;
@Column({ default: 0 })
quantity: number;
}

View File

@@ -0,0 +1,21 @@
import { Controller, Get, UseGuards, Req } from '@nestjs/common';
import { Request } from 'express';
import { MaterialService } from './material.service';
import { AuthGuard } from '../auth/guards/auth.guard';
import { User } from '../user/user.entity';
@Controller('materials')
export class MaterialController {
constructor(private readonly materialService: MaterialService) {}
@Get()
findAll() {
return this.materialService.findAll();
}
@Get('inventory')
@UseGuards(AuthGuard)
getInventory(@Req() req: Request & { user: User }) {
return this.materialService.getInventory(req.user);
}
}

View File

@@ -0,0 +1,18 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
export type MaterialRarity = 'common' | 'rare' | 'epic';
@Entity('materials')
export class Material {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 100 })
name: string;
@Column({ type: 'text', nullable: true })
description: string | null;
@Column({ type: 'varchar', length: 20 })
rarity: MaterialRarity;
}

View File

@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Material } from './material.entity';
import { CharacterMaterial } from './character-material.entity';
import { MaterialService } from './material.service';
import { MaterialController } from './material.controller';
import { AuthModule } from '../auth/auth.module';
import { Character } from '../character/entities/character.entity';
@Module({
imports: [TypeOrmModule.forFeature([Material, CharacterMaterial, Character]), AuthModule],
controllers: [MaterialController],
providers: [MaterialService],
exports: [MaterialService, TypeOrmModule],
})
export class MaterialModule {}

View File

@@ -0,0 +1,61 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MoreThan, Repository } from 'typeorm';
import { Material } from './material.entity';
import { CharacterMaterial } from './character-material.entity';
import { Character } from '../character/entities/character.entity';
import { User } from '../user/user.entity';
@Injectable()
export class MaterialService {
constructor(
@InjectRepository(Material)
private readonly materialRepository: Repository<Material>,
@InjectRepository(CharacterMaterial)
private readonly charMatRepository: Repository<CharacterMaterial>,
@InjectRepository(Character)
private readonly characterRepository: Repository<Character>,
) {}
findAll() {
return this.materialRepository.find({ order: { rarity: 'ASC', name: 'ASC' } });
}
async getInventory(user: User) {
const char = await this.getCharacter(user);
return this.charMatRepository.find({
where: { characterId: char.id, quantity: MoreThan(0) },
});
}
// Appelé par CombatService après victoire (loot)
async addMaterial(characterId: string, materialId: string, quantity: number): Promise<CharacterMaterial> {
let entry = await this.charMatRepository.findOne({ where: { characterId, materialId } });
if (entry) {
entry.quantity += quantity;
} else {
entry = this.charMatRepository.create({ characterId, materialId, quantity });
}
return this.charMatRepository.save(entry);
}
// Appelé par CraftService pour consommer les ingrédients
async consumeMaterials(characterId: string, ingredients: { materialId: string; quantity: number }[]): Promise<void> {
for (const ing of ingredients) {
const entry = await this.charMatRepository.findOne({
where: { characterId, materialId: ing.materialId },
});
if (!entry || entry.quantity < ing.quantity) {
throw new BadRequestException('Matériaux insuffisants pour ce craft');
}
entry.quantity -= ing.quantity;
await this.charMatRepository.save(entry);
}
}
private async getCharacter(user: User): Promise<Character> {
const char = await this.characterRepository.findOne({ where: { userId: user.id } });
if (!char) throw new BadRequestException('Aucun personnage trouvé');
return char;
}
}