import { findNearestPartyNavigationSurface, type PartyNavigationGraph } from "./partyNavigation"; import { emptyPartyRouteState, nextPartyRouteRecoveryWaypoint, nextPartyRouteWaypoint, partyRouteNeedsPlanning, planPartyRoute, type PartyRouteOptions, type PartyRouteState } from "./partyPathing"; import type { PartyWorldPosition } from "./partyRuntimeRegistry"; export const PARTY_REJOIN_DISTANCE = 3; /** Returning resurrected members target the player, not a distant boss or leader. */ export function partyRejoinMovement( id: string, current: PartyWorldPosition, player: PartyWorldPosition, nowMs: number, graph: PartyNavigationGraph | null, route: PartyRouteState, options: Pick, ) { const reachedPlayer = (point: PartyWorldPosition) => Math.hypot( point[0] - player[0], point[1] - player[1], point[2] - player[2], ) <= PARTY_REJOIN_DISTANCE && Boolean(options.directSegmentAllowed?.(point, player)); if (reachedPlayer(current)) return { rejoined: true, route: emptyPartyRouteState(), waypoint: null, blocked: false }; if (options.directSegmentAllowed?.(current, player)) { return { rejoined: false, route: emptyPartyRouteState(), waypoint: player, blocked: false }; } const destination = graph ? findNearestPartyNavigationSurface(graph, player, 5)?.position ?? player : player; const goalKey = `member:${id}|resurrection-rejoin`; if (partyRouteNeedsPlanning(route, goalKey, current, destination, nowMs, { replanIntervalMs: 350, blockedRetryMs: 800 })) { route = planPartyRoute(graph, goalKey, current, destination, nowMs, { ...options, maxOriginSnapDistance: 6, maxDestinationSnapDistance: 5, localFinishDistance: PARTY_REJOIN_DISTANCE, destinationReached: reachedPlayer, }); } const waypoint: [number, number, number] = [0, 0, 0]; const hasWaypoint = nextPartyRouteWaypoint(route, current, waypoint) || nextPartyRouteRecoveryWaypoint(route, current, waypoint, options.directSegmentAllowed ?? (() => false)); return { rejoined: false, route, waypoint: hasWaypoint ? waypoint : null, blocked: route.blocked && !hasWaypoint }; }