Fluids and networks
The vanilla fluid registry is the identity of every fluid. Water, lava and empty are minecraft:water,
minecraft:lava and minecraft:empty; HBM’s fluids are hbm:<name>; yours are <ns>:<name>. Saves name fluids by id,
never by number.
Any registered fluid works in HBM’s tanks and ducts. A fluid from another mod becomes a “foreign” FluidType
(FluidType.isForeign()). Register through HBM when you want HBM’s properties, traits, icons and tank skins.
Registering
Section titled “Registering”public static synchronized FluidType register(String namespace, String name, int color)public static synchronized void declareVanilla(String namespace, List<FluidType> fluids)register creates <namespace>:<lower-case name>, after HBM’s own fluids. Call it at mod construction. A second
registration of the same id throws. declareVanilla makes your fluids vanilla Fluids too (Golem Core TokenFluids).
Without it, recipe JSON naming <ns>:<name> and other mods’ fluid APIs cannot see them.
public final class SpaceFluids {
public static final FluidType NITROGEN = Fluids.register(NtmSpace.MOD_ID, "NITROGEN", 0xB3C6D2); public static final FluidType HCL = Fluids.register(NtmSpace.MOD_ID, "HCL", 0x00D452);
private static boolean initialised;
/** Mod construction: the trait format, the files, the vanilla fluids. */ public static synchronized void init() { if (initialised) return; initialised = true; SpaceFluidTraits.register(); List<FluidType> all = List.of(NITROGEN, HCL /* ... */); FluidProperties.applyBundled(SpaceFluids.class, all); FluidProperties.applyBundledPatches(SpaceFluids.class, NtmSpace.MOD_ID); Fluids.declareVanilla(NtmSpace.MOD_ID, all); }}Order matters: register your trait types first, then the fluids, then apply the bundled files, then declare them as vanilla fluids.
Properties
Section titled “Properties”A fluid’s colour, tint, temperature, phases, flags, traits and hazard diamond come from
data/<ns>/fluid_property/<name>.json, in the same schema as HBM’s files. Datapacks may replace them.
public static void applyBundled(Class<?> owner, Iterable<FluidType> fluids)public static synchronized void applyBundledPatches(Class<?> owner, String namespace)applyBundledreads each fluid’s file from your own jar (owneris any class of your mod) when you create the fluids, so menus, tooltips and registration see the values before any world. A fluid without a file keeps its code defaults. Every datapack load applies the files again.applyBundledPatchesreads your patches of other mods’ fluids,data/<ns>/fluid_property_patch/<fluid ns>/<fluid path>.json, from your jar.
Changing another mod’s fluid
Section titled “Changing another mod’s fluid”Never ship a file at data/hbm/fluid_property/<name>.json: which mod’s pack wins a path is not reliable across loaders.
Ship a patch under your own namespace:
{ "add": { "traits": {"ntmspace:rocket": {"isp": 308, "thrust": 981000}} }}A patch may also hold "remove": [names] (traits, flags or phases) and the overrides "color", "tint",
"temperature" and "hazard". After every load each patched fluid starts again from its own file, then takes every
patch in patching-namespace order: removals, additions, then overrides. Two mods patching one fluid apply in namespace
order (ntmprobe before ntmspace), so a trait both add is the later namespace’s. A patch naming an unregistered fluid
is ignored.
New trait types
Section titled “New trait types”public static void registerTrait(String name, Class<?> type, Function<JsonObject, Object> reader, Function<Object, JsonObject> writer)public static synchronized void registerFlag(String name, Object trait)registerTrait adds a parameterised trait, "traits": {"<name>": {...}}, to the file format. registerFlag adds a
parameterless one (a singleton instance) to "flags". Name them <ns>:<name> and call at mod construction, before any
property file is read. A name registered twice throws.
public static synchronized void register() { FluidProperties.registerTrait(FT_Rocket.NAME, FT_Rocket.class, FT_Rocket::read, FT_Rocket::write); FluidProperties.registerTrait(FT_Terraformer.NAME, FT_Terraformer.class, FT_Terraformer::read, FT_Terraformer::write); FluidProperties.registerFlag("ntmspace:uk", ULTRAKILL); FluidProperties.registerFlag("ntmspace:explosive", EXPLOSIVE);}Read a trait in code with fluid.hasTrait(FT_Rocket.class).
Textures
Section titled “Textures”- Droplet icons: ship a greyscale droplet as
assets/<ns>/textures/items/fluid_icon.png(a copy of HBM’s, or your own) and callcom.hbm.client.FluidSprites.install(ns)from your client set-up, after registering your fluids and before the first resource load. It generates one tinted sprite per fluid,<ns>:items/fluid_icon_<key>. - Fluid tank skins:
assets/<ns>/textures/models/tank/tank_<key>.png. A fluid without one showstank_none.
Container colours (canisters, gas tanks) are the data map hbm:fluid/fluid_containers; add yours with a patch
(see Data maps).
Connecting to HBM’s networks
Section titled “Connecting to HBM’s networks”A block entity joins HBM’s fluid ducts and power cables by implementing the network interfaces and declaring its ports.
The interfaces are in com.hbm.api.fluidmk2 and com.hbm.api.energymk2.
Fluid interfaces
Section titled “Fluid interfaces”| Interface | Methods |
|---|---|
IFluidConnectorMK2 |
canConnect(FluidType type, ForgeDirection dir); default: any side but UNKNOWN |
IFluidUserMK2 |
extends the connector; FluidTankNTM[] getAllTanks() |
IFluidReceiverMK2 |
long transferFluid(FluidType type, int pressure, long amount) (returns the remainder), long getDemand(FluidType type, int pressure); defaults for getReceiverSpeed, getReceivingPressureRange, getFluidPriority |
IFluidProviderMK2 |
void useUpFluid(FluidType type, int pressure, long amount), long getFluidAvailable(FluidType type, int pressure); defaults for getProviderSpeed, getProvidingPressureRange |
IFluidStandardReceiverMK2 |
implements the receiver from FluidTankNTM[] getReceivingTanks() |
IFluidStandardSenderMK2 |
implements the provider from FluidTankNTM[] getSendingTanks() |
IFluidStandardTransceiverMK2 |
both of the above |
IFluidConnectorBlockMK2 |
for a block (a multiblock cell with no block entity): canConnect(FluidType type, BlockGetter world, int x, int y, int z, ForgeDirection dir) |
Pressure ranges from 0 to IFluidUserMK2.HIGHEST_VALID_PRESSURE (5).
Power interfaces
Section titled “Power interfaces”| Interface | Methods |
|---|---|
IEnergyConnectorMK2 |
canConnect(ForgeDirection dir); default: any side but UNKNOWN |
IEnergyHandlerMK2 |
long getPower(), void setPower(long power), long getMaxPower(); defaults for usePower, transferPower, getProviderSpeed, getReceiverSpeed, getPriority, allowDirectProvision |
IEnergyReceiverMK2 |
a handler that takes power; getReceiverSpeed() defaults to getMaxPower() |
IEnergyProviderMK2 |
a handler that sends power; getProviderSpeed() defaults to getMaxPower() |
IEnergyConnectorBlock |
for a block: canConnect(BlockGetter world, BlockPos pos, ForgeDirection dir) (visual connection only); isPort(connections, cell, dir) helpers |
Priority is IEnergyHandlerMK2.ConnectionPriority: LOWEST, LOW, NORMAL, HIGH, HIGHEST.
A machine’s connection points are its ports. HBM’s com.hbm.tileentity.TileEntityLoadedBase (the root of HBM’s block
entities, and of TileEntityMachineBase) holds them. Connections are made when the machine is placed or a port’s fluid
type changes; there are no per-tick subscriptions.
public class TileEntityAirScrubber extends TileEntityMachineBase implements IFluidStandardSenderMK2, IEnergyReceiverMK2 {
public final FluidTankNTM tank = new FluidTankNTM(Fluids.CARBONDIOXIDE, 16_000); private long power; private PortDef[] cachedPorts;
public static void serverTick(Level level, BlockPos pos, BlockState state, TileEntityAirScrubber te) { te.setupAllPorts(te.getPorts()); te.updatePortPIFIFO(); // ... the machine's work }
/** Power in and CO2 out on all six sides. */ public PortDef[] getPorts() { if (cachedPorts == null) cachedPorts = TilePortShapes.around(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()); return cachedPorts; }
@Override public FluidTankNTM[] getAllTanks() { return new FluidTankNTM[] {tank}; } @Override public FluidTankNTM[] getSendingTanks() { return new FluidTankNTM[] {tank}; } @Override public long getPower() { return power; } @Override public void setPower(long power) { this.power = power; } @Override public long getMaxPower() { return 10_000; }}TileEntityLoadedBase method |
Does |
|---|---|
setupAllPorts(PortDef[] ports) |
power ports, plus fluid in and out ports where the tile is a standard receiver or sender; does nothing once set up |
setupPowerPorts(ports) / setupFluidPorts(ports) |
one kind only |
updatePortFIFO() |
updates ports, then receives into the receiving tanks and provides from the sending tanks |
updatePortPIFIFO() |
the same, plus power in |
updatePortPOFIFO() |
the same, plus power out |
destroyAllPorts() |
disables and drops every port (a machine that stops being a machine) |
TilePort.PortDef describes ports: PortDef.make(x, y, z, dirs...), atPos(...), withCon(DirPos...), and
PortDef.combine(...). TilePortShapes has HBM’s common shapes, such as around(x, y, z) (all six sides),
horizontal(x, y, z) and fromConnections(DirPos...).
Other mods
Section titled “Other mods”Other mods’ pipes reach HBM machines through com.hbm.capability.ForeignFluids, which exposes a machine’s own
getDemand/transferFluid and getFluidAvailable/useUpFluid on each loader’s fluid capability (Golem Core
FluidExposure), at pressure 0 only. It is registered for blocks in the hbm namespace only, so it does not cover an
addon’s blocks. HBM’s own duct network does not reach foreign tanks.