Skip to content

Instantly share code, notes, and snippets.

@Lanse505
Last active August 7, 2021 15:08
Show Gist options
  • Save Lanse505/e95c9da5d97c89d30253597d8c404d40 to your computer and use it in GitHub Desktop.
Save Lanse505/e95c9da5d97c89d30253597d8c404d40 to your computer and use it in GitHub Desktop.
/**
* Determines whether the entity is able to be pushed by a fluid.
*
* @param state The fluid pushing the entity
* @return If the entity can be pushed, defaults to vanilla behavior for water
*/
default boolean isPushedByFluid(FluidState state)
{
return !(self() instanceof Player) || !((Player) self()).getAbilities().flying;
}
package net.minecraftforge.common.extensions;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.material.FluidState;
public interface IForgeLivingEntity {
default LivingEntity getLivingEntity()
{
return (LivingEntity) this;
}
/**
* Returns whether the {@link LivingEntity} can breathe in the fluid.
*
* @param state The current {@link FluidState} the entity is within
* @return Whether the {@link LivingEntity} can breathe in the fluid
*/
default boolean canBreatheInFluid(FluidState state)
{
return false;
}
}
package net.minecraftforge.common.extensions;
import net.minecraft.world.level.material.FlowingFluid;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
public interface IForgeFlowingFluid extends IForgeFluid
{
default FlowingFluid self()
{
return (FlowingFluid) this;
}
/**
* Impl Spec of {@link IForgeFluid#is(FluidState, FluidState)}
* Used to check if two FluidStates matches each other by comparing the fluid of the state itself.
*
* @param state The primary state to check
* @param otherState The secondary state to check
* @return Whether the fluid type of the other fluid matches either the flowing or source type of the first fluidstate.
*/
@Override
default boolean is(FluidState state, FluidState otherState)
{
Fluid otherFluid = otherState.getType();
return otherFluid == self().getFlowing() || otherFluid == self().getSource();
}
}
package net.minecraftforge.common.extensions;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.SetTag;
import net.minecraft.tags.Tag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.ConcretePowderBlock;
import net.minecraft.world.level.block.FarmBlock;
import net.minecraft.world.level.block.SpongeBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
public interface IForgeFluidState
{
private FluidState self()
{
return (FluidState)this;
}
/**
* Location sensitive version of getExplosionResistance
*
* @param world The current world
* @param pos Block position in world
* @param explosion The explosion
* @return The amount of the explosion absorbed.
*/
default float getExplosionResistance(BlockGetter world, BlockPos pos, Explosion explosion)
{
return self().getType().getExplosionResistance(self(), world, pos, explosion);
}
/**
* Queried for the Fluids Base PathNodeType.
* Used to determine what the path node priority value is for the fluid.
* <ul>
* <li>Negative Values = Untraversable</li>
* <li>0 = Best</li>
* <li>Highest = Worst</li>
* </ul>
* @param level The current world's block reader
* @param pos The position of the fluid
* @return {@code null} for default behavior; otherwise, returns the fluid's PathNodeType for pathfinding purposes
*/
@Nullable
default BlockPathTypes getPathNodeType(BlockGetter level, BlockPos pos)
{
return getPathNodeType(level, pos, null);
}
/**
* Queried for the Fluids Base PathNodeType.
* Used to determine what the path node priority value is for the fluid.
* <ul>
* <li>Negative Values = Untraversable</li>
* <li>0 = Best</li>
* <li>Highest = Worst</li>
* </ul>
* @param level The current level's block getter
* @param pos The position of the fluid
* @param entity The pathing entity, can be null
* @return {@code null} for default behavior; otherwise, returns the fluid's PathNodeType for pathfinding purposes
*/
@Nullable
default BlockPathTypes getPathNodeType(BlockGetter level, BlockPos pos, @Nullable Mob entity)
{
return self().getType().getPathNodeType(self(), level, pos, entity);
}
/**
* Gets the {@link BlockPathTypes} of the fluid when adjacent to some pathfinding entity.
* <ul>
* <li>Negative Values = Untraversable</li>
* <li>0 = Best</li>
* <li>Highest = Worst</li>
* </ul>
* @param level The current level's block getter
* @param pos The position of the fluid
* @param originalType The {@link BlockPathTypes} obtained from {@link IForgeBlock#getAiPathNodeType(BlockState, BlockGetter, BlockPos, Mob)}
* @return {@code null} for default behavior; otherwise, returns the fluid's adjacent {@link BlockPathTypes}
*/
@Nullable
default BlockPathTypes getAdjacentNodeType(BlockGetter level, BlockPos pos, BlockPathTypes originalType)
{
return getAdjacentNodeType(level, pos, null, originalType);
}
/**
* Gets the {@link BlockPathTypes} of the fluid when adjacent to some pathfinding entity.
* <ul>
* <li>Negative Values = Untraversable</li>
* <li>0 = Best</li>
* <li>Highest = Worst</li>
* </ul>
* @param level The current level's block getter
* @param pos The position of the fluid
* @param entity The pathing entity, can be null
* @param originalType The {@link BlockPathTypes} obtained from {@link IForgeBlock#getAiPathNodeType(BlockState, BlockGetter, BlockPos, Mob)}
* @return {@code null} for default behavior; otherwise, returns the fluid's adjacent {@link BlockPathTypes}
*/
@Nullable
default BlockPathTypes getAdjacentNodeType(BlockGetter level, BlockPos pos, @Nullable Mob entity, BlockPathTypes originalType)
{
return self().getType().getAdjacentNodeType(self(), level, pos, entity, originalType);
}
/**
* Handles acceleration or "pushing" while moving through the fluid.
* This implementation is slightly modified default behavior for fluid acceleration, based on {@link Entity#updateFluidHeightAndDoFluidPushing(Tag, double)}.
*
* @param entity The current {@link Entity} that motion is being applied to
* @return Whether the motion was successfully applied to the {@link Entity}
*/
default boolean updateFluidHeightAndDoFluidPushing(Entity entity)
{
return self().getType().updateFluidHeightAndDoFluidPushing(self(), entity);
}
/**
* Handles "motion" modification for fluids.
* Things like slower movement, "swimming" slowdown, etc.
*
* @param entity The {@link LivingEntity} whose motion is being handled
* @param travelVector The current travel {@link Vec3}
* @param gravity The current gravity being applied to the {@link LivingEntity}
*/
default void handleMotion(LivingEntity entity, Vec3 travelVector, double gravity)
{
self().getType().handleMotion(self(), entity, travelVector, gravity);
}
/**
* Handles modification of 'jumps' inside of a fluid.
*
* @param entity The {@link LivingEntity} whose jump is being modified
*/
default void jump(LivingEntity entity)
{
self().getType().jump(self(), entity);
}
/**
* Handles modifications of 'sinking' inside of a fluid
*
* @param entity The {@link LivingEntity} whose jump is being modified
*/
default void sink(LivingEntity entity)
{
self().getType().sink(self(), entity);
}
/**
* Dictates whether a player can swim in this fluid or not.
* Swimming in this case refers the the "sneak" behavior swimming and swimming animation in custom fluids.
*
* @return Whether a player can "swim" in this fluid
*/
default boolean canSwim()
{
return self().getType().canSwim(self());
}
/**
* Dictates whether a {@link LivingEntity} can drown in this fluid or not.
*
* @param entity The entity within the fluid
* @return Whether the {@link LivingEntity} can drown or not
*/
default boolean canDrown(LivingEntity entity)
{
return self().getType().canDrown(self(), entity);
}
/**
* Dictates whether this {@link FluidState} can provide "hydration" to the provided {@link BlockState}
* This is used for checks such as:
* - {@link net.minecraft.world.level.block.FarmBlock#isNearWater(LevelReader, BlockPos)}
* - {@link net.minecraft.world.level.block.ConcretePowderBlock#touchesLiquid(BlockGetter, BlockPos)}
* - {@link net.minecraft.world.level.block.CoralBlock#scanForWater(BlockGetter, BlockPos)}
* - {@link net.minecraft.world.level.block.SpongeBlock#tryAbsorbWater(Level, BlockPos)}
* - {@link net.minecraft.world.level.block.SugarCaneBlock#canSurvive(BlockState, LevelReader, BlockPos)}
*
* @param blockState the provided {@link BlockState}
* @return Whether the provided {@link FluidState} can provide hydration for the {@link BlockState}
*/
default boolean canHydrate(BlockState blockState)
{
return self().getType().canHydrate(self(), blockState);
}
/**
* This method is used to handle fluid interactions.
* The current position of the fluid is the one that should be replaced during the interaction.
*
* IE. (Fluid + Catalyst = Result) where the Fluid and Result are in the same position.
* Lava(Source/Flowing) + Water = Obsidian/Cobblestone.
* Lava(Source/Flowing) + Blue Ice = Basalt.
*
* @param level The {@link Level} containing the interaction
* @param pos The {@link BlockPos} the interaction is being applied at
* @return Whether a fluid tick needs to be scheduled. Should return true only if no reaction has occurred or the Result is another fluid.
*/
default boolean handleFluidInteraction(Level level, BlockPos pos)
{
return self().getType().handleFluidInteraction(self(), level, pos);
}
/**
* This is a checker method to check whether two fluidstates match each other using their Fluid as the reference point.
*
* @param otherState The secondary state to check
* @return Whether the two provided {@link FluidState}s {@link Fluid}s match.
*/
default boolean is(FluidState otherState)
{
return self().getType().is(self(), otherState);
}
/**
* This method dictates whether this fluid supports boats being "usable" with it.
*
* @param boat The supplied {@link Boat} entity
* @return Whether the fluid supports boats being used with it.
*/
default boolean canBoat(Boat boat)
{
return self().getType().canBoat(self(), boat);
}
}
package net.minecraftforge.common.extensions;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.fluids.FluidAttributes;
import javax.annotation.Nullable;
import java.util.Set;
public interface IForgeFluid
{
/**
* Location sensitive version of getExplosionResistance
*
* @param state the provided {@link FluidState}
* @param level the current {@link Level}
* @param pos the provided {@link BlockPos} location
* @param explosion the occurring {@link Explosion}
* @return The amount of the explosion absorbed, defaults to the states getExplosionResistance value.
*/
@SuppressWarnings("deprecation")
default float getExplosionResistance(FluidState state, BlockGetter level, BlockPos pos, Explosion explosion)
{
return state.getExplosionResistance();
}
/**
* Retrieves a list of tags names this is known to be associated with.
* This should be used in favor of TagCollection.getOwningTags, as this caches the result and automatically updates when the TagCollection changes.
*/
Set<ResourceLocation> getTags();
/**
* Retrieves the non-vanilla fluid attributes.
*/
FluidAttributes getAttributes();
/**
* Queried for the Fluids Base PathNodeType.
* Used to determine what the path node priority value is for the fluid.
* <ul>
* <li>Negative Values = Untraversable</li>
* <li>0 = Best</li>
* <li>Highest = Worst</li>
* </ul>
* @param state The current FluidState
* @param level The current {@link Level}'s block reader
* @param pos The position of the fluid
* @param entity The pathing entity, can be null
* @return {@code null} for default behavior; otherwise, returns the fluid's {@link BlockPathTypes} for pathfinding purposes
*/
@Nullable
default BlockPathTypes getPathNodeType(FluidState state, BlockGetter level, BlockPos pos, @Nullable Mob entity)
{
return null;
}
/**
* Gets the {@link BlockPathTypes} of the fluid when adjacent to some pathfinding entity.
* <ul>
* <li>Negative Values = Untraversable</li>
* <li>0 = Best</li>
* <li>Highest = Worst</li>
* </ul>
* @param state the current FluidState
* @param level the current {@link Level}'s block reader
* @param pos the position of the fluid
* @param entity the pathing entity, can be null
* @param originalType the {@link BlockPathTypes} obtained from {@link IForgeBlock#getAiPathNodeType(BlockState, BlockGetter, BlockPos, Mob)}
* @return {@code null} for default behavior; otherwise, returns the fluid's adjacent {@link BlockPathTypes}
*/
@Nullable
default BlockPathTypes getAdjacentNodeType(FluidState state, BlockGetter level, BlockPos pos, @Nullable Mob entity, BlockPathTypes originalType)
{
return null;
}
/**
* Handles acceleration or "pushing" while moving through the fluid.
* This implementation is slightly modified default behavior for fluid acceleration, based on {@link Entity#updateFluidHeightAndDoFluidPushing(net.minecraft.tags.Tag, double)}.
*
* @param state The {@link FluidState} the entity is in
* @param entity The current {@link Entity} that motion is being applied to
* @return Whether the motion was successfully applied to the {@link Entity}
*/
default boolean updateFluidHeightAndDoFluidPushing(FluidState state, Entity entity)
{
if (entity.touchingUnloadedChunk()) return false;
else
{
AABB box = entity.getBoundingBox().deflate(0.001D);
int minX = Mth.floor(box.minX);
int maxX = Mth.ceil(box.maxX);
int minY = Mth.floor(box.minY);
int maxY = Mth.ceil(box.maxY);
int minZ = Mth.floor(box.minZ);
int maxZ = Mth.ceil(box.maxZ);
double eyeLevel = 0.0D;
boolean isInFluid = false;
Vec3 motion = Vec3.ZERO;
int withinFluidBlocks = 0;
BlockPos.MutableBlockPos fluidPos = new BlockPos.MutableBlockPos();
for (int x = minX; x < maxX; ++x)
{
for (int y = minY; y < maxY; ++y)
{
for (int z = minZ; z < maxZ; ++z)
{
fluidPos.set(x, y, z);
FluidState currentState = entity.level.getFluidState(fluidPos);
if (currentState == state)
{
double fluidHeight = y + currentState.getHeight(entity.level, fluidPos);
if (fluidHeight >= box.minY)
{
isInFluid = true;
eyeLevel = Math.max(fluidHeight - box.minY, eyeLevel);
if (entity.isPushedByFluid(currentState))
{
Vec3 fluidFlow = currentState.getFlow(entity.level, fluidPos);
if (eyeLevel < 0.4D) fluidFlow = fluidFlow.scale(eyeLevel);
motion = motion.add(fluidFlow);
++withinFluidBlocks;
}
}
}
}
}
}
if (motion.length() > 0.0D)
{
if (withinFluidBlocks > 0) motion = motion.scale(1.0D / (double) withinFluidBlocks);
if (!(this instanceof Player)) motion = motion.normalize();
Vec3 entityMotion = entity.getDeltaMovement();
motion = motion.scale(getAttributes().getMotionScale(state, entity));
if (Math.abs(entityMotion.x) < 0.003D && Math.abs(entityMotion.z) < 0.003D && motion.length() < 0.0045D)
motion = motion.normalize().scale(0.0045D);
entity.setDeltaMovement(entityMotion.add(motion));
}
entity.setInFluid(state);
entity.addFluidHeight(state, eyeLevel);
if (isInFluid)
{
FluidAttributes attr = state.getType().getAttributes();
entity.fallDistance *= attr.getFallDistanceModifier(state, entity);
if (attr.canExtinguish(state, entity)) entity.clearFire();
}
return isInFluid;
}
}
/**
* Handles "motion" modification for fluids.
* Things like slower movement, "swimming" slowdown, etc.
*
* @param state The current {@link FluidState}
* @param entity The {@link LivingEntity} whose motion is being handled
* @param travelVector The current travel {@link Vec3}
* @param gravity The current gravity being applied to the {@link LivingEntity}
*/
default void handleMotion(FluidState state, LivingEntity entity, Vec3 travelVector, double gravity)
{
FluidAttributes attributes = state.getType().getAttributes();
boolean hasNegativeYMotion = entity.getDeltaMovement().y <= 0.0D;
double originalY = entity.getY();
float horizontalModifier = attributes.getHorizontalMotionModifier(state, entity);
float movementAmount = 0.02F;
float depthStriderModifier = attributes.getEnchantmentMotionModifier(Enchantments.DEPTH_STRIDER, entity);
if (depthStriderModifier > 3.0F) depthStriderModifier = 3.0F;
if (!entity.isOnGround()) depthStriderModifier *= 0.5F;
if (depthStriderModifier > 0.0F)
{
horizontalModifier += (0.546 - horizontalModifier) * depthStriderModifier / 3.0F;
movementAmount += (entity.getSpeed() - movementAmount) * depthStriderModifier / 3.0F;
}
if (entity.hasEffect(MobEffects.DOLPHINS_GRACE)) attributes.modifyMotionByEffect(MobEffects.DOLPHINS_GRACE, entity, horizontalModifier, true);
movementAmount *= entity.getAttribute(ForgeMod.SWIM_SPEED.get()).getValue();
entity.moveRelative(movementAmount, travelVector);
entity.move(MoverType.SELF, entity.getDeltaMovement());
Vec3 entityMotion = entity.getDeltaMovement();
if (entity.horizontalCollision && entity.onClimbable())
entityMotion = new Vec3(entityMotion.x, 0.2D, entityMotion.z);
entity.setDeltaMovement(entityMotion.multiply(horizontalModifier, 0.8F, horizontalModifier));
Vec3 finalMotion = entity.getFluidFallingAdjustedMovement(gravity, hasNegativeYMotion, entity.getDeltaMovement());
entity.setDeltaMovement(finalMotion);
if (entity.horizontalCollision && entity.isFree(finalMotion.x, finalMotion.y + 0.6D - entity.getY() + originalY, finalMotion.z))
entity.setDeltaMovement(finalMotion.x, 0.3F, finalMotion.z);
}
/**
* Handles modification of jumps inside of a fluid.
*
* @param state The current {@link FluidState} the {@link LivingEntity} is in
* @param entity The {@link LivingEntity} whose jump is being modified
*/
default void jump(FluidState state, LivingEntity entity)
{
entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, 0.04D * entity.getAttribute(ForgeMod.SWIM_SPEED.get()).getValue(), 0.0D));
}
/**
* Handles modifications of 'sinking' inside of a fluid
*
* @param state The current {@link FluidState} the {@link LivingEntity} is in
* @param entity The {@link LivingEntity} whose jump is being modified
*/
default void sink(FluidState state, LivingEntity entity)
{
entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, (double)-0.04F * entity.getAttribute(net.minecraftforge.common.ForgeMod.SWIM_SPEED.get()).getValue(), 0.0D));
}
/**
* This method is used to handle fluid interactions.
* The current position of the fluid is the one that should be replaced during the interaction.
*
* IE. (Fluid + Catalyst = Result) where the Fluid and Result are in the same position.
* Lava(Source/Flowing) + Water = Obsidian/Cobblestone.
* Lava(Source/Flowing) + Blue Ice = Basalt.
*
* @param state The current {@link FluidState}
* @param level The {@link Level} containing the interaction
* @param pos The {@link BlockPos} the interaction is being applied at
* @return Whether a fluid tick needs to be scheduled. Should return true only if no reaction has occurred or the Result is another fluid.
*/
default boolean handleFluidInteraction(FluidState state, Level level, BlockPos pos)
{
return true;
}
/**
* This is a checker method to check whether two fluidstates match each other using their Fluid as the reference point.
*
* @param state The primary state to check
* @param otherState The secondary state to check
* @return Whether the two provided {@link FluidState}s {@link Fluid}s match.
*/
default boolean is(FluidState state, FluidState otherState)
{
return state.getType() == otherState.getType();
}
/**
* Dictates whether a player can swim in this fluid or not.
* Swimming in this case refers the the "sneak" behavior swimming and swimming animation in custom fluids.
*
* @param state The current {@link FluidState} the player is in
* @return Whether a player can "swim" in this fluid
*/
default boolean canSwim(FluidState state)
{
return state.getType().getAttributes().canSwim(state);
}
/**
* Dictates whether a {@link LivingEntity} can drown in this fluid or not.
*
* @param state The current {@link FluidState}
* @param entity The entity within the fluid
* @return Whether the {@link LivingEntity} can drown or not
*/
default boolean canDrown(FluidState state, LivingEntity entity)
{
return state.getType().getAttributes().canDrown(state, entity);
}
/**
* Dictates whether a {@link Entity} can be extinguished by the fluid if on fire.
*
* @param state the current {@link FluidState}
* @param entity the {@link Entity} to test against
* @return Whether the provided {@link Entity} can be extinguished
*/
default boolean canExtinguish(FluidState state, Entity entity) {
return state.getType().getAttributes().canExtinguish(state, entity);
}
/**
* Dictates whether this {@link FluidState} can provide "hydration" to the provided {@link BlockState}
* This is used for checks such as:
* - {@link net.minecraft.world.level.block.FarmBlock#isNearWater(LevelReader, BlockPos)}
* - {@link net.minecraft.world.level.block.ConcretePowderBlock#touchesLiquid(BlockGetter, BlockPos)}
* - {@link net.minecraft.world.level.block.CoralBlock#scanForWater(BlockGetter, BlockPos)}
* - {@link net.minecraft.world.level.block.SpongeBlock#tryAbsorbWater(Level, BlockPos)}
* - {@link net.minecraft.world.level.block.SugarCaneBlock#canSurvive(BlockState, LevelReader, BlockPos)}
*
* @param fluidState the provided {@link FluidState}
* @param blockState the provided {@link BlockState}
* @return Whether the provided {@link FluidState} can provide hydration for the {@link BlockState}
*/
default boolean canHydrate(FluidState fluidState, BlockState blockState)
{
return fluidState.getType().getAttributes().canHydrate(blockState);
}
/**
* This method dictates whether this fluid supports boats being "usable" with it.
*
* @param state The supplied {@link FluidState}
* @param boat The supplied {@link Boat} entity
* @return Whether the fluid supports boats being used with it.
*/
default boolean canBoat(FluidState state, Boat boat)
{
return state.getType().getAttributes().canBoat(state, boat);
}
}
package net.minecraftforge.client;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Matrix4f;
import com.mojang.math.Vector3f;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.FogRenderer;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.fluids.FluidAttributes;
import javax.annotation.Nullable;
public interface IFluidRenderProperties
{
IFluidRenderProperties DUMMY = new IFluidRenderProperties()
{
};
/**
* Used to render the FluidOverlay while inside of a Fluid
* @param mc The current {@link Minecraft} client instance
* @param stack The current {@link PoseStack} instance
*/
default void renderOverlay(Minecraft mc, PoseStack stack)
{
Player player = mc.player;
FluidAttributes attr = player.getTouchingFluid().getType().getAttributes();
@Nullable ResourceLocation overlayTexture = attr.getViewOverlayTexture();
if (overlayTexture != null)
{
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.enableTexture();
RenderSystem.setShaderTexture(0, overlayTexture);
BufferBuilder builder = Tesselator.getInstance().getBuilder();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
float brightness = player.getBrightness();
long color = Integer.toUnsignedLong(attr.getColor());
float red = ((color >> 16) & 0xFF) / 255.0F * brightness;
float green = ((color >> 8) & 0xFF) / 255.0F * brightness;
float blue = (color & 0xFF) / 255.0F * brightness;
float alpha = 0.1F;
float uOffset = -player.getYRot() / 64.0F;
float vOffset = player.getXRot() / 64.0F;
Matrix4f matrix = stack.last().pose();
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR_TEX);
builder.vertex(matrix, -1.0F, -1.0F, -0.5F).color(red, green, blue, alpha).uv(4.0F + uOffset, 4.0F + vOffset).endVertex();
builder.vertex(matrix, 1.0F, -1.0F, -0.5F).color(red, green, blue, alpha).uv(uOffset, 4.0F + vOffset).endVertex();
builder.vertex(matrix, 1.0F, 1.0F, -0.5F).color(red, green, blue, alpha).uv(uOffset, vOffset).endVertex();
builder.vertex(matrix, -1.0F, 1.0F, -0.5F).color(red, green, blue, alpha).uv(4.0F + uOffset, vOffset).endVertex();
builder.end();
BufferUploader.end(builder);
RenderSystem.disableBlend();
}
}
/**
* Used to properly set the fog color into an Vector3f for the Shader system to interpret.
*
* @param camera The current {@link Camera} instance
* @param partialTicks The current partial ticks represented as a float
* @param level The current {@link ClientLevel}
* @return Returns an {@link Vector3f} representation of the stored color value of the fluid the camera is currently inside of.
*/
default Vector3f setFogColor(Camera camera, float partialTicks, ClientLevel level)
{
long color = Integer.toUnsignedLong(camera.getEntity().getTouchingFluid().getType().getAttributes().getColor());
return new Vector3f(((color >> 16) & 0xFF) / 255.0F, ((color >> 8) & 0xFF) / 255.0F, (color & 0xFF) / 255.0F);
}
/**
* Used to manually modify the current fog color while inside of a fluid.
* This new fog color is represented as a {@link Vector3f} for the Shader system to interpret.
*
* @param camera The current {@link Camera} instance
* @param partialTicks The current partial ticks represented as a float
* @param level The current {@link ClientLevel}
* @param red The red value represented as a float between 0-1
* @param green The green value represented as a float between 0-1
* @param blue The blue value represented as a float between 0-1
* @return Returns an {@link Vector3f} representation of the stored color value of the fluid the camera is currently inside of.
*/
default Vector3f modifyFogColor(Camera camera, float partialTicks, ClientLevel level, float red, float green, float blue)
{
return new Vector3f(red, green, blue);
}
/**
* Used to properly setup the fluid fog for when you're inside of the fluid.
*
* @param camera The current {@link Camera} instance
* @param fogMode The currently set {@link net.minecraft.client.renderer.FogRenderer.FogMode}
* @param renderDistance The currently set renderDistance represented as a float value.
*/
default void setupFog(Camera camera, FogRenderer.FogMode fogMode, float renderDistance)
{
float constant = 180.0F;
if (camera.getEntity() instanceof LocalPlayer player) {
constant *= player.areEyesInCustomFluid() ? 1.0 : 0.25;
}
RenderSystem.setShaderFogStart(-8F);
RenderSystem.setShaderFogEnd(constant * 0.5F);
net.minecraftforge.client.ForgeHooksClient.onFogRender(fogMode, camera, renderDistance, constant);
}
}
package net.minecraftforge.fluids;
import net.minecraft.Util;
import net.minecraft.client.renderer.BiomeColors;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Rarity;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.WaterWalkerEnchantment;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.function.ToDoubleBiFunction;
import java.util.stream.Stream;
/**
* Minecraft Forge Fluid Implementation
*
* This class is a fluid (liquid or gas) equivalent to "Item." It describes the nature of a fluid
* and contains its general properties.
*
* These properties do not have inherent gameplay mechanics - they are provided so that mods may
* choose to take advantage of them.
*
* Fluid implementations are not required to actively use these properties, nor are objects
* interfacing with fluids required to make use of them, but it is encouraged.
*
* The default values can be used as a reference point for mods adding fluids such as oil or heavy
* water.
*
*/
public class FluidAttributes
{
public static final int BUCKET_VOLUME = 1000;
private String translationKey;
private final ResourceLocation stillTexture;
private final ResourceLocation flowingTexture;
@Nullable
private final ResourceLocation overlayTexture;
@Nullable
private final ResourceLocation viewOverlayTexture;
private final SoundEvent fillSound;
private final SoundEvent emptySound;
/**
* The light level emitted by this fluid.
*
* Default value is 0, as most fluids do not actively emit light.
*/
private final int luminosity;
/**
* Density of the fluid - completely arbitrary; negative density indicates that the fluid is
* lighter than air.
*
* Default value is approximately the real-life density of water in kg/m^3.
*/
private final int density;
/**
* Temperature of the fluid - completely arbitrary; higher temperature indicates that the fluid is
* hotter than air.
*
* Default value is approximately the real-life room temperature of water in degrees Kelvin.
*/
private final int temperature;
/**
* Viscosity ("thickness") of the fluid - completely arbitrary; negative values are not
* permissible.
*
* Default value is approximately the real-life density of water in m/s^2 (x10^-3).
*
* Higher viscosity means that a fluid flows more slowly, like molasses.
* Lower viscosity means that a fluid flows more quickly, like helium.
*
*/
private final int viscosity;
/**
* The rarity of the fluid.
*
* Used primarily in tool tips.
*/
private final Rarity rarity;
/**
* Color used by universal bucket and the ModelFluid baked model.
* Note that this int includes the alpha so converting this to RGB with alpha would be
* float r = ((color >> 16) & 0xFF) / 255f; // red
* float g = ((color >> 8) & 0xFF) / 255f; // green
* float b = ((color >> 0) & 0xFF) / 255f; // blue
* float a = ((color >> 24) & 0xFF) / 255f; // alpha
*/
private final int color;
/**
* The scaled motion {@link ToDoubleBiFunction} of the fluid when "pushing" entities.
*/
private final ToDoubleBiFunction<FluidState, Entity> motionScale;
/**
* The fall distance multiplier {@link ToDoubleBiFunction} while within a fluid.
* For example used if it should increase/dampen fall damage.
*/
private final ToDoubleBiFunction<FluidState, Entity> fallDistanceModifier;
/**
* The {@link Predicate} used to check if a player can swim in the {@link Fluid}.
*/
private final Predicate<FluidState> canSwim;
/**
* The {@link BiPredicate} used to determine if a {@link LivingEntity} can drown in the {@link Fluid}.
*/
private final BiPredicate<FluidState, LivingEntity> canDrown;
/**
* The {@link BiPredicate} used to determine if the {@link Fluid} should extinguish a burning entity.
*/
private final BiPredicate<FluidState, Entity> canExtinguish;
/**
* The {@link Predicate} used to determine if the {@link Fluid} can hydrate a specific {@link BlockState}.
* Used for example with {@link net.minecraft.world.level.block.ConcretePowderBlock#canSolidify(BlockState)} (BlockState)}, {@link net.minecraft.world.level.block.SpongeBlock#tryAbsorbWater(Level, BlockPos)} and {@link net.minecraft.world.level.block.FarmBlock#isNearWater(LevelReader, BlockPos)}.
*/
private final Predicate<BlockState> canHydrate;
/**
* The {@link BiFunction} used to determine the {@link Fluid}s horizontal motion modifier.
* Used inside of {@link net.minecraftforge.common.extensions.IForgeFluid#handleMotion(FluidState, LivingEntity, Vec3, double)} to modify the horizontal movement speed while inside of the {@link Fluid}.
*/
private final BiFunction<FluidState, LivingEntity, Float> horizontalMotionModifier;
/**
* Used to determine motion modifiers based off an {@link Enchantment} and an {@link LivingEntity}.
* Used inside of {@link net.minecraftforge.common.extensions.IForgeFluid#handleMotion(FluidState, LivingEntity, Vec3, double)} to modify the final motion vectors.
*/
private final BiFunction<Enchantment, LivingEntity, Float> enchantmentModifier;
/**
* Used to alters the motion of the entity based off an {@link MobEffect}, an {@link LivingEntity}, an original horizontal motion modifier represented as a {@link Float} and finally a boolean dictating is the motion is horizontal.
* Used inside of {@link net.minecraftforge.common.extensions.IForgeFluid#handleMotion(FluidState, LivingEntity, Vec3, double)} to modify the final motion vectors.
*/
private final IFluidEffectModifier effectModifier;
/**
* Used to decide if a {@link Boat} should have intended interaction behaviour with a fluid.
*/
private final BiPredicate<FluidState, Boat> canBoat;
protected FluidAttributes(Builder builder, Fluid fluid)
{
this.translationKey = builder.translationKey != null ? builder.translationKey : Util.makeDescriptionId("fluid", fluid.getRegistryName());
this.stillTexture = builder.stillTexture;
this.flowingTexture = builder.flowingTexture;
this.overlayTexture = builder.overlayTexture;
this.viewOverlayTexture = builder.viewOverlayTexture;
this.color = builder.color;
this.fillSound = builder.fillSound;
this.emptySound = builder.emptySound;
this.luminosity = builder.luminosity;
this.temperature = builder.temperature;
this.viscosity = builder.viscosity;
this.density = builder.density;
this.rarity = builder.rarity;
this.motionScale = builder.motionScale;
this.fallDistanceModifier = builder.fallDistanceModifier;
this.canSwim = builder.canSwim;
this.canDrown = builder.canDrown;
this.canExtinguish = builder.canExtinguish;
this.canHydrate = builder.canHydrate;
this.horizontalMotionModifier = builder.horizontalMotionModifier;
this.enchantmentModifier = builder.enchantmentModifier;
this.effectModifier = builder.effectModifier;
this.canBoat = builder.canBoat;
}
public ItemStack getBucket(FluidStack stack)
{
return new ItemStack(stack.getFluid().getBucket());
}
public BlockState getBlock(BlockAndTintGetter reader, BlockPos pos, FluidState state)
{
return state.createLegacyBlock();
}
public FluidState getStateForPlacement(BlockAndTintGetter reader, BlockPos pos, FluidStack state)
{
return state.getFluid().defaultFluidState();
}
public final boolean canBePlacedInWorld(BlockAndTintGetter reader, BlockPos pos, FluidState state)
{
return !getBlock(reader, pos, state).isAir();
}
public final boolean canBePlacedInWorld(BlockAndTintGetter reader, BlockPos pos, FluidStack state)
{
return !getBlock(reader, pos, getStateForPlacement(reader, pos, state)).isAir();
}
/**
* Determines if the fluid is lighter than "air" based of the fluids density value.
* If the density value is lower than or equal to 0 where 0 is the "canonical" density of air.
* Then the fluid will be using the upside-down bucket texture/model, as well as the upward-flowing fluid block model.
* @return Returns whether the fluid is lighter than air, making it use the upside down bucket texture and fluid block model.
*/
public final boolean isLighterThanAir()
{
return this.getDensity() <= 0;
}
/**
* Determines if this fluid should vaporize in dimensions where water vaporizes when placed.
* To preserve the intentions of vanilla, fluids that can turn lava into obsidian should vaporize.
* This prevents players from making the nether safe with a single bucket.
* Based on {@link net.minecraft.world.item.BucketItem#emptyContents(Player, Level, BlockPos, BlockHitResult)}
*
* @param fluidStack The fluidStack is trying to be placed.
* @return true if this fluid should vaporize in dimensions where water vaporizes when placed.
*/
public boolean doesVaporize(BlockAndTintGetter reader, BlockPos pos, FluidStack fluidStack)
{
BlockState blockstate = getBlock(reader, pos, getStateForPlacement(reader, pos, fluidStack));
if (blockstate == null)
return false;
return blockstate.getMaterial() == net.minecraft.world.level.material.Material.WATER;
}
/**
* Called instead of placing the fluid block if {@link net.minecraft.world.level.dimension.DimensionType#ultrawarm()} and {@link #doesVaporize(BlockAndTintGetter, BlockPos, FluidStack)} are true.
* Override this to make your explosive liquid blow up instead of the default smoke, etc.
* Based on {@link net.minecraft.world.item.BucketItem#emptyContents(Player, Level, BlockPos, BlockHitResult)}
*
* @param player Player who tried to place the fluid. May be null for blocks like dispensers.
* @param worldIn World to vaporize the fluid in.
* @param pos The position in the world the fluid block was going to be placed.
* @param fluidStack The fluidStack that was going to be placed.
*/
public void vaporize(@Nullable Player player, Level worldIn, BlockPos pos, FluidStack fluidStack)
{
worldIn.playSound(player, pos, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (worldIn.random.nextFloat() - worldIn.random.nextFloat()) * 0.8F);
for (int l = 0; l < 8; ++l)
{
worldIn.addAlwaysVisibleParticle(ParticleTypes.LARGE_SMOKE, (double) pos.getX() + Math.random(), (double) pos.getY() + Math.random(), (double) pos.getZ() + Math.random(), 0.0D, 0.0D, 0.0D);
}
}
/**
* Returns the localized name of the provided {@code FluidStack}.
* @param stack the provided {@code FluidStack} to get the display name of.
* @return Returns the display name of the provided {@code FluidStack} as a {@code Component}.
*/
public Component getDisplayName(FluidStack stack)
{
return new TranslatableComponent(getTranslationKey());
}
/**
* A FluidStack sensitive version of getTranslationKey
* @param stack the provided {@code FluidStack} to get the display name of.
* @return Returns the translation key of the provided {@code FluidStack} as a string.
*/
public String getTranslationKey(FluidStack stack)
{
return this.getTranslationKey();
}
/**
* @return Returns the translation key of this fluid.
*/
public String getTranslationKey()
{
return this.translationKey;
}
/**
* Luminosity is currently only used as part of {@link net.minecraftforge.client.model.DynamicBucketModel#bake(IModelConfiguration, ModelBakery, Function<Material, TextureAtlasSprite>, ModelState, ItemOverrides, ResourceLocation)} to determine if the fluid should render fullbright in the bucket texture or not.
* The luminosity check for fullbright on the DynamicBucketModel is calculated as 'luminosity > 0' and requires the "applyFluidLuminosity" model attribute to be present and enabled in the fluid model file.
* @return Returns the luminosity value for the fluid.
*/
/* Default Accessors */
public final int getLuminosity()
{
return this.luminosity;
}
public final int getDensity()
{
return this.density;
}
public final int getTemperature()
{
return this.temperature;
}
public final int getViscosity()
{
return this.viscosity;
}
public Rarity getRarity()
{
return rarity;
}
public int getColor()
{
return color;
}
public double getMotionScale(FluidState state, Entity entity)
{
return motionScale.applyAsDouble(state, entity);
}
public double getFallDistanceModifier(FluidState state, Entity entity)
{
return fallDistanceModifier.applyAsDouble(state, entity);
}
public boolean canSwim(FluidState state)
{
return canSwim.test(state);
}
public boolean canDrown(FluidState state, LivingEntity entity)
{
return canDrown.test(state, entity);
}
public boolean canExtinguish(FluidState state, Entity entity)
{
return canExtinguish.test(state, entity);
}
public boolean canHydrate(BlockState state)
{
return canHydrate.test(state);
}
public float getHorizontalMotionModifier(FluidState state, LivingEntity entity)
{
return this.horizontalMotionModifier.apply(state, entity);
}
public float getEnchantmentMotionModifier(Enchantment enchantment, LivingEntity entity)
{
return this.enchantmentModifier.apply(enchantment, entity);
}
public boolean canBoat(FluidState state, Boat boat)
{
return this.canBoat.test(state, boat);
}
public void modifyMotionByEffect(MobEffect effect, LivingEntity entity, Float originalMovement, Boolean isHorizontal)
{
this.effectModifier.modify(effect, entity, originalMovement, isHorizontal);
}
public ResourceLocation getStillTexture()
{
return stillTexture;
}
public ResourceLocation getFlowingTexture()
{
return flowingTexture;
}
@Nullable
public ResourceLocation getOverlayTexture()
{
return overlayTexture;
}
@Nullable
public ResourceLocation getViewOverlayTexture()
{
return viewOverlayTexture;
}
public SoundEvent getFillSound()
{
return fillSound;
}
public SoundEvent getEmptySound()
{
return emptySound;
}
/* Stack-based Accessors */
public int getLuminosity(FluidStack stack){ return getLuminosity(); }
public int getDensity(FluidStack stack){ return getDensity(); }
public int getTemperature(FluidStack stack){ return getTemperature(); }
public int getViscosity(FluidStack stack){ return getViscosity(); }
public Rarity getRarity(FluidStack stack){ return getRarity(); }
public int getColor(FluidStack stack){ return getColor(); }
public ResourceLocation getStillTexture(FluidStack stack) { return getStillTexture(); }
public ResourceLocation getFlowingTexture(FluidStack stack) { return getFlowingTexture(); }
public SoundEvent getFillSound(FluidStack stack) { return getFillSound(); }
public SoundEvent getEmptySound(FluidStack stack) { return getEmptySound(); }
/* World-based Accessors */
public int getLuminosity(BlockAndTintGetter world, BlockPos pos){ return getLuminosity(); }
public int getDensity(BlockAndTintGetter world, BlockPos pos){ return getDensity(); }
public int getTemperature(BlockAndTintGetter world, BlockPos pos){ return getTemperature(); }
public int getViscosity(BlockAndTintGetter world, BlockPos pos){ return getViscosity(); }
public Rarity getRarity(BlockAndTintGetter world, BlockPos pos){ return getRarity(); }
public int getColor(BlockAndTintGetter world, BlockPos pos){ return getColor(); }
public ResourceLocation getStillTexture(BlockAndTintGetter world, BlockPos pos) { return getStillTexture(); }
public ResourceLocation getFlowingTexture(BlockAndTintGetter world, BlockPos pos) { return getFlowingTexture(); }
public SoundEvent getFillSound(BlockAndTintGetter world, BlockPos pos) { return getFillSound(); }
public SoundEvent getEmptySound(BlockAndTintGetter world, BlockPos pos) { return getEmptySound(); }
public boolean canSwim(BlockAndTintGetter world, BlockPos pos) { return canSwim(world.getFluidState(pos)); }
public boolean canHydrate(BlockAndTintGetter world, BlockPos pos) { return canHydrate(world.getBlockState(pos)); }
public static Builder builder(ResourceLocation stillTexture, ResourceLocation flowingTexture) {
return new Builder(stillTexture, flowingTexture, FluidAttributes::new);
}
public Stream<ResourceLocation> getTextures()
{
if (overlayTexture != null)
return Stream.of(stillTexture, flowingTexture, overlayTexture);
return Stream.of(stillTexture, flowingTexture);
}
public static class Builder
{
private final ResourceLocation stillTexture;
private final ResourceLocation flowingTexture;
private ResourceLocation overlayTexture;
private ResourceLocation viewOverlayTexture;
private int color = 0xFFFFFFFF;
private String translationKey;
private SoundEvent fillSound;
private SoundEvent emptySound;
private int luminosity = 0;
private int density = 1000;
private int temperature = 300;
private int viscosity = 1000;
private Rarity rarity = Rarity.COMMON;
private ToDoubleBiFunction<FluidState, Entity> motionScale = (state, entity) -> 0.014D;
private ToDoubleBiFunction<FluidState, Entity> fallDistanceModifier = (state, entity) -> 0.0D;
private Predicate<FluidState> canSwim = state -> false;
private BiPredicate<FluidState, LivingEntity> canDrown = (state, entity) -> true;
private BiPredicate<FluidState, Entity> canExtinguish = (state, entity) -> false;
private Predicate<BlockState> canHydrate = state -> false;
private BiFunction<FluidState, LivingEntity, Float> horizontalMotionModifier = (state, entity) -> entity.isSprinting() ? 0.9F : 0.8F;
private BiFunction<Enchantment, LivingEntity, Float> enchantmentModifier = (enchantment, entity) -> enchantment instanceof WaterWalkerEnchantment ? EnchantmentHelper.getDepthStrider(entity) : 0F;
private IFluidEffectModifier effectModifier = (effect, entity, originalModifier, isHorizontal) -> { if (isHorizontal && effect.equals(MobEffects.DOLPHINS_GRACE) && entity.hasEffect(effect)) { originalModifier = 0.97f; } };
private BiPredicate<FluidState, Boat> canBoat = (state, boat) -> false;
private BiFunction<Builder,Fluid,FluidAttributes> factory;
protected Builder(ResourceLocation stillTexture, ResourceLocation flowingTexture, BiFunction<Builder,Fluid,FluidAttributes> factory) {
this.factory = factory;
this.stillTexture = stillTexture;
this.flowingTexture = flowingTexture;
}
public final Builder translationKey(String translationKey)
{
this.translationKey = translationKey;
return this;
}
public final Builder color(int color)
{
this.color = color;
return this;
}
public final Builder overlay(ResourceLocation texture)
{
overlayTexture = texture;
return this;
}
public final Builder viewOverlay(ResourceLocation texture)
{
viewOverlayTexture = new ResourceLocation(texture.getNamespace(), "textures/" + texture.getPath() + ".png");
return this;
}
public final Builder luminosity(int luminosity)
{
this.luminosity = luminosity;
return this;
}
public final Builder density(int density)
{
this.density = density;
return this;
}
public final Builder temperature(int temperature)
{
this.temperature = temperature;
return this;
}
public final Builder viscosity(int viscosity)
{
this.viscosity = viscosity;
return this;
}
public final Builder rarity(Rarity rarity)
{
this.rarity = rarity;
return this;
}
public final Builder sound(SoundEvent sound)
{
this.fillSound = this.emptySound = sound;
return this;
}
public final Builder sound(SoundEvent fillSound, SoundEvent emptySound)
{
this.fillSound = fillSound;
this.emptySound = emptySound;
return this;
}
public final Builder motionScale(ToDoubleBiFunction<FluidState, Entity> motionScale)
{
this.motionScale = motionScale;
return this;
}
public final Builder fallDistanceModifier(ToDoubleBiFunction<FluidState, Entity> fallDistanceModifier)
{
this.fallDistanceModifier = fallDistanceModifier;
return this;
}
public final Builder canSwim(Predicate<FluidState> canSwim)
{
this.canSwim = canSwim;
return this;
}
public final Builder canDrown(BiPredicate<FluidState, LivingEntity> canDrown)
{
this.canDrown = canDrown;
return this;
}
public final Builder canExtinguish(BiPredicate<FluidState, Entity> canExtinguish)
{
this.canExtinguish = canExtinguish;
return this;
}
public final Builder canHydrate(Predicate<BlockState> canHydrate)
{
this.canHydrate = canHydrate;
return this;
}
public final Builder horizontalMotionModifier(BiFunction<FluidState, LivingEntity, Float> horizontalMotionModifier)
{
this.horizontalMotionModifier = horizontalMotionModifier;
return this;
}
public final Builder enchantmentModifier(BiFunction<Enchantment, LivingEntity, Float> enchantmentModifier)
{
this.enchantmentModifier = enchantmentModifier;
return this;
}
public final Builder effectModifier(IFluidEffectModifier effectModifier)
{
this.effectModifier = effectModifier;
return this;
}
public final Builder canBoat(BiPredicate<FluidState, Boat> canBoat)
{
this.canBoat = canBoat;
return this;
}
public FluidAttributes build(Fluid fluid)
{
return factory.apply(this, fluid);
}
}
public static class Water extends FluidAttributes
{
protected Water(Builder builder, Fluid fluid)
{
super(builder, fluid);
}
@Override
public int getColor(BlockAndTintGetter world, BlockPos pos)
{
return BiomeColors.getAverageWaterColor(world, pos) | 0xFF000000;
}
public static Builder builder(ResourceLocation stillTexture, ResourceLocation flowingTexture) {
return new Builder(stillTexture, flowingTexture, Water::new).canExtinguish((state, entity) -> true);
}
}
public interface IFluidEffectModifier {
void modify(MobEffect effect, LivingEntity entity, float motion, boolean isHorizontal);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment