@rpgjs/client 5.0.0-alpha.2 → 5.0.0-alpha.20

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 (163) hide show
  1. package/dist/Game/AnimationManager.d.ts +8 -0
  2. package/dist/Game/Map.d.ts +7 -1
  3. package/dist/Gui/Gui.d.ts +128 -5
  4. package/dist/RpgClient.d.ts +217 -59
  5. package/dist/RpgClientEngine.d.ts +345 -6
  6. package/dist/Sound.d.ts +199 -0
  7. package/dist/components/animations/index.d.ts +4 -0
  8. package/dist/components/dynamics/parse-value.d.ts +1 -0
  9. package/dist/components/gui/index.d.ts +3 -3
  10. package/dist/components/index.d.ts +3 -1
  11. package/dist/components/prebuilt/index.d.ts +18 -0
  12. package/dist/index.d.ts +4 -1
  13. package/dist/index.js +9 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/index10.js +149 -4
  16. package/dist/index10.js.map +1 -1
  17. package/dist/index11.js +21 -7
  18. package/dist/index11.js.map +1 -1
  19. package/dist/index12.js +6 -4
  20. package/dist/index12.js.map +1 -1
  21. package/dist/index13.js +11 -14
  22. package/dist/index13.js.map +1 -1
  23. package/dist/index14.js +8 -40
  24. package/dist/index14.js.map +1 -1
  25. package/dist/index15.js +187 -180
  26. package/dist/index15.js.map +1 -1
  27. package/dist/index16.js +104 -7
  28. package/dist/index16.js.map +1 -1
  29. package/dist/index17.js +82 -372
  30. package/dist/index17.js.map +1 -1
  31. package/dist/index18.js +361 -26
  32. package/dist/index18.js.map +1 -1
  33. package/dist/index19.js +46 -20
  34. package/dist/index19.js.map +1 -1
  35. package/dist/index2.js +683 -32
  36. package/dist/index2.js.map +1 -1
  37. package/dist/index20.js +5 -2417
  38. package/dist/index20.js.map +1 -1
  39. package/dist/index21.js +383 -97
  40. package/dist/index21.js.map +1 -1
  41. package/dist/index22.js +41 -104
  42. package/dist/index22.js.map +1 -1
  43. package/dist/index23.js +21 -67
  44. package/dist/index23.js.map +1 -1
  45. package/dist/index24.js +2632 -20
  46. package/dist/index24.js.map +1 -1
  47. package/dist/index25.js +107 -34
  48. package/dist/index25.js.map +1 -1
  49. package/dist/index26.js +69 -3
  50. package/dist/index26.js.map +1 -1
  51. package/dist/index27.js +17 -318
  52. package/dist/index27.js.map +1 -1
  53. package/dist/index28.js +24 -22
  54. package/dist/index28.js.map +1 -1
  55. package/dist/index29.js +92 -8
  56. package/dist/index29.js.map +1 -1
  57. package/dist/index3.js +68 -8
  58. package/dist/index3.js.map +1 -1
  59. package/dist/index30.js +37 -7
  60. package/dist/index30.js.map +1 -1
  61. package/dist/index31.js +18 -168
  62. package/dist/index31.js.map +1 -1
  63. package/dist/index32.js +3 -499
  64. package/dist/index32.js.map +1 -1
  65. package/dist/index33.js +332 -9
  66. package/dist/index33.js.map +1 -1
  67. package/dist/index34.js +24 -4400
  68. package/dist/index34.js.map +1 -1
  69. package/dist/index35.js +6 -311
  70. package/dist/index35.js.map +1 -1
  71. package/dist/index36.js +8 -88
  72. package/dist/index36.js.map +1 -1
  73. package/dist/index37.js +182 -56
  74. package/dist/index37.js.map +1 -1
  75. package/dist/index38.js +500 -16
  76. package/dist/index38.js.map +1 -1
  77. package/dist/index39.js +10 -18
  78. package/dist/index39.js.map +1 -1
  79. package/dist/index4.js +23 -5
  80. package/dist/index4.js.map +1 -1
  81. package/dist/index40.js +7 -0
  82. package/dist/index40.js.map +1 -0
  83. package/dist/index41.js +3690 -0
  84. package/dist/index41.js.map +1 -0
  85. package/dist/index42.js +77 -0
  86. package/dist/index42.js.map +1 -0
  87. package/dist/index43.js +6 -0
  88. package/dist/index43.js.map +1 -0
  89. package/dist/index44.js +20 -0
  90. package/dist/index44.js.map +1 -0
  91. package/dist/index45.js +146 -0
  92. package/dist/index45.js.map +1 -0
  93. package/dist/index46.js +12 -0
  94. package/dist/index46.js.map +1 -0
  95. package/dist/index47.js +113 -0
  96. package/dist/index47.js.map +1 -0
  97. package/dist/index48.js +136 -0
  98. package/dist/index48.js.map +1 -0
  99. package/dist/index49.js +137 -0
  100. package/dist/index49.js.map +1 -0
  101. package/dist/index5.js +2 -1
  102. package/dist/index5.js.map +1 -1
  103. package/dist/index50.js +112 -0
  104. package/dist/index50.js.map +1 -0
  105. package/dist/index51.js +141 -0
  106. package/dist/index51.js.map +1 -0
  107. package/dist/index52.js +9 -0
  108. package/dist/index52.js.map +1 -0
  109. package/dist/index53.js +54 -0
  110. package/dist/index53.js.map +1 -0
  111. package/dist/index6.js +1 -1
  112. package/dist/index6.js.map +1 -1
  113. package/dist/index7.js +11 -3
  114. package/dist/index7.js.map +1 -1
  115. package/dist/index8.js +68 -7
  116. package/dist/index8.js.map +1 -1
  117. package/dist/index9.js +230 -15
  118. package/dist/index9.js.map +1 -1
  119. package/dist/presets/animation.d.ts +31 -0
  120. package/dist/presets/faceset.d.ts +30 -0
  121. package/dist/presets/index.d.ts +103 -0
  122. package/dist/presets/lpc.d.ts +89 -0
  123. package/dist/services/loadMap.d.ts +123 -2
  124. package/dist/services/mmorpg.d.ts +9 -4
  125. package/dist/services/standalone.d.ts +51 -2
  126. package/package.json +22 -18
  127. package/src/Game/{EffectManager.ts → AnimationManager.ts} +3 -2
  128. package/src/Game/Map.ts +20 -2
  129. package/src/Game/Object.ts +163 -9
  130. package/src/Gui/Gui.ts +300 -17
  131. package/src/RpgClient.ts +222 -58
  132. package/src/RpgClientEngine.ts +804 -36
  133. package/src/Sound.ts +253 -0
  134. package/src/components/{effects → animations}/animation.ce +3 -6
  135. package/src/components/{effects → animations}/index.ts +1 -1
  136. package/src/components/character.ce +165 -37
  137. package/src/components/dynamics/parse-value.ts +80 -0
  138. package/src/components/dynamics/text.ce +183 -0
  139. package/src/components/gui/box.ce +17 -0
  140. package/src/components/gui/dialogbox/index.ce +73 -35
  141. package/src/components/gui/dialogbox/selection.ce +16 -1
  142. package/src/components/gui/index.ts +3 -4
  143. package/src/components/index.ts +5 -1
  144. package/src/components/prebuilt/hp-bar.ce +255 -0
  145. package/src/components/prebuilt/index.ts +21 -0
  146. package/src/components/scenes/draw-map.ce +6 -23
  147. package/src/components/scenes/event-layer.ce +9 -3
  148. package/src/core/setup.ts +2 -0
  149. package/src/index.ts +5 -2
  150. package/src/module.ts +72 -6
  151. package/src/presets/animation.ts +46 -0
  152. package/src/presets/faceset.ts +60 -0
  153. package/src/presets/index.ts +7 -1
  154. package/src/presets/lpc.ts +108 -0
  155. package/src/services/loadMap.ts +132 -3
  156. package/src/services/mmorpg.ts +27 -5
  157. package/src/services/standalone.ts +68 -6
  158. package/tsconfig.json +1 -1
  159. package/vite.config.ts +1 -1
  160. package/dist/Game/EffectManager.d.ts +0 -5
  161. package/dist/components/effects/index.d.ts +0 -4
  162. package/src/components/scenes/element-map.ce +0 -23
  163. /package/src/components/{effects → animations}/hit.ce +0 -0
@@ -0,0 +1,89 @@
1
+ export declare const LPCSpritesheetPreset: (options: {
2
+ id: string;
3
+ imageSource: string;
4
+ width: number;
5
+ height: number;
6
+ ratio?: number;
7
+ }) => {
8
+ id: string;
9
+ image: string;
10
+ width: number;
11
+ height: number;
12
+ opacity: number;
13
+ rectWidth: number;
14
+ rectHeight: number;
15
+ framesWidth: number;
16
+ framesHeight: number;
17
+ spriteRealSize: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ textures: {
22
+ attack3?: {
23
+ offset: {
24
+ x: number;
25
+ y: number;
26
+ };
27
+ rectWidth: number;
28
+ rectHeight: number;
29
+ framesWidth: number;
30
+ framesHeight: number;
31
+ animations: ({ direction }: {
32
+ direction: any;
33
+ }) => any[];
34
+ } | undefined;
35
+ stand: {
36
+ offset: {
37
+ x: number;
38
+ y: number;
39
+ };
40
+ animations: ({ direction }: {
41
+ direction: any;
42
+ }) => {
43
+ time: number;
44
+ frameX: number;
45
+ frameY: number;
46
+ }[][];
47
+ };
48
+ walk: {
49
+ offset: {
50
+ x: number;
51
+ y: number;
52
+ };
53
+ framesWidth: number;
54
+ framesHeight: number;
55
+ animations: ({ direction }: {
56
+ direction: any;
57
+ }) => any[];
58
+ };
59
+ attack: {
60
+ offset: {
61
+ x: number;
62
+ y: number;
63
+ };
64
+ framesWidth: number;
65
+ framesHeight: number;
66
+ animations: ({ direction }: {
67
+ direction: any;
68
+ }) => any[];
69
+ };
70
+ skill: {
71
+ framesWidth: number;
72
+ framesHeight: number;
73
+ animations: ({ direction }: {
74
+ direction: any;
75
+ }) => any[];
76
+ };
77
+ attack2: {
78
+ offset: {
79
+ x: number;
80
+ y: number;
81
+ };
82
+ framesWidth: number;
83
+ framesHeight: number;
84
+ animations: ({ direction }: {
85
+ direction: any;
86
+ }) => any[];
87
+ };
88
+ };
89
+ };
@@ -1,14 +1,135 @@
1
1
  import { Context } from '@signe/di';
2
2
  export declare const LoadMapToken = "LoadMapToken";
3
- export type LoadMapOptions = (mapId: string) => Promise<void>;
3
+ /**
4
+ * Represents the structure of map data that should be returned by the load map callback.
5
+ * This interface defines all the properties that can be provided when loading a custom map.
6
+ *
7
+ * @interface MapData
8
+ */
9
+ type MapData = {
10
+ /** Raw map data that will be passed to the map component */
11
+ data: any;
12
+ /** CanvasEngine component that will render the map */
13
+ component: any;
14
+ /** Optional map width in pixels, used for viewport calculations */
15
+ width?: number;
16
+ /** Optional map height in pixels, used for viewport calculations */
17
+ height?: number;
18
+ /** Optional map events data (NPCs, interactive objects, etc.) */
19
+ events?: any;
20
+ /** Optional map identifier, defaults to the mapId parameter if not provided */
21
+ id?: string;
22
+ };
23
+ /**
24
+ * Callback function type for loading map data.
25
+ * This function receives a map ID and should return either a MapData object directly
26
+ * or a Promise that resolves to a MapData object.
27
+ *
28
+ * @callback LoadMapOptions
29
+ * @param {string} mapId - The identifier of the map to load
30
+ * @returns {Promise<MapData> | MapData} The map data object or a promise resolving to it
31
+ */
32
+ export type LoadMapOptions = (mapId: string) => Promise<MapData> | MapData;
4
33
  export declare class LoadMapService {
5
34
  private context;
6
35
  private options;
7
36
  private updateMapService;
8
37
  constructor(context: Context, options: LoadMapOptions);
9
- load(mapId: string): Promise<void>;
38
+ load(mapId: string): Promise<MapData>;
10
39
  }
40
+ /**
41
+ * Creates a dependency injection configuration for custom map loading on the client side.
42
+ *
43
+ * This function allows you to customize how maps are loaded and displayed by providing
44
+ * a callback that defines custom map data and rendering components. It's designed to work
45
+ * with the RPG-JS dependency injection system and enables integration of custom map formats
46
+ * like Tiled TMX files or any other map data structure.
47
+ *
48
+ * The function sets up the necessary service providers for map loading, including:
49
+ * - UpdateMapToken: Handles map updates in the client context
50
+ * - LoadMapToken: Provides the LoadMapService with your custom loading logic
51
+ *
52
+ * **Design Concept:**
53
+ * The function follows the provider pattern, creating a modular way to inject custom
54
+ * map loading behavior into the RPG-JS client engine. It separates the concern of
55
+ * map data fetching from map rendering, allowing developers to focus on their specific
56
+ * map format while leveraging the engine's rendering capabilities.
57
+ *
58
+ * @param {LoadMapOptions} options - Callback function that handles map loading logic
59
+ * @returns {Array<Object>} Array of dependency injection provider configurations
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { provideLoadMap } from '@rpgjs/client'
64
+ * import { createModule } from '@rpgjs/common'
65
+ * import MyTiledMapComponent from './MyTiledMapComponent.ce'
66
+ *
67
+ * // Basic usage with JSON map data
68
+ * export function provideCustomMap() {
69
+ * return createModule("CustomMap", [
70
+ * provideLoadMap(async (mapId) => {
71
+ * const response = await fetch(`/maps/${mapId}.json`)
72
+ * const mapData = await response.json()
73
+ *
74
+ * return {
75
+ * data: mapData,
76
+ * component: MyTiledMapComponent,
77
+ * width: mapData.width,
78
+ * height: mapData.height,
79
+ * events: mapData.events
80
+ * }
81
+ * })
82
+ * ])
83
+ * }
84
+ *
85
+ * // Advanced usage with Tiled TMX files
86
+ * export function provideTiledMap() {
87
+ * return createModule("TiledMap", [
88
+ * provideLoadMap(async (mapId) => {
89
+ * // Load TMX file
90
+ * const tmxResponse = await fetch(`/tiled/${mapId}.tmx`)
91
+ * const tmxData = await tmxResponse.text()
92
+ *
93
+ * // Parse TMX data (using a TMX parser)
94
+ * const parsedMap = parseTMX(tmxData)
95
+ *
96
+ * return {
97
+ * data: parsedMap,
98
+ * component: TiledMapRenderer,
99
+ * width: parsedMap.width * parsedMap.tilewidth,
100
+ * height: parsedMap.height * parsedMap.tileheight,
101
+ * events: parsedMap.objectGroups?.find(g => g.name === 'events')?.objects
102
+ * }
103
+ * })
104
+ * ])
105
+ * }
106
+ *
107
+ * // Synchronous usage for static maps
108
+ * export function provideStaticMap() {
109
+ * return createModule("StaticMap", [
110
+ * provideLoadMap((mapId) => {
111
+ * const staticMaps = {
112
+ * 'town': { tiles: [...], npcs: [...] },
113
+ * 'dungeon': { tiles: [...], monsters: [...] }
114
+ * }
115
+ *
116
+ * return {
117
+ * data: staticMaps[mapId],
118
+ * component: StaticMapComponent,
119
+ * width: 800,
120
+ * height: 600
121
+ * }
122
+ * })
123
+ * ])
124
+ * }
125
+ * ```
126
+ *
127
+ * @since 4.0.0
128
+ * @see {@link LoadMapOptions} for callback function signature
129
+ * @see {@link MapData} for return data structure
130
+ */
11
131
  export declare function provideLoadMap(options: LoadMapOptions): {
12
132
  provide: string;
13
133
  useFactory: (context: Context) => void;
14
134
  }[];
135
+ export {};
@@ -4,17 +4,22 @@ import { RpgClientEngine } from '../RpgClientEngine';
4
4
  import { AbstractWebsocket } from './AbstractSocket';
5
5
  import { UpdateMapService } from '@rpgjs/common';
6
6
  interface MmorpgOptions {
7
- host: string;
7
+ host?: string;
8
8
  }
9
9
  declare class BridgeWebsocket extends AbstractWebsocket {
10
10
  protected context: Context;
11
11
  private options;
12
12
  private socket;
13
- constructor(context: Context, options: MmorpgOptions);
14
- connection(): Promise<void>;
13
+ private privateId;
14
+ constructor(context: Context, options?: MmorpgOptions);
15
+ connection(listeners?: (data: any) => void): Promise<void>;
15
16
  on(key: string, callback: (data: any) => void): void;
16
17
  off(event: string, callback: (data: any) => void): void;
17
18
  emit(event: string, data: any): void;
19
+ updateProperties({ room }: {
20
+ room: any;
21
+ }): void;
22
+ reconnect(listeners?: (data: any) => void): Promise<void>;
18
23
  }
19
24
  declare class UpdateMapStandaloneService extends UpdateMapService {
20
25
  protected context: Context;
@@ -22,7 +27,7 @@ declare class UpdateMapStandaloneService extends UpdateMapService {
22
27
  constructor(context: Context, options: MmorpgOptions);
23
28
  update(map: any): Promise<void>;
24
29
  }
25
- export declare function provideMmorpg(options: MmorpgOptions): (typeof RpgGui | typeof RpgClientEngine | {
30
+ export declare function provideMmorpg(options: MmorpgOptions): (typeof RpgClientEngine | typeof RpgGui | {
26
31
  provide: string;
27
32
  useFactory: (context: Context) => BridgeWebsocket;
28
33
  } | {
@@ -8,21 +8,70 @@ declare class BridgeWebsocket extends AbstractWebsocket {
8
8
  private server;
9
9
  private room;
10
10
  private socket;
11
+ private rooms;
11
12
  constructor(context: Context, server: any);
12
13
  connection(listeners?: (data: any) => void): Promise<any>;
14
+ private _connection;
13
15
  on(key: string, callback: (data: any) => void): void;
14
16
  off(event: string, callback: (data: any) => void): void;
15
17
  emit(event: string, data: any): void;
18
+ /**
19
+ * Update underlying connection properties before a reconnect
20
+ *
21
+ * Design
22
+ * - Dynamically register a factory for the requested room to ensure a fresh server instance
23
+ * - Swap the internal ServerIo to target the new room
24
+ *
25
+ * @param params - Properties to update
26
+ * @param params.room - The target room id (e.g. `map-simplemap2`)
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * websocket.updateProperties({ room: 'map-simplemap2' })
31
+ * await websocket.reconnect()
32
+ * ```
33
+ */
16
34
  updateProperties({ room }: {
17
35
  room: any;
18
36
  }): void;
37
+ /**
38
+ * Reconnect the client to the current Party room
39
+ *
40
+ * Design
41
+ * - Must be called after `updateProperties()` when switching rooms
42
+ * - Rebuilds the client <-> server bridge and re-triggers connection listeners
43
+ *
44
+ * @param listeners - Optional callback to re-bind event handlers on the new socket
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * websocket.updateProperties({ room: 'map-dungeon' })
49
+ * await websocket.reconnect((socket) => {
50
+ * // re-bind events here
51
+ * })
52
+ * ```
53
+ */
19
54
  reconnect(listeners?: (data: any) => void): Promise<void>;
20
55
  }
21
56
  declare class UpdateMapStandaloneService extends UpdateMapService {
22
57
  private server;
23
- update(mapId: string, map: any): Promise<void>;
58
+ /**
59
+ * Update the current room map data on the server side
60
+ *
61
+ * Design
62
+ * - Uses the in-memory server instance stored in context (standalone mode)
63
+ * - Builds a local HTTP-like request to the current Party room endpoint
64
+ *
65
+ * @param map - The map payload to apply on the server
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * await updateMapService.update({ width: 1024, height: 768, events: [] })
70
+ * ```
71
+ */
72
+ update(map: any): Promise<void>;
24
73
  }
25
- export declare function provideRpg(server: any): (typeof RpgGui | typeof RpgClientEngine | {
74
+ export declare function provideRpg(server: any): (typeof RpgClientEngine | typeof RpgGui | {
26
75
  provide: string;
27
76
  useFactory: (context: Context) => BridgeWebsocket;
28
77
  useClass?: undefined;
package/package.json CHANGED
@@ -1,16 +1,12 @@
1
1
  {
2
2
  "name": "@rpgjs/client",
3
- "version": "5.0.0-alpha.2",
3
+ "version": "5.0.0-alpha.20",
4
4
  "description": "RPGJS is a framework for creating RPG/MMORPG games",
5
5
  "main": "dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
- "scripts": {
11
- "dev": "vite build --watch",
12
- "build": "vite build"
13
- },
14
10
  "keywords": [
15
11
  "rpg",
16
12
  "game",
@@ -20,21 +16,29 @@
20
16
  ],
21
17
  "author": "Samuel Ronce",
22
18
  "license": "MIT",
19
+ "peerDependencies": {
20
+ "@canvasengine/presets": "*",
21
+ "canvasengine": "*",
22
+ "pixi.js": "^8.9.2"
23
+ },
23
24
  "dependencies": {
24
- "@canvasengine/presets": "2.0.0-beta.22",
25
- "@rpgjs/common": "workspace:*",
26
- "@signe/di": "^2.3.3",
27
- "@signe/room": "^2.3.3",
28
- "@signe/sync": "^2.3.3",
29
- "canvasengine": "2.0.0-beta.22",
30
- "pixi.js": "^8.10.1",
25
+ "@rpgjs/client": "5.0.0-alpha.20",
26
+ "@rpgjs/common": "5.0.0-alpha.20",
27
+ "@rpgjs/server": "5.0.0-alpha.20",
28
+ "@signe/di": "^2.5.1",
29
+ "@signe/room": "^2.5.1",
30
+ "@signe/sync": "^2.5.1",
31
31
  "rxjs": "^7.8.2"
32
32
  },
33
33
  "devDependencies": {
34
- "@canvasengine/compiler": "2.0.0-beta.21",
35
- "vite": "^6.2.5",
36
- "vite-plugin-dts": "^4.5.3",
37
- "vitest": "^3.1.1"
34
+ "@canvasengine/compiler": "2.0.0-beta.34",
35
+ "vite": "^7.2.4",
36
+ "vite-plugin-dts": "^4.5.4",
37
+ "vitest": "^4.0.14"
38
38
  },
39
- "type": "module"
40
- }
39
+ "type": "module",
40
+ "scripts": {
41
+ "dev": "vite build --watch",
42
+ "build": "vite build"
43
+ }
44
+ }
@@ -1,16 +1,17 @@
1
1
  import { generateUID, RpgCommonPlayer } from "@rpgjs/common";
2
2
  import { signal } from "canvasengine";
3
3
 
4
- export class EffectManager {
4
+ export class AnimationManager {
5
5
  current = signal<any[]>([]);
6
6
 
7
- displayEffect(params: any, player: RpgCommonPlayer) {
7
+ displayEffect(params: any, player: RpgCommonPlayer | { x: number, y: number }) {
8
8
  const id = generateUID();
9
9
  this.current().push({
10
10
  ...params,
11
11
  id,
12
12
  x: player.x,
13
13
  y: player.y,
14
+ object: player,
14
15
  onFinish: () => {
15
16
  const index = this.current().findIndex((value) => value.id === id);
16
17
  this.current().splice(index, 1);
package/src/Game/Map.ts CHANGED
@@ -1,10 +1,28 @@
1
1
  import { RpgCommonMap } from "@rpgjs/common";
2
2
  import { sync, users } from "@signe/sync";
3
3
  import { RpgClientPlayer } from "./Player";
4
- import { Signal, signal } from "canvasengine";
4
+ import { Signal, signal, computed } from "canvasengine";
5
5
  import { RpgClientEvent } from "./Event";
6
+ import { RpgClientEngine } from "../RpgClientEngine";
7
+ import { inject } from "../core/inject";
6
8
 
7
- export class RpgClientMap extends RpgCommonMap<RpgClientPlayer> {
9
+ export class RpgClientMap extends RpgCommonMap<any> {
10
+ engine: RpgClientEngine = inject(RpgClientEngine)
8
11
  @users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});
9
12
  @sync(RpgClientEvent) events = signal<Record<string, RpgClientEvent>>({});
13
+ currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()!])
14
+
15
+ getCurrentPlayer() {
16
+ return this.currentPlayer()
17
+ }
18
+
19
+ reset() {
20
+ this.players.set({})
21
+ this.events.set({})
22
+ this.clearPhysic()
23
+ }
24
+
25
+ stepPredictionTick(): void {
26
+ this.forceSingleTick();
27
+ }
10
28
  }
@@ -1,16 +1,170 @@
1
- import { RpgCommonPlayer } from "@rpgjs/common";
1
+ import { Hooks, ModulesToken, RpgCommonPlayer } from "@rpgjs/common";
2
2
  import { trigger, signal } from "canvasengine";
3
+ import { filter, from, map, Subscription, switchMap } from "rxjs";
4
+ import { inject } from "../core/inject";
5
+ import { RpgClientEngine } from "../RpgClientEngine";
6
+ import TextComponent from "../components/dynamics/text.ce";
7
+
8
+ const DYNAMIC_COMPONENTS = {
9
+ text: TextComponent,
10
+ }
3
11
 
4
12
  export abstract class RpgClientObject extends RpgCommonPlayer {
5
13
  abstract type: string;
6
- emitParticleTrigger = trigger()
7
- particleName = signal('')
14
+ emitParticleTrigger = trigger();
15
+ particleName = signal("");
16
+ animationCurrentIndex = signal(0);
17
+ animationIsPlaying = signal(false);
18
+ _param = signal({});
19
+ frames: { x: number; y: number; ts: number }[] = [];
20
+ graphicsSignals = signal<any[]>([]);
21
+ _component = {} // temporary component memory
22
+
23
+ constructor() {
24
+ super();
25
+ this.hooks.callHooks("client-sprite-onInit", this).subscribe();
26
+
27
+ this._frames.observable.subscribe(({ items }) => {
28
+ if (!this.id) return;
29
+ //if (this.id == this.engine.playerIdSignal()!) return;
30
+ this.frames = [...this.frames, ...items];
31
+ });
32
+
33
+ this.graphics.observable
34
+ .pipe(
35
+ map(({ items }) => items),
36
+ filter(graphics => graphics.length > 0),
37
+ switchMap(graphics => from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic)))))
38
+ )
39
+ .subscribe((sheets) => {
40
+ this.graphicsSignals.set(sheets);
41
+ });
42
+
43
+ this.componentsTop.observable
44
+ .pipe(
45
+ filter(value => value !== null && value !== undefined),
46
+ map((value) => typeof value === 'string' ? JSON.parse(value) : value),
47
+ )
48
+ .subscribe(({components}) => {
49
+ for (const component of components) {
50
+ for (const [key, value] of Object.entries(component)) {
51
+ this._component = value as any; // temporary component memory
52
+ console.log(value)
53
+ const type = (value as any).type as keyof typeof DYNAMIC_COMPONENTS;
54
+ if (DYNAMIC_COMPONENTS[type]) {
55
+ this.engine.addSpriteComponentInFront(DYNAMIC_COMPONENTS[type]);
56
+ }
57
+ }
58
+ }
59
+ });
60
+
61
+ this.engine.tick
62
+ .pipe
63
+ //throttleTime(10)
64
+ ()
65
+ .subscribe(() => {
66
+ const frame = this.frames.shift();
67
+ if (frame) {
68
+ if (!frame.x || !frame.y) return;
69
+ this.engine.scene.setBodyPosition(
70
+ this.id,
71
+ frame.x,
72
+ frame.y,
73
+ "top-left"
74
+ );
75
+ }
76
+ });
77
+ }
78
+
79
+ get hooks() {
80
+ return inject<Hooks>(ModulesToken);
81
+ }
82
+
83
+ get engine() {
84
+ return inject(RpgClientEngine);
85
+ }
86
+
87
+ private animationSubscription?: Subscription;
8
88
 
9
89
  flash(color: string, duration: number = 100) {
10
- const lastTint = this.tint()
11
- this.tint.set(color);
12
- setTimeout(() => {
13
- this.tint.set(lastTint)
14
- }, duration)
90
+ return new Promise((resolve) => {
91
+ const lastTint = this.tint();
92
+ this.tint.set(color);
93
+ setTimeout(() => {
94
+ this.tint.set(lastTint);
95
+ resolve(true);
96
+ }, duration);
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Reset animation state when animation changes externally
102
+ *
103
+ * This method should be called when the animation changes due to movement
104
+ * or other external factors to ensure the animation system doesn't get stuck
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * // Reset when player starts moving
109
+ * player.resetAnimationState();
110
+ * ```
111
+ */
112
+ resetAnimationState() {
113
+ this.animationIsPlaying.set(false);
114
+ this.animationCurrentIndex.set(0);
115
+ if (this.animationSubscription) {
116
+ this.animationSubscription.unsubscribe();
117
+ this.animationSubscription = undefined;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Set a custom animation for a specific number of times
123
+ *
124
+ * Plays a custom animation for the specified number of repetitions.
125
+ * The animation system prevents overlapping animations and automatically
126
+ * returns to the previous animation when complete.
127
+ *
128
+ * @param animationName - Name of the animation to play
129
+ * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * // Play attack animation 3 times
134
+ * player.setAnimation('attack', 3);
135
+ *
136
+ * // Play continuous spell animation
137
+ * player.setAnimation('spell');
138
+ * ```
139
+ */
140
+ setAnimation(animationName: string, nbTimes: number = Infinity) {
141
+ if (this.animationIsPlaying()) return;
142
+ this.animationIsPlaying.set(true);
143
+ const previousAnimationName = this.animationName();
144
+ this.animationCurrentIndex.set(0);
145
+
146
+ // Clean up any existing subscription
147
+ if (this.animationSubscription) {
148
+ this.animationSubscription.unsubscribe();
149
+ }
150
+
151
+ this.animationSubscription =
152
+ this.animationCurrentIndex.observable.subscribe((index) => {
153
+ if (index >= nbTimes) {
154
+ this.animationCurrentIndex.set(0);
155
+ this.animationName.set(previousAnimationName);
156
+ this.animationIsPlaying.set(false);
157
+ if (this.animationSubscription) {
158
+ this.animationSubscription.unsubscribe();
159
+ this.animationSubscription = undefined;
160
+ }
161
+ }
162
+ });
163
+ this.animationName.set(animationName);
164
+ }
165
+
166
+ showComponentAnimation(id: string, params: any) {
167
+ const engine = inject(RpgClientEngine);
168
+ engine.getComponentAnimation(id).displayEffect(params, this);
15
169
  }
16
- }
170
+ }