package net.glowstone.block.blocktype;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.GlowBlockState;
import net.glowstone.entity.GlowPlayer;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
public abstract class BlockLiquid extends BlockType {
private final Material bucketType;
private static final BlockFace[] dirNESW = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST};
private static final BlockFace[] dirNESWU = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.UP};
private static final BlockFace[] dirNESWD = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.DOWN};
private static final BlockFace[] dirNESWUD = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN};
protected BlockLiquid(Material bucketType) {
this.bucketType = bucketType;
}
////////////////////////////////////////////////////////////////////////////
// Public accessors
/**
* 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);
////////////////////////////////////////////////////////////////////////////
// Overrides
@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);
updatePhysics(state.getBlock());
}
@Override
public void onNearBlockChanged(GlowBlock block, BlockFace face, GlowBlock changedBlock, Material oldType, byte oldData, Material newType, byte newData) {
updatePhysics(block);
}
/**
* Pulse the block to calculate its flow.
* @param block The block to calculate flow of.
*/
@Override
public void receivePulse(GlowBlock block) {
calculateFlow(block);
}
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 = 5;
private static final int TICK_RATE_LAVA = 20;
private void calculateFlow(GlowBlock block) {
GlowBlockState oldState = block.getState();
GlowBlockState newState = block.getState();
boolean isWater = isWater(newState.getType());
List<GlowBlock> updates = new ArrayList<>(6);
if (isSource(isWater, newState.getRawData())) {
// We are a source block, let's spread.
for (BlockFace face : dirNESWD) {
GlowBlock target = block.getRelative(face);
// Check mixing liquid types.
if (target.isLiquid() && !isWater(target.getType()) && isWater) {
target.setType(isSource(isWater, target.getData()) ? Material.OBSIDIAN : Material.COBBLESTONE, true);
updates.add(target);
} else if (target.isLiquid() && isWater(target.getType()) && !isWater) {
target.setType(face == BlockFace.DOWN ? Material.STONE : Material.COBBLESTONE, true);
updates.add(target);
} else if (target.isLiquid() && target.getData() > STRENGTH_MAX || target.getType().isTransparent()) {
// No mixes, just spread normally!
target.setType(newState.getType(), STRENGTH_MAX, false);
target.getWorld().requestPulse(target, isWater ? TICK_RATE_WATER : TICK_RATE_LAVA);
}
}
} else {
// We are flowing, let's calculate!
// Let's check that we can still stand.
int sourceBlocks = 0;
boolean sourceAbove = false;
boolean fluidAbove = false;
byte strength = isWater ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA;
for (BlockFace face : dirNESWU) {
GlowBlock target = block.getRelative(face);
// Check that we are touching liquid.
if (target.isLiquid() && isWater(target.getType()) == isWater) {
// Found sources? Lets score them.
if (isSource(isWater, target.getData())) {
strength = STRENGTH_MAX;
sourceBlocks++;
if (face == BlockFace.UP) {
sourceAbove = true;
}
} else {
// No source, lets get strength.
if (face == BlockFace.UP) {
strength = STRENGTH_SOURCE;
fluidAbove = true;
} else if (target.getData() < strength) {
strength = target.getData();
}
}
}
}
if (isWater && sourceBlocks > (sourceAbove ? 2 : 1)) {
// We can now become a source.
newState.setRawData(STRENGTH_SOURCE);
} else if (sourceBlocks > 0 && newState.getRawData() != STRENGTH_MAX) {
// We are attached to the source, max strength.
newState.setRawData(STRENGTH_MAX);
} else if (sourceBlocks < 1 && strength == (isWater ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA)) {
// Water is now too weak to continue!
newState.setType(Material.AIR);
} else if (!fluidAbove && sourceBlocks < 1 && newState.getRawData() != strength + 1) {
// We should correct our water strength now.
newState.setRawData((byte) (strength + 1));
} else {
// The water stream is stable, let's spread!
byte newData = (byte) (newState.getRawData() + 1);
// Start with flowing down, otherwise outwards.
GlowBlock down = block.getRelative(BlockFace.DOWN);
// Check mixing liquid types.
if (down.isLiquid() && !isWater(down.getType()) && isWater) {
down.setType(isSource(isWater, down.getData()) ? Material.OBSIDIAN : Material.COBBLESTONE, true);
updates.add(down);
} else if (down.isLiquid() && isWater(down.getType()) && !isWater) {
down.setType(Material.STONE, true);
updates.add(down);
} else if (down.isLiquid() && down.getData() > STRENGTH_MAX || down.getType().isTransparent()) {
// No mixes, just spread normally!
down.setType(newState.getType(), STRENGTH_MAX, false);
down.getWorld().requestPulse(down, isWater ? TICK_RATE_WATER : TICK_RATE_LAVA);
} else if (!down.isLiquid() && newData <= (isWater ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA)) { // No downwards? Check outwards.
for (BlockFace face : dirNESW) {
GlowBlock target = block.getRelative(face);
// Check mixing liquid types.
if (target.isLiquid() && !isWater(target.getType()) && isWater) {
target.setType(isSource(isWater, target.getData()) ? Material.OBSIDIAN : Material.COBBLESTONE, true);
updates.add(target);
} else if (target.isLiquid() && isWater(target.getType()) && !isWater) {
target.setType(Material.COBBLESTONE, true);
updates.add(target);
} else if (target.isLiquid() && target.getData() > newData || target.getType().isTransparent()) {
// No mixes, just spread normally!
target.setType(newState.getType(), newData, false);
target.getWorld().requestPulse(target, isWater ? TICK_RATE_WATER : TICK_RATE_LAVA);
}
}
}
}
}
// Nothing changed? Lets stop pulsing.
if (oldState.getType() == newState.getType()
&& oldState.getRawData() == newState.getRawData()) {
newState.setType(getOpposite(oldState.getType()));
newState.setData(oldState.getData());
block.getWorld().cancelPulse(block);
} else {
for (BlockFace face : dirNESWUD) {
GlowBlock target = block.getRelative(face);
if (target.isLiquid()) {
block.getWorld().requestPulse(target, isWater ? TICK_RATE_WATER : TICK_RATE_LAVA);
}
}
}
// Lets update our changes.
newState.update(true, false);
// Update any other changes afterwards to force pulses for other sources.
for (GlowBlock update : updates) {
update.setType(update.getType());
}
}
private static boolean isSource(boolean isWater, byte data) {
return data < STRENGTH_MAX || data > (isWater ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA);
}
@Override
public void updatePhysics(GlowBlock block) {
if (isStationary(block.getType())) {
block.setType(getOpposite(block.getType()), block.getData(), false);
}
block.getWorld().requestPulse(block, isWater(block.getType()) ? TICK_RATE_WATER : TICK_RATE_LAVA);
}
@Override
public void updateBlock(GlowBlock block) {
updatePhysics(block);
}
@Override
public boolean canTickRandomly() {
return true;
}
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;
}
}
}