@rpgjs/server 5.0.0-alpha.4 → 5.0.0-alpha.41

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 (101) hide show
  1. package/dist/Gui/DialogGui.d.ts +5 -0
  2. package/dist/Gui/GameoverGui.d.ts +23 -0
  3. package/dist/Gui/Gui.d.ts +6 -0
  4. package/dist/Gui/MenuGui.d.ts +22 -3
  5. package/dist/Gui/NotificationGui.d.ts +1 -2
  6. package/dist/Gui/SaveLoadGui.d.ts +13 -0
  7. package/dist/Gui/ShopGui.d.ts +28 -3
  8. package/dist/Gui/TitleGui.d.ts +23 -0
  9. package/dist/Gui/index.d.ts +10 -1
  10. package/dist/Player/BattleManager.d.ts +34 -12
  11. package/dist/Player/ClassManager.d.ts +46 -13
  12. package/dist/Player/ComponentManager.d.ts +123 -0
  13. package/dist/Player/Components.d.ts +345 -0
  14. package/dist/Player/EffectManager.d.ts +86 -0
  15. package/dist/Player/ElementManager.d.ts +104 -0
  16. package/dist/Player/GoldManager.d.ts +22 -0
  17. package/dist/Player/GuiManager.d.ts +259 -0
  18. package/dist/Player/ItemFixture.d.ts +6 -0
  19. package/dist/Player/ItemManager.d.ts +450 -9
  20. package/dist/Player/MoveManager.d.ts +324 -69
  21. package/dist/Player/ParameterManager.d.ts +344 -14
  22. package/dist/Player/Player.d.ts +460 -8
  23. package/dist/Player/SkillManager.d.ts +197 -15
  24. package/dist/Player/StateManager.d.ts +89 -25
  25. package/dist/Player/VariableManager.d.ts +74 -0
  26. package/dist/RpgServer.d.ts +502 -64
  27. package/dist/RpgServerEngine.d.ts +2 -1
  28. package/dist/decorators/event.d.ts +46 -0
  29. package/dist/decorators/map.d.ts +287 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +21653 -20900
  32. package/dist/index.js.map +1 -1
  33. package/dist/logs/log.d.ts +2 -3
  34. package/dist/module.d.ts +43 -1
  35. package/dist/presets/index.d.ts +0 -9
  36. package/dist/rooms/BaseRoom.d.ts +132 -0
  37. package/dist/rooms/lobby.d.ts +10 -2
  38. package/dist/rooms/map.d.ts +1236 -17
  39. package/dist/services/save.d.ts +43 -0
  40. package/dist/storage/index.d.ts +1 -0
  41. package/dist/storage/localStorage.d.ts +23 -0
  42. package/package.json +14 -10
  43. package/src/Gui/DialogGui.ts +19 -4
  44. package/src/Gui/GameoverGui.ts +39 -0
  45. package/src/Gui/Gui.ts +23 -1
  46. package/src/Gui/MenuGui.ts +155 -6
  47. package/src/Gui/NotificationGui.ts +1 -2
  48. package/src/Gui/SaveLoadGui.ts +60 -0
  49. package/src/Gui/ShopGui.ts +146 -16
  50. package/src/Gui/TitleGui.ts +39 -0
  51. package/src/Gui/index.ts +15 -2
  52. package/src/Player/BattleManager.ts +91 -49
  53. package/src/Player/ClassManager.ts +118 -50
  54. package/src/Player/ComponentManager.ts +425 -19
  55. package/src/Player/Components.ts +380 -0
  56. package/src/Player/EffectManager.ts +81 -44
  57. package/src/Player/ElementManager.ts +109 -86
  58. package/src/Player/GoldManager.ts +32 -35
  59. package/src/Player/GuiManager.ts +308 -150
  60. package/src/Player/ItemFixture.ts +4 -5
  61. package/src/Player/ItemManager.ts +774 -355
  62. package/src/Player/MoveManager.ts +1544 -774
  63. package/src/Player/ParameterManager.ts +546 -104
  64. package/src/Player/Player.ts +1163 -88
  65. package/src/Player/SkillManager.ts +520 -195
  66. package/src/Player/StateManager.ts +170 -182
  67. package/src/Player/VariableManager.ts +101 -63
  68. package/src/RpgServer.ts +525 -63
  69. package/src/core/context.ts +1 -0
  70. package/src/decorators/event.ts +61 -0
  71. package/src/decorators/map.ts +327 -0
  72. package/src/index.ts +11 -1
  73. package/src/logs/log.ts +10 -3
  74. package/src/module.ts +126 -3
  75. package/src/presets/index.ts +1 -10
  76. package/src/rooms/BaseRoom.ts +232 -0
  77. package/src/rooms/lobby.ts +25 -7
  78. package/src/rooms/map.ts +2502 -194
  79. package/src/services/save.ts +147 -0
  80. package/src/storage/index.ts +1 -0
  81. package/src/storage/localStorage.ts +76 -0
  82. package/tests/battle.spec.ts +375 -0
  83. package/tests/change-map.spec.ts +72 -0
  84. package/tests/class.spec.ts +274 -0
  85. package/tests/effect.spec.ts +219 -0
  86. package/tests/element.spec.ts +221 -0
  87. package/tests/event.spec.ts +80 -0
  88. package/tests/gold.spec.ts +99 -0
  89. package/tests/item.spec.ts +609 -0
  90. package/tests/module.spec.ts +38 -0
  91. package/tests/move.spec.ts +601 -0
  92. package/tests/player-param.spec.ts +28 -0
  93. package/tests/prediction-reconciliation.spec.ts +182 -0
  94. package/tests/random-move.spec.ts +65 -0
  95. package/tests/skill.spec.ts +658 -0
  96. package/tests/state.spec.ts +467 -0
  97. package/tests/variable.spec.ts +185 -0
  98. package/tests/world-maps.spec.ts +896 -0
  99. package/vite.config.ts +16 -0
  100. package/dist/Player/Event.d.ts +0 -0
  101. package/src/Player/Event.ts +0 -0
@@ -1,7 +1,26 @@
1
- import { MockConnection, RoomOnJoin } from '@signe/room';
2
- import { Hooks, RpgCommonMap, ZoneData } from '@rpgjs/common';
1
+ import { MockConnection, RoomMethods, RoomOnJoin } from '@signe/room';
2
+ import { Hooks, RpgCommonMap, RpgShape, MapPhysicsInitContext, MapPhysicsEntityContext, WorldMapsManager, WeatherState, WorldMapConfig } from '../../../common/src';
3
3
  import { RpgPlayer, RpgEvent } from '../Player/Player';
4
4
  import { BehaviorSubject } from 'rxjs';
5
+ import { MapOptions } from '../decorators/map';
6
+ import { EventMode } from '../decorators/event';
7
+ /**
8
+ * Interface for input controls configuration
9
+ *
10
+ * Defines the structure for input validation and anti-cheat controls
11
+ */
12
+ export interface Controls {
13
+ /** Maximum allowed time delta between inputs in milliseconds */
14
+ maxTimeDelta?: number;
15
+ /** Maximum allowed frame delta between inputs */
16
+ maxFrameDelta?: number;
17
+ /** Minimum time between inputs in milliseconds */
18
+ minTimeBetweenInputs?: number;
19
+ /** Whether to enable anti-cheat validation */
20
+ enableAntiCheat?: boolean;
21
+ /** Maximum number of queued inputs processed per server tick */
22
+ maxInputsPerTick?: number;
23
+ }
5
24
  /**
6
25
  * Interface representing hook methods available for map events
7
26
  *
@@ -17,11 +36,13 @@ export interface EventHooks {
17
36
  /** Called when a player touches this event */
18
37
  onPlayerTouch?: (player: RpgPlayer) => void;
19
38
  /** Called when a player enters a shape */
20
- onInShape?: (zone: ZoneData, player: RpgPlayer) => void;
39
+ onInShape?: (zone: RpgShape, player: RpgPlayer) => void;
21
40
  /** Called when a player exits a shape */
22
- onOutShape?: (zone: ZoneData, player: RpgPlayer) => void;
23
- onDetectInShape?: (player: RpgPlayer, shape: ZoneData) => void;
24
- onDetectOutShape?: (player: RpgPlayer, shape: ZoneData) => void;
41
+ onOutShape?: (zone: RpgShape, player: RpgPlayer) => void;
42
+ /** Called when a player is detected entering a shape */
43
+ onDetectInShape?: (player: RpgPlayer, shape: RpgShape) => void;
44
+ /** Called when a player is detected exiting a shape */
45
+ onDetectOutShape?: (player: RpgPlayer, shape: RpgShape) => void;
25
46
  }
26
47
  /** Type for event class constructor */
27
48
  export type EventConstructor = new () => RpgPlayer;
@@ -30,9 +51,13 @@ export type EventPosOption = {
30
51
  /** ID of the event */
31
52
  id?: string;
32
53
  /** X position of the event on the map */
33
- x: number;
54
+ x?: number;
34
55
  /** Y position of the event on the map */
35
- y: number;
56
+ y?: number;
57
+ /** Event mode override */
58
+ mode?: EventMode | "shared" | "scenario";
59
+ /** Owner player id when mode is scenario */
60
+ scenarioOwnerId?: string;
36
61
  /**
37
62
  * Event definition - can be either:
38
63
  * - A class that extends RpgPlayer
@@ -40,25 +65,661 @@ export type EventPosOption = {
40
65
  */
41
66
  event: EventConstructor | (EventHooks & Record<string, any>);
42
67
  };
68
+ type CreateDynamicEventOptions = {
69
+ mode?: EventMode | "shared" | "scenario";
70
+ scenarioOwnerId?: string;
71
+ };
72
+ interface WeatherSetOptions {
73
+ sync?: boolean;
74
+ }
43
75
  export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
76
+ /**
77
+ * Synchronized signal containing all players currently on the map
78
+ *
79
+ * This signal is automatically synchronized with clients using @signe/sync.
80
+ * Players are indexed by their unique ID.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * // Get all players
85
+ * const allPlayers = map.players();
86
+ *
87
+ * // Get a specific player
88
+ * const player = map.players()['player-id'];
89
+ * ```
90
+ */
44
91
  players: import('@signe/reactive').WritableObjectSignal<{}>;
92
+ /**
93
+ * Synchronized signal containing all events (NPCs, objects) on the map
94
+ *
95
+ * This signal is automatically synchronized with clients using @signe/sync.
96
+ * Events are indexed by their unique ID.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // Get all events
101
+ * const allEvents = map.events();
102
+ *
103
+ * // Get a specific event
104
+ * const event = map.events()['event-id'];
105
+ * ```
106
+ */
45
107
  events: import('@signe/reactive').WritableObjectSignal<{}>;
108
+ /**
109
+ * Signal containing the map's database of items, classes, and other game data
110
+ *
111
+ * This database can be dynamically populated using `addInDatabase()` and
112
+ * `removeInDatabase()` methods. It's used to store game entities like items,
113
+ * classes, skills, etc. that are specific to this map.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * // Add data to database
118
+ * map.addInDatabase('Potion', PotionClass);
119
+ *
120
+ * // Access database
121
+ * const potion = map.database()['Potion'];
122
+ * ```
123
+ */
46
124
  database: import('@signe/reactive').WritableObjectSignal<{}>;
47
- maps: any[];
125
+ /**
126
+ * Array of map configurations - can contain MapOptions objects or instances of map classes
127
+ *
128
+ * This array stores the configuration for this map and any related maps.
129
+ * It's populated when the map is loaded via `updateMap()`.
130
+ */
131
+ maps: (MapOptions | any)[];
132
+ /**
133
+ * Array of sound IDs to play when players join the map
134
+ *
135
+ * These sounds are automatically played for each player when they join the map.
136
+ * Sounds must be defined on the client side.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * // Set sounds for the map
141
+ * map.sounds = ['background-music', 'ambient-forest'];
142
+ * ```
143
+ */
144
+ sounds: string[];
145
+ /**
146
+ * BehaviorSubject that completes when the map data is ready
147
+ *
148
+ * This subject is used to signal when the map has finished loading all its data.
149
+ * Players wait for this to complete before the map is fully initialized.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * // Wait for map data to be ready
154
+ * map.dataIsReady$.subscribe(() => {
155
+ * console.log('Map is ready!');
156
+ * });
157
+ * ```
158
+ */
48
159
  dataIsReady$: BehaviorSubject<void>;
160
+ /**
161
+ * Global configuration object for the map
162
+ *
163
+ * This object contains configuration settings that apply to the entire map.
164
+ * It's populated from the map data when `updateMap()` is called.
165
+ */
49
166
  globalConfig: any;
167
+ /**
168
+ * Damage formulas configuration for the map
169
+ *
170
+ * Contains formulas for calculating damage from skills, physical attacks,
171
+ * critical hits, and element coefficients. Default formulas are merged
172
+ * with custom formulas when the map is loaded.
173
+ */
50
174
  damageFormulas: any;
175
+ private _weatherState;
176
+ /** Internal: Map of shapes by name */
177
+ private _shapes;
178
+ /** Internal: Map of shape entity UUIDs to RpgShape instances */
179
+ private _shapeEntities;
180
+ /** Internal: Subscription for the input processing loop */
181
+ private _inputLoopSubscription?;
182
+ /** Enable/disable automatic tick processing (useful for unit tests) */
183
+ private _autoTickEnabled;
184
+ /** Runtime templates for scenario events to instantiate per player */
185
+ private _scenarioEventTemplates;
186
+ /** Runtime registry of event mode by id */
187
+ private _eventModeById;
188
+ /** Runtime registry of scenario owner by event id */
189
+ private _eventOwnerById;
190
+ /** Runtime registry of spawned scenario event ids by player id */
191
+ private _scenarioEventIdsByPlayer;
192
+ autoSync: boolean;
193
+ constructor(room: any);
194
+ onStart(): Promise<void>;
195
+ protected emitPhysicsInit(context: MapPhysicsInitContext): void;
196
+ protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void;
197
+ protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void;
198
+ protected emitPhysicsReset(): void;
199
+ private isPositiveNumber;
200
+ private resolveTrustedMapDimensions;
201
+ private normalizeEventMode;
202
+ private resolveEventMode;
203
+ private resolveScenarioOwnerId;
204
+ private normalizeEventObject;
205
+ private cloneEventTemplate;
206
+ private buildRuntimeEventId;
207
+ private setEventRuntimeMetadata;
208
+ private clearEventRuntimeMetadata;
209
+ private getEventModeById;
210
+ private getScenarioOwnerIdByEventId;
211
+ isEventVisibleForPlayer(eventOrId: string | RpgEvent, playerOrId: string | RpgPlayer): boolean;
212
+ private spawnScenarioEventsForPlayer;
213
+ private removeScenarioEventsForPlayer;
214
+ /**
215
+ * Setup collision detection between players, events, and shapes
216
+ *
217
+ * This method listens to physics collision events and triggers hooks:
218
+ * - `onPlayerTouch` on events when a player collides with them
219
+ * - `onInShape` on players and events when they enter a shape
220
+ * - `onOutShape` on players and events when they exit a shape
221
+ *
222
+ * ## Architecture
223
+ *
224
+ * Uses the physics engine's collision event system to detect when entities collide.
225
+ * When a collision is detected:
226
+ * - Between a player and an event: triggers `onPlayerTouch` on the event
227
+ * - Between a player/event and a shape: triggers `onInShape`/`onOutShape` hooks
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * // Event with onPlayerTouch hook
232
+ * map.createDynamicEvent({
233
+ * x: 100,
234
+ * y: 200,
235
+ * event: {
236
+ * onPlayerTouch(player) {
237
+ * console.log(`Player ${player.id} touched this event!`);
238
+ * }
239
+ * }
240
+ * });
241
+ *
242
+ * // Player with onInShape hook
243
+ * const player: RpgPlayerHooks = {
244
+ * onInShape(player: RpgPlayer, shape: RpgShape) {
245
+ * console.log('in', player.name, shape.name);
246
+ * },
247
+ * onOutShape(player: RpgPlayer, shape: RpgShape) {
248
+ * console.log('out', player.name, shape.name);
249
+ * }
250
+ * };
251
+ * ```
252
+ */
253
+ private setupCollisionDetection;
254
+ /**
255
+ * Intercepts and modifies packets before they are sent to clients
256
+ *
257
+ * This method is automatically called by @signe/room for each packet sent to clients.
258
+ * It adds timestamp and acknowledgment information to sync packets for client-side
259
+ * prediction reconciliation. This helps with network synchronization and reduces
260
+ * perceived latency.
261
+ *
262
+ * ## Architecture
263
+ *
264
+ * Adds metadata to packets:
265
+ * - `timestamp`: Current server time for client-side prediction
266
+ * - `ack`: Acknowledgment info with last processed frame and authoritative position
267
+ *
268
+ * @param player - The player receiving the packet
269
+ * @param packet - The packet data to intercept
270
+ * @param conn - The connection object
271
+ * @returns Modified packet with timestamp and ack info, or null if player is invalid
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * // This method is called automatically by the framework
276
+ * // You typically don't call it directly
277
+ * ```
278
+ */
279
+ interceptorPacket(player: RpgPlayer, packet: any, conn: MockConnection): any;
280
+ /**
281
+ * Called when a player joins the map
282
+ *
283
+ * This method is automatically called by @signe/room when a player connects to the map.
284
+ * It initializes the player's connection, sets up the map context, and waits for
285
+ * the map data to be ready before playing sounds and triggering hooks.
286
+ *
287
+ * ## Architecture
288
+ *
289
+ * 1. Sets player's map reference and context
290
+ * 2. Initializes the player
291
+ * 3. Waits for map data to be ready
292
+ * 4. Plays map sounds for the player
293
+ * 5. Triggers `server-player-onJoinMap` hook
294
+ *
295
+ * @param player - The player joining the map
296
+ * @param conn - The connection object for the player
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * // This method is called automatically by the framework
301
+ * // You can listen to the hook to perform custom logic
302
+ * server.addHook('server-player-onJoinMap', (player, map) => {
303
+ * console.log(`Player ${player.id} joined map ${map.id}`);
304
+ * });
305
+ * ```
306
+ */
51
307
  onJoin(player: RpgPlayer, conn: MockConnection): void;
308
+ /**
309
+ * Called when a player leaves the map
310
+ *
311
+ * This method is automatically called by @signe/room when a player disconnects from the map.
312
+ * It cleans up the player's pending inputs and triggers the appropriate hooks.
313
+ *
314
+ * ## Architecture
315
+ *
316
+ * 1. Triggers `server-player-onLeaveMap` hook
317
+ * 2. Clears pending inputs to prevent processing after disconnection
318
+ *
319
+ * @param player - The player leaving the map
320
+ * @param conn - The connection object for the player
321
+ *
322
+ * @example
323
+ * ```ts
324
+ * // This method is called automatically by the framework
325
+ * // You can listen to the hook to perform custom cleanup
326
+ * server.addHook('server-player-onLeaveMap', (player, map) => {
327
+ * console.log(`Player ${player.id} left map ${map.id}`);
328
+ * });
329
+ * ```
330
+ */
331
+ onLeave(player: RpgPlayer, conn: MockConnection): Promise<void>;
332
+ /**
333
+ * Get the hooks system for this map
334
+ *
335
+ * Returns the dependency-injected Hooks instance that allows you to trigger
336
+ * and listen to various game events.
337
+ *
338
+ * @returns The Hooks instance for this map
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * // Trigger a custom hook
343
+ * map.hooks.callHooks('custom-event', data).subscribe();
344
+ * ```
345
+ */
52
346
  get hooks(): Hooks;
53
- guiInteraction(player: RpgPlayer, value: any): void;
347
+ onSessionRestore(payload: {
348
+ userSnapshot: any;
349
+ user?: RpgPlayer;
350
+ }): Promise<any>;
351
+ /**
352
+ * Handle GUI interaction from a player
353
+ *
354
+ * This method is called when a player interacts with a GUI element.
355
+ * It synchronizes the player's changes to ensure the client state is up to date.
356
+ *
357
+ * @param player - The player performing the interaction
358
+ * @param value - The interaction data from the client
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * // This method is called automatically when a player interacts with a GUI
363
+ * // The interaction data is sent from the client
364
+ * ```
365
+ */
366
+ guiInteraction(player: RpgPlayer, value: {
367
+ guiId: string;
368
+ name: string;
369
+ data: any;
370
+ }): Promise<void>;
371
+ /**
372
+ * Handle GUI exit from a player
373
+ *
374
+ * This method is called when a player closes or exits a GUI.
375
+ * It removes the GUI from the player's active GUIs.
376
+ *
377
+ * @param player - The player exiting the GUI
378
+ * @param guiId - The ID of the GUI being exited
379
+ * @param data - Optional data associated with the GUI exit
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * // This method is called automatically when a player closes a GUI
384
+ * // The GUI is removed from the player's active GUIs
385
+ * ```
386
+ */
54
387
  guiExit(player: RpgPlayer, { guiId, data }: {
55
388
  guiId: any;
56
389
  data: any;
57
390
  }): void;
391
+ /**
392
+ * Handle action input from a player
393
+ *
394
+ * This method is called when a player performs an action (like pressing a button).
395
+ * It checks for collisions with events and triggers the appropriate hooks.
396
+ *
397
+ * ## Architecture
398
+ *
399
+ * 1. Gets all entities colliding with the player
400
+ * 2. Triggers `onAction` hook on colliding events
401
+ * 3. Triggers `onInput` hook on the player
402
+ *
403
+ * @param player - The player performing the action
404
+ * @param action - The action data (button pressed, etc.)
405
+ *
406
+ * @example
407
+ * ```ts
408
+ * // This method is called automatically when a player presses an action button
409
+ * // Events near the player will have their onAction hook triggered
410
+ * ```
411
+ */
58
412
  onAction(player: RpgPlayer, action: any): void;
59
- onInput(player: RpgPlayer, input: any): void;
413
+ /**
414
+ * Handle movement input from a player
415
+ *
416
+ * This method is called when a player sends movement input from the client.
417
+ * It queues the input for processing by the game loop. Inputs are processed
418
+ * with frame numbers to ensure proper ordering and client-side prediction.
419
+ *
420
+ * ## Architecture
421
+ *
422
+ * - Inputs are queued in `player.pendingInputs`
423
+ * - Duplicate frames are skipped to prevent processing the same input twice
424
+ * - Inputs are processed asynchronously by the game loop
425
+ *
426
+ * @param player - The player sending the movement input
427
+ * @param input - The input data containing frame number, input direction, and timestamp
428
+ *
429
+ * @example
430
+ * ```ts
431
+ * // This method is called automatically when a player moves
432
+ * // The input is queued and processed by processInput()
433
+ * ```
434
+ */
435
+ onInput(player: RpgPlayer, input: any): Promise<void>;
436
+ onPing(player: RpgPlayer, payload: {
437
+ clientTime?: number;
438
+ clientFrame?: number;
439
+ }): void;
440
+ saveSlot(player: RpgPlayer, value: {
441
+ requestId: string;
442
+ index: number;
443
+ meta?: any;
444
+ }): Promise<void>;
445
+ loadSlot(player: RpgPlayer, value: {
446
+ requestId: string;
447
+ index: number;
448
+ }): Promise<void>;
449
+ listSaveSlots(player: RpgPlayer, value: {
450
+ requestId: string;
451
+ }): Promise<import('../../../common/src').SaveSlotList>;
452
+ /**
453
+ * Update the map configuration and data
454
+ *
455
+ * This endpoint receives map data from the client and initializes the map.
456
+ * It loads the map configuration, damage formulas, events, and physics.
457
+ *
458
+ * ## Architecture
459
+ *
460
+ * 1. Validates the request body using MapUpdateSchema
461
+ * 2. Updates map data, global config, and damage formulas
462
+ * 3. Merges events and sounds from map configuration
463
+ * 4. Triggers hooks for map loading
464
+ * 5. Loads physics engine
465
+ * 6. Creates all events on the map
466
+ * 7. Completes the dataIsReady$ subject
467
+ *
468
+ * @param request - HTTP request containing map data
469
+ * @returns Promise that resolves when the map is fully loaded
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * // This endpoint is called automatically when a map is loaded
474
+ * // POST /map/update
475
+ * // Body: { id: string, width: number, height: number, config?: any, damageFormulas?: any }
476
+ * ```
477
+ */
60
478
  updateMap(request: Request): Promise<void>;
61
- addInDatabase(id: string, data: any): void;
479
+ /**
480
+ * Update (or create) a world configuration and propagate to all maps in that world
481
+ *
482
+ * This endpoint receives world map configuration data (typically from Tiled world import)
483
+ * and creates or updates the world manager. The world ID is extracted from the URL path.
484
+ *
485
+ * ## Architecture
486
+ *
487
+ * 1. Extracts world ID from URL path parameter
488
+ * 2. Normalizes input to array of WorldMapConfig
489
+ * 3. Ensures all required map properties are present (width, height, tile sizes)
490
+ * 4. Creates or updates the world manager
491
+ *
492
+ * Expected payload examples:
493
+ * - `{ id: string, maps: WorldMapConfig[] }`
494
+ * - `WorldMapConfig[]`
495
+ *
496
+ * @param request - HTTP request containing world configuration
497
+ * @returns Promise resolving to `{ ok: true }` when complete
498
+ *
499
+ * @example
500
+ * ```ts
501
+ * // POST /world/my-world/update
502
+ * // Body: [{ id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 }]
503
+ *
504
+ * // Or with nested structure
505
+ * // Body: { id: 'my-world', maps: [{ id: 'map1', ... }] }
506
+ * ```
507
+ */
508
+ updateWorld(request: Request): Promise<any>;
509
+ /**
510
+ * Process pending inputs for a player with anti-cheat validation
511
+ *
512
+ * This method processes pending inputs for a player while performing
513
+ * anti-cheat validation to prevent time manipulation and frame skipping.
514
+ * It validates the time deltas between inputs and ensures they are within
515
+ * acceptable ranges. To preserve movement itinerary under network bursts,
516
+ * the number of inputs processed per call is capped.
517
+ *
518
+ * ## Architecture
519
+ *
520
+ * **Important**: This method only updates entity velocities - it does NOT step
521
+ * the physics engine. Physics simulation is handled centrally by the game loop
522
+ * (`tick$` -> `runFixedTicks`). This ensures:
523
+ * - Consistent physics timing (60fps fixed timestep)
524
+ * - No double-stepping when multiple inputs are processed
525
+ * - Deterministic physics regardless of input frequency
526
+ *
527
+ * @param playerId - The ID of the player to process inputs for
528
+ * @param controls - Optional anti-cheat configuration
529
+ * @returns Promise containing the player and processed input strings
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * // Process inputs with default anti-cheat settings
534
+ * const result = await map.processInput('player1');
535
+ * console.log('Processed inputs:', result.inputs);
536
+ *
537
+ * // Process inputs with custom anti-cheat configuration
538
+ * const result = await map.processInput('player1', {
539
+ * maxTimeDelta: 100,
540
+ * maxFrameDelta: 5,
541
+ * minTimeBetweenInputs: 16,
542
+ * enableAntiCheat: true
543
+ * });
544
+ * ```
545
+ */
546
+ processInput(playerId: string, controls?: Controls): Promise<{
547
+ player: RpgPlayer;
548
+ inputs: string[];
549
+ }>;
550
+ /**
551
+ * Main game loop that processes player inputs
552
+ *
553
+ * This private method subscribes to tick$ and processes pending inputs
554
+ * for all players on the map with a throttle of 50ms. It ensures inputs are
555
+ * processed in order and prevents concurrent processing for the same player.
556
+ *
557
+ * ## Architecture
558
+ *
559
+ * - Subscribes to tick$ with throttleTime(50ms) for responsive input processing
560
+ * - Processes inputs for each player with pending inputs
561
+ * - Uses a flag to prevent concurrent processing for the same player
562
+ * - Calls `processInput()` to handle anti-cheat validation and movement
563
+ *
564
+ * @example
565
+ * ```ts
566
+ * // This method is called automatically in the constructor if autoTick is enabled
567
+ * // You typically don't call it directly
568
+ * ```
569
+ */
570
+ private loop;
571
+ /**
572
+ * Enable or disable automatic tick processing
573
+ *
574
+ * When disabled, the input processing loop will not run automatically.
575
+ * This is useful for unit tests where you want manual control over when
576
+ * inputs are processed.
577
+ *
578
+ * @param enabled - Whether to enable automatic tick processing (default: true)
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * // Disable auto tick for testing
583
+ * map.setAutoTick(false);
584
+ *
585
+ * // Manually trigger tick processing
586
+ * await map.processInput('player1');
587
+ * ```
588
+ */
589
+ setAutoTick(enabled: boolean): void;
590
+ /**
591
+ * Get a world manager by id
592
+ *
593
+ * Returns the world maps manager for the given world ID. Currently, only
594
+ * one world manager is supported per map instance.
595
+ *
596
+ * @param id - The world ID (currently unused, returns the single manager)
597
+ * @returns The WorldMapsManager instance, or null if not initialized
598
+ *
599
+ * @example
600
+ * ```ts
601
+ * const worldManager = map.getWorldMaps('my-world');
602
+ * if (worldManager) {
603
+ * const mapInfo = worldManager.getMapInfo('map1');
604
+ * }
605
+ * ```
606
+ */
607
+ getWorldMaps(id: string): WorldMapsManager | null;
608
+ /**
609
+ * Delete a world manager by id
610
+ *
611
+ * Removes the world maps manager from this map instance. Currently, only
612
+ * one world manager is supported, so this clears the single manager.
613
+ *
614
+ * @param id - The world ID (currently unused)
615
+ * @returns true if the manager was deleted, false if it didn't exist
616
+ *
617
+ * @example
618
+ * ```ts
619
+ * const deleted = map.deleteWorldMaps('my-world');
620
+ * if (deleted) {
621
+ * console.log('World manager removed');
622
+ * }
623
+ * ```
624
+ */
625
+ deleteWorldMaps(id: string): boolean;
626
+ /**
627
+ * Create a world manager dynamically
628
+ *
629
+ * Creates a new WorldMapsManager instance and configures it with the provided
630
+ * map configurations. This is used when loading world data from Tiled or
631
+ * other map editors.
632
+ *
633
+ * @param world - World configuration object
634
+ * @param world.id - Optional world identifier
635
+ * @param world.maps - Array of map configurations for the world
636
+ * @returns The newly created WorldMapsManager instance
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * const manager = map.createDynamicWorldMaps({
641
+ * id: 'my-world',
642
+ * maps: [
643
+ * { id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 },
644
+ * { id: 'map2', worldX: 800, worldY: 0, width: 800, height: 600 }
645
+ * ]
646
+ * });
647
+ * ```
648
+ */
649
+ createDynamicWorldMaps(world: {
650
+ id?: string;
651
+ maps: WorldMapConfig[];
652
+ }): WorldMapsManager;
653
+ /**
654
+ * Update world maps by id. Auto-create when missing.
655
+ *
656
+ * Updates the world maps configuration. If the world manager doesn't exist,
657
+ * it is automatically created. This is useful for dynamically loading world
658
+ * data or updating map positions.
659
+ *
660
+ * @param id - The world ID
661
+ * @param maps - Array of map configurations to update
662
+ * @returns Promise that resolves when the update is complete
663
+ *
664
+ * @example
665
+ * ```ts
666
+ * await map.updateWorldMaps('my-world', [
667
+ * { id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 },
668
+ * { id: 'map2', worldX: 800, worldY: 0, width: 800, height: 600 }
669
+ * ]);
670
+ * ```
671
+ */
672
+ updateWorldMaps(id: string, maps: WorldMapConfig[]): Promise<void>;
673
+ /**
674
+ * Add data to the map's database
675
+ *
676
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
677
+ *
678
+ * @param id - Unique identifier for the data
679
+ * @param data - The data to store (can be a class, object, or any value)
680
+ * @param options - Optional configuration
681
+ * @param options.force - If true, overwrites existing data even if ID already exists (default: false)
682
+ * @returns true if data was added, false if ignored (ID already exists)
683
+ *
684
+ * @example
685
+ * ```ts
686
+ * // Add an item class to the database
687
+ * map.addInDatabase('Potion', PotionClass);
688
+ *
689
+ * // Add an item object to the database
690
+ * map.addInDatabase('custom-item', {
691
+ * name: 'Custom Item',
692
+ * price: 100
693
+ * });
694
+ *
695
+ * // Force overwrite existing data
696
+ * map.addInDatabase('Potion', UpdatedPotionClass, { force: true });
697
+ * ```
698
+ */
699
+ addInDatabase(id: string, data: any, options?: {
700
+ force?: boolean;
701
+ }): boolean;
702
+ /**
703
+ * Remove data from the map's database
704
+ *
705
+ * This method delegates to BaseRoom's implementation to avoid code duplication.
706
+ *
707
+ * @param id - Unique identifier of the data to remove
708
+ * @returns true if data was removed, false if ID didn't exist
709
+ *
710
+ * @example
711
+ * ```ts
712
+ * // Remove an item from the database
713
+ * map.removeInDatabase('Potion');
714
+ *
715
+ * // Check if removal was successful
716
+ * const removed = map.removeInDatabase('custom-item');
717
+ * if (removed) {
718
+ * console.log('Item removed successfully');
719
+ * }
720
+ * ```
721
+ */
722
+ removeInDatabase(id: string): boolean;
62
723
  /**
63
724
  * Creates a dynamic event on the map
64
725
  *
@@ -97,14 +758,572 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
97
758
  * }
98
759
  * });
99
760
  */
100
- createDynamicEvent(eventObj: EventPosOption): Promise<void>;
761
+ createDynamicEvent(eventObj: EventPosOption, options?: CreateDynamicEventOptions): Promise<string | undefined>;
762
+ /**
763
+ * Get an event by its ID
764
+ *
765
+ * Returns the event with the specified ID, or undefined if not found.
766
+ * The return type can be narrowed using TypeScript generics.
767
+ *
768
+ * @param eventId - The unique identifier of the event
769
+ * @returns The event instance, or undefined if not found
770
+ *
771
+ * @example
772
+ * ```ts
773
+ * // Get any event
774
+ * const event = map.getEvent('npc-1');
775
+ *
776
+ * // Get event with type narrowing
777
+ * const npc = map.getEvent<MyNPC>('npc-1');
778
+ * if (npc) {
779
+ * npc.speak('Hello!');
780
+ * }
781
+ * ```
782
+ */
101
783
  getEvent<T extends RpgPlayer>(eventId: string): T | undefined;
784
+ /**
785
+ * Get a player by their ID
786
+ *
787
+ * Returns the player with the specified ID, or undefined if not found.
788
+ *
789
+ * @param playerId - The unique identifier of the player
790
+ * @returns The player instance, or undefined if not found
791
+ *
792
+ * @example
793
+ * ```ts
794
+ * const player = map.getPlayer('player-123');
795
+ * if (player) {
796
+ * console.log(`Player ${player.name} is on the map`);
797
+ * }
798
+ * ```
799
+ */
102
800
  getPlayer(playerId: string): RpgPlayer | undefined;
801
+ /**
802
+ * Get all players currently on the map
803
+ *
804
+ * Returns an array of all players that are currently connected to this map.
805
+ *
806
+ * @returns Array of all RpgPlayer instances on the map
807
+ *
808
+ * @example
809
+ * ```ts
810
+ * const players = map.getPlayers();
811
+ * console.log(`There are ${players.length} players on the map`);
812
+ *
813
+ * players.forEach(player => {
814
+ * console.log(`- ${player.name}`);
815
+ * });
816
+ * ```
817
+ */
818
+ getPlayers(): RpgPlayer[];
819
+ /**
820
+ * Get all events on the map
821
+ *
822
+ * Returns an array of all events (NPCs, objects, etc.) that are currently
823
+ * on this map.
824
+ *
825
+ * @returns Array of all RpgEvent instances on the map
826
+ *
827
+ * @example
828
+ * ```ts
829
+ * const events = map.getEvents();
830
+ * console.log(`There are ${events.length} events on the map`);
831
+ *
832
+ * events.forEach(event => {
833
+ * console.log(`- ${event.name} at (${event.x}, ${event.y})`);
834
+ * });
835
+ * ```
836
+ */
103
837
  getEvents(): RpgEvent[];
838
+ getEventsForPlayer(playerOrId: string | RpgPlayer): RpgEvent[];
839
+ /**
840
+ * Get the first event that matches a condition
841
+ *
842
+ * Searches through all events on the map and returns the first one that
843
+ * matches the provided callback function.
844
+ *
845
+ * @param cb - Callback function that returns true for the desired event
846
+ * @returns The first matching event, or undefined if none found
847
+ *
848
+ * @example
849
+ * ```ts
850
+ * // Find an event by name
851
+ * const npc = map.getEventBy(event => event.name === 'Merchant');
852
+ *
853
+ * // Find an event at a specific position
854
+ * const chest = map.getEventBy(event =>
855
+ * event.x === 100 && event.y === 200
856
+ * );
857
+ * ```
858
+ */
859
+ getEventBy(cb: (event: RpgEvent) => boolean): RpgEvent | undefined;
860
+ /**
861
+ * Get all events that match a condition
862
+ *
863
+ * Searches through all events on the map and returns all events that
864
+ * match the provided callback function.
865
+ *
866
+ * @param cb - Callback function that returns true for desired events
867
+ * @returns Array of all matching events
868
+ *
869
+ * @example
870
+ * ```ts
871
+ * // Find all NPCs
872
+ * const npcs = map.getEventsBy(event => event.name.startsWith('NPC-'));
873
+ *
874
+ * // Find all events in a specific area
875
+ * const nearbyEvents = map.getEventsBy(event =>
876
+ * event.x >= 0 && event.x <= 100 &&
877
+ * event.y >= 0 && event.y <= 100
878
+ * );
879
+ * ```
880
+ */
881
+ getEventsBy(cb: (event: RpgEvent) => boolean): RpgEvent[];
882
+ /**
883
+ * Remove an event from the map
884
+ *
885
+ * Removes the event with the specified ID from the map. The event will
886
+ * be removed from the synchronized events signal, causing it to disappear
887
+ * on all clients.
888
+ *
889
+ * @param eventId - The unique identifier of the event to remove
890
+ *
891
+ * @example
892
+ * ```ts
893
+ * // Remove an event
894
+ * map.removeEvent('npc-1');
895
+ *
896
+ * // Remove event after interaction
897
+ * const chest = map.getEvent('chest-1');
898
+ * if (chest) {
899
+ * // ... do something with chest ...
900
+ * map.removeEvent('chest-1');
901
+ * }
902
+ * ```
903
+ */
104
904
  removeEvent(eventId: string): void;
105
- showAnimation(animationName: string, object: RpgPlayer): void;
905
+ /**
906
+ * Display a component animation at a specific position on the map
907
+ *
908
+ * This method broadcasts a component animation to all clients connected to the map,
909
+ * allowing temporary visual effects to be displayed at any location on the map.
910
+ * Component animations are custom Canvas Engine components that can display
911
+ * complex effects with custom logic and parameters.
912
+ *
913
+ * @param id - The ID of the component animation to display
914
+ * @param position - The x, y coordinates where to display the animation
915
+ * @param params - Parameters to pass to the component animation
916
+ *
917
+ * @example
918
+ * ```ts
919
+ * // Show explosion at specific coordinates
920
+ * map.showComponentAnimation("explosion", { x: 300, y: 400 }, {
921
+ * intensity: 2.5,
922
+ * duration: 1500
923
+ * });
924
+ *
925
+ * // Show area damage effect
926
+ * map.showComponentAnimation("area-damage", { x: player.x, y: player.y }, {
927
+ * radius: 100,
928
+ * color: "red",
929
+ * damage: 50
930
+ * });
931
+ *
932
+ * // Show treasure spawn effect
933
+ * map.showComponentAnimation("treasure-spawn", { x: 150, y: 200 }, {
934
+ * sparkle: true,
935
+ * sound: "treasure-appear"
936
+ * });
937
+ * ```
938
+ */
939
+ showComponentAnimation(id: string, position: {
940
+ x: number;
941
+ y: number;
942
+ }, params: any): void;
943
+ /**
944
+ * Display a spritesheet animation at a specific position on the map
945
+ *
946
+ * This method displays a temporary visual animation using a spritesheet at any
947
+ * location on the map. It's a convenience method that internally uses showComponentAnimation
948
+ * with the built-in 'animation' component. This is useful for spell effects, environmental
949
+ * animations, or any visual feedback that uses predefined spritesheets.
950
+ *
951
+ * @param position - The x, y coordinates where to display the animation
952
+ * @param graphic - The ID of the spritesheet to use for the animation
953
+ * @param animationName - The name of the animation within the spritesheet (default: 'default')
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * // Show explosion at specific coordinates
958
+ * map.showAnimation({ x: 100, y: 200 }, "explosion");
959
+ *
960
+ * // Show spell effect at player position
961
+ * const playerPos = { x: player.x, y: player.y };
962
+ * map.showAnimation(playerPos, "spell-effects", "lightning");
963
+ *
964
+ * // Show environmental effect
965
+ * map.showAnimation({ x: 300, y: 150 }, "nature-effects", "wind-gust");
966
+ *
967
+ * // Show portal opening animation
968
+ * map.showAnimation({ x: 500, y: 400 }, "portals", "opening");
969
+ * ```
970
+ */
971
+ showAnimation(position: {
972
+ x: number;
973
+ y: number;
974
+ }, graphic: string, animationName?: string): void;
975
+ private cloneWeatherState;
976
+ /**
977
+ * Get the current map weather state.
978
+ */
979
+ getWeather(): WeatherState | null;
980
+ /**
981
+ * Set the full weather state for this map.
982
+ *
983
+ * When `sync` is true (default), all connected clients receive the new weather.
984
+ */
985
+ setWeather(next: WeatherState | null, options?: WeatherSetOptions): WeatherState | null;
986
+ /**
987
+ * Patch the current weather state.
988
+ *
989
+ * Nested `params` values are merged.
990
+ */
991
+ patchWeather(patch: Partial<WeatherState>, options?: WeatherSetOptions): WeatherState | null;
992
+ /**
993
+ * Clear weather for this map.
994
+ */
995
+ clearWeather(options?: WeatherSetOptions): void;
996
+ /**
997
+ * Configure runtime synchronized properties on the map
998
+ *
999
+ * This method allows you to dynamically add synchronized properties to the map
1000
+ * that will be automatically synced with clients. The schema follows the same
1001
+ * structure as module properties with `$initial`, `$syncWithClient`, and `$permanent` options.
1002
+ *
1003
+ * ## Architecture
1004
+ *
1005
+ * - Reads a schema object shaped like module props
1006
+ * - Creates typed sync signals with @signe/sync
1007
+ * - Properties are accessible as `map.propertyName`
1008
+ *
1009
+ * @param schema - Schema object defining the properties to sync
1010
+ * @param schema[key].$initial - Initial value for the property
1011
+ * @param schema[key].$syncWithClient - Whether to sync this property to clients
1012
+ * @param schema[key].$permanent - Whether to persist this property
1013
+ *
1014
+ * @example
1015
+ * ```ts
1016
+ * // Add synchronized properties to the map
1017
+ * map.setSync({
1018
+ * weather: {
1019
+ * $initial: 'sunny',
1020
+ * $syncWithClient: true,
1021
+ * $permanent: false
1022
+ * },
1023
+ * timeOfDay: {
1024
+ * $initial: 12,
1025
+ * $syncWithClient: true,
1026
+ * $permanent: false
1027
+ * }
1028
+ * });
1029
+ *
1030
+ * // Use the properties
1031
+ * map.weather.set('rainy');
1032
+ * const currentWeather = map.weather();
1033
+ * ```
1034
+ */
1035
+ setSync(schema: Record<string, any>): void;
1036
+ /**
1037
+ * Apply sync to the client
1038
+ *
1039
+ * This method applies sync to the client by calling the `$applySync()` method.
1040
+ *
1041
+ * @example
1042
+ * ```ts
1043
+ * map.applySyncToClient();
1044
+ * ```
1045
+ */
1046
+ applySyncToClient(): void;
1047
+ /**
1048
+ * Create a shape dynamically on the map
1049
+ *
1050
+ * This method creates a static hitbox on the map that can be used for
1051
+ * collision detection, area triggers, or visual boundaries. The shape is
1052
+ * backed by the physics engine's static entity system for accurate collision detection.
1053
+ *
1054
+ * ## Architecture
1055
+ *
1056
+ * Creates a static entity (hitbox) in the physics engine at the specified position and size.
1057
+ * The shape is stored internally and can be retrieved by name. When players or events
1058
+ * collide with this hitbox, the `onInShape` and `onOutShape` hooks are automatically
1059
+ * triggered on both the player and the event.
1060
+ *
1061
+ * @param obj - Shape configuration object
1062
+ * @param obj.x - X position of the shape (top-left corner) (required)
1063
+ * @param obj.y - Y position of the shape (top-left corner) (required)
1064
+ * @param obj.width - Width of the shape in pixels (required)
1065
+ * @param obj.height - Height of the shape in pixels (required)
1066
+ * @param obj.name - Name of the shape (optional, auto-generated if not provided)
1067
+ * @param obj.z - Z position/depth for rendering (optional)
1068
+ * @param obj.color - Color in hexadecimal format, shared with client (optional)
1069
+ * @param obj.collision - Whether the shape has collision (optional)
1070
+ * @param obj.properties - Additional custom properties (optional)
1071
+ * @returns The created RpgShape instance
1072
+ *
1073
+ * @example
1074
+ * ```ts
1075
+ * // Create a simple rectangular shape
1076
+ * const shape = map.createShape({
1077
+ * x: 100,
1078
+ * y: 200,
1079
+ * width: 50,
1080
+ * height: 50,
1081
+ * name: "spawn-zone"
1082
+ * });
1083
+ *
1084
+ * // Create a shape with visual properties
1085
+ * const triggerZone = map.createShape({
1086
+ * x: 300,
1087
+ * y: 400,
1088
+ * width: 100,
1089
+ * height: 100,
1090
+ * name: "treasure-area",
1091
+ * color: "#FFD700",
1092
+ * z: 1,
1093
+ * collision: false,
1094
+ * properties: {
1095
+ * type: "treasure",
1096
+ * value: 100
1097
+ * }
1098
+ * });
1099
+ *
1100
+ * // Player hooks will be triggered automatically
1101
+ * const player: RpgPlayerHooks = {
1102
+ * onInShape(player: RpgPlayer, shape: RpgShape) {
1103
+ * console.log('in', player.name, shape.name);
1104
+ * },
1105
+ * onOutShape(player: RpgPlayer, shape: RpgShape) {
1106
+ * console.log('out', player.name, shape.name);
1107
+ * }
1108
+ * };
1109
+ * ```
1110
+ */
1111
+ createShape(obj: {
1112
+ x: number;
1113
+ y: number;
1114
+ width: number;
1115
+ height: number;
1116
+ name?: string;
1117
+ z?: number;
1118
+ color?: string;
1119
+ collision?: boolean;
1120
+ properties?: Record<string, any>;
1121
+ }): RpgShape;
1122
+ /**
1123
+ * Delete a shape from the map
1124
+ *
1125
+ * Removes a shape by its name and cleans up the associated static hitbox entity.
1126
+ * If the shape doesn't exist, the method does nothing.
1127
+ *
1128
+ * @param name - Name of the shape to remove
1129
+ * @returns void
1130
+ *
1131
+ * @example
1132
+ * ```ts
1133
+ * // Create and then remove a shape
1134
+ * const shape = map.createShape({
1135
+ * x: 100,
1136
+ * y: 200,
1137
+ * width: 50,
1138
+ * height: 50,
1139
+ * name: "temp-zone"
1140
+ * });
1141
+ *
1142
+ * // Later, remove it
1143
+ * map.removeShape("temp-zone");
1144
+ * ```
1145
+ */
1146
+ removeShape(name: string): void;
1147
+ /**
1148
+ * Get all shapes on the map
1149
+ *
1150
+ * Returns an array of all shapes that have been created on this map,
1151
+ * regardless of whether they are static shapes or player-attached shapes.
1152
+ *
1153
+ * @returns Array of RpgShape instances
1154
+ *
1155
+ * @example
1156
+ * ```ts
1157
+ * // Create multiple shapes
1158
+ * map.createShape({ x: 0, y: 0, width: 50, height: 50, name: "zone1" });
1159
+ * map.createShape({ x: 100, y: 100, width: 50, height: 50, name: "zone2" });
1160
+ *
1161
+ * // Get all shapes
1162
+ * const allShapes = map.getShapes();
1163
+ * console.log(allShapes.length); // 2
1164
+ * ```
1165
+ */
1166
+ getShapes(): RpgShape[];
1167
+ /**
1168
+ * Get a shape by its name
1169
+ *
1170
+ * Returns a shape with the specified name, or undefined if no shape
1171
+ * with that name exists on the map.
1172
+ *
1173
+ * @param name - Name of the shape to retrieve
1174
+ * @returns The RpgShape instance, or undefined if not found
1175
+ *
1176
+ * @example
1177
+ * ```ts
1178
+ * // Create a shape with a specific name
1179
+ * map.createShape({
1180
+ * x: 100,
1181
+ * y: 200,
1182
+ * width: 50,
1183
+ * height: 50,
1184
+ * name: "spawn-point"
1185
+ * });
1186
+ *
1187
+ * // Retrieve it later
1188
+ * const spawnZone = map.getShape("spawn-point");
1189
+ * if (spawnZone) {
1190
+ * console.log(`Spawn zone at (${spawnZone.x}, ${spawnZone.y})`);
1191
+ * }
1192
+ * ```
1193
+ */
1194
+ getShape(name: string): RpgShape | undefined;
1195
+ /**
1196
+ * Play a sound for all players on the map
1197
+ *
1198
+ * This method plays a sound for all players currently on the map by iterating
1199
+ * over each player and calling `player.playSound()`. The sound must be defined
1200
+ * on the client side (in the client module configuration).
1201
+ * This is ideal for environmental sounds, battle music, or map-wide events that
1202
+ * all players should hear simultaneously.
1203
+ *
1204
+ * ## Design
1205
+ *
1206
+ * Iterates over all players on the map and calls `player.playSound()` for each one.
1207
+ * This avoids code duplication and reuses the existing player sound logic.
1208
+ * For player-specific sounds, use `player.playSound()` directly.
1209
+ *
1210
+ * @param soundId - Sound identifier, defined on the client side
1211
+ * @param options - Optional sound configuration
1212
+ * @param options.volume - Volume level (0.0 to 1.0, default: 1.0)
1213
+ * @param options.loop - Whether the sound should loop (default: false)
1214
+ *
1215
+ * @example
1216
+ * ```ts
1217
+ * // Play a sound for all players on the map
1218
+ * map.playSound("explosion");
1219
+ *
1220
+ * // Play background music for everyone with volume and loop
1221
+ * map.playSound("battle-theme", {
1222
+ * volume: 0.7,
1223
+ * loop: true
1224
+ * });
1225
+ *
1226
+ * // Play a door opening sound at low volume
1227
+ * map.playSound("door-open", { volume: 0.4 });
1228
+ * ```
1229
+ */
1230
+ playSound(soundId: string, options?: {
1231
+ volume?: number;
1232
+ loop?: boolean;
1233
+ }): void;
1234
+ /**
1235
+ * Stop a sound for all players on the map
1236
+ *
1237
+ * This method stops a sound that was previously started with `map.playSound()`
1238
+ * for all players on the map by iterating over each player and calling `player.stopSound()`.
1239
+ *
1240
+ * @param soundId - Sound identifier to stop
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * // Start background music for everyone
1245
+ * map.playSound("battle-theme", { loop: true });
1246
+ *
1247
+ * // Later, stop it for everyone
1248
+ * map.stopSound("battle-theme");
1249
+ * ```
1250
+ */
1251
+ stopSound(soundId: string): void;
1252
+ /**
1253
+ * Shake the map for all players
1254
+ *
1255
+ * This method triggers a shake animation on the map for all players currently on the map.
1256
+ * The shake effect creates a visual feedback that can be used for earthquakes, explosions,
1257
+ * impacts, or any dramatic event that should affect the entire map visually.
1258
+ *
1259
+ * ## Architecture
1260
+ *
1261
+ * Broadcasts a shake event to all clients connected to the map. Each client receives
1262
+ * the shake configuration and triggers the shake animation on the map container using
1263
+ * Canvas Engine's shake directive.
1264
+ *
1265
+ * @param options - Optional shake configuration
1266
+ * @param options.intensity - Shake intensity in pixels (default: 10)
1267
+ * @param options.duration - Duration of the shake animation in milliseconds (default: 500)
1268
+ * @param options.frequency - Number of shake oscillations during the animation (default: 10)
1269
+ * @param options.direction - Direction of the shake - 'x', 'y', or 'both' (default: 'both')
1270
+ *
1271
+ * @example
1272
+ * ```ts
1273
+ * // Basic shake with default settings
1274
+ * map.shakeMap();
1275
+ *
1276
+ * // Intense earthquake effect
1277
+ * map.shakeMap({
1278
+ * intensity: 25,
1279
+ * duration: 1000,
1280
+ * frequency: 15,
1281
+ * direction: 'both'
1282
+ * });
1283
+ *
1284
+ * // Horizontal shake for side impact
1285
+ * map.shakeMap({
1286
+ * intensity: 15,
1287
+ * duration: 400,
1288
+ * direction: 'x'
1289
+ * });
1290
+ *
1291
+ * // Vertical shake for ground impact
1292
+ * map.shakeMap({
1293
+ * intensity: 20,
1294
+ * duration: 600,
1295
+ * direction: 'y'
1296
+ * });
1297
+ * ```
1298
+ */
1299
+ shakeMap(options?: {
1300
+ intensity?: number;
1301
+ duration?: number;
1302
+ frequency?: number;
1303
+ direction?: 'x' | 'y' | 'both';
1304
+ }): void;
1305
+ /**
1306
+ * Clear all server resources and reset state
1307
+ *
1308
+ * This method should be called to clean up all server-side resources when
1309
+ * shutting down or resetting the map. It stops the input processing loop
1310
+ * and ensures that all subscriptions are properly cleaned up.
1311
+ *
1312
+ * ## Design
1313
+ *
1314
+ * This method is used primarily in testing environments to ensure clean
1315
+ * state between tests. It stops the tick subscription to prevent memory leaks.
1316
+ *
1317
+ * @example
1318
+ * ```ts
1319
+ * // In test cleanup
1320
+ * afterEach(() => {
1321
+ * map.clear();
1322
+ * });
1323
+ * ```
1324
+ */
1325
+ clear(): void;
106
1326
  }
107
- export interface RpgMap {
108
- $send: (conn: MockConnection, data: any) => void;
109
- $broadcast: (data: any) => void;
1327
+ export interface RpgMap extends RoomMethods {
110
1328
  }
1329
+ export {};