Skip to content

API Fluids

Piggo41 edited this page Oct 12, 2026 · 10 revisions

Fluids

Lua API / Fluids

Custom fluids use a registered fluid identity backed by separate flowing and still block IDs. The IDs do not need to be adjacent. Fluid simulation, rendering, bucket handling, and interactions compare the registered identity rather than treating every block with the same material as the same fluid.

On this page

Quick registration

betamoon.fluids:add(definition) validates the complete declaration before allocating either block or the optional bucket. It returns a read-only fluid reference.

local acid = betamoon.fluids:add {
    key = "my_mod:fluid/acid",
    material = betamoon.mc.blockMaterials.water,
    preset = "water",

    blocks = {
        flowing = { id = 180, key = "my_mod:block/acid_flowing" },
        still = { id = 184, key = "my_mod:block/acid_still" }
    },

    bucket = {
        id = 5200,
        key = "my_mod:item/acid_bucket",
        displayName = "Acid Bucket",
        texture = "items/acid_bucket.png"
    },

    appearance = {
        still = "blocks/acid_still.png",
        flowing = "blocks/acid_flowing.png",
        tint = 0x83D936,
        renderPass = "translucent",
        light = 0,
        lightOpacity = 3
    },

    flow = {
        tickRate = 8,
        sourceCreation = "never"
    },

    displacement = { blocks = "drop" },

    entity = {
        movement = "water", currentScale = 0.6,
        drowning = false, countsAsWet = false, extinguishesFire = false,
        contactDamage = 2, contactIntervalTicks = 10
    },

    camera = { fogColor = 0x29451B, fogDensity = 0.16, overlay = false }
}

The flowing and still blocks do not receive ordinary inventory forms. Use the bucket or another item/block of your own design when players need to carry or place the fluid. The intermediate custom-fluid example is a runnable lesson with a bucket, contact damage, and eye-fluid fog. Keep its numbered filename when using its example: keys; use your own namespace when adapting it into another mod.

Fluid fields

Field Type Default / behavior
key string Required canonical namespace:fluid/name key; namespace:name is accepted here as typed shorthand.
material water or lava Required temporary native carrier. See compatibility limits.
preset neutral, water, or lava neutral; copies initial defaults before explicit fields are applied.
appearance table Required still/flowing textures and optional render properties.
flow table Optional simulation overrides.
displacement table Optional ordinary-block displacement policy.
entity table Optional movement, breathing, wetness, fire, and damage policy.
camera table Optional eye-fluid fog and overlay policy.
blocks table Required by fluids:add; one flowing and one still declaration.
bucket table or false Optional filled bucket. Omit it or use false for no bucket.

The preset controls BetaMoon behavior; material does not select the preset automatically. This separation prevents the temporary native material from becoming fluid identity.

appearance

Field Type Default / behavior
still texture or atlas integer Required top/bottom texture.
flowing texture or atlas integer Required side and moving-surface texture.
tint RGB integer 0xFFFFFF.
renderPass solid or translucent solid for the lava preset, otherwise translucent.
light integer 0–15 15 for the lava preset, otherwise 0.
lightOpacity integer 0–255 255 for the lava preset, otherwise 3.

Texture values accept the same registered texture references, namespaced asset keys, direct PNG paths, and atlas indices as the block/item APIs. See Textures.

flow

Field Range / values Default
tickRate integer 1–1200 5 for neutral/water; 30 for lava.
levelStep integer 1–7 1 for neutral/water; 2 for lava.
slopeSearch integer 0–8 4.
sourceCreation never, two_adjacent two_adjacent for water; otherwise never.
retreatDelayChance number 0–1 0.75 for lava; otherwise 0.
dimensions table Optional Nether partial override.

The supported dimension keys are nether, hell, and minecraft:dimension/hell; they name the same override, so declare only one. An override accepts tickRate, levelStep, or both. The lava preset uses level step 1 in the Nether.

Metadata is reserved for fluid state: 0 is a source, 1–7 are horizontal levels, and 8–15 are falling states. Custom block metadata/state declarations are therefore not compatible with fluid blocks.

displacement

displacement.blocks accepts:

  • drop: break an eligible replaceable block and emit its normal drops;
  • destroy: replace it without drops;
  • block: do not replace ordinary blocks.

Another registered fluid and any block with a tile entity always block displacement. Cross-fluid replacement must be declared through an interaction.

entity

Entity behavior is selected by registered fluid identity, independently of material. Omitted fields copy the selected preset's defaults.

Field Values Behavior
movement none, water, lava Native-compatible swimming or lava movement; none keeps ordinary movement.
currentScale number 0–8 Multiplies current acceleration; 0 disables currents.
drowning boolean Living entities consume air while their eye is immersed; also controls the player's air HUD.
countsAsWet boolean Body contact satisfies BetaMoon's wetness check.
extinguishesFire boolean Body contact clears fire.
ignitesEntities boolean Body contact sets an entity on fire.
contactDamage nonnegative integer Environmental damage after each contact interval.
contactIntervalTicks integer 5–200 Interval for damage from this fluid; defaults to 10.

Body contact selects movement by greatest intersecting volume. Eye contact selects breathing and camera behavior. Equal body coverage resolves by block coordinate and fluid key. Wetness applies if any touching fluid declares it; extinguishing happens before ignition when both are present. Damage is aggregated per entity and fluid, so several blocks of the same fluid do not multiply a hit. Leaving contact resets its damage timer. The engine samples at most 64 fluid cells per entity snapshot; very large entities use an approximate sample.

The water preset supplies swimming, currents, drowning, wetness, and extinguishing. The lava preset supplies lava movement, ignition, and contact damage. neutral supplies none of those behaviors. Explicit fields can combine them independently, such as a swim-friendly fluid that does not cause drowning.

camera

The camera uses the fluid at its eye. fogColor is a 24-bit RGB integer; fogDensity is an exponential density from 0 to 1, with 0 disabling fluid fog. overlay accepts a registered texture reference, namespaced texture key, or relative PNG path using the asset rules. Set it to false to suppress the preset overlay. The water preset has water fog and an overlay; the lava preset has dense warm fog and no overlay; neutral has neither. Camera rendering reads the compiled policy and never invokes Lua during a frame.

Fluid registration requires BetaMoon's native entity hooks. A requested camera effect also requires the client fog and overlay hooks; unavailable hooks raise a registration error. The camera overlay asset must resolve before registration.

blocks and bucket

Each blocks.flowing / blocks.still entry requires id and canonical block key; displayName is optional. IDs must be distinct unused values in 1–255, but do not need to be consecutive.

The optional bucket requires a shifted item id (at least 256), canonical item key, and either texture or icon = { x, y }; displayName is optional. It stacks to one and returns the vanilla empty bucket. Empty vanilla buckets collect only metadata-0 sources. Filled buckets place a flowing metadata-0 source. A water-preset bucket evaporates in the Nether with the vanilla fizz/smoke effect.

Using an empty vanilla bucket on a custom source which has no valid configured bucket leaves both the source and the empty bucket unchanged; it never falls through and converts the source into a vanilla water/lava bucket. Placement checks modification permission for both the selected block and the face-offset destination. A filled bucket returns the vanilla empty bucket only after placement succeeds or its configured Nether evaporation completes.

Advanced block and bucket registration

Use betamoon.fluids:define when block/item declarations need to be explicit. The returned token is unpublished and must be bound during the same registration pass.

local definition = betamoon.fluids:define {
    key = "my_mod:fluid/brine",
    material = betamoon.mc.blockMaterials.water,
    appearance = { still = 19, flowing = 20 }
}

local flowing = betamoon.blocks:add {
    type = "fluid", fluid = definition, fluidRole = "flowing",
    id = 181, key = "my_mod:block/brine_flowing"
}

local still = betamoon.blocks:add {
    type = "fluid", fluid = definition, fluidRole = "still",
    id = 189, key = "my_mod:block/brine_still"
}

local brine = definition:bind { flowing = flowing, still = still }

A specialized bucket uses type = "fluid_bucket", the same definition token, id, key, optional displayName, and texture or icon. Pass its returned item reference as bucket to bind. The specialized declarations reject ordinary block/item fields that would conflict with fluid-owned simulation and presentation.

An unbound definition is reported as a registration error and is never visible through fluid lookup.

Lookup and references

betamoon.fluids provides get, getRequired, find, first, and one.

local water = betamoon.fluids:getRequired("minecraft:fluid/water")
local customWaterCarriers = betamoon.fluids:find {
    material = "water", isVanilla = false
}

get returns nil when missing; getRequired raises an error. find returns an ordinary array. first returns the first match or nil; one returns nil for none and errors for multiple matches. Query fields are key, material, preset, bucket, and isVanilla.

Fluid references expose key, owner, isVanilla, exists, material, preset, flowingBlock, stillBlock, and bucketItem (possibly nil). A fluid block reference also exposes fluid and fluidRole; a bucket item reference exposes fluid. The built-in minecraft:fluid/water and minecraft:fluid/lava references are read-only lookup entries.

Fluid interactions

betamoon.fluids.interactions:add(definition) declares deterministic contact results. fluids must contain exactly two distinct registered fluid references or keys. replace selects which one's cell changes.

betamoon.fluids.interactions:add {
    key = "my_mod:fluid_interaction/acid_water",
    fluids = { acid, "minecraft:fluid/water" },
    replace = acid,
    faces = { "north", "south", "east", "west", "up" },
    outcomes = {
        { when = { source = true }, replaceWith = "my_mod:block/acid_crystal" },
        { when = { source = false }, replaceWith = false }
    },
    effects = {
        sound = "random.fizz", volume = 0.5, pitch = 2.6,
        particle = "largesmoke", particleCount = 8
    }
}

faces defaults to all six cardinal faces. Outcomes are checked in order; when may filter source, falling, minLevel, and maxLevel. replaceWith must resolve to a registered non-fluid block, or be false for air. Omitting a matching clause leaves the fluid unchanged. Effects support sound, volume, pitch, particle, a bounded particleCount (0–64), and when = "changed" (default) or "contact".

Two rules cannot cover the same replacement fluid, counterpart, and face. Register separate non-overlapping face sets when multiple rules are required.

Vanilla lava-water hardening is also represented by the shared interaction evaluator. It preserves the Beta behavior: a lava source becomes obsidian, non-falling levels 1–4 become cobblestone, water below does not trigger hardening, and the fizz/smoke effect still occurs for contact which does not replace deep or falling lava. This built-in rule is permanent and cannot be replaced by a script rule.

Runtime behavior

  • Flow tries downward first, then all equally short horizontal routes to a drop.
  • A stable flowing cell changes to its explicitly paired still block.
  • A neighbor change wakes a still cell without assuming adjacent IDs.
  • Source creation follows the selected policy and requires support below.
  • Rendering uses a dedicated identity-aware fluid mesh and independent still/flowing textures.
  • Fluid cells have no collision box and can be ray-selected only when they are sources and liquid selection is active.
  • Current acceleration follows the selected entity.movement and currentScale policy.

Current compatibility limits

Custom materials are not part of this API yet. material = water or material = lava is a temporary compatibility carrier so Minecraft recognizes the block as liquid. BetaMoon's flow, rendering, buckets, and declared interactions use the registered fluid identity, not that carrier. The internal carrier boundary is designed so a later material registry can supply an extended material without changing fluid keys, paired IDs, or metadata.

Vanilla and third-party code outside BetaMoon's audited entity and camera paths can still classify a custom fluid by its carrier when it directly checks Material.water or Material.lava. Boats, farmland, reeds, sponges, aquatic spawning, pathfinding, and similar ecology integrations do not yet have custom identity-aware policies. Use the carrier with that limitation in mind. Scripted contact callbacks and status effects are also not part of this phase.

With BetaMoon's native hooks active, vanilla lava-water hardening uses registered vanilla fluid identity and therefore does not mistake a water-backed custom fluid for vanilla water. If those hooks are unavailable, Minecraft's original material-based hardening remains as a safe fallback and has the carrier limitation described above.

Static still/flowing textures are supported now. Fluid-specific animated atlas strips and custom empty-container types are not yet available.

Reload compatibility

Flow settings, appearance, displacement, entity and camera policies, and interaction rules can be republished on reload. Interaction rules which are no longer declared are removed at the end of the load pass. The fluid key, owner, carrier material, flowing/still block IDs and roles, bucket ID, and presence of a bucket are structural. Changing a structural field fails before shorthand allocation and requires restarting Minecraft.

If a declaration disappears, its native blocks/items remain retained until restart to protect loaded worlds and inventories. Keep numeric IDs and keys stable, and back up worlds before removing content.

Clone this wiki locally