Release Healer Man 0.1.35

This commit is contained in:
phenom committed 2026-09-12 16:02:15 -04:00
1 parent 46c272f889
commit 40d9f346d8
13 files changed
+218 -118

No files matched your search

+7 -11
View File
@@ -12,7 +12,6 @@ import {
partyNaturalFollowPosition,
partyPersonalizedCombatMovementGoal,
partyPresentationY,
partySeparationGoal,
partyShouldRecallForStuck,
partyShouldMoveToComfortTarget,
PARTY_STUCK_RECALL_DISTANCE,
@@ -102,7 +101,7 @@ describe("party world movement", () => {
}
});
it("uses a stable combat bearing and separates overlapping allies", () => {
it("uses a stable combat bearing without forcing in-range actors to reposition", () => {
const out: [number, number, number] = [0, 0, 0];
expect(partyPersonalizedCombatMovementGoal(
[20, 0, 0],
@@ -110,23 +109,20 @@ describe("party world movement", () => {
true,
{ minimum: 10, preferred: 14, maximum: 18 },
Math.PI / 2,
false,
out,
)).toBe("approach");
expect(out[0]).toBeCloseTo(14);
expect(out[1]).toBe(0);
expect(out[2]).toBeCloseTo(0);
const separated: [number, number, number] = [0, 0, 0];
expect(partySeparationGoal(
expect(partyPersonalizedCombatMovementGoal(
[1, 0, 1],
[2, 0, 2],
[[1, 0, 1]],
[0, 0, 0],
true,
{ minimum: 0, preferred: 2.9, maximum: 3.15 },
Math.PI / 2,
separated,
)).toBe(true);
expect(separated[0]).toBeGreaterThan(2);
expect(separated[2]).toBeCloseTo(2);
out,
)).toBe("hold");
});
it("recalls only distant allies that should be moving and stopped making progress", () => {
+3 -42
View File
@@ -19,7 +19,7 @@ export interface PartyBreadcrumbCursor {
breadcrumbId: number | null;
}
export type PartyCombatMovementIntent = "hold" | "approach" | "retreat" | "reposition";
export type PartyCombatMovementIntent = "hold" | "approach" | "retreat";
export interface PartyMovementProfile {
readonly followDistanceJitter: number;
@@ -427,44 +427,6 @@ export function personalizedPartyCombatRangeBand(
};
}
/** Adds a bounded local repulsion so allies do not occupy the same point. */
export function partySeparationGoal(
current: PartyWorldPosition,
desired: PartyWorldPosition,
peers: readonly PartyWorldPosition[],
fallbackAngle: number,
out: MutablePartyWorldPosition,
separationDistance = 0.85,
maximumCorrection = 0.35,
): boolean {
let offsetX = 0;
let offsetZ = 0;
const safeSeparation = Math.max(EPSILON, separationDistance);
for (const peer of peers) {
const dx = current[0] - peer[0];
const dz = current[2] - peer[2];
const distance = Math.hypot(dx, dz);
if (!Number.isFinite(distance) || distance >= safeSeparation) continue;
const weight = (safeSeparation - distance) / safeSeparation;
if (distance <= EPSILON) {
offsetX += Math.sin(fallbackAngle) * weight;
offsetZ += Math.cos(fallbackAngle) * weight;
} else {
offsetX += dx / distance * weight;
offsetZ += dz / distance * weight;
}
}
const length = Math.hypot(offsetX, offsetZ);
out[0] = desired[0];
out[1] = desired[1];
out[2] = desired[2];
if (length <= EPSILON) return false;
const correction = Math.min(Math.max(0, maximumCorrection), length);
out[0] += offsetX / length * correction;
out[2] += offsetZ / length * correction;
return true;
}
/**
* Chooses an individual combat destination. Melee only closes distance;
* ranged attackers also back out when a target collapses their preferred band.
@@ -524,7 +486,6 @@ export function partyPersonalizedCombatMovementGoal(
targetVisible: boolean,
rangeBand: PartyCombatRangeLike,
preferredBearing: number,
forceReposition: boolean,
out: MutablePartyWorldPosition,
): PartyCombatMovementIntent {
const intent = partyCombatMovementGoal(
@@ -538,10 +499,10 @@ export function partyPersonalizedCombatMovementGoal(
Math.sin(preferredBearing),
Math.cos(preferredBearing),
);
if (!targetVisible || (intent === "hold" && !forceReposition)) return intent;
if (!targetVisible || intent === "hold") return intent;
out[0] = target[0] + Math.sin(preferredBearing) * rangeBand.preferred;
out[1] = target[1];
out[2] = target[2] + Math.cos(preferredBearing) * rangeBand.preferred;
return intent === "hold" ? "reposition" : intent;
return intent;
}
+4 -3
View File
@@ -19,6 +19,7 @@ import { derivePartySpawnSlots } from "./partySpawnSlots";
import { collisionTrimeshes, prepareCollision } from "../scene/productionDungeonCollision";
import { createPartyDirectSegmentValidator, createPartyRouteSegmentValidator } from "../scene/partyTraversal";
import { configurePlayerCharacterController, PLAYER_CHARACTER_CONTROLLER_OFFSET } from "./playerMovement";
import { ACTOR_COLLISION_GROUPS, ACTOR_TERRAIN_QUERY_GROUPS } from "../scene/actorCollision";
const STOCKADES_NAVIGATION = "public/assets/game/manastorm/34-stormwindjail/navigation.glb";
const TANK_RANGE = Object.freeze({ minimum: 0, preferred: 2.9, maximum: 3.15 });
@@ -302,7 +303,7 @@ describe("Stormwind Stockade party navigation", () => {
const body = world.createRigidBody(RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(
player[0], player[1] + 0.84, player[2],
));
world.createCollider(RAPIER.ColliderDesc.capsule(0.48, 0.36), body);
world.createCollider(RAPIER.ColliderDesc.capsule(0.48, 0.36).setCollisionGroups(ACTOR_COLLISION_GROUPS), body);
world.step();
expect(createPartyDirectSegmentValidator(RAPIER, world)(point(0.4), point(0.6))).toBe(true);
expect(createPartyRouteSegmentValidator(RAPIER, world)(point(0.4), point(0.6))).toBe(true);
@@ -321,7 +322,7 @@ describe("Stormwind Stockade party navigation", () => {
const body = world.createRigidBody(RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(
ramp.start[0] + dx * startT, startY + centerHeight + 0.02, ramp.start[2] + dz * startT,
));
const collider = world.createCollider(RAPIER.ColliderDesc.capsule(0.48, 0.36), body);
const collider = world.createCollider(RAPIER.ColliderDesc.capsule(0.48, 0.36).setCollisionGroups(ACTOR_COLLISION_GROUPS), body);
world.step();
const controller = configurePlayerCharacterController(world.createCharacterController(PLAYER_CHARACTER_CONTROLLER_OFFSET));
const progress = () => {
@@ -338,7 +339,7 @@ describe("Stormwind Stockade party navigation", () => {
x: dx / length * direction * 4.8 / 60,
y: -0.5 / 60,
z: dz / length * direction * 4.8 / 60,
}, RAPIER.QueryFilterFlags.EXCLUDE_SENSORS | RAPIER.QueryFilterFlags.EXCLUDE_DYNAMIC);
}, RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, ACTOR_TERRAIN_QUERY_GROUPS);
const position = body.translation();
const move = controller.computedMovement();
body.setNextKinematicTranslation({ x: position.x + move.x, y: position.y + move.y, z: position.z + move.z });