@rpgjs/client 5.0.0-beta.6 → 5.0.0-beta.8

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 (147) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/Game/AnimationManager.d.ts +2 -2
  3. package/dist/Game/AnimationManager.js +18 -9
  4. package/dist/Game/AnimationManager.js.map +1 -1
  5. package/dist/Game/AnimationManager.spec.d.ts +1 -0
  6. package/dist/Game/Map.d.ts +7 -9
  7. package/dist/Game/Map.js +5 -4
  8. package/dist/Game/Map.js.map +1 -1
  9. package/dist/Game/Object.d.ts +44 -20
  10. package/dist/Game/Object.js +28 -14
  11. package/dist/Game/Object.js.map +1 -1
  12. package/dist/Gui/Gui.d.ts +19 -6
  13. package/dist/Gui/Gui.js +64 -34
  14. package/dist/Gui/Gui.js.map +1 -1
  15. package/dist/Gui/Gui.spec.d.ts +1 -0
  16. package/dist/Gui/NotificationManager.d.ts +1 -1
  17. package/dist/Gui/NotificationManager.js.map +1 -1
  18. package/dist/Resource.js +1 -1
  19. package/dist/Resource.js.map +1 -1
  20. package/dist/RpgClient.d.ts +57 -2
  21. package/dist/RpgClientEngine.d.ts +55 -16
  22. package/dist/RpgClientEngine.js +60 -5
  23. package/dist/RpgClientEngine.js.map +1 -1
  24. package/dist/Sound.js.map +1 -1
  25. package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.128.0}/helpers/decorate.js +1 -1
  26. package/dist/_virtual/{_@oxc-project_runtime@0.127.0 → _@oxc-project_runtime@0.128.0}/helpers/decorateMetadata.js +1 -1
  27. package/dist/components/animations/animation.ce.js.map +1 -1
  28. package/dist/components/animations/hit.ce.js.map +1 -1
  29. package/dist/components/character.ce.js +280 -3
  30. package/dist/components/character.ce.js.map +1 -1
  31. package/dist/components/dynamics/bar.ce.js +96 -0
  32. package/dist/components/dynamics/bar.ce.js.map +1 -0
  33. package/dist/components/dynamics/image.ce.js +23 -0
  34. package/dist/components/dynamics/image.ce.js.map +1 -0
  35. package/dist/components/dynamics/parse-value.d.ts +4 -1
  36. package/dist/components/dynamics/parse-value.js +51 -35
  37. package/dist/components/dynamics/parse-value.js.map +1 -1
  38. package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
  39. package/dist/components/dynamics/shape-utils.d.ts +16 -0
  40. package/dist/components/dynamics/shape-utils.js +73 -0
  41. package/dist/components/dynamics/shape-utils.js.map +1 -0
  42. package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
  43. package/dist/components/dynamics/shape.ce.js +83 -0
  44. package/dist/components/dynamics/shape.ce.js.map +1 -0
  45. package/dist/components/dynamics/text.ce.js +28 -41
  46. package/dist/components/dynamics/text.ce.js.map +1 -1
  47. package/dist/components/gui/box.ce.js.map +1 -1
  48. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  49. package/dist/components/gui/gameover.ce.js.map +1 -1
  50. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  51. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  52. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  53. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  54. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  55. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  56. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  57. package/dist/components/gui/mobile/index.d.ts +1 -1
  58. package/dist/components/gui/mobile/index.js.map +1 -1
  59. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  60. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  61. package/dist/components/gui/save-load.ce.js.map +1 -1
  62. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  63. package/dist/components/gui/title-screen.ce.js.map +1 -1
  64. package/dist/components/player-components-utils.d.ts +67 -0
  65. package/dist/components/player-components-utils.js +162 -0
  66. package/dist/components/player-components-utils.js.map +1 -0
  67. package/dist/components/player-components-utils.spec.d.ts +1 -0
  68. package/dist/components/player-components.ce.js +188 -0
  69. package/dist/components/player-components.ce.js.map +1 -0
  70. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  71. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  72. package/dist/components/scenes/canvas.ce.js.map +1 -1
  73. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  74. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  75. package/dist/core/inject.js +1 -1
  76. package/dist/core/inject.js.map +1 -1
  77. package/dist/core/setup.js +1 -1
  78. package/dist/core/setup.js.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/module.js +4 -1
  81. package/dist/module.js.map +1 -1
  82. package/dist/node_modules/.pnpm/{@signe_di@2.9.0 → @signe_di@2.10.0}/node_modules/@signe/di/dist/index.js +7 -117
  83. package/dist/node_modules/.pnpm/@signe_di@2.10.0/node_modules/@signe/di/dist/index.js.map +1 -0
  84. package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js +239 -0
  85. package/dist/node_modules/.pnpm/@signe_reactive@2.10.0/node_modules/@signe/reactive/dist/index.js.map +1 -0
  86. package/dist/node_modules/.pnpm/@signe_room@2.10.0/node_modules/@signe/room/dist/index.js +611 -0
  87. package/dist/node_modules/.pnpm/@signe_room@2.10.0/node_modules/@signe/room/dist/index.js.map +1 -0
  88. package/dist/node_modules/.pnpm/@signe_sync@2.10.0/node_modules/@signe/sync/dist/client/index.js +44 -0
  89. package/dist/node_modules/.pnpm/@signe_sync@2.10.0/node_modules/@signe/sync/dist/client/index.js.map +1 -0
  90. package/dist/node_modules/.pnpm/{@signe_sync@2.9.0 → @signe_sync@2.10.0}/node_modules/@signe/sync/dist/index.js +29 -136
  91. package/dist/node_modules/.pnpm/@signe_sync@2.10.0/node_modules/@signe/sync/dist/index.js.map +1 -0
  92. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -1
  93. package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
  94. package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
  95. package/dist/presets/animation.js.map +1 -1
  96. package/dist/presets/faceset.js.map +1 -1
  97. package/dist/presets/icon.js.map +1 -1
  98. package/dist/presets/lpc.js.map +1 -1
  99. package/dist/presets/rmspritesheet.js.map +1 -1
  100. package/dist/services/AbstractSocket.js.map +1 -1
  101. package/dist/services/keyboardControls.js.map +1 -1
  102. package/dist/services/loadMap.d.ts +6 -0
  103. package/dist/services/loadMap.js +1 -1
  104. package/dist/services/loadMap.js.map +1 -1
  105. package/dist/services/mmorpg.js +1 -1
  106. package/dist/services/mmorpg.js.map +1 -1
  107. package/dist/services/save.js.map +1 -1
  108. package/dist/services/standalone.js +1 -1
  109. package/dist/services/standalone.js.map +1 -1
  110. package/dist/utils/getEntityProp.js.map +1 -1
  111. package/package.json +8 -8
  112. package/src/Game/AnimationManager.spec.ts +30 -0
  113. package/src/Game/AnimationManager.ts +22 -10
  114. package/src/Game/Map.ts +12 -2
  115. package/src/Game/Object.ts +68 -43
  116. package/src/Gui/Gui.spec.ts +273 -0
  117. package/src/Gui/Gui.ts +105 -50
  118. package/src/Resource.ts +1 -2
  119. package/src/RpgClient.ts +63 -2
  120. package/src/RpgClientEngine.ts +82 -12
  121. package/src/components/character.ce +353 -1
  122. package/src/components/dynamics/bar.ce +87 -0
  123. package/src/components/dynamics/image.ce +20 -0
  124. package/src/components/dynamics/parse-value.spec.ts +41 -0
  125. package/src/components/dynamics/parse-value.ts +102 -37
  126. package/src/components/dynamics/shape-utils.spec.ts +46 -0
  127. package/src/components/dynamics/shape-utils.ts +61 -0
  128. package/src/components/dynamics/shape.ce +89 -0
  129. package/src/components/dynamics/text.ce +34 -149
  130. package/src/components/player-components-utils.spec.ts +109 -0
  131. package/src/components/player-components-utils.ts +205 -0
  132. package/src/components/player-components.ce +221 -0
  133. package/src/core/setup.ts +2 -2
  134. package/src/module.ts +5 -1
  135. package/src/services/loadMap.ts +2 -0
  136. package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +0 -1
  137. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +0 -463
  138. package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +0 -1
  139. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +0 -2191
  140. package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +0 -1
  141. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js +0 -10
  142. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/chunk-7QVYU63E.js.map +0 -1
  143. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js +0 -91
  144. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +0 -1
  145. package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +0 -1
  146. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js +0 -14
  147. package/dist/node_modules/.pnpm/dset@3.1.4/node_modules/dset/dist/index.js.map +0 -1
package/dist/Gui/Gui.js CHANGED
@@ -1,4 +1,4 @@
1
- import { inject } from "../node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js";
1
+ import { inject } from "../node_modules/.pnpm/@signe_di@2.10.0/node_modules/@signe/di/dist/index.js";
2
2
  import { WebSocketToken } from "../services/AbstractSocket.js";
3
3
  import __ce_component from "../components/gui/dialogbox/index.ce.js";
4
4
  import __ce_component$1 from "../components/gui/shop/shop.ce.js";
@@ -146,6 +146,7 @@ var RpgGui = class {
146
146
  */
147
147
  _setVueGuiInstance(vueGuiInstance) {
148
148
  this.vueGuiInstance = vueGuiInstance;
149
+ this._initializeVueComponents();
149
150
  }
150
151
  /**
151
152
  * Notify VueGui about GUI state changes
@@ -156,35 +157,16 @@ var RpgGui = class {
156
157
  * @param data - Component data
157
158
  */
158
159
  _notifyVueGui(guiId, display, data = {}) {
159
- if (this.vueGuiInstance && this.vueGuiInstance.vm) {
160
- const extraGui = this.extraGuis.find((gui) => gui.name === guiId);
161
- if (extraGui) {
162
- this.vueGuiInstance.vm.gui[guiId] = {
163
- name: guiId,
164
- display,
165
- data,
166
- attachToSprite: extraGui.attachToSprite || false
167
- };
168
- this.vueGuiInstance.vm.gui = Object.assign({}, this.vueGuiInstance.vm.gui);
169
- }
170
- }
160
+ const extraGui = this.extraGuis.find((gui) => gui.name === guiId);
161
+ if (!extraGui) return;
162
+ this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));
171
163
  }
172
164
  /**
173
165
  * Initialize Vue components in the VueGui instance
174
166
  * This should be called after VueGui is mounted
175
167
  */
176
168
  _initializeVueComponents() {
177
- if (this.vueGuiInstance && this.vueGuiInstance.vm) {
178
- this.extraGuis.forEach((gui) => {
179
- this.vueGuiInstance.vm.gui[gui.name] = {
180
- name: gui.name,
181
- display: gui.display(),
182
- data: gui.data(),
183
- attachToSprite: gui.attachToSprite || false
184
- };
185
- });
186
- this.vueGuiInstance.vm.gui = Object.assign({}, this.vueGuiInstance.vm.gui);
187
- }
169
+ this.vueGuiInstance?.initializeGuiStates?.(this.extraGuis.map((gui) => this.toGuiState(gui)));
188
170
  }
189
171
  guiInteraction(guiId, name, data) {
190
172
  const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;
@@ -244,24 +226,34 @@ var RpgGui = class {
244
226
  * ```
245
227
  */
246
228
  add(gui) {
247
- const guiId = gui.name || gui.id;
229
+ const component = this.resolveComponent(gui);
230
+ const guiId = this.resolveGuiId(gui, component);
248
231
  if (!guiId) throw new Error("GUI must have a name or id");
232
+ const attachToSprite = this.resolveAttachToSprite(gui, component);
249
233
  const guiInstance = {
250
234
  name: guiId,
251
- component: gui.component,
235
+ component,
252
236
  display: signal(gui.display || false),
253
237
  data: signal(gui.data || {}),
254
238
  autoDisplay: gui.autoDisplay || false,
255
239
  dependencies: gui.dependencies ? gui.dependencies() : [],
256
- attachToSprite: gui.attachToSprite || false
240
+ attachToSprite
257
241
  };
258
- if (typeof gui.component !== "function") {
259
- guiInstance.component = gui;
260
- this.extraGuis.push(guiInstance);
261
- if (guiInstance.autoDisplay) this._notifyVueGui(guiId, true, gui.data || {});
242
+ if (this.isVueComponentInstance(guiInstance)) {
243
+ this.removeCanvasGui(guiId);
244
+ const existingIndex = this.extraGuis.findIndex((existing) => existing.name === guiId);
245
+ if (existingIndex >= 0) {
246
+ this.extraGuis[existingIndex].subscription?.unsubscribe();
247
+ this.extraGuis[existingIndex] = guiInstance;
248
+ } else this.extraGuis.push(guiInstance);
249
+ this._initializeVueComponents();
250
+ if (guiInstance.autoDisplay) this.display(guiId, gui.data);
251
+ else this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());
262
252
  return;
263
253
  }
254
+ this.removeVueGui(guiId);
264
255
  this.gui()[guiId] = guiInstance;
256
+ this._initializeVueComponents();
265
257
  if (guiInstance.autoDisplay && typeof gui.component === "function") this.display(guiId, gui.data);
266
258
  }
267
259
  registerOptimisticReducer(guiId, reducer) {
@@ -283,8 +275,13 @@ var RpgGui = class {
283
275
  * ```
284
276
  */
285
277
  getAttachedGuis() {
286
- const allGuis = this.getAll();
287
- return Object.values(allGuis).filter((gui) => gui.attachToSprite === true);
278
+ return Object.values(this.gui()).filter((gui) => gui.attachToSprite === true);
279
+ }
280
+ getVueGuis() {
281
+ return [...this.extraGuis];
282
+ }
283
+ getAttachedVueGuis() {
284
+ return this.extraGuis.filter((gui) => gui.attachToSprite === true);
288
285
  }
289
286
  /**
290
287
  * Check if a player should display attached GUIs
@@ -358,7 +355,7 @@ var RpgGui = class {
358
355
  guiInstance.subscription.unsubscribe();
359
356
  guiInstance.subscription = void 0;
360
357
  }
361
- const deps = dependencies.length > 0 ? dependencies : guiInstance.dependencies ? guiInstance.dependencies() : [];
358
+ const deps = dependencies.length > 0 ? dependencies : guiInstance.dependencies ?? [];
362
359
  if (deps.length > 0) {
363
360
  guiInstance.subscription = combineLatest(deps.map((dependency) => dependency.observable)).subscribe((values) => {
364
361
  if (values.every((value) => value !== void 0)) {
@@ -399,6 +396,39 @@ var RpgGui = class {
399
396
  isVueComponent(id) {
400
397
  return this.extraGuis.some((gui) => gui.name === id);
401
398
  }
399
+ isVueComponentInstance(gui) {
400
+ return typeof gui.component !== "function";
401
+ }
402
+ removeCanvasGui(guiId) {
403
+ const current = this.gui();
404
+ if (!(guiId in current)) return;
405
+ const next = { ...current };
406
+ delete next[guiId];
407
+ this.gui.set(next);
408
+ }
409
+ removeVueGui(guiId) {
410
+ const removed = this.extraGuis.filter((existing) => existing.name === guiId);
411
+ removed.forEach((gui) => gui.subscription?.unsubscribe());
412
+ if (removed.length > 0) this.extraGuis = this.extraGuis.filter((existing) => existing.name !== guiId);
413
+ }
414
+ resolveComponent(gui) {
415
+ return gui?.component ?? gui;
416
+ }
417
+ resolveGuiId(gui, component) {
418
+ return gui?.name || gui?.id || component?.name || component?.__name;
419
+ }
420
+ resolveAttachToSprite(gui, component) {
421
+ return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);
422
+ }
423
+ toGuiState(gui, display = gui.display(), data = gui.data()) {
424
+ return {
425
+ name: gui.name,
426
+ component: gui.component,
427
+ display,
428
+ data,
429
+ attachToSprite: gui.attachToSprite || false
430
+ };
431
+ }
402
432
  clearPendingActions(guiId) {
403
433
  this.pendingActions.delete(guiId);
404
434
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { delay, PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n}\n\ninterface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n autoDisplay: boolean;\n dependencies?: () => Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: any = null; // Reference to VueGui instance\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data);\n });\n\n this.webSocket.on(\"gui.exit\", (guiId: string) => {\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n if (this.vueGuiInstance && this.vueGuiInstance.vm) {\n // Find the GUI in extraGuis\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (extraGui) {\n // Update the Vue component's display state and data\n this.vueGuiInstance.vm.gui[guiId] = {\n name: guiId,\n display,\n data,\n attachToSprite: extraGui.attachToSprite || false\n };\n // Trigger Vue reactivity\n this.vueGuiInstance.vm.gui = Object.assign({}, this.vueGuiInstance.vm.gui);\n }\n }\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n if (this.vueGuiInstance && this.vueGuiInstance.vm) {\n // Initialize all extraGuis in the Vue instance\n this.extraGuis.forEach(gui => {\n this.vueGuiInstance.vm.gui[gui.name] = {\n name: gui.name,\n display: gui.display(),\n data: gui.data(),\n attachToSprite: gui.attachToSprite || false\n };\n });\n \n // Trigger Vue reactivity\n this.vueGuiInstance.vm.gui = Object.assign({}, this.vueGuiInstance.vm.gui);\n }\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any) {\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions) {\n const guiId = gui.name || gui.id;\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const guiInstance: GuiInstance = {\n name: guiId,\n component: gui.component,\n display: signal(gui.display || false),\n data: signal(gui.data || {}),\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite: gui.attachToSprite || false,\n };\n\n // Accept both CanvasEngine components (.ce) and Vue components\n // Vue components will be handled by VueGui if available\n if (typeof gui.component !== 'function') {\n guiInstance.component = gui;\n this.extraGuis.push(guiInstance);\n \n // Auto display Vue components if enabled\n if (guiInstance.autoDisplay) {\n this._notifyVueGui(guiId, true, gui.data || {});\n }\n return;\n }\n\n this.gui()[guiId] = guiInstance;\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n const allGuis = this.getAll();\n return Object.values(allGuis).filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = []) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Check if it's a Vue component (in extraGuis)\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n \n if (isVueComponent) {\n // Handle Vue component display\n this._handleVueComponentDisplay(id, data, dependencies, guiInstance);\n } else {\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n }\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ? guiInstance.dependencies() : []);\n\n if (deps.length > 0) {\n // Subscribe to dependencies\n guiInstance.subscription = combineLatest(\n deps.map(dependency => dependency.observable)\n ).subscribe((values) => {\n if (values.every(value => value !== undefined)) {\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n });\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data);\n });\n\n this.webSocket.on(\"gui.exit\", (guiId: string) => {\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any) {\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = []) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Check if it's a Vue component (in extraGuis)\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n \n if (isVueComponent) {\n // Handle Vue component display\n this._handleVueComponentDisplay(id, data, dependencies, guiInstance);\n } else {\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n }\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n // Subscribe to dependencies\n guiInstance.subscription = combineLatest(\n deps.map(dependency => dependency.observable)\n ).subscribe((values) => {\n if (values.every(value => value !== undefined)) {\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n });\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -14,7 +14,7 @@ export interface NotificationItem extends NotificationPayload {
14
14
  removing: boolean;
15
15
  }
16
16
  export declare class NotificationManager {
17
- stack: import('canvasengine').WritableArraySignal<NotificationItem[]>;
17
+ stack: any;
18
18
  private _counter;
19
19
  add(payload: NotificationPayload, engine?: {
20
20
  playSound?: (id: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationManager.js","names":[],"sources":["../../src/Gui/NotificationManager.ts"],"sourcesContent":["import { signal, animatedSignal } from \"canvasengine\";\n\nexport type NotificationType = \"info\" | \"warn\" | \"error\";\n\nexport interface NotificationPayload {\n message: string;\n type?: NotificationType;\n icon?: string;\n time?: number;\n sound?: string;\n}\n\nexport interface NotificationItem extends NotificationPayload {\n id: number;\n opacity: any;\n offset: any;\n layoutY: any;\n removing: boolean;\n}\n\nconst DEFAULT_DURATION = 220;\n\nexport class NotificationManager {\n stack = signal<NotificationItem[]>([]);\n private _counter = 0;\n\n add(payload: NotificationPayload, engine?: { playSound?: (id: string) => void }) {\n const id = ++this._counter;\n const opacity = animatedSignal(0, { duration: DEFAULT_DURATION });\n const offset = animatedSignal(12, { duration: DEFAULT_DURATION });\n const layoutY = animatedSignal(0, { duration: DEFAULT_DURATION });\n const item: NotificationItem = {\n id,\n message: payload.message,\n type: payload.type || \"info\",\n icon: payload.icon,\n time: payload.time,\n sound: payload.sound,\n opacity,\n offset,\n layoutY,\n removing: false,\n };\n this.stack.update((list) => [...list, item]);\n opacity.set(1);\n offset.set(0);\n\n if (payload.sound && engine?.playSound) {\n engine.playSound(payload.sound);\n }\n\n const delay = typeof payload.time === \"number\" ? payload.time : 2000;\n setTimeout(() => {\n this.remove(id);\n }, delay);\n }\n\n remove(id: number) {\n const list = this.stack();\n const item = list.find((it) => it.id === id);\n if (!item || item.removing) return;\n item.removing = true;\n item.opacity.set(0);\n item.offset.set(-8);\n setTimeout(() => {\n this.stack.update((items) => items.filter((it) => it.id !== id));\n }, DEFAULT_DURATION);\n }\n}\n"],"mappings":";;AAoBA,IAAM,mBAAmB;AAEzB,IAAa,sBAAb,MAAiC;;eACvB,OAA2B,EAAE,CAAC;kBACnB;;CAEnB,IAAI,SAA8B,QAA+C;EAC/E,MAAM,KAAK,EAAE,KAAK;EAClB,MAAM,UAAU,eAAe,GAAG,EAAE,UAAU,kBAAkB,CAAC;EACjE,MAAM,SAAS,eAAe,IAAI,EAAE,UAAU,kBAAkB,CAAC;EACjE,MAAM,UAAU,eAAe,GAAG,EAAE,UAAU,kBAAkB,CAAC;EACjE,MAAM,OAAyB;GAC7B;GACA,SAAS,QAAQ;GACjB,MAAM,QAAQ,QAAQ;GACtB,MAAM,QAAQ;GACd,MAAM,QAAQ;GACd,OAAO,QAAQ;GACf;GACA;GACA;GACA,UAAU;GACX;AACD,OAAK,MAAM,QAAQ,SAAS,CAAC,GAAG,MAAM,KAAK,CAAC;AAC5C,UAAQ,IAAI,EAAE;AACd,SAAO,IAAI,EAAE;AAEb,MAAI,QAAQ,SAAS,QAAQ,UAC3B,QAAO,UAAU,QAAQ,MAAM;EAGjC,MAAM,QAAQ,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;AAChE,mBAAiB;AACf,QAAK,OAAO,GAAG;KACd,MAAM;;CAGX,OAAO,IAAY;EAEjB,MAAM,OADO,KAAK,OACL,CAAK,MAAM,OAAO,GAAG,OAAO,GAAG;AAC5C,MAAI,CAAC,QAAQ,KAAK,SAAU;AAC5B,OAAK,WAAW;AAChB,OAAK,QAAQ,IAAI,EAAE;AACnB,OAAK,OAAO,IAAI,GAAG;AACnB,mBAAiB;AACf,QAAK,MAAM,QAAQ,UAAU,MAAM,QAAQ,OAAO,GAAG,OAAO,GAAG,CAAC;KAC/D,iBAAiB"}
1
+ {"version":3,"file":"NotificationManager.js","names":[],"sources":["../../src/Gui/NotificationManager.ts"],"sourcesContent":["import { signal, animatedSignal } from \"canvasengine\";\n\nexport type NotificationType = \"info\" | \"warn\" | \"error\";\n\nexport interface NotificationPayload {\n message: string;\n type?: NotificationType;\n icon?: string;\n time?: number;\n sound?: string;\n}\n\nexport interface NotificationItem extends NotificationPayload {\n id: number;\n opacity: any;\n offset: any;\n layoutY: any;\n removing: boolean;\n}\n\nconst DEFAULT_DURATION = 220;\n\nexport class NotificationManager {\n stack = signal<NotificationItem[]>([]);\n private _counter = 0;\n\n add(payload: NotificationPayload, engine?: { playSound?: (id: string) => void }) {\n const id = ++this._counter;\n const opacity = animatedSignal(0, { duration: DEFAULT_DURATION });\n const offset = animatedSignal(12, { duration: DEFAULT_DURATION });\n const layoutY = animatedSignal(0, { duration: DEFAULT_DURATION });\n const item: NotificationItem = {\n id,\n message: payload.message,\n type: payload.type || \"info\",\n icon: payload.icon,\n time: payload.time,\n sound: payload.sound,\n opacity,\n offset,\n layoutY,\n removing: false,\n };\n this.stack.update((list) => [...list, item]);\n opacity.set(1);\n offset.set(0);\n\n if (payload.sound && engine?.playSound) {\n engine.playSound(payload.sound);\n }\n\n const delay = typeof payload.time === \"number\" ? payload.time : 2000;\n setTimeout(() => {\n this.remove(id);\n }, delay);\n }\n\n remove(id: number) {\n const list = this.stack();\n const item = list.find((it) => it.id === id);\n if (!item || item.removing) return;\n item.removing = true;\n item.opacity.set(0);\n item.offset.set(-8);\n setTimeout(() => {\n this.stack.update((items) => items.filter((it) => it.id !== id));\n }, DEFAULT_DURATION);\n }\n}\n"],"mappings":";;AAoBA,IAAM,mBAAmB;AAEzB,IAAa,sBAAb,MAAiC;;eACvB,OAA2B,EAAE,CAAC;kBACnB;;CAEnB,IAAI,SAA8B,QAA+C;EAC/E,MAAM,KAAK,EAAE,KAAK;EAClB,MAAM,UAAU,eAAe,GAAG,EAAE,UAAU,kBAAkB,CAAC;EACjE,MAAM,SAAS,eAAe,IAAI,EAAE,UAAU,kBAAkB,CAAC;EACjE,MAAM,UAAU,eAAe,GAAG,EAAE,UAAU,kBAAkB,CAAC;EACjE,MAAM,OAAyB;GAC7B;GACA,SAAS,QAAQ;GACjB,MAAM,QAAQ,QAAQ;GACtB,MAAM,QAAQ;GACd,MAAM,QAAQ;GACd,OAAO,QAAQ;GACf;GACA;GACA;GACA,UAAU;GACX;EACD,KAAK,MAAM,QAAQ,SAAS,CAAC,GAAG,MAAM,KAAK,CAAC;EAC5C,QAAQ,IAAI,EAAE;EACd,OAAO,IAAI,EAAE;EAEb,IAAI,QAAQ,SAAS,QAAQ,WAC3B,OAAO,UAAU,QAAQ,MAAM;EAGjC,MAAM,QAAQ,OAAO,QAAQ,SAAS,WAAW,QAAQ,OAAO;EAChE,iBAAiB;GACf,KAAK,OAAO,GAAG;KACd,MAAM;;CAGX,OAAO,IAAY;EAEjB,MAAM,OADO,KAAK,OACL,CAAK,MAAM,OAAO,GAAG,OAAO,GAAG;EAC5C,IAAI,CAAC,QAAQ,KAAK,UAAU;EAC5B,KAAK,WAAW;EAChB,KAAK,QAAQ,IAAI,EAAE;EACnB,KAAK,OAAO,IAAI,GAAG;EACnB,iBAAiB;GACf,KAAK,MAAM,QAAQ,UAAU,MAAM,QAAQ,OAAO,GAAG,OAAO,GAAG,CAAC;KAC/D,iBAAiB"}
package/dist/Resource.js CHANGED
@@ -61,7 +61,7 @@ var RpgResource = class RpgResource {
61
61
  RpgResource._spritesheets.clear();
62
62
  RpgResource.engine.spritesheets.forEach((spritesheet, id) => {
63
63
  const imageLink = spritesheet?.image || spritesheet?.imageSource || void 0;
64
- if (imageLink) RpgResource._spritesheets.set(id, imageLink);
64
+ if (imageLink) RpgResource._spritesheets.set(String(id), imageLink);
65
65
  });
66
66
  RpgResource._sounds.clear();
67
67
  RpgResource.engine.sounds.forEach((sound, id) => {
@@ -1 +1 @@
1
- {"version":3,"file":"Resource.js","names":[],"sources":["../src/Resource.ts"],"sourcesContent":["import { RpgClientEngine } from './RpgClientEngine';\n\n/**\n * RpgResource class\n * \n * Provides a unified API to access resource file links (images and sounds) in the game.\n * Resources are stored as Maps of resource IDs to file paths/URLs.\n * \n * ## Design\n * \n * RpgResource acts as a facade over the engine's resource storage, providing\n * easy access to resource file links. It maintains Maps that are synchronized\n * with the engine's internal storage, but only stores the file paths/URLs,\n * not the full resource objects.\n * \n * @example\n * ```ts\n * import { RpgResource } from '@rpgjs/client'\n * \n * // Get spritesheet image link\n * const imageLink = RpgResource.spritesheets.get('hero')\n * \n * // Get sound file link\n * const soundLink = RpgResource.sounds.get('town-music')\n * \n * // Set a new resource link\n * RpgResource.spritesheets.set('new-sprite', './assets/new-sprite.png')\n * ```\n */\nexport class RpgResource {\n private static engine: RpgClientEngine | null = null;\n private static _spritesheets: Map<string, string> = new Map();\n private static _sounds: Map<string, string> = new Map();\n\n /**\n * Initialize RpgResource with the engine instance\n * \n * This is called automatically by the engine during initialization.\n * It synchronizes the resource Maps with the engine's internal storage.\n * \n * @param engine - The RpgClientEngine instance\n */\n static init(engine: RpgClientEngine): void {\n RpgResource.engine = engine;\n RpgResource.syncResources();\n }\n\n /**\n * Synchronize resource Maps with the engine's internal storage\n * \n * Extracts file links from spritesheets and sounds stored in the engine\n * and updates the Maps accordingly.\n * \n * @private\n */\n private static syncResources(): void {\n if (!RpgResource.engine) {\n return;\n }\n\n // Sync spritesheets\n RpgResource._spritesheets.clear();\n RpgResource.engine.spritesheets.forEach((spritesheet, id) => {\n // Extract image path from spritesheet\n const imageLink = spritesheet?.image || spritesheet?.imageSource || undefined;\n if (imageLink) {\n RpgResource._spritesheets.set(id, imageLink);\n }\n });\n\n // Sync sounds\n RpgResource._sounds.clear();\n RpgResource.engine.sounds.forEach((sound, id) => {\n // Extract src path from sound\n let soundLink: string | undefined;\n \n // If it's a Howler instance, try to get src from _src or src property\n if (sound && typeof sound === 'object') {\n if (sound._src && Array.isArray(sound._src) && sound._src.length > 0) {\n soundLink = sound._src[0];\n } else if (sound.src && typeof sound.src === 'string') {\n soundLink = sound.src;\n } else if (sound.src && Array.isArray(sound.src) && sound.src.length > 0) {\n soundLink = sound.src[0];\n }\n }\n \n if (soundLink) {\n RpgResource._sounds.set(id, soundLink);\n }\n });\n }\n\n /**\n * Get/Set image links for spritesheets\n * \n * Map of spritesheet IDs to their image file paths/URLs.\n * This Map is synchronized with the engine's spritesheet storage.\n * \n * @type {Map<string, string>}\n * \n * @example\n * ```ts\n * // Get an image link\n * const imageLink = RpgResource.spritesheets.get('hero')\n * \n * // Set a new image link\n * RpgResource.spritesheets.set('new-sprite', './assets/new-sprite.png')\n * \n * // Check if a spritesheet exists\n * if (RpgResource.spritesheets.has('monster')) {\n * const link = RpgResource.spritesheets.get('monster')\n * }\n * ```\n */\n static get spritesheets(): Map<string, string> {\n // Sync before returning to ensure we have the latest data\n RpgResource.syncResources();\n return RpgResource._spritesheets;\n }\n\n /**\n * Get/Set sound file links\n * \n * Map of sound IDs to their audio file paths/URLs.\n * This Map is synchronized with the engine's sound storage.\n * \n * @type {Map<string, string>}\n * \n * @example\n * ```ts\n * // Get a sound link\n * const soundLink = RpgResource.sounds.get('town-music')\n * \n * // Set a new sound link\n * RpgResource.sounds.set('new-sound', './assets/new-sound.ogg')\n * \n * // Iterate over all sounds\n * RpgResource.sounds.forEach((link, id) => {\n * console.log(`Sound ${id}: ${link}`)\n * })\n * ```\n */\n static get sounds(): Map<string, string> {\n // Sync before returning to ensure we have the latest data\n RpgResource.syncResources();\n return RpgResource._sounds;\n }\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAa,cAAb,MAAa,YAAY;;gBACyB;;;uCACI,IAAI,KAAK;;;iCACf,IAAI,KAAK;;;;;;;;;;CAUvD,OAAO,KAAK,QAA+B;AACzC,cAAY,SAAS;AACrB,cAAY,eAAe;;;;;;;;;;CAW7B,OAAe,gBAAsB;AACnC,MAAI,CAAC,YAAY,OACf;AAIF,cAAY,cAAc,OAAO;AACjC,cAAY,OAAO,aAAa,SAAS,aAAa,OAAO;GAE3D,MAAM,YAAY,aAAa,SAAS,aAAa,eAAe,KAAA;AACpE,OAAI,UACF,aAAY,cAAc,IAAI,IAAI,UAAU;IAE9C;AAGF,cAAY,QAAQ,OAAO;AAC3B,cAAY,OAAO,OAAO,SAAS,OAAO,OAAO;GAE/C,IAAI;AAGJ,OAAI,SAAS,OAAO,UAAU;QACxB,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,KAAK,SAAS,EACjE,aAAY,MAAM,KAAK;aACd,MAAM,OAAO,OAAO,MAAM,QAAQ,SAC3C,aAAY,MAAM;aACT,MAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,IAAI,SAAS,EACrE,aAAY,MAAM,IAAI;;AAI1B,OAAI,UACF,aAAY,QAAQ,IAAI,IAAI,UAAU;IAExC;;;;;;;;;;;;;;;;;;;;;;;;CAyBJ,WAAW,eAAoC;AAE7C,cAAY,eAAe;AAC3B,SAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;CAyBrB,WAAW,SAA8B;AAEvC,cAAY,eAAe;AAC3B,SAAO,YAAY"}
1
+ {"version":3,"file":"Resource.js","names":[],"sources":["../src/Resource.ts"],"sourcesContent":["import { RpgClientEngine } from './RpgClientEngine';\n\n/**\n * RpgResource class\n * \n * Provides a unified API to access resource file links (images and sounds) in the game.\n * Resources are stored as Maps of resource IDs to file paths/URLs.\n * \n * ## Design\n * \n * RpgResource acts as a facade over the engine's resource storage, providing\n * easy access to resource file links. It maintains Maps that are synchronized\n * with the engine's internal storage, but only stores the file paths/URLs,\n * not the full resource objects.\n * \n * @example\n * ```ts\n * import { RpgResource } from '@rpgjs/client'\n * \n * // Get spritesheet image link\n * const imageLink = RpgResource.spritesheets.get('hero')\n * \n * // Get sound file link\n * const soundLink = RpgResource.sounds.get('town-music')\n * \n * // Set a new resource link\n * RpgResource.spritesheets.set('new-sprite', './assets/new-sprite.png')\n * ```\n */\nexport class RpgResource {\n private static engine: RpgClientEngine | null = null;\n private static _spritesheets: Map<string, string> = new Map();\n private static _sounds: Map<string, string> = new Map();\n\n /**\n * Initialize RpgResource with the engine instance\n * \n * This is called automatically by the engine during initialization.\n * It synchronizes the resource Maps with the engine's internal storage.\n * \n * @param engine - The RpgClientEngine instance\n */\n static init(engine: RpgClientEngine): void {\n RpgResource.engine = engine;\n RpgResource.syncResources();\n }\n\n /**\n * Synchronize resource Maps with the engine's internal storage\n * \n * Extracts file links from spritesheets and sounds stored in the engine\n * and updates the Maps accordingly.\n * \n * @private\n */\n private static syncResources(): void {\n if (!RpgResource.engine) {\n return;\n }\n\n // Sync spritesheets\n RpgResource._spritesheets.clear();\n RpgResource.engine.spritesheets.forEach((spritesheet, id) => {\n // Extract image path from spritesheet\n const imageLink = spritesheet?.image || spritesheet?.imageSource || undefined;\n if (imageLink) {\n RpgResource._spritesheets.set(String(id), imageLink);\n }\n });\n\n // Sync sounds\n RpgResource._sounds.clear();\n RpgResource.engine.sounds.forEach((sound, id) => {\n // Extract src path from sound\n let soundLink: string | undefined;\n \n // If it's a Howler instance, try to get src from _src or src property\n if (sound && typeof sound === 'object') {\n if (sound._src && Array.isArray(sound._src) && sound._src.length > 0) {\n soundLink = sound._src[0];\n } else if (sound.src && typeof sound.src === 'string') {\n soundLink = sound.src;\n } else if (sound.src && Array.isArray(sound.src) && sound.src.length > 0) {\n soundLink = sound.src[0];\n }\n }\n \n if (soundLink) {\n RpgResource._sounds.set(id, soundLink);\n }\n });\n }\n\n /**\n * Get/Set image links for spritesheets\n * \n * Map of spritesheet IDs to their image file paths/URLs.\n * This Map is synchronized with the engine's spritesheet storage.\n * \n * @type {Map<string, string>}\n * \n * @example\n * ```ts\n * // Get an image link\n * const imageLink = RpgResource.spritesheets.get('hero')\n * \n * // Set a new image link\n * RpgResource.spritesheets.set('new-sprite', './assets/new-sprite.png')\n * \n * // Check if a spritesheet exists\n * if (RpgResource.spritesheets.has('monster')) {\n * const link = RpgResource.spritesheets.get('monster')\n * }\n * ```\n */\n static get spritesheets(): Map<string, string> {\n // Sync before returning to ensure we have the latest data\n RpgResource.syncResources();\n return RpgResource._spritesheets;\n }\n\n /**\n * Get/Set sound file links\n * \n * Map of sound IDs to their audio file paths/URLs.\n * This Map is synchronized with the engine's sound storage.\n * \n * @type {Map<string, string>}\n * \n * @example\n * ```ts\n * // Get a sound link\n * const soundLink = RpgResource.sounds.get('town-music')\n * \n * // Set a new sound link\n * RpgResource.sounds.set('new-sound', './assets/new-sound.ogg')\n * \n * // Iterate over all sounds\n * RpgResource.sounds.forEach((link, id) => {\n * console.log(`Sound ${id}: ${link}`)\n * })\n * ```\n */\n static get sounds(): Map<string, string> {\n // Sync before returning to ensure we have the latest data\n RpgResource.syncResources();\n return RpgResource._sounds;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAa,cAAb,MAAa,YAAY;;gBACyB;;;uCACI,IAAI,KAAK;;;iCACf,IAAI,KAAK;;;;;;;;;;CAUvD,OAAO,KAAK,QAA+B;EACzC,YAAY,SAAS;EACrB,YAAY,eAAe;;;;;;;;;;CAW7B,OAAe,gBAAsB;EACnC,IAAI,CAAC,YAAY,QACf;EAIF,YAAY,cAAc,OAAO;EACjC,YAAY,OAAO,aAAa,SAAS,aAAa,OAAO;GAE3D,MAAM,YAAY,aAAa,SAAS,aAAa,eAAe,KAAA;GACpE,IAAI,WACF,YAAY,cAAc,IAAI,OAAO,GAAG,EAAE,UAAU;IAEtD;EAGF,YAAY,QAAQ,OAAO;EAC3B,YAAY,OAAO,OAAO,SAAS,OAAO,OAAO;GAE/C,IAAI;GAGJ,IAAI,SAAS,OAAO,UAAU;QACxB,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,KAAK,SAAS,GACjE,YAAY,MAAM,KAAK;SAClB,IAAI,MAAM,OAAO,OAAO,MAAM,QAAQ,UAC3C,YAAY,MAAM;SACb,IAAI,MAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,IAAI,SAAS,GACrE,YAAY,MAAM,IAAI;;GAI1B,IAAI,WACF,YAAY,QAAQ,IAAI,IAAI,UAAU;IAExC;;;;;;;;;;;;;;;;;;;;;;;;CAyBJ,WAAW,eAAoC;EAE7C,YAAY,eAAe;EAC3B,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;;;;CAyBrB,WAAW,SAA8B;EAEvC,YAAY,eAAe;EAC3B,OAAO,YAAY"}
@@ -5,6 +5,23 @@ import { RpgClientObject } from './Game/Object';
5
5
  import { MapPhysicsEntityContext, MapPhysicsInitContext } from '@rpgjs/common';
6
6
  type RpgComponent = RpgClientObject;
7
7
  type SceneMap = Container;
8
+ export type SpriteComponentConfig = ComponentFunction | {
9
+ component: ComponentFunction;
10
+ props?: Record<string, any> | ((object: RpgClientObject) => Record<string, any>);
11
+ data?: Record<string, any> | ((object: RpgClientObject) => Record<string, any>);
12
+ dependencies?: (object: RpgClientObject) => any[];
13
+ };
14
+ export interface RpgSpriteBeforeRemoveContext {
15
+ reason?: string;
16
+ data?: any;
17
+ transition?: {
18
+ animation?: string;
19
+ graphic?: string | string[];
20
+ duration?: number;
21
+ effect?: string;
22
+ };
23
+ timeoutMs?: number;
24
+ }
8
25
  export interface RpgClientEngineHooks {
9
26
  /**
10
27
  * When the engine is started. If you send false, you prevent the client from connecting to the server
@@ -74,7 +91,7 @@ export interface RpgSpriteHooks {
74
91
  * }
75
92
  * ```
76
93
  */
77
- componentsBehind?: ComponentFunction[];
94
+ componentsBehind?: SpriteComponentConfig[];
78
95
  /**
79
96
  * Array of components to render in front of the sprite
80
97
  * These components will be displayed with a higher z-index than the sprite itself
@@ -88,7 +105,27 @@ export interface RpgSpriteHooks {
88
105
  * }
89
106
  * ```
90
107
  */
91
- componentsInFront?: ComponentFunction[];
108
+ componentsInFront?: SpriteComponentConfig[];
109
+ /**
110
+ * Reusable sprite components addressable by server-side component definitions.
111
+ *
112
+ * The server sends only the component id and serializable props. The client
113
+ * registry maps that id to the CanvasEngine component that renders it.
114
+ *
115
+ * @prop {Record<string, ComponentFunction>} [components]
116
+ * @memberof RpgSpriteHooks
117
+ * @example
118
+ * ```ts
119
+ * import GuildBadge from './components/guild-badge.ce'
120
+ *
121
+ * const sprite: RpgSpriteHooks = {
122
+ * components: {
123
+ * guildBadge: GuildBadge
124
+ * }
125
+ * }
126
+ * ```
127
+ */
128
+ components?: Record<string, ComponentFunction>;
92
129
  /**
93
130
  * As soon as the sprite is initialized
94
131
  *
@@ -103,6 +140,17 @@ export interface RpgSpriteHooks {
103
140
  * @memberof RpgSpriteHooks
104
141
  */
105
142
  onDestroy?: (sprite: RpgComponent) => any;
143
+ /**
144
+ * Called when a sprite removal is requested, before it disappears from the scene.
145
+ *
146
+ * Return a promise to keep the sprite visible while an animation, effect, or
147
+ * sound transition is running. The server still owns gameplay removal and
148
+ * uses the timeout carried by the remove request as a safety limit.
149
+ *
150
+ * @prop { (sprite: RpgSprite, context: RpgSpriteBeforeRemoveContext) => any } [onBeforeRemove]
151
+ * @memberof RpgSpriteHooks
152
+ */
153
+ onBeforeRemove?: (sprite: RpgComponent, context: RpgSpriteBeforeRemoveContext) => any;
106
154
  /**
107
155
  * As soon as a data is changed on the server side (the name for example), you are able to know the new data but also the old data.
108
156
  *
@@ -457,6 +505,13 @@ export interface RpgClient {
457
505
  * ```
458
506
  */
459
507
  attachToSprite?: boolean;
508
+ /**
509
+ * Vue v4 compatibility alias for `attachToSprite`.
510
+ *
511
+ * Prefer `attachToSprite` in v5 projects. This is read by `@rpgjs/vue`
512
+ * for Vue GUI components migrated from the v4 GUI API.
513
+ */
514
+ rpgAttachToSprite?: boolean;
460
515
  } | any)[];
461
516
  /**
462
517
  * Array containing the list of sounds
@@ -1,9 +1,19 @@
1
+ import { Trigger } from 'canvasengine';
1
2
  import { AbstractWebsocket } from './services/AbstractSocket';
2
3
  import { Direction } from '@rpgjs/common';
3
4
  import { RpgClientMap } from './Game/Map';
4
5
  import { AnimationManager } from './Game/AnimationManager';
5
6
  import { Observable } from 'rxjs';
6
7
  import * as PIXI from "pixi.js";
8
+ type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
9
+ start(config?: T): Promise<void>;
10
+ };
11
+ type MapShakeOptions = {
12
+ intensity?: number;
13
+ duration?: number;
14
+ frequency?: number;
15
+ direction?: string;
16
+ };
7
17
  export declare class RpgClientEngine<T = any> {
8
18
  context: any;
9
19
  private guiService;
@@ -15,9 +25,9 @@ export declare class RpgClientEngine<T = any> {
15
25
  globalConfig: T;
16
26
  sceneComponent: any;
17
27
  stopProcessingInput: boolean;
18
- width: import('canvasengine').WritableSignal<string>;
19
- height: import('canvasengine').WritableSignal<string>;
20
- spritesheets: Map<string, any>;
28
+ width: any;
29
+ height: any;
30
+ spritesheets: Map<string | number, any>;
21
31
  sounds: Map<string, any>;
22
32
  componentAnimations: any[];
23
33
  private spritesheetResolver?;
@@ -29,15 +39,16 @@ export declare class RpgClientEngine<T = any> {
29
39
  tick: Observable<number>;
30
40
  private canvasApp?;
31
41
  private canvasElement?;
32
- playerIdSignal: import('canvasengine').WritableSignal<string | null>;
33
- spriteComponentsBehind: import('canvasengine').WritableArraySignal<any[]>;
34
- spriteComponentsInFront: import('canvasengine').WritableArraySignal<any[]>;
42
+ playerIdSignal: any;
43
+ spriteComponentsBehind: any;
44
+ spriteComponentsInFront: any;
45
+ spriteComponents: Map<string, any>;
35
46
  /** ID of the sprite that the camera should follow. null means follow the current player */
36
- cameraFollowTargetId: import('canvasengine').WritableSignal<string | null>;
47
+ cameraFollowTargetId: any;
37
48
  /** Trigger for map shake animation */
38
- mapShakeTrigger: import('canvasengine').Trigger<any>;
39
- controlsReady: import('canvasengine').WritableSignal<undefined>;
40
- gamePause: import('canvasengine').WritableSignal<boolean>;
49
+ mapShakeTrigger: ConfigurableTrigger<MapShakeOptions>;
50
+ controlsReady: any;
51
+ gamePause: any;
41
52
  private predictionEnabled;
42
53
  private prediction?;
43
54
  private readonly SERVER_CORRECTION_THRESHOLD;
@@ -175,7 +186,7 @@ export declare class RpgClientEngine<T = any> {
175
186
  * });
176
187
  * ```
177
188
  */
178
- setSpritesheetResolver(resolver: (id: string) => any | Promise<any>): void;
189
+ setSpritesheetResolver(resolver: (id: string | number) => any | Promise<any>): void;
179
190
  /**
180
191
  * Get a spritesheet by ID, using resolver if not found in cache
181
192
  *
@@ -183,7 +194,7 @@ export declare class RpgClientEngine<T = any> {
183
194
  * If not found and a resolver is set, it calls the resolver to create the spritesheet.
184
195
  * The resolved spritesheet is automatically cached for future use.
185
196
  *
186
- * @param id - The spritesheet ID to retrieve
197
+ * @param id - The spritesheet ID or legacy tile ID to retrieve
187
198
  * @returns The spritesheet if found or created, or undefined if not found and no resolver
188
199
  * @returns Promise<any> if the resolver is asynchronous
189
200
  *
@@ -196,7 +207,7 @@ export declare class RpgClientEngine<T = any> {
196
207
  * const spritesheet = await engine.getSpriteSheet('dynamic-sprite');
197
208
  * ```
198
209
  */
199
- getSpriteSheet(id: string): any | Promise<any>;
210
+ getSpriteSheet(id: string | number): any | Promise<any>;
200
211
  /**
201
212
  * Add a sound to the engine
202
213
  *
@@ -451,6 +462,30 @@ export declare class RpgClientEngine<T = any> {
451
462
  props: (object: any) => any;
452
463
  dependencies?: (object: any) => any[];
453
464
  }): any;
465
+ /**
466
+ * Register a reusable sprite component that can be addressed by the server.
467
+ *
468
+ * Server-side component definitions only carry the component id and
469
+ * serializable props. The client registry maps that id to the CanvasEngine
470
+ * component that performs the actual rendering.
471
+ *
472
+ * @param id - Stable component id used by server component definitions
473
+ * @param component - CanvasEngine component to render for this id
474
+ * @returns The registered component
475
+ *
476
+ * @example
477
+ * ```ts
478
+ * engine.registerSpriteComponent('guildBadge', GuildBadgeComponent);
479
+ * ```
480
+ */
481
+ registerSpriteComponent(id: string, component: any): any;
482
+ /**
483
+ * Get a reusable sprite component by id.
484
+ *
485
+ * @param id - Component id registered on the client
486
+ * @returns The CanvasEngine component, or undefined when missing
487
+ */
488
+ getSpriteComponent(id: string): any;
454
489
  /**
455
490
  * Add a component animation to the engine
456
491
  *
@@ -520,9 +555,12 @@ export declare class RpgClientEngine<T = any> {
520
555
  * duration: 1000,
521
556
  * onFinish: () => console.log('Fade complete')
522
557
  * });
558
+ *
559
+ * // Wait until the transition component calls onFinish
560
+ * await engine.startTransition('fade', { duration: 1000 });
523
561
  * ```
524
562
  */
525
- startTransition(id: string, props?: any): void;
563
+ startTransition(id: string, props?: any): Promise<void>;
526
564
  processInput({ input }: {
527
565
  input: Direction;
528
566
  }): Promise<void>;
@@ -531,7 +569,7 @@ export declare class RpgClientEngine<T = any> {
531
569
  }): void;
532
570
  get PIXI(): typeof PIXI;
533
571
  get socket(): AbstractWebsocket;
534
- get playerId(): string | null;
572
+ get playerId(): any;
535
573
  get scene(): RpgClientMap;
536
574
  private getPhysicsTick;
537
575
  private ensureCurrentPlayerBody;
@@ -543,7 +581,7 @@ export declare class RpgClientEngine<T = any> {
543
581
  private getLocalPlayerState;
544
582
  private applyAuthoritativeState;
545
583
  private initializePredictionController;
546
- getCurrentPlayer(): import('.').RpgClientPlayer;
584
+ getCurrentPlayer(): any;
547
585
  emitSceneMapHook(hookName: string, ...args: any[]): void;
548
586
  /**
549
587
  * Setup RxJS observer to wait for all conditions before calling onAfterLoading hook
@@ -697,3 +735,4 @@ export declare class RpgClientEngine<T = any> {
697
735
  */
698
736
  clear(): void;
699
737
  }
738
+ export {};