// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.spawning.beacons;

import com.hypixel.hytale.component.dependency.RootDependency;
import com.hypixel.hytale.server.spawning.jobs.SpawnJob;
import com.hypixel.hytale.server.core.entity.Frozen;
import it.unimi.dsi.fastutil.Pair;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.spawning.SpawningContext;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.function.consumer.TriConsumer;
import com.hypixel.hytale.server.flock.FlockPlugin;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.spawning.controllers.SpawnJobSystem;
import java.util.function.Supplier;
import java.util.ArrayList;
import com.hypixel.hytale.server.spawning.controllers.SpawnController;
import java.util.UUID;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.server.npc.role.Role;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import java.util.Comparator;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.dependency.OrderPriority;
import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.spawning.jobs.NPCBeaconSpawnJob;
import com.hypixel.hytale.server.spawning.controllers.SpawnControllerSystem;
import java.util.List;
import java.time.Instant;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.component.dependency.Order;
import com.hypixel.hytale.component.dependency.Dependency;
import java.util.Set;
import com.hypixel.hytale.server.spawning.util.FloodFillEntryPoolProviderSimple;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.core.asset.type.responsecurve.ScaledXYResponseCurve;
import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.random.RandomExtra;
import com.hypixel.hytale.server.spawning.util.FloodFillPositionSelector;
import com.hypixel.hytale.server.spawning.controllers.BeaconSpawnController;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import com.hypixel.hytale.component.RemoveReason;
import java.util.logging.Level;
import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.AddReason;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.logger.HytaleLogger;

public class SpawnBeaconSystems
{
    public static final HytaleLogger LOGGER;
    public static final double[] POSITION_CALCULATION_DELAY_RANGE;
    private static final double LOAD_TIME_SPAWN_DELAY = 15.0;
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        POSITION_CALCULATION_DELAY_RANGE = new double[] { 0.0, 1.0 };
    }
    
    public static class LegacyEntityAdded extends RefSystem<EntityStore>
    {
        private final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType;
        
        public LegacyEntityAdded(final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final LegacySpawnBeaconEntity legacySpawnBeaconComponent = store.getComponent(ref, this.componentType);
            assert legacySpawnBeaconComponent != null;
            final String spawnConfigId = legacySpawnBeaconComponent.getSpawnConfigId();
            final int index = BeaconNPCSpawn.getAssetMap().getIndex(spawnConfigId);
            if (index == Integer.MIN_VALUE) {
                SpawnBeaconSystems.LOGGER.at(Level.SEVERE).log("Beacon %s removed due to missing spawn beacon type: %s", ref, spawnConfigId);
                commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                return;
            }
            legacySpawnBeaconComponent.setSpawnWrapper(SpawningPlugin.get().getBeaconSpawnWrapper(index));
            final World world = store.getExternalData().getWorld();
            final BeaconSpawnController spawnController = new BeaconSpawnController(world, ref);
            legacySpawnBeaconComponent.setSpawnController(spawnController);
            final BeaconSpawnWrapper spawnWrapper = legacySpawnBeaconComponent.getSpawnWrapper();
            if (spawnWrapper != null) {
                spawnController.initialise(spawnWrapper);
                final FloodFillPositionSelector positionSelector = new FloodFillPositionSelector(world, spawnWrapper);
                positionSelector.setCalculatePositionsAfter(RandomExtra.randomRange(SpawnBeaconSystems.POSITION_CALCULATION_DELAY_RANGE[0], SpawnBeaconSystems.POSITION_CALCULATION_DELAY_RANGE[1]));
                commandBuffer.putComponent(ref, FloodFillPositionSelector.getComponentType(), positionSelector);
                final ScaledXYResponseCurve maxSpawnScaleCurve = spawnWrapper.getSpawn().getMaxSpawnsScalingCurve();
                final int baseMaxTotalSpawns = spawnController.getBaseMaxTotalSpawns();
                final int currentScaledMaxTotalSpawns = (maxSpawnScaleCurve != null) ? (baseMaxTotalSpawns + MathUtil.floor(maxSpawnScaleCurve.computeY(legacySpawnBeaconComponent.getLastPlayerCount()) + 0.25)) : baseMaxTotalSpawns;
                spawnController.setCurrentScaledMaxTotalSpawns(currentScaledMaxTotalSpawns);
            }
            if (reason == AddReason.LOAD) {
                final InitialBeaconDelay delay = new InitialBeaconDelay();
                delay.setLoadTimeSpawnDelay(15.0);
                commandBuffer.putComponent(ref, InitialBeaconDelay.getComponentType(), delay);
            }
            commandBuffer.ensureComponent(ref, PrefabCopyableComponent.getComponentType());
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<EntityStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
    }
    
    public static class EntityAdded extends RefSystem<EntityStore>
    {
        private final ComponentType<EntityStore, SpawnBeacon> componentType;
        
        public EntityAdded(final ComponentType<EntityStore, SpawnBeacon> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final SpawnBeacon spawnBeaconComponent = store.getComponent(ref, this.componentType);
            assert spawnBeaconComponent != null;
            final String config = spawnBeaconComponent.getSpawnConfigId();
            final int index = BeaconNPCSpawn.getAssetMap().getIndex(config);
            if (index == Integer.MIN_VALUE) {
                SpawnBeaconSystems.LOGGER.at(Level.SEVERE).log("Beacon %s removed due to missing spawn beacon type: %s", ref, config);
                commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                return;
            }
            final BeaconSpawnWrapper spawnWrapper = SpawningPlugin.get().getBeaconSpawnWrapper(index);
            spawnBeaconComponent.setSpawnWrapper(spawnWrapper);
            final FloodFillPositionSelector positionSelector = new FloodFillPositionSelector(store.getExternalData().getWorld(), spawnWrapper);
            positionSelector.setCalculatePositionsAfter(RandomExtra.randomRange(SpawnBeaconSystems.POSITION_CALCULATION_DELAY_RANGE[0], SpawnBeaconSystems.POSITION_CALCULATION_DELAY_RANGE[1]));
            commandBuffer.putComponent(ref, FloodFillPositionSelector.getComponentType(), positionSelector);
            commandBuffer.ensureComponent(ref, PrefabCopyableComponent.getComponentType());
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<EntityStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
    }
    
    public static class PositionSelectorUpdate extends EntityTickingSystem<EntityStore>
    {
        private final ComponentType<EntityStore, FloodFillPositionSelector> componentType;
        private final ComponentType<EntityStore, TransformComponent> transformComponentType;
        private final ResourceType<EntityStore, FloodFillEntryPoolProviderSimple> floodFillEntryPoolProviderSimpleResourceType;
        @Nonnull
        private final Query<EntityStore> query;
        private final Set<Dependency<EntityStore>> dependencies;
        
        public PositionSelectorUpdate(final ComponentType<EntityStore, FloodFillPositionSelector> componentType, final ResourceType<EntityStore, FloodFillEntryPoolProviderSimple> floodFillEntryPoolProviderSimpleResourceType) {
            this.dependencies = (Set<Dependency<EntityStore>>)Set.of(new SystemDependency(Order.AFTER, CheckDespawn.class));
            this.componentType = componentType;
            this.transformComponentType = TransformComponent.getComponentType();
            this.floodFillEntryPoolProviderSimpleResourceType = floodFillEntryPoolProviderSimpleResourceType;
            this.query = (Query<EntityStore>)Query.and(componentType, this.transformComponentType);
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return this.dependencies;
        }
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return this.query;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return false;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final FloodFillPositionSelector positionSelectorComponent = archetypeChunk.getComponent(index, this.componentType);
            assert positionSelectorComponent != null;
            if (positionSelectorComponent.shouldRebuildCache() && positionSelectorComponent.tickCalculatePositionsAfter(dt)) {
                positionSelectorComponent.init();
                final TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType);
                assert transformComponent != null;
                final Vector3d position = transformComponent.getPosition();
                final FloodFillEntryPoolProviderSimple poolProvider = store.getResource(this.floodFillEntryPoolProviderSimpleResourceType);
                positionSelectorComponent.buildPositionCache(position, poolProvider.getPool());
            }
        }
    }
    
    public static class CheckDespawn extends EntityTickingSystem<EntityStore>
    {
        private static final HytaleLogger LOGGER;
        private final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType;
        @Nullable
        private final ComponentType<EntityStore, NPCEntity> npcComponentType;
        @Nonnull
        private final Query<EntityStore> query;
        
        public CheckDespawn(final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType, final ComponentType<EntityStore, InitialBeaconDelay> initialBeaconDelayComponentType) {
            this.componentType = componentType;
            this.npcComponentType = NPCEntity.getComponentType();
            this.query = (Query<EntityStore>)Query.and(componentType, UUIDComponent.getComponentType(), Query.not((Query<Object>)initialBeaconDelayComponentType));
        }
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return this.query;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final LegacySpawnBeaconEntity legacySpawnBeaconComponent = archetypeChunk.getComponent(index, this.componentType);
            assert legacySpawnBeaconComponent != null;
            final UUIDComponent uuidComponent = archetypeChunk.getComponent(index, UUIDComponent.getComponentType());
            assert uuidComponent != null;
            final BeaconSpawnController spawnController = legacySpawnBeaconComponent.getSpawnController();
            final Instant despawnSelfAfter = legacySpawnBeaconComponent.getDespawnSelfAfter();
            final WorldTimeResource worldTimeResource = commandBuffer.getResource(WorldTimeResource.getResourceType());
            if (despawnSelfAfter != null && worldTimeResource.getGameTime().isAfter(despawnSelfAfter)) {
                this.despawnAllSpawns(spawnController.getSpawnedEntities(), commandBuffer);
                commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
                return;
            }
            final World world = store.getExternalData().getWorld();
            final BeaconSpawnWrapper spawnWrapper = legacySpawnBeaconComponent.getSpawnWrapper();
            if (spawnWrapper.shouldDespawn(world, worldTimeResource)) {
                CheckDespawn.LOGGER.at(Level.FINE).log("Removing spawn beacon %s due to matching despawn parameters", uuidComponent.getUuid());
                this.despawnAllSpawns(spawnController.getSpawnedEntities(), commandBuffer);
                commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
            }
        }
        
        private void despawnAllSpawns(@Nonnull final List<Ref<EntityStore>> spawnedEntities, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            for (int i = 0; i < spawnedEntities.size(); ++i) {
                final Ref<EntityStore> ref = spawnedEntities.get(i);
                if (ref.isValid()) {
                    final NPCEntity npc = commandBuffer.getComponent(ref, this.npcComponentType);
                    if (npc != null) {
                        if (!npc.getRole().getStateSupport().isInBusyState()) {
                            if (!npc.isDespawning()) {
                                npc.setToDespawn();
                            }
                        }
                    }
                }
            }
            spawnedEntities.clear();
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
    
    public static class ControllerTick extends SpawnControllerSystem<NPCBeaconSpawnJob, BeaconSpawnController>
    {
        private static final ThreadLocal<List<NPCEntity>> THREAD_LOCAL_VALIDATED_ENTITIES;
        private final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType;
        private final ComponentType<EntityStore, FloodFillPositionSelector> floodFillPositionSelectorComponentType;
        private final ComponentType<EntityStore, PlayerRef> playerRefComponentType;
        @Nullable
        private final ComponentType<EntityStore, NPCEntity> npcComponentType;
        private final ComponentType<EntityStore, TransformComponent> transformComponentType;
        private final ComponentType<EntityStore, DeathComponent> deathComponentComponentType;
        private final ComponentType<EntityStore, UUIDComponent> uuidComponentType;
        private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> playerSpatialResource;
        @Nonnull
        private final Query<EntityStore> query;
        @Nonnull
        private final Set<Dependency<EntityStore>> dependencies;
        
        public ControllerTick(final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType, final ComponentType<EntityStore, FloodFillPositionSelector> floodFillPositionSelectorComponentType, final ComponentType<EntityStore, InitialBeaconDelay> initialBeaconDelayComponentType) {
            this.uuidComponentType = UUIDComponent.getComponentType();
            this.componentType = componentType;
            this.floodFillPositionSelectorComponentType = floodFillPositionSelectorComponentType;
            this.playerRefComponentType = PlayerRef.getComponentType();
            this.npcComponentType = NPCEntity.getComponentType();
            this.transformComponentType = TransformComponent.getComponentType();
            this.deathComponentComponentType = DeathComponent.getComponentType();
            this.playerSpatialResource = EntityModule.get().getPlayerSpatialResourceType();
            this.query = (Query<EntityStore>)Query.and(componentType, floodFillPositionSelectorComponentType, this.transformComponentType, Query.not((Query<Object>)initialBeaconDelayComponentType));
            this.dependencies = (Set<Dependency<EntityStore>>)Set.of(new SystemDependency(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST), new SystemDependency(Order.AFTER, PositionSelectorUpdate.class));
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return this.dependencies;
        }
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return this.query;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return false;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final FloodFillPositionSelector positionSelectorComponent = archetypeChunk.getComponent(index, this.floodFillPositionSelectorComponentType);
            assert positionSelectorComponent != null;
            if (positionSelectorComponent.shouldRebuildCache()) {
                return;
            }
            final TransformComponent transformComponent = archetypeChunk.getComponent(index, this.transformComponentType);
            assert transformComponent != null;
            final Vector3d position = transformComponent.getPosition();
            final LegacySpawnBeaconEntity legacySpawnBeaconComponent = archetypeChunk.getComponent(index, this.componentType);
            assert legacySpawnBeaconComponent != null;
            legacySpawnBeaconComponent.setSpawnAttempts(0);
            final List<NPCEntity> validatedEntityList = ControllerTick.THREAD_LOCAL_VALIDATED_ENTITIES.get();
            final BeaconSpawnController spawnController = legacySpawnBeaconComponent.getSpawnController();
            final List<Ref<EntityStore>> spawnedEntities = spawnController.getSpawnedEntities();
            if (!spawnedEntities.isEmpty()) {
                final Object2DoubleMap<Ref<EntityStore>> entityTimeoutCounter = spawnController.getEntityTimeoutCounter();
                final boolean despawnNPCsIfIdle = spawnController.isDespawnNPCsIfIdle();
                final double beaconRadiusSquared = spawnController.getBeaconRadiusSquared();
                final double despawnNPCAfterTimeout = spawnController.getDespawnNPCAfterTimeout();
                for (int i = spawnedEntities.size() - 1; i >= 0; --i) {
                    final Ref<EntityStore> spawnedEntityReference = spawnedEntities.get(i);
                    if (!spawnedEntityReference.isValid()) {
                        spawnedEntities.remove(i);
                    }
                    else {
                        final NPCEntity spawnedEntityNpcComponent = commandBuffer.getComponent(spawnedEntityReference, this.npcComponentType);
                        if (spawnedEntityNpcComponent != null) {
                            if (!spawnedEntityNpcComponent.isDespawning()) {
                                final Role role = spawnedEntityNpcComponent.getRole();
                                final boolean hasTarget = role.getMarkedEntitySupport().hasMarkedEntityInSlot(legacySpawnBeaconComponent.getSpawnWrapper().getSpawn().getTargetSlot());
                                final TransformComponent spawnedEntityTransformComponent = commandBuffer.getComponent(spawnedEntityReference, this.transformComponentType);
                                assert spawnedEntityTransformComponent != null;
                                final Vector3d npcPosition = spawnedEntityTransformComponent.getPosition();
                                final double beaconDistance = npcPosition.distanceSquaredTo(position);
                                if (((despawnNPCsIfIdle && !hasTarget) || beaconDistance > beaconRadiusSquared) && !role.getStateSupport().isInBusyState()) {
                                    final double timeout = entityTimeoutCounter.mergeDouble(spawnedEntityReference, dt, Double::sum);
                                    if (timeout >= despawnNPCAfterTimeout) {
                                        spawnedEntityNpcComponent.setToDespawn();
                                    }
                                }
                                else {
                                    entityTimeoutCounter.put(spawnedEntityReference, 0.0);
                                    validatedEntityList.add(spawnedEntityNpcComponent);
                                }
                            }
                        }
                    }
                }
            }
            final WorldTimeResource timeManager = commandBuffer.getResource(WorldTimeResource.getResourceType());
            if (!isReadyToRespawn(legacySpawnBeaconComponent, timeManager)) {
                validatedEntityList.clear();
                return;
            }
            final int y = MathUtil.floor(position.getY());
            final BeaconSpawnWrapper spawnWrapper = legacySpawnBeaconComponent.getSpawnWrapper();
            final int[] yRange = spawnWrapper.getSpawn().getYRange();
            final double minY = y + yRange[0];
            final double maxY = y + yRange[1];
            final SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(this.playerSpatialResource);
            final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
            spatialResource.getSpatialStructure().collect(position, spawnWrapper.getBeaconRadius(), results);
            final List<PlayerRef> playersInRegion = spawnController.getPlayersInRegion();
            for (int j = 0; j < results.size(); ++j) {
                final Ref<EntityStore> result = results.get(j);
                if (result.isValid()) {
                    final PlayerRef resultPlayerComponent = commandBuffer.getComponent(result, this.playerRefComponentType);
                    assert resultPlayerComponent != null;
                    final TransformComponent resultTransformComponent = commandBuffer.getComponent(result, this.transformComponentType);
                    assert resultTransformComponent != null;
                    final double yPos = resultTransformComponent.getPosition().getY();
                    if (yPos >= minY && yPos <= maxY) {
                        if (!commandBuffer.getArchetype(result).contains(this.deathComponentComponentType)) {
                            playersInRegion.add(resultPlayerComponent);
                        }
                    }
                }
            }
            legacySpawnBeaconComponent.setLastPlayerCount(playersInRegion.size());
            final Ref<EntityStore> spawnBeaconRef = archetypeChunk.getReferenceTo(index);
            if (playersInRegion.isEmpty()) {
                LegacySpawnBeaconEntity.setToDespawnAfter(spawnBeaconRef, spawnController.getDespawnBeaconAfterTimeout(), commandBuffer);
                validatedEntityList.clear();
                return;
            }
            boolean playersInSpawnRange = false;
            for (int k = 0; k < playersInRegion.size(); ++k) {
                final Ref<EntityStore> playerReference = playersInRegion.get(k).getReference();
                final TransformComponent playerTransformComponent = commandBuffer.getComponent(playerReference, this.transformComponentType);
                assert playerTransformComponent != null;
                final Vector3d playerPos = playerTransformComponent.getPosition();
                if (playerPos.distanceSquaredTo(position) <= spawnController.getSpawnRadiusSquared()) {
                    playersInSpawnRange = true;
                    break;
                }
            }
            if (playersInSpawnRange) {
                LegacySpawnBeaconEntity.clearDespawnTimer(spawnBeaconRef, commandBuffer);
            }
            else {
                LegacySpawnBeaconEntity.setToDespawnAfter(spawnBeaconRef, spawnController.getDespawnBeaconAfterTimeout(), commandBuffer);
            }
            final ScaledXYResponseCurve maxSpawnScaleCurve = spawnWrapper.getSpawn().getMaxSpawnsScalingCurve();
            final int baseMaxTotalSpawns = spawnController.getBaseMaxTotalSpawns();
            final int currentScaledMaxTotalSpawns = (maxSpawnScaleCurve != null) ? (baseMaxTotalSpawns + MathUtil.floor(maxSpawnScaleCurve.computeY(playersInRegion.size()) + 0.25)) : baseMaxTotalSpawns;
            spawnController.setCurrentScaledMaxTotalSpawns(currentScaledMaxTotalSpawns);
            if (spawnController.getSpawnedEntities().size() >= currentScaledMaxTotalSpawns) {
                playersInRegion.clear();
                validatedEntityList.clear();
                return;
            }
            final Object2IntMap<UUID> entitiesPerPlayer = spawnController.getEntitiesPerPlayer();
            for (int l = 0; l < validatedEntityList.size(); ++l) {
                final NPCEntity npc = validatedEntityList.get(l);
                final Ref<EntityStore> lockedTargetRef = npc.getRole().getMarkedEntitySupport().getMarkedEntityRef(legacySpawnBeaconComponent.getSpawnWrapper().getSpawn().getTargetSlot());
                if (lockedTargetRef != null) {
                    final UUIDComponent lockedTarget = commandBuffer.getComponent(lockedTargetRef, this.uuidComponentType);
                    if (lockedTarget != null) {
                        entitiesPerPlayer.mergeInt(lockedTarget.getUuid(), 1, Integer::sum);
                    }
                }
            }
            playersInRegion.sort(spawnController.getThreatComparator());
            entitiesPerPlayer.clear();
            ((SpawnControllerSystem<J, BeaconSpawnController>)this).tickController(spawnController, store);
            playersInRegion.clear();
            spawnController.setNextPlayerIndex(0);
            validatedEntityList.clear();
        }
        
        private static boolean isReadyToRespawn(final LegacySpawnBeaconEntity spawnBeacon, final WorldTimeResource timeManager) {
            final Instant nextSpawnAfter = spawnBeacon.getNextSpawnAfter();
            if (nextSpawnAfter == null) {
                return true;
            }
            final Instant now = spawnBeacon.isNextSpawnAfterRealtime() ? Instant.now() : timeManager.getGameTime();
            return now.isAfter(nextSpawnAfter);
        }
        
        @Override
        protected void prepareSpawnJobGeneration(@Nonnull final BeaconSpawnController spawnController, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
            if (spawnController.isRoundStart()) {
                final Ref<EntityStore> ownerRef = spawnController.getOwnerRef();
                final LegacySpawnBeaconEntity legacySpawnBeaconComponent = componentAccessor.getComponent(ownerRef, LegacySpawnBeaconEntity.getComponentType());
                assert legacySpawnBeaconComponent != null;
                final ScaledXYResponseCurve concurrentSpawnScaleCurve = legacySpawnBeaconComponent.getSpawnWrapper().getSpawn().getConcurrentSpawnsScalingCurve();
                final int[] baseMaxConcurrentSpawns = spawnController.getBaseMaxConcurrentSpawns();
                final List<PlayerRef> playersInRegion = spawnController.getPlayersInRegion();
                int min;
                int max;
                if (concurrentSpawnScaleCurve != null) {
                    min = baseMaxConcurrentSpawns[0] + MathUtil.floor(concurrentSpawnScaleCurve.computeY(playersInRegion.size()) + 0.25);
                    max = baseMaxConcurrentSpawns[1] + MathUtil.floor(concurrentSpawnScaleCurve.computeY(playersInRegion.size()) + 0.25);
                }
                else {
                    min = baseMaxConcurrentSpawns[0];
                    max = baseMaxConcurrentSpawns[1];
                }
                spawnController.setCurrentScaledMaxConcurrentSpawns(RandomExtra.randomRange(min, max));
                spawnController.setRoundStart(false);
            }
            final int remainingSpawns = Math.max(0, spawnController.getCurrentScaledMaxConcurrentSpawns()) - spawnController.getSpawnsThisRound();
            spawnController.setRemainingSpawns(remainingSpawns);
            if (remainingSpawns == 0) {
                spawnController.onAllConcurrentSpawned(componentAccessor);
            }
        }
        
        @Override
        protected void createRandomSpawnJobs(@Nonnull final BeaconSpawnController spawnController, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
            while (spawnController.getActiveJobCount() < spawnController.getMaxActiveJobs()) {
                if (spawnController.createRandomSpawnJob(componentAccessor) == null) {
                    spawnController.addRoundSpawn();
                }
            }
        }
        
        static {
            THREAD_LOCAL_VALIDATED_ENTITIES = ThreadLocal.withInitial((Supplier<? extends List<NPCEntity>>)ArrayList::new);
        }
    }
    
    public static class SpawnJobTick extends SpawnJobSystem<NPCBeaconSpawnJob, BeaconSpawnController>
    {
        private static final HytaleLogger LOGGER;
        private final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType;
        @Nonnull
        private final ComponentType<EntityStore, Player> playerComponentType;
        private final ComponentType<EntityStore, TransformComponent> transformComponentType;
        @Nonnull
        private final Query<EntityStore> query;
        private final Set<Dependency<EntityStore>> dependencies;
        
        public SpawnJobTick(final ComponentType<EntityStore, LegacySpawnBeaconEntity> componentType, final ComponentType<EntityStore, InitialBeaconDelay> initialBeaconDelayComponentType) {
            this.dependencies = (Set<Dependency<EntityStore>>)Set.of(new SystemDependency(Order.AFTER, ControllerTick.class));
            this.componentType = componentType;
            this.playerComponentType = Player.getComponentType();
            this.transformComponentType = TransformComponent.getComponentType();
            this.query = (Query<EntityStore>)Query.and(componentType, Query.not((Query<Object>)initialBeaconDelayComponentType));
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return this.dependencies;
        }
        
        @Nonnull
        @Override
        public Query<EntityStore> getQuery() {
            return this.query;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return false;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final LegacySpawnBeaconEntity legacySpawnBeaconComponent = archetypeChunk.getComponent(index, this.componentType);
            assert legacySpawnBeaconComponent != null;
            ((SpawnJobSystem<J, BeaconSpawnController>)this).tickSpawnJobs(legacySpawnBeaconComponent.getSpawnController(), store, commandBuffer);
        }
        
        @Override
        protected void onStartRun(final NPCBeaconSpawnJob spawnJob) {
        }
        
        @Override
        protected void onEndProbing(@Nonnull final BeaconSpawnController spawnController, @Nonnull final NPCBeaconSpawnJob spawnJob, final Result result, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
            final Ref<EntityStore> ownerRef = spawnController.getOwnerRef();
            if (!ownerRef.isValid()) {
                return;
            }
            final LegacySpawnBeaconEntity legacySpawnBeaconEntity = componentAccessor.getComponent(ownerRef, LegacySpawnBeaconEntity.getComponentType());
            assert legacySpawnBeaconEntity != null;
            if (result == Result.FAILED && legacySpawnBeaconEntity.getSpawnAttempts() > 5) {
                LegacySpawnBeaconEntity.prepareNextSpawnTimer(ownerRef, componentAccessor);
                spawnJob.setBudgetUsed(spawnJob.getColumnBudget());
            }
            else if (result == Result.PERMANENT_FAILURE) {
                legacySpawnBeaconEntity.remove();
            }
            else if (result != Result.SUCCESS) {
                legacySpawnBeaconEntity.notifyFailedSpawn();
            }
        }
        
        @Override
        protected boolean pickSpawnPosition(@Nonnull final BeaconSpawnController spawnController, @Nonnull final NPCBeaconSpawnJob spawnJob, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final Ref<EntityStore> playerReference = spawnJob.getPlayer();
            if (playerReference == null || !playerReference.isValid()) {
                return false;
            }
            final TransformComponent playerTransformComponent = commandBuffer.getComponent(playerReference, this.transformComponentType);
            assert playerTransformComponent != null;
            final Vector3d playerPosition = playerTransformComponent.getPosition();
            final Ref<EntityStore> ownerRef = spawnController.getOwnerRef();
            final LegacySpawnBeaconEntity legacySpawnBeaconComponent = commandBuffer.getComponent(ownerRef, LegacySpawnBeaconEntity.getComponentType());
            assert legacySpawnBeaconComponent != null;
            return legacySpawnBeaconComponent.prepareSpawnContext(playerPosition, spawnJob.getSpawnsThisRound(), spawnJob.getRoleIndex(), spawnJob.getSpawningContext(), commandBuffer);
        }
        
        @Nonnull
        @Override
        protected Result trySpawn(@Nonnull final BeaconSpawnController spawnController, @Nonnull final NPCBeaconSpawnJob spawnJob, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            return this.spawn(spawnJob.getSpawningContext().world, spawnController, spawnJob, commandBuffer);
        }
        
        @Nonnull
        @Override
        protected Result spawn(final World world, @Nonnull final BeaconSpawnController spawnController, @Nonnull final NPCBeaconSpawnJob spawnJob, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final SpawningContext spawningContext = spawnJob.getSpawningContext();
            final Vector3d position = spawningContext.newPosition();
            final Vector3f rotation = spawningContext.newRotation();
            final int roleIndex = spawnJob.getRoleIndex();
            commandBuffer.run(_store -> {
                try {
                    final Pair<Ref<EntityStore>, NPCEntity> npcPair = NPCPlugin.get().spawnEntity(_store, roleIndex, position, rotation, spawningContext.getModel(), (npc, ref, store) -> postSpawn(npc, ref, roleIndex, spawnController.isDebugSpawnFrozen(), store));
                    final Ref<EntityStore> npcRef = npcPair.first();
                    FlockPlugin.trySpawnFlock(npcRef, npcPair.second(), roleIndex, position, rotation, spawnJob.getFlockSize(), spawnJob.getFlockAsset(), null, (npc, ref, store) -> postSpawn(npc, ref, roleIndex, spawnController.isDebugSpawnFrozen(), store), _store);
                    this.onSpawn(npcRef, spawnController, spawnJob, _store);
                    this.endProbing(spawnController, spawnJob, Result.SUCCESS, _store);
                }
                catch (final RuntimeException e) {
                    SpawnJobTick.LOGGER.at(Level.WARNING).log("Spawn job %s: Failed to create %s: %s", spawnJob.getJobId(), NPCPlugin.get().getName(roleIndex), e.getMessage());
                    this.endProbing(spawnController, spawnJob, Result.FAILED, _store);
                }
                spawnController.addIdleJob(spawnJob);
                return;
            });
            return Result.PENDING_SPAWN;
        }
        
        private void onSpawn(@Nonnull final Ref<EntityStore> npcReference, @Nonnull final BeaconSpawnController spawnController, @Nonnull final NPCBeaconSpawnJob spawnJob, @Nonnull final Store<EntityStore> store) {
            final HytaleLogger.Api context = SpawnJobTick.LOGGER.at(Level.FINE);
            if (context.isEnabled()) {
                final TransformComponent transformComponent = store.getComponent(npcReference, this.transformComponentType);
                assert transformComponent != null;
                final Vector3d pos = transformComponent.getPosition();
                context.log("Spawn job %s: Created %s at position %s", spawnJob.getJobId(), NPCPlugin.get().getName(spawnJob.getRoleIndex()), pos);
            }
            final Ref<EntityStore> playerRef = spawnJob.getPlayer();
            assert playerRef != null;
            final Player playerComponent = store.getComponent(spawnJob.getPlayer(), this.playerComponentType);
            assert playerComponent != null;
            final Ref<EntityStore> ownerRef = spawnController.getOwnerRef();
            final LegacySpawnBeaconEntity legacySpawnBeaconComponent = store.getComponent(ownerRef, LegacySpawnBeaconEntity.getComponentType());
            assert legacySpawnBeaconComponent != null;
            legacySpawnBeaconComponent.notifySpawn(playerComponent, npcReference, store);
        }
        
        private static void postSpawn(@Nonnull final NPCEntity entity, @Nonnull final Ref<EntityStore> ref, final int roleIndex, final boolean spawnFrozen, @Nonnull final Store<EntityStore> store) {
            entity.setSpawnRoleIndex(roleIndex);
            if (spawnFrozen) {
                store.ensureComponent(ref, Frozen.getComponentType());
            }
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
    
    public static class LoadTimeDelay extends EntityTickingSystem<EntityStore>
    {
        private final ComponentType<EntityStore, InitialBeaconDelay> componentType;
        private final Set<Dependency<EntityStore>> dependencies;
        
        public LoadTimeDelay(final ComponentType<EntityStore, InitialBeaconDelay> componentType) {
            this.dependencies = RootDependency.lastSet();
            this.componentType = componentType;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<EntityStore>> getDependencies() {
            return this.dependencies;
        }
        
        @Override
        public Query<EntityStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final InitialBeaconDelay beaconDelayComponent = archetypeChunk.getComponent(index, this.componentType);
            assert beaconDelayComponent != null;
            if (!beaconDelayComponent.tickLoadTimeSpawnDelay(dt)) {
                return;
            }
            commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.componentType);
        }
    }
}
