package net.glowstone.block.blocktype;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.GlowBlockState;
import net.glowstone.block.ItemTable;
import net.glowstone.entity.GlowPlayer;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.BlockFace;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import static org.bukkit.block.BlockFace.*;
public abstract class BlockLiquid extends BlockType {
private static final byte STRENGTH_SOURCE = 0;
private static final byte STRENGTH_MAX = 1;
private static final byte STRENGTH_MIN_WATER = 7;
private static final byte STRENGTH_MIN_LAVA = 4;
private static final int TICK_RATE_WATER = 4;
private static final int TICK_RATE_LAVA = 20;
private final Material bucketType;
private BlockFace[] sides = {NORTH, EAST, SOUTH, WEST};
protected BlockLiquid(Material bucketType) {
this.bucketType = bucketType;
}
private static boolean isSource(boolean isWater, byte data) {
return data < STRENGTH_MAX || data > (isWater ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA);
}
private static boolean isStationary(Material material) {
switch (material) {
case STATIONARY_WATER:
case STATIONARY_LAVA:
return true;
default:
return false;
}
}
private static boolean isWater(Material material) {
switch (material) {
case STATIONARY_WATER:
case WATER:
return true;
default:
return false;
}
}
private static Material getOpposite(Material material) {
switch (material) {
case STATIONARY_WATER:
return Material.WATER;
case STATIONARY_LAVA:
return Material.LAVA;
case WATER:
return Material.STATIONARY_WATER;
case LAVA:
return Material.STATIONARY_LAVA;
default:
return Material.AIR;
}
}
/**
* Get the bucket type to replace the empty bucket when the liquid has
* been collected.
*
* @return The associated bucket types material
*/
public Material getBucketType() {
return bucketType;
}
/**
* Check if the BlockState block is collectible by a bucket.
*
* @param block The block state to check
* @return Boolean representing if its collectible
*/
public abstract boolean isCollectible(GlowBlockState block);
@Override
public void placeBlock(GlowPlayer player, GlowBlockState state, BlockFace face, ItemStack holding, Vector clickedLoc) {
// 0 = Full liquid block
state.setType(getMaterial());
state.setRawData((byte) 0);
state.getBlock().getWorld().requestPulse(state.getBlock());
}
@Override
public void onNearBlockChanged(GlowBlock block, BlockFace face, GlowBlock changedBlock, Material oldType, byte oldData, Material newType, byte newData) {
if (block.getState().getFlowed() && !(isWater(newType) || newType == Material.LAVA || newType == Material.STATIONARY_LAVA)) {
block.getState().setFlowed(false);
}
block.getWorld().requestPulse(block);
}
/**
* Pulse the block to calculate its flow.
*
* @param block The block to calculate flow of.
*/
@Override
public void receivePulse(GlowBlock block) {
updatePhysics(block);
}
private void calculateFlow(GlowBlock block) {
if (!block.getState().getFlowed()) {
GlowBlockState state = block.getState();
// see if we can flow down
if (block.getY() > 0) {
if (calculateTarget(block.getRelative(DOWN), DOWN, true)) {
if (!block.getRelative(UP).isLiquid() && Byte.compare(state.getRawData(), STRENGTH_SOURCE) == 0) {
for (BlockFace face : sides) {
calculateTarget(block.getRelative(face), face, true);
}
}
} else {
// we can't flow down, or if we're a source block, let's flow horizontally
// search 5 blocks out
for (int j = 1; j < 6; j++) {
// from each horizontal face
for (BlockFace face : sides) {
if (calculateTarget(block.getRelative(face, j).getRelative(DOWN), face, false) && calculateTarget(block.getRelative(face), face, true)) {
state.setFlowed(true);
}
}
// if we already found a match at this radius, stop
if (state.getFlowed()) {
return;
}
}
for (BlockFace face : sides) {
calculateTarget(block.getRelative(face), face, true);
}
state.setFlowed(true);
}
}
}
}
private boolean calculateTarget(GlowBlock target, BlockFace direction, boolean flow) {
if (!target.getChunk().isLoaded()) // Don't flow inside unloaded chunks
return false;
if (target.getType() == Material.AIR || ItemTable.instance().getBlock(target.getType()) instanceof BlockNeedsAttached) {
// we flowed
if (flow) {
flow(target.getRelative(direction.getOppositeFace()), direction);
}
return true;
}
if (target.isLiquid()) {
// let's mix
if (flow) {
mix(target, direction, target.getRelative(direction.getOppositeFace()).getType(), target.getType());
}
return true;
}
// it is solid, we can't flow
return false;
}
private void flow(GlowBlock source, BlockFace direction) {
// if we're not going down
BlockFromToEvent fromToEvent = new BlockFromToEvent(source, direction);
if (fromToEvent.isCancelled()) {
return;
}
byte strength = fromToEvent.getBlock().getState().getRawData();
if (DOWN != fromToEvent.getFace()) {
if (Byte.compare(strength, isWater(fromToEvent.getBlock().getType()) || fromToEvent.getBlock().getBiome() == Biome.HELL ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA) < 0) {
// decrease the strength
strength += 1;
} else {
// no strength, can't flow
return;
}
} else {
// reset the strength if we're going down
strength = STRENGTH_MAX;
}
// flow to the target
GlowBlock toBlock = (GlowBlock) fromToEvent.getToBlock();
toBlock.setType(fromToEvent.getBlock().getType(), strength, false);
toBlock.getWorld().requestPulse(toBlock);
}
private void mix(GlowBlock target, BlockFace direction, Material flowingMaterial, Material targetMaterial) {
if (flowingMaterial == Material.WATER && targetMaterial == Material.LAVA) {
if (target.getState().getRawData() == STRENGTH_SOURCE) {
target.setType(Material.OBSIDIAN);
} else if (direction == DOWN) {
target.setType(Material.COBBLESTONE);
}
}
if (flowingMaterial == Material.LAVA && (targetMaterial == Material.WATER || targetMaterial == Material.STATIONARY_WATER)) {
if (direction == DOWN) {
target.setType(Material.STONE);
}
if (direction == NORTH || direction == SOUTH || direction == EAST || direction == WEST) {
target.setType(Material.COBBLESTONE);
}
}
}
@Override
public void updatePhysics(GlowBlock block) {
if (isStationary(block.getType())) {
block.setType(getOpposite(block.getType()), block.getData(), false);
}
if (Byte.compare(block.getState().getRawData(), STRENGTH_SOURCE) != 0) {
BlockFace[] faces = {UP, NORTH, EAST, SOUTH, WEST};
boolean connected = false;
int count = 0;
for (BlockFace face : faces) {
if (block.getRelative(face).getType() == block.getType()) {
if (count < 2 && face != UP && Byte.compare(block.getRelative(face).getState().getRawData(), STRENGTH_SOURCE) == 0) {
count++;
}
if (!connected && face == UP || Byte.compare(block.getRelative(face).getState().getRawData(), block.getState().getRawData()) < 0) {
connected = true;
if (block.getWorld().getServer().getClassicWater()) {
block.getState().setRawData(STRENGTH_SOURCE);
}
}
if (block.getWorld().getServer().getClassicWater() && Byte.compare(block.getRelative(face).getState().getRawData(), STRENGTH_SOURCE) == 0) {
block.getRelative(face).setType(Material.AIR);
}
}
}
if (!connected) {
block.setType(Material.AIR);
return;
}
if (count == 2) {
block.getState().setRawData(STRENGTH_SOURCE);
return;
}
}
if (!(Byte.compare(block.getState().getRawData(), isWater(block.getType()) || block.getBiome() == Biome.HELL ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA) == 0) || block.getRelative(DOWN).getType() == Material.AIR) {
calculateFlow(block);
}
}
@Override
public boolean isPulseOnce(GlowBlock block) {
return true;
}
@Override
public int getPulseTickSpeed(GlowBlock block) {
return isWater(block.getType()) || block.getBiome() == Biome.HELL ? TICK_RATE_WATER : TICK_RATE_LAVA;
}
}