Skip to content

Instantly share code, notes, and snippets.

@Lanse505
Created August 1, 2021 21:04
Show Gist options
  • Save Lanse505/b6aeae94957c9e38497b7449d7a60c3e to your computer and use it in GitHub Desktop.
Save Lanse505/b6aeae94957c9e38497b7449d7a60c3e to your computer and use it in GitHub Desktop.
package net.minecraftforge.common.extensions;
import java.util.Set;
import javax.annotation.Nullable;
import com.mojang.math.Vector3d;
import net.minecraft.util.Mth;
import net.minecraft.world.effect.MobEffects;
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.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.tags.SetTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.phys.AABB;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.ForgeMod;
import net.minecraftforge.fluids.FluidAttributes;
public interface IForgeFluid
{
/**
* Called when the entity is inside this block, may be used to determined if the entity can breathing,
* display material overlays, or if the entity can swim inside a block.
*
* @param world that is being tested.
* @param pos position thats being tested.
* @param entity that is being tested.
* @param yToTest, primarily for testingHead, which sends the the eye level of the entity, other wise it sends a y that can be tested vs liquid height.
* @param tag Fluid category
* @param testingHead when true, its testing the entities head for vision, breathing ect... otherwise its testing the body, for swimming and movement adjustment.
*/
default boolean isEntityInside(FluidState state, LevelReader world, BlockPos pos, Entity entity, double yToTest, SetTag<Fluid> tag, boolean testingHead)
{
return state.is(tag) && yToTest < (double)(pos.getY() + state.getHeight(world, pos) + 0.11111111F);
}
/**
* Called when boats or fishing hooks are inside the block to check if they are inside
* the material requested.
*
* @param world world that is being tested.
* @param pos block thats being tested.
* @param boundingBox box to test, generally the bounds of an entity that are besting tested.
* @param materialIn to check for.
* @return null for default behavior, true if the box is within the material, false if it was not.
*/
@Nullable
default Boolean isAABBInsideMaterial(FluidState state, LevelReader world, BlockPos pos, AABB boundingBox, Material materialIn)
{
return null;
}
/**
* Called when entities are moving to check if they are inside a liquid
*
* @param world world that is being tested.
* @param pos block thats being tested.
* @param boundingBox box to test, generally the bounds of an entity that are besting tested.
* @return null for default behavior, true if the box is within the material, false if it was not.
*/
@Nullable
default Boolean isAABBInsideLiquid(FluidState state, LevelReader world, BlockPos pos, AABB boundingBox)
{
return null;
}
/**
* 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.
*/
@SuppressWarnings("deprecation")
default float getExplosionResistance(FluidState state, BlockGetter world, 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, including localized name.
*/
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 world The current world'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 world, 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 world The current world'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 world, 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); //0_FLUIDS: Check potential hardcoding instance
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 Vector3d}
* @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));
}
/**
* 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 this fluid can act as an "hydration source" for a specific {@link BlockState}.
* For example, this can hydrate {@link net.minecraft.world.level.block.ConcretePowderBlock}s, hydrate {@link net.minecraft.world.level.block.FarmBlock}s, or be absorbed by {@link net.minecraft.world.level.block.SpongeBlock}s.
*
* @param fluidState The current {@link FluidState}
* @param blockState The provided {@link BlockState} to be hydrated
* @return Returns whether the {@link FluidState} can hydrate the provided {@link BlockState}
*/
default boolean canHydrate(FluidState fluidState, BlockState blockState)
{
return fluidState.getType().getAttributes().canHydrate(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 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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment