UNPKG

sc4

Version:

A command line utility for automating SimCity 4 modding tasks & modifying savegames

211 lines (210 loc) 7.2 kB
// # sim-grid.ts import Stream from './stream.js'; import WriteBuffer from './write-buffer.js'; import { FileType } from './enums.js'; import { kFileType, kFileTypeArray } from './symbols.js'; const TypedArrays = { [FileType.SimGridUint8]: Uint8Array, [FileType.SimGridSint8]: Int8Array, [FileType.SimGridUint16]: Uint16Array, [FileType.SimGridSint16]: Int16Array, [FileType.SimGridUint32]: Uint32Array, [FileType.SimGridFloat32]: Float32Array, }; const Readers = { [FileType.SimGridUint8]: Stream.prototype.uint8, [FileType.SimGridSint8]: Stream.prototype.int8, [FileType.SimGridUint16]: Stream.prototype.uint16, [FileType.SimGridSint16]: Stream.prototype.int16, [FileType.SimGridUint32]: Stream.prototype.uint32, [FileType.SimGridFloat32]: Stream.prototype.float, }; const Writers = { [FileType.SimGridUint8]: WriteBuffer.prototype.uint8, [FileType.SimGridSint8]: WriteBuffer.prototype.int8, [FileType.SimGridUint16]: WriteBuffer.prototype.uint16, [FileType.SimGridSint16]: WriteBuffer.prototype.int16, [FileType.SimGridUint32]: WriteBuffer.prototype.uint32, [FileType.SimGridFloat32]: WriteBuffer.prototype.float, }; // # getTypedArray(type) // Returns the typed array constructor to use for a specific type of SimGrid, // independent. function getTypedArray(type) { return TypedArrays[type]; } // # SimGrid // SimCity4 has different classes for each SimGrid, so we'll reflect this as // well. We want them to have similar behavior though, so we'll extend the class // from the base class automatically. class SimGrid { // Note: I think some of the unknowns identifies the data type, where // 0x01 is UInt8 etc. Not sure though, we should investigate this // deeper. crc = 0x00000000; mem = 0x00000000; major = 0x0001; u1 = 0x01; type; data; dataId = 0x00000000; resolution = 0x00000001; resolutionExponent = 0x00000000; xSize = 0x00000040; zSize = 0x00000040; u6 = 0x00000000; u7 = 0x00000000; u8 = 0x00000000; u9 = 0x00000000; // ## constructor(opts) // The SimGrid constructor allows us to construct a SimGrid easily by // automically figuring out a bunch of properties. For example, if a single // "size" field is specified, it assumes that we're dealing with a square - // which is simply always the case. constructor(opts = {}) { let { data, size = 0, xSize = size, zSize = size, resolution = 1, resolutionExponent = Math.log2(resolution), ...rest } = opts; if (data) { if (Array.isArray(data)) { const TypedArray = getTypedArray(this.type); this.data = new TypedArray(data); } else { this.data = data; } } else if (this.type) { const TypedArray = getTypedArray(this.type); this.data = new TypedArray(size); } Object.assign(this, { xSize, zSize, resolution, resolutionExponent, ...rest, }); } // ## parse(rs) parse(rs) { rs.size(); this.crc = rs.dword(); this.mem = rs.dword(); this.major = rs.word(); this.u1 = rs.byte(); this.type = rs.type(); this.dataId = rs.dword(); this.resolution = rs.dword(); this.resolutionExponent = rs.dword(); this.xSize = rs.dword(); this.zSize = rs.dword(); this.u6 = rs.dword(); this.u7 = rs.dword(); this.u8 = rs.dword(); this.u9 = rs.dword(); // Don't know if multiple values are possible here, the SInt8 does // some pretty weird stuff... Anyway, for now we'll just read in the // rest into the appropriate underlying array type. // Note: we could directly copy the arraybuffer, but it's pretty error // prone apparently, especially with the offsets and stuff. Hence // we'll write in manually. const TypedArray = getTypedArray(this.type); const reader = Readers[this.type]; const count = this.xSize * this.zSize; let data = this.data = new TypedArray(count); for (let i = 0; i < count; i++) { data[i] = reader.call(rs); } // Ensure that we've read everything correctly. rs.assert(); // Done! Easy data access is available by calling createProxy(). Using // this it's possible to access the data as if it were a // multidimensional array. return this; } // ## toBuffer() toBuffer() { // Pre-allocate the header. let ws = new WriteBuffer(); ws.dword(this.mem); ws.word(this.major); ws.byte(this.u1); ws.dword(this.type); ws.dword(this.dataId); ws.dword(this.resolution); ws.dword(this.resolutionExponent); ws.dword(this.xSize); ws.dword(this.zSize); ws.dword(this.u6); ws.dword(this.u7); ws.dword(this.u8); ws.dword(this.u9); // Use the underlying buffer of our data view. At least on LE systems // this should be good to be used directly. const writer = Writers[this.type]; for (let value of this.data) { writer.call(ws, value); } return ws.seal(); } // ## get(x, z) // Returns the value stored in cell (x, z) get(x, z) { let { zSize } = this; return this.data[x * zSize + z]; } // ## set(x, z) // Sets the value stored in cell (x, z) set(x, z, value) { this.data[x * this.zSize + z] = value; return this; } // ## clear(value) // Clears the entire simgrid again. Note that some simgrids might be cleared // with different values - like -128 for exammple - so this need sto be // customizable. clear(value = 0) { this.data.fill(value); } // ## createProxy() // Creates a data proxy so that we can access the data in an array-like // way. createProxy() { return new Proxy(this, { get(target, prop) { let x = +prop; return new Proxy(target, { get(target, prop) { let z = +prop; let { zSize, data } = target; return data[x * zSize + z]; }, }); }, }); } } // Helper function for creating the actual class. function createClass(type) { return class extends SimGrid { type = type; static [kFileType] = type; static [kFileTypeArray] = type; }; } export class SimGridUint8 extends createClass(FileType.SimGridUint8) { } ; export class SimGridSint8 extends createClass(FileType.SimGridSint8) { } ; export class SimGridUint16 extends createClass(FileType.SimGridUint16) { } ; export class SimGridSint16 extends createClass(FileType.SimGridSint16) { } ; export class SimGridUint32 extends createClass(FileType.SimGridUint32) { } ; export class SimGridFloat32 extends createClass(FileType.SimGridFloat32) { }