@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/src/RpgServer.ts CHANGED
@@ -1,17 +1,45 @@
1
+ import { MapOptions } from "./decorators/map"
1
2
  import { RpgPlayer } from "./Player/Player"
2
3
  import { type RpgMap } from "./rooms/map"
3
4
  import { RpgServerEngine } from "./RpgServerEngine"
5
+ import { WorldMapConfig, RpgShape } from "@rpgjs/common"
6
+ import { RpgEvent } from "./Player/Player"
4
7
 
5
- type RpgShape = any
6
- type RpgClassMap<T> = any
7
- type RpgClassEvent<T> = any
8
- type RpgEvent = any
8
+ type RpgClassMap<T> = new () => T
9
+ type RpgClassEvent<T> = RpgEvent
9
10
  type MatchMakerOption = any
10
11
  type RpgMatchMaker = any
11
12
  type IStoreState = any
12
- type TiledMap = any
13
- type WorldMap = any
14
- type MapOptions = any
13
+
14
+ /**
15
+ * Interface for world map configuration
16
+ *
17
+ * Represents a world that contains multiple maps with their spatial relationships.
18
+ * This is typically used with Tiled Map Editor's world files.
19
+ *
20
+ * @interface WorldMap
21
+ * @example
22
+ * ```ts
23
+ * const worldMap: WorldMap = {
24
+ * id: 'my-world',
25
+ * maps: [
26
+ * { id: 'map1', worldX: 0, worldY: 0, width: 800, height: 600 },
27
+ * { id: 'map2', worldX: 800, worldY: 0, width: 800, height: 600 }
28
+ * ]
29
+ * }
30
+ * ```
31
+ */
32
+ export interface WorldMap {
33
+ /** Optional world identifier */
34
+ id?: string;
35
+ /** Array of map configurations that belong to this world */
36
+ maps: WorldMapConfig[];
37
+ /** Only show adjacent maps (used by Tiled Map Editor) */
38
+ onlyShowAdjacentMaps?: boolean;
39
+ /** Type identifier (used by Tiled Map Editor, should be 'world') */
40
+ type?: 'world';
41
+ }
42
+
15
43
 
16
44
  export interface RpgServerEngineHooks {
17
45
  /**
@@ -420,6 +448,9 @@ export interface RpgEventHooks {
420
448
  /**
421
449
  * Map hooks interface for handling map lifecycle events
422
450
  *
451
+ * These hooks are global hooks that apply to all maps in the game.
452
+ * They are defined in the RpgModule configuration and executed for every map instance.
453
+ *
423
454
  * @interface RpgMapHooks
424
455
  * @since 4.0.0
425
456
  */
@@ -472,6 +503,74 @@ export interface RpgMapHooks {
472
503
  * ```
473
504
  */
474
505
  onBeforeUpdate<T, U = RpgMap>(mapData: T, map: U): U | Promise<U>
506
+
507
+ /**
508
+ * Called when a map is loaded and initialized
509
+ *
510
+ * This hook is executed once when the map data is loaded and ready.
511
+ * It applies to all maps globally. Use this to initialize map-specific properties
512
+ * or setup that should happen for every map.
513
+ *
514
+ * @param {RpgMap} map - The map instance that was loaded
515
+ * @returns {any}
516
+ * @memberof RpgMapHooks
517
+ * @example
518
+ * ```ts
519
+ * const mapHooks: RpgMapHooks = {
520
+ * onLoad(map: RpgMap) {
521
+ * console.log(`Map ${map.id} loaded`)
522
+ * // Initialize global map properties
523
+ * }
524
+ * }
525
+ * ```
526
+ */
527
+ onLoad?: (map: RpgMap) => any
528
+
529
+ /**
530
+ * Called when a player joins any map
531
+ *
532
+ * This hook is executed each time a player joins any map in the game.
533
+ * It applies globally to all maps. Use this to perform actions that should
534
+ * happen whenever a player enters any map.
535
+ *
536
+ * @param {RpgPlayer} player - The player joining the map
537
+ * @param {RpgMap} map - The map instance the player joined
538
+ * @returns {any}
539
+ * @memberof RpgMapHooks
540
+ * @example
541
+ * ```ts
542
+ * const mapHooks: RpgMapHooks = {
543
+ * onJoin(player: RpgPlayer, map: RpgMap) {
544
+ * console.log(`${player.name} joined map ${map.id}`)
545
+ * // Perform global actions when player joins any map
546
+ * }
547
+ * }
548
+ * ```
549
+ */
550
+ onJoin?: (player: RpgPlayer, map: RpgMap) => any
551
+
552
+ /**
553
+ * Called when a player leaves any map
554
+ *
555
+ * This hook is executed each time a player leaves any map in the game.
556
+ * It applies globally to all maps. Use this to perform cleanup or actions
557
+ * that should happen whenever a player exits any map.
558
+ *
559
+ * @param {RpgPlayer} player - The player leaving the map
560
+ * @param {RpgMap} map - The map instance the player left
561
+ * @returns {any}
562
+ * @memberof RpgMapHooks
563
+ * @example
564
+ * ```ts
565
+ * const mapHooks: RpgMapHooks = {
566
+ * onLeave(player: RpgPlayer, map: RpgMap) {
567
+ * console.log(`${player.name} left map ${map.id}`)
568
+ * // Perform global cleanup when player leaves any map
569
+ * }
570
+ * }
571
+ * ```
572
+ */
573
+ onLeave?: (player: RpgPlayer, map: RpgMap) => any
475
574
  }
476
575
 
477
576
  export interface RpgServer {
@@ -485,14 +584,14 @@ export interface RpgServer {
485
584
  * @example
486
585
  *
487
586
  * ```ts
488
- * import { RpgServer, RpgModule } from '@rpgjs/server'
587
+ * import { RpgServer } from '@rpgjs/server'
588
+ * import { defineModule } from '@rpgjs/common'
489
589
  *
490
- * @RpgModule<RpgServer>({
590
+ * export default defineModule<RpgServer>({
491
591
  * hooks: {
492
592
  * player: ['onAuth']
493
593
  * }
494
594
  * })
495
- * class RpgServerEngine { }
496
595
  * ```
497
596
  *
498
597
  * Emit the hook:
@@ -532,18 +631,18 @@ export interface RpgServer {
532
631
  * Object containing the hooks concerning the engine
533
632
  *
534
633
  * ```ts
535
- * import { RpgServerEngine, RpgServerEngineHooks, RpgModule, RpgClient } from '@rpgjs/server'
634
+ * import { RpgServerEngine, RpgServerEngineHooks, RpgServer } from '@rpgjs/server'
635
+ * import { defineModule } from '@rpgjs/common'
536
636
  *
537
- * const engine: RpgEngineHooks = {
637
+ * const engine: RpgServerEngineHooks = {
538
638
  * onStart(server: RpgServerEngine) {
539
639
  * console.log('server is started')
540
640
  * }
541
641
  * }
542
642
  *
543
- * @RpgModule<RpgServer>({
643
+ * export default defineModule<RpgServer>({
544
644
  * engine
545
645
  * })
546
- * class RpgServerModule {}
547
646
  * ```
548
647
  *
549
648
  * @prop {RpgServerEngineHooks} [engine]
@@ -555,7 +654,8 @@ export interface RpgServer {
555
654
  * Give the `player` object hooks. Each time a player connects, an instance of `RpgPlayer` is created.
556
655
  *
557
656
  * ```ts
558
- * import { RpgPlayer, RpgServer, RpgPlayerHooks, RpgModule } from '@rpgjs/server'
657
+ * import { RpgPlayer, RpgServer, RpgPlayerHooks } from '@rpgjs/server'
658
+ * import { defineModule } from '@rpgjs/common'
559
659
  *
560
660
  * const player: RpgPlayerHooks = {
561
661
  * onConnected(player: RpgPlayer) {
@@ -563,10 +663,9 @@ export interface RpgServer {
563
663
  * }
564
664
  * }
565
665
  *
566
- * @RpgModule<RpgServer>({
666
+ * export default defineModule<RpgServer>({
567
667
  * player
568
668
  * })
569
- * class RpgServerEngine { }
570
669
  * ```
571
670
  *
572
671
  * @prop {RpgClassPlayer<RpgPlayer>} [player]
@@ -578,15 +677,15 @@ export interface RpgServer {
578
677
  * References all data in the server. it is mainly used to retrieve data according to their identifier
579
678
  *
580
679
  * ```ts
581
- * import { RpgServer, RpgModule } from '@rpgjs/server'
680
+ * import { RpgServer } from '@rpgjs/server'
681
+ * import { defineModule } from '@rpgjs/common'
582
682
  * import { Potion } from 'my-database/items/potion'
583
683
  *
584
- * @RpgModule<RpgServer>({
684
+ * export default defineModule<RpgServer>({
585
685
  * database: {
586
686
  * Potion
587
687
  * }
588
688
  * })
589
- * class RpgServerEngine { }
590
689
  * ```
591
690
  *
592
691
  * @prop { { [dataName]: data } } [database]
@@ -595,11 +694,13 @@ export interface RpgServer {
595
694
  database?: object | any[],
596
695
 
597
696
  /**
598
- * Array of all maps. Each element is an `RpgMap` class
697
+ * Array of all maps. Each element can be either a class (decorated with `@MapData` or not) or a `MapOptions` object
599
698
  *
600
699
  * ```ts
601
- * import { RpgMap, MapData, RpgServer, RpgModule } from '@rpgjs/server'
700
+ * import { RpgMap, MapData, RpgServer } from '@rpgjs/server'
701
+ * import { defineModule } from '@rpgjs/common'
602
702
  *
703
+ * // Class that extends RpgMap (optional)
603
704
  * @MapData({
604
705
  * id: 'town',
605
706
  * file: require('./tmx/mymap.tmx'),
@@ -607,18 +708,29 @@ export interface RpgServer {
607
708
  * })
608
709
  * class TownMap extends RpgMap { }
609
710
  *
610
- * @RpgModule<RpgServer>({
711
+ * // Or a simple class without extending RpgMap
712
+ * @MapData({
713
+ * id: 'map',
714
+ * file: '',
715
+ * events: [{ event: Event() }]
716
+ * })
717
+ * class SimpleMap {}
718
+ *
719
+ * export default defineModule<RpgServer>({
611
720
  * maps: [
612
- * TownMap
721
+ * TownMap,
722
+ * SimpleMap
613
723
  * ]
614
724
  * })
615
- * class RpgServerEngine { }
616
725
  * ```
617
726
  *
618
727
  * It is possible to just give the object as well
619
728
  *
620
729
  * ```ts
621
- * @RpgModule<RpgServer>({
730
+ * import { RpgServer } from '@rpgjs/server'
731
+ * import { defineModule } from '@rpgjs/common'
732
+ *
733
+ * export default defineModule<RpgServer>({
622
734
  * maps: [
623
735
  * {
624
736
  * id: 'town',
@@ -627,25 +739,65 @@ export interface RpgServer {
627
739
  * }
628
740
  * ]
629
741
  * })
630
- * class RpgServerEngine { }
631
742
  * ```
632
743
  *
633
744
  * Since version 3.0.0-beta.8, you can just pass the path to the file. The identifier will then be the name of the file
634
745
  *
635
746
  * ```ts
636
- * @RpgModule<RpgServer>({
747
+ * import { RpgServer } from '@rpgjs/server'
748
+ * import { defineModule } from '@rpgjs/common'
749
+ *
750
+ * export default defineModule<RpgServer>({
637
751
  * maps: [
638
752
  * require('./tmx/mymap.tmx') // id is "mymap"
639
753
  * ]
640
754
  * })
641
- * class RpgServerEngine { }
642
755
  * ```
643
756
  *
644
- * @prop {RpgClassMap<RpgMap>[]} [maps]
757
+ * @prop {(new () => any) | MapOptions)[]} [maps]
645
758
  * @memberof RpgServer
646
759
  * */
647
- maps?: RpgClassMap<RpgMap>[] | MapOptions[] | string[] | TiledMap[],
760
+ maps?: ((new () => any) | MapOptions)[],
648
761
 
762
+ /**
763
+ * Global map hooks that apply to all maps in the game
764
+ *
765
+ * These hooks are executed for every map instance and allow you to define
766
+ * global behavior that should happen for all maps. They are different from
767
+ * map-specific hooks defined in `@MapData` which only apply to a specific map class.
768
+ *
769
+ * ```ts
770
+ * import { RpgServer, RpgMapHooks, RpgMap, RpgPlayer } from '@rpgjs/server'
771
+ * import { defineModule } from '@rpgjs/common'
772
+ *
773
+ * const mapHooks: RpgMapHooks = {
774
+ * onLoad(map: RpgMap) {
775
+ * console.log(`Map ${map.id} loaded`)
776
+ * // Initialize global map properties
777
+ * },
778
+ * onJoin(player: RpgPlayer, map: RpgMap) {
779
+ * console.log(`${player.name} joined map ${map.id}`)
780
+ * // Perform global actions when player joins any map
781
+ * },
782
+ * onLeave(player: RpgPlayer, map: RpgMap) {
783
+ * console.log(`${player.name} left map ${map.id}`)
784
+ * // Perform global cleanup when player leaves any map
785
+ * },
786
+ * onBeforeUpdate(mapData, map) {
787
+ * // Modify map data before update
788
+ * return map
789
+ * }
790
+ * }
791
+ *
792
+ * export default defineModule<RpgServer>({
793
+ * map: mapHooks
794
+ * })
795
+ * ```
796
+ *
797
+ * @prop {RpgMapHooks} [map]
798
+ * @memberof RpgServer
799
+ * @since 4.0.0
800
+ * */
649
801
  map?: RpgMapHooks
650
802
 
651
803
  event?: RpgEventHooks
@@ -661,41 +813,62 @@ export interface RpgServer {
661
813
  events?: RpgClassEvent<RpgEvent>[]
662
814
 
663
815
  /**
664
- * Loads the content of a `.world` file from Tiled Map Editor into the map scene
816
+ * Array of world map configurations
665
817
  *
666
- * > Note, that if the map already exists (i.e. you have already defined an RpgMap), the world will retrieve the already existing map. Otherwise it will create a new map
818
+ * Loads the content of a `.world` file from Tiled Map Editor into the map scene.
819
+ * Each world contains multiple maps with their spatial relationships.
667
820
  *
668
- * @prop {object[]} [worldMaps]
669
- * object is
670
- * ```ts
671
- * {
672
- * id?: string
673
- * maps: {
674
- * id?: string
675
- * properties?: object
676
- * fileName: string;
677
- height: number;
678
- width: number;
679
- x: number;
680
- y: number;
681
- * }[],
682
- onlyShowAdjacentMaps: boolean, // only for Tiled Map Editor
683
- type: 'world' // only for Tiled Map Editor
684
- * }
685
- * ```
821
+ * > Note: If a map already exists (i.e. you have already defined an RpgMap),
822
+ * > the world will retrieve the already existing map. Otherwise it will create a new map.
823
+ *
824
+ * @prop {WorldMap[]} [worldMaps]
686
825
  * @since 3.0.0-beta.8
826
+ * @memberof RpgServer
687
827
  * @example
688
828
  * ```ts
829
+ * import { RpgServer } from '@rpgjs/server'
830
+ * import { defineModule } from '@rpgjs/common'
689
831
  * import myworld from 'myworld.world'
690
832
  *
691
- * @RpgModule<RpgServer>({
692
- * worldMaps: [
693
- * myworld
694
- * ]
833
+ * export default defineModule<RpgServer>({
834
+ * worldMaps: [
835
+ * myworld
836
+ * ]
837
+ * })
838
+ * ```
839
+ *
840
+ * @example
841
+ * ```ts
842
+ * import { RpgServer } from '@rpgjs/server'
843
+ * import { defineModule } from '@rpgjs/common'
844
+ *
845
+ * // Manual world configuration
846
+ * export default defineModule<RpgServer>({
847
+ * worldMaps: [
848
+ * {
849
+ * id: 'my-world',
850
+ * maps: [
851
+ * {
852
+ * id: 'map1',
853
+ * worldX: 0,
854
+ * worldY: 0,
855
+ * width: 800,
856
+ * height: 600,
857
+ * tileWidth: 32,
858
+ * tileHeight: 32
859
+ * },
860
+ * {
861
+ * id: 'map2',
862
+ * worldX: 800,
863
+ * worldY: 0,
864
+ * width: 800,
865
+ * height: 600
866
+ * }
867
+ * ]
868
+ * }
869
+ * ]
695
870
  * })
696
- * class RpgServerEngine { }
697
871
  * ```
698
- * @memberof RpgServer
699
872
  */
700
873
  worldMaps?: WorldMap[]
701
874
 
@@ -720,11 +893,12 @@ export interface RpgServer {
720
893
  * Example:
721
894
  *
722
895
  * ```ts
723
- * import { RpgModule, RpgServer, Presets } from '@rpgjs/server'
896
+ * import { RpgServer, Presets } from '@rpgjs/server'
897
+ * import { defineModule } from '@rpgjs/common'
724
898
  *
725
899
  * const { ATK, PDEF } = Presets
726
900
  *
727
- * @RpgModule<RpgServer>({
901
+ * export default defineModule<RpgServer>({
728
902
  * damageFormulas: {
729
903
  * damagePhysic(a, b) {
730
904
  * let damage = a[ATK] - b[PDEF]
@@ -733,7 +907,6 @@ export interface RpgServer {
733
907
  * }
734
908
  * }
735
909
  * })
736
- * class RpgServerEngine { }
737
910
  * ```
738
911
  * @prop {object} damageFormulas
739
912
  * @memberof RpgServer
@@ -745,6 +918,10 @@ export interface RpgServer {
745
918
  coefficientElements?: (a, b, bDef) => number
746
919
  }
747
920
 
921
+ /*
922
+ * Scalability configuration for the server
923
+ * @deprecated
924
+ */
748
925
  scalability?: {
749
926
  matchMaker: MatchMakerOption,
750
927
  stateStore: IStoreState
@@ -24,7 +24,7 @@ export interface MapOptions {
24
24
  * @prop {string} file
25
25
  * @memberof MapData
26
26
  * */
27
- file: any,
27
+ file?: any,
28
28
 
29
29
  /**
30
30
  * The name of the map.
@@ -97,6 +97,29 @@ export interface MapOptions {
97
97
  * */
98
98
  sounds?: string[]
99
99
 
100
+ /**
101
+ * Whether to stop all sounds before playing the map sounds when a player joins.
102
+ *
103
+ * If set to `true`, all currently playing sounds will be stopped before playing the new map sounds.
104
+ * This prevents sound overlap when changing maps.
105
+ *
106
+ * By default, this is `false`, meaning sounds from the previous map will continue playing.
107
+ *
108
+ * ```ts
109
+ * @MapData({
110
+ * id: 'battle-map',
111
+ * sounds: ['battle-theme'],
112
+ * stopAllSoundsBeforeJoin: true // Stop all sounds before playing battle theme
113
+ * })
114
+ * class BattleMap extends RpgMap {}
115
+ * ```
116
+ *
117
+ * @prop {boolean} [stopAllSoundsBeforeJoin=false]
118
+ * @memberof MapData
119
+ * @since 5.0.0
120
+ * */
121
+ stopAllSoundsBeforeJoin?: boolean
122
+
100
123
  /**
101
124
  * Specify which properties will be synchronized with the client. On the client side, you can retrieve the values synchronized with the valueChanges property on the scene
102
125
  *
@@ -175,6 +198,75 @@ export interface MapOptions {
175
198
  * @memberof MapData
176
199
  * */
177
200
  lowMemory?: boolean
201
+
202
+ /**
203
+ * Called when the map is loaded and initialized
204
+ *
205
+ * This hook is executed once when the map data is loaded and ready.
206
+ * Use this to initialize map-specific properties or setup.
207
+ *
208
+ * @prop { () => any } [onLoad]
209
+ * @memberof MapData
210
+ * @example
211
+ * ```ts
212
+ * @MapData({
213
+ * id: 'town',
214
+ * file: require('./tmx/town.tmx'),
215
+ * onLoad() {
216
+ * console.log('Town map loaded')
217
+ * // Initialize map properties
218
+ * }
219
+ * })
220
+ * class TownMap extends RpgMap {}
221
+ * ```
222
+ * */
223
+ onLoad?: () => any
224
+
225
+ /**
226
+ * Called when a player joins the map
227
+ *
228
+ * This hook is executed each time a player joins the map.
229
+ * Use this to perform actions when a player enters the map.
230
+ *
231
+ * @prop { (player: RpgPlayer) => any } [onJoin]
232
+ * @memberof MapData
233
+ * @example
234
+ * ```ts
235
+ * @MapData({
236
+ * id: 'town',
237
+ * file: require('./tmx/town.tmx'),
238
+ * onJoin(player: RpgPlayer) {
239
+ * console.log(`${player.name} joined the town`)
240
+ * // Perform actions when player joins
241
+ * }
242
+ * })
243
+ * class TownMap extends RpgMap {}
244
+ * ```
245
+ * */
246
+ onJoin?: (player: RpgPlayer) => any
247
+
248
+ /**
249
+ * Called when a player leaves the map
250
+ *
251
+ * This hook is executed each time a player leaves the map.
252
+ * Use this to perform cleanup or actions when a player exits the map.
253
+ *
254
+ * @prop { (player: RpgPlayer) => any } [onLeave]
255
+ * @memberof MapData
256
+ * @example
257
+ * ```ts
258
+ * @MapData({
259
+ * id: 'town',
260
+ * file: require('./tmx/town.tmx'),
261
+ * onLeave(player: RpgPlayer) {
262
+ * console.log(`${player.name} left the town`)
263
+ * // Perform cleanup when player leaves
264
+ * }
265
+ * })
266
+ * class TownMap extends RpgMap {}
267
+ * ```
268
+ * */
269
+ onLeave?: (player: RpgPlayer) => any
178
270
  }
179
271
 
180
272
  export function MapData(options: MapOptions) {
@@ -187,6 +279,7 @@ export function MapData(options: MapOptions) {
187
279
  target.prototype.id = options.id
188
280
  target.prototype.sounds = options.sounds
189
281
  target.prototype.lowMemory = options.lowMemory
282
+ target.prototype.stopAllSoundsBeforeJoin = options.stopAllSoundsBeforeJoin
190
283
 
191
284
  target.prototype.$schema = {}
192
285
 
@@ -194,5 +287,16 @@ export function MapData(options: MapOptions) {
194
287
  target.prototype.$schema = options.syncSchema
195
288
  }
196
289
  target.prototype._events = options.events
290
+
291
+ // Store hooks on prototype
292
+ if (options.onLoad) {
293
+ target.prototype.onLoad = options.onLoad
294
+ }
295
+ if (options.onJoin) {
296
+ target.prototype.onJoin = options.onJoin
297
+ }
298
+ if (options.onLeave) {
299
+ target.prototype.onLeave = options.onLeave
300
+ }
197
301
  }
198
302
  }
package/src/module.ts CHANGED
@@ -3,12 +3,66 @@ import { FactoryProvider } from "@signe/di";
3
3
  import { RpgServerEngine } from "./RpgServerEngine";
4
4
  import { RpgMap } from "./rooms/map";
5
5
  import { RpgPlayer } from "./Player/Player";
6
+ import { RpgServer } from "./RpgServer";
6
7
 
7
- export function provideServerModules(modules: any[]): FactoryProvider {
8
+ /**
9
+ * Type for server modules that can be either:
10
+ * - An object implementing RpgServer interface
11
+ * - A class decorated with @RpgModule decorator
12
+ */
13
+ export type RpgServerModule = RpgServer | (new () => any);
14
+
15
+ /**
16
+ * Provides server modules configuration to Angular Dependency Injection
17
+ *
18
+ * This function accepts an array of server modules that can be either:
19
+ * - Objects implementing the RpgServer interface
20
+ * - Classes decorated with the @RpgModule decorator (which will be instantiated)
21
+ *
22
+ * @param modules - Array of server modules (objects or classes)
23
+ * @returns FactoryProvider configuration for Angular DI
24
+ * @example
25
+ * ```ts
26
+ * // Using an object
27
+ * provideServerModules([
28
+ * {
29
+ * player: {
30
+ * onConnected(player) {
31
+ * console.log('Player connected')
32
+ * }
33
+ * }
34
+ * }
35
+ * ])
36
+ *
37
+ * // Using a decorated class
38
+ * @RpgModule<RpgServer>({
39
+ * engine: {
40
+ * onStart(server) {
41
+ * console.log('Server started')
42
+ * }
43
+ * }
44
+ * })
45
+ * class MyServerModule {}
46
+ *
47
+ * provideServerModules([MyServerModule])
48
+ * ```
49
+ */
50
+ export function provideServerModules(modules: RpgServerModule[]): FactoryProvider {
8
51
  return provideModules(modules, "server", (modules, context) => {
9
52
  const mainModuleServer = findModules(context, 'Server')
10
53
  modules = [...mainModuleServer, ...modules]
11
54
  modules = modules.map((module) => {
55
+ // If module is a class (constructor function), instantiate it
56
+ // The RpgModule decorator adds properties to the prototype, which will be accessible via the instance
57
+ if (typeof module === 'function') {
58
+ const instance = new module() as any;
59
+ // Copy all enumerable properties (including from prototype) to a plain object
60
+ const moduleObj: any = {};
61
+ for (const key in instance) {
62
+ moduleObj[key] = instance[key];
63
+ }
64
+ module = moduleObj;
65
+ }
12
66
  if ('server' in module) {
13
67
  module = module.server as any;
14
68
  }
@@ -29,7 +83,32 @@ export function provideServerModules(modules: any[]): FactoryProvider {
29
83
  maps: {
30
84
  load: (engine: RpgMap) => {
31
85
  maps.forEach((map) => {
32
- engine.maps.push(map);
86
+ // If map is a class (constructor function), extract properties from class and prototype
87
+ // Otherwise, use the object directly
88
+ let mapInstance: any;
89
+ if (typeof map === 'function') {
90
+ // Extract properties from the class (static properties set by @MapData decorator)
91
+ // and from the prototype (instance properties like _events)
92
+ // The decorator sets properties on both the class and prototype, so we check both
93
+ const MapClass = map as any;
94
+ mapInstance = {
95
+ id: MapClass.prototype?.id ?? MapClass.id,
96
+ file: MapClass.prototype?.file ?? MapClass.file,
97
+ type: MapClass.type,
98
+ name: MapClass.prototype?.name,
99
+ sounds: MapClass.prototype?.sounds,
100
+ lowMemory: MapClass.prototype?.lowMemory,
101
+ stopAllSoundsBeforeJoin: MapClass.prototype?.stopAllSoundsBeforeJoin,
102
+ events: MapClass.prototype?._events,
103
+ syncSchema: MapClass.prototype?.$schema,
104
+ onLoad: MapClass.prototype?.onLoad,
105
+ onJoin: MapClass.prototype?.onJoin,
106
+ onLeave: MapClass.prototype?.onLeave,
107
+ };
108
+ } else {
109
+ mapInstance = map;
110
+ }
111
+ engine.maps.push(mapInstance);
33
112
  });
34
113
  },
35
114
  }