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> = { '/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; } } }