Liquid IO
The Example Fluid Washer consumes 250 mB of Example Coolant per craft. It has one liquid tank at liquid index 0, while its inventory separately stores the tank's display item at slot 4.
This page uses the template implementation instead of adding liquid storage to the Thermal Crusher.
Fluid Washer layout
| Kind | Index or slot | Purpose |
|---|---|---|
| Energy display slot | 0 | Current Dorios Energy. |
| Label slot | 1 | Machine status. |
| Progress slot | 2 | Progress frame. |
| Item slot | 3 | Material to wash. |
| Liquid index | 0 | Example Coolant storage. |
| Liquid display slot | 4 | Frame item generated by fluid.display(4). |
| Item slot | 5 | Washed output. |
| Upgrade slots | 6–8 | Speed, energy cost, and batch. |
| Item IO buttons | 9–14 | Six item faces. |
| Liquid IO buttons | 15–20 | Six liquid faces. |
The highest inventory slot is 20, so the helper entity uses inventory_size: 21.
1. Give the block liquid storage
Inside minecraft:block.components, the relevant block components are:
{
"utilitycraft:fluid_container": {},
"utilitycraft:example_fluid_washer": {
"entity": {
"type": "fluid_machine",
"inventory_size": 21
},
"machine": {
"energy_cap": 64000,
"energy_cost": 800,
"rate_speed_base": 40,
"fluid_cap": 16000,
"fluid_types": 1,
"upgrades": [6, 7, 8]
}
},
"tag:dorios:fluid": {}
}
| Property | Meaning |
|---|---|
type: "fluid_machine" | Spawns the shared helper entity family that supports liquids. |
fluid_cap: 16000 | Capacity of each liquid tank in millibuckets. |
fluid_types: 1 | Creates one indexed tank, so only index 0 is valid. |
utilitycraft:fluid_container | Enables UtilityCraft fluid-container interaction behavior. |
tag:dorios:fluid | Identifies the block as a fluid-compatible endpoint. |
These are block-component values, not fields passed to registerIOInterface().
2. Register item and liquid policies
import {
FluidStorage,
Machine,
registerIOInterface,
} from "DoriosCore/index.js";
const BLOCK_ID = "utilitycraft:example_fluid_washer";
const INPUT_SLOT = 3;
const FLUID_DISPLAY_SLOT = 4;
const OUTPUT_SLOT = 5;
const FLUID_INDEX = 0;
const ITEM_IO_BUTTON_SLOTS = [9, 14];
const LIQUID_IO_BUTTON_SLOTS = [15, 20];
registerIOInterface(BLOCK_ID, {
items: {
buttonSlots: ITEM_IO_BUTTON_SLOTS,
anyInputSlots: [INPUT_SLOT],
anyOutputSlots: [OUTPUT_SLOT],
modes: [
{ id: "disabled" },
{ id: "input_1", inputSlots: [INPUT_SLOT] },
{ id: "output_1", outputSlots: [OUTPUT_SLOT] },
],
},
liquids: {
buttonSlots: LIQUID_IO_BUTTON_SLOTS,
anyInputIndices: [FLUID_INDEX],
anyOutputIndices: [],
modes: [
{ id: "disabled" },
{ id: "input_1", inputIndices: [FLUID_INDEX] },
],
},
});
Liquid policies use tank indices:
inputIndices: [0]
Item policies use inventory slots:
inputSlots: [3]
The washer consumes coolant and never exposes it as an output, so anyOutputIndices is empty and no liquid output_1 mode is registered.
3. Access and display the liquid tank
After constructing a valid machine, obtain index 0 and draw it at display slot 4:
const fluid = FluidStorage.initializeSingle(machine.entity);
fluid.display(FLUID_DISPLAY_SLOT);
initializeSingle() creates or loads the scoreboard-backed storage for liquid index 0. It returns a FluidStorage instance with methods such as:
| Method | Purpose |
|---|---|
get() | Current amount. |
getFreeSpace() | Remaining capacity. |
getType() or type | Stored liquid type. |
setType(type) | Assign the tank type. |
add(amount) | Add a clamped amount. |
consume(amount) | Remove an amount when enough is available. |
display(slot) | Write the current bar frame into an inventory display slot. |
The technical FluidStorage reference documents every method.
4. Process liquid transfers
Call IO before reading the tank for the recipe tick:
const machine = new Machine(block, settings);
if (!machine.valid) return;
machine.processIO();
const fluid = FluidStorage.initializeSingle(machine.entity);
fluid.display(FLUID_DISPLAY_SLOT);
When a face is in liquid input_1 mode, processIO() can pull a compatible liquid from the neighboring endpoint into tank index 0. The default total liquid limit is 2500 mB per call.
To use a smaller limit:
machine.processIO({
maxFluidMovedPerTick: 1000,
});
5. Validate and consume coolant
The recipe script still decides which liquid is valid:
const FLUID_PER_CRAFT = 250;
if (fluid.type !== "example_coolant" || fluid.get() < FLUID_PER_CRAFT) {
machine.off();
machine.showWarning("Needs Example Coolant", {
resetProgress: false,
});
return;
}
Consume only after the item, output capacity, energy, and batch count have all been validated:
fluid.consume(craftCount * FLUID_PER_CRAFT);
fluid.display(FLUID_DISPLAY_SLOT);
The storage rejects incompatible mixing during normal transfers. The recipe check is still necessary because an empty tank or a different registered liquid is not valid fuel for this machine.
6. Display the tank and liquid IO page
The Resource Pack reads display slot 4:
{
"coolant@uc.liquid_input_1_bar": {
"$collection_index": 4,
"$has_bg": false,
"offset": [50, 0]
}
}
The IO tab enables both item and liquid pages:
{
"io@uc.io_tab": {
"$has_item_io": true,
"$has_fluid_io": true,
"$has_gas_io": false,
"$io_items_modes_description": "ui.utilitycraft:io.example_fluid_washer.items",
"$io_fluids_modes_description": "ui.utilitycraft:io.example_fluid_washer.liquids",
"$io_top_texture": "textures/blocks/examples/fluid_washer_up",
"$io_left_texture": "textures/blocks/examples/fluid_washer_west",
"$io_front_texture": "textures/blocks/examples/fluid_washer_north",
"$io_right_texture": "textures/blocks/examples/fluid_washer_east",
"$io_bottom_texture": "textures/blocks/examples/fluid_washer_down",
"$io_back_texture": "textures/blocks/examples/fluid_washer_south",
"$io_item_top_index": 9,
"$io_item_left_index": 10,
"$io_item_front_index": 11,
"$io_item_right_index": 12,
"$io_item_bottom_index": 13,
"$io_item_back_index": 14,
"$io_fluid_top_index": 15,
"$io_fluid_left_index": 16,
"$io_fluid_front_index": 17,
"$io_fluid_right_index": 18,
"$io_fluid_bottom_index": 19,
"$io_fluid_back_index": 20
}
}
liquids is the scripting configuration name. UI Core uses the older $has_fluid_io and $io_fluid_* variable names. This difference is intentional.
Add the legend:
ui.utilitycraft:io.example_fluid_washer.items=§rItem I/O Modes:\n§r- §9Input\n§r- §cOutput
ui.utilitycraft:io.example_fluid_washer.liquids=§rLiquid I/O Modes:\n§r- §9Coolant Input
Custom-liquid display requirement
FluidStorage.display() renders frame items named like:
utilitycraft:example_coolant_00
...
utilitycraft:example_coolant_48
Every custom liquid needs the complete display-item and texture sequence. Otherwise the machine can store the liquid but logs an invalid item identifier when it tries to draw the bar. The Fluids and gases registry section covers those addon assets and registrations.
Test liquid IO
- Put Example Coolant in a compatible tank beside the washer.
- Set the touching liquid face to
input_1. - Confirm the coolant bar fills and no item slot receives a fluid item.
- Insert Gravel into the washer's item input.
- Confirm every completed craft consumes
250 mB. - Change the liquid face to
disabledand confirm automatic filling stops. - Try a different liquid and confirm it cannot mix with the existing coolant.
Continue with Gas IO.