Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix structure clear removing instead of restoring blocks #1107

Merged
merged 2 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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