Files
ashen-realms/apps/web/src/app/features/world/location-page/location-page.component.ts
Bastian Wagner 081c9f83f9 docs
2026-08-22 16:41:47 +02:00

113 lines
3.6 KiB
TypeScript

import { Component, OnInit, inject } from '@angular/core';
import { Router } from '@angular/router';
import {
LocationInteractionType,
LocationPointOfInterest,
LocationPrimaryAction,
} from '../../../core/api/game-api.models';
import { LocalLocationStore } from '../local-location.store';
import { LocationInteractionPanelComponent } from '../location-interaction-panel/location-interaction-panel.component';
import { LocationIconComponent } from '../location-icon/location-icon.component';
import { LocationPoiComponent } from '../location-poi/location-poi.component';
import { LocationSidebarComponent } from '../location-sidebar/location-sidebar.component';
const RUNTIME_ARTWORK: Readonly<Record<string, string>> = {
'/images/backgrounds/Suedtor.png': '/images/backgrounds/runtime/Suedtor-960.jpg',
'/images/backgrounds/Aschestrasse.png': '/images/backgrounds/runtime/Aschestrasse-960.jpg',
};
/**
* The screen the player stands on between activities.
*
* It renders whatever the current location's content describes and owns no
* knowledge of any particular place. Hunting and travel are not reimplemented
* here: HUNT and MAP hotspots hand off to the existing screens, and everything
* that reveals text goes through the server-authoritative interaction endpoint.
*/
@Component({
selector: 'app-location-page',
imports: [
LocationIconComponent,
LocationInteractionPanelComponent,
LocationPoiComponent,
LocationSidebarComponent,
],
templateUrl: './location-page.component.html',
styleUrl: './location-page.component.scss',
})
export class LocationPageComponent implements OnInit {
protected readonly store = inject(LocalLocationStore);
private readonly router = inject(Router);
ngOnInit(): void {
if (this.store.location() === null) {
void this.store.load();
}
}
protected retry(): void {
void this.store.load();
}
protected activatePoi(poi: LocationPointOfInterest): void {
this.dispatch(poi.type, poi.key, poi.npcKey);
}
protected activateAction(action: LocationPrimaryAction): void {
this.dispatch(action.type, action.poiKey ?? action.key, action.npcKey);
}
protected runtimeArtwork(artworkPath: string): string | undefined {
return RUNTIME_ARTWORK[artworkPath];
}
/** True while this control's own interaction is in flight. */
protected isPending(key: string | undefined): boolean {
return key !== undefined && this.store.interactionPending() === key;
}
protected get busy(): boolean {
return this.store.interactionPending() !== null;
}
/**
* Routes a hotspot or action by its interaction type. Navigation types leave
* for the screen that owns them; every other type asks the server what
* happened. Unimplemented types are ignored rather than faked.
*/
private dispatch(
type: LocationInteractionType,
interactionKey: string,
npcKey?: string,
): void {
switch (type) {
case 'HUNT':
void this.router.navigate(['/hunt']);
return;
case 'MAP':
case 'TRAVEL':
void this.router.navigate(['/world']);
return;
case 'INVESTIGATE':
case 'SEARCH':
case 'NPC':
// A hotspot that names an NPC opens that person's screen; one that
// does not is scenery with a line of authored text behind it, and
// still goes through the interaction endpoint.
if (npcKey) {
void this.router.navigate(['/npc', npcKey]);
return;
}
void this.store.runInteraction(interactionKey);
return;
case 'SHOP':
if (npcKey) {
void this.router.navigate(['/npc', npcKey]);
}
return;
default:
return;
}
}
}