diff --git a/client/net/minecraftforge/client/ForgeHooksClient.java b/client/net/minecraftforge/client/ForgeHooksClient.java index b47ae3b4f..4efe3792e 100644 --- a/client/net/minecraftforge/client/ForgeHooksClient.java +++ b/client/net/minecraftforge/client/ForgeHooksClient.java @@ -11,9 +11,11 @@ import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.PixelFormat; import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.client.registry.RenderingRegistry; import net.minecraft.client.Minecraft; import net.minecraft.block.Block; +import net.minecraft.block.BlockFluid; import net.minecraft.entity.item.EntityItem; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; @@ -38,6 +40,8 @@ import net.minecraftforge.client.event.TextureLoadEvent; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.common.IArmorTextureProvider; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.RenderBlockFluid; import static net.minecraftforge.client.IItemRenderer.ItemRenderType.*; import static net.minecraftforge.client.IItemRenderer.ItemRendererHelper.*; @@ -251,6 +255,9 @@ public class ForgeHooksClient public static void onTextureStitchedPost(TextureMap map) { MinecraftForge.EVENT_BUS.post(new TextureStitchEvent.Post(map)); + + FluidRegistry.WATER.setIcons(BlockFluid.func_94424_b("water"), BlockFluid.func_94424_b("water_flow")); + FluidRegistry.LAVA.setIcons(BlockFluid.func_94424_b("lava"), BlockFluid.func_94424_b("lava_flow")); } /** @@ -305,4 +312,12 @@ public class ForgeHooksClient stencilBits = 0; } } + + /** + * Initialization of Forge Renderers. + */ + static { + FluidRegistry.renderIdFluid = RenderingRegistry.getNextAvailableRenderId(); + RenderingRegistry.registerBlockHandler(RenderBlockFluid.instance); + } } diff --git a/common/net/minecraftforge/common/ForgeDummyContainer.java b/common/net/minecraftforge/common/ForgeDummyContainer.java index 140ac978e..2815eeb8b 100644 --- a/common/net/minecraftforge/common/ForgeDummyContainer.java +++ b/common/net/minecraftforge/common/ForgeDummyContainer.java @@ -45,6 +45,7 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces public static boolean removeErroringEntities = false; public static boolean removeErroringTileEntities = false; public static boolean disableStitchedFileSaving = false; + public static boolean forceDuplicateFluidBlockCrash = true; public ForgeDummyContainer() { @@ -99,7 +100,7 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces } prop = config.get(Configuration.CATEGORY_GENERAL, "legacyFurnaceOutput", false); - prop.comment = "Controls the sides of vanilla furnaces for Forge's ISidedInventroy, Vanilla defines the output as the bottom, but mods/Forge define it as the sides. Settings this to true will restore the old side relations."; + prop.comment = "Controls the sides of vanilla furnaces for Forge's ISidedInventory, Vanilla defines the output as the bottom, but mods/Forge define it as the sides. Settings this to true will restore the old side relations."; legacyFurnaceSides = prop.getBoolean(false); prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringEntities", false); @@ -108,7 +109,7 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces if (removeErroringEntities) { - FMLLog.warning("Enableing removal of erroring Entities USE AT YOUR OWN RISK"); + FMLLog.warning("Enabling removal of erroring Entities - USE AT YOUR OWN RISK"); } prop = config.get(Configuration.CATEGORY_GENERAL, "removeErroringTileEntities", false); @@ -117,13 +118,22 @@ public class ForgeDummyContainer extends DummyModContainer implements WorldAcces if (removeErroringTileEntities) { - FMLLog.warning("Enableing removal of erroring Tile Entities USE AT YOUR OWN RISK"); + FMLLog.warning("Enabling removal of erroring Tile Entities - USE AT YOUR OWN RISK"); } prop = config.get(Configuration.CATEGORY_GENERAL, "disableStitchedFileSaving", true); prop.comment = "Set this to just disable the texture stitcher from writing the 'debug.stitched_{name}.png file to disc. Just a small performance tweak. Default: true"; disableStitchedFileSaving = prop.getBoolean(true); + prop = config.get(Configuration.CATEGORY_GENERAL, "forceDuplicateFluidBlockCrash", true); + prop.comment = "Set this to force a crash if more than one block attempts to link back to the same Fluid. Enabled by default."; + forceDuplicateFluidBlockCrash = prop.getBoolean(true); + + if (!forceDuplicateFluidBlockCrash) + { + FMLLog.warning("Disabling forced crashes on duplicate Fluid Blocks - USE AT YOUR OWN RISK"); + } + if (config.hasChanged()) { config.save(); diff --git a/common/net/minecraftforge/common/network/ForgeConnectionHandler.java b/common/net/minecraftforge/common/network/ForgeConnectionHandler.java index eae6d19f5..42ff849bb 100644 --- a/common/net/minecraftforge/common/network/ForgeConnectionHandler.java +++ b/common/net/minecraftforge/common/network/ForgeConnectionHandler.java @@ -4,8 +4,11 @@ import net.minecraft.network.INetworkManager; import net.minecraft.network.NetLoginHandler; import net.minecraft.network.packet.NetHandler; import net.minecraft.network.packet.Packet1Login; +import net.minecraft.network.packet.Packet250CustomPayload; import net.minecraft.server.MinecraftServer; +import net.minecraftforge.fluids.FluidIdMapPacket; import cpw.mods.fml.common.network.IConnectionHandler; +import cpw.mods.fml.common.network.PacketDispatcher; import cpw.mods.fml.common.network.Player; public class ForgeConnectionHandler implements IConnectionHandler { @@ -13,7 +16,10 @@ public class ForgeConnectionHandler implements IConnectionHandler { @Override public void playerLoggedIn(Player player, NetHandler netHandler, INetworkManager manager) { - + Packet250CustomPayload[] fluidPackets = ForgePacket.makePacketSet(new FluidIdMapPacket()); + for (int i = 0; i < fluidPackets.length; i++) { + PacketDispatcher.sendPacketToPlayer(fluidPackets[i], player); + } } @Override diff --git a/common/net/minecraftforge/common/network/ForgePacket.java b/common/net/minecraftforge/common/network/ForgePacket.java index ee7df1f47..fdda42cf6 100644 --- a/common/net/minecraftforge/common/network/ForgePacket.java +++ b/common/net/minecraftforge/common/network/ForgePacket.java @@ -8,6 +8,7 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.INetworkManager; import net.minecraft.network.packet.NetHandler; import net.minecraft.network.packet.Packet250CustomPayload; +import net.minecraftforge.fluids.FluidIdMapPacket; import com.google.common.base.Throwables; import com.google.common.collect.MapMaker; @@ -25,7 +26,10 @@ public abstract class ForgePacket public static final String CHANNEL_ID = "FORGE"; enum Type { - FAKE_TEMP(ForgePacket.class); + /** + * The Fluid ID map to send to the client + */ + FLUID_IDMAP(FluidIdMapPacket.class); private Class packetType; private ConcurrentMap partTracker; diff --git a/common/net/minecraftforge/fluids/BlockFluidBase.java b/common/net/minecraftforge/fluids/BlockFluidBase.java new file mode 100644 index 000000000..529a6b2c2 --- /dev/null +++ b/common/net/minecraftforge/fluids/BlockFluidBase.java @@ -0,0 +1,416 @@ + +package net.minecraftforge.fluids; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.entity.Entity; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.util.Vec3; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +/** + * 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. + * + * @author King Lemming, OvermindDL1 + * + */ +public abstract class BlockFluidBase extends Block implements IFluidBlock { + + protected final static Map defaultDisplacementIds = new HashMap(); + + static { + defaultDisplacementIds.put(Block.doorWood.blockID, false); + defaultDisplacementIds.put(Block.doorIron.blockID, false); + defaultDisplacementIds.put(Block.signPost.blockID, false); + defaultDisplacementIds.put(Block.signWall.blockID, false); + defaultDisplacementIds.put(Block.reed.blockID, false); + } + protected Map displacementIds = new HashMap(); + + protected int quantaPerBlock = 8; + protected float quantaPerBlockFloat = 8F; + protected int density = 1; + protected int densityDir = -1; + + protected int tickRate = 20; + protected int renderPass = 1; + protected int maxScaledLight = 0; + + protected final String fluidName; + + public BlockFluidBase(int id, Fluid fluid, Material material) { + + super(id, material); + this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F); + this.setTickRandomly(true); + this.disableStats(); + + this.fluidName = fluid.getName(); + this.density = fluid.density; + this.maxScaledLight = fluid.luminosity; + this.tickRate = fluid.viscosity / 200; + fluid.setBlockID(id); + + displacementIds.putAll(defaultDisplacementIds); + } + + public BlockFluidBase setQuantaPerBlock(int quantaPerBlock) { + + if (quantaPerBlock > 16 || quantaPerBlock < 1) { + quantaPerBlock = 8; + } + this.quantaPerBlock = quantaPerBlock; + this.quantaPerBlockFloat = quantaPerBlock; + 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 setTickRate(int tickRate) { + + if (tickRate <= 0) { + tickRate = 20; + } + this.tickRate = tickRate; + return this; + } + + public BlockFluidBase setRenderPass(int renderPass) { + + this.renderPass = renderPass; + return this; + } + + public BlockFluidBase setMaxScaledLight(int maxScaledLight) { + + this.maxScaledLight = maxScaledLight; + return this; + } + + /** + * Returns true if the block at (x, y, z) is displaceable. Does not displace the block. + */ + public boolean canDisplace(IBlockAccess world, int x, int y, int z) { + + int bId = world.getBlockId(x, y, z); + + if (bId == 0) { + return true; + } + if (bId == blockID) { + return false; + } + if (displacementIds.containsKey(bId)) { + return displacementIds.get(bId); + } + Material material = Block.blocksList[bId].blockMaterial; + + if (material.blocksMovement() || material == Material.portal) { + return false; + } + return true; + } + + /** + * Attempt to displace the block at (x, y, z), return true if it was displaced. + */ + public boolean displaceIfPossible(World world, int x, int y, int z) { + + int bId = world.getBlockId(x, y, z); + + if (bId == 0) { + return true; + } + if (bId == blockID) { + return false; + } + if (displacementIds.containsKey(bId)) { + if (displacementIds.get(bId)) { + Block.blocksList[bId].dropBlockAsItem(world, x, y, z, world.getBlockMetadata(x, y, z), 0); + return true; + } + return false; + } + Material material = Block.blocksList[bId].blockMaterial; + + if (material.blocksMovement() || material == Material.portal) { + return false; + } + Block.blocksList[bId].dropBlockAsItem(world, x, y, z, world.getBlockMetadata(x, y, z), 0); + return true; + } + + public abstract int getQuantaValue(IBlockAccess world, int x, int y, int z); + + @Override + public abstract boolean canCollideCheck(int meta, boolean fullHit); + + public abstract int getMaxRenderHeightMeta(); + + /* BLOCK FUNCTIONS */ + @Override + public void onBlockAdded(World world, int x, int y, int z) { + + world.scheduleBlockUpdate(x, y, z, blockID, tickRate); + } + + @Override + public void onNeighborBlockChange(World world, int x, int y, int z, int blockId) { + + world.scheduleBlockUpdate(x, y, z, blockID, tickRate); + } + + // Used to prevent updates on chunk generation + @Override + public boolean func_82506_l() { + + return false; + } + + @Override + public boolean getBlocksMovement(IBlockAccess world, int x, int y, int z) { + + return true; + } + + @Override + public AxisAlignedBB getCollisionBoundingBoxFromPool(World world, int x, int y, int z) { + + return null; + } + + @Override + public int idDropped(int par1, Random par2Random, int par3) { + + return 0; + } + + @Override + public int quantityDropped(Random par1Random) { + + return 0; + } + + @Override + public int tickRate(World world) { + + return tickRate; + } + + @Override + public void velocityToAddToEntity(World world, int x, int y, int z, Entity entity, Vec3 vec) { + + if (densityDir > 0) { + return; + } + Vec3 vec_flow = this.getFlowVector(world, x, y, z); + vec.xCoord += vec_flow.xCoord * (quantaPerBlock * 4); + vec.yCoord += vec_flow.yCoord * (quantaPerBlock * 4); + vec.zCoord += vec_flow.zCoord * (quantaPerBlock * 4); + } + + @Override + public int getLightValue(IBlockAccess world, int x, int y, int z) { + + if (maxScaledLight == 0) { + return super.getLightValue(world, x, y, z); + } + int data = world.getBlockMetadata(x, y, z); + return (int) (data / quantaPerBlockFloat * maxScaledLight); + } + + @Override + public int getRenderType() { + + return FluidRegistry.renderIdFluid; + } + + @Override + public boolean isOpaqueCube() { + + return false; + } + + @Override + public boolean renderAsNormalBlock() { + + return false; + } + + @Override + public float getBlockBrightness(IBlockAccess world, int x, int y, int z) { + + float lightThis = world.getLightBrightness(x, y, z); + float lightUp = world.getLightBrightness(x, y + 1, z); + + return lightThis > lightUp ? lightThis : lightUp; + } + + @Override + public int getMixedBrightnessForBlock(IBlockAccess world, int x, int y, int z) { + + int lightThis = world.getLightBrightnessForSkyBlocks(x, y, z, 0); + int lightUp = world.getLightBrightnessForSkyBlocks(x, y + 1, z, 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 + public int getRenderBlockPass() { + + return renderPass; + } + + @Override + public boolean shouldSideBeRendered(IBlockAccess world, int x, int y, int z, int side) { + + if (world.getBlockId(x, y, z) != blockID) { + return !world.isBlockOpaqueCube(x, y, z); + } + Material mat = world.getBlockMaterial(x, y, z); + return mat == this.blockMaterial ? false : super.shouldSideBeRendered(world, x, y, z, side); + } + + /* FLUID FUNCTIONS */ + public static final int getDensity(IBlockAccess world, int x, int y, int z) { + + Block block = Block.blocksList[world.getBlockId(x, y, z)]; + + if (!(block instanceof BlockFluidBase)) { + return Integer.MAX_VALUE; + } + return ((BlockFluidBase) block).density; + } + + public static double getFlowDirection(IBlockAccess world, int x, int y, int z) { + + Block block = Block.blocksList[world.getBlockId(x, y, z)]; + + if (!(Block.blocksList[world.getBlockId(x, y, z)] instanceof BlockFluidBase)) { + return -1000.0; + } + Vec3 vec = ((BlockFluidBase) block).getFlowVector(world, x, y, z); + return vec.xCoord == 0.0D && vec.zCoord == 0.0D ? -1000.0D : Math.atan2(vec.zCoord, vec.xCoord) - Math.PI / 2D; + } + + public final int getQuantaValueBelow(IBlockAccess world, int x, int y, int z, int belowThis) { + + int quantaRemaining = getQuantaValue(world, x, y, z); + + if (quantaRemaining >= belowThis) { + return -1; + } + return quantaRemaining; + } + + public final int getQuantaValueAbove(IBlockAccess world, int x, int y, int z, int aboveThis) { + + int quantaRemaining = getQuantaValue(world, x, y, z); + + if (quantaRemaining <= aboveThis) { + return -1; + } + return quantaRemaining; + } + + public final float getQuantaPercentage(IBlockAccess world, int x, int y, int z) { + + int quantaRemaining = getQuantaValue(world, x, y, z); + return quantaRemaining / quantaPerBlockFloat; + } + + public Vec3 getFlowVector(IBlockAccess world, int x, int y, int z) { + + Vec3 vec = world.getWorldVec3Pool().getVecFromPool(0.0D, 0.0D, 0.0D); + int decay = quantaPerBlock - getQuantaValue(world, x, y, z); + + for (int side = 0; side < 4; ++side) { + int x2 = x; + int z2 = z; + + switch (side) { + case 0: + --x2; + break; + case 1: + --z2; + break; + case 2: + ++x2; + break; + case 3: + ++z2; + break; + } + + int otherDecay = quantaPerBlock - getQuantaValue(world, x2, y, z2); + + if (otherDecay >= quantaPerBlock) { + if (!world.getBlockMaterial(x2, y, z2).blocksMovement()) { + otherDecay = quantaPerBlock - getQuantaValue(world, x2, y - 1, z2); + + if (otherDecay >= 0) { + int power = otherDecay - (decay - quantaPerBlock); + vec = vec.addVector((x2 - x) * power, (y - y) * power, (z2 - z) * power); + } + } + } else if (otherDecay >= 0) { + int power = otherDecay - decay; + vec = vec.addVector((x2 - x) * power, (y - y) * power, (z2 - z) * power); + } + } + if (world.getBlockId(x, y + 1, z) == blockID) { + boolean flag = false; + + if (this.isBlockSolid(world, x, y, z - 1, 2)) { + flag = true; + } else if (this.isBlockSolid(world, x, y, z + 1, 3)) { + flag = true; + } else if (this.isBlockSolid(world, x - 1, y, z, 4)) { + flag = true; + } else if (this.isBlockSolid(world, x + 1, y, z, 5)) { + flag = true; + } else if (this.isBlockSolid(world, x, y + 1, z - 1, 2)) { + flag = true; + } else if (this.isBlockSolid(world, x, y + 1, z + 1, 3)) { + flag = true; + } else if (this.isBlockSolid(world, x - 1, y + 1, z, 4)) { + flag = true; + } else if (this.isBlockSolid(world, x + 1, y + 1, z, 5)) { + flag = true; + } + if (flag) { + vec = vec.normalize().addVector(0.0D, -6.0D, 0.0D); + } + } + vec = vec.normalize(); + return vec; + } + + /* IFluidBlock */ + @Override + public Fluid getFluid() { + + return FluidRegistry.getFluid(fluidName); + } + +} diff --git a/common/net/minecraftforge/fluids/BlockFluidClassic.java b/common/net/minecraftforge/fluids/BlockFluidClassic.java new file mode 100644 index 000000000..60a1958f7 --- /dev/null +++ b/common/net/minecraftforge/fluids/BlockFluidClassic.java @@ -0,0 +1,309 @@ + +package net.minecraftforge.fluids; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +/** + * 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. + * + * @author King Lemming + * + */ +public class BlockFluidClassic extends BlockFluidBase { + + protected boolean[] isOptimalFlowDirection = new boolean[4]; + protected int[] flowCost = new int[4]; + + protected FluidStack stack; + + public BlockFluidClassic(int id, Fluid fluid, Material material) { + + super(id, fluid, material); + stack = new FluidStack(fluid, FluidContainerRegistry.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, int x, int y, int z) { + + if (world.getBlockId(x, y, z) == 0) { + return 0; + } + if (world.getBlockId(x, y, z) != blockID) { + return -1; + } + int quantaRemaining = quantaPerBlock - world.getBlockMetadata(x, y, z); + return quantaRemaining; + } + + @Override + public boolean canCollideCheck(int meta, boolean fullHit) { + + return fullHit && meta == 0; + } + + @Override + public int getMaxRenderHeightMeta() { + + return 0; + } + + @Override + public int getLightValue(IBlockAccess world, int x, int y, int z) { + + if (maxScaledLight == 0) { + return super.getLightValue(world, x, y, z); + } + int data = quantaPerBlock - world.getBlockMetadata(x, y, z) - 1; + return (int) (data / quantaPerBlockFloat * maxScaledLight); + } + + @Override + public void updateTick(World world, int x, int y, int z, Random rand) { + + int quantaRemaining = quantaPerBlock - world.getBlockMetadata(x, y, z); + int expQuanta = -101; + + // check adjacent block levels if non-source + if (quantaRemaining < quantaPerBlock) { + int y2 = y - densityDir; + + if (world.getBlockId(x, y2, z) == blockID || world.getBlockId(x - 1, y2, z) == blockID || world.getBlockId(x + 1, y2, z) == blockID + || world.getBlockId(x, y2, z - 1) == blockID || world.getBlockId(x, y2, z + 1) == blockID) { + expQuanta = quantaPerBlock - 1; + + } else { + int maxQuanta = -100; + maxQuanta = getLargerQuanta(world, x - 1, y, z, maxQuanta); + maxQuanta = getLargerQuanta(world, x + 1, y, z, maxQuanta); + maxQuanta = getLargerQuanta(world, x, y, z - 1, maxQuanta); + maxQuanta = getLargerQuanta(world, x, y, z + 1, maxQuanta); + + expQuanta = maxQuanta - 1; + } + // decay calculation + if (expQuanta != quantaRemaining) { + quantaRemaining = expQuanta; + + if (expQuanta <= 0) { + world.setBlockToAir(x, y, z); + } else { + world.setBlockMetadataWithNotify(x, y, z, quantaPerBlock - expQuanta, 3); + world.scheduleBlockUpdate(x, y, z, blockID, tickRate); + world.notifyBlocksOfNeighborChange(x, y, z, blockID); + } + } + } else if (quantaRemaining > quantaPerBlock) { + world.setBlockMetadataWithNotify(x, y, z, 0, 3); + } + // Flow vertically if possible + if (canDisplace(world, x, y + densityDir, z)) { + flowIntoBlock(world, x, y + densityDir, z, 1); + return; + } + // Flow outward if possible + int flowMeta = quantaPerBlock - quantaRemaining + 1; + if (flowMeta >= quantaPerBlock) { + return; + } + + if (isSourceBlock(world, x, y, z) || !isFlowingVertically(world, x, y, z)) { + + if (world.getBlockId(x, y - densityDir, z) == blockID) { + flowMeta = 1; + } + boolean flowTo[] = getOptimalFlowDirections(world, x, y, z); + + if (flowTo[0]) { + flowIntoBlock(world, x - 1, y, z, flowMeta); + } + if (flowTo[1]) { + flowIntoBlock(world, x + 1, y, z, flowMeta); + } + if (flowTo[2]) { + flowIntoBlock(world, x, y, z - 1, flowMeta); + } + if (flowTo[3]) { + flowIntoBlock(world, x, y, z + 1, flowMeta); + } + } + } + + public boolean isFlowingVertically(IBlockAccess world, int x, int y, int z) { + + return world.getBlockId(x, y + densityDir, z) == blockID || world.getBlockId(x, y, z) == blockID && canFlowInto(world, x, y + densityDir, z); + } + + public boolean isSourceBlock(IBlockAccess world, int x, int y, int z) { + + return world.getBlockId(x, y, z) == blockID && world.getBlockMetadata(x, y, z) == 0; + } + + protected boolean[] getOptimalFlowDirections(World world, int x, int y, int z) { + + for (int side = 0; side < 4; side++) { + flowCost[side] = 1000; + + int x2 = x; + int y2 = y; + int z2 = z; + + switch (side) { + case 0: + --x2; + break; + case 1: + ++x2; + break; + case 2: + --z2; + break; + case 3: + ++z2; + break; + } + + if (!canFlowInto(world, x2, y2, z2) || isSourceBlock(world, x2, y2, z2)) { + continue; + } + if (canFlowInto(world, x2, y2 + densityDir, z2)) { + flowCost[side] = 0; + } else { + flowCost[side] = calculateFlowCost(world, x2, y2, z2, 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, int x, int y, int z, 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; + } + + int x2 = x; + int y2 = y; + int z2 = z; + + switch (adjSide) { + case 0: + --x2; + break; + case 1: + ++x2; + break; + case 2: + --z2; + break; + case 3: + ++z2; + break; + } + + if (!canFlowInto(world, x2, y2, z2) || isSourceBlock(world, x2, y2, z2)) { + continue; + } + if (canFlowInto(world, x2, y2 + densityDir, z2)) { + return recurseDepth; + } + if (recurseDepth >= 4) { + continue; + } + int min = calculateFlowCost(world, x2, y2, z2, recurseDepth + 1, adjSide); + if (min < cost) { + cost = min; + } + } + return cost; + } + + protected void flowIntoBlock(World world, int x, int y, int z, int meta) { + + if (meta < 0) { + return; + } + if (displaceIfPossible(world, x, y, z)) { + world.setBlock(x, y, z, this.blockID, meta, 3); + } + } + + protected boolean canFlowInto(IBlockAccess world, int x, int y, int z) { + + int bId = world.getBlockId(x, y, z); + if (bId == 0) { + return true; + } + if (bId == blockID) { + return true; + } + if (displacementIds.containsKey(bId)) { + return displacementIds.get(bId); + } + Material material = Block.blocksList[bId].blockMaterial; + if (material.blocksMovement() || material == Material.water || material == Material.lava || material == Material.portal) { + return false; + } + return true; + } + + protected int getLargerQuanta(IBlockAccess world, int x, int y, int z, int compare) { + + int quantaRemaining = getQuantaValue(world, x, y, z); + + if (quantaRemaining <= 0) { + return compare; + } + return quantaRemaining >= compare ? quantaRemaining : compare; + } + + /* IFluidBlock */ + @Override + public FluidStack drain(World world, int x, int y, int z, boolean doDrain) { + + if (!isSourceBlock(world, x, y, z)) { + return null; + } + if (doDrain) { + world.setBlockToAir(x, y, z); + } + return stack.copy(); + } + + @Override + public boolean canDrain(World world, int x, int y, int z) { + + return isSourceBlock(world, x, y, z); + } + +} diff --git a/common/net/minecraftforge/fluids/BlockFluidFinite.java b/common/net/minecraftforge/fluids/BlockFluidFinite.java new file mode 100644 index 000000000..1d0e4b945 --- /dev/null +++ b/common/net/minecraftforge/fluids/BlockFluidFinite.java @@ -0,0 +1,271 @@ + +package net.minecraftforge.fluids; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +/** + * This is a cellular-automata based finite fluid block implementation. + * + * It is highly recommended that you use/extend this class for finite fluid blocks. + * + * @author OvermindDL1, KingLemming + * + */ +public class BlockFluidFinite extends BlockFluidBase { + + public BlockFluidFinite(int id, Fluid fluid, Material material) { + + super(id, fluid, material); + } + + @Override + public int getQuantaValue(IBlockAccess world, int x, int y, int z) { + + if (world.getBlockId(x, y, z) == 0) { + return 0; + } + if (world.getBlockId(x, y, z) != blockID) { + return -1; + } + int quantaRemaining = world.getBlockMetadata(x, y, z) + 1; + return quantaRemaining; + } + + @Override + public boolean canCollideCheck(int meta, boolean fullHit) { + + return fullHit && meta == quantaPerBlock - 1; + } + + @Override + public int getMaxRenderHeightMeta() { + + return quantaPerBlock - 1; + } + + @Override + public void updateTick(World world, int x, int y, int z, Random rand) { + + boolean changed = false; + int quantaRemaining = world.getBlockMetadata(x, y, z) + 1; + + // Flow vertically if possible + int prevRemaining = quantaRemaining; + quantaRemaining = tryToFlowVerticallyInto(world, x, y, z, quantaRemaining); + + if (quantaRemaining < 1) { + return; + } else if (quantaRemaining != prevRemaining) { + changed = true; + + if (quantaRemaining == 1) { + world.setBlockMetadataWithNotify(x, y, z, quantaRemaining - 1, 2); + return; + } + } else if (quantaRemaining == 1) { + return; + } + + // Flow out if possible + int lowerthan = quantaRemaining - 1; + + if (displaceIfPossible(world, x, y, z - 1)) { + world.setBlock(x, y, z - 1, 0); + } + if (displaceIfPossible(world, x, y, z + 1)) { + world.setBlock(x, y, z + 1, 0); + } + if (displaceIfPossible(world, x - 1, y, z)) { + world.setBlock(x - 1, y, z, 0); + } + if (displaceIfPossible(world, x + 1, y, z)) { + world.setBlock(x + 1, y, z, 0); + } + int north = getQuantaValueBelow(world, x, y, z - 1, lowerthan); + int south = getQuantaValueBelow(world, x, y, z + 1, lowerthan); + int west = getQuantaValueBelow(world, x - 1, y, z, lowerthan); + int east = getQuantaValueBelow(world, x + 1, y, z, lowerthan); + int total = quantaRemaining; + int count = 1; + + if (north >= 0) { + ++count; + total += north; + } + if (south >= 0) { + ++count; + total += south; + } + if (west >= 0) { + ++count; + total += west; + } + if (east >= 0) { + ++count; + total += east; + } + if (count == 1) { + if (changed) { + world.setBlockMetadataWithNotify(x, y, z, quantaRemaining - 1, 2); + } + return; + } + int each = total / count; + int rem = total % count; + if (north >= 0) { + int newnorth = each; + + if (rem == count || rem > 1 && rand.nextInt(count - rem) != 0) { + ++newnorth; + --rem; + } + if (newnorth != north) { + if (newnorth == 0) { + world.setBlock(x, y, z - 1, 0); + } else { + world.setBlock(x, y, z - 1, blockID, newnorth - 1, 2); + } + world.scheduleBlockUpdate(x, y, z - 1, blockID, tickRate); + } + --count; + } + if (south >= 0) { + int newsouth = each; + + if (rem == count || rem > 1 && rand.nextInt(count - rem) != 0) { + ++newsouth; + --rem; + } + if (newsouth != south) { + if (newsouth == 0) { + world.setBlock(x, y, z + 1, 0); + } else { + world.setBlock(x, y, z + 1, blockID, newsouth - 1, 2); + } + world.scheduleBlockUpdate(x, y, z + 1, blockID, tickRate); + } + --count; + } + if (west >= 0) { + int newwest = each; + + if (rem == count || rem > 1 && rand.nextInt(count - rem) != 0) { + ++newwest; + --rem; + } + if (newwest != west) { + if (newwest == 0) { + world.setBlock(x - 1, y, z, 0); + } else { + world.setBlock(x - 1, y, z, blockID, newwest - 1, 2); + } + world.scheduleBlockUpdate(x - 1, y, z, blockID, tickRate); + } + --count; + } + if (east >= 0) { + int neweast = each; + + if (rem == count || rem > 1 && rand.nextInt(count - rem) != 0) { + ++neweast; + --rem; + } + if (neweast != east) { + if (neweast == 0) { + world.setBlock(x + 1, y, z, 0); + } else { + world.setBlock(x + 1, y, z, blockID, neweast - 1, 2); + } + world.scheduleBlockUpdate(x + 1, y, z, blockID, tickRate); + } + --count; + } + if (rem > 0) { + ++each; + } + world.setBlockMetadataWithNotify(x, y, z, each - 1, 2); + } + + public int tryToFlowVerticallyInto(World world, int x, int y, int z, int amtToInput) { + + int otherY = y + densityDir; + + if (otherY < 0 || otherY >= world.getHeight()) { + world.setBlockToAir(x, y, z); + return 0; + } + int amt = getQuantaValueBelow(world, x, otherY, z, quantaPerBlock); + + if (amt >= 0) { + amt += amtToInput; + if (amt > quantaPerBlock) { + world.setBlock(x, otherY, z, blockID, quantaPerBlock - 1, 3); + world.scheduleBlockUpdate(x, otherY, z, blockID, tickRate); + return amt - quantaPerBlock; + } else if (amt > 0) { + world.setBlock(x, otherY, z, blockID, amt - 1, 3); + world.scheduleBlockUpdate(x, otherY, z, blockID, tickRate); + world.setBlockToAir(x, y, z); + return 0; + } + return amtToInput; + } else { + int density_other = getDensity(world, x, otherY, z); + + if (density_other == Integer.MAX_VALUE) { + if (displaceIfPossible(world, x, otherY, z)) { + world.setBlock(x, otherY, z, blockID, amtToInput - 1, 3); + world.scheduleBlockUpdate(x, otherY, z, blockID, tickRate); + world.setBlockToAir(x, y, z); + return 0; + } else { + return amtToInput; + } + } + if (densityDir < 0) { + if (density_other < density) // then swap + { + int bId = world.getBlockId(x, otherY, z); + BlockFluidBase block = (BlockFluidBase) Block.blocksList[bId]; + int otherData = world.getBlockMetadata(x, otherY, z); + world.setBlock(x, otherY, z, blockID, amtToInput - 1, 3); + world.setBlock(x, y, z, bId, otherData, 3); + world.scheduleBlockUpdate(x, otherY, z, blockID, tickRate); + world.scheduleBlockUpdate(x, y, z, bId, block.tickRate(world)); + return 0; + } + } else { + if (density_other > density) { + int bId = world.getBlockId(x, otherY, z); + BlockFluidBase block = (BlockFluidBase) Block.blocksList[bId]; + int otherData = world.getBlockMetadata(x, otherY, z); + world.setBlock(x, otherY, z, blockID, amtToInput - 1, 3); + world.setBlock(x, y, z, bId, otherData, 3); + world.scheduleBlockUpdate(x, otherY, z, blockID, tickRate); + world.scheduleBlockUpdate(x, y, z, bId, block.tickRate(world)); + return 0; + } + } + return amtToInput; + } + } + + /* IFluidBlock */ + @Override + public FluidStack drain(World world, int x, int y, int z, boolean doDrain) { + + return null; + } + + @Override + public boolean canDrain(World world, int x, int y, int z) { + + return false; + } + +} diff --git a/common/net/minecraftforge/fluids/Fluid.java b/common/net/minecraftforge/fluids/Fluid.java new file mode 100644 index 000000000..2c29fe19e --- /dev/null +++ b/common/net/minecraftforge/fluids/Fluid.java @@ -0,0 +1,327 @@ + +package net.minecraftforge.fluids; + +import java.util.Locale; + +import net.minecraft.block.Block; +import net.minecraft.util.Icon; +import net.minecraft.util.StatCollector; +import net.minecraft.world.World; +import net.minecraftforge.common.ForgeDummyContainer; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.LoaderException; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * 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. + * + * @author King Lemming + * + */ +public class Fluid { + + /** The unique identification name for this fluid. */ + protected final String fluidName; + + /** The unlocalized name of this fluid. */ + protected String unlocalizedName; + + /** The Icons for this fluid. */ + @SideOnly(Side.CLIENT) + protected Icon stillIcon; + @SideOnly(Side.CLIENT) + protected Icon flowingIcon; + + /** + * 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; + + /** + * 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). + */ + protected int viscosity = 1000; + + /** + * This indicates if the fluid is gaseous. + * + * Useful for rendering the fluid in containers and the world. + * + * Generally this is associated with negative density fluids. + */ + protected boolean isGaseous; + + /** + * If there is a Block implementation of the Fluid, the BlockID is linked here. + * + * The default value of -1 should remain for any Fluid without a Block implementation. + */ + protected int blockID = -1; + + public Fluid(String fluidName) { + + this.fluidName = fluidName.toLowerCase(Locale.ENGLISH); + this.unlocalizedName = fluidName; + } + + public Fluid setUnlocalizedName(String unlocalizedName) { + + this.unlocalizedName = unlocalizedName; + return this; + } + + public Fluid setBlockID(int blockID) { + + if (this.blockID == -1 || this.blockID == blockID) { + this.blockID = blockID; + } else if (!ForgeDummyContainer.forceDuplicateFluidBlockCrash) { + FMLLog.warning("A mod has attempted to assign BlockID " + blockID + " to the Fluid '" + fluidName + "' but this Fluid has already been linked to BlockID " + + this.blockID + ". Configure your mods to prevent this from happening."); + } else { + FMLLog.severe("A mod has attempted to assign BlockID " + blockID + " to the Fluid '" + fluidName + "' but this Fluid has already been linked to BlockID " + + this.blockID + ". Configure your mods to prevent this from happening."); + throw new LoaderException(new RuntimeException("A mod has attempted to assign BlockID " + blockID + " to the Fluid '" + fluidName + + "' but this Fluid has already been linked to BlockID " + this.blockID + ". Configure your mods to prevent this from happening.")); + } + return this; + } + + public Fluid setBlockID(Block block) { + + return setBlockID(block.blockID); + } + + public Fluid setLuminosity(int luminosity) { + + this.luminosity = luminosity; + return this; + } + + public Fluid setDensity(int density) { + + this.density = density; + return this; + } + + public Fluid setViscosity(int viscosity) { + + this.viscosity = viscosity; + return this; + } + + public Fluid setGaseous(boolean isGaseous) { + + this.isGaseous = isGaseous; + return this; + } + + public final String getName() { + + return this.fluidName; + } + + public final int getID() { + + return FluidRegistry.getFluidID(this.fluidName); + } + + public final int getBlockID() { + + return blockID; + } + + public final boolean canBePlacedInWorld() { + + return blockID != -1; + } + + /** + * Returns the localized name of this fluid. + */ + public String getLocalizedName() { + + String s = this.getUnlocalizedName(); + return s == null ? "" : StatCollector.translateToLocal(s); + } + + /** + * Returns the unlocalized name of this fluid. + */ + public String getUnlocalizedName() { + + return "fluid." + this.unlocalizedName; + } + + /** + * Returns 0 for "/terrain.png". ALL FLUID TEXTURES MUST BE ON THIS SHEET. + */ + public final int getSpriteNumber() { + + return 0; + } + + /* Default Accessors */ + public final int getLuminosity() { + + return this.luminosity; + } + + public final int getDensity() { + + return this.density; + } + + public final int getViscosity() { + + return this.viscosity; + } + + public final boolean isGaseous() { + + return this.isGaseous; + } + + public int getColor() { + + return 0xFFFFFF; + } + + /* Stack-based Accessors */ + public int getLuminosity(FluidStack stack) { + + return getLuminosity(); + } + + public int getDensity(FluidStack stack) { + + return getDensity(); + } + + public int getViscosity(FluidStack stack) { + + return getViscosity(); + } + + public boolean isGaseous(FluidStack stack) { + + return isGaseous(); + } + + public int getColor(FluidStack stack) { + + return getColor(); + } + + /* World-based Accessors */ + public int getLuminosity(World world, int x, int y, int z) { + + return getLuminosity(); + } + + public int getDensity(World world, int x, int y, int z) { + + return getDensity(); + } + + public int getViscosity(World world, int x, int y, int z) { + + return getViscosity(); + } + + public boolean isGaseous(World world, int x, int y, int z) { + + return isGaseous(); + } + + public int getColor(World world, int x, int y, int z) { + + return getColor(); + } + + @SideOnly(Side.CLIENT) + public final Fluid setStillIcon(Icon stillIcon) { + + this.stillIcon = stillIcon; + return this; + } + + @SideOnly(Side.CLIENT) + public final Fluid setFlowingIcon(Icon flowingIcon) { + + this.flowingIcon = flowingIcon; + return this; + } + + @SideOnly(Side.CLIENT) + public final Fluid setIcons(Icon stillIcon, Icon flowingIcon) { + + this.stillIcon = stillIcon; + this.flowingIcon = flowingIcon; + return this; + } + + @SideOnly(Side.CLIENT) + public final Fluid setIcons(Icon commonIcon) { + + this.stillIcon = commonIcon; + this.flowingIcon = commonIcon; + return this; + } + + @SideOnly(Side.CLIENT) + public Icon getIcon() { + + return getStillIcon(); + } + + @SideOnly(Side.CLIENT) + public Icon getIcon(FluidStack stack) { + + return getIcon(); + } + + @SideOnly(Side.CLIENT) + public Icon getIcon(World world, int x, int y, int z) { + + return getIcon(); + } + + @SideOnly(Side.CLIENT) + public Icon getStillIcon() { + + return this.stillIcon; + } + + @SideOnly(Side.CLIENT) + public Icon getFlowingIcon() { + + return this.flowingIcon; + } + +} diff --git a/common/net/minecraftforge/fluids/FluidContainerRegistry.java b/common/net/minecraftforge/fluids/FluidContainerRegistry.java new file mode 100644 index 000000000..c94880e32 --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidContainerRegistry.java @@ -0,0 +1,260 @@ + +package net.minecraftforge.fluids; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.Event; + +/** + * Register simple items that contain fluids here. Useful for buckets, bottles, and things that have + * ID/metadata mappings. + * + * For more complex items, use {@link IFluidContainerItem} instead. + * + * @author King Lemming + * + */ +public abstract class FluidContainerRegistry { + + private static Map containerFluidMap = new HashMap(); + private static Map filledContainerMap = new HashMap(); + private static Set emptyContainers = new HashSet(); + + public static final int BUCKET_VOLUME = 1000; + public static final ItemStack EMPTY_BUCKET = new ItemStack(Item.bucketEmpty); + public static final ItemStack EMPTY_BOTTLE = new ItemStack(Item.glassBottle); + + static { + registerFluidContainer(FluidRegistry.WATER, new ItemStack(Item.bucketWater), EMPTY_BUCKET); + registerFluidContainer(FluidRegistry.LAVA, new ItemStack(Item.bucketLava), EMPTY_BUCKET); + registerFluidContainer(FluidRegistry.WATER, new ItemStack(Item.potion), EMPTY_BOTTLE); + } + + private FluidContainerRegistry() { + + } + + /** + * Register a new fluid containing item. + * + * @param stack + * FluidStack containing the type and amount of the fluid stored in the item. + * @param filledContainer + * ItemStack representing the container when it is full. + * @param emptyContainer + * ItemStack representing the container when it is empty. + * @return True if container was successfully registered; false if it already is. + */ + public static boolean registerFluidContainer(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer) { + + return registerFluidContainer(new FluidContainerData(stack, filledContainer, emptyContainer)); + } + + /** + * Register a new fluid containing item. The item is assumed to hold 1000 mB of fluid. Also + * registers the Fluid if possible. + * + * @param fluid + * Fluid type that is stored in the item. + * @param filledContainer + * ItemStack representing the container when it is full. + * @param emptyContainer + * ItemStack representing the container when it is empty. + * @return True if container was successfully registered; false if it already is. + */ + public static boolean registerFluidContainer(Fluid fluid, ItemStack filledContainer, ItemStack emptyContainer) { + + if (!FluidRegistry.isFluidRegistered(fluid)) { + FluidRegistry.registerFluid(fluid); + } + return registerFluidContainer(new FluidStack(fluid, BUCKET_VOLUME), filledContainer, emptyContainer); + } + + /** + * Register a new fluid containing item that does not have an empty container. + * + * @param stack + * FluidStack containing the type and amount of the fluid stored in the item. + * @param filledContainer + * ItemStack representing the container when it is full. + * @return True if container was successfully registered; false if it already is. + */ + public static boolean registerFluidContainer(FluidStack stack, ItemStack filledContainer) { + + return registerFluidContainer(new FluidContainerData(stack, filledContainer, null, true)); + } + + /** + * Register a new fluid containing item that does not have an empty container. The item is + * assumed to hold 1000 mB of fluid. Also registers the Fluid if possible. + * + * @param fluid + * Fluid type that is stored in the item. + * @param filledContainer + * ItemStack representing the container when it is full. + * @return True if container was successfully registered; false if it already is. + */ + public static boolean registerFluidContainer(Fluid fluid, ItemStack filledContainer) { + + if (!FluidRegistry.isFluidRegistered(fluid)) { + FluidRegistry.registerFluid(fluid); + } + return registerFluidContainer(new FluidStack(fluid, BUCKET_VOLUME), filledContainer); + } + + /** + * Register a new fluid containing item. + * + * @param data + * See {@link FluidContainerData}. + * @return True if container was successfully registered; false if it already is. + */ + public static boolean registerFluidContainer(FluidContainerData data) { + + if (isFilledContainer(data.filledContainer)) { + return false; + } + containerFluidMap.put(Arrays.asList(data.filledContainer.itemID, data.filledContainer.getItemDamage()), data); + + if (data.emptyContainer != null) { + filledContainerMap.put(Arrays.asList(data.emptyContainer.itemID, data.emptyContainer.getItemDamage(), data.fluid.fluidID), data); + emptyContainers.add(Arrays.asList(data.emptyContainer.itemID, data.emptyContainer.getItemDamage())); + } + MinecraftForge.EVENT_BUS.post(new FluidContainerRegisterEvent(data)); + return true; + } + + /** + * Determines the fluid type and amount inside a container. + * + * @param container + * The fluid container. + * @return FluidStack representing stored fluid. + */ + public static FluidStack getFluidForFilledItem(ItemStack container) { + + if (container == null) { + return null; + } + FluidContainerData data = containerFluidMap.get(Arrays.asList(container.itemID, container.getItemDamage())); + return data == null ? null : data.fluid.copy(); + } + + /** + * Attempts to fill an empty container with a fluid. + * + * NOTE: Returns null on fail, NOT the empty container. + * + * @param fluid + * FluidStack containing the type and amount of fluid to fill. + * @param container + * ItemStack representing the empty container. + * @return Filled container if successful, otherwise null. + */ + public static ItemStack fillFluidContainer(FluidStack fluid, ItemStack container) { + + if (container == null || fluid == null) { + return null; + } + FluidContainerData data = filledContainerMap.get(Arrays.asList(container.itemID, container.getItemDamage(), fluid.fluidID)); + if (data != null && fluid.amount >= data.fluid.amount) { + return data.filledContainer.copy(); + } + return null; + } + + /** + * Determines if a container holds a specific fluid. + */ + public static boolean containsFluid(ItemStack container, FluidStack fluid) { + + if (container == null || fluid == null) { + return false; + } + FluidContainerData data = filledContainerMap.get(Arrays.asList(container.itemID, container.getItemDamage(), fluid.fluidID)); + return data == null ? false : data.fluid.isFluidEqual(fluid); + } + + public static boolean isBucket(ItemStack container) { + + if (container == null) { + return false; + } + if (container.isItemEqual(EMPTY_BUCKET)) { + return true; + } + FluidContainerData data = containerFluidMap.get(Arrays.asList(container.itemID, container.getItemDamage())); + return data != null && data.emptyContainer.isItemEqual(EMPTY_BUCKET); + } + + public static boolean isContainer(ItemStack container) { + + return isEmptyContainer(container) || isFilledContainer(container); + } + + public static boolean isEmptyContainer(ItemStack container) { + + return container != null && emptyContainers.contains(Arrays.asList(container.itemID, container.getItemDamage())); + } + + public static boolean isFilledContainer(ItemStack container) { + + return container != null && getFluidForFilledItem(container) != null; + } + + public static FluidContainerData[] getRegisteredFluidContainerData() { + + return (FluidContainerData[]) containerFluidMap.values().toArray(); + } + + /** + * Wrapper class for the registry entries. Ensures that none of the attempted registrations + * contain null references unless permitted. + */ + public static class FluidContainerData { + + public final FluidStack fluid; + public final ItemStack filledContainer; + public final ItemStack emptyContainer; + + public FluidContainerData(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer) { + + this(stack, filledContainer, emptyContainer, false); + } + + public FluidContainerData(FluidStack stack, ItemStack filledContainer, ItemStack emptyContainer, boolean nullEmpty) { + + this.fluid = stack; + this.filledContainer = filledContainer; + this.emptyContainer = emptyContainer; + + if (stack == null || filledContainer == null || emptyContainer == null && !nullEmpty) { + throw new RuntimeException("Invalid FluidContainerData - a parameter was null."); + } + } + + public FluidContainerData copy() { + + return new FluidContainerData(fluid, filledContainer, emptyContainer, true); + } + } + + public static class FluidContainerRegisterEvent extends Event { + + public final FluidContainerData data; + + public FluidContainerRegisterEvent(FluidContainerData data) { + + this.data = data.copy(); + } + } + +} diff --git a/common/net/minecraftforge/fluids/FluidEvent.java b/common/net/minecraftforge/fluids/FluidEvent.java new file mode 100644 index 000000000..328ba706b --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidEvent.java @@ -0,0 +1,100 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.Event; + +public class FluidEvent extends Event { + + public final FluidStack fluid; + public final int x; + public final int y; + public final int z; + public final World world; + + public FluidEvent(FluidStack fluid, World world, int x, int y, int z) { + + this.fluid = fluid; + this.world = world; + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Mods should fire this event when they move fluids around. + * + * @author cpw + * + */ + public static class FluidMotionEvent extends FluidEvent { + + public FluidMotionEvent(FluidStack fluid, World world, int x, int y, int z) { + + super(fluid, world, x, y, z); + } + } + + /** + * Mods should fire this event when a fluid is {@link IFluidTank#fill(FluidStack, boolean)} + * their tank implementation. {@link FluidTank} does. + * + * @author cpw + * + */ + public static class FluidFillingEvent extends FluidEvent { + + public final IFluidTank tank; + + public FluidFillingEvent(FluidStack fluid, World world, int x, int y, int z, IFluidTank tank) { + + super(fluid, world, x, y, z); + this.tank = tank; + } + } + + /** + * Mods should fire this event when a fluid is {@link IFluidTank#drain(int, boolean)} from their + * tank. + * + * @author cpw + * + */ + public static class FluidDrainingEvent extends FluidEvent { + + public final IFluidTank tank; + + public FluidDrainingEvent(FluidStack fluid, World world, int x, int y, int z, IFluidTank tank) { + + super(fluid, world, x, y, z); + this.tank = tank; + } + } + + /** + * Mods should fire this event when a fluid "spills", for example, if a block containing fluid + * is broken. + * + * @author cpw + * + */ + public static class FluidSpilledEvent extends FluidEvent { + + public FluidSpilledEvent(FluidStack fluid, World world, int x, int y, int z) { + + super(fluid, world, x, y, z); + } + } + + /** + * A handy shortcut for firing the various fluid events. + * + * @param event + */ + public static final void fireEvent(FluidEvent event) { + + MinecraftForge.EVENT_BUS.post(event); + } + +} diff --git a/common/net/minecraftforge/fluids/FluidIdMapPacket.java b/common/net/minecraftforge/fluids/FluidIdMapPacket.java new file mode 100644 index 000000000..2baead08d --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidIdMapPacket.java @@ -0,0 +1,52 @@ + +package net.minecraftforge.fluids; + +import java.util.Map; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.network.INetworkManager; +import net.minecraftforge.common.network.ForgePacket; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; + +public class FluidIdMapPacket extends ForgePacket { + + private BiMap fluidIds = HashBiMap.create(); + + @Override + public byte[] generatePacket(Object... data) { + + ByteArrayDataOutput dat = ByteStreams.newDataOutput(); + + dat.writeInt(FluidRegistry.maxID); + for (Map.Entry entry : FluidRegistry.fluidIDs.entrySet()) { + dat.writeUTF(entry.getKey()); + dat.writeInt(entry.getValue()); + } + return dat.toByteArray(); + } + + @Override + public ForgePacket consumePacket(byte[] data) { + + ByteArrayDataInput dat = ByteStreams.newDataInput(data); + int listSize = dat.readInt(); + for (int i = 0; i < listSize; i++) { + String fluidName = dat.readUTF(); + int fluidId = dat.readInt(); + fluidIds.put(fluidName, fluidId); + } + return this; + } + + @Override + public void execute(INetworkManager network, EntityPlayer player) { + + FluidRegistry.initFluidIDs(fluidIds); + } + +} diff --git a/common/net/minecraftforge/fluids/FluidRegistry.java b/common/net/minecraftforge/fluids/FluidRegistry.java new file mode 100644 index 000000000..34bd87fc1 --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidRegistry.java @@ -0,0 +1,142 @@ + +package net.minecraftforge.fluids; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.block.Block; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.Event; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableMap; + +/** + * Handles Fluid registrations. Fluids MUST be registered in order to function. + * + * @author King Lemming, CovertJaguar (LiquidDictionary) + * + */ +public abstract class FluidRegistry { + + static int maxID = 0; + + static HashMap fluids = new HashMap(); + static BiMap fluidIDs = HashBiMap.create(); + + public static final Fluid WATER = new Fluid("water").setBlockID(Block.waterStill.blockID); + public static final Fluid LAVA = new Fluid("lava").setBlockID(Block.lavaStill.blockID).setLuminosity(15).setDensity(3000).setViscosity(6000); + + public static int renderIdFluid = -1; + + static { + registerFluid(WATER); + registerFluid(LAVA); + } + + private FluidRegistry() { + + } + + /** + * Called by Forge to prepare the ID map for server -> client sync. + */ + static void initFluidIDs(BiMap newfluidIDs) { + + maxID = newfluidIDs.size(); + fluidIDs.clear(); + fluidIDs.putAll(newfluidIDs); + } + + /** + * Register a new Fluid. If a fluid with the same name already exists, registration is denied. + * + * @param fluid + * The fluid to register. + * @return True if the fluid was successfully registered; false if there is a name clash. + */ + public static boolean registerFluid(Fluid fluid) { + + if (fluidIDs.containsKey(fluid.getName())) { + return false; + } + fluids.put(fluid.getName(), fluid); + fluidIDs.put(fluid.getName(), ++maxID); + + MinecraftForge.EVENT_BUS.post(new FluidRegisterEvent(fluid.getName(), maxID)); + return true; + } + + public static boolean isFluidRegistered(Fluid fluid) { + + return fluidIDs.containsKey(fluid.getName()); + } + + public static boolean isFluidRegistered(String fluidName) { + + return fluidIDs.containsKey(fluidName); + } + + public static Fluid getFluid(String fluidName) { + + return fluids.get(fluidName); + } + + public static Fluid getFluid(int fluidID) { + + return fluids.get(getFluidName(fluidID)); + } + + public static String getFluidName(int fluidID) { + + return fluidIDs.inverse().get(fluidID); + } + + public static String getFluidName(FluidStack stack) { + + return getFluidName(stack.fluidID); + } + + public static int getFluidID(String fluidName) { + + return fluidIDs.get(fluidName); + } + + public static FluidStack getFluidStack(String fluidName, int amount) { + + if (!fluidIDs.containsKey(fluidName)) { + return null; + } + return new FluidStack(getFluidID(fluidName), amount); + } + + /** + * Returns a read-only map containing Fluid Names and their associated Fluids. + */ + public static Map getRegisteredFluids() { + + return ImmutableMap.copyOf(fluids); + } + + /** + * Returns a read-only map containing Fluid Names and their associated IDs. + */ + public static Map getRegisteredFluidIDs() { + + return ImmutableMap.copyOf(fluidIDs); + } + + public static class FluidRegisterEvent extends Event { + + public final String fluidName; + public final int fluidID; + + public FluidRegisterEvent(String fluidName, int fluidID) { + + this.fluidName = fluidName; + this.fluidID = fluidID; + } + } + +} diff --git a/common/net/minecraftforge/fluids/FluidStack.java b/common/net/minecraftforge/fluids/FluidStack.java new file mode 100644 index 000000000..ede8942b1 --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidStack.java @@ -0,0 +1,177 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +/** + * 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. + * + * @author King Lemming, SirSengir (LiquidStack) + * + */ +public class FluidStack { + + public int fluidID; + public int amount; + public NBTTagCompound tag; + + public FluidStack(Fluid fluid, int amount) { + + this.fluidID = fluid.getID(); + this.amount = amount; + } + + public FluidStack(int fluidID, int amount) { + + this.fluidID = fluidID; + this.amount = amount; + } + + public FluidStack(int fluidID, int amount, NBTTagCompound nbt) { + + this(fluidID, amount); + + if (nbt != null) { + tag = (NBTTagCompound) nbt.copy(); + } + } + + public FluidStack(FluidStack stack, int amount) { + + this(stack.fluidID, amount, stack.tag); + } + + /** + * This provides a safe method for retrieving a FluidStack - if the Fluid is invalid, the stack + * will return as null. + */ + public static FluidStack loadFluidStackFromNBT(NBTTagCompound nbt) { + + if (nbt == null || FluidRegistry.getFluid(nbt.getString("FluidName")) == null) { + return null; + } + FluidStack stack = new FluidStack(FluidRegistry.getFluidID(nbt.getString("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(fluidID)); + nbt.setInteger("Amount", amount); + + if (tag != null) { + nbt.setTag("Tag", tag); + } + return nbt; + } + + public final Fluid getFluid() { + + return FluidRegistry.getFluid(fluidID); + } + + /** + * @return A copy of this FluidStack + */ + public FluidStack copy() { + + return new FluidStack(fluidID, 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(FluidStack other) { + + return other != null && fluidID == other.fluidID && 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(FluidStack stack1, 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(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; + } + if (other.getItem() instanceof IFluidContainerItem) { + return isFluidEqual(((IFluidContainerItem) other.getItem()).getFluid(other)); + } + return isFluidEqual(FluidContainerRegistry.getFluidForFilledItem(other)); + } + + @Override + public final int hashCode() { + + return fluidID; + } + + /** + * 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); + } + +} diff --git a/common/net/minecraftforge/fluids/FluidTank.java b/common/net/minecraftforge/fluids/FluidTank.java new file mode 100644 index 000000000..ee39fa515 --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidTank.java @@ -0,0 +1,161 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; + +/** + * Reference implementation of {@link IFluidTank}. Use/extend this or implement your own. + * + * @author King Lemming, cpw (LiquidTank) + * + */ +public class FluidTank implements IFluidTank { + + protected FluidStack fluid; + protected int capacity; + protected TileEntity tile; + + public FluidTank(int capacity) { + + this(null, capacity); + } + + public FluidTank(FluidStack stack, int capacity) { + + this.fluid = stack; + 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); + + if (fluid != null) { + setFluid(fluid); + } + } + return this; + } + + public NBTTagCompound writeToNBT(NBTTagCompound nbt) { + + if (fluid != null) { + fluid.writeToNBT(nbt); + } else { + nbt.setString("Empty", ""); + } + return nbt; + } + + public void setFluid(FluidStack fluid) { + + this.fluid = fluid; + } + + public void setCapacity(int capacity) { + + this.capacity = capacity; + } + + /* IFluidTank */ + @Override + public FluidStack getFluid() { + + return fluid; + } + + @Override + public int getFluidAmount() { + + if (fluid == null) { + return 0; + } + return fluid.amount; + } + + @Override + public int getCapacity() { + + return capacity; + } + + @Override + public FluidTankInfo getInfo() { + + return new FluidTankInfo(this); + } + + @Override + public int fill(FluidStack resource, boolean doFill) { + + if (resource == null) { + 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)); + + if (tile != null) { + FluidEvent.fireEvent(new FluidEvent.FluidFillingEvent(fluid, tile.worldObj, tile.xCoord, tile.yCoord, tile.zCoord, this)); + } + 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; + } + if (tile != null) { + FluidEvent.fireEvent(new FluidEvent.FluidFillingEvent(fluid, tile.worldObj, tile.xCoord, tile.yCoord, tile.zCoord, this)); + } + return filled; + } + + @Override + public FluidStack drain(int maxDrain, boolean doDrain) { + + if (fluid == null) { + 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; + } + if (tile != null) { + FluidEvent.fireEvent(new FluidEvent.FluidDrainingEvent(fluid, tile.worldObj, tile.xCoord, tile.yCoord, tile.zCoord, this)); + } + } + return stack; + } + +} diff --git a/common/net/minecraftforge/fluids/FluidTankInfo.java b/common/net/minecraftforge/fluids/FluidTankInfo.java new file mode 100644 index 000000000..8443f1452 --- /dev/null +++ b/common/net/minecraftforge/fluids/FluidTankInfo.java @@ -0,0 +1,27 @@ + +package net.minecraftforge.fluids; + +/** + * Wrapper class used to encapsulate information about an IFluidTank. + * + * @author King Lemming + * + */ +public final class FluidTankInfo { + + public final FluidStack fluid; + public final int capacity; + + public FluidTankInfo(FluidStack fluid, int capacity) { + + this.fluid = fluid; + this.capacity = capacity; + } + + public FluidTankInfo(IFluidTank tank) { + + this.fluid = tank.getFluid(); + this.capacity = tank.getCapacity(); + } + +} diff --git a/common/net/minecraftforge/fluids/IFluidBlock.java b/common/net/minecraftforge/fluids/IFluidBlock.java new file mode 100644 index 000000000..5d66980a6 --- /dev/null +++ b/common/net/minecraftforge/fluids/IFluidBlock.java @@ -0,0 +1,41 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.world.World; + +/** + * Implement this interface on Block classes which represent world-placeable Fluids. + * + * NOTE: Using/extending the reference implementations {@link BlockFluidBase} is encouraged. + * + * @author King Lemming + * + */ +public interface IFluidBlock { + + /** + * Returns the Fluid associated with this Block. + */ + Fluid getFluid(); + + /** + * 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 + */ + FluidStack drain(World world, int x, int y, int z, boolean doDrain); + + /** + * Check to see if a block can be drained. This method should be called by devices such as + * pumps. + * + * @param doDrain + * If false, the drain will only be simulated. + * @return + */ + boolean canDrain(World world, int x, int y, int z); +} diff --git a/common/net/minecraftforge/fluids/IFluidContainerItem.java b/common/net/minecraftforge/fluids/IFluidContainerItem.java new file mode 100644 index 000000000..c3ab9924a --- /dev/null +++ b/common/net/minecraftforge/fluids/IFluidContainerItem.java @@ -0,0 +1,61 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.item.ItemStack; + +/** + * Implement this interface on Item classes that support external manipulation of their internal + * fluid storage. + * + * A reference implementation is provided {@link ItemFluidContainer}. + * + * NOTE: Use of NBT data on the containing ItemStack is encouraged. + * + * @author King Lemming + * + */ +public interface IFluidContainerItem { + + /** + * + * @param container + * ItemStack which is the fluid container. + * @return FluidStack representing the fluid in the container, null if the container is empty. + */ + FluidStack getFluid(ItemStack container); + + /** + * + * @param container + * ItemStack which is the fluid container. + * @return Capacity of this fluid container. + */ + int getCapacity(ItemStack container); + + /** + * + * @param container + * ItemStack which is the fluid container. + * @param resource + * FluidStack attempting to fill the container. + * @param doFill + * If false, the fill will only be simulated. + * @return Amount of fluid that was (or would have been, if simulated) filled into the + * container. + */ + int fill(ItemStack container, FluidStack resource, boolean doFill); + + /** + * + * @param container + * ItemStack which is the fluid container. + * @param maxDrain + * Maximum amount of fluid to be removed from the container. + * @param doFill + * If false, the drain will only be simulated. + * @return Amount of fluid that was (or would have been, if simulated) drained from the + * container. + */ + FluidStack drain(ItemStack container, int maxDrain, boolean doDrain); + +} diff --git a/common/net/minecraftforge/fluids/IFluidHandler.java b/common/net/minecraftforge/fluids/IFluidHandler.java new file mode 100644 index 000000000..17e96343e --- /dev/null +++ b/common/net/minecraftforge/fluids/IFluidHandler.java @@ -0,0 +1,84 @@ + +package net.minecraftforge.fluids; + +import net.minecraftforge.common.ForgeDirection; + +/** + * Implement this interface on TileEntities which should handle fluids, generally storing them in + * one or more internal {@link IFluidTank} objects. + * + * A reference implementation is provided {@link TileFluidHandler}. + * + * @author King Lemming + * + */ +public interface IFluidHandler { + + /** + * Fills fluid into internal tanks, distribution is left entirely to the IFluidHandler. + * + * @param from + * Orientation the Fluid is pumped in from. + * @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(ForgeDirection from, FluidStack resource, boolean doFill); + + /** + * Drains fluid out of internal tanks, distribution is left entirely to the IFluidHandler. + * + * @param from + * Orientation the Fluid is drained to. + * @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. + */ + FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain); + + /** + * Drains fluid out of internal tanks, distribution is left entirely to the IFluidHandler. + * + * This method is not Fluid-sensitive. + * + * @param from + * Orientation the fluid is drained to. + * @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. + */ + FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain); + + /** + * Returns true if the given fluid can be inserted into the given direction. + * + * More formally, this should return true if fluid is able to enter from the given direction. + */ + boolean canFill(ForgeDirection from, Fluid fluid); + + /** + * Returns true if the given fluid can be extracted from the given direction. + * + * More formally, this should return true if fluid is able to leave from the given direction. + */ + boolean canDrain(ForgeDirection from, Fluid fluid); + + /** + * Returns an array of objects which represent the internal tanks. These objects cannot be used + * to manipulate the internal tanks. See {@link FluidTankInfo}. + * + * @param from + * Orientation determining which tanks should be queried. + * @return Info for the relevant internal tanks. + */ + FluidTankInfo[] getTankInfo(ForgeDirection from); + +} diff --git a/common/net/minecraftforge/fluids/IFluidTank.java b/common/net/minecraftforge/fluids/IFluidTank.java new file mode 100644 index 000000000..f88564857 --- /dev/null +++ b/common/net/minecraftforge/fluids/IFluidTank.java @@ -0,0 +1,59 @@ + +package net.minecraftforge.fluids; + +/** + * A tank is the unit of interaction with Fluid inventories. + * + * A reference implementation can be found at {@link FluidTank}. + * + * @author King Lemming, cpw (ILiquidTank) + * + */ +public interface IFluidTank { + + /** + * @return FluidStack representing the fluid in the tank, null if the tank is empty. + */ + 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 doFill + * If false, the fill will only be simulated. + * @return Amount of fluid that was removed from the tank. + */ + FluidStack drain(int maxDrain, boolean doDrain); + +} diff --git a/common/net/minecraftforge/fluids/ItemFluidContainer.java b/common/net/minecraftforge/fluids/ItemFluidContainer.java new file mode 100644 index 000000000..5503cf423 --- /dev/null +++ b/common/net/minecraftforge/fluids/ItemFluidContainer.java @@ -0,0 +1,132 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +/** + * Reference implementation of {@link IFluidContainerItem}. Use/extend this or implement your own. + * + * @author King Lemming + * + */ +public class ItemFluidContainer extends Item implements IFluidContainerItem { + + protected int capacity; + + public ItemFluidContainer(int itemID) { + + super(itemID); + } + + public ItemFluidContainer(int itemID, int capacity) { + + super(itemID); + this.capacity = capacity; + } + + public ItemFluidContainer setCapacity(int capacity) { + + this.capacity = capacity; + return this; + } + + /* IFluidContainerItem */ + @Override + public FluidStack getFluid(ItemStack container) { + + if (container.stackTagCompound == null || !container.stackTagCompound.hasKey("Fluid")) { + return null; + } + return FluidStack.loadFluidStackFromNBT(container.stackTagCompound.getCompoundTag("Fluid")); + } + + @Override + public int getCapacity(ItemStack container) { + + return capacity; + } + + @Override + public int fill(ItemStack container, FluidStack resource, boolean doFill) { + + if (resource == null) { + return 0; + } + if (!doFill) { + if (container.stackTagCompound == null || !container.stackTagCompound.hasKey("Fluid")) { + return Math.min(capacity, resource.amount); + } + FluidStack stack = FluidStack.loadFluidStackFromNBT(container.stackTagCompound.getCompoundTag("Fluid")); + + if (stack == null) { + return Math.min(capacity, resource.amount); + } + if (!stack.isFluidEqual(resource)) { + return 0; + } + return Math.min(capacity - stack.amount, resource.amount); + } + if (container.stackTagCompound == null) { + container.stackTagCompound = new NBTTagCompound(); + } + if (!container.stackTagCompound.hasKey("Fluid")) { + NBTTagCompound fluidTag = resource.writeToNBT(new NBTTagCompound()); + + if (capacity < resource.amount) { + fluidTag.setInteger("Amount", capacity); + container.stackTagCompound.setTag("Fluid", fluidTag); + return capacity; + } + container.stackTagCompound.setTag("Fluid", fluidTag); + return resource.amount; + } + NBTTagCompound fluidTag = container.stackTagCompound.getCompoundTag("Fluid"); + FluidStack stack = FluidStack.loadFluidStackFromNBT(fluidTag); + + if (!stack.isFluidEqual(resource)) { + return 0; + } + int filled = capacity - resource.amount; + + if (resource.amount < filled) { + stack.amount += resource.amount; + filled = resource.amount; + } else { + stack.amount = capacity; + } + container.stackTagCompound.setTag("Fluid", stack.writeToNBT(fluidTag)); + return filled; + } + + @Override + public FluidStack drain(ItemStack container, int maxDrain, boolean doDrain) { + + if (container.stackTagCompound == null || !container.stackTagCompound.hasKey("Fluid")) { + return null; + } + FluidStack stack = FluidStack.loadFluidStackFromNBT(container.stackTagCompound.getCompoundTag("Fluid")); + + if (stack == null) { + return null; + } + stack.amount = Math.min(stack.amount, maxDrain); + + if (doDrain) { + if (maxDrain >= capacity) { + container.stackTagCompound.removeTag("Fluid"); + + if (container.stackTagCompound.hasNoTags()) { + container.stackTagCompound = null; + } + return stack; + } + NBTTagCompound fluidTag = container.stackTagCompound.getCompoundTag("Fluid"); + fluidTag.setInteger("Amount", fluidTag.getInteger("Amount") - maxDrain); + container.stackTagCompound.setTag("Fluid", fluidTag); + } + return stack; + } + +} diff --git a/common/net/minecraftforge/fluids/RenderBlockFluid.java b/common/net/minecraftforge/fluids/RenderBlockFluid.java new file mode 100644 index 000000000..971d62fbb --- /dev/null +++ b/common/net/minecraftforge/fluids/RenderBlockFluid.java @@ -0,0 +1,290 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.Icon; +import net.minecraft.util.MathHelper; +import net.minecraft.world.IBlockAccess; +import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler; + +/** + * Default renderer for Forge fluid blocks. + * + * @author King Lemming + * + */ +public class RenderBlockFluid implements ISimpleBlockRenderingHandler { + + public static RenderBlockFluid instance = new RenderBlockFluid(); + + static final float LIGHT_Y_NEG = 0.5F; + static final float LIGHT_Y_POS = 1.0F; + static final float LIGHT_XZ_NEG = 0.8F; + static final float LIGHT_XZ_POS = 0.6F; + static final double RENDER_OFFSET = 0.0010000000474974513D; + + public float getFluidHeightAverage(float[] flow) { + + float total = 0; + int count = 0; + + for (int i = 0; i < flow.length; i++) { + if (flow[i] >= 0.875F) { + return flow[i]; + } + if (flow[i] >= 0) { + total += flow[i]; + count++; + } + } + return total / count; + } + + public float getFluidHeightForRender(IBlockAccess world, int x, int y, int z, BlockFluidBase block) { + + if (world.getBlockId(x, y, z) == block.blockID) { + + if (world.getBlockId(x, y - block.densityDir, z) == block.blockID) { + return 1; + } + if (world.getBlockMetadata(x, y, z) == block.getMaxRenderHeightMeta()) { + return 0.875F; + } + } + return !world.getBlockMaterial(x, y, z).isSolid() && world.getBlockId(x, y - block.densityDir, z) == block.blockID ? 1 : block.getQuantaPercentage(world, x, y, z) * 0.875F; + } + + /* ISimpleBlockRenderingHandler */ + @Override + public void renderInventoryBlock(Block block, int metadata, int modelID, RenderBlocks renderer) { + + } + + @Override + public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) { + + if (!(block instanceof BlockFluidBase)) { + return false; + } + Tessellator tessellator = Tessellator.instance; + int color = block.colorMultiplier(world, x, y, z); + float red = (color >> 16 & 255) / 255.0F; + float green = (color >> 8 & 255) / 255.0F; + float blue = (color & 255) / 255.0F; + + BlockFluidBase theFluid = (BlockFluidBase) block; + int bMeta = world.getBlockMetadata(x, y, z); + + boolean renderTop = world.getBlockId(x, y - theFluid.densityDir, z) != theFluid.blockID; + + boolean renderBottom = block.shouldSideBeRendered(world, x, y + theFluid.densityDir, z, 0) && world.getBlockId(x, y + theFluid.densityDir, z) != theFluid.blockID; + + boolean[] renderSides = new boolean[] { block.shouldSideBeRendered(world, x, y, z - 1, 2), block.shouldSideBeRendered(world, x, y, z + 1, 3), + block.shouldSideBeRendered(world, x - 1, y, z, 4), block.shouldSideBeRendered(world, x + 1, y, z, 5) }; + + if (!renderTop && !renderBottom && !renderSides[0] && !renderSides[1] && !renderSides[2] && !renderSides[3]) { + return false; + } else { + boolean rendered = false; + + double heightNW, heightSW, heightSE, heightNE; + + float flow11 = getFluidHeightForRender(world, x, y, z, theFluid); + + if (flow11 != 1) { + float flow00 = getFluidHeightForRender(world, x - 1, y, z - 1, theFluid); + float flow01 = getFluidHeightForRender(world, x - 1, y, z, theFluid); + float flow02 = getFluidHeightForRender(world, x - 1, y, z + 1, theFluid); + float flow10 = getFluidHeightForRender(world, x, y, z - 1, theFluid); + float flow12 = getFluidHeightForRender(world, x, y, z + 1, theFluid); + float flow20 = getFluidHeightForRender(world, x + 1, y, z - 1, theFluid); + float flow21 = getFluidHeightForRender(world, x + 1, y, z, theFluid); + float flow22 = getFluidHeightForRender(world, x + 1, y, z + 1, theFluid); + + heightNW = getFluidHeightAverage(new float[] { flow00, flow01, flow10, flow11 }); + heightSW = getFluidHeightAverage(new float[] { flow01, flow02, flow12, flow11 }); + heightSE = getFluidHeightAverage(new float[] { flow12, flow21, flow22, flow11 }); + heightNE = getFluidHeightAverage(new float[] { flow10, flow20, flow21, flow11 }); + } else { + heightNW = flow11; + heightSW = flow11; + heightSE = flow11; + heightNE = flow11; + } + + boolean rises = theFluid.densityDir == 1; + + if (renderer.renderAllFaces || renderTop) { + rendered = true; + + Icon iconStill = block.getIcon(1, bMeta); + float flowDir = (float) BlockFluidBase.getFlowDirection(world, x, y, z); + + if (flowDir > -999.0F) { + iconStill = block.getIcon(2, bMeta); + } + heightNW -= RENDER_OFFSET; + heightSW -= RENDER_OFFSET; + heightSE -= RENDER_OFFSET; + heightNE -= RENDER_OFFSET; + + double u1, u2, u3, u4, v1, v2, v3, v4; + + if (flowDir < -999.0F) { + u2 = iconStill.getInterpolatedU(0.0D); + v2 = iconStill.getInterpolatedV(0.0D); + u1 = u2; + v1 = iconStill.getInterpolatedV(16.0D); + u4 = iconStill.getInterpolatedU(16.0D); + v4 = v1; + u3 = u4; + v3 = v2; + } else { + float xFlow = MathHelper.sin(flowDir) * 0.25F; + float zFlow = MathHelper.cos(flowDir) * 0.25F; + u2 = iconStill.getInterpolatedU(8.0F + (-zFlow - xFlow) * 16.0F); + v2 = iconStill.getInterpolatedV(8.0F + (-zFlow + xFlow) * 16.0F); + u1 = iconStill.getInterpolatedU(8.0F + (-zFlow + xFlow) * 16.0F); + v1 = iconStill.getInterpolatedV(8.0F + (zFlow + xFlow) * 16.0F); + u4 = iconStill.getInterpolatedU(8.0F + (zFlow + xFlow) * 16.0F); + v4 = iconStill.getInterpolatedV(8.0F + (zFlow - xFlow) * 16.0F); + u3 = iconStill.getInterpolatedU(8.0F + (zFlow - xFlow) * 16.0F); + v3 = iconStill.getInterpolatedV(8.0F + (-zFlow - xFlow) * 16.0F); + } + tessellator.setBrightness(block.getMixedBrightnessForBlock(world, x, y, z)); + tessellator.setColorOpaque_F(LIGHT_Y_POS * red, LIGHT_Y_POS * green, LIGHT_Y_POS * blue); + + if (!rises) { + tessellator.addVertexWithUV(x + 0, y + heightNW, z + 0, u2, v2); + tessellator.addVertexWithUV(x + 0, y + heightSW, z + 1, u1, v1); + tessellator.addVertexWithUV(x + 1, y + heightSE, z + 1, u4, v4); + tessellator.addVertexWithUV(x + 1, y + heightNE, z + 0, u3, v3); + } else { + tessellator.addVertexWithUV(x + 1, y + 1 - heightNE, z + 0, u3, v3); + tessellator.addVertexWithUV(x + 1, y + 1 - heightSE, z + 1, u4, v4); + tessellator.addVertexWithUV(x + 0, y + 1 - heightSW, z + 1, u1, v1); + tessellator.addVertexWithUV(x + 0, y + 1 - heightNW, z + 0, u2, v2); + } + } + + if (renderer.renderAllFaces || renderBottom) { + rendered = true; + + tessellator.setBrightness(block.getMixedBrightnessForBlock(world, x, y - 1, z)); + + if (!rises) { + tessellator.setColorOpaque_F(LIGHT_Y_NEG, LIGHT_Y_NEG, LIGHT_Y_NEG); + renderer.renderFaceYNeg(block, x, y + RENDER_OFFSET, z, block.getIcon(0, bMeta)); + } else { + tessellator.setColorOpaque_F(LIGHT_Y_POS, LIGHT_Y_POS, LIGHT_Y_POS); + renderer.renderFaceYPos(block, x, y + RENDER_OFFSET, z, block.getIcon(1, bMeta)); + } + } + for (int side = 0; side < 4; ++side) { + int x2 = x; + int z2 = z; + + switch (side) { + case 0: + --z2; + break; + case 1: + ++z2; + break; + case 2: + --x2; + break; + case 3: + ++x2; + break; + } + Icon iconFlow = block.getIcon(side + 2, bMeta); + + if (renderer.renderAllFaces || renderSides[side]) { + rendered = true; + + double ty1; + double tx1; + double ty2; + double tx2; + double tz1; + double tz2; + + if (side == 0) { + ty1 = heightNW; + ty2 = heightNE; + tx1 = x; + tx2 = x + 1; + tz1 = z + RENDER_OFFSET; + tz2 = z + RENDER_OFFSET; + } else if (side == 1) { + ty1 = heightSE; + ty2 = heightSW; + tx1 = x + 1; + tx2 = x; + tz1 = z + 1 - RENDER_OFFSET; + tz2 = z + 1 - RENDER_OFFSET; + } else if (side == 2) { + ty1 = heightSW; + ty2 = heightNW; + tx1 = x + RENDER_OFFSET; + tx2 = x + RENDER_OFFSET; + tz1 = z + 1; + tz2 = z; + } else { + ty1 = heightNE; + ty2 = heightSE; + tx1 = x + 1 - RENDER_OFFSET; + tx2 = x + 1 - RENDER_OFFSET; + tz1 = z; + tz2 = z + 1; + } + float u1Flow = iconFlow.getInterpolatedU(0.0D); + float u2Flow = iconFlow.getInterpolatedU(8.0D); + float v1Flow = iconFlow.getInterpolatedV((1.0D - ty1) * 16.0D * 0.5D); + float v2Flow = iconFlow.getInterpolatedV((1.0D - ty2) * 16.0D * 0.5D); + float v3Flow = iconFlow.getInterpolatedV(8.0D); + tessellator.setBrightness(block.getMixedBrightnessForBlock(world, x2, y, z2)); + float sideLighting = 1.0F; + + if (side < 2) { + sideLighting = LIGHT_XZ_NEG; + } else { + sideLighting = LIGHT_XZ_POS; + } + tessellator.setColorOpaque_F(LIGHT_Y_POS * sideLighting * red, LIGHT_Y_POS * sideLighting * green, LIGHT_Y_POS * sideLighting * blue); + + if (!rises) { + tessellator.addVertexWithUV(tx1, y + ty1, tz1, u1Flow, v1Flow); + tessellator.addVertexWithUV(tx2, y + ty2, tz2, u2Flow, v2Flow); + tessellator.addVertexWithUV(tx2, y + 0, tz2, u2Flow, v3Flow); + tessellator.addVertexWithUV(tx1, y + 0, tz1, u1Flow, v3Flow); + } else { + tessellator.addVertexWithUV(tx1, y + 1 - 0, tz1, u1Flow, v3Flow); + tessellator.addVertexWithUV(tx2, y + 1 - 0, tz2, u2Flow, v3Flow); + tessellator.addVertexWithUV(tx2, y + 1 - ty2, tz2, u2Flow, v2Flow); + tessellator.addVertexWithUV(tx1, y + 1 - ty1, tz1, u1Flow, v1Flow); + } + } + } + renderer.renderMinY = 0; + renderer.renderMaxY = 1; + return rendered; + } + } + + @Override + public boolean shouldRender3DInInventory() { + + return false; + } + + @Override + public int getRenderId() { + + return FluidRegistry.renderIdFluid; + } + +} diff --git a/common/net/minecraftforge/fluids/TileFluidHandler.java b/common/net/minecraftforge/fluids/TileFluidHandler.java new file mode 100644 index 000000000..988c80f66 --- /dev/null +++ b/common/net/minecraftforge/fluids/TileFluidHandler.java @@ -0,0 +1,72 @@ + +package net.minecraftforge.fluids; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.common.ForgeDirection; + +/** + * Reference Tile Entity implementation of {@link IFluidHandler}. Use/extend this or write your own. + * + * @author King Lemming + * + */ +public class TileFluidHandler extends TileEntity implements IFluidHandler { + + protected FluidTank tank = new FluidTank(FluidContainerRegistry.BUCKET_VOLUME); + + @Override + public void readFromNBT(NBTTagCompound tag) { + + super.readFromNBT(tag); + tank.writeToNBT(tag); + } + + @Override + public void writeToNBT(NBTTagCompound tag) { + + super.writeToNBT(tag); + tank.readFromNBT(tag); + } + + /* IFluidHandler */ + @Override + public int fill(ForgeDirection from, FluidStack resource, boolean doFill) { + + return tank.fill(resource, doFill); + } + + @Override + public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain) { + + if (resource == null || !resource.isFluidEqual(tank.getFluid())) { + return null; + } + return tank.drain(resource.amount, doDrain); + } + + @Override + public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) { + + return tank.drain(maxDrain, doDrain); + } + + @Override + public boolean canFill(ForgeDirection from, Fluid fluid) { + + return true; + } + + @Override + public boolean canDrain(ForgeDirection from, Fluid fluid) { + + return true; + } + + @Override + public FluidTankInfo[] getTankInfo(ForgeDirection from) { + + return new FluidTankInfo[] { tank.getInfo() }; + } + +}