Plusieurs ajouts :
Some checks failed
Build & Push Images / build (brain) (push) Has been cancelled
Build & Push Images / build (core) (push) Has been cancelled
Build & Push Images / build (web) (push) Has been cancelled
Build & Push Images / build-switcher (push) Has been cancelled

- Possibilité de configurer des lieux dans une scène : permet de configurer un donjon par exemple avec les pièces, les trésors par pièce, la narration..... Mise en place également de conditions permettant de conditionner le déblocage des quêtes.
- Possibilité de transformé un arc en instance non linéaire afin de faire un hub. Permet de jouer de préparer des campagnes type Dragon of Icespire peak plus facilement.
- Configuration de partie : chaque partie va contenir les séances, ce qui permettra de suivre le déblocage des conditions pour les quêtes.

Passage en 0.9.1-beta
This commit is contained in:
2026-06-03 15:44:48 +02:00
parent e3fc96a1bc
commit 504c4b7b6c
147 changed files with 5347 additions and 392 deletions

View File

@@ -0,0 +1,133 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { LucideAngularModule, Plus, Trash2, ChevronDown, ChevronUp, GitBranch, X } from 'lucide-angular';
import { Room, RoomBranch } from '../../services/campaign.model';
/**
* Éditeur de pièces (Rooms) d'une Scene explorable.
*
* Contrôlé : reçoit la liste, émet la nouvelle liste à chaque mutation.
* Une pièce est repliée par défaut (juste le nom visible) ; un clic l'expand
* pour éditer narration / ennemis / loot / pièges / notes MJ / étage.
*
* Les branches (graphe inter-pièces) sont éditables dans la card expand.
*/
@Component({
selector: 'app-rooms-editor',
standalone: true,
imports: [CommonModule, FormsModule, LucideAngularModule],
templateUrl: './rooms-editor.component.html',
styleUrls: ['./rooms-editor.component.scss']
})
export class RoomsEditorComponent {
@Input() rooms: Room[] = [];
@Output() roomsChange = new EventEmitter<Room[]>();
readonly Plus = Plus;
readonly Trash2 = Trash2;
readonly ChevronDown = ChevronDown;
readonly ChevronUp = ChevronUp;
readonly GitBranch = GitBranch;
readonly X = X;
/** Set des IDs de pièces expandées (UI state local). */
private expanded = new Set<string>();
isExpanded(id: string): boolean { return this.expanded.has(id); }
toggleExpanded(id: string): void {
if (this.expanded.has(id)) this.expanded.delete(id);
else this.expanded.add(id);
}
/** Génère un UUID v4 simplifié pour l'ID stable d'une pièce. */
private newId(): string {
return 'room-' + Math.random().toString(36).slice(2) + '-' + Date.now().toString(36);
}
addRoom(): void {
const nextOrder = this.rooms.length > 0
? Math.max(...this.rooms.map(r => r.order ?? 0)) + 1
: 0;
const newRoom: Room = {
id: this.newId(),
name: `Salle ${this.rooms.length + 1}`,
order: nextOrder,
branches: []
};
this.rooms = [...this.rooms, newRoom];
this.expanded.add(newRoom.id);
this.emit();
}
removeRoom(room: Room): void {
this.rooms = this.rooms
.filter(r => r.id !== room.id)
.map(r => ({
...r,
// Nettoyer les branches qui pointaient vers la pièce supprimée
branches: (r.branches ?? []).filter(b => b.targetRoomId !== room.id)
}));
this.expanded.delete(room.id);
this.emit();
}
/** Patch un champ d'une pièce et émet. */
patch(room: Room, patch: Partial<Room>): void {
this.rooms = this.rooms.map(r => r.id === room.id ? { ...r, ...patch } : r);
this.emit();
}
// === Branches inter-pièces ===
addBranch(room: Room): void {
const others = this.rooms.filter(r => r.id !== room.id);
const branch: RoomBranch = {
label: 'Porte',
targetRoomId: others[0]?.id ?? '',
condition: ''
};
this.patch(room, { branches: [...(room.branches ?? []), branch] });
}
removeBranch(room: Room, index: number): void {
const next = (room.branches ?? []).filter((_, i) => i !== index);
this.patch(room, { branches: next });
}
updateBranch(room: Room, index: number, patch: Partial<RoomBranch>): void {
const next = (room.branches ?? []).map((b, i) => i === index ? { ...b, ...patch } : b);
this.patch(room, { branches: next });
}
/** Pièces candidates pour cibler une branche (toutes sauf celle d'origine). */
otherRooms(room: Room): Room[] {
return this.rooms.filter(r => r.id !== room.id);
}
/** Réorganise l'ordre via boutons up/down (simple : swap avec voisin). */
moveUp(room: Room): void {
const idx = this.rooms.findIndex(r => r.id === room.id);
if (idx <= 0) return;
const arr = [...this.rooms];
[arr[idx - 1], arr[idx]] = [arr[idx], arr[idx - 1]];
this.rooms = arr.map((r, i) => ({ ...r, order: i }));
this.emit();
}
moveDown(room: Room): void {
const idx = this.rooms.findIndex(r => r.id === room.id);
if (idx === -1 || idx >= this.rooms.length - 1) return;
const arr = [...this.rooms];
[arr[idx], arr[idx + 1]] = [arr[idx + 1], arr[idx]];
this.rooms = arr.map((r, i) => ({ ...r, order: i }));
this.emit();
}
trackById(_: number, r: Room): string { return r.id; }
private emit(): void {
this.roomsChange.emit(this.rooms);
}
}