2016-06-22 23:49:47 -04:00
/*
* Minecraft Forge
2019-02-10 17:57:03 -05:00
* Copyright (c) 2016-2019.
2016-06-22 23:49:47 -04:00
*
* 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
*/
2016-01-02 14:38:18 +01:00
package net.minecraftforge.fluids ;
2016-11-13 17:09:54 -05:00
import javax.annotation.Nonnull ;
2016-06-03 18:26:41 -07:00
import javax.annotation.Nullable ;
2017-05-01 17:25:45 -07:00
import com.google.common.base.Preconditions ;
2016-05-16 22:20:31 +02:00
import net.minecraft.block.Block ;
import net.minecraft.block.material.Material ;
2019-05-23 16:02:15 -07:00
import net.minecraft.block.BlockState ;
import net.minecraft.entity.player.PlayerEntity ;
2019-07-28 21:35:30 +02:00
import net.minecraft.fluid.Fluid ;
2019-08-29 03:01:11 +02:00
import net.minecraft.fluid.Fluids ;
2018-09-20 23:50:50 -07:00
import net.minecraft.item.BlockItemUseContext ;
2016-01-02 14:38:18 +01:00
import net.minecraft.item.ItemStack ;
2019-05-23 16:02:15 -07:00
import net.minecraft.item.ItemUseContext ;
2019-08-29 03:01:11 +02:00
import net.minecraft.item.Items ;
2016-06-03 18:26:41 -07:00
import net.minecraft.tileentity.TileEntity ;
2019-08-29 03:01:11 +02:00
import net.minecraft.util.* ;
2016-06-03 18:26:41 -07:00
import net.minecraft.util.math.BlockPos ;
2019-05-23 16:02:15 -07:00
import net.minecraft.util.math.BlockRayTraceResult ;
import net.minecraft.util.math.Vec3d ;
2016-06-03 18:26:41 -07:00
import net.minecraft.world.World ;
2019-01-27 01:38:53 -05:00
import net.minecraftforge.common.util.LazyOptional ;
2016-06-03 18:26:41 -07:00
import net.minecraftforge.fluids.capability.CapabilityFluidHandler ;
import net.minecraftforge.fluids.capability.IFluidHandler ;
2016-10-10 00:44:50 -07:00
import net.minecraftforge.fluids.capability.IFluidHandlerItem ;
2016-12-05 20:17:47 -08:00
import net.minecraftforge.fluids.capability.wrappers.BlockWrapper ;
2017-05-01 17:25:45 -07:00
import net.minecraftforge.items.CapabilityItemHandler ;
2016-01-02 14:38:18 +01:00
import net.minecraftforge.items.IItemHandler ;
import net.minecraftforge.items.ItemHandlerHelper ;
public class FluidUtil
{
private FluidUtil ()
{
}
2017-05-01 17:25:45 -07:00
/**
* 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.
*/
2019-05-23 16:02:15 -07:00
public static boolean interactWithFluidHandler ( @Nonnull PlayerEntity player , @Nonnull Hand hand , @Nonnull World world , @Nonnull BlockPos pos , @Nullable Direction side )
2017-05-01 17:25:45 -07:00
{
Preconditions . checkNotNull ( world );
Preconditions . checkNotNull ( pos );
2018-09-09 17:27:15 -04:00
return getFluidHandler ( world , pos , side ). map ( handler -> interactWithFluidHandler ( player , hand , handler )). orElse ( false );
2017-06-26 02:22:52 +01:00
}
/**
* 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.
*/
2019-05-23 16:02:15 -07:00
public static boolean interactWithFluidHandler ( @Nonnull PlayerEntity player , @Nonnull Hand hand , @Nonnull IFluidHandler handler )
2017-06-26 02:22:52 +01:00
{
Preconditions . checkNotNull ( player );
Preconditions . checkNotNull ( hand );
Preconditions . checkNotNull ( handler );
2017-05-01 17:25:45 -07:00
ItemStack heldItem = player . getHeldItem ( hand );
if ( ! heldItem . isEmpty ())
{
2018-09-09 17:27:15 -04:00
return player . getCapability ( CapabilityItemHandler . ITEM_HANDLER_CAPABILITY )
. map ( playerInventory -> {
2018-09-20 23:50:50 -07:00
2018-09-09 17:27:15 -04:00
FluidActionResult fluidActionResult = tryFillContainerAndStow ( heldItem , handler , playerInventory , Integer . MAX_VALUE , player , true );
if ( ! fluidActionResult . isSuccess ())
{
fluidActionResult = tryEmptyContainerAndStow ( heldItem , handler , playerInventory , Integer . MAX_VALUE , player , true );
}
2018-09-20 23:50:50 -07:00
2018-09-09 17:27:15 -04:00
if ( fluidActionResult . isSuccess ())
{
player . setHeldItem ( hand , fluidActionResult . getResult ());
return true ;
}
return false ;
})
. orElse ( false );
2017-05-01 17:25:45 -07:00
}
return false ;
}
2016-06-03 18:26:41 -07:00
/**
* Fill a container from the given fluidSource.
*
* @param container The container to be filled. Will not be modified.
2016-10-10 00:44:50 -07:00
* Separate handling must be done to reduce the stack size, stow containers, etc, on success.
2019-07-28 21:35:30 +02:00
* See {@link #tryFillContainerAndStow(ItemStack, IFluidHandler, IItemHandler, int, PlayerEntity, boolean)}.
2016-06-03 18:26:41 -07:00
* @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.
2016-10-10 00:44:50 -07:00
* @return a {@link FluidActionResult} holding the filled container if successful.
2016-06-03 18:26:41 -07:00
*/
2016-11-13 17:09:54 -05:00
@Nonnull
2019-05-23 16:02:15 -07:00
public static FluidActionResult tryFillContainer ( @Nonnull ItemStack container , IFluidHandler fluidSource , int maxAmount , @Nullable PlayerEntity player , boolean doFill )
2016-06-03 18:26:41 -07:00
{
2017-05-01 17:25:45 -07:00
ItemStack containerCopy = ItemHandlerHelper . copyStackWithSize ( container , 1 ); // do not modify the input
2018-09-09 17:27:15 -04:00
return getFluidHandler ( containerCopy )
. map ( containerFluidHandler -> {
FluidStack simulatedTransfer = tryFluidTransfer ( containerFluidHandler , fluidSource , maxAmount , false );
2019-08-29 03:01:11 +02:00
if ( ! simulatedTransfer . isEmpty ())
2016-06-03 18:26:41 -07:00
{
2018-09-09 17:27:15 -04:00
if ( doFill )
{
tryFluidTransfer ( containerFluidHandler , fluidSource , maxAmount , true );
if ( player != null )
{
2019-07-28 21:35:30 +02:00
SoundEvent soundevent = simulatedTransfer . getFluid (). getAttributes (). getFillSound ( simulatedTransfer );
2019-12-11 03:27:07 +01:00
player . world . playSound ( null , player . func_226277_ct_ (), player . func_226278_cu_ () + 0 . 5 , player . func_226281_cx_ (), soundevent , SoundCategory . BLOCKS , 1 . 0F , 1 . 0F );
2018-09-09 17:27:15 -04:00
}
}
else
{
2019-07-28 21:35:30 +02:00
containerFluidHandler . fill ( simulatedTransfer , IFluidHandler . FluidAction . SIMULATE );
2018-09-09 17:27:15 -04:00
}
2018-09-20 23:50:50 -07:00
2018-09-09 17:27:15 -04:00
ItemStack resultContainer = containerFluidHandler . getContainer ();
return new FluidActionResult ( resultContainer );
2016-06-03 18:26:41 -07:00
}
2018-09-09 17:27:15 -04:00
return FluidActionResult . FAILURE ;
})
. orElse ( FluidActionResult . FAILURE );
2016-06-03 18:26:41 -07:00
}
/**
* Takes a filled container and tries to empty it into the given tank.
*
* @param container The filled container. Will not be modified.
2016-10-10 00:44:50 -07:00
* Separate handling must be done to reduce the stack size, stow containers, etc, on success.
2019-07-28 21:35:30 +02:00
* See {@link #tryEmptyContainerAndStow(ItemStack, IFluidHandler, IItemHandler, int, PlayerEntity, boolean)}.
2016-06-03 18:26:41 -07:00
* @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.
2016-10-10 00:44:50 -07:00
* @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.
2016-06-03 18:26:41 -07:00
*/
2016-11-13 17:09:54 -05:00
@Nonnull
2019-05-23 16:02:15 -07:00
public static FluidActionResult tryEmptyContainer ( @Nonnull ItemStack container , IFluidHandler fluidDestination , int maxAmount , @Nullable PlayerEntity player , boolean doDrain )
2016-06-03 18:26:41 -07:00
{
2017-05-01 17:25:45 -07:00
ItemStack containerCopy = ItemHandlerHelper . copyStackWithSize ( container , 1 ); // do not modify the input
2018-09-09 17:27:15 -04:00
return getFluidHandler ( containerCopy )
. map ( containerFluidHandler -> {
2019-10-05 19:49:26 +02:00
// We are acting on a COPY of the stack, so performing changes is acceptable even if we are simulating.
FluidStack transfer = tryFluidTransfer ( fluidDestination , containerFluidHandler , maxAmount , true );
if ( transfer . isEmpty ())
return FluidActionResult . FAILURE ;
if ( doDrain && player != null )
2018-09-09 17:27:15 -04:00
{
2019-10-05 19:49:26 +02:00
SoundEvent soundevent = transfer . getFluid (). getAttributes (). getEmptySound ( transfer );
2019-12-11 03:27:07 +01:00
player . world . playSound ( null , player . func_226277_ct_ (), player . func_226278_cu_ () + 0 . 5 , player . func_226281_cx_ (), soundevent , SoundCategory . BLOCKS , 1 . 0F , 1 . 0F );
2018-09-09 17:27:15 -04:00
}
2019-10-05 19:49:26 +02:00
ItemStack resultContainer = containerFluidHandler . getContainer ();
return new FluidActionResult ( resultContainer );
2018-09-09 17:27:15 -04:00
})
. orElse ( FluidActionResult . FAILURE );
2016-06-03 18:26:41 -07:00
}
2018-01-18 13:23:30 -08:00
/**
* 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
2019-05-23 16:02:15 -07:00
public static FluidActionResult tryFillContainerAndStow ( @Nonnull ItemStack container , IFluidHandler fluidSource , IItemHandler inventory , int maxAmount , @Nullable PlayerEntity player , boolean doFill )
2016-06-03 18:26:41 -07:00
{
2016-12-21 15:52:30 -08:00
if ( container . isEmpty ())
2016-06-03 18:26:41 -07:00
{
2016-10-10 00:44:50 -07:00
return FluidActionResult . FAILURE ;
2016-06-03 18:26:41 -07:00
}
2019-06-21 22:42:16 -04:00
if ( player != null && player . abilities . isCreativeMode )
2016-06-03 18:26:41 -07:00
{
2018-01-18 13:23:30 -08:00
FluidActionResult filledReal = tryFillContainer ( container , fluidSource , maxAmount , player , doFill );
2016-10-10 00:44:50 -07:00
if ( filledReal . isSuccess ())
2016-06-03 18:26:41 -07:00
{
2016-10-10 00:44:50 -07:00
return new FluidActionResult ( container ); // creative mode: item does not change
2016-06-03 18:26:41 -07:00
}
}
2016-12-21 15:52:30 -08:00
else if ( container . getCount () == 1 ) // don't need to stow anything, just fill the container stack
2016-06-03 18:26:41 -07:00
{
2018-01-18 13:23:30 -08:00
FluidActionResult filledReal = tryFillContainer ( container , fluidSource , maxAmount , player , doFill );
2016-10-10 00:44:50 -07:00
if ( filledReal . isSuccess ())
2016-06-03 18:26:41 -07:00
{
2016-10-10 00:44:50 -07:00
return filledReal ;
2016-01-02 14:38:18 +01:00
}
}
else
{
2016-10-10 00:44:50 -07:00
FluidActionResult filledSimulated = tryFillContainer ( container , fluidSource , maxAmount , player , false );
if ( filledSimulated . isSuccess ())
2016-01-02 14:38:18 +01:00
{
2016-06-03 18:26:41 -07:00
// check if we can give the itemStack to the inventory
2016-10-10 00:44:50 -07:00
ItemStack remainder = ItemHandlerHelper . insertItemStacked ( inventory , filledSimulated . getResult (), true );
2016-12-21 15:52:30 -08:00
if ( remainder . isEmpty () || player != null )
2016-06-03 18:26:41 -07:00
{
2018-01-18 13:23:30 -08:00
FluidActionResult filledReal = tryFillContainer ( container , fluidSource , maxAmount , player , doFill );
remainder = ItemHandlerHelper . insertItemStacked ( inventory , filledReal . getResult (), ! doFill );
2016-06-03 18:26:41 -07:00
// give it to the player or drop it at their feet
2018-01-18 13:23:30 -08:00
if ( ! remainder . isEmpty () && player != null && doFill )
2016-06-03 18:26:41 -07:00
{
ItemHandlerHelper . giveItemToPlayer ( player , remainder );
}
2016-10-10 00:44:50 -07:00
ItemStack containerCopy = container . copy ();
2016-12-21 15:52:30 -08:00
containerCopy . shrink ( 1 );
2016-10-10 00:44:50 -07:00
return new FluidActionResult ( containerCopy );
2016-06-03 18:26:41 -07:00
}
2016-01-02 14:38:18 +01:00
}
2016-06-03 18:26:41 -07:00
}
2016-10-10 00:44:50 -07:00
return FluidActionResult . FAILURE ;
2016-06-03 18:26:41 -07:00
}
/**
* 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.
*
2016-10-10 00:44:50 -07:00
* @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.
2018-01-18 13:23:30 -08:00
* @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
2019-05-23 16:02:15 -07:00
public static FluidActionResult tryEmptyContainerAndStow ( @Nonnull ItemStack container , IFluidHandler fluidDestination , IItemHandler inventory , int maxAmount , @Nullable PlayerEntity player , boolean doDrain )
2016-06-03 18:26:41 -07:00
{
2016-12-21 15:52:30 -08:00
if ( container . isEmpty ())
2016-06-03 18:26:41 -07:00
{
2016-10-10 00:44:50 -07:00
return FluidActionResult . FAILURE ;
2016-06-03 18:26:41 -07:00
}
2016-01-02 14:38:18 +01:00
2019-06-21 22:42:16 -04:00
if ( player != null && player . abilities . isCreativeMode )
2016-06-03 18:26:41 -07:00
{
2018-01-18 13:23:30 -08:00
FluidActionResult emptiedReal = tryEmptyContainer ( container , fluidDestination , maxAmount , player , doDrain );
2016-10-10 00:44:50 -07:00
if ( emptiedReal . isSuccess ())
2016-06-03 18:26:41 -07:00
{
2016-10-10 00:44:50 -07:00
return new FluidActionResult ( container ); // creative mode: item does not change
2016-06-03 18:26:41 -07:00
}
}
2016-12-21 15:52:30 -08:00
else if ( container . getCount () == 1 ) // don't need to stow anything, just fill and edit the container stack
2016-06-03 18:26:41 -07:00
{
2018-01-18 13:23:30 -08:00
FluidActionResult emptiedReal = tryEmptyContainer ( container , fluidDestination , maxAmount , player , doDrain );
2016-10-10 00:44:50 -07:00
if ( emptiedReal . isSuccess ())
2016-11-13 17:09:54 -05:00
{
2016-10-10 00:44:50 -07:00
return emptiedReal ;
2016-06-03 18:26:41 -07:00
}
}
else
{
2016-10-10 00:44:50 -07:00
FluidActionResult emptiedSimulated = tryEmptyContainer ( container , fluidDestination , maxAmount , player , false );
if ( emptiedSimulated . isSuccess ())
2016-11-13 17:09:54 -05:00
{
// check if we can give the itemStack to the inventory
2016-10-10 00:44:50 -07:00
ItemStack remainder = ItemHandlerHelper . insertItemStacked ( inventory , emptiedSimulated . getResult (), true );
2016-12-21 15:52:30 -08:00
if ( remainder . isEmpty () || player != null )
2016-06-03 18:26:41 -07:00
{
2018-01-18 13:23:30 -08:00
FluidActionResult emptiedReal = tryEmptyContainer ( container , fluidDestination , maxAmount , player , doDrain );
remainder = ItemHandlerHelper . insertItemStacked ( inventory , emptiedReal . getResult (), ! doDrain );
2016-06-03 18:26:41 -07:00
2016-11-13 17:09:54 -05:00
// give it to the player or drop it at their feet
2018-01-18 13:23:30 -08:00
if ( ! remainder . isEmpty () && player != null && doDrain )
2016-11-13 17:09:54 -05:00
{
ItemHandlerHelper . giveItemToPlayer ( player , remainder );
2016-01-02 14:38:18 +01:00
}
2016-11-13 17:09:54 -05:00
2016-10-10 00:44:50 -07:00
ItemStack containerCopy = container . copy ();
2016-12-21 15:52:30 -08:00
containerCopy . shrink ( 1 );
2016-10-10 00:44:50 -07:00
return new FluidActionResult ( containerCopy );
2016-01-02 14:38:18 +01:00
}
}
}
2016-10-10 00:44:50 -07:00
return FluidActionResult . FAILURE ;
2016-01-02 14:38:18 +01:00
}
2016-06-03 18:26:41 -07:00
/**
2017-05-01 17:25:45 -07:00
* 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.
2016-06-03 18:26:41 -07:00
*
* @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.
*/
2019-08-29 03:01:11 +02:00
@Nonnull
2016-06-03 18:26:41 -07:00
public static FluidStack tryFluidTransfer ( IFluidHandler fluidDestination , IFluidHandler fluidSource , int maxAmount , boolean doTransfer )
2016-05-16 22:20:31 +02:00
{
2019-07-28 21:35:30 +02:00
FluidStack drainable = fluidSource . drain ( maxAmount , IFluidHandler . FluidAction . SIMULATE );
2019-08-29 03:01:11 +02:00
if ( ! drainable . isEmpty ())
2016-06-03 18:26:41 -07:00
{
2017-05-01 17:25:45 -07:00
return tryFluidTransfer_Internal ( fluidDestination , fluidSource , drainable , doTransfer );
}
2019-08-29 03:01:11 +02:00
return FluidStack . EMPTY ;
2017-05-01 17:25:45 -07:00
}
/**
* 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.
*/
2019-08-29 03:01:11 +02:00
@Nonnull
2017-05-01 17:25:45 -07:00
public static FluidStack tryFluidTransfer ( IFluidHandler fluidDestination , IFluidHandler fluidSource , FluidStack resource , boolean doTransfer )
{
2019-07-28 21:35:30 +02:00
FluidStack drainable = fluidSource . drain ( resource , IFluidHandler . FluidAction . SIMULATE );
2019-08-29 03:01:11 +02:00
if ( ! drainable . isEmpty () && resource . isFluidEqual ( drainable ))
2017-05-01 17:25:45 -07:00
{
return tryFluidTransfer_Internal ( fluidDestination , fluidSource , drainable , doTransfer );
}
2019-08-29 03:01:11 +02:00
return FluidStack . EMPTY ;
2017-05-01 17:25:45 -07:00
}
/**
* 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)}.
*/
2019-08-29 03:01:11 +02:00
@Nonnull
2017-05-01 17:25:45 -07:00
private static FluidStack tryFluidTransfer_Internal ( IFluidHandler fluidDestination , IFluidHandler fluidSource , FluidStack drainable , boolean doTransfer )
{
2019-07-28 21:35:30 +02:00
int fillableAmount = fluidDestination . fill ( drainable , IFluidHandler . FluidAction . SIMULATE );
2017-05-01 17:25:45 -07:00
if ( fillableAmount > 0 )
{
if ( doTransfer )
2016-06-03 18:26:41 -07:00
{
2019-07-28 21:35:30 +02:00
FluidStack drained = fluidSource . drain ( fillableAmount , IFluidHandler . FluidAction . EXECUTE );
2019-08-29 03:01:11 +02:00
if ( ! drained . isEmpty ())
2016-10-10 00:44:50 -07:00
{
2019-08-29 03:01:11 +02:00
drained . setAmount ( fluidDestination . fill ( drained , IFluidHandler . FluidAction . EXECUTE ));
2017-05-01 17:25:45 -07:00
return drained ;
2016-06-03 18:26:41 -07:00
}
}
2017-05-01 17:25:45 -07:00
else
{
2019-08-29 03:01:11 +02:00
drainable . setAmount ( fillableAmount );
2017-05-01 17:25:45 -07:00
return drainable ;
}
2016-06-03 18:26:41 -07:00
}
2019-08-29 03:01:11 +02:00
return FluidStack . EMPTY ;
2016-05-16 22:20:31 +02:00
}
2016-01-02 14:38:18 +01:00
/**
2016-10-10 00:44:50 -07:00
* Helper method to get an {@link IFluidHandlerItem} for an itemStack.
2016-01-02 14:38:18 +01:00
*
2016-10-10 00:44:50 -07:00
* 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.
2016-06-03 18:26:41 -07:00
*
* Note that the itemStack MUST have a stackSize of 1 if you want to fill or drain it.
2016-10-10 00:44:50 -07:00
* You can't fill or drain multiple items at once, if you do then liquid is multiplied or destroyed.
2016-06-03 18:26:41 -07:00
*
* Vanilla buckets will be converted to universal buckets if they are enabled.
2016-01-02 14:38:18 +01:00
*/
2019-01-27 01:38:53 -05:00
public static LazyOptional < IFluidHandlerItem > getFluidHandler ( @Nonnull ItemStack itemStack )
2016-01-02 14:38:18 +01:00
{
2018-09-09 17:27:15 -04:00
return itemStack . getCapability ( CapabilityFluidHandler . FLUID_HANDLER_ITEM_CAPABILITY );
2016-06-03 18:26:41 -07:00
}
/**
* Helper method to get the fluid contained in an itemStack
*/
2019-01-27 01:38:53 -05:00
public static LazyOptional < FluidStack > getFluidContained ( @Nonnull ItemStack container )
2016-06-03 18:26:41 -07:00
{
2016-12-21 15:52:30 -08:00
if ( ! container . isEmpty ())
2016-01-02 14:38:18 +01:00
{
2017-05-01 17:25:45 -07:00
container = ItemHandlerHelper . copyStackWithSize ( container , 1 );
2018-09-09 17:27:15 -04:00
return getFluidHandler ( container )
2019-07-28 21:35:30 +02:00
. map ( handler -> handler . drain ( Integer . MAX_VALUE , IFluidHandler . FluidAction . SIMULATE ));
2016-01-02 14:38:18 +01:00
}
2019-01-27 01:38:53 -05:00
return LazyOptional . empty ();
2016-01-02 14:38:18 +01:00
}
2016-06-03 18:26:41 -07:00
/**
* Helper method to get an IFluidHandler for at a block position.
*/
2019-05-23 16:02:15 -07:00
public static LazyOptional < IFluidHandler > getFluidHandler ( World world , BlockPos blockPos , @Nullable Direction side )
2016-05-16 22:20:31 +02:00
{
2019-05-23 16:02:15 -07:00
BlockState state = world . getBlockState ( blockPos );
2016-06-03 18:26:41 -07:00
Block block = state . getBlock ();
if ( block . hasTileEntity ( state ))
{
TileEntity tileEntity = world . getTileEntity ( blockPos );
2018-09-09 17:27:15 -04:00
if ( tileEntity != null )
2016-06-03 18:26:41 -07:00
{
return tileEntity . getCapability ( CapabilityFluidHandler . FLUID_HANDLER_CAPABILITY , side );
}
}
2019-01-27 01:38:53 -05:00
return LazyOptional . empty ();
2016-05-16 22:20:31 +02:00
}
2016-06-03 18:26:41 -07:00
/**
* Attempts to pick up a fluid in the world and put it in an empty container item.
*
2016-10-10 00:44:50 -07:00
* @param emptyContainer The empty container to fill.
* Will not be modified directly, if modifications are necessary a modified copy is returned in the result.
2016-06-03 18:26:41 -07:00
* @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.
2016-10-10 00:44:50 -07:00
* @return a {@link FluidActionResult} holding the result and the resulting container.
2016-06-03 18:26:41 -07:00
*/
2016-11-13 17:09:54 -05:00
@Nonnull
2019-05-23 16:02:15 -07:00
public static FluidActionResult tryPickUpFluid ( @Nonnull ItemStack emptyContainer , @Nullable PlayerEntity playerIn , World worldIn , BlockPos pos , Direction side )
2016-06-03 18:26:41 -07:00
{
2016-12-21 15:52:30 -08:00
if ( emptyContainer . isEmpty () || worldIn == null || pos == null )
2016-10-10 00:44:50 -07:00
{
return FluidActionResult . FAILURE ;
2016-06-03 18:26:41 -07:00
}
2019-05-23 16:02:15 -07:00
BlockState state = worldIn . getBlockState ( pos );
2016-08-12 01:36:18 -07:00
Block block = state . getBlock ();
2019-08-29 03:01:11 +02:00
if ( block instanceof IFluidBlock )
2016-06-03 18:26:41 -07:00
{
2019-08-29 03:01:11 +02:00
IFluidHandler targetFluidHandler = getFluidHandler ( worldIn , pos , side ). orElse ( null );
2016-08-12 01:36:18 -07:00
if ( targetFluidHandler != null )
{
2017-05-01 17:25:45 -07:00
return tryFillContainer ( emptyContainer , targetFluidHandler , Integer . MAX_VALUE , playerIn , true );
2016-08-12 01:36:18 -07:00
}
2019-08-29 03:01:11 +02:00
}
2016-10-10 00:44:50 -07:00
return FluidActionResult . FAILURE ;
2016-01-02 14:38:18 +01:00
}
2016-12-05 20:17:47 -08:00
/**
2019-07-28 21:35:30 +02:00
* ItemStack version of {@link #tryPlaceFluid(PlayerEntity, World, BlockPos, IFluidHandler, FluidStack)}.
2017-05-01 17:25:45 -07:00
* Use the returned {@link FluidActionResult} to update the container ItemStack.
2016-12-05 20:17:47 -08:00
*
* @param player Player who places the fluid. May be null for blocks like dispensers.
* @param world World to place the fluid in
2019-12-11 03:27:07 +01:00
* @param hand
2016-12-05 20:17:47 -08:00
* @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
2019-05-23 16:02:15 -07:00
public static FluidActionResult tryPlaceFluid ( @Nullable PlayerEntity player , World world , Hand hand , BlockPos pos , @Nonnull ItemStack container , FluidStack resource )
2017-05-01 17:25:45 -07:00
{
ItemStack containerCopy = ItemHandlerHelper . copyStackWithSize ( container , 1 ); // do not modify the input
2018-09-09 17:27:15 -04:00
return getFluidHandler ( containerCopy )
2019-05-23 16:02:15 -07:00
. filter ( handler -> tryPlaceFluid ( player , world , hand , pos , handler , resource ))
2018-09-09 17:27:15 -04:00
. map ( IFluidHandlerItem :: getContainer )
. map ( FluidActionResult :: new )
. orElse ( FluidActionResult . FAILURE );
2017-05-01 17:25:45 -07:00
}
/**
* 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.
*
2019-07-28 21:35:30 +02:00
* Modeled after {@link ItemBucket#tryPlaceContainedLiquid(PlayerEntity, World, BlockPos)}
2017-05-01 17:25:45 -07:00
*
* @param player Player who places the fluid. May be null for blocks like dispensers.
* @param world World to place the fluid in
2019-12-11 03:27:07 +01:00
* @param hand
2017-05-01 17:25:45 -07:00
* @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
*/
2019-05-23 16:02:15 -07:00
public static boolean tryPlaceFluid ( @Nullable PlayerEntity player , World world , Hand hand , BlockPos pos , IFluidHandler fluidSource , FluidStack resource )
2016-12-05 20:17:47 -08:00
{
2019-08-29 03:01:11 +02:00
if ( world == null || pos == null )
2016-12-05 20:17:47 -08:00
{
2017-05-01 17:25:45 -07:00
return false ;
2016-12-05 20:17:47 -08:00
}
Fluid fluid = resource . getFluid ();
2019-07-28 21:35:30 +02:00
if ( fluid == null || ! fluid . getAttributes (). canBePlacedInWorld ( world , pos , resource ))
2016-12-05 20:17:47 -08:00
{
2017-05-01 17:25:45 -07:00
return false ;
}
2019-08-29 21:09:42 +02:00
if ( fluidSource . drain ( resource , IFluidHandler . FluidAction . SIMULATE ). isEmpty ())
2017-05-01 17:25:45 -07:00
{
return false ;
2016-12-05 20:17:47 -08:00
}
2019-05-23 16:02:15 -07:00
BlockItemUseContext context = new BlockItemUseContext ( new ItemUseContext ( player , hand , new BlockRayTraceResult ( Vec3d . ZERO , Direction . UP , pos , false ))); //TODO: This neds proper context...
2018-09-20 23:50:50 -07:00
2016-12-05 20:17:47 -08:00
// check that we can place the fluid at the destination
2019-05-23 16:02:15 -07:00
BlockState destBlockState = world . getBlockState ( pos );
2016-12-05 20:17:47 -08:00
Material destMaterial = destBlockState . getMaterial ();
boolean isDestNonSolid = ! destMaterial . isSolid ();
2018-09-22 03:40:22 -07:00
boolean isDestReplaceable = destBlockState . isReplaceable ( context );
2016-12-05 20:17:47 -08:00
if ( ! world . isAirBlock ( pos ) && ! isDestNonSolid && ! isDestReplaceable )
{
2017-05-01 17:25:45 -07:00
return false ; // Non-air, solid, unreplacable block. We can't put fluid here.
2016-12-05 20:17:47 -08:00
}
2019-07-28 21:35:30 +02:00
if ( world . dimension . doesWaterVaporize () && fluid . getAttributes (). doesVaporize ( world , pos , resource ))
2016-12-05 20:17:47 -08:00
{
2019-07-28 21:35:30 +02:00
FluidStack result = fluidSource . drain ( resource , IFluidHandler . FluidAction . EXECUTE );
2019-08-29 21:09:42 +02:00
if ( ! result . isEmpty ())
2017-05-01 17:25:45 -07:00
{
2019-07-28 21:35:30 +02:00
result . getFluid (). getAttributes (). vaporize ( player , world , pos , result );
2017-05-01 17:25:45 -07:00
return true ;
}
2016-12-05 20:17:47 -08:00
}
else
{
2017-05-01 17:25:45 -07:00
// This fluid handler places the fluid block when filled
IFluidHandler handler = getFluidBlockHandler ( fluid , world , pos );
FluidStack result = tryFluidTransfer ( handler , fluidSource , resource , true );
2019-08-29 21:09:42 +02:00
if ( ! result . isEmpty ())
2016-12-05 20:17:47 -08:00
{
2019-07-28 21:35:30 +02:00
SoundEvent soundevent = resource . getFluid (). getAttributes (). getEmptySound ( resource );
2017-05-01 17:25:45 -07:00
world . playSound ( player , pos , soundevent , SoundCategory . BLOCKS , 1 . 0F , 1 . 0F );
return true ;
2016-12-05 20:17:47 -08:00
}
2017-05-01 17:25:45 -07:00
}
return false ;
}
2016-12-05 20:17:47 -08:00
2017-05-01 17:25:45 -07:00
/**
* Internal method for getting a fluid block handler for placing a fluid.
*
2019-07-28 21:35:30 +02:00
* Modders: Instead of this method, use {@link #tryPlaceFluid(PlayerEntity, World, BlockPos, ItemStack, FluidStack)}
* or {@link #tryPlaceFluid(PlayerEntity, World, BlockPos, IFluidHandler, FluidStack)}
2017-05-01 17:25:45 -07:00
*/
private static IFluidHandler getFluidBlockHandler ( Fluid fluid , World world , BlockPos pos )
{
2019-08-29 03:01:11 +02:00
BlockState state = fluid . getAttributes (). getBlock ( world , pos , fluid . getDefaultState ());
return new BlockWrapper ( state , world , pos );
2017-05-01 17:25:45 -07:00
}
/**
* Destroys a block when a fluid is placed in the same position.
2019-07-28 21:35:30 +02:00
* Modeled after {@link ItemBucket#tryPlaceContainedLiquid(PlayerEntity, World, BlockPos)}
2017-05-01 17:25:45 -07:00
*
* 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 )
{
2019-05-23 16:02:15 -07:00
BlockState destBlockState = world . getBlockState ( pos );
2017-05-01 17:25:45 -07:00
Material destMaterial = destBlockState . getMaterial ();
boolean isDestNonSolid = ! destMaterial . isSolid ();
2018-09-20 23:50:50 -07:00
boolean isDestReplaceable = false ; //TODO: Needs BlockItemUseContext destBlockState.getBlock().isReplaceable(world, pos);
2017-05-01 17:25:45 -07:00
if (( isDestNonSolid || isDestReplaceable ) && ! destMaterial . isLiquid ())
2016-12-05 20:17:47 -08:00
{
2017-05-01 17:25:45 -07:00
world . destroyBlock ( pos , true );
2016-12-05 20:17:47 -08:00
}
}
}
2017-03-10 21:06:40 -08:00
/**
* @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 )
2019-08-29 03:01:11 +02:00
{
2017-03-10 21:06:40 -08:00
Fluid fluid = fluidStack . getFluid ();
2019-08-29 03:01:11 +02:00
if ( ! fluidStack . hasTag () || fluidStack . getTag (). isEmpty ())
2017-03-10 21:06:40 -08:00
{
2019-08-29 03:01:11 +02:00
if ( fluid == Fluids . WATER )
2017-03-10 21:06:40 -08:00
{
return new ItemStack ( Items . WATER_BUCKET );
}
2019-08-29 03:01:11 +02:00
else if ( fluid == Fluids . LAVA )
2017-03-10 21:06:40 -08:00
{
return new ItemStack ( Items . LAVA_BUCKET );
}
2019-08-29 03:01:11 +02:00
else if ( fluid . getRegistryName (). equals ( new ResourceLocation ( "milk" )))
2017-03-10 21:06:40 -08:00
{
return new ItemStack ( Items . MILK_BUCKET );
}
}
2019-08-29 03:01:11 +02:00
return fluid . getAttributes (). getBucket ( fluidStack );
2017-03-10 21:06:40 -08:00
}
2016-01-02 14:38:18 +01:00
}