UNPKG

creatures

Version:

Library to interface with the Creatures 2 game

428 lines (318 loc) 8.71 kB
var Extractor = require('binary-extractor'), Blast = __Protoblast, Fn = Blast.Collection.Function; // Get the Creatures namespace var Creatures = Fn.getNamespace('Develry.Creatures'); /** * The Export class is a type of creature * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.1 * @version 0.2.1 * * @param {CreaturesApplication} app * @param {String} path */ var Export = Fn.inherits('Develry.Creatures.Creature', function Export(app, path) { // The parent creatures app this.app = app; // The path to the file this.path = path; // Has this instance been imported? this.imported = 0; }); /** * Don't register this as an in-world creature * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.7 * @version 0.2.7 * * @type {Boolean} */ Export.setProperty('in_world_type', false); /** * Read the file and process it * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.1 * @version 0.2.1 * * @param {Function} callback */ Export.setMethod(function load(callback) { var that = this; if (!callback) { callback = Fn.Thrower; } if (this.hasBeenSeen('loaded')) { return Blast.setImmediate(callback); } if (this.hasBeenSeen('loading')) { return this.afterOnce('loaded', function loaded() { Blast.setImmediate(callback); }); } this.emit('loading'); this.readFile(this.path, function gotFileBuffer(err, buffer) { if (err) { return callback(err); } // Process the buffer that.processBuffer(buffer); callback(); }); }); /** * Process the buffer * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.1 * @version 0.2.5 * * @param {Buffer} buffer */ Export.setMethod(function processBuffer(buffer) { var version, found, temp, name, ex = new Extractor(), i; // Store the buffer this.buffer = buffer; ex.setBuffer(buffer); // Skip the first 2 bytes ex.skip(2); // Read the version version = ex.readByte(); if (version == 1) { throw new Error('This is a Creatures 1 export file, it is not supported'); } if (version != 2) { throw new Error('Only Creatures 2 export files are supported'); } // 3 more unknown bytes that don't seem to change ex.skip(3); if (ex.readBytes(8).toString() != 'Creature') { throw new Error('This does not seem to be a Creature export file'); } // Skip to index 66 ex.index = 66; // Read the moniker this.moniker = ex.readBytes(4).toString(); // Skip to index 76 (Start of CGallery interesting data) ex.index = 78; // Create the cgallery array this.cgallery = []; for (i = 0; i < 250; i++) { temp = {}; // The next 2 bytes are always 0x0400 ex.skip(2); // The next byte is always 0 or 1 temp.bool1 = ex.readByte(1); // Next is another byte temp.byte1 = ex.readByte(1); // Then we have 3 0 bytes again ex.skip(3); // Another unknown byte temp.byte2 = ex.readByte(1); // Another set of 3 0 bytes ex.skip(3); // Then we have another 2 bytes temp.byte3 = ex.readByte(1); temp.byte4 = ex.readByte(1); // Go back 2, it might also be a word... no idea ex.index -= 2; temp.word = ex.readWord(); // Some kind of number.ranging from 0 to 14 temp.nr = ex.readByte(1); // The next is another 0 ex.skip(1); this.cgallery.push(temp); } // At 4283 the creature's moniker appears again ex.index = 4283; if (ex.readBytes(4).toString() != this.moniker) { console.warn('Wrong moniker?') } // Next is the mother's moniker this.mother_moniker = ex.readBytes(4).toString(); // And the father's this.father_moniker = ex.readBytes(4).toString(); // Next is the mother's name this.mother_name = ex.readLString(); // And the father's name this.father_name = ex.readLString(); // Now there is always 2 FF bytes ex.skip(2); // Followed by 0x01000400 ex.skip(4); // Next is the Body part of the CGallery if (ex.readBytes(4).toString() != 'Body') { console.warn('Export "Body" section not found'); } else { // Next is 0x0400 ex.skip(2); } // Jump to the CBiochemistry part (+/- 89033 bytes) ex.after('CBiochemistry'); // Load chemistry this.chemistry = new Creatures.Chemistry(this.app); this.chemistry.processBuffer(ex); // Get the health this.health = this.chemistry.getValue(72); // Skip over COrgan and some other data found = ex.after([0, 0x14, 0, 0, 0, 0x70, 0x01]); if (found == -1) { found = ex.after([0, 0x15, 0, 0, 0, 0x70, 0x02]); } if (found == -1) { found = ex.after([0, 0x15, 0, 0, 0, 0x6c, 0x02]); } if (found == -1) { found = ex.after([0, 0x15, 0, 0, 0]); ex.skip(2); } // Next byte is the lifestage this.agen = ex.readByte(1); // Now it's 3 unknown bytes ex.skip(3); // Followed by 1 0 byte ex.skip(1); // Now a moniker may or may not appear again. // This is sometimes the same moniker, or the "original" creature moniker? // Sometimes it's empty ex.skip(4); // Now there are 5 zero bytes again ex.skip(5); // And 1 more unknown byte ex.skip(1); // The next 2 bytes are the age this.age = ex.readWord(); // Parse the age this.age = ~~((this.age * 25.6) / 60); // Let's find the creatures name ex.after([0x3C, 0x80, 0xF8, 0x8D, 0xFC, 0x88]); // Look for this creature's moniker again ex.after(this.moniker); // And now we can get the name this.name = ex.readLString(); // Next is the mother's moniker ex.skip(4); // And the mother's name ex.readLString(); // The father's moniker ex.skip(4); // And the father's name ex.readLString(); // Next is the birthdate this.birthdate_string = ex.readLString(); // Javascript can parse this date easily this.birthdate = new Date(this.birthdate_string); // And the birthplace name this.birthplace = ex.readLString(); // The original owner (if registered) this.owner_name = ex.readLString(); // The owner's url this.owner_url = ex.readLString(); // The owner's notes this.notes = ex.readLString(); // And the owner's email address this.email = ex.readLString(); // Jump to the CGenome part (+/- 98120 bytes) ex.after('CGenome'); // The first 2 bytes is the length of the chunk this.genome_chunk_length = ex.readWord(); // Then there are 2 0 bytes ex.skip(2); // Next is the moniker, again if (ex.readBytes(4).toString() != this.moniker) { console.warn('Genome moniker missmatch'); } // And now we have the gender this.male = ex.readByte(1) == 1; this.female = !this.male; // Another 3 0 bytes ex.skip(3); // And finally the lifestage this.agen = ex.readByte(1); // Process the genome this.genome = new Blast.Classes.Develry.Creatures.Genome(this.app); // Process the genome part of the buffer this.genome.processBuffer(ex, true); this.emit('loaded'); }); /** * Import this file in the current world * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.1 * @version 0.2.4 * * @param {Boolean} force * @param {Object} position * @param {Function} callback */ Export.setMethod('import', function _import(force, position, callback) { var that = this; if (force && typeof force == 'object') { callback = position; position = force; force = false; } if (typeof position == 'function') { callback = position; position = null; } if (typeof force == 'function') { callback = force; position = null; force = false; } if (!force && this.imported) { return Blast.setImmediate(function alreadyImported() { return callback(new Error('This instance has already been imported')); }); } // Increment the import count of this instance // (not of the creature) this.imported++; // Actually import it this.app.importCreature(this, function imported(err) { if (err) { that.imported--; return callback(err); } if (!position) { return callback(null); } that.log('debug', 'Getting imported creature with moniker', that.moniker); that.app.getCreature(that.moniker, function gotCreature(err, creature) { if (err) { that.log('error', 'Failed getting imported creature:', err, creature); return callback(err); } creature.move(position.x, position.y, function moved(err) { if (err) { return callback(err); } callback(null); }); }); }); }); /** * Get a specific body image of this creature * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.1 * @version 0.2.1 * * @param {String} part_name The body part type * @param {Function} callback */ Export.setAfterMethod('loaded', function getBodyPartImage(part_name, callback) { this.genome.getBodyPartImage(this, part_name, callback); }); module.exports = Export;