@rpgjs/client 5.0.0-beta.12 → 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.
- package/CHANGELOG.md +10 -0
- package/dist/Game/Object.d.ts +2 -0
- package/dist/Game/Object.js +20 -6
- package/dist/Game/Object.js.map +1 -1
- package/dist/Gui/Gui.d.ts +3 -2
- package/dist/Gui/Gui.js +18 -6
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/RpgClient.d.ts +21 -1
- package/dist/RpgClientEngine.d.ts +20 -2
- package/dist/RpgClientEngine.js +180 -17
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/character.ce.js +82 -7
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/gui/dialogbox/index.ce.js +27 -12
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/gameover.ce.js +4 -3
- package/dist/components/gui/gameover.ce.js.map +1 -1
- package/dist/components/gui/menu/equip-menu.ce.js +9 -8
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/exit-menu.ce.js +7 -5
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/items-menu.ce.js +8 -7
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/main-menu.ce.js +12 -11
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/options-menu.ce.js +7 -5
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/skills-menu.ce.js +4 -2
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
- package/dist/components/gui/notification/notification.ce.js +4 -1
- package/dist/components/gui/notification/notification.ce.js.map +1 -1
- package/dist/components/gui/save-load.ce.js +10 -9
- package/dist/components/gui/save-load.ce.js.map +1 -1
- package/dist/components/gui/shop/shop.ce.js +17 -16
- package/dist/components/gui/shop/shop.ce.js.map +1 -1
- package/dist/components/gui/title-screen.ce.js +4 -3
- package/dist/components/gui/title-screen.ce.js.map +1 -1
- package/dist/components/interaction-components.ce.js +20 -0
- package/dist/components/interaction-components.ce.js.map +1 -0
- package/dist/components/scenes/canvas.ce.js +12 -7
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +18 -13
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/i18n.d.ts +55 -0
- package/dist/i18n.js +60 -0
- package/dist/i18n.js.map +1 -0
- package/dist/i18n.spec.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/module.js +23 -3
- package/dist/module.js.map +1 -1
- package/dist/services/interactions.d.ts +159 -0
- package/dist/services/interactions.js +460 -0
- package/dist/services/interactions.js.map +1 -0
- package/dist/services/interactions.spec.d.ts +1 -0
- package/dist/services/keyboardControls.d.ts +1 -0
- package/dist/services/keyboardControls.js +1 -0
- package/dist/services/keyboardControls.js.map +1 -1
- package/package.json +4 -4
- package/src/Game/Object.spec.ts +14 -1
- package/src/Game/Object.ts +34 -10
- package/src/Gui/Gui.spec.ts +67 -0
- package/src/Gui/Gui.ts +24 -7
- package/src/RpgClient.ts +28 -1
- package/src/RpgClientEngine.ts +248 -29
- package/src/components/character.ce +90 -7
- package/src/components/gui/dialogbox/index.ce +35 -14
- package/src/components/gui/gameover.ce +4 -3
- package/src/components/gui/menu/equip-menu.ce +9 -8
- package/src/components/gui/menu/exit-menu.ce +4 -3
- package/src/components/gui/menu/items-menu.ce +8 -7
- package/src/components/gui/menu/main-menu.ce +12 -11
- package/src/components/gui/menu/options-menu.ce +4 -3
- package/src/components/gui/menu/skills-menu.ce +2 -1
- package/src/components/gui/notification/notification.ce +7 -1
- package/src/components/gui/save-load.ce +11 -10
- package/src/components/gui/shop/shop.ce +17 -16
- package/src/components/gui/title-screen.ce +4 -3
- package/src/components/interaction-components.ce +23 -0
- package/src/components/scenes/canvas.ce +12 -7
- package/src/components/scenes/draw-map.ce +16 -5
- package/src/i18n.spec.ts +39 -0
- package/src/i18n.ts +59 -0
- package/src/index.ts +2 -0
- package/src/module.ts +32 -10
- package/src/services/interactions.spec.ts +175 -0
- package/src/services/interactions.ts +722 -0
- package/src/services/keyboardControls.ts +2 -1
package/src/Gui/Gui.spec.ts
CHANGED
|
@@ -64,6 +64,73 @@ const VueTooltip = {
|
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
describe("RpgGui Vue integration", () => {
|
|
67
|
+
test("tracks GUI open ids and sends them back when closing", async () => {
|
|
68
|
+
const { gui, socket } = await createGui();
|
|
69
|
+
await gui._initialize();
|
|
70
|
+
const openHandler = socket.on.mock.calls.find(([event]) => event === "gui.open")?.[1];
|
|
71
|
+
|
|
72
|
+
openHandler({
|
|
73
|
+
guiId: PrebuiltGui.Dialog,
|
|
74
|
+
guiOpenId: "open-1",
|
|
75
|
+
data: { message: "Hello" },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(gui.get(PrebuiltGui.Dialog)?.openId).toBe("open-1");
|
|
79
|
+
|
|
80
|
+
gui.guiClose(PrebuiltGui.Dialog, 0, gui.get(PrebuiltGui.Dialog)?.openId);
|
|
81
|
+
|
|
82
|
+
expect(socket.emit).toHaveBeenCalledWith("gui.exit", {
|
|
83
|
+
guiId: PrebuiltGui.Dialog,
|
|
84
|
+
guiOpenId: "open-1",
|
|
85
|
+
data: 0,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("does not emit malformed GUI open ids", async () => {
|
|
90
|
+
const { gui, socket } = await createGui();
|
|
91
|
+
|
|
92
|
+
gui.guiClose(PrebuiltGui.Dialog, 0, (() => "open-1") as any);
|
|
93
|
+
|
|
94
|
+
expect(socket.emit).toHaveBeenCalledWith("gui.exit", {
|
|
95
|
+
guiId: PrebuiltGui.Dialog,
|
|
96
|
+
guiOpenId: undefined,
|
|
97
|
+
data: 0,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("ignores stale server close events for previous GUI opens", async () => {
|
|
102
|
+
const { gui, socket } = await createGui();
|
|
103
|
+
await gui._initialize();
|
|
104
|
+
const openHandler = socket.on.mock.calls.find(([event]) => event === "gui.open")?.[1];
|
|
105
|
+
const exitHandler = socket.on.mock.calls.find(([event]) => event === "gui.exit")?.[1];
|
|
106
|
+
|
|
107
|
+
openHandler({
|
|
108
|
+
guiId: PrebuiltGui.Dialog,
|
|
109
|
+
guiOpenId: "open-1",
|
|
110
|
+
data: { message: "First" },
|
|
111
|
+
});
|
|
112
|
+
openHandler({
|
|
113
|
+
guiId: PrebuiltGui.Dialog,
|
|
114
|
+
guiOpenId: "open-2",
|
|
115
|
+
data: { message: "Second" },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
exitHandler({
|
|
119
|
+
guiId: PrebuiltGui.Dialog,
|
|
120
|
+
guiOpenId: "open-1",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(gui.isDisplaying(PrebuiltGui.Dialog)).toBe(true);
|
|
124
|
+
expect(gui.get(PrebuiltGui.Dialog)?.data()).toEqual({ message: "Second" });
|
|
125
|
+
|
|
126
|
+
exitHandler({
|
|
127
|
+
guiId: PrebuiltGui.Dialog,
|
|
128
|
+
guiOpenId: "open-2",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(gui.isDisplaying(PrebuiltGui.Dialog)).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
67
134
|
test("separates CanvasEngine and Vue GUI registries", async () => {
|
|
68
135
|
const { gui } = await createGui();
|
|
69
136
|
|
package/src/Gui/Gui.ts
CHANGED
|
@@ -39,6 +39,7 @@ export interface GuiInstance {
|
|
|
39
39
|
component: any;
|
|
40
40
|
display: WritableSignal<boolean>;
|
|
41
41
|
data: WritableSignal<any>;
|
|
42
|
+
openId?: string;
|
|
42
43
|
autoDisplay: boolean;
|
|
43
44
|
dependencies?: Signal[];
|
|
44
45
|
subscription?: Subscription;
|
|
@@ -50,6 +51,7 @@ type GuiState = {
|
|
|
50
51
|
component: any;
|
|
51
52
|
display: boolean;
|
|
52
53
|
data: any;
|
|
54
|
+
openId?: string;
|
|
53
55
|
attachToSprite: boolean;
|
|
54
56
|
};
|
|
55
57
|
|
|
@@ -179,12 +181,18 @@ export class RpgGui {
|
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
async _initialize() {
|
|
182
|
-
this.webSocket.on("gui.open", (data: { guiId: string; data: any }) => {
|
|
184
|
+
this.webSocket.on("gui.open", (data: { guiId: string; data: any; guiOpenId?: string }) => {
|
|
183
185
|
this.clearPendingActions(data.guiId);
|
|
184
|
-
this.display(data.guiId, data.data);
|
|
186
|
+
this.display(data.guiId, data.data, [], data.guiOpenId);
|
|
185
187
|
});
|
|
186
188
|
|
|
187
|
-
this.webSocket.on("gui.exit", (guiId: string) => {
|
|
189
|
+
this.webSocket.on("gui.exit", (payload: string | { guiId: string; guiOpenId?: string }) => {
|
|
190
|
+
const guiId = typeof payload === "string" ? payload : payload.guiId;
|
|
191
|
+
const guiOpenId = typeof payload === "string" ? undefined : payload.guiOpenId;
|
|
192
|
+
const current = this.get(guiId);
|
|
193
|
+
if (guiOpenId && current?.openId && current.openId !== guiOpenId) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
188
196
|
this.hide(guiId);
|
|
189
197
|
});
|
|
190
198
|
|
|
@@ -256,9 +264,12 @@ export class RpgGui {
|
|
|
256
264
|
});
|
|
257
265
|
}
|
|
258
266
|
|
|
259
|
-
guiClose(guiId: string, data?: any) {
|
|
267
|
+
guiClose(guiId: string, data?: any, guiOpenId?: unknown) {
|
|
268
|
+
const normalizedOpenId =
|
|
269
|
+
typeof guiOpenId === "string" && guiOpenId.length > 0 ? guiOpenId : undefined;
|
|
260
270
|
this.webSocket.emit("gui.exit", {
|
|
261
271
|
guiId,
|
|
272
|
+
guiOpenId: normalizedOpenId,
|
|
262
273
|
data,
|
|
263
274
|
});
|
|
264
275
|
}
|
|
@@ -308,6 +319,7 @@ export class RpgGui {
|
|
|
308
319
|
component,
|
|
309
320
|
display: signal<boolean>(gui.display || false),
|
|
310
321
|
data: signal<any>(gui.data || {}),
|
|
322
|
+
openId: undefined,
|
|
311
323
|
autoDisplay: gui.autoDisplay || false,
|
|
312
324
|
dependencies: gui.dependencies ? gui.dependencies() : [],
|
|
313
325
|
attachToSprite,
|
|
@@ -431,7 +443,7 @@ export class RpgGui {
|
|
|
431
443
|
* gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);
|
|
432
444
|
* ```
|
|
433
445
|
*/
|
|
434
|
-
display(id: string, data = {}, dependencies: Signal[] = []) {
|
|
446
|
+
display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {
|
|
435
447
|
if (!this.exists(id)) {
|
|
436
448
|
throw throwError(id);
|
|
437
449
|
}
|
|
@@ -443,8 +455,9 @@ export class RpgGui {
|
|
|
443
455
|
|
|
444
456
|
if (isVueComponent) {
|
|
445
457
|
// Handle Vue component display
|
|
446
|
-
this._handleVueComponentDisplay(id, data, dependencies, guiInstance);
|
|
458
|
+
this._handleVueComponentDisplay(id, data, dependencies, guiInstance, openId);
|
|
447
459
|
} else {
|
|
460
|
+
guiInstance.openId = openId;
|
|
448
461
|
guiInstance.data.set(data);
|
|
449
462
|
guiInstance.display.set(true);
|
|
450
463
|
}
|
|
@@ -464,7 +477,7 @@ export class RpgGui {
|
|
|
464
477
|
* @param dependencies - Runtime dependencies
|
|
465
478
|
* @param guiInstance - GUI instance
|
|
466
479
|
*/
|
|
467
|
-
private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance) {
|
|
480
|
+
private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance, openId?: string) {
|
|
468
481
|
// Unsubscribe from previous subscription if exists
|
|
469
482
|
if (guiInstance.subscription) {
|
|
470
483
|
guiInstance.subscription.unsubscribe();
|
|
@@ -482,6 +495,7 @@ export class RpgGui {
|
|
|
482
495
|
deps.map(dependency => dependency.observable)
|
|
483
496
|
).subscribe((values) => {
|
|
484
497
|
if (values.every(value => value !== undefined)) {
|
|
498
|
+
guiInstance.openId = openId;
|
|
485
499
|
guiInstance.data.set(data);
|
|
486
500
|
guiInstance.display.set(true);
|
|
487
501
|
this._notifyVueGui(id, true, data);
|
|
@@ -491,6 +505,7 @@ export class RpgGui {
|
|
|
491
505
|
}
|
|
492
506
|
|
|
493
507
|
// No dependencies, display immediately
|
|
508
|
+
guiInstance.openId = openId;
|
|
494
509
|
guiInstance.data.set(data);
|
|
495
510
|
guiInstance.display.set(true);
|
|
496
511
|
this._notifyVueGui(id, true, data);
|
|
@@ -523,6 +538,7 @@ export class RpgGui {
|
|
|
523
538
|
}
|
|
524
539
|
|
|
525
540
|
guiInstance.display.set(false)
|
|
541
|
+
guiInstance.openId = undefined;
|
|
526
542
|
|
|
527
543
|
// Check if it's a Vue component and notify VueGui
|
|
528
544
|
const isVueComponent = this.extraGuis.some(gui => gui.name === id);
|
|
@@ -573,6 +589,7 @@ export class RpgGui {
|
|
|
573
589
|
component: gui.component,
|
|
574
590
|
display,
|
|
575
591
|
data,
|
|
592
|
+
openId: gui.openId,
|
|
576
593
|
attachToSprite: gui.attachToSprite || false,
|
|
577
594
|
};
|
|
578
595
|
}
|
package/src/RpgClient.ts
CHANGED
|
@@ -3,12 +3,16 @@ import { RpgClientEngine } from './RpgClientEngine'
|
|
|
3
3
|
import { Loader, Container } from 'pixi.js'
|
|
4
4
|
import { RpgClientObject } from './Game/Object'
|
|
5
5
|
import type { RpgClientEvent } from './Game/Event'
|
|
6
|
-
import { type MapPhysicsEntityContext, type MapPhysicsInitContext, type RpgActionName } from '@rpgjs/common'
|
|
6
|
+
import { type I18nMessages, type MapPhysicsEntityContext, type MapPhysicsInitContext, type RpgActionName } from '@rpgjs/common'
|
|
7
7
|
import type {
|
|
8
8
|
ClientProjectileSpawn,
|
|
9
9
|
RenderedProjectileProps,
|
|
10
10
|
} from './Game/ProjectileManager'
|
|
11
11
|
import type { ClientVisualMap } from './Game/ClientVisuals'
|
|
12
|
+
import type {
|
|
13
|
+
RpgInteractionBehavior,
|
|
14
|
+
RpgInteractionMatcher,
|
|
15
|
+
} from './services/interactions'
|
|
12
16
|
|
|
13
17
|
type RpgClass<T = any> = new (...args: any[]) => T
|
|
14
18
|
type RpgComponent = RpgClientObject
|
|
@@ -363,6 +367,14 @@ export interface RpgProjectileHooks {
|
|
|
363
367
|
}
|
|
364
368
|
|
|
365
369
|
export interface RpgClient {
|
|
370
|
+
/**
|
|
371
|
+
* Default translations owned by this client module.
|
|
372
|
+
*
|
|
373
|
+
* Game-level translations provided with `provideI18n()` override module
|
|
374
|
+
* translations when they share the same locale and key.
|
|
375
|
+
*/
|
|
376
|
+
i18n?: I18nMessages
|
|
377
|
+
|
|
366
378
|
/**
|
|
367
379
|
* Add hooks to the player or engine. All modules can listen to the hook
|
|
368
380
|
*
|
|
@@ -796,4 +808,19 @@ export interface RpgClient {
|
|
|
796
808
|
* compact spawn/impact/destroy events and the client predicts x/y locally.
|
|
797
809
|
*/
|
|
798
810
|
projectiles?: RpgProjectileHooks
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Client-only pointer interactions attached to sprites.
|
|
814
|
+
*
|
|
815
|
+
* Use this for hover popovers, selection, drag previews, cursor changes, and
|
|
816
|
+
* explicit mouse-driven gameplay actions. Pointer feedback stays local unless
|
|
817
|
+
* the behavior calls `ctx.action(...)`.
|
|
818
|
+
*/
|
|
819
|
+
interactions?:
|
|
820
|
+
| ((engine: RpgClientEngine) => void)
|
|
821
|
+
| {
|
|
822
|
+
setup?: (engine: RpgClientEngine) => void
|
|
823
|
+
load?: (engine: RpgClientEngine) => void
|
|
824
|
+
use?: Array<[RpgInteractionMatcher, RpgInteractionBehavior | ComponentFunction]>
|
|
825
|
+
}
|
|
799
826
|
}
|