package net.minecraft.network.protocol.game;

import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo;
import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.network.syncher.SynchedEntityData.DataValue;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayer.RespawnConfig;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.ai.attributes.AttributeMap;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.Difficulty;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.SignTextSlot;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.Heightmap.Types;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;

import org.bukkit.potion.PotionEffectType;

import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.ints.IntArrayList;

import io.netty.buffer.Unpooled;

import com.bergerkiller.bukkit.common.wrappers.BlockStateChange;
import com.bergerkiller.bukkit.common.wrappers.ChatText;
import com.bergerkiller.bukkit.common.wrappers.Holder;
import com.bergerkiller.bukkit.common.wrappers.HumanHandRole;
import com.bergerkiller.bukkit.common.resources.BlockStateType;
import com.bergerkiller.bukkit.common.resources.DimensionType;
import com.bergerkiller.bukkit.common.wrappers.WindowType;
import com.bergerkiller.bukkit.common.block.SignSide;
import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;

import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetCameraPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetEquipmentPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetEquipmentPacketHandle.OwnerType;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetPassengersPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetEntityDataPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ServerboundPlayerActionPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ServerboundPlayerActionPacketHandle.ActionHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundOpenScreenPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ServerboundSignUpdatePacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetTitlesPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ServerboundTeleportToEntityPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundBlockUpdatePacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundBundlePacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundCustomSoundPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundStopSoundPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundSwingAnimationPacketHandle;
import com.bergerkiller.generated.net.minecraft.sounds.SoundSourceHandle;
import com.bergerkiller.generated.net.minecraft.world.effect.MobEffectInstanceHandle;
import com.bergerkiller.generated.net.minecraft.world.effect.MobEffectHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.ai.attributes.AttributeInstanceHandle;
import com.bergerkiller.generated.net.minecraft.server.level.ServerPlayerHandle;

class ClientboundRemoveEntitiesPacket extends Packet {
#if version >= 1.17.1
    #require net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket private it.unimi.dsi.fastutil.ints.IntList entityIds;
#elseif version >= 1.17
    #require net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket private final int entityId;
#else
    #require net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket private int[] entityIds:a;
#endif

    <code>
    public static boolean canDestroyMultiple() {
        return com.bergerkiller.bukkit.common.internal.CommonCapabilities.PACKET_DESTROY_MULTIPLE;
    }
    </code>

    public boolean canSupportMultipleEntityIds() {
        return com.bergerkiller.bukkit.common.internal.CommonCapabilities.PACKET_DESTROY_MULTIPLE;
    }

    public boolean hasMultipleEntityIds() {
#if version >= 1.17.1
        IntList ids = instance#entityIds;
        return ids.size() > 1;
#elseif version >= 1.17
        return false;
#else
        int[] ids = instance#entityIds;
        return ids != null && ids.length > 1;
#endif
    }

    public int getSingleEntityId() {
#if version >= 1.17.1
        IntList ids = instance#entityIds;
        int size = ids.size();
        if (size == 0) {
            return -1;
        } else if (size == 1) {
            return ids.getInt(0);
        } else {
            throw new UnsupportedOperationException("This destroy packet has more than one entity id");
        }
#elseif version >= 1.17
        return instance#entityId;
#else
        int[] ids = instance#entityIds;
        if (ids == null || ids.length == 0) {
            return -1;
        } else if (ids.length == 1) {
            return ids[0];
        } else {
            throw new UnsupportedOperationException("This destroy packet has more than one entity id");
        }
#endif
    }

    public int[] getEntityIds() {
#if version >= 1.17.1
        IntList ids = instance#entityIds;
        int[] result = new int[ids.size()];
        ids.getElements(0, result, 0, result.length);
        return result;
#elseif version >= 1.17
        int id = instance#entityId;
        if (id >= 0) {
            int[] ids = new int[1];
            ids[0] = id;
            return ids;
        } else {
            return new int[0];
        }
#else
        return instance#entityIds;
#endif
    }

    public void setSingleEntityId(int entityId) {
#if version >= 1.17.1
        int[] arr;
        if (entityId >= 0) {
            arr = new int[1];
            arr[0] = entityId;
        } else {
            arr = new int[0];
        }
        IntList newIds = new IntArrayList(arr);
        instance#entityIds = newIds;
#elseif version >= 1.17
        instance#entityId = entityId;
#else
        if (entityId >= 0) {
            int[] ids = new int[1];
            ids[0] = entityId;
            instance#entityIds = ids;
        } else {
            instance#entityIds = new int[0];
        }
#endif
    }

    public void setMultipleEntityIds(int[] multipleEntityIds) {
#if version >= 1.17.1
        IntList newIds = new IntArrayList(multipleEntityIds);
        instance#entityIds = newIds;
#elseif version >= 1.17
        if (multipleEntityIds == null || multipleEntityIds.length == 0) {
            instance#entityId = -1;
        } else if (multipleEntityIds.length == 1) {
            instance#entityId = multipleEntityIds[0];
        } else {
            throw new UnsupportedOperationException("Multiple entity id's are not supported on Minecraft 1.17 and later");
        }
#else
        instance#entityIds = multipleEntityIds;
#endif
    }

    public static (ClientboundRemoveEntitiesPacketHandle) ClientboundRemoveEntitiesPacket createNewSingle(int entityId) {
#if version >= 1.17.1
        if (entityId >= 0) {
            int[] ids = new int[1];
            ids[0] = entityId;
            return new ClientboundRemoveEntitiesPacket(ids);
        } else {
            return new ClientboundRemoveEntitiesPacket(new int[0]);
        }
#elseif version >= 1.17
        return new ClientboundRemoveEntitiesPacket(entityId);
#else
        if (entityId >= 0) {
            int[] ids = new int[1];
            ids[0] = entityId;
            return new ClientboundRemoveEntitiesPacket(ids);
        } else {
            return new ClientboundRemoveEntitiesPacket(new int[0]);
        }
#endif
    }

    public static (ClientboundRemoveEntitiesPacketHandle) ClientboundRemoveEntitiesPacket createNewMultiple(int[] multipleEntityIds) {
#if version >= 1.17 && version < 1.17.1
        if (multipleEntityIds == null || multipleEntityIds.length == 0) {
            return new ClientboundRemoveEntitiesPacket(-1);
        } else if (multipleEntityIds.length == 1) {
            return new ClientboundRemoveEntitiesPacket(multipleEntityIds[0]);
        } else {
            throw new UnsupportedOperationException("Multiple entity id's are not supported on Minecraft 1.17 and later");
        }
#else
        return new ClientboundRemoveEntitiesPacket(multipleEntityIds);
#endif
    }
}

// 1.8 - 1.21.11: PacketPlayOutAnimation
// 26.1 - 26.2: ClientboundAnimatePacket
class ClientboundSwingAnimationPacket extends Packet {

    public static (ClientboundSwingAnimationPacketHandle) ClientboundSwingAnimationPacket createNewWhack(int entityId, (HumanHandRole) InteractionHand hand) {
#if version >= 26.3
        return new ClientboundSwingAnimationPacket(entityId, hand, net.minecraft.world.item.component.SwingAnimation.DEFAULT);
#elseif version >= 1.17
        #require ClientboundSwingAnimationPacket private final int id;
        #require ClientboundSwingAnimationPacket private final int action;

        int action = (hand == InteractionHand.MAIN_HAND)
                ? ClientboundSwingAnimationPacket.SWING_MAIN_HAND
                : ClientboundSwingAnimationPacket.SWING_OFF_HAND;

        ClientboundSwingAnimationPacket packet = (ClientboundSwingAnimationPacket) ClientboundSwingAnimationPacketHandle.T.newInstanceNull();
        packet#id = entityId;
        packet#action = action;
        return packet;
#else
        #require ClientboundSwingAnimationPacket private int id:a;
        #require ClientboundSwingAnimationPacket private int action:b;

  #if version >= 1.9
        int action = (hand == InteractionHand.MAIN_HAND) ? 0 : 3;
  #else
        int action = 0;
  #endif

        ClientboundSwingAnimationPacket packet = new ClientboundSwingAnimationPacket();
        packet#id = entityId;
        packet#action = action;
        return packet;
#endif
    }
}

class ServerboundPlayerActionPacket extends Packet {
#if version >= 1.17
    private (IntVector3) BlockPos position:pos;
    private (org.bukkit.block.BlockFace) Direction direction;
    private (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action action;
#else
    private (IntVector3) BlockPos position:a;
    private (org.bukkit.block.BlockFace) Direction direction:b;
    private (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action action:c;
#endif

#if version >= 1.19
    #require ServerboundPlayerActionPacket private final int sequence;
    public int getSequence() { return instance#sequence; }
    public void setSequence(int sequence) { instance#sequence = sequence; }
#else
    public int getSequence() { return 0; }
    public void setSequence(int sequence) {}
#endif

    public static (ServerboundPlayerActionPacketHandle) ServerboundPlayerActionPacket createNew((IntVector3) BlockPos position, (org.bukkit.block.BlockFace) Direction direction, (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action action, int sequence) {
#if version >= 1.19
          return new ServerboundPlayerActionPacket(action, position, direction, sequence);
#elseif version >= 1.17
          return new ServerboundPlayerActionPacket(action, position, direction);
#else
          ServerboundPlayerActionPacket packet = new ServerboundPlayerActionPacket();
          #require ServerboundPlayerActionPacket private BlockPos position:a;
          #require ServerboundPlayerActionPacket private Direction direction:b;
          #require ServerboundPlayerActionPacket private ServerboundPlayerActionPacket.Action action:c;
          packet#position = position;
          packet#direction = direction;
          packet#action = action;
          return packet;
#endif
    }

    <code>
    /** @deprecated Use {@link #getAction()} instead */
    @Deprecated
    public ActionHandle getDigType() { return getAction(); }

    /** @deprecated Use {@link #setAction(ActionHandle)} instead */
    @Deprecated
    public void setDigType(ActionHandle action) { setAction(action); }
    </code>

    class ServerboundPlayerActionPacket.Action {
        enum (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action START_DESTROY_BLOCK;
        enum (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action ABORT_DESTROY_BLOCK;
        enum (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action STOP_DESTROY_BLOCK;
        enum (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action DROP_ALL_ITEMS;
        enum (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action DROP_ITEM;
        enum (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action RELEASE_USE_ITEM;
        //TODO: SWAP_ITEM_WITH_OFFHAND?
    }
}

class ClientboundTakeItemEntityPacket extends Packet {
#if version >= 1.17
    private int collectedItemId:itemId;
    private int collectorEntityId:playerId;
#else
    private int collectedItemId:a;
    private int collectorEntityId:b;
#endif

    // since 1.11.2
#if version >= 1.17
    private optional int amount;
#else
    private optional int amount:c;
#endif
}

class ClientboundContainerSetSlotPacket extends Packet {
#if version >= 1.17
    private int windowId:containerId;
    private int slot;
    private (org.bukkit.inventory.ItemStack) ItemStack item:itemStack;
#else
    private int windowId:a;
    private int slot:b;
    private (org.bukkit.inventory.ItemStack) ItemStack item:c;
#endif

    public static (ClientboundContainerSetSlotPacketHandle) ClientboundContainerSetSlotPacket createNew(int containerId, int slot, (org.bukkit.inventory.ItemStack) ItemStack item) {
        // Items can't be null since this version
#if version >= 1.11
        if (item == null) {
            item = (ItemStack) com.bergerkiller.generated.net.minecraft.world.item.ItemStackHandle.EMPTY_ITEM.getRaw();
        }
#endif

        // Added state parameter since MC 1.17.1, default to 0
#if version >= 1.17.1
        return new ClientboundContainerSetSlotPacket(containerId, 0, slot, item);
#else
        return new ClientboundContainerSetSlotPacket(containerId, slot, item);
#endif
    }
}

class ClientboundContainerSetDataPacket extends Packet {
#if version >= 1.17
    private final int windowId:containerId;
    private final int id;
    private final int value;
#else
    private int windowId:a;
    private int id:b;
    private int value:c;
#endif
}

class ClientboundContainerSetContentPacket extends Packet {
#if version >= 1.17
    private int windowId:containerId;
#else
    private int windowId:a;
#endif

#if version >= 1.17
    private (List<org.bukkit.inventory.ItemStack>) List<ItemStack> items;
#elseif version >= 1.11
    private (List<org.bukkit.inventory.ItemStack>) List<ItemStack> items:b;
#else
    private (List<org.bukkit.inventory.ItemStack>) ItemStack[] items:b;
#endif
}

class ClientboundContainerClosePacket extends Packet {
#if version >= 1.17
    private int windowId:containerId;
#else
    private int windowId:a;
#endif
}

class ClientboundOpenScreenPacket extends Packet {
#if version >= 1.17
    private int windowId:containerId;
    private (ChatText) Component windowTitle:title;
#else
    private int windowId:a;
    private (ChatText) Component windowTitle:c;
#endif

    public static (ClientboundOpenScreenPacketHandle) ClientboundOpenScreenPacket createNew() {
#if version >= 1.20.5
        return new ClientboundOpenScreenPacket(0, (net.minecraft.world.inventory.MenuType) null, (Component) null);
#elseif version >= 1.17
        return new ClientboundOpenScreenPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new ClientboundOpenScreenPacket();
#endif
    }

#if version >= 1.19
    #require net.minecraft.network.protocol.game.ClientboundOpenScreenPacket private final net.minecraft.world.inventory.MenuType<?> windowType:type;

    public WindowType getWindowType() {
        return com.bergerkiller.bukkit.common.wrappers.WindowType.fromNMSType(instance#windowType);
    }

    public void setWindowType(WindowType windowType) {
        Object nmsType = windowType.getNMSType();
        if (nmsType == null) {
            throw new IllegalArgumentException("Window type " + windowType.name() + " is not supported");
        }
        instance#windowType = (net.minecraft.world.inventory.MenuType)nmsType;
    }
#elseif version >= 1.14
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.ClientboundOpenScreenPacket private int windowTypeId:type;
  #else
    #require net.minecraft.network.protocol.game.ClientboundOpenScreenPacket private int windowTypeId:b;
  #endif

    public WindowType getWindowType() {
        return com.bergerkiller.bukkit.common.wrappers.WindowType.fromWindowTypeId(instance#windowTypeId);
    }

    public void setWindowType(WindowType windowType) {
        int id = windowType.getTypeId();
        if (id == -1) {
            throw new IllegalArgumentException("Window type " + windowType.name() + " is not supported");
        }
        instance#windowTypeId = id;
    }
#else
    #require net.minecraft.network.protocol.game.ClientboundOpenScreenPacket private String windowName:b;
    #require net.minecraft.network.protocol.game.ClientboundOpenScreenPacket private int slotCount:d;

    public WindowType getWindowType() {
        String name = instance#windowName;
        int slotCount = instance#slotCount;
        return com.bergerkiller.bukkit.common.wrappers.WindowType.fromLegacyName_1_8(name, slotCount);
    }

    public void setWindowType(WindowType windowType) {
        String legacyName = windowType.getLegacyName_1_8();
        if (legacyName == null) {
            throw new IllegalArgumentException("Window type " + windowType.name() + " is not supported");
        }
        instance#windowName = legacyName;
        instance#slotCount = windowType.getInventorySlots();
    }
#endif
}

class ClientboundUpdateMobEffectPacket extends Packet {
    <code>
    public static final int FLAG_AMBIENT = 1;
    public static final int FLAG_VISIBLE = 2;
    public static final int FLAG_SHOW_ICON = 4;
    </code>

#if version >= 1.17
    private final int entityId;
    private final int effectDurationTicks;
    private final byte flags;
#else
    private int entityId:a;
    private int effectDurationTicks:d;
    private byte flags:e;
#endif

#if version >= 1.20.5
    #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private final int effectAmplifier;

    public int getEffectAmplifier() {
        return instance#effectAmplifier;
    }

    public void setEffectAmplifier(int amplifier) {
        instance#effectAmplifier = amplifier;
    }
#else
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private final byte effectAmplifier;
  #else
    #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private byte effectAmplifier:c;
  #endif

    public int getEffectAmplifier() {
        byte b = instance#effectAmplifier;
        return (int) (b & 0xFF);
    }

    public void setEffectAmplifier(int amplifier) {
        byte b = (byte) amplifier;
        instance#effectAmplifier = b;
    }
#endif

#if version >= 1.20.5
    public (Holder<MobEffectHandle>) net.minecraft.core.Holder<MobEffect> getEffect();
    public void setEffect((Holder<MobEffectHandle>) net.minecraft.core.Holder<MobEffect> effect) {
        #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private final net.minecraft.core.Holder<MobEffect> effect;
        instance#effect = effect;
    }
#elseif version >= 1.19
    public (Holder<MobEffectHandle>) MobEffect getEffect();
    public void setEffect((Holder<MobEffectHandle>) MobEffect effect) {
        #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private final MobEffect effect;
        instance#effect = effect;
    }
#elseif version >= 1.18.2
    public (Holder<MobEffectHandle>) int getEffect:getEffectId();
    public void setEffect((Holder<MobEffectHandle>) int effect) {
        #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private final int effectId;
        instance#effectId = effect;
    }
#else
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private final byte effectId;
  #else
    #require net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket private byte effectId:b;
  #endif

    public (Holder<MobEffectHandle>) int getEffect() {
        return (int) instance#effectId;
    }

    public void setEffect((Holder<MobEffectHandle>) int effect) {
        byte b = (byte) effect;
        instance#effectId = b;
    }
#endif

    <code>
    public org.bukkit.potion.PotionEffectType getPotionEffectType() {
        return MobEffectHandle.holderToBukkit(getEffect());
    }

    public void setPotionEffectType(org.bukkit.potion.PotionEffectType effectType) {
        setEffect(MobEffectHandle.holderFromBukkit(effectType));
    }

    public static ClientboundUpdateMobEffectPacketHandle createNew(int entityId, org.bukkit.potion.PotionEffect effect) {
        return createNew(entityId, effect, false);
    }
    </code>

    public static (ClientboundUpdateMobEffectPacketHandle) ClientboundUpdateMobEffectPacket createNew(int entityId, (org.bukkit.potion.PotionEffect) MobEffectInstance effect, boolean blend) {
#if version >= 1.20.5
        return new ClientboundUpdateMobEffectPacket(entityId, effect, blend);
#else
        return new ClientboundUpdateMobEffectPacket(entityId, effect);
#endif
    }
}

class ClientboundRemoveMobEffectPacket extends Packet {
#if version >= 1.20.5
    public int getEntityId:entityId();
#else
    public int getEntityId() {
  #if version >= 1.17
        #require ClientboundRemoveMobEffectPacket private int entityId;
  #else
        #require ClientboundRemoveMobEffectPacket private int entityId:a;
  #endif
        return instance#entityId;
    }
#endif

#if version >= 1.20.5
    public (Holder<MobEffectHandle>) net.minecraft.core.Holder<MobEffect> getEffect:effect();
#elseif version >= 1.18
    public (Holder<MobEffectHandle>) MobEffect getEffect();
#elseif version >= 1.17
    public (Holder<MobEffectHandle>) MobEffect getEffect:b();
#elseif version >= 1.9
    public (Holder<MobEffectHandle>) MobEffect getEffect() {
        #require ClientboundRemoveMobEffectPacket private MobEffect effect:b;
        return instance#effect;
    }
#else
    public (Holder<MobEffectHandle>) int getEffect() {
        #require ClientboundRemoveMobEffectPacket private int effect:b;
        return instance#effect;
    }
#endif

#if version >= 1.20.5
    public static (ClientboundRemoveMobEffectPacketHandle) ClientboundRemoveMobEffectPacket createNew(int entityId, (Holder<MobEffectHandle>) net.minecraft.core.Holder<MobEffect> effect) {
        return new ClientboundRemoveMobEffectPacket(entityId, effect);
    }
#elseif version >= 1.9
    public static (ClientboundRemoveMobEffectPacketHandle) ClientboundRemoveMobEffectPacket createNew(int entityId, (Holder<MobEffectHandle>) MobEffect effect) {
        return new ClientboundRemoveMobEffectPacket(entityId, effect);
    }
#else
    public static (ClientboundRemoveMobEffectPacketHandle) ClientboundRemoveMobEffectPacket createNew(int entityId, (Holder<MobEffectHandle>) int effect) {
        ClientboundRemoveMobEffectPacket packet = new ClientboundRemoveMobEffectPacket();
        #require ClientboundRemoveMobEffectPacket private int entityId:a;
        #require ClientboundRemoveMobEffectPacket private int effectId:b;
        packet#entityId = entityId;
        packet#effectId = effect;
        return packet;
    }
#endif

    <code>
    public org.bukkit.potion.PotionEffectType getPotionEffectType() {
        return MobEffectHandle.holderToBukkit(getEffect());
    }

    public static ClientboundRemoveMobEffectPacketHandle createNew(int entityId, org.bukkit.potion.PotionEffectType effectType) {
        return createNew(entityId, MobEffectHandle.holderFromBukkit(effectType));
    }
    </code>
}

class ClientboundLoginPacket extends Packet {
#if version >= 1.19
    public int getPlayerId:playerId();
    public boolean isHardcore:hardcore();
    public int getMaxPlayers:maxPlayers();
    public int getViewDistance:chunkRadius();
    public boolean isReducedDebugInfo:reducedDebugInfo();

#else
    public int getPlayerId() {
  #if version >= 1.17
        #require ClientboundLoginPacket private readonly int playerId;
  #else
        #require ClientboundLoginPacket private readonly int playerId:a;
  #endif
        return instance#playerId;
    }

    public boolean isHardcore() {
  #if version >= 1.17
        #require ClientboundLoginPacket private readonly boolean hardcore;
  #elseif version >= 1.15
        #require ClientboundLoginPacket private readonly boolean hardcore:c;
  #else
        #require ClientboundLoginPacket private readonly boolean hardcore:b;
  #endif
        return instance#hardcore;
    }

    public int getMaxPlayers() {
  #select version >=
  #case 1.17:  #require ClientboundLoginPacket private readonly int maxPlayers;
  #case 1.16:  #require ClientboundLoginPacket private readonly int maxPlayers:j;
  #case 1.15:  #require ClientboundLoginPacket private readonly int maxPlayers:f;
  #case 1.14:  #require ClientboundLoginPacket private readonly int maxPlayers:e;
  #case else:  #require ClientboundLoginPacket private readonly int maxPlayers:f;
  #endselect
        return instance#maxPlayers;
    }

    public int getViewDistance() {
  #if version >= 1.14
    #select version >=
    #case 1.17:  #require ClientboundLoginPacket private readonly int chunkRadius;
    #case 1.16:  #require ClientboundLoginPacket private readonly int chunkRadius:k;
    #case 1.15:  #require ClientboundLoginPacket private readonly int chunkRadius:h;
    #case else:  #require ClientboundLoginPacket private readonly int chunkRadius:g;
    #endselect
        return instance#chunkRadius;
  #else
        return 10;
  #endif
    }

    public boolean isReducedDebugInfo() {
  #select version >=
  #case 1.17:  #require ClientboundLoginPacket private readonly boolean reducedDebugInfo;
  #case 1.16:  #require ClientboundLoginPacket private readonly boolean reducedDebugInfo:l;
  #case 1.15:  #require ClientboundLoginPacket private readonly boolean reducedDebugInfo:i;
  #case else:  #require ClientboundLoginPacket private readonly boolean reducedDebugInfo:h;
  #endselect
        return instance#reducedDebugInfo;
    }
#endif

#if version >= 1.20.5
    public (org.bukkit.GameMode) GameType getGameMode() {
        return instance.commonPlayerSpawnInfo().gameType();
    }
    public (DimensionType) net.minecraft.core.Holder<net.minecraft.world.level.dimension.DimensionType> getDimensionType() {
        return instance.commonPlayerSpawnInfo().dimensionType();
    }
#elseif version >= 1.20.2
    public (org.bukkit.GameMode) GameType getGameMode() {
        return instance.commonPlayerSpawnInfo().gameType();
    }
    public (DimensionType) ResourceKey<net.minecraft.world.level.dimension.DimensionType> getDimensionType() {
        return instance.commonPlayerSpawnInfo().dimensionType();
    }
#else
  #if version >= 1.17
    #require ClientboundLoginPacket private readonly GameType gameType;
  #elseif version >= 1.15
    #require ClientboundLoginPacket private readonly GameType gameType:d;
  #else
    #require ClientboundLoginPacket private readonly GameType gameType:c;
  #endif

    #select version >=
    #case 1.19:     #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) ResourceKey<net.minecraft.world.level.dimension.DimensionType> dimensionType;
    #case 1.18.2:   #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.core.Holder<net.minecraft.world.level.dimension.DimensionType> dimensionType;
    #case 1.17:     #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType;
    #case 1.16.2:   #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType:h;
    #case 1.16:     #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) ResourceKey<net.minecraft.world.level.dimension.DimensionType> dimensionType:h;
    #case 1.15:     #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType:e;
    #case 1.13.1:   #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType:d;
    #case else:     #require ClientboundLoginPacket private (net.minecraft.world.level.dimension.DimensionType) int dimensionType:d;
    #endselect

    public (org.bukkit.GameMode) GameType getGameMode() { return instance#gameType; }
    public (DimensionType) net.minecraft.world.level.dimension.DimensionType getDimensionType() { return instance#dimensionType; }
#endif

#if version >= 26.3
    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        return (GameType) instance.commonPlayerSpawnInfo().previousGameType().orElse(null);
    }
#elseif version >= 1.20.2
    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        return instance.commonPlayerSpawnInfo().previousGameType();
    }
#elseif version >= 1.16
    // Since 1.16 the previous game mode of the player is also sent
    // This allows the player to toggle back and forth between modes
  #if version >= 1.17
    #require ClientboundLoginPacket private net.minecraft.world.level.GameType previousGameMode:previousGameType;
  #else
    #require ClientboundLoginPacket private net.minecraft.world.level.GameType previousGameMode:e;
  #endif

    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        GameType mode = instance#previousGameMode;
  #if version <= 1.16.5
        if (mode == GameType.NOT_SET) {
            return null;
        }
  #endif
        return mode;
    }
#else
    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        return null;
    }
#endif

    // Difficulty field only exists <= 1.13.2
#if version >= 1.14
    private optional (org.bukkit.Difficulty) Difficulty difficulty:###;
#else
    private optional (org.bukkit.Difficulty) Difficulty difficulty:e;
#endif

#if version >= 1.17 && version < 1.20.2
    // private unknown boolean noImmediateRespawn:showDeathScreen;
    // private unknown boolean isDebugWorld:isDebug;
    // private unknown boolean isFlatWorld:isFlat;
#elseif version >= 1.16
    // private unknown boolean noImmediateRespawn:m;
    // private unknown boolean isDebugWorld:n;
    // private unknown boolean isFlatWorld:o;
#elseif version >= 1.15
    // private unknown boolean noImmediateRespawn:j;
#endif

    // Encrypted world seed field was added on MC 1.15
    // It has little meaning, so we just add a method to initialize it
    //public void setEncryptedWorldSeed((org.bukkit.World) ServerLevel world) {
#if version >= 1.20.2
    //    #require CommonPlayerSpawnInfo private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.obfuscateSeed(world.getSeed());
#elseif version >= 1.18
    //    #require net.minecraft.network.protocol.game.ClientboundLoginPacket private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.obfuscateSeed(world.getSeed());
#elseif version >= 1.17
    //    #require net.minecraft.network.protocol.game.ClientboundLoginPacket private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.16
    //    #require net.minecraft.network.protocol.game.ClientboundLoginPacket private long encryptedWorldSeed:b;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.15
    //    #require net.minecraft.network.protocol.game.ClientboundLoginPacket private long encryptedWorldSeed:b;
    //    instance#encryptedWorldSeed = WorldData.c(world.getWorldData().getSeed());
#endif
    //}
}

class ClientboundRespawnPacket extends Packet {
#if version >= 1.20.5
    public (DimensionType) net.minecraft.core.Holder<net.minecraft.world.level.dimension.DimensionType> getDimensionType() {
        return instance.commonPlayerSpawnInfo().dimensionType();
    }
    public (org.bukkit.GameMode) GameType getGamemode() {
        return instance.commonPlayerSpawnInfo().gameType();
    }
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<Level> getWorldName() {
        return instance.commonPlayerSpawnInfo().dimension();
    }
#elseif version >= 1.20.2
    public (DimensionType) ResourceKey<net.minecraft.world.level.dimension.DimensionType> getDimensionType() {
        return instance.commonPlayerSpawnInfo().dimensionType();
    }
    public (org.bukkit.GameMode) GameType getGamemode() {
        return instance.commonPlayerSpawnInfo().gameType();
    }
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<Level> getWorldName() {
        return instance.commonPlayerSpawnInfo().dimension();
    }
#else
    #select version >=
    #case 1.19:    #require ClientboundRespawnPacket private final (net.minecraft.world.level.dimension.DimensionType) ResourceKey<net.minecraft.world.level.dimension.DimensionType> dimensionType;
    #case 1.18.2:  #require ClientboundRespawnPacket private final (net.minecraft.world.level.dimension.DimensionType) net.minecraft.core.Holder<net.minecraft.world.level.dimension.DimensionType> dimensionType;
    #case 1.17:    #require ClientboundRespawnPacket private final (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType;
    #case 1.16.2:  #require ClientboundRespawnPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType:a;
    #case 1.16:    #require ClientboundRespawnPacket private (net.minecraft.world.level.dimension.DimensionType) ResourceKey<net.minecraft.world.level.dimension.DimensionType> dimensionType:a;
    #case 1.13.1:  #require ClientboundRespawnPacket private (net.minecraft.world.level.dimension.DimensionType) net.minecraft.world.level.dimension.DimensionType dimensionType:a;
    #case else:    #require ClientboundRespawnPacket private (net.minecraft.world.level.dimension.DimensionType) int dimensionType:a;
    #endselect

    #select version >=
    #case 1.17:    #require ClientboundRespawnPacket private GameType gameType:playerGameType;
    #case 1.16:    #require ClientboundRespawnPacket private GameType gameType:d;
    #case 1.15:    #require ClientboundRespawnPacket private GameType gameType:c;
    #case 1.14:    #require ClientboundRespawnPacket private GameType gameType:b;
    #case else:    #require ClientboundRespawnPacket private GameType gameType:c;
    #endselect

  #if version >= 1.17
    #require ClientboundRespawnPacket private net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> worldName:dimension;
  #elseif version >= 1.16
    #require ClientboundRespawnPacket private net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> worldName:b;
  #endif

    public (DimensionType) net.minecraft.world.level.dimension.DimensionType getDimensionType() { return instance#dimensionType; }
    public (org.bukkit.GameMode) GameType getGamemode() { return instance#gameType; }
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<Level> getWorldName() {
        // Since Minecraft 1.16: World environment has a resource key, too!
  #if version >= 1.16
        return instance#worldName;
  #else
        return null;
  #endif
    }
#endif

    // Dimension and WorldType are now resource keys
    // private ResourceKey<Level> b;

    // Difficulty field only exists <= 1.13.2
#if version >= 1.14
    private optional (org.bukkit.Difficulty) Difficulty difficulty:###;
#else
    private optional (org.bukkit.Difficulty) Difficulty difficulty:b;
#endif

    // Since 1.16 the previous game mode of the player is also sent
    // This allows the player to toggle back and forth between modes
#if version >= 26.3
    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        return (GameType) instance.commonPlayerSpawnInfo().previousGameType().orElse(null);
    }
#elseif version >= 1.20.2
    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        return instance.commonPlayerSpawnInfo().previousGameType();
    }
#elseif version >= 1.16
  #if version >= 1.17
    #require ClientboundRespawnPacket private net.minecraft.world.level.GameType previousGameMode:previousPlayerGameType;
  #else
    #require ClientboundRespawnPacket private net.minecraft.world.level.GameType previousGameMode:e;
  #endif

    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        GameType mode = instance#previousGameMode;
  #if version <= 1.16.5
        if (mode == GameType.NOT_SET) {
            return null;
        }
  #endif
        return mode;
    }
#else
    public (org.bukkit.GameMode) GameType getPreviousGameMode() {
        return null;
    }
#endif

    // Encrypted world seed field was added on MC 1.15
    // It has little meaning, so we just add a method to initialize it
    //public void setEncryptedWorldSeed((org.bukkit.World) ServerLevel world) {
#if version >= 1.20.2
    //    #require CommonPlayerSpawnInfo private long encryptedWorldSeed:seed;
    //    instance#common()#encryptedWorldSeed = BiomeManager.obfuscateSeed(world.getSeed());
#elseif version >= 1.18
    //    #require net.minecraft.network.protocol.game.ClientboundRespawnPacket private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.obfuscateSeed(world.getSeed());
#elseif version >= 1.17
    //    #require net.minecraft.network.protocol.game.ClientboundRespawnPacket private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.16
    //    #require net.minecraft.network.protocol.game.ClientboundRespawnPacket private long encryptedWorldSeed:c;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.15
    //    #require net.minecraft.network.protocol.game.ClientboundRespawnPacket private long encryptedWorldSeed:b;
    //    instance#encryptedWorldSeed = WorldData.c(world.getWorldData().getSeed());
#endif
    //}
}

class ClientboundSetHealthPacket extends Packet {
#if version >= 1.17
    private float health;
    private int food;
    private float foodSaturation:saturation;
#else
    private float health:a;
    private int food:b;
    private float foodSaturation:c;
#endif
}

class ClientboundSetExperiencePacket extends Packet {
#if version >= 1.17
    private float experienceProgress;
    private int totalExperience;
    private int experienceLevel;
#else
    private float experienceProgress:a;
    private int totalExperience:b;
    private int experienceLevel:c;
#endif
}

class ClientboundSetTimePacket extends Packet {
#if version >= 1.17
    private long gameTime;
    //private long dayTime; // Broke 26.1
#else
    private long gameTime:a;
    //private long dayTime:b;
#endif
}

class ClientboundChangeDifficultyPacket extends Packet {
#if version >= 1.17
    private final (org.bukkit.Difficulty) Difficulty difficulty;
    private final boolean hardcore:locked;
#else
    private final (org.bukkit.Difficulty) Difficulty difficulty:a;
    private final boolean hardcore:b;
#endif
}

class ClientboundTabListPacket extends Packet {
#if version >= 1.13.1
    private (ChatText) Component header;
    private (ChatText) Component footer;
#else
    private (ChatText) Component header:a;
    private (ChatText) Component footer:b;
#endif
}

// Is 'ClientboundLevelChunkWithLightPacket' on Minecraft 1.18+
//   Then all data is stored in Chunk and Light data fields, not individual fields
// is 'ClientboundLevelChunkPacket' on Minecraft 1.8 - 1.17
//   Then light data is sent separately
//
//TODO: Shortly a BitSet/int of a mask of sections stored was used
//      Add it? Same for 'biome storage' and 'has biome data'
//      On 1.18 and later light data is also stored (also 1.8.9 and before)
//      I don't use it, so if anyone wants it, PR it.
class ClientboundLevelChunkWithLightPacket extends Packet {
#if version >= 1.17
    private int x;
    private int z;
#else
    private int x:a;
    private int z:b;
#endif

#if version >= 26.3
    #require ClientboundLevelChunkWithLightPacket public ClientboundLevelChunkPacketData chunkData();
#elseif version >= 1.18
    #require ClientboundLevelChunkWithLightPacket public ClientboundLevelChunkPacketData chunkData:getChunkData();
#endif

    // Heightmap data, not available on MC 1.13.2 and before
    public (CommonTagCompound) CompoundTag getHeightmaps() {
#if version >= 1.21.5
        // Convert Map<Heightmap.Types, long[]> into NBT
        CompoundTag heightmapsNBT = new CompoundTag();

        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
        java.util.Map heightmaps = chunkData.getHeightmaps();
        java.util.Iterator iter = heightmaps.entrySet().iterator();
        while (iter.hasNext()) {
            java.util.Map$Entry entry = (java.util.Map$Entry) iter.next();
            net.minecraft.world.level.levelgen.Heightmap$Types heightmapType = (net.minecraft.world.level.levelgen.Heightmap$Types) entry.getKey();
            long[] heightmapData = (long[]) entry.getValue();
            heightmapsNBT.putLongArray(heightmapType.getSerializedName(), heightmapData);
        }

        return heightmapsNBT;

#elseif version >= 1.18
        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
        return chunkData.getHeightmaps();
#elseif version >= 1.17
        return instance.f();
#elseif version >= 1.14
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private CompoundTag heightmapsField:d;
        return instance#heightmapsField;
#else
        return null; // Not available
#endif
    }

    public void setHeightmaps((CommonTagCompound) CompoundTag heightmapsData) {
#if version >= 1.21.5
        // Convert NBT into Map<Heightmap.Types, long[]>
        java.util.Map heightmaps = new java.util.HashMap(heightmapsData.size());

        net.minecraft.world.level.levelgen.Heightmap$Types[] heightmapTypes = net.minecraft.world.level.levelgen.Heightmap$Types.values();

        //   public Set<Map.Entry<String, Tag>> entrySet() {
        java.util.Iterator nbtEntryIter = heightmapsData.entrySet().iterator();
        while (nbtEntryIter.hasNext()) {
            java.util.Map$Entry entry = (java.util.Map$Entry) nbtEntryIter.next();

            // Key -> Heightmap.Types
            // TODO: Use the Codec to avoid a for loop here
            String serializedName = (String) entry.getKey();
            net.minecraft.world.level.levelgen.Heightmap$Types heightmapType = null;
            for (int i = 0; i < heightmapTypes.length; i++) {
                if (heightmapTypes[i].getSerializedName().equals(serializedName)) {
                    heightmapType = heightmapTypes[i];
                    break;
                }
            }
            if (heightmapType == null) {
                continue;
            }

            // Tag -> long[]
            Tag heightNBT = (Tag) entry.getValue();
            if (!(heightNBT instanceof LongArrayTag)) {
                continue;
            }

            long[] heightmapData = ((LongArrayTag) heightNBT).getAsLongArray();

            heightmaps.put(heightmapType, heightmapData);
        }

        #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final Map<Heightmap.Types, long[]> heightmaps;
        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
        chunkData#heightmaps = heightmaps;

#elseif version >= 1.18
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final CompoundTag heightmaps;
        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
        chunkData#heightmaps = heightmapsData;
#elseif version >= 1.17
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private final CompoundTag heightmaps;
        instance#heightmaps = heightmapsData;
#elseif version >= 1.14
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private CompoundTag heightmaps:d;
        instance#heightmapsField = heightmapsData;
#else
        // Not available
#endif
    }

    // Byte buffer of serialized chunk block data. Use at your own peril.
    // TODO: Kind of useless without the section mask...
#if version >= 1.18
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final byte[] chunkDataBuffer:buffer;
#elseif version >= 1.17
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private byte[] chunkDataBuffer:buffer;
#elseif version >= 1.15
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private byte[] chunkDataBuffer:f;
#elseif version >= 1.14
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private byte[] chunkDataBuffer:e;
#elseif version >= 1.9
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private byte[] chunkDataBuffer:d;
#else
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData chunkData:c;
    #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData public byte[] chunkDataBuffer:a;
#endif

    public byte[] getBuffer() {
#if version >= 1.18
        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
        return chunkData#chunkDataBuffer;
#elseif version >= 1.9
        return instance#chunkDataBuffer;
#else
        ClientboundLevelChunkPacketData data = instance#chunkData;
        return data#chunkDataBuffer;
#endif
    }

    public void setBuffer(byte[] buffer) {
#if version >= 1.18
        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
        chunkData#chunkDataBuffer = buffer;
#elseif version >= 1.9
        instance#chunkDataBuffer = buffer;
#else
        ClientboundLevelChunkPacketData data = instance#chunkData;
        data#chunkDataBuffer = buffer;
#endif
    }

    // List of Block states that are inside the chunk. We offer a clean API for this.
    public List<BlockStateChange> getBlockStates() {
#if version >= 1.18
        // Complicated: requires conversion using chunk x/z
        ClientboundLevelChunkPacketData chunkData = instance#chunkData();
  #if version >= 26.3
        int cx = instance.x();
        int cz = instance.z();
  #else
        int cx = instance.getX();
        int cz = instance.getZ();
  #endif
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final List<net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntitiesData;
        return com.bergerkiller.bukkit.common.conversion.blockstate.ChunkBlockStateChangeConverter.convertList(
                chunkData#blockEntitiesData, cx, cz);
#elseif version >= 1.9.4
  #if version >= 1.17
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private (List<BlockStateChange) List<CompoundTag> blockEntitiesTags;
  #elseif version >= 1.15
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private (List<BlockStateChange) List<CompoundTag> blockEntitiesTags:g;
  #elseif version >= 1.14
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private (List<BlockStateChange) List<CompoundTag> blockEntitiesTags:f;
  #else
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private (List<BlockStateChange) List<CompoundTag> blockEntitiesTags:e;
  #endif
        return instance#blockEntitiesTags;
#else
        // Does not exist, uses update sign packets instead
        return java.util.Collections.emptyList();
#endif
    }

    <code>
    public void setBlockStates(List<BlockStateChange> states) {
        List<BlockStateChange> baseStates = this.getBlockStates();
        int count = states.size();
        int limit = Math.min(count, baseStates.size());
        for (int i = 0; i < limit; i++) {
            BlockStateChange change = states.get(i);
            if (baseStates.get(i) != change) {
                baseStates.set(i, change);
            }
        }
        for (int i = limit; i < count; i++) {
            baseStates.add(states.get(i));
        }
        while (baseStates.size() > count) {
            baseStates.remove(baseStates.size()-1);
        }
    }
    </code>
}

// Only >= MC 1.9 (MC 1.8.9 relies on clients themselves to unload as needed, for some reason)
optional class ClientboundForgetLevelChunkPacket extends Packet {
#if version >= 26.1
    public int getCx() { return instance.pos().x(); }
    public int getCz() { return instance.pos().z(); }
    public void setChunk(int cx, int cz) {
        #require ClientboundForgetLevelChunkPacket private final net.minecraft.world.level.ChunkPos pos;
        net.minecraft.world.level.ChunkPos pair = new net.minecraft.world.level.ChunkPos(cx, cz);
        instance#pos = pair;
    }
#elseif version >= 1.20.2
    public int getCx() { return instance.pos().x; }
    public int getCz() { return instance.pos().z; }
    public void setChunk(int cx, int cz) {
        #require ClientboundForgetLevelChunkPacket private final net.minecraft.world.level.ChunkPos pos;
        net.minecraft.world.level.ChunkPos pair = new net.minecraft.world.level.ChunkPos(cx, cz);
        instance#pos = pair;
    }
#else
  #if version >= 1.17
    #require ClientboundForgetLevelChunkPacket private int cx:x;
    #require ClientboundForgetLevelChunkPacket private int cz:z;
  #elseif version >= 1.9
    #require ClientboundForgetLevelChunkPacket private int cx:a;
    #require ClientboundForgetLevelChunkPacket private int cz:b;
  #endif

    public int getCx() { return instance#cx; }
    public int getCz() { return instance#cz; }
    public void setChunk(int cx, int cz) {
        instance#cx = cx;
        instance#cz = cz;
    }
#endif

    // Deprecated old stuff for bw compatibility
    <code>
    public void setCx(int cx) {
        setChunk(cx, getCz());
    }
    public void setCz(int cz) {
        setChunk(getCx(), cz);
    }
    </code>
}

class ClientboundSoundPacket extends Packet {
#if version >= 1.17
  #if version >= 1.19.3
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.core.Holder<net.minecraft.sounds.SoundEvent> sound;
  #else
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.sounds.SoundEvent sound;
  #endif
    private optional (String) SoundSource category_1_10_2:source;
    private int x;
    private int y;
    private int z;
    private float volume;
    private optional int pitch_1_8_8:###;
    private optional float pitch_1_10_2:pitch;
#elseif version >= 1.9
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.sounds.SoundEvent sound:a;
    private optional (String) SoundSource category_1_10_2:b;
    private int x:c;
    private int y:d;
    private int z:e;
    private float volume:f;
    private optional int pitch_1_8_8:###;
    private optional float pitch_1_10_2:g;
#else
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String sound:a;
    private optional (String) SoundSource category_1_10_2:###;
    private int x:b;
    private int y:c;
    private int z:d;
    private float volume:e;
    private optional int pitch_1_8_8:f;
    private optional float pitch_1_10_2:###;
#endif

    // Accessor functions for pitch
    <code>
    public float getPitch() {
        if (T.pitch_1_10_2.isAvailable()) {
            return T.pitch_1_10_2.getFloat(getRaw());
        } else {
            return (float) T.pitch_1_8_8.getInteger(getRaw()) / 63.0f;
        }
    }

    public void setPitch(float pitch) {
        if (T.pitch_1_10_2.isAvailable()) {
            T.pitch_1_10_2.setFloat(getRaw(), pitch);
        } else {
            T.pitch_1_8_8.setInteger(getRaw(), (int) (pitch * 63.0f));
        }
    }
    </code>

    // Accessor function for sound category (by name)
    // On MC 1.8.9 this does nothing (always returns "master")
    <code>
    public String getCategory() {
        if (T.category_1_10_2.isAvailable()) {
            return T.category_1_10_2.get(getRaw());
        } else {
            return "master";
        }
    }

    public void setCategory(String categoryName) {
        if (T.category_1_10_2.isAvailable()) {
            T.category_1_10_2.set(getRaw(), categoryName);
        } else {
            // Do nothing, unused
        }
    }
    </code>

}

// This packet class is handled by ClientboundSoundPacket on MC 1.8 - 1.8.8, where it
// can only really play Vanilla sound effects.
// It is also handled by ClientboundSoundPacket on MC 1.19.3+ where the two packets
// were merged into one.
class ClientboundCustomSoundPacket extends Packet {
#if version >= 1.19.3
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.core.Holder<net.minecraft.sounds.SoundEvent> sound;
#elseif version >= 1.17
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.Identifier sound:name;
#elseif version >= 1.13
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.Identifier sound:a;
#else
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String sound:a;
#endif

#if version >= 1.17
    private float volume;
#elseif version >= 1.9
    private float volume:f;
#else
    private float volume:e;
#endif

#if version >= 1.19.3
    // Note: is ClientboundSoundPacket
    public static (ClientboundCustomSoundPacketHandle) ClientboundCustomSoundPacket createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.core.Holder<net.minecraft.sounds.SoundEvent> soundEffect, (String) SoundSource category, double x, double y, double z, float volume, float pitch, long randomSeed) {
        return new ClientboundCustomSoundPacket(soundEffect, category, x, y, z, volume, pitch, randomSeed);
    }
#elseif version >= 1.13
    public static (ClientboundCustomSoundPacketHandle) ClientboundCustomSoundPacket createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.Identifier soundEffect, (String) SoundSource category, double x, double y, double z, float volume, float pitch, long randomSeed) {
  #if version >= 1.19
        return new ClientboundCustomSoundPacket(soundEffect, category, new Vec3(x, y, z), volume, pitch, randomSeed);
  #else
        return new ClientboundCustomSoundPacket(soundEffect, category, new Vec3(x, y, z), volume, pitch);
  #endif
    }
#elseif version >= 1.9
    public static (ClientboundCustomSoundPacketHandle) ClientboundCustomSoundPacket createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String soundEffect, (String) SoundSource category, double x, double y, double z, float volume, float pitch, long randomSeed) {
        return new ClientboundCustomSoundPacket(soundEffect, category, x, y, z, volume, pitch);
    }
#else
    // Note: no sound category supported. Is ClientboundSoundPacket
    public static (ClientboundCustomSoundPacketHandle) ClientboundCustomSoundPacket createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String soundEffect, (String) String category, double x, double y, double z, float volume, float pitch, long randomSeed) {
        return new ClientboundCustomSoundPacket(soundEffect, x, y, z, volume, pitch);
    }
#endif

    <code>
    /** Used for the random seed when creating new sound packets. */
    public static final java.util.Random SOUND_RANDOM_SEED_SOURCE = new java.util.Random();

    public static ClientboundCustomSoundPacketHandle createNew(com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect> soundEffect, String category, org.bukkit.Location location, float volume, float pitch) {
        return createNew(soundEffect, category, location.getX(), location.getY(), location.getZ(), volume, pitch);
    }

    public static ClientboundCustomSoundPacketHandle createNew(com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect> soundEffect, String category, double x, double y, double z, float volume, float pitch) {
        long randomSeed = SOUND_RANDOM_SEED_SOURCE.nextLong();
        return createNew(soundEffect, category, x, y, z, volume, pitch, randomSeed);
    }

    /** @deprecated World argument is no longer needed */
    @Deprecated
    public static ClientboundCustomSoundPacketHandle createNew(com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect> soundEffect, String category, org.bukkit.World world, double x, double y, double z, float volume, float pitch) {
        return createNew(soundEffect, category, x, y, z, volume, pitch);
    }
    </code>

#if version >= 1.17
    #require ClientboundCustomSoundPacket private (String) SoundSource customSoundSource:source;
    #require ClientboundCustomSoundPacket private int customSoundX:x;
    #require ClientboundCustomSoundPacket private int customSoundY:y;
    #require ClientboundCustomSoundPacket private int customSoundZ:z;
    #require ClientboundCustomSoundPacket private float customSoundPitch:pitch;
#elseif version >= 1.9
    #require ClientboundCustomSoundPacket private (String) SoundSource customSoundSource:b;
    #require ClientboundCustomSoundPacket private int customSoundX:c;
    #require ClientboundCustomSoundPacket private int customSoundY:d;
    #require ClientboundCustomSoundPacket private int customSoundZ:e;
  #if version >= 1.10.2
    #require ClientboundCustomSoundPacket private float customSoundPitch:g;
  #else
    #require ClientboundCustomSoundPacket private int customSoundPitchInt:g;
  #endif
#else
    #require ClientboundCustomSoundPacket private int customSoundX:b;
    #require ClientboundCustomSoundPacket private int customSoundY:c;
    #require ClientboundCustomSoundPacket private int customSoundZ:d;
    #require ClientboundCustomSoundPacket private int customSoundPitchInt:f;
#endif

    // Category supported since 1.9
#if version >= 1.9
    public String getCategory() { return instance#customSoundSource; }
    public void setCategory(String category) { instance#customSoundSource = category; }
#else
    public String getCategory() { return "master"; }
    public void setCategory(String category) { /* No-op */ }
#endif

    // x/y/z fields are encoded as int fields x8 scale
    public double getX() { return (double) instance#customSoundX / 8.0; }
    public double getY() { return (double) instance#customSoundY / 8.0; }
    public double getZ() { return (double) instance#customSoundZ / 8.0; }
    public void setX(double x) { instance#customSoundX = (int)(x*8.0); }
    public void setY(double y) { instance#customSoundY = (int)(y*8.0); }
    public void setZ(double z) { instance#customSoundZ = (int)(z*8.0); }

    // For whatever ungodly reason Pitch was encoded as int at one point
#if version >= 1.10.2
    public void setPitch(float pitch) { instance#customSoundPitch = pitch; }
    public float getPitch() { return instance#customSoundPitch; }
#else
    public void setPitch(float pitch) { instance#customSoundPitchInt = (int)(pitch*63.0f); }
    public float getPitch() { return (float) instance#customSoundPitchInt / 63.0f; }
#endif

    // New 'seed' field since 1.19
#if version >= 1.19
    #require ClientboundCustomSoundPacket private final long customSoundSeed:seed;
    public long getRandomSeed() { return instance#customSoundSeed; }
    public void setRandomSeed(long seed) { instance#customSoundSeed = seed; }
#else
    public long getRandomSeed() { return 0L; }
    public void setRandomSeed(long seed) { /* No-op */ }
#endif
}

// Note: this packet is ClientboundCustomPayloadPacket on 1.8 - 1.12.2 where it sends an "MC|StopSound" message
class ClientboundStopSoundPacket extends Packet {
#if version >= 1.9
    public static (ClientboundStopSoundPacketHandle) ClientboundStopSoundPacket createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.Identifier soundEffect, (String) SoundSource category) {
  #if version >= 1.13
        return new ClientboundStopSoundPacket(soundEffect, category);
  #else
        FriendlyByteBuf packetdataserializer = new FriendlyByteBuf(Unpooled.buffer());
        packetdataserializer.a(category == null ? "" : category.a());
    #if version >= 1.12
        packetdataserializer.a(soundEffect.getKey());
    #else
        packetdataserializer.a(soundEffect.a());
    #endif
        return new ClientboundStopSoundPacket("MC|StopSound", packetdataserializer);
  #endif
    }
#else
    public static (ClientboundStopSoundPacketHandle) ClientboundStopSoundPacket createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.Identifier soundEffect, String category) {
        FriendlyByteBuf packetdataserializer = new FriendlyByteBuf(Unpooled.buffer());
        packetdataserializer.a(category == null ? "" : category);
        packetdataserializer.a(soundEffect.a());
        return new ClientboundStopSoundPacket("MC|StopSound", packetdataserializer);
    }
#endif
}

// Available >= MC 1.9
// Too complicated since 1.17 to use properly, Im disabling it.
// And we never added the proper api's/wrapper types for the fields anyway.
/*
optional class ClientboundBossEventPacket extends Packet {
#if version >= 1.17
    private UUID entityUUID:id;
    private (Object) ClientboundBossEventPacket.Action action:operation;

    //TODO: Moved to Action class!
    // ==================
    private (Object) Component chat:MOVED;
    private float progress:MOVED;
    private (Object) BossBattle.BarColor bossBarColor:MOVED;
    private (Object) BossBattle.BarStyle bossBarStyle:MOVED;
    private boolean unknown1:MOVED;
    private boolean unknown2:MOVED;
    private boolean unknown3:MOVED;
    // ==================
    
#else
    private UUID entityUUID:a;
    private (Object) ClientboundBossEventPacket.Action action:b;
    private (Object) Component chat:c;
    private float progress:d;
    private (Object) BossBattle.BarColor bossBarColor:e;
    private (Object) BossBattle.BarStyle bossBarStyle:f;
    private boolean unknown1:g;
    private boolean unknown2:h;
    private boolean unknown3:i;
#endif
}
*/

class ClientboundSetEntityLinkPacket extends Packet {
#if version >= 1.17
    private optional int leashId:###;
    private int passengerId:sourceId;
    private int vehicleId:destId;
#elseif version >= 1.9
    private optional int leashId:###;
    private int passengerId:a;
    private int vehicleId:b;
#else
    private optional int leashId:a;
    private int passengerId:b;
    private int vehicleId:c;
#endif

    public static (ClientboundSetEntityLinkPacketHandle) ClientboundSetEntityLinkPacket createNew() {
#if version >= 1.17
        #require net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket private ClientboundSetEntityLinkPacket createSpawnPacket:<init>(net.minecraft.network.FriendlyByteBuf serializer);
        return #createSpawnPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new ClientboundSetEntityLinkPacket();
#endif
    }

#if version >= 1.9
    public boolean isLeash() {
        return true;
    }

    public void setIsLeash(boolean isLeash) {
    }

    public static (ClientboundSetEntityLinkPacketHandle) ClientboundSetEntityLinkPacket createNewMount((org.bukkit.entity.Entity) Entity passengerEntity, (org.bukkit.entity.Entity) Entity vehicleEntity) {
        throw new UnsupportedOperationException("Not supported >= MC 1.9, use Mount packet instead");
    }

    public static (ClientboundSetEntityLinkPacketHandle) ClientboundSetEntityLinkPacket createNewLeash((org.bukkit.entity.Entity) Entity leashedEntity, (org.bukkit.entity.Entity) Entity holderEntity) {
        return new ClientboundSetEntityLinkPacket(leashedEntity, holderEntity);
    }
#else
    #require net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket private int leashId:a;
    public boolean isLeash() {
        int leashId = instance#leashId;
        return leashId == 1;
    }

    public void setIsLeash(boolean isLeash) {
        int id = isLeash ? 1 : 0;
        instance#leashId = id;
    }

    public static (ClientboundSetEntityLinkPacketHandle) ClientboundSetEntityLinkPacket createNewMount((org.bukkit.entity.Entity) Entity passengerEntity, (org.bukkit.entity.Entity) Entity vehicleEntity) {
        return new ClientboundSetEntityLinkPacket(0, passengerEntity, vehicleEntity);
    }

    public static (ClientboundSetEntityLinkPacketHandle) ClientboundSetEntityLinkPacket createNewLeash((org.bukkit.entity.Entity) Entity leashedEntity, (org.bukkit.entity.Entity) Entity holderEntity) {
        return new ClientboundSetEntityLinkPacket(1, leashedEntity, holderEntity);
    }
#endif

    <code>
    public static ClientboundSetEntityLinkPacketHandle createNewLeash(int leashedEntityId, int holderEntityId) {
        ClientboundSetEntityLinkPacketHandle packet = createNew();
        packet.setVehicleId(holderEntityId);
        packet.setPassengerId(leashedEntityId);
        packet.setIsLeash(true);
        return packet;
    }

    public static ClientboundSetEntityLinkPacketHandle createNewMount(int passengerEntityId, int vehicleEntityId) {
        if (!T.leashId.isAvailable()) {
            throw new UnsupportedOperationException("Not supported >= MC 1.9, use Mount packet instead");
        }
        ClientboundSetEntityLinkPacketHandle packet = createNew();
        packet.setVehicleId(vehicleEntityId);
        packet.setPassengerId(passengerEntityId);
        packet.setIsLeash(false);
        return packet;
    }
    </code>
}

class ClientboundSetEquipmentPacket extends Packet {
#if version >= 1.17
    private int entityId:entity;
#else
    private int entityId:a;
#endif

#if version >= 1.16
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket private final java.util.List<com.mojang.datafixers.util.Pair<net.minecraft.world.entity.EquipmentSlot, net.minecraft.world.item.ItemStack>> slots;
  #else
    #require net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket private final java.util.List<com.mojang.datafixers.util.Pair<net.minecraft.world.entity.EquipmentSlot, net.minecraft.world.item.ItemStack>> slots:b;
  #endif

    // Multiple slots are possible
    public int getSlotCount() {
        java.util.List slots = instance#slots;
        return slots.size();
    }

    public static (ClientboundSetEquipmentPacketHandle) ClientboundSetEquipmentPacket createNew(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int entityId, (org.bukkit.inventory.EquipmentSlot) EquipmentSlot slot, (org.bukkit.inventory.ItemStack) ItemStack itemStack) {
        java.util.List slots = com.google.common.collect.Lists.newArrayList();
        slots.add(com.mojang.datafixers.util.Pair.of(slot, itemStack));
        return new ClientboundSetEquipmentPacket(entityId, slots);
    }

    public (org.bukkit.inventory.EquipmentSlot) EquipmentSlot getEquipmentSlot(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int index) {
        java.util.List slots = instance#slots;
        com.mojang.datafixers.util.Pair entry = (com.mojang.datafixers.util.Pair) slots.get(index);
        return (EquipmentSlot) entry.getFirst();
    }

    public void setEquipmentSlot(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int index, (org.bukkit.inventory.EquipmentSlot) EquipmentSlot slot) {
        java.util.List slots = instance#slots;
        com.mojang.datafixers.util.Pair entry = (com.mojang.datafixers.util.Pair) slots.get(index);
        slots.set(index, com.mojang.datafixers.util.Pair.of(slot, entry.getSecond()));
    }

    public (org.bukkit.inventory.ItemStack) ItemStack getItemStack(int index) {
        java.util.List slots = instance#slots;
        com.mojang.datafixers.util.Pair entry = (com.mojang.datafixers.util.Pair) slots.get(index);
        return (ItemStack) entry.getSecond();
    }

    public void setItemStack(int index, (org.bukkit.inventory.ItemStack) ItemStack itemStack) {
        java.util.List slots = instance#slots;
        com.mojang.datafixers.util.Pair entry = (com.mojang.datafixers.util.Pair) slots.get(index);
        slots.set(index, com.mojang.datafixers.util.Pair.of(entry.getFirst(), itemStack));
    }
#else
    // Only one slot is possible
    public int getSlotCount() {
        return 1;
    }

    public static (ClientboundSetEquipmentPacketHandle) ClientboundSetEquipmentPacket createNew(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int entityId, (org.bukkit.inventory.EquipmentSlot) EquipmentSlot slot, (org.bukkit.inventory.ItemStack) ItemStack itemStack) {
  #if version >= 1.9
        return new ClientboundSetEquipmentPacket(entityId, slot, itemStack);
  #else
        return new ClientboundSetEquipmentPacket(entityId, ownerType.isPlayer() ? slot.b() : slot.c(), itemStack);
  #endif
    }

  #if version >= 1.9
    #require net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket private net.minecraft.world.entity.EquipmentSlot slot:b;

    public (org.bukkit.inventory.EquipmentSlot) EquipmentSlot getEquipmentSlot(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int index) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        return instance#slot;
    }
    public void setEquipmentSlot(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int index, (org.bukkit.inventory.EquipmentSlot) EquipmentSlot slot) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        instance#slot = slot;
    }
  #else
    #require net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket private int slot:b;
    public (org.bukkit.inventory.EquipmentSlot) EquipmentSlot getEquipmentSlot(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int index) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        int slotIdx = instance#slot;
        if (ownerType.isPlayer()) {
            return EquipmentSlot.fromPlayerSlotIndex(slotIdx);
        } else {
            return EquipmentSlot.fromNonPlayerSlotIndex(slotIdx);
        }
    }
    public void setEquipmentSlot(ClientboundSetEquipmentPacketHandle.OwnerType ownerType, int index, (org.bukkit.inventory.EquipmentSlot) EquipmentSlot slot) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        int slotIdx = ownerType.isPlayer() ? slot.b() : slot.c();
        instance#slot = slotIdx;
    }
  #endif

    #require net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket private net.minecraft.world.item.ItemStack itemStack:c;
    public (org.bukkit.inventory.ItemStack) ItemStack getItemStack(int index) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        return instance#itemStack;
    }
    public void setItemStack(int index, (org.bukkit.inventory.ItemStack) ItemStack itemStack) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        instance#itemStack = itemStack;
    }
#endif

    <code>
    public enum OwnerType {
        NON_PLAYER(false), PLAYER(true);

        private final boolean isPlayer;

        OwnerType(boolean isPlayer) {
            this.isPlayer = isPlayer;
        }

        public boolean isPlayer() {
            return isPlayer;
        }
    }

    // Should no longer be used because it could be invalid on Minecraft 1.8 (player vs non-player)
    @Deprecated
    public static ClientboundSetEquipmentPacketHandle createNew(int entityId, org.bukkit.inventory.EquipmentSlot slot, org.bukkit.inventory.ItemStack itemStack) {
        return createNew(OwnerType.NON_PLAYER, entityId, slot, itemStack);
    }

    @Deprecated
    public org.bukkit.inventory.EquipmentSlot getEquipmentSlot(int index) {
        return getEquipmentSlot(OwnerType.NON_PLAYER, index);
    }

    @Deprecated
    public void setEquipmentSlot(int index, org.bukkit.inventory.EquipmentSlot slot) {
        setEquipmentSlot(OwnerType.NON_PLAYER, index, slot);
    }
    </code>
}

// Only available >= MC 1.9
optional class ClientboundSetPassengersPacket extends Packet {
#if version >= 1.20.5
    #bootstrap com.bergerkiller.bukkit.common.internal.CommonBootstrap.initServer();
#endif

#if version >= 1.17
    private int entityId:vehicle;
    private int[] mountedEntityIds:passengers;
#else
    private int entityId:a;
    private int[] mountedEntityIds:b;
#endif

    public static (ClientboundSetPassengersPacketHandle) ClientboundSetPassengersPacket createNew() {
#if version >= 1.20.5
        #require net.minecraft.network.protocol.game.ClientboundSetPassengersPacket private ClientboundSetPassengersPacket createMountPacket:<init>(net.minecraft.network.FriendlyByteBuf serializer);
        return #createMountPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#elseif version >= 1.17
        return new ClientboundSetPassengersPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new ClientboundSetPassengersPacket();
#endif
    }

    <code>
    public void addMountedEntityId(int entityId) {
        int[] oldIds = this.getMountedEntityIds();
        if (oldIds == null || oldIds.length == 0) {
            this.setMountedEntityIds(new int[] {entityId});
        } else {
            int[] newIds = new int[oldIds.length + 1];
            for (int i = 0; i < oldIds.length; i++) {
                newIds[i] = oldIds[i];
            }
            newIds[newIds.length - 1] = entityId;
            this.setMountedEntityIds(newIds);
        }
    }

    public static ClientboundSetPassengersPacketHandle createNew(int entityId, int[] mountedEntityIds) {
        ClientboundSetPassengersPacketHandle handle = createNew();
        handle.setEntityId(entityId);
        handle.setMountedEntityIds(mountedEntityIds);
        return handle;
    }
    </code>
}

// Only >= MC 1.9
optional class ClientboundCooldownPacket extends Packet {
#if version >= 1.17
    //private (org.bukkit.Material) Item material:item;
    private int cooldown:duration;
#else
    //private (org.bukkit.Material) Item material:a;
    private int cooldown:b;
#endif
}

// Changed completely on MC 1.13
// class ServerboundCommandSuggestionPacket extends Packet {
//     private String text:a;
// #if version >= 1.9
//     private optional boolean assumeCommand:b;
//     private (IntVector3) BlockPos position:c;
// #else
//     private optional boolean assumeCommand:###;
//     private (IntVector3) BlockPos position:b;
// #endif
// }

class ServerboundSetCreativeModeSlotPacket extends Packet {

    public static (ServerboundSetCreativeModeSlotPacketHandle) ServerboundSetCreativeModeSlotPacket createNew(int slotIndex, (org.bukkit.inventory.ItemStack) ItemStack item) {
#if version >= 1.20.5
        return new ServerboundSetCreativeModeSlotPacket((short) slotIndex, item);
#elseif version >= 1.17
        return new ServerboundSetCreativeModeSlotPacket(slotIndex, item);
#else
        ServerboundSetCreativeModeSlotPacket packet = new ServerboundSetCreativeModeSlotPacket();
        #require ServerboundSetCreativeModeSlotPacket private int slotIndex:slot;
        #require ServerboundSetCreativeModeSlotPacket private ItemStack item:b;
        packet#slotIndex = slotIndex;
        packet#item = item;
        return packet;
#endif
    }

#select version >=
#case 1.20.5:     public (org.bukkit.inventory.ItemStack) ItemStack getItem:itemStack();
#case 1.18:       public (org.bukkit.inventory.ItemStack) ItemStack getItem();
#case else:       public (org.bukkit.inventory.ItemStack) ItemStack getItem:getItemStack();
#endselect

#if version >= 1.20.5
    public int getSlotIndex() {
        return (int) instance.slotNum();
    }
#else
  #select version >=
  #case 1.18:   public int getSlotIndex:getSlotNum();
  #case 1.13:   public int getSlotIndex:b();
  #case else:   public int getSlotIndex:a();
  #endselect
#endif
}

class ClientboundSetEntityDataPacket extends Packet {
#if version >= 1.17
    private int entityId:id;
#else
    private int entityId:a;
#endif

#if version >= 1.17
    private (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<Object>>) List<SynchedEntityData.DataValue<?>> metadataItems:packedItems;
#elseif version >= 1.9
    private (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<Object>>) List<SynchedEntityData.DataValue<?>> metadataItems:b;
#else
    private (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<Object>>) List<SynchedEntityData.DataValue> metadataItems:b;
#endif

    public static (ClientboundSetEntityDataPacketHandle) ClientboundSetEntityDataPacket createForSpawn(int entityId, (com.bergerkiller.bukkit.common.wrappers.DataWatcher) SynchedEntityData datawatcher) {
        // Pack all datawatcher values, and do NOT reset dirty on the datawatcher or the values
        // A spawn shouldn't affect the 'dirty' elements that still need to be synchronized to
        // other players
        java.util.List values = datawatcher.getNonDefaultValues();
        if (values == null) {
            values = java.util.Collections.emptyList();
        }

#if version >= 1.19.3
        return new ClientboundSetEntityDataPacket(entityId, values);
#else
        // Cannot use the metadata packet constructor, because then it resets dirty on the datawatcher.
        // This would negatively effect other players who have not yet received the changes.
        //return new ClientboundSetEntityDataPacket(entityId, datawatcher, true);

  #if version >= 1.17
        ClientboundSetEntityDataPacket packet = (ClientboundSetEntityDataPacket) ClientboundSetEntityDataPacketHandle.T.newInstanceNull();
  #else
        ClientboundSetEntityDataPacket packet = new ClientboundSetEntityDataPacket();
  #endif

        // Assign id and values list
        ClientboundSetEntityDataPacketHandle.T.entityId.setInteger(packet, entityId);
        ((com.bergerkiller.mountiplex.reflection.declarations.Template.Field) ClientboundSetEntityDataPacketHandle.T.metadataItems.raw)
                .set(packet, values);

        return packet;
#endif
    }

    public static (ClientboundSetEntityDataPacketHandle) ClientboundSetEntityDataPacket createForChanges(int entityId, (com.bergerkiller.bukkit.common.wrappers.DataWatcher) SynchedEntityData datawatcher) {
#if version >= 1.19.3
        java.util.List values = datawatcher.packDirty();
        if (values == null) {
            values = java.util.Collections.emptyList();
        }
        return new ClientboundSetEntityDataPacket(entityId, values);
#else
        return new ClientboundSetEntityDataPacket(entityId, datawatcher, false);
#endif
    }

    <code>
    public static ClientboundSetEntityDataPacketHandle createNew(int entityId, DataWatcher datawatcher, boolean includeUnchangedData) {
        if (includeUnchangedData) {
            return createForSpawn(entityId, datawatcher);
        } else {
            return createForChanges(entityId, datawatcher);
        }
    }

    @Override
    public com.bergerkiller.bukkit.common.protocol.PacketType getPacketType() {
        return com.bergerkiller.bukkit.common.protocol.PacketType.OUT_ENTITY_METADATA;
    }
    </code>
}

// No longer exists since MC 1.14
optional class PacketPlayOutBed extends Packet {
    private int entityId:a;
    private (IntVector3) BlockPos bedPosition:b;
}

class ClientboundAnimatePacket extends Packet {
#if version >= 1.17
    private int entityId:id;
    private int action;
#else
    private int entityId:a;
    private int action:b;
#endif
}

class ClientboundEntityEventPacket extends Packet {
#if version >= 1.17
    private int entityId;
    private byte eventId;
#else
    private int entityId:a;
    private byte eventId:b;
#endif
}

class ClientboundUpdateAttributesPacket extends Packet {
#if version >= 1.17
    private int entityId;
#else
    private int entityId:a;
#endif

    public static (ClientboundUpdateAttributesPacketHandle) ClientboundUpdateAttributesPacket createNew(int entityId, (java.util.Collection<AttributeInstanceHandle>) java.util.Collection<AttributeInstance> attributes) {
        return new ClientboundUpdateAttributesPacket(entityId, attributes);
    }

    public static (ClientboundUpdateAttributesPacketHandle) ClientboundUpdateAttributesPacket createZeroMaxHealth(int entityId) {
#if version >= 1.16
        // Internal code uses a Consumer for callbacks, so no need to create a proxy for that
        java.util.function.Consumer callback = com.bergerkiller.bukkit.common.utils.LogicUtil.noopConsumer();
        AttributeInstance attribute = new AttributeInstance(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH, callback);
#else
        // Internal code calls a callback on this instance, so we need a dummy one to catch that
        AttributeMap attributeMapBase = (AttributeMap) com.bergerkiller.bukkit.common.internal.proxy.DummyAttributeMapBase.INSTANCE;

        // Create a modifiable attribute for MAX_HEALTH, and set the value to 0
  #if version >= 1.14
        AttributeInstance attribute = new AttributeInstance(attributeMapBase, GenericAttributes.MAX_HEALTH);
  #else
        AttributeInstance attribute = new AttributeInstance(attributeMapBase, GenericAttributes.maxHealth);
  #endif
#endif

#if version >= 1.18
        attribute.setBaseValue(0.0);
#else
        attribute.setValue(0.0);
#endif

        // Create packet sending just this one attribute
        java.util.Collection attributes = java.util.Collections.singleton(attribute);
        return new ClientboundUpdateAttributesPacket(entityId, attributes);
    }
}

class ServerboundClientCommandPacket extends Packet {
#if version >= 1.17
    private (Object) ServerboundClientCommandPacket.Action action;
#else
    private (Object) ServerboundClientCommandPacket.Action action:a;
#endif
}

class ServerboundSignUpdatePacket extends Packet {
#select version >=
#case 26.3:  public (IntVector3) BlockPos getPosition:pos();
#case 1.18:   public (IntVector3) BlockPos getPosition:getPos();
#case 1.13:   public (IntVector3) BlockPos getPosition:b();
#case else:   public (IntVector3) BlockPos getPosition:a();
#endselect

    // Lines, stored as plaintext strings. The number of lines must be exactly 4.
    // This is NOT json-encoded components, and the server normally strips color codes from the text.
    // Relies on ChatText <> String converting to legacy text, and vice versa.
#select version >=
#case 26.3:  public (List<ChatText>) List<String> getLines:lines();
#case 1.18:   public (List<ChatText>) String[] getLines();
#case 1.13:   public (List<ChatText>) String[] getLines:c();
#case 1.9:     public (List<ChatText>) String[] getLines:b();
#case else:   public (List<ChatText>) Component[] getLines:b();
#endselect

#if version >= 26.3
    public (SignSide) SignTextSlot getSide:slot();
#elseif version >= 1.20
    public (SignSide) SignSide getSide() {
        return instance.isFrontText() ? SignSide.FRONT : SignSide.BACK;
    }
#else
    public (SignSide) SignSide getSide() {
        return SignSide.FRONT;
    }
#endif

#if version >= 26.3
    public static (ServerboundSignUpdatePacketHandle) ServerboundSignUpdatePacket createNew((IntVector3) BlockPos blockPos, (List<ChatText>) List<String> lines, (SignSide) SignTextSlot side) {
        if (lines.size() != 4) {
            throw new IllegalArgumentException("Number of sign lines must be exactly 4");
        }
        return new ServerboundSignUpdatePacket(blockPos, lines, side);
    }
#elseif version >= 1.9
    public static (ServerboundSignUpdatePacketHandle) ServerboundSignUpdatePacket createNew((IntVector3) BlockPos blockPos, (List<ChatText>) List<String> lines, (SignSide) SignSide side) {
        if (lines.size() != 4) {
            throw new IllegalArgumentException("Number of sign lines must be exactly 4");
        }
  #if version < 1.20
        if (side == SignSide.BACK) {
            throw new IllegalArgumentException("Back side of the sign is not supported on this version of the server");
        }
  #endif

  #if version >= 1.20
        // Now can specify sign side
        return new ServerboundSignUpdatePacket(
            blockPos,
            side.isFront(),
            (String) lines.get(0), (String) lines.get(1), (String) lines.get(2), (String) lines.get(3)
        );
  #elseif version >= 1.17
        return new ServerboundSignUpdatePacket(
            blockPos,
            (String) lines.get(0), (String) lines.get(1), (String) lines.get(2), (String) lines.get(3)
        );
  #else
        #require PacketPlayInUpdateSign private BlockPos blockPos:a;
        #require PacketPlayInUpdateSign private String[] lines:b;
        String[] linesArray = lines.toArray(new String[0]);
        PacketPlayInUpdateSign packet = new PacketPlayInUpdateSign();
        packet#blockPos = blockPos;
        packet#lines = linesArray;
        return packet;
  #endif
    }
#else
    public static (ServerboundSignUpdatePacketHandle) ServerboundSignUpdatePacket createNew((IntVector3) BlockPos blockPos, (List<ChatText>) List<Component> lines, (SignSide) SignSide side) {
        if (lines.size() != 4) {
            throw new IllegalArgumentException("Number of sign lines must be exactly 4");
        }
        if (side == SignSide.BACK) {
            throw new IllegalArgumentException("Back side of the sign is not supported on this version of the server");
        }

        #require PacketPlayInUpdateSign private BlockPos blockPos:a;
        #require PacketPlayInUpdateSign private Component[] lines:b;
        Component[] linesArray = lines.toArray(new Component[0]);
        PacketPlayInUpdateSign packet = new PacketPlayInUpdateSign();
        packet#blockPos = blockPos;
        packet#lines = linesArray;
        return packet;
    }
#endif
}

class ServerboundContainerClickPacket extends Packet {
#if version >= 1.21.5
    private int windowId:containerId;
    private (short) short slot:slotNum;
    private (byte) byte button:buttonNum;

    // Note: this is now a "HashedStack" which we will need an API for
    //private (org.bukkit.inventory.ItemStack) ItemStack item:carriedItem;
#elseif version >= 1.17
    private int windowId:containerId;
    private (short) int slot:slotNum;
    private (byte) int button:buttonNum;
    //private (org.bukkit.inventory.ItemStack) ItemStack item:carriedItem;
#else
    private int windowId:a;
    private (short) int slot;
    private (byte) int button;
    private unknown short action:d; //TODO: Do something with this?
    //private (org.bukkit.inventory.ItemStack) ItemStack item;
#endif

#if version >= 26.1
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) net.minecraft.world.inventory.ContainerInput mode:containerInput;
#elseif version >= 1.17
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) net.minecraft.world.inventory.ContainerInput mode:clickType;
#elseif version >= 1.9
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) net.minecraft.world.inventory.ContainerInput mode:shift;
#else
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) int mode:shift;
#endif
}

// Since MC 1.9
optional class ServerboundAcceptTeleportationPacket extends Packet {
#if version >= 1.17
    private int teleportId:id;
#else
    private int teleportId:a;
#endif
}

// Only <= MC 1.8.9
optional class PacketPlayOutUpdateSign extends Packet {
    private (org.bukkit.World) Level world:a;
    private (IntVector3) BlockPos position:b;
    private (ChatText[]) Component[] lines:c;
}

class ClientboundBlockUpdatePacket extends Packet {
#if version >= 1.17
    private final (IntVector3) BlockPos position:pos;
    public final (com.bergerkiller.bukkit.common.wrappers.BlockData) BlockState blockData:blockState;
#else
    private (IntVector3) BlockPos position:a;
    public (com.bergerkiller.bukkit.common.wrappers.BlockData) BlockState blockData:block;
#endif

    public static (ClientboundBlockUpdatePacketHandle) ClientboundBlockUpdatePacket createNewNull() {
#if version >= 1.16.2
        return new ClientboundBlockUpdatePacket((BlockPos) null, (BlockState) null);
#else
        return new ClientboundBlockUpdatePacket();
#endif
    }

    public static (ClientboundBlockUpdatePacketHandle) ClientboundBlockUpdatePacket createNew((IntVector3) BlockPos position, (com.bergerkiller.bukkit.common.wrappers.BlockData) BlockState blockData) {
#if version >= 1.16.2
        return new ClientboundBlockUpdatePacket(position, blockData);
#else
        ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket();
        #require ClientboundBlockUpdatePacket private BlockPos position:a;
        #require ClientboundBlockUpdatePacket public BlockState blockData:block;
        packet#position = position;
        packet#blockData = blockData;
        return packet;
#endif
    }
}

class ClientboundBlockDestructionPacket extends Packet {
#if version >= 1.17
    private final int id;
    private final (IntVector3) BlockPos position:pos;
    private final int progress;
#else
    private int id:a;
    private (IntVector3) BlockPos position:b;
    private int progress:c;
#endif
}

class ClientboundBlockEventPacket extends Packet {
#if version >= 1.17
    private final (IntVector3) BlockPos position:pos;
    private final int b0;
    private final int b1;
    private final (org.bukkit.Material) net.minecraft.world.level.block.Block block;
#else
    private (IntVector3) BlockPos position:a;
    private int b0:b;
    private int b1:c;
    private (org.bukkit.Material) net.minecraft.world.level.block.Block block:d;
#endif
}

class ClientboundSetCameraPacket extends Packet {
#if version >= 1.17
    private final int entityId:cameraId;
#else
    public int entityId:a;
#endif

    <code>
    public static ClientboundSetCameraPacketHandle createNew(int entityId) {
        ClientboundSetCameraPacketHandle packet = createNew();
        packet.setEntityId(entityId);
        return packet;
    }
    </code>

    public static (ClientboundSetCameraPacketHandle) ClientboundSetCameraPacket createNew() {
#if version >= 1.20.5
        #require net.minecraft.network.protocol.game.ClientboundSetCameraPacket private ClientboundSetCameraPacket createCameraPacket:<init>(net.minecraft.network.FriendlyByteBuf serializer);
        return #createCameraPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#elseif version >= 1.17
        return new ClientboundSetCameraPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new ClientboundSetCameraPacket();
#endif
    }
}

class ServerboundSetCarriedItemPacket extends Packet {
#if version >= 1.17
    private final int itemInHandIndex:slot;
#else
    private int itemInHandIndex;
#endif
}

class ClientboundSetHeldSlotPacket extends Packet {
#if version >= 1.17
    private final int itemInHandIndex:slot;
#else
    private int itemInHandIndex:a;
#endif
}

class ServerboundChatPacket extends Packet {
#if version >= 1.17
    private final String message;
#else
    private String message:a;
#endif
}

class ClientboundBlockEntityDataPacket extends Packet {
#if version >= 1.18
    private final (IntVector3) BlockPos position:pos;
    private final (BlockStateType) BlockEntityType<?> type;
    private final (CommonTagCompound) CompoundTag data:tag;
#elseif version >= 1.17
    private final (IntVector3) BlockPos position:pos;
    private final (BlockStateType) int type;
    private final (CommonTagCompound) CompoundTag data:tag;
#else
    private (IntVector3) BlockPos position:a;
    private (BlockStateType) int type:b;
    private (CommonTagCompound) CompoundTag data:c;
#endif

#if version >= 1.18
    private (ClientboundBlockEntityDataPacketHandle) ClientboundBlockEntityDataPacket((IntVector3) BlockPos blockPosition, (BlockStateType) BlockEntityType<?> type, (CommonTagCompound) CompoundTag data);
#else
    public (ClientboundBlockEntityDataPacketHandle) ClientboundBlockEntityDataPacket((IntVector3) BlockPos blockPosition, (BlockStateType) int type, (CommonTagCompound) CompoundTag data);
#endif
}

class ClientboundOpenSignEditorPacket extends Packet {

    // Block coordinates the sign is at
#if version >= 26.3
    public (IntVector3) BlockPos getSignPosition:pos();
#elseif version >= 1.18
    public (IntVector3) BlockPos getSignPosition:getPos();
#elseif version >= 1.17
    public (IntVector3) BlockPos getSignPosition:b();
#else
    public (IntVector3) BlockPos getSignPosition() {
        #require ClientboundOpenSignEditorPacket private BlockPos position:a;
        return instance#position;
    }
#endif

    // Front or back (since 1.20)
#if version >= 26.3
    public (SignSide) SignTextSlot getSide:slot();
#elseif version >= 1.20
    public (SignSide) SignSide getSide() {
        return SignSide.byFront(instance.isFrontText());
    }
#else
    public (SignSide) SignSide getSide() {
        return SignSide.FRONT;
    }
#endif

#if version >= 26.3
    public static (ClientboundOpenSignEditorPacketHandle) ClientboundOpenSignEditorPacket createNew((IntVector3) BlockPos signPosition, (SignSide) SignTextSlot side) {
        return new ClientboundOpenSignEditorPacket(signPosition, side);
    }
#elseif version >= 1.20
    public static (ClientboundOpenSignEditorPacketHandle) ClientboundOpenSignEditorPacket createNew((IntVector3) BlockPos signPosition, (SignSide) SignSide side) {
        return new ClientboundOpenSignEditorPacket(signPosition, side.isFront());
    }
#else
    public static (ClientboundOpenSignEditorPacketHandle) ClientboundOpenSignEditorPacket createNew((IntVector3) BlockPos signPosition, (SignSide) SignSide side) {
        if (!side.isFront()) {
            throw new UnsupportedOperationException("Back text not supported on this version of Minecraft");
        }
        return new ClientboundOpenSignEditorPacket(signPosition);
    }
#endif

    <code>
    public boolean isFrontText() {
        return getSide().isFront();
    }

    public static ClientboundOpenSignEditorPacketHandle createNew(IntVector3 signPosition) {
        return createNew(signPosition, SignSide.FRONT);
    }

    public static ClientboundOpenSignEditorPacketHandle createNew(IntVector3 signPosition, boolean isFrontText) {
        return createNew(signPosition, SignSide.byFront(isFrontText));
    }
    </code>
}

class ServerboundPlayerAbilitiesPacket extends Packet {
#if version >= 1.17
    private final boolean isFlying;
#elseif version >= 1.16
    private boolean isFlying:a;
#else
    private boolean isFlying:b;
#endif
}

class ClientboundPlayerAbilitiesPacket extends Packet {
#if version >= 1.17
    private final boolean invulnerable;
    private final boolean isFlying;
    private final boolean canFly;
    private final boolean instabuild;
    private final float flyingSpeed;
    private final float walkingSpeed;
#else
    private boolean invulnerable:a;
    private boolean isFlying:b;
    private boolean canFly:c;
    private boolean instabuild:d;
    private float flyingSpeed:e;
    private float walkingSpeed:f;
#endif

    public (ClientboundPlayerAbilitiesPacketHandle) ClientboundPlayerAbilitiesPacket((com.bergerkiller.bukkit.common.wrappers.PlayerAbilities) net.minecraft.world.entity.player.Abilities abilities);
}

class ClientboundLevelEventPacket extends Packet {
#if version >= 1.17
    private final int effectId:type;
    private final (IntVector3) BlockPos position:pos;
    private final int data;
    private final boolean globalEvent;
#else
    private int effectId:a;
    private (IntVector3) BlockPos position:b;
    private int data:c;
    private boolean globalEvent:d;
#endif
}

class ClientboundSetDefaultSpawnPositionPacket extends Packet {
#if version >= 1.21.9
    public static (ClientboundSetDefaultSpawnPositionPacketHandle) ClientboundSetDefaultSpawnPositionPacket createNew((ServerPlayerHandle.RespawnConfigHandle) ServerPlayer.RespawnConfig spawnConfig) {
        return new ClientboundSetDefaultSpawnPositionPacket(spawnConfig.respawnData());
    }

    public (ServerPlayerHandle.RespawnConfigHandle) ServerPlayer.RespawnConfig getSpawn() {
        return new net.minecraft.server.level.ServerPlayer$RespawnConfig(instance.respawnData(), true);
    }
#else
    public static (ClientboundSetDefaultSpawnPositionPacketHandle) ClientboundSetDefaultSpawnPositionPacket createNew((ServerPlayerHandle.RespawnConfigHandle) ServerPlayer.RespawnConfig spawnConfig) {
        BlockPos position = (BlockPos) spawnConfig.pos();
  #if version >= 1.16.2
        float angle = spawnConfig.angle();
        return new ClientboundSetDefaultSpawnPositionPacket(position, angle);
  #else
        return new ClientboundSetDefaultSpawnPositionPacket(position);
  #endif
    }

    public (ServerPlayerHandle.RespawnConfigHandle) ServerPlayer.RespawnConfig getSpawn() {
  #if version >= 1.17
        BlockPos position = instance.pos;
  #else
        BlockPos position = instance.position;
  #endif

        float yaw = 0.0f;
  #if version >= 1.16.2
    #if version >= 1.17
        #require net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket private final float angle;
    #else
        #require net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket private float angle:b;
    #endif
        yaw = instance#angle;
  #endif

        // Pack it into a RespawnConfig object
        // For older versions of Minecraft we use our own proxy class
  #if version >= 1.21.5
        return new net.minecraft.server.level.ServerPlayer$RespawnConfig(null, position, yaw, true);
  #else
        return new net.minecraft.server.level.ServerPlayer$RespawnConfig(null, null, position, yaw, true);
  #endif
    }
#endif
}

class ServerboundPlayerCommandPacket extends Packet {
#if version >= 1.17
    private final int playerId:id;
    private final (Object) ServerboundPlayerCommandPacket.Action action;
    private final int data;
#else
    private int playerId:a;
    private (Object) ServerboundPlayerCommandPacket.Action action:animation;
    private int data:c;
#endif
}

class ServerboundContainerClosePacket extends Packet {
#if version >= 1.17
    private final int windowId:containerId;
#else
    private int windowId:id;
#endif
}

class ServerboundContainerButtonClickPacket extends Packet {
#if version >= 1.17
    private final int windowId:containerId;
    private final int buttonId;
#else
    private int windowId:a;
    private int buttonId:b;
#endif
}

class ServerboundTeleportToEntityPacket extends Packet {
#if version >= 1.17
    private final UUID uuid;
#else
    private UUID uuid:a;
#endif

    public (ServerboundTeleportToEntityPacketHandle) ServerboundTeleportToEntityPacket(UUID uuid);
}

// Since Minecraft 1.12
optional class ClientboundUpdateAdvancementsPacket extends Packet {
#if version >= 1.18
    public boolean shouldReset();
#elseif version >= 1.17
    public boolean shouldReset:e();
#elseif version >= 1.12
    public boolean shouldReset() {
        #require ClientboundUpdateAdvancementsPacket private boolean reset:a;
        return instance#reset;
    }
#else
    public boolean shouldReset();
#endif
}

// Since MC 1.19.4
optional class ClientboundBundlePacket extends Packet {

#if version >= 1.20.5
    #require net.minecraft.network.protocol.BundlePacket private final Iterable<Packet<?>> bundlePackets:packets;
#elseif version >= 1.19.4
    #require net.minecraft.network.protocol.BundlePacket private final Iterable<Packet<T extends net.minecraft.network.PacketListener>> bundlePackets:packets;
#endif

    public static (ClientboundBundlePacketHandle) ClientboundBundlePacket createNew(Iterable<Object> rawPackets) {
        return new ClientboundBundlePacket(rawPackets);
    }

#if version >= 1.20.5
    public final (Iterable<Object>) Iterable<Packet<?>> subPackets();
#else
    public final (Iterable<Object>) Iterable<Packet<T extends net.minecraft.network.PacketListener>> subPackets();
#endif

    public void setSubPackets(Iterable<Object> packets) {
        instance#bundlePackets = packets;
    }

    public boolean filterSubPackets(java.util.function.Predicate<Object> filter) {
        Iterable packets = instance.subPackets();

        // Begin by simply iterating and sending all elements to the filter
        // So long the filter does not return false, we don't have to change anything
        // Count the number of packets we've iterated so we know how many packets to
        // copy over later.
        int numIterated = 0;
        java.util.Iterator iter = packets.iterator();
        while (true) {
            if (!iter.hasNext())
                return true;

            if (filter.test(iter.next())) {
                numIterated++;
                continue;
            }
        }

        // Must rewrite the subpackets. For this, create a new ArrayList
        // If the original iterable is already a List we can re-use its capacity
        java.util.List newPackets;
        if (packets instanceof java.util.List) {
            java.util.List packetsList = (java.util.List) packets;
            newPackets = new java.util.ArrayList(packetsList.size());
            for (int i = 0; i < numIterated; i++) {
                newPackets.add(packetsList.get(i));
            }
        } else {
            newPackets = new java.util.ArrayList(numIterated + 8);
            java.util.Iterator temp = packets.iterator();
            for (int i = 0; i < numIterated && temp.hasNext(); i++) {
                newPackets.add(temp.next());
            }
        }

        // Resume as before and fill the new list of rewritten packets
        while (true) {
            if (iter.hasNext()) {
                Object packet = iter.next();
                if (filter.test(packet)) {
                    newPackets.add(packet);
                }
            } else {
                break;
            }
        }

        // Update field with the new packets
        instance#bundlePackets = newPackets;

        boolean hasPackets = !newPackets.isEmpty();
        return hasPackets;
    }
}
