diff --git a/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts b/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts index 576b9d832e..ff66e21f49 100644 --- a/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts +++ b/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts @@ -1,3 +1,4 @@ +import buildlogic.getLibrary import buildlogic.stringyLibs import buildlogic.getVersion diff --git a/config/checkstyle/checkstyle-suppression.xml b/config/checkstyle/checkstyle-suppression.xml index 8f37b5bec9..13e951be27 100644 --- a/config/checkstyle/checkstyle-suppression.xml +++ b/config/checkstyle/checkstyle-suppression.xml @@ -9,8 +9,8 @@ - - + + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 4c533604de..962e210cf2 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -184,7 +184,7 @@ Checks based on Google Checks, modified for EngineHub. --> - + diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightAdapter.java index 30adb5b05b..0035ae1b37 100644 --- a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightAdapter.java @@ -34,12 +34,17 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_3.wna.PaperweightNativeBlockState; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_3.wna.PaperweightNativeWorld; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.platform.Watchdog; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -171,7 +176,6 @@ import org.spigotmc.SpigotConfig; import org.spigotmc.WatchdogThread; -import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -199,9 +203,28 @@ import static com.google.common.base.Preconditions.checkState; public final class PaperweightAdapter implements BukkitImplAdapter { + public static BlockPos adaptPos(NativePosition pos) { + return new BlockPos(pos.x(), pos.y(), pos.z()); + } private final Logger logger = Logger.getLogger(getClass().getCanonicalName()); + private final NativeAdapter nativeAdapter = new NativeAdapter() { + @Override + public NativeBlockState toNative(BlockState state) { + return new PaperweightNativeBlockState(adapt(state)); + } + + @Override + public BlockState fromNative(NativeBlockState state) { + return adapt(((PaperweightNativeBlockState) state).delegate); + } + + @Override public NativePosition newBlockPos(BlockVector3 pos) { + return pos; + } + }; + private final Field serverWorldsField; private final Method getChunkFutureMethod; private final Field chunkProviderExecutorField; @@ -270,7 +293,7 @@ public DataFixer getDataFixer() { * @param tileEntity the tile entity * @param tag the tag */ - static void readTagIntoTileEntity(net.minecraft.nbt.CompoundTag tag, BlockEntity tileEntity) { + static void readTagIntoTileEntity(CompoundTag tag, BlockEntity tileEntity) { tileEntity.loadWithComponents(tag, MinecraftServer.getServer().registryAccess()); tileEntity.setChanged(); } @@ -291,7 +314,7 @@ private static String getEntityId(Entity entity) { * @param entity the entity * @param tag the tag */ - private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag tag) { + private static void readEntityIntoTag(Entity entity, CompoundTag tag) { entity.save(tag); } @@ -375,7 +398,7 @@ public BaseBlock getFullBlock(Location location) { // Read the NBT data BlockEntity te = chunk.getBlockEntity(blockPos); if (te != null) { - net.minecraft.nbt.CompoundTag tag = te.saveWithId(MinecraftServer.getServer().registryAccess()); + CompoundTag tag = te.saveWithId(MinecraftServer.getServer().registryAccess()); return state.toBaseBlock(LazyReference.from(() -> (LinCompoundTag) toNative(tag))); } @@ -417,8 +440,8 @@ public void setBiome(Location location, BiomeType biome) { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { - return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle())); + public NativeWorld createNativeInterface(World world) { + return new PaperweightNativeWorld(this, nativeAdapter, ((CraftWorld) world).getHandle()); } private static net.minecraft.core.Direction adapt(Direction face) { @@ -479,7 +502,7 @@ public BaseEntity getEntity(org.bukkit.entity.Entity entity) { String id = getEntityId(mcEntity); - net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + CompoundTag tag = new CompoundTag(); readEntityIntoTag(mcEntity, tag); return new BaseEntity( EntityTypes.get(id), @@ -499,12 +522,12 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state String entityId = state.getType().id(); LinCompoundTag nativeTag = state.getNbt(); - net.minecraft.nbt.CompoundTag tag; + CompoundTag tag; if (nativeTag != null) { - tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag); + tag = (CompoundTag) fromNative(nativeTag); removeUnwantedEntityTagsRecursively(tag); } else { - tag = new net.minecraft.nbt.CompoundTag(); + tag = new CompoundTag(); } tag.putString("id", entityId); @@ -523,14 +546,14 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state } // This removes all unwanted tags from the main entity and all its passengers - private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { + private void removeUnwantedEntityTagsRecursively(CompoundTag tag) { for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive if (tag.contains("Passengers", LinTagId.LIST.id())) { - net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", LinTagId.COMPOUND.id()); + ListTag nbttaglist = tag.getList("Passengers", LinTagId.COMPOUND.id()); for (int i = 0; i < nbttaglist.size(); ++i) { removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i)); @@ -610,7 +633,7 @@ public void sendFakeNBT(Player player, BlockVector3 pos, LinCompoundTag nbtData) structureBlock.setLevel(((CraftPlayer) player).getHandle().level()); ((CraftPlayer) player).getHandle().connection.send(ClientboundBlockEntityDataPacket.create( structureBlock, - (blockEntity, registryAccess) -> (net.minecraft.nbt.CompoundTag) fromNative(nbtData) + (blockEntity, registryAccess) -> (CompoundTag) fromNative(nbtData) )); } @@ -834,7 +857,7 @@ private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld Objects.requireNonNull(state); BlockEntity blockEntity = chunk.getBlockEntity(pos); if (blockEntity != null) { - net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(serverWorld.registryAccess()); + CompoundTag tag = blockEntity.saveWithId(serverWorld.registryAccess()); state = state.toBaseBlock(LazyReference.from(() -> (LinCompoundTag) toNative(tag))); } extent.setBlock(vec, state.toBaseBlock()); @@ -997,44 +1020,44 @@ public void sendBiomeUpdates(World world, Iterable chunks) { * @param foreign non-native NMS NBT structure * @return native WorldEdit NBT structure */ - LinTag toNative(net.minecraft.nbt.Tag foreign) { + public LinTag toNative(Tag foreign) { if (foreign == null) { return null; } - if (foreign instanceof net.minecraft.nbt.CompoundTag compoundTag) { + if (foreign instanceof CompoundTag compoundTag) { LinCompoundTag.Builder builder = LinCompoundTag.builder(); for (var entry : compoundTag.getAllKeys()) { builder.put(entry, toNative(compoundTag.get(entry))); } return builder.build(); - } else if (foreign instanceof net.minecraft.nbt.ByteTag byteTag) { + } else if (foreign instanceof ByteTag byteTag) { return LinByteTag.of(byteTag.getAsByte()); - } else if (foreign instanceof net.minecraft.nbt.ByteArrayTag byteArrayTag) { + } else if (foreign instanceof ByteArrayTag byteArrayTag) { return LinByteArrayTag.of(byteArrayTag.getAsByteArray()); - } else if (foreign instanceof net.minecraft.nbt.DoubleTag doubleTag) { + } else if (foreign instanceof DoubleTag doubleTag) { return LinDoubleTag.of(doubleTag.getAsDouble()); - } else if (foreign instanceof net.minecraft.nbt.FloatTag floatTag) { + } else if (foreign instanceof FloatTag floatTag) { return LinFloatTag.of(floatTag.getAsFloat()); - } else if (foreign instanceof net.minecraft.nbt.IntTag intTag) { + } else if (foreign instanceof IntTag intTag) { return LinIntTag.of(intTag.getAsInt()); - } else if (foreign instanceof net.minecraft.nbt.IntArrayTag intArrayTag) { + } else if (foreign instanceof IntArrayTag intArrayTag) { return LinIntArrayTag.of(intArrayTag.getAsIntArray()); - } else if (foreign instanceof net.minecraft.nbt.LongArrayTag longArrayTag) { + } else if (foreign instanceof LongArrayTag longArrayTag) { return LinLongArrayTag.of(longArrayTag.getAsLongArray()); - } else if (foreign instanceof net.minecraft.nbt.ListTag listTag) { + } else if (foreign instanceof ListTag listTag) { try { return toNativeList(listTag); } catch (Throwable e) { logger.log(Level.WARNING, "Failed to convert net.minecraft.nbt.ListTag", e); return LinListTag.empty(LinTagType.endTag()); } - } else if (foreign instanceof net.minecraft.nbt.LongTag longTag) { + } else if (foreign instanceof LongTag longTag) { return LinLongTag.of(longTag.getAsLong()); - } else if (foreign instanceof net.minecraft.nbt.ShortTag shortTag) { + } else if (foreign instanceof ShortTag shortTag) { return LinShortTag.of(shortTag.getAsShort()); - } else if (foreign instanceof net.minecraft.nbt.StringTag stringTag) { + } else if (foreign instanceof StringTag stringTag) { return LinStringTag.of(stringTag.getAsString()); - } else if (foreign instanceof net.minecraft.nbt.EndTag) { + } else if (foreign instanceof EndTag) { return LinEndTag.instance(); } else { throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName()); @@ -1049,12 +1072,12 @@ LinTag toNative(net.minecraft.nbt.Tag foreign) { * @throws SecurityException on error * @throws IllegalArgumentException on error */ - private LinListTag toNativeList(net.minecraft.nbt.ListTag foreign) throws SecurityException, IllegalArgumentException { + private LinListTag toNativeList(ListTag foreign) throws SecurityException, IllegalArgumentException { LinListTag.Builder> builder = LinListTag.builder( LinTagType.fromId(LinTagId.fromId(foreign.getElementType())) ); - for (net.minecraft.nbt.Tag tag : foreign) { + for (Tag tag : foreign) { builder.add(toNative(tag)); } @@ -1067,12 +1090,12 @@ private LinListTag toNativeList(net.minecraft.nbt.ListTag foreign) throws Sec * @param foreign structure to convert * @return non-native structure */ - Tag fromNative(LinTag foreign) { + public Tag fromNative(LinTag foreign) { if (foreign == null) { return null; } if (foreign instanceof LinCompoundTag compoundTag) { - net.minecraft.nbt.CompoundTag tag = new CompoundTag(); + CompoundTag tag = new CompoundTag(); for (var entry : compoundTag.value().entrySet()) { tag.put(entry.getKey(), fromNative(entry.getValue())); } @@ -1092,7 +1115,7 @@ Tag fromNative(LinTag foreign) { } else if (foreign instanceof LinLongArrayTag longArrayTag) { return new LongArrayTag(longArrayTag.value()); } else if (foreign instanceof LinListTag listTag) { - net.minecraft.nbt.ListTag tag = new ListTag(); + ListTag tag = new ListTag(); for (var t : listTag.value()) { tag.add(fromNative(t)); } diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightWorldNativeAccess.java deleted file mode 100644 index 70b7786a10..0000000000 --- a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/PaperweightWorldNativeAccess.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.impl.v1_21_3; - -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.world.block.BlockState; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.Tag; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; -import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.block.data.CraftBlockData; -import org.bukkit.event.block.BlockPhysicsEvent; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.annotation.Nullable; - -public class PaperweightWorldNativeAccess implements WorldNativeAccess { - private static final int UPDATE = 1; - private static final int NOTIFY = 2; - - private final PaperweightAdapter adapter; - private final WeakReference world; - private SideEffectSet sideEffectSet; - - public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { - this.adapter = adapter; - this.world = world; - } - - private ServerLevel getWorld() { - return Objects.requireNonNull(world.get(), "The reference to the world was lost"); - } - - @Override - public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - this.sideEffectSet = sideEffectSet; - } - - @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); - } - - @Override - public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) { - int stateId = BlockStateIdAccess.getBlockStateId(state); - return BlockStateIdAccess.isValidInternalId(stateId) - ? Block.stateById(stateId) - : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); - } - - @Override - public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) { - return chunk.getBlockState(position); - } - - @Nullable - @Override - public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) { - return chunk.setBlockState(position, state, false, this.sideEffectSet.shouldApply(SideEffect.UPDATE)); - } - - @Override - public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) { - return Block.updateFromNeighbourShapes(block, getWorld(), position); - } - - @Override - public BlockPos getPosition(int x, int y, int z) { - return new BlockPos(x, y, z); - } - - @Override - public void updateLightingForBlock(BlockPos position) { - getWorld().getChunkSource().getLightEngine().checkBlock(position); - } - - @Override - public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { - // We will assume that the tile entity was created for us - BlockEntity tileEntity = getWorld().getBlockEntity(position); - if (tileEntity == null) { - return false; - } - Tag nativeTag = adapter.fromNative(tag); - PaperweightAdapter.readTagIntoTileEntity((net.minecraft.nbt.CompoundTag) nativeTag, tileEntity); - return true; - } - - @Override - public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); - } - } - - @Override - public boolean isChunkTicking(LevelChunk chunk) { - return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); - } - - @Override - public void markBlockChanged(LevelChunk chunk, BlockPos position) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().getChunkSource().blockChanged(position); - } - } - - @Override - public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - ServerLevel world = getWorld(); - if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { - world.updateNeighborsAt(pos, oldState.getBlock()); - } else { - // When we don't want events, manually run the physics without them. - Block block = oldState.getBlock(); - fireNeighborChanged(pos, world, block, pos.west()); - fireNeighborChanged(pos, world, block, pos.east()); - fireNeighborChanged(pos, world, block, pos.below()); - fireNeighborChanged(pos, world, block, pos.above()); - fireNeighborChanged(pos, world, block, pos.north()); - fireNeighborChanged(pos, world, block, pos.south()); - } - if (newState.hasAnalogOutputSignal()) { - world.updateNeighbourForOutputSignal(pos, newState.getBlock()); - } - } - - @Override - public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - ServerLevel world = getWorld(); - newState.onPlace(world, pos, oldState, false); - } - - private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { - world.getBlockState(neighborPos).handleNeighborChanged(world, neighborPos, block, ExperimentalRedstoneUtils.initialOrientation(world, null, null), false); - } - - @Override - public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) { - ServerLevel world = getWorld(); - oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { - CraftWorld craftWorld = world.getWorld(); - BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState)); - world.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - } - newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - } - - @Override - public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - getWorld().onBlockStateChange(pos, oldState, newState); - } -} diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/StaticRefraction.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/StaticRefraction.java index 56b11e71ba..83f2fb47d6 100644 --- a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/StaticRefraction.java +++ b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/StaticRefraction.java @@ -88,4 +88,19 @@ public final class StaticRefraction { public static final String DESTROY_BLOCK_BREAKING_ENTITY_MAX_UPDATE = Refraction.pickName( "destroyBlock", "a" ); + public static final String GET_VISIBLE_CHUNK_IF_PRESENT = Refraction.pickName( + "getVisibleChunkIfPresent", "b" + ); + public static final String CHANGED_BLOCKS_PER_SECTION = Refraction.pickName( + "changedBlocksPerSection", "n" + ); + public static final String HAS_CHANGED_SECTIONS = Refraction.pickName( + "hasChangedSections", "m" + ); + /** + * {@code updateBlockEntityTicker(BlockEntity blockEntity)}. + */ + public static final String UPDATE_BLOCK_ENTITY_TICKER = Refraction.pickName( + "updateBlockEntityTicker", "c" + ); } diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeBlockState.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeBlockState.java new file mode 100644 index 0000000000..0b7760d1db --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeBlockState.java @@ -0,0 +1,57 @@ +/* + * 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.impl.v1_21_3.wna; + +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_3.PaperweightAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public final class PaperweightNativeBlockState implements NativeBlockState { + public final BlockState delegate; + + public PaperweightNativeBlockState(BlockState delegate) { + this.delegate = delegate; + } + + @Override + public boolean isSame(NativeBlockState other) { + return this.delegate == ((PaperweightNativeBlockState) other).delegate; + } + + @Override + public boolean isSameBlockType(NativeBlockState other) { + return delegate.getBlock() == ((PaperweightNativeBlockState) other).delegate.getBlock(); + } + + @Override + public boolean hasBlockEntity() { + return delegate.hasBlockEntity(); + } + + @Override + public NativeBlockState updateFromNeighbourShapes(NativeWorld world, NativePosition position) { + return new PaperweightNativeBlockState(Block.updateFromNeighbourShapes( + delegate, ((PaperweightNativeWorld) world).delegate, PaperweightAdapter.adaptPos(position) + )); + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeChunk.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeChunk.java new file mode 100644 index 0000000000..dbfa01da6c --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeChunk.java @@ -0,0 +1,231 @@ +/* + * 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.impl.v1_21_3.wna; + +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_3.PaperweightAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_3.StaticRefraction; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import com.sk89q.worldedit.math.BlockVector3; +import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; +import it.unimi.dsi.fastutil.shorts.ShortSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.Heightmap; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; + +public final class PaperweightNativeChunk implements NativeChunk { + private static final MethodHandle GET_VISIBLE_CHUNK_IF_PRESENT; + private static final MethodHandle GET_CHANGED_BLOCKS_PER_SECTION; + private static final MethodHandle SET_HAS_CHANGED_SECTIONS; + private static final MethodHandle UPDATE_BLOCK_ENTITY_TICKER; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + Method m = ServerChunkCache.class.getDeclaredMethod( + StaticRefraction.GET_VISIBLE_CHUNK_IF_PRESENT, long.class + ); + m.setAccessible(true); + GET_VISIBLE_CHUNK_IF_PRESENT = lookup.unreflect(m); + + Field f = ChunkHolder.class.getDeclaredField(StaticRefraction.CHANGED_BLOCKS_PER_SECTION); + f.setAccessible(true); + GET_CHANGED_BLOCKS_PER_SECTION = lookup.unreflectGetter(f); + + f = ChunkHolder.class.getDeclaredField(StaticRefraction.HAS_CHANGED_SECTIONS); + f.setAccessible(true); + SET_HAS_CHANGED_SECTIONS = lookup.unreflectSetter(f); + + m = LevelChunk.class.getDeclaredMethod(StaticRefraction.UPDATE_BLOCK_ENTITY_TICKER, BlockEntity.class); + m.setAccessible(true); + UPDATE_BLOCK_ENTITY_TICKER = lookup.unreflect(m); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + private static final Set HEIGHTMAPS = EnumSet.of( + Heightmap.Types.WORLD_SURFACE, + Heightmap.Types.OCEAN_FLOOR, + Heightmap.Types.MOTION_BLOCKING, + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES + ); + + private final NativeWorld owner; + private final LevelChunk delegate; + + public PaperweightNativeChunk(NativeWorld owner, LevelChunk delegate) { + this.owner = owner; + this.delegate = delegate; + } + + @Override + public NativeWorld getWorld() { + return owner; + } + + @Override + public boolean isTicking() { + return delegate.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + @Override + public NativePosition getWorldPos(int offsetX, int offsetY, int offsetZ) { + ChunkPos pos = delegate.getPos(); + return new BlockVector3(pos.getBlockX(offsetX), offsetY, pos.getBlockZ(offsetZ)); + } + + @Override + public NativeBlockState getBlockState(NativePosition blockPos) { + return new PaperweightNativeBlockState(delegate.getBlockState(PaperweightAdapter.adaptPos(blockPos))); + } + + @Override + public @Nullable NativeBlockState setBlockState(NativePosition blockPos, NativeBlockState newState, boolean update) { + BlockState blockState = delegate.setBlockState( + PaperweightAdapter.adaptPos(blockPos), + ((PaperweightNativeBlockState) newState).delegate, + false, + update + ); + if (blockState == null) { + return null; + } + return new PaperweightNativeBlockState(blockState); + } + + @Override + public void markSectionChanged(int index, ChunkSectionPosSet changed) { + ServerChunkCache serverChunkCache = (ServerChunkCache) delegate.getLevel().getChunkSource(); + ChunkHolder holder; + try { + holder = (ChunkHolder) GET_VISIBLE_CHUNK_IF_PRESENT.invoke( + serverChunkCache, + delegate.getPos().toLong() + ); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + if (holder != null) { + ShortSet[] changedBlocksPerSection; + try { + changedBlocksPerSection = (ShortSet[]) GET_CHANGED_BLOCKS_PER_SECTION.invoke(holder); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + if (changedBlocksPerSection[index] == null) { + try { + SET_HAS_CHANGED_SECTIONS.invoke(holder, true); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + changedBlocksPerSection[index] = new ShortOpenHashSet(changed.asSectionPosEncodedShorts()); + } else { + changedBlocksPerSection[index].addAll(changed.asSectionPosEncodedShorts()); + } + // Trick to get the holder into the broadcast set + serverChunkCache.onChunkReadyToSend(holder); + } + } + + @Override + public void updateHeightmaps() { + Heightmap.primeHeightmaps(delegate, HEIGHTMAPS); + } + + @Override + public void updateLightingForSectionAirChange(int index, boolean onlyAir) { + delegate.getLevel().getLightEngine().updateSectionStatus( + SectionPos.of(delegate.getPos(), delegate.getLevel().getSectionYFromSectionIndex(index)), + onlyAir + ); + } + + @Override + public void removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ) { + delegate.removeBlockEntity(delegate.getPos().getBlockAt(chunkX, chunkY, chunkZ)); + } + + @SuppressWarnings("deprecation") + @Override + public void initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState) { + BlockPos pos = delegate.getPos().getBlockAt(chunkX, chunkY, chunkZ); + BlockEntity blockEntity = delegate.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + BlockState nativeState = ((PaperweightNativeBlockState) newState).delegate; + if (blockEntity == null) { + blockEntity = ((EntityBlock) nativeState.getBlock()).newBlockEntity(pos, nativeState); + if (blockEntity != null) { + delegate.addAndRegisterBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(nativeState); + try { + UPDATE_BLOCK_ENTITY_TICKER.invoke(blockEntity); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + } + } + + @Override + public NativeChunkSection getChunkSection(int index) { + return new PaperweightNativeChunkSection(delegate.getSection(index)); + } + + @Override + public NativeChunkSection setChunkSection(int index, NativeChunkSection section, ChunkSectionPosSet modifiedBlocks) { + Preconditions.checkPositionIndex(index, delegate.getSectionsCount()); + LevelChunkSection[] chunkSections = delegate.getSections(); + var oldSection = new PaperweightNativeChunkSection(chunkSections[index]); + chunkSections[index] = ((PaperweightNativeChunkSection) section).delegate; + WNASharedImpl.postChunkSectionReplacement(this, index, oldSection, section, modifiedBlocks); + delegate.markUnsaved(); + return oldSection; + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeChunkSection.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeChunkSection.java new file mode 100644 index 0000000000..0317d9d084 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeChunkSection.java @@ -0,0 +1,59 @@ +/* + * 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.impl.v1_21_3.wna; + +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; + +public final class PaperweightNativeChunkSection implements NativeChunkSection { + final LevelChunkSection delegate; + + public PaperweightNativeChunkSection(LevelChunkSection delegate) { + this.delegate = delegate; + } + + @Override + public boolean isOnlyAir() { + return delegate.hasOnlyAir(); + } + + @Override + public NativeBlockState getThenSetBlock(int i, int j, int k, NativeBlockState blockState) { + BlockState nativeState = ((PaperweightNativeBlockState) blockState).delegate; + if (isOnlyAir() && nativeState.isAir()) { + return blockState; + } + return new PaperweightNativeBlockState(delegate.setBlockState(i, j, k, nativeState, false)); + } + + @Override + public NativeBlockState getBlock(int i, int j, int k) { + return new PaperweightNativeBlockState(delegate.getBlockState(i, j, k)); + } + + @Override + public NativeChunkSection copy() { + return new PaperweightNativeChunkSection(new LevelChunkSection( + delegate.getStates().copy(), delegate.getBiomes().copy() + )); + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeWorld.java b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeWorld.java new file mode 100644 index 0000000000..35887399d4 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_3/wna/PaperweightNativeWorld.java @@ -0,0 +1,178 @@ +/* + * 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.impl.v1_21_3.wna; + +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_3.PaperweightAdapter; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.profiling.Profiler; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.enginehub.linbus.tree.LinCompoundTag; + +public final class PaperweightNativeWorld implements NativeWorld { + + private final PaperweightAdapter adapter; + private final NativeAdapter nativeAdapter; + final ServerLevel delegate; + + public PaperweightNativeWorld(PaperweightAdapter adapter, NativeAdapter nativeAdapter, ServerLevel delegate) { + this.adapter = adapter; + this.nativeAdapter = nativeAdapter; + this.delegate = delegate; + } + + @Override + public NativeAdapter getAdapter() { + return nativeAdapter; + } + + @Override + public int getSectionIndex(int y) { + return delegate.getSectionIndex(y); + } + + @Override + public int getYForSectionIndex(int index) { + return SectionPos.sectionToBlockCoord(delegate.getSectionYFromSectionIndex(index)); + } + + @Override + public NativeChunk getChunk(int chunkX, int chunkZ) { + return new PaperweightNativeChunk(this, delegate.getChunk(chunkX, chunkZ)); + } + + @Override + public void notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + delegate.sendBlockUpdated( + PaperweightAdapter.adaptPos(pos), + ((PaperweightNativeBlockState) oldState).delegate, + ((PaperweightNativeBlockState) newState).delegate, + Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS + ); + } + + @Override + public void markBlockChanged(NativePosition pos) { + delegate.getChunkSource().blockChanged(PaperweightAdapter.adaptPos(pos)); + } + + @Override + public void updateLightingForBlock(NativePosition position) { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("updateSkyLightSources"); + /* Paper removes the update here */ + profilerFiller.popPush("queueCheckLight"); + delegate.getChunkSource().getLightEngine().checkBlock(PaperweightAdapter.adaptPos(position)); + profilerFiller.pop(); + } + + @Override + public boolean updateTileEntity(NativePosition position, LinCompoundTag tag) { + CompoundTag nativeTag = (CompoundTag) adapter.fromNative(tag); + BlockPos nativePos = PaperweightAdapter.adaptPos(position); + BlockEntity tileEntity = delegate.getChunkAt(nativePos).getBlockEntity(nativePos); + if (tileEntity == null) { + return false; + } + tileEntity.loadWithComponents(nativeTag, delegate.registryAccess()); + tileEntity.setChanged(); + return true; + } + + @Override + public void notifyNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, boolean events + ) { + BlockPos nativePos = PaperweightAdapter.adaptPos(pos); + if (events) { + delegate.updateNeighborsAt(nativePos, ((PaperweightNativeBlockState) oldState).delegate.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + Block block = ((PaperweightNativeBlockState) oldState).delegate.getBlock(); + fireNeighborChanged(nativePos, delegate, block, nativePos.west()); + fireNeighborChanged(nativePos, delegate, block, nativePos.east()); + fireNeighborChanged(nativePos, delegate, block, nativePos.below()); + fireNeighborChanged(nativePos, delegate, block, nativePos.above()); + fireNeighborChanged(nativePos, delegate, block, nativePos.north()); + fireNeighborChanged(nativePos, delegate, block, nativePos.south()); + } + BlockState nativeNewState = ((PaperweightNativeBlockState) newState).delegate; + if (nativeNewState.hasAnalogOutputSignal()) { + delegate.updateNeighbourForOutputSignal(nativePos, nativeNewState.getBlock()); + } + } + + private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { + world.getBlockState(neighborPos).handleNeighborChanged(world, neighborPos, block, null, false); + } + + @Override + public void updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + BlockPos nativePos = PaperweightAdapter.adaptPos(pos); + BlockState nativeOldState = ((PaperweightNativeBlockState) oldState).delegate; + BlockState nativeNewState = ((PaperweightNativeBlockState) newState).delegate; + nativeOldState.onRemove(delegate, nativePos, nativeNewState, false); + nativeNewState.onPlace(delegate, nativePos, nativeOldState, false); + } + + @Override + public void updateNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit, boolean events + ) { + BlockPos nativePos = PaperweightAdapter.adaptPos(pos); + BlockState nativeOldState = ((PaperweightNativeBlockState) oldState).delegate; + BlockState nativeNewState = ((PaperweightNativeBlockState) newState).delegate; + nativeOldState.updateIndirectNeighbourShapes(delegate, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + if (events) { + BlockPhysicsEvent event = new BlockPhysicsEvent( + CraftBlock.at(delegate, nativePos), + CraftBlockData.fromData(nativeNewState) + ); + delegate.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + nativeNewState.updateNeighbourShapes(delegate, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateIndirectNeighbourShapes(delegate, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + } + + @Override + public void onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + delegate.onBlockStateChange( + PaperweightAdapter.adaptPos(pos), + ((PaperweightNativeBlockState) oldState).delegate, + ((PaperweightNativeBlockState) newState).delegate + ); + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightAdapter.java index 59b193b3f6..e2c58a2ad4 100644 --- a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightAdapter.java @@ -35,12 +35,17 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_4.wna.PaperweightNativeBlockState; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_4.wna.PaperweightNativeWorld; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.platform.Watchdog; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.Constants; import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -171,7 +176,6 @@ import org.spigotmc.SpigotConfig; import org.spigotmc.WatchdogThread; -import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -199,9 +203,28 @@ import static com.google.common.base.Preconditions.checkState; public final class PaperweightAdapter implements BukkitImplAdapter { + public static BlockPos adaptPos(NativePosition pos) { + return new BlockPos(pos.x(), pos.y(), pos.z()); + } private final Logger logger = Logger.getLogger(getClass().getCanonicalName()); + private final NativeAdapter nativeAdapter = new NativeAdapter() { + @Override + public NativeBlockState toNative(BlockState state) { + return new PaperweightNativeBlockState(adapt(state)); + } + + @Override + public BlockState fromNative(NativeBlockState state) { + return adapt(((PaperweightNativeBlockState) state).delegate); + } + + @Override public NativePosition newBlockPos(BlockVector3 pos) { + return pos; + } + }; + private final Field serverWorldsField; private final Method getChunkFutureMethod; private final Field chunkProviderExecutorField; @@ -417,8 +440,8 @@ public void setBiome(Location location, BiomeType biome) { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { - return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle())); + public NativeWorld createNativeInterface(World world) { + return new PaperweightNativeWorld(this, nativeAdapter, ((CraftWorld) world).getHandle()); } private static net.minecraft.core.Direction adapt(Direction face) { @@ -1090,7 +1113,7 @@ private LinListTag toNativeList(net.minecraft.nbt.ListTag foreign) throws Sec * @param foreign structure to convert * @return non-native structure */ - Tag fromNative(LinTag foreign) { + public Tag fromNative(LinTag foreign) { if (foreign == null) { return null; } diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightWorldNativeAccess.java deleted file mode 100644 index ea0a4ab8dc..0000000000 --- a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/PaperweightWorldNativeAccess.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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.impl.v1_21_4; - -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.world.block.BlockState; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.Tag; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; -import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.block.data.CraftBlockData; -import org.bukkit.event.block.BlockPhysicsEvent; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.annotation.Nullable; - -public class PaperweightWorldNativeAccess implements WorldNativeAccess { - private static final int UPDATE = 1; - private static final int NOTIFY = 2; - - private final PaperweightAdapter adapter; - private final WeakReference world; - private SideEffectSet sideEffectSet; - - public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { - this.adapter = adapter; - this.world = world; - } - - private ServerLevel getWorld() { - return Objects.requireNonNull(world.get(), "The reference to the world was lost"); - } - - @Override - public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - this.sideEffectSet = sideEffectSet; - } - - @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); - } - - @Override - public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) { - int stateId = BlockStateIdAccess.getBlockStateId(state); - return BlockStateIdAccess.isValidInternalId(stateId) - ? Block.stateById(stateId) - : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); - } - - @Override - public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) { - return chunk.getBlockState(position); - } - - @Nullable - @Override - public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) { - return chunk.setBlockState(position, state, false, this.sideEffectSet.shouldApply(SideEffect.UPDATE)); - } - - @Override - public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) { - return Block.updateFromNeighbourShapes(block, getWorld(), position); - } - - @Override - public BlockPos getPosition(int x, int y, int z) { - return new BlockPos(x, y, z); - } - - @Override - public void updateLightingForBlock(BlockPos position) { - getWorld().getChunkSource().getLightEngine().checkBlock(position); - } - - @Override - public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { - // We will assume that the tile entity was created for us - BlockEntity tileEntity = getWorld().getBlockEntity(position); - if (tileEntity == null) { - return false; - } - Tag nativeTag = adapter.fromNative(tag); - PaperweightAdapter.readTagIntoTileEntity((net.minecraft.nbt.CompoundTag) nativeTag, tileEntity); - return true; - } - - @Override - public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); - } - } - - @Override - public boolean isChunkTicking(LevelChunk chunk) { - return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); - } - - @Override - public void markBlockChanged(LevelChunk chunk, BlockPos position) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().getChunkSource().blockChanged(position); - } - } - - @Override - public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - ServerLevel world = getWorld(); - if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { - world.updateNeighborsAt(pos, oldState.getBlock()); - } else { - // When we don't want events, manually run the physics without them. - Block block = oldState.getBlock(); - fireNeighborChanged(pos, world, block, pos.west()); - fireNeighborChanged(pos, world, block, pos.east()); - fireNeighborChanged(pos, world, block, pos.below()); - fireNeighborChanged(pos, world, block, pos.above()); - fireNeighborChanged(pos, world, block, pos.north()); - fireNeighborChanged(pos, world, block, pos.south()); - } - if (newState.hasAnalogOutputSignal()) { - world.updateNeighbourForOutputSignal(pos, newState.getBlock()); - } - } - - @Override - public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - ServerLevel world = getWorld(); - newState.onPlace(world, pos, oldState, false); - } - - private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { - world.getBlockState(neighborPos).handleNeighborChanged(world, neighborPos, block, ExperimentalRedstoneUtils.initialOrientation(world, null, null), false); - } - - @Override - public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) { - ServerLevel world = getWorld(); - oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { - CraftWorld craftWorld = world.getWorld(); - BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), CraftBlockData.fromData(newState)); - world.getCraftServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - } - newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - } - - @Override - public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { - getWorld().onBlockStateChange(pos, oldState, newState); - } -} diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/StaticRefraction.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/StaticRefraction.java index 9e5272571e..ee88c7c83b 100644 --- a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/StaticRefraction.java +++ b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/StaticRefraction.java @@ -88,4 +88,37 @@ public final class StaticRefraction { public static final String DESTROY_BLOCK_BREAKING_ENTITY_MAX_UPDATE = Refraction.pickName( "destroyBlock", "a" ); + public static final String GET_VISIBLE_CHUNK_IF_PRESENT = Refraction.pickName( + "getVisibleChunkIfPresent", "b" + ); + public static final String CHANGED_BLOCKS_PER_SECTION = Refraction.pickName( + "changedBlocksPerSection", "n" + ); + public static final String HAS_CHANGED_SECTIONS = Refraction.pickName( + "hasChangedSections", "m" + ); + /** + * {@code updateBlockEntityTicker(BlockEntity blockEntity)}. + */ + public static final String UPDATE_BLOCK_ENTITY_TICKER = Refraction.pickName( + "updateBlockEntityTicker", "c" + ); + public static final String PALETTED_CONTAINER_DATA = Refraction.pickName( + "data", "d" + ); + public static final String PALETTED_CONTAINER_DATA_CLASS = Refraction.pickName( + "net.minecraft.world.level.chunk.PalettedContainer$Data", "net.minecraft.world.level.chunk.DataPaletteBlock$c" + ); + public static final String PALETTED_CONTAINER_DATA_STORAGE = Refraction.pickName( + "storage", "b" + ); + public static final String PALETTED_CONTAINER_DATA_PALETTE = Refraction.pickName( + "palette", "c" + ); + public static final String CREATE_OR_REUSE_DATA = Refraction.pickName( + "createOrReuseData", "a" + ); + public static final String COPY_FROM = Refraction.pickName( + "copyFrom", "a" + ); } diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeBlockState.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeBlockState.java new file mode 100644 index 0000000000..bab61e9313 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeBlockState.java @@ -0,0 +1,57 @@ +/* + * 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.impl.v1_21_4.wna; + +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_4.PaperweightAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public final class PaperweightNativeBlockState implements NativeBlockState { + public final BlockState delegate; + + public PaperweightNativeBlockState(BlockState delegate) { + this.delegate = delegate; + } + + @Override + public boolean isSame(NativeBlockState other) { + return this.delegate == ((PaperweightNativeBlockState) other).delegate; + } + + @Override + public boolean isSameBlockType(NativeBlockState other) { + return delegate.getBlock() == ((PaperweightNativeBlockState) other).delegate.getBlock(); + } + + @Override + public boolean hasBlockEntity() { + return delegate.hasBlockEntity(); + } + + @Override + public NativeBlockState updateFromNeighbourShapes(NativeWorld world, NativePosition position) { + return new PaperweightNativeBlockState(Block.updateFromNeighbourShapes( + delegate, ((PaperweightNativeWorld) world).delegate, PaperweightAdapter.adaptPos(position) + )); + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeChunk.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeChunk.java new file mode 100644 index 0000000000..556044ac2b --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeChunk.java @@ -0,0 +1,231 @@ +/* + * 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.impl.v1_21_4.wna; + +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_4.PaperweightAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_4.StaticRefraction; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import com.sk89q.worldedit.math.BlockVector3; +import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; +import it.unimi.dsi.fastutil.shorts.ShortSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.Heightmap; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; + +public final class PaperweightNativeChunk implements NativeChunk { + private static final MethodHandle GET_VISIBLE_CHUNK_IF_PRESENT; + private static final MethodHandle GET_CHANGED_BLOCKS_PER_SECTION; + private static final MethodHandle SET_HAS_CHANGED_SECTIONS; + private static final MethodHandle UPDATE_BLOCK_ENTITY_TICKER; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + Method m = ServerChunkCache.class.getDeclaredMethod( + StaticRefraction.GET_VISIBLE_CHUNK_IF_PRESENT, long.class + ); + m.setAccessible(true); + GET_VISIBLE_CHUNK_IF_PRESENT = lookup.unreflect(m); + + Field f = ChunkHolder.class.getDeclaredField(StaticRefraction.CHANGED_BLOCKS_PER_SECTION); + f.setAccessible(true); + GET_CHANGED_BLOCKS_PER_SECTION = lookup.unreflectGetter(f); + + f = ChunkHolder.class.getDeclaredField(StaticRefraction.HAS_CHANGED_SECTIONS); + f.setAccessible(true); + SET_HAS_CHANGED_SECTIONS = lookup.unreflectSetter(f); + + m = LevelChunk.class.getDeclaredMethod(StaticRefraction.UPDATE_BLOCK_ENTITY_TICKER, BlockEntity.class); + m.setAccessible(true); + UPDATE_BLOCK_ENTITY_TICKER = lookup.unreflect(m); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + private static final Set HEIGHTMAPS = EnumSet.of( + Heightmap.Types.WORLD_SURFACE, + Heightmap.Types.OCEAN_FLOOR, + Heightmap.Types.MOTION_BLOCKING, + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES + ); + + private final NativeWorld owner; + private final LevelChunk delegate; + + public PaperweightNativeChunk(NativeWorld owner, LevelChunk delegate) { + this.owner = owner; + this.delegate = delegate; + } + + @Override + public NativeWorld getWorld() { + return owner; + } + + @Override + public boolean isTicking() { + return delegate.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + @Override + public NativePosition getWorldPos(int offsetX, int offsetY, int offsetZ) { + ChunkPos pos = delegate.getPos(); + return new BlockVector3(pos.getBlockX(offsetX), offsetY, pos.getBlockZ(offsetZ)); + } + + @Override + public NativeBlockState getBlockState(NativePosition blockPos) { + return new PaperweightNativeBlockState(delegate.getBlockState(PaperweightAdapter.adaptPos(blockPos))); + } + + @Override + public @Nullable NativeBlockState setBlockState(NativePosition blockPos, NativeBlockState newState, boolean update) { + BlockState blockState = delegate.setBlockState( + PaperweightAdapter.adaptPos(blockPos), + ((PaperweightNativeBlockState) newState).delegate, + false, + update + ); + if (blockState == null) { + return null; + } + return new PaperweightNativeBlockState(blockState); + } + + @Override + public void markSectionChanged(int index, ChunkSectionPosSet changed) { + ServerChunkCache serverChunkCache = (ServerChunkCache) delegate.getLevel().getChunkSource(); + ChunkHolder holder; + try { + holder = (ChunkHolder) GET_VISIBLE_CHUNK_IF_PRESENT.invoke( + serverChunkCache, + delegate.getPos().toLong() + ); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + if (holder != null) { + ShortSet[] changedBlocksPerSection; + try { + changedBlocksPerSection = (ShortSet[]) GET_CHANGED_BLOCKS_PER_SECTION.invoke(holder); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + if (changedBlocksPerSection[index] == null) { + try { + SET_HAS_CHANGED_SECTIONS.invoke(holder, true); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + changedBlocksPerSection[index] = new ShortOpenHashSet(changed.asSectionPosEncodedShorts()); + } else { + changedBlocksPerSection[index].addAll(changed.asSectionPosEncodedShorts()); + } + // Trick to get the holder into the broadcast set + serverChunkCache.onChunkReadyToSend(holder); + } + } + + @Override + public void updateHeightmaps() { + Heightmap.primeHeightmaps(delegate, HEIGHTMAPS); + } + + @Override + public void updateLightingForSectionAirChange(int index, boolean onlyAir) { + delegate.getLevel().getLightEngine().updateSectionStatus( + SectionPos.of(delegate.getPos(), delegate.getLevel().getSectionYFromSectionIndex(index)), + onlyAir + ); + } + + @Override + public void removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ) { + delegate.removeBlockEntity(delegate.getPos().getBlockAt(chunkX, chunkY, chunkZ)); + } + + @SuppressWarnings("deprecation") + @Override + public void initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState) { + BlockPos pos = delegate.getPos().getBlockAt(chunkX, chunkY, chunkZ); + BlockEntity blockEntity = delegate.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + BlockState nativeState = ((PaperweightNativeBlockState) newState).delegate; + if (blockEntity == null) { + blockEntity = ((EntityBlock) nativeState.getBlock()).newBlockEntity(pos, nativeState); + if (blockEntity != null) { + delegate.addAndRegisterBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(nativeState); + try { + UPDATE_BLOCK_ENTITY_TICKER.invoke(blockEntity); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + } + } + + @Override + public NativeChunkSection getChunkSection(int index) { + return new PaperweightNativeChunkSection(delegate.getSection(index)); + } + + @Override + public NativeChunkSection setChunkSection(int index, NativeChunkSection section, ChunkSectionPosSet modifiedBlocks) { + Preconditions.checkPositionIndex(index, delegate.getSectionsCount()); + LevelChunkSection[] chunkSections = delegate.getSections(); + var oldSection = new PaperweightNativeChunkSection(chunkSections[index]); + chunkSections[index] = ((PaperweightNativeChunkSection) section).delegate; + WNASharedImpl.postChunkSectionReplacement(this, index, oldSection, section, modifiedBlocks); + delegate.markUnsaved(); + return oldSection; + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeChunkSection.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeChunkSection.java new file mode 100644 index 0000000000..75ba5ce282 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeChunkSection.java @@ -0,0 +1,59 @@ +/* + * 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.impl.v1_21_4.wna; + +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; + +public final class PaperweightNativeChunkSection implements NativeChunkSection { + final LevelChunkSection delegate; + + public PaperweightNativeChunkSection(LevelChunkSection delegate) { + this.delegate = delegate; + } + + @Override + public boolean isOnlyAir() { + return delegate.hasOnlyAir(); + } + + @Override + public NativeBlockState getThenSetBlock(int i, int j, int k, NativeBlockState blockState) { + BlockState nativeState = ((PaperweightNativeBlockState) blockState).delegate; + if (isOnlyAir() && nativeState.isAir()) { + return blockState; + } + return new PaperweightNativeBlockState(delegate.setBlockState(i, j, k, nativeState, false)); + } + + @Override + public NativeBlockState getBlock(int i, int j, int k) { + return new PaperweightNativeBlockState(delegate.getBlockState(i, j, k)); + } + + @Override + public NativeChunkSection copy() { + return new PaperweightNativeChunkSection(new LevelChunkSection( + delegate.getStates().copy(), delegate.getBiomes().copy() + )); + } +} diff --git a/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeWorld.java b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeWorld.java new file mode 100644 index 0000000000..fc597b4439 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1.21.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_21_4/wna/PaperweightNativeWorld.java @@ -0,0 +1,178 @@ +/* + * 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.impl.v1_21_4.wna; + +import com.sk89q.worldedit.bukkit.adapter.impl.v1_21_4.PaperweightAdapter; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.profiling.Profiler; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.enginehub.linbus.tree.LinCompoundTag; + +public final class PaperweightNativeWorld implements NativeWorld { + + private final PaperweightAdapter adapter; + private final NativeAdapter nativeAdapter; + final ServerLevel delegate; + + public PaperweightNativeWorld(PaperweightAdapter adapter, NativeAdapter nativeAdapter, ServerLevel delegate) { + this.adapter = adapter; + this.nativeAdapter = nativeAdapter; + this.delegate = delegate; + } + + @Override + public NativeAdapter getAdapter() { + return nativeAdapter; + } + + @Override + public int getSectionIndex(int y) { + return delegate.getSectionIndex(y); + } + + @Override + public int getYForSectionIndex(int index) { + return SectionPos.sectionToBlockCoord(delegate.getSectionYFromSectionIndex(index)); + } + + @Override + public NativeChunk getChunk(int chunkX, int chunkZ) { + return new PaperweightNativeChunk(this, delegate.getChunk(chunkX, chunkZ)); + } + + @Override + public void notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + delegate.sendBlockUpdated( + PaperweightAdapter.adaptPos(pos), + ((PaperweightNativeBlockState) oldState).delegate, + ((PaperweightNativeBlockState) newState).delegate, + Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS + ); + } + + @Override + public void markBlockChanged(NativePosition pos) { + delegate.getChunkSource().blockChanged(PaperweightAdapter.adaptPos(pos)); + } + + @Override + public void updateLightingForBlock(NativePosition position) { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("updateSkyLightSources"); + /* Paper removes the update here */ + profilerFiller.popPush("queueCheckLight"); + delegate.getChunkSource().getLightEngine().checkBlock(PaperweightAdapter.adaptPos(position)); + profilerFiller.pop(); + } + + @Override + public boolean updateTileEntity(NativePosition position, LinCompoundTag tag) { + CompoundTag nativeTag = (CompoundTag) adapter.fromNative(tag); + BlockPos nativePos = PaperweightAdapter.adaptPos(position); + BlockEntity tileEntity = delegate.getChunkAt(nativePos).getBlockEntity(nativePos); + if (tileEntity == null) { + return false; + } + tileEntity.loadWithComponents(nativeTag, delegate.registryAccess()); + tileEntity.setChanged(); + return true; + } + + @Override + public void notifyNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, boolean events + ) { + BlockPos nativePos = PaperweightAdapter.adaptPos(pos); + if (events) { + delegate.updateNeighborsAt(nativePos, ((PaperweightNativeBlockState) oldState).delegate.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + Block block = ((PaperweightNativeBlockState) oldState).delegate.getBlock(); + fireNeighborChanged(nativePos, delegate, block, nativePos.west()); + fireNeighborChanged(nativePos, delegate, block, nativePos.east()); + fireNeighborChanged(nativePos, delegate, block, nativePos.below()); + fireNeighborChanged(nativePos, delegate, block, nativePos.above()); + fireNeighborChanged(nativePos, delegate, block, nativePos.north()); + fireNeighborChanged(nativePos, delegate, block, nativePos.south()); + } + BlockState nativeNewState = ((PaperweightNativeBlockState) newState).delegate; + if (nativeNewState.hasAnalogOutputSignal()) { + delegate.updateNeighbourForOutputSignal(nativePos, nativeNewState.getBlock()); + } + } + + private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { + world.getBlockState(neighborPos).handleNeighborChanged(world, neighborPos, block, null, false); + } + + @Override + public void updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + BlockPos nativePos = PaperweightAdapter.adaptPos(pos); + BlockState nativeOldState = ((PaperweightNativeBlockState) oldState).delegate; + BlockState nativeNewState = ((PaperweightNativeBlockState) newState).delegate; + nativeOldState.onRemove(delegate, nativePos, nativeNewState, false); + nativeNewState.onPlace(delegate, nativePos, nativeOldState, false); + } + + @Override + public void updateNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit, boolean events + ) { + BlockPos nativePos = PaperweightAdapter.adaptPos(pos); + BlockState nativeOldState = ((PaperweightNativeBlockState) oldState).delegate; + BlockState nativeNewState = ((PaperweightNativeBlockState) newState).delegate; + nativeOldState.updateIndirectNeighbourShapes(delegate, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + if (events) { + BlockPhysicsEvent event = new BlockPhysicsEvent( + CraftBlock.at(delegate, nativePos), + CraftBlockData.fromData(nativeNewState) + ); + delegate.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + nativeNewState.updateNeighbourShapes(delegate, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateIndirectNeighbourShapes(delegate, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + } + + @Override + public void onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + delegate.onBlockStateChange( + PaperweightAdapter.adaptPos(pos), + ((PaperweightNativeBlockState) oldState).delegate, + ((PaperweightNativeBlockState) newState).delegate + ); + } +} diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index af2d4c00bf..93600472ba 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -83,14 +83,12 @@ tasks.named("shadowJar") { include(dependency("org.antlr:antlr4-runtime")) include(dependency("org.bstats:")) include(dependency("io.papermc:paperlib")) - include(dependency("it.unimi.dsi:fastutil")) include(dependency("com.sk89q.lib:jlibnoise")) exclude(dependency("$group:$name")) relocate("org.bstats", "com.sk89q.worldedit.bstats") relocate("io.papermc.lib", "com.sk89q.worldedit.bukkit.paperlib") - relocate("it.unimi.dsi.fastutil", "com.sk89q.worldedit.bukkit.fastutil") relocate("net.royawesome.jlibnoise", "com.sk89q.worldedit.jlibnoise") } project.project(":worldedit-bukkit:adapters").subprojects.forEach { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 6f94aa6886..e3e9282d43 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -32,7 +32,8 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.util.LogManagerCompat; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -91,7 +92,7 @@ public class BukkitWorld extends AbstractWorld { } private final WeakReference worldRef; - private final WorldNativeAccess worldNativeAccess; + private final NativeWorld nativeWorld; /** * Construct the object. @@ -102,12 +103,17 @@ public BukkitWorld(World world) { this.worldRef = new WeakReference<>(world); BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); if (adapter != null) { - this.worldNativeAccess = adapter.createWorldNativeAccess(world); + this.nativeWorld = adapter.createNativeInterface(world); } else { - this.worldNativeAccess = null; + this.nativeWorld = null; } } + @Override + public @Nullable NativeWorld getNativeInterface() { + return nativeWorld; + } + @Override public List getEntities(Region region) { World world = getWorld(); @@ -480,9 +486,9 @@ public com.sk89q.worldedit.world.block.BlockState getBlock(BlockVector3 position @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) { clearContainerBlockContents(position); - if (worldNativeAccess != null) { + if (nativeWorld != null) { try { - return worldNativeAccess.setBlock(position, block, sideEffects); + return WNASharedImpl.setBlock(nativeWorld, position, block, sideEffects); } catch (Exception e) { if (block instanceof BaseBlock baseBlock && baseBlock.getNbt() != null) { LOGGER.warn("Tried to set a corrupt tile entity at " + position.toString() @@ -514,8 +520,8 @@ public BaseBlock getFullBlock(BlockVector3 position) { @Override public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) { - if (worldNativeAccess != null) { - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); + if (nativeWorld != null) { + WNASharedImpl.applySideEffects(nativeWorld, sideEffectSet, position, previousType); return Sets.intersection( WorldEditPlugin.getInstance().getInternalPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply() 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 ee10cb7669..770b407bf0 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 @@ -24,7 +24,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -100,12 +100,12 @@ default void tickWatchdog() { BaseBlock getFullBlock(Location location); /** - * Create a {@link WorldNativeAccess} for the given world reference. + * Create a {@link NativeWorld} for the given world reference. * * @param world the world reference * @return the native access object */ - WorldNativeAccess createWorldNativeAccess(World world); + NativeWorld createNativeInterface(World world); /** * Get the state for the given entity. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 5d98f6554a..4ba391d11c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -44,6 +44,7 @@ import com.sk89q.worldedit.extent.world.SideEffectExtent; import com.sk89q.worldedit.extent.world.SurvivalModeExtent; import com.sk89q.worldedit.extent.world.WatchdogTickingExtent; +import com.sk89q.worldedit.extent.world.internal.SectionBufferingExtent; import com.sk89q.worldedit.function.GroundFunction; import com.sk89q.worldedit.function.RegionMaskingFilter; import com.sk89q.worldedit.function.biome.BiomeReplace; @@ -86,6 +87,7 @@ import com.sk89q.worldedit.internal.expression.ExpressionTimeoutException; import com.sk89q.worldedit.internal.expression.LocalSlot.Variable; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.MathUtils; import com.sk89q.worldedit.math.Vector2; @@ -115,6 +117,7 @@ import com.sk89q.worldedit.util.eventbus.EventBus; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.AbstractWorld; import com.sk89q.worldedit.world.NullWorld; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeType; @@ -254,7 +257,16 @@ public String getDisplayName() { Extent extent; // These extents are ALWAYS used - extent = traceIfNeeded(sideEffectExtent = new SideEffectExtent(world)); + sideEffectExtent = new SideEffectExtent(world); + NativeWorld nativeInterface = null; + boolean usingNativeInterface = WorldEdit.getInstance().getConfiguration().chunkSectionEditing + && world instanceof AbstractWorld internalWorld + && (nativeInterface = internalWorld.getNativeInterface()) != null; + if (usingNativeInterface) { + extent = traceIfNeeded(new SectionBufferingExtent(nativeInterface, sideEffectExtent)); + } else { + extent = traceIfNeeded(sideEffectExtent); + } if (watchdog != null) { // Reset watchdog before world placement WatchdogTickingExtent watchdogExtent = new WatchdogTickingExtent(extent, watchdog); @@ -269,7 +281,11 @@ public String getDisplayName() { this.bypassReorderHistory = traceIfNeeded(new DataValidatorExtent(extent, world)); // This extent can be skipped by calling rawSetBlock() - extent = traceIfNeeded(batchingExtent = new BatchingExtent(extent)); + if (!usingNativeInterface) { + // We need to ensure that blocks are not immediately committed to the world for masks + // This is done by the SectionBufferingExtent normally, but if we can't use it we need a fallback + extent = traceIfNeeded(batchingExtent = new BatchingExtent(extent)); + } @SuppressWarnings("deprecation") MultiStageReorder reorder = new MultiStageReorder(extent, false); extent = traceIfNeeded(reorderExtent = reorder); @@ -619,12 +635,13 @@ public void setBatchingChunks(boolean batchingChunks) { } return; } - assert batchingExtent != null : "same nullness as chunkBatchingExtent"; if (!batchingChunks && isBatchingChunks()) { internalFlushSession(); } chunkBatchingExtent.setEnabled(batchingChunks); - batchingExtent.setEnabled(!batchingChunks); + if (batchingExtent != null) { + batchingExtent.setEnabled(!batchingChunks); + } } /** @@ -653,8 +670,9 @@ public void disableBuffering() { setReorderMode(ReorderMode.NONE); if (chunkBatchingExtent != null) { chunkBatchingExtent.setEnabled(false); - assert batchingExtent != null : "same nullness as chunkBatchingExtent"; - batchingExtent.setEnabled(true); + if (batchingExtent != null) { + batchingExtent.setEnabled(true); + } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java index c3a2748451..8296f3eeeb 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java @@ -94,6 +94,7 @@ public abstract class LocalConfiguration { public boolean commandBlockSupport = false; public String defaultLocaleName = "default"; public Locale defaultLocale = Locale.getDefault(); + public boolean chunkSectionEditing = true; @SuppressWarnings("deprecation") protected String[] getDefaultDisallowedBlocks() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/internal/SectionBufferingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/internal/SectionBufferingExtent.java new file mode 100644 index 0000000000..87c7bb2f0a --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/internal/SectionBufferingExtent.java @@ -0,0 +1,280 @@ +/* + * 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.extent.world.internal; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Watchdog; +import com.sk89q.worldedit.extent.AbstractBufferingExtent; +import com.sk89q.worldedit.extent.world.SideEffectExtent; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.RunContext; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.util.collection.BlockMap; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.util.Map; +import javax.annotation.Nullable; + +/** + * A special extent that buffers all changes as {@link NativeChunkSection chunk sections}. This allows highly optimized + * block setting, but has a higher likelihood of different semantics compared to traditional block setting. + */ +public class SectionBufferingExtent extends AbstractBufferingExtent { + + private static long getTopSectionKey(BlockVector3 sectionPos) { + return ((long) sectionPos.x() & 0xFF_FF_FF_FFL) | (((long) sectionPos.z() & 0xFF_FF_FF_FFL) << 32); + } + + private record SectionData( + NativeChunkSection section, + ChunkSectionPosSet modified + ) { + } + + private final Long2ObjectMap> sectionTable = new Long2ObjectLinkedOpenHashMap<>(); + private final BlockMap blockEntityMap = BlockMap.create(); + private final NativeWorld nativeWorld; + private final SideEffectExtent sideEffectExtent; + private boolean enabled; + + public SectionBufferingExtent(NativeWorld nativeWorld, SideEffectExtent sideEffectExtent) { + this(nativeWorld, sideEffectExtent, true); + } + + public SectionBufferingExtent(NativeWorld nativeWorld, SideEffectExtent sideEffectExtent, boolean enabled) { + super(sideEffectExtent); + this.nativeWorld = nativeWorld; + this.sideEffectExtent = sideEffectExtent; + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean commitRequired() { + return enabled; + } + + private BlockVector3 toSectionPos(BlockVector3 position) { + return BlockVector3.at( + position.x() >> 4, + nativeWorld.getSectionIndex(position.y()), + position.z() >> 4 + ); + } + + private @Nullable SectionData getSectionData(BlockVector3 sectionPos) { + Int2ObjectArrayMap sections = sectionTable.get(getTopSectionKey(sectionPos)); + if (sections == null) { + return null; + } + return sections.get(sectionPos.y()); + } + + @Override + public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { + if (!enabled) { + return setDelegateBlock(location, block); + } + if (!sideEffectExtent.isPostEditSimulationEnabled()) { + throw new IllegalStateException("SectionBufferingExtent requires SideEffectExtent to have post-edit simulation enabled"); + } + BlockVector3 sectionPos = toSectionPos(location); + SectionData data = getSectionData(sectionPos); + if (data != null) { + NativeBlockState newState = nativeWorld.getAdapter().toNative(block.toImmutableState()); + NativeBlockState oldState = data.section.getThenSetBlock( + location.x() & 0xF, location.y() & 0xF, location.z() & 0xF, + newState + ); + if (oldState == newState && block.toBaseBlock().getNbt() == null) { + // Optimize out the change if it's the same + return false; + } + } else { + if (block.toBaseBlock().getNbt() == null) { + BaseBlock existingState = getExtent().getFullBlock(location); + if (existingState.toImmutableState() == block && existingState.getNbt() == null) { + // Optimize out the change if it's the same + return false; + } + } + data = new SectionData( + nativeWorld.getChunk(sectionPos.x(), sectionPos.z()).getChunkSection(sectionPos.y()).copy(), + new ChunkSectionPosSet() + ); + sectionTable.computeIfAbsent(getTopSectionKey(sectionPos), k -> new Int2ObjectArrayMap<>()) + .put(sectionPos.y(), data); + data.section.getThenSetBlock( + location.x() & 0xF, location.y() & 0xF, location.z() & 0xF, + nativeWorld.getAdapter().toNative(block.toImmutableState()) + ); + } + + LinCompoundTag nbt = block.toBaseBlock().getNbt(); + if (nbt != null) { + blockEntityMap.put(location, nbt); + } + + data.modified.set(location.x() & 0xF, location.y() & 0xF, location.z() & 0xF); + + return true; + } + + @Override + protected BaseBlock getBufferedFullBlock(BlockVector3 position) { + if (!enabled) { + // Early exit if we're not enabled. + return null; + } + BlockVector3 sectionPos = toSectionPos(position); + SectionData data = getSectionData(sectionPos); + if (data == null) { + return null; + } + NativeBlockState state = data.section.getBlock(position.x() & 0xF, position.y() & 0xF, position.z() & 0xF); + return nativeWorld.getAdapter().fromNative(state).toBaseBlock(blockEntityMap.get(position)); + } + + @Override + protected Operation commitBefore() { + if (!commitRequired()) { + return null; + } + return new Operation() { + @Override + public Operation resume(RunContext run) { + finishOperation(); + return null; + } + + @Override + public void cancel() { + } + }; + } + + private void finishOperation() { + record SectionDataWithOld( + NativeChunkSection section, + NativeChunkSection oldSection, + ChunkSectionPosSet modified + ) { + } + + + Watchdog watchdog = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.GAME_HOOKS).getWatchdog(); + + Long2ObjectMap> effectData = new Long2ObjectLinkedOpenHashMap<>(); + + int ops = 0; + for (Long2ObjectMap.Entry> entry : sectionTable.long2ObjectEntrySet()) { + int chunkX = (int) entry.getLongKey(); + int chunkZ = (int) (entry.getLongKey() >> 32); + NativeChunk chunk = nativeWorld.getChunk(chunkX, chunkZ); + Int2ObjectArrayMap oldSections = new Int2ObjectArrayMap<>(entry.getValue().size()); + for (Int2ObjectArrayMap.Entry sectionEntry : entry.getValue().int2ObjectEntrySet()) { + SectionData data = sectionEntry.getValue(); + NativeChunkSection old = chunk.setChunkSection(sectionEntry.getIntKey(), data.section, data.modified); + oldSections.put(sectionEntry.getIntKey(), new SectionDataWithOld(data.section, old, data.modified)); + } + effectData.put(entry.getLongKey(), oldSections); + + chunk.updateHeightmaps(); + + if (watchdog != null) { + ops = updateWatchdog(ops, watchdog); + } + } + sectionTable.clear(); + + for (Map.Entry entry : blockEntityMap.entrySet()) { + BlockVector3 position = entry.getKey(); + LinCompoundTag tag = entry.getValue(); + NativePosition pos = nativeWorld.getAdapter().newBlockPos(position); + WNASharedImpl.updateTileEntity(nativeWorld, tag, pos); + + if (watchdog != null) { + ops = updateWatchdog(ops, watchdog); + } + } + blockEntityMap.clear(); + + SideEffectSet sideEffectSet = sideEffectExtent.getSideEffectSet(); + SideEffectSet perBlockEffects = sideEffectSet + .with(SideEffect.NETWORK, SideEffect.State.OFF); + for (Long2ObjectMap.Entry> entry : effectData.long2ObjectEntrySet()) { + int chunkX = (int) entry.getLongKey(); + int chunkZ = (int) (entry.getLongKey() >> 32); + NativeChunk chunk = nativeWorld.getChunk(chunkX, chunkZ); + for (Int2ObjectArrayMap.Entry sectionEntry : entry.getValue().int2ObjectEntrySet()) { + int index = sectionEntry.getIntKey(); + SectionDataWithOld data = sectionEntry.getValue(); + if (sideEffectSet.shouldApply(SideEffect.NETWORK)) { + chunk.markSectionChanged(index, data.modified); + } + int sectionBlockY = nativeWorld.getYForSectionIndex(index); + data.modified.forEach((x, y, z) -> { + NativePosition pos = chunk.getWorldPos(x, sectionBlockY + y, z); + WNASharedImpl.applySideEffectsNoLookups( + nativeWorld, chunk, perBlockEffects, pos, + data.oldSection.getBlock(x, y, z), data.section.getBlock(x, y, z) + ); + }); + } + + if (watchdog != null) { + ops = updateWatchdog(ops, watchdog); + } + } + } + + private static int updateWatchdog(int ops, Watchdog watchdog) { + ops++; + if (ops == 100) { + watchdog.tick(); + ops = 0; + } + return ops; + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/collection/ChunkSectionPosSet.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/collection/ChunkSectionPosSet.java new file mode 100644 index 0000000000..70e67667de --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/util/collection/ChunkSectionPosSet.java @@ -0,0 +1,94 @@ +/* + * 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.internal.util.collection; + +import it.unimi.dsi.fastutil.shorts.AbstractShortCollection; +import it.unimi.dsi.fastutil.shorts.ShortCollection; +import it.unimi.dsi.fastutil.shorts.ShortIterator; + +import java.util.BitSet; + +/** + * A set of positions in a chunk section. + * + *

+ * This has a defined order based on the order of the bits in the internal encoding. It is not guaranteed to be stable + * between Minecraft versions, but it is stable within a single one. + *

+ */ +public final class ChunkSectionPosSet { + private static int index(int x, int y, int z) { + // Each value is 0-15, so 4 bits + // NOTE: This encoding specifically matches the encoding used by SectionPos in Minecraft, do not change unless they do + return (x << 8) | (z << 4) | y; + } + + @FunctionalInterface + public interface PosConsumer { + void apply(int x, int y, int z); + } + + private final BitSet mask = new BitSet(4096); + + public void set(int x, int y, int z) { + mask.set(index(x, y, z)); + } + + public void forEach(PosConsumer consumer) { + for (int i = mask.nextSetBit(0); i >= 0; i = mask.nextSetBit(i + 1)) { + consumer.apply((i >> 8) & 0xF, i & 0xF, (i >> 4) & 0xF); + } + } + + /** + * {@return a view of this set as a short collection} These shorts match those used by {@code SectionPos}. + */ + public ShortCollection asSectionPosEncodedShorts() { + return new AbstractShortCollection() { + @Override + public ShortIterator iterator() { + return new ShortIterator() { + private int next = mask.nextSetBit(0); + + @Override + public short nextShort() { + if (!hasNext()) { + throw new IllegalStateException(); + } + // Uses the fact that we share the encoding with SectionPos to efficiently map + short value = (short) next; + next = mask.nextSetBit(next + 1); + return value; + } + + @Override + public boolean hasNext() { + return next >= 0; + } + }; + } + + @Override + public int size() { + return mask.cardinality(); + } + }; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeAdapter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeAdapter.java new file mode 100644 index 0000000000..20c17dd920 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeAdapter.java @@ -0,0 +1,34 @@ +/* + * 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.internal.wna; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockState; + +/** + * Methods for adapting data to the native types. + */ +public interface NativeAdapter { + NativeBlockState toNative(BlockState state); + + BlockState fromNative(NativeBlockState state); + + NativePosition newBlockPos(BlockVector3 pos); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeBlockState.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeBlockState.java new file mode 100644 index 0000000000..2770a1926f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeBlockState.java @@ -0,0 +1,33 @@ +/* + * 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.internal.wna; + +/** + * The equivalent of {@link com.sk89q.worldedit.world.block.BlockState}, but in the platform's base. + */ +public interface NativeBlockState { + boolean isSame(NativeBlockState other); + + boolean isSameBlockType(NativeBlockState other); + + boolean hasBlockEntity(); + + NativeBlockState updateFromNeighbourShapes(NativeWorld world, NativePosition position); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunk.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunk.java new file mode 100644 index 0000000000..8af904f0d6 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunk.java @@ -0,0 +1,71 @@ +/* + * 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.internal.wna; + +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; + +import javax.annotation.Nullable; + +/** + * Represents a chunk in the world. Made of {@link NativeChunkSection PlatformChunkSections}. + */ +public interface NativeChunk { + NativeWorld getWorld(); + + boolean isTicking(); + + NativePosition getWorldPos(int offsetX, int offsetY, int offsetZ); + + NativeBlockState getBlockState(NativePosition blockPos); + + @Nullable + NativeBlockState setBlockState(NativePosition blockPos, NativeBlockState newState, boolean update); + + void markSectionChanged(int index, ChunkSectionPosSet changed); + + void updateHeightmaps(); + + void updateLightingForSectionAirChange(int index, boolean onlyAir); + + void removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ); + + void initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState); + + /** + * Get the chunk section at the given index. + * + * @param index the index, from 0 to the max height divided by 16 + * @return the chunk section + */ + NativeChunkSection getChunkSection(int index); + + /** + * Replaces a chunk section in the given chunk. This method is also responsible for updating heightmaps + * and creating block entities, to keep consistency with {@link #setBlockState(NativePosition, NativeBlockState, boolean)} + * (the method we used to use). This is usually easily done by calling + * {@link WNASharedImpl#postChunkSectionReplacement(NativeChunk, int, NativeChunkSection, NativeChunkSection, ChunkSectionPosSet)}. + * + * @param index the index, from 0 to the max height divided by 16 + * @param section the new chunk section + * @param modifiedBlocks the set of modified blocks + * @return the old chunk section + */ + NativeChunkSection setChunkSection(int index, NativeChunkSection section, ChunkSectionPosSet modifiedBlocks); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunkSection.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunkSection.java new file mode 100644 index 0000000000..5e93e057cd --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeChunkSection.java @@ -0,0 +1,68 @@ +/* + * 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.internal.wna; + +import com.sk89q.worldedit.world.registry.BlockMaterial; + +/** + * Represents a 16x16x16 section of a chunk. + * + *

+ * Mapped onto the platform representation of a chunk section. However, unlike the platform representation, this + * interface is not thread-safe, as it is intended to be used in a single-threaded context. + * Internally this uses the platform representation's unsafe methods. + *

+ */ +public interface NativeChunkSection { + /** + * Set a block in the section. + * + * @param i the x-coordinate, 0-15 + * @param j the y-coordinate, 0-15 + * @param k the z-coordinate, 0-15 + * @param blockState the block state + * @return the old block state + */ + NativeBlockState getThenSetBlock(int i, int j, int k, NativeBlockState blockState); + + /** + * Get a block in the section. + * + * @param i the x-coordinate, 0-15 + * @param j the y-coordinate, 0-15 + * @param k the z-coordinate, 0-15 + * @return the block state + */ + NativeBlockState getBlock(int i, int j, int k); + + /** + * Get if this section is made of only air (specifically, {@link BlockMaterial#isAir()}). + * + * @return true if the section is only air + */ + boolean isOnlyAir(); + + /** + * Copy the section. + * + * @return the copy + */ + NativeChunkSection copy(); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativePosition.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativePosition.java new file mode 100644 index 0000000000..02ae2ebcb7 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativePosition.java @@ -0,0 +1,31 @@ +/* + * 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.internal.wna; + +/** + * The equivalent of {@link com.sk89q.worldedit.math.BlockVector3}, but in the platform's base. + */ +public interface NativePosition { + int x(); + + int y(); + + int z(); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeWorld.java new file mode 100644 index 0000000000..c287b0afb9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/NativeWorld.java @@ -0,0 +1,56 @@ +/* + * 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.internal.wna; + +import org.enginehub.linbus.tree.LinCompoundTag; + +/** + * The equivalent of {@link com.sk89q.worldedit.world.World}, but in the platform's base. + */ +public interface NativeWorld { + /** + * Hacky way to get the adapter. Ideally would be part of an internal platform API or something. + * + * @return the adapter + */ + NativeAdapter getAdapter(); + + int getSectionIndex(int y); + + int getYForSectionIndex(int index); + + NativeChunk getChunk(int chunkX, int chunkZ); + + void notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); + + void markBlockChanged(NativePosition pos); + + void updateLightingForBlock(NativePosition position); + + boolean updateTileEntity(NativePosition position, LinCompoundTag tag); + + void notifyNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState, boolean events); + + void updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); + + void updateNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit, boolean events); + + void onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WNASharedImpl.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WNASharedImpl.java new file mode 100644 index 0000000000..2abda4346f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WNASharedImpl.java @@ -0,0 +1,204 @@ +/* + * 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.internal.wna; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTagType; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Shared implementation bits of {@link NativeWorld}. Platforms may call into this to use common code. + */ +public class WNASharedImpl { + public static > boolean setBlock( + NativeWorld nativeWorld, BlockVector3 position, B block, SideEffectSet sideEffects + ) throws WorldEditException { + checkNotNull(position); + checkNotNull(block); + + // First set the block + NativeChunk chunk = nativeWorld.getChunk(position.x() >> 4, position.z() >> 4); + NativePosition pos = nativeWorld.getAdapter().newBlockPos(position); + NativeBlockState old = chunk.getBlockState(pos); + NativeBlockState newState = nativeWorld.getAdapter().toNative(block.toImmutableState()); + // change block prior to placing if it should be fixed + if (sideEffects.shouldApply(SideEffect.VALIDATION)) { + newState = newState.updateFromNeighbourShapes(nativeWorld, pos); + } + NativeBlockState lastValue = chunk.setBlockState(pos, newState, sideEffects.shouldApply(SideEffect.UPDATE)); + boolean successful = lastValue != null; + + // Create the TileEntity + if (successful || old == newState) { + if (block instanceof BaseBlock baseBlock) { + successful |= updateTileEntity(nativeWorld, baseBlock.getNbt(), pos); + } + } + + if (successful) { + if (sideEffects.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { + nativeWorld.updateLightingForBlock(pos); + } + markAndNotifyBlock(nativeWorld, pos, chunk, old, newState, sideEffects); + } + + return successful; + } + + public static boolean updateTileEntity(NativeWorld nativeWorld, LinCompoundTag tag, NativePosition pos) { + if (tag == null) { + return false; + } + LinCompoundTag.Builder tagBuilder = tag.toBuilder() + .putInt("x", pos.x()) + .putInt("y", pos.y()) + .putInt("z", pos.z()); + String nbtId = extractNbtId(tag); + if (!nbtId.isBlank()) { + tagBuilder.putString("id", nbtId); + } + tag = tagBuilder.build(); + + // update if TE changed as well + return nativeWorld.updateTileEntity(pos, tag); + } + + public static String extractNbtId(LinCompoundTag tag) { + LinStringTag idTag = tag.findTag("id", LinTagType.stringTag()); + return idTag != null ? idTag.value() : ""; + } + + public static void applySideEffects( + NativeWorld nativeWorld, SideEffectSet sideEffectSet, BlockVector3 position, BlockState previousType + ) { + NativePosition pos = nativeWorld.getAdapter().newBlockPos(position); + NativeChunk chunk = nativeWorld.getChunk(position.x() >> 4, position.z() >> 4); + NativeBlockState oldData = nativeWorld.getAdapter().toNative(previousType); + NativeBlockState newData = chunk.getBlockState(pos); + + applySideEffectsNoLookups(nativeWorld, chunk, sideEffectSet, pos, oldData, newData); + } + + public static void applySideEffectsNoLookups( + NativeWorld nativeWorld, NativeChunk chunk, SideEffectSet sideEffectSet, NativePosition pos, + NativeBlockState oldData, NativeBlockState newData + ) { + if (sideEffectSet.shouldApply(SideEffect.UPDATE)) { + nativeWorld.updateBlock(pos, oldData, newData); + } + + if (sideEffectSet.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { + nativeWorld.updateLightingForBlock(pos); + } + + markAndNotifyBlock(nativeWorld, pos, chunk, oldData, newData, sideEffectSet); + } + + /** + * This is a heavily modified function stripped from MC to apply WorldEdit-modifications. + * + *

+ * See NeoForge's Level.markAndNotifyBlock + *

+ */ + public static void markAndNotifyBlock( + NativeWorld nativeWorld, NativePosition pos, NativeChunk chunk, NativeBlockState oldState, NativeBlockState newState, + SideEffectSet sideEffectSet + ) { + // Removed redundant branches + + if (chunk.isTicking()) { + if (sideEffectSet.shouldApply(SideEffect.ENTITY_AI)) { + nativeWorld.notifyBlockUpdate(pos, oldState, newState); + } else if (sideEffectSet.shouldApply(SideEffect.NETWORK)) { + // If we want to skip entity AI, just mark the block for sending + nativeWorld.markBlockChanged(pos); + } + } + + if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { + nativeWorld.notifyNeighbors(pos, oldState, newState, sideEffectSet.shouldApply(SideEffect.EVENTS)); + } + + // Make connection updates optional + if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { + nativeWorld.updateNeighbors(pos, oldState, newState, 512, sideEffectSet.shouldApply(SideEffect.EVENTS)); + } + + // Seems used only for PoI updates + if (sideEffectSet.shouldApply(SideEffect.POI_UPDATE)) { + nativeWorld.onBlockStateChange(pos, oldState, newState); + } + } + + /** + * After a chunk section replacement, this function can be called to update the heightmaps, block entities, etc. + * to keep consistency with {@link NativeChunk#setBlockState(NativePosition, NativeBlockState, boolean)}. Doing this allows + * skipping redundant updates caused by multiple set calls, and filtering out unwanted side effects. + * + * @param chunk the chunk + * @param index the replaced section index + * @param oldSection the old section + * @param newSection the new section + * @param modifiedBlocks the set of modified blocks + */ + public static void postChunkSectionReplacement( + NativeChunk chunk, int index, NativeChunkSection oldSection, NativeChunkSection newSection, + ChunkSectionPosSet modifiedBlocks + ) { + modifiedBlocks.forEach((secX, secY, secZ) -> { + NativeBlockState oldState = oldSection.getBlock(secX, secY, secZ); + NativeBlockState newState = newSection.getBlock(secX, secY, secZ); + int chunkY = chunk.getWorld().getYForSectionIndex(index) + secY; + // We skip heightmaps, they're optimized at a higher level to a single call. + + // We skip onRemove here, will call in UPDATE side effect if necessary. + + if (!oldState.isSameBlockType(newState) && oldState.hasBlockEntity()) { + chunk.removeSectionBlockEntity(secX, chunkY, secZ); + } + + // We skip onPlace here, will call in UPDATE side effect if necessary. + + if (newState.hasBlockEntity()) { + chunk.initializeBlockEntity(secX, chunkY, secZ, newState); + } + }); + + boolean wasOnlyAir = oldSection.isOnlyAir(); + boolean onlyAir = newSection.isOnlyAir(); + if (wasOnlyAir != onlyAir) { + chunk.updateLightingForSectionAirChange(index, onlyAir); + } + } + + private WNASharedImpl() { + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java deleted file mode 100644 index 52d64cb130..0000000000 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/WorldNativeAccess.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.internal.wna; - -import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; -import com.sk89q.worldedit.world.block.BlockStateHolder; -import org.enginehub.linbus.tree.LinCompoundTag; - -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Natively access and perform operations on the world. - * - * @param the native chunk type - * @param the native block state type - * @param the native position type - */ -public interface WorldNativeAccess { - - default > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { - checkNotNull(position); - checkNotNull(block); - setCurrentSideEffectSet(sideEffects); - - int x = position.x(); - int y = position.y(); - int z = position.z(); - - // First set the block - NC chunk = getChunk(x >> 4, z >> 4); - NP pos = getPosition(x, y, z); - NBS old = getBlockState(chunk, pos); - NBS newState = toNative(block.toImmutableState()); - // change block prior to placing if it should be fixed - if (sideEffects.shouldApply(SideEffect.VALIDATION)) { - newState = getValidBlockForPosition(newState, pos); - } - NBS lastValue = setBlockState(chunk, pos, newState); - boolean successful = lastValue != null; - - // Create the TileEntity - if (successful || old == newState) { - if (block instanceof BaseBlock baseBlock) { - LinCompoundTag tag = baseBlock.getNbt(); - if (tag != null) { - LinCompoundTag.Builder tagBuilder = tag.toBuilder() - .putInt("x", position.x()) - .putInt("y", position.y()) - .putInt("z", position.z()); - if (!baseBlock.getNbtId().isBlank()) { - tagBuilder.putString("id", baseBlock.getNbtId()); - } - tag = tagBuilder.build(); - - // update if TE changed as well - successful = updateTileEntity(pos, tag); - } - } - } - - if (successful) { - if (sideEffects.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { - updateLightingForBlock(pos); - } - markAndNotifyBlock(pos, chunk, old, newState, sideEffects); - } - - return successful; - } - - default void applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - setCurrentSideEffectSet(sideEffectSet); - NP pos = getPosition(position.x(), position.y(), position.z()); - NC chunk = getChunk(position.x() >> 4, position.z() >> 4); - NBS oldData = toNative(previousType); - NBS newData = getBlockState(chunk, pos); - - if (sideEffectSet.shouldApply(SideEffect.UPDATE)) { - updateBlock(pos, oldData, newData); - } - - if (sideEffectSet.getState(SideEffect.LIGHTING) == SideEffect.State.ON) { - updateLightingForBlock(pos); - } - - markAndNotifyBlock(pos, chunk, oldData, newData, sideEffectSet); - } - - // state-keeping functions for WNA - // may be thread-unsafe, as this is single-threaded code - - /** - * Receive the current side-effect set from the high level call. - * - *

- * This allows the implementation to branch on the side-effects internally. - *

- * - * @param sideEffectSet the set of side-effects - */ - default void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - } - - // access functions - - NC getChunk(int x, int z); - - NBS toNative(BlockState state); - - NBS getBlockState(NC chunk, NP position); - - @Nullable - NBS setBlockState(NC chunk, NP position, NBS state); - - NBS getValidBlockForPosition(NBS block, NP position); - - NP getPosition(int x, int y, int z); - - void updateLightingForBlock(NP position); - - boolean updateTileEntity(NP position, LinCompoundTag tag); - - void notifyBlockUpdate(NC chunk, NP position, NBS oldState, NBS newState); - - boolean isChunkTicking(NC chunk); - - void markBlockChanged(NC chunk, NP position); - - void notifyNeighbors(NP pos, NBS oldState, NBS newState); - - default void updateBlock(NP pos, NBS oldState, NBS newState) { - } - - void updateNeighbors(NP pos, NBS oldState, NBS newState, int recursionLimit); - - void onBlockStateChange(NP pos, NBS oldState, NBS newState); - - /** - * This is a heavily modified function stripped from MC to apply WorldEdit-modifications. - * - *

- * See Forge's World.markAndNotifyBlock - *

- */ - default void markAndNotifyBlock(NP pos, NC chunk, NBS oldState, NBS newState, SideEffectSet sideEffectSet) { - NBS blockState1 = getBlockState(chunk, pos); - if (blockState1 != newState) { - return; - } - - // Remove redundant branches - if (isChunkTicking(chunk)) { - if (sideEffectSet.shouldApply(SideEffect.ENTITY_AI)) { - notifyBlockUpdate(chunk, pos, oldState, newState); - } else if (sideEffectSet.shouldApply(SideEffect.NETWORK)) { - // If we want to skip entity AI, just mark the block for sending - markBlockChanged(chunk, pos); - } - } - - if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { - notifyNeighbors(pos, oldState, newState); - } - - // Make connection updates optional - if (sideEffectSet.shouldApply(SideEffect.NEIGHBORS)) { - updateNeighbors(pos, oldState, newState, 512); - } - - // Seems used only for PoI updates - if (sideEffectSet.shouldApply(SideEffect.POI_UPDATE)) { - onBlockStateChange(pos, oldState, blockState1); - } - } - -} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java index 182a404487..c7ea6d325c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/wna/package-info.java @@ -21,7 +21,8 @@ * "WNA", or WorldEdit Native Access. * *

- * Contains internal helper functions for sharing code between platforms. + * Contains internal helper functions for sharing code between platforms. "Native*" interfaces are wrapped around or + * mixed in to the native structures. *

*/ package com.sk89q.worldedit.internal.wna; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java index a051594a2c..2332e4bfb9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.math; +import com.sk89q.worldedit.internal.wna.NativePosition; import com.sk89q.worldedit.math.transform.AffineTransform; import java.util.Comparator; @@ -32,7 +33,8 @@ /** * An immutable 3-dimensional vector. */ -public record BlockVector3(int x, int y, int z) { +// This implements the internal `NativePosition` interface so that Bukkit can use this as its native position type +public record BlockVector3(int x, int y, int z) implements NativePosition { public static final BlockVector3 ZERO = new BlockVector3(0, 0, 0); public static final BlockVector3 UNIT_X = new BlockVector3(1, 0, 0); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java index 27ecf063a1..1c23e2b1d5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java @@ -134,6 +134,7 @@ public void load() { extendedYLimit = getBool("extended-y-limit", extendedYLimit); setDefaultLocaleName(getString("default-locale", defaultLocaleName)); commandBlockSupport = getBool("command-block-support", commandBlockSupport); + chunkSectionEditing = getBool("chunk-section-editing", chunkSectionEditing); LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15)); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java index f2bea3c107..e1e6768b5b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java @@ -131,6 +131,8 @@ public void load() { setDefaultLocaleName(config.getString("default-locale", defaultLocaleName)); commandBlockSupport = config.getBoolean("command-block-support", false); + + chunkSectionEditing = config.getBoolean("chunk-section-editing", chunkSectionEditing); } public void unload() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java index 14c607fa4b..07cb56177d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java @@ -26,6 +26,7 @@ import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.internal.wna.NativeWorld; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -49,6 +50,13 @@ public abstract class AbstractWorld implements World { private final PriorityQueue effectQueue = new PriorityQueue<>(); private int taskId = -1; + /** + * {@return the native interface to the world} Internal use only. + */ + public @Nullable NativeWorld getNativeInterface() { + return null; + } + @Override public boolean useItem(BlockVector3 position, BaseItem item, Direction face) { return false; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java index 609f7c7e26..a870b3dda6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BaseBlock.java @@ -21,12 +21,11 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.blocks.TileEntityBlock; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.concurrency.LazyReference; import org.enginehub.linbus.format.snbt.LinStringIO; import org.enginehub.linbus.tree.LinCompoundTag; -import org.enginehub.linbus.tree.LinStringTag; -import org.enginehub.linbus.tree.LinTagType; import java.util.Map; import java.util.Objects; @@ -120,8 +119,7 @@ public String getNbtId() { if (nbtData == null) { return ""; } - LinStringTag idTag = nbtData.getValue().findTag("id", LinTagType.stringTag()); - return idTag != null ? idTag.value() : ""; + return WNASharedImpl.extractNbtId(nbtData.getValue()); } @Nullable diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java index e5c2e7882b..ecef7f062a 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java @@ -285,4 +285,5 @@ public static Actor adaptCommandSource(CommandSourceStack commandSourceStack) { return new FabricCommandSender(commandSourceStack); } + } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java index 259d00f356..f0b51fccb7 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java @@ -38,11 +38,12 @@ import com.sk89q.worldedit.fabric.internal.ExtendedMinecraftServer; import com.sk89q.worldedit.fabric.internal.FabricEntity; import com.sk89q.worldedit.fabric.internal.FabricServerLevelDelegateProxy; -import com.sk89q.worldedit.fabric.internal.FabricWorldNativeAccess; import com.sk89q.worldedit.fabric.internal.NBTConverter; import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -149,7 +150,6 @@ private static ResourceLocation getDimensionRegistryKey(Level world) { } private final WeakReference worldRef; - private final FabricWorldNativeAccess worldNativeAccess; /** * Construct a new world. @@ -159,7 +159,11 @@ private static ResourceLocation getDimensionRegistryKey(Level world) { FabricWorld(Level world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.worldNativeAccess = new FabricWorldNativeAccess(worldRef); + } + + @Override + public NativeWorld getNativeInterface() { + return (NativeWorld) getWorld(); } /** @@ -199,12 +203,12 @@ public Path getStoragePath() { @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { clearContainerBlockContents(position); - return worldNativeAccess.setBlock(position, block, sideEffects); + return WNASharedImpl.setBlock(getNativeInterface(), position, block, sideEffects); } @Override public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); + WNASharedImpl.applySideEffects(getNativeInterface(), sideEffectSet, position, previousType); return Sets.intersection(FabricWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); } diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricNativeAdapter.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricNativeAdapter.java new file mode 100644 index 0000000000..31466974f2 --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricNativeAdapter.java @@ -0,0 +1,50 @@ +/* + * 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.fabric.internal; + +import com.sk89q.worldedit.fabric.FabricAdapter; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; + +public final class FabricNativeAdapter implements NativeAdapter { + public static final FabricNativeAdapter INSTANCE = new FabricNativeAdapter(); + + private FabricNativeAdapter() { + } + + @Override + public NativeBlockState toNative(BlockState state) { + return (NativeBlockState) FabricAdapter.adapt(state); + } + + @Override + public BlockState fromNative(NativeBlockState state) { + return FabricAdapter.adapt((net.minecraft.world.level.block.state.BlockState) state); + } + + @Override + public NativePosition newBlockPos(BlockVector3 pos) { + return (NativePosition) new BlockPos(pos.x(), pos.y(), pos.z()); + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java deleted file mode 100644 index 55695ac166..0000000000 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/internal/FabricWorldNativeAccess.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.fabric.internal; - -import com.sk89q.worldedit.fabric.FabricAdapter; -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.LevelChunk; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.annotation.Nullable; - -public class FabricWorldNativeAccess implements WorldNativeAccess { - private static final int UPDATE = 1; - private static final int NOTIFY = 2; - - private final WeakReference world; - private SideEffectSet sideEffectSet; - - public FabricWorldNativeAccess(WeakReference world) { - this.world = world; - } - - private Level getWorld() { - return Objects.requireNonNull(world.get(), "The reference to the world was lost"); - } - - @Override - public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - this.sideEffectSet = sideEffectSet; - } - - @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); - } - - @Override - public BlockState toNative(com.sk89q.worldedit.world.block.BlockState state) { - int stateId = BlockStateIdAccess.getBlockStateId(state); - return BlockStateIdAccess.isValidInternalId(stateId) - ? Block.stateById(stateId) - : FabricAdapter.adapt(state); - } - - @Override - public BlockState getBlockState(LevelChunk chunk, BlockPos position) { - return chunk.getBlockState(position); - } - - @Nullable - @Override - public BlockState setBlockState(LevelChunk chunk, BlockPos position, BlockState state) { - if (chunk instanceof ExtendedChunk) { - return ((ExtendedChunk) chunk).setBlockState( - position, state, false, sideEffectSet.shouldApply(SideEffect.UPDATE) - ); - } - return chunk.setBlockState(position, state, false); - } - - @Override - public BlockState getValidBlockForPosition(BlockState block, BlockPos position) { - return Block.updateFromNeighbourShapes(block, getWorld(), position); - } - - @Override - public BlockPos getPosition(int x, int y, int z) { - return new BlockPos(x, y, z); - } - - @Override - public void updateLightingForBlock(BlockPos position) { - getWorld().getChunkSource().getLightEngine().checkBlock(position); - } - - @Override - public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { - CompoundTag nativeTag = NBTConverter.toNative(tag); - Level level = getWorld(); - BlockEntity tileEntity = level.getChunkAt(position).getBlockEntity(position); - if (tileEntity == null) { - return false; - } - tileEntity.loadWithComponents(nativeTag, level.registryAccess()); - tileEntity.setChanged(); - return true; - } - - @Override - public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, BlockState oldState, BlockState newState) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); - } - } - - @Override - public boolean isChunkTicking(LevelChunk chunk) { - return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); - } - - @Override - public void markBlockChanged(LevelChunk chunk, BlockPos position) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - ((ServerChunkCache) getWorld().getChunkSource()).blockChanged(position); - } - } - - @Override - public void notifyNeighbors(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().blockUpdated(pos, oldState.getBlock()); - if (newState.hasAnalogOutputSignal()) { - getWorld().updateNeighbourForOutputSignal(pos, newState.getBlock()); - } - } - - @Override - public void updateBlock(BlockPos pos, BlockState oldState, BlockState newState) { - Level world = getWorld(); - newState.onPlace(world, pos, oldState, false); - } - - @Override - public void updateNeighbors(BlockPos pos, BlockState oldState, BlockState newState, int recursionLimit) { - Level world = getWorld(); - oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - } - - @Override - public void onBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().onBlockStateChange(pos, oldState, newState); - } -} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeBlockState.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeBlockState.java new file mode 100644 index 0000000000..5204010baa --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeBlockState.java @@ -0,0 +1,63 @@ +/* + * 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.fabric.mixin; + +import com.mojang.serialization.MapCodec; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockState.class) +@Implements(@Interface(iface = NativeBlockState.class, prefix = "nbs$")) +public abstract class MixinNativeBlockState extends BlockBehaviour.BlockStateBase { + protected MixinNativeBlockState(Block owner, Reference2ObjectArrayMap, Comparable> values, MapCodec propertiesCodec) { + super(owner, values, propertiesCodec); + } + + public boolean nbs$isSame(NativeBlockState other) { + return this == other; + } + + public boolean nbs$isSameBlockType(NativeBlockState other) { + return this.getBlock() == ((BlockState) other).getBlock(); + } + + public boolean nbs$hasBlockEntity() { + return super.hasBlockEntity(); + } + + public NativeBlockState nbs$updateFromNeighbourShapes( + NativeWorld world, NativePosition position + ) { + return (NativeBlockState) Block.updateFromNeighbourShapes( + (BlockState) (Object) this, (LevelAccessor) world, (BlockPos) position + ); + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunk.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunk.java new file mode 100644 index 0000000000..8b3621b341 --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunk.java @@ -0,0 +1,183 @@ +/* + * 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.fabric.mixin; + +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.fabric.internal.ExtendedChunk; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; + +@Mixin(LevelChunk.class) +@Implements(@Interface(iface = NativeChunk.class, prefix = "nc$")) +public abstract class MixinNativeChunk extends ChunkAccess { + @Unique + private static final Set HEIGHTMAPS = EnumSet.of( + Heightmap.Types.WORLD_SURFACE, + Heightmap.Types.OCEAN_FLOOR, + Heightmap.Types.MOTION_BLOCKING, + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES + ); + + public MixinNativeChunk(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, long inhabitedTime, @org.jetbrains.annotations.Nullable LevelChunkSection[] sections, @org.jetbrains.annotations.Nullable BlendingData blendingData) { + super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData); + } + + @Shadow + public abstract FullChunkStatus getFullStatus(); + + @Shadow + public abstract BlockState getBlockState(BlockPos pos); + + @Shadow + public abstract @Nullable BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); + + @Shadow + public abstract Level getLevel(); + + @Shadow + public abstract void removeBlockEntity(BlockPos pos); + + @Shadow + public abstract @Nullable BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType); + + @Shadow + public abstract void addAndRegisterBlockEntity(BlockEntity blockEntity); + + @Shadow + protected abstract void updateBlockEntityTicker(T blockEntity); + + @Shadow public abstract void markUnsaved(); + + public NativeWorld nc$getWorld() { + return (NativeWorld) getLevel(); + } + + public boolean nc$isTicking() { + return getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + public NativePosition nc$getWorldPos(int offsetX, int offsetY, int offsetZ) { + return (NativePosition) getPos().getBlockAt(offsetX, offsetY, offsetZ); + } + + public NativeBlockState nc$getBlockState(NativePosition blockPos) { + return (NativeBlockState) getBlockState((BlockPos) blockPos); + } + + public @Nullable NativeBlockState nc$setBlockState(NativePosition blockPos, NativeBlockState newState, boolean update) { + return (NativeBlockState) ((ExtendedChunk) this).setBlockState( + (BlockPos) blockPos, (BlockState) newState, false, update + ); + } + + public void nc$markSectionChanged(int index, ChunkSectionPosSet changed) { + ServerChunkCache serverChunkCache = (ServerChunkCache) getLevel().getChunkSource(); + ChunkHolder holder = serverChunkCache.getVisibleChunkIfPresent(getPos().toLong()); + if (holder != null) { + if (holder.changedBlocksPerSection[index] == null) { + holder.hasChangedSections = true; + holder.changedBlocksPerSection[index] = new ShortOpenHashSet(changed.asSectionPosEncodedShorts()); + } else { + holder.changedBlocksPerSection[index].addAll(changed.asSectionPosEncodedShorts()); + } + // Trick to get the holder into the broadcast set + ((ServerChunkCache) getLevel().getChunkSource()).onChunkReadyToSend(holder); + } + } + + public void nc$updateHeightmaps() { + Heightmap.primeHeightmaps(this, HEIGHTMAPS); + } + + public void nc$updateLightingForSectionAirChange(int index, boolean onlyAir) { + getLevel().getLightEngine().updateSectionStatus( + SectionPos.of(getPos(), getLevel().getSectionYFromSectionIndex(index)), + onlyAir + ); + } + + public void nc$removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ) { + removeBlockEntity(getPos().getBlockAt(chunkX, chunkY, chunkZ)); + } + + @SuppressWarnings("deprecation") + public void nc$initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState) { + BlockPos pos = getPos().getBlockAt(chunkX, chunkY, chunkZ); + BlockEntity blockEntity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + BlockState nativeState = (BlockState) newState; + if (blockEntity == null) { + blockEntity = ((EntityBlock) nativeState.getBlock()).newBlockEntity(pos, nativeState); + if (blockEntity != null) { + this.addAndRegisterBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(nativeState); + this.updateBlockEntityTicker(blockEntity); + } + } + + public NativeChunkSection nc$getChunkSection(int index) { + return (NativeChunkSection) getSection(index); + } + + public NativeChunkSection nc$setChunkSection(int index, NativeChunkSection section, ChunkSectionPosSet modifiedBlocks) { + Preconditions.checkPositionIndex(index, getSectionsCount()); + LevelChunkSection[] chunkSections = getSections(); + var oldSection = (NativeChunkSection) chunkSections[index]; + chunkSections[index] = (LevelChunkSection) section; + WNASharedImpl.postChunkSectionReplacement((NativeChunk) this, index, oldSection, section, modifiedBlocks); + this.markUnsaved(); + return oldSection; + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunkSection.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunkSection.java new file mode 100644 index 0000000000..80b5c67b0b --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeChunkSection.java @@ -0,0 +1,82 @@ +/* + * 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.fabric.mixin; + +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(LevelChunkSection.class) +@Implements(@Interface(iface = NativeChunkSection.class, prefix = "ncs$")) +public abstract class MixinNativeChunkSection { + @Shadow + public abstract net.minecraft.world.level.block.state.BlockState setBlockState( + int x, int y, int z, net.minecraft.world.level.block.state.BlockState state, boolean lock + ); + + @Shadow + public abstract net.minecraft.world.level.block.state.BlockState getBlockState( + int x, int y, int z + ); + + @Final + @Mutable + @Shadow + private PalettedContainer states; + + @Shadow + private PalettedContainerRO> biomes; + + @Shadow + public abstract boolean hasOnlyAir(); + + public boolean ncs$isOnlyAir() { + return hasOnlyAir(); + } + + public NativeBlockState ncs$getThenSetBlock(int i, int j, int k, NativeBlockState blockState) { + BlockState nativeState = (BlockState) blockState; + if (ncs$isOnlyAir() && nativeState.isAir()) { + return blockState; + } + return (NativeBlockState) setBlockState(i, j, k, nativeState, false); + } + + public NativeBlockState ncs$getBlock(int i, int j, int k) { + return (NativeBlockState) getBlockState(i, j, k); + } + + public NativeChunkSection ncs$copy() { + return (NativeChunkSection) new LevelChunkSection( + states.copy(), biomes.copy() + ); + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativePosition.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativePosition.java new file mode 100644 index 0000000000..532176d783 --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativePosition.java @@ -0,0 +1,47 @@ +/* + * 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.fabric.mixin; + +import com.sk89q.worldedit.internal.wna.NativePosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockPos.class) +@Implements(@Interface(iface = NativePosition.class, prefix = "nc$")) +public abstract class MixinNativePosition extends Vec3i { + public MixinNativePosition(int x, int y, int z) { + super(x, y, z); + } + + public int nc$x() { + return getX(); + } + + public int nc$y() { + return getY(); + } + + public int nc$z() { + return getZ(); + } +} diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeWorld.java new file mode 100644 index 0000000000..eeb33e3415 --- /dev/null +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinNativeWorld.java @@ -0,0 +1,137 @@ +/* + * 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.fabric.mixin; + +import com.sk89q.worldedit.fabric.internal.FabricNativeAdapter; +import com.sk89q.worldedit.fabric.internal.NBTConverter; +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.profiling.Profiler; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.storage.WritableLevelData; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ServerLevel.class) +@Implements(@Interface(iface = NativeWorld.class, prefix = "nw$")) +public abstract class MixinNativeWorld extends Level { + public MixinNativeWorld(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, boolean bl, boolean bl2, long l, int i) { + super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i); + } + + public NativeAdapter nw$getAdapter() { + return FabricNativeAdapter.INSTANCE; + } + + public int nw$getSectionIndex(int y) { + return super.getSectionIndex(y); + } + + public int nw$getYForSectionIndex(int index) { + return SectionPos.sectionToBlockCoord(super.getSectionYFromSectionIndex(index)); + } + + public NativeChunk nw$getChunk(int chunkX, int chunkZ) { + return (NativeChunk) getChunk(chunkX, chunkZ); + } + + public void nw$notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + sendBlockUpdated( + (BlockPos) pos, (BlockState) oldState, (BlockState) newState, + Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS + ); + } + + public void nw$markBlockChanged(NativePosition pos) { + ((ServerChunkCache) getChunkSource()).blockChanged((BlockPos) pos); + } + + public void nw$updateLightingForBlock(NativePosition position) { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("updateSkyLightSources"); + getChunk(position.x() >> 4, position.z() >> 4).getSkyLightSources() + .update(this, position.x() & 0xF, position.y(), position.z() & 0xF); + profilerFiller.popPush("queueCheckLight"); + getChunkSource().getLightEngine().checkBlock((BlockPos) position); + profilerFiller.pop(); + } + + public boolean nw$updateTileEntity(NativePosition position, LinCompoundTag tag) { + CompoundTag nativeTag = NBTConverter.toNative(tag); + BlockPos nativePos = (BlockPos) position; + BlockEntity tileEntity = getChunkAt(nativePos).getBlockEntity(nativePos); + if (tileEntity == null) { + return false; + } + tileEntity.loadWithComponents(nativeTag, registryAccess()); + tileEntity.setChanged(); + return true; + } + + public void nw$notifyNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState, boolean events) { + BlockPos nativePos = (BlockPos) pos; + blockUpdated(nativePos, ((BlockState) oldState).getBlock()); + BlockState nativeNewState = (BlockState) newState; + if (nativeNewState.hasAnalogOutputSignal()) { + updateNeighbourForOutputSignal(nativePos, nativeNewState.getBlock()); + } + } + + public void nw$updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + BlockPos nativePos = (BlockPos) pos; + BlockState nativeOldState = (BlockState) oldState; + BlockState nativeNewState = (BlockState) newState; + nativeOldState.onRemove(this, nativePos, nativeNewState, false); + nativeNewState.onPlace(this, nativePos, nativeOldState, false); + } + + public void nw$updateNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit, boolean events + ) { + BlockPos nativePos = (BlockPos) pos; + BlockState nativeOldState = (BlockState) oldState; + BlockState nativeNewState = (BlockState) newState; + nativeOldState.updateIndirectNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateIndirectNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + } + + public void nw$onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + onBlockStateChange((BlockPos) pos, (BlockState) oldState, (BlockState) newState); + } +} diff --git a/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json b/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json index 232b19e65f..b8e6542f12 100644 --- a/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json +++ b/worldedit-fabric/src/main/resources/worldedit-fabric.mixins.json @@ -5,11 +5,14 @@ "mixins": [ "MixinLevelChunkSetBlockHook", "MixinMinecraftServer", + "MixinNativeBlockState", + "MixinNativeChunk", + "MixinNativeChunkSection", + "MixinNativePosition", + "MixinNativeWorld", "MixinServerGamePacketListenerImpl" ], "plugin": "com.sk89q.worldedit.fabric.internal.MixinConfigPlugin", - "server": [ - ], "injectors": { "defaultRequire": 1 } diff --git a/worldedit-fabric/src/main/resources/worldedit.accesswidener b/worldedit-fabric/src/main/resources/worldedit.accesswidener index 88f77ce207..ee03b2c584 100644 --- a/worldedit-fabric/src/main/resources/worldedit.accesswidener +++ b/worldedit-fabric/src/main/resources/worldedit.accesswidener @@ -13,3 +13,8 @@ accessible field net/minecraft/world/level/storage/PrimaryLevelData worldOptions mutable field net/minecraft/world/level/storage/PrimaryLevelData worldOptions Lnet/minecraft/world/level/levelgen/WorldOptions; accessible method net/minecraft/network/protocol/game/ClientboundBlockEntityDataPacket (Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V + +# Ability to mark sections changed +accessible method net/minecraft/server/level/ServerChunkCache getVisibleChunkIfPresent (J)Lnet/minecraft/server/level/ChunkHolder; +accessible field net/minecraft/server/level/ChunkHolder changedBlocksPerSection [Lit/unimi/dsi/fastutil/shorts/ShortSet; +accessible field net/minecraft/server/level/ChunkHolder hasChangedSections Z diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeAdapter.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeAdapter.java index 8706822486..3e60a74372 100644 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeAdapter.java +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeAdapter.java @@ -67,7 +67,6 @@ import static com.google.common.base.Preconditions.checkNotNull; public final class NeoForgeAdapter { - private NeoForgeAdapter() { } @@ -272,4 +271,5 @@ public static Actor adaptCommandSource(CommandSourceStack commandSourceStack) { return new NeoForgeCommandSender(commandSourceStack); } + } diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java index 1e764406f5..209c9f561b 100644 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/NeoForgeWorld.java @@ -38,13 +38,14 @@ import com.sk89q.worldedit.function.mask.AbstractExtentMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.neoforge.internal.NBTConverter; import com.sk89q.worldedit.neoforge.internal.NeoForgeEntity; import com.sk89q.worldedit.neoforge.internal.NeoForgeServerLevelDelegateProxy; -import com.sk89q.worldedit.neoforge.internal.NeoForgeWorldNativeAccess; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; @@ -145,7 +146,6 @@ private static ResourceLocation getDimensionRegistryKey(ServerLevel world) { } private final WeakReference worldRef; - private final NeoForgeWorldNativeAccess nativeAccess; /** * Construct a new world. @@ -155,7 +155,11 @@ private static ResourceLocation getDimensionRegistryKey(ServerLevel world) { NeoForgeWorld(ServerLevel world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.nativeAccess = new NeoForgeWorldNativeAccess(worldRef); + } + + @Override + public NativeWorld getNativeInterface() { + return (NativeWorld) getWorld(); } /** @@ -192,12 +196,12 @@ public Path getStoragePath() { @Override public > boolean setBlock(BlockVector3 position, B block, SideEffectSet sideEffects) throws WorldEditException { clearContainerBlockContents(position); - return nativeAccess.setBlock(position, block, sideEffects); + return WNASharedImpl.setBlock(getNativeInterface(), position, block, sideEffects); } @Override public Set applySideEffects(BlockVector3 position, BlockState previousType, SideEffectSet sideEffectSet) { - nativeAccess.applySideEffects(position, previousType, sideEffectSet); + WNASharedImpl.applySideEffects(getNativeInterface(), sideEffectSet, position, previousType); return Sets.intersection(NeoForgeWorldEdit.inst.getPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply()); } diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeNativeAdapter.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeNativeAdapter.java new file mode 100644 index 0000000000..86dfb97a3c --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeNativeAdapter.java @@ -0,0 +1,51 @@ +/* + * 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.neoforge.internal; + +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.neoforge.NeoForgeAdapter; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; + +public class NeoForgeNativeAdapter implements NativeAdapter { + + public static final NeoForgeNativeAdapter INSTANCE = new NeoForgeNativeAdapter(); + + private NeoForgeNativeAdapter() { + } + + @Override + public NativeBlockState toNative(BlockState state) { + return (NativeBlockState) NeoForgeAdapter.adapt(state); + } + + @Override + public BlockState fromNative(NativeBlockState state) { + return NeoForgeAdapter.adapt((net.minecraft.world.level.block.state.BlockState) state); + } + + @Override + public NativePosition newBlockPos(BlockVector3 pos) { + return (NativePosition) new BlockPos(pos.x(), pos.y(), pos.z()); + } +} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeWorldNativeAccess.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeWorldNativeAccess.java deleted file mode 100644 index 9c4ffd35db..0000000000 --- a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/internal/NeoForgeWorldNativeAccess.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.neoforge.internal; - -import com.sk89q.worldedit.internal.block.BlockStateIdAccess; -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.neoforge.NeoForgeAdapter; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.annotation.Nullable; - -public class NeoForgeWorldNativeAccess implements WorldNativeAccess { - private static final int UPDATE = 1; - private static final int NOTIFY = 2; - - private final WeakReference world; - private SideEffectSet sideEffectSet; - - public NeoForgeWorldNativeAccess(WeakReference world) { - this.world = world; - } - - private ServerLevel getWorld() { - return Objects.requireNonNull(world.get(), "The reference to the world was lost"); - } - - @Override - public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - this.sideEffectSet = sideEffectSet; - } - - @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); - } - - @Override - public BlockState toNative(com.sk89q.worldedit.world.block.BlockState state) { - int stateId = BlockStateIdAccess.getBlockStateId(state); - return BlockStateIdAccess.isValidInternalId(stateId) - ? Block.stateById(stateId) - : NeoForgeAdapter.adapt(state); - } - - @Override - public BlockState getBlockState(LevelChunk chunk, BlockPos position) { - return chunk.getBlockState(position); - } - - @Nullable - @Override - public BlockState setBlockState(LevelChunk chunk, BlockPos position, BlockState state) { - if (chunk instanceof ExtendedChunk) { - return ((ExtendedChunk) chunk).setBlockState( - position, state, false, sideEffectSet.shouldApply(SideEffect.UPDATE) - ); - } - return chunk.setBlockState(position, state, false); - } - - @Override - public BlockState getValidBlockForPosition(BlockState block, BlockPos position) { - return Block.updateFromNeighbourShapes(block, getWorld(), position); - } - - @Override - public BlockPos getPosition(int x, int y, int z) { - return new BlockPos(x, y, z); - } - - @Override - public void updateLightingForBlock(BlockPos position) { - getWorld().getChunkSource().getLightEngine().checkBlock(position); - } - - @Override - public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { - net.minecraft.nbt.CompoundTag nativeTag = NBTConverter.toNative(tag); - Level level = getWorld(); - BlockEntity tileEntity = level.getChunkAt(position).getBlockEntity(position); - if (tileEntity == null) { - return false; - } - tileEntity.loadWithComponents(nativeTag, level.registryAccess()); - tileEntity.setChanged(); - return true; - } - - @Override - public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, BlockState oldState, BlockState newState) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); - } - } - - @Override - public boolean isChunkTicking(LevelChunk chunk) { - return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); - } - - @Override - public void markBlockChanged(LevelChunk chunk, BlockPos position) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().getChunkSource().blockChanged(position); - } - } - - @Override - public void notifyNeighbors(BlockPos pos, BlockState oldState, BlockState newState) { - ServerLevel world = getWorld(); - if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { - world.updateNeighborsAt(pos, oldState.getBlock()); - } else { - // Bypasses events currently, watch for changes... - world.updateNeighborsAt(pos, oldState.getBlock(), ExperimentalRedstoneUtils.initialOrientation( - world, null, null - )); - } - if (newState.hasAnalogOutputSignal()) { - world.updateNeighbourForOutputSignal(pos, newState.getBlock()); - } - } - - @Override - public void updateBlock(BlockPos pos, BlockState oldState, BlockState newState) { - ServerLevel world = getWorld(); - newState.onPlace(world, pos, oldState, false); - } - - @Override - public void updateNeighbors(BlockPos pos, BlockState oldState, BlockState newState, int recursionLimit) { - ServerLevel world = getWorld(); - oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - } - - @Override - public void onBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().onBlockStateChange(pos, oldState, newState); - newState.onBlockStateChange(getWorld(), pos, oldState); - } -} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeBlockState.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeBlockState.java new file mode 100644 index 0000000000..44bf463b0f --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeBlockState.java @@ -0,0 +1,63 @@ +/* + * 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.neoforge.mixin; + +import com.mojang.serialization.MapCodec; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockState.class) +@Implements(@Interface(iface = NativeBlockState.class, prefix = "nbs$")) +public abstract class MixinNativeBlockState extends BlockBehaviour.BlockStateBase { + protected MixinNativeBlockState(Block owner, Reference2ObjectArrayMap, Comparable> values, MapCodec propertiesCodec) { + super(owner, values, propertiesCodec); + } + + public boolean nbs$isSame(NativeBlockState other) { + return this == other; + } + + public boolean nbs$isSameBlockType(NativeBlockState other) { + return this.getBlock() == ((BlockState) other).getBlock(); + } + + public boolean nbs$hasBlockEntity() { + return super.hasBlockEntity(); + } + + public NativeBlockState nbs$updateFromNeighbourShapes( + NativeWorld world, NativePosition position + ) { + return (NativeBlockState) Block.updateFromNeighbourShapes( + (BlockState) (Object) this, (LevelAccessor) world, (BlockPos) position + ); + } +} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeChunk.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeChunk.java new file mode 100644 index 0000000000..3b6e115506 --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeChunk.java @@ -0,0 +1,183 @@ +/* + * 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.neoforge.mixin; + +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.internal.util.collection.ChunkSectionPosSet; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.internal.wna.WNASharedImpl; +import com.sk89q.worldedit.neoforge.internal.ExtendedChunk; +import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; + +@Mixin(LevelChunk.class) +@Implements(@Interface(iface = NativeChunk.class, prefix = "nc$")) +public abstract class MixinNativeChunk extends ChunkAccess { + @Unique + private static final Set HEIGHTMAPS = EnumSet.of( + Heightmap.Types.WORLD_SURFACE, + Heightmap.Types.OCEAN_FLOOR, + Heightmap.Types.MOTION_BLOCKING, + Heightmap.Types.MOTION_BLOCKING_NO_LEAVES + ); + + public MixinNativeChunk(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, long inhabitedTime, @org.jetbrains.annotations.Nullable LevelChunkSection[] sections, @org.jetbrains.annotations.Nullable BlendingData blendingData) { + super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData); + } + + @Shadow + public abstract FullChunkStatus getFullStatus(); + + @Shadow + public abstract BlockState getBlockState(BlockPos pos); + + @Shadow + public abstract @Nullable BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); + + @Shadow + public abstract Level getLevel(); + + @Shadow + public abstract void removeBlockEntity(BlockPos pos); + + @Shadow + public abstract @Nullable BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType); + + @Shadow + public abstract void addAndRegisterBlockEntity(BlockEntity blockEntity); + + @Shadow + protected abstract void updateBlockEntityTicker(T blockEntity); + + @Shadow public abstract void markUnsaved(); + + public NativeWorld nc$getWorld() { + return (NativeWorld) getLevel(); + } + + public boolean nc$isTicking() { + return getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + public NativePosition nc$getWorldPos(int offsetX, int offsetY, int offsetZ) { + return (NativePosition) getPos().getBlockAt(offsetX, offsetY, offsetZ); + } + + public NativeBlockState nc$getBlockState(NativePosition blockPos) { + return (NativeBlockState) getBlockState((BlockPos) blockPos); + } + + public @Nullable NativeBlockState nc$setBlockState(NativePosition blockPos, NativeBlockState newState, boolean update) { + return (NativeBlockState) ((ExtendedChunk) this).setBlockState( + (BlockPos) blockPos, (BlockState) newState, false, update + ); + } + + public void nc$markSectionChanged(int index, ChunkSectionPosSet changed) { + ServerChunkCache serverChunkCache = (ServerChunkCache) getLevel().getChunkSource(); + ChunkHolder holder = serverChunkCache.getVisibleChunkIfPresent(getPos().toLong()); + if (holder != null) { + if (holder.changedBlocksPerSection[index] == null) { + holder.hasChangedSections = true; + holder.changedBlocksPerSection[index] = new ShortOpenHashSet(changed.asSectionPosEncodedShorts()); + } else { + holder.changedBlocksPerSection[index].addAll(changed.asSectionPosEncodedShorts()); + } + // Trick to get the holder into the broadcast set + ((ServerChunkCache) getLevel().getChunkSource()).onChunkReadyToSend(holder); + } + } + + public void nc$updateHeightmaps() { + Heightmap.primeHeightmaps(this, HEIGHTMAPS); + } + + public void nc$updateLightingForSectionAirChange(int index, boolean onlyAir) { + getLevel().getLightEngine().updateSectionStatus( + SectionPos.of(getPos(), getLevel().getSectionYFromSectionIndex(index)), + onlyAir + ); + } + + public void nc$removeSectionBlockEntity(int chunkX, int chunkY, int chunkZ) { + removeBlockEntity(getPos().getBlockAt(chunkX, chunkY, chunkZ)); + } + + @SuppressWarnings("deprecation") + public void nc$initializeBlockEntity(int chunkX, int chunkY, int chunkZ, NativeBlockState newState) { + BlockPos pos = getPos().getBlockAt(chunkX, chunkY, chunkZ); + BlockEntity blockEntity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK); + BlockState nativeState = (BlockState) newState; + if (blockEntity == null) { + blockEntity = ((EntityBlock) nativeState.getBlock()).newBlockEntity(pos, nativeState); + if (blockEntity != null) { + this.addAndRegisterBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(nativeState); + this.updateBlockEntityTicker(blockEntity); + } + } + + public NativeChunkSection nc$getChunkSection(int index) { + return (NativeChunkSection) getSection(index); + } + + public NativeChunkSection nc$setChunkSection(int index, NativeChunkSection section, ChunkSectionPosSet modifiedBlocks) { + Preconditions.checkPositionIndex(index, getSectionsCount()); + LevelChunkSection[] chunkSections = getSections(); + var oldSection = (NativeChunkSection) chunkSections[index]; + chunkSections[index] = (LevelChunkSection) section; + WNASharedImpl.postChunkSectionReplacement((NativeChunk) this, index, oldSection, section, modifiedBlocks); + markUnsaved(); + return oldSection; + } +} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeChunkSection.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeChunkSection.java new file mode 100644 index 0000000000..48ef30f917 --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeChunkSection.java @@ -0,0 +1,82 @@ +/* + * 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.neoforge.mixin; + +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunkSection; +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(LevelChunkSection.class) +@Implements(@Interface(iface = NativeChunkSection.class, prefix = "ncs$")) +public abstract class MixinNativeChunkSection { + @Shadow + public abstract BlockState setBlockState( + int x, int y, int z, BlockState state, boolean lock + ); + + @Shadow + public abstract BlockState getBlockState( + int x, int y, int z + ); + + @Final + @Mutable + @Shadow + private PalettedContainer states; + + @Shadow + private PalettedContainerRO> biomes; + + @Shadow + public abstract boolean hasOnlyAir(); + + public boolean ncs$isOnlyAir() { + return hasOnlyAir(); + } + + public NativeBlockState ncs$getThenSetBlock(int i, int j, int k, NativeBlockState blockState) { + BlockState nativeState = (BlockState) blockState; + if (ncs$isOnlyAir() && nativeState.isAir()) { + return blockState; + } + return (NativeBlockState) setBlockState(i, j, k, nativeState, false); + } + + public NativeBlockState ncs$getBlock(int i, int j, int k) { + return (NativeBlockState) getBlockState(i, j, k); + } + + public NativeChunkSection ncs$copy() { + return (NativeChunkSection) new LevelChunkSection( + states.copy(), biomes.copy() + ); + } +} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativePosition.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativePosition.java new file mode 100644 index 0000000000..68ae22b94b --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativePosition.java @@ -0,0 +1,47 @@ +/* + * 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.neoforge.mixin; + +import com.sk89q.worldedit.internal.wna.NativePosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(BlockPos.class) +@Implements(@Interface(iface = NativePosition.class, prefix = "nc$")) +public abstract class MixinNativePosition extends Vec3i { + public MixinNativePosition(int x, int y, int z) { + super(x, y, z); + } + + public int nc$x() { + return getX(); + } + + public int nc$y() { + return getY(); + } + + public int nc$z() { + return getZ(); + } +} diff --git a/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeWorld.java b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeWorld.java new file mode 100644 index 0000000000..b8e3a10b9a --- /dev/null +++ b/worldedit-neoforge/src/main/java/com/sk89q/worldedit/neoforge/mixin/MixinNativeWorld.java @@ -0,0 +1,137 @@ +/* + * 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.neoforge.mixin; + +import com.sk89q.worldedit.internal.wna.NativeAdapter; +import com.sk89q.worldedit.internal.wna.NativeBlockState; +import com.sk89q.worldedit.internal.wna.NativeChunk; +import com.sk89q.worldedit.internal.wna.NativePosition; +import com.sk89q.worldedit.internal.wna.NativeWorld; +import com.sk89q.worldedit.neoforge.internal.NBTConverter; +import com.sk89q.worldedit.neoforge.internal.NeoForgeNativeAdapter; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.profiling.Profiler; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.storage.WritableLevelData; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.spongepowered.asm.mixin.Implements; +import org.spongepowered.asm.mixin.Interface; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ServerLevel.class) +@Implements(@Interface(iface = NativeWorld.class, prefix = "nw$")) +public abstract class MixinNativeWorld extends Level { + public MixinNativeWorld(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, boolean bl, boolean bl2, long l, int i) { + super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i); + } + + public NativeAdapter nw$getAdapter() { + return NeoForgeNativeAdapter.INSTANCE; + } + + public int nw$getSectionIndex(int y) { + return super.getSectionIndex(y); + } + + public int nw$getYForSectionIndex(int index) { + return SectionPos.sectionToBlockCoord(super.getSectionYFromSectionIndex(index)); + } + + public NativeChunk nw$getChunk(int chunkX, int chunkZ) { + return (NativeChunk) getChunk(chunkX, chunkZ); + } + + public void nw$notifyBlockUpdate(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + sendBlockUpdated( + (BlockPos) pos, (BlockState) oldState, (BlockState) newState, + Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS + ); + } + + public void nw$markBlockChanged(NativePosition pos) { + ((ServerChunkCache) getChunkSource()).blockChanged((BlockPos) pos); + } + + public void nw$updateLightingForBlock(NativePosition position) { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("updateSkyLightSources"); + getChunk(position.x() >> 4, position.z() >> 4).getSkyLightSources() + .update(this, position.x() & 0xF, position.y(), position.z() & 0xF); + profilerFiller.popPush("queueCheckLight"); + getChunkSource().getLightEngine().checkBlock((BlockPos) position); + profilerFiller.pop(); + } + + public boolean nw$updateTileEntity(NativePosition position, LinCompoundTag tag) { + CompoundTag nativeTag = NBTConverter.toNative(tag); + BlockPos nativePos = (BlockPos) position; + BlockEntity tileEntity = getChunkAt(nativePos).getBlockEntity(nativePos); + if (tileEntity == null) { + return false; + } + tileEntity.loadWithComponents(nativeTag, registryAccess()); + tileEntity.setChanged(); + return true; + } + + public void nw$notifyNeighbors(NativePosition pos, NativeBlockState oldState, NativeBlockState newState, boolean events) { + BlockPos nativePos = (BlockPos) pos; + blockUpdated(nativePos, ((BlockState) oldState).getBlock()); + BlockState nativeNewState = (BlockState) newState; + if (nativeNewState.hasAnalogOutputSignal()) { + updateNeighbourForOutputSignal(nativePos, nativeNewState.getBlock()); + } + } + + public void nw$updateBlock(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + BlockPos nativePos = (BlockPos) pos; + BlockState nativeOldState = (BlockState) oldState; + BlockState nativeNewState = (BlockState) newState; + nativeOldState.onRemove(this, nativePos, nativeNewState, false); + nativeNewState.onPlace(this, nativePos, nativeOldState, false); + } + + public void nw$updateNeighbors( + NativePosition pos, NativeBlockState oldState, NativeBlockState newState, int recursionLimit, boolean events + ) { + BlockPos nativePos = (BlockPos) pos; + BlockState nativeOldState = (BlockState) oldState; + BlockState nativeNewState = (BlockState) newState; + nativeOldState.updateIndirectNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + nativeNewState.updateIndirectNeighbourShapes(this, nativePos, Block.UPDATE_CLIENTS, recursionLimit); + } + + public void nw$onBlockStateChange(NativePosition pos, NativeBlockState oldState, NativeBlockState newState) { + onBlockStateChange((BlockPos) pos, (BlockState) oldState, (BlockState) newState); + } +} diff --git a/worldedit-neoforge/src/main/resources/META-INF/accesstransformer.cfg b/worldedit-neoforge/src/main/resources/META-INF/accesstransformer.cfg index 08401db51c..1939389b6c 100644 --- a/worldedit-neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ b/worldedit-neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -8,3 +8,8 @@ public net.minecraft.world.level.chunk.ChunkBiomeContainer biomes public-f net.minecraft.world.level.storage.PrimaryLevelData worldOptions public net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket (Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/nbt/CompoundTag;)V + +# Ability to mark sections changed +public net.minecraft.server.level.ServerChunkCache getVisibleChunkIfPresent(J)Lnet/minecraft/server/level/ChunkHolder; +public net.minecraft.server.level.ChunkHolder changedBlocksPerSection +public net.minecraft.server.level.ChunkHolder hasChangedSections diff --git a/worldedit-neoforge/src/main/resources/worldedit-neoforge.mixins.json b/worldedit-neoforge/src/main/resources/worldedit-neoforge.mixins.json index b807508229..c944fc56ca 100644 --- a/worldedit-neoforge/src/main/resources/worldedit-neoforge.mixins.json +++ b/worldedit-neoforge/src/main/resources/worldedit-neoforge.mixins.json @@ -5,10 +5,13 @@ "mixins": [ "AccessorServerPlayerGameMode", "MixinLevelChunkSetBlockHook", + "MixinNativeBlockState", + "MixinNativeChunk", + "MixinNativeChunkSection", + "MixinNativePosition", + "MixinNativeWorld", "MixinServerGamePacketListenerImpl" ], - "server": [ - ], "injectors": { "defaultRequire": 1 }, diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java index 5f308f7dce..ccb7c62f3a 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorld.java @@ -32,7 +32,6 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.sponge.internal.NbtAdapter; -import com.sk89q.worldedit.sponge.internal.SpongeWorldNativeAccess; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; @@ -103,7 +102,6 @@ public final class SpongeWorld extends AbstractWorld { private static final Logger LOGGER = LogManagerCompat.getLogger(); private final WeakReference worldRef; - private final SpongeWorldNativeAccess worldNativeAccess; /** * Construct a new world. @@ -113,7 +111,6 @@ public final class SpongeWorld extends AbstractWorld { SpongeWorld(ServerWorld world) { checkNotNull(world); this.worldRef = new WeakReference<>(world); - this.worldNativeAccess = new SpongeWorldNativeAccess(new WeakReference<>((ServerLevel) world)); } /** @@ -232,8 +229,6 @@ public > boolean setBlock(BlockVector3 position, B public Set applySideEffects(BlockVector3 position, com.sk89q.worldedit.world.block.BlockState previousType, SideEffectSet sideEffectSet) throws WorldEditException { checkNotNull(position); - worldNativeAccess.applySideEffects(position, previousType, sideEffectSet); - return Sets.intersection( SpongeWorldEdit.inst().getInternalPlatform().getSupportedSideEffects(), sideEffectSet.getSideEffectsToApply() diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java index 14adfe8e0b..a2aae511b3 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java @@ -145,5 +145,7 @@ public void load() { setDefaultLocaleName(node.node("default-locale").getString(defaultLocaleName)); commandBlockSupport = node.node("command-block-support").getBoolean(false); + + chunkSectionEditing = node.node("chunk-section-editing").getBoolean(chunkSectionEditing); } } diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java deleted file mode 100644 index f831404759..0000000000 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/internal/SpongeWorldNativeAccess.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.sponge.internal; - -import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.sponge.SpongeAdapter; -import com.sk89q.worldedit.util.SideEffect; -import com.sk89q.worldedit.util.SideEffectSet; -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.server.level.FullChunkStatus; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.LevelChunk; -import org.enginehub.linbus.tree.LinCompoundTag; - -import java.lang.ref.WeakReference; -import java.util.Objects; -import javax.annotation.Nullable; - -public class SpongeWorldNativeAccess implements WorldNativeAccess { - private static final int UPDATE = 1; - private static final int NOTIFY = 2; - - private final WeakReference world; - private SideEffectSet sideEffectSet; - - public SpongeWorldNativeAccess(WeakReference world) { - this.world = world; - } - - private ServerLevel getWorld() { - return Objects.requireNonNull(world.get(), "The reference to the world was lost"); - } - - @Override - public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { - this.sideEffectSet = sideEffectSet; - } - - @Override - public LevelChunk getChunk(int x, int z) { - return getWorld().getChunk(x, z); - } - - @Override - public BlockState toNative(com.sk89q.worldedit.world.block.BlockState state) { - return (BlockState) SpongeAdapter.adapt(state); - } - - @Override - public BlockState getBlockState(LevelChunk chunk, BlockPos position) { - return chunk.getBlockState(position); - } - - @Nullable - @Override - public BlockState setBlockState(LevelChunk chunk, BlockPos position, BlockState state) { - if (chunk instanceof ExtendedChunk) { - return ((ExtendedChunk) chunk).setBlockState( - position, state, false, sideEffectSet.shouldApply(SideEffect.UPDATE) - ); - } - return chunk.setBlockState(position, state, false); - } - - @Override - public BlockState getValidBlockForPosition(BlockState block, BlockPos position) { - return Block.updateFromNeighbourShapes(block, getWorld(), position); - } - - @Override - public BlockPos getPosition(int x, int y, int z) { - return new BlockPos(x, y, z); - } - - @Override - public void updateLightingForBlock(BlockPos position) { - getWorld().getChunkSource().getLightEngine().checkBlock(position); - } - - @Override - public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { - CompoundTag nativeTag = NbtAdapter.adaptNMSToWorldEdit(tag); - BlockEntity tileEntity = getWorld().getChunk(position).getBlockEntity(position); - if (tileEntity == null) { - return false; - } - tileEntity.setLevel(getWorld()); - tileEntity.loadWithComponents(nativeTag, getWorld().registryAccess()); - return true; - } - - @Override - public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, BlockState oldState, BlockState newState) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); - } - } - - @Override - public boolean isChunkTicking(LevelChunk chunk) { - return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); - } - - @Override - public void markBlockChanged(LevelChunk chunk, BlockPos position) { - if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { - getWorld().getChunkSource().blockChanged(position); - } - } - - @Override - public void notifyNeighbors(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().updateNeighborsAt(pos, oldState.getBlock()); - if (newState.hasAnalogOutputSignal()) { - getWorld().updateNeighbourForOutputSignal(pos, newState.getBlock()); - } - } - - @Override - public void updateBlock(BlockPos pos, BlockState oldState, BlockState newState) { - ServerLevel world = getWorld(); - newState.onPlace(world, pos, oldState, false); - } - - @Override - public void updateNeighbors(BlockPos pos, BlockState oldState, BlockState newState, int recursionLimit) { - ServerLevel world = getWorld(); - oldState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); - newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); - } - - @Override - public void onBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { - getWorld().onBlockStateChange(pos, oldState, newState); - } -}