UNPKG

sc4

Version:

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

165 lines (164 loc) 6.72 kB
// # 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; } }