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

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 (118) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/Game/ProjectileManager.d.ts +89 -0
  3. package/dist/Game/ProjectileManager.js +179 -0
  4. package/dist/Game/ProjectileManager.js.map +1 -0
  5. package/dist/Game/ProjectileManager.spec.d.ts +1 -0
  6. package/dist/RpgClient.d.ts +53 -13
  7. package/dist/RpgClientEngine.d.ts +25 -4
  8. package/dist/RpgClientEngine.js +197 -48
  9. package/dist/RpgClientEngine.js.map +1 -1
  10. package/dist/components/animations/hit.ce.js.map +1 -1
  11. package/dist/components/character.ce.js +32 -30
  12. package/dist/components/character.ce.js.map +1 -1
  13. package/dist/components/dynamics/bar.ce.js +4 -3
  14. package/dist/components/dynamics/bar.ce.js.map +1 -1
  15. package/dist/components/dynamics/image.ce.js +2 -1
  16. package/dist/components/dynamics/image.ce.js.map +1 -1
  17. package/dist/components/dynamics/shape.ce.js +3 -2
  18. package/dist/components/dynamics/shape.ce.js.map +1 -1
  19. package/dist/components/dynamics/text.ce.js +9 -8
  20. package/dist/components/dynamics/text.ce.js.map +1 -1
  21. package/dist/components/gui/dialogbox/index.ce.js +3 -2
  22. package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
  23. package/dist/components/gui/gameover.ce.js +3 -2
  24. package/dist/components/gui/gameover.ce.js.map +1 -1
  25. package/dist/components/gui/hud/hud.ce.js.map +1 -1
  26. package/dist/components/gui/menu/equip-menu.ce.js +2 -1
  27. package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
  28. package/dist/components/gui/menu/exit-menu.ce.js +2 -1
  29. package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
  30. package/dist/components/gui/menu/items-menu.ce.js +3 -2
  31. package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
  32. package/dist/components/gui/menu/main-menu.ce.js +3 -2
  33. package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
  34. package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
  35. package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
  36. package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
  37. package/dist/components/gui/notification/notification.ce.js.map +1 -1
  38. package/dist/components/gui/save-load.ce.js +2 -1
  39. package/dist/components/gui/save-load.ce.js.map +1 -1
  40. package/dist/components/gui/shop/shop.ce.js +3 -2
  41. package/dist/components/gui/shop/shop.ce.js.map +1 -1
  42. package/dist/components/gui/title-screen.ce.js +3 -2
  43. package/dist/components/gui/title-screen.ce.js.map +1 -1
  44. package/dist/components/index.d.ts +2 -1
  45. package/dist/components/index.js +1 -0
  46. package/dist/components/player-components.ce.js +11 -10
  47. package/dist/components/player-components.ce.js.map +1 -1
  48. package/dist/components/prebuilt/hp-bar.ce.js +4 -3
  49. package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
  50. package/dist/components/prebuilt/light-halo.ce.js +2 -1
  51. package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
  52. package/dist/components/scenes/canvas.ce.js +12 -4
  53. package/dist/components/scenes/canvas.ce.js.map +1 -1
  54. package/dist/components/scenes/draw-map.ce.js +6 -3
  55. package/dist/components/scenes/draw-map.ce.js.map +1 -1
  56. package/dist/components/scenes/event-layer.ce.js.map +1 -1
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.js +9 -5
  59. package/dist/module.js +11 -0
  60. package/dist/module.js.map +1 -1
  61. package/dist/services/actionInput.d.ts +12 -0
  62. package/dist/services/actionInput.js +27 -0
  63. package/dist/services/actionInput.js.map +1 -0
  64. package/dist/services/actionInput.spec.d.ts +1 -0
  65. package/dist/services/mmorpg-connection.d.ts +5 -0
  66. package/dist/services/mmorpg-connection.js +50 -0
  67. package/dist/services/mmorpg-connection.js.map +1 -0
  68. package/dist/services/mmorpg-connection.spec.d.ts +1 -0
  69. package/dist/services/mmorpg.d.ts +10 -4
  70. package/dist/services/mmorpg.js +48 -30
  71. package/dist/services/mmorpg.js.map +1 -1
  72. package/dist/services/pointerContext.d.ts +11 -0
  73. package/dist/services/pointerContext.js +48 -0
  74. package/dist/services/pointerContext.js.map +1 -0
  75. package/dist/services/pointerContext.spec.d.ts +1 -0
  76. package/dist/services/standalone-message.d.ts +1 -0
  77. package/dist/services/standalone-message.js +9 -0
  78. package/dist/services/standalone-message.js.map +1 -0
  79. package/dist/services/standalone.js +3 -2
  80. package/dist/services/standalone.js.map +1 -1
  81. package/dist/services/standalone.spec.d.ts +1 -0
  82. package/package.json +7 -7
  83. package/src/Game/ProjectileManager.spec.ts +338 -0
  84. package/src/Game/ProjectileManager.ts +324 -0
  85. package/src/RpgClient.ts +62 -15
  86. package/src/RpgClientEngine.ts +287 -65
  87. package/src/components/character.ce +34 -32
  88. package/src/components/dynamics/bar.ce +4 -3
  89. package/src/components/dynamics/image.ce +2 -1
  90. package/src/components/dynamics/shape.ce +3 -2
  91. package/src/components/dynamics/text.ce +9 -8
  92. package/src/components/gui/dialogbox/index.ce +3 -2
  93. package/src/components/gui/gameover.ce +2 -1
  94. package/src/components/gui/menu/equip-menu.ce +2 -1
  95. package/src/components/gui/menu/exit-menu.ce +2 -1
  96. package/src/components/gui/menu/items-menu.ce +3 -2
  97. package/src/components/gui/menu/main-menu.ce +2 -1
  98. package/src/components/gui/save-load.ce +2 -1
  99. package/src/components/gui/shop/shop.ce +3 -2
  100. package/src/components/gui/title-screen.ce +2 -1
  101. package/src/components/index.ts +2 -1
  102. package/src/components/player-components.ce +11 -10
  103. package/src/components/prebuilt/hp-bar.ce +4 -3
  104. package/src/components/prebuilt/light-halo.ce +2 -2
  105. package/src/components/scenes/canvas.ce +10 -2
  106. package/src/components/scenes/draw-map.ce +17 -3
  107. package/src/index.ts +3 -0
  108. package/src/module.ts +13 -0
  109. package/src/services/actionInput.spec.ts +101 -0
  110. package/src/services/actionInput.ts +53 -0
  111. package/src/services/mmorpg-connection.spec.ts +99 -0
  112. package/src/services/mmorpg-connection.ts +69 -0
  113. package/src/services/mmorpg.ts +60 -34
  114. package/src/services/pointerContext.spec.ts +36 -0
  115. package/src/services/pointerContext.ts +84 -0
  116. package/src/services/standalone-message.ts +7 -0
  117. package/src/services/standalone.spec.ts +34 -0
  118. package/src/services/standalone.ts +3 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @rpgjs/client
2
2
 
3
+ ## 5.0.0-beta.11
4
+
5
+ ### Patch Changes
6
+
7
+ - Add projectile runtime support with client-side prediction, action input payload handling, pointer context helpers, standalone message handling, and MMORPG connection authentication.
8
+
9
+ Add composable CanvasEngine scene map components and update built-in GUI/dynamic components for the current CanvasEngine release.
10
+
11
+ - @rpgjs/common@5.0.0-beta.11
12
+ - @rpgjs/server@5.0.0-beta.11
13
+ - @rpgjs/ui-css@5.0.0-beta.11
14
+
3
15
  ## 5.0.0-beta.10
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,89 @@
1
+ import { Hooks } from '@rpgjs/common';
2
+ export interface ClientProjectileSpawn {
3
+ id: string;
4
+ type: string;
5
+ ownerId?: string;
6
+ origin: {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ direction: {
11
+ x: number;
12
+ y: number;
13
+ };
14
+ speed: number;
15
+ range: number;
16
+ ttl: number;
17
+ spawnTick: number;
18
+ delay?: number;
19
+ index?: number;
20
+ count?: number;
21
+ params?: Record<string, unknown>;
22
+ collisionMask?: number;
23
+ ignoreOwner?: boolean;
24
+ predictImpact?: boolean;
25
+ }
26
+ export interface ClientProjectileImpact {
27
+ id: string;
28
+ targetId?: string;
29
+ x: number;
30
+ y: number;
31
+ distance?: number;
32
+ }
33
+ export interface ClientProjectileDestroy {
34
+ id: string;
35
+ reason?: string;
36
+ targetId?: string;
37
+ x?: number;
38
+ y?: number;
39
+ distance?: number;
40
+ }
41
+ export interface RenderedProjectileProps extends ClientProjectileSpawn {
42
+ x: number;
43
+ y: number;
44
+ angle: number;
45
+ distance: number;
46
+ elapsed: number;
47
+ progress: number;
48
+ impact?: ClientProjectileImpact;
49
+ impactElapsed?: number;
50
+ impactProgress?: number;
51
+ destroyed?: boolean;
52
+ }
53
+ export interface RenderedProjectile {
54
+ id: string;
55
+ type: string;
56
+ component: any;
57
+ props: RenderedProjectileProps;
58
+ }
59
+ export type ProjectilePredictionResolver = (projectile: ClientProjectileSpawn) => ClientProjectileImpact | null | undefined;
60
+ export interface ProjectileSpawnClock {
61
+ now?: number;
62
+ currentServerTick?: number;
63
+ tickDurationMs?: number;
64
+ }
65
+ export declare class ProjectileManager {
66
+ private readonly hooks;
67
+ private readonly predictionResolver?;
68
+ private readonly components;
69
+ private readonly projectiles;
70
+ private readonly version;
71
+ private readonly impactDurationMs;
72
+ constructor(hooks: Hooks, predictionResolver?: ProjectilePredictionResolver | undefined);
73
+ current: import('canvasengine').ComputedSignal<RenderedProjectile[]>;
74
+ register(type: string, component: any): any;
75
+ get(type: string): any;
76
+ spawnBatch(projectiles: ClientProjectileSpawn[], clock?: ProjectileSpawnClock): void;
77
+ impactBatch(impacts: ClientProjectileImpact[]): void;
78
+ destroyBatch(projectiles: ClientProjectileDestroy[]): void;
79
+ clear(): void;
80
+ step(): void;
81
+ private toProps;
82
+ private isWaitingForDelay;
83
+ private setPredictedImpact;
84
+ private getActivePredictedImpact;
85
+ private setImpact;
86
+ private resolveVisualImpact;
87
+ private isSameTarget;
88
+ private touch;
89
+ }
@@ -0,0 +1,179 @@
1
+ import { computed, signal } from "canvasengine";
2
+ //#region src/Game/ProjectileManager.ts
3
+ var ProjectileManager = class {
4
+ constructor(hooks, predictionResolver) {
5
+ this.hooks = hooks;
6
+ this.predictionResolver = predictionResolver;
7
+ this.components = /* @__PURE__ */ new Map();
8
+ this.projectiles = /* @__PURE__ */ new Map();
9
+ this.version = signal(0);
10
+ this.impactDurationMs = 350;
11
+ this.current = computed(() => {
12
+ this.version();
13
+ const now = Date.now();
14
+ const rendered = [];
15
+ for (const projectile of this.projectiles.values()) {
16
+ const props = this.toProps(projectile, now);
17
+ if (!props) continue;
18
+ rendered.push({
19
+ id: projectile.spawn.id,
20
+ type: projectile.spawn.type,
21
+ component: projectile.component,
22
+ props
23
+ });
24
+ }
25
+ return rendered;
26
+ });
27
+ }
28
+ register(type, component) {
29
+ this.components.set(type, component);
30
+ return component;
31
+ }
32
+ get(type) {
33
+ return this.components.get(type);
34
+ }
35
+ spawnBatch(projectiles, clock = {}) {
36
+ const now = clock.now ?? Date.now();
37
+ for (const projectile of projectiles) {
38
+ const component = this.components.get(projectile.type);
39
+ if (!component) continue;
40
+ const runtime = {
41
+ spawn: {
42
+ ...projectile,
43
+ delay: projectile.delay ?? 0,
44
+ index: projectile.index ?? 0,
45
+ count: projectile.count ?? 1
46
+ },
47
+ component,
48
+ createdAt: now
49
+ };
50
+ this.setPredictedImpact(runtime);
51
+ this.projectiles.set(projectile.id, runtime);
52
+ this.hooks.callHooks("client-projectiles-onSpawn", runtime.spawn).subscribe();
53
+ }
54
+ this.touch();
55
+ }
56
+ impactBatch(impacts) {
57
+ const now = Date.now();
58
+ for (const impact of impacts) {
59
+ const projectile = this.projectiles.get(impact.id);
60
+ if (!projectile) continue;
61
+ this.setImpact(projectile, impact, now);
62
+ this.hooks.callHooks("client-projectiles-onImpact", this.toProps(projectile, now)).subscribe();
63
+ }
64
+ this.touch();
65
+ }
66
+ destroyBatch(projectiles) {
67
+ const now = Date.now();
68
+ for (const destroyed of projectiles) {
69
+ const projectile = this.projectiles.get(destroyed.id);
70
+ if (!projectile) continue;
71
+ if (destroyed.reason === "hit") {
72
+ const current = this.toProps(projectile, now);
73
+ this.setImpact(projectile, {
74
+ id: destroyed.id,
75
+ targetId: destroyed.targetId ?? projectile.impact?.targetId,
76
+ x: destroyed.x ?? projectile.impact?.x ?? current?.x ?? projectile.spawn.origin.x,
77
+ y: destroyed.y ?? projectile.impact?.y ?? current?.y ?? projectile.spawn.origin.y,
78
+ distance: destroyed.distance ?? projectile.impact?.distance ?? current?.distance
79
+ }, now);
80
+ }
81
+ projectile.destroyReason = destroyed.reason;
82
+ projectile.destroyAt = projectile.destroyAt ?? (projectile.impact && projectile.impactStartedAt !== void 0 ? projectile.impactStartedAt + this.impactDurationMs : now);
83
+ this.hooks.callHooks("client-projectiles-onDestroy", this.toProps(projectile, now)).subscribe();
84
+ }
85
+ this.touch();
86
+ }
87
+ clear() {
88
+ this.projectiles.clear();
89
+ this.touch();
90
+ }
91
+ step() {
92
+ const now = Date.now();
93
+ let changed = false;
94
+ for (const [id, projectile] of this.projectiles) if (!this.toProps(projectile, now) && !this.isWaitingForDelay(projectile, now) || projectile.destroyAt !== void 0 && now >= projectile.destroyAt) {
95
+ this.projectiles.delete(id);
96
+ changed = true;
97
+ }
98
+ this.touch(changed || this.projectiles.size > 0);
99
+ }
100
+ toProps(projectile, now) {
101
+ const spawn = projectile.spawn;
102
+ const delayMs = (spawn.delay ?? 0) * 1e3;
103
+ const elapsedMs = now - projectile.createdAt - delayMs;
104
+ if (elapsedMs < 0) return null;
105
+ const elapsed = elapsedMs / 1e3;
106
+ const ttl = Math.max(.001, spawn.ttl);
107
+ const rawDistance = Math.min(spawn.speed * elapsed, spawn.range);
108
+ const predictedImpact = this.getActivePredictedImpact(projectile, now, rawDistance);
109
+ const visualImpact = projectile.visualImpact ?? projectile.impact;
110
+ const distance = visualImpact?.distance ?? predictedImpact?.distance ?? rawDistance;
111
+ const progress = Math.min(1, distance / spawn.range);
112
+ const x = visualImpact?.x ?? predictedImpact?.x ?? spawn.origin.x + spawn.direction.x * distance;
113
+ const y = visualImpact?.y ?? predictedImpact?.y ?? spawn.origin.y + spawn.direction.y * distance;
114
+ const impactElapsedMs = projectile.impactStartedAt !== void 0 ? Math.max(0, now - projectile.impactStartedAt) : void 0;
115
+ return {
116
+ ...spawn,
117
+ x,
118
+ y,
119
+ angle: Math.atan2(spawn.direction.y, spawn.direction.x),
120
+ distance,
121
+ elapsed,
122
+ progress,
123
+ impact: projectile.impact,
124
+ impactElapsed: impactElapsedMs === void 0 ? void 0 : impactElapsedMs / 1e3,
125
+ impactProgress: impactElapsedMs === void 0 ? void 0 : Math.min(1, impactElapsedMs / this.impactDurationMs),
126
+ destroyed: projectile.destroyAt !== void 0,
127
+ ttl
128
+ };
129
+ }
130
+ isWaitingForDelay(projectile, now) {
131
+ const delayMs = (projectile.spawn.delay ?? 0) * 1e3;
132
+ return now - projectile.createdAt - delayMs < 0;
133
+ }
134
+ setPredictedImpact(projectile) {
135
+ if (projectile.spawn.predictImpact === false) return;
136
+ const impact = this.predictionResolver?.(projectile.spawn);
137
+ if (!impact || !Number.isFinite(impact.x) || !Number.isFinite(impact.y)) return;
138
+ const distance = typeof impact.distance === "number" && Number.isFinite(impact.distance) ? impact.distance : Math.hypot(impact.x - projectile.spawn.origin.x, impact.y - projectile.spawn.origin.y);
139
+ if (!Number.isFinite(distance) || distance < 0 || distance > projectile.spawn.range) return;
140
+ projectile.predictedImpact = {
141
+ ...impact,
142
+ distance
143
+ };
144
+ }
145
+ getActivePredictedImpact(projectile, now, rawDistance) {
146
+ if (!projectile.predictedImpact || projectile.impact) return;
147
+ const distance = projectile.predictedImpact.distance;
148
+ if (distance === void 0 || rawDistance < distance) return;
149
+ return projectile.predictedImpact;
150
+ }
151
+ setImpact(projectile, impact, now) {
152
+ projectile.visualImpact = this.resolveVisualImpact(projectile, impact, now);
153
+ projectile.impact = impact;
154
+ projectile.predictedImpact = void 0;
155
+ projectile.impactStartedAt = projectile.impactStartedAt ?? now;
156
+ const impactDestroyAt = projectile.impactStartedAt + this.impactDurationMs;
157
+ projectile.destroyAt = Math.max(projectile.destroyAt ?? 0, impactDestroyAt);
158
+ }
159
+ resolveVisualImpact(projectile, impact, now) {
160
+ const predicted = projectile.predictedImpact;
161
+ if (!predicted || !this.isSameTarget(predicted, impact)) return impact;
162
+ const distance = predicted.distance;
163
+ if (distance === void 0) return impact;
164
+ const delayMs = (projectile.spawn.delay ?? 0) * 1e3;
165
+ const elapsedMs = now - projectile.createdAt - delayMs;
166
+ if (elapsedMs < 0) return impact;
167
+ return Math.min(projectile.spawn.speed * (elapsedMs / 1e3), projectile.spawn.range) >= distance ? predicted : impact;
168
+ }
169
+ isSameTarget(a, b) {
170
+ return a.targetId !== void 0 && a.targetId === b.targetId;
171
+ }
172
+ touch(force = true) {
173
+ if (force) this.version.update((value) => value + 1);
174
+ }
175
+ };
176
+ //#endregion
177
+ export { ProjectileManager };
178
+
179
+ //# sourceMappingURL=ProjectileManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectileManager.js","names":[],"sources":["../../src/Game/ProjectileManager.ts"],"sourcesContent":["import { computed, signal } from \"canvasengine\";\nimport { Hooks } from \"@rpgjs/common\";\n\nexport interface ClientProjectileSpawn {\n id: string;\n type: string;\n ownerId?: string;\n origin: { x: number; y: number };\n direction: { x: number; y: number };\n speed: number;\n range: number;\n ttl: number;\n spawnTick: number;\n delay?: number;\n index?: number;\n count?: number;\n params?: Record<string, unknown>;\n collisionMask?: number;\n ignoreOwner?: boolean;\n predictImpact?: boolean;\n}\n\nexport interface ClientProjectileImpact {\n id: string;\n targetId?: string;\n x: number;\n y: number;\n distance?: number;\n}\n\nexport interface ClientProjectileDestroy {\n id: string;\n reason?: string;\n targetId?: string;\n x?: number;\n y?: number;\n distance?: number;\n}\n\nexport interface RenderedProjectileProps extends ClientProjectileSpawn {\n x: number;\n y: number;\n angle: number;\n distance: number;\n elapsed: number;\n progress: number;\n impact?: ClientProjectileImpact;\n impactElapsed?: number;\n impactProgress?: number;\n destroyed?: boolean;\n}\n\nexport interface RenderedProjectile {\n id: string;\n type: string;\n component: any;\n props: RenderedProjectileProps;\n}\n\nexport type ProjectilePredictionResolver = (\n projectile: ClientProjectileSpawn,\n) => ClientProjectileImpact | null | undefined;\n\nexport interface ProjectileSpawnClock {\n now?: number;\n currentServerTick?: number;\n tickDurationMs?: number;\n}\n\ninterface RuntimeProjectile {\n spawn: ClientProjectileSpawn;\n component: any;\n createdAt: number;\n impact?: ClientProjectileImpact;\n visualImpact?: ClientProjectileImpact;\n predictedImpact?: ClientProjectileImpact;\n impactStartedAt?: number;\n destroyAt?: number;\n destroyReason?: string;\n}\n\nexport class ProjectileManager {\n private readonly components = new Map<string, any>();\n private readonly projectiles = new Map<string, RuntimeProjectile>();\n private readonly version = signal(0);\n private readonly impactDurationMs = 350;\n\n constructor(\n private readonly hooks: Hooks,\n private readonly predictionResolver?: ProjectilePredictionResolver,\n ) {}\n\n current = computed<RenderedProjectile[]>(() => {\n this.version();\n const now = Date.now();\n const rendered: RenderedProjectile[] = [];\n for (const projectile of this.projectiles.values()) {\n const props = this.toProps(projectile, now);\n if (!props) {\n continue;\n }\n rendered.push({\n id: projectile.spawn.id,\n type: projectile.spawn.type,\n component: projectile.component,\n props,\n });\n }\n return rendered;\n });\n\n register(type: string, component: any): any {\n this.components.set(type, component);\n return component;\n }\n\n get(type: string): any {\n return this.components.get(type);\n }\n\n spawnBatch(projectiles: ClientProjectileSpawn[], clock: ProjectileSpawnClock = {}): void {\n const now = clock.now ?? Date.now();\n for (const projectile of projectiles) {\n const component = this.components.get(projectile.type);\n if (!component) {\n continue;\n }\n const runtime: RuntimeProjectile = {\n spawn: {\n ...projectile,\n delay: projectile.delay ?? 0,\n index: projectile.index ?? 0,\n count: projectile.count ?? 1,\n },\n component,\n createdAt: now,\n };\n this.setPredictedImpact(runtime);\n this.projectiles.set(projectile.id, runtime);\n this.hooks.callHooks(\"client-projectiles-onSpawn\", runtime.spawn).subscribe();\n }\n this.touch();\n }\n\n impactBatch(impacts: ClientProjectileImpact[]): void {\n const now = Date.now();\n for (const impact of impacts) {\n const projectile = this.projectiles.get(impact.id);\n if (!projectile) {\n continue;\n }\n this.setImpact(projectile, impact, now);\n this.hooks.callHooks(\"client-projectiles-onImpact\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n destroyBatch(projectiles: ClientProjectileDestroy[]): void {\n const now = Date.now();\n for (const destroyed of projectiles) {\n const projectile = this.projectiles.get(destroyed.id);\n if (!projectile) {\n continue;\n }\n if (destroyed.reason === \"hit\") {\n const current = this.toProps(projectile, now);\n this.setImpact(projectile, {\n id: destroyed.id,\n targetId: destroyed.targetId ?? projectile.impact?.targetId,\n x: destroyed.x ?? projectile.impact?.x ?? current?.x ?? projectile.spawn.origin.x,\n y: destroyed.y ?? projectile.impact?.y ?? current?.y ?? projectile.spawn.origin.y,\n distance: destroyed.distance ?? projectile.impact?.distance ?? current?.distance,\n }, now);\n }\n projectile.destroyReason = destroyed.reason;\n projectile.destroyAt = projectile.destroyAt ?? (\n projectile.impact && projectile.impactStartedAt !== undefined\n ? projectile.impactStartedAt + this.impactDurationMs\n : now\n );\n this.hooks.callHooks(\"client-projectiles-onDestroy\", this.toProps(projectile, now)).subscribe();\n }\n this.touch();\n }\n\n clear(): void {\n this.projectiles.clear();\n this.touch();\n }\n\n step(): void {\n const now = Date.now();\n let changed = false;\n for (const [id, projectile] of this.projectiles) {\n const props = this.toProps(projectile, now);\n if (\n (!props && !this.isWaitingForDelay(projectile, now)) ||\n (projectile.destroyAt !== undefined && now >= projectile.destroyAt)\n ) {\n this.projectiles.delete(id);\n changed = true;\n }\n }\n this.touch(changed || this.projectiles.size > 0);\n }\n\n private toProps(projectile: RuntimeProjectile, now: number): RenderedProjectileProps | null {\n const spawn = projectile.spawn;\n const delayMs = (spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return null;\n }\n const elapsed = elapsedMs / 1000;\n const ttl = Math.max(0.001, spawn.ttl);\n const rawDistance = Math.min(spawn.speed * elapsed, spawn.range);\n const predictedImpact = this.getActivePredictedImpact(projectile, now, rawDistance);\n const visualImpact = projectile.visualImpact ?? projectile.impact;\n const distance = visualImpact?.distance ?? predictedImpact?.distance ?? rawDistance;\n const progress = Math.min(1, distance / spawn.range);\n const x = visualImpact?.x ?? predictedImpact?.x ?? spawn.origin.x + spawn.direction.x * distance;\n const y = visualImpact?.y ?? predictedImpact?.y ?? spawn.origin.y + spawn.direction.y * distance;\n const impactElapsedMs = projectile.impactStartedAt !== undefined\n ? Math.max(0, now - projectile.impactStartedAt)\n : undefined;\n return {\n ...spawn,\n x,\n y,\n angle: Math.atan2(spawn.direction.y, spawn.direction.x),\n distance,\n elapsed,\n progress,\n impact: projectile.impact,\n impactElapsed: impactElapsedMs === undefined ? undefined : impactElapsedMs / 1000,\n impactProgress: impactElapsedMs === undefined\n ? undefined\n : Math.min(1, impactElapsedMs / this.impactDurationMs),\n destroyed: projectile.destroyAt !== undefined,\n ttl,\n };\n }\n\n private isWaitingForDelay(projectile: RuntimeProjectile, now: number): boolean {\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n return now - projectile.createdAt - delayMs < 0;\n }\n\n private setPredictedImpact(projectile: RuntimeProjectile): void {\n if (projectile.spawn.predictImpact === false) {\n return;\n }\n const impact = this.predictionResolver?.(projectile.spawn);\n if (!impact || !Number.isFinite(impact.x) || !Number.isFinite(impact.y)) {\n return;\n }\n const distance = typeof impact.distance === \"number\" && Number.isFinite(impact.distance)\n ? impact.distance\n : Math.hypot(impact.x - projectile.spawn.origin.x, impact.y - projectile.spawn.origin.y);\n if (!Number.isFinite(distance) || distance < 0 || distance > projectile.spawn.range) {\n return;\n }\n projectile.predictedImpact = {\n ...impact,\n distance,\n };\n }\n\n private getActivePredictedImpact(\n projectile: RuntimeProjectile,\n now: number,\n rawDistance: number,\n ): ClientProjectileImpact | undefined {\n if (!projectile.predictedImpact || projectile.impact) {\n return undefined;\n }\n const distance = projectile.predictedImpact.distance;\n if (distance === undefined || rawDistance < distance) {\n return undefined;\n }\n return projectile.predictedImpact;\n }\n\n private setImpact(projectile: RuntimeProjectile, impact: ClientProjectileImpact, now: number): void {\n projectile.visualImpact = this.resolveVisualImpact(projectile, impact, now);\n projectile.impact = impact;\n projectile.predictedImpact = undefined;\n projectile.impactStartedAt = projectile.impactStartedAt ?? now;\n const impactDestroyAt = projectile.impactStartedAt + this.impactDurationMs;\n projectile.destroyAt = Math.max(projectile.destroyAt ?? 0, impactDestroyAt);\n }\n\n private resolveVisualImpact(\n projectile: RuntimeProjectile,\n impact: ClientProjectileImpact,\n now: number,\n ): ClientProjectileImpact {\n const predicted = projectile.predictedImpact;\n if (!predicted || !this.isSameTarget(predicted, impact)) {\n return impact;\n }\n const distance = predicted.distance;\n if (distance === undefined) {\n return impact;\n }\n const delayMs = (projectile.spawn.delay ?? 0) * 1000;\n const elapsedMs = now - projectile.createdAt - delayMs;\n if (elapsedMs < 0) {\n return impact;\n }\n const rawDistance = Math.min(projectile.spawn.speed * (elapsedMs / 1000), projectile.spawn.range);\n return rawDistance >= distance ? predicted : impact;\n }\n\n private isSameTarget(a: ClientProjectileImpact, b: ClientProjectileImpact): boolean {\n return a.targetId !== undefined && a.targetId === b.targetId;\n }\n\n private touch(force = true): void {\n if (force) {\n this.version.update((value) => value + 1);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -2,7 +2,8 @@ import { ComponentFunction, Signal } from 'canvasengine';
2
2
  import { RpgClientEngine } from './RpgClientEngine';
3
3
  import { Loader, Container } from 'pixi.js';
4
4
  import { RpgClientObject } from './Game/Object';
5
- import { MapPhysicsEntityContext, MapPhysicsInitContext } from '@rpgjs/common';
5
+ import { MapPhysicsEntityContext, MapPhysicsInitContext, RpgActionName } from '@rpgjs/common';
6
+ import { ClientProjectileSpawn, RenderedProjectileProps } from './Game/ProjectileManager';
6
7
  type RpgComponent = RpgClientObject;
7
8
  type SceneMap = Container;
8
9
  export type SpriteComponentConfig = ComponentFunction | {
@@ -40,15 +41,18 @@ export interface RpgClientEngineHooks {
40
41
  /**
41
42
  * Recover keys from the pressed keyboard
42
43
  *
43
- * @prop { (engine: RpgClientEngine, obj: { input: string, playerId: number }) => any } [onInput]
44
+ * @prop { (engine: RpgClientEngine, obj: { input: string | number, action?: string | number, data?: any, playerId: number }) => any } [onInput]
44
45
  * @memberof RpgEngineHooks
45
46
  */
46
47
  onInput?: (engine: RpgClientEngine, obj: {
47
- input: string;
48
+ input: RpgActionName;
49
+ action?: RpgActionName;
50
+ data?: any;
48
51
  playerId: number;
49
52
  }) => any;
50
53
  /**
51
- * Called when the user is connected to the server
54
+ * Called when the user is connected to the server. In MMORPG mode, this
55
+ * runs after the server sends the RPGJS connection acceptance packet.
52
56
  *
53
57
  * @prop { (engine: RpgClientEngine, socket: any) => any } [onConnected]
54
58
  * @memberof RpgEngineHooks
@@ -62,7 +66,8 @@ export interface RpgClientEngineHooks {
62
66
  */
63
67
  onDisconnect?: (engine: RpgClientEngine, reason: any, socket: any) => any;
64
68
  /**
65
- * Called when there was a connection error
69
+ * Called when there was a connection error. In MMORPG mode, this also runs
70
+ * when server-side auth refuses the connection.
66
71
  *
67
72
  * @prop { (engine: RpgClientEngine, err: any, socket: any) => any } [onConnectError]
68
73
  * @memberof RpgEngineHooks
@@ -222,6 +227,13 @@ export interface RpgSceneHooks<Scene> {
222
227
  onDraw?: (scene: Scene, t: number) => any;
223
228
  }
224
229
  export interface RpgSceneMapHooks extends RpgSceneHooks<SceneMap> {
230
+ /**
231
+ * Root CanvasEngine component used to render the RPG scene map.
232
+ *
233
+ * Use the exported `SceneMap` component inside your custom component to
234
+ * keep the default map rendering and compose additional scene children.
235
+ */
236
+ component?: ComponentFunction;
225
237
  /**
226
238
  * The map and resources are being loaded
227
239
  *
@@ -261,6 +273,24 @@ export interface RpgSceneMapHooks extends RpgSceneHooks<SceneMap> {
261
273
  */
262
274
  onPhysicsReset?: (scene: SceneMap) => any;
263
275
  }
276
+ export interface RpgProjectileHooks {
277
+ /**
278
+ * CanvasEngine components used to render server-authoritative projectiles.
279
+ */
280
+ components?: Record<string, ComponentFunction>;
281
+ /**
282
+ * Called when a projectile spawn batch is received from the server.
283
+ */
284
+ onSpawn?: (projectile: ClientProjectileSpawn) => any;
285
+ /**
286
+ * Called when the server confirms a projectile impact.
287
+ */
288
+ onImpact?: (projectile: RenderedProjectileProps | null) => any;
289
+ /**
290
+ * Called when the server destroys a projectile.
291
+ */
292
+ onDestroy?: (projectile: RenderedProjectileProps | null) => any;
293
+ }
264
294
  export interface RpgClient {
265
295
  /**
266
296
  * Add hooks to the player or engine. All modules can listen to the hook
@@ -588,30 +618,33 @@ export interface RpgClient {
588
618
  * */
589
619
  sprite?: RpgSpriteHooks;
590
620
  /**
591
- * Reference the scenes of the game. Here you can put your own class that inherits RpgSceneMap
621
+ * Reference the scenes of the game.
592
622
  *
593
623
  * ```ts
594
624
  * import { RpgSceneMapHooks, RpgClient, defineModule } from '@rpgjs/client'
625
+ * import MyScene from './my-scene.ce'
595
626
  *
596
627
  * export const sceneMap: RpgSceneMapHooks = {
597
- *
628
+ * component: MyScene
598
629
  * }
599
630
  *
600
631
  * defineModule<RpgClient>({
601
- * scenes: {
602
- * // If you put the RpgSceneMap scene, Thhe key is called mandatory `map`
603
- * map: sceneMap
604
- * }
632
+ * sceneMap
605
633
  * })
606
634
  * ```
607
635
  *
608
- * @prop { [sceneName: string]: RpgSceneMapHooks } [scenes]
636
+ * @prop {RpgSceneMapHooks} [sceneMap]
609
637
  * @memberof RpgClient
610
638
  * */
639
+ sceneMap?: RpgSceneMapHooks;
640
+ /**
641
+ * Legacy scene map hook container.
642
+ *
643
+ * Prefer `sceneMap` for new code.
644
+ */
611
645
  scenes?: {
612
646
  map: RpgSceneMapHooks;
613
647
  };
614
- sceneMap?: RpgSceneMapHooks;
615
648
  /**
616
649
  * Array containing the list of component animations
617
650
  * Each element defines a temporary component to display for animations like hits, effects, etc.
@@ -642,5 +675,12 @@ export interface RpgClient {
642
675
  id: string;
643
676
  component: ComponentFunction;
644
677
  }[];
678
+ /**
679
+ * Client-side projectile rendering configuration.
680
+ *
681
+ * Register a CanvasEngine component per projectile type. The server sends
682
+ * compact spawn/impact/destroy events and the client predicts x/y locally.
683
+ */
684
+ projectiles?: RpgProjectileHooks;
645
685
  }
646
686
  export {};
@@ -1,9 +1,11 @@
1
1
  import { Trigger } from 'canvasengine';
2
2
  import { AbstractWebsocket } from './services/AbstractSocket';
3
- import { Direction } from '@rpgjs/common';
3
+ import { Direction, RpgActionInput, RpgActionName } from '@rpgjs/common';
4
4
  import { RpgClientMap } from './Game/Map';
5
5
  import { AnimationManager } from './Game/AnimationManager';
6
6
  import { Observable } from 'rxjs';
7
+ import { ProjectileManager } from './Game/ProjectileManager';
8
+ import { ClientPointerContext } from './services/pointerContext';
7
9
  import * as PIXI from "pixi.js";
8
10
  type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
9
11
  start(config?: T): Promise<void>;
@@ -24,12 +26,15 @@ export declare class RpgClientEngine<T = any> {
24
26
  private selector;
25
27
  globalConfig: T;
26
28
  sceneComponent: any;
29
+ sceneMapComponent: any;
27
30
  stopProcessingInput: boolean;
28
31
  width: import('canvasengine').WritableSignal<string>;
29
32
  height: import('canvasengine').WritableSignal<string>;
30
33
  spritesheets: Map<string | number, any>;
31
34
  sounds: Map<string, any>;
32
35
  componentAnimations: any[];
36
+ projectiles: ProjectileManager;
37
+ pointer: ClientPointerContext;
33
38
  private spritesheetResolver?;
34
39
  private soundResolver?;
35
40
  particleSettings: {
@@ -56,6 +61,8 @@ export declare class RpgClientEngine<T = any> {
56
61
  private pendingPredictionFrames;
57
62
  private lastClientPhysicsStepAt;
58
63
  private frameOffset;
64
+ private latestServerTick?;
65
+ private latestServerTickAt;
59
66
  private rtt;
60
67
  private pingInterval;
61
68
  private readonly PING_INTERVAL_MS;
@@ -72,6 +79,9 @@ export declare class RpgClientEngine<T = any> {
72
79
  private sceneResetQueued;
73
80
  private tickSubscriptions;
74
81
  private resizeHandler?;
82
+ private pointerMoveHandler?;
83
+ private pointerCanvas?;
84
+ private pendingSyncPackets;
75
85
  private notificationManager;
76
86
  constructor(context: any);
77
87
  /**
@@ -109,9 +119,15 @@ export declare class RpgClientEngine<T = any> {
109
119
  */
110
120
  setKeyboardControls(controlInstance: any): void;
111
121
  start(): Promise<void>;
122
+ private resolveSceneMapComponent;
123
+ private setupPointerTracking;
124
+ private findViewportInstance;
112
125
  private prepareSyncPayload;
113
126
  private normalizeAckWithSyncState;
114
127
  private initListeners;
128
+ private callConnectError;
129
+ private flushPendingSyncPackets;
130
+ private applySyncPacket;
115
131
  /**
116
132
  * Start periodic ping/pong for client-server synchronization
117
133
  *
@@ -486,6 +502,8 @@ export declare class RpgClientEngine<T = any> {
486
502
  * @returns The CanvasEngine component, or undefined when missing
487
503
  */
488
504
  getSpriteComponent(id: string): any;
505
+ registerProjectileComponent(type: string, component: any): any;
506
+ getProjectileComponent(type: string): any;
489
507
  /**
490
508
  * Add a component animation to the engine
491
509
  *
@@ -564,14 +582,17 @@ export declare class RpgClientEngine<T = any> {
564
582
  processInput({ input }: {
565
583
  input: Direction;
566
584
  }): Promise<void>;
567
- processAction({ action }: {
568
- action: number;
569
- }): void;
585
+ processAction(action: RpgActionName, data?: any): void;
586
+ processAction(action: RpgActionInput): void;
570
587
  get PIXI(): typeof PIXI;
571
588
  get socket(): AbstractWebsocket;
572
589
  get playerId(): string | null;
573
590
  get scene(): RpgClientMap;
574
591
  private getPhysicsTick;
592
+ private getPhysicsTickDurationMs;
593
+ private updateServerTickEstimate;
594
+ private estimateServerTick;
595
+ private predictProjectileImpact;
575
596
  private ensureCurrentPlayerBody;
576
597
  private stepClientPhysicsTick;
577
598
  private flushPendingPredictedStates;