1.18 Chunk data in 1.18

Discussion in 'Spigot Plugin Development' started by Kurasava, Dec 1, 2021.

  1. Judging by the wiki.vg, they changed the structure of the chunk data package in this way, and what is the question, they got rid of the int array which determined the type of biome, through which it was possible to manipulate the type of biome in packages. But I do not understand how to do this now because nowhere is it written about new methods for defining the biome in packages, if anyone knows, tell me
     
  2. Buime data is move away from chunk column packet. Instead you define them per Chunksection.
     
  3. @Kurasava The 1.18 changes still haven't been merged on the wiki's release page, but you can find a rough draft of the changes on the chunk format here.
     
  4. md_5

    Administrator Developer

    • Agree Agree x 2
  5. If this is about your other thread, where you asked how to change the biome and I failed spectacularly to see a very simple way, then I think it's quite simple if you did what I said in the second edit, that being: set the biome to bukkit chunk, get nms chunk, send nms chunk, set back bukkit biomes. Just do exactly what you are doing now, but instead of PacketPlayOutMapChunk, use the ClientboundLevelChunkPacketData (it takes a nms chunk as argument and I think it does the same thing but updated to this version and with a fancier name). If you didn't do it the way I suggested, then I have no idea how you can work with the Biomes in NMS, however, the Protocol wiki seems to have some info about it, as @WinX64 mentioned. So you might want to work with ProtocolLib to help you out here.
     
  6. I used the lib protocol, at 1.17 I intercepted a packet sent to the client, received an array of int, in which the id of biomes are stored per chunk, then replaced the ones I needed and sent it to the client and it worked.
     
  7. And here's another thing, you mentioned that you need to use ClientboundLevelChunkPacketData rather than PacketPlayOutMapChunk. Is it the same MapChunk package in the protocollib or is it a different package and if so, what is it called
     
  8. I've been having a play with this as a way to change the sky colour in a custom dimension.
    I create a custom biome:
    Code (Java):

    public static void addCustomBiome(CustomBiomeData data) {
        DedicatedServer dedicatedServer = ((CraftServer) Bukkit.getServer()).getServer();
        ResourceKey<Biome> minecraftKey = ResourceKey.create(Registry.BIOME_REGISTRY, new ResourceLocation("minecraft", data.getMinecraftName()));
        ResourceKey<Biome> customKey = ResourceKey.create(Registry.BIOME_REGISTRY, new ResourceLocation("tardis", data.getCustomName()));
        WritableRegistry<Biome> registrywritable = dedicatedServer.registryAccess().ownedRegistry(Registry.BIOME_REGISTRY).get();
        Biome minecraftbiome = registrywritable.get(minecraftKey);
        Biome.BiomeBuilder newBiome = new Biome.BiomeBuilder();
        newBiome.biomeCategory(minecraftbiome.getBiomeCategory());
        newBiome.precipitation(minecraftbiome.getPrecipitation());
        MobSpawnSettings biomeSettingMobs = minecraftbiome.getMobSettings();
        newBiome.mobSpawnSettings(biomeSettingMobs);
        BiomeGenerationSettings biomeSettingGen = minecraftbiome.getGenerationSettings();
        newBiome.generationSettings(biomeSettingGen);
        newBiome.temperature(data.getTemperature());
        newBiome.downfall(data.getDownfall());
        newBiome.temperatureAdjustment(data.isFrozen() ? Biome.TemperatureModifier.NONE : Biome.TemperatureModifier.FROZEN);
        BiomeSpecialEffects.Builder newFog = new BiomeSpecialEffects.Builder();
        newFog.grassColorModifier(BiomeSpecialEffects.GrassColorModifier.NONE);
        newFog.fogColor(data.getFogColour());
        newFog.waterColor(data.getWaterColour());
        newFog.waterFogColor(data.getWaterFogColour());
        newFog.skyColor(data.getSkyColour());
        newFog.foliageColorOverride(data.getFoliageColour());
        newFog.grassColorOverride(data.getGrassColour());
        newBiome.specialEffects(newFog.build());
        Biome biome = newBiome.build();
        TARDISHelper.biomeMap.put(data.getCustomName(), biome);
        registrywritable.register(customKey, biome, Lifecycle.stable());
    }
     
    Then listen for the ClientboundLevelChunkWithLightPacket packets, where I clone the chunk and set its biome to my custom biome, then write that data to the packet (adapted from @Daskrr's post here):
    Code (Java):

    public static void injectPlayer(Player player) {
        ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() {

            @Override
            public void channelRead(ChannelHandlerContext channelHandlerContext, Object packet) throws Exception {
                super.channelRead(channelHandlerContext, packet);
            }

            @Override
            public void write(ChannelHandlerContext channelHandlerContext, Object packet, ChannelPromise channelPromise) throws Exception {
                // get the packet
                if (packet instanceof ClientboundLevelChunkWithLightPacket chunkPacket) {
                    String world = player.getWorld().getName();
                   // only for TARDIS custom dimensions
                    if (world.endsWith("_tardis_gallifrey") || world.endsWith("_tardis_skaro")) {
                        // clone the chunk
                        LevelChunk levelChunk = cloneChunk(((CraftChunk) player.getWorld().getChunkAt(chunkPacket.getX(), chunkPacket.getZ())).getHandle());
                        String key = (world.endsWith("_tardis_gallifrey")) ? "gallifrey_badlands" : "skaro_desert";
                        // get the correct custom biome
                        Biome biome = TARDISHelper.biomeMap.get(key);
                        if (biome != null) {
                            // set the biome of the cloned chunk
                            for (int x = 0; x < 16; x++) {
                                for (int z = 0; z < 16; z++) {
                                    for (int y = -64; y < 384; y++) {
                                        levelChunk.setBiome(x >> 2, y >> 2, z >> 2, biome);
                                    }
                                }
                            }
                            // create a new chunk packet
                            ClientboundLevelChunkPacketData chunkPacketData = new ClientboundLevelChunkPacketData(levelChunk);
                            // overwrite the original chunk's data
                            chunkPacket.getChunkData().write(chunkPacketData.getReadBuffer());
                        } else {
                            Bukkit.getLogger().log(Level.INFO, "biome was null");
                        }
                    }
                }
                super.write(channelHandlerContext, packet, channelPromise);
            }
        };
        ChannelPipeline pipeline = ((CraftPlayer) player).getHandle().connection.connection.channel.pipeline();
        pipeline.addBefore("packet_handler", player.getName(), channelDuplexHandler);
    }
     
    Needless to say, I wouldn't be posting here if it was working... here's the error when joining a custom dimension:
    Code (Text):

    [Server thread/INFO]: eccentric_nz lost connection: Internal Exception: io.netty.handler.codec.EncoderException: java.lang.IndexOutOfBoundsException: writerIndex(21072) + minWritableBytes(1) exceeds maxCapacity(21072): UnpooledHeapByteBuf(ridx: 0, widx: 21072, cap: 21072/21072)
     
    What am I doing wrong?
     
  9. OK fixed it:
    Code (Java):

    if (packet instanceof ClientboundLevelChunkWithLightPacket chunkPacket) {
        String world = player.getWorld().getName();
        if (world.endsWith("_tardis_gallifrey") || world.endsWith("_tardis_skaro")) {
            LevelChunk levelChunk = cloneChunk(((CraftChunk) player.getWorld().getChunkAt(chunkPacket.getX(), chunkPacket.getZ())).getHandle());
            String key = (world.endsWith("_tardis_gallifrey")) ? "gallifrey_badlands" : "skaro_desert";
            Biome biome = TARDISHelper.biomeMap.get(key);
            if (biome != null) {
                for (LevelChunkSection section : levelChunk.getSections()) {
                    for (int x = 0; x < 4; ++x) {
                        for (int z = 0; z < 4; ++z) {
                            for (int y = 0; y < 4; ++y) {
                                section.setBiome(x, y, z, biome);
                            }
                        }
                    }
                }
                // just make a new packet from the cloned chunk
                packet = new ClientboundLevelChunkWithLightPacket(levelChunk, levelChunk.getLevel().getLightEngine(), null, null, true);
            } else {
                Bukkit.getLogger().log(Level.INFO, "biome was null");
            }
        }
    }