feat(combat): add frontend Combat models and API methods
Adds Combat domain models (Combat, CombatPlayer, CombatMonster, CombatEvent) and three GameApiService methods to interact with backend combat endpoints: - startCombat(encounterId): POST /api/hunt-encounters/:id/attack - getCombat(combatId): GET /api/combats/:id - performCombatAction(combatId, action): POST /api/combats/:id/actions Includes test coverage for all three methods. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,3 +68,41 @@ export interface HuntResult {
|
|||||||
location: LocationSummary;
|
location: LocationSummary;
|
||||||
encounters: HuntEncounter[];
|
encounters: HuntEncounter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CombatStatus = 'ACTIVE' | 'WON' | 'LOST';
|
||||||
|
export type CombatEventType = 'DAMAGE' | 'COMBAT_WON' | 'COMBAT_LOST';
|
||||||
|
export type CombatSide = 'PLAYER' | 'MONSTER';
|
||||||
|
export type CombatAction = 'ATTACK';
|
||||||
|
|
||||||
|
export interface CombatEvent {
|
||||||
|
round: number;
|
||||||
|
sequence: number;
|
||||||
|
type: CombatEventType;
|
||||||
|
source: CombatSide;
|
||||||
|
target: CombatSide;
|
||||||
|
amount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CombatPlayer {
|
||||||
|
name: string;
|
||||||
|
maxHp: number;
|
||||||
|
currentHp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CombatMonster {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
level: number;
|
||||||
|
maxHp: number;
|
||||||
|
currentHp: number;
|
||||||
|
artworkPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Combat {
|
||||||
|
id: string;
|
||||||
|
status: CombatStatus;
|
||||||
|
round: number;
|
||||||
|
player: CombatPlayer;
|
||||||
|
monster: CombatMonster;
|
||||||
|
events: CombatEvent[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,4 +47,30 @@ describe('GameApiService', () => {
|
|||||||
expect(request.request.body).toEqual({ targetLocationId: 'target-uuid' });
|
expect(request.request.body).toEqual({ targetLocationId: 'target-uuid' });
|
||||||
request.flush({ status: 'IDLE' });
|
request.flush({ status: 'IDLE' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('posts to the encounter-scoped attack endpoint with an empty body to start a combat', () => {
|
||||||
|
service.startCombat('encounter-uuid').subscribe();
|
||||||
|
|
||||||
|
const request = http.expectOne('/api/hunt-encounters/encounter-uuid/attack');
|
||||||
|
expect(request.request.method).toBe('POST');
|
||||||
|
expect(request.request.body).toEqual({});
|
||||||
|
request.flush({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets a combat by id', () => {
|
||||||
|
service.getCombat('combat-uuid').subscribe();
|
||||||
|
|
||||||
|
const request = http.expectOne('/api/combats/combat-uuid');
|
||||||
|
expect(request.request.method).toBe('GET');
|
||||||
|
request.flush({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts only the action enum when performing a combat action', () => {
|
||||||
|
service.performCombatAction('combat-uuid', 'ATTACK').subscribe();
|
||||||
|
|
||||||
|
const request = http.expectOne('/api/combats/combat-uuid/actions');
|
||||||
|
expect(request.request.method).toBe('POST');
|
||||||
|
expect(request.request.body).toEqual({ action: 'ATTACK' });
|
||||||
|
request.flush({});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { CharacterResponse, CurrentLocationResponse, CurrentTravel, HuntResult } from './game-api.models';
|
import {
|
||||||
|
CharacterResponse,
|
||||||
|
Combat,
|
||||||
|
CombatAction,
|
||||||
|
CurrentLocationResponse,
|
||||||
|
CurrentTravel,
|
||||||
|
HuntResult,
|
||||||
|
} from './game-api.models';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class GameApiService {
|
export class GameApiService {
|
||||||
@@ -26,4 +33,16 @@ export class GameApiService {
|
|||||||
startHunt(): Observable<HuntResult> {
|
startHunt(): Observable<HuntResult> {
|
||||||
return this.http.post<HuntResult>('/api/hunts', {});
|
return this.http.post<HuntResult>('/api/hunts', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startCombat(encounterId: string): Observable<Combat> {
|
||||||
|
return this.http.post<Combat>(`/api/hunt-encounters/${encounterId}/attack`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCombat(combatId: string): Observable<Combat> {
|
||||||
|
return this.http.get<Combat>(`/api/combats/${combatId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
performCombatAction(combatId: string, action: CombatAction): Observable<Combat> {
|
||||||
|
return this.http.post<Combat>(`/api/combats/${combatId}/actions`, { action });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user