diff --git a/apps/directory/src/routes/path/Operator.jsx b/apps/directory/src/routes/path/Operator.jsx index bf629bb..b53543c 100644 --- a/apps/directory/src/routes/path/Operator.jsx +++ b/apps/directory/src/routes/path/Operator.jsx @@ -77,6 +77,7 @@ export default function Operator() { const [voiceSrc, setVoiceSrc] = useState(null) const [isVoicePlaying, _setIsVoicePlaying] = useState(false) const isVoicePlayingRef = useRef(isVoicePlaying) + const [spineAnimationList, setSpineAnimationList] = useState([]) const setVoiceLang = (value) => { voiceLangRef.current = value @@ -154,7 +155,7 @@ export default function Operator() { useEffect(() => { if (config) { - setTitle(getPartialName('name', config.codename[language])) + setTitle(config.codename[language]) } }, [config, language, key, setTitle]) @@ -180,6 +181,9 @@ export default function Operator() { fps: 60, defaultMix: 0.3, success: (player) => { + setSpineAnimationList( + player.skeleton.data.animations.map((e) => e.name) + ) if ( player.skeleton.data.animations .map((e) => e.name) @@ -283,9 +287,9 @@ export default function Operator() { (animation) => { if (voiceLangRef.current) { let id = null - if (animation === 'Idle') id = 'CN_011' - if (animation === 'Interact') id = 'CN_034' - if (animation === 'Special') id = 'CN_042' + if (animation.startsWith('Idle')) id = 'CN_011' + if (animation.startsWith('Interact')) id = 'CN_034' + if (animation.startsWith('Special')) id = 'CN_042' if (id) { setCurrentVoiceId(id) setVoiceSrc( @@ -320,29 +324,15 @@ export default function Operator() { const spineSettings = [ { name: 'animation', - options: [ - { - name: 'idle', - onClick: () => setSpineAnimation('Idle'), + options: spineAnimationList.map((name) => { + return { + name, + onClick: () => setSpineAnimation(name), activeRule: () => { - return spineAnimationName === 'Idle' + return spineAnimationName === name }, - }, - { - name: 'interact', - onClick: () => setSpineAnimation('Interact'), - activeRule: () => { - return spineAnimationName === 'Interact' - }, - }, - { - name: 'special', - onClick: () => setSpineAnimation('Special'), - activeRule: () => { - return spineAnimationName === 'Special' - }, - }, - ], + } + }), }, { name: 'voice', diff --git a/apps/showcase/src/components/player.js b/apps/showcase/src/components/player.js index e0ff089..00b2122 100644 --- a/apps/showcase/src/components/player.js +++ b/apps/showcase/src/components/player.js @@ -16,6 +16,7 @@ export default class Player { #resetTime = window.performance.now() #isPlayingInteract = false #spine + #animationList = [] #default = { fps: 60, padding: { @@ -62,6 +63,9 @@ export default class Player { fps: 60, defaultMix: 0, success: function (widget) { + _this.#animationList = widget.skeleton.data.animations.map( + (e) => e.name + ) if ( widget.skeleton.data.animations .map((e) => e.name) @@ -70,10 +74,15 @@ export default class Player { ) { widget.animationState.setAnimation(0, 'Start', false) } - widget.animationState.addAnimation(0, 'Idle', true, 0) + widget.animationState.addAnimation( + 0, + _this.#selectRandomAnimation('Idle'), + true, + 0 + ) widget.animationState.addListener({ end: (e) => { - if (e.animation.name == 'Interact') { + if (e.animation.name.startsWith('Interact')) { _this.#isPlayingInteract = false } }, @@ -85,13 +94,13 @@ export default class Player { _this.#resetTime = performance.now() let entry = widget.animationState.setAnimation( 0, - 'Special', + _this.#selectRandomAnimation('Special'), false ) entry.mixDuration = 0.3 widget.animationState.addAnimation( 0, - 'Idle', + _this.#selectRandomAnimation('Idle'), true, 0 ) @@ -105,11 +114,16 @@ export default class Player { _this.#isPlayingInteract = true let entry = widget.animationState.setAnimation( 0, - 'Interact', + _this.#selectRandomAnimation('Interact'), false ) entry.mixDuration = 0.3 - widget.animationState.addAnimation(0, 'Idle', true, 0) + widget.animationState.addAnimation( + 0, + _this.#selectRandomAnimation('Idle'), + true, + 0 + ) } document.dispatchEvent(Events.Ready.handler()) }, @@ -124,10 +138,7 @@ export default class Player { success() { this.#loadViewport() - updateHTMLOptions( - this.#spine.skeleton.data.animations.map((e) => e.name), - 'animation-selection' - ) + updateHTMLOptions(this.#animationList, 'animation-selection') } resetPadding() { @@ -176,6 +187,13 @@ export default class Player { }) } + #selectRandomAnimation(name) { + const animationList = this.#animationList.filter((animation) => + animation.startsWith(name) + ) + return animationList[Math.floor(Math.random() * animationList.length)] + } + get usePadding() { return this.#config.usePadding } diff --git a/packages/config/config.yaml b/packages/config/config.yaml index 8dc3b6c..ff8d06d 100644 --- a/packages/config/config.yaml +++ b/packages/config/config.yaml @@ -39,6 +39,10 @@ module: title: zh-CN: '明日方舟:' en-US: 'Arknights: ' + sp_filename_prefix: sp_ + sp_title: + zh-CN: '「SP」 ' + en-US: '[SP] ' project_json: project_json: project.json preview_jpg: preview.jpg diff --git a/packages/config/types.ts b/packages/config/types.ts index 0b1aff9..27e3476 100644 --- a/packages/config/types.ts +++ b/packages/config/types.ts @@ -1,3 +1,8 @@ +export type TitleLanguages = { + 'zh-CN': string + 'en-US': string +} + export type Config = { site_id: string total_size: number @@ -44,10 +49,9 @@ export type Config = { Texture2D: string character_table_json: string skin_table_json: string - title: { - 'zh-CN': string - 'en-US': string - } + title: TitleLanguages + sp_filename_prefix: string + sp_title: TitleLanguages } project_json: { project_json: string diff --git a/packages/operator/config.yaml b/packages/operator/config.yaml index 27e41ed..bd0638e 100644 --- a/packages/operator/config.yaml +++ b/packages/operator/config.yaml @@ -64,6 +64,7 @@ ines_melodic_flutter: !include config/ines_melodic_flutter.yaml wisadel_supernova: !include config/wisadel_supernova.yaml archetto_glory_of_the_devout: !include config/archetto_glory_of_the_devout.yaml kroos_moonlit_voyage: !include config/kroos_moonlit_voyage.yaml +kroos_moonlit_voyage_sp: !include config/kroos_moonlit_voyage_sp.yaml exusiai_the_new_covenant: !include config/exusiai_the_new_covenant.yaml ascalon_phototaxis: !include config/ascalon_phototaxis.yaml lin_summer_flowers_fa137: !include config/lin_summer_flowers_fa137.yaml diff --git a/packages/operator/config/kroos_moonlit_voyage_sp.yaml b/packages/operator/config/kroos_moonlit_voyage_sp.yaml new file mode 100644 index 0000000..be7a043 --- /dev/null +++ b/packages/operator/config/kroos_moonlit_voyage_sp.yaml @@ -0,0 +1,6 @@ +codename: + zh-CN: 星月漂流记 · 克洛丝 + en-US: Moonlit Voyage / Kroos +official_id: '202504992' +isSP: true +viewport_top: 2 \ No newline at end of file diff --git a/packages/operator/index.ts b/packages/operator/index.ts index 38d9a2a..de5c3ba 100644 --- a/packages/operator/index.ts +++ b/packages/operator/index.ts @@ -1,3 +1,4 @@ +import { TitleLanguages } from './../config/types' import path from 'node:path' import { yaml, file, alphaComposite } from '@aklive2d/libs' import config from '@aklive2d/config' @@ -53,8 +54,10 @@ export const generateAssetsJson = async ( extractedDir: string, targetDir: string, _opts: { + isSP?: boolean useSymLink?: boolean } = { + isSP: false, useSymLink: true, } ) => { @@ -64,7 +67,7 @@ export const generateAssetsJson = async ( * Special Cases: * - ines_melodic_flutter */ - filename = getActualFilename(filename, extractedDir) + filename = getActualFilename(filename, extractedDir, _opts.isSP) const skelFilename = findSkel(filename, extractedDir) const atlasFilename = `${filename}.atlas` @@ -160,8 +163,23 @@ const generateMapping = () => { type === 'skin' ? skinEntry.skinId.replace(/@/, '_') : `${skinEntry.charId}_2` + + const regions = Object.keys( + operator.codename + ) as (keyof TitleLanguages)[] + if (operator.isSP) { + regions.forEach((region: keyof TitleLanguages) => { + operator.codename[region] = + `${config.module.operator.sp_title[region]}${operator.codename[region]}` + }) + } // add title - operator.title = `${config.module.operator.title['en-US']}${operator.codename['en-US']} - ${config.module.operator.title['zh-CN']}${operator.codename['zh-CN']}` + operator.title = regions + .map( + (region: keyof TitleLanguages) => + `${config.module.operator.title[region]}${operator.codename[region]}` + ) + .join(' - ') // add type operator.type = operatorInfo.type diff --git a/packages/operator/libs/builder.ts b/packages/operator/libs/builder.ts index f9cbbf4..53d3b45 100644 --- a/packages/operator/libs/builder.ts +++ b/packages/operator/libs/builder.ts @@ -112,6 +112,9 @@ const generateAssets = async (name: string) => { await generateAssetsJson( operators[name].filename, extractedDir, - getDistFolder(name) + getDistFolder(name), + { + isSP: operators[name].isSP, + } ) } diff --git a/packages/operator/libs/utils.ts b/packages/operator/libs/utils.ts index 9351af9..9478335 100644 --- a/packages/operator/libs/utils.ts +++ b/packages/operator/libs/utils.ts @@ -168,7 +168,13 @@ export const findCodename = ( return codename } -export const getActualFilename = (filename: string, dir: string) => { +export const getActualFilename = ( + filename: string, + dir: string, + isSP: boolean = false +) => { + if (isSP) + filename = `${config.module.operator.sp_filename_prefix}${filename}` const files = file.readdirSync(dir) const actualFilename = files.find((e) => { const name = path.parse(e).name diff --git a/packages/operator/types.ts b/packages/operator/types.ts index 74efc2e..1f4c715 100644 --- a/packages/operator/types.ts +++ b/packages/operator/types.ts @@ -21,6 +21,7 @@ export interface OperatorConfig { date: string voice_id: string | null color: string + isSP: boolean // kroos_moonlit_voyage_sp } export type Config = { diff --git a/packages/vite-helpers/index.ts b/packages/vite-helpers/index.ts index 3892dfd..681c107 100644 --- a/packages/vite-helpers/index.ts +++ b/packages/vite-helpers/index.ts @@ -21,6 +21,8 @@ interface DirectoryOperatorConfig extends OperatorConfig { workshopId: string | null } +const SPINE_FILENAME_PREFIX = 'dyn_illust_' + export const copyShowcaseData = ( name: string, { @@ -37,7 +39,13 @@ export const copyShowcaseData = ( ) const spineFilenames = file .readdirSync(operatorAssetsDir) - .filter((item) => item.startsWith('dyn_illust_')) + .filter((item) => + item.startsWith( + operators[name].isSP + ? `${config.module.operator.sp_filename_prefix}${SPINE_FILENAME_PREFIX}` + : SPINE_FILENAME_PREFIX + ) + ) const q = [ { fn: file.symlink, @@ -113,7 +121,8 @@ export const copyShowcaseData = ( }) const filename = getActualFilename( operators[name].filename, - getExtractedFolder(name) + getExtractedFolder(name), + operators[name].isSP ) const buildConfig = { insight_id: config.insight.id, @@ -201,7 +210,8 @@ export const copyDirectoryData = async ({ const curD = cur as DirectoryOperatorConfig curD.filename = getActualFilename( operators[curD.link].filename, - getExtractedFolder(curD.link) + getExtractedFolder(curD.link), + operators[curD.link].isSP ) curD.use_json = findSkel( curD.filename,