sc4
Version:
A command line utility for automating SimCity 4 modding tasks & modifying savegames
165 lines (164 loc) • 6.72 kB
JavaScript
// # savegame.js
import DBPF from './dbpf.js';
import FileType from './file-types.js';
import { kFileTypeArray } from './symbols.js';
import { getConstructorByType } from './file-classes-helpers.js';
import {} from './enums.js';
import SavegameContext from './savegame-context.js';
import RegionView from './region-view.js';
import { randomId } from 'sc4/utils';
// # Savegame()
// A class specifically designed for some Savegame functionality. Obviously
// extends the DBPF class because savegames are dbpf files.
export default class Savegame extends DBPF {
// ## get GID()
// Every savegame has a group id apparently, which is for all entries the
// same. Not sure what this is used for.
get GID() {
return this.entries[0]?.group ?? 0;
}
get lots() { return this.readByType(FileType.Lot); }
get buildings() { return this.readByType(FileType.Building); }
get props() { return this.readByType(FileType.Prop); }
get propDeveloper() { return this.readByType(FileType.PropDeveloper); }
get propManager() { return this.readByType(FileType.PropManager); }
get textures() { return this.readByType(FileType.BaseTexture); }
get flora() { return this.readByType(FileType.Flora); }
get itemIndex() { return this.readByType(FileType.ItemIndex); }
get zoneDeveloper() { return this.readByType(FileType.ZoneDeveloper); }
get zones() { return this.zoneDeveloper; }
get lotDeveloper() { return this.readByType(FileType.LotDeveloper); }
get zoneManager() { return this.readByType(FileType.ZoneManager); }
get COMSerializer() { return this.readByType(FileType.COMSerializer); }
get lineItems() { return this.readByType(FileType.LineItem); }
get departmentBudgets() { return this.readByType(FileType.DepartmentBudget); }
get pipes() { return this.readByType(FileType.Pipe); }
get plumbingSimulator() { return this.readByType(FileType.PlumbingSimulator); }
get network() { return this.readByType(FileType.Network); }
get tunnels() { return this.readByType(FileType.NetworkTunnelOccupant); }
get bridges() { return this.readByType(FileType.NetworkBridgeOccupant); }
get prebuiltNetwork() { return this.readByType(FileType.PrebuiltNetwork); }
get networkIndex() { return this.readByType(FileType.NetworkIndex); }
get networkManager() { return this.readByType(FileType.NetworkManager); }
get terrainFlags() { return this.readByType(FileType.TerrainFlags); }
get cityInfo() { return this.readByType(FileType.cSC4City); }
get date() { return this.readByType(FileType.cSC4Simulator); }
get clock() { return this.readByType(FileType.cSC424HourClock); }
// ## get regionView()
get regionView() { return this.readByType(FileType.RegionView); }
// ## get terrain()
// The terrain is a bit special because there are multiple instances of -
// probably used for the neighbour connections.
get terrain() {
let entry = this.find({
type: FileType.TerrainMap,
instance: 0x01,
});
return entry?.read();
}
// ## get width()
// Getter for easily accessing the width of the city. We read this from the
// terrain map.
get width() {
return 64 * this.regionView.xSize;
}
// ## get depth()
// Same for the city depth.
get depth() {
return 64 * this.regionView.zSize;
}
// ## get tileWidth()
get tileWidth() {
return this.width;
}
// ## get tileDepth()
get tileDepth() {
return this.depth;
}
// ## get metricWidth()
get metricWidth() {
return 16 * this.tileWidth;
}
// ## get tileDepth()
get metricDepth() {
return 16 * this.tileDepth;
}
// ## createContext()
// Returns a Savegame context object that allows us to dereference pointers,
// as well as generate new unique pointer addresses.
createContext() {
return new SavegameContext(this);
}
// ## getSimGrid(dataId, type)
// Returns the sim grid with the given data id. Note that we used to specify
// the file type as well, but we only accept this as a hint now. It's not
// per se needed anymore.
getSimGrid(dataId, type) {
if (type !== undefined) {
let grids = this.readByType(type);
return grids.find(grid => grid.dataId === dataId);
}
else {
const types = [
FileType.SimGridFloat32,
FileType.SimGridUint32,
FileType.SimGridSint16,
FileType.SimGridUint16,
FileType.SimGridSint8,
FileType.SimGridUint8,
];
for (let type of types) {
let grid = this.getSimGrid(dataId, type);
if (grid)
return grid;
}
}
}
// # getByType(type)
// This method returns an entry in the savegame by type. If it doesn't
// exist yet, it is created.
getByType(type) {
let entry = this.find({ type });
if (!entry) {
const Constructor = getConstructorByType(type);
let { GID: group } = this;
let instance = 0;
if (kFileTypeArray in Constructor) {
entry = this.add({ type, group, instance }, []);
}
else {
entry = this.add({ type, group, instance }, new Constructor());
}
}
return entry;
}
// ## readByType(type)
// Helper function that reads an entry when it can be returned.
readByType(type) {
return this.getByType(type).read();
}
// ## static create(opts)
// This static method generates an entire new savegame from scratch with
// only the bare minimum needed for the game to read it.
// Note: this doesn't work yet! We only implemented the bare minimum to be
// able to test the city manager without loading an existing city. In order
// for the game to correctly recognize this city, much more is needed!
static create(opts) {
// The first file we'll add is the region view.
let size = ({ small: 1, medium: 2, large: 4 })[opts.size];
let group = randomId();
let dbpf = new Savegame();
let regionView = new RegionView();
regionView.xSize = regionView.zSize = size;
regionView.population = {
residential: 0,
commercial: 0,
industrial: 0,
};
regionView.mode = 'mayor';
regionView.name = 'North West';
regionView.mayorName = 'Sebastiaan';
dbpf.add([FileType.RegionView, group, 0], regionView);
return dbpf;
}
}