@rpgjs/client 5.0.0-beta.26 → 5.0.0-beta.28

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 (135) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/Game/AnimationManager.js.map +1 -1
  3. package/dist/Game/ClientVisuals.js.map +1 -1
  4. package/dist/Game/Map.js +2 -2
  5. package/dist/Game/Map.js.map +1 -1
  6. package/dist/Game/Object.js +1 -1
  7. package/dist/Game/Object.js.map +1 -1
  8. package/dist/Game/ProjectileManager.js.map +1 -1
  9. package/dist/Gui/Gui.d.ts +36 -17
  10. package/dist/Gui/Gui.js +40 -22
  11. package/dist/Gui/Gui.js.map +1 -1
  12. package/dist/Gui/NotificationManager.js.map +1 -1
  13. package/dist/RpgClient.d.ts +3 -64
  14. package/dist/RpgClientEngine.js +1 -1
  15. package/dist/RpgClientEngine.js.map +1 -1
  16. package/dist/_virtual/{_@oxc-project_runtime@0.133.0 → _@oxc-project_runtime@0.139.0}/helpers/esm/decorate.js +2 -2
  17. package/dist/_virtual/{_@oxc-project_runtime@0.133.0 → _@oxc-project_runtime@0.139.0}/helpers/esm/decorateMetadata.js +2 -2
  18. package/dist/components/animations/animation.ce.js.map +1 -1
  19. package/dist/components/animations/fx.ce.js.map +1 -1
  20. package/dist/components/animations/hit.ce.js.map +1 -1
  21. package/dist/components/character-hitbox.js +1 -1
  22. package/dist/components/character.ce.js +3 -1
  23. package/dist/components/character.ce.js.map +1 -1
  24. package/dist/components/dynamics/bar.ce.js.map +1 -1
  25. package/dist/components/dynamics/image.ce.js.map +1 -1
  26. package/dist/components/dynamics/parse-value.js +1 -1
  27. package/dist/components/dynamics/parse-value.js.map +1 -1
  28. package/dist/components/dynamics/shape-utils.js +1 -1
  29. package/dist/components/dynamics/shape.ce.js.map +1 -1
  30. package/dist/components/dynamics/text.ce.js.map +1 -1
  31. package/dist/components/gui/box.ce.js.map +1 -1
  32. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  33. package/dist/components/gui/gameover.ce.js.map +1 -1
  34. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  35. package/dist/components/gui/index.js +17 -16
  36. package/dist/components/gui/input-field.ce.js.map +1 -1
  37. package/dist/components/gui/input.ce.js.map +1 -1
  38. package/dist/components/gui/menu/equip-menu.ce.js +1 -1
  39. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  40. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  41. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  42. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  43. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  44. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  45. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  46. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  47. package/dist/components/gui/save-load.ce.js.map +1 -1
  48. package/dist/components/gui/shop/shop.ce.js +4 -2
  49. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  50. package/dist/components/gui/title-screen.ce.js.map +1 -1
  51. package/dist/components/index.js +4 -3
  52. package/dist/components/interaction-components.ce.js.map +1 -1
  53. package/dist/components/player-components-utils.js +1 -1
  54. package/dist/components/player-components-utils.js.map +1 -1
  55. package/dist/components/player-components.ce.js.map +1 -1
  56. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  57. package/dist/components/prebuilt/index.js +3 -2
  58. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  59. package/dist/components/scenes/canvas.ce.js.map +1 -1
  60. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  61. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  62. package/dist/components/scenes/weather-tick-lifecycle.js.map +1 -1
  63. package/dist/core/inject.d.ts +4 -4
  64. package/dist/core/inject.js.map +1 -1
  65. package/dist/core/setup.d.ts +3 -3
  66. package/dist/core/setup.js +1 -1
  67. package/dist/core/setup.js.map +1 -1
  68. package/dist/core/setup.spec.d.ts +1 -0
  69. package/dist/i18n.d.ts +1 -1
  70. package/dist/index.d.ts +1 -1
  71. package/dist/index.js +1 -2
  72. package/dist/module.d.ts +2 -2
  73. package/dist/module.js.map +1 -1
  74. package/dist/node_modules/.pnpm/@signe_di@3.1.0/node_modules/@signe/di/dist/index.js +1 -1
  75. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js +1 -1
  76. package/dist/node_modules/.pnpm/@signe_reactive@3.1.0/node_modules/@signe/reactive/dist/index.js.map +1 -1
  77. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js +2 -2
  78. package/dist/node_modules/.pnpm/@signe_room@3.1.0_react@19.2.7/node_modules/@signe/room/dist/index.js.map +1 -1
  79. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/client/index.js +1 -1
  80. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js +1 -1
  81. package/dist/node_modules/.pnpm/@signe_sync@3.1.0_react@19.2.7/node_modules/@signe/sync/dist/index.js.map +1 -1
  82. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js +2 -2
  83. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/index.js.map +1 -1
  84. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js +1 -1
  85. package/dist/node_modules/.pnpm/partysocket@1.2.0_react@19.2.7/node_modules/partysocket/dist/ws.js.map +1 -1
  86. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/index.js +7 -7
  87. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js.map +1 -1
  88. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js +1 -1
  89. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js +2 -2
  90. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js.map +1 -1
  91. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js.map +1 -1
  92. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js.map +1 -1
  93. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js +1 -1
  94. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js.map +1 -1
  95. package/dist/presets/faceset.js.map +1 -1
  96. package/dist/presets/rmspritesheet.js.map +1 -1
  97. package/dist/public-api-types.spec.d.ts +1 -0
  98. package/dist/services/AbstractSocket.d.ts +3 -3
  99. package/dist/services/AbstractSocket.js.map +1 -1
  100. package/dist/services/cameraFollow.js +1 -1
  101. package/dist/services/cameraFollow.js.map +1 -1
  102. package/dist/services/interactions.js.map +1 -1
  103. package/dist/services/loadMap.d.ts +3 -7
  104. package/dist/services/loadMap.js +2 -2
  105. package/dist/services/loadMap.js.map +1 -1
  106. package/dist/services/mapStreaming.d.ts +2 -16
  107. package/dist/services/mapStreaming.js.map +1 -1
  108. package/dist/services/mmorpg-connection.js +1 -1
  109. package/dist/services/mmorpg-connection.js.map +1 -1
  110. package/dist/services/mmorpg.d.ts +4 -26
  111. package/dist/services/mmorpg.js.map +1 -1
  112. package/dist/services/pointerContext.js +2 -1
  113. package/dist/services/pointerContext.js.map +1 -1
  114. package/dist/services/save.js.map +1 -1
  115. package/dist/services/standalone.d.ts +2 -98
  116. package/dist/services/standalone.js.map +1 -1
  117. package/dist/utils/getEntityProp.js.map +1 -1
  118. package/dist/utils/syncHitbox.js.map +1 -1
  119. package/package.json +10 -10
  120. package/src/Game/Object.spec.ts +1 -1
  121. package/src/Game/Object.ts +4 -4
  122. package/src/Gui/Gui.spec.ts +26 -1
  123. package/src/Gui/Gui.ts +104 -40
  124. package/src/RpgClient.ts +2 -63
  125. package/src/core/inject.ts +6 -5
  126. package/src/core/setup.spec.ts +35 -0
  127. package/src/core/setup.ts +8 -6
  128. package/src/index.ts +12 -1
  129. package/src/module.ts +2 -2
  130. package/src/public-api-types.spec.ts +37 -0
  131. package/src/services/AbstractSocket.ts +2 -2
  132. package/src/services/loadMap.ts +8 -8
  133. package/src/services/mapStreaming.ts +7 -5
  134. package/src/services/mmorpg.ts +6 -7
  135. package/src/services/standalone.ts +4 -5
@@ -1 +1 @@
1
- {"version":3,"file":"faceset.js","names":[],"sources":["../../src/presets/faceset.ts"],"sourcesContent":["/**\n * Creates a faceset preset for character expressions\n * \n * This preset allows you to define multiple facial expressions for a character,\n * where each expression corresponds to a specific frame position (frameX, frameY)\n * within a single faceset texture. Each expression is defined by its position\n * in the faceset grid.\n * \n * @param options - Object containing the faceset configuration\n * @param framesWidth - Number of frames horizontally in the faceset texture\n * @param framesHeight - Number of frames vertically in the faceset texture\n * @param expressions - Object mapping expression names to their frame positions as tuples [frameX, frameY]\n * @returns Faceset configuration with animations for each expression\n * \n * @example\n * ```typescript\n * const faceset = FacesetPreset({\n * id: \"facesetId\",\n * image: \"faceset.png\",\n * width: 1024,\n * height: 1024,\n * }, 4, 2, {\n * happy: [0, 0],\n * sad: [1, 0],\n * angry: [2, 0],\n * surprised: [3, 0]\n * });\n * ```\n */\nexport const FacesetPreset = (\n options: any,\n framesWidth: number, \n framesHeight: number,\n expressions: Record<string, [number, number]>,\n) => {\n \n const textures: Record<string, any> = {};\n \n // Create texture configuration for each expression\n Object.keys(expressions).forEach((expressionName) => {\n const [frameX, frameY] = expressions[expressionName];\n textures[expressionName] = {\n animations: () => [\n [{ \n time: 0, \n frameX: frameX, \n frameY: frameY \n }]\n ],\n };\n });\n \n return {\n ...options,\n framesWidth,\n framesHeight,\n textures\n };\n};\n "],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAa,iBACT,SACA,aACA,cACA,gBACC;CAED,MAAM,WAAgC,CAAC;CAGvC,OAAO,KAAK,WAAW,EAAE,SAAS,mBAAmB;EACjD,MAAM,CAAC,QAAQ,UAAU,YAAY;EACrC,SAAS,kBAAkB,EACvB,kBAAkB,CACd,CAAC;GACG,MAAM;GACE;GACA;EACZ,CAAC,CACL,EACJ;CACJ,CAAC;CAED,OAAO;EACH,GAAG;EACH;EACA;EACA;CACJ;AACJ"}
1
+ {"version":3,"file":"faceset.js","names":[],"sources":["../../src/presets/faceset.ts"],"sourcesContent":["/**\n * Creates a faceset preset for character expressions\n * \n * This preset allows you to define multiple facial expressions for a character,\n * where each expression corresponds to a specific frame position (frameX, frameY)\n * within a single faceset texture. Each expression is defined by its position\n * in the faceset grid.\n * \n * @param options - Object containing the faceset configuration\n * @param framesWidth - Number of frames horizontally in the faceset texture\n * @param framesHeight - Number of frames vertically in the faceset texture\n * @param expressions - Object mapping expression names to their frame positions as tuples [frameX, frameY]\n * @returns Faceset configuration with animations for each expression\n * \n * @example\n * ```typescript\n * const faceset = FacesetPreset({\n * id: \"facesetId\",\n * image: \"faceset.png\",\n * width: 1024,\n * height: 1024,\n * }, 4, 2, {\n * happy: [0, 0],\n * sad: [1, 0],\n * angry: [2, 0],\n * surprised: [3, 0]\n * });\n * ```\n */\nexport const FacesetPreset = (\n options: any,\n framesWidth: number, \n framesHeight: number,\n expressions: Record<string, [number, number]>,\n) => {\n \n const textures: Record<string, any> = {};\n \n // Create texture configuration for each expression\n Object.keys(expressions).forEach((expressionName) => {\n const [frameX, frameY] = expressions[expressionName];\n textures[expressionName] = {\n animations: () => [\n [{ \n time: 0, \n frameX: frameX, \n frameY: frameY \n }]\n ],\n };\n });\n \n return {\n ...options,\n framesWidth,\n framesHeight,\n textures\n };\n};\n "],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,IAAa,iBACT,SACA,aACA,cACA,gBACC;CAED,MAAM,WAAgC,CAAC;CAGvC,OAAO,KAAK,WAAW,CAAC,CAAC,SAAS,mBAAmB;EACjD,MAAM,CAAC,QAAQ,UAAU,YAAY;EACrC,SAAS,kBAAkB,EACvB,kBAAkB,CACd,CAAC;GACG,MAAM;GACE;GACA;EACZ,CAAC,CACL,EACJ;CACJ,CAAC;CAED,OAAO;EACH,GAAG;EACH;EACA;EACA;CACJ;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"rmspritesheet.js","names":[],"sources":["../../src/presets/rmspritesheet.ts"],"sourcesContent":["import { Direction, Animation } from '@rpgjs/common'\n\nexport const RMSpritesheet = (framesWidth: number, framesHeight: number, frameStand: number = 1) => {\n\n if (framesWidth <= frameStand) {\n frameStand = framesWidth - 1\n }\n\n const frameY = direction => {\n const gap = Math.max(4 - framesHeight, 0)\n return {\n [Direction.Down]: 0,\n [Direction.Left]: Math.max(0, 1 - gap),\n [Direction.Right]: Math.max(0, 2 - gap),\n [Direction.Up]: Math.max(0, 3 - gap)\n }[direction]\n }\n\n const stand = (direction: number) => [{ time: 0, frameX: frameStand, frameY: frameY(direction) }]\n const walk = direction => {\n const array: any = []\n const durationFrame = 10\n for (let i = 0; i < framesWidth; i++) {\n array.push({ time: i * durationFrame, frameX: i, frameY: frameY(direction) })\n }\n array.push({ time: array[array.length - 1].time + durationFrame })\n return array\n }\n\n return {\n textures: {\n [Animation.Stand]: {\n animations: ({direction}) => [stand(direction)]\n },\n [Animation.Walk]: {\n animations: ({direction}) => [walk(direction)]\n }\n },\n framesHeight,\n framesWidth\n }\n}"],"mappings":";;AAEA,IAAa,iBAAiB,aAAqB,cAAsB,aAAqB,MAAM;CAEhG,IAAI,eAAe,YACf,aAAa,cAAc;CAG/B,MAAM,UAAS,cAAa;EACxB,MAAM,MAAM,KAAK,IAAI,IAAI,cAAc,CAAC;EACxC,OAAO;IACF,UAAU,OAAO;IACjB,UAAU,OAAO,KAAK,IAAI,GAAG,IAAI,GAAG;IACpC,UAAU,QAAQ,KAAK,IAAI,GAAG,IAAI,GAAG;IACrC,UAAU,KAAK,KAAK,IAAI,GAAG,IAAI,GAAG;EACvC,EAAE;CACN;CAEA,MAAM,SAAS,cAAsB,CAAC;EAAE,MAAM;EAAG,QAAQ;EAAY,QAAQ,OAAO,SAAS;CAAE,CAAC;CAChG,MAAM,QAAO,cAAa;EACtB,MAAM,QAAa,CAAC;EACpB,MAAM,gBAAgB;EACtB,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAC7B,MAAM,KAAK;GAAE,MAAM,IAAI;GAAe,QAAQ;GAAG,QAAQ,OAAO,SAAS;EAAE,CAAC;EAEhF,MAAM,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,GAAG,OAAO,cAAc,CAAC;EACjE,OAAO;CACX;CAEA,OAAO;EACH,UAAU;IACL,UAAU,QAAQ,EACf,aAAa,EAAC,gBAAe,CAAC,MAAM,SAAS,CAAC,EAClD;IACC,UAAU,OAAO,EACd,aAAa,EAAC,gBAAe,CAAC,KAAK,SAAS,CAAC,EACjD;EACJ;EACA;EACA;CACJ;AACJ"}
1
+ {"version":3,"file":"rmspritesheet.js","names":[],"sources":["../../src/presets/rmspritesheet.ts"],"sourcesContent":["import { Direction, Animation } from '@rpgjs/common'\n\nexport const RMSpritesheet = (framesWidth: number, framesHeight: number, frameStand: number = 1) => {\n\n if (framesWidth <= frameStand) {\n frameStand = framesWidth - 1\n }\n\n const frameY = direction => {\n const gap = Math.max(4 - framesHeight, 0)\n return {\n [Direction.Down]: 0,\n [Direction.Left]: Math.max(0, 1 - gap),\n [Direction.Right]: Math.max(0, 2 - gap),\n [Direction.Up]: Math.max(0, 3 - gap)\n }[direction]\n }\n\n const stand = (direction: number) => [{ time: 0, frameX: frameStand, frameY: frameY(direction) }]\n const walk = direction => {\n const array: any = []\n const durationFrame = 10\n for (let i = 0; i < framesWidth; i++) {\n array.push({ time: i * durationFrame, frameX: i, frameY: frameY(direction) })\n }\n array.push({ time: array[array.length - 1].time + durationFrame })\n return array\n }\n\n return {\n textures: {\n [Animation.Stand]: {\n animations: ({direction}) => [stand(direction)]\n },\n [Animation.Walk]: {\n animations: ({direction}) => [walk(direction)]\n }\n },\n framesHeight,\n framesWidth\n }\n}"],"mappings":";;AAEA,IAAa,iBAAiB,aAAqB,cAAsB,aAAqB,MAAM;CAEhG,IAAI,eAAe,YACf,aAAa,cAAc;CAG/B,MAAM,UAAS,cAAa;EACxB,MAAM,MAAM,KAAK,IAAI,IAAI,cAAc,CAAC;EACxC,OAAO;IACF,UAAU,OAAO;IACjB,UAAU,OAAO,KAAK,IAAI,GAAG,IAAI,GAAG;IACpC,UAAU,QAAQ,KAAK,IAAI,GAAG,IAAI,GAAG;IACrC,UAAU,KAAK,KAAK,IAAI,GAAG,IAAI,GAAG;EACvC,EAAE;CACN;CAEA,MAAM,SAAS,cAAsB,CAAC;EAAE,MAAM;EAAG,QAAQ;EAAY,QAAQ,OAAO,SAAS;CAAE,CAAC;CAChG,MAAM,QAAO,cAAa;EACtB,MAAM,QAAa,CAAC;EACpB,MAAM,gBAAgB;EACtB,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,KAC7B,MAAM,KAAK;GAAE,MAAM,IAAI;GAAe,QAAQ;GAAG,QAAQ,OAAO,SAAS;EAAE,CAAC;EAEhF,MAAM,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,EAAE,CAAC,OAAO,cAAc,CAAC;EACjE,OAAO;CACX;CAEA,OAAO;EACH,UAAU;IACL,UAAU,QAAQ,EACf,aAAa,EAAC,gBAAe,CAAC,MAAM,SAAS,CAAC,EAClD;IACC,UAAU,OAAO,EACd,aAAa,EAAC,gBAAe,CAAC,KAAK,SAAS,CAAC,EACjD;EACJ;EACA;EACA;CACJ;AACJ"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { Context } from '@signe/di';
1
+ import { RpgContext } from '@rpgjs/common';
2
2
  export declare const WebSocketToken = "websocket";
3
3
  export type SocketQueryValue = string | null | undefined;
4
4
  export type SocketQuery = Record<string, SocketQueryValue>;
@@ -9,9 +9,9 @@ export type SocketUpdateProperties = {
9
9
  };
10
10
  export type WebSocketMode = "standalone" | "mmorpg";
11
11
  export declare abstract class AbstractWebsocket {
12
- protected context: Context;
12
+ protected context: RpgContext;
13
13
  readonly mode?: WebSocketMode;
14
- constructor(context: Context);
14
+ constructor(context: RpgContext);
15
15
  abstract connection(listeners?: (data: any) => void): Promise<void>;
16
16
  abstract emit(event: string, data: any): void;
17
17
  abstract on(event: string, callback: (data: any) => void): void;
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractSocket.js","names":[],"sources":["../../src/services/AbstractSocket.ts"],"sourcesContent":["import { Context } from \"@signe/di\";\n\nexport const WebSocketToken = \"websocket\";\n\nexport type SocketQueryValue = string | null | undefined;\nexport type SocketQuery = Record<string, SocketQueryValue>;\nexport type SocketUpdateProperties = {\n room: string;\n host?: string;\n query?: SocketQuery;\n};\nexport type WebSocketMode = \"standalone\" | \"mmorpg\";\n\nexport abstract class AbstractWebsocket {\n readonly mode?: WebSocketMode;\n\n constructor(protected context: Context) {}\n\n abstract connection(listeners?: (data: any) => void): Promise<void>;\n abstract emit(event: string, data: any): void;\n abstract on(event: string, callback: (data: any) => void): void;\n abstract off(event: string, callback: (data: any) => void): void;\n abstract updateProperties(params: SocketUpdateProperties): void;\n abstract reconnect(listeners?: (data: any) => void): Promise<void>;\n}\n"],"mappings":";AAEA,IAAa,iBAAiB;AAW9B,IAAsB,oBAAtB,MAAwC;CAGtC,YAAY,SAA4B;EAAlB,KAAA,UAAA;CAAmB;AAQ3C"}
1
+ {"version":3,"file":"AbstractSocket.js","names":[],"sources":["../../src/services/AbstractSocket.ts"],"sourcesContent":["import type { RpgContext } from \"@rpgjs/common\";\n\nexport const WebSocketToken = \"websocket\";\n\nexport type SocketQueryValue = string | null | undefined;\nexport type SocketQuery = Record<string, SocketQueryValue>;\nexport type SocketUpdateProperties = {\n room: string;\n host?: string;\n query?: SocketQuery;\n};\nexport type WebSocketMode = \"standalone\" | \"mmorpg\";\n\nexport abstract class AbstractWebsocket {\n readonly mode?: WebSocketMode;\n\n constructor(protected context: RpgContext) {}\n\n abstract connection(listeners?: (data: any) => void): Promise<void>;\n abstract emit(event: string, data: any): void;\n abstract on(event: string, callback: (data: any) => void): void;\n abstract off(event: string, callback: (data: any) => void): void;\n abstract updateProperties(params: SocketUpdateProperties): void;\n abstract reconnect(listeners?: (data: any) => void): Promise<void>;\n}\n"],"mappings":";AAEA,IAAa,iBAAiB;AAW9B,IAAsB,oBAAtB,MAAwC;CAGtC,YAAY,SAA+B;EAArB,KAAA,UAAA;CAAsB;AAQ9C"}
@@ -129,6 +129,6 @@ var applyCameraFollow = ({ viewport, target, smoothMove, followRevision, isCurre
129
129
  return true;
130
130
  };
131
131
  //#endregion
132
- export { applyCameraFollow, clearCameraFollowPlugins, ownsCameraFollowRevision };
132
+ export { CAMERA_FOLLOW_EASES, DEFAULT_CAMERA_FOLLOW_EASE, DEFAULT_CAMERA_FOLLOW_TIME, applyCameraFollow, cameraFollowAnimationOptions, cameraFollowOptions, clearCameraFollowPlugins, followCameraInstantly, ownsCameraFollowRevision, readCameraFollowPosition, smoothMoveEnabled };
133
133
 
134
134
  //# sourceMappingURL=cameraFollow.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cameraFollow.js","names":[],"sources":["../../src/services/cameraFollow.ts"],"sourcesContent":["export const DEFAULT_CAMERA_FOLLOW_TIME = 1000;\nexport const DEFAULT_CAMERA_FOLLOW_EASE = \"easeInOutSine\";\n\nexport const CAMERA_FOLLOW_EASES = [\n \"linear\",\n \"easeInQuad\",\n \"easeOutQuad\",\n \"easeInOutQuad\",\n \"easeInCubic\",\n \"easeOutCubic\",\n \"easeInOutCubic\",\n \"easeInQuart\",\n \"easeOutQuart\",\n \"easeInOutQuart\",\n \"easeInQuint\",\n \"easeOutQuint\",\n \"easeInOutQuint\",\n \"easeInSine\",\n \"easeOutSine\",\n \"easeInOutSine\",\n \"easeInExpo\",\n \"easeOutExpo\",\n \"easeInOutExpo\",\n \"easeInCirc\",\n \"easeOutCirc\",\n \"easeInOutCirc\",\n \"easeInElastic\",\n \"easeOutElastic\",\n \"easeInOutElastic\",\n \"easeInBack\",\n \"easeOutBack\",\n \"easeInOutBack\",\n \"easeInBounce\",\n \"easeOutBounce\",\n \"easeInOutBounce\",\n] as const;\n\nexport type CameraFollowEase = typeof CAMERA_FOLLOW_EASES[number];\n\nexport type CameraFollowSmoothMoveOptions = {\n /** Enable or disable the smooth transition when options are sent as an object. */\n enabled?: boolean;\n /** Duration of the transition to the new camera target, in milliseconds. */\n time?: number;\n /** pixi-viewport easing name, for example \"easeInOutQuad\". */\n ease?: CameraFollowEase;\n /** Continuous follow speed after the transition. 0 keeps the target centered instantly. */\n speed?: number;\n /** Continuous follow acceleration after the transition. */\n acceleration?: number | null;\n /** Center radius where the followed target can move without moving the viewport. */\n radius?: number | null;\n};\n\nexport type CameraFollowSmoothMove = boolean | CameraFollowSmoothMoveOptions;\n\nexport type CameraFollowTarget = {\n x: number;\n y: number;\n destroyed?: boolean;\n} | null | undefined;\n\nexport type CameraFollowPosition = {\n x: number;\n y: number;\n};\n\nexport interface CameraFollowApplyContext {\n viewport: any;\n target: CameraFollowTarget;\n smoothMove: CameraFollowSmoothMove;\n followRevision: number;\n isCurrentRevision: (revision: number) => boolean;\n shouldFollowCamera: () => boolean;\n}\n\nconst finiteNumber = (value: unknown, fallback: number) => {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n};\n\nconst isCameraFollowEase = (value: unknown): value is CameraFollowEase => {\n return typeof value === \"string\" && (CAMERA_FOLLOW_EASES as readonly string[]).includes(value);\n};\n\nexport const smoothMoveEnabled = (smoothMove: CameraFollowSmoothMove) => {\n if (smoothMove === false) return false;\n if (typeof smoothMove === \"object\" && smoothMove !== null && smoothMove.enabled === false) {\n return false;\n }\n return true;\n};\n\nexport const cameraFollowAnimationOptions = (\n smoothMove: CameraFollowSmoothMove\n) => {\n if (!smoothMoveEnabled(smoothMove)) return null;\n const options = typeof smoothMove === \"object\" && smoothMove !== null ? smoothMove : {};\n return {\n time: Math.max(0, finiteNumber(options.time, DEFAULT_CAMERA_FOLLOW_TIME)),\n ease: isCameraFollowEase(options.ease) ? options.ease : DEFAULT_CAMERA_FOLLOW_EASE,\n };\n};\n\nexport const cameraFollowOptions = (smoothMove: CameraFollowSmoothMove) => {\n if (typeof smoothMove !== \"object\" || smoothMove === null) return undefined;\n const options: { speed?: number; acceleration?: number | null; radius?: number | null } = {};\n if (typeof smoothMove.speed === \"number\" && Number.isFinite(smoothMove.speed)) {\n options.speed = Math.max(0, smoothMove.speed);\n }\n if (typeof smoothMove.acceleration === \"number\" && Number.isFinite(smoothMove.acceleration)) {\n options.acceleration = Math.max(0, smoothMove.acceleration);\n } else if (smoothMove.acceleration === null) {\n options.acceleration = null;\n }\n if (typeof smoothMove.radius === \"number\" && Number.isFinite(smoothMove.radius)) {\n options.radius = Math.max(0, smoothMove.radius);\n } else if (smoothMove.radius === null) {\n options.radius = null;\n }\n return Object.keys(options).length > 0 ? options : undefined;\n};\n\nexport const clearCameraFollowPlugins = (viewport: any) => {\n viewport?.plugins?.remove?.(\"animate\");\n viewport?.plugins?.remove?.(\"follow\");\n};\n\nexport const ownsCameraFollowRevision = (\n appliedRevision: number | null,\n currentRevision: number\n) => {\n return appliedRevision !== null && appliedRevision === currentRevision;\n};\n\nexport const readCameraFollowPosition = (\n target: CameraFollowTarget\n): CameraFollowPosition | null => {\n if (!target) return null;\n\n try {\n if (target.destroyed) return null;\n const x = target.x;\n const y = target.y;\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return { x, y };\n } catch {\n return null;\n }\n};\n\nconst createCameraFollowTarget = (\n target: CameraFollowTarget,\n initialPosition: CameraFollowPosition\n) => {\n let lastPosition = initialPosition;\n\n const readPosition = () => {\n const nextPosition = readCameraFollowPosition(target);\n if (nextPosition) {\n lastPosition = nextPosition;\n }\n return lastPosition;\n };\n\n return {\n get x() {\n return readPosition().x;\n },\n get y() {\n return readPosition().y;\n },\n };\n};\n\nexport const followCameraInstantly = (\n viewport: any,\n target: CameraFollowTarget,\n smoothMove: CameraFollowSmoothMove\n) => {\n const position = readCameraFollowPosition(target);\n if (!position) return false;\n\n const followTarget = createCameraFollowTarget(target, position);\n const followOptions = cameraFollowOptions(smoothMove);\n if (followOptions) {\n viewport.follow(followTarget, followOptions);\n } else {\n viewport.follow(followTarget);\n }\n return true;\n};\n\nexport const applyCameraFollow = ({\n viewport,\n target,\n smoothMove,\n followRevision,\n isCurrentRevision,\n shouldFollowCamera,\n}: CameraFollowApplyContext) => {\n clearCameraFollowPlugins(viewport);\n\n const position = readCameraFollowPosition(target);\n if (!position) return false;\n\n const animationOptions = cameraFollowAnimationOptions(smoothMove);\n if (!animationOptions || animationOptions.time <= 0) {\n return followCameraInstantly(viewport, target, smoothMove);\n }\n\n viewport.animate({\n position,\n time: animationOptions.time,\n ease: animationOptions.ease,\n callbackOnComplete: () => {\n if (!isCurrentRevision(followRevision) || !shouldFollowCamera()) return;\n if (!readCameraFollowPosition(target)) return;\n followCameraInstantly(viewport, target, smoothMove);\n },\n });\n return true;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"cameraFollow.js","names":[],"sources":["../../src/services/cameraFollow.ts"],"sourcesContent":["export const DEFAULT_CAMERA_FOLLOW_TIME = 1000;\nexport const DEFAULT_CAMERA_FOLLOW_EASE = \"easeInOutSine\";\n\nexport const CAMERA_FOLLOW_EASES = [\n \"linear\",\n \"easeInQuad\",\n \"easeOutQuad\",\n \"easeInOutQuad\",\n \"easeInCubic\",\n \"easeOutCubic\",\n \"easeInOutCubic\",\n \"easeInQuart\",\n \"easeOutQuart\",\n \"easeInOutQuart\",\n \"easeInQuint\",\n \"easeOutQuint\",\n \"easeInOutQuint\",\n \"easeInSine\",\n \"easeOutSine\",\n \"easeInOutSine\",\n \"easeInExpo\",\n \"easeOutExpo\",\n \"easeInOutExpo\",\n \"easeInCirc\",\n \"easeOutCirc\",\n \"easeInOutCirc\",\n \"easeInElastic\",\n \"easeOutElastic\",\n \"easeInOutElastic\",\n \"easeInBack\",\n \"easeOutBack\",\n \"easeInOutBack\",\n \"easeInBounce\",\n \"easeOutBounce\",\n \"easeInOutBounce\",\n] as const;\n\nexport type CameraFollowEase = typeof CAMERA_FOLLOW_EASES[number];\n\nexport type CameraFollowSmoothMoveOptions = {\n /** Enable or disable the smooth transition when options are sent as an object. */\n enabled?: boolean;\n /** Duration of the transition to the new camera target, in milliseconds. */\n time?: number;\n /** pixi-viewport easing name, for example \"easeInOutQuad\". */\n ease?: CameraFollowEase;\n /** Continuous follow speed after the transition. 0 keeps the target centered instantly. */\n speed?: number;\n /** Continuous follow acceleration after the transition. */\n acceleration?: number | null;\n /** Center radius where the followed target can move without moving the viewport. */\n radius?: number | null;\n};\n\nexport type CameraFollowSmoothMove = boolean | CameraFollowSmoothMoveOptions;\n\nexport type CameraFollowTarget = {\n x: number;\n y: number;\n destroyed?: boolean;\n} | null | undefined;\n\nexport type CameraFollowPosition = {\n x: number;\n y: number;\n};\n\nexport interface CameraFollowApplyContext {\n viewport: any;\n target: CameraFollowTarget;\n smoothMove: CameraFollowSmoothMove;\n followRevision: number;\n isCurrentRevision: (revision: number) => boolean;\n shouldFollowCamera: () => boolean;\n}\n\nconst finiteNumber = (value: unknown, fallback: number) => {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n};\n\nconst isCameraFollowEase = (value: unknown): value is CameraFollowEase => {\n return typeof value === \"string\" && (CAMERA_FOLLOW_EASES as readonly string[]).includes(value);\n};\n\nexport const smoothMoveEnabled = (smoothMove: CameraFollowSmoothMove) => {\n if (smoothMove === false) return false;\n if (typeof smoothMove === \"object\" && smoothMove !== null && smoothMove.enabled === false) {\n return false;\n }\n return true;\n};\n\nexport const cameraFollowAnimationOptions = (\n smoothMove: CameraFollowSmoothMove\n) => {\n if (!smoothMoveEnabled(smoothMove)) return null;\n const options = typeof smoothMove === \"object\" && smoothMove !== null ? smoothMove : {};\n return {\n time: Math.max(0, finiteNumber(options.time, DEFAULT_CAMERA_FOLLOW_TIME)),\n ease: isCameraFollowEase(options.ease) ? options.ease : DEFAULT_CAMERA_FOLLOW_EASE,\n };\n};\n\nexport const cameraFollowOptions = (smoothMove: CameraFollowSmoothMove) => {\n if (typeof smoothMove !== \"object\" || smoothMove === null) return undefined;\n const options: { speed?: number; acceleration?: number | null; radius?: number | null } = {};\n if (typeof smoothMove.speed === \"number\" && Number.isFinite(smoothMove.speed)) {\n options.speed = Math.max(0, smoothMove.speed);\n }\n if (typeof smoothMove.acceleration === \"number\" && Number.isFinite(smoothMove.acceleration)) {\n options.acceleration = Math.max(0, smoothMove.acceleration);\n } else if (smoothMove.acceleration === null) {\n options.acceleration = null;\n }\n if (typeof smoothMove.radius === \"number\" && Number.isFinite(smoothMove.radius)) {\n options.radius = Math.max(0, smoothMove.radius);\n } else if (smoothMove.radius === null) {\n options.radius = null;\n }\n return Object.keys(options).length > 0 ? options : undefined;\n};\n\nexport const clearCameraFollowPlugins = (viewport: any) => {\n viewport?.plugins?.remove?.(\"animate\");\n viewport?.plugins?.remove?.(\"follow\");\n};\n\nexport const ownsCameraFollowRevision = (\n appliedRevision: number | null,\n currentRevision: number\n) => {\n return appliedRevision !== null && appliedRevision === currentRevision;\n};\n\nexport const readCameraFollowPosition = (\n target: CameraFollowTarget\n): CameraFollowPosition | null => {\n if (!target) return null;\n\n try {\n if (target.destroyed) return null;\n const x = target.x;\n const y = target.y;\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return { x, y };\n } catch {\n return null;\n }\n};\n\nconst createCameraFollowTarget = (\n target: CameraFollowTarget,\n initialPosition: CameraFollowPosition\n) => {\n let lastPosition = initialPosition;\n\n const readPosition = () => {\n const nextPosition = readCameraFollowPosition(target);\n if (nextPosition) {\n lastPosition = nextPosition;\n }\n return lastPosition;\n };\n\n return {\n get x() {\n return readPosition().x;\n },\n get y() {\n return readPosition().y;\n },\n };\n};\n\nexport const followCameraInstantly = (\n viewport: any,\n target: CameraFollowTarget,\n smoothMove: CameraFollowSmoothMove\n) => {\n const position = readCameraFollowPosition(target);\n if (!position) return false;\n\n const followTarget = createCameraFollowTarget(target, position);\n const followOptions = cameraFollowOptions(smoothMove);\n if (followOptions) {\n viewport.follow(followTarget, followOptions);\n } else {\n viewport.follow(followTarget);\n }\n return true;\n};\n\nexport const applyCameraFollow = ({\n viewport,\n target,\n smoothMove,\n followRevision,\n isCurrentRevision,\n shouldFollowCamera,\n}: CameraFollowApplyContext) => {\n clearCameraFollowPlugins(viewport);\n\n const position = readCameraFollowPosition(target);\n if (!position) return false;\n\n const animationOptions = cameraFollowAnimationOptions(smoothMove);\n if (!animationOptions || animationOptions.time <= 0) {\n return followCameraInstantly(viewport, target, smoothMove);\n }\n\n viewport.animate({\n position,\n time: animationOptions.time,\n ease: animationOptions.ease,\n callbackOnComplete: () => {\n if (!isCurrentRevision(followRevision) || !shouldFollowCamera()) return;\n if (!readCameraFollowPosition(target)) return;\n followCameraInstantly(viewport, target, smoothMove);\n },\n });\n return true;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"interactions.js","names":[],"sources":["../../src/services/interactions.ts"],"sourcesContent":["import { signal } from \"canvasengine\";\nimport type { RpgActionInput, RpgActionName } from \"@rpgjs/common\";\nimport type { RpgClientEngine } from \"../RpgClientEngine\";\n\nexport type RpgInteractionEventName =\n | \"pointerenter\"\n | \"pointerleave\"\n | \"pointerover\"\n | \"pointerout\"\n | \"pointerdown\"\n | \"pointerup\"\n | \"pointermove\"\n | \"click\"\n | \"dragstart\"\n | \"dragmove\"\n | \"drop\"\n | \"cancel\";\n\nexport type RpgInteractionPosition = {\n x: number;\n y: number;\n};\n\nexport type RpgInteractionBounds = {\n left: number;\n top: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n centerX: number;\n centerY: number;\n contains(point: RpgInteractionPosition | null | undefined): boolean;\n};\n\nexport type RpgInteractionBoundsSet = {\n bounds?: RpgInteractionBounds;\n hitbox?: RpgInteractionBounds;\n graphic?: RpgInteractionBounds;\n [key: string]: RpgInteractionBounds | undefined;\n};\n\nexport type RpgInteractionTile = {\n x: number;\n y: number;\n worldX: number;\n worldY: number;\n width: number;\n height: number;\n};\n\nexport type RpgInteractionState = {\n hovered: boolean;\n pressed: boolean;\n selected: boolean;\n dragging: boolean;\n data: Record<string, any>;\n overlays: Record<string, RpgInteractionOverlay>;\n};\n\nexport type RpgInteractionOverlay = {\n component: any;\n props?: Record<string, any>;\n};\n\nexport type RpgInteractionMatcher =\n | string\n | ((ctx: RpgInteractionMatcherContext) => boolean);\n\nexport type RpgInteractionMatcherContext = {\n client: RpgClientEngine;\n target: any;\n sprite: any;\n};\n\nexport type RpgInteractionHandler = (ctx: RpgInteractionContext) => void;\n\nexport type RpgInteractionBehavior = {\n component?: any;\n props?: Record<string, any> | ((ctx: RpgInteractionContext) => Record<string, any>);\n dependencies?: any[] | ((ctx: RpgInteractionContext) => any[]);\n cursor?: string | ((ctx: RpgInteractionContext) => string | undefined);\n hitTest?: (ctx: RpgInteractionContext) => boolean;\n pointerenter?: RpgInteractionHandler;\n pointerleave?: RpgInteractionHandler;\n pointerover?: RpgInteractionHandler;\n pointerout?: RpgInteractionHandler;\n pointerdown?: RpgInteractionHandler;\n pointerup?: RpgInteractionHandler;\n pointermove?: RpgInteractionHandler;\n click?: RpgInteractionHandler;\n dragstart?: RpgInteractionHandler;\n dragmove?: RpgInteractionHandler;\n drop?: RpgInteractionHandler;\n cancel?: RpgInteractionHandler;\n};\n\nexport type RpgInteractionComponentEntry = {\n component: any;\n props: Record<string, any>;\n dependencies: any[];\n};\n\nexport type RpgInteractionContext = {\n client: RpgClientEngine;\n target: any;\n sprite: any;\n event?: any;\n behavior: RpgInteractionBehavior;\n behaviorId: string;\n pointer: {\n screen(): RpgInteractionPosition | null;\n world(): RpgInteractionPosition | null;\n tile(): RpgInteractionTile | null;\n };\n bounds(kind?: string): RpgInteractionBounds;\n state: {\n value(): RpgInteractionState;\n get<T = any>(key: string): T | undefined;\n set(key: string, value: any): void;\n patch(patch: Partial<Omit<RpgInteractionState, \"data\" | \"overlays\">>): void;\n };\n overlay: {\n render(component: any, props?: Record<string, any>): void;\n update(props?: Record<string, any>): void;\n clear(): void;\n };\n select(selected?: boolean): void;\n action(action: RpgActionName, data?: any): void;\n action(input: RpgActionInput): void;\n cancel(): void;\n};\n\ntype InteractionRegistration = {\n id: string;\n matcher: RpgInteractionMatcher;\n behavior: RpgInteractionBehavior;\n};\n\ntype InteractionEventInput = {\n event?: any;\n bounds?: RpgInteractionBoundsSet;\n};\n\ntype ActiveDrag = {\n sprite: any;\n behavior: RpgInteractionBehavior;\n behaviorId: string;\n bounds?: RpgInteractionBoundsSet;\n cancelled: boolean;\n};\n\nconst DEFAULT_STATE: RpgInteractionState = {\n hovered: false,\n pressed: false,\n selected: false,\n dragging: false,\n data: {},\n overlays: {},\n};\n\nfunction readValue(value: any): any {\n return typeof value === \"function\" ? value() : value;\n}\n\nfunction createBounds(value: any): RpgInteractionBounds {\n const left = Number(value?.left ?? value?.x ?? 0);\n const top = Number(value?.top ?? value?.y ?? 0);\n const width = Number(value?.width ?? value?.w ?? 0);\n const height = Number(value?.height ?? value?.h ?? 0);\n const right = Number(value?.right ?? left + width);\n const bottom = Number(value?.bottom ?? top + height);\n const resolvedWidth = Number.isFinite(width) && width > 0 ? width : Math.max(0, right - left);\n const resolvedHeight = Number.isFinite(height) && height > 0 ? height : Math.max(0, bottom - top);\n\n return {\n left,\n top,\n right,\n bottom,\n width: resolvedWidth,\n height: resolvedHeight,\n centerX: Number(value?.centerX ?? left + resolvedWidth / 2),\n centerY: Number(value?.centerY ?? top + resolvedHeight / 2),\n contains(point) {\n if (!point) return false;\n return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;\n },\n };\n}\n\nfunction normalizeBounds(bounds?: RpgInteractionBoundsSet): RpgInteractionBoundsSet {\n if (!bounds) return {};\n\n return Object.entries(bounds).reduce<RpgInteractionBoundsSet>((next, [key, value]) => {\n if (value) next[key] = createBounds(value);\n return next;\n }, {});\n}\n\nfunction readNumber(value: any, fallback = 0): number {\n const resolved = readValue(value);\n const number = Number(resolved);\n return Number.isFinite(number) ? number : fallback;\n}\n\nfunction offsetBounds(bounds: RpgInteractionBounds, x: number, y: number): RpgInteractionBounds {\n return createBounds({\n left: bounds.left + x,\n top: bounds.top + y,\n right: bounds.right + x,\n bottom: bounds.bottom + y,\n width: bounds.width,\n height: bounds.height,\n centerX: bounds.centerX + x,\n centerY: bounds.centerY + y,\n });\n}\n\nfunction toWorldBounds(sprite: any, bounds: RpgInteractionBoundsSet): RpgInteractionBoundsSet {\n const x = readNumber(sprite?.x);\n const y = readNumber(sprite?.y);\n\n return Object.entries(bounds).reduce<RpgInteractionBoundsSet>((next, [key, value]) => {\n if (value) next[key] = offsetBounds(value, x, y);\n return next;\n }, {});\n}\n\nfunction normalizeBehavior(behavior: RpgInteractionBehavior | any): RpgInteractionBehavior {\n if (typeof behavior === \"function\") {\n return { component: behavior };\n }\n return behavior ?? {};\n}\n\nexport class RpgClientInteractions {\n private registrations = signal<InteractionRegistration[]>([]);\n private states = signal<Record<string, RpgInteractionState>>({});\n private activeDrag?: ActiveDrag;\n private nextId = 0;\n\n constructor(private client: RpgClientEngine) {}\n\n use(matcher: RpgInteractionMatcher, behavior: RpgInteractionBehavior | any): () => void {\n const registration = {\n id: `interaction:${++this.nextId}`,\n matcher,\n behavior: normalizeBehavior(behavior),\n };\n\n this.registrations.update((registrations) => [...registrations, registration]);\n\n return () => {\n this.registrations.update((registrations) =>\n registrations.filter((entry) => entry.id !== registration.id)\n );\n };\n }\n\n getState(sprite: any): RpgInteractionState {\n const id = this.getSpriteId(sprite);\n return {\n ...DEFAULT_STATE,\n ...(id ? this.states()[id] : undefined),\n data: id ? { ...(this.states()[id]?.data ?? {}) } : {},\n overlays: id ? { ...(this.states()[id]?.overlays ?? {}) } : {},\n };\n }\n\n getRenderedComponents(sprite: any, bounds?: RpgInteractionBoundsSet): RpgInteractionComponentEntry[] {\n const normalizedBounds = normalizeBounds(bounds);\n const matched = this.getMatches(sprite);\n const state = this.getState(sprite);\n const entries: RpgInteractionComponentEntry[] = [];\n\n matched.forEach((registration) => {\n if (!registration.behavior.component) return;\n const ctx = this.createContext(sprite, registration, { bounds: normalizedBounds });\n entries.push({\n component: registration.behavior.component,\n props: {\n ...this.defaultComponentProps(sprite, state, normalizedBounds),\n ...this.resolveProps(registration.behavior.props, ctx),\n },\n dependencies: this.resolveDependencies(registration.behavior.dependencies, ctx),\n });\n });\n\n Object.entries(state.overlays).forEach(([id, overlay]) => {\n entries.push({\n component: overlay.component,\n props: {\n ...this.defaultComponentProps(sprite, state, normalizedBounds),\n ...(overlay.props ?? {}),\n overlayId: id,\n },\n dependencies: [],\n });\n });\n\n return entries;\n }\n\n cursorFor(sprite: any, bounds?: RpgInteractionBoundsSet): string | undefined {\n this.states();\n for (const registration of this.getMatches(sprite)) {\n const cursor = registration.behavior.cursor;\n if (!cursor) continue;\n const ctx = this.createContext(sprite, registration, { bounds: normalizeBounds(bounds) });\n if (!this.passesHitTest(registration.behavior, ctx, \"pointermove\")) continue;\n const resolved = typeof cursor === \"function\" ? cursor(ctx) : cursor;\n if (resolved) return resolved;\n }\n return undefined;\n }\n\n handle(sprite: any, type: RpgInteractionEventName, input: InteractionEventInput = {}): void {\n const matches = this.getMatches(sprite);\n const bounds = normalizeBounds(input.bounds);\n const entries = matches\n .map((registration) => ({\n registration,\n ctx: this.createContext(sprite, registration, {\n event: input.event,\n bounds,\n }),\n }))\n .filter(({ registration, ctx }) =>\n this.passesHitTest(registration.behavior, ctx, type)\n );\n\n if (type === \"pointerover\" || type === \"pointerenter\") {\n if (entries.length > 0) {\n this.patchState(sprite, { hovered: true });\n }\n }\n if (type === \"pointerout\" || type === \"pointerleave\") {\n if (matches.length > 0) {\n this.patchState(sprite, { hovered: false, pressed: false });\n }\n }\n if (type === \"pointerdown\") {\n if (entries.length > 0) {\n this.patchState(sprite, { pressed: true });\n }\n }\n if (type === \"pointermove\" && matches.length > 0) {\n this.patchState(sprite, { hovered: entries.length > 0 });\n }\n if (type === \"pointerup\") {\n if (matches.length > 0) {\n this.patchState(sprite, { pressed: false });\n }\n }\n\n entries.forEach(({ registration, ctx }) => {\n this.callHandler(registration.behavior, type, ctx);\n\n if (type === \"pointerdown\" && this.isDraggable(registration.behavior)) {\n this.activeDrag = {\n sprite,\n behavior: registration.behavior,\n behaviorId: registration.id,\n bounds,\n cancelled: false,\n };\n this.patchState(sprite, { dragging: true });\n this.callHandler(registration.behavior, \"dragstart\", ctx);\n }\n });\n }\n\n handlePointerMove(event?: any): void {\n if (!this.activeDrag) return;\n const drag = this.activeDrag;\n const registration = {\n id: drag.behaviorId,\n matcher: \"*\",\n behavior: drag.behavior,\n };\n const ctx = this.createContext(drag.sprite, registration, {\n event,\n bounds: drag.bounds,\n });\n this.callHandler(drag.behavior, \"dragmove\", ctx);\n }\n\n handlePointerUp(event?: any): void {\n if (!this.activeDrag) return;\n const drag = this.activeDrag;\n this.activeDrag = undefined;\n this.patchState(drag.sprite, { dragging: false, pressed: false });\n\n const registration = {\n id: drag.behaviorId,\n matcher: \"*\",\n behavior: drag.behavior,\n };\n const ctx = this.createContext(drag.sprite, registration, {\n event,\n bounds: drag.bounds,\n });\n\n this.callHandler(drag.behavior, drag.cancelled ? \"cancel\" : \"drop\", ctx);\n }\n\n cancelDrag(event?: any): void {\n if (!this.activeDrag) return;\n this.activeDrag.cancelled = true;\n this.handlePointerUp(event);\n }\n\n private getMatches(sprite: any): InteractionRegistration[] {\n return this.registrations().filter((registration) =>\n this.matches(registration.matcher, sprite)\n );\n }\n\n private matches(matcher: RpgInteractionMatcher, sprite: any): boolean {\n if (typeof matcher === \"function\") {\n return !!matcher({ client: this.client, target: sprite, sprite });\n }\n\n if (matcher === \"*\") return true;\n\n const candidates = [\n readValue(sprite?.id),\n readValue(sprite?.name),\n readValue(sprite?._name),\n readValue(sprite?.type),\n readValue(sprite?._type),\n sprite?.constructor?.name,\n ].filter(Boolean);\n\n return candidates.includes(matcher);\n }\n\n private createContext(\n sprite: any,\n registration: Pick<InteractionRegistration, \"id\" | \"behavior\">,\n input: InteractionEventInput = {},\n ): RpgInteractionContext {\n const bounds = toWorldBounds(sprite, normalizeBounds(input.bounds));\n\n const ctx = {\n client: this.client,\n target: sprite,\n sprite,\n event: input.event,\n behavior: registration.behavior,\n behaviorId: registration.id,\n pointer: {\n screen: () => this.client.pointer.screen(),\n world: () => this.client.pointer.world(),\n tile: () => this.getPointerTile(),\n },\n bounds: (kind = \"bounds\") =>\n bounds[kind] ?? bounds.graphic ?? bounds.hitbox ?? bounds.bounds ?? createBounds({}),\n state: {\n value: () => this.getState(sprite),\n get: <T = any>(key: string) => this.getState(sprite).data[key] as T | undefined,\n set: (key: string, value: any) => this.patchStateData(sprite, { [key]: value }),\n patch: (patch: Partial<Omit<RpgInteractionState, \"data\" | \"overlays\">>) =>\n this.patchState(sprite, patch),\n },\n overlay: {\n render: (component: any, props?: Record<string, any>) =>\n this.patchOverlay(sprite, registration.id, { component, props }),\n update: (props?: Record<string, any>) =>\n this.updateOverlay(sprite, registration.id, props),\n clear: () => this.clearOverlay(sprite, registration.id),\n },\n select: (selected = true) => this.patchState(sprite, { selected }),\n action: (action: RpgActionName | RpgActionInput, data?: any) =>\n this.client.processAction(action as any, data),\n cancel: () => {\n const activeDrag = this.activeDrag;\n if (activeDrag && activeDrag.sprite === sprite) {\n activeDrag.cancelled = true;\n }\n },\n };\n\n return ctx;\n }\n\n private callHandler(\n behavior: RpgInteractionBehavior,\n type: RpgInteractionEventName,\n ctx: RpgInteractionContext,\n ): void {\n const handler = behavior[type];\n handler?.(ctx);\n\n if (type === \"pointerover\") {\n behavior.pointerenter?.(ctx);\n }\n if (type === \"pointerout\") {\n behavior.pointerleave?.(ctx);\n }\n }\n\n private passesHitTest(\n behavior: RpgInteractionBehavior,\n ctx: RpgInteractionContext,\n type: RpgInteractionEventName,\n ): boolean {\n if (!behavior.hitTest) return true;\n if (type === \"pointerout\" || type === \"pointerleave\" || type === \"cancel\" || type === \"drop\") {\n return true;\n }\n return behavior.hitTest(ctx);\n }\n\n private isDraggable(behavior: RpgInteractionBehavior): boolean {\n return !!(behavior.dragstart || behavior.dragmove || behavior.drop || behavior.cancel);\n }\n\n private defaultComponentProps(\n sprite: any,\n state: RpgInteractionState,\n bounds: RpgInteractionBoundsSet,\n ): Record<string, any> {\n return {\n target: sprite,\n sprite,\n state,\n bounds: bounds.bounds ?? bounds.graphic ?? bounds.hitbox ?? createBounds({}),\n hitboxBounds: bounds.hitbox,\n graphicBounds: bounds.graphic,\n pointer: this.client.pointer,\n client: this.client,\n };\n }\n\n private resolveProps(\n props: RpgInteractionBehavior[\"props\"],\n ctx: RpgInteractionContext,\n ): Record<string, any> {\n if (!props) return {};\n return typeof props === \"function\" ? props(ctx) : props;\n }\n\n private resolveDependencies(\n dependencies: RpgInteractionBehavior[\"dependencies\"],\n ctx: RpgInteractionContext,\n ): any[] {\n if (!dependencies) return [];\n return typeof dependencies === \"function\" ? dependencies(ctx) : dependencies;\n }\n\n private getSpriteId(sprite: any): string | undefined {\n const id = readValue(sprite?.id);\n return id == null ? undefined : String(id);\n }\n\n private patchState(\n sprite: any,\n patch: Partial<Omit<RpgInteractionState, \"data\" | \"overlays\">>,\n ): void {\n const id = this.getSpriteId(sprite);\n if (!id) return;\n this.states.update((states) => ({\n ...states,\n [id]: {\n ...DEFAULT_STATE,\n ...(states[id] ?? {}),\n ...patch,\n data: states[id]?.data ?? {},\n overlays: states[id]?.overlays ?? {},\n },\n }));\n }\n\n private patchStateData(sprite: any, patch: Record<string, any>): void {\n const id = this.getSpriteId(sprite);\n if (!id) return;\n const current = this.getState(sprite);\n this.states.update((states) => ({\n ...states,\n [id]: {\n ...current,\n data: {\n ...current.data,\n ...patch,\n },\n },\n }));\n }\n\n private patchOverlay(sprite: any, id: string, overlay: RpgInteractionOverlay): void {\n const spriteId = this.getSpriteId(sprite);\n if (!spriteId) return;\n const current = this.getState(sprite);\n this.states.update((states) => ({\n ...states,\n [spriteId]: {\n ...current,\n overlays: {\n ...current.overlays,\n [id]: overlay,\n },\n },\n }));\n }\n\n private updateOverlay(sprite: any, id: string, props?: Record<string, any>): void {\n const current = this.getState(sprite);\n const overlay = current.overlays[id];\n if (!overlay) return;\n this.patchOverlay(sprite, id, {\n ...overlay,\n props: {\n ...(overlay.props ?? {}),\n ...(props ?? {}),\n },\n });\n }\n\n private clearOverlay(sprite: any, id: string): void {\n const spriteId = this.getSpriteId(sprite);\n if (!spriteId) return;\n const current = this.getState(sprite);\n const { [id]: _removed, ...overlays } = current.overlays;\n this.states.update((states) => ({\n ...states,\n [spriteId]: {\n ...current,\n overlays,\n },\n }));\n }\n\n private getPointerTile(): RpgInteractionTile | null {\n const world = this.client.pointer.world();\n if (!world) return null;\n\n const map = (this.client as any).sceneMap;\n const width = Number(map?.tileWidth ?? 32);\n const height = Number(map?.tileHeight ?? 32);\n const tileX = Math.floor(world.x / width);\n const tileY = Math.floor(world.y / height);\n\n return {\n x: tileX,\n y: tileY,\n worldX: tileX * width,\n worldY: tileY * height,\n width,\n height,\n };\n }\n}\n\nexport function hoverPopover(component: any, props?: Record<string, any>): RpgInteractionBehavior {\n return {\n component,\n props,\n cursor: \"pointer\",\n };\n}\n\nexport function selectable(options: {\n cursor?: string;\n onSelect?: RpgInteractionHandler;\n} = {}): RpgInteractionBehavior {\n return {\n cursor: options.cursor ?? \"pointer\",\n click(ctx) {\n ctx.select();\n options.onSelect?.(ctx);\n },\n };\n}\n\nexport function draggable(options: {\n cursor?: string;\n start?: RpgInteractionHandler;\n move?: RpgInteractionHandler;\n drop?: RpgInteractionHandler;\n cancel?: RpgInteractionHandler;\n} = {}): RpgInteractionBehavior {\n return {\n cursor: options.cursor ?? \"grab\",\n dragstart(ctx) {\n ctx.state.patch({ dragging: true });\n options.start?.(ctx);\n },\n dragmove: options.move,\n drop(ctx) {\n ctx.state.patch({ dragging: false, pressed: false });\n options.drop?.(ctx);\n },\n cancel(ctx) {\n ctx.state.patch({ dragging: false, pressed: false });\n options.cancel?.(ctx);\n },\n };\n}\n\nexport function dragToTile(options: {\n action?: RpgActionName;\n data?: (ctx: RpgInteractionContext) => any;\n onDrop?: RpgInteractionHandler;\n cursor?: string;\n}): RpgInteractionBehavior {\n return draggable({\n cursor: options.cursor,\n drop(ctx) {\n if (options.onDrop) {\n options.onDrop(ctx);\n return;\n }\n if (!options.action) return;\n ctx.action(options.action, options.data ? options.data(ctx) : {\n eventId: ctx.target.id,\n position: ctx.pointer.tile(),\n });\n },\n });\n}\n"],"mappings":";;AAwJA,IAAM,gBAAqC;CACzC,SAAS;CACT,SAAS;CACT,UAAU;CACV,UAAU;CACV,MAAM,CAAC;CACP,UAAU,CAAC;AACb;AAEA,SAAS,UAAU,OAAiB;CAClC,OAAO,OAAO,UAAU,aAAa,MAAM,IAAI;AACjD;AAEA,SAAS,aAAa,OAAkC;CACtD,MAAM,OAAO,OAAO,OAAO,QAAQ,OAAO,KAAK,CAAC;CAChD,MAAM,MAAM,OAAO,OAAO,OAAO,OAAO,KAAK,CAAC;CAC9C,MAAM,QAAQ,OAAO,OAAO,SAAS,OAAO,KAAK,CAAC;CAClD,MAAM,SAAS,OAAO,OAAO,UAAU,OAAO,KAAK,CAAC;CACpD,MAAM,QAAQ,OAAO,OAAO,SAAS,OAAO,KAAK;CACjD,MAAM,SAAS,OAAO,OAAO,UAAU,MAAM,MAAM;CACnD,MAAM,gBAAgB,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,GAAG,QAAQ,IAAI;CAC5F,MAAM,iBAAiB,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,GAAG,SAAS,GAAG;CAEhG,OAAO;EACL;EACA;EACA;EACA;EACA,OAAO;EACP,QAAQ;EACR,SAAS,OAAO,OAAO,WAAW,OAAO,gBAAgB,CAAC;EAC1D,SAAS,OAAO,OAAO,WAAW,MAAM,iBAAiB,CAAC;EAC1D,SAAS,OAAO;GACd,IAAI,CAAC,OAAO,OAAO;GACnB,OAAO,MAAM,KAAK,QAAQ,MAAM,KAAK,SAAS,MAAM,KAAK,OAAO,MAAM,KAAK;EAC7E;CACF;AACF;AAEA,SAAS,gBAAgB,QAA2D;CAClF,IAAI,CAAC,QAAQ,OAAO,CAAC;CAErB,OAAO,OAAO,QAAQ,MAAM,EAAE,QAAiC,MAAM,CAAC,KAAK,WAAW;EACpF,IAAI,OAAO,KAAK,OAAO,aAAa,KAAK;EACzC,OAAO;CACT,GAAG,CAAC,CAAC;AACP;AAEA,SAAS,WAAW,OAAY,WAAW,GAAW;CACpD,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,SAAS,OAAO,QAAQ;CAC9B,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,aAAa,QAA8B,GAAW,GAAiC;CAC9F,OAAO,aAAa;EAClB,MAAM,OAAO,OAAO;EACpB,KAAK,OAAO,MAAM;EAClB,OAAO,OAAO,QAAQ;EACtB,QAAQ,OAAO,SAAS;EACxB,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,SAAS,OAAO,UAAU;EAC1B,SAAS,OAAO,UAAU;CAC5B,CAAC;AACH;AAEA,SAAS,cAAc,QAAa,QAA0D;CAC5F,MAAM,IAAI,WAAW,QAAQ,CAAC;CAC9B,MAAM,IAAI,WAAW,QAAQ,CAAC;CAE9B,OAAO,OAAO,QAAQ,MAAM,EAAE,QAAiC,MAAM,CAAC,KAAK,WAAW;EACpF,IAAI,OAAO,KAAK,OAAO,aAAa,OAAO,GAAG,CAAC;EAC/C,OAAO;CACT,GAAG,CAAC,CAAC;AACP;AAEA,SAAS,kBAAkB,UAAgE;CACzF,IAAI,OAAO,aAAa,YACtB,OAAO,EAAE,WAAW,SAAS;CAE/B,OAAO,YAAY,CAAC;AACtB;AAEA,IAAa,wBAAb,MAAmC;CAMjC,YAAY,QAAiC;EAAzB,KAAA,SAAA;uBALI,OAAkC,CAAC,CAAC;gBAC3C,OAA4C,CAAC,CAAC;gBAE9C;CAE6B;CAE9C,IAAI,SAAgC,UAAoD;EACtF,MAAM,eAAe;GACnB,IAAI,eAAe,EAAE,KAAK;GAC1B;GACA,UAAU,kBAAkB,QAAQ;EACtC;EAEA,KAAK,cAAc,QAAQ,kBAAkB,CAAC,GAAG,eAAe,YAAY,CAAC;EAE7E,aAAa;GACX,KAAK,cAAc,QAAQ,kBACzB,cAAc,QAAQ,UAAU,MAAM,OAAO,aAAa,EAAE,CAC9D;EACF;CACF;CAEA,SAAS,QAAkC;EACzC,MAAM,KAAK,KAAK,YAAY,MAAM;EAClC,OAAO;GACL,GAAG;GACH,GAAI,KAAK,KAAK,OAAO,EAAE,MAAM,KAAA;GAC7B,MAAM,KAAK,EAAE,GAAI,KAAK,OAAO,EAAE,KAAK,QAAQ,CAAC,EAAG,IAAI,CAAC;GACrD,UAAU,KAAK,EAAE,GAAI,KAAK,OAAO,EAAE,KAAK,YAAY,CAAC,EAAG,IAAI,CAAC;EAC/D;CACF;CAEA,sBAAsB,QAAa,QAAkE;EACnG,MAAM,mBAAmB,gBAAgB,MAAM;EAC/C,MAAM,UAAU,KAAK,WAAW,MAAM;EACtC,MAAM,QAAQ,KAAK,SAAS,MAAM;EAClC,MAAM,UAA0C,CAAC;EAEjD,QAAQ,SAAS,iBAAiB;GAChC,IAAI,CAAC,aAAa,SAAS,WAAW;GACtC,MAAM,MAAM,KAAK,cAAc,QAAQ,cAAc,EAAE,QAAQ,iBAAiB,CAAC;GACjF,QAAQ,KAAK;IACX,WAAW,aAAa,SAAS;IACjC,OAAO;KACL,GAAG,KAAK,sBAAsB,QAAQ,OAAO,gBAAgB;KAC7D,GAAG,KAAK,aAAa,aAAa,SAAS,OAAO,GAAG;IACvD;IACA,cAAc,KAAK,oBAAoB,aAAa,SAAS,cAAc,GAAG;GAChF,CAAC;EACH,CAAC;EAED,OAAO,QAAQ,MAAM,QAAQ,EAAE,SAAS,CAAC,IAAI,aAAa;GACxD,QAAQ,KAAK;IACX,WAAW,QAAQ;IACnB,OAAO;KACL,GAAG,KAAK,sBAAsB,QAAQ,OAAO,gBAAgB;KAC7D,GAAI,QAAQ,SAAS,CAAC;KACtB,WAAW;IACb;IACA,cAAc,CAAC;GACjB,CAAC;EACH,CAAC;EAED,OAAO;CACT;CAEA,UAAU,QAAa,QAAsD;EAC3E,KAAK,OAAO;EACZ,KAAK,MAAM,gBAAgB,KAAK,WAAW,MAAM,GAAG;GAClD,MAAM,SAAS,aAAa,SAAS;GACrC,IAAI,CAAC,QAAQ;GACb,MAAM,MAAM,KAAK,cAAc,QAAQ,cAAc,EAAE,QAAQ,gBAAgB,MAAM,EAAE,CAAC;GACxF,IAAI,CAAC,KAAK,cAAc,aAAa,UAAU,KAAK,aAAa,GAAG;GACpE,MAAM,WAAW,OAAO,WAAW,aAAa,OAAO,GAAG,IAAI;GAC9D,IAAI,UAAU,OAAO;EACvB;CAEF;CAEA,OAAO,QAAa,MAA+B,QAA+B,CAAC,GAAS;EAC1F,MAAM,UAAU,KAAK,WAAW,MAAM;EACtC,MAAM,SAAS,gBAAgB,MAAM,MAAM;EAC3C,MAAM,UAAU,QACb,KAAK,kBAAkB;GACtB;GACA,KAAK,KAAK,cAAc,QAAQ,cAAc;IAC5C,OAAO,MAAM;IACb;GACF,CAAC;EACH,EAAE,EACD,QAAQ,EAAE,cAAc,UACvB,KAAK,cAAc,aAAa,UAAU,KAAK,IAAI,CACrD;EAEF,IAAI,SAAS,iBAAiB,SAAS;OACjC,QAAQ,SAAS,GACnB,KAAK,WAAW,QAAQ,EAAE,SAAS,KAAK,CAAC;EAAA;EAG7C,IAAI,SAAS,gBAAgB,SAAS;OAChC,QAAQ,SAAS,GACnB,KAAK,WAAW,QAAQ;IAAE,SAAS;IAAO,SAAS;GAAM,CAAC;EAAA;EAG9D,IAAI,SAAS;OACP,QAAQ,SAAS,GACnB,KAAK,WAAW,QAAQ,EAAE,SAAS,KAAK,CAAC;EAAA;EAG7C,IAAI,SAAS,iBAAiB,QAAQ,SAAS,GAC7C,KAAK,WAAW,QAAQ,EAAE,SAAS,QAAQ,SAAS,EAAE,CAAC;EAEzD,IAAI,SAAS;OACP,QAAQ,SAAS,GACnB,KAAK,WAAW,QAAQ,EAAE,SAAS,MAAM,CAAC;EAAA;EAI9C,QAAQ,SAAS,EAAE,cAAc,UAAU;GACzC,KAAK,YAAY,aAAa,UAAU,MAAM,GAAG;GAEjD,IAAI,SAAS,iBAAiB,KAAK,YAAY,aAAa,QAAQ,GAAG;IACrE,KAAK,aAAa;KAChB;KACA,UAAU,aAAa;KACvB,YAAY,aAAa;KACzB;KACA,WAAW;IACb;IACA,KAAK,WAAW,QAAQ,EAAE,UAAU,KAAK,CAAC;IAC1C,KAAK,YAAY,aAAa,UAAU,aAAa,GAAG;GAC1D;EACF,CAAC;CACH;CAEA,kBAAkB,OAAmB;EACnC,IAAI,CAAC,KAAK,YAAY;EACtB,MAAM,OAAO,KAAK;EAClB,MAAM,eAAe;GACnB,IAAI,KAAK;GACT,SAAS;GACT,UAAU,KAAK;EACjB;EACA,MAAM,MAAM,KAAK,cAAc,KAAK,QAAQ,cAAc;GACxD;GACA,QAAQ,KAAK;EACf,CAAC;EACD,KAAK,YAAY,KAAK,UAAU,YAAY,GAAG;CACjD;CAEA,gBAAgB,OAAmB;EACjC,IAAI,CAAC,KAAK,YAAY;EACtB,MAAM,OAAO,KAAK;EAClB,KAAK,aAAa,KAAA;EAClB,KAAK,WAAW,KAAK,QAAQ;GAAE,UAAU;GAAO,SAAS;EAAM,CAAC;EAEhE,MAAM,eAAe;GACnB,IAAI,KAAK;GACT,SAAS;GACT,UAAU,KAAK;EACjB;EACA,MAAM,MAAM,KAAK,cAAc,KAAK,QAAQ,cAAc;GACxD;GACA,QAAQ,KAAK;EACf,CAAC;EAED,KAAK,YAAY,KAAK,UAAU,KAAK,YAAY,WAAW,QAAQ,GAAG;CACzE;CAEA,WAAW,OAAmB;EAC5B,IAAI,CAAC,KAAK,YAAY;EACtB,KAAK,WAAW,YAAY;EAC5B,KAAK,gBAAgB,KAAK;CAC5B;CAEA,WAAmB,QAAwC;EACzD,OAAO,KAAK,cAAc,EAAE,QAAQ,iBAClC,KAAK,QAAQ,aAAa,SAAS,MAAM,CAC3C;CACF;CAEA,QAAgB,SAAgC,QAAsB;EACpE,IAAI,OAAO,YAAY,YACrB,OAAO,CAAC,CAAC,QAAQ;GAAE,QAAQ,KAAK;GAAQ,QAAQ;GAAQ;EAAO,CAAC;EAGlE,IAAI,YAAY,KAAK,OAAO;EAW5B,OATmB;GACjB,UAAU,QAAQ,EAAE;GACpB,UAAU,QAAQ,IAAI;GACtB,UAAU,QAAQ,KAAK;GACvB,UAAU,QAAQ,IAAI;GACtB,UAAU,QAAQ,KAAK;GACvB,QAAQ,aAAa;EACvB,EAAE,OAAO,OAEF,EAAW,SAAS,OAAO;CACpC;CAEA,cACE,QACA,cACA,QAA+B,CAAC,GACT;EACvB,MAAM,SAAS,cAAc,QAAQ,gBAAgB,MAAM,MAAM,CAAC;EAyClE,OAAO;GAtCL,QAAQ,KAAK;GACb,QAAQ;GACR;GACA,OAAO,MAAM;GACb,UAAU,aAAa;GACvB,YAAY,aAAa;GACzB,SAAS;IACP,cAAc,KAAK,OAAO,QAAQ,OAAO;IACzC,aAAa,KAAK,OAAO,QAAQ,MAAM;IACvC,YAAY,KAAK,eAAe;GAClC;GACA,SAAS,OAAO,aACd,OAAO,SAAS,OAAO,WAAW,OAAO,UAAU,OAAO,UAAU,aAAa,CAAC,CAAC;GACrF,OAAO;IACL,aAAa,KAAK,SAAS,MAAM;IACjC,MAAe,QAAgB,KAAK,SAAS,MAAM,EAAE,KAAK;IAC1D,MAAM,KAAa,UAAe,KAAK,eAAe,QAAQ,GAAG,MAAM,MAAM,CAAC;IAC9E,QAAQ,UACN,KAAK,WAAW,QAAQ,KAAK;GACjC;GACA,SAAS;IACP,SAAS,WAAgB,UACvB,KAAK,aAAa,QAAQ,aAAa,IAAI;KAAE;KAAW;IAAM,CAAC;IACjE,SAAS,UACP,KAAK,cAAc,QAAQ,aAAa,IAAI,KAAK;IACnD,aAAa,KAAK,aAAa,QAAQ,aAAa,EAAE;GACxD;GACA,SAAS,WAAW,SAAS,KAAK,WAAW,QAAQ,EAAE,SAAS,CAAC;GACjE,SAAS,QAAwC,SAC/C,KAAK,OAAO,cAAc,QAAe,IAAI;GAC/C,cAAc;IACZ,MAAM,aAAa,KAAK;IACxB,IAAI,cAAc,WAAW,WAAW,QACtC,WAAW,YAAY;GAE3B;EAGK;CACT;CAEA,YACE,UACA,MACA,KACM;EACN,MAAM,UAAU,SAAS;EACzB,UAAU,GAAG;EAEb,IAAI,SAAS,eACX,SAAS,eAAe,GAAG;EAE7B,IAAI,SAAS,cACX,SAAS,eAAe,GAAG;CAE/B;CAEA,cACE,UACA,KACA,MACS;EACT,IAAI,CAAC,SAAS,SAAS,OAAO;EAC9B,IAAI,SAAS,gBAAgB,SAAS,kBAAkB,SAAS,YAAY,SAAS,QACpF,OAAO;EAET,OAAO,SAAS,QAAQ,GAAG;CAC7B;CAEA,YAAoB,UAA2C;EAC7D,OAAO,CAAC,EAAE,SAAS,aAAa,SAAS,YAAY,SAAS,QAAQ,SAAS;CACjF;CAEA,sBACE,QACA,OACA,QACqB;EACrB,OAAO;GACL,QAAQ;GACR;GACA;GACA,QAAQ,OAAO,UAAU,OAAO,WAAW,OAAO,UAAU,aAAa,CAAC,CAAC;GAC3E,cAAc,OAAO;GACrB,eAAe,OAAO;GACtB,SAAS,KAAK,OAAO;GACrB,QAAQ,KAAK;EACf;CACF;CAEA,aACE,OACA,KACqB;EACrB,IAAI,CAAC,OAAO,OAAO,CAAC;EACpB,OAAO,OAAO,UAAU,aAAa,MAAM,GAAG,IAAI;CACpD;CAEA,oBACE,cACA,KACO;EACP,IAAI,CAAC,cAAc,OAAO,CAAC;EAC3B,OAAO,OAAO,iBAAiB,aAAa,aAAa,GAAG,IAAI;CAClE;CAEA,YAAoB,QAAiC;EACnD,MAAM,KAAK,UAAU,QAAQ,EAAE;EAC/B,OAAO,MAAM,OAAO,KAAA,IAAY,OAAO,EAAE;CAC3C;CAEA,WACE,QACA,OACM;EACN,MAAM,KAAK,KAAK,YAAY,MAAM;EAClC,IAAI,CAAC,IAAI;EACT,KAAK,OAAO,QAAQ,YAAY;GAC9B,GAAG;IACF,KAAK;IACJ,GAAG;IACH,GAAI,OAAO,OAAO,CAAC;IACnB,GAAG;IACH,MAAM,OAAO,KAAK,QAAQ,CAAC;IAC3B,UAAU,OAAO,KAAK,YAAY,CAAC;GACrC;EACF,EAAE;CACJ;CAEA,eAAuB,QAAa,OAAkC;EACpE,MAAM,KAAK,KAAK,YAAY,MAAM;EAClC,IAAI,CAAC,IAAI;EACT,MAAM,UAAU,KAAK,SAAS,MAAM;EACpC,KAAK,OAAO,QAAQ,YAAY;GAC9B,GAAG;IACF,KAAK;IACJ,GAAG;IACH,MAAM;KACJ,GAAG,QAAQ;KACX,GAAG;IACL;GACF;EACF,EAAE;CACJ;CAEA,aAAqB,QAAa,IAAY,SAAsC;EAClF,MAAM,WAAW,KAAK,YAAY,MAAM;EACxC,IAAI,CAAC,UAAU;EACf,MAAM,UAAU,KAAK,SAAS,MAAM;EACpC,KAAK,OAAO,QAAQ,YAAY;GAC9B,GAAG;IACF,WAAW;IACV,GAAG;IACH,UAAU;KACR,GAAG,QAAQ;MACV,KAAK;IACR;GACF;EACF,EAAE;CACJ;CAEA,cAAsB,QAAa,IAAY,OAAmC;EAEhF,MAAM,UADU,KAAK,SAAS,MACd,EAAQ,SAAS;EACjC,IAAI,CAAC,SAAS;EACd,KAAK,aAAa,QAAQ,IAAI;GAC5B,GAAG;GACH,OAAO;IACL,GAAI,QAAQ,SAAS,CAAC;IACtB,GAAI,SAAS,CAAC;GAChB;EACF,CAAC;CACH;CAEA,aAAqB,QAAa,IAAkB;EAClD,MAAM,WAAW,KAAK,YAAY,MAAM;EACxC,IAAI,CAAC,UAAU;EACf,MAAM,UAAU,KAAK,SAAS,MAAM;EACpC,MAAM,GAAG,KAAK,UAAU,GAAG,aAAa,QAAQ;EAChD,KAAK,OAAO,QAAQ,YAAY;GAC9B,GAAG;IACF,WAAW;IACV,GAAG;IACH;GACF;EACF,EAAE;CACJ;CAEA,iBAAoD;EAClD,MAAM,QAAQ,KAAK,OAAO,QAAQ,MAAM;EACxC,IAAI,CAAC,OAAO,OAAO;EAEnB,MAAM,MAAO,KAAK,OAAe;EACjC,MAAM,QAAQ,OAAO,KAAK,aAAa,EAAE;EACzC,MAAM,SAAS,OAAO,KAAK,cAAc,EAAE;EAC3C,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,KAAK;EACxC,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;EAEzC,OAAO;GACL,GAAG;GACH,GAAG;GACH,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB;GACA;EACF;CACF;AACF;AAEA,SAAgB,aAAa,WAAgB,OAAqD;CAChG,OAAO;EACL;EACA;EACA,QAAQ;CACV;AACF;AAEA,SAAgB,WAAW,UAGvB,CAAC,GAA2B;CAC9B,OAAO;EACL,QAAQ,QAAQ,UAAU;EAC1B,MAAM,KAAK;GACT,IAAI,OAAO;GACX,QAAQ,WAAW,GAAG;EACxB;CACF;AACF;AAEA,SAAgB,UAAU,UAMtB,CAAC,GAA2B;CAC9B,OAAO;EACL,QAAQ,QAAQ,UAAU;EAC1B,UAAU,KAAK;GACb,IAAI,MAAM,MAAM,EAAE,UAAU,KAAK,CAAC;GAClC,QAAQ,QAAQ,GAAG;EACrB;EACA,UAAU,QAAQ;EAClB,KAAK,KAAK;GACR,IAAI,MAAM,MAAM;IAAE,UAAU;IAAO,SAAS;GAAM,CAAC;GACnD,QAAQ,OAAO,GAAG;EACpB;EACA,OAAO,KAAK;GACV,IAAI,MAAM,MAAM;IAAE,UAAU;IAAO,SAAS;GAAM,CAAC;GACnD,QAAQ,SAAS,GAAG;EACtB;CACF;AACF;AAEA,SAAgB,WAAW,SAKA;CACzB,OAAO,UAAU;EACf,QAAQ,QAAQ;EAChB,KAAK,KAAK;GACR,IAAI,QAAQ,QAAQ;IAClB,QAAQ,OAAO,GAAG;IAClB;GACF;GACA,IAAI,CAAC,QAAQ,QAAQ;GACrB,IAAI,OAAO,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,KAAK,GAAG,IAAI;IAC5D,SAAS,IAAI,OAAO;IACpB,UAAU,IAAI,QAAQ,KAAK;GAC7B,CAAC;EACH;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"interactions.js","names":[],"sources":["../../src/services/interactions.ts"],"sourcesContent":["import { signal } from \"canvasengine\";\nimport type { RpgActionInput, RpgActionName } from \"@rpgjs/common\";\nimport type { RpgClientEngine } from \"../RpgClientEngine\";\n\nexport type RpgInteractionEventName =\n | \"pointerenter\"\n | \"pointerleave\"\n | \"pointerover\"\n | \"pointerout\"\n | \"pointerdown\"\n | \"pointerup\"\n | \"pointermove\"\n | \"click\"\n | \"dragstart\"\n | \"dragmove\"\n | \"drop\"\n | \"cancel\";\n\nexport type RpgInteractionPosition = {\n x: number;\n y: number;\n};\n\nexport type RpgInteractionBounds = {\n left: number;\n top: number;\n right: number;\n bottom: number;\n width: number;\n height: number;\n centerX: number;\n centerY: number;\n contains(point: RpgInteractionPosition | null | undefined): boolean;\n};\n\nexport type RpgInteractionBoundsSet = {\n bounds?: RpgInteractionBounds;\n hitbox?: RpgInteractionBounds;\n graphic?: RpgInteractionBounds;\n [key: string]: RpgInteractionBounds | undefined;\n};\n\nexport type RpgInteractionTile = {\n x: number;\n y: number;\n worldX: number;\n worldY: number;\n width: number;\n height: number;\n};\n\nexport type RpgInteractionState = {\n hovered: boolean;\n pressed: boolean;\n selected: boolean;\n dragging: boolean;\n data: Record<string, any>;\n overlays: Record<string, RpgInteractionOverlay>;\n};\n\nexport type RpgInteractionOverlay = {\n component: any;\n props?: Record<string, any>;\n};\n\nexport type RpgInteractionMatcher =\n | string\n | ((ctx: RpgInteractionMatcherContext) => boolean);\n\nexport type RpgInteractionMatcherContext = {\n client: RpgClientEngine;\n target: any;\n sprite: any;\n};\n\nexport type RpgInteractionHandler = (ctx: RpgInteractionContext) => void;\n\nexport type RpgInteractionBehavior = {\n component?: any;\n props?: Record<string, any> | ((ctx: RpgInteractionContext) => Record<string, any>);\n dependencies?: any[] | ((ctx: RpgInteractionContext) => any[]);\n cursor?: string | ((ctx: RpgInteractionContext) => string | undefined);\n hitTest?: (ctx: RpgInteractionContext) => boolean;\n pointerenter?: RpgInteractionHandler;\n pointerleave?: RpgInteractionHandler;\n pointerover?: RpgInteractionHandler;\n pointerout?: RpgInteractionHandler;\n pointerdown?: RpgInteractionHandler;\n pointerup?: RpgInteractionHandler;\n pointermove?: RpgInteractionHandler;\n click?: RpgInteractionHandler;\n dragstart?: RpgInteractionHandler;\n dragmove?: RpgInteractionHandler;\n drop?: RpgInteractionHandler;\n cancel?: RpgInteractionHandler;\n};\n\nexport type RpgInteractionComponentEntry = {\n component: any;\n props: Record<string, any>;\n dependencies: any[];\n};\n\nexport type RpgInteractionContext = {\n client: RpgClientEngine;\n target: any;\n sprite: any;\n event?: any;\n behavior: RpgInteractionBehavior;\n behaviorId: string;\n pointer: {\n screen(): RpgInteractionPosition | null;\n world(): RpgInteractionPosition | null;\n tile(): RpgInteractionTile | null;\n };\n bounds(kind?: string): RpgInteractionBounds;\n state: {\n value(): RpgInteractionState;\n get<T = any>(key: string): T | undefined;\n set(key: string, value: any): void;\n patch(patch: Partial<Omit<RpgInteractionState, \"data\" | \"overlays\">>): void;\n };\n overlay: {\n render(component: any, props?: Record<string, any>): void;\n update(props?: Record<string, any>): void;\n clear(): void;\n };\n select(selected?: boolean): void;\n action(action: RpgActionName, data?: any): void;\n action(input: RpgActionInput): void;\n cancel(): void;\n};\n\ntype InteractionRegistration = {\n id: string;\n matcher: RpgInteractionMatcher;\n behavior: RpgInteractionBehavior;\n};\n\ntype InteractionEventInput = {\n event?: any;\n bounds?: RpgInteractionBoundsSet;\n};\n\ntype ActiveDrag = {\n sprite: any;\n behavior: RpgInteractionBehavior;\n behaviorId: string;\n bounds?: RpgInteractionBoundsSet;\n cancelled: boolean;\n};\n\nconst DEFAULT_STATE: RpgInteractionState = {\n hovered: false,\n pressed: false,\n selected: false,\n dragging: false,\n data: {},\n overlays: {},\n};\n\nfunction readValue(value: any): any {\n return typeof value === \"function\" ? value() : value;\n}\n\nfunction createBounds(value: any): RpgInteractionBounds {\n const left = Number(value?.left ?? value?.x ?? 0);\n const top = Number(value?.top ?? value?.y ?? 0);\n const width = Number(value?.width ?? value?.w ?? 0);\n const height = Number(value?.height ?? value?.h ?? 0);\n const right = Number(value?.right ?? left + width);\n const bottom = Number(value?.bottom ?? top + height);\n const resolvedWidth = Number.isFinite(width) && width > 0 ? width : Math.max(0, right - left);\n const resolvedHeight = Number.isFinite(height) && height > 0 ? height : Math.max(0, bottom - top);\n\n return {\n left,\n top,\n right,\n bottom,\n width: resolvedWidth,\n height: resolvedHeight,\n centerX: Number(value?.centerX ?? left + resolvedWidth / 2),\n centerY: Number(value?.centerY ?? top + resolvedHeight / 2),\n contains(point) {\n if (!point) return false;\n return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;\n },\n };\n}\n\nfunction normalizeBounds(bounds?: RpgInteractionBoundsSet): RpgInteractionBoundsSet {\n if (!bounds) return {};\n\n return Object.entries(bounds).reduce<RpgInteractionBoundsSet>((next, [key, value]) => {\n if (value) next[key] = createBounds(value);\n return next;\n }, {});\n}\n\nfunction readNumber(value: any, fallback = 0): number {\n const resolved = readValue(value);\n const number = Number(resolved);\n return Number.isFinite(number) ? number : fallback;\n}\n\nfunction offsetBounds(bounds: RpgInteractionBounds, x: number, y: number): RpgInteractionBounds {\n return createBounds({\n left: bounds.left + x,\n top: bounds.top + y,\n right: bounds.right + x,\n bottom: bounds.bottom + y,\n width: bounds.width,\n height: bounds.height,\n centerX: bounds.centerX + x,\n centerY: bounds.centerY + y,\n });\n}\n\nfunction toWorldBounds(sprite: any, bounds: RpgInteractionBoundsSet): RpgInteractionBoundsSet {\n const x = readNumber(sprite?.x);\n const y = readNumber(sprite?.y);\n\n return Object.entries(bounds).reduce<RpgInteractionBoundsSet>((next, [key, value]) => {\n if (value) next[key] = offsetBounds(value, x, y);\n return next;\n }, {});\n}\n\nfunction normalizeBehavior(behavior: RpgInteractionBehavior | any): RpgInteractionBehavior {\n if (typeof behavior === \"function\") {\n return { component: behavior };\n }\n return behavior ?? {};\n}\n\nexport class RpgClientInteractions {\n private registrations = signal<InteractionRegistration[]>([]);\n private states = signal<Record<string, RpgInteractionState>>({});\n private activeDrag?: ActiveDrag;\n private nextId = 0;\n\n constructor(private client: RpgClientEngine) {}\n\n use(matcher: RpgInteractionMatcher, behavior: RpgInteractionBehavior | any): () => void {\n const registration = {\n id: `interaction:${++this.nextId}`,\n matcher,\n behavior: normalizeBehavior(behavior),\n };\n\n this.registrations.update((registrations) => [...registrations, registration]);\n\n return () => {\n this.registrations.update((registrations) =>\n registrations.filter((entry) => entry.id !== registration.id)\n );\n };\n }\n\n getState(sprite: any): RpgInteractionState {\n const id = this.getSpriteId(sprite);\n return {\n ...DEFAULT_STATE,\n ...(id ? this.states()[id] : undefined),\n data: id ? { ...(this.states()[id]?.data ?? {}) } : {},\n overlays: id ? { ...(this.states()[id]?.overlays ?? {}) } : {},\n };\n }\n\n getRenderedComponents(sprite: any, bounds?: RpgInteractionBoundsSet): RpgInteractionComponentEntry[] {\n const normalizedBounds = normalizeBounds(bounds);\n const matched = this.getMatches(sprite);\n const state = this.getState(sprite);\n const entries: RpgInteractionComponentEntry[] = [];\n\n matched.forEach((registration) => {\n if (!registration.behavior.component) return;\n const ctx = this.createContext(sprite, registration, { bounds: normalizedBounds });\n entries.push({\n component: registration.behavior.component,\n props: {\n ...this.defaultComponentProps(sprite, state, normalizedBounds),\n ...this.resolveProps(registration.behavior.props, ctx),\n },\n dependencies: this.resolveDependencies(registration.behavior.dependencies, ctx),\n });\n });\n\n Object.entries(state.overlays).forEach(([id, overlay]) => {\n entries.push({\n component: overlay.component,\n props: {\n ...this.defaultComponentProps(sprite, state, normalizedBounds),\n ...(overlay.props ?? {}),\n overlayId: id,\n },\n dependencies: [],\n });\n });\n\n return entries;\n }\n\n cursorFor(sprite: any, bounds?: RpgInteractionBoundsSet): string | undefined {\n this.states();\n for (const registration of this.getMatches(sprite)) {\n const cursor = registration.behavior.cursor;\n if (!cursor) continue;\n const ctx = this.createContext(sprite, registration, { bounds: normalizeBounds(bounds) });\n if (!this.passesHitTest(registration.behavior, ctx, \"pointermove\")) continue;\n const resolved = typeof cursor === \"function\" ? cursor(ctx) : cursor;\n if (resolved) return resolved;\n }\n return undefined;\n }\n\n handle(sprite: any, type: RpgInteractionEventName, input: InteractionEventInput = {}): void {\n const matches = this.getMatches(sprite);\n const bounds = normalizeBounds(input.bounds);\n const entries = matches\n .map((registration) => ({\n registration,\n ctx: this.createContext(sprite, registration, {\n event: input.event,\n bounds,\n }),\n }))\n .filter(({ registration, ctx }) =>\n this.passesHitTest(registration.behavior, ctx, type)\n );\n\n if (type === \"pointerover\" || type === \"pointerenter\") {\n if (entries.length > 0) {\n this.patchState(sprite, { hovered: true });\n }\n }\n if (type === \"pointerout\" || type === \"pointerleave\") {\n if (matches.length > 0) {\n this.patchState(sprite, { hovered: false, pressed: false });\n }\n }\n if (type === \"pointerdown\") {\n if (entries.length > 0) {\n this.patchState(sprite, { pressed: true });\n }\n }\n if (type === \"pointermove\" && matches.length > 0) {\n this.patchState(sprite, { hovered: entries.length > 0 });\n }\n if (type === \"pointerup\") {\n if (matches.length > 0) {\n this.patchState(sprite, { pressed: false });\n }\n }\n\n entries.forEach(({ registration, ctx }) => {\n this.callHandler(registration.behavior, type, ctx);\n\n if (type === \"pointerdown\" && this.isDraggable(registration.behavior)) {\n this.activeDrag = {\n sprite,\n behavior: registration.behavior,\n behaviorId: registration.id,\n bounds,\n cancelled: false,\n };\n this.patchState(sprite, { dragging: true });\n this.callHandler(registration.behavior, \"dragstart\", ctx);\n }\n });\n }\n\n handlePointerMove(event?: any): void {\n if (!this.activeDrag) return;\n const drag = this.activeDrag;\n const registration = {\n id: drag.behaviorId,\n matcher: \"*\",\n behavior: drag.behavior,\n };\n const ctx = this.createContext(drag.sprite, registration, {\n event,\n bounds: drag.bounds,\n });\n this.callHandler(drag.behavior, \"dragmove\", ctx);\n }\n\n handlePointerUp(event?: any): void {\n if (!this.activeDrag) return;\n const drag = this.activeDrag;\n this.activeDrag = undefined;\n this.patchState(drag.sprite, { dragging: false, pressed: false });\n\n const registration = {\n id: drag.behaviorId,\n matcher: \"*\",\n behavior: drag.behavior,\n };\n const ctx = this.createContext(drag.sprite, registration, {\n event,\n bounds: drag.bounds,\n });\n\n this.callHandler(drag.behavior, drag.cancelled ? \"cancel\" : \"drop\", ctx);\n }\n\n cancelDrag(event?: any): void {\n if (!this.activeDrag) return;\n this.activeDrag.cancelled = true;\n this.handlePointerUp(event);\n }\n\n private getMatches(sprite: any): InteractionRegistration[] {\n return this.registrations().filter((registration) =>\n this.matches(registration.matcher, sprite)\n );\n }\n\n private matches(matcher: RpgInteractionMatcher, sprite: any): boolean {\n if (typeof matcher === \"function\") {\n return !!matcher({ client: this.client, target: sprite, sprite });\n }\n\n if (matcher === \"*\") return true;\n\n const candidates = [\n readValue(sprite?.id),\n readValue(sprite?.name),\n readValue(sprite?._name),\n readValue(sprite?.type),\n readValue(sprite?._type),\n sprite?.constructor?.name,\n ].filter(Boolean);\n\n return candidates.includes(matcher);\n }\n\n private createContext(\n sprite: any,\n registration: Pick<InteractionRegistration, \"id\" | \"behavior\">,\n input: InteractionEventInput = {},\n ): RpgInteractionContext {\n const bounds = toWorldBounds(sprite, normalizeBounds(input.bounds));\n\n const ctx = {\n client: this.client,\n target: sprite,\n sprite,\n event: input.event,\n behavior: registration.behavior,\n behaviorId: registration.id,\n pointer: {\n screen: () => this.client.pointer.screen(),\n world: () => this.client.pointer.world(),\n tile: () => this.getPointerTile(),\n },\n bounds: (kind = \"bounds\") =>\n bounds[kind] ?? bounds.graphic ?? bounds.hitbox ?? bounds.bounds ?? createBounds({}),\n state: {\n value: () => this.getState(sprite),\n get: <T = any>(key: string) => this.getState(sprite).data[key] as T | undefined,\n set: (key: string, value: any) => this.patchStateData(sprite, { [key]: value }),\n patch: (patch: Partial<Omit<RpgInteractionState, \"data\" | \"overlays\">>) =>\n this.patchState(sprite, patch),\n },\n overlay: {\n render: (component: any, props?: Record<string, any>) =>\n this.patchOverlay(sprite, registration.id, { component, props }),\n update: (props?: Record<string, any>) =>\n this.updateOverlay(sprite, registration.id, props),\n clear: () => this.clearOverlay(sprite, registration.id),\n },\n select: (selected = true) => this.patchState(sprite, { selected }),\n action: (action: RpgActionName | RpgActionInput, data?: any) =>\n this.client.processAction(action as any, data),\n cancel: () => {\n const activeDrag = this.activeDrag;\n if (activeDrag && activeDrag.sprite === sprite) {\n activeDrag.cancelled = true;\n }\n },\n };\n\n return ctx;\n }\n\n private callHandler(\n behavior: RpgInteractionBehavior,\n type: RpgInteractionEventName,\n ctx: RpgInteractionContext,\n ): void {\n const handler = behavior[type];\n handler?.(ctx);\n\n if (type === \"pointerover\") {\n behavior.pointerenter?.(ctx);\n }\n if (type === \"pointerout\") {\n behavior.pointerleave?.(ctx);\n }\n }\n\n private passesHitTest(\n behavior: RpgInteractionBehavior,\n ctx: RpgInteractionContext,\n type: RpgInteractionEventName,\n ): boolean {\n if (!behavior.hitTest) return true;\n if (type === \"pointerout\" || type === \"pointerleave\" || type === \"cancel\" || type === \"drop\") {\n return true;\n }\n return behavior.hitTest(ctx);\n }\n\n private isDraggable(behavior: RpgInteractionBehavior): boolean {\n return !!(behavior.dragstart || behavior.dragmove || behavior.drop || behavior.cancel);\n }\n\n private defaultComponentProps(\n sprite: any,\n state: RpgInteractionState,\n bounds: RpgInteractionBoundsSet,\n ): Record<string, any> {\n return {\n target: sprite,\n sprite,\n state,\n bounds: bounds.bounds ?? bounds.graphic ?? bounds.hitbox ?? createBounds({}),\n hitboxBounds: bounds.hitbox,\n graphicBounds: bounds.graphic,\n pointer: this.client.pointer,\n client: this.client,\n };\n }\n\n private resolveProps(\n props: RpgInteractionBehavior[\"props\"],\n ctx: RpgInteractionContext,\n ): Record<string, any> {\n if (!props) return {};\n return typeof props === \"function\" ? props(ctx) : props;\n }\n\n private resolveDependencies(\n dependencies: RpgInteractionBehavior[\"dependencies\"],\n ctx: RpgInteractionContext,\n ): any[] {\n if (!dependencies) return [];\n return typeof dependencies === \"function\" ? dependencies(ctx) : dependencies;\n }\n\n private getSpriteId(sprite: any): string | undefined {\n const id = readValue(sprite?.id);\n return id == null ? undefined : String(id);\n }\n\n private patchState(\n sprite: any,\n patch: Partial<Omit<RpgInteractionState, \"data\" | \"overlays\">>,\n ): void {\n const id = this.getSpriteId(sprite);\n if (!id) return;\n this.states.update((states) => ({\n ...states,\n [id]: {\n ...DEFAULT_STATE,\n ...(states[id] ?? {}),\n ...patch,\n data: states[id]?.data ?? {},\n overlays: states[id]?.overlays ?? {},\n },\n }));\n }\n\n private patchStateData(sprite: any, patch: Record<string, any>): void {\n const id = this.getSpriteId(sprite);\n if (!id) return;\n const current = this.getState(sprite);\n this.states.update((states) => ({\n ...states,\n [id]: {\n ...current,\n data: {\n ...current.data,\n ...patch,\n },\n },\n }));\n }\n\n private patchOverlay(sprite: any, id: string, overlay: RpgInteractionOverlay): void {\n const spriteId = this.getSpriteId(sprite);\n if (!spriteId) return;\n const current = this.getState(sprite);\n this.states.update((states) => ({\n ...states,\n [spriteId]: {\n ...current,\n overlays: {\n ...current.overlays,\n [id]: overlay,\n },\n },\n }));\n }\n\n private updateOverlay(sprite: any, id: string, props?: Record<string, any>): void {\n const current = this.getState(sprite);\n const overlay = current.overlays[id];\n if (!overlay) return;\n this.patchOverlay(sprite, id, {\n ...overlay,\n props: {\n ...(overlay.props ?? {}),\n ...(props ?? {}),\n },\n });\n }\n\n private clearOverlay(sprite: any, id: string): void {\n const spriteId = this.getSpriteId(sprite);\n if (!spriteId) return;\n const current = this.getState(sprite);\n const { [id]: _removed, ...overlays } = current.overlays;\n this.states.update((states) => ({\n ...states,\n [spriteId]: {\n ...current,\n overlays,\n },\n }));\n }\n\n private getPointerTile(): RpgInteractionTile | null {\n const world = this.client.pointer.world();\n if (!world) return null;\n\n const map = (this.client as any).sceneMap;\n const width = Number(map?.tileWidth ?? 32);\n const height = Number(map?.tileHeight ?? 32);\n const tileX = Math.floor(world.x / width);\n const tileY = Math.floor(world.y / height);\n\n return {\n x: tileX,\n y: tileY,\n worldX: tileX * width,\n worldY: tileY * height,\n width,\n height,\n };\n }\n}\n\nexport function hoverPopover(component: any, props?: Record<string, any>): RpgInteractionBehavior {\n return {\n component,\n props,\n cursor: \"pointer\",\n };\n}\n\nexport function selectable(options: {\n cursor?: string;\n onSelect?: RpgInteractionHandler;\n} = {}): RpgInteractionBehavior {\n return {\n cursor: options.cursor ?? \"pointer\",\n click(ctx) {\n ctx.select();\n options.onSelect?.(ctx);\n },\n };\n}\n\nexport function draggable(options: {\n cursor?: string;\n start?: RpgInteractionHandler;\n move?: RpgInteractionHandler;\n drop?: RpgInteractionHandler;\n cancel?: RpgInteractionHandler;\n} = {}): RpgInteractionBehavior {\n return {\n cursor: options.cursor ?? \"grab\",\n dragstart(ctx) {\n ctx.state.patch({ dragging: true });\n options.start?.(ctx);\n },\n dragmove: options.move,\n drop(ctx) {\n ctx.state.patch({ dragging: false, pressed: false });\n options.drop?.(ctx);\n },\n cancel(ctx) {\n ctx.state.patch({ dragging: false, pressed: false });\n options.cancel?.(ctx);\n },\n };\n}\n\nexport function dragToTile(options: {\n action?: RpgActionName;\n data?: (ctx: RpgInteractionContext) => any;\n onDrop?: RpgInteractionHandler;\n cursor?: string;\n}): RpgInteractionBehavior {\n return draggable({\n cursor: options.cursor,\n drop(ctx) {\n if (options.onDrop) {\n options.onDrop(ctx);\n return;\n }\n if (!options.action) return;\n ctx.action(options.action, options.data ? options.data(ctx) : {\n eventId: ctx.target.id,\n position: ctx.pointer.tile(),\n });\n },\n });\n}\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import { Context } from '@signe/di';
2
- import { LightingState } from '@rpgjs/common';
1
+ import { LightingState, RpgContext, RpgProvider } from '@rpgjs/common';
3
2
  import { RpgClientMap } from '../Game/Map';
4
3
  export declare const LoadMapToken = "LoadMapToken";
5
4
  /**
@@ -51,7 +50,7 @@ export declare class LoadMapService {
51
50
  private context;
52
51
  private options;
53
52
  private updateMapService;
54
- constructor(context: Context, options: LoadMapOptions);
53
+ constructor(context: RpgContext, options: LoadMapOptions);
55
54
  initialize(): void;
56
55
  load(mapId: string): Promise<MapData>;
57
56
  }
@@ -146,7 +145,4 @@ export declare class LoadMapService {
146
145
  * @see {@link LoadMapOptions} for callback function signature
147
146
  * @see {@link MapData} for return data structure
148
147
  */
149
- export declare function provideLoadMap(options: LoadMapOptions): {
150
- provide: string;
151
- useFactory: (context: Context) => void;
152
- }[];
148
+ export declare function provideLoadMap(options: LoadMapOptions): RpgProvider[];
@@ -6,7 +6,7 @@ var LoadMapService = class {
6
6
  constructor(context, options) {
7
7
  this.context = context;
8
8
  this.options = options;
9
- if (context["side"] === "server") return;
9
+ if (context.side === "server") return;
10
10
  }
11
11
  initialize() {}
12
12
  async load(mapId) {
@@ -111,7 +111,7 @@ function provideLoadMap(options) {
111
111
  return [{
112
112
  provide: UpdateMapToken,
113
113
  useFactory: (context) => {
114
- if (context["side"] === "client") console.warn("UpdateMapToken is not overridden");
114
+ if (context.side === "client") console.warn("UpdateMapToken is not overridden");
115
115
  }
116
116
  }, {
117
117
  provide: LoadMapToken,
@@ -1 +1 @@
1
- {"version":3,"file":"loadMap.js","names":[],"sources":["../../src/services/loadMap.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { UpdateMapToken, UpdateMapService, type LightingState } from \"@rpgjs/common\";\nimport type { RpgClientMap } from \"../Game/Map\";\n\nexport const LoadMapToken = 'LoadMapToken'\n\n/**\n * Represents the structure of map data that should be returned by the load map callback.\n * This interface defines all the properties that can be provided when loading a custom map.\n * \n * @interface MapData\n */\nexport type MapData = {\n /** Raw map data that will be passed to the map component */\n data: any;\n /** CanvasEngine component that will render the map */\n component: any;\n /** Optional map width in pixels, used for viewport calculations */\n width?: number;\n /** Optional map height in pixels, used for viewport calculations */\n height?: number;\n /** Optional map events data (NPCs, interactive objects, etc.) */\n events?: any;\n /** Optional named positions, for example Tiled point objects used by changeMap(\"map\", \"name\") */\n positions?: Record<string, { x: number; y: number; z?: number }>;\n /** Optional map identifier, defaults to the mapId parameter if not provided */\n id?: string;\n /** Optional initial lighting state for the loaded map */\n lighting?: LightingState | null;\n /** Optional render parameters passed to the map component. */\n params?: Record<string, unknown>;\n /** Internal controller used by a progressive map provider. */\n streamController?: { attach(map: RpgClientMap): void; detach(): void };\n}\n\n/**\n * Callback function type for loading map data.\n * This function receives a map ID and should return either a MapData object directly\n * or a Promise that resolves to a MapData object.\n * \n * @callback LoadMapOptions\n * @param {string} mapId - The identifier of the map to load\n * @returns {Promise<MapData> | MapData} The map data object or a promise resolving to it\n */\nexport type LoadMapOptions = (mapId: string) => Promise<MapData> | MapData \n\nexport class LoadMapService {\n private updateMapService: UpdateMapService;\n\n constructor(private context: Context, private options: LoadMapOptions) {\n if (context['side'] === 'server') {\n return\n }\n }\n\n initialize(): void {}\n\n async load(mapId: string) {\n this.updateMapService ??= inject(this.context, UpdateMapToken);\n const map = await this.options(mapId.replace('map-', ''))\n await this.updateMapService.update(map);\n return map;\n }\n}\n\n/**\n * Creates a dependency injection configuration for custom map loading on the client side.\n * \n * This function allows you to customize how maps are loaded and displayed by providing\n * a callback that defines custom map data and rendering components. It's designed to work\n * with the RPG-JS dependency injection system and enables integration of custom map formats\n * like Tiled TMX files or any other map data structure.\n * \n * The function sets up the necessary service providers for map loading, including:\n * - UpdateMapToken: Handles map updates in the client context\n * - LoadMapToken: Provides the LoadMapService with your custom loading logic\n * \n * **Design Concept:**\n * The function follows the provider pattern, creating a modular way to inject custom\n * map loading behavior into the RPG-JS client engine. It separates the concern of\n * map data fetching from map rendering, allowing developers to focus on their specific\n * map format while leveraging the engine's rendering capabilities.\n * \n * @param {LoadMapOptions} options - Callback function that handles map loading logic\n * @returns {Array<Object>} Array of dependency injection provider configurations\n * \n * @example\n * ```typescript\n * import { provideLoadMap } from '@rpgjs/client'\n * import { createModule } from '@rpgjs/common'\n * import MyTiledMapComponent from './MyTiledMapComponent.ce'\n * \n * // Basic usage with JSON map data\n * export function provideCustomMap() {\n * return createModule(\"CustomMap\", [\n * provideLoadMap(async (mapId) => {\n * const response = await fetch(`/maps/${mapId}.json`)\n * const mapData = await response.json()\n * \n * return {\n * data: mapData,\n * component: MyTiledMapComponent,\n * width: mapData.width,\n * height: mapData.height,\n * events: mapData.events\n * }\n * })\n * ])\n * }\n * \n * // Advanced usage with Tiled TMX files\n * export function provideTiledMap() {\n * return createModule(\"TiledMap\", [\n * provideLoadMap(async (mapId) => {\n * // Load TMX file\n * const tmxResponse = await fetch(`/tiled/${mapId}.tmx`)\n * const tmxData = await tmxResponse.text()\n * \n * // Parse TMX data (using a TMX parser)\n * const parsedMap = parseTMX(tmxData)\n * \n * return {\n * data: parsedMap,\n * component: TiledMapRenderer,\n * width: parsedMap.width * parsedMap.tilewidth,\n * height: parsedMap.height * parsedMap.tileheight,\n * events: parsedMap.objectGroups?.find(g => g.name === 'events')?.objects\n * }\n * })\n * ])\n * }\n * \n * // Synchronous usage for static maps\n * export function provideStaticMap() {\n * return createModule(\"StaticMap\", [\n * provideLoadMap((mapId) => {\n * const staticMaps = {\n * 'town': { tiles: [...], npcs: [...] },\n * 'dungeon': { tiles: [...], monsters: [...] }\n * }\n * \n * return {\n * data: staticMaps[mapId],\n * component: StaticMapComponent,\n * width: 800,\n * height: 600\n * }\n * })\n * ])\n * }\n * ```\n * \n * @since 4.0.0\n * @see {@link LoadMapOptions} for callback function signature\n * @see {@link MapData} for return data structure\n */\nexport function provideLoadMap(options: LoadMapOptions) {\n return [\n {\n provide: UpdateMapToken,\n useFactory: (context: Context) => {\n if (context['side'] === 'client') {\n console.warn('UpdateMapToken is not overridden')\n }\n return\n },\n },\n {\n provide: LoadMapToken,\n useFactory: (context: Context) => new LoadMapService(context, options),\n },\n ];\n}\n"],"mappings":";;;AAIA,IAAa,eAAe;AA0C5B,IAAa,iBAAb,MAA4B;CAG1B,YAAY,SAA0B,SAAiC;EAAnD,KAAA,UAAA;EAA0B,KAAA,UAAA;EAC5C,IAAI,QAAQ,YAAY,UACtB;CAEJ;CAEA,aAAmB,CAAC;CAEpB,MAAM,KAAK,OAAe;EACxB,KAAK,qBAAqB,OAAO,KAAK,SAAS,cAAc;EAC7D,MAAM,MAAM,MAAM,KAAK,QAAQ,MAAM,QAAQ,QAAQ,EAAE,CAAC;EACxD,MAAM,KAAK,iBAAiB,OAAO,GAAG;EACtC,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA,SAAgB,eAAe,SAAyB;CACtD,OAAO,CACL;EACE,SAAS;EACT,aAAa,YAAqB;GAChC,IAAI,QAAQ,YAAY,UACtB,QAAQ,KAAK,kCAAkC;EAGnD;CACF,GACA;EACE,SAAS;EACT,aAAa,YAAqB,IAAI,eAAe,SAAS,OAAO;CACvE,CACF;AACF"}
1
+ {"version":3,"file":"loadMap.js","names":[],"sources":["../../src/services/loadMap.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { UpdateMapToken, UpdateMapService, type LightingState, type RpgContext, type RpgProvider } from \"@rpgjs/common\";\nimport type { RpgClientMap } from \"../Game/Map\";\n\nexport const LoadMapToken = 'LoadMapToken'\n\n/**\n * Represents the structure of map data that should be returned by the load map callback.\n * This interface defines all the properties that can be provided when loading a custom map.\n * \n * @interface MapData\n */\nexport type MapData = {\n /** Raw map data that will be passed to the map component */\n data: any;\n /** CanvasEngine component that will render the map */\n component: any;\n /** Optional map width in pixels, used for viewport calculations */\n width?: number;\n /** Optional map height in pixels, used for viewport calculations */\n height?: number;\n /** Optional map events data (NPCs, interactive objects, etc.) */\n events?: any;\n /** Optional named positions, for example Tiled point objects used by changeMap(\"map\", \"name\") */\n positions?: Record<string, { x: number; y: number; z?: number }>;\n /** Optional map identifier, defaults to the mapId parameter if not provided */\n id?: string;\n /** Optional initial lighting state for the loaded map */\n lighting?: LightingState | null;\n /** Optional render parameters passed to the map component. */\n params?: Record<string, unknown>;\n /** Internal controller used by a progressive map provider. */\n streamController?: { attach(map: RpgClientMap): void; detach(): void };\n}\n\n/**\n * Callback function type for loading map data.\n * This function receives a map ID and should return either a MapData object directly\n * or a Promise that resolves to a MapData object.\n * \n * @callback LoadMapOptions\n * @param {string} mapId - The identifier of the map to load\n * @returns {Promise<MapData> | MapData} The map data object or a promise resolving to it\n */\nexport type LoadMapOptions = (mapId: string) => Promise<MapData> | MapData \n\nexport class LoadMapService {\n private updateMapService: UpdateMapService;\n\n constructor(private context: RpgContext, private options: LoadMapOptions) {\n if (context.side === 'server') {\n return\n }\n }\n\n initialize(): void {}\n\n async load(mapId: string) {\n this.updateMapService ??= inject(this.context as Context, UpdateMapToken);\n const map = await this.options(mapId.replace('map-', ''))\n await this.updateMapService.update(map);\n return map;\n }\n}\n\n/**\n * Creates a dependency injection configuration for custom map loading on the client side.\n * \n * This function allows you to customize how maps are loaded and displayed by providing\n * a callback that defines custom map data and rendering components. It's designed to work\n * with the RPG-JS dependency injection system and enables integration of custom map formats\n * like Tiled TMX files or any other map data structure.\n * \n * The function sets up the necessary service providers for map loading, including:\n * - UpdateMapToken: Handles map updates in the client context\n * - LoadMapToken: Provides the LoadMapService with your custom loading logic\n * \n * **Design Concept:**\n * The function follows the provider pattern, creating a modular way to inject custom\n * map loading behavior into the RPG-JS client engine. It separates the concern of\n * map data fetching from map rendering, allowing developers to focus on their specific\n * map format while leveraging the engine's rendering capabilities.\n * \n * @param {LoadMapOptions} options - Callback function that handles map loading logic\n * @returns {Array<Object>} Array of dependency injection provider configurations\n * \n * @example\n * ```typescript\n * import { provideLoadMap } from '@rpgjs/client'\n * import { createModule } from '@rpgjs/common'\n * import MyTiledMapComponent from './MyTiledMapComponent.ce'\n * \n * // Basic usage with JSON map data\n * export function provideCustomMap() {\n * return createModule(\"CustomMap\", [\n * provideLoadMap(async (mapId) => {\n * const response = await fetch(`/maps/${mapId}.json`)\n * const mapData = await response.json()\n * \n * return {\n * data: mapData,\n * component: MyTiledMapComponent,\n * width: mapData.width,\n * height: mapData.height,\n * events: mapData.events\n * }\n * })\n * ])\n * }\n * \n * // Advanced usage with Tiled TMX files\n * export function provideTiledMap() {\n * return createModule(\"TiledMap\", [\n * provideLoadMap(async (mapId) => {\n * // Load TMX file\n * const tmxResponse = await fetch(`/tiled/${mapId}.tmx`)\n * const tmxData = await tmxResponse.text()\n * \n * // Parse TMX data (using a TMX parser)\n * const parsedMap = parseTMX(tmxData)\n * \n * return {\n * data: parsedMap,\n * component: TiledMapRenderer,\n * width: parsedMap.width * parsedMap.tilewidth,\n * height: parsedMap.height * parsedMap.tileheight,\n * events: parsedMap.objectGroups?.find(g => g.name === 'events')?.objects\n * }\n * })\n * ])\n * }\n * \n * // Synchronous usage for static maps\n * export function provideStaticMap() {\n * return createModule(\"StaticMap\", [\n * provideLoadMap((mapId) => {\n * const staticMaps = {\n * 'town': { tiles: [...], npcs: [...] },\n * 'dungeon': { tiles: [...], monsters: [...] }\n * }\n * \n * return {\n * data: staticMaps[mapId],\n * component: StaticMapComponent,\n * width: 800,\n * height: 600\n * }\n * })\n * ])\n * }\n * ```\n * \n * @since 4.0.0\n * @see {@link LoadMapOptions} for callback function signature\n * @see {@link MapData} for return data structure\n */\nexport function provideLoadMap(options: LoadMapOptions): RpgProvider[] {\n return [\n {\n provide: UpdateMapToken,\n useFactory: (context: RpgContext) => {\n if (context.side === 'client') {\n console.warn('UpdateMapToken is not overridden')\n }\n return\n },\n },\n {\n provide: LoadMapToken,\n useFactory: (context: RpgContext) => new LoadMapService(context, options),\n },\n ];\n}\n"],"mappings":";;;AAIA,IAAa,eAAe;AA0C5B,IAAa,iBAAb,MAA4B;CAG1B,YAAY,SAA6B,SAAiC;EAAtD,KAAA,UAAA;EAA6B,KAAA,UAAA;EAC/C,IAAI,QAAQ,SAAS,UACnB;CAEJ;CAEA,aAAmB,CAAC;CAEpB,MAAM,KAAK,OAAe;EACxB,KAAK,qBAAqB,OAAO,KAAK,SAAoB,cAAc;EACxE,MAAM,MAAM,MAAM,KAAK,QAAQ,MAAM,QAAQ,QAAQ,EAAE,CAAC;EACxD,MAAM,KAAK,iBAAiB,OAAO,GAAG;EACtC,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA,SAAgB,eAAe,SAAwC;CACrE,OAAO,CACL;EACE,SAAS;EACT,aAAa,YAAwB;GACnC,IAAI,QAAQ,SAAS,UACnB,QAAQ,KAAK,kCAAkC;EAGnD;CACF,GACA;EACE,SAAS;EACT,aAAa,YAAwB,IAAI,eAAe,SAAS,OAAO;CAC1E,CACF;AACF"}
@@ -1,5 +1,4 @@
1
- import { Context } from '@signe/di';
2
- import { MapChunkHitbox, MapStreamChunk, MapStreamManifest, MapStreamPacket } from '@rpgjs/common';
1
+ import { MapChunkHitbox, MapStreamChunk, MapStreamManifest, MapStreamPacket, RpgProvider } from '@rpgjs/common';
3
2
  import { MapData, LoadMapOptions } from './loadMap';
4
3
  export interface ClientMapStreamingAdapter<TManifestData = unknown, TChunkData = unknown, TState = unknown> {
5
4
  /** CanvasEngine component that renders the provider-specific state. */
@@ -47,16 +46,6 @@ export declare class MapStreamClientController<TManifestData, TChunkData, TState
47
46
  private publish;
48
47
  private updatePredictionBoundary;
49
48
  }
50
- declare class MapStreamingLoadMapService<TManifestData, TChunkData, TState> {
51
- private readonly context;
52
- private readonly options;
53
- private socket?;
54
- private stream?;
55
- private updateMap?;
56
- constructor(context: Context, options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>);
57
- initialize(): void;
58
- load(mapId: string): Promise<MapData>;
59
- }
60
49
  /**
61
50
  * Provide a transport-neutral client map loader backed by authoritative chunks.
62
51
  * Standalone mode may keep a direct loader while MMORPG mode never fetches the
@@ -78,8 +67,5 @@ declare class MapStreamingLoadMapService<TManifestData, TChunkData, TState> {
78
67
  * })
79
68
  * ```
80
69
  */
81
- export declare function provideClientMapStreaming<TManifestData, TChunkData, TState>(options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>): {
82
- provide: string;
83
- useFactory: (context: Context) => MapStreamingLoadMapService<TManifestData, TChunkData, TState>;
84
- }[];
70
+ export declare function provideClientMapStreaming<TManifestData, TChunkData, TState>(options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>): RpgProvider[];
85
71
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"mapStreaming.js","names":[],"sources":["../../src/services/mapStreaming.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport {\n MAP_STREAM_EVENT,\n MAP_STREAM_REQUEST_EVENT,\n type MapChunkHitbox,\n type MapStreamChunk,\n type MapStreamManifest,\n type MapStreamPacket,\n} from \"@rpgjs/common\";\nimport { AbstractWebsocket, WebSocketToken } from \"./AbstractSocket\";\nimport { LoadMapToken, type MapData, type LoadMapOptions } from \"./loadMap\";\nimport { UpdateMapService, UpdateMapToken } from \"@rpgjs/common\";\n\nexport interface ClientMapStreamingAdapter<TManifestData = unknown, TChunkData = unknown, TState = unknown> {\n /** CanvasEngine component that renders the provider-specific state. */\n component: unknown;\n /** Create empty client render state from public manifest data. */\n createState(manifest: MapStreamManifest<TManifestData>): TState;\n /** Apply one disclosed render chunk to the state. */\n applyChunk(state: TState, chunk: MapStreamChunk<TChunkData>): void;\n /** Remove one chunk that left the retention window. */\n removeChunk(state: TState, key: string): void;\n /** Return the serializable/component-ready map value exposed to CanvasEngine. */\n getData(state: TState): unknown;\n /** Optionally derive component parameters from the public manifest. */\n getParams?(manifest: MapStreamManifest<TManifestData>): Record<string, unknown>;\n}\n\nexport interface ClientMapStreamingOptions<TManifestData = unknown, TChunkData = unknown, TState = unknown> {\n /** Format adapter paired with the server compiler. */\n adapter: ClientMapStreamingAdapter<TManifestData, TChunkData, TState>;\n /** Optional direct loader used only by standalone RPG mode. */\n directLoad?: LoadMapOptions;\n /** Maximum wait for the initial authoritative manifest. Defaults to 10 seconds. */\n timeoutMs?: number;\n}\n\ntype StreamingPhysicsMap = {\n data: { (): MapData | null; set(value: MapData): void };\n replaceStreamedStaticHitboxes(namespace: string, hitboxes: MapChunkHitbox[]): void;\n clearStreamedStaticHitboxes(namespace: string): void;\n};\n\ntype Waiter = {\n resolve(controller: MapStreamClientController<any, any, any>): void;\n reject(error: Error): void;\n timer: ReturnType<typeof setTimeout>;\n};\n\nexport class MapStreamClientController<TManifestData, TChunkData, TState> {\n private state: TState;\n private manifest: MapStreamManifest<TManifestData>;\n private readonly chunks = new Map<string, MapStreamChunk<TChunkData>>();\n private attachedMap?: StreamingPhysicsMap;\n\n constructor(\n private readonly adapter: ClientMapStreamingAdapter<TManifestData, TChunkData, TState>,\n manifest: MapStreamManifest<TManifestData>,\n ) {\n this.manifest = manifest;\n this.state = adapter.createState(manifest);\n }\n\n get revision(): string {\n return this.manifest.revision;\n }\n\n reset(manifest: MapStreamManifest<TManifestData>): void {\n const attachedMap = this.attachedMap;\n this.detach();\n this.manifest = manifest;\n this.state = this.adapter.createState(manifest);\n this.chunks.clear();\n this.attachedMap = attachedMap;\n }\n\n receive(packet: MapStreamPacket<TManifestData, TChunkData>): void {\n for (const key of packet.removed) {\n this.chunks.delete(key);\n this.adapter.removeChunk(this.state, key);\n this.attachedMap?.clearStreamedStaticHitboxes(key);\n }\n for (const chunk of packet.chunks) {\n this.chunks.set(chunk.key, chunk);\n this.adapter.applyChunk(this.state, chunk);\n this.attachedMap?.replaceStreamedStaticHitboxes(chunk.key, chunk.hitboxes);\n }\n this.publish();\n }\n\n toMapData(): MapData {\n return {\n id: this.manifest.mapId,\n width: this.manifest.width,\n height: this.manifest.height,\n data: this.adapter.getData(this.state),\n component: this.adapter.component,\n params: this.adapter.getParams?.(this.manifest),\n streamController: this,\n };\n }\n\n attach(map: StreamingPhysicsMap): void {\n this.attachedMap = map;\n for (const chunk of this.chunks.values()) {\n map.replaceStreamedStaticHitboxes(chunk.key, chunk.hitboxes);\n }\n this.updatePredictionBoundary();\n }\n\n detach(): void {\n if (!this.attachedMap) return;\n for (const key of this.chunks.keys()) {\n this.attachedMap.clearStreamedStaticHitboxes(key);\n }\n this.attachedMap.clearStreamedStaticHitboxes(\"__boundary__\");\n this.attachedMap = undefined;\n }\n\n private publish(): void {\n if (!this.attachedMap) return;\n const current = this.attachedMap.data();\n if (current) {\n this.attachedMap.data.set({ ...current, data: this.adapter.getData(this.state) });\n }\n this.updatePredictionBoundary();\n }\n\n private updatePredictionBoundary(): void {\n const map = this.attachedMap;\n if (!map) return;\n if (this.chunks.size === 0) {\n map.clearStreamedStaticHitboxes(\"__boundary__\");\n return;\n }\n const chunks = [...this.chunks.values()];\n const left = Math.min(...chunks.map((chunk) => chunk.bounds.x));\n const top = Math.min(...chunks.map((chunk) => chunk.bounds.y));\n const right = Math.max(...chunks.map((chunk) => chunk.bounds.x + chunk.bounds.width));\n const bottom = Math.max(...chunks.map((chunk) => chunk.bounds.y + chunk.bounds.height));\n const thickness = 2;\n const barriers: MapChunkHitbox[] = [];\n if (left > 0) barriers.push({ x: left - thickness, y: top, width: thickness, height: bottom - top });\n if (top > 0) barriers.push({ x: left, y: top - thickness, width: right - left, height: thickness });\n if (right < this.manifest.width) barriers.push({ x: right, y: top, width: thickness, height: bottom - top });\n if (bottom < this.manifest.height) barriers.push({ x: left, y: bottom, width: right - left, height: thickness });\n map.replaceStreamedStaticHitboxes(\"__boundary__\", barriers);\n }\n}\n\nclass MapStreamingClientService<TManifestData, TChunkData, TState> {\n private readonly controllers = new Map<string, MapStreamClientController<TManifestData, TChunkData, TState>>();\n private readonly waiters = new Map<string, Waiter[]>();\n\n constructor(\n private readonly socket: AbstractWebsocket,\n private readonly options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>,\n ) {\n socket.on(MAP_STREAM_EVENT, (packet) => this.receive(packet));\n }\n\n async load(mapId: string): Promise<MapData> {\n const normalizedId = mapId.replace(/^map-/, \"\");\n const existing = this.controllers.get(normalizedId);\n if (existing) {\n this.socket.emit(MAP_STREAM_REQUEST_EVENT, { mapId: normalizedId });\n return existing.toMapData();\n }\n\n const shouldRequest = !this.waiters.has(normalizedId);\n const controllerPromise = new Promise<MapStreamClientController<TManifestData, TChunkData, TState>>((resolve, reject) => {\n const timeoutMs = Math.max(1, this.options.timeoutMs ?? 10_000);\n const waiter: Waiter = { resolve, reject, timer: undefined as unknown as ReturnType<typeof setTimeout> };\n const timer = setTimeout(() => {\n const current = this.waiters.get(normalizedId) ?? [];\n const remaining = current.filter((entry) => entry !== waiter);\n if (remaining.length > 0) this.waiters.set(normalizedId, remaining);\n else this.waiters.delete(normalizedId);\n reject(new Error(`Map stream '${normalizedId}' was not received after ${timeoutMs}ms`));\n }, timeoutMs);\n waiter.timer = timer;\n const waiters = this.waiters.get(normalizedId) ?? [];\n waiters.push(waiter);\n this.waiters.set(normalizedId, waiters);\n });\n // The map-room connection is established before load() runs. Request the\n // initial packet only after the waiter is registered so fast local and DO\n // transports cannot deliver it before the client is ready.\n if (shouldRequest) {\n this.socket.emit(MAP_STREAM_REQUEST_EVENT, { mapId: normalizedId });\n }\n return (await controllerPromise).toMapData();\n }\n\n private receive(packet: MapStreamPacket<TManifestData, TChunkData>): void {\n if (!packet || typeof packet.mapId !== \"string\") return;\n const mapId = packet.mapId.replace(/^map-/, \"\");\n let controller = this.controllers.get(mapId);\n if (!controller) {\n if (!packet.manifest) return;\n controller = new MapStreamClientController(this.options.adapter, packet.manifest);\n this.controllers.set(mapId, controller);\n }\n else if (packet.manifest && controller.revision !== packet.manifest.revision) {\n controller.reset(packet.manifest);\n }\n controller.receive(packet);\n\n const waiters = this.waiters.get(mapId) ?? [];\n waiters.forEach((waiter) => {\n clearTimeout(waiter.timer);\n waiter.resolve(controller!);\n });\n this.waiters.delete(mapId);\n }\n}\n\nclass MapStreamingLoadMapService<TManifestData, TChunkData, TState> {\n private socket?: AbstractWebsocket;\n private stream?: MapStreamingClientService<TManifestData, TChunkData, TState>;\n private updateMap?: UpdateMapService;\n\n constructor(\n private readonly context: Context,\n private readonly options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>,\n ) {}\n\n initialize(): void {\n if (this.stream) return;\n this.socket = inject<AbstractWebsocket>(this.context, WebSocketToken);\n this.stream = new MapStreamingClientService(this.socket, this.options);\n }\n\n async load(mapId: string): Promise<MapData> {\n this.initialize();\n const map = this.socket?.mode === \"standalone\" && this.options.directLoad\n ? await this.options.directLoad(mapId.replace(/^map-/, \"\"))\n : await this.stream!.load(mapId);\n this.updateMap ??= inject<UpdateMapService>(this.context, UpdateMapToken);\n await this.updateMap.update(map);\n return map;\n }\n}\n\n/**\n * Provide a transport-neutral client map loader backed by authoritative chunks.\n * Standalone mode may keep a direct loader while MMORPG mode never fetches the\n * private source map.\n *\n * @param options - Client adapter, optional standalone loader, and timeout.\n * @returns Dependency-injection providers for the RPGJS map loader.\n *\n * @example\n * ```ts\n * provideClientMapStreaming({\n * adapter: {\n * component: MyMap,\n * createState: (manifest) => ({ manifest, chunks: new Map() }),\n * applyChunk: (state, chunk) => state.chunks.set(chunk.key, chunk.renderData),\n * removeChunk: (state, key) => state.chunks.delete(key),\n * getData: (state) => state,\n * },\n * })\n * ```\n */\nexport function provideClientMapStreaming<TManifestData, TChunkData, TState>(\n options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>,\n) {\n return [\n {\n provide: LoadMapToken,\n useFactory: (context: Context) => new MapStreamingLoadMapService(context, options),\n },\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mapStreaming.js","names":[],"sources":["../../src/services/mapStreaming.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport {\n MAP_STREAM_EVENT,\n MAP_STREAM_REQUEST_EVENT,\n type MapChunkHitbox,\n type MapStreamChunk,\n type MapStreamManifest,\n type MapStreamPacket,\n type RpgContext,\n type RpgProvider,\n} from \"@rpgjs/common\";\nimport { AbstractWebsocket, WebSocketToken } from \"./AbstractSocket\";\nimport { LoadMapToken, type MapData, type LoadMapOptions } from \"./loadMap\";\nimport { UpdateMapService, UpdateMapToken } from \"@rpgjs/common\";\n\nexport interface ClientMapStreamingAdapter<TManifestData = unknown, TChunkData = unknown, TState = unknown> {\n /** CanvasEngine component that renders the provider-specific state. */\n component: unknown;\n /** Create empty client render state from public manifest data. */\n createState(manifest: MapStreamManifest<TManifestData>): TState;\n /** Apply one disclosed render chunk to the state. */\n applyChunk(state: TState, chunk: MapStreamChunk<TChunkData>): void;\n /** Remove one chunk that left the retention window. */\n removeChunk(state: TState, key: string): void;\n /** Return the serializable/component-ready map value exposed to CanvasEngine. */\n getData(state: TState): unknown;\n /** Optionally derive component parameters from the public manifest. */\n getParams?(manifest: MapStreamManifest<TManifestData>): Record<string, unknown>;\n}\n\nexport interface ClientMapStreamingOptions<TManifestData = unknown, TChunkData = unknown, TState = unknown> {\n /** Format adapter paired with the server compiler. */\n adapter: ClientMapStreamingAdapter<TManifestData, TChunkData, TState>;\n /** Optional direct loader used only by standalone RPG mode. */\n directLoad?: LoadMapOptions;\n /** Maximum wait for the initial authoritative manifest. Defaults to 10 seconds. */\n timeoutMs?: number;\n}\n\ntype StreamingPhysicsMap = {\n data: { (): MapData | null; set(value: MapData): void };\n replaceStreamedStaticHitboxes(namespace: string, hitboxes: MapChunkHitbox[]): void;\n clearStreamedStaticHitboxes(namespace: string): void;\n};\n\ntype Waiter = {\n resolve(controller: MapStreamClientController<any, any, any>): void;\n reject(error: Error): void;\n timer: ReturnType<typeof setTimeout>;\n};\n\nexport class MapStreamClientController<TManifestData, TChunkData, TState> {\n private state: TState;\n private manifest: MapStreamManifest<TManifestData>;\n private readonly chunks = new Map<string, MapStreamChunk<TChunkData>>();\n private attachedMap?: StreamingPhysicsMap;\n\n constructor(\n private readonly adapter: ClientMapStreamingAdapter<TManifestData, TChunkData, TState>,\n manifest: MapStreamManifest<TManifestData>,\n ) {\n this.manifest = manifest;\n this.state = adapter.createState(manifest);\n }\n\n get revision(): string {\n return this.manifest.revision;\n }\n\n reset(manifest: MapStreamManifest<TManifestData>): void {\n const attachedMap = this.attachedMap;\n this.detach();\n this.manifest = manifest;\n this.state = this.adapter.createState(manifest);\n this.chunks.clear();\n this.attachedMap = attachedMap;\n }\n\n receive(packet: MapStreamPacket<TManifestData, TChunkData>): void {\n for (const key of packet.removed) {\n this.chunks.delete(key);\n this.adapter.removeChunk(this.state, key);\n this.attachedMap?.clearStreamedStaticHitboxes(key);\n }\n for (const chunk of packet.chunks) {\n this.chunks.set(chunk.key, chunk);\n this.adapter.applyChunk(this.state, chunk);\n this.attachedMap?.replaceStreamedStaticHitboxes(chunk.key, chunk.hitboxes);\n }\n this.publish();\n }\n\n toMapData(): MapData {\n return {\n id: this.manifest.mapId,\n width: this.manifest.width,\n height: this.manifest.height,\n data: this.adapter.getData(this.state),\n component: this.adapter.component,\n params: this.adapter.getParams?.(this.manifest),\n streamController: this,\n };\n }\n\n attach(map: StreamingPhysicsMap): void {\n this.attachedMap = map;\n for (const chunk of this.chunks.values()) {\n map.replaceStreamedStaticHitboxes(chunk.key, chunk.hitboxes);\n }\n this.updatePredictionBoundary();\n }\n\n detach(): void {\n if (!this.attachedMap) return;\n for (const key of this.chunks.keys()) {\n this.attachedMap.clearStreamedStaticHitboxes(key);\n }\n this.attachedMap.clearStreamedStaticHitboxes(\"__boundary__\");\n this.attachedMap = undefined;\n }\n\n private publish(): void {\n if (!this.attachedMap) return;\n const current = this.attachedMap.data();\n if (current) {\n this.attachedMap.data.set({ ...current, data: this.adapter.getData(this.state) });\n }\n this.updatePredictionBoundary();\n }\n\n private updatePredictionBoundary(): void {\n const map = this.attachedMap;\n if (!map) return;\n if (this.chunks.size === 0) {\n map.clearStreamedStaticHitboxes(\"__boundary__\");\n return;\n }\n const chunks = [...this.chunks.values()];\n const left = Math.min(...chunks.map((chunk) => chunk.bounds.x));\n const top = Math.min(...chunks.map((chunk) => chunk.bounds.y));\n const right = Math.max(...chunks.map((chunk) => chunk.bounds.x + chunk.bounds.width));\n const bottom = Math.max(...chunks.map((chunk) => chunk.bounds.y + chunk.bounds.height));\n const thickness = 2;\n const barriers: MapChunkHitbox[] = [];\n if (left > 0) barriers.push({ x: left - thickness, y: top, width: thickness, height: bottom - top });\n if (top > 0) barriers.push({ x: left, y: top - thickness, width: right - left, height: thickness });\n if (right < this.manifest.width) barriers.push({ x: right, y: top, width: thickness, height: bottom - top });\n if (bottom < this.manifest.height) barriers.push({ x: left, y: bottom, width: right - left, height: thickness });\n map.replaceStreamedStaticHitboxes(\"__boundary__\", barriers);\n }\n}\n\nclass MapStreamingClientService<TManifestData, TChunkData, TState> {\n private readonly controllers = new Map<string, MapStreamClientController<TManifestData, TChunkData, TState>>();\n private readonly waiters = new Map<string, Waiter[]>();\n\n constructor(\n private readonly socket: AbstractWebsocket,\n private readonly options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>,\n ) {\n socket.on(MAP_STREAM_EVENT, (packet) => this.receive(packet));\n }\n\n async load(mapId: string): Promise<MapData> {\n const normalizedId = mapId.replace(/^map-/, \"\");\n const existing = this.controllers.get(normalizedId);\n if (existing) {\n this.socket.emit(MAP_STREAM_REQUEST_EVENT, { mapId: normalizedId });\n return existing.toMapData();\n }\n\n const shouldRequest = !this.waiters.has(normalizedId);\n const controllerPromise = new Promise<MapStreamClientController<TManifestData, TChunkData, TState>>((resolve, reject) => {\n const timeoutMs = Math.max(1, this.options.timeoutMs ?? 10_000);\n const waiter: Waiter = { resolve, reject, timer: undefined as unknown as ReturnType<typeof setTimeout> };\n const timer = setTimeout(() => {\n const current = this.waiters.get(normalizedId) ?? [];\n const remaining = current.filter((entry) => entry !== waiter);\n if (remaining.length > 0) this.waiters.set(normalizedId, remaining);\n else this.waiters.delete(normalizedId);\n reject(new Error(`Map stream '${normalizedId}' was not received after ${timeoutMs}ms`));\n }, timeoutMs);\n waiter.timer = timer;\n const waiters = this.waiters.get(normalizedId) ?? [];\n waiters.push(waiter);\n this.waiters.set(normalizedId, waiters);\n });\n // The map-room connection is established before load() runs. Request the\n // initial packet only after the waiter is registered so fast local and DO\n // transports cannot deliver it before the client is ready.\n if (shouldRequest) {\n this.socket.emit(MAP_STREAM_REQUEST_EVENT, { mapId: normalizedId });\n }\n return (await controllerPromise).toMapData();\n }\n\n private receive(packet: MapStreamPacket<TManifestData, TChunkData>): void {\n if (!packet || typeof packet.mapId !== \"string\") return;\n const mapId = packet.mapId.replace(/^map-/, \"\");\n let controller = this.controllers.get(mapId);\n if (!controller) {\n if (!packet.manifest) return;\n controller = new MapStreamClientController(this.options.adapter, packet.manifest);\n this.controllers.set(mapId, controller);\n }\n else if (packet.manifest && controller.revision !== packet.manifest.revision) {\n controller.reset(packet.manifest);\n }\n controller.receive(packet);\n\n const waiters = this.waiters.get(mapId) ?? [];\n waiters.forEach((waiter) => {\n clearTimeout(waiter.timer);\n waiter.resolve(controller!);\n });\n this.waiters.delete(mapId);\n }\n}\n\nclass MapStreamingLoadMapService<TManifestData, TChunkData, TState> {\n private socket?: AbstractWebsocket;\n private stream?: MapStreamingClientService<TManifestData, TChunkData, TState>;\n private updateMap?: UpdateMapService;\n\n constructor(\n private readonly context: RpgContext,\n private readonly options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>,\n ) {}\n\n initialize(): void {\n if (this.stream) return;\n this.socket = inject<AbstractWebsocket>(this.context as Context, WebSocketToken);\n this.stream = new MapStreamingClientService(this.socket, this.options);\n }\n\n async load(mapId: string): Promise<MapData> {\n this.initialize();\n const map = this.socket?.mode === \"standalone\" && this.options.directLoad\n ? await this.options.directLoad(mapId.replace(/^map-/, \"\"))\n : await this.stream!.load(mapId);\n this.updateMap ??= inject<UpdateMapService>(this.context as Context, UpdateMapToken);\n await this.updateMap.update(map);\n return map;\n }\n}\n\n/**\n * Provide a transport-neutral client map loader backed by authoritative chunks.\n * Standalone mode may keep a direct loader while MMORPG mode never fetches the\n * private source map.\n *\n * @param options - Client adapter, optional standalone loader, and timeout.\n * @returns Dependency-injection providers for the RPGJS map loader.\n *\n * @example\n * ```ts\n * provideClientMapStreaming({\n * adapter: {\n * component: MyMap,\n * createState: (manifest) => ({ manifest, chunks: new Map() }),\n * applyChunk: (state, chunk) => state.chunks.set(chunk.key, chunk.renderData),\n * removeChunk: (state, key) => state.chunks.delete(key),\n * getData: (state) => state,\n * },\n * })\n * ```\n */\nexport function provideClientMapStreaming<TManifestData, TChunkData, TState>(\n options: ClientMapStreamingOptions<TManifestData, TChunkData, TState>,\n): RpgProvider[] {\n return [\n {\n provide: LoadMapToken,\n useFactory: (context: RpgContext) => new MapStreamingLoadMapService(context, options),\n },\n ];\n}\n"],"mappings":"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"}
@@ -45,6 +45,6 @@ function waitForRpgjsConnected(conn, timeoutMs = 1e4, options = {}) {
45
45
  });
46
46
  }
47
47
  //#endregion
48
- export { isNativeSocketEvent, waitForRpgjsConnected };
48
+ export { isNativeSocketEvent, parseSocketMessage, waitForRpgjsConnected };
49
49
 
50
50
  //# sourceMappingURL=mmorpg-connection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mmorpg-connection.js","names":[],"sources":["../../src/services/mmorpg-connection.ts"],"sourcesContent":["export function parseSocketMessage(data: any) {\n if (typeof data !== \"string\") {\n return data;\n }\n try {\n return JSON.parse(data);\n }\n catch {\n return undefined;\n }\n}\n\nexport function isNativeSocketEvent(event: string) {\n return event === \"open\" || event === \"close\" || event === \"error\";\n}\n\nexport function waitForRpgjsConnected(\n conn: any,\n timeoutMs = 10000,\n options: { ignoreCleanClose?: boolean } = {},\n): Promise<void> {\n return new Promise((resolve, reject) => {\n let timeoutId: number | undefined;\n\n const cleanup = () => {\n conn.removeEventListener(\"message\", onMessage);\n conn.removeEventListener(\"close\", onClose);\n conn.removeEventListener(\"error\", onError);\n if (timeoutId !== undefined) {\n window.clearTimeout(timeoutId);\n }\n };\n const rejectWith = (error: Error) => {\n cleanup();\n reject(error);\n };\n const onMessage = (event: MessageEvent) => {\n const data = parseSocketMessage(event.data);\n if (data?.type !== \"connected\") {\n return;\n }\n cleanup();\n resolve();\n };\n const onClose = (event: CloseEvent) => {\n const rawReason: unknown = (event as any).reason;\n if (\n options.ignoreCleanClose\n && (event.code === 1000 || rawReason instanceof Event || typeof rawReason !== \"string\")\n ) {\n return;\n }\n const reason = typeof rawReason === \"string\" && rawReason\n ? rawReason\n : \"WebSocket closed before RPGJS connection was accepted\";\n rejectWith(new Error(reason));\n };\n const onError = (event: ErrorEvent) => {\n rejectWith(new Error(event.message || \"WebSocket connection failed\"));\n };\n\n conn.addEventListener(\"message\", onMessage);\n conn.addEventListener(\"close\", onClose);\n conn.addEventListener(\"error\", onError);\n timeoutId = window.setTimeout(() => {\n rejectWith(new Error(\"RPGJS connection timeout\"));\n }, timeoutMs);\n });\n}\n"],"mappings":";AAAA,SAAgB,mBAAmB,MAAW;CAC5C,IAAI,OAAO,SAAS,UAClB,OAAO;CAET,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QACM;EACJ;CACF;AACF;AAEA,SAAgB,oBAAoB,OAAe;CACjD,OAAO,UAAU,UAAU,UAAU,WAAW,UAAU;AAC5D;AAEA,SAAgB,sBACd,MACA,YAAY,KACZ,UAA0C,CAAC,GAC5B;CACf,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI;EAEJ,MAAM,gBAAgB;GACpB,KAAK,oBAAoB,WAAW,SAAS;GAC7C,KAAK,oBAAoB,SAAS,OAAO;GACzC,KAAK,oBAAoB,SAAS,OAAO;GACzC,IAAI,cAAc,KAAA,GAChB,OAAO,aAAa,SAAS;EAEjC;EACA,MAAM,cAAc,UAAiB;GACnC,QAAQ;GACR,OAAO,KAAK;EACd;EACA,MAAM,aAAa,UAAwB;GAEzC,IADa,mBAAmB,MAAM,IAClC,GAAM,SAAS,aACjB;GAEF,QAAQ;GACR,QAAQ;EACV;EACA,MAAM,WAAW,UAAsB;GACrC,MAAM,YAAsB,MAAc;GAC1C,IACE,QAAQ,qBACJ,MAAM,SAAS,OAAQ,qBAAqB,SAAS,OAAO,cAAc,WAE9E;GAKF,WAAW,IAAI,MAHA,OAAO,cAAc,YAAY,YAC5C,YACA,uDACuB,CAAC;EAC9B;EACA,MAAM,WAAW,UAAsB;GACrC,WAAW,IAAI,MAAM,MAAM,WAAW,6BAA6B,CAAC;EACtE;EAEA,KAAK,iBAAiB,WAAW,SAAS;EAC1C,KAAK,iBAAiB,SAAS,OAAO;EACtC,KAAK,iBAAiB,SAAS,OAAO;EACtC,YAAY,OAAO,iBAAiB;GAClC,2BAAW,IAAI,MAAM,0BAA0B,CAAC;EAClD,GAAG,SAAS;CACd,CAAC;AACH"}
1
+ {"version":3,"file":"mmorpg-connection.js","names":[],"sources":["../../src/services/mmorpg-connection.ts"],"sourcesContent":["export function parseSocketMessage(data: any) {\n if (typeof data !== \"string\") {\n return data;\n }\n try {\n return JSON.parse(data);\n }\n catch {\n return undefined;\n }\n}\n\nexport function isNativeSocketEvent(event: string) {\n return event === \"open\" || event === \"close\" || event === \"error\";\n}\n\nexport function waitForRpgjsConnected(\n conn: any,\n timeoutMs = 10000,\n options: { ignoreCleanClose?: boolean } = {},\n): Promise<void> {\n return new Promise((resolve, reject) => {\n let timeoutId: number | undefined;\n\n const cleanup = () => {\n conn.removeEventListener(\"message\", onMessage);\n conn.removeEventListener(\"close\", onClose);\n conn.removeEventListener(\"error\", onError);\n if (timeoutId !== undefined) {\n window.clearTimeout(timeoutId);\n }\n };\n const rejectWith = (error: Error) => {\n cleanup();\n reject(error);\n };\n const onMessage = (event: MessageEvent) => {\n const data = parseSocketMessage(event.data);\n if (data?.type !== \"connected\") {\n return;\n }\n cleanup();\n resolve();\n };\n const onClose = (event: CloseEvent) => {\n const rawReason: unknown = (event as any).reason;\n if (\n options.ignoreCleanClose\n && (event.code === 1000 || rawReason instanceof Event || typeof rawReason !== \"string\")\n ) {\n return;\n }\n const reason = typeof rawReason === \"string\" && rawReason\n ? rawReason\n : \"WebSocket closed before RPGJS connection was accepted\";\n rejectWith(new Error(reason));\n };\n const onError = (event: ErrorEvent) => {\n rejectWith(new Error(event.message || \"WebSocket connection failed\"));\n };\n\n conn.addEventListener(\"message\", onMessage);\n conn.addEventListener(\"close\", onClose);\n conn.addEventListener(\"error\", onError);\n timeoutId = window.setTimeout(() => {\n rejectWith(new Error(\"RPGJS connection timeout\"));\n }, timeoutMs);\n });\n}\n"],"mappings":";AAAA,SAAgB,mBAAmB,MAAW;CAC5C,IAAI,OAAO,SAAS,UAClB,OAAO;CAET,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QACM;EACJ;CACF;AACF;AAEA,SAAgB,oBAAoB,OAAe;CACjD,OAAO,UAAU,UAAU,UAAU,WAAW,UAAU;AAC5D;AAEA,SAAgB,sBACd,MACA,YAAY,KACZ,UAA0C,CAAC,GAC5B;CACf,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI;EAEJ,MAAM,gBAAgB;GACpB,KAAK,oBAAoB,WAAW,SAAS;GAC7C,KAAK,oBAAoB,SAAS,OAAO;GACzC,KAAK,oBAAoB,SAAS,OAAO;GACzC,IAAI,cAAc,KAAA,GAChB,OAAO,aAAa,SAAS;EAEjC;EACA,MAAM,cAAc,UAAiB;GACnC,QAAQ;GACR,OAAO,KAAK;EACd;EACA,MAAM,aAAa,UAAwB;GAEzC,IADa,mBAAmB,MAAM,IAClC,CAAA,EAAM,SAAS,aACjB;GAEF,QAAQ;GACR,QAAQ;EACV;EACA,MAAM,WAAW,UAAsB;GACrC,MAAM,YAAsB,MAAc;GAC1C,IACE,QAAQ,qBACJ,MAAM,SAAS,OAAQ,qBAAqB,SAAS,OAAO,cAAc,WAE9E;GAKF,WAAW,IAAI,MAHA,OAAO,cAAc,YAAY,YAC5C,YACA,uDACuB,CAAC;EAC9B;EACA,MAAM,WAAW,UAAsB;GACrC,WAAW,IAAI,MAAM,MAAM,WAAW,6BAA6B,CAAC;EACtE;EAEA,KAAK,iBAAiB,WAAW,SAAS;EAC1C,KAAK,iBAAiB,SAAS,OAAO;EACtC,KAAK,iBAAiB,SAAS,OAAO;EACtC,YAAY,OAAO,iBAAiB;GAClC,2BAAW,IAAI,MAAM,0BAA0B,CAAC;EAClD,GAAG,SAAS;CACd,CAAC;AACH"}
@@ -1,8 +1,5 @@
1
- import { Context } from '@signe/di';
2
- import { RpgGui } from '../Gui/Gui';
3
- import { RpgClientEngine } from '../RpgClientEngine';
4
1
  import { AbstractWebsocket, SocketQuery, SocketUpdateProperties } from './AbstractSocket';
5
- import { UpdateMapService } from '@rpgjs/common';
2
+ import { RpgContext, RpgProvider } from '@rpgjs/common';
6
3
  export interface MmorpgOptions {
7
4
  host?: string;
8
5
  connectionId?: string;
@@ -17,7 +14,7 @@ export interface MmorpgOptions {
17
14
  connectionAcceptanceTimeoutMs?: number;
18
15
  }
19
16
  export declare class BridgeWebsocket extends AbstractWebsocket {
20
- protected context: Context;
17
+ protected context: RpgContext;
21
18
  private options;
22
19
  readonly mode: "mmorpg";
23
20
  private socket;
@@ -25,7 +22,7 @@ export declare class BridgeWebsocket extends AbstractWebsocket {
25
22
  private pendingOn;
26
23
  private acceptedOpenListeners;
27
24
  private targetRoom;
28
- constructor(context: Context, options?: MmorpgOptions);
25
+ constructor(context: RpgContext, options?: MmorpgOptions);
29
26
  private resolveConnectionId;
30
27
  private resolveQuery;
31
28
  connection(listeners?: (data: any) => void): Promise<void>;
@@ -39,23 +36,4 @@ export declare class BridgeWebsocket extends AbstractWebsocket {
39
36
  reconnect(_listeners?: (data: any) => void): Promise<void>;
40
37
  getCurrentRoom(): string;
41
38
  }
42
- declare class UpdateMapStandaloneService extends UpdateMapService {
43
- protected context: Context;
44
- private _options;
45
- constructor(context: Context, _options: MmorpgOptions);
46
- update(_map: any): Promise<void>;
47
- }
48
- export declare function provideMmorpg(options: MmorpgOptions): (typeof RpgClientEngine | typeof RpgGui | {
49
- provide: typeof import('./save').SaveClientService;
50
- useClass: typeof import('./save').SaveClientService;
51
- } | {
52
- provide: string;
53
- useValue: null;
54
- } | {
55
- provide: string;
56
- useFactory: (context: Context) => BridgeWebsocket;
57
- } | {
58
- provide: string;
59
- useFactory: (context: Context) => UpdateMapStandaloneService;
60
- })[];
61
- export {};
39
+ export declare function provideMmorpg(options: MmorpgOptions): RpgProvider[];
@@ -1 +1 @@
1
- {"version":3,"file":"mmorpg.js","names":[],"sources":["../../src/services/mmorpg.ts"],"sourcesContent":["import { Context } from \"@signe/di\";\nimport { connectionRoom } from \"@signe/sync/client\";\nimport { RpgGui } from \"../Gui/Gui\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { AbstractWebsocket, SocketQuery, SocketUpdateProperties, WebSocketToken } from \"./AbstractSocket\";\nimport { UpdateMapService, UpdateMapToken } from \"@rpgjs/common\";\nimport { provideKeyboardControls } from \"./keyboardControls\";\nimport { provideSaveClient } from \"./save\";\nimport { isNativeSocketEvent, waitForRpgjsConnected } from \"./mmorpg-connection\";\n\nexport interface MmorpgOptions {\n host?: string;\n connectionId?: string;\n connectionIdScope?: \"local\" | \"session\" | \"ephemeral\";\n query?: SocketQuery | (() => SocketQuery | undefined);\n socketOptions?: Record<string, any>;\n /**\n * Time allowed for a room to restore and send the RPGJS acceptance packet.\n * Increase this for cold edge rooms or local Durable Object startup.\n * Defaults to 10 seconds.\n */\n connectionAcceptanceTimeoutMs?: number;\n}\n\nexport class BridgeWebsocket extends AbstractWebsocket {\n readonly mode = \"mmorpg\" as const;\n\n private socket: any;\n private privateId: string;\n private pendingOn: Array<{ event: string; callback: (data: any) => void }> = [];\n private acceptedOpenListeners = new Set<(data: any) => void>();\n private targetRoom = \"lobby-1\";\n\n constructor(protected context: Context, private options: MmorpgOptions = {}) {\n super(context);\n this.privateId = this.resolveConnectionId();\n }\n\n private resolveConnectionId(): string {\n if (this.options.connectionId) {\n return this.options.connectionId;\n }\n\n const scope = this.options.connectionIdScope ?? \"local\";\n const key = \"rpgjs-user-id\";\n\n if (scope === \"ephemeral\") {\n return crypto.randomUUID();\n }\n\n const storage =\n scope === \"session\"\n ? window.sessionStorage\n : window.localStorage;\n\n const existing = storage.getItem(key);\n if (existing) {\n return existing;\n }\n\n const id = crypto.randomUUID();\n storage.setItem(key, id);\n return id;\n }\n\n private resolveQuery(): SocketQuery {\n const query = typeof this.options.query === \"function\"\n ? this.options.query()\n : this.options.query;\n\n return query ?? {};\n }\n\n async connection(listeners?: (data: any) => void) {\n // tmp\n class Room {\n \n }\n const instance = new Room()\n const host = this.options.host || window.location.host;\n this.socket = await connectionRoom({\n maxRetries: 0,\n ...this.options.socketOptions,\n host,\n room: this.targetRoom,\n id: this.privateId,\n query: {\n ...this.resolveQuery(),\n id: this.privateId,\n },\n }, instance)\n\n const pendingOn = this.pendingOn;\n this.pendingOn = [];\n pendingOn\n .filter(({ event }) => !this.isNativeSocketEvent(event))\n .forEach(({ event, callback }) => this.attachEvent(event, callback));\n await waitForRpgjsConnected(\n this.socket.conn,\n this.options.connectionAcceptanceTimeoutMs ?? 10_000,\n );\n pendingOn\n .filter(({ event }) => this.isNativeSocketEvent(event))\n .forEach(({ event, callback }) => this.attachEvent(event, callback));\n this.emitAcceptedOpen();\n listeners?.(this.socket)\n }\n\n on(key: string, callback: (data: any) => void) {\n if (!this.socket) {\n this.pendingOn.push({ event: key, callback });\n return;\n }\n this.attachEvent(key, callback);\n }\n\n off(event: string, callback: (data: any) => void) {\n if (!this.socket) return;\n if (event === \"open\") {\n this.acceptedOpenListeners.delete(callback);\n return;\n }\n if (this.isNativeSocketEvent(event)) {\n this.socket.conn.removeEventListener(event, callback);\n return;\n }\n this.socket.off(event, callback);\n }\n\n emit(event: string, data: any) {\n this.socket.emit(event, data);\n }\n\n private attachEvent(event: string, callback: (data: any) => void) {\n if (event === \"open\") {\n this.acceptedOpenListeners.add(callback);\n return;\n }\n if (this.isNativeSocketEvent(event)) {\n this.socket.conn.addEventListener(event, callback);\n return;\n }\n this.socket.on(event, callback);\n }\n\n private emitAcceptedOpen() {\n const event = new Event(\"open\");\n this.acceptedOpenListeners.forEach((callback) => callback(event));\n }\n\n updateProperties({ room, host, query }: SocketUpdateProperties) {\n if (!this.socket?.conn) return;\n this.targetRoom = room;\n this.socket.conn.updateProperties({\n room,\n id: this.privateId,\n host: host || this.options.host || window.location.host,\n query: {\n ...this.resolveQuery(),\n ...query,\n id: this.privateId,\n },\n })\n }\n\n private isNativeSocketEvent(event: string) {\n return isNativeSocketEvent(event);\n }\n\n async reconnect(_listeners?: (data: any) => void): Promise<void> {\n if (!this.socket?.conn) return;\n const conn = this.socket.conn;\n const connected = waitForRpgjsConnected(\n conn,\n this.options.connectionAcceptanceTimeoutMs ?? 10_000,\n { ignoreCleanClose: true },\n );\n conn.reconnect();\n await connected;\n this.emitAcceptedOpen();\n }\n\n getCurrentRoom(): string {\n return this.targetRoom || this.socket?.conn?.room || \"lobby-1\";\n }\n}\n\nclass UpdateMapStandaloneService extends UpdateMapService {\n constructor(protected context: Context, private _options: MmorpgOptions) {\n super(context);\n }\n\n async update(_map: any) {\n // In MMORPG mode, clients are untrusted and must not push map definitions.\n // Map bootstrap/update is handled server-side by @rpgjs/vite.\n return;\n }\n}\n\nexport function provideMmorpg(options: MmorpgOptions) {\n return [\n {\n provide: WebSocketToken,\n useFactory: (context: Context) => new BridgeWebsocket(context, options),\n },\n {\n provide: UpdateMapToken,\n useFactory: (context: Context) => new UpdateMapStandaloneService(context, options),\n },\n provideKeyboardControls(),\n provideSaveClient(),\n RpgGui,\n RpgClientEngine,\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mmorpg.js","names":[],"sources":["../../src/services/mmorpg.ts"],"sourcesContent":["import { connectionRoom } from \"@signe/sync/client\";\nimport { RpgGui } from \"../Gui/Gui\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { AbstractWebsocket, SocketQuery, SocketUpdateProperties, WebSocketToken } from \"./AbstractSocket\";\nimport { UpdateMapService, UpdateMapToken, type RpgContext, type RpgProvider } from \"@rpgjs/common\";\nimport { provideKeyboardControls } from \"./keyboardControls\";\nimport { provideSaveClient } from \"./save\";\nimport { isNativeSocketEvent, waitForRpgjsConnected } from \"./mmorpg-connection\";\n\nexport interface MmorpgOptions {\n host?: string;\n connectionId?: string;\n connectionIdScope?: \"local\" | \"session\" | \"ephemeral\";\n query?: SocketQuery | (() => SocketQuery | undefined);\n socketOptions?: Record<string, any>;\n /**\n * Time allowed for a room to restore and send the RPGJS acceptance packet.\n * Increase this for cold edge rooms or local Durable Object startup.\n * Defaults to 10 seconds.\n */\n connectionAcceptanceTimeoutMs?: number;\n}\n\nexport class BridgeWebsocket extends AbstractWebsocket {\n readonly mode = \"mmorpg\" as const;\n\n private socket: any;\n private privateId: string;\n private pendingOn: Array<{ event: string; callback: (data: any) => void }> = [];\n private acceptedOpenListeners = new Set<(data: any) => void>();\n private targetRoom = \"lobby-1\";\n\n constructor(protected context: RpgContext, private options: MmorpgOptions = {}) {\n super(context);\n this.privateId = this.resolveConnectionId();\n }\n\n private resolveConnectionId(): string {\n if (this.options.connectionId) {\n return this.options.connectionId;\n }\n\n const scope = this.options.connectionIdScope ?? \"local\";\n const key = \"rpgjs-user-id\";\n\n if (scope === \"ephemeral\") {\n return crypto.randomUUID();\n }\n\n const storage =\n scope === \"session\"\n ? window.sessionStorage\n : window.localStorage;\n\n const existing = storage.getItem(key);\n if (existing) {\n return existing;\n }\n\n const id = crypto.randomUUID();\n storage.setItem(key, id);\n return id;\n }\n\n private resolveQuery(): SocketQuery {\n const query = typeof this.options.query === \"function\"\n ? this.options.query()\n : this.options.query;\n\n return query ?? {};\n }\n\n async connection(listeners?: (data: any) => void) {\n // tmp\n class Room {\n \n }\n const instance = new Room()\n const host = this.options.host || window.location.host;\n this.socket = await connectionRoom({\n maxRetries: 0,\n ...this.options.socketOptions,\n host,\n room: this.targetRoom,\n id: this.privateId,\n query: {\n ...this.resolveQuery(),\n id: this.privateId,\n },\n }, instance)\n\n const pendingOn = this.pendingOn;\n this.pendingOn = [];\n pendingOn\n .filter(({ event }) => !this.isNativeSocketEvent(event))\n .forEach(({ event, callback }) => this.attachEvent(event, callback));\n await waitForRpgjsConnected(\n this.socket.conn,\n this.options.connectionAcceptanceTimeoutMs ?? 10_000,\n );\n pendingOn\n .filter(({ event }) => this.isNativeSocketEvent(event))\n .forEach(({ event, callback }) => this.attachEvent(event, callback));\n this.emitAcceptedOpen();\n listeners?.(this.socket)\n }\n\n on(key: string, callback: (data: any) => void) {\n if (!this.socket) {\n this.pendingOn.push({ event: key, callback });\n return;\n }\n this.attachEvent(key, callback);\n }\n\n off(event: string, callback: (data: any) => void) {\n if (!this.socket) return;\n if (event === \"open\") {\n this.acceptedOpenListeners.delete(callback);\n return;\n }\n if (this.isNativeSocketEvent(event)) {\n this.socket.conn.removeEventListener(event, callback);\n return;\n }\n this.socket.off(event, callback);\n }\n\n emit(event: string, data: any) {\n this.socket.emit(event, data);\n }\n\n private attachEvent(event: string, callback: (data: any) => void) {\n if (event === \"open\") {\n this.acceptedOpenListeners.add(callback);\n return;\n }\n if (this.isNativeSocketEvent(event)) {\n this.socket.conn.addEventListener(event, callback);\n return;\n }\n this.socket.on(event, callback);\n }\n\n private emitAcceptedOpen() {\n const event = new Event(\"open\");\n this.acceptedOpenListeners.forEach((callback) => callback(event));\n }\n\n updateProperties({ room, host, query }: SocketUpdateProperties) {\n if (!this.socket?.conn) return;\n this.targetRoom = room;\n this.socket.conn.updateProperties({\n room,\n id: this.privateId,\n host: host || this.options.host || window.location.host,\n query: {\n ...this.resolveQuery(),\n ...query,\n id: this.privateId,\n },\n })\n }\n\n private isNativeSocketEvent(event: string) {\n return isNativeSocketEvent(event);\n }\n\n async reconnect(_listeners?: (data: any) => void): Promise<void> {\n if (!this.socket?.conn) return;\n const conn = this.socket.conn;\n const connected = waitForRpgjsConnected(\n conn,\n this.options.connectionAcceptanceTimeoutMs ?? 10_000,\n { ignoreCleanClose: true },\n );\n conn.reconnect();\n await connected;\n this.emitAcceptedOpen();\n }\n\n getCurrentRoom(): string {\n return this.targetRoom || this.socket?.conn?.room || \"lobby-1\";\n }\n}\n\nclass UpdateMapStandaloneService extends UpdateMapService {\n constructor(protected context: RpgContext, private _options: MmorpgOptions) {\n super(context);\n }\n\n async update(_map: any) {\n // In MMORPG mode, clients are untrusted and must not push map definitions.\n // Map bootstrap/update is handled server-side by @rpgjs/vite.\n return;\n }\n}\n\nexport function provideMmorpg(options: MmorpgOptions): RpgProvider[] {\n return [\n {\n provide: WebSocketToken,\n useFactory: (context: RpgContext) => new BridgeWebsocket(context, options),\n },\n {\n provide: UpdateMapToken,\n useFactory: (context: RpgContext) => new UpdateMapStandaloneService(context, options),\n },\n provideKeyboardControls(),\n provideSaveClient(),\n RpgGui,\n RpgClientEngine,\n ];\n}\n"],"mappings":";;;;;;;;;AAuBA,IAAa,kBAAb,cAAqC,kBAAkB;CASrD,YAAY,SAA+B,UAAiC,CAAC,GAAG;EAC9E,MAAM,OAAO;EADO,KAAA,UAAA;EAA6B,KAAA,UAAA;cARnC;mBAI6D,CAAC;+CAC9C,IAAI,IAAyB;oBACxC;EAInB,KAAK,YAAY,KAAK,oBAAoB;CAC5C;CAEA,sBAAsC;EACpC,IAAI,KAAK,QAAQ,cACf,OAAO,KAAK,QAAQ;EAGtB,MAAM,QAAQ,KAAK,QAAQ,qBAAqB;EAChD,MAAM,MAAM;EAEZ,IAAI,UAAU,aACZ,OAAO,OAAO,WAAW;EAG3B,MAAM,UACJ,UAAU,YACN,OAAO,iBACP,OAAO;EAEb,MAAM,WAAW,QAAQ,QAAQ,GAAG;EACpC,IAAI,UACF,OAAO;EAGT,MAAM,KAAK,OAAO,WAAW;EAC7B,QAAQ,QAAQ,KAAK,EAAE;EACvB,OAAO;CACT;CAEA,eAAoC;EAKlC,QAJc,OAAO,KAAK,QAAQ,UAAU,aACxC,KAAK,QAAQ,MAAM,IACnB,KAAK,QAAQ,UAED,CAAC;CACnB;CAEA,MAAM,WAAW,WAAiC;EAEhD,MAAM,KAAK,CAEX;EACA,MAAM,WAAW,IAAI,KAAK;EAC1B,MAAM,OAAO,KAAK,QAAQ,QAAQ,OAAO,SAAS;EAClD,KAAK,SAAS,MAAM,eAAe;GAC/B,YAAY;GACZ,GAAG,KAAK,QAAQ;GAChB;GACA,MAAM,KAAK;GACX,IAAI,KAAK;GACT,OAAO;IACL,GAAG,KAAK,aAAa;IACrB,IAAI,KAAK;GACX;EACJ,GAAG,QAAQ;EAEX,MAAM,YAAY,KAAK;EACvB,KAAK,YAAY,CAAC;EAClB,UACG,QAAQ,EAAE,YAAY,CAAC,KAAK,oBAAoB,KAAK,CAAC,CAAC,CACvD,SAAS,EAAE,OAAO,eAAe,KAAK,YAAY,OAAO,QAAQ,CAAC;EACrE,MAAM,sBACJ,KAAK,OAAO,MACZ,KAAK,QAAQ,iCAAiC,GAChD;EACA,UACG,QAAQ,EAAE,YAAY,KAAK,oBAAoB,KAAK,CAAC,CAAC,CACtD,SAAS,EAAE,OAAO,eAAe,KAAK,YAAY,OAAO,QAAQ,CAAC;EACrE,KAAK,iBAAiB;EACtB,YAAY,KAAK,MAAM;CACzB;CAEA,GAAG,KAAa,UAA+B;EAC7C,IAAI,CAAC,KAAK,QAAQ;GAChB,KAAK,UAAU,KAAK;IAAE,OAAO;IAAK;GAAS,CAAC;GAC5C;EACF;EACA,KAAK,YAAY,KAAK,QAAQ;CAChC;CAEA,IAAI,OAAe,UAA+B;EAChD,IAAI,CAAC,KAAK,QAAQ;EAClB,IAAI,UAAU,QAAQ;GACpB,KAAK,sBAAsB,OAAO,QAAQ;GAC1C;EACF;EACA,IAAI,KAAK,oBAAoB,KAAK,GAAG;GACnC,KAAK,OAAO,KAAK,oBAAoB,OAAO,QAAQ;GACpD;EACF;EACA,KAAK,OAAO,IAAI,OAAO,QAAQ;CACjC;CAEA,KAAK,OAAe,MAAW;EAC7B,KAAK,OAAO,KAAK,OAAO,IAAI;CAC9B;CAEA,YAAoB,OAAe,UAA+B;EAChE,IAAI,UAAU,QAAQ;GACpB,KAAK,sBAAsB,IAAI,QAAQ;GACvC;EACF;EACA,IAAI,KAAK,oBAAoB,KAAK,GAAG;GACnC,KAAK,OAAO,KAAK,iBAAiB,OAAO,QAAQ;GACjD;EACF;EACA,KAAK,OAAO,GAAG,OAAO,QAAQ;CAChC;CAEA,mBAA2B;EACzB,MAAM,QAAQ,IAAI,MAAM,MAAM;EAC9B,KAAK,sBAAsB,SAAS,aAAa,SAAS,KAAK,CAAC;CAClE;CAEA,iBAAiB,EAAE,MAAM,MAAM,SAAiC;EAC9D,IAAI,CAAC,KAAK,QAAQ,MAAM;EACxB,KAAK,aAAa;EAClB,KAAK,OAAO,KAAK,iBAAiB;GAChC;GACA,IAAI,KAAK;GACT,MAAM,QAAQ,KAAK,QAAQ,QAAQ,OAAO,SAAS;GACnD,OAAO;IACL,GAAG,KAAK,aAAa;IACrB,GAAG;IACH,IAAI,KAAK;GACX;EACF,CAAC;CACH;CAEA,oBAA4B,OAAe;EACzC,OAAO,oBAAoB,KAAK;CAClC;CAEA,MAAM,UAAU,YAAiD;EAC/D,IAAI,CAAC,KAAK,QAAQ,MAAM;EACxB,MAAM,OAAO,KAAK,OAAO;EACzB,MAAM,YAAY,sBAChB,MACA,KAAK,QAAQ,iCAAiC,KAC9C,EAAE,kBAAkB,KAAK,CAC3B;EACA,KAAK,UAAU;EACf,MAAM;EACN,KAAK,iBAAiB;CACxB;CAEA,iBAAyB;EACvB,OAAO,KAAK,cAAc,KAAK,QAAQ,MAAM,QAAQ;CACvD;AACF;AAEA,IAAM,6BAAN,cAAyC,iBAAiB;CACxD,YAAY,SAA+B,UAAiC;EAC1E,MAAM,OAAO;EADO,KAAA,UAAA;EAA6B,KAAA,WAAA;CAEnD;CAEA,MAAM,OAAO,MAAW,CAIxB;AACF;AAEA,SAAgB,cAAc,SAAuC;CACnE,OAAO;EACL;GACE,SAAS;GACT,aAAa,YAAwB,IAAI,gBAAgB,SAAS,OAAO;EAC3E;EACA;GACE,SAAS;GACT,aAAa,YAAwB,IAAI,2BAA2B,SAAS,OAAO;EACtF;EACA,wBAAwB;EACxB,kBAAkB;EAClB;EACA;CACF;AACF"}