@rpgjs/client 5.0.0-beta.2 → 5.0.0-beta.4
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/dist/Game/Map.js +2 -2
- package/dist/Game/Object.d.ts +10 -2
- package/dist/Game/Object.js +56 -26
- package/dist/Game/Object.js.map +1 -1
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/Gui/NotificationManager.js.map +1 -1
- package/dist/RpgClientEngine.d.ts +16 -0
- package/dist/RpgClientEngine.js +53 -6
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/Sound.js.map +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.127.0}/helpers/decorate.js +1 -1
- package/dist/_virtual/{_@oxc-project_runtime@0.122.0 → _@oxc-project_runtime@0.127.0}/helpers/decorateMetadata.js +1 -1
- package/dist/components/animations/animation.ce.js +2 -4
- package/dist/components/animations/animation.ce.js.map +1 -1
- package/dist/components/animations/hit.ce.js +17 -24
- package/dist/components/animations/hit.ce.js.map +1 -1
- package/dist/components/character.ce.js +75 -222
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/dynamics/text.ce.js +14 -25
- package/dist/components/dynamics/text.ce.js.map +1 -1
- package/dist/components/gui/box.ce.js +4 -7
- package/dist/components/gui/box.ce.js.map +1 -1
- package/dist/components/gui/dialogbox/index.ce.js +37 -54
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -1
- package/dist/components/gui/gameover.ce.js +36 -61
- package/dist/components/gui/gameover.ce.js.map +1 -1
- package/dist/components/gui/hud/hud.ce.js +18 -28
- package/dist/components/gui/hud/hud.ce.js.map +1 -1
- package/dist/components/gui/menu/equip-menu.ce.js +108 -163
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/exit-menu.ce.js +4 -4
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/items-menu.ce.js +47 -66
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/main-menu.ce.js +58 -77
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/options-menu.ce.js +3 -3
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -1
- package/dist/components/gui/menu/skills-menu.ce.js +10 -16
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -1
- package/dist/components/gui/mobile/mobile.ce.js +3 -3
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
- package/dist/components/gui/notification/notification.ce.js +13 -19
- package/dist/components/gui/notification/notification.ce.js.map +1 -1
- package/dist/components/gui/save-load.ce.js +68 -247
- package/dist/components/gui/save-load.ce.js.map +1 -1
- package/dist/components/gui/shop/shop.ce.js +84 -123
- package/dist/components/gui/shop/shop.ce.js.map +1 -1
- package/dist/components/gui/title-screen.ce.js +38 -65
- package/dist/components/gui/title-screen.ce.js.map +1 -1
- package/dist/components/prebuilt/hp-bar.ce.js +39 -43
- package/dist/components/prebuilt/hp-bar.ce.js.map +1 -1
- package/dist/components/prebuilt/light-halo.ce.js +33 -58
- package/dist/components/prebuilt/light-halo.ce.js.map +1 -1
- package/dist/components/scenes/canvas.ce.js +11 -19
- package/dist/components/scenes/canvas.ce.js.map +1 -1
- package/dist/components/scenes/draw-map.ce.js +20 -25
- package/dist/components/scenes/draw-map.ce.js.map +1 -1
- package/dist/components/scenes/event-layer.ce.js +4 -4
- package/dist/components/scenes/event-layer.ce.js.map +1 -1
- package/dist/core/setup.js.map +1 -1
- package/dist/module.js.map +1 -1
- package/dist/node_modules/.pnpm/@signe_di@2.9.0/node_modules/@signe/di/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js +1 -1
- package/dist/node_modules/.pnpm/@signe_reactive@2.9.0/node_modules/@signe/reactive/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js +1 -1
- package/dist/node_modules/.pnpm/@signe_room@2.9.0/node_modules/@signe/room/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/client/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@signe_sync@2.9.0/node_modules/@signe/sync/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -1
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +27 -27
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -1
- package/dist/services/keyboardControls.js.map +1 -1
- package/dist/services/mmorpg.js +2 -1
- package/dist/services/mmorpg.js.map +1 -1
- package/package.json +7 -7
- package/src/Game/Object.ts +86 -32
- package/src/RpgClientEngine.ts +83 -12
- package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js +0 -457
- package/dist/node_modules/.pnpm/@signe_reactive@2.8.3/node_modules/@signe/reactive/dist/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboardControls.js","names":[],"sources":["../../src/services/keyboardControls.ts"],"sourcesContent":["import { KeyboardControls } from \"canvasengine\";\n\nexport enum Control {\n Action = 'action',\n Attack = 'attack',\n Defense = 'defense',\n Skill = 'skill',\n Back = 'back',\n Up = 1,\n Down = 3,\n Right = 2,\n Left = 4\n}\n\nexport function provideKeyboardControls() {\n return {\n provide: 'KeyboardControls',\n useValue: null,\n };\n}"],"mappings":";AAEA,IAAY,UAAL,yBAAA,SAAA;AACL,SAAA,
|
|
1
|
+
{"version":3,"file":"keyboardControls.js","names":[],"sources":["../../src/services/keyboardControls.ts"],"sourcesContent":["import { KeyboardControls } from \"canvasengine\";\n\nexport enum Control {\n Action = 'action',\n Attack = 'attack',\n Defense = 'defense',\n Skill = 'skill',\n Back = 'back',\n Up = 1,\n Down = 3,\n Right = 2,\n Left = 4\n}\n\nexport function provideKeyboardControls() {\n return {\n provide: 'KeyboardControls',\n useValue: null,\n };\n}"],"mappings":";AAEA,IAAY,UAAL,yBAAA,SAAA;AACL,SAAA,YAAS;AACT,SAAA,YAAS;AACT,SAAA,aAAU;AACV,SAAA,WAAQ;AACR,SAAA,UAAO;AACP,SAAA,QAAA,QAAK,KAAA;AACL,SAAA,QAAA,UAAO,KAAA;AACP,SAAA,QAAA,WAAQ,KAAA;AACR,SAAA,QAAA,UAAO,KAAA;;KACR;AAED,SAAgB,0BAA0B;AACxC,QAAO;EACL,SAAS;EACT,UAAU;EACX"}
|
package/dist/services/mmorpg.js
CHANGED
|
@@ -30,8 +30,9 @@ var BridgeWebsocket = class extends AbstractWebsocket {
|
|
|
30
30
|
async connection(listeners) {
|
|
31
31
|
class Room {}
|
|
32
32
|
const instance = new Room();
|
|
33
|
+
const host = this.options.host || window.location.host;
|
|
33
34
|
this.socket = await connectionRoom({
|
|
34
|
-
host
|
|
35
|
+
host,
|
|
35
36
|
room: this.targetRoom,
|
|
36
37
|
id: this.privateId
|
|
37
38
|
}, instance);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mmorpg.js","names":[],"sources":["../../src/services/mmorpg.ts"],"sourcesContent":["import { Context } from \"@signe/di\";\nimport { connectionRoom } from \"@signe/sync/client\";\nimport { RpgGui } from \"../Gui/Gui\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { AbstractWebsocket, SocketUpdateProperties, WebSocketToken } from \"./AbstractSocket\";\nimport { UpdateMapService, UpdateMapToken } from \"@rpgjs/common\";\nimport { provideKeyboardControls } from \"./keyboardControls\";\nimport { provideSaveClient } from \"./save\";\n\ninterface MmorpgOptions {\n host?: string;\n connectionId?: string;\n connectionIdScope?: \"local\" | \"session\" | \"ephemeral\";\n}\n\nclass BridgeWebsocket extends AbstractWebsocket {\n private socket: any;\n private privateId: string;\n private pendingOn: Array<{ event: string; callback: (data: any) => void }> = [];\n private targetRoom = \"lobby-1\";\n\n constructor(protected context: Context, private options: MmorpgOptions = {}) {\n super(context);\n this.privateId = this.resolveConnectionId();\n }\n\n private resolveConnectionId(): string {\n if (this.options.connectionId) {\n return this.options.connectionId;\n }\n\n const scope = this.options.connectionIdScope ?? \"local\";\n const key = \"rpgjs-user-id\";\n\n if (scope === \"ephemeral\") {\n return crypto.randomUUID();\n }\n\n const storage =\n scope === \"session\"\n ? window.sessionStorage\n : window.localStorage;\n\n const existing = storage.getItem(key);\n if (existing) {\n return existing;\n }\n\n const id = crypto.randomUUID();\n storage.setItem(key, id);\n return id;\n }\n\n async connection(listeners?: (data: any) => void) {\n // tmp\n class Room {\n \n }\n const instance = new Room()\n const host = this.options.host || window.location.host;\n this.socket = await connectionRoom({\n host,\n room: this.targetRoom,\n id: this.privateId\n }, instance)\n\n listeners?.(this.socket)\n this.pendingOn.forEach(({ event, callback }) => this.socket.on(event, callback));\n this.pendingOn = [];\n }\n\n on(key: string, callback: (data: any) => void) {\n if (!this.socket) {\n this.pendingOn.push({ event: key, callback });\n return;\n }\n this.socket.on(key, callback);\n }\n\n off(event: string, callback: (data: any) => void) {\n if (!this.socket) return;\n this.socket.off(event, callback);\n }\n\n emit(event: string, data: any) {\n this.socket.emit(event, data);\n }\n\n updateProperties({ room, host, query }: SocketUpdateProperties) {\n if (!this.socket?.conn) return;\n this.targetRoom = room;\n this.socket.conn.updateProperties({\n room,\n id: this.privateId,\n host: host || this.options.host || window.location.host,\n query,\n })\n }\n\n private waitForNextOpen(conn: any, timeoutMs = 10000): Promise<void> {\n return new Promise((resolve, reject) => {\n let timeoutId: number | undefined;\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = () => {\n cleanup();\n reject(new Error(\"WebSocket reconnect failed\"));\n };\n const cleanup = () => {\n conn.removeEventListener(\"open\", onOpen);\n conn.removeEventListener(\"error\", onError);\n if (timeoutId !== undefined) {\n window.clearTimeout(timeoutId);\n }\n };\n\n conn.addEventListener(\"open\", onOpen);\n conn.addEventListener(\"error\", onError);\n timeoutId = window.setTimeout(() => {\n cleanup();\n reject(new Error(\"WebSocket reconnect timeout\"));\n }, timeoutMs);\n });\n }\n\n async reconnect(_listeners?: (data: any) => void): Promise<void> {\n if (!this.socket?.conn) return;\n const conn = this.socket.conn;\n const opened = this.waitForNextOpen(conn);\n conn.reconnect();\n await opened;\n }\n\n getCurrentRoom(): string {\n return this.targetRoom || this.socket?.conn?.room || \"lobby-1\";\n }\n}\n\nclass UpdateMapStandaloneService extends UpdateMapService {\n constructor(protected context: Context, private _options: MmorpgOptions) {\n super(context);\n }\n\n async update(_map: any) {\n // In MMORPG mode, clients are untrusted and must not push map definitions.\n // Map bootstrap/update is handled server-side by @rpgjs/vite.\n return;\n }\n}\n\nexport function provideMmorpg(options: MmorpgOptions) {\n return [\n {\n provide: WebSocketToken,\n useFactory: (context: Context) => new BridgeWebsocket(context, options),\n },\n {\n provide: UpdateMapToken,\n useFactory: (context: Context) => new UpdateMapStandaloneService(context, options),\n },\n provideKeyboardControls(),\n provideSaveClient(),\n RpgGui,\n RpgClientEngine,\n ];\n}\n"],"mappings":";;;;;;;;AAeA,IAAM,kBAAN,cAA8B,kBAAkB;CAM9C,YAAY,SAA4B,UAAiC,EAAE,EAAE;AAC3E,QAAM,QAAQ;AADM,OAAA,UAAA;AAA0B,OAAA,UAAA;mBAH6B,EAAE;oBAC1D;AAInB,OAAK,YAAY,KAAK,qBAAqB;;CAG7C,sBAAsC;AACpC,MAAI,KAAK,QAAQ,aACf,QAAO,KAAK,QAAQ;EAGtB,MAAM,QAAQ,KAAK,QAAQ,qBAAqB;EAChD,MAAM,MAAM;AAEZ,MAAI,UAAU,YACZ,QAAO,OAAO,YAAY;EAG5B,MAAM,UACJ,UAAU,YACN,OAAO,iBACP,OAAO;EAEb,MAAM,WAAW,QAAQ,QAAQ,IAAI;AACrC,MAAI,SACF,QAAO;EAGT,MAAM,KAAK,OAAO,YAAY;AAC9B,UAAQ,QAAQ,KAAK,GAAG;AACxB,SAAO;;CAGT,MAAM,WAAW,WAAiC;EAEhD,MAAM,KAAK;EAGX,MAAM,WAAW,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"mmorpg.js","names":[],"sources":["../../src/services/mmorpg.ts"],"sourcesContent":["import { Context } from \"@signe/di\";\nimport { connectionRoom } from \"@signe/sync/client\";\nimport { RpgGui } from \"../Gui/Gui\";\nimport { RpgClientEngine } from \"../RpgClientEngine\";\nimport { AbstractWebsocket, SocketUpdateProperties, WebSocketToken } from \"./AbstractSocket\";\nimport { UpdateMapService, UpdateMapToken } from \"@rpgjs/common\";\nimport { provideKeyboardControls } from \"./keyboardControls\";\nimport { provideSaveClient } from \"./save\";\n\ninterface MmorpgOptions {\n host?: string;\n connectionId?: string;\n connectionIdScope?: \"local\" | \"session\" | \"ephemeral\";\n}\n\nclass BridgeWebsocket extends AbstractWebsocket {\n private socket: any;\n private privateId: string;\n private pendingOn: Array<{ event: string; callback: (data: any) => void }> = [];\n private targetRoom = \"lobby-1\";\n\n constructor(protected context: Context, private options: MmorpgOptions = {}) {\n super(context);\n this.privateId = this.resolveConnectionId();\n }\n\n private resolveConnectionId(): string {\n if (this.options.connectionId) {\n return this.options.connectionId;\n }\n\n const scope = this.options.connectionIdScope ?? \"local\";\n const key = \"rpgjs-user-id\";\n\n if (scope === \"ephemeral\") {\n return crypto.randomUUID();\n }\n\n const storage =\n scope === \"session\"\n ? window.sessionStorage\n : window.localStorage;\n\n const existing = storage.getItem(key);\n if (existing) {\n return existing;\n }\n\n const id = crypto.randomUUID();\n storage.setItem(key, id);\n return id;\n }\n\n async connection(listeners?: (data: any) => void) {\n // tmp\n class Room {\n \n }\n const instance = new Room()\n const host = this.options.host || window.location.host;\n this.socket = await connectionRoom({\n host,\n room: this.targetRoom,\n id: this.privateId\n }, instance)\n\n listeners?.(this.socket)\n this.pendingOn.forEach(({ event, callback }) => this.socket.on(event, callback));\n this.pendingOn = [];\n }\n\n on(key: string, callback: (data: any) => void) {\n if (!this.socket) {\n this.pendingOn.push({ event: key, callback });\n return;\n }\n this.socket.on(key, callback);\n }\n\n off(event: string, callback: (data: any) => void) {\n if (!this.socket) return;\n this.socket.off(event, callback);\n }\n\n emit(event: string, data: any) {\n this.socket.emit(event, data);\n }\n\n updateProperties({ room, host, query }: SocketUpdateProperties) {\n if (!this.socket?.conn) return;\n this.targetRoom = room;\n this.socket.conn.updateProperties({\n room,\n id: this.privateId,\n host: host || this.options.host || window.location.host,\n query,\n })\n }\n\n private waitForNextOpen(conn: any, timeoutMs = 10000): Promise<void> {\n return new Promise((resolve, reject) => {\n let timeoutId: number | undefined;\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = () => {\n cleanup();\n reject(new Error(\"WebSocket reconnect failed\"));\n };\n const cleanup = () => {\n conn.removeEventListener(\"open\", onOpen);\n conn.removeEventListener(\"error\", onError);\n if (timeoutId !== undefined) {\n window.clearTimeout(timeoutId);\n }\n };\n\n conn.addEventListener(\"open\", onOpen);\n conn.addEventListener(\"error\", onError);\n timeoutId = window.setTimeout(() => {\n cleanup();\n reject(new Error(\"WebSocket reconnect timeout\"));\n }, timeoutMs);\n });\n }\n\n async reconnect(_listeners?: (data: any) => void): Promise<void> {\n if (!this.socket?.conn) return;\n const conn = this.socket.conn;\n const opened = this.waitForNextOpen(conn);\n conn.reconnect();\n await opened;\n }\n\n getCurrentRoom(): string {\n return this.targetRoom || this.socket?.conn?.room || \"lobby-1\";\n }\n}\n\nclass UpdateMapStandaloneService extends UpdateMapService {\n constructor(protected context: Context, private _options: MmorpgOptions) {\n super(context);\n }\n\n async update(_map: any) {\n // In MMORPG mode, clients are untrusted and must not push map definitions.\n // Map bootstrap/update is handled server-side by @rpgjs/vite.\n return;\n }\n}\n\nexport function provideMmorpg(options: MmorpgOptions) {\n return [\n {\n provide: WebSocketToken,\n useFactory: (context: Context) => new BridgeWebsocket(context, options),\n },\n {\n provide: UpdateMapToken,\n useFactory: (context: Context) => new UpdateMapStandaloneService(context, options),\n },\n provideKeyboardControls(),\n provideSaveClient(),\n RpgGui,\n RpgClientEngine,\n ];\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/client",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.4",
|
|
4
4
|
"description": "RPGJS is a framework for creating RPG/MMORPG games",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"pixi.js": "^8.9.2"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rpgjs/common": "5.0.0-beta.
|
|
26
|
-
"@rpgjs/server": "5.0.0-beta.
|
|
27
|
-
"@rpgjs/ui-css": "5.0.0-beta.
|
|
25
|
+
"@rpgjs/common": "5.0.0-beta.4",
|
|
26
|
+
"@rpgjs/server": "5.0.0-beta.4",
|
|
27
|
+
"@rpgjs/ui-css": "5.0.0-beta.4",
|
|
28
28
|
"@signe/di": "^2.9.0",
|
|
29
29
|
"@signe/room": "^2.9.0",
|
|
30
30
|
"@signe/sync": "^2.9.0",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"rxjs": "^7.8.2"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@canvasengine/compiler": "2.0.0-beta.
|
|
36
|
-
"vite": "^8.0.
|
|
35
|
+
"@canvasengine/compiler": "2.0.0-beta.58",
|
|
36
|
+
"vite": "^8.0.10",
|
|
37
37
|
"vite-plugin-dts": "^4.5.4",
|
|
38
|
-
"vitest": "^4.1.
|
|
38
|
+
"vitest": "^4.1.5"
|
|
39
39
|
},
|
|
40
40
|
"type": "module",
|
|
41
41
|
"scripts": {
|
package/src/Game/Object.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hooks, ModulesToken, RpgCommonPlayer } from "@rpgjs/common";
|
|
2
2
|
import { trigger, signal, effect } from "canvasengine";
|
|
3
|
-
import { filter, from, map, Subscription, switchMap } from "rxjs";
|
|
3
|
+
import { filter, from, map, of, Subscription, switchMap } from "rxjs";
|
|
4
4
|
import { inject } from "../core/inject";
|
|
5
5
|
import { RpgClientEngine } from "../RpgClientEngine";
|
|
6
6
|
import TextComponent from "../components/dynamics/text.ce";
|
|
@@ -11,6 +11,11 @@ const DYNAMIC_COMPONENTS = {
|
|
|
11
11
|
|
|
12
12
|
type Frame = { x: number; y: number; ts: number };
|
|
13
13
|
|
|
14
|
+
type AnimationRestoreOptions = {
|
|
15
|
+
restoreAnimationName?: string;
|
|
16
|
+
restoreGraphics?: any[];
|
|
17
|
+
};
|
|
18
|
+
|
|
14
19
|
export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
15
20
|
abstract _type: string;
|
|
16
21
|
emitParticleTrigger = trigger();
|
|
@@ -22,6 +27,10 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
22
27
|
graphicsSignals = signal<any[]>([]);
|
|
23
28
|
_component = {} // temporary component memory
|
|
24
29
|
flashTrigger = trigger();
|
|
30
|
+
private animationRestoreState?: {
|
|
31
|
+
animationName: string;
|
|
32
|
+
graphics: any[];
|
|
33
|
+
};
|
|
25
34
|
|
|
26
35
|
constructor() {
|
|
27
36
|
super();
|
|
@@ -39,8 +48,10 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
39
48
|
this.graphics.observable
|
|
40
49
|
.pipe(
|
|
41
50
|
map(({ items }) => items),
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
switchMap(graphics => {
|
|
52
|
+
if (graphics.length === 0) return of([]);
|
|
53
|
+
return from(Promise.all(graphics.map(graphic => this.engine.getSpriteSheet(graphic))));
|
|
54
|
+
})
|
|
44
55
|
)
|
|
45
56
|
.subscribe((sheets) => {
|
|
46
57
|
this.graphicsSignals.set(sheets);
|
|
@@ -101,6 +112,30 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
private animationSubscription?: Subscription;
|
|
115
|
+
private animationResetTimeout?: ReturnType<typeof setTimeout>;
|
|
116
|
+
|
|
117
|
+
private clearAnimationControls() {
|
|
118
|
+
if (this.animationSubscription) {
|
|
119
|
+
this.animationSubscription.unsubscribe();
|
|
120
|
+
this.animationSubscription = undefined;
|
|
121
|
+
}
|
|
122
|
+
if (this.animationResetTimeout) {
|
|
123
|
+
clearTimeout(this.animationResetTimeout);
|
|
124
|
+
this.animationResetTimeout = undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private finishTemporaryAnimation() {
|
|
129
|
+
const restoreState = this.animationRestoreState;
|
|
130
|
+
this.clearAnimationControls();
|
|
131
|
+
this.animationCurrentIndex.set(0);
|
|
132
|
+
if (restoreState) {
|
|
133
|
+
this.animationName.set(restoreState.animationName);
|
|
134
|
+
this.graphics.set([...restoreState.graphics]);
|
|
135
|
+
}
|
|
136
|
+
this.animationRestoreState = undefined;
|
|
137
|
+
this.animationIsPlaying.set(false);
|
|
138
|
+
}
|
|
104
139
|
|
|
105
140
|
/**
|
|
106
141
|
* Trigger a flash animation on this sprite
|
|
@@ -199,12 +234,13 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
199
234
|
* ```
|
|
200
235
|
*/
|
|
201
236
|
resetAnimationState() {
|
|
237
|
+
if (this.animationRestoreState) {
|
|
238
|
+
this.finishTemporaryAnimation();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
202
241
|
this.animationIsPlaying.set(false);
|
|
203
242
|
this.animationCurrentIndex.set(0);
|
|
204
|
-
|
|
205
|
-
this.animationSubscription.unsubscribe();
|
|
206
|
-
this.animationSubscription = undefined;
|
|
207
|
-
}
|
|
243
|
+
this.clearAnimationControls();
|
|
208
244
|
}
|
|
209
245
|
|
|
210
246
|
/**
|
|
@@ -226,7 +262,7 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
226
262
|
* player.setAnimation('spell');
|
|
227
263
|
* ```
|
|
228
264
|
*/
|
|
229
|
-
setAnimation(animationName: string, nbTimes?: number): void;
|
|
265
|
+
setAnimation(animationName: string, nbTimes?: number, options?: AnimationRestoreOptions): void;
|
|
230
266
|
/**
|
|
231
267
|
* Set a custom animation with temporary graphic change
|
|
232
268
|
*
|
|
@@ -244,30 +280,52 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
244
280
|
* player.setAnimation('attack', 'hero_attack', 3);
|
|
245
281
|
* ```
|
|
246
282
|
*/
|
|
247
|
-
setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number): void;
|
|
248
|
-
setAnimation(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
283
|
+
setAnimation(animationName: string, graphic?: string | string[], nbTimes?: number, options?: AnimationRestoreOptions): void;
|
|
284
|
+
setAnimation(
|
|
285
|
+
animationName: string,
|
|
286
|
+
graphicOrNbTimes?: string | string[] | number,
|
|
287
|
+
nbTimesOrOptions?: number | AnimationRestoreOptions,
|
|
288
|
+
options?: AnimationRestoreOptions
|
|
289
|
+
): void {
|
|
255
290
|
let graphic: string | string[] | undefined;
|
|
256
291
|
let finalNbTimes: number = Infinity;
|
|
292
|
+
let restoreOptions: AnimationRestoreOptions | undefined = options;
|
|
257
293
|
|
|
258
294
|
// Handle overloads
|
|
259
295
|
if (typeof graphicOrNbTimes === 'number') {
|
|
260
296
|
// setAnimation(animationName, nbTimes)
|
|
261
297
|
finalNbTimes = graphicOrNbTimes;
|
|
298
|
+
restoreOptions = typeof nbTimesOrOptions === 'object' ? nbTimesOrOptions : options;
|
|
262
299
|
} else if (graphicOrNbTimes !== undefined) {
|
|
263
300
|
// setAnimation(animationName, graphic, nbTimes)
|
|
264
301
|
graphic = graphicOrNbTimes;
|
|
265
|
-
|
|
302
|
+
if (typeof nbTimesOrOptions === 'number') {
|
|
303
|
+
finalNbTimes = nbTimesOrOptions;
|
|
304
|
+
} else {
|
|
305
|
+
finalNbTimes = Infinity;
|
|
306
|
+
restoreOptions = nbTimesOrOptions ?? options;
|
|
307
|
+
}
|
|
266
308
|
} else {
|
|
267
309
|
// setAnimation(animationName) - nbTimes remains Infinity
|
|
268
310
|
finalNbTimes = Infinity;
|
|
269
311
|
}
|
|
270
312
|
|
|
313
|
+
if (this.animationIsPlaying()) {
|
|
314
|
+
this.finishTemporaryAnimation();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.animationIsPlaying.set(true);
|
|
318
|
+
const previousAnimationName =
|
|
319
|
+
restoreOptions?.restoreAnimationName ?? this.animationName();
|
|
320
|
+
const previousGraphics = restoreOptions?.restoreGraphics
|
|
321
|
+
? [...restoreOptions.restoreGraphics]
|
|
322
|
+
: [...this.graphics()];
|
|
323
|
+
this.animationRestoreState = {
|
|
324
|
+
animationName: previousAnimationName,
|
|
325
|
+
graphics: previousGraphics,
|
|
326
|
+
};
|
|
327
|
+
this.animationCurrentIndex.set(0);
|
|
328
|
+
|
|
271
329
|
// Temporarily change graphic if provided
|
|
272
330
|
if (graphic !== undefined) {
|
|
273
331
|
if (Array.isArray(graphic)) {
|
|
@@ -277,27 +335,23 @@ export abstract class RpgClientObject extends RpgCommonPlayer {
|
|
|
277
335
|
}
|
|
278
336
|
}
|
|
279
337
|
|
|
280
|
-
|
|
281
|
-
if (this.animationSubscription) {
|
|
282
|
-
this.animationSubscription.unsubscribe();
|
|
283
|
-
}
|
|
338
|
+
this.clearAnimationControls();
|
|
284
339
|
|
|
285
340
|
this.animationSubscription =
|
|
286
341
|
this.animationCurrentIndex.observable.subscribe((index) => {
|
|
287
342
|
if (index >= finalNbTimes) {
|
|
288
|
-
this.
|
|
289
|
-
this.animationName.set(previousAnimationName);
|
|
290
|
-
// Reset graphic to previous value if it was changed
|
|
291
|
-
if (graphic !== undefined) {
|
|
292
|
-
this.graphics.set(previousGraphics);
|
|
293
|
-
}
|
|
294
|
-
this.animationIsPlaying.set(false);
|
|
295
|
-
if (this.animationSubscription) {
|
|
296
|
-
this.animationSubscription.unsubscribe();
|
|
297
|
-
this.animationSubscription = undefined;
|
|
298
|
-
}
|
|
343
|
+
this.finishTemporaryAnimation();
|
|
299
344
|
}
|
|
300
345
|
});
|
|
346
|
+
|
|
347
|
+
if (finalNbTimes !== Infinity) {
|
|
348
|
+
this.animationResetTimeout = setTimeout(() => {
|
|
349
|
+
if (this.animationIsPlaying()) {
|
|
350
|
+
this.finishTemporaryAnimation();
|
|
351
|
+
}
|
|
352
|
+
}, Math.max(1000, finalNbTimes * 1000));
|
|
353
|
+
}
|
|
354
|
+
|
|
301
355
|
this.animationName.set(animationName);
|
|
302
356
|
}
|
|
303
357
|
|
package/src/RpgClientEngine.ts
CHANGED
|
@@ -374,15 +374,27 @@ export class RpgClientEngine<T = any> {
|
|
|
374
374
|
this.notificationManager.add(data);
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
-
|
|
378
|
-
const {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
377
|
+
this.webSocket.on("setAnimation", (data) => {
|
|
378
|
+
const {
|
|
379
|
+
animationName,
|
|
380
|
+
nbTimes,
|
|
381
|
+
object,
|
|
382
|
+
graphic,
|
|
383
|
+
restoreAnimationName,
|
|
384
|
+
restoreGraphics,
|
|
385
|
+
} = data;
|
|
386
|
+
const player = object ? this.sceneMap.getObjectById(object) : undefined;
|
|
387
|
+
if (!player) return;
|
|
388
|
+
const restoreOptions = {
|
|
389
|
+
restoreAnimationName,
|
|
390
|
+
restoreGraphics,
|
|
391
|
+
};
|
|
392
|
+
if (graphic !== undefined) {
|
|
393
|
+
player.setAnimation(animationName, graphic, nbTimes, restoreOptions);
|
|
394
|
+
} else {
|
|
395
|
+
player.setAnimation(animationName, nbTimes, restoreOptions);
|
|
396
|
+
}
|
|
397
|
+
})
|
|
386
398
|
|
|
387
399
|
this.webSocket.on("playSound", (data) => {
|
|
388
400
|
const { soundId, volume, loop } = data;
|
|
@@ -1174,6 +1186,18 @@ export class RpgClientEngine<T = any> {
|
|
|
1174
1186
|
}
|
|
1175
1187
|
|
|
1176
1188
|
async processInput({ input }: { input: Direction }) {
|
|
1189
|
+
if (this.stopProcessingInput) return;
|
|
1190
|
+
|
|
1191
|
+
const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
|
|
1192
|
+
const canMove =
|
|
1193
|
+
!currentPlayer ||
|
|
1194
|
+
typeof currentPlayer.canMove !== "function" ||
|
|
1195
|
+
currentPlayer.canMove();
|
|
1196
|
+
if (!canMove) {
|
|
1197
|
+
this.interruptCurrentPlayerMovement(currentPlayer);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1177
1201
|
const timestamp = Date.now();
|
|
1178
1202
|
let frame: number;
|
|
1179
1203
|
let tick: number;
|
|
@@ -1188,7 +1212,6 @@ export class RpgClientEngine<T = any> {
|
|
|
1188
1212
|
this.inputFrameCounter = frame;
|
|
1189
1213
|
this.hooks.callHooks("client-engine-onInput", this, { input, playerId: this.playerId }).subscribe();
|
|
1190
1214
|
|
|
1191
|
-
const currentPlayer = this.sceneMap.getCurrentPlayer();
|
|
1192
1215
|
const bodyReady = this.ensureCurrentPlayerBody();
|
|
1193
1216
|
if (currentPlayer && bodyReady) {
|
|
1194
1217
|
currentPlayer.changeDirection(input);
|
|
@@ -1207,6 +1230,13 @@ export class RpgClientEngine<T = any> {
|
|
|
1207
1230
|
|
|
1208
1231
|
processAction({ action }: { action: number }) {
|
|
1209
1232
|
if (this.stopProcessingInput) return;
|
|
1233
|
+
const currentPlayer = this.sceneMap.getCurrentPlayer() as any;
|
|
1234
|
+
const canMove =
|
|
1235
|
+
!currentPlayer ||
|
|
1236
|
+
typeof currentPlayer.canMove !== "function" ||
|
|
1237
|
+
currentPlayer.canMove();
|
|
1238
|
+
if (!canMove) return;
|
|
1239
|
+
|
|
1210
1240
|
this.hooks.callHooks("client-engine-onInput", this, { input: 'action', playerId: this.playerId }).subscribe();
|
|
1211
1241
|
this.webSocket.emit('action', { action })
|
|
1212
1242
|
}
|
|
@@ -1337,6 +1367,15 @@ export class RpgClientEngine<T = any> {
|
|
|
1337
1367
|
if (!this.predictionEnabled || !this.prediction) {
|
|
1338
1368
|
return;
|
|
1339
1369
|
}
|
|
1370
|
+
const player = this.sceneMap?.getCurrentPlayer?.() as any;
|
|
1371
|
+
if (
|
|
1372
|
+
player &&
|
|
1373
|
+
typeof player.canMove === "function" &&
|
|
1374
|
+
!player.canMove()
|
|
1375
|
+
) {
|
|
1376
|
+
this.interruptCurrentPlayerMovement(player);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1340
1379
|
const pendingInputs = this.prediction.getPendingInputs();
|
|
1341
1380
|
if (pendingInputs.length === 0) {
|
|
1342
1381
|
return;
|
|
@@ -1475,6 +1514,34 @@ export class RpgClientEngine<T = any> {
|
|
|
1475
1514
|
this.lastMovePathSentFrame = 0;
|
|
1476
1515
|
}
|
|
1477
1516
|
|
|
1517
|
+
/**
|
|
1518
|
+
* Stop local movement immediately and discard pending predicted movement.
|
|
1519
|
+
*
|
|
1520
|
+
* Use this before a blocking action such as an A-RPG attack, dialog, dash
|
|
1521
|
+
* startup, or any client-side state where already buffered movement inputs
|
|
1522
|
+
* must not be replayed after server reconciliation.
|
|
1523
|
+
*
|
|
1524
|
+
* @param player - Player object to stop. Defaults to the current player.
|
|
1525
|
+
* @returns `true` when a player was found and interrupted.
|
|
1526
|
+
*
|
|
1527
|
+
* @example
|
|
1528
|
+
* ```ts
|
|
1529
|
+
* engine.interruptCurrentPlayerMovement();
|
|
1530
|
+
* ```
|
|
1531
|
+
*/
|
|
1532
|
+
interruptCurrentPlayerMovement(player: any = this.sceneMap?.getCurrentPlayer?.()): boolean {
|
|
1533
|
+
if (!player) {
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
(this.sceneMap as any)?.stopMovement?.(player);
|
|
1537
|
+
this.prediction?.clearPendingInputs();
|
|
1538
|
+
this.pendingPredictionFrames = [];
|
|
1539
|
+
this.lastInputTime = 0;
|
|
1540
|
+
this.lastMovePathSentAt = Date.now();
|
|
1541
|
+
this.lastMovePathSentFrame = this.inputFrameCounter;
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1478
1545
|
/**
|
|
1479
1546
|
* Trigger a flash animation on a sprite
|
|
1480
1547
|
*
|
|
@@ -1560,7 +1627,7 @@ export class RpgClientEngine<T = any> {
|
|
|
1560
1627
|
if (typeof ack.x !== "number" || typeof ack.y !== "number") {
|
|
1561
1628
|
return;
|
|
1562
1629
|
}
|
|
1563
|
-
const player = this.getCurrentPlayer();
|
|
1630
|
+
const player = this.getCurrentPlayer() as any;
|
|
1564
1631
|
const myId = this.playerIdSignal();
|
|
1565
1632
|
if (!player || !myId) {
|
|
1566
1633
|
return;
|
|
@@ -1583,10 +1650,14 @@ export class RpgClientEngine<T = any> {
|
|
|
1583
1650
|
authoritativeState: PredictionState<Direction>,
|
|
1584
1651
|
pendingInputs: PredictionHistoryEntry<Direction>[],
|
|
1585
1652
|
): void {
|
|
1586
|
-
const player = this.getCurrentPlayer();
|
|
1653
|
+
const player = this.getCurrentPlayer() as any;
|
|
1587
1654
|
if (!player) {
|
|
1588
1655
|
return;
|
|
1589
1656
|
}
|
|
1657
|
+
if (typeof player.canMove === "function" && !player.canMove()) {
|
|
1658
|
+
this.interruptCurrentPlayerMovement(player);
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1590
1661
|
|
|
1591
1662
|
(this.sceneMap as any).stopMovement(player);
|
|
1592
1663
|
this.applyAuthoritativeState(authoritativeState);
|