revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s
Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
This commit is contained in:
1 parent
27de078f54
commit
e85e4d74ea
378 files changed
+20710
-22428
No files matched your search
@@ -7,569 +7,575 @@
|
||||
// ── Interfaces ─────────────────────────────────────────────────────
|
||||
|
||||
export interface AssetOffset {
|
||||
x: number;
|
||||
y: number;
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface NitroVisualization {
|
||||
layerCount: number;
|
||||
angle: number;
|
||||
size: number;
|
||||
layers?: Record<string, Record<string, unknown>>;
|
||||
directions?: Record<string, Record<string, unknown>>;
|
||||
colors?: Record<string, unknown>;
|
||||
animations?: Record<string, unknown>;
|
||||
postures?: { defaultPosture?: string; postures?: { id: string; animationId: number }[] };
|
||||
gestures?: { id: string; animationId: number }[];
|
||||
layerCount: number
|
||||
angle: number
|
||||
size: number
|
||||
layers?: Record<string, Record<string, unknown>>
|
||||
directions?: Record<string, Record<string, unknown>>
|
||||
colors?: Record<string, unknown>
|
||||
animations?: Record<string, unknown>
|
||||
postures?: { defaultPosture?: string; postures?: { id: string; animationId: number }[] }
|
||||
gestures?: { id: string; animationId: number }[]
|
||||
}
|
||||
|
||||
// ── Minimal XML Parser ─────────────────────────────────────────────
|
||||
|
||||
export interface XmlNode {
|
||||
tag: string;
|
||||
attrs: Record<string, string>;
|
||||
children: XmlNode[];
|
||||
text: string;
|
||||
tag: string
|
||||
attrs: Record<string, string>
|
||||
children: XmlNode[]
|
||||
text: string
|
||||
}
|
||||
|
||||
export function parseXml(xml: string): XmlNode | null {
|
||||
const stack: XmlNode[] = [];
|
||||
const nodes: XmlNode[] = [];
|
||||
const stack: XmlNode[] = []
|
||||
const nodes: XmlNode[] = []
|
||||
|
||||
// Remove XML declaration and comments
|
||||
const clean = xml
|
||||
.replace(/<\?[^?]*\?>/g, "")
|
||||
.replace(/<!--[\s\S]*?-->/g, "")
|
||||
.trim();
|
||||
.replace(/<\?[^?]*\?>/g, '')
|
||||
.replace(/<!--[\s\S]*?-->/g, '')
|
||||
.trim()
|
||||
|
||||
const tagRe = /<\/?([a-zA-Z_][\w.-]*)((?:\s+[a-zA-Z_][\w.-]*\s*=\s*"[^"]*")*)\s*\/?>/g;
|
||||
let lastIndex = 0;
|
||||
const tagRe = /<\/?([a-zA-Z_][\w.-]*)((?:\s+[a-zA-Z_][\w.-]*\s*=\s*"[^"]*")*)\s*\/?>/g
|
||||
let lastIndex = 0
|
||||
|
||||
for (const match of clean.matchAll(tagRe)) {
|
||||
const fullMatch = match[0];
|
||||
const tagName = match[1];
|
||||
const attrsStr = match[2] || "";
|
||||
const matchIndex = match.index ?? 0;
|
||||
const fullMatch = match[0]
|
||||
const tagName = match[1]
|
||||
const attrsStr = match[2] || ''
|
||||
const matchIndex = match.index ?? 0
|
||||
|
||||
const text = clean.substring(lastIndex, matchIndex).trim();
|
||||
const text = clean.substring(lastIndex, matchIndex).trim()
|
||||
if (text && stack.length > 0) {
|
||||
stack[stack.length - 1].text += text;
|
||||
stack[stack.length - 1].text += text
|
||||
}
|
||||
lastIndex = matchIndex + fullMatch.length;
|
||||
lastIndex = matchIndex + fullMatch.length
|
||||
|
||||
if (fullMatch.startsWith("</")) {
|
||||
if (fullMatch.startsWith('</')) {
|
||||
if (stack.length > 0) {
|
||||
const node = stack.pop()!;
|
||||
const node = stack.pop()!
|
||||
if (stack.length > 0) {
|
||||
stack[stack.length - 1].children.push(node);
|
||||
stack[stack.length - 1].children.push(node)
|
||||
} else {
|
||||
nodes.push(node);
|
||||
nodes.push(node)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const attrs: Record<string, string> = {};
|
||||
const attrRe = /([a-zA-Z_][\w.-]*)\s*=\s*"([^"]*)"/g;
|
||||
const attrs: Record<string, string> = {}
|
||||
const attrRe = /([a-zA-Z_][\w.-]*)\s*=\s*"([^"]*)"/g
|
||||
for (const am of attrsStr.matchAll(attrRe)) {
|
||||
attrs[am[1]] = am[2];
|
||||
attrs[am[1]] = am[2]
|
||||
}
|
||||
|
||||
const node: XmlNode = { tag: tagName, attrs, children: [], text: "" };
|
||||
const node: XmlNode = { tag: tagName, attrs, children: [], text: '' }
|
||||
|
||||
if (fullMatch.endsWith("/>")) {
|
||||
if (fullMatch.endsWith('/>')) {
|
||||
if (stack.length > 0) {
|
||||
stack[stack.length - 1].children.push(node);
|
||||
stack[stack.length - 1].children.push(node)
|
||||
} else {
|
||||
nodes.push(node);
|
||||
nodes.push(node)
|
||||
}
|
||||
} else {
|
||||
stack.push(node);
|
||||
stack.push(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (stack.length > 1) {
|
||||
const node = stack.pop()!;
|
||||
stack[stack.length - 1].children.push(node);
|
||||
const node = stack.pop()!
|
||||
stack[stack.length - 1].children.push(node)
|
||||
}
|
||||
if (stack.length === 1) nodes.push(stack[0]);
|
||||
if (stack.length === 1) nodes.push(stack[0])
|
||||
|
||||
return nodes[0] || null;
|
||||
return nodes[0] || null
|
||||
}
|
||||
|
||||
export function findChild(node: XmlNode, tag: string): XmlNode | undefined {
|
||||
return node.children.find((c) => c.tag === tag);
|
||||
return node.children.find((c) => c.tag === tag)
|
||||
}
|
||||
|
||||
export function findChildren(node: XmlNode, tag: string): XmlNode[] {
|
||||
return node.children.filter((c) => c.tag === tag);
|
||||
return node.children.filter((c) => c.tag === tag)
|
||||
}
|
||||
|
||||
// ── XML Data Processors ────────────────────────────────────────────
|
||||
|
||||
export interface ManifestAsset {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
name: string
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export function processManifest(xml: string): { assets: ManifestAsset[] } {
|
||||
const assets: ManifestAsset[] = [];
|
||||
const root = parseXml(xml);
|
||||
if (!root) return { assets };
|
||||
const assets: ManifestAsset[] = []
|
||||
const root = parseXml(xml)
|
||||
if (!root) return { assets }
|
||||
|
||||
const libNode = root.tag === "manifest" ? findChild(root, "library") : root;
|
||||
if (!libNode) return { assets };
|
||||
const libNode = root.tag === 'manifest' ? findChild(root, 'library') : root
|
||||
if (!libNode) return { assets }
|
||||
|
||||
const assetsNode = findChild(libNode, "assets");
|
||||
if (!assetsNode) return { assets };
|
||||
const assetsNode = findChild(libNode, 'assets')
|
||||
if (!assetsNode) return { assets }
|
||||
|
||||
for (const asset of findChildren(assetsNode, "asset")) {
|
||||
const name = asset.attrs.name || "";
|
||||
if (!name) continue;
|
||||
for (const asset of findChildren(assetsNode, 'asset')) {
|
||||
const name = asset.attrs.name || ''
|
||||
if (!name) continue
|
||||
// Skip shadow sprites
|
||||
if (name.startsWith("sh_")) continue;
|
||||
if (name.startsWith('sh_')) continue
|
||||
|
||||
let x = 0,
|
||||
y = 0;
|
||||
const paramNode = findChild(asset, "param");
|
||||
if (paramNode && paramNode.attrs.key === "offset") {
|
||||
const parts = (paramNode.attrs.value || "").split(",");
|
||||
x = parseInt(parts[0] || "0", 10);
|
||||
y = parseInt(parts[1] || "0", 10);
|
||||
y = 0
|
||||
const paramNode = findChild(asset, 'param')
|
||||
if (paramNode && paramNode.attrs.key === 'offset') {
|
||||
const parts = (paramNode.attrs.value || '').split(',')
|
||||
x = parseInt(parts[0] || '0', 10)
|
||||
y = parseInt(parts[1] || '0', 10)
|
||||
}
|
||||
|
||||
assets.push({ name, x, y });
|
||||
assets.push({ name, x, y })
|
||||
}
|
||||
|
||||
return { assets };
|
||||
return { assets }
|
||||
}
|
||||
|
||||
export interface PaletteEntry {
|
||||
id: number;
|
||||
source?: string;
|
||||
master?: boolean;
|
||||
tags?: string[];
|
||||
breed?: number;
|
||||
colorTag?: number;
|
||||
color1?: string;
|
||||
color2?: string;
|
||||
id: number
|
||||
source?: string
|
||||
master?: boolean
|
||||
tags?: string[]
|
||||
breed?: number
|
||||
colorTag?: number
|
||||
color1?: string
|
||||
color2?: string
|
||||
}
|
||||
|
||||
export function processAssetsXml(xml: string): {
|
||||
offsets: Map<string, AssetOffset>;
|
||||
palettes: Record<string, PaletteEntry>;
|
||||
offsets: Map<string, AssetOffset>
|
||||
palettes: Record<string, PaletteEntry>
|
||||
} {
|
||||
const offsets = new Map<string, AssetOffset>();
|
||||
const palettes: Record<string, PaletteEntry> = {};
|
||||
const root = parseXml(xml);
|
||||
if (!root) return { offsets, palettes };
|
||||
const offsets = new Map<string, AssetOffset>()
|
||||
const palettes: Record<string, PaletteEntry> = {}
|
||||
const root = parseXml(xml)
|
||||
if (!root) return { offsets, palettes }
|
||||
|
||||
const assetsNode = root.tag === "assets" ? root : findChild(root, "assets") || root;
|
||||
const assetsNode = root.tag === 'assets' ? root : findChild(root, 'assets') || root
|
||||
|
||||
for (const asset of findChildren(assetsNode, "asset")) {
|
||||
const name = asset.attrs.name || "";
|
||||
if (!name) continue;
|
||||
for (const asset of findChildren(assetsNode, 'asset')) {
|
||||
const name = asset.attrs.name || ''
|
||||
if (!name) continue
|
||||
|
||||
const x = parseInt(asset.attrs.x || "0", 10);
|
||||
const y = parseInt(asset.attrs.y || "0", 10);
|
||||
const x = parseInt(asset.attrs.x || '0', 10)
|
||||
const y = parseInt(asset.attrs.y || '0', 10)
|
||||
|
||||
offsets.set(name, { x, y });
|
||||
offsets.set(name, { x, y })
|
||||
}
|
||||
|
||||
// Parse palettes
|
||||
for (const pal of findChildren(assetsNode, "palette")) {
|
||||
const id = parseInt(pal.attrs.id || "0", 10);
|
||||
const entry: PaletteEntry = { id };
|
||||
if (pal.attrs.source) entry.source = pal.attrs.source;
|
||||
if (pal.attrs.master === "1" || pal.attrs.master === "true") entry.master = true;
|
||||
if (pal.attrs.tags) entry.tags = pal.attrs.tags.split(",");
|
||||
if (pal.attrs.breed) entry.breed = parseInt(pal.attrs.breed, 10);
|
||||
if (pal.attrs.colorTag) entry.colorTag = parseInt(pal.attrs.colorTag, 10);
|
||||
if (pal.attrs.color1) entry.color1 = pal.attrs.color1;
|
||||
if (pal.attrs.color2) entry.color2 = pal.attrs.color2;
|
||||
palettes[String(id)] = entry;
|
||||
for (const pal of findChildren(assetsNode, 'palette')) {
|
||||
const id = parseInt(pal.attrs.id || '0', 10)
|
||||
const entry: PaletteEntry = { id }
|
||||
if (pal.attrs.source) entry.source = pal.attrs.source
|
||||
if (pal.attrs.master === '1' || pal.attrs.master === 'true') entry.master = true
|
||||
if (pal.attrs.tags) entry.tags = pal.attrs.tags.split(',')
|
||||
if (pal.attrs.breed) entry.breed = parseInt(pal.attrs.breed, 10)
|
||||
if (pal.attrs.colorTag) entry.colorTag = parseInt(pal.attrs.colorTag, 10)
|
||||
if (pal.attrs.color1) entry.color1 = pal.attrs.color1
|
||||
if (pal.attrs.color2) entry.color2 = pal.attrs.color2
|
||||
palettes[String(id)] = entry
|
||||
}
|
||||
|
||||
return { offsets, palettes };
|
||||
return { offsets, palettes }
|
||||
}
|
||||
|
||||
export function processIndex(
|
||||
xml: string,
|
||||
): { visualizationType: string; logicType: string; specialtype: number } | null {
|
||||
const root = parseXml(xml);
|
||||
if (!root) return null;
|
||||
const root = parseXml(xml)
|
||||
if (!root) return null
|
||||
|
||||
const obj = root.tag === "object" ? root : findChild(root, "object");
|
||||
if (!obj) return null;
|
||||
const obj = root.tag === 'object' ? root : findChild(root, 'object')
|
||||
if (!obj) return null
|
||||
|
||||
return {
|
||||
visualizationType: obj.attrs.visualization || "",
|
||||
logicType: obj.attrs.logic || "",
|
||||
specialtype: parseInt(obj.attrs.specialtype || "0", 10),
|
||||
};
|
||||
visualizationType: obj.attrs.visualization || '',
|
||||
logicType: obj.attrs.logic || '',
|
||||
specialtype: parseInt(obj.attrs.specialtype || '0', 10),
|
||||
}
|
||||
}
|
||||
|
||||
export interface LogicResult {
|
||||
dimensions: { x: number; y: number; z: number; centerZ?: number };
|
||||
directions: number[];
|
||||
maskType: string;
|
||||
canstandon: boolean;
|
||||
cansiton: boolean;
|
||||
canlayon: boolean;
|
||||
action?: { link?: string; startState?: number };
|
||||
credits?: string;
|
||||
soundSample?: { id: number; noPitch?: boolean };
|
||||
customVars?: { variables: string[] };
|
||||
particleSystems?: Record<string, unknown>[];
|
||||
planetSystems?: Record<string, unknown>[];
|
||||
dimensions: { x: number; y: number; z: number; centerZ?: number }
|
||||
directions: number[]
|
||||
maskType: string
|
||||
canstandon: boolean
|
||||
cansiton: boolean
|
||||
canlayon: boolean
|
||||
action?: { link?: string; startState?: number }
|
||||
credits?: string
|
||||
soundSample?: { id: number; noPitch?: boolean }
|
||||
customVars?: { variables: string[] }
|
||||
particleSystems?: Record<string, unknown>[]
|
||||
planetSystems?: Record<string, unknown>[]
|
||||
}
|
||||
|
||||
export function processLogic(xml: string): LogicResult {
|
||||
const defaults: LogicResult = {
|
||||
dimensions: { x: 1, y: 1, z: 0 },
|
||||
directions: [0],
|
||||
maskType: "",
|
||||
maskType: '',
|
||||
canstandon: false,
|
||||
cansiton: false,
|
||||
canlayon: false,
|
||||
};
|
||||
const root = parseXml(xml);
|
||||
if (!root) return defaults;
|
||||
}
|
||||
const root = parseXml(xml)
|
||||
if (!root) return defaults
|
||||
|
||||
const model = findChild(root, "model") || root;
|
||||
const dims = findChild(model, "dimensions");
|
||||
const dirsNode = findChild(model, "directions");
|
||||
const model = findChild(root, 'model') || root
|
||||
const dims = findChild(model, 'dimensions')
|
||||
const dirsNode = findChild(model, 'directions')
|
||||
|
||||
const dimensions: LogicResult["dimensions"] = {
|
||||
x: parseInt(dims?.attrs.x || "1", 10),
|
||||
y: parseInt(dims?.attrs.y || "1", 10),
|
||||
z: parseFloat(dims?.attrs.z || "0"),
|
||||
};
|
||||
const dimensions: LogicResult['dimensions'] = {
|
||||
x: parseInt(dims?.attrs.x || '1', 10),
|
||||
y: parseInt(dims?.attrs.y || '1', 10),
|
||||
z: parseFloat(dims?.attrs.z || '0'),
|
||||
}
|
||||
if (dims?.attrs.centerZ) {
|
||||
dimensions.centerZ = parseFloat(dims.attrs.centerZ);
|
||||
dimensions.centerZ = parseFloat(dims.attrs.centerZ)
|
||||
}
|
||||
|
||||
const directions: number[] = [];
|
||||
const directions: number[] = []
|
||||
if (dirsNode) {
|
||||
for (const d of findChildren(dirsNode, "direction")) {
|
||||
directions.push(parseInt(d.attrs.id || "0", 10));
|
||||
for (const d of findChildren(dirsNode, 'direction')) {
|
||||
directions.push(parseInt(d.attrs.id || '0', 10))
|
||||
}
|
||||
}
|
||||
// Default directions [0, 90] when missing (matches Retrosprite behavior)
|
||||
if (directions.length === 0) directions.push(0, 90);
|
||||
if (directions.length === 0) directions.push(0, 90)
|
||||
|
||||
// Parse mask type
|
||||
const maskNode = findChild(root, "mask");
|
||||
const maskType = maskNode?.attrs.type || "";
|
||||
const maskNode = findChild(root, 'mask')
|
||||
const maskType = maskNode?.attrs.type || ''
|
||||
|
||||
// Infer canstandon/cansiton/canlayon from action nodes
|
||||
const actions = findChildren(root, "action");
|
||||
const cansiton = actions.some((a) => a.attrs.kind === "sit");
|
||||
const canlayon = actions.some((a) => a.attrs.kind === "lay");
|
||||
const canstandon = !cansiton && !canlayon && dimensions.z > 0;
|
||||
const actions = findChildren(root, 'action')
|
||||
const cansiton = actions.some((a) => a.attrs.kind === 'sit')
|
||||
const canlayon = actions.some((a) => a.attrs.kind === 'lay')
|
||||
const canstandon = !cansiton && !canlayon && dimensions.z > 0
|
||||
|
||||
const result: LogicResult = { dimensions, directions, maskType, canstandon, cansiton, canlayon };
|
||||
const result: LogicResult = { dimensions, directions, maskType, canstandon, cansiton, canlayon }
|
||||
|
||||
// Parse action link/startState
|
||||
const actionNode = findChild(root, "action");
|
||||
const actionNode = findChild(root, 'action')
|
||||
if (actionNode && (actionNode.attrs.link || actionNode.attrs.startState)) {
|
||||
const action: LogicResult["action"] = {};
|
||||
if (actionNode.attrs.link) action.link = actionNode.attrs.link;
|
||||
if (actionNode.attrs.startState) action.startState = parseInt(actionNode.attrs.startState, 10);
|
||||
result.action = action;
|
||||
const action: LogicResult['action'] = {}
|
||||
if (actionNode.attrs.link) action.link = actionNode.attrs.link
|
||||
if (actionNode.attrs.startState) action.startState = parseInt(actionNode.attrs.startState, 10)
|
||||
result.action = action
|
||||
}
|
||||
|
||||
// Parse credits
|
||||
const creditsNode = findChild(root, "credits");
|
||||
const creditsNode = findChild(root, 'credits')
|
||||
if (creditsNode?.attrs.value) {
|
||||
result.credits = creditsNode.attrs.value;
|
||||
result.credits = creditsNode.attrs.value
|
||||
}
|
||||
|
||||
// Parse soundSample
|
||||
const soundNode = findChild(root, "sound");
|
||||
const soundNode = findChild(root, 'sound')
|
||||
if (soundNode) {
|
||||
const sampleNode = findChild(soundNode, "sample");
|
||||
const sampleNode = findChild(soundNode, 'sample')
|
||||
if (sampleNode) {
|
||||
const sample: LogicResult["soundSample"] = { id: parseInt(sampleNode.attrs.id || "0", 10) };
|
||||
if (sampleNode.attrs.noPitch === "1") sample.noPitch = true;
|
||||
result.soundSample = sample;
|
||||
const sample: LogicResult['soundSample'] = { id: parseInt(sampleNode.attrs.id || '0', 10) }
|
||||
if (sampleNode.attrs.noPitch === '1') sample.noPitch = true
|
||||
result.soundSample = sample
|
||||
}
|
||||
}
|
||||
|
||||
// Parse customVars
|
||||
const customVarsNode = findChild(root, "customVars");
|
||||
const customVarsNode = findChild(root, 'customVars')
|
||||
if (customVarsNode) {
|
||||
const variables: string[] = [];
|
||||
for (const v of findChildren(customVarsNode, "variable")) {
|
||||
if (v.attrs.name) variables.push(v.attrs.name);
|
||||
const variables: string[] = []
|
||||
for (const v of findChildren(customVarsNode, 'variable')) {
|
||||
if (v.attrs.name) variables.push(v.attrs.name)
|
||||
}
|
||||
if (variables.length > 0) result.customVars = { variables };
|
||||
if (variables.length > 0) result.customVars = { variables }
|
||||
}
|
||||
|
||||
// Parse particleSystems
|
||||
const particleNode = findChild(root, "particleSystems");
|
||||
const particleNode = findChild(root, 'particleSystems')
|
||||
if (particleNode) {
|
||||
const systems: Record<string, unknown>[] = [];
|
||||
for (const ps of findChildren(particleNode, "particleSystem")) {
|
||||
const system: Record<string, unknown> = {};
|
||||
if (ps.attrs.size) system.size = parseInt(ps.attrs.size, 10);
|
||||
if (ps.attrs.canvasId) system.canvasId = parseInt(ps.attrs.canvasId, 10);
|
||||
if (ps.attrs.offsetY) system.offsetY = parseInt(ps.attrs.offsetY, 10);
|
||||
if (ps.attrs.blend) system.blend = parseInt(ps.attrs.blend, 10);
|
||||
if (ps.attrs.bgColor) system.bgColor = ps.attrs.bgColor;
|
||||
const systems: Record<string, unknown>[] = []
|
||||
for (const ps of findChildren(particleNode, 'particleSystem')) {
|
||||
const system: Record<string, unknown> = {}
|
||||
if (ps.attrs.size) system.size = parseInt(ps.attrs.size, 10)
|
||||
if (ps.attrs.canvasId) system.canvasId = parseInt(ps.attrs.canvasId, 10)
|
||||
if (ps.attrs.offsetY) system.offsetY = parseInt(ps.attrs.offsetY, 10)
|
||||
if (ps.attrs.blend) system.blend = parseInt(ps.attrs.blend, 10)
|
||||
if (ps.attrs.bgColor) system.bgColor = ps.attrs.bgColor
|
||||
|
||||
const emitters: Record<string, unknown>[] = [];
|
||||
for (const em of findChildren(ps, "emitter")) {
|
||||
const emitter: Record<string, unknown> = {};
|
||||
if (em.attrs.id) emitter.id = parseInt(em.attrs.id, 10);
|
||||
if (em.attrs.name) emitter.name = em.attrs.name;
|
||||
if (em.attrs.spriteId) emitter.spriteId = parseInt(em.attrs.spriteId, 10);
|
||||
if (em.attrs.maxNumParticles) emitter.maxNumParticles = parseInt(em.attrs.maxNumParticles, 10);
|
||||
if (em.attrs.particlesPerFrame) emitter.particlesPerFrame = parseInt(em.attrs.particlesPerFrame, 10);
|
||||
if (em.attrs.burstPulse) emitter.burstPulse = parseInt(em.attrs.burstPulse, 10);
|
||||
if (em.attrs.fuseTime) emitter.fuseTime = parseInt(em.attrs.fuseTime, 10);
|
||||
const emitters: Record<string, unknown>[] = []
|
||||
for (const em of findChildren(ps, 'emitter')) {
|
||||
const emitter: Record<string, unknown> = {}
|
||||
if (em.attrs.id) emitter.id = parseInt(em.attrs.id, 10)
|
||||
if (em.attrs.name) emitter.name = em.attrs.name
|
||||
if (em.attrs.spriteId) emitter.spriteId = parseInt(em.attrs.spriteId, 10)
|
||||
if (em.attrs.maxNumParticles)
|
||||
emitter.maxNumParticles = parseInt(em.attrs.maxNumParticles, 10)
|
||||
if (em.attrs.particlesPerFrame)
|
||||
emitter.particlesPerFrame = parseInt(em.attrs.particlesPerFrame, 10)
|
||||
if (em.attrs.burstPulse) emitter.burstPulse = parseInt(em.attrs.burstPulse, 10)
|
||||
if (em.attrs.fuseTime) emitter.fuseTime = parseInt(em.attrs.fuseTime, 10)
|
||||
|
||||
const simNode = findChild(em, "simulation");
|
||||
const simNode = findChild(em, 'simulation')
|
||||
if (simNode) {
|
||||
const simulation: Record<string, unknown> = {};
|
||||
if (simNode.attrs.force) simulation.force = parseFloat(simNode.attrs.force);
|
||||
if (simNode.attrs.direction) simulation.direction = parseFloat(simNode.attrs.direction);
|
||||
if (simNode.attrs.gravity) simulation.gravity = parseFloat(simNode.attrs.gravity);
|
||||
if (simNode.attrs.airFriction) simulation.airFriction = parseFloat(simNode.attrs.airFriction);
|
||||
if (simNode.attrs.shape) simulation.shape = simNode.attrs.shape;
|
||||
if (simNode.attrs.energy) simulation.energy = parseFloat(simNode.attrs.energy);
|
||||
emitter.simulation = simulation;
|
||||
const simulation: Record<string, unknown> = {}
|
||||
if (simNode.attrs.force) simulation.force = parseFloat(simNode.attrs.force)
|
||||
if (simNode.attrs.direction) simulation.direction = parseFloat(simNode.attrs.direction)
|
||||
if (simNode.attrs.gravity) simulation.gravity = parseFloat(simNode.attrs.gravity)
|
||||
if (simNode.attrs.airFriction)
|
||||
simulation.airFriction = parseFloat(simNode.attrs.airFriction)
|
||||
if (simNode.attrs.shape) simulation.shape = simNode.attrs.shape
|
||||
if (simNode.attrs.energy) simulation.energy = parseFloat(simNode.attrs.energy)
|
||||
emitter.simulation = simulation
|
||||
}
|
||||
|
||||
const particles: Record<string, unknown>[] = [];
|
||||
for (const p of findChildren(em, "particle")) {
|
||||
const particle: Record<string, unknown> = {};
|
||||
if (p.attrs.isEmitter === "1") particle.isEmitter = true;
|
||||
if (p.attrs.lifeTime) particle.lifeTime = parseInt(p.attrs.lifeTime, 10);
|
||||
if (p.attrs.fade === "1") particle.fade = true;
|
||||
const pFrames: string[] = [];
|
||||
for (const pf of findChildren(p, "frame")) {
|
||||
if (pf.attrs.name) pFrames.push(pf.attrs.name);
|
||||
const particles: Record<string, unknown>[] = []
|
||||
for (const p of findChildren(em, 'particle')) {
|
||||
const particle: Record<string, unknown> = {}
|
||||
if (p.attrs.isEmitter === '1') particle.isEmitter = true
|
||||
if (p.attrs.lifeTime) particle.lifeTime = parseInt(p.attrs.lifeTime, 10)
|
||||
if (p.attrs.fade === '1') particle.fade = true
|
||||
const pFrames: string[] = []
|
||||
for (const pf of findChildren(p, 'frame')) {
|
||||
if (pf.attrs.name) pFrames.push(pf.attrs.name)
|
||||
}
|
||||
if (pFrames.length > 0) particle.frames = pFrames;
|
||||
particles.push(particle);
|
||||
if (pFrames.length > 0) particle.frames = pFrames
|
||||
particles.push(particle)
|
||||
}
|
||||
if (particles.length > 0) emitter.particles = particles;
|
||||
emitters.push(emitter);
|
||||
if (particles.length > 0) emitter.particles = particles
|
||||
emitters.push(emitter)
|
||||
}
|
||||
if (emitters.length > 0) system.emitters = emitters;
|
||||
systems.push(system);
|
||||
if (emitters.length > 0) system.emitters = emitters
|
||||
systems.push(system)
|
||||
}
|
||||
if (systems.length > 0) result.particleSystems = systems;
|
||||
if (systems.length > 0) result.particleSystems = systems
|
||||
}
|
||||
|
||||
// Parse planetSystems
|
||||
const planetNode = findChild(root, "planetSystems");
|
||||
const planetNode = findChild(root, 'planetSystems')
|
||||
if (planetNode) {
|
||||
const systems: Record<string, unknown>[] = [];
|
||||
const systems: Record<string, unknown>[] = []
|
||||
for (const ps of planetNode.children) {
|
||||
const system: Record<string, unknown> = { ...ps.attrs };
|
||||
const system: Record<string, unknown> = { ...ps.attrs }
|
||||
// Convert numeric attrs
|
||||
for (const key of Object.keys(system)) {
|
||||
const val = system[key] as string;
|
||||
if (/^-?\d+(\.\d+)?$/.test(val)) system[key] = parseFloat(val);
|
||||
const val = system[key] as string
|
||||
if (/^-?\d+(\.\d+)?$/.test(val)) system[key] = parseFloat(val)
|
||||
}
|
||||
systems.push(system);
|
||||
systems.push(system)
|
||||
}
|
||||
if (systems.length > 0) result.planetSystems = systems;
|
||||
if (systems.length > 0) result.planetSystems = systems
|
||||
}
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
export function processVisualization(xml: string): NitroVisualization[] {
|
||||
const result: NitroVisualization[] = [];
|
||||
const root = parseXml(xml);
|
||||
if (!root) return result;
|
||||
const result: NitroVisualization[] = []
|
||||
const root = parseXml(xml)
|
||||
if (!root) return result
|
||||
|
||||
const graphics = findChild(root, "graphics") || root;
|
||||
const graphics = findChild(root, 'graphics') || root
|
||||
|
||||
for (const viz of findChildren(graphics, "visualization")) {
|
||||
const size = parseInt(viz.attrs.size || "64", 10);
|
||||
for (const viz of findChildren(graphics, 'visualization')) {
|
||||
const size = parseInt(viz.attrs.size || '64', 10)
|
||||
// Skip size 32 (small catalog icons) – only keep size 1 (special) and 64 (standard)
|
||||
if (size === 32) continue;
|
||||
if (size === 32) continue
|
||||
|
||||
const layerCount = parseInt(viz.attrs.layerCount || "1", 10);
|
||||
const angle = parseInt(viz.attrs.angle || "45", 10);
|
||||
const layerCount = parseInt(viz.attrs.layerCount || '1', 10)
|
||||
const angle = parseInt(viz.attrs.angle || '45', 10)
|
||||
|
||||
const entry: NitroVisualization = { layerCount, angle, size };
|
||||
const entry: NitroVisualization = { layerCount, angle, size }
|
||||
|
||||
// Layers (before directions to match reference key order)
|
||||
const layersNode = findChild(viz, "layers");
|
||||
const layersNode = findChild(viz, 'layers')
|
||||
if (layersNode) {
|
||||
const layers: Record<string, Record<string, unknown>> = {};
|
||||
for (const l of findChildren(layersNode, "layer")) {
|
||||
const id = l.attrs.id || "0";
|
||||
const layerObj: Record<string, unknown> = {};
|
||||
if (l.attrs.x) layerObj.x = parseInt(l.attrs.x, 10);
|
||||
if (l.attrs.y) layerObj.y = parseInt(l.attrs.y, 10);
|
||||
if (l.attrs.z) layerObj.z = parseInt(l.attrs.z, 10);
|
||||
if (l.attrs.alpha) layerObj.alpha = parseInt(l.attrs.alpha, 10);
|
||||
if (l.attrs.ink) layerObj.ink = l.attrs.ink;
|
||||
if (l.attrs.tag) layerObj.tag = l.attrs.tag;
|
||||
if (l.attrs.ignoreMouse) layerObj.ignoreMouse = l.attrs.ignoreMouse === "1";
|
||||
layers[id] = layerObj;
|
||||
const layers: Record<string, Record<string, unknown>> = {}
|
||||
for (const l of findChildren(layersNode, 'layer')) {
|
||||
const id = l.attrs.id || '0'
|
||||
const layerObj: Record<string, unknown> = {}
|
||||
if (l.attrs.x) layerObj.x = parseInt(l.attrs.x, 10)
|
||||
if (l.attrs.y) layerObj.y = parseInt(l.attrs.y, 10)
|
||||
if (l.attrs.z) layerObj.z = parseInt(l.attrs.z, 10)
|
||||
if (l.attrs.alpha) layerObj.alpha = parseInt(l.attrs.alpha, 10)
|
||||
if (l.attrs.ink) layerObj.ink = l.attrs.ink
|
||||
if (l.attrs.tag) layerObj.tag = l.attrs.tag
|
||||
if (l.attrs.ignoreMouse) layerObj.ignoreMouse = l.attrs.ignoreMouse === '1'
|
||||
layers[id] = layerObj
|
||||
}
|
||||
if (Object.keys(layers).length > 0) entry.layers = layers;
|
||||
if (Object.keys(layers).length > 0) entry.layers = layers
|
||||
}
|
||||
|
||||
// Directions
|
||||
const dirsNode = findChild(viz, "directions");
|
||||
const dirsNode = findChild(viz, 'directions')
|
||||
if (dirsNode) {
|
||||
const dirs: Record<string, Record<string, unknown>> = {};
|
||||
for (const d of findChildren(dirsNode, "direction")) {
|
||||
const id = d.attrs.id || "0";
|
||||
const layersInDir = findChildren(d, "layer");
|
||||
const dirs: Record<string, Record<string, unknown>> = {}
|
||||
for (const d of findChildren(dirsNode, 'direction')) {
|
||||
const id = d.attrs.id || '0'
|
||||
const layersInDir = findChildren(d, 'layer')
|
||||
if (layersInDir.length > 0) {
|
||||
const layerData: Record<string, Record<string, unknown>> = {};
|
||||
const layerData: Record<string, Record<string, unknown>> = {}
|
||||
for (const l of layersInDir) {
|
||||
const lid = l.attrs.id || "0";
|
||||
const layerObj: Record<string, unknown> = {};
|
||||
if (l.attrs.x) layerObj.x = parseInt(l.attrs.x, 10);
|
||||
if (l.attrs.y) layerObj.y = parseInt(l.attrs.y, 10);
|
||||
if (l.attrs.z) layerObj.z = parseFloat(l.attrs.z);
|
||||
if (l.attrs.alpha) layerObj.alpha = parseInt(l.attrs.alpha, 10);
|
||||
if (l.attrs.ink) layerObj.ink = l.attrs.ink;
|
||||
if (l.attrs.tag) layerObj.tag = l.attrs.tag;
|
||||
if (l.attrs.ignoreMouse) layerObj.ignoreMouse = l.attrs.ignoreMouse === "1";
|
||||
layerData[lid] = layerObj;
|
||||
const lid = l.attrs.id || '0'
|
||||
const layerObj: Record<string, unknown> = {}
|
||||
if (l.attrs.x) layerObj.x = parseInt(l.attrs.x, 10)
|
||||
if (l.attrs.y) layerObj.y = parseInt(l.attrs.y, 10)
|
||||
if (l.attrs.z) layerObj.z = parseFloat(l.attrs.z)
|
||||
if (l.attrs.alpha) layerObj.alpha = parseInt(l.attrs.alpha, 10)
|
||||
if (l.attrs.ink) layerObj.ink = l.attrs.ink
|
||||
if (l.attrs.tag) layerObj.tag = l.attrs.tag
|
||||
if (l.attrs.ignoreMouse) layerObj.ignoreMouse = l.attrs.ignoreMouse === '1'
|
||||
layerData[lid] = layerObj
|
||||
}
|
||||
dirs[id] = { layers: layerData };
|
||||
dirs[id] = { layers: layerData }
|
||||
} else {
|
||||
dirs[id] = {};
|
||||
dirs[id] = {}
|
||||
}
|
||||
}
|
||||
entry.directions = dirs;
|
||||
entry.directions = dirs
|
||||
}
|
||||
|
||||
// Colors
|
||||
const colorsNode = findChild(viz, "colors");
|
||||
const colorsNode = findChild(viz, 'colors')
|
||||
if (colorsNode) {
|
||||
const colors: Record<string, { layers: Record<string, { color: number }> }> = {};
|
||||
for (const color of findChildren(colorsNode, "color")) {
|
||||
const cid = color.attrs.id || "0";
|
||||
const colorLayers: Record<string, { color: number }> = {};
|
||||
for (const cl of findChildren(color, "colorLayer")) {
|
||||
colorLayers[cl.attrs.id || "0"] = { color: parseInt(cl.attrs.color || "0", 16) };
|
||||
const colors: Record<string, { layers: Record<string, { color: number }> }> = {}
|
||||
for (const color of findChildren(colorsNode, 'color')) {
|
||||
const cid = color.attrs.id || '0'
|
||||
const colorLayers: Record<string, { color: number }> = {}
|
||||
for (const cl of findChildren(color, 'colorLayer')) {
|
||||
colorLayers[cl.attrs.id || '0'] = { color: parseInt(cl.attrs.color || '0', 16) }
|
||||
}
|
||||
colors[cid] = { layers: colorLayers };
|
||||
colors[cid] = { layers: colorLayers }
|
||||
}
|
||||
if (Object.keys(colors).length > 0) entry.colors = colors;
|
||||
if (Object.keys(colors).length > 0) entry.colors = colors
|
||||
}
|
||||
|
||||
// Animations
|
||||
const animsNode = findChild(viz, "animations");
|
||||
const animsNode = findChild(viz, 'animations')
|
||||
if (animsNode) {
|
||||
const animations: Record<string, unknown> = {};
|
||||
for (const anim of findChildren(animsNode, "animation")) {
|
||||
const aid = anim.attrs.id || "0";
|
||||
const animLayers: Record<string, unknown> = {};
|
||||
for (const al of findChildren(anim, "animationLayer")) {
|
||||
const alid = al.attrs.id || "0";
|
||||
const frameSeqs: Record<string, unknown> = {};
|
||||
for (const fs of findChildren(al, "frameSequence")) {
|
||||
const fsid = fs.attrs.id || Object.keys(frameSeqs).length.toString();
|
||||
const frames: Record<string, Record<string, unknown>> = {};
|
||||
let fi = 0;
|
||||
for (const f of findChildren(fs, "frame")) {
|
||||
const rawId = f.attrs.id || "0";
|
||||
const animations: Record<string, unknown> = {}
|
||||
for (const anim of findChildren(animsNode, 'animation')) {
|
||||
const aid = anim.attrs.id || '0'
|
||||
const animLayers: Record<string, unknown> = {}
|
||||
for (const al of findChildren(anim, 'animationLayer')) {
|
||||
const alid = al.attrs.id || '0'
|
||||
const frameSeqs: Record<string, unknown> = {}
|
||||
for (const fs of findChildren(al, 'frameSequence')) {
|
||||
const fsid = fs.attrs.id || Object.keys(frameSeqs).length.toString()
|
||||
const frames: Record<string, Record<string, unknown>> = {}
|
||||
let fi = 0
|
||||
for (const f of findChildren(fs, 'frame')) {
|
||||
const rawId = f.attrs.id || '0'
|
||||
const frameObj: Record<string, unknown> = {
|
||||
id: (rawId === "NaN" ? 0 : parseInt(rawId, 10)) || 0,
|
||||
};
|
||||
if (f.attrs.x) frameObj.x = parseInt(f.attrs.x, 10);
|
||||
if (f.attrs.y) frameObj.y = parseInt(f.attrs.y, 10);
|
||||
if (f.attrs.randomX) frameObj.randomX = parseInt(f.attrs.randomX, 10);
|
||||
if (f.attrs.randomY) frameObj.randomY = parseInt(f.attrs.randomY, 10);
|
||||
// Per-direction offsets (keyed by sequential index, matching Retrosprite format)
|
||||
const offsetNodes = findChildren(f, "offset");
|
||||
if (offsetNodes.length > 0) {
|
||||
const offsets: Record<string, { direction: number; x: number; y: number }> = {};
|
||||
for (let oi = 0; oi < offsetNodes.length; oi++) {
|
||||
const o = offsetNodes[oi];
|
||||
offsets[String(oi)] = {
|
||||
direction: parseInt(o.attrs.direction || "0", 10),
|
||||
x: parseInt(o.attrs.x || "0", 10),
|
||||
y: parseInt(o.attrs.y || "0", 10),
|
||||
};
|
||||
}
|
||||
frameObj.offsets = offsets;
|
||||
id: (rawId === 'NaN' ? 0 : parseInt(rawId, 10)) || 0,
|
||||
}
|
||||
frames[String(fi)] = frameObj;
|
||||
fi++;
|
||||
if (f.attrs.x) frameObj.x = parseInt(f.attrs.x, 10)
|
||||
if (f.attrs.y) frameObj.y = parseInt(f.attrs.y, 10)
|
||||
if (f.attrs.randomX) frameObj.randomX = parseInt(f.attrs.randomX, 10)
|
||||
if (f.attrs.randomY) frameObj.randomY = parseInt(f.attrs.randomY, 10)
|
||||
// Per-direction offsets (keyed by sequential index, matching Retrosprite format)
|
||||
const offsetNodes = findChildren(f, 'offset')
|
||||
if (offsetNodes.length > 0) {
|
||||
const offsets: Record<string, { direction: number; x: number; y: number }> = {}
|
||||
for (let oi = 0; oi < offsetNodes.length; oi++) {
|
||||
const o = offsetNodes[oi]
|
||||
offsets[String(oi)] = {
|
||||
direction: parseInt(o.attrs.direction || '0', 10),
|
||||
x: parseInt(o.attrs.x || '0', 10),
|
||||
y: parseInt(o.attrs.y || '0', 10),
|
||||
}
|
||||
}
|
||||
frameObj.offsets = offsets
|
||||
}
|
||||
frames[String(fi)] = frameObj
|
||||
fi++
|
||||
}
|
||||
const seqObj: Record<string, unknown> = { frames };
|
||||
if (fs.attrs.loopCount) seqObj.loopCount = parseInt(fs.attrs.loopCount, 10);
|
||||
if (fs.attrs.random) seqObj.random = parseInt(fs.attrs.random, 10);
|
||||
frameSeqs[fsid] = seqObj;
|
||||
const seqObj: Record<string, unknown> = { frames }
|
||||
if (fs.attrs.loopCount) seqObj.loopCount = parseInt(fs.attrs.loopCount, 10)
|
||||
if (fs.attrs.random) seqObj.random = parseInt(fs.attrs.random, 10)
|
||||
frameSeqs[fsid] = seqObj
|
||||
}
|
||||
const layerObj: Record<string, unknown> = {
|
||||
loopCount: parseInt(al.attrs.loopCount || "0", 10),
|
||||
loopCount: parseInt(al.attrs.loopCount || '0', 10),
|
||||
frameSequences: frameSeqs,
|
||||
};
|
||||
if (al.attrs.frameRepeat) layerObj.frameRepeat = parseInt(al.attrs.frameRepeat, 10);
|
||||
if (al.attrs.random) layerObj.random = parseInt(al.attrs.random, 10);
|
||||
animLayers[alid] = layerObj;
|
||||
}
|
||||
if (al.attrs.frameRepeat) layerObj.frameRepeat = parseInt(al.attrs.frameRepeat, 10)
|
||||
if (al.attrs.random) layerObj.random = parseInt(al.attrs.random, 10)
|
||||
animLayers[alid] = layerObj
|
||||
}
|
||||
const animObj: Record<string, unknown> = { layers: animLayers };
|
||||
if (anim.attrs.transitionTo) animObj.transitionTo = parseInt(anim.attrs.transitionTo, 10);
|
||||
if (anim.attrs.transitionFrom) animObj.transitionFrom = parseInt(anim.attrs.transitionFrom, 10);
|
||||
if (anim.attrs.immediateChangeFrom) animObj.immediateChangeFrom = anim.attrs.immediateChangeFrom;
|
||||
if (anim.attrs.randomStart === "1") animObj.randomStart = true;
|
||||
animations[aid] = animObj;
|
||||
const animObj: Record<string, unknown> = { layers: animLayers }
|
||||
if (anim.attrs.transitionTo) animObj.transitionTo = parseInt(anim.attrs.transitionTo, 10)
|
||||
if (anim.attrs.transitionFrom)
|
||||
animObj.transitionFrom = parseInt(anim.attrs.transitionFrom, 10)
|
||||
if (anim.attrs.immediateChangeFrom)
|
||||
animObj.immediateChangeFrom = anim.attrs.immediateChangeFrom
|
||||
if (anim.attrs.randomStart === '1') animObj.randomStart = true
|
||||
animations[aid] = animObj
|
||||
}
|
||||
if (Object.keys(animations).length > 0) entry.animations = animations;
|
||||
if (Object.keys(animations).length > 0) entry.animations = animations
|
||||
}
|
||||
|
||||
// Postures
|
||||
const posturesNode = findChild(viz, "postures");
|
||||
const posturesNode = findChild(viz, 'postures')
|
||||
if (posturesNode) {
|
||||
const posturesList: { id: string; animationId: number }[] = [];
|
||||
for (const p of findChildren(posturesNode, "posture")) {
|
||||
const posturesList: { id: string; animationId: number }[] = []
|
||||
for (const p of findChildren(posturesNode, 'posture')) {
|
||||
posturesList.push({
|
||||
id: p.attrs.id || "",
|
||||
animationId: parseInt(p.attrs.animationId || "0", 10),
|
||||
});
|
||||
id: p.attrs.id || '',
|
||||
animationId: parseInt(p.attrs.animationId || '0', 10),
|
||||
})
|
||||
}
|
||||
const posturesObj: NitroVisualization["postures"] = {};
|
||||
if (posturesNode.attrs.defaultPosture) posturesObj.defaultPosture = posturesNode.attrs.defaultPosture;
|
||||
if (posturesList.length > 0) posturesObj.postures = posturesList;
|
||||
entry.postures = posturesObj;
|
||||
const posturesObj: NitroVisualization['postures'] = {}
|
||||
if (posturesNode.attrs.defaultPosture)
|
||||
posturesObj.defaultPosture = posturesNode.attrs.defaultPosture
|
||||
if (posturesList.length > 0) posturesObj.postures = posturesList
|
||||
entry.postures = posturesObj
|
||||
}
|
||||
|
||||
// Gestures
|
||||
const gesturesNode = findChild(viz, "gestures");
|
||||
const gesturesNode = findChild(viz, 'gestures')
|
||||
if (gesturesNode) {
|
||||
const gesturesList: { id: string; animationId: number }[] = [];
|
||||
for (const g of findChildren(gesturesNode, "gesture")) {
|
||||
const gesturesList: { id: string; animationId: number }[] = []
|
||||
for (const g of findChildren(gesturesNode, 'gesture')) {
|
||||
gesturesList.push({
|
||||
id: g.attrs.id || "",
|
||||
animationId: parseInt(g.attrs.animationId || "0", 10),
|
||||
});
|
||||
id: g.attrs.id || '',
|
||||
animationId: parseInt(g.attrs.animationId || '0', 10),
|
||||
})
|
||||
}
|
||||
if (gesturesList.length > 0) entry.gestures = gesturesList;
|
||||
if (gesturesList.length > 0) entry.gestures = gesturesList
|
||||
}
|
||||
|
||||
result.push(entry);
|
||||
result.push(entry)
|
||||
}
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
// ── Metadata Helpers ────────────────────────────────────────────────
|
||||
|
||||
export function extractPartColors(visualizations: NitroVisualization[]): { color: string[] } {
|
||||
const viz64 = visualizations.find((v) => v.size === 64);
|
||||
if (!viz64?.colors) return { color: [] };
|
||||
return { color: Object.keys(viz64.colors) };
|
||||
const viz64 = visualizations.find((v) => v.size === 64)
|
||||
if (!viz64?.colors) return { color: [] }
|
||||
return { color: Object.keys(viz64.colors) }
|
||||
}
|
||||
|
||||
export function computeDefaultDir(directions: number[]): number {
|
||||
if (directions.includes(2)) return 2;
|
||||
if (directions.includes(0)) return 0;
|
||||
return directions[0] || 0;
|
||||
if (directions.includes(2)) return 2
|
||||
if (directions.includes(0)) return 0
|
||||
return directions[0] || 0
|
||||
}
|
||||
Reference in new issue
Block a user