-
-
Notifications
You must be signed in to change notification settings - Fork 94
Fluids #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
IchHabeHunger54
wants to merge
13
commits into
neoforged:main
Choose a base branch
from
IchHabeHunger54:feature/fluids
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Fluids #342
Changes from 2 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
86123a9
fluid registration
IchHabeHunger54 60923cc
fluid states and waterlogging
IchHabeHunger54 ec95c48
fluid blocks, buckets and cauldrons
IchHabeHunger54 3150b57
address Champ's comments and do some rearranging
IchHabeHunger54 5331307
create a separate fluids category
IchHabeHunger54 8bd5fe4
replace copypastas
IchHabeHunger54 c35f032
address some comments
IchHabeHunger54 daf7675
address most of Champ's remaining comments
IchHabeHunger54 2a89e7a
add fluid stack docs
IchHabeHunger54 82d319d
Merge branch 'main' into feature/fluids
IchHabeHunger54 eb3f92d
fluid ingredients
IchHabeHunger54 9d7f042
address ChampionAsh's comments
IchHabeHunger54 faccfb0
fix two copypastas
IchHabeHunger54 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| --- | ||
| description: How to work with fluids, fluid states and fluid stacks, and how to add your own. | ||
| sidebar_position: 3 | ||
| --- | ||
| # Fluids | ||
|
|
||
| In vanilla Minecraft, the two fluids - water and lava - are special types of [blocks][block] that can spread to neighboring blocks over a certain distance. They are generally not solid, and [entities][entity] can enter and "swim" in them. | ||
|
|
||
| In modded Minecraft, especially in many tech mods, fluids also take on the role of recipe ingredients. This is possible because fluids exist in a separate registry and are only added to the world using fluid blocks, essentially meaning that fluids can be seen in complete independence from blocks. | ||
|
|
||
| This article aims to showcase both the in-world and the recipe aspects of fluids. | ||
|
|
||
| :::warning | ||
| Due to vanilla only having two fluids, and those fluids having a lot of special-casing, some of these systems are very hacky and - due to a lot of edge cases that cannot be reasonably caught in testing - may not always work correctly. If you find a bug with fluids, please reach out to us on Discord. | ||
| ::: | ||
|
|
||
| ## `Fluid` and `FluidType` | ||
|
|
||
| Before we can register a fluid, we must first understand a few design decisions made by Minecraft and NeoForge. | ||
|
|
||
| In Minecraft, water and lava each have two variants: a flowing fluid and a source fluid. The way this works is mostly due to hardcoding, in some association with `FluidState`s (see below). Since this hardcoding is inconvenient at best and practically impossible to use at worst, NeoForge introduces the `FluidType` class and patches a ton of places to use it. The main purpose of the `FluidType` is to contain the common logic of the fluid - e.g. the sounds it makes, whether boats can be used in it, etc. - and only leave the actual flowing logic in the fluid itself. `FluidType`s live in a separate registry added by NeoForge, and thus must be registered in addition to `Fluid`s. | ||
|
|
||
| With that in mind, let's start creating our fluid! For the sake of example, we're going to create a molten iron fluid. To get started, we need two [registries][registries]: | ||
|
|
||
| ```java | ||
| public static final DeferredRegister<Fluid> FLUIDS = | ||
| DeferredRegister.create(Registries.FLUID, ExampleMod.MOD_ID); | ||
| public static final DeferredRegister<FluidType> FLUID_TYPES = | ||
| DeferredRegister.create(NeoForgeRegistries.FLUID_TYPES, ExampleMod.MOD_ID); | ||
| ``` | ||
|
|
||
| Since `Fluid`s require a `FluidType` to be created, we create the `FluidType` first. A `FluidType`'s options are defined in a `Properties` object, similar to block properties. | ||
|
|
||
| ```java | ||
| public static final DeferredHolder<FluidType, FluidType> MOLTEN_IRON_TYPE = FLUID_TYPES.register( | ||
| // The registry name of the fluid type. Usually it makes sense to name it the same as the `Fluid`. | ||
| "molten_iron", | ||
| // The supplier for the fluid type, accepting a `FluidType.Properties` object. | ||
| () -> new FluidType(FluidType.Properties.create() | ||
| // The translation key of the fluid. While this will not be visible in vanilla Minecraft, | ||
| // it will be visible if the fluid is stored in e.g. a modded tank, or when looked at in-world | ||
| // with WAILA (What Am I Looking At?) or similar mods installed. | ||
| // In order to later make datagen easier, we use a block translation key here. | ||
| // If you do not plan on adding a block, you can replace "block." with "fluid." | ||
| .descriptionId("block." + ExampleMod.MOD_ID + ".molten_iron") | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
| // Set lava-like sounds for our fluid. This is only relevant if you have a bucket item, | ||
| // which we will look at later. | ||
| .sound(SoundActions.BUCKET_FILL, SoundEvents.BUCKET_FILL_LAVA) | ||
| .sound(SoundActions.BUCKET_EMPTY, SoundEvents.BUCKET_EMPTY_LAVA) | ||
| // We cannot swim or drown in molten iron. | ||
| .canDrown(false) | ||
| .canSwim(false) | ||
| // We want molten iron to slightly glow. | ||
| .lightLevel(5) | ||
| )); | ||
| ``` | ||
|
|
||
| :::tip | ||
| There are a bunch of other methods in `FluidType`. For example, if you were to make a more water-like fluid, the `supportsBoating()` and `isWaterLike()` methods could be interesting to you. For a full list of available methods, please see the source of `FluidType.Properties`. | ||
|
|
||
| Not all of these methods are used by vanilla systems. Some of them, such as `temperature()` or `density()`, were requested in the original design phase of the `FluidType` system for mod compatibility, and may or may not be used by modded systems. | ||
| ::: | ||
|
|
||
| With our `FluidType` created, we can move to the `Fluid` itself. NeoForge provides the `BaseFlowingFluid` class as a base for us to use, which has three inner classes: `Source`, `Flowing` and `Properties`. `Source` and `Flowing` are subclasses of `BaseFlowingFluid`, following the layout of vanilla's `WaterFluid` and `LavaFluid`, while `Properties` is once again a block properties-like object, this time responsible for tying the fluid type, source fluid, flowing fluid and later also stuff like the bucket or the fluid block together. | ||
|
|
||
| Since the source and flowing fluids depend on the fluid properties but the fluid properties also depends on the two fluids, we need to be a little careful with static initialization order and qualify with the class name in some places. Assuming you are keeping your fluids in a class named `ModFluids`, the code looks as follows: | ||
|
|
||
| ```java | ||
| // The source fluid. This is usually named without specifying "source" in the name. | ||
| public static final DeferredHolder<Fluid, BaseFlowingFluid.Source> MOLTEN_IRON = FLUIDS.register( | ||
| // The registry name. | ||
| "molten_iron", | ||
| // The source fluid supplier. Qualify the properties with the class name here. | ||
| () -> new BaseFlowingFluid.Source(ModFluids.MOLTEN_IRON_PROPERTIES)); | ||
|
|
||
| // The flowing fluid. The name is commonly prefixed with "flowing_". | ||
| public static final DeferredHolder<Fluid, BaseFlowingFluid.Flowing> FLOWING_MOLTEN_IRON = FLUIDS.register( | ||
| // The registry name. | ||
| "flowing_molten_iron", | ||
| // The flowing fluid supplier. Again, qualify the properties with the class name. | ||
| () -> new BaseFlowingFluid.Flowing(ModFluids.MOLTEN_IRON_PROPERTIES)); | ||
|
|
||
| // The fluid properties. We will use this later to connect additional stuff to the fluid, for example the bucket. | ||
| public static final BaseFlowingFluid.Properties MOLTEN_IRON_PROPERTIES = | ||
| // Parameters are the fluid type, the source fluid and the flowing fluid. | ||
| new BaseFlowingFluid.Properties(MOLTEN_IRON_TYPE, MOLTEN_IRON, FLOWING_MOLTEN_IRON); | ||
| ``` | ||
|
|
||
| With this done, your fluid should now be loaded into the game, and recipes will be able to make use of it. | ||
|
|
||
| ## Resources | ||
|
|
||
| While our fluid now exists, we aren't done yet: we still need to add the resource files for the fluid. For a fluid without a block, this is limited to textures and a translation. Blocks later also require a model and a renderer to be set up. | ||
|
|
||
| Let's start by adding the texture files. When creating your assets, it is recommended to use the vanilla water or lava texture as a basis; this is especially important with flowing fluids as they use what is effectively a 2x2 texture that is sampled by the flowing fluid renderer. The texture files must be named and placed as follows (where `examplemod` is your mod id): | ||
|
|
||
| - `assets/examplemod/textures/block/molten_iron_still.png` for the still texture, and | ||
| - `assets/examplemod/textures/block/molten_iron_flowing.png` for the flowing texture. | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
|
|
||
| Most fluids are animated, so they will also need accompanying `.png.mcmeta` files. Again, you can base these off the vanilla files. For more information, see the article on [textures]. | ||
|
|
||
| Now for the translations. The translation key used by fluids is defined by `FluidType#descriptionId()`. In our example, we used `block.examplemod.molten_iron`, so we would add a translation like so: | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```java | ||
| @Override | ||
| protected void addTranslations() { | ||
| // other translations here | ||
|
|
||
| add("block.examplemod.molten_iron", "Molten Iron"); | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Alternatively, once you have created a fluid block later: | ||
| addBlock(ModBlocks.MOLTEN_IRON.get(), "Molten Iron"); | ||
| } | ||
| ``` | ||
|
|
||
| For more information, see [I18n and L10n/Datagen][i18n]. | ||
|
|
||
| ## In-World Fluids | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
|
|
||
| When placing fluids in world, `FluidState`s are used instead of `Fluid`s, closely mirroring the use of [`BlockState`s][blockstate] versus `Block`s. Similar to `BlockState`s, `FluidState`s can be set into a level using `Level#setFluidState()`, a `FluidState` at a position can be queried using `Level#getFluidState()`, and the default state can be obtained using `Fluid#defaultFluidState()`. | ||
|
|
||
| However, `FluidState`s also exhibit a few differences to `BlockState`s. Most notably, their different states do not operate using properties, at least not properties defined in the same way as block state properties, instead the exact `FluidState` is computed by the level from fluid spreading mechanics. For most use cases the exact `FluidState` is irrelevant, save for some properties such as `isSource()` which can be queried from the `FluidState` if needed. | ||
|
|
||
| Unfortunately, the current implementation of `FluidState`s in levels is very much half-baked. Even more unfortunately, it is impossible for NeoForge to fix this without breaking compatibility with vanilla worlds. Basically all `FluidState` logic is tied to `BlockState` in some way, despite there not really being a need to. In the current implementation, `Level#getFluidState()` essentially boils down to `BlockState#getFluidState()`, happening very deep in chunk storage. It is expected that Mojang will eventually rework this, however for now we have to make do with what we have. | ||
|
|
||
| ### Waterlogging | ||
|
|
||
| _See also [Blocks][block] and [Block States][blockstate]._ | ||
|
|
||
| The epitome of this half-baked `FluidState` system is waterlogging. Waterlogging is the ability of certain non-full blocks, e.g. slabs, to also contain a water source at the same time. This is currently implemented via the `WATERLOGGED` block state property: | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```java | ||
| // Implementing SimpleWaterloggedBlock automatically enables bucket pickup | ||
| // and makes some helper methods available. | ||
| public class MyBlock extends Block implements SimpleWaterloggedBlock { | ||
| // Add the WATERLOGGED property to our class for easy access. | ||
| public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; | ||
|
|
||
| // Set WATERLOGGED to false by default. | ||
| public MyBlock(Properties properties) { | ||
| super(properties); | ||
| registerDefaultState(getStateDefinition().any().setValue(WATERLOGGED, false)); | ||
| } | ||
|
|
||
| // Add WATERLOGGED to the block state definition. | ||
| @Override | ||
| protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) { | ||
| super.createBlockStateDefinition(builder); | ||
| builder.add(WATERLOGGED); | ||
| } | ||
|
|
||
| // The important part: Query the WATERLOGGED property when asked for the fluid state. | ||
| // The `false` parameter in Fluids.WATER.getSource(false) means "falling" and is set to false | ||
| // for all vanilla waterlogging implementations. | ||
| @Override | ||
| public FluidState getFluidState(BlockState state) { | ||
| return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The `WATERLOGGED` is also the #1 reason NeoForge cannot easily fix this, because removing the `WATERLOGGED` property would break compatibility with vanilla servers due to a different set of block states. | ||
|
IchHabeHunger54 marked this conversation as resolved.
Outdated
|
||
|
|
||
| ### Fluid Blocks | ||
|
|
||
| TODO | ||
|
|
||
| ### Cauldrons | ||
|
|
||
| TODO | ||
|
|
||
| ## Fluids in Recipes | ||
|
|
||
| TODO | ||
|
|
||
| ### `FluidStack` | ||
|
|
||
| TODO | ||
|
|
||
| ### `FluidIngredient` | ||
|
|
||
| TODO | ||
|
|
||
| [block]: index.md | ||
| [blockstate]: states.md | ||
| [entity]: ../entities/index.md | ||
| [i18n]: ../resources/client/i18n.md#datagen | ||
| [registries]: ../concepts/registries.md | ||
| [tags]: ../resources/server/tags.md#datagen | ||
| [textures]: ../resources/client/textures.md | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.