126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
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<GameState, 'sceneHostileNpcs' | 'currentEncounter'>) {
|
|
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, 'sceneHostileNpcs' | 'currentEncounter'>,
|
|
): 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<GameState, 'sceneHostileNpcs' | 'currentEncounter'>,
|
|
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;
|
|
}
|