API reference

Every public module hangs off the framework table (RCSF / RCStructureFramework) and can also be required directly: require("RCStructureFramework/<Module>"). Data structures (RCSFPlan, RCSFPiece, RCSFStructureDef, …) live in Data contracts and Structure definition.

The three calls you’ll use most

RCSF.build(structureId, plan, character, opts?) - one-call headless build.
RCSF.defineStructure(def) - validating one-call registration.
RCSF.enable(key) / RCSF.disable(key) - toggle an auto-system.

Note

Several modules can be copied into your own mod and used standalone, without depending on the framework. The Vendoring guide lists which ones.

Modules

Group

Modules

Core

Registry · PieceLibrary · Builder

Headless API

Build · Rooms · Introspect · Events

Plans & geometry

Plans · Footprints · Geometry

Materials

MaterialSource · RecipeSource · MaterialContainers

Placement & validation

PlacementHelpers · PlacementValidation · DefaultValidators

Rooms & auto-systems

RoomPersistence · PlannedConstructions · PiecePresence · RoomLighting · SpritePropertyPatcher

Presets, config & UI

Presets · Config · Client UI


Core

Registry

Structure-definition registry. Validates defs on registration (see how validation works).

registerStructure(def) -> boolean

Register an RCSFStructureDef. Returns false only if id is missing.

defineStructure(def) -> RCSFStructureDef?

Validate, default, and register in one call; an inline def.pieces array is batch-registered, and a single { default = true } variant is filled in when none is given. Also RCSF.defineStructure(def).

registerPieces(structureId, entries) -> integer

Batch-register pieces; returns the count. Auto-fills each piece’s structureId and id.

getStructure(structureId) -> RCSFStructureDef?
requireStructure(structureId) -> RCSFStructureDef

Look up a def. requireStructure errors if the id is unknown.

getStructureByRoomName(roomName) -> RCSFStructureDef?

Resolve a def from a live IsoRoom name (handles _N suffixes).

getPieceSpriteName(structureId, variant, pieceType, north) -> string?

Resolve a piece sprite.

getAllStructures() -> table<string, RCSFStructureDef>

The live registry table. Prefer Introspect for safe, copied queries.

PieceLibrary

Catalog of pickable pieces, indexed by category / tag / structure, with unlock gating.

register(piece) -> boolean
unregister(id)
unregisterStructure(structureId)

Add, remove, or bulk-remove pieces.

get(id) -> RCSFPiece?

Look up one piece.

getByCategory(category) -> RCSFPiece[]
getByCategoryAndTag(category, tag) -> RCSFPiece[]
getByCategoryGroup(group) -> RCSFPiece[]

Bucket queries.

find(predicate) -> RCSFPiece?

predicate is a function, or a descriptor table (fast path for structureId + variant + pieceType + north).

findSpriteName(structureId, variant, pieceType, north) -> string?

Resolve a sprite by descriptor.

all() -> table<string, RCSFPiece>
iter() -> fun(): RCSFPiece?

Iterate every registered piece.

isUnlockedFor(piece, player) -> boolean

Is this piece unlocked for the player (skill / magazine / research)?

addRecipeKnowledgeProvider(name, fn)

Register fn(player) -> table<string, bool>? of known recipe keys. Use this to feed magazine / research unlocks; multiple providers OR together.

makeResearchKey(research) -> string?

Stable key (item: / sprite: / entity: form) for a research source.

rebuildBuckets()

Advanced. Rebuild the indexes after mutating a piece in place.

setKnownRecipesProvider(fn)

Deprecated. Single-slot shim for addRecipeKnowledgeProvider.

Builder

Generic per-piece builder, piece-kind registry, and disassembly. Returns RCSFBuildOutcome / RCSFDisassembleOutcome; a failed build rolls back every piece placed this call.

buildFromPlan(structureId, character, materialSource, plan, options) -> RCSFBuildOutcome

The main build entry. options = { configureWallObject?, configureCellObject?, configureRoofObject?, container? }.

disassembleFromPlan(structureId, character, options) -> RCSFDisassembleOutcome

Tear down and refund. options = { data, objects?, materialSource? }.

buildFromContainer(structureId, character, container, plan) -> boolean

Legacy delegation to def.buildFromContainer.

buildCompletion(structureId, object, character) -> boolean

Finalize a placed structure.

registerPieceKind(name, handler) -> boolean

Add your own piece kind to the dispatch loop. handler = { arrayKey, place(piece, ctx) -> obj, errorReason? }.

unregisterPieceKind(name)
getPieceKindHandler(name) -> table?
getPieceKinds() -> string[]

Manage / inspect registered piece kinds.

getGableAxis(...) / getRoofPieceCount(...) / getRoofPreview(...) / getMinimumContainerMaterialCount(structureId)

Roof-geometry and material-gating helpers (forward to the def’s callbacks).

Built-in piece kinds, in iteration order: wall, cell, roof, furniture, appliance, decorative, vegetation.


Headless API

The first-class, no-UI entry points. Build and Rooms are server-authoritative - they run on the MP server / in singleplayer and no-op (or fail) on an MP client (see Multiplayer).

Build

One-call programmatic build for prefab spawners, quest rewards, and tests. The caller’s plan is never mutated. Guide: Build without the UI.

RCSF.build(structureId, plan, character, opts?) -> RCSFBuildOutcome

Also RCSF.Build.build. opts = { variant?, materialSource?, free?, container?, builderOptions?, createRoom?=true, allowClient? }. free consumes nothing; createRoom (default true) materializes the IsoRoom when the def names a room and the plan has rects (sets outcome.roomCreated).

Rooms

Claim an arbitrary area as a persisted, MP-synced IsoRoom, decoupled from the build pipeline. Fires OnRCSFRoomAssigned / OnRCSFRoomUnassigned. Guide: Assign a room.

RCSF.Rooms.assign(rectOrRects, name, opts?) -> RCSFRoomAssignment?

rectOrRects is a single {x,y,z,w,h}, a list of them, or { rects = {...} }. opts = { id?, stairs? } (id defaults to a namespaced derivation of name). Returns { id, name, rects }.

RCSF.Rooms.unassign(target, opts?) -> boolean

target is the descriptor from assign, or a rect / rect-list plus opts = { id? | name? }. Tears down the IsoBuilding(s) and forgets the record.

Introspect

Documented, mutation-safe cross-mod queries over the registry. Returns plain summaries / shallow copies, never the live def. Guide: Introspect the registry.

hasStructure(id) -> boolean
listStructureIds() -> string[]

Existence check; sorted id list.

getStructure(id) -> table?
listStructures() -> table[]

Summary { id, roomName, variantIds[], useGenericBuilder, materialSource, materialSourceKind, buildMode, hasValidation, pieceCount }.

listVariants(id) -> string[]

A structure’s variant ids.

getPiece(pieceId) -> table?

One piece (shallow copy).

listPieces(filter?) -> table[]
countPieces(filter?) -> integer

filter = { structureId?, category?, categoryGroup?, variant?, pieceType?, tag? }.

listCategories() -> string[]
listCategoryGroups() -> string[]

Distinct values in use.

Events

The framework’s custom Lua events, registered via LuaEventManager.AddEvent. Subscribe with Events.OnRCSF*.Add(handler); each delivers one descriptor table. Full reference: Events.

OnRCSFStructureBuilt(info)

info = { structureId, plan, character, placed }.

OnRCSFStructureDisassembled(info)

info = { structureId, character, removed }.

OnRCSFRoomAssigned(info) / OnRCSFRoomUnassigned(info)

info = { id, name, rects }.

RCSF.Events.NAMES holds the four names; the fire* helpers are the dispatchers the framework calls internally.


Plans & geometry

Plans

Plan construction, keys, deep-copy, and normalization.

normalizePlan(plan) -> RCSFPlan

Call this first. Stamps schemaVersion, fills every array, and lifts a legacy single rect into rects[1]. Idempotent.

getSelectionRect(startX, startY, endX, endY, z) -> RCSFRect
getSelection(startX, startY, endX, endY, z, existingRects?) -> table

Build a single-rect or multi-rect selection.

getRectanglePerimeterWalls(rect, pieceType) -> RCSFWall[]

The perimeter walls for a rect - handy when building a plan by hand.

wallSlotIsInsideRect(rect, x, y, north) -> boolean

Slot-containment test.

copyPlan(plan) / copyWall / copyRoof / copyStair / copyFurniture / copyAppliance / copyDecorative / copyVegetation

Deep-copy helpers (the authoritative field lists).

wallKey / makeWallKey / roofKey / makeRoofKey / buildWallMap / buildRoofMap

Keying and lookup-map helpers.

getRoofZ(rectIndex, plan) -> integer?
getStairLinks(plan) -> {fromZ, toZ, x, y}[]

Derived geometry.

Footprints

getFootprintFromRoomRect(rect, gableAxis?) / getFootprintFromRects(rects, gableAxis?) / getFootprintFromCells(cells, z?) / getFootprintFromPlan(structureId, plan) -> table?

Derive a footprint from a rect, rect list, cells, or a whole plan.

Geometry

Coordinate and rect utilities.

squareKey(x, y, z) / roomRecordKey(rect) / numberFromValue(v)

Stable keys and value coercion.

ensureSquare(x, y, z) -> IsoGridSquare

Get-or-create a grid square.

rectsOverlap(a, b) / rectsEdgeAdjacent4(a, b) / rectContainsCell(r, x, y) / cellInOrAdjacentToRect(r, x, y) -> boolean

Rect / cell predicates.

getStairLandingTile(stair) -> x, y, z

The Z+1 tile a stair leads onto.

isInteriorSquare(...) / isAdjacentToFootprint(...) / findNearestOutsideSquare(...) / findNearestAdjacentFootprintSquare(...) / findNearestAdjacentFootprintWalkTarget(...)

Footprint adjacency and nearest-square helpers.


Materials

See Concepts → Material sources for the model.

MaterialSource

Factory registry for material consumption. A source implements canConsume / consume / refund / availableSummary / describe.

register(kind, factory)
create(kind, ctx) -> source?

Register / instantiate a source factory fn(ctx) -> source.

fromDef(structureId, character, container, plan) -> source?

Resolve from def.materialSource / def.createMaterialSource.

Built-in kinds: "raw" (player inventory), "universal" (one container holding per-piece counts), "bag" (a bag of variant containers).

RecipeSource

Atomic heterogeneous-recipe consumption (items + tags, with keep for tools).

countAvailable(recipe, character, containers?) -> table

How many of each requirement are available.

hasAll(recipe, character, containers?) -> boolean, table?

Returns (ok, missing?).

consumeAtomic(recipe, character, containers?) -> boolean, table

Validate the whole recipe, then consume all-or-nothing.

MaterialContainers

Legacy container / loose-material tracking (item modData). Server-authoritative operations route via OnClientCommand.

isContainer(structureId, item) -> boolean / isLooseMaterial(structureId, item) -> boolean

Classify an item.

getMaterialCount(structureId, item) / getVariant(...) / getContainerVariantFromItem(...) / getMaterialVariantFromItem(...) / setState(structureId, item, variant, count)

Read and write container state.

packLooseMaterials(...) / takeMaterials(...) / addLooseMaterials(...)

Inventory operations.


Placement & validation

PlacementHelpers

Low-level object placement and tagging (used by the builder).

placeWallObject(square, north, spriteName, slotKind, options) -> IsoObject?

Place a wall / door / window slot object.

placeFloorObject(...) / placeRugObject(...) / placeRoofObject(...) / placeDoor(...) / placeWindow(...) / placeStair(...) / placeFurniture(...) / placeAppliance(...) / placeDecorative(...) / placeVegetation(...)

Per-kind placement helpers, each returning the placed IsoObject.

placeLightSwitch(...) / isLightSwitchSprite(spriteName) / squareHasRug(square) / ensureSquare(x, y, z) / getStairLandingTile(...)

Specialized helpers.

removeObject(object) -> boolean

Remove a placed object cleanly.

PlacementValidation

validateContainerPlacement(structureId, character, container, plan) -> boolean, string?, table?

Runs the default validators, then the def’s validate* hook. Returns (ok, reasonKey?, data?).

validateCompletion(structureId, character, object) / validateDisassembly(structureId, character, object)

Completion and disassembly gates.

getPlacementSummary(structureId, plan) -> table / getPieceSpriteName(...) / getRemovableObjects(structureId, data)

UI summary and lookups.

DefaultValidators

Each takes (plan) and returns boolean, string?. Opt in by name via def.validation.useDefaults.

Available validators: noEmptyPlan, noOverlap, slotKindCompatible, roofNeedsWallUnder, floorNeedsCell, zAboveEmpty, minimumRoomRectSize, stairLinks, obstructionFree, footprintFitsInRect, multiRectEdgeConnectivity.

runAll(plan, names) -> boolean, string?

Run a named set yourself.


Rooms & auto-systems

RoomPersistence

Runtime IsoRoom / IsoBuilding creation, persistence, and MP sync. Server-authoritative (see Authority & rooms). MOD_DATA_KEY = "RCStructureFrameworkRooms".

createRoom(structureId, rectOrFootprint, loading?) -> boolean

Idempotent room creation.

createAssignedRoom(structureId, rectOrFootprint, loading?) -> boolean

Like createRoom, but flags the record as a standalone assignment (backs RCSF.Rooms).

removeRoomByRect(structureId, rect, clearRecord?, loading?) -> boolean
removeRoomByRects(structureId, rects, clearRecord?, loading?) -> boolean

Single / multi-rect teardown.

rememberRoom(...) / rememberRoomFromRects(...) / forgetRoom(...) / forgetRoomByRects(...) / ensureAssignmentDefs(records?)

Record bookkeeping; ensureAssignmentDefs reconstitutes synthetic assignment defs on load.

getRoomDef(...) / hasInteriorRoom(rect) / markRoomRuntimeOnly(...) / getRoomRecords() / getRectFromRecord(record) / partitionRectsByConnectivity(rects, stairs?)

Queries and helpers.

transmitRoomRecords() / syncRoomRecords(rooms?, loading?) / restorePersistedRooms(loading?) / restoreLoadedRoomDefs(loading?)

Sync and restore.

PlannedConstructions

Server-authoritative ghost-preview store (persists unbuilt plans across relogs). MOD_DATA_KEY = "RCStructureFrameworkPlanned".

register(params) -> string?

params = { ownerId, blueprintItemId?, plan }. Server-only; clients get nil.

cancel(recordId, requesterId) -> boolean

Authorization: the owner, or "ADMIN".

markBuilt(recordId, pieceIndex, builtBy?) -> boolean

May trigger room creation when a connected group completes.

getRecord(...) / getRecordsForChunk(...) / intersects(candidatePlan) / getNextUnbuiltPieceFor(player, opts) / getRequiredMaterials(recordId, opts)

Queries.

PiecePresence

“Is this slot already a real object?” detection (for ghost rendering / dedup).

hasRealWallAt(...) / hasRealFloorAt(...) / hasRealStairAt(...) / hasObjectWithSpriteAt(...) -> boolean / isPieceRealized(piece) -> boolean

Presence checks.

wallIsoOrder / pieceIsoOrder(a, b) / sortedWallIndices(walls) / sortedPieceIndices(pieces) / inZPass(panel, pieceZ)

Render-ordering helpers.

RoomLighting

Auto-system that gives runtime-created framework rooms working light switches (vanilla only wires a switch to its room glow at chunk-load, which has already passed for a room you build at runtime). The server owns light state; clients render. It runs on its own when enabled - toggle the whole system with RCSF.enable("roomLighting") / RCSF.disable("roomLighting"). The one thing you may want to configure:

RoomLighting.setRoomFilter(fn)

Set the predicate fn(roomName) -> boolean that decides which IsoRooms RoomLighting manages. The framework injects a default matching registered structure rooms; override it only if you vendored RoomLighting standalone (so it has no Registry to consult) or want custom room detection.

SpritePropertyPatcher

Auto-system that marks door / window-frame sprites traversable in runtime rooms (vanilla relies on map-baked sprite properties a runtime build doesn’t have). Toggle with RCSF.enable("spritePatcher") / RCSF.disable("spritePatcher") - this only gates the save-load re-patch sweep; the patch on the live build path always runs.

SpritePropertyPatcher.applyToSprite(spriteName, north, slotKind)

Apply the sprite-property patch directly. Always available, even with the auto-system disabled.


Presets, config & UI

Presets

Save / load / transform layout presets (versioned JSON, auto-migrated). The JSON codec itself is the standalone Json module.

toRelative(structureId, plan) -> preset
toPlanAt(structureId, preset, anchorX, anchorY, z) -> RCSFPlan

Convert between an absolute plan and a re-anchorable preset.

load(structureId) -> preset[] / save(structureId, list) / add(...) / remove(...) / rename(...)

CRUD on a structure’s presets file.

jsonEncode(value) -> string / jsonDecode(text) -> any?

Thin wrappers over the Json codec.

Config / EventRegistration

RCSF_Config

Load-time config table. RCSF_Config.systems.<key> = false disables an auto-system; RCSF_Config.validateDefs = false disables def validation.

RCSF.enable(key) / RCSF.disable(key)

Runtime toggle for an auto-system. Keys: roomLighting, spritePatcher, roomSync, materialContainers, plannedConstructions. See Vendoring.

Each auto-system also exposes registerEvents() / unregisterEvents() (e.g. RoomLighting.registerEvents()) - the bind / unbind lifecycle that RCSF.enable / RCSF.disable call for you. Prefer the enable / disable shortcuts; reach for the raw lifecycle only when driving a vendored module yourself.

Client UI entry points

RCStructurePlacementUI.open(structureId, playerIndex, character, container)

The builder panel (require("RCStructureFramework/PlacementUI") first). RCStructurePlacementPanel is subclassable.

RCStructureDisassemblyUI.open(structureId, object, character) / RCStructurePresetsWindow.openFor(...) / RCStructurePieceCatalogPanel.openFor(opts) / RCStructureSavePresetDialog.openFor(panel)

The other UI entry points.

Internal modules

Not part of the supported surface: Migrations (preset versions; use Presets.load), System (room-sync event glue), and BuildRecipeCallbacks (routes def.buildRecipeCallbacks).