package net.minecraft.network.protocol.game;

import net.minecraft.core.EnumDirection;
import net.minecraft.core.BlockPosition;
import net.minecraft.core.particles.Particle;
import net.minecraft.core.particles.ParticleParam;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo;
import net.minecraft.network.syncher.DataWatcher;
import net.minecraft.network.syncher.DataWatcher.PackedItem;
import net.minecraft.network.syncher.DataWatcher.WatchableObject;
import net.minecraft.network.PacketDataSerializer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagLongArray;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.WorldServer;
import net.minecraft.server.level.EntityPlayer;
import net.minecraft.server.level.EntityPlayer.RespawnConfig;
import net.minecraft.sounds.SoundCategory;
import net.minecraft.world.entity.ai.attributes.AttributeMapBase;
import net.minecraft.world.entity.ai.attributes.AttributeModifiable;
import net.minecraft.world.entity.EnumItemSlot;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.EnumHand;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectList;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.PositionMoveRotation;
import net.minecraft.world.entity.RelativeMovement;
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.TileEntityTypes;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.levelgen.HeightMap;
import net.minecraft.world.level.levelgen.HeightMap.Type;
import net.minecraft.world.level.EnumGamemode;
import net.minecraft.world.level.dimension.DimensionManager;
import net.minecraft.world.level.World;
import net.minecraft.world.phys.MovingObjectPositionBlock;
import net.minecraft.world.phys.Vec3D;

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.UseAction;
import com.bergerkiller.bukkit.common.wrappers.Holder;
import com.bergerkiller.bukkit.common.wrappers.HumanHand;
import com.bergerkiller.bukkit.common.wrappers.RelativeFlags;
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.PacketPlayOutEntityDestroyHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayInUseEntityHandle.EnumEntityUseActionHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutTitleHandle.EnumTitleActionHandle
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutRemoveEntityEffectHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutMapChunkHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutAttachEntityHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutCameraHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutEntityEquipmentHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutEntityEquipmentHandle.OwnerType;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutMountHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutEntityMetadataHandle
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayInBlockDigHandle.EnumPlayerDigTypeHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutOpenWindowHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutPositionHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayInSteerVehicleHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlotHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributesHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutTitleHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutTileEntityDataHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutExplosionHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutEntityEffectHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutAbilitiesHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayInSpectateHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutWorldParticlesHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutBlockChangeHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutSetSlotHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.ClientboundBundlePacketHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutCustomSoundEffectHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutOpenSignEditorHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutStopSoundHandle;
import com.bergerkiller.generated.net.minecraft.network.protocol.game.PacketPlayOutSpawnPositionHandle;
import com.bergerkiller.generated.net.minecraft.resources.MinecraftKeyHandle;
import com.bergerkiller.generated.net.minecraft.sounds.SoundCategoryHandle;
import com.bergerkiller.generated.net.minecraft.world.effect.MobEffectListHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.ai.attributes.AttributeModifiableHandle;
import com.bergerkiller.generated.net.minecraft.server.level.EntityPlayerHandle.RespawnConfigHandle;

class PacketPlayOutEntityDestroy extends Packet {
#if version >= 1.17.1
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy private it.unimi.dsi.fastutil.ints.IntList entityIds;
#elseif version >= 1.17
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy private final int entityId;
#else
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy 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 (PacketPlayOutEntityDestroyHandle) PacketPlayOutEntityDestroy createNewSingle(int entityId) {
#if version >= 1.17.1
        if (entityId >= 0) {
            int[] ids = new int[1];
            ids[0] = entityId;
            return new PacketPlayOutEntityDestroy(ids);
        } else {
            return new PacketPlayOutEntityDestroy(new int[0]);
        }
#elseif version >= 1.17
        return new PacketPlayOutEntityDestroy(entityId);
#else
        if (entityId >= 0) {
            int[] ids = new int[1];
            ids[0] = entityId;
            return new PacketPlayOutEntityDestroy(ids);
        } else {
            return new PacketPlayOutEntityDestroy(new int[0]);
        }
#endif
    }

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

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

    // Since 1.17 there is an immutable UseType class storing all the details of the packet
    // It is incorrectly remapped to EnumEntityUseAction (!)
#if version >= 1.18
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity.UseType public abstract (Enum) net.minecraft.network.protocol.game.PacketPlayInUseEntity.EnumEntityUseAction getType();
#elseif version >= 1.17
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity.UseType public abstract (Enum) net.minecraft.network.protocol.game.PacketPlayInUseEntity.EnumEntityUseAction getType:a();
#elseif version >= 1.13
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity public PacketPlayInUseEntity.EnumEntityUseAction getType:b();
#else
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity public PacketPlayInUseEntity.EnumEntityUseAction getType:a();
#endif

#if version >= 1.17
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity private final PacketPlayInUseEntity.UseType action;
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity.d private final readonly net.minecraft.world.EnumHand interactHand:hand;
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity.e private final readonly net.minecraft.world.EnumHand interactAtHand:hand;
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity.e private final readonly net.minecraft.world.phys.Vec3D interactAtLocation:location;
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity public String getActionName() {
        return instance#action#getType().name();
    }

    public boolean isInteract() {
        return instance#getActionName().equals("INTERACT");
    }

    public boolean isInteractAt() {
        return instance#getActionName().equals("INTERACT_AT");
    }

    public boolean isAttack() {
        return instance#getActionName().equals("ATTACK");
    }

    public com.bergerkiller.bukkit.common.wrappers.HumanHand getInteractHand(org.bukkit.entity.HumanEntity humanEntity) {
        EnumHand hand;
        Object useAction = instance#action;
        String name = useAction#getType().name();
        if (name.equals("INTERACT")) {
            hand = useAction#interactHand;
        } else if (name.equals("INTERACT_AT")) {
            hand = useAction#interactAtHand;
        } else {
            return null;
        }

        return com.bergerkiller.bukkit.common.wrappers.HumanHand.fromNMSEnumHand(humanEntity, hand);
    }

    public (org.bukkit.util.Vector) Vec3D getInteractAtPosition() {
        Object useAction = instance#action;
        if (useAction#getType().name().equals("INTERACT_AT")) {
            return useAction#interactAtLocation;
        } else {
            return null;
        }
    }

    public void setAttack() {
        #require net.minecraft.network.protocol.game.PacketPlayInUseEntity static final PacketPlayInUseEntity.UseType ATTACK_ACTION;
        instance#action = #ATTACK_ACTION;
    }

    public void setInteract(org.bukkit.entity.HumanEntity humanEntity, com.bergerkiller.bukkit.common.wrappers.HumanHand hand) {
        #require net.minecraft.network.protocol.game.PacketPlayInUseEntity.d d createUseTypeInteract:<init>(net.minecraft.world.EnumHand hand);
        EnumHand hand = (EnumHand) com.bergerkiller.bukkit.common.wrappers.HumanHand.toNMSEnumHand(humanEntity, hand);
        instance#action = #createUseTypeInteract(hand);
    }
#else
    public boolean isInteract() {
        return instance#getType() == PacketPlayInUseEntity$EnumEntityUseAction.INTERACT;
    }

    public boolean isInteractAt() {
        return instance#getType() == PacketPlayInUseEntity$EnumEntityUseAction.INTERACT_AT;
    }

    public boolean isAttack() {
        return instance#getType() == PacketPlayInUseEntity$EnumEntityUseAction.ATTACK;
    }

    public com.bergerkiller.bukkit.common.wrappers.HumanHand getInteractHand(org.bukkit.entity.HumanEntity humanEntity) {
  #if version >= 1.9
    #if version >= 1.13
        EnumHand hand = instance.c();
    #else
        EnumHand hand = instance.b();
    #endif
        return com.bergerkiller.bukkit.common.wrappers.HumanHand.fromNMSEnumHand(humanEntity, hand);
  #else
        PacketPlayInUseEntity$EnumEntityUseAction type = instance#getType();
        if (type == PacketPlayInUseEntity$EnumEntityUseAction.INTERACT || type == PacketPlayInUseEntity$EnumEntityUseAction.INTERACT_AT) {
            return com.bergerkiller.bukkit.common.wrappers.HumanHand.RIGHT;
        } else {
            return null;
        }
  #endif
    }

    public (org.bukkit.util.Vector) Vec3D getInteractAtPosition() {
  #select version >=
  #case 1.13: return instance.d();
  #case 1.9:  return instance.c();
  #case else: return instance.b();
  #endselect
    }

    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity private PacketPlayInUseEntity.EnumEntityUseAction action;
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity private net.minecraft.world.phys.Vec3D offset:c;
  #if version >= 1.9
    #require net.minecraft.network.protocol.game.PacketPlayInUseEntity private net.minecraft.world.EnumHand hand:d;
  #endif

    public void setAttack() {
        instance#action = PacketPlayInUseEntity$EnumEntityUseAction.ATTACK;
        instance#offset = null;
  #if version >= 1.9
        instance#hand = null;
  #endif
    }

    public void setInteract(org.bukkit.entity.HumanEntity humanEntity, com.bergerkiller.bukkit.common.wrappers.HumanHand hand) {
        instance#action = PacketPlayInUseEntity$EnumEntityUseAction.INTERACT;
        instance#offset = null;
  #if version >= 1.9
        EnumHand hand = (EnumHand) com.bergerkiller.bukkit.common.wrappers.HumanHand.toNMSEnumHand(humanEntity, hand);
        instance#hand = hand;
  #endif
    }
#endif

#if version >= 1.17
    public static boolean hasSecondaryActionField() {
        return true;
    }

  #if version >= 1.18
    public boolean isUsingSecondaryAction();
  #else
    public boolean isUsingSecondaryAction:b();
  #endif

    public void setUsingSecondaryAction(boolean using) {
        #require net.minecraft.network.protocol.game.PacketPlayInUseEntity private boolean usingSecondaryAction;
        instance#usingSecondaryAction = using;
    }
#elseif version >= 1.16
    public static boolean hasSecondaryActionField() {
        return true;
    }

    public boolean isUsingSecondaryAction:e();

    public void setUsingSecondaryAction(boolean using) {
        #require net.minecraft.network.protocol.game.PacketPlayInUseEntity private boolean usingSecondaryOption:e;
        instance#usingSecondaryOption = using;
    }
#else
    public static boolean hasSecondaryActionField() {
        return false;
    }

    public boolean isUsingSecondaryAction() {
        return false;
    }

    public void setUsingSecondaryAction(boolean using) {
    }
#endif
}

class PacketPlayInBlockPlace extends Packet {
#if version >= 1.17
    private optional (Object) EnumHand enumHand:hand;
#elseif version >= 1.9
    private optional (Object) EnumHand enumHand:a;
#else
    private optional (Object) EnumHand enumHand:###;
#endif

    // Spigot only
    public optional long timestamp;

#if version >= 1.21
    #require PacketPlayInBlockPlace private final float yRot;
    #require PacketPlayInBlockPlace private final float xRot;

    public float getYaw() { return instance#yRot; }
    public float getPitch() { return instance#xRot; }
    public void setYaw(float yaw) { instance#yRot = yaw; }
    public void setPitch(float pitch) { instance#xRot = pitch; }
#else
    public float getYaw() { return 0.0f; }
    public float getPitch() { return 0.0f; }
    public void setYaw(float yaw) {}
    public void setPitch(float pitch) { }
#endif

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

    public void setTimestamp(long timestamp) {
        if (T.timestamp.isAvailable()) {
            T.timestamp.setLong(getRaw(), timestamp);
        }
    }

    public com.bergerkiller.bukkit.common.wrappers.HumanHand getHand(org.bukkit.entity.HumanEntity humanEntity) {
        return internalGetHand(T.enumHand, humanEntity);
    }

    public void setHand(org.bukkit.entity.HumanEntity humanEntity, com.bergerkiller.bukkit.common.wrappers.HumanHand hand) {
        internalSetHand(T.enumHand, humanEntity, hand);
    }
    </code>
}

// PacketPlayInBlockPlace on MC 1.8.9 and before when int direction == 255
class PacketPlayInUseItem extends Packet {
    // Hand since 1.9
#if version >= 1.9
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.world.EnumHand enumHand:hand;
  #elseif version >= 1.14
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.world.EnumHand enumHand:b;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.world.EnumHand enumHand:c;
  #endif
    public com.bergerkiller.bukkit.common.wrappers.HumanHand getHand(org.bukkit.entity.HumanEntity humanEntity) {
        return com.bergerkiller.bukkit.common.wrappers.HumanHand.fromNMSEnumHand(humanEntity, instance#enumHand);
    }

    public void setHand(org.bukkit.entity.HumanEntity humanEntity, com.bergerkiller.bukkit.common.wrappers.HumanHand hand) {
        instance#enumHand = (EnumHand) hand.toNMSEnumHand(humanEntity);
    }
#else
    public com.bergerkiller.bukkit.common.wrappers.HumanHand getHand(org.bukkit.entity.HumanEntity humanEntity) {
        return com.bergerkiller.bukkit.common.wrappers.HumanHand.RIGHT;
    }

    public void setHand(org.bukkit.entity.HumanEntity humanEntity, com.bergerkiller.bukkit.common.wrappers.HumanHand hand) {
    }
#endif

    // Initializes the moving object position block field, when required
#if version >= 1.18
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem public net.minecraft.world.phys.MovingObjectPositionBlock getHitResult();
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem public net.minecraft.world.phys.MovingObjectPositionBlock initMovingObject() {
        net.minecraft.world.phys.MovingObjectPositionBlock moving = instance.getHitResult();
        if (moving != null) {
            return moving;
        }

        #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.world.phys.MovingObjectPositionBlock movingObject:blockHit;
        moving = net.minecraft.world.phys.MovingObjectPositionBlock.miss(Vec3D.ZERO, EnumDirection.DOWN, BlockPosition.ZERO);
        instance#movingObject = moving;
        return moving;
    }

#elseif version >= 1.14
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem public net.minecraft.world.phys.MovingObjectPositionBlock getHitResult:c();
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem public net.minecraft.world.phys.MovingObjectPositionBlock initMovingObject() {
        net.minecraft.world.phys.MovingObjectPositionBlock moving = instance.c();
        if (moving != null) {
            return moving;
        }

  #if version >= 1.17
        #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.world.phys.MovingObjectPositionBlock movingObject:blockHit;
  #else
        #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.world.phys.MovingObjectPositionBlock movingObject:a;
  #endif

        Vec3D origin = Vec3D.ZERO;

        moving = net.minecraft.world.phys.MovingObjectPositionBlock.a(origin, EnumDirection.DOWN, BlockPosition.ZERO);
        instance#movingObject = moving; 
        return moving;
    }

#endif

    // Direction
#if version >= 1.14
  #if version >= 1.17
    #require net.minecraft.world.phys.MovingObjectPositionBlock private final net.minecraft.core.EnumDirection direction_face:direction;
  #else
    #require net.minecraft.world.phys.MovingObjectPositionBlock private final net.minecraft.core.EnumDirection direction_face:b;
  #endif
    public (org.bukkit.block.BlockFace) EnumDirection getDirection() {
        MovingObjectPositionBlock result = instance#getHitResult();
        return (result == null) ? null : result.getDirection();
    }
    public void setDirection((org.bukkit.block.BlockFace) EnumDirection direction) {
        instance#initMovingObject()#direction_face = direction;
    }
#elseif version >= 1.9
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.core.EnumDirection direction_face:b;
    public (org.bukkit.block.BlockFace) EnumDirection getDirection() {
        return instance#direction_face;
    }
    public void setDirection((org.bukkit.block.BlockFace) EnumDirection direction) {
        instance#direction_face = direction;
    }
#else
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private int direction_index:c;
    public (org.bukkit.block.BlockFace) EnumDirection getDirection() {
        int index = instance#direction_index;
        return EnumDirection.fromType1(index);
    }
    public void setDirection((org.bukkit.block.BlockFace) EnumDirection direction) {
        instance#direction_index = direction.a();
    }
#endif

    // Direction index value of 255 was used for the 'block place' packet on 1.8.9 and before
#if version >= 1.9
    public boolean isBlockPlacePacket() {
        return false;
    }
    public void setBlockPlacePacket() {
    }
#else
    public boolean isBlockPlacePacket() {
        int index = instance#direction_index;
        return index == 255;
    }
    public void setBlockPlacePacket() {
        instance#direction_index = 255;
    }
#endif

#if version >= 1.14
  #if version >= 1.17
    #require net.minecraft.world.phys.MovingObjectPosition protected final net.minecraft.world.phys.Vec3D position:location;
    #require net.minecraft.world.phys.MovingObjectPositionBlock private final net.minecraft.core.BlockPosition blockPosition:blockPos;
  #else
    #require net.minecraft.world.phys.MovingObjectPosition protected final net.minecraft.world.phys.Vec3D position:pos;
    #require net.minecraft.world.phys.MovingObjectPositionBlock private final net.minecraft.core.BlockPosition blockPosition:c;
  #endif
    public (IntVector3) BlockPosition getPosition() {
        MovingObjectPositionBlock result = instance#getHitResult();
  #if version >= 1.18
        return (result == null) ? null : result.getBlockPos();
  #else
        return (result == null) ? null : result.getBlockPosition();
  #endif
    }
    public void setPosition((IntVector3) BlockPosition blockPosition) {
        MovingObjectPositionBlock result = instance#initMovingObject();

        // Make sure to preserve deltas
  #if version >= 1.18
        Vec3D position = result.getLocation();
        double new_x = position.x - result.getBlockPos().getX() + blockPosition.getX();
        double new_y = position.y - result.getBlockPos().getY() + blockPosition.getY();
        double new_z = position.z - result.getBlockPos().getZ() + blockPosition.getZ();
  #else
        Vec3D position = result.getPos();
        double new_x = position.x - result.getBlockPosition().getX() + blockPosition.getX();
        double new_y = position.y - result.getBlockPosition().getY() + blockPosition.getY();
        double new_z = position.z - result.getBlockPosition().getZ() + blockPosition.getZ();
  #endif
        Vec3D new_pos = new Vec3D(new_x, new_y, new_z);
        result#position = new_pos;
        result#blockPosition = blockPosition;
    }
    public float getDeltaX() {
        MovingObjectPositionBlock result = instance#getHitResult();
        if (result == null) return 0.0f;
  #if version >= 1.18
        return (float) (result.getLocation().x - (double) result.getBlockPos().getX());
  #else
        return (float) (result.getPos().x - (double) result.getBlockPosition().getX());
  #endif
    }
    public float getDeltaY() {
        MovingObjectPositionBlock result = instance#getHitResult();
        if (result == null) return 0.0f;
  #if version >= 1.18
        return (float) (result.getLocation().y - (double) result.getBlockPos().getY());
  #else
        return (float) (result.getPos().y - (double) result.getBlockPosition().getY());
  #endif
    }
    public float getDeltaZ() {
        MovingObjectPositionBlock result = instance#getHitResult();
        if (result == null) return 0.0f;
  #if version >= 1.18
        return (float) (result.getLocation().z - (double) result.getBlockPos().getZ());
  #else
        return (float) (result.getPos().z - (double) result.getBlockPosition().getZ());
  #endif
    }
    public void setDeltaX(float dx) {
        MovingObjectPositionBlock result = instance#initMovingObject();
  #if version >= 1.18
        Vec3D old_position = result.getLocation();
        Vec3D new_position = new Vec3D((double) dx + result.getBlockPos().getX(), old_position.y, old_position.z);
  #else
        Vec3D old_position = result.getPos();
        Vec3D new_position = new Vec3D((double) dx + result.getBlockPosition().getX(), old_position.y, old_position.z);
  #endif
        result#position = new_position;
    }
    public void setDeltaY(float dy) {
        MovingObjectPositionBlock result = instance#initMovingObject();
  #if version >= 1.18
        Vec3D old_position = result.getLocation();
        Vec3D new_position = new Vec3D(old_position.x, (double) dy + result.getBlockPos().getY(), old_position.z);
  #else
        Vec3D old_position = result.getPos();
        Vec3D new_position = new Vec3D(old_position.x, (double) dy + result.getBlockPosition().getY(), old_position.z);
  #endif
        result#position = new_position;
    }
    public void setDeltaZ(float dz) {
        MovingObjectPositionBlock result = instance#initMovingObject();
  #if version >= 1.18
        Vec3D old_position = result.getLocation();
        Vec3D new_position = new Vec3D(old_position.x, old_position.y, (double) dz + result.getBlockPos().getZ());
  #else
        Vec3D old_position = result.getPos();
        Vec3D new_position = new Vec3D(old_position.x, old_position.y, (double) dz + result.getBlockPosition().getZ());
  #endif
        result#position = new_position;
    }
#else
  #if version >= 1.9
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.core.BlockPosition position:a;
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private float deltaX:d;
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private float deltaY:e;
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private float deltaZ:f;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private net.minecraft.core.BlockPosition position:b;
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private float deltaX:e;
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private float deltaY:f;
    #require net.minecraft.network.protocol.game.PacketPlayInUseItem private float deltaZ:g;
  #endif
    public (IntVector3) BlockPosition getPosition() {
        return instance#position;
    }
    public void setPosition((IntVector3) BlockPosition position) {
        instance#position = position;
    }
    public float getDeltaX() {
        return instance#deltaX;
    }
    public float getDeltaY() {
        return instance#deltaY;
    }
    public float getDeltaZ() {
        return instance#deltaZ;
    }
    public void setDeltaX(float dx) {
        return instance#deltaX = dx;
    }
    public void setDeltaY(float dy) {
        return instance#deltaY = dy;
    }
    public void setDeltaZ(float dz) {
        return instance#deltaZ = dz;
    }
#endif

    // Spigot
    public optional long timestamp;

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

    public void setTimestamp(long timestamp) {
        if (T.timestamp.isAvailable()) {
            T.timestamp.setLong(getRaw(), timestamp);
        }
    }
    </code>
}

class PacketPlayInBlockDig extends Packet {
#if version >= 1.17
    private (IntVector3) BlockPosition position:pos;
    private (org.bukkit.block.BlockFace) EnumDirection direction;
    private (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType digType:action;
#else
    private (IntVector3) BlockPosition position:a;
    private (org.bukkit.block.BlockFace) EnumDirection direction:b;
    private (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType digType:c;
#endif

    class PacketPlayInBlockDig.EnumPlayerDigType {
        enum (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType START_DESTROY_BLOCK;
        enum (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType ABORT_DESTROY_BLOCK;
        enum (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType STOP_DESTROY_BLOCK;
        enum (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType DROP_ALL_ITEMS;
        enum (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType DROP_ITEM;
        enum (EnumPlayerDigTypeHandle) PacketPlayInBlockDig.EnumPlayerDigType RELEASE_USE_ITEM;
        //TODO: SWAP_ITEM_WITH_OFFHAND?
    }
}

// Gone since 1.17, we don't use it, it's not worth the time to support this.
/*
class PacketPlayOutTitle extends Packet {
    private (PacketPlayOutTitleHandle.EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction action:a;
    private (ChatText) IChatBaseComponent title:b;

    // Introduced in newer 1.16.5 paperspigot builds, replacing the "components" field
    // If this field is set, it overrules the 'title' field
#if exists net.minecraft.network.protocol.game.PacketPlayOutTitle public net.kyori.adventure.text.Component adventure$text;
    public unknown net.kyori.adventure.text.Component adventure$text;
#endif

    private int fadeIn:c;
    private int stay:d;
    private int fadeOut:e;

    // Note: paper includes a 'components' field for md_5's chat api
    //       if this field is set, it overrules the 'title' field
    // public net.md_5.bungee.api.chat.BaseComponent[] components;

    class PacketPlayOutTitle.EnumTitleAction {
        enum (EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction TITLE;
        enum (EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction SUBTITLE;
        enum optional (EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction ACTIONBAR;
        enum (EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction TIMES;
        enum (EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction CLEAR;
        enum (EnumTitleActionHandle) PacketPlayOutTitle.EnumTitleAction RESET;
    }
}
*/

class PacketPlayOutCollect 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 PacketPlayOutSetSlot 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 (PacketPlayOutSetSlotHandle) PacketPlayOutSetSlot 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 PacketPlayOutSetSlot(containerId, 0, slot, item);
#else
        return new PacketPlayOutSetSlot(containerId, slot, item);
#endif
    }
}

class PacketPlayOutWindowData 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 PacketPlayOutWindowItems 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 PacketPlayOutCloseWindow extends Packet {
#if version >= 1.17
    private int windowId:containerId;
#else
    private int windowId:a;
#endif
}

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

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

#if version >= 1.19
    #require net.minecraft.network.protocol.game.PacketPlayOutOpenWindow private final net.minecraft.world.inventory.Containers<?> 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.Containers)nmsType;
    }
#elseif version >= 1.14
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.PacketPlayOutOpenWindow private int windowTypeId:type;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayOutOpenWindow 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.PacketPlayOutOpenWindow private String windowName:b;
    #require net.minecraft.network.protocol.game.PacketPlayOutOpenWindow 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 PacketPlayInArmAnimation extends Packet {
#if version >= 1.17
    private optional (Object) EnumHand enumHand:hand;
#elseif version >= 1.9
    private optional (Object) EnumHand enumHand:a;
#else
    private optional (Object) EnumHand enumHand:###;
#endif

    <code>
    public com.bergerkiller.bukkit.common.wrappers.HumanHand getHand(org.bukkit.entity.HumanEntity humanEntity) {
        return internalGetHand(T.enumHand, humanEntity);
    }

    public void setHand(org.bukkit.entity.HumanEntity humanEntity, com.bergerkiller.bukkit.common.wrappers.HumanHand hand) {
        internalSetHand(T.enumHand, humanEntity, hand);
    }
    </code>
}

class PacketPlayOutEntityEffect 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.PacketPlayOutEntityEffect 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.PacketPlayOutEntityEffect private final byte effectAmplifier;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityEffect 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<MobEffectListHandle>) net.minecraft.core.Holder<MobEffectList> getEffect();
    public void setEffect((Holder<MobEffectListHandle>) net.minecraft.core.Holder<MobEffectList> effect) {
        #require net.minecraft.network.protocol.game.PacketPlayOutEntityEffect private final net.minecraft.core.Holder<MobEffectList> effect;
        instance#effect = effect;
    }
#elseif version >= 1.19
    public (Holder<MobEffectListHandle>) MobEffectList getEffect();
    public void setEffect((Holder<MobEffectListHandle>) MobEffectList effect) {
        #require net.minecraft.network.protocol.game.PacketPlayOutEntityEffect private final MobEffectList effect;
        instance#effect = effect;
    }
#elseif version >= 1.18.2
    public (Holder<MobEffectListHandle>) int getEffect:getEffectId();
    public void setEffect((Holder<MobEffectListHandle>) int effect) {
        #require net.minecraft.network.protocol.game.PacketPlayOutEntityEffect private final int effectId;
        instance#effectId = effect;
    }
#else
  #if version >= 1.17
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityEffect private final byte effectId;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityEffect private byte effectId:b;
  #endif

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

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

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

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

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

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

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

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

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

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

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

class PacketPlayOutLogin 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 PacketPlayOutLogin private readonly int playerId;
  #else
        #require PacketPlayOutLogin private readonly int playerId:a;
  #endif
        return instance#playerId;
    }

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

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

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

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

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

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

    public (org.bukkit.GameMode) EnumGamemode getGameMode() { return instance#gameType; }
    public (DimensionType) DimensionType getDimensionType() { return instance#dimensionType; }
#endif

#if version >= 1.20.2
    public (org.bukkit.GameMode) EnumGamemode 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 PacketPlayOutLogin private net.minecraft.world.level.EnumGamemode previousGameMode:previousGameType;
  #else
    #require PacketPlayOutLogin private net.minecraft.world.level.EnumGamemode previousGameMode:e;
  #endif

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

    // Difficulty field only exists <= 1.13.2
#if version >= 1.14
    private optional (org.bukkit.Difficulty) EnumDifficulty difficulty:###;
#else
    private optional (org.bukkit.Difficulty) EnumDifficulty 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) WorldServer 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.PacketPlayOutLogin private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.obfuscateSeed(world.getSeed());
#elseif version >= 1.17
    //    #require net.minecraft.network.protocol.game.PacketPlayOutLogin private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.16
    //    #require net.minecraft.network.protocol.game.PacketPlayOutLogin private long encryptedWorldSeed:b;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.15
    //    #require net.minecraft.network.protocol.game.PacketPlayOutLogin private long encryptedWorldSeed:b;
    //    instance#encryptedWorldSeed = WorldData.c(world.getWorldData().getSeed());
#endif
    //}
}

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

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

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

    public (DimensionType) DimensionType getDimensionType() { return instance#dimensionType; }
    public (org.bukkit.GameMode) EnumGamemode getGamemode() { return instance#gameType; }
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> 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<World> b;

    // Difficulty field only exists <= 1.13.2
#if version >= 1.14
    private optional (org.bukkit.Difficulty) EnumDifficulty difficulty:###;
#else
    private optional (org.bukkit.Difficulty) EnumDifficulty 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) EnumGamemode getPreviousGameMode() {
        return instance.commonPlayerSpawnInfo().previousGameType();
    }
#elseif version >= 1.16
  #if version >= 1.17
    #require PacketPlayOutRespawn private net.minecraft.world.level.EnumGamemode previousGameMode:previousPlayerGameType;
  #else
    #require PacketPlayOutRespawn private net.minecraft.world.level.EnumGamemode previousGameMode:e;
  #endif

    public (org.bukkit.GameMode) EnumGamemode getPreviousGameMode() {
        EnumGamemode mode = instance#previousGameMode;
  #if version <= 1.16.5
        if (mode == EnumGamemode.NOT_SET) {
            return null;
        }
  #endif
        return mode;
    }
#else
    public (org.bukkit.GameMode) EnumGamemode 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) WorldServer 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.PacketPlayOutRespawn private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.obfuscateSeed(world.getSeed());
#elseif version >= 1.17
    //    #require net.minecraft.network.protocol.game.PacketPlayOutRespawn private long encryptedWorldSeed:seed;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.16
    //    #require net.minecraft.network.protocol.game.PacketPlayOutRespawn private long encryptedWorldSeed:c;
    //    instance#encryptedWorldSeed = BiomeManager.a(world.getSeed());
#elseif version >= 1.15
    //    #require net.minecraft.network.protocol.game.PacketPlayOutRespawn private long encryptedWorldSeed:b;
    //    instance#encryptedWorldSeed = WorldData.c(world.getWorldData().getSeed());
#endif
    //}
}

class PacketPlayOutUpdateHealth 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 PacketPlayOutExperience 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 PacketPlayOutUpdateTime extends Packet {
#if version >= 1.17
    private long gameTime;
    private long dayTime;
#else
    private long gameTime:a;
    private long dayTime:b;
#endif
}

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

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

// Is 'ClientboundLevelChunkWithLightPacket' on Minecraft 1.18 and later
// Then all data is stored in Chunk and Light data fields, not individual fields
// This is actually the ClientboundLevelChunkWithLightPacket
//
//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 PacketPlayOutMapChunk extends Packet {
#if version >= 1.17
    private int x;
    private int z;
#else
    private int x:a;
    private int z:b;
#endif

    public static (PacketPlayOutMapChunkHandle) PacketPlayOutMapChunk createNew() {
#if version >= 1.20.5
        #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private PacketPlayOutMapChunk createMapChunkPacket:<init>(net.minecraft.network.RegistryFriendlyByteBuf buf);
        return #createMapChunkPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#elseif version >= 1.17
        return new PacketPlayOutMapChunk(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new PacketPlayOutMapChunk();
#endif
    }

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

        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$Type heightmapType = (net.minecraft.world.level.levelgen.HeightMap$Type) 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.PacketPlayOutMapChunk private NBTTagCompound heightmapsField:d;
        return instance#heightmapsField;
#else
        return null; // Not available
#endif
    }

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

        net.minecraft.world.level.levelgen.HeightMap$Type[] heightmapTypes = net.minecraft.world.level.levelgen.HeightMap$Type.values();

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

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

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

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

            heightmaps.put(heightmapType, heightmapData);
        }

        #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final Map<HeightMap.Type, long[]> heightmaps;
        instance#heightmaps = heightmaps;

#elseif version >= 1.18
        #require net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData private final NBTTagCompound heightmaps;
        net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData data = instance.getChunkData();
        data#heightmaps = heightmapsData;
#elseif version >= 1.17
        #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private final NBTTagCompound heightmaps;
        instance#heightmaps = heightmapsData;
#elseif version >= 1.14
        #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private NBTTagCompound 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.PacketPlayOutMapChunk private byte[] chunkDataBuffer:buffer;
#elseif version >= 1.15
    #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private byte[] chunkDataBuffer:f;
#elseif version >= 1.14
    #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private byte[] chunkDataBuffer:e;
#elseif version >= 1.9
    #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private byte[] chunkDataBuffer:d;
#else
    #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private PacketPlayOutMapChunk.ChunkMap chunkData:c;
    #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk.ChunkMap  public byte[] chunkDataBuffer:a;
#endif

    public byte[] getBuffer() {
#if version >= 1.18
        net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData data = instance.getChunkData();
        return data#chunkDataBuffer;
#elseif version >= 1.9
        return instance#chunkDataBuffer;
#else
        PacketPlayOutMapChunk$ChunkMap data = instance#chunkData;
        return data#chunkDataBuffer;
#endif
    }

    public void setBuffer(byte[] buffer) {
#if version >= 1.18
        net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData data = instance.getChunkData();
        data#chunkDataBuffer = buffer;
#elseif version >= 1.9
        instance#chunkDataBuffer = buffer;
#else
        PacketPlayOutMapChunk$ChunkMap 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.BlockEntityData> 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.PacketPlayOutMapChunk private (List<BlockStateChange) List<NBTTagCompound> blockEntitiesTags;
  #elseif version >= 1.15
        #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private (List<BlockStateChange) List<NBTTagCompound> blockEntitiesTags:g;
  #elseif version >= 1.14
        #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private (List<BlockStateChange) List<NBTTagCompound> blockEntitiesTags:f;
  #else
        #require net.minecraft.network.protocol.game.PacketPlayOutMapChunk private (List<BlockStateChange) List<NBTTagCompound> 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 PacketPlayOutUnloadChunk extends Packet {
#if 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 PacketPlayOutUnloadChunk private final net.minecraft.world.level.ChunkCoordIntPair pos;
        net.minecraft.world.level.ChunkCoordIntPair pair = new net.minecraft.world.level.ChunkCoordIntPair(cx, cz);
        instance#pos = pair;
    }
#else
  #if version >= 1.17
    #require PacketPlayOutUnloadChunk private int cx:x;
    #require PacketPlayOutUnloadChunk private int cz:z;
  #elseif version >= 1.9
    #require PacketPlayOutUnloadChunk private int cx:a;
    #require PacketPlayOutUnloadChunk 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 PacketPlayOutNamedSoundEffect 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.SoundEffect> sound;
  #else
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.sounds.SoundEffect sound;
  #endif
    private optional (String) SoundCategory 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.SoundEffect sound:a;
    private optional (String) SoundCategory 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) SoundCategory 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 PacketPlayOutNamedSoundEffect on MC 1.8 - 1.8.8, where it
// can only really play Vanilla sound effects.
// It is also handled by PacketPlayOutNamedSoundEffect on MC 1.19.3+ where the two packets
// were merged into one.
class PacketPlayOutCustomSoundEffect 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.SoundEffect> sound;
#elseif version >= 1.17
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.MinecraftKey sound:name;
#elseif version >= 1.13
    private (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.MinecraftKey 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 PacketPlayOutNamedSoundEffect
    public static (PacketPlayOutCustomSoundEffectHandle) PacketPlayOutCustomSoundEffect createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.core.Holder<net.minecraft.sounds.SoundEffect> soundEffect, (String) SoundCategory category, double x, double y, double z, float volume, float pitch, long randomSeed) {
        return new PacketPlayOutCustomSoundEffect(soundEffect, category, x, y, z, volume, pitch, randomSeed);
    }
#elseif version >= 1.13
    public static (PacketPlayOutCustomSoundEffectHandle) PacketPlayOutCustomSoundEffect createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) net.minecraft.resources.MinecraftKey soundEffect, (String) SoundCategory category, double x, double y, double z, float volume, float pitch, long randomSeed) {
  #if version >= 1.19
        return new PacketPlayOutCustomSoundEffect(soundEffect, category, new Vec3D(x, y, z), volume, pitch, randomSeed);
  #else
        return new PacketPlayOutCustomSoundEffect(soundEffect, category, new Vec3D(x, y, z), volume, pitch);
  #endif
    }
#elseif version >= 1.9
    public static (PacketPlayOutCustomSoundEffectHandle) PacketPlayOutCustomSoundEffect createNew((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String soundEffect, (String) SoundCategory category, double x, double y, double z, float volume, float pitch, long randomSeed) {
        return new PacketPlayOutCustomSoundEffect(soundEffect, category, x, y, z, volume, pitch);
    }
#else
    // Note: no sound category supported. Is PacketPlayOutNamedSoundEffect
    public static (PacketPlayOutCustomSoundEffectHandle) PacketPlayOutCustomSoundEffect 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 PacketPlayOutCustomSoundEffect(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 PacketPlayOutCustomSoundEffectHandle 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 PacketPlayOutCustomSoundEffectHandle 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 PacketPlayOutCustomSoundEffectHandle 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 PacketPlayOutCustomSoundEffect private (String) SoundCategory customSoundCategory:source;
    #require PacketPlayOutCustomSoundEffect private int customSoundX:x;
    #require PacketPlayOutCustomSoundEffect private int customSoundY:y;
    #require PacketPlayOutCustomSoundEffect private int customSoundZ:z;
    #require PacketPlayOutCustomSoundEffect private float customSoundPitch:pitch;
#elseif version >= 1.9
    #require PacketPlayOutCustomSoundEffect private (String) SoundCategory customSoundCategory:b;
    #require PacketPlayOutCustomSoundEffect private int customSoundX:c;
    #require PacketPlayOutCustomSoundEffect private int customSoundY:d;
    #require PacketPlayOutCustomSoundEffect private int customSoundZ:e;
  #if version >= 1.10.2
    #require PacketPlayOutCustomSoundEffect private float customSoundPitch:g;
  #else
    #require PacketPlayOutCustomSoundEffect private int customSoundPitchInt:g;
  #endif
#else
    #require PacketPlayOutCustomSoundEffect private int customSoundX:b;
    #require PacketPlayOutCustomSoundEffect private int customSoundY:c;
    #require PacketPlayOutCustomSoundEffect private int customSoundZ:d;
    #require PacketPlayOutCustomSoundEffect private int customSoundPitchInt:f;
#endif

    // Category supported since 1.9
#if version >= 1.9
    public String getCategory() { return instance#customSoundCategory; }
    public void setCategory(String category) { instance#customSoundCategory = 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 PacketPlayOutCustomSoundEffect 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
}

class PacketPlayInSteerVehicle extends Packet {
#if version >= 1.21.2
    public static (PacketPlayInSteerVehicleHandle) PacketPlayInSteerVehicle createNew(boolean isLeft, boolean isRight, boolean isForward, boolean isBackward, boolean isJump, boolean isUnmount, boolean isSprint) {
        return new PacketPlayInSteerVehicle(new net.minecraft.world.entity.player.Input(
            isForward, isBackward, isLeft, isRight, isJump, isUnmount, isSprint));
    }

    public boolean isLeft() { return instance.input().left(); }
    public boolean isRight() { return instance.input().right(); }
    public boolean isForward() { return instance.input().forward(); }
    public boolean isBackward() { return instance.input().backward(); }

    public float getSideways() {
        net.minecraft.world.entity.player.Input input = instance.input();
        if (input.left() != input.right()) {
            return input.left() ? 0.98f : -0.98f;
        } else {
            return 0.0f;
        }
    }

    public float getForwards() {
        net.minecraft.world.entity.player.Input input = instance.input();
        if (input.forward() != input.backward()) {
            return input.forward() ? 0.98f : -0.98f;
        } else {
            return 0.0f;
        }
    }

    public boolean isJump() { return instance.input().jump(); }
    public boolean isUnmount() { return instance.input().shift(); }
    public boolean isSprint() { return instance.input().sprint(); }

#else
  #if version >= 1.17
    #require PacketPlayInSteerVehicle private final float sideways:xxa;
    #require PacketPlayInSteerVehicle private final float forwards:zza;
    #require PacketPlayInSteerVehicle private final boolean jump:isJumping;
    #require PacketPlayInSteerVehicle private final boolean unmount:isShiftKeyDown;
  #else
    #require PacketPlayInSteerVehicle private float sideways:a;
    #require PacketPlayInSteerVehicle private float forwards:b;
    #require PacketPlayInSteerVehicle private boolean jump:c;
    #require PacketPlayInSteerVehicle private boolean unmount:d;
  #endif

    public static (PacketPlayInSteerVehicleHandle) PacketPlayInSteerVehicle createNew(boolean isLeft, boolean isRight, boolean isForward, boolean isBackward, boolean isJump, boolean isUnmount, boolean isSprint) {
       float sideways = (isLeft == isRight) ? 0.0f : (isLeft ? 0.98f : -0.98f);
       float forwards = (isForward == isBackward) ? 0.0f : (isForward ? 0.98f : -0.98f);
  #if version >= 1.17
       return new PacketPlayInSteerVehicle(sideways, forwards, isJump, isUnmount);
  #else
       PacketPlayInSteerVehicle packet = new PacketPlayInSteerVehicle();
       packet#sideways = sideways;
       packet#forwards = forwards;
       packet#jump = isJump;
       packet#unmount = isUnmount;
       return packet;
  #endif
    }

    public boolean isLeft() { return instance#sideways > 0.0f; }
    public boolean isRight() { return instance#sideways < 0.0f; }
    public boolean isForward() { return instance#forwards > 0.0f; }
    public boolean isBackward() { return instance#forwards < 0.0f; }

    public float getSideways() { return instance#sideways; }
    public float getForwards() { return instance#forwards; }
    public boolean isJump() { return instance#jump; }
    public boolean isUnmount() { return instance#unmount; }
    public boolean isSprint() { return false; }
#endif
}

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

class PacketPlayOutPosition extends Packet {
#if version >= 1.21.2
    public double getX() { return instance.change().position().x; }
    public double getY() { return instance.change().position().y; }
    public double getZ() { return instance.change().position().z; }
    public float getYaw() { return instance.change().yRot(); }
    public float getPitch() { return instance.change().xRot(); }
    public (RelativeFlags) Set<RelativeMovement> getRelativeFlags:relatives();
    public int getTeleportWaitTimer:id();
#elseif version >= 1.18
    public double getX();
    public double getY();
    public double getZ();
    public float getYaw:getYRot();
    public float getPitch:getXRot();
    public (RelativeFlags) Set<RelativeMovement> getRelativeFlags:getRelativeArguments();
    public int getTeleportWaitTimer:getId();
#elseif version >= 1.17
    public double getX:b();
    public double getY:c();
    public double getZ:d();
    public float getYaw:e();
    public float getPitch:f();
    public (RelativeFlags) Set<RelativeMovement> getRelativeFlags:i();
    public int getTeleportWaitTimer:g();
#else
    #require PacketPlayOutPosition private double x:a;
    #require PacketPlayOutPosition private double y:b;
    #require PacketPlayOutPosition private double z:c;
    #require PacketPlayOutPosition private float yaw:d;
    #require PacketPlayOutPosition private float pitch:e;
    #require PacketPlayOutPosition private Set<RelativeMovement> relativeArguments:f;

    public double getX() { return instance#x; }
    public double getY() { return instance#y; }
    public double getZ() { return instance#z; }
    public float getYaw() { return instance#yaw; }
    public float getPitch() { return instance#pitch; }
    public (RelativeFlags) Set<RelativeMovement> getRelativeFlags() { return instance#relativeArguments; }

    public int getTeleportWaitTimer() {
  #if version >= 1.9
        #require PacketPlayOutPosition private int teleportWaitTimer:g;
        return instance#teleportWaitTimer;
  #else
        return 0;
  #endif
    }
#endif

    public static (PacketPlayOutPositionHandle) PacketPlayOutPosition createNew(double x, double y, double z, float yaw, float pitch, double deltaX, double deltaY, double deltaZ, (RelativeFlags) Set<RelativeMovement> relativeFlags, int teleportWaitTimer) {
#if version >= 1.21.2
        return PacketPlayOutPosition.of(teleportWaitTimer, new PositionMoveRotation(
            new Vec3D(x, y, z), new Vec3D(deltaX, deltaY, deltaZ), yaw, pitch
        ), relativeFlags);
#elseif version >= 1.19.4
        return new PacketPlayOutPosition(x, y, z, yaw, pitch, relativeFlags, teleportWaitTimer);
#elseif version >= 1.17
        return new PacketPlayOutPosition(x, y, z, yaw, pitch, relativeFlags, teleportWaitTimer, false);
#elseif version >= 1.9
        return new PacketPlayOutPosition(x, y, z, yaw, pitch, relativeFlags, teleportWaitTimer);
#else
        return new PacketPlayOutPosition(x, y, z, yaw, pitch, relativeFlags);
#endif
    }

    <code>
    public static PacketPlayOutPositionHandle createNew(double x, double y, double z, float yaw, float pitch, double deltaX, double deltaY, double deltaZ, RelativeFlags relativeFlags) {
        return createNew(x, y, z, yaw, pitch, deltaX, deltaY, deltaZ, relativeFlags, 0);
    }

    public static PacketPlayOutPositionHandle createNew(double x, double y, double z, float yaw, float pitch, RelativeFlags relativeFlags) {
        return createNew(x, y, z, yaw, pitch, 0.0, 0.0, 0.0, relativeFlags, 0);
    }

    public static PacketPlayOutPositionHandle createRelative(double dx, double dy, double dz, float dyaw, float dpitch) {
        return createNew(dx, dy, dz, dyaw, dpitch, RelativeFlags.RELATIVE_POSITION_ROTATION);
    }

    public static PacketPlayOutPositionHandle createAbsolute(org.bukkit.Location location) {
        return createAbsolute(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
    }

    public static PacketPlayOutPositionHandle createAbsolute(double x, double y, double z, float yaw, float pitch) {
        return createNew(x, y, z, yaw, pitch, RelativeFlags.ABSOLUTE_POSITION);
    }
    </code>
}

// 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 PacketPlayOutBoss extends Packet {
#if version >= 1.17
    private UUID entityUUID:id;
    private (Object) PacketPlayOutBoss.Action action:operation;

    //TODO: Moved to Action class!
    // ==================
    private (Object) IChatBaseComponent 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) PacketPlayOutBoss.Action action:b;
    private (Object) IChatBaseComponent 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 PacketPlayOutAttachEntity 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 (PacketPlayOutAttachEntityHandle) PacketPlayOutAttachEntity createNew() {
#if version >= 1.17
        #require net.minecraft.network.protocol.game.PacketPlayOutAttachEntity private PacketPlayOutAttachEntity createSpawnPacket:<init>(net.minecraft.network.PacketDataSerializer serializer);
        return #createSpawnPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new PacketPlayOutAttachEntity();
#endif
    }

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

    public void setIsLeash(boolean isLeash) {
    }

    public static (PacketPlayOutAttachEntityHandle) PacketPlayOutAttachEntity 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 (PacketPlayOutAttachEntityHandle) PacketPlayOutAttachEntity createNewLeash((org.bukkit.entity.Entity) Entity leashedEntity, (org.bukkit.entity.Entity) Entity holderEntity) {
        return new PacketPlayOutAttachEntity(leashedEntity, holderEntity);
    }
#else
    #require net.minecraft.network.protocol.game.PacketPlayOutAttachEntity 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 (PacketPlayOutAttachEntityHandle) PacketPlayOutAttachEntity createNewMount((org.bukkit.entity.Entity) Entity passengerEntity, (org.bukkit.entity.Entity) Entity vehicleEntity) {
        return new PacketPlayOutAttachEntity(0, passengerEntity, vehicleEntity);
    }

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

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

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

class PacketPlayOutEntityEquipment 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.PacketPlayOutEntityEquipment private final java.util.List<com.mojang.datafixers.util.Pair<net.minecraft.world.entity.EnumItemSlot, net.minecraft.world.item.ItemStack>> slots;
  #else
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment private final java.util.List<com.mojang.datafixers.util.Pair<net.minecraft.world.entity.EnumItemSlot, 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 (PacketPlayOutEntityEquipmentHandle) PacketPlayOutEntityEquipment createNew(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int entityId, (org.bukkit.inventory.EquipmentSlot) EnumItemSlot 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 PacketPlayOutEntityEquipment(entityId, slots);
    }

    public (org.bukkit.inventory.EquipmentSlot) EnumItemSlot getEquipmentSlot(PacketPlayOutEntityEquipmentHandle.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 (EnumItemSlot) entry.getFirst();
    }

    public void setEquipmentSlot(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int index, (org.bukkit.inventory.EquipmentSlot) EnumItemSlot 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 (PacketPlayOutEntityEquipmentHandle) PacketPlayOutEntityEquipment createNew(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int entityId, (org.bukkit.inventory.EquipmentSlot) EnumItemSlot slot, (org.bukkit.inventory.ItemStack) ItemStack itemStack) {
  #if version >= 1.9
        return new PacketPlayOutEntityEquipment(entityId, slot, itemStack);
  #else
        return new PacketPlayOutEntityEquipment(entityId, ownerType.isPlayer() ? slot.b() : slot.c(), itemStack);
  #endif
    }

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

    public (org.bukkit.inventory.EquipmentSlot) EnumItemSlot getEquipmentSlot(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int index) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        return instance#slot;
    }
    public void setEquipmentSlot(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int index, (org.bukkit.inventory.EquipmentSlot) EnumItemSlot slot) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        instance#slot = slot;
    }
  #else
    #require net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment private int slot:b;
    public (org.bukkit.inventory.EquipmentSlot) EnumItemSlot getEquipmentSlot(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int index) {
        if (index != 0) { throw new IndexOutOfBoundsException("Index out of range: " + index); }
        int slotIdx = instance#slot;
        if (ownerType.isPlayer()) {
            return EnumItemSlot.fromPlayerSlotIndex(slotIdx);
        } else {
            return EnumItemSlot.fromNonPlayerSlotIndex(slotIdx);
        }
    }
    public void setEquipmentSlot(PacketPlayOutEntityEquipmentHandle.OwnerType ownerType, int index, (org.bukkit.inventory.EquipmentSlot) EnumItemSlot 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.PacketPlayOutEntityEquipment 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 PacketPlayOutEntityEquipmentHandle 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 PacketPlayOutMount 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 (PacketPlayOutMountHandle) PacketPlayOutMount createNew() {
#if version >= 1.20.5
        #require net.minecraft.network.protocol.game.PacketPlayOutMount private PacketPlayOutMount createMountPacket:<init>(net.minecraft.network.PacketDataSerializer serializer);
        return #createMountPacket(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#elseif version >= 1.17
        return new PacketPlayOutMount(com.bergerkiller.bukkit.common.internal.logic.NullPacketDataSerializer.INSTANCE);
#else
        return new PacketPlayOutMount();
#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 PacketPlayOutMountHandle createNew(int entityId, int[] mountedEntityIds) {
        PacketPlayOutMountHandle handle = createNew();
        handle.setEntityId(entityId);
        handle.setMountedEntityIds(mountedEntityIds);
        return handle;
    }
    </code>
}

// Gone since 1.17, not worth keeping.
/*
class PacketPlayOutCombatEvent extends Packet {
    public (Object) PacketPlayOutCombatEvent.EnumCombatEventType eventType:a;
    public int entityId1:b;
    public int entityId2:c;
    public int tickDuration:d;
#if version >= 1.9
    public (ChatText) IChatBaseComponent message:e;
#else
    public (ChatText) String message:e;
#endif
}
*/

// Only >= MC 1.9
optional class PacketPlayOutSetCooldown 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 PacketPlayInTabComplete extends Packet {
//     private String text:a;
// #if version >= 1.9
//     private optional boolean assumeCommand:b;
//     private (IntVector3) BlockPosition position:c;
// #else
//     private optional boolean assumeCommand:###;
//     private (IntVector3) BlockPosition position:b;
// #endif
// }

class PacketPlayInSetCreativeSlot extends Packet {

    public static (PacketPlayInSetCreativeSlotHandle) PacketPlayInSetCreativeSlot createNew(int slotIndex, (org.bukkit.inventory.ItemStack) ItemStack item) {
#if version >= 1.20.5
        return new PacketPlayInSetCreativeSlot((short) slotIndex, item);
#elseif version >= 1.17
        return new PacketPlayInSetCreativeSlot(slotIndex, item);
#else
        PacketPlayInSetCreativeSlot packet = new PacketPlayInSetCreativeSlot();
        #require PacketPlayInSetCreativeSlot private int slotIndex:slot;
        #require PacketPlayInSetCreativeSlot 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 PacketPlayOutEntityMetadata 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<DataWatcher.PackedItem<?>> metadataItems:packedItems;
#elseif version >= 1.9
    private (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<Object>>) List<DataWatcher.PackedItem<?>> metadataItems:b;
#else
    private (List<com.bergerkiller.bukkit.common.wrappers.DataWatcher.PackedItem<Object>>) List<DataWatcher.WatchableObject> metadataItems:b;
#endif

    public static (PacketPlayOutEntityMetadataHandle) PacketPlayOutEntityMetadata createForSpawn(int entityId, (com.bergerkiller.bukkit.common.wrappers.DataWatcher) DataWatcher 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 PacketPlayOutEntityMetadata(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 PacketPlayOutEntityMetadata(entityId, datawatcher, true);

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

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

        return packet;
#endif
    }

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

    <code>
    public static PacketPlayOutEntityMetadataHandle 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) BlockPosition bedPosition:b;
}

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

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

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

    public static (PacketPlayOutUpdateAttributesHandle) PacketPlayOutUpdateAttributes createNew(int entityId, (java.util.Collection<AttributeModifiableHandle>) java.util.Collection<AttributeModifiable> attributes) {
        return new PacketPlayOutUpdateAttributes(entityId, attributes);
    }

    public static (PacketPlayOutUpdateAttributesHandle) PacketPlayOutUpdateAttributes 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();
        AttributeModifiable attribute = new AttributeModifiable(net.minecraft.world.entity.ai.attributes.GenericAttributes.MAX_HEALTH, callback);
#else
        // Internal code calls a callback on this instance, so we need a dummy one to catch that
        AttributeMapBase attributeMapBase = (AttributeMapBase) 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
        AttributeModifiable attribute = new AttributeModifiable(attributeMapBase, GenericAttributes.MAX_HEALTH);
  #else
        AttributeModifiable attribute = new AttributeModifiable(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 PacketPlayOutUpdateAttributes(entityId, attributes);
    }
}

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

class PacketPlayInUpdateSign extends Packet {
#if version >= 1.17
    private (IntVector3) BlockPosition position:pos;
#else
    private (IntVector3) BlockPosition position:a;
#endif

#if version >= 1.17
    private (ChatText[]) String[] lines;
#elseif version >= 1.9
    private (ChatText[]) String[] lines:b;
#else
    private (ChatText[]) IChatBaseComponent[] lines:b;
#endif
}

class PacketPlayInWindowClick 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 >= 1.17
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) net.minecraft.world.inventory.InventoryClickType mode:clickType;
#elseif version >= 1.9
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) net.minecraft.world.inventory.InventoryClickType mode:shift;
#else
    private (com.bergerkiller.bukkit.common.wrappers.InventoryClickType) int mode:shift;
#endif
}

// Since MC 1.9
optional class PacketPlayInTeleportAccept 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) World world:a;
    private (IntVector3) BlockPosition position:b;
    private (ChatText[]) IChatBaseComponent[] lines:c;
}

class PacketPlayOutWorldParticles extends Packet {
    // TODO: Create a common interface for particle + data
    // Since 1.13 there is a ParticleParam 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 PacketPlayOutWorldParticles private double pos_x:x;
    #require PacketPlayOutWorldParticles private double pos_y:y;
    #require PacketPlayOutWorldParticles 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 PacketPlayOutWorldParticles private ParticleParam particle;
#elseif version >= 1.15
    #require PacketPlayOutWorldParticles private double pos_x:a;
    #require PacketPlayOutWorldParticles private double pos_y:b;
    #require PacketPlayOutWorldParticles 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.PacketPlayOutWorldParticles private ParticleParam particle:j;
#elseif version >= 1.13
    #require PacketPlayOutWorldParticles private float pos_x:a;
    #require PacketPlayOutWorldParticles private float pos_y:b;
    #require PacketPlayOutWorldParticles 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.PacketPlayOutWorldParticles private ParticleParam particle:j;
#else
    #require PacketPlayOutWorldParticles private Particle particle:a;
    #require PacketPlayOutWorldParticles private float pos_x:b;
    #require PacketPlayOutWorldParticles private float pos_y:c;
    #require PacketPlayOutWorldParticles 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 PacketPlayOutWorldParticles private int[] particleOptions:k;
#endif

    public static (PacketPlayOutWorldParticlesHandle) PacketPlayOutWorldParticles createNew() {
#if version >= 1.21.4
        return new PacketPlayOutWorldParticles((ParticleParam) null, false, false, 0.0, 0.0, 0.0, 0.0f, 0.0f, 0.0f, 0.0f, 1);
#elseif version >= 1.15
        return new PacketPlayOutWorldParticles((ParticleParam) null, false, 0.0, 0.0, 0.0, 0.0f, 0.0f, 0.0f, 0.0f, 1);
#elseif version >= 1.13
        return new PacketPlayOutWorldParticles((ParticleParam) null, false, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1);
#else
        return new PacketPlayOutWorldParticles((Particle) 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
    // ParticleParam API
    public optional void setParticle(Particle<?> particleType, Object options) {
        ParticleParam param;
        if (particleType instanceof ParticleParam) {
            param = (ParticleParam) 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.ParticleParamBlock(particleType,
                    (IBlockData) ((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);
            param = new net.minecraft.core.particles.ParticleParamItem(particleType, itemstack);

  #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 BlockPosition(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
            BlockPosition origin = new BlockPosition(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.ParticleParamRedstone(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.ParticleParamRedstone(new org.joml.Vector3f(r,g,b), dust.scale);
    #elseif version >= 1.17
            param = new net.minecraft.core.particles.ParticleParamRedstone(new com.mojang.math.Vector3fa(r,g,b), dust.scale);
    #else
            param = new net.minecraft.core.particles.ParticleParamRedstone(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<?>) Particle<?> getParticleType() {
        ParticleParam 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
    // EnumParticle + int[] args
    public optional void setParticle(Particle 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(
                    (IBlockData) ((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 PacketPlayOutWorldParticles private float randomX:e;
            #require PacketPlayOutWorldParticles private float randomY:f;
            #require PacketPlayOutWorldParticles private float randomZ:g;
            #require PacketPlayOutWorldParticles private float speed:h;
            #require PacketPlayOutWorldParticles 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<?>) Particle<?> 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 PacketPlayOutBlockChange extends Packet {
#if version >= 1.17
    private final (IntVector3) BlockPosition position:pos;
    public final (com.bergerkiller.bukkit.common.wrappers.BlockData) IBlockData blockData:blockState;
#else
    private (IntVector3) BlockPosition position:a;
    public (com.bergerkiller.bukkit.common.wrappers.BlockData) IBlockData blockData:block;
#endif

    public static (PacketPlayOutBlockChangeHandle) PacketPlayOutBlockChange createNewNull() {
#if version >= 1.16.2
        return new PacketPlayOutBlockChange((BlockPosition) null, (IBlockData) null);
#else
        return new PacketPlayOutBlockChange();
#endif
    }

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

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

class PacketPlayOutBlockAction extends Packet {
#if version >= 1.17
    private final (IntVector3) BlockPosition 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) BlockPosition position:a;
    private int b0:b;
    private int b1:c;
    private (org.bukkit.Material) net.minecraft.world.level.block.Block block:d;
#endif
}

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

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

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

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

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

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

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

#if version >= 1.18
    private (PacketPlayOutTileEntityDataHandle) PacketPlayOutTileEntityData((IntVector3) BlockPosition blockPosition, (BlockStateType) TileEntityTypes<?> type, (CommonTagCompound) NBTTagCompound data);
#else
    public (PacketPlayOutTileEntityDataHandle) PacketPlayOutTileEntityData((IntVector3) BlockPosition blockPosition, (BlockStateType) int type, (CommonTagCompound) NBTTagCompound data);
#endif
}

class PacketPlayOutOpenSignEditor extends Packet {
#if version >= 1.17
    private final (IntVector3) BlockPosition signPosition:pos;
#else
    private (IntVector3) BlockPosition signPosition:a;
#endif

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

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

#if version >= 1.20
    #require PacketPlayOutOpenSignEditor 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 PacketPlayInAbilities 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 PacketPlayOutAbilities 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 (PacketPlayOutAbilitiesHandle) PacketPlayOutAbilities((com.bergerkiller.bukkit.common.wrappers.PlayerAbilities) net.minecraft.world.entity.player.PlayerAbilities abilities);
}

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

class PacketPlayOutSpawnPosition extends Packet {
#if version >= 1.21.9
    public static (PacketPlayOutSpawnPositionHandle) PacketPlayOutSpawnPosition createNew((EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig spawnConfig) {
        return new PacketPlayOutSpawnPosition(spawnConfig.respawnData());
    }

    public (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig getSpawn() {
        return new net.minecraft.server.level.EntityPlayer$RespawnConfig(instance.respawnData(), true);
    }
#else
    public static (PacketPlayOutSpawnPositionHandle) PacketPlayOutSpawnPosition createNew((EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig spawnConfig) {
        BlockPosition position = (BlockPosition) spawnConfig.pos();
  #if version >= 1.16.2
        float angle = spawnConfig.angle();
        return new PacketPlayOutSpawnPosition(position, angle);
  #else
        return new PacketPlayOutSpawnPosition(position);
  #endif
    }

    public (EntityPlayerHandle.RespawnConfigHandle) EntityPlayer.RespawnConfig getSpawn() {
  #if version >= 1.17
        BlockPosition position = instance.pos;
  #else
        BlockPosition position = instance.position;
  #endif

        float yaw = 0.0f;
  #if version >= 1.16.2
    #if version >= 1.17
        #require net.minecraft.network.protocol.game.PacketPlayOutSpawnPosition private final float angle;
    #else
        #require net.minecraft.network.protocol.game.PacketPlayOutSpawnPosition 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.EntityPlayer$RespawnConfig(null, position, yaw, true);
  #else
        return new net.minecraft.server.level.EntityPlayer$RespawnConfig(null, null, position, yaw, true);
  #endif
    }
#endif
}

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

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

class PacketPlayInEnchantItem 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 PacketPlayInSpectate extends Packet {
#if version >= 1.17
    private final UUID uuid;
#else
    private UUID uuid:a;
#endif

    public (PacketPlayInSpectateHandle) PacketPlayInSpectate(UUID uuid);
}

// Since Minecraft 1.12
optional class PacketPlayOutAdvancements 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;
    }
}
