package net.minecraft.server.level;

import net.minecraft.world.phys.Vec3D;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.VecDeltaCodec;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;

import com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle;
import com.bergerkiller.generated.net.minecraft.server.level.EntityTrackerEntryHandle;
import com.bergerkiller.generated.net.minecraft.server.level.EntityTrackerEntryStateHandle;
import com.bergerkiller.bukkit.common.protocol.CommonPacket;

// Same as PlayerChunkMap on MC 1.14 and later
class EntityTracker {
#if version >= 1.14
  #if version >= 1.17
    #require net.minecraft.server.level.EntityTracker public final net.minecraft.server.level.WorldServer world:level;
  #else
    #require net.minecraft.server.level.EntityTracker public final net.minecraft.server.level.WorldServer world;
  #endif

    public (org.bukkit.World) WorldServer getWorld() {
        return instance#world;
    }
    public void setWorld((org.bukkit.World) WorldServer world) {
        instance#world = world;
    }

  #if version >= 1.17
    #require net.minecraft.server.level.EntityTracker public final it.unimi.dsi.fastutil.ints.Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities:entityMap;
  #else
    #require net.minecraft.server.level.EntityTracker public final it.unimi.dsi.fastutil.ints.Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities;
  #endif

    public (Collection<com.bergerkiller.generated.net.minecraft.server.level.EntityTrackerEntryHandle>) Collection<EntityTrackerEntry> getEntries() {
        return instance#trackedEntities.values();
    }

    public (EntityTrackerEntryHandle) EntityTrackerEntry getEntry(int entityId) {
        return (PlayerChunkMap$EntityTracker) instance#trackedEntities.get(entityId);
    }

    public (EntityTrackerEntryHandle) EntityTrackerEntry putEntry(int entityId, (EntityTrackerEntryHandle) EntityTrackerEntry entry) {
        it.unimi.dsi.fastutil.ints.Int2ObjectMap trackedEntities = instance#trackedEntities;
        if (entry == null) {
            return (PlayerChunkMap$EntityTracker) trackedEntities.remove(entityId);
        } else if (trackedEntities.getClass() == it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap.class) {
            return (PlayerChunkMap$EntityTracker) trackedEntities.put(entityId, entry);
        } else {
            // ProtocolSupport (or other hooks): bypass put() callback handler to guarantee it is put
            trackedEntities.remove(entityId);
            return (PlayerChunkMap$EntityTracker) trackedEntities.putIfAbsent(entityId, entry);
        }
    }

  #if exists net.minecraft.server.level.PlayerChunkMap.ProtectedVisibleChunksMap
    public optional void setVisibleChunksToUpdatingChunks:###();
  #elseif exists net.minecraft.server.level.PlayerChunkMap public volatile it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<net.minecraft.server.level.PlayerChunk> visibleChunksClone;
    public optional void setVisibleChunksToUpdatingChunks() {
        // Used on newer builds of paperspigot
        synchronized (instance.visibleChunks) {
            instance.visibleChunksClone = null;
        }
    }
  #elseif exists net.minecraft.server.level.PlayerChunkMap public volatile it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<net.minecraft.server.level.PlayerChunk> visibleChunks;
    public optional void setVisibleChunksToUpdatingChunks() {
        // Note: field is private on some server types (Mohist)
        #require net.minecraft.server.level.PlayerChunkMap private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<net.minecraft.server.level.PlayerChunk> updatingChunks;
        instance.visibleChunks = instance#updatingChunks;
    }
  #else
    public optional void setVisibleChunksToUpdatingChunks:###();
  #endif

#else
  #if exists net.minecraft.server.level.EntityTracker private final List<EntityTrackerEntry> c;
    // CopyOnWrite List on WindSpigot / others? on MC 1.8.8
    #require net.minecraft.server.level.EntityTracker private final List<EntityTrackerEntry> trackedEntitiesSet:c;
  #elseif exists net.minecraft.server.level.EntityTracker public me.rastrian.dev.utils.IndexedLinkedHashSet<net.minecraft.server.level.EntityTrackerEntry> c;
    // Also seems to be used on (older?) WindSpigot/NachoSpigot 1.8.8 crap
    #require net.minecraft.server.level.EntityTracker public me.rastrian.dev.utils.IndexedLinkedHashSet<net.minecraft.server.level.EntityTrackerEntry> trackedEntitiesSet:c;
  #elseif exists net.minecraft.server.level.EntityTracker private final dev.azurite.spigot.optimizations.ObjectMapList<net.minecraft.server.level.EntityTrackerEntry> trackedEntitiesSet:c;
    // Azurite
    #require net.minecraft.server.level.EntityTracker private final dev.azurite.spigot.optimizations.ObjectMapList<net.minecraft.server.level.EntityTrackerEntry> trackedEntitiesSet:c;
  #else
    #require net.minecraft.server.level.EntityTracker private final Set<EntityTrackerEntry> trackedEntitiesSet:c;
  #endif

    public (org.bukkit.World) WorldServer getWorld() {
  #if exists net.minecraft.server.level.EntityTracker private final net.minecraft.server.level.WorldServer world;
        #require net.minecraft.server.level.EntityTracker private final net.minecraft.server.level.WorldServer world;
        return instance#world;
  #else
        // WindSpigot removed this field for whatever reason. Must iterate the server worlds to figure this one out.
        java.util.Iterator worlds_iter = net.minecraft.server.MinecraftServer.getServer().worlds.iterator();
        while (worlds_iter.hasNext()) {
            net.minecraft.server.level.WorldServer world = (net.minecraft.server.level.WorldServer) worlds_iter.next();
            if (world.tracker == instance) {
                return world;
            }
        }
        throw new UnsupportedOperationException("Entity tracker not used by any World for some reason");
  #endif
    }

    public void setWorld((org.bukkit.World) WorldServer world) {
  #if exists net.minecraft.server.level.EntityTracker private final net.minecraft.server.level.WorldServer world;
        #require net.minecraft.server.level.EntityTracker private final net.minecraft.server.level.WorldServer world;
        instance#world = world;
  #endif
    }

    public (Collection<EntityTrackerEntryHandle>) Collection<EntityTrackerEntry> getEntries() {
        return (java.util.Collection) instance#trackedEntitiesSet;
    }

    public (EntityTrackerEntryHandle) EntityTrackerEntry getEntry(int entityId) {
        return (EntityTrackerEntry) instance.trackedEntities.get(entityId);
    }

    public (EntityTrackerEntryHandle) EntityTrackerEntry putEntry(int entityId, (EntityTrackerEntryHandle) EntityTrackerEntry entry) {
        java.util.Collection allEntries = (java.util.Collection) instance#trackedEntitiesSet;

        // Remove any previous entry
        EntityTrackerEntry previous = (EntityTrackerEntry) instance.trackedEntities.d(entityId);
        if (previous != null) {
            allEntries.remove(previous);
        }

        // Add the new entry
        if (entry != null) {
            instance.trackedEntities.a(entityId, (Object) entry);
            allEntries.add(entry);
        }

        return previous;
    }

    public optional void setVisibleChunksToUpdatingChunks:###();

#endif

#if version >= 1.20.2
    int trackingDistance:serverViewDistance;
#elseif version >= 1.17
    int trackingDistance:viewDistance;
#elseif version >= 1.14.1
    private int trackingDistance:viewDistance;
#elseif version >= 1.14
    private int trackingDistance:A;
#elseif fieldexists net.minecraft.server.level.EntityTracker private int trackingDistance
    private int trackingDistance;
#else
    private int trackingDistance:e;
#endif

#if version >= 1.21.9
    protected void sendPacketToEntity:sendToTrackingPlayersAndSelf((org.bukkit.entity.Entity) Entity entity, (CommonPacket) Packet<?> packet);
#elseif version >= 1.18
    protected void sendPacketToEntity:broadcastAndSend((org.bukkit.entity.Entity) Entity entity, (CommonPacket) Packet<?> packet);
#elseif version >= 1.14
    protected void sendPacketToEntity:broadcastIncludingSelf((org.bukkit.entity.Entity) Entity entity, (CommonPacket) Packet<?> packet);
#elseif version >= 1.9
    public void sendPacketToEntity((org.bukkit.entity.Entity) Entity entity, (CommonPacket) Packet<?> packet);
#else
    public void sendPacketToEntity((org.bukkit.entity.Entity) Entity entity, (CommonPacket) Packet packet);
#endif

#if version >= 1.14
  #if exists net.minecraft.server.level.PlayerChunkMap public void addEntity(net.minecraft.world.entity.Entity);
    public void trackEntity:addEntity((org.bukkit.entity.Entity) Entity entity);
  #else
    protected void trackEntity:addEntity((org.bukkit.entity.Entity) Entity entity);
  #endif

    protected void untrackEntity:removeEntity((org.bukkit.entity.Entity) Entity entity);
#else
    public void trackEntity:track((org.bukkit.entity.Entity) Entity entity);
    public void untrackEntity((org.bukkit.entity.Entity) Entity entity);
#endif

    // Also sends chunk data on 1.14 since the two got merged into one
    // On 1.13.2 and before, it just sends all packets required for spawning the entities in the chunk
    // public void spawnEntities:a((org.bukkit.entity.Player) EntityPlayer entityplayer, (org.bukkit.Chunk) Chunk chunk);
}

// Same as EntityTrackerEntry on 1.13.2 and before
// PlayerChunkMap.EntityTracker on 1.14 and later
class EntityTrackerEntry {
#if version >= 1.14
  #if version >= 1.17
    private final int trackingDistance:range;
  #else
    private final int trackingDistance;
  #endif

    public int getPlayerViewDistance() {
  #if exists net.minecraft.server.level.EntityTrackerEntry final net.minecraft.server.level.PlayerChunkMap this$0;
        #require net.minecraft.server.level.EntityTrackerEntry final net.minecraft.server.level.PlayerChunkMap entityTracker:this$0;
  #else
        #require net.minecraft.server.level.EntityTrackerEntry final net.minecraft.server.level.PlayerChunkMap entityTracker:a;
  #endif
  #if version >= 1.20.2
        #require net.minecraft.server.level.PlayerChunkMap private int playerViewDistance:serverViewDistance;
  #elseif version >= 1.14.1
        #require net.minecraft.server.level.PlayerChunkMap private int playerViewDistance:viewDistance;
  #else
        #require net.minecraft.server.level.PlayerChunkMap private int playerViewDistance:A;
  #endif
        net.minecraft.server.level.PlayerChunkMap playerChunkMap = instance#entityTracker;
        return playerChunkMap#playerViewDistance;
    }
#elseif version >= 1.9
    private final int trackingDistance:e;

    public int getPlayerViewDistance() {
        #require net.minecraft.server.level.EntityTrackerEntry private int playerViewDistance:f;
        return instance#playerViewDistance;
    }
#else
    public int trackingDistance:b;

    public int getPlayerViewDistance() {
        return instance.b;
    }
#endif

#if version >= 1.17
    // Stores a set of ServerPlayerConnection objects, requires <> EntityPlayer conversion

    public (java.util.Collection<org.bukkit.entity.Player>) Set<ServerPlayerConnection> getViewers() {
        return instance.seenBy;
    }

    public java.util.Collection<Object> getRawViewers() {
        return instance.seenBy;
    }

    public static org.bukkit.entity.Player convertRawViewer(Object viewer) {
  #if version >= 1.18
        return (org.bukkit.entity.Player) ((ServerPlayerConnection) viewer).getPlayer().getBukkitEntity();
  #else
        return (org.bukkit.entity.Player) ((ServerPlayerConnection) viewer).d().getBukkitEntity();
  #endif
    }

    public void clearViewers() {
        instance.seenBy.clear();
    }

    public boolean addViewerToSet((org.bukkit.entity.Player) ServerPlayerConnection viewer) {
        return instance.seenBy.add(viewer);
    }

    public boolean removeViewerFromSet((org.bukkit.entity.Player) ServerPlayerConnection viewer) {
        return instance.seenBy.remove(viewer);
    }

#elseif fieldexists net.minecraft.server.level.EntityTrackerEntry public Map<EntityPlayer, Boolean> trackedPlayerMap;
    // Stored as a Map of players (Paperspigot)

    public (java.util.Collection<org.bukkit.entity.Player>) Set<EntityPlayer> getViewers() {
        return instance.trackedPlayerMap.keySet();
    }

    public java.util.Collection<Object> getRawViewers() {
        return instance.trackedPlayerMap.keySet();
    }

    public static org.bukkit.entity.Player convertRawViewer(Object viewer) {
        return (org.bukkit.entity.Player) ((EntityPlayer) viewer).getBukkitEntity();
    }

    public void clearViewers() {
        instance.trackedPlayerMap.clear();
    }

    public boolean addViewerToSet((org.bukkit.entity.Player) EntityPlayer viewer) {
        if (instance.trackedPlayerMap.containsKey(viewer)) {
            return false;
        } else {
            instance.trackedPlayerMap.put(viewer, Boolean.TRUE);
            return true;
        }
    }

    public boolean removeViewerFromSet((org.bukkit.entity.Player) EntityPlayer viewer) {
        return instance.trackedPlayerMap.remove(viewer) != null;
    }
#else
    // Stored as a Set of players

    public (java.util.Collection<org.bukkit.entity.Player>) Set<EntityPlayer> getViewers() {
        return instance.trackedPlayers;
    }

    public java.util.Collection<Object> getRawViewers() {
        return instance.trackedPlayers;
    }

    public static org.bukkit.entity.Player convertRawViewer(Object viewer) {
        return (org.bukkit.entity.Player) ((EntityPlayer) viewer).getBukkitEntity();
    }

    public void clearViewers() {
        instance.trackedPlayers.clear();
    }

    public boolean addViewerToSet((org.bukkit.entity.Player) EntityPlayer viewer) {
        return instance.trackedPlayers.add(viewer);
    }

    public boolean removeViewerFromSet((org.bukkit.entity.Player) EntityPlayer viewer) {
        return instance.trackedPlayers.remove(viewer);
    }
#endif

#if version >= 1.14
  #if version >= 1.17
    #require net.minecraft.server.level.PlayerChunkMap$EntityTracker private final net.minecraft.world.entity.Entity entity;
    #require net.minecraft.server.level.PlayerChunkMap$EntityTracker private final EntityTrackerEntryState trackerEntry:serverEntity;
    #require net.minecraft.server.level.EntityTrackerEntryState private final net.minecraft.world.entity.Entity trackerEntryEntity:entity;
  #else
    #require net.minecraft.server.level.PlayerChunkMap$EntityTracker private final net.minecraft.world.entity.Entity entity:tracker;
    #require net.minecraft.server.level.PlayerChunkMap$EntityTracker private final EntityTrackerEntryState trackerEntry;
    #require net.minecraft.server.level.EntityTrackerEntryState private final net.minecraft.world.entity.Entity trackerEntryEntity:tracker;
  #endif

    public (EntityTrackerEntryStateHandle) EntityTrackerEntryState getState() {
        return instance#trackerEntry;
    }

    public optional void setState((EntityTrackerEntryStateHandle) EntityTrackerEntryState state) {
        instance#trackerEntry = state;
    }

    public (EntityHandle) Entity getEntity() {
        return instance#entity;
    }

    public void setEntity((EntityHandle) Entity entity) {
        instance#entity = entity;
        EntityTrackerEntryState entry = instance#trackerEntry;
        entry#trackerEntryEntity = entity;
    }

    public void updateViewers() {
        Entity entity = instance#entity;
  #if exists net.minecraft.server.level.PlayerChunkMap$EntityTracker final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.EntityPlayer> newTrackerCandidates);
        // Paper after some optimizations were made to tracking
        #require net.minecraft.server.level.PlayerChunkMap$EntityTracker final void paperUpdatePlayers:updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.EntityPlayer> newTrackerCandidates);
        #require Entity com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.EntityPlayer> getPlayersInTrackRange();
        com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = entity#getPlayersInTrackRange();
        instance#paperUpdatePlayers(players);
  #else
        // Spigot / old paper
    #if version >= 1.20
        WorldServer world = (WorldServer) entity.level();
    #elseif version >= 1.17
        WorldServer world = (WorldServer) entity.level;
    #else
        WorldServer world = (WorldServer) entity.world;
    #endif
    #if version >= 1.18
        java.util.List playersOnWorld = world.players();
        instance.updatePlayers(playersOnWorld);
    #else
        java.util.List playersOnWorld = world.getPlayers();
        instance.track(playersOnWorld);
    #endif
  #endif
    }

  #if version >= 1.18
    public void removeViewer:removePlayer((org.bukkit.entity.Player) EntityPlayer player);
  #else
    public void removeViewer:clear((org.bukkit.entity.Player) EntityPlayer player);
  #endif
#else
    #require net.minecraft.server.level.EntityTrackerEntry private final net.minecraft.world.entity.Entity entity:tracker;

    public (EntityTrackerEntryStateHandle) EntityTrackerEntryState getState() {
        return instance;
    }

    public optional void setState:###((EntityTrackerEntryStateHandle) EntityTrackerEntryState state);

    public (EntityHandle) Entity getEntity() {
        return instance#entity;
    }

    public void setEntity((EntityHandle) Entity entity) {
        instance#entity = entity;
    }

    public void updateViewers() {
        Entity entity = instance#entity;

        // Spigot / old paper
        WorldServer world = (WorldServer) entity.world;
        java.util.List playersOnWorld = world.players;
  #if exists net.minecraft.server.level.EntityTrackerEntry public void scanPlayers(java.util.List<net.minecraft.server.EntityPlayer> players);
        instance.scanPlayers(playersOnWorld);
  #else
        for (int i = 0; i < playersOnWorld.size(); i++) {
            instance.updatePlayer((EntityPlayer) playersOnWorld.get(i));
        }
  #endif
    }

  #if exists net.minecraft.server.level.EntityTrackerEntry public void clear(net.minecraft.server.EntityPlayer a); && !exists net.minecraft.server.level.EntityTrackerEntry public void a(net.minecraft.server.EntityPlayer a);
    // This is used on Azurite / 1.8.x forks. Both methods behave pretty much the same.
    public void removeViewer:clear((org.bukkit.entity.Player) EntityPlayer player);
  #else
    public void removeViewer:a((org.bukkit.entity.Player) EntityPlayer player);
  #endif
#endif

    public void updatePlayer((org.bukkit.entity.Player) EntityPlayer player);

#if version >= 1.18
    public void hideForAll:broadcastRemoved();
#else
    public void hideForAll:a();
#endif

#if version >= 1.21.9
    public void broadcastRawPacket:sendToTrackingPlayersAndSelf((Object) Packet packet);
#elseif version >= 1.9
    public void broadcastRawPacket:broadcast((Object) Packet packet);
#else
    public void broadcastRawPacket:broadcast((Object) Packet<?> packet);
#endif

    // These got moved to the State class, but proxy them here to keep depending plugins compatible
    <code>
    @Deprecated
    public static final boolean hasProtocolRotationChanged(float angle1, float angle2) {
        return EntityTrackerEntryStateHandle.hasProtocolRotationChanged(angle1, angle2);
    }

    @Deprecated
    public static final int getProtocolRotation(float angle) {
        return EntityTrackerEntryStateHandle.getProtocolRotation(angle);
    }

    @Deprecated
    public static final float getRotationFromProtocol(int protocol) {
        return EntityTrackerEntryStateHandle.getRotationFromProtocol(protocol);
    }

    @Deprecated
    public void setTimeSinceLocationSync(int time) {
        getState().setTimeSinceLocationSync(time);
    }
    </code>
}

// Alias for EntityTrackerEntry, but because we're using PlayerChunkMap$EntityTracker as
// main handle on 1.14 it is renamed to differentiate the two.
class EntityTrackerEntryState {
#if version >= 1.17
    private final (EntityHandle) Entity entity;
    private final int updateInterval;
    private final boolean isMobile:trackDelta;
#elseif version >= 1.14
    private final (EntityHandle) Entity entity:tracker;
    private final int updateInterval:d;
    private final boolean isMobile:e;
#elseif version >= 1.9
    private final readonly (EntityHandle) Entity entity:tracker;
    private final int updateInterval:g;
#else
    public final readonly (EntityHandle) Entity entity:tracker;
    public int updateInterval:c;
#endif

#if version >= 1.21.9
    private optional final java.util.function.Consumer broadcastMethod:###;
#elseif version >= 1.17
    private optional final java.util.function.Consumer broadcastMethod:broadcast;
#elseif version >= 1.14
    private optional final java.util.function.Consumer broadcastMethod:f;
#else
    private optional final java.util.function.Consumer broadcastMethod:###;
#endif

#if version >= 1.17
    public int tickCounter:tickCount;
#elseif version >= 1.15
    public int tickCounter;
#elseif version >= 1.14
    public int tickCounter:n;
#elseif version >= 1.9
    public int tickCounter:a;
#elseif nachospigot
    public int tickCounter:tickCount;
#else
    public int tickCounter:m;
#endif

#if version >= 1.19
    #require EntityTrackerEntryState private final VecDeltaCodec positionCodec;
    #require VecDeltaCodec private Vec3D vecDelta_base:base;
    #require EntityTrackerEntryState public Vec3D getSyncPos() {
        VecDeltaCodec codec = instance#positionCodec;
        return codec#vecDelta_base;
    }
    public org.bukkit.util.Vector getLoc() {
        Vec3D pos = instance#getSyncPos();
        return new org.bukkit.util.Vector(pos.x, pos.y, pos.z);
    }
    public double getLocX() {
        Vec3D pos = instance#getSyncPos();
        return pos.x;
    }
    public double getLocY() {
        Vec3D pos = instance#getSyncPos();
        return pos.y;
    }
    public double getLocZ() {
        Vec3D pos = instance#getSyncPos();
        return pos.z;
    }
    public void setLoc(double x, double y, double z) {
        VecDeltaCodec codec = instance#positionCodec;
        codec.setBase(new Vec3D(x, y, z));
    }
    public void setLocX(double x) {
        VecDeltaCodec codec = instance#positionCodec;
        Vec3D pos = codec#vecDelta_base;
        codec.setBase(new Vec3D(x, pos.y, pos.z));
    }
    public void setLocY(double y) {
        VecDeltaCodec codec = instance#positionCodec;
        Vec3D pos = codec#vecDelta_base;
        codec.setBase(new Vec3D(pos.x, y, pos.z));
    }
    public void setLocZ(double z) {
        VecDeltaCodec codec = instance#positionCodec;
        Vec3D pos = codec#vecDelta_base;
        codec.setBase(new Vec3D(pos.x, pos.y, z));
    }
#elseif version >= 1.9
  #if version >= 1.17
    #require net.minecraft.server.level.EntityTrackerEntryState private long xLoc:xp;
    #require net.minecraft.server.level.EntityTrackerEntryState private long yLoc:yp;
    #require net.minecraft.server.level.EntityTrackerEntryState private long zLoc:zp;
  #else
    #require net.minecraft.server.level.EntityTrackerEntryState private long xLoc;
    #require net.minecraft.server.level.EntityTrackerEntryState private long yLoc;
    #require net.minecraft.server.level.EntityTrackerEntryState private long zLoc;
  #endif
    public org.bukkit.util.Vector getLoc() {
        double x = instance#xLoc / 4096.0;
        double y = instance#yLoc / 4096.0;
        double z = instance#zLoc / 4096.0;
        return new org.bukkit.util.Vector(x, y, z);
    }
    public double getLocX() {
        return instance#xLoc / 4096.0;
    }
    public double getLocY() {
        return instance#yLoc / 4096.0;
    }
    public double getLocZ() {
        return instance#zLoc / 4096.0;
    }
    public void setLoc(double x, double y, double z) {
        long xEnc = com.bergerkiller.bukkit.common.utils.MathUtil.longFloor(x * 4096.0);
        instance#xLoc = xEnc;
        long yEnc = com.bergerkiller.bukkit.common.utils.MathUtil.longFloor(y * 4096.0);
        instance#yLoc = yEnc;
        long zEnc = com.bergerkiller.bukkit.common.utils.MathUtil.longFloor(z * 4096.0);
        instance#zLoc = zEnc;
    }
    public void setLocX(double x) {
        long xEnc = com.bergerkiller.bukkit.common.utils.MathUtil.longFloor(x * 4096.0);
        instance#xLoc = xEnc;
    }
    public void setLocY(double y) {
        long yEnc = com.bergerkiller.bukkit.common.utils.MathUtil.longFloor(y * 4096.0);
        instance#yLoc = yEnc;
    }
    public void setLocZ(double z) {
        long zEnc = com.bergerkiller.bukkit.common.utils.MathUtil.longFloor(z * 4096.0);
        instance#zLoc = zEnc;
    }
#else
    public org.bukkit.util.Vector getLoc() {
        double x = instance.xLoc / 32.0;
        double y = instance.yLoc / 32.0;
        double z = instance.zLoc / 32.0;
        return new org.bukkit.util.Vector(x, y, z);
    }
    public double getLocX() {
        return instance.xLoc / 32.0;
    }
    public double getLocY() {
        return instance.yLoc / 32.0;
    }
    public double getLocZ() {
        return instance.zLoc / 32.0;
    }
    public void setLoc(double x, double y, double z) {
        instance.xLoc = com.bergerkiller.bukkit.common.utils.MathUtil.floor(x * 32.0);
        instance.yLoc = com.bergerkiller.bukkit.common.utils.MathUtil.floor(y * 32.0);
        instance.zLoc = com.bergerkiller.bukkit.common.utils.MathUtil.floor(z * 32.0);
    }
    public void setLocX(double x) {
        instance.xLoc = com.bergerkiller.bukkit.common.utils.MathUtil.floor(x * 32.0);
    }
    public void setLocY(double y) {
        instance.yLoc = com.bergerkiller.bukkit.common.utils.MathUtil.floor(y * 32.0);
    }
    public void setLocZ(double z) {
        instance.zLoc = com.bergerkiller.bukkit.common.utils.MathUtil.floor(z * 32.0);
    }
#endif

#if version >= 1.14
    // Velocity is a Vec3D
  #if version >= 1.21
    #require net.minecraft.server.level.EntityTrackerEntryState private Vec3D vel:lastSentMovement;
  #elseif version >= 1.17
    #require net.minecraft.server.level.EntityTrackerEntryState private Vec3D vel:ap;
  #else
    #require net.minecraft.server.level.EntityTrackerEntryState private Vec3D vel:m;
  #endif

    public double getXVel() {
        Object velocity = instance#vel;
        return com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.x.getDouble(velocity);
    }

    public double getYVel() {
        Object velocity = instance#vel;
        return com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.y.getDouble(velocity);
    }

    public double getZVel() {
        Object velocity = instance#vel;
        return com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.z.getDouble(velocity);
    }

    public void setXVel(double x) {
        Object velocity = instance#vel;
        double y = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.y.getDouble(velocity);
        double z = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.z.getDouble(velocity);
        Vec3D newVel = new Vec3D(x, y, z);
        instance#vel = newVel;
    }

    public void setYVel(double y) {
        Object velocity = instance#vel;
        double x = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.x.getDouble(velocity);
        double z = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.z.getDouble(velocity);
        Vec3D newVel = new Vec3D(x, y, z);
        instance#vel = newVel;
    }

    public void setZVel(double z) {
        Object velocity = instance#vel;
        double x = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.x.getDouble(velocity);
        double y = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.y.getDouble(velocity);
        Vec3D newVel = new Vec3D(x, y, z);
        instance#vel = newVel;
    }

    public org.bukkit.util.Vector getVelocity() {
        Object velocity = instance#vel;
        double x = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.x.getDouble(velocity);
        double y = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.y.getDouble(velocity);
        double z = com.bergerkiller.generated.net.minecraft.world.phys.Vec3DHandle.T.z.getDouble(velocity);
        return new org.bukkit.util.Vector(x, y, z);
    }

    public void setVelocity(double x, double y, double z) {
        Vec3D velocity = new Vec3D(x, y, z);
        instance#vel = velocity;
    }

#else
    // Velocity is three separate x/y/z fields
  #if version >= 1.9
    #require net.minecraft.server.level.EntityTrackerEntryState private double xVel:n;
    #require net.minecraft.server.level.EntityTrackerEntryState private double yVel:o;
    #require net.minecraft.server.level.EntityTrackerEntryState private double zVel:p;
  #elseif nachospigot
    #require net.minecraft.server.level.EntityTrackerEntryState private double xVel:motionX;
    #require net.minecraft.server.level.EntityTrackerEntryState private double yVel:motionY;
    #require net.minecraft.server.level.EntityTrackerEntryState private double zVel:motionZ;
  #else
    #require net.minecraft.server.level.EntityTrackerEntryState private double xVel:j;
    #require net.minecraft.server.level.EntityTrackerEntryState private double yVel:k;
    #require net.minecraft.server.level.EntityTrackerEntryState private double zVel:l;
  #endif

    public double getXVel() {
        return instance#xVel;
    }

    public double getYVel() {
        return instance#yVel;
    }

    public double getZVel() {
        return instance#zVel;
    }

    public void setXVel(double x) {
        instance#xVel = x;
    }

    public void setYVel(double y) {
        instance#yVel = y;
    }

    public void setZVel(double z) {
        instance#zVel = z;
    }

    public org.bukkit.util.Vector getVelocity() {
        double x = instance#xVel;
        double y = instance#yVel;
        double z = instance#zVel;
        return new org.bukkit.util.Vector(x, y, z);
    }

    public void setVelocity(double x, double y, double z) {
        instance#xVel = x;
        instance#yVel = y;
        instance#zVel = z;
    }
#endif

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

    // These are needed for correct remapping of the state hook
#if version >= 1.18
    public optional void removePairing((Object) EntityPlayer viewer);
    public optional void addPairing((Object) EntityPlayer viewer);
#elseif version >= 1.14
    public optional void removePairing:a((Object) EntityPlayer viewer);
    public optional void addPairing:b((Object) EntityPlayer viewer);
#else
    public optional void removePairing:###((Object) EntityPlayer viewer);
    public optional void addPairing:###((Object) EntityPlayer viewer);
#endif

#if version >= 1.14
    // On Minecraft 1.14 this resync distance check is no longer done
    public boolean checkTrackNeeded() {
        return true;
    }
#else
    // On Minecraft 1.13.2 this tracks the last position
    // When it changes by more than 16 blocks, rescan all players
    public boolean checkTrackNeeded() {
        #require net.minecraft.server.level.EntityTrackerEntryState private final net.minecraft.world.entity.Entity entity:tracker;
  #if nachospigot
        #require net.minecraft.server.level.EntityTrackerEntryState private double prevX:posX;
        #require net.minecraft.server.level.EntityTrackerEntryState private double prevY:posY;
        #require net.minecraft.server.level.EntityTrackerEntryState private double prevZ:posZ;
  #else
        #require net.minecraft.server.level.EntityTrackerEntryState private double prevX:q;
        #require net.minecraft.server.level.EntityTrackerEntryState private double prevY:r;
        #require net.minecraft.server.level.EntityTrackerEntryState private double prevZ:s;
  #endif
        #require net.minecraft.server.level.EntityTrackerEntryState private boolean synched:isMoving;

        Entity entity = instance#entity;
        if (entity == null) {
            return true;
        }

        boolean isSynched = instance#synched;
        if (isSynched) {
            double lastSyncX = instance#prevX;
            double lastSyncY = instance#prevY;
            double lastSyncZ = instance#prevZ;
            double distance = entity.e(lastSyncX, lastSyncY, lastSyncZ);
            if (distance <= 16.0) {
                return false;
            }
        }

        // Update tracking data
        instance#prevX = entity.locX;
        instance#prevY = entity.locY;
        instance#prevZ = entity.locZ;
        instance#synched = true;
        return true;
    }
#endif

    // Moved to top on MC 1.14
#if version <= 1.13.2
    private final boolean isMobile:u;
#endif

#if version >= 1.21.2
    #require EntityTrackerEntryState private byte lastSentYRot;
    #require EntityTrackerEntryState private byte lastSentXRot;
    #require EntityTrackerEntryState private byte lastSentYHeadRot;
#elseif version >= 1.21
    #require EntityTrackerEntryState private int lastSentYRot;
    #require EntityTrackerEntryState private int lastSentXRot;
    #require EntityTrackerEntryState private int lastSentYHeadRot;
#elseif version >= 1.17
    #require EntityTrackerEntryState private int lastSentYRot:yRotp;
    #require EntityTrackerEntryState private int lastSentXRot:xRotp;
    #require EntityTrackerEntryState private int lastSentYHeadRot:yHeadRotp;
#else
    #require EntityTrackerEntryState private int lastSentYRot:yRot;
    #require EntityTrackerEntryState private int lastSentXRot:xRot;
  #if version >= 1.9
    #require EntityTrackerEntryState private int lastSentYHeadRot:headYaw;
  #elseif nachospigot
    #require EntityTrackerEntryState private int lastSentYHeadRot:lastHeadYaw;
  #else
    #require EntityTrackerEntryState private int lastSentYHeadRot:i;
  #endif
#endif

#if version >= 1.21.2
    // byte <> int
    public int getEncodedPitch() { return ((int) instance#lastSentYRot) & 0xFF; }
    public void setEncodedPitch(int encoded) { instance#lastSentYRot = (byte) (encoded & 0xFF); }
    public int getEncodedYaw() { return ((int) instance#lastSentXRot) & 0xFF; }
    public void setEncodedYaw(int encoded) { instance#lastSentXRot = (byte) (encoded & 0xFF); }
    public int getEncodedHeadYaw() { return ((int) instance#lastSentYHeadRot) & 0xFF; }
    public void setEncodedHeadYaw(int encoded) { instance#lastSentYHeadRot = (byte) (encoded & 0xFF); }
#else
    public int getEncodedPitch() { return instance#lastSentYRot; }
    public void setEncodedPitch(int encoded) { instance#lastSentYRot = encoded; }
    public int getEncodedYaw() { return instance#lastSentXRot; }
    public void setEncodedYaw(int encoded) { instance#lastSentXRot = encoded; }
    public int getEncodedHeadYaw() { return instance#lastSentYHeadRot; }
    public void setEncodedHeadYaw(int encoded) { instance#lastSentYHeadRot = encoded; }
#endif

    <code>
    // Minimal change of x/y/z position or rotation that is supported
    public static final double POSITION_STEP;
    public static final float ROTATION_STEP;
    public static final float ROTATION_STEP_INV;
    static {
        if (com.bergerkiller.bukkit.common.internal.CommonBootstrap.evaluateMCVersion(">=", "1.9")) {
            POSITION_STEP = 1.0 / 4096.0;
        } else {
            POSITION_STEP = 1.0 / 32.0;
        }
        ROTATION_STEP = 360.0f / 256.0f;
        ROTATION_STEP_INV = 256.0f / 360.0f;
    }

    public static final boolean hasProtocolRotationChanged(float angle1, float angle2) {
        if (angle1 == angle2) {
            return false;
        }

        int prot_diff = com.bergerkiller.bukkit.common.utils.MathUtil.floor((angle2-angle1)*ROTATION_STEP_INV) & 0xFF;
        if (prot_diff > 0 && prot_diff < 255) {
            return true;
        }

        int prot1 = com.bergerkiller.bukkit.common.utils.MathUtil.floor(angle1*ROTATION_STEP_INV);
        int prot2 = com.bergerkiller.bukkit.common.utils.MathUtil.floor(angle2*ROTATION_STEP_INV);
        return ((prot1 - prot2) & 0xFF) != 0;
    }

    public static final int getProtocolRotation(float angle) {
        int protAngle = com.bergerkiller.bukkit.common.utils.MathUtil.floor(angle * ROTATION_STEP_INV) & 0xFF;
        if (protAngle >= 128) {
            protAngle -= 256;
        }
        return protAngle;
    }

    public static final float getRotationFromProtocol(int protocol) {
        int protAngle = protocol & 0xFF;
        if (protAngle >= 128) {
            protAngle -= 256;
        }
        return (float) protAngle * ROTATION_STEP;
    }

    // Performs protocol translation to set the synchronized yaw rotation
    public void setYaw(float yaw) {
        setEncodedYaw(getProtocolRotation(yaw));
    }

    // Performs protocol translation to set the synchronized pitch rotation
    public void setPitch(float pitch) {
        setEncodedPitch(getProtocolRotation(pitch));
    }

    // Performs protocol translation to set the synchronized head rotation
    public void setHeadYaw(float headYaw) {
        setEncodedHeadYaw(getProtocolRotation(headYaw));
    }

    // Performs protocol translation to get the synchronized yaw rotation
    public float getYaw() {
        return getRotationFromProtocol(getEncodedYaw());
    }

    // Performs protocol translation to get the synchronized pitch rotation
    public float getPitch() {
        return getRotationFromProtocol(getEncodedPitch());
    }

    // Performs protocol translation to get the synchronized head rotation
    public float getHeadYaw() {
        return getRotationFromProtocol(getEncodedHeadYaw());
    }
    </code>

#if version >= 1.17
    private int timeSinceLocationSync:teleportDelay;
    private optional (List<org.bukkit.entity.Entity>) List<Entity> opt_passengers:lastPassengers;
    private optional (org.bukkit.entity.Entity) Entity opt_vehicle:###;
#elseif version >= 1.14
    private int timeSinceLocationSync:o;
    private optional (List<org.bukkit.entity.Entity>) List<Entity> opt_passengers:p;
    private optional (org.bukkit.entity.Entity) Entity opt_vehicle:###;
#elseif version >= 1.9
    private int timeSinceLocationSync:v;
    private optional (List<org.bukkit.entity.Entity>) List<Entity> opt_passengers:w;
    private optional (org.bukkit.entity.Entity) Entity opt_vehicle:###;
#elseif nachospigot
    private int timeSinceLocationSync:ticksSinceLastForcedTeleport;
    private optional (List<org.bukkit.entity.Entity>) List<Entity> opt_passengers:###;
    private optional (org.bukkit.entity.Entity) Entity opt_vehicle:lastRecoredRider;
#else
    private int timeSinceLocationSync:v;
    private optional (List<org.bukkit.entity.Entity>) List<Entity> opt_passengers:###;
    private optional (org.bukkit.entity.Entity) Entity opt_vehicle:w;
#endif

#if version >= 1.14
    public (CommonPacket) Packet<?> getSpawnPacket() {
  #if version >= 1.17
        #require net.minecraft.server.level.EntityTrackerEntryState private final net.minecraft.world.entity.Entity entity;
  #else
        #require net.minecraft.server.level.EntityTrackerEntryState private final net.minecraft.world.entity.Entity entity:tracker;
  #endif
        Entity entity = instance#entity;
  #select version >=
  #case 1.21:   return entity.getAddEntityPacket(instance);
  #case 1.18:   return entity.getAddEntityPacket();
  #case 1.17:   return entity.getPacket();
  #case 1.16.2: return entity.P();
  #case 1.16:   return entity.O();
  #case 1.15:   return entity.L();
  #case else:   return entity.N();
  #endselect
    }
#elseif version >= 1.9
    private (CommonPacket) Packet<?> getSpawnPacket:e();
#elseif exists net.minecraft.server.level.EntityTrackerEntryState protected Packet getSpawnPacket:c();
    // Nachospigot / Azurite
    protected (CommonPacket) Packet getSpawnPacket:c();
#else
    private (CommonPacket) Packet getSpawnPacket:c();
#endif

#if version >= 1.18
    public void onTick:sendChanges();
#elseif version >= 1.14
    public void onTick:a();
#else
    public void onTick() {
        // Call track() with an empty list of players to scan
        instance.track(java.util.Collections.emptyList());
    }
#endif

}