Architecture
Two things to hold in your head: how the game is split into assemblies, and how ShapezShifter is split into layers. Pick the wrong layer and you will fight the API for days; pick the right one and most mods are a few hundred lines.
ShapezShifter's three layers
ShapezShifter is deliberately stacked from "convenient" down to "unlimited". Always start at the top and drop a layer only when the one above cannot express what you want.
┌───────────────────────────────────────────────────────────┐
│ Flow fluent builders — buildings, islands, │ ← start here
│ research, toolbar, console commands │
├───────────────────────────────────────────────────────────┤
│ Hijack interceptors + rewirers — extend a game │
│ structure at a defined extension point │
├───────────────────────────────────────────────────────────┤
│ SharpDetour hooks — prefix/postfix/replace any method │ ← unlimited, brittle
└───────────────────────────────────────────────────────────┘
Flow — ShapezShifter.Flow
Fluent builders for adding content. If your mod adds a building, an island, a research side-upgrade, a toolbar entry, or a console command, this is the whole API you need:
IBuildingBuilder builder = Building.Create(new BuildingDefinitionId("DiagonalCutter"))
.WithConnectorData(connectorData)
.DynamicallyRendering<Renderer, Simulation, RendererData>(new DiagonalCutterDrawData())
.WithStaticDrawData(drawData)
.WithoutSound()
.WithoutSimulationConfiguration()
.WithEfficiencyData(new BuildingEfficiencyData(2.0f, 1));
Flow also carries ShapezShifter.Kit, a grab-bag of helpers that are useful from any
layer: ModDirectoryLocator / ModFolderLocator (find your own files at runtime),
AssetBundleHelper, FileTextureLoader, FileMeshLoader, LodMeshBuilder,
MaterialHelper, ShaderHelper, and GameHelper.
Hijack — ShapezShifter.Hijack
Interception points for structures the game builds once per session. The pattern is always the same pair: an interceptor catches the game mid-construction, and a rewirer you register says what to add.
Interceptors that exist today:
| Interceptor | Extension point |
|---|---|
BuildingsInterceptor / IBuildingsRewirer |
building definitions |
IslandsInterceptor / IIslandsRewirer |
island definitions |
BuildingModulesInterceptor / IBuildingModulesRewirer |
HUD side-panel modules for a selected building |
IslandModulesInterceptor / IIslandModulesRewirer |
the same, for islands |
SimulationSystemsInterceptor / ISimulationSystemsRewirer |
systems in the simulation loop |
PredictionSystemsInterceptor / IPredictionSystemsRewirer |
the prediction/preview simulation |
PlacementInitiatorsInterceptor / I*PlacementRewirers |
placers, per entity family |
ToolbarInterceptor / IToolbarDataRewirer, IToolbarModelRewirer |
toolbar contents |
GameScenarioInterceptor / IGameScenarioRewirer |
scenario/game-data |
SaveDataInterceptor / ISaveDataRewirer |
extra blobs in the savegame |
ConsoleInterceptor / IConsoleRewirer |
dev console commands |
BuffablesInterceptor / IBuffablesRewirer |
buffs/speed upgrades |
TickInterceptor / ITickRewirer |
a per-frame callback |
Rewirers are registered with GameRewirers.AddRewirer(...), which hands back a
RewirerHandle you can dispose. Most of the time you will not construct an interceptor
yourself — Flow does it for you, and for ticks there is a one-line extension
(see Mod Lifecycle).
SharpDetour — ShapezShifter.SharpDetour
A MonoMod wrapper for patching arbitrary methods. Everything else is built on it. You express the target method as a lambda rather than a reflection string, so it is compile-time checked and survives renames:
Hook hook = DetourHelper.CreatePostfixHook<MapDrawer, FrameDrawOptionsNoLOD>(
(drawer, options) => drawer.Draw(options), // which method
(drawer, options) => MyOverlay.Draw(options) // what to run after it
);
Full details and the available overload shapes are in Hooking the Game.
Assembly map
The game is split into roughly fifty assemblies. These are the ones a mod usually touches:
| Assembly | Contains |
|---|---|
SPZGameAssembly |
The big one: HUD, rendering, drawers, building/island definitions, statistics, Globals |
Game.Core |
Engine-level odds and ends — keybindings, preferences |
Game.Core.Coordinates |
Every coordinate and transform type (Coordinates) |
Game.Core.Map.Model |
IMapModel, IslandModel, BuildingModel (Map Model) |
Game.Core.Map.Layout.Model |
IslandInstanceModel, BuildingInstanceModel — the stored instance data behind the models |
Game.Core.Map.Simulation |
ISimulator, ILocalizedSimulation, SimulationStateContainer, ICustomDataReader |
Game.Core.Simulation |
Ticks, Steps, simulation buffers and constants |
Game.Content |
Concrete machines: cutters, painters, rotators, stackers, trains, fluids |
Game.Content.Features |
The item/lane model (IItemLane, BeltLane) and the generic simulation systems |
Game.Core.Rendering |
Rendering primitives shared by the drawers |
Game.Orchestration |
GameSessionOrchestrator — wires a play session together |
Game.Interaction |
Placement, blueprints, player interaction |
Game.Hud, Game.Core.HUD |
HUD framework types |
Core, Core.Localization |
Logging, events, collections, IText / .T() translation |
A rough rule: Game.Core.* is model and framework, Game.Content* is the actual
machines, and SPZGameAssembly is presentation — HUD and rendering. If you cannot
find a type, it is probably in SPZGameAssembly.
How a session fits together
GameSessionOrchestrator (in Game.Orchestration) is the object that owns a running
game. Its fields are a useful table of contents for the whole engine —
MapModel, Simulator, Draw (a DrawManager), HUD, Research, Savegame,
SimulationSpeed, MapLayout, and so on.
The pieces a mod interacts with most:
GameSessionOrchestrator
├── MapModel (IMapModel) islands, buildings, layout → map-model.md
│ └── Simulator (ISimulator) the running machines → simulations-and-lanes.md
├── Draw (DrawManager) per-frame draw pipeline → rendering.md
└── HUD side panels, dialogs, overlays
GameSessionOrchestrator.Tick(float) is the per-frame pulse; ShapezShifter's
TickInterceptor postfixes exactly that method to drive ITickRewirer.