@rpgjs/common 4.3.0 → 5.0.0-alpha.1

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.
Files changed (148) hide show
  1. package/dist/Physic.d.ts +619 -0
  2. package/dist/Player.d.ts +198 -0
  3. package/{lib → dist}/Utils.d.ts +19 -2
  4. package/dist/database/Item.d.ts +10 -0
  5. package/dist/database/index.d.ts +1 -0
  6. package/dist/index.d.ts +9 -0
  7. package/dist/index.js +16741 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/modules.d.ts +92 -0
  10. package/dist/movement/MovementManager.d.ts +84 -0
  11. package/dist/movement/MovementStrategy.d.ts +39 -0
  12. package/dist/movement/index.d.ts +12 -0
  13. package/dist/movement/strategies/CompositeMovement.d.ts +76 -0
  14. package/dist/movement/strategies/Dash.d.ts +52 -0
  15. package/dist/movement/strategies/IceMovement.d.ts +87 -0
  16. package/dist/movement/strategies/Knockback.d.ts +50 -0
  17. package/dist/movement/strategies/LinearMove.d.ts +43 -0
  18. package/dist/movement/strategies/LinearRepulsion.d.ts +55 -0
  19. package/dist/movement/strategies/Oscillate.d.ts +60 -0
  20. package/dist/movement/strategies/PathFollow.d.ts +78 -0
  21. package/dist/movement/strategies/ProjectileMovement.d.ts +138 -0
  22. package/dist/movement/strategies/SeekAvoid.d.ts +27 -0
  23. package/dist/rooms/Map.d.ts +109 -0
  24. package/dist/services/updateMap.d.ts +7 -0
  25. package/package.json +17 -17
  26. package/src/Physic.ts +1644 -0
  27. package/src/Player.ts +262 -26
  28. package/src/{gui/PrebuiltGui.ts → PrebuiltGui.ts} +1 -1
  29. package/src/Utils.ts +184 -123
  30. package/src/database/Item.ts +19 -0
  31. package/src/database/index.ts +1 -0
  32. package/src/index.ts +9 -25
  33. package/src/modules.ts +230 -0
  34. package/src/movement/MovementManager.ts +142 -0
  35. package/src/movement/MovementStrategy.ts +42 -0
  36. package/src/movement/index.ts +15 -0
  37. package/src/movement/strategies/CompositeMovement.ts +173 -0
  38. package/src/movement/strategies/Dash.ts +82 -0
  39. package/src/movement/strategies/IceMovement.ts +158 -0
  40. package/src/movement/strategies/Knockback.ts +81 -0
  41. package/src/movement/strategies/LinearMove.ts +58 -0
  42. package/src/movement/strategies/LinearRepulsion.ts +128 -0
  43. package/src/movement/strategies/Oscillate.ts +144 -0
  44. package/src/movement/strategies/PathFollow.ts +156 -0
  45. package/src/movement/strategies/ProjectileMovement.ts +322 -0
  46. package/src/movement/strategies/SeekAvoid.ts +123 -0
  47. package/src/rooms/Map.ts +272 -0
  48. package/src/services/updateMap.ts +9 -0
  49. package/tests/physic.spec.ts +454 -0
  50. package/tsconfig.json +8 -3
  51. package/vite.config.ts +21 -0
  52. package/CHANGELOG.md +0 -160
  53. package/LICENSE +0 -19
  54. package/browser/manifest.json +0 -7
  55. package/browser/rpg.common.js +0 -11357
  56. package/browser/rpg.common.umd.cjs +0 -11358
  57. package/lib/AbstractObject.d.ts +0 -322
  58. package/lib/AbstractObject.js +0 -872
  59. package/lib/AbstractObject.js.map +0 -1
  60. package/lib/Color.d.ts +0 -1
  61. package/lib/Color.js +0 -25
  62. package/lib/Color.js.map +0 -1
  63. package/lib/DefaultInput.d.ts +0 -2
  64. package/lib/DefaultInput.js +0 -26
  65. package/lib/DefaultInput.js.map +0 -1
  66. package/lib/Event.d.ts +0 -3
  67. package/lib/Event.js +0 -4
  68. package/lib/Event.js.map +0 -1
  69. package/lib/EventEmitter.d.ts +0 -10
  70. package/lib/EventEmitter.js +0 -61
  71. package/lib/EventEmitter.js.map +0 -1
  72. package/lib/Game.d.ts +0 -28
  73. package/lib/Game.js +0 -127
  74. package/lib/Game.js.map +0 -1
  75. package/lib/Hit.d.ts +0 -16
  76. package/lib/Hit.js +0 -65
  77. package/lib/Hit.js.map +0 -1
  78. package/lib/Inject.d.ts +0 -9
  79. package/lib/Inject.js +0 -17
  80. package/lib/Inject.js.map +0 -1
  81. package/lib/Logger.d.ts +0 -2
  82. package/lib/Logger.js +0 -7
  83. package/lib/Logger.js.map +0 -1
  84. package/lib/Map.d.ts +0 -174
  85. package/lib/Map.js +0 -263
  86. package/lib/Map.js.map +0 -1
  87. package/lib/Module.d.ts +0 -16
  88. package/lib/Module.js +0 -139
  89. package/lib/Module.js.map +0 -1
  90. package/lib/Player.d.ts +0 -26
  91. package/lib/Player.js +0 -19
  92. package/lib/Player.js.map +0 -1
  93. package/lib/Plugin.d.ts +0 -67
  94. package/lib/Plugin.js +0 -92
  95. package/lib/Plugin.js.map +0 -1
  96. package/lib/Scheduler.d.ts +0 -26
  97. package/lib/Scheduler.js +0 -90
  98. package/lib/Scheduler.js.map +0 -1
  99. package/lib/Shape.d.ts +0 -127
  100. package/lib/Shape.js +0 -261
  101. package/lib/Shape.js.map +0 -1
  102. package/lib/Utils.js +0 -181
  103. package/lib/Utils.js.map +0 -1
  104. package/lib/Vector2d.d.ts +0 -20
  105. package/lib/Vector2d.js +0 -63
  106. package/lib/Vector2d.js.map +0 -1
  107. package/lib/VirtualGrid.d.ts +0 -26
  108. package/lib/VirtualGrid.js +0 -68
  109. package/lib/VirtualGrid.js.map +0 -1
  110. package/lib/Worker.d.ts +0 -7
  111. package/lib/Worker.js +0 -13
  112. package/lib/Worker.js.map +0 -1
  113. package/lib/WorldMaps.d.ts +0 -105
  114. package/lib/WorldMaps.js +0 -184
  115. package/lib/WorldMaps.js.map +0 -1
  116. package/lib/gui/PrebuiltGui.js +0 -29
  117. package/lib/gui/PrebuiltGui.js.map +0 -1
  118. package/lib/index.d.ts +0 -25
  119. package/lib/index.js +0 -26
  120. package/lib/index.js.map +0 -1
  121. package/lib/transports/io.d.ts +0 -22
  122. package/lib/transports/io.js +0 -82
  123. package/lib/transports/io.js.map +0 -1
  124. package/lib/workers/move.d.ts +0 -1
  125. package/lib/workers/move.js +0 -57
  126. package/lib/workers/move.js.map +0 -1
  127. package/rpg.toml +0 -11
  128. package/src/AbstractObject.ts +0 -973
  129. package/src/Color.ts +0 -29
  130. package/src/DefaultInput.ts +0 -26
  131. package/src/Event.ts +0 -3
  132. package/src/EventEmitter.ts +0 -65
  133. package/src/Game.ts +0 -159
  134. package/src/Hit.ts +0 -70
  135. package/src/Inject.ts +0 -22
  136. package/src/Logger.ts +0 -7
  137. package/src/Map.ts +0 -335
  138. package/src/Module.ts +0 -144
  139. package/src/Plugin.ts +0 -100
  140. package/src/Scheduler.ts +0 -95
  141. package/src/Shape.ts +0 -302
  142. package/src/Vector2d.ts +0 -70
  143. package/src/VirtualGrid.ts +0 -78
  144. package/src/Worker.ts +0 -17
  145. package/src/WorldMaps.ts +0 -204
  146. package/src/transports/io.ts +0 -91
  147. package/src/workers/move.ts +0 -61
  148. /package/{lib/gui → dist}/PrebuiltGui.d.ts +0 -0
@@ -0,0 +1,58 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Applies constant velocity movement in a specified direction
6
+ *
7
+ * Used for simple linear movement at a constant speed
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // Move entity right at 5 pixels per frame
12
+ * movementManager.add('entity1', new LinearMove(5, 0));
13
+ *
14
+ * // Move diagonally
15
+ * movementManager.add('entity1', new LinearMove(3, 3));
16
+ * ```
17
+ */
18
+ export class LinearMove implements MovementStrategy {
19
+ /**
20
+ * Create a linear movement
21
+ *
22
+ * @param vx - X velocity component
23
+ * @param vy - Y velocity component
24
+ * @param duration - Optional duration in milliseconds (if not set, movement continues indefinitely)
25
+ */
26
+ constructor(
27
+ private vx: number,
28
+ private vy: number,
29
+ private duration?: number
30
+ ) {
31
+ this.elapsed = 0;
32
+ }
33
+
34
+ private elapsed: number;
35
+
36
+ /**
37
+ * Apply velocity to the body
38
+ *
39
+ * @param body - Matter.js body to move
40
+ * @param dt - Time delta in milliseconds
41
+ */
42
+ update(body: Matter.Body, dt: number): void {
43
+ if (this.duration !== undefined) {
44
+ this.elapsed += dt;
45
+ }
46
+
47
+ Matter.Body.setVelocity(body, { x: this.vx, y: this.vy });
48
+ }
49
+
50
+ /**
51
+ * Check if movement duration has elapsed
52
+ *
53
+ * @returns True if movement should stop
54
+ */
55
+ isFinished(): boolean {
56
+ return this.duration !== undefined && this.elapsed >= this.duration;
57
+ }
58
+ }
@@ -0,0 +1,128 @@
1
+ import * as Matter from 'matter-js';
2
+ import { RpgCommonPhysic } from '../../Physic';
3
+ import { MovementStrategy } from '../MovementStrategy';
4
+
5
+ /** Utilitaires Vec2 pour éviter des new() à chaque frame */
6
+ const vTmp1 = Matter.Vector.create();
7
+ const vTmp2 = Matter.Vector.create();
8
+
9
+ /**
10
+ * Implements a seek behavior with linear obstacle avoidance
11
+ *
12
+ * Similar to SeekAvoid but uses a linear repulsion model instead of inverse square.
13
+ * This creates a smoother avoidance field with gradual transitions.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Make enemy follow player with smooth avoidance
18
+ * const playerPosition = () => Matter.Vector.create(player.x(), player.y());
19
+ * movementManager.add('enemy1', new LinearRepulsion(physics, playerPosition, 3, 50, 5));
20
+ * ```
21
+ */
22
+ export class LinearRepulsion implements MovementStrategy {
23
+ private repulseRadius2: number;
24
+
25
+ /**
26
+ * Create a seeking movement with linear obstacle avoidance
27
+ *
28
+ * @param phys - Physics system to query for nearby obstacles
29
+ * @param target - Function returning the current target position
30
+ * @param maxSpeed - Maximum movement speed
31
+ * @param repulseRadius - Radius around entity to check for obstacles
32
+ * @param repulseWeight - Strength of repulsion from obstacles
33
+ */
34
+ constructor(
35
+ private phys: RpgCommonPhysic,
36
+ private target: () => Matter.Vector,
37
+ private maxSpeed = 2.5,
38
+ private repulseRadius = 32,
39
+ private repulseWeight = 4
40
+ ) {
41
+ this.repulseRadius2 = this.repulseRadius * this.repulseRadius;
42
+ }
43
+
44
+ /**
45
+ * Update seek and avoid behavior with linear repulsion
46
+ *
47
+ * @param body - Matter.js body to move
48
+ * @param dt - Time delta in milliseconds
49
+ */
50
+ update(body: Matter.Body, dt: number): void {
51
+ // 1) Attraction (pull) vers la cible actuelle
52
+ Matter.Vector.sub(this.target(), body.position, vTmp1);
53
+ const distance = Matter.Vector.magnitude(vTmp1);
54
+ if (distance > 0.0001) Matter.Vector.mult(vTmp1, 1 / distance, vTmp1);
55
+
56
+ // 2) Répulsion linéaire - force plus douce qui diminue graduellement
57
+ const area = {
58
+ min: { x: body.position.x - this.repulseRadius,
59
+ y: body.position.y - this.repulseRadius },
60
+ max: { x: body.position.x + this.repulseRadius,
61
+ y: body.position.y + this.repulseRadius }
62
+ };
63
+ const nearby = Matter.Query.region(this.phys.getWorld().bodies, area);
64
+
65
+ let pushX = 0, pushY = 0;
66
+ for (const b of nearby) {
67
+ if (b === body || b.isSensor) continue;
68
+
69
+ Matter.Vector.sub(body.position, b.position, vTmp2);
70
+ const d2 = Matter.Vector.magnitudeSquared(vTmp2);
71
+
72
+ if (d2 > this.repulseRadius2) continue; // trop loin, pas de répulsion
73
+
74
+ // Répulsion linéaire: force maximale au centre, nulle au bord
75
+ const d = Math.sqrt(d2);
76
+ const w = this.repulseWeight * (this.repulseRadius - d) / this.repulseRadius;
77
+
78
+ // Normalisation du vecteur de répulsion
79
+ if (d > 0.0001) {
80
+ pushX += vTmp2.x * w / d;
81
+ pushY += vTmp2.y * w / d;
82
+ }
83
+ }
84
+
85
+ // Limiter la norme du push cumulé
86
+ const MAX_PUSH_LEN = this.maxSpeed * 3;
87
+ let pushLen = Math.hypot(pushX, pushY);
88
+ if (pushLen > MAX_PUSH_LEN) {
89
+ pushX = pushX * MAX_PUSH_LEN / pushLen;
90
+ pushY = pushY * MAX_PUSH_LEN / pushLen;
91
+ }
92
+
93
+ // 3) Vitesse désirée = pull + push, puis clamp à maxSpeed
94
+ const desired = Matter.Vector.create(
95
+ vTmp1.x * this.maxSpeed + pushX,
96
+ vTmp1.y * this.maxSpeed + pushY
97
+ );
98
+ const len = Matter.Vector.magnitude(desired);
99
+ if (len > this.maxSpeed) Matter.Vector.mult(desired, this.maxSpeed / len, desired);
100
+
101
+ Matter.Body.setVelocity(body, desired);
102
+ }
103
+
104
+ /**
105
+ * Update the target to follow
106
+ *
107
+ * @param newTarget - Function returning the new target position
108
+ */
109
+ setTarget(newTarget: () => Matter.Vector): void {
110
+ this.target = newTarget;
111
+ }
112
+
113
+ /**
114
+ * Update movement parameters
115
+ *
116
+ * @param maxSpeed - New maximum speed
117
+ * @param repulseRadius - New repulsion radius
118
+ * @param repulseWeight - New repulsion weight
119
+ */
120
+ setParameters(maxSpeed?: number, repulseRadius?: number, repulseWeight?: number): void {
121
+ if (maxSpeed !== undefined) this.maxSpeed = maxSpeed;
122
+ if (repulseRadius !== undefined) {
123
+ this.repulseRadius = repulseRadius;
124
+ this.repulseRadius2 = this.repulseRadius * this.repulseRadius;
125
+ }
126
+ if (repulseWeight !== undefined) this.repulseWeight = repulseWeight;
127
+ }
128
+ }
@@ -0,0 +1,144 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Implements an oscillating movement pattern
6
+ *
7
+ * Entity moves back and forth along a single axis or in a pattern
8
+ * like sine wave, useful for patrols or ambient movements
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Horizontal oscillation with amplitude 100 and period 3000ms
13
+ * movementManager.add('entity1', new Oscillate({ x: 1, y: 0 }, 100, 3000));
14
+ *
15
+ * // Vertical oscillation
16
+ * movementManager.add('entity1', new Oscillate({ x: 0, y: 1 }, 50, 2000));
17
+ *
18
+ * // Circular oscillation
19
+ * movementManager.add('entity1', new Oscillate({ x: 1, y: 0 }, 100, 4000, 'circular'));
20
+ * ```
21
+ */
22
+ export class Oscillate implements MovementStrategy {
23
+ private elapsed: number = 0;
24
+ private startPosition: { x: number, y: number } = { x: 0, y: 0 };
25
+ private positionSet: boolean = false;
26
+
27
+ /**
28
+ * Create an oscillating movement
29
+ *
30
+ * @param direction - Primary axis of oscillation (normalized)
31
+ * @param amplitude - Maximum distance from center position
32
+ * @param period - Time in ms for a complete cycle
33
+ * @param type - Oscillation pattern type ('linear', 'sine', 'circular')
34
+ * @param duration - Optional total duration in ms (undefined for infinite)
35
+ */
36
+ constructor(
37
+ private direction: { x: number, y: number },
38
+ private amplitude: number,
39
+ private period: number,
40
+ private type: 'linear' | 'sine' | 'circular' = 'sine',
41
+ private duration?: number
42
+ ) {
43
+ // Normalize direction vector
44
+ const magnitude = Math.sqrt(direction.x * direction.x + direction.y * direction.y);
45
+ if (magnitude !== 0 && magnitude !== 1) {
46
+ this.direction = {
47
+ x: direction.x / magnitude,
48
+ y: direction.y / magnitude
49
+ };
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Apply oscillating movement
55
+ *
56
+ * @param body - Matter.js body to move
57
+ * @param dt - Time delta in milliseconds
58
+ */
59
+ update(body: Matter.Body, dt: number): void {
60
+ // Record start position on first update
61
+ if (!this.positionSet) {
62
+ this.startPosition = { ...body.position };
63
+ this.positionSet = true;
64
+ }
65
+
66
+ this.elapsed += dt;
67
+
68
+ // Calculate oscillation factor (0 to 1 to 0)
69
+ let factor: number;
70
+ let perpFactor: number = 0;
71
+
72
+ // Calculate progress through cycle (0 to 1)
73
+ const cycleProgress = (this.elapsed % this.period) / this.period;
74
+
75
+ switch (this.type) {
76
+ case 'linear':
77
+ // Triangle wave (0->1->0)
78
+ factor = cycleProgress < 0.5
79
+ ? cycleProgress * 2
80
+ : (1 - cycleProgress) * 2;
81
+ break;
82
+
83
+ case 'sine':
84
+ // Sine wave
85
+ factor = Math.sin(cycleProgress * Math.PI * 2) * 0.5 + 0.5;
86
+ break;
87
+
88
+ case 'circular':
89
+ // Circular motion
90
+ factor = Math.sin(cycleProgress * Math.PI * 2);
91
+ perpFactor = Math.cos(cycleProgress * Math.PI * 2);
92
+ break;
93
+
94
+ default:
95
+ factor = Math.sin(cycleProgress * Math.PI * 2) * 0.5 + 0.5;
96
+ }
97
+
98
+ // Calculate new position
99
+ let newX, newY;
100
+
101
+ if (this.type === 'circular') {
102
+ // For circular, we need both sine and cosine components
103
+ const perpDirection = { x: -this.direction.y, y: this.direction.x };
104
+
105
+ newX = this.startPosition.x +
106
+ (this.direction.x * factor * this.amplitude) +
107
+ (perpDirection.x * perpFactor * this.amplitude);
108
+
109
+ newY = this.startPosition.y +
110
+ (this.direction.y * factor * this.amplitude) +
111
+ (perpDirection.y * perpFactor * this.amplitude);
112
+ } else {
113
+ // Map factor from 0-1 to -1 to 1 range for linear and sine
114
+ const mappedFactor = factor * 2 - 1;
115
+
116
+ newX = this.startPosition.x + (this.direction.x * mappedFactor * this.amplitude);
117
+ newY = this.startPosition.y + (this.direction.y * mappedFactor * this.amplitude);
118
+ }
119
+
120
+ // Calculate velocity based on position difference
121
+ const vx = (newX - body.position.x) / (dt / 1000);
122
+ const vy = (newY - body.position.y) / (dt / 1000);
123
+
124
+ // Apply velocity
125
+ Matter.Body.setVelocity(body, { x: vx, y: vy });
126
+ }
127
+
128
+ /**
129
+ * Check if oscillation duration has elapsed
130
+ *
131
+ * @returns True if movement is finished
132
+ */
133
+ isFinished(): boolean {
134
+ return this.duration !== undefined && this.elapsed >= this.duration;
135
+ }
136
+
137
+ /**
138
+ * Reset the oscillation to start from the current position
139
+ */
140
+ reset(): void {
141
+ this.elapsed = 0;
142
+ this.positionSet = false;
143
+ }
144
+ }
@@ -0,0 +1,156 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Implements a path following movement that navigates through waypoints
6
+ *
7
+ * The entity will follow a sequence of waypoints at a specified speed,
8
+ * with optional looping behavior.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Create a patrol path for an NPC
13
+ * const waypoints = [
14
+ * { x: 100, y: 100 },
15
+ * { x: 300, y: 100 },
16
+ * { x: 300, y: 300 },
17
+ * { x: 100, y: 300 }
18
+ * ];
19
+ *
20
+ * // Follow path once
21
+ * movementManager.add('npc1', new PathFollow(waypoints, 3));
22
+ *
23
+ * // Loop indefinitely (patrol)
24
+ * movementManager.add('npc1', new PathFollow(waypoints, 3, true));
25
+ * ```
26
+ */
27
+ export class PathFollow implements MovementStrategy {
28
+ private currentWaypoint: number = 0;
29
+ private finished: boolean = false;
30
+ private direction: { x: number, y: number } = { x: 0, y: 0 };
31
+ private waypointThreshold: number = 5; // Distance threshold to consider waypoint reached
32
+
33
+ /**
34
+ * Create a path following movement
35
+ *
36
+ * @param waypoints - Array of x,y positions to follow
37
+ * @param speed - Movement speed in pixels per frame
38
+ * @param loop - Whether to loop back to start after reaching final waypoint
39
+ * @param pauseAtWaypoints - Optional time in ms to pause at each waypoint
40
+ */
41
+ constructor(
42
+ private waypoints: Array<{ x: number, y: number }>,
43
+ private speed: number,
44
+ private loop: boolean = false,
45
+ private pauseAtWaypoints: number = 0
46
+ ) {
47
+ if (waypoints.length === 0) {
48
+ this.finished = true;
49
+ }
50
+ }
51
+
52
+ private pauseTimer: number = 0;
53
+ private isPaused: boolean = false;
54
+
55
+ /**
56
+ * Update path following logic
57
+ *
58
+ * @param body - Matter.js body to move
59
+ * @param dt - Time delta in milliseconds
60
+ */
61
+ update(body: Matter.Body, dt: number): void {
62
+ if (this.finished || this.waypoints.length === 0) {
63
+ Matter.Body.setVelocity(body, { x: 0, y: 0 });
64
+ return;
65
+ }
66
+
67
+ // Handle pausing at waypoints
68
+ if (this.isPaused) {
69
+ Matter.Body.setVelocity(body, { x: 0, y: 0 });
70
+ this.pauseTimer += dt;
71
+
72
+ if (this.pauseTimer >= this.pauseAtWaypoints) {
73
+ this.isPaused = false;
74
+ this.pauseTimer = 0;
75
+ }
76
+ return;
77
+ }
78
+
79
+ const currentTarget = this.waypoints[this.currentWaypoint];
80
+ const distanceX = currentTarget.x - body.position.x;
81
+ const distanceY = currentTarget.y - body.position.y;
82
+ const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
83
+
84
+ // Check if waypoint is reached
85
+ if (distance <= this.waypointThreshold) {
86
+ // Go to next waypoint
87
+ this.currentWaypoint++;
88
+
89
+ // Check if we've finished the path
90
+ if (this.currentWaypoint >= this.waypoints.length) {
91
+ if (this.loop) {
92
+ // Loop back to beginning
93
+ this.currentWaypoint = 0;
94
+ } else {
95
+ // End path following
96
+ this.finished = true;
97
+ Matter.Body.setVelocity(body, { x: 0, y: 0 });
98
+ return;
99
+ }
100
+ }
101
+
102
+ // Pause at waypoint if specified
103
+ if (this.pauseAtWaypoints > 0) {
104
+ this.isPaused = true;
105
+ this.pauseTimer = 0;
106
+ return;
107
+ }
108
+ }
109
+
110
+ // Calculate movement direction toward current waypoint
111
+ if (distance > 0) {
112
+ this.direction = {
113
+ x: distanceX / distance,
114
+ y: distanceY / distance
115
+ };
116
+ }
117
+
118
+ // Apply velocity toward current waypoint
119
+ Matter.Body.setVelocity(body, {
120
+ x: this.direction.x * this.speed,
121
+ y: this.direction.y * this.speed
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Check if path has been fully traversed
127
+ *
128
+ * @returns True if path following is complete
129
+ */
130
+ isFinished(): boolean {
131
+ return this.finished;
132
+ }
133
+
134
+ /**
135
+ * Get current waypoint index
136
+ *
137
+ * @returns The index of the current target waypoint
138
+ */
139
+ getCurrentWaypoint(): number {
140
+ return this.currentWaypoint;
141
+ }
142
+
143
+ /**
144
+ * Set a new path of waypoints
145
+ *
146
+ * @param waypoints - New waypoints to follow
147
+ * @param resetProgress - Whether to start from the first waypoint
148
+ */
149
+ setWaypoints(waypoints: Array<{ x: number, y: number }>, resetProgress: boolean = true): void {
150
+ this.waypoints = waypoints;
151
+ if (resetProgress) {
152
+ this.currentWaypoint = 0;
153
+ }
154
+ this.finished = waypoints.length === 0;
155
+ }
156
+ }