@rpgjs/client 5.0.0-beta.11 → 5.0.0-beta.13

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 (133) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/Game/AnimationManager.d.ts +1 -0
  3. package/dist/Game/AnimationManager.js +3 -0
  4. package/dist/Game/AnimationManager.js.map +1 -1
  5. package/dist/Game/ClientVisuals.d.ts +61 -0
  6. package/dist/Game/ClientVisuals.js +96 -0
  7. package/dist/Game/ClientVisuals.js.map +1 -0
  8. package/dist/Game/ClientVisuals.spec.d.ts +1 -0
  9. package/dist/Game/EventComponentResolver.d.ts +16 -0
  10. package/dist/Game/EventComponentResolver.js +52 -0
  11. package/dist/Game/EventComponentResolver.js.map +1 -0
  12. package/dist/Game/EventComponentResolver.spec.d.ts +1 -0
  13. package/dist/Game/Map.js +9 -0
  14. package/dist/Game/Map.js.map +1 -1
  15. package/dist/Game/Object.d.ts +2 -0
  16. package/dist/Game/Object.js +22 -8
  17. package/dist/Game/Object.js.map +1 -1
  18. package/dist/Game/Object.spec.d.ts +1 -0
  19. package/dist/Game/ProjectileManager.d.ts +11 -2
  20. package/dist/Game/ProjectileManager.js +19 -2
  21. package/dist/Game/ProjectileManager.js.map +1 -1
  22. package/dist/Gui/Gui.d.ts +3 -2
  23. package/dist/Gui/Gui.js +18 -6
  24. package/dist/Gui/Gui.js.map +1 -1
  25. package/dist/RpgClient.d.ts +85 -1
  26. package/dist/RpgClientEngine.d.ts +77 -2
  27. package/dist/RpgClientEngine.js +290 -31
  28. package/dist/RpgClientEngine.js.map +1 -1
  29. package/dist/components/animations/fx.ce.js +58 -0
  30. package/dist/components/animations/fx.ce.js.map +1 -0
  31. package/dist/components/animations/index.d.ts +1 -0
  32. package/dist/components/animations/index.js +3 -1
  33. package/dist/components/animations/index.js.map +1 -1
  34. package/dist/components/character.ce.js +192 -19
  35. package/dist/components/character.ce.js.map +1 -1
  36. package/dist/components/gui/dialogbox/index.ce.js +27 -12
  37. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  38. package/dist/components/gui/gameover.ce.js +4 -3
  39. package/dist/components/gui/gameover.ce.js.map +1 -1
  40. package/dist/components/gui/menu/equip-menu.ce.js +9 -8
  41. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  42. package/dist/components/gui/menu/exit-menu.ce.js +7 -5
  43. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  44. package/dist/components/gui/menu/items-menu.ce.js +8 -7
  45. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  46. package/dist/components/gui/menu/main-menu.ce.js +12 -11
  47. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  48. package/dist/components/gui/menu/options-menu.ce.js +7 -5
  49. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  50. package/dist/components/gui/menu/skills-menu.ce.js +4 -2
  51. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  52. package/dist/components/gui/notification/notification.ce.js +4 -1
  53. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  54. package/dist/components/gui/save-load.ce.js +10 -9
  55. package/dist/components/gui/save-load.ce.js.map +1 -1
  56. package/dist/components/gui/shop/shop.ce.js +17 -16
  57. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  58. package/dist/components/gui/title-screen.ce.js +4 -3
  59. package/dist/components/gui/title-screen.ce.js.map +1 -1
  60. package/dist/components/interaction-components.ce.js +20 -0
  61. package/dist/components/interaction-components.ce.js.map +1 -0
  62. package/dist/components/scenes/canvas.ce.js +12 -7
  63. package/dist/components/scenes/canvas.ce.js.map +1 -1
  64. package/dist/components/scenes/draw-map.ce.js +18 -13
  65. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  66. package/dist/i18n.d.ts +55 -0
  67. package/dist/i18n.js +60 -0
  68. package/dist/i18n.js.map +1 -0
  69. package/dist/i18n.spec.d.ts +1 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +5 -2
  72. package/dist/module.js +30 -3
  73. package/dist/module.js.map +1 -1
  74. package/dist/services/actionInput.d.ts +3 -1
  75. package/dist/services/actionInput.js +33 -1
  76. package/dist/services/actionInput.js.map +1 -1
  77. package/dist/services/interactions.d.ts +159 -0
  78. package/dist/services/interactions.js +460 -0
  79. package/dist/services/interactions.js.map +1 -0
  80. package/dist/services/interactions.spec.d.ts +1 -0
  81. package/dist/services/keyboardControls.d.ts +1 -0
  82. package/dist/services/keyboardControls.js +1 -0
  83. package/dist/services/keyboardControls.js.map +1 -1
  84. package/dist/services/standalone.d.ts +3 -1
  85. package/dist/services/standalone.js +31 -13
  86. package/dist/services/standalone.js.map +1 -1
  87. package/dist/utils/mapId.d.ts +1 -0
  88. package/dist/utils/mapId.js +6 -0
  89. package/dist/utils/mapId.js.map +1 -0
  90. package/package.json +4 -4
  91. package/src/Game/AnimationManager.ts +4 -0
  92. package/src/Game/ClientVisuals.spec.ts +56 -0
  93. package/src/Game/ClientVisuals.ts +184 -0
  94. package/src/Game/EventComponentResolver.spec.ts +84 -0
  95. package/src/Game/EventComponentResolver.ts +74 -0
  96. package/src/Game/Map.ts +10 -0
  97. package/src/Game/Object.spec.ts +59 -0
  98. package/src/Game/Object.ts +36 -12
  99. package/src/Game/ProjectileManager.spec.ts +111 -0
  100. package/src/Game/ProjectileManager.ts +24 -2
  101. package/src/Gui/Gui.spec.ts +67 -0
  102. package/src/Gui/Gui.ts +24 -7
  103. package/src/RpgClient.ts +96 -1
  104. package/src/RpgClientEngine.ts +378 -45
  105. package/src/components/animations/fx.ce +101 -0
  106. package/src/components/animations/index.ts +4 -2
  107. package/src/components/character.ce +243 -17
  108. package/src/components/gui/dialogbox/index.ce +35 -14
  109. package/src/components/gui/gameover.ce +4 -3
  110. package/src/components/gui/menu/equip-menu.ce +9 -8
  111. package/src/components/gui/menu/exit-menu.ce +4 -3
  112. package/src/components/gui/menu/items-menu.ce +8 -7
  113. package/src/components/gui/menu/main-menu.ce +12 -11
  114. package/src/components/gui/menu/options-menu.ce +4 -3
  115. package/src/components/gui/menu/skills-menu.ce +2 -1
  116. package/src/components/gui/notification/notification.ce +7 -1
  117. package/src/components/gui/save-load.ce +11 -10
  118. package/src/components/gui/shop/shop.ce +17 -16
  119. package/src/components/gui/title-screen.ce +4 -3
  120. package/src/components/interaction-components.ce +23 -0
  121. package/src/components/scenes/canvas.ce +12 -7
  122. package/src/components/scenes/draw-map.ce +16 -5
  123. package/src/i18n.spec.ts +39 -0
  124. package/src/i18n.ts +59 -0
  125. package/src/index.ts +3 -0
  126. package/src/module.ts +43 -10
  127. package/src/services/actionInput.spec.ts +54 -0
  128. package/src/services/actionInput.ts +68 -1
  129. package/src/services/interactions.spec.ts +175 -0
  130. package/src/services/interactions.ts +722 -0
  131. package/src/services/keyboardControls.ts +2 -1
  132. package/src/services/standalone.ts +39 -10
  133. package/src/utils/mapId.ts +2 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @rpgjs/client
2
2
 
3
+ ## 5.0.0-beta.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Release the next RPGJS beta with client interactions, i18n support, movement and physics improvements, Studio fixes, action battle updates, playground migration, and related runtime documentation.
8
+ - Updated dependencies
9
+ - @rpgjs/common@5.0.0-beta.13
10
+ - @rpgjs/server@5.0.0-beta.13
11
+ - @rpgjs/ui-css@5.0.0-beta.12
12
+
13
+ ## 5.0.0-beta.12
14
+
15
+ ### Patch Changes
16
+
17
+ - Prepare beta.12 with action battle AI, area queries, client visuals, event component resolvers, projectile handling, and related Vite/runtime updates.
18
+ - Updated dependencies
19
+ - @rpgjs/common@5.0.0-beta.12
20
+ - @rpgjs/server@5.0.0-beta.12
21
+
3
22
  ## 5.0.0-beta.11
4
23
 
5
24
  ### Patch Changes
@@ -1,6 +1,7 @@
1
1
  import { RpgCommonPlayer } from '@rpgjs/common';
2
2
  export declare class AnimationManager {
3
3
  current: import('canvasengine').WritableArraySignal<any[]>;
4
+ clear(): void;
4
5
  displayEffect(params: any, player: RpgCommonPlayer | {
5
6
  x: number;
6
7
  y: number;
@@ -5,6 +5,9 @@ var AnimationManager = class {
5
5
  constructor() {
6
6
  this.current = signal([]);
7
7
  }
8
+ clear() {
9
+ this.current.set([]);
10
+ }
8
11
  displayEffect(params, player) {
9
12
  const id = generateUID();
10
13
  const effectParams = params ?? {};
@@ -1 +1 @@
1
- {"version":3,"file":"AnimationManager.js","names":[],"sources":["../../src/Game/AnimationManager.ts"],"sourcesContent":["import { generateUID, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { signal } from \"canvasengine\";\n\nexport class AnimationManager {\n current = signal<any[]>([]);\n\n displayEffect(params: any, player: RpgCommonPlayer | { x: number, y: number }): Promise<void> {\n const id = generateUID();\n const effectParams = params ?? {};\n return new Promise<void>((resolve) => {\n let finished = false;\n const finish = (data?: any) => {\n if (finished) return;\n finished = true;\n const index = this.current().findIndex((value) => value.id === id);\n if (index !== -1) {\n this.current().splice(index, 1);\n }\n effectParams.onFinish?.(data);\n resolve();\n };\n\n this.current().push({\n ...effectParams,\n id,\n x: player.x,\n y: player.y,\n object: player,\n onFinish: finish,\n });\n });\n }\n}\n"],"mappings":";;;AAGA,IAAa,mBAAb,MAA8B;;iBAClB,OAAc,CAAC,CAAC;;CAE1B,cAAc,QAAa,QAAmE;EAC5F,MAAM,KAAK,YAAY;EACvB,MAAM,eAAe,UAAU,CAAC;EAChC,OAAO,IAAI,SAAe,YAAY;GACpC,IAAI,WAAW;GACf,MAAM,UAAU,SAAe;IAC7B,IAAI,UAAU;IACd,WAAW;IACX,MAAM,QAAQ,KAAK,QAAQ,EAAE,WAAW,UAAU,MAAM,OAAO,EAAE;IACjE,IAAI,UAAU,IACZ,KAAK,QAAQ,EAAE,OAAO,OAAO,CAAC;IAEhC,aAAa,WAAW,IAAI;IAC5B,QAAQ;GACV;GAEA,KAAK,QAAQ,EAAE,KAAK;IAClB,GAAG;IACH;IACA,GAAG,OAAO;IACV,GAAG,OAAO;IACV,QAAQ;IACR,UAAU;GACZ,CAAC;EACH,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"AnimationManager.js","names":[],"sources":["../../src/Game/AnimationManager.ts"],"sourcesContent":["import { generateUID, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { signal } from \"canvasengine\";\n\nexport class AnimationManager {\n current = signal<any[]>([]);\n\n clear(): void {\n this.current.set([]);\n }\n\n displayEffect(params: any, player: RpgCommonPlayer | { x: number, y: number }): Promise<void> {\n const id = generateUID();\n const effectParams = params ?? {};\n return new Promise<void>((resolve) => {\n let finished = false;\n const finish = (data?: any) => {\n if (finished) return;\n finished = true;\n const index = this.current().findIndex((value) => value.id === id);\n if (index !== -1) {\n this.current().splice(index, 1);\n }\n effectParams.onFinish?.(data);\n resolve();\n };\n\n this.current().push({\n ...effectParams,\n id,\n x: player.x,\n y: player.y,\n object: player,\n onFinish: finish,\n });\n });\n }\n}\n"],"mappings":";;;AAGA,IAAa,mBAAb,MAA8B;;iBAClB,OAAc,CAAC,CAAC;;CAE1B,QAAc;EACZ,KAAK,QAAQ,IAAI,CAAC,CAAC;CACrB;CAEA,cAAc,QAAa,QAAmE;EAC5F,MAAM,KAAK,YAAY;EACvB,MAAM,eAAe,UAAU,CAAC;EAChC,OAAO,IAAI,SAAe,YAAY;GACpC,IAAI,WAAW;GACf,MAAM,UAAU,SAAe;IAC7B,IAAI,UAAU;IACd,WAAW;IACX,MAAM,QAAQ,KAAK,QAAQ,EAAE,WAAW,UAAU,MAAM,OAAO,EAAE;IACjE,IAAI,UAAU,IACZ,KAAK,QAAQ,EAAE,OAAO,OAAO,CAAC;IAEhC,aAAa,WAAW,IAAI;IAC5B,QAAQ;GACV;GAEA,KAAK,QAAQ,EAAE,KAAK;IAClB,GAAG;IACH;IACA,GAAG,OAAO;IACV,GAAG,OAAO;IACV,QAAQ;IACR,UAAU;GACZ,CAAC;EACH,CAAC;CACH;AACF"}
@@ -0,0 +1,61 @@
1
+ import { RpgClientEngine } from '../RpgClientEngine';
2
+ import { RpgClientMap } from './Map';
3
+ export type ClientVisualPosition = {
4
+ x: number;
5
+ y: number;
6
+ z?: number;
7
+ };
8
+ export type ClientVisualPayload = Record<string, any> & {
9
+ source?: string;
10
+ sourceId?: string;
11
+ target?: string;
12
+ targetId?: string;
13
+ object?: string;
14
+ objectId?: string;
15
+ position?: ClientVisualPosition;
16
+ };
17
+ export type ClientVisualPacket = {
18
+ name: string;
19
+ data?: ClientVisualPayload;
20
+ };
21
+ export type ClientVisualObjectTarget = string | Record<string, any> | undefined | null;
22
+ export type ClientVisualComponentTarget = ClientVisualObjectTarget | ClientVisualPosition;
23
+ export type ClientVisualContext = {
24
+ name: string;
25
+ data: ClientVisualPayload;
26
+ engine: RpgClientEngine;
27
+ scene: RpgClientMap;
28
+ source?: any;
29
+ target?: any;
30
+ object?: any;
31
+ position?: ClientVisualPosition;
32
+ };
33
+ export type ClientVisualHelpers = {
34
+ getObject(id?: string | null): any;
35
+ flash(target?: ClientVisualObjectTarget, options?: Record<string, any>): void;
36
+ showHit(target: ClientVisualObjectTarget, text: string): void;
37
+ component(id: string, target?: ClientVisualComponentTarget, params?: Record<string, any>): void;
38
+ sound(id: string, options?: {
39
+ volume?: number;
40
+ loop?: boolean;
41
+ }): Promise<void>;
42
+ animation(target: ClientVisualObjectTarget, animationName: string, options?: {
43
+ graphic?: string | string[];
44
+ repeat?: number;
45
+ }): void;
46
+ shake(options?: {
47
+ intensity?: number;
48
+ duration?: number;
49
+ frequency?: number;
50
+ direction?: string;
51
+ }): void;
52
+ };
53
+ export type ClientVisualHandler = (context: ClientVisualContext, helpers: ClientVisualHelpers) => void | Promise<void>;
54
+ export type ClientVisualMap = Record<string, ClientVisualHandler>;
55
+ export declare class ClientVisualRegistry {
56
+ private readonly handlers;
57
+ register(name: string, handler: ClientVisualHandler): ClientVisualHandler;
58
+ registerMany(visuals: ClientVisualMap): void;
59
+ get(name: string): ClientVisualHandler | undefined;
60
+ play(packet: ClientVisualPacket, engine: RpgClientEngine): Promise<void>;
61
+ }
@@ -0,0 +1,96 @@
1
+ //#region src/Game/ClientVisuals.ts
2
+ var isPosition = (value) => value && typeof value === "object" && typeof value.x === "number" && typeof value.y === "number";
3
+ var resolvePosition = (data) => {
4
+ if (isPosition(data.position)) return data.position;
5
+ if (typeof data.x === "number" && typeof data.y === "number") return {
6
+ x: data.x,
7
+ y: data.y,
8
+ z: data.z
9
+ };
10
+ };
11
+ var resolveObject = (engine, target) => {
12
+ if (!target) return void 0;
13
+ if (typeof target === "string") return engine.getObjectById(target);
14
+ return target;
15
+ };
16
+ var resolvePayloadObject = (engine, data, keys) => {
17
+ return resolveObject(engine, keys.map((key) => data[key]).find((value) => typeof value === "string"));
18
+ };
19
+ var createClientVisualHelpers = (engine) => ({
20
+ getObject(id) {
21
+ if (!id) return void 0;
22
+ return engine.getObjectById(id);
23
+ },
24
+ flash(target, options = {}) {
25
+ resolveObject(engine, target)?.flash?.(options);
26
+ },
27
+ showHit(target, text) {
28
+ resolveObject(engine, target)?.showHit?.(text);
29
+ },
30
+ component(id, target, params = {}) {
31
+ const object = isPosition(target) ? void 0 : resolveObject(engine, target);
32
+ const position = isPosition(target) ? target : void 0;
33
+ const anchor = object ?? position;
34
+ if (!anchor) return;
35
+ engine.getComponentAnimation(id).displayEffect(params, anchor);
36
+ },
37
+ sound(id, options) {
38
+ return engine.playSound(id, options);
39
+ },
40
+ animation(target, animationName, options = {}) {
41
+ const object = resolveObject(engine, target);
42
+ if (!object?.setAnimation) return;
43
+ if (options.graphic !== void 0) {
44
+ object.setAnimation(animationName, options.graphic, options.repeat ?? 1);
45
+ return;
46
+ }
47
+ object.setAnimation(animationName, options.repeat ?? 1);
48
+ },
49
+ shake(options = {}) {
50
+ engine.mapShakeTrigger.start(options);
51
+ }
52
+ });
53
+ var ClientVisualRegistry = class {
54
+ constructor() {
55
+ this.handlers = /* @__PURE__ */ new Map();
56
+ }
57
+ register(name, handler) {
58
+ this.handlers.set(name, handler);
59
+ return handler;
60
+ }
61
+ registerMany(visuals) {
62
+ Object.entries(visuals).forEach(([name, handler]) => {
63
+ this.register(name, handler);
64
+ });
65
+ }
66
+ get(name) {
67
+ return this.handlers.get(name);
68
+ }
69
+ async play(packet, engine) {
70
+ const handler = this.handlers.get(packet.name);
71
+ if (!handler) {
72
+ console.warn(`Client visual "${packet.name}" is not registered`);
73
+ return;
74
+ }
75
+ const data = packet.data ?? {};
76
+ const context = {
77
+ name: packet.name,
78
+ data,
79
+ engine,
80
+ scene: engine.scene,
81
+ source: resolvePayloadObject(engine, data, ["sourceId", "source"]),
82
+ target: resolvePayloadObject(engine, data, ["targetId", "target"]),
83
+ object: resolvePayloadObject(engine, data, ["objectId", "object"]),
84
+ position: resolvePosition(data)
85
+ };
86
+ try {
87
+ await handler(context, createClientVisualHelpers(engine));
88
+ } catch (error) {
89
+ console.error(`Client visual "${packet.name}" failed`, error);
90
+ }
91
+ }
92
+ };
93
+ //#endregion
94
+ export { ClientVisualRegistry };
95
+
96
+ //# sourceMappingURL=ClientVisuals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientVisuals.js","names":[],"sources":["../../src/Game/ClientVisuals.ts"],"sourcesContent":["import type { RpgClientEngine } from \"../RpgClientEngine\";\nimport type { RpgClientMap } from \"./Map\";\n\nexport type ClientVisualPosition = {\n x: number;\n y: number;\n z?: number;\n};\n\nexport type ClientVisualPayload = Record<string, any> & {\n source?: string;\n sourceId?: string;\n target?: string;\n targetId?: string;\n object?: string;\n objectId?: string;\n position?: ClientVisualPosition;\n};\n\nexport type ClientVisualPacket = {\n name: string;\n data?: ClientVisualPayload;\n};\n\nexport type ClientVisualObjectTarget = string | Record<string, any> | undefined | null;\nexport type ClientVisualComponentTarget =\n | ClientVisualObjectTarget\n | ClientVisualPosition;\n\nexport type ClientVisualContext = {\n name: string;\n data: ClientVisualPayload;\n engine: RpgClientEngine;\n scene: RpgClientMap;\n source?: any;\n target?: any;\n object?: any;\n position?: ClientVisualPosition;\n};\n\nexport type ClientVisualHelpers = {\n getObject(id?: string | null): any;\n flash(target?: ClientVisualObjectTarget, options?: Record<string, any>): void;\n showHit(target: ClientVisualObjectTarget, text: string): void;\n component(\n id: string,\n target?: ClientVisualComponentTarget,\n params?: Record<string, any>\n ): void;\n sound(id: string, options?: { volume?: number; loop?: boolean }): Promise<void>;\n animation(\n target: ClientVisualObjectTarget,\n animationName: string,\n options?: { graphic?: string | string[]; repeat?: number }\n ): void;\n shake(options?: {\n intensity?: number;\n duration?: number;\n frequency?: number;\n direction?: string;\n }): void;\n};\n\nexport type ClientVisualHandler = (\n context: ClientVisualContext,\n helpers: ClientVisualHelpers\n) => void | Promise<void>;\n\nexport type ClientVisualMap = Record<string, ClientVisualHandler>;\n\nconst isPosition = (value: any): value is ClientVisualPosition =>\n value &&\n typeof value === \"object\" &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\";\n\nconst resolvePosition = (data: ClientVisualPayload): ClientVisualPosition | undefined => {\n if (isPosition(data.position)) return data.position;\n if (typeof data.x === \"number\" && typeof data.y === \"number\") {\n return { x: data.x, y: data.y, z: data.z };\n }\n return undefined;\n};\n\nconst resolveObject = (engine: RpgClientEngine, target: ClientVisualObjectTarget) => {\n if (!target) return undefined;\n if (typeof target === \"string\") return engine.getObjectById(target);\n return target;\n};\n\nconst resolvePayloadObject = (\n engine: RpgClientEngine,\n data: ClientVisualPayload,\n keys: string[]\n) => {\n const id = keys\n .map((key) => data[key])\n .find((value) => typeof value === \"string\");\n return resolveObject(engine, id);\n};\n\nconst createClientVisualHelpers = (\n engine: RpgClientEngine\n): ClientVisualHelpers => ({\n getObject(id) {\n if (!id) return undefined;\n return engine.getObjectById(id);\n },\n flash(target, options = {}) {\n const object = resolveObject(engine, target);\n object?.flash?.(options);\n },\n showHit(target, text) {\n const object = resolveObject(engine, target);\n object?.showHit?.(text);\n },\n component(id, target, params = {}) {\n const object = isPosition(target) ? undefined : resolveObject(engine, target);\n const position = isPosition(target) ? target : undefined;\n const anchor = object ?? position;\n if (!anchor) return;\n engine.getComponentAnimation(id).displayEffect(params, anchor);\n },\n sound(id, options) {\n return engine.playSound(id, options);\n },\n animation(target, animationName, options = {}) {\n const object = resolveObject(engine, target);\n if (!object?.setAnimation) return;\n if (options.graphic !== undefined) {\n object.setAnimation(animationName, options.graphic, options.repeat ?? 1);\n return;\n }\n object.setAnimation(animationName, options.repeat ?? 1);\n },\n shake(options = {}) {\n engine.mapShakeTrigger.start(options);\n },\n});\n\nexport class ClientVisualRegistry {\n private readonly handlers = new Map<string, ClientVisualHandler>();\n\n register(name: string, handler: ClientVisualHandler) {\n this.handlers.set(name, handler);\n return handler;\n }\n\n registerMany(visuals: ClientVisualMap) {\n Object.entries(visuals).forEach(([name, handler]) => {\n this.register(name, handler);\n });\n }\n\n get(name: string) {\n return this.handlers.get(name);\n }\n\n async play(packet: ClientVisualPacket, engine: RpgClientEngine) {\n const handler = this.handlers.get(packet.name);\n if (!handler) {\n console.warn(`Client visual \"${packet.name}\" is not registered`);\n return;\n }\n\n const data = packet.data ?? {};\n const context: ClientVisualContext = {\n name: packet.name,\n data,\n engine,\n scene: engine.scene,\n source: resolvePayloadObject(engine, data, [\"sourceId\", \"source\"]),\n target: resolvePayloadObject(engine, data, [\"targetId\", \"target\"]),\n object: resolvePayloadObject(engine, data, [\"objectId\", \"object\"]),\n position: resolvePosition(data),\n };\n\n try {\n await handler(context, createClientVisualHelpers(engine));\n } catch (error) {\n console.error(`Client visual \"${packet.name}\" failed`, error);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { EventComponentConfig, EventComponentSprite } from '../RpgClient';
2
+ import { RpgClientEvent } from './Event';
3
+ export type EventComponentResolver = (event: EventComponentSprite) => EventComponentConfig | null | undefined;
4
+ export interface NormalizedEventComponentConfig {
5
+ component: any;
6
+ props: Record<string, any>;
7
+ dependencies: any[];
8
+ renderGraphic: boolean;
9
+ }
10
+ export declare class EventComponentResolverRegistry {
11
+ private resolvers;
12
+ add(resolver: EventComponentResolver): EventComponentResolver;
13
+ resolve(event: RpgClientEvent): EventComponentConfig | null;
14
+ clear(): void;
15
+ }
16
+ export declare function normalizeEventComponent(componentConfig: EventComponentConfig | null | undefined, sprite: RpgClientEvent): NormalizedEventComponentConfig | null;
@@ -0,0 +1,52 @@
1
+ //#region src/Game/EventComponentResolver.ts
2
+ var EventComponentResolverRegistry = class {
3
+ constructor() {
4
+ this.resolvers = [];
5
+ }
6
+ add(resolver) {
7
+ this.resolvers.push(resolver);
8
+ return resolver;
9
+ }
10
+ resolve(event) {
11
+ let resolved = null;
12
+ this.resolvers.forEach((resolver) => {
13
+ const result = resolver(event);
14
+ if (result !== void 0 && result !== null) resolved = result;
15
+ });
16
+ return resolved;
17
+ }
18
+ clear() {
19
+ this.resolvers = [];
20
+ }
21
+ };
22
+ function withoutReservedSpriteProp(props) {
23
+ if (!props || typeof props !== "object") return {};
24
+ const { sprite: _ignoredSprite, ...safeProps } = props;
25
+ return safeProps;
26
+ }
27
+ function normalizeEventComponent(componentConfig, sprite) {
28
+ if (!componentConfig) return null;
29
+ if (typeof componentConfig === "object" && "component" in componentConfig) {
30
+ const propsValue = componentConfig.props !== void 0 ? componentConfig.props : componentConfig.data;
31
+ const props = typeof propsValue === "function" ? propsValue(sprite) : propsValue;
32
+ return {
33
+ component: componentConfig.component,
34
+ props: {
35
+ ...withoutReservedSpriteProp(props),
36
+ sprite
37
+ },
38
+ dependencies: componentConfig.dependencies ? componentConfig.dependencies(sprite) : [],
39
+ renderGraphic: componentConfig.renderGraphic === true
40
+ };
41
+ }
42
+ return {
43
+ component: componentConfig,
44
+ props: { sprite },
45
+ dependencies: [],
46
+ renderGraphic: false
47
+ };
48
+ }
49
+ //#endregion
50
+ export { EventComponentResolverRegistry, normalizeEventComponent };
51
+
52
+ //# sourceMappingURL=EventComponentResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventComponentResolver.js","names":[],"sources":["../../src/Game/EventComponentResolver.ts"],"sourcesContent":["import type { EventComponentConfig, EventComponentSprite } from \"../RpgClient\";\nimport type { RpgClientEvent } from \"./Event\";\n\nexport type EventComponentResolver = (event: EventComponentSprite) => EventComponentConfig | null | undefined;\n\nexport interface NormalizedEventComponentConfig {\n component: any;\n props: Record<string, any>;\n dependencies: any[];\n renderGraphic: boolean;\n}\n\nexport class EventComponentResolverRegistry {\n private resolvers: EventComponentResolver[] = [];\n\n add(resolver: EventComponentResolver) {\n this.resolvers.push(resolver);\n return resolver;\n }\n\n resolve(event: RpgClientEvent): EventComponentConfig | null {\n let resolved: EventComponentConfig | null = null;\n this.resolvers.forEach((resolver) => {\n const result = resolver(event as EventComponentSprite);\n if (result !== undefined && result !== null) {\n resolved = result;\n }\n });\n return resolved;\n }\n\n clear() {\n this.resolvers = [];\n }\n}\n\nfunction withoutReservedSpriteProp(props: unknown): Record<string, any> {\n if (!props || typeof props !== \"object\") return {};\n const { sprite: _ignoredSprite, ...safeProps } = props as Record<string, any>;\n return safeProps;\n}\n\nexport function normalizeEventComponent(\n componentConfig: EventComponentConfig | null | undefined,\n sprite: RpgClientEvent\n): NormalizedEventComponentConfig | null {\n if (!componentConfig) return null;\n\n if (typeof componentConfig === \"object\" && \"component\" in componentConfig) {\n const propsValue = componentConfig.props !== undefined\n ? componentConfig.props\n : componentConfig.data;\n const props = typeof propsValue === \"function\"\n ? propsValue(sprite as EventComponentSprite)\n : propsValue;\n\n return {\n component: componentConfig.component,\n props: {\n ...withoutReservedSpriteProp(props),\n sprite\n },\n dependencies: componentConfig.dependencies ? componentConfig.dependencies(sprite as EventComponentSprite) : [],\n renderGraphic: componentConfig.renderGraphic === true\n };\n }\n\n return {\n component: componentConfig,\n props: { sprite },\n dependencies: [],\n renderGraphic: false\n };\n}\n"],"mappings":";AAYA,IAAa,iCAAb,MAA4C;;mBACI,CAAC;;CAE/C,IAAI,UAAkC;EACpC,KAAK,UAAU,KAAK,QAAQ;EAC5B,OAAO;CACT;CAEA,QAAQ,OAAoD;EAC1D,IAAI,WAAwC;EAC5C,KAAK,UAAU,SAAS,aAAa;GACnC,MAAM,SAAS,SAAS,KAA6B;GACrD,IAAI,WAAW,KAAA,KAAa,WAAW,MACrC,WAAW;EAEf,CAAC;EACD,OAAO;CACT;CAEA,QAAQ;EACN,KAAK,YAAY,CAAC;CACpB;AACF;AAEA,SAAS,0BAA0B,OAAqC;CACtE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO,CAAC;CACjD,MAAM,EAAE,QAAQ,gBAAgB,GAAG,cAAc;CACjD,OAAO;AACT;AAEA,SAAgB,wBACd,iBACA,QACuC;CACvC,IAAI,CAAC,iBAAiB,OAAO;CAE7B,IAAI,OAAO,oBAAoB,YAAY,eAAe,iBAAiB;EACzE,MAAM,aAAa,gBAAgB,UAAU,KAAA,IACzC,gBAAgB,QAChB,gBAAgB;EACpB,MAAM,QAAQ,OAAO,eAAe,aAChC,WAAW,MAA8B,IACzC;EAEJ,OAAO;GACL,WAAW,gBAAgB;GAC3B,OAAO;IACL,GAAG,0BAA0B,KAAK;IAClC;GACF;GACA,cAAc,gBAAgB,eAAe,gBAAgB,aAAa,MAA8B,IAAI,CAAC;GAC7G,eAAe,gBAAgB,kBAAkB;EACnD;CACF;CAEA,OAAO;EACL,WAAW;EACX,OAAO,EAAE,OAAO;EAChB,cAAc,CAAC;EACf,eAAe;CACjB;AACF"}
@@ -0,0 +1 @@
1
+ export {};
package/dist/Game/Map.js CHANGED
@@ -52,6 +52,15 @@ var RpgClientMap = class extends RpgCommonMap {
52
52
  reset(force = false) {
53
53
  const currentPlayerId = this.engine.playerIdSignal();
54
54
  const currentPlayer = !force && currentPlayerId ? this.players()[currentPlayerId] : void 0;
55
+ const players = this.players();
56
+ const events = this.events();
57
+ Object.entries(players).forEach(([id, player]) => {
58
+ if (!player || !force && id === currentPlayerId) return;
59
+ player.resetAnimationState?.();
60
+ });
61
+ Object.values(events).forEach((event) => {
62
+ event?.resetAnimationState?.();
63
+ });
55
64
  this.players.set(currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {});
56
65
  this.events.set({});
57
66
  this.weatherState.set(null);
@@ -1 +1 @@
1
- {"version":3,"file":"Map.js","names":[],"sources":["../../src/Game/Map.ts"],"sourcesContent":["import {\n RpgCommonMap,\n cloneLightingState,\n normalizeLightingState,\n type LightSpot,\n type LightingState,\n type WeatherState,\n type MapPhysicsInitContext,\n type MapPhysicsEntityContext,\n} from \"@rpgjs/common\";\nimport { sync, users } from \"@signe/sync\";\nimport { RpgClientPlayer } from \"./Player\";\nimport { Signal, signal, computed, effect } from \"canvasengine\";\nimport { RpgClientEvent } from \"./Event\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { inject } from \"../core/inject\";\n\ntype TestGlobalScope = typeof globalThis & {\n process?: {\n env?: {\n TEST?: string;\n };\n };\n __RPGJS_TEST__?: boolean;\n};\n\nexport class RpgClientMap extends RpgCommonMap<any> {\n engine: RpgClientEngine = inject(RpgClientEngine)\n @users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});\n @sync(RpgClientEvent) events = signal<Record<string, RpgClientEvent>>({});\n currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()!])\n weatherState = signal<WeatherState | null>(null);\n localWeatherOverride = signal<WeatherState | null>(null);\n lightingState = signal<LightingState | null>(null);\n localLightSpots = signal<Record<string, LightSpot>>({});\n weather = computed<WeatherState | null>(() => {\n const local = this.localWeatherOverride() \n const state = this.weatherState()\n return local ?? state\n });\n lighting = computed<LightingState | null>(() => {\n const state = cloneLightingState(this.lightingState());\n const localSpots = Object.entries(this.localLightSpots()).map(([id, spot]) => ({\n ...spot,\n id: spot.id ?? id,\n }));\n\n if (!state && localSpots.length === 0) {\n return null;\n }\n\n const next: LightingState = { ...(state ?? {}) };\n if (!state?.ambient) {\n delete next.ambient;\n }\n next.spots = [\n ...(state?.spots ?? []),\n ...localSpots,\n ];\n return next;\n });\n private manualClientPhysicsTick = false;\n private readonly isTestEnvironment: boolean;\n\n constructor() {\n super();\n // Détecter l'environnement de test\n const testGlobal = globalThis as TestGlobalScope;\n const isTest = testGlobal.process?.env?.TEST === 'true'\n || testGlobal.__RPGJS_TEST__ === true;\n this.isTestEnvironment = isTest;\n if (isTest) {\n this.autoTickEnabled = false;\n }\n }\n\n configureClientPrediction(enabled: boolean): void {\n this.manualClientPhysicsTick = enabled;\n this.autoTickEnabled = enabled ? false : !this.isTestEnvironment;\n }\n\n getCurrentPlayer() {\n return this.currentPlayer()\n }\n\n reset(force = false) {\n const currentPlayerId = this.engine.playerIdSignal();\n const currentPlayer = !force && currentPlayerId\n ? this.players()[currentPlayerId]\n : undefined;\n\n this.players.set(\n currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {}\n );\n this.events.set({})\n this.weatherState.set(null);\n this.localWeatherOverride.set(null);\n this.lightingState.set(null);\n this.localLightSpots.set({});\n this.clearPhysic()\n }\n\n getWeather(): WeatherState | null {\n return this.weather();\n }\n\n setLocalWeather(next: WeatherState | null): void {\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherOverride.set(null);\n }\n\n getLighting(): LightingState | null {\n return this.lighting();\n }\n\n addLightSpot(id: string, spot: LightSpot): void {\n const normalized = normalizeLightingState({ spots: [{ ...spot, id }] });\n const nextSpot = normalized?.spots?.[0];\n if (!nextSpot) {\n return;\n }\n this.localLightSpots.update((spots) => ({\n ...spots,\n [id]: nextSpot,\n }));\n }\n\n patchLightSpot(id: string, patch: Partial<LightSpot>): void {\n this.localLightSpots.update((spots) => {\n const current = spots[id];\n if (!current) {\n return spots;\n }\n return {\n ...spots,\n [id]: {\n ...current,\n ...patch,\n id,\n x: patch.x ?? current.x,\n y: patch.y ?? current.y,\n },\n };\n });\n }\n\n removeLightSpot(id: string): void {\n this.localLightSpots.update((spots) => {\n if (!(id in spots)) {\n return spots;\n }\n const next = { ...spots };\n delete next[id];\n return next;\n });\n }\n\n clearLightSpots(): void {\n this.localLightSpots.set({});\n }\n\n stepClientPhysics(deltaMs: number): number {\n if (!this.manualClientPhysicsTick) {\n return 0;\n }\n return this.nextTick(deltaMs);\n }\n\n stepPredictionTick(): void {\n this.forceSingleTick();\n }\n\n protected emitPhysicsInit(context: MapPhysicsInitContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsInit\", this, context);\n }\n\n protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityAdd\", this, context);\n }\n\n protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityRemove\", this, context);\n }\n\n protected emitPhysicsReset(): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsReset\", this);\n }\n}\n"],"mappings":";;;;;;;;;;AA0BA,IAAa,eAAb,cAAkC,aAAkB;CAsClD,cAAc;EACZ,MAAM;gBAtCkB,OAAO,eAAe;iBACd,OAAwC,CAAC,CAAC;gBAC7C,OAAuC,CAAC,CAAC;uBACxD,eAAe,KAAK,QAAQ,EAAE,KAAK,OAAO,eAAe,EAAG;sBAC7D,OAA4B,IAAI;8BACxB,OAA4B,IAAI;uBACvC,OAA6B,IAAI;yBAC/B,OAAkC,CAAC,CAAC;iBAC5C,eAAoC;GAC5C,MAAM,QAAQ,KAAK,qBAAqB;GACxC,MAAM,QAAQ,KAAK,aAAa;GAChC,OAAO,SAAS;EAClB,CAAC;kBACU,eAAqC;GAC9C,MAAM,QAAQ,mBAAmB,KAAK,cAAc,CAAC;GACrD,MAAM,aAAa,OAAO,QAAQ,KAAK,gBAAgB,CAAC,EAAE,KAAK,CAAC,IAAI,WAAW;IAC7E,GAAG;IACH,IAAI,KAAK,MAAM;GACjB,EAAE;GAEF,IAAI,CAAC,SAAS,WAAW,WAAW,GAClC,OAAO;GAGT,MAAM,OAAsB,EAAE,GAAI,SAAS,CAAC,EAAG;GAC/C,IAAI,CAAC,OAAO,SACV,OAAO,KAAK;GAEd,KAAK,QAAQ,CACX,GAAI,OAAO,SAAS,CAAC,GACrB,GAAG,UACL;GACA,OAAO;EACT,CAAC;iCACiC;EAMhC,MAAM,aAAa;EACnB,MAAM,SAAS,WAAW,SAAS,KAAK,SAAS,UAC5C,WAAW,mBAAmB;EACnC,KAAK,oBAAoB;EACzB,IAAI,QACF,KAAK,kBAAkB;CAE3B;CAEA,0BAA0B,SAAwB;EAChD,KAAK,0BAA0B;EAC/B,KAAK,kBAAkB,UAAU,QAAQ,CAAC,KAAK;CACjD;CAEA,mBAAmB;EACjB,OAAO,KAAK,cAAc;CAC5B;CAEA,MAAM,QAAQ,OAAO;EACnB,MAAM,kBAAkB,KAAK,OAAO,eAAe;EACnD,MAAM,gBAAgB,CAAC,SAAS,kBAC5B,KAAK,QAAQ,EAAE,mBACf,KAAA;EAEJ,KAAK,QAAQ,IACX,mBAAmB,gBAAgB,GAAG,kBAAkB,cAAc,IAAI,CAAC,CAC7E;EACA,KAAK,OAAO,IAAI,CAAC,CAAC;EAClB,KAAK,aAAa,IAAI,IAAI;EAC1B,KAAK,qBAAqB,IAAI,IAAI;EAClC,KAAK,cAAc,IAAI,IAAI;EAC3B,KAAK,gBAAgB,IAAI,CAAC,CAAC;EAC3B,KAAK,YAAY;CACnB;CAEA,aAAkC;EAChC,OAAO,KAAK,QAAQ;CACtB;CAEA,gBAAgB,MAAiC;EAC/C,KAAK,qBAAqB,IAAI,IAAI;CACpC;CAEA,oBAA0B;EACxB,KAAK,qBAAqB,IAAI,IAAI;CACpC;CAEA,cAAoC;EAClC,OAAO,KAAK,SAAS;CACvB;CAEA,aAAa,IAAY,MAAuB;EAE9C,MAAM,WADa,uBAAuB,EAAE,OAAO,CAAC;GAAE,GAAG;GAAM;EAAG,CAAC,EAAE,CACpD,GAAY,QAAQ;EACrC,IAAI,CAAC,UACH;EAEF,KAAK,gBAAgB,QAAQ,WAAW;GACtC,GAAG;IACF,KAAK;EACR,EAAE;CACJ;CAEA,eAAe,IAAY,OAAiC;EAC1D,KAAK,gBAAgB,QAAQ,UAAU;GACrC,MAAM,UAAU,MAAM;GACtB,IAAI,CAAC,SACH,OAAO;GAET,OAAO;IACL,GAAG;KACF,KAAK;KACJ,GAAG;KACH,GAAG;KACH;KACA,GAAG,MAAM,KAAK,QAAQ;KACtB,GAAG,MAAM,KAAK,QAAQ;IACxB;GACF;EACF,CAAC;CACH;CAEA,gBAAgB,IAAkB;EAChC,KAAK,gBAAgB,QAAQ,UAAU;GACrC,IAAI,EAAE,MAAM,QACV,OAAO;GAET,MAAM,OAAO,EAAE,GAAG,MAAM;GACxB,OAAO,KAAK;GACZ,OAAO;EACT,CAAC;CACH;CAEA,kBAAwB;EACtB,KAAK,gBAAgB,IAAI,CAAC,CAAC;CAC7B;CAEA,kBAAkB,SAAyB;EACzC,IAAI,CAAC,KAAK,yBACR,OAAO;EAET,OAAO,KAAK,SAAS,OAAO;CAC9B;CAEA,qBAA2B;EACzB,KAAK,gBAAgB;CACvB;CAEA,gBAA0B,SAAsC;EAC9D,KAAK,QAAQ,mBAAmB,iBAAiB,MAAM,OAAO;CAChE;CAEA,qBAA+B,SAAwC;EACrE,KAAK,QAAQ,mBAAmB,sBAAsB,MAAM,OAAO;CACrE;CAEA,wBAAkC,SAAwC;EACxE,KAAK,QAAQ,mBAAmB,yBAAyB,MAAM,OAAO;CACxE;CAEA,mBAAmC;EACjC,KAAK,QAAQ,mBAAmB,kBAAkB,IAAI;CACxD;AACF;YAlKG,MAAM,eAAe,GAAA,mBAAA,eAAA,MAAA,CAAA,GAAA,aAAA,WAAA,WAAA,KAAA,CAAA;YACrB,KAAK,cAAc,GAAA,mBAAA,eAAA,MAAA,CAAA,GAAA,aAAA,WAAA,UAAA,KAAA,CAAA"}
1
+ {"version":3,"file":"Map.js","names":[],"sources":["../../src/Game/Map.ts"],"sourcesContent":["import {\n RpgCommonMap,\n cloneLightingState,\n normalizeLightingState,\n type LightSpot,\n type LightingState,\n type WeatherState,\n type MapPhysicsInitContext,\n type MapPhysicsEntityContext,\n} from \"@rpgjs/common\";\nimport { sync, users } from \"@signe/sync\";\nimport { RpgClientPlayer } from \"./Player\";\nimport { Signal, signal, computed, effect } from \"canvasengine\";\nimport { RpgClientEvent } from \"./Event\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { inject } from \"../core/inject\";\n\ntype TestGlobalScope = typeof globalThis & {\n process?: {\n env?: {\n TEST?: string;\n };\n };\n __RPGJS_TEST__?: boolean;\n};\n\nexport class RpgClientMap extends RpgCommonMap<any> {\n engine: RpgClientEngine = inject(RpgClientEngine)\n @users(RpgClientPlayer) players = signal<Record<string, RpgClientPlayer>>({});\n @sync(RpgClientEvent) events = signal<Record<string, RpgClientEvent>>({});\n currentPlayer = computed(() => this.players()[this.engine.playerIdSignal()!])\n weatherState = signal<WeatherState | null>(null);\n localWeatherOverride = signal<WeatherState | null>(null);\n lightingState = signal<LightingState | null>(null);\n localLightSpots = signal<Record<string, LightSpot>>({});\n weather = computed<WeatherState | null>(() => {\n const local = this.localWeatherOverride() \n const state = this.weatherState()\n return local ?? state\n });\n lighting = computed<LightingState | null>(() => {\n const state = cloneLightingState(this.lightingState());\n const localSpots = Object.entries(this.localLightSpots()).map(([id, spot]) => ({\n ...spot,\n id: spot.id ?? id,\n }));\n\n if (!state && localSpots.length === 0) {\n return null;\n }\n\n const next: LightingState = { ...(state ?? {}) };\n if (!state?.ambient) {\n delete next.ambient;\n }\n next.spots = [\n ...(state?.spots ?? []),\n ...localSpots,\n ];\n return next;\n });\n private manualClientPhysicsTick = false;\n private readonly isTestEnvironment: boolean;\n\n constructor() {\n super();\n // Détecter l'environnement de test\n const testGlobal = globalThis as TestGlobalScope;\n const isTest = testGlobal.process?.env?.TEST === 'true'\n || testGlobal.__RPGJS_TEST__ === true;\n this.isTestEnvironment = isTest;\n if (isTest) {\n this.autoTickEnabled = false;\n }\n }\n\n configureClientPrediction(enabled: boolean): void {\n this.manualClientPhysicsTick = enabled;\n this.autoTickEnabled = enabled ? false : !this.isTestEnvironment;\n }\n\n getCurrentPlayer() {\n return this.currentPlayer()\n }\n\n reset(force = false) {\n const currentPlayerId = this.engine.playerIdSignal();\n const currentPlayer = !force && currentPlayerId\n ? this.players()[currentPlayerId]\n : undefined;\n const players = this.players();\n const events = this.events();\n\n Object.entries(players).forEach(([id, player]) => {\n if (!player || (!force && id === currentPlayerId)) return;\n (player as any).resetAnimationState?.();\n });\n Object.values(events).forEach((event) => {\n (event as any)?.resetAnimationState?.();\n });\n\n this.players.set(\n currentPlayerId && currentPlayer ? { [currentPlayerId]: currentPlayer } : {}\n );\n this.events.set({})\n this.weatherState.set(null);\n this.localWeatherOverride.set(null);\n this.lightingState.set(null);\n this.localLightSpots.set({});\n this.clearPhysic()\n }\n\n getWeather(): WeatherState | null {\n return this.weather();\n }\n\n setLocalWeather(next: WeatherState | null): void {\n this.localWeatherOverride.set(next);\n }\n\n clearLocalWeather(): void {\n this.localWeatherOverride.set(null);\n }\n\n getLighting(): LightingState | null {\n return this.lighting();\n }\n\n addLightSpot(id: string, spot: LightSpot): void {\n const normalized = normalizeLightingState({ spots: [{ ...spot, id }] });\n const nextSpot = normalized?.spots?.[0];\n if (!nextSpot) {\n return;\n }\n this.localLightSpots.update((spots) => ({\n ...spots,\n [id]: nextSpot,\n }));\n }\n\n patchLightSpot(id: string, patch: Partial<LightSpot>): void {\n this.localLightSpots.update((spots) => {\n const current = spots[id];\n if (!current) {\n return spots;\n }\n return {\n ...spots,\n [id]: {\n ...current,\n ...patch,\n id,\n x: patch.x ?? current.x,\n y: patch.y ?? current.y,\n },\n };\n });\n }\n\n removeLightSpot(id: string): void {\n this.localLightSpots.update((spots) => {\n if (!(id in spots)) {\n return spots;\n }\n const next = { ...spots };\n delete next[id];\n return next;\n });\n }\n\n clearLightSpots(): void {\n this.localLightSpots.set({});\n }\n\n stepClientPhysics(deltaMs: number): number {\n if (!this.manualClientPhysicsTick) {\n return 0;\n }\n return this.nextTick(deltaMs);\n }\n\n stepPredictionTick(): void {\n this.forceSingleTick();\n }\n\n protected emitPhysicsInit(context: MapPhysicsInitContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsInit\", this, context);\n }\n\n protected emitPhysicsEntityAdd(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityAdd\", this, context);\n }\n\n protected emitPhysicsEntityRemove(context: MapPhysicsEntityContext): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsEntityRemove\", this, context);\n }\n\n protected emitPhysicsReset(): void {\n this.engine?.emitSceneMapHook?.(\"onPhysicsReset\", this);\n }\n}\n"],"mappings":"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"}
@@ -25,6 +25,8 @@ type FlashTriggerOptions = Omit<FlashOptions, "tint"> & {
25
25
  type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
26
26
  start(config?: T): Promise<void>;
27
27
  };
28
+ export declare const withGraphicDisplayScale: (spritesheet: any, scale: unknown) => any;
29
+ export declare const appendFramePayload: (current: unknown, items: unknown) => Frame[];
28
30
  export declare abstract class RpgClientObject extends RpgCommonPlayer {
29
31
  abstract _type: string;
30
32
  emitParticleTrigger: Trigger<any>;
@@ -2,8 +2,20 @@ import { inject } from "../core/inject.js";
2
2
  import { RpgClientEngine } from "../RpgClientEngine.js";
3
3
  import { signal, trigger } from "canvasengine";
4
4
  import { ModulesToken, RpgCommonPlayer } from "@rpgjs/common";
5
- import { from, map, of, switchMap } from "rxjs";
5
+ import { combineLatest, from, map, of, startWith, switchMap } from "rxjs";
6
6
  //#region src/Game/Object.ts
7
+ var withGraphicDisplayScale = (spritesheet, scale) => {
8
+ if (!spritesheet || typeof spritesheet !== "object") return spritesheet;
9
+ if (scale === void 0 || scale === null) return spritesheet;
10
+ return {
11
+ ...spritesheet,
12
+ displayScale: scale
13
+ };
14
+ };
15
+ var appendFramePayload = (current, items) => {
16
+ const nextFrames = (Array.isArray(items) ? items : items ? [items] : []).flatMap((item) => Array.isArray(item) ? item : [item]);
17
+ return (Array.isArray(current) ? current : []).concat(nextFrames);
18
+ };
7
19
  var RpgClientObject = class extends RpgCommonPlayer {
8
20
  constructor() {
9
21
  super();
@@ -18,12 +30,14 @@ var RpgClientObject = class extends RpgCommonPlayer {
18
30
  this.hooks.callHooks("client-sprite-onInit", this).subscribe();
19
31
  this._frames.observable.subscribe(({ items }) => {
20
32
  if (!this.id) return;
21
- const nextFrames = items.flatMap((item) => Array.isArray(item) ? item : [item]);
22
- this.frames = [...this.frames, ...nextFrames];
33
+ this.frames = appendFramePayload(this.frames, items);
23
34
  });
24
- this.graphics.observable.pipe(map(({ items }) => items), switchMap((graphics) => {
25
- if (graphics.length === 0) return of([]);
26
- return from(Promise.all(graphics.map((graphic) => this.engine.getSpriteSheet(graphic))));
35
+ combineLatest([this.graphics.observable.pipe(map(({ items }) => items)), this._graphicScale.observable.pipe(startWith({ value: this._graphicScale() }), map((payload) => payload?.value ?? payload))]).pipe(switchMap(([graphics, scale]) => {
36
+ const graphicRefs = Array.isArray(graphics) ? graphics : [];
37
+ if (graphicRefs.length === 0) return of([]);
38
+ return from(Promise.all(graphicRefs.map(async (graphic) => {
39
+ return withGraphicDisplayScale(await this.engine.getSpriteSheet(graphic), scale);
40
+ })));
27
41
  })).subscribe((sheets) => {
28
42
  this.graphicsSignals.set(sheets);
29
43
  });
@@ -70,12 +84,12 @@ var RpgClientObject = class extends RpgCommonPlayer {
70
84
  const restoreState = this.animationRestoreState;
71
85
  this.clearAnimationControls();
72
86
  this.animationCurrentIndex.set(0);
87
+ this.animationRestoreState = void 0;
88
+ this.animationIsPlaying.set(false);
73
89
  if (restoreState) {
74
90
  this.animationName.set(restoreState.animationName);
75
91
  this.graphics.set([...restoreState.graphics]);
76
92
  }
77
- this.animationRestoreState = void 0;
78
- this.animationIsPlaying.set(false);
79
93
  this.resolveAnimationWait();
80
94
  }
81
95
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"Object.js","names":[],"sources":["../../src/Game/Object.ts"],"sourcesContent":["import { Hooks, ModulesToken, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { trigger, signal, type Trigger } from \"canvasengine\";\nimport { from, map, of, Subscription, switchMap } from \"rxjs\";\nimport { inject } from \"../core/inject\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\ntype Frame = { x: number; y: number; ts: number };\n\ntype AnimationRestoreOptions = {\n restoreAnimationName?: string;\n restoreGraphics?: any[];\n timeoutMs?: number;\n};\n\ntype FlashType = 'alpha' | 'tint' | 'both';\n\ntype FlashOptions = {\n type?: FlashType;\n duration?: number;\n cycles?: number;\n alpha?: number;\n tint?: number | string;\n};\n\ntype FlashTriggerOptions = Omit<FlashOptions, \"tint\"> & {\n tint: number;\n};\n\ntype ConfigurableTrigger<T> = Omit<Trigger<T>, \"start\"> & {\n start(config?: T): Promise<void>;\n};\n\nexport abstract class RpgClientObject extends RpgCommonPlayer {\n abstract _type: string;\n emitParticleTrigger = trigger();\n particleName = signal(\"\");\n animationCurrentIndex = signal(0);\n animationIsPlaying = signal(false);\n _param = signal({});\n frames: Frame[] = [];\n graphicsSignals = signal<any[]>([]);\n flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();\n private animationRestoreState?: {\n animationName: string;\n graphics: any[];\n };\n\n constructor() {\n super();\n this.hooks.callHooks(\"client-sprite-onInit\", this).subscribe();\n\n this._frames.observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n const nextFrames = items.flatMap((item): Frame[] =>\n Array.isArray(item) ? item : [item]\n );\n this.frames = [...this.frames, ...nextFrames];\n });\n\n this.graphics.observable\n .pipe(\n map(({ items }) => items),\n switchMap(graphics => {\n if (graphics.length === 0) return of([]);\n return from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic))));\n })\n )\n .subscribe((sheets) => { \n this.graphicsSignals.set(sheets);\n });\n\n this.engine.tick\n .pipe\n //throttleTime(10)\n ()\n .subscribe(() => {\n const frame = this.frames.shift();\n if (frame) {\n if (typeof frame.x !== \"number\" || typeof frame.y !== \"number\") return;\n this.engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n }\n });\n }\n\n /**\n * Access the shared client hook registry.\n *\n * @returns The hook service used to register and trigger client-side hooks.\n */\n get hooks() {\n return inject<Hooks>(ModulesToken);\n }\n\n /**\n * Access the current client engine instance.\n *\n * @returns The active {@link RpgClientEngine} instance.\n */\n get engine() {\n return inject(RpgClientEngine);\n }\n\n private animationSubscription?: Subscription;\n private animationResetTimeout?: ReturnType<typeof setTimeout>;\n private animationWaitResolve?: () => void;\n\n private clearAnimationControls() {\n if (this.animationSubscription) {\n this.animationSubscription.unsubscribe();\n this.animationSubscription = undefined;\n }\n if (this.animationResetTimeout) {\n clearTimeout(this.animationResetTimeout);\n this.animationResetTimeout = undefined;\n }\n }\n\n private resolveAnimationWait() {\n const resolve = this.animationWaitResolve;\n this.animationWaitResolve = undefined;\n resolve?.();\n }\n\n private finishTemporaryAnimation() {\n const restoreState = this.animationRestoreState;\n this.clearAnimationControls();\n this.animationCurrentIndex.set(0);\n if (restoreState) {\n this.animationName.set(restoreState.animationName);\n this.graphics.set([...restoreState.graphics]);\n }\n this.animationRestoreState = undefined;\n this.animationIsPlaying.set(false);\n this.resolveAnimationWait();\n }\n\n /**\n * Trigger a flash animation on this sprite\n * \n * This method triggers a flash effect using CanvasEngine's flash directive.\n * The flash can be configured with various options including type (alpha, tint, or both),\n * duration, cycles, and color.\n * \n * ## Design\n * \n * The flash uses a trigger system that is connected to the flash directive in the\n * character component. This allows for flexible configuration and can be triggered\n * from both server events and client-side code.\n * \n * @param options - Flash configuration options\n * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')\n * @param options.duration - Duration of the flash animation in milliseconds (default: 300)\n * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)\n * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)\n * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)\n * \n * @example\n * ```ts\n * // Simple flash with default settings (alpha flash)\n * player.flash();\n * \n * // Flash with red tint\n * player.flash({ type: 'tint', tint: 0xff0000 });\n * \n * // Flash with both alpha and tint\n * player.flash({ \n * type: 'both', \n * alpha: 0.5, \n * tint: 0xff0000,\n * duration: 200,\n * cycles: 2\n * });\n * \n * // Quick damage flash\n * player.flash({ \n * type: 'tint', \n * tint: 0xff0000, \n * duration: 150,\n * cycles: 1\n * });\n * ```\n */\n flash(options?: FlashOptions): void {\n const flashOptions = {\n type: options?.type || 'alpha',\n duration: options?.duration ?? 300,\n cycles: options?.cycles ?? 1,\n alpha: options?.alpha ?? 0.3,\n tint: options?.tint ?? 0xffffff,\n };\n \n // Convert color name to hex if needed\n let tintValue = flashOptions.tint;\n if (typeof tintValue === 'string') {\n // Common color name to hex mapping\n const colorMap: Record<string, number> = {\n 'white': 0xffffff,\n 'red': 0xff0000,\n 'green': 0x00ff00,\n 'blue': 0x0000ff,\n 'yellow': 0xffff00,\n 'cyan': 0x00ffff,\n 'magenta': 0xff00ff,\n 'black': 0x000000,\n };\n tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;\n }\n \n this.flashTrigger.start({\n ...flashOptions,\n tint: tintValue,\n });\n }\n\n /**\n * Reset animation state when animation changes externally\n *\n * This method should be called when the animation changes due to movement\n * or other external factors to ensure the animation system doesn't get stuck\n *\n * @example\n * ```ts\n * // Reset when player starts moving\n * player.resetAnimationState();\n * ```\n */\n resetAnimationState() {\n if (this.animationRestoreState) {\n this.finishTemporaryAnimation();\n return;\n }\n this.animationIsPlaying.set(false);\n this.animationCurrentIndex.set(0);\n this.clearAnimationControls();\n this.resolveAnimationWait();\n }\n\n /**\n * Set a custom animation for a specific number of times\n *\n * Plays a custom animation for the specified number of repetitions.\n * The animation system prevents overlapping animations and automatically\n * returns to the previous animation when complete.\n *\n * @param animationName - Name of the animation to play\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation 3 times\n * await player.setAnimation('attack', 3);\n *\n * // Play continuous spell animation\n * player.setAnimation('spell');\n * ```\n */\n setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n /**\n * Set a custom animation with temporary graphic change\n *\n * Plays a custom animation for the specified number of repetitions and temporarily\n * changes the player's graphic (sprite sheet) during the animation. The graphic\n * is automatically reset when the animation finishes.\n *\n * @param animationName - Name of the animation to play\n * @param graphic - The graphic(s) to temporarily use during the animation\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation with temporary graphic change\n * await player.setAnimation('attack', 'hero_attack', 3);\n * ```\n */\n setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n setAnimation(\n animationName: string,\n graphicOrNbTimes?: string | string[] | number,\n nbTimesOrOptions?: number | AnimationRestoreOptions,\n options?: AnimationRestoreOptions\n ): Promise<void> {\n let graphic: string | string[] | undefined;\n let finalNbTimes: number = Infinity;\n let restoreOptions: AnimationRestoreOptions | undefined = options;\n\n // Handle overloads\n if (typeof graphicOrNbTimes === 'number') {\n // setAnimation(animationName, nbTimes)\n finalNbTimes = graphicOrNbTimes;\n restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;\n } else if (graphicOrNbTimes !== undefined) {\n // setAnimation(animationName, graphic, nbTimes)\n graphic = graphicOrNbTimes;\n if (typeof nbTimesOrOptions === 'number') {\n finalNbTimes = nbTimesOrOptions;\n } else {\n finalNbTimes = Infinity;\n restoreOptions = nbTimesOrOptions ?? options;\n }\n } else {\n // setAnimation(animationName) - nbTimes remains Infinity\n finalNbTimes = Infinity;\n }\n\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n\n const waitPromise =\n finalNbTimes === Infinity\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n this.animationWaitResolve = resolve;\n });\n\n this.animationIsPlaying.set(true);\n const previousAnimationName =\n restoreOptions?.restoreAnimationName ?? this.animationName();\n const previousGraphics = restoreOptions?.restoreGraphics\n ? [...restoreOptions.restoreGraphics]\n : [...this.graphics()];\n this.animationRestoreState = {\n animationName: previousAnimationName,\n graphics: previousGraphics,\n };\n this.animationCurrentIndex.set(0);\n\n // Temporarily change graphic if provided\n if (graphic !== undefined) {\n if (Array.isArray(graphic)) {\n this.graphics.set(graphic);\n } else {\n this.graphics.set([graphic]);\n }\n }\n\n this.clearAnimationControls();\n\n this.animationSubscription =\n this.animationCurrentIndex.observable.subscribe((index) => {\n if (index >= finalNbTimes) {\n this.finishTemporaryAnimation();\n }\n });\n\n if (finalNbTimes !== Infinity) {\n this.animationResetTimeout = setTimeout(() => {\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));\n }\n\n this.animationName.set(animationName);\n\n return waitPromise;\n }\n\n /**\n * Display a registered component animation effect on this object.\n *\n * @param id - Identifier of the component animation to play.\n * @param params - Parameters forwarded to the animation effect.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showComponentAnimation(id: string, params: any): Promise<void> {\n const engine = inject(RpgClientEngine);\n return engine.getComponentAnimation(id).displayEffect(params, this);\n }\n\n /**\n * Display a registered spritesheet animation effect on this object.\n *\n * @param graphic - Identifier of the spritesheet to use.\n * @param animationName - Name of the animation inside the spritesheet.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {\n return this.showComponentAnimation('animation', {\n graphic,\n animationName,\n });\n }\n \n /**\n * Check whether this client object represents an event.\n *\n * @returns `true` if the object type is `event`, otherwise `false`.\n */\n isEvent(): boolean {\n return this._type === 'event';\n }\n\n /**\n * Check whether this client object represents a player.\n *\n * @returns `true` if the object type is `player`, otherwise `false`.\n */\n isPlayer(): boolean {\n return this._type === 'player';\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Object.js","names":[],"sources":["../../src/Game/Object.ts"],"sourcesContent":["import { Hooks, ModulesToken, RpgCommonPlayer } from \"@rpgjs/common\";\nimport { trigger, signal, type Trigger } from \"canvasengine\";\nimport { combineLatest, from, map, of, startWith, Subscription, switchMap } from \"rxjs\";\nimport { inject } from \"../core/inject\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\ntype Frame = { x: number; y: number; ts: number };\n\ntype AnimationRestoreOptions = {\n restoreAnimationName?: string;\n restoreGraphics?: any[];\n timeoutMs?: number;\n};\n\ntype FlashType = 'alpha' | 'tint' | 'both';\n\ntype FlashOptions = {\n type?: FlashType;\n duration?: number;\n cycles?: number;\n alpha?: number;\n tint?: number | string;\n};\n\ntype FlashTriggerOptions = Omit<FlashOptions, \"tint\"> & {\n tint: number;\n};\n\ntype ConfigurableTrigger<T> = Omit<Trigger<T>, \"start\"> & {\n start(config?: T): Promise<void>;\n};\n\nexport const withGraphicDisplayScale = (spritesheet: any, scale: unknown): any => {\n if (!spritesheet || typeof spritesheet !== \"object\") return spritesheet;\n if (scale === undefined || scale === null) return spritesheet;\n return {\n ...spritesheet,\n displayScale: scale,\n };\n};\n\nexport const appendFramePayload = (current: unknown, items: unknown): Frame[] => {\n const frameItems = Array.isArray(items) ? items : items ? [items] : [];\n const nextFrames = frameItems.flatMap((item): Frame[] =>\n Array.isArray(item) ? item : [item as Frame]\n );\n const currentFrames = Array.isArray(current) ? current as Frame[] : [];\n return currentFrames.concat(nextFrames);\n};\n\nexport abstract class RpgClientObject extends RpgCommonPlayer {\n abstract _type: string;\n emitParticleTrigger = trigger();\n particleName = signal(\"\");\n animationCurrentIndex = signal(0);\n animationIsPlaying = signal(false);\n _param = signal({});\n frames: Frame[] = [];\n graphicsSignals = signal<any[]>([]);\n flashTrigger: ConfigurableTrigger<FlashTriggerOptions> = trigger<FlashTriggerOptions>();\n private animationRestoreState?: {\n animationName: string;\n graphics: any[];\n };\n\n constructor() {\n super();\n this.hooks.callHooks(\"client-sprite-onInit\", this).subscribe();\n\n this._frames.observable.subscribe(({ items }) => {\n if (!this.id) return;\n //if (this.id == this.engine.playerIdSignal()!) return;\n this.frames = appendFramePayload(this.frames, items);\n });\n\n const graphics$ = this.graphics.observable.pipe(map(({ items }) => items));\n const graphicScale$ = this._graphicScale.observable.pipe(\n startWith({ value: this._graphicScale() }),\n map((payload: any) => payload?.value ?? payload),\n );\n\n combineLatest([graphics$, graphicScale$])\n .pipe(\n switchMap(([graphics, scale]) => {\n const graphicRefs = Array.isArray(graphics) ? graphics : [];\n if (graphicRefs.length === 0) return of([]);\n return from(Promise.all(graphicRefs.map(async (graphic) => {\n const spritesheet = await this.engine.getSpriteSheet(graphic);\n return withGraphicDisplayScale(spritesheet, scale);\n })));\n })\n )\n .subscribe((sheets) => { \n this.graphicsSignals.set(sheets);\n });\n\n this.engine.tick\n .pipe\n //throttleTime(10)\n ()\n .subscribe(() => {\n const frame = this.frames.shift();\n if (frame) {\n if (typeof frame.x !== \"number\" || typeof frame.y !== \"number\") return;\n this.engine.scene.setBodyPosition(\n this.id,\n frame.x,\n frame.y,\n \"top-left\"\n );\n }\n });\n }\n\n /**\n * Access the shared client hook registry.\n *\n * @returns The hook service used to register and trigger client-side hooks.\n */\n get hooks() {\n return inject<Hooks>(ModulesToken);\n }\n\n /**\n * Access the current client engine instance.\n *\n * @returns The active {@link RpgClientEngine} instance.\n */\n get engine() {\n return inject(RpgClientEngine);\n }\n\n private animationSubscription?: Subscription;\n private animationResetTimeout?: ReturnType<typeof setTimeout>;\n private animationWaitResolve?: () => void;\n\n private clearAnimationControls() {\n if (this.animationSubscription) {\n this.animationSubscription.unsubscribe();\n this.animationSubscription = undefined;\n }\n if (this.animationResetTimeout) {\n clearTimeout(this.animationResetTimeout);\n this.animationResetTimeout = undefined;\n }\n }\n\n private resolveAnimationWait() {\n const resolve = this.animationWaitResolve;\n this.animationWaitResolve = undefined;\n resolve?.();\n }\n\n private finishTemporaryAnimation() {\n const restoreState = this.animationRestoreState;\n this.clearAnimationControls();\n this.animationCurrentIndex.set(0);\n this.animationRestoreState = undefined;\n this.animationIsPlaying.set(false);\n if (restoreState) {\n this.animationName.set(restoreState.animationName);\n this.graphics.set([...restoreState.graphics]);\n }\n this.resolveAnimationWait();\n }\n\n /**\n * Trigger a flash animation on this sprite\n * \n * This method triggers a flash effect using CanvasEngine's flash directive.\n * The flash can be configured with various options including type (alpha, tint, or both),\n * duration, cycles, and color.\n * \n * ## Design\n * \n * The flash uses a trigger system that is connected to the flash directive in the\n * character component. This allows for flexible configuration and can be triggered\n * from both server events and client-side code.\n * \n * @param options - Flash configuration options\n * @param options.type - Type of flash effect: 'alpha' (opacity), 'tint' (color), or 'both' (default: 'alpha')\n * @param options.duration - Duration of the flash animation in milliseconds (default: 300)\n * @param options.cycles - Number of flash cycles (flash on/off) (default: 1)\n * @param options.alpha - Alpha value when flashing, from 0 to 1 (default: 0.3)\n * @param options.tint - Tint color when flashing as hex value or color name (default: 0xffffff - white)\n * \n * @example\n * ```ts\n * // Simple flash with default settings (alpha flash)\n * player.flash();\n * \n * // Flash with red tint\n * player.flash({ type: 'tint', tint: 0xff0000 });\n * \n * // Flash with both alpha and tint\n * player.flash({ \n * type: 'both', \n * alpha: 0.5, \n * tint: 0xff0000,\n * duration: 200,\n * cycles: 2\n * });\n * \n * // Quick damage flash\n * player.flash({ \n * type: 'tint', \n * tint: 0xff0000, \n * duration: 150,\n * cycles: 1\n * });\n * ```\n */\n flash(options?: FlashOptions): void {\n const flashOptions = {\n type: options?.type || 'alpha',\n duration: options?.duration ?? 300,\n cycles: options?.cycles ?? 1,\n alpha: options?.alpha ?? 0.3,\n tint: options?.tint ?? 0xffffff,\n };\n \n // Convert color name to hex if needed\n let tintValue = flashOptions.tint;\n if (typeof tintValue === 'string') {\n // Common color name to hex mapping\n const colorMap: Record<string, number> = {\n 'white': 0xffffff,\n 'red': 0xff0000,\n 'green': 0x00ff00,\n 'blue': 0x0000ff,\n 'yellow': 0xffff00,\n 'cyan': 0x00ffff,\n 'magenta': 0xff00ff,\n 'black': 0x000000,\n };\n tintValue = colorMap[tintValue.toLowerCase()] ?? 0xffffff;\n }\n \n this.flashTrigger.start({\n ...flashOptions,\n tint: tintValue,\n });\n }\n\n /**\n * Reset animation state when animation changes externally\n *\n * This method should be called when the animation changes due to movement\n * or other external factors to ensure the animation system doesn't get stuck\n *\n * @example\n * ```ts\n * // Reset when player starts moving\n * player.resetAnimationState();\n * ```\n */\n resetAnimationState() {\n if (this.animationRestoreState) {\n this.finishTemporaryAnimation();\n return;\n }\n this.animationIsPlaying.set(false);\n this.animationCurrentIndex.set(0);\n this.clearAnimationControls();\n this.resolveAnimationWait();\n }\n\n /**\n * Set a custom animation for a specific number of times\n *\n * Plays a custom animation for the specified number of repetitions.\n * The animation system prevents overlapping animations and automatically\n * returns to the previous animation when complete.\n *\n * @param animationName - Name of the animation to play\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation 3 times\n * await player.setAnimation('attack', 3);\n *\n * // Play continuous spell animation\n * player.setAnimation('spell');\n * ```\n */\n setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n /**\n * Set a custom animation with temporary graphic change\n *\n * Plays a custom animation for the specified number of repetitions and temporarily\n * changes the player's graphic (sprite sheet) during the animation. The graphic\n * is automatically reset when the animation finishes.\n *\n * @param animationName - Name of the animation to play\n * @param graphic - The graphic(s) to temporarily use during the animation\n * @param nbTimes - Number of times to repeat the animation (default: Infinity for continuous)\n * @param options - Restore and timeout options\n * @returns A promise resolved when a finite animation finishes, is interrupted, or times out\n *\n * @example\n * ```ts\n * // Play attack animation with temporary graphic change\n * await player.setAnimation('attack', 'hero_attack', 3);\n * ```\n */\n setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): Promise<void>;\n setAnimation(\n animationName: string,\n graphicOrNbTimes?: string | string[] | number,\n nbTimesOrOptions?: number | AnimationRestoreOptions,\n options?: AnimationRestoreOptions\n ): Promise<void> {\n let graphic: string | string[] | undefined;\n let finalNbTimes: number = Infinity;\n let restoreOptions: AnimationRestoreOptions | undefined = options;\n\n // Handle overloads\n if (typeof graphicOrNbTimes === 'number') {\n // setAnimation(animationName, nbTimes)\n finalNbTimes = graphicOrNbTimes;\n restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;\n } else if (graphicOrNbTimes !== undefined) {\n // setAnimation(animationName, graphic, nbTimes)\n graphic = graphicOrNbTimes;\n if (typeof nbTimesOrOptions === 'number') {\n finalNbTimes = nbTimesOrOptions;\n } else {\n finalNbTimes = Infinity;\n restoreOptions = nbTimesOrOptions ?? options;\n }\n } else {\n // setAnimation(animationName) - nbTimes remains Infinity\n finalNbTimes = Infinity;\n }\n\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n\n const waitPromise =\n finalNbTimes === Infinity\n ? Promise.resolve()\n : new Promise<void>((resolve) => {\n this.animationWaitResolve = resolve;\n });\n\n this.animationIsPlaying.set(true);\n const previousAnimationName =\n restoreOptions?.restoreAnimationName ?? this.animationName();\n const previousGraphics = restoreOptions?.restoreGraphics\n ? [...restoreOptions.restoreGraphics]\n : [...this.graphics()];\n this.animationRestoreState = {\n animationName: previousAnimationName,\n graphics: previousGraphics,\n };\n this.animationCurrentIndex.set(0);\n\n // Temporarily change graphic if provided\n if (graphic !== undefined) {\n if (Array.isArray(graphic)) {\n this.graphics.set(graphic);\n } else {\n this.graphics.set([graphic]);\n }\n }\n\n this.clearAnimationControls();\n\n this.animationSubscription =\n this.animationCurrentIndex.observable.subscribe((index) => {\n if (index >= finalNbTimes) {\n this.finishTemporaryAnimation();\n }\n });\n\n if (finalNbTimes !== Infinity) {\n this.animationResetTimeout = setTimeout(() => {\n if (this.animationIsPlaying()) {\n this.finishTemporaryAnimation();\n }\n }, restoreOptions?.timeoutMs ?? Math.max(1000, finalNbTimes * 1000));\n }\n\n this.animationName.set(animationName);\n\n return waitPromise;\n }\n\n /**\n * Display a registered component animation effect on this object.\n *\n * @param id - Identifier of the component animation to play.\n * @param params - Parameters forwarded to the animation effect.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showComponentAnimation(id: string, params: any): Promise<void> {\n const engine = inject(RpgClientEngine);\n return engine.getComponentAnimation(id).displayEffect(params, this);\n }\n\n /**\n * Display a registered spritesheet animation effect on this object.\n *\n * @param graphic - Identifier of the spritesheet to use.\n * @param animationName - Name of the animation inside the spritesheet.\n * @returns A promise resolved when the animation component calls `onFinish`.\n */\n showAnimation(graphic: string, animationName: string = 'default'): Promise<void> {\n return this.showComponentAnimation('animation', {\n graphic,\n animationName,\n });\n }\n \n /**\n * Check whether this client object represents an event.\n *\n * @returns `true` if the object type is `event`, otherwise `false`.\n */\n isEvent(): boolean {\n return this._type === 'event';\n }\n\n /**\n * Check whether this client object represents a player.\n *\n * @returns `true` if the object type is `player`, otherwise `false`.\n */\n isPlayer(): boolean {\n return this._type === 'player';\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -61,6 +61,7 @@ export interface ProjectileSpawnClock {
61
61
  now?: number;
62
62
  currentServerTick?: number;
63
63
  tickDurationMs?: number;
64
+ mapId?: string;
64
65
  }
65
66
  export declare class ProjectileManager {
66
67
  private readonly hooks;
@@ -69,16 +70,24 @@ export declare class ProjectileManager {
69
70
  private readonly projectiles;
70
71
  private readonly version;
71
72
  private readonly impactDurationMs;
73
+ private mapId?;
72
74
  constructor(hooks: Hooks, predictionResolver?: ProjectilePredictionResolver | undefined);
73
75
  current: import('canvasengine').ComputedSignal<RenderedProjectile[]>;
74
76
  register(type: string, component: any): any;
75
77
  get(type: string): any;
78
+ setMapId(mapId: string | undefined): void;
79
+ getMapId(): string | undefined;
76
80
  spawnBatch(projectiles: ClientProjectileSpawn[], clock?: ProjectileSpawnClock): void;
77
- impactBatch(impacts: ClientProjectileImpact[]): void;
78
- destroyBatch(projectiles: ClientProjectileDestroy[]): void;
81
+ impactBatch(impacts: ClientProjectileImpact[], context?: {
82
+ mapId?: string;
83
+ }): void;
84
+ destroyBatch(projectiles: ClientProjectileDestroy[], context?: {
85
+ mapId?: string;
86
+ }): void;
79
87
  clear(): void;
80
88
  step(): void;
81
89
  private toProps;
90
+ private acceptsMap;
82
91
  private isWaitingForDelay;
83
92
  private setPredictedImpact;
84
93
  private getActivePredictedImpact;
@@ -1,3 +1,4 @@
1
+ import { normalizeRoomMapId } from "../utils/mapId.js";
1
2
  import { computed, signal } from "canvasengine";
2
3
  //#region src/Game/ProjectileManager.ts
3
4
  var ProjectileManager = class {
@@ -32,7 +33,17 @@ var ProjectileManager = class {
32
33
  get(type) {
33
34
  return this.components.get(type);
34
35
  }
36
+ setMapId(mapId) {
37
+ const normalizedMapId = normalizeRoomMapId(mapId);
38
+ if (this.mapId === normalizedMapId) return;
39
+ this.mapId = normalizedMapId;
40
+ this.clear();
41
+ }
42
+ getMapId() {
43
+ return this.mapId;
44
+ }
35
45
  spawnBatch(projectiles, clock = {}) {
46
+ if (!this.acceptsMap(clock.mapId)) return;
36
47
  const now = clock.now ?? Date.now();
37
48
  for (const projectile of projectiles) {
38
49
  const component = this.components.get(projectile.type);
@@ -53,7 +64,8 @@ var ProjectileManager = class {
53
64
  }
54
65
  this.touch();
55
66
  }
56
- impactBatch(impacts) {
67
+ impactBatch(impacts, context = {}) {
68
+ if (!this.acceptsMap(context.mapId)) return;
57
69
  const now = Date.now();
58
70
  for (const impact of impacts) {
59
71
  const projectile = this.projectiles.get(impact.id);
@@ -63,7 +75,8 @@ var ProjectileManager = class {
63
75
  }
64
76
  this.touch();
65
77
  }
66
- destroyBatch(projectiles) {
78
+ destroyBatch(projectiles, context = {}) {
79
+ if (!this.acceptsMap(context.mapId)) return;
67
80
  const now = Date.now();
68
81
  for (const destroyed of projectiles) {
69
82
  const projectile = this.projectiles.get(destroyed.id);
@@ -127,6 +140,10 @@ var ProjectileManager = class {
127
140
  ttl
128
141
  };
129
142
  }
143
+ acceptsMap(mapId) {
144
+ const normalizedMapId = normalizeRoomMapId(mapId);
145
+ return !normalizedMapId || !this.mapId || normalizedMapId === this.mapId;
146
+ }
130
147
  isWaitingForDelay(projectile, now) {
131
148
  const delayMs = (projectile.spawn.delay ?? 0) * 1e3;
132
149
  return now - projectile.createdAt - delayMs < 0;