mirror of
https://github.com/isledecomp/isle.pizza.git
synced 2026-03-01 06:17:38 +00:00
Add actor editor with animated 3D character preview
Browse and customize all 66 game actors with a Three.js rendered preview featuring skeletal walk cycle animations. Click interaction matches the game's character-dependent behavior (Pepper=hat, Nick=color, etc.). - Parse WDB global parts and global textures for character assembly - Parse and serialize character data (66 entries x 16 bytes) in save files - AnimationParser for .ani binary format with hierarchical keyframe evaluation - Full g_cycles animation table (11 types x 17 animations) driven by move/sound - Per-mesh texture support for hats, torso, and face textures
This commit is contained in:
parent
f7e1a56055
commit
c390c735b4
BIN
public/animations/CNs001Bd.ani
Normal file
BIN
public/animations/CNs001Bd.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Br.ani
Normal file
BIN
public/animations/CNs001Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001La.ani
Normal file
BIN
public/animations/CNs001La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Ma.ani
Normal file
BIN
public/animations/CNs001Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Ni.ani
Normal file
BIN
public/animations/CNs001Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Pa.ani
Normal file
BIN
public/animations/CNs001Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Pe.ani
Normal file
BIN
public/animations/CNs001Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Pg.ani
Normal file
BIN
public/animations/CNs001Pg.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Rd.ani
Normal file
BIN
public/animations/CNs001Rd.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Sk.ani
Normal file
BIN
public/animations/CNs001Sk.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001Sy.ani
Normal file
BIN
public/animations/CNs001Sy.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs001xx.ani
Normal file
BIN
public/animations/CNs001xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002Br.ani
Normal file
BIN
public/animations/CNs002Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002La.ani
Normal file
BIN
public/animations/CNs002La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002Ma.ani
Normal file
BIN
public/animations/CNs002Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002Ni.ani
Normal file
BIN
public/animations/CNs002Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002Pa.ani
Normal file
BIN
public/animations/CNs002Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002Pe.ani
Normal file
BIN
public/animations/CNs002Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs002xx.ani
Normal file
BIN
public/animations/CNs002xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003Br.ani
Normal file
BIN
public/animations/CNs003Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003La.ani
Normal file
BIN
public/animations/CNs003La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003Ma.ani
Normal file
BIN
public/animations/CNs003Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003Ni.ani
Normal file
BIN
public/animations/CNs003Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003Pa.ani
Normal file
BIN
public/animations/CNs003Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003Pe.ani
Normal file
BIN
public/animations/CNs003Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs003xx.ani
Normal file
BIN
public/animations/CNs003xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004Br.ani
Normal file
BIN
public/animations/CNs004Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004La.ani
Normal file
BIN
public/animations/CNs004La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004Ma.ani
Normal file
BIN
public/animations/CNs004Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004Ni.ani
Normal file
BIN
public/animations/CNs004Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004Pa.ani
Normal file
BIN
public/animations/CNs004Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004Pe.ani
Normal file
BIN
public/animations/CNs004Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs004xx.ani
Normal file
BIN
public/animations/CNs004xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005Br.ani
Normal file
BIN
public/animations/CNs005Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005La.ani
Normal file
BIN
public/animations/CNs005La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005Ma.ani
Normal file
BIN
public/animations/CNs005Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005Ni.ani
Normal file
BIN
public/animations/CNs005Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005Pa.ani
Normal file
BIN
public/animations/CNs005Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005Pe.ani
Normal file
BIN
public/animations/CNs005Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs005xx.ani
Normal file
BIN
public/animations/CNs005xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006Br.ani
Normal file
BIN
public/animations/CNs006Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006La.ani
Normal file
BIN
public/animations/CNs006La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006Ma.ani
Normal file
BIN
public/animations/CNs006Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006Ni.ani
Normal file
BIN
public/animations/CNs006Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006Pa.ani
Normal file
BIN
public/animations/CNs006Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006Pe.ani
Normal file
BIN
public/animations/CNs006Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs006xx.ani
Normal file
BIN
public/animations/CNs006xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007Br.ani
Normal file
BIN
public/animations/CNs007Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007La.ani
Normal file
BIN
public/animations/CNs007La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007Ma.ani
Normal file
BIN
public/animations/CNs007Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007Ni.ani
Normal file
BIN
public/animations/CNs007Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007Pa.ani
Normal file
BIN
public/animations/CNs007Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007Pe.ani
Normal file
BIN
public/animations/CNs007Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs007xx.ani
Normal file
BIN
public/animations/CNs007xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008Br.ani
Normal file
BIN
public/animations/CNs008Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008La.ani
Normal file
BIN
public/animations/CNs008La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008Ma.ani
Normal file
BIN
public/animations/CNs008Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008Ni.ani
Normal file
BIN
public/animations/CNs008Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008Pa.ani
Normal file
BIN
public/animations/CNs008Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008Pe.ani
Normal file
BIN
public/animations/CNs008Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs008xx.ani
Normal file
BIN
public/animations/CNs008xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009Br.ani
Normal file
BIN
public/animations/CNs009Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009La.ani
Normal file
BIN
public/animations/CNs009La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009Ma.ani
Normal file
BIN
public/animations/CNs009Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009Ni.ani
Normal file
BIN
public/animations/CNs009Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009Pa.ani
Normal file
BIN
public/animations/CNs009Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009Pe.ani
Normal file
BIN
public/animations/CNs009Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs009xx.ani
Normal file
BIN
public/animations/CNs009xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010Br.ani
Normal file
BIN
public/animations/CNs010Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010La.ani
Normal file
BIN
public/animations/CNs010La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010Ma.ani
Normal file
BIN
public/animations/CNs010Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010Ni.ani
Normal file
BIN
public/animations/CNs010Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010Pa.ani
Normal file
BIN
public/animations/CNs010Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010Pe.ani
Normal file
BIN
public/animations/CNs010Pe.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs010xx.ani
Normal file
BIN
public/animations/CNs010xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs011Br.ani
Normal file
BIN
public/animations/CNs011Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs011La.ani
Normal file
BIN
public/animations/CNs011La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs011Ma.ani
Normal file
BIN
public/animations/CNs011Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs011Ni.ani
Normal file
BIN
public/animations/CNs011Ni.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs011Pa.ani
Normal file
BIN
public/animations/CNs011Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs011xx.ani
Normal file
BIN
public/animations/CNs011xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs012Br.ani
Normal file
BIN
public/animations/CNs012Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs012Ma.ani
Normal file
BIN
public/animations/CNs012Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs012Pa.ani
Normal file
BIN
public/animations/CNs012Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs012xx.ani
Normal file
BIN
public/animations/CNs012xx.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs013Br.ani
Normal file
BIN
public/animations/CNs013Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs013Ma.ani
Normal file
BIN
public/animations/CNs013Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs013Pa.ani
Normal file
BIN
public/animations/CNs013Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs014Br.ani
Normal file
BIN
public/animations/CNs014Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs0x4Ma.ani
Normal file
BIN
public/animations/CNs0x4Ma.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs0x4Pa.ani
Normal file
BIN
public/animations/CNs0x4Pa.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs900Br.ani
Normal file
BIN
public/animations/CNs900Br.ani
Normal file
Binary file not shown.
BIN
public/animations/CNs901BR.ani
Normal file
BIN
public/animations/CNs901BR.ani
Normal file
Binary file not shown.
BIN
public/animations/CNsx11La.ani
Normal file
BIN
public/animations/CNsx11La.ani
Normal file
Binary file not shown.
BIN
public/animations/CNsx11Ni.ani
Normal file
BIN
public/animations/CNsx11Ni.ani
Normal file
Binary file not shown.
16
public/animations/manifest.json
Normal file
16
public/animations/manifest.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"walkAnimations": {
|
||||
"xx": "CNs001xx.ani",
|
||||
"Pe": "CNs001Pe.ani",
|
||||
"Ma": "CNs001Ma.ani",
|
||||
"Pa": "CNs001Pa.ani",
|
||||
"Ni": "CNs001Ni.ani",
|
||||
"La": "CNs001La.ani",
|
||||
"Br": "CNs001Br.ani",
|
||||
"Bd": "CNs001Bd.ani",
|
||||
"Pg": "CNs001Pg.ani",
|
||||
"Rd": "CNs001Rd.ani",
|
||||
"Sy": "CNs001Sy.ani",
|
||||
"Sk": "CNs001Sk.ani"
|
||||
}
|
||||
}
|
||||
217
src/core/formats/AnimationParser.js
Normal file
217
src/core/formats/AnimationParser.js
Normal file
@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Parser for LEGO Island .ani animation files.
|
||||
* Format spec: isle/docs/animation.ksy
|
||||
*
|
||||
* Binary layout:
|
||||
* S32 magic (0x11)
|
||||
* F32 boundingRadius, F32 centerX, F32 centerY, F32 centerZ
|
||||
* S32 hasCameraAnim, S32 unused
|
||||
* U32 numActors
|
||||
* For each actor: U32 nameLen, char[nameLen] name, U32 actorType (if nameLen > 0)
|
||||
* S32 duration (ms)
|
||||
* [optional camera_anim if hasCameraAnim != 0]
|
||||
* tree_node root
|
||||
*
|
||||
* tree_node:
|
||||
* node_data data
|
||||
* U32 numChildren
|
||||
* tree_node[numChildren] children
|
||||
*
|
||||
* node_data:
|
||||
* U32 nameLen, char[nameLen] name (if nameLen > 0)
|
||||
* U16 numTranslationKeys, translation_key[...]
|
||||
* U16 numRotationKeys, rotation_key[...]
|
||||
* U16 numScaleKeys, scale_key[...]
|
||||
* U16 numMorphKeys, morph_key[...]
|
||||
*
|
||||
* Keys share packed time_and_flags (S32): bits 0-23 = time ms, bits 24-31 = flags
|
||||
* translation_key: anim_key + F32 x,y,z
|
||||
* rotation_key: anim_key + F32 angle(w), F32 x, F32 y, F32 z (quaternion)
|
||||
* scale_key: anim_key + F32 x,y,z
|
||||
* morph_key: anim_key + U8 visible
|
||||
*/
|
||||
|
||||
import { BinaryReader } from './BinaryReader.js';
|
||||
|
||||
// Keyframe flags
|
||||
const KEY_ACTIVE = 0x01;
|
||||
const KEY_NEGATE_ROTATION = 0x02;
|
||||
const KEY_SKIP_INTERPOLATION = 0x04;
|
||||
|
||||
export class AnimationParser {
|
||||
/**
|
||||
* @param {ArrayBuffer} buffer
|
||||
*/
|
||||
constructor(buffer) {
|
||||
this.reader = new BinaryReader(buffer);
|
||||
}
|
||||
|
||||
parse() {
|
||||
const magic = this.reader.readS32();
|
||||
if (magic !== 0x11) {
|
||||
throw new Error(`Invalid animation magic: 0x${magic.toString(16)}, expected 0x11`);
|
||||
}
|
||||
|
||||
const boundingRadius = this.reader.readF32();
|
||||
const centerX = this.reader.readF32();
|
||||
const centerY = this.reader.readF32();
|
||||
const centerZ = this.reader.readF32();
|
||||
const hasCameraAnim = this.reader.readS32();
|
||||
const unused = this.reader.readS32();
|
||||
|
||||
const numActors = this.reader.readU32();
|
||||
const actors = [];
|
||||
for (let i = 0; i < numActors; i++) {
|
||||
actors.push(this.parseActorEntry());
|
||||
}
|
||||
|
||||
const duration = this.reader.readS32();
|
||||
|
||||
let cameraAnim = null;
|
||||
if (hasCameraAnim !== 0) {
|
||||
cameraAnim = this.parseCameraAnim();
|
||||
}
|
||||
|
||||
const rootNode = this.parseTreeNode();
|
||||
|
||||
return {
|
||||
boundingRadius,
|
||||
center: { x: centerX, y: centerY, z: centerZ },
|
||||
actors,
|
||||
duration,
|
||||
cameraAnim,
|
||||
rootNode
|
||||
};
|
||||
}
|
||||
|
||||
parseActorEntry() {
|
||||
const nameLen = this.reader.readU32();
|
||||
if (nameLen === 0) {
|
||||
return { name: '', actorType: 0 };
|
||||
}
|
||||
const name = this.reader.readString(nameLen);
|
||||
const actorType = this.reader.readU32();
|
||||
return { name, actorType };
|
||||
}
|
||||
|
||||
parseCameraAnim() {
|
||||
const numTranslationKeys = this.reader.readU16();
|
||||
const translationKeys = [];
|
||||
for (let i = 0; i < numTranslationKeys; i++) {
|
||||
translationKeys.push(this.parseTranslationKey());
|
||||
}
|
||||
|
||||
const numTargetKeys = this.reader.readU16();
|
||||
const targetKeys = [];
|
||||
for (let i = 0; i < numTargetKeys; i++) {
|
||||
targetKeys.push(this.parseTranslationKey());
|
||||
}
|
||||
|
||||
const numRotationKeys = this.reader.readU16();
|
||||
const rotationKeys = [];
|
||||
for (let i = 0; i < numRotationKeys; i++) {
|
||||
rotationKeys.push(this.parseRotationZKey());
|
||||
}
|
||||
|
||||
return { translationKeys, targetKeys, rotationKeys };
|
||||
}
|
||||
|
||||
parseTreeNode() {
|
||||
const data = this.parseNodeData();
|
||||
const numChildren = this.reader.readU32();
|
||||
const children = [];
|
||||
for (let i = 0; i < numChildren; i++) {
|
||||
children.push(this.parseTreeNode());
|
||||
}
|
||||
return { data, children };
|
||||
}
|
||||
|
||||
parseNodeData() {
|
||||
const nameLen = this.reader.readU32();
|
||||
let name = '';
|
||||
if (nameLen > 0) {
|
||||
name = this.reader.readString(nameLen);
|
||||
}
|
||||
|
||||
const numTranslationKeys = this.reader.readU16();
|
||||
const translationKeys = [];
|
||||
for (let i = 0; i < numTranslationKeys; i++) {
|
||||
translationKeys.push(this.parseTranslationKey());
|
||||
}
|
||||
|
||||
const numRotationKeys = this.reader.readU16();
|
||||
const rotationKeys = [];
|
||||
for (let i = 0; i < numRotationKeys; i++) {
|
||||
rotationKeys.push(this.parseRotationKey());
|
||||
}
|
||||
|
||||
const numScaleKeys = this.reader.readU16();
|
||||
const scaleKeys = [];
|
||||
for (let i = 0; i < numScaleKeys; i++) {
|
||||
scaleKeys.push(this.parseScaleKey());
|
||||
}
|
||||
|
||||
const numMorphKeys = this.reader.readU16();
|
||||
const morphKeys = [];
|
||||
for (let i = 0; i < numMorphKeys; i++) {
|
||||
morphKeys.push(this.parseMorphKey());
|
||||
}
|
||||
|
||||
return { name, translationKeys, rotationKeys, scaleKeys, morphKeys };
|
||||
}
|
||||
|
||||
parseAnimKey() {
|
||||
const timeAndFlags = this.reader.readS32();
|
||||
return {
|
||||
time: timeAndFlags & 0xFFFFFF,
|
||||
flags: (timeAndFlags >>> 24) & 0xFF
|
||||
};
|
||||
}
|
||||
|
||||
parseTranslationKey() {
|
||||
const key = this.parseAnimKey();
|
||||
const x = this.reader.readF32();
|
||||
const y = this.reader.readF32();
|
||||
const z = this.reader.readF32();
|
||||
return { ...key, x, y, z };
|
||||
}
|
||||
|
||||
parseRotationKey() {
|
||||
const key = this.parseAnimKey();
|
||||
const w = this.reader.readF32(); // angle/scalar component
|
||||
const x = this.reader.readF32();
|
||||
const y = this.reader.readF32();
|
||||
const z = this.reader.readF32();
|
||||
return { ...key, w, x, y, z };
|
||||
}
|
||||
|
||||
parseScaleKey() {
|
||||
const key = this.parseAnimKey();
|
||||
const x = this.reader.readF32();
|
||||
const y = this.reader.readF32();
|
||||
const z = this.reader.readF32();
|
||||
return { ...key, x, y, z };
|
||||
}
|
||||
|
||||
parseMorphKey() {
|
||||
const key = this.parseAnimKey();
|
||||
const visible = this.reader.readU8();
|
||||
return { ...key, visible: visible !== 0 };
|
||||
}
|
||||
|
||||
parseRotationZKey() {
|
||||
const key = this.parseAnimKey();
|
||||
const z = this.reader.readF32();
|
||||
return { ...key, z };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an animation buffer.
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @returns {Object} Parsed animation data
|
||||
*/
|
||||
export function parseAnimation(buffer) {
|
||||
const parser = new AnimationParser(buffer);
|
||||
return parser.parse();
|
||||
}
|
||||
@ -100,10 +100,31 @@ export class SaveGameParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip character manager data (66 characters * 16 bytes = 1056 bytes)
|
||||
* Parse character manager data (66 characters * 16 bytes = 1056 bytes)
|
||||
* Each character: sound(S32) + move(S32) + mood(U8)
|
||||
* + hatPartNameIndex(U8) + hatNameIndex(U8) + infogronNameIndex(U8)
|
||||
* + armlftNameIndex(U8) + armrtNameIndex(U8) + leglftNameIndex(U8) + legrtNameIndex(U8)
|
||||
*/
|
||||
skipCharacters() {
|
||||
this.reader.skip(66 * 16);
|
||||
parseCharacters() {
|
||||
this.parsed.charactersOffset = this.reader.tell();
|
||||
const characters = [];
|
||||
|
||||
for (let i = 0; i < 66; i++) {
|
||||
characters.push({
|
||||
sound: this.reader.readS32(),
|
||||
move: this.reader.readS32(),
|
||||
mood: this.reader.readU8(),
|
||||
hatPartNameIndex: this.reader.readU8(),
|
||||
hatNameIndex: this.reader.readU8(),
|
||||
infogronNameIndex: this.reader.readU8(),
|
||||
armlftNameIndex: this.reader.readU8(),
|
||||
armrtNameIndex: this.reader.readU8(),
|
||||
leglftNameIndex: this.reader.readU8(),
|
||||
legrtNameIndex: this.reader.readU8()
|
||||
});
|
||||
}
|
||||
|
||||
this.parsed.characters = characters;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -403,7 +424,7 @@ export class SaveGameParser {
|
||||
parse() {
|
||||
this.parseHeader();
|
||||
this.parseVariables();
|
||||
this.skipCharacters();
|
||||
this.parseCharacters();
|
||||
this.skipPlants();
|
||||
this.skipBuildings();
|
||||
this.parseGameStates();
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { SaveGameParser } from './SaveGameParser.js';
|
||||
import { BinaryWriter } from './BinaryWriter.js';
|
||||
import { GameStateTypes, GameStateSizes, Actor, Act1TextureOrder } from '../savegame/constants.js';
|
||||
import { GameStateTypes, GameStateSizes, Actor, Act1TextureOrder, CharacterFieldOffsets, CHARACTER_RECORD_SIZE } from '../savegame/constants.js';
|
||||
|
||||
/**
|
||||
* Offsets for header fields
|
||||
@ -461,6 +461,40 @@ export class SaveGameSerializer {
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a character field in the save file
|
||||
* @param {number} characterIndex - Character index (0-65)
|
||||
* @param {string} field - Field name from CharacterFieldOffsets
|
||||
* @param {number} value - New value
|
||||
* @param {ArrayBuffer} [buffer] - Optional buffer to use
|
||||
* @returns {ArrayBuffer|null} - Modified buffer or null on error
|
||||
*/
|
||||
updateCharacter(characterIndex, field, value, buffer = null) {
|
||||
if (characterIndex < 0 || characterIndex > 65) {
|
||||
console.error(`Invalid character index: ${characterIndex}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const fieldOffset = CharacterFieldOffsets[field];
|
||||
if (fieldOffset === undefined) {
|
||||
console.error(`Unknown character field: ${field}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const workingBuffer = buffer || this.createCopy();
|
||||
const view = new DataView(workingBuffer);
|
||||
|
||||
const offset = this.parsed.charactersOffset + (characterIndex * CHARACTER_RECORD_SIZE) + fieldOffset;
|
||||
|
||||
if (field === 'sound' || field === 'move') {
|
||||
view.setInt32(offset, value, true);
|
||||
} else {
|
||||
view.setUint8(offset, value);
|
||||
}
|
||||
|
||||
return workingBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the byte offset for a mission score
|
||||
* @param {string} missionType
|
||||
|
||||
@ -23,14 +23,18 @@ export class WdbParser {
|
||||
}
|
||||
|
||||
const globalTexturesSize = this.reader.readU32();
|
||||
// Skip global textures for now - BIGCUBE.GIF is in model_data
|
||||
this.reader.skip(globalTexturesSize);
|
||||
let globalTextures = [];
|
||||
if (globalTexturesSize > 0) {
|
||||
globalTextures = this.parseTextureInfo();
|
||||
}
|
||||
|
||||
const globalPartsSize = this.reader.readU32();
|
||||
// Skip global parts
|
||||
this.reader.skip(globalPartsSize);
|
||||
let globalParts = null;
|
||||
if (globalPartsSize > 0) {
|
||||
globalParts = this.parseGlobalParts(globalPartsSize);
|
||||
}
|
||||
|
||||
return { worlds, globalTexturesSize, globalPartsSize };
|
||||
return { worlds, globalTexturesSize, globalPartsSize, globalParts, globalTextures };
|
||||
}
|
||||
|
||||
parseWorldEntry() {
|
||||
@ -125,6 +129,40 @@ export class WdbParser {
|
||||
return { parts, textures };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse global parts block (same structure as parsePartData but inline)
|
||||
* @param {number} size - Size of global parts block
|
||||
* @returns {{ parts: Array, textures: Array }}
|
||||
*/
|
||||
parseGlobalParts(size) {
|
||||
const startOffset = this.reader.tell();
|
||||
const textureInfoOffset = this.reader.readU32();
|
||||
const numRois = this.reader.readU32();
|
||||
const parts = [];
|
||||
|
||||
for (let i = 0; i < numRois; i++) {
|
||||
const nameLen = this.reader.readU32();
|
||||
const name = this.readCleanString(nameLen);
|
||||
const numLods = this.reader.readU32();
|
||||
const roiInfoOffset = this.reader.readU32();
|
||||
|
||||
const lods = [];
|
||||
for (let j = 0; j < numLods; j++) {
|
||||
lods.push(this.parseLod());
|
||||
}
|
||||
|
||||
parts.push({ name, lods });
|
||||
}
|
||||
|
||||
this.reader.seek(startOffset + textureInfoOffset);
|
||||
const textures = this.parseTextureInfo();
|
||||
|
||||
// Ensure we've consumed the full block
|
||||
this.reader.seek(startOffset + size);
|
||||
|
||||
return { parts, textures };
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse model_data blob at specified offset
|
||||
* @param {number} offset - Absolute file offset
|
||||
@ -510,6 +548,21 @@ export function resolveLods(roi, partsMap) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a parts lookup map from global parts
|
||||
* @param {{ parts: Array }} globalParts - Parsed global parts from WdbParser
|
||||
* @returns {Map} - Map of part name (lowercase) -> part data
|
||||
*/
|
||||
export function buildGlobalPartsMap(globalParts) {
|
||||
const partsMap = new Map();
|
||||
if (!globalParts || !globalParts.parts) return partsMap;
|
||||
|
||||
for (const part of globalParts.parts) {
|
||||
partsMap.set(part.name.toLowerCase(), part);
|
||||
}
|
||||
return partsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a parts lookup map from a world's parts array
|
||||
* @param {WdbParser} parser - Parser instance for reading part data
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user