GlowstoneMC / Glowstone-Legacy

An open-source server for the Bukkit Minecraft modding interface
Other
363 stars 122 forks source link

[IMPROPER PULL REQUEST] Water Code #635

Closed SamNosliw closed 9 years ago

SamNosliw commented 9 years ago

Hey man, I have great respect for what your doing with Project Glowstone, and I've had a look at the project, modifier it a bit and such, and I've managed to get water/lava working pretty well, I believe the same as vanilla Minecraft. I don't know what your plans are, but I would just like to offer this to you.

I also added a couple requestPulse functions into the world, these allow water to update in a similar manor to normal Minecraft, they are a way to regulate updates. Anyway, here is the code if you want to have a look, please tell me if I missed out any code or haven't explained it well, the lack of comments is a bad habit of mine, thanks for your time:

net.glowstone.block.blocktype.BlockLiquid.java

import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.custom.sliwco.Util; 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;

public abstract class BlockLiquid extends BlockType {

private final Material bucketType;

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 this.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);
}

@Override
public void recievePulse(GlowBlock me) {
    calculateFlow(me);
}
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<>();

    if (isSource(isWater, newState.getRawData())) {
        // We are a source block, lets spread.

        for (BlockFace face : Util.NESWD) {
            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, lets calculate!

        // Lets check that we can still stand.
        int sourceBlocks = 0;
        boolean sourceAbove = false, fluidAbove = false;
        byte strength = isWater ? STRENGTH_MIN_WATER : STRENGTH_MIN_LAVA;
        for (BlockFace face : Util.NESWU) {
            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 attatched 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, lets 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 : Util.NESW) {
                    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 : Util.NESWUD) {
            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 (Iterator<GlowBlock> it = updates.iterator(); it.hasNext();) {
        GlowBlock update = it.next();
        update.setType(update.getType());
    }
}

private 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);

}

private boolean isStationary(Material material) {
    switch (material) {
        case STATIONARY_WATER:
        case STATIONARY_LAVA:
            return true;
        default:
            return false;
    }
}

private boolean isWater(Material material) {
    switch (material) {
        case STATIONARY_WATER:
        case WATER:
            return true;
        default:
            return false;
    }
}

private 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;
    }
}

}

I added a physics update to block breaking/placing, as well as a pulse method for receiving the regular pulses:

net.glowstone.block.blocktype.BlockType.java

public void afterPlace(GlowPlayer player, GlowBlock block, ItemStack holding, GlowBlockState oldState) {

    // <SLIWCO> - Update Block after placing.
    block.applyPhysics(oldState.getType(), block.getTypeId(), oldState.getRawData(), block.getData());

    // do nothing
}

public void afterDestroy(GlowPlayer player, GlowBlock block, GlowBlockState oldState) {

    // <SLIWCO> - Update Block after placing.
    block.applyPhysics(oldState.getType(), block.getTypeId(), oldState.getRawData(), block.getData());

    // do nothing
}

/**
 * Called when the BlockType gets pulsed as requested..
 * @param me The block
 */
public void recievePulse(GlowBlock me) {

// Cancel if pulse sent to empty block data (caused when updated and not removed). me.getWorld().cancelPulse(me); }

Here is also my pulse framework that I added into the world

net.glowstone.GlowWorld.java

private final Map<Location, Integer> tickMap = new HashMap<>();

private void pulseTickMap() { ItemTable itemTable = ItemTable.instance(); Map<Location, Integer> map = getTickMap(); for (Map.Entry<Location, Integer> entry : map.entrySet()) { if (worldAge % entry.getValue() == 0) { GlowBlock block = this.getBlockAt(entry.getKey()); BlockType notifyType = itemTable.getBlock(block.getTypeId()); if (notifyType != null) notifyType.recievePulse(block); } } }

private Map<Location, Integer> getTickMap() {
    return new HashMap<>(tickMap);
}

public void requestPulse(GlowBlock block, int tickRate) {
    Map<Location, Integer> map = getTickMap();
    Location target = block.getLocation();

    for (Location location : map.keySet()) {
        if (target.equals(location)) {
            if (tickRate > 0)
                tickMap.put(location, tickRate);
            else
                tickMap.remove(location);
            return;
        }
    }

        if (tickRate > 0)
            tickMap.put(target, tickRate);
}

public void cancelPulse(GlowBlock block) {
    requestPulse(block, 0);
}

and this wad added inside the GlowWorld.pulse() method, allowing it to function.

    this.pulseTickMap();

Thanks for your time, Nosliw

turt2live commented 9 years ago

This would be better suited as a pull request so it can be more easily reviewed. From your description, you've identified a possible problem with Glowstone which may be valid, but the lengthy amount of code presented is simply unreviewable because it would be too time consuming.

If possible, please link/create a ticket for the issue and open a pull request with these changes. Thanks!

ethsmith commented 9 years ago

Yes, seems like a waste of all that work to put it into a non-met gable issue lol

ethsmith commented 9 years ago

Non mergable issue

SamNosliw commented 9 years ago

Ok, I didn't really understand how GitHub worked with this, but now I've looked into it a little more. Although I still don't understand I made a pull request (I hope).. https://github.com/GlowstoneMC/Glowstone/pull/636