@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.
- package/CHANGELOG.md +19 -0
- package/dist/Game/AnimationManager.d.ts +1 -0
- package/dist/Game/AnimationManager.js +3 -0
- package/dist/Game/AnimationManager.js.map +1 -1
- package/dist/Game/ClientVisuals.d.ts +61 -0
- package/dist/Game/ClientVisuals.js +96 -0
- package/dist/Game/ClientVisuals.js.map +1 -0
- package/dist/Game/ClientVisuals.spec.d.ts +1 -0
- package/dist/Game/EventComponentResolver.d.ts +16 -0
- package/dist/Game/EventComponentResolver.js +52 -0
- package/dist/Game/EventComponentResolver.js.map +1 -0
- package/dist/Game/EventComponentResolver.spec.d.ts +1 -0
- package/dist/Game/Map.js +9 -0
- package/dist/Game/Map.js.map +1 -1
- package/dist/Game/Object.d.ts +2 -0
- package/dist/Game/Object.js +22 -8
- package/dist/Game/Object.js.map +1 -1
- package/dist/Game/Object.spec.d.ts +1 -0
- package/dist/Game/ProjectileManager.d.ts +11 -2
- package/dist/Game/ProjectileManager.js +19 -2
- package/dist/Game/ProjectileManager.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 +85 -1
- package/dist/RpgClientEngine.d.ts +77 -2
- package/dist/RpgClientEngine.js +290 -31
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/animations/fx.ce.js +58 -0
- package/dist/components/animations/fx.ce.js.map +1 -0
- package/dist/components/animations/index.d.ts +1 -0
- package/dist/components/animations/index.js +3 -1
- package/dist/components/animations/index.js.map +1 -1
- package/dist/components/character.ce.js +192 -19
- 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 +3 -0
- package/dist/index.js +5 -2
- package/dist/module.js +30 -3
- package/dist/module.js.map +1 -1
- package/dist/services/actionInput.d.ts +3 -1
- package/dist/services/actionInput.js +33 -1
- package/dist/services/actionInput.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/dist/services/standalone.d.ts +3 -1
- package/dist/services/standalone.js +31 -13
- package/dist/services/standalone.js.map +1 -1
- package/dist/utils/mapId.d.ts +1 -0
- package/dist/utils/mapId.js +6 -0
- package/dist/utils/mapId.js.map +1 -0
- package/package.json +4 -4
- package/src/Game/AnimationManager.ts +4 -0
- package/src/Game/ClientVisuals.spec.ts +56 -0
- package/src/Game/ClientVisuals.ts +184 -0
- package/src/Game/EventComponentResolver.spec.ts +84 -0
- package/src/Game/EventComponentResolver.ts +74 -0
- package/src/Game/Map.ts +10 -0
- package/src/Game/Object.spec.ts +59 -0
- package/src/Game/Object.ts +36 -12
- package/src/Game/ProjectileManager.spec.ts +111 -0
- package/src/Game/ProjectileManager.ts +24 -2
- package/src/Gui/Gui.spec.ts +67 -0
- package/src/Gui/Gui.ts +24 -7
- package/src/RpgClient.ts +96 -1
- package/src/RpgClientEngine.ts +378 -45
- package/src/components/animations/fx.ce +101 -0
- package/src/components/animations/index.ts +4 -2
- package/src/components/character.ce +243 -17
- 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 +3 -0
- package/src/module.ts +43 -10
- package/src/services/actionInput.spec.ts +54 -0
- package/src/services/actionInput.ts +68 -1
- package/src/services/interactions.spec.ts +175 -0
- package/src/services/interactions.ts +722 -0
- package/src/services/keyboardControls.ts +2 -1
- package/src/services/standalone.ts +39 -10
- package/src/utils/mapId.ts +2 -0
|
@@ -1 +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":";;AAiFA,IAAa,oBAAb,MAA+B;CAM7B,YACE,OACA,oBACA;EAFiB,KAAA,QAAA;EACA,KAAA,qBAAA;oCAPW,IAAI,IAAiB;qCACpB,IAAI,IAA+B;iBACvC,OAAO,CAAC;0BACC;iBAO1B,eAAqC;GAC7C,KAAK,QAAQ;GACb,MAAM,MAAM,KAAK,IAAI;GACrB,MAAM,WAAiC,CAAC;GACxC,KAAK,MAAM,cAAc,KAAK,YAAY,OAAO,GAAG;IAClD,MAAM,QAAQ,KAAK,QAAQ,YAAY,GAAG;IAC1C,IAAI,CAAC,OACH;IAEF,SAAS,KAAK;KACZ,IAAI,WAAW,MAAM;KACrB,MAAM,WAAW,MAAM;KACvB,WAAW,WAAW;KACtB;IACF,CAAC;GACH;GACA,OAAO;EACT,CAAC;CAnBE;CAqBH,SAAS,MAAc,WAAqB;EAC1C,KAAK,WAAW,IAAI,MAAM,SAAS;EACnC,OAAO;CACT;CAEA,IAAI,MAAmB;EACrB,OAAO,KAAK,WAAW,IAAI,IAAI;CACjC;CAEA,WAAW,aAAsC,QAA8B,CAAC,GAAS;EACvF,MAAM,MAAM,MAAM,OAAO,KAAK,IAAI;EAClC,KAAK,MAAM,cAAc,aAAa;GACpC,MAAM,YAAY,KAAK,WAAW,IAAI,WAAW,IAAI;GACrD,IAAI,CAAC,WACH;GAEF,MAAM,UAA6B;IACjC,OAAO;KACL,GAAG;KACH,OAAO,WAAW,SAAS;KAC3B,OAAO,WAAW,SAAS;KAC3B,OAAO,WAAW,SAAS;IAC7B;IACA;IACA,WAAW;GACb;GACA,KAAK,mBAAmB,OAAO;GAC/B,KAAK,YAAY,IAAI,WAAW,IAAI,OAAO;GAC3C,KAAK,MAAM,UAAU,8BAA8B,QAAQ,KAAK,EAAE,UAAU;EAC9E;EACA,KAAK,MAAM;CACb;CAEA,YAAY,SAAyC;EACnD,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,aAAa,KAAK,YAAY,IAAI,OAAO,EAAE;GACjD,IAAI,CAAC,YACH;GAEF,KAAK,UAAU,YAAY,QAAQ,GAAG;GACtC,KAAK,MAAM,UAAU,+BAA+B,KAAK,QAAQ,YAAY,GAAG,CAAC,EAAE,UAAU;EAC/F;EACA,KAAK,MAAM;CACb;CAEA,aAAa,aAA8C;EACzD,MAAM,MAAM,KAAK,IAAI;EACrB,KAAK,MAAM,aAAa,aAAa;GACnC,MAAM,aAAa,KAAK,YAAY,IAAI,UAAU,EAAE;GACpD,IAAI,CAAC,YACH;GAEF,IAAI,UAAU,WAAW,OAAO;IAC9B,MAAM,UAAU,KAAK,QAAQ,YAAY,GAAG;IAC5C,KAAK,UAAU,YAAY;KACzB,IAAI,UAAU;KACd,UAAU,UAAU,YAAY,WAAW,QAAQ;KACnD,GAAG,UAAU,KAAK,WAAW,QAAQ,KAAK,SAAS,KAAK,WAAW,MAAM,OAAO;KAChF,GAAG,UAAU,KAAK,WAAW,QAAQ,KAAK,SAAS,KAAK,WAAW,MAAM,OAAO;KAChF,UAAU,UAAU,YAAY,WAAW,QAAQ,YAAY,SAAS;IAC1E,GAAG,GAAG;GACR;GACA,WAAW,gBAAgB,UAAU;GACrC,WAAW,YAAY,WAAW,cAChC,WAAW,UAAU,WAAW,oBAAoB,KAAA,IAChD,WAAW,kBAAkB,KAAK,mBAClC;GAEN,KAAK,MAAM,UAAU,gCAAgC,KAAK,QAAQ,YAAY,GAAG,CAAC,EAAE,UAAU;EAChG;EACA,KAAK,MAAM;CACb;CAEA,QAAc;EACZ,KAAK,YAAY,MAAM;EACvB,KAAK,MAAM;CACb;CAEA,OAAa;EACX,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,UAAU;EACd,KAAK,MAAM,CAAC,IAAI,eAAe,KAAK,aAElC,IACG,CAFW,KAAK,QAAQ,YAAY,GAEnC,KAAS,CAAC,KAAK,kBAAkB,YAAY,GAAG,KACjD,WAAW,cAAc,KAAA,KAAa,OAAO,WAAW,WACzD;GACA,KAAK,YAAY,OAAO,EAAE;GAC1B,UAAU;EACZ;EAEF,KAAK,MAAM,WAAW,KAAK,YAAY,OAAO,CAAC;CACjD;CAEA,QAAgB,YAA+B,KAA6C;EAC1F,MAAM,QAAQ,WAAW;EACzB,MAAM,WAAW,MAAM,SAAS,KAAK;EACrC,MAAM,YAAY,MAAM,WAAW,YAAY;EAC/C,IAAI,YAAY,GACd,OAAO;EAET,MAAM,UAAU,YAAY;EAC5B,MAAM,MAAM,KAAK,IAAI,MAAO,MAAM,GAAG;EACrC,MAAM,cAAc,KAAK,IAAI,MAAM,QAAQ,SAAS,MAAM,KAAK;EAC/D,MAAM,kBAAkB,KAAK,yBAAyB,YAAY,KAAK,WAAW;EAClF,MAAM,eAAe,WAAW,gBAAgB,WAAW;EAC3D,MAAM,WAAW,cAAc,YAAY,iBAAiB,YAAY;EACxE,MAAM,WAAW,KAAK,IAAI,GAAG,WAAW,MAAM,KAAK;EACnD,MAAM,IAAI,cAAc,KAAK,iBAAiB,KAAK,MAAM,OAAO,IAAI,MAAM,UAAU,IAAI;EACxF,MAAM,IAAI,cAAc,KAAK,iBAAiB,KAAK,MAAM,OAAO,IAAI,MAAM,UAAU,IAAI;EACxF,MAAM,kBAAkB,WAAW,oBAAoB,KAAA,IACnD,KAAK,IAAI,GAAG,MAAM,WAAW,eAAe,IAC5C,KAAA;EACJ,OAAO;GACL,GAAG;GACH;GACA;GACA,OAAO,KAAK,MAAM,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC;GACtD;GACA;GACA;GACA,QAAQ,WAAW;GACnB,eAAe,oBAAoB,KAAA,IAAY,KAAA,IAAY,kBAAkB;GAC7E,gBAAgB,oBAAoB,KAAA,IAChC,KAAA,IACA,KAAK,IAAI,GAAG,kBAAkB,KAAK,gBAAgB;GACvD,WAAW,WAAW,cAAc,KAAA;GACpC;EACF;CACF;CAEA,kBAA0B,YAA+B,KAAsB;EAC7E,MAAM,WAAW,WAAW,MAAM,SAAS,KAAK;EAChD,OAAO,MAAM,WAAW,YAAY,UAAU;CAChD;CAEA,mBAA2B,YAAqC;EAC9D,IAAI,WAAW,MAAM,kBAAkB,OACrC;EAEF,MAAM,SAAS,KAAK,qBAAqB,WAAW,KAAK;EACzD,IAAI,CAAC,UAAU,CAAC,OAAO,SAAS,OAAO,CAAC,KAAK,CAAC,OAAO,SAAS,OAAO,CAAC,GACpE;EAEF,MAAM,WAAW,OAAO,OAAO,aAAa,YAAY,OAAO,SAAS,OAAO,QAAQ,IACnF,OAAO,WACP,KAAK,MAAM,OAAO,IAAI,WAAW,MAAM,OAAO,GAAG,OAAO,IAAI,WAAW,MAAM,OAAO,CAAC;EACzF,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,WAAW,KAAK,WAAW,WAAW,MAAM,OAC5E;EAEF,WAAW,kBAAkB;GAC3B,GAAG;GACH;EACF;CACF;CAEA,yBACE,YACA,KACA,aACoC;EACpC,IAAI,CAAC,WAAW,mBAAmB,WAAW,QAC5C;EAEF,MAAM,WAAW,WAAW,gBAAgB;EAC5C,IAAI,aAAa,KAAA,KAAa,cAAc,UAC1C;EAEF,OAAO,WAAW;CACpB;CAEA,UAAkB,YAA+B,QAAgC,KAAmB;EAClG,WAAW,eAAe,KAAK,oBAAoB,YAAY,QAAQ,GAAG;EAC1E,WAAW,SAAS;EACpB,WAAW,kBAAkB,KAAA;EAC7B,WAAW,kBAAkB,WAAW,mBAAmB;EAC3D,MAAM,kBAAkB,WAAW,kBAAkB,KAAK;EAC1D,WAAW,YAAY,KAAK,IAAI,WAAW,aAAa,GAAG,eAAe;CAC5E;CAEA,oBACE,YACA,QACA,KACwB;EACxB,MAAM,YAAY,WAAW;EAC7B,IAAI,CAAC,aAAa,CAAC,KAAK,aAAa,WAAW,MAAM,GACpD,OAAO;EAET,MAAM,WAAW,UAAU;EAC3B,IAAI,aAAa,KAAA,GACf,OAAO;EAET,MAAM,WAAW,WAAW,MAAM,SAAS,KAAK;EAChD,MAAM,YAAY,MAAM,WAAW,YAAY;EAC/C,IAAI,YAAY,GACd,OAAO;EAGT,OADoB,KAAK,IAAI,WAAW,MAAM,SAAS,YAAY,MAAO,WAAW,MAAM,KACpF,KAAe,WAAW,YAAY;CAC/C;CAEA,aAAqB,GAA2B,GAAoC;EAClF,OAAO,EAAE,aAAa,KAAA,KAAa,EAAE,aAAa,EAAE;CACtD;CAEA,MAAc,QAAQ,MAAY;EAChC,IAAI,OACF,KAAK,QAAQ,QAAQ,UAAU,QAAQ,CAAC;CAE5C;AACF"}
|
|
1
|
+
{"version":3,"file":"ProjectileManager.js","names":[],"sources":["../../src/Game/ProjectileManager.ts"],"sourcesContent":["import { computed, signal } from \"canvasengine\";\nimport { Hooks } from \"@rpgjs/common\";\nimport { normalizeRoomMapId } from \"../utils/mapId\";\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 mapId?: string;\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 private mapId?: string;\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 setMapId(mapId: string | undefined): void {\n const normalizedMapId = normalizeRoomMapId(mapId);\n if (this.mapId === normalizedMapId) return;\n this.mapId = normalizedMapId;\n this.clear();\n }\n\n getMapId(): string | undefined {\n return this.mapId;\n }\n\n spawnBatch(projectiles: ClientProjectileSpawn[], clock: ProjectileSpawnClock = {}): void {\n if (!this.acceptsMap(clock.mapId)) return;\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[], context: { mapId?: string } = {}): void {\n if (!this.acceptsMap(context.mapId)) return;\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[], context: { mapId?: string } = {}): void {\n if (!this.acceptsMap(context.mapId)) return;\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 acceptsMap(mapId: string | undefined): boolean {\n const normalizedMapId = normalizeRoomMapId(mapId);\n return !normalizedMapId || !this.mapId || normalizedMapId === this.mapId;\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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"}
|
package/dist/Gui/Gui.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface GuiInstance {
|
|
|
34
34
|
component: any;
|
|
35
35
|
display: WritableSignal<boolean>;
|
|
36
36
|
data: WritableSignal<any>;
|
|
37
|
+
openId?: string;
|
|
37
38
|
autoDisplay: boolean;
|
|
38
39
|
dependencies?: Signal[];
|
|
39
40
|
subscription?: Subscription;
|
|
@@ -83,7 +84,7 @@ export declare class RpgGui {
|
|
|
83
84
|
*/
|
|
84
85
|
_initializeVueComponents(): void;
|
|
85
86
|
guiInteraction(guiId: string, name: string, data: any): void;
|
|
86
|
-
guiClose(guiId: string, data?: any): void;
|
|
87
|
+
guiClose(guiId: string, data?: any, guiOpenId?: unknown): void;
|
|
87
88
|
/**
|
|
88
89
|
* Add a GUI component to the system
|
|
89
90
|
*
|
|
@@ -167,7 +168,7 @@ export declare class RpgGui {
|
|
|
167
168
|
* gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);
|
|
168
169
|
* ```
|
|
169
170
|
*/
|
|
170
|
-
display(id: string, data?: {}, dependencies?: Signal[]): void;
|
|
171
|
+
display(id: string, data?: {}, dependencies?: Signal[], openId?: string): void;
|
|
171
172
|
isDisplaying(id: string): boolean;
|
|
172
173
|
/**
|
|
173
174
|
* Handle Vue component display logic
|
package/dist/Gui/Gui.js
CHANGED
|
@@ -118,9 +118,13 @@ var RpgGui = class {
|
|
|
118
118
|
async _initialize() {
|
|
119
119
|
this.webSocket.on("gui.open", (data) => {
|
|
120
120
|
this.clearPendingActions(data.guiId);
|
|
121
|
-
this.display(data.guiId, data.data);
|
|
121
|
+
this.display(data.guiId, data.data, [], data.guiOpenId);
|
|
122
122
|
});
|
|
123
|
-
this.webSocket.on("gui.exit", (
|
|
123
|
+
this.webSocket.on("gui.exit", (payload) => {
|
|
124
|
+
const guiId = typeof payload === "string" ? payload : payload.guiId;
|
|
125
|
+
const guiOpenId = typeof payload === "string" ? void 0 : payload.guiOpenId;
|
|
126
|
+
const current = this.get(guiId);
|
|
127
|
+
if (guiOpenId && current?.openId && current.openId !== guiOpenId) return;
|
|
124
128
|
this.hide(guiId);
|
|
125
129
|
});
|
|
126
130
|
this.webSocket.on("gui.update", (payload) => {
|
|
@@ -186,9 +190,11 @@ var RpgGui = class {
|
|
|
186
190
|
data: actionData
|
|
187
191
|
});
|
|
188
192
|
}
|
|
189
|
-
guiClose(guiId, data) {
|
|
193
|
+
guiClose(guiId, data, guiOpenId) {
|
|
194
|
+
const normalizedOpenId = typeof guiOpenId === "string" && guiOpenId.length > 0 ? guiOpenId : void 0;
|
|
190
195
|
this.webSocket.emit("gui.exit", {
|
|
191
196
|
guiId,
|
|
197
|
+
guiOpenId: normalizedOpenId,
|
|
192
198
|
data
|
|
193
199
|
});
|
|
194
200
|
}
|
|
@@ -235,6 +241,7 @@ var RpgGui = class {
|
|
|
235
241
|
component,
|
|
236
242
|
display: signal(gui.display || false),
|
|
237
243
|
data: signal(gui.data || {}),
|
|
244
|
+
openId: void 0,
|
|
238
245
|
autoDisplay: gui.autoDisplay || false,
|
|
239
246
|
dependencies: gui.dependencies ? gui.dependencies() : [],
|
|
240
247
|
attachToSprite
|
|
@@ -328,11 +335,12 @@ var RpgGui = class {
|
|
|
328
335
|
* gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);
|
|
329
336
|
* ```
|
|
330
337
|
*/
|
|
331
|
-
display(id, data = {}, dependencies = []) {
|
|
338
|
+
display(id, data = {}, dependencies = [], openId) {
|
|
332
339
|
if (!this.exists(id)) throw throwError(id);
|
|
333
340
|
const guiInstance = this.get(id);
|
|
334
|
-
if (this.extraGuis.some((gui) => gui.name === id)) this._handleVueComponentDisplay(id, data, dependencies, guiInstance);
|
|
341
|
+
if (this.extraGuis.some((gui) => gui.name === id)) this._handleVueComponentDisplay(id, data, dependencies, guiInstance, openId);
|
|
335
342
|
else {
|
|
343
|
+
guiInstance.openId = openId;
|
|
336
344
|
guiInstance.data.set(data);
|
|
337
345
|
guiInstance.display.set(true);
|
|
338
346
|
}
|
|
@@ -350,7 +358,7 @@ var RpgGui = class {
|
|
|
350
358
|
* @param dependencies - Runtime dependencies
|
|
351
359
|
* @param guiInstance - GUI instance
|
|
352
360
|
*/
|
|
353
|
-
_handleVueComponentDisplay(id, data, dependencies, guiInstance) {
|
|
361
|
+
_handleVueComponentDisplay(id, data, dependencies, guiInstance, openId) {
|
|
354
362
|
if (guiInstance.subscription) {
|
|
355
363
|
guiInstance.subscription.unsubscribe();
|
|
356
364
|
guiInstance.subscription = void 0;
|
|
@@ -359,6 +367,7 @@ var RpgGui = class {
|
|
|
359
367
|
if (deps.length > 0) {
|
|
360
368
|
guiInstance.subscription = combineLatest(deps.map((dependency) => dependency.observable)).subscribe((values) => {
|
|
361
369
|
if (values.every((value) => value !== void 0)) {
|
|
370
|
+
guiInstance.openId = openId;
|
|
362
371
|
guiInstance.data.set(data);
|
|
363
372
|
guiInstance.display.set(true);
|
|
364
373
|
this._notifyVueGui(id, true, data);
|
|
@@ -366,6 +375,7 @@ var RpgGui = class {
|
|
|
366
375
|
});
|
|
367
376
|
return;
|
|
368
377
|
}
|
|
378
|
+
guiInstance.openId = openId;
|
|
369
379
|
guiInstance.data.set(data);
|
|
370
380
|
guiInstance.display.set(true);
|
|
371
381
|
this._notifyVueGui(id, true, data);
|
|
@@ -391,6 +401,7 @@ var RpgGui = class {
|
|
|
391
401
|
guiInstance.subscription = void 0;
|
|
392
402
|
}
|
|
393
403
|
guiInstance.display.set(false);
|
|
404
|
+
guiInstance.openId = void 0;
|
|
394
405
|
if (this.extraGuis.some((gui) => gui.name === id)) this._notifyVueGui(id, false);
|
|
395
406
|
}
|
|
396
407
|
isVueComponent(id) {
|
|
@@ -426,6 +437,7 @@ var RpgGui = class {
|
|
|
426
437
|
component: gui.component,
|
|
427
438
|
display,
|
|
428
439
|
data,
|
|
440
|
+
openId: gui.openId,
|
|
429
441
|
attachToSprite: gui.attachToSprite || false
|
|
430
442
|
};
|
|
431
443
|
}
|
package/dist/Gui/Gui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data);\n });\n\n this.webSocket.on(\"gui.exit\", (guiId: string) => {\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any) {\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = []) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Check if it's a Vue component (in extraGuis)\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n \n if (isVueComponent) {\n // Handle Vue component display\n this._handleVueComponentDisplay(id, data, dependencies, guiInstance);\n } else {\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n }\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n // Subscribe to dependencies\n guiInstance.subscription = combineLatest(\n deps.map(dependency => dependency.observable)\n ).subscribe((values) => {\n if (values.every(value => value !== undefined)) {\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n });\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAqEA,IAAM,cAAc,OAAe;CACjC,MAAM,iBAAiB,GAAG;AAC5B;AAEA,IAAM,sBAAsB,OAAc,OAAe;CACvD,MAAM,QAAQ,MAAM,WAAW,SAAS,MAAM,OAAO,EAAE;CACvD,IAAI,UAAU,IAAI,OAAO;CACzB,MAAM,OAAO,MAAM;CACnB,IAAI,MAAM,WAAW,OAAO,OAAO;CACnC,IAAI,MAAM,eAAe,OAAO,OAAO;CACvC,MAAM,WAAW,OAAO,MAAM,aAAa,WAAW,KAAK,WAAW;CACtE,MAAM,eAAe,KAAK,IAAI,GAAG,WAAW,CAAC;CAC7C,IAAI,iBAAiB,UAAU,OAAO;CACtC,IAAI,gBAAgB,GAClB,OAAO,MAAM,QAAQ,GAAG,QAAQ,QAAQ,KAAK;CAE/C,MAAM,YAAY,MAAM,MAAM;CAC9B,UAAU,SAAS;EAAE,GAAG;EAAM,UAAU;CAAa;CACrD,OAAO;AACT;AAEA,IAAM,sBAAsB,OAAc,IAAY,UAAmB;CACvE,MAAM,QAAQ,MAAM,WAAW,SAAS,MAAM,OAAO,EAAE;CACvD,IAAI,UAAU,IAAI,OAAO;CACzB,MAAM,OAAO,MAAM;CACnB,IAAI,MAAM,aAAa,OAAO,OAAO;CACrC,MAAM,YAAY,MAAM,MAAM;CAC9B,UAAU,SAAS;EAAE,GAAG;EAAM,UAAU;CAAM;CAC9C,OAAO;AACT;AAEA,IAAM,6BAAgD,MAAM,WAAW;CACrE,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,IAAI,OAAO,SAAS,WAAW;EAC7B,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,GAAG,OAAO;EACvC,MAAM,KAAK,OAAO,MAAM;EACxB,IAAI,CAAC,IAAI,OAAO;EAChB,MAAM,YAAY,mBAAmB,KAAK,OAAO,EAAE;EACnD,IAAI,cAAc,KAAK,OAAO,OAAO;EACrC,OAAO;GAAE,GAAG;GAAM,OAAO;EAAU;CACrC;CACA,IAAI,OAAO,SAAS,aAAa;EAC/B,MAAM,KAAK,OAAO,MAAM;EACxB,IAAI,CAAC,MAAM,OAAO,OAAO,MAAM,UAAU,WAAW,OAAO;EAC3D,MAAM,QAAQ,OAAO,KAAK;EAC1B,IAAI,YAAY,KAAK;EACrB,IAAI,aAAa,KAAK;EACtB,IAAI,MAAM,QAAQ,KAAK,KAAK,GAC1B,YAAY,mBAAmB,KAAK,OAAO,IAAI,KAAK;EAEtD,IAAI,MAAM,QAAQ,KAAK,MAAM,GAC3B,aAAa,mBAAmB,KAAK,QAAQ,IAAI,KAAK;EAExD,IAAI,cAAc,KAAK,SAAS,eAAe,KAAK,QAAQ,OAAO;EACnE,OAAO;GACL,GAAG;GACH,GAAI,cAAc,KAAK,QAAQ,EAAE,OAAO,UAAU,IAAI,CAAC;GACvD,GAAI,eAAe,KAAK,SAAS,EAAE,QAAQ,WAAW,IAAI,CAAC;EAC7D;CACF;CACA,OAAO;AACT;AAEA,IAAa,SAAb,MAAoB;CAalB,YAAY,SAA0B;EAAlB,KAAA,UAAA;aAXd,OAAoC,CAAC,CAAC;mBACjB,CAAC;wBACkB;4CACjB,IAAI,IAAiC;wCACzC,IAAI,IAAyB;iCAK5B,OAAgC,CAAC,CAAC;EAG1D,KAAK,YAAY,OAAO,SAAS,cAAc;EAC/C,KAAK,IAAI;GACP,MAAM;GACN,WAAW;EACb,CAAC;EACD,KAAK,IAAI;GACP,MAAM,YAAY;GAClB,WAAW;EACb,CAAC;EACD,KAAK,IAAI;GACP,MAAM,YAAY;GAClB,WAAW;EACb,CAAC;EACD,KAAK,IAAI;GACP,MAAM,YAAY;GAClB,WAAW;GACX,aAAa;EACf,CAAC;EACD,KAAK,IAAI;GACP,MAAM,YAAY;GAClB,WAAW;EACb,CAAC;EACD,KAAK,IAAI;GACP,MAAM,YAAY;GAClB,WAAW;EACb,CAAC;EACD,KAAK,IAAI;GACP,MAAM,YAAY;GAClB,WAAW;EACb,CAAC;EAED,KAAK,0BAA0B,YAAY,UAAU,yBAAyB;CAChF;CAEA,MAAM,cAAc;EAClB,KAAK,UAAU,GAAG,aAAa,SAAuC;GACpE,KAAK,oBAAoB,KAAK,KAAK;GACnC,KAAK,QAAQ,KAAK,OAAO,KAAK,IAAI;EACpC,CAAC;EAED,KAAK,UAAU,GAAG,aAAa,UAAkB;GAC/C,KAAK,KAAK,KAAK;EACjB,CAAC;EAED,KAAK,UAAU,GAAG,eAAe,YAAmE;GAClG,KAAK,kBAAkB,QAAQ,OAAO,QAAQ,MAAM,QAAQ,cAAc;EAC5E,CAAC;;;;;EAMD,KAAK,UAAU,GAAG,gBAAgB,SAAkD;GAClF,MAAM,eAAe,EAAE,GAAG,KAAK,wBAAwB,EAAE;GACzD,KAAK,QAAQ,SAAS,aAAa;IACjC,aAAa,YAAY,KAAK;GAChC,CAAC;GACD,KAAK,wBAAwB,IAAI,YAAY;EAC/C,CAAC;CACH;;;;;;;CAQA,mBAAmB,gBAAqB;EACtC,KAAK,iBAAiB;EACtB,KAAK,yBAAyB;CAChC;;;;;;;;;CAUA,cAAsB,OAAe,SAAkB,OAAY,CAAC,GAAG;EACrE,MAAM,WAAW,KAAK,UAAU,MAAK,QAAO,IAAI,SAAS,KAAK;EAC9D,IAAI,CAAC,UAAU;EACf,KAAK,gBAAgB,iBAAiB,KAAK,WAAW,UAAU,SAAS,IAAI,CAAC;CAChF;;;;;CAMA,2BAA2B;EACzB,KAAK,gBAAgB,sBACnB,KAAK,UAAU,KAAI,QAAO,KAAK,WAAW,GAAG,CAAC,CAChD;CACF;CAEA,eAAe,OAAe,MAAc,MAAW;EACrD,MAAM,iBAAiB,WAAW,QAAQ,aAAa,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO;EACzF,MAAM,aAAa;GAAE,GAAI,QAAQ,CAAC;GAAI;EAAe;EACrD,KAAK,sBAAsB;GACzB;GACA;GACA,MAAM;GACN;EACF,CAAC;EACD,KAAK,UAAU,KAAK,mBAAmB;GACrC;GACA;GACA,MAAM;EACR,CAAC;CACH;CAEA,SAAS,OAAe,MAAY;EAClC,KAAK,UAAU,KAAK,YAAY;GAC9B;GACA;EACF,CAAC;CACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCA,IAAI,KAAuB;EACzB,MAAM,YAAY,KAAK,iBAAiB,GAAG;EAC3C,MAAM,QAAQ,KAAK,aAAa,KAAK,SAAS;EAC9C,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,4BAA4B;EAE9C,MAAM,iBAAiB,KAAK,sBAAsB,KAAK,SAAS;EAChE,MAAM,cAA2B;GAC/B,MAAM;GACN;GACA,SAAS,OAAgB,IAAI,WAAW,KAAK;GAC7C,MAAM,OAAY,IAAI,QAAQ,CAAC,CAAC;GAChC,aAAa,IAAI,eAAe;GAChC,cAAc,IAAI,eAAe,IAAI,aAAa,IAAI,CAAC;GACvD;EACF;EAEA,IAAI,KAAK,uBAAuB,WAAW,GAAG;GAC5C,KAAK,gBAAgB,KAAK;GAC1B,MAAM,gBAAgB,KAAK,UAAU,WAAU,aAAY,SAAS,SAAS,KAAK;GAClF,IAAI,iBAAiB,GAAG;IACtB,KAAK,UAAU,eAAe,cAAc,YAAY;IACxD,KAAK,UAAU,iBAAiB;GAClC,OACE,KAAK,UAAU,KAAK,WAAW;GAGjC,KAAK,yBAAyB;GAE9B,IAAI,YAAY,aACd,KAAK,QAAQ,OAAO,IAAI,IAAI;QAE5B,KAAK,cAAc,OAAO,YAAY,QAAQ,GAAG,YAAY,KAAK,CAAC;GAErE;EACF;EAEA,KAAK,aAAa,KAAK;EACvB,KAAK,IAAI,EAAE,SAAS;EACpB,KAAK,yBAAyB;EAG9B,IAAI,YAAY,eAAe,OAAO,IAAI,cAAc,YACtD,KAAK,QAAQ,OAAO,IAAI,IAAI;CAEhC;CAEA,0BAA0B,OAAe,SAA4B;EACnE,MAAM,WAAW,KAAK,mBAAmB,IAAI,KAAK,KAAK,CAAC;EACxD,KAAK,mBAAmB,IAAI,OAAO,SAAS,OAAO,OAAO,CAAC;CAC7D;;;;;;;;;;;;;;;CAgBA,kBAAiC;EAC/B,OAAO,OAAO,OAAO,KAAK,IAAI,CAAC,EAAE,QAAO,QAAO,IAAI,mBAAmB,IAAI;CAC5E;CAEA,aAA4B;EAC1B,OAAO,CAAC,GAAG,KAAK,SAAS;CAC3B;CAEA,qBAAoC;EAClC,OAAO,KAAK,UAAU,QAAO,QAAO,IAAI,mBAAmB,IAAI;CACjE;;;;;;;CAQA,yBAAyB,UAA2B;EAClD,OAAO,KAAK,wBAAwB,EAAE,cAAc;CACtD;CAEA,IAAI,IAAqC;EAEvC,MAAM,YAAY,KAAK,IAAI,EAAE;EAC7B,IAAI,WACF,OAAO;EAIT,OAAO,KAAK,UAAU,MAAK,QAAO,IAAI,SAAS,EAAE;CACnD;CAEA,OAAO,IAAqB;EAC1B,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE;CACtB;CAEA,SAAsC;EACpC,MAAM,UAAU,EAAE,GAAG,KAAK,IAAI,EAAE;EAGhC,KAAK,UAAU,SAAQ,QAAO;GAC5B,QAAQ,IAAI,QAAQ;EACtB,CAAC;EAED,OAAO;CACT;;;;;;;;;;;;;;;;;;;;;;CAuBA,QAAQ,IAAY,OAAO,CAAC,GAAG,eAAyB,CAAC,GAAG;EAC1D,IAAI,CAAC,KAAK,OAAO,EAAE,GACjB,MAAM,WAAW,EAAE;EAGrB,MAAM,cAAc,KAAK,IAAI,EAAE;EAK/B,IAFuB,KAAK,UAAU,MAAK,QAAO,IAAI,SAAS,EAE3D,GAEF,KAAK,2BAA2B,IAAI,MAAM,cAAc,WAAW;OAC9D;GACL,YAAY,KAAK,IAAI,IAAI;GACzB,YAAY,QAAQ,IAAI,IAAI;EAC9B;CACF;CAEA,aAAa,IAAqB;EAChC,MAAM,cAAc,KAAK,IAAI,EAAE;EAC/B,IAAI,CAAC,aAAa,OAAO;EACzB,OAAO,YAAY,QAAQ;CAC7B;;;;;;;;;CAUA,2BAAmC,IAAY,MAAW,cAAwB,aAA0B;EAE1G,IAAI,YAAY,cAAc;GAC5B,YAAY,aAAa,YAAY;GACrC,YAAY,eAAe,KAAA;EAC7B;EAGA,MAAM,OAAO,aAAa,SAAS,IAC/B,eACC,YAAY,gBAAgB,CAAC;EAElC,IAAI,KAAK,SAAS,GAAG;GAEnB,YAAY,eAAe,cACzB,KAAK,KAAI,eAAc,WAAW,UAAU,CAC9C,EAAE,WAAW,WAAW;IACtB,IAAI,OAAO,OAAM,UAAS,UAAU,KAAA,CAAS,GAAG;KAC9C,YAAY,KAAK,IAAI,IAAI;KACzB,YAAY,QAAQ,IAAI,IAAI;KAC5B,KAAK,cAAc,IAAI,MAAM,IAAI;IACnC;GACF,CAAC;GACD;EACF;EAGA,YAAY,KAAK,IAAI,IAAI;EACzB,YAAY,QAAQ,IAAI,IAAI;EAC5B,KAAK,cAAc,IAAI,MAAM,IAAI;CACnC;;;;;;;;;;;;;;CAeA,KAAK,IAAY;EACf,IAAI,CAAC,KAAK,OAAO,EAAE,GACjB,MAAM,WAAW,EAAE;EAGrB,MAAM,cAAc,KAAK,IAAI,EAAE;EAG/B,IAAI,YAAY,cAAc;GAC5B,YAAY,aAAa,YAAY;GACrC,YAAY,eAAe,KAAA;EAC7B;EAEA,YAAY,QAAQ,IAAI,KAAK;EAI7B,IADuB,KAAK,UAAU,MAAK,QAAO,IAAI,SAAS,EAC3D,GACF,KAAK,cAAc,IAAI,KAAK;CAEhC;CAEA,eAAuB,IAAY;EACjC,OAAO,KAAK,UAAU,MAAK,QAAO,IAAI,SAAS,EAAE;CACnD;CAEA,uBAA+B,KAAkB;EAC/C,OAAO,OAAO,IAAI,cAAc;CAClC;CAEA,gBAAwB,OAAe;EACrC,MAAM,UAAU,KAAK,IAAI;EACzB,IAAI,EAAE,SAAS,UAAU;EACzB,MAAM,OAAO,EAAE,GAAG,QAAQ;EAC1B,OAAO,KAAK;EACZ,KAAK,IAAI,IAAI,IAAI;CACnB;CAEA,aAAqB,OAAe;EAClC,MAAM,UAAU,KAAK,UAAU,QAAO,aAAY,SAAS,SAAS,KAAK;EACzE,QAAQ,SAAQ,QAAO,IAAI,cAAc,YAAY,CAAC;EACtD,IAAI,QAAQ,SAAS,GACnB,KAAK,YAAY,KAAK,UAAU,QAAO,aAAY,SAAS,SAAS,KAAK;CAE9E;CAEA,iBAAyB,KAAuB;EAC9C,OAAO,KAAK,aAAa;CAC3B;CAEA,aAAqB,KAAuB,WAAgB;EAC1D,OAAO,KAAK,QAAQ,KAAK,MAAM,WAAW,QAAQ,WAAW;CAC/D;CAEA,sBAA8B,KAAuB,WAAgB;EACnE,OAAO,CAAC,EAAE,KAAK,kBAAkB,KAAK,qBAAqB,WAAW,kBAAkB,WAAW;CACrG;CAEA,WAAmB,KAAkB,UAAU,IAAI,QAAQ,GAAG,OAAO,IAAI,KAAK,GAAa;EACzF,OAAO;GACL,MAAM,IAAI;GACV,WAAW,IAAI;GACf;GACA;GACA,gBAAgB,IAAI,kBAAkB;EACxC;CACF;CAEA,oBAA4B,OAAe;EACzC,KAAK,eAAe,OAAO,KAAK;CAClC;CAEA,cAAsB,OAAe,MAAW,SAAsB;EACpE,MAAM,WAAW,KAAK,mBAAmB,IAAI,KAAK;EAClD,IAAI,CAAC,YAAY,SAAS,WAAW,GAAG,OAAO;EAC/C,IAAI,OAAO;EACX,KAAK,MAAM,UAAU,SACnB,KAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,UAAU,QAAQ,MAAM,MAAM;GACpC,IAAI,YAAY,KAAA,KAAa,YAAY,QAAQ,YAAY,MAC3D,OAAO;EAEX;EAEF,OAAO;CACT;CAEA,sBAA8B,QAAmB;EAC/C,MAAM,cAAc,KAAK,IAAI,OAAO,KAAK;EACzC,IAAI,CAAC,aAAa;EAClB,MAAM,WAAW,KAAK,mBAAmB,IAAI,OAAO,KAAK;EACzD,IAAI,CAAC,YAAY,SAAS,WAAW,GAAG;EACxC,MAAM,cAAc,YAAY,KAAK;EACrC,MAAM,WAAW,KAAK,cAAc,OAAO,OAAO,aAAa,CAAC,MAAM,CAAC;EACvE,IAAI,aAAa,aAAa;EAC9B,YAAY,KAAK,IAAI,QAAQ;EAC7B,MAAM,UAAU,KAAK,eAAe,IAAI,OAAO,KAAK,KAAK,CAAC;EAC1D,QAAQ,KAAK,MAAM;EACnB,KAAK,eAAe,IAAI,OAAO,OAAO,OAAO;EAC7C,IAAI,KAAK,eAAe,OAAO,KAAK,GAClC,KAAK,cAAc,OAAO,OAAO,YAAY,QAAQ,GAAG,QAAQ;CAEpE;CAEA,kBAA0B,OAAe,MAAW,gBAAyB;EAC3E,MAAM,cAAc,KAAK,IAAI,KAAK;EAClC,IAAI,CAAC,aAAa;EAClB,IAAI,UAAU,KAAK,eAAe,IAAI,KAAK,KAAK,CAAC;EACjD,IAAI,gBACF,UAAU,QAAQ,QAAO,WAAU,OAAO,mBAAmB,cAAc;OAE3E,UAAU,CAAC;EAEb,IAAI,WAAW;EACf,IAAI,QAAQ,QACV,WAAW,KAAK,cAAc,OAAO,UAAU,OAAO;EAExD,YAAY,KAAK,IAAI,QAAQ;EAC7B,KAAK,eAAe,IAAI,OAAO,OAAO;EACtC,IAAI,KAAK,eAAe,KAAK,GAC3B,KAAK,cAAc,OAAO,YAAY,QAAQ,GAAG,QAAQ;CAE7D;AACF"}
|
|
1
|
+
{"version":3,"file":"Gui.js","names":[],"sources":["../../src/Gui/Gui.ts"],"sourcesContent":["import { Context, inject } from \"@signe/di\";\nimport { signal, Signal, WritableSignal } from \"canvasengine\";\nimport { AbstractWebsocket, WebSocketToken } from \"../services/AbstractSocket\";\nimport { DialogboxComponent, ShopComponent, SaveLoadComponent, MainMenuComponent, NotificationComponent, TitleScreenComponent, GameoverComponent } from \"../components/gui\";\nimport { combineLatest, Subscription } from \"rxjs\";\nimport { PrebuiltGui } from \"@rpgjs/common\";\n\ninterface GuiOptions {\n name?: string;\n id?: string;\n component?: any;\n display?: boolean;\n data?: any;\n /**\n * Auto display the GUI when added to the system\n * @default false\n */\n autoDisplay?: boolean;\n /**\n * Function that returns an array of Signal dependencies\n * The GUI will only display when all dependencies are resolved (!= undefined)\n * @returns Array of Signal dependencies\n */\n dependencies?: () => Signal[];\n /**\n * Attach the GUI to sprites instead of displaying globally\n * When true, the GUI will be rendered in character.ce for each sprite\n * @default false\n */\n attachToSprite?: boolean;\n /**\n * Vue v4 compatibility flag. Prefer attachToSprite in v5 projects.\n */\n rpgAttachToSprite?: boolean;\n}\n\nexport interface GuiInstance {\n name: string;\n component: any;\n display: WritableSignal<boolean>;\n data: WritableSignal<any>;\n openId?: string;\n autoDisplay: boolean;\n dependencies?: Signal[];\n subscription?: Subscription;\n attachToSprite?: boolean;\n}\n\ntype GuiState = {\n name: string;\n component: any;\n display: boolean;\n data: any;\n openId?: string;\n attachToSprite: boolean;\n};\n\ntype VueGuiBridge = {\n updateGuiState?: (state: GuiState) => void;\n initializeGuiStates?: (states: GuiState[]) => void;\n};\n\ninterface GuiAction {\n guiId: string;\n name: string;\n data: any;\n clientActionId: string;\n}\n\ntype OptimisticReducer = (data: any, action: GuiAction) => any;\n\nconst throwError = (id: string) => {\n throw `The GUI named ${id} is non-existent. Please add the component in the gui property of the decorator @RpgClient`;\n};\n\nconst updateItemQuantity = (items: any[], id: string) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.usable === false) return items;\n if (item?.consumable === false) return items;\n const quantity = typeof item?.quantity === \"number\" ? item.quantity : 1;\n const nextQuantity = Math.max(0, quantity - 1);\n if (nextQuantity === quantity) return items;\n if (nextQuantity <= 0) {\n return items.filter((_, idx) => idx !== index);\n }\n const nextItems = items.slice();\n nextItems[index] = { ...item, quantity: nextQuantity };\n return nextItems;\n};\n\nconst updateEquippedFlag = (items: any[], id: string, equip: boolean) => {\n const index = items.findIndex((item) => item?.id === id);\n if (index === -1) return items;\n const item = items[index];\n if (item?.equipped === equip) return items;\n const nextItems = items.slice();\n nextItems[index] = { ...item, equipped: equip };\n return nextItems;\n};\n\nconst mainMenuOptimisticReducer: OptimisticReducer = (data, action) => {\n if (!data || typeof data !== \"object\") return data;\n if (action.name === \"useItem\") {\n if (!Array.isArray(data.items)) return data;\n const id = action.data?.id;\n if (!id) return data;\n const nextItems = updateItemQuantity(data.items, id);\n if (nextItems === data.items) return data;\n return { ...data, items: nextItems };\n }\n if (action.name === \"equipItem\") {\n const id = action.data?.id;\n if (!id || typeof action.data?.equip !== \"boolean\") return data;\n const equip = action.data.equip;\n let nextItems = data.items;\n let nextEquips = data.equips;\n if (Array.isArray(data.items)) {\n nextItems = updateEquippedFlag(data.items, id, equip);\n }\n if (Array.isArray(data.equips)) {\n nextEquips = updateEquippedFlag(data.equips, id, equip);\n }\n if (nextItems === data.items && nextEquips === data.equips) return data;\n return {\n ...data,\n ...(nextItems !== data.items ? { items: nextItems } : {}),\n ...(nextEquips !== data.equips ? { equips: nextEquips } : {})\n };\n }\n return data;\n};\n\nexport class RpgGui {\n private webSocket: AbstractWebsocket;\n gui = signal<Record<string, GuiInstance>>({});\n extraGuis: GuiInstance[] = [];\n private vueGuiInstance: VueGuiBridge | null = null;\n private optimisticReducers = new Map<string, OptimisticReducer[]>();\n private pendingActions = new Map<string, GuiAction[]>();\n /**\n * Signal tracking which player IDs should display attached GUIs\n * Key: player ID, Value: boolean (true = show, false = hide)\n */\n attachedGuiDisplayState = signal<Record<string, boolean>>({});\n\n constructor(private context: Context) {\n this.webSocket = inject(context, WebSocketToken);\n this.add({\n name: \"rpg-dialog\",\n component: DialogboxComponent,\n });\n this.add({\n name: PrebuiltGui.MainMenu,\n component: MainMenuComponent,\n });\n this.add({\n name: PrebuiltGui.Shop,\n component: ShopComponent,\n });\n this.add({\n name: PrebuiltGui.Notification,\n component: NotificationComponent,\n autoDisplay: true,\n });\n this.add({\n name: PrebuiltGui.Save,\n component: SaveLoadComponent,\n });\n this.add({\n name: PrebuiltGui.TitleScreen,\n component: TitleScreenComponent,\n });\n this.add({\n name: PrebuiltGui.Gameover,\n component: GameoverComponent,\n });\n\n this.registerOptimisticReducer(PrebuiltGui.MainMenu, mainMenuOptimisticReducer);\n }\n\n async _initialize() {\n this.webSocket.on(\"gui.open\", (data: { guiId: string; data: any; guiOpenId?: string }) => {\n this.clearPendingActions(data.guiId);\n this.display(data.guiId, data.data, [], data.guiOpenId);\n });\n\n this.webSocket.on(\"gui.exit\", (payload: string | { guiId: string; guiOpenId?: string }) => {\n const guiId = typeof payload === \"string\" ? payload : payload.guiId;\n const guiOpenId = typeof payload === \"string\" ? undefined : payload.guiOpenId;\n const current = this.get(guiId);\n if (guiOpenId && current?.openId && current.openId !== guiOpenId) {\n return;\n }\n this.hide(guiId);\n });\n\n this.webSocket.on(\"gui.update\", (payload: { guiId: string; data: any; clientActionId?: string }) => {\n this.applyServerUpdate(payload.guiId, payload.data, payload.clientActionId);\n });\n\n /**\n * Listen for tooltip display state changes from server\n * This is triggered by showAttachedGui/hideAttachedGui on the server\n */\n this.webSocket.on(\"gui.tooltip\", (data: { players: string[]; display: boolean }) => {\n const currentState = { ...this.attachedGuiDisplayState() };\n data.players.forEach((playerId) => {\n currentState[playerId] = data.display;\n });\n this.attachedGuiDisplayState.set(currentState);\n });\n }\n\n /**\n * Set the VueGui instance reference for Vue component management\n * This is called by VueGui when it's initialized\n * \n * @param vueGuiInstance - The VueGui instance\n */\n _setVueGuiInstance(vueGuiInstance: any) {\n this.vueGuiInstance = vueGuiInstance;\n this._initializeVueComponents();\n }\n\n /**\n * Notify VueGui about GUI state changes\n * This synchronizes the Vue component display state\n * \n * @param guiId - The GUI component ID\n * @param display - Display state\n * @param data - Component data\n */\n private _notifyVueGui(guiId: string, display: boolean, data: any = {}) {\n const extraGui = this.extraGuis.find(gui => gui.name === guiId);\n if (!extraGui) return;\n this.vueGuiInstance?.updateGuiState?.(this.toGuiState(extraGui, display, data));\n }\n\n /**\n * Initialize Vue components in the VueGui instance\n * This should be called after VueGui is mounted\n */\n _initializeVueComponents() {\n this.vueGuiInstance?.initializeGuiStates?.(\n this.extraGuis.map(gui => this.toGuiState(gui))\n );\n }\n\n guiInteraction(guiId: string, name: string, data: any) {\n const clientActionId = globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random()}`;\n const actionData = { ...(data || {}), clientActionId };\n this.applyOptimisticAction({\n guiId,\n name,\n data: actionData,\n clientActionId\n });\n this.webSocket.emit(\"gui.interaction\", {\n guiId,\n name,\n data: actionData,\n });\n }\n\n guiClose(guiId: string, data?: any, guiOpenId?: unknown) {\n const normalizedOpenId =\n typeof guiOpenId === \"string\" && guiOpenId.length > 0 ? guiOpenId : undefined;\n this.webSocket.emit(\"gui.exit\", {\n guiId,\n guiOpenId: normalizedOpenId,\n data,\n });\n }\n\n /**\n * Add a GUI component to the system\n * \n * By default, only CanvasEngine components (.ce files) are accepted.\n * Vue components should be handled by the @rpgjs/vue package.\n * \n * @param gui - GUI configuration options\n * @param gui.name - Name or ID of the GUI component\n * @param gui.id - Alternative ID if name is not provided\n * @param gui.component - The component to render (must be a CanvasEngine component)\n * @param gui.display - Initial display state (default: false)\n * @param gui.data - Initial data for the component\n * @param gui.autoDisplay - Auto display when added (default: false)\n * @param gui.dependencies - Function returning Signal dependencies\n * @param gui.attachToSprite - Attach GUI to sprites instead of global display (default: false)\n * \n * @example\n * ```ts\n * gui.add({\n * name: 'inventory',\n * component: InventoryComponent, // Must be a .ce component\n * autoDisplay: true,\n * dependencies: () => [playerSignal, inventorySignal]\n * });\n * \n * // Attach to sprites\n * gui.add({\n * name: 'tooltip',\n * component: TooltipComponent,\n * attachToSprite: true\n * });\n * ```\n */\n add(gui: GuiOptions | any) {\n const component = this.resolveComponent(gui);\n const guiId = this.resolveGuiId(gui, component);\n if (!guiId) {\n throw new Error(\"GUI must have a name or id\");\n }\n const attachToSprite = this.resolveAttachToSprite(gui, component);\n const guiInstance: GuiInstance = {\n name: guiId,\n component,\n display: signal<boolean>(gui.display || false),\n data: signal<any>(gui.data || {}),\n openId: undefined,\n autoDisplay: gui.autoDisplay || false,\n dependencies: gui.dependencies ? gui.dependencies() : [],\n attachToSprite,\n };\n\n if (this.isVueComponentInstance(guiInstance)) {\n this.removeCanvasGui(guiId);\n const existingIndex = this.extraGuis.findIndex(existing => existing.name === guiId);\n if (existingIndex >= 0) {\n this.extraGuis[existingIndex].subscription?.unsubscribe();\n this.extraGuis[existingIndex] = guiInstance;\n } else {\n this.extraGuis.push(guiInstance);\n }\n\n this._initializeVueComponents();\n \n if (guiInstance.autoDisplay) {\n this.display(guiId, gui.data);\n } else {\n this._notifyVueGui(guiId, guiInstance.display(), guiInstance.data());\n }\n return;\n }\n\n this.removeVueGui(guiId);\n this.gui()[guiId] = guiInstance;\n this._initializeVueComponents();\n\n // Auto display if enabled and it's a CanvasEngine component\n if (guiInstance.autoDisplay && typeof gui.component === 'function') {\n this.display(guiId, gui.data);\n }\n }\n\n registerOptimisticReducer(guiId: string, reducer: OptimisticReducer) {\n const existing = this.optimisticReducers.get(guiId) || [];\n this.optimisticReducers.set(guiId, existing.concat(reducer));\n }\n\n /**\n * Get all attached GUI components (attachToSprite: true)\n * \n * Returns all GUI instances that are configured to be attached to sprites.\n * These GUIs should be rendered in character.ce instead of canvas.ce.\n * \n * @returns Array of GUI instances with attachToSprite: true\n * \n * @example\n * ```ts\n * const attachedGuis = gui.getAttachedGuis();\n * // Use in character.ce to render tooltips\n * ```\n */\n getAttachedGuis(): GuiInstance[] {\n return Object.values(this.gui()).filter(gui => gui.attachToSprite === true);\n }\n\n getVueGuis(): GuiInstance[] {\n return [...this.extraGuis];\n }\n\n getAttachedVueGuis(): GuiInstance[] {\n return this.extraGuis.filter(gui => gui.attachToSprite === true);\n }\n\n /**\n * Check if a player should display attached GUIs\n * \n * @param playerId - The player ID to check\n * @returns true if attached GUIs should be displayed for this player\n */\n shouldDisplayAttachedGui(playerId: string): boolean {\n return this.attachedGuiDisplayState()[playerId] === true;\n }\n\n get(id: string): GuiInstance | undefined {\n // Check CanvasEngine GUIs first\n const canvasGui = this.gui()[id];\n if (canvasGui) {\n return canvasGui;\n }\n \n // Check Vue GUIs in extraGuis\n return this.extraGuis.find(gui => gui.name === id);\n }\n\n exists(id: string): boolean {\n return !!this.get(id);\n }\n\n getAll(): Record<string, GuiInstance> {\n const allGuis = { ...this.gui() };\n \n // Add extraGuis to the result\n this.extraGuis.forEach(gui => {\n allGuis[gui.name] = gui;\n });\n \n return allGuis;\n }\n\n /**\n * Display a GUI component\n * \n * Displays the GUI immediately if no dependencies are configured,\n * or waits for all dependencies to be resolved if dependencies are present.\n * Automatically manages subscriptions to prevent memory leaks.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * @param data - Data to pass to the component\n * @param dependencies - Optional runtime dependencies (overrides config dependencies)\n * \n * @example\n * ```ts\n * // Display immediately\n * gui.display('inventory', { items: [] });\n * \n * // Display with runtime dependencies\n * gui.display('shop', { shopId: 1 }, [playerSignal, shopSignal]);\n * ```\n */\n display(id: string, data = {}, dependencies: Signal[] = [], openId?: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Check if it's a Vue component (in extraGuis)\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n \n if (isVueComponent) {\n // Handle Vue component display\n this._handleVueComponentDisplay(id, data, dependencies, guiInstance, openId);\n } else {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n }\n }\n\n isDisplaying(id: string): boolean {\n const guiInstance = this.get(id);\n if (!guiInstance) return false;\n return guiInstance.display();\n }\n\n /**\n * Handle Vue component display logic\n * \n * @param id - GUI component ID\n * @param data - Component data\n * @param dependencies - Runtime dependencies\n * @param guiInstance - GUI instance\n */\n private _handleVueComponentDisplay(id: string, data: any, dependencies: Signal[], guiInstance: GuiInstance, openId?: string) {\n // Unsubscribe from previous subscription if exists\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n // Use runtime dependencies or config dependencies\n const deps = dependencies.length > 0 \n ? dependencies \n : (guiInstance.dependencies ?? []);\n\n if (deps.length > 0) {\n // Subscribe to dependencies\n guiInstance.subscription = combineLatest(\n deps.map(dependency => dependency.observable)\n ).subscribe((values) => {\n if (values.every(value => value !== undefined)) {\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n });\n return;\n }\n\n // No dependencies, display immediately\n guiInstance.openId = openId;\n guiInstance.data.set(data);\n guiInstance.display.set(true);\n this._notifyVueGui(id, true, data);\n }\n\n /**\n * Hide a GUI component\n * \n * Hides the GUI and cleans up any active subscriptions.\n * Works with both CanvasEngine components and Vue components.\n * \n * @param id - The GUI component ID\n * \n * @example\n * ```ts\n * gui.hide('inventory');\n * ```\n */\n hide(id: string) {\n if (!this.exists(id)) {\n throw throwError(id);\n }\n\n const guiInstance = this.get(id)!;\n \n // Unsubscribe if there's an active subscription\n if (guiInstance.subscription) {\n guiInstance.subscription.unsubscribe();\n guiInstance.subscription = undefined;\n }\n\n guiInstance.display.set(false)\n guiInstance.openId = undefined;\n \n // Check if it's a Vue component and notify VueGui\n const isVueComponent = this.extraGuis.some(gui => gui.name === id);\n if (isVueComponent) {\n this._notifyVueGui(id, false);\n }\n }\n\n private isVueComponent(id: string) {\n return this.extraGuis.some(gui => gui.name === id);\n }\n\n private isVueComponentInstance(gui: GuiInstance) {\n return typeof gui.component !== \"function\";\n }\n\n private removeCanvasGui(guiId: string) {\n const current = this.gui();\n if (!(guiId in current)) return;\n const next = { ...current };\n delete next[guiId];\n this.gui.set(next);\n }\n\n private removeVueGui(guiId: string) {\n const removed = this.extraGuis.filter(existing => existing.name === guiId);\n removed.forEach(gui => gui.subscription?.unsubscribe());\n if (removed.length > 0) {\n this.extraGuis = this.extraGuis.filter(existing => existing.name !== guiId);\n }\n }\n\n private resolveComponent(gui: GuiOptions | any) {\n return gui?.component ?? gui;\n }\n\n private resolveGuiId(gui: GuiOptions | any, component: any) {\n return gui?.name || gui?.id || component?.name || component?.__name;\n }\n\n private resolveAttachToSprite(gui: GuiOptions | any, component: any) {\n return !!(gui?.attachToSprite || gui?.rpgAttachToSprite || component?.attachToSprite || component?.rpgAttachToSprite);\n }\n\n private toGuiState(gui: GuiInstance, display = gui.display(), data = gui.data()): GuiState {\n return {\n name: gui.name,\n component: gui.component,\n display,\n data,\n openId: gui.openId,\n attachToSprite: gui.attachToSprite || false,\n };\n }\n\n private clearPendingActions(guiId: string) {\n this.pendingActions.delete(guiId);\n }\n\n private applyReducers(guiId: string, data: any, actions: GuiAction[]) {\n const reducers = this.optimisticReducers.get(guiId);\n if (!reducers || reducers.length === 0) return data;\n let next = data;\n for (const action of actions) {\n for (const reducer of reducers) {\n const updated = reducer(next, action);\n if (updated !== undefined && updated !== null && updated !== next) {\n next = updated;\n }\n }\n }\n return next;\n }\n\n private applyOptimisticAction(action: GuiAction) {\n const guiInstance = this.get(action.guiId);\n if (!guiInstance) return;\n const reducers = this.optimisticReducers.get(action.guiId);\n if (!reducers || reducers.length === 0) return;\n const currentData = guiInstance.data();\n const nextData = this.applyReducers(action.guiId, currentData, [action]);\n if (nextData === currentData) return;\n guiInstance.data.set(nextData);\n const pending = this.pendingActions.get(action.guiId) || [];\n pending.push(action);\n this.pendingActions.set(action.guiId, pending);\n if (this.isVueComponent(action.guiId)) {\n this._notifyVueGui(action.guiId, guiInstance.display(), nextData);\n }\n }\n\n private applyServerUpdate(guiId: string, data: any, clientActionId?: string) {\n const guiInstance = this.get(guiId);\n if (!guiInstance) return;\n let pending = this.pendingActions.get(guiId) || [];\n if (clientActionId) {\n pending = pending.filter(action => action.clientActionId !== clientActionId);\n } else {\n pending = [];\n }\n let nextData = data;\n if (pending.length) {\n nextData = this.applyReducers(guiId, nextData, pending);\n }\n guiInstance.data.set(nextData);\n this.pendingActions.set(guiId, pending);\n if (this.isVueComponent(guiId)) {\n this._notifyVueGui(guiId, guiInstance.display(), nextData);\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/RpgClient.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ 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 {
|
|
5
|
+
import { RpgClientEvent } from './Game/Event';
|
|
6
|
+
import { I18nMessages, MapPhysicsEntityContext, MapPhysicsInitContext, RpgActionName } from '@rpgjs/common';
|
|
6
7
|
import { ClientProjectileSpawn, RenderedProjectileProps } from './Game/ProjectileManager';
|
|
8
|
+
import { ClientVisualMap } from './Game/ClientVisuals';
|
|
9
|
+
import { RpgInteractionBehavior, RpgInteractionMatcher } from './services/interactions';
|
|
7
10
|
type RpgComponent = RpgClientObject;
|
|
8
11
|
type SceneMap = Container;
|
|
9
12
|
export type SpriteComponentConfig = ComponentFunction | {
|
|
@@ -12,6 +15,14 @@ export type SpriteComponentConfig = ComponentFunction | {
|
|
|
12
15
|
data?: Record<string, any> | ((object: RpgClientObject) => Record<string, any>);
|
|
13
16
|
dependencies?: (object: RpgClientObject) => any[];
|
|
14
17
|
};
|
|
18
|
+
export type EventComponentSprite = RpgClientEvent & Record<string, any>;
|
|
19
|
+
export type EventComponentConfig = ComponentFunction | {
|
|
20
|
+
component: ComponentFunction;
|
|
21
|
+
props?: Record<string, any> | ((event: EventComponentSprite) => Record<string, any>);
|
|
22
|
+
data?: Record<string, any> | ((event: EventComponentSprite) => Record<string, any>);
|
|
23
|
+
dependencies?: (event: EventComponentSprite) => any[];
|
|
24
|
+
renderGraphic?: boolean;
|
|
25
|
+
};
|
|
15
26
|
export interface RpgSpriteBeforeRemoveContext {
|
|
16
27
|
reason?: string;
|
|
17
28
|
data?: any;
|
|
@@ -131,6 +142,30 @@ export interface RpgSpriteHooks {
|
|
|
131
142
|
* ```
|
|
132
143
|
*/
|
|
133
144
|
components?: Record<string, ComponentFunction>;
|
|
145
|
+
/**
|
|
146
|
+
* Resolve a custom CanvasEngine component for a specific event.
|
|
147
|
+
*
|
|
148
|
+
* The component always receives the synced event object as the `sprite` prop.
|
|
149
|
+
* Custom props are merged in addition to `sprite`, but cannot replace it.
|
|
150
|
+
* Return `null` or `undefined` to keep the default graphic renderer.
|
|
151
|
+
*
|
|
152
|
+
* @prop { (event: EventComponentSprite) => EventComponentConfig | null | undefined } [eventComponent]
|
|
153
|
+
* @memberof RpgSpriteHooks
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* import ChestEvent from './components/chest-event.ce'
|
|
157
|
+
*
|
|
158
|
+
* const sprite: RpgSpriteHooks = {
|
|
159
|
+
* eventComponent(sprite) {
|
|
160
|
+
* if (sprite.name === 'CHEST') {
|
|
161
|
+
* return ChestEvent
|
|
162
|
+
* }
|
|
163
|
+
* return null
|
|
164
|
+
* }
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
eventComponent?: (event: EventComponentSprite) => EventComponentConfig | null | undefined;
|
|
134
169
|
/**
|
|
135
170
|
* As soon as the sprite is initialized
|
|
136
171
|
*
|
|
@@ -292,6 +327,13 @@ export interface RpgProjectileHooks {
|
|
|
292
327
|
onDestroy?: (projectile: RenderedProjectileProps | null) => any;
|
|
293
328
|
}
|
|
294
329
|
export interface RpgClient {
|
|
330
|
+
/**
|
|
331
|
+
* Default translations owned by this client module.
|
|
332
|
+
*
|
|
333
|
+
* Game-level translations provided with `provideI18n()` override module
|
|
334
|
+
* translations when they share the same locale and key.
|
|
335
|
+
*/
|
|
336
|
+
i18n?: I18nMessages;
|
|
295
337
|
/**
|
|
296
338
|
* Add hooks to the player or engine. All modules can listen to the hook
|
|
297
339
|
*
|
|
@@ -675,6 +717,36 @@ export interface RpgClient {
|
|
|
675
717
|
id: string;
|
|
676
718
|
component: ComponentFunction;
|
|
677
719
|
}[];
|
|
720
|
+
/**
|
|
721
|
+
* Named client-side visual macros.
|
|
722
|
+
*
|
|
723
|
+
* Use client visuals when the server needs to trigger a group of existing
|
|
724
|
+
* client visual primitives at once, such as a flash, damage text, sound,
|
|
725
|
+
* component animation, and camera shake. The server sends only the visual
|
|
726
|
+
* name and a serializable payload; the rendering details live on the client.
|
|
727
|
+
*
|
|
728
|
+
* For a single sound, flash, or component animation, prefer the direct
|
|
729
|
+
* server APIs (`playSound`, `flash`, `showComponentAnimation`). Client
|
|
730
|
+
* visuals are meant to group several visual operations and reduce bandwidth.
|
|
731
|
+
*
|
|
732
|
+
* ```ts
|
|
733
|
+
* import { defineModule, RpgClient } from '@rpgjs/client'
|
|
734
|
+
*
|
|
735
|
+
* export default defineModule<RpgClient>({
|
|
736
|
+
* clientVisuals: {
|
|
737
|
+
* hit({ target, data }, helpers) {
|
|
738
|
+
* helpers.flash(target, { type: 'tint', tint: 'red' })
|
|
739
|
+
* helpers.showHit(target, `-${data.damage}`)
|
|
740
|
+
* helpers.sound('hit')
|
|
741
|
+
* }
|
|
742
|
+
* }
|
|
743
|
+
* })
|
|
744
|
+
* ```
|
|
745
|
+
*
|
|
746
|
+
* @prop {Record<string, ClientVisualHandler>} [clientVisuals]
|
|
747
|
+
* @memberof RpgClient
|
|
748
|
+
*/
|
|
749
|
+
clientVisuals?: ClientVisualMap;
|
|
678
750
|
/**
|
|
679
751
|
* Client-side projectile rendering configuration.
|
|
680
752
|
*
|
|
@@ -682,5 +754,17 @@ export interface RpgClient {
|
|
|
682
754
|
* compact spawn/impact/destroy events and the client predicts x/y locally.
|
|
683
755
|
*/
|
|
684
756
|
projectiles?: RpgProjectileHooks;
|
|
757
|
+
/**
|
|
758
|
+
* Client-only pointer interactions attached to sprites.
|
|
759
|
+
*
|
|
760
|
+
* Use this for hover popovers, selection, drag previews, cursor changes, and
|
|
761
|
+
* explicit mouse-driven gameplay actions. Pointer feedback stays local unless
|
|
762
|
+
* the behavior calls `ctx.action(...)`.
|
|
763
|
+
*/
|
|
764
|
+
interactions?: ((engine: RpgClientEngine) => void) | {
|
|
765
|
+
setup?: (engine: RpgClientEngine) => void;
|
|
766
|
+
load?: (engine: RpgClientEngine) => void;
|
|
767
|
+
use?: Array<[RpgInteractionMatcher, RpgInteractionBehavior | ComponentFunction]>;
|
|
768
|
+
};
|
|
685
769
|
}
|
|
686
770
|
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Trigger } from 'canvasengine';
|
|
2
2
|
import { AbstractWebsocket } from './services/AbstractSocket';
|
|
3
|
-
import {
|
|
3
|
+
import { I18nParams, RpgActionInput, RpgActionName, RpgDashInput, RpgMovementInput } from '@rpgjs/common';
|
|
4
|
+
import { EventComponentConfig } from './RpgClient';
|
|
5
|
+
import { RpgClientEvent } from './Game/Event';
|
|
4
6
|
import { RpgClientMap } from './Game/Map';
|
|
5
7
|
import { AnimationManager } from './Game/AnimationManager';
|
|
6
8
|
import { Observable } from 'rxjs';
|
|
7
9
|
import { ProjectileManager } from './Game/ProjectileManager';
|
|
10
|
+
import { ClientVisualRegistry, ClientVisualHandler, ClientVisualMap, ClientVisualPacket } from './Game/ClientVisuals';
|
|
8
11
|
import { ClientPointerContext } from './services/pointerContext';
|
|
12
|
+
import { RpgClientInteractions } from './services/interactions';
|
|
13
|
+
import { EventComponentResolver } from './Game/EventComponentResolver';
|
|
9
14
|
import * as PIXI from "pixi.js";
|
|
10
15
|
type ConfigurableTrigger<T> = Omit<Trigger<T>, "start"> & {
|
|
11
16
|
start(config?: T): Promise<void>;
|
|
@@ -31,10 +36,13 @@ export declare class RpgClientEngine<T = any> {
|
|
|
31
36
|
width: import('canvasengine').WritableSignal<string>;
|
|
32
37
|
height: import('canvasengine').WritableSignal<string>;
|
|
33
38
|
spritesheets: Map<string | number, any>;
|
|
39
|
+
private spritesheetPromises;
|
|
34
40
|
sounds: Map<string, any>;
|
|
35
41
|
componentAnimations: any[];
|
|
42
|
+
clientVisuals: ClientVisualRegistry;
|
|
36
43
|
projectiles: ProjectileManager;
|
|
37
44
|
pointer: ClientPointerContext;
|
|
45
|
+
interactions: RpgClientInteractions;
|
|
38
46
|
private spritesheetResolver?;
|
|
39
47
|
private soundResolver?;
|
|
40
48
|
particleSettings: {
|
|
@@ -48,6 +56,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
48
56
|
spriteComponentsBehind: import('canvasengine').WritableArraySignal<any[]>;
|
|
49
57
|
spriteComponentsInFront: import('canvasengine').WritableArraySignal<any[]>;
|
|
50
58
|
spriteComponents: Map<string, any>;
|
|
59
|
+
private eventComponentResolvers;
|
|
51
60
|
/** ID of the sprite that the camera should follow. null means follow the current player */
|
|
52
61
|
cameraFollowTargetId: import('canvasengine').WritableSignal<string | null>;
|
|
53
62
|
/** Trigger for map shake animation */
|
|
@@ -63,6 +72,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
63
72
|
private frameOffset;
|
|
64
73
|
private latestServerTick?;
|
|
65
74
|
private latestServerTickAt;
|
|
75
|
+
private dashLockedUntil;
|
|
66
76
|
private rtt;
|
|
67
77
|
private pingInterval;
|
|
68
78
|
private readonly PING_INTERVAL_MS;
|
|
@@ -77,13 +87,27 @@ export declare class RpgClientEngine<T = any> {
|
|
|
77
87
|
private eventsReceived$;
|
|
78
88
|
private onAfterLoadingSubscription?;
|
|
79
89
|
private sceneResetQueued;
|
|
90
|
+
private mapTransitionInProgress;
|
|
91
|
+
private currentMapRoomId?;
|
|
92
|
+
private socketListenersInitialized;
|
|
80
93
|
private tickSubscriptions;
|
|
81
94
|
private resizeHandler?;
|
|
82
95
|
private pointerMoveHandler?;
|
|
96
|
+
private pointerUpHandler?;
|
|
97
|
+
private pointerCancelHandler?;
|
|
83
98
|
private pointerCanvas?;
|
|
84
99
|
private pendingSyncPackets;
|
|
85
100
|
private notificationManager;
|
|
101
|
+
private i18nService;
|
|
102
|
+
private locale?;
|
|
86
103
|
constructor(context: any);
|
|
104
|
+
setLocale(locale: string): void;
|
|
105
|
+
getLocale(): string;
|
|
106
|
+
t(key: string, params?: I18nParams): string;
|
|
107
|
+
i18n(): {
|
|
108
|
+
locale: string;
|
|
109
|
+
t: (key: string, params?: I18nParams) => string;
|
|
110
|
+
};
|
|
87
111
|
/**
|
|
88
112
|
* Assigns a CanvasEngine KeyboardControls instance to the dependency injection context
|
|
89
113
|
*
|
|
@@ -121,10 +145,14 @@ export declare class RpgClientEngine<T = any> {
|
|
|
121
145
|
start(): Promise<void>;
|
|
122
146
|
private resolveSceneMapComponent;
|
|
123
147
|
private setupPointerTracking;
|
|
148
|
+
updatePointerFromInteractionEvent(event: any): void;
|
|
124
149
|
private findViewportInstance;
|
|
125
150
|
private prepareSyncPayload;
|
|
126
151
|
private normalizeAckWithSyncState;
|
|
127
152
|
private initListeners;
|
|
153
|
+
private beginMapTransfer;
|
|
154
|
+
private clearComponentAnimations;
|
|
155
|
+
private shouldProcessProjectilePacket;
|
|
128
156
|
private callConnectError;
|
|
129
157
|
private flushPendingSyncPackets;
|
|
130
158
|
private applySyncPacket;
|
|
@@ -502,8 +530,52 @@ export declare class RpgClientEngine<T = any> {
|
|
|
502
530
|
* @returns The CanvasEngine component, or undefined when missing
|
|
503
531
|
*/
|
|
504
532
|
getSpriteComponent(id: string): any;
|
|
533
|
+
/**
|
|
534
|
+
* Register a custom event component resolver.
|
|
535
|
+
*
|
|
536
|
+
* The last resolver returning a component wins. This lets later modules
|
|
537
|
+
* override earlier defaults without replacing the whole map scene.
|
|
538
|
+
*
|
|
539
|
+
* @param resolver - Function receiving the synced event object
|
|
540
|
+
* @returns The registered resolver
|
|
541
|
+
*/
|
|
542
|
+
addEventComponentResolver(resolver: EventComponentResolver): EventComponentResolver;
|
|
543
|
+
/**
|
|
544
|
+
* Resolve the custom CanvasEngine component for an event, if any.
|
|
545
|
+
*
|
|
546
|
+
* @param event - Synced client event object
|
|
547
|
+
* @returns The component/config returned by the last matching resolver
|
|
548
|
+
*/
|
|
549
|
+
resolveEventComponent(event: RpgClientEvent): EventComponentConfig | null;
|
|
505
550
|
registerProjectileComponent(type: string, component: any): any;
|
|
506
551
|
getProjectileComponent(type: string): any;
|
|
552
|
+
/**
|
|
553
|
+
* Register a named client visual macro.
|
|
554
|
+
*
|
|
555
|
+
* Client visuals are small client-side functions that group existing visual
|
|
556
|
+
* primitives such as flash, sound, component animations, sprite animation, or
|
|
557
|
+
* map shake. The server sends only the visual name and a serializable payload.
|
|
558
|
+
*
|
|
559
|
+
* @param name - Stable visual name sent by the server
|
|
560
|
+
* @param handler - Client-side visual handler
|
|
561
|
+
* @returns The registered handler
|
|
562
|
+
*/
|
|
563
|
+
registerClientVisual(name: string, handler: ClientVisualHandler): ClientVisualHandler;
|
|
564
|
+
/**
|
|
565
|
+
* Register several named client visual macros.
|
|
566
|
+
*
|
|
567
|
+
* @param visuals - Map of visual names to client-side handlers
|
|
568
|
+
*/
|
|
569
|
+
registerClientVisuals(visuals: ClientVisualMap): void;
|
|
570
|
+
/**
|
|
571
|
+
* Play a registered client visual locally.
|
|
572
|
+
*
|
|
573
|
+
* This is also used by the websocket listener when the server calls
|
|
574
|
+
* `player.clientVisual()` or `map.clientVisual()`.
|
|
575
|
+
*
|
|
576
|
+
* @param packet - Visual name and serializable payload
|
|
577
|
+
*/
|
|
578
|
+
playClientVisual(packet: ClientVisualPacket): Promise<void>;
|
|
507
579
|
/**
|
|
508
580
|
* Add a component animation to the engine
|
|
509
581
|
*
|
|
@@ -580,14 +652,16 @@ export declare class RpgClientEngine<T = any> {
|
|
|
580
652
|
*/
|
|
581
653
|
startTransition(id: string, props?: any): Promise<void>;
|
|
582
654
|
processInput({ input }: {
|
|
583
|
-
input:
|
|
655
|
+
input: RpgMovementInput;
|
|
584
656
|
}): Promise<void>;
|
|
657
|
+
processDash(input?: Partial<RpgDashInput>): Promise<void>;
|
|
585
658
|
processAction(action: RpgActionName, data?: any): void;
|
|
586
659
|
processAction(action: RpgActionInput): void;
|
|
587
660
|
get PIXI(): typeof PIXI;
|
|
588
661
|
get socket(): AbstractWebsocket;
|
|
589
662
|
get playerId(): string | null;
|
|
590
663
|
get scene(): RpgClientMap;
|
|
664
|
+
getObjectById(id: string): any;
|
|
591
665
|
private getPhysicsTick;
|
|
592
666
|
private getPhysicsTickDurationMs;
|
|
593
667
|
private updateServerTickEstimate;
|
|
@@ -599,6 +673,7 @@ export declare class RpgClientEngine<T = any> {
|
|
|
599
673
|
private buildPendingMoveTrajectory;
|
|
600
674
|
private emitMovePacket;
|
|
601
675
|
private flushPendingMovePath;
|
|
676
|
+
private applyPredictedMovementInput;
|
|
602
677
|
private getLocalPlayerState;
|
|
603
678
|
private applyAuthoritativeState;
|
|
604
679
|
private initializePredictionController;
|