dsh-pet-live2d 2.2.0 → 2.3.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.
- package/CHANGELOG.md +90 -0
- package/README.md +30 -0
- package/lib/client.js +859 -39
- package/lib/index.js +99 -0
- package/package.json +1 -1
- package/pets/ds-whale-girl/pet.json +58 -0
package/lib/index.js
CHANGED
|
@@ -274,6 +274,88 @@ function normaliseSlots(raw) {
|
|
|
274
274
|
return out
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* 名字里带这些字的部件算"头":作者在 cdi3 里给每个部件起了中文名
|
|
279
|
+
* (`Part46` = 脸蛋、`Part57/58` = 眼睛、`Part14` = 头发、`Part45/68` = 耳朵…)。
|
|
280
|
+
*/
|
|
281
|
+
const HEAD_PART_NAME_HINTS = /头|脸|面|眼|眉|嘴|耳|发|eye|face|hair|head|ear|brow|mouth|cheek|nose/i
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 名字里带这些字的部件算"尾巴/翅膀"(这只宠物有 15 个:狐狸尾 / 猫尾 / 狼尾 / 大翅膀…,
|
|
285
|
+
* 都是可换的配件,同一时刻只有一个是显形的)。
|
|
286
|
+
*/
|
|
287
|
+
const TAIL_PART_NAME_HINTS = /尾|鳍|翅|翼|fin|tail|wing/i
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 从 cdi3 里按名字挑部件。
|
|
291
|
+
*
|
|
292
|
+
* 为什么要读 cdi3:摸头/摸尾巴要知道"哪些 drawable 是头、是尾巴"。原来浏览器半区用的是
|
|
293
|
+
* 写死的英文正则(`face|eye|mouth|…`),而这只模型的 drawable id 是 `Part46` 这种 ——
|
|
294
|
+
* **一个都匹配不上**,于是判定静默退化成"点哪都算头"。作者自己的中文名才是权威分类。
|
|
295
|
+
*
|
|
296
|
+
* @returns {string[]} 部件 id;没有 cdi3 或没挑到就是空数组(浏览器半区会退回旧行为)。
|
|
297
|
+
*/
|
|
298
|
+
function readPartsMatching(dir, modelPath, hints) {
|
|
299
|
+
try {
|
|
300
|
+
const base = modelPath.replace(/\.model3\.json$/i, '')
|
|
301
|
+
const file = findCdi3(dir, base)
|
|
302
|
+
if (file === undefined) return []
|
|
303
|
+
const cdi3 = readJson(file)
|
|
304
|
+
const parts = cdi3?.Parts
|
|
305
|
+
if (!Array.isArray(parts)) return []
|
|
306
|
+
const out = []
|
|
307
|
+
for (const part of parts) {
|
|
308
|
+
if (part === null || typeof part !== 'object') continue
|
|
309
|
+
const id = typeof part.Id === 'string' ? part.Id : undefined
|
|
310
|
+
if (id === undefined || id === '') continue
|
|
311
|
+
const name = typeof part.Name === 'string' ? part.Name : ''
|
|
312
|
+
if (!hints.test(name) && !hints.test(id)) continue
|
|
313
|
+
out.push(id)
|
|
314
|
+
}
|
|
315
|
+
return out
|
|
316
|
+
} catch {
|
|
317
|
+
return []
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 找 cdi3.json。
|
|
323
|
+
*
|
|
324
|
+
* 它**不在** model3.json 的引用里(那是 Cubism Editor 的元数据,运行时不读),所以只能
|
|
325
|
+
* 按约定找:与模型同名的 `<base>.cdi3.json` 优先,其次模型旁边、再其次宠物目录和它的
|
|
326
|
+
* 直接子目录 —— 这只宠物就放在 `model/` 子目录里,只在根目录找会一个都挑不到。
|
|
327
|
+
*/
|
|
328
|
+
function findCdi3(dir, base) {
|
|
329
|
+
const direct = [join(dir, base + '.cdi3.json'), join(dir, 'model', base + '.cdi3.json')]
|
|
330
|
+
for (const file of direct) {
|
|
331
|
+
if (existsSync(file)) return file
|
|
332
|
+
}
|
|
333
|
+
const roots = [dir]
|
|
334
|
+
try {
|
|
335
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
336
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') roots.push(join(dir, entry.name))
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
/* 读不了就只用 dir 本身 */
|
|
340
|
+
}
|
|
341
|
+
for (const root of roots) {
|
|
342
|
+
try {
|
|
343
|
+
const hit = readdirSync(root).find((name) => name.toLowerCase().endsWith('.cdi3.json'))
|
|
344
|
+
if (hit !== undefined) return join(root, hit)
|
|
345
|
+
} catch {
|
|
346
|
+
/* 跳过读不了的目录 */
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return undefined
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 互动反应候选:一串标签(动作的中文名,或表情名)。非字符串/空的都丢掉。
|
|
354
|
+
*/
|
|
355
|
+
function readReactionList(value) {
|
|
356
|
+
return Array.isArray(value) ? value.filter((name) => typeof name === 'string' && name !== '') : []
|
|
357
|
+
}
|
|
358
|
+
|
|
277
359
|
/** Scan one pet directory into a catalog entry, or undefined when unusable. */
|
|
278
360
|
export function scanPet(dir, id) {
|
|
279
361
|
const manifestFile = join(dir, 'pet.json')
|
|
@@ -426,6 +508,17 @@ export function scanPet(dir, id) {
|
|
|
426
508
|
// premise is missing (a selfie with no phone out, a spray with no whale)
|
|
427
509
|
// must not play at all, from ANY path: the panel, a fidget or a phase.
|
|
428
510
|
motionGuards: typeof block.motionGuards === 'object' && block.motionGuards !== null ? block.motionGuards : {},
|
|
511
|
+
// 头部 / 尾巴部件(部件 id):摸头、摸尾巴按这些部件的**真实几何**判定。
|
|
512
|
+
// 空数组 = 没 cdi3 或没挑到,浏览器半区退回旧行为。
|
|
513
|
+
headParts: readPartsMatching(dir, modelPath, HEAD_PART_NAME_HINTS),
|
|
514
|
+
tailParts: readPartsMatching(dir, modelPath, TAIL_PART_NAME_HINTS),
|
|
515
|
+
// 台词:宠物自己的声音(问候 / 点击 / 摸头 / 摸尾巴 / 转晕 / 归位 / 每个相位)。
|
|
516
|
+
// 用户能在设置里逐条改,改过的存浏览器;这里是**默认值**。
|
|
517
|
+
lines: typeof block.lines === 'object' && block.lines !== null ? block.lines : {},
|
|
518
|
+
// 互动反应候选(标签:动作的中文名或表情名)。摸头 / 摸尾巴 / 转晕各一组。
|
|
519
|
+
patReactions: readReactionList(block.patReactions),
|
|
520
|
+
tailReactions: readReactionList(block.tailReactions),
|
|
521
|
+
spinReactions: readReactionList(block.spinReactions),
|
|
429
522
|
// 摸鱼默认盯哪几个槽位。以前这六个是**写死在浏览器半区**的("宠物自己的身子"),
|
|
430
523
|
// 但宠物作者(和用户)会想改:这只宠物把「自拍」也放进了摸鱼池。写在这里之后,
|
|
431
524
|
// "默认集合"也成了宠物自己声明的东西,用户加的槽位照样覆盖在上面。
|
|
@@ -624,6 +717,12 @@ function catalogRoute() {
|
|
|
624
717
|
expressionSlots: pet.expressionSlots,
|
|
625
718
|
looksByPhase: pet.looksByPhase,
|
|
626
719
|
motionGuards: pet.motionGuards,
|
|
720
|
+
headParts: pet.headParts,
|
|
721
|
+
tailParts: pet.tailParts,
|
|
722
|
+
lines: pet.lines,
|
|
723
|
+
patReactions: pet.patReactions,
|
|
724
|
+
tailReactions: pet.tailReactions,
|
|
725
|
+
spinReactions: pet.spinReactions,
|
|
627
726
|
fidgetSlots: pet.fidgetSlots,
|
|
628
727
|
hiddenMotions: pet.hiddenMotions,
|
|
629
728
|
motionOptions: pet.motionOptions,
|
package/package.json
CHANGED
|
@@ -610,6 +610,64 @@
|
|
|
610
610
|
]
|
|
611
611
|
}
|
|
612
612
|
},
|
|
613
|
+
"lines": {
|
|
614
|
+
"greet": [
|
|
615
|
+
"你好呀,我是鲸鱼娘~",
|
|
616
|
+
"今天也一起加油吧!",
|
|
617
|
+
"终于见到你了",
|
|
618
|
+
"摸鱼时间到?"
|
|
619
|
+
],
|
|
620
|
+
"click": [
|
|
621
|
+
"呀!",
|
|
622
|
+
"痒痒的~",
|
|
623
|
+
"干嘛呀",
|
|
624
|
+
"摸摸头?",
|
|
625
|
+
"嘿嘿"
|
|
626
|
+
],
|
|
627
|
+
"pat": [
|
|
628
|
+
"嘿嘿,好舒服~",
|
|
629
|
+
"再摸一下嘛",
|
|
630
|
+
"唔…头发要乱了"
|
|
631
|
+
],
|
|
632
|
+
"tail": [
|
|
633
|
+
"呀!尾巴不能乱摸!",
|
|
634
|
+
"尾巴…痒痒的",
|
|
635
|
+
"别拽尾巴啦"
|
|
636
|
+
],
|
|
637
|
+
"spin": [
|
|
638
|
+
"呜…转晕了…",
|
|
639
|
+
"别转了别转了"
|
|
640
|
+
],
|
|
641
|
+
"reset": [
|
|
642
|
+
"表情归位~",
|
|
643
|
+
"清清爽爽"
|
|
644
|
+
],
|
|
645
|
+
"loadFailed": [
|
|
646
|
+
"呜呜,模型加载失败了"
|
|
647
|
+
],
|
|
648
|
+
"phase": {
|
|
649
|
+
"thinking": "让我想想…",
|
|
650
|
+
"tool": "正在忙…",
|
|
651
|
+
"waiting": "需要你确认一下",
|
|
652
|
+
"asking": "在等你回答哦",
|
|
653
|
+
"helper": "叫了帮手一起弄",
|
|
654
|
+
"queued": "收到,记下了",
|
|
655
|
+
"done": "搞定!",
|
|
656
|
+
"failed": "呜…出错了"
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
"patReactions": [
|
|
660
|
+
"重锤出击",
|
|
661
|
+
"问号",
|
|
662
|
+
"星星眼"
|
|
663
|
+
],
|
|
664
|
+
"tailReactions": [
|
|
665
|
+
"吐魂",
|
|
666
|
+
"问号"
|
|
667
|
+
],
|
|
668
|
+
"spinReactions": [
|
|
669
|
+
"晕晕"
|
|
670
|
+
],
|
|
613
671
|
"hiddenMotions": [
|
|
614
672
|
"OpenCase",
|
|
615
673
|
"BubbleGum"
|