Skip to content

Commit

Permalink
Add FluidInteractionRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaMode committed May 20, 2024
1 parent 2c4e33a commit 219d725
Show file tree
Hide file tree
Showing 11 changed files with 311 additions and 19 deletions.
1 change: 1 addition & 0 deletions modules/fluids/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
portingLib.enableTestMod()
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package io.github.fabricators_of_create.porting_lib.fluids;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LevelEvent;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;

/**
* A registry which defines the interactions a source fluid can have with its
* surroundings. Each possible flow direction is checked for all interactions with
* the source.
*
* <p>Fluid interactions mimic the behavior of {@code LiquidBlock#shouldSpreadLiquid}.
* As such, all directions, besides {@link Direction#DOWN} is tested and then replaced.
* Any fluids which cause a change in the down interaction must be handled in
* {@code FlowingFluid#spreadTo} and not by this interaction manager.
*/
public final class FluidInteractionRegistry {
private static final Map<FluidType, List<InteractionInformation>> INTERACTIONS = new HashMap<>();

/**
* Adds an interaction between a source and its surroundings.
*
* @param source the source of the interaction, this will be replaced if the interaction occurs
* @param interaction the interaction data to check and perform
*/
public static synchronized void addInteraction(FluidType source, InteractionInformation interaction) {
INTERACTIONS.computeIfAbsent(source, s -> new ArrayList<>()).add(interaction);
}

/**
* Performs all potential fluid interactions at a given position.
*
* <p>Note: Only the first interaction check that succeeds will occur.
*
* @param level the level the interactions take place in
* @param pos the position of the source fluid
* @return {@code true} if an interaction took place, {@code false} otherwise
*/
public static boolean canInteract(Level level, BlockPos pos) {
return canInteract(level, pos, true);
}

/**
* Performs all potential fluid interactions at a given position.
*
* <p>Note: Only the first interaction check that succeeds will occur.
*
* @param level the level the interactions take place in
* @param pos the position of the source fluid
* @return {@code true} if an interaction took place, {@code false} otherwise
*/
public static boolean canInteract(Level level, BlockPos pos, boolean overrideVanilla) {
FluidState state = level.getFluidState(pos);
for (Direction direction : LiquidBlock.POSSIBLE_FLOW_DIRECTIONS) {
BlockPos relativePos = pos.relative(direction.getOpposite());
List<InteractionInformation> interactions = INTERACTIONS.getOrDefault(state.getFluidType(), Collections.emptyList());
for (InteractionInformation interaction : interactions) {
if (interaction.isVanilla() && !overrideVanilla)
continue;
if (interaction.predicate().test(level, pos, relativePos, state)) {
interaction.interaction().interact(level, pos, relativePos, state);
return true;
}
}
}

return false;
}

static {
// Lava + Water = Obsidian (Source Lava) / Cobblestone (Flowing Lava)
addInteraction(PortingLibFluids.LAVA_TYPE, new InteractionInformation(
PortingLibFluids.WATER_TYPE,
fluidState -> fluidState.isSource() ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.COBBLESTONE.defaultBlockState(),
true
));

// Lava + Soul Soil (Below) + Blue Ice = Basalt
addInteraction(PortingLibFluids.LAVA_TYPE, new InteractionInformation(
(level, currentPos, relativePos, currentState) -> level.getBlockState(currentPos.below()).is(Blocks.SOUL_SOIL) && level.getBlockState(relativePos).is(Blocks.BLUE_ICE),
Blocks.BASALT.defaultBlockState(),
true
));
}

/**
* Holds the interaction data for a given source type on when to succeed
* and what to perform.
*
* @param predicate a test to see whether an interaction can occur
* @param interaction the interaction to perform
*/
public record InteractionInformation(HasFluidInteraction predicate, FluidInteraction interaction,
boolean isVanilla) {

public InteractionInformation(HasFluidInteraction predicate, FluidInteraction interaction) {
this(predicate, interaction, false);
}

/**
* Constructor which checks the surroundings fluids for a specific type
* and then transforms the source state into a block.
*
* @param type the type of the fluid that must be surrounding the source
* @param state the state of the block replacing the source
*/
public InteractionInformation(FluidType type, BlockState state) {
this(type, fluidState -> state);
}

/**
* Constructor which transforms the source state into a block.
*
* @param predicate a test to see whether an interaction can occur
* @param state the state of the block replacing the source
*/
public InteractionInformation(HasFluidInteraction predicate, BlockState state) {
this(predicate, state, false);
}

/**
* Constructor which transforms the source state into a block.
*
* @param predicate a test to see whether an interaction can occur
* @param state the state of the block replacing the source
*/
public InteractionInformation(HasFluidInteraction predicate, BlockState state, boolean isVanilla) {
this(predicate, fluidState -> state, isVanilla);
}

/**
* Constructor which checks the surroundings fluids for a specific type
* and then transforms the source state into a block.
*
* @param type the type of the fluid that must be surrounding the source
* @param getState a function to transform the source fluid into a block state
*/
public InteractionInformation(FluidType type, Function<FluidState, BlockState> getState) {
this((level, currentPos, relativePos, currentState) -> level.getFluidState(relativePos).getFluidType() == type, getState);
}

/**
* Constructor which checks the surroundings fluids for a specific type
* and then transforms the source state into a block.
*
* @param type the type of the fluid that must be surrounding the source
* @param getState a function to transform the source fluid into a block state
*/
public InteractionInformation(FluidType type, Function<FluidState, BlockState> getState, boolean isVanilla) {
this((level, currentPos, relativePos, currentState) -> level.getFluidState(relativePos).getFluidType() == type, getState, isVanilla);
}

/**
* Constructor which transforms the source state into a block.
*
* @param predicate a test to see whether an interaction can occur
* @param getState a function to transform the source fluid into a block state
*/
public InteractionInformation(HasFluidInteraction predicate, Function<FluidState, BlockState> getState) {
this(predicate, getState, false);
}

/**
* Constructor which transforms the source state into a block.
*
* @param predicate a test to see whether an interaction can occur
* @param getState a function to transform the source fluid into a block state
*/
public InteractionInformation(HasFluidInteraction predicate, Function<FluidState, BlockState> getState, boolean isVanilla) {
this(predicate, (level, currentPos, relativePos, currentState) ->
{
level.setBlockAndUpdate(currentPos, getState.apply(currentState)/*ForgeEventFactory.fireFluidPlaceBlockEvent(level, currentPos, currentPos, getState.apply(currentState))*/);
level.levelEvent(LevelEvent.LAVA_FIZZ, currentPos, 0);
}, isVanilla);
}
}

/**
* An interface which tests whether a source fluid can interact with its
* surroundings.
*/
@FunctionalInterface
public interface HasFluidInteraction {
/**
* Returns whether the interaction can occur.
*
* @param level the level the interaction takes place in
* @param currentPos the position of the source
* @param relativePos a position surrounding the source
* @param currentState the state of the fluid surrounding the source
* @return {@code true} if an interaction can occur, {@code false} otherwise
*/
boolean test(Level level, BlockPos currentPos, BlockPos relativePos, FluidState currentState);
}

/**
* An interface which performs an interaction for a source.
*/
@FunctionalInterface
public interface FluidInteraction {
/**
* Performs the interaction between the source and the surrounding data.
*
* @param level the level the interaction takes place in
* @param currentPos the position of the source
* @param relativePos a position surrounding the source
* @param currentState the state of the fluid surrounding the source
*/
void interact(Level level, BlockPos currentPos, BlockPos relativePos, FluidState currentState);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.github.fabricators_of_create.porting_lib.fluids.extensions;

import io.github.fabricators_of_create.porting_lib.fluids.FluidType;
import org.jetbrains.annotations.Nullable;

public interface FluidExtension {
@Nullable
default FluidType getFluidType() {
throw new RuntimeException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
import io.github.fabricators_of_create.porting_lib.fluids.FluidType;
import net.minecraft.world.level.material.FluidState;

import org.jetbrains.annotations.Nullable;

public interface FluidStateExtension {
/**
* Returns the type of this fluid.
*
* @return the type of this fluid
*/
@Nullable
default FluidType getFluidType() {
return ((FluidState) this).getType().getFluidType();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@ public class FluidMixin implements FluidExtension {
@Override
public FluidType getFluidType() {
var fluid = (Fluid) (Object) this;
var handler = FluidVariantAttributes.getHandler(fluid);
if (portingLibFluidType == null && handler == null) {
if (portingLibFluidType == null) {
if (fluid == Fluids.EMPTY)
portingLibFluidType = new MergingFluidAttributeFluidType(PortingLibFluids.EMPTY_TYPE, FluidVariant.of(fluid), handler);
portingLibFluidType = PortingLibFluids.EMPTY_TYPE;
if (fluid == Fluids.WATER || fluid == Fluids.FLOWING_WATER)
portingLibFluidType = new MergingFluidAttributeFluidType(PortingLibFluids.WATER_TYPE, FluidVariant.of(fluid), handler);
portingLibFluidType = PortingLibFluids.WATER_TYPE;//new MergingFluidAttributeFluidType(PortingLibFluids.WATER_TYPE, FluidVariant.of(fluid), FluidVariantAttributes.getHandler(Fluids.WATER));
if (fluid == Fluids.LAVA || fluid == Fluids.FLOWING_LAVA)
return new MergingFluidAttributeFluidType(PortingLibFluids.LAVA_TYPE, FluidVariant.of(fluid), handler);
portingLibFluidType = new FluidAttributeFluidType(FluidVariant.of(fluid), handler);
portingLibFluidType = PortingLibFluids.LAVA_TYPE;//new MergingFluidAttributeFluidType(PortingLibFluids.LAVA_TYPE, FluidVariant.of(fluid), FluidVariantAttributes.getHandler(Fluids.LAVA));
}
if (portingLibFluidType == null)
throw new RuntimeException("Mod fluids must override getFluidType.");
return this.portingLibFluidType;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.github.fabricators_of_create.porting_lib.fluids.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

import com.llamalad7.mixinextras.injector.ModifyExpressionValue;

import io.github.fabricators_of_create.porting_lib.fluids.FluidInteractionRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockState;

@Mixin(LiquidBlock.class)
public class LiquidBlockMixin {
@ModifyExpressionValue(method = "onPlace", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/LiquidBlock;shouldSpreadLiquid(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)Z"))
private boolean canPlaceCustom(boolean original, BlockState blockState, Level level, BlockPos blockPos) {
if (!FluidInteractionRegistry.canInteract(level, blockPos, false))
return true;
return original;
}

@ModifyExpressionValue(method = "neighborChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/LiquidBlock;shouldSpreadLiquid(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)Z"))
private boolean neighborChangedCustom(boolean original, BlockState blockState, Level level, BlockPos blockPos) {
if (!FluidInteractionRegistry.canInteract(level, blockPos, false))
return true;
return original;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,23 @@ public class MergingFluidAttributeFluidType extends FluidType {
private final FluidVariantAttributeHandler handler;
private final FluidType superType;
public MergingFluidAttributeFluidType(FluidType superType, FluidVariant variant, FluidVariantAttributeHandler handler) {
super(Properties.create()
.viscosity(handler.getViscosity(variant, null))
.temperature(handler.getTemperature(variant))
.lightLevel(handler.getLuminance(variant))
.sound(SoundActions.BUCKET_FILL, handler.getFillSound(variant).get())
.sound(SoundActions.BUCKET_EMPTY, handler.getEmptySound(variant).get())
.density(handler.isLighterThanAir(variant) ? -1 : 1)
);
super(createProperties(variant, handler));
this.variant = variant;
this.handler = handler;
this.superType = superType;
}

public static Properties createProperties(FluidVariant variant, FluidVariantAttributeHandler handler) {
Properties properties = Properties.create()
.viscosity(handler.getViscosity(variant, null))
.temperature(handler.getTemperature(variant))
.lightLevel(handler.getLuminance(variant))
.density(handler.isLighterThanAir(variant) ? -1 : 1);
handler.getFillSound(variant).ifPresent(soundEvent -> properties.sound(SoundActions.BUCKET_FILL, soundEvent));
handler.getEmptySound(variant).ifPresent(soundEvent -> properties.sound(SoundActions.BUCKET_EMPTY, soundEvent));
return properties;
}

@Override
public Component getDescription() {
return handler.getName(variant);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"mixins": [
"FluidMixin",
"FluidStateMixin"
"FluidStateMixin",
"LiquidBlockMixin"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.fabricators_of_create.porting_lib.fluids.testmod;

import io.github.fabricators_of_create.porting_lib.fluids.FluidInteractionRegistry;
import io.github.fabricators_of_create.porting_lib.fluids.PortingLibFluids;
import net.fabricmc.api.ModInitializer;
import net.minecraft.world.level.block.Blocks;

public class PortingLibFluidsTestmod implements ModInitializer {
@Override
public void onInitialize() {
FluidInteractionRegistry.addInteraction(PortingLibFluids.WATER_TYPE, new FluidInteractionRegistry.InteractionInformation(
(level, currentPos, relativePos, currentState) -> level.getBlockState(currentPos.below()).is(Blocks.BAMBOO_BLOCK) && level.getBlockState(relativePos).is(Blocks.MAGMA_BLOCK),
Blocks.EMERALD_BLOCK.defaultBlockState()
));
}
}
17 changes: 17 additions & 0 deletions modules/fluids/src/testmod/resources/fabric.mod.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"schemaVersion": 1,
"id": "porting_lib_fluids_testmod",
"version": "${version}",
"name": "Porting Lib Fluids Testmod",
"description": "Tests the fluids module",
"authors": [
"The Create Fabric Team"
],
"license": "LGPL",
"environment": "*",
"entrypoints": {
"main": [
"io.github.fabricators_of_create.porting_lib.fluids.testmod.PortingLibFluidsTestmod"
]
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Made in Blockbench 4.7.4
mtllib ring.mtl

o torus
io torus
v 0.03277173140761911 0.2502682088509125 0.49999999999999994
v 0.03856500978500702 0.21357799994557292 0.5883883476483184
v 0.04096466425952389 0.125 0.625
Expand Down Expand Up @@ -2690,4 +2690,4 @@ f 380/1517/380 381/1518/380 5/1519/380 4/1520/380
f 381/1521/381 382/1522/381 6/1523/381 5/1524/381
f 382/1525/382 383/1526/382 7/1527/382 6/1528/382
f 383/1529/383 384/1530/383 8/1531/383 7/1532/383
f 384/1533/384 377/1534/384 1/1535/384 8/1536/384
f 384/1533/384 377/1534/384 1/1535/384 8/1536/384

0 comments on commit 219d725

Please sign in to comment.