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

package com.hypixel.hytale.server.spawning.world.system;

import com.hypixel.hytale.server.spawning.assets.spawns.config.NPCSpawn;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.server.spawning.world.component.ChunkSpawnedNPCData;
import com.hypixel.hytale.server.spawning.world.component.ChunkSpawnData;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.NonTicking;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.spawning.world.WorldEnvironmentSpawnData;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
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.math.vector.Vector3d;
import com.hypixel.hytale.server.flock.FlockPlugin;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.function.consumer.TriConsumer;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.spawning.SpawnTestResult;
import com.hypixel.hytale.server.spawning.SpawnRejection;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.spawning.util.RandomChunkColumnIterator;
import it.unimi.dsi.fastutil.ints.IntSet;
import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap;
import com.hypixel.hytale.server.spawning.suppression.SuppressionSpanHelper;
import com.hypixel.hytale.server.spawning.SpawningContext;
import com.hypixel.hytale.server.spawning.ISpawnableWithModel;
import com.hypixel.hytale.server.spawning.suppression.component.ChunkSuppressionEntry;
import java.util.logging.Level;
import com.hypixel.hytale.server.spawning.suppression.component.SpawnSuppressionController;
import com.hypixel.hytale.server.spawning.world.component.WorldSpawnData;
import com.hypixel.hytale.server.spawning.world.ChunkEnvironmentSpawnData;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.spawning.world.component.SpawnJobData;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.logger.HytaleLogger;

public class WorldSpawnJobSystems
{
    private static final HytaleLogger LOGGER;
    private static final Query<ChunkStore> QUERY;
    private static final Query<ChunkStore> TICKING_QUERY;
    private static final int JOB_BUDGET = 64;
    
    @Nonnull
    private static Result run(@Nonnull final SpawnJobData spawnJobData, @Nonnull final WorldChunk chunk, @Nonnull final ChunkEnvironmentSpawnData chunkEnvironmentSpawnData, @Nonnull final WorldSpawnData worldSpawnData, @Nonnull final SpawnSuppressionController spawnSuppressionController) {
        final int roleIndex = spawnJobData.getRoleIndex();
        ISpawnableWithModel spawnable;
        try {
            spawnable = getSpawnable(roleIndex);
        }
        catch (final IllegalArgumentException e) {
            endProbing(Result.PERMANENT_FAILURE, spawnJobData, chunk, worldSpawnData);
            throw e;
        }
        if (spawnable == null) {
            final HytaleLogger.Api context = WorldSpawnJobSystems.LOGGER.at(Level.FINEST);
            if (context.isEnabled()) {
                context.log("Spawn job %s: Terminated, spawnable %s gone", spawnJobData.getJobId(), getSpawnableName(roleIndex));
            }
            return endProbing(Result.FAILED, spawnJobData, chunk, worldSpawnData);
        }
        final SpawningContext spawningContext = spawnJobData.getSpawningContext();
        if (!spawningContext.setSpawnable(spawnable)) {
            final HytaleLogger.Api context2 = WorldSpawnJobSystems.LOGGER.at(Level.FINEST);
            if (context2.isEnabled()) {
                context2.log("Spawn job %s: Terminated, Unable to set spawnable %s", spawnJobData.getJobId(), getSpawnableName(roleIndex));
            }
            return endProbing(Result.FAILED, spawnJobData, chunk, worldSpawnData);
        }
        spawningContext.setChunk(chunk, spawnJobData.getEnvironmentIndex());
        final SuppressionSpanHelper suppressionSpanHelper = spawnJobData.getSuppressionSpanHelper();
        final Long2ObjectConcurrentHashMap<ChunkSuppressionEntry> chunkSuppressionMap = spawnSuppressionController.getChunkSuppressionMap();
        suppressionSpanHelper.optimiseSuppressedSpans(roleIndex, chunkSuppressionMap.get(chunk.getIndex()));
        try {
            final IntSet spawnBlockSet = spawnJobData.getSpawnConfig().getSpawnBlockSet(roleIndex);
            final int spawnFluidTag = spawnJobData.getSpawnConfig().getSpawnFluidTag(roleIndex);
            final RandomChunkColumnIterator iterator = chunkEnvironmentSpawnData.getRandomChunkColumnIterator();
            spawnJobData.setBudgetUsed(0);
            while (spawnJobData.getBudgetUsed() < 64) {
                iterator.nextPositionAvoidBorders();
                spawnJobData.adjustBudgetUsed(3);
                spawningContext.setColumn(iterator.getCurrentX(), iterator.getCurrentZ(), suppressionSpanHelper);
                final Result result = trySpawn(spawnable, spawnBlockSet, spawnFluidTag, spawnJobData, chunk, chunkEnvironmentSpawnData, worldSpawnData);
                if (result != Result.TRY_AGAIN) {
                    return result;
                }
            }
        }
        finally {
            spawningContext.release();
        }
        return Result.TRY_AGAIN;
    }
    
    @Nullable
    private static ISpawnableWithModel getSpawnable(final int roleIndex) {
        final Builder<Role> role = NPCPlugin.get().tryGetCachedValidRole(roleIndex);
        if (role == null) {
            return null;
        }
        if (!role.isSpawnable()) {
            throw new IllegalArgumentException("Spawn job: Role must be a spawnable (non-abstract) type for spawning: " + NPCPlugin.get().getName(roleIndex));
        }
        if (!(role instanceof ISpawnableWithModel)) {
            throw new IllegalArgumentException("Spawn job: Need ISpawnableWithModel interface for spawning: " + NPCPlugin.get().getName(roleIndex));
        }
        return (ISpawnableWithModel)role;
    }
    
    @Nonnull
    private static Result trySpawn(@Nonnull final ISpawnableWithModel spawnable, final IntSet spawnBlockSet, final int spawnFluidTag, @Nonnull final SpawnJobData spawnJobData, @Nonnull final WorldChunk worldChunk, @Nonnull final ChunkEnvironmentSpawnData environmentSpawnData, @Nonnull final WorldSpawnData worldSpawnData) {
        spawnJobData.incrementTotalColumnsTested();
        final SpawningContext spawningContext = spawnJobData.getSpawningContext();
        try {
            int spansBlocked = 0;
            int spansTested = 0;
            while (spawningContext.selectRandomSpawnSpan()) {
                spawnJobData.adjustBudgetUsed(1);
                spawnJobData.incrementSpansTried();
                ++spansTested;
                if (!spawnJobData.getSpawnConfig().withinLightRange(spawningContext)) {
                    rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.OUTSIDE_LIGHT_RANGE);
                }
                else if (!canSpawnOnBlock(spawnBlockSet, spawnFluidTag, spawningContext)) {
                    ++spansBlocked;
                    rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.INVALID_SPAWN_BLOCK);
                }
                else {
                    final SpawnTestResult spawnTestResult = spawningContext.canSpawn();
                    if (spawnTestResult == SpawnTestResult.TEST_OK) {
                        return spawn(spawnJobData, worldChunk, worldSpawnData);
                    }
                    if (spawnTestResult == SpawnTestResult.FAIL_INVALID_POSITION) {
                        rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.INVALID_POSITION);
                        ++spansBlocked;
                    }
                    else if (spawnTestResult == SpawnTestResult.FAIL_NO_POSITION) {
                        rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.NO_POSITION);
                        ++spansBlocked;
                    }
                    else if (spawnTestResult == SpawnTestResult.FAIL_NOT_BREATHABLE) {
                        rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.NOT_BREATHABLE);
                        ++spansBlocked;
                    }
                    else {
                        rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.OTHER);
                    }
                }
                spawningContext.deleteCurrentSpawnSpan();
            }
            if (spansBlocked > 0 && spansTested == spansBlocked) {
                spawnJobData.incrementTotalColumnsBlocked();
            }
        }
        catch (final IllegalStateException | NullPointerException e) {
            WorldSpawnJobSystems.LOGGER.at(Level.WARNING).log("%s with spawnable=%s spwnCfg=%s X/Y/Z=%s/%s/%s", e.getMessage(), getSpawnableName(spawnJobData.getRoleIndex()), ((NPCSpawn)spawnJobData.getSpawnConfig().getSpawn()).getId(), spawningContext.xSpawn, spawningContext.ySpawn, spawningContext.zSpawn);
            spawnable.markNeedsReload();
            return endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
        }
        if (environmentSpawnData.getRandomChunkColumnIterator().isAtSavedIteratorPosition()) {
            if (spawnJobData.getTotalColumnsBlocked() == spawnJobData.getTotalColumnsTested()) {
                environmentSpawnData.markRoleAsUnspawnable(spawnJobData.getRoleIndex());
                final HytaleLogger.Api context = WorldSpawnJobSystems.LOGGER.at(Level.FINEST);
                if (context.isEnabled()) {
                    context.log("Spawn job %s: No column to create %s (env %s) at chunk %s/%s, columns probed %s", spawnJobData.getJobId(), getSpawnableName(spawnJobData.getRoleIndex()), spawnJobData.getEnvironment().getId(), worldChunk.getX(), worldChunk.getZ(), spawnJobData.getTotalColumnsTested());
                }
                if (environmentSpawnData.allRolesUnspawnable()) {
                    worldSpawnData.queueUnspawnableChunk(spawnJobData.getEnvironmentIndex(), worldChunk.getIndex());
                    if (context.isEnabled()) {
                        context.log("Spawn job %s: All roles unspawnable. Queued for processing.", spawnJobData.getJobId());
                    }
                }
            }
            return endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
        }
        return Result.TRY_AGAIN;
    }
    
    @Nonnull
    private static Result spawn(@Nonnull final SpawnJobData spawnJobData, @Nonnull final WorldChunk worldChunk, @Nonnull final WorldSpawnData worldSpawnData) {
        final NPCPlugin npcModule = NPCPlugin.get();
        final SpawningContext spawningContext = spawnJobData.getSpawningContext();
        final Vector3d position = spawningContext.newPosition();
        final Vector3f rotation = spawningContext.newRotation();
        final int roleIndex = spawnJobData.getRoleIndex();
        try {
            final Store<EntityStore> store = spawningContext.world.getEntityStore().getStore();
            final Pair<Ref<EntityStore>, NPCEntity> npcPair = npcModule.spawnEntity(store, roleIndex, position, rotation, spawningContext.getModel(), (_npc, _holder, _store) -> preAddToWorld(_npc, _holder, roleIndex, spawnJobData), null);
            final NPCEntity npcComponent = npcPair.right();
            final Ref<EntityStore> npcRef = npcPair.left();
            FlockPlugin.trySpawnFlock(npcRef, npcComponent, roleIndex, position, rotation, spawnJobData.getFlockSize(), spawnJobData.getFlockAsset(), (_npc, _holder, _store) -> preAddToWorld(_npc, _holder, roleIndex, spawnJobData), null, store);
        }
        catch (final RuntimeException e) {
            WorldSpawnJobSystems.LOGGER.at(Level.SEVERE).withCause(e).log("Spawn job %s: Failed to create %s: %s", spawnJobData.getJobId(), npcModule.getName(roleIndex), e.getMessage());
            rejectSpan(spawnJobData.getRejectionMap(), SpawnRejection.OTHER);
            return endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
        }
        final HytaleLogger.Api context = WorldSpawnJobSystems.LOGGER.at(Level.FINEST);
        if (context.isEnabled()) {
            context.log("Spawn job %s: Created %s with flock size %s (env %s) at chunk %s/%s, columns probed %s", spawnJobData.getJobId(), NPCPlugin.get().getName(roleIndex), spawnJobData.getFlockSize(), spawnJobData.getEnvironment().getId(), worldChunk.getX(), worldChunk.getZ(), spawnJobData.getTotalColumnsTested());
        }
        spawnJobData.incrementSpansSuccess();
        return endProbing(Result.SUCCESS, spawnJobData, worldChunk, worldSpawnData);
    }
    
    private static void preAddToWorld(@Nonnull final NPCEntity npc, @Nonnull final Holder<EntityStore> holder, final int roleIndex, @Nonnull final SpawnJobData spawnJobData) {
        npc.setSpawnRoleIndex(roleIndex);
        if (spawnJobData.isSpawnFrozen()) {
            holder.ensureComponent(Frozen.getComponentType());
        }
        npc.setEnvironment(spawnJobData.getEnvironmentIndex());
        npc.setSpawnConfiguration(spawnJobData.getSpawnConfigIndex());
    }
    
    private static boolean canSpawnOnBlock(@Nullable final IntSet spawnBlockSet, final int spawnFluidTag, @Nonnull final SpawningContext spawningContext) {
        return (spawnBlockSet == null && spawnFluidTag == Integer.MIN_VALUE) || (spawnBlockSet != null && spawnBlockSet.contains(spawningContext.groundBlockId)) || (spawnFluidTag != Integer.MIN_VALUE && Fluid.getAssetMap().getIndexesForTag(spawnFluidTag).contains(spawningContext.groundFluidId));
    }
    
    private static void rejectSpan(@Nonnull final Object2IntMap<SpawnRejection> rejectionMap, final SpawnRejection rejection) {
        rejectionMap.mergeInt(rejection, 1, Integer::sum);
    }
    
    protected static Result endProbing(final Result result, @Nonnull final SpawnJobData spawnJobData, @Nonnull final WorldChunk worldChunk, @Nonnull final WorldSpawnData worldSpawnData) {
        final HytaleLogger.Api context = WorldSpawnJobSystems.LOGGER.at(Level.FINEST);
        if (context.isEnabled()) {
            context.log("Term Spawnjob id=%s env=%s role=%s chunk=[%s/%s] tested=%s result=%s budgetUsed=%s", spawnJobData.getJobId(), spawnJobData.getEnvironment().getId(), getSpawnableName(spawnJobData.getRoleIndex()), worldChunk.getX(), worldChunk.getZ(), spawnJobData.getTotalColumnsTested(), result, spawnJobData.getTotalBudgetUsed());
        }
        worldSpawnData.untrackNPC(spawnJobData.getEnvironmentIndex(), spawnJobData.getRoleIndex(), spawnJobData.getFlockSize());
        updateSpawnStats(worldSpawnData, spawnJobData, result);
        worldSpawnData.adjustActiveSpawnJobs(-1, -spawnJobData.getFlockSize());
        return result;
    }
    
    private static void updateSpawnStats(@Nonnull final WorldSpawnData worldSpawnData, @Nonnull final SpawnJobData spawnJobData, final Result result) {
        final boolean success = result == Result.SUCCESS;
        final WorldEnvironmentSpawnData worldEnvironmentSpawnStats = worldSpawnData.getWorldEnvironmentSpawnData(spawnJobData.getEnvironmentIndex());
        worldEnvironmentSpawnStats.updateSpawnStats(spawnJobData.getRoleIndex(), spawnJobData.getSpansTried(), spawnJobData.getSpansSuccess(), spawnJobData.getTotalBudgetUsed(), spawnJobData.getRejectionMap(), success);
        worldSpawnData.addCompletedSpawnJob(spawnJobData.getTotalBudgetUsed());
    }
    
    @Nullable
    private static String getSpawnableName(final int roleIndex) {
        return NPCPlugin.get().getName(roleIndex);
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        QUERY = Archetype.of(SpawnJobData.getComponentType(), WorldChunk.getComponentType());
        TICKING_QUERY = Query.and(Query.not((Query<Object>)ChunkStore.REGISTRY.getNonTickingComponentType()), WorldSpawnJobSystems.QUERY);
    }
    
    public static class EntityRemoved extends HolderSystem<ChunkStore>
    {
        private final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType;
        private final ComponentType<ChunkStore, SpawnJobData> spawnJobDataComponentType;
        private final ComponentType<ChunkStore, WorldChunk> worldChunkComponentType;
        
        public EntityRemoved(final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType, final ComponentType<ChunkStore, SpawnJobData> spawnJobDataComponentType) {
            this.worldSpawnDataResourceType = worldSpawnDataResourceType;
            this.spawnJobDataComponentType = spawnJobDataComponentType;
            this.worldChunkComponentType = WorldChunk.getComponentType();
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return WorldSpawnJobSystems.TICKING_QUERY;
        }
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> entityHolder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
            final SpawnJobData spawnJobData = entityHolder.getComponent(this.spawnJobDataComponentType);
            final WorldChunk worldChunk = entityHolder.getComponent(this.worldChunkComponentType);
            final WorldSpawnData worldSpawnData = store.getExternalData().getWorld().getEntityStore().getStore().getResource(this.worldSpawnDataResourceType);
            WorldSpawnJobSystems.endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
            entityHolder.removeComponent(this.spawnJobDataComponentType);
        }
    }
    
    public static class TickingState extends RefChangeSystem<ChunkStore, NonTicking<ChunkStore>>
    {
        private final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType;
        private final ComponentType<ChunkStore, SpawnJobData> spawnJobDataComponentType;
        private final ComponentType<ChunkStore, WorldChunk> worldChunkComponentType;
        
        public TickingState(final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType, final ComponentType<ChunkStore, SpawnJobData> spawnJobDataComponentType) {
            this.worldSpawnDataResourceType = worldSpawnDataResourceType;
            this.spawnJobDataComponentType = spawnJobDataComponentType;
            this.worldChunkComponentType = WorldChunk.getComponentType();
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return WorldSpawnJobSystems.QUERY;
        }
        
        @Nonnull
        @Override
        public ComponentType<ChunkStore, NonTicking<ChunkStore>> componentType() {
            return ChunkStore.REGISTRY.getNonTickingComponentType();
        }
        
        @Override
        public void onComponentAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final NonTicking<ChunkStore> component, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final SpawnJobData spawnJobData = store.getComponent(ref, this.spawnJobDataComponentType);
            final WorldChunk worldChunk = store.getComponent(ref, this.worldChunkComponentType);
            final WorldSpawnData worldSpawnData = store.getExternalData().getWorld().getEntityStore().getStore().getResource(this.worldSpawnDataResourceType);
            WorldSpawnJobSystems.endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
            commandBuffer.removeComponent(ref, this.spawnJobDataComponentType);
        }
        
        @Override
        public void onComponentSet(@Nonnull final Ref<ChunkStore> ref, final NonTicking<ChunkStore> oldComponent, @Nonnull final NonTicking<ChunkStore> newComponent, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        @Override
        public void onComponentRemoved(@Nonnull final Ref<ChunkStore> ref, @Nonnull final NonTicking<ChunkStore> component, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
    }
    
    public static class Ticking extends EntityTickingSystem<ChunkStore>
    {
        private final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType;
        private final ResourceType<EntityStore, SpawnSuppressionController> spawnSuppressionControllerResourceType;
        private final ComponentType<ChunkStore, SpawnJobData> spawnJobDataComponentType;
        private final ComponentType<ChunkStore, WorldChunk> worldChunkComponentType;
        private final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType;
        private final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType;
        
        public Ticking(final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType, final ResourceType<EntityStore, SpawnSuppressionController> spawnSuppressionControllerResourceType, final ComponentType<ChunkStore, SpawnJobData> spawnJobDataComponentType, final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType, final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType) {
            this.worldSpawnDataResourceType = worldSpawnDataResourceType;
            this.spawnSuppressionControllerResourceType = spawnSuppressionControllerResourceType;
            this.spawnJobDataComponentType = spawnJobDataComponentType;
            this.worldChunkComponentType = WorldChunk.getComponentType();
            this.chunkSpawnDataComponentType = chunkSpawnDataComponentType;
            this.chunkSpawnedNPCDataComponentType = chunkSpawnedNPCDataComponentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return WorldSpawnJobSystems.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<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final World world = store.getExternalData().getWorld();
            final Store<EntityStore> entityStore = world.getEntityStore().getStore();
            final SpawnJobData spawnJobData = archetypeChunk.getComponent(index, this.spawnJobDataComponentType);
            final WorldSpawnData worldSpawnData = entityStore.getResource(this.worldSpawnDataResourceType);
            final WorldChunk worldChunk = archetypeChunk.getComponent(index, this.worldChunkComponentType);
            if (spawnJobData.isTerminated() || worldSpawnData.getActualNPCs() > worldSpawnData.getExpectedNPCs()) {
                WorldSpawnJobSystems.endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
                commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.spawnJobDataComponentType);
                return;
            }
            final ChunkSpawnData chunkSpawnData = archetypeChunk.getComponent(index, this.chunkSpawnDataComponentType);
            final ChunkSpawnedNPCData chunkSpawnedNPCData = archetypeChunk.getComponent(index, this.chunkSpawnedNPCDataComponentType);
            final int environmentIndex = spawnJobData.getEnvironmentIndex();
            final double spawnedNPCs = chunkSpawnedNPCData.getEnvironmentSpawnCount(environmentIndex);
            final ChunkEnvironmentSpawnData chunkEnvironmentSpawnData = chunkSpawnData.getEnvironmentSpawnData(environmentIndex);
            if (chunkEnvironmentSpawnData.allRolesUnspawnable() || (!spawnJobData.isIgnoreFullyPopulated() && chunkEnvironmentSpawnData.isFullyPopulated(spawnedNPCs))) {
                WorldSpawnJobSystems.endProbing(Result.FAILED, spawnJobData, worldChunk, worldSpawnData);
                commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.spawnJobDataComponentType);
                return;
            }
            final SpawnSuppressionController spawnSuppressionController = entityStore.getResource(this.spawnSuppressionControllerResourceType);
            final Result result = WorldSpawnJobSystems.run(spawnJobData, worldChunk, chunkEnvironmentSpawnData, worldSpawnData, spawnSuppressionController);
            if (result == Result.SUCCESS) {
                chunkSpawnData.setLastSpawn(java.lang.System.nanoTime());
            }
            if (result != Result.TRY_AGAIN) {
                commandBuffer.removeComponent(archetypeChunk.getReferenceTo(index), this.spawnJobDataComponentType);
            }
        }
    }
    
    protected enum Result
    {
        SUCCESS, 
        FAILED, 
        TRY_AGAIN, 
        PERMANENT_FAILURE;
    }
}
