base mod created

This commit is contained in:
Mohammad-Ali Minaie
2018-10-08 09:07:47 -04:00
parent 0a7700c356
commit b86dedad2f
7848 changed files with 584664 additions and 1 deletions

View File

@@ -0,0 +1,843 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import java.util.Map;
import java.util.Random;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import net.minecraft.block.Block;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.BlockStairs;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.entity.Entity;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.property.IExtendedBlockState;
import net.minecraftforge.common.property.IUnlistedProperty;
import net.minecraftforge.common.property.Properties;
import net.minecraftforge.common.property.PropertyFloat;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/**
* This is a base implementation for Fluid blocks.
*
* It is highly recommended that you extend this class or one of the Forge-provided child classes.
*
*/
public abstract class BlockFluidBase extends Block implements IFluidBlock
{
protected final static Map<Block, Boolean> defaultDisplacements = Maps.newHashMap();
static
{
defaultDisplacements.put(Blocks.OAK_DOOR, false);
defaultDisplacements.put(Blocks.SPRUCE_DOOR, false);
defaultDisplacements.put(Blocks.BIRCH_DOOR, false);
defaultDisplacements.put(Blocks.JUNGLE_DOOR, false);
defaultDisplacements.put(Blocks.ACACIA_DOOR, false);
defaultDisplacements.put(Blocks.DARK_OAK_DOOR, false);
defaultDisplacements.put(Blocks.TRAPDOOR, false);
defaultDisplacements.put(Blocks.IRON_TRAPDOOR, false);
defaultDisplacements.put(Blocks.OAK_FENCE, false);
defaultDisplacements.put(Blocks.SPRUCE_FENCE, false);
defaultDisplacements.put(Blocks.BIRCH_FENCE, false);
defaultDisplacements.put(Blocks.JUNGLE_FENCE, false);
defaultDisplacements.put(Blocks.DARK_OAK_FENCE, false);
defaultDisplacements.put(Blocks.ACACIA_FENCE, false);
defaultDisplacements.put(Blocks.NETHER_BRICK_FENCE, false);
defaultDisplacements.put(Blocks.OAK_FENCE_GATE, false);
defaultDisplacements.put(Blocks.SPRUCE_FENCE_GATE, false);
defaultDisplacements.put(Blocks.BIRCH_FENCE_GATE, false);
defaultDisplacements.put(Blocks.JUNGLE_FENCE_GATE, false);
defaultDisplacements.put(Blocks.DARK_OAK_FENCE_GATE, false);
defaultDisplacements.put(Blocks.ACACIA_FENCE_GATE, false);
defaultDisplacements.put(Blocks.WOODEN_PRESSURE_PLATE, false);
defaultDisplacements.put(Blocks.STONE_PRESSURE_PLATE, false);
defaultDisplacements.put(Blocks.LIGHT_WEIGHTED_PRESSURE_PLATE, false);
defaultDisplacements.put(Blocks.HEAVY_WEIGHTED_PRESSURE_PLATE, false);
defaultDisplacements.put(Blocks.LADDER, false);
defaultDisplacements.put(Blocks.IRON_BARS, false);
defaultDisplacements.put(Blocks.GLASS_PANE, false);
defaultDisplacements.put(Blocks.STAINED_GLASS_PANE, false);
defaultDisplacements.put(Blocks.PORTAL, false);
defaultDisplacements.put(Blocks.END_PORTAL, false);
defaultDisplacements.put(Blocks.COBBLESTONE_WALL, false);
defaultDisplacements.put(Blocks.BARRIER, false);
defaultDisplacements.put(Blocks.STANDING_BANNER, false);
defaultDisplacements.put(Blocks.WALL_BANNER, false);
defaultDisplacements.put(Blocks.CAKE, false);
defaultDisplacements.put(Blocks.IRON_DOOR, false);
defaultDisplacements.put(Blocks.STANDING_SIGN, false);
defaultDisplacements.put(Blocks.WALL_SIGN, false);
defaultDisplacements.put(Blocks.REEDS, false);
}
protected Map<Block, Boolean> displacements = Maps.newHashMap();
private static final class UnlistedPropertyBool extends Properties.PropertyAdapter<Boolean>
{
public UnlistedPropertyBool(String name)
{
super(PropertyBool.create(name));
}
}
public static final PropertyInteger LEVEL = PropertyInteger.create("level", 0, 15);
public static final PropertyFloat[] LEVEL_CORNERS = new PropertyFloat[4];
public static final PropertyFloat FLOW_DIRECTION = new PropertyFloat("flow_direction", -1000f, 1000f);
public static final UnlistedPropertyBool[] SIDE_OVERLAYS = new UnlistedPropertyBool[4];
public static final ImmutableList<IUnlistedProperty<?>> FLUID_RENDER_PROPS;
static
{
ImmutableList.Builder<IUnlistedProperty<?>> builder = ImmutableList.builder();
builder.add(FLOW_DIRECTION);
for(int i = 0; i < 4; i++)
{
LEVEL_CORNERS[i] = new PropertyFloat("level_corner_" + i, 0f, 1f);
builder.add(LEVEL_CORNERS[i]);
SIDE_OVERLAYS[i] = new UnlistedPropertyBool("side_overlay_" + i);
builder.add(SIDE_OVERLAYS[i]);
}
FLUID_RENDER_PROPS = builder.build();
}
protected int quantaPerBlock = 8;
protected float quantaPerBlockFloat = 8F;
protected float quantaFraction = 8f / 9f;
protected int density = 1;
protected int densityDir = -1;
protected int temperature = 295;
protected int tickRate = 20;
protected BlockRenderLayer renderLayer = BlockRenderLayer.TRANSLUCENT;
protected int maxScaledLight = 0;
protected final String fluidName;
/**
* This is the fluid used in the constructor. Use this reference to configure things
* like icons for your block. It might not be active in the registry, so do
* NOT expose it.
*/
protected final Fluid definedFluid;
public BlockFluidBase(Fluid fluid, Material material)
{
super(material);
this.setTickRandomly(true);
this.disableStats();
this.fluidName = fluid.getName();
this.density = fluid.density;
this.temperature = fluid.temperature;
this.maxScaledLight = fluid.luminosity;
this.tickRate = fluid.viscosity / 200;
this.densityDir = fluid.density > 0 ? -1 : 1;
fluid.setBlock(this);
this.definedFluid = fluid;
displacements.putAll(defaultDisplacements);
this.setDefaultState(blockState.getBaseState().withProperty(LEVEL, getMaxRenderHeightMeta()));
}
@Override
@Nonnull
protected BlockStateContainer createBlockState()
{
return new BlockStateContainer.Builder(this)
.add(LEVEL)
.add(FLUID_RENDER_PROPS.toArray(new IUnlistedProperty<?>[0]))
.build();
}
/**
* Convert the BlockState into the correct metadata value
*/
@Override
public int getMetaFromState(@Nonnull IBlockState state)
{
return state.getValue(LEVEL);
}
/**
* Convert the given metadata into a BlockState for this Block
*/
@Override
@Deprecated
@Nonnull
public IBlockState getStateFromMeta(int meta)
{
return this.getDefaultState().withProperty(LEVEL, meta);
}
public BlockFluidBase setQuantaPerBlock(int quantaPerBlock)
{
if (quantaPerBlock > 16 || quantaPerBlock < 1) quantaPerBlock = 8;
this.quantaPerBlock = quantaPerBlock;
this.quantaPerBlockFloat = quantaPerBlock;
this.quantaFraction = quantaPerBlock / (quantaPerBlock + 1f);
return this;
}
public BlockFluidBase setDensity(int density)
{
if (density == 0) density = 1;
this.density = density;
this.densityDir = density > 0 ? -1 : 1;
return this;
}
public BlockFluidBase setTemperature(int temperature)
{
this.temperature = temperature;
return this;
}
public BlockFluidBase setTickRate(int tickRate)
{
if (tickRate <= 0) tickRate = 20;
this.tickRate = tickRate;
return this;
}
public BlockFluidBase setRenderLayer(BlockRenderLayer renderLayer)
{
this.renderLayer = renderLayer;
return this;
}
public BlockFluidBase setMaxScaledLight(int maxScaledLight)
{
this.maxScaledLight = maxScaledLight;
return this;
}
public final int getDensity()
{
return density;
}
public final int getTemperature()
{
return temperature;
}
/**
* Returns true if the block at (pos) is displaceable. Does not displace the block.
*/
public boolean canDisplace(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
Block block = state.getBlock();
if (block.isAir(state, world, pos))
{
return true;
}
if (block == this)
{
return false;
}
if (displacements.containsKey(block))
{
return displacements.get(block);
}
Material material = state.getMaterial();
if (material.blocksMovement() || material == Material.PORTAL || material == Material.STRUCTURE_VOID)
{
return false;
}
int density = getDensity(world, pos);
if (density == Integer.MAX_VALUE)
{
return true;
}
return this.density > density;
}
/**
* Attempt to displace the block at (pos), return true if it was displaced.
*/
public boolean displaceIfPossible(World world, BlockPos pos)
{
boolean canDisplace = canDisplace(world, pos);
if (canDisplace)
{
IBlockState state = world.getBlockState(pos);
Block block = state.getBlock();
if (!block.isAir(state, world, pos) && !isFluid(state))
{
// Forge: Vanilla has a 'bug' where snowballs don't drop like every other block. So special case because ewww...
if (block != Blocks.SNOW_LAYER) block.dropBlockAsItem(world, pos, state, 0);
}
}
return canDisplace;
}
public abstract int getQuantaValue(IBlockAccess world, BlockPos pos);
@Override
public abstract boolean canCollideCheck(@Nonnull IBlockState state, boolean fullHit);
public abstract int getMaxRenderHeightMeta();
/* BLOCK FUNCTIONS */
/**
* Called after the block is set in the Chunk data, but before the Tile Entity is set
*/
@Override
public void onBlockAdded(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state)
{
world.scheduleUpdate(pos, this, tickRate);
}
/**
* Called when a neighboring block was changed and marks that this state should perform any checks during a neighbor
* change. Cases may include when redstone power is updated, cactus blocks popping off due to a neighboring solid
* block, etc.
*/
@Override
public void neighborChanged(@Nonnull IBlockState state, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Block neighborBlock, @Nonnull BlockPos neighbourPos)
{
world.scheduleUpdate(pos, this, tickRate);
}
// Used to prevent updates on chunk generation
@Override
public boolean requiresUpdates()
{
return false;
}
/**
* Determines if an entity can path through this block
*/
@Override
public boolean isPassable(@Nonnull IBlockAccess world, @Nonnull BlockPos pos)
{
return true;
}
/**
* Get the Item that this Block should drop when harvested.
*/
@Override
@Nonnull
public Item getItemDropped(@Nonnull IBlockState state, @Nonnull Random rand, int fortune)
{
return Items.AIR;
}
/**
* Returns the quantity of items to drop on block destruction.
*/
@Override
public int quantityDropped(@Nonnull Random par1Random)
{
return 0;
}
/**
* How many world ticks before ticking
*/
@Override
public int tickRate(@Nonnull World world)
{
return tickRate;
}
@Override
@Nonnull
public Vec3d modifyAcceleration(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull Entity entity, @Nonnull Vec3d vec)
{
return densityDir > 0 ? vec : vec.add(getFlowVector(world, pos));
}
@Override
public int getLightValue(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos)
{
if (maxScaledLight == 0)
{
return super.getLightValue(state, world, pos);
}
return (int) (getQuantaPercentage(world, pos) * maxScaledLight);
}
/**
* Used to determine ambient occlusion and culling when rebuilding chunks for render
*/
@Override
public boolean isOpaqueCube(@Nonnull IBlockState state)
{
return false;
}
@Override
public boolean isFullCube(@Nonnull IBlockState state)
{
return false;
}
@Override
public int getPackedLightmapCoords(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos)
{
int lightThis = world.getCombinedLight(pos, 0);
int lightUp = world.getCombinedLight(pos.up(), 0);
int lightThisBase = lightThis & 255;
int lightUpBase = lightUp & 255;
int lightThisExt = lightThis >> 16 & 255;
int lightUpExt = lightUp >> 16 & 255;
return (lightThisBase > lightUpBase ? lightThisBase : lightUpBase) |
((lightThisExt > lightUpExt ? lightThisExt : lightUpExt) << 16);
}
@Override
@SideOnly(Side.CLIENT)
@Nonnull
public BlockRenderLayer getBlockLayer()
{
return this.renderLayer;
}
/**
* Get the geometry of the queried face at the given position and state. This is used to decide whether things like
* buttons are allowed to be placed on the face, or how glass panes connect to the face, among other things.
* <p>
* Common values are {@code SOLID}, which is the default, and {@code UNDEFINED}, which represents something that
* does not fit the other descriptions and will generally cause other things not to connect to the face.
*
* @return an approximation of the form of the given face
*/
@Override
@Nonnull
public BlockFaceShape getBlockFaceShape(@Nonnull IBlockAccess worldIn, @Nonnull IBlockState state, @Nonnull BlockPos pos, @Nonnull EnumFacing face)
{
return BlockFaceShape.UNDEFINED;
}
@Override
public boolean shouldSideBeRendered(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side)
{
IBlockState neighbor = world.getBlockState(pos.offset(side));
if (neighbor.getMaterial() == state.getMaterial())
{
return false;
}
if (side == (densityDir < 0 ? EnumFacing.UP : EnumFacing.DOWN))
{
return true;
}
return super.shouldSideBeRendered(state, world, pos, side);
}
private static boolean isFluid(@Nonnull IBlockState blockstate)
{
return blockstate.getMaterial().isLiquid() || blockstate.getBlock() instanceof IFluidBlock;
}
@Override
@Nonnull
public IBlockState getExtendedState(@Nonnull IBlockState oldState, @Nonnull IBlockAccess world, @Nonnull BlockPos pos)
{
IExtendedBlockState state = (IExtendedBlockState)oldState;
state = state.withProperty(FLOW_DIRECTION, (float)getFlowDirection(world, pos));
IBlockState[][] upBlockState = new IBlockState[3][3];
float[][] height = new float[3][3];
float[][] corner = new float[2][2];
upBlockState[1][1] = world.getBlockState(pos.down(densityDir));
height[1][1] = getFluidHeightForRender(world, pos, upBlockState[1][1]);
if (height[1][1] == 1)
{
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
corner[i][j] = 1;
}
}
}
else
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i != 1 || j != 1)
{
upBlockState[i][j] = world.getBlockState(pos.add(i - 1, 0, j - 1).down(densityDir));
height[i][j] = getFluidHeightForRender(world, pos.add(i - 1, 0, j - 1), upBlockState[i][j]);
}
}
}
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 2; j++)
{
corner[i][j] = getFluidHeightAverage(height[i][j], height[i][j + 1], height[i + 1][j], height[i + 1][j + 1]);
}
}
//check for downflow above corners
boolean n = isFluid(upBlockState[0][1]);
boolean s = isFluid(upBlockState[2][1]);
boolean w = isFluid(upBlockState[1][0]);
boolean e = isFluid(upBlockState[1][2]);
boolean nw = isFluid(upBlockState[0][0]);
boolean ne = isFluid(upBlockState[0][2]);
boolean sw = isFluid(upBlockState[2][0]);
boolean se = isFluid(upBlockState[2][2]);
if (nw || n || w)
{
corner[0][0] = 1;
}
if (ne || n || e)
{
corner[0][1] = 1;
}
if (sw || s || w)
{
corner[1][0] = 1;
}
if (se || s || e)
{
corner[1][1] = 1;
}
}
for (int i = 0; i < 4; i++)
{
EnumFacing side = EnumFacing.getHorizontal(i);
BlockPos offset = pos.offset(side);
boolean useOverlay = world.getBlockState(offset).getBlockFaceShape(world, offset, side.getOpposite()) == BlockFaceShape.SOLID;
state = state.withProperty(SIDE_OVERLAYS[i], useOverlay);
}
state = state.withProperty(LEVEL_CORNERS[0], corner[0][0]);
state = state.withProperty(LEVEL_CORNERS[1], corner[0][1]);
state = state.withProperty(LEVEL_CORNERS[2], corner[1][1]);
state = state.withProperty(LEVEL_CORNERS[3], corner[1][0]);
return state;
}
/* FLUID FUNCTIONS */
public static int getDensity(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
Block block = state.getBlock();
if (block instanceof BlockFluidBase)
{
return ((BlockFluidBase)block).getDensity();
}
Fluid fluid = getFluid(state);
if (fluid != null)
{
return fluid.getDensity();
}
return Integer.MAX_VALUE;
}
public static int getTemperature(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
Block block = state.getBlock();
if (block instanceof BlockFluidBase)
{
return ((BlockFluidBase)block).getTemperature();
}
Fluid fluid = getFluid(state);
if (fluid != null)
{
return fluid.getTemperature();
}
return Integer.MAX_VALUE;
}
@Nullable
private static Fluid getFluid(IBlockState state)
{
Block block = state.getBlock();
if (block instanceof IFluidBlock)
{
return ((IFluidBlock)block).getFluid();
}
if (block instanceof BlockLiquid)
{
if (state.getMaterial() == Material.WATER)
{
return FluidRegistry.WATER;
}
if (state.getMaterial() == Material.LAVA)
{
return FluidRegistry.LAVA;
}
}
return null;
}
public static double getFlowDirection(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
if (!state.getMaterial().isLiquid())
{
return -1000.0;
}
Vec3d vec = ((BlockFluidBase)state.getBlock()).getFlowVector(world, pos);
return vec.x == 0.0D && vec.z == 0.0D ? -1000.0D : MathHelper.atan2(vec.z, vec.x) - Math.PI / 2D;
}
public final int getQuantaValueBelow(IBlockAccess world, BlockPos pos, int belowThis)
{
int quantaRemaining = getQuantaValue(world, pos);
if (quantaRemaining >= belowThis)
{
return -1;
}
return quantaRemaining;
}
public final int getQuantaValueAbove(IBlockAccess world, BlockPos pos, int aboveThis)
{
int quantaRemaining = getQuantaValue(world, pos);
if (quantaRemaining <= aboveThis)
{
return -1;
}
return quantaRemaining;
}
public final float getQuantaPercentage(IBlockAccess world, BlockPos pos)
{
int quantaRemaining = getQuantaValue(world, pos);
return quantaRemaining / quantaPerBlockFloat;
}
public float getFluidHeightAverage(float... flow)
{
float total = 0;
int count = 0;
for (int i = 0; i < flow.length; i++)
{
if (flow[i] >= quantaFraction)
{
total += flow[i] * 10;
count += 10;
}
if (flow[i] >= 0)
{
total += flow[i];
count++;
}
}
return total / count;
}
public float getFluidHeightForRender(IBlockAccess world, BlockPos pos, @Nonnull IBlockState up)
{
IBlockState here = world.getBlockState(pos);
if (here.getBlock() == this)
{
if (isFluid(up))
{
return 1;
}
if (getMetaFromState(here) == getMaxRenderHeightMeta())
{
return quantaFraction;
}
}
if (here.getBlock() instanceof BlockLiquid)
{
return Math.min(1 - BlockLiquid.getLiquidHeightPercent(here.getValue(BlockLiquid.LEVEL)), quantaFraction);
}
return !here.getMaterial().isSolid() && up.getBlock() == this ? 1 : this.getQuantaPercentage(world, pos) * quantaFraction;
}
public Vec3d getFlowVector(IBlockAccess world, BlockPos pos)
{
Vec3d vec = new Vec3d(0.0D, 0.0D, 0.0D);
int decay = getFlowDecay(world, pos);
for (EnumFacing side : EnumFacing.Plane.HORIZONTAL)
{
BlockPos offset = pos.offset(side);
int otherDecay = getFlowDecay(world, offset);
if (otherDecay >= quantaPerBlock)
{
if (!world.getBlockState(offset).getMaterial().blocksMovement())
{
otherDecay = getFlowDecay(world, offset.up(densityDir));
if (otherDecay < quantaPerBlock)
{
int power = otherDecay - (decay - quantaPerBlock);
vec = vec.addVector(side.getFrontOffsetX() * power, 0, side.getFrontOffsetZ() * power);
}
}
}
else
{
int power = otherDecay - decay;
vec = vec.addVector(side.getFrontOffsetX() * power, 0, side.getFrontOffsetZ() * power);
}
}
if (hasVerticalFlow(world, pos))
{
for (EnumFacing side : EnumFacing.Plane.HORIZONTAL)
{
BlockPos offset = pos.offset(side);
if (causesDownwardCurrent(world, offset, side) || causesDownwardCurrent(world, offset.down(densityDir), side))
{
vec = vec.normalize().addVector(0.0, 6.0 * densityDir, 0.0);
break;
}
}
}
return vec.normalize();
}
private int getFlowDecay(IBlockAccess world, BlockPos pos)
{
int quantaValue = getQuantaValue(world, pos);
return quantaValue > 0 && hasVerticalFlow(world, pos) ? 0 : quantaPerBlock - quantaValue;
}
private boolean hasVerticalFlow(IBlockAccess world, BlockPos pos)
{
return world.getBlockState(pos.down(densityDir)).getBlock() == this;
}
protected boolean causesDownwardCurrent(IBlockAccess world, BlockPos pos, EnumFacing face)
{
IBlockState state = world.getBlockState(pos);
Block block = state.getBlock();
if (block == this) return false;
if (face == (densityDir < 0 ? EnumFacing.UP : EnumFacing.DOWN)) return true;
if (state.getMaterial() == Material.ICE) return false;
boolean flag = isExceptBlockForAttachWithPiston(block) || block instanceof BlockStairs;
return !flag && state.getBlockFaceShape(world, pos, face) == BlockFaceShape.SOLID;
}
/* IFluidBlock */
@Override
public Fluid getFluid()
{
return FluidRegistry.getFluid(fluidName);
}
@Override
public float getFilledPercentage(World world, BlockPos pos)
{
return getFilledPercentage((IBlockAccess) world, pos);
}
public float getFilledPercentage(IBlockAccess world, BlockPos pos)
{
int quantaRemaining = getQuantaValue(world, pos);
float remaining = (quantaRemaining + 1f) / (quantaPerBlockFloat + 1f);
return remaining * (density > 0 ? 1 : -1);
}
@Override
public AxisAlignedBB getCollisionBoundingBox(@Nonnull IBlockState blockState, @Nonnull IBlockAccess worldIn, @Nonnull BlockPos pos)
{
return NULL_AABB;
}
@Override
@SideOnly (Side.CLIENT)
public Vec3d getFogColor(World world, BlockPos pos, IBlockState state, Entity entity, Vec3d originalColor, float partialTicks)
{
if (!isWithinFluid(world, pos, ActiveRenderInfo.projectViewFromEntity(entity, partialTicks)))
{
BlockPos otherPos = pos.down(densityDir);
IBlockState otherState = world.getBlockState(otherPos);
return otherState.getBlock().getFogColor(world, otherPos, otherState, entity, originalColor, partialTicks);
}
if (getFluid() != null)
{
int color = getFluid().getColor();
float red = (color >> 16 & 0xFF) / 255.0F;
float green = (color >> 8 & 0xFF) / 255.0F;
float blue = (color & 0xFF) / 255.0F;
return new Vec3d(red, green, blue);
}
return super.getFogColor(world, pos, state, entity, originalColor, partialTicks);
}
@Override
public IBlockState getStateAtViewpoint(IBlockState state, IBlockAccess world, BlockPos pos, Vec3d viewpoint)
{
if (!isWithinFluid(world, pos, viewpoint))
{
return world.getBlockState(pos.down(densityDir));
}
return super.getStateAtViewpoint(state, world, pos, viewpoint);
}
private boolean isWithinFluid(IBlockAccess world, BlockPos pos, Vec3d vec)
{
float filled = getFilledPercentage(world, pos);
return filled < 0 ? vec.y > pos.getY() + filled + 1
: vec.y < pos.getY() + filled;
}
}

View File

@@ -0,0 +1,338 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import com.google.common.primitives.Ints;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.event.ForgeEventFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This is a fluid block implementation which emulates vanilla Minecraft fluid behavior.
*
* It is highly recommended that you use/extend this class for "classic" fluid blocks.
*
*/
public class BlockFluidClassic extends BlockFluidBase
{
protected static final List<EnumFacing> SIDES = Collections.unmodifiableList(Arrays.asList(
EnumFacing.WEST, EnumFacing.EAST, EnumFacing.NORTH, EnumFacing.SOUTH));
protected boolean[] isOptimalFlowDirection = new boolean[4];
protected int[] flowCost = new int[4];
protected boolean canCreateSources = false;
protected FluidStack stack;
public BlockFluidClassic(Fluid fluid, Material material)
{
super(fluid, material);
stack = new FluidStack(fluid, Fluid.BUCKET_VOLUME);
}
public BlockFluidClassic setFluidStack(FluidStack stack)
{
this.stack = stack;
return this;
}
public BlockFluidClassic setFluidStackAmount(int amount)
{
this.stack.amount = amount;
return this;
}
@Override
public int getQuantaValue(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
if (state.getBlock().isAir(state, world, pos))
{
return 0;
}
if (state.getBlock() != this)
{
return -1;
}
return quantaPerBlock - state.getValue(LEVEL);
}
@Override
public boolean canCollideCheck(@Nonnull IBlockState state, boolean fullHit)
{
return fullHit && state.getValue(LEVEL) == 0;
}
@Override
public int getMaxRenderHeightMeta()
{
return 0;
}
@Override
public void updateTick(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Random rand)
{
int quantaRemaining = quantaPerBlock - state.getValue(LEVEL);
int expQuanta = -101;
// check adjacent block levels if non-source
if (quantaRemaining < quantaPerBlock)
{
int adjacentSourceBlocks = 0;
if (ForgeEventFactory.canCreateFluidSource(world, pos, state, canCreateSources))
{
for (EnumFacing side : EnumFacing.Plane.HORIZONTAL)
{
if (isSourceBlock(world, pos.offset(side))) adjacentSourceBlocks++;
}
}
// new source block
if (adjacentSourceBlocks >= 2 && (world.getBlockState(pos.up(densityDir)).getMaterial().isSolid() || isSourceBlock(world, pos.up(densityDir))))
{
expQuanta = quantaPerBlock;
}
// unobstructed flow from 'above'
else if (world.getBlockState(pos.down(densityDir)).getBlock() == this
|| hasDownhillFlow(world, pos, EnumFacing.EAST)
|| hasDownhillFlow(world, pos, EnumFacing.WEST)
|| hasDownhillFlow(world, pos, EnumFacing.NORTH)
|| hasDownhillFlow(world, pos, EnumFacing.SOUTH))
{
expQuanta = quantaPerBlock - 1;
}
else
{
int maxQuanta = -100;
for (EnumFacing side : EnumFacing.Plane.HORIZONTAL)
{
maxQuanta = getLargerQuanta(world, pos.offset(side), maxQuanta);
}
expQuanta = maxQuanta - 1;
}
// decay calculation
if (expQuanta != quantaRemaining)
{
quantaRemaining = expQuanta;
if (expQuanta <= 0)
{
world.setBlockToAir(pos);
}
else
{
world.setBlockState(pos, state.withProperty(LEVEL, quantaPerBlock - expQuanta), 2);
world.scheduleUpdate(pos, this, tickRate);
world.notifyNeighborsOfStateChange(pos, this, false);
}
}
}
// Flow vertically if possible
if (canDisplace(world, pos.up(densityDir)))
{
flowIntoBlock(world, pos.up(densityDir), 1);
return;
}
// Flow outward if possible
int flowMeta = quantaPerBlock - quantaRemaining + 1;
if (flowMeta >= quantaPerBlock)
{
return;
}
if (isSourceBlock(world, pos) || !isFlowingVertically(world, pos))
{
if (world.getBlockState(pos.down(densityDir)).getBlock() == this)
{
flowMeta = 1;
}
boolean flowTo[] = getOptimalFlowDirections(world, pos);
for (int i = 0; i < 4; i++)
{
if (flowTo[i]) flowIntoBlock(world, pos.offset(SIDES.get(i)), flowMeta);
}
}
}
protected final boolean hasDownhillFlow(IBlockAccess world, BlockPos pos, EnumFacing direction)
{
return world.getBlockState(pos.offset(direction).down(densityDir)).getBlock() == this
&& (canFlowInto(world, pos.offset(direction))
|| canFlowInto(world, pos.down(densityDir)));
}
public boolean isFlowingVertically(IBlockAccess world, BlockPos pos)
{
return world.getBlockState(pos.up(densityDir)).getBlock() == this ||
(world.getBlockState(pos).getBlock() == this && canFlowInto(world, pos.up(densityDir)));
}
public boolean isSourceBlock(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
return state.getBlock() == this && state.getValue(LEVEL) == 0;
}
protected boolean[] getOptimalFlowDirections(World world, BlockPos pos)
{
for (int side = 0; side < 4; side++)
{
flowCost[side] = 1000;
BlockPos pos2 = pos.offset(SIDES.get(side));
if (!canFlowInto(world, pos2) || isSourceBlock(world, pos2))
{
continue;
}
if (canFlowInto(world, pos2.up(densityDir)))
{
flowCost[side] = 0;
}
else
{
flowCost[side] = calculateFlowCost(world, pos2, 1, side);
}
}
int min = Ints.min(flowCost);
for (int side = 0; side < 4; side++)
{
isOptimalFlowDirection[side] = flowCost[side] == min;
}
return isOptimalFlowDirection;
}
protected int calculateFlowCost(World world, BlockPos pos, int recurseDepth, int side)
{
int cost = 1000;
for (int adjSide = 0; adjSide < 4; adjSide++)
{
if (SIDES.get(adjSide) == SIDES.get(side).getOpposite())
{
continue;
}
BlockPos pos2 = pos.offset(SIDES.get(adjSide));
if (!canFlowInto(world, pos2) || isSourceBlock(world, pos2))
{
continue;
}
if (canFlowInto(world, pos2.up(densityDir)))
{
return recurseDepth;
}
if (recurseDepth >= quantaPerBlock / 2)
{
continue;
}
cost = Math.min(cost, calculateFlowCost(world, pos2, recurseDepth + 1, adjSide));
}
return cost;
}
protected void flowIntoBlock(World world, BlockPos pos, int meta)
{
if (meta < 0) return;
if (displaceIfPossible(world, pos))
{
world.setBlockState(pos, this.getDefaultState().withProperty(LEVEL, meta), 3);
}
}
protected boolean canFlowInto(IBlockAccess world, BlockPos pos)
{
return world.getBlockState(pos).getBlock() == this || canDisplace(world, pos);
}
protected int getLargerQuanta(IBlockAccess world, BlockPos pos, int compare)
{
int quantaRemaining = getQuantaValue(world, pos);
if (quantaRemaining <= 0)
{
return compare;
}
return quantaRemaining >= compare ? quantaRemaining : compare;
}
/* IFluidBlock */
@Override
public int place(World world, BlockPos pos, @Nonnull FluidStack fluidStack, boolean doPlace)
{
if (fluidStack.amount < Fluid.BUCKET_VOLUME)
{
return 0;
}
if (doPlace)
{
FluidUtil.destroyBlockOnFluidPlacement(world, pos);
world.setBlockState(pos, this.getDefaultState(), 11);
}
return Fluid.BUCKET_VOLUME;
}
@Override
@Nullable
public FluidStack drain(World world, BlockPos pos, boolean doDrain)
{
if (!isSourceBlock(world, pos))
{
return null;
}
if (doDrain)
{
world.setBlockToAir(pos);
}
return stack.copy();
}
@Override
public boolean canDrain(World world, BlockPos pos)
{
return isSourceBlock(world, pos);
}
}

View File

@@ -0,0 +1,300 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import java.util.Random;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
/**
* This is a cellular-automata based finite fluid block implementation.
*
* It is highly recommended that you use/extend this class for finite fluid blocks.
*
*/
public class BlockFluidFinite extends BlockFluidBase
{
public BlockFluidFinite(Fluid fluid, Material material)
{
super(fluid, material);
}
@Override
public int getQuantaValue(IBlockAccess world, BlockPos pos)
{
IBlockState state = world.getBlockState(pos);
if (state.getBlock().isAir(state, world, pos))
{
return 0;
}
if (state.getBlock() != this)
{
return -1;
}
return state.getValue(LEVEL) + 1;
}
@Override
public boolean canCollideCheck(@Nonnull IBlockState state, boolean fullHit)
{
return fullHit;
}
@Override
public int getMaxRenderHeightMeta()
{
return quantaPerBlock - 1;
}
@Override
public void updateTick(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Random rand)
{
boolean changed = false;
int quantaRemaining = state.getValue(LEVEL) + 1;
// Flow vertically if possible
int prevRemaining = quantaRemaining;
quantaRemaining = tryToFlowVerticallyInto(world, pos, quantaRemaining);
if (quantaRemaining < 1)
{
return;
}
else if (quantaRemaining != prevRemaining)
{
changed = true;
if (quantaRemaining == 1)
{
world.setBlockState(pos, state.withProperty(LEVEL, quantaRemaining - 1), 2);
return;
}
}
else if (quantaRemaining == 1)
{
return;
}
// Flow out if possible
int lowerThan = quantaRemaining - 1;
int total = quantaRemaining;
int count = 1;
for (EnumFacing side : EnumFacing.Plane.HORIZONTAL)
{
BlockPos off = pos.offset(side);
if (displaceIfPossible(world, off))
world.setBlockToAir(off);
int quanta = getQuantaValueBelow(world, off, lowerThan);
if (quanta >= 0)
{
count++;
total += quanta;
}
}
if (count == 1)
{
if (changed)
{
world.setBlockState(pos, state.withProperty(LEVEL, quantaRemaining - 1), 2);
}
return;
}
int each = total / count;
int rem = total % count;
for (EnumFacing side : EnumFacing.Plane.HORIZONTAL)
{
BlockPos off = pos.offset(side);
int quanta = getQuantaValueBelow(world, off, lowerThan);
if (quanta >= 0)
{
int newQuanta = each;
if (rem == count || rem > 1 && rand.nextInt(count - rem) != 0)
{
++newQuanta;
--rem;
}
if (newQuanta != quanta)
{
if (newQuanta == 0)
{
world.setBlockToAir(off);
}
else
{
world.setBlockState(off, getDefaultState().withProperty(LEVEL, newQuanta - 1), 2);
}
world.scheduleUpdate(off, this, tickRate);
}
--count;
}
}
if (rem > 0)
{
++each;
}
world.setBlockState(pos, state.withProperty(LEVEL, each - 1), 2);
}
public int tryToFlowVerticallyInto(World world, BlockPos pos, int amtToInput)
{
IBlockState myState = world.getBlockState(pos);
BlockPos other = pos.add(0, densityDir, 0);
if (other.getY() < 0 || other.getY() >= world.getHeight())
{
world.setBlockToAir(pos);
return 0;
}
int amt = getQuantaValueBelow(world, other, quantaPerBlock);
if (amt >= 0)
{
amt += amtToInput;
if (amt > quantaPerBlock)
{
world.setBlockState(other, myState.withProperty(LEVEL, quantaPerBlock - 1), 3);
world.scheduleUpdate(other, this, tickRate);
return amt - quantaPerBlock;
}
else if (amt > 0)
{
world.setBlockState(other, myState.withProperty(LEVEL, amt - 1), 3);
world.scheduleUpdate(other, this, tickRate);
world.setBlockToAir(pos);
return 0;
}
return amtToInput;
}
else
{
int density_other = getDensity(world, other);
if (density_other == Integer.MAX_VALUE)
{
if (displaceIfPossible(world, other))
{
world.setBlockState(other, myState.withProperty(LEVEL, amtToInput - 1), 3);
world.scheduleUpdate(other, this, tickRate);
world.setBlockToAir(pos);
return 0;
}
else
{
return amtToInput;
}
}
if (densityDir < 0)
{
if (density_other < density) // then swap
{
IBlockState state = world.getBlockState(other);
world.setBlockState(other, myState.withProperty(LEVEL, amtToInput - 1), 3);
world.setBlockState(pos, state, 3);
world.scheduleUpdate(other, this, tickRate);
world.scheduleUpdate(pos, state.getBlock(), state.getBlock().tickRate(world));
return 0;
}
}
else
{
if (density_other > density)
{
IBlockState state = world.getBlockState(other);
world.setBlockState(other, myState.withProperty(LEVEL, amtToInput - 1), 3);
world.setBlockState(pos, state, 3);
world.scheduleUpdate(other, this, tickRate);
world.scheduleUpdate(pos, state.getBlock(), state.getBlock().tickRate(world));
return 0;
}
}
return amtToInput;
}
}
/* IFluidBlock */
@Override
public int place(World world, BlockPos pos, @Nonnull FluidStack fluidStack, boolean doPlace)
{
IBlockState existing = world.getBlockState(pos);
float quantaAmount = Fluid.BUCKET_VOLUME / quantaPerBlockFloat;
// If the stack contains more available fluid than the full source block,
// set a source block
int closest = Fluid.BUCKET_VOLUME;
int quanta = quantaPerBlock;
if (fluidStack.amount < closest)
{
// Figure out maximum level to match stack amount
closest = MathHelper.floor(quantaAmount * MathHelper.floor(fluidStack.amount / quantaAmount));
quanta = MathHelper.floor(closest / quantaAmount);
}
if (existing.getBlock() == this)
{
int existingQuanta = existing.getValue(LEVEL) + 1;
int missingQuanta = quantaPerBlock - existingQuanta;
closest = Math.min(closest, MathHelper.floor(missingQuanta * quantaAmount));
quanta = Math.min(quanta + existingQuanta, quantaPerBlock);
}
// If too little (or too much, technically impossible) fluid is to be placed, abort
if (quanta < 1 || quanta > 16)
return 0;
if (doPlace)
{
FluidUtil.destroyBlockOnFluidPlacement(world, pos);
world.setBlockState(pos, getDefaultState().withProperty(LEVEL, quanta - 1), 11);
}
return closest;
}
@Override
public FluidStack drain(World world, BlockPos pos, boolean doDrain)
{
final FluidStack fluidStack = new FluidStack(getFluid(), MathHelper.floor(getQuantaPercentage(world, pos) * Fluid.BUCKET_VOLUME));
if (doDrain)
{
world.setBlockToAir(pos);
}
return fluidStack;
}
@Override
public boolean canDrain(World world, BlockPos pos)
{
return true;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nonnull;
import net.minecraft.block.BlockDispenser;
import net.minecraft.dispenser.BehaviorDefaultDispenseItem;
import net.minecraft.dispenser.IBlockSource;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntityDispenser;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
/**
* Fills or drains a fluid container item using a Dispenser.
*/
public class DispenseFluidContainer extends BehaviorDefaultDispenseItem
{
private static final DispenseFluidContainer INSTANCE = new DispenseFluidContainer();
public static DispenseFluidContainer getInstance()
{
return INSTANCE;
}
private DispenseFluidContainer() {}
private final BehaviorDefaultDispenseItem dispenseBehavior = new BehaviorDefaultDispenseItem();
/**
* Dispense the specified stack, play the dispense sound and spawn particles.
*/
@Override
@Nonnull
public ItemStack dispenseStack(@Nonnull IBlockSource source, @Nonnull ItemStack stack)
{
if (FluidUtil.getFluidContained(stack) != null)
{
return dumpContainer(source, stack);
}
else
{
return fillContainer(source, stack);
}
}
/**
* Picks up fluid in front of a Dispenser and fills a container with it.
*/
@Nonnull
private ItemStack fillContainer(@Nonnull IBlockSource source, @Nonnull ItemStack stack)
{
World world = source.getWorld();
EnumFacing dispenserFacing = source.getBlockState().getValue(BlockDispenser.FACING);
BlockPos blockpos = source.getBlockPos().offset(dispenserFacing);
FluidActionResult actionResult = FluidUtil.tryPickUpFluid(stack, null, world, blockpos, dispenserFacing.getOpposite());
ItemStack resultStack = actionResult.getResult();
if (!actionResult.isSuccess() || resultStack.isEmpty())
{
return super.dispenseStack(source, stack);
}
if (stack.getCount() == 1)
{
return resultStack;
}
else if (((TileEntityDispenser)source.getBlockTileEntity()).addItemStack(resultStack) < 0)
{
this.dispenseBehavior.dispense(source, resultStack);
}
ItemStack stackCopy = stack.copy();
stackCopy.shrink(1);
return stackCopy;
}
/**
* Drains a filled container and places the fluid in front of the Dispenser.
*/
@Nonnull
private ItemStack dumpContainer(IBlockSource source, @Nonnull ItemStack stack)
{
ItemStack singleStack = stack.copy();
singleStack.setCount(1);
IFluidHandlerItem fluidHandler = FluidUtil.getFluidHandler(singleStack);
if (fluidHandler == null)
{
return super.dispenseStack(source, stack);
}
FluidStack fluidStack = fluidHandler.drain(Fluid.BUCKET_VOLUME, false);
EnumFacing dispenserFacing = source.getBlockState().getValue(BlockDispenser.FACING);
BlockPos blockpos = source.getBlockPos().offset(dispenserFacing);
FluidActionResult result = fluidStack != null ? FluidUtil.tryPlaceFluid(null, source.getWorld(), blockpos, stack, fluidStack) : FluidActionResult.FAILURE;
if (result.isSuccess())
{
ItemStack drainedStack = result.getResult();
if (drainedStack.getCount() == 1)
{
return drainedStack;
}
else if (!drainedStack.isEmpty() && ((TileEntityDispenser)source.getBlockTileEntity()).addItemStack(drainedStack) < 0)
{
this.dispenseBehavior.dispense(source, drainedStack);
}
ItemStack stackCopy = drainedStack.copy();
stackCopy.shrink(1);
return stackCopy;
}
else
{
return this.dispenseBehavior.dispense(source, stack);
}
}
}

View File

@@ -0,0 +1,456 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nullable;
import java.awt.Color;
import java.util.Locale;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.SoundEvents;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.translation.I18n;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraft.world.WorldProvider;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraft.item.EnumRarity;
/**
* 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 Fluid
{
public static final int BUCKET_VOLUME = 1000;
/** The unique identification name for this fluid. */
protected final String fluidName;
/** The unlocalized name of this fluid. */
protected String unlocalizedName;
protected final ResourceLocation still;
protected final ResourceLocation flowing;
@Nullable
protected final ResourceLocation overlay;
private SoundEvent fillSound;
private SoundEvent emptySound;
/**
* The light level emitted by this fluid.
*
* Default value is 0, as most fluids do not actively emit light.
*/
protected int luminosity = 0;
/**
* 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.
*/
protected int density = 1000;
/**
* 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.
*/
protected int temperature = 300;
/**
* 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.
*
*/
protected int viscosity = 1000;
/**
* This indicates if the fluid is gaseous.
*
* Generally this is associated with negative density fluids.
*/
protected boolean isGaseous;
/**
* The rarity of the fluid.
*
* Used primarily in tool tips.
*/
protected EnumRarity rarity = EnumRarity.COMMON;
/**
* If there is a Block implementation of the Fluid, the Block is linked here.
*
* The default value of null should remain for any Fluid without a Block implementation.
*/
protected Block block = null;
/**
* 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
*/
protected int color = 0xFFFFFFFF;
public Fluid(String fluidName, ResourceLocation still, ResourceLocation flowing, Color color)
{
this(fluidName, still, flowing, null, color);
}
public Fluid(String fluidName, ResourceLocation still, ResourceLocation flowing, @Nullable ResourceLocation overlay, Color color)
{
this(fluidName, still, flowing, overlay);
this.setColor(color);
}
public Fluid(String fluidName, ResourceLocation still, ResourceLocation flowing, int color)
{
this(fluidName, still, flowing, null, color);
}
public Fluid(String fluidName, ResourceLocation still, ResourceLocation flowing, @Nullable ResourceLocation overlay, int color)
{
this(fluidName, still, flowing, overlay);
this.setColor(color);
}
public Fluid(String fluidName, ResourceLocation still, ResourceLocation flowing)
{
this(fluidName, still, flowing, (ResourceLocation) null);
}
public Fluid(String fluidName, ResourceLocation still, ResourceLocation flowing, @Nullable ResourceLocation overlay)
{
this.fluidName = fluidName.toLowerCase(Locale.ENGLISH);
this.unlocalizedName = fluidName;
this.still = still;
this.flowing = flowing;
this.overlay = overlay;
}
public Fluid setUnlocalizedName(String unlocalizedName)
{
this.unlocalizedName = unlocalizedName;
return this;
}
public Fluid setBlock(Block block)
{
if (this.block == null || this.block == block)
{
this.block = block;
}
else
{
FMLLog.log.warn("A mod has attempted to assign Block {} to the Fluid '{}' but this Fluid has already been linked to the Block {}. "
+ "You may have duplicate Fluid Blocks as a result. It *may* be possible to configure your mods to avoid this.", block, fluidName, this.block);
}
return this;
}
public Fluid setLuminosity(int luminosity)
{
this.luminosity = luminosity;
return this;
}
public Fluid setDensity(int density)
{
this.density = density;
return this;
}
public Fluid setTemperature(int temperature)
{
this.temperature = temperature;
return this;
}
public Fluid setViscosity(int viscosity)
{
this.viscosity = viscosity;
return this;
}
public Fluid setGaseous(boolean isGaseous)
{
this.isGaseous = isGaseous;
return this;
}
public Fluid setRarity(EnumRarity rarity)
{
this.rarity = rarity;
return this;
}
public Fluid setFillSound(SoundEvent fillSound)
{
this.fillSound = fillSound;
return this;
}
public Fluid setEmptySound(SoundEvent emptySound)
{
this.emptySound = emptySound;
return this;
}
public Fluid setColor(Color color)
{
this.color = color.getRGB();
return this;
}
public Fluid setColor(int color)
{
this.color = color;
return this;
}
public final String getName()
{
return this.fluidName;
}
public final Block getBlock()
{
return block;
}
public final boolean canBePlacedInWorld()
{
return block != null;
}
public final boolean isLighterThanAir()
{
int density = this.density;
if (block instanceof BlockFluidBase)
{
density = ((BlockFluidBase) block).getDensity();
}
return density <= 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.item.ItemBucket#tryPlaceContainedLiquid(EntityPlayer, World, BlockPos)}
*
* @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(FluidStack fluidStack)
{
if (block == null)
return false;
return block.getDefaultState().getMaterial() == Material.WATER;
}
/**
* Called instead of placing the fluid block if {@link WorldProvider#doesWaterVaporize()} and {@link #doesVaporize(FluidStack)} are true.
* Override this to make your explosive liquid blow up instead of the default smoke, etc.
* Based on {@link net.minecraft.item.ItemBucket#tryPlaceContainedLiquid(EntityPlayer, World, BlockPos)}
*
* @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 EntityPlayer player, World worldIn, BlockPos pos, FluidStack fluidStack)
{
worldIn.playSound(player, pos, SoundEvents.BLOCK_FIRE_EXTINGUISH, SoundCategory.BLOCKS, 0.5F, 2.6F + (worldIn.rand.nextFloat() - worldIn.rand.nextFloat()) * 0.8F);
for (int l = 0; l < 8; ++l)
{
worldIn.spawnParticle(EnumParticleTypes.SMOKE_LARGE, (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 this fluid.
*/
public String getLocalizedName(FluidStack stack)
{
String s = this.getUnlocalizedName();
return s == null ? "" : I18n.translateToLocal(s);
}
/**
* A FluidStack sensitive version of getUnlocalizedName
*/
public String getUnlocalizedName(FluidStack stack)
{
return this.getUnlocalizedName();
}
/**
* Returns the unlocalized name of this fluid.
*/
public String getUnlocalizedName()
{
return "fluid." + this.unlocalizedName;
}
/* 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 final boolean isGaseous()
{
return this.isGaseous;
}
public EnumRarity getRarity()
{
return rarity;
}
public int getColor()
{
return color;
}
public ResourceLocation getStill()
{
return still;
}
public ResourceLocation getFlowing()
{
return flowing;
}
@Nullable
public ResourceLocation getOverlay()
{
return overlay;
}
public SoundEvent getFillSound()
{
if(fillSound == null)
{
if(getBlock() != null && getBlock().getDefaultState().getMaterial() == Material.LAVA)
{
fillSound = SoundEvents.ITEM_BUCKET_FILL_LAVA;
}
else
{
fillSound = SoundEvents.ITEM_BUCKET_FILL;
}
}
return fillSound;
}
public SoundEvent getEmptySound()
{
if(emptySound == null)
{
if(getBlock() != null && getBlock().getDefaultState().getMaterial() == Material.LAVA)
{
emptySound = SoundEvents.ITEM_BUCKET_EMPTY_LAVA;
}
else
{
emptySound = SoundEvents.ITEM_BUCKET_EMPTY;
}
}
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 boolean isGaseous(FluidStack stack){ return isGaseous(); }
public EnumRarity getRarity(FluidStack stack){ return getRarity(); }
public int getColor(FluidStack stack){ return getColor(); }
public ResourceLocation getStill(FluidStack stack) { return getStill(); }
public ResourceLocation getFlowing(FluidStack stack) { return getFlowing(); }
public SoundEvent getFillSound(FluidStack stack) { return getFillSound(); }
public SoundEvent getEmptySound(FluidStack stack) { return getEmptySound(); }
/* World-based Accessors */
public int getLuminosity(World world, BlockPos pos){ return getLuminosity(); }
public int getDensity(World world, BlockPos pos){ return getDensity(); }
public int getTemperature(World world, BlockPos pos){ return getTemperature(); }
public int getViscosity(World world, BlockPos pos){ return getViscosity(); }
public boolean isGaseous(World world, BlockPos pos){ return isGaseous(); }
public EnumRarity getRarity(World world, BlockPos pos){ return getRarity(); }
public int getColor(World world, BlockPos pos){ return getColor(); }
public ResourceLocation getStill(World world, BlockPos pos) { return getStill(); }
public ResourceLocation getFlowing(World world, BlockPos pos) { return getFlowing(); }
public SoundEvent getFillSound(World world, BlockPos pos) { return getFillSound(); }
public SoundEvent getEmptySound(World world, BlockPos pos) { return getEmptySound(); }
}

View File

@@ -0,0 +1,64 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nonnull;
import net.minecraft.item.ItemStack;
/**
* Holds the result of a fluid action from {@link FluidUtil}.
*
* Failed actions will always have {@link #isSuccess()} == false and an empty ItemStack result. See {@link #FAILURE}.
*
* Successful actions will always have {@link #isSuccess()} == true.
* Successful actions may have an empty ItemStack result in some cases,
* for example the action succeeded and the resulting item was consumed.
*/
public class FluidActionResult
{
public static final FluidActionResult FAILURE = new FluidActionResult(false, ItemStack.EMPTY);
public final boolean success;
@Nonnull
public final ItemStack result;
public FluidActionResult(@Nonnull ItemStack result)
{
this(true, result);
}
private FluidActionResult(boolean success, @Nonnull ItemStack result)
{
this.success = success;
this.result = result;
}
public boolean isSuccess()
{
return success;
}
@Nonnull
public ItemStack getResult()
{
return result;
}
}

View File

@@ -0,0 +1,145 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.Event;
public class FluidEvent extends Event
{
private final FluidStack fluid;
private final World world;
private final BlockPos pos;
public FluidEvent(FluidStack fluid, World world, BlockPos pos)
{
this.fluid = fluid;
this.world = world;
this.pos = pos;
}
public FluidStack getFluid()
{
return fluid;
}
public World getWorld()
{
return world;
}
public BlockPos getPos()
{
return pos;
}
/**
* Mods should fire this event when they move fluids around.
*
*/
public static class FluidMotionEvent extends FluidEvent
{
public FluidMotionEvent(FluidStack fluid, World world, BlockPos pos)
{
super(fluid, world, pos);
}
}
/**
* Mods should fire this event when a fluid is {@link IFluidTank#fill(FluidStack, boolean)}
* their tank implementation. {@link FluidTank} does.
*
*/
public static class FluidFillingEvent extends FluidEvent
{
private final IFluidTank tank;
private final int amount;
public FluidFillingEvent(FluidStack fluid, World world, BlockPos pos, IFluidTank tank, int amount)
{
super(fluid, world, pos);
this.tank = tank;
this.amount = amount;
}
public IFluidTank getTank()
{
return tank;
}
public int getAmount()
{
return amount;
}
}
/**
* Mods should fire this event when a fluid is {@link IFluidTank#drain(int, boolean)} from their
* tank.
*
*/
public static class FluidDrainingEvent extends FluidEvent
{
private final IFluidTank tank;
private final int amount;
public FluidDrainingEvent(FluidStack fluid, World world, BlockPos pos, IFluidTank tank, int amount)
{
super(fluid, world, pos);
this.amount = amount;
this.tank = tank;
}
public IFluidTank getTank()
{
return tank;
}
public int getAmount()
{
return amount;
}
}
/**
* Mods should fire this event when a fluid "spills", for example, if a block containing fluid
* is broken.
*
*/
public static class FluidSpilledEvent extends FluidEvent
{
public FluidSpilledEvent(FluidStack fluid, World world, BlockPos pos)
{
super(fluid, world, pos);
}
}
/**
* A handy shortcut for firing the various fluid events.
*
* @param event
*/
public static final void fireEvent(FluidEvent event)
{
MinecraftForge.EVENT_BUS.post(event);
}
}

View File

@@ -0,0 +1,476 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.minecraftforge.fml.common.LoaderState;
import org.apache.logging.log4j.Level;
import net.minecraft.block.Block;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.translation.I18n;
import net.minecraftforge.common.MinecraftForge;
import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.registries.IRegistryDelegate;
import javax.annotation.Nullable;
/**
* Handles Fluid registrations. Fluids MUST be registered in order to function.
*/
public abstract class FluidRegistry
{
static int maxID = 0;
static BiMap<String, Fluid> fluids = HashBiMap.create();
static BiMap<Fluid, Integer> fluidIDs = HashBiMap.create();
static BiMap<Integer, String> fluidNames = HashBiMap.create(); //Caching this just makes some other calls faster
static BiMap<Block, Fluid> fluidBlocks;
// the globally unique fluid map - only used to associate non-defaults during world/server loading
static BiMap<String,Fluid> masterFluidReference = HashBiMap.create();
static BiMap<String,String> defaultFluidName = HashBiMap.create();
static Map<Fluid,FluidDelegate> delegates = Maps.newHashMap();
static boolean universalBucketEnabled = false;
static Set<Fluid> bucketFluids = Sets.newHashSet();
public static final Fluid WATER = new Fluid("water", new ResourceLocation("blocks/water_still"), new ResourceLocation("blocks/water_flow"), new ResourceLocation("blocks/water_overlay")) {
@Override
public String getLocalizedName(FluidStack fs) {
return I18n.translateToLocal("tile.water.name");
}
}.setBlock(Blocks.WATER).setUnlocalizedName(Blocks.WATER.getUnlocalizedName());
public static final Fluid LAVA = new Fluid("lava", new ResourceLocation("blocks/lava_still"), new ResourceLocation("blocks/lava_flow")) {
@Override
public String getLocalizedName(FluidStack fs) {
return I18n.translateToLocal("tile.lava.name");
}
}.setBlock(Blocks.LAVA).setLuminosity(15).setDensity(3000).setViscosity(6000).setTemperature(1300).setUnlocalizedName(Blocks.LAVA.getUnlocalizedName());
static
{
registerFluid(WATER);
registerFluid(LAVA);
}
private FluidRegistry(){}
/**
* Called by Forge to prepare the ID map for server -> client sync.
* Modders, DO NOT call this.
*/
public static void initFluidIDs(BiMap<Fluid, Integer> newfluidIDs, Set<String> defaultNames)
{
maxID = newfluidIDs.size();
loadFluidDefaults(newfluidIDs, defaultNames);
}
/**
* Called by forge to load default fluid IDs from the world or from server -> client for syncing
* DO NOT call this and expect useful behaviour.
* @param localFluidIDs
* @param defaultNames
*/
private static void loadFluidDefaults(BiMap<Fluid, Integer> localFluidIDs, Set<String> defaultNames)
{
// If there's an empty set of default names, use the defaults as defined locally
if (defaultNames.isEmpty()) {
defaultNames.addAll(defaultFluidName.values());
}
BiMap<String, Fluid> localFluids = HashBiMap.create(fluids);
for (String defaultName : defaultNames)
{
Fluid fluid = masterFluidReference.get(defaultName);
if (fluid == null) {
String derivedName = defaultName.split(":",2)[1];
String localDefault = defaultFluidName.get(derivedName);
if (localDefault == null) {
FMLLog.log.error("The fluid {} (specified as {}) is missing from this instance - it will be removed", derivedName, defaultName);
continue;
}
fluid = masterFluidReference.get(localDefault);
FMLLog.log.error("The fluid {} specified as default is not present - it will be reverted to default {}", defaultName, localDefault);
}
FMLLog.log.debug("The fluid {} has been selected as the default fluid for {}", defaultName, fluid.getName());
Fluid oldFluid = localFluids.put(fluid.getName(), fluid);
Integer id = localFluidIDs.remove(oldFluid);
localFluidIDs.put(fluid, id);
}
BiMap<Integer, String> localFluidNames = HashBiMap.create();
for (Entry<Fluid, Integer> e : localFluidIDs.entrySet()) {
localFluidNames.put(e.getValue(), e.getKey().getName());
}
fluidIDs = localFluidIDs;
fluids = localFluids;
fluidNames = localFluidNames;
fluidBlocks = null;
for (FluidDelegate fd : delegates.values())
{
fd.rebind();
}
}
/**
* Register a new Fluid. If a fluid with the same name already exists, registration the alternative fluid is tracked
* in case it is the default in another place
*
* @param fluid
* The fluid to register.
* @return True if the fluid was registered as the current default fluid, false if it was only registered as an alternative
*/
public static boolean registerFluid(Fluid fluid)
{
masterFluidReference.put(uniqueName(fluid), fluid);
delegates.put(fluid, new FluidDelegate(fluid, fluid.getName()));
if (fluids.containsKey(fluid.getName()))
{
return false;
}
fluids.put(fluid.getName(), fluid);
maxID++;
fluidIDs.put(fluid, maxID);
fluidNames.put(maxID, fluid.getName());
defaultFluidName.put(fluid.getName(), uniqueName(fluid));
MinecraftForge.EVENT_BUS.post(new FluidRegisterEvent(fluid.getName(), maxID));
return true;
}
private static String uniqueName(Fluid fluid)
{
ModContainer activeModContainer = Loader.instance().activeModContainer();
String activeModContainerName = activeModContainer == null ? "minecraft" : activeModContainer.getModId();
return activeModContainerName+":"+fluid.getName();
}
/**
* Is the supplied fluid the current default fluid for it's name
* @param fluid the fluid we're testing
* @return if the fluid is default
*/
public static boolean isFluidDefault(Fluid fluid)
{
return fluids.containsValue(fluid);
}
/**
* Does the supplied fluid have an entry for it's name (whether or not the fluid itself is default)
* @param fluid the fluid we're testing
* @return if the fluid's name has a registration entry
*/
public static boolean isFluidRegistered(Fluid fluid)
{
return fluid != null && fluids.containsKey(fluid.getName());
}
public static boolean isFluidRegistered(String fluidName)
{
return fluids.containsKey(fluidName);
}
public static Fluid getFluid(String fluidName)
{
return fluids.get(fluidName);
}
public static String getFluidName(Fluid fluid)
{
return fluids.inverse().get(fluid);
}
public static String getFluidName(FluidStack stack)
{
return getFluidName(stack.getFluid());
}
@Nullable
public static FluidStack getFluidStack(String fluidName, int amount)
{
if (!fluids.containsKey(fluidName))
{
return null;
}
return new FluidStack(getFluid(fluidName), amount);
}
/**
* Returns a read-only map containing Fluid Names and their associated Fluids.
*/
public static Map<String, Fluid> getRegisteredFluids()
{
return ImmutableMap.copyOf(fluids);
}
/**
* Returns a read-only map containing Fluid Names and their associated IDs.
* Modders should never actually use this, use the String names.
*/
@Deprecated
public static Map<Fluid, Integer> getRegisteredFluidIDs()
{
return ImmutableMap.copyOf(fluidIDs);
}
/**
* Enables the universal bucket in forge.
* Has to be called before pre-initialization.
* Actually just call it statically in your mod class.
*/
public static void enableUniversalBucket()
{
if (Loader.instance().hasReachedState(LoaderState.PREINITIALIZATION))
{
ModContainer modContainer = Loader.instance().activeModContainer();
String modContainerName = modContainer == null ? null : modContainer.getName();
FMLLog.log.error("Trying to activate the universal filled bucket too late. Call it statically in your Mods class. Mod: {}", modContainerName);
}
else
{
universalBucketEnabled = true;
}
}
public static boolean isUniversalBucketEnabled()
{
return universalBucketEnabled;
}
/**
* Registers a fluid with the universal bucket.
* This only has an effect if the universal bucket is enabled.
* @param fluid The fluid that the bucket shall be able to hold
* @return True if the fluid was added successfully, false if it already was registered or couldn't be registered with the bucket.
*/
public static boolean addBucketForFluid(Fluid fluid)
{
if(fluid == null) {
return false;
}
// register unregistered fluids
if (!isFluidRegistered(fluid))
{
registerFluid(fluid);
}
return bucketFluids.add(fluid);
}
/**
* All fluids registered with the universal bucket
* @return An immutable set containing the fluids
*/
public static Set<Fluid> getBucketFluids()
{
return ImmutableSet.copyOf(bucketFluids);
}
public static Fluid lookupFluidForBlock(Block block)
{
if (fluidBlocks == null)
{
BiMap<Block, Fluid> tmp = HashBiMap.create();
for (Fluid fluid : fluids.values())
{
if (fluid.canBePlacedInWorld() && fluid.getBlock() != null)
{
tmp.put(fluid.getBlock(), fluid);
}
}
fluidBlocks = tmp;
}
if (block == Blocks.FLOWING_WATER)
{
block = Blocks.WATER;
}
else if (block == Blocks.FLOWING_LAVA)
{
block = Blocks.LAVA;
}
return fluidBlocks.get(block);
}
public static class FluidRegisterEvent extends Event
{
private final String fluidName;
private final int fluidID;
public FluidRegisterEvent(String fluidName, int fluidID)
{
this.fluidName = fluidName;
this.fluidID = fluidID;
}
public String getFluidName()
{
return fluidName;
}
public int getFluidID()
{
return fluidID;
}
}
public static int getMaxID()
{
return maxID;
}
public static String getDefaultFluidName(Fluid key)
{
String name = masterFluidReference.inverse().get(key);
if (Strings.isNullOrEmpty(name)) {
FMLLog.log.error("The fluid registry is corrupted. A fluid {} {} is not properly registered. The mod that registered this is broken", key.getClass().getName(), key.getName());
throw new IllegalStateException("The fluid registry is corrupted");
}
return name;
}
@Nullable
public static String getModId(@Nullable FluidStack fluidStack)
{
if (fluidStack != null)
{
String defaultFluidName = getDefaultFluidName(fluidStack.getFluid());
if (defaultFluidName != null)
{
ResourceLocation fluidResourceName = new ResourceLocation(defaultFluidName);
return fluidResourceName.getResourceDomain();
}
}
return null;
}
public static void loadFluidDefaults(NBTTagCompound tag)
{
Set<String> defaults = Sets.newHashSet();
if (tag.hasKey("DefaultFluidList",9))
{
FMLLog.log.debug("Loading persistent fluid defaults from world");
NBTTagList tl = tag.getTagList("DefaultFluidList", 8);
for (int i = 0; i < tl.tagCount(); i++)
{
defaults.add(tl.getStringTagAt(i));
}
}
else
{
FMLLog.log.debug("World is missing persistent fluid defaults - using local defaults");
}
loadFluidDefaults(HashBiMap.create(fluidIDs), defaults);
}
public static void writeDefaultFluidList(NBTTagCompound forgeData)
{
NBTTagList tagList = new NBTTagList();
for (Entry<String, Fluid> def : fluids.entrySet())
{
tagList.appendTag(new NBTTagString(getDefaultFluidName(def.getValue())));
}
forgeData.setTag("DefaultFluidList", tagList);
}
public static void validateFluidRegistry()
{
Set<Fluid> illegalFluids = Sets.newHashSet();
for (Fluid f : fluids.values())
{
if (!masterFluidReference.containsValue(f))
{
illegalFluids.add(f);
}
}
if (!illegalFluids.isEmpty())
{
FMLLog.log.fatal("The fluid registry is corrupted. Something has inserted a fluid without registering it");
FMLLog.log.fatal("There is {} unregistered fluids", illegalFluids.size());
for (Fluid f: illegalFluids)
{
FMLLog.log.fatal(" Fluid name : {}, type: {}", f.getName(), f.getClass().getName());
}
FMLLog.log.fatal("The mods that own these fluids need to register them properly");
throw new IllegalStateException("The fluid map contains fluids unknown to the master fluid registry");
}
}
static IRegistryDelegate<Fluid> makeDelegate(Fluid fl)
{
return delegates.get(fl);
}
private static class FluidDelegate implements IRegistryDelegate<Fluid>
{
private String name;
private Fluid fluid;
FluidDelegate(Fluid fluid, String name)
{
this.fluid = fluid;
this.name = name;
}
@Override
public Fluid get()
{
return fluid;
}
@Override
public ResourceLocation name() {
return new ResourceLocation(name);
}
@Override
public Class<Fluid> type()
{
return Fluid.class;
}
void rebind()
{
fluid = fluids.get(name);
}
}
}

View File

@@ -0,0 +1,232 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.registries.IRegistryDelegate;
import javax.annotation.Nullable;
/**
* ItemStack substitute for Fluids.
*
* NOTE: Equality is based on the Fluid, not the amount. Use
* {@link #isFluidStackIdentical(FluidStack)} to determine if FluidID, Amount and NBT Tag are all
* equal.
*
*/
public class FluidStack
{
public int amount;
public NBTTagCompound tag;
private IRegistryDelegate<Fluid> fluidDelegate;
public FluidStack(Fluid fluid, int amount)
{
if (fluid == null)
{
FMLLog.bigWarning("Null fluid supplied to fluidstack. Did you try and create a stack for an unregistered fluid?");
throw new IllegalArgumentException("Cannot create a fluidstack from a null fluid");
}
else if (!FluidRegistry.isFluidRegistered(fluid))
{
FMLLog.bigWarning("Failed attempt to create a FluidStack for an unregistered Fluid {} (type {})", fluid.getName(), fluid.getClass().getName());
throw new IllegalArgumentException("Cannot create a fluidstack from an unregistered fluid");
}
this.fluidDelegate = FluidRegistry.makeDelegate(fluid);
this.amount = amount;
}
public FluidStack(Fluid fluid, int amount, NBTTagCompound nbt)
{
this(fluid, amount);
if (nbt != null)
{
tag = (NBTTagCompound) nbt.copy();
}
}
public FluidStack(FluidStack stack, int amount)
{
this(stack.getFluid(), amount, stack.tag);
}
/**
* This provides a safe method for retrieving a FluidStack - if the Fluid is invalid, the stack
* will return as null.
*/
@Nullable
public static FluidStack loadFluidStackFromNBT(NBTTagCompound nbt)
{
if (nbt == null)
{
return null;
}
if (!nbt.hasKey("FluidName", Constants.NBT.TAG_STRING))
{
return null;
}
String fluidName = nbt.getString("FluidName");
if (FluidRegistry.getFluid(fluidName) == null)
{
return null;
}
FluidStack stack = new FluidStack(FluidRegistry.getFluid(fluidName), nbt.getInteger("Amount"));
if (nbt.hasKey("Tag"))
{
stack.tag = nbt.getCompoundTag("Tag");
}
return stack;
}
public NBTTagCompound writeToNBT(NBTTagCompound nbt)
{
nbt.setString("FluidName", FluidRegistry.getFluidName(getFluid()));
nbt.setInteger("Amount", amount);
if (tag != null)
{
nbt.setTag("Tag", tag);
}
return nbt;
}
public final Fluid getFluid()
{
return fluidDelegate.get();
}
public String getLocalizedName()
{
return this.getFluid().getLocalizedName(this);
}
public String getUnlocalizedName()
{
return this.getFluid().getUnlocalizedName(this);
}
/**
* @return A copy of this FluidStack
*/
public FluidStack copy()
{
return new FluidStack(getFluid(), amount, tag);
}
/**
* Determines if the FluidIDs and NBT Tags are equal. This does not check amounts.
*
* @param other
* The FluidStack for comparison
* @return true if the Fluids (IDs and NBT Tags) are the same
*/
public boolean isFluidEqual(@Nullable FluidStack other)
{
return other != null && getFluid() == other.getFluid() && isFluidStackTagEqual(other);
}
private boolean isFluidStackTagEqual(FluidStack other)
{
return tag == null ? other.tag == null : other.tag == null ? false : tag.equals(other.tag);
}
/**
* Determines if the NBT Tags are equal. Useful if the FluidIDs are known to be equal.
*/
public static boolean areFluidStackTagsEqual(@Nullable FluidStack stack1, @Nullable FluidStack stack2)
{
return stack1 == null && stack2 == null ? true : stack1 == null || stack2 == null ? false : stack1.isFluidStackTagEqual(stack2);
}
/**
* Determines if the Fluids are equal and this stack is larger.
*
* @param other
* @return true if this FluidStack contains the other FluidStack (same fluid and >= amount)
*/
public boolean containsFluid(@Nullable FluidStack other)
{
return isFluidEqual(other) && amount >= other.amount;
}
/**
* Determines if the FluidIDs, Amounts, and NBT Tags are all equal.
*
* @param other
* - the FluidStack for comparison
* @return true if the two FluidStacks are exactly the same
*/
public boolean isFluidStackIdentical(FluidStack other)
{
return isFluidEqual(other) && amount == other.amount;
}
/**
* Determines if the FluidIDs and NBT Tags are equal compared to a registered container
* ItemStack. This does not check amounts.
*
* @param other
* The ItemStack for comparison
* @return true if the Fluids (IDs and NBT Tags) are the same
*/
public boolean isFluidEqual(ItemStack other)
{
if (other == null)
{
return false;
}
return isFluidEqual(FluidUtil.getFluidContained(other));
}
@Override
public final int hashCode()
{
int code = 1;
code = 31*code + getFluid().hashCode();
code = 31*code + amount;
if (tag != null)
code = 31*code + tag.hashCode();
return code;
}
/**
* Default equality comparison for a FluidStack. Same functionality as isFluidEqual().
*
* This is included for use in data structures.
*/
@Override
public final boolean equals(Object o)
{
if (!(o instanceof FluidStack))
{
return false;
}
return isFluidEqual((FluidStack) o);
}
}

View File

@@ -0,0 +1,357 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nullable;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.fluids.capability.FluidTankPropertiesWrapper;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
/**
* Reference implementation of {@link IFluidTank}. Use/extend this or implement your own.
*/
public class FluidTank implements IFluidTank, IFluidHandler
{
@Nullable
protected FluidStack fluid;
protected int capacity;
protected TileEntity tile;
protected boolean canFill = true;
protected boolean canDrain = true;
protected IFluidTankProperties[] tankProperties;
public FluidTank(int capacity)
{
this(null, capacity);
}
public FluidTank(@Nullable FluidStack fluidStack, int capacity)
{
this.fluid = fluidStack;
this.capacity = capacity;
}
public FluidTank(Fluid fluid, int amount, int capacity)
{
this(new FluidStack(fluid, amount), capacity);
}
public FluidTank readFromNBT(NBTTagCompound nbt)
{
if (!nbt.hasKey("Empty"))
{
FluidStack fluid = FluidStack.loadFluidStackFromNBT(nbt);
setFluid(fluid);
}
else
{
setFluid(null);
}
return this;
}
public NBTTagCompound writeToNBT(NBTTagCompound nbt)
{
if (fluid != null)
{
fluid.writeToNBT(nbt);
}
else
{
nbt.setString("Empty", "");
}
return nbt;
}
/* IFluidTank */
@Override
@Nullable
public FluidStack getFluid()
{
return fluid;
}
public void setFluid(@Nullable FluidStack fluid)
{
this.fluid = fluid;
}
@Override
public int getFluidAmount()
{
if (fluid == null)
{
return 0;
}
return fluid.amount;
}
@Override
public int getCapacity()
{
return capacity;
}
public void setCapacity(int capacity)
{
this.capacity = capacity;
}
public void setTileEntity(TileEntity tile)
{
this.tile = tile;
}
@Override
public FluidTankInfo getInfo()
{
return new FluidTankInfo(this);
}
@Override
public IFluidTankProperties[] getTankProperties()
{
if (this.tankProperties == null)
{
this.tankProperties = new IFluidTankProperties[] { new FluidTankPropertiesWrapper(this) };
}
return this.tankProperties;
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
if (!canFillFluidType(resource))
{
return 0;
}
return fillInternal(resource, doFill);
}
/**
* Use this method to bypass the restrictions from {@link #canFillFluidType(FluidStack)}
* Meant for use by the owner of the tank when they have {@link #canFill() set to false}.
*/
public int fillInternal(FluidStack resource, boolean doFill)
{
if (resource == null || resource.amount <= 0)
{
return 0;
}
if (!doFill)
{
if (fluid == null)
{
return Math.min(capacity, resource.amount);
}
if (!fluid.isFluidEqual(resource))
{
return 0;
}
return Math.min(capacity - fluid.amount, resource.amount);
}
if (fluid == null)
{
fluid = new FluidStack(resource, Math.min(capacity, resource.amount));
onContentsChanged();
if (tile != null)
{
FluidEvent.fireEvent(new FluidEvent.FluidFillingEvent(fluid, tile.getWorld(), tile.getPos(), this, fluid.amount));
}
return fluid.amount;
}
if (!fluid.isFluidEqual(resource))
{
return 0;
}
int filled = capacity - fluid.amount;
if (resource.amount < filled)
{
fluid.amount += resource.amount;
filled = resource.amount;
}
else
{
fluid.amount = capacity;
}
onContentsChanged();
if (tile != null)
{
FluidEvent.fireEvent(new FluidEvent.FluidFillingEvent(fluid, tile.getWorld(), tile.getPos(), this, filled));
}
return filled;
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (!canDrainFluidType(getFluid()))
{
return null;
}
return drainInternal(resource, doDrain);
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (!canDrainFluidType(fluid))
{
return null;
}
return drainInternal(maxDrain, doDrain);
}
/**
* Use this method to bypass the restrictions from {@link #canDrainFluidType(FluidStack)}
* Meant for use by the owner of the tank when they have {@link #canDrain()} set to false}.
*/
@Nullable
public FluidStack drainInternal(FluidStack resource, boolean doDrain)
{
if (resource == null || !resource.isFluidEqual(getFluid()))
{
return null;
}
return drainInternal(resource.amount, doDrain);
}
/**
* Use this method to bypass the restrictions from {@link #canDrainFluidType(FluidStack)}
* Meant for use by the owner of the tank when they have {@link #canDrain()} set to false}.
*/
@Nullable
public FluidStack drainInternal(int maxDrain, boolean doDrain)
{
if (fluid == null || maxDrain <= 0)
{
return null;
}
int drained = maxDrain;
if (fluid.amount < drained)
{
drained = fluid.amount;
}
FluidStack stack = new FluidStack(fluid, drained);
if (doDrain)
{
fluid.amount -= drained;
if (fluid.amount <= 0)
{
fluid = null;
}
onContentsChanged();
if (tile != null)
{
FluidEvent.fireEvent(new FluidEvent.FluidDrainingEvent(fluid, tile.getWorld(), tile.getPos(), this, drained));
}
}
return stack;
}
/**
* Whether this tank can be filled with {@link IFluidHandler}
*
* @see IFluidTankProperties#canFill()
*/
public boolean canFill()
{
return canFill;
}
/**
* Whether this tank can be drained with {@link IFluidHandler}
*
* @see IFluidTankProperties#canDrain()
*/
public boolean canDrain()
{
return canDrain;
}
/**
* Set whether this tank can be filled with {@link IFluidHandler}
*
* @see IFluidTankProperties#canFill()
*/
public void setCanFill(boolean canFill)
{
this.canFill = canFill;
}
/**
* Set whether this tank can be drained with {@link IFluidHandler}
*
* @see IFluidTankProperties#canDrain()
*/
public void setCanDrain(boolean canDrain)
{
this.canDrain = canDrain;
}
/**
* Returns true if the tank can be filled with this type of fluid.
* Used as a filter for fluid types.
* Does not consider the current contents or capacity of the tank,
* only whether it could ever fill with this type of fluid.
*
* @see IFluidTankProperties#canFillFluidType(FluidStack)
*/
public boolean canFillFluidType(FluidStack fluid)
{
return canFill();
}
/**
* Returns true if the tank can drain out this type of fluid.
* Used as a filter for fluid types.
* Does not consider the current contents or capacity of the tank,
* only whether it could ever drain out this type of fluid.
*
* @see IFluidTankProperties#canDrainFluidType(FluidStack)
*/
public boolean canDrainFluidType(@Nullable FluidStack fluid)
{
return fluid != null && canDrain();
}
protected void onContentsChanged()
{
}
}

View File

@@ -0,0 +1,44 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nullable;
/**
* Wrapper class used to encapsulate information about an IFluidTank.
*/
public final class FluidTankInfo
{
@Nullable
public final FluidStack fluid;
public final int capacity;
public FluidTankInfo(@Nullable FluidStack fluid, int capacity)
{
this.fluid = fluid;
this.capacity = capacity;
}
public FluidTankInfo(IFluidTank tank)
{
this.fluid = tank.getFluid();
this.capacity = tank.getCapacity();
}
}

View File

@@ -0,0 +1,758 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import net.minecraft.block.Block;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemBucket;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.fluids.capability.wrappers.BlockLiquidWrapper;
import net.minecraftforge.fluids.capability.wrappers.BlockWrapper;
import net.minecraftforge.fluids.capability.wrappers.FluidBlockWrapper;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.ItemHandlerHelper;
public class FluidUtil
{
private FluidUtil()
{
}
/**
* Used to handle the common case of a player holding a fluid item and right-clicking on a fluid handler block.
* First it tries to fill the item from the block,
* if that action fails then it tries to drain the item into the block.
* Automatically updates the item in the player's hand and stashes any extra items created.
*
* @param player The player doing the interaction between the item and fluid handler block.
* @param hand The player's hand that is holding an item that should interact with the fluid handler block.
* @param world The world that contains the fluid handler block.
* @param pos The position of the fluid handler block in the world.
* @param side The side of the block to interact with. May be null.
* @return true if the interaction succeeded and updated the item held by the player, false otherwise.
*/
public static boolean interactWithFluidHandler(@Nonnull EntityPlayer player, @Nonnull EnumHand hand, @Nonnull World world, @Nonnull BlockPos pos, @Nullable EnumFacing side)
{
Preconditions.checkNotNull(world);
Preconditions.checkNotNull(pos);
IFluidHandler blockFluidHandler = getFluidHandler(world, pos, side);
return blockFluidHandler != null && interactWithFluidHandler(player, hand, blockFluidHandler);
}
/**
* Used to handle the common case of a player holding a fluid item and right-clicking on a fluid handler.
* First it tries to fill the item from the handler,
* if that action fails then it tries to drain the item into the handler.
* Automatically updates the item in the player's hand and stashes any extra items created.
*
* @param player The player doing the interaction between the item and fluid handler.
* @param hand The player's hand that is holding an item that should interact with the fluid handler.
* @param handler The fluid handler.
* @return true if the interaction succeeded and updated the item held by the player, false otherwise.
*/
public static boolean interactWithFluidHandler(@Nonnull EntityPlayer player, @Nonnull EnumHand hand, @Nonnull IFluidHandler handler)
{
Preconditions.checkNotNull(player);
Preconditions.checkNotNull(hand);
Preconditions.checkNotNull(handler);
ItemStack heldItem = player.getHeldItem(hand);
if (!heldItem.isEmpty())
{
IItemHandler playerInventory = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, null);
if (playerInventory != null)
{
FluidActionResult fluidActionResult = tryFillContainerAndStow(heldItem, handler, playerInventory, Integer.MAX_VALUE, player, true);
if (!fluidActionResult.isSuccess())
{
fluidActionResult = tryEmptyContainerAndStow(heldItem, handler, playerInventory, Integer.MAX_VALUE, player, true);
}
if (fluidActionResult.isSuccess())
{
player.setHeldItem(hand, fluidActionResult.getResult());
return true;
}
}
}
return false;
}
/**
* Fill a container from the given fluidSource.
*
* @param container The container to be filled. Will not be modified.
* Separate handling must be done to reduce the stack size, stow containers, etc, on success.
* See {@link #tryFillContainerAndStow(ItemStack, IFluidHandler, IItemHandler, int, EntityPlayer, boolean)}.
* @param fluidSource The fluid handler to be drained.
* @param maxAmount The largest amount of fluid that should be transferred.
* @param player The player to make the filling noise. Pass null for no noise.
* @param doFill true if the container should actually be filled, false if it should be simulated.
* @return a {@link FluidActionResult} holding the filled container if successful.
*/
@Nonnull
public static FluidActionResult tryFillContainer(@Nonnull ItemStack container, IFluidHandler fluidSource, int maxAmount, @Nullable EntityPlayer player, boolean doFill)
{
ItemStack containerCopy = ItemHandlerHelper.copyStackWithSize(container, 1); // do not modify the input
IFluidHandlerItem containerFluidHandler = getFluidHandler(containerCopy);
if (containerFluidHandler != null)
{
FluidStack simulatedTransfer = tryFluidTransfer(containerFluidHandler, fluidSource, maxAmount, false);
if (simulatedTransfer != null)
{
if (doFill)
{
tryFluidTransfer(containerFluidHandler, fluidSource, maxAmount, true);
if (player != null)
{
SoundEvent soundevent = simulatedTransfer.getFluid().getFillSound(simulatedTransfer);
player.world.playSound(null, player.posX, player.posY + 0.5, player.posZ, soundevent, SoundCategory.BLOCKS, 1.0F, 1.0F);
}
}
else
{
containerFluidHandler.fill(simulatedTransfer, true);
}
ItemStack resultContainer = containerFluidHandler.getContainer();
return new FluidActionResult(resultContainer);
}
}
return FluidActionResult.FAILURE;
}
/**
* Takes a filled container and tries to empty it into the given tank.
*
* @param container The filled container. Will not be modified.
* Separate handling must be done to reduce the stack size, stow containers, etc, on success.
* See {@link #tryEmptyContainerAndStow(ItemStack, IFluidHandler, IItemHandler, int, EntityPlayer, boolean)}.
* @param fluidDestination The fluid handler to be filled by the container.
* @param maxAmount The largest amount of fluid that should be transferred.
* @param player Player for making the bucket drained sound. Pass null for no noise.
* @param doDrain true if the container should actually be drained, false if it should be simulated.
* @return a {@link FluidActionResult} holding the empty container if the fluid handler was filled.
* NOTE If the container is consumable, the empty container will be null on success.
*/
@Nonnull
public static FluidActionResult tryEmptyContainer(@Nonnull ItemStack container, IFluidHandler fluidDestination, int maxAmount, @Nullable EntityPlayer player, boolean doDrain)
{
ItemStack containerCopy = ItemHandlerHelper.copyStackWithSize(container, 1); // do not modify the input
IFluidHandlerItem containerFluidHandler = getFluidHandler(containerCopy);
if (containerFluidHandler != null)
{
if (doDrain)
{
FluidStack transfer = tryFluidTransfer(fluidDestination, containerFluidHandler, maxAmount, true);
if (transfer != null)
{
if (player != null)
{
SoundEvent soundevent = transfer.getFluid().getEmptySound(transfer);
player.world.playSound(null, player.posX, player.posY + 0.5, player.posZ, soundevent, SoundCategory.BLOCKS, 1.0F, 1.0F);
}
ItemStack resultContainer = containerFluidHandler.getContainer();
return new FluidActionResult(resultContainer);
}
}
else
{
FluidStack simulatedTransfer = tryFluidTransfer(fluidDestination, containerFluidHandler, maxAmount, false);
if (simulatedTransfer != null)
{
containerFluidHandler.drain(simulatedTransfer, true);
ItemStack resultContainer = containerFluidHandler.getContainer();
return new FluidActionResult(resultContainer);
}
}
}
return FluidActionResult.FAILURE;
}
/**
* Takes an Fluid Container Item and tries to fill it from the given tank.
* If the player is in creative mode, the container will not be modified on success, and no additional items created.
* If the input itemstack has a stacksize > 1 it will stow the filled container in the given inventory.
* If the inventory does not accept it, it will be given to the player or dropped at the players feet.
* If player is null in this case, the action will be aborted.
*
* @param container The Fluid Container ItemStack to fill.
* Will not be modified directly, if modifications are necessary a modified copy is returned in the result.
* @param fluidSource The fluid source to fill from
* @param inventory An inventory where any additionally created item (filled container if multiple empty are present) are put
* @param maxAmount Maximum amount of fluid to take from the tank.
* @param player The player that gets the items the inventory can't take.
* Can be null, only used if the inventory cannot take the filled stack.
* @return a {@link FluidActionResult} holding the result and the resulting container. The resulting container is empty on failure.
* @deprecated use {@link #tryFillContainerAndStow(ItemStack, IFluidHandler, IItemHandler, int, EntityPlayer, boolean)}
*/
@Deprecated // TODO remove in 1.13
@Nonnull
public static FluidActionResult tryFillContainerAndStow(@Nonnull ItemStack container, IFluidHandler fluidSource, IItemHandler inventory, int maxAmount, @Nullable EntityPlayer player)
{
return tryFillContainerAndStow(container, fluidSource, inventory, maxAmount, player, true);
}
/**
* Takes an Fluid Container Item and tries to fill it from the given tank.
* If the player is in creative mode, the container will not be modified on success, and no additional items created.
* If the input itemstack has a stacksize > 1 it will stow the filled container in the given inventory.
* If the inventory does not accept it, it will be given to the player or dropped at the players feet.
* If player is null in this case, the action will be aborted.
*
* @param container The Fluid Container ItemStack to fill.
* Will not be modified directly, if modifications are necessary a modified copy is returned in the result.
* @param fluidSource The fluid source to fill from
* @param inventory An inventory where any additionally created item (filled container if multiple empty are present) are put
* @param maxAmount Maximum amount of fluid to take from the tank.
* @param player The player that gets the items the inventory can't take.
* Can be null, only used if the inventory cannot take the filled stack.
* @param doFill true if the container should actually be filled, false if it should be simulated.
* @return a {@link FluidActionResult} holding the result and the resulting container. The resulting container is empty on failure.
*/
@Nonnull
public static FluidActionResult tryFillContainerAndStow(@Nonnull ItemStack container, IFluidHandler fluidSource, IItemHandler inventory, int maxAmount, @Nullable EntityPlayer player, boolean doFill)
{
if (container.isEmpty())
{
return FluidActionResult.FAILURE;
}
if (player != null && player.capabilities.isCreativeMode)
{
FluidActionResult filledReal = tryFillContainer(container, fluidSource, maxAmount, player, doFill);
if (filledReal.isSuccess())
{
return new FluidActionResult(container); // creative mode: item does not change
}
}
else if (container.getCount() == 1) // don't need to stow anything, just fill the container stack
{
FluidActionResult filledReal = tryFillContainer(container, fluidSource, maxAmount, player, doFill);
if (filledReal.isSuccess())
{
return filledReal;
}
}
else
{
FluidActionResult filledSimulated = tryFillContainer(container, fluidSource, maxAmount, player, false);
if (filledSimulated.isSuccess())
{
// check if we can give the itemStack to the inventory
ItemStack remainder = ItemHandlerHelper.insertItemStacked(inventory, filledSimulated.getResult(), true);
if (remainder.isEmpty() || player != null)
{
FluidActionResult filledReal = tryFillContainer(container, fluidSource, maxAmount, player, doFill);
remainder = ItemHandlerHelper.insertItemStacked(inventory, filledReal.getResult(), !doFill);
// give it to the player or drop it at their feet
if (!remainder.isEmpty() && player != null && doFill)
{
ItemHandlerHelper.giveItemToPlayer(player, remainder);
}
ItemStack containerCopy = container.copy();
containerCopy.shrink(1);
return new FluidActionResult(containerCopy);
}
}
}
return FluidActionResult.FAILURE;
}
/**
* Takes an Fluid Container Item, tries to empty it into the fluid handler, and stows it in the given inventory.
* If the player is in creative mode, the container will not be modified on success, and no additional items created.
* If the input itemstack has a stacksize > 1 it will stow the emptied container in the given inventory.
* If the inventory does not accept the emptied container, it will be given to the player or dropped at the players feet.
* If player is null in this case, the action will be aborted.
*
* @param container The filled Fluid Container Itemstack to empty.
* Will not be modified directly, if modifications are necessary a modified copy is returned in the result.
* @param fluidDestination The fluid destination to fill from the fluid container.
* @param inventory An inventory where any additionally created item (filled container if multiple empty are present) are put
* @param maxAmount Maximum amount of fluid to take from the tank.
* @param player The player that gets the items the inventory can't take. Can be null, only used if the inventory cannot take the filled stack.
* @return a {@link FluidActionResult} holding the result and the resulting container. The resulting container is empty on failure.
* @deprecated use {@link #tryEmptyContainerAndStow(ItemStack, IFluidHandler, IItemHandler, int, EntityPlayer, boolean)}
*/
@Deprecated // TODO: remove in 1.13
@Nonnull
public static FluidActionResult tryEmptyContainerAndStow(@Nonnull ItemStack container, IFluidHandler fluidDestination, IItemHandler inventory, int maxAmount, @Nullable EntityPlayer player)
{
return tryEmptyContainerAndStow(container, fluidDestination, inventory, maxAmount, player, true);
}
/**
* Takes an Fluid Container Item, tries to empty it into the fluid handler, and stows it in the given inventory.
* If the player is in creative mode, the container will not be modified on success, and no additional items created.
* If the input itemstack has a stacksize > 1 it will stow the emptied container in the given inventory.
* If the inventory does not accept the emptied container, it will be given to the player or dropped at the players feet.
* If player is null in this case, the action will be aborted.
*
* @param container The filled Fluid Container Itemstack to empty.
* Will not be modified directly, if modifications are necessary a modified copy is returned in the result.
* @param fluidDestination The fluid destination to fill from the fluid container.
* @param inventory An inventory where any additionally created item (filled container if multiple empty are present) are put
* @param maxAmount Maximum amount of fluid to take from the tank.
* @param player The player that gets the items the inventory can't take. Can be null, only used if the inventory cannot take the filled stack.
* @param doDrain true if the container should actually be drained, false if it should be simulated.
* @return a {@link FluidActionResult} holding the result and the resulting container. The resulting container is empty on failure.
*/
@Nonnull
public static FluidActionResult tryEmptyContainerAndStow(@Nonnull ItemStack container, IFluidHandler fluidDestination, IItemHandler inventory, int maxAmount, @Nullable EntityPlayer player, boolean doDrain)
{
if (container.isEmpty())
{
return FluidActionResult.FAILURE;
}
if (player != null && player.capabilities.isCreativeMode)
{
FluidActionResult emptiedReal = tryEmptyContainer(container, fluidDestination, maxAmount, player, doDrain);
if (emptiedReal.isSuccess())
{
return new FluidActionResult(container); // creative mode: item does not change
}
}
else if (container.getCount() == 1) // don't need to stow anything, just fill and edit the container stack
{
FluidActionResult emptiedReal = tryEmptyContainer(container, fluidDestination, maxAmount, player, doDrain);
if (emptiedReal.isSuccess())
{
return emptiedReal;
}
}
else
{
FluidActionResult emptiedSimulated = tryEmptyContainer(container, fluidDestination, maxAmount, player, false);
if (emptiedSimulated.isSuccess())
{
// check if we can give the itemStack to the inventory
ItemStack remainder = ItemHandlerHelper.insertItemStacked(inventory, emptiedSimulated.getResult(), true);
if (remainder.isEmpty() || player != null)
{
FluidActionResult emptiedReal = tryEmptyContainer(container, fluidDestination, maxAmount, player, doDrain);
remainder = ItemHandlerHelper.insertItemStacked(inventory, emptiedReal.getResult(), !doDrain);
// give it to the player or drop it at their feet
if (!remainder.isEmpty() && player != null && doDrain)
{
ItemHandlerHelper.giveItemToPlayer(player, remainder);
}
ItemStack containerCopy = container.copy();
containerCopy.shrink(1);
return new FluidActionResult(containerCopy);
}
}
}
return FluidActionResult.FAILURE;
}
/**
* Fill a destination fluid handler from a source fluid handler with a max amount.
* To specify a fluid to transfer instead of max amount, use {@link #tryFluidTransfer(IFluidHandler, IFluidHandler, FluidStack, boolean)}
* To transfer as much as possible, use {@link Integer#MAX_VALUE} for maxAmount.
*
* @param fluidDestination The fluid handler to be filled.
* @param fluidSource The fluid handler to be drained.
* @param maxAmount The largest amount of fluid that should be transferred.
* @param doTransfer True if the transfer should actually be done, false if it should be simulated.
* @return the fluidStack that was transferred from the source to the destination. null on failure.
*/
@Nullable
public static FluidStack tryFluidTransfer(IFluidHandler fluidDestination, IFluidHandler fluidSource, int maxAmount, boolean doTransfer)
{
FluidStack drainable = fluidSource.drain(maxAmount, false);
if (drainable != null && drainable.amount > 0)
{
return tryFluidTransfer_Internal(fluidDestination, fluidSource, drainable, doTransfer);
}
return null;
}
/**
* Fill a destination fluid handler from a source fluid handler using a specific fluid.
* To specify a max amount to transfer instead of specific fluid, use {@link #tryFluidTransfer(IFluidHandler, IFluidHandler, int, boolean)}
* To transfer as much as possible, use {@link Integer#MAX_VALUE} for resource.amount.
*
* @param fluidDestination The fluid handler to be filled.
* @param fluidSource The fluid handler to be drained.
* @param resource The fluid that should be transferred. Amount represents the maximum amount to transfer.
* @param doTransfer True if the transfer should actually be done, false if it should be simulated.
* @return the fluidStack that was transferred from the source to the destination. null on failure.
*/
@Nullable
public static FluidStack tryFluidTransfer(IFluidHandler fluidDestination, IFluidHandler fluidSource, FluidStack resource, boolean doTransfer)
{
FluidStack drainable = fluidSource.drain(resource, false);
if (drainable != null && drainable.amount > 0 && resource.isFluidEqual(drainable))
{
return tryFluidTransfer_Internal(fluidDestination, fluidSource, drainable, doTransfer);
}
return null;
}
/**
* Internal method for filling a destination fluid handler from a source fluid handler using a specific fluid.
* Assumes that "drainable" can be drained from "fluidSource".
*
* Modders: Instead of this method, use {@link #tryFluidTransfer(IFluidHandler, IFluidHandler, FluidStack, boolean)}
* or {@link #tryFluidTransfer(IFluidHandler, IFluidHandler, int, boolean)}.
*/
@Nullable
private static FluidStack tryFluidTransfer_Internal(IFluidHandler fluidDestination, IFluidHandler fluidSource, FluidStack drainable, boolean doTransfer)
{
int fillableAmount = fluidDestination.fill(drainable, false);
if (fillableAmount > 0)
{
if (doTransfer)
{
FluidStack drained = fluidSource.drain(fillableAmount, true);
if (drained != null)
{
drained.amount = fluidDestination.fill(drained, true);
return drained;
}
}
else
{
drainable.amount = fillableAmount;
return drainable;
}
}
return null;
}
/**
* Helper method to get an {@link IFluidHandlerItem} for an itemStack.
*
* The itemStack passed in here WILL be modified, the {@link IFluidHandlerItem} acts on it directly.
* Some {@link IFluidHandlerItem} will change the item entirely, always use {@link IFluidHandlerItem#getContainer()}
* after using the fluid handler to get the resulting item back.
*
* Note that the itemStack MUST have a stackSize of 1 if you want to fill or drain it.
* You can't fill or drain multiple items at once, if you do then liquid is multiplied or destroyed.
*
* Vanilla buckets will be converted to universal buckets if they are enabled.
*
* Returns null if the itemStack passed in does not have a fluid handler.
*/
@Nullable
public static IFluidHandlerItem getFluidHandler(@Nonnull ItemStack itemStack)
{
if (itemStack.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null))
{
return itemStack.getCapability(CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY, null);
}
else
{
return null;
}
}
/**
* Helper method to get the fluid contained in an itemStack
*/
@Nullable
public static FluidStack getFluidContained(@Nonnull ItemStack container)
{
if (!container.isEmpty())
{
container = ItemHandlerHelper.copyStackWithSize(container, 1);
IFluidHandlerItem fluidHandler = getFluidHandler(container);
if (fluidHandler != null)
{
return fluidHandler.drain(Integer.MAX_VALUE, false);
}
}
return null;
}
/**
* Helper method to get an IFluidHandler for at a block position.
*
* Returns null if there is no valid fluid handler.
*/
@Nullable
public static IFluidHandler getFluidHandler(World world, BlockPos blockPos, @Nullable EnumFacing side)
{
IBlockState state = world.getBlockState(blockPos);
Block block = state.getBlock();
if (block.hasTileEntity(state))
{
TileEntity tileEntity = world.getTileEntity(blockPos);
if (tileEntity != null && tileEntity.hasCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side))
{
return tileEntity.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, side);
}
}
if (block instanceof IFluidBlock)
{
return new FluidBlockWrapper((IFluidBlock) block, world, blockPos);
}
else if (block instanceof BlockLiquid)
{
return new BlockLiquidWrapper((BlockLiquid) block, world, blockPos);
}
return null;
}
/**
* Attempts to pick up a fluid in the world and put it in an empty container item.
*
* @param emptyContainer The empty container to fill.
* Will not be modified directly, if modifications are necessary a modified copy is returned in the result.
* @param playerIn The player filling the container. Optional.
* @param worldIn The world the fluid is in.
* @param pos The position of the fluid in the world.
* @param side The side of the fluid that is being drained.
* @return a {@link FluidActionResult} holding the result and the resulting container.
*/
@Nonnull
public static FluidActionResult tryPickUpFluid(@Nonnull ItemStack emptyContainer, @Nullable EntityPlayer playerIn, World worldIn, BlockPos pos, EnumFacing side)
{
if (emptyContainer.isEmpty() || worldIn == null || pos == null)
{
return FluidActionResult.FAILURE;
}
IBlockState state = worldIn.getBlockState(pos);
Block block = state.getBlock();
if (block instanceof IFluidBlock || block instanceof BlockLiquid)
{
IFluidHandler targetFluidHandler = getFluidHandler(worldIn, pos, side);
if (targetFluidHandler != null)
{
return tryFillContainer(emptyContainer, targetFluidHandler, Integer.MAX_VALUE, playerIn, true);
}
}
return FluidActionResult.FAILURE;
}
/**
* ItemStack version of {@link #tryPlaceFluid(EntityPlayer, World, BlockPos, IFluidHandler, FluidStack)}.
* Use the returned {@link FluidActionResult} to update the container ItemStack.
*
* @param player Player who places the fluid. May be null for blocks like dispensers.
* @param world World to place the fluid in
* @param pos The position in the world to place the fluid block
* @param container The fluid container holding the fluidStack to place
* @param resource The fluidStack to place
* @return the container's ItemStack with the remaining amount of fluid if the placement was successful, null otherwise
*/
@Nonnull
public static FluidActionResult tryPlaceFluid(@Nullable EntityPlayer player, World world, BlockPos pos, @Nonnull ItemStack container, FluidStack resource)
{
ItemStack containerCopy = ItemHandlerHelper.copyStackWithSize(container, 1); // do not modify the input
IFluidHandlerItem containerFluidHandler = getFluidHandler(containerCopy);
if (containerFluidHandler != null && tryPlaceFluid(player, world, pos, containerFluidHandler, resource))
{
return new FluidActionResult(containerFluidHandler.getContainer());
}
return FluidActionResult.FAILURE;
}
/**
* Tries to place a fluid resource into the world as a block and drains the fluidSource.
* Makes a fluid emptying or vaporization sound when successful.
* Honors the amount of fluid contained by the used container.
* Checks if water-like fluids should vaporize like in the nether.
*
* Modeled after {@link ItemBucket#tryPlaceContainedLiquid(EntityPlayer, World, BlockPos)}
*
* @param player Player who places the fluid. May be null for blocks like dispensers.
* @param world World to place the fluid in
* @param pos The position in the world to place the fluid block
* @param fluidSource The fluid source holding the fluidStack to place
* @param resource The fluidStack to place.
* @return true if the placement was successful, false otherwise
*/
public static boolean tryPlaceFluid(@Nullable EntityPlayer player, World world, BlockPos pos, IFluidHandler fluidSource, FluidStack resource)
{
if (world == null || resource == null || pos == null)
{
return false;
}
Fluid fluid = resource.getFluid();
if (fluid == null || !fluid.canBePlacedInWorld())
{
return false;
}
if (fluidSource.drain(resource, false) == null)
{
return false;
}
// check that we can place the fluid at the destination
IBlockState destBlockState = world.getBlockState(pos);
Material destMaterial = destBlockState.getMaterial();
boolean isDestNonSolid = !destMaterial.isSolid();
boolean isDestReplaceable = destBlockState.getBlock().isReplaceable(world, pos);
if (!world.isAirBlock(pos) && !isDestNonSolid && !isDestReplaceable)
{
return false; // Non-air, solid, unreplacable block. We can't put fluid here.
}
if (world.provider.doesWaterVaporize() && fluid.doesVaporize(resource))
{
FluidStack result = fluidSource.drain(resource, true);
if (result != null)
{
result.getFluid().vaporize(player, world, pos, result);
return true;
}
}
else
{
// This fluid handler places the fluid block when filled
IFluidHandler handler = getFluidBlockHandler(fluid, world, pos);
FluidStack result = tryFluidTransfer(handler, fluidSource, resource, true);
if (result != null)
{
SoundEvent soundevent = resource.getFluid().getEmptySound(resource);
world.playSound(player, pos, soundevent, SoundCategory.BLOCKS, 1.0F, 1.0F);
return true;
}
}
return false;
}
/**
* Internal method for getting a fluid block handler for placing a fluid.
*
* Modders: Instead of this method, use {@link #tryPlaceFluid(EntityPlayer, World, BlockPos, ItemStack, FluidStack)}
* or {@link #tryPlaceFluid(EntityPlayer, World, BlockPos, IFluidHandler, FluidStack)}
*/
private static IFluidHandler getFluidBlockHandler(Fluid fluid, World world, BlockPos pos)
{
Block block = fluid.getBlock();
if (block instanceof IFluidBlock)
{
return new FluidBlockWrapper((IFluidBlock) block, world, pos);
}
else if (block instanceof BlockLiquid)
{
return new BlockLiquidWrapper((BlockLiquid) block, world, pos);
}
else
{
return new BlockWrapper(block, world, pos);
}
}
/**
* Destroys a block when a fluid is placed in the same position.
* Modeled after {@link ItemBucket#tryPlaceContainedLiquid(EntityPlayer, World, BlockPos)}
*
* This is a helper method for implementing {@link IFluidBlock#place(World, BlockPos, FluidStack, boolean)}.
*
* @param world the world that the fluid will be placed in
* @param pos the location that the fluid will be placed
*/
public static void destroyBlockOnFluidPlacement(World world, BlockPos pos)
{
if (!world.isRemote)
{
IBlockState destBlockState = world.getBlockState(pos);
Material destMaterial = destBlockState.getMaterial();
boolean isDestNonSolid = !destMaterial.isSolid();
boolean isDestReplaceable = destBlockState.getBlock().isReplaceable(world, pos);
if ((isDestNonSolid || isDestReplaceable) && !destMaterial.isLiquid())
{
world.destroyBlock(pos, true);
}
}
}
/**
* @param fluidStack contents used to fill the bucket.
* FluidStack is used instead of Fluid to preserve fluid NBT, the amount is ignored.
* @return a filled vanilla bucket or filled universal bucket.
* Returns empty itemStack if none of the enabled buckets can hold the fluid.
*/
@Nonnull
public static ItemStack getFilledBucket(@Nonnull FluidStack fluidStack)
{
Fluid fluid = fluidStack.getFluid();
if (fluidStack.tag == null || fluidStack.tag.hasNoTags())
{
if (fluid == FluidRegistry.WATER)
{
return new ItemStack(Items.WATER_BUCKET);
}
else if (fluid == FluidRegistry.LAVA)
{
return new ItemStack(Items.LAVA_BUCKET);
}
else if (fluid.getName().equals("milk"))
{
return new ItemStack(Items.MILK_BUCKET);
}
}
if (FluidRegistry.isUniversalBucketEnabled() && FluidRegistry.getBucketFluids().contains(fluid))
{
UniversalBucket bucket = ForgeModContainer.getInstance().universalBucket;
ItemStack filledBucket = new ItemStack(bucket);
FluidStack fluidContents = new FluidStack(fluidStack, bucket.getCapacity());
NBTTagCompound tag = new NBTTagCompound();
fluidContents.writeToNBT(tag);
filledBucket.setTagCompound(tag);
return filledBucket;
}
return ItemStack.EMPTY;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Implement this interface on Block classes which represent world-placeable Fluids.
*
* NOTE: Using/extending the reference implementations {@link BlockFluidBase} is encouraged.
*
*/
public interface IFluidBlock
{
/**
* Returns the Fluid associated with this Block.
*/
Fluid getFluid();
/**
* Attempts to place the block at a given position. The placed block's level will correspond
* to the provided fluid amount.
* This method should be called by fluid containers such as buckets, but it is recommended
* to use {@link FluidUtil}.
*
* @param world the world to place the block in
* @param pos the position to place the block at
* @param fluidStack the fluid stack to get the required data from
* @param doPlace if false, the placement will only be simulated
* @return the amount of fluid extracted from the provided stack to achieve some fluid level
*/
int place(World world, BlockPos pos, @Nonnull FluidStack fluidStack, boolean doPlace);
/**
* Attempt to drain the block. This method should be called by devices such as pumps.
*
* NOTE: The block is intended to handle its own state changes.
*
* @param doDrain
* If false, the drain will only be simulated.
* @return
*/
@Nullable
FluidStack drain(World world, BlockPos pos, boolean doDrain);
/**
* Check to see if a block can be drained. This method should be called by devices such as
* pumps.
*
* @return
*/
boolean canDrain(World world, BlockPos pos);
/**
* Returns the amount of a single block is filled. Value between 0 and 1.
* 1 meaning the entire 1x1x1 cube is full, 0 meaning completely empty.
*
* If the return value is negative. It will be treated as filling the block
* from the top down instead of bottom up.
*
* @return
*/
float getFilledPercentage(World world, BlockPos pos);
}

View File

@@ -0,0 +1,77 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import javax.annotation.Nullable;
/**
* A tank is the unit of interaction with Fluid inventories.
*
* A reference implementation can be found at {@link FluidTank}.
*/
public interface IFluidTank
{
/**
* @return FluidStack representing the fluid in the tank, null if the tank is empty.
*/
@Nullable
FluidStack getFluid();
/**
* @return Current amount of fluid in the tank.
*/
int getFluidAmount();
/**
* @return Capacity of this fluid tank.
*/
int getCapacity();
/**
* Returns a wrapper object {@link FluidTankInfo } containing the capacity of the tank and the
* FluidStack it holds.
*
* Should prevent manipulation of the IFluidTank. See {@link FluidTank}.
*
* @return State information for the IFluidTank.
*/
FluidTankInfo getInfo();
/**
*
* @param resource
* FluidStack attempting to fill the tank.
* @param doFill
* If false, the fill will only be simulated.
* @return Amount of fluid that was accepted by the tank.
*/
int fill(FluidStack resource, boolean doFill);
/**
*
* @param maxDrain
* Maximum amount of fluid to be removed from the container.
* @param doDrain
* If false, the drain will only be simulated.
* @return Amount of fluid that was removed from the tank.
*/
@Nullable
FluidStack drain(int maxDrain, boolean doDrain);
}

View File

@@ -0,0 +1,309 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids;
import net.minecraft.block.BlockDispenser;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.stats.StatList;
import net.minecraft.util.ActionResult;
import net.minecraft.util.EnumActionResult;
import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.text.translation.I18n;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.event.entity.player.FillBucketEvent;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.fluids.capability.wrappers.FluidBucketWrapper;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.items.ItemHandlerHelper;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A universal bucket that can hold any liquid
*/
public class UniversalBucket extends Item
{
private final int capacity; // how much the bucket holds
@Nonnull
private final ItemStack empty; // empty item to return and recognize when filling
private final boolean nbtSensitive;
public UniversalBucket()
{
this(Fluid.BUCKET_VOLUME, new ItemStack(Items.BUCKET), false);
}
/**
* @param capacity Capacity of the container
* @param empty Item used for filling with the bucket event and returned when emptied
* @param nbtSensitive Whether the empty item is NBT sensitive (usually true if empty and full are the same items)
*/
public UniversalBucket(int capacity, @Nonnull ItemStack empty, boolean nbtSensitive)
{
this.capacity = capacity;
this.empty = empty;
this.nbtSensitive = nbtSensitive;
this.setMaxStackSize(1);
this.setCreativeTab(CreativeTabs.MISC);
BlockDispenser.DISPENSE_BEHAVIOR_REGISTRY.putObject(this, DispenseFluidContainer.getInstance());
}
@Override
public boolean hasContainerItem(@Nonnull ItemStack stack)
{
return !getEmpty().isEmpty();
}
@Nonnull
@Override
public ItemStack getContainerItem(@Nonnull ItemStack itemStack)
{
if (!getEmpty().isEmpty())
{
// Create a copy such that the game can't mess with it
return getEmpty().copy();
}
return super.getContainerItem(itemStack);
}
/**
* returns a list of items with the same ID, but different meta (eg: dye returns 16 items)
*/
@Override
public void getSubItems(@Nullable CreativeTabs tab, @Nonnull NonNullList<ItemStack> subItems)
{
if (!this.isInCreativeTab(tab))
return;
for (Fluid fluid : FluidRegistry.getRegisteredFluids().values())
{
if (fluid != FluidRegistry.WATER && fluid != FluidRegistry.LAVA && !fluid.getName().equals("milk"))
{
// add all fluids that the bucket can be filled with
FluidStack fs = new FluidStack(fluid, getCapacity());
ItemStack stack = new ItemStack(this);
IFluidHandlerItem fluidHandler = new FluidBucketWrapper(stack);
if (fluidHandler.fill(fs, true) == fs.amount)
{
ItemStack filled = fluidHandler.getContainer();
subItems.add(filled);
}
}
}
}
@Override
@Nonnull
public String getItemStackDisplayName(@Nonnull ItemStack stack)
{
FluidStack fluidStack = getFluid(stack);
if (fluidStack == null)
{
if(!getEmpty().isEmpty())
{
return getEmpty().getDisplayName();
}
return super.getItemStackDisplayName(stack);
}
String unloc = this.getUnlocalizedNameInefficiently(stack);
if (I18n.canTranslate(unloc + "." + fluidStack.getFluid().getName()))
{
return I18n.translateToLocal(unloc + "." + fluidStack.getFluid().getName());
}
return I18n.translateToLocalFormatted(unloc + ".name", fluidStack.getLocalizedName());
}
/**
* Called when the equipped item is right clicked.
*/
@Override
@Nonnull
public ActionResult<ItemStack> onItemRightClick(@Nonnull World world, @Nonnull EntityPlayer player, @Nonnull EnumHand hand)
{
ItemStack itemstack = player.getHeldItem(hand);
FluidStack fluidStack = getFluid(itemstack);
// empty bucket shouldn't exist, do nothing since it should be handled by the bucket event
if (fluidStack == null)
{
return ActionResult.newResult(EnumActionResult.PASS, itemstack);
}
// clicked on a block?
RayTraceResult mop = this.rayTrace(world, player, false);
ActionResult<ItemStack> ret = ForgeEventFactory.onBucketUse(player, world, itemstack, mop);
if (ret != null) return ret;
if(mop == null || mop.typeOfHit != RayTraceResult.Type.BLOCK)
{
return ActionResult.newResult(EnumActionResult.PASS, itemstack);
}
BlockPos clickPos = mop.getBlockPos();
// can we place liquid there?
if (world.isBlockModifiable(player, clickPos))
{
// the block adjacent to the side we clicked on
BlockPos targetPos = clickPos.offset(mop.sideHit);
// can the player place there?
if (player.canPlayerEdit(targetPos, mop.sideHit, itemstack))
{
// try placing liquid
FluidActionResult result = FluidUtil.tryPlaceFluid(player, world, targetPos, itemstack, fluidStack);
if (result.isSuccess() && !player.capabilities.isCreativeMode)
{
// success!
player.addStat(StatList.getObjectUseStats(this));
itemstack.shrink(1);
ItemStack drained = result.getResult();
ItemStack emptyStack = !drained.isEmpty() ? drained.copy() : new ItemStack(this);
// check whether we replace the item or add the empty one to the inventory
if (itemstack.isEmpty())
{
return ActionResult.newResult(EnumActionResult.SUCCESS, emptyStack);
}
else
{
// add empty bucket to player inventory
ItemHandlerHelper.giveItemToPlayer(player, emptyStack);
return ActionResult.newResult(EnumActionResult.SUCCESS, itemstack);
}
}
}
}
// couldn't place liquid there2
return ActionResult.newResult(EnumActionResult.FAIL, itemstack);
}
@SubscribeEvent(priority = EventPriority.LOW) // low priority so other mods can handle their stuff first
public void onFillBucket(FillBucketEvent event)
{
if (event.getResult() != Event.Result.DEFAULT)
{
// event was already handled
return;
}
// not for us to handle
ItemStack emptyBucket = event.getEmptyBucket();
if (emptyBucket.isEmpty() ||
!emptyBucket.isItemEqual(getEmpty()) ||
(isNbtSensitive() && ItemStack.areItemStackTagsEqual(emptyBucket, getEmpty())))
{
return;
}
// needs to target a block
RayTraceResult target = event.getTarget();
if (target == null || target.typeOfHit != RayTraceResult.Type.BLOCK)
{
return;
}
World world = event.getWorld();
BlockPos pos = target.getBlockPos();
ItemStack singleBucket = emptyBucket.copy();
singleBucket.setCount(1);
FluidActionResult filledResult = FluidUtil.tryPickUpFluid(singleBucket, event.getEntityPlayer(), world, pos, target.sideHit);
if (filledResult.isSuccess())
{
event.setResult(Event.Result.ALLOW);
event.setFilledBucket(filledResult.getResult());
}
else
{
// cancel event, otherwise the vanilla minecraft ItemBucket would
// convert it into a water/lava bucket depending on the blocks material
event.setCanceled(true);
}
}
/**
* @deprecated use the NBT-sensitive version {@link FluidUtil#getFilledBucket(FluidStack)}
*/
@Deprecated
@Nonnull
public static ItemStack getFilledBucket(@Nonnull UniversalBucket item, Fluid fluid)
{
return FluidUtil.getFilledBucket(new FluidStack(fluid, Fluid.BUCKET_VOLUME));
}
@Nullable
public FluidStack getFluid(@Nonnull ItemStack container)
{
return FluidStack.loadFluidStackFromNBT(container.getTagCompound());
}
public int getCapacity()
{
return capacity;
}
@Nonnull
public ItemStack getEmpty()
{
return empty;
}
public boolean isNbtSensitive()
{
return nbtSensitive;
}
@Nullable
@Override
public String getCreatorModId(@Nonnull ItemStack itemStack)
{
FluidStack fluidStack = getFluid(itemStack);
String modId = FluidRegistry.getModId(fluidStack);
return modId != null ? modId : super.getCreatorModId(itemStack);
}
@Override
public ICapabilityProvider initCapabilities(@Nonnull ItemStack stack, NBTTagCompound nbt)
{
return new FluidBucketWrapper(stack);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStack;
public class CapabilityFluidHandler
{
@CapabilityInject(IFluidHandler.class)
public static Capability<IFluidHandler> FLUID_HANDLER_CAPABILITY = null;
@CapabilityInject(IFluidHandlerItem.class)
public static Capability<IFluidHandlerItem> FLUID_HANDLER_ITEM_CAPABILITY = null;
public static void register()
{
CapabilityManager.INSTANCE.register(IFluidHandler.class, new DefaultFluidHandlerStorage<>(), () -> new FluidTank(Fluid.BUCKET_VOLUME));
CapabilityManager.INSTANCE.register(IFluidHandlerItem.class, new DefaultFluidHandlerStorage<>(), () -> new FluidHandlerItemStack(new ItemStack(Items.BUCKET), Fluid.BUCKET_VOLUME));
}
private static class DefaultFluidHandlerStorage<T extends IFluidHandler> implements Capability.IStorage<T> {
@Override
public NBTBase writeNBT(Capability<T> capability, T instance, EnumFacing side)
{
if (!(instance instanceof IFluidTank))
throw new RuntimeException("IFluidHandler instance does not implement IFluidTank");
NBTTagCompound nbt = new NBTTagCompound();
IFluidTank tank = (IFluidTank) instance;
FluidStack fluid = tank.getFluid();
if (fluid != null)
{
fluid.writeToNBT(nbt);
}
else
{
nbt.setString("Empty", "");
}
nbt.setInteger("Capacity", tank.getCapacity());
return nbt;
}
@Override
public void readNBT(Capability<T> capability, T instance, EnumFacing side, NBTBase nbt)
{
if (!(instance instanceof FluidTank))
throw new RuntimeException("IFluidHandler instance is not instance of FluidTank");
NBTTagCompound tags = (NBTTagCompound) nbt;
FluidTank tank = (FluidTank) instance;
tank.setCapacity(tags.getInteger("Capacity"));
tank.readFromNBT(tags);
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import javax.annotation.Nullable;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
/**
* Basic implementation of {@link IFluidTankProperties}.
*/
public class FluidTankProperties implements IFluidTankProperties
{
public static FluidTankProperties[] convert(FluidTankInfo[] fluidTankInfos)
{
FluidTankProperties[] properties = new FluidTankProperties[fluidTankInfos.length];
for (int i = 0; i < fluidTankInfos.length; i++)
{
FluidTankInfo info = fluidTankInfos[i];
properties[i] = new FluidTankProperties(info.fluid, info.capacity);
}
return properties;
}
@Nullable
private final FluidStack contents;
private final int capacity;
private final boolean canFill;
private final boolean canDrain;
public FluidTankProperties(@Nullable FluidStack contents, int capacity)
{
this(contents, capacity, true, true);
}
public FluidTankProperties(@Nullable FluidStack contents, int capacity, boolean canFill, boolean canDrain)
{
this.contents = contents;
this.capacity = capacity;
this.canFill = canFill;
this.canDrain = canDrain;
}
@Nullable
@Override
public FluidStack getContents()
{
return contents == null ? null : contents.copy();
}
@Override
public int getCapacity()
{
return capacity;
}
@Override
public boolean canFill()
{
return canFill;
}
@Override
public boolean canDrain()
{
return canDrain;
}
@Override
public boolean canFillFluidType(FluidStack fluidStack)
{
return canFill;
}
@Override
public boolean canDrainFluidType(FluidStack fluidStack)
{
return canDrain;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import javax.annotation.Nullable;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTank;
/**
* Basic {@link IFluidTankProperties} wrapper for {@link FluidTank}.
*/
public class FluidTankPropertiesWrapper implements IFluidTankProperties
{
protected final FluidTank tank;
public FluidTankPropertiesWrapper(FluidTank tank)
{
this.tank = tank;
}
@Nullable
@Override
public FluidStack getContents()
{
FluidStack contents = tank.getFluid();
return contents == null ? null : contents.copy();
}
@Override
public int getCapacity()
{
return tank.getCapacity();
}
@Override
public boolean canFill()
{
return tank.canFill();
}
@Override
public boolean canDrain()
{
return tank.canDrain();
}
@Override
public boolean canFillFluidType(FluidStack fluidStack)
{
return tank.canFillFluidType(fluidStack);
}
@Override
public boolean canDrainFluidType(FluidStack fluidStack)
{
return tank.canDrainFluidType(fluidStack);
}
}

View File

@@ -0,0 +1,74 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import javax.annotation.Nullable;
import net.minecraftforge.fluids.*;
/**
* Implement this interface as a capability which should handle fluids, generally storing them in
* one or more internal {@link IFluidTank} objects.
* <p/>
* A reference implementation is provided {@link TileFluidHandler}.
*/
public interface IFluidHandler
{
/**
* Returns an array of objects which represent the internal tanks.
* These objects cannot be used to manipulate the internal tanks.
*
* @return Properties for the relevant internal tanks.
*/
IFluidTankProperties[] getTankProperties();
/**
* Fills fluid into internal tanks, distribution is left entirely to the IFluidHandler.
*
* @param resource FluidStack representing the Fluid and maximum amount of fluid to be filled.
* @param doFill If false, fill will only be simulated.
* @return Amount of resource that was (or would have been, if simulated) filled.
*/
int fill(FluidStack resource, boolean doFill);
/**
* Drains fluid out of internal tanks, distribution is left entirely to the IFluidHandler.
*
* @param resource FluidStack representing the Fluid and maximum amount of fluid to be drained.
* @param doDrain If false, drain will only be simulated.
* @return FluidStack representing the Fluid and amount that was (or would have been, if
* simulated) drained.
*/
@Nullable
FluidStack drain(FluidStack resource, boolean doDrain);
/**
* Drains fluid out of internal tanks, distribution is left entirely to the IFluidHandler.
* <p/>
* This method is not Fluid-sensitive.
*
* @param maxDrain Maximum amount of fluid to drain.
* @param doDrain If false, drain will only be simulated.
* @return FluidStack representing the Fluid and amount that was (or would have been, if
* simulated) drained.
*/
@Nullable
FluidStack drain(int maxDrain, boolean doDrain);
}

View File

@@ -0,0 +1,41 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.item.ItemStack;
/**
* ItemStacks handled by an {@link IFluidHandler} may change, so this class allows
* users of the fluid handler to get the container after it has been used.
*/
public interface IFluidHandlerItem extends IFluidHandler
{
/**
* Get the container currently acted on by this fluid handler.
* The ItemStack may be different from its initial state, in the case of fluid containers that have different items
* for their filled and empty states.
* May be an empty item if the container was drained and is consumable.
*/
@Nonnull
ItemStack getContainer();
}

View File

@@ -0,0 +1,84 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import javax.annotation.Nullable;
import net.minecraftforge.fluids.FluidStack;
/**
* Simplified Read-only Information about the internals of an {@link IFluidHandler}.
* This is useful for displaying information, and as hints for interacting with it.
* These properties are constant and do not depend on the fluid contents (except the contents themselves, of course).
*
* The information here may not tell the full story of how the tank actually works,
* for real fluid transactions you must use {@link IFluidHandler} to simulate, check, and then interact.
* None of the information in these properties is required to successfully interact using a {@link IFluidHandler}.
*/
public interface IFluidTankProperties
{
/**
* @return A copy of the fluid contents of this tank. May be null.
* To modify the contents, use {@link IFluidHandler}.
*/
@Nullable
FluidStack getContents();
/**
* @return The maximum amount of fluid this tank can hold, in millibuckets.
*/
int getCapacity();
/**
* Returns true if the tank can be filled at any time (even if it is currently full).
* It does not consider the contents or capacity of the tank.
*
* This value is constant. If the tank behavior is more complicated, returns true.
*/
boolean canFill();
/**
* Returns true if the tank can be drained at any time (even if it is currently empty).
* It does not consider the contents or capacity of the tank.
*
* This value is constant. If the tank behavior is more complicated, returns true.
*/
boolean canDrain();
/**
* Returns true if the tank can be filled with a specific type of fluid.
* Used as a filter for fluid types.
*
* Does not consider the current contents or capacity of the tank,
* only whether it could ever fill with this type of fluid.
* {@link FluidStack} is used here because fluid properties can depend on NBT, the amount is ignored.
*/
boolean canFillFluidType(FluidStack fluidStack);
/**
* Returns true if the tank can drain out this a specific of fluid.
* Used as a filter for fluid types.
*
* Does not consider the current contents or capacity of the tank,
* only whether it could ever drain out this type of fluid.
* {@link FluidStack} is used here because fluid properties can depend on NBT, the amount is ignored.
*/
boolean canDrainFluidType(FluidStack fluidStack);
}

View File

@@ -0,0 +1,53 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A simple fluid container, to replace the functionality of the old FluidContainerRegistry and IFluidContainerItem.
* This fluid container may be set so that is can only completely filled or empty. (binary)
* It may also be set so that it gets consumed when it is drained. (consumable)
*/
public class ItemFluidContainer extends Item
{
protected final int capacity;
/**
* @param capacity The maximum capacity of this fluid container.
*/
public ItemFluidContainer(int capacity)
{
this.capacity = capacity;
}
@Override
public ICapabilityProvider initCapabilities(@Nonnull ItemStack stack, @Nullable NBTTagCompound nbt)
{
return new FluidHandlerItemStack(stack, capacity);
}
}

View File

@@ -0,0 +1,66 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidTank;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class TileFluidHandler extends TileEntity
{
protected FluidTank tank = new FluidTank(Fluid.BUCKET_VOLUME);
@Override
public void readFromNBT(NBTTagCompound tag)
{
super.readFromNBT(tag);
tank.readFromNBT(tag);
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound tag)
{
tag = super.writeToNBT(tag);
tank.writeToNBT(tag);
return tag;
}
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing)
{
return capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY || super.hasCapability(capability, facing);
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing)
{
if (capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY)
return (T) tank;
return super.getCapability(capability, facing);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.templates;
import javax.annotation.Nullable;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandler;
public class EmptyFluidHandler implements IFluidHandler, IFluidTank
{
public static final EmptyFluidHandler INSTANCE = new EmptyFluidHandler();
public static final FluidTankInfo EMPTY_TANK_INFO = new FluidTankInfo(null, 0);
public static final IFluidTankProperties EMPTY_TANK_PROPERTIES = new FluidTankProperties(null, 0, false, false);
public static final IFluidTankProperties[] EMPTY_TANK_PROPERTIES_ARRAY = new IFluidTankProperties[] { EMPTY_TANK_PROPERTIES };
protected EmptyFluidHandler() {}
@Override
public IFluidTankProperties[] getTankProperties()
{
return EMPTY_TANK_PROPERTIES_ARRAY;
}
@Override
@Nullable
public FluidStack getFluid()
{
return null;
}
@Override
public int getFluidAmount()
{
return 0;
}
@Override
public int getCapacity()
{
return 0;
}
@Override
public FluidTankInfo getInfo()
{
return EMPTY_TANK_INFO;
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
return 0;
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
return null;
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
return null;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.templates;
import com.google.common.collect.Lists;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandler;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* FluidHandlerConcatenate is a template class for concatenating multiple handlers into one.
* If each tank is restricted to exactly one type of fluid, then use {@link FluidHandlerFluidMap} as it is more efficient.
*/
public class FluidHandlerConcatenate implements IFluidHandler
{
protected final IFluidHandler[] subHandlers;
public FluidHandlerConcatenate(IFluidHandler... subHandlers)
{
this.subHandlers = subHandlers;
}
public FluidHandlerConcatenate(Collection<IFluidHandler> subHandlers)
{
this.subHandlers = subHandlers.toArray(new IFluidHandler[subHandlers.size()]);
}
@Override
public IFluidTankProperties[] getTankProperties()
{
List<IFluidTankProperties> tanks = Lists.newArrayList();
for (IFluidHandler handler : subHandlers)
{
Collections.addAll(tanks, handler.getTankProperties());
}
return tanks.toArray(new IFluidTankProperties[tanks.size()]);
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
if (resource == null || resource.amount <= 0)
return 0;
resource = resource.copy();
int totalFillAmount = 0;
for (IFluidHandler handler : subHandlers)
{
int fillAmount = handler.fill(resource, doFill);
totalFillAmount += fillAmount;
resource.amount -= fillAmount;
if (resource.amount <= 0)
break;
}
return totalFillAmount;
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (resource == null || resource.amount <= 0)
return null;
resource = resource.copy();
FluidStack totalDrained = null;
for (IFluidHandler handler : subHandlers)
{
FluidStack drain = handler.drain(resource, doDrain);
if (drain != null)
{
if (totalDrained == null)
totalDrained = drain;
else
totalDrained.amount += drain.amount;
resource.amount -= drain.amount;
if (resource.amount <= 0)
break;
}
}
return totalDrained;
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (maxDrain == 0)
return null;
FluidStack totalDrained = null;
for (IFluidHandler handler : subHandlers)
{
if (totalDrained == null)
{
totalDrained = handler.drain(maxDrain, doDrain);
if (totalDrained != null)
{
maxDrain -= totalDrained.amount;
}
}
else
{
FluidStack copy = totalDrained.copy();
copy.amount = maxDrain;
FluidStack drain = handler.drain(copy, doDrain);
if (drain != null)
{
totalDrained.amount += drain.amount;
maxDrain -= drain.amount;
}
}
if (maxDrain <= 0)
break;
}
return totalDrained;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.templates;
import com.google.common.collect.Lists;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandler;
import java.util.*;
/**
* FluidHandlerFluidMap is a template class for concatenating multiple handlers into one,
* where each handler is associated with a different fluid.
*/
public class FluidHandlerFluidMap implements IFluidHandler
{
protected final Map<Fluid, IFluidHandler> handlers;
public FluidHandlerFluidMap()
{
// LinkedHashMap to ensure iteration order is consistent.
this(new LinkedHashMap<Fluid, IFluidHandler>());
}
public FluidHandlerFluidMap(Map<Fluid, IFluidHandler> handlers)
{
this.handlers = handlers;
}
public FluidHandlerFluidMap addHandler(Fluid fluid, IFluidHandler handler)
{
handlers.put(fluid, handler);
return this;
}
@Override
public IFluidTankProperties[] getTankProperties()
{
List<IFluidTankProperties> tanks = Lists.newArrayList();
for (IFluidHandler iFluidHandler : handlers.values())
{
Collections.addAll(tanks, iFluidHandler.getTankProperties());
}
return tanks.toArray(new IFluidTankProperties[tanks.size()]);
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
if (resource == null)
return 0;
IFluidHandler handler = handlers.get(resource.getFluid());
if (handler == null)
return 0;
return handler.fill(resource, doFill);
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (resource == null)
return null;
IFluidHandler handler = handlers.get(resource.getFluid());
if (handler == null)
return null;
return handler.drain(resource, doDrain);
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
for (IFluidHandler handler : handlers.values())
{
FluidStack drain = handler.drain(maxDrain, doDrain);
if (drain != null)
return drain;
}
return null;
}
}

View File

@@ -0,0 +1,255 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.templates;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
/**
* FluidHandlerItemStack is a template capability provider for ItemStacks.
* Data is stored directly in the vanilla NBT, in the same way as the old ItemFluidContainer.
*
* This class allows an itemStack to contain any partial level of fluid up to its capacity, unlike {@link FluidHandlerItemStackSimple}
*
* Additional examples are provided to enable consumable fluid containers (see {@link Consumable}),
* fluid containers with different empty and full items (see {@link SwapEmpty},
*/
public class FluidHandlerItemStack implements IFluidHandlerItem, ICapabilityProvider
{
public static final String FLUID_NBT_KEY = "Fluid";
@Nonnull
protected ItemStack container;
protected int capacity;
/**
* @param container The container itemStack, data is stored on it directly as NBT.
* @param capacity The maximum capacity of this fluid tank.
*/
public FluidHandlerItemStack(@Nonnull ItemStack container, int capacity)
{
this.container = container;
this.capacity = capacity;
}
@Nonnull
@Override
public ItemStack getContainer()
{
return container;
}
@Nullable
public FluidStack getFluid()
{
NBTTagCompound tagCompound = container.getTagCompound();
if (tagCompound == null || !tagCompound.hasKey(FLUID_NBT_KEY))
{
return null;
}
return FluidStack.loadFluidStackFromNBT(tagCompound.getCompoundTag(FLUID_NBT_KEY));
}
protected void setFluid(FluidStack fluid)
{
if (!container.hasTagCompound())
{
container.setTagCompound(new NBTTagCompound());
}
NBTTagCompound fluidTag = new NBTTagCompound();
fluid.writeToNBT(fluidTag);
container.getTagCompound().setTag(FLUID_NBT_KEY, fluidTag);
}
@Override
public IFluidTankProperties[] getTankProperties()
{
return new FluidTankProperties[] { new FluidTankProperties(getFluid(), capacity) };
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
if (container.getCount() != 1 || resource == null || resource.amount <= 0 || !canFillFluidType(resource))
{
return 0;
}
FluidStack contained = getFluid();
if (contained == null)
{
int fillAmount = Math.min(capacity, resource.amount);
if (doFill)
{
FluidStack filled = resource.copy();
filled.amount = fillAmount;
setFluid(filled);
}
return fillAmount;
}
else
{
if (contained.isFluidEqual(resource))
{
int fillAmount = Math.min(capacity - contained.amount, resource.amount);
if (doFill && fillAmount > 0) {
contained.amount += fillAmount;
setFluid(contained);
}
return fillAmount;
}
return 0;
}
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (container.getCount() != 1 || resource == null || resource.amount <= 0 || !resource.isFluidEqual(getFluid()))
{
return null;
}
return drain(resource.amount, doDrain);
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (container.getCount() != 1 || maxDrain <= 0)
{
return null;
}
FluidStack contained = getFluid();
if (contained == null || contained.amount <= 0 || !canDrainFluidType(contained))
{
return null;
}
final int drainAmount = Math.min(contained.amount, maxDrain);
FluidStack drained = contained.copy();
drained.amount = drainAmount;
if (doDrain)
{
contained.amount -= drainAmount;
if (contained.amount == 0)
{
setContainerToEmpty();
}
else
{
setFluid(contained);
}
}
return drained;
}
public boolean canFillFluidType(FluidStack fluid)
{
return true;
}
public boolean canDrainFluidType(FluidStack fluid)
{
return true;
}
/**
* Override this method for special handling.
* Can be used to swap out or destroy the container.
*/
protected void setContainerToEmpty()
{
container.getTagCompound().removeTag(FLUID_NBT_KEY);
}
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing)
{
return capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY;
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing)
{
return capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY ? (T) this : null;
}
/**
* Destroys the container item when it's emptied.
*/
public static class Consumable extends FluidHandlerItemStack
{
public Consumable(ItemStack container, int capacity)
{
super(container, capacity);
}
@Override
protected void setContainerToEmpty()
{
super.setContainerToEmpty();
container.shrink(1);
}
}
/**
* Swaps the container item for a different one when it's emptied.
*/
public static class SwapEmpty extends FluidHandlerItemStack
{
protected final ItemStack emptyContainer;
public SwapEmpty(ItemStack container, ItemStack emptyContainer, int capacity)
{
super(container, capacity);
this.emptyContainer = emptyContainer;
}
@Override
protected void setContainerToEmpty()
{
super.setContainerToEmpty();
container = emptyContainer;
}
}
}

View File

@@ -0,0 +1,232 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.templates;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
/**
* FluidHandlerItemStackSimple is a template capability provider for ItemStacks.
* Data is stored directly in the vanilla NBT, in the same way as the old ItemFluidContainer.
*
* This implementation only allows item containers to be fully filled or emptied, similar to vanilla buckets.
*/
public class FluidHandlerItemStackSimple implements IFluidHandlerItem, ICapabilityProvider
{
public static final String FLUID_NBT_KEY = "Fluid";
@Nonnull
protected ItemStack container;
protected int capacity;
/**
* @param container The container itemStack, data is stored on it directly as NBT.
* @param capacity The maximum capacity of this fluid tank.
*/
public FluidHandlerItemStackSimple(@Nonnull ItemStack container, int capacity)
{
this.container = container;
this.capacity = capacity;
}
@Nonnull
@Override
public ItemStack getContainer()
{
return container;
}
@Nullable
public FluidStack getFluid()
{
NBTTagCompound tagCompound = container.getTagCompound();
if (tagCompound == null || !tagCompound.hasKey(FLUID_NBT_KEY))
{
return null;
}
return FluidStack.loadFluidStackFromNBT(tagCompound.getCompoundTag(FLUID_NBT_KEY));
}
protected void setFluid(FluidStack fluid)
{
if (!container.hasTagCompound())
{
container.setTagCompound(new NBTTagCompound());
}
NBTTagCompound fluidTag = new NBTTagCompound();
fluid.writeToNBT(fluidTag);
container.getTagCompound().setTag(FLUID_NBT_KEY, fluidTag);
}
@Override
public IFluidTankProperties[] getTankProperties()
{
return new IFluidTankProperties[] { new FluidTankProperties(getFluid(), capacity) };
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
if (container.getCount() != 1 || resource == null || resource.amount <= 0 || !canFillFluidType(resource))
{
return 0;
}
FluidStack contained = getFluid();
if (contained == null)
{
int fillAmount = Math.min(capacity, resource.amount);
if (fillAmount == capacity) {
if (doFill) {
FluidStack filled = resource.copy();
filled.amount = fillAmount;
setFluid(filled);
}
return fillAmount;
}
}
return 0;
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (container.getCount() != 1 || resource == null || resource.amount <= 0 || !resource.isFluidEqual(getFluid()))
{
return null;
}
return drain(resource.amount, doDrain);
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (container.getCount() != 1 || maxDrain <= 0)
{
return null;
}
FluidStack contained = getFluid();
if (contained == null || contained.amount <= 0 || !canDrainFluidType(contained))
{
return null;
}
final int drainAmount = Math.min(contained.amount, maxDrain);
if (drainAmount == capacity) {
FluidStack drained = contained.copy();
if (doDrain) {
setContainerToEmpty();
}
return drained;
}
return null;
}
public boolean canFillFluidType(FluidStack fluid)
{
return true;
}
public boolean canDrainFluidType(FluidStack fluid)
{
return true;
}
/**
* Override this method for special handling.
* Can be used to swap out the container's item for a different one with "container.setItem".
* Can be used to destroy the container with "container.stackSize--"
*/
protected void setContainerToEmpty()
{
container.getTagCompound().removeTag(FLUID_NBT_KEY);
}
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing)
{
return capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY;
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing)
{
return capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY ? (T) this : null;
}
/**
* Destroys the container item when it's emptied.
*/
public static class Consumable extends FluidHandlerItemStackSimple
{
public Consumable(ItemStack container, int capacity)
{
super(container, capacity);
}
@Override
protected void setContainerToEmpty()
{
super.setContainerToEmpty();
container.shrink(1);
}
}
/**
* Swaps the container item for a different one when it's emptied.
*/
public static class SwapEmpty extends FluidHandlerItemStackSimple
{
protected final ItemStack emptyContainer;
public SwapEmpty(ItemStack container, ItemStack emptyContainer, int capacity)
{
super(container, capacity);
this.emptyContainer = emptyContainer;
}
@Override
protected void setContainerToEmpty()
{
super.setContainerToEmpty();
container = emptyContainer;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.templates;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import javax.annotation.Nullable;
import static net.minecraftforge.fluids.capability.templates.EmptyFluidHandler.EMPTY_TANK_INFO;
import static net.minecraftforge.fluids.capability.templates.EmptyFluidHandler.EMPTY_TANK_PROPERTIES_ARRAY;
/**
* VoidFluidHandler is a template fluid handler that can be filled indefinitely without ever getting full.
* It does not store fluid that gets filled into it, but "destroys" it upon receiving it.
*/
public class VoidFluidHandler implements IFluidHandler, IFluidTank
{
public static final EmptyFluidHandler INSTANCE = new EmptyFluidHandler();
public VoidFluidHandler() {}
@Override
public IFluidTankProperties[] getTankProperties()
{
return EMPTY_TANK_PROPERTIES_ARRAY;
}
@Override
@Nullable
public FluidStack getFluid()
{
return null;
}
@Override
public int getFluidAmount()
{
return 0;
}
@Override
public int getCapacity()
{
return Integer.MAX_VALUE;
}
@Override
public FluidTankInfo getInfo()
{
return EMPTY_TANK_INFO;
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
return resource.amount;
}
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
return null;
}
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
return null;
}
}

View File

@@ -0,0 +1,157 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.wrappers;
import javax.annotation.Nullable;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemBucket;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandler;
/**
* Wrapper to handle vanilla Water or Lava as an IFluidHandler.
* Methods are modeled after {@link ItemBucket#onItemRightClick(World, EntityPlayer, EnumHand)}
*/
public class BlockLiquidWrapper implements IFluidHandler
{
protected final BlockLiquid blockLiquid;
protected final World world;
protected final BlockPos blockPos;
public BlockLiquidWrapper(BlockLiquid blockLiquid, World world, BlockPos blockPos)
{
this.blockLiquid = blockLiquid;
this.world = world;
this.blockPos = blockPos;
}
@Override
public IFluidTankProperties[] getTankProperties()
{
FluidStack containedStack = null;
IBlockState blockState = world.getBlockState(blockPos);
if (blockState.getBlock() == blockLiquid)
{
containedStack = getStack(blockState);
}
return new FluidTankProperties[]{new FluidTankProperties(containedStack, Fluid.BUCKET_VOLUME, false, true)};
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
// NOTE: "Filling" means placement in this context!
if (resource.amount < Fluid.BUCKET_VOLUME)
{
return 0;
}
if (doFill)
{
Material material = blockLiquid.getDefaultState().getMaterial();
BlockLiquid block = BlockLiquid.getFlowingBlock(material);
world.setBlockState(blockPos, block.getDefaultState().withProperty(BlockLiquid.LEVEL, 0), 11);
}
return Fluid.BUCKET_VOLUME;
}
@Nullable
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (resource == null || resource.amount < Fluid.BUCKET_VOLUME)
{
return null;
}
IBlockState blockState = world.getBlockState(blockPos);
if (blockState.getBlock() == blockLiquid && blockState.getValue(BlockLiquid.LEVEL) == 0)
{
FluidStack containedStack = getStack(blockState);
if (containedStack != null && resource.containsFluid(containedStack))
{
if (doDrain)
{
world.setBlockState(blockPos, Blocks.AIR.getDefaultState(), 11);
}
return containedStack;
}
}
return null;
}
@Nullable
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (maxDrain < Fluid.BUCKET_VOLUME)
{
return null;
}
IBlockState blockState = world.getBlockState(blockPos);
if (blockState.getBlock() == blockLiquid)
{
FluidStack containedStack = getStack(blockState);
if (containedStack != null && containedStack.amount <= maxDrain)
{
if (doDrain)
{
world.setBlockState(blockPos, Blocks.AIR.getDefaultState(), 11);
}
return containedStack;
}
}
return null;
}
@Nullable
private FluidStack getStack(IBlockState blockState)
{
Material material = blockState.getMaterial();
if (material == Material.WATER && blockState.getValue(BlockLiquid.LEVEL) == 0)
{
return new FluidStack(FluidRegistry.WATER, Fluid.BUCKET_VOLUME);
}
else if (material == Material.LAVA && blockState.getValue(BlockLiquid.LEVEL) == 0)
{
return new FluidStack(FluidRegistry.LAVA, Fluid.BUCKET_VOLUME);
}
else
{
return null;
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.wrappers;
import net.minecraft.block.Block;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.capability.templates.VoidFluidHandler;
/**
* Wrapper around any block, only accounts for fluid placement, otherwise the block acts a void.
* If the block in question inherits from the default Vanilla or Forge implementations,
* consider using {@link BlockLiquidWrapper} or {@link FluidBlockWrapper} respectively.
*/
public class BlockWrapper extends VoidFluidHandler
{
protected final Block block;
protected final World world;
protected final BlockPos blockPos;
public BlockWrapper(Block block, World world, BlockPos blockPos)
{
this.block = block;
this.world = world;
this.blockPos = blockPos;
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
// NOTE: "Filling" means placement in this context!
if (resource.amount < Fluid.BUCKET_VOLUME)
{
return 0;
}
if (doFill)
{
FluidUtil.destroyBlockOnFluidPlacement(world, blockPos);
world.setBlockState(blockPos, block.getDefaultState(), 11);
}
return Fluid.BUCKET_VOLUME;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.wrappers;
import javax.annotation.Nullable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.IFluidBlock;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandler;
/**
* Wrapper to handle {@link IFluidBlock} as an IFluidHandler
*/
public class FluidBlockWrapper implements IFluidHandler
{
protected final IFluidBlock fluidBlock;
protected final World world;
protected final BlockPos blockPos;
public FluidBlockWrapper(IFluidBlock fluidBlock, World world, BlockPos blockPos)
{
this.fluidBlock = fluidBlock;
this.world = world;
this.blockPos = blockPos;
}
@Override
public IFluidTankProperties[] getTankProperties()
{
float percentFilled = fluidBlock.getFilledPercentage(world, blockPos);
if (percentFilled < 0)
{
percentFilled *= -1;
}
int amountFilled = Math.round(Fluid.BUCKET_VOLUME * percentFilled);
FluidStack fluid = amountFilled > 0 ? new FluidStack(fluidBlock.getFluid(), amountFilled) : null;
return new FluidTankProperties[]{ new FluidTankProperties(fluid, Fluid.BUCKET_VOLUME, false, true)};
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
// NOTE: "Filling" means placement in this context!
if (resource == null)
{
return 0;
}
return fluidBlock.place(world, blockPos, resource, doFill);
}
@Nullable
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (resource == null || !fluidBlock.canDrain(world, blockPos))
{
return null;
}
FluidStack simulatedDrain = fluidBlock.drain(world, blockPos, false);
if (resource.containsFluid(simulatedDrain))
{
if (doDrain)
{
return fluidBlock.drain(world, blockPos, true);
}
else
{
return simulatedDrain;
}
}
return null;
}
@Nullable
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (maxDrain <= 0 || !fluidBlock.canDrain(world, blockPos))
{
return null;
}
FluidStack simulatedDrain = fluidBlock.drain(world, blockPos, false);
if (simulatedDrain != null && simulatedDrain.amount <= maxDrain)
{
if (doDrain)
{
return fluidBlock.drain(world, blockPos, true);
}
else
{
return simulatedDrain;
}
}
return null;
}
}

View File

@@ -0,0 +1,196 @@
/*
* Minecraft Forge
* Copyright (c) 2016-2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fluids.capability.wrappers;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBucketMilk;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.capability.FluidTankProperties;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.fluids.capability.IFluidTankProperties;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
/**
* Wrapper for vanilla and forge buckets.
* Swaps between empty bucket and filled bucket of the correct type.
*/
public class FluidBucketWrapper implements IFluidHandlerItem, ICapabilityProvider
{
@Nonnull
protected ItemStack container;
public FluidBucketWrapper(@Nonnull ItemStack container)
{
this.container = container;
}
@Nonnull
@Override
public ItemStack getContainer()
{
return container;
}
public boolean canFillFluidType(FluidStack fluid)
{
if (fluid.getFluid() == FluidRegistry.WATER || fluid.getFluid() == FluidRegistry.LAVA || fluid.getFluid().getName().equals("milk"))
{
return true;
}
return FluidRegistry.isUniversalBucketEnabled() && FluidRegistry.getBucketFluids().contains(fluid.getFluid());
}
@Nullable
public FluidStack getFluid()
{
Item item = container.getItem();
if (item == Items.WATER_BUCKET)
{
return new FluidStack(FluidRegistry.WATER, Fluid.BUCKET_VOLUME);
}
else if (item == Items.LAVA_BUCKET)
{
return new FluidStack(FluidRegistry.LAVA, Fluid.BUCKET_VOLUME);
}
else if (item == Items.MILK_BUCKET)
{
return FluidRegistry.getFluidStack("milk", Fluid.BUCKET_VOLUME);
}
else if (item == ForgeModContainer.getInstance().universalBucket)
{
return ForgeModContainer.getInstance().universalBucket.getFluid(container);
}
else
{
return null;
}
}
/**
* @deprecated use the NBT-sensitive version {@link #setFluid(FluidStack)}
*/
@Deprecated
protected void setFluid(@Nullable Fluid fluid) {
setFluid(new FluidStack(fluid, Fluid.BUCKET_VOLUME));
}
protected void setFluid(@Nullable FluidStack fluidStack)
{
if (fluidStack == null)
container = new ItemStack(Items.BUCKET);
else
container = FluidUtil.getFilledBucket(fluidStack);
}
@Override
public IFluidTankProperties[] getTankProperties()
{
return new FluidTankProperties[] { new FluidTankProperties(getFluid(), Fluid.BUCKET_VOLUME) };
}
@Override
public int fill(FluidStack resource, boolean doFill)
{
if (container.getCount() != 1 || resource == null || resource.amount < Fluid.BUCKET_VOLUME || container.getItem() instanceof ItemBucketMilk || getFluid() != null || !canFillFluidType(resource))
{
return 0;
}
if (doFill)
{
setFluid(resource);
}
return Fluid.BUCKET_VOLUME;
}
@Nullable
@Override
public FluidStack drain(FluidStack resource, boolean doDrain)
{
if (container.getCount() != 1 || resource == null || resource.amount < Fluid.BUCKET_VOLUME)
{
return null;
}
FluidStack fluidStack = getFluid();
if (fluidStack != null && fluidStack.isFluidEqual(resource))
{
if (doDrain)
{
setFluid((FluidStack) null);
}
return fluidStack;
}
return null;
}
@Nullable
@Override
public FluidStack drain(int maxDrain, boolean doDrain)
{
if (container.getCount() != 1 || maxDrain < Fluid.BUCKET_VOLUME)
{
return null;
}
FluidStack fluidStack = getFluid();
if (fluidStack != null)
{
if (doDrain)
{
setFluid((FluidStack) null);
}
return fluidStack;
}
return null;
}
@Override
public boolean hasCapability(@Nonnull Capability<?> capability, @Nullable EnumFacing facing)
{
return capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY;
}
@Override
@Nullable
public <T> T getCapability(@Nonnull Capability<T> capability, @Nullable EnumFacing facing)
{
if (capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY)
{
return CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY.cast(this);
}
return null;
}
}