@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 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;;;;;;OAMG;IACI,OAAO,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI;IAe9C;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,gBAAgB;IAiCxB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAyEzB;;;;OAIG;IACI,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,IAAI;CAKrD"}
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';
@@ -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
  }
@@ -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":"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"}
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":"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;AAAA;AAAA;AAAA,EAYO,QAAQ,WAAgC;AAC7C,UAAM,EAAE,SAAS,SAAS,QAAQ,UAAU;AAG5C,QAAI,QAAQ,KAAK,OAAO,qBAAqB;AAC3C;AAAA,IACF;AAIA,QAAI,CAAC,QAAQ,2BAA2B,OAAO,GAAG;AAChD;AAAA,IACF;AAGA,SAAK,iBAAiB,SAAS,SAAS,QAAQ,KAAK;AAGrD,SAAK,kBAAkB,SAAS,SAAS,MAAM;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,iBACN,SACA,SACA,QACA,OACM;AACN,UAAM,eAAe,QAAQ,UAAU,QAAQ;AAC/C,QAAI,iBAAiB,GAAG;AACtB;AAAA,IACF;AAGA,UAAM,aAAa,KAAK;AAAA,MACtB,QAAQ,KAAK,OAAO;AAAA,MACpB,KAAK,OAAO;AAAA,IAAA;AAId,UAAM,cAAc,OAAO,IAAI,CAAC,cAAc,QAAQ,UAAU,aAAa;AAC7E,UAAM,cAAc,OAAO,IAAI,cAAc,QAAQ,UAAU,aAAa;AAI5E,QAAI,CAAC,QAAQ,YAAY;AACvB,cAAQ,SAAS,WAAW,WAAW;AACvC,cAAQ,qBAAA;AAAA,IACV;AACA,QAAI,CAAC,QAAQ,YAAY;AACvB,cAAQ,SAAS,WAAW,WAAW;AACvC,cAAQ,qBAAA;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,kBACN,SACA,SACA,QACM;AAEN,UAAM,mBAAmB,QAAQ,SAAS,IAAI,QAAQ,QAAQ;AAC9D,UAAM,sBAAsB,iBAAiB,IAAI,MAAM;AAGvD,QAAI,sBAAsB,GAAG;AAC3B;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,IAAI,QAAQ,aAAa,QAAQ,WAAW;AAGrE,UAAM,eAAe,QAAQ,UAAU,QAAQ;AAC/C,QAAI,iBAAiB,GAAG;AACtB;AAAA,IACF;AAGA,UAAM,gBAAgB,EAAE,IAAI,eAAe,sBAAsB;AACjE,UAAM,UAAU,OAAO,IAAI,aAAa;AAGxC,QAAI,CAAC,QAAQ,YAAY;AACvB,cAAQ,SAAS,WAAW,QAAQ,IAAI,CAAC,QAAQ,OAAO,CAAC;AACzD,cAAQ,qBAAA;AAAA,IAIV;AACA,QAAI,CAAC,QAAQ,YAAY;AACvB,cAAQ,SAAS,WAAW,QAAQ,IAAI,QAAQ,OAAO,CAAC;AACxD,cAAQ,qBAAA;AAAA,IAIV;AAGA,UAAM,UAAU,iBAAiB,IAAI,OAAO,IAAI,mBAAmB,CAAC;AACpE,UAAM,gBAAgB,QAAQ,OAAA;AAE9B,QAAI,gBAAgB,MAAM;AACxB,YAAM,WAAW,KAAK,KAAK,QAAQ,WAAW,QAAQ,QAAQ;AAC9D,YAAM,oBAAoB,QAAQ,UAAA;AAClC,YAAM,kBAAkB,kBAAkB,IAAI,CAAC,gBAAgB,WAAW,YAAY;AAGtF,YAAM,cAAc,KAAK,IAAI,gBAAgB,QAAQ;AACrD,UAAI,gBAAgB,OAAA,IAAW,aAAa;AAC1C,wBAAgB,iBAAA,EAAmB,WAAW,WAAW;AAAA,MAC3D;AAEA,UAAI,CAAC,QAAQ,YAAY;AACvB,gBAAQ,SAAS,WAAW,gBAAgB,IAAI,CAAC,QAAQ,OAAO,CAAC;AACjE,gBAAQ,qBAAA;AAAA,MAGV;AACA,UAAI,CAAC,QAAQ,YAAY;AACvB,gBAAQ,SAAS,WAAW,gBAAgB,IAAI,QAAQ,OAAO,CAAC;AAChE,gBAAQ,qBAAA;AAAA,MAGV;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,WAAW,YAAmC;AACnD,eAAW,aAAa,YAAY;AAClC,WAAK,QAAQ,SAAS;AAAA,IACxB;AAAA,EACF;AACF;"}
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
- this.entries.get(key).strategies.push(strategy);
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.indexOf(strategy);
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 ? [...entry.strategies] : [];
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 current = strategies[i];
148
- if (!current) {
199
+ const strategyEntry = strategies[i];
200
+ if (!strategyEntry) {
149
201
  continue;
150
202
  }
151
- current.update(body, dt);
152
- if (current.isFinished?.()) {
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
- current.onFinished?.();
212
+ strategy.onFinished?.();
213
+ options?.onComplete?.();
214
+ resolve?.();
155
215
  }
156
216
  }
157
217
  if (strategies.length === 0) {
@@ -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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"}
@@ -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":"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"}
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
- return (categoryA & maskB) !== 0 && (categoryB & maskA) !== 0;
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
@@ -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;AAEpE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,YAAY,GAAG,SAAS,CAAC;AAOtE;;;;;;;;;;;;;;;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;;;;;OAKG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAW7E;;;;;;OAMG;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;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IA6BxB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAIhB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,cAAc;CASvB"}
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;IA0E7C,aAAa,CACX,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI;CAeR"}
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"}
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpgjs/physic",
3
- "version": "5.0.0-alpha.26",
3
+ "version": "5.0.0-alpha.28",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",