@rpgjs/server 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 (219) hide show
  1. package/{lib → dist}/Gui/DialogGui.d.ts +1 -2
  2. package/{lib → dist}/Gui/Gui.d.ts +1 -2
  3. package/dist/Player/BattleManager.d.ts +22 -0
  4. package/dist/Player/ClassManager.d.ts +18 -0
  5. package/dist/Player/Event.d.ts +0 -0
  6. package/dist/Player/ItemManager.d.ts +17 -0
  7. package/dist/Player/MoveManager.d.ts +177 -0
  8. package/dist/Player/ParameterManager.d.ts +42 -0
  9. package/dist/Player/Player.d.ts +73 -0
  10. package/dist/Player/SkillManager.d.ts +23 -0
  11. package/dist/Player/StateManager.d.ts +39 -0
  12. package/{lib → dist}/RpgServer.d.ts +32 -15
  13. package/dist/RpgServerEngine.d.ts +5 -0
  14. package/dist/core/context.d.ts +2 -0
  15. package/dist/core/inject.d.ts +5 -0
  16. package/dist/core/setup.d.ts +6 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +29673 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/module.d.ts +2 -0
  21. package/dist/rooms/lobby.d.ts +6 -0
  22. package/dist/rooms/map.d.ts +110 -0
  23. package/package.json +15 -37
  24. package/src/Gui/DialogGui.ts +3 -4
  25. package/src/Gui/Gui.ts +4 -6
  26. package/src/Player/BattleManager.ts +108 -99
  27. package/src/Player/ClassManager.ts +47 -46
  28. package/src/Player/ComponentManager.ts +26 -535
  29. package/src/Player/EffectManager.ts +44 -59
  30. package/src/Player/ElementManager.ts +70 -72
  31. package/src/Player/Event.ts +0 -0
  32. package/src/Player/GoldManager.ts +24 -6
  33. package/src/Player/GuiManager.ts +106 -97
  34. package/src/Player/ItemFixture.ts +22 -17
  35. package/src/Player/ItemManager.ts +234 -225
  36. package/src/Player/MoveManager.ts +1047 -457
  37. package/src/Player/ParameterManager.ts +59 -37
  38. package/src/Player/Player.ts +272 -1058
  39. package/src/Player/SkillManager.ts +146 -137
  40. package/src/Player/StateManager.ts +124 -98
  41. package/src/Player/VariableManager.ts +23 -3
  42. package/src/RpgServer.ts +36 -12
  43. package/src/RpgServerEngine.ts +7 -0
  44. package/src/core/context.ts +3 -0
  45. package/src/core/inject.ts +17 -0
  46. package/src/core/setup.ts +20 -0
  47. package/src/index.ts +9 -31
  48. package/src/module.ts +32 -0
  49. package/src/presets/index.ts +1 -3
  50. package/src/rooms/lobby.ts +24 -0
  51. package/src/rooms/map.ts +311 -0
  52. package/tsconfig.json +26 -23
  53. package/vite.config.ts +21 -0
  54. package/CHANGELOG.md +0 -174
  55. package/LICENSE +0 -19
  56. package/browser/manifest.json +0 -7
  57. package/browser/rpg.server.js +0 -22379
  58. package/browser/rpg.server.umd.cjs +0 -22410
  59. package/lib/Game/EventManager.d.ts +0 -54
  60. package/lib/Game/EventManager.js +0 -107
  61. package/lib/Game/EventManager.js.map +0 -1
  62. package/lib/Game/Map.d.ts +0 -191
  63. package/lib/Game/Map.js +0 -419
  64. package/lib/Game/Map.js.map +0 -1
  65. package/lib/Game/WorldMaps.d.ts +0 -17
  66. package/lib/Game/WorldMaps.js +0 -28
  67. package/lib/Game/WorldMaps.js.map +0 -1
  68. package/lib/Gui/DialogGui.js +0 -57
  69. package/lib/Gui/DialogGui.js.map +0 -1
  70. package/lib/Gui/Gui.js +0 -37
  71. package/lib/Gui/Gui.js.map +0 -1
  72. package/lib/Gui/MenuGui.js +0 -23
  73. package/lib/Gui/MenuGui.js.map +0 -1
  74. package/lib/Gui/NotificationGui.js +0 -8
  75. package/lib/Gui/NotificationGui.js.map +0 -1
  76. package/lib/Gui/ShopGui.js +0 -40
  77. package/lib/Gui/ShopGui.js.map +0 -1
  78. package/lib/Gui/index.js +0 -7
  79. package/lib/Gui/index.js.map +0 -1
  80. package/lib/Interfaces/Gui.d.ts +0 -4
  81. package/lib/Interfaces/Gui.js +0 -2
  82. package/lib/Interfaces/Gui.js.map +0 -1
  83. package/lib/Interfaces/StateStore.d.ts +0 -5
  84. package/lib/Interfaces/StateStore.js +0 -2
  85. package/lib/Interfaces/StateStore.js.map +0 -1
  86. package/lib/MatchMaker.d.ts +0 -24
  87. package/lib/MatchMaker.js +0 -42
  88. package/lib/MatchMaker.js.map +0 -1
  89. package/lib/Monitor/index.d.ts +0 -17
  90. package/lib/Monitor/index.js +0 -72
  91. package/lib/Monitor/index.js.map +0 -1
  92. package/lib/Player/BattleManager.d.ts +0 -36
  93. package/lib/Player/BattleManager.js +0 -101
  94. package/lib/Player/BattleManager.js.map +0 -1
  95. package/lib/Player/ClassManager.d.ts +0 -47
  96. package/lib/Player/ClassManager.js +0 -65
  97. package/lib/Player/ClassManager.js.map +0 -1
  98. package/lib/Player/ComponentManager.d.ts +0 -397
  99. package/lib/Player/ComponentManager.js +0 -505
  100. package/lib/Player/ComponentManager.js.map +0 -1
  101. package/lib/Player/EffectManager.d.ts +0 -46
  102. package/lib/Player/EffectManager.js +0 -75
  103. package/lib/Player/EffectManager.js.map +0 -1
  104. package/lib/Player/ElementManager.d.ts +0 -108
  105. package/lib/Player/ElementManager.js +0 -120
  106. package/lib/Player/ElementManager.js.map +0 -1
  107. package/lib/Player/GoldManager.d.ts +0 -17
  108. package/lib/Player/GoldManager.js +0 -27
  109. package/lib/Player/GoldManager.js.map +0 -1
  110. package/lib/Player/GuiManager.d.ts +0 -247
  111. package/lib/Player/GuiManager.js +0 -291
  112. package/lib/Player/GuiManager.js.map +0 -1
  113. package/lib/Player/ItemFixture.d.ts +0 -7
  114. package/lib/Player/ItemFixture.js +0 -19
  115. package/lib/Player/ItemFixture.js.map +0 -1
  116. package/lib/Player/ItemManager.d.ts +0 -331
  117. package/lib/Player/ItemManager.js +0 -475
  118. package/lib/Player/ItemManager.js.map +0 -1
  119. package/lib/Player/MoveManager.d.ts +0 -333
  120. package/lib/Player/MoveManager.js +0 -450
  121. package/lib/Player/MoveManager.js.map +0 -1
  122. package/lib/Player/ParameterManager.d.ts +0 -316
  123. package/lib/Player/ParameterManager.js +0 -408
  124. package/lib/Player/ParameterManager.js.map +0 -1
  125. package/lib/Player/Player.d.ts +0 -828
  126. package/lib/Player/Player.js +0 -927
  127. package/lib/Player/Player.js.map +0 -1
  128. package/lib/Player/SkillManager.d.ts +0 -163
  129. package/lib/Player/SkillManager.js +0 -223
  130. package/lib/Player/SkillManager.js.map +0 -1
  131. package/lib/Player/StateManager.d.ts +0 -185
  132. package/lib/Player/StateManager.js +0 -228
  133. package/lib/Player/StateManager.js.map +0 -1
  134. package/lib/Player/VariableManager.d.ts +0 -46
  135. package/lib/Player/VariableManager.js +0 -52
  136. package/lib/Player/VariableManager.js.map +0 -1
  137. package/lib/Query.d.ts +0 -127
  138. package/lib/Query.js +0 -159
  139. package/lib/Query.js.map +0 -1
  140. package/lib/RpgServer.js +0 -2
  141. package/lib/RpgServer.js.map +0 -1
  142. package/lib/Scenes/Map.d.ts +0 -136
  143. package/lib/Scenes/Map.js +0 -273
  144. package/lib/Scenes/Map.js.map +0 -1
  145. package/lib/decorators/event.d.ts +0 -43
  146. package/lib/decorators/event.js +0 -13
  147. package/lib/decorators/event.js.map +0 -1
  148. package/lib/decorators/map.d.ts +0 -178
  149. package/lib/decorators/map.js +0 -43
  150. package/lib/decorators/map.js.map +0 -1
  151. package/lib/entry-point.d.ts +0 -34
  152. package/lib/entry-point.js +0 -70
  153. package/lib/entry-point.js.map +0 -1
  154. package/lib/express/api.d.ts +0 -3
  155. package/lib/express/api.js +0 -105
  156. package/lib/express/api.js.map +0 -1
  157. package/lib/express/errors/NotAuthorized.d.ts +0 -4
  158. package/lib/express/errors/NotAuthorized.js +0 -7
  159. package/lib/express/errors/NotAuthorized.js.map +0 -1
  160. package/lib/express/errors/NotFound.d.ts +0 -4
  161. package/lib/express/errors/NotFound.js +0 -7
  162. package/lib/express/errors/NotFound.js.map +0 -1
  163. package/lib/express/server.d.ts +0 -18
  164. package/lib/express/server.js +0 -70
  165. package/lib/express/server.js.map +0 -1
  166. package/lib/index.d.ts +0 -20
  167. package/lib/index.js +0 -19
  168. package/lib/index.js.map +0 -1
  169. package/lib/inject.d.ts +0 -22
  170. package/lib/inject.js +0 -29
  171. package/lib/inject.js.map +0 -1
  172. package/lib/logs/index.js +0 -6
  173. package/lib/logs/index.js.map +0 -1
  174. package/lib/logs/item.js +0 -34
  175. package/lib/logs/item.js.map +0 -1
  176. package/lib/logs/log.js +0 -7
  177. package/lib/logs/log.js.map +0 -1
  178. package/lib/logs/skill.js +0 -19
  179. package/lib/logs/skill.js.map +0 -1
  180. package/lib/logs/state.js +0 -13
  181. package/lib/logs/state.js.map +0 -1
  182. package/lib/models/Item.d.ts +0 -10
  183. package/lib/models/Item.js +0 -2
  184. package/lib/models/Item.js.map +0 -1
  185. package/lib/presets/index.js +0 -65
  186. package/lib/presets/index.js.map +0 -1
  187. package/lib/server.d.ts +0 -137
  188. package/lib/server.js +0 -443
  189. package/lib/server.js.map +0 -1
  190. package/rpg.toml +0 -14
  191. package/src/Game/EventManager.ts +0 -125
  192. package/src/Game/Map.ts +0 -500
  193. package/src/Game/WorldMaps.ts +0 -45
  194. package/src/Interfaces/Gui.ts +0 -4
  195. package/src/Interfaces/StateStore.ts +0 -5
  196. package/src/MatchMaker.ts +0 -65
  197. package/src/Monitor/index.ts +0 -78
  198. package/src/Query.ts +0 -172
  199. package/src/Scenes/Map.ts +0 -310
  200. package/src/decorators/event.ts +0 -58
  201. package/src/decorators/map.ts +0 -226
  202. package/src/entry-point.ts +0 -111
  203. package/src/express/api.ts +0 -118
  204. package/src/express/errors/NotAuthorized.ts +0 -6
  205. package/src/express/errors/NotFound.ts +0 -6
  206. package/src/express/server.ts +0 -94
  207. package/src/inject.ts +0 -33
  208. package/src/models/Item.ts +0 -11
  209. package/src/server.ts +0 -459
  210. /package/{lib → dist}/Gui/MenuGui.d.ts +0 -0
  211. /package/{lib → dist}/Gui/NotificationGui.d.ts +0 -0
  212. /package/{lib → dist}/Gui/ShopGui.d.ts +0 -0
  213. /package/{lib → dist}/Gui/index.d.ts +0 -0
  214. /package/{lib → dist}/logs/index.d.ts +0 -0
  215. /package/{lib → dist}/logs/item.d.ts +0 -0
  216. /package/{lib → dist}/logs/log.d.ts +0 -0
  217. /package/{lib → dist}/logs/skill.d.ts +0 -0
  218. /package/{lib → dist}/logs/state.d.ts +0 -0
  219. /package/{lib → dist}/presets/index.d.ts +0 -0
@@ -1,21 +1,65 @@
1
- import { Direction, LiteralDirection, RpgShape, Vector2d } from '@rpgjs/common'
2
- import { Utils } from '@rpgjs/common'
3
- import { Behavior, ClientMode, MoveMode, MoveTo, PositionXY, SocketEvents, SocketMethods, Tick } from '@rpgjs/types'
4
- import { Observable, Subscription, takeUntil, Subject, tap, switchMap, of, from, debounceTime } from 'rxjs'
5
- import { RpgServerEngine } from '../server'
6
- import { RpgEvent, RpgPlayer } from './Player'
7
-
8
- const {
9
- arrayFlat,
10
- random,
11
- isFunction,
12
- capitalize
13
- } = Utils
1
+ import { type Constructor } from "@rpgjs/common";
2
+ import { RpgCommonPlayer, Matter, Direction } from "@rpgjs/common";
3
+ import {
4
+ MovementManager,
5
+ MovementStrategy,
6
+ LinearMove,
7
+ Dash,
8
+ Knockback,
9
+ PathFollow,
10
+ Oscillate,
11
+ CompositeMovement,
12
+ SeekAvoid,
13
+ LinearRepulsion,
14
+ IceMovement,
15
+ ProjectileMovement,
16
+ ProjectileType,
17
+ random,
18
+ isFunction,
19
+ capitalize
20
+ } from "@rpgjs/common";
21
+ import { RpgMap } from "../rooms/map";
22
+ import { Observable, Subscription, takeUntil, Subject, tap, switchMap, of, from } from 'rxjs';
23
+ import { RpgPlayer } from "./Player";
24
+
25
+
26
+ interface PlayerWithMixins extends RpgCommonPlayer {
27
+ getCurrentMap(): RpgMap;
28
+ id: string;
29
+ server: any;
30
+ _destroy$: Subject<void>;
31
+ frequency: number;
32
+ nbPixelInTile: number;
33
+ moveByDirection: (direction: Direction, deltaTimeInt: number) => Promise<boolean>;
34
+ changeDirection: (direction: Direction) => boolean;
35
+ }
36
+
37
+ export interface IMoveManager {
38
+ addMovement(strategy: MovementStrategy): void;
39
+ removeMovement(strategy: MovementStrategy): boolean;
40
+ clearMovements(): void;
41
+ hasActiveMovements(): boolean;
42
+ getActiveMovements(): MovementStrategy[];
43
+
44
+ moveTo(target: RpgCommonPlayer | { x: number, y: number }): void;
45
+ stopMoveTo(): void;
46
+ dash(direction: { x: number, y: number }, speed?: number, duration?: number): void;
47
+ knockback(direction: { x: number, y: number }, force?: number, duration?: number): void;
48
+ followPath(waypoints: Array<{ x: number, y: number }>, speed?: number, loop?: boolean): void;
49
+ oscillate(direction: { x: number, y: number }, amplitude?: number, period?: number): void;
50
+ applyIceMovement(direction: { x: number, y: number }, maxSpeed?: number): void;
51
+ shootProjectile(type: ProjectileType, direction: { x: number, y: number }, speed?: number): void;
52
+ moveRoutes(routes: Routes): Promise<boolean>;
53
+ infiniteMoveRoute(routes: Routes): void;
54
+ breakRoutes(force?: boolean): void;
55
+ replayRoutes(): void;
56
+ }
57
+
14
58
 
15
59
  function wait(sec: number) {
16
- return new Promise((resolve) => {
17
- setTimeout(resolve, sec * 1000)
18
- })
60
+ return new Promise((resolve) => {
61
+ setTimeout(resolve, sec * 1000)
62
+ })
19
63
  }
20
64
 
21
65
  type CallbackTileMove = (player: RpgPlayer, map) => Direction[]
@@ -23,23 +67,23 @@ type CallbackTurnMove = (player: RpgPlayer, map) => string
23
67
  type Routes = (string | Promise<any> | Direction | Direction[] | Function)[]
24
68
 
25
69
  export enum Frequency {
26
- Lowest = 600,
27
- Lower = 400,
28
- Low = 200,
29
- High = 100,
30
- Higher = 50,
31
- Highest = 25,
32
- None = 0
70
+ Lowest = 600,
71
+ Lower = 400,
72
+ Low = 200,
73
+ High = 100,
74
+ Higher = 50,
75
+ Highest = 25,
76
+ None = 0
33
77
  }
34
78
 
35
79
  export enum Speed {
36
- Slowest = 0.2,
37
- Slower = 0.5,
38
- Slow = 1,
39
- Normal = 3,
40
- Fast = 5,
41
- Faster = 7,
42
- Fastest = 10
80
+ Slowest = 0.2,
81
+ Slower = 0.5,
82
+ Slow = 1,
83
+ Normal = 3,
84
+ Fast = 5,
85
+ Faster = 7,
86
+ Fastest = 10
43
87
  }
44
88
 
45
89
  /**
@@ -71,262 +115,375 @@ export enum Speed {
71
115
  * */
72
116
  class MoveList {
73
117
 
74
- repeatMove(direction: Direction, repeat: number): Direction[] {
75
- return new Array(repeat).fill(direction)
76
- }
118
+ repeatMove(direction: Direction, repeat: number): Direction[] {
119
+ // Safety check for valid repeat value
120
+ if (!Number.isFinite(repeat) || repeat < 0 || repeat > 10000) {
121
+ console.warn('Invalid repeat value:', repeat, 'using default value 1');
122
+ repeat = 1;
123
+ }
124
+
125
+ // Ensure repeat is an integer
126
+ repeat = Math.floor(repeat);
127
+
128
+ // Additional safety check - ensure repeat is a safe integer
129
+ if (repeat < 0 || repeat > Number.MAX_SAFE_INTEGER || !Number.isSafeInteger(repeat)) {
130
+ console.warn('Unsafe repeat value:', repeat, 'using default value 1');
131
+ repeat = 1;
132
+ }
133
+
134
+ try {
135
+ return new Array(repeat).fill(direction);
136
+ } catch (error) {
137
+ console.error('Error creating array with repeat:', repeat, error);
138
+ return [direction]; // Return single direction as fallback
139
+ }
140
+ }
77
141
 
78
- private repeatTileMove(direction: string, repeat: number, propMap: string): CallbackTileMove {
79
- return (player: RpgPlayer, map): Direction[] => {
80
- const repeatTile = Math.floor(map[propMap] / player.speed) * repeat
81
- return this[direction](repeatTile)
82
- }
83
- }
142
+ private repeatTileMove(direction: string, repeat: number, propMap: string): CallbackTileMove {
143
+ return (player: RpgPlayer, map): Direction[] => {
144
+ const playerSpeed = typeof player.speed === 'function' ? player.speed() : player.speed;
145
+
146
+ // Safety checks
147
+ if (!playerSpeed || playerSpeed <= 0) {
148
+ console.warn('Invalid player speed:', playerSpeed, 'using default speed 3');
149
+ return this[direction](repeat);
150
+ }
151
+
152
+ const repeatTile = Math.floor((map[propMap] || 32) / playerSpeed) * repeat;
153
+
154
+ // Additional safety check for the calculated repeat value
155
+ if (!Number.isFinite(repeatTile) || repeatTile < 0 || repeatTile > 10000) {
156
+ console.warn('Calculated repeatTile is invalid:', repeatTile, 'using original repeat:', repeat);
157
+ return this[direction](repeat);
158
+ }
159
+
160
+ // Final safety check before calling the method
161
+ if (!Number.isSafeInteger(repeatTile)) {
162
+ console.warn('repeatTile is not a safe integer:', repeatTile, 'using original repeat:', repeat);
163
+ return this[direction](repeat);
164
+ }
165
+
166
+ try {
167
+ return this[direction](repeatTile);
168
+ } catch (error) {
169
+ console.error('Error calling direction method with repeatTile:', repeatTile, error);
170
+ return this[direction](repeat); // Fallback to original repeat
171
+ }
172
+ }
173
+ }
84
174
 
85
- right(repeat: number = 1): Direction[] {
86
- return this.repeatMove(Direction.Right, repeat)
87
- }
175
+ right(repeat: number = 1): Direction[] {
176
+ return this.repeatMove(Direction.Right, repeat)
177
+ }
88
178
 
89
- left(repeat: number = 1): Direction[] {
90
- return this.repeatMove(Direction.Left, repeat)
91
- }
179
+ left(repeat: number = 1): Direction[] {
180
+ return this.repeatMove(Direction.Left, repeat)
181
+ }
92
182
 
93
- up(repeat: number = 1): Direction[] {
94
- return this.repeatMove(Direction.Up, repeat)
95
- }
183
+ up(repeat: number = 1): Direction[] {
184
+ return this.repeatMove(Direction.Up, repeat)
185
+ }
96
186
 
97
- down(repeat: number = 1): Direction[] {
98
- return this.repeatMove(Direction.Down, repeat)
99
- }
187
+ down(repeat: number = 1): Direction[] {
188
+ return this.repeatMove(Direction.Down, repeat)
189
+ }
100
190
 
101
- wait(sec: number): Promise<unknown> {
102
- return wait(sec)
103
- }
191
+ wait(sec: number): Promise<unknown> {
192
+ return wait(sec)
193
+ }
104
194
 
105
- random(repeat: number = 1): Direction[] {
106
- return new Array(repeat).fill(null).map(() => [
107
- Direction.Right,
108
- Direction.Left,
109
- Direction.Up,
110
- Direction.Down
111
- ][random(0, 3)])
112
- }
195
+ random(repeat: number = 1): Direction[] {
196
+ // Safety check for valid repeat value
197
+ if (!Number.isFinite(repeat) || repeat < 0 || repeat > 10000) {
198
+ console.warn('Invalid repeat value in random:', repeat, 'using default value 1');
199
+ repeat = 1;
200
+ }
201
+
202
+ // Ensure repeat is an integer
203
+ repeat = Math.floor(repeat);
204
+
205
+ // Additional safety check - ensure repeat is a safe integer
206
+ if (repeat < 0 || repeat > Number.MAX_SAFE_INTEGER || !Number.isSafeInteger(repeat)) {
207
+ console.warn('Unsafe repeat value in random:', repeat, 'using default value 1');
208
+ repeat = 1;
209
+ }
210
+
211
+ try {
212
+ return new Array(repeat).fill(null).map(() => [
213
+ Direction.Right,
214
+ Direction.Left,
215
+ Direction.Up,
216
+ Direction.Down
217
+ ][random(0, 3)]);
218
+ } catch (error) {
219
+ console.error('Error creating random array with repeat:', repeat, error);
220
+ return [Direction.Down]; // Return single direction as fallback
221
+ }
222
+ }
113
223
 
114
- tileRight(repeat: number = 1): CallbackTileMove {
115
- return this.repeatTileMove('right', repeat, 'tileWidth')
116
- }
224
+ tileRight(repeat: number = 1): CallbackTileMove {
225
+ return this.repeatTileMove('right', repeat, 'tileWidth')
226
+ }
117
227
 
118
- tileLeft(repeat: number = 1): CallbackTileMove {
119
- return this.repeatTileMove('left', repeat, 'tileWidth')
120
- }
228
+ tileLeft(repeat: number = 1): CallbackTileMove {
229
+ return this.repeatTileMove('left', repeat, 'tileWidth')
230
+ }
121
231
 
122
- tileUp(repeat: number = 1): CallbackTileMove {
123
- return this.repeatTileMove('up', repeat, 'tileHeight')
124
- }
232
+ tileUp(repeat: number = 1): CallbackTileMove {
233
+ return this.repeatTileMove('up', repeat, 'tileHeight')
234
+ }
125
235
 
126
- tileDown(repeat: number = 1): CallbackTileMove {
127
- return this.repeatTileMove('down', repeat, 'tileHeight')
128
- }
236
+ tileDown(repeat: number = 1): CallbackTileMove {
237
+ return this.repeatTileMove('down', repeat, 'tileHeight')
238
+ }
129
239
 
130
- tileRandom(repeat: number = 1): CallbackTileMove {
131
- return (player: RpgPlayer, map): Direction[] => {
132
- let directions: Direction[] = []
133
- for (let i = 0; i < repeat; i++) {
134
- const randFn: CallbackTileMove = [
135
- this.tileRight(),
136
- this.tileLeft(),
137
- this.tileUp(),
138
- this.tileDown()
139
- ][random(0, 3)]
140
- directions = [
141
- ...directions,
142
- ...randFn(player, map)
143
- ]
144
- }
145
- return directions
146
- }
147
- }
240
+ tileRandom(repeat: number = 1): CallbackTileMove {
241
+ return (player: RpgPlayer, map): Direction[] => {
242
+ // Safety check for valid repeat value
243
+ if (!Number.isFinite(repeat) || repeat < 0 || repeat > 1000) {
244
+ console.warn('Invalid repeat value in tileRandom:', repeat, 'using default value 1');
245
+ repeat = 1;
246
+ }
247
+
248
+ // Ensure repeat is an integer
249
+ repeat = Math.floor(repeat);
250
+
251
+ let directions: Direction[] = []
252
+ for (let i = 0; i < repeat; i++) {
253
+ const randFn: CallbackTileMove = [
254
+ this.tileRight(),
255
+ this.tileLeft(),
256
+ this.tileUp(),
257
+ this.tileDown()
258
+ ][random(0, 3)]
259
+
260
+ try {
261
+ const newDirections = randFn(player, map);
262
+ if (Array.isArray(newDirections)) {
263
+ directions = [...directions, ...newDirections];
264
+ }
265
+ } catch (error) {
266
+ console.warn('Error in tileRandom iteration:', error);
267
+ // Continue with next iteration instead of breaking
268
+ }
269
+
270
+ // Safety check to prevent excessive array growth
271
+ if (directions.length > 10000) {
272
+ console.warn('tileRandom generated too many directions, truncating');
273
+ break;
274
+ }
275
+ }
276
+ return directions
277
+ }
278
+ }
148
279
 
149
- private _awayFromPlayerDirection(player: RpgPlayer, otherPlayer: RpgPlayer): number {
150
- const directionOtherPlayer = otherPlayer.getDirection()
151
- let newDirection = 0
152
- switch (directionOtherPlayer) {
153
- case Direction.Left:
154
- case Direction.Right:
155
- if (otherPlayer.position.x > player.position.x) {
156
- newDirection = Direction.Left
157
- }
158
- else {
159
- newDirection = Direction.Right
160
- }
161
- break
162
- case Direction.Up:
163
- case Direction.Down:
164
- if (otherPlayer.position.y > player.position.y) {
165
- newDirection = Direction.Up
166
- }
167
- else {
168
- newDirection = Direction.Down
169
- }
170
- break
171
- }
172
- return newDirection
173
- }
280
+ private _awayFromPlayerDirection(player: RpgPlayer, otherPlayer: RpgPlayer): Direction {
281
+ const directionOtherPlayer = otherPlayer.getDirection()
282
+ let newDirection: Direction = Direction.Down
174
283
 
175
- private _towardPlayerDirection(player: RpgPlayer, otherPlayer: RpgPlayer): number {
176
- const directionOtherPlayer = otherPlayer.getDirection()
177
- let newDirection = 0
178
- switch (directionOtherPlayer) {
179
- case Direction.Left:
180
- case Direction.Right:
181
- if (otherPlayer.position.x > player.position.x) {
182
- newDirection = Direction.Right
183
- }
184
- else {
185
- newDirection = Direction.Left
186
- }
187
- break
188
- case Direction.Up:
189
- case Direction.Down:
190
- if (otherPlayer.position.y > player.position.y) {
191
- newDirection = Direction.Down
192
- }
193
- else {
194
- newDirection = Direction.Up
195
- }
196
- break
197
- }
198
- return newDirection
199
- }
284
+ switch (directionOtherPlayer) {
285
+ case Direction.Left:
286
+ case Direction.Right:
287
+ if (otherPlayer.x() > player.x()) {
288
+ newDirection = Direction.Left
289
+ }
290
+ else {
291
+ newDirection = Direction.Right
292
+ }
293
+ break
294
+ case Direction.Up:
295
+ case Direction.Down:
296
+ if (otherPlayer.y() > player.y()) {
297
+ newDirection = Direction.Up
298
+ }
299
+ else {
300
+ newDirection = Direction.Down
301
+ }
302
+ break
303
+ }
304
+ return newDirection
305
+ }
200
306
 
201
- private _awayFromPlayer({ isTile, typeMov }: { isTile: boolean, typeMov: string }, otherPlayer: RpgPlayer, repeat: number = 1) {
202
- const method = (dir: number) => {
203
- const direction: string = LiteralDirection[dir]
204
- return this[isTile ? 'tile' + capitalize(direction) : direction](repeat)
205
- }
206
- return (player: RpgPlayer, map) => {
207
- let newDirection = 0
208
- switch (typeMov) {
209
- case 'away':
210
- newDirection = this._awayFromPlayerDirection(player, otherPlayer)
211
- break;
212
- case 'toward':
213
- newDirection = this._towardPlayerDirection(player, otherPlayer)
214
- break
215
- }
216
- let direction: any = method(newDirection)
217
- if (isFunction(direction)) {
218
- direction = direction(player, map)
219
- }
220
- return direction
221
- }
222
- }
307
+ private _towardPlayerDirection(player: RpgPlayer, otherPlayer: RpgPlayer): Direction {
308
+ const directionOtherPlayer = otherPlayer.getDirection()
309
+ let newDirection: Direction = Direction.Down
310
+
311
+ switch (directionOtherPlayer) {
312
+ case Direction.Left:
313
+ case Direction.Right:
314
+ if (otherPlayer.x() > player.x()) {
315
+ newDirection = Direction.Right
316
+ }
317
+ else {
318
+ newDirection = Direction.Left
319
+ }
320
+ break
321
+ case Direction.Up:
322
+ case Direction.Down:
323
+ if (otherPlayer.y() > player.y()) {
324
+ newDirection = Direction.Down
325
+ }
326
+ else {
327
+ newDirection = Direction.Up
328
+ }
329
+ break
330
+ }
331
+ return newDirection
332
+ }
223
333
 
224
- towardPlayer(player: RpgPlayer, repeat: number = 1) {
225
- return this._awayFromPlayer({ isTile: false, typeMov: 'toward' }, player, repeat)
226
- }
334
+ private _awayFromPlayer({ isTile, typeMov }: { isTile: boolean, typeMov: string }, otherPlayer: RpgPlayer, repeat: number = 1) {
335
+ const method = (dir: Direction) => {
336
+ const direction: string = DirectionNames[dir as any] || 'down'
337
+ return this[isTile ? 'tile' + capitalize(direction) : direction](repeat)
338
+ }
339
+ return (player: RpgPlayer, map) => {
340
+ let newDirection: Direction = Direction.Down
341
+ switch (typeMov) {
342
+ case 'away':
343
+ newDirection = this._awayFromPlayerDirection(player, otherPlayer)
344
+ break;
345
+ case 'toward':
346
+ newDirection = this._towardPlayerDirection(player, otherPlayer)
347
+ break
348
+ }
349
+ let direction: any = method(newDirection)
350
+ if (isFunction(direction)) {
351
+ direction = direction(player, map)
352
+ }
353
+ return direction
354
+ }
355
+ }
227
356
 
228
- tileTowardPlayer(player: RpgPlayer, repeat: number = 1) {
229
- return this._awayFromPlayer({ isTile: true, typeMov: 'toward' }, player, repeat)
230
- }
357
+ towardPlayer(player: RpgPlayer, repeat: number = 1) {
358
+ return this._awayFromPlayer({ isTile: false, typeMov: 'toward' }, player, repeat)
359
+ }
231
360
 
232
- awayFromPlayer(player: RpgPlayer, repeat: number = 1): CallbackTileMove {
233
- return this._awayFromPlayer({ isTile: false, typeMov: 'away' }, player, repeat)
234
- }
361
+ tileTowardPlayer(player: RpgPlayer, repeat: number = 1) {
362
+ return this._awayFromPlayer({ isTile: true, typeMov: 'toward' }, player, repeat)
363
+ }
235
364
 
236
- tileAwayFromPlayer(player: RpgPlayer, repeat: number = 1): CallbackTileMove {
237
- return this._awayFromPlayer({ isTile: true, typeMov: 'away' }, player, repeat)
238
- }
365
+ awayFromPlayer(player: RpgPlayer, repeat: number = 1): CallbackTileMove {
366
+ return this._awayFromPlayer({ isTile: false, typeMov: 'away' }, player, repeat)
367
+ }
239
368
 
240
- turnLeft(): string {
241
- return 'turn-' + Direction.Left
242
- }
369
+ tileAwayFromPlayer(player: RpgPlayer, repeat: number = 1): CallbackTileMove {
370
+ return this._awayFromPlayer({ isTile: true, typeMov: 'away' }, player, repeat)
371
+ }
243
372
 
244
- turnRight(): string {
245
- return 'turn-' + Direction.Right
246
- }
373
+ turnLeft(): string {
374
+ return 'turn-' + Direction.Left
375
+ }
247
376
 
248
- turnUp(): string {
249
- return 'turn-' + Direction.Up
250
- }
377
+ turnRight(): string {
378
+ return 'turn-' + Direction.Right
379
+ }
251
380
 
252
- turnDown(): string {
253
- return 'turn-' + Direction.Down
254
- }
381
+ turnUp(): string {
382
+ return 'turn-' + Direction.Up
383
+ }
255
384
 
256
- turnRandom(): string {
257
- return [
258
- this.turnRight(),
259
- this.turnLeft(),
260
- this.turnUp(),
261
- this.turnDown()
262
- ][random(0, 3)]
263
- }
385
+ turnDown(): string {
386
+ return 'turn-' + Direction.Down
387
+ }
264
388
 
265
- turnAwayFromPlayer(otherPlayer: RpgPlayer): CallbackTurnMove {
266
- return (player: RpgPlayer) => {
267
- const direction = this._awayFromPlayerDirection(player, otherPlayer)
268
- return 'turn-' + direction
269
- }
270
- }
389
+ turnRandom(): string {
390
+ return [
391
+ this.turnRight(),
392
+ this.turnLeft(),
393
+ this.turnUp(),
394
+ this.turnDown()
395
+ ][random(0, 3)]
396
+ }
271
397
 
272
- turnTowardPlayer(otherPlayer: RpgPlayer): CallbackTurnMove {
273
- return (player: RpgPlayer) => {
274
- const direction = this._towardPlayerDirection(player, otherPlayer)
275
- return 'turn-' + direction
276
- }
277
- }
278
- }
398
+ turnAwayFromPlayer(otherPlayer: RpgPlayer): CallbackTurnMove {
399
+ return (player: RpgPlayer) => {
400
+ const direction = this._awayFromPlayerDirection(player, otherPlayer)
401
+ return 'turn-' + direction
402
+ }
403
+ }
279
404
 
280
- export const Move = new MoveList()
405
+ turnTowardPlayer(otherPlayer: RpgPlayer): CallbackTurnMove {
406
+ return (player: RpgPlayer) => {
407
+ const direction = this._towardPlayerDirection(player, otherPlayer)
408
+ return 'turn-' + direction
409
+ }
410
+ }
411
+ }
281
412
 
282
- export class MoveManager {
283
- private movingSubscription: Subscription
284
- private _infiniteRoutes: Routes
285
- private _finishRoute: Function
413
+ // Direction mapping for string conversion
414
+ const DirectionNames: { [key: string]: string } = {
415
+ [Direction.Up]: 'up',
416
+ [Direction.Down]: 'down',
417
+ [Direction.Left]: 'left',
418
+ [Direction.Right]: 'right'
419
+ };
286
420
 
287
- /**
288
- * Changes the player's speed
289
- *
290
- * ```ts
291
- * player.speed = 1
292
- * ```
293
- *
294
- * You can use Speed enum
295
- *
296
- * ```ts
297
- * import { Speed } from '@rpgjs/server'
298
- * player.speed = Speed.Slow
299
- * ```
300
- *
301
- * @title Change Speed
302
- * @prop {number} player.speed
303
- * @enum {number}
304
- *
305
- * Speed.Slowest | 0.2
306
- * Speed.Slower | 0.5
307
- * Speed.Slow | 1
308
- * Speed.Normal | 3
309
- * Speed.Fast | 5
310
- * Speed.Faster | 7
311
- * Speed.Fastest | 10
312
- * @default 3
313
- * @memberof MoveManager
314
- * */
315
- speed: number
421
+ export const Move = new MoveList();
316
422
 
317
- /**
318
- * Blocks the movement. The player will not be able to move even if he presses the direction keys on the keyboard.
319
- *
320
- * ```ts
321
- * player.canMove = false
322
- * ```
323
- *
324
- * @title Block movement
325
- * @prop {boolean} player.canMove
326
- * @default true
327
- * @memberof MoveManager
328
- * */
329
- canMove: boolean
423
+ /**
424
+ * Move Manager mixin
425
+ *
426
+ * Adds comprehensive movement management capabilities to a player class.
427
+ * Provides access to all available movement strategies and utility methods
428
+ * for common movement patterns.
429
+ *
430
+ * ## Features
431
+ * - **Strategy Management**: Add, remove, and query movement strategies
432
+ * - **Predefined Movements**: Quick access to common movement patterns
433
+ * - **Composite Movements**: Combine multiple strategies
434
+ * - **Physics Integration**: Seamless integration with Matter.js physics
435
+ *
436
+ * ## Available Movement Strategies
437
+ * - `LinearMove`: Constant velocity movement
438
+ * - `Dash`: Quick burst movement
439
+ * - `Knockback`: Push effect with decay
440
+ * - `PathFollow`: Follow waypoint sequences
441
+ * - `Oscillate`: Back-and-forth patterns
442
+ * - `SeekAvoid`: AI pathfinding with obstacle avoidance
443
+ * - `LinearRepulsion`: Smoother obstacle avoidance
444
+ * - `IceMovement`: Slippery surface physics
445
+ * - `ProjectileMovement`: Ballistic trajectories
446
+ * - `CompositeMovement`: Combine multiple strategies
447
+ *
448
+ * @param Base - The base class to extend
449
+ * @returns A new class with comprehensive movement management capabilities
450
+ *
451
+ * @example
452
+ * ```ts
453
+ * // Basic usage
454
+ * class MyPlayer extends WithMoveManager(RpgCommonPlayer) {
455
+ * onInput(direction: { x: number, y: number }) {
456
+ * // Apply dash movement on input
457
+ * this.dash(direction, 8, 200);
458
+ * }
459
+ *
460
+ * onIceTerrain() {
461
+ * // Switch to ice physics
462
+ * this.clearMovements();
463
+ * this.applyIceMovement({ x: 1, y: 0 }, 4);
464
+ * }
465
+ *
466
+ * createPatrol() {
467
+ * // Create patrol path
468
+ * const waypoints = [
469
+ * { x: 100, y: 100 },
470
+ * { x: 300, y: 100 },
471
+ * { x: 300, y: 300 }
472
+ * ];
473
+ * this.followPath(waypoints, 2, true);
474
+ * }
475
+ * }
476
+ * ```
477
+ */
478
+ export function WithMoveManager<TBase extends Constructor<RpgCommonPlayer>>(
479
+ Base: TBase
480
+ ): Constructor<IMoveManager> & TBase {
481
+ return class extends Base implements IMoveManager {
482
+
483
+ // Private properties for infinite route management
484
+ private _infiniteRoutes: Routes | null = null;
485
+ private _finishRoute: ((value: boolean) => void) | null = null;
486
+ private _isInfiniteRouteActive: boolean = false;
330
487
 
331
488
  /**
332
489
  * The player passes through the other players (or vice versa). But the player does not go through the events.
@@ -340,7 +497,13 @@ export class MoveManager {
340
497
  * @default true
341
498
  * @memberof MoveManager
342
499
  * */
343
- throughOtherPlayer: boolean
500
+ set throughOtherPlayer(value: boolean) {
501
+ this._throughOtherPlayer.set(value);
502
+ }
503
+
504
+ get throughOtherPlayer(): boolean {
505
+ return this._throughOtherPlayer();
506
+ }
344
507
 
345
508
  /**
346
509
  * The player goes through the event or the other players (or vice versa)
@@ -354,7 +517,13 @@ export class MoveManager {
354
517
  * @default false
355
518
  * @memberof MoveManager
356
519
  * */
357
- through: boolean
520
+ set through(value: boolean) {
521
+ this._through.set(value);
522
+ }
523
+
524
+ get through(): boolean {
525
+ return this._through();
526
+ }
358
527
 
359
528
  /**
360
529
  * The frequency allows to put a stop time between each movement in the array of the moveRoutes() method.
@@ -385,271 +554,692 @@ export class MoveManager {
385
554
  * @default 0
386
555
  * @memberof MoveManager
387
556
  * */
388
- frequency: number
557
+ set frequency(value: number) {
558
+ this._frequency.set(value);
559
+ }
389
560
 
561
+ get frequency(): number {
562
+ return this._frequency();
563
+ }
564
+
390
565
  /**
391
- * Gives an itinerary.
566
+ * Add a custom movement strategy to this entity
392
567
  *
393
- * You can create your own motion function:
568
+ * Allows adding any custom MovementStrategy implementation.
569
+ * Multiple strategies can be active simultaneously.
394
570
  *
571
+ * @param strategy - The movement strategy to add
572
+ *
573
+ * @example
395
574
  * ```ts
396
- * import { Direction } from '@rpgjs/server'
575
+ * // Add custom movement
576
+ * const customMove = new LinearMove(5, 0, 1000);
577
+ * player.addMovement(customMove);
397
578
  *
398
- * const customMove = () => {
399
- * return [Direction.Left, Direction.Up]
400
- * }
579
+ * // Add multiple movements
580
+ * player.addMovement(new Dash(8, { x: 1, y: 0 }, 200));
581
+ * player.addMovement(new Oscillate({ x: 0, y: 1 }, 10, 1000));
582
+ * ```
583
+ */
584
+ addMovement(strategy: MovementStrategy): void {
585
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
586
+ if (!map) return;
587
+
588
+ map.moveManager.add((this as unknown as PlayerWithMixins).id, strategy);
589
+ }
590
+
591
+ /**
592
+ * Remove a specific movement strategy from this entity
593
+ *
594
+ * @param strategy - The strategy instance to remove
595
+ * @returns True if the strategy was found and removed
596
+ *
597
+ * @example
598
+ * ```ts
599
+ * const dashMove = new Dash(8, { x: 1, y: 0 }, 200);
600
+ * player.addMovement(dashMove);
401
601
  *
402
- * player.moveRoutes([ customMove() ])
602
+ * // Later, remove the specific movement
603
+ * const removed = player.removeMovement(dashMove);
604
+ * console.log('Movement removed:', removed);
403
605
  * ```
606
+ */
607
+ removeMovement(strategy: MovementStrategy): boolean {
608
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
609
+ if (!map) return false;
610
+
611
+ return map.moveManager.remove((this as unknown as PlayerWithMixins).id, strategy);
612
+ }
613
+
614
+ /**
615
+ * Remove all active movement strategies from this entity
404
616
  *
405
- * Your function can also return a function:
617
+ * Stops all current movements immediately.
406
618
  *
407
- * ```ts
408
- * import { Direction, RpgPlayer } from '@rpgjs/server'
619
+ * @example
620
+ * ```ts
621
+ * // Stop all movements when player dies
622
+ * player.clearMovements();
409
623
  *
410
- * // This function can be found in another file. By returning a function, you have access to the player who is making a move.
411
- * const customMove = (otherPlayer: RpgPlayer) => {
412
- * return (player: RpgPlayer, map) => {
413
- * return otherPlayer.position.x > player.position.x ? Direction.Left : Direction.Right
414
- * }
624
+ * // Clear movements before applying new ones
625
+ * player.clearMovements();
626
+ * player.dash({ x: 1, y: 0 });
627
+ * ```
628
+ */
629
+ clearMovements(): void {
630
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
631
+ if (!map) return;
632
+
633
+ map.moveManager.clear((this as unknown as PlayerWithMixins).id);
634
+ }
635
+
636
+ /**
637
+ * Check if this entity has any active movement strategies
638
+ *
639
+ * @returns True if entity has active movements
640
+ *
641
+ * @example
642
+ * ```ts
643
+ * // Don't accept input while movements are active
644
+ * if (!player.hasActiveMovements()) {
645
+ * player.dash(inputDirection);
415
646
  * }
416
647
  *
417
- * player.moveRoutes([ customMove(otherPlayer) ])
648
+ * // Check before adding new movement
649
+ * if (player.hasActiveMovements()) {
650
+ * player.clearMovements();
651
+ * }
418
652
  * ```
653
+ */
654
+ hasActiveMovements(): boolean {
655
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
656
+ if (!map) return false;
657
+
658
+ return map.moveManager.hasActiveStrategies((this as unknown as PlayerWithMixins).id);
659
+ }
660
+
661
+ /**
662
+ * Get all active movement strategies for this entity
419
663
  *
420
- * the function contains two parameters:
664
+ * @returns Array of active movement strategies
421
665
  *
422
- * - `player`: the player concerned by the movement
423
- * - `map`: The information of the current map
666
+ * @example
667
+ * ```ts
668
+ * // Check what movements are currently active
669
+ * const movements = player.getActiveMovements();
670
+ * console.log(`Player has ${movements.length} active movements`);
424
671
  *
425
- * @title Give an itinerary
426
- * @method player.moveRoutes(routes)
427
- * @param {Array<Move>} routes
428
- * @returns {Promise}
429
- * @memberof MoveManager
430
- * @example
672
+ * // Find specific movement type
673
+ * const hasDash = movements.some(m => m instanceof Dash);
674
+ * ```
675
+ */
676
+ getActiveMovements(): MovementStrategy[] {
677
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
678
+ if (!map) return [];
679
+
680
+ return map.moveManager.getStrategies((this as unknown as PlayerWithMixins).id);
681
+ }
682
+
683
+ /**
684
+ * Move toward a target player or position using AI pathfinding
685
+ *
686
+ * Uses SeekAvoid strategy for intelligent pathfinding with obstacle avoidance.
687
+ * The entity will seek toward the target while avoiding obstacles.
431
688
  *
689
+ * @param target - Target player or position to move toward
690
+ *
691
+ * @example
432
692
  * ```ts
433
- * import { Move } from '@rpgjs/server'
693
+ * // Move toward another player
694
+ * const targetPlayer = game.getPlayer('player2');
695
+ * player.moveTo(targetPlayer);
696
+ *
697
+ * // Move toward a specific position
698
+ * player.moveTo({ x: 300, y: 200 });
434
699
  *
435
- * await player.moveRoutes([ Move.tileLeft(), Move.tileDown(2) ])
436
- * // The path is over when the promise is resolved
700
+ * // Stop the movement later
701
+ * player.stopMoveTo();
437
702
  * ```
438
703
  */
439
- moveRoutes(routes: Routes): Promise<boolean> {
440
- let count = 0
441
- let frequence = 0
442
- this.breakRoutes() // break previous route
443
- return new Promise(async (resolve) => {
444
- this._finishRoute = resolve
445
- routes = routes.map((route: any) => {
446
- if (isFunction(route)) {
447
- const map = this.getCurrentMap()
448
- if (!map) {
449
- return undefined
450
- }
451
- return route.apply(route, [this, map])
452
- }
453
- return route
454
- })
455
- routes = arrayFlat(routes)
456
- const move = (): Observable<any> => {
457
- // If movement continues while the player no longer exists or is no longer on the map
458
- if (!this) {
459
- return of(null)
460
- }
461
- // if map not exists
462
- if (!this.getCurrentMap()) {
463
- return of(null)
464
- }
465
- if (count >= this['nbPixelInTile']) {
466
- if (frequence < this.frequency) {
467
- frequence++
468
- return of(null)
469
- }
470
- }
471
-
472
- frequence = 0
473
- count++
704
+ moveTo(target: RpgCommonPlayer | { x: number, y: number }): void {
705
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
706
+ if (!map) return;
707
+
708
+ let targetBody: Matter.Body | null = null;
709
+
710
+ if ('id' in target) {
711
+ // Target is a player
712
+ targetBody = map.physic.getBody(target.id);
713
+ } else {
714
+ // Target is a position - create a temporary target function
715
+ const getTargetPos = () => Matter.Vector.create(target.x, target.y);
716
+ map.moveManager.add(
717
+ (this as unknown as PlayerWithMixins).id,
718
+ new SeekAvoid(map.physic, getTargetPos, 3, 50, 5)
719
+ );
720
+ return;
721
+ }
722
+
723
+ if (targetBody) {
724
+ map.moveManager.add(
725
+ (this as unknown as PlayerWithMixins).id,
726
+ new SeekAvoid(map.physic, targetBody, 3, 50, 5)
727
+ );
728
+ }
729
+ }
474
730
 
475
- const [route] = routes
731
+ /**
732
+ * Stop the current moveTo behavior
733
+ *
734
+ * Removes any active SeekAvoid strategies.
735
+ *
736
+ * @example
737
+ * ```ts
738
+ * // Start following a target
739
+ * player.moveTo(targetPlayer);
740
+ *
741
+ * // Stop following when target is reached
742
+ * if (distanceToTarget < 10) {
743
+ * player.stopMoveTo();
744
+ * }
745
+ * ```
746
+ */
747
+ stopMoveTo(): void {
748
+ const map = (this as unknown as PlayerWithMixins).getCurrentMap();
749
+ if (!map) return;
750
+
751
+ const strategies = this.getActiveMovements();
752
+ strategies.forEach(strategy => {
753
+ if (strategy instanceof SeekAvoid || strategy instanceof LinearRepulsion) {
754
+ this.removeMovement(strategy);
755
+ }
756
+ });
757
+ }
476
758
 
477
- if (route === undefined) {
478
- this.breakRoutes()
479
- return of(null)
480
- }
759
+ /**
760
+ * Perform a dash movement in the specified direction
761
+ *
762
+ * Applies high-speed movement for a short duration.
763
+ *
764
+ * @param direction - Normalized direction vector
765
+ * @param speed - Movement speed (default: 8)
766
+ * @param duration - Duration in milliseconds (default: 200)
767
+ *
768
+ * @example
769
+ * ```ts
770
+ * // Dash right
771
+ * player.dash({ x: 1, y: 0 });
772
+ *
773
+ * // Dash diagonally with custom speed and duration
774
+ * player.dash({ x: 0.7, y: 0.7 }, 12, 300);
775
+ *
776
+ * // Dash in input direction
777
+ * player.dash(inputDirection, 10, 150);
778
+ * ```
779
+ */
780
+ dash(direction: { x: number, y: number }, speed: number = 8, duration: number = 200): void {
781
+ this.addMovement(new Dash(speed, direction, duration));
782
+ }
481
783
 
482
- let ob$ = new Observable()
483
-
484
- switch (route) {
485
- case Direction.Left:
486
- case Direction.Down:
487
- case Direction.Right:
488
- case Direction.Up:
489
- ob$ = from(this.moveByDirection(route, 1))
490
- break
491
- case 'turn-' + Direction.Left:
492
- ob$ = of(this.changeDirection(Direction.Left))
493
- break
494
- case 'turn-' + Direction.Right:
495
- ob$ = of(this.changeDirection(Direction.Right))
496
- break
497
- case 'turn-' + Direction.Up:
498
- ob$ = of(this.changeDirection(Direction.Up))
499
- break
500
- case 'turn-' + Direction.Down:
501
- ob$ = of(this.changeDirection(Direction.Down))
502
- break
503
- }
784
+ /**
785
+ * Apply knockback effect in the specified direction
786
+ *
787
+ * Creates a push effect that gradually decreases over time.
788
+ *
789
+ * @param direction - Normalized direction vector
790
+ * @param force - Initial knockback force (default: 5)
791
+ * @param duration - Duration in milliseconds (default: 300)
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * // Knockback from explosion
796
+ * const explosionDir = { x: -1, y: 0 };
797
+ * player.knockback(explosionDir, 8, 400);
798
+ *
799
+ * // Light knockback from attack
800
+ * player.knockback(attackDirection, 3, 200);
801
+ * ```
802
+ */
803
+ knockback(direction: { x: number, y: number }, force: number = 5, duration: number = 300): void {
804
+ this.addMovement(new Knockback(direction, force, duration));
805
+ }
504
806
 
505
- return ob$.pipe(
506
- tap(() => {
507
- routes.shift()
508
- })
509
- )
510
- }
511
- this.movingSubscription = this.server.tick
512
- .pipe(
513
- takeUntil(
514
- this._destroy$.pipe(
515
- tap(() => {
516
- this.breakRoutes(true)
517
- })
518
- )),
519
- switchMap(move)
520
- )
521
- .subscribe()
522
- })
807
+ /**
808
+ * Follow a sequence of waypoints
809
+ *
810
+ * Entity will move through each waypoint in order.
811
+ *
812
+ * @param waypoints - Array of x,y positions to follow
813
+ * @param speed - Movement speed (default: 2)
814
+ * @param loop - Whether to loop back to start (default: false)
815
+ *
816
+ * @example
817
+ * ```ts
818
+ * // Create a patrol route
819
+ * const patrolPoints = [
820
+ * { x: 100, y: 100 },
821
+ * { x: 300, y: 100 },
822
+ * { x: 300, y: 300 },
823
+ * { x: 100, y: 300 }
824
+ * ];
825
+ * player.followPath(patrolPoints, 3, true);
826
+ *
827
+ * // One-time path to destination
828
+ * player.followPath([{ x: 500, y: 200 }], 4);
829
+ * ```
830
+ */
831
+ followPath(waypoints: Array<{ x: number, y: number }>, speed: number = 2, loop: boolean = false): void {
832
+ this.addMovement(new PathFollow(waypoints, speed, loop));
523
833
  }
524
834
 
525
835
  /**
526
- * Giving a path that repeats itself in a loop to a character
836
+ * Apply oscillating movement pattern
527
837
  *
528
- * You can stop the movement at any time with `breakRoutes()` and replay it with `replayRoutes()`.
838
+ * Entity moves back and forth along the specified axis.
529
839
  *
530
- * @title Infinite Move Routes
531
- * @method player.infiniteMoveRoute(routes)
532
- * @param {Array<Move>} routes
533
- * @returns {void}
534
- * @memberof MoveManager
535
- * @example
840
+ * @param direction - Primary oscillation axis (normalized)
841
+ * @param amplitude - Maximum distance from center (default: 50)
842
+ * @param period - Time for complete cycle in ms (default: 2000)
536
843
  *
844
+ * @example
537
845
  * ```ts
538
- * import { Move } from '@rpgjs/server'
846
+ * // Horizontal oscillation
847
+ * player.oscillate({ x: 1, y: 0 }, 100, 3000);
848
+ *
849
+ * // Vertical oscillation
850
+ * player.oscillate({ x: 0, y: 1 }, 30, 1500);
539
851
  *
540
- * player.infiniteMoveRoute([ Move.tileRandom() ])
852
+ * // Diagonal oscillation
853
+ * player.oscillate({ x: 0.7, y: 0.7 }, 75, 2500);
541
854
  * ```
542
855
  */
543
- infiniteMoveRoute(routes: Routes): void {
544
- this._infiniteRoutes = routes
856
+ oscillate(direction: { x: number, y: number }, amplitude: number = 50, period: number = 2000): void {
857
+ this.addMovement(new Oscillate(direction, amplitude, period));
858
+ }
545
859
 
546
- const move = (isBreaking: boolean) => {
547
- if (isBreaking) return
548
- this.moveRoutes(routes).then(move)
549
- }
860
+ /**
861
+ * Apply ice movement physics
862
+ *
863
+ * Creates slippery movement with gradual acceleration and inertia.
864
+ * Perfect for ice terrains or slippery surfaces.
865
+ *
866
+ * @param direction - Target movement direction
867
+ * @param maxSpeed - Maximum speed when fully accelerated (default: 4)
868
+ *
869
+ * @example
870
+ * ```ts
871
+ * // Apply ice physics when on ice terrain
872
+ * if (onIceTerrain) {
873
+ * player.applyIceMovement(inputDirection, 5);
874
+ * }
875
+ *
876
+ * // Update direction when input changes
877
+ * iceMovement.setTargetDirection(newDirection);
878
+ * ```
879
+ */
880
+ applyIceMovement(direction: { x: number, y: number }, maxSpeed: number = 4): void {
881
+ this.addMovement(new IceMovement(direction, maxSpeed));
882
+ }
550
883
 
551
- move(false)
884
+ /**
885
+ * Shoot a projectile in the specified direction
886
+ *
887
+ * Creates projectile movement with various trajectory types.
888
+ *
889
+ * @param type - Type of projectile trajectory
890
+ * @param direction - Normalized direction vector
891
+ * @param speed - Projectile speed (default: 200)
892
+ *
893
+ * @example
894
+ * ```ts
895
+ * // Shoot arrow
896
+ * player.shootProjectile(ProjectileType.Straight, { x: 1, y: 0 }, 300);
897
+ *
898
+ * // Throw grenade with arc
899
+ * player.shootProjectile(ProjectileType.Arc, { x: 0.7, y: 0.7 }, 150);
900
+ *
901
+ * // Bouncing projectile
902
+ * player.shootProjectile(ProjectileType.Bounce, { x: 1, y: 0 }, 100);
903
+ * ```
904
+ */
905
+ shootProjectile(type: ProjectileType, direction: { x: number, y: number }, speed: number = 200): void {
906
+ const config = {
907
+ speed,
908
+ direction,
909
+ maxRange: type === ProjectileType.Straight ? 500 : undefined,
910
+ maxHeight: type === ProjectileType.Arc ? 100 : undefined,
911
+ gravity: type !== ProjectileType.Straight ? 400 : undefined,
912
+ maxBounces: type === ProjectileType.Bounce ? 3 : undefined,
913
+ bounciness: type === ProjectileType.Bounce ? 0.6 : undefined
914
+ };
915
+
916
+ this.addMovement(new ProjectileMovement(type, config));
552
917
  }
553
918
 
554
919
  /**
555
- * Works only for infinite movements. You must first use the method `infiniteMoveRoute()`
920
+ * Give an itinerary to follow using movement strategies
556
921
  *
557
- * @title Stop an infinite movement
558
- * @method player.breakRoutes(force=false)
559
- * @param {boolean} [force] Forces the stop of the infinite movement
560
- * @returns {void}
561
- * @memberof MoveManager
562
- * @example
922
+ * Executes a sequence of movements and actions in order. Each route can be:
923
+ * - A Direction enum value for basic movement
924
+ * - A string starting with "turn-" for direction changes
925
+ * - A function that returns directions or actions
926
+ * - A Promise for async operations
927
+ *
928
+ * The method processes routes sequentially, respecting the entity's frequency
929
+ * setting for timing between movements.
563
930
  *
931
+ * @param routes - Array of movement instructions to execute
932
+ * @returns Promise that resolves when all routes are completed
933
+ *
934
+ * @example
564
935
  * ```ts
565
- * import { Move } from '@rpgjs/server'
936
+ * // Basic directional movements
937
+ * await player.moveRoutes([
938
+ * Direction.Right,
939
+ * Direction.Up,
940
+ * Direction.Left
941
+ * ]);
942
+ *
943
+ * // Mix of movements and turns
944
+ * await player.moveRoutes([
945
+ * Direction.Right,
946
+ * 'turn-' + Direction.Up,
947
+ * Direction.Up
948
+ * ]);
949
+ *
950
+ * // Using functions for dynamic behavior
951
+ * const customMove = (player, map) => [Direction.Right, Direction.Down];
952
+ * await player.moveRoutes([customMove]);
566
953
  *
567
- * player.infiniteMoveRoute([ Move.tileRandom() ])
568
- * player.breakRoutes(true)
954
+ * // With async operations
955
+ * await player.moveRoutes([
956
+ * Direction.Right,
957
+ * new Promise(resolve => setTimeout(resolve, 1000)), // Wait 1 second
958
+ * Direction.Left
959
+ * ]);
569
960
  * ```
570
961
  */
571
- breakRoutes(force: boolean = false): void {
572
- if (this._finishRoute) {
573
- this.movingSubscription?.unsubscribe()
574
- this._finishRoute(force)
962
+ moveRoutes(routes: Routes): Promise<boolean> {
963
+ let count = 0;
964
+ let frequence = 0;
965
+ const player = this as unknown as PlayerWithMixins;
966
+
967
+ // Break any existing route movement
968
+ this.clearMovements();
969
+
970
+ return new Promise(async (resolve) => {
971
+ // Store the resolve function for potential breaking
972
+ this._finishRoute = resolve;
973
+
974
+ // Process function routes first
975
+ const processedRoutes = routes.map((route: any) => {
976
+ if (typeof route === 'function') {
977
+ const map = player.getCurrentMap();
978
+ if (!map) {
979
+ return undefined;
980
+ }
981
+ return route.apply(route, [player, map]);
982
+ }
983
+ return route;
984
+ });
985
+
986
+ // Flatten nested arrays
987
+ const flatRoutes = this.flattenRoutes(processedRoutes);
988
+ let routeIndex = 0;
989
+
990
+ const executeNextRoute = async (): Promise<void> => {
991
+ // Check if player still exists and is on a map
992
+ if (!player || !player.getCurrentMap()) {
993
+ this._finishRoute = null;
994
+ resolve(false);
995
+ return;
996
+ }
997
+
998
+ // Handle frequency timing
999
+ if (count >= (player.nbPixelInTile || 32)) {
1000
+ if (frequence < (player.frequency || 0)) {
1001
+ frequence++;
1002
+ setTimeout(executeNextRoute, 16); // ~60fps timing
1003
+ return;
1004
+ }
1005
+ }
1006
+
1007
+ frequence = 0;
1008
+ count++;
1009
+
1010
+ // Check if we've completed all routes
1011
+ if (routeIndex >= flatRoutes.length) {
1012
+ this._finishRoute = null;
1013
+ resolve(true);
1014
+ return;
1015
+ }
1016
+
1017
+ const currentRoute = flatRoutes[routeIndex];
1018
+ routeIndex++;
1019
+
1020
+ if (currentRoute === undefined) {
1021
+ executeNextRoute();
1022
+ return;
1023
+ }
1024
+
1025
+ try {
1026
+ // Handle different route types
1027
+ if (typeof currentRoute === 'object' && 'then' in currentRoute) {
1028
+ // Handle Promise
1029
+ await currentRoute;
1030
+ executeNextRoute();
1031
+ } else if (typeof currentRoute === 'string' && currentRoute.startsWith('turn-')) {
1032
+ // Handle turn commands
1033
+ const directionStr = currentRoute.replace('turn-', '');
1034
+ let direction: Direction = Direction.Down;
1035
+
1036
+ // Convert string direction to Direction enum
1037
+ switch (directionStr) {
1038
+ case 'up':
1039
+ case Direction.Up:
1040
+ direction = Direction.Up;
1041
+ break;
1042
+ case 'down':
1043
+ case Direction.Down:
1044
+ direction = Direction.Down;
1045
+ break;
1046
+ case 'left':
1047
+ case Direction.Left:
1048
+ direction = Direction.Left;
1049
+ break;
1050
+ case 'right':
1051
+ case Direction.Right:
1052
+ direction = Direction.Right;
1053
+ break;
1054
+ }
1055
+
1056
+ if (player.changeDirection) {
1057
+ player.changeDirection(direction);
1058
+ }
1059
+ executeNextRoute();
1060
+ } else if (typeof currentRoute === 'number') {
1061
+ // Handle Direction enum values
1062
+ if (player.moveByDirection) {
1063
+ await player.moveByDirection(currentRoute as unknown as Direction, 1);
1064
+ } else {
1065
+ // Fallback to movement strategy - use direction as velocity components
1066
+ let vx = 0, vy = 0;
1067
+ const direction = currentRoute as unknown as Direction;
1068
+ switch (direction) {
1069
+ case Direction.Right: vx = 1; break;
1070
+ case Direction.Left: vx = -1; break;
1071
+ case Direction.Down: vy = 1; break;
1072
+ case Direction.Up: vy = -1; break;
1073
+ }
1074
+ this.addMovement(new LinearMove(vx * (player.speed?.() || 3), vy * (player.speed?.() || 3), 100));
1075
+ setTimeout(executeNextRoute, 100);
1076
+ return;
1077
+ }
1078
+ executeNextRoute();
1079
+ } else {
1080
+ // Unknown route type, skip
1081
+ executeNextRoute();
1082
+ }
1083
+ } catch (error) {
1084
+ console.warn('Error executing route:', error);
1085
+ executeNextRoute();
1086
+ }
1087
+ };
1088
+
1089
+ executeNextRoute();
1090
+ });
1091
+ }
1092
+
1093
+ /**
1094
+ * Utility method to flatten nested route arrays
1095
+ *
1096
+ * @private
1097
+ * @param routes - Routes array that may contain nested arrays
1098
+ * @returns Flattened array of routes
1099
+ */
1100
+ private flattenRoutes(routes: any[]): any[] {
1101
+ const result: any[] = [];
1102
+
1103
+ for (const route of routes) {
1104
+ if (Array.isArray(route)) {
1105
+ result.push(...this.flattenRoutes(route));
1106
+ } else {
1107
+ result.push(route);
575
1108
  }
1109
+ }
1110
+
1111
+ return result;
576
1112
  }
577
1113
 
578
1114
  /**
579
- * Works only for infinite movements. You must first use the method `infiniteMoveRoute()`
580
- * If the road was stopped with `breakRoutes()`, you can restart it with this method
1115
+ * Give a path that repeats itself in a loop to a character
581
1116
  *
582
- * @title Replay an infinite movement
583
- * @method player.replayRoutes()
584
- * @returns {void}
585
- * @memberof MoveManager
586
- * @example
1117
+ * Creates an infinite movement pattern that continues until manually stopped.
1118
+ * The routes will repeat in a continuous loop, making it perfect for patrol
1119
+ * patterns, ambient movements, or any repetitive behavior.
587
1120
  *
1121
+ * You can stop the movement at any time with `breakRoutes()` and replay it
1122
+ * with `replayRoutes()`.
1123
+ *
1124
+ * @param routes - Array of movement instructions to repeat infinitely
1125
+ *
1126
+ * @example
588
1127
  * ```ts
589
- * import { Move } from '@rpgjs/server'
1128
+ * // Create an infinite random movement pattern
1129
+ * player.infiniteMoveRoute([Move.random()]);
590
1130
  *
591
- * player.infiniteMoveRoute([ Move.tileRandom() ])
592
- * player.breakRoutes(true)
593
- * player.replayRoutes()
1131
+ * // Create a patrol route
1132
+ * player.infiniteMoveRoute([
1133
+ * Direction.Right,
1134
+ * Direction.Right,
1135
+ * Direction.Down,
1136
+ * Direction.Left,
1137
+ * Direction.Left,
1138
+ * Direction.Up
1139
+ * ]);
1140
+ *
1141
+ * // Mix movements and rotations
1142
+ * player.infiniteMoveRoute([
1143
+ * Move.turnRight(),
1144
+ * Direction.Right,
1145
+ * Move.wait(1),
1146
+ * Move.turnLeft(),
1147
+ * Direction.Left
1148
+ * ]);
594
1149
  * ```
595
1150
  */
596
- replayRoutes(): void {
597
- if (this._infiniteRoutes) this.infiniteMoveRoute(this._infiniteRoutes)
1151
+ infiniteMoveRoute(routes: Routes): void {
1152
+ this._infiniteRoutes = routes;
1153
+ this._isInfiniteRouteActive = true;
1154
+
1155
+ const executeInfiniteRoute = (isBreaking: boolean = false) => {
1156
+ if (isBreaking || !this._isInfiniteRouteActive) return;
1157
+
1158
+ this.moveRoutes(routes).then((completed) => {
1159
+ // Only continue if the route completed successfully and we're still active
1160
+ if (completed && this._isInfiniteRouteActive) {
1161
+ executeInfiniteRoute();
1162
+ }
1163
+ }).catch((error) => {
1164
+ console.warn('Error in infinite route execution:', error);
1165
+ // Try to continue even if there was an error
1166
+ if (this._isInfiniteRouteActive) {
1167
+ setTimeout(() => executeInfiniteRoute(), 100);
1168
+ }
1169
+ });
1170
+ };
1171
+
1172
+ executeInfiniteRoute();
598
1173
  }
599
1174
 
600
1175
  /**
601
- * Move the event to another event, a player, a shape or a specific position.
602
- * The event will avoid obstacles, but you can tell if it is stuck or has completed its path
603
- *
604
- * @title Move To
605
- * @method player.moveTo()
606
- * @param {RpgPlayer|RpgEvent|RpgShape|Position} target the target
607
- * @param {object} [options] - animate. Set a boolean to use default parameters
608
- * @param {boolean} [options.infinite=false] - moves infinitely towards the target, you have to stop its movement manually with the method `stopMoveTo()`
609
- * @param {() => void} [options.onComplete] - Callback when the event arrives at the destination
610
- * @param {(duration:number) => void} [options.onStuck] - callback when the event is blocked against a wall. Duration gives you the duration (in frames) of the blocking time
611
- * @returns {Observable<void>}
612
- * @since 3.2.0
613
- * @memberof MoveManager
614
- * @example
1176
+ * Stop an infinite movement
615
1177
  *
1178
+ * Works only for infinite movements created with `infiniteMoveRoute()`.
1179
+ * This method stops the current route execution and prevents the next
1180
+ * iteration from starting.
1181
+ *
1182
+ * @param force - Forces the stop of the infinite movement immediately
1183
+ *
1184
+ * @example
616
1185
  * ```ts
617
- * import { Move } from '@rpgjs/server'
1186
+ * // Start infinite movement
1187
+ * player.infiniteMoveRoute([Move.random()]);
618
1188
  *
619
- * player.moveTo(otherPlayer).subscribe()
1189
+ * // Stop it when player enters combat
1190
+ * if (inCombat) {
1191
+ * player.breakRoutes(true);
1192
+ * }
1193
+ *
1194
+ * // Gentle stop (completes current route first)
1195
+ * player.breakRoutes();
620
1196
  * ```
621
1197
  */
622
- moveTo(event: RpgEvent, options?: MoveTo): Observable<void>
623
- moveTo(player: RpgPlayer, options?: MoveTo): Observable<void>
624
- moveTo(position: PositionXY, options?: MoveTo): Observable<void>
625
- moveTo(shape: RpgShape, options?: MoveTo): Observable<void>
626
- moveTo(position: RpgPlayer | RpgShape | PositionXY, options?: MoveTo): Observable<void> {
627
- return this['_moveTo'](this.server.tick, position, options)
1198
+ breakRoutes(force: boolean = false): void {
1199
+ this._isInfiniteRouteActive = false;
1200
+
1201
+ if (force) {
1202
+ // Force stop by clearing all movements immediately
1203
+ this.clearMovements();
1204
+ }
1205
+
1206
+ // If there's an active route promise, resolve it
1207
+ if (this._finishRoute) {
1208
+ this._finishRoute(force);
1209
+ this._finishRoute = null;
1210
+ }
628
1211
  }
629
1212
 
630
- // TODO
631
- setMoveMode(mode: MoveMode): void {
632
- if (mode.checkCollision) this.checkCollision = mode.checkCollision
633
- if (mode.clientMode) this.clientModeMove = mode.clientMode
634
- if (mode.behavior) this.behavior = mode.behavior
635
- this.emit(SocketEvents.CallMethod, {
636
- objectId: this.id,
637
- name: SocketMethods.ModeMove,
638
- params: [mode]
639
- })
1213
+ /**
1214
+ * Replay an infinite movement
1215
+ *
1216
+ * Works only for infinite movements that were previously created with
1217
+ * `infiniteMoveRoute()`. If the route was stopped with `breakRoutes()`,
1218
+ * you can restart it with this method using the same route configuration.
1219
+ *
1220
+ * @example
1221
+ * ```ts
1222
+ * // Create infinite movement
1223
+ * player.infiniteMoveRoute([Move.random()]);
1224
+ *
1225
+ * // Stop it temporarily
1226
+ * player.breakRoutes(true);
1227
+ *
1228
+ * // Resume the same movement pattern
1229
+ * player.replayRoutes();
1230
+ *
1231
+ * // Stop and start with different conditions
1232
+ * if (playerNearby) {
1233
+ * player.breakRoutes();
1234
+ * } else {
1235
+ * player.replayRoutes();
1236
+ * }
1237
+ * ```
1238
+ */
1239
+ replayRoutes(): void {
1240
+ if (this._infiniteRoutes && !this._isInfiniteRouteActive) {
1241
+ this.infiniteMoveRoute(this._infiniteRoutes);
1242
+ }
640
1243
  }
1244
+ };
641
1245
  }
642
-
643
- export interface MoveManager {
644
- moveByDirection: (direction: Direction, deltaTimeInt: number) => Promise<boolean>
645
- changeDirection: (direction: Direction) => boolean
646
- getCurrentMap: any
647
- checkCollision: boolean
648
- clientModeMove: ClientMode
649
- behavior: Behavior
650
- emit(name: SocketEvents, params: any)
651
- id: string
652
- server: RpgServerEngine
653
- position: Vector2d
654
- _destroy$: Subject<void>
655
- }