The hunt screen kept whatever roll was last in memory, so a player coming back from a fight saw every encounter as fresh. Encounters now carry their own status, which the combat module advances as fights start and end. - hunt_encounters.status replaces consumed_at, which only recorded that a fight had begun and could not distinguish a win from a loss - a lost fight hands the encounter back as AVAILABLE, so it can be retried; the unique index tying one combat to one encounter goes with it - GET /hunts/active serves the resumable hunt, which the hunt page adopts on entry rather than trusting its in-memory roll - defeated encounters are crossed out and lose their hover and attack action - a fresh page load rejoins a combat the server still holds open Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
import { Component, OnInit, inject } from '@angular/core';
|
|
import { Router } from '@angular/router';
|
|
import { CombatStore } from '../../combat/combat.store';
|
|
import { WorldStore } from '../../world/world.store';
|
|
import { EncounterCardComponent } from '../encounter-card/encounter-card.component';
|
|
import { HuntingStore } from '../hunting.store';
|
|
|
|
@Component({
|
|
selector: 'app-hunt-page',
|
|
imports: [EncounterCardComponent],
|
|
templateUrl: './hunt-page.component.html',
|
|
styleUrl: './hunt-page.component.scss',
|
|
})
|
|
export class HuntPageComponent implements OnInit {
|
|
protected readonly worldStore = inject(WorldStore);
|
|
protected readonly huntingStore = inject(HuntingStore);
|
|
protected readonly combatStore = inject(CombatStore);
|
|
private readonly router = inject(Router);
|
|
|
|
ngOnInit(): void {
|
|
if (this.worldStore.currentLocation() === null) {
|
|
void this.worldStore.load();
|
|
}
|
|
|
|
// The server owns which encounters are still open, so entering the page --
|
|
// including on the way back from a fight -- takes its word over whatever
|
|
// roll is still in memory.
|
|
void this.huntingStore.loadActiveHunt();
|
|
}
|
|
|
|
protected startHunt(): void {
|
|
void this.huntingStore.startHunt();
|
|
}
|
|
|
|
protected refreshHunt(): void {
|
|
void this.huntingStore.refreshHunt();
|
|
}
|
|
|
|
protected retry(): void {
|
|
if (this.huntingStore.currentHunt() === null) {
|
|
void this.huntingStore.startHunt();
|
|
} else {
|
|
void this.huntingStore.refreshHunt();
|
|
}
|
|
}
|
|
|
|
protected goToWorld(): void {
|
|
void this.router.navigate(['/world']);
|
|
}
|
|
|
|
protected async onAttack(encounterId: string): Promise<void> {
|
|
await this.combatStore.startCombat(encounterId);
|
|
const combat = this.combatStore.combat();
|
|
if (combat) {
|
|
void this.router.navigate(['/combat', combat.id]);
|
|
return;
|
|
}
|
|
|
|
// A fight already running is not a dead end: rejoin it rather than
|
|
// leaving the player stuck behind an error they cannot act on.
|
|
if (this.combatStore.errorCode() === 'COMBAT_ALREADY_ACTIVE') {
|
|
const active = await this.combatStore.loadActiveCombat();
|
|
if (active) {
|
|
void this.router.navigate(['/combat', active.id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected dismissCombatError(): void {
|
|
this.combatStore.clearError();
|
|
}
|
|
}
|