package tc.oc.pgm.renewable; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.material.MaterialData; import org.bukkit.util.BlockVector; import org.bukkit.geometry.Cuboid; import org.bukkit.geometry.Vec3; import tc.oc.commons.core.util.DefaultMapAdapter; import java.util.HashMap; import java.util.Map; /** * Array-backed volume of block states (id:data pairs) * with fixed size and location. All positions in or out * are in world coordinates. Initially filled with air. */ public class BlockImage { private final World world; private final Vec3 origin; private final Vec3 size; private final Cuboid bounds; private final int volume; private final short[] blockIds; private final byte[] blockData; private final Map<MaterialData, Integer> blockCounts; public BlockImage(World world, Cuboid bounds) { this(world, bounds, false); } public BlockImage(World world, Cuboid bounds, boolean keepCounts) { this.world = world; this.bounds = bounds; this.origin = this.bounds.minimumBlockInside(); this.size = this.bounds.blockSize(); this.volume = Math.max(0, this.bounds.blockVolume()); blockIds = new short[this.volume]; blockData = new byte[this.volume]; if(keepCounts) { this.blockCounts = new DefaultMapAdapter<>(new HashMap<MaterialData, Integer>(), 0); } else { this.blockCounts = null; } } /** * @return The boundaries of this image in world coordinates */ public Cuboid getBounds() { return bounds; } public Map<MaterialData, Integer> getBlockCounts() { return blockCounts; } private int offset(BlockVector pos) { if(!this.bounds.containsBlock(pos)) { throw new IndexOutOfBoundsException("Block is not inside this BlockImage"); } return (pos.coarseZ() - this.origin.coarseZ()) * this.size.coarseX() * this.size.coarseY() + (pos.coarseY() - this.origin.coarseY()) * this.size.coarseX() + (pos.coarseX() - this.origin.coarseX()); } /** * @param pos Block position in world coordinates * @return Block state saved in this image at the given position */ @SuppressWarnings("deprecation") public MaterialData get(BlockVector pos) { int offset = this.offset(pos); return new MaterialData(this.blockIds[offset], this.blockData[offset]); } @SuppressWarnings("deprecation") public BlockState getState(BlockVector pos) { int offset = this.offset(pos); BlockState state = pos.toLocation(this.world).getBlock().getState(); state.setTypeId(this.blockIds[offset]); state.setRawData(this.blockData[offset]); return state; } /** * Set every block in this image to its current state in the world */ @SuppressWarnings("deprecation") public void save() { if(this.blockCounts != null) { this.blockCounts.clear(); } int offset = 0; for(Vec3 v : this.bounds.blockRegion().mutableIterable()) { Block block = this.world.getBlockAt(v.coarseX(), v.coarseY(), v.coarseZ()); this.blockIds[offset] = (short) block.getTypeId(); this.blockData[offset] = block.getData(); ++offset; if(this.blockCounts != null) { MaterialData md = block.getState().getData(); this.blockCounts.put(md, this.blockCounts.get(md) + 1); } } } /** * Copy the block at the given position from the image to the world * @param pos Block position in world coordinates */ @SuppressWarnings("deprecation") public void restore(BlockVector pos) { int offset = this.offset(pos); pos.toLocation(this.world).getBlock().setTypeIdAndData(this.blockIds[offset], this.blockData[offset], true); } }