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);
}
}
}