package tc.oc.pgm.snapshot; import com.google.common.collect.ImmutableList; import java.util.Collection; 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.pgm.api.PGM; import tc.oc.pgm.api.event.BlockTransformEvent; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.api.match.MatchModule; import tc.oc.pgm.api.match.MatchScope; import tc.oc.pgm.api.match.factory.MatchModuleFactory; import tc.oc.pgm.api.module.exception.ModuleLoadException; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.renewable.RenewableMatchModule; import tc.oc.pgm.util.chunk.ChunkVector; /** * Keeps a snapshot of the block state of the entire match world at build 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 world of the map. * * <p>The correct functioning of this module depends on EVERY block change firing a {@link * BlockTransformEvent}, without exception. */ @ListenerScope(MatchScope.LOADED) public class SnapshotMatchModule implements MatchModule, Listener { public static class Factory implements MatchModuleFactory<SnapshotMatchModule> { @Override public Collection<Class<? extends MatchModule>> getSoftDependencies() { return ImmutableList.of( RenewableMatchModule.class); // Only needs to load if Renewables are loaded } @Override public SnapshotMatchModule createMatchModule(Match match) throws ModuleLoadException { return new SnapshotMatchModule(match); } } private final Match match; private final Map<ChunkVector, ChunkSnapshot> chunkSnapshots = new HashMap<>(); private SnapshotMatchModule(Match match) { this.match = match; } 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 match.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 = match.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) { Match match = PGM.get().getMatchManager().getMatch(event.getWorld()); // Dont carry over old chunks into a new match if (match == null || match.isFinished()) return; Chunk chunk = event.getOldState().getChunk(); ChunkVector chunkVector = ChunkVector.of(chunk); if (!chunkSnapshots.containsKey(chunkVector)) { match.getLogger().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); } } }