diff --git a/README.md b/README.md index ce47cc2..fc9a6cf 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ Download extracted game assets ``` ### Webpage & JavaScript -Add query string `aklive2d` to bring up the settings panel to adjust your settings. -Settings can be adjusted under `window.aklive2d` or by dispatching custom events (under `window.aklive2d.events`) to `document`. +Add query string `aklive2d` to bring up the settings panel to adjust your settings. +Settings can be adjusted under `window.aklive2d` or by dispatching custom events (under `window.aklive2d.events`) to `document`. Examples can be found at `apps/showcase/src/components/wallpaper_engine.js`. ```js @@ -194,7 +194,7 @@ ling: !include config/ling.yaml nearl: !include config/nearl.yaml nian: !include config/nian.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 skadi: !include config/skadi.yaml skadi_sublimation: !include config/skadi_sublimation.yaml diff --git a/bun.lock b/bun.lock index d22884c..0a9b3fc 100644 --- a/bun.lock +++ b/bun.lock @@ -242,8 +242,12 @@ "@aklive2d/libs": "workspace:*", "@aklive2d/official-info": "workspace:*", "@aklive2d/prettier-config": "workspace:*", + "unidecode": "^1.1.0", "yaml": "^2.7.0", }, + "devDependencies": { + "@types/unidecode": "^1.1.0", + }, "peerDependencies": { "globals": ">=16.0.0", "typescript": ">=5.8.2", @@ -685,6 +689,8 @@ "@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/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=="], + "unidecode": ["unidecode@1.1.0", "", {}, "sha512-GIp57N6DVVJi8dpeIU6/leJGdv7W65ZSXFLFiNmxvexXkc0nXdqUvhA/qL9KqBKsILxMwg5MnmYNOIDJLb5JVA=="], + "union": ["union@0.5.0", "", { "dependencies": { "qs": "^6.4.0" } }, "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], diff --git a/packages/config/config.yaml b/packages/config/config.yaml index 2bcf0f1..8dc3b6c 100644 --- a/packages/config/config.yaml +++ b/packages/config/config.yaml @@ -34,6 +34,8 @@ module: directory_assets: _directory MonoBehaviour: MonoBehaviour Texture2D: Texture2D + character_table_json: character_table.json + skin_table_json: skin_table.json title: zh-CN: '明日方舟:' en-US: 'Arknights: ' diff --git a/packages/config/types.ts b/packages/config/types.ts index 66bc93b..0b1aff9 100644 --- a/packages/config/types.ts +++ b/packages/config/types.ts @@ -42,6 +42,8 @@ export type Config = { directory_assets: string MonoBehaviour: string Texture2D: string + character_table_json: string + skin_table_json: string title: { 'zh-CN': string 'en-US': string diff --git a/packages/libs/libs/file.ts b/packages/libs/libs/file.ts index 34db5eb..cf218fe 100644 --- a/packages/libs/libs/file.ts +++ b/packages/libs/libs/file.ts @@ -3,6 +3,11 @@ import path from 'node:path' import yauzl from 'yauzl-promise' import yazl from 'yazl' +type ReadOpts = { + encoding?: BufferEncoding + useAsPrefix?: boolean +} + export async function write( content: string | NodeJS.ArrayBufferView, filePath: string @@ -21,14 +26,32 @@ export function writeSync( export async function read( 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)) { - return fs.readFileSync(filePath, { encoding, flag: 'r' }) + return fs.readFileSync(filePath, { ...opts, flag: 'r' }) } return null } diff --git a/packages/libs/libs/yaml.ts b/packages/libs/libs/yaml.ts index ff23782..3e650f7 100644 --- a/packages/libs/libs/yaml.ts +++ b/packages/libs/libs/yaml.ts @@ -17,7 +17,7 @@ export function read( return data }, } - const file = fs.readFileSync(file_dir, 'utf8') + const file = fs.readFileSync(file_dir, { encoding: 'utf8' }) return parse(file, { customTags: [include, ...customTags], } as SchemaOptions) diff --git a/packages/official-info/auto_update/official_info.json b/packages/official-info/auto_update/official_info.json index 076924a..9ab4e4c 100644 --- a/packages/official-info/auto_update/official_info.json +++ b/packages/official-info/auto_update/official_info.json @@ -77,7 +77,7 @@ { "codename": { "zh-CN": "新约能天使", - "en-US": "Exusiai the New Covenant" + "en-US": "Exusiai the New Covenant" }, "type": "operator", "link": "https://ak.hypergryph.com/archive/dynamicCompile/202504941.html", @@ -703,4 +703,4 @@ } ] } -} \ No newline at end of file +} diff --git a/packages/official-info/types.ts b/packages/official-info/types.ts index 89f5771..000c394 100644 --- a/packages/official-info/types.ts +++ b/packages/official-info/types.ts @@ -46,3 +46,21 @@ export interface OperatorConfig extends OfficialInfoOperatorConfig { export type OfficialInfoMapping = { [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 +} diff --git a/packages/operator/.gitignore b/packages/operator/.gitignore index 6320cd2..1b5ce3e 100644 --- a/packages/operator/.gitignore +++ b/packages/operator/.gitignore @@ -1 +1,2 @@ -data \ No newline at end of file +data +auto_update diff --git a/packages/operator/config.yaml b/packages/operator/config.yaml index 265a938..7150306 100644 --- a/packages/operator/config.yaml +++ b/packages/operator/config.yaml @@ -5,7 +5,7 @@ ling: !include config/ling.yaml nearl: !include config/nearl.yaml nian: !include config/nian.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 skadi: !include config/skadi.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 nearl_relight: !include config/nearl_relight.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 chongyue: !include config/chongyue.yaml ling_it_does_wash_the_strings: !include config/ling_it_does_wash_the_strings.yaml diff --git a/packages/operator/config/_template.yaml b/packages/operator/config/_template.yaml index c356614..7028f13 100644 --- a/packages/operator/config/_template.yaml +++ b/packages/operator/config/_template.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: zh-CN: 假日威龙陈 en-US: Ch'en/Chen the Holungday -use_json: false diff --git a/packages/operator/config/amiya_solo_around_the_world.yaml b/packages/operator/config/amiya_solo_around_the_world.yaml index 704f408..bc5212d 100644 --- a/packages/operator/config/amiya_solo_around_the_world.yaml +++ b/packages/operator/config/amiya_solo_around_the_world.yaml @@ -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: zh-CN: 寰宇独奏 · 阿米娅 en-US: Solo Around The World / Amiya -use_json: false official_id: '202412473' diff --git a/packages/operator/config/archetto_glory_of_the_devout.yaml b/packages/operator/config/archetto_glory_of_the_devout.yaml index 9b410d1..83095b7 100644 --- a/packages/operator/config/archetto_glory_of_the_devout.yaml +++ b/packages/operator/config/archetto_glory_of_the_devout.yaml @@ -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: zh-CN: 至虔者荣光 · 空弦 en-US: Glory of the Devout / Archetto -use_json: false official_id: '202504953' diff --git a/packages/operator/config/chen.yaml b/packages/operator/config/chen.yaml index ea86e0f..bf8ce90 100644 --- a/packages/operator/config/chen.yaml +++ b/packages/operator/config/chen.yaml @@ -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: zh-CN: 假日威龙陈 en-US: Ch'en/Chen the Holungday -use_json: false official_id: '20220345' diff --git a/packages/operator/config/chen_ten_thousand_mountains.yaml b/packages/operator/config/chen_ten_thousand_mountains.yaml index d45255b..14e1d51 100644 --- a/packages/operator/config/chen_ten_thousand_mountains.yaml +++ b/packages/operator/config/chen_ten_thousand_mountains.yaml @@ -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: zh-CN: 万重山 · 假日威龙陈 en-US: Ten Thousand Mountains / Ch'en/Chen the Holungday -use_json: false official_id: '202304659' diff --git a/packages/operator/config/chongyue.yaml b/packages/operator/config/chongyue.yaml index b38dfe7..a263de1 100644 --- a/packages/operator/config/chongyue.yaml +++ b/packages/operator/config/chongyue.yaml @@ -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: zh-CN: 重岳 en-US: Chongyue -use_json: false official_id: '202301606' diff --git a/packages/operator/config/chongyue_alighting.yaml b/packages/operator/config/chongyue_alighting.yaml index ace4f88..8128cdc 100644 --- a/packages/operator/config/chongyue_alighting.yaml +++ b/packages/operator/config/chongyue_alighting.yaml @@ -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: zh-CN: 何处栖 · 重岳 en-US: Alighting / Chongyue -use_json: false official_id: '202401812' diff --git a/packages/operator/config/chongyue_allround_actor.yaml b/packages/operator/config/chongyue_allround_actor.yaml index 823608c..764231b 100644 --- a/packages/operator/config/chongyue_allround_actor.yaml +++ b/packages/operator/config/chongyue_allround_actor.yaml @@ -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: zh-CN: 全能演员 · 重岳 en-US: All-Round Actor / Chongyue -use_json: false official_id: '202412452' diff --git a/packages/operator/config/degenbrecher_the_shadow_of_dark_moon.yaml b/packages/operator/config/degenbrecher_the_shadow_of_dark_moon.yaml index 089aab5..4ee1284 100644 --- a/packages/operator/config/degenbrecher_the_shadow_of_dark_moon.yaml +++ b/packages/operator/config/degenbrecher_the_shadow_of_dark_moon.yaml @@ -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: zh-CN: 暗月的影子 · 锏 en-US: The Shadow Of The Dark Moon / Degenbrecher -use_json: false official_id: '202410426' diff --git a/packages/operator/config/dusk.yaml b/packages/operator/config/dusk.yaml index e4fb4c8..f60782f 100644 --- a/packages/operator/config/dusk.yaml +++ b/packages/operator/config/dusk.yaml @@ -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: zh-CN: 夕 en-US: Dusk -use_json: false official_id: '202203263' diff --git a/packages/operator/config/dusk_everything_is_a_miracle.yaml b/packages/operator/config/dusk_everything_is_a_miracle.yaml index 666a665..53595c7 100644 --- a/packages/operator/config/dusk_everything_is_a_miracle.yaml +++ b/packages/operator/config/dusk_everything_is_a_miracle.yaml @@ -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: zh-CN: 染尘烟 · 夕 en-US: Everything is a Miracle / Dusk -use_json: false official_id: '20220321' diff --git a/packages/operator/config/executor_the_ex_foedere_allmind_as_one.yaml b/packages/operator/config/executor_the_ex_foedere_allmind_as_one.yaml index 11fb6fb..302d658 100644 --- a/packages/operator/config/executor_the_ex_foedere_allmind_as_one.yaml +++ b/packages/operator/config/executor_the_ex_foedere_allmind_as_one.yaml @@ -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: zh-CN: 众志归一 · 圣约送葬人 en-US: Allmind as one / Executor the Ex Foedere -use_json: false official_id: '202410488' diff --git a/packages/operator/config/exusiai_the_new_covenant.yaml b/packages/operator/config/exusiai_the_new_covenant.yaml index 7ac014f..ef94260 100644 --- a/packages/operator/config/exusiai_the_new_covenant.yaml +++ b/packages/operator/config/exusiai_the_new_covenant.yaml @@ -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: zh-CN: 新约能天使 en-US: Exusiai the New Covenant -use_json: false official_id: '202504941' diff --git a/packages/operator/config/eyjafjalla_the_hvit_aska.yaml b/packages/operator/config/eyjafjalla_the_hvit_aska.yaml index 8b6c50d..206be59 100644 --- a/packages/operator/config/eyjafjalla_the_hvit_aska.yaml +++ b/packages/operator/config/eyjafjalla_the_hvit_aska.yaml @@ -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: zh-CN: 纯烬艾雅法拉 en-US: Eyjafjalla the Hvít Aska -use_json: false official_id: '202307865' diff --git a/packages/operator/config/eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip.yaml b/packages/operator/config/eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip.yaml index 5fd1140..8eb007b 100644 --- a/packages/operator/config/eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip.yaml +++ b/packages/operator/config/eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip.yaml @@ -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: zh-CN: 远行前的野餐 · 纯烬艾雅法拉 en-US: A Picnic Before A Long Trip / Eyjafjalla the Hvít Aska -use_json: true official_id: '202407072' diff --git a/packages/operator/config/gavial.yaml b/packages/operator/config/gavial.yaml index 3081099..20d9770 100644 --- a/packages/operator/config/gavial.yaml +++ b/packages/operator/config/gavial.yaml @@ -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: - zh-CN: 百练嘉维尔 + zh-CN: 百炼嘉维尔 en-US: Gavial the Invincible -use_json: false official_id: '202208258' diff --git a/packages/operator/config/gavial_the_invincible_holiday_hd26.yaml b/packages/operator/config/gavial_the_invincible_holiday_hd26.yaml index 33346d3..5155343 100644 --- a/packages/operator/config/gavial_the_invincible_holiday_hd26.yaml +++ b/packages/operator/config/gavial_the_invincible_holiday_hd26.yaml @@ -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: zh-CN: 悠然假日 HD26 · 百炼嘉维尔 en-US: Holiday HD26 / Gavial the Invincible -use_json: false official_id: '202307886' diff --git a/packages/operator/config/goldenglow_summer_flowers_fa394.yaml b/packages/operator/config/goldenglow_summer_flowers_fa394.yaml index 4ad84da..e0a09cc 100644 --- a/packages/operator/config/goldenglow_summer_flowers_fa394.yaml +++ b/packages/operator/config/goldenglow_summer_flowers_fa394.yaml @@ -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: zh-CN: 夏卉 FA394 · 澄闪 en-US: Summer Flowers FA394 / Goldenglow -use_json: false official_id: '202307824' diff --git a/packages/operator/config/ines_melodic_flutter.yaml b/packages/operator/config/ines_melodic_flutter.yaml index e2873fc..e1673fc 100644 --- a/packages/operator/config/ines_melodic_flutter.yaml +++ b/packages/operator/config/ines_melodic_flutter.yaml @@ -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: zh-CN: 蝶舞华章 · 伊内丝 en-US: Melodic Flutter / Ines -use_json: false official_id: "202504968" diff --git a/packages/operator/config/ines_under_the_flaming_dome.yaml b/packages/operator/config/ines_under_the_flaming_dome.yaml index 1691c72..f9aa7cd 100644 --- a/packages/operator/config/ines_under_the_flaming_dome.yaml +++ b/packages/operator/config/ines_under_the_flaming_dome.yaml @@ -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: zh-CN: 燃烧天穹下 · 伊内丝 en-US: Under the Flaming Dome / Ines -use_json: false official_id: '202404087' diff --git a/packages/operator/config/kaltsit_remnant.yaml b/packages/operator/config/kaltsit_remnant.yaml index 6f0139e..d663678 100644 --- a/packages/operator/config/kaltsit_remnant.yaml +++ b/packages/operator/config/kaltsit_remnant.yaml @@ -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: zh-CN: 残余 · 凯尔希 - en-US: Remnant / Kal'tsit -use_json: false + en-US: Remnant / Kal'tsit/Kaltsit official_id: '202304833' diff --git a/packages/operator/config/kroos_moonlit_voyage.yaml b/packages/operator/config/kroos_moonlit_voyage.yaml index 0d056ca..9a79ba4 100644 --- a/packages/operator/config/kroos_moonlit_voyage.yaml +++ b/packages/operator/config/kroos_moonlit_voyage.yaml @@ -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: zh-CN: 星月漂流记 · 克洛丝 en-US: Moonlit Voyage / Kroos -use_json: false official_id: '202504992' diff --git a/packages/operator/config/lappland_the_decadenza.yaml b/packages/operator/config/lappland_the_decadenza.yaml index bf99d15..94fb4d2 100644 --- a/packages/operator/config/lappland_the_decadenza.yaml +++ b/packages/operator/config/lappland_the_decadenza.yaml @@ -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: zh-CN: 荒芜拉普兰德 en-US: Lappland the Decadenza -use_json: false official_id: '202410440' diff --git a/packages/operator/config/lee_trust_your_eyes.yaml b/packages/operator/config/lee_trust_your_eyes.yaml index 5e957cd..5eadee7 100644 --- a/packages/operator/config/lee_trust_your_eyes.yaml +++ b/packages/operator/config/lee_trust_your_eyes.yaml @@ -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: zh-CN: 手到牌来 · 老鲤 en-US: Trust Your Eyes / Lee -use_json: false official_id: '202210279' diff --git a/packages/operator/config/lin_heavenly_mirage.yaml b/packages/operator/config/lin_heavenly_mirage.yaml index a35d7e2..b1974e0 100644 --- a/packages/operator/config/lin_heavenly_mirage.yaml +++ b/packages/operator/config/lin_heavenly_mirage.yaml @@ -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: zh-CN: 列瑶台 · 林 en-US: Heavenly Mirage / Lin -use_json: false official_id: '202401034' diff --git a/packages/operator/config/ling.yaml b/packages/operator/config/ling.yaml index 4f82adb..4f29890 100644 --- a/packages/operator/config/ling.yaml +++ b/packages/operator/config/ling.yaml @@ -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: zh-CN: 令 en-US: Ling -use_json: false official_id: '20220383' diff --git a/packages/operator/config/ling_it_does_wash_the_strings.yaml b/packages/operator/config/ling_it_does_wash_the_strings.yaml index 2195f9e..0a5fcfb 100644 --- a/packages/operator/config/ling_it_does_wash_the_strings.yaml +++ b/packages/operator/config/ling_it_does_wash_the_strings.yaml @@ -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: zh-CN: 濯缨 · 令 en-US: It Does Wash the Strings / Ling -use_json: false official_id: '202301647' diff --git a/packages/operator/config/ling_towering_is_cliff_of_nostalgia.yaml b/packages/operator/config/ling_towering_is_cliff_of_nostalgia.yaml index 9991f6d..c217a79 100644 --- a/packages/operator/config/ling_towering_is_cliff_of_nostalgia.yaml +++ b/packages/operator/config/ling_towering_is_cliff_of_nostalgia.yaml @@ -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: zh-CN: 崖高梦远 · 令 - en-US: Towering is Cliff of Nostalgia -use_json: false + en-US: Towering is Cliff of Nostalgia / Ling official_id: '202308807' diff --git a/packages/operator/config/logos_radiant_serenity.yaml b/packages/operator/config/logos_radiant_serenity.yaml index aba3204..bd356dc 100644 --- a/packages/operator/config/logos_radiant_serenity.yaml +++ b/packages/operator/config/logos_radiant_serenity.yaml @@ -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: zh-CN: 辉煌的静谧 · 逻各斯 en-US: Radiant Serenity / Logos -use_json: false official_id: "202504989" diff --git a/packages/operator/config/mizuki_summer_feast.yaml b/packages/operator/config/mizuki_summer_feast.yaml index 4d221a9..9d14300 100644 --- a/packages/operator/config/mizuki_summer_feast.yaml +++ b/packages/operator/config/mizuki_summer_feast.yaml @@ -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: zh-CN: 夏日餮宴 · 水月 en-US: Summer Feast / Mizuki -use_json: false official_id: '202211685' diff --git a/packages/operator/config/muelsyse.yaml b/packages/operator/config/muelsyse.yaml index 6cf0149..7fcedea 100644 --- a/packages/operator/config/muelsyse.yaml +++ b/packages/operator/config/muelsyse.yaml @@ -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: zh-CN: 缪尔赛思 en-US: Muelsyse -use_json: false official_id: '202304611' diff --git a/packages/operator/config/muelsyse_golden_reverie.yaml b/packages/operator/config/muelsyse_golden_reverie.yaml index 425acfc..e8968fd 100644 --- a/packages/operator/config/muelsyse_golden_reverie.yaml +++ b/packages/operator/config/muelsyse_golden_reverie.yaml @@ -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: zh-CN: 漫步于黄金之梦 · 缪尔赛思 en-US: Golden Reverie / Muelsyse -use_json: false official_id: "202504900" diff --git a/packages/operator/config/muelsyse_young_branch.yaml b/packages/operator/config/muelsyse_young_branch.yaml index bad2010..fdda0df 100644 --- a/packages/operator/config/muelsyse_young_branch.yaml +++ b/packages/operator/config/muelsyse_young_branch.yaml @@ -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: zh-CN: 新枝 · 缪尔赛思 en-US: Young Branch / Muelsyse -use_json: false official_id: '202404090' diff --git a/packages/operator/config/mwynar_w_dali.yaml b/packages/operator/config/mwynar_w_dali.yaml index 9a0a07f..8105c14 100644 --- a/packages/operator/config/mwynar_w_dali.yaml +++ b/packages/operator/config/mwynar_w_dali.yaml @@ -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: zh-CN: 远路 · 玛恩纳 en-US: W Dali / Młynar -use_json: false official_id: '202310850' diff --git a/packages/operator/config/nearl.yaml b/packages/operator/config/nearl.yaml index 20bfd7e..468a385 100644 --- a/packages/operator/config/nearl.yaml +++ b/packages/operator/config/nearl.yaml @@ -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: zh-CN: 耀骑士临光 en-US: Nearl the Radiant Knight -use_json: false official_id: '20220304' diff --git a/packages/operator/config/nearl_relight.yaml b/packages/operator/config/nearl_relight.yaml index 1467b40..b6a1851 100644 --- a/packages/operator/config/nearl_relight.yaml +++ b/packages/operator/config/nearl_relight.yaml @@ -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: zh-CN: 复现荣光 · 耀骑士临光 - en-US: Relight / Nearl -use_json: false + en-US: Relight / Nearl the Radiant Knight official_id: '202210623' diff --git a/packages/operator/config/nian.yaml b/packages/operator/config/nian.yaml index bf598e4..b76a31b 100644 --- a/packages/operator/config/nian.yaml +++ b/packages/operator/config/nian.yaml @@ -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: zh-CN: 年 en-US: Nian -use_json: false official_id: '202203231' diff --git a/packages/operator/config/nian_thunderbolt_director.yaml b/packages/operator/config/nian_thunderbolt_director.yaml index 0152502..11fdb78 100644 --- a/packages/operator/config/nian_thunderbolt_director.yaml +++ b/packages/operator/config/nian_thunderbolt_director.yaml @@ -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: zh-CN: 霹雳导演 · 年 en-US: Thunderbolt Director / Nian -use_json: false official_id: '202412491' diff --git a/packages/operator/config/nian_unfettered_freedom.yaml b/packages/operator/config/nian_unfettered_freedom.yaml index 9a654e5..d411fb4 100644 --- a/packages/operator/config/nian_unfettered_freedom.yaml +++ b/packages/operator/config/nian_unfettered_freedom.yaml @@ -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: zh-CN: 乐逍遥 · 年 en-US: Unfettered Freedom / Nian -use_json: false official_id: '20220362' diff --git a/packages/operator/config/nightingale_iakhu_of_flows.yaml b/packages/operator/config/nightingale_iakhu_of_flows.yaml index cf8a0e1..7b78dad 100644 --- a/packages/operator/config/nightingale_iakhu_of_flows.yaml +++ b/packages/operator/config/nightingale_iakhu_of_flows.yaml @@ -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: zh-CN: 流辉 · 夜莺 en-US: Iakhu of Flows / Nightingale -use_json: false official_id: '202407435' diff --git a/packages/operator/config/passager_dream_in_a_moment.yaml b/packages/operator/config/passager_dream_in_a_moment.yaml deleted file mode 100644 index 4f4e423..0000000 --- a/packages/operator/config/passager_dream_in_a_moment.yaml +++ /dev/null @@ -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' diff --git a/packages/operator/config/passenger_dream_in_a_moment.yaml b/packages/operator/config/passenger_dream_in_a_moment.yaml new file mode 100644 index 0000000..5c1e8b7 --- /dev/null +++ b/packages/operator/config/passenger_dream_in_a_moment.yaml @@ -0,0 +1,4 @@ +codename: + zh-CN: 今昔须臾之梦 · 异客 + en-US: Dream in a Moment / Passenger +official_id: '202210664' diff --git a/packages/operator/config/pepe.yaml b/packages/operator/config/pepe.yaml index 878dd10..549c35c 100644 --- a/packages/operator/config/pepe.yaml +++ b/packages/operator/config/pepe.yaml @@ -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: zh-CN: 佩佩 en-US: Pepe -use_json: true official_id: '202407013' diff --git a/packages/operator/config/phantom_focus.yaml b/packages/operator/config/phantom_focus.yaml new file mode 100644 index 0000000..4ac1091 --- /dev/null +++ b/packages/operator/config/phantom_focus.yaml @@ -0,0 +1,4 @@ +codename: + zh-CN: 焦点 · 傀影 + en-US: Focus / Phantom +official_id: '202203222' diff --git a/packages/operator/config/phatom_focus.yaml b/packages/operator/config/phatom_focus.yaml deleted file mode 100644 index 3344737..0000000 --- a/packages/operator/config/phatom_focus.yaml +++ /dev/null @@ -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' diff --git a/packages/operator/config/pozemka_snowy_plains_in_words.yaml b/packages/operator/config/pozemka_snowy_plains_in_words.yaml index 92c56b6..f69328f 100644 --- a/packages/operator/config/pozemka_snowy_plains_in_words.yaml +++ b/packages/operator/config/pozemka_snowy_plains_in_words.yaml @@ -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: zh-CN: 字句中的雪原 · 鸿雪 - en-US: Snowy Plains in Words / Позёмка -use_json: false + en-US: Snowy Plains in Words / Позёмка/Pozemka official_id: '202302698' diff --git a/packages/operator/config/reed_the_frame_shadow_curator.yaml b/packages/operator/config/reed_the_frame_shadow_curator.yaml index dced695..53e9735 100644 --- a/packages/operator/config/reed_the_frame_shadow_curator.yaml +++ b/packages/operator/config/reed_the_frame_shadow_curator.yaml @@ -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: zh-CN: 博物 · 焰影苇草 en-US: Curator / Reed The Flame Shadow -use_json: false official_id: '202401871' diff --git a/packages/operator/config/reed_the_frame_shadow_summer_flower.yaml b/packages/operator/config/reed_the_frame_shadow_summer_flower.yaml index ec43545..a965b22 100644 --- a/packages/operator/config/reed_the_frame_shadow_summer_flower.yaml +++ b/packages/operator/config/reed_the_frame_shadow_summer_flower.yaml @@ -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: zh-CN: 夏卉 FA075 · 焰影苇草 en-US: Summer Flowers FA075 / Reed The Flame Shadow -use_json: true official_id: '202407051' diff --git a/packages/operator/config/rosmontis.yaml b/packages/operator/config/rosmontis.yaml index 783383d..9e70e03 100644 --- a/packages/operator/config/rosmontis.yaml +++ b/packages/operator/config/rosmontis.yaml @@ -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: zh-CN: 迷迭香 en-US: Rosmontis -use_json: false official_id: '20220378' diff --git a/packages/operator/config/rosmontis_become_anew.yaml b/packages/operator/config/rosmontis_become_anew.yaml index 9359264..f0d334c 100644 --- a/packages/operator/config/rosmontis_become_anew.yaml +++ b/packages/operator/config/rosmontis_become_anew.yaml @@ -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: zh-CN: 拥抱新生 · 迷迭香 en-US: Become Anew / Rosmontis -use_json: false official_id: '202210632' diff --git a/packages/operator/config/shu.yaml b/packages/operator/config/shu.yaml index ba0efae..6c23773 100644 --- a/packages/operator/config/shu.yaml +++ b/packages/operator/config/shu.yaml @@ -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: zh-CN: 黍 en-US: Shu -use_json: false official_id: '202401025' diff --git a/packages/operator/config/shu_spring_feast.yaml b/packages/operator/config/shu_spring_feast.yaml index 838169f..1857e03 100644 --- a/packages/operator/config/shu_spring_feast.yaml +++ b/packages/operator/config/shu_spring_feast.yaml @@ -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: zh-CN: 春日宴 · 黍 en-US: Spring Feast / Shu -use_json: false official_id: '202501936' diff --git a/packages/operator/config/silverash_never_melting_ice.yaml b/packages/operator/config/silverash_never_melting_ice.yaml index 63d488b..06365a1 100644 --- a/packages/operator/config/silverash_never_melting_ice.yaml +++ b/packages/operator/config/silverash_never_melting_ice.yaml @@ -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: zh-CN: 不融冰 · 银灰 en-US: Never-Melting Ice / SilverAsh -use_json: false official_id: '202404066' diff --git a/packages/operator/config/skadi.yaml b/packages/operator/config/skadi.yaml index 2639869..5e874a0 100644 --- a/packages/operator/config/skadi.yaml +++ b/packages/operator/config/skadi.yaml @@ -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: zh-CN: 浊心斯卡蒂 en-US: Skadi the Corrupting Heart -use_json: false official_id: '20220396' diff --git a/packages/operator/config/skadi_sublimation.yaml b/packages/operator/config/skadi_sublimation.yaml index 96188fb..8a4768a 100644 --- a/packages/operator/config/skadi_sublimation.yaml +++ b/packages/operator/config/skadi_sublimation.yaml @@ -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: zh-CN: 升华 · 浊心斯卡蒂 en-US: Sublimation / Skadi the Corrupting Heart -use_json: false official_id: '202204205' diff --git a/packages/operator/config/skadi_the_corrupting_heart_red_countess.yaml b/packages/operator/config/skadi_the_corrupting_heart_red_countess.yaml index 28a4984..757cd99 100644 --- a/packages/operator/config/skadi_the_corrupting_heart_red_countess.yaml +++ b/packages/operator/config/skadi_the_corrupting_heart_red_countess.yaml @@ -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: zh-CN: 红女爵 · 浊心斯卡蒂 en-US: Red Countess / Skadi the Corrupting Heart -use_json: false official_id: '202404008' diff --git a/packages/operator/config/specter.yaml b/packages/operator/config/specter.yaml index a7b16f9..f216338 100644 --- a/packages/operator/config/specter.yaml +++ b/packages/operator/config/specter.yaml @@ -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: zh-CN: 归溟幽灵鲨 en-US: Specter the Unchained -use_json: false official_id: '202204284' diff --git a/packages/operator/config/specter_born_as_one.yaml b/packages/operator/config/specter_born_as_one.yaml index d61ceb1..bb30847 100644 --- a/packages/operator/config/specter_born_as_one.yaml +++ b/packages/operator/config/specter_born_as_one.yaml @@ -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: zh-CN: 生而为一 · 归溟幽灵鲨 en-US: Born as One / Specter the Unchained -use_json: false official_id: '202304670' diff --git a/packages/operator/config/surtr_colorful_wonderland.yaml b/packages/operator/config/surtr_colorful_wonderland.yaml index aa2a3aa..222d1f3 100644 --- a/packages/operator/config/surtr_colorful_wonderland.yaml +++ b/packages/operator/config/surtr_colorful_wonderland.yaml @@ -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: zh-CN: 缤纷奇境 CW03 · 史尔特尔 en-US: Colorful Wonderland CW03 / Surtr -use_json: false official_id: '202208297' diff --git a/packages/operator/config/texas_the_omertosa.yaml b/packages/operator/config/texas_the_omertosa.yaml index 3253160..466b9b6 100644 --- a/packages/operator/config/texas_the_omertosa.yaml +++ b/packages/operator/config/texas_the_omertosa.yaml @@ -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: zh-CN: 缄默德克萨斯 en-US: Texas the Omertosa -use_json: false official_id: '202210210' diff --git a/packages/operator/config/texas_the_omertosa_il_se_de_no.yaml b/packages/operator/config/texas_the_omertosa_il_se_de_no.yaml index 02351d6..ab31330 100644 --- a/packages/operator/config/texas_the_omertosa_il_se_de_no.yaml +++ b/packages/operator/config/texas_the_omertosa_il_se_de_no.yaml @@ -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: zh-CN: 幽兰秘辛 · 缄默德克萨斯 en-US: Il Segreto Della Notte / Texas the Omertosa -use_json: false official_id: '202410409' diff --git a/packages/operator/config/texas_the_omertosa_wingbreaker.yaml b/packages/operator/config/texas_the_omertosa_wingbreaker.yaml index 42907cd..685edb5 100644 --- a/packages/operator/config/texas_the_omertosa_wingbreaker.yaml +++ b/packages/operator/config/texas_the_omertosa_wingbreaker.yaml @@ -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: zh-CN: 破翼者 · 缄默德克萨斯 en-US: Wingbreaker / Texas the Omertosa -use_json: false official_id: '202310899' diff --git a/packages/operator/config/virtuosa.yaml b/packages/operator/config/virtuosa.yaml index fffc7a8..6df9219 100644 --- a/packages/operator/config/virtuosa.yaml +++ b/packages/operator/config/virtuosa.yaml @@ -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: zh-CN: 塑心 en-US: Virtuosa -use_json: false official_id: '202310848' diff --git a/packages/operator/config/virtuosa_diversity_oneness.yaml b/packages/operator/config/virtuosa_diversity_oneness.yaml index 56be7e6..792d70d 100644 --- a/packages/operator/config/virtuosa_diversity_oneness.yaml +++ b/packages/operator/config/virtuosa_diversity_oneness.yaml @@ -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: zh-CN: 无我唯识 · 塑心 en-US: Diversity Oneness / Virtuosa -use_json: false official_id: '202410467' diff --git a/packages/operator/config/w.yaml b/packages/operator/config/w.yaml index 311a3c5..408fe3c 100644 --- a/packages/operator/config/w.yaml +++ b/packages/operator/config/w.yaml @@ -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: zh-CN: W en-US: W -use_json: false official_id: '20220319' diff --git a/packages/operator/config/w_wonder.yaml b/packages/operator/config/w_wonder.yaml index cd1e10b..93391f0 100644 --- a/packages/operator/config/w_wonder.yaml +++ b/packages/operator/config/w_wonder.yaml @@ -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: zh-CN: 恍惚 · W en-US: Wonder / W -use_json: false official_id: '202206246' diff --git a/packages/operator/config/wisadel.yaml b/packages/operator/config/wisadel.yaml index 4adc8e0..76d32f1 100644 --- a/packages/operator/config/wisadel.yaml +++ b/packages/operator/config/wisadel.yaml @@ -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: zh-CN: 维什戴尔 en-US: Wisadel -use_json: false official_id: '202404049' diff --git a/packages/operator/config/wisadel_supernova.yaml b/packages/operator/config/wisadel_supernova.yaml index f241be4..58df56c 100644 --- a/packages/operator/config/wisadel_supernova.yaml +++ b/packages/operator/config/wisadel_supernova.yaml @@ -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: zh-CN: 超新星 · 维什戴尔 en-US: Supernova / Wisadel -use_json: false official_id: '202504974' diff --git a/packages/operator/config/yu.yaml b/packages/operator/config/yu.yaml index 14d3117..07ea32e 100644 --- a/packages/operator/config/yu.yaml +++ b/packages/operator/config/yu.yaml @@ -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: zh-CN: 余 en-US: Yu -use_json: false official_id: '202501414' diff --git a/packages/operator/config/zuole_youthful_journey.yaml b/packages/operator/config/zuole_youthful_journey.yaml index 8de8c2c..cd56983 100644 --- a/packages/operator/config/zuole_youthful_journey.yaml +++ b/packages/operator/config/zuole_youthful_journey.yaml @@ -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: zh-CN: 少年游 · 左乐 en-US: Youthful Journey / Zuo Le -use_json: false official_id: '202501927' diff --git a/packages/operator/index.ts b/packages/operator/index.ts index dc428c6..faa37b9 100644 --- a/packages/operator/index.ts +++ b/packages/operator/index.ts @@ -1,9 +1,24 @@ import path from 'node:path' -import { stringify } from 'yaml' import { yaml, file, alphaComposite } from '@aklive2d/libs' import config from '@aklive2d/config' 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( import.meta.dirname, @@ -14,78 +29,16 @@ export const OPERATOR_SOURCE_FOLDER = path.resolve( import.meta.dirname, config.dir_name.data ) -const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist) - -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 - ) - ) -} - -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 DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist) +export const CONFIG_FOLDER = path.resolve( + import.meta.dirname, + config.module.operator.config +) export const has = (name: string) => { 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') { 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') } -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 ( filename: string, extractedDir: string, targetDir: string, _opts: { - useJSON?: boolean useSymLink?: boolean } = { - useJSON: false, useSymLink: true, } ) => { const assetsJson: AssetsJson = [] - let skelFilename - if (_opts.useJSON) { - skelFilename = `${filename}.json` - } else { - skelFilename = `${filename}.skel` - } + /* + * Special Cases: + * - ines_melodic_flutter + */ + filename = getActualFilename(filename, extractedDir) + + const skelFilename = findSkel(filename, extractedDir) const atlasFilename = `${filename}.atlas` 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)) if (!matches) 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 voiceFolders = getVoiceFolders(name) - const extractedFolder = getExtractedFolder(name) - const operatorConfigFolder = getConfigFolder() - const foldersToCreate = [extractedFolder, ...voiceFolders] - - const template = yaml.read( - path.resolve(operatorConfigFolder, config.module.operator.template_yaml) +const characterTable = (() => { + const character_table_json = path.resolve( + AUTO_UPDATE_FOLDER, + config.module.operator.character_table_json ) - foldersToCreate.forEach((dir) => { - file.mkdir(dir) - }) - const currentOpertor = officialInfoMapping[id] - if (currentOpertor === undefined) { - throw new Error('Invalid operator id') + const t = file.readSync(character_table_json, { + useAsPrefix: true, + }) as string + if (!t) throw new Error('character_table.json not found') + return JSON.parse(t) as CharacterTableJson +})() + +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( - stringify(template), - path.resolve(operatorConfigFolder, `${name}.yaml`) - ) - file.appendSync( - `${name}: !include ${config.module.operator.config}/${name}.yaml\n`, - CONFIG_PATH - ) + return CONFIG } + +const operators = generateMapping() + +export default operators diff --git a/packages/operator/libs/builder.ts b/packages/operator/libs/builder.ts new file mode 100644 index 0000000..f9cbbf4 --- /dev/null +++ b/packages/operator/libs/builder.ts @@ -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) + ) +} diff --git a/packages/operator/libs/initer.ts b/packages/operator/libs/initer.ts new file mode 100644 index 0000000..4568e7b --- /dev/null +++ b/packages/operator/libs/initer.ts @@ -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 + ) +} diff --git a/packages/operator/libs/updater.ts b/packages/operator/libs/updater.ts new file mode 100644 index 0000000..39b09d1 --- /dev/null +++ b/packages/operator/libs/updater.ts @@ -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 + ) +} diff --git a/packages/operator/libs/utils.ts b/packages/operator/libs/utils.ts new file mode 100644 index 0000000..1863a10 --- /dev/null +++ b/packages/operator/libs/utils.ts @@ -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') + } +} diff --git a/packages/operator/package.json b/packages/operator/package.json index 91686a1..1327911 100644 --- a/packages/operator/package.json +++ b/packages/operator/package.json @@ -5,12 +5,13 @@ "main": "index.ts", "type": "module", "dependencies": { - "yaml": "^2.7.0", - "@aklive2d/libs": "workspace:*", "@aklive2d/config": "workspace:*", - "@aklive2d/official-info": "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": { "globals": ">=16.0.0", @@ -18,9 +19,13 @@ "typescript": ">=5.8.2" }, "scripts": { + "update": "mode=update bun runner.ts", "build": "mode=build bun runner.ts", "init": "mode=init bun runner.ts", "lint": "eslint && prettier --check .", "build:cleanup": "rm -rf ./dist ./data" + }, + "devDependencies": { + "@types/unidecode": "^1.1.0" } } diff --git a/packages/operator/runner.ts b/packages/operator/runner.ts index dc5877c..def66ed 100644 --- a/packages/operator/runner.ts +++ b/packages/operator/runner.ts @@ -1,5 +1,7 @@ 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 = { mode: string @@ -27,6 +29,9 @@ async function main() { case 'build': await build(name) break + case 'update': + await update() + break case 'init': if (!name.length) { throw new Error('Please set the operator name.') diff --git a/packages/operator/types.ts b/packages/operator/types.ts index bf3c1d4..74efc2e 100644 --- a/packages/operator/types.ts +++ b/packages/operator/types.ts @@ -1,22 +1,26 @@ export type Codename = { 'zh-CN': string; 'en-US': string } +export type OperatorEntryType = 'operator' | 'skin' + export interface OperatorConfig { filename: string logo: string fallback_name: string - viewport_left: number + viewport_left: number // should be default to 0 in the future viewport_right: number viewport_top: number viewport_bottom: number invert_filter: boolean codename: Codename - use_json: boolean - official_id: string + use_json: boolean // can be detected automatically + official_id: string // reverse lookup from official_update? title: string - type: 'operator' | 'skin' + type: OperatorEntryType link: string - id: string + id: string // used in directory date: string + voice_id: string | null + color: string } export type Config = { @@ -91,3 +95,40 @@ export type AssetsJson = { path?: string content?: string | Buffer | 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 + } +} diff --git a/packages/vite-helpers/index.ts b/packages/vite-helpers/index.ts index c6d3001..3892dfd 100644 --- a/packages/vite-helpers/index.ts +++ b/packages/vite-helpers/index.ts @@ -4,6 +4,11 @@ import operators, { OPERATOR_SOURCE_FOLDER, generateAssetsJson, } from '@aklive2d/operator' +import { + getExtractedFolder, + getActualFilename, + findSkel, +} from '@aklive2d/operator/libs/utils' import type { OperatorConfig } from '@aklive2d/operator/types' import { DIST_DIR as ASSETS_DIST_DIR } from '@aklive2d/assets' import { file, env } from '@aklive2d/libs' @@ -106,10 +111,14 @@ export const copyShowcaseData = ( fn(source, target) } }) + const filename = getActualFilename( + operators[name].filename, + getExtractedFolder(name) + ) const buildConfig = { insight_id: config.insight.id, link: operators[name].link, - filename: operators[name].filename.replace(/#/g, '%23'), + filename: filename.replace(/#/g, '%23'), logo_filename: operators[name].logo, fallback_filename: operators[name].fallback_name.replace(/#/g, '%23'), viewport_left: operators[name].viewport_left, @@ -126,7 +135,7 @@ export const copyShowcaseData = ( voice_folders: config.dir_name.voice, music_folder: config.module.assets.music, 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}/`, logo_dir: mode === 'build:directory' @@ -190,6 +199,14 @@ export const copyDirectoryData = async ({ Object.values(operators).reduce( (acc, cur) => { 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 curD.workshopId = null @@ -200,7 +217,7 @@ export const copyDirectoryData = async ({ cur.link, config.module.project_json.project_json ) - ) + ) as string if (!text) { console.log(`No workshop id for ${cur.link}!`) } else {