feat(combat): add CombatPageComponent and replace the combat/new placeholder route
Wires up the Slice 0.3 combat screen (player/monster HP bars, round display, Angriff action, grouped German combat log, victory/defeat panels) and replaces the Slice 0.2 combat/new placeholder route with combat/:combatId loading CombatPageComponent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import type { CombatEvent } from '../../../core/api/game-api.models';
|
||||
import { runtimeMonsterArtworkPath } from '../../../shared/monster-artwork';
|
||||
import { CombatStore } from '../combat.store';
|
||||
|
||||
interface CombatLogRound {
|
||||
round: number;
|
||||
events: CombatEvent[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-combat-page',
|
||||
templateUrl: './combat-page.component.html',
|
||||
styleUrl: './combat-page.component.scss',
|
||||
})
|
||||
export class CombatPageComponent implements OnInit {
|
||||
protected readonly combatStore = inject(CombatStore);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadFromRoute();
|
||||
}
|
||||
|
||||
protected attack(): void {
|
||||
void this.combatStore.attack();
|
||||
}
|
||||
|
||||
protected retry(): void {
|
||||
this.loadFromRoute();
|
||||
}
|
||||
|
||||
protected goToHunt(): void {
|
||||
void this.router.navigate(['/hunt']);
|
||||
}
|
||||
|
||||
protected runtimeMonsterArtwork(artworkPath: string): string | undefined {
|
||||
return runtimeMonsterArtworkPath(artworkPath);
|
||||
}
|
||||
|
||||
protected playerHpPercent(): number {
|
||||
const combat = this.combatStore.combat();
|
||||
return combat ? (combat.player.currentHp / combat.player.maxHp) * 100 : 0;
|
||||
}
|
||||
|
||||
protected monsterHpPercent(): number {
|
||||
const combat = this.combatStore.combat();
|
||||
return combat ? (combat.monster.currentHp / combat.monster.maxHp) * 100 : 0;
|
||||
}
|
||||
|
||||
protected logRounds(): CombatLogRound[] {
|
||||
const combat = this.combatStore.combat();
|
||||
if (!combat) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rounds = new Map<number, CombatEvent[]>();
|
||||
for (const event of combat.events) {
|
||||
const events = rounds.get(event.round) ?? [];
|
||||
events.push(event);
|
||||
rounds.set(event.round, events);
|
||||
}
|
||||
|
||||
return [...rounds.entries()].sort(([a], [b]) => a - b).map(([round, events]) => ({ round, events }));
|
||||
}
|
||||
|
||||
protected formatEvent(event: CombatEvent): string {
|
||||
const combat = this.combatStore.combat();
|
||||
const playerName = combat?.player.name ?? 'Du';
|
||||
const monsterName = combat?.monster.name ?? 'Der Gegner';
|
||||
|
||||
if (event.type === 'DAMAGE') {
|
||||
const attacker = event.source === 'PLAYER' ? playerName : monsterName;
|
||||
const defender = event.target === 'PLAYER' ? playerName : monsterName;
|
||||
return `${attacker} trifft ${defender} für ${event.amount} Schaden.`;
|
||||
}
|
||||
|
||||
if (event.type === 'COMBAT_WON') {
|
||||
return `${monsterName} wurde besiegt.`;
|
||||
}
|
||||
|
||||
return `${playerName} wurde im Kampf besiegt.`;
|
||||
}
|
||||
|
||||
private loadFromRoute(): void {
|
||||
const combatId = this.route.snapshot.paramMap.get('combatId');
|
||||
if (combatId) {
|
||||
void this.combatStore.loadCombat(combatId);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user