212 lines
6.4 KiB
TypeScript
212 lines
6.4 KiB
TypeScript
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 { OfficialInfoOperatorConfigV2 } 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: OfficialInfoOperatorConfigV2
|
|
) => {
|
|
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.skinName['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.skinName['zh-CN']} · ${officialInfo.operatorName}`
|
|
codename['en-US'] = `${engSkinName} / ${modelNameNormalized}`
|
|
} else {
|
|
codename['zh-CN'] = officialInfo.operatorName
|
|
codename['en-US'] = modelNameNormalized
|
|
}
|
|
return codename
|
|
}
|
|
|
|
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
|
|
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')
|
|
}
|
|
}
|