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/FaweMutableBlockPlaceContext.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/FaweMutableBlockPlaceContext.java new file mode 100644 index 000000000..4f6828429 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/FaweMutableBlockPlaceContext.java @@ -0,0 +1,137 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FaweMutableBlockPlaceContext extends BlockPlaceContext { + + private static final BlockHitResult DEFAULT_BLOCK_HIT = new BlockHitResult(Vec3.ZERO, Direction.NORTH, BlockPos.ZERO, false); + private final ServerLevel level; + private BlockHitResult hitResult = null; + private Direction direction = null; + private BlockPos relativePos; + + @SuppressWarnings("DataFlowIssue") + public FaweMutableBlockPlaceContext(ServerLevel level) { + super( + level, + null, + null, + null, + DEFAULT_BLOCK_HIT + + ); + this.level = level; + this.replaceClicked = false; + } + + public FaweMutableBlockPlaceContext withSetting(BlockHitResult hitResult, Direction direction) { + this.hitResult = hitResult; + this.direction = direction; + this.relativePos = hitResult.getBlockPos().relative(hitResult.getDirection()); + return this; + } + + @Override + @Nonnull + public BlockPos getClickedPos() { + return this.relativePos; + } + + @Override + @Nonnull + public Direction getClickedFace() { + return this.hitResult.getDirection(); + } + + @Override + @Nonnull + public Vec3 getClickLocation() { + return this.hitResult.getLocation(); + } + + @Override + public boolean isInside() { + return this.hitResult.isInside(); + } + + @Override + @SuppressWarnings("NullableProblems") + public ItemStack getItemInHand() { + return ItemStack.EMPTY; + } + + @Nullable + @Override + public Player getPlayer() { + return null; + } + + @Override + @SuppressWarnings("NullableProblems") + public InteractionHand getHand() { + return null; + } + + @Override + @Nonnull + public Level getLevel() { + return this.level; + } + + @Override + @Nonnull + public Direction getHorizontalDirection() { + return this.direction.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.direction; + } + + @Override + public boolean isSecondaryUseActive() { + return false; + } + + @Override + public float getRotation() { + return (float) (this.direction.get2DDataValue() * 90); + } + + @Override + public boolean canPlace() { + return this.getLevel().getBlockState(this.getClickedPos()).canBeReplaced(this); + } + + @Override + public boolean replacingClickedOnBlock() { + return false; + } + + @Override + @Nonnull + public Direction getNearestLookingDirection() { + return direction; + } + + @Override + @Nonnull + public Direction getNearestLookingVerticalDirection() { + return direction.getAxis() == Direction.Axis.Y ? Direction.UP : Direction.DOWN; + } + + @Override + @Nonnull + public Direction[] getNearestLookingDirections() { + return new Direction[]{direction}; + } + +} 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 bb2d506f5..f59ad4bc6 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; @@ -21,6 +22,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; @@ -442,6 +444,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter(extent).findAndGet(BukkitWorld.class)) == null) { + throw new UnsupportedOperationException("Cannot find world of extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + PaperweightLevelProxy proxyLevel = PaperweightLevelProxy.getInstance( + ((CraftWorld) bukkitWorld.getWorld()).getHandle(), + extent + ); + mutableBlockPlaceContext = new FaweMutableBlockPlaceContext(proxyLevel); + proxyLevel.setEnabled(true); + } + + @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, includeUnedited); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor(extent, mask, includeUnedited); + } + +} 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/FaweMutableBlockPlaceContext.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/FaweMutableBlockPlaceContext.java new file mode 100644 index 000000000..36aab0805 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/FaweMutableBlockPlaceContext.java @@ -0,0 +1,137 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FaweMutableBlockPlaceContext extends BlockPlaceContext { + + private static final BlockHitResult DEFAULT_BLOCK_HIT = new BlockHitResult(Vec3.ZERO, Direction.NORTH, BlockPos.ZERO, false); + private final ServerLevel level; + private BlockHitResult hitResult = null; + private Direction direction = null; + private BlockPos relativePos; + + @SuppressWarnings("DataFlowIssue") + public FaweMutableBlockPlaceContext(ServerLevel level) { + super( + level, + null, + null, + null, + DEFAULT_BLOCK_HIT + + ); + this.level = level; + this.replaceClicked = false; + } + + public FaweMutableBlockPlaceContext withSetting(BlockHitResult hitResult, Direction direction) { + this.hitResult = hitResult; + this.direction = direction; + this.relativePos = hitResult.getBlockPos().relative(hitResult.getDirection()); + return this; + } + + @Override + @Nonnull + public BlockPos getClickedPos() { + return this.relativePos; + } + + @Override + @Nonnull + public Direction getClickedFace() { + return this.hitResult.getDirection(); + } + + @Override + @Nonnull + public Vec3 getClickLocation() { + return this.hitResult.getLocation(); + } + + @Override + public boolean isInside() { + return this.hitResult.isInside(); + } + + @Override + @SuppressWarnings("NullableProblems") + public ItemStack getItemInHand() { + return ItemStack.EMPTY; + } + + @Nullable + @Override + public Player getPlayer() { + return null; + } + + @Override + @SuppressWarnings("NullableProblems") + public InteractionHand getHand() { + return null; + } + + @Override + @Nonnull + public Level getLevel() { + return this.level; + } + + @Override + @Nonnull + public Direction getHorizontalDirection() { + return this.direction.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.direction; + } + + @Override + public boolean isSecondaryUseActive() { + return false; + } + + @Override + public float getRotation() { + return (float) (this.direction.get2DDataValue() * 90); + } + + @Override + public boolean canPlace() { + return this.getLevel().getBlockState(this.getClickedPos()).canBeReplaced(this); + } + + @Override + public boolean replacingClickedOnBlock() { + return false; + } + + @Override + @Nonnull + public Direction getNearestLookingDirection() { + return direction; + } + + @Override + @Nonnull + public Direction getNearestLookingVerticalDirection() { + return direction.getAxis() == Direction.Axis.Y ? Direction.UP : Direction.DOWN; + } + + @Override + @Nonnull + public Direction[] getNearestLookingDirections() { + return new Direction[]{direction}; + } + +} 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 c4af78e95..20766fbe0 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; @@ -21,7 +22,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; @@ -442,6 +443,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter(extent).findAndGet(BukkitWorld.class)) == null) { + throw new UnsupportedOperationException("Cannot find world of extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + PaperweightLevelProxy proxyLevel = PaperweightLevelProxy.getInstance( + ((CraftWorld) bukkitWorld.getWorld()).getHandle(), + extent + ); + mutableBlockPlaceContext = new FaweMutableBlockPlaceContext(proxyLevel); + proxyLevel.setEnabled(true); + } + + @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, includeUnedited); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor(extent, mask, includeUnedited); + } + +} 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/FaweMutableBlockPlaceContext.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/FaweMutableBlockPlaceContext.java new file mode 100644 index 000000000..609ad796d --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/FaweMutableBlockPlaceContext.java @@ -0,0 +1,138 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FaweMutableBlockPlaceContext extends BlockPlaceContext { + + private static final BlockHitResult DEFAULT_BLOCK_HIT = new BlockHitResult(Vec3.ZERO, Direction.NORTH, BlockPos.ZERO, false); + private final ServerLevel level; + private BlockHitResult hitResult = null; + private Direction direction = null; + private BlockPos relativePos; + + @SuppressWarnings("DataFlowIssue") + public FaweMutableBlockPlaceContext(ServerLevel level) { + super( + level, + null, + null, + null, + DEFAULT_BLOCK_HIT + + ); + this.level = level; + this.replaceClicked = false; + } + + public FaweMutableBlockPlaceContext withSetting(BlockHitResult hitResult, Direction direction) { + this.hitResult = hitResult; + this.direction = direction; + this.relativePos = hitResult.getBlockPos().relative(hitResult.getDirection()); + return this; + } + + @Override + @Nonnull + public BlockPos getClickedPos() { + return this.relativePos; + } + + @Override + @Nonnull + public Direction getClickedFace() { + return this.hitResult.getDirection(); + } + + @Override + @Nonnull + public Vec3 getClickLocation() { + return this.hitResult.getLocation(); + } + + @Override + public boolean isInside() { + return this.hitResult.isInside(); + } + + @Override + @SuppressWarnings("NullableProblems") + public ItemStack getItemInHand() { + return ItemStack.EMPTY; + } + + @Nullable + @Override + public Player getPlayer() { + return null; + } + + @Override + @SuppressWarnings("NullableProblems") + public InteractionHand getHand() { + return null; + } + + @Override + @Nonnull + public Level getLevel() { + return this.level; + } + + @Override + @Nonnull + public Direction getHorizontalDirection() { + return this.direction.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.direction; + } + + @Override + public boolean isSecondaryUseActive() { + return false; + } + + @Override + public float getRotation() { + return (float) (this.direction.get2DDataValue() * 90); + } + + @Override + public boolean canPlace() { + return this.getLevel().getBlockState(this.getClickedPos()).canBeReplaced(this); + } + + @Override + public boolean replacingClickedOnBlock() { + return false; + } + + @Override + @Nonnull + public Direction getNearestLookingDirection() { + return direction; + } + + @Override + @Nonnull + public Direction getNearestLookingVerticalDirection() { + return direction.getAxis() == Direction.Axis.Y ? Direction.UP : Direction.DOWN; + } + + @Override + @Nonnull + public Direction[] getNearestLookingDirections() { + return new Direction[]{direction}; + } + +} + 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 151ffd1f3..95e80d1f2 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; @@ -22,6 +23,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; @@ -451,6 +453,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter(extent).findAndGet(BukkitWorld.class)) == null) { + throw new UnsupportedOperationException("Cannot find world of extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + PaperweightLevelProxy proxyLevel = PaperweightLevelProxy.getInstance( + ((CraftWorld) bukkitWorld.getWorld()).getHandle(), + extent + ); + mutableBlockPlaceContext = new FaweMutableBlockPlaceContext(proxyLevel); + proxyLevel.setEnabled(true); + } + + @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, includeUnedited); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor(extent, mask, includeUnedited); + } + +} 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/FaweMutableBlockPlaceContext.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/FaweMutableBlockPlaceContext.java new file mode 100644 index 000000000..c7a879220 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/FaweMutableBlockPlaceContext.java @@ -0,0 +1,138 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FaweMutableBlockPlaceContext extends BlockPlaceContext { + + private static final BlockHitResult DEFAULT_BLOCK_HIT = new BlockHitResult(Vec3.ZERO, Direction.NORTH, BlockPos.ZERO, false); + private final ServerLevel level; + private BlockHitResult hitResult = null; + private Direction direction = null; + private BlockPos relativePos; + + @SuppressWarnings("DataFlowIssue") + public FaweMutableBlockPlaceContext(ServerLevel level) { + super( + level, + null, + null, + null, + DEFAULT_BLOCK_HIT + + ); + this.level = level; + this.replaceClicked = false; + } + + public FaweMutableBlockPlaceContext withSetting(BlockHitResult hitResult, Direction direction) { + this.hitResult = hitResult; + this.direction = direction; + this.relativePos = hitResult.getBlockPos().relative(hitResult.getDirection()); + return this; + } + + @Override + @Nonnull + public BlockPos getClickedPos() { + return this.relativePos; + } + + @Override + @Nonnull + public Direction getClickedFace() { + return this.hitResult.getDirection(); + } + + @Override + @Nonnull + public Vec3 getClickLocation() { + return this.hitResult.getLocation(); + } + + @Override + public boolean isInside() { + return this.hitResult.isInside(); + } + + @Override + @SuppressWarnings("NullableProblems") + public ItemStack getItemInHand() { + return ItemStack.EMPTY; + } + + @Nullable + @Override + public Player getPlayer() { + return null; + } + + @Override + @SuppressWarnings("NullableProblems") + public InteractionHand getHand() { + return null; + } + + @Override + @Nonnull + public Level getLevel() { + return this.level; + } + + @Override + @Nonnull + public Direction getHorizontalDirection() { + return this.direction.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.direction; + } + + @Override + public boolean isSecondaryUseActive() { + return false; + } + + @Override + public float getRotation() { + return (float) (this.direction.get2DDataValue() * 90); + } + + @Override + public boolean canPlace() { + return this.getLevel().getBlockState(this.getClickedPos()).canBeReplaced(this); + } + + @Override + public boolean replacingClickedOnBlock() { + return false; + } + + @Override + @Nonnull + public Direction getNearestLookingDirection() { + return direction; + } + + @Override + @Nonnull + public Direction getNearestLookingVerticalDirection() { + return direction.getAxis() == Direction.Axis.Y ? Direction.UP : Direction.DOWN; + } + + @Override + @Nonnull + public Direction[] getNearestLookingDirections() { + return new Direction[]{direction}; + } + +} + 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 34fc70f8d..458428b49 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; @@ -22,6 +23,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; @@ -451,6 +453,10 @@ public final class PaperweightFaweAdapter extends FaweAdapter(extent).findAndGet(BukkitWorld.class)) == null) { + throw new UnsupportedOperationException("Cannot find world of extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + PaperweightLevelProxy proxyLevel = PaperweightLevelProxy.getInstance( + ((CraftWorld) bukkitWorld.getWorld()).getHandle(), + extent + ); + mutableBlockPlaceContext = new FaweMutableBlockPlaceContext(proxyLevel); + proxyLevel.setEnabled(true); + } + + @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, includeUnedited); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor(extent, mask, includeUnedited); + } + +} 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..4e04368f7 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,6 +38,8 @@ 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.util.SideEffect; import com.sk89q.worldedit.util.lifecycle.Lifecycled; @@ -309,5 +312,10 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser } return this.plugin.getBukkitImplAdapter().getTickingPostProcessor(); } + + @Override + public PlacementStateProcessor getPlatformPlacementProcessor(Extent extent, BlockTypeMask mask, boolean includeUnedited) { + return this.plugin.getBukkitImplAdapter().getPlatformPlacementProcessor(extent, mask, includeUnedited); + } //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..bd29c1b40 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, boolean includeUnedited) { + return null; + } //FAWE end } 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..f4e1fc360 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java @@ -0,0 +1,267 @@ +package com.fastasyncworldedit.core.extent.processor; + +import com.fastasyncworldedit.core.extent.filter.block.FilterBlock; +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.fastasyncworldedit.core.math.MutableVector3; +import com.fastasyncworldedit.core.queue.Filter; +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.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.NBTUtils; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.NullExtent; +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.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.Set; +import java.util.stream.Collectors; + +public abstract class PlacementStateProcessor extends AbstractDelegateExtent implements IBatchProcessor, Filter, Pattern { + + private static final Direction[] NESW = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + private static final boolean SETUP = false; + private static BlockTypeMask DEFAULT_MASK = null; + + protected final Extent extent; + protected final BlockTypeMask mask; + protected final boolean includeUnedited; + private final MutableVector3 clickPos = new MutableVector3(); + private final MutableBlockVector3 clickedBlock = new MutableBlockVector3(); + + public PlacementStateProcessor(Extent extent, BlockTypeMask mask, boolean includeUnedited) { + 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.includeUnedited = includeUnedited; + } + + private static void setup() { + DEFAULT_MASK = new BlockTypeMask(new NullExtent()); + DEFAULT_MASK.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.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, + 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.STAIRS, BlockCategories.WALLS, BlockCategories.BAMBOO_BLOCKS, BlockCategories.CAVE_VINES, BlockCategories.TALL_FLOWERS}; + for (BlockCategory category : categories) { + if (category != null) { + DEFAULT_MASK.add(category.getAll()); + } + } + } + + @Override + public IChunkSet processSet(IChunk iChunk, IChunkGet iChunkGet, IChunkSet iChunkSet) { + int chunkX = iChunk.getX() << 4; + int chunkZ = iChunk.getZ() << 4; + for (int layer = iChunkGet.getMinSectionPosition(); layer <= iChunkGet.getMaxSectionPosition(); layer++) { + int layerY = layer << 4; + char[] set = iChunkSet.loadIfPresent(layer); + char[] get = null; + if (set == null) { + if (!includeUnedited) { + continue; + } + } + for (int y = 0, i = 0; y < 16; y++) { + int blockY = layerY + y; + for (int z = 0; z < 16; z++) { + int blockZ = chunkZ + z; + for (int x = 0; x < 16; x++, i++) { + int blockX = chunkX + x; + char ordinal = set == null ? BlockTypesCache.ReservedIDs.__RESERVED__ : set[i]; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + if (!includeUnedited) { + continue; + } + if (get == null) { + get = iChunkGet.load(layer); + } + ordinal = get[i]; + } + BlockState state = BlockTypesCache.states[ordinal]; + if (!mask.test(state.getBlockType())) { + continue; + } + char newOrdinal = getBlockOrdinal(blockX, blockY, blockZ, state); + if (set == null) { + set = iChunkSet.load(layer); + } + set[i] = newOrdinal; + } + } + } + } + return iChunkSet; + } + + @Override + public ProcessorScope getScope() { + return ProcessorScope.CHANGING_BLOCKS; + } + + @Override + public abstract PlacementStateProcessor fork(); + + // Require block type to avoid duplicate lookup + protected abstract char getStateAtFor( + int x, int y, int z, BlockState state, Vector3 clickPos, Direction clickedFaceDirection, BlockVector3 clickedBlock + ); + + private char getBlockOrdinal( + final int blockX, + final int blockY, + final int blockZ, + final BlockState state + ) { + 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(); // opposite + 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.5); + } + } 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); + } + + @Override + public void applyBlock(FilterBlock block) { + BlockState state = BlockTypesCache.states[block.getOrdinal()]; + if (!mask.test(state.getBlockType())) { + return; + } + 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.getBlockType())) { + 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, newState); + LinCompoundTag nbt = block.getNbt(); + if (nbt != null && newState.getBlockType() == block.getBlockType()) { + orDefault.setTile(set.x(), set.y(), set.z(), new CompoundTag(nbt)); + } + return true; + } + return false; + } + + @Override + public BaseBlock applyBlock(final BlockVector3 position) { + BaseBlock block = extent.getFullBlock(position); + if (!mask.test(block.getBlockType())) { + 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()) { + return state.toBaseBlock(nbt); + } + return state.toBaseBlock(); + } + return null; + } + +} 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/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 565aecb9a..d371ee6a0 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; @@ -908,4 +910,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, true) + ); + 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..25130a90c 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,12 +19,15 @@ 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.registry.Keyed; import com.sk89q.worldedit.util.SideEffect; @@ -276,5 +279,13 @@ public interface Platform extends Keyed { default IBatchProcessor getPlatformPostProcessor(boolean fastMode) { return null; } + + /** + * Returns an {@link PlacementStateProcessor} instance for processing placed blocks to "fix" them. + * @since TODO + */ + default PlacementStateProcessor getPlatformPlacementProcessor(Extent extent, BlockTypeMask mask, boolean includeUnedited) { + return null; + } //FAWE end } 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..5a0e902da 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,14 +21,20 @@ 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.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.OptionalInt; +import java.util.Set; /** * A collection of cardinal, ordinal, and secondary-ordinal directions. @@ -36,32 +42,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 +101,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 +358,42 @@ public enum Direction { } + //FAWE start - utility methods for block states + public static EnumSet getDirections(BlockState state) { + Set directions = new HashSet<>(); + 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); + } + if (value instanceof Boolean) { + 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 EnumSet.copyOf(directions); + } + //FAWE end + }