desktop-pet-app 0.3.27 → 0.4.3
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.
|
@@ -170,12 +170,15 @@ class SpriteRenderer {
|
|
|
170
170
|
}
|
|
171
171
|
ctx;
|
|
172
172
|
img = new Image();
|
|
173
|
+
source = this.img;
|
|
173
174
|
ready = false;
|
|
174
175
|
load(skin) {
|
|
175
176
|
this.ready = false;
|
|
177
|
+
this.source = this.img;
|
|
176
178
|
this.canvas.width = skin.displaySize?.width ?? Math.round(skin.frameWidth * SPRITE_SCALE);
|
|
177
179
|
this.canvas.height = skin.displaySize?.height ?? Math.round(skin.frameHeight * SPRITE_SCALE);
|
|
178
180
|
this.img.onload = () => {
|
|
181
|
+
this.source = skin.chromaKey ? this.removeChromaKey(skin) : this.img;
|
|
179
182
|
this.ready = true;
|
|
180
183
|
this.onReady();
|
|
181
184
|
};
|
|
@@ -202,14 +205,17 @@ class SpriteRenderer {
|
|
|
202
205
|
ctx.scale(-1, 1);
|
|
203
206
|
}
|
|
204
207
|
ctx.imageSmoothingEnabled = skin.smoothing ?? true;
|
|
205
|
-
const
|
|
206
|
-
const
|
|
208
|
+
const sourceRect = skin.sourceRect;
|
|
209
|
+
const sourceWidth = sourceRect?.width ?? skin.frameWidth;
|
|
210
|
+
const sourceHeight = sourceRect?.height ?? skin.frameHeight;
|
|
211
|
+
const sx = (sourceRect?.x ?? 0) + (skin.imageLayout === "single" ? 0 : frame * sourceWidth);
|
|
212
|
+
const sy = (sourceRect?.y ?? 0) + (skin.imageLayout === "single" ? 0 : s.row * sourceHeight);
|
|
207
213
|
ctx.drawImage(
|
|
208
|
-
this.
|
|
214
|
+
this.source,
|
|
209
215
|
sx,
|
|
210
216
|
sy,
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
sourceWidth,
|
|
218
|
+
sourceHeight,
|
|
213
219
|
0,
|
|
214
220
|
0,
|
|
215
221
|
w,
|
|
@@ -231,6 +237,28 @@ class SpriteRenderer {
|
|
|
231
237
|
ctx.fillText("Z", w - 16, 16 - off);
|
|
232
238
|
}
|
|
233
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* 生成图使用纯品红背景以避免透明通道被错误烘焙为棋盘格。
|
|
242
|
+
* 图片只在加载时处理一次,绘制循环复用离屏画布。
|
|
243
|
+
*/
|
|
244
|
+
removeChromaKey(skin) {
|
|
245
|
+
const key = skin.chromaKey;
|
|
246
|
+
const tolerance = key.tolerance ?? 24;
|
|
247
|
+
const canvas2 = document.createElement("canvas");
|
|
248
|
+
canvas2.width = this.img.naturalWidth;
|
|
249
|
+
canvas2.height = this.img.naturalHeight;
|
|
250
|
+
const ctx = canvas2.getContext("2d", { willReadFrequently: true });
|
|
251
|
+
ctx.drawImage(this.img, 0, 0);
|
|
252
|
+
const imageData = ctx.getImageData(0, 0, canvas2.width, canvas2.height);
|
|
253
|
+
const pixels = imageData.data;
|
|
254
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
255
|
+
if (isChromaPixel(pixels[i], pixels[i + 1], pixels[i + 2], key, tolerance)) {
|
|
256
|
+
pixels[i + 3] = 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
ctx.putImageData(imageData, 0, 0);
|
|
260
|
+
return canvas2;
|
|
261
|
+
}
|
|
234
262
|
drawHeart(ctx, px, py) {
|
|
235
263
|
for (let y = 0; y < HEART.length; y++) {
|
|
236
264
|
for (let x = 0; x < HEART[y].length; x++) {
|
|
@@ -239,6 +267,9 @@ class SpriteRenderer {
|
|
|
239
267
|
}
|
|
240
268
|
}
|
|
241
269
|
}
|
|
270
|
+
function isChromaPixel(red, green, blue, key, tolerance = key.tolerance ?? 24) {
|
|
271
|
+
return Math.abs(red - key.red) <= tolerance && Math.abs(green - key.green) <= tolerance && Math.abs(blue - key.blue) <= tolerance;
|
|
272
|
+
}
|
|
242
273
|
function proceduralMotion(state, elapsedMs) {
|
|
243
274
|
const wave = Math.sin(elapsedMs / 240);
|
|
244
275
|
switch (state) {
|
|
@@ -358,6 +389,37 @@ const bird = {
|
|
|
358
389
|
failed: [FAILED$9]
|
|
359
390
|
}
|
|
360
391
|
};
|
|
392
|
+
const yierBubuUrl = "" + new URL("yier-bubu-v2-BaEcMG7v.png", import.meta.url).href;
|
|
393
|
+
const still$2 = { row: 0, frames: 1, frameMs: 180 };
|
|
394
|
+
const bubu = {
|
|
395
|
+
id: "bubu",
|
|
396
|
+
name: "布布",
|
|
397
|
+
image: yierBubuUrl,
|
|
398
|
+
imageLayout: "single",
|
|
399
|
+
sourceRect: { x: 800, y: 160, width: 600, height: 720 },
|
|
400
|
+
frameWidth: 600,
|
|
401
|
+
frameHeight: 720,
|
|
402
|
+
displaySize: { width: 120, height: 144 },
|
|
403
|
+
recommendedWindow: { width: 200, height: 190 },
|
|
404
|
+
smoothing: false,
|
|
405
|
+
chromaKey: { red: 255, green: 0, blue: 255, tolerance: 150 },
|
|
406
|
+
states: {
|
|
407
|
+
idle: { ...still$2, frameMs: 900 },
|
|
408
|
+
walk: { ...still$2, frameMs: 180 },
|
|
409
|
+
sleep: { ...still$2, frameMs: 900 },
|
|
410
|
+
excited: { ...still$2, frameMs: 160 },
|
|
411
|
+
drag: { ...still$2, frameMs: 160 },
|
|
412
|
+
working: { ...still$2, frameMs: 300 },
|
|
413
|
+
success: { ...still$2, frameMs: 160 },
|
|
414
|
+
failed: { ...still$2, frameMs: 400 }
|
|
415
|
+
},
|
|
416
|
+
anchors: {
|
|
417
|
+
ground: { x: 0.5, y: 0.99 },
|
|
418
|
+
bubble: { x: 0.5, y: 0.04 },
|
|
419
|
+
effects: { character: { x: 0.5, y: 0.5 } },
|
|
420
|
+
contentBounds: { x: 0.08, y: 0.03, width: 0.83, height: 0.96 }
|
|
421
|
+
}
|
|
422
|
+
};
|
|
361
423
|
const IDLE$8 = [
|
|
362
424
|
"...oo......oo...",
|
|
363
425
|
"..oiao....oaio..",
|
|
@@ -1426,7 +1488,7 @@ const whale = {
|
|
|
1426
1488
|
}
|
|
1427
1489
|
};
|
|
1428
1490
|
const robotBlueUrl = "" + new URL("robot-blue-CFH9bWjZ.png", import.meta.url).href;
|
|
1429
|
-
const still = { row: 0, frames: 1, frameMs: 180 };
|
|
1491
|
+
const still$1 = { row: 0, frames: 1, frameMs: 180 };
|
|
1430
1492
|
const robotBlue = {
|
|
1431
1493
|
id: "robot-blue",
|
|
1432
1494
|
name: "蓝色协作机器人",
|
|
@@ -1438,14 +1500,14 @@ const robotBlue = {
|
|
|
1438
1500
|
recommendedWindow: { width: 260, height: 320 },
|
|
1439
1501
|
smoothing: true,
|
|
1440
1502
|
states: {
|
|
1441
|
-
idle: { ...still, frameMs: 220 },
|
|
1442
|
-
walk: { ...still, frameMs: 110 },
|
|
1443
|
-
sleep: { ...still, frameMs: 260 },
|
|
1444
|
-
excited: { ...still, frameMs: 110 },
|
|
1445
|
-
drag: { ...still, frameMs: 120 },
|
|
1446
|
-
working: { ...still, frameMs: 150 },
|
|
1447
|
-
success: { ...still, frameMs: 110 },
|
|
1448
|
-
failed: { ...still, frameMs: 180 }
|
|
1503
|
+
idle: { ...still$1, frameMs: 220 },
|
|
1504
|
+
walk: { ...still$1, frameMs: 110 },
|
|
1505
|
+
sleep: { ...still$1, frameMs: 260 },
|
|
1506
|
+
excited: { ...still$1, frameMs: 110 },
|
|
1507
|
+
drag: { ...still$1, frameMs: 120 },
|
|
1508
|
+
working: { ...still$1, frameMs: 150 },
|
|
1509
|
+
success: { ...still$1, frameMs: 110 },
|
|
1510
|
+
failed: { ...still$1, frameMs: 180 }
|
|
1449
1511
|
},
|
|
1450
1512
|
anchors: {
|
|
1451
1513
|
ground: { x: 0.5, y: 0.955 },
|
|
@@ -1458,6 +1520,36 @@ const robotBlue = {
|
|
|
1458
1520
|
contentBounds: { x: 0.08, y: 0.02, width: 0.84, height: 0.94 }
|
|
1459
1521
|
}
|
|
1460
1522
|
};
|
|
1523
|
+
const still = { row: 0, frames: 1, frameMs: 180 };
|
|
1524
|
+
const yier = {
|
|
1525
|
+
id: "yier",
|
|
1526
|
+
name: "一二",
|
|
1527
|
+
image: yierBubuUrl,
|
|
1528
|
+
imageLayout: "single",
|
|
1529
|
+
sourceRect: { x: 144, y: 160, width: 600, height: 720 },
|
|
1530
|
+
frameWidth: 600,
|
|
1531
|
+
frameHeight: 720,
|
|
1532
|
+
displaySize: { width: 120, height: 144 },
|
|
1533
|
+
recommendedWindow: { width: 200, height: 190 },
|
|
1534
|
+
smoothing: false,
|
|
1535
|
+
chromaKey: { red: 255, green: 0, blue: 255, tolerance: 150 },
|
|
1536
|
+
states: {
|
|
1537
|
+
idle: { ...still, frameMs: 900 },
|
|
1538
|
+
walk: { ...still, frameMs: 180 },
|
|
1539
|
+
sleep: { ...still, frameMs: 900 },
|
|
1540
|
+
excited: { ...still, frameMs: 160 },
|
|
1541
|
+
drag: { ...still, frameMs: 160 },
|
|
1542
|
+
working: { ...still, frameMs: 300 },
|
|
1543
|
+
success: { ...still, frameMs: 160 },
|
|
1544
|
+
failed: { ...still, frameMs: 400 }
|
|
1545
|
+
},
|
|
1546
|
+
anchors: {
|
|
1547
|
+
ground: { x: 0.5, y: 0.99 },
|
|
1548
|
+
bubble: { x: 0.5, y: 0.04 },
|
|
1549
|
+
effects: { character: { x: 0.5, y: 0.5 } },
|
|
1550
|
+
contentBounds: { x: 0.07, y: 0.03, width: 0.84, height: 0.96 }
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1461
1553
|
const skins = {
|
|
1462
1554
|
codex,
|
|
1463
1555
|
claude,
|
|
@@ -1470,7 +1562,12 @@ const skins = {
|
|
|
1470
1562
|
whale,
|
|
1471
1563
|
penguin
|
|
1472
1564
|
};
|
|
1473
|
-
const spriteSkins = {
|
|
1565
|
+
const spriteSkins = {
|
|
1566
|
+
hoot,
|
|
1567
|
+
"robot-blue": robotBlue,
|
|
1568
|
+
yier,
|
|
1569
|
+
bubu
|
|
1570
|
+
};
|
|
1474
1571
|
const defaultSkin = codex;
|
|
1475
1572
|
function resolveSkin(id) {
|
|
1476
1573
|
const ascii = skins[id];
|
|
Binary file
|
package/out/renderer/index.html
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "desktop-pet-app",
|
|
3
|
-
"version": "0.3
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "AI desktop pet with MCP support and a self-hosted relay server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dev": "electron-vite dev",
|
|
28
28
|
"build": "electron-vite build && npm run build:mcp",
|
|
29
29
|
"build:mcp": "esbuild src/mcp-server/index.ts --bundle --platform=node --format=esm --outfile=out/mcp-server/index.js && node -e \"require('fs').writeFileSync('out/mcp-server/package.json', JSON.stringify({ type: 'module' }))\"",
|
|
30
|
+
"e2e:capabilities": "node scripts/e2e-capability-modes.mjs",
|
|
30
31
|
"e2e:duty": "node scripts/e2e-duty-sessions.mjs",
|
|
31
32
|
"e2e:strict-handoff": "vitest run src/mcp-server/strict-handoff.e2e.test.ts",
|
|
32
33
|
"e2e:codebuddy:git": "node scripts/e2e-codebuddy-git-task.mjs",
|