@rpgjs/physic 5.0.0-alpha.26 → 5.0.0-alpha.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/collision/resolver.d.ts +3 -0
- package/dist/collision/resolver.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index17.js +6 -0
- package/dist/index17.js.map +1 -1
- package/dist/index30.js +69 -9
- package/dist/index30.js.map +1 -1
- package/dist/index41.js.map +1 -1
- package/dist/index7.js +110 -1
- package/dist/index7.js.map +1 -1
- package/dist/movement/MovementManager.d.ts +40 -2
- package/dist/movement/MovementManager.d.ts.map +1 -1
- package/dist/movement/MovementStrategy.d.ts +35 -0
- package/dist/movement/MovementStrategy.d.ts.map +1 -1
- package/dist/movement/strategies/SeekAvoid.d.ts.map +1 -1
- package/dist/physics/Entity.d.ts +126 -0
- package/dist/physics/Entity.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -793,6 +793,59 @@ loop();
|
|
|
793
793
|
- Call `movement.update(dt)` manually when you need custom timing, or use `engine.stepWithMovements(dt)` to update movements and advance the simulation in one call.
|
|
794
794
|
- Use `movement.stopMovement(entity)` to completely stop an entity's movement, clearing all strategies and stopping velocity (useful when changing maps or teleporting).
|
|
795
795
|
|
|
796
|
+
#### Awaiting Movement Completion
|
|
797
|
+
|
|
798
|
+
The `add()` method returns a Promise that resolves when the movement completes (when `isFinished()` returns true). This allows you to chain movements or execute code after a movement finishes:
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
// Wait for a dash to complete
|
|
802
|
+
await movement.add(player, new Dash(8, { x: 1, y: 0 }, 0.2));
|
|
803
|
+
console.log('Dash completed!');
|
|
804
|
+
|
|
805
|
+
// Chain multiple movements
|
|
806
|
+
await movement.add(player, new Dash(8, { x: 1, y: 0 }, 0.2));
|
|
807
|
+
await movement.add(player, new Dash(8, { x: 0, y: 1 }, 0.2));
|
|
808
|
+
console.log('Both dashes completed!');
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
#### Movement Callbacks
|
|
812
|
+
|
|
813
|
+
You can pass `MovementOptions` to the `add()` method for lifecycle callbacks:
|
|
814
|
+
|
|
815
|
+
```typescript
|
|
816
|
+
import { MovementOptions, Knockback } from '@rpgjs/physic';
|
|
817
|
+
|
|
818
|
+
// Apply knockback with callbacks
|
|
819
|
+
await movement.add(player, new Knockback({ x: -1, y: 0 }, 5, 0.3), {
|
|
820
|
+
onStart: () => {
|
|
821
|
+
// Called when the movement starts (first update)
|
|
822
|
+
player.directionFixed = true;
|
|
823
|
+
player.animationFixed = true;
|
|
824
|
+
console.log('Knockback started!');
|
|
825
|
+
},
|
|
826
|
+
onComplete: () => {
|
|
827
|
+
// Called when the movement completes
|
|
828
|
+
player.directionFixed = false;
|
|
829
|
+
player.animationFixed = false;
|
|
830
|
+
console.log('Knockback finished!');
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
The `MovementOptions` interface:
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
interface MovementOptions {
|
|
839
|
+
/** Callback executed when the movement starts (first update call) */
|
|
840
|
+
onStart?: () => void;
|
|
841
|
+
|
|
842
|
+
/** Callback executed when the movement completes (isFinished returns true) */
|
|
843
|
+
onComplete?: () => void;
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Note:** If the strategy doesn't implement `isFinished()`, the Promise resolves immediately after the strategy is added, and `onComplete` will not be called automatically.
|
|
848
|
+
|
|
796
849
|
### Static Obstacles
|
|
797
850
|
|
|
798
851
|
Create immovable obstacles (walls, trees, decorations) by setting `mass` to `0` or `Infinity`.
|
|
@@ -34,6 +34,9 @@ export declare class CollisionResolver {
|
|
|
34
34
|
* Resolves a collision
|
|
35
35
|
*
|
|
36
36
|
* Separates entities and applies collision response.
|
|
37
|
+
* First checks if the collision should be resolved using resolution filters.
|
|
38
|
+
* If any entity has a resolution filter that returns false, the collision
|
|
39
|
+
* is skipped (entities pass through) but events are still fired.
|
|
37
40
|
*
|
|
38
41
|
* @param collision - Collision information to resolve
|
|
39
42
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/collision/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA2B;IAEzC;;;;OAIG;gBACS,MAAM,GAAE,cAAmB;IAQvC
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/collision/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAA2B;IAEzC;;;;OAIG;gBACS,MAAM,GAAE,cAAmB;IAQvC;;;;;;;;;OASG;IACI,OAAO,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI;IAqB9C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;IAiCxB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAyEzB;;;;OAIG;IACI,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,IAAI;CAKrD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { Matrix2 } from './core/math/Matrix2';
|
|
|
8
8
|
export { AABB } from './core/math/AABB';
|
|
9
9
|
export * from './core/math/utils';
|
|
10
10
|
export * from './core/types';
|
|
11
|
-
export { Entity, type EntityConfig, type CardinalDirection, type EntityCollisionEvent, type EntityCollisionHandler, type EntityPositionSyncEvent, type EntityPositionSyncHandler, type EntityDirectionSyncEvent, type EntityDirectionSyncHandler, } from './physics/Entity';
|
|
11
|
+
export { Entity, type EntityConfig, type CardinalDirection, type CollisionFilter, type ResolutionFilter, type EntityCollisionEvent, type EntityCollisionHandler, type EntityPositionSyncEvent, type EntityPositionSyncHandler, type EntityDirectionSyncEvent, type EntityDirectionSyncHandler, } from './physics/Entity';
|
|
12
12
|
export { Integrator, IntegrationMethod } from './physics/integrator';
|
|
13
13
|
export * from './physics/forces';
|
|
14
14
|
export * from './physics/constraints';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAG7B,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAGtC,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACzG,OAAO,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGpE,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACjF,cAAc,oBAAoB,CAAC;AAGnC,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAG3B,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAG1E,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,GACrB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,wBAAwB,EACxB,KAAK,WAAW,GACjB,MAAM,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAG7B,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACrE,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AAGtC,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACzG,OAAO,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGpE,OAAO,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACjF,cAAc,oBAAoB,CAAC;AAGnC,OAAO,EACL,aAAa,EACb,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC;AAG3B,cAAc,YAAY,CAAC;AAG3B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAG1E,OAAO,EACL,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,GACrB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,wBAAwB,EACxB,KAAK,WAAW,GACjB,MAAM,oCAAoC,CAAC"}
|
package/dist/index17.js
CHANGED
|
@@ -15,6 +15,9 @@ class CollisionResolver {
|
|
|
15
15
|
* Resolves a collision
|
|
16
16
|
*
|
|
17
17
|
* Separates entities and applies collision response.
|
|
18
|
+
* First checks if the collision should be resolved using resolution filters.
|
|
19
|
+
* If any entity has a resolution filter that returns false, the collision
|
|
20
|
+
* is skipped (entities pass through) but events are still fired.
|
|
18
21
|
*
|
|
19
22
|
* @param collision - Collision information to resolve
|
|
20
23
|
*/
|
|
@@ -23,6 +26,9 @@ class CollisionResolver {
|
|
|
23
26
|
if (depth < this.config.minPenetrationDepth) {
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
29
|
+
if (!entityA.shouldResolveCollisionWith(entityB)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
26
32
|
this.separateEntities(entityA, entityB, normal, depth);
|
|
27
33
|
this.resolveVelocities(entityA, entityB, normal);
|
|
28
34
|
}
|
package/dist/index17.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index17.js","sources":["../src/collision/resolver.ts"],"sourcesContent":["import { Vector2 } from '../core/math/Vector2';\nimport { Entity } from '../physics/Entity';\nimport { CollisionInfo } from './Collider';\n\n/**\n * Collision resolver configuration\n */\nexport interface ResolverConfig {\n /** Position correction factor (0-1, higher = more correction) */\n positionCorrectionFactor?: number;\n /** Minimum penetration depth to resolve */\n minPenetrationDepth?: number;\n /** Maximum position correction per step */\n maxPositionCorrection?: number;\n}\n\n/**\n * Collision resolver\n * \n * Resolves collisions by separating entities and applying impulses.\n * Uses impulse-based resolution for stable, deterministic physics.\n * \n * @example\n * ```typescript\n * const resolver = new CollisionResolver();\n * resolver.resolve(collision);\n * ```\n */\nexport class CollisionResolver {\n private config: Required<ResolverConfig>;\n\n /**\n * Creates a new collision resolver\n * \n * @param config - Resolver configuration\n */\n constructor(config: ResolverConfig = {}) {\n this.config = {\n positionCorrectionFactor: config.positionCorrectionFactor ?? 0.8,\n minPenetrationDepth: config.minPenetrationDepth ?? 0.0001,\n maxPositionCorrection: config.maxPositionCorrection ?? 5,\n };\n }\n\n /**\n * Resolves a collision\n * \n * Separates entities and applies collision response.\n * \n * @param collision - Collision information to resolve\n */\n public resolve(collision: CollisionInfo): void {\n const { entityA, entityB, normal, depth } = collision;\n\n // Skip if penetration is too small\n if (depth < this.config.minPenetrationDepth) {\n return;\n }\n\n // Position correction (separate entities)\n this.separateEntities(entityA, entityB, normal, depth);\n\n // Velocity resolution (collision response)\n this.resolveVelocities(entityA, entityB, normal);\n }\n\n /**\n * Separates two entities by moving them apart\n * \n * This method applies position corrections to resolve penetration between\n * colliding entities. After applying corrections, it notifies position change\n * handlers to ensure proper synchronization with game logic (e.g., updating\n * owner.x/y signals for network sync).\n * \n * @param entityA - First entity\n * @param entityB - Second entity\n * @param normal - Separation normal (from A to B)\n * @param depth - Penetration depth\n */\n private separateEntities(\n entityA: Entity,\n entityB: Entity,\n normal: Vector2,\n depth: number\n ): void {\n const totalInvMass = entityA.invMass + entityB.invMass;\n if (totalInvMass === 0) {\n return; // Both static\n }\n\n // Calculate separation amount\n const correction = Math.min(\n depth * this.config.positionCorrectionFactor,\n this.config.maxPositionCorrection\n );\n\n // Distribute correction based on inverse mass\n const correctionA = normal.mul(-correction * (entityA.invMass / totalInvMass));\n const correctionB = normal.mul(correction * (entityB.invMass / totalInvMass));\n\n // Apply corrections and notify position change handlers\n // This ensures that owner.x/y signals are updated after collision resolution\n if (!entityA.isStatic()) {\n entityA.position.addInPlace(correctionA);\n entityA.notifyPositionChange();\n }\n if (!entityB.isStatic()) {\n entityB.position.addInPlace(correctionB);\n entityB.notifyPositionChange();\n }\n }\n\n /**\n * Resolves velocities using impulse-based collision response\n * \n * @param entityA - First entity\n * @param entityB - Second entity\n * @param normal - Collision normal (from A to B)\n */\n private resolveVelocities(\n entityA: Entity,\n entityB: Entity,\n normal: Vector2\n ): void {\n // Relative velocity\n const relativeVelocity = entityB.velocity.sub(entityA.velocity);\n const velocityAlongNormal = relativeVelocity.dot(normal);\n\n // Don't resolve if velocities are separating\n if (velocityAlongNormal > 0) {\n return;\n }\n\n // Calculate restitution (bounciness)\n const restitution = Math.min(entityA.restitution, entityB.restitution);\n\n // Calculate impulse scalar\n const totalInvMass = entityA.invMass + entityB.invMass;\n if (totalInvMass === 0) {\n return; // Both static\n }\n\n // Impulse: j = -(1 + e) * v_rel · n / (1/mA + 1/mB)\n const impulseScalar = -(1 + restitution) * velocityAlongNormal / totalInvMass;\n const impulse = normal.mul(impulseScalar);\n\n // Apply impulse\n if (!entityA.isStatic()) {\n entityA.velocity.addInPlace(impulse.mul(-entityA.invMass));\n entityA.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because collision response\n // should not change the entity's intended direction (visual orientation).\n // Direction changes should only come from intentional movement (player input, AI).\n }\n if (!entityB.isStatic()) {\n entityB.velocity.addInPlace(impulse.mul(entityB.invMass));\n entityB.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because collision response\n // should not change the entity's intended direction (visual orientation).\n // Direction changes should only come from intentional movement (player input, AI).\n }\n\n // Friction (simplified, using velocity tangent to collision)\n const tangent = relativeVelocity.sub(normal.mul(velocityAlongNormal));\n const tangentLength = tangent.length();\n\n if (tangentLength > 1e-5) {\n const friction = Math.sqrt(entityA.friction * entityB.friction);\n const tangentNormalized = tangent.normalize();\n const frictionImpulse = tangentNormalized.mul(-tangentLength * friction / totalInvMass);\n\n // Clamp friction impulse to not exceed relative velocity\n const maxFriction = Math.abs(impulseScalar * friction);\n if (frictionImpulse.length() > maxFriction) {\n frictionImpulse.normalizeInPlace().mulInPlace(maxFriction);\n }\n\n if (!entityA.isStatic()) {\n entityA.velocity.addInPlace(frictionImpulse.mul(-entityA.invMass));\n entityA.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because friction adjustments\n // should not change the entity's intended direction (visual orientation).\n }\n if (!entityB.isStatic()) {\n entityB.velocity.addInPlace(frictionImpulse.mul(entityB.invMass));\n entityB.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because friction adjustments\n // should not change the entity's intended direction (visual orientation).\n }\n }\n }\n\n /**\n * Resolves multiple collisions\n * \n * @param collisions - Array of collisions to resolve\n */\n public resolveAll(collisions: CollisionInfo[]): void {\n for (const collision of collisions) {\n this.resolve(collision);\n }\n }\n}\n\n"],"names":[],"mappings":"AA4BO,MAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,YAAY,SAAyB,IAAI;AACvC,SAAK,SAAS;AAAA,MACZ,0BAA0B,OAAO,4BAA4B;AAAA,MAC7D,qBAAqB,OAAO,uBAAuB;AAAA,MACnD,uBAAuB,OAAO,yBAAyB;AAAA,IAAA;AAAA,EAE3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"index17.js","sources":["../src/collision/resolver.ts"],"sourcesContent":["import { Vector2 } from '../core/math/Vector2';\nimport { Entity } from '../physics/Entity';\nimport { CollisionInfo } from './Collider';\n\n/**\n * Collision resolver configuration\n */\nexport interface ResolverConfig {\n /** Position correction factor (0-1, higher = more correction) */\n positionCorrectionFactor?: number;\n /** Minimum penetration depth to resolve */\n minPenetrationDepth?: number;\n /** Maximum position correction per step */\n maxPositionCorrection?: number;\n}\n\n/**\n * Collision resolver\n * \n * Resolves collisions by separating entities and applying impulses.\n * Uses impulse-based resolution for stable, deterministic physics.\n * \n * @example\n * ```typescript\n * const resolver = new CollisionResolver();\n * resolver.resolve(collision);\n * ```\n */\nexport class CollisionResolver {\n private config: Required<ResolverConfig>;\n\n /**\n * Creates a new collision resolver\n * \n * @param config - Resolver configuration\n */\n constructor(config: ResolverConfig = {}) {\n this.config = {\n positionCorrectionFactor: config.positionCorrectionFactor ?? 0.8,\n minPenetrationDepth: config.minPenetrationDepth ?? 0.0001,\n maxPositionCorrection: config.maxPositionCorrection ?? 5,\n };\n }\n\n /**\n * Resolves a collision\n * \n * Separates entities and applies collision response.\n * First checks if the collision should be resolved using resolution filters.\n * If any entity has a resolution filter that returns false, the collision\n * is skipped (entities pass through) but events are still fired.\n * \n * @param collision - Collision information to resolve\n */\n public resolve(collision: CollisionInfo): void {\n const { entityA, entityB, normal, depth } = collision;\n\n // Skip if penetration is too small\n if (depth < this.config.minPenetrationDepth) {\n return;\n }\n\n // Check resolution filters - if either entity says no, skip resolution\n // This allows entities to pass through each other while still detecting collisions\n if (!entityA.shouldResolveCollisionWith(entityB)) {\n return;\n }\n\n // Position correction (separate entities)\n this.separateEntities(entityA, entityB, normal, depth);\n\n // Velocity resolution (collision response)\n this.resolveVelocities(entityA, entityB, normal);\n }\n\n /**\n * Separates two entities by moving them apart\n * \n * This method applies position corrections to resolve penetration between\n * colliding entities. After applying corrections, it notifies position change\n * handlers to ensure proper synchronization with game logic (e.g., updating\n * owner.x/y signals for network sync).\n * \n * @param entityA - First entity\n * @param entityB - Second entity\n * @param normal - Separation normal (from A to B)\n * @param depth - Penetration depth\n */\n private separateEntities(\n entityA: Entity,\n entityB: Entity,\n normal: Vector2,\n depth: number\n ): void {\n const totalInvMass = entityA.invMass + entityB.invMass;\n if (totalInvMass === 0) {\n return; // Both static\n }\n\n // Calculate separation amount\n const correction = Math.min(\n depth * this.config.positionCorrectionFactor,\n this.config.maxPositionCorrection\n );\n\n // Distribute correction based on inverse mass\n const correctionA = normal.mul(-correction * (entityA.invMass / totalInvMass));\n const correctionB = normal.mul(correction * (entityB.invMass / totalInvMass));\n\n // Apply corrections and notify position change handlers\n // This ensures that owner.x/y signals are updated after collision resolution\n if (!entityA.isStatic()) {\n entityA.position.addInPlace(correctionA);\n entityA.notifyPositionChange();\n }\n if (!entityB.isStatic()) {\n entityB.position.addInPlace(correctionB);\n entityB.notifyPositionChange();\n }\n }\n\n /**\n * Resolves velocities using impulse-based collision response\n * \n * @param entityA - First entity\n * @param entityB - Second entity\n * @param normal - Collision normal (from A to B)\n */\n private resolveVelocities(\n entityA: Entity,\n entityB: Entity,\n normal: Vector2\n ): void {\n // Relative velocity\n const relativeVelocity = entityB.velocity.sub(entityA.velocity);\n const velocityAlongNormal = relativeVelocity.dot(normal);\n\n // Don't resolve if velocities are separating\n if (velocityAlongNormal > 0) {\n return;\n }\n\n // Calculate restitution (bounciness)\n const restitution = Math.min(entityA.restitution, entityB.restitution);\n\n // Calculate impulse scalar\n const totalInvMass = entityA.invMass + entityB.invMass;\n if (totalInvMass === 0) {\n return; // Both static\n }\n\n // Impulse: j = -(1 + e) * v_rel · n / (1/mA + 1/mB)\n const impulseScalar = -(1 + restitution) * velocityAlongNormal / totalInvMass;\n const impulse = normal.mul(impulseScalar);\n\n // Apply impulse\n if (!entityA.isStatic()) {\n entityA.velocity.addInPlace(impulse.mul(-entityA.invMass));\n entityA.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because collision response\n // should not change the entity's intended direction (visual orientation).\n // Direction changes should only come from intentional movement (player input, AI).\n }\n if (!entityB.isStatic()) {\n entityB.velocity.addInPlace(impulse.mul(entityB.invMass));\n entityB.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because collision response\n // should not change the entity's intended direction (visual orientation).\n // Direction changes should only come from intentional movement (player input, AI).\n }\n\n // Friction (simplified, using velocity tangent to collision)\n const tangent = relativeVelocity.sub(normal.mul(velocityAlongNormal));\n const tangentLength = tangent.length();\n\n if (tangentLength > 1e-5) {\n const friction = Math.sqrt(entityA.friction * entityB.friction);\n const tangentNormalized = tangent.normalize();\n const frictionImpulse = tangentNormalized.mul(-tangentLength * friction / totalInvMass);\n\n // Clamp friction impulse to not exceed relative velocity\n const maxFriction = Math.abs(impulseScalar * friction);\n if (frictionImpulse.length() > maxFriction) {\n frictionImpulse.normalizeInPlace().mulInPlace(maxFriction);\n }\n\n if (!entityA.isStatic()) {\n entityA.velocity.addInPlace(frictionImpulse.mul(-entityA.invMass));\n entityA.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because friction adjustments\n // should not change the entity's intended direction (visual orientation).\n }\n if (!entityB.isStatic()) {\n entityB.velocity.addInPlace(frictionImpulse.mul(entityB.invMass));\n entityB.notifyMovementChange();\n // Note: We don't call notifyDirectionChange() here because friction adjustments\n // should not change the entity's intended direction (visual orientation).\n }\n }\n }\n\n /**\n * Resolves multiple collisions\n * \n * @param collisions - Array of collisions to resolve\n */\n public resolveAll(collisions: CollisionInfo[]): void {\n for (const collision of collisions) {\n this.resolve(collision);\n }\n }\n}\n\n"],"names":[],"mappings":"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"}
|
package/dist/index30.js
CHANGED
|
@@ -22,20 +22,64 @@ class MovementManager {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Adds a movement strategy to an entity.
|
|
25
|
+
*
|
|
26
|
+
* Returns a Promise that resolves when the movement completes (when `isFinished()` returns true).
|
|
27
|
+
* If the strategy doesn't implement `isFinished()`, the Promise resolves immediately after adding.
|
|
25
28
|
*
|
|
26
29
|
* @param target - Entity instance or entity UUID when a resolver is configured
|
|
27
30
|
* @param strategy - Strategy to execute
|
|
31
|
+
* @param options - Optional callbacks for movement lifecycle events
|
|
32
|
+
* @returns Promise that resolves when the movement completes
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* // Simple usage - fire and forget
|
|
37
|
+
* manager.add(player, new Dash(8, { x: 1, y: 0 }, 200));
|
|
38
|
+
*
|
|
39
|
+
* // Wait for completion
|
|
40
|
+
* await manager.add(player, new Dash(8, { x: 1, y: 0 }, 200));
|
|
41
|
+
* console.log('Dash finished!');
|
|
42
|
+
*
|
|
43
|
+
* // With callbacks
|
|
44
|
+
* await manager.add(player, new Knockback({ x: -1, y: 0 }, 5, 300), {
|
|
45
|
+
* onStart: () => {
|
|
46
|
+
* player.directionFixed = true;
|
|
47
|
+
* player.animationFixed = true;
|
|
48
|
+
* },
|
|
49
|
+
* onComplete: () => {
|
|
50
|
+
* player.directionFixed = false;
|
|
51
|
+
* player.animationFixed = false;
|
|
52
|
+
* }
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
28
55
|
*/
|
|
29
|
-
add(target, strategy) {
|
|
56
|
+
add(target, strategy, options) {
|
|
30
57
|
const body = this.resolveTarget(target);
|
|
31
58
|
const key = body.id;
|
|
32
59
|
if (!this.entries.has(key)) {
|
|
33
60
|
this.entries.set(key, { body, strategies: [] });
|
|
34
61
|
}
|
|
35
|
-
|
|
62
|
+
if (!strategy.isFinished) {
|
|
63
|
+
const entry = { strategy, started: false };
|
|
64
|
+
if (options) {
|
|
65
|
+
entry.options = options;
|
|
66
|
+
}
|
|
67
|
+
this.entries.get(key).strategies.push(entry);
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const entry = { strategy, resolve, started: false };
|
|
72
|
+
if (options) {
|
|
73
|
+
entry.options = options;
|
|
74
|
+
}
|
|
75
|
+
this.entries.get(key).strategies.push(entry);
|
|
76
|
+
});
|
|
36
77
|
}
|
|
37
78
|
/**
|
|
38
79
|
* Removes a specific strategy from an entity.
|
|
80
|
+
*
|
|
81
|
+
* Note: This will NOT trigger the onComplete callback or resolve the Promise.
|
|
82
|
+
* Use this when you want to cancel a movement without completion.
|
|
39
83
|
*
|
|
40
84
|
* @param target - Entity instance or identifier
|
|
41
85
|
* @param strategy - Strategy instance to remove
|
|
@@ -47,7 +91,7 @@ class MovementManager {
|
|
|
47
91
|
if (!entry) {
|
|
48
92
|
return false;
|
|
49
93
|
}
|
|
50
|
-
const index = entry.strategies.
|
|
94
|
+
const index = entry.strategies.findIndex((e) => e.strategy === strategy);
|
|
51
95
|
if (index === -1) {
|
|
52
96
|
return false;
|
|
53
97
|
}
|
|
@@ -126,13 +170,21 @@ class MovementManager {
|
|
|
126
170
|
getStrategies(target) {
|
|
127
171
|
const body = this.resolveTarget(target);
|
|
128
172
|
const entry = this.entries.get(body.id);
|
|
129
|
-
return entry ?
|
|
173
|
+
return entry ? entry.strategies.map((e) => e.strategy) : [];
|
|
130
174
|
}
|
|
131
175
|
/**
|
|
132
176
|
* Updates all registered strategies.
|
|
133
177
|
*
|
|
134
178
|
* Call this method once per frame before `PhysicsEngine.step()` so that the
|
|
135
179
|
* physics simulation integrates the velocities that strategies configure.
|
|
180
|
+
*
|
|
181
|
+
* This method handles the movement lifecycle:
|
|
182
|
+
* - Triggers `onStart` callback on first update
|
|
183
|
+
* - Calls `strategy.update()` each frame
|
|
184
|
+
* - When `isFinished()` returns true:
|
|
185
|
+
* - Calls `strategy.onFinished()` if defined
|
|
186
|
+
* - Triggers `onComplete` callback
|
|
187
|
+
* - Resolves the Promise returned by `add()`
|
|
136
188
|
*
|
|
137
189
|
* @param dt - Time delta in seconds
|
|
138
190
|
*/
|
|
@@ -144,14 +196,22 @@ class MovementManager {
|
|
|
144
196
|
continue;
|
|
145
197
|
}
|
|
146
198
|
for (let i = strategies.length - 1; i >= 0; i -= 1) {
|
|
147
|
-
const
|
|
148
|
-
if (!
|
|
199
|
+
const strategyEntry = strategies[i];
|
|
200
|
+
if (!strategyEntry) {
|
|
149
201
|
continue;
|
|
150
202
|
}
|
|
151
|
-
|
|
152
|
-
if (
|
|
203
|
+
const { strategy, options, resolve } = strategyEntry;
|
|
204
|
+
if (!strategyEntry.started) {
|
|
205
|
+
strategyEntry.started = true;
|
|
206
|
+
options?.onStart?.();
|
|
207
|
+
}
|
|
208
|
+
strategy.update(body, dt);
|
|
209
|
+
const isFinished = strategy.isFinished?.();
|
|
210
|
+
if (isFinished) {
|
|
153
211
|
strategies.splice(i, 1);
|
|
154
|
-
|
|
212
|
+
strategy.onFinished?.();
|
|
213
|
+
options?.onComplete?.();
|
|
214
|
+
resolve?.();
|
|
155
215
|
}
|
|
156
216
|
}
|
|
157
217
|
if (strategies.length === 0) {
|
package/dist/index30.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index30.js","sources":["../src/movement/MovementManager.ts"],"sourcesContent":["import { PhysicsEngine } from '../api/PhysicsEngine';\nimport { Entity } from '../physics/Entity';\nimport { EntityMovementBody } from './adapters/EntityMovementBody';\nimport { MovementBody, MovementStrategy } from './MovementStrategy';\n\n/**\n * Resolves an entity from an identifier.\n *\n * When provided to the movement manager, the resolver enables ergonomic calls\n * that pass a string identifier instead of the `Entity` instance.\n *\n * @example\n * ```typescript\n * const manager = new MovementManager((id) => engine.getEntityByUUID(id));\n * manager.add(player.uuid, new Dash(6, { x: 1, y: 0 }, 0.2));\n * ```\n */\nexport type EntityResolver = (id: string) => MovementBody | undefined;\n\ninterface MovementEntry {\n body: MovementBody;\n strategies: MovementStrategy[];\n}\n\n/**\n * Manages movement strategies assigned to entities.\n *\n * The manager executes strategies before each physics step, removes completed\n * behaviours and exposes utilities to inspect and maintain assignments.\n *\n * @example\n * ```typescript\n * const manager = new MovementManager((id) => engine.getEntityByUUID(id));\n * manager.add(playerEntity, new LinearMove({ x: 2, y: 0 }));\n *\n * // Game loop\n * manager.update(deltaSeconds);\n * engine.step();\n * ```\n */\nexport class MovementManager {\n private readonly entries: Map<string, MovementEntry> = new Map();\n private readonly entityWrappers = new WeakMap<Entity, EntityMovementBody>();\n\n constructor(private readonly resolveEntity?: EntityResolver) {}\n\n /**\n * Convenience factory that binds the manager to a physics engine.\n *\n * @param engine - Physics engine whose entities will be controlled\n * @returns A movement manager configured with an entity resolver\n */\n static forEngine(engine: PhysicsEngine): MovementManager {\n let manager: MovementManager;\n manager = new MovementManager((id) => {\n const entity = engine.getEntityByUUID(id);\n return entity ? manager.wrapEntity(entity) : undefined;\n });\n return manager;\n }\n\n /**\n * Adds a movement strategy to an entity.\n *\n * @param target - Entity instance or entity UUID when a resolver is configured\n * @param strategy - Strategy to execute\n */\n add(target: Entity | MovementBody | string, strategy: MovementStrategy): void {\n const body = this.resolveTarget(target);\n const key = body.id;\n\n if (!this.entries.has(key)) {\n this.entries.set(key, { body, strategies: [] });\n }\n\n this.entries.get(key)!.strategies.push(strategy);\n }\n\n /**\n * Removes a specific strategy from an entity.\n *\n * @param target - Entity instance or identifier\n * @param strategy - Strategy instance to remove\n * @returns True when the strategy has been removed\n */\n remove(target: Entity | MovementBody | string, strategy: MovementStrategy): boolean {\n const body = this.resolveTarget(target);\n const entry = this.entries.get(body.id);\n if (!entry) {\n return false;\n }\n\n const index = entry.strategies.indexOf(strategy);\n if (index === -1) {\n return false;\n }\n\n entry.strategies.splice(index, 1);\n if (entry.strategies.length === 0) {\n this.entries.delete(body.id);\n }\n return true;\n }\n\n /**\n * Removes all strategies from an entity.\n *\n * @param target - Entity or identifier\n */\n clear(target: Entity | MovementBody | string): void {\n const body = this.resolveTarget(target);\n this.entries.delete(body.id);\n }\n\n /**\n * Stops all movement for an entity immediately\n * \n * This method completely stops an entity's movement by:\n * - Removing all active movement strategies (dash, linear moves, etc.)\n * - Stopping the entity's velocity and angular velocity\n * - Clearing accumulated forces\n * - Waking up the entity if it was sleeping\n * \n * This is useful when changing maps, teleporting, or when you need\n * to halt an entity's movement completely without making it static.\n * \n * @param target - Entity, MovementBody, or identifier\n * \n * @example\n * ```ts\n * // Stop movement when changing maps\n * if (mapChanged) {\n * movement.stopMovement(playerEntity);\n * }\n * \n * // Stop movement after teleporting\n * entity.position.set(100, 200);\n * movement.stopMovement(entity);\n * \n * // Stop movement when player dies\n * if (player.isDead()) {\n * movement.stopMovement(playerEntity);\n * }\n * ```\n */\n stopMovement(target: Entity | MovementBody | string): void {\n const body = this.resolveTarget(target);\n \n // Remove all movement strategies\n this.clear(target);\n \n // If the body wraps an Entity, stop its movement directly\n if ('getEntity' in body && typeof (body as any).getEntity === 'function') {\n const entity = (body as any).getEntity();\n if (entity && typeof entity.stopMovement === 'function') {\n entity.stopMovement();\n }\n }\n }\n\n /**\n * Checks if an entity has active strategies.\n *\n * @param target - Entity or identifier\n * @returns True when strategies are registered\n */\n hasActiveStrategies(target: Entity | MovementBody | string): boolean {\n const body = this.resolveTarget(target);\n return (this.entries.get(body.id)?.strategies.length ?? 0) > 0;\n }\n\n /**\n * Returns a snapshot of the strategies assigned to an entity.\n *\n * @param target - Entity or identifier\n * @returns Copy of the strategies array (empty array when none)\n */\n getStrategies(target: Entity | MovementBody | string): MovementStrategy[] {\n const body = this.resolveTarget(target);\n const entry = this.entries.get(body.id);\n return entry ? [...entry.strategies] : [];\n }\n\n /**\n * Updates all registered strategies.\n *\n * Call this method once per frame before `PhysicsEngine.step()` so that the\n * physics simulation integrates the velocities that strategies configure.\n *\n * @param dt - Time delta in seconds\n */\n update(dt: number): void {\n for (const [key, entry] of this.entries) {\n const { body, strategies } = entry;\n\n if (strategies.length === 0) {\n this.entries.delete(key);\n continue;\n }\n\n for (let i = strategies.length - 1; i >= 0; i -= 1) {\n const current = strategies[i];\n if (!current) {\n continue;\n }\n\n current.update(body, dt);\n\n if (current.isFinished?.()) {\n strategies.splice(i, 1);\n current.onFinished?.();\n }\n }\n\n if (strategies.length === 0) {\n this.entries.delete(key);\n }\n }\n }\n\n /**\n * Removes all strategies from all entities.\n */\n clearAll(): void {\n this.entries.clear();\n }\n\n private resolveTarget(target: Entity | MovementBody | string): MovementBody {\n if (this.isMovementBody(target)) {\n return target;\n }\n\n if (target instanceof Entity) {\n return this.wrapEntity(target);\n }\n\n if (!this.resolveEntity) {\n throw new Error('MovementManager: cannot resolve entity from identifier without a resolver.');\n }\n\n const entity = this.resolveEntity(target);\n if (!entity) {\n throw new Error(`MovementManager: unable to resolve entity for identifier ${target}.`);\n }\n return entity;\n }\n\n private wrapEntity(entity: Entity): EntityMovementBody {\n let wrapper = this.entityWrappers.get(entity);\n if (!wrapper) {\n wrapper = new EntityMovementBody(entity);\n this.entityWrappers.set(entity, wrapper);\n }\n return wrapper;\n }\n\n private isMovementBody(value: unknown): value is MovementBody {\n return Boolean(\n value &&\n typeof value === 'object' &&\n 'id' in value &&\n 'setVelocity' in value &&\n typeof (value as MovementBody).setVelocity === 'function',\n );\n }\n}\n\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index30.js","sources":["../src/movement/MovementManager.ts"],"sourcesContent":["import { PhysicsEngine } from '../api/PhysicsEngine';\nimport { Entity } from '../physics/Entity';\nimport { EntityMovementBody } from './adapters/EntityMovementBody';\nimport { MovementBody, MovementStrategy, MovementOptions } from './MovementStrategy';\n\n/**\n * Resolves an entity from an identifier.\n *\n * When provided to the movement manager, the resolver enables ergonomic calls\n * that pass a string identifier instead of the `Entity` instance.\n *\n * @example\n * ```typescript\n * const manager = new MovementManager((id) => engine.getEntityByUUID(id));\n * manager.add(player.uuid, new Dash(6, { x: 1, y: 0 }, 0.2));\n * ```\n */\nexport type EntityResolver = (id: string) => MovementBody | undefined;\n\n/**\n * Internal entry for tracking a strategy with its options and Promise resolver\n */\ninterface StrategyEntry {\n strategy: MovementStrategy;\n options?: MovementOptions;\n resolve?: () => void;\n started: boolean;\n}\n\ninterface MovementEntry {\n body: MovementBody;\n strategies: StrategyEntry[];\n}\n\n/**\n * Manages movement strategies assigned to entities.\n *\n * The manager executes strategies before each physics step, removes completed\n * behaviours and exposes utilities to inspect and maintain assignments.\n *\n * @example\n * ```typescript\n * const manager = new MovementManager((id) => engine.getEntityByUUID(id));\n * manager.add(playerEntity, new LinearMove({ x: 2, y: 0 }));\n *\n * // Game loop\n * manager.update(deltaSeconds);\n * engine.step();\n * ```\n */\nexport class MovementManager {\n private readonly entries: Map<string, MovementEntry> = new Map();\n private readonly entityWrappers = new WeakMap<Entity, EntityMovementBody>();\n\n constructor(private readonly resolveEntity?: EntityResolver) {}\n\n /**\n * Convenience factory that binds the manager to a physics engine.\n *\n * @param engine - Physics engine whose entities will be controlled\n * @returns A movement manager configured with an entity resolver\n */\n static forEngine(engine: PhysicsEngine): MovementManager {\n let manager: MovementManager;\n manager = new MovementManager((id) => {\n const entity = engine.getEntityByUUID(id);\n return entity ? manager.wrapEntity(entity) : undefined;\n });\n return manager;\n }\n\n /**\n * Adds a movement strategy to an entity.\n * \n * Returns a Promise that resolves when the movement completes (when `isFinished()` returns true).\n * If the strategy doesn't implement `isFinished()`, the Promise resolves immediately after adding.\n *\n * @param target - Entity instance or entity UUID when a resolver is configured\n * @param strategy - Strategy to execute\n * @param options - Optional callbacks for movement lifecycle events\n * @returns Promise that resolves when the movement completes\n * \n * @example\n * ```typescript\n * // Simple usage - fire and forget\n * manager.add(player, new Dash(8, { x: 1, y: 0 }, 200));\n * \n * // Wait for completion\n * await manager.add(player, new Dash(8, { x: 1, y: 0 }, 200));\n * console.log('Dash finished!');\n * \n * // With callbacks\n * await manager.add(player, new Knockback({ x: -1, y: 0 }, 5, 300), {\n * onStart: () => {\n * player.directionFixed = true;\n * player.animationFixed = true;\n * },\n * onComplete: () => {\n * player.directionFixed = false;\n * player.animationFixed = false;\n * }\n * });\n * ```\n */\n add(target: Entity | MovementBody | string, strategy: MovementStrategy, options?: MovementOptions): Promise<void> {\n const body = this.resolveTarget(target);\n const key = body.id;\n\n if (!this.entries.has(key)) {\n this.entries.set(key, { body, strategies: [] });\n }\n\n // If the strategy doesn't have isFinished, resolve immediately\n if (!strategy.isFinished) {\n const entry: StrategyEntry = { strategy, started: false };\n if (options) {\n entry.options = options;\n }\n this.entries.get(key)!.strategies.push(entry);\n return Promise.resolve();\n }\n\n // Create a Promise that will resolve when the strategy finishes\n return new Promise<void>((resolve) => {\n const entry: StrategyEntry = { strategy, resolve, started: false };\n if (options) {\n entry.options = options;\n }\n this.entries.get(key)!.strategies.push(entry);\n });\n }\n\n /**\n * Removes a specific strategy from an entity.\n * \n * Note: This will NOT trigger the onComplete callback or resolve the Promise.\n * Use this when you want to cancel a movement without completion.\n *\n * @param target - Entity instance or identifier\n * @param strategy - Strategy instance to remove\n * @returns True when the strategy has been removed\n */\n remove(target: Entity | MovementBody | string, strategy: MovementStrategy): boolean {\n const body = this.resolveTarget(target);\n const entry = this.entries.get(body.id);\n if (!entry) {\n return false;\n }\n\n const index = entry.strategies.findIndex(e => e.strategy === strategy);\n if (index === -1) {\n return false;\n }\n\n entry.strategies.splice(index, 1);\n if (entry.strategies.length === 0) {\n this.entries.delete(body.id);\n }\n return true;\n }\n\n /**\n * Removes all strategies from an entity.\n *\n * @param target - Entity or identifier\n */\n clear(target: Entity | MovementBody | string): void {\n const body = this.resolveTarget(target);\n this.entries.delete(body.id);\n }\n\n /**\n * Stops all movement for an entity immediately\n * \n * This method completely stops an entity's movement by:\n * - Removing all active movement strategies (dash, linear moves, etc.)\n * - Stopping the entity's velocity and angular velocity\n * - Clearing accumulated forces\n * - Waking up the entity if it was sleeping\n * \n * This is useful when changing maps, teleporting, or when you need\n * to halt an entity's movement completely without making it static.\n * \n * @param target - Entity, MovementBody, or identifier\n * \n * @example\n * ```ts\n * // Stop movement when changing maps\n * if (mapChanged) {\n * movement.stopMovement(playerEntity);\n * }\n * \n * // Stop movement after teleporting\n * entity.position.set(100, 200);\n * movement.stopMovement(entity);\n * \n * // Stop movement when player dies\n * if (player.isDead()) {\n * movement.stopMovement(playerEntity);\n * }\n * ```\n */\n stopMovement(target: Entity | MovementBody | string): void {\n const body = this.resolveTarget(target);\n \n // Remove all movement strategies\n this.clear(target);\n \n // If the body wraps an Entity, stop its movement directly\n if ('getEntity' in body && typeof (body as any).getEntity === 'function') {\n const entity = (body as any).getEntity();\n if (entity && typeof entity.stopMovement === 'function') {\n entity.stopMovement();\n }\n }\n }\n\n /**\n * Checks if an entity has active strategies.\n *\n * @param target - Entity or identifier\n * @returns True when strategies are registered\n */\n hasActiveStrategies(target: Entity | MovementBody | string): boolean {\n const body = this.resolveTarget(target);\n return (this.entries.get(body.id)?.strategies.length ?? 0) > 0;\n }\n\n /**\n * Returns a snapshot of the strategies assigned to an entity.\n *\n * @param target - Entity or identifier\n * @returns Copy of the strategies array (empty array when none)\n */\n getStrategies(target: Entity | MovementBody | string): MovementStrategy[] {\n const body = this.resolveTarget(target);\n const entry = this.entries.get(body.id);\n return entry ? entry.strategies.map(e => e.strategy) : [];\n }\n\n /**\n * Updates all registered strategies.\n *\n * Call this method once per frame before `PhysicsEngine.step()` so that the\n * physics simulation integrates the velocities that strategies configure.\n * \n * This method handles the movement lifecycle:\n * - Triggers `onStart` callback on first update\n * - Calls `strategy.update()` each frame\n * - When `isFinished()` returns true:\n * - Calls `strategy.onFinished()` if defined\n * - Triggers `onComplete` callback\n * - Resolves the Promise returned by `add()`\n *\n * @param dt - Time delta in seconds\n */\n update(dt: number): void {\n for (const [key, entry] of this.entries) {\n const { body, strategies } = entry;\n\n if (strategies.length === 0) {\n this.entries.delete(key);\n continue;\n }\n\n for (let i = strategies.length - 1; i >= 0; i -= 1) {\n const strategyEntry = strategies[i];\n if (!strategyEntry) {\n continue;\n }\n\n const { strategy, options, resolve } = strategyEntry;\n\n // Trigger onStart on first update\n if (!strategyEntry.started) {\n strategyEntry.started = true;\n options?.onStart?.();\n }\n\n strategy.update(body, dt);\n\n const isFinished = strategy.isFinished?.();\n\n if (isFinished) {\n strategies.splice(i, 1);\n \n // Call strategy's own onFinished callback\n strategy.onFinished?.();\n \n // Call options onComplete callback\n options?.onComplete?.();\n \n // Resolve the Promise\n resolve?.();\n }\n }\n\n if (strategies.length === 0) {\n this.entries.delete(key);\n }\n }\n }\n\n /**\n * Removes all strategies from all entities.\n */\n clearAll(): void {\n this.entries.clear();\n }\n\n private resolveTarget(target: Entity | MovementBody | string): MovementBody {\n if (this.isMovementBody(target)) {\n return target;\n }\n\n if (target instanceof Entity) {\n return this.wrapEntity(target);\n }\n\n if (!this.resolveEntity) {\n throw new Error('MovementManager: cannot resolve entity from identifier without a resolver.');\n }\n\n const entity = this.resolveEntity(target);\n if (!entity) {\n throw new Error(`MovementManager: unable to resolve entity for identifier ${target}.`);\n }\n return entity;\n }\n\n private wrapEntity(entity: Entity): EntityMovementBody {\n let wrapper = this.entityWrappers.get(entity);\n if (!wrapper) {\n wrapper = new EntityMovementBody(entity);\n this.entityWrappers.set(entity, wrapper);\n }\n return wrapper;\n }\n\n private isMovementBody(value: unknown): value is MovementBody {\n return Boolean(\n value &&\n typeof value === 'object' &&\n 'id' in value &&\n 'setVelocity' in value &&\n typeof (value as MovementBody).setVelocity === 'function',\n );\n }\n}\n\n"],"names":[],"mappings":"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"}
|
package/dist/index41.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index41.js","sources":["../src/movement/strategies/SeekAvoid.ts"],"sourcesContent":["import { PhysicsEngine } from '../../api/PhysicsEngine';\nimport { AABB } from '../../core/math/AABB';\nimport { Vector2 } from '../../core/math/Vector2';\nimport { Entity } from '../../physics/Entity';\nimport { MovementBody, MovementStrategy } from '../MovementStrategy';\n\nconst EPSILON = 1e-3;\n\n/**\n * Seeks a moving target while avoiding nearby obstacles.\n *\n * The strategy combines a pull towards the target with repulsive forces from\n * close entities, creating smooth avoidance behaviour.\n *\n * @example\n * ```typescript\n * const seek = new SeekAvoid(engine, () => playerEntity, 3, 2, 6, 0.5);\n * movementManager.add(enemy, seek);\n * ```\n */\nexport class SeekAvoid implements MovementStrategy {\n private repulseRadiusSq: number;\n private arriveRadiusSq: number;\n\n /**\n * @param engine - Physics engine used for spatial queries\n * @param targetProvider - Function returning the target entity (or null)\n * @param maxSpeed - Maximum speed in units per second\n * @param repulseRadius - Radius in which obstacles apply repulsion\n * @param repulseWeight - Strength of the repulsion force\n * @param arriveRadius - Distance considered as arrival\n */\n constructor(\n private readonly engine: PhysicsEngine,\n private readonly targetProvider: () => Entity | null | undefined,\n private maxSpeed = 2.5,\n private repulseRadius = 2,\n private repulseWeight = 4,\n arriveRadius = 0.5,\n ) {\n this.repulseRadiusSq = repulseRadius * repulseRadius;\n this.arriveRadiusSq = arriveRadius * arriveRadius;\n }\n\n update(body: MovementBody, _dt: number): void {\n const entity = body.getEntity?.();\n if (!entity) {\n throw new Error('SeekAvoid requires a movement body backed by a physics entity.');\n }\n\n const target = this.targetProvider();\n if (!target) {\n body.setVelocity({ x: 0, y: 0 });\n return;\n }\n\n const toTarget = new Vector2(target.position.x - entity.position.x, target.position.y - entity.position.y);\n const distSq = toTarget.lengthSquared();\n let arrived = false;\n\n if (distSq <= this.arriveRadiusSq) {\n toTarget.set(0, 0);\n arrived = true;\n } else if (distSq > 0) {\n toTarget.divInPlace(Math.sqrt(distSq));\n }\n\n const bounds = AABB.fromCenterSize(entity.position.x, entity.position.y, this.repulseRadius * 2, this.repulseRadius * 2);\n const neighbors = this.engine.queryAABB(bounds);\n\n const push = new Vector2(0, 0);\n if (!arrived) {\n for (const other of neighbors) {\n if (other === entity || other === target || other.isStatic()) {\n continue;\n }\n\n const diff = new Vector2(entity.position.x - other.position.x, entity.position.y - other.position.y);\n let d2 = diff.lengthSquared();\n if (d2 > this.repulseRadiusSq) {\n continue;\n }\n\n if (d2 < EPSILON) {\n d2 = EPSILON;\n }\n\n const weight = this.repulseWeight / d2;\n push.addInPlace(diff.mul(weight));\n }\n }\n\n const pushLength = push.length();\n // Clamp avoidance so it cannot overwhelm the attraction toward the target.\n // Push should be weaker than the seek force\n const maxPush = this.maxSpeed * 0.5;\n if (pushLength > maxPush && pushLength > 0) {\n push.mulInPlace(maxPush / pushLength);\n }\n\n // Combine seek and avoid forces\n const desired = toTarget.mul(this.maxSpeed).add(push);\n const desiredLength = desired.length();\n \n // Allow the combined velocity to exceed maxSpeed slightly to maintain responsiveness\n const maxCombinedSpeed = this.maxSpeed * 1.5;\n if (desiredLength > maxCombinedSpeed && desiredLength > 0) {\n desired.mulInPlace(maxCombinedSpeed / desiredLength);\n }\n\n if (!Number.isFinite(desired.x) || !Number.isFinite(desired.y)) {\n body.setVelocity({ x: 0, y: 0 });\n return;\n }\n\n body.setVelocity(desired);\n }\n\n setParameters(\n maxSpeed?: number,\n repulseRadius?: number,\n repulseWeight?: number,\n arriveRadius?: number,\n ): void {\n if (maxSpeed !== undefined) {\n this.maxSpeed = maxSpeed;\n }\n if (repulseRadius !== undefined) {\n this.repulseRadius = repulseRadius;\n this.repulseRadiusSq = repulseRadius * repulseRadius;\n }\n if (repulseWeight !== undefined) {\n this.repulseWeight = repulseWeight;\n }\n if (arriveRadius !== undefined) {\n this.arriveRadiusSq = arriveRadius * arriveRadius;\n }\n }\n}\n\n"],"names":[],"mappings":";;AAMA,MAAM,UAAU;AAcT,MAAM,UAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYjD,YACmB,QACA,gBACT,WAAW,KACX,gBAAgB,GAChB,gBAAgB,GACxB,eAAe,KACf;AANiB,SAAA,SAAA;AACA,SAAA,iBAAA;AACT,SAAA,WAAA;AACA,SAAA,gBAAA;AACA,SAAA,gBAAA;AAGR,SAAK,kBAAkB,gBAAgB;AACvC,SAAK,iBAAiB,eAAe;AAAA,EACvC;AAAA,EAEA,OAAO,MAAoB,KAAmB;AAC5C,UAAM,SAAS,KAAK,YAAA;AACpB,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,gEAAgE;AAAA,IAClF;AAEA,UAAM,SAAS,KAAK,eAAA;AACpB,QAAI,CAAC,QAAQ;AACX,WAAK,YAAY,EAAE,GAAG,GAAG,GAAG,GAAG;AAC/B;AAAA,IACF;AAEA,UAAM,WAAW,IAAI,QAAQ,OAAO,SAAS,IAAI,OAAO,SAAS,GAAG,OAAO,SAAS,IAAI,OAAO,SAAS,CAAC;AACzG,UAAM,SAAS,SAAS,cAAA;AACxB,QAAI,UAAU;AAEd,QAAI,UAAU,KAAK,gBAAgB;AACjC,eAAS,IAAI,GAAG,CAAC;AACjB,gBAAU;AAAA,IACZ,WAAW,SAAS,GAAG;AACrB,eAAS,WAAW,KAAK,KAAK,MAAM,CAAC;AAAA,IACvC;AAEA,UAAM,SAAS,KAAK,eAAe,OAAO,SAAS,GAAG,OAAO,SAAS,GAAG,KAAK,gBAAgB,GAAG,KAAK,gBAAgB,CAAC;AACvH,UAAM,YAAY,KAAK,OAAO,UAAU,MAAM;AAE9C,UAAM,OAAO,IAAI,QAAQ,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"index41.js","sources":["../src/movement/strategies/SeekAvoid.ts"],"sourcesContent":["import { PhysicsEngine } from '../../api/PhysicsEngine';\nimport { AABB } from '../../core/math/AABB';\nimport { Vector2 } from '../../core/math/Vector2';\nimport { Entity } from '../../physics/Entity';\nimport { MovementBody, MovementStrategy } from '../MovementStrategy';\n\nconst EPSILON = 1e-3;\n\n/**\n * Seeks a moving target while avoiding nearby obstacles.\n *\n * The strategy combines a pull towards the target with repulsive forces from\n * close entities, creating smooth avoidance behaviour.\n *\n * @example\n * ```typescript\n * const seek = new SeekAvoid(engine, () => playerEntity, 3, 2, 6, 0.5);\n * movementManager.add(enemy, seek);\n * ```\n */\nexport class SeekAvoid implements MovementStrategy {\n private repulseRadiusSq: number;\n private arriveRadiusSq: number;\n\n /**\n * @param engine - Physics engine used for spatial queries\n * @param targetProvider - Function returning the target entity (or null)\n * @param maxSpeed - Maximum speed in units per second\n * @param repulseRadius - Radius in which obstacles apply repulsion\n * @param repulseWeight - Strength of the repulsion force\n * @param arriveRadius - Distance considered as arrival\n */\n constructor(\n private readonly engine: PhysicsEngine,\n private readonly targetProvider: () => Entity | null | undefined,\n private maxSpeed = 2.5,\n private repulseRadius = 2,\n private repulseWeight = 4,\n arriveRadius = 0.5,\n ) {\n this.repulseRadiusSq = repulseRadius * repulseRadius;\n this.arriveRadiusSq = arriveRadius * arriveRadius;\n }\n\n update(body: MovementBody, _dt: number): void {\n const entity = body.getEntity?.();\n if (!entity) {\n throw new Error('SeekAvoid requires a movement body backed by a physics entity.');\n }\n\n const target = this.targetProvider();\n if (!target) {\n body.setVelocity({ x: 0, y: 0 });\n return;\n }\n\n const toTarget = new Vector2(target.position.x - entity.position.x, target.position.y - entity.position.y);\n const distSq = toTarget.lengthSquared();\n let arrived = false;\n\n if (distSq <= this.arriveRadiusSq) {\n toTarget.set(0, 0);\n arrived = true;\n } else if (distSq > 0) {\n toTarget.divInPlace(Math.sqrt(distSq));\n }\n\n const bounds = AABB.fromCenterSize(entity.position.x, entity.position.y, this.repulseRadius * 2, this.repulseRadius * 2);\n const neighbors = this.engine.queryAABB(bounds);\n\n const push = new Vector2(0, 0);\n let neighborCount = 0;\n if (!arrived) {\n for (const other of neighbors) {\n if (other === entity || other === target || other.isStatic()) {\n continue;\n }\n\n const diff = new Vector2(entity.position.x - other.position.x, entity.position.y - other.position.y);\n let d2 = diff.lengthSquared();\n if (d2 > this.repulseRadiusSq) {\n continue;\n }\n\n neighborCount++;\n if (d2 < EPSILON) {\n d2 = EPSILON;\n }\n\n const weight = this.repulseWeight / d2;\n push.addInPlace(diff.mul(weight));\n }\n }\n\n const pushLength = push.length();\n // Clamp avoidance so it cannot overwhelm the attraction toward the target.\n // Push should be weaker than the seek force\n const maxPush = this.maxSpeed * 0.5;\n if (pushLength > maxPush && pushLength > 0) {\n push.mulInPlace(maxPush / pushLength);\n }\n\n // Combine seek and avoid forces\n const desired = toTarget.mul(this.maxSpeed).add(push);\n const desiredLength = desired.length();\n \n // Allow the combined velocity to exceed maxSpeed slightly to maintain responsiveness\n const maxCombinedSpeed = this.maxSpeed * 1.5;\n if (desiredLength > maxCombinedSpeed && desiredLength > 0) {\n desired.mulInPlace(maxCombinedSpeed / desiredLength);\n }\n\n if (!Number.isFinite(desired.x) || !Number.isFinite(desired.y)) {\n body.setVelocity({ x: 0, y: 0 });\n return;\n }\n\n body.setVelocity(desired);\n }\n\n setParameters(\n maxSpeed?: number,\n repulseRadius?: number,\n repulseWeight?: number,\n arriveRadius?: number,\n ): void {\n if (maxSpeed !== undefined) {\n this.maxSpeed = maxSpeed;\n }\n if (repulseRadius !== undefined) {\n this.repulseRadius = repulseRadius;\n this.repulseRadiusSq = repulseRadius * repulseRadius;\n }\n if (repulseWeight !== undefined) {\n this.repulseWeight = repulseWeight;\n }\n if (arriveRadius !== undefined) {\n this.arriveRadiusSq = arriveRadius * arriveRadius;\n }\n }\n}\n\n"],"names":[],"mappings":"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"}
|
package/dist/index7.js
CHANGED
|
@@ -63,6 +63,8 @@ class Entity {
|
|
|
63
63
|
this.enterTileHandlers = /* @__PURE__ */ new Set();
|
|
64
64
|
this.leaveTileHandlers = /* @__PURE__ */ new Set();
|
|
65
65
|
this.canEnterTileHandlers = /* @__PURE__ */ new Set();
|
|
66
|
+
this.collisionFilterHandlers = /* @__PURE__ */ new Set();
|
|
67
|
+
this.resolutionFilterHandlers = /* @__PURE__ */ new Set();
|
|
66
68
|
this.wasMoving = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ;
|
|
67
69
|
}
|
|
68
70
|
/**
|
|
@@ -609,9 +611,46 @@ class Entity {
|
|
|
609
611
|
this.angularVelocity = Math.sign(this.angularVelocity) * this.maxAngularVelocity;
|
|
610
612
|
}
|
|
611
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Adds a collision filter to this entity
|
|
616
|
+
*
|
|
617
|
+
* Collision filters allow dynamic, conditional collision filtering beyond static bitmasks.
|
|
618
|
+
* Each filter is called when checking if this entity can collide with another.
|
|
619
|
+
* If any filter returns `false`, the collision is ignored.
|
|
620
|
+
*
|
|
621
|
+
* This enables scenarios like:
|
|
622
|
+
* - Players passing through other players (`throughOtherPlayer`)
|
|
623
|
+
* - Entities passing through all characters (`through`)
|
|
624
|
+
* - Custom game-specific collision rules
|
|
625
|
+
*
|
|
626
|
+
* @param filter - Function that returns `true` to allow collision, `false` to ignore
|
|
627
|
+
* @returns Unsubscribe function to remove the filter
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* ```typescript
|
|
631
|
+
* // Allow entity to pass through other players
|
|
632
|
+
* const unsubscribe = entity.addCollisionFilter((self, other) => {
|
|
633
|
+
* const otherOwner = (other as any).owner;
|
|
634
|
+
* if (otherOwner?.type === 'player') {
|
|
635
|
+
* return false; // No collision with players
|
|
636
|
+
* }
|
|
637
|
+
* return true; // Collide with everything else
|
|
638
|
+
* });
|
|
639
|
+
*
|
|
640
|
+
* // Later, remove the filter
|
|
641
|
+
* unsubscribe();
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
644
|
+
addCollisionFilter(filter) {
|
|
645
|
+
this.collisionFilterHandlers.add(filter);
|
|
646
|
+
return () => this.collisionFilterHandlers.delete(filter);
|
|
647
|
+
}
|
|
612
648
|
/**
|
|
613
649
|
* Checks if this entity can collide with another entity
|
|
614
650
|
*
|
|
651
|
+
* First checks collision masks (bitmask filtering), then executes all registered
|
|
652
|
+
* collision filters. If any filter returns `false`, the collision is ignored.
|
|
653
|
+
*
|
|
615
654
|
* @param other - Other entity to check
|
|
616
655
|
* @returns True if collision is possible
|
|
617
656
|
*/
|
|
@@ -620,7 +659,77 @@ class Entity {
|
|
|
620
659
|
const maskA = this.collisionMask;
|
|
621
660
|
const categoryB = other.collisionCategory;
|
|
622
661
|
const maskB = other.collisionMask;
|
|
623
|
-
|
|
662
|
+
if ((categoryA & maskB) === 0 || (categoryB & maskA) === 0) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
for (const filter of this.collisionFilterHandlers) {
|
|
666
|
+
if (!filter(this, other)) {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
for (const filter of other.collisionFilterHandlers) {
|
|
671
|
+
if (!filter(other, this)) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Adds a resolution filter to this entity
|
|
679
|
+
*
|
|
680
|
+
* Resolution filters determine whether a collision should be **resolved** (blocking)
|
|
681
|
+
* or just **detected** (notification only). Unlike collision filters which prevent
|
|
682
|
+
* detection entirely, resolution filters allow collision events to fire while
|
|
683
|
+
* optionally skipping the physical blocking.
|
|
684
|
+
*
|
|
685
|
+
* This enables scenarios like:
|
|
686
|
+
* - Players passing through other players but still triggering touch events
|
|
687
|
+
* - Entities passing through characters but still calling onPlayerTouch hooks
|
|
688
|
+
* - Ghost mode where collisions are detected but not resolved
|
|
689
|
+
*
|
|
690
|
+
* @param filter - Function that returns `true` to resolve (block), `false` to skip
|
|
691
|
+
* @returns Unsubscribe function to remove the filter
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```typescript
|
|
695
|
+
* // Allow entity to pass through players but still trigger events
|
|
696
|
+
* const unsubscribe = entity.addResolutionFilter((self, other) => {
|
|
697
|
+
* const otherOwner = (other as any).owner;
|
|
698
|
+
* if (otherOwner?.type === 'player') {
|
|
699
|
+
* return false; // Pass through but events still fire
|
|
700
|
+
* }
|
|
701
|
+
* return true; // Block other entities
|
|
702
|
+
* });
|
|
703
|
+
*
|
|
704
|
+
* // Later, remove the filter
|
|
705
|
+
* unsubscribe();
|
|
706
|
+
* ```
|
|
707
|
+
*/
|
|
708
|
+
addResolutionFilter(filter) {
|
|
709
|
+
this.resolutionFilterHandlers.add(filter);
|
|
710
|
+
return () => this.resolutionFilterHandlers.delete(filter);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Checks if this entity should resolve (block) a collision with another entity
|
|
714
|
+
*
|
|
715
|
+
* This is called by the CollisionResolver to determine if the collision should
|
|
716
|
+
* result in physical blocking or just notification.
|
|
717
|
+
*
|
|
718
|
+
* @param other - Other entity to check
|
|
719
|
+
* @returns True if collision should be resolved (blocking), false to pass through
|
|
720
|
+
*/
|
|
721
|
+
shouldResolveCollisionWith(other) {
|
|
722
|
+
for (const filter of this.resolutionFilterHandlers) {
|
|
723
|
+
if (!filter(this, other)) {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
for (const filter of other.resolutionFilterHandlers) {
|
|
728
|
+
if (!filter(other, this)) {
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
return true;
|
|
624
733
|
}
|
|
625
734
|
/**
|
|
626
735
|
* @internal
|
package/dist/index7.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index7.js","sources":["../src/physics/Entity.ts"],"sourcesContent":["import { Vector2 } from '../core/math/Vector2';\nimport { UUID, EntityState } from '../core/types';\nimport { generateUUID } from '../utils/uuid';\nimport { CollisionInfo } from '../collision/Collider';\n\nconst MOVEMENT_EPSILON = 1e-3;\nconst MOVEMENT_EPSILON_SQ = MOVEMENT_EPSILON * MOVEMENT_EPSILON;\nconst DIRECTION_CHANGE_THRESHOLD = 1.0;\nconst DIRECTION_CHANGE_THRESHOLD_SQ = DIRECTION_CHANGE_THRESHOLD * DIRECTION_CHANGE_THRESHOLD;\n\nexport type CardinalDirection = 'idle' | 'up' | 'down' | 'left' | 'right';\n\n/**\n * Configuration options for creating an entity\n */\nexport interface EntityConfig {\n /** Initial position */\n position?: Vector2 | { x: number; y: number };\n /** Initial velocity */\n velocity?: Vector2 | { x: number; y: number };\n /** Initial rotation in radians */\n rotation?: number;\n /** Initial angular velocity in radians per second */\n angularVelocity?: number;\n /** Mass of the entity (0 or Infinity for static/immovable entities) */\n mass?: number;\n /** Radius for circular collider */\n radius?: number;\n /** Width for AABB collider */\n width?: number;\n /** Height for AABB collider */\n height?: number;\n /** Capsule collider configuration (if used) */\n capsule?: {\n radius: number;\n height: number;\n };\n /** Enable continuous collision detection (CCD) */\n continuous?: boolean;\n /** Entity state flags */\n state?: EntityState;\n /** Restitution (bounciness) coefficient (0-1) */\n restitution?: number;\n /** Friction coefficient */\n friction?: number;\n /** Linear damping (0-1, higher = more damping) */\n linearDamping?: number;\n /** Angular damping (0-1, higher = more damping) */\n angularDamping?: number;\n /** Maximum linear velocity */\n maxLinearVelocity?: number;\n /** Maximum angular velocity */\n maxAngularVelocity?: number;\n /** Custom UUID (auto-generated if not provided) */\n uuid?: UUID;\n /** Collision mask (bitmask for collision filtering) */\n collisionMask?: number;\n /** Collision category (bitmask) */\n collisionCategory?: number;\n}\n\n/**\n * Physical entity in the physics world\n * \n * Represents a dynamic or static object that can be affected by forces,\n * collisions, and other physical interactions.\n * \n * ## Creating Static Obstacles\n * \n * To create immovable obstacles (walls, decorations), set `mass` to `0` or `Infinity`.\n * This makes the entity static - it will block other entities but cannot be pushed.\n * \n * @example\n * ```typescript\n * // Dynamic entity (player, movable object)\n * const player = new Entity({\n * position: { x: 0, y: 0 },\n * radius: 10,\n * mass: 1,\n * velocity: { x: 5, y: 0 }\n * });\n * \n * // Static obstacle (wall, tree, decoration)\n * const wall = new Entity({\n * position: { x: 100, y: 0 },\n * width: 20,\n * height: 100,\n * mass: Infinity // or mass: 0\n * });\n * \n * player.applyForce(new Vector2(10, 0));\n * ```\n */\nexport class Entity {\n /**\n * Unique identifier (UUID)\n */\n public readonly uuid: UUID;\n\n /**\n * Position in world space\n */\n public position: Vector2;\n\n /**\n * Linear velocity\n */\n public velocity: Vector2;\n\n /**\n * Rotation in radians\n */\n public rotation: number;\n\n /**\n * Angular velocity in radians per second\n */\n public angularVelocity: number;\n\n /**\n * Mass (0 or Infinity means infinite mass / static)\n */\n public mass: number;\n\n /**\n * Inverse mass (cached for performance, 0 if mass is 0 or Infinity)\n */\n public invMass: number;\n\n /**\n * Radius for circular collider (if used)\n */\n public radius: number;\n\n /**\n * Width for AABB collider (if used)\n */\n public width: number;\n\n /**\n * Height for AABB collider (if used)\n */\n public height: number;\n\n /**\n * Capsule collider configuration (if used)\n */\n public capsule?: {\n radius: number;\n height: number;\n };\n\n /**\n * Enable continuous collision detection (CCD)\n */\n public continuous: boolean;\n\n /**\n * Entity state flags\n */\n public state: EntityState;\n\n /**\n * Restitution (bounciness) coefficient (0-1)\n */\n public restitution: number;\n\n /**\n * Friction coefficient\n */\n public friction: number;\n\n /**\n * Linear damping (0-1)\n */\n public linearDamping: number;\n\n /**\n * Angular damping (0-1)\n */\n public angularDamping: number;\n\n /**\n * Maximum linear velocity\n */\n public maxLinearVelocity: number;\n\n /**\n * Maximum angular velocity\n */\n public maxAngularVelocity: number;\n\n /**\n * Accumulated force for this frame\n */\n public force: Vector2;\n\n /**\n * Accumulated torque for this frame\n */\n public torque: number;\n\n /**\n * Collision mask (bitmask)\n */\n public collisionMask: number;\n\n /**\n * Collision category (bitmask)\n */\n public collisionCategory: number;\n\n /**\n * Time since last movement (for sleep detection)\n */\n public timeSinceMovement: number;\n\n /**\n * Threshold for sleep detection (seconds of inactivity)\n */\n public sleepThreshold: number;\n\n /**\n * Current tile coordinates (x, y)\n */\n public currentTile: Vector2;\n\n private collisionEnterHandlers: Set<EntityCollisionHandler>;\n private collisionExitHandlers: Set<EntityCollisionHandler>;\n private positionSyncHandlers: Set<EntityPositionSyncHandler>;\n private directionSyncHandlers: Set<EntityDirectionSyncHandler>;\n private movementChangeHandlers: Set<EntityMovementChangeHandler>;\n private enterTileHandlers: Set<EntityTileHandler>;\n private leaveTileHandlers: Set<EntityTileHandler>;\n private canEnterTileHandlers: Set<EntityCanEnterTileHandler>;\n private wasMoving: boolean;\n private lastCardinalDirection: CardinalDirection = 'idle';\n\n /**\n * Creates a new entity\n * \n * @param config - Entity configuration\n */\n constructor(config: EntityConfig = {}) {\n // Generate UUID if not provided\n this.uuid = config.uuid ?? generateUUID();\n\n // Position and velocity\n if (config.position instanceof Vector2) {\n this.position = config.position.clone();\n } else if (config.position) {\n this.position = new Vector2(config.position.x, config.position.y);\n } else {\n this.position = new Vector2(0, 0);\n }\n\n this.currentTile = new Vector2(0, 0); // Will be updated by World\n\n if (config.velocity instanceof Vector2) {\n this.velocity = config.velocity.clone();\n } else if (config.velocity) {\n this.velocity = new Vector2(config.velocity.x, config.velocity.y);\n } else {\n this.velocity = new Vector2(0, 0);\n }\n\n // Rotation\n this.rotation = config.rotation ?? 0;\n this.angularVelocity = config.angularVelocity ?? 0;\n\n // Mass\n this.mass = config.mass ?? 1;\n this.invMass = this.mass > 0 ? 1 / this.mass : 0;\n\n // Collider dimensions\n this.radius = config.radius ?? 0;\n this.width = config.width ?? 0;\n this.height = config.height ?? 0;\n if (config.capsule !== undefined) {\n this.capsule = config.capsule;\n }\n this.continuous = config.continuous ?? false;\n\n // State\n this.state = config.state ?? EntityState.Dynamic;\n\n // Material properties\n this.restitution = config.restitution ?? 0.2;\n this.friction = config.friction ?? 0.3;\n this.linearDamping = config.linearDamping ?? 0.01;\n this.angularDamping = config.angularDamping ?? 0.01;\n\n // Velocity limits\n this.maxLinearVelocity = config.maxLinearVelocity ?? Infinity;\n this.maxAngularVelocity = config.maxAngularVelocity ?? Infinity;\n\n // Forces\n this.force = new Vector2(0, 0);\n this.torque = 0;\n\n // Collision filtering\n this.collisionMask = config.collisionMask ?? 0xffffffff;\n this.collisionCategory = config.collisionCategory ?? 0x00000001;\n\n // Sleep detection\n this.timeSinceMovement = 0;\n this.sleepThreshold = 0.5; // 0.5 seconds of inactivity\n\n // Event handlers\n this.collisionEnterHandlers = new Set();\n this.collisionExitHandlers = new Set();\n this.positionSyncHandlers = new Set();\n this.directionSyncHandlers = new Set();\n this.positionSyncHandlers = new Set();\n this.directionSyncHandlers = new Set();\n this.movementChangeHandlers = new Set();\n this.enterTileHandlers = new Set();\n this.leaveTileHandlers = new Set();\n this.canEnterTileHandlers = new Set();\n\n // Initialize movement state\n this.wasMoving = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ;\n }\n\n /**\n * Registers a handler fired when this entity starts colliding with another one.\n *\n * - **Purpose:** offer per-entity collision hooks without subscribing to the global event system.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n *\n * @param handler - Collision enter listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onCollisionEnter(({ other }) => {\n * console.log('Started colliding with', other.uuid);\n * });\n * ```\n */\n public onCollisionEnter(handler: EntityCollisionHandler): () => void {\n this.collisionEnterHandlers.add(handler);\n return () => this.collisionEnterHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when this entity stops colliding with another one.\n *\n * - **Purpose:** detect collision separation at the entity level for local gameplay reactions.\n * - **Design:** mirrors `onCollisionEnter` with identical lifecycle management semantics.\n *\n * @param handler - Collision exit listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onCollisionExit(({ other }) => {\n * console.log('Stopped colliding with', other.uuid);\n * });\n * ```\n */\n public onCollisionExit(handler: EntityCollisionHandler): () => void {\n this.collisionExitHandlers.add(handler);\n return () => this.collisionExitHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when the entity position changes (x, y).\n *\n * - **Purpose:** synchronize position changes for logging, rendering, network sync, etc.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n *\n * @param handler - Position change listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onPositionChange(({ x, y }) => {\n * console.log('Position changed to', x, y);\n * // Update rendering, sync network, etc.\n * });\n * ```\n */\n public onPositionChange(handler: EntityPositionSyncHandler): () => void {\n this.positionSyncHandlers.add(handler);\n return () => this.positionSyncHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when the entity direction changes.\n *\n * - **Purpose:** synchronize direction changes for logging, rendering, network sync, etc.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n *\n * @param handler - Direction change listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onDirectionChange(({ direction, cardinalDirection }) => {\n * console.log('Direction changed to', cardinalDirection);\n * // Update rendering, sync network, etc.\n * });\n * ```\n */\n public onDirectionChange(handler: EntityDirectionSyncHandler): () => void {\n this.directionSyncHandlers.add(handler);\n return () => this.directionSyncHandlers.delete(handler);\n }\n\n /**\n * Manually notifies that the position has changed.\n *\n * - **Purpose:** allow external code to trigger position sync hooks when position is modified directly.\n * - **Design:** can be called after direct position modifications (e.g., `entity.position.set()`).\n *\n * @example\n * ```typescript\n * entity.position.set(100, 200);\n * entity.notifyPositionChange(); // Trigger sync hooks\n * ```\n */\n public notifyPositionChange(): void {\n if (this.positionSyncHandlers.size === 0) {\n return;\n }\n\n const payload: EntityPositionSyncEvent = {\n entity: this,\n x: this.position.x,\n y: this.position.y,\n };\n\n for (const handler of this.positionSyncHandlers) {\n handler(payload);\n }\n }\n\n /**\n * Manually notifies that the direction has changed.\n *\n * - **Purpose:** allow external code to trigger direction sync hooks when direction is modified directly.\n * - **Design:** computes direction from velocity and cardinal direction.\n *\n * @example\n * ```typescript\n * entity.velocity.set(5, 0);\n * entity.notifyDirectionChange(); // Trigger sync hooks\n * ```\n */\n public notifyDirectionChange(): void {\n const isMoving = this.velocity.lengthSquared() > DIRECTION_CHANGE_THRESHOLD_SQ;\n const direction = isMoving ? this.velocity.clone().normalize() : new Vector2(0, 0);\n const cardinalDirection = this.computeCardinalDirection(direction);\n\n // Update state to support hysteresis\n if (cardinalDirection !== 'idle') {\n this.lastCardinalDirection = cardinalDirection;\n }\n\n if (this.directionSyncHandlers.size === 0) {\n return;\n }\n\n const payload: EntityDirectionSyncEvent = {\n entity: this,\n direction,\n cardinalDirection,\n };\n\n for (const handler of this.directionSyncHandlers) {\n handler(payload);\n }\n }\n\n /**\n * Gets the current cardinal direction.\n * \n * This value is updated whenever `notifyDirectionChange()` is called (e.g. by `setVelocity`).\n * It includes hysteresis logic to prevent rapid direction flipping during collisions.\n * \n * @returns The current cardinal direction ('up', 'down', 'left', 'right', 'idle')\n * \n * @example\n * ```typescript\n * const dir = entity.cardinalDirection;\n * if (dir === 'left') {\n * // Render left-facing sprite\n * }\n * ```\n */\n public get cardinalDirection(): CardinalDirection {\n return this.lastCardinalDirection;\n }\n\n /**\n * Registers a handler fired when the entity movement state changes (moving/stopped).\n *\n * - **Purpose:** detect when an entity starts or stops moving for gameplay reactions, animations, or network sync.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n * - **Movement detection:** uses `MOVEMENT_EPSILON` threshold to determine if entity is moving.\n * - **Intensity:** provides the movement speed magnitude to allow fine-grained animation control (e.g., walk vs run).\n *\n * @param handler - Movement state change listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onMovementChange(({ isMoving, intensity }) => {\n * console.log('Entity is', isMoving ? 'moving' : 'stopped', 'at speed', intensity);\n * // Update animations based on intensity\n * if (isMoving && intensity > 100) {\n * // Fast movement - use run animation\n * } else if (isMoving) {\n * // Slow movement - use walk animation\n * }\n * });\n * ```\n */\n public onMovementChange(handler: EntityMovementChangeHandler): () => void {\n this.movementChangeHandlers.add(handler);\n return () => this.movementChangeHandlers.delete(handler);\n }\n\n /**\n * Manually notifies that the movement state has changed.\n *\n * - **Purpose:** allow external code to trigger movement state sync hooks when velocity is modified directly.\n * - **Design:** checks if movement state (moving/stopped) has changed and notifies handlers with movement intensity.\n * - **Intensity:** calculated as the magnitude of the velocity vector (speed in pixels per second).\n *\n * @example\n * ```typescript\n * entity.velocity.set(5, 0);\n * entity.notifyMovementChange(); // Trigger sync hooks if state changed\n * ```\n */\n public notifyMovementChange(): void {\n const isMoving = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ;\n const intensity = this.velocity.length(); // Movement speed magnitude\n\n if (this.movementChangeHandlers.size === 0) {\n // Still update wasMoving even if no handlers to track state correctly\n this.wasMoving = isMoving;\n return;\n }\n\n // Only notify if state actually changed\n if (isMoving !== this.wasMoving) {\n this.wasMoving = isMoving;\n\n const payload: EntityMovementChangeEvent = {\n entity: this,\n isMoving,\n intensity,\n };\n\n for (const handler of this.movementChangeHandlers) {\n handler(payload);\n }\n } else {\n // Update wasMoving even if state didn't change to keep it in sync\n this.wasMoving = isMoving;\n }\n }\n\n /**\n * Registers a handler fired when the entity enters a new tile.\n * \n * @param handler - Tile enter listener\n * @returns Unsubscribe closure\n */\n public onEnterTile(handler: EntityTileHandler): () => void {\n this.enterTileHandlers.add(handler);\n return () => this.enterTileHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when the entity leaves a tile.\n * \n * @param handler - Tile leave listener\n * @returns Unsubscribe closure\n */\n public onLeaveTile(handler: EntityTileHandler): () => void {\n this.leaveTileHandlers.add(handler);\n return () => this.leaveTileHandlers.delete(handler);\n }\n\n /**\n * Registers a handler to check if the entity can enter a tile.\n * If any handler returns false, the entity cannot enter.\n * \n * @param handler - Can enter tile listener\n * @returns Unsubscribe closure\n */\n public canEnterTile(handler: EntityCanEnterTileHandler): () => void {\n this.canEnterTileHandlers.add(handler);\n return () => this.canEnterTileHandlers.delete(handler);\n }\n\n /**\n * @internal\n * Notifies that the entity has entered a tile.\n */\n public notifyEnterTile(x: number, y: number): void {\n if (this.enterTileHandlers.size === 0) return;\n const event: EntityTileEvent = { entity: this, x, y };\n for (const handler of this.enterTileHandlers) {\n handler(event);\n }\n }\n\n /**\n * @internal\n * Notifies that the entity has left a tile.\n */\n public notifyLeaveTile(x: number, y: number): void {\n if (this.leaveTileHandlers.size === 0) return;\n const event: EntityTileEvent = { entity: this, x, y };\n for (const handler of this.leaveTileHandlers) {\n handler(event);\n }\n }\n\n /**\n * @internal\n * Checks if the entity can enter a tile.\n */\n public checkCanEnterTile(x: number, y: number): boolean {\n if (this.canEnterTileHandlers.size === 0) return true;\n const event: EntityTileEvent = { entity: this, x, y };\n for (const handler of this.canEnterTileHandlers) {\n if (handler(event) === false) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Applies a force to the entity\n * \n * Force is accumulated and applied during integration.\n * \n * @param force - Force vector to apply\n * @returns This entity for chaining\n * \n * @example\n * ```typescript\n * entity.applyForce(new Vector2(10, 0)); // Push right\n * ```\n */\n public applyForce(force: Vector2): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n this.force.addInPlace(force);\n return this;\n }\n\n /**\n * Applies a force at a specific point (creates torque)\n * \n * @param force - Force vector to apply\n * @param point - Point of application in world space\n * @returns This entity for chaining\n */\n public applyForceAtPoint(force: Vector2, point: Vector2): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n this.force.addInPlace(force);\n\n // Calculate torque: r × F\n const r = point.sub(this.position);\n this.torque += r.cross(force);\n\n return this;\n }\n\n /**\n * Applies an impulse (instantaneous change in velocity)\n * \n * @param impulse - Impulse vector\n * @returns This entity for chaining\n * \n * @example\n * ```typescript\n * entity.applyImpulse(new Vector2(5, 0)); // Instant push\n * ```\n */\n public applyImpulse(impulse: Vector2): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n this.velocity.addInPlace(impulse.mul(this.invMass));\n this.notifyMovementChange();\n this.notifyDirectionChange();\n return this;\n }\n\n /**\n * Applies an angular impulse (instantaneous change in angular velocity)\n * \n * @param impulse - Angular impulse value\n * @returns This entity for chaining\n */\n public applyAngularImpulse(impulse: number): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n // Simplified: assume moment of inertia = mass * radius^2\n const momentOfInertia = this.mass * this.radius * this.radius;\n if (momentOfInertia > 0) {\n this.angularVelocity += impulse / momentOfInertia;\n }\n return this;\n }\n\n /**\n * Teleports the entity to a new position\n * \n * @param position - New position\n * @returns This entity for chaining\n */\n public teleport(position: Vector2 | { x: number; y: number }): Entity {\n if (position instanceof Vector2) {\n this.position.copyFrom(position);\n } else {\n this.position.set(position.x, position.y);\n }\n this.wakeUp();\n this.notifyPositionChange();\n return this;\n }\n\n /**\n * Sets the velocity directly\n * \n * @param velocity - New velocity\n * @returns This entity for chaining\n */\n public setVelocity(velocity: Vector2 | { x: number; y: number }): Entity {\n const oldVelocity = this.velocity.clone();\n if (velocity instanceof Vector2) {\n this.velocity.copyFrom(velocity);\n } else {\n this.velocity.set(velocity.x, velocity.y);\n }\n this.wakeUp();\n\n // Check if direction changed\n const oldDirection = oldVelocity.lengthSquared() > MOVEMENT_EPSILON_SQ\n ? oldVelocity.clone().normalize()\n : new Vector2(0, 0);\n const newDirection = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ\n ? this.velocity.clone().normalize()\n : new Vector2(0, 0);\n\n const oldCardinal = this.computeCardinalDirection(oldDirection);\n const newCardinal = this.computeCardinalDirection(newDirection);\n\n if (oldCardinal !== newCardinal || Math.abs(oldDirection.dot(newDirection) - 1) > 0.01) {\n this.notifyDirectionChange();\n }\n\n // Check if movement state changed\n this.notifyMovementChange();\n\n return this;\n }\n\n /**\n * Freezes the entity (makes it static)\n * \n * @returns This entity for chaining\n */\n public freeze(): Entity {\n this.state = EntityState.Static;\n this.velocity.set(0, 0);\n this.angularVelocity = 0;\n this.force.set(0, 0);\n this.torque = 0;\n this.notifyMovementChange();\n return this;\n }\n\n /**\n * Unfreezes the entity (makes it dynamic)\n * \n * @returns This entity for chaining\n */\n public unfreeze(): Entity {\n if (this.mass > 0) {\n this.state = EntityState.Dynamic;\n }\n return this;\n }\n\n /**\n * Puts the entity to sleep (stops updating)\n * \n * @returns This entity for chaining\n */\n public sleep(): Entity {\n if (!this.isStatic()) {\n this.state |= EntityState.Sleeping;\n this.velocity.set(0, 0);\n this.angularVelocity = 0;\n this.force.set(0, 0);\n this.torque = 0;\n this.notifyMovementChange();\n }\n return this;\n }\n\n /**\n * Wakes up the entity (resumes updating)\n * \n * @returns This entity for chaining\n */\n public wakeUp(): Entity {\n this.state &= ~EntityState.Sleeping;\n this.timeSinceMovement = 0;\n return this;\n }\n\n /**\n * Checks if the entity is static\n * \n * An entity is considered static if:\n * - It has the Static state flag, OR\n * - It has infinite mass (mass = Infinity), OR\n * - It has zero inverse mass (invMass = 0)\n * \n * @returns True if static\n */\n public isStatic(): boolean {\n return (this.state & EntityState.Static) !== 0 || this.invMass === 0;\n }\n\n /**\n * Checks if the entity is dynamic\n * \n * @returns True if dynamic\n */\n public isDynamic(): boolean {\n return (this.state & EntityState.Dynamic) !== 0 && this.mass > 0;\n }\n\n /**\n * Checks if the entity is sleeping\n * \n * @returns True if sleeping\n */\n public isSleeping(): boolean {\n return (this.state & EntityState.Sleeping) !== 0;\n }\n\n /**\n * Checks if the entity is kinematic\n * \n * @returns True if kinematic\n */\n public isKinematic(): boolean {\n return (this.state & EntityState.Kinematic) !== 0;\n }\n\n /**\n * Resets accumulated forces and torques\n * \n * Called at the start of each physics step.\n */\n public clearForces(): void {\n this.force.set(0, 0);\n this.torque = 0;\n }\n\n /**\n * Stops all movement immediately\n * \n * Completely stops the entity's movement by:\n * - Setting velocity to zero\n * - Setting angular velocity to zero\n * - Clearing accumulated forces and torques\n * - Waking up the entity if it was sleeping\n * - Notifying movement state change\n * \n * Unlike `freeze()`, this method keeps the entity dynamic and does not\n * change its state. It's useful for stopping movement when changing maps,\n * teleporting, or when you need to halt an entity without making it static.\n * \n * @returns This entity for chaining\n * \n * @example\n * ```ts\n * // Stop movement when changing maps\n * if (mapChanged) {\n * entity.stopMovement();\n * }\n * \n * // Stop movement after teleporting\n * entity.position.set(100, 200);\n * entity.stopMovement();\n * \n * // Stop movement when player dies\n * if (player.isDead()) {\n * playerEntity.stopMovement();\n * }\n * ```\n */\n public stopMovement(): Entity {\n this.velocity.set(0, 0);\n this.angularVelocity = 0;\n this.clearForces();\n this.wakeUp();\n this.notifyMovementChange();\n this.notifyDirectionChange();\n return this;\n }\n\n /**\n * Clamps velocities to maximum values\n */\n public clampVelocities(): void {\n const speed = this.velocity.length();\n if (speed > this.maxLinearVelocity) {\n this.velocity.normalizeInPlace().mulInPlace(this.maxLinearVelocity);\n }\n\n if (Math.abs(this.angularVelocity) > this.maxAngularVelocity) {\n this.angularVelocity = Math.sign(this.angularVelocity) * this.maxAngularVelocity;\n }\n }\n\n /**\n * Checks if this entity can collide with another entity\n * \n * @param other - Other entity to check\n * @returns True if collision is possible\n */\n public canCollideWith(other: Entity): boolean {\n // Check collision masks\n const categoryA = this.collisionCategory;\n const maskA = this.collisionMask;\n const categoryB = other.collisionCategory;\n const maskB = other.collisionMask;\n\n return (categoryA & maskB) !== 0 && (categoryB & maskA) !== 0;\n }\n\n /**\n * @internal\n *\n * Notifies the entity that a collision has started.\n *\n * @param collision - Collision information shared by the world\n * @param other - The counterpart entity\n */\n public notifyCollisionEnter(collision: CollisionInfo, other: Entity): void {\n if (this.collisionEnterHandlers.size === 0) {\n return;\n }\n\n const payload: EntityCollisionEvent = {\n entity: this,\n other,\n collision,\n };\n\n for (const handler of this.collisionEnterHandlers) {\n handler(payload);\n }\n }\n\n /**\n * @internal\n *\n * Notifies the entity that a collision has ended.\n *\n * @param collision - Collision information stored before separation\n * @param other - The counterpart entity\n */\n public notifyCollisionExit(collision: CollisionInfo, other: Entity): void {\n if (this.collisionExitHandlers.size === 0) {\n return;\n }\n\n const payload: EntityCollisionEvent = {\n entity: this,\n other,\n collision,\n };\n\n for (const handler of this.collisionExitHandlers) {\n handler(payload);\n }\n }\n\n\n private computeCardinalDirection(direction: Vector2): CardinalDirection {\n if (direction.lengthSquared() <= MOVEMENT_EPSILON_SQ) {\n return 'idle';\n }\n\n // If we were idle, just return the strongest direction without bias\n if (this.lastCardinalDirection === 'idle') {\n const absX = Math.abs(direction.x);\n const absY = Math.abs(direction.y);\n if (absX >= absY) {\n return direction.x >= 0 ? 'right' : 'left';\n }\n return direction.y >= 0 ? 'down' : 'up';\n }\n\n // Check for 180-degree flips (Bounce protection)\n // If the new direction is strictly opposite to the last one, we require a higher velocity\n // to accept the change. This filters out collision rebounds.\n const isOpposite =\n (this.lastCardinalDirection === 'left' && direction.x > 0.5) ||\n (this.lastCardinalDirection === 'right' && direction.x < -0.5) ||\n (this.lastCardinalDirection === 'up' && direction.y > 0.5) ||\n (this.lastCardinalDirection === 'down' && direction.y < -0.5);\n\n const speedSq = this.velocity.lengthSquared();\n\n // Threshold to accept a 180-degree turn (avoid jitter on bounce)\n // We expect a \"real\" turn to have some acceleration or accumulated velocity\n if (isOpposite && speedSq < 100.0) { // Speed < 10\n return this.lastCardinalDirection;\n }\n\n const absX = Math.abs(direction.x);\n const absY = Math.abs(direction.y);\n const bias = 2.0; // Strong bias to keep current direction\n\n // Hysteresis: favor current axis if we have a valid last direction\n if (['left', 'right'].includes(this.lastCardinalDirection)) {\n // Currently horizontal: stick to it unless vertical is significantly stronger\n // AND vertical component has meaningful speed (prevents slide when blocked)\n if (absY > absX * bias) {\n // Check if the \"new\" vertical movement is actually significant\n // e.g. if we are blocked Horizontally (x=0), absY will win even if it's 0.0001 without this check\n if (Math.abs(this.velocity.y) > 5.0) {\n return direction.y >= 0 ? 'down' : 'up';\n }\n }\n // Default: keep horizontal orientation, just update sign if needed (and not filtered by opposite check)\n // If we are here, it means we didn't switch axis, and we didn't trigger the \"Opposite\" guard above.\n // However, if we are \"blocked\" (velocity very low), we should probably not even flip sign.\n if (speedSq > 1.0) {\n return direction.x >= 0 ? 'right' : 'left';\n }\n return this.lastCardinalDirection;\n } else {\n // Currently vertical: stick to it unless horizontal is significantly stronger\n if (absX > absY * bias) {\n if (Math.abs(this.velocity.x) > 5.0) {\n return direction.x >= 0 ? 'right' : 'left';\n }\n }\n if (speedSq > 1.0) {\n return direction.y >= 0 ? 'down' : 'up';\n }\n return this.lastCardinalDirection;\n }\n }\n\n /**\n * Creates a copy of this entity\n * \n * @returns New entity with copied properties\n */\n public clone(): Entity {\n const entity = new Entity({\n position: this.position.clone(),\n velocity: this.velocity.clone(),\n rotation: this.rotation,\n angularVelocity: this.angularVelocity,\n mass: this.mass,\n radius: this.radius,\n width: this.width,\n height: this.height,\n state: this.state,\n restitution: this.restitution,\n friction: this.friction,\n linearDamping: this.linearDamping,\n angularDamping: this.angularDamping,\n maxLinearVelocity: this.maxLinearVelocity,\n maxAngularVelocity: this.maxAngularVelocity,\n collisionMask: this.collisionMask,\n collisionCategory: this.collisionCategory,\n uuid: this.uuid,\n });\n return entity;\n }\n}\n\nexport interface EntityCollisionEvent {\n entity: Entity;\n other: Entity;\n collision: CollisionInfo;\n}\n\nexport type EntityCollisionHandler = (event: EntityCollisionEvent) => void;\n\n\nexport interface EntityPositionSyncEvent {\n entity: Entity;\n x: number;\n y: number;\n}\n\nexport type EntityPositionSyncHandler = (event: EntityPositionSyncEvent) => void;\n\nexport interface EntityDirectionSyncEvent {\n entity: Entity;\n direction: Vector2;\n cardinalDirection: CardinalDirection;\n}\n\nexport type EntityDirectionSyncHandler = (event: EntityDirectionSyncEvent) => void;\n\nexport interface EntityMovementChangeEvent {\n entity: Entity;\n isMoving: boolean;\n /** Movement intensity (speed magnitude) */\n intensity: number;\n}\n\nexport type EntityMovementChangeHandler = (event: EntityMovementChangeEvent) => void;\n\nexport interface EntityTileEvent {\n entity: Entity;\n x: number;\n y: number;\n}\n\nexport type EntityTileHandler = (event: EntityTileEvent) => void;\nexport type EntityCanEnterTileHandler = (event: EntityTileEvent) => boolean;\n\n\n"],"names":["absX","absY"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index7.js","sources":["../src/physics/Entity.ts"],"sourcesContent":["import { Vector2 } from '../core/math/Vector2';\nimport { UUID, EntityState } from '../core/types';\nimport { generateUUID } from '../utils/uuid';\nimport { CollisionInfo } from '../collision/Collider';\n\nconst MOVEMENT_EPSILON = 1e-3;\nconst MOVEMENT_EPSILON_SQ = MOVEMENT_EPSILON * MOVEMENT_EPSILON;\nconst DIRECTION_CHANGE_THRESHOLD = 1.0;\nconst DIRECTION_CHANGE_THRESHOLD_SQ = DIRECTION_CHANGE_THRESHOLD * DIRECTION_CHANGE_THRESHOLD;\n\nexport type CardinalDirection = 'idle' | 'up' | 'down' | 'left' | 'right';\n\n/**\n * Collision filter function type\n * \n * A filter that determines whether a collision should be processed between two entities.\n * Return `true` to allow the collision, `false` to ignore it.\n * \n * @param self - The entity that owns this filter\n * @param other - The other entity in the collision\n * @returns `true` if collision should occur, `false` to ignore\n * \n * @example\n * ```typescript\n * // Filter that ignores collisions with entities tagged as \"ghost\"\n * const ghostFilter: CollisionFilter = (self, other) => {\n * return !(other as any).isGhost;\n * };\n * ```\n */\nexport type CollisionFilter = (self: Entity, other: Entity) => boolean;\n\n/**\n * Resolution filter function type\n * \n * A filter that determines whether a collision should be **resolved** (blocking) \n * or just **detected** (notification only).\n * \n * Unlike CollisionFilter which prevents detection entirely, ResolutionFilter\n * allows collision events to fire while optionally skipping the physical blocking.\n * \n * Return `true` to resolve the collision (entities block each other),\n * `false` to skip resolution (entities pass through but events still fire).\n * \n * @param self - The entity that owns this filter\n * @param other - The other entity in the collision\n * @returns `true` to resolve (block), `false` to skip resolution (pass through)\n * \n * @example\n * ```typescript\n * // Filter that allows passing through players but still triggers events\n * const throughPlayerFilter: ResolutionFilter = (self, other) => {\n * const otherOwner = (other as any).owner;\n * if (otherOwner?.type === 'player') {\n * return false; // Pass through but events still fire\n * }\n * return true; // Block other entities\n * };\n * ```\n */\nexport type ResolutionFilter = (self: Entity, other: Entity) => boolean;\n\n/**\n * Configuration options for creating an entity\n */\nexport interface EntityConfig {\n /** Initial position */\n position?: Vector2 | { x: number; y: number };\n /** Initial velocity */\n velocity?: Vector2 | { x: number; y: number };\n /** Initial rotation in radians */\n rotation?: number;\n /** Initial angular velocity in radians per second */\n angularVelocity?: number;\n /** Mass of the entity (0 or Infinity for static/immovable entities) */\n mass?: number;\n /** Radius for circular collider */\n radius?: number;\n /** Width for AABB collider */\n width?: number;\n /** Height for AABB collider */\n height?: number;\n /** Capsule collider configuration (if used) */\n capsule?: {\n radius: number;\n height: number;\n };\n /** Enable continuous collision detection (CCD) */\n continuous?: boolean;\n /** Entity state flags */\n state?: EntityState;\n /** Restitution (bounciness) coefficient (0-1) */\n restitution?: number;\n /** Friction coefficient */\n friction?: number;\n /** Linear damping (0-1, higher = more damping) */\n linearDamping?: number;\n /** Angular damping (0-1, higher = more damping) */\n angularDamping?: number;\n /** Maximum linear velocity */\n maxLinearVelocity?: number;\n /** Maximum angular velocity */\n maxAngularVelocity?: number;\n /** Custom UUID (auto-generated if not provided) */\n uuid?: UUID;\n /** Collision mask (bitmask for collision filtering) */\n collisionMask?: number;\n /** Collision category (bitmask) */\n collisionCategory?: number;\n}\n\n/**\n * Physical entity in the physics world\n * \n * Represents a dynamic or static object that can be affected by forces,\n * collisions, and other physical interactions.\n * \n * ## Creating Static Obstacles\n * \n * To create immovable obstacles (walls, decorations), set `mass` to `0` or `Infinity`.\n * This makes the entity static - it will block other entities but cannot be pushed.\n * \n * @example\n * ```typescript\n * // Dynamic entity (player, movable object)\n * const player = new Entity({\n * position: { x: 0, y: 0 },\n * radius: 10,\n * mass: 1,\n * velocity: { x: 5, y: 0 }\n * });\n * \n * // Static obstacle (wall, tree, decoration)\n * const wall = new Entity({\n * position: { x: 100, y: 0 },\n * width: 20,\n * height: 100,\n * mass: Infinity // or mass: 0\n * });\n * \n * player.applyForce(new Vector2(10, 0));\n * ```\n */\nexport class Entity {\n /**\n * Unique identifier (UUID)\n */\n public readonly uuid: UUID;\n\n /**\n * Position in world space\n */\n public position: Vector2;\n\n /**\n * Linear velocity\n */\n public velocity: Vector2;\n\n /**\n * Rotation in radians\n */\n public rotation: number;\n\n /**\n * Angular velocity in radians per second\n */\n public angularVelocity: number;\n\n /**\n * Mass (0 or Infinity means infinite mass / static)\n */\n public mass: number;\n\n /**\n * Inverse mass (cached for performance, 0 if mass is 0 or Infinity)\n */\n public invMass: number;\n\n /**\n * Radius for circular collider (if used)\n */\n public radius: number;\n\n /**\n * Width for AABB collider (if used)\n */\n public width: number;\n\n /**\n * Height for AABB collider (if used)\n */\n public height: number;\n\n /**\n * Capsule collider configuration (if used)\n */\n public capsule?: {\n radius: number;\n height: number;\n };\n\n /**\n * Enable continuous collision detection (CCD)\n */\n public continuous: boolean;\n\n /**\n * Entity state flags\n */\n public state: EntityState;\n\n /**\n * Restitution (bounciness) coefficient (0-1)\n */\n public restitution: number;\n\n /**\n * Friction coefficient\n */\n public friction: number;\n\n /**\n * Linear damping (0-1)\n */\n public linearDamping: number;\n\n /**\n * Angular damping (0-1)\n */\n public angularDamping: number;\n\n /**\n * Maximum linear velocity\n */\n public maxLinearVelocity: number;\n\n /**\n * Maximum angular velocity\n */\n public maxAngularVelocity: number;\n\n /**\n * Accumulated force for this frame\n */\n public force: Vector2;\n\n /**\n * Accumulated torque for this frame\n */\n public torque: number;\n\n /**\n * Collision mask (bitmask)\n */\n public collisionMask: number;\n\n /**\n * Collision category (bitmask)\n */\n public collisionCategory: number;\n\n /**\n * Time since last movement (for sleep detection)\n */\n public timeSinceMovement: number;\n\n /**\n * Threshold for sleep detection (seconds of inactivity)\n */\n public sleepThreshold: number;\n\n /**\n * Current tile coordinates (x, y)\n */\n public currentTile: Vector2;\n\n private collisionEnterHandlers: Set<EntityCollisionHandler>;\n private collisionExitHandlers: Set<EntityCollisionHandler>;\n private positionSyncHandlers: Set<EntityPositionSyncHandler>;\n private directionSyncHandlers: Set<EntityDirectionSyncHandler>;\n private movementChangeHandlers: Set<EntityMovementChangeHandler>;\n private enterTileHandlers: Set<EntityTileHandler>;\n private leaveTileHandlers: Set<EntityTileHandler>;\n private canEnterTileHandlers: Set<EntityCanEnterTileHandler>;\n private collisionFilterHandlers: Set<CollisionFilter>;\n private resolutionFilterHandlers: Set<ResolutionFilter>;\n private wasMoving: boolean;\n private lastCardinalDirection: CardinalDirection = 'idle';\n\n /**\n * Creates a new entity\n * \n * @param config - Entity configuration\n */\n constructor(config: EntityConfig = {}) {\n // Generate UUID if not provided\n this.uuid = config.uuid ?? generateUUID();\n\n // Position and velocity\n if (config.position instanceof Vector2) {\n this.position = config.position.clone();\n } else if (config.position) {\n this.position = new Vector2(config.position.x, config.position.y);\n } else {\n this.position = new Vector2(0, 0);\n }\n\n this.currentTile = new Vector2(0, 0); // Will be updated by World\n\n if (config.velocity instanceof Vector2) {\n this.velocity = config.velocity.clone();\n } else if (config.velocity) {\n this.velocity = new Vector2(config.velocity.x, config.velocity.y);\n } else {\n this.velocity = new Vector2(0, 0);\n }\n\n // Rotation\n this.rotation = config.rotation ?? 0;\n this.angularVelocity = config.angularVelocity ?? 0;\n\n // Mass\n this.mass = config.mass ?? 1;\n this.invMass = this.mass > 0 ? 1 / this.mass : 0;\n\n // Collider dimensions\n this.radius = config.radius ?? 0;\n this.width = config.width ?? 0;\n this.height = config.height ?? 0;\n if (config.capsule !== undefined) {\n this.capsule = config.capsule;\n }\n this.continuous = config.continuous ?? false;\n\n // State\n this.state = config.state ?? EntityState.Dynamic;\n\n // Material properties\n this.restitution = config.restitution ?? 0.2;\n this.friction = config.friction ?? 0.3;\n this.linearDamping = config.linearDamping ?? 0.01;\n this.angularDamping = config.angularDamping ?? 0.01;\n\n // Velocity limits\n this.maxLinearVelocity = config.maxLinearVelocity ?? Infinity;\n this.maxAngularVelocity = config.maxAngularVelocity ?? Infinity;\n\n // Forces\n this.force = new Vector2(0, 0);\n this.torque = 0;\n\n // Collision filtering\n this.collisionMask = config.collisionMask ?? 0xffffffff;\n this.collisionCategory = config.collisionCategory ?? 0x00000001;\n\n // Sleep detection\n this.timeSinceMovement = 0;\n this.sleepThreshold = 0.5; // 0.5 seconds of inactivity\n\n // Event handlers\n this.collisionEnterHandlers = new Set();\n this.collisionExitHandlers = new Set();\n this.positionSyncHandlers = new Set();\n this.directionSyncHandlers = new Set();\n this.positionSyncHandlers = new Set();\n this.directionSyncHandlers = new Set();\n this.movementChangeHandlers = new Set();\n this.enterTileHandlers = new Set();\n this.leaveTileHandlers = new Set();\n this.canEnterTileHandlers = new Set();\n this.collisionFilterHandlers = new Set();\n this.resolutionFilterHandlers = new Set();\n\n // Initialize movement state\n this.wasMoving = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ;\n }\n\n /**\n * Registers a handler fired when this entity starts colliding with another one.\n *\n * - **Purpose:** offer per-entity collision hooks without subscribing to the global event system.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n *\n * @param handler - Collision enter listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onCollisionEnter(({ other }) => {\n * console.log('Started colliding with', other.uuid);\n * });\n * ```\n */\n public onCollisionEnter(handler: EntityCollisionHandler): () => void {\n this.collisionEnterHandlers.add(handler);\n return () => this.collisionEnterHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when this entity stops colliding with another one.\n *\n * - **Purpose:** detect collision separation at the entity level for local gameplay reactions.\n * - **Design:** mirrors `onCollisionEnter` with identical lifecycle management semantics.\n *\n * @param handler - Collision exit listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onCollisionExit(({ other }) => {\n * console.log('Stopped colliding with', other.uuid);\n * });\n * ```\n */\n public onCollisionExit(handler: EntityCollisionHandler): () => void {\n this.collisionExitHandlers.add(handler);\n return () => this.collisionExitHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when the entity position changes (x, y).\n *\n * - **Purpose:** synchronize position changes for logging, rendering, network sync, etc.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n *\n * @param handler - Position change listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onPositionChange(({ x, y }) => {\n * console.log('Position changed to', x, y);\n * // Update rendering, sync network, etc.\n * });\n * ```\n */\n public onPositionChange(handler: EntityPositionSyncHandler): () => void {\n this.positionSyncHandlers.add(handler);\n return () => this.positionSyncHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when the entity direction changes.\n *\n * - **Purpose:** synchronize direction changes for logging, rendering, network sync, etc.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n *\n * @param handler - Direction change listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onDirectionChange(({ direction, cardinalDirection }) => {\n * console.log('Direction changed to', cardinalDirection);\n * // Update rendering, sync network, etc.\n * });\n * ```\n */\n public onDirectionChange(handler: EntityDirectionSyncHandler): () => void {\n this.directionSyncHandlers.add(handler);\n return () => this.directionSyncHandlers.delete(handler);\n }\n\n /**\n * Manually notifies that the position has changed.\n *\n * - **Purpose:** allow external code to trigger position sync hooks when position is modified directly.\n * - **Design:** can be called after direct position modifications (e.g., `entity.position.set()`).\n *\n * @example\n * ```typescript\n * entity.position.set(100, 200);\n * entity.notifyPositionChange(); // Trigger sync hooks\n * ```\n */\n public notifyPositionChange(): void {\n if (this.positionSyncHandlers.size === 0) {\n return;\n }\n\n const payload: EntityPositionSyncEvent = {\n entity: this,\n x: this.position.x,\n y: this.position.y,\n };\n\n for (const handler of this.positionSyncHandlers) {\n handler(payload);\n }\n }\n\n /**\n * Manually notifies that the direction has changed.\n *\n * - **Purpose:** allow external code to trigger direction sync hooks when direction is modified directly.\n * - **Design:** computes direction from velocity and cardinal direction.\n *\n * @example\n * ```typescript\n * entity.velocity.set(5, 0);\n * entity.notifyDirectionChange(); // Trigger sync hooks\n * ```\n */\n public notifyDirectionChange(): void {\n const isMoving = this.velocity.lengthSquared() > DIRECTION_CHANGE_THRESHOLD_SQ;\n const direction = isMoving ? this.velocity.clone().normalize() : new Vector2(0, 0);\n const cardinalDirection = this.computeCardinalDirection(direction);\n\n // Update state to support hysteresis\n if (cardinalDirection !== 'idle') {\n this.lastCardinalDirection = cardinalDirection;\n }\n\n if (this.directionSyncHandlers.size === 0) {\n return;\n }\n\n const payload: EntityDirectionSyncEvent = {\n entity: this,\n direction,\n cardinalDirection,\n };\n\n for (const handler of this.directionSyncHandlers) {\n handler(payload);\n }\n }\n\n /**\n * Gets the current cardinal direction.\n * \n * This value is updated whenever `notifyDirectionChange()` is called (e.g. by `setVelocity`).\n * It includes hysteresis logic to prevent rapid direction flipping during collisions.\n * \n * @returns The current cardinal direction ('up', 'down', 'left', 'right', 'idle')\n * \n * @example\n * ```typescript\n * const dir = entity.cardinalDirection;\n * if (dir === 'left') {\n * // Render left-facing sprite\n * }\n * ```\n */\n public get cardinalDirection(): CardinalDirection {\n return this.lastCardinalDirection;\n }\n\n /**\n * Registers a handler fired when the entity movement state changes (moving/stopped).\n *\n * - **Purpose:** detect when an entity starts or stops moving for gameplay reactions, animations, or network sync.\n * - **Design:** lightweight Set-based listeners returning an unsubscribe closure to keep GC pressure low.\n * - **Movement detection:** uses `MOVEMENT_EPSILON` threshold to determine if entity is moving.\n * - **Intensity:** provides the movement speed magnitude to allow fine-grained animation control (e.g., walk vs run).\n *\n * @param handler - Movement state change listener\n * @returns Unsubscribe closure\n * @example\n * ```typescript\n * const unsubscribe = entity.onMovementChange(({ isMoving, intensity }) => {\n * console.log('Entity is', isMoving ? 'moving' : 'stopped', 'at speed', intensity);\n * // Update animations based on intensity\n * if (isMoving && intensity > 100) {\n * // Fast movement - use run animation\n * } else if (isMoving) {\n * // Slow movement - use walk animation\n * }\n * });\n * ```\n */\n public onMovementChange(handler: EntityMovementChangeHandler): () => void {\n this.movementChangeHandlers.add(handler);\n return () => this.movementChangeHandlers.delete(handler);\n }\n\n /**\n * Manually notifies that the movement state has changed.\n *\n * - **Purpose:** allow external code to trigger movement state sync hooks when velocity is modified directly.\n * - **Design:** checks if movement state (moving/stopped) has changed and notifies handlers with movement intensity.\n * - **Intensity:** calculated as the magnitude of the velocity vector (speed in pixels per second).\n *\n * @example\n * ```typescript\n * entity.velocity.set(5, 0);\n * entity.notifyMovementChange(); // Trigger sync hooks if state changed\n * ```\n */\n public notifyMovementChange(): void {\n const isMoving = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ;\n const intensity = this.velocity.length(); // Movement speed magnitude\n\n if (this.movementChangeHandlers.size === 0) {\n // Still update wasMoving even if no handlers to track state correctly\n this.wasMoving = isMoving;\n return;\n }\n\n // Only notify if state actually changed\n if (isMoving !== this.wasMoving) {\n this.wasMoving = isMoving;\n\n const payload: EntityMovementChangeEvent = {\n entity: this,\n isMoving,\n intensity,\n };\n\n for (const handler of this.movementChangeHandlers) {\n handler(payload);\n }\n } else {\n // Update wasMoving even if state didn't change to keep it in sync\n this.wasMoving = isMoving;\n }\n }\n\n /**\n * Registers a handler fired when the entity enters a new tile.\n * \n * @param handler - Tile enter listener\n * @returns Unsubscribe closure\n */\n public onEnterTile(handler: EntityTileHandler): () => void {\n this.enterTileHandlers.add(handler);\n return () => this.enterTileHandlers.delete(handler);\n }\n\n /**\n * Registers a handler fired when the entity leaves a tile.\n * \n * @param handler - Tile leave listener\n * @returns Unsubscribe closure\n */\n public onLeaveTile(handler: EntityTileHandler): () => void {\n this.leaveTileHandlers.add(handler);\n return () => this.leaveTileHandlers.delete(handler);\n }\n\n /**\n * Registers a handler to check if the entity can enter a tile.\n * If any handler returns false, the entity cannot enter.\n * \n * @param handler - Can enter tile listener\n * @returns Unsubscribe closure\n */\n public canEnterTile(handler: EntityCanEnterTileHandler): () => void {\n this.canEnterTileHandlers.add(handler);\n return () => this.canEnterTileHandlers.delete(handler);\n }\n\n /**\n * @internal\n * Notifies that the entity has entered a tile.\n */\n public notifyEnterTile(x: number, y: number): void {\n if (this.enterTileHandlers.size === 0) return;\n const event: EntityTileEvent = { entity: this, x, y };\n for (const handler of this.enterTileHandlers) {\n handler(event);\n }\n }\n\n /**\n * @internal\n * Notifies that the entity has left a tile.\n */\n public notifyLeaveTile(x: number, y: number): void {\n if (this.leaveTileHandlers.size === 0) return;\n const event: EntityTileEvent = { entity: this, x, y };\n for (const handler of this.leaveTileHandlers) {\n handler(event);\n }\n }\n\n /**\n * @internal\n * Checks if the entity can enter a tile.\n */\n public checkCanEnterTile(x: number, y: number): boolean {\n if (this.canEnterTileHandlers.size === 0) return true;\n const event: EntityTileEvent = { entity: this, x, y };\n for (const handler of this.canEnterTileHandlers) {\n if (handler(event) === false) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Applies a force to the entity\n * \n * Force is accumulated and applied during integration.\n * \n * @param force - Force vector to apply\n * @returns This entity for chaining\n * \n * @example\n * ```typescript\n * entity.applyForce(new Vector2(10, 0)); // Push right\n * ```\n */\n public applyForce(force: Vector2): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n this.force.addInPlace(force);\n return this;\n }\n\n /**\n * Applies a force at a specific point (creates torque)\n * \n * @param force - Force vector to apply\n * @param point - Point of application in world space\n * @returns This entity for chaining\n */\n public applyForceAtPoint(force: Vector2, point: Vector2): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n this.force.addInPlace(force);\n\n // Calculate torque: r × F\n const r = point.sub(this.position);\n this.torque += r.cross(force);\n\n return this;\n }\n\n /**\n * Applies an impulse (instantaneous change in velocity)\n * \n * @param impulse - Impulse vector\n * @returns This entity for chaining\n * \n * @example\n * ```typescript\n * entity.applyImpulse(new Vector2(5, 0)); // Instant push\n * ```\n */\n public applyImpulse(impulse: Vector2): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n this.velocity.addInPlace(impulse.mul(this.invMass));\n this.notifyMovementChange();\n this.notifyDirectionChange();\n return this;\n }\n\n /**\n * Applies an angular impulse (instantaneous change in angular velocity)\n * \n * @param impulse - Angular impulse value\n * @returns This entity for chaining\n */\n public applyAngularImpulse(impulse: number): Entity {\n if (this.isStatic() || this.isSleeping()) {\n return this;\n }\n // Simplified: assume moment of inertia = mass * radius^2\n const momentOfInertia = this.mass * this.radius * this.radius;\n if (momentOfInertia > 0) {\n this.angularVelocity += impulse / momentOfInertia;\n }\n return this;\n }\n\n /**\n * Teleports the entity to a new position\n * \n * @param position - New position\n * @returns This entity for chaining\n */\n public teleport(position: Vector2 | { x: number; y: number }): Entity {\n if (position instanceof Vector2) {\n this.position.copyFrom(position);\n } else {\n this.position.set(position.x, position.y);\n }\n this.wakeUp();\n this.notifyPositionChange();\n return this;\n }\n\n /**\n * Sets the velocity directly\n * \n * @param velocity - New velocity\n * @returns This entity for chaining\n */\n public setVelocity(velocity: Vector2 | { x: number; y: number }): Entity {\n const oldVelocity = this.velocity.clone();\n\n if (velocity instanceof Vector2) {\n this.velocity.copyFrom(velocity);\n } else {\n this.velocity.set(velocity.x, velocity.y);\n }\n this.wakeUp();\n\n // Check if direction changed\n const oldDirection = oldVelocity.lengthSquared() > MOVEMENT_EPSILON_SQ\n ? oldVelocity.clone().normalize()\n : new Vector2(0, 0);\n const newDirection = this.velocity.lengthSquared() > MOVEMENT_EPSILON_SQ\n ? this.velocity.clone().normalize()\n : new Vector2(0, 0);\n\n const oldCardinal = this.computeCardinalDirection(oldDirection);\n const newCardinal = this.computeCardinalDirection(newDirection);\n\n if (oldCardinal !== newCardinal || Math.abs(oldDirection.dot(newDirection) - 1) > 0.01) {\n this.notifyDirectionChange();\n }\n\n // Check if movement state changed\n this.notifyMovementChange();\n\n return this;\n }\n\n /**\n * Freezes the entity (makes it static)\n * \n * @returns This entity for chaining\n */\n public freeze(): Entity {\n this.state = EntityState.Static;\n this.velocity.set(0, 0);\n this.angularVelocity = 0;\n this.force.set(0, 0);\n this.torque = 0;\n this.notifyMovementChange();\n return this;\n }\n\n /**\n * Unfreezes the entity (makes it dynamic)\n * \n * @returns This entity for chaining\n */\n public unfreeze(): Entity {\n if (this.mass > 0) {\n this.state = EntityState.Dynamic;\n }\n return this;\n }\n\n /**\n * Puts the entity to sleep (stops updating)\n * \n * @returns This entity for chaining\n */\n public sleep(): Entity {\n if (!this.isStatic()) {\n this.state |= EntityState.Sleeping;\n this.velocity.set(0, 0);\n this.angularVelocity = 0;\n this.force.set(0, 0);\n this.torque = 0;\n this.notifyMovementChange();\n }\n return this;\n }\n\n /**\n * Wakes up the entity (resumes updating)\n * \n * @returns This entity for chaining\n */\n public wakeUp(): Entity {\n this.state &= ~EntityState.Sleeping;\n this.timeSinceMovement = 0;\n return this;\n }\n\n /**\n * Checks if the entity is static\n * \n * An entity is considered static if:\n * - It has the Static state flag, OR\n * - It has infinite mass (mass = Infinity), OR\n * - It has zero inverse mass (invMass = 0)\n * \n * @returns True if static\n */\n public isStatic(): boolean {\n return (this.state & EntityState.Static) !== 0 || this.invMass === 0;\n }\n\n /**\n * Checks if the entity is dynamic\n * \n * @returns True if dynamic\n */\n public isDynamic(): boolean {\n return (this.state & EntityState.Dynamic) !== 0 && this.mass > 0;\n }\n\n /**\n * Checks if the entity is sleeping\n * \n * @returns True if sleeping\n */\n public isSleeping(): boolean {\n return (this.state & EntityState.Sleeping) !== 0;\n }\n\n /**\n * Checks if the entity is kinematic\n * \n * @returns True if kinematic\n */\n public isKinematic(): boolean {\n return (this.state & EntityState.Kinematic) !== 0;\n }\n\n /**\n * Resets accumulated forces and torques\n * \n * Called at the start of each physics step.\n */\n public clearForces(): void {\n this.force.set(0, 0);\n this.torque = 0;\n }\n\n /**\n * Stops all movement immediately\n * \n * Completely stops the entity's movement by:\n * - Setting velocity to zero\n * - Setting angular velocity to zero\n * - Clearing accumulated forces and torques\n * - Waking up the entity if it was sleeping\n * - Notifying movement state change\n * \n * Unlike `freeze()`, this method keeps the entity dynamic and does not\n * change its state. It's useful for stopping movement when changing maps,\n * teleporting, or when you need to halt an entity without making it static.\n * \n * @returns This entity for chaining\n * \n * @example\n * ```ts\n * // Stop movement when changing maps\n * if (mapChanged) {\n * entity.stopMovement();\n * }\n * \n * // Stop movement after teleporting\n * entity.position.set(100, 200);\n * entity.stopMovement();\n * \n * // Stop movement when player dies\n * if (player.isDead()) {\n * playerEntity.stopMovement();\n * }\n * ```\n */\n public stopMovement(): Entity {\n this.velocity.set(0, 0);\n this.angularVelocity = 0;\n this.clearForces();\n this.wakeUp();\n this.notifyMovementChange();\n this.notifyDirectionChange();\n return this;\n }\n\n /**\n * Clamps velocities to maximum values\n */\n public clampVelocities(): void {\n const speed = this.velocity.length();\n if (speed > this.maxLinearVelocity) {\n this.velocity.normalizeInPlace().mulInPlace(this.maxLinearVelocity);\n }\n\n if (Math.abs(this.angularVelocity) > this.maxAngularVelocity) {\n this.angularVelocity = Math.sign(this.angularVelocity) * this.maxAngularVelocity;\n }\n }\n\n /**\n * Adds a collision filter to this entity\n * \n * Collision filters allow dynamic, conditional collision filtering beyond static bitmasks.\n * Each filter is called when checking if this entity can collide with another.\n * If any filter returns `false`, the collision is ignored.\n * \n * This enables scenarios like:\n * - Players passing through other players (`throughOtherPlayer`)\n * - Entities passing through all characters (`through`)\n * - Custom game-specific collision rules\n * \n * @param filter - Function that returns `true` to allow collision, `false` to ignore\n * @returns Unsubscribe function to remove the filter\n * \n * @example\n * ```typescript\n * // Allow entity to pass through other players\n * const unsubscribe = entity.addCollisionFilter((self, other) => {\n * const otherOwner = (other as any).owner;\n * if (otherOwner?.type === 'player') {\n * return false; // No collision with players\n * }\n * return true; // Collide with everything else\n * });\n * \n * // Later, remove the filter\n * unsubscribe();\n * ```\n */\n public addCollisionFilter(filter: CollisionFilter): () => void {\n this.collisionFilterHandlers.add(filter);\n return () => this.collisionFilterHandlers.delete(filter);\n }\n\n /**\n * Checks if this entity can collide with another entity\n * \n * First checks collision masks (bitmask filtering), then executes all registered\n * collision filters. If any filter returns `false`, the collision is ignored.\n * \n * @param other - Other entity to check\n * @returns True if collision is possible\n */\n public canCollideWith(other: Entity): boolean {\n // Check collision masks first (fast path)\n const categoryA = this.collisionCategory;\n const maskA = this.collisionMask;\n const categoryB = other.collisionCategory;\n const maskB = other.collisionMask;\n\n if ((categoryA & maskB) === 0 || (categoryB & maskA) === 0) {\n return false;\n }\n\n // Check collision filters on this entity\n for (const filter of this.collisionFilterHandlers) {\n if (!filter(this, other)) {\n return false;\n }\n }\n\n // Check collision filters on the other entity\n for (const filter of other.collisionFilterHandlers) {\n if (!filter(other, this)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Adds a resolution filter to this entity\n * \n * Resolution filters determine whether a collision should be **resolved** (blocking)\n * or just **detected** (notification only). Unlike collision filters which prevent\n * detection entirely, resolution filters allow collision events to fire while\n * optionally skipping the physical blocking.\n * \n * This enables scenarios like:\n * - Players passing through other players but still triggering touch events\n * - Entities passing through characters but still calling onPlayerTouch hooks\n * - Ghost mode where collisions are detected but not resolved\n * \n * @param filter - Function that returns `true` to resolve (block), `false` to skip\n * @returns Unsubscribe function to remove the filter\n * \n * @example\n * ```typescript\n * // Allow entity to pass through players but still trigger events\n * const unsubscribe = entity.addResolutionFilter((self, other) => {\n * const otherOwner = (other as any).owner;\n * if (otherOwner?.type === 'player') {\n * return false; // Pass through but events still fire\n * }\n * return true; // Block other entities\n * });\n * \n * // Later, remove the filter\n * unsubscribe();\n * ```\n */\n public addResolutionFilter(filter: ResolutionFilter): () => void {\n this.resolutionFilterHandlers.add(filter);\n return () => this.resolutionFilterHandlers.delete(filter);\n }\n\n /**\n * Checks if this entity should resolve (block) a collision with another entity\n * \n * This is called by the CollisionResolver to determine if the collision should\n * result in physical blocking or just notification.\n * \n * @param other - Other entity to check\n * @returns True if collision should be resolved (blocking), false to pass through\n */\n public shouldResolveCollisionWith(other: Entity): boolean {\n // Check resolution filters on this entity\n for (const filter of this.resolutionFilterHandlers) {\n if (!filter(this, other)) {\n return false;\n }\n }\n\n // Check resolution filters on the other entity\n for (const filter of other.resolutionFilterHandlers) {\n if (!filter(other, this)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * @internal\n *\n * Notifies the entity that a collision has started.\n *\n * @param collision - Collision information shared by the world\n * @param other - The counterpart entity\n */\n public notifyCollisionEnter(collision: CollisionInfo, other: Entity): void {\n if (this.collisionEnterHandlers.size === 0) {\n return;\n }\n\n const payload: EntityCollisionEvent = {\n entity: this,\n other,\n collision,\n };\n\n for (const handler of this.collisionEnterHandlers) {\n handler(payload);\n }\n }\n\n /**\n * @internal\n *\n * Notifies the entity that a collision has ended.\n *\n * @param collision - Collision information stored before separation\n * @param other - The counterpart entity\n */\n public notifyCollisionExit(collision: CollisionInfo, other: Entity): void {\n if (this.collisionExitHandlers.size === 0) {\n return;\n }\n\n const payload: EntityCollisionEvent = {\n entity: this,\n other,\n collision,\n };\n\n for (const handler of this.collisionExitHandlers) {\n handler(payload);\n }\n }\n\n\n private computeCardinalDirection(direction: Vector2): CardinalDirection {\n if (direction.lengthSquared() <= MOVEMENT_EPSILON_SQ) {\n return 'idle';\n }\n\n // If we were idle, just return the strongest direction without bias\n if (this.lastCardinalDirection === 'idle') {\n const absX = Math.abs(direction.x);\n const absY = Math.abs(direction.y);\n if (absX >= absY) {\n return direction.x >= 0 ? 'right' : 'left';\n }\n return direction.y >= 0 ? 'down' : 'up';\n }\n\n // Check for 180-degree flips (Bounce protection)\n // If the new direction is strictly opposite to the last one, we require a higher velocity\n // to accept the change. This filters out collision rebounds.\n const isOpposite =\n (this.lastCardinalDirection === 'left' && direction.x > 0.5) ||\n (this.lastCardinalDirection === 'right' && direction.x < -0.5) ||\n (this.lastCardinalDirection === 'up' && direction.y > 0.5) ||\n (this.lastCardinalDirection === 'down' && direction.y < -0.5);\n\n const speedSq = this.velocity.lengthSquared();\n\n // Threshold to accept a 180-degree turn (avoid jitter on bounce)\n // We expect a \"real\" turn to have some acceleration or accumulated velocity\n if (isOpposite && speedSq < 100.0) { // Speed < 10\n return this.lastCardinalDirection;\n }\n\n const absX = Math.abs(direction.x);\n const absY = Math.abs(direction.y);\n const bias = 2.0; // Strong bias to keep current direction\n\n // Hysteresis: favor current axis if we have a valid last direction\n if (['left', 'right'].includes(this.lastCardinalDirection)) {\n // Currently horizontal: stick to it unless vertical is significantly stronger\n // AND vertical component has meaningful speed (prevents slide when blocked)\n if (absY > absX * bias) {\n // Check if the \"new\" vertical movement is actually significant\n // e.g. if we are blocked Horizontally (x=0), absY will win even if it's 0.0001 without this check\n if (Math.abs(this.velocity.y) > 5.0) {\n return direction.y >= 0 ? 'down' : 'up';\n }\n }\n // Default: keep horizontal orientation, just update sign if needed (and not filtered by opposite check)\n // If we are here, it means we didn't switch axis, and we didn't trigger the \"Opposite\" guard above.\n // However, if we are \"blocked\" (velocity very low), we should probably not even flip sign.\n if (speedSq > 1.0) {\n return direction.x >= 0 ? 'right' : 'left';\n }\n return this.lastCardinalDirection;\n } else {\n // Currently vertical: stick to it unless horizontal is significantly stronger\n if (absX > absY * bias) {\n if (Math.abs(this.velocity.x) > 5.0) {\n return direction.x >= 0 ? 'right' : 'left';\n }\n }\n if (speedSq > 1.0) {\n return direction.y >= 0 ? 'down' : 'up';\n }\n return this.lastCardinalDirection;\n }\n }\n\n /**\n * Creates a copy of this entity\n * \n * @returns New entity with copied properties\n */\n public clone(): Entity {\n const entity = new Entity({\n position: this.position.clone(),\n velocity: this.velocity.clone(),\n rotation: this.rotation,\n angularVelocity: this.angularVelocity,\n mass: this.mass,\n radius: this.radius,\n width: this.width,\n height: this.height,\n state: this.state,\n restitution: this.restitution,\n friction: this.friction,\n linearDamping: this.linearDamping,\n angularDamping: this.angularDamping,\n maxLinearVelocity: this.maxLinearVelocity,\n maxAngularVelocity: this.maxAngularVelocity,\n collisionMask: this.collisionMask,\n collisionCategory: this.collisionCategory,\n uuid: this.uuid,\n });\n return entity;\n }\n}\n\nexport interface EntityCollisionEvent {\n entity: Entity;\n other: Entity;\n collision: CollisionInfo;\n}\n\nexport type EntityCollisionHandler = (event: EntityCollisionEvent) => void;\n\n\nexport interface EntityPositionSyncEvent {\n entity: Entity;\n x: number;\n y: number;\n}\n\nexport type EntityPositionSyncHandler = (event: EntityPositionSyncEvent) => void;\n\nexport interface EntityDirectionSyncEvent {\n entity: Entity;\n direction: Vector2;\n cardinalDirection: CardinalDirection;\n}\n\nexport type EntityDirectionSyncHandler = (event: EntityDirectionSyncEvent) => void;\n\nexport interface EntityMovementChangeEvent {\n entity: Entity;\n isMoving: boolean;\n /** Movement intensity (speed magnitude) */\n intensity: number;\n}\n\nexport type EntityMovementChangeHandler = (event: EntityMovementChangeEvent) => void;\n\nexport interface EntityTileEvent {\n entity: Entity;\n x: number;\n y: number;\n}\n\nexport type EntityTileHandler = (event: EntityTileEvent) => void;\nexport type EntityCanEnterTileHandler = (event: EntityTileEvent) => boolean;\n\n\n"],"names":["absX","absY"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PhysicsEngine } from '../api/PhysicsEngine';
|
|
2
2
|
import { Entity } from '../physics/Entity';
|
|
3
|
-
import { MovementBody, MovementStrategy } from './MovementStrategy';
|
|
3
|
+
import { MovementBody, MovementStrategy, MovementOptions } from './MovementStrategy';
|
|
4
4
|
/**
|
|
5
5
|
* Resolves an entity from an identifier.
|
|
6
6
|
*
|
|
@@ -45,13 +45,43 @@ export declare class MovementManager {
|
|
|
45
45
|
/**
|
|
46
46
|
* Adds a movement strategy to an entity.
|
|
47
47
|
*
|
|
48
|
+
* Returns a Promise that resolves when the movement completes (when `isFinished()` returns true).
|
|
49
|
+
* If the strategy doesn't implement `isFinished()`, the Promise resolves immediately after adding.
|
|
50
|
+
*
|
|
48
51
|
* @param target - Entity instance or entity UUID when a resolver is configured
|
|
49
52
|
* @param strategy - Strategy to execute
|
|
53
|
+
* @param options - Optional callbacks for movement lifecycle events
|
|
54
|
+
* @returns Promise that resolves when the movement completes
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Simple usage - fire and forget
|
|
59
|
+
* manager.add(player, new Dash(8, { x: 1, y: 0 }, 200));
|
|
60
|
+
*
|
|
61
|
+
* // Wait for completion
|
|
62
|
+
* await manager.add(player, new Dash(8, { x: 1, y: 0 }, 200));
|
|
63
|
+
* console.log('Dash finished!');
|
|
64
|
+
*
|
|
65
|
+
* // With callbacks
|
|
66
|
+
* await manager.add(player, new Knockback({ x: -1, y: 0 }, 5, 300), {
|
|
67
|
+
* onStart: () => {
|
|
68
|
+
* player.directionFixed = true;
|
|
69
|
+
* player.animationFixed = true;
|
|
70
|
+
* },
|
|
71
|
+
* onComplete: () => {
|
|
72
|
+
* player.directionFixed = false;
|
|
73
|
+
* player.animationFixed = false;
|
|
74
|
+
* }
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
50
77
|
*/
|
|
51
|
-
add(target: Entity | MovementBody | string, strategy: MovementStrategy): void
|
|
78
|
+
add(target: Entity | MovementBody | string, strategy: MovementStrategy, options?: MovementOptions): Promise<void>;
|
|
52
79
|
/**
|
|
53
80
|
* Removes a specific strategy from an entity.
|
|
54
81
|
*
|
|
82
|
+
* Note: This will NOT trigger the onComplete callback or resolve the Promise.
|
|
83
|
+
* Use this when you want to cancel a movement without completion.
|
|
84
|
+
*
|
|
55
85
|
* @param target - Entity instance or identifier
|
|
56
86
|
* @param strategy - Strategy instance to remove
|
|
57
87
|
* @returns True when the strategy has been removed
|
|
@@ -115,6 +145,14 @@ export declare class MovementManager {
|
|
|
115
145
|
* Call this method once per frame before `PhysicsEngine.step()` so that the
|
|
116
146
|
* physics simulation integrates the velocities that strategies configure.
|
|
117
147
|
*
|
|
148
|
+
* This method handles the movement lifecycle:
|
|
149
|
+
* - Triggers `onStart` callback on first update
|
|
150
|
+
* - Calls `strategy.update()` each frame
|
|
151
|
+
* - When `isFinished()` returns true:
|
|
152
|
+
* - Calls `strategy.onFinished()` if defined
|
|
153
|
+
* - Triggers `onComplete` callback
|
|
154
|
+
* - Resolves the Promise returned by `add()`
|
|
155
|
+
*
|
|
118
156
|
* @param dt - Time delta in seconds
|
|
119
157
|
*/
|
|
120
158
|
update(dt: number): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MovementManager.d.ts","sourceRoot":"","sources":["../../src/movement/MovementManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"MovementManager.d.ts","sourceRoot":"","sources":["../../src/movement/MovementManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,CAAC;AAiBtE;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAH3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyC;IACjE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6C;gBAE/C,aAAa,CAAC,EAAE,cAAc,YAAA;IAE3D;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe;IASxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BjH;;;;;;;;;OASG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO;IAmBnF;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,IAAI;IAKnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,IAAI;IAe1D;;;;;OAKG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,OAAO;IAKpE;;;;;OAKG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,gBAAgB,EAAE;IAMzE;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IA+CxB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAIhB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,cAAc;CASvB"}
|
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { Entity } from '../physics/Entity';
|
|
2
|
+
/**
|
|
3
|
+
* Options for movement strategies
|
|
4
|
+
*
|
|
5
|
+
* Allows configuring callbacks that are triggered during the movement lifecycle.
|
|
6
|
+
* These options can be passed when adding a strategy to the MovementManager.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Add a dash with callbacks
|
|
11
|
+
* const promise = manager.add(player, new Dash(8, { x: 1, y: 0 }, 200), {
|
|
12
|
+
* onStart: () => {
|
|
13
|
+
* player.directionFixed = true;
|
|
14
|
+
* player.animationFixed = true;
|
|
15
|
+
* },
|
|
16
|
+
* onComplete: () => {
|
|
17
|
+
* player.directionFixed = false;
|
|
18
|
+
* player.animationFixed = false;
|
|
19
|
+
* }
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* // Wait for completion
|
|
23
|
+
* await promise;
|
|
24
|
+
* console.log('Dash completed!');
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface MovementOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Callback executed when the movement starts (first update call)
|
|
30
|
+
*/
|
|
31
|
+
onStart?: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Callback executed when the movement completes (isFinished returns true)
|
|
34
|
+
*/
|
|
35
|
+
onComplete?: () => void;
|
|
36
|
+
}
|
|
2
37
|
/**
|
|
3
38
|
* Minimal interface implemented by elements that can receive movement updates.
|
|
4
39
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MovementStrategy.d.ts","sourceRoot":"","sources":["../../src/movement/MovementStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,QAAQ,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,uBAAuB;IACvB,QAAQ,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,6BAA6B;IAC7B,WAAW,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,kCAAkC;IAClC,SAAS,CAAC,CAAC,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,iCAAiC;IACjC,QAAQ,CAAC,IAAI,OAAO,CAAC;IACrB,yDAAyD;IACzD,SAAS,CAAC,IAAI,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7C;;;OAGG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB"}
|
|
1
|
+
{"version":3,"file":"MovementStrategy.d.ts","sourceRoot":"","sources":["../../src/movement/MovementStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,QAAQ,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,uBAAuB;IACvB,QAAQ,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,6BAA6B;IAC7B,WAAW,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,kCAAkC;IAClC,SAAS,CAAC,CAAC,KAAK,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,iCAAiC;IACjC,QAAQ,CAAC,IAAI,OAAO,CAAC;IACrB,yDAAyD;IACzD,SAAS,CAAC,IAAI,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7C;;;OAGG;IACH,UAAU,CAAC,IAAI,OAAO,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SeekAvoid.d.ts","sourceRoot":"","sources":["../../../src/movement/strategies/SeekAvoid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGxD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAIrE;;;;;;;;;;;GAWG;AACH,qBAAa,SAAU,YAAW,gBAAgB;IAa9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,aAAa;IAhBvB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAAS;IAE/B;;;;;;;OAOG;gBAEgB,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,EACxD,QAAQ,SAAM,EACd,aAAa,SAAI,EACjB,aAAa,SAAI,EACzB,YAAY,SAAM;IAMpB,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"SeekAvoid.d.ts","sourceRoot":"","sources":["../../../src/movement/strategies/SeekAvoid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGxD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAIrE;;;;;;;;;;;GAWG;AACH,qBAAa,SAAU,YAAW,gBAAgB;IAa9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,aAAa;IAhBvB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,cAAc,CAAS;IAE/B;;;;;;;OAOG;gBAEgB,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,EACxD,QAAQ,SAAM,EACd,aAAa,SAAI,EACjB,aAAa,SAAI,EACzB,YAAY,SAAM;IAMpB,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IA4E7C,aAAa,CACX,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI;CAeR"}
|
package/dist/physics/Entity.d.ts
CHANGED
|
@@ -2,6 +2,54 @@ import { Vector2 } from '../core/math/Vector2';
|
|
|
2
2
|
import { UUID, EntityState } from '../core/types';
|
|
3
3
|
import { CollisionInfo } from '../collision/Collider';
|
|
4
4
|
export type CardinalDirection = 'idle' | 'up' | 'down' | 'left' | 'right';
|
|
5
|
+
/**
|
|
6
|
+
* Collision filter function type
|
|
7
|
+
*
|
|
8
|
+
* A filter that determines whether a collision should be processed between two entities.
|
|
9
|
+
* Return `true` to allow the collision, `false` to ignore it.
|
|
10
|
+
*
|
|
11
|
+
* @param self - The entity that owns this filter
|
|
12
|
+
* @param other - The other entity in the collision
|
|
13
|
+
* @returns `true` if collision should occur, `false` to ignore
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Filter that ignores collisions with entities tagged as "ghost"
|
|
18
|
+
* const ghostFilter: CollisionFilter = (self, other) => {
|
|
19
|
+
* return !(other as any).isGhost;
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export type CollisionFilter = (self: Entity, other: Entity) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Resolution filter function type
|
|
26
|
+
*
|
|
27
|
+
* A filter that determines whether a collision should be **resolved** (blocking)
|
|
28
|
+
* or just **detected** (notification only).
|
|
29
|
+
*
|
|
30
|
+
* Unlike CollisionFilter which prevents detection entirely, ResolutionFilter
|
|
31
|
+
* allows collision events to fire while optionally skipping the physical blocking.
|
|
32
|
+
*
|
|
33
|
+
* Return `true` to resolve the collision (entities block each other),
|
|
34
|
+
* `false` to skip resolution (entities pass through but events still fire).
|
|
35
|
+
*
|
|
36
|
+
* @param self - The entity that owns this filter
|
|
37
|
+
* @param other - The other entity in the collision
|
|
38
|
+
* @returns `true` to resolve (block), `false` to skip resolution (pass through)
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // Filter that allows passing through players but still triggers events
|
|
43
|
+
* const throughPlayerFilter: ResolutionFilter = (self, other) => {
|
|
44
|
+
* const otherOwner = (other as any).owner;
|
|
45
|
+
* if (otherOwner?.type === 'player') {
|
|
46
|
+
* return false; // Pass through but events still fire
|
|
47
|
+
* }
|
|
48
|
+
* return true; // Block other entities
|
|
49
|
+
* };
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export type ResolutionFilter = (self: Entity, other: Entity) => boolean;
|
|
5
53
|
/**
|
|
6
54
|
* Configuration options for creating an entity
|
|
7
55
|
*/
|
|
@@ -204,6 +252,8 @@ export declare class Entity {
|
|
|
204
252
|
private enterTileHandlers;
|
|
205
253
|
private leaveTileHandlers;
|
|
206
254
|
private canEnterTileHandlers;
|
|
255
|
+
private collisionFilterHandlers;
|
|
256
|
+
private resolutionFilterHandlers;
|
|
207
257
|
private wasMoving;
|
|
208
258
|
private lastCardinalDirection;
|
|
209
259
|
/**
|
|
@@ -554,13 +604,89 @@ export declare class Entity {
|
|
|
554
604
|
* Clamps velocities to maximum values
|
|
555
605
|
*/
|
|
556
606
|
clampVelocities(): void;
|
|
607
|
+
/**
|
|
608
|
+
* Adds a collision filter to this entity
|
|
609
|
+
*
|
|
610
|
+
* Collision filters allow dynamic, conditional collision filtering beyond static bitmasks.
|
|
611
|
+
* Each filter is called when checking if this entity can collide with another.
|
|
612
|
+
* If any filter returns `false`, the collision is ignored.
|
|
613
|
+
*
|
|
614
|
+
* This enables scenarios like:
|
|
615
|
+
* - Players passing through other players (`throughOtherPlayer`)
|
|
616
|
+
* - Entities passing through all characters (`through`)
|
|
617
|
+
* - Custom game-specific collision rules
|
|
618
|
+
*
|
|
619
|
+
* @param filter - Function that returns `true` to allow collision, `false` to ignore
|
|
620
|
+
* @returns Unsubscribe function to remove the filter
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```typescript
|
|
624
|
+
* // Allow entity to pass through other players
|
|
625
|
+
* const unsubscribe = entity.addCollisionFilter((self, other) => {
|
|
626
|
+
* const otherOwner = (other as any).owner;
|
|
627
|
+
* if (otherOwner?.type === 'player') {
|
|
628
|
+
* return false; // No collision with players
|
|
629
|
+
* }
|
|
630
|
+
* return true; // Collide with everything else
|
|
631
|
+
* });
|
|
632
|
+
*
|
|
633
|
+
* // Later, remove the filter
|
|
634
|
+
* unsubscribe();
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
addCollisionFilter(filter: CollisionFilter): () => void;
|
|
557
638
|
/**
|
|
558
639
|
* Checks if this entity can collide with another entity
|
|
559
640
|
*
|
|
641
|
+
* First checks collision masks (bitmask filtering), then executes all registered
|
|
642
|
+
* collision filters. If any filter returns `false`, the collision is ignored.
|
|
643
|
+
*
|
|
560
644
|
* @param other - Other entity to check
|
|
561
645
|
* @returns True if collision is possible
|
|
562
646
|
*/
|
|
563
647
|
canCollideWith(other: Entity): boolean;
|
|
648
|
+
/**
|
|
649
|
+
* Adds a resolution filter to this entity
|
|
650
|
+
*
|
|
651
|
+
* Resolution filters determine whether a collision should be **resolved** (blocking)
|
|
652
|
+
* or just **detected** (notification only). Unlike collision filters which prevent
|
|
653
|
+
* detection entirely, resolution filters allow collision events to fire while
|
|
654
|
+
* optionally skipping the physical blocking.
|
|
655
|
+
*
|
|
656
|
+
* This enables scenarios like:
|
|
657
|
+
* - Players passing through other players but still triggering touch events
|
|
658
|
+
* - Entities passing through characters but still calling onPlayerTouch hooks
|
|
659
|
+
* - Ghost mode where collisions are detected but not resolved
|
|
660
|
+
*
|
|
661
|
+
* @param filter - Function that returns `true` to resolve (block), `false` to skip
|
|
662
|
+
* @returns Unsubscribe function to remove the filter
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* // Allow entity to pass through players but still trigger events
|
|
667
|
+
* const unsubscribe = entity.addResolutionFilter((self, other) => {
|
|
668
|
+
* const otherOwner = (other as any).owner;
|
|
669
|
+
* if (otherOwner?.type === 'player') {
|
|
670
|
+
* return false; // Pass through but events still fire
|
|
671
|
+
* }
|
|
672
|
+
* return true; // Block other entities
|
|
673
|
+
* });
|
|
674
|
+
*
|
|
675
|
+
* // Later, remove the filter
|
|
676
|
+
* unsubscribe();
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
addResolutionFilter(filter: ResolutionFilter): () => void;
|
|
680
|
+
/**
|
|
681
|
+
* Checks if this entity should resolve (block) a collision with another entity
|
|
682
|
+
*
|
|
683
|
+
* This is called by the CollisionResolver to determine if the collision should
|
|
684
|
+
* result in physical blocking or just notification.
|
|
685
|
+
*
|
|
686
|
+
* @param other - Other entity to check
|
|
687
|
+
* @returns True if collision should be resolved (blocking), false to pass through
|
|
688
|
+
*/
|
|
689
|
+
shouldResolveCollisionWith(other: Entity): boolean;
|
|
564
690
|
/**
|
|
565
691
|
* @internal
|
|
566
692
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Entity.d.ts","sourceRoot":"","sources":["../../src/physics/Entity.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Entity.d.ts","sourceRoot":"","sources":["../../src/physics/Entity.ts"],"names":[],"mappings":"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"}
|