NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/map/BlockCache.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;
}
}