feat: automated most of operator config detection
This commit is contained in:
@@ -194,7 +194,7 @@ ling: !include config/ling.yaml
|
|||||||
nearl: !include config/nearl.yaml
|
nearl: !include config/nearl.yaml
|
||||||
nian: !include config/nian.yaml
|
nian: !include config/nian.yaml
|
||||||
nian_unfettered_freedom: !include config/nian_unfettered_freedom.yaml
|
nian_unfettered_freedom: !include config/nian_unfettered_freedom.yaml
|
||||||
phatom_focus: !include config/phatom_focus.yaml
|
phantom_focus: !include config/phantom_focus.yaml
|
||||||
rosmontis: !include config/rosmontis.yaml
|
rosmontis: !include config/rosmontis.yaml
|
||||||
skadi: !include config/skadi.yaml
|
skadi: !include config/skadi.yaml
|
||||||
skadi_sublimation: !include config/skadi_sublimation.yaml
|
skadi_sublimation: !include config/skadi_sublimation.yaml
|
||||||
|
|||||||
8
bun.lock
8
bun.lock
@@ -242,8 +242,12 @@
|
|||||||
"@aklive2d/libs": "workspace:*",
|
"@aklive2d/libs": "workspace:*",
|
||||||
"@aklive2d/official-info": "workspace:*",
|
"@aklive2d/official-info": "workspace:*",
|
||||||
"@aklive2d/prettier-config": "workspace:*",
|
"@aklive2d/prettier-config": "workspace:*",
|
||||||
|
"unidecode": "^1.1.0",
|
||||||
"yaml": "^2.7.0",
|
"yaml": "^2.7.0",
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/unidecode": "^1.1.0",
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"globals": ">=16.0.0",
|
"globals": ">=16.0.0",
|
||||||
"typescript": ">=5.8.2",
|
"typescript": ">=5.8.2",
|
||||||
@@ -685,6 +689,8 @@
|
|||||||
|
|
||||||
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
||||||
|
|
||||||
|
"@types/unidecode": ["@types/unidecode@1.1.0", "", {}, "sha512-NTIsFsTe9WRek39/8DDj7KiQ0nU33DHMrKwNHcD1rKlUvn4N0Rc4Di8q/Xavs8bsDZmBa4MMtQA8+HNgwfxC/A=="],
|
||||||
|
|
||||||
"@types/yauzl-promise": ["@types/yauzl-promise@4.0.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-qYEC3rJwqiJpdQ9b+bPNeuSY0c3JUM8vIuDy08qfuVN7xHm3ZDsHn2kGphUIB0ruEXrPGNXZ64nMUcu4fDjViQ=="],
|
"@types/yauzl-promise": ["@types/yauzl-promise@4.0.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-qYEC3rJwqiJpdQ9b+bPNeuSY0c3JUM8vIuDy08qfuVN7xHm3ZDsHn2kGphUIB0ruEXrPGNXZ64nMUcu4fDjViQ=="],
|
||||||
|
|
||||||
"@types/yazl": ["@types/yazl@2.4.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg=="],
|
"@types/yazl": ["@types/yazl@2.4.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/ifFjQtcKaoZOjl5NNCQRR0fAKafB3Foxd7J/WvFPTMea46zekapcR30uzkwIkKAAuq5T6d0dkwz754RFH27hg=="],
|
||||||
@@ -1535,6 +1541,8 @@
|
|||||||
|
|
||||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||||
|
|
||||||
|
"unidecode": ["unidecode@1.1.0", "", {}, "sha512-GIp57N6DVVJi8dpeIU6/leJGdv7W65ZSXFLFiNmxvexXkc0nXdqUvhA/qL9KqBKsILxMwg5MnmYNOIDJLb5JVA=="],
|
||||||
|
|
||||||
"union": ["union@0.5.0", "", { "dependencies": { "qs": "^6.4.0" } }, "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA=="],
|
"union": ["union@0.5.0", "", { "dependencies": { "qs": "^6.4.0" } }, "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA=="],
|
||||||
|
|
||||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ module:
|
|||||||
directory_assets: _directory
|
directory_assets: _directory
|
||||||
MonoBehaviour: MonoBehaviour
|
MonoBehaviour: MonoBehaviour
|
||||||
Texture2D: Texture2D
|
Texture2D: Texture2D
|
||||||
|
character_table_json: character_table.json
|
||||||
|
skin_table_json: skin_table.json
|
||||||
title:
|
title:
|
||||||
zh-CN: '明日方舟:'
|
zh-CN: '明日方舟:'
|
||||||
en-US: 'Arknights: '
|
en-US: 'Arknights: '
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export type Config = {
|
|||||||
directory_assets: string
|
directory_assets: string
|
||||||
MonoBehaviour: string
|
MonoBehaviour: string
|
||||||
Texture2D: string
|
Texture2D: string
|
||||||
|
character_table_json: string
|
||||||
|
skin_table_json: string
|
||||||
title: {
|
title: {
|
||||||
'zh-CN': string
|
'zh-CN': string
|
||||||
'en-US': string
|
'en-US': string
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ import path from 'node:path'
|
|||||||
import yauzl from 'yauzl-promise'
|
import yauzl from 'yauzl-promise'
|
||||||
import yazl from 'yazl'
|
import yazl from 'yazl'
|
||||||
|
|
||||||
|
type ReadOpts = {
|
||||||
|
encoding?: BufferEncoding
|
||||||
|
useAsPrefix?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export async function write(
|
export async function write(
|
||||||
content: string | NodeJS.ArrayBufferView,
|
content: string | NodeJS.ArrayBufferView,
|
||||||
filePath: string
|
filePath: string
|
||||||
@@ -21,14 +26,32 @@ export function writeSync(
|
|||||||
|
|
||||||
export async function read(
|
export async function read(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
encoding: BufferEncoding = 'utf8'
|
opts: ReadOpts = { encoding: 'utf8', useAsPrefix: false }
|
||||||
) {
|
) {
|
||||||
return await fsP.readFile(filePath, { encoding, flag: 'r' })
|
return await fsP.readFile(filePath, { ...opts, flag: 'r' })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readSync(filePath: string, encoding: BufferEncoding = 'utf8') {
|
export function readSync(
|
||||||
|
filePath: string,
|
||||||
|
opts: ReadOpts = { encoding: 'utf8', useAsPrefix: false }
|
||||||
|
) {
|
||||||
|
const useAsPrefix = opts.useAsPrefix
|
||||||
|
delete opts.useAsPrefix
|
||||||
|
if (useAsPrefix) {
|
||||||
|
const parent = path.dirname(filePath)
|
||||||
|
const filename = path.parse(filePath).name
|
||||||
|
const ext = path.parse(filePath).ext
|
||||||
|
const file = readdirSync(parent).find(
|
||||||
|
(e) => e.startsWith(filename) && e.endsWith(ext)
|
||||||
|
)
|
||||||
|
if (!file) return null
|
||||||
|
return fs.readFileSync(path.join(parent, file), {
|
||||||
|
...opts,
|
||||||
|
flag: 'r',
|
||||||
|
})
|
||||||
|
}
|
||||||
if (exists(filePath)) {
|
if (exists(filePath)) {
|
||||||
return fs.readFileSync(filePath, { encoding, flag: 'r' })
|
return fs.readFileSync(filePath, { ...opts, flag: 'r' })
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function read(
|
|||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const file = fs.readFileSync(file_dir, 'utf8')
|
const file = fs.readFileSync(file_dir, { encoding: 'utf8' })
|
||||||
return parse(file, {
|
return parse(file, {
|
||||||
customTags: [include, ...customTags],
|
customTags: [include, ...customTags],
|
||||||
} as SchemaOptions)
|
} as SchemaOptions)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
{
|
{
|
||||||
"codename": {
|
"codename": {
|
||||||
"zh-CN": "新约能天使",
|
"zh-CN": "新约能天使",
|
||||||
"en-US": "Exusiai the New Covenant"
|
"en-US": "Exusiai the New Covenant"
|
||||||
},
|
},
|
||||||
"type": "operator",
|
"type": "operator",
|
||||||
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202504941.html",
|
"link": "https://ak.hypergryph.com/archive/dynamicCompile/202504941.html",
|
||||||
|
|||||||
@@ -46,3 +46,21 @@ export interface OperatorConfig extends OfficialInfoOperatorConfig {
|
|||||||
export type OfficialInfoMapping = {
|
export type OfficialInfoMapping = {
|
||||||
[id: string]: OperatorConfig
|
[id: string]: OperatorConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OfficialInfoV2 = {
|
||||||
|
length: number
|
||||||
|
dates: string[]
|
||||||
|
info: OfficialInfoOperatorConfigV2[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OfficialInfoOperatorConfigV2 = {
|
||||||
|
operatorName: string
|
||||||
|
skinName: {
|
||||||
|
'zh-CN': string
|
||||||
|
'en-US': string
|
||||||
|
}
|
||||||
|
type: 'operator' | 'skin'
|
||||||
|
link: string
|
||||||
|
id: number
|
||||||
|
date: string
|
||||||
|
}
|
||||||
|
|||||||
1
packages/operator/.gitignore
vendored
1
packages/operator/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
data
|
data
|
||||||
|
auto_update
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ ling: !include config/ling.yaml
|
|||||||
nearl: !include config/nearl.yaml
|
nearl: !include config/nearl.yaml
|
||||||
nian: !include config/nian.yaml
|
nian: !include config/nian.yaml
|
||||||
nian_unfettered_freedom: !include config/nian_unfettered_freedom.yaml
|
nian_unfettered_freedom: !include config/nian_unfettered_freedom.yaml
|
||||||
phatom_focus: !include config/phatom_focus.yaml
|
phantom_focus: !include config/phantom_focus.yaml
|
||||||
rosmontis: !include config/rosmontis.yaml
|
rosmontis: !include config/rosmontis.yaml
|
||||||
skadi: !include config/skadi.yaml
|
skadi: !include config/skadi.yaml
|
||||||
skadi_sublimation: !include config/skadi_sublimation.yaml
|
skadi_sublimation: !include config/skadi_sublimation.yaml
|
||||||
@@ -18,7 +18,7 @@ lee_trust_your_eyes: !include config/lee_trust_your_eyes.yaml
|
|||||||
texas_the_omertosa: !include config/texas_the_omertosa.yaml
|
texas_the_omertosa: !include config/texas_the_omertosa.yaml
|
||||||
nearl_relight: !include config/nearl_relight.yaml
|
nearl_relight: !include config/nearl_relight.yaml
|
||||||
rosmontis_become_anew: !include config/rosmontis_become_anew.yaml
|
rosmontis_become_anew: !include config/rosmontis_become_anew.yaml
|
||||||
passager_dream_in_a_moment: !include config/passager_dream_in_a_moment.yaml
|
passenger_dream_in_a_moment: !include config/passenger_dream_in_a_moment.yaml
|
||||||
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
|
mizuki_summer_feast: !include config/mizuki_summer_feast.yaml
|
||||||
chongyue: !include config/chongyue.yaml
|
chongyue: !include config/chongyue.yaml
|
||||||
ling_it_does_wash_the_strings: !include config/ling_it_does_wash_the_strings.yaml
|
ling_it_does_wash_the_strings: !include config/ling_it_does_wash_the_strings.yaml
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
filename: dyn_illust_char_1013_chen2
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_1013_chen2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 假日威龙陈
|
zh-CN: 假日威龙陈
|
||||||
en-US: Ch'en/Chen the Holungday
|
en-US: Ch'en/Chen the Holungday
|
||||||
use_json: false
|
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1037_amiya3_sale#13
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_1037_amiya3_sale#13
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 寰宇独奏 · 阿米娅
|
zh-CN: 寰宇独奏 · 阿米娅
|
||||||
en-US: Solo Around The World / Amiya
|
en-US: Solo Around The World / Amiya
|
||||||
use_json: false
|
|
||||||
official_id: '202412473'
|
official_id: '202412473'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_332_archet_sale#14
|
|
||||||
logo: logo_Laterano
|
|
||||||
fallback_name: char_332_archet_sale#14
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 至虔者荣光 · 空弦
|
zh-CN: 至虔者荣光 · 空弦
|
||||||
en-US: Glory of the Devout / Archetto
|
en-US: Glory of the Devout / Archetto
|
||||||
use_json: false
|
|
||||||
official_id: '202504953'
|
official_id: '202504953'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1013_chen2
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_1013_chen2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 1
|
|
||||||
viewport_bottom: 1
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 假日威龙陈
|
zh-CN: 假日威龙陈
|
||||||
en-US: Ch'en/Chen the Holungday
|
en-US: Ch'en/Chen the Holungday
|
||||||
use_json: false
|
|
||||||
official_id: '20220345'
|
official_id: '20220345'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1013_chen2_boc#6
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_1013_chen2_boc#6
|
|
||||||
viewport_left: -1
|
|
||||||
viewport_right: 1
|
|
||||||
viewport_top: 2
|
|
||||||
viewport_bottom: -1
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 万重山 · 假日威龙陈
|
zh-CN: 万重山 · 假日威龙陈
|
||||||
en-US: Ten Thousand Mountains / Ch'en/Chen the Holungday
|
en-US: Ten Thousand Mountains / Ch'en/Chen the Holungday
|
||||||
use_json: false
|
|
||||||
official_id: '202304659'
|
official_id: '202304659'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2024_chyue
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2024_chyue_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 重岳
|
zh-CN: 重岳
|
||||||
en-US: Chongyue
|
en-US: Chongyue
|
||||||
use_json: false
|
|
||||||
official_id: '202301606'
|
official_id: '202301606'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2024_chyue_nian#10
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2024_chyue_nian#10
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 何处栖 · 重岳
|
zh-CN: 何处栖 · 重岳
|
||||||
en-US: Alighting / Chongyue
|
en-US: Alighting / Chongyue
|
||||||
use_json: false
|
|
||||||
official_id: '202401812'
|
official_id: '202401812'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2024_chyue_cfa#1
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2024_chyue_cfa#1
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 全能演员 · 重岳
|
zh-CN: 全能演员 · 重岳
|
||||||
en-US: All-Round Actor / Chongyue
|
en-US: All-Round Actor / Chongyue
|
||||||
use_json: false
|
|
||||||
official_id: '202412452'
|
official_id: '202412452'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4116_blkkgt_witch#5
|
|
||||||
logo: logo_kjerag
|
|
||||||
fallback_name: char_4116_blkkgt_witch#5
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 暗月的影子 · 锏
|
zh-CN: 暗月的影子 · 锏
|
||||||
en-US: The Shadow Of The Dark Moon / Degenbrecher
|
en-US: The Shadow Of The Dark Moon / Degenbrecher
|
||||||
use_json: false
|
|
||||||
official_id: '202410426'
|
official_id: '202410426'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2015_dusk
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2015_dusk_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 夕
|
zh-CN: 夕
|
||||||
en-US: Dusk
|
en-US: Dusk
|
||||||
use_json: false
|
|
||||||
official_id: '202203263'
|
official_id: '202203263'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2015_dusk_nian#7
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2015_dusk_nian#7
|
|
||||||
viewport_left: 10
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 染尘烟 · 夕
|
zh-CN: 染尘烟 · 夕
|
||||||
en-US: Everything is a Miracle / Dusk
|
en-US: Everything is a Miracle / Dusk
|
||||||
use_json: false
|
|
||||||
official_id: '20220321'
|
official_id: '20220321'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1032_excu2_sale#12
|
|
||||||
logo: logo_Laterano
|
|
||||||
fallback_name: char_1032_excu2_sale#12
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 众志归一 · 圣约送葬人
|
zh-CN: 众志归一 · 圣约送葬人
|
||||||
en-US: Allmind as one / Executor the Ex Foedere
|
en-US: Allmind as one / Executor the Ex Foedere
|
||||||
use_json: false
|
|
||||||
official_id: '202410488'
|
official_id: '202410488'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1041_angel2
|
|
||||||
logo: logo_penguin
|
|
||||||
fallback_name: char_1041_angel2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 新约能天使
|
zh-CN: 新约能天使
|
||||||
en-US: Exusiai the New Covenant
|
en-US: Exusiai the New Covenant
|
||||||
use_json: false
|
|
||||||
official_id: '202504941'
|
official_id: '202504941'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1016_agoat2
|
|
||||||
logo: logo_Leithanien
|
|
||||||
fallback_name: char_1016_agoat2_2
|
|
||||||
viewport_left: 1
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 5
|
|
||||||
viewport_bottom: 5
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 纯烬艾雅法拉
|
zh-CN: 纯烬艾雅法拉
|
||||||
en-US: Eyjafjalla the Hvít Aska
|
en-US: Eyjafjalla the Hvít Aska
|
||||||
use_json: false
|
|
||||||
official_id: '202307865'
|
official_id: '202307865'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1016_agoat2_epoque#34
|
|
||||||
logo: logo_Leithanien
|
|
||||||
fallback_name: char_1016_agoat2_epoque#34
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 远行前的野餐 · 纯烬艾雅法拉
|
zh-CN: 远行前的野餐 · 纯烬艾雅法拉
|
||||||
en-US: A Picnic Before A Long Trip / Eyjafjalla the Hvít Aska
|
en-US: A Picnic Before A Long Trip / Eyjafjalla the Hvít Aska
|
||||||
use_json: true
|
|
||||||
official_id: '202407072'
|
official_id: '202407072'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1026_gvial2
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_1026_gvial2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 百练嘉维尔
|
zh-CN: 百炼嘉维尔
|
||||||
en-US: Gavial the Invincible
|
en-US: Gavial the Invincible
|
||||||
use_json: false
|
|
||||||
official_id: '202208258'
|
official_id: '202208258'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1026_gvial2_summer#12
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_1026_gvial2_summer#12
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 悠然假日 HD26 · 百炼嘉维尔
|
zh-CN: 悠然假日 HD26 · 百炼嘉维尔
|
||||||
en-US: Holiday HD26 / Gavial the Invincible
|
en-US: Holiday HD26 / Gavial the Invincible
|
||||||
use_json: false
|
|
||||||
official_id: '202307886'
|
official_id: '202307886'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_377_gdglow_summer#12
|
|
||||||
logo: logo_victoria
|
|
||||||
fallback_name: char_377_gdglow_summer#12
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 夏卉 FA394 · 澄闪
|
zh-CN: 夏卉 FA394 · 澄闪
|
||||||
en-US: Summer Flowers FA394 / Goldenglow
|
en-US: Summer Flowers FA394 / Goldenglow
|
||||||
use_json: false
|
|
||||||
official_id: '202307824'
|
official_id: '202307824'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4087_ines_ambienceSynesthesia
|
|
||||||
logo: logo_babel
|
|
||||||
fallback_name: char_4087_ines_ambienceSynesthesia#5
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 蝶舞华章 · 伊内丝
|
zh-CN: 蝶舞华章 · 伊内丝
|
||||||
en-US: Melodic Flutter / Ines
|
en-US: Melodic Flutter / Ines
|
||||||
use_json: false
|
|
||||||
official_id: "202504968"
|
official_id: "202504968"
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4087_ines_boc#8
|
|
||||||
logo: logo_babel
|
|
||||||
fallback_name: char_4087_ines_boc#8
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 燃烧天穹下 · 伊内丝
|
zh-CN: 燃烧天穹下 · 伊内丝
|
||||||
en-US: Under the Flaming Dome / Ines
|
en-US: Under the Flaming Dome / Ines
|
||||||
use_json: false
|
|
||||||
official_id: '202404087'
|
official_id: '202404087'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_003_kalts_boc#6
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_003_kalts_boc#6
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 残余 · 凯尔希
|
zh-CN: 残余 · 凯尔希
|
||||||
en-US: Remnant / Kal'tsit
|
en-US: Remnant / Kal'tsit/Kaltsit
|
||||||
use_json: false
|
|
||||||
official_id: '202304833'
|
official_id: '202304833'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_124_kroos_sale#14
|
|
||||||
logo: logo_reserve1
|
|
||||||
fallback_name: char_124_kroos_sale#14
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 星月漂流记 · 克洛丝
|
zh-CN: 星月漂流记 · 克洛丝
|
||||||
en-US: Moonlit Voyage / Kroos
|
en-US: Moonlit Voyage / Kroos
|
||||||
use_json: false
|
|
||||||
official_id: '202504992'
|
official_id: '202504992'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1038_whitw2
|
|
||||||
logo: logo_chiave
|
|
||||||
fallback_name: char_1038_whitw2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 荒芜拉普兰德
|
zh-CN: 荒芜拉普兰德
|
||||||
en-US: Lappland the Decadenza
|
en-US: Lappland the Decadenza
|
||||||
use_json: false
|
|
||||||
official_id: '202410440'
|
official_id: '202410440'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_322_lmlee_witch#3
|
|
||||||
logo: logo_lee
|
|
||||||
fallback_name: char_322_lmlee_witch#3
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 手到牌来 · 老鲤
|
zh-CN: 手到牌来 · 老鲤
|
||||||
en-US: Trust Your Eyes / Lee
|
en-US: Trust Your Eyes / Lee
|
||||||
use_json: false
|
|
||||||
official_id: '202210279'
|
official_id: '202210279'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4080_lin_nian#10
|
|
||||||
logo: logo_lungmen
|
|
||||||
fallback_name: char_4080_lin_nian#10
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 列瑶台 · 林
|
zh-CN: 列瑶台 · 林
|
||||||
en-US: Heavenly Mirage / Lin
|
en-US: Heavenly Mirage / Lin
|
||||||
use_json: false
|
|
||||||
official_id: '202401034'
|
official_id: '202401034'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2023_ling
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2023_ling_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 令
|
zh-CN: 令
|
||||||
en-US: Ling
|
en-US: Ling
|
||||||
use_json: false
|
|
||||||
official_id: '20220383'
|
official_id: '20220383'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2023_ling_nian#9
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2023_ling_nian#9
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 濯缨 · 令
|
zh-CN: 濯缨 · 令
|
||||||
en-US: It Does Wash the Strings / Ling
|
en-US: It Does Wash the Strings / Ling
|
||||||
use_json: false
|
|
||||||
official_id: '202301647'
|
official_id: '202301647'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2023_ling_ncg#1
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2023_ling_ncg#1
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 崖高梦远 · 令
|
zh-CN: 崖高梦远 · 令
|
||||||
en-US: Towering is Cliff of Nostalgia
|
en-US: Towering is Cliff of Nostalgia / Ling
|
||||||
use_json: false
|
|
||||||
official_id: '202308807'
|
official_id: '202308807'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4133_logos_ambienceSynesthesia#6
|
|
||||||
logo: logo_elite
|
|
||||||
fallback_name: char_4133_logos_ambienceSynesthesia#6
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 辉煌的静谧 · 逻各斯
|
zh-CN: 辉煌的静谧 · 逻各斯
|
||||||
en-US: Radiant Serenity / Logos
|
en-US: Radiant Serenity / Logos
|
||||||
use_json: false
|
|
||||||
official_id: "202504989"
|
official_id: "202504989"
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_437_mizuki_sale#7
|
|
||||||
logo: logo_higashi
|
|
||||||
fallback_name: char_437_mizuki_sale#7
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 夏日餮宴 · 水月
|
zh-CN: 夏日餮宴 · 水月
|
||||||
en-US: Summer Feast / Mizuki
|
en-US: Summer Feast / Mizuki
|
||||||
use_json: false
|
|
||||||
official_id: '202211685'
|
official_id: '202211685'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_249_mlyss
|
|
||||||
logo: logo_rhine
|
|
||||||
fallback_name: char_249_mlyss_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 3
|
|
||||||
viewport_bottom: 2
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 缪尔赛思
|
zh-CN: 缪尔赛思
|
||||||
en-US: Muelsyse
|
en-US: Muelsyse
|
||||||
use_json: false
|
|
||||||
official_id: '202304611'
|
official_id: '202304611'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_249_mlyss_ambienceSynesthesia#6
|
|
||||||
logo: logo_rhine
|
|
||||||
fallback_name: char_249_mlyss_ambienceSynesthesia#6
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 漫步于黄金之梦 · 缪尔赛思
|
zh-CN: 漫步于黄金之梦 · 缪尔赛思
|
||||||
en-US: Golden Reverie / Muelsyse
|
en-US: Golden Reverie / Muelsyse
|
||||||
use_json: false
|
|
||||||
official_id: "202504900"
|
official_id: "202504900"
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_249_mlyss_boc#8
|
|
||||||
logo: logo_rhine
|
|
||||||
fallback_name: char_249_mlyss_boc#8
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 新枝 · 缪尔赛思
|
zh-CN: 新枝 · 缪尔赛思
|
||||||
en-US: Young Branch / Muelsyse
|
en-US: Young Branch / Muelsyse
|
||||||
use_json: false
|
|
||||||
official_id: '202404090'
|
official_id: '202404090'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4064_mlynar_epoque#28
|
|
||||||
logo: logo_kazimierz
|
|
||||||
fallback_name: char_4064_mlynar_epoque#28
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 远路 · 玛恩纳
|
zh-CN: 远路 · 玛恩纳
|
||||||
en-US: W Dali / Młynar
|
en-US: W Dali / Młynar
|
||||||
use_json: false
|
|
||||||
official_id: '202310850'
|
official_id: '202310850'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1014_nearl2
|
|
||||||
logo: logo_kazimierz
|
|
||||||
fallback_name: char_1014_nearl2_2
|
|
||||||
viewport_left: 2
|
|
||||||
viewport_right: 3
|
|
||||||
viewport_top: 10
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 耀骑士临光
|
zh-CN: 耀骑士临光
|
||||||
en-US: Nearl the Radiant Knight
|
en-US: Nearl the Radiant Knight
|
||||||
use_json: false
|
|
||||||
official_id: '20220304'
|
official_id: '20220304'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1014_nearl2_epoque#17
|
|
||||||
logo: logo_kazimierz
|
|
||||||
fallback_name: char_1014_nearl2_epoque#17
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 复现荣光 · 耀骑士临光
|
zh-CN: 复现荣光 · 耀骑士临光
|
||||||
en-US: Relight / Nearl
|
en-US: Relight / Nearl the Radiant Knight
|
||||||
use_json: false
|
|
||||||
official_id: '202210623'
|
official_id: '202210623'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2014_nian
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2014_nian_2
|
|
||||||
viewport_left: 2
|
|
||||||
viewport_right: 2
|
|
||||||
viewport_top: 3
|
|
||||||
viewport_bottom: 5
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 年
|
zh-CN: 年
|
||||||
en-US: Nian
|
en-US: Nian
|
||||||
use_json: false
|
|
||||||
official_id: '202203231'
|
official_id: '202203231'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2014_nian_cfa#1
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2014_nian_cfa#1
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 霹雳导演 · 年
|
zh-CN: 霹雳导演 · 年
|
||||||
en-US: Thunderbolt Director / Nian
|
en-US: Thunderbolt Director / Nian
|
||||||
use_json: false
|
|
||||||
official_id: '202412491'
|
official_id: '202412491'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2014_nian_nian#4
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2014_nian_nian#4
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 乐逍遥 · 年
|
zh-CN: 乐逍遥 · 年
|
||||||
en-US: Unfettered Freedom / Nian
|
en-US: Unfettered Freedom / Nian
|
||||||
use_json: false
|
|
||||||
official_id: '20220362'
|
official_id: '20220362'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_179_cgbird_sightseer#1
|
|
||||||
logo: logo_followers
|
|
||||||
fallback_name: char_179_cgbird_sightseer#1
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 流辉 · 夜莺
|
zh-CN: 流辉 · 夜莺
|
||||||
en-US: Iakhu of Flows / Nightingale
|
en-US: Iakhu of Flows / Nightingale
|
||||||
use_json: false
|
|
||||||
official_id: '202407435'
|
official_id: '202407435'
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
filename: dyn_illust_char_472_pasngr_epoque#17
|
|
||||||
logo: logo_sargon
|
|
||||||
fallback_name: char_472_pasngr_epoque#17
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
|
||||||
zh-CN: 今昔须臾之梦 · 异客
|
|
||||||
en-US: Dream in a Moment / Passager
|
|
||||||
use_json: false
|
|
||||||
official_id: '202210664'
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
codename:
|
||||||
|
zh-CN: 今昔须臾之梦 · 异客
|
||||||
|
en-US: Dream in a Moment / Passenger
|
||||||
|
official_id: '202210664'
|
||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4058_pepe
|
|
||||||
logo: logo_sargon
|
|
||||||
fallback_name: char_4058_pepe_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 佩佩
|
zh-CN: 佩佩
|
||||||
en-US: Pepe
|
en-US: Pepe
|
||||||
use_json: true
|
|
||||||
official_id: '202407013'
|
official_id: '202407013'
|
||||||
|
|||||||
4
packages/operator/config/phantom_focus.yaml
Normal file
4
packages/operator/config/phantom_focus.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
codename:
|
||||||
|
zh-CN: 焦点 · 傀影
|
||||||
|
en-US: Focus / Phantom
|
||||||
|
official_id: '202203222'
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
filename: dyn_illust_char_250_phatom_sale#4
|
|
||||||
logo: logo_victoria
|
|
||||||
fallback_name: char_250_phatom_sale#4
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 5
|
|
||||||
viewport_bottom: 1
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
|
||||||
zh-CN: 焦点 · 傀影
|
|
||||||
en-US: Focus / Phatom
|
|
||||||
use_json: false
|
|
||||||
official_id: '202203222'
|
|
||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4055_bgsnow_wild#7
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_4055_bgsnow_wild#7
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 字句中的雪原 · 鸿雪
|
zh-CN: 字句中的雪原 · 鸿雪
|
||||||
en-US: Snowy Plains in Words / Позёмка
|
en-US: Snowy Plains in Words / Позёмка/Pozemka
|
||||||
use_json: false
|
|
||||||
official_id: '202302698'
|
official_id: '202302698'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1020_reed2_epoque#30
|
|
||||||
logo: logo_dublinn
|
|
||||||
fallback_name: char_1020_reed2_epoque#30
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 博物 · 焰影苇草
|
zh-CN: 博物 · 焰影苇草
|
||||||
en-US: Curator / Reed The Flame Shadow
|
en-US: Curator / Reed The Flame Shadow
|
||||||
use_json: false
|
|
||||||
official_id: '202401871'
|
official_id: '202401871'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1020_reed2_summer#17
|
|
||||||
logo: logo_dublinn
|
|
||||||
fallback_name: char_1020_reed2_summer#17
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 夏卉 FA075 · 焰影苇草
|
zh-CN: 夏卉 FA075 · 焰影苇草
|
||||||
en-US: Summer Flowers FA075 / Reed The Flame Shadow
|
en-US: Summer Flowers FA075 / Reed The Flame Shadow
|
||||||
use_json: true
|
|
||||||
official_id: '202407051'
|
official_id: '202407051'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_391_rosmon
|
|
||||||
logo: logo_elite
|
|
||||||
fallback_name: char_391_rosmon_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: -14
|
|
||||||
viewport_top: -38
|
|
||||||
viewport_bottom: -1
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 迷迭香
|
zh-CN: 迷迭香
|
||||||
en-US: Rosmontis
|
en-US: Rosmontis
|
||||||
use_json: false
|
|
||||||
official_id: '20220378'
|
official_id: '20220378'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_391_rosmon_epoque#17
|
|
||||||
logo: logo_elite
|
|
||||||
fallback_name: char_391_rosmon_epoque#17
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 拥抱新生 · 迷迭香
|
zh-CN: 拥抱新生 · 迷迭香
|
||||||
en-US: Become Anew / Rosmontis
|
en-US: Become Anew / Rosmontis
|
||||||
use_json: false
|
|
||||||
official_id: '202210632'
|
official_id: '202210632'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2025_shu
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2025_shu_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 黍
|
zh-CN: 黍
|
||||||
en-US: Shu
|
en-US: Shu
|
||||||
use_json: false
|
|
||||||
official_id: '202401025'
|
official_id: '202401025'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2025_shu_nian#11
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2025_shu_nian#11
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 春日宴 · 黍
|
zh-CN: 春日宴 · 黍
|
||||||
en-US: Spring Feast / Shu
|
en-US: Spring Feast / Shu
|
||||||
use_json: false
|
|
||||||
official_id: '202501936'
|
official_id: '202501936'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_172_svrash_ambienceSynesthesia#4
|
|
||||||
logo: logo_kjerag
|
|
||||||
fallback_name: char_172_svrash_ambienceSynesthesia#4
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 不融冰 · 银灰
|
zh-CN: 不融冰 · 银灰
|
||||||
en-US: Never-Melting Ice / SilverAsh
|
en-US: Never-Melting Ice / SilverAsh
|
||||||
use_json: false
|
|
||||||
official_id: '202404066'
|
official_id: '202404066'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1012_skadi2
|
|
||||||
logo: logo_egir
|
|
||||||
fallback_name: char_1012_skadi2_2
|
|
||||||
viewport_left: -5
|
|
||||||
viewport_right: -10
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: -12
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 浊心斯卡蒂
|
zh-CN: 浊心斯卡蒂
|
||||||
en-US: Skadi the Corrupting Heart
|
en-US: Skadi the Corrupting Heart
|
||||||
use_json: false
|
|
||||||
official_id: '20220396'
|
official_id: '20220396'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1012_skadi2_boc#4
|
|
||||||
logo: logo_egir
|
|
||||||
fallback_name: char_1012_skadi2_boc#4
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 升华 · 浊心斯卡蒂
|
zh-CN: 升华 · 浊心斯卡蒂
|
||||||
en-US: Sublimation / Skadi the Corrupting Heart
|
en-US: Sublimation / Skadi the Corrupting Heart
|
||||||
use_json: false
|
|
||||||
official_id: '202204205'
|
official_id: '202204205'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1012_skadi2_iteration#2
|
|
||||||
logo: logo_egir
|
|
||||||
fallback_name: char_1012_skadi2_iteration#2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 红女爵 · 浊心斯卡蒂
|
zh-CN: 红女爵 · 浊心斯卡蒂
|
||||||
en-US: Red Countess / Skadi the Corrupting Heart
|
en-US: Red Countess / Skadi the Corrupting Heart
|
||||||
use_json: false
|
|
||||||
official_id: '202404008'
|
official_id: '202404008'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1023_ghost2
|
|
||||||
logo: logo_abyssal
|
|
||||||
fallback_name: char_1023_ghost2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 归溟幽灵鲨
|
zh-CN: 归溟幽灵鲨
|
||||||
en-US: Specter the Unchained
|
en-US: Specter the Unchained
|
||||||
use_json: false
|
|
||||||
official_id: '202204284'
|
official_id: '202204284'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1023_ghost2_boc#6
|
|
||||||
logo: logo_abyssal
|
|
||||||
fallback_name: char_1023_ghost2_boc#6
|
|
||||||
viewport_left: -1
|
|
||||||
viewport_right: 1
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 1
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 生而为一 · 归溟幽灵鲨
|
zh-CN: 生而为一 · 归溟幽灵鲨
|
||||||
en-US: Born as One / Specter the Unchained
|
en-US: Born as One / Specter the Unchained
|
||||||
use_json: false
|
|
||||||
official_id: '202304670'
|
official_id: '202304670'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_350_surtr_summer#9
|
|
||||||
logo: logo_rhodes_override
|
|
||||||
fallback_name: char_350_surtr_summer#9
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 6
|
|
||||||
viewport_top: 1
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: false
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 缤纷奇境 CW03 · 史尔特尔
|
zh-CN: 缤纷奇境 CW03 · 史尔特尔
|
||||||
en-US: Colorful Wonderland CW03 / Surtr
|
en-US: Colorful Wonderland CW03 / Surtr
|
||||||
use_json: false
|
|
||||||
official_id: '202208297'
|
official_id: '202208297'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1028_texas2
|
|
||||||
logo: logo_penguin
|
|
||||||
fallback_name: char_1028_texas2_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 缄默德克萨斯
|
zh-CN: 缄默德克萨斯
|
||||||
en-US: Texas the Omertosa
|
en-US: Texas the Omertosa
|
||||||
use_json: false
|
|
||||||
official_id: '202210210'
|
official_id: '202210210'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1028_texas2_epoque#36
|
|
||||||
logo: logo_penguin
|
|
||||||
fallback_name: char_1028_texas2_epoque#36
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 幽兰秘辛 · 缄默德克萨斯
|
zh-CN: 幽兰秘辛 · 缄默德克萨斯
|
||||||
en-US: Il Segreto Della Notte / Texas the Omertosa
|
en-US: Il Segreto Della Notte / Texas the Omertosa
|
||||||
use_json: false
|
|
||||||
official_id: '202410409'
|
official_id: '202410409'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1028_texas2_iteration#1
|
|
||||||
logo: logo_penguin
|
|
||||||
fallback_name: char_1028_texas2_iteration#1
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 破翼者 · 缄默德克萨斯
|
zh-CN: 破翼者 · 缄默德克萨斯
|
||||||
en-US: Wingbreaker / Texas the Omertosa
|
en-US: Wingbreaker / Texas the Omertosa
|
||||||
use_json: false
|
|
||||||
official_id: '202310899'
|
official_id: '202310899'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_245_cello
|
|
||||||
logo: logo_Laterano
|
|
||||||
fallback_name: char_245_cello_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 塑心
|
zh-CN: 塑心
|
||||||
en-US: Virtuosa
|
en-US: Virtuosa
|
||||||
use_json: false
|
|
||||||
official_id: '202310848'
|
official_id: '202310848'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_245_cello_sale#12
|
|
||||||
logo: logo_Laterano
|
|
||||||
fallback_name: char_245_cello_sale#12
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 无我唯识 · 塑心
|
zh-CN: 无我唯识 · 塑心
|
||||||
en-US: Diversity Oneness / Virtuosa
|
en-US: Diversity Oneness / Virtuosa
|
||||||
use_json: false
|
|
||||||
official_id: '202410467'
|
official_id: '202410467'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_113_cqbw
|
|
||||||
logo: logo_babel
|
|
||||||
fallback_name: char_113_cqbw_2
|
|
||||||
viewport_left: 3
|
|
||||||
viewport_right: -3
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 1
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: W
|
zh-CN: W
|
||||||
en-US: W
|
en-US: W
|
||||||
use_json: false
|
|
||||||
official_id: '20220319'
|
official_id: '20220319'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_113_cqbw_epoque#7
|
|
||||||
logo: logo_babel
|
|
||||||
fallback_name: char_113_cqbw_epoque#7
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 1
|
|
||||||
viewport_bottom: -4
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 恍惚 · W
|
zh-CN: 恍惚 · W
|
||||||
en-US: Wonder / W
|
en-US: Wonder / W
|
||||||
use_json: false
|
|
||||||
official_id: '202206246'
|
official_id: '202206246'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1035_wisdel
|
|
||||||
logo: logo_babel
|
|
||||||
fallback_name: char_1035_wisdel_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 维什戴尔
|
zh-CN: 维什戴尔
|
||||||
en-US: Wisadel
|
en-US: Wisadel
|
||||||
use_json: false
|
|
||||||
official_id: '202404049'
|
official_id: '202404049'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_1035_wisdel_sale#14
|
|
||||||
logo: logo_babel
|
|
||||||
fallback_name: char_1035_wisdel_sale#14
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 超新星 · 维什戴尔
|
zh-CN: 超新星 · 维什戴尔
|
||||||
en-US: Supernova / Wisadel
|
en-US: Supernova / Wisadel
|
||||||
use_json: false
|
|
||||||
official_id: '202504974'
|
official_id: '202504974'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_2026_yu
|
|
||||||
logo: logo_sui
|
|
||||||
fallback_name: char_2026_yu_2
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 余
|
zh-CN: 余
|
||||||
en-US: Yu
|
en-US: Yu
|
||||||
use_json: false
|
|
||||||
official_id: '202501414'
|
official_id: '202501414'
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
filename: dyn_illust_char_4121_zuole_nian#11
|
|
||||||
logo: logo_yan
|
|
||||||
fallback_name: char_4121_zuole_nian#11
|
|
||||||
viewport_left: 0
|
|
||||||
viewport_right: 0
|
|
||||||
viewport_top: 0
|
|
||||||
viewport_bottom: 0
|
|
||||||
invert_filter: true
|
|
||||||
codename:
|
codename:
|
||||||
zh-CN: 少年游 · 左乐
|
zh-CN: 少年游 · 左乐
|
||||||
en-US: Youthful Journey / Zuo Le
|
en-US: Youthful Journey / Zuo Le
|
||||||
use_json: false
|
|
||||||
official_id: '202501927'
|
official_id: '202501927'
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { stringify } from 'yaml'
|
|
||||||
import { yaml, file, alphaComposite } from '@aklive2d/libs'
|
import { yaml, file, alphaComposite } from '@aklive2d/libs'
|
||||||
import config from '@aklive2d/config'
|
import config from '@aklive2d/config'
|
||||||
import { mapping as officialInfoMapping } from '@aklive2d/official-info'
|
import { mapping as officialInfoMapping } from '@aklive2d/official-info'
|
||||||
import type { Config, PortraitHub, PortraitJson, AssetsJson } from './types.ts'
|
import type {
|
||||||
|
Config,
|
||||||
|
AssetsJson,
|
||||||
|
CharacterTableJson,
|
||||||
|
SkinTableJson,
|
||||||
|
} from './types.ts'
|
||||||
|
import {
|
||||||
|
findLogo,
|
||||||
|
findSkinEntry,
|
||||||
|
findSkel,
|
||||||
|
getActualFilename,
|
||||||
|
} from './libs/utils.ts'
|
||||||
|
|
||||||
|
export const AUTO_UPDATE_FOLDER = path.resolve(
|
||||||
|
import.meta.dirname,
|
||||||
|
config.dir_name.auto_update
|
||||||
|
)
|
||||||
|
|
||||||
export const CONFIG_PATH = path.resolve(
|
export const CONFIG_PATH = path.resolve(
|
||||||
import.meta.dirname,
|
import.meta.dirname,
|
||||||
@@ -14,78 +29,16 @@ export const OPERATOR_SOURCE_FOLDER = path.resolve(
|
|||||||
import.meta.dirname,
|
import.meta.dirname,
|
||||||
config.dir_name.data
|
config.dir_name.data
|
||||||
)
|
)
|
||||||
const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist)
|
export const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist)
|
||||||
|
export const CONFIG_FOLDER = path.resolve(
|
||||||
const getVoiceFolders = (name: string) => {
|
import.meta.dirname,
|
||||||
return config.dir_name.voice.sub.map((sub) =>
|
config.module.operator.config
|
||||||
path.join(
|
)
|
||||||
OPERATOR_SOURCE_FOLDER,
|
|
||||||
name,
|
|
||||||
config.dir_name.voice.main,
|
|
||||||
sub.name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getExtractedFolder = (name: string) => {
|
|
||||||
return path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.extracted)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getConfigFolder = () => {
|
|
||||||
return path.join(import.meta.dirname, config.module.operator.config)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDistFolder = (name: string) => {
|
|
||||||
return path.join(DIST_DIR, config.module.operator.operator, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const has = (name: string) => {
|
export const has = (name: string) => {
|
||||||
return Object.keys(operators).includes(name)
|
return Object.keys(operators).includes(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateMapping = () => {
|
|
||||||
if (officialInfoMapping) {
|
|
||||||
for (const [operatorName, operator] of Object.entries(CONFIG)) {
|
|
||||||
const operatorInfo = officialInfoMapping[operator.official_id]
|
|
||||||
// add title
|
|
||||||
operator.title = `${config.module.operator.title['en-US']}${operator.codename['en-US']} - ${config.module.operator.title['zh-CN']}${operator.codename['zh-CN']}`
|
|
||||||
// add type
|
|
||||||
operator.type = operatorInfo.type
|
|
||||||
|
|
||||||
// add link
|
|
||||||
operator.link = operatorName
|
|
||||||
|
|
||||||
// id
|
|
||||||
operator.id = getOperatorId(operator.filename).replace(
|
|
||||||
/^(char_)(\d+)(_.+)$/g,
|
|
||||||
'$2'
|
|
||||||
)
|
|
||||||
|
|
||||||
operator.date = operatorInfo.date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CONFIG
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyVoices = (name: string) => {
|
|
||||||
file.symlinkAll(
|
|
||||||
path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.voice.main),
|
|
||||||
path.join(DIST_DIR, config.dir_name.voice.main, name)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyLogos = () => {
|
|
||||||
file.symlink(
|
|
||||||
path.join(OPERATOR_SOURCE_FOLDER, config.module.operator.logos_assets),
|
|
||||||
path.join(DIST_DIR, config.module.operator.logos)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const operators = generateMapping()
|
|
||||||
|
|
||||||
export default operators
|
|
||||||
|
|
||||||
export function getOperatorId(name: string, matcher = '$2$3$4') {
|
export function getOperatorId(name: string, matcher = '$2$3$4') {
|
||||||
return name.replace(/^(.*)(char_[\d]+)(_[A-Za-z0-9]+)(|_.*)$/g, matcher)
|
return name.replace(/^(.*)(char_[\d]+)(_[A-Za-z0-9]+)(|_.*)$/g, matcher)
|
||||||
}
|
}
|
||||||
@@ -94,125 +47,28 @@ export const getOperatorAlternativeId = (id: string) => {
|
|||||||
return getOperatorId(id, '$2$3')
|
return getOperatorId(id, '$2$3')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const build = async (namesToBuild: string[]) => {
|
|
||||||
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
|
|
||||||
console.log('Generating assets for', names.length, 'operators')
|
|
||||||
for (const name of names) {
|
|
||||||
await generateAssets(name)
|
|
||||||
copyVoices(name)
|
|
||||||
}
|
|
||||||
copyLogos()
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateAssets = async (name: string) => {
|
|
||||||
const extractedDir = getExtractedFolder(name)
|
|
||||||
const outDir = getDistFolder(name)
|
|
||||||
file.rmdir(outDir)
|
|
||||||
file.mkdir(outDir)
|
|
||||||
|
|
||||||
const fallback_name = operators[name].fallback_name
|
|
||||||
const fallbackFilename = `${fallback_name}.png`
|
|
||||||
const alphaCompositeFilename = `${path.parse(fallbackFilename).name}[alpha].png`
|
|
||||||
if (file.exists(path.join(extractedDir, alphaCompositeFilename))) {
|
|
||||||
const fallbackBuffer = await alphaComposite.process(
|
|
||||||
fallbackFilename,
|
|
||||||
alphaCompositeFilename,
|
|
||||||
extractedDir
|
|
||||||
)
|
|
||||||
file.writeSync(
|
|
||||||
fallbackBuffer,
|
|
||||||
path.join(getDistFolder(name), fallbackFilename)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
await file.copy(
|
|
||||||
path.join(extractedDir, fallbackFilename),
|
|
||||||
path.join(getDistFolder(name), fallbackFilename)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate portrait
|
|
||||||
const portraitDir = path.join(
|
|
||||||
OPERATOR_SOURCE_FOLDER,
|
|
||||||
config.module.operator.portraits
|
|
||||||
)
|
|
||||||
const portraitHubContent = file.readSync(
|
|
||||||
path.join(
|
|
||||||
portraitDir,
|
|
||||||
config.module.operator.MonoBehaviour,
|
|
||||||
'portrait_hub.json'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (!portraitHubContent) throw new Error('portrait_hub.json not found')
|
|
||||||
const portraitHub: PortraitHub = JSON.parse(portraitHubContent)
|
|
||||||
const fallback_name_lowerCase = fallback_name.toLowerCase()
|
|
||||||
const portraitItem = portraitHub._sprites.find(
|
|
||||||
(item) => item.name.toLowerCase() === fallback_name_lowerCase
|
|
||||||
)
|
|
||||||
if (!portraitItem) throw new Error(`portrait ${fallback_name} not found`)
|
|
||||||
const portraitAtlas = portraitItem.atlas
|
|
||||||
const portraitJsonText = file.readSync(
|
|
||||||
path.join(
|
|
||||||
portraitDir,
|
|
||||||
config.module.operator.MonoBehaviour,
|
|
||||||
`portraits#${portraitAtlas}.json`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (!portraitJsonText)
|
|
||||||
throw new Error(`portrait ${fallback_name} json not found`)
|
|
||||||
const portraitJson: PortraitJson = JSON.parse(portraitJsonText)
|
|
||||||
const item = portraitJson._sprites.find(
|
|
||||||
(item) => item.name.toLowerCase() === fallback_name_lowerCase
|
|
||||||
)
|
|
||||||
if (!item) throw new Error(`portrait ${fallback_name} not found`)
|
|
||||||
const rect = {
|
|
||||||
...item.rect,
|
|
||||||
rotate: item.rotate,
|
|
||||||
}
|
|
||||||
const protraitFilename = `portraits#${portraitAtlas}.png`
|
|
||||||
const portraitBuffer = await alphaComposite.process(
|
|
||||||
protraitFilename,
|
|
||||||
`${path.parse(protraitFilename).name}a.png`,
|
|
||||||
path.join(portraitDir, config.module.operator.Texture2D)
|
|
||||||
)
|
|
||||||
const croppedBuffer = await alphaComposite.crop(portraitBuffer, rect)
|
|
||||||
file.writeSync(
|
|
||||||
croppedBuffer,
|
|
||||||
path.join(getDistFolder(name), `${fallback_name}_portrait.png`)
|
|
||||||
)
|
|
||||||
|
|
||||||
await generateAssetsJson(
|
|
||||||
operators[name].filename,
|
|
||||||
extractedDir,
|
|
||||||
getDistFolder(name),
|
|
||||||
{
|
|
||||||
useJSON: operators[name].use_json,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateAssetsJson = async (
|
export const generateAssetsJson = async (
|
||||||
filename: string,
|
filename: string,
|
||||||
extractedDir: string,
|
extractedDir: string,
|
||||||
targetDir: string,
|
targetDir: string,
|
||||||
_opts: {
|
_opts: {
|
||||||
useJSON?: boolean
|
|
||||||
useSymLink?: boolean
|
useSymLink?: boolean
|
||||||
} = {
|
} = {
|
||||||
useJSON: false,
|
|
||||||
useSymLink: true,
|
useSymLink: true,
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const assetsJson: AssetsJson = []
|
const assetsJson: AssetsJson = []
|
||||||
|
|
||||||
let skelFilename
|
/*
|
||||||
if (_opts.useJSON) {
|
* Special Cases:
|
||||||
skelFilename = `${filename}.json`
|
* - ines_melodic_flutter
|
||||||
} else {
|
*/
|
||||||
skelFilename = `${filename}.skel`
|
filename = getActualFilename(filename, extractedDir)
|
||||||
}
|
|
||||||
|
const skelFilename = findSkel(filename, extractedDir)
|
||||||
const atlasFilename = `${filename}.atlas`
|
const atlasFilename = `${filename}.atlas`
|
||||||
const atlasPath = path.join(extractedDir, atlasFilename)
|
const atlasPath = path.join(extractedDir, atlasFilename)
|
||||||
let atlas = await file.read(atlasPath)
|
let atlas = file.readSync(atlasPath) as string
|
||||||
const matches = atlas.match(new RegExp(/(.*).png/g))
|
const matches = atlas.match(new RegExp(/(.*).png/g))
|
||||||
if (!matches)
|
if (!matches)
|
||||||
throw new Error(`No matches found in atlas file ${atlasFilename}`)
|
throw new Error(`No matches found in atlas file ${atlasFilename}`)
|
||||||
@@ -258,31 +114,82 @@ export const generateAssetsJson = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const init = (name: string, id: string) => {
|
const characterTable = (() => {
|
||||||
const voiceFolders = getVoiceFolders(name)
|
const character_table_json = path.resolve(
|
||||||
const extractedFolder = getExtractedFolder(name)
|
AUTO_UPDATE_FOLDER,
|
||||||
const operatorConfigFolder = getConfigFolder()
|
config.module.operator.character_table_json
|
||||||
const foldersToCreate = [extractedFolder, ...voiceFolders]
|
|
||||||
|
|
||||||
const template = yaml.read(
|
|
||||||
path.resolve(operatorConfigFolder, config.module.operator.template_yaml)
|
|
||||||
)
|
)
|
||||||
foldersToCreate.forEach((dir) => {
|
const t = file.readSync(character_table_json, {
|
||||||
file.mkdir(dir)
|
useAsPrefix: true,
|
||||||
})
|
}) as string
|
||||||
const currentOpertor = officialInfoMapping[id]
|
if (!t) throw new Error('character_table.json not found')
|
||||||
if (currentOpertor === undefined) {
|
return JSON.parse(t) as CharacterTableJson
|
||||||
throw new Error('Invalid operator id')
|
})()
|
||||||
|
|
||||||
|
export const skinTable = (() => {
|
||||||
|
const skinTable = path.resolve(
|
||||||
|
AUTO_UPDATE_FOLDER,
|
||||||
|
config.module.operator.skin_table_json
|
||||||
|
)
|
||||||
|
const t = file.readSync(skinTable, {
|
||||||
|
useAsPrefix: true,
|
||||||
|
}) as string
|
||||||
|
if (!t) throw new Error('skin_table.json not found')
|
||||||
|
return JSON.parse(t) as SkinTableJson
|
||||||
|
})()
|
||||||
|
|
||||||
|
const generateMapping = () => {
|
||||||
|
if (officialInfoMapping) {
|
||||||
|
for (const [operatorName, operator] of Object.entries(CONFIG)) {
|
||||||
|
const operatorInfo = officialInfoMapping[operator.official_id]
|
||||||
|
const type = operatorInfo.type
|
||||||
|
const name =
|
||||||
|
type === 'skin'
|
||||||
|
? operatorInfo.codename['zh-CN'].split(' · ')[0]
|
||||||
|
: operatorInfo.codename['en-US']
|
||||||
|
const skinEntry = findSkinEntry(skinTable, name, type)
|
||||||
|
operator.filename = skinEntry.dynIllustId.replace(/_2$/, '')
|
||||||
|
operator.fallback_name =
|
||||||
|
type === 'skin'
|
||||||
|
? skinEntry.skinId.replace(/@/, '_')
|
||||||
|
: `${skinEntry.charId}_2`
|
||||||
|
// add title
|
||||||
|
operator.title = `${config.module.operator.title['en-US']}${operator.codename['en-US']} - ${config.module.operator.title['zh-CN']}${operator.codename['zh-CN']}`
|
||||||
|
|
||||||
|
// add type
|
||||||
|
operator.type = operatorInfo.type
|
||||||
|
|
||||||
|
// add link
|
||||||
|
operator.link = operatorName
|
||||||
|
|
||||||
|
// add default viewport
|
||||||
|
operator.viewport_left = 0
|
||||||
|
operator.viewport_right = 0
|
||||||
|
operator.viewport_top = 0
|
||||||
|
operator.viewport_bottom = 0
|
||||||
|
|
||||||
|
operator.voice_id = skinEntry.voiceId
|
||||||
|
|
||||||
|
const logo = findLogo(characterTable, skinEntry.charId)
|
||||||
|
operator.logo = logo.logo
|
||||||
|
operator.invert_filter = logo.invert_filter
|
||||||
|
|
||||||
|
operator.color =
|
||||||
|
skinEntry.displaySkin.colorList.find((e) => e !== '') || '#000'
|
||||||
|
|
||||||
|
// id
|
||||||
|
operator.id = getOperatorId(operator.filename).replace(
|
||||||
|
/^(char_)(\d+)(_.+)$/g,
|
||||||
|
'$2'
|
||||||
|
)
|
||||||
|
|
||||||
|
operator.date = operatorInfo.date
|
||||||
|
}
|
||||||
}
|
}
|
||||||
template.official_id = currentOpertor.id
|
|
||||||
template.codename = currentOpertor.codename
|
|
||||||
|
|
||||||
file.writeSync(
|
return CONFIG
|
||||||
stringify(template),
|
|
||||||
path.resolve(operatorConfigFolder, `${name}.yaml`)
|
|
||||||
)
|
|
||||||
file.appendSync(
|
|
||||||
`${name}: !include ${config.module.operator.config}/${name}.yaml\n`,
|
|
||||||
CONFIG_PATH
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const operators = generateMapping()
|
||||||
|
|
||||||
|
export default operators
|
||||||
|
|||||||
117
packages/operator/libs/builder.ts
Normal file
117
packages/operator/libs/builder.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import config from '@aklive2d/config'
|
||||||
|
import { file, alphaComposite } from '@aklive2d/libs'
|
||||||
|
import operators, {
|
||||||
|
DIST_DIR,
|
||||||
|
OPERATOR_SOURCE_FOLDER,
|
||||||
|
generateAssetsJson,
|
||||||
|
} from '../index.ts'
|
||||||
|
import { getExtractedFolder, getDistFolder } from './utils.ts'
|
||||||
|
import type { PortraitHub, PortraitJson } from '../types.ts'
|
||||||
|
|
||||||
|
export const build = async (namesToBuild: string[]) => {
|
||||||
|
const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild
|
||||||
|
console.log('Generating assets for', names.length, 'operators')
|
||||||
|
for (const name of names) {
|
||||||
|
await generateAssets(name)
|
||||||
|
copyVoices(name)
|
||||||
|
}
|
||||||
|
copyLogos()
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyVoices = (name: string) => {
|
||||||
|
file.symlinkAll(
|
||||||
|
path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.voice.main),
|
||||||
|
path.join(DIST_DIR, config.dir_name.voice.main, name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyLogos = () => {
|
||||||
|
file.symlink(
|
||||||
|
path.join(OPERATOR_SOURCE_FOLDER, config.module.operator.logos_assets),
|
||||||
|
path.join(DIST_DIR, config.module.operator.logos)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateAssets = async (name: string) => {
|
||||||
|
const extractedDir = getExtractedFolder(name)
|
||||||
|
const outDir = getDistFolder(name)
|
||||||
|
file.rmdir(outDir)
|
||||||
|
file.mkdir(outDir)
|
||||||
|
|
||||||
|
const fallback_name = operators[name].fallback_name
|
||||||
|
const fallbackFilename = `${fallback_name}.png`
|
||||||
|
const alphaCompositeFilename = `${path.parse(fallbackFilename).name}[alpha].png`
|
||||||
|
if (file.exists(path.join(extractedDir, alphaCompositeFilename))) {
|
||||||
|
const fallbackBuffer = await alphaComposite.process(
|
||||||
|
fallbackFilename,
|
||||||
|
alphaCompositeFilename,
|
||||||
|
extractedDir
|
||||||
|
)
|
||||||
|
file.writeSync(
|
||||||
|
fallbackBuffer,
|
||||||
|
path.join(getDistFolder(name), fallbackFilename)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await file.copy(
|
||||||
|
path.join(extractedDir, fallbackFilename),
|
||||||
|
path.join(getDistFolder(name), fallbackFilename)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate portrait
|
||||||
|
const portraitDir = path.join(
|
||||||
|
OPERATOR_SOURCE_FOLDER,
|
||||||
|
config.module.operator.portraits
|
||||||
|
)
|
||||||
|
const portraitHubContent = file.readSync(
|
||||||
|
path.join(
|
||||||
|
portraitDir,
|
||||||
|
config.module.operator.MonoBehaviour,
|
||||||
|
'portrait_hub.json'
|
||||||
|
)
|
||||||
|
) as string
|
||||||
|
if (!portraitHubContent) throw new Error('portrait_hub.json not found')
|
||||||
|
const portraitHub: PortraitHub = JSON.parse(portraitHubContent)
|
||||||
|
const fallback_name_lowerCase = fallback_name.toLowerCase()
|
||||||
|
const portraitItem = portraitHub._sprites.find(
|
||||||
|
(item) => item.name.toLowerCase() === fallback_name_lowerCase
|
||||||
|
)
|
||||||
|
if (!portraitItem) throw new Error(`portrait ${fallback_name} not found`)
|
||||||
|
const portraitAtlas = portraitItem.atlas
|
||||||
|
const portraitJsonText = file.readSync(
|
||||||
|
path.join(
|
||||||
|
portraitDir,
|
||||||
|
config.module.operator.MonoBehaviour,
|
||||||
|
`portraits#${portraitAtlas}.json`
|
||||||
|
)
|
||||||
|
) as string
|
||||||
|
if (!portraitJsonText)
|
||||||
|
throw new Error(`portrait ${fallback_name} json not found`)
|
||||||
|
const portraitJson: PortraitJson = JSON.parse(portraitJsonText)
|
||||||
|
const item = portraitJson._sprites.find(
|
||||||
|
(item) => item.name.toLowerCase() === fallback_name_lowerCase
|
||||||
|
)
|
||||||
|
if (!item) throw new Error(`portrait ${fallback_name} not found`)
|
||||||
|
const rect = {
|
||||||
|
...item.rect,
|
||||||
|
rotate: item.rotate,
|
||||||
|
}
|
||||||
|
const protraitFilename = `portraits#${portraitAtlas}.png`
|
||||||
|
const portraitBuffer = await alphaComposite.process(
|
||||||
|
protraitFilename,
|
||||||
|
`${path.parse(protraitFilename).name}a.png`,
|
||||||
|
path.join(portraitDir, config.module.operator.Texture2D)
|
||||||
|
)
|
||||||
|
const croppedBuffer = await alphaComposite.crop(portraitBuffer, rect)
|
||||||
|
file.writeSync(
|
||||||
|
croppedBuffer,
|
||||||
|
path.join(getDistFolder(name), `${fallback_name}_portrait.png`)
|
||||||
|
)
|
||||||
|
|
||||||
|
await generateAssetsJson(
|
||||||
|
operators[name].filename,
|
||||||
|
extractedDir,
|
||||||
|
getDistFolder(name)
|
||||||
|
)
|
||||||
|
}
|
||||||
55
packages/operator/libs/initer.ts
Normal file
55
packages/operator/libs/initer.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import { stringify } from 'yaml'
|
||||||
|
import { yaml, file } from '@aklive2d/libs'
|
||||||
|
import config from '@aklive2d/config'
|
||||||
|
import { mapping as officialInfoMapping } from '@aklive2d/official-info'
|
||||||
|
import { CONFIG_PATH, skinTable, CONFIG_FOLDER } from '../index.ts'
|
||||||
|
import {
|
||||||
|
getVoiceFolders,
|
||||||
|
getExtractedFolder,
|
||||||
|
findSkinEntry,
|
||||||
|
findCodename,
|
||||||
|
} from './utils.ts'
|
||||||
|
|
||||||
|
export const init = (name: string, id: string) => {
|
||||||
|
const voiceFolders = getVoiceFolders(name)
|
||||||
|
const extractedFolder = getExtractedFolder(name)
|
||||||
|
const operatorConfigFolder = CONFIG_FOLDER
|
||||||
|
const foldersToCreate = [extractedFolder, ...voiceFolders]
|
||||||
|
|
||||||
|
const template = yaml.read(
|
||||||
|
path.resolve(operatorConfigFolder, config.module.operator.template_yaml)
|
||||||
|
)
|
||||||
|
foldersToCreate.forEach((dir) => {
|
||||||
|
file.mkdir(dir)
|
||||||
|
})
|
||||||
|
const currentOpertor = officialInfoMapping[id]
|
||||||
|
if (currentOpertor === undefined) {
|
||||||
|
throw new Error('Invalid operator id')
|
||||||
|
}
|
||||||
|
template.official_id = currentOpertor.id
|
||||||
|
try {
|
||||||
|
const entryName =
|
||||||
|
currentOpertor.type === 'skin'
|
||||||
|
? currentOpertor.codename['zh-CN'].split(' · ')[0]
|
||||||
|
: currentOpertor.codename['en-US']
|
||||||
|
const skinEntry = findSkinEntry(
|
||||||
|
skinTable,
|
||||||
|
entryName,
|
||||||
|
currentOpertor.type
|
||||||
|
)
|
||||||
|
template.codename = findCodename(skinEntry, currentOpertor)
|
||||||
|
} catch (e: unknown) {
|
||||||
|
console.log(e as string)
|
||||||
|
template.codename = currentOpertor.codename
|
||||||
|
}
|
||||||
|
|
||||||
|
file.writeSync(
|
||||||
|
stringify(template),
|
||||||
|
path.resolve(operatorConfigFolder, `${name}.yaml`)
|
||||||
|
)
|
||||||
|
file.appendSync(
|
||||||
|
`${name}: !include ${config.module.operator.config}/${name}.yaml\n`,
|
||||||
|
CONFIG_PATH
|
||||||
|
)
|
||||||
|
}
|
||||||
25
packages/operator/libs/updater.ts
Normal file
25
packages/operator/libs/updater.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import { githubDownload } from '@aklive2d/downloader'
|
||||||
|
import config from '@aklive2d/config'
|
||||||
|
import { AUTO_UPDATE_FOLDER } from '../index.ts'
|
||||||
|
|
||||||
|
export const update = async () => {
|
||||||
|
const character_table_json = path.resolve(
|
||||||
|
AUTO_UPDATE_FOLDER,
|
||||||
|
config.module.operator.character_table_json
|
||||||
|
)
|
||||||
|
const skin_table_json = path.resolve(
|
||||||
|
AUTO_UPDATE_FOLDER,
|
||||||
|
config.module.operator.skin_table_json
|
||||||
|
)
|
||||||
|
await githubDownload(
|
||||||
|
`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/character_table.json`,
|
||||||
|
`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/character_table.json`,
|
||||||
|
character_table_json
|
||||||
|
)
|
||||||
|
await githubDownload(
|
||||||
|
`https://api.github.com/repos/Kengxxiao/ArknightsGameData/commits?path=zh_CN/gamedata/excel/skin_table.json`,
|
||||||
|
`https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/skin_table.json`,
|
||||||
|
skin_table_json
|
||||||
|
)
|
||||||
|
}
|
||||||
204
packages/operator/libs/utils.ts
Normal file
204
packages/operator/libs/utils.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import unidecode from 'unidecode'
|
||||||
|
import config from '@aklive2d/config'
|
||||||
|
import { DIST_DIR, OPERATOR_SOURCE_FOLDER } from '../index.ts'
|
||||||
|
import { file } from '@aklive2d/libs'
|
||||||
|
import {
|
||||||
|
CharacterTableJson,
|
||||||
|
SkinTableJson,
|
||||||
|
OperatorEntryType,
|
||||||
|
SkinTableJsonCharSkinEntry,
|
||||||
|
Codename,
|
||||||
|
} from '../types.ts'
|
||||||
|
import type { OfficialInfoOperatorConfig } from '@aklive2d/official-info/types'
|
||||||
|
|
||||||
|
export const getExtractedFolder = (name: string) => {
|
||||||
|
return path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.extracted)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getDistFolder = (name: string) => {
|
||||||
|
return path.join(DIST_DIR, config.module.operator.operator, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getVoiceFolders = (name: string) => {
|
||||||
|
return config.dir_name.voice.sub.map((sub) =>
|
||||||
|
path.join(
|
||||||
|
OPERATOR_SOURCE_FOLDER,
|
||||||
|
name,
|
||||||
|
config.dir_name.voice.main,
|
||||||
|
sub.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findLogo = (characterTable: CharacterTableJson, id: string) => {
|
||||||
|
const SEARCH_ORDER = ['teamId', 'groupId', 'nationId'] as const
|
||||||
|
let logo: string | null = null
|
||||||
|
const entry = characterTable[id]
|
||||||
|
if (!entry) throw new Error(`Character ${id} not found`)
|
||||||
|
for (const key of SEARCH_ORDER) {
|
||||||
|
logo = entry[key]
|
||||||
|
if (logo) break
|
||||||
|
}
|
||||||
|
if (logo === null) throw new Error(`Logo not found for character ${id}`)
|
||||||
|
let invert_filter = true
|
||||||
|
if (logo === 'rhodes') {
|
||||||
|
logo = 'rhodes_override'
|
||||||
|
invert_filter = false
|
||||||
|
}
|
||||||
|
logo = `logo_${logo}`
|
||||||
|
const logoFiles = file
|
||||||
|
.readdirSync(
|
||||||
|
path.resolve(
|
||||||
|
OPERATOR_SOURCE_FOLDER,
|
||||||
|
config.module.operator.logos_assets
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((e) => {
|
||||||
|
const name = path.parse(e).name
|
||||||
|
return {
|
||||||
|
file: name,
|
||||||
|
name: name.toLowerCase(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const logoFile = logoFiles.find((f) => f.name === logo)
|
||||||
|
if (!logoFile)
|
||||||
|
throw new Error(`Logo file not found for character ${id}, ${logo}`)
|
||||||
|
logo = logoFile.file
|
||||||
|
|
||||||
|
return {
|
||||||
|
logo,
|
||||||
|
invert_filter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findSkinEntry = (
|
||||||
|
skinTableJson: SkinTableJson,
|
||||||
|
name: string,
|
||||||
|
type: OperatorEntryType
|
||||||
|
) => {
|
||||||
|
const OVERWRITE_ENTRIES = {
|
||||||
|
WISADEL: "Wiš'adel",
|
||||||
|
} as const
|
||||||
|
type OverwriteKeys = keyof typeof OVERWRITE_ENTRIES
|
||||||
|
|
||||||
|
name = OVERWRITE_ENTRIES[name as OverwriteKeys] ?? name
|
||||||
|
const dynSkinEntries = Object.values(skinTableJson.charSkins).filter(
|
||||||
|
(entry) => entry.dynIllustId !== null
|
||||||
|
)
|
||||||
|
let entry: SkinTableJsonCharSkinEntry | undefined
|
||||||
|
if (type === 'operator') {
|
||||||
|
entry = dynSkinEntries.find(
|
||||||
|
(e) => e.displaySkin.modelName?.toLowerCase() === name.toLowerCase()
|
||||||
|
)
|
||||||
|
} else if (type === 'skin') {
|
||||||
|
entry = dynSkinEntries.find(
|
||||||
|
(e) => e.displaySkin.skinName?.toLowerCase() === name.toLowerCase()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw new Error(`Invalid type: ${type}`)
|
||||||
|
}
|
||||||
|
if (!entry) throw new Error(`Skin entry not found for ${name}`)
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name from Official Info sometimes is incorrect, can only be used as a
|
||||||
|
* reference
|
||||||
|
* @param skinEntry
|
||||||
|
* @param officialInfo
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const findCodename = (
|
||||||
|
skinEntry: SkinTableJsonCharSkinEntry,
|
||||||
|
officialInfo: OfficialInfoOperatorConfig
|
||||||
|
) => {
|
||||||
|
const UPPER_CASE_EXCEPTION_WORDS = [
|
||||||
|
'the',
|
||||||
|
'is',
|
||||||
|
'of',
|
||||||
|
'and',
|
||||||
|
'for',
|
||||||
|
'a',
|
||||||
|
'an',
|
||||||
|
'are',
|
||||||
|
'in',
|
||||||
|
'as',
|
||||||
|
]
|
||||||
|
const codename: Codename = { 'zh-CN': '', 'en-US': '' }
|
||||||
|
const regexp = /[^(\w) ]/
|
||||||
|
let modelNameNormalized = skinEntry.displaySkin.modelName
|
||||||
|
const hasSpecialCharInModelName = regexp.test(
|
||||||
|
skinEntry.displaySkin.modelName
|
||||||
|
)
|
||||||
|
if (hasSpecialCharInModelName) {
|
||||||
|
modelNameNormalized = unidecode(modelNameNormalized).replace(regexp, '')
|
||||||
|
const modelNameArray = skinEntry.displaySkin.modelName.split(' ')
|
||||||
|
const modelNameNormalizedArray = modelNameNormalized.split(' ')
|
||||||
|
modelNameArray.forEach((word, index) => {
|
||||||
|
if (word !== modelNameNormalizedArray[index]) {
|
||||||
|
modelNameNormalizedArray[index] =
|
||||||
|
`${word}/${modelNameNormalizedArray[index]}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
modelNameNormalized = modelNameNormalizedArray.join(' ')
|
||||||
|
}
|
||||||
|
if (skinEntry.displaySkin.skinName) {
|
||||||
|
let engSkinName = officialInfo.codename['en-US']
|
||||||
|
const engkinNameArray = engSkinName.split(' ')
|
||||||
|
engkinNameArray.forEach((word, index) => {
|
||||||
|
if (/^[a-zA-Z]+$/.test(word)) {
|
||||||
|
word = word.toLowerCase()
|
||||||
|
if (UPPER_CASE_EXCEPTION_WORDS.includes(word)) {
|
||||||
|
engkinNameArray[index] = word
|
||||||
|
} else {
|
||||||
|
engkinNameArray[index] =
|
||||||
|
word[0].toUpperCase() + word.slice(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
engSkinName = engkinNameArray.join(' ')
|
||||||
|
codename['zh-CN'] = officialInfo.codename['zh-CN'].replace(/ +$/, '')
|
||||||
|
codename['en-US'] = `${engSkinName} / ${modelNameNormalized}`
|
||||||
|
} else {
|
||||||
|
codename['zh-CN'] = officialInfo.codename['zh-CN'].replace(/ +$/, '')
|
||||||
|
codename['en-US'] = modelNameNormalized
|
||||||
|
}
|
||||||
|
return codename
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getActualFilename = (filename: string, dir: string) => {
|
||||||
|
const files = file.readdirSync(dir)
|
||||||
|
const actualFilename = files.find((e) => {
|
||||||
|
const name = path.parse(e).name
|
||||||
|
return filename.startsWith(name) && !name.endsWith('_Start')
|
||||||
|
})
|
||||||
|
return actualFilename ? path.parse(actualFilename).name : filename
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findSkel = (name: string, dir: string) => {
|
||||||
|
const files = file.readdirSync(dir)
|
||||||
|
const skel = files.find((e) => {
|
||||||
|
const actualName = path.parse(e)
|
||||||
|
return (
|
||||||
|
name.startsWith(actualName.name) &&
|
||||||
|
!actualName.name.endsWith('_Start') &&
|
||||||
|
actualName.ext === '.skel'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const json = files.find((e) => {
|
||||||
|
const actualName = path.parse(e)
|
||||||
|
return (
|
||||||
|
name.startsWith(actualName.name) &&
|
||||||
|
!actualName.name.endsWith('_Start') &&
|
||||||
|
actualName.ext === '.json'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if (skel) {
|
||||||
|
return skel
|
||||||
|
} else if (json) {
|
||||||
|
return json
|
||||||
|
} else {
|
||||||
|
throw new Error('No skel or json file found')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,13 @@
|
|||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yaml": "^2.7.0",
|
|
||||||
"@aklive2d/libs": "workspace:*",
|
|
||||||
"@aklive2d/config": "workspace:*",
|
"@aklive2d/config": "workspace:*",
|
||||||
"@aklive2d/official-info": "workspace:*",
|
|
||||||
"@aklive2d/eslint-config": "workspace:*",
|
"@aklive2d/eslint-config": "workspace:*",
|
||||||
"@aklive2d/prettier-config": "workspace:*"
|
"@aklive2d/libs": "workspace:*",
|
||||||
|
"@aklive2d/official-info": "workspace:*",
|
||||||
|
"@aklive2d/prettier-config": "workspace:*",
|
||||||
|
"unidecode": "^1.1.0",
|
||||||
|
"yaml": "^2.7.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"globals": ">=16.0.0",
|
"globals": ">=16.0.0",
|
||||||
@@ -18,9 +19,13 @@
|
|||||||
"typescript": ">=5.8.2"
|
"typescript": ">=5.8.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"update": "mode=update bun runner.ts",
|
||||||
"build": "mode=build bun runner.ts",
|
"build": "mode=build bun runner.ts",
|
||||||
"init": "mode=init bun runner.ts",
|
"init": "mode=init bun runner.ts",
|
||||||
"lint": "eslint && prettier --check .",
|
"lint": "eslint && prettier --check .",
|
||||||
"build:cleanup": "rm -rf ./dist ./data"
|
"build:cleanup": "rm -rf ./dist ./data"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/unidecode": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { envParser } from '@aklive2d/libs'
|
import { envParser } from '@aklive2d/libs'
|
||||||
import { build, init } from './index.ts'
|
import { init } from './libs/initer.ts'
|
||||||
|
import { build } from './libs/builder.ts'
|
||||||
|
import { update } from './libs/updater.ts'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
mode: string
|
mode: string
|
||||||
@@ -27,6 +29,9 @@ async function main() {
|
|||||||
case 'build':
|
case 'build':
|
||||||
await build(name)
|
await build(name)
|
||||||
break
|
break
|
||||||
|
case 'update':
|
||||||
|
await update()
|
||||||
|
break
|
||||||
case 'init':
|
case 'init':
|
||||||
if (!name.length) {
|
if (!name.length) {
|
||||||
throw new Error('Please set the operator name.')
|
throw new Error('Please set the operator name.')
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
export type Codename = { 'zh-CN': string; 'en-US': string }
|
export type Codename = { 'zh-CN': string; 'en-US': string }
|
||||||
|
|
||||||
|
export type OperatorEntryType = 'operator' | 'skin'
|
||||||
|
|
||||||
export interface OperatorConfig {
|
export interface OperatorConfig {
|
||||||
filename: string
|
filename: string
|
||||||
logo: string
|
logo: string
|
||||||
fallback_name: string
|
fallback_name: string
|
||||||
viewport_left: number
|
viewport_left: number // should be default to 0 in the future
|
||||||
viewport_right: number
|
viewport_right: number
|
||||||
viewport_top: number
|
viewport_top: number
|
||||||
viewport_bottom: number
|
viewport_bottom: number
|
||||||
invert_filter: boolean
|
invert_filter: boolean
|
||||||
codename: Codename
|
codename: Codename
|
||||||
use_json: boolean
|
use_json: boolean // can be detected automatically
|
||||||
official_id: string
|
official_id: string // reverse lookup from official_update?
|
||||||
title: string
|
title: string
|
||||||
type: 'operator' | 'skin'
|
type: OperatorEntryType
|
||||||
link: string
|
link: string
|
||||||
id: string
|
id: string // used in directory
|
||||||
date: string
|
date: string
|
||||||
|
voice_id: string | null
|
||||||
|
color: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
@@ -91,3 +95,40 @@ export type AssetsJson = {
|
|||||||
path?: string
|
path?: string
|
||||||
content?: string | Buffer<ArrayBufferLike> | Buffer
|
content?: string | Buffer<ArrayBufferLike> | Buffer
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum type for character_table.json
|
||||||
|
* Implmented for:
|
||||||
|
* - findLogo
|
||||||
|
*/
|
||||||
|
export type CharacterTableJson = {
|
||||||
|
[id: string]: {
|
||||||
|
name: string // operator chinese name
|
||||||
|
appellation: string // operator english name
|
||||||
|
nationId: string // class i logo classifier
|
||||||
|
groupId: string | null // class ii logo classifier
|
||||||
|
teamId: string | null // class iii logo classifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum type for skin_table.json
|
||||||
|
* Intended for:
|
||||||
|
* - replacing OperatorConfig.filename
|
||||||
|
*/
|
||||||
|
export type SkinTableJsonCharSkinEntry = {
|
||||||
|
skinId: string
|
||||||
|
charId: string
|
||||||
|
dynIllustId: string // when replacing filename, remove suffix `_2`
|
||||||
|
voiceId: string | null // if null, use default voice
|
||||||
|
displaySkin: {
|
||||||
|
skinName: string | null // if null, not a skin
|
||||||
|
modelName: string
|
||||||
|
colorList: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export type SkinTableJson = {
|
||||||
|
charSkins: {
|
||||||
|
[id: string]: SkinTableJsonCharSkinEntry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import operators, {
|
|||||||
OPERATOR_SOURCE_FOLDER,
|
OPERATOR_SOURCE_FOLDER,
|
||||||
generateAssetsJson,
|
generateAssetsJson,
|
||||||
} from '@aklive2d/operator'
|
} from '@aklive2d/operator'
|
||||||
|
import {
|
||||||
|
getExtractedFolder,
|
||||||
|
getActualFilename,
|
||||||
|
findSkel,
|
||||||
|
} from '@aklive2d/operator/libs/utils'
|
||||||
import type { OperatorConfig } from '@aklive2d/operator/types'
|
import type { OperatorConfig } from '@aklive2d/operator/types'
|
||||||
import { DIST_DIR as ASSETS_DIST_DIR } from '@aklive2d/assets'
|
import { DIST_DIR as ASSETS_DIST_DIR } from '@aklive2d/assets'
|
||||||
import { file, env } from '@aklive2d/libs'
|
import { file, env } from '@aklive2d/libs'
|
||||||
@@ -106,10 +111,14 @@ export const copyShowcaseData = (
|
|||||||
fn(source, target)
|
fn(source, target)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const filename = getActualFilename(
|
||||||
|
operators[name].filename,
|
||||||
|
getExtractedFolder(name)
|
||||||
|
)
|
||||||
const buildConfig = {
|
const buildConfig = {
|
||||||
insight_id: config.insight.id,
|
insight_id: config.insight.id,
|
||||||
link: operators[name].link,
|
link: operators[name].link,
|
||||||
filename: operators[name].filename.replace(/#/g, '%23'),
|
filename: filename.replace(/#/g, '%23'),
|
||||||
logo_filename: operators[name].logo,
|
logo_filename: operators[name].logo,
|
||||||
fallback_filename: operators[name].fallback_name.replace(/#/g, '%23'),
|
fallback_filename: operators[name].fallback_name.replace(/#/g, '%23'),
|
||||||
viewport_left: operators[name].viewport_left,
|
viewport_left: operators[name].viewport_left,
|
||||||
@@ -126,7 +135,7 @@ export const copyShowcaseData = (
|
|||||||
voice_folders: config.dir_name.voice,
|
voice_folders: config.dir_name.voice,
|
||||||
music_folder: config.module.assets.music,
|
music_folder: config.module.assets.music,
|
||||||
music_mapping: musicMapping.musicFileMapping,
|
music_mapping: musicMapping.musicFileMapping,
|
||||||
use_json: operators[name].use_json,
|
use_json: findSkel(filename, getExtractedFolder(name)).endsWith('json'),
|
||||||
default_assets_dir: `${config.app.showcase.assets}/`,
|
default_assets_dir: `${config.app.showcase.assets}/`,
|
||||||
logo_dir:
|
logo_dir:
|
||||||
mode === 'build:directory'
|
mode === 'build:directory'
|
||||||
@@ -190,6 +199,14 @@ export const copyDirectoryData = async ({
|
|||||||
Object.values(operators).reduce(
|
Object.values(operators).reduce(
|
||||||
(acc, cur) => {
|
(acc, cur) => {
|
||||||
const curD = cur as DirectoryOperatorConfig
|
const curD = cur as DirectoryOperatorConfig
|
||||||
|
curD.filename = getActualFilename(
|
||||||
|
operators[curD.link].filename,
|
||||||
|
getExtractedFolder(curD.link)
|
||||||
|
)
|
||||||
|
curD.use_json = findSkel(
|
||||||
|
curD.filename,
|
||||||
|
getExtractedFolder(curD.link)
|
||||||
|
).endsWith('json')
|
||||||
const date = curD.date
|
const date = curD.date
|
||||||
|
|
||||||
curD.workshopId = null
|
curD.workshopId = null
|
||||||
@@ -200,7 +217,7 @@ export const copyDirectoryData = async ({
|
|||||||
cur.link,
|
cur.link,
|
||||||
config.module.project_json.project_json
|
config.module.project_json.project_json
|
||||||
)
|
)
|
||||||
)
|
) as string
|
||||||
if (!text) {
|
if (!text) {
|
||||||
console.log(`No workshop id for ${cur.link}!`)
|
console.log(`No workshop id for ${cur.link}!`)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user