/* * Minecraft Forge * Copyright (c) 2016. * * 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.init.Blocks; 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 boolean[] isOptimalFlowDirection = new boolean[4]; protected int[] flowCost = new int[4]; 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() == Blocks.AIR) { return 0; } if (state.getBlock() != this) { return -1; } int quantaRemaining = quantaPerBlock - state.getValue(LEVEL); return quantaRemaining; } @Override public boolean canCollideCheck(@Nonnull IBlockState state, boolean fullHit) { return fullHit && state.getValue(LEVEL) == 0; } @Override public int getMaxRenderHeightMeta() { return 0; } @Override public int getLightValue(@Nonnull IBlockState state, @Nonnull IBlockAccess world, @Nonnull BlockPos pos) { if (maxScaledLight == 0) { return super.getLightValue(state, world, pos); } int data = quantaPerBlock - state.getValue(LEVEL) - 1; return (int) (data / quantaPerBlockFloat * maxScaledLight); } @Override public void updateTick(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Random rand) { if (!isSourceBlock(world, pos) && ForgeEventFactory.canCreateFluidSource(world, pos, state, false)) { int adjacentSourceBlocks = (isSourceBlock(world, pos.north()) ? 1 : 0) + (isSourceBlock(world, pos.south()) ? 1 : 0) + (isSourceBlock(world, pos.east()) ? 1 : 0) + (isSourceBlock(world, pos.west()) ? 1 : 0); if (adjacentSourceBlocks >= 2 && (world.getBlockState(pos.up(densityDir)).getMaterial().isSolid() || isSourceBlock(world, pos.up(densityDir)))) world.setBlockState(pos, state.withProperty(LEVEL, 0)); } int quantaRemaining = quantaPerBlock - state.getValue(LEVEL); int expQuanta = -101; // check adjacent block levels if non-source if (quantaRemaining < quantaPerBlock) { if (world.getBlockState(pos.add( 0, -densityDir, 0)).getBlock() == this || world.getBlockState(pos.add(-1, -densityDir, 0)).getBlock() == this || world.getBlockState(pos.add( 1, -densityDir, 0)).getBlock() == this || world.getBlockState(pos.add( 0, -densityDir, -1)).getBlock() == this || world.getBlockState(pos.add( 0, -densityDir, 1)).getBlock() == this) { expQuanta = quantaPerBlock - 1; } else { int maxQuanta = -100; maxQuanta = getLargerQuanta(world, pos.add(-1, 0, 0), maxQuanta); maxQuanta = getLargerQuanta(world, pos.add( 1, 0, 0), maxQuanta); maxQuanta = getLargerQuanta(world, pos.add( 0, 0, -1), maxQuanta); maxQuanta = getLargerQuanta(world, pos.add( 0, 0, 1), 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); } } } // This is a "source" block, set meta to zero, and send a server only update else if (quantaRemaining >= quantaPerBlock) { world.setBlockState(pos, this.getDefaultState(), 2); } // 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); if (flowTo[0]) flowIntoBlock(world, pos.add(-1, 0, 0), flowMeta); if (flowTo[1]) flowIntoBlock(world, pos.add( 1, 0, 0), flowMeta); if (flowTo[2]) flowIntoBlock(world, pos.add( 0, 0, -1), flowMeta); if (flowTo[3]) flowIntoBlock(world, pos.add( 0, 0, 1), flowMeta); } } 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; switch (side) { case 0: pos2 = pos2.add(-1, 0, 0); break; case 1: pos2 = pos2.add( 1, 0, 0); break; case 2: pos2 = pos2.add( 0, 0, -1); break; case 3: pos2 = pos2.add( 0, 0, 1); break; } if (!canFlowInto(world, pos2) || isSourceBlock(world, pos2)) { continue; } if (canFlowInto(world, pos2.add(0, densityDir, 0))) { flowCost[side] = 0; } else { flowCost[side] = calculateFlowCost(world, pos2, 1, side); } } int min = flowCost[0]; for (int side = 1; side < 4; side++) { if (flowCost[side] < min) { min = flowCost[side]; } } 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 ((adjSide == 0 && side == 1) || (adjSide == 1 && side == 0) || (adjSide == 2 && side == 3) || (adjSide == 3 && side == 2)) { continue; } BlockPos pos2 = pos; switch (adjSide) { case 0: pos2 = pos2.add(-1, 0, 0); break; case 1: pos2 = pos2.add( 1, 0, 0); break; case 2: pos2 = pos2.add( 0, 0, -1); break; case 3: pos2 = pos2.add( 0, 0, 1); break; } if (!canFlowInto(world, pos2) || isSourceBlock(world, pos2)) { continue; } if (canFlowInto(world, pos2.add(0, densityDir, 0))) { return recurseDepth; } if (recurseDepth >= 4) { continue; } int min = calculateFlowCost(world, pos2, recurseDepth + 1, adjSide); if (min < cost) { cost = min; } } return cost; } protected void flowIntoBlock(World world, BlockPos pos, int meta) { if (meta < 0) return; if (displaceIfPossible(world, pos)) { world.setBlockState(pos, this.getBlockState().getBaseState().withProperty(LEVEL, meta), 3); } } protected boolean canFlowInto(IBlockAccess world, BlockPos pos) { if (world.isAirBlock(pos)) return true; IBlockState state = world.getBlockState(pos); if (state.getBlock() == this) { return true; } if (displacements.containsKey(state.getBlock())) { return displacements.get(state.getBlock()); } Material material = state.getMaterial(); if (material.blocksMovement() || material == Material.WATER || material == Material.LAVA || material == Material.PORTAL) { return false; } int density = getDensity(world, pos); if (density == Integer.MAX_VALUE) { return true; } if (this.density > density) { return true; } else { return false; } } 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); } }