diff --git a/build.gradle.kts b/build.gradle.kts index a98ff0c..648ac87 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,14 @@ plugins { `java-library` - id("io.papermc.paperweight.userdev") version "1.5.5" - id("xyz.jpenilla.run-paper") version "2.1.0" // Adds runServer and runMojangMappedServer tasks for testing + id("io.papermc.paperweight.userdev") version "1.5.8" + id("xyz.jpenilla.run-paper") version "2.2.0" // Adds runServer and runMojangMappedServer tasks for testing // Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/ id("com.github.johnrengelman.shadow") version "8.1.1" } group = "com.moulberry.axiom" -version = "1.2.1" +version = "1.5.1" description = "Serverside component for Axiom on Paper" java { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..c30b486 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/moulberry/axiom/AxiomConstants.java b/src/main/java/com/moulberry/axiom/AxiomConstants.java index d0da771..06186b3 100644 --- a/src/main/java/com/moulberry/axiom/AxiomConstants.java +++ b/src/main/java/com/moulberry/axiom/AxiomConstants.java @@ -12,7 +12,7 @@ public class AxiomConstants { } } - public static final int API_VERSION = 6; + public static final int API_VERSION = 7; public static final NamespacedKey ACTIVE_HOTBAR_INDEX = new NamespacedKey("axiom", "active_hotbar_index"); public static final NamespacedKey HOTBAR_DATA = new NamespacedKey("axiom", "hotbar_data"); diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index 5692863..1704367 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -2,10 +2,7 @@ package com.moulberry.axiom; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent; -import com.moulberry.axiom.event.AxiomTimeChangeEvent; import com.moulberry.axiom.packet.*; -import com.moulberry.axiom.world_properties.WorldPropertyCategory; -import com.moulberry.axiom.world_properties.WorldPropertyWidgetType; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import com.moulberry.axiom.world_properties.server.ServerWorldProperty; import io.netty.buffer.Unpooled; @@ -22,13 +19,11 @@ import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.GameRules; import org.bukkit.*; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; import org.checkerframework.checker.nullness.qual.NonNull; @@ -56,7 +51,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { msg.registerOutgoingPluginChannel(this, "axiom:enable"); msg.registerOutgoingPluginChannel(this, "axiom:initialize_hotbars"); msg.registerOutgoingPluginChannel(this, "axiom:set_editor_views"); - msg.registerOutgoingPluginChannel(this, "axiom:block_entities"); + msg.registerOutgoingPluginChannel(this, "axiom:response_chunk_data"); msg.registerOutgoingPluginChannel(this, "axiom:register_world_properties"); msg.registerOutgoingPluginChannel(this, "axiom:set_world_property"); msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties"); @@ -71,7 +66,7 @@ public class AxiomPaper extends JavaPlugin implements Listener { msg.registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", new SwitchActiveHotbarPacketListener()); msg.registerIncomingPluginChannel(this, "axiom:teleport", new TeleportPacketListener()); msg.registerIncomingPluginChannel(this, "axiom:set_editor_views", new SetEditorViewsPacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:request_block_entity", new RequestBlockEntityPacketListener(this)); + msg.registerIncomingPluginChannel(this, "axiom:request_chunk_data", new RequestChunkDataPacketListener(this)); SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this); @@ -104,7 +99,9 @@ public class AxiomPaper extends JavaPlugin implements Listener { if (!player.hasPermission("axiom.*")) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); buf.writeBoolean(false); - player.sendPluginMessage(this, "axiom:enable", buf.accessByteBufWithCorrectSize()); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.sendPluginMessage(this, "axiom:enable", bytes); } else { newActiveAxiomPlayers.add(player.getUniqueId()); } @@ -130,9 +127,12 @@ public class AxiomPaper extends JavaPlugin implements Listener { @EventHandler public void onFailMove(PlayerFailMoveEvent event) { - if (event.getPlayer().hasPermission("axiom.*") && - event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { - event.setAllowed(true); + if (event.getPlayer().hasPermission("axiom.*")) { + if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { + event.setAllowed(true); // Support for arcball camera + } else if (event.getPlayer().isFlying()) { + event.setAllowed(true); // Support for noclip + } } } @@ -149,22 +149,6 @@ public class AxiomPaper extends JavaPlugin implements Listener { } } - @EventHandler - public void onJoin(PlayerJoinEvent event) { - Player player = event.getPlayer(); - Bukkit.getScheduler().scheduleSyncDelayedTask(this, () -> { - World world = player.getWorld(); - - ServerWorldPropertiesRegistry properties = getWorldProperties(world); - - if (properties == null) { - player.sendPluginMessage(this, "axiom:register_world_properties", new byte[]{0}); - } else { - properties.registerFor(this, player); - } - }, 20); // Why does this need to be delayed? - } - @EventHandler public void onGameRuleChanged(WorldGameRuleChangeEvent event) { if (event.getGameRule() == GameRule.DO_WEATHER_CYCLE) { diff --git a/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java b/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java index 8f22020..5158140 100644 --- a/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java +++ b/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java @@ -47,7 +47,7 @@ public class RegionProtectionWorldGuard { // Don't do any protection if player has bypass if (platform.getSessionManager().hasBypass(worldGuardPlayer, worldEditWorld)) { // todo: enable bypass -// return null; + return null; } RegionManager regionManager = regionContainer.get(worldEditWorld); @@ -108,7 +108,7 @@ public class RegionProtectionWorldGuard { // } } - System.out.println("returning default"); + // System.out.println("returning default"); StateFlag.State fallback = Flags.BUILD.getDefault(); return fallback == StateFlag.State.DENY ? SectionProtection.DENY : SectionProtection.ALLOW; } @@ -117,7 +117,7 @@ public class RegionProtectionWorldGuard { for (Map.Entry entry : consideredValues.entrySet()) { ProtectedRegion region = entry.getKey(); if (entry.getValue() == StateFlag.State.DENY) { - System.out.println("found region with deny!"); + // System.out.println("found region with deny!"); if (region instanceof GlobalProtectedRegion) { return SectionProtection.DENY; } else if (region instanceof ProtectedCuboidRegion && doesRegionCompletelyContainSection(region, cx, cy, cz)) { @@ -128,7 +128,7 @@ public class RegionProtectionWorldGuard { } if (hasPartialDeny) { - System.out.println("returning check!"); + // System.out.println("returning check!"); return new SectionProtection() { @Override public SectionState getSectionState() { @@ -143,7 +143,7 @@ public class RegionProtectionWorldGuard { // return complex thing } - System.out.println("returning allow!"); + // System.out.println("returning allow!"); return SectionProtection.ALLOW; } diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index 56224d0..d20deed 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -6,11 +6,14 @@ import com.moulberry.axiom.View; import com.moulberry.axiom.event.AxiomHandshakeEvent; import com.moulberry.axiom.persistence.ItemStackDataType; import com.moulberry.axiom.persistence.UUIDDataType; +import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import io.netty.buffer.Unpooled; import net.kyori.adventure.text.Component; +import net.minecraft.SharedConstants; import net.minecraft.network.FriendlyByteBuf; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; +import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -40,8 +43,14 @@ public class HelloPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); int apiVersion = friendlyByteBuf.readVarInt(); + int dataVersion = friendlyByteBuf.readVarInt(); friendlyByteBuf.readNbt(); // Discard + if (dataVersion != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) { + player.kick(Component.text("Axiom: Incompatible data version detected, are you using ViaVersion?")); + return; + } + if (apiVersion != AxiomConstants.API_VERSION) { player.kick(Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION + ", while client is " + apiVersion)); @@ -66,7 +75,10 @@ public class HelloPacketListener implements PluginMessageListener { buf.writeVarInt(5); // Maximum Reach buf.writeVarInt(16); // Max editor views buf.writeBoolean(true); // Editable Views - player.sendPluginMessage(this.plugin, "axiom:enable", buf.accessByteBufWithCorrectSize()); + + byte[] enableBytes = new byte[buf.writerIndex()]; + buf.getBytes(0, enableBytes); + player.sendPluginMessage(this.plugin, "axiom:enable", enableBytes); // Initialize Hotbars PersistentDataContainer container = player.getPersistentDataContainer(); @@ -84,7 +96,10 @@ public class HelloPacketListener implements PluginMessageListener { buf.writeItem(CraftItemStack.asNMSCopy(stack)); } } - player.sendPluginMessage(this.plugin, "axiom:initialize_hotbars", buf.accessByteBufWithCorrectSize()); + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.sendPluginMessage(this.plugin, "axiom:initialize_hotbars", bytes); } // Initialize Views @@ -99,7 +114,19 @@ public class HelloPacketListener implements PluginMessageListener { View.load(view).write(buf); } - player.sendPluginMessage(this.plugin, "axiom:set_editor_views", buf.accessByteBufWithCorrectSize()); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.sendPluginMessage(this.plugin, "axiom:set_editor_views", bytes); + } + + // Register world properties + World world = player.getWorld(); + ServerWorldPropertiesRegistry properties = plugin.getWorldProperties(world); + + if (properties == null) { + player.sendPluginMessage(plugin, "axiom:register_world_properties", new byte[]{0}); + } else { + properties.registerFor(plugin, player); } } diff --git a/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java deleted file mode 100644 index 84d3cf0..0000000 --- a/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.moulberry.axiom.packet; - -import com.moulberry.axiom.AxiomPaper; -import com.moulberry.axiom.buffer.CompressedBlockEntity; -import io.netty.buffer.Unpooled; -import it.unimi.dsi.fastutil.longs.*; -import net.minecraft.core.BlockPos; -import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.entity.Player; -import org.bukkit.plugin.messaging.PluginMessageListener; -import org.jetbrains.annotations.NotNull; - -import java.io.ByteArrayOutputStream; - -public class RequestBlockEntityPacketListener implements PluginMessageListener { - - private final AxiomPaper plugin; - - public RequestBlockEntityPacketListener(AxiomPaper plugin) { - this.plugin = plugin; - } - - @Override - public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); - long id = friendlyByteBuf.readLong(); - - if (!bukkitPlayer.hasPermission("axiom.*")) { - // We always send an 'empty' response in order to make the client happy - sendEmptyResponse(bukkitPlayer, id); - return; - } - - ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); - MinecraftServer server = player.getServer(); - if (server == null) { - sendEmptyResponse(bukkitPlayer, id); - return; - } - - ResourceKey worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION); - ServerLevel level = server.getLevel(worldKey); - if (level == null) { - sendEmptyResponse(bukkitPlayer, id); - return; - } - - Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); - - // Save and compress block entities - int count = friendlyByteBuf.readVarInt(); - for (int i = 0; i < count; i++) { - long pos = friendlyByteBuf.readLong(); - BlockEntity blockEntity = level.getBlockEntity(mutableBlockPos.set(pos)); - if (blockEntity != null) { - CompoundTag tag = blockEntity.saveWithoutMetadata(); - map.put(pos, CompressedBlockEntity.compress(tag, baos)); - } - } - - // Send response packet - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(16)); - buf.writeLong(id); - buf.writeVarInt(map.size()); - for (Long2ObjectMap.Entry entry : map.long2ObjectEntrySet()) { - buf.writeLong(entry.getLongKey()); - entry.getValue().write(buf); - } - bukkitPlayer.sendPluginMessage(this.plugin, "axiom:block_entities", buf.accessByteBufWithCorrectSize()); - } - - private void sendEmptyResponse(Player player, long id) { - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(16)); - buf.writeLong(id); - buf.writeByte(0); // no block entities - player.sendPluginMessage(this.plugin, "axiom:block_entities", buf.accessByteBufWithCorrectSize()); - } - -} diff --git a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java new file mode 100644 index 0000000..b848347 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java @@ -0,0 +1,263 @@ +package com.moulberry.axiom.packet; + +import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.buffer.CompressedBlockEntity; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.longs.*; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +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.chunk.PalettedContainer; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; + +import java.io.ByteArrayOutputStream; + +public class RequestChunkDataPacketListener implements PluginMessageListener { + + private static final ResourceLocation RESPONSE_ID = new ResourceLocation("axiom:response_chunk_data"); + + private final AxiomPaper plugin; + + public RequestChunkDataPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + + @Override + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) { + ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); + long id = friendlyByteBuf.readLong(); + + if (!bukkitPlayer.hasPermission("axiom.*")) { + // We always send an 'empty' response in order to make the client happy + sendEmptyResponse(player, id); + return; + } + + MinecraftServer server = player.getServer(); + if (server == null) { + sendEmptyResponse(player, id); + return; + } + + ResourceKey worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION); + ServerLevel level = server.getLevel(worldKey); + if (level == null) { + sendEmptyResponse(player, id); + return; + } + + boolean sendBlockEntitiesInChunks= friendlyByteBuf.readBoolean(); + + Long2ObjectMap blockEntityMap = new Long2ObjectOpenHashMap<>(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + + // Save and compress block entities + int count = friendlyByteBuf.readVarInt(); + for (int i = 0; i < count; i++) { + long pos = friendlyByteBuf.readLong(); + BlockEntity blockEntity = level.getBlockEntity(mutableBlockPos.set(pos)); + if (blockEntity != null) { + CompoundTag tag = blockEntity.saveWithoutMetadata(); + blockEntityMap.put(pos, CompressedBlockEntity.compress(tag, baos)); + } + } + + int playerSectionX = player.getBlockX() >> 4; + int playerSectionZ = player.getBlockZ() >> 4; + + Long2ObjectMap> sections = new Long2ObjectOpenHashMap<>(); + count = friendlyByteBuf.readVarInt(); + for (int i = 0; i < count; i++) { + long pos = friendlyByteBuf.readLong(); + + int sx = BlockPos.getX(pos); + int sy = BlockPos.getY(pos); + int sz = BlockPos.getZ(pos); + + int distance = Math.abs(playerSectionX - sx) + Math.abs(playerSectionZ - sz); + if (distance > 128) continue; + + LevelChunk chunk = level.getChunk(sx, sz); + int sectionIndex = chunk.getSectionIndexFromSectionY(sy); + if (sectionIndex < 0 || sectionIndex >= chunk.getSectionsCount()) continue; + LevelChunkSection section = chunk.getSection(sectionIndex); + + if (section.hasOnlyAir()) { + sections.put(pos, null); + } else { + PalettedContainer container = section.getStates(); + sections.put(pos, container); + + if (sendBlockEntitiesInChunks && section.maybeHas(BlockState::hasBlockEntity)) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + BlockState blockState = container.get(x, y, z); + if (blockState.hasBlockEntity()) { + mutableBlockPos.set(sx*16 + x, sy*16 + y, sz*16 + z); + BlockEntity blockEntity = chunk.getBlockEntity(mutableBlockPos, LevelChunk.EntityCreationType.CHECK); + if (blockEntity != null) { + CompoundTag tag = blockEntity.saveWithoutMetadata(); + blockEntityMap.put(mutableBlockPos.asLong(), CompressedBlockEntity.compress(tag, baos)); + } + } + } + } + } + } + } + } + + + // Send response packet + + boolean firstPart = true; + int maxSize = 0x100000 - 64; // Leeway of 64 bytes + + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeLong(id); + + for (Long2ObjectMap.Entry entry : blockEntityMap.long2ObjectEntrySet()) { + int beforeWriterIndex = buf.writerIndex(); + + buf.writeLong(entry.getLongKey()); + entry.getValue().write(buf); + + if (buf.writerIndex() >= maxSize) { + if (firstPart) { + // Finish and send current packet + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeBoolean(false); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.connection.send(new ClientboundCustomPayloadPacket(RESPONSE_ID, buf)); + + // Continuation packet + buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeLong(id); + } else { + // Copy extra bytes + int copiedSize = buf.writerIndex() - beforeWriterIndex; + byte[] copied = new byte[copiedSize]; + buf.getBytes(beforeWriterIndex, copied); + + // Discard extra bytes + buf.writerIndex(beforeWriterIndex); + + // Finish and send current packet + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeBoolean(false); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.connection.send(new ClientboundCustomPayloadPacket(RESPONSE_ID, buf)); + + // Continuation packet + buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeLong(id); + + // Write start of new packet + buf.writeBytes(copied); + firstPart = true; + } + } else { + firstPart = false; + } + } + + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + + for (Long2ObjectMap.Entry> entry : sections.long2ObjectEntrySet()) { + int beforeWriterIndex = buf.writerIndex(); + + buf.writeLong(entry.getLongKey()); + var container = entry.getValue(); + if (container == null) { + buf.writeBoolean(false); + } else { + buf.writeBoolean(true); + entry.getValue().write(buf); + } + + if (buf.writerIndex() >= maxSize) { + if (firstPart) { + // Finish and send current packet + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeBoolean(false); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.connection.send(new ClientboundCustomPayloadPacket(RESPONSE_ID, buf)); + + // Continuation packet + buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeLong(id); + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + } else { + // Copy extra bytes + int copiedSize = buf.writerIndex() - beforeWriterIndex; + byte[] copied = new byte[copiedSize]; + buf.getBytes(beforeWriterIndex, copied); + + // Discard extra bytes + buf.writerIndex(beforeWriterIndex); + + // Finish and send current packet + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeBoolean(false); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.connection.send(new ClientboundCustomPayloadPacket(RESPONSE_ID, buf)); + + // Continuation packet + buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeLong(id); + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + + // Write start of new packet + buf.writeBytes(copied); + firstPart = true; + } + } else { + firstPart = false; + } + } + + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); + buf.writeBoolean(true); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.connection.send(new ClientboundCustomPayloadPacket(RESPONSE_ID, buf)); + } + + private void sendEmptyResponse(ServerPlayer player, long id) { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(16)); + buf.writeLong(id); + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); // no block entities + buf.writeLong(AxiomConstants.MIN_POSITION_LONG); // no chunks + buf.writeBoolean(true); // finished + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.connection.send(new ClientboundCustomPayloadPacket(RESPONSE_ID, buf)); + } + +} diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index d433834..c7883ac 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -35,6 +35,8 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.entity.ai.village.poi.PoiTypes; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; @@ -50,12 +52,6 @@ import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LightEngine; import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.entity.Player; -import org.bukkit.plugin.messaging.PluginMessageListener; -import org.jetbrains.annotations.NotNull; import xyz.jpenilla.reflectionremapper.ReflectionRemapper; import java.lang.reflect.InvocationTargetException; @@ -118,7 +114,7 @@ public class SetBlockBufferPacketListener { Bukkit.getPluginManager().callEvent(modifyWorldEvent); if (modifyWorldEvent.isCancelled()) return; - RegionProtection regionProtection = new RegionProtection(player.getBukkitEntity(), world.getWorld()); + // RegionProtection regionProtection = new RegionProtection(player.getBukkitEntity(), world.getWorld()); // Allowed, apply buffer BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); @@ -137,7 +133,7 @@ public class SetBlockBufferPacketListener { continue; } - SectionProtection sectionProtection = regionProtection.getSection(cx, cy, cz); +// SectionProtection sectionProtection = regionProtection.getSection(cx, cy, cz); // switch (sectionProtection.getSectionState()) { // case ALLOW -> sectionProtection = null; // case DENY -> { @@ -169,99 +165,105 @@ public class SetBlockBufferPacketListener { Short2ObjectMap blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey()); - sectionStates.acquire(); - try { - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - BlockState blockState = container.get(x, y, z); - if (blockState == emptyState) continue; + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + BlockState blockState = container.get(x, y, z); + if (blockState == emptyState) continue; - switch (sectionProtection.getSectionState()) { - case ALLOW -> {} - case DENY -> blockState = Blocks.REDSTONE_BLOCK.defaultBlockState(); - case CHECK -> blockState = Blocks.DIAMOND_BLOCK.defaultBlockState(); - } +// switch (sectionProtection.getSectionState()) { +// case ALLOW -> {} +// case DENY -> blockState = Blocks.REDSTONE_BLOCK.defaultBlockState(); +// case CHECK -> blockState = Blocks.DIAMOND_BLOCK.defaultBlockState(); +// } - int bx = cx*16 + x; - int by = cy*16 + y; - int bz = cz*16 + z; + int bx = cx*16 + x; + int by = cy*16 + y; + int bz = cz*16 + z; -// if (!regionProtection.canBuild(bx, by, bz)) { -// continue; -// } +// if (!regionProtection.canBuild(bx, by, bz)) { +// continue; +// } - blockPos.set(bx, by, bz); + blockPos.set(bx, by, bz); - if (hasOnlyAir && blockState.isAir()) { - continue; - } + if (hasOnlyAir && blockState.isAir()) { + continue; + } - BlockState old = section.setBlockState(x, y, z, blockState, false); - if (blockState != old) { - Block block = blockState.getBlock(); - motionBlocking.update(x, by, z, blockState); - motionBlockingNoLeaves.update(x, by, z, blockState); - oceanFloor.update(x, by, z, blockState); - worldSurface.update(x, by, z, blockState); + BlockState old = section.setBlockState(x, y, z, blockState, true); + if (blockState != old) { + Block block = blockState.getBlock(); + motionBlocking.update(x, by, z, blockState); + motionBlockingNoLeaves.update(x, by, z, blockState); + oceanFloor.update(x, by, z, blockState); + worldSurface.update(x, by, z, blockState); - if (false) { // Full update - old.onRemove(world, blockPos, blockState, false); + if (false) { // Full update + old.onRemove(world, blockPos, blockState, false); - if (sectionStates.get(x, y, z).is(block)) { - blockState.onPlace(world, blockPos, old, false); - } + if (sectionStates.get(x, y, z).is(block)) { + blockState.onPlace(world, blockPos, old, false); } + } - if (blockState.hasBlockEntity()) { - BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); + if (blockState.hasBlockEntity()) { + BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); - if (blockEntity == null) { - // There isn't a block entity here, create it! - blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); - if (blockEntity != null) { - chunk.addAndRegisterBlockEntity(blockEntity); - } - } else if (blockEntity.getType().isValid(blockState)) { - // Block entity is here and the type is correct - blockEntity.setBlockState(blockState); - - try { - this.updateBlockEntityTicker.invoke(chunk, blockEntity); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } else { - // Block entity type isn't correct, we need to recreate it - chunk.removeBlockEntity(blockPos); - - blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); - if (blockEntity != null) { - chunk.addAndRegisterBlockEntity(blockEntity); - } + if (blockEntity == null) { + // There isn't a block entity here, create it! + blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); + if (blockEntity != null) { + chunk.addAndRegisterBlockEntity(blockEntity); } - if (blockEntity != null && blockEntityChunkMap != null) { - int key = x | (y << 4) | (z << 8); - CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key); - if (savedBlockEntity != null) { - blockEntity.load(savedBlockEntity.decompress()); - } + } else if (blockEntity.getType().isValid(blockState)) { + // Block entity is here and the type is correct + blockEntity.setBlockState(blockState); + + try { + this.updateBlockEntityTicker.invoke(chunk, blockEntity); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); } - } else if (old.hasBlockEntity()) { + } else { + // Block entity type isn't correct, we need to recreate it chunk.removeBlockEntity(blockPos); - } - world.getChunkSource().blockChanged(blockPos); // todo: maybe simply resend chunk instead of this? - - if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { - lightEngine.checkBlock(blockPos); + blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState); + if (blockEntity != null) { + chunk.addAndRegisterBlockEntity(blockEntity); + } } + if (blockEntity != null && blockEntityChunkMap != null) { + int key = x | (y << 4) | (z << 8); + CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key); + if (savedBlockEntity != null) { + blockEntity.load(savedBlockEntity.decompress()); + } + } + } else if (old.hasBlockEntity()) { + chunk.removeBlockEntity(blockPos); + } + + // Mark block changed + world.getChunkSource().blockChanged(blockPos); // todo: maybe simply resend chunk instead of this? + + // Update Light + if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { + chunk.getSkyLightSources().update(chunk, x, by, z); + lightEngine.checkBlock(blockPos); + } + + // Update Poi + Optional> newPoi = PoiTypes.forState(blockState); + Optional> oldPoi = PoiTypes.forState(old); + if (!Objects.equals(oldPoi, newPoi)) { + if (oldPoi.isPresent()) world.getPoiManager().remove(blockPos); + if (newPoi.isPresent()) world.getPoiManager().add(blockPos, newPoi.get()); } } } } - } finally { - sectionStates.release(); } boolean nowHasOnlyAir = section.hasOnlyAir(); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index a8e417d..a0ff7da 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -4,11 +4,14 @@ import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomModifyWorldEvent; import io.netty.buffer.Unpooled; import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.core.SectionPos; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.entity.ai.village.poi.PoiTypes; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; @@ -33,7 +36,8 @@ import xyz.jpenilla.reflectionremapper.ReflectionRemapper; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; -import java.util.logging.Level; +import java.util.Objects; +import java.util.Optional; public class SetBlockPacketListener implements PluginMessageListener { @@ -142,7 +146,7 @@ public class SetBlockPacketListener implements PluginMessageListener { } } - BlockState old = section.setBlockState(x, y, z, blockState, false); + BlockState old = section.setBlockState(x, y, z, blockState, true); if (blockState != old) { Block block = blockState.getBlock(); motionBlocking.update(x, by, z, blockState); @@ -182,10 +186,22 @@ public class SetBlockPacketListener implements PluginMessageListener { chunk.removeBlockEntity(blockPos); } + // Mark block changed level.getChunkSource().blockChanged(blockPos); + + // Update Light if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { + chunk.getSkyLightSources().update(chunk, x, by, z); level.getChunkSource().getLightEngine().checkBlock(blockPos); } + + // Update Poi + Optional> newPoi = PoiTypes.forState(blockState); + Optional> oldPoi = PoiTypes.forState(old); + if (!Objects.equals(oldPoi, newPoi)) { + if (oldPoi.isPresent()) level.getPoiManager().remove(blockPos); + if (newPoi.isPresent()) level.getPoiManager().add(blockPos, newPoi.get()); + } } boolean nowHasOnlyAir = section.hasOnlyAir(); diff --git a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java index 5b52e4e..fe5fbe3 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java @@ -1,12 +1,9 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent; -import com.moulberry.axiom.event.AxiomGameModeChangeEvent; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.world.level.GameType; import org.bukkit.Bukkit; -import org.bukkit.GameMode; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; diff --git a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java index d1ae1af..c18a72f 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java @@ -1,27 +1,15 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomPaper; -import com.moulberry.axiom.event.AxiomTimeChangeEvent; -import com.moulberry.axiom.world_properties.WorldPropertyCategory; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import com.moulberry.axiom.world_properties.server.ServerWorldProperty; import io.netty.buffer.Unpooled; -import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.GameRules; -import net.minecraft.world.level.Level; -import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.Map; - public class SetWorldPropertyListener implements PluginMessageListener { @Override @@ -46,8 +34,10 @@ public class SetWorldPropertyListener implements PluginMessageListener { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); buf.writeVarInt(updateId); - player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:ack_world_properties", - buf.accessByteBufWithCorrectSize()); + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:ack_world_properties", bytes); } } diff --git a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java index 68e59f2..21f8d85 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java +++ b/src/main/java/com/moulberry/axiom/world_properties/WorldPropertyDataType.java @@ -42,7 +42,10 @@ public abstract class WorldPropertyDataType { public byte[] serialize(Integer value) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); buf.writeVarInt(value); - return buf.accessByteBufWithCorrectSize(); + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + return bytes; } @Override @@ -79,7 +82,10 @@ public abstract class WorldPropertyDataType { public byte[] serialize(Item value) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); buf.writeId(BuiltInRegistries.ITEM, value); - return buf.accessByteBufWithCorrectSize(); + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + return bytes; } @Override @@ -99,7 +105,10 @@ public abstract class WorldPropertyDataType { public byte[] serialize(Block value) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8)); buf.writeId(BuiltInRegistries.BLOCK, value); - return buf.accessByteBufWithCorrectSize(); + + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + return bytes; } @Override diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java index 91414d2..966c024 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldPropertiesRegistry.java @@ -53,8 +53,9 @@ public class ServerWorldPropertiesRegistry { buf.writeCollection(entry.getValue(), (buffer, p) -> p.write(buffer)); } - bukkitPlayer.sendPluginMessage(plugin, "axiom:register_world_properties", - buf.accessByteBufWithCorrectSize()); + byte[] bytes = new byte[buf.writerIndex()]; + buf.getBytes(0, bytes); + bukkitPlayer.sendPluginMessage(plugin, "axiom:register_world_properties", bytes); } public void registerDefault(World world) { diff --git a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java index 5f861f7..e5c8d5b 100644 --- a/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java +++ b/src/main/java/com/moulberry/axiom/world_properties/server/ServerWorldProperty.java @@ -61,7 +61,8 @@ public class ServerWorldProperty { buf.writeVarInt(this.widget.dataType().getTypeId()); buf.writeByteArray(this.widget.dataType().serialize(this.value)); - byte[] message = buf.accessByteBufWithCorrectSize(); + byte[] message = new byte[buf.writerIndex()]; + buf.getBytes(0, message); for (Player player : world.getPlayers()) { if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUniqueId())) { player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:set_world_property", message);