From 632b6934a887d102e72c9e1703eafd495bd1d4a6 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 15 Dec 2024 19:59:40 +0000 Subject: [PATCH] feat: implement command to fix block connections (#2746) * feat: implement command to fix block connections - closes #313 * set SETUP to true on setup * Add (untested) processing capability and second passes where appropriate * Minor refactor, add various javadocs * Minor adjustments * Better chest handling * Utilise thread extends when able * Add strings, changes to fastmode/side effect handling * Cleanup * Add for 1.21 * Adjustment for tall flowers - add capability to forcefully override block updates * Move to Fawe/LinTag * fix: adjustments, fix certain upside-down blocks * Add for 1.21.3 --- .../ext/fawe/v1_20_R2/PaperweightAdapter.java | 12 +- .../PaperweightServerLevelDelegateProxy.java | 237 ++++++++ .../fawe/v1_20_R2/PaperweightFaweAdapter.java | 22 +- ...perweightFaweMutableBlockPlaceContext.java | 142 +++++ .../fawe/v1_20_R2/PaperweightGetBlocks.java | 5 +- .../fawe/v1_20_R2/PaperweightLevelProxy.java | 116 ++++ .../PaperweightPlacementStateProcessor.java | 112 ++++ .../ext.fawe/v1_20_R3/PaperweightAdapter.java | 12 +- .../PaperweightServerLevelDelegateProxy.java | 237 ++++++++ .../fawe/v1_20_R3/PaperweightFaweAdapter.java | 26 +- ...perweightFaweMutableBlockPlaceContext.java | 137 +++++ .../fawe/v1_20_R3/PaperweightGetBlocks.java | 5 +- .../fawe/v1_20_R3/PaperweightLevelProxy.java | 116 ++++ .../PaperweightPlacementStateProcessor.java | 112 ++++ .../ext.fawe/v1_20_R4/PaperweightAdapter.java | 12 +- .../PaperweightServerLevelDelegateProxy.java | 240 ++++++++ .../fawe/v1_20_R4/PaperweightFaweAdapter.java | 22 +- ...perweightFaweMutableBlockPlaceContext.java | 143 +++++ .../fawe/v1_20_R4/PaperweightGetBlocks.java | 5 +- .../fawe/v1_20_R4/PaperweightLevelProxy.java | 116 ++++ .../PaperweightPlacementStateProcessor.java | 111 ++++ .../ext/fawe/v1_21_R1/PaperweightAdapter.java | 12 +- .../PaperweightServerLevelDelegateProxy.java | 240 ++++++++ .../fawe/v1_21_R1/PaperweightFaweAdapter.java | 22 +- ...perweightFaweMutableBlockPlaceContext.java | 143 +++++ .../fawe/v1_21_R1/PaperweightGetBlocks.java | 5 +- .../fawe/v1_21_R1/PaperweightLevelProxy.java | 115 ++++ .../PaperweightPlacementStateProcessor.java | 111 ++++ .../ext/fawe/v1_21_3/PaperweightAdapter.java | 10 +- .../PaperweightServerLevelDelegateProxy.java | 240 ++++++++ .../fawe/v1_21_3/PaperweightFaweAdapter.java | 22 +- ...perweightFaweMutableBlockPlaceContext.java | 143 +++++ .../fawe/v1_21_3/PaperweightGetBlocks.java | 5 +- .../fawe/v1_21_3/PaperweightLevelProxy.java | 136 +++++ .../PaperweightPlacementStateProcessor.java | 111 ++++ .../bukkit/BukkitServerInterface.java | 9 + .../bukkit/adapter/BukkitImplAdapter.java | 10 + .../com/fastasyncworldedit/core/FaweAPI.java | 10 +- .../parser/mask/Adjacent2DMaskParser.java | 51 ++ .../core/extent/PassthroughExtent.java | 63 +- .../processor/PlacementStateProcessor.java | 545 ++++++++++++++++++ .../core/function/mask/Adjacent2DMask.java | 66 +++ .../core/function/mask/AdjacentAny2DMask.java | 90 +++ .../core/function/mask/AdjacentAnyMask.java | 2 +- .../changeset/FaweStreamChangeSet.java | 1 - .../core/math/MutableBlockVector3.java | 140 ++++- .../fastasyncworldedit/core/queue/Filter.java | 9 + .../core/queue/IChunkSet.java | 16 + .../core/queue/IDelegateFilter.java | 5 + .../core/queue/IQueueExtent.java | 15 + .../implementation/ParallelQueueExtent.java | 41 +- .../SingleThreadQueueExtent.java | 24 +- .../implementation/blocks/BitSetBlocks.java | 11 + .../implementation/blocks/CharSetBlocks.java | 15 +- .../blocks/ThreadUnsafeCharBlocks.java | 19 +- .../implementation/chunk/ChunkHolder.java | 24 +- .../queue/implementation/chunk/NullChunk.java | 11 + .../core/util/ExtentTraverser.java | 24 + .../sk89q/worldedit/EditSessionBuilder.java | 60 +- .../com/sk89q/worldedit/LocalSession.java | 21 +- .../worldedit/command/RegionCommands.java | 31 + .../extension/platform/Platform.java | 15 +- .../function/mask/BlockCategoryMask.java | 10 + .../function/mask/BlockTypeMask.java | 12 +- .../sk89q/worldedit/math/BlockVector3.java | 4 + .../com/sk89q/worldedit/util/Direction.java | 101 +++- .../com/sk89q/worldedit/util/SideEffect.java | 17 +- .../sk89q/worldedit/util/SideEffectSet.java | 54 +- .../src/main/resources/lang/strings.json | 6 +- 69 files changed, 4663 insertions(+), 124 deletions(-) create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightServerLevelDelegateProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweMutableBlockPlaceContext.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightLevelProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlacementStateProcessor.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweMutableBlockPlaceContext.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightLevelProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlacementStateProcessor.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweMutableBlockPlaceContext.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightLevelProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlacementStateProcessor.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweMutableBlockPlaceContext.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightLevelProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlacementStateProcessor.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweMutableBlockPlaceContext.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightLevelProxy.java create mode 100644 worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightPlacementStateProcessor.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/Adjacent2DMaskParser.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/Adjacent2DMask.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAny2DMask.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java index 9aaeb7bc2..e429fede3 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java @@ -857,12 +857,12 @@ public final class PaperweightAdapter implements BukkitImplAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( - SideEffect.NEIGHBORS, - SideEffect.LIGHTING, - SideEffect.VALIDATION, - SideEffect.ENTITY_AI, - SideEffect.EVENTS, - SideEffect.UPDATE + //FAWE start - FAWE-supported side effects + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + //FAWE end ); @Override diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightServerLevelDelegateProxy.java new file mode 100644 index 000000000..b2d946f1c --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightServerLevelDelegateProxy.java @@ -0,0 +1,237 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2; + +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightFaweAdapter; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + // FAWE start - extent not EditSession + private final Extent editSession; + //FAWE end + private final ServerLevel serverLevel; + //FAWE start - use FAWE adapter + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + //FAWE end + //FAWE start - force error if method not caught by this instance + private final boolean errorOnPassthrough; + //FAWE end + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + //FAWE start + this.errorOnPassthrough = false; + //FAWE end + } + + public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) + ); + } + + //FAWE start - force error if method not caught by this instance + private PaperweightServerLevelDelegateProxy(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + this.editSession = extent; + this.serverLevel = serverLevel; + this.errorOnPassthrough = errorOnPassthrough; + } + + public static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + ); + } + //FAWE end + + @Nullable + private BlockEntity getBlockEntity(BlockPos blockPos) { + BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); + if (tileEntity == null) { + return null; + } + BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); + newEntity.load((CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ).getNbtReference().getValue())); + + return newEntity; + } + + private BlockState getBlockState(BlockPos blockPos) { + return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + } + + private boolean setBlock(BlockPos blockPos, BlockState blockState) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private boolean removeBlock(BlockPos blockPos, boolean bl) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private FluidState getFluidState(BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + + private boolean isWaterAt(BlockPos pos) { + return getBlockState(pos).getFluidState().is(FluidTags.WATER); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //FAWE start - cannot use switch where method names are equal + String methodName = method.getName(); + if (Refraction.pickName("getBlockState", "a_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } + } + if (Refraction.pickName("getBlockEntity", "c_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } + } + if ("a".equals(methodName) || "setBlock".equals(methodName) || "removeBlock".equals(methodName) || "destroyBlock".equals( + methodName)) { + if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) { + // setBlock + return setBlock(blockPos, blockState); + } else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) { + // removeBlock (and also matches destroyBlock) + return removeBlock(blockPos, bl); + } + } + //FAWE start + if (Refraction.pickName("getFluidState", "b_").equals(methodName)) { //net.minecraft.world.level.BlockGetter + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return getFluidState(blockPos); + } + } + if (Refraction.pickName("isWaterAt", "z").equals(methodName)) { //net.minecraft.world.level.LevelReader + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return isWaterAt(blockPos); + } + } + if (Refraction.pickName("getEntities", "a_").equals(methodName)) { //net.minecraft.world.level.EntityGetter + if (args.length == 2 && args[0] instanceof Entity && args[1] instanceof AABB) { + return new ArrayList<>(); + } + } + // Specific passthroughs that we want to allow + // net.minecraft.world.level.BlockAndTintGetter + if (Refraction.pickName("getRawBrightness", "b").equals(methodName)) { + return method.invoke(this.serverLevel, args); + } + // net.minecraft.world.level.LevelHeightAccessor + if (Refraction.pickName("getMaxBuildHeight", "al").equals(methodName)) { + if (args.length == 0) { + return method.invoke(this.serverLevel, args); + } + } + // net.minecraft.world.level.SignalGetter + if (Refraction.pickName("hasNeighborSignal", "C").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getSignal", "c").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getControlInputSignal", "a").equals(methodName)) { + if (args.length == 3 && args[0] instanceof BlockPos && args[1] instanceof Direction && args[2] instanceof Boolean) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getDirectSignal", "a").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + //FAWE start - force error if method not caught by this instance + if (errorOnPassthrough) { + LOGGER.error( + """ + Attempted passthough of method {}. + Method argument types: {} + Method argument values: {} + """, + method.getName(), + Arrays.stream(args).map(a -> a.getClass().getName()).toList(), + Arrays.stream(args).map(Object::toString).toList() + ); + throw new FaweException("Method required passthrough."); + } + //FAWE end + + return method.invoke(this.serverLevel, args); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java index 0bc339f82..af676c0fd 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java @@ -4,6 +4,7 @@ import com.fastasyncworldedit.bukkit.adapter.FaweAdapter; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBatchProcessor; @@ -13,6 +14,7 @@ import com.fastasyncworldedit.core.util.NbtUtils; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -21,6 +23,7 @@ import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazy import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; @@ -33,7 +36,6 @@ import com.sk89q.worldedit.registry.state.IntegerProperty; 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.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; @@ -284,9 +286,16 @@ public final class PaperweightFaweAdapter extends FaweAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + ); + @Override public Set getSupportedSideEffects() { - return SideEffectSet.defaults().getSideEffectsToApply(); + return SUPPORTED_SIDE_EFFECTS; } @Override @@ -434,6 +443,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter crossChunkSecondPasses, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent, mask, crossChunkSecondPasses, threadProcessors, region, finished); + World world = ExtentTraverser.getWorldFromExtent(extent); + if (world == null) { + throw new UnsupportedOperationException( + "World is required for PlacementStateProcessor but none found in given extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + this.proxyLevel = PaperweightLevelProxy.getInstance(((CraftWorld) bukkitWorld.getWorld()).getHandle(), this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + @Override + protected char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ) { + Block block = ((PaperweightBlockMaterial) state.getMaterial()).getBlock(); + Vec3 pos = new Vec3(clickPos.x(), clickPos.y(), clickPos.z()); + net.minecraft.core.Direction side = net.minecraft.core.Direction.valueOf(clickedFaceDirection.toString()); + BlockPos blockPos = new BlockPos(clickedBlock.x(), clickedBlock.y(), clickedBlock.z()); + net.minecraft.world.level.block.state.BlockState newState = block.getStateForPlacement(mutableBlockPlaceContext.withSetting( + new BlockHitResult(pos, side, blockPos, false), + side.getOpposite() + )); + return newState == null ? BlockTypesCache.ReservedIDs.AIR : adapter.ibdIDToOrdinal(Block.BLOCK_STATE_REGISTRY.getId( + newState)); + } + + @Override + @Nullable + public Extent construct(Extent child) { + if (child == getExtent()) { + return this; + } + return new PaperweightPlacementStateProcessor(child, mask, region); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor(extent, mask, postCompleteSecondPasses, threadProcessors, region, finished); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java index ded4bcfdd..ebf1ada46 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java @@ -856,12 +856,12 @@ public final class PaperweightAdapter implements BukkitImplAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( - SideEffect.NEIGHBORS, - SideEffect.LIGHTING, - SideEffect.VALIDATION, - SideEffect.ENTITY_AI, - SideEffect.EVENTS, - SideEffect.UPDATE + //FAWE start - FAWE-supported side effects + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + //FAWE end ); @Override diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java new file mode 100644 index 000000000..84727c8c5 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java @@ -0,0 +1,237 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R3; + +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.PaperweightFaweAdapter; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + // FAWE start - extent not EditSession + private final Extent editSession; + //FAWE end + private final ServerLevel serverLevel; + //FAWE start - use FAWE adapter + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + //FAWE end + //FAWE start - force error if method not caught by this instance + private final boolean errorOnPassthrough; + //FAWE end + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + //FAWE start + this.errorOnPassthrough = false; + //FAWE end + } + + public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) + ); + } + + //FAWE start - force error if method not caught by this instance + private PaperweightServerLevelDelegateProxy(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + this.editSession = extent; + this.serverLevel = serverLevel; + this.errorOnPassthrough = errorOnPassthrough; + } + + public static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + ); + } + //FAWE end + + @Nullable + private BlockEntity getBlockEntity(BlockPos blockPos) { + BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); + if (tileEntity == null) { + return null; + } + BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); + newEntity.load((CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ).getNbtReference().getValue())); + + return newEntity; + } + + private BlockState getBlockState(BlockPos blockPos) { + return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + } + + private boolean setBlock(BlockPos blockPos, BlockState blockState) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private boolean removeBlock(BlockPos blockPos, boolean bl) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private FluidState getFluidState(BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + + private boolean isWaterAt(BlockPos pos) { + return getBlockState(pos).getFluidState().is(FluidTags.WATER); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //FAWE start - cannot use switch where method names are equal + String methodName = method.getName(); + if (Refraction.pickName("getBlockState", "a_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } + } + if (Refraction.pickName("getBlockEntity", "c_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } + } + if ("a".equals(methodName) || "setBlock".equals(methodName) || "removeBlock".equals(methodName) || "destroyBlock".equals( + methodName)) { + if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) { + // setBlock + return setBlock(blockPos, blockState); + } else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) { + // removeBlock (and also matches destroyBlock) + return removeBlock(blockPos, bl); + } + } + //FAWE start + if (Refraction.pickName("getFluidState", "b_").equals(methodName)) { //net.minecraft.world.level.BlockGetter + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return getFluidState(blockPos); + } + } + if (Refraction.pickName("isWaterAt", "z").equals(methodName)) { //net.minecraft.world.level.LevelReader + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return isWaterAt(blockPos); + } + } + if (Refraction.pickName("getEntities", "a_").equals(methodName)) { //net.minecraft.world.level.EntityGetter + if (args.length == 2 && args[0] instanceof Entity && args[1] instanceof AABB) { + return new ArrayList<>(); + } + } + // Specific passthroughs that we want to allow + // net.minecraft.world.level.BlockAndTintGetter + if (Refraction.pickName("getRawBrightness", "b").equals(methodName)) { + return method.invoke(this.serverLevel, args); + } + // net.minecraft.world.level.LevelHeightAccessor + if (Refraction.pickName("getMaxBuildHeight", "al").equals(methodName)) { + if (args.length == 0) { + return method.invoke(this.serverLevel, args); + } + } + // net.minecraft.world.level.SignalGetter + if (Refraction.pickName("hasNeighborSignal", "C").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getSignal", "c").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getControlInputSignal", "a").equals(methodName)) { + if (args.length == 3 && args[0] instanceof BlockPos && args[1] instanceof Direction && args[2] instanceof Boolean) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getDirectSignal", "a").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + //FAWE start - force error if method not caught by this instance + if (errorOnPassthrough) { + LOGGER.error( + """ + Attempted passthough of method {}. + Method argument types: {} + Method argument values: {} + """, + method.getName(), + Arrays.stream(args).map(a -> a.getClass().getName()).toList(), + Arrays.stream(args).map(Object::toString).toList() + ); + throw new FaweException("Method required passthrough."); + } + //FAWE end + + return method.invoke(this.serverLevel, args); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java index 1abfc5b04..34a2d6ee0 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java @@ -4,6 +4,7 @@ import com.fastasyncworldedit.bukkit.adapter.FaweAdapter; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBatchProcessor; @@ -13,6 +14,7 @@ import com.fastasyncworldedit.core.util.NbtUtils; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -21,7 +23,7 @@ import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.nbt.PaperweightLazy import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.regen.PaperweightRegen; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.math.BlockVector3; @@ -33,7 +35,6 @@ import com.sk89q.worldedit.registry.state.IntegerProperty; 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.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; @@ -284,9 +285,16 @@ public final class PaperweightFaweAdapter extends FaweAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + ); + @Override public Set getSupportedSideEffects() { - return SideEffectSet.defaults().getSideEffectsToApply(); + return SUPPORTED_SIDE_EFFECTS; } @Override @@ -434,6 +442,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter crossChunkSecondPasses, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent, mask, crossChunkSecondPasses, threadProcessors, region, finished); + World world = ExtentTraverser.getWorldFromExtent(extent); + if (world == null) { + throw new UnsupportedOperationException( + "World is required for PlacementStateProcessor but none found in given extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + this.proxyLevel = PaperweightLevelProxy.getInstance(((CraftWorld) bukkitWorld.getWorld()).getHandle(), this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + @Override + protected char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ) { + Block block = ((PaperweightBlockMaterial) state.getMaterial()).getBlock(); + Vec3 pos = new Vec3(clickPos.x(), clickPos.y(), clickPos.z()); + net.minecraft.core.Direction side = net.minecraft.core.Direction.valueOf(clickedFaceDirection.toString()); + BlockPos blockPos = new BlockPos(clickedBlock.x(), clickedBlock.y(), clickedBlock.z()); + net.minecraft.world.level.block.state.BlockState newState = block.getStateForPlacement(mutableBlockPlaceContext.withSetting( + new BlockHitResult(pos, side, blockPos, false), + side.getOpposite() + )); + return newState == null ? BlockTypesCache.ReservedIDs.AIR : adapter.ibdIDToOrdinal(Block.BLOCK_STATE_REGISTRY.getId( + newState)); + } + + @Override + @Nullable + public Extent construct(Extent child) { + if (child == getExtent()) { + return this; + } + return new PaperweightPlacementStateProcessor(child, mask, region); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor(extent, mask, postCompleteSecondPasses, threadProcessors, region, finished); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java index 206ade66a..f45104c14 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java @@ -880,12 +880,12 @@ public final class PaperweightAdapter implements BukkitImplAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( - SideEffect.NEIGHBORS, - SideEffect.LIGHTING, - SideEffect.VALIDATION, - SideEffect.ENTITY_AI, - SideEffect.EVENTS, - SideEffect.UPDATE + //FAWE start - FAWE-supported side effects + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + //FAWE end ); @Override diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java new file mode 100644 index 000000000..f2f32edcb --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java @@ -0,0 +1,240 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R4; + +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.PaperweightFaweAdapter; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + // FAWE start - extent not EditSession + private final Extent editSession; + //FAWE end + private final ServerLevel serverLevel; + //FAWE start - use FAWE adapter + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + //FAWE end + //FAWE start - force error if method not caught by this instance + private final boolean errorOnPassthrough; + //FAWE end + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + //FAWE start + this.errorOnPassthrough = false; + //FAWE end + } + + public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) + ); + } + + //FAWE start - force error if method not caught by this instance + private PaperweightServerLevelDelegateProxy(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + this.editSession = extent; + this.serverLevel = serverLevel; + this.errorOnPassthrough = errorOnPassthrough; + } + + public static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + ); + } + //FAWE end + + @Nullable + private BlockEntity getBlockEntity(BlockPos blockPos) { + BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); + if (tileEntity == null) { + return null; + } + BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); + newEntity.loadWithComponents( + (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ).getNbtReference().getValue()), + this.serverLevel.registryAccess() + ); + + return newEntity; + } + + private BlockState getBlockState(BlockPos blockPos) { + return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + } + + private boolean setBlock(BlockPos blockPos, BlockState blockState) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private boolean removeBlock(BlockPos blockPos, boolean bl) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private FluidState getFluidState(BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + + private boolean isWaterAt(BlockPos pos) { + return getBlockState(pos).getFluidState().is(FluidTags.WATER); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //FAWE start - cannot use switch where method names are equal + String methodName = method.getName(); + if (Refraction.pickName("getBlockState", "a_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } + } + if (Refraction.pickName("getBlockEntity", "c_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } + } + if ("a".equals(methodName) || "setBlock".equals(methodName) || "removeBlock".equals(methodName) || "destroyBlock".equals( + methodName)) { + if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) { + // setBlock + return setBlock(blockPos, blockState); + } else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) { + // removeBlock (and also matches destroyBlock) + return removeBlock(blockPos, bl); + } + } + //FAWE start + if (Refraction.pickName("getFluidState", "b_").equals(methodName)) { //net.minecraft.world.level.BlockGetter + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return getFluidState(blockPos); + } + } + if (Refraction.pickName("isWaterAt", "z").equals(methodName)) { //net.minecraft.world.level.LevelReader + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return isWaterAt(blockPos); + } + } + if (Refraction.pickName("getEntities", "a_").equals(methodName)) { //net.minecraft.world.level.EntityGetter + if (args.length == 2 && args[0] instanceof Entity && args[1] instanceof AABB) { + return new ArrayList<>(); + } + } + // Specific passthroughs that we want to allow + // net.minecraft.world.level.BlockAndTintGetter + if (Refraction.pickName("getRawBrightness", "b").equals(methodName)) { + return method.invoke(this.serverLevel, args); + } + // net.minecraft.world.level.LevelHeightAccessor + if (Refraction.pickName("getMaxBuildHeight", "al").equals(methodName)) { + if (args.length == 0) { + return method.invoke(this.serverLevel, args); + } + } + // net.minecraft.world.level.SignalGetter + if (Refraction.pickName("hasNeighborSignal", "C").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getSignal", "c").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getControlInputSignal", "a").equals(methodName)) { + if (args.length == 3 && args[0] instanceof BlockPos && args[1] instanceof Direction && args[2] instanceof Boolean) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getDirectSignal", "a").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + //FAWE start - force error if method not caught by this instance + if (errorOnPassthrough) { + LOGGER.error( + """ + Attempted passthough of method {}. + Method argument types: {} + Method argument values: {} + """, + method.getName(), + Arrays.stream(args).map(a -> a.getClass().getName()).toList(), + Arrays.stream(args).map(Object::toString).toList() + ); + throw new FaweException("Method required passthrough."); + } + //FAWE end + + return method.invoke(this.serverLevel, args); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java index 692e03a94..6351210df 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java @@ -4,6 +4,7 @@ import com.fastasyncworldedit.bukkit.adapter.FaweAdapter; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBatchProcessor; @@ -13,6 +14,7 @@ import com.fastasyncworldedit.core.util.NbtUtils; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -22,6 +24,7 @@ import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.nbt.PaperweightLazy import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.regen.PaperweightRegen; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; @@ -34,7 +37,6 @@ import com.sk89q.worldedit.registry.state.IntegerProperty; 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.util.concurrency.LazyReference; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; @@ -293,9 +295,16 @@ public final class PaperweightFaweAdapter extends FaweAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + ); + @Override public Set getSupportedSideEffects() { - return SideEffectSet.defaults().getSideEffectsToApply(); + return SUPPORTED_SIDE_EFFECTS; } @Override @@ -443,6 +452,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter crossChunkSecondPasses, + ServerLevel serverLevel, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent, mask, crossChunkSecondPasses, threadProcessors, region, finished); + this.proxyLevel = PaperweightLevelProxy.getInstance(serverLevel, this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + @Override + protected char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ) { + Block block = ((PaperweightBlockMaterial) state.getMaterial()).getBlock(); + Vec3 pos = new Vec3(clickPos.x(), clickPos.y(), clickPos.z()); + net.minecraft.core.Direction side = net.minecraft.core.Direction.valueOf(clickedFaceDirection.toString()); + BlockPos blockPos = new BlockPos(clickedBlock.x(), clickedBlock.y(), clickedBlock.z()); + net.minecraft.world.level.block.state.BlockState newState = block.getStateForPlacement(mutableBlockPlaceContext.withSetting( + new BlockHitResult(pos, side, blockPos, false), + side.getOpposite() + )); + return newState == null ? BlockTypesCache.ReservedIDs.AIR : adapter.ibdIDToOrdinal(Block.BLOCK_STATE_REGISTRY.getId( + newState)); + } + + @Override + @Nullable + public Extent construct(Extent child) { + if (child == getExtent()) { + return this; + } + return new PaperweightPlacementStateProcessor(child, mask, region); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor( + extent, + mask, + postCompleteSecondPasses, + proxyLevel.serverLevel, + threadProcessors, + region, + finished + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java index 125d000bf..995b22135 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java @@ -886,12 +886,12 @@ public final class PaperweightAdapter implements BukkitImplAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( - SideEffect.NEIGHBORS, - SideEffect.LIGHTING, - SideEffect.VALIDATION, - SideEffect.ENTITY_AI, - SideEffect.EVENTS, - SideEffect.UPDATE + //FAWE start - FAWE-supported side effects + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + //FAWE end ); @Override diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java new file mode 100644 index 000000000..a992e936c --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java @@ -0,0 +1,240 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_R1; + +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.PaperweightFaweAdapter; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + // FAWE start - extent not EditSession + private final Extent editSession; + //FAWE end + private final ServerLevel serverLevel; + //FAWE start - use FAWE adapter + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + //FAWE end + //FAWE start - force error if method not caught by this instance + private final boolean errorOnPassthrough; + //FAWE end + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + //FAWE start + this.errorOnPassthrough = false; + //FAWE end + } + + public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) + ); + } + + //FAWE start - force error if method not caught by this instance + private PaperweightServerLevelDelegateProxy(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + this.editSession = extent; + this.serverLevel = serverLevel; + this.errorOnPassthrough = errorOnPassthrough; + } + + public static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + ); + } + //FAWE end + + @Nullable + private BlockEntity getBlockEntity(BlockPos blockPos) { + BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); + if (tileEntity == null) { + return null; + } + BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); + newEntity.loadWithComponents( + (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ).getNbtReference().getValue()), + this.serverLevel.registryAccess() + ); + + return newEntity; + } + + private BlockState getBlockState(BlockPos blockPos) { + return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + } + + private boolean setBlock(BlockPos blockPos, BlockState blockState) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private boolean removeBlock(BlockPos blockPos, boolean bl) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private FluidState getFluidState(BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + + private boolean isWaterAt(BlockPos pos) { + return getBlockState(pos).getFluidState().is(FluidTags.WATER); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //FAWE start - cannot use switch where method names are equal + String methodName = method.getName(); + if (Refraction.pickName("getBlockState", "a_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } + } + if (Refraction.pickName("getBlockEntity", "c_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } + } + if ("a".equals(methodName) || "setBlock".equals(methodName) || "removeBlock".equals(methodName) || "destroyBlock".equals( + methodName)) { + if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) { + // setBlock + return setBlock(blockPos, blockState); + } else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) { + // removeBlock (and also matches destroyBlock) + return removeBlock(blockPos, bl); + } + } + //FAWE start + if (Refraction.pickName("getFluidState", "b_").equals(methodName)) { //net.minecraft.world.level.BlockGetter + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return getFluidState(blockPos); + } + } + if (Refraction.pickName("isWaterAt", "z").equals(methodName)) { //net.minecraft.world.level.LevelReader + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return isWaterAt(blockPos); + } + } + if (Refraction.pickName("getEntities", "a_").equals(methodName)) { //net.minecraft.world.level.EntityGetter + if (args.length == 2 && args[0] instanceof Entity && args[1] instanceof AABB) { + return new ArrayList<>(); + } + } + // Specific passthroughs that we want to allow + // net.minecraft.world.level.BlockAndTintGetter + if (Refraction.pickName("getRawBrightness", "b").equals(methodName)) { + return method.invoke(this.serverLevel, args); + } + // net.minecraft.world.level.LevelHeightAccessor + if (Refraction.pickName("getMaxBuildHeight", "al").equals(methodName)) { + if (args.length == 0) { + return method.invoke(this.serverLevel, args); + } + } + // net.minecraft.world.level.SignalGetter + if (Refraction.pickName("hasNeighborSignal", "C").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getSignal", "c").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getControlInputSignal", "a").equals(methodName)) { + if (args.length == 3 && args[0] instanceof BlockPos && args[1] instanceof Direction && args[2] instanceof Boolean) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getDirectSignal", "a").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + //FAWE start - force error if method not caught by this instance + if (errorOnPassthrough) { + LOGGER.error( + """ + Attempted passthough of method {}. + Method argument types: {} + Method argument values: {} + """, + method.getName(), + Arrays.stream(args).map(a -> a.getClass().getName()).toList(), + Arrays.stream(args).map(Object::toString).toList() + ); + throw new FaweException("Method required passthrough."); + } + //FAWE end + + return method.invoke(this.serverLevel, args); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java index 353f6b5c1..b75a6529e 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java @@ -4,6 +4,7 @@ import com.fastasyncworldedit.bukkit.adapter.FaweAdapter; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBatchProcessor; @@ -13,6 +14,7 @@ import com.fastasyncworldedit.core.util.NbtUtils; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -22,6 +24,7 @@ import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.nbt.PaperweightLazy import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.regen.PaperweightRegen; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; @@ -34,7 +37,6 @@ import com.sk89q.worldedit.registry.state.IntegerProperty; 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.util.concurrency.LazyReference; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; @@ -293,9 +295,16 @@ public final class PaperweightFaweAdapter extends FaweAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + ); + @Override public Set getSupportedSideEffects() { - return SideEffectSet.defaults().getSideEffectsToApply(); + return SUPPORTED_SIDE_EFFECTS; } @Override @@ -443,6 +452,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter crossChunkSecondPasses, + ServerLevel serverLevel, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent, mask, crossChunkSecondPasses, threadProcessors, region, finished); + this.proxyLevel = PaperweightLevelProxy.getInstance(serverLevel, this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + @Override + protected char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ) { + Block block = ((PaperweightBlockMaterial) state.getMaterial()).getBlock(); + Vec3 pos = new Vec3(clickPos.x(), clickPos.y(), clickPos.z()); + net.minecraft.core.Direction side = net.minecraft.core.Direction.valueOf(clickedFaceDirection.toString()); + BlockPos blockPos = new BlockPos(clickedBlock.x(), clickedBlock.y(), clickedBlock.z()); + net.minecraft.world.level.block.state.BlockState newState = block.getStateForPlacement(mutableBlockPlaceContext.withSetting( + new BlockHitResult(pos, side, blockPos, false), + side.getOpposite() + )); + return newState == null ? BlockTypesCache.ReservedIDs.AIR : adapter.ibdIDToOrdinal(Block.BLOCK_STATE_REGISTRY.getId( + newState)); + } + + @Override + @Nullable + public Extent construct(Extent child) { + if (child == getExtent()) { + return this; + } + return new PaperweightPlacementStateProcessor(child, mask, region); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor( + extent, + mask, + postCompleteSecondPasses, + proxyLevel.serverLevel, + threadProcessors, + region, + finished + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java index 6d61641d7..d3497145c 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java @@ -849,12 +849,12 @@ public final class PaperweightAdapter implements BukkitImplAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( - SideEffect.NEIGHBORS, + //FAWE start - FAWE-supported side effects + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, SideEffect.LIGHTING, - SideEffect.VALIDATION, - SideEffect.ENTITY_AI, - SideEffect.EVENTS, - SideEffect.UPDATE + SideEffect.NEIGHBORS + //FAWE end ); @Override diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java new file mode 100644 index 000000000..4b660ccf3 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java @@ -0,0 +1,240 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_3; + +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_3.PaperweightFaweAdapter; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + // FAWE start - extent not EditSession + private final Extent editSession; + //FAWE end + private final ServerLevel serverLevel; + //FAWE start - use FAWE adapter + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + //FAWE end + //FAWE start - force error if method not caught by this instance + private final boolean errorOnPassthrough; + //FAWE end + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + //FAWE start + this.errorOnPassthrough = false; + //FAWE end + } + + public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) + ); + } + + //FAWE start - force error if method not caught by this instance + private PaperweightServerLevelDelegateProxy(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + this.editSession = extent; + this.serverLevel = serverLevel; + this.errorOnPassthrough = errorOnPassthrough; + } + + public static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + ); + } + //FAWE end + + @Nullable + private BlockEntity getBlockEntity(BlockPos blockPos) { + BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); + if (tileEntity == null) { + return null; + } + BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); + newEntity.loadWithComponents( + (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ).getNbtReference().getValue()), + this.serverLevel.registryAccess() + ); + + return newEntity; + } + + private BlockState getBlockState(BlockPos blockPos) { + return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + } + + private boolean setBlock(BlockPos blockPos, BlockState blockState) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private boolean removeBlock(BlockPos blockPos, boolean bl) { + try { + return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private FluidState getFluidState(BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + + private boolean isWaterAt(BlockPos pos) { + return getBlockState(pos).getFluidState().is(FluidTags.WATER); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + //FAWE start - cannot use switch where method names are equal + String methodName = method.getName(); + if (Refraction.pickName("getBlockState", "a_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } + } + if (Refraction.pickName("getBlockEntity", "c_").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } + } + if ("a".equals(methodName) || "setBlock".equals(methodName) || "removeBlock".equals(methodName) || "destroyBlock".equals( + methodName)) { + if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) { + // setBlock + return setBlock(blockPos, blockState); + } else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) { + // removeBlock (and also matches destroyBlock) + return removeBlock(blockPos, bl); + } + } + //FAWE start + if (Refraction.pickName("getFluidState", "b_").equals(methodName)) { //net.minecraft.world.level.BlockGetter + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return getFluidState(blockPos); + } + } + if (Refraction.pickName("isWaterAt", "z").equals(methodName)) { //net.minecraft.world.level.LevelReader + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + return isWaterAt(blockPos); + } + } + if (Refraction.pickName("getEntities", "a_").equals(methodName)) { //net.minecraft.world.level.EntityGetter + if (args.length == 2 && args[0] instanceof Entity && args[1] instanceof AABB) { + return new ArrayList<>(); + } + } + // Specific passthroughs that we want to allow + // net.minecraft.world.level.BlockAndTintGetter + if (Refraction.pickName("getRawBrightness", "b").equals(methodName)) { + return method.invoke(this.serverLevel, args); + } + // net.minecraft.world.level.LevelHeightAccessor + if (Refraction.pickName("getMaxBuildHeight", "al").equals(methodName)) { + if (args.length == 0) { + return method.invoke(this.serverLevel, args); + } + } + // net.minecraft.world.level.SignalGetter + if (Refraction.pickName("hasNeighborSignal", "C").equals(methodName)) { + if (args.length == 1 && args[0] instanceof BlockPos) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getSignal", "c").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getControlInputSignal", "a").equals(methodName)) { + if (args.length == 3 && args[0] instanceof BlockPos && args[1] instanceof Direction && args[2] instanceof Boolean) { + return method.invoke(this.serverLevel, args); + } + } + if (Refraction.pickName("getDirectSignal", "a").equals(methodName)) { + if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { + return method.invoke(this.serverLevel, args); + } + } + //FAWE start - force error if method not caught by this instance + if (errorOnPassthrough) { + LOGGER.error( + """ + Attempted passthough of method {}. + Method argument types: {} + Method argument values: {} + """, + method.getName(), + Arrays.stream(args).map(a -> a.getClass().getName()).toList(), + Arrays.stream(args).map(Object::toString).toList() + ); + throw new FaweException("Method required passthrough."); + } + //FAWE end + + return method.invoke(this.serverLevel, args); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java index 40e6ead32..69ba10a71 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java @@ -4,6 +4,7 @@ import com.fastasyncworldedit.bukkit.adapter.FaweAdapter; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBatchProcessor; @@ -13,6 +14,7 @@ import com.fastasyncworldedit.core.util.NbtUtils; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -22,6 +24,7 @@ import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_3.PaperweightAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_3.regen.PaperweightRegen; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; @@ -34,7 +37,6 @@ import com.sk89q.worldedit.registry.state.IntegerProperty; 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.util.concurrency.LazyReference; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.RegenOptions; @@ -279,9 +281,16 @@ public final class PaperweightFaweAdapter extends FaweAdapter SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS + ); + @Override public Set getSupportedSideEffects() { - return SideEffectSet.defaults().getSideEffectsToApply(); + return SUPPORTED_SIDE_EFFECTS; } @Override @@ -425,6 +434,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter crossChunkSecondPasses, + ServerLevel serverLevel, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent, mask, crossChunkSecondPasses, threadProcessors, region, finished); + this.proxyLevel = PaperweightLevelProxy.getInstance(serverLevel, this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + @Override + protected char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ) { + Block block = ((PaperweightBlockMaterial) state.getMaterial()).getBlock(); + Vec3 pos = new Vec3(clickPos.x(), clickPos.y(), clickPos.z()); + net.minecraft.core.Direction side = net.minecraft.core.Direction.valueOf(clickedFaceDirection.toString()); + BlockPos blockPos = new BlockPos(clickedBlock.x(), clickedBlock.y(), clickedBlock.z()); + net.minecraft.world.level.block.state.BlockState newState = block.getStateForPlacement(mutableBlockPlaceContext.withSetting( + new BlockHitResult(pos, side, blockPos, false), + side.getOpposite() + )); + return newState == null ? BlockTypesCache.ReservedIDs.AIR : adapter.ibdIDToOrdinal(Block.BLOCK_STATE_REGISTRY.getId( + newState)); + } + + @Override + @Nullable + public Extent construct(Extent child) { + if (child == getExtent()) { + return this; + } + return new PaperweightPlacementStateProcessor(child, mask, region); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor( + extent, + mask, + postCompleteSecondPasses, + proxyLevel.serverLevel, + threadProcessors, + region, + finished + ); + } + +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 01d84e8ef..73a7421c3 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -21,6 +21,7 @@ package com.sk89q.worldedit.bukkit; import com.fastasyncworldedit.bukkit.util.MinecraftVersion; import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.google.common.collect.Sets; @@ -37,7 +38,10 @@ import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.MultiUserPlatform; import com.sk89q.worldedit.extension.platform.Preference; import com.sk89q.worldedit.extension.platform.Watchdog; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.lifecycle.Lifecycled; import com.sk89q.worldedit.world.DataFixer; @@ -309,5 +313,10 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser } return this.plugin.getBukkitImplAdapter().getTickingPostProcessor(); } + + @Override + public PlacementStateProcessor getPlatformPlacementProcessor(Extent extent, BlockTypeMask mask, Region region) { + return this.plugin.getBukkitImplAdapter().getPlatformPlacementProcessor(extent, mask, region); + } //FAWE end } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 8fee3b417..986b348d3 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -23,6 +23,7 @@ import com.fastasyncworldedit.bukkit.FaweBukkit; import com.fastasyncworldedit.bukkit.adapter.IBukkitAdapter; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; @@ -34,6 +35,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; @@ -402,5 +404,13 @@ public interface BukkitImplAdapter extends IBukkitAdapter { default IBatchProcessor getTickingPostProcessor() { return null; } + + /** + * Returns an {@link PlacementStateProcessor} instance for processing placed blocks to "fix" them. + * @since TODO + */ + default PlacementStateProcessor getPlatformPlacementProcessor(Extent extent, BlockTypeMask mask, Region region) { + return null; + } //FAWE end } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweAPI.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweAPI.java index 28367a949..6ee50e2f0 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweAPI.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweAPI.java @@ -32,6 +32,8 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.world.World; import org.apache.logging.log4j.Logger; @@ -355,8 +357,12 @@ public class FaweAPI { if (unwrapped instanceof IQueueExtent) { queue = (IQueueExtent) unwrapped; } else if (Settings.settings().QUEUE.PARALLEL_THREADS > 1) { - ParallelQueueExtent parallel = - new ParallelQueueExtent(Fawe.instance().getQueueHandler(), world, true); + ParallelQueueExtent parallel = new ParallelQueueExtent( + Fawe.instance().getQueueHandler(), + world, + true, + SideEffectSet.none().with(SideEffect.LIGHTING) + ); queue = parallel.getExtent(); } else { queue = Fawe.instance().getQueueHandler().getQueue(world); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/Adjacent2DMaskParser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/Adjacent2DMaskParser.java new file mode 100644 index 000000000..116d3634a --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extension/factory/parser/mask/Adjacent2DMaskParser.java @@ -0,0 +1,51 @@ +package com.fastasyncworldedit.core.extension.factory.parser.mask; + +import com.fastasyncworldedit.core.extension.factory.parser.RichParser; +import com.fastasyncworldedit.core.function.mask.Adjacent2DMask; +import com.fastasyncworldedit.core.function.mask.AdjacentAny2DMask; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.SuggestionHelper; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.mask.Mask; + +import javax.annotation.Nonnull; +import java.util.stream.Stream; + +public class Adjacent2DMaskParser extends RichParser { + + public Adjacent2DMaskParser(WorldEdit worldEdit) { + super(worldEdit, "~2d", "adjacent2d"); + } + + @Override + protected Stream getSuggestions(String argumentInput, int index) { + if (index == 0) { + return worldEdit.getMaskFactory().getSuggestions(argumentInput).stream(); + } else if (index == 1 || index == 2) { + return SuggestionHelper.suggestPositiveDoubles(argumentInput); + } + return Stream.empty(); + } + + @Override + protected Mask parseFromInput(@Nonnull String[] arguments, ParserContext context) throws InputParseException { + if (arguments.length == 0) { + return null; + } + Mask subMask = worldEdit.getMaskFactory().parseFromInput(arguments[0], context); + int min = arguments.length > 1 ? Integer.parseInt(arguments[1]) : -1; + int max = arguments.length > 2 ? Integer.parseInt(arguments[2]) : -1; + if (min == -1 && max == -1) { + min = 1; + max = 4; + } else if (max == -1) { + max = min; + } + if (max >= 4 && min == 1) { + return new AdjacentAny2DMask(subMask); + } + return new Adjacent2DMask(subMask, min, max); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/PassthroughExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/PassthroughExtent.java index 5ac2f9d7b..7a118d8c1 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/PassthroughExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/PassthroughExtent.java @@ -6,16 +6,18 @@ import com.fastasyncworldedit.core.queue.Filter; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.Countable; +import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -25,6 +27,7 @@ import com.sk89q.worldedit.world.block.BlockType; import javax.annotation.Nullable; import java.util.List; import java.util.Set; +import java.util.UUID; public class PassthroughExtent extends AbstractDelegateExtent { @@ -194,17 +197,55 @@ public class PassthroughExtent extends AbstractDelegateExtent { return getExtent().getBlock(position); } + @Override + public BlockState getBlock(int x, int y, int z) { + return getExtent().getBlock(x, y, z); + } + @Override public BaseBlock getFullBlock(BlockVector3 position) { return getExtent().getFullBlock(position); } + @Override + public BaseBlock getFullBlock(int x, int y, int z) { + return getExtent().getFullBlock(x, y, z); + } + + @Override + public List getEntities(Region region) { + return getExtent().getEntities(region); + } + + @Override + public List getEntities() { + return getExtent().getEntities(); + } + + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity entity) { + return getExtent().createEntity(location, entity); + } + + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity entity, UUID uuid) { + return getExtent().createEntity(location, entity, uuid); + } + @Override @Deprecated public > boolean setBlock(BlockVector3 position, T block) throws WorldEditException { return getExtent().setBlock(position, block); } + @Override + public > boolean setBlock(int x, int y, int z, T block) throws + WorldEditException { + return getExtent().setBlock(x, y, z, block); + } + @Override public boolean setTile(int x, int y, int z, CompoundTag tile) throws WorldEditException { return getExtent().setTile(x, y, z, tile); @@ -216,9 +257,8 @@ public class PassthroughExtent extends AbstractDelegateExtent { } @Override - @Nullable - public Operation commit() { - return getExtent().commit(); + public boolean setBiome(int x, int y, int z, BiomeType biome) { + return getExtent().setBiome(x, y, z, biome); } @Override @@ -226,6 +266,11 @@ public class PassthroughExtent extends AbstractDelegateExtent { return getExtent().cancel(); } + @Override + public void removeEntity(int x, int y, int z, UUID uuid) { + getExtent().removeEntity(x, y, z, uuid); + } + @Override public boolean isQueueEnabled() { return getExtent().isQueueEnabled(); @@ -251,6 +296,16 @@ public class PassthroughExtent extends AbstractDelegateExtent { return getExtent().apply(region, filter, full); } + @Override + public BiomeType getBiome(BlockVector3 position) { + return getExtent().getBiome(position); + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + return getExtent().getBiomeType(x, y, z); + } + @Override public T apply(Iterable positions, T filter) { return getExtent().apply(positions, filter); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java new file mode 100644 index 000000000..3479271d6 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java @@ -0,0 +1,545 @@ +package com.fastasyncworldedit.core.extent.processor; + +import com.fastasyncworldedit.core.configuration.Caption; +import com.fastasyncworldedit.core.extent.NullExtent; +import com.fastasyncworldedit.core.extent.filter.block.FilterBlock; +import com.fastasyncworldedit.core.function.mask.AdjacentAny2DMask; +import com.fastasyncworldedit.core.math.BlockVector3ChunkMap; +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.fastasyncworldedit.core.math.MutableVector3; +import com.fastasyncworldedit.core.nbt.FaweCompoundTag; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.fastasyncworldedit.core.queue.IChunk; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.registry.state.PropertyKey; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockCategoryMask; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockCategories; +import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +/** + * Processor/pattern that uses Minecraft internal methods to determine the shape of blocks, e.g. stairs and fences + * + * @since TODO + */ +public abstract class PlacementStateProcessor extends AbstractDelegateExtent implements IBatchProcessor, Pattern { + + private static final Direction[] NESW = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + private static final int CHUNK_BLOCK_POS_MASK = -1 << 4; + + private static volatile boolean SETUP = false; + private static BlockTypeMask DEFAULT_MASK = null; + private static BlockTypeMask IN_FIRST_PASS = null; + private static BlockTypeMask REQUIRES_SECOND_PASS = null; + private static BlockTypeMask IN_FIRST_PASS_WITHOUT_SECOND = null; + private static AdjacentAny2DMask ADJACENT_STAIR_MASK = null; + + protected final Extent extent; + protected final BlockTypeMask mask; + protected final Region region; + protected final Map postCompleteSecondPasses; + protected final ThreadLocal threadProcessors; + protected final AtomicBoolean finished; + private final MutableVector3 clickPos = new MutableVector3(); + private final MutableBlockVector3 clickedBlock = new MutableBlockVector3(); + private final MutableBlockVector3 placedBlock = new MutableBlockVector3(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + + private IChunkGet processChunkGet = null; + private IChunkSet processChunkSet = null; + private int processChunkX; + private int processChunkZ; + + /** + * Processor/pattern for performing block updates, e.g. stair shape and glass pane connections + * + * @param extent Extent to use + * @param mask Mask of blocks to perform updates on + * @since TODO + */ + public PlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { + super(extent); + // Required here as child classes are located within adapters and will therefore be statically accessed on startup, + // meaning we attempt to access BlockTypes class before it is correctly initialised. + if (!SETUP) { + synchronized (PlacementStateProcessor.class) { + if (!SETUP) { + setup(); + } + } + } + this.extent = extent; + this.mask = mask == null ? DEFAULT_MASK : mask; + this.region = region; + this.postCompleteSecondPasses = new ConcurrentHashMap<>(); + this.threadProcessors = ThreadLocal.withInitial(this::fork); + this.finished = new AtomicBoolean(); + } + + protected PlacementStateProcessor( + Extent extent, + BlockTypeMask mask, + Map crossChunkSecondPasses, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent); + this.extent = extent; + this.mask = mask; + this.region = region; + this.postCompleteSecondPasses = crossChunkSecondPasses; + this.threadProcessors = threadProcessors; + this.finished = finished; + } + + private static void setup() { + NullExtent nullExtent = new NullExtent( + com.sk89q.worldedit.extent.NullExtent.INSTANCE, + Caption.of("PlacementStateProcessor fell through to null extent") + ); + + IN_FIRST_PASS = new BlockTypeMask(nullExtent); + IN_FIRST_PASS.add( + BlockTypes.CHEST, + BlockTypes.TRAPPED_CHEST + ); + IN_FIRST_PASS.add(BlockCategories.STAIRS.getAll()); + + IN_FIRST_PASS_WITHOUT_SECOND = new BlockTypeMask(nullExtent); + IN_FIRST_PASS_WITHOUT_SECOND.add(BlockCategories.STAIRS.getAll()); + + REQUIRES_SECOND_PASS = new BlockTypeMask(nullExtent); + REQUIRES_SECOND_PASS.add( + BlockTypes.IRON_BARS, + BlockTypes.GLASS_PANE, + BlockTypes.BLACK_STAINED_GLASS_PANE, + BlockTypes.BLUE_STAINED_GLASS_PANE, + BlockTypes.BROWN_STAINED_GLASS_PANE, + BlockTypes.LIGHT_BLUE_STAINED_GLASS_PANE, + BlockTypes.PINK_STAINED_GLASS_PANE, + BlockTypes.LIGHT_GRAY_STAINED_GLASS_PANE, + BlockTypes.GRAY_STAINED_GLASS_PANE, + BlockTypes.CYAN_STAINED_GLASS_PANE, + BlockTypes.PURPLE_STAINED_GLASS_PANE, + BlockTypes.GREEN_STAINED_GLASS_PANE, + BlockTypes.LIME_STAINED_GLASS_PANE, + BlockTypes.MAGENTA_STAINED_GLASS_PANE, + BlockTypes.YELLOW_STAINED_GLASS_PANE, + BlockTypes.ORANGE_STAINED_GLASS_PANE, + BlockTypes.RED_STAINED_GLASS_PANE, + BlockTypes.WHITE_STAINED_GLASS_PANE, + BlockTypes.TRIPWIRE, + BlockTypes.TWISTING_VINES_PLANT, + BlockTypes.CAVE_VINES_PLANT, + BlockTypes.WEEPING_VINES_PLANT, + BlockTypes.VINE, + BlockTypes.REDSTONE_WIRE + ); + BlockCategory[] categories = new BlockCategory[]{BlockCategories.FENCES, BlockCategories.FENCE_GATES, BlockCategories.WALLS, BlockCategories.CAVE_VINES}; + for (BlockCategory category : categories) { + if (category != null) { + REQUIRES_SECOND_PASS.add(category.getAll()); + } + } + + DEFAULT_MASK = REQUIRES_SECOND_PASS.copy(); + DEFAULT_MASK.add( + BlockTypes.CHORUS_PLANT, + BlockTypes.DRIPSTONE_BLOCK, + BlockTypes.POINTED_DRIPSTONE, + BlockTypes.BIG_DRIPLEAF, + BlockTypes.BIG_DRIPLEAF_STEM, + BlockTypes.CAMPFIRE, + BlockTypes.CHEST, + BlockTypes.TRAPPED_CHEST, + BlockTypes.CRAFTER, + BlockTypes.MUSHROOM_STEM, + BlockTypes.BROWN_MUSHROOM_BLOCK, + BlockTypes.RED_MUSHROOM_BLOCK + ); + categories = new BlockCategory[]{BlockCategories.STAIRS, BlockCategories.BAMBOO_BLOCKS, BlockCategories.TALL_FLOWERS}; + for (BlockCategory category : categories) { + if (category != null) { + DEFAULT_MASK.add(category.getAll()); + } + } + + ADJACENT_STAIR_MASK = new AdjacentAny2DMask(new BlockCategoryMask(nullExtent, BlockCategories.STAIRS), false); + SETUP = true; + } + + @Override + public IChunkSet processSet(IChunk iChunk, IChunkGet chunkGet, IChunkSet chunkSet) { + if (finished.get()) { + return chunkSet; + } + PlacementStateProcessor threadProcessor = threadProcessors.get(); + try { + threadProcessor.initProcess(iChunk, chunkGet, chunkSet); + return threadProcessor.process(); + } finally { + threadProcessor.uninit(); + } + } + + private void initProcess(IChunk iChunk, IChunkGet chunkGet, IChunkSet chunkSet) { + this.processChunkX = iChunk.getX() << 4; + this.processChunkZ = iChunk.getZ() << 4; + this.processChunkGet = chunkGet; + this.processChunkSet = chunkSet; + } + + private void uninit() { + this.processChunkGet = null; + this.processChunkSet = null; + } + + private IChunkSet process() { + Map setTiles = processChunkSet.tiles(); + for (int layer = processChunkGet.getMinSectionPosition(); layer <= processChunkGet.getMaxSectionPosition(); layer++) { + int layerY = layer << 4; + char[] set = processChunkSet.loadIfPresent(layer); + if (set == null) { + continue; + } + for (int y = 0, i = 0; y < 16; y++, i += 256) { + int blockY = layerY + y; + checkAndPerformUpdate(setTiles, set, i, blockY, true); + checkAndPerformUpdate(setTiles, set, i, blockY, false); + } + } + return processChunkSet; + } + + private void checkAndPerformUpdate( + Map setTiles, + char[] set, + int index, + int blockY, + boolean firstPass + ) { + for (int z = 0; z < 16; z++) { + int blockZ = processChunkZ + z; + for (int x = 0; x < 16; x++, index++) { + int blockX = processChunkX + x; + char ordinal = set[index]; + BlockState state = BlockTypesCache.states[ordinal]; + if (firstPass) { + if (!IN_FIRST_PASS.test(state)) { + continue; + } + } else if (IN_FIRST_PASS_WITHOUT_SECOND.test(state)) { + continue; + } + if (!mask.test(state)) { + continue; + } + boolean atEdge = x == 0 || x == 15 || z == 0 || z == 15; + if (!firstPass && atEdge && REQUIRES_SECOND_PASS.test(state)) { + postCompleteSecondPasses.put(new SecondPass( + blockX, + blockY, + blockZ, + setTiles.isEmpty() ? null : ((BlockVector3ChunkMap) setTiles).remove(x, blockY, z) + ), ordinal); + set[index] = BlockTypesCache.ReservedIDs.__RESERVED__; + continue; + } + if (state.getBlockType().equals(BlockTypes.CHEST) || state.getBlockType().equals(BlockTypes.TRAPPED_CHEST)) { + placedBlock.setComponents(blockX, blockY, blockZ); + } else { + placedBlock.setComponents(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + } + char newOrdinal = getBlockOrdinal(blockX, blockY, blockZ, state); + if (newOrdinal == ordinal) { + continue; + } + set[index] = newOrdinal; + } + } + } + + @Override + public ProcessorScope getScope() { + return ProcessorScope.CHANGING_BLOCKS; + } + + @Override + public void finish() { + flush(); + } + + @Override + public void flush() { + finished.set(true); + for (Map.Entry entry : postCompleteSecondPasses.entrySet()) { + BlockState state; + char ordinal = entry.getValue(); + SecondPass secondPass = entry.getKey(); + if (ordinal != 0) { + state = BlockTypesCache.states[ordinal]; + } else { + state = extent.getBlock(secondPass.x, secondPass.y, secondPass.z); + } + char newOrdinal = getBlockOrdinal(secondPass.x, secondPass.y, secondPass.z, state); + if (newOrdinal == state.getOrdinalChar() && ordinal == 0) { + continue; + } + if (secondPass.tile != null) { + extent.tile(secondPass.x, secondPass.y, secondPass.z, secondPass.tile); + } + extent.setBlock(secondPass.x, secondPass.y, secondPass.z, BlockTypesCache.states[newOrdinal]); + } + postCompleteSecondPasses.clear(); + } + + @Override + public abstract PlacementStateProcessor fork(); + + protected abstract char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ); + + public BlockState getBlockStateAt(int x, int y, int z) { + Character ord = postCompleteSecondPasses.get(new SecondPass(x, y, z, null)); + if (ord != null && ord != 0) { + return BlockTypesCache.states[ord]; + } + if (processChunkSet == null || (x & CHUNK_BLOCK_POS_MASK) != processChunkX || (z & CHUNK_BLOCK_POS_MASK) != processChunkZ) { + return extent.getBlock(x, y, z); + } + char[] set = processChunkSet.loadIfPresent(y >> 4); + if (set == null) { + return processChunkGet.getBlock(x & 15, y, z & 15); + } + char ordinal = set[(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + return processChunkGet.getBlock(x & 15, y, z & 15); + } + BlockState state = BlockTypesCache.states[ordinal]; + // "Hack" for chests as internal server methods will only accept "single" chests for joining + if (state.getBlockType().equals(BlockTypes.CHEST) || state.getBlockType().equals(BlockTypes.TRAPPED_CHEST)) { + String shape = state.getState(PropertyKey.TYPE).toString(); + if (shape.equals("right")) { + Direction facing = state.getState(PropertyKey.FACING); + Direction left = facing.getLeft(); + int testX = x + left.getBlockX(); + int testZ = z + left.getBlockZ(); + if (placedBlock.isAt(testX, y, testZ)) { + return state.with(PropertyKey.TYPE, "single"); + } + } else if(shape.equals("left")) { + Direction facing = state.getState(PropertyKey.FACING); + Direction right = facing.getRight(); + int testX = x + right.getBlockX(); + int testZ = z + right.getBlockZ(); + if (placedBlock.isAt(testX, y, testZ)) { + return state.with(PropertyKey.TYPE, "single"); + } + } + } + return state; + } + + public LinCompoundTag getTileAt(int x, int y, int z) { + SecondPass secondPass = new SecondPass(x, y, z, null); + Character ord = postCompleteSecondPasses.get(secondPass); + if (ord != null && ord != 0) { + // This should be rare enough... + for (SecondPass pass : postCompleteSecondPasses.keySet()) { + if (pass.hashCode() == secondPass.hashCode()) { + return pass.tile != null ? pass.tile.linTag() : BlockTypesCache.states[ord].getNbt(); + } + } + return BlockTypesCache.states[ord].getNbt(); + } + if (processChunkSet == null || (x & CHUNK_BLOCK_POS_MASK) != processChunkX || (z & CHUNK_BLOCK_POS_MASK) != processChunkZ) { + return extent.getFullBlock(x, y, z).getNbt(); + } + FaweCompoundTag tile = processChunkSet.tile(x & 15, y, z & 15); + if (tile != null) { + return tile.linTag(); + } + char[] set = processChunkSet.loadIfPresent(y >> 4); + if (set == null) { + return processChunkGet.getFullBlock(x & 15, y, z & 15).getNbt(); + } + char ordinal = set[(y & 15) << 8 | (z & 15) << 4 | (x & 15)]; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + return processChunkGet.getFullBlock(x & 15, y, z & 15).getNbt(); + } + return BlockTypesCache.states[ordinal].getNbt(); + } + + private char getBlockOrdinal(int blockX, int blockY, int blockZ, BlockState state) { + char override = getOverrideBlockOrdinal(blockX, blockY, blockZ, state); + if (override != BlockTypesCache.ReservedIDs.__RESERVED__) { + return override; + } + EnumSet dirs = Direction.getDirections(state); + Direction clickedFaceDirection = null; // This should be always be set by the below. + Set states = state.getStates().keySet().stream().map(Property::getName).collect(Collectors.toSet()); + if (dirs.isEmpty() || states.contains("NORTH") && states.contains("EAST")) { + clickPos.setComponents(blockX + 0.5d, blockY, blockZ + 0.5d); + clickedFaceDirection = Direction.UP; + clickedBlock.setComponents(blockX, blockY - 1, blockZ); + } else { + boolean hadNesw = false; + for (Direction dir : NESW) { + if (dirs.contains(dir)) { + clickedFaceDirection = dir.getLeft().getLeft(); + if (state.getBlockType() == BlockTypes.CHEST || state.getBlockType() == BlockTypes.TRAPPED_CHEST) { + Direction tmp = clickedFaceDirection; + clickedFaceDirection = dir; + dir = tmp; + } + clickPos.setComponents( + (double) blockX + 0.5 * (1 + dir.getBlockX()), + (double) blockY + 0.2, + (double) blockZ + 0.5 * (1 + dir.getBlockZ()) + ); + clickedBlock.setComponents(blockX, blockY, blockZ).add(dir.toBlockVector()); + hadNesw = true; + break; + } + } + if (hadNesw) { + if (dirs.contains(Direction.UP)) { + clickPos.mutY(blockY + 0.6); + } + } else if (dirs.contains(Direction.UP)) { + clickedFaceDirection = Direction.DOWN; + clickPos.setComponents(blockX + 0.5d, blockY + 1d, blockZ + 0.5d); + clickedBlock.setComponents(blockX, blockY + 1, blockZ); + } else if (dirs.contains(Direction.DOWN)) { + clickedFaceDirection = Direction.UP; + clickPos.setComponents(blockX + 0.5d, blockY - 1d, blockZ + 0.5d); + clickedBlock.setComponents(blockX, blockY - 1, blockZ); + } + } + return getStateAtFor(blockX, blockY, blockZ, state, clickPos, clickedFaceDirection, clickedBlock); + } + + protected char getOverrideBlockOrdinal(int blockX, int blockY, int blockZ, BlockState state) { + if (BlockCategories.TALL_FLOWERS.contains(state)) { + PropertyKey propertyKey = PropertyKey.HALF; + BlockState plantState = extent.getBlock(blockX, blockY - 1, blockZ).getBlockType().equals(state.getBlockType()) + ? state.with(propertyKey, "upper") + : state.with(propertyKey, "lower"); + return plantState.getOrdinalChar(); + } + return BlockTypesCache.ReservedIDs.__RESERVED__; + } + + @Override + public void applyBlock(FilterBlock block) { + if (finished.get()) { + return; + } + BlockState state = BlockTypesCache.states[block.getOrdinal()]; + if (!mask.test(state)) { + return; + } + if (REQUIRES_SECOND_PASS.test(block.getBlock()) && ADJACENT_STAIR_MASK.test(extent, block)) { + postCompleteSecondPasses.put(new SecondPass(block), (char) 0); + } + char ordinal = (char) block.getOrdinal(); + char newOrdinal = getBlockOrdinal(block.x(), block.y(), block.z(), block.getBlock()); + if (ordinal != newOrdinal) { + block.setBlock(BlockTypesCache.states[newOrdinal]); + } + } + + @Override + public boolean apply(Extent orDefault, BlockVector3 get, BlockVector3 set) throws WorldEditException { + if (orDefault == null) { + orDefault = extent; + } + BaseBlock block = orDefault.getFullBlock(get); + if (!mask.test(block)) { + return false; + } + if (REQUIRES_SECOND_PASS.test(block) && ADJACENT_STAIR_MASK.test(extent, set)) { + postCompleteSecondPasses.put(new SecondPass(set), (char) 0); + return false; + } + char newOrdinal = getBlockOrdinal(set.x(), set.y(), set.z(), block.toBlockState()); + if (block.getOrdinalChar() != newOrdinal) { + BlockState newState = BlockTypesCache.states[newOrdinal]; + orDefault.setBlock(set.x(), set.y(), set.z(), newState); + LinCompoundTag nbt = block.getNbt(); + if (nbt != null && newState.getBlockType() == block.getBlockType()) { + orDefault.tile(set.x(), set.y(), set.z(), FaweCompoundTag.of(nbt)); + } + return true; + } + return false; + } + + @Override + public BaseBlock applyBlock(BlockVector3 position) { + if (finished.get()) { + return null; + } + BaseBlock block = extent.getFullBlock(position); + if (!mask.test(block)) { + return null; + } + if (REQUIRES_SECOND_PASS.test(block) && ADJACENT_STAIR_MASK.test(extent, position)) { + postCompleteSecondPasses.put(new SecondPass(position), (char) 0); + return null; + } + char newOrdinal = getBlockOrdinal(position.x(), position.y(), position.z(), block.toBlockState()); + if (block.getOrdinalChar() != newOrdinal) { + BlockState state = BlockTypesCache.states[newOrdinal]; + LinCompoundTag nbt = block.getNbt(); + if (nbt != null && state.getBlockType() == block.getBlockType()) { + state.toBaseBlock(nbt); + } + return state.toBaseBlock(); + } + return null; + } + + protected record SecondPass(int x, int y, int z, FaweCompoundTag tile) { + + private SecondPass(BlockVector3 pos) { + this(pos.x(), pos.y(), pos.z(), null); + } + + @Override + public int hashCode() { + return (x ^ (z << 12)) ^ (y << 24); + } + + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/Adjacent2DMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/Adjacent2DMask.java new file mode 100644 index 000000000..22d5d7ff7 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/Adjacent2DMask.java @@ -0,0 +1,66 @@ +package com.fastasyncworldedit.core.function.mask; + +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.sk89q.worldedit.function.mask.AbstractMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.math.BlockVector3; + +/** + * Mask that tests adjacency only in 2D/the same y-level + * + * @since TODO + */ +public class Adjacent2DMask extends AbstractMask { + + private final int min; + private final int max; + private final Mask mask; + private final MutableBlockVector3 vector; + + /** + * Mask that tests adjacency only in 2D/the same y-level + * + * @param mask Mask required to be adjacent + * @param requiredMin Minimum number of positive adjacency matches required + * @param requiredMax Maximum number of positive adjacency matches required + * @since TODO + */ + public Adjacent2DMask(Mask mask, int requiredMin, int requiredMax) { + this.mask = mask; + this.min = requiredMin; + this.max = requiredMax; + this.vector = new MutableBlockVector3(); + } + + @Override + public boolean test(BlockVector3 bv) { + vector.setComponents(bv); + double x = bv.x(); + double z = bv.z(); + vector.mutX(x + 1); + int count = 0; + if (mask.test(vector) && ++count == min && max >= 4) { + return true; + } + vector.mutX(x - 1); + if (mask.test(vector) && ++count == min && max >= 4) { + return true; + } + vector.mutX(x); + vector.mutZ(z + 1); + if (mask.test(vector) && ++count == min && max >= 4) { + return true; + } + vector.mutZ(z - 1); + if (mask.test(vector) && ++count == min && max >= 4) { + return true; + } + return count >= min && count <= max; + } + + @Override + public Mask copy() { + return new Adjacent2DMask(mask.copy(), min, max); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAny2DMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAny2DMask.java new file mode 100644 index 000000000..9bc8aa67e --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAny2DMask.java @@ -0,0 +1,90 @@ +package com.fastasyncworldedit.core.function.mask; + +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.AbstractExtentMask; +import com.sk89q.worldedit.function.mask.AbstractMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.math.BlockVector3; + +/** + * Optimized version of {@link Adjacent2DMask} for testing for any single adjacency + * + * @since TODO + */ +public class AdjacentAny2DMask extends AbstractMask { + + private final Mask mask; + private final MutableBlockVector3 mutable; + + /** + * Optimized version of {@link Adjacent2DMask} for testing for any single adjacency. Caches results of the adjacent mask + * + * @param mask Mask required to be adjacent + * @since TODO + */ + public AdjacentAny2DMask(Mask mask) { + this(mask, true); + } + + /** + * Optimized version of {@link Adjacent2DMask} for testing for any single adjacency + * + * @param mask Mask required to be adjacent + * @param cache If the result of the adjacency mask should be cached + * @since TODO + */ + public AdjacentAny2DMask(Mask mask, boolean cache) { + this.mask = cache ? CachedMask.cache(mask) : mask; + mutable = new MutableBlockVector3(); + } + + @Override + public boolean test(BlockVector3 v) { + int x = v.x(); + int y = v.y(); + int z = v.z(); + if (mask.test(mutable.setComponents(x + 1, y, z))) { + return true; + } + if (mask.test(mutable.setComponents(x - 1, y, z))) { + return true; + } + if (mask.test(mutable.setComponents(x, y, z + 1))) { + return true; + } + return mask.test(mutable.setComponents(x, y, z - 1)); + } + + /** + * Test this mask for the given extent + * + * @param extent extent to test in + * @param position position to test at + * @since TODO + */ + public boolean test(Extent extent, BlockVector3 position) { + if (!(mask instanceof AbstractExtentMask extentMask)) { + throw new UnsupportedOperationException("Adjacency mask must inherit from AbstractExtentMask"); + } + int x = position.x(); + int y = position.y(); + int z = position.z(); + if (extentMask.test(extent, mutable.setComponents(x + 1, y, z))) { + return true; + } + if (extentMask.test(extent, mutable.setComponents(x - 1, y, z))) { + return true; + } + if (extentMask.test(extent, mutable.setComponents(x, y, z + 1))) { + return true; + } + return extentMask.test(extent, mutable.setComponents(x, y, z - 1)); + } + + @Override + public Mask copy() { + return new AdjacentAny2DMask(mask.copy(), false); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAnyMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAnyMask.java index 956daf67d..cc57be2bb 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAnyMask.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/mask/AdjacentAnyMask.java @@ -23,7 +23,7 @@ public class AdjacentAnyMask extends AbstractMask implements ResettableMask { } AdjacentAnyMask(CachedMask mask, int minY, int maxY) { - this.mask = CachedMask.cache(mask); + this.mask = mask; mutable = new MutableBlockVector3(); this.minY = minY; this.maxY = maxY; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java index f8ff1956f..8286a1ed5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java @@ -461,7 +461,6 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet { } catch (EOFException ignored) { } catch (Exception e) { e.printStackTrace(); - e.printStackTrace(); } try { is.close(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector3.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector3.java index e4cfda85a..01aa4aebb 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector3.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/MutableBlockVector3.java @@ -62,7 +62,7 @@ public class MutableBlockVector3 extends BlockVector3 { } @Override - public BlockVector3 getMinimum(BlockVector3 v2) { + public MutableBlockVector3 getMinimum(BlockVector3 v2) { this.x = Math.min(v2.x(), x); this.y = Math.min(v2.y(), y); this.z = Math.min(v2.z(), z); @@ -70,7 +70,7 @@ public class MutableBlockVector3 extends BlockVector3 { } @Override - public BlockVector3 getMaximum(BlockVector3 v2) { + public MutableBlockVector3 getMaximum(BlockVector3 v2) { this.x = Math.max(v2.x(), x); this.y = Math.max(v2.y(), y); this.z = Math.max(v2.z(), z); @@ -113,4 +113,140 @@ public class MutableBlockVector3 extends BlockVector3 { return this; } + @Override + public final MutableBlockVector3 withX(int x) { + this.x = x; + return this; + } + + @Override + public final MutableBlockVector3 withY(int y) { + this.y = y; + return this; + } + + @Override + public final MutableBlockVector3 withZ(int z) { + this.z = z; + return this; + } + + @Override + public MutableBlockVector3 add(BlockVector3 other) { + return add(other.x(), other.y(), other.z()); + } + + @Override + public MutableBlockVector3 add(int x, int y, int z) { + this.x += x; + this.y += y; + this.z += z; + return this; + } + + @Override + public MutableBlockVector3 add(BlockVector3... others) { + for (BlockVector3 other : others) { + this.x += other.x(); + this.y += other.y(); + this.z += other.z(); + } + return this; + } + + @Override + public MutableBlockVector3 subtract(BlockVector3 other) { + return subtract(other.x(), other.y(), other.z()); + } + + @Override + public MutableBlockVector3 subtract(int x, int y, int z) { + this.x -= x; + this.y -= y; + this.z -= z; + return this; + } + + @Override + public MutableBlockVector3 subtract(BlockVector3... others) { + for (BlockVector3 other : others) { + this.x -= other.x(); + this.y -= other.y(); + this.z -= other.z(); + } + return this; + } + + @Override + public MutableBlockVector3 multiply(BlockVector3 other) { + return multiply(other.x(), other.y(), other.z()); + } + + @Override + public MutableBlockVector3 multiply(int x, int y, int z) { + this.x *= x; + this.y *= y; + this.z *= z; + return this; + } + + @Override + public MutableBlockVector3 multiply(BlockVector3... others) { + for (BlockVector3 other : others) { + this.x *= other.x(); + this.y *= other.y(); + this.z *= other.z(); + } + return this; + } + + @Override + public MutableBlockVector3 multiply(int n) { + return multiply(n, n, n); + } + + @Override + public MutableBlockVector3 divide(BlockVector3 other) { + return divide(other.x(), other.y(), other.z()); + } + + @Override + public MutableBlockVector3 divide(int x, int y, int z) { + this.x /= x; + this.y /= y; + this.z /= z; + return this; + } + + @Override + public MutableBlockVector3 divide(int n) { + return divide(n, n, n); + } + + @Override + public MutableBlockVector3 shr(int x, int y, int z) { + this.x = this.x >> x; + this.y = this.y >> y; + this.z = this.z >> z; + return this; + } + + @Override + public MutableBlockVector3 shr(int n) { + return shr(n, n, n); + } + + @Override + public MutableBlockVector3 shl(int x, int y, int z) { + this.x = this.x >> x; + this.y = this.y >> y; + this.z = this.z >> z; + return this; + } + + @Override + public MutableBlockVector3 shl(int n) { + return shl(n, n, n); + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java index f308d4806..ab277ded4 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/Filter.java @@ -62,4 +62,13 @@ public interface Filter { } + /** + * Signals to the filter the edit has concluded + * + * @since TODO + */ + default void finish() { + + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java index fc1482d6e..da6ea03e5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkSet.java @@ -7,6 +7,7 @@ import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.OutputExtent; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockStateHolder; @@ -139,4 +140,19 @@ public interface IChunkSet extends IBlocks, OutputExtent { return this; } + /** + * Set the side effects to be used when settings these blocks + * + * @since TODO + */ + void setSideEffectSet(@Nonnull SideEffectSet sideEffectSet); + + /** + * Get the side effects to be used when settings these blocks + * + * @since TODO + */ + @Nonnull + SideEffectSet getSideEffectSet(); + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IDelegateFilter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IDelegateFilter.java index 1e172dd19..8d309ed58 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IDelegateFilter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IDelegateFilter.java @@ -38,6 +38,11 @@ public interface IDelegateFilter extends Filter { return this; } + @Override + default void finish() { + getParent().finish(); + } + Filter newInstance(Filter other); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java index d09b7f04b..e1714f039 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java @@ -10,6 +10,7 @@ import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.SideEffectSet; import javax.annotation.Nullable; import java.io.Flushable; @@ -81,6 +82,20 @@ public interface IQueueExtent extends Flushable, Trimable, ICh boolean isFastMode(); + /** + * Set the side effects to be used with this extent + * + * @since TODO + */ + void setSideEffectSet(SideEffectSet sideEffectSet); + + /** + * Get the side effects to be used with this extent + * + * @since TODO + */ + SideEffectSet getSideEffectSet(); + /** * Create a new root IChunk object. Full chunks will be reused, so a more optimized chunk can be * returned in that case. diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java index 56a238166..591c1bcd0 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java @@ -20,11 +20,14 @@ import com.fastasyncworldedit.core.queue.Filter; import com.fastasyncworldedit.core.queue.IQueueChunk; import com.fastasyncworldedit.core.queue.IQueueExtent; import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.mask.BlockMask; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.RunContext; import com.sk89q.worldedit.function.pattern.BlockPattern; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.util.LogManagerCompat; @@ -32,6 +35,7 @@ import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Countable; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -39,6 +43,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import org.apache.logging.log4j.Logger; +import javax.annotation.Nullable; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -58,11 +63,12 @@ public class ParallelQueueExtent extends PassthroughExtent { // not very important) private final boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length]; private final boolean fastmode; + private final SideEffectSet sideEffectSet; private int changes; private int lastException = Integer.MIN_VALUE; private int exceptionCount = 0; - public ParallelQueueExtent(QueueHandler handler, World world, boolean fastmode) { + public ParallelQueueExtent(QueueHandler handler, World world, boolean fastmode, @Nullable SideEffectSet sideEffectSet) { super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder())); this.world = world; this.handler = handler; @@ -75,6 +81,7 @@ public class ParallelQueueExtent extends PassthroughExtent { ((MultiBatchProcessor) this.postProcessor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed); } this.fastmode = fastmode; + this.sideEffectSet = sideEffectSet == null ? SideEffectSet.defaults() : sideEffectSet; } /** @@ -121,7 +128,12 @@ public class ParallelQueueExtent extends PassthroughExtent { @SuppressWarnings("rawtypes") private IQueueExtent getNewQueue() { - return handler.getQueue(world, this.processor, this.postProcessor); + SingleThreadQueueExtent queue = (SingleThreadQueueExtent) handler.getQueue(world, this.processor, this.postProcessor); + queue.setFastMode(fastmode); + queue.setSideEffectSet(sideEffectSet); + queue.setFaweExceptionArray(faweExceptionReasonsUsed); + enter(queue); + return queue; } @Override @@ -140,6 +152,8 @@ public class ParallelQueueExtent extends PassthroughExtent { BlockVector2 pos = chunksIter.next(); block = getExtent().apply(block, filter, region, pos.x(), pos.z(), full); } + getExtent().flush(); + filter.finish(); } else { final ForkJoinTask[] tasks = IntStream.range(0, size).mapToObj(i -> handler.submit(() -> { try { @@ -147,13 +161,9 @@ public class ParallelQueueExtent extends PassthroughExtent { final Region newRegion = region.clone(); // Create a chunk that we will reuse/reset for each operation final SingleThreadQueueExtent queue = (SingleThreadQueueExtent) getNewQueue(); - queue.setFastMode(fastmode); - queue.setFaweExceptionArray(faweExceptionReasonsUsed); - enter(queue); synchronized (queue) { try { ChunkFilterBlock block = null; - while (true) { // Get the next chunk posWeakChunk final int chunkX; @@ -169,6 +179,7 @@ public class ParallelQueueExtent extends PassthroughExtent { block = queue.apply(block, newFilter, newRegion, chunkX, chunkZ, full); } queue.flush(); + filter.finish(); } catch (Throwable t) { if (t instanceof FaweException) { Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t, LOGGER); @@ -205,6 +216,24 @@ public class ParallelQueueExtent extends PassthroughExtent { return filter; } + @Override + protected Operation commitBefore() { + return new Operation() { + @Override + public Operation resume(final RunContext run) throws WorldEditException { + extent.commit(); + processor.flush(); + ((IQueueExtent>) extent).flush(); + return null; + } + + @Override + public void cancel() { + + } + }; + } + @Override public int countBlocks(Region region, Mask searchMask) { return diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index b0239a5a3..389f0d66e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.World; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import org.apache.logging.log4j.Logger; @@ -68,6 +69,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length]; private int lastException = Integer.MIN_VALUE; private int exceptionCount = 0; + private SideEffectSet sideEffectSet = SideEffectSet.defaults(); public SingleThreadQueueExtent() { } @@ -110,6 +112,16 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen this.fastmode = fastmode; } + @Override + public void setSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public SideEffectSet getSideEffectSet() { + return sideEffectSet; + } + @Override public int getMinY() { return minY; @@ -120,6 +132,10 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen return maxY; } + public World getWorld() { + return world; + } + /** * Sets the cached boolean array of length {@code FaweException.Type.values().length} that determines if a thrown * {@link FaweException} of type {@link FaweException.Type} should be output to console, rethrown to attempt to be visible @@ -278,10 +294,16 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen private ChunkHolder poolOrCreate(int chunkX, int chunkZ) { ChunkHolder next = create(false); next.init(this, chunkX, chunkZ); - next.setFastMode(isFastMode()); return next; } + @Override + public IQueueChunk wrap(IQueueChunk chunk) { + chunk.setFastMode(isFastMode()); + chunk.setSideEffectSet(getSideEffectSet()); + return chunk; + } + @Override public final IQueueChunk getOrCreateChunk(int x, int z) { getChunkLock.lock(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java index 76f2191ed..0ace5150a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/BitSetBlocks.java @@ -6,6 +6,7 @@ import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.util.collection.MemBlockSet; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; @@ -212,6 +213,16 @@ public class BitSetBlocks implements IChunkSet { return false; } + @Override + public void setSideEffectSet(SideEffectSet sideEffectSet) { + + } + + @Override + public SideEffectSet getSideEffectSet() { + return SideEffectSet.none(); + } + @Override public int getSectionCount() { return layers; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java index 8d7c059ed..c08271d97 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharSetBlocks.java @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.queue.Pool; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -44,6 +45,7 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { public EnumMap heightMaps; private boolean fastMode = false; private int bitMask = -1; + private SideEffectSet sideEffectSet = SideEffectSet.defaults(); private CharSetBlocks() { // Expand as we go @@ -374,10 +376,21 @@ public class CharSetBlocks extends CharBlocks implements IChunkSet { heightMaps != null ? new EnumMap<>(heightMaps) : null, defaultOrdinal(), fastMode, - bitMask + bitMask, + sideEffectSet ); } + @Override + public void setSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public SideEffectSet getSideEffectSet() { + return sideEffectSet; + } + static char[][] createLightCopy(char[][] lightArr, int sectionCount) { if (lightArr == null) { return null; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java index c21e932a9..c36dcbeef 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/ThreadUnsafeCharBlocks.java @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.queue.IBlocks; import com.fastasyncworldedit.core.queue.IChunkSet; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; @@ -51,6 +52,7 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { private Map heightMaps; private boolean fastMode; private int bitMask; + private SideEffectSet sideEffectSet; /** * New instance given the data stored in a {@link CharSetBlocks} instance. @@ -71,7 +73,8 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { Map heightMaps, char defaultOrdinal, boolean fastMode, - int bitMask + int bitMask, + SideEffectSet sideEffectSet ) { this.blocks = blocks; this.minSectionPosition = minSectionPosition; @@ -87,6 +90,7 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { this.defaultOrdinal = defaultOrdinal; this.fastMode = fastMode; this.bitMask = bitMask; + this.sideEffectSet = sideEffectSet; } @Override @@ -480,10 +484,21 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks { heightMaps != null ? new HashMap<>(heightMaps) : null, defaultOrdinal, fastMode, - bitMask + bitMask, + sideEffectSet ); } + @Override + public void setSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public SideEffectSet getSideEffectSet() { + return sideEffectSet; + } + @Override public boolean trim(boolean aggressive) { return false; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java index 5e951cd2b..c3a36be2d 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java @@ -15,6 +15,7 @@ import com.fastasyncworldedit.core.util.MemUtil; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -51,6 +52,7 @@ public class ChunkHolder> implements IQueueChunk { private boolean isInit = false; // Lighting handles queue differently. It relies on the chunk cache and not doing init. private boolean createCopy = false; private long initTime = -1L; + private SideEffectSet sideEffectSet; private ChunkHolder() { this.delegate = NULL; @@ -150,6 +152,16 @@ public class ChunkHolder> implements IQueueChunk { return chunkSet != null && chunkSet.hasBiomes(layer); } + @Override + public void setSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public SideEffectSet getSideEffectSet() { + return sideEffectSet; + } + public boolean isInit() { return isInit; } @@ -874,7 +886,6 @@ public class ChunkHolder> implements IQueueChunk { public synchronized void filterBlocks(Filter filter, ChunkFilterBlock block, @Nullable Region region, boolean full) { final IChunkGet get = getOrCreateGet(); final IChunkSet set = getOrCreateSet(); - set.setFastMode(fastmode); try { block.filter(this, get, set, filter, region, full); } finally { @@ -948,13 +959,21 @@ public class ChunkHolder> implements IQueueChunk { return chunkSet; } + public final IChunkSet getChunkSet() { + return chunkSet; + } + /** * Create a wrapped set object * - The purpose of wrapping is to allow different extents to intercept / alter behavior * - e.g., caching, optimizations, filtering */ private IChunkSet newWrappedSet() { - return extent.getCachedSet(chunkX, chunkZ); + IChunkSet set = extent.getCachedSet(chunkX, chunkZ); + set.setFastMode(fastmode); + set.setSideEffectSet(sideEffectSet); + set.setBitMask(bitMask); + return set; } /** @@ -985,7 +1004,6 @@ public class ChunkHolder> implements IQueueChunk { @Override public synchronized T call() { if (chunkSet != null && !chunkSet.isEmpty()) { - chunkSet.setBitMask(bitMask); IChunkSet copy = chunkSet.createCopy(); return this.call(copy, () -> { // Do nothing diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java index 14107e83a..770a878a9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java @@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.queue.IQueueChunk; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -86,6 +87,16 @@ public final class NullChunk implements IQueueChunk { return false; } + @Override + public void setSideEffectSet(SideEffectSet sideEffectSet) { + + } + + @Override + public SideEffectSet getSideEffectSet() { + return SideEffectSet.none(); + } + @Nonnull public int[] getHeightMap(@Nullable HeightMapType type) { return new int[256]; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java index 6ae147765..e1a106979 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java @@ -1,7 +1,11 @@ package com.fastasyncworldedit.core.util; +import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.world.World; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -21,6 +25,26 @@ public class ExtentTraverser { this.parent = parent; } + /** + * Get the world backing the given extent, if present, else null. + * + * @since TODO + */ + @Nullable + public static World getWorldFromExtent(Extent extent) { + if (extent.isWorld()) { + return (World) extent; + } else if (extent instanceof EditSession session) { + return session.getWorld(); + } else if (extent instanceof SingleThreadQueueExtent stqe) { + return stqe.getWorld(); + } else if (extent instanceof ParallelQueueExtent pqe) { + return ((SingleThreadQueueExtent) pqe.getExtent()).getWorld(); + } else { + return new ExtentTraverser<>(extent).findAndGet(World.class); + } + } + public boolean exists() { return root != null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java index 26650bf9c..418e54cff 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java @@ -64,7 +64,10 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionIntersection; import com.sk89q.worldedit.util.Identifiable; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.util.eventbus.EventBus; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; @@ -104,6 +107,7 @@ public final class EditSessionBuilder { private Extent extent; private boolean compiled; private boolean wrapped; + private SideEffectSet sideEffectSet = null; private @Nullable World world; @@ -415,6 +419,16 @@ public final class EditSessionBuilder { return setDirty(); } + /** + * Set the side effects to be used with this edit + * + * @since TODO + */ + public EditSessionBuilder setSideEffectSet(@Nullable SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + return setDirty(); + } + /** * Compile the builder to the settings given. Prepares history, limits, lighting, etc. */ @@ -445,6 +459,10 @@ public final class EditSessionBuilder { fastMode = actor.getSession().hasFastMode(); } } + if (sideEffectSet == null) { + // Keep heightmaps to maintain behaviour + sideEffectSet = fastMode ? SideEffectSet.none().with(SideEffect.HEIGHTMAPS) : SideEffectSet.defaults(); + } if (checkMemory == null) { checkMemory = actor != null && !this.fastMode; } @@ -470,12 +488,18 @@ public final class EditSessionBuilder { if (unwrapped instanceof IQueueExtent) { extent = queue = (IQueueExtent) unwrapped; } else if (Settings.settings().QUEUE.PARALLEL_THREADS > 1 && !Fawe.isMainThread()) { - ParallelQueueExtent parallel = new ParallelQueueExtent(Fawe.instance().getQueueHandler(), world, fastMode); + ParallelQueueExtent parallel = new ParallelQueueExtent( + Fawe.instance().getQueueHandler(), + world, + fastMode, + sideEffectSet + ); queue = parallel.getExtent(); extent = parallel; } else { extent = queue = Fawe.instance().getQueueHandler().getQueue(world); } + queue.setSideEffectSet(sideEffectSet); } else { wnaMode = true; extent = world; @@ -491,7 +515,7 @@ public final class EditSessionBuilder { } extent = this.bypassAll = wrapExtent(extent, eventBus, event, EditSession.Stage.BEFORE_CHANGE); this.bypassHistory = this.extent = wrapExtent(bypassAll, eventBus, event, EditSession.Stage.BEFORE_REORDER); - if (!this.fastMode || changeSet != null) { + if (!this.fastMode || this.sideEffectSet.shouldApply(SideEffect.HISTORY) || changeSet != null) { if (changeSet == null) { if (Settings.settings().HISTORY.USE_DISK) { UUID uuid = actor == null ? Identifiable.CONSOLE : actor.getUniqueId(); @@ -546,13 +570,28 @@ public final class EditSessionBuilder { } // There's no need to do the below (and it'll also just be a pain to implement) if we're not placing chunks if (placeChunks) { - if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) { - relighter = WorldEdit.getInstance().getPlatformManager() + if (this.sideEffectSet.shouldApply(SideEffect.LIGHTING) || (relightMode != null && relightMode != RelightMode.NONE)) { + relighter = WorldEdit + .getInstance() + .getPlatformManager() .queryCapability(Capability.WORLD_EDITING) - .getRelighterFactory().createRelighter(relightMode, world, queue); + .getRelighterFactory() + .createRelighter(relightMode, world, queue); queue.addProcessor(new RelightProcessor(relighter)); } - queue.addProcessor(new HeightmapProcessor(world.getMinY(), world.getMaxY())); + if (this.sideEffectSet.shouldApply(SideEffect.HEIGHTMAPS)) { + queue.addProcessor(new HeightmapProcessor(world.getMinY(), world.getMaxY())); + } + if (this.sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { + Region region = allowedRegions == null || allowedRegions.length == 0 + ? null + : allowedRegions.length == 1 ? allowedRegions[0] : new RegionIntersection(allowedRegions); + queue.addProcessor(WorldEdit + .getInstance() + .getPlatformManager() + .queryCapability(Capability.WORLD_EDITING) + .getPlatformPlacementProcessor(extent, null, region)); + } if (!Settings.settings().EXPERIMENTAL.KEEP_ENTITIES_IN_BLOCKS) { queue.addProcessor(new EntityInBlockRemovingProcessor()); @@ -714,6 +753,15 @@ public final class EditSessionBuilder { return changeSet; } + /** + * Get the SideEffectSet that will be used + * + * @since TODO + */ + public SideEffectSet getSideEffectSet() { + return sideEffectSet; + } + /** * Get the ultimate resultant extent */ diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 006de1b0b..8ad66684c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -154,6 +154,7 @@ public class LocalSession implements TextureHolder { private transient TextureUtil texture; private transient ResettableExtent transform = null; private transient World currentWorld; + private transient boolean fastMode = false; //FAWE end private transient ClipboardHolder clipboard; private transient final Object clipboardLock = new Object(); @@ -1725,11 +1726,12 @@ public class LocalSession implements TextureHolder { * @return an edit session */ public EditSession createEditSession(Actor actor) { - //FAWE start + //FAWE start - save command used return createEditSession(actor, null); } public EditSession createEditSession(Actor actor, String command) { + //FAWE end checkNotNull(actor); World world = null; @@ -1740,17 +1742,18 @@ public class LocalSession implements TextureHolder { } // Create an edit session - EditSession editSession; EditSessionBuilder builder = WorldEdit.getInstance().newEditSessionBuilder().world(world); if (actor.isPlayer() && actor instanceof Player) { BlockBag blockBag = getBlockBag((Player) actor); builder.actor(actor); builder.blockBag(blockBag); } + //FAWE start builder.command(command); - builder.fastMode(!this.sideEffectSet.doesApplyAny()); + builder.fastMode(this.fastMode); + builder.setSideEffectSet(this.sideEffectSet); - editSession = builder.build(); + EditSession editSession = builder.build(); if (mask != null) { editSession.setMask(mask); @@ -1762,7 +1765,7 @@ public class LocalSession implements TextureHolder { editSession.addTransform(transform); } editSession.setTickingWatchdog(tickingWatchdog); - + //FAWE end return editSession; } //FAWE end @@ -1801,7 +1804,9 @@ public class LocalSession implements TextureHolder { */ @Deprecated public boolean hasFastMode() { - return !this.sideEffectSet.doesApplyAny(); + //FAWE start - use fastmode boolean not side effects + return this.fastMode; + //FAWE end } /** @@ -1811,7 +1816,9 @@ public class LocalSession implements TextureHolder { */ @Deprecated public void setFastMode(boolean fastMode) { - this.sideEffectSet = fastMode ? SideEffectSet.none() : SideEffectSet.defaults(); + //FAWE start - use fastmode boolean not side effects + this.fastMode = fastMode; + //FAWE end } /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 2847542a5..c44bc26fd 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -29,6 +29,7 @@ import com.fastasyncworldedit.core.util.MaskTraverser; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; @@ -38,6 +39,7 @@ import com.sk89q.worldedit.command.util.annotation.Preload; import com.sk89q.worldedit.command.util.annotation.SynchronousSettingExpected; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.function.GroundFunction; import com.sk89q.worldedit.function.generator.FloraGenerator; import com.sk89q.worldedit.function.mask.ExistingBlockMask; @@ -915,4 +917,33 @@ public class RegionCommands { return affected; } + //FAWE start - block "fixing" + @Command( + name = "/fixblocks", + aliases = {"/updateblocks", "/fixconnect"}, + desc = "\"Fixes\" all blocks in the region to the correct shape/connections based on surrounding blocks" + ) + @CommandPermissions("worldedit.region.fixblocks") + @Logging(REGION) + @Confirm(Confirm.Processor.REGION) + @Preload(Preload.PreloadCheck.PRELOAD) + public int fixblocks( + Actor actor, EditSession editSession, @Selection Region region + ) { + int affected = editSession.setBlocks( + region, + WorldEdit + .getInstance() + .getPlatformManager() + .queryCapability(Capability.WORLD_EDITING) + .getPlatformPlacementProcessor(editSession, null, region) + ); + if (affected != 0) { + actor.print(Caption.of("worldedit.set.done", TextComponent.of(affected))); + + } + return affected; + } + //FAWE end + } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java index 472dce93c..346098de5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java @@ -19,13 +19,16 @@ package com.sk89q.worldedit.extension.platform; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.sk89q.worldedit.LocalConfiguration; -import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.internal.util.NonAbstractForCompatibility; +import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.Keyed; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.io.ResourceLoader; @@ -276,5 +279,15 @@ public interface Platform extends Keyed { default IBatchProcessor getPlatformPostProcessor(boolean fastMode) { return null; } + + /** + * Returns an {@link PlacementStateProcessor} instance for processing placed blocks to "fix" them. Optional region to + * prevent any changes outside of, as sometimes block neighbours will also be updated otherwise. + * + * @since TODO + */ + default PlacementStateProcessor getPlatformPlacementProcessor(Extent extent, BlockTypeMask mask, @Nullable Region region) { + return null; + } //FAWE end } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockCategoryMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockCategoryMask.java index a1b869efb..bb89b0a08 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockCategoryMask.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockCategoryMask.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.function.mask; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; import javax.annotation.Nullable; @@ -54,6 +55,15 @@ public class BlockCategoryMask extends AbstractExtentMask { public boolean test(Extent extent, BlockVector3 vector) { return category.contains(extent.getBlock(vector)); } + + /** + * Test a specific block against this category mask + * + * @since TODO + */ + public > boolean test(B blockStateHolder) { + return category.contains(blockStateHolder); + } //FAWE end @Nullable diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockTypeMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockTypeMask.java index c3567b6d5..55f7519d0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockTypeMask.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockTypeMask.java @@ -21,6 +21,7 @@ package com.sk89q.worldedit.function.mask; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; @@ -140,6 +141,15 @@ public class BlockTypeMask extends AbstractExtentMask { public boolean test(BlockType block) { return types[block.getInternalId()]; } + + /** + * Test a block state against this block type mask + * + * @since TODO + */ + public > boolean test(B blockStateHolder) { + return types[blockStateHolder.getBlockType().getInternalId()]; + } //FAWE end @Nullable @@ -149,7 +159,7 @@ public class BlockTypeMask extends AbstractExtentMask { } @Override - public Mask copy() { + public BlockTypeMask copy() { return new BlockTypeMask(getExtent(), types.clone(), hasAir); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java index 017ec7ca0..1917e68b2 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java @@ -127,6 +127,10 @@ public abstract class BlockVector3 { } //FAWE start + public boolean isAt(int x, int y, int z) { + return x() == x && y() == y && z() == z; + } + public MutableBlockVector3 setComponents(double x, double y, double z) { return new MutableBlockVector3((int) x, (int) y, (int) z); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java index c10312503..3d21ef587 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/Direction.java @@ -21,9 +21,13 @@ package com.sk89q.worldedit.util; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockState; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -36,32 +40,32 @@ import java.util.OptionalInt; public enum Direction { //FAWE start - left, right - NORTH(Vector3.at(0, 0, -1), Flag.CARDINAL, 3, 1), - EAST(Vector3.at(1, 0, 0), Flag.CARDINAL, 0, 2), - SOUTH(Vector3.at(0, 0, 1), Flag.CARDINAL, 1, 3), - WEST(Vector3.at(-1, 0, 0), Flag.CARDINAL, 2, 0), + NORTH(Vector3.at(0, 0, -1), Flag.CARDINAL, 3, 1), // 0 + EAST(Vector3.at(1, 0, 0), Flag.CARDINAL, 0, 2), // 1 + SOUTH(Vector3.at(0, 0, 1), Flag.CARDINAL, 1, 3), // 2 + WEST(Vector3.at(-1, 0, 0), Flag.CARDINAL, 2, 0), // 3 - UP(Vector3.at(0, 1, 0), Flag.UPRIGHT, -1, -1), - DOWN(Vector3.at(0, -1, 0), Flag.UPRIGHT, -1, -1), + UP(Vector3.at(0, 1, 0), Flag.UPRIGHT, -1, -1), // 4 + DOWN(Vector3.at(0, -1, 0), Flag.UPRIGHT, -1, -1), // 5 - NORTHEAST(Vector3.at(1, 0, -1), Flag.ORDINAL, 7, 8), - NORTHWEST(Vector3.at(-1, 0, -1), Flag.ORDINAL, 9, 6), - SOUTHEAST(Vector3.at(1, 0, 1), Flag.ORDINAL, 6, 9), - SOUTHWEST(Vector3.at(-1, 0, 1), Flag.ORDINAL, 8, 7), + NORTHEAST(Vector3.at(1, 0, -1), Flag.ORDINAL, 7, 8), // 6 + NORTHWEST(Vector3.at(-1, 0, -1), Flag.ORDINAL, 9, 6), // 7 + SOUTHEAST(Vector3.at(1, 0, 1), Flag.ORDINAL, 6, 9), // 8 + SOUTHWEST(Vector3.at(-1, 0, 1), Flag.ORDINAL, 8, 7), // 9 - WEST_NORTHWEST(Vector3.at(-Math.cos(Math.PI / 8), 0, -Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 9, 6), - WEST_SOUTHWEST(Vector3.at(-Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 8, 7), - NORTH_NORTHWEST(Vector3.at(-Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 9, 6), - NORTH_NORTHEAST(Vector3.at(Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 7, 8), - EAST_NORTHEAST(Vector3.at(Math.cos(Math.PI / 8), 0, -Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 7, 8), - EAST_SOUTHEAST(Vector3.at(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 6, 9), - SOUTH_SOUTHEAST(Vector3.at(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 6, 9), - SOUTH_SOUTHWEST(Vector3.at(-Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 8, 7), + WEST_NORTHWEST(Vector3.at(-Math.cos(Math.PI / 8), 0, -Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 18, 14), // 11 + WEST_SOUTHWEST(Vector3.at(-Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 17, 13), // 12 + NORTH_NORTHWEST(Vector3.at(-Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 12, 15), // 13 + NORTH_NORTHEAST(Vector3.at(Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 11, 16), // 14 + EAST_NORTHEAST(Vector3.at(Math.cos(Math.PI / 8), 0, -Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 13, 17), // 15 + EAST_SOUTHEAST(Vector3.at(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 14, 18), // 16 + SOUTH_SOUTHEAST(Vector3.at(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 15, 12), // 17 + SOUTH_SOUTHWEST(Vector3.at(-Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL, 16, 11), // 18 - ASCENDING_NORTH(Vector3.at(0, 1, -1), Flag.ASCENDING_CARDINAL, 3 + 18, 1 + 18), - ASCENDING_EAST(Vector3.at(1, 1, 0), Flag.ASCENDING_CARDINAL, 0 + 18, 2 + 18), - ASCENDING_SOUTH(Vector3.at(0, 1, 1), Flag.ASCENDING_CARDINAL, 1 + 18, 3 + 18), - ASCENDING_WEST(Vector3.at(-1, 1, 0), Flag.ASCENDING_CARDINAL, 2 + 18, 0 + 18), + ASCENDING_NORTH(Vector3.at(0, 1, -1), Flag.ASCENDING_CARDINAL, 3 + 18, 1 + 18), // 19 + ASCENDING_EAST(Vector3.at(1, 1, 0), Flag.ASCENDING_CARDINAL, 0 + 18, 2 + 18), // 20 + ASCENDING_SOUTH(Vector3.at(0, 1, 1), Flag.ASCENDING_CARDINAL, 1 + 18, 3 + 18), // 21 + ASCENDING_WEST(Vector3.at(-1, 1, 0), Flag.ASCENDING_CARDINAL, 2 + 18, 0 + 18), // 22 ; //FAWE end @@ -95,12 +99,18 @@ public enum Direction { return map.get(sequence); } + /** + * Get the direction 90 degrees left (anti-clockwise) of this direction if possible, else return this direction + */ public Direction getLeft() { - return left != -1 ? values()[left] : null; + return left != -1 ? values()[left] : this; } + /** + * Get the direction 90 degrees right (clockwise) of this direction if possible, else return this direction + */ public Direction getRight() { - return right != -1 ? values()[right] : null; + return right != -1 ? values()[right] : this; } public double getX() { @@ -346,5 +356,46 @@ public enum Direction { } -} + //FAWE start - utility methods for block states + /** + * Get the directions associated with the given block state, e.g. the connections a fence makes or the direction stairs face + * + * @since TODO + */ + public static EnumSet getDirections(BlockState state) { + EnumSet directions = EnumSet.noneOf(Direction.class); + for (Property property : state.getBlockType().getProperties()) { + if (property instanceof DirectionalProperty dirProp) { + directions.add(state.getState(dirProp)); + continue; + } + Object value = state.getState(property); + if (!(value instanceof String str)) { + if (value instanceof Integer i) { + fromRotationIndex(i).ifPresent(directions::add); + } else if (value instanceof Boolean b && b) { + try { + directions.add(Direction.valueOf(property.getName().toUpperCase(Locale.ROOT))); + } catch (IllegalArgumentException ignored) { + } + } + continue; + } + switch (str.toLowerCase(Locale.ROOT)) { + case "upper", "ceiling", "up", "top" -> directions.add(Direction.UP); + case "lower", "floor", "down", "bottom", "y" -> directions.add(Direction.DOWN); + case "double", "wall" -> {} // Do nothing + case "south" -> directions.add(Direction.SOUTH); + case "x", "east" -> directions.add(Direction.EAST); + case "z", "north" -> directions.add(Direction.NORTH); + case "west" -> directions.add(Direction.WEST); + case "hinge" -> {} // Do nothing for now + case "shape" -> {} // Do nothing for now + } + } + return directions; + } + //FAWE end + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffect.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffect.java index 3c3910be7..49d8bc26d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffect.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffect.java @@ -19,23 +19,30 @@ package com.sk89q.worldedit.util; +import com.fastasyncworldedit.core.configuration.Settings; + import java.util.Locale; public enum SideEffect { - LIGHTING(State.ON, true), - NEIGHBORS(State.ON, true), - UPDATE(State.ON, true), + //FAWE start - adjust defaults, add history and heightmaps + HISTORY(State.ON, true), + HEIGHTMAPS(State.ON, true), + LIGHTING(Settings.settings().LIGHTING.MODE == 0 ? State.OFF : State.ON, true), + NEIGHBORS(State.OFF, true), + UPDATE(State.OFF, true), + //FAWE end VALIDATION(State.OFF, true), ENTITY_AI(State.OFF, true), EVENTS(State.OFF, true), /** * Internal use only. */ - POI_UPDATE(State.ON, false), + POI_UPDATE(State.OFF, false), /** * Internal use only. */ - NETWORK(State.ON, false); + NETWORK(State.OFF, false); + //FAWE end private final String displayName; private final String description; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffectSet.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffectSet.java index 2aa3bcea5..73e3805eb 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffectSet.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/SideEffectSet.java @@ -19,7 +19,6 @@ package com.sk89q.worldedit.util; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import java.util.Arrays; @@ -31,7 +30,12 @@ import java.util.stream.Collectors; public class SideEffectSet { - private static final SideEffectSet DEFAULT = new SideEffectSet(); + //FAWE start - assign value map + private static final SideEffectSet DEFAULT = new SideEffectSet( + Arrays.stream(SideEffect.values()) + .filter(SideEffect::isExposed) + .collect(Collectors.toMap(Function.identity(), SideEffect::getDefaultValue))); + //FAWE end private static final SideEffectSet NONE = new SideEffectSet( Arrays.stream(SideEffect.values()) .filter(SideEffect::isExposed) @@ -42,12 +46,6 @@ public class SideEffectSet { private final Set appliedSideEffects; private final boolean appliesAny; - //FAWE start - private SideEffectSet() { - this(ImmutableMap.of()); - } - //FAWE end - public SideEffectSet(Map sideEffects) { this.sideEffects = Maps.immutableEnumMap(sideEffects); @@ -61,6 +59,27 @@ public class SideEffectSet { //FAWE end } + //FAWE start - simple overload method for setting side effects + + /** + * Create a new {@link SideEffectSet} with the given side effect set to "on" + * + * @since TODO + */ + public SideEffectSet with(SideEffect sideEffect) { + return with(sideEffect, SideEffect.State.ON); + } + + /** + * Create a new {@link SideEffectSet} with the given side effect set to "off" + * + * @since TODO + */ + public SideEffectSet without(SideEffect sideEffect) { + return with(sideEffect, SideEffect.State.OFF); + } + //FAWE end + public SideEffectSet with(SideEffect sideEffect, SideEffect.State state) { Map entries = this.sideEffects.isEmpty() ? Maps.newEnumMap(SideEffect.class) @@ -103,4 +122,23 @@ public class SideEffectSet { return NONE; } + //FAWE start + + /** + * API-friendly side effect set. + * Sets: + * - Heightmaps + * - Lighting (if set to mode 1 or 2 in config) + * Does not set: + * - History + * - Neighbours + * - Lighting (if set to mode 0 in config + * + * @since TODO + */ + public static SideEffectSet api() { + return defaults().without(SideEffect.HISTORY); + } + //FAWE end + } diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index c3468f9d8..6b7ab1990 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -627,10 +627,14 @@ "worldedit.selection.polygon2d.explain.secondary": "Added point #{0} at {1}.", "worldedit.selection.sphere.explain.secondary": "Radius set to {0}.", "worldedit.selection.sphere.explain.secondary-defined": "Radius set to {0} ({1}).", + "worldedit.sideeffect.history": "History", + "worldedit.sideeffect.history.description": "Writes history of the change", + "worldedit.sideeffect.heightmaps": "Heightmaps", + "worldedit.sideeffect.heightmaps.description": "Updates heightmaps", "worldedit.sideeffect.lighting": "Lighting", "worldedit.sideeffect.lighting.description": "Updates block lighting", "worldedit.sideeffect.neighbors": "Neighbors", - "worldedit.sideeffect.neighbors.description": "Notifies nearby blocks of changes", + "worldedit.sideeffect.neighbors.description": "Updates shapes of blocks in the edit", "worldedit.sideeffect.update": "Update", "worldedit.sideeffect.update.description": "Notifies the changed block", "worldedit.sideeffect.validation": "Validation",