dsh-offwork 1.0.1 → 1.1.1

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 (41) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +38 -20
  3. package/assets/animations/NOTICE.md +15 -0
  4. package/assets/animations/alpha-probe.webm +0 -0
  5. package/assets/animations/ornate-drink.webm +0 -0
  6. package/assets/animations/ornate-greet.webm +0 -0
  7. package/assets/animations/ornate-idle.webm +0 -0
  8. package/assets/animations/whale-drink.webm +0 -0
  9. package/assets/animations/whale-greet.webm +0 -0
  10. package/assets/characters/NOTICE.md +11 -0
  11. package/assets/characters/whale-actions.webp +0 -0
  12. package/assets/characters/whale-dress.webp +0 -0
  13. package/assets/characters/whale-ornate-actions.webp +0 -0
  14. package/assets/characters/whale-ornate.webp +0 -0
  15. package/client.js +4033 -188
  16. package/client.source.js +2449 -214
  17. package/companion.js +185 -0
  18. package/hydration-store.js +101 -0
  19. package/hydration.js +303 -0
  20. package/locale/en.json +1 -1
  21. package/locale/zh.json +1 -1
  22. package/package.json +13 -2
  23. package/pet-interactions.js +138 -0
  24. package/pet-motion.js +110 -0
  25. package/pet-surfaces.js +194 -0
  26. package/pet-video.js +231 -0
  27. package/pet-view.js +323 -0
  28. package/tests/companion.test.mjs +86 -0
  29. package/tests/hydration-extras.test.mjs +226 -0
  30. package/tests/hydration-store.test.mjs +287 -0
  31. package/tests/hydration.test.mjs +211 -0
  32. package/tests/pet-character.test.mjs +62 -0
  33. package/tests/pet-interactions.test.mjs +88 -0
  34. package/tests/pet-motion.test.mjs +139 -0
  35. package/tests/pet-surfaces.test.mjs +128 -0
  36. package/tests/pet-video.test.mjs +337 -0
  37. package/tests/pet-view.test.mjs +48 -0
  38. package/tests/ui.test.mjs +22 -2
  39. package/tools/build.mjs +62 -6
  40. package/tools/check-package.mjs +20 -0
  41. package/ui-utils.js +22 -4
package/tools/build.mjs CHANGED
@@ -1,7 +1,19 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
2
 
3
3
  const root = new URL('../', import.meta.url);
4
- const moduleNames = ['client.source.js', 'clock.js', 'ui-utils.js'];
4
+ const moduleNames = [
5
+ 'client.source.js',
6
+ 'clock.js',
7
+ 'ui-utils.js',
8
+ 'companion.js',
9
+ 'pet-view.js',
10
+ 'pet-motion.js',
11
+ 'pet-video.js',
12
+ 'pet-interactions.js',
13
+ 'pet-surfaces.js',
14
+ 'hydration.js',
15
+ 'hydration-store.js',
16
+ ];
5
17
  const [entry, ...modules] = await Promise.all(
6
18
  moduleNames.map((name) => readFile(new URL(name, root), 'utf8')),
7
19
  );
@@ -10,11 +22,55 @@ if (entry.split(marker).length !== 2) {
10
22
  throw new Error('Expected exactly one pure-module marker in client.source.js');
11
23
  }
12
24
 
13
- // Preserve the pure modules verbatim apart from exports so the browser runs
14
- // the same implementation that the Node.js tests import.
15
- const pure = modules.map((source) => source.replace(/^export /gm, '')).join('\n');
16
- const banner = '// Generated by tools/build.mjs; edit client.source.js, clock.js or ui-utils.js.\n';
17
- const result = banner + entry.replace(marker, pure);
25
+ // Embed the tested modules, removing only their internal module wiring.
26
+ // The browser and Node.js tests execute the same calculation and store code.
27
+ const pure = modules
28
+ .map((source) =>
29
+ source.replace(/^import .* from '\.\/hydration\.js';\n/gm, '').replace(/^export /gm, ''),
30
+ )
31
+ .join('\n');
32
+ const banner = '// Generated by tools/build.mjs; edit the source modules, not this file.\n';
33
+ const spriteMarker = '/* PET_SPRITES */';
34
+ if (entry.split(spriteMarker).length !== 2) throw new Error('Expected one sprite marker');
35
+ const sprites = Object.fromEntries(
36
+ await Promise.all(
37
+ ['whale-dress', 'whale-ornate', 'whale-actions', 'whale-ornate-actions'].map(async (id) => {
38
+ const bytes = await readFile(new URL(`assets/characters/${id}.webp`, root));
39
+ if (bytes.toString('ascii', 8, 12) !== 'WEBP' || bytes.length > 200000)
40
+ throw new Error(`Invalid sprite: ${id}`);
41
+ return [id, 'data:image/webp;base64,' + bytes.toString('base64')];
42
+ }),
43
+ ),
44
+ );
45
+ const clipMarker = '/* PET_CLIPS */';
46
+ if (entry.split(clipMarker).length !== 2) throw new Error('Expected one clip marker');
47
+ async function clip(name, maximum) {
48
+ const bytes = await readFile(new URL(`assets/animations/${name}.webm`, root));
49
+ if (bytes.readUInt32BE(0) !== 0x1a45dfa3 || bytes.length > maximum)
50
+ throw new Error(`Invalid or oversized WebM: ${name}`);
51
+ return 'data:video/webm;base64,' + bytes.toString('base64');
52
+ }
53
+ const [probe, greet, celebrate, ornateIdle, ornateGreet, ornateDrink] = await Promise.all([
54
+ clip('alpha-probe', 10000),
55
+ clip('whale-greet', 2000000),
56
+ clip('whale-drink', 2000000),
57
+ clip('ornate-idle', 2000000),
58
+ clip('ornate-greet', 2000000),
59
+ clip('ornate-drink', 2000000),
60
+ ]);
61
+ const clips = {
62
+ probe,
63
+ models: {
64
+ 'whale-actions': { greet, celebrate },
65
+ 'whale-ornate-actions': { idle: ornateIdle, greet: ornateGreet, celebrate: ornateDrink },
66
+ },
67
+ };
68
+ const result =
69
+ banner +
70
+ entry
71
+ .replace(marker, pure)
72
+ .replace(spriteMarker, () => `const PET_SPRITES = ${JSON.stringify(sprites)};`)
73
+ .replace(clipMarker, () => `const PET_CLIPS = ${JSON.stringify(clips)};`);
18
74
  const output = new URL('client.js', root);
19
75
 
20
76
  if (process.argv.includes('--check')) {
@@ -68,8 +68,28 @@ for (const path of [
68
68
  'package.json',
69
69
  'index.js',
70
70
  'client.js',
71
+ 'companion.js',
72
+ 'pet-view.js',
73
+ 'pet-motion.js',
74
+ 'pet-video.js',
75
+ 'pet-interactions.js',
76
+ 'pet-surfaces.js',
77
+ 'hydration.js',
78
+ 'hydration-store.js',
71
79
  'cordis.patch.yml',
72
80
  'icon.svg',
81
+ 'assets/characters/whale-dress.webp',
82
+ 'assets/characters/whale-ornate.webp',
83
+ 'assets/characters/whale-actions.webp',
84
+ 'assets/characters/whale-ornate-actions.webp',
85
+ 'assets/animations/ornate-idle.webm',
86
+ 'assets/animations/ornate-greet.webm',
87
+ 'assets/animations/ornate-drink.webm',
88
+ 'assets/animations/whale-greet.webm',
89
+ 'assets/animations/whale-drink.webm',
90
+ 'assets/animations/alpha-probe.webm',
91
+ 'assets/animations/NOTICE.md',
92
+ 'assets/characters/NOTICE.md',
73
93
  'locale/zh.json',
74
94
  'locale/en.json',
75
95
  'README.md',
package/ui-utils.js CHANGED
@@ -11,11 +11,13 @@ export function clampPosition(position, viewport, size) {
11
11
  const width = Math.max(0, viewport.width),
12
12
  height = Math.max(0, viewport.height);
13
13
  const edge = Math.min(12, width / 2, height / 2);
14
- const maxX = Math.max(edge, width - size.width - edge);
15
- const maxY = Math.max(edge, height - size.height - edge);
14
+ const left = viewport.left ?? 0,
15
+ top = viewport.top ?? 0;
16
+ const maxX = Math.max(left + edge, left + width - size.width - edge);
17
+ const maxY = Math.max(top + edge, top + height - size.height - edge);
16
18
  return {
17
- x: Math.max(edge, Math.min(Number.isFinite(position?.x) ? position.x : maxX, maxX)),
18
- y: Math.max(edge, Math.min(Number.isFinite(position?.y) ? position.y : 88, maxY)),
19
+ x: Math.max(left + edge, Math.min(Number.isFinite(position?.x) ? position.x : maxX, maxX)),
20
+ y: Math.max(top + edge, Math.min(Number.isFinite(position?.y) ? position.y : top + 88, maxY)),
19
21
  };
20
22
  }
21
23
 
@@ -29,6 +31,22 @@ export function normalizeView(value) {
29
31
  return {
30
32
  mini: value?.mini !== false,
31
33
  masked: value?.masked !== false,
34
+ companion: value?.companion !== false,
35
+ companionMotion: value?.companionMotion !== false,
36
+ petModel: [
37
+ 'whale-dress',
38
+ 'whale-ornate',
39
+ 'whale-actions',
40
+ 'whale-ornate-actions',
41
+ 'xiaoxie-original',
42
+ ].includes(value?.petModel)
43
+ ? value.petModel
44
+ : 'whale-dress',
45
+ petSize: Math.min(180, Math.max(80, Number.isFinite(value?.petSize) ? value.petSize : 132)),
46
+ petPosition:
47
+ Number.isFinite(value?.petPosition?.x) && Number.isFinite(value?.petPosition?.y)
48
+ ? { x: value.petPosition.x, y: value.petPosition.y }
49
+ : null,
32
50
  opacity: clampOpacity(value?.opacity),
33
51
  position:
34
52
  Number.isFinite(value?.position?.x) && Number.isFinite(value?.position?.y)