import { GameState } from '../types'; import { getFacingTowardPlayer, getMonsterGroupAnchorX, PLAYER_BASE_X_METERS } from './hostileNpcs'; function roundMeters(value: number) { return Number(value.toFixed(2)); } function lerp(start: number, end: number, progress: number) { return roundMeters(start + ((end - start) * progress)); } export function hasEncounterEntity(state: Pick) { return state.sceneHostileNpcs.length > 0 || Boolean(state.currentEncounter); } export function buildEncounterEntryState( finalState: GameState, entryX: number, ): GameState { if (finalState.sceneHostileNpcs.length > 0) { const anchorX = getMonsterGroupAnchorX(finalState.sceneHostileNpcs); return { ...finalState, sceneHostileNpcs: finalState.sceneHostileNpcs.map(monster => { const offset = monster.xMeters - anchorX; const xMeters = roundMeters(entryX + offset); return { ...monster, xMeters, animation: 'move' as const, facing: getFacingTowardPlayer(xMeters, PLAYER_BASE_X_METERS), }; }), currentEncounter: null, }; } if (finalState.currentEncounter) { return { ...finalState, currentEncounter: { ...finalState.currentEncounter, xMeters: entryX, }, sceneHostileNpcs: [], }; } return finalState; } export function buildEncounterTransitionState( finalState: GameState, sourceState: Pick, ): GameState { if (finalState.sceneHostileNpcs.length > 0) { const sourceById = new Map(sourceState.sceneHostileNpcs.map(monster => [monster.id, monster])); return { ...finalState, sceneHostileNpcs: finalState.sceneHostileNpcs.map(monster => { const sourceMonster = sourceById.get(monster.id); const xMeters = sourceMonster?.xMeters ?? monster.xMeters; return { ...monster, xMeters, animation: 'move' as const, facing: getFacingTowardPlayer(xMeters, PLAYER_BASE_X_METERS), }; }), currentEncounter: null, }; } if (finalState.currentEncounter) { return { ...finalState, currentEncounter: { ...finalState.currentEncounter, xMeters: sourceState.currentEncounter?.xMeters ?? finalState.currentEncounter.xMeters, }, sceneHostileNpcs: [], }; } return finalState; } export function interpolateEncounterTransitionState( startState: Pick, finalState: GameState, progress: number, ): GameState { if (finalState.sceneHostileNpcs.length > 0) { const startById = new Map(startState.sceneHostileNpcs.map(monster => [monster.id, monster])); return { ...finalState, sceneHostileNpcs: finalState.sceneHostileNpcs.map(monster => { const startMonster = startById.get(monster.id); const xMeters = lerp(startMonster?.xMeters ?? monster.xMeters, monster.xMeters, progress); return { ...monster, xMeters, animation: progress < 1 ? ('move' as const) : monster.animation, facing: getFacingTowardPlayer(xMeters, PLAYER_BASE_X_METERS), }; }), currentEncounter: null, }; } if (finalState.currentEncounter) { const startX = startState.currentEncounter?.xMeters ?? finalState.currentEncounter.xMeters ?? 0; const endX = finalState.currentEncounter.xMeters ?? startX; return { ...finalState, currentEncounter: { ...finalState.currentEncounter, xMeters: lerp(startX, endX, progress), }, sceneHostileNpcs: [], }; } return finalState; }