@rpgjs/server 5.0.0-alpha.21 → 5.0.0-alpha.23
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.
- package/dist/Player/BattleManager.d.ts +43 -31
- package/dist/Player/ClassManager.d.ts +23 -3
- package/dist/Player/EffectManager.d.ts +49 -3
- package/dist/Player/ElementManager.d.ts +76 -3
- package/dist/Player/ItemManager.d.ts +292 -4
- package/dist/Player/MoveManager.d.ts +145 -4
- package/dist/Player/Player.d.ts +135 -0
- package/dist/Player/SkillManager.d.ts +42 -3
- package/dist/Player/StateManager.d.ts +74 -3
- package/dist/Player/VariableManager.d.ts +47 -3
- package/dist/RpgServer.d.ts +228 -61
- package/dist/decorators/map.d.ts +89 -1
- package/dist/index.js +804 -1703
- package/dist/index.js.map +1 -1
- package/dist/module.d.ts +43 -1
- package/dist/rooms/map.d.ts +676 -12
- package/package.json +8 -8
- package/src/Player/BattleManager.ts +38 -55
- package/src/Player/ClassManager.ts +21 -71
- package/src/Player/EffectManager.ts +50 -96
- package/src/Player/ElementManager.ts +74 -152
- package/src/Player/ItemManager.ts +302 -359
- package/src/Player/MoveManager.ts +141 -438
- package/src/Player/Player.ts +217 -0
- package/src/Player/SkillManager.ts +44 -147
- package/src/Player/StateManager.ts +63 -259
- package/src/Player/VariableManager.ts +53 -150
- package/src/RpgServer.ts +237 -60
- package/src/decorators/map.ts +105 -1
- package/src/module.ts +81 -2
- package/src/rooms/map.ts +757 -23
package/src/rooms/map.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { BehaviorSubject } from "rxjs";
|
|
|
12
12
|
import { COEFFICIENT_ELEMENTS, DAMAGE_CRITICAL, DAMAGE_PHYSIC, DAMAGE_SKILL } from "../presets";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { EntityState } from "@rpgjs/physic";
|
|
15
|
+
import { MapOptions } from "../decorators/map";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Interface for input controls configuration
|
|
@@ -66,8 +67,9 @@ export interface EventHooks {
|
|
|
66
67
|
onInShape?: (zone: RpgShape, player: RpgPlayer) => void;
|
|
67
68
|
/** Called when a player exits a shape */
|
|
68
69
|
onOutShape?: (zone: RpgShape, player: RpgPlayer) => void;
|
|
69
|
-
|
|
70
|
+
/** Called when a player is detected entering a shape */
|
|
70
71
|
onDetectInShape?: (player: RpgPlayer, shape: RpgShape) => void;
|
|
72
|
+
/** Called when a player is detected exiting a shape */
|
|
71
73
|
onDetectOutShape?: (player: RpgPlayer, shape: RpgShape) => void;
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -95,12 +97,111 @@ export type EventPosOption = {
|
|
|
95
97
|
path: "map-{id}"
|
|
96
98
|
})
|
|
97
99
|
export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
100
|
+
/**
|
|
101
|
+
* Synchronized signal containing all players currently on the map
|
|
102
|
+
*
|
|
103
|
+
* This signal is automatically synchronized with clients using @signe/sync.
|
|
104
|
+
* Players are indexed by their unique ID.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* // Get all players
|
|
109
|
+
* const allPlayers = map.players();
|
|
110
|
+
*
|
|
111
|
+
* // Get a specific player
|
|
112
|
+
* const player = map.players()['player-id'];
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
98
115
|
@users(RpgPlayer) players = signal({});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Synchronized signal containing all events (NPCs, objects) on the map
|
|
119
|
+
*
|
|
120
|
+
* This signal is automatically synchronized with clients using @signe/sync.
|
|
121
|
+
* Events are indexed by their unique ID.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* // Get all events
|
|
126
|
+
* const allEvents = map.events();
|
|
127
|
+
*
|
|
128
|
+
* // Get a specific event
|
|
129
|
+
* const event = map.events()['event-id'];
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
99
132
|
@sync(RpgPlayer) events = signal({});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Signal containing the map's database of items, classes, and other game data
|
|
136
|
+
*
|
|
137
|
+
* This database can be dynamically populated using `addInDatabase()` and
|
|
138
|
+
* `removeInDatabase()` methods. It's used to store game entities like items,
|
|
139
|
+
* classes, skills, etc. that are specific to this map.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* // Add data to database
|
|
144
|
+
* map.addInDatabase('Potion', PotionClass);
|
|
145
|
+
*
|
|
146
|
+
* // Access database
|
|
147
|
+
* const potion = map.database()['Potion'];
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
100
150
|
database = signal({});
|
|
101
|
-
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Array of map configurations - can contain MapOptions objects or instances of map classes
|
|
154
|
+
*
|
|
155
|
+
* This array stores the configuration for this map and any related maps.
|
|
156
|
+
* It's populated when the map is loaded via `updateMap()`.
|
|
157
|
+
*/
|
|
158
|
+
maps: (MapOptions | any)[] = []
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Array of sound IDs to play when players join the map
|
|
162
|
+
*
|
|
163
|
+
* These sounds are automatically played for each player when they join the map.
|
|
164
|
+
* Sounds must be defined on the client side.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* // Set sounds for the map
|
|
169
|
+
* map.sounds = ['background-music', 'ambient-forest'];
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
sounds: string[] = []
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* BehaviorSubject that completes when the map data is ready
|
|
176
|
+
*
|
|
177
|
+
* This subject is used to signal when the map has finished loading all its data.
|
|
178
|
+
* Players wait for this to complete before the map is fully initialized.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* // Wait for map data to be ready
|
|
183
|
+
* map.dataIsReady$.subscribe(() => {
|
|
184
|
+
* console.log('Map is ready!');
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
102
188
|
dataIsReady$ = new BehaviorSubject<void>(undefined);
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Global configuration object for the map
|
|
192
|
+
*
|
|
193
|
+
* This object contains configuration settings that apply to the entire map.
|
|
194
|
+
* It's populated from the map data when `updateMap()` is called.
|
|
195
|
+
*/
|
|
103
196
|
globalConfig: any = {}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Damage formulas configuration for the map
|
|
200
|
+
*
|
|
201
|
+
* Contains formulas for calculating damage from skills, physical attacks,
|
|
202
|
+
* critical hits, and element coefficients. Default formulas are merged
|
|
203
|
+
* with custom formulas when the map is loaded.
|
|
204
|
+
*/
|
|
104
205
|
damageFormulas: any = {}
|
|
105
206
|
/** Internal: Map of shapes by name */
|
|
106
207
|
private _shapes: Map<string, RpgShape> = new Map();
|
|
@@ -271,7 +372,31 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
271
372
|
});
|
|
272
373
|
}
|
|
273
374
|
|
|
274
|
-
|
|
375
|
+
/**
|
|
376
|
+
* Intercepts and modifies packets before they are sent to clients
|
|
377
|
+
*
|
|
378
|
+
* This method is automatically called by @signe/room for each packet sent to clients.
|
|
379
|
+
* It adds timestamp and acknowledgment information to sync packets for client-side
|
|
380
|
+
* prediction reconciliation. This helps with network synchronization and reduces
|
|
381
|
+
* perceived latency.
|
|
382
|
+
*
|
|
383
|
+
* ## Architecture
|
|
384
|
+
*
|
|
385
|
+
* Adds metadata to packets:
|
|
386
|
+
* - `timestamp`: Current server time for client-side prediction
|
|
387
|
+
* - `ack`: Acknowledgment info with last processed frame and authoritative position
|
|
388
|
+
*
|
|
389
|
+
* @param player - The player receiving the packet
|
|
390
|
+
* @param packet - The packet data to intercept
|
|
391
|
+
* @param conn - The connection object
|
|
392
|
+
* @returns Modified packet with timestamp and ack info, or null if player is invalid
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* // This method is called automatically by the framework
|
|
397
|
+
* // You typically don't call it directly
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
275
400
|
interceptorPacket(player: RpgPlayer, packet: any, conn: MockConnection) {
|
|
276
401
|
let obj: any = {}
|
|
277
402
|
|
|
@@ -308,63 +433,262 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
308
433
|
};
|
|
309
434
|
}
|
|
310
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Called when a player joins the map
|
|
438
|
+
*
|
|
439
|
+
* This method is automatically called by @signe/room when a player connects to the map.
|
|
440
|
+
* It initializes the player's connection, sets up the map context, and waits for
|
|
441
|
+
* the map data to be ready before playing sounds and triggering hooks.
|
|
442
|
+
*
|
|
443
|
+
* ## Architecture
|
|
444
|
+
*
|
|
445
|
+
* 1. Sets player's map reference and context
|
|
446
|
+
* 2. Initializes the player
|
|
447
|
+
* 3. Waits for map data to be ready
|
|
448
|
+
* 4. Plays map sounds for the player
|
|
449
|
+
* 5. Triggers `server-player-onJoinMap` hook
|
|
450
|
+
*
|
|
451
|
+
* @param player - The player joining the map
|
|
452
|
+
* @param conn - The connection object for the player
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* ```ts
|
|
456
|
+
* // This method is called automatically by the framework
|
|
457
|
+
* // You can listen to the hook to perform custom logic
|
|
458
|
+
* server.addHook('server-player-onJoinMap', (player, map) => {
|
|
459
|
+
* console.log(`Player ${player.id} joined map ${map.id}`);
|
|
460
|
+
* });
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
311
463
|
onJoin(player: RpgPlayer, conn: MockConnection) {
|
|
312
464
|
player.map = this;
|
|
313
465
|
player.context = context;
|
|
314
466
|
player.conn = conn;
|
|
315
467
|
player._onInit()
|
|
316
468
|
this.dataIsReady$.pipe(
|
|
317
|
-
finalize(() => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.
|
|
469
|
+
finalize(async () => {
|
|
470
|
+
// Check if we should stop all sounds before playing new ones
|
|
471
|
+
if ((this as any).stopAllSoundsBeforeJoin) {
|
|
472
|
+
player.stopAllSounds();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this.sounds.forEach(sound => player.playSound(sound,{ loop: true }));
|
|
476
|
+
|
|
477
|
+
// Execute global map hooks (from RpgServer.map)
|
|
478
|
+
await lastValueFrom(this.hooks.callHooks("server-map-onJoin", player, this));
|
|
479
|
+
|
|
480
|
+
// // Execute map-specific hooks (from @MapData or MapOptions)
|
|
481
|
+
if (typeof (this as any)._onJoin === 'function') {
|
|
482
|
+
await (this as any)._onJoin(player);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Execute player hooks
|
|
486
|
+
await lastValueFrom(this.hooks.callHooks("server-player-onJoinMap", player, this));
|
|
321
487
|
})
|
|
322
488
|
).subscribe();
|
|
323
489
|
}
|
|
324
490
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
491
|
+
/**
|
|
492
|
+
* Called when a player leaves the map
|
|
493
|
+
*
|
|
494
|
+
* This method is automatically called by @signe/room when a player disconnects from the map.
|
|
495
|
+
* It cleans up the player's pending inputs and triggers the appropriate hooks.
|
|
496
|
+
*
|
|
497
|
+
* ## Architecture
|
|
498
|
+
*
|
|
499
|
+
* 1. Triggers `server-player-onLeaveMap` hook
|
|
500
|
+
* 2. Clears pending inputs to prevent processing after disconnection
|
|
501
|
+
*
|
|
502
|
+
* @param player - The player leaving the map
|
|
503
|
+
* @param conn - The connection object for the player
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```ts
|
|
507
|
+
* // This method is called automatically by the framework
|
|
508
|
+
* // You can listen to the hook to perform custom cleanup
|
|
509
|
+
* server.addHook('server-player-onLeaveMap', (player, map) => {
|
|
510
|
+
* console.log(`Player ${player.id} left map ${map.id}`);
|
|
511
|
+
* });
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
async onLeave(player: RpgPlayer, conn: MockConnection) {
|
|
515
|
+
// Execute global map hooks (from RpgServer.map)
|
|
516
|
+
await lastValueFrom(this.hooks.callHooks("server-map-onLeave", player, this));
|
|
517
|
+
|
|
518
|
+
// Execute map-specific hooks (from @MapData or MapOptions)
|
|
519
|
+
if (typeof (this as any)._onLeave === 'function') {
|
|
520
|
+
await (this as any)._onLeave(player);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Execute player hooks
|
|
524
|
+
await lastValueFrom(this.hooks.callHooks("server-player-onLeaveMap", player, this));
|
|
329
525
|
player.pendingInputs = [];
|
|
330
526
|
}
|
|
331
527
|
|
|
528
|
+
/**
|
|
529
|
+
* Get the hooks system for this map
|
|
530
|
+
*
|
|
531
|
+
* Returns the dependency-injected Hooks instance that allows you to trigger
|
|
532
|
+
* and listen to various game events.
|
|
533
|
+
*
|
|
534
|
+
* @returns The Hooks instance for this map
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```ts
|
|
538
|
+
* // Trigger a custom hook
|
|
539
|
+
* map.hooks.callHooks('custom-event', data).subscribe();
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
332
542
|
get hooks() {
|
|
333
543
|
return inject<Hooks>(context, ModulesToken);
|
|
334
544
|
}
|
|
335
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Get the width of the map in pixels
|
|
548
|
+
*
|
|
549
|
+
* @returns The width of the map in pixels, or 0 if not loaded
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* ```ts
|
|
553
|
+
* const width = map.widthPx;
|
|
554
|
+
* console.log(`Map width: ${width}px`);
|
|
555
|
+
* ```
|
|
556
|
+
*/
|
|
336
557
|
get widthPx(): number {
|
|
337
558
|
return this.data()?.width ?? 0
|
|
338
559
|
}
|
|
339
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Get the height of the map in pixels
|
|
563
|
+
*
|
|
564
|
+
* @returns The height of the map in pixels, or 0 if not loaded
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* ```ts
|
|
568
|
+
* const height = map.heightPx;
|
|
569
|
+
* console.log(`Map height: ${height}px`);
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
340
572
|
get heightPx(): number {
|
|
341
573
|
return this.data()?.height ?? 0
|
|
342
574
|
}
|
|
343
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Get the unique identifier of the map
|
|
578
|
+
*
|
|
579
|
+
* @returns The map ID, or empty string if not loaded
|
|
580
|
+
*
|
|
581
|
+
* @example
|
|
582
|
+
* ```ts
|
|
583
|
+
* const mapId = map.id;
|
|
584
|
+
* console.log(`Current map: ${mapId}`);
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
344
587
|
get id(): string {
|
|
345
588
|
return this.data()?.id ?? ''
|
|
346
589
|
}
|
|
347
590
|
|
|
591
|
+
/**
|
|
592
|
+
* Get the X position of this map in the world coordinate system
|
|
593
|
+
*
|
|
594
|
+
* This is used when maps are part of a larger world map. The world position
|
|
595
|
+
* indicates where this map is located relative to other maps.
|
|
596
|
+
*
|
|
597
|
+
* @returns The X position in world coordinates, or 0 if not in a world
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* ```ts
|
|
601
|
+
* const worldX = map.worldX;
|
|
602
|
+
* console.log(`Map is at world position (${worldX}, ${map.worldY})`);
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
348
605
|
get worldX(): number {
|
|
349
606
|
const worldMaps = this.getWorldMapsManager?.();
|
|
350
607
|
return worldMaps?.getMapInfo(this.id)?.worldX ?? 0
|
|
351
608
|
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Get the Y position of this map in the world coordinate system
|
|
612
|
+
*
|
|
613
|
+
* This is used when maps are part of a larger world map. The world position
|
|
614
|
+
* indicates where this map is located relative to other maps.
|
|
615
|
+
*
|
|
616
|
+
* @returns The Y position in world coordinates, or 0 if not in a world
|
|
617
|
+
*
|
|
618
|
+
* @example
|
|
619
|
+
* ```ts
|
|
620
|
+
* const worldY = map.worldY;
|
|
621
|
+
* console.log(`Map is at world position (${map.worldX}, ${worldY})`);
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
352
624
|
get worldY(): number {
|
|
353
625
|
const worldMaps = this.getWorldMapsManager?.();
|
|
354
626
|
return worldMaps?.getMapInfo(this.id)?.worldY ?? 0
|
|
355
627
|
}
|
|
356
628
|
|
|
629
|
+
/**
|
|
630
|
+
* Handle GUI interaction from a player
|
|
631
|
+
*
|
|
632
|
+
* This method is called when a player interacts with a GUI element.
|
|
633
|
+
* It synchronizes the player's changes to ensure the client state is up to date.
|
|
634
|
+
*
|
|
635
|
+
* @param player - The player performing the interaction
|
|
636
|
+
* @param value - The interaction data from the client
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* ```ts
|
|
640
|
+
* // This method is called automatically when a player interacts with a GUI
|
|
641
|
+
* // The interaction data is sent from the client
|
|
642
|
+
* ```
|
|
643
|
+
*/
|
|
357
644
|
@Action('gui.interaction')
|
|
358
645
|
guiInteraction(player: RpgPlayer, value) {
|
|
359
646
|
//this.hooks.callHooks("server-player-guiInteraction", player, value);
|
|
360
647
|
player.syncChanges();
|
|
361
648
|
}
|
|
362
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Handle GUI exit from a player
|
|
652
|
+
*
|
|
653
|
+
* This method is called when a player closes or exits a GUI.
|
|
654
|
+
* It removes the GUI from the player's active GUIs.
|
|
655
|
+
*
|
|
656
|
+
* @param player - The player exiting the GUI
|
|
657
|
+
* @param guiId - The ID of the GUI being exited
|
|
658
|
+
* @param data - Optional data associated with the GUI exit
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* // This method is called automatically when a player closes a GUI
|
|
663
|
+
* // The GUI is removed from the player's active GUIs
|
|
664
|
+
* ```
|
|
665
|
+
*/
|
|
363
666
|
@Action('gui.exit')
|
|
364
667
|
guiExit(player: RpgPlayer, { guiId, data }) {
|
|
365
668
|
player.removeGui(guiId, data)
|
|
366
669
|
}
|
|
367
670
|
|
|
671
|
+
/**
|
|
672
|
+
* Handle action input from a player
|
|
673
|
+
*
|
|
674
|
+
* This method is called when a player performs an action (like pressing a button).
|
|
675
|
+
* It checks for collisions with events and triggers the appropriate hooks.
|
|
676
|
+
*
|
|
677
|
+
* ## Architecture
|
|
678
|
+
*
|
|
679
|
+
* 1. Gets all entities colliding with the player
|
|
680
|
+
* 2. Triggers `onAction` hook on colliding events
|
|
681
|
+
* 3. Triggers `onInput` hook on the player
|
|
682
|
+
*
|
|
683
|
+
* @param player - The player performing the action
|
|
684
|
+
* @param action - The action data (button pressed, etc.)
|
|
685
|
+
*
|
|
686
|
+
* @example
|
|
687
|
+
* ```ts
|
|
688
|
+
* // This method is called automatically when a player presses an action button
|
|
689
|
+
* // Events near the player will have their onAction hook triggered
|
|
690
|
+
* ```
|
|
691
|
+
*/
|
|
368
692
|
@Action('action')
|
|
369
693
|
onAction(player: RpgPlayer, action: any) {
|
|
370
694
|
// Get collisions using the helper method from RpgCommonMap
|
|
@@ -378,6 +702,28 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
378
702
|
player.execMethod('onInput', [action]);
|
|
379
703
|
}
|
|
380
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Handle movement input from a player
|
|
707
|
+
*
|
|
708
|
+
* This method is called when a player sends movement input from the client.
|
|
709
|
+
* It queues the input for processing by the game loop. Inputs are processed
|
|
710
|
+
* with frame numbers to ensure proper ordering and client-side prediction.
|
|
711
|
+
*
|
|
712
|
+
* ## Architecture
|
|
713
|
+
*
|
|
714
|
+
* - Inputs are queued in `player.pendingInputs`
|
|
715
|
+
* - Duplicate frames are skipped to prevent processing the same input twice
|
|
716
|
+
* - Inputs are processed asynchronously by the game loop
|
|
717
|
+
*
|
|
718
|
+
* @param player - The player sending the movement input
|
|
719
|
+
* @param input - The input data containing frame number, input direction, and timestamp
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```ts
|
|
723
|
+
* // This method is called automatically when a player moves
|
|
724
|
+
* // The input is queued and processed by processInput()
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
381
727
|
@Action('move')
|
|
382
728
|
async onInput(player: RpgPlayer, input: any) {
|
|
383
729
|
if (typeof input?.frame === 'number') {
|
|
@@ -395,6 +741,32 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
395
741
|
}
|
|
396
742
|
}
|
|
397
743
|
|
|
744
|
+
/**
|
|
745
|
+
* Update the map configuration and data
|
|
746
|
+
*
|
|
747
|
+
* This endpoint receives map data from the client and initializes the map.
|
|
748
|
+
* It loads the map configuration, damage formulas, events, and physics.
|
|
749
|
+
*
|
|
750
|
+
* ## Architecture
|
|
751
|
+
*
|
|
752
|
+
* 1. Validates the request body using MapUpdateSchema
|
|
753
|
+
* 2. Updates map data, global config, and damage formulas
|
|
754
|
+
* 3. Merges events and sounds from map configuration
|
|
755
|
+
* 4. Triggers hooks for map loading
|
|
756
|
+
* 5. Loads physics engine
|
|
757
|
+
* 6. Creates all events on the map
|
|
758
|
+
* 7. Completes the dataIsReady$ subject
|
|
759
|
+
*
|
|
760
|
+
* @param request - HTTP request containing map data
|
|
761
|
+
* @returns Promise that resolves when the map is fully loaded
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* ```ts
|
|
765
|
+
* // This endpoint is called automatically when a map is loaded
|
|
766
|
+
* // POST /map/update
|
|
767
|
+
* // Body: { id: string, width: number, height: number, config?: any, damageFormulas?: any }
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
398
770
|
@Request({
|
|
399
771
|
path: "/map/update",
|
|
400
772
|
method: "POST"
|
|
@@ -424,6 +796,29 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
424
796
|
...map.events
|
|
425
797
|
]
|
|
426
798
|
}
|
|
799
|
+
if (mapFound?.sounds) {
|
|
800
|
+
this.sounds = [
|
|
801
|
+
...(map.sounds ?? []),
|
|
802
|
+
...mapFound.sounds
|
|
803
|
+
]
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
this.sounds = map.sounds ?? []
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Attach map-specific hooks from MapOptions or @MapData
|
|
810
|
+
if (mapFound?.onLoad) {
|
|
811
|
+
(this as any)._onLoad = mapFound.onLoad;
|
|
812
|
+
}
|
|
813
|
+
if (mapFound?.onJoin) {
|
|
814
|
+
(this as any)._onJoin = mapFound.onJoin;
|
|
815
|
+
}
|
|
816
|
+
if (mapFound?.onLeave) {
|
|
817
|
+
(this as any)._onLeave = mapFound.onLeave;
|
|
818
|
+
}
|
|
819
|
+
if (mapFound?.stopAllSoundsBeforeJoin !== undefined) {
|
|
820
|
+
(this as any).stopAllSoundsBeforeJoin = mapFound.stopAllSoundsBeforeJoin;
|
|
821
|
+
}
|
|
427
822
|
}
|
|
428
823
|
|
|
429
824
|
await lastValueFrom(this.hooks.callHooks("server-map-onBeforeUpdate", map, this))
|
|
@@ -435,18 +830,46 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
435
830
|
}
|
|
436
831
|
|
|
437
832
|
this.dataIsReady$.complete()
|
|
833
|
+
|
|
834
|
+
// Execute global map hooks (from RpgServer.map)
|
|
835
|
+
await lastValueFrom(this.hooks.callHooks("server-map-onLoad", this))
|
|
836
|
+
|
|
837
|
+
// Execute map-specific hooks (from @MapData or MapOptions)
|
|
838
|
+
if (typeof (this as any)._onLoad === 'function') {
|
|
839
|
+
await (this as any)._onLoad();
|
|
840
|
+
}
|
|
841
|
+
|
|
438
842
|
// TODO: Update map
|
|
439
843
|
}
|
|
440
844
|
|
|
441
845
|
/**
|
|
442
846
|
* Update (or create) a world configuration and propagate to all maps in that world
|
|
443
847
|
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
848
|
+
* This endpoint receives world map configuration data (typically from Tiled world import)
|
|
849
|
+
* and creates or updates the world manager. The world ID is extracted from the URL path.
|
|
850
|
+
*
|
|
851
|
+
* ## Architecture
|
|
852
|
+
*
|
|
853
|
+
* 1. Extracts world ID from URL path parameter
|
|
854
|
+
* 2. Normalizes input to array of WorldMapConfig
|
|
855
|
+
* 3. Ensures all required map properties are present (width, height, tile sizes)
|
|
856
|
+
* 4. Creates or updates the world manager
|
|
446
857
|
*
|
|
447
858
|
* Expected payload examples:
|
|
448
|
-
* - { id: string, maps: WorldMapConfig[] }
|
|
449
|
-
* - WorldMapConfig[]
|
|
859
|
+
* - `{ id: string, maps: WorldMapConfig[] }`
|
|
860
|
+
* - `WorldMapConfig[]`
|
|
861
|
+
*
|
|
862
|
+
* @param request - HTTP request containing world configuration
|
|
863
|
+
* @returns Promise resolving to `{ ok: true }` when complete
|
|
864
|
+
*
|
|
865
|
+
* @example
|
|
866
|
+
* ```ts
|
|
867
|
+
* // POST /world/my-world/update
|
|
868
|
+
* // Body: [{ id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 }]
|
|
869
|
+
*
|
|
870
|
+
* // Or with nested structure
|
|
871
|
+
* // Body: { id: 'my-world', maps: [{ id: 'map1', ... }] }
|
|
872
|
+
* ```
|
|
450
873
|
*/
|
|
451
874
|
@Request({
|
|
452
875
|
path: "/world/:id/update",
|
|
@@ -626,11 +1049,31 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
626
1049
|
};
|
|
627
1050
|
}
|
|
628
1051
|
|
|
1052
|
+
/**
|
|
1053
|
+
* Main game loop that processes player inputs
|
|
1054
|
+
*
|
|
1055
|
+
* This private method runs continuously every 50ms to process pending inputs
|
|
1056
|
+
* for all players on the map. It ensures inputs are processed in order and
|
|
1057
|
+
* prevents concurrent processing for the same player.
|
|
1058
|
+
*
|
|
1059
|
+
* ## Architecture
|
|
1060
|
+
*
|
|
1061
|
+
* - Runs every 50ms for responsive input processing
|
|
1062
|
+
* - Processes inputs for each player with pending inputs
|
|
1063
|
+
* - Uses a flag to prevent concurrent processing for the same player
|
|
1064
|
+
* - Calls `processInput()` to handle anti-cheat validation and movement
|
|
1065
|
+
*
|
|
1066
|
+
* @example
|
|
1067
|
+
* ```ts
|
|
1068
|
+
* // This method is called automatically in the constructor
|
|
1069
|
+
* // You typically don't call it directly
|
|
1070
|
+
* ```
|
|
1071
|
+
*/
|
|
629
1072
|
private loop() {
|
|
630
1073
|
setInterval(async () => {
|
|
631
1074
|
for (const player of this.getPlayers()) {
|
|
632
1075
|
if (player.pendingInputs.length > 0) {
|
|
633
|
-
const anyPlayer = player as
|
|
1076
|
+
const anyPlayer = player as any;
|
|
634
1077
|
if (!anyPlayer._isProcessingInputs) {
|
|
635
1078
|
anyPlayer._isProcessingInputs = true;
|
|
636
1079
|
await this.processInput(player.id).finally(() => {
|
|
@@ -643,7 +1086,21 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
643
1086
|
}
|
|
644
1087
|
|
|
645
1088
|
/**
|
|
646
|
-
* Get a world manager by id
|
|
1089
|
+
* Get a world manager by id
|
|
1090
|
+
*
|
|
1091
|
+
* Returns the world maps manager for the given world ID. Currently, only
|
|
1092
|
+
* one world manager is supported per map instance.
|
|
1093
|
+
*
|
|
1094
|
+
* @param id - The world ID (currently unused, returns the single manager)
|
|
1095
|
+
* @returns The WorldMapsManager instance, or null if not initialized
|
|
1096
|
+
*
|
|
1097
|
+
* @example
|
|
1098
|
+
* ```ts
|
|
1099
|
+
* const worldManager = map.getWorldMaps('my-world');
|
|
1100
|
+
* if (worldManager) {
|
|
1101
|
+
* const mapInfo = worldManager.getMapInfo('map1');
|
|
1102
|
+
* }
|
|
1103
|
+
* ```
|
|
647
1104
|
*/
|
|
648
1105
|
getWorldMaps(id: string): WorldMapsManager | null {
|
|
649
1106
|
if (!this.worldMapsManager) return null;
|
|
@@ -652,6 +1109,20 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
652
1109
|
|
|
653
1110
|
/**
|
|
654
1111
|
* Delete a world manager by id
|
|
1112
|
+
*
|
|
1113
|
+
* Removes the world maps manager from this map instance. Currently, only
|
|
1114
|
+
* one world manager is supported, so this clears the single manager.
|
|
1115
|
+
*
|
|
1116
|
+
* @param id - The world ID (currently unused)
|
|
1117
|
+
* @returns true if the manager was deleted, false if it didn't exist
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* ```ts
|
|
1121
|
+
* const deleted = map.deleteWorldMaps('my-world');
|
|
1122
|
+
* if (deleted) {
|
|
1123
|
+
* console.log('World manager removed');
|
|
1124
|
+
* }
|
|
1125
|
+
* ```
|
|
655
1126
|
*/
|
|
656
1127
|
deleteWorldMaps(id: string): boolean {
|
|
657
1128
|
if (!this.worldMapsManager) return false;
|
|
@@ -662,6 +1133,26 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
662
1133
|
|
|
663
1134
|
/**
|
|
664
1135
|
* Create a world manager dynamically
|
|
1136
|
+
*
|
|
1137
|
+
* Creates a new WorldMapsManager instance and configures it with the provided
|
|
1138
|
+
* map configurations. This is used when loading world data from Tiled or
|
|
1139
|
+
* other map editors.
|
|
1140
|
+
*
|
|
1141
|
+
* @param world - World configuration object
|
|
1142
|
+
* @param world.id - Optional world identifier
|
|
1143
|
+
* @param world.maps - Array of map configurations for the world
|
|
1144
|
+
* @returns The newly created WorldMapsManager instance
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* ```ts
|
|
1148
|
+
* const manager = map.createDynamicWorldMaps({
|
|
1149
|
+
* id: 'my-world',
|
|
1150
|
+
* maps: [
|
|
1151
|
+
* { id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 },
|
|
1152
|
+
* { id: 'map2', worldX: 800, worldY: 0, width: 800, height: 600 }
|
|
1153
|
+
* ]
|
|
1154
|
+
* });
|
|
1155
|
+
* ```
|
|
665
1156
|
*/
|
|
666
1157
|
createDynamicWorldMaps(world: { id?: string; maps: WorldMapConfig[] }): WorldMapsManager {
|
|
667
1158
|
const manager = new WorldMapsManager();
|
|
@@ -672,6 +1163,22 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
672
1163
|
|
|
673
1164
|
/**
|
|
674
1165
|
* Update world maps by id. Auto-create when missing.
|
|
1166
|
+
*
|
|
1167
|
+
* Updates the world maps configuration. If the world manager doesn't exist,
|
|
1168
|
+
* it is automatically created. This is useful for dynamically loading world
|
|
1169
|
+
* data or updating map positions.
|
|
1170
|
+
*
|
|
1171
|
+
* @param id - The world ID
|
|
1172
|
+
* @param maps - Array of map configurations to update
|
|
1173
|
+
* @returns Promise that resolves when the update is complete
|
|
1174
|
+
*
|
|
1175
|
+
* @example
|
|
1176
|
+
* ```ts
|
|
1177
|
+
* await map.updateWorldMaps('my-world', [
|
|
1178
|
+
* { id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 },
|
|
1179
|
+
* { id: 'map2', worldX: 800, worldY: 0, width: 800, height: 600 }
|
|
1180
|
+
* ]);
|
|
1181
|
+
* ```
|
|
675
1182
|
*/
|
|
676
1183
|
async updateWorldMaps(id: string, maps: WorldMapConfig[]) {
|
|
677
1184
|
let world = this.getWorldMaps(id);
|
|
@@ -867,30 +1374,165 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
867
1374
|
await eventInstance.execMethod('onInit')
|
|
868
1375
|
}
|
|
869
1376
|
|
|
1377
|
+
/**
|
|
1378
|
+
* Get an event by its ID
|
|
1379
|
+
*
|
|
1380
|
+
* Returns the event with the specified ID, or undefined if not found.
|
|
1381
|
+
* The return type can be narrowed using TypeScript generics.
|
|
1382
|
+
*
|
|
1383
|
+
* @param eventId - The unique identifier of the event
|
|
1384
|
+
* @returns The event instance, or undefined if not found
|
|
1385
|
+
*
|
|
1386
|
+
* @example
|
|
1387
|
+
* ```ts
|
|
1388
|
+
* // Get any event
|
|
1389
|
+
* const event = map.getEvent('npc-1');
|
|
1390
|
+
*
|
|
1391
|
+
* // Get event with type narrowing
|
|
1392
|
+
* const npc = map.getEvent<MyNPC>('npc-1');
|
|
1393
|
+
* if (npc) {
|
|
1394
|
+
* npc.speak('Hello!');
|
|
1395
|
+
* }
|
|
1396
|
+
* ```
|
|
1397
|
+
*/
|
|
870
1398
|
getEvent<T extends RpgPlayer>(eventId: string): T | undefined {
|
|
871
1399
|
return this.events()[eventId] as T
|
|
872
1400
|
}
|
|
873
1401
|
|
|
1402
|
+
/**
|
|
1403
|
+
* Get a player by their ID
|
|
1404
|
+
*
|
|
1405
|
+
* Returns the player with the specified ID, or undefined if not found.
|
|
1406
|
+
*
|
|
1407
|
+
* @param playerId - The unique identifier of the player
|
|
1408
|
+
* @returns The player instance, or undefined if not found
|
|
1409
|
+
*
|
|
1410
|
+
* @example
|
|
1411
|
+
* ```ts
|
|
1412
|
+
* const player = map.getPlayer('player-123');
|
|
1413
|
+
* if (player) {
|
|
1414
|
+
* console.log(`Player ${player.name} is on the map`);
|
|
1415
|
+
* }
|
|
1416
|
+
* ```
|
|
1417
|
+
*/
|
|
874
1418
|
getPlayer(playerId: string): RpgPlayer | undefined {
|
|
875
1419
|
return this.players()[playerId]
|
|
876
1420
|
}
|
|
877
1421
|
|
|
1422
|
+
/**
|
|
1423
|
+
* Get all players currently on the map
|
|
1424
|
+
*
|
|
1425
|
+
* Returns an array of all players that are currently connected to this map.
|
|
1426
|
+
*
|
|
1427
|
+
* @returns Array of all RpgPlayer instances on the map
|
|
1428
|
+
*
|
|
1429
|
+
* @example
|
|
1430
|
+
* ```ts
|
|
1431
|
+
* const players = map.getPlayers();
|
|
1432
|
+
* console.log(`There are ${players.length} players on the map`);
|
|
1433
|
+
*
|
|
1434
|
+
* players.forEach(player => {
|
|
1435
|
+
* console.log(`- ${player.name}`);
|
|
1436
|
+
* });
|
|
1437
|
+
* ```
|
|
1438
|
+
*/
|
|
878
1439
|
getPlayers(): RpgPlayer[] {
|
|
879
1440
|
return Object.values(this.players())
|
|
880
1441
|
}
|
|
881
1442
|
|
|
1443
|
+
/**
|
|
1444
|
+
* Get all events on the map
|
|
1445
|
+
*
|
|
1446
|
+
* Returns an array of all events (NPCs, objects, etc.) that are currently
|
|
1447
|
+
* on this map.
|
|
1448
|
+
*
|
|
1449
|
+
* @returns Array of all RpgEvent instances on the map
|
|
1450
|
+
*
|
|
1451
|
+
* @example
|
|
1452
|
+
* ```ts
|
|
1453
|
+
* const events = map.getEvents();
|
|
1454
|
+
* console.log(`There are ${events.length} events on the map`);
|
|
1455
|
+
*
|
|
1456
|
+
* events.forEach(event => {
|
|
1457
|
+
* console.log(`- ${event.name} at (${event.x}, ${event.y})`);
|
|
1458
|
+
* });
|
|
1459
|
+
* ```
|
|
1460
|
+
*/
|
|
882
1461
|
getEvents(): RpgEvent[] {
|
|
883
1462
|
return Object.values(this.events())
|
|
884
1463
|
}
|
|
885
1464
|
|
|
1465
|
+
/**
|
|
1466
|
+
* Get the first event that matches a condition
|
|
1467
|
+
*
|
|
1468
|
+
* Searches through all events on the map and returns the first one that
|
|
1469
|
+
* matches the provided callback function.
|
|
1470
|
+
*
|
|
1471
|
+
* @param cb - Callback function that returns true for the desired event
|
|
1472
|
+
* @returns The first matching event, or undefined if none found
|
|
1473
|
+
*
|
|
1474
|
+
* @example
|
|
1475
|
+
* ```ts
|
|
1476
|
+
* // Find an event by name
|
|
1477
|
+
* const npc = map.getEventBy(event => event.name === 'Merchant');
|
|
1478
|
+
*
|
|
1479
|
+
* // Find an event at a specific position
|
|
1480
|
+
* const chest = map.getEventBy(event =>
|
|
1481
|
+
* event.x === 100 && event.y === 200
|
|
1482
|
+
* );
|
|
1483
|
+
* ```
|
|
1484
|
+
*/
|
|
886
1485
|
getEventBy(cb: (event: RpgEvent) => boolean): RpgEvent | undefined {
|
|
887
1486
|
return this.getEventsBy(cb)[0]
|
|
888
1487
|
}
|
|
889
1488
|
|
|
1489
|
+
/**
|
|
1490
|
+
* Get all events that match a condition
|
|
1491
|
+
*
|
|
1492
|
+
* Searches through all events on the map and returns all events that
|
|
1493
|
+
* match the provided callback function.
|
|
1494
|
+
*
|
|
1495
|
+
* @param cb - Callback function that returns true for desired events
|
|
1496
|
+
* @returns Array of all matching events
|
|
1497
|
+
*
|
|
1498
|
+
* @example
|
|
1499
|
+
* ```ts
|
|
1500
|
+
* // Find all NPCs
|
|
1501
|
+
* const npcs = map.getEventsBy(event => event.name.startsWith('NPC-'));
|
|
1502
|
+
*
|
|
1503
|
+
* // Find all events in a specific area
|
|
1504
|
+
* const nearbyEvents = map.getEventsBy(event =>
|
|
1505
|
+
* event.x >= 0 && event.x <= 100 &&
|
|
1506
|
+
* event.y >= 0 && event.y <= 100
|
|
1507
|
+
* );
|
|
1508
|
+
* ```
|
|
1509
|
+
*/
|
|
890
1510
|
getEventsBy(cb: (event: RpgEvent) => boolean): RpgEvent[] {
|
|
891
1511
|
return this.getEvents().filter(cb)
|
|
892
1512
|
}
|
|
893
1513
|
|
|
1514
|
+
/**
|
|
1515
|
+
* Remove an event from the map
|
|
1516
|
+
*
|
|
1517
|
+
* Removes the event with the specified ID from the map. The event will
|
|
1518
|
+
* be removed from the synchronized events signal, causing it to disappear
|
|
1519
|
+
* on all clients.
|
|
1520
|
+
*
|
|
1521
|
+
* @param eventId - The unique identifier of the event to remove
|
|
1522
|
+
*
|
|
1523
|
+
* @example
|
|
1524
|
+
* ```ts
|
|
1525
|
+
* // Remove an event
|
|
1526
|
+
* map.removeEvent('npc-1');
|
|
1527
|
+
*
|
|
1528
|
+
* // Remove event after interaction
|
|
1529
|
+
* const chest = map.getEvent('chest-1');
|
|
1530
|
+
* if (chest) {
|
|
1531
|
+
* // ... do something with chest ...
|
|
1532
|
+
* map.removeEvent('chest-1');
|
|
1533
|
+
* }
|
|
1534
|
+
* ```
|
|
1535
|
+
*/
|
|
894
1536
|
removeEvent(eventId: string) {
|
|
895
1537
|
delete this.events()[eventId]
|
|
896
1538
|
}
|
|
@@ -975,16 +1617,44 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
975
1617
|
})
|
|
976
1618
|
}
|
|
977
1619
|
|
|
978
|
-
/**
|
|
979
|
-
* Set the sync schema for the map
|
|
980
|
-
* @param schema - The schema to set
|
|
981
|
-
*/
|
|
982
1620
|
/**
|
|
983
1621
|
* Configure runtime synchronized properties on the map
|
|
984
|
-
*
|
|
985
|
-
*
|
|
1622
|
+
*
|
|
1623
|
+
* This method allows you to dynamically add synchronized properties to the map
|
|
1624
|
+
* that will be automatically synced with clients. The schema follows the same
|
|
1625
|
+
* structure as module properties with `$initial`, `$syncWithClient`, and `$permanent` options.
|
|
1626
|
+
*
|
|
1627
|
+
* ## Architecture
|
|
1628
|
+
*
|
|
986
1629
|
* - Reads a schema object shaped like module props
|
|
987
1630
|
* - Creates typed sync signals with @signe/sync
|
|
1631
|
+
* - Properties are accessible as `map.propertyName`
|
|
1632
|
+
*
|
|
1633
|
+
* @param schema - Schema object defining the properties to sync
|
|
1634
|
+
* @param schema[key].$initial - Initial value for the property
|
|
1635
|
+
* @param schema[key].$syncWithClient - Whether to sync this property to clients
|
|
1636
|
+
* @param schema[key].$permanent - Whether to persist this property
|
|
1637
|
+
*
|
|
1638
|
+
* @example
|
|
1639
|
+
* ```ts
|
|
1640
|
+
* // Add synchronized properties to the map
|
|
1641
|
+
* map.setSync({
|
|
1642
|
+
* weather: {
|
|
1643
|
+
* $initial: 'sunny',
|
|
1644
|
+
* $syncWithClient: true,
|
|
1645
|
+
* $permanent: false
|
|
1646
|
+
* },
|
|
1647
|
+
* timeOfDay: {
|
|
1648
|
+
* $initial: 12,
|
|
1649
|
+
* $syncWithClient: true,
|
|
1650
|
+
* $permanent: false
|
|
1651
|
+
* }
|
|
1652
|
+
* });
|
|
1653
|
+
*
|
|
1654
|
+
* // Use the properties
|
|
1655
|
+
* map.weather.set('rainy');
|
|
1656
|
+
* const currentWeather = map.weather();
|
|
1657
|
+
* ```
|
|
988
1658
|
*/
|
|
989
1659
|
setSync(schema: Record<string, any>) {
|
|
990
1660
|
for (let key in schema) {
|
|
@@ -1302,6 +1972,70 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1302
1972
|
player.stopSound(soundId);
|
|
1303
1973
|
});
|
|
1304
1974
|
}
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* Shake the map for all players
|
|
1978
|
+
*
|
|
1979
|
+
* This method triggers a shake animation on the map for all players currently on the map.
|
|
1980
|
+
* The shake effect creates a visual feedback that can be used for earthquakes, explosions,
|
|
1981
|
+
* impacts, or any dramatic event that should affect the entire map visually.
|
|
1982
|
+
*
|
|
1983
|
+
* ## Architecture
|
|
1984
|
+
*
|
|
1985
|
+
* Broadcasts a shake event to all clients connected to the map. Each client receives
|
|
1986
|
+
* the shake configuration and triggers the shake animation on the map container using
|
|
1987
|
+
* Canvas Engine's shake directive.
|
|
1988
|
+
*
|
|
1989
|
+
* @param options - Optional shake configuration
|
|
1990
|
+
* @param options.intensity - Shake intensity in pixels (default: 10)
|
|
1991
|
+
* @param options.duration - Duration of the shake animation in milliseconds (default: 500)
|
|
1992
|
+
* @param options.frequency - Number of shake oscillations during the animation (default: 10)
|
|
1993
|
+
* @param options.direction - Direction of the shake - 'x', 'y', or 'both' (default: 'both')
|
|
1994
|
+
*
|
|
1995
|
+
* @example
|
|
1996
|
+
* ```ts
|
|
1997
|
+
* // Basic shake with default settings
|
|
1998
|
+
* map.shakeMap();
|
|
1999
|
+
*
|
|
2000
|
+
* // Intense earthquake effect
|
|
2001
|
+
* map.shakeMap({
|
|
2002
|
+
* intensity: 25,
|
|
2003
|
+
* duration: 1000,
|
|
2004
|
+
* frequency: 15,
|
|
2005
|
+
* direction: 'both'
|
|
2006
|
+
* });
|
|
2007
|
+
*
|
|
2008
|
+
* // Horizontal shake for side impact
|
|
2009
|
+
* map.shakeMap({
|
|
2010
|
+
* intensity: 15,
|
|
2011
|
+
* duration: 400,
|
|
2012
|
+
* direction: 'x'
|
|
2013
|
+
* });
|
|
2014
|
+
*
|
|
2015
|
+
* // Vertical shake for ground impact
|
|
2016
|
+
* map.shakeMap({
|
|
2017
|
+
* intensity: 20,
|
|
2018
|
+
* duration: 600,
|
|
2019
|
+
* direction: 'y'
|
|
2020
|
+
* });
|
|
2021
|
+
* ```
|
|
2022
|
+
*/
|
|
2023
|
+
shakeMap(options?: {
|
|
2024
|
+
intensity?: number;
|
|
2025
|
+
duration?: number;
|
|
2026
|
+
frequency?: number;
|
|
2027
|
+
direction?: 'x' | 'y' | 'both';
|
|
2028
|
+
}): void {
|
|
2029
|
+
this.$broadcast({
|
|
2030
|
+
type: "shakeMap",
|
|
2031
|
+
value: {
|
|
2032
|
+
intensity: options?.intensity ?? 10,
|
|
2033
|
+
duration: options?.duration ?? 500,
|
|
2034
|
+
frequency: options?.frequency ?? 10,
|
|
2035
|
+
direction: options?.direction ?? 'both',
|
|
2036
|
+
},
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
1305
2039
|
}
|
|
1306
2040
|
|
|
1307
2041
|
export interface RpgMap {
|