Mod file structure
example-mod/
├── modinfo.json # required
├── main.js # manifest.entry
├── worker.js # manifest.workerEntry
├── patches.json # manifest.patches (auto-loaded if present)
├── preview.png # 512x512px, required for Workshop upload
├── workshop.json # generated after the first upload (don't change!)
├── config/
│ └── drill.json # manifest.configOverrides
├── shaders/
│ └── sky.glsl # manifest.shaderOverrides
├── assets/
│ └── texture.png # manifest.textureOverrides / provides
└── map/ # manifest.map.blueprints
├── terrain.png # required for maps
├── lights.png
├── sensors.png
├── authorization.png
├── wall.png
├── lights_meta.png
├── decor.png
└── config.json
Mod manifest
modinfo.json minimal
{
"manifestVersion": 1,
"id": "author.example-mod",
"name": "Example Mod",
"version": "1.0.0",
"apiVersion": 1,
"entry": "main.js"
}
modinfo.json complete
{
"manifestVersion": 1,
"id": "author.example-mod",
"name": "Example Mod",
"version": "1.0.0",
"apiVersion": 1,
"entry": "main.js",
"workerEntry": "worker.js",
"patches": "patches.json",
"description": "Example description",
"author": "Example author",
"gameVersion": {
"minimum": "0.5.0",
"maximum": "0.5.9"
},
"dependencies": [],
"loadOrder": 0,
"configSchema": {
"speed": {
"type": "number",
"default": 1,
"min": 0.5,
"max": 2,
"step": 0.1,
"label": "Speed",
"labelKey": "mods|example|speed",
"description": "Adjusts the speed.",
"descriptionKey": "mods|example|speedDescription"
},
"enabled": {
"type": "boolean",
"default": true,
"label": "Enabled"
},
"mode": {
"type": "choice",
"default": "balanced",
"label": "Mode",
"options": [
{ "value": "balanced", "label": "Balanced" },
{ "value": "fast", "labelKey": "mods|example|modeFast" }
]
}
},
"configOverrides": {
"drill": "config/drill.json"
},
"shaderOverrides": {
"sky": "shaders/sky.glsl"
},
"textureOverrides": {
"farm": {
"path": "assets/texture.png",
"frameWidth": 18,
"frames": 6,
"intervalMs": 166
}
},
"provides": [
{
"kind": "structureTextures",
"id": "industrial",
"textureOverrides": {
"pump": "assets/texture.png"
}
}
],
"map": {
"blueprints": {
"terrain": "map/terrain.png",
"lights": "map/lights.png",
"sensors": "map/sensors.png",
"authorization": "map/authorization.png",
"wall": "map/wall.png",
"lightsMeta": "map/lights_meta.png",
"decor": "map/decor.png",
"config": "map/config.json"
},
"width": 320,
"height": 320,
"spawn": { "x": 160, "y": 140 },
"unstuck": { "x": 160, "y": 140 },
"deployment": "skip",
"topBounds": {
"hard": 0,
"soft": 100
},
"depthLight": {
"startY": 640,
"endY": 1280,
"maxSize": 400,
"minSize": 120
},
"parallax": {
"widthScale": 1,
"offsetY": 0
},
"colorMappings": {
"38, 0, 0": {
"background": "SandiumSoil",
"foreground": "Obsidian"
},
"4, 5, 6": "GoldSoil"
}
}
}
Accessing the API
sandkit is injected directly into entry and workerEntry.
const api = sandkit.api; // Stable API
// Unstable engine escape hatch
const engineApi = sandkit.engine.api;
const engineState = sandkit.engine.state;
sandkit.apiVersion- currently1.sandkit.enumssandkit.react- React 18 module in the main entry only.
Mutations
Main entry grid mutations (elements, terrain etc) are deferred, so reads see the old grid. Worker entry grid mutations are immediate. For state-dependent grid writes, use api.grid.mutate:
api.grid.mutate((writer) => {
if (api.terrains.isTypeAtCell(cellX, cellY, "ice")) {
writer.elements.replaceAtCell(cellX, cellY, "water");
}
});
Patching compiled JavaScript
patches.json example
[
{
"file": "js/bundle.js",
"find": "const message = 'Hello';",
"operation": "replace",
"code": "const message = 'Hello from my mod';",
"expectedMatches": 1
},
{
"file": "js/simulation-worker.js",
"regex": {
"pattern": "const ([a-z]+) = false;"
},
"operation": "replace",
"code": "const $1 = true;",
"expectedMatches": 1
},
{
"file": "js/bundle.js",
"find": "doThing();",
"operation": "wrap",
"before": "if (enabled) { ",
"after": " }",
"expectedMatches": 1
}
]
Available bundles:
js/bundle.js- main rendererjs/manager-worker.js- manager workerjs/simulation-worker.js- simulation workersjs/utility-worker.js- utility worker
Supported operations are replace, remove, insertBefore, insertAfter, and wrap.
Compiled bundles change between releases, so mods using patches will most likely break when the game is updated.
Internal IDs and display names
Use internal legacy IDs in API calls:
- Advanced Filter:
filterRightMk2/filterLeftMk2(filterMk2localization key) - Conveyor Portal:
quantumPortal - Void Gun:
implosionGun - Voltblub:
eyes - Kinetic Press:
velocitySoaker- Texture override:
velocity
- Texture override:
- Planter Box:
grower- Texture override:
farm
- Texture override:
- Flux Emanator:
gloomEmitter- Texture override:
gloom_emitter
- Texture override:
- Collector:
collector- Texture override:
sell
- Texture override:
- Foundation:
foundation- Texture override:
block
- Texture override:
- Delete:
demolisher- Texture override:
x
- Texture override:
- Select & Move:
copier- Texture override:
copy
- Texture override:
- Amethelis:
petalium - Snow:
freezingIce - Redsand:
sandium - Voidbloom:
gloom - Cinder:
basalt
Other quirks:
- Toolbox:
player.inventory
Config overrides
config/transport.json defaults
{
"conveyors": {
"structures": {
"conveyorLeft": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "left" } },
"conveyorRight": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "right" } },
"conveyorLeftMk2": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "beltsMk2", "run": "left" } },
"conveyorRightMk2": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "beltsMk2", "run": "right" } },
"burnerBeltLeft": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "left" } },
"burnerBeltRight": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "right" } },
"filterLeft": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "left" } },
"filterRight": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "right" } },
"filterLeftMk2": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "left" } },
"filterRightMk2": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "belts", "run": "right" } },
"shakerLeft": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "shakers", "run": "left" } },
"shakerRight": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "shakers", "run": "right" } },
"clearingFrameLeft": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "clearingFrames", "run": "left" } },
"clearingFrameRight": { "maxDisplacementCellsPerPass": 1, "schedule": { "pass": "clearingFrames", "run": "right" } }
},
"passes": {
"belts": { "cadence": { "unit": "ms", "every": 332 }, "runOrder": ["right", "left"] },
"beltsMk2": { "cadence": { "unit": "ms", "every": 166 }, "runOrder": ["right", "left"] },
"shakers": { "cadence": { "unit": "ms", "every": 3333 }, "runOrder": ["right", "left"] },
"clearingFrames": { "cadence": { "unit": "ms", "every": 1332 }, "runOrder": ["right", "left"] }
}
},
"launchers": {
"structures": {
"launcherUp": { "velocityCellsPerSecond": { "x": 0, "y": -44.4 }, "softDropVelocityCellsPerSecond": { "x": 0, "y": -30 }, "schedule": { "pass": "standard" } },
"launcherLeft": { "velocityCellsPerSecond": { "x": -44.4, "y": -44.4 }, "softDropVelocityCellsPerSecond": { "x": -30, "y": -30 }, "schedule": { "pass": "standard" } },
"launcherRight": { "velocityCellsPerSecond": { "x": 44.4, "y": -44.4 }, "softDropVelocityCellsPerSecond": { "x": 30, "y": -30 }, "schedule": { "pass": "standard" } },
"launcherUpMk2": { "velocityCellsPerSecond": { "x": 0, "y": -88.8 }, "softDropVelocityCellsPerSecond": { "x": 0, "y": -45 }, "schedule": { "pass": "mk2" } },
"launcherLeftMk2": { "velocityCellsPerSecond": { "x": -88.8, "y": -88.8 }, "softDropVelocityCellsPerSecond": { "x": -45, "y": -45 }, "schedule": { "pass": "mk2" } },
"launcherRightMk2": { "velocityCellsPerSecond": { "x": 88.8, "y": -88.8 }, "softDropVelocityCellsPerSecond": { "x": 45, "y": -45 }, "schedule": { "pass": "mk2" } }
},
"passes": {
"standard": { "cadence": { "unit": "ms", "every": 683 } },
"mk2": { "cadence": { "unit": "ms", "every": 341 } }
}
}
}
config/sprint.json defaults
{
"drainRateMultiplier": 1,
"drainDurationSeconds": 2.2,
"regenDurationSeconds": 3.4,
"horizontalMaxSpeedBonus": 240,
"verticalMaxSpeedBonus": 180,
"resumeMeterRatio": 1,
"durationBonusRatioPerLevel": 0.5,
"powerSpeedBonusMultiplier": 1.5
}
config/sky.json defaults
{
"solidOverrideEnabled": false,
"color": 0,
"layerTint": "override"
}
config/laser.json defaults
{
"energyCost": 60,
"chargeMs": 1000,
"maxRangeCells": 250,
"patternSize": 7,
"excavationPower": 1,
"debrisEjectionSpeedPixelsPerSecond": 300,
"beamColor": 16711680
}
config/implosionGun.json defaults
{
"baseTankCapacity": 4000,
"tankCapacityPerLevel": 2000,
"maxShotPower": 200,
"voidCostPerPower": 2,
"chargePowerPerSecond": 110,
"minPatternDiameterCells": 11,
"maxPatternDiameterCells": 75,
"minExcavationPower": 1,
"maxExcavationPower": 80,
"excavationPowerExponent": 2,
"drillTierDamage": 1,
"projectileSpeedPixelsPerSecond": 480,
"upgrades": {
"tankCapacity": {
"costs": [500, 1000, 2000]
}
}
}
config/locator.json defaults
{
"tankCapacity": 1000,
"voidCostPerScan": 100,
"voidDrainPerSecond": 80,
"effectDurationMs": 5000,
"outerTint": [0, 255, 255],
"innerTint": [128, 0, 255],
"upgrades": {
"triangulationLens": {
"costs": [2000]
}
}
}
config/vacuum.json defaults
{
"intake": {
"patternDiameterCells": 11,
"nozzleDistanceCells": 8,
"blowAwaySpeedCellsPerSecond": 120
},
"tanks": {
"capacityByLevel": [500, 1000, 1500, 2000, 2500, 3000],
"countByLevel": [2, 3, 4, 5, 6]
},
"spray": {
"cooldownMs": 20,
"maxElementsPerPulse": 11,
"muzzleDistanceCells": 6,
"speedCellsPerSecond": 240,
"speedVariationRatio": {
"min": 0.8,
"max": 1.2
},
"spreadAngleRadians": 0.6,
"positionSpreadPixels": 20
},
"upgrades": {
"capacity": {
"costs": [250, 500, 1000, 3000, 5000]
},
"tankCount": {
"costs": [300, 1000, 3000, 5000]
}
}
}
Types
sandkit.enums
sandkit.enums.CellType
sandkit.enums.ElementType
sandkit.enums.MatterType
sandkit.enums.StructureType
sandkit.enums.ItemType
sandkit.enums.ItemId
sandkit.enums.ProjectileType
sandkit.enums.ActionType
sandkit.enums.ActionState
sandkit.enums.AbilityType
sandkit.enums.ReloadType
sandkit.enums.ComponentId
sandkit.enums.KeyBinding
sandkit.enums.KeyState
sandkit.enums.BuildMode
sandkit.enums.BuildingClearance
sandkit.enums.AuthorizationType
sandkit.enums.DroneType
sandkit.enums.PickupType
sandkit.enums.Scene
sandkit.enums.Tech
sandkit.enums.TechStatus
- Deprecated alias:
sandkit.enums.WorldItemType
Main entry
api.constants
physics
normal:0skip:1aggressiveSkip:2
api.gameConfig
get(key)
key:"namespace:path"
getAll()Deprecated aliases
drill:maxRange>drill:maxRangeCellsdrill:normalExcavationRate>drill:normalExcavationChancedrill:reducedExcavationRate>drill:reducedExcavationChance
api.settings
get(fieldId)getAll()onChange(callback)
callback(values)
const unsubscribe = api.settings.onChange((values) => {
applySettings(values);
});
api.storage
ensure(modId)get(modId, key)set(modId, key, value)remove(modId, key)local.get(key)local.set(key, value)local.remove(key)api.mods
getProviders(kind)api.authorization
canBuildAtCell(cellX, cellY)canGrabAtCell(cellX, cellY)canUseTool(player, isFlamethrower?)canUseToolAtCell(cellX, cellY, isFlamethrower?)getZoneIdAtCell(cellX, cellY)getPlayerZoneId()api.building
getSnappedPositionAtCell(cellX, cellY)isBlockedAtCell(cellX, cellY)cancelPlacement()selectStructure(structureTypeOrId)api.camera
snapToPlayer()setFocusAtWorld(worldX, worldY)releaseFocus(options?)
options.durationMs(optional)
const released = api.camera.releaseFocus({ durationMs: 250 });
api.collector
getValueFromCellId(cellId)getValueByType(elementType)isCellIdCollectable(cellId)isCellIdCollectableForSprite(cellId)notifyPickupAtCell(cellX, cellY)api.discoveries
addElementByType(elementType)addTerrainByType(terrainType)api.hooks
intercept(hookId, callback, options?)
item:use
hookId:"item:use"callback(args, context)args.itemIdargs.useIdargs.kind:"instant"|"sustained"|"chargeThenFire"args.baseline(read-only)args.prepared(mutable)context.cancel()context.cancelled
options.itemIdsoptions.priority
const unsubscribe = api.hooks.intercept(
"item:use",
(args, context) => {
args.prepared.energyCost = Number(args.baseline.energyCost) * 2;
if (args.prepared.energyCost > 1000) {
context.cancel();
}
},
{ itemIds: ["laser"], priority: 0 },
);
teleport:effect:create
hookId:"teleport:effect:create"callback(args, context)options(optional)- Deprecated alias:
"teleport:effect"
api.hooks.intercept("teleport:effect:create", (args, context) => {
context.cancel();
});
action:start
hookId:"action:start"callback(args, context)options(optional)- Deprecated alias:
"action:intercept"
api.hooks.intercept("action:start", (args, context) => {
if (args.action?.id === "example") context.cancel();
});
input:keyDown
hookId:"input:keyDown"callback(args, context)options(optional)- Deprecated alias:
"input:keydown"
api.hooks.intercept("input:keyDown", (args, context) => {
if (args.code === "KeyK") context.cancel();
});
input:keyUp
hookId:"input:keyUp"callback(args, context)options(optional)- Deprecated alias:
"input:keyup"
api.hooks.intercept("input:keyUp", (args, context) => {
if (args.code === "KeyK") context.cancel();
});
placePoints:suppress
hookId:"placePoints:suppress"callback(args, context)options(optional)- Deprecated alias:
"placePoints:isSuppressed"
api.hooks.intercept("placePoints:suppress", (args, context) => {
if (args.type === "exampleStructure") context.cancel();
});
placePoints:directionalArrows:suppress
hookId:"placePoints:directionalArrows:suppress"callback(args, context)options(optional)- Deprecated alias:
"placePoints:directionalArrows:isSuppressed"
api.hooks.intercept(
"placePoints:directionalArrows:suppress",
(args, context) => {
if (args.type === "exampleStructure") context.cancel();
},
);
entity:update
hookId:"entity:update"callback(args, context)args.entityTypeId(read-only)args.entity(mutable)args.deltaTimeSeconds(read-only)args.phase(read-only):"normal"|"capturing"|"launching"args.isVisible(read-only)args.playerWorldX(read-only)args.playerWorldY(read-only)args.worldMinX(read-only)args.worldMinY(read-only)args.worldMaxX(read-only)args.worldMaxY(read-only)args.cellSize(read-only)args.timeSeconds(read-only)context.cancel()context.cancelled
options.entityTypesoptions.priority
const unsubscribe = api.hooks.intercept(
"entity:update",
(args) => {
if (args.phase !== "normal") return;
args.entity.targetX = args.playerWorldX;
args.entity.targetY = args.playerWorldY;
},
{ entityTypes: ["lumling"], priority: 0 },
);
building:place
hookId:"building:place"callback(args, context)args.structureIdargs.xargs.yargs.dataoptions.structureTypes(optional)options.priority(optional)
api.hooks.intercept("building:place", (args, context) => {
if (args.structureId === "exampleStructure") context.cancel();
});
building:clearShape
hookId:"building:clearShape"callback(args, context)args.structureoptions(optional)
api.hooks.intercept("building:clearShape", (args, context) => {
if (args.structure.data?.protected) context.cancel();
});
input:scroll
hookId:"input:scroll"callback(args, context)args.deltaYoptions(optional)
api.hooks.intercept("input:scroll", (args, context) => {
if (args.deltaY !== 0) context.cancel();
});
input:boostDown
hookId:"input:boostDown"callback(args, context)options(optional)- Deprecated alias:
"input:boost-down"
api.hooks.intercept("input:boostDown", (args, context) => {
context.cancel();
});
input:descendDown
hookId:"input:descendDown"callback(args, context)options(optional)- Deprecated alias:
"input:descend-down"
api.hooks.intercept("input:descendDown", (args, context) => {
context.cancel();
});
input:escape
hookId:"input:escape"callback(args, context)options(optional)
api.hooks.intercept("input:escape", (args, context) => {
context.cancel();
});
interactable:suppressHover
hookId:"interactable:suppressHover"callback(args, context)args.typeargs.structureoptions(optional)
api.hooks.intercept("interactable:suppressHover", (args, context) => {
if (args.type === "exampleStructure") context.cancel();
});
fire:element:ignite
hookId:"fire:element:ignite"callback(args, context)args.xargs.yargs.elementTypeoptions(optional)
api.hooks.intercept("fire:element:ignite", (args, context) => {
if (args.elementType === exampleElementType) context.cancel();
});
projectile:fire:overStructure
hookId:"projectile:fire:overStructure"callback(args, context)args.projectileargs.xargs.yoptions.projectileTypes(optional)options.priority(optional)
api.hooks.intercept(
"projectile:fire:overStructure",
(args, context) => {
if (args.projectile.type === "exampleProjectile") context.cancel();
},
);
projectile:hit
hookId:"projectile:hit"callback(args, context)args.projectileargs.travelResultoptions.projectileTypes(optional)options.priority(optional)
api.hooks.intercept("projectile:hit", (args, context) => {
if (args.projectile.type === "exampleProjectile") context.cancel();
});
player:position:commit
hookId:"player:position:commit"callback(args, context)args.previousWorldXargs.previousWorldYargs.proposedWorldXargs.proposedWorldYargs.velocityXargs.velocityYoptions(optional)
api.hooks.intercept("player:position:commit", (args) => {
args.velocityX *= 0.5;
args.velocityY *= 0.5;
});
progression:purchase
hookId:"progression:purchase"callback(args, context)args.domain:"tech"|"upgrade"args.idargs.itemId(optional)args.costsoptions(optional)
api.hooks.intercept("progression:purchase", (args, context) => {
if (args.id === "exampleTech") context.cancel();
});
modify(hookId, callback, options?)
excavation:prepare
hookId:"excavation:prepare"callback(args)args.sourceId(read-only)args.sourceKind(read-only):"tool"|"projectile"|"structure"|"drone"args.originCellX(read-only)args.originCellY(read-only)args.consumedVoid(read-only)args.profileId(mutable)args.patternDiameterCells(mutable)args.drillTierDamage(mutable)
options.priority
const unsubscribe = api.hooks.modify(
"excavation:prepare",
(args) => {
if (args.sourceId !== "implosionGun") return;
args.profileId = "example:voidGun";
args.patternDiameterCells = 21;
args.drillTierDamage = 8;
},
{ priority: 0 },
);
locator:scan:prepare
hookId:"locator:scan:prepare"callback(args)args.originWorldX(read-only)args.originWorldY(read-only)args.hasTarget(mutable)args.targetCellX(mutable)args.targetCellY(mutable)args.outerTint(mutable): [R, G, B]args.innerTint(mutable): [R, G, B]args.noTargetToast(mutable)args.noTargetToastKey(mutable)args.triangulationLensOverride:true|false|null(null is standard behavior)
options.priority
const unsubscribe = api.hooks.modify(
"locator:scan:prepare",
(args) => {
const target = findNearestTarget(args.originWorldX, args.originWorldY);
args.hasTarget = target !== null;
if (!target) {
args.noTargetToast = "No example target was found.";
args.noTargetToastKey = "mods|example|noTarget";
return;
}
args.targetCellX = target.cellX;
args.targetCellY = target.cellY;
args.outerTint[0] = 103;
args.outerTint[1] = 232;
args.outerTint[2] = 249;
args.triangulationLensOverride = true;
},
{ priority: 0 },
);
vacuum:prepare
hookId:"vacuum:prepare"callback(args)args.nozzleCellX(read-only)args.nozzleCellY(read-only)args.targetCellX(mutable)args.targetCellY(mutable)args.pattern(mutable)
options.priority
const vacuumPattern = [
[0, 1, 0],
[1, 1, 1],
[0, 1, 0],
];
const unsubscribe = api.hooks.modify(
"vacuum:prepare",
(args) => {
const target = api.input.getMousePositionAtCell();
args.targetCellX = target.x;
args.targetCellY = target.y;
args.pattern = vacuumPattern;
},
{ priority: 0 },
);
vacuum:element:prepare
hookId:"vacuum:element:prepare"callback(args)args.elementType(read-only)args.matterType(read-only)args.isTransportable(read-only)args.collectable(mutable)args.visibleInPicker(mutable)
options.priority
const unsubscribe = api.hooks.modify(
"vacuum:element:prepare",
(args) => {
if (args.matterType !== sandkit.enums.MatterType.Liquid) return;
args.collectable = true;
args.visibleInPicker = true;
},
{ priority: 0 },
);
player:movement:prepare
hookId:"player:movement:prepare"callback(args)options(optional)- Deprecated alias:
"player:movement"
api.hooks.modify("player:movement:prepare", (args) => {
args.horizontalMaxSpeed *= 1.25;
});
building:placementLimit:prepare
hookId:"building:placementLimit:prepare"callback(args)options(optional)- Deprecated alias:
"building:placementLimit" - Deprecated alias:
"building:placement-limit"
api.hooks.modify("building:placementLimit:prepare", (args) => {
args.maxCount = args.maxCount === null ? 10 : args.maxCount + 10;
});
fluxEmanator:processing:prepare
hookId:"fluxEmanator:processing:prepare"callback(args)options(optional)- Deprecated alias:
"fluxEmanator:processing" - Deprecated alias:
"flux-emanator:processing"
api.hooks.modify("fluxEmanator:processing:prepare", (args) => {
args.speedMultiplier *= 2;
});
render:pipes:prepare
hookId:"render:pipes:prepare"callback(args)options(optional)- Deprecated alias:
"render:pipes"
api.hooks.modify("render:pipes:prepare", (args) => {
args.layer = "foreground";
});
structures:moved:prepare
hookId:"structures:moved:prepare"callback(args)args.movedargs.failedToPlaceoptions(optional)
api.hooks.modify("structures:moved:prepare", (args) => {
prepareMovedStructures(args.moved, args.failedToPlace);
});
structures:removed:prepare
hookId:"structures:removed:prepare"callback(args)args.removedargs.structures(optional)args.byMoveoptions(optional)
api.hooks.modify("structures:removed:prepare", (args) => {
prepareRemovedStructures(args.removed, args.byMove);
});
weapon:reload:prepare
hookId:"weapon:reload:prepare"callback(args)args.weaponIdargs.reloadMsargs.maxAmmooptions.weaponIds(optional)options.priority(optional)
api.hooks.modify("weapon:reload:prepare", (args) => {
args.reloadMs *= 0.8;
}, { weaponIds: ["exampleWeapon"] });
projectile:travel:prepare
hookId:"projectile:travel:prepare"callback(args)args.projectileTypeargs.firstCollisionStepargs.maxCollisionStepsargs.collidesWithTerrainargs.collidesWithStructuresoptions.projectileTypes(optional)options.priority(optional)
api.hooks.modify("projectile:travel:prepare", (args) => {
args.collidesWithStructures = false;
}, { projectileTypes: ["exampleProjectile"] });
projectile:impact:prepare
hookId:"projectile:impact:prepare"callback(args)args.projectileTypeargs.impactKindargs.profileIdargs.powerargs.centerPowerargs.radiusCellsoptions.projectileTypes(optional)options.priority(optional)- Deprecated alias:
args.radius
api.hooks.modify("projectile:impact:prepare", (args) => {
args.radiusCells = 8;
}, { projectileTypes: ["exampleProjectile"] });
player:collision:prepare
hookId:"player:collision:prepare"callback(args)args.phaseThroughTerrainargs.phaseThroughStructuresargs.maxStepCellsoptions(optional)
api.hooks.modify("player:collision:prepare", (args) => {
args.maxStepCells = 4;
});
trigger:schedule:prepare
hookId:"trigger:schedule:prepare"callback(args)args.triggerIdargs.intervalMsargs.sequentialRunsoptions.triggerIds(optional)options.priority(optional)
api.hooks.modify("trigger:schedule:prepare", (args) => {
args.intervalMs *= 0.5;
}, { triggerIds: ["pump"] });
progression:cost:prepare
hookId:"progression:cost:prepare"callback(args)args.domain:"tech"|"upgrade"args.idargs.itemId(optional)args.currencyIdargs.amountoptions(optional)
api.hooks.modify("progression:cost:prepare", (args) => {
if (args.currencyId === "gold") args.amount *= 0.9;
});
resource:collection:prepare
hookId:"resource:collection:prepare"callback(args)args.resourceIdargs.sourceKindargs.cellXargs.cellYargs.amountargs.feedback:"default"|"silent"options.resourceIds(optional)options.priority(optional)
api.hooks.modify("resource:collection:prepare", (args) => {
args.amount *= 2;
}, { resourceIds: ["fluxite"] });
resource:delivery:prepare
hookId:"resource:delivery:prepare"callback(args)args.resourceIdargs.sourceKindargs.sourceIdargs.sourceCellXargs.sourceCellYargs.targetCellXargs.targetCellYargs.mode:"world"|"collection"args.amountargs.feedback:"default"|"silent"options.resourceIds(optional)options.priority(optional)
api.hooks.modify("resource:delivery:prepare", (args) => {
args.mode = "collection";
}, { resourceIds: ["fluxite"] });
resource:balance:prepare
hookId:"resource:balance:prepare"args.resourceId(read-only)args.balance
api.hooks.modify("resource:balance:prepare", (args) => {
args.balance += api.storage.get("example", "gold") ?? 0;
}, { resourceIds: ["gold"] });
gold:removal:prepare
hookId:"gold:removal:prepare"args.requestedAmount(read-only)args.shortfall
api.hooks.modify("gold:removal:prepare", (args) => {
const banked = api.storage.get("example", "gold") ?? 0;
args.shortfall = Math.max(0, args.shortfall - banked);
});
gold:removal:settle
hookId:"gold:removal:settle"args.requestedAmount,args.physicalRemoved(read-only)args.shortfall
api.hooks.modify("gold:removal:settle", (args) => {
const banked = api.storage.get("example", "gold") ?? 0;
const covered = Math.min(banked, args.shortfall);
api.storage.set("example", "gold", banked - covered);
args.shortfall -= covered;
});
api.events
on(eventId, callback)
item:used
eventId:"item:used"callback(payload)payload.itemIdpayload.useIdpayload.kindpayload.cellXpayload.cellYpayload.prepared(read-only)
const unsubscribe = api.events.on("item:used", (payload) => {
if (payload.itemId !== "laser") return;
spawnSparklesAtCell(payload.cellX, payload.cellY);
});
frame:render
eventId:"frame:render"callback(payload)
api.events.on("frame:render", () => {
drawOverlay();
});
scene:game:started
eventId:"scene:game:started"callback(payload)- Deprecated alias:
"scene:started:game"
api.events.on("scene:game:started", () => {
initializeGameScene();
});
earlyAccess:completed
eventId:"earlyAccess:completed"callback(payload)- Deprecated alias:
"earlyAccess:complete"
api.events.on("earlyAccess:completed", (payload) => {
onEarlyAccessCompleted(payload);
});
terrain:destroyed
eventId:"terrain:destroyed"callback(payload)payload.cellXpayload.cellYpayload.cellType- Deprecated alias:
payload.xandpayload.y
api.events.on("terrain:destroyed", (payload) => {
onTerrainDestroyed(payload.cellX, payload.cellY, payload.cellType);
});
fog:cellRevealed
eventId:"fog:cellRevealed"callback(payload)payload.cellXpayload.cellY- Deprecated alias:
payload.xandpayload.y
api.events.on("fog:cellRevealed", (payload) => {
onFogCellRevealed(payload.cellX, payload.cellY);
});
upgrade:levelSelected
eventId:"upgrade:levelSelected"callback(payload)payload.itemIdpayload.upgradeIdpayload.level
api.events.on("upgrade:levelSelected", (payload) => {
onLevelSelected(payload.itemId, payload.upgradeId, payload.level);
});
building:placed
eventId:"building:placed"callback(payload)payload.structurepayload.xpayload.ypayload.isBatchpayload.isCopied
api.events.on("building:placed", (payload) => {
onBuildingPlaced(payload.structure, payload.x, payload.y);
});
building:removed
eventId:"building:removed"callback(payload)payload.structureIdpayload.xpayload.ypayload.isBatch
api.events.on("building:removed", (payload) => {
onBuildingRemoved(payload.structureId, payload.x, payload.y);
});
structures:placed
eventId:"structures:placed"callback(payload)payload.structures
api.events.on("structures:placed", (payload) => {
onStructuresPlaced(payload.structures);
});
structures:removed
eventId:"structures:removed"callback(payload)payload.removedpayload.structures(optional)payload.byMove
api.events.on("structures:removed", (payload) => {
onStructuresRemoved(payload.removed, payload.byMove);
});
structures:moved
eventId:"structures:moved"callback(payload)payload.movedpayload.failedToPlace
api.events.on("structures:moved", (payload) => {
onStructuresMoved(payload.moved, payload.failedToPlace);
});
game:ready
eventId:"game:ready"callback(payload)
api.events.on("game:ready", () => {
initializeExample();
});
game:started
eventId:"game:started"callback(payload)
api.events.on("game:started", () => {
startExample();
});
tutorial:stepChanged
eventId:"tutorial:stepChanged"callback(payload)payload.step
api.events.on("tutorial:stepChanged", (payload) => {
onTutorialStepChanged(payload.step);
});
tutorial:completed
eventId:"tutorial:completed"callback(payload)payload.skipped
api.events.on("tutorial:completed", (payload) => {
onTutorialCompleted(payload.skipped);
});
tech:unlocked
eventId:"tech:unlocked"callback(payload)payload.techIdpayload.suppressMusic
api.events.on("tech:unlocked", (payload) => {
onTechUnlocked(payload.techId, payload.suppressMusic);
});
worldItem:pickedUp
eventId:"worldItem:pickedUp"callback(payload)payload.worldItemIdpayload.type
api.events.on("worldItem:pickedUp", (payload) => {
onPickup(payload.worldItemId, payload.type);
});
resource:collected
eventId:"resource:collected"callback(payload)payload.resourceIdpayload.amountpayload.sourceKindpayload.cellXpayload.cellY
api.events.on("resource:collected", (payload) => {
onResourceCollected(payload.resourceId, payload.amount);
});
emit(eventId, payload)api.assets
getUrl(relativePath)getSelectedProvider(kind)setSelectedProvider(kind, providerId)
- Deprecated alias:
selectProvider(kind, providerId)
api.effects
createDistortionWaveAtWorld(worldX, worldY, options?)
api.effects.createDistortionWaveAtWorld(worldX, worldY, {
style: "implode",
});
createAtWorld(effectId, worldX, worldY, options?)
- Deprecated alias:
createEffectAtWorld(effectId, worldX, worldY, options?)
createLaserAtWorld(startWorldX, startWorldY, endWorldX, endWorldY, options?)createParticlesAtWorld(worldX, worldY, options?)
api.effects.createParticlesAtWorld(worldX, worldY, {
count: 12,
});
api.energy
registerType(structureId, type, options?)
type:"conductor"|"storage"
addAtCell(cellX, cellY, amount, options?)consume(amount, options?)consumeExcludingNetworkAtCell(cellX, cellY, amount)getNetworkAtCell(cellX, cellY)
entry.cellXentry.cellYentry.type- Deprecated alias:
entry.xandentry.y
const network = api.energy.getNetworkAtCell(cellX, cellY);
for (const entry of network) {
useNetworkCell(entry.cellX, entry.cellY, entry.type);
}
getNetworkFreeCapacityAtCell(cellX, cellY)api.input
registerBinding(bindingId, defaultKeys, definition)
definition.displayNamedefinition.displayNameKeydefinition.displayNameParamsdefinition.subsection(optional)
api.input.registerBinding("ExampleToggle", ["KeyO"], {
displayName: "Toggle example",
displayNameKey: "mods|example|toggle",
subsection: {
title: "Example controls",
titleKey: "mods|example|controlsTitle",
description: "Bindings installed by the example mod.",
descriptionKey: "mods|example|controlsDescription",
},
handlers: { down: toggleExample },
});
getMousePositionAtCell()
- Deprecated alias:
getMouseCellPosition()
getMousePositionAtWorld()getBoundKeys(bindingId)getDisplayKey(bindingId, defaultLabel?)triggerBinding(bindingId)pressBinding(bindingId)releaseBinding(bindingId)resetMouseState()isCtrlHeld()isAltHeld()api.items
createById(itemId)
- Deprecated alias:
createFromId(itemId)
getRegisteredIds()spriteMountsregister(definition)updateDefinition(itemId, partial)
api.items.updateDefinition("exampleTool", {
name: "Updated Example Tool",
});
getDefinitionById(itemId)getActive()isActiveById(itemId, itemType?)api.patterns
createCircle(diameterCells)excavateAtCell(cellX, cellY, pattern, outVelocity, power, options?)
api.patterns.excavateAtCell(
cellX,
cellY,
api.patterns.createCircle(5),
{ x: 0, y: -120 },
2,
);
api.player
getPositionAtWorld()
- Deprecated alias:
getWorldPosition()
setPositionAtWorld(worldX, worldY)
- Deprecated alias:
setWorldPosition(worldX, worldY)
setVelocity(velocityX, velocityY)setMovementSpeedMultiplier(multiplier)setMovementMode(mode)
mode:"normal"|"hover"
isOnGround()teleportToGround()isCollidingWithCell(cellX, cellY)isWithinRadiusOfCell(cellX, cellY, radiusCells)isPositionClearAtWorld(worldX, worldY)
- Deprecated alias:
isWorldPositionClear(worldX, worldY)
inventory.hasById(itemId)
itemId:ItemId | string
inventory.addById(itemId)
itemId:ItemId | string- Deprecated alias:
inventory.addFromId(itemId)
api.progression
complete({ domain: "tutorial", grantNormalUnlocks? } | { domain: "objective", id })
const completed = api.progression.complete({
domain: "objective",
id: "all",
});
api.projectiles
register(definition)getDefinitionById(projectileId)createBlueprintById(projectileId)
- Deprecated alias:
createBlueprintFromId(projectileId)
getAll()getById(projectileId)remove(projectile)spawnAtWorld(worldX, worldY, angleRadians, blueprint)api.random
int(min, max)float(min, max)api.raycast
castAtWorld(startWorldX, startWorldY, angleRadians, maxDistanceWorldPixels)
result.cellXresult.cellYresult.distanceWorldPixels- Deprecated alias:
castFromWorld(startWorldX, startWorldY, angleRadians, maxDistanceWorldPixels) - Deprecated alias:
result.x,result.y, andresult.distance
api.sprites
load(spriteId, path, options?)loadFromMod(spriteId, relativePath, options?)getById(spriteId)hideAllForPlayer()
- Deprecated alias:
hideAllPlayerModSprites()
rotateAllForPlayer(angleRadians)
- Deprecated alias:
rotatePlayerModSprites(angleRadians)
api.time
getElapsedMs()
- Deprecated alias:
getTimeMs()
getTick()api.ui
update(componentId, options?)openPauseMenu()showTooltip(data)toast(message, options?)
api.ui.toast({ key: "mods|example|saved" });
alert(message, title?)
await api.ui.alert(
{ key: "mods|example|details" },
{ key: "mods|example|title" },
);
confirm(message, title?)
const confirmed = await api.ui.confirm(
{ key: "mods|example|confirm" },
);
prompt(message, defaultValue?, placeholder?, title?, allowCopy?)
const value = await api.ui.prompt(
{ key: "mods|example|enterValue" },
"",
);
select(options, opts?)
options[].labeloptions[].valueopts.message?opts.title?opts.defaultValue?opts.buttonLabel?
const selected = await api.ui.select(
[
{ label: "Sand", value: "sand" },
{ label: "Fluxite", value: "fluxite" },
],
{ title: "Select element", defaultValue: "sand", buttonLabel: "Choose" },
);
useRefresh(componentIds)useScale()useGameEvent(eventId, handler)
handler(payload)
api.ui.useGameEvent("resource:collected", (payload) => {
console.log(payload.resourceId, payload.amount);
});
inject(componentId, Component)regions
mount(regionId, mountId, options)
options.placement?:"raised"|"docked"- Not a fan of this naming but
"docked"puts it right on top of the hotbar (between it and panels such as Filter Config) while"raised"puts it above those panels. Let me know ifyou can think of a better naming scheme.
- Not a fan of this naming but
options.order?options.render()- Deprecated alias:
api.ui.overlays.register(slot, overlayId, render)
const mountHandle = api.ui.regions.mount(
"hotbar",
"extra-actions",
{
placement: "docked",
order: 0,
render: () => sandkit.react.createElement(ExtraActions),
},
);
mountHandle.update(options)
options.placement?:"raised"|"docked"options.order?options.render?- Deprecated alias:
api.ui.overlays.update(slot)
mountHandle.update({
order: 10,
render: () => sandkit.react.createElement(UpdatedActions),
});
mountHandle.unmount()
- Deprecated alias:
api.ui.overlays.unregister(slot, overlayId)
setVisible(regionId, visible)
visibilityHandle.restore()overrides
register(componentId, wrapper)
wrapper(Original, props)
const overrideHandle = api.ui.overrides.register(
"resources",
(Original) => sandkit.react.createElement(
sandkit.react.Fragment,
null,
sandkit.react.createElement(Original),
sandkit.react.createElement(ResourceAddon),
),
);
overrideHandle.remove()hotbar
createBankSource(options)
options.bankOffsetoptions.minimumBankCount?
const source = api.ui.hotbar.createBankSource({
bankOffset: 1,
minimumBankCount: 2,
});
source.isAvailable()source.getBankIndex()source.getSlotCount()source.getAction(slotIndex)source.activateSlot(slotIndex)source.clearSlot(slotIndex)source.dispose()selectAction(action)getBankCount()getActiveBankIndex()getActiveSlotIndex()getSlotKeyLabel(bindingId)useHotbar()
const hotbar = api.ui.hotbar.useHotbar();
console.log(
hotbar.bankCount,
hotbar.activeBankIndex,
hotbar.activeSlotIndex,
);
components
ActionSlot(props)
props.sourceprops.slotIndexprops.actionprops.keyLabel?props.active?props.onSelect?props.onClear?
const slot = sandkit.react.createElement(
api.ui.components.ActionSlot,
{ source, slotIndex: 0, keyLabel: "1" },
);
Panel(props)
props.title?props.childrenprops.className?props.style?
const panel = sandkit.react.createElement(
api.ui.components.Panel,
{ title: "Options" },
"Panel content",
);
Button(props)
props.childrenprops.active?props.border?:true|falseprops.disabled?props.small?props.variant?:"primary"|"danger"props.className?props.style?props.onClick?
const button = sandkit.react.createElement(
api.ui.components.Button,
{ onClick: openPanel },
"Open",
);
navigation
useFocusable(options)
const focusable = api.ui.navigation.useFocusable({
id: "example-button",
scope: "example-scope",
onActivate: openExample,
});
useFocusScope(options)
api.ui.navigation.useFocusScope({
id: "example-scope",
active: true,
priority: 10,
});
getControllerFocusClass(focused)
- Deprecated alias:
controllerFocusClass(focused)
api.utils
getDistance(pointA, pointB)getDirection(pointA, pointB)getAngle(pointA, pointB)getCoordinatesBetweenCells(pointA, pointB)
- Deprecated alias:
getCoordinatesBetweenPoints(pointA, pointB)
api.action
getActive()getSelected()setCustomData(data)
api.action.setCustomData({ mode: "example" });
api.scene
getActive()api.cooldown
start(cooldown)
- Deprecated alias:
check(cooldown, durationOverrideMs?)
isReady(cooldown, durationOverrideMs?)api.elements
getRegisteredTypes()register(definition)updateDefinition(elementTypeOrId, partial)
elementTypeOrId:ElementType | string
api.elements.updateDefinition("exampleElement", {
showInFilterPicker: false,
});
addInteractionInfo(elementTypeOrId, interaction)
elementTypeOrId:ElementType | string
getTypeById(elementId)
- Deprecated alias:
getTypeFromId(elementId)
getIdByType(elementType)getNameByType(elementType)getDefinitionByType(elementType)getTypeAtCell(cellX, cellY)getResolvedTypeAtCell(cellX, cellY)getResolvedTypeFromCellId(cellId)getInfoAtCell(cellX, cellY)getMatterTypeAtCell(cellX, cellY)isTypeAtCell(cellX, cellY, elementTypeOrId)
elementTypeOrId:ElementType | string
isFreeFallingAtCell(cellX, cellY)findFreeCellInStructure(structureCellX, structureCellY, structureSizeCells)createAtCell(cellX, cellY, elementTypeOrId, options?)
elementTypeOrId:ElementType | string- Deprecated alias:
createAtCellWhenIdle(cellX, cellY, elementTypeOrId, options?)
api.elements.createAtCell(cellX, cellY, "water", {
durationTicks: 60,
});
replaceAtCell(cellX, cellY, elementTypeOrId, options?)
elementTypeOrId:ElementType | string- Deprecated alias:
replaceAtCellWhenIdle(cellX, cellY, elementTypeOrId, options?)
removeAtCell(cellX, cellY, options?)
- Deprecated alias:
removeAtCellWhenIdle(cellX, cellY, options?)
teleportBetweenCells(fromCellX, fromCellY, toCellX, toCellY)
- Deprecated alias:
teleportBetweenCellsWhenIdle(fromCellX, fromCellY, toCellX, toCellY)
getVelocityAtCell(cellX, cellY)setVelocityAtCell(cellX, cellY, velocity)
- Deprecated alias:
setVelocityAtCellWhenIdle(cellX, cellY, velocity)
api.elements.setVelocityAtCell(cellX, cellY, { x: 0, y: -120 });
addParticleVelocityAtCell(cellX, cellY, velocity, maxSpeedCellsPerSecond?)
- Deprecated alias:
addParticleVelocityAtCellWhenIdle(cellX, cellY, velocity, maxSpeedCellsPerSecond?)
api.elements.addParticleVelocityAtCell(
cellX,
cellY,
{ x: 4, y: -8 },
120,
);
convertToParticleAtCell(cellX, cellY, velocity)
- Deprecated alias:
convertToParticleAtCellWhenIdle(cellX, cellY, velocity)
api.elements.convertToParticleAtCell(
cellX,
cellY,
{ x: 0, y: -120 },
);
convertFromParticleAtCell(cellX, cellY)
- Deprecated alias:
convertFromParticleAtCellWhenIdle(cellX, cellY)
getDataFieldAtCell(cellX, cellY, dataFieldNumber)setDataFieldAtCell(cellX, cellY, dataFieldNumber, value)
- Deprecated alias:
setDataFieldAtCellWhenIdle(cellX, cellY, dataFieldNumber, value)
refreshColorAtCell(cellX, cellY)
- Deprecated alias:
refreshColorAtCellWhenIdle(cellX, cellY)
setPhysicsAtCell(cellX, cellY, physicsState)
- Deprecated alias:
setPhysicsAtCellWhenIdle(cellX, cellY, physicsState)
setDurationAtCell(cellX, cellY, durationTicks, options?)
- Deprecated alias:
setDurationAtCellWhenIdle(cellX, cellY, durationTicks, options?)
api.elements.setDurationAtCell(
cellX,
cellY,
120,
{ updateMax: true },
);
options.durationTicks
durationTicks:number(optional)- Deprecated alias:
duration
- Deprecated alias:
api.elements.createAtCell(cellX, cellY, "steam", {
durationTicks: 120,
});
api.entities
getById(entityId)getAllByType(entityTypeId)spawnAtWorld(entityTypeId, worldX, worldY)remove(entityId)launch(entityId, angleRadians, speed?)startCapture(entityId)collect(entityId)api.fire
canBurnElementAtCell(cellX, cellY)burnElementAtCell(cellX, cellY)
- Deprecated alias:
burnElementAtCellWhenIdle(cellX, cellY)
api.i18n
t(key, params?)
const message = api.i18n.t("mods|example|count", {
count: 3,
});
register(locale, translations)
api.i18n.register("en", {
"mods|example|title": "Example",
});
getLocale()hasTranslation(key, locale?)setLocale(locale)getLanguages()getAvailableLocales()formatNumber(value, options?)
const formatted = api.i18n.formatNumber(1234.5, {
maximumFractionDigits: 1,
});
getName(definition) / getDescription(definition)
const name = api.i18n.getName({
name: "Example Machine",
nameKey: "structures|exampleMachine|name",
});
joinKey(...parts)
- Deprecated alias:
key(...parts)
createTranslatable(key, fallback)
- Deprecated alias:
translatable(key, fallback)
setGlobal(key, value)getGlobal(key)removeGlobal(key)
- Deprecated alias:
clearGlobal(key)
getGlobals()formatKeyForDisplay(keyCode)api.resources
collectFluxiteAtCell(cellX, cellY)refresh(resourceId)adjustEnergy(amount, options?)
- Deprecated alias:
updateEnergy(amount, options?)
api.resources.adjustEnergy(100, { deferUi: true });
api.structureBehaviors
registerConveyorType(structureId, options?)
options.transportOffset(optional)options.velocity(optional)options.maxTransportDistance(optional)options.transportHeight(optional)options.runWith(optional):"left"|"right"options.skipQueued(optional)
api.structureBehaviors.registerConveyorType(
"exampleConveyor",
{ runWith: "right" },
);
registerLauncherType(definition)
definition.upTypedefinition.leftTypedefinition.rightTypedefinition.velocitydefinition.softDropVelocitydefinition.runTickSharedBufferKey(optional)
api.signals
targets.register(structureTypeOrId, apply)
payload.combinedpayload.inputCountpayload.onCount
api.signals.targets.register("exampleMachine", (structure, payload) => {
api.structures.processing.setEnabledAtCell(structure.x, structure.y, payload.combined);
});
interactables.register(structureTypeOrId, handler)
handler(structure)
api.signals.interactables.register("exampleLever", (structure) => {
structure.data.on = !structure.data.on;
api.structures.update(structure);
});
registerSenderType(structureId, getOutput?)
api.signals.registerSenderType("exampleSensor", (structure) => {
return structure.data.charge >= structure.data.threshold;
});
setOutputAtCell(cellX, cellY, on)
api.structures.forEachOfType("exampleSensor", (structure) => {
api.signals.setOutputAtCell(structure.x, structure.y, structure.data.active);
});
api.structures
recipes.register(id, definition)
api.structures.recipes.register("kineticPress", {
input: "sand",
outputs: [
{ elementType: "compressedSand", chance: 1 },
],
minimumDownwardVelocityCellsPerSecond: 20,
});
register(definition, options?)
definition.buildModes[].spanTiles(optional)
api.structures.register({
id: "exampleJunction",
name: "Example Junction",
nameKey: "structures|exampleJunction|name",
description: "Links two fixed-span endpoints.",
descriptionKey: "structures|exampleJunction|description",
categoryKey: "logistics",
buildModes: [{
type: "line",
directions: ["horizontal", "vertical"],
spanTiles: 4,
}],
linkedClearance: "allOrNothing",
tooltipHover,
variants: [{
id: "exampleJunction",
angles: [-180, -90, 0, 90, 180],
}],
render: {
imageName: "exampleJunction",
size: { width: 16, height: 16 },
},
});
tooltipHover
tooltipHover: {
type: "custom",
dataFieldMessage: {
message: "Mode {mode}; channel {channel}.",
messageKey: "mods|example|machineTooltip",
fields: [
{
param: "mode",
field: "mode",
valueLabels: { input: "Receiving", output: "Sending" },
valueKeys: {
input: "mods|example|receiving",
output: "mods|example|sending",
},
},
{ param: "channel", field: "channel", fallback: 1, round: true },
],
},
}
updateDefinition(structureTypeOrId, partial, options?)
api.structures.updateDefinition("exampleJunction", {
buildModes: [{
type: "line",
directions: ["horizontal", "vertical"],
spanTiles: 6,
}],
});
registerVariant(baseStructureTypeOrId, variant, options?)
- Deprecated alias:
addVariant(baseStructureTypeOrId, variant, options?)
api.structures.registerVariant(
"exampleStructure",
{
id: "exampleStructureVertical",
angles: [-90, 90],
},
{
addBuildMode: {
type: "line",
directions: ["vertical"],
spanTiles: 4,
},
},
);
forEachOfType(structureTypeOrId, callback)
api.structures.forEachOfType("exampleStructure", (structure) => {
api.structures.updateData(structure, { active: true });
});
registerPlacementConfig(definition)
api.structures.registerPlacementConfig({
structureId: "exampleStructure",
fields: [
{
type: "integer",
id: "channel",
label: "Channel",
default: 1,
min: 1,
max: 8,
},
{
type: "choice",
id: "mode",
labelKey: "structures|exampleStructure|mode",
default: "input",
options: [
{ value: "input", label: "Input" },
{ value: "output", labelKey: "structures|exampleStructure|output" },
],
},
],
});
getAtCell(cellX, cellY)getDefinitionByType(structureType)getAvailableTypes()
- Deprecated alias:
getUnlockedTypes()
getTypeById(structureId)
- Deprecated alias:
getTypeFromId(structureId)
hasBuiltAtCell(cellX, cellY)isBlockedByPlayerAtCell(cellX, cellY)isLauncherAtCell(cellX, cellY)isType(structure, structureId)isTypeAtCell(cellX, cellY, structureId)isLockedByType(structureType)
- Deprecated alias:
isUnlockedByType(structureType)
mapValueToSpritesheetIndex(value, thresholds)
const index = api.structures.mapValueToSpritesheetIndex(
pressure,
[0, 25, 50, 75],
);
setSpritesheetIndex(structure, index)setSpritesheetIndexAtCell(cellX, cellY, index)setSpritesheetIndexByValue(structure, value, thresholds)
api.structures.setSpritesheetIndexByValue(
structure,
pressure,
[0, 25, 50, 75],
);
setSpritesheetIndexByValueAtCell(cellX, cellY, value, thresholds)
api.structures.setSpritesheetIndexByValueAtCell(
cellX,
cellY,
pressure,
[0, 25, 50, 75],
);
update(structure, options?)
api.structures.update(structure, {
propagateToWorkers: true,
});
updateData(structure, partial, options?)
- Deprecated alias:
setData(structure, partial, options?)
api.structures.updateData(
structure,
{ mode: "allow" },
{ propagateToWorkers: true },
);
buildAtCell(cellX, cellY, structureTypeOrId, options?)
- Deprecated alias:
buildAtCellWhenIdle(cellX, cellY, structureTypeOrId, options?)
removeAtCell(cellX, cellY, options?)
- Deprecated alias:
removeAtCellWhenIdle(cellX, cellY, options?)
removeBetweenCells(startCellX, startCellY, endCellX, endCellY, options?)
- Deprecated alias:
removeBetweenCellsWhenIdle(startCellX, startCellY, endCellX, endCellY, options?)
removeAtCells(positions, options?)
- Deprecated alias:
removeAtCellsWhenIdle(positions, options?)
api.structures.removeAtCells([
{ x: firstCellX, y: firstCellY },
{ x: secondCellX, y: secondCellY },
]);
processing
register(id, definition)
definition.structureTypedefinition.intervalMsdefinition.process- Deprecated alias:
api.structures.addProcessor(structureId, definition)
api.structures.processing.register(
"exampleStructure:process",
{
structureType: "exampleStructure",
intervalMs: 250,
process: (structure, context) => {
const empty = context.isCellEmptyAtCell(
structure.x,
structure.y,
);
},
},
);
context
getResolvedTypeAtCell(cellX, cellY)
- Deprecated alias:
getElementTypeAtCell(cellX, cellY)
isCellEmptyAtCell(cellX, cellY)
- Deprecated alias:
isCellEmpty(cellX, cellY)
commit(mutations)isEnabledAtCell(cellX, cellY)
- Deprecated alias:
isEnabledAt(cellX, cellY)
setEnabledAtCell(cellX, cellY, enabled)
- Deprecated alias:
setEnabledAt(cellX, cellY, enabled)
api.tech
getDefinitionById(techId)updateDefinition(techId, partial)
api.tech.updateDefinition("exampleTech", {
cost: 200,
});
registerDefinition(techId, definition)
- Deprecated alias:
addDefinition(techId, definition)
api.tech.registerDefinition("exampleTech", {
name: "Example research",
nameKey: "mods|example|techName",
description: "Unlocks the example machine.",
descriptionKey: "mods|example|techDescription",
cost: 100,
});
registerNode(techId, definition, options)
options.parentIdoptions.preferredPosition(optional)
const position = api.tech.registerNode(
"exampleTech",
techDefinition,
{ parentId: parentTechId },
);
conservatory.appendUnlock(techId, unlocks)
unlocks.structures(optional)unlocks.items(optional)
api.tech.conservatory.appendUnlock(sandkit.enums.Tech.SignalDevices, {
structures: ["exampleSensor"],
});
isResearchedById(techId)
techId:Tech | string
isLockedById(techId)setLockedById(techId, locked)api.terrains
register(definition)updateDefinition(terrainTypeOrId, partial)getTypeById(terrainId)
- Deprecated alias:
getTypeFromId(terrainId)
getIdByType(terrainType)getDefinitionByType(terrainType)getTypeAtCell(cellX, cellY)getDataAtCell(cellX, cellY)
result.hitPoints- Deprecated alias:
result.hp
isAtCell(cellX, cellY)isTypeAtCell(cellX, cellY, terrainId)isCellIdTerrain(cellId)createAtCell(cellX, cellY, terrainTypeOrId, options?)
terrainTypeOrId:number | string- Deprecated alias:
createAtCellWhenIdle(cellX, cellY, terrainTypeOrId, options?)
replaceAtCell(cellX, cellY, terrainTypeOrId, options?)
terrainTypeOrId:number | string- Deprecated alias:
replaceAtCellWhenIdle(cellX, cellY, terrainTypeOrId, options?)
removeAtCell(cellX, cellY, options?)
- Deprecated alias:
removeAtCellWhenIdle(cellX, cellY, options?)
damageAtCell(cellX, cellY, damage)setHitPointsAtCell(cellX, cellY, hitPoints)
- Deprecated alias:
setHpAtCell(cellX, cellY, hitPoints)andsetHpAtCellWhenIdle(cellX, cellY, hitPoints)
api.triggers
register(triggerId, definition)
definition.intervalMs- Deprecated alias:
definition.interval
- Deprecated alias:
definition.sequentialRunCount- Deprecated alias:
definition.sequentialRuns
- Deprecated alias:
definition.callback(trigger, deltaTimeMs)definition.datatrigger.data- Deprecated alias:
definition.extraandtrigger.extra
- Deprecated alias:
api.triggers.register("example:update", {
intervalMs: 250,
callback: (trigger, deltaTimeMs) => {
updateExample(trigger, deltaTimeMs);
},
});
api.lights.temporary
Deprecated alias: api.lights.vfx
createAtWorld(worldX, worldY, options?)
light.lightId- Deprecated alias:
light.index - Deprecated alias:
api.effects.createLightAtWorld(worldX, worldY, options?)
const light = api.lights.temporary.createAtWorld(worldX, worldY, {
brightness: 1,
durationMs: 250,
size: 80,
});
const lightId = light.lightId;
removeById(lightId)
- Deprecated alias:
api.effects.removeLightById(lightId)
if (light.lightId !== null) {
api.lights.temporary.removeById(light.lightId);
}
options.durationTicks
durationTicks:number(optional)- Deprecated alias:
duration
- Deprecated alias:
api.lights.temporary.createAtWorld(worldX, worldY, {
durationTicks: 15,
});
options.durationMs
durationMs:number(optional)
api.lights.temporary.createAtWorld(worldX, worldY, {
durationMs: 250,
});
api.lights.persistent
createAtWorld(worldX, worldY, options?)
const light = api.lights.persistent.createAtWorld(
worldX,
worldY,
{ brightness: 1, size: 80 },
);
removeAtWorld(worldX, worldY)fadeAtWorld(worldX, worldY, durationMs?)markDirty()api.player.buildings
unlockById(structureId)
- Deprecated alias:
unlockByType(structureId)
removeById(structureId)api.tools.grabber
setSize(size)getSize()isActive()isLoaded()api.shared.buffers
ensure(key, config)
config.typeconfig.length- Deprecated alias:
create(key, config)
const counts = api.shared.buffers.ensure("counts", {
type: "uint32",
length: 4,
});
get(key)api.workers
setPostUpdateEnabled(enabled)api.schedule
nextTick(callback)
callback()
api.schedule.nextTick(() => {
runDeferredWork();
});
api.sound
play(soundId, options?)playActive(soundId, options?)playLayers(layers, options?)calculateDistanceOptionsAtWorld(worldX, worldY, baseVolume?)stopBySoundId(soundId)
- Deprecated alias:
stopById(soundId)
stopActive()stopAll()api.grid
- Deprecated alias:
api.world
getDimensions()
const { widthCells, heightCells } = api.grid.getDimensions();
getCellIdAtCell(cellX, cellY)isCellEmptyAtCell(cellX, cellY)isTerrainAtCell(cellX, cellY)mutate(callback)
callback(writer)- Deprecated alias:
api.world.runWhenSimulationIdle(callback)
const waterType = api.elements.getTypeById("water");
api.events.on("item:used", ({ itemId, cellX, cellY }) => {
if (itemId !== "laser") return;
api.grid.mutate((writer) => {
if (!api.terrains.isTypeAtCell(cellX, cellY, "ice")) return;
writer.elements.replaceAtCell(cellX, cellY, waterType);
});
});
writer
elements.createAtCell(cellX, cellY, elementTypeOrId, options?)
elementTypeOrId:ElementType | string
elements.replaceAtCell(cellX, cellY, elementTypeOrId, options?)
elementTypeOrId:ElementType | string
elements.removeAtCell(cellX, cellY, options?)terrains.createAtCell(cellX, cellY, terrainTypeOrId, options?)
terrainTypeOrId:number | string
terrains.replaceAtCell(cellX, cellY, terrainTypeOrId, options?)
terrainTypeOrId:number | string
terrains.removeAtCell(cellX, cellY, options?)reportActivityAtCell(cellX, cellY)excavateAtCell(cellX, cellY, outVelocity, damage, options?)
api.grid.excavateAtCell(
cellX,
cellY,
{ x: 0, y: -120 },
25,
);
revealFogAtCell(cellX, cellY)redrawAroundCell(cellX, cellY, rangeCells)
- Deprecated alias:
api.world.redrawAroundCellWhenIdle(cellX, cellY, rangeCells)
forEachCellInRectangle(cellX, cellY, widthCells, heightCells, callback)
- Deprecated alias:
forEachCellInRect(cellX, cellY, widthCells, heightCells, callback)
forEachCellInCircle(centerCellX, centerCellY, radiusCells, callback)api.pickups
- Deprecated alias:
api.world.pickups
spawnAtWorld(type, worldX, worldY, data?, light?)
type:PickupType
remove(pickup)
- Deprecated alias:
destroy(pickup)
pickUp(pickup)getAll()getById(pickupId)api.excavation
registerProfile(id, definition)
definition.patterndefinition.powerdefinition.optionsfromGunfromRocketExplosionfromDrilluseLiteralOutVelocitydestroyNonDestructibleforceRemoveAlldrillTierDamage
definition.terrainRules[]cellType- Deprecated alias:
terrainType damageoutputElementType
const profileId = "example:voidGun";
const duneType = api.terrains.getTypeById("dune");
const sandType = api.elements.getTypeById("sand");
api.excavation.registerProfile(profileId, {
power: 8,
terrainRules: [
{
cellType: duneType,
outputElementType: sandType,
},
],
});
api.hooks.modify("excavation:prepare", (args) => {
if (
args.sourceKind !== "projectile"
|| args.sourceId !== "implosionGun"
) {
return;
}
args.profileId = profileId;
});
api.blueprints
serializeStructures(structures)localizeStructures(structures)api.factory
getLevel()getProcessCount(processId)
processId:"shakeWetSand" | "pressBurntResidue" | "growFlowers" | "condenseFlorin"
getProcessRate(processId)
processId:"shakeWetSand" | "pressBurntResidue" | "growFlowers" | "condenseFlorin"
api.game
start(options?)
options.skipIntro(optional)
api.game.start({ skipIntro: true });
api.maps
getArtifactLocations()
api.events.on("game:ready", () => {
api.maps.getArtifactLocations().forEach(({ cellX, cellY, name }) => {
addMarker(cellX, cellY, name);
});
});
getActive()getAvailable()start(mapId)api.pipes
isAtCell(cellX, cellY)isEnabledAtCell(cellX, cellY)getConnectedVentsAtCell(cellX, cellY)setEnabledAtCell(cellX, cellY, enabled)api.reactions
registerContact(definition)
definition.inputAdefinition.inputBdefinition.outputAdefinition.outputBdefinition.orientation(optional):"any"|"stacked"
api.reactions.registerContact({
inputA: "water",
inputB: "examplePowder",
outputA: "steam",
outputB: null,
orientation: "any",
});
api.rendering
getDrawPositionAtWorld(worldX, worldY)
api.events.on("frame:render", () => {
const drawPos = api.rendering.getDrawPositionAtWorld(worldX, worldY);
drawMarker(drawPos.x, drawPos.y);
});
getDrawPositionAtCell(cellX, cellY)getGridMetrics()
const { cellSize, snapGridCellSize } = api.rendering.getGridMetrics();
getOverlayViewportSize()withOverlayContext(callback)
callback(context)
api.rendering.withOverlayContext((context) => {
context.fillRect(0, 0, 16, 16);
});
api.upgrades
registerCategory(definition)register(definition)updateDefinition(itemId, upgradeId, partial)getLevelById(itemId, upgradeId)getAvailableLevelById(itemId, upgradeId)setLevelById(itemId, upgradeId, level)Worker entry
api.constants
physics.normalphysics.skipphysics.aggressiveSkipapi.collector
getValueFromCellId(cellId)getValueByType(elementType)isCellIdCollectable(cellId)isCellIdCollectableForSprite(cellId)notifyPickupAtCell(cellX, cellY)api.effects
createAtWorld(effectId, worldX, worldY, options?)
- Deprecated alias:
createEffectAtWorld(effectId, worldX, worldY, options?)
createParticlesAtWorld(worldX, worldY, options?)
api.effects.createParticlesAtWorld(worldX, worldY, { count: 8 });
api.elements
getTypeById(elementId)
- Deprecated alias:
getTypeFromId(elementId)
getIdByType(elementType)swapBetweenCells(firstCellX, firstCellY, secondCellX, secondCellY)
- Deprecated alias:
swapCells(firstCellX, firstCellY, secondCellX, secondCellY)
addParticleVelocityAtCell(cellX, cellY, velocity, maxSpeedCellsPerSecond?)getDataFieldAtCell(cellX, cellY, dataFieldNumber)setDataFieldAtCell(cellX, cellY, dataFieldNumber, value)markMovementBlockedByIndex(elementIndex)
- Deprecated alias:
markMovementBlockedByElementIndex(elementIndex)
isTypeAtCell(cellX, cellY, elementTypeOrId)
elementTypeOrId:ElementType | string
createAtCell(cellX, cellY, elementTypeOrId, options?)
elementTypeOrId:ElementType | string
api.elements.createAtCell(cellX, cellY, "water", {
durationTicks: 60,
});
replaceAtCell(cellX, cellY, elementTypeOrId, options?)
elementTypeOrId:ElementType | string
setDurationAtCell(cellX, cellY, durationTicks, options?)
const updated = api.elements.setDurationAtCell(
cellX,
cellY,
120,
{ updateMax: true },
);
getDefinitionByType(elementType)getTypeAtCell(cellX, cellY)getResolvedTypeAtCell(cellX, cellY)getResolvedTypeFromCellId(cellId)getInfoAtCell(cellX, cellY)getMatterTypeAtCell(cellX, cellY)isFreeFallingAtCell(cellX, cellY)removeAtCell(cellX, cellY, options?)moveBetweenCells(fromCellX, fromCellY, toCellX, toCellY)teleportBetweenCells(fromCellX, fromCellY, toCellX, toCellY)getVelocityAtCell(cellX, cellY)setVelocityAtCell(cellX, cellY, velocity)convertToParticleAtCell(cellX, cellY, velocity)convertFromParticleAtCell(cellX, cellY)refreshColorAtCell(cellX, cellY)setPhysicsAtCell(cellX, cellY, physicsState)api.events
on(eventId, callback, options?)
element:moved
eventId:"element:moved"callback(payload)options.guard.elementType(required)
api.events.on(
"element:moved",
(payload) => handleElementMoved(payload),
{ guard: { elementType } },
);
terrain:updated
eventId:"terrain:updated"callback(payload)options.guard.terrainType(required)- Deprecated alias:
"terrain:update"
api.events.on(
"terrain:updated",
(payload) => {
handleTerrainUpdate(payload);
},
{ guard: { terrainType } },
);
worker:update:post
eventId:"worker:update:post"callback(payload)- Deprecated alias:
"update:post"
api.events.on("worker:update:post", (payload) => {
runPostUpdate(payload);
});
emit(eventId, payload, options?)
options.guard.elementType(optional)options.guard.terrainType(optional)
api.hooks
intercept(hookId, callback, options?)
cell:process
hookId:"cell:process"callback(args, context)options.guard.elementType(required)options.priority(optional)
api.hooks.intercept("cell:process", handleCell, {
guard: { elementType },
});
element:update
hookId:"element:update"callback(args, context)options.guard.elementType(required)options.priority(optional)
api.hooks.intercept("element:update", handleUpdate, {
guard: { elementType },
});
element:move
hookId:"element:move"callback(args, context)options(optional)
api.hooks.intercept("element:move", (args, context) => {
handleElementMove(args, context);
});
element:move:blocked
hookId:"element:move:blocked"callback(args, context)options.guard.elementType(required)- Deprecated alias:
"element:blocked"
api.hooks.intercept(
"element:move:blocked",
(args, context) => {
handleBlockedMovement(args, context);
},
{ guard: { elementType } },
);
element:duration:expire
hookId:"element:duration:expire"callback(args, context)options.guard.elementType(required)- Deprecated alias:
"element:duration"
api.hooks.intercept(
"element:duration:expire",
(args, context) => {
handleDurationExpiry(args, context);
},
{ guard: { elementType } },
);
fire:element:burn
hookId:"fire:element:burn"callback(args, context)options(optional)
api.hooks.intercept("fire:element:burn", (args, context) => {
handleElementBurn(args, context);
});
shaker:elementOn
hookId:"shaker:elementOn"callback(args, context)options(optional)
api.hooks.intercept("shaker:elementOn", (args, context) => {
handleShakerElement(args, context);
});
modify(hookId, callback, options?)
hookIdcallback(args)options.guard(optional)options.priority(optional)
api.hooks.modify("example:prepare", (args) => {
args.value *= 2;
});
api.fire
canBurnElementAtCell(cellX, cellY)burnElementAtCell(cellX, cellY)api.patterns
createCircle(diameterCells)excavateAtCell(cellX, cellY, pattern, outVelocity, power, options?)
api.patterns.excavateAtCell(
cellX,
cellY,
pattern,
{ x: 0, y: -1 },
10,
);
api.player
getPositionAtWorld()
- Deprecated alias:
getWorldPosition()
isCollidingWithCell(cellX, cellY)isWithinRadiusOfCell(cellX, cellY, radiusCells)api.random
int(min, max)float(min, max)api.terrains
getTypeById(terrainId)
- Deprecated alias:
getTypeFromId(terrainId)
getIdByType(terrainType)getDefinitionByType(terrainType)getDataAtCell(cellX, cellY)
result.hitPoints- Deprecated alias:
result.hp
setHitPointsAtCell(cellX, cellY, hitPoints)
- Deprecated alias:
setHpAtCell(cellX, cellY, hitPoints)
getTypeAtCell(cellX, cellY)isAtCell(cellX, cellY)isTypeAtCell(cellX, cellY, terrainId)isCellIdTerrain(cellId)createAtCell(cellX, cellY, terrainTypeOrId, options?)replaceAtCell(cellX, cellY, terrainTypeOrId, options?)removeAtCell(cellX, cellY, options?)damageAtCell(cellX, cellY, damage)api.ui
toast(message, options?)
api.ui.toast({ key: "mods|example|workerToast" });
api.utils
getDistance(pointA, pointB)getDirection(pointA, pointB)getAngle(pointA, pointB)getCoordinatesBetweenCells(pointA, pointB)
- Deprecated alias:
getCoordinatesBetweenPoints(pointA, pointB)
api.lights.temporary
Deprecated alias: api.lights.vfx
createAtWorld(worldX, worldY, options?)
light.lightId- Deprecated alias:
light.index - Deprecated alias:
api.effects.createLightAtWorld(worldX, worldY, options?)
const light = api.lights.temporary.createAtWorld(worldX, worldY, {
durationTicks: 15,
});
const lightId = light.lightId;
api.main
emitEvent(eventId, payload)api.maps
getActive()api.worker
getIndex()getCount()api.shared.buffers
get(key)require(key, config)
config.typeconfig.length
const counts = api.shared.buffers.require("counts", {
type: "uint32",
length: 4,
});
api.structures
getTypeById(structureId)
- Deprecated alias:
getTypeFromId(structureId)
updateData(structure, partial, options?)
- Deprecated alias:
setData(structure, partial, options?)
api.structures.updateData(
structure,
{ mode: "allow" },
{ propagateToWorkers: true },
);
getAtCell(cellX, cellY)getDefinitionByType(structureType)hasBuiltAtCell(cellX, cellY)isType(structure, structureId)isTypeAtCell(cellX, cellY, structureId)forEachOfType(structureTypeOrId, callback)
callback(structure)
api.structures.forEachOfType("exampleStructure", (structure) => {
processStructure(structure);
});
update(structure, options?)
api.structures.update(structure, { propagateToWorkers: true });
setSpritesheetIndex(structure, index)setSpritesheetIndexAtCell(cellX, cellY, index)setSpritesheetIndexByValue(structure, value, thresholds)setSpritesheetIndexByValueAtCell(cellX, cellY, value, thresholds)api.structures.processing
isEnabledAtCell(cellX, cellY)
- Deprecated alias:
isEnabledAt(cellX, cellY)
api.grid
- Deprecated alias:
api.world
getDimensions()
const { widthCells, heightCells } = api.grid.getDimensions();
getCellIdAtCell(cellX, cellY)isCellEmptyAtCell(cellX, cellY)isTerrainAtCell(cellX, cellY)reportActivityAtCell(cellX, cellY)excavateAtCell(cellX, cellY, outVelocity, damage, options?)