Skip to content
Permalink
Browse files

Validate placed blocks / WNA (#1263)

* Optionally validate placed blocks

This allows re-connection of fences, chests, panes, etc.; removal of
blocks in invalid states; and more!

The Connections side-effect already did this for neighbors, so this is
now renamed to Validation for accuracy, and left on by default.

This also fixes some inconsistencies between the Fabric & Forge
markAndNotifyBlock function.

* Add forge comment to fabric world

* Add WorldNativeAccess common logic

* Add Fabric WNA

* Add Bukkit WNA for adapters

* Fix imports for new Fabric mappings

* Pass raw world ref to Bukkit adapters

* Remove client-only block update call

We always assume a server world.

* Allow WNA impls to inspect side-effects

* Update adapters for WNA

* Licenses

* Fix adapter JAR

* Generify EVENTS side effect

* Update adapters for EVENTS change
  • Loading branch information
octylFractal committed Mar 23, 2020
1 parent 17880b8 commit 2f4c44f80c861533c1d03b4e90c90b7b2e1039d9
Showing with 580 additions and 290 deletions.
  1. +24 −23 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java
  2. +6 −19 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java
  3. BIN worldedit-bukkit/src/main/resources/worldedit-adapters.jar
  4. +184 −0 worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java
  5. +25 −0 worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java
  6. +2 −2 worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffect.java
  7. +4 −4 worldedit-core/src/main/resources/lang/strings.json
  8. +2 −2 worldedit-fabric/build.gradle.kts
  9. +1 −0 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java
  10. +1 −0 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricDataFixer.java
  11. +1 −0 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntity.java
  12. +1 −1 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlatform.java
  13. +4 −3 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java
  14. +6 −94 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java
  15. +139 −0 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java
  16. +2 −2 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/{ → internal}/NBTConverter.java
  17. +1 −7 worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java
  18. +1 −0 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeAdapter.java
  19. +1 −0 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeDataFixer.java
  20. +1 −0 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeEntity.java
  21. +1 −1 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlatform.java
  22. +1 −0 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
  23. +9 −98 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorld.java
  24. +152 −0 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/internal/ForgeWorldNativeAccess.java
  25. +2 −2 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/{ → internal}/NBTConverter.java
  26. +9 −32 worldedit-forge/src/main/java/com/sk89q/worldedit/forge/{ → internal}/TileEntityUtils.java
@@ -19,6 +19,7 @@

package com.sk89q.worldedit.bukkit;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
@@ -27,6 +28,7 @@
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
@@ -78,6 +80,7 @@
}

private final WeakReference<World> worldRef;
private final WorldNativeAccess<?, ?, ?> worldNativeAccess;

/**
* Construct the object.
@@ -86,6 +89,12 @@
*/
public BukkitWorld(World world) {
this.worldRef = new WeakReference<>(world);
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
if (adapter != null) {
this.worldNativeAccess = adapter.createWorldNativeAccess(world);
} else {
this.worldNativeAccess = null;
}
}

@Override
@@ -417,26 +426,22 @@ public void simulateBlockMine(BlockVector3 pt) {
}

@Override
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException {
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
if (adapter != null) {
public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) {
if (worldNativeAccess != null) {
try {
return adapter.setBlock(BukkitAdapter.adapt(getWorld(), position), block, sideEffects);
return worldNativeAccess.setBlock(position, block, sideEffects);
} catch (Exception e) {
if (block instanceof BaseBlock && ((BaseBlock) block).getNbtData() != null) {
logger.warn("Tried to set a corrupt tile entity at " + position.toString());
logger.warn(((BaseBlock) block).getNbtData().toString());
logger.warn("Tried to set a corrupt tile entity at " + position.toString() +
": " + ((BaseBlock) block).getNbtData(), e);
} else {
logger.warn("Failed to set block via adapter, falling back to generic", e);
}
e.printStackTrace();
Block bukkitBlock = getWorld().getBlockAt(position.getBlockX(), position.getBlockY(), position.getBlockZ());
bukkitBlock.setBlockData(BukkitAdapter.adapt(block), sideEffects.doesApplyAny());
return true;
}
} else {
Block bukkitBlock = getWorld().getBlockAt(position.getBlockX(), position.getBlockY(), position.getBlockZ());
bukkitBlock.setBlockData(BukkitAdapter.adapt(block), sideEffects.doesApplyAny());
return true;
}
Block bukkitBlock = getWorld().getBlockAt(position.getBlockX(), position.getBlockY(), position.getBlockZ());
bukkitBlock.setBlockData(BukkitAdapter.adapt(block), sideEffects.doesApplyAny());
return true;
}

@Override
@@ -451,20 +456,16 @@ public BaseBlock getFullBlock(BlockVector3 position) {

@Override
public Set<SideEffect> applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType,
SideEffectSet sideEffectSet) throws WorldEditException {
BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
if (adapter != null) {
adapter.applySideEffects(BukkitAdapter.adapt(getWorld(), position), previousType, sideEffectSet);
SideEffectSet sideEffectSet) {
if (worldNativeAccess != null) {
worldNativeAccess.applySideEffects(position, previousType, sideEffectSet);
return Sets.intersection(
adapter.getSupportedSideEffects(),
WorldEditPlugin.getInstance().getInternalPlatform().getSupportedSideEffects(),
sideEffectSet.getSideEffectsToApply()
);
}

return Sets.intersection(
WorldEditPlugin.getInstance().getInternalPlatform().getSupportedSideEffects(),
sideEffectSet.getSideEffectsToApply()
);
return ImmutableSet.of();
}

@Override
@@ -24,16 +24,15 @@
import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.internal.wna.WorldNativeAccess;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.DataFixer;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import org.bukkit.Location;
import org.bukkit.World;
@@ -42,12 +41,11 @@
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;

import javax.annotation.Nullable;

/**
* An interface for adapters of various Bukkit implementations.
*/
@@ -90,23 +88,12 @@ default void tickWatchdog() {
BaseBlock getBlock(Location location);

/**
* Set the block at the given location.
*
* @param location the location
* @param state the block
* @param sideEffectSet side effects to apply
* @return true if a block was likely changed
*/
boolean setBlock(Location location, BlockStateHolder<?> state, SideEffectSet sideEffectSet);

/**
* Applies side effects on the given block.
* Create a {@link WorldNativeAccess} for the given world reference.
*
* @param position position of the block
* @param previousType the type of the previous block that was there
* @param sideEffectSet side effects to apply
* @param world the world reference
* @return the native access object
*/
void applySideEffects(Location position, BlockState previousType, SideEffectSet sideEffectSet);
WorldNativeAccess<?, ?, ?> createWorldNativeAccess(World world);

/**
* Get the state for the given entity.
Binary file not shown.
@@ -0,0 +1,184 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.sk89q.worldedit.internal.wna;

import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;

import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;

/**
* Natively access and perform operations on the world.
*
* @param <NC> the native chunk type
* @param <NBS> the native block state type
* @param <NP> the native position type
*/
public interface WorldNativeAccess<NC, NBS, NP> {

default <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException {
checkNotNull(position);
checkNotNull(block);
setCurrentSideEffectSet(sideEffects);

int x = position.getBlockX();
int y = position.getBlockY();
int z = position.getBlockZ();

// First set the block
NC chunk = getChunk(x >> 4, z >> 4);
NP pos = getPosition(x, y, z);
NBS old = getBlockState(chunk, pos);
NBS newState = toNative(block.toImmutableState());
// change block prior to placing if it should be fixed
if (sideEffects.shouldApply(SideEffect.VALIDATION)) {
newState = getValidBlockForPosition(newState, pos);
}
NBS successState = setBlockState(chunk, pos, newState);
boolean successful = successState != null;

// Create the TileEntity
if (successful || old == newState) {
if (block instanceof BaseBlock) {
BaseBlock baseBlock = (BaseBlock) block;
CompoundTag tag = baseBlock.getNbtData();
if (tag != null) {
tag = tag.createBuilder()
.putString("id", baseBlock.getNbtId())
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ())
.build();
// update if TE changed as well
successful = updateTileEntity(pos, tag);
}
}
}

if (successful) {
if (sideEffects.getState(SideEffect.LIGHTING) == SideEffect.State.ON) {
updateLightingForBlock(pos);
}
markAndNotifyBlock(pos, chunk, old, newState, sideEffects);
}

return successful;
}

default void applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) {
setCurrentSideEffectSet(sideEffectSet);
NP pos = getPosition(position.getX(), position.getY(), position.getZ());
NC chunk = getChunk(position.getX() >> 4, position.getZ() >> 4);
NBS oldData = toNative(previousType);
NBS newData = getBlockState(chunk, pos);

if (sideEffectSet.getState(SideEffect.LIGHTING) == SideEffect.State.ON) {
updateLightingForBlock(pos);
}

markAndNotifyBlock(pos, chunk, oldData, newData, sideEffectSet);
}

// state-keeping functions for WNA
// may be thread-unsafe, as this is single-threaded code

/**
* Receive the current side-effect set from the high level call.
*
* This allows the implementation to branch on the side-effects internally.
*
* @param sideEffectSet the set of side-effects
*/
default void setCurrentSideEffectSet(SideEffectSet sideEffectSet) {
}

// access functions

NC getChunk(int x, int z);

NBS toNative(BlockState state);

NBS getBlockState(NC chunk, NP position);

@Nullable
NBS setBlockState(NC chunk, NP position, NBS state);

NBS getValidBlockForPosition(NBS block, NP position);

NP getPosition(int x, int y, int z);

void updateLightingForBlock(NP position);

boolean updateTileEntity(NP position, CompoundTag tag);

void notifyBlockUpdate(NP position, NBS oldState, NBS newState);

boolean isChunkTicking(NC chunk);

void markBlockChanged(NP position);

void notifyNeighbors(NP pos, NBS oldState, NBS newState);

void updateNeighbors(NP pos, NBS oldState, NBS newState);

void onBlockStateChange(NP pos, NBS oldState, NBS newState);

/**
* This is a heavily modified function stripped from MC to apply worldedit-modifications.
*
* See Forge's World.markAndNotifyBlock
*/
default void markAndNotifyBlock(NP pos, NC chunk, NBS oldState, NBS newState, SideEffectSet sideEffectSet) {
NBS blockState1 = getBlockState(chunk, pos);
if (blockState1 != newState) {
return;
}

// Remove redundant branches
if (isChunkTicking(chunk)) {
if (sideEffectSet.shouldApply(SideEffect.ENTITY_AI)) {
notifyBlockUpdate(pos, oldState, newState);
} else {
// If we want to skip entity AI, just mark the block for sending
markBlockChanged(pos);
}
}

if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) {
notifyNeighbors(pos, oldState, newState);
}

// Make connection updates optional
if (sideEffectSet.shouldApply(SideEffect.VALIDATION)) {
updateNeighbors(pos, oldState, newState);
}

onBlockStateChange(pos, oldState, newState);
}

}
@@ -0,0 +1,25 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* "WNA", or WorldEdit Native Access.
*
* Contains internal helper functions for sharing code between platforms.
*/
package com.sk89q.worldedit.internal.wna;
@@ -24,9 +24,9 @@
public enum SideEffect {
LIGHTING(State.ON),
NEIGHBORS(State.ON),
CONNECTIONS(State.ON),
VALIDATION(State.ON),
ENTITY_AI(State.OFF),
PLUGIN_EVENTS(State.OFF);
EVENTS(State.OFF);

private final String displayName;
private final String description;
@@ -333,12 +333,12 @@
"worldedit.sideeffect.lighting.description": "Updates block lighting",
"worldedit.sideeffect.neighbors": "Neighbors",
"worldedit.sideeffect.neighbors.description": "Notifies nearby blocks of changes",
"worldedit.sideeffect.connections": "Connections",
"worldedit.sideeffect.connections.description": "Updates connections for blocks like fences",
"worldedit.sideeffect.validation": "Validation",
"worldedit.sideeffect.validation.description": "Validates and fixes inconsistent world state, such as disconnected blocks",
"worldedit.sideeffect.entity_ai": "Entity AI",
"worldedit.sideeffect.entity_ai.description": "Updates Entity AI paths for the block changes",
"worldedit.sideeffect.plugin_events": "Plugin Events",
"worldedit.sideeffect.plugin_events.description": "Tells other plugins/mods about these changes when applicable",
"worldedit.sideeffect.events": "Mod/Plugin Events",
"worldedit.sideeffect.events.description": "Tells other mods/plugins about these changes when applicable",
"worldedit.sideeffect.state.on": "On",
"worldedit.sideeffect.state.delayed": "Delayed",
"worldedit.sideeffect.state.off": "Off",

0 comments on commit 2f4c44f

Please sign in to comment.
You can’t perform that action at this time.