489 lines
14 KiB
Java
489 lines
14 KiB
Java
/*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package fr.neatmonster.nocheatplus.utilities.map;
|
|
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.World;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.entity.Entity;
|
|
|
|
import fr.neatmonster.nocheatplus.compat.AlmostBoolean;
|
|
import fr.neatmonster.nocheatplus.utilities.ds.map.CoordHashMap;
|
|
import fr.neatmonster.nocheatplus.utilities.ds.map.CoordMap;
|
|
|
|
/**
|
|
* Access to block properties using caching technique (id, data, bounds).
|
|
*
|
|
* @author asofold
|
|
*
|
|
*/
|
|
public abstract class BlockCache {
|
|
|
|
/**
|
|
* Read access to a BlockCacheNode.
|
|
* @author asofold
|
|
*
|
|
*/
|
|
public static interface IBlockCacheNode {
|
|
|
|
public boolean isDataFetched();
|
|
|
|
public boolean isBoundsFetched();
|
|
|
|
/**
|
|
* Test for (useful) bounds being there, i.e. fetched and bounds being
|
|
* null.
|
|
*
|
|
* @return YES, if bounds have been fetched and are not null. NO if
|
|
* bounds have been fetched and are null. MAYBE, if bounds have
|
|
* not been fetched.
|
|
*/
|
|
public AlmostBoolean hasNonNullBounds();
|
|
|
|
/**
|
|
* Always set.
|
|
* @return
|
|
*/
|
|
public Material getType();
|
|
|
|
/**
|
|
* Ensure to test with isDataSet().
|
|
* @return
|
|
*/
|
|
public int getData();
|
|
|
|
/**
|
|
* Ensure to test with isBoundsSet().
|
|
* @return
|
|
*/
|
|
public double[] getBounds();
|
|
|
|
/**
|
|
* Convenience method to return either the set data, or return data
|
|
* fetched from the given BlockCache instance. The internal state of
|
|
* this node is not updated by this call, unless the BlockCache instance
|
|
* does so.
|
|
*
|
|
* @param blockCache
|
|
* @param x
|
|
* @param y
|
|
* @param z
|
|
* @return
|
|
*/
|
|
public int getData(BlockCache blockCache, int x, int y, int z);
|
|
|
|
/**
|
|
* Convenience method to return either the set bounds, or return bounds
|
|
* fetched from the given BlockCache instance. The internal state of
|
|
* this node is not updated by this call, unless the BlockCache instance
|
|
* does so.
|
|
*
|
|
* @param blockCache
|
|
* @param x
|
|
* @param y
|
|
* @param z
|
|
* @return
|
|
*/
|
|
public double[] getBounds(BlockCache blockCache, int x, int y, int z);
|
|
|
|
|
|
}
|
|
|
|
public static class BlockCacheNode implements IBlockCacheNode {
|
|
|
|
private static final short FETCHED_ID = 0x01;
|
|
private static final short FETCHED_DATA = 0x02;
|
|
private static final short FETCHED_BOUNDS = 0x04;
|
|
|
|
private short fetched;
|
|
private Material id;
|
|
private int data = 0;
|
|
private double[] bounds = null;
|
|
|
|
public BlockCacheNode(Material id) {
|
|
this.id = id;
|
|
fetched = FETCHED_ID;
|
|
}
|
|
|
|
@Override
|
|
public boolean isDataFetched() {
|
|
return (fetched & FETCHED_DATA) != 0;
|
|
}
|
|
|
|
@Override
|
|
public boolean isBoundsFetched() {
|
|
return (fetched & FETCHED_BOUNDS) != 0;
|
|
}
|
|
|
|
@Override
|
|
public AlmostBoolean hasNonNullBounds() {
|
|
return isBoundsFetched() ? (bounds == null ? AlmostBoolean.NO : AlmostBoolean.YES) : AlmostBoolean.MAYBE;
|
|
}
|
|
|
|
@Override
|
|
public Material getType() {
|
|
return id;
|
|
}
|
|
|
|
@Override
|
|
public int getData() {
|
|
return data;
|
|
}
|
|
|
|
@Override
|
|
public double[] getBounds() {
|
|
return bounds;
|
|
}
|
|
|
|
@Override
|
|
public int getData(BlockCache blockCache, int x, int y, int z) {
|
|
return isDataFetched() ? data : blockCache.getData(x, y, z);
|
|
}
|
|
|
|
@Override
|
|
public double[] getBounds(BlockCache blockCache, int x, int y, int z) {
|
|
return isBoundsFetched() ? bounds : blockCache.getBounds(x, y, z);
|
|
}
|
|
|
|
public void setData(int data) {
|
|
this.data = data;
|
|
fetched |= FETCHED_DATA;
|
|
}
|
|
|
|
public void setBounds(double[] bounds) {
|
|
this.bounds = bounds;
|
|
fetched |= FETCHED_BOUNDS;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(final Object obj) {
|
|
if (obj == this) {
|
|
return true;
|
|
}
|
|
if (obj instanceof IBlockCacheNode) {
|
|
final IBlockCacheNode other = (IBlockCacheNode) obj;
|
|
return id == other.getType()
|
|
&& (!isDataFetched() && !other.isDataFetched()
|
|
|| isDataFetched() && other.isDataFetched() && data == other.getData())
|
|
&& (!isBoundsFetched() && !other.isBoundsFetched()
|
|
|| isBoundsFetched() && other.isBoundsFetched()
|
|
&& BlockProperties.isSameShape(bounds, other.getBounds())
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
// Instance
|
|
|
|
/** Nodes for cached block properties. */
|
|
private final CoordMap<BlockCacheNode> nodeMap = new CoordHashMap<BlockCacheNode>(23);
|
|
|
|
/** The max block y. */
|
|
protected int maxBlockY = 255;
|
|
|
|
private final BlockCacheNode airNode = new BlockCacheNode(Material.AIR);
|
|
// TODO: setBlockCacheConfig -> set static nodes (rather only by id).
|
|
|
|
/**
|
|
* Instantiates a new block cache.
|
|
*/
|
|
public BlockCache() {
|
|
airNode.setData(0);
|
|
airNode.setBounds(null);
|
|
}
|
|
|
|
/**
|
|
* Instantiates a new block cache.
|
|
*
|
|
* @param world
|
|
* the world
|
|
*/
|
|
public BlockCache(final World world) {
|
|
setAccess(world);
|
|
}
|
|
|
|
/**
|
|
* Does not do cleanup.
|
|
*
|
|
* @param world
|
|
* the new access
|
|
* @return This BlockCache instance for chaining.
|
|
*/
|
|
public abstract BlockCache setAccess(final World world);
|
|
|
|
/**
|
|
* Fetch the type id from the underlying world.
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return the int
|
|
*/
|
|
public abstract Material fetchTypeId(int x, int y, int z);
|
|
|
|
/**
|
|
* Fetch the data from the underlying world.
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return the int
|
|
*/
|
|
public abstract int fetchData(int x, int y, int z);
|
|
|
|
/**
|
|
* Find out bounds for the block, this should not return null for
|
|
* performance reasons.
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return the double[]
|
|
*/
|
|
public abstract double[] fetchBounds(int x, int y, int z);
|
|
|
|
/**
|
|
* This is a on-ground type check just for standing on minecarts / boats.
|
|
*
|
|
* @param entity
|
|
* the entity
|
|
* @param minX
|
|
* the min x
|
|
* @param minY
|
|
* the min y
|
|
* @param minZ
|
|
* the min z
|
|
* @param maxX
|
|
* the max x
|
|
* @param maxY
|
|
* the max y
|
|
* @param maxZ
|
|
* the max z
|
|
* @return true, if successful
|
|
*/
|
|
public abstract boolean standsOnEntity(Entity entity, final double minX, final double minY, final double minZ, final double maxX, final double maxY, final double maxZ);
|
|
|
|
|
|
/**
|
|
* Remove references.<br>
|
|
* NOTE: You must delete world references with this one.
|
|
*/
|
|
public void cleanup() {
|
|
nodeMap.clear();
|
|
}
|
|
|
|
/**
|
|
* If there is no node stored, create a new node only with the type id set.
|
|
*
|
|
* @param x
|
|
* @param y
|
|
* @param z
|
|
* @return
|
|
*/
|
|
private BlockCacheNode getOrCreateNode(final int x, final int y, final int z) {
|
|
BlockCacheNode node = nodeMap.get(x, y, z);
|
|
if (node != null) {
|
|
return node;
|
|
}
|
|
final Material id = (y < 0 || y > maxBlockY) ? Material.AIR : fetchTypeId(x, y, z);
|
|
// (Later: Static id-node map from config.)
|
|
if (BlockProperties.isAir(id)) {
|
|
return airNode;
|
|
}
|
|
else {
|
|
node = new BlockCacheNode(id);
|
|
nodeMap.put(x, y, z, node);
|
|
return node;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* (Convenience method, uses cache).
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return the type id
|
|
*/
|
|
public Material getType(double x, double y, double z) {
|
|
return getType(Location.locToBlock(x), Location.locToBlock(y), Location.locToBlock(z));
|
|
}
|
|
|
|
/**
|
|
* (Convenience method, uses cache).
|
|
* @param x
|
|
* @param y
|
|
* @param z
|
|
* @param forceSetAll
|
|
* @return
|
|
*/
|
|
public IBlockCacheNode getOrCreateBlockCacheNode(double x, double y, double z, boolean forceSetAll) {
|
|
return getOrCreateBlockCacheNode(Location.locToBlock(x), Location.locToBlock(y), Location.locToBlock(z), forceSetAll);
|
|
}
|
|
|
|
/**
|
|
* (convenience method, uses cache).
|
|
*
|
|
* @param block
|
|
* the block
|
|
* @return the type id
|
|
*/
|
|
public Material getType(final Block block) {
|
|
return getType(block.getX(), block.getY(), block.getZ());
|
|
}
|
|
|
|
/**
|
|
* Get type id with cache access.
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return the type id
|
|
*/
|
|
public Material getType(final int x, final int y, final int z) {
|
|
return getOrCreateNode(x, y, z).getType();
|
|
}
|
|
|
|
/**
|
|
* Get data value with cache access.
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return the data
|
|
*/
|
|
public int getData(final int x, final int y, final int z) {
|
|
final BlockCacheNode node = getOrCreateNode(x, y, z);
|
|
if (node.isDataFetched()) {
|
|
return node.getData();
|
|
}
|
|
final int nData = fetchData(x, y, z);
|
|
node.setData(nData);
|
|
return nData;
|
|
}
|
|
|
|
/**
|
|
* Get block bounds - <b>Do not change these in-place, because the returned
|
|
* array is cached internally.</b>
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return Array of floats (minX, minY, minZ, maxX, maxY, maxZ), may be null
|
|
* theoretically. Do not change these in place, because they might
|
|
* get cached.
|
|
*/
|
|
public double[] getBounds(final int x, final int y, final int z) {
|
|
final BlockCacheNode node = getOrCreateNode(x, y, z);
|
|
if (node.isBoundsFetched()) {
|
|
return node.getBounds();
|
|
}
|
|
final double[] nBounds = fetchBounds(x, y, z);
|
|
// TODO: Convention for null bounds -> full ?
|
|
node.setBounds(nBounds);
|
|
return nBounds;
|
|
}
|
|
|
|
/**
|
|
* Get an IBlockCacheNode instance for the given coordinates. With
|
|
* forceSetAll set to true, it will be ensured that all properties are set
|
|
* for the returned node.
|
|
*
|
|
* @param x
|
|
* @param y
|
|
* @param z
|
|
* @param forceSetAll
|
|
* @return If forceSetAll is true, a node will always be returned with all
|
|
* properties set. If forceSetAll is false, a node with at least the
|
|
* id set will be returned.
|
|
*/
|
|
public IBlockCacheNode getOrCreateBlockCacheNode(int x, int y, int z, boolean forceSetAll) {
|
|
final BlockCacheNode node = getOrCreateNode(x, y, z);
|
|
if (forceSetAll) {
|
|
// TODO: Consider a half-lazy variant (only force fetch bounds, which may or may not fetch data).
|
|
if (!node.isDataFetched()) {
|
|
node.setData(fetchData(x, y, z));
|
|
}
|
|
if (!node.isBoundsFetched()) {
|
|
node.setBounds(fetchBounds(x, y, z));
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Just return the internally stored node for these coordinates, or null if
|
|
* none is there.
|
|
*
|
|
* @param x
|
|
* @param y
|
|
* @param z
|
|
* @return
|
|
*/
|
|
public IBlockCacheNode getBlockCacheNode(int x, int y, int z) {
|
|
return nodeMap.get(x, y, z);
|
|
}
|
|
|
|
/**
|
|
* Convenience method to check if the bounds for a block cover the full
|
|
* block.
|
|
*
|
|
* @param x
|
|
* the x
|
|
* @param y
|
|
* the y
|
|
* @param z
|
|
* the z
|
|
* @return true, if is full bounds
|
|
*/
|
|
public boolean isFullBounds(final int x, final int y, final int z) {
|
|
return MapUtil.isFullBounds(getBounds(x, y, z));
|
|
}
|
|
|
|
/**
|
|
* Get the maximal y coordinate a block can be at (non air).
|
|
*
|
|
* @return the max block y
|
|
*/
|
|
public int getMaxBlockY() {
|
|
return maxBlockY;
|
|
}
|
|
|
|
}
|