import path from 'node:path' import Matcher from './libs/content_processor.ts' import { yaml, file } from '@aklive2d/libs' import config from '@aklive2d/config' import operators, { OPERATOR_SOURCE_FOLDER } from '@aklive2d/operator' import { files as backgrounds } from '@aklive2d/background' import { mapping as musics } from '@aklive2d/music' import { getLangs } from '@aklive2d/charword-table' import type { ScalarTag } from 'yaml' import type { Assets, TemplateYAML, ProjectJSON, ProjectJSONProperty, } from './types.ts' const DIST_DIR = path.join(import.meta.dirname, config.dir_name.dist) export const build = (namesToBuild: string[]) => { const err = [] const names = !namesToBuild.length ? Object.keys(operators) : namesToBuild console.log('Generating assets for', names.length, 'operators') const musicMapping = [] // sort music mapping based on background file order for (const item of backgrounds) { if (musics.musicFileMapping[item]) { musicMapping.push(item) } else { err.push( `Music folder doesn't contain music for ${item} background.` ) } } for (const name of names) { const { voiceLangs, subtitleLangs } = getLangs(name) load(name, { backgrounds, voiceLangs, subtitleLangs, music: musicMapping, }) file.symlink( path.join( getDefaultPath(name), config.module.project_json.preview_jpg ), path.join(getDistDir(name), config.module.project_json.preview_jpg) ) } return err } const getDistDir = (name: string) => { return path.join(DIST_DIR, name) } const getDefaultPath = (name: string) => { return path.join(OPERATOR_SOURCE_FOLDER, name) } const getPath = (name: string) => { // if exists, do not use the template const defaultPath = path.join( getDefaultPath(name), config.module.project_json.project_json ) if (file.exists(defaultPath)) { return defaultPath } else { return path.join( import.meta.dirname, config.module.project_json.project_json ) } } const process = ({ name, data, template, }: { name: string data: ProjectJSON template: TemplateYAML }) => { const { r, g, b } = getRGBfromHEXColor(operators[name].color) return { ...data, description: template.description, title: operators[name].title, general: { ...data.general, localization: template.localization, properties: { ...getProperties(template), schemecolor: { order: 0, text: 'ui_browse_properties_scheme_color', type: 'color', value: `${r / 255} ${g / 255} ${b / 255}`, }, }, }, } as ProjectJSON } const getRGBfromHEXColor = (hex: string) => { const hexNo = parseInt(hex.replace('#', ''), 16) const r = (hexNo >> 16) & 255 const g = (hexNo >> 8) & 255 const b = hexNo & 255 return { r, g, b, } } const getProperties = (template: TemplateYAML) => { const properties = template.properties const output = {} as { [key: string]: ProjectJSONProperty } for (let i = 0; i < properties.length; i++) { output[properties[i].key] = { index: i, order: 100 + i, ...properties[i].value, } } return output } const load = async (name: string, assets: Assets) => { // load json from file let data = JSON.parse(await file.read(getPath(name))) as ProjectJSON const matcher = new Matcher('~{', '}', operators[name], { ...assets, ...(() => { const output = {} as { [key: string]: { label: string; value: string }[] } for (const [key, value] of Object.entries(assets)) { output[`${key}Options`] = value.map((b) => { return { label: b, value: b, } }) } return output })(), }) const match = { identify: (value: unknown) => typeof value === 'string' && value.startsWith('!match'), tag: '!match', resolve(str: string) { matcher.content = str return matcher.result }, } as ScalarTag const template = yaml.read( path.resolve( import.meta.dirname, config.module.project_json.template_yaml ), [match] ) as TemplateYAML data = process({ name, data, template, }) file.writeSync( JSON.stringify(data, null, 2), path.join(getDistDir(name), config.module.project_json.project_json) ) }