@rpgjs/common 4.2.1 → 5.0.0-alpha.0

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 -152
  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,142 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from './MovementStrategy';
3
+ import { RpgCommonPhysic } from '../Physic';
4
+
5
+ /**
6
+ * Manager for entity movement strategies
7
+ *
8
+ * Handles the execution and lifecycle of all movement strategies
9
+ * for entities in the game world.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // In game loop
14
+ * const movementManager = new MovementManager();
15
+ *
16
+ * // Add dash movement to player
17
+ * movementManager.add('player1', new Dash(200, { x: 1, y: 0 }, 300));
18
+ *
19
+ * // Update in game loop
20
+ * function gameLoop(dt) {
21
+ * movementManager.update(dt, physic);
22
+ * physic.update(dt);
23
+ * }
24
+ * ```
25
+ */
26
+ export class MovementManager {
27
+ /** Map of entity IDs to their active movement strategies */
28
+ private strategies: Map<string, MovementStrategy[]> = new Map();
29
+
30
+ /**
31
+ * Add a movement strategy to an entity
32
+ *
33
+ * @param id - Entity ID
34
+ * @param strategy - Movement strategy to apply
35
+ */
36
+ add(id: string, strategy: MovementStrategy): void {
37
+ if (!this.strategies.has(id)) {
38
+ this.strategies.set(id, []);
39
+ }
40
+
41
+ this.strategies.get(id)!.push(strategy);
42
+ }
43
+
44
+ /**
45
+ * Remove a specific movement strategy from an entity
46
+ *
47
+ * @param id - Entity ID
48
+ * @param strategy - The strategy instance to remove
49
+ * @returns True if the strategy was found and removed
50
+ */
51
+ remove(id: string, strategy: MovementStrategy): boolean {
52
+ const entityStrategies = this.strategies.get(id);
53
+ if (!entityStrategies) return false;
54
+
55
+ const index = entityStrategies.indexOf(strategy);
56
+ if (index === -1) return false;
57
+
58
+ entityStrategies.splice(index, 1);
59
+ return true;
60
+ }
61
+
62
+ /**
63
+ * Remove all movement strategies from an entity
64
+ *
65
+ * @param id - Entity ID
66
+ */
67
+ clear(id: string): void {
68
+ this.strategies.delete(id);
69
+ }
70
+
71
+ /**
72
+ * Check if an entity has any active movement strategies
73
+ *
74
+ * @param id - Entity ID
75
+ * @returns True if entity has active strategies
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * // Check if player has active movements
80
+ * if (movementManager.hasActiveStrategies('player1')) {
81
+ * // Don't accept input while movements are active
82
+ * }
83
+ * ```
84
+ */
85
+ hasActiveStrategies(id: string): boolean {
86
+ const entityStrategies = this.strategies.get(id);
87
+ return Boolean(entityStrategies && entityStrategies.length > 0);
88
+ }
89
+
90
+ /**
91
+ * Get all active movement strategies for an entity
92
+ *
93
+ * @param id - Entity ID
94
+ * @returns Array of active strategies or empty array if none
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * // Get all player movements
99
+ * const movements = movementManager.getStrategies('player1');
100
+ * ```
101
+ */
102
+ getStrategies(id: string): MovementStrategy[] {
103
+ return this.strategies.get(id) || [];
104
+ }
105
+
106
+ /**
107
+ * Update all movement strategies
108
+ * Must be called BEFORE physics.update()
109
+ *
110
+ * @param dt - Time delta in milliseconds
111
+ * @param physic - Physics system to get bodies from
112
+ */
113
+ update(dt: number, physic: RpgCommonPhysic): void {
114
+ for (const [id, strategies] of this.strategies.entries()) {
115
+ // Skip if no strategies
116
+ if (strategies.length === 0) {
117
+ this.strategies.delete(id);
118
+ continue;
119
+ }
120
+
121
+ // Get the body for this entity
122
+ const body = physic.getBody(id);
123
+ if (!body) continue;
124
+
125
+ // Update each strategy and remove finished ones
126
+ for (let i = strategies.length - 1; i >= 0; i--) {
127
+ const strategy = strategies[i];
128
+
129
+ // Apply the movement logic
130
+ strategy.update(body, dt);
131
+
132
+ // Check if movement is finished
133
+ if (strategy.isFinished?.()) {
134
+ strategies.splice(i, 1);
135
+
136
+ // Trigger onFinished callback if present
137
+ strategy.onFinished?.();
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
@@ -0,0 +1,42 @@
1
+ import * as Matter from 'matter-js';
2
+
3
+ /**
4
+ * Interface defining the contract for movement strategies
5
+ *
6
+ * Movement strategies are responsible for calculating how a body should move
7
+ * each frame without directly modifying the physics engine.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * class SimpleMove implements MovementStrategy {
12
+ * constructor(private vx: number, private vy: number) {}
13
+ *
14
+ * update(body: Matter.Body, dt: number): void {
15
+ * Matter.Body.setVelocity(body, { x: this.vx, y: this.vy });
16
+ * }
17
+ * }
18
+ * ```
19
+ */
20
+ export interface MovementStrategy {
21
+ /**
22
+ * Called each frame BEFORE the physics engine update
23
+ * Calculates and applies movement to the provided body
24
+ *
25
+ * @param body - The Matter.js body to move
26
+ * @param dt - Time delta in milliseconds
27
+ */
28
+ update(body: Matter.Body, dt: number): void;
29
+
30
+ /**
31
+ * Returns true when the movement is finished (optional)
32
+ * If implemented, the strategy will be automatically removed
33
+ * from the movement manager when it returns true
34
+ */
35
+ isFinished?(): boolean;
36
+
37
+ /**
38
+ * Event triggered when the movement completes (optional)
39
+ * Useful for chaining movements
40
+ */
41
+ onFinished?(): void;
42
+ }
@@ -0,0 +1,15 @@
1
+ // Movement system core
2
+ export * from './MovementStrategy';
3
+ export * from './MovementManager';
4
+
5
+ // Movement strategies
6
+ export * from './strategies/LinearMove';
7
+ export * from './strategies/Dash';
8
+ export * from './strategies/Knockback';
9
+ export * from './strategies/PathFollow';
10
+ export * from './strategies/Oscillate';
11
+ export * from './strategies/CompositeMovement';
12
+ export * from './strategies/SeekAvoid';
13
+ export * from './strategies/LinearRepulsion';
14
+ export * from './strategies/IceMovement';
15
+ export * from './strategies/ProjectileMovement';
@@ -0,0 +1,173 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Combines multiple movement strategies into a single movement
6
+ *
7
+ * Allows composition of movement patterns by either:
8
+ * - Running multiple strategies in parallel (adding their effects)
9
+ * - Running strategies in sequence (one after another)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // Parallel movement (oscillate while following a path)
14
+ * const composite = new CompositeMovement('parallel', [
15
+ * new PathFollow(waypoints, 2),
16
+ * new Oscillate({ x: 0, y: 1 }, 10, 1000)
17
+ * ]);
18
+ *
19
+ * // Sequential movement (dash, then knockback)
20
+ * const sequence = new CompositeMovement('sequence', [
21
+ * new Dash(8, { x: 1, y: 0 }, 200),
22
+ * new Knockback({ x: -0.5, y: 0 }, 3, 300)
23
+ * ]);
24
+ *
25
+ * movementManager.add('entity1', composite);
26
+ * ```
27
+ */
28
+ export class CompositeMovement implements MovementStrategy {
29
+ private currentStrategyIndex: number = 0;
30
+
31
+ /**
32
+ * Create a composite movement
33
+ *
34
+ * @param mode - How to combine strategies ('parallel' or 'sequence')
35
+ * @param strategies - Array of movement strategies to combine
36
+ */
37
+ constructor(
38
+ private mode: 'parallel' | 'sequence',
39
+ private strategies: MovementStrategy[]
40
+ ) {}
41
+
42
+ /**
43
+ * Apply the composite movement
44
+ *
45
+ * @param body - Matter.js body to move
46
+ * @param dt - Time delta in milliseconds
47
+ */
48
+ update(body: Matter.Body, dt: number): void {
49
+ if (this.strategies.length === 0) return;
50
+
51
+ if (this.mode === 'parallel') {
52
+ // Update all strategies simultaneously
53
+ // Their effects will be combined by the physics engine
54
+ this.updateParallel(body, dt);
55
+ } else {
56
+ // Update strategies in sequence
57
+ this.updateSequence(body, dt);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Update all strategies in parallel
63
+ */
64
+ private updateParallel(body: Matter.Body, dt: number): void {
65
+ // Make a copy of the array since we might remove elements during iteration
66
+ const currentStrategies = [...this.strategies];
67
+
68
+ // Track original velocity to combine movement effects
69
+ const originalVelocity = { x: body.velocity.x, y: body.velocity.y };
70
+ let velocitiesApplied = 0;
71
+
72
+ // Apply each strategy
73
+ for (let i = currentStrategies.length - 1; i >= 0; i--) {
74
+ const strategy = currentStrategies[i];
75
+
76
+ // Store velocity before strategy update
77
+ const beforeVx = body.velocity.x;
78
+ const beforeVy = body.velocity.y;
79
+
80
+ // Apply the strategy
81
+ strategy.update(body, dt);
82
+
83
+ // Track if velocity was changed
84
+ if (beforeVx !== body.velocity.x || beforeVy !== body.velocity.y) {
85
+ velocitiesApplied++;
86
+ }
87
+
88
+ // Remove finished strategies
89
+ if (strategy.isFinished?.()) {
90
+ this.strategies.splice(this.strategies.indexOf(strategy), 1);
91
+ strategy.onFinished?.();
92
+ }
93
+ }
94
+
95
+ // If multiple strategies applied velocities, calculate the average
96
+ if (velocitiesApplied > 1) {
97
+ Matter.Body.setVelocity(body, {
98
+ x: (body.velocity.x + originalVelocity.x) / 2,
99
+ y: (body.velocity.y + originalVelocity.y) / 2
100
+ });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Update strategies in sequence
106
+ */
107
+ private updateSequence(body: Matter.Body, dt: number): void {
108
+ if (this.currentStrategyIndex >= this.strategies.length) {
109
+ return;
110
+ }
111
+
112
+ const currentStrategy = this.strategies[this.currentStrategyIndex];
113
+
114
+ // Apply the current strategy
115
+ currentStrategy.update(body, dt);
116
+
117
+ // Check if current strategy is finished
118
+ if (currentStrategy.isFinished?.()) {
119
+ // Trigger completion callback
120
+ currentStrategy.onFinished?.();
121
+
122
+ // Move to next strategy
123
+ this.currentStrategyIndex++;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Check if all strategies have finished
129
+ *
130
+ * @returns True if all strategies are completed
131
+ */
132
+ isFinished(): boolean {
133
+ if (this.strategies.length === 0) return true;
134
+
135
+ if (this.mode === 'parallel') {
136
+ // In parallel mode, we're finished when all strategies are finished
137
+ return this.strategies.length === 0;
138
+ } else {
139
+ // In sequence mode, we're finished when we've gone through all strategies
140
+ return this.currentStrategyIndex >= this.strategies.length;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Add a new strategy to the composite
146
+ *
147
+ * @param strategy - Movement strategy to add
148
+ */
149
+ add(strategy: MovementStrategy): void {
150
+ this.strategies.push(strategy);
151
+ }
152
+
153
+ /**
154
+ * Remove a strategy from the composite
155
+ *
156
+ * @param strategy - Movement strategy to remove
157
+ * @returns True if the strategy was found and removed
158
+ */
159
+ remove(strategy: MovementStrategy): boolean {
160
+ const index = this.strategies.indexOf(strategy);
161
+ if (index === -1) return false;
162
+
163
+ this.strategies.splice(index, 1);
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Reset the composite to start from the beginning
169
+ */
170
+ reset(): void {
171
+ this.currentStrategyIndex = 0;
172
+ }
173
+ }
@@ -0,0 +1,82 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Implements a dash movement in a specified direction for a limited time
6
+ *
7
+ * Dash applies high velocity movement in a direction for a short duration
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // Dash right for 200ms at speed 10
12
+ * movementManager.add('player1', new Dash(10, { x: 1, y: 0 }, 200));
13
+ *
14
+ * // Dash diagonally (normalized)
15
+ * movementManager.add('player1', new Dash(8, { x: 0.7071, y: 0.7071 }, 150));
16
+ * ```
17
+ */
18
+ export class Dash implements MovementStrategy {
19
+ private elapsed: number = 0;
20
+ private finished: boolean = false;
21
+
22
+ /**
23
+ * Create a dash movement
24
+ *
25
+ * @param speed - Movement speed (pixels per frame)
26
+ * @param direction - Normalized direction vector { x, y }
27
+ * @param duration - Duration in milliseconds
28
+ */
29
+ constructor(
30
+ private speed: number,
31
+ private direction: { x: number, y: number },
32
+ private duration: number
33
+ ) {
34
+ // Normalize direction vector if not already normalized
35
+ const magnitude = Math.sqrt(direction.x * direction.x + direction.y * direction.y);
36
+ if (magnitude !== 0 && magnitude !== 1) {
37
+ this.direction = {
38
+ x: direction.x / magnitude,
39
+ y: direction.y / magnitude
40
+ };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Apply dash movement to the body
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
+ this.elapsed += dt;
52
+
53
+ if (this.elapsed <= this.duration) {
54
+ // Apply dash velocity
55
+ Matter.Body.setVelocity(body, {
56
+ x: this.direction.x * this.speed,
57
+ y: this.direction.y * this.speed
58
+ });
59
+ } else {
60
+ // Stop the body when dash is complete
61
+ Matter.Body.setVelocity(body, { x: 0, y: 0 });
62
+ this.finished = true;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Check if dash duration has elapsed
68
+ *
69
+ * @returns True if dash is complete
70
+ */
71
+ isFinished(): boolean {
72
+ return this.finished;
73
+ }
74
+
75
+ /**
76
+ * Optional callback for when dash completes
77
+ * Can be used to chain into another movement
78
+ */
79
+ onFinished(): void {
80
+ // Optional event hook for dash completion
81
+ }
82
+ }
@@ -0,0 +1,158 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Implements a slippery movement for icy surfaces
6
+ *
7
+ * Creates a realistic ice-physics effect with:
8
+ * - Slow acceleration when starting to move
9
+ * - Slippery inertia when stopping
10
+ * - Gradual turning with momentum
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // Apply ice movement to player on ice terrain
15
+ * movementManager.add('player1', new IceMovement({ x: 1, y: 0 }, 5));
16
+ *
17
+ * // Update direction when player changes direction
18
+ * iceMovement.setTargetDirection({ x: 0, y: 1 });
19
+ * ```
20
+ */
21
+ export class IceMovement implements MovementStrategy {
22
+ // Current velocity components
23
+ private currentVx: number = 0;
24
+ private currentVy: number = 0;
25
+
26
+ // Flags
27
+ private stopped: boolean = false;
28
+ private elapsed: number = 0;
29
+
30
+ /**
31
+ * Create an ice movement behavior
32
+ *
33
+ * @param targetDirection - Desired movement direction (normalized vector)
34
+ * @param maxSpeed - Maximum speed when fully accelerated
35
+ * @param acceleration - Acceleration rate (0-1, lower = more slippery start)
36
+ * @param friction - Friction rate (0-1, lower = more slippery stop)
37
+ * @param duration - Optional duration limit for the movement
38
+ */
39
+ constructor(
40
+ private targetDirection: { x: number, y: number },
41
+ private maxSpeed: number = 4,
42
+ private acceleration: number = 0.15,
43
+ private friction: number = 0.05,
44
+ private duration?: number
45
+ ) {
46
+ // Normalize direction
47
+ this.normalizeDirection();
48
+ }
49
+
50
+ /**
51
+ * Update the ice movement physics
52
+ *
53
+ * @param body - Matter.js body to move
54
+ * @param dt - Time delta in milliseconds
55
+ */
56
+ update(body: Matter.Body, dt: number): void {
57
+ // Update elapsed time if duration is set
58
+ if (this.duration !== undefined) {
59
+ this.elapsed += dt;
60
+ if (this.elapsed >= this.duration) {
61
+ this.stopped = true;
62
+ }
63
+ }
64
+
65
+ // Handle acceleration and friction physics
66
+ if (!this.stopped) {
67
+ // Accelerate gradually toward target speed in target direction
68
+ this.currentVx += (this.targetDirection.x * this.maxSpeed - this.currentVx) * this.acceleration;
69
+ this.currentVy += (this.targetDirection.y * this.maxSpeed - this.currentVy) * this.acceleration;
70
+ } else {
71
+ // Apply friction to gradually slow down
72
+ this.currentVx *= (1 - this.friction);
73
+ this.currentVy *= (1 - this.friction);
74
+ }
75
+
76
+ // Apply the calculated velocity
77
+ Matter.Body.setVelocity(body, {
78
+ x: this.currentVx,
79
+ y: this.currentVy
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Check if movement is finished
85
+ * (movement is considered finished when almost stopped)
86
+ *
87
+ * @returns True if movement is effectively stopped
88
+ */
89
+ isFinished(): boolean {
90
+ const speed = Math.hypot(this.currentVx, this.currentVy);
91
+
92
+ // Movement is finished when:
93
+ // 1. We're in stopped state AND
94
+ // 2. Speed is negligible (< 0.05)
95
+ return this.stopped && speed < 0.05;
96
+ }
97
+
98
+ /**
99
+ * Stop the movement (will start applying friction)
100
+ */
101
+ stop(): void {
102
+ this.stopped = true;
103
+ }
104
+
105
+ /**
106
+ * Resume movement in the current direction
107
+ */
108
+ resume(): void {
109
+ this.stopped = false;
110
+ }
111
+
112
+ /**
113
+ * Set a new target direction
114
+ * The actual movement will gradually change toward this direction
115
+ *
116
+ * @param direction - New target direction
117
+ */
118
+ setTargetDirection(direction: { x: number, y: number }): void {
119
+ this.targetDirection = direction;
120
+ this.normalizeDirection();
121
+
122
+ // If we were stopped, resume movement
123
+ this.stopped = false;
124
+ }
125
+
126
+ /**
127
+ * Set movement parameters
128
+ *
129
+ * @param maxSpeed - New maximum speed
130
+ * @param acceleration - New acceleration rate
131
+ * @param friction - New friction rate
132
+ */
133
+ setParameters(maxSpeed?: number, acceleration?: number, friction?: number): void {
134
+ if (maxSpeed !== undefined) this.maxSpeed = maxSpeed;
135
+ if (acceleration !== undefined) this.acceleration = acceleration;
136
+ if (friction !== undefined) this.friction = friction;
137
+ }
138
+
139
+ /**
140
+ * Normalize the direction vector
141
+ */
142
+ private normalizeDirection(): void {
143
+ const magnitude = Math.sqrt(
144
+ this.targetDirection.x * this.targetDirection.x +
145
+ this.targetDirection.y * this.targetDirection.y
146
+ );
147
+
148
+ if (magnitude > 0) {
149
+ this.targetDirection = {
150
+ x: this.targetDirection.x / magnitude,
151
+ y: this.targetDirection.y / magnitude
152
+ };
153
+ } else {
154
+ // Default to no direction
155
+ this.targetDirection = { x: 0, y: 0 };
156
+ }
157
+ }
158
+ }
@@ -0,0 +1,81 @@
1
+ import * as Matter from 'matter-js';
2
+ import { MovementStrategy } from '../MovementStrategy';
3
+
4
+ /**
5
+ * Implements a knockback effect with decreasing force over time
6
+ *
7
+ * Applies an initial velocity that gradually decreases to simulate
8
+ * being pushed back by an impact
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Knockback player to the left with force 8 for 300ms
13
+ * movementManager.add('player1', new Knockback({ x: -1, y: 0 }, 8, 300));
14
+ *
15
+ * // Knockback with decay (resistance)
16
+ * movementManager.add('enemy1', new Knockback({ x: 0, y: 1 }, 5, 200, 0.9));
17
+ * ```
18
+ */
19
+ export class Knockback implements MovementStrategy {
20
+ private elapsed: number = 0;
21
+ private currentSpeed: number;
22
+
23
+ /**
24
+ * Create a knockback movement
25
+ *
26
+ * @param direction - Normalized direction vector { x, y }
27
+ * @param force - Initial force of the knockback
28
+ * @param duration - Duration in milliseconds
29
+ * @param decayFactor - Speed decay multiplier per frame (0.8-0.95 typical)
30
+ */
31
+ constructor(
32
+ private direction: { x: number, y: number },
33
+ private force: number,
34
+ private duration: number,
35
+ private decayFactor: number = 0.9
36
+ ) {
37
+ this.currentSpeed = force;
38
+
39
+ // Normalize direction vector
40
+ const magnitude = Math.sqrt(direction.x * direction.x + direction.y * direction.y);
41
+ if (magnitude !== 0 && magnitude !== 1) {
42
+ this.direction = {
43
+ x: direction.x / magnitude,
44
+ y: direction.y / magnitude
45
+ };
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Apply knockback movement with decreasing force
51
+ *
52
+ * @param body - Matter.js body to move
53
+ * @param dt - Time delta in milliseconds
54
+ */
55
+ update(body: Matter.Body, dt: number): void {
56
+ this.elapsed += dt;
57
+
58
+ if (this.elapsed <= this.duration) {
59
+ // Apply decreasing velocity
60
+ Matter.Body.setVelocity(body, {
61
+ x: this.direction.x * this.currentSpeed,
62
+ y: this.direction.y * this.currentSpeed
63
+ });
64
+
65
+ // Decay the speed
66
+ this.currentSpeed *= this.decayFactor;
67
+ } else {
68
+ // Stop movement when knockback is complete
69
+ Matter.Body.setVelocity(body, { x: 0, y: 0 });
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Check if knockback duration has elapsed
75
+ *
76
+ * @returns True if knockback is complete
77
+ */
78
+ isFinished(): boolean {
79
+ return this.elapsed >= this.duration;
80
+ }
81
+ }