package net.minecraft.network.protocol.game;

import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.core.particles.ParticleOptions;
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.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.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.resources.BlockStateType;
import com.bergerkiller.bukkit.common.resources.DimensionType;
import com.bergerkiller.bukkit.common.wrappers.WindowType;
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.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.ClientboundLevelParticlesPacketHandle;
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.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.RespawnConfigHandle;

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
    }
}

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 digType:action;
#else
    private (IntVector3) BlockPos position:a;
    private (org.bukkit.block.BlockFace) Direction direction:b;
    private (ServerboundPlayerActionPacketHandle.ActionHandle) ServerboundPlayerActionPacket.Action digType:c;
#endif

    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 >= 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 >= 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

    public static (ClientboundLevelChunkWithLightPacketHandle) ClientboundLevelChunkWithLightPacket createNew() {
#if version >= 1.20.5
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket private ClientboundLevelChunkWithLightPacket createMapChunkPacket:<init>(net.minecraft.network.RegistryFriendlyByteBuf buf);
        return #createMapChunkPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#elseif version >= 1.17
        return new ClientboundLevelChunkWithLightPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new ClientboundLevelChunkWithLightPacket();
#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();

        java.util.Map heightmaps = instance.getChunkData().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
        return instance.getChunkData().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;
        instance#heightmaps = heightmaps;

#elseif version >= 1.18
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final CompoundTag heightmaps;
        ClientboundLevelChunkPacketData data = instance.getChunkData();
        data#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 data = instance.getChunkData();
        return data#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 data = instance.getChunkData();
        data#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
        net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData data = instance.getChunkData();
        #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(
                data#blockEntitiesData, instance.getX(), instance.getZ());
#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 {
#if version >= 1.17
    private (IntVector3) BlockPos position:pos;
#else
    private (IntVector3) BlockPos position:a;
#endif

#if version >= 1.17
    private (ChatText[]) String[] lines;
#elseif version >= 1.9
    private (ChatText[]) String[] lines:b;
#else
    private (ChatText[]) Component[] lines:b;
#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 ClientboundLevelParticlesPacket extends Packet {
    // TODO: Create a common interface for particle + data
    // Since 1.13 there is a ParticleOptions with many different types
    // Best option would be coming up with a proxy similar to this for <= 1.12.2
#if version >= 1.17
    #require ClientboundLevelParticlesPacket private double pos_x:x;
    #require ClientboundLevelParticlesPacket private double pos_y:y;
    #require ClientboundLevelParticlesPacket private double pos_z:z;
    private float randomX:xDist;
    private float randomY:yDist;
    private float randomZ:zDist;
    private float speed:maxSpeed;
    private int count;
    private boolean overrideLimiter;
    #require ClientboundLevelParticlesPacket private ParticleOptions particle;
#elseif version >= 1.15
    #require ClientboundLevelParticlesPacket private double pos_x:a;
    #require ClientboundLevelParticlesPacket private double pos_y:b;
    #require ClientboundLevelParticlesPacket private double pos_z:c;
    private float randomX:d;
    private float randomY:e;
    private float randomZ:f;
    private float speed:g;
    private int count:h;
    private boolean overrideLimiter:i;
    #require net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket private ParticleOptions particle:j;
#elseif version >= 1.13
    #require ClientboundLevelParticlesPacket private float pos_x:a;
    #require ClientboundLevelParticlesPacket private float pos_y:b;
    #require ClientboundLevelParticlesPacket private float pos_z:c;
    private float randomX:d;
    private float randomY:e;
    private float randomZ:f;
    private float speed:g;
    private int count:h;
    private boolean overrideLimiter:i;
    #require net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket private ParticleOptions particle:j;
#else
    #require ClientboundLevelParticlesPacket private ParticleType particle:a;
    #require ClientboundLevelParticlesPacket private float pos_x:b;
    #require ClientboundLevelParticlesPacket private float pos_y:c;
    #require ClientboundLevelParticlesPacket private float pos_z:d;
    private float randomX:e;
    private float randomY:f;
    private float randomZ:g;
    private float speed:h;
    private int count:i;
    private boolean overrideLimiter:j;
    #require ClientboundLevelParticlesPacket private int[] particleOptions:k;
#endif

    public static (ClientboundLevelParticlesPacketHandle) ClientboundLevelParticlesPacket createNew() {
#if version >= 1.21.4
        return new ClientboundLevelParticlesPacket((ParticleOptions) null, false, false, 0.0, 0.0, 0.0, 0.0f, 0.0f, 0.0f, 0.0f, 1);
#elseif version >= 1.15
        return new ClientboundLevelParticlesPacket((ParticleOptions) null, false, 0.0, 0.0, 0.0, 0.0f, 0.0f, 0.0f, 0.0f, 1);
#elseif version >= 1.13
        return new ClientboundLevelParticlesPacket((ParticleOptions) null, false, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1);
#else
        return new ClientboundLevelParticlesPacket((ParticleType) null, false, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1, new int[0]);
#endif
    }

    <code>
    public void setParticle(com.bergerkiller.bukkit.common.resources.ParticleType<Void> particleType) {
        setParticle(particleType, null);
    }

    public <T> void setParticle(com.bergerkiller.bukkit.common.resources.ParticleType<T> particleType, T value) {
        T.setParticle.invoker.invoke(getRaw(), particleType.getRawHandle(), value);
    }
    </code>

#if version >= 1.13
    // ParticleOptions API
    public optional void setParticle(ParticleType<?> particleType, Object options) {
        ParticleOptions param;
        if (particleType instanceof ParticleOptions) {
            param = (ParticleOptions) particleType;

        } else if (particleType == null) {
            throw new IllegalArgumentException("Particle type is not supported");

        } else if (options == null) {
            com.bergerkiller.bukkit.common.resources.ParticleType typeHandle;
            typeHandle = com.bergerkiller.bukkit.common.resources.ParticleType.byNMSParticleHandle(particleType);
            throw new IllegalArgumentException("Particle type " + typeHandle.toString() + " requires extra options, none specified");

        } else if (options instanceof com.bergerkiller.bukkit.common.wrappers.BlockData) {
            param = new net.minecraft.core.particles.BlockParticleOption(particleType,
                    (BlockState) ((com.bergerkiller.bukkit.common.wrappers.BlockData) options).getData());

        } else if (options instanceof org.bukkit.inventory.ItemStack) {
            net.minecraft.world.item.ItemStack itemstack = (net.minecraft.world.item.ItemStack) com.bergerkiller.bukkit.common.conversion.type.HandleConversion
                    .toItemStackHandle((org.bukkit.inventory.ItemStack) options);
  #if version >= 26.1
            param = new net.minecraft.core.particles.ItemParticleOption(particleType, itemstack.getItem());
  #else
            param = new net.minecraft.core.particles.ItemParticleOption(particleType, itemstack);
  #endif

  #if version >= 1.19
        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$SculkChargeOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$SculkChargeOptions sc = (com.bergerkiller.bukkit.common.resources.ParticleType$SculkChargeOptions) options;
            param = new net.minecraft.core.particles.SculkChargeParticleOptions(sc.roll);

        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$ShriekOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$ShriekOptions sh = (com.bergerkiller.bukkit.common.resources.ParticleType$ShriekOptions) options;
            param = new net.minecraft.core.particles.ShriekParticleOption(sh.delay);
  #endif

  #if version >= 1.17
        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$DustColorTransitionOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$DustColorTransitionOptions ts = (com.bergerkiller.bukkit.common.resources.ParticleType$DustColorTransitionOptions) options;
    #if version >= 1.21.2
            int color = ts.color.asRGB();
            int to_color = ts.endColor.asRGB();
    #elseif version >= 1.19.3
            org.joml.Vector3f color = new org.joml.Vector3f((float) ts.color.getRed() / 255.0f,
                                                            (float) ts.color.getGreen() / 255.0f,
                                                            (float) ts.color.getBlue() / 255.0f);
            org.joml.Vector3f to_color = new org.joml.Vector3f((float) ts.endColor.getRed() / 255.0f,
                                                               (float) ts.endColor.getGreen() / 255.0f,
                                                               (float) ts.endColor.getBlue() / 255.0f);
    #else
            com.mojang.math.Vector3fa color = new com.mojang.math.Vector3fa((float) ts.color.getRed() / 255.0f,
                                                                            (float) ts.color.getGreen() / 255.0f,
                                                                            (float) ts.color.getBlue() / 255.0f);
            com.mojang.math.Vector3fa to_color = new com.mojang.math.Vector3fa((float) ts.endColor.getRed() / 255.0f,
                                                                               (float) ts.endColor.getGreen() / 255.0f,
                                                                               (float) ts.endColor.getBlue() / 255.0f);
    #endif
            param = new net.minecraft.core.particles.DustColorTransitionOptions(color, to_color, ts.scale);

        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$VibrationOptions) {
    #if version >= 1.19
            #require net.minecraft.world.level.gameevent.EntityPositionSource static EntityPositionSource new_eps:<init>(com.mojang.datafixers.util.Either<net.minecraft.world.entity.Entity, com.mojang.datafixers.util.Either<java.util.UUID, Integer>> either, float f);
    #endif
            com.bergerkiller.bukkit.common.resources.ParticleType$VibrationOptions vo = (com.bergerkiller.bukkit.common.resources.ParticleType$VibrationOptions) options;
            net.minecraft.world.level.gameevent.PositionSource source;
            if (vo.destination instanceof com.bergerkiller.bukkit.common.resources.ParticleType$BlockPositionOption) {
                com.bergerkiller.bukkit.common.resources.ParticleType$BlockPositionOption dest = (com.bergerkiller.bukkit.common.resources.ParticleType$BlockPositionOption) vo.destination;
                source = new net.minecraft.world.level.gameevent.BlockPositionSource(new BlockPos(dest.x, dest.y, dest.z));
            } else if (vo.destination instanceof com.bergerkiller.bukkit.common.resources.ParticleType$EntityByIdPositionOption) {
                com.bergerkiller.bukkit.common.resources.ParticleType$EntityByIdPositionOption pos = (com.bergerkiller.bukkit.common.resources.ParticleType$EntityByIdPositionOption) vo.destination;
    #if version >= 1.19
                source = #new_eps(com.mojang.datafixers.util.Either.right((Object)com.mojang.datafixers.util.Either.right((Object)Integer.valueOf(pos.entityId))), pos.yOffset);
    #else
                source = new net.minecraft.world.level.gameevent.EntityPositionSource(pos.entityId);
    #endif
    #if version >= 1.19
            } else if (vo.destination instanceof com.bergerkiller.bukkit.common.resources.ParticleType$EntityByUUIDPositionOption) {
                com.bergerkiller.bukkit.common.resources.ParticleType$EntityByUUIDPositionOption pos = (com.bergerkiller.bukkit.common.resources.ParticleType$EntityByUUIDPositionOption) vo.destination;
                source = #new_eps(com.mojang.datafixers.util.Either.right((Object)com.mojang.datafixers.util.Either.left((Object)pos.entityUUID)), pos.yOffset);
    #endif
            } else if (vo.destination == null) {
                throw new IllegalArgumentException("Vibration option destination is null");
            } else {
                throw new IllegalArgumentException("Unknown Vibration position source: " + vo.destination);
            }

    #if version >= 1.19
            param = new net.minecraft.core.particles.VibrationParticleOption(source, vo.arrivalInTicks);
    #else
            BlockPos origin = new BlockPos(vo.origin.x, vo.origin.y, vo.origin.z);
            net.minecraft.world.level.gameevent.vibrations.VibrationPath path = new net.minecraft.world.level.gameevent.vibrations.VibrationPath(origin, source, vo.arrivalInTicks);
            param = new net.minecraft.core.particles.VibrationParticleOption(path);
    #endif
  #endif

        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions color = (com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions) options;
  #if version >= 1.20.5
            param = net.minecraft.core.particles.ColorParticleOption.create(particleType, color.color.asARGB());
  #else
            throw new IllegalArgumentException("Incompatible particle options: " + options.getClass().getName());
  #endif

        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$DustOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$DustOptions dust = (com.bergerkiller.bukkit.common.resources.ParticleType$DustOptions) options;

  #if version >= 1.21.2
            param = new net.minecraft.core.particles.DustParticleOptions(dust.color.asRGB(), dust.scale);
  #else
            float r = (float) dust.color.getRed() / 255.0f;
            float g = (float) dust.color.getGreen() / 255.0f;
            float b = (float) dust.color.getBlue() / 255.0f;
    #if version >= 1.19.3
            param = new net.minecraft.core.particles.DustParticleOptions(new org.joml.Vector3f(r,g,b), dust.scale);
    #elseif version >= 1.17
            param = new net.minecraft.core.particles.DustParticleOptions(new com.mojang.math.Vector3fa(r,g,b), dust.scale);
    #else
            param = new net.minecraft.core.particles.DustParticleOptions(r, g, b, dust.scale);
    #endif
  #endif

        } else {
            throw new IllegalArgumentException("Unknown particle options: " + options.getClass().getName());
        }
        instance#particle = param;

  #if version < 1.20.5
        // Set RGB fields when using the (ambient) entity effect particles and having colors specified
        if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions color = (com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions) options;
            //TODO: Implement this!
        }
  #endif
    }

    public (com.bergerkiller.bukkit.common.resources.ParticleType<?>) ParticleType<?> getParticleType() {
        ParticleOptions param = instance#particle;
        if (param == null) return null;
  #if version >= 1.18
        return param.getType();
  #elseif version >= 1.14.4
        return param.getParticle();
  #else
        return param.b();
  #endif
    }

#else
    // ParticleType + int[] args
    public optional void setParticle(ParticleType particleType, Object options) {
        int[] data;
        if (options instanceof com.bergerkiller.bukkit.common.wrappers.BlockData) {
            data = new int[1];
            data[0] = net.minecraft.world.level.block.Block.getCombinedId(
                    (BlockState) ((com.bergerkiller.bukkit.common.wrappers.BlockData) options).getData());

        } else if (options instanceof org.bukkit.inventory.ItemStack) {
            net.minecraft.world.item.ItemStack itemstack = (net.minecraft.world.item.ItemStack) com.bergerkiller.bukkit.common.conversion.type.HandleConversion
                    .toItemStackHandle((org.bukkit.inventory.ItemStack) options);
            data = new int[1];
            data[0] = net.minecraft.world.item.Item.getId(itemstack.getItem());

        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$DustOptions) {
            com.bergerkiller.bukkit.common.resources.ParticleType$DustOptions dust = (com.bergerkiller.bukkit.common.resources.ParticleType$DustOptions) options;

            // rgb is encoded in the random x/y/z of this packet
            float r = -1.0f + (float) dust.color.getRed() / 255.0f;
            float g = (float) dust.color.getGreen() / 255.0f;
            float b = (float) dust.color.getBlue() / 255.0f;
            #require ClientboundLevelParticlesPacket private float randomX:e;
            #require ClientboundLevelParticlesPacket private float randomY:f;
            #require ClientboundLevelParticlesPacket private float randomZ:g;
            #require ClientboundLevelParticlesPacket private float speed:h;
            #require ClientboundLevelParticlesPacket private int count:i;
            instance#randomX = r;
            instance#randomY = g;
            instance#randomZ = b;
            instance#speed = 1.0f;
            instance#count = 0;
            data = new int[0];

        } else if (options instanceof com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions) {
            // Set RGB fields when using the (ambient) entity effect particles and having colors specified
            //TODO: Implement this!
            com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions color = (com.bergerkiller.bukkit.common.resources.ParticleType$ColorOptions) options;
            data = new int[0];

        } else {
            data = new int[0];
        }
        instance#particle = particleType;
        instance#particleOptions = data;
    }

    public (com.bergerkiller.bukkit.common.resources.ParticleType<?>) ParticleType<?> getParticleType() {
        return instance#particle;
    }
#endif

    <code>
    public void setPos(double x, double y, double z) {
        setPosX(x);
        setPosY(y);
        setPosZ(z);
    }

    public void setPos(org.bukkit.util.Vector pos) {
        setPos(pos.getX(), pos.getY(), pos.getZ());
    }

    public void setPos(org.bukkit.Location loc) {
        setPos(loc.getX(), loc.getY(), loc.getZ());
    }

    public void setRandom(double rx, double ry, double rz) {
        setRandom((float) rx, (float) ry, (float) rz);
    }

    public void setRandom(float rx, float ry, float rz) {
        setRandomX(rx);
        setRandomY(ry);
        setRandomZ(rz);
    }

    public void setRandom(org.bukkit.util.Vector random) {
        setRandom(random.getX(), random.getY(), random.getZ());
    }
    </code>

    public double getPosX() {
        return (double) instance#pos_x;
    }

    public double getPosY() {
        return (double) instance#pos_y;
    }

    public double getPosZ() {
        return (double) instance#pos_z;
    }

#if version >= 1.15
    public void setPosX(double x) {
        instance#pos_x = x;
    }

    public void setPosY(double y) {
        instance#pos_y = y;
    }

    public void setPosZ(double z) {
        instance#pos_z = z;
    }
#else
    public void setPosX(double x) {
        instance#pos_x = (float)x;
    }

    public void setPosY(double y) {
        instance#pos_y = (float)y;
    }

    public void setPosZ(double z) {
        instance#pos_z = (float)z;
    }
#endif
}

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 {
#if version >= 1.17
    private final (IntVector3) BlockPos signPosition:pos;
#else
    private (IntVector3) BlockPos signPosition:a;
#endif

    <code>
    public static ClientboundOpenSignEditorPacketHandle createNew(IntVector3 signPosition) {
        return createNew(signPosition, true);
    }
    </code>

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

#if version >= 1.20
    #require ClientboundOpenSignEditorPacket private final boolean isFrontText;
    public boolean isFrontText() { return instance#isFrontText; }
    public void setFrontText(boolean front) { instance#isFrontText = front; }
#else
    public boolean isFrontText() {
        return true;
    }

    public void setFrontText(boolean front) {
        if (!front) {
            throw new UnsupportedOperationException("Back text not supported on this version of Minecraft");
        }
    }
#endif
}

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.17
    private boolean initial:reset;
#else
    private boolean initial:a;
#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;
    }
}
