@rpgjs/client 5.0.0-alpha.13 → 5.0.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/Game/Map.d.ts +2 -1
  2. package/dist/RpgClient.d.ts +39 -0
  3. package/dist/RpgClientEngine.d.ts +138 -2
  4. package/dist/index10.js +2 -3
  5. package/dist/index10.js.map +1 -1
  6. package/dist/index15.js +59 -17
  7. package/dist/index15.js.map +1 -1
  8. package/dist/index16.js.map +1 -1
  9. package/dist/index19.js.map +1 -1
  10. package/dist/index2.js +303 -3
  11. package/dist/index2.js.map +1 -1
  12. package/dist/index20.js +3 -0
  13. package/dist/index20.js.map +1 -1
  14. package/dist/index22.js +31 -31
  15. package/dist/index22.js.map +1 -1
  16. package/dist/index23.js +2 -2
  17. package/dist/index23.js.map +1 -1
  18. package/dist/index25.js +1 -2
  19. package/dist/index25.js.map +1 -1
  20. package/dist/index30.js.map +1 -1
  21. package/dist/index31.js.map +1 -1
  22. package/dist/index33.js +1 -1
  23. package/dist/index34.js +1 -1
  24. package/dist/index36.js +6 -4402
  25. package/dist/index36.js.map +1 -1
  26. package/dist/index37.js +3686 -170
  27. package/dist/index37.js.map +1 -1
  28. package/dist/index38.js +172 -486
  29. package/dist/index38.js.map +1 -1
  30. package/dist/index39.js +498 -71
  31. package/dist/index39.js.map +1 -1
  32. package/dist/index4.js +6 -1
  33. package/dist/index4.js.map +1 -1
  34. package/dist/index40.js +67 -10
  35. package/dist/index40.js.map +1 -1
  36. package/dist/index41.js +3 -93
  37. package/dist/index41.js.map +1 -1
  38. package/dist/index42.js +123 -0
  39. package/dist/index42.js.map +1 -0
  40. package/dist/index43.js +20 -0
  41. package/dist/index43.js.map +1 -0
  42. package/dist/index44.js +12 -0
  43. package/dist/index44.js.map +1 -0
  44. package/dist/index45.js +113 -0
  45. package/dist/index45.js.map +1 -0
  46. package/dist/index46.js +136 -0
  47. package/dist/index46.js.map +1 -0
  48. package/dist/index47.js +137 -0
  49. package/dist/index47.js.map +1 -0
  50. package/dist/index48.js +112 -0
  51. package/dist/index48.js.map +1 -0
  52. package/dist/index49.js +9 -0
  53. package/dist/index49.js.map +1 -0
  54. package/dist/index8.js +8 -0
  55. package/dist/index8.js.map +1 -1
  56. package/dist/services/mmorpg.d.ts +1 -0
  57. package/package.json +8 -8
  58. package/src/Game/Map.ts +5 -1
  59. package/src/Game/Object.ts +37 -4
  60. package/src/RpgClient.ts +40 -0
  61. package/src/RpgClientEngine.ts +374 -11
  62. package/src/components/animations/animation.ce +1 -2
  63. package/src/components/character.ce +81 -20
  64. package/src/components/gui/dialogbox/index.ce +1 -2
  65. package/src/module.ts +8 -0
  66. package/src/services/mmorpg.ts +7 -1
@@ -7,10 +7,9 @@
7
7
  const { x, y, animationName, graphic, onFinish } = defineProps();
8
8
 
9
9
  const client = inject(RpgClientEngine);
10
- const spritesheets = client.spritesheets;
11
10
 
12
11
  const sheet = {
13
- definition: spritesheets.get(graphic()),
12
+ definition: client.getSpriteSheet(graphic()),
14
13
  playing: animationName() ?? 'default',
15
14
  onFinish
16
15
  };
@@ -1,4 +1,4 @@
1
- <Container x y zIndex={y} viewportFollow={isMe} controls onBeforeDestroy>
1
+ <Container x={smoothX} y={smoothY} zIndex={y} viewportFollow={isMe} controls onBeforeDestroy visible>
2
2
  @for (component of componentsBehind) {
3
3
  <Container>
4
4
  <component object />
@@ -18,8 +18,8 @@
18
18
  </Container>
19
19
 
20
20
  <script>
21
- import { signal, effect, mount, computed, tick } from "canvasengine";
22
- import { lastValueFrom } from "rxjs";
21
+ import { signal, effect, mount, computed, tick, animatedSignal } from "canvasengine";
22
+ import { lastValueFrom, combineLatest, pairwise, filter, map, startWith } from "rxjs";
23
23
  import { Particle } from "@canvasengine/presets";
24
24
  import { GameEngineToken, ModulesToken } from "@rpgjs/common";
25
25
  import { RpgClientEngine } from "../RpgClientEngine";
@@ -28,7 +28,7 @@
28
28
  import Hit from "./effects/hit.ce";
29
29
 
30
30
  const { object, id } = defineProps();
31
-
31
+
32
32
  const client = inject(RpgClientEngine);
33
33
  const hooks = inject(ModulesToken);
34
34
 
@@ -48,7 +48,8 @@
48
48
  emitParticleTrigger,
49
49
  particleName,
50
50
  graphics,
51
- hitbox
51
+ hitbox,
52
+ isConnected,
52
53
  } = object;
53
54
 
54
55
  const particleSettings = client.particleSettings;
@@ -56,6 +57,13 @@
56
57
  const canControls = () => isMe() && object.canMove()
57
58
  const keyboardControls = client.globalConfig.keyboardControls;
58
59
 
60
+ const visible = computed(() => {
61
+ if (object.type === 'event') {
62
+ return true
63
+ }
64
+ return isConnected()
65
+ });
66
+
59
67
  const controls = signal({
60
68
  down: {
61
69
  repeat: true,
@@ -98,10 +106,28 @@
98
106
  },
99
107
  });
100
108
 
109
+ const smoothX = animatedSignal(x(), {
110
+ duration: isMe() ? 0 : 0
111
+ });
112
+
113
+ const smoothY = animatedSignal(y(), {
114
+ duration: isMe() ? 0 : 0,
115
+ });
116
+
117
+ const realAnimationName = signal(animationName());
118
+
119
+ const xSubscription = x.observable.subscribe((value) => {
120
+ smoothX.set(value);
121
+ });
122
+
123
+ const ySubscription = y.observable.subscribe((value) => {
124
+ smoothY.set(value);
125
+ });
126
+
101
127
  const sheet = (graphicId) => {
102
128
  return {
103
- definition: spritesheets.get(graphicId),
104
- playing: animationName,
129
+ definition: client.getSpriteSheet(graphicId),
130
+ playing: realAnimationName,
105
131
  params: {
106
132
  direction
107
133
  },
@@ -111,27 +137,62 @@
111
137
  };
112
138
  }
113
139
 
114
- // Track animation changes to reset animation state when needed
115
- let previousAnimationName = animationName();
116
- effect(() => {
117
- const currentAnimationName = animationName();
118
-
119
- // If animation changed externally (not through setAnimation), reset the state
120
- if (currentAnimationName !== previousAnimationName && object.animationIsPlaying && object.animationIsPlaying()) {
121
- // Check if this is a movement animation (walk, stand) that should interrupt custom animations
122
- const movementAnimations = ['walk', 'stand'];
123
- if (movementAnimations.includes(currentAnimationName)) {
140
+ // Combine animation change detection with movement state from smoothX/smoothY
141
+ const movementAnimations = ['walk', 'stand'];
142
+ const epsilon = 0; // movement threshold to consider the easing still running
143
+
144
+ const stateX$ = smoothX.animatedState.observable;
145
+ const stateY$ = smoothY.animatedState.observable;
146
+ const animationName$ = animationName.observable;
147
+
148
+ const moving$ = combineLatest([stateX$, stateY$]).pipe(
149
+ map(([sx, sy]) => {
150
+ const xFinished = Math.abs(sx.value.current - sx.value.end) <= epsilon;
151
+ const yFinished = Math.abs(sy.value.current - sy.value.end) <= epsilon;
152
+ return !xFinished || !yFinished; // moving if X or Y is not finished
153
+ }),
154
+ startWith(false)
155
+ );
156
+
157
+ const animationChange$ = animationName$.pipe(
158
+ startWith(animationName()),
159
+ pairwise(),
160
+ filter(([prev, curr]) => prev !== curr)
161
+ );
162
+
163
+ const animationMovementSubscription = combineLatest([animationChange$, moving$]).subscribe(([[prev, curr], isMoving]) => {
164
+ if (curr == 'stand' && !isMoving) {
165
+ realAnimationName.set(curr);
166
+ }
167
+ else if (curr == 'walk' && isMoving) {
168
+ realAnimationName.set(curr);
169
+ }
170
+ else if (!movementAnimations.includes(curr)) {
171
+ realAnimationName.set(curr);
172
+ }
173
+ if (!isMoving && object.animationIsPlaying && object.animationIsPlaying()) {
174
+ if (movementAnimations.includes(curr)) {
124
175
  if (typeof object.resetAnimationState === 'function') {
125
176
  object.resetAnimationState();
126
177
  }
127
178
  }
128
179
  }
129
-
130
- previousAnimationName = currentAnimationName;
131
-
132
180
  });
133
181
 
182
+ /**
183
+ * Cleanup subscriptions and call hooks before sprite destruction.
184
+ *
185
+ * # Design
186
+ * - Prevent memory leaks by unsubscribing from all local subscriptions created in this component.
187
+ * - Execute destruction hooks to notify modules and scene map of sprite removal.
188
+ *
189
+ * @example
190
+ * await onBeforeDestroy();
191
+ */
134
192
  const onBeforeDestroy = async () => {
193
+ animationMovementSubscription.unsubscribe();
194
+ xSubscription.unsubscribe();
195
+ ySubscription.unsubscribe();
135
196
  await lastValueFrom(hooks.callHooks("client-sprite-onDestroy", object))
136
197
  await lastValueFrom(hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, object))
137
198
  }
@@ -78,7 +78,6 @@
78
78
  }
79
79
  const dialogBoxTypewriterSound = client.globalConfig?.box?.sounds?.typewriter
80
80
 
81
- const spritesheets = client.spritesheets;
82
81
  const sounds = client.sounds;
83
82
 
84
83
  client.stopProcessingInput = true;
@@ -140,7 +139,7 @@
140
139
 
141
140
  const faceSheet = (graphicId, animationName) => {
142
141
  return {
143
- definition: spritesheets.get(graphicId),
142
+ definition: client.getSpriteSheet(graphicId),
144
143
  playing: animationName,
145
144
  };
146
145
  }
package/src/module.ts CHANGED
@@ -22,6 +22,14 @@ export function provideClientModules(modules: RpgClient[]) {
22
22
  },
23
23
  };
24
24
  }
25
+ if (module.spritesheetResolver) {
26
+ const resolver = module.spritesheetResolver;
27
+ module.spritesheetResolver = {
28
+ load: (engine: RpgClientEngine) => {
29
+ engine.setSpritesheetResolver(resolver);
30
+ },
31
+ };
32
+ }
25
33
  if (module.sounds) {
26
34
  const sounds = [...module.sounds];
27
35
  module.sounds = {
@@ -10,10 +10,14 @@ interface MmorpgOptions {
10
10
  }
11
11
 
12
12
  class BridgeWebsocket extends AbstractWebsocket {
13
- private socket: any;
13
+ private socket: any;
14
+ private privateId: string;
14
15
 
15
16
  constructor(protected context: Context, private options: MmorpgOptions = {}) {
16
17
  super(context);
18
+ const id = localStorage.getItem("rpgjs-user-id") || crypto.randomUUID()
19
+ localStorage.setItem("rpgjs-user-id", id)
20
+ this.privateId = id
17
21
  }
18
22
 
19
23
  async connection(listeners?: (data: any) => void) {
@@ -25,6 +29,7 @@ class BridgeWebsocket extends AbstractWebsocket {
25
29
  this.socket = await connectionRoom({
26
30
  host: this.options.host || window.location.host,
27
31
  room: "lobby-1",
32
+ id: this.privateId
28
33
  }, instance)
29
34
 
30
35
  listeners?.(this.socket)
@@ -45,6 +50,7 @@ class BridgeWebsocket extends AbstractWebsocket {
45
50
  updateProperties({ room }: { room: any }) {
46
51
  this.socket.conn.updateProperties({
47
52
  room: room,
53
+ id: this.privateId,
48
54
  host: this.options.host
49
55
  })
50
56
  }