Ajout de la possibilité de faire des stats blocs pour tout ce qui est ennemis / créatures adverses.
All checks were successful
All checks were successful
Dorénavant, l'IA est capable de prendre en compte le format des quêtes, chapitres, Arc.... pour proposer des blocs plus complets. Les ennemis sont également référençables directement dans la campagne. Les références vers les ennemis dans la partie "donjon" est en cours d'ajout
This commit is contained in:
@@ -8,6 +8,7 @@ import { CampaignService } from '../../../services/campaign.service';
|
||||
import { CharacterService } from '../../../services/character.service';
|
||||
import { NpcService } from '../../../services/npc.service';
|
||||
import { RandomTableService } from '../../../services/random-table.service';
|
||||
import { EnemyService } from '../../../services/enemy.service';
|
||||
import { LayoutService } from '../../../services/layout.service';
|
||||
import { loadCampaignTreeData, buildCampaignSidebarConfig } from '../../campaign-tree.helper';
|
||||
import { IconPickerComponent } from '../../../shared/icon-picker/icon-picker.component';
|
||||
@@ -42,6 +43,7 @@ export class SceneCreateComponent implements OnInit, OnDestroy {
|
||||
private characterService: CharacterService,
|
||||
private npcService: NpcService,
|
||||
private randomTableService: RandomTableService,
|
||||
private enemyService: EnemyService,
|
||||
private layoutService: LayoutService
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
@@ -61,7 +63,7 @@ export class SceneCreateComponent implements OnInit, OnDestroy {
|
||||
forkJoin({
|
||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService, this.npcService, this.randomTableService)
|
||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService, this.npcService, this.randomTableService, this.enemyService)
|
||||
}).subscribe(({ campaign, allCampaigns, treeData }) => {
|
||||
const currentChapter = (treeData.chaptersByArc[this.arcId] ?? []).find(c => c.id === this.chapterId);
|
||||
this.chapterName = currentChapter?.name ?? '';
|
||||
|
||||
@@ -202,7 +202,19 @@
|
||||
<input id="scene-edit-combat-difficulty" type="text" formControlName="combatDifficulty" placeholder="Ex: Moyenne, 3 gobelins niveau 2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="scene-edit-enemies">Ennemis et créatures</label>
|
||||
<label>Ennemis du bestiaire</label>
|
||||
<app-enemy-link-picker
|
||||
[value]="enemyIds"
|
||||
[availableEnemies]="availableEnemies"
|
||||
[campaignId]="campaignId"
|
||||
(valueChange)="enemyIds = $event">
|
||||
</app-enemy-link-picker>
|
||||
<small class="field-hint">
|
||||
Référencez des fiches de votre bestiaire — cliquez sur un chip pour ouvrir la fiche.
|
||||
</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="scene-edit-enemies">Ennemis et créatures (texte libre)</label>
|
||||
<textarea
|
||||
id="scene-edit-enemies"
|
||||
formControlName="enemies"
|
||||
|
||||
@@ -9,14 +9,17 @@ import { CampaignService } from '../../../services/campaign.service';
|
||||
import { CharacterService } from '../../../services/character.service';
|
||||
import { NpcService } from '../../../services/npc.service';
|
||||
import { RandomTableService } from '../../../services/random-table.service';
|
||||
import { EnemyService } from '../../../services/enemy.service';
|
||||
import { PageService } from '../../../services/page.service';
|
||||
import { LayoutService } from '../../../services/layout.service';
|
||||
import { PageTitleService } from '../../../services/page-title.service';
|
||||
import { Scene, SceneBranch, Room } from '../../../services/campaign.model';
|
||||
import { Page } from '../../../services/page.model';
|
||||
import { Enemy } from '../../../services/enemy.model';
|
||||
import { loadCampaignTreeData, buildCampaignSidebarConfig } from '../../campaign-tree.helper';
|
||||
import { ExpandableSectionComponent } from '../../../shared/expandable-section/expandable-section.component';
|
||||
import { LoreLinkPickerComponent } from '../../../shared/lore-link-picker/lore-link-picker.component';
|
||||
import { EnemyLinkPickerComponent } from '../../../shared/enemy-link-picker/enemy-link-picker.component';
|
||||
import { AiChatDrawerComponent } from '../../../shared/ai-chat-drawer/ai-chat-drawer.component';
|
||||
import { ImageGalleryComponent } from '../../../shared/image-gallery/image-gallery.component';
|
||||
import { IconPickerComponent } from '../../../shared/icon-picker/icon-picker.component';
|
||||
@@ -30,7 +33,7 @@ import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dia
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-scene-edit',
|
||||
imports: [ReactiveFormsModule, LucideAngularModule, ExpandableSectionComponent, LoreLinkPickerComponent, AiChatDrawerComponent, ImageGalleryComponent, IconPickerComponent, RoomsEditorComponent],
|
||||
imports: [ReactiveFormsModule, LucideAngularModule, ExpandableSectionComponent, LoreLinkPickerComponent, EnemyLinkPickerComponent, AiChatDrawerComponent, ImageGalleryComponent, IconPickerComponent, RoomsEditorComponent],
|
||||
templateUrl: './scene-edit.component.html',
|
||||
styleUrls: ['./scene-edit.component.scss']
|
||||
})
|
||||
@@ -60,6 +63,9 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
||||
availablePages: Page[] = [];
|
||||
loreId: string | null = null;
|
||||
relatedPageIds: string[] = [];
|
||||
/** Bestiaire de la campagne + fiches liées à la rencontre. */
|
||||
availableEnemies: Enemy[] = [];
|
||||
enemyIds: string[] = [];
|
||||
illustrationImageIds: string[] = [];
|
||||
mapImageIds: string[] = [];
|
||||
|
||||
@@ -81,6 +87,7 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
||||
private characterService: CharacterService,
|
||||
private npcService: NpcService,
|
||||
private randomTableService: RandomTableService,
|
||||
private enemyService: EnemyService,
|
||||
private pageService: PageService,
|
||||
private layoutService: LayoutService,
|
||||
private pageTitleService: PageTitleService,
|
||||
@@ -133,7 +140,7 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||
scene: this.campaignService.getSceneById(this.sceneId),
|
||||
chapterScenes: this.campaignService.getScenes(this.chapterId),
|
||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService, this.npcService, this.randomTableService)
|
||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService, this.npcService, this.randomTableService, this.enemyService)
|
||||
}).pipe(
|
||||
switchMap(data => {
|
||||
const lid = data.campaign.loreId ?? null;
|
||||
@@ -146,6 +153,8 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
||||
this.loreId = loreId;
|
||||
this.availablePages = pages;
|
||||
this.relatedPageIds = [...(scene.relatedPageIds ?? [])];
|
||||
this.availableEnemies = treeData.enemies ?? [];
|
||||
this.enemyIds = [...(scene.enemyIds ?? [])];
|
||||
this.selectedIcon = scene.icon ?? null;
|
||||
this.illustrationImageIds = [...(scene.illustrationImageIds ?? [])];
|
||||
this.mapImageIds = [...(scene.mapImageIds ?? [])];
|
||||
@@ -184,6 +193,7 @@ export class SceneEditComponent implements OnInit, OnDestroy {
|
||||
choicesConsequences: this.form.value.choicesConsequences,
|
||||
combatDifficulty: this.form.value.combatDifficulty,
|
||||
enemies: this.form.value.enemies,
|
||||
enemyIds: this.enemyIds,
|
||||
relatedPageIds: this.relatedPageIds,
|
||||
illustrationImageIds: this.illustrationImageIds,
|
||||
mapImageIds: this.mapImageIds,
|
||||
|
||||
@@ -81,17 +81,29 @@
|
||||
</section>
|
||||
}
|
||||
<!-- Combat ou rencontre -->
|
||||
@if (scene.combatDifficulty?.trim() || scene.enemies?.trim()) {
|
||||
@if (scene.combatDifficulty?.trim() || scene.enemies?.trim() || linkedEnemies.length > 0) {
|
||||
@if (scene.combatDifficulty?.trim()) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">⚔️</span> Difficulté estimée</h2>
|
||||
<p class="view-section-body">{{ scene.combatDifficulty }}</p>
|
||||
</section>
|
||||
}
|
||||
@if (scene.enemies?.trim()) {
|
||||
@if (scene.enemies?.trim() || linkedEnemies.length > 0) {
|
||||
<section class="view-section">
|
||||
<h2 class="view-section-title"><span class="view-section-icon">🐲</span> Ennemis et créatures</h2>
|
||||
<p class="view-section-body">{{ scene.enemies }}</p>
|
||||
@if (linkedEnemies.length > 0) {
|
||||
<div class="view-chips">
|
||||
@for (e of linkedEnemies; track e.id) {
|
||||
<a class="view-chip"
|
||||
[routerLink]="['/campaigns', campaignId, 'enemies', e.id]">
|
||||
{{ enemyLabel(e) }}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (scene.enemies?.trim()) {
|
||||
<p class="view-section-body">{{ scene.enemies }}</p>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import { CampaignService } from '../../../services/campaign.service';
|
||||
import { CharacterService } from '../../../services/character.service';
|
||||
import { NpcService } from '../../../services/npc.service';
|
||||
import { RandomTableService } from '../../../services/random-table.service';
|
||||
import { EnemyService } from '../../../services/enemy.service';
|
||||
import { PageService } from '../../../services/page.service';
|
||||
import { LayoutService } from '../../../services/layout.service';
|
||||
import { PageTitleService } from '../../../services/page-title.service';
|
||||
import { Scene } from '../../../services/campaign.model';
|
||||
import { Page } from '../../../services/page.model';
|
||||
import { Enemy } from '../../../services/enemy.model';
|
||||
import { loadCampaignTreeData, buildCampaignSidebarConfig } from '../../campaign-tree.helper';
|
||||
import { ImageGalleryComponent } from '../../../shared/image-gallery/image-gallery.component';
|
||||
import { ConfirmDialogService } from '../../../shared/confirm-dialog/confirm-dialog.service';
|
||||
@@ -41,6 +43,8 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
||||
|
||||
loreId: string | null = null;
|
||||
availablePages: Page[] = [];
|
||||
/** Bestiaire de la campagne — résout les enemyIds de la scène en fiches. */
|
||||
availableEnemies: Enemy[] = [];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -49,6 +53,7 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
||||
private characterService: CharacterService,
|
||||
private npcService: NpcService,
|
||||
private randomTableService: RandomTableService,
|
||||
private enemyService: EnemyService,
|
||||
private pageService: PageService,
|
||||
private layoutService: LayoutService,
|
||||
private pageTitleService: PageTitleService,
|
||||
@@ -79,7 +84,7 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
||||
campaign: this.campaignService.getCampaignById(this.campaignId),
|
||||
allCampaigns: this.campaignService.getAllCampaigns(),
|
||||
scene: this.campaignService.getSceneById(this.sceneId),
|
||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService, this.npcService, this.randomTableService)
|
||||
treeData: loadCampaignTreeData(this.campaignService, this.campaignId, this.characterService, this.npcService, this.randomTableService, this.enemyService)
|
||||
}).pipe(
|
||||
switchMap(data => {
|
||||
const lid = data.campaign.loreId ?? null;
|
||||
@@ -90,6 +95,7 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
||||
this.scene = scene;
|
||||
this.loreId = loreId;
|
||||
this.availablePages = pages;
|
||||
this.availableEnemies = treeData.enemies ?? [];
|
||||
this.pageTitleService.set(scene.name);
|
||||
|
||||
this.layoutService.show(buildCampaignSidebarConfig(campaign, allCampaigns, treeData, this.campaignId));
|
||||
@@ -100,6 +106,19 @@ export class SceneViewComponent implements OnInit, OnDestroy {
|
||||
return this.availablePages.find(p => p.id === pageId)?.title ?? '(page supprimée)';
|
||||
}
|
||||
|
||||
/** Fiches du bestiaire liées à la rencontre (IDs orphelins ignorés). */
|
||||
get linkedEnemies(): Enemy[] {
|
||||
return (this.scene?.enemyIds ?? [])
|
||||
.map(id => this.availableEnemies.find(e => e.id === id))
|
||||
.filter((e): e is Enemy => !!e);
|
||||
}
|
||||
|
||||
/** Libellé d'un chip ennemi : nom + niveau s'il est renseigné. */
|
||||
enemyLabel(enemy: Enemy): string {
|
||||
const level = enemy.level?.trim();
|
||||
return level ? `${enemy.name} (${level})` : enemy.name;
|
||||
}
|
||||
|
||||
/** Résout le nom d'une pièce cible (pour afficher les sorties inter-pièces). */
|
||||
roomNameById(scene: Scene | null, roomId: string): string {
|
||||
return scene?.rooms?.find(r => r.id === roomId)?.name ?? '(pièce supprimée)';
|
||||
|
||||
Reference in New Issue
Block a user