Repository navigation
API 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
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.
| 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.
| 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.
| 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.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 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.
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.
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.
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.
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.
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.
- 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.movementandcurrentScalepolicy.
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.
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.
- Installation
- Install scripts
- Getting started
- VS Code and LuaLS
- Examples
- Reload and compatibility
- Troubleshooting
- Changelog
Scripts and shared values
- Script lifecycle
- Packaged Lua mods
- Modules
- References and stacks
- Queries
- Overrides
- Minecraft constants
- Chat and positions
- Structured data
- Script configuration
- Particles
Blocks and items
- Multiblocks
- Blocks
- Block state and placement
- Block shapes and rendering
- Block callbacks
- Block ticks and redstone
- Items
- Fluids
- Item callbacks
- Tools and armor
- Materials
- Textures
Entities
- Entities
- Entity access and movement
- Entity lifecycle and data
- Entity health and loot
- Entity inventories and relationships
- Projectiles and pickups
- Entity presentation and sounds
- Living entities and AI
- Entity parts and sensors
Assets and audio
- Assets
- Models and animations
- Built-in Minecraft models
- Model appearances
- Blockbench workflow
- Audio
- Texture-pack overrides
Events and world actions
World generation
World systems
Inventories and screens
Recipes