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