feat: add hunt page, combat placeholder, and Jagd navigation

Wires up Slice 0.2 end-to-end: HuntPageComponent renders the
hunting-unavailable/ready/loading/encounters-found states off
HuntingStore and WorldStore, Angreifen hands the HuntEncounter id to a
new inert CombatPlaceholderPageComponent via /combat/new, the Jagd nav
entry is enabled with router-driven active state (matching Karte's),
and the context panel now lists possible encounters for hunting-enabled
locations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-19 14:06:57 +02:00
parent 784bd3ce3a
commit 4e684dc45e
11 changed files with 664 additions and 24 deletions

View File

@@ -30,6 +30,12 @@
<dt>Jagd</dt>
<dd>{{ location.huntingEnabled ? 'Jagd möglich' : 'Keine Jagd' }}</dd>
</div>
@if (location.huntingEnabled && location.possibleMonsters.length) {
<div>
<dt>Mögliche Begegnungen</dt>
<dd>{{ location.possibleMonsters.join(', ') }}</dd>
</div>
}
</dl>
} @else {
<span class="context-panel__eyebrow">GEBIETSINFO</span>

View File

@@ -3,6 +3,33 @@ import { TestBed } from '@angular/core/testing';
import { WorldStore } from '../../features/world/world.store';
import { ContextPanelComponent } from './context-panel.component';
const southGate = {
id: 'south-gate-id',
key: 'south-gate',
name: 'Südtor von Graufurt',
description: 'Der letzte sichere Schritt vor den Aschenfeldern.',
regionKey: 'ashen-fields',
minRecommendedLevel: 1,
maxRecommendedLevel: 1,
dangerLevel: 0,
isSafe: true,
huntingEnabled: false,
artworkPath: '/images/backgrounds/Suedtor.png',
connections: [],
possibleMonsters: [],
};
const burnedRoad = {
...southGate,
id: 'burned-road-id',
key: 'burned-road',
name: 'Verbrannte Straße',
isSafe: false,
huntingEnabled: true,
artworkPath: '/images/backgrounds/Aschestrasse.png',
possibleMonsters: ['Aschenratte', 'Straßenräuber'],
};
describe('ContextPanelComponent', () => {
it('uses a runtime location derivative while preserving the API artwork path as image fallback', async () => {
await TestBed.configureTestingModule({
@@ -11,20 +38,7 @@ describe('ContextPanelComponent', () => {
{
provide: WorldStore,
useValue: {
currentLocation: signal({
id: 'south-gate-id',
key: 'south-gate',
name: 'Südtor von Graufurt',
description: 'Der letzte sichere Schritt vor den Aschenfeldern.',
regionKey: 'ashen-fields',
minRecommendedLevel: 1,
maxRecommendedLevel: 1,
dangerLevel: 0,
isSafe: true,
huntingEnabled: false,
artworkPath: '/images/backgrounds/Suedtor.png',
connections: [],
}),
currentLocation: signal(southGate),
selectedConnection: signal(null),
},
},
@@ -42,4 +56,48 @@ describe('ContextPanelComponent', () => {
'/images/backgrounds/Suedtor.png',
);
});
it('hides "Mögliche Begegnungen" when the location has no hunting', async () => {
await TestBed.configureTestingModule({
imports: [ContextPanelComponent],
providers: [
{
provide: WorldStore,
useValue: {
currentLocation: signal(southGate),
selectedConnection: signal(null),
},
},
],
}).compileComponents();
const fixture = TestBed.createComponent(ContextPanelComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
expect(element.textContent).not.toContain('Mögliche Begegnungen');
});
it('lists possible monsters as plain text when hunting is enabled', async () => {
await TestBed.configureTestingModule({
imports: [ContextPanelComponent],
providers: [
{
provide: WorldStore,
useValue: {
currentLocation: signal(burnedRoad),
selectedConnection: signal(null),
},
},
],
}).compileComponents();
const fixture = TestBed.createComponent(ContextPanelComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
expect(element.textContent).toContain('Mögliche Begegnungen');
expect(element.textContent).toContain('Aschenratte, Straßenräuber');
expect(element.textContent).not.toMatch(/\d+\s?%/);
});
});

View File

@@ -1,10 +1,12 @@
<nav class="side-navigation" aria-label="Spielnavigation">
<button
class="side-navigation__item side-navigation__item--active"
class="side-navigation__item"
type="button"
routerLink="/world"
routerLinkActive="side-navigation__item--active"
[routerLinkActiveOptions]="{ exact: true }"
ariaCurrentWhenActive="page"
data-navigation="world"
aria-current="page"
aria-label="Karte"
>
<img src="/images/hud/runtime/MapsIcon-128.png" alt="" />
@@ -14,9 +16,12 @@
<button
class="side-navigation__item"
type="button"
routerLink="/hunt"
routerLinkActive="side-navigation__item--active"
[routerLinkActiveOptions]="{ exact: true }"
ariaCurrentWhenActive="page"
data-navigation="hunt"
disabled
aria-label="Jagd ist noch nicht verfügbar"
aria-label="Jagd"
>
<img src="/images/hud/runtime/HuntIcon-128.png" alt="" />
<span>Jagd</span>

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { RouterLink, RouterLinkActive } from '@angular/router';
@Component({
selector: 'app-side-navigation',
imports: [RouterLink],
imports: [RouterLink, RouterLinkActive],
templateUrl: './side-navigation.component.html',
styleUrl: './side-navigation.component.scss',
})