Release Healer Man 0.1.6

This commit is contained in:
phenom committed 2026-08-20 14:05:50 -04:00
1 parent 55cfd43d66
commit b12fb84859
96 files changed
+10874 -609

No files matched your search

+78 -1
View File
@@ -65,6 +65,7 @@ import { partyCombatRangeBand } from "../game/partyRuntime";
import { useWailingEncounterStore } from "../game/wailingCavernsEncounter";
import {
clearPartyRuntimeAttackCorridor,
getPartyRuntimePosition,
registerPartyRuntimePosition,
updatePartyRuntimeAttackCorridor,
updatePartyRuntimePosition,
@@ -75,6 +76,8 @@ import {
import { usePartyStore } from "../game/partyStore";
import { useGameStore } from "../game/store";
import { useManastormStore } from "../game/manastormStore";
import { registerCombatSpatialProvider } from "../game/combatSpatial";
import { PLAYER_AGGRO_ID } from "../game/aggro";
const PARTY_MOVE_SPEED = 5.25;
const PARTY_CATCH_UP_SPEED = 7.5;
@@ -266,12 +269,14 @@ function initialMemberPosition(index: number): MutablePartyWorldPosition {
function PartyActors({
active,
simulate,
navigationGraph,
navigationFailed,
objectives,
stackAtEntrance,
}: {
readonly active: boolean;
readonly simulate: boolean;
readonly navigationGraph: PartyNavigationGraph | null;
readonly navigationFailed: boolean;
readonly objectives: readonly DungeonBossObjective[];
@@ -442,6 +447,56 @@ function PartyActors({
const directFollowMovingRef = useRef(new Map<string, boolean>());
const combatAnchorsRef = useRef(new Map<string, PartyCombatAnchor>());
const memberIdKey = members.map((member) => member.id).join("|");
useEffect(() => registerCombatSpatialProvider({
actor: (actorId) => {
const gameState = useGameStore.getState();
if (actorId === PLAYER_AGGRO_ID) {
return { id: actorId, position: gameState.playerPosition, yaw: gameState.cameraYaw };
}
const memberIndex = usePartyStore.getState().members.findIndex((member) => member.id === actorId);
const memberPosition = positionsRef.current.get(actorId);
if (memberPosition) {
return {
id: actorId,
position: memberPosition,
yaw: actorRefs.current[memberIndex]?.rotation.y ?? gameState.cameraYaw,
};
}
const summon = useCombatStore.getState().summons.find((candidate) => candidate.id === actorId);
if (summon) return { id: actorId, position: summon.position, yaw: gameState.cameraYaw };
const mobPosition = getMobPosition(actorId);
if (!mobPosition) return null;
const mob = useCombatStore.getState().mobs[actorId];
const targetPosition = mob?.targetActorId === PLAYER_AGGRO_ID
? gameState.playerPosition
: mob?.targetActorId
? positionsRef.current.get(mob.targetActorId)
?? useCombatStore.getState().summons.find((candidate) => candidate.id === mob.targetActorId)?.position
: null;
const yaw = targetPosition
? Math.atan2(targetPosition[0] - mobPosition[0], targetPosition[2] - mobPosition[2])
: 0;
return { id: actorId, position: mobPosition, yaw };
},
hasLineOfSight: hasStaticLineOfSight,
projectGroundPoint: (requested, maximumDistance, requestedOrigin) => {
const origin = requestedOrigin ?? useGameStore.getState().playerPosition;
const dx = requested[0] - origin[0];
const dz = requested[2] - origin[2];
const planar = Math.hypot(dx, dz);
const scale = planar > maximumDistance && planar > 0 ? maximumDistance / planar : 1;
const x = origin[0] + dx * scale;
const z = origin[2] + dz * scale;
const ray = new rapier.Ray({ x, y: requested[1] + 8, z }, { x: 0, y: -1, z: 0 });
const hit = world.castRay(
ray,
20,
true,
rapier.QueryFilterFlags.EXCLUDE_SENSORS | rapier.QueryFilterFlags.EXCLUDE_DYNAMIC,
);
return hit ? [x, ray.origin.y - hit.timeOfImpact, z] : null;
},
}), [hasStaticLineOfSight, memberIdKey, rapier, world]);
const teleportPartyToPlayer = useCallback((player: PartyWorldPosition, now: number): void => {
const party = usePartyStore.getState();
party.members.forEach((member, index) => {
@@ -606,6 +661,21 @@ function PartyActors({
if (!active) return;
const party = usePartyStore.getState();
if (!party.members.length) return;
if (!simulate) {
party.members.forEach((member, index) => {
const synchronized = getPartyRuntimePosition(member.id);
if (!synchronized) return;
const position = positionsRef.current.get(member.id) ?? initialMemberPosition(index);
position[0] = synchronized[0];
position[1] = synchronized[1];
position[2] = synchronized[2];
positionsRef.current.set(member.id, position);
actorRefs.current[index]?.position.set(position[0], presentationY(position), position[2]);
const movingSignal = movingSignalsRef.current.get(member.id);
if (movingSignal) movingSignal.current = false;
});
return;
}
const game = useGameStore.getState();
const playerNavigation = game.playerNavigationPosition;
appendPartyBreadcrumb(playerTrailRef.current, playerNavigation);
@@ -1320,7 +1390,13 @@ function PartyActors({
);
}
export function PartyPopulation({ active = true }: { readonly active?: boolean }) {
export function PartyPopulation({
active = true,
simulate = true,
}: {
readonly active?: boolean;
readonly simulate?: boolean;
}) {
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
const gameMode = useGameStore((state) => state.gameMode);
const sessionRevision = useGameStore((state) => state.sessionRevision);
@@ -1383,6 +1459,7 @@ export function PartyPopulation({ active = true }: { readonly active?: boolean }
<PartyActors
key={activeDungeonId}
active={active}
simulate={simulate}
navigationGraph={navigationGraph}
navigationFailed={navigationFailed || !assets.navigation}
objectives={objectives}