package net.minecraft.server.level;

import net.minecraft.core.BlockPosition;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.EntityHuman;
import net.minecraft.world.level.chunk.Chunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.ChunkCoordIntPair;
import net.minecraft.world.level.dimension.DimensionManager;
import net.minecraft.world.level.ForcedChunk;
import net.minecraft.world.level.IBlockAccess;
import net.minecraft.world.level.World;
import net.minecraft.world.phys.Vec3D;
import net.minecraft.world.ticks.TickListServer;

import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.Ticket;
import net.minecraft.server.level.TicketType;

import java.util.function.Function;
import java.util.function.LongPredicate;

import com.bergerkiller.bukkit.common.wrappers.PlayerRespawnPointNearBlock;

import com.bergerkiller.generated.net.minecraft.server.level.ChunkProviderServerHandle;
import com.bergerkiller.generated.net.minecraft.server.level.EntityPlayerHandle;
import com.bergerkiller.generated.net.minecraft.server.level.PlayerChunkMapHandle;
import com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle;
import com.bergerkiller.generated.net.minecraft.server.MinecraftServerHandle;
import com.bergerkiller.generated.net.minecraft.world.entity.EntityHandle;
import com.bergerkiller.generated.net.minecraft.world.level.ForcedChunkHandle;

class WorldServer extends World {
    public optional (ChunkProviderServerHandle) ChunkProviderServer field_chunkProviderServer:chunkProviderServer;

    // ====================== WorldServer PlayerChunkMap/ChunkProviderServer accessors ======================
#if version >= 1.18
    #require net.minecraft.server.level.WorldServer public PlayerChunkMap getPlayerChunkMap() {
        return ((ChunkProviderServer) instance.getChunkSource()).chunkMap;
    }
    public (PlayerChunkMapHandle) PlayerChunkMap getPlayerChunkMap() {
        return ((ChunkProviderServer) instance.getChunkSource()).chunkMap;
    }
#elseif version >= 1.17
    #require net.minecraft.server.level.WorldServer public PlayerChunkMap getPlayerChunkMap() {
        return ((ChunkProviderServer) instance.getChunkProvider()).chunkMap;
    }
    public (PlayerChunkMapHandle) PlayerChunkMap getPlayerChunkMap() {
        return ((ChunkProviderServer) instance.getChunkProvider()).chunkMap;
    }
#elseif version >= 1.14
    #require net.minecraft.server.level.WorldServer public PlayerChunkMap getPlayerChunkMap() {
        return ((ChunkProviderServer) instance.getChunkProvider()).playerChunkMap;
    }
    public (PlayerChunkMapHandle) PlayerChunkMap getPlayerChunkMap() {
        return ((ChunkProviderServer) instance.getChunkProvider()).playerChunkMap;
    }
#else
    #require net.minecraft.server.level.WorldServer public PlayerChunkMap getPlayerChunkMap();
    public (PlayerChunkMapHandle) PlayerChunkMap getPlayerChunkMap();
#endif
#if version >= 1.18
    #require net.minecraft.server.level.WorldServer public ChunkProviderServer getChunkProviderServer() {
        return (ChunkProviderServer) instance.getChunkSource();
    }
    public (ChunkProviderServerHandle) ChunkProviderServer getChunkProviderServer() {
        return (ChunkProviderServer) instance.getChunkSource();
    }
#elseif version >= 1.13.2
    #require net.minecraft.server.level.WorldServer public ChunkProviderServer getChunkProviderServer() {
        return (ChunkProviderServer) instance.getChunkProvider();
    }
    public (ChunkProviderServerHandle) ChunkProviderServer getChunkProviderServer() {
        return (ChunkProviderServer) instance.getChunkProvider();
    }
#elseif version >= 1.9
    // Note: Don't simplify or it breaks on Forge, which returns IChunkProvider.
    #require net.minecraft.server.level.WorldServer public ChunkProviderServer getChunkProviderServer() {
        return (ChunkProviderServer) instance.getChunkProviderServer();
    }
    public (ChunkProviderServerHandle) ChunkProviderServer getChunkProviderServer() {
        return (ChunkProviderServer) instance.getChunkProviderServer();
    }
#else
    #require net.minecraft.server.level.WorldServer public ChunkProviderServer getChunkProviderServer() {
        return instance.chunkProviderServer;
    }
    public (ChunkProviderServerHandle) ChunkProviderServer getChunkProviderServer() {
        return instance.chunkProviderServer;
    }
#endif
    // ==========================================================================================================

    public boolean isLoaded() {
#if version >= 1.18
        #require net.minecraft.world.level.storage.Convertable.ConversionSession final net.minecraft.util.SessionLock conv_lock:lock;
  #if version > 1.21.4 && paper
        net.minecraft.util.SessionLock lock = instance.levelStorageAccess#conv_lock;
  #elseif version == 1.21.4 && paper && exists net.minecraft.server.level.WorldServer public final net.minecraft.world.level.storage.Convertable.ConversionSession levelStorageAccess;
        net.minecraft.util.SessionLock lock = instance.levelStorageAccess#conv_lock;
  #else
        net.minecraft.util.SessionLock lock = instance.convertable#conv_lock;
  #endif
        return lock.isValid();
#elseif version >= 1.17
        #require net.minecraft.world.level.storage.Convertable.ConversionSession final net.minecraft.util.SessionLock conv_lock:lock;
        net.minecraft.util.SessionLock lock = instance.convertable#conv_lock;
        return lock.a();
#elseif version >= 1.16
        #require net.minecraft.world.level.storage.Convertable.ConversionSession private final net.minecraft.util.SessionLock conv_lock:lock;
        net.minecraft.util.SessionLock lock = instance.convertable#conv_lock;
        return lock.a();
#else
        // Due to a bug in CraftServer, unloading a world with save=false resulted in only a partial de-registering
        // As a result, the only reliable way to check whether the world is loaded is to check the worlds list
        // Since we already track world loading/unloading anyway, in a much more efficient IdentityHashMap,
        // we instead just let the BKCL OfflineWorld API handle this.
        // On 1.15 it's a mess, we ignore that one.
        org.bukkit.World world = instance.getWorld();
        return com.bergerkiller.bukkit.common.offline.OfflineWorld.of(world).getLoadedWorld() == world;
#endif
    }

#if version >= 1.18
    public (List<EntityPlayerHandle>) List<EntityPlayer> getPlayers:players();
#elseif version >= 1.14
    public (List<EntityPlayerHandle>) List<EntityPlayer> getPlayers();
#else
    // Note: is List<EntityHuman> on 1.13.2 and before, but only stores players, so this is safe.
    public (List<EntityPlayerHandle>) List<EntityPlayer> getPlayers() {
        return ((net.minecraft.world.level.World)instance).players;
    }
#endif

    public (org.bukkit.entity.Entity) Entity getEntityByUUID(UUID entityUUID) {
#if version >= 1.18
        return (Entity) instance.getEntities().get(entityUUID);
#elseif version >= 1.17
        return (Entity) instance.getEntities().a(entityUUID);
#else
        #require net.minecraft.server.level.WorldServer private final Map<java.util.UUID, Entity> entitiesByUUID;
        Map map = instance#entitiesByUUID;
        return (Entity) map.get((Object) entityUUID);
#endif
    }

    public com.bergerkiller.bukkit.common.wrappers.EntityTracker getEntityTracker() {
        Object handle = com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle.T.getEntityTrackerHandle.invoke(instance);

        // If hooked, make sure to take the original so that changes can be made safely
        com.bergerkiller.mountiplex.reflection.ClassInterceptor hook;
        hook = com.bergerkiller.mountiplex.reflection.ClassInterceptor.get(handle, com.bergerkiller.bukkit.common.internal.hooks.EntityTrackerHook.class);
        if (hook != null) {
            handle = ((com.bergerkiller.bukkit.common.internal.hooks.EntityTrackerHook) hook).original;
        }

        // Convert to EntityTracker
        return new com.bergerkiller.bukkit.common.wrappers.EntityTracker(handle);
    }

    public Object getEntityTrackerHandle() {
#if version >= 1.14
        return instance#getPlayerChunkMap();
#else
        return instance.tracker;
#endif
    }

    public void setEntityTrackerHandle((Object) EntityTracker entityTrackerHandle) {
#if version >= 1.14
        ChunkProviderServer cps = instance#getChunkProviderServer();
  #if version >= 1.17
        #require net.minecraft.server.level.ChunkProviderServer public final PlayerChunkMap playerChunkMap:chunkMap;
  #else
        #require net.minecraft.server.level.ChunkProviderServer public final PlayerChunkMap playerChunkMap;
  #endif
        cps#playerChunkMap = entityTrackerHandle;
#else
        instance.tracker = entityTrackerHandle;
#endif
    }

#if version >= 1.18
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> getDimensionKey:dimension();

    public static (org.bukkit.World) WorldServer getByDimensionKey((com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> key) {
        return MinecraftServer.getServer().getLevel(key);
    }
#elseif version >= 1.16
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> getDimensionKey();

    public static (org.bukkit.World) WorldServer getByDimensionKey((com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> key) {
        return MinecraftServer.getServer().getWorldServer(key);
    }
#else
    public (com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> getDimensionKey() {
        // If one of the main worlds (world, world_nether, world_the_end), then use the overworld, the_nether or the_end constants
        // Otherwise, create a new resource key using the world name
    #if version >= 1.13.1 && exists net.minecraft.server.level.WorldServer public final net.minecraft.world.level.dimension.DimensionManager dimension;
        int dimension = instance.dimension.getDimensionID();
    #elseif version >= 1.13.1
        int dimension = instance.worldProvider.getDimensionManager().getDimensionID();
    #else
        int dimension = instance.dimension;
    #endif
        // First three default main worlds
        if (dimension == 0) {
            return ResourceKey.WORLD_DIMENSION_OVERWORLD;
        } else if (dimension == -1) {
            return ResourceKey.WORLD_DIMENSION_THE_NETHER;
        } else if (dimension == 1) {
            return ResourceKey.WORLD_DIMENSION_THE_END;
        }

        // Custom world, dimension key is by world name lower-cased
        String name = instance.worldData.getName().toLowerCase(java.util.Locale.ENGLISH);
        net.minecraft.resources.MinecraftKey key = #parseMinecraftKey(name);
        return ResourceKey.create(ResourceKey.CATEGORY_WORLD_DIMENSION, key);
    }

    public static (org.bukkit.World) WorldServer getByDimensionKey((com.bergerkiller.bukkit.common.resources.ResourceKey<org.bukkit.World>) ResourceKey<World> key) {
        String name = (String) com.bergerkiller.generated.net.minecraft.resources.MinecraftKeyHandle.T.getName.invoke(key.name);
        MinecraftServer server = MinecraftServer.getServer();

  #if version >= 1.13.1
        // Uses DimensionManager, rather than dimension id's
        if (name.equals("overworld")) {
            return server.getWorldServer(DimensionManager.OVERWORLD);
        } else if (name.equals("the_nether")) {
    #if exists net.minecraft.world.level.dimension.DimensionManager public static final DimensionManager NETHER;
            return server.getWorldServer(DimensionManager.NETHER);
    #else
            return server.getWorldServer(DimensionManager.THE_NETHER);
    #endif
        } else if (name.equals("the_end")) {
            return server.getWorldServer(DimensionManager.THE_END);
        }
  #else
        // Uses dimension id's
        if (name.equals("overworld")) {
            return server.getWorldServer(0);
        } else if (name.equals("the_nether")) {
            return server.getWorldServer(-1);
        } else if (name.equals("the_end")) {
            return server.getWorldServer(1);
        }
  #endif

        // Non-main world, get by name
        return ((org.bukkit.craftbukkit.CraftWorld) server.server.getWorld(name)).getHandle();
    }
#endif

    public (org.bukkit.Chunk) Chunk getChunkIfLoaded(int cx, int cz) {
#if version >= 1.14
  #if version >= 1.18
        long key = ChunkCoordIntPair.asLong(cx, cz);
  #else
        long key = ChunkCoordIntPair.pair(cx, cz);
  #endif
        PlayerChunkMap playerChunkMap = instance#getPlayerChunkMap();

  #if exists net.minecraft.server.level.PlayerChunkMap public PlayerChunk getVisibleChunkIfPresent(long key);
        PlayerChunk chunk = (PlayerChunk) playerChunkMap.getVisibleChunkIfPresent(key);
  #elseif exists net.minecraft.server.level.PlayerChunkMap public PlayerChunk getVisibleChunk(long key);
        PlayerChunk chunk = (PlayerChunk) playerChunkMap.getVisibleChunk(key);
  #elseif version >= 1.17
        PlayerChunk chunk = (PlayerChunk) playerChunkMap.visibleChunkMap.get(key);
  #else
        PlayerChunk chunk = (PlayerChunk) playerChunkMap.visibleChunks.get(key);
  #endif

        if (chunk != null) {
  #if exists net.minecraft.server.level.PlayerChunk public net.minecraft.world.level.chunk.Chunk getFullChunkNow();
            return chunk.getFullChunkNow();
  #elseif exists net.minecraft.server.level.PlayerChunk public net.minecraft.world.level.chunk.Chunk getFullChunk();
            return chunk.getFullChunk();
  #else
            java.util.concurrent.CompletableFuture statusFuture;
    #if version >= 1.18
            statusFuture = chunk.getFutureIfPresentUnchecked(ChunkStatus.FULL);
    #elseif exists net.minecraft.server.level.PlayerChunk public java.util.concurrent.CompletableFuture getStatusFutureUnchecked(net.minecraft.world.level.chunk.status.ChunkStatus chunkstatus)
            statusFuture = chunk.getStatusFutureUnchecked(ChunkStatus.FULL);
    #else
            statusFuture = chunk.a(ChunkStatus.FULL);
    #endif
            com.mojang.datafixers.util.Either either = (com.mojang.datafixers.util.Either) statusFuture.getNow(null);
            return either == null ? null : (Chunk) either.left().orElse(null);
  #endif
        }
        return null;

        // Note: suffers server crash bug on earlier versions of Spigot
        // return instance.getChunkAt(cx, cz, ChunkStatus.FULL, false);
#elseif version >= 1.13.1
        long key = ChunkCoordIntPair.a(cx, cz);
        ChunkProviderServer cps = instance#getChunkProviderServer();
        return (Chunk) cps.chunks.get(key);

        // Note: suffers deadlock because of synchronized (this.chunkLoader) (chunks are already synchronized, anyway)
        // return cps.getChunkAt(cx, cz, false, false);
#else
        ChunkProviderServer cps = instance#getChunkProviderServer();
        return cps.getChunkIfLoaded(cx, cz);
#endif
    }

#if version >= 1.21.5
    public void setForceLoadedAsync(int x, int z, org.bukkit.plugin.Plugin plugin, boolean loaded, int radius) {
        // On Spigot the public field is in CPS
        // But on Paper they made it public in ChunkMapDistance instead...
  #if exists net.minecraft.server.level.ChunkMapDistance public net.minecraft.world.level.TicketStorage ticketStorage;
        PlayerChunkMap chunkMap = instance#getPlayerChunkMap();
        ChunkMapDistance chunkDistanceManager = chunkMap.getDistanceManager();
        net.minecraft.world.level.TicketStorage ticketStorage = chunkDistanceManager.ticketStorage;
  #else
        ChunkProviderServer cps = instance#getChunkProviderServer();
        net.minecraft.world.level.TicketStorage ticketStorage = cps.ticketStorage;
  #endif

        // Translate ticket radius to ticket level
        int ticketLevel = net.minecraft.server.level.ChunkLevel.byStatus(FullChunkStatus.FULL) - radius;

        // On Paper we use the constructor, they removed the of() function for some dumb reason
  #if exists net.minecraft.server.level.Ticket public static Ticket of(TicketType tickettype, int i, Object key);
        Ticket ticket = Ticket.of(TicketType.PLUGIN_TICKET, ticketLevel, (Object) plugin);
  #else
        Ticket ticket = new Ticket(TicketType.PLUGIN_TICKET, ticketLevel, (Object) plugin);
  #endif

        ChunkCoordIntPair chunkCoord = new ChunkCoordIntPair(x, z);

        if (loaded) {
            ticketStorage.addTicket(ticket, chunkCoord);
        } else {
            ticketStorage.removeTicket(ticket, chunkCoord);
        }
    }
#elseif exists net.minecraft.server.level.TicketType public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET;
    public void setForceLoadedAsync(int x, int z, org.bukkit.plugin.Plugin plugin, boolean loaded, int radius) {
        PlayerChunkMap chunkMap = instance#getPlayerChunkMap();
        ChunkMapDistance chunkDistanceManager;
  #if version >= 1.18
        chunkDistanceManager = chunkMap.getDistanceManager();
  #elseif version >= 1.17
        chunkDistanceManager = chunkMap.distanceManager;
  #elseif exists net.minecraft.server.level.PlayerChunkMap private net.minecraft.server.level.PlayerChunkMap.a chunkDistanceManager;
        chunkDistanceManager = chunkMap.chunkDistanceManager;
  #else
        chunkDistanceManager = chunkMap.u;
  #endif

        ChunkCoordIntPair chunkCoord = new ChunkCoordIntPair(x, z);
  #if version >= 1.21.5
          if (loaded) {
              chunkDistanceManager.addRegionTicket(TicketType.PLUGIN_TICKET, chunkCoord, radius, plugin);
          } else {
              chunkDistanceManager.removeRegionTicket(TicketType.PLUGIN_TICKET, chunkCoord, radius, plugin);
          }
  #elseif version >= 1.18
        if (loaded) {
            chunkDistanceManager.addRegionTicket(TicketType.PLUGIN_TICKET, chunkCoord, radius, plugin);
        } else {
            chunkDistanceManager.removeRegionTicket(TicketType.PLUGIN_TICKET, chunkCoord, radius, plugin);
        }
  #else
        int level = 33 - radius;
        if (loaded) {
            chunkDistanceManager.addTicketAtLevel(TicketType.PLUGIN_TICKET, chunkCoord, level, plugin);
        } else {
            chunkDistanceManager.removeTicketAtLevel(TicketType.PLUGIN_TICKET, chunkCoord, level, plugin);
        }
  #endif
    }
#elseif version >= 1.13.1
    public void setForceLoadedAsync(int x, int z, org.bukkit.plugin.Plugin plugin, boolean loaded, int radius) {
        com.bergerkiller.mountiplex.reflection.declarations.Template.Method getForcedChunkMethod;
        getForcedChunkMethod = (com.bergerkiller.mountiplex.reflection.declarations.Template.Method) com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle.T.getForcedChunk.raw;
        ForcedChunk forcedchunk = (ForcedChunk) getForcedChunkMethod.invoke(instance);

  #if version >= 1.14
        long key = ChunkCoordIntPair.pair(x, z);
  #else
        long key = ChunkCoordIntPair.a(x, z);
  #endif

        boolean changed;
        if (loaded) {
            changed = forcedchunk.a().add(key);
        } else {
            changed = forcedchunk.a().remove(key);
        }
        forcedchunk.a(changed);

  #if version >= 1.14
        if (changed) {
            ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(x, z);
            instance.getChunkProvider().a(chunkcoordintpair, loaded);
        }
  #endif
    }
#else
    public void setForceLoadedAsync(int x, int z, org.bukkit.plugin.Plugin plugin, boolean loaded, int radius) {
        throw new UnsupportedOperationException("Not supported on this version of Minecraft");
    }
#endif

#if version >= 1.20.2
    public optional (ForcedChunkHandle) ForcedChunk getForcedChunk() {
        return (ForcedChunk) instance.getDataStorage().get(
            ForcedChunk.factory(),
            "chunks"
        );
    }
#elseif version >= 1.18
    public optional (ForcedChunkHandle) ForcedChunk getForcedChunk() {
        return (ForcedChunk) instance.getDataStorage().get(
            (java.util.function.Function) com.bergerkiller.bukkit.common.internal.logic.ForcedChunkSupplier.INSTANCE,
            "chunks"
        );
    }
#elseif version >= 1.17
    public optional (ForcedChunkHandle) ForcedChunk getForcedChunk() {
        return (ForcedChunk) instance.getWorldPersistentData().a(
            (java.util.function.Function) com.bergerkiller.bukkit.common.internal.logic.ForcedChunkSupplier.INSTANCE,
            "chunks"
        );
    }
#elseif version >= 1.14
    public optional (ForcedChunkHandle) ForcedChunk getForcedChunk() {
        return (ForcedChunk) instance.getWorldPersistentData().a(
            (java.util.function.Supplier) com.bergerkiller.bukkit.common.internal.logic.ForcedChunkSupplier.INSTANCE,
            "chunks"
        );
    }
#elseif version >= 1.13.1
    public optional (ForcedChunkHandle) ForcedChunk getForcedChunk() {
        ForcedChunk forcedchunk = (ForcedChunk) instance.a(
            instance.worldProvider.getDimensionManager(),
            (java.util.function.Function) com.bergerkiller.bukkit.common.internal.logic.ForcedChunkSupplier.INSTANCE,
            "chunks"
        );
        if (forcedchunk == null) {
            forcedchunk = new ForcedChunk("chunks");
            instance.a(instance.worldProvider.getDimensionManager(), "chunks", forcedchunk);
        }
        return forcedchunk;
    }
#else
    public optional (ForcedChunkHandle) ForcedChunk getForcedChunk:###();
#endif

#if version >= 1.18
    public (Iterable<org.bukkit.entity.Entity>) Iterable<Entity> getEntities:getAllEntities();
#elseif version >= 1.17
    public (Iterable<org.bukkit.entity.Entity>) Iterable<Entity> getEntities() {
        return instance.getEntities().a();
    }
#elseif version >= 1.14
    public (Iterable<org.bukkit.entity.Entity>) Iterable<Entity> getEntities() {
        return instance.entitiesById.values();
    }
#elseif fieldexists net.minecraft.world.level.World public final com.destroystokyo.paper.PaperWorldEntityList entityList
    public (Iterable<org.bukkit.entity.Entity>) com.destroystokyo.paper.PaperWorldEntityList getEntities() {
        return ((net.minecraft.world.level.World)instance).entityList;
    }
#else
    public (Iterable<org.bukkit.entity.Entity>) Iterable<Entity> getEntities() {
        return ((net.minecraft.world.level.World)instance).entityList;
    }
#endif

#if version >= 1.17
    public void removeEntity((EntityHandle) Entity entity) {
        entity.setRemoved(net.minecraft.world.entity.Entity$RemovalReason.DISCARDED);
    }
#else
    public void removeEntity((EntityHandle) Entity entity);
#endif

    // Removes an entity from the World without firing any events
    // Packets for removal ARE sent to viewers of the entity, but entity death
    // isn't fired, for one.
#if version >= 1.17
    public void removeEntityWithoutDeath((EntityHandle) Entity entity) {
        entity.setRemoved(net.minecraft.world.entity.Entity$RemovalReason.CHANGED_DIMENSION);
    }
#elseif version >= 1.14
    public void removeEntityWithoutDeath:removeEntity((EntityHandle) Entity entity);
#else
    public void removeEntityWithoutDeath((EntityHandle) Entity entity) {
        org.spigotmc.AsyncCatcher.catchOp( "entity remove"); // Spigot check

        // Cannot be done during entity tick - protect!
        // Code doesn't work for some servers like Mohist that removed this field
  #if exists net.minecraft.world.level.World private boolean guardEntityList;
        #require net.minecraft.world.level.World private boolean guardEntityList;
        boolean isTickingEntities = instance#guardEntityList;
        if (isTickingEntities) {
            throw new IllegalStateException("Can not remove entity silently while ticking entities");
        }
  #endif

        // Do not execute these two!
        //entity.b(false);
        //entity.die();

        // Remove from chunk
  #if fieldexists net.minecraft.world.entity.Entity public int chunkX
        if (entity.inChunk)
            instance.getChunkAt(entity.chunkX, entity.chunkZ).b(entity);
  #elseif version >= 1.13
        if (entity.inChunk)
            instance.getChunkAt(entity.ae, entity.ag).b(entity);
  #elseif version >= 1.11
        if (entity.aa)
            instance.getChunkAt(entity.ab, entity.ad).b(entity);
  #elseif version >= 1.10.2
        if (entity.ab)
            instance.getChunkAt(entity.ac, entity.ae).b(entity);
  #elseif version >= 1.9
        if (entity.aa)
            instance.getChunkAt(entity.ab, entity.ad).b(entity);
  #else
        if (entity.ad)
            instance.getChunkAt(entity.ae, entity.ag).b(entity);
  #endif

        // Remove from entity tick list
        #require net.minecraft.world.level.World private int tickPosition;
        int index = instance.entityList.indexOf(entity);
        if (index != -1) {
            int tickPosition = instance#tickPosition;
            if (index <= tickPosition) {
                instance#tickPosition = (tickPosition-1);
            }
            instance.entityList.remove(index);
        }

        // Notify entity removal - sends destroy packets (network sync)
  #if version >= 1.9
        #require net.minecraft.world.level.World protected void notifyEntityRemoved:c(Entity entity);
  #else
        #require net.minecraft.world.level.World protected void notifyEntityRemoved:b(Entity entity);
  #endif
        instance#notifyEntityRemoved(entity);
    }
#endif

#if version >= 1.18
    public boolean addEntity:addFreshEntity((EntityHandle) Entity entity);
    public (MinecraftServerHandle) MinecraftServer getMinecraftServer:getServer();
#elseif forge_nms_obfuscated
    public boolean addEntity:a((EntityHandle) Entity entity);
    public (MinecraftServerHandle) MinecraftServer getMinecraftServer:u();
#else
    public boolean addEntity((EntityHandle) Entity entity);
    public (MinecraftServerHandle) MinecraftServer getMinecraftServer();
#endif

#if version >= 1.18
    public void saveLevel() {
        #require net.minecraft.server.level.PlayerChunkMap protected void saveChunkMap:saveAllChunks(boolean flag);
        PlayerChunkMap chunkMap = instance#getPlayerChunkMap();
        chunkMap#saveChunkMap(true);
    }
#elseif version >= 1.14
    public void saveLevel() {
        #require net.minecraft.server.level.PlayerChunkMap protected void saveChunkMap:save(boolean flag);
        PlayerChunkMap chunkMap = instance#getPlayerChunkMap();
        chunkMap#saveChunkMap(true);
    }
#elseif version >= 1.13
    public void saveLevel() {
        instance.getDataManager().a();
    }
#else
    public void saveLevel();
#endif

    <code>
    @Deprecated
    public org.bukkit.Location findSafeSpawn(PlayerRespawnPointNearBlock respawnPoint, boolean alsoWhenDestroyed, boolean isDeathRespawn) {
        return findSafeSpawn(respawnPoint.withForced(alsoWhenDestroyed), isDeathRespawn);
    }
    </code>

    public org.bukkit.Location findSafeSpawn((PlayerRespawnPointNearBlock) net.minecraft.server.level.EntityPlayer.RespawnConfig respawnPoint, boolean isDeathRespawn) {
        // If isDeathRespawn is true, removes one use tick from a bed respawn anchor, if used
        boolean ignoreRespawnAnchorUses = !isDeathRespawn;

        // Extract Dimension
#if version >= 1.21.9
        net.minecraft.resources.ResourceKey dimension = respawnPoint.respawnData().dimension();
#else
        net.minecraft.resources.ResourceKey dimension = (net.minecraft.resources.ResourceKey) respawnPoint.dimension();
#endif

        // We will need the Bukkit World from the dimension regardless
        // If this lookup fails, fail right away as we cannot proceed
        WorldServer nmsWorld = (WorldServer) com.bergerkiller.generated.net.minecraft.server.level.WorldServerHandle.T.getByDimensionKey.raw().invoke(dimension);
        if (nmsWorld == null) {
            return null;
        }
        org.bukkit.World bukkitWorld = nmsWorld.getWorld();

#if version >= 1.21
        // Optional<EntityPlayer.RespawnPosAngle>
  #if version >= 1.21.5
        java.util.Optional result = EntityPlayer.findRespawnAndUseSpawnBlock(instance, respawnPoint, ignoreRespawnAnchorUses);
  #else
        BlockPosition blockposition = (BlockPosition) respawnPoint.pos();
        java.util.Optional result = EntityPlayer.findRespawnAndUseSpawnBlock(instance, blockposition, respawnPoint.angle(), respawnPoint.forced(), ignoreRespawnAnchorUses);
  #endif
        EntityPlayer$RespawnPosAngle respawnPos = (EntityPlayer$RespawnPosAngle) result.orElse(null);
        if (respawnPos == null) {
            return null;
        } else {
            // public static record RespawnPosAngle(Vec3D position, float yaw, boolean isBedSpawn, boolean isAnchorSpawn)
            Vec3D vec3d = respawnPos.position();
            return new org.bukkit.Location(bukkitWorld,vec3d.x(), vec3d.y(), vec3d.z(), respawnPos.yaw(), 0.0f);
        }

#elseif version >= 1.14
        BlockPosition blockposition = (BlockPosition) respawnPoint.pos();
        // Optional<Vec3D>
  #if version >= 1.18
        java.util.Optional result = EntityHuman.findRespawnPositionAndUseSpawnBlock(instance, blockposition, respawnPoint.angle(), respawnPoint.forced(), ignoreRespawnAnchorUses);
  #elseif version >= 1.16.2
        java.util.Optional result = EntityHuman.getBed(instance, blockposition, respawnPoint.angle(), respawnPoint.forced(), ignoreRespawnAnchorUses);
  #elseif version >= 1.16
        java.util.Optional result = EntityHuman.getBed(instance, blockposition, respawnPoint.forced(), ignoreRespawnAnchorUses);
  #else
        java.util.Optional result = EntityHuman.getBed((IWorldReader) instance, blockposition, respawnPoint.forced());
  #endif
        Vec3D vec3d = (Vec3D) result.orElse(null);
        if (vec3d == null) {
            return null;
        } else {
  #if version >= 1.18
            return new org.bukkit.Location(bukkitWorld, vec3d.x(), vec3d.y(), vec3d.z());
  #else
            return new org.bukkit.Location(bukkitWorld, vec3d.getX(), vec3d.getY(), vec3d.getZ());
  #endif
        }
#else
        BlockPosition blockposition = (BlockPosition) respawnPoint.pos();
  #if version >= 1.13
        BlockPosition result = EntityHuman.getBed((IBlockAccess) instance, blockposition, respawnPoint.forced());
  #else
        BlockPosition result = EntityHuman.getBed((net.minecraft.world.level.World) instance, blockposition, respawnPoint.forced());
  #endif
        if (result == null) {
            return null;
        } else {
            return new org.bukkit.Location(bukkitWorld, (double) result.getX() + 0.5, (double) result.getY(), (double) result.getZ() + 0.5);
        }
#endif
    }

    <code>
    public static WorldServerHandle fromBukkit(org.bukkit.World world) {
        return createHandle(com.bergerkiller.bukkit.common.conversion.Conversion.toWorldHandle.convert(world));
    }

    public void setChunkProviderServer(ChunkProviderServerHandle chunkProviderServerHandle) {
        if (T.field_chunkProviderServer.isAvailable()) {
            T.field_chunkProviderServer.set(getRaw(), chunkProviderServerHandle);
        }
        if (WorldHandle.T.field_chunkProvider.isAvailable()) {
            WorldHandle.T.field_chunkProvider.set(getRaw(), chunkProviderServerHandle.getRaw());
        }
    }
    </code>
}
