Construction (Right-Click Constructable)

Vintage Story can raise a block in stages instead of placing it whole. The player sets down the first piece, right-clicks it holding the next material, and each accepted material reveals another part of the block's shape. The game calls this right-click construction, and it is how a player assembles a machine too large to carry as one item.

Building one from a mod meets two problems. The game's own construction behaviour exists only from version 1.22 on, so one block JSON cannot serve 1.20 and 1.21 too. And a half-built machine is a state your mod must answer for: broken, it should hand back some of what went into it; unfinished, its machine tick should usually not run.

Blocks/Construction/ answers both. exlib registers one behaviour under the JSON name ExRightClickConstructable on every supported version - a thin subclass of the game's own on 1.22, a full port of it on 1.20 / 1.21 - so your block JSON does not change with the game version. On top of the vanilla flow it replaces the break handler, scattering the materials of every completed stage at a fraction each mod can expose in its config, and it answers a production tick with "not yet" while the build is unfinished. You list the behaviour on your block with the stages it builds through, and that one entry carries all three.

ExRightClickConstructable

The behaviour name carries no mod prefix: exlib owns ExRightClickConstructable on every version, so one JSON entry loads against the vanilla subclass or the port alike.

"entityBehaviors": [ { "name": "ExRightClickConstructable", "properties": { /* stages */ } } ]

A stage names the shape elements it adds or removes and the material stacks it consumes; on 1.20 and 1.21 exlib's ported ExConstructionStage, ExConstructionIngredient and ExRightClickConstruction read that same table.

Public surface:

// Available on all versions:
public ItemStack[] GetConstructionDrops(float ratio, Random rand);   // materials scattered at `ratio` (0..1)
public WorldInteraction[]? GetConstructionInteractionHelp();         // next-stage build-material hover help

// 1.20 / 1.21 only (the reimplementation exposes extra state):
public bool IsComplete { get; }
public CompositeShape shape { get; }
public event Action<CompositeShape>? OnShapeChanged;
public static WorldInteraction[] AppendConstructionHelp(IWorldAccessor world, BlockSelection selection, WorldInteraction[] baseHelp);

GetConstructionDrops(ratio, rand) returns the materials this block would scatter at the given fraction of consumed stacks, summed across every completed stage - call it to salvage a partly built construction yourself, for a burst boiler or a demolition tool. GetConstructionInteractionHelp is the hover text telling the player which stack the next stage wants, and AppendConstructionHelp prepends it to the help your block already offers. IsComplete is what rendering, GetBlockInfo and the production gate below read; shape is the block narrowed to the elements built so far, and OnShapeChanged fires when a stage changes that set.

The behaviour draws no mesh of its own, so a constructable block is invisible without a companion animator (BEBehaviorAnimatable) running an always-on, looping idle animation. ConstructedAnimator wires the two together: it follows OnShapeChanged, rebuilds the animator's mesh from the elements built so far and re-applies your pose. Call its Initialize with that pose callback, its Dispose from OnBlockRemoved and OnBlockUnloaded.

A stage whose requireStacks ingredient uses a wildcard code must set storeWildCard: drop resolution needs the variant the player actually fed it, and without that record breaking the block throws inside vanilla GetDrops.

Construction gates production

exlib's production tick asks every publisher of IProductionReadiness on a block entity - the block entity itself and each of its behaviours - whether work may run (see Production Machines). ExRightClickConstructable is one: IsReadyToProduce is IsComplete and StopsProductionWhenNotReady is true, so a host that carries the behaviour and a production tick waits for the build with no gate written by hand.

"entityBehaviors": [
  { "name": "ExRightClickConstructable", "properties": { "stages": [ /* ... */ ], "gatesProduction": false } }
]

A machine that must keep ticking while unfinished (rendering a break, holding a status message) sets gatesProduction: false, either in raw JSON or through the definition builder:

.Construction(c => c.Stage(s => s.AddElements("frame")).GatesProduction(false))

With the opt-out, IsReadyToProduce is always true and StopsProductionWhenNotReady is false - the behaviour publishes nothing that blocks or stops the tick, while IsComplete still answers whatever else reads it directly (rendering, GetBlockInfo).

ExRccSettings

How much of a broken mega-block the player gets back is a balance question each mod answers for itself. ExRccSettings is the registry for that salvage fraction, resolved at break time from the block's Code.Domain, so two mods on one server can hold different values.

public static class ExRccSettings
{
    public static void RegisterBrokenDropsRatio(string domain, Func<float> ratio);
    public static float? BrokenDropsRatio(string domain);   // null if the domain didn't register
}

Register at startup, wiring the getter to your config so players can tune it live:

ExRccSettings.RegisterBrokenDropsRatio("iiex", () => IiexValues.BoilerSalvageRatio);

That is the whole integration: the behaviour reads the getter on every break, falls back to the block's own brokenDropsRatio JSON property when the domain registered nothing, and scatters the materials itself from its OnBlockBroken - not from Block.GetDrops, which returns nothing for a constructable block. A break in creative mode drops nothing.

A mega-block that should not also drop its own frame item needs GetDrops overridden to return [] on the controller block: a JSON drops: [] is not honoured for variant blocks.

  • Config System - back the salvage ratio with a live-editable value.
  • Recipe Costs - RCC stage costs are also adjustable per cost profile.
  • Helpers & Renderers - ToggleAnimator, the same animator plumbing for a machine that is animated but not built in stages.