package net.minecraft.world.entity;

import net.minecraft.core.BlockPosition;
import net.minecraft.core.SectionPosition;
import net.minecraft.CrashReportSystemDetails;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.syncher.DataWatcherObject;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.level.ChunkProviderServer;
import net.minecraft.server.level.WorldServer;
import net.minecraft.sounds.SoundEffect;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.item.EntityItem;
import net.minecraft.world.entity.player.EntityHuman;
import net.minecraft.world.EnumHand;
import net.minecraft.world.EnumInteractionResult;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.chunk.Chunk;
import net.minecraft.world.level.World;
import net.minecraft.world.phys.AxisAlignedBB;
import net.minecraft.world.phys.Vec3D;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.level.storage.ValueInput;

import org.bukkit.craftbukkit.entity.CraftEntity;
import com.bergerkiller.bukkit.common.wrappers.ChatText;
import com.bergerkiller.bukkit.common.wrappers.DataWatcher;
import com.bergerkiller.bukkit.common.wrappers.DataWatcher.Key;
import com.bergerkiller.bukkit.common.bases.IntVector3;
import com.bergerkiller.bukkit.common.wrappers.BlockData;
import com.bergerkiller.bukkit.common.wrappers.InteractionResult;
import com.bergerkiller.bukkit.common.nbt.CommonTagCompound;

import com.bergerkiller.generated.net.minecraft.CrashReportSystemDetailsHandle;
import com.bergerkiller.generated.net.minecraft.server.level.EntityTrackerEntryHandle;
import com.bergerkiller.generated.net.minecraft.world.damagesource.DamageSourceHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle;
import com.bergerkiller.generated.net.minecraft.world.level.WorldHandle;
import com.bergerkiller.generated.net.minecraft.world.phys.AxisAlignedBBHandle;
import com.bergerkiller.generated.net.minecraft.util.RandomSourceHandle;
import com.bergerkiller.generated.net.minecraft.world.level.storage.ValueOutputHandle;

class Entity {
#if version >= 1.9
    protected (org.bukkit.entity.Entity) CraftEntity bukkitEntityField:bukkitEntity;
#endif

    public void setTrackerEntry((EntityTrackerEntryHandle) net.minecraft.server.level.EntityTrackerEntry tracker) {
#if exists net.minecraft.world.entity.Entity net.minecraft.server.level.EntityTrackerEntry tracker;
        #require Entity net.minecraft.server.level.EntityTrackerEntry tracker;
        instance#tracker = tracker;
#endif

#if exists net.minecraft.world.entity.Entity public final void moonrise$setTrackedEntity(net.minecraft.server.level.EntityTrackerEntry trackedEntity);
        instance.moonrise$setTrackedEntity(tracker);
#endif
    }

#if version >= 1.17
    private static optional java.util.concurrent.atomic.AtomicInteger opt_atomic_entityCount:ENTITY_COUNTER;
    private static optional int opt_int_entityCount:###;
#elseif version >= 1.14
    private static optional java.util.concurrent.atomic.AtomicInteger opt_atomic_entityCount:entityCount;
    private static optional int opt_int_entityCount:###;
#else
    private static optional java.util.concurrent.atomic.AtomicInteger opt_atomic_entityCount:###;
    private static optional int opt_int_entityCount:entityCount;
#endif

    private int idField:id;

#if version >= 1.17
    public boolean preventBlockPlace:blocksBuilding;
#elseif version >= 1.14
    public boolean preventBlockPlace:i;
#elseif version >= 1.13
    public boolean preventBlockPlace:j;
#elseif version >= 1.9
    public boolean preventBlockPlace:i;
#else
    public boolean preventBlockPlace:k;
#endif

#select version >=
#case 1.13.1: private (EntityHandle) Entity vehicle;
#case 1.13:   private (EntityHandle) Entity vehicle:ax;
#case 1.10.2: private (EntityHandle) Entity vehicle:au;
#case 1.9.4:  private (EntityHandle) Entity vehicle:at;
#case 1.9:    private (EntityHandle) Entity vehicle:as;
#case else:   private (EntityHandle) Entity vehicle;
#endselect

    // Gets an unmodifiable List of the passengers of this entity
    public (List<EntityHandle>) List<Entity> getPassengers() {
#if version >= 1.17
        if (instance.passengers == null) {
            return java.util.Collections.emptyList();
        } else {
            return instance.passengers; // ImmutableList<Entity>
        }
#elseif version >= 1.9
        if (instance.passengers == null) {
            return java.util.Collections.emptyList();
        } else {
            return java.util.Collections.unmodifiableList(instance.passengers);
        }
#else
        if (instance.passenger != null) {
            return java.util.Collections.singletonList(instance.passenger);
        } else {
            return java.util.Collections.emptyList();
        }
#endif
    }

#if version >= 1.9
    public boolean isVehicle();
    public boolean isPassenger();
    public boolean hasPassengers() {
        return instance.passengers != null && !instance.passengers.isEmpty();
    }
#else
    public boolean isVehicle() {
        return instance.passenger != null;
    }
    public boolean isPassenger() {
        return instance.vehicle != null;
    }
    public boolean hasPassengers() {
        return instance.passenger != null;
    }
#endif

    public void setPassengers((List<EntityHandle>) List<Entity> passengers) {
#if version >= 1.17
        instance.passengers = com.google.common.collect.ImmutableList.copyOf(passengers);
#elseif version >= 1.9
        if (instance.passengers == null) {
            instance.passengers = new java.util.ArrayList();
        }
        instance.passengers.clear();
        instance.passengers.addAll(passengers);
#else
        instance.passenger = passengers.isEmpty() ? null : (Entity) passengers.get(0);
#endif
    }

#if version >= 1.18
    public boolean isInSameVehicle:isPassengerOfSameVehicle((EntityHandle) Entity entity);
#elseif version >= 1.15.2
    public boolean isInSameVehicle:isSameVehicle((EntityHandle) Entity entity);
#elseif version >= 1.9
    public boolean isInSameVehicle:x((EntityHandle) Entity entity);
#else
    public boolean isInSameVehicle((EntityHandle) Entity entity) {
        return entity == instance.passenger || entity == instance.vehicle;
    }
#endif

#if version >= 1.17
    public boolean isIgnoreChunkCheck() {
        return false; // Seems to have been removed completely with no replacement
    }

    public void setIgnoreChunkCheck(boolean ignore) {
    }
#else
    public boolean isIgnoreChunkCheck() {
        return instance.attachedToPlayer;
    }

    public void setIgnoreChunkCheck(boolean ignore) {
        instance.attachedToPlayer = ignore;
    }
#endif

#if version >= 1.17
    public double lastX:xOld;
    public double lastY:yOld;
    public double lastZ:zOld;
#else
    public double lastX;
    public double lastY;
    public double lastZ;
#endif

#if version >= 1.18
    public double getLocX:getX();
    public double getLocY:getY();
    public double getLocZ:getZ();
    public void setLoc:setPosRaw(double x, double y, double z);

    public void setLocX(double x) {
        instance.setPosRaw(x, instance.getY(), instance.getZ());
    }

    public void setLocY(double y) {
        instance.setPosRaw(instance.getX(), y, instance.getZ());
    }

    public void setLocZ(double z) {
        instance.setPosRaw(instance.getX(), instance.getY(), z);
    }

    public org.bukkit.util.Vector getLoc() {
        Vec3D loc = instance.position();
        return new org.bukkit.util.Vector(loc.x, loc.y, loc.z);
    }

#elseif version >= 1.15
    // locX/Y/Z fields became private on 1.15
    public double getLocX:locX();
    public double getLocY:locY();
    public double getLocZ:locZ();
    public void setLoc:setPositionRaw(double x, double y, double z);

  #if version >= 1.16
    // Since 1.16 they got merged into a 'Vec3D loc' field
    // It is better and safer to just call setPositionRaw

    public void setLocX(double x) {
        Vec3D loc = instance.getPositionVector();
        instance.setPositionRaw(x, loc.y, loc.z);
    }

    public void setLocY(double y) {
        Vec3D loc = instance.getPositionVector();
        instance.setPositionRaw(loc.x, y, loc.z);
    }

    public void setLocZ(double z) {
        Vec3D loc = instance.getPositionVector();
        instance.setPositionRaw(loc.x, loc.y, z);
    }

    public org.bukkit.util.Vector getLoc() {
        Vec3D loc = instance.getPositionVector();
        return new org.bukkit.util.Vector(loc.x, loc.y, loc.z);
    }
  #else
    // We can use reflection to set the individual fields efficiently

    public void setLocX(double x) {
        #require net.minecraft.world.entity.Entity private double locX;
        instance#locX = x;
    }

    public void setLocY(double y) {
        #require net.minecraft.world.entity.Entity private double locY;
        instance#locY = y;
    }

    public void setLocZ(double z) {
        #require net.minecraft.world.entity.Entity private double locZ;
        instance#locZ = z;
    }

    public org.bukkit.util.Vector getLoc() {
        return new org.bukkit.util.Vector(instance.locX(), instance.locY(), instance.locZ());
    }
  #endif
#else
    public double getLocX() { return instance.locX; }
    public double getLocY() { return instance.locY; }
    public double getLocZ() { return instance.locZ; }

    public void setLocX(double x) { instance.locX = x; }
    public void setLocY(double y) { instance.locY = y; }
    public void setLocZ(double z) { instance.locZ = z; }

    public org.bukkit.util.Vector getLoc() {
        return new org.bukkit.util.Vector(instance.locX, instance.locY, instance.locZ);
    }

    public void setLoc(double x, double y, double z) {
        instance.locX = x;
        instance.locY = y;
        instance.locZ = z;
    }
#endif

    <code>
    public com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle getWorldServer() {
        return com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle.createHandle(T.getWorld.raw.invoke(getRaw()));
    }
    </code>

    // Assigns this Entity instance to deep members of this Entity
    // Used during entity replacement logic to swap out two instances
    public void assignEntityReference() {
        //TODO: Migrate more from CommonEntity to here

        // -- DataWatcher Owner field --
#if version >= 1.18
        net.minecraft.network.syncher.DataWatcher dw = instance.getEntityData();
#else
        net.minecraft.network.syncher.DataWatcher dw = instance.getDataWatcher();
#endif
        if (dw != null) {
#if version >= 1.20.5
            #require net.minecraft.network.syncher.DataWatcher private final net.minecraft.network.syncher.SyncedDataHolder dw_owner:owner;
#else
            #require net.minecraft.network.syncher.DataWatcher private final net.minecraft.world.entity.Entity dw_owner:owner;
#endif
            dw#dw_owner = instance;
        }

        // MinecartBehavior instance
#if version >= 1.21.2
        if (instance instanceof net.minecraft.world.entity.vehicle.EntityMinecartAbstract) {
            net.minecraft.world.entity.vehicle.MinecartBehavior behavior;
            behavior = ((net.minecraft.world.entity.vehicle.EntityMinecartAbstract) instance).getBehavior();
            #require net.minecraft.world.entity.vehicle.MinecartBehavior protected final net.minecraft.world.entity.vehicle.EntityMinecartAbstract minecart;
            behavior#minecart = (net.minecraft.world.entity.vehicle.EntityMinecartAbstract)instance;
        }
#endif

        // CraftBukkit added a 'CommandSource' field to, I guess, avoid instance creation?
#if version >= 1.21.2 && exists net.minecraft.world.entity.Entity private final net.minecraft.commands.ICommandListener commandSource || version > 1.21.3
        #require Entity private final net.minecraft.commands.ICommandListener commandSource;
        net.minecraft.commands.ICommandListener commandSource = instance#commandSource;
        // Can't use #require here because we don't know the name of this anonymous class
        // Identify the instance by inspecting the current value...which could fail
        try {
            java.lang.reflect.Field f = commandSource.getClass().getDeclaredField("this$0");
            f.setAccessible(true);
            f.set((Object) commandSource, (Object) instance);
        } catch (Throwable t) {
            com.bergerkiller.bukkit.common.Logging.LOGGER_REFLECTION.log(java.util.logging.Level.SEVERE, "Failed to assign part of entity reference during swap", t);
        }
#endif

        // -- Paper LootableInventory field --
#if exists net.minecraft.world.entity.Entity public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData;
        if (instance.lootableData != null) {
            #require com.destroystokyo.paper.loottable.PaperLootableInventoryData private final com.destroystokyo.paper.loottable.PaperLootableInventory paperLootable:lootable;
            com.destroystokyo.paper.loottable.PaperLootableInventory paperLootableInv = instance.lootableData#paperLootable;

  #if exists com.destroystokyo.paper.loottable.PaperMinecartLootableInventory
            if (paperLootableInv instanceof com.destroystokyo.paper.loottable.PaperMinecartLootableInventory) {
                com.destroystokyo.paper.loottable.PaperMinecartLootableInventory minecartInv = (com.destroystokyo.paper.loottable.PaperMinecartLootableInventory) paperLootableInv;
                #require com.destroystokyo.paper.loottable.PaperMinecartLootableInventory private net.minecraft.world.entity.vehicle.EntityMinecartContainer emcEntity:entity;
                minecartInv#emcEntity = instance;
            }
  #endif
  #if exists com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory
            if (paperLootableInv instanceof com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory) {
                com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory containerInv = (com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory) paperLootableInv;
                #require com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory private final net.minecraft.world.entity.vehicle.ContainerEntity emcEntity:entity;
                containerInv#emcEntity = instance;
            }
  #endif
        }
#endif
    }

#if version >= 1.18
    public boolean isLastAndCurrentPositionDifferent() {
        return instance.getX() != instance.xOld || instance.getY() != instance.yOld || instance.getZ() != instance.zOld ||
               instance.getYRot() != instance.yRotO || instance.getXRot() != instance.xRotO;
    }
#elseif version >= 1.17
    public boolean isLastAndCurrentPositionDifferent() {
        return instance.locX() != instance.xOld || instance.locY() != instance.yOld || instance.locZ() != instance.zOld ||
               instance.getYRot() != instance.yRotO || instance.getXRot() != instance.xRotO;
    }
#elseif version >= 1.15
    public boolean isLastAndCurrentPositionDifferent() {
        return instance.locX() != instance.lastX || instance.locY() != instance.lastY || instance.locZ() != instance.lastZ ||
               instance.yaw != instance.lastYaw || instance.pitch != instance.lastPitch;
    }
#else
    public boolean isLastAndCurrentPositionDifferent() {
        return instance.locX != instance.lastX || instance.locY != instance.lastY || instance.locZ != instance.lastZ ||
               instance.yaw != instance.lastYaw || instance.pitch != instance.lastPitch;
    }
#endif


#if version >= 1.18
    public org.bukkit.util.Vector getMot() {
        Vec3D mot = instance.getDeltaMovement();
        return new org.bukkit.util.Vector(mot.x, mot.y, mot.z);
    }

    public void setMotVector(org.bukkit.util.Vector mot) {
        instance.setDeltaMovement(new Vec3D(mot.getX(), mot.getY(), mot.getZ()));
    }

    public void setMot(double x, double y, double z) {
        instance.setDeltaMovement(new Vec3D(x, y, z));
    }

    public void fixMotNaN() {
        Vec3D mot = instance.getDeltaMovement();
        if (Double.isNaN(mot.x) || Double.isNaN(mot.y) || Double.isNaN(mot.z)) {
            double x = mot.x;
            double y = mot.y;
            double z = mot.z;
            if (Double.isNaN(x)) x = 0.0;
            if (Double.isNaN(y)) y = 0.0;
            if (Double.isNaN(z)) z = 0.0;
            instance.setDeltaMovement(new Vec3D(x, y, z));
        }
    }

    public void setMotX(double x) {
        Vec3D mot = instance.getDeltaMovement();
        instance.setDeltaMovement(new Vec3D(x, mot.y, mot.z));
    }

    public void setMotY(double y) {
        Vec3D mot = instance.getDeltaMovement();
        instance.setDeltaMovement(new Vec3D(mot.x, y, mot.z));
    }

    public void setMotZ(double z) {
        Vec3D mot = instance.getDeltaMovement();
        instance.setDeltaMovement(new Vec3D(mot.x, mot.y, z));
    }

    public double getMotX() {
        return instance.getDeltaMovement().x;
    }

    public double getMotY() {
        return instance.getDeltaMovement().y;
    }

    public double getMotZ() {
        return instance.getDeltaMovement().z;
    }

#elseif version >= 1.14
    // motX/Y/Z is private 'mot' field on 1.14
    // For performance reasons, use the getMot/setMot methods
    // Reflection to access the private mot Vec3D is likely slower.
    public org.bukkit.util.Vector getMot() {
        Vec3D mot = instance.getMot();
        return new org.bukkit.util.Vector(mot.x, mot.y, mot.z);
    }

    public void setMotVector(org.bukkit.util.Vector mot) {
        instance.setMot(new Vec3D(mot.getX(), mot.getY(), mot.getZ()));
    }

    public void setMot(double x, double y, double z) {
        instance.setMot(new Vec3D(x, y, z));
    }

    public void fixMotNaN() {
        Vec3D mot = instance.getMot();
        if (Double.isNaN(mot.x) || Double.isNaN(mot.y) || Double.isNaN(mot.z)) {
            double x = mot.x;
            double y = mot.y;
            double z = mot.z;
            if (Double.isNaN(x)) x = 0.0;
            if (Double.isNaN(y)) y = 0.0;
            if (Double.isNaN(z)) z = 0.0;
            instance.setMot(new Vec3D(x, y, z));
        }
    }

    public void setMotX(double x) {
        Vec3D mot = instance.getMot();
        instance.setMot(new Vec3D(x, mot.y, mot.z));
    }

    public void setMotY(double y) {
        Vec3D mot = instance.getMot();
        instance.setMot(new Vec3D(mot.x, y, mot.z));
    }

    public void setMotZ(double z) {
        Vec3D mot = instance.getMot();
        instance.setMot(new Vec3D(mot.x, mot.y, z));
    }

    public double getMotX() {
        return instance.getMot().x;
    }

    public double getMotY() {
        return instance.getMot().y;
    }

    public double getMotZ() {
        return instance.getMot().z;
    }

#else
    public org.bukkit.util.Vector getMot() {
        return new org.bukkit.util.Vector(instance.motX, instance.motY, instance.motZ);
    }

    public void setMotVector(org.bukkit.util.Vector mot) {
        instance.motX = mot.getX();
        instance.motY = mot.getY();
        instance.motZ = mot.getZ();
    }

    public void setMot(double x, double y, double z) {
        instance.motX = x;
        instance.motY = y;
        instance.motZ = z;
    }

    public void fixMotNaN() {
        if (Double.isNaN(instance.motX)) {
            instance.motX = 0.0;
        }
        if (Double.isNaN(instance.motY)) {
            instance.motY = 0.0;
        }
        if (Double.isNaN(instance.motZ)) {
            instance.motZ = 0.0;
        }
    }

    public void setMotX(double x) {
        instance.motX = x;
    }

    public void setMotY(double y) {
        instance.motY = y;
    }

    public void setMotZ(double z) {
        instance.motZ = z;
    }

    public double getMotX() {
        return instance.motX;
    }

    public double getMotY() {
        return instance.motY;
    }

    public double getMotZ() {
        return instance.motZ;
    }
#endif

#if version >= 1.17
    private float yaw:yRot;
    private float pitch:xRot;
    public float lastYaw:yRotO;
    public float lastPitch:xRotO;
    private (AxisAlignedBBHandle) AxisAlignedBB boundingBoxField:bb;
#else
    public float yaw;
    public float pitch;
    public float lastYaw;
    public float lastPitch;
    private (AxisAlignedBBHandle) AxisAlignedBB boundingBoxField:boundingBox;
#endif

#if version >= 1.16
    protected boolean onGround;
#else
    public boolean onGround;
#endif

#if version >= 1.17
    public readonly boolean horizontalMovementBlocked:horizontalCollision;
#else
    public readonly boolean horizontalMovementBlocked:positionChanged;
#endif

#if version >= 1.17
    public readonly boolean verticalMovementBlocked:verticalCollision;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.horizontalCollision = blocked;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.verticalCollision = blocked;
    }
#elseif version >= 1.16
    public readonly boolean verticalMovementBlocked:v;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.positionChanged = blocked;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.v = blocked;
    }
#elseif version >= 1.15
    public readonly boolean verticalMovementBlocked:v;
    public unknown boolean movementBlocked:w;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.positionChanged = blocked;
        instance.w = instance.positionChanged || instance.v;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.v = blocked;
        instance.w = instance.positionChanged || instance.v;
    }
#elseif version >= 1.14
    public readonly boolean verticalMovementBlocked:y;
    public unknown boolean movementBlocked:z;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.positionChanged = blocked;
        instance.z = instance.positionChanged || instance.y;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.y = blocked;
        instance.z = instance.positionChanged || instance.y;
    }
#elseif version >= 1.13
    public readonly boolean verticalMovementBlocked:C;
    public unknown boolean movementBlocked:D;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.positionChanged = blocked;
        instance.D = instance.positionChanged || instance.C;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.C = blocked;
        instance.D = instance.positionChanged || instance.C;
    }
#elseif version >= 1.9
    public readonly boolean verticalMovementBlocked:B;
    public unknown boolean movementBlocked:C;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.positionChanged = blocked;
        instance.C = instance.positionChanged || instance.B;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.B = blocked;
        instance.C = instance.positionChanged || instance.B;
    }
#else
    public readonly boolean verticalMovementBlocked:E;
    public unknown boolean movementBlocked:F;

    public void setHorizontalMovementBlocked(boolean blocked) {
        instance.positionChanged = blocked;
        instance.F = instance.positionChanged || instance.E;
    }

    public void setVerticalMovementBlocked(boolean blocked) {
        instance.E = blocked;
        instance.F = instance.positionChanged || instance.E;
    }
#endif

#if version >= 1.17
    public boolean velocityChanged:hurtMarked;
#else
    public boolean velocityChanged;
#endif

#if version >= 1.14
    // On Minecraft 1.14 and later, instead of a boolean, it stores the actual slowdown to apply
    // If this is greater than 1.0E-7D then the factor should be applied
  #if version >= 1.17
    #require net.minecraft.world.entity.Entity protected Vec3D collidingWithBlockMultiplier:stuckSpeedMultiplier;
  #elseif version >= 1.16
    #require net.minecraft.world.entity.Entity protected Vec3D collidingWithBlockMultiplier:x;
  #elseif version >= 1.15
    #require net.minecraft.world.entity.Entity protected Vec3D collidingWithBlockMultiplier:y;
  #else
    #require net.minecraft.world.entity.Entity protected Vec3D collidingWithBlockMultiplier:B;
  #endif

    public boolean isCollidingWithBlock() {
        Vec3D multiplier = instance#collidingWithBlockMultiplier;
  #if version >= 1.18
        return multiplier.lengthSqr() > 1.0E-7;
  #else
        return multiplier.g() > 1.0E-7;
  #endif
    }

    public org.bukkit.util.Vector getBlockCollisionMultiplier() {
        Vec3D multiplier = instance#collidingWithBlockMultiplier;
        return new org.bukkit.util.Vector(multiplier.x, multiplier.y, multiplier.z);
    }

    public void setNotCollidingWithBlock() {
        instance#collidingWithBlockMultiplier = Vec3D.ZERO;
    }
#else
    // On Minecraft 1.13.2 and before, there was only a 'collidingWithBlock' property
    // This always has the effect of slowing down the entity with factor 0.25/0.05/0.25
  #if version >= 1.13
    #require net.minecraft.world.entity.Entity protected boolean collidingWithBlock:F;
  #elseif version >= 1.9
    #require net.minecraft.world.entity.Entity protected boolean collidingWithBlock:E;
  #else
    #require net.minecraft.world.entity.Entity protected boolean collidingWithBlock:H;
  #endif

    public boolean isCollidingWithBlock() {
        return instance#collidingWithBlock;
    }

    public org.bukkit.util.Vector getBlockCollisionMultiplier() {
        boolean colliding = instance#collidingWithBlock;
        if (colliding) {
            return new org.bukkit.util.Vector(0.25, 0.05, 0.25);
        } else {
            return new org.bukkit.util.Vector(0.0, 0.0, 0.0);
        }
    }

    public void setNotCollidingWithBlock() {
        instance#collidingWithBlock = false;
    }
#endif

    // Seems unused.
    // #if version >= 1.13
    //     private unknown boolean outsideWorldBorderField:az;
    // #elseif version >= 1.10.2
    //     private unknown boolean outsideWorldBorderField:aw;
    // #elseif version >= 1.9.4
    //     private unknown boolean outsideWorldBorderField:av;
    // #elseif version >= 1.9
    //     private unknown boolean outsideWorldBorderField:au;
    // #else
    //     private unknown boolean outsideWorldBorderField:g;
    // #endif

    <code>
    @Deprecated
    public boolean isDead() {
        return isDestroyed();
    }
    </code>

#if version >= 1.17
    #require net.minecraft.world.entity.Entity private Entity.RemovalReason removalReason;

    // Marks the entity as removed, passively. Does not fire events.
    // This is called when the old entity has been hot-swapped over.
    public void setRemovedPassive() {
        instance#removalReason = Entity$RemovalReason.DISCARDED;
        instance.valid = false;
    }

    public void setDestroyed(boolean dead) {
        if (dead) {
            Entity$RemovalReason reason = instance.getRemovalReason();
  #if version >= 1.18
            if (reason == null || !reason.shouldDestroy()) {
                instance.remove(Entity$RemovalReason.KILLED);
            }
  #else
            if (reason == null || !reason.a()) {
                instance.a(Entity$RemovalReason.KILLED);
            }
  #endif
        } else {
            //TODO: Hmmm??
            instance.unsetRemoved();
        }
    }

    public boolean isDestroyed() {
        Entity$RemovalReason reason = instance.getRemovalReason();
  #if version >= 1.18
        return reason != null && reason.shouldDestroy();
  #else
        return reason != null && reason.a();
  #endif
    }

    public boolean isSavingAllowed() {
        Entity$RemovalReason reason = instance.getRemovalReason();
  #if version >= 1.18
        return reason == null || reason.shouldSave();
  #else
        return reason == null || reason.b();
  #endif
    }

    public boolean isLoadedInWorld() {
        return !instance.isRemoved();
    }

  #if version >= 1.18
    public int getChunkX() {
        BlockPosition blockPosition = instance.blockPosition();
        return blockPosition.getX() >> 4;
    }

    public int getChunkY() {
        BlockPosition blockPosition = instance.blockPosition();
        return blockPosition.getY() >> 4;
    }

    public int getChunkZ() {
        BlockPosition blockPosition = instance.blockPosition();
        return blockPosition.getZ() >> 4;
    }
  #else
    public int getChunkX() {
        BlockPosition blockPosition = instance.getChunkCoordinates(); // Note: block coords, NOT chunk coords!
        return blockPosition.getX() >> 4;
    }

    public int getChunkY() {
        BlockPosition blockPosition = instance.getChunkCoordinates(); // Note: block coords, NOT chunk coords!
        return blockPosition.getY() >> 4;
    }

    public int getChunkZ() {
        BlockPosition blockPosition = instance.getChunkCoordinates(); // Note: block coords, NOT chunk coords!
        return blockPosition.getZ() >> 4;
    }
  #endif

    public optional void setLoadedInWorld_pre_1_17:###(boolean loaded);

  #if exists net.minecraft.world.entity.Entity public net.minecraft.world.level.chunk.Chunk getCurrentChunk();
    public (org.bukkit.Chunk) Chunk getCurrentChunk();
  #else
    public (org.bukkit.Chunk) Chunk getCurrentChunk() {
        WorldServer world;
    #if version >= 1.20
        world = (WorldServer) instance.level();
    #else
        world = (WorldServer) instance.level;
    #endif

    #if exists net.minecraft.world.entity.Entity public int moonrise$getSectionX();
        // Paper new chunk system STILL does not care about all this
        return world.getChunkIfLoaded(instance.moonrise$getSectionX(), instance.moonrise$getSectionZ());
    #elseif exists io.papermc.paper.chunk.system.entity.EntityLookup.EntityCallback
        // Paper new chunk system does not care about all this
        return world.getChunkIfLoaded(instance.sectionX, instance.sectionZ);
    #else
        #require net.minecraft.world.entity.Entity private net.minecraft.world.level.entity.EntityInLevelCallback levelCallback;
        net.minecraft.world.level.entity.EntityInLevelCallback callback = instance#levelCallback;
        if (callback == net.minecraft.world.level.entity.EntityInLevelCallback.NULL) {
            return null;
        }

        // Read the x/y/z long chunk coordinates from the entry
        #require net.minecraft.world.level.entity.PersistentEntitySectionManager.a private long currentSectionKey;
        long coordKey = callback#currentSectionKey;

        // Convert to chunk x/z coordinates
      #if version >= 1.18
        int chunkX = SectionPosition.x(coordKey);
        //int chunkY = SectionPosition.y(coordKey);
        int chunkZ = SectionPosition.z(coordKey);
      #else
        int chunkX = SectionPosition.b(coordKey);
        //int chunkY = SectionPosition.c(coordKey);
        int chunkZ = SectionPosition.d(coordKey);
      #endif
        return world.getChunkIfLoaded(chunkX, chunkZ);
    #endif
    }
  #endif
#else
  #if version >= 1.13
    #require net.minecraft.world.entity.Entity public boolean isLoaded:inChunk;
    #if fieldexists net.minecraft.world.entity.Entity public int chunkX
      #require net.minecraft.world.entity.Entity public int chunkX;
      #require net.minecraft.world.entity.Entity public int chunkY;
      #require net.minecraft.world.entity.Entity public int chunkZ;
    #else
      #require net.minecraft.world.entity.Entity public int chunkX:ae;
      #require net.minecraft.world.entity.Entity public int chunkY:af;
      #require net.minecraft.world.entity.Entity public int chunkZ:ag;
    #endif
  #elseif version >= 1.11
    #require net.minecraft.world.entity.Entity public boolean isLoaded:aa;
    #require net.minecraft.world.entity.Entity public int chunkX:ab;
    #require net.minecraft.world.entity.Entity public int chunkY:ac;
    #require net.minecraft.world.entity.Entity public int chunkZ:ad;
  #elseif version >= 1.10.2
    #require net.minecraft.world.entity.Entity public boolean isLoaded:ab;
    #require net.minecraft.world.entity.Entity public int chunkX:ac;
    #require net.minecraft.world.entity.Entity public int chunkY:ad;
    #require net.minecraft.world.entity.Entity public int chunkZ:ae;
  #elseif version >= 1.9
    #require net.minecraft.world.entity.Entity public boolean isLoaded:aa;
    #require net.minecraft.world.entity.Entity public int chunkX:ab;
    #require net.minecraft.world.entity.Entity public int chunkY:ac;
    #require net.minecraft.world.entity.Entity public int chunkZ:ad;
  #else
    #require net.minecraft.world.entity.Entity public boolean isLoaded:ad;
    #require net.minecraft.world.entity.Entity public int chunkX:ae;
    #require net.minecraft.world.entity.Entity public int chunkY:af;
    #require net.minecraft.world.entity.Entity public int chunkZ:ag;
  #endif

  // #if exists net.minecraft.world.entity.Entity public void setCurrentChunk(net.minecraft.world.level.chunk.Chunk chunk);
  //   public void setChunkX(int value) {
  //       instance.setCurrentChunk(null);
  //       com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle.T.chunkX.setInteger(instance, value);
  //   }
  //   public void setChunkY(int value) {
  //       instance.setCurrentChunk(null);
  //       com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle.T.chunkY.setInteger(instance, value);
  //   }
  //   public void setChunkZ(int value) {
  //       instance.setCurrentChunk(null);
  //       com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle.T.chunkZ.setInteger(instance, value);
  //   }
  // #else
  //   public void setChunkX(int value) {
  //       com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle.T.chunkX.setInteger(instance, value);
  //   }
  //   public void setChunkY(int value) {
  //       com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle.T.chunkY.setInteger(instance, value);
  //   }
  //   public void setChunkZ(int value) {
  //       com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle.T.chunkZ.setInteger(instance, value);
  //   }
  // #endif

    // Marks the entity as removed, passively. Does not fire events.
    // This is called when the old entity has been hot-swapped over.
    public void setRemovedPassive() {
        instance.dead = true;
        instance.valid = false;
    }

    public void setDestroyed(boolean dead) {
        instance.dead = dead;
    }

    public boolean isDestroyed() {
        return instance.dead;
    }

    public boolean isSavingAllowed() {
        return !instance.dead;
    }

    public boolean isLoadedInWorld() {
        return instance#isLoaded;
    }

    public int getChunkX() {
        return instance#chunkX;
    }

    public int getChunkY() {
        return instance#chunkY;
    }

    public int getChunkZ() {
        return instance#chunkZ;
    }

    public optional void setLoadedInWorld_pre_1_17(boolean loaded) {
        instance#isLoaded = loaded;
    }

  #if exists net.minecraft.world.entity.Entity public net.minecraft.world.level.chunk.Chunk getCurrentChunk();
    public (org.bukkit.Chunk) Chunk getCurrentChunk();
  #elseif version >= 1.13 && exists net.minecraft.world.entity.Entity public int chunkX;
    public (org.bukkit.Chunk) Chunk getCurrentChunk() {
        if (!instance.inChunk) {
            return null;
        }
    #if version >= 1.16
        return ((WorldServer) instance.world).getChunkIfLoaded(instance.chunkX, instance.chunkZ);
    #elseif exists net.minecraft.world.level.World public net.minecraft.world.level.chunk.Chunk getChunkIfLoaded(int x, int z);
        return instance.world.getChunkIfLoaded(instance.chunkX, instance.chunkZ);
    #else
        return ((ChunkProviderServer) instance.world.getChunkProvider()).getChunkAt(instance.chunkX, instance.chunkZ, false);
    #endif
    }
  #else
    public (org.bukkit.Chunk) Chunk getCurrentChunk() {
        boolean isLoaded = instance#isLoaded;
        if (!isLoaded) {
            return null;
        }

        int chunkX = instance#chunkX;
        int chunkZ = instance#chunkZ;
        return instance.world.getChunkIfLoaded(chunkX, chunkZ);
    }
  #endif
#endif

#if version >= 1.18
    public float getWidth:getBbWidth();
    public float getHeight:getBbHeight();
#elseif version >= 1.14
    public float getWidth();
    public float getHeight();
#else
    public unknown float width;
    public unknown float length;

    public float getWidth() {
        return instance.width;
    }

    public float getHeight() {
        return instance.length;
    }
#endif

#if version >= 1.17
    public float fallDistance:flyDist;
#else
    public float fallDistance;
#endif

#select version >=
#case 1.17:   #require net.minecraft.world.entity.Entity private float stepCounter:nextStep;
#case 1.16.2: #require net.minecraft.world.entity.Entity private float stepCounter:am;
#case 1.16:   #require net.minecraft.world.entity.Entity private float stepCounter:at;
#case 1.14:   #require net.minecraft.world.entity.Entity private float stepCounter:av;
#case 1.13:   #require net.minecraft.world.entity.Entity private float stepCounter:aA;
#case 1.10.2: #require net.minecraft.world.entity.Entity private int stepCounter:ax;
#case 1.9.4:  #require net.minecraft.world.entity.Entity private int stepCounter:aw;
#case 1.9:    #require net.minecraft.world.entity.Entity private int stepCounter:av;
#case else:   #require net.minecraft.world.entity.Entity private int stepCounter:h;
#endselect

    public void setStepCounter(float value) {
#if version >= 1.13
        instance#stepCounter = value;
#else
        instance#stepCounter = (int)value;
#endif
    }

    public float getStepCounter() {
        return (float) instance#stepCounter;
    }

#if version >= 1.19.4
    public float getHeightOffset:maxUpStep();
#else
    public float getHeightOffset() {
  #select version >=
  #case 1.17:  return instance.maxUpStep;
  #case 1.16:  return instance.G;
  #case 1.15:  return instance.H;
  #case 1.14:  return instance.K;
  #case 1.13:  return instance.Q;
  #case 1.9:   return instance.P;
  #case else:  return instance.S;
  #endselect
    }
#endif

#if version >= 1.17
    public boolean noclip:noPhysics;
#elseif version >= 1.8.3
    public boolean noclip;
#else
    public boolean noclip:T;
#endif

    protected (RandomSourceHandle) net.minecraft.util.RandomSource random;

    private int fireTicks:remainingFireTicks;

#if version >= 1.17
    public int ticksLived:tickCount;
    public optional int field_maxFireTicks:maxFireTicks;
    protected final (com.bergerkiller.bukkit.common.wrappers.DataWatcher) net.minecraft.network.syncher.DataWatcher datawatcherField:entityData;
#else
    public int ticksLived;
    public optional int field_maxFireTicks:maxFireTicks;
    protected (com.bergerkiller.bukkit.common.wrappers.DataWatcher) net.minecraft.network.syncher.DataWatcher datawatcherField:datawatcher;
#endif

#if version >= 1.17
    protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:DATA_SHARED_FLAGS_ID;
    private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:DATA_AIR_SUPPLY_ID;
    private static optional final (DataWatcher.Key<?>) DataWatcherObject<java.util.Optional<IChatBaseComponent>> DATA_CUSTOM_NAME;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY;

#elseif version >= 1.16.2
    protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:S;
    private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:AIR_TICKS;
    private static optional final (DataWatcher.Key<?>) DataWatcherObject<java.util.Optional<IChatBaseComponent>> DATA_CUSTOM_NAME:aq;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:ar;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:as;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:at;

#elseif version >= 1.16
    protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:T;
    private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:AIR_TICKS;
    private static optional final (DataWatcher.Key<?>) DataWatcherObject<java.util.Optional<IChatBaseComponent>> DATA_CUSTOM_NAME:ax;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:ay;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:az;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:aA;

#elseif version >= 1.14
  #if version >= 1.15
    protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:T;
  #else
    protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:W;
  #endif
    private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:AIR_TICKS;
    private static optional final (DataWatcher.Key<?>) DataWatcherObject<java.util.Optional<IChatBaseComponent>> DATA_CUSTOM_NAME:az;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:aA;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:aB;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:aC;

    // TODO: New Pose datawatcher entry since 1.14
    // protected static final DataWatcherObject<EntityPose> X = DataWatcher.a(Entity.class, DataWatcherRegistry.s);

#elseif version >= 1.9
    /* === MC 1.9 and onwards has constants like these for datawatcher keys === */
    #select version >=
    #case 1.13:   protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:ac;
    #case 1.11:   protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:Z;
    #case 1.10.2: protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:aa;
    #case 1.9.4:  private static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:ay;
    #case else:   private static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:ax;
    #endselect
    #if version >= 1.13
        private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:aD;
        private static optional final (DataWatcher.Key<?>) DataWatcherObject<java.util.Optional<IChatBaseComponent>> DATA_CUSTOM_NAME:aE;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:aF;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:aG;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:aH;
    #elseif version >= 1.12
        private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:aA;
        private static optional final (DataWatcher.Key<?>) DataWatcherObject<String> DATA_CUSTOM_NAME:aB;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:aC;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:aD;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:aE;
    #elseif version >= 1.10.2
        private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:az;
        private static optional final (DataWatcher.Key<?>) DataWatcherObject<String> DATA_CUSTOM_NAME:aA;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:aB;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:aC;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:aD;
    #elseif version >= 1.9.4
        private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:az;
        private static optional final (DataWatcher.Key<?>) DataWatcherObject<String> DATA_CUSTOM_NAME:aA;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:aB;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:aC;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:###;
    #else
        private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:ay;
        private static optional final (DataWatcher.Key<?>) DataWatcherObject<String> DATA_CUSTOM_NAME:az;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:aA;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:aB;
        private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:###;
    #endif
#else
    /* === MC 1.8.9: These constants do not exist == */
    protected static optional final (DataWatcher.Key<Byte>) DataWatcherObject<Byte> DATA_FLAGS:###;
    private static optional final (DataWatcher.Key<Integer>) DataWatcherObject<Integer> DATA_AIR_TICKS:###;
    private static optional final (DataWatcher.Key<?>) DataWatcherObject<String> DATA_CUSTOM_NAME:###;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_CUSTOM_NAME_VISIBLE:###;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_SILENT:###;
    private static optional final (DataWatcher.Key<Boolean>) DataWatcherObject<Boolean> DATA_NO_GRAVITY:###;
#endif
    <code>
    public static final DataWatcher.Key<Byte> DATA_FLAGS = DataWatcher.Key.Type.BYTE.createKey(T.DATA_FLAGS, 0);
    public static final DataWatcher.Key<Integer> DATA_AIR_TICKS = DataWatcher.Key.Type.INTEGER.createKey(T.DATA_AIR_TICKS, 1);
    public static final DataWatcher.Key<ChatText> DATA_CUSTOM_NAME;
    static {
        if (com.bergerkiller.bukkit.common.Common.evaluateMCVersion(">=", "1.13")) {
            DATA_CUSTOM_NAME = DataWatcher.Key.Type.CHAT_TEXT.createKey(T.DATA_CUSTOM_NAME, 2);
        } else {
            DATA_CUSTOM_NAME = DataWatcher.Key.Type.STRING.translate(ChatText.class).createKey(T.DATA_CUSTOM_NAME, 2);
        }
    }
    public static final DataWatcher.Key<Boolean> DATA_CUSTOM_NAME_VISIBLE = DataWatcher.Key.Type.BOOLEAN.createKey(T.DATA_CUSTOM_NAME_VISIBLE, 3);
    public static final DataWatcher.Key<Boolean> DATA_SILENT = DataWatcher.Key.Type.BOOLEAN.createKey(T.DATA_SILENT, 4);
    public static final DataWatcher.Key<Boolean> DATA_NO_GRAVITY = DataWatcher.Key.Type.BOOLEAN.createKey(T.DATA_NO_GRAVITY, -1);

    public static final int DATA_FLAG_ON_FIRE = (1 << 0);
    public static final int DATA_FLAG_SNEAKING = (1 << 1);
    public static final int DATA_FLAG_UNKNOWN1 = (1 << 2);
    public static final int DATA_FLAG_SPRINTING = (1 << 3);
    public static final int DATA_FLAG_UNKNOWN2 = (1 << 4);
    public static final int DATA_FLAG_INVISIBLE = (1 << 5);
    public static final int DATA_FLAG_GLOWING = (1 << 6);
    public static final int DATA_FLAG_FLYING = (1 << 7);
    </code>

#if version >= 1.17
    public boolean positionChanged:hasImpulse;
#elseif version >= 1.9
    public boolean positionChanged:impulse;
#else
    public boolean positionChanged:ai;
#endif

    public int portalCooldown;

#if version >= 1.17
    optional private final double[] move_SomeArray:pistonDeltas;
    optional private long move_SomeState:pistonDeltasGameTime;
#elseif version >= 1.16.2
    optional private final double[] move_SomeArray:aA;
    optional private long move_SomeState:aB;
#elseif version >= 1.16
    optional private final double[] move_SomeArray:aF;
    optional private long move_SomeState:aG;
#elseif version >= 1.14
    optional private final double[] move_SomeArray:aG;
    optional private long move_SomeState:aH;
#elseif version >= 1.13
    optional private final double[] move_SomeArray:aL;
    optional private long move_SomeState:aM;
#elseif version >= 1.12
    optional private final double[] move_SomeArray:aJ;
    optional private long move_SomeState:aK;
#elseif version >= 1.11.2
    optional private double[] move_SomeArray:aI;
    optional private long move_SomeState:aJ;
#else
    optional private double[] move_SomeArray:###;
    optional private long move_SomeState:###;
#endif

    public boolean valid;

// For some reason was down here on 1.8.9 and earlier
#if version <= 1.8.9
    protected (org.bukkit.entity.Entity) CraftEntity bukkitEntityField:bukkitEntity;
#endif

    /*
     # protected void ##METHODNAME##(BlockPosition blockposition, Block block) {
     *     SoundEffectType soundeffecttype = block.getStepSound();
     *     if (this.world.getType(blockposition.up()).getBlock() == Blocks.SNOW_LAYER) {
     *         soundeffecttype = Blocks.SNOW_LAYER.getStepSound();
     *         this.a(soundeffecttype.d(), soundeffecttype.a() * 0.15F, soundeffecttype.b());
     *     } else if (!block.getBlockData().getMaterial().isLiquid()) {
     *         this.a(soundeffecttype.d(), soundeffecttype.a() * 0.15F, soundeffecttype.b());
     *     }
     * }
     */
#if version >= 1.13
    protected void playStepSound((IntVector3) BlockPosition position, (BlockData) IBlockData blockData);
#else
    protected void playStepSound((IntVector3) BlockPosition position, (BlockData) Block blockData);
#endif

    /*
     # protected void ##METHODNAME##(float f, float f1) {
     *     // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
     *     if (Float.isNaN(f)) {
     *         f = 0;
     *     }
     *     if (f == Float.POSITIVE_INFINITY || f == Float.NEGATIVE_INFINITY) {
     *         if (this instanceof EntityPlayer) {
     *             this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid yaw");
     *             ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)"); //Spigot "Nope" -> Descriptive reason
     *         }
     *         f = 0;
     *     }
     *     ...
     * }
     */
#if exists net.minecraft.world.entity.Entity public void setRot(float yaw, float pitch);
    public void setRotation:setRot(float yaw, float pitch);
#elseif exists net.minecraft.world.entity.Entity public void setYawPitch(float yaw, float pitch);
    public void setRotation:setYawPitch(float yaw, float pitch);
#elseif version >= 1.18
    protected void setRotation:setRot(float yaw, float pitch);
#else
    protected void setRotation:setYawPitch(float yaw, float pitch);
#endif

    /*
     # protected void ##METHODNAME##(float i) { // CraftBukkit - int -> float
     *     if (!this.fireProof) {
     *         this.damageEntity(DamageSource.FIRE, (float) i);
     *     }
     * }
     */
#if exists net.minecraft.world.entity.Entity public void burn(float dmg);
    public void burn(float dmg);
#elseif version >= 1.19.4
    public void burn(float dmg) {
        if (!instance.fireImmune()) {
            instance.hurt(instance.damageSources().inFire(), dmg);
        }
    }
#elseif version >= 1.18
    public void burn(float dmg) {
        if (!instance.fireImmune()) {
            instance.hurt(DamageSource.IN_FIRE, dmg);
        }
    }
#elseif version >= 1.16.5 && forge == mohist
    // MohistMC remapping bug (I think?)
    protected void burn:d(float dmg);
#elseif version >= 1.16
    public void burn(float dmg) {
        if (!instance.isFireProof()) {
  #if version >= 1.17
            instance.damageEntity(DamageSource.IN_FIRE, dmg);
  #else
            instance.damageEntity(DamageSource.FIRE, dmg);
  #endif
        }
    }
#elseif forge
    public void burn(float dmg) {
        #require net.minecraft.world.entity.Entity protected void burn(int dmg);
        instance#burn((int)dmg);
    }
#else
    protected void burn(float dmg);
#endif

#if version >= 1.21.2
    public (org.bukkit.entity.Item) EntityItem dropItem((org.bukkit.Material) Item material, int amount, float force) {
        return instance.spawnAtLocation((WorldServer) instance.level(), new ItemStack(material, amount), force);
    }
#elseif version >= 1.18
    public (org.bukkit.entity.Item) EntityItem dropItem((org.bukkit.Material) Item material, int amount, float force) {
        return instance.spawnAtLocation(new ItemStack(material, amount), force);
    }
#elseif version >= 1.13
    public (org.bukkit.entity.Item) EntityItem dropItem((org.bukkit.Material) Item material, int amount, float force) {
        return instance.a(new ItemStack(material, amount), force);
    }
#else
    public (org.bukkit.entity.Item) EntityItem dropItem:a((org.bukkit.Material) Item material, int amount, float force);
#endif

#if version >= 1.21.2
    public (org.bukkit.entity.Item) EntityItem dropItemStack((org.bukkit.inventory.ItemStack) ItemStack itemstack, float force) {
        WorldServer world = (WorldServer) instance.level();
        return instance.spawnAtLocation(world, itemstack, force);
    }
#elseif version >= 1.18
    public (org.bukkit.entity.Item) EntityItem dropItemStack:spawnAtLocation((org.bukkit.inventory.ItemStack) ItemStack itemstack, float force);
#else
    public (org.bukkit.entity.Item) EntityItem dropItemStack:a((org.bukkit.inventory.ItemStack) ItemStack itemstack, float force);
#endif

    /*
     * void move(...) {
     *     ...
     #     this.a(this.##METHODNAME##(), f1, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
     * }
     */
#if exists net.minecraft.world.entity.Entity public net.minecraft.sounds.SoundEffect getSwimSound();
    // Paper made it public
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) SoundEffect getSwimSound();
#elseif version >= 1.9
    protected (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) SoundEffect getSwimSound();
#else
    protected (com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String getSwimSound();
#endif

    public void handleMovementEmissionAndPlaySound((org.bukkit.util.Vector) Vec3D movement, (IntVector3) BlockPosition blockPosition, (BlockData) IBlockData iblockdata) {
#if version >= 1.21.2
        // Get what this entity should emit
        #require Entity protected Entity.MovementEmission getMovementEmission();
        Entity$MovementEmission entity_movementemission = instance#getMovementEmission();
        if (!entity_movementemission.emitsAnything() || instance.isPassenger()) {
            return;
        }

        // Call the default logic method
        #require Entity private void applyMovementEmissionAndPlaySound(Entity.MovementEmission emission, Vec3D vec3d, BlockPosition blockPosition, IBlockData iBlockData);
        instance#applyMovementEmissionAndPlaySound(entity_movementemission, movement, blockPosition, iblockdata);

#else
        //TODO: This stuff is vastly outdated :(

  #select version >=
  #case 1.17
        #require Entity public float walkedDistanceXZ:walkDist;
        #require Entity public float walkedDistanceXYZ:moveDist;
  #case 1.16
        #require Entity public float walkedDistanceXZ:A;
        #require Entity public float walkedDistanceXYZ:B;
  #case 1.15
        #require Entity public float walkedDistanceXZ:B;
        #require Entity public float walkedDistanceXYZ:C;
  #case 1.14
        #require Entity public float walkedDistanceXZ:E;
        #require Entity public float walkedDistanceXYZ:F;
  #case 1.13
        #require Entity public float walkedDistanceXZ:K;
        #require Entity public float walkedDistanceXYZ:L;
  #case 1.9
        #require Entity public float walkedDistanceXZ:J;
        #require Entity public float walkedDistanceXYZ:K;
  #case else
        #require Entity public float walkedDistanceXZ:M;
        #require Entity public float walkedDistanceXYZ:N;
  #endselect

  #if version >= 1.9
        double dx = movement.x;
        double dy = movement.y;
        double dz = movement.z;
  #else
        double dx = movement.a;
        double dy = movement.b;
        double dz = movement.c;
  #endif

        float newWalkedDistanceXZ = (float) ((double) instance#walkedDistanceXZ + Math.sqrt(dx * dx + dz * dz) * 0.6D);
        float newWalkedDistanceXYZ = (float) ((double) instance#walkedDistanceXYZ + Math.sqrt(dx * dx + dz * dz + dy * dy) * 0.6D);
        instance#walkedDistanceXZ = newWalkedDistanceXZ;
        instance#walkedDistanceXYZ = newWalkedDistanceXYZ;

        // Play at an interval and if not in the air
        if (instance#walkedDistanceXYZ <= (float) instance#stepCounter || iblockdata.getBlock() == Blocks.AIR) {
            return;
        }

        int newStepCounter = (int) instance#walkedDistanceXYZ + 1;
  #if version >= 1.13
        instance#stepCounter = (float)newStepCounter;
  #else
        instance#stepCounter = (int)newStepCounter;
  #endif

        // isInWater()
  #if version >= 1.9
        boolean isInWater = instance.isInWater();
  #else
        boolean isInWater = instance.V();
  #endif

        if (isInWater) {
            Entity driver = null;
  #if version >= 1.9
            driver = instance.isVehicle() ? instance.getControllingPassenger() : null;
  #endif

            float f = (driver == instance) ? 0.35F : 0.4F;

  #if version >= 1.14
    #if version >= 1.18
            Vec3D mot = instance.getDeltaMovement();
    #else
            Vec3D mot = instance.getMot();
    #endif
            double motX = mot.x;
            double motY = mot.y;
            double motZ = mot.z;
  #else
            double motX = instance.motX;
            double motY = instance.motY;
            double motZ = instance.motZ;
  #endif

            float volume = (float) Math.sqrt(motX * motX * 0.2 +
                                             motY * motY +
                                             motZ * motZ * 0.2) * f;
            if (volume > 1.0F) {
                volume = 1.0F;
            }

            net.minecraft.util.RandomSource random = instance.random; // Mojang random 1.19+, java Random before
            float pitch = 1.0F + (random.nextFloat() - random.nextFloat()) * 0.4F;

  #if exists net.minecraft.world.entity.Entity public net.minecraft.sounds.SoundEffect getSwimSound();
            SoundEffect swimSound = instance.getSwimSound();
  #elseif version >= 1.9
            #require Entity protected SoundEffect getSwimSound();
            SoundEffect swimSound = instance#getSwimSound();
  #else
            #require Entity protected String getSwimSound();
            String swimSound = instance#getSwimSound();
  #endif
            instance.playSound(swimSound, volume, pitch);

        } else {
  #if version >= 1.13
            #require Entity protected void playStepSound(BlockPosition position, IBlockData blockData);
            instance#playStepSound(blockPosition, iblockdata);
  #else
            #require Entity protected void playStepSound(BlockPosition position, Block block);
            instance#playStepSound(blockPosition, iblockdata.getBlock());
  #endif
        }
#endif
    }

    /*
     * void move(...) {
     *     ...
     *     this.##METHODNAME##(this.aa(), f1, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
     * }
     * 
     # public void ##METHODNAME##(SoundEffect soundeffect, float f, float f1) {
     *     if (!this.isSilent()) {
     *         this.world.a((EntityHuman) null, this.locX, this.locY, this.locZ, soundeffect, this.bC(), f, f1);
     *     }
     * }
     */
#if version >= 1.9
    public void makeSound:playSound((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) SoundEffect soundeffect, float volume, float pitch);
#else
    public void makeSound:playSound((com.bergerkiller.bukkit.common.resources.ResourceKey<com.bergerkiller.bukkit.common.resources.SoundEffect>) String soundeffect, float volume, float pitch);
#endif

    // Note: counts rain too as being wet!
#if version >= 1.21.5
    public boolean isWet() {
        return instance.isInWaterOrRain() || instance.getInBlockState().is(Blocks.BUBBLE_COLUMN);
    }
#else
  #select version >=
  #case 1.18:   public boolean isWet:isInWaterRainOrBubble();
  #case 1.17:   public boolean isWet:aN();
  #case 1.16.4: public boolean isWet:aG();
  #case 1.16.2: public boolean isWet:aF();
  #case 1.16:   public boolean isWet:aC();
  #case 1.15:   public boolean isWet:ay();
  #case 1.14.3: public boolean isWet:au();
  #case 1.14:   public boolean isWet:at();
  #case 1.13:   public boolean isWet:ap();
  #case 1.12:   public boolean isWet:an();
  #case 1.10.2: public boolean isWet:ai();
  #case 1.9.4:  public boolean isWet:ah();
  #case 1.9:    public boolean isWet:ah();
  #case else:   public boolean isWet:U();
  #endselect
#endif

    // Note: checks just standing in water! Rain does not count.
#if version >= 1.21.5
    public boolean isInWaterUpdate() {
        return instance.isInWater() || instance.getInBlockState().is(Blocks.BUBBLE_COLUMN);
    }
#else
  #select version >=
  #case 1.18:   public boolean isInWaterUpdate:isInWaterOrBubble();
  #case 1.17:   public boolean isInWaterUpdate:aO();
  #case 1.16.4: public boolean isInWaterUpdate:aH();
  #case 1.16.2: public boolean isInWaterUpdate:aG();
  #case 1.16:   public boolean isInWaterUpdate:aD();
  #case 1.15:   public boolean isInWaterUpdate:az();
  #case 1.14:   public boolean isInWaterUpdate:au();
  #case 1.12:   public boolean isInWaterUpdate:aq();
  #case 1.10.2: public boolean isInWaterUpdate:ak();
  #case 1.9:    public boolean isInWaterUpdate:aj();
  #case else:   public boolean isInWaterUpdate:W();
  #endselect
#endif

    /*
     # public boolean ###METHODNAME###() {
     *     return this.inWater;
     * }
     */
#if version >= 1.9
    public boolean isInWater();
#else
    public boolean isInWater:V();
#endif

    /*
     * public void move(double d0, double d1, double d2) {
     *     ....
     *     {
     *         if (bl.getType() != org.bukkit.Material.AIR) {
     *             VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl);
     *             world.getServer().getPluginManager().callEvent(event);
     *         }
     *     }
     *     
     *     // CraftBukkit end
     *
     #     if (this.###METHODNAME###() && (!this.onGround || !this.isSneaking() || !(this instanceof EntityHuman)) && !this.isPassenger()) {
     *         double d22 = this.locX - d4;
     *         double d23 = this.locY - d5;
     *         ...
     *     }
     * }
     */
#if version >= 1.18
    protected boolean hasMovementSound() {
        #require net.minecraft.world.entity.Entity protected Entity.MovementEmission getMovementEmission();
        Entity$MovementEmission entity_movementemission = instance#getMovementEmission();
        return entity_movementemission.emitsSounds(); // sound only
    }
#elseif version >= 1.17
    protected boolean hasMovementSound() {
        #require net.minecraft.world.entity.Entity protected Entity.MovementEmission getMovementEmission:aI();
        Entity$MovementEmission entity_movementemission = instance#getMovementEmission();
        return entity_movementemission.c(); // sound only
    }
#elseif version >= 1.9
    protected boolean hasMovementSound:playStepSound();
#elseif version >= 1.8.3
    protected boolean hasMovementSound:s_();
#else
    protected boolean hasMovementSound:r_();
#endif

    /*
     # protected void ##METHODNAME##(double d0, boolean flag, IBlockData iblockdata, BlockPosition blockposition) {
     *     if (flag) {
     *         if (this.fallDistance > 0.0F) {
     *             iblockdata.getBlock().fallOn(this.world, blockposition, this, this.fallDistance);
     *         }
     *
     *         this.fallDistance = 0.0F;
     *     } else if (d0 < 0.0D) {
     *         this.fallDistance = ((float)(this.fallDistance - d0));
     *     }
     * }
     */
#if version >= 1.18
    protected void updateFalling:checkFallDamage(double d0, boolean flag, (BlockData) IBlockData blockData, (IntVector3) BlockPosition position);
#elseif version >= 1.9
    protected void updateFalling:a(double d0, boolean flag, (BlockData) IBlockData blockData, (IntVector3) BlockPosition position);
#else
    protected void updateFalling:a(double d0, boolean flag, (BlockData) Block blockData, (IntVector3) BlockPosition position);
#endif

    // Purpur: enables the use of legacy entity tracking, which calls updateViewer() every tick
    public optional final void setLegacyTrackingEntity(boolean isLegacyTrackingEntity);

    // See outsideWorldBorderField - is set and returned by these methods
#if version >= 1.14
    // Removed
    public boolean isOutsideWorldBorder() {
        return false;
    }
    public void setOutsideWorldBorder(boolean outside) {
    }
#elseif version >= 1.13
    public boolean isOutsideWorldBorder:bG();
    public void setOutsideWorldBorder:n(boolean outside);
#elseif version >= 1.12
    public boolean isOutsideWorldBorder:bz();
    public void setOutsideWorldBorder:k(boolean outside);
#elseif version >= 1.10.2
    public boolean isOutsideWorldBorder:br();
    public void setOutsideWorldBorder:k(boolean outside);
#elseif version >= 1.9.4
    public boolean isOutsideWorldBorder:bp();
    public void setOutsideWorldBorder:j(boolean outside);
#elseif version >= 1.9
    public boolean isOutsideWorldBorder:bo();
    public void setOutsideWorldBorder:j(boolean outside);
#elseif version >= 1.8.3
    public boolean isOutsideWorldBorder:aT();
    public void setOutsideWorldBorder:h(boolean outside);
#else
    public boolean isOutsideWorldBorder:aS();
    public void setOutsideWorldBorder:h(boolean outside);
#endif

    /*
     * protected void checkBlockCollisions() {
     *     AxisAlignedBB axisalignedbb = getBoundingBox();
     *     BlockPosition.PooledBlockPosition blockposition_pooledblockposition = BlockPosition.PooledBlockPosition.d(axisalignedbb.a + 0.001D, axisalignedbb.b + 0.001D, axisalignedbb.c + 0.001D);
     *     BlockPosition.PooledBlockPosition blockposition_pooledblockposition1 = BlockPosition.PooledBlockPosition.d(axisalignedbb.d - 0.001D, axisalignedbb.e - 0.001D, axisalignedbb.f - 0.001D);
     *     BlockPosition.PooledBlockPosition blockposition_pooledblockposition2 = BlockPosition.PooledBlockPosition.s();
     *     ...
     * }
     */
#if version >= 1.21.5
    protected void applyEffectsFromBlocks();
#elseif version >= 1.21.2
    public void applyEffectsFromBlocks();
#elseif version >= 1.18
    protected void applyEffectsFromBlocks:checkInsideBlocks();
#else
    protected void applyEffectsFromBlocks:checkBlockCollisions();
#endif

    /*
     * public double ##METHODNAME##(double d0, double d1, double d2) {
     *     double d3 = this.locX - d0;
     *     double d4 = this.locY - d1;
     *     double d5 = this.locZ - d2;
     *
     *     return MathHelper.sqrt(d3 * d3 + d4 * d4 + d5 * d5);
     * }
     */
#if version >= 1.18
    public double calculateDistanceSquared:distanceToSqr(double x, double y, double z);
#elseif version >= 1.16.2
    public double calculateDistanceSquared:h(double x, double y, double z);
#elseif version >= 1.15
    public double calculateDistanceSquared:g(double x, double y, double z);
#else
    public double calculateDistanceSquared:e(double x, double y, double z);
#endif

#select version >=
#case 1.21.2:  public boolean damageEntity:hurtOrSimulate((DamageSourceHandle) DamageSource damagesource, float damage);
#case 1.18:    public boolean damageEntity:hurt((DamageSourceHandle) DamageSource damagesource, float damage);
#case else:    public boolean damageEntity((DamageSourceHandle) DamageSource damagesource, float damage);
#endselect

#if version >= 1.18
    public String getStringUUID();
    public void setPosition:setPos(double x, double y, double z);
#else
    public String getStringUUID:getName();
    public void setPosition(double x, double y, double z);
#endif

#if version >= 1.18
    public void setSize(float width, float height) {
        #require net.minecraft.world.entity.Entity private EntitySize dimensions;
        #require net.minecraft.world.entity.Entity private float eyeHeight;

        EntityPose entitypose = instance.getPose();
        EntitySize newSize = EntitySize.scalable(width, height);

        instance#dimensions = newSize;

  #if version >= 1.20.5
        #require net.minecraft.world.entity.Entity protected float getEyeHeight(EntityPose entitypose);
        instance#eyeHeight = instance#getEyeHeight(entitypose);
  #else
        #require net.minecraft.world.entity.Entity protected float getEyeHeight(EntityPose entitypose, EntitySize entitysize);
        instance#eyeHeight = instance#getEyeHeight(entitypose, newSize);
  #endif

        double half_width = (double) width / 2.0;
        AxisAlignedBB new_BoundingBox = new AxisAlignedBB(
            instance.getX() - half_width, instance.getY(), instance.getZ() - half_width,
            instance.getX() + half_width, instance.getY() + (double) height, instance.getZ() + half_width);

        instance.setBoundingBox(new_BoundingBox);
    }
#elseif version >= 1.17
    public void setSize(float width, float height) {
        #require net.minecraft.world.entity.Entity private EntitySize dimensions;
        #require net.minecraft.world.entity.Entity private float eyeHeight;
        #require net.minecraft.world.entity.Entity protected float getEyeHeight:getHeadHeight(EntityPose entitypose, EntitySize entitysize);

        EntityPose entitypose = instance.getPose();
        EntitySize newSize = EntitySize.b(width, height);

        instance#dimensions = newSize;
        instance#eyeHeight = instance#getEyeHeight(entitypose, newSize);

        double half_width = (double) width / 2.0;
        AxisAlignedBB new_BoundingBox = new AxisAlignedBB(
            instance.locX() - half_width, instance.locY(), instance.locZ() - half_width,
            instance.locX() + half_width, instance.locY() + (double) height, instance.locZ() + half_width);

        instance.a(new_BoundingBox);
    }
#elseif version >= 1.14
    public void setSize(float width, float height) {
        #require net.minecraft.world.entity.Entity private EntitySize size;
        #require net.minecraft.world.entity.Entity private float headHeight;
        #require net.minecraft.world.entity.Entity protected float getHeadHeight(EntityPose entitypose, EntitySize entitysize);

  #if version >= 1.14.1
        EntityPose entitypose = instance.getPose();
  #else
        EntityPose entitypose = instance.Z();
  #endif
        EntitySize newSize = EntitySize.b(width, height);

        instance#size = newSize;
        instance#headHeight = instance#getHeadHeight(entitypose, newSize);

        double half_width = (double) width / 2.0;
  #if version >= 1.15
        AxisAlignedBB new_BoundingBox = new AxisAlignedBB(
            instance.locX() - half_width, instance.locY(), instance.locZ() - half_width,
            instance.locX() + half_width, instance.locY() + (double) height, instance.locZ() + half_width);
  #else
        AxisAlignedBB new_BoundingBox = new AxisAlignedBB(
            instance.locX - half_width, instance.locY, instance.locZ - half_width,
            instance.locX + half_width, instance.locY + (double) height, instance.locZ + half_width);
  #endif

        instance.a(new_BoundingBox);
    }
#elseif version >= 1.8.3
  #if forge
    protected void setSize(float width, float height);
  #else
    public void setSize(float width, float height);
  #endif
#else
    public void setSize:a(float width, float height);
#endif

    public (AxisAlignedBBHandle) AxisAlignedBB getBoundingBox();

#if version >= 1.21.5
    public void setPositionRotation:snapTo(double x, double y, double z, float yaw, float pitch);
    public void setLocation:absSnapTo(double x, double y, double z, float yaw, float pitch);
    public void setBoundingBox((AxisAlignedBBHandle) AxisAlignedBB axisalignedbb);
    public float getHeadRotation:getYHeadRot();
#elseif version >= 1.18
    public void setPositionRotation:moveTo(double x, double y, double z, float yaw, float pitch);
    public void setLocation:absMoveTo(double x, double y, double z, float yaw, float pitch);
    public void setBoundingBox((AxisAlignedBBHandle) AxisAlignedBB axisalignedbb);
    public float getHeadRotation:getYHeadRot();
#else
    public void setPositionRotation(double x, double y, double z, float yaw, float pitch);
    public void setLocation(double x, double y, double z, float yaw, float pitch);
    public void setBoundingBox:a((AxisAlignedBBHandle) AxisAlignedBB axisalignedbb);
    public float getHeadRotation();
#endif

#select version >=
#case 1.18:  public void setHeadRotation:setYHeadRot(float angle);
#case 1.12:  public void setHeadRotation(float angle);
#case 1.9:   public void setHeadRotation:h(float angle);
#case else:  public void setHeadRotation:f(float angle);
#endselect

#if version >= 1.16.2
  // Method exists here
  #select version >=
  #case 1.18:  public boolean canCollideWith((EntityHandle) Entity otherEntity);
  #case 1.17:  public boolean canCollideWith:h((EntityHandle) Entity entity);
  #case else:  public boolean canCollideWith:j((EntityHandle) Entity entity);
  #endselect
#else
  // Only a method exists that returns a collidable bounding box for the other entity
  // This is implemented by shulker / boat to return non-null if alive and such
  public boolean canCollideWith((EntityHandle) Entity otherEntity) {
  #select version >=
  #case 1.16:   return otherEntity.ay() != null;
  #case 1.15:   return otherEntity.au() != null;
  #case 1.14.3: return otherEntity.aq() != null;
  #case 1.14:   return otherEntity.ap() != null;
  #case 1.12:   return otherEntity.al() != null;
  #case 1.10.2: return otherEntity.ag() != null;
  #case 1.9:    return otherEntity.af() != null;
  #case else:   return otherEntity.S() != null;
  #endselect
  }
#endif

#if version >= 1.16.2
    // Since 1.16.2 this was removed, implementation taken from 1.16.1 (minecart/strider/etc.)
    public (AxisAlignedBBHandle) AxisAlignedBB getEntityBoundingBox((EntityHandle) Entity entity) {
  #if version >= 1.18
        return instance.isPushable() ? entity.getBoundingBox() : null;
  #else
        return instance.isCollidable() ? entity.getBoundingBox() : null;
  #endif
    }
#else
    public (AxisAlignedBBHandle) AxisAlignedBB getEntityBoundingBox:j((EntityHandle) Entity entity);
#endif

#if version >= 1.17
    // Removed since 1.17, maybe because it was 'unsafe'. Calculates position using bounding box.
    public void setPositionFromBoundingBox() {
        AxisAlignedBB aabb = instance.getBoundingBox();
  #if version >= 1.18
        instance.setPosRaw((aabb.minX + aabb.maxX) / 2.0, aabb.minY, (aabb.minZ + aabb.maxZ) / 2.0);
  #else
        instance.setPositionRaw((aabb.minX + aabb.maxX) / 2.0, aabb.minY, (aabb.minZ + aabb.maxZ) / 2.0);
  #endif
    }
#else
    // public since 1.9, but private will still match
    // On some servers like Azurite it's protected
    private void setPositionFromBoundingBox:recalcPosition();
#endif

    public void handleFireBlockTick() {
        #require Entity private int remainingFireTicks;

        int newFireTicks = instance#remainingFireTicks;
        newFireTicks++;
        instance#remainingFireTicks = newFireTicks;

        if (newFireTicks == 0) {
            // CraftBukkit start
#if version >= 1.21
            org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent((org.bukkit.block.Block) null, instance.getBukkitEntity(), 8.0F);
#else
            org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent((org.bukkit.block.Block) null, instance.getBukkitEntity(), 8);
#endif
            org.bukkit.Bukkit.getPluginManager().callEvent(event);

            if (!event.isCancelled()) {
#if version >= 1.21
                float duration = event.getDuration();
#else
                int duration = event.getDuration();
#endif
#if version >= 1.13.2
                instance.igniteForSeconds(duration, false);
#else
                instance.igniteForSeconds(duration);
#endif
            }
            // CraftBukkit end
        }
    }

    public boolean isBurning:isOnFire();
    public optional int prop_getMaxFireTicks:getFireImmuneTicks();

#if version >= 1.21
    public void setOnFire:igniteForSeconds((float) float numSeconds);
#else
    public void setOnFire:igniteForSeconds((float) int numSeconds);
#endif

    <code>
    public int getMaxFireTicks() {
        if (T.prop_getMaxFireTicks.isAvailable()) {
            return T.prop_getMaxFireTicks.invoke(getRaw());
        } else if (T.field_maxFireTicks.isAvailable()) {
            return T.field_maxFireTicks.getInteger(getRaw());
        } else {
            throw new UnsupportedOperationException("Max Fire Ticks can not be read");
        }
    }
    </code>

#if version >= 1.19.4
    public (EntityHandle) net.minecraft.world.entity.EntityLiving getDriverEntity:getControllingPassenger();
#elseif version >= 1.9
    public (EntityHandle) Entity getDriverEntity:getControllingPassenger();
#else
    public (EntityHandle) Entity getDriverEntity() {
        return null; // driver feature not a thing on this server
    }
#endif

#if version >= 1.13
    public void onTick:tick();
#elseif version >= 1.12
    public void onTick:B_();
#else if version >= 1.11
    public void onTick:A_();
#elseif version >= 1.9
    public void onTick:m();
#elseif version >= 1.8.3
    public void onTick:t_();
#else
    public void onTick:s_();
#endif

#if version >= 1.21.6
    public void loadFromNBT((CommonTagCompound) NBTTagCompound compound) {
        com.bergerkiller.bukkit.common.internal.logic.ScopedProblemReporter reporter = #createScopedProblemReporter();
        try {
            ValueInput input = #createTagValueInput(reporter, instance.registryAccess(), compound);
            instance.load(input);
        } finally {
            reporter.close();
        }
    }
#elseif version >= 1.18
    public void loadFromNBT:load((CommonTagCompound) NBTTagCompound compound);
#elseif version >= 1.17
    public void loadFromNBT:load((CommonTagCompound) NBTTagCompound compound);
#elseif version >= 1.16
    public void loadFromNBT:load((CommonTagCompound) NBTTagCompound compound);
#else
    public void loadFromNBT:f((CommonTagCompound) NBTTagCompound compound);
#endif

    // Note: ValueOutput == NBTTagCompound on versions before 1.21.
    // Paper/spigot and across versions these parameters changed a lot
    // The API has all parameters that can possibly be specified (for paper)
    // Unsupported ones are ignored.
    public void saveWithoutId((ValueOutputHandle) ValueOutput valueoutput, boolean includeAll, boolean includeNonSaveable, boolean forceSerialization) {
#if version >= 1.21.4 && paper
        instance.saveWithoutId(valueoutput, includeAll, includeNonSaveable, forceSerialization);
#elseif version >= 1.20.3
        instance.saveWithoutId(valueoutput, includeAll);
#else
        instance.saveWithoutId(valueoutput);
#endif
    }

#if version >= 1.21.6
    public void saveToNBT((CommonTagCompound) NBTTagCompound compound) {
        com.bergerkiller.bukkit.common.internal.logic.ScopedProblemReporter reporter = #createScopedProblemReporter();
        try {
            ValueOutput output = #createTagValueOutput(reporter, instance.registryAccess(), compound);
            instance.saveWithoutId(output);
        } finally {
            reporter.close();
        }
    }
#else
    public void saveToNBT((CommonTagCompound) NBTTagCompound compound) {
        // NBTTagCompound == ValueOutput so call the other one directly
        instance.saveWithoutId(compound);
    }
#endif

    public int getId();

#if version >= 1.18
    public UUID getUniqueID:getUUID();
    public (com.bergerkiller.bukkit.common.wrappers.DataWatcher) net.minecraft.network.syncher.DataWatcher getDataWatcher:getEntityData();
    public boolean isSneaking:isShiftKeyDown();
    public void appendEntityCrashDetails:fillCrashReportCategory((CrashReportSystemDetailsHandle) CrashReportSystemDetails crashreportsystemdetails);
#else
    public UUID getUniqueID();
    public (com.bergerkiller.bukkit.common.wrappers.DataWatcher) net.minecraft.network.syncher.DataWatcher getDataWatcher();
    public boolean isSneaking();
    public void appendEntityCrashDetails((CrashReportSystemDetailsHandle) CrashReportSystemDetails crashreportsystemdetails);
#endif

#select version >=
#case 1.18:     public void onPush:push(double d0, double d1, double d2);
#case 1.16.2:   public void onPush:i(double d0, double d1, double d2);
#case 1.15:     public void onPush:h(double d0, double d1, double d2);
#case 1.11:     public void onPush:f(double d0, double d1, double d2);
#case else:     public void onPush:g(double d0, double d1, double d2);
#endselect

#select version >=
#case 1.20:   public final void positionRider((org.bukkit.entity.Entity) Entity passenger);
#case 1.18:   public void positionRider((org.bukkit.entity.Entity) Entity passenger);
#case 1.17:   public void positionRider:i((org.bukkit.entity.Entity) Entity passenger);
#case 1.9:    public void positionRider:k((org.bukkit.entity.Entity) Entity passenger);
#case else:   public void positionRider((org.bukkit.entity.Entity) Entity passenger) { instance.al(); }
#endselect

    /*
     * Entity class:
     *
     * public int ##METHODNAME##() {
     *     return 300;
     * }
     */
#select version >=
#case 1.18:   public int getPortalCooldownMaximum:getDimensionChangingDelay();
#case 1.16:   public int getPortalCooldownMaximum:getDefaultPortalCooldown();
#case 1.15:   public int getPortalCooldownMaximum:ba();
#case 1.14.3: public int getPortalCooldownMaximum:aX();
#case 1.14:   public int getPortalCooldownMaximum:aW();
#case 1.13:   public int getPortalCooldownMaximum:aQ();
#case 1.12:   public int getPortalCooldownMaximum:aM();
#case 1.10.2: public int getPortalCooldownMaximum:aE();
#case 1.9:    public int getPortalCooldownMaximum:aC();
#case 1.8.3:  public int getPortalCooldownMaximum:aq();
#case else:   public int getPortalCooldownMaximum:ar();
#endselect

#if version >= 1.21
    public boolean isInsidePortalThisTick() {
        PortalProcessor processor = instance.portalProcess;
        return processor != null && processor.isInsidePortalThisTick();
    }

    public void suppressPortalThisTick() {
        PortalProcessor processor = instance.portalProcess;
        if (processor != null) {
            processor.setAsInsidePortalThisTick(false);
        }
    }

    public int getPortalTime() {
        PortalProcessor processor = instance.portalProcess;
        return processor == null ? 0 : processor.getPortalTime();
    }

    public boolean setPortalTime(int portalTimeTicks) {
        PortalProcessor processor = instance.portalProcess;
        if (processor != null) {
            #require net.minecraft.world.entity.PortalProcessor private int portalTime;
            processor#portalTime = portalTimeTicks;
            return true;
        } else {
            return false;
        }
    }

    public int getPortalWaitTime() {
        PortalProcessor processor = instance.portalProcess;
        if (processor == null) {
            // Return 1 so that the cooldown of 0 doesn't trigger any teleportations
            return 1;
        }

        #require PortalProcessor private net.minecraft.world.level.block.Portal portalProcessorPortal:portal;
        net.minecraft.world.level.block.Portal portal = processor#portalProcessorPortal;
        return portal.getPortalTransitionTime((WorldServer) instance.level(), instance);
    }
#else
    #require Entity protected boolean insidePortalThisTick;
    #require Entity protected int portalTime;

    public boolean isInsidePortalThisTick() {
        return instance#insidePortalThisTick;
    }

    public void suppressPortalThisTick() {
        instance#insidePortalThisTick = false;
    }

    public int getPortalTime() {
        return instance#portalTime;
    }

    public boolean setPortalTime(int portalTimeTicks) {
        instance#portalTime = portalTimeTicks;
        return true;
    }

    public int getPortalWaitTime();
#endif

    // On some versions of the server, entities push items into hoppers instead of hoppers pulling from entities
    // This is normally done in onTick, but if overrided, it wouldn't run anymore. Call this method
    // in onTick
#if exists net.techcable.tacospigot.HopperPusher
    public optional void opt_tick_pushToHopper() {
        if (instance instanceof net.techcable.tacospigot.HopperPusher) {
            ((net.techcable.tacospigot.HopperPusher) instance).tryPutInHopper();
        }
    }
#else
    public optional void opt_tick_pushToHopper:###();
#endif

    // Since MC 1.17. Has a special 'cause' Bukkit parameter on paper since 1.20.4-ish.
#if version >= 1.18
    public optional void remove(net.minecraft.world.entity.Entity.RemovalReason removalReason);
#elseif version >= 1.17
    public optional void remove:a(net.minecraft.world.entity.Entity.RemovalReason removalReason);
#else
    public optional void remove:###(net.minecraft.world.entity.Entity.RemovalReason removalReason);
#endif

    // Since MC 1.16
#if version >= 1.18
    public optional (InteractionResult) EnumInteractionResult onInteractBy_1_16:interact((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#elseif version >= 1.16
    public optional (InteractionResult) EnumInteractionResult onInteractBy_1_16:a((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#else
    public optional (InteractionResult) EnumInteractionResult onInteractBy_1_16:###((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#endif

    // Since MC 1.11.2
#if version >= 1.11.2 && version < 1.16
    public optional boolean onInteractBy_1_11_2:b((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#else
    public optional boolean onInteractBy_1_11_2:###((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#endif

    // Since MC 1.9
#if version >= 1.9 && version < 1.11.2
    public optional boolean onInteractBy_1_9:a((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.ItemStack) ItemStack itemstack, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#else
    public optional boolean onInteractBy_1_9:###((org.bukkit.entity.HumanEntity) EntityHuman human, (org.bukkit.inventory.ItemStack) ItemStack itemstack, (org.bukkit.inventory.MainHand) EnumHand enumhand);
#endif

    // Since MC 1.8.9
#if version < 1.9
    public optional boolean onInteractBy_1_8_8:e((org.bukkit.entity.HumanEntity) EntityHuman entityhuman);
#else
    public optional boolean onInteractBy_1_8_8:###((org.bukkit.entity.HumanEntity) EntityHuman entityhuman);
#endif

    // Since MC 1.17 - whether the entity is always ticked even if no players are nearby
    // Used by the persistent entity manager, setting visibility to TICKING
#if version >= 1.18
    public boolean isAlwaysTicked:isAlwaysTicking();
#elseif version >= 1.17
    public boolean isAlwaysTicked:dn();
#else
    public boolean isAlwaysTicked() {
        return true;
    }
#endif

    public boolean hasCustomName();

#if version >= 1.13
    public (ChatText) IChatBaseComponent getCustomName();
#else
    public (ChatText) String getCustomName();
#endif

#if version >= 1.18
    public void collide:push((EntityHandle) Entity entity);
#else
    public void collide((EntityHandle) Entity entity);
#endif

#if version >= 1.20
    public org.bukkit.World getBukkitWorld() {
        return instance.level().getWorld();
    }

    public (WorldHandle) World getWorld:level();

    // Public on Paper
  #if exists net.minecraft.world.entity.Entity public void setLevel(net.minecraft.world.level.World world);
    public void setWorld:setLevel((WorldHandle) World world);
  #else
    protected void setWorld:setLevel((WorldHandle) World world);
  #endif
#else
    public org.bukkit.World getBukkitWorld() {
  #if version >= 1.17
        return instance.level.getWorld();
  #else
        return instance.world.getWorld();
  #endif
    }

    public (WorldHandle) World getWorld() {
  #if version >= 1.17
        return instance.level;
  #else
        return instance.world;
  #endif
    }

    public void setWorld((WorldHandle) World world) {
  #if version >= 1.17
        instance.level = world;
  #else
        instance.world = world;
  #endif
  #if version < 1.16
        if (world != null) {
    #if version >= 1.13.1
            instance.dimension = world.worldProvider.getDimensionManager();
    #elseif version >= 1.9
            instance.dimension = world.worldProvider.getDimensionManager().getDimensionID();
    #else
            instance.dimension = world.worldProvider.getDimension();
    #endif
        } else {
    #if version >= 1.13.1
            instance.dimension = null;
    #else
            instance.dimension = 0;
    #endif
        }
  #endif
    }
#endif

    public org.bukkit.entity.Entity getBukkitEntity() {
#if version >= 1.20
        if (instance.level() == null) {
#elseif version >= 1.17
        if (instance.level == null) {
#else
        if (instance.world == null) {
#endif
            // We need this to avoid NPE's for non-spawned entities!
            #require net.minecraft.world.entity.Entity protected org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity;
            org.bukkit.entity.Entity entity = instance#bukkitEntity;
            if (entity == null) {
                org.bukkit.craftbukkit.CraftServer server = (org.bukkit.craftbukkit.CraftServer) org.bukkit.Bukkit.getServer();
                entity = org.bukkit.craftbukkit.entity.CraftEntity.getEntity(server, instance);
                instance#bukkitEntity = entity;
            }
            return entity;
        } else {
            return instance.getBukkitEntity();
        }
    }

    <code>
    public static EntityHandle fromBukkit(org.bukkit.entity.Entity entity) {
        return createHandle(com.bergerkiller.bukkit.common.conversion.type.HandleConversion.toEntityHandle(entity));
    }
    </code>
}
