Skip to content

Commit

Permalink
Fix structure clear removing instead of restoring blocks (#1107)
Browse files Browse the repository at this point in the history
Signed-off-by: Pablete1234 <[email protected]>
  • Loading branch information
Pablete1234 authored Nov 28, 2022
1 parent 395c1d1 commit 5103d4a
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 200 deletions.
29 changes: 9 additions & 20 deletions core/src/main/java/tc/oc/pgm/snapshot/BudgetWorldEdit.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,22 @@
*/
class BudgetWorldEdit {

// Ugly optimization, match material via primitive id
private static final int AIR_ID = Material.AIR.getId();
private final World world;
private final WorldSnapshot snapshot;

private final Match match;
private final SnapshotMatchModule smm;

BudgetWorldEdit(Match match, SnapshotMatchModule snapshotMatchModule) {
this.match = match;
this.smm = snapshotMatchModule;
BudgetWorldEdit(World world, WorldSnapshot snapshot) {
this.world = world;
this.snapshot = snapshot;
}

/**
* Places blocks in the region from the {@link SnapshotMatchModule} memory.
*
* @param region region where the blocks were when they got saved
* @param offset the offset to add when placing blocks
* @param includeAir whether to place air if it's found in the memory
*/
public void placeBlocks(Region region, BlockVector offset, boolean includeAir) {
final World world = match.getWorld();
for (BlockData blockData : smm.getOriginalMaterials(region)) {
if (!includeAir && blockData.getTypeId() == AIR_ID) continue;
public void placeBlocks(Region region, BlockVector offset) {
for (BlockData blockData : snapshot.getMaterials(region)) {

BlockState state = blockData.getBlock(world, offset).getState();
state.setMaterialData(blockData.getMaterialData());
Expand All @@ -49,14 +43,9 @@ public void placeBlocks(Region region, BlockVector offset, boolean includeAir) {
*
* @param region The region to remove blocks from
* @param offset an offset to add to the region coordinates if the blocks were offset when placed
* @param includeAir if blocks that originally were air should be included or not
*/
public void removeBlocks(Region region, BlockVector offset, boolean includeAir) {
final World world = match.getWorld();

for (BlockData blockData : smm.getOriginalMaterials(region)) {
if (!includeAir && blockData.getTypeId() == AIR_ID) continue;

public void removeBlocks(Region region, BlockVector offset) {
for (BlockData blockData : snapshot.getMaterials(region)) {
Block block = blockData.getBlock(world, offset);
// Ignore if already air
if (!block.getType().equals(Material.AIR)) block.setType(Material.AIR);
Expand Down
154 changes: 9 additions & 145 deletions core/src/main/java/tc/oc/pgm/snapshot/SnapshotMatchModule.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package tc.oc.pgm.snapshot;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
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 org.jetbrains.annotations.Nullable;
import tc.oc.pgm.api.event.BlockTransformEvent;
Expand All @@ -19,11 +13,8 @@
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.api.region.Region;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.util.BlockData;
import tc.oc.pgm.util.chunk.ChunkVector;
import tc.oc.pgm.util.nms.NMSHacks;

/**
* Keeps a snapshot of the block state of the entire match world at build time, using a
Expand All @@ -45,66 +36,24 @@ public SnapshotMatchModule createMatchModule(Match match) throws ModuleLoadExcep
}

private final Match match;
private final Map<ChunkVector, ChunkSnapshot> chunkSnapshots = new HashMap<>();
private final BudgetWorldEdit worldEdit;
// Represents the world state before any changes have been applied to it
private final WorldSnapshot snapshot;

private SnapshotMatchModule(Match match) {
this.match = match;
this.worldEdit = new BudgetWorldEdit(match, this);
}

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().getData();
}
this.snapshot = new WorldSnapshot(match.getWorld());
}

public MaterialData getOriginalMaterial(Vector pos) {
return getOriginalMaterial(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
return snapshot.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;
return snapshot.getOriginalBlock(x, y, z);
}

public BlockState getOriginalBlock(Vector pos) {
return getOriginalBlock(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
}

/**
* Get the original material data for a {@code region}.
*
* @param region the region to get block states from
*/
public Iterable<BlockData> getOriginalMaterials(Region region) {
return () -> new BlockDataIterator(region);
return snapshot.getOriginalBlock(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
}

// Listen on lowest priority so that the original block is available to other handlers of this
Expand All @@ -122,95 +71,10 @@ public void onBlockChange(BlockTransformEvent event) {
* @param state optional block state to write on the snapshot
*/
public void saveSnapshot(ChunkVector cv, @Nullable BlockState state) {
if (!chunkSnapshots.containsKey(cv)) {
this.match.getLogger().fine("Copying chunk at " + cv);

ChunkSnapshot snapshot = cv.getChunk(match.getWorld()).getChunkSnapshot();

// ChunkSnapshot is very likely to have the post-event state already,
// so we have to correct it
if (state != null) NMSHacks.updateChunkSnapshot(snapshot, state);

chunkSnapshots.put(cv, snapshot);
}
}

public void saveRegion(Region region) {
region.getChunkPositions().forEach(cv -> this.saveSnapshot(cv, null));
}

public void placeBlocks(Region region, BlockVector offset, boolean includeAir) {
worldEdit.placeBlocks(region, offset, includeAir);
}

public void removeBlocks(Region region, BlockVector offset, boolean includeAir) {
worldEdit.removeBlocks(region, offset, includeAir);
snapshot.saveSnapshot(cv, state);
}

/**
* Works in a similar fashion to {@link tc.oc.pgm.util.block.CuboidBlockIterator}. Implements both
* {@link BlockData} and {@link Iterator}, changes its own state while iterating, and returns
* itself from {@link #next()}. In this way, it avoids creating any objects while iterating. It
* additionally provides no methods to mutate the state.
*/
private class BlockDataIterator implements Iterator<BlockData>, BlockData {

private final Iterator<BlockVector> vectors;

private ChunkVector chunkVector = null;
private ChunkSnapshot snapshot = null;

private BlockVector blockVector;
private int materialId;
private int data;

private BlockDataIterator(Region region) {
this.vectors = region.getBlockVectorIterator();
}

@Override
public boolean hasNext() {
return this.vectors.hasNext();
}

@Override
public BlockData next() {
blockVector = this.vectors.next();

// If this block is in the same chunk as the previous one, keep using the same snapshot
// without fetching a new one
if (snapshot == null
|| blockVector.getBlockZ() >> 4 != chunkVector.getChunkZ()
|| blockVector.getBlockX() >> 4 != chunkVector.getChunkX()) {
chunkVector = ChunkVector.ofBlock(blockVector);
snapshot = chunkSnapshots.get(chunkVector);
}

// Equivalent to chunkVector.worldToChunk(blockVector), but avoids allocations
int offsetX = blockVector.getBlockX() - chunkVector.getBlockMinX();
int offsetY = blockVector.getBlockY();
int offsetZ = blockVector.getBlockZ() - chunkVector.getBlockMinZ();

// Calling getMaterialData would cause an allocation, so instead use raw types
materialId = snapshot.getBlockTypeId(offsetX, offsetY, offsetZ);
data = snapshot.getBlockData(offsetX, offsetY, offsetZ);

return this;
}

@Override
public int getTypeId() {
return materialId;
}

@Override
public int getData() {
return data;
}

@Override
public BlockVector getBlockVector() {
return blockVector;
}
public WorldSnapshot getOriginalSnapshot() {
return snapshot;
}
}
Loading

0 comments on commit 5103d4a

Please sign in to comment.