package tc.oc.pgm.snapshot; import java.util.HashMap; import java.util.Map; import org.bukkit.Chunk; import org.bukkit.ChunkSnapshot; import org.bukkit.Material; import org.bukkit.block.BlockState; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.material.MaterialData; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import tc.oc.commons.bukkit.util.ChunkVector; import tc.oc.pgm.events.BlockTransformEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchScope; /** * Keeps a snapshot of the block state of the entire match world at load time, * using a copy-on-write strategy. This module does nothing on its own, but * other modules can use it to query for the original materials of the map. * * The correct functioning of this module depends on EVERY block change firing * a {@link BlockTransformEvent}, without exception. */ @ListenerScope(MatchScope.LOADED) public class SnapshotMatchModule extends MatchModule implements Listener { private final Map<ChunkVector, ChunkSnapshot> chunkSnapshots = new HashMap<>(); public MaterialData getOriginalMaterial(int x, int y, int z) { if(y < 0 || y >= 256) return new MaterialData(Material.AIR); ChunkVector chunkVector = ChunkVector.ofBlock(x, y, z); ChunkSnapshot chunkSnapshot = chunkSnapshots.get(chunkVector); if(chunkSnapshot != null) { BlockVector chunkPos = chunkVector.worldToChunk(x, y, z); return new MaterialData(chunkSnapshot.getBlockTypeId(chunkPos.getBlockX(), chunkPos.getBlockY(), chunkPos.getBlockZ()), (byte) chunkSnapshot.getBlockData(chunkPos.getBlockX(), chunkPos.getBlockY(), chunkPos.getBlockZ())); } else { return getMatch().getWorld().getBlockAt(x, y, z).getState().getMaterialData(); } } public MaterialData getOriginalMaterial(Vector pos) { return getOriginalMaterial(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); } public BlockState getOriginalBlock(int x, int y, int z) { BlockState state = getMatch().getWorld().getBlockAt(x, y, z).getState(); if(y < 0 || y >= 256) return state; ChunkVector chunkVector = ChunkVector.ofBlock(x, y, z); ChunkSnapshot chunkSnapshot = chunkSnapshots.get(chunkVector); if(chunkSnapshot != null) { BlockVector chunkPos = chunkVector.worldToChunk(x, y, z); state.setMaterialData(new MaterialData(chunkSnapshot.getBlockTypeId(chunkPos.getBlockX(), chunkPos.getBlockY(), chunkPos.getBlockZ()), (byte) chunkSnapshot.getBlockData(chunkPos.getBlockX(), chunkPos.getBlockY(), chunkPos.getBlockZ()))); } return state; } public BlockState getOriginalBlock(Vector pos) { return getOriginalBlock(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); } // Listen on lowest priority so that the original block is available to other handlers of this event @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onBlockChange(BlockTransformEvent event) { Chunk chunk = event.getOldState().getChunk(); ChunkVector chunkVector = ChunkVector.of(chunk); if(!chunkSnapshots.containsKey(chunkVector)) { logger.fine("Copying chunk at " + chunkVector); ChunkSnapshot chunkSnapshot = chunk.getChunkSnapshot(); chunkSnapshot.updateBlock(event.getOldState()); // ChunkSnapshot is very likely to have the post-event state already, so we have to correct it chunkSnapshots.put(chunkVector, chunkSnapshot); } } }