feat: migrate to turbo (#22)

* feat: migrate top turbo

* ci: ci test

* fix: fix codeql issues

* feat: ci test

* chore: lint

* chore: misc changes

* feat: rename vite helpers

* feat: use fetch to handle assets

* feat: update directory

* feat: fetch charword table

* feat: migrate download game data and detect missing voice files

* feat: symlink relative path

* feat: finish wrangler upload

* feat: migrate wrangler download

* feat: finish

* chore: auto update

* ci: update ci

* ci: update ci

---------

Co-authored-by: Halyul <Halyul@users.noreply.github.com>
This commit is contained in:
Haoyu Xu
2025-02-22 15:11:30 +08:00
committed by GitHub
parent 17c61ce5d4
commit d6e7bc20d3
352 changed files with 12911 additions and 9411 deletions

1
packages/operator/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
data

View File

@@ -0,0 +1,3 @@
dist
data
auto_update

View File

@@ -0,0 +1,60 @@
chen: !include config/chen.yaml
dusk: !include config/dusk.yaml
dusk_everything_is_a_miracle: !include config/dusk_everything_is_a_miracle.yaml
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
rosmontis: !include config/rosmontis.yaml
skadi: !include config/skadi.yaml
skadi_sublimation: !include config/skadi_sublimation.yaml
w: !include config/w.yaml
w_wonder: !include config/w_wonder.yaml
specter: !include config/specter.yaml
gavial: !include config/gavial.yaml
surtr_colorful_wonderland: !include config/surtr_colorful_wonderland.yaml
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
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
pozemka_snowy_plains_in_words: !include config/pozemka_snowy_plains_in_words.yaml
chen_ten_thousand_mountains: !include config/chen_ten_thousand_mountains.yaml
specter_born_as_one: !include config/specter_born_as_one.yaml
muelsyse: !include config/muelsyse.yaml
kaltsit_remnant: !include config/kaltsit_remnant.yaml
eyjafjalla_the_hvit_aska: !include config/eyjafjalla_the_hvit_aska.yaml
goldenglow_summer_flowers_fa394: !include config/goldenglow_summer_flowers_fa394.yaml
gavial_the_invincible_holiday_hd26: !include config/gavial_the_invincible_holiday_hd26.yaml
ling_towering_is_cliff_of_nostalgia: !include config/ling_towering_is_cliff_of_nostalgia.yaml
virtuosa: !include config/virtuosa.yaml
texas_the_omertosa_wingbreaker: !include config/texas_the_omertosa_wingbreaker.yaml
mwynar_w_dali: !include config/mwynar_w_dali.yaml
reed_the_frame_shadow_curator: !include config/reed_the_frame_shadow_curator.yaml
shu: !include config/shu.yaml
lin_heavenly_mirage: !include config/lin_heavenly_mirage.yaml
chongyue_alighting: !include config/chongyue_alighting.yaml
wisadel: !include config/wisadel.yaml
muelsyse_young_branch: !include config/muelsyse_young_branch.yaml
skadi_the_corrupting_heart_red_countess: !include config/skadi_the_corrupting_heart_red_countess.yaml
ines_under_the_flaming_dome: !include config/ines_under_the_flaming_dome.yaml
silverash_never_melting_ice: !include config/silverash_never_melting_ice.yaml
reed_the_frame_shadow_summer_flower: !include config/reed_the_frame_shadow_summer_flower.yaml
eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip: !include config/eyjafjalla_the_hvit_aska_a_picnic_before_a_long_trip.yaml
pepe: !include config/pepe.yaml
nightingale_iakhu_of_flows: !include config/nightingale_iakhu_of_flows.yaml
degenbrecher_the_shadow_of_dark_moon: !include config/degenbrecher_the_shadow_of_dark_moon.yaml
lappland_the_decadenza: !include config/lappland_the_decadenza.yaml
texas_the_omertosa_il_se_de_no: !include config/texas_the_omertosa_il_se_de_no.yaml
executor_the_ex_foedere_allmind_as_one: !include config/executor_the_ex_foedere_allmind_as_one.yaml
virtuosa_diversity_oneness: !include config/virtuosa_diversity_oneness.yaml
chongyue_allround_actor: !include config/chongyue_allround_actor.yaml
nian_thunderbolt_director: !include config/nian_thunderbolt_director.yaml
amiya_solo_around_the_world: !include config/amiya_solo_around_the_world.yaml
zuole_youthful_journey: !include config/zuole_youthful_journey.yaml
shu_spring_feast: !include config/shu_spring_feast.yaml
yu: !include config/yu.yaml

View File

@@ -0,0 +1,12 @@
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

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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: 百练嘉维尔
en-US: Gavial the Invincible
use_json: false
official_id: '202208258'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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
official_id: '202304833'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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
official_id: '202308807'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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
official_id: '202210623'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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
official_id: '202302698'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,13 @@
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'

View File

@@ -0,0 +1,3 @@
import baseConfig from '@aklive2d/eslint-config'
/** @type {import('eslint').Config} */
export default [...baseConfig]

276
packages/operator/index.js Normal file
View File

@@ -0,0 +1,276 @@
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'
export const CONFIG_PATH = path.resolve(
import.meta.dirname,
config.module.operator.config_yaml
)
const CONFIG = yaml.read(CONFIG_PATH)
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) => {
return config.dir_name.voice.sub.map((sub) =>
path.join(
OPERATOR_SOURCE_FOLDER,
name,
config.dir_name.voice.main,
sub.name
)
)
}
const getExtractedFolder = (name) => {
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) => {
return path.join(DIST_DIR, config.dir_name.operator, name)
}
export const has = (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) => {
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.dir_name.logos)
)
}
const operators = generateMapping()
export default operators
export function getOperatorId(name, matcher = '$2$3$4') {
return name.replace(/^(.*)(char_[\d]+)(_[A-Za-z0-9]+)(|_.*)$/g, matcher)
}
export const getOperatorAlternativeId = (id) => {
return getOperatorId(id, '$2$3')
}
export const build = async (namesToBuild) => {
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) => {
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 portraitHub = JSON.parse(
file.readSync(
path.join(
portraitDir,
config.module.operator.MonoBehaviour,
'portrait_hub.json'
)
)
)
const fallback_name_lowerCase = fallback_name.toLowerCase()
const portraitAtlas = portraitHub._sprites.find(
(item) => item.name.toLowerCase() === fallback_name_lowerCase
).atlas
const portraitJson = JSON.parse(
file.readSync(
path.join(
portraitDir,
config.module.operator.MonoBehaviour,
`portraits#${portraitAtlas}.json`
)
)
)
const item = portraitJson._sprites.find(
(item) => item.name.toLowerCase() === fallback_name_lowerCase
)
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,
extractedDir,
targetDir,
_opts = {
useJSON: false,
useSymLink: true,
}
) => {
const assetsJson = []
let skelFilename
if (_opts.useJSON) {
skelFilename = `${filename}.json`
} else {
skelFilename = `${filename}.skel`
}
const atlasFilename = `${filename}.atlas`
const atlasPath = path.join(extractedDir, atlasFilename)
let atlas = await file.read(atlasPath)
const matches = atlas.match(new RegExp(/(.*).png/g))
for (const item of matches) {
let buffer
const alphaCompositeFilename = `${path.parse(item).name}[alpha].png`
if (file.exists(path.join(extractedDir, alphaCompositeFilename))) {
buffer = await alphaComposite.process(
item,
alphaCompositeFilename,
extractedDir
)
} else {
buffer = await alphaComposite.toBuffer(item, extractedDir)
}
assetsJson.push({
filename: item,
content: buffer,
})
atlas = atlas.replace(item, item.replace(/#/g, '%23'))
}
assetsJson.push({
filename: skelFilename,
path: path.join(extractedDir, skelFilename),
})
assetsJson.push({
filename: atlasFilename,
content: atlas,
})
assetsJson.map((item) => {
const dir = path.join(targetDir, item.filename)
if (item.content) {
file.writeSync(item.content, dir)
} else {
if (_opts.useSymLink) {
file.symlink(item.path, dir)
} else {
file.cpSync(item.path, dir)
}
}
})
}
export const init = (name, id) => {
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)
)
foldersToCreate.forEach((dir) => {
file.mkdir(dir)
})
const currentOpertor = officialInfoMapping[id]
if (currentOpertor === undefined) {
throw new Error('Invalid operator id')
}
template.official_id = currentOpertor.id
template.codename = currentOpertor.codename
file.writeSync(
stringify(template),
path.resolve(operatorConfigFolder, `${name}.yaml`)
)
file.appendSync(
`\n${name}: !include ${config.module.operator.config}/${name}.yaml`,
CONFIG_PATH
)
}

View File

@@ -0,0 +1,20 @@
{
"name": "@aklive2d/operator",
"private": true,
"version": "0.0.0",
"main": "index.js",
"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:*"
},
"scripts": {
"build": "mode=build node runner.js",
"init": "mode=init node runner.js",
"lint": "eslint \"*.js\" \"**/*.js\" && prettier --check ."
}
}

View File

@@ -0,0 +1,11 @@
import baseConfig from '@aklive2d/prettier-config'
/**
* @type {import("prettier").Config}
*/
const config = {
...baseConfig,
semi: false,
}
export default config

View File

@@ -0,0 +1,38 @@
import { envParser } from '@aklive2d/libs'
import { build, init } from './index.js'
async function main() {
const { mode, name, id } = envParser.parse({
mode: {
type: 'string',
short: 'm',
},
name: {
type: 'string',
short: 'n',
multiple: true,
default: [],
},
id: {
type: 'string',
},
})
switch (mode) {
case 'build':
await build(name)
break
case 'init':
if (!name.length) {
throw new Error('Please set the operator name.')
}
if (!id) {
throw new Error('Please set the operator id.')
}
init(name[0], id)
break
default:
throw new Error(`Unknown mode: ${mode}`)
}
}
main()