sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
260 lines (259 loc) • 7.57 kB
JavaScript
// # pipe.ts
import { FileType } from './enums.js';
import Unknown from './unknown.js';
import WriteBuffer from './write-buffer.js';
import SGProp from './sgprop.js';
import Matrix from './matrix.js';
import Matrix3 from './matrix-3.js';
import Vertex from './vertex.js';
import Box3 from './box-3.js';
import TractInfo from './tract-info.js';
import Vector3 from './vector-3.js';
import { kFileType, kFileTypeArray } from './symbols.js';
// # Pipe
// Pipe tiles are suprisingly large data structures (usually about 700 bytes).
// Their structure mostly corresponds to the
export default class Pipe {
static [kFileType] = FileType.Pipe;
static [kFileTypeArray] = true;
crc = 0x00000000;
mem = 0x00000000;
major = 0x0003;
minor = 0x0003;
zot = 0x0008;
appearance = 0x05;
tract = new TractInfo();
sgprops = [];
GID = 0x00000000;
TID = 0x00000000;
IID = 0x00000000;
matrix3 = new Matrix3();
position = new Vector3();
vertices = [
new Vertex(),
new Vertex(),
new Vertex(),
new Vertex(),
];
textureId = 0x000004b00;
orientation = 0x00;
networkType = 0x04;
westConnection = 0x00;
northConnection = 0x00;
eastConnection = 0x00;
southConnection = 0x00;
bbox = new Box3();
blocks = 0x00000000;
sideTextures = new SideTextures();
matrix = new Matrix();
xTile = 0;
zTile = 0;
diagonalFlipped = false;
sideFlag1 = true;
sideFlag2 = true;
yNW = 0;
ySW = 0;
ySE = 0;
yNE = 0;
yModel = 0;
subfileId = 0x49c05b9f;
u = new Unknown()
.byte(0x04)
.dword(0x00000000)
.dword(0xc772bf98)
.byte(0x05)
.bytes([0, 0, 0, 0, 0])
.bytes([0x02, 0, 0])
.dword(0x00000000)
.bytes([0x10, 0x00, 0x00])
.byte(0x10)
.repeat(4, u => u.dword(0x00000000))
.dword(0x00000001)
.dword(0x00000000)
.word(0x0000)
.float(1)
.dword(0x00000000)
.dword(0x00000000)
.dword(0x00000001);
// ## constructor(opts)
constructor(opts = {}) {
Object.assign(this, opts);
}
// ## parse(rs)
parse(rs) {
const unknown = this.u.reader(rs);
rs.size();
this.crc = rs.dword();
this.mem = rs.dword();
this.major = rs.word();
this.minor = rs.word();
this.zot = rs.word();
unknown.byte();
unknown.dword();
this.appearance = rs.byte();
unknown.dword();
this.tract = rs.tract();
this.sgprops = rs.sgprops();
this.GID = rs.dword();
this.TID = rs.dword();
this.IID = rs.dword();
unknown.byte();
this.matrix3 = rs.struct(Matrix3);
this.position = rs.vector3();
this.vertices = [
rs.vertex(),
rs.vertex(),
rs.vertex(),
rs.vertex(),
];
this.textureId = rs.dword();
unknown.bytes(5);
this.orientation = rs.byte();
unknown.bytes(3);
this.networkType = rs.byte();
this.westConnection = rs.byte();
this.northConnection = rs.byte();
this.eastConnection = rs.byte();
this.southConnection = rs.byte();
unknown.dword();
this.bbox = rs.bbox({ range: true });
unknown.bytes(3);
unknown.byte();
unknown.repeat(4, () => unknown.dword());
unknown.dword();
unknown.dword();
unknown.word();
unknown.float();
this.blocks = rs.dword();
this.sideTextures = new SideTextures().parse(rs);
unknown.dword();
this.matrix = rs.struct(Matrix);
this.xTile = rs.dword();
this.zTile = rs.dword();
this.diagonalFlipped = rs.bool();
this.sideFlag1 = rs.bool();
this.sideFlag2 = rs.bool();
// It's only here that the structure starts to differ from the network
// subfile records.
this.yNW = rs.float();
this.ySW = rs.float();
this.ySE = rs.float();
this.yNE = rs.float();
this.yModel = rs.float();
this.subfileId = rs.dword();
unknown.dword();
unknown.dword();
rs.assert();
return this;
}
// ## toBuffer()
toBuffer() {
const ws = new WriteBuffer();
const unknown = this.u.writer(ws);
ws.dword(this.mem);
ws.word(this.major);
ws.word(this.minor);
ws.word(this.zot);
unknown.byte();
unknown.dword();
ws.byte(this.appearance);
unknown.dword();
ws.tract(this.tract);
ws.array(this.sgprops);
ws.dword(this.GID);
ws.dword(this.TID);
ws.dword(this.IID);
unknown.byte();
ws.write(this.matrix3);
ws.vector3(this.position);
this.vertices.forEach(v => ws.vertex(v));
// Reading model starts below.
ws.dword(this.textureId);
unknown.bytes();
ws.byte(this.orientation);
unknown.bytes();
ws.byte(this.networkType);
ws.byte(this.westConnection);
ws.byte(this.northConnection);
ws.byte(this.eastConnection);
ws.byte(this.southConnection);
unknown.dword();
ws.bbox(this.bbox, { range: true });
unknown.bytes();
unknown.byte();
unknown.repeat(4, () => unknown.dword());
unknown.dword();
unknown.dword();
unknown.word();
unknown.float();
ws.dword(this.blocks);
ws.write(this.sideTextures);
unknown.dword();
ws.write(this.matrix);
ws.dword(this.xTile);
ws.dword(this.zTile);
ws.bool(this.diagonalFlipped);
ws.bool(this.sideFlag1);
ws.bool(this.sideFlag2);
ws.float(this.yNW);
ws.float(this.ySW);
ws.float(this.ySE);
ws.float(this.yNE);
ws.float(this.yModel);
ws.dword(this.subfileId);
unknown.dword();
unknown.dword();
unknown.assert();
return ws.seal();
}
}
// # SideTextures
// Tiny helper class for representing an array of side textures. Provides
// west, north, east, south and bottom getters to make everything a bit more
// readable.
class SideTextures extends Array {
constructor() {
super([], [], [], [], []);
}
get west() { return this[0]; }
set west(value) { this[0] = value; }
get north() { return this[1]; }
set north(value) { this[1] = value; }
get east() { return this[2]; }
set east(value) { this[2] = value; }
get south() { return this[3]; }
set south(value) { this[3] = value; }
get bottom() { return this[4]; }
set bottom(value) { this[4] = value; }
// ## vertical()
*vertical() {
yield this.west;
yield this.north;
yield this.east;
yield this.south;
}
// ## parse(rs)
parse(rs) {
for (let i = 0; i < 5; i++) {
this[i] = rs.array(() => rs.vertex());
}
return this;
}
// ## write(ws)
// Writes to the given buffer. This is a nice alternative to using
// toBuffer() because it doesn't require creating a new buffer for writing
// small stuff!
write(ws) {
for (let side of this) {
ws.dword(side.length);
for (let vertex of side) {
ws.vertex(vertex);
}
}
return ws;
}
// ## toBuffer()
toBuffer() {
return this.write(new WriteBuffer()).toBuffer();
}
}