@rpgjs/client 5.0.0-beta.20 → 5.0.0-beta.23
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 +16 -0
- package/dist/Game/Object.d.ts +1 -0
- package/dist/Game/Object.js +10 -0
- package/dist/Game/Object.js.map +1 -1
- package/dist/Gui/Gui.js +37 -4
- package/dist/Gui/Gui.js.map +1 -1
- package/dist/RpgClientEngine.d.ts +18 -9
- package/dist/RpgClientEngine.js +40 -13
- package/dist/RpgClientEngine.js.map +1 -1
- package/dist/components/character-hitbox.d.ts +13 -0
- package/dist/components/character-hitbox.js +38 -0
- package/dist/components/character-hitbox.js.map +1 -0
- package/dist/components/character-hitbox.spec.d.ts +1 -0
- package/dist/components/character.ce.js +179 -36
- package/dist/components/character.ce.js.map +1 -1
- package/dist/components/gui/mobile/index.d.ts +51 -2
- package/dist/components/gui/mobile/index.js +12 -4
- package/dist/components/gui/mobile/index.js.map +1 -1
- package/dist/components/gui/mobile/index.spec.d.ts +1 -0
- package/dist/components/gui/mobile/mobile.ce.js +303 -55
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/services/cameraFollow.d.ts +51 -0
- package/dist/services/cameraFollow.js +134 -0
- package/dist/services/cameraFollow.js.map +1 -0
- package/dist/services/cameraFollow.spec.d.ts +1 -0
- package/dist/services/standalone.d.ts +6 -1
- package/dist/services/standalone.js +35 -17
- package/dist/services/standalone.js.map +1 -1
- package/dist/utils/syncHitbox.d.ts +1 -0
- package/dist/utils/syncHitbox.js +69 -0
- package/dist/utils/syncHitbox.js.map +1 -0
- package/dist/utils/syncHitbox.spec.d.ts +1 -0
- package/package.json +5 -5
- package/src/Game/Object.spec.ts +44 -4
- package/src/Game/Object.ts +19 -0
- package/src/Gui/Gui.spec.ts +19 -0
- package/src/Gui/Gui.ts +50 -11
- package/src/RpgClientEngine.ts +56 -24
- package/src/components/character-hitbox.spec.ts +33 -0
- package/src/components/character-hitbox.ts +72 -0
- package/src/components/character.ce +207 -48
- package/src/components/gui/mobile/index.spec.ts +94 -0
- package/src/components/gui/mobile/index.ts +74 -6
- package/src/components/gui/mobile/mobile.ce +347 -65
- package/src/index.ts +12 -0
- package/src/services/cameraFollow.spec.ts +220 -0
- package/src/services/cameraFollow.ts +222 -0
- package/src/services/standalone.spec.ts +47 -0
- package/src/services/standalone.ts +53 -20
- package/src/utils/syncHitbox.spec.ts +79 -0
- package/src/utils/syncHitbox.ts +104 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type CharacterHitbox = {
|
|
2
|
+
w: number;
|
|
3
|
+
h: number;
|
|
4
|
+
anchorMode?: "top-left" | "center" | "foot";
|
|
5
|
+
};
|
|
6
|
+
export declare const toPositiveNumber: (value: unknown) => number | undefined;
|
|
7
|
+
export declare const scaleHitboxForGraphicDisplay: (box: CharacterHitbox | null | undefined, scale: [number, number]) => CharacterHitbox | null;
|
|
8
|
+
export declare const resolveHitboxAnchor: (spriteWidth: number | undefined, spriteHeight: number | undefined, realSize: number | {
|
|
9
|
+
height?: number;
|
|
10
|
+
} | undefined, box: CharacterHitbox | null | undefined) => [number, number];
|
|
11
|
+
export declare const resolveScaledHitboxAnchor: (spriteWidth: number | undefined, spriteHeight: number | undefined, realSize: number | {
|
|
12
|
+
height?: number;
|
|
13
|
+
} | undefined, box: CharacterHitbox | null | undefined, scale: [number, number]) => [number, number];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/components/character-hitbox.ts
|
|
2
|
+
var toPositiveNumber = (value) => {
|
|
3
|
+
const number = typeof value === "number" ? value : parseFloat(String(value));
|
|
4
|
+
return Number.isFinite(number) && number > 0 ? number : void 0;
|
|
5
|
+
};
|
|
6
|
+
var clampRatio = (value) => Math.min(1, Math.max(0, value));
|
|
7
|
+
var scaleHitboxForGraphicDisplay = (box, scale) => {
|
|
8
|
+
if (!box) return null;
|
|
9
|
+
const scaleX = Math.abs(toPositiveNumber(scale[0]) ?? 1);
|
|
10
|
+
const scaleY = Math.abs(toPositiveNumber(scale[1]) ?? scaleX);
|
|
11
|
+
return {
|
|
12
|
+
...box,
|
|
13
|
+
w: box.w / scaleX,
|
|
14
|
+
h: box.h / scaleY
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
var resolveHitboxAnchor = (spriteWidth, spriteHeight, realSize, box) => {
|
|
18
|
+
if (!spriteWidth || !spriteHeight || !box) return [0, 0];
|
|
19
|
+
const resolvedHeight = toPositiveNumber(typeof realSize === "number" ? realSize : realSize?.height) ?? spriteHeight;
|
|
20
|
+
const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
|
|
21
|
+
const hitboxTopLeftX = clampRatio((spriteWidth - box.w) / 2 / spriteWidth);
|
|
22
|
+
const hitboxTopLeftY = clampRatio((spriteHeight - box.h - gap) / spriteHeight);
|
|
23
|
+
const hitboxCenterX = clampRatio(hitboxTopLeftX + box.w / 2 / spriteWidth);
|
|
24
|
+
const hitboxCenterY = clampRatio(hitboxTopLeftY + box.h / 2 / spriteHeight);
|
|
25
|
+
const footY = clampRatio((spriteHeight - gap) / spriteHeight);
|
|
26
|
+
switch (box.anchorMode ?? "top-left") {
|
|
27
|
+
case "center": return [hitboxCenterX, hitboxCenterY];
|
|
28
|
+
case "foot": return [hitboxCenterX, footY];
|
|
29
|
+
default: return [hitboxTopLeftX, hitboxTopLeftY];
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var resolveScaledHitboxAnchor = (spriteWidth, spriteHeight, realSize, box, scale) => {
|
|
33
|
+
return resolveHitboxAnchor(spriteWidth, spriteHeight, realSize, scaleHitboxForGraphicDisplay(box, scale));
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { resolveScaledHitboxAnchor, scaleHitboxForGraphicDisplay, toPositiveNumber };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=character-hitbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"character-hitbox.js","names":[],"sources":["../../src/components/character-hitbox.ts"],"sourcesContent":["export type CharacterHitbox = {\n w: number;\n h: number;\n anchorMode?: \"top-left\" | \"center\" | \"foot\";\n};\n\nexport const toPositiveNumber = (value: unknown): number | undefined => {\n const number = typeof value === \"number\" ? value : parseFloat(String(value));\n return Number.isFinite(number) && number > 0 ? number : undefined;\n};\n\nconst clampRatio = (value: number): number => Math.min(1, Math.max(0, value));\n\nexport const scaleHitboxForGraphicDisplay = (\n box: CharacterHitbox | null | undefined,\n scale: [number, number],\n): CharacterHitbox | null => {\n if (!box) return null;\n const scaleX = Math.abs(toPositiveNumber(scale[0]) ?? 1);\n const scaleY = Math.abs(toPositiveNumber(scale[1]) ?? scaleX);\n\n return {\n ...box,\n w: box.w / scaleX,\n h: box.h / scaleY,\n };\n};\n\nexport const resolveHitboxAnchor = (\n spriteWidth: number | undefined,\n spriteHeight: number | undefined,\n realSize: number | { height?: number } | undefined,\n box: CharacterHitbox | null | undefined,\n): [number, number] => {\n if (!spriteWidth || !spriteHeight || !box) {\n return [0, 0];\n }\n\n const heightOfSprite = typeof realSize === \"number\" ? realSize : realSize?.height;\n const resolvedHeight = toPositiveNumber(heightOfSprite) ?? spriteHeight;\n const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);\n const hitboxTopLeftX = clampRatio((spriteWidth - box.w) / 2 / spriteWidth);\n const hitboxTopLeftY = clampRatio((spriteHeight - box.h - gap) / spriteHeight);\n const hitboxCenterX = clampRatio(hitboxTopLeftX + box.w / 2 / spriteWidth);\n const hitboxCenterY = clampRatio(hitboxTopLeftY + box.h / 2 / spriteHeight);\n const footY = clampRatio((spriteHeight - gap) / spriteHeight);\n\n switch (box.anchorMode ?? \"top-left\") {\n case \"center\":\n return [hitboxCenterX, hitboxCenterY];\n case \"foot\":\n return [hitboxCenterX, footY];\n case \"top-left\":\n default:\n return [hitboxTopLeftX, hitboxTopLeftY];\n }\n};\n\nexport const resolveScaledHitboxAnchor = (\n spriteWidth: number | undefined,\n spriteHeight: number | undefined,\n realSize: number | { height?: number } | undefined,\n box: CharacterHitbox | null | undefined,\n scale: [number, number],\n): [number, number] => {\n return resolveHitboxAnchor(\n spriteWidth,\n spriteHeight,\n realSize,\n scaleHitboxForGraphicDisplay(box, scale),\n );\n};\n"],"mappings":";AAMA,IAAa,oBAAoB,UAAuC;CACtE,MAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,WAAW,OAAO,KAAK,CAAC;CAC3E,OAAO,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS,KAAA;AAC1D;AAEA,IAAM,cAAc,UAA0B,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAE5E,IAAa,gCACX,KACA,UAC2B;CAC3B,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,SAAS,KAAK,IAAI,iBAAiB,MAAM,EAAE,KAAK,CAAC;CACvD,MAAM,SAAS,KAAK,IAAI,iBAAiB,MAAM,EAAE,KAAK,MAAM;CAE5D,OAAO;EACL,GAAG;EACH,GAAG,IAAI,IAAI;EACX,GAAG,IAAI,IAAI;CACb;AACF;AAEA,IAAa,uBACX,aACA,cACA,UACA,QACqB;CACrB,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,KACpC,OAAO,CAAC,GAAG,CAAC;CAId,MAAM,iBAAiB,iBADA,OAAO,aAAa,WAAW,WAAW,UAAU,MACrB,KAAK;CAC3D,MAAM,MAAM,KAAK,IAAI,IAAI,eAAe,kBAAkB,CAAC;CAC3D,MAAM,iBAAiB,YAAY,cAAc,IAAI,KAAK,IAAI,WAAW;CACzE,MAAM,iBAAiB,YAAY,eAAe,IAAI,IAAI,OAAO,YAAY;CAC7E,MAAM,gBAAgB,WAAW,iBAAiB,IAAI,IAAI,IAAI,WAAW;CACzE,MAAM,gBAAgB,WAAW,iBAAiB,IAAI,IAAI,IAAI,YAAY;CAC1E,MAAM,QAAQ,YAAY,eAAe,OAAO,YAAY;CAE5D,QAAQ,IAAI,cAAc,YAA1B;EACE,KAAK,UACH,OAAO,CAAC,eAAe,aAAa;EACtC,KAAK,QACH,OAAO,CAAC,eAAe,KAAK;EAE9B,SACE,OAAO,CAAC,gBAAgB,cAAc;CAC1C;AACF;AAEA,IAAa,6BACX,aACA,cACA,UACA,KACA,UACqB;CACrB,OAAO,oBACL,aACA,cACA,UACA,6BAA6B,KAAK,KAAK,CACzC;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,6 +4,8 @@ import { getCanMoveValue } from "../utils/readPropValue.js";
|
|
|
4
4
|
import { RpgGui } from "../Gui/Gui.js";
|
|
5
5
|
import { normalizeEventComponent } from "../Game/EventComponentResolver.js";
|
|
6
6
|
import { RpgClientEngine } from "../RpgClientEngine.js";
|
|
7
|
+
import { applyCameraFollow, clearCameraFollowPlugins, ownsCameraFollowRevision } from "../services/cameraFollow.js";
|
|
8
|
+
import { resolveScaledHitboxAnchor, scaleHitboxForGraphicDisplay, toPositiveNumber } from "./character-hitbox.js";
|
|
7
9
|
import __ce_component$1 from "./player-components.ce.js";
|
|
8
10
|
import __ce_component$2 from "./interaction-components.ce.js";
|
|
9
11
|
import { Container, Sprite, animatedSignal, computed, cond, effect, h, loop, mount, on, signal, tick, useDefineEmits, useDefineProps, useProps } from "canvasengine";
|
|
@@ -118,8 +120,33 @@ function component($$props) {
|
|
|
118
120
|
const canControls = () => isMe() && getCanMoveValue(sprite);
|
|
119
121
|
const keyboardControls = client.globalConfig.keyboardControls;
|
|
120
122
|
const activeDirectionKeys = /* @__PURE__ */ new Map();
|
|
123
|
+
const activeControlDirections = /* @__PURE__ */ new Map();
|
|
124
|
+
const activeControlDirectionReleaseTimers = /* @__PURE__ */ new Map();
|
|
125
|
+
const CONTROL_DIRECTION_RELEASE_GRACE_MS = 160;
|
|
126
|
+
const hasHeldDirection = () => activeDirectionKeys.size > 0 || activeControlDirections.size > 0;
|
|
127
|
+
const publishMobileDebug = (type, payload = {}) => {
|
|
128
|
+
const target = typeof window !== "undefined" ? window : globalThis;
|
|
129
|
+
if (!target.__RPGJS_MOBILE_DEBUG__) return;
|
|
130
|
+
const event = {
|
|
131
|
+
source: "character",
|
|
132
|
+
type,
|
|
133
|
+
time: Date.now(),
|
|
134
|
+
isCurrentPlayer: isCurrentPlayer(),
|
|
135
|
+
animationName: animationName(),
|
|
136
|
+
realAnimationName: realAnimationName?.(),
|
|
137
|
+
activeKeyboardDirections: Array.from(activeDirectionKeys.values()),
|
|
138
|
+
activeControlDirections: Array.from(activeControlDirections.values()),
|
|
139
|
+
heldDirection: resolveHeldDirection(),
|
|
140
|
+
canControls: canControls(),
|
|
141
|
+
x: x(),
|
|
142
|
+
y: y(),
|
|
143
|
+
...payload
|
|
144
|
+
};
|
|
145
|
+
target.__RPGJS_MOBILE_DEBUG_EVENTS__ = [...(target.__RPGJS_MOBILE_DEBUG_EVENTS__ || []).slice(-199), event];
|
|
146
|
+
target.__RPGJS_MOBILE_DEBUG_LAST__ = event;
|
|
147
|
+
};
|
|
121
148
|
const resolveHeldDirection = () => {
|
|
122
|
-
const directions = Array.from(activeDirectionKeys.values());
|
|
149
|
+
const directions = [...Array.from(activeDirectionKeys.values()), ...Array.from(activeControlDirections.values())];
|
|
123
150
|
return directions[directions.length - 1];
|
|
124
151
|
};
|
|
125
152
|
const resolveSpriteDirection = () => {
|
|
@@ -163,24 +190,75 @@ function component($$props) {
|
|
|
163
190
|
};
|
|
164
191
|
const resolveCurrentActionInput = () => withCurrentDirection(resolveKeyboardActionInput(keyboardControls.action, client, sprite));
|
|
165
192
|
const playPredictedWalkAnimation = () => {
|
|
166
|
-
if (sprite.animationFixed)
|
|
167
|
-
|
|
193
|
+
if (sprite.animationFixed) {
|
|
194
|
+
publishMobileDebug("walk:skip", { reason: "animationFixed" });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (sprite.animationIsPlaying && sprite.animationIsPlaying()) {
|
|
198
|
+
publishMobileDebug("walk:skip", { reason: "animationIsPlaying" });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
168
201
|
realAnimationName.set("walk");
|
|
202
|
+
publishMobileDebug("walk:set", { reason: "predicted" });
|
|
169
203
|
};
|
|
170
204
|
const resumeHeldDirectionWalkAnimation = () => {
|
|
171
|
-
if (!isCurrentPlayer())
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
205
|
+
if (!isCurrentPlayer()) {
|
|
206
|
+
publishMobileDebug("walk:resume-fail", { reason: "notCurrentPlayer" });
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (!hasHeldDirection()) {
|
|
210
|
+
publishMobileDebug("walk:resume-fail", { reason: "noHeldDirection" });
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
if (!canControls()) {
|
|
214
|
+
publishMobileDebug("walk:resume-fail", { reason: "cannotControl" });
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
if (sprite.animationFixed) {
|
|
218
|
+
publishMobileDebug("walk:resume-fail", { reason: "animationFixed" });
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (sprite.animationIsPlaying && sprite.animationIsPlaying()) {
|
|
222
|
+
publishMobileDebug("walk:resume-fail", { reason: "animationIsPlaying" });
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
176
225
|
realAnimationName.set("walk");
|
|
226
|
+
publishMobileDebug("walk:set", { reason: "resumeHeldDirection" });
|
|
177
227
|
return true;
|
|
178
228
|
};
|
|
179
229
|
const processMovementInput = (input) => {
|
|
180
230
|
if (!canControls()) return;
|
|
231
|
+
publishMobileDebug("movement:input", { input });
|
|
181
232
|
client.processInput({ input });
|
|
182
233
|
playPredictedWalkAnimation();
|
|
183
234
|
};
|
|
235
|
+
const activateControlDirection = (name, directionInput) => {
|
|
236
|
+
const releaseTimer = activeControlDirectionReleaseTimers.get(name);
|
|
237
|
+
if (releaseTimer) {
|
|
238
|
+
clearTimeout(releaseTimer);
|
|
239
|
+
activeControlDirectionReleaseTimers.delete(name);
|
|
240
|
+
}
|
|
241
|
+
activeControlDirections.delete(name);
|
|
242
|
+
activeControlDirections.set(name, directionInput);
|
|
243
|
+
publishMobileDebug("control:activate", {
|
|
244
|
+
name,
|
|
245
|
+
directionInput
|
|
246
|
+
});
|
|
247
|
+
resumeHeldDirectionWalkAnimation();
|
|
248
|
+
};
|
|
249
|
+
const releaseControlDirection = (name) => {
|
|
250
|
+
const previousTimer = activeControlDirectionReleaseTimers.get(name);
|
|
251
|
+
if (previousTimer) clearTimeout(previousTimer);
|
|
252
|
+
activeControlDirectionReleaseTimers.set(name, setTimeout(() => {
|
|
253
|
+
activeControlDirectionReleaseTimers.delete(name);
|
|
254
|
+
activeControlDirections.delete(name);
|
|
255
|
+
publishMobileDebug("control:release", { name });
|
|
256
|
+
if (!hasHeldDirection() && animationName() === "stand") {
|
|
257
|
+
realAnimationName.set("stand");
|
|
258
|
+
publishMobileDebug("stand:set", { reason: "controlRelease" });
|
|
259
|
+
}
|
|
260
|
+
}, CONTROL_DIRECTION_RELEASE_GRACE_MS));
|
|
261
|
+
};
|
|
184
262
|
const processDashInput = () => {
|
|
185
263
|
if (!canControls()) return;
|
|
186
264
|
client.processDash({ direction: directionToDashVector(resolveSpriteDirection()) });
|
|
@@ -213,28 +291,44 @@ function component($$props) {
|
|
|
213
291
|
repeat: true,
|
|
214
292
|
bind: keyboardControls.down,
|
|
215
293
|
keyDown() {
|
|
294
|
+
activateControlDirection("down", Direction.Down);
|
|
216
295
|
processMovementInput(Direction.Down);
|
|
296
|
+
},
|
|
297
|
+
keyUp() {
|
|
298
|
+
releaseControlDirection("down");
|
|
217
299
|
}
|
|
218
300
|
},
|
|
219
301
|
up: {
|
|
220
302
|
repeat: true,
|
|
221
303
|
bind: keyboardControls.up,
|
|
222
304
|
keyDown() {
|
|
305
|
+
activateControlDirection("up", Direction.Up);
|
|
223
306
|
processMovementInput(Direction.Up);
|
|
307
|
+
},
|
|
308
|
+
keyUp() {
|
|
309
|
+
releaseControlDirection("up");
|
|
224
310
|
}
|
|
225
311
|
},
|
|
226
312
|
left: {
|
|
227
313
|
repeat: true,
|
|
228
314
|
bind: keyboardControls.left,
|
|
229
315
|
keyDown() {
|
|
316
|
+
activateControlDirection("left", Direction.Left);
|
|
230
317
|
processMovementInput(Direction.Left);
|
|
318
|
+
},
|
|
319
|
+
keyUp() {
|
|
320
|
+
releaseControlDirection("left");
|
|
231
321
|
}
|
|
232
322
|
},
|
|
233
323
|
right: {
|
|
234
324
|
repeat: true,
|
|
235
325
|
bind: keyboardControls.right,
|
|
236
326
|
keyDown() {
|
|
327
|
+
activateControlDirection("right", Direction.Right);
|
|
237
328
|
processMovementInput(Direction.Right);
|
|
329
|
+
},
|
|
330
|
+
keyUp() {
|
|
331
|
+
releaseControlDirection("right");
|
|
238
332
|
}
|
|
239
333
|
},
|
|
240
334
|
action: {
|
|
@@ -249,12 +343,33 @@ function component($$props) {
|
|
|
249
343
|
processDashInput();
|
|
250
344
|
}
|
|
251
345
|
},
|
|
346
|
+
back: {
|
|
347
|
+
bind: keyboardControls.escape,
|
|
348
|
+
keyDown() {
|
|
349
|
+
if (canControls()) client.processAction({ action: "escape" });
|
|
350
|
+
}
|
|
351
|
+
},
|
|
252
352
|
escape: {
|
|
253
353
|
bind: keyboardControls.escape,
|
|
254
354
|
keyDown() {
|
|
255
355
|
if (canControls()) client.processAction({ action: "escape" });
|
|
256
356
|
}
|
|
257
357
|
},
|
|
358
|
+
joystick: {
|
|
359
|
+
enabled: true,
|
|
360
|
+
directionMapping: {
|
|
361
|
+
top: "up",
|
|
362
|
+
bottom: "down",
|
|
363
|
+
left: "left",
|
|
364
|
+
right: "right",
|
|
365
|
+
top_left: ["up", "left"],
|
|
366
|
+
top_right: ["up", "right"],
|
|
367
|
+
bottom_left: ["down", "left"],
|
|
368
|
+
bottom_right: ["down", "right"]
|
|
369
|
+
},
|
|
370
|
+
moveInterval: 50,
|
|
371
|
+
threshold: .1
|
|
372
|
+
},
|
|
258
373
|
gamepad: { enabled: true }
|
|
259
374
|
};
|
|
260
375
|
const smoothX = animatedSignal(x(), { duration: isMe() ? 0 : 0 });
|
|
@@ -281,20 +396,36 @@ function component($$props) {
|
|
|
281
396
|
};
|
|
282
397
|
};
|
|
283
398
|
const graphicScale = (graphicObject) => {
|
|
284
|
-
const scale = graphicObject?.
|
|
399
|
+
const scale = graphicObject?.scale;
|
|
400
|
+
if (Array.isArray(scale)) return scale;
|
|
401
|
+
if (typeof scale === "number") return [scale, scale];
|
|
402
|
+
if (scale && typeof scale === "object") {
|
|
403
|
+
const x = typeof scale.x === "number" ? scale.x : 1;
|
|
404
|
+
return [x, typeof scale.y === "number" ? scale.y : x];
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const graphicContainerScale = (graphicObject) => {
|
|
408
|
+
const scale = graphicObject?.displayScale;
|
|
285
409
|
if (Array.isArray(scale)) return scale;
|
|
286
410
|
if (typeof scale === "number") return [scale, scale];
|
|
287
411
|
if (scale && typeof scale === "object") {
|
|
288
412
|
const x = typeof scale.x === "number" ? scale.x : 1;
|
|
289
413
|
return [x, typeof scale.y === "number" ? scale.y : x];
|
|
290
414
|
}
|
|
415
|
+
return [1, 1];
|
|
416
|
+
};
|
|
417
|
+
const multiplyScale = (left, right) => {
|
|
418
|
+
const a = normalizePair(left);
|
|
419
|
+
const b = normalizePair(right);
|
|
420
|
+
return [a[0] * b[0], a[1] * b[1]];
|
|
421
|
+
};
|
|
422
|
+
const graphicHitbox = (graphicObject) => {
|
|
423
|
+
return scaleHitboxForGraphicDisplay(hitbox(), graphicEffectiveScale(graphicObject));
|
|
291
424
|
};
|
|
292
425
|
const shadowCaster = (graphicObject) => {
|
|
293
426
|
const box = hitbox();
|
|
294
427
|
const bounds = graphicBounds();
|
|
295
|
-
const
|
|
296
|
-
const scaleY = Array.isArray(scale) && typeof scale[1] === "number" ? Math.abs(scale[1]) : 1;
|
|
297
|
-
const height = Math.max(bounds?.height ?? box?.h ?? 32, box?.h ?? 32) * scaleY;
|
|
428
|
+
const height = Math.max(bounds?.height ?? box?.h ?? 32, box?.h ?? 32);
|
|
298
429
|
return {
|
|
299
430
|
enabled: shadowsEnabled,
|
|
300
431
|
height,
|
|
@@ -318,10 +449,6 @@ function component($$props) {
|
|
|
318
449
|
};
|
|
319
450
|
const imageDimensions = signal({});
|
|
320
451
|
const loadingImageDimensions = /* @__PURE__ */ new Set();
|
|
321
|
-
const toPositiveNumber = (value) => {
|
|
322
|
-
const number = typeof value === "number" ? value : parseFloat(value);
|
|
323
|
-
return Number.isFinite(number) && number > 0 ? number : void 0;
|
|
324
|
-
};
|
|
325
452
|
const toFiniteNumber = (value, fallback = 0) => {
|
|
326
453
|
const number = typeof value === "number" ? value : parseFloat(value);
|
|
327
454
|
return Number.isFinite(number) ? number : fallback;
|
|
@@ -394,6 +521,10 @@ function component($$props) {
|
|
|
394
521
|
const optionValue = (prop, frame, textureOptions, graphicObject) => {
|
|
395
522
|
return frame?.[prop] ?? textureOptions?.[prop] ?? graphicObject?.[prop];
|
|
396
523
|
};
|
|
524
|
+
const graphicEffectiveScale = (graphicObject) => {
|
|
525
|
+
const textureOptions = resolveTextureOptions(graphicObject);
|
|
526
|
+
return multiplyScale(normalizePair(optionValue("scale", resolveFirstAnimationFrame(textureOptions), textureOptions, graphicObject) ?? graphicScale(graphicObject)), graphicContainerScale(graphicObject));
|
|
527
|
+
};
|
|
397
528
|
const resolveFrameSize = (textureOptions, dimensions) => {
|
|
398
529
|
const framesWidth = toPositiveNumber(textureOptions?.framesWidth) ?? 1;
|
|
399
530
|
const framesHeight = toPositiveNumber(textureOptions?.framesHeight) ?? 1;
|
|
@@ -406,21 +537,6 @@ function component($$props) {
|
|
|
406
537
|
height: toPositiveNumber(textureOptions?.rectHeight) ?? toPositiveNumber(textureOptions?.spriteHeight) ?? (fullHeight ? fullHeight / framesHeight : void 0)
|
|
407
538
|
};
|
|
408
539
|
};
|
|
409
|
-
const resolveHitboxAnchor = (spriteWidth, spriteHeight, realSize, box) => {
|
|
410
|
-
if (!spriteWidth || !spriteHeight || !box) return [0, 0];
|
|
411
|
-
const resolvedHeight = toPositiveNumber(typeof realSize === "number" ? realSize : realSize?.height) ?? spriteHeight;
|
|
412
|
-
const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
|
|
413
|
-
const hitboxTopLeftX = clampRatio((spriteWidth - box.w) / 2 / spriteWidth);
|
|
414
|
-
const hitboxTopLeftY = clampRatio((spriteHeight - box.h - gap) / spriteHeight);
|
|
415
|
-
const hitboxCenterX = clampRatio(hitboxTopLeftX + box.w / 2 / spriteWidth);
|
|
416
|
-
const hitboxCenterY = clampRatio(hitboxTopLeftY + box.h / 2 / spriteHeight);
|
|
417
|
-
const footY = clampRatio((spriteHeight - gap) / spriteHeight);
|
|
418
|
-
switch (box.anchorMode ?? "top-left") {
|
|
419
|
-
case "center": return [hitboxCenterX, hitboxCenterY];
|
|
420
|
-
case "foot": return [hitboxCenterX, footY];
|
|
421
|
-
default: return [hitboxTopLeftX, hitboxTopLeftY];
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
540
|
const loadImageDimensions = (image) => {
|
|
425
541
|
const source = resolveImageSource(image);
|
|
426
542
|
if (!source || imageDimensions()[source] || loadingImageDimensions.has(source)) return;
|
|
@@ -482,8 +598,8 @@ function component($$props) {
|
|
|
482
598
|
const spriteWidth = size.width ?? box?.w;
|
|
483
599
|
const spriteHeight = size.height ?? box?.h;
|
|
484
600
|
if (!spriteWidth || !spriteHeight) return;
|
|
485
|
-
const
|
|
486
|
-
const
|
|
601
|
+
const scale = graphicEffectiveScale(graphicObject);
|
|
602
|
+
const anchor = normalizeAnchor(optionValue("anchor", frame, textureOptions, graphicObject)) ?? resolveScaledHitboxAnchor(spriteWidth, spriteHeight, optionValue("spriteRealSize", frame, textureOptions, graphicObject), box, scale);
|
|
487
603
|
const x = toFiniteNumber(optionValue("x", frame, textureOptions, graphicObject), 0);
|
|
488
604
|
const y = toFiniteNumber(optionValue("y", frame, textureOptions, graphicObject), 0);
|
|
489
605
|
const leftEdge = -anchor[0] * spriteWidth * scale[0];
|
|
@@ -628,12 +744,15 @@ function component($$props) {
|
|
|
628
744
|
removeTransitionSubscription?.unsubscribe();
|
|
629
745
|
animationMovementSubscription.unsubscribe();
|
|
630
746
|
resumeWalkSubscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
747
|
+
activeControlDirectionReleaseTimers.forEach((timer) => clearTimeout(timer));
|
|
748
|
+
activeControlDirectionReleaseTimers.clear();
|
|
631
749
|
xSubscription.unsubscribe();
|
|
632
750
|
ySubscription.unsubscribe();
|
|
633
751
|
await lastValueFrom(hooks.callHooks("client-sprite-onDestroy", sprite));
|
|
634
752
|
await lastValueFrom(hooks.callHooks("client-sceneMap-onRemoveSprite", client.sceneMap, sprite));
|
|
635
753
|
};
|
|
636
754
|
mount((element) => {
|
|
755
|
+
let appliedCameraFollowRevision = null;
|
|
637
756
|
if (typeof document !== "undefined") {
|
|
638
757
|
document.addEventListener("keydown", handleNativeActionWhileMoving);
|
|
639
758
|
document.addEventListener("keyup", handleNativeActionWhileMoving);
|
|
@@ -643,6 +762,31 @@ function component($$props) {
|
|
|
643
762
|
effect(() => {
|
|
644
763
|
if (isCurrentPlayer()) client.setKeyboardControls(element.directives.controls);
|
|
645
764
|
});
|
|
765
|
+
effect(() => {
|
|
766
|
+
const followRevision = client.cameraFollowRevision();
|
|
767
|
+
if (!shouldFollowCamera()) {
|
|
768
|
+
appliedCameraFollowRevision = null;
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const smoothMove = client.cameraFollowSmoothMove;
|
|
772
|
+
const viewport = element.props.context?.viewport;
|
|
773
|
+
const target = element.componentInstance;
|
|
774
|
+
if (!viewport || !target) {
|
|
775
|
+
appliedCameraFollowRevision = null;
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
appliedCameraFollowRevision = applyCameraFollow({
|
|
779
|
+
viewport,
|
|
780
|
+
target,
|
|
781
|
+
smoothMove,
|
|
782
|
+
followRevision,
|
|
783
|
+
isCurrentRevision: (revision) => client.cameraFollowRevision() === revision,
|
|
784
|
+
shouldFollowCamera
|
|
785
|
+
}) ? followRevision : null;
|
|
786
|
+
});
|
|
787
|
+
return () => {
|
|
788
|
+
if (ownsCameraFollowRevision(appliedCameraFollowRevision, client.cameraFollowRevision())) clearCameraFollowPlugins(element.props.context?.viewport);
|
|
789
|
+
};
|
|
646
790
|
});
|
|
647
791
|
const normalizeOpenId = (value) => {
|
|
648
792
|
const resolved = typeof value === "function" ? value() : value;
|
|
@@ -664,7 +808,6 @@ function component($$props) {
|
|
|
664
808
|
x: smoothX,
|
|
665
809
|
y: smoothY,
|
|
666
810
|
zIndex: z,
|
|
667
|
-
viewportFollow: shouldFollowCamera,
|
|
668
811
|
controls,
|
|
669
812
|
onBeforeDestroy,
|
|
670
813
|
visible,
|
|
@@ -696,11 +839,11 @@ function component($$props) {
|
|
|
696
839
|
zIndex: 1e3,
|
|
697
840
|
name: particleName
|
|
698
841
|
}),
|
|
699
|
-
h(Container, null, [loop(renderedGraphics, (graphicObj) => h(Container, { scale: computed(() =>
|
|
842
|
+
h(Container, null, [loop(renderedGraphics, (graphicObj) => h(Container, { scale: computed(() => graphicContainerScale(graphicObj)) }, h(Sprite, {
|
|
700
843
|
sheet: computed(() => sheet(graphicObj)),
|
|
701
844
|
direction,
|
|
702
845
|
tint,
|
|
703
|
-
hitbox,
|
|
846
|
+
hitbox: computed(() => graphicHitbox(graphicObj)),
|
|
704
847
|
shadowCaster: computed(() => shadowCaster(graphicObj)),
|
|
705
848
|
flash: flashConfig
|
|
706
849
|
}))), loop(resolvedEventComponents, (eventComponent) => h(Container, { dependencies: eventComponent.dependencies }, h(eventComponent.component, eventComponent.props)))]),
|