forked from phenom/healer-man
Release Healer Man 0.1.35
This commit is contained in:
1 parent
46c272f889
commit
40d9f346d8
13 files changed
+218
-118
No files matched your search
@@ -0,0 +1,114 @@
|
||||
import RAPIER from "@dimforge/rapier3d-compat";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { configurePlayerCharacterController, PLAYER_CHARACTER_CONTROLLER_OFFSET } from "../game/playerMovement";
|
||||
import { isWalkableGroundHit } from "../game/playerJump";
|
||||
import { ACTOR_COLLISION_GROUPS, ACTOR_TERRAIN_QUERY_GROUPS } from "./actorCollision";
|
||||
import { DUNGEON_PHYSICAL_COLLISION_GROUPS, DUNGEON_WALL_COLLISION_GROUPS } from "./dungeonLineOfSight";
|
||||
import { createPartyDirectSegmentValidator, createPartyRouteSegmentValidator } from "./partyTraversal";
|
||||
|
||||
const BODY_TYPES = ["fixed", "kinematic", "dynamic"] as const;
|
||||
type BodyType = typeof BODY_TYPES[number];
|
||||
|
||||
beforeAll(async () => {
|
||||
await RAPIER.init();
|
||||
});
|
||||
|
||||
function actor(world: RAPIER.World, bodyType: BodyType, z = 0, groups = ACTOR_COLLISION_GROUPS) {
|
||||
const description = bodyType === "fixed"
|
||||
? RAPIER.RigidBodyDesc.fixed()
|
||||
: bodyType === "kinematic"
|
||||
? RAPIER.RigidBodyDesc.kinematicPositionBased()
|
||||
: RAPIER.RigidBodyDesc.dynamic();
|
||||
const body = world.createRigidBody(description.setTranslation(0, 0.84, z));
|
||||
const collider = world.createCollider(
|
||||
RAPIER.ColliderDesc.capsule(0.48, 0.36)
|
||||
.setCollisionGroups(groups)
|
||||
.setActiveCollisionTypes(RAPIER.ActiveCollisionTypes.ALL),
|
||||
body,
|
||||
);
|
||||
return { body, collider };
|
||||
}
|
||||
|
||||
function terrain(world: RAPIER.World, wall = false) {
|
||||
const floor = world.createCollider(RAPIER.ColliderDesc.cuboid(5, 0.1, 8)
|
||||
.setTranslation(0, -0.1, 0).setCollisionGroups(DUNGEON_PHYSICAL_COLLISION_GROUPS));
|
||||
if (wall) world.createCollider(RAPIER.ColliderDesc.cuboid(5, 2, 0.1)
|
||||
.setTranslation(0, 2, 2.5).setCollisionGroups(DUNGEON_WALL_COLLISION_GROUPS));
|
||||
return floor;
|
||||
}
|
||||
|
||||
describe("non-blocking combat actors", () => {
|
||||
it.each(BODY_TYPES)("has no physical contacts with overlapping %s actors", (bodyType) => {
|
||||
const world = new RAPIER.World({ x: 0, y: 0, z: 0 });
|
||||
const player = actor(world, "dynamic");
|
||||
// Party members, mobs, bosses, and remote players use the same actor policy.
|
||||
const others = ["party", "mob", "boss", "remote-player"].map(() => actor(world, bodyType));
|
||||
world.step();
|
||||
for (const other of others) {
|
||||
world.contactPair(player.collider, other.collider, () => {
|
||||
throw new Error("Actors must not produce physical contacts");
|
||||
});
|
||||
expect(other.body.translation().z).toBeCloseTo(0);
|
||||
}
|
||||
expect(player.body.translation().z).toBeCloseTo(0);
|
||||
world.free();
|
||||
});
|
||||
|
||||
it("detects contacts when an actor mistakenly uses the terrain layer", () => {
|
||||
const world = new RAPIER.World({ x: 0, y: 0, z: 0 });
|
||||
const player = actor(world, "dynamic");
|
||||
const obstacle = actor(world, "fixed", 0.4, DUNGEON_PHYSICAL_COLLISION_GROUPS);
|
||||
world.step();
|
||||
let contacts = 0;
|
||||
world.contactPair(player.collider, obstacle.collider, (manifold) => { contacts += manifold.numContacts(); });
|
||||
expect(contacts).toBeGreaterThan(0);
|
||||
world.free();
|
||||
});
|
||||
|
||||
it.each(BODY_TYPES)("walks through %s actors but still stops at a dungeon wall", (bodyType) => {
|
||||
for (const hasWall of [false, true]) {
|
||||
const world = new RAPIER.World({ x: 0, y: 0, z: 0 });
|
||||
terrain(world, hasWall);
|
||||
actor(world, bodyType);
|
||||
actor(world, bodyType, -2); // Start overlapped: no depenetration or detour.
|
||||
const player = actor(world, "kinematic", -2);
|
||||
const controller = configurePlayerCharacterController(world.createCharacterController(PLAYER_CHARACTER_CONTROLLER_OFFSET));
|
||||
world.step();
|
||||
for (let frame = 0; frame < 90; frame += 1) {
|
||||
controller.computeColliderMovement(player.collider, { x: 0, y: -0.5 / 60, z: 4.8 / 60 },
|
||||
RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, ACTOR_TERRAIN_QUERY_GROUPS);
|
||||
const position = player.body.translation();
|
||||
const movement = controller.computedMovement();
|
||||
player.body.setNextKinematicTranslation({ x: position.x + movement.x, y: position.y + movement.y, z: position.z + movement.z });
|
||||
world.step();
|
||||
}
|
||||
const position = player.body.translation();
|
||||
if (hasWall) {
|
||||
expect(position.z).toBeGreaterThan(1.8);
|
||||
expect(position.z).toBeLessThan(2.1);
|
||||
} else expect(position.z).toBeGreaterThan(5);
|
||||
expect(position.x).toBeCloseTo(0);
|
||||
expect(position.y).toBeCloseTo(0.84, 1);
|
||||
world.removeCharacterController(controller);
|
||||
world.free();
|
||||
}
|
||||
});
|
||||
|
||||
it.each(BODY_TYPES)("never treats a %s actor as ground, a route obstacle, or a camera wall", (bodyType) => {
|
||||
const world = new RAPIER.World({ x: 0, y: 0, z: 0 });
|
||||
const floor = terrain(world);
|
||||
actor(world, bodyType);
|
||||
world.step();
|
||||
const ground = world.castRayAndGetNormal(new RAPIER.Ray({ x: 0, y: 4, z: 0 }, { x: 0, y: -1, z: 0 }),
|
||||
5, true, RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, ACTOR_TERRAIN_QUERY_GROUPS);
|
||||
expect(ground?.collider.handle).toBe(floor.handle);
|
||||
expect(ground?.timeOfImpact).toBeCloseTo(4);
|
||||
expect(isWalkableGroundHit(ground, 5)).toBe(true);
|
||||
expect(createPartyDirectSegmentValidator(RAPIER, world)([0, 0, -1.5], [0, 0, 1.5])).toBe(true);
|
||||
expect(createPartyRouteSegmentValidator(RAPIER, world)([0, 0, -1.5], [0, 0, 1.5])).toBe(true);
|
||||
expect(world.castShape({ x: 0, y: 0.9, z: -1.5 }, { x: 0, y: 0, z: 0, w: 1 },
|
||||
{ x: 0, y: 0, z: 1 }, new RAPIER.Ball(0.24), 0.08, 3, false,
|
||||
RAPIER.QueryFilterFlags.EXCLUDE_SENSORS, ACTOR_TERRAIN_QUERY_GROUPS)).toBeNull();
|
||||
world.free();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user