Debug enum

Пакет: com.hypixel.hytale.server.spawning.util

Файл: com/hypixel/hytale/server/spawning/util/FloodFillPositionSelector.java

Поля (1)

МодификаторыТипИмя
DISABLED, IRREGULARITIES, ALL

Исходный код

Показать/скрыть
class="kw">package com.hypixel.hytale.server.spawning.util;

class="kw">import com.hypixel.fastutil.longs.Long2ObjectConcurrentHashMap;
class="kw">import com.hypixel.hytale.component.Component;
class="kw">import com.hypixel.hytale.component.ComponentType;
class="kw">import com.hypixel.hytale.math.random.RandomExtra;
class="kw">import com.hypixel.hytale.math.util.ChunkUtil;
class="kw">import com.hypixel.hytale.math.util.MathUtil;
class="kw">import com.hypixel.hytale.protocol.BlockMaterial;
class="kw">import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
class="kw">import com.hypixel.hytale.server.core.universe.world.World;
class="kw">import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor;
class="kw">import com.hypixel.hytale.server.core.universe.world.accessor.LocalCachedChunkAccessor;
class="kw">import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
class="kw">import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
class="kw">import com.hypixel.hytale.server.npc.NPCPlugin;
class="kw">import com.hypixel.hytale.server.npc.asset.builder.Builder;
class="kw">import com.hypixel.hytale.server.npc.movement.MovementMode;
class="kw">import com.hypixel.hytale.server.npc.role.Role;
class="kw">import com.hypixel.hytale.server.spawning.ISpawnable;
class="kw">import com.hypixel.hytale.server.spawning.ISpawnableWithModel;
class="kw">import com.hypixel.hytale.server.spawning.SpawnTestResult;
class="kw">import com.hypixel.hytale.server.spawning.SpawningContext;
class="kw">import com.hypixel.hytale.server.spawning.SpawningPlugin;
class="kw">import com.hypixel.hytale.server.spawning.assets.spawns.config.RoleSpawnParameters;
class="kw">import com.hypixel.hytale.server.spawning.suppression.component.ChunkSuppressionEntry;
class="kw">import com.hypixel.hytale.server.spawning.suppression.component.SpawnSuppressionController;
class="kw">import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper;
class="kw">import it.unimi.dsi.fastutil.ints.Int2IntMap;
class="kw">import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
class="kw">import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
class="kw">import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
class="kw">import it.unimi.dsi.fastutil.ints.IntArrayList;
class="kw">import it.unimi.dsi.fastutil.ints.IntLists;
class="kw">import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
class="kw">import it.unimi.dsi.fastutil.ints.IntSet;
class="kw">import it.unimi.dsi.fastutil.objects.ObjectArrayList;
class="kw">import it.unimi.dsi.fastutil.objects.ObjectArrays;
class="kw">import java.util.ArrayDeque;
class="kw">import java.util.Arrays;
class="kw">import java.util.BitSet;
class="kw">import java.util.Comparator;
class="kw">import java.util.Deque;
class="kw">import java.util.concurrent.ThreadLocalRandom;
class="kw">import java.util.logging.Level;
class="kw">import javax.annotation.Nonnull;
class="kw">import javax.annotation.Nullable;
class="kw">import org.joml.Vector3d;
class="kw">import org.joml.Vector3i;

class="kw">public class FloodFillPositionSelector class="kw">implements Component<EntityStore> {
   class="kw">private class="kw">static class="kw">final int MAX_SPAWN_POSITIONS_HINT = 30;
   class="kw">private class="kw">static class="kw">final double SPAWN_POSITION_DENSITY = 0.1;
   class="kw">private class="kw">static class="kw">final int CONCURRENT_POSITION_OPTION_MULTIPLIER = 3;
   class="kw">private class="kw">static class="kw">final double MAX_FAILED_SPAWN_POSITION_RATIO = 0.25;
   class="kw">private class="kw">static class="kw">final double IRREGULAR_MIN_SPAWNS_MULTIPLIER = 0.3;
   class="kw">private class="kw">static class="kw">final double IRREGULAR_MAX_SPAWNS_MULTIPLIER = 5.0;
   class="kw">private class="kw">static class="kw">final int NOT_CHECKED = -1;
   class="kw">private class="kw">static class="kw">final int BLOCKED = -2;
   class="kw">private class="kw">static class="kw">final int TOO_HIGH = Integer.MAX_VALUE;
   class="kw">private class="kw">static class="kw">final int TOO_LOW = Integer.MIN_VALUE;
   class="kw">private class="kw">static class="kw">final int MAX_RESOLUTION_DIVISOR = 8;
   class="kw">private class="kw">static class="kw">final ThreadLocal<FloodFillPositionSelector.SortBufferProvider> sortBufferProvider = ThreadLocal.withInitial(
      FloodFillPositionSelector.SortBufferProvider::new
   );
   class="kw">private class="kw">static class="kw">final Comparator<Object> WEIGHTED_POSITION_COMPARATOR = (var0, var1) -> Double.compare(
      ((FloodFillPositionSelector.WeightedPosition)var1).getWeight(), ((FloodFillPositionSelector.WeightedPosition)var0).getWeight()
   );
   class="kw">private class="kw">final World world;
   @Nullable
   class="kw">private ChunkAccessor chunkAccessor;
   class="kw">private class="kw">final int size;
   class="kw">private int beaconY;
   class="kw">private int minY;
   class="kw">private int maxY;
   @Nonnull
   class="kw">private class="kw">final BeaconSpawnWrapper spawnWrapper;
   @Nonnull
   class="kw">private class="kw">final int[] roleIndexes;
   @Nonnull
   class="kw">private class="kw">final int[] heightGrid;
   @Nonnull
   class="kw">private class="kw">final Int2ObjectMap<BitSet> resolutionMaps;
   class="kw">private class="kw">final BitSet fullResolutionMap;
   class="kw">private int desiredPositionCount;
   class="kw">private class="kw">final IntArrayList highResolutionOptions = new IntArrayList(4);
   class="kw">private class="kw">final Deque<int[]> floodFillQueue = new ArrayDeque<>();
   class="kw">private class="kw">final SpawningContext spawningContext = new SpawningContext();
   @Nullable
   class="kw">private WorldChunk chunk;
   class="kw">private class="kw">final IntSet positionIndexes = new IntOpenHashSet();
   class="kw">private class="kw">final Int2ObjectMap<ObjectArrayList<FloodFillPositionSelector.WeightedPosition>> positionsByRole = new Int2ObjectOpenHashMap();
   class="kw">private class="kw">final Int2IntMap failedSpawnsByRole = new Int2IntOpenHashMap();
   class="kw">private double lowestSpawnOverYRange;
   @Nullable
   class="kw">private MovementMode lowestSpawnOverYRangeMode;
   class="kw">private boolean hasRun;
   class="kw">private FloodFillPositionSelector.Debug debug;
   class="kw">private boolean irregularCase;
   class="kw">private IntSet failedPositionTestIndexes;
   class="kw">private double calculatePositionsAfter;

   class="kw">public class="kw">static ComponentType<EntityStore, FloodFillPositionSelector> getComponentType() {
      class="kw">return SpawningPlugin.get().getFloodFillPositionSelectorComponentType();
   }

   class="kw">public FloodFillPositionSelector(World var1, @Nonnull BeaconSpawnWrapper var2) {
      this.world = var1;
      int var3 = MathUtil.ceil(var2.getSpawnRadius()) * 2;
      this.size = (var3 + 8 - 1) / 8 * 8;
      this.heightGrid = new int[this.size * this.size];
      Arrays.fill(this.heightGrid, -1);
      this.resolutionMaps = new Int2ObjectOpenHashMap();

      for (int var4 = 1; var4 <= 8; var4 *= 2) {
         this.resolutionMaps.put(var4, new BitSet(this.heightGrid.length / var4));
      }

      this.fullResolutionMap = (BitSet)this.resolutionMaps.get(1);
      this.spawnWrapper = var2;
      RoleSpawnParameters[] var7 = var2.getSpawn().getNPCs();
      this.roleIndexes = new int[var7.length];

      for (int var5 = 0; var5 < this.roleIndexes.length; var5++) {
         int var6 = NPCPlugin.get().getIndex(var7[var5].getId());
         this.roleIndexes[var5] = var6;
         this.positionsByRole.put(var6, new ObjectArrayList());
      }

      this.debug = var2.getSpawn().getDebug();
      if (this.debug != FloodFillPositionSelector.Debug.DISABLED) {
         this.failedPositionTestIndexes = new IntOpenHashSet();
      }
   }

   class="kw">public void setCalculatePositionsAfter(double var1) {
      this.calculatePositionsAfter = var1;
   }

   class="kw">public boolean tickCalculatePositionsAfter(float var1) {
      class="kw">return (this.calculatePositionsAfter -= var1) <= 0.0;
   }

   class="kw">public boolean hasPositionsForRole(int var1) {
      class="kw">return !((ObjectArrayList)this.positionsByRole.get(var1)).isEmpty();
   }

   class="kw">public boolean hasValidMovementModeWeights(int var1, @Nonnull SpawningContext var2) {
      ISpawnable var3 = (ISpawnable)NPCPlugin.get().tryGetCachedValidRole(var1);
      class="kw">return var3 == null ? true : ((RoleSpawnParameters)this.spawnWrapper.getRoles().get(var1)).getOrComputeMovementModeWeights(var3, var2) != null;
   }

   class="kw">public boolean prepareSpawnContext(@Nonnull Vector3d var1, int var2, int var3, @Nonnull SpawningContext var4, @Nonnull BeaconSpawnWrapper var5) {
      ObjectArrayList var6 = (ObjectArrayList<FloodFillPositionSelector.WeightedPosition>)this.positionsByRole.get(var3);
      if (var6.isEmpty()) {
         class="kw">return false;
      }

      RoleSpawnParameters var7 = (RoleSpawnParameters)var5.getRoles().get(var3);
      ISpawnable var8 = (ISpawnable)NPCPlugin.get().tryGetCachedValidRole(var3);
      if (var8 == null) {
         class="kw">return false;
      }

      double[] var9 = var7.getOrComputeMovementModeWeights(var8, var4);
      if (var9 == null) {
         class="kw">return false;
      }

      int var10 = RandomExtra.pickWeightedIndex(var9);
      int var11 = 1 << var10;
      if (!var4.setMovementMode(MovementMode.values()[var10], var5.getEnableSafeSpawning(var3))) {
         class="kw">return false;
      }

      double var12 = var5.getMinDistanceFromPlayerSquared();
      double var14 = var5.getTargetDistanceFromPlayerSquared();

      for (int var16 = 0; var16 < var6.size(); var16++) {
         FloodFillPositionSelector.WeightedPosition var17 = (FloodFillPositionSelector.WeightedPosition)var6.get(var16);
         if ((var17.validMovementModesMask & var11) == 0) {
            var17.weight = 0.0;
         } else {
            double var18 = var1.distanceSquared(var17.position.x, var17.position.y, var17.position.z);
            var17.weight = var18 < var12 ? 0.0 : Math.max(0.0, var14 - Math.abs(var18 - var14));
         }
      }

      int var29 = var2 * 3;
      int var30 = var6.size();
      FloodFillPositionSelector.WeightedPosition[] var31 = sortBufferProvider.get().getBuffer(var30);
      System.arraycopy(var6.elements(), 0, var31, 0, var30);
      ObjectArrays.mergeSort(var6.elements(), 0, var30, WEIGHTED_POSITION_COMPARATOR, var31);
      double var19 = 0.0;

      for (int var21 = 0; var21 < var29 && var21 < var6.size(); var21++) {
         FloodFillPositionSelector.WeightedPosition var22 = (FloodFillPositionSelector.WeightedPosition)var6.get(var21);
         if (var22.weight == 0.0) {
            break;
         }

         var19 += var22.weight;
      }

      if (var19 == 0.0) {
         class="kw">return false;
      }

      var19 = ThreadLocalRandom.current().nextDouble(var19);
      int var33 = -1;
      FloodFillPositionSelector.WeightedPosition var34 = null;

      for (int var23 = 0; var23 < var29 && var23 < var6.size(); var23++) {
         var34 = (FloodFillPositionSelector.WeightedPosition)var6.get(var23);
         var33 = var23;
         var19 -= var34.weight;
         if (var19 < 0.0) {
            break;
         }
      }

      if (var34 == null) {
         class="kw">return false;
      }

      Vector3i var35 = var34.position;
      SpawnSuppressionController var24 = this.world.getEntityStore().getStore().getResource(SpawnSuppressionController.getResourceType());
      long var25 = ChunkUtil.indexChunk(ChunkUtil.chunkCoordinate(var35.x), ChunkUtil.chunkCoordinate(var35.z));
      ChunkSuppressionEntry var27 = var24.getChunkSuppressionMap().get(var25);
      if ((var27 == null || !var27.isSuppressingRoleAt(var3, var35.y))
         && var4.set(this.world, var35.x, var35.y, var35.z)
         && var4.canSpawn() == SpawnTestResult.TEST_OK) {
         class="kw">return true;
      }

      var6.remove(var33);
      int var28 = this.failedSpawnsByRole.mergeInt(var3, 1, Integer::sum);
      if (var28 > var6.size() * 0.25) {
         this.hasRun = false;
      }

      class="kw">return false;
   }

   class="kw">public boolean shouldRebuildCache() {
      class="kw">return !this.hasRun;
   }

   class="kw">public void forceRebuildCache() {
      this.hasRun = false;
   }

   class="kw">public void init() {
      Arrays.fill(this.heightGrid, -1);

      for (int var1 = 1; var1 <= 8; var1 *= 2) {
         ((BitSet)this.resolutionMaps.get(var1)).clear();
      }

      for (int var4 : this.roleIndexes) {
         ((ObjectArrayList)this.positionsByRole.get(var4)).clear();
         this.failedSpawnsByRole.put(var4, 0);
      }

      this.irregularCase = false;
   }

   class="kw">public void buildPositionCache(@Nonnull Vector3d var1, @Nonnull FloodFillEntryPoolSimple var2) {
      int var3 = this.size / 2;
      int var4 = MathUtil.floor(var1.x());
      int var5 = MathUtil.floor(var1.y());
      int var6 = MathUtil.floor(var1.z());
      this.chunkAccessor = LocalCachedChunkAccessor.atWorldCoords(this.world, var4, var6, var3);
      this.chunk = this.chunkAccessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(var4, var6));
      if (this.chunk != null) {
         int[] var7 = this.spawnWrapper.getSpawn().getYRange();
         this.beaconY = var5;
         this.minY = Math.max(0, var5 + var7[0]);
         this.maxY = Math.min(319, var5 + var7[1]);
         this.floodFill(var4, var5, var6, var3, var3, var2);
         this.desiredPositionCount = Math.min(MathUtil.ceil(this.fullResolutionMap.cardinality() * 0.1), 30);
         if (this.desiredPositionCount > 0) {
            this.findPositions(var4, var6);
         } else {
            SpawningPlugin.get().getLogger().at(Level.WARNING).log("Spawn beacon at: %s unable to find any suitable positions to check", var1);
         }

         if (this.debug != FloodFillPositionSelector.Debug.DISABLED && (this.irregularCase || this.debug == FloodFillPositionSelector.Debug.ALL)) {
            for (int var8 = 2; var8 <= 8; var8 *= 2) {
               BitSet var9 = (BitSet)this.resolutionMaps.get(var8);
               if (var9.cardinality() > 0) {
                  SpawningPlugin.get().getLogger().at(Level.WARNING).log("%s", this.debugDumpLowResolutionMap(var9, this.size / var8));
               }
            }

            SpawningPlugin.get().getLogger().at(Level.WARNING).log("Spawn beacon at: %s%s", var1, this.irregularCase ? " is an irregular case" : "");
         }

         this.chunkAccessor = null;
         this.chunk = null;
         this.spawningContext.releaseFull();
         this.hasRun = true;
      }
   }

   class="kw">private void floodFill(int var1, int var2, int var3, int var4, int var5, @Nonnull FloodFillEntryPoolSimple var6) {
      if (this.chunk != null) {
         this.floodFillQueue.clear();
         int[] var7 = var6.allocate();
         var7[0] = var1;
         var7[1] = var2;
         var7[2] = var3;
         var7[3] = var4;
         var7[4] = var5;
         this.floodFillQueue.add(var7);
         int var8 = this.chunk.getX();
         int var9 = this.chunk.getZ();

         label66:
         while (!this.floodFillQueue.isEmpty()) {
            int[] var10 = this.floodFillQueue.poll();
            var1 = var10[0];
            var2 = var10[1];
            var3 = var10[2];
            var4 = var10[3];
            var5 = var10[4];
            if (var4 >= 0 && var4 < this.size && var5 >= 0 && var5 < this.size) {
               int var11 = getPositionIndex(var4, var5, this.size);
               if (this.heightGrid[var11] != -1) {
                  var6.deallocate(var10);
               } else {
                  if (!ChunkUtil.isInsideChunk(var8, var9, var1, var3)) {
                     WorldChunk var12 = this.chunkAccessor.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(var1, var3));
                     if (var12 == null) {
                        this.heightGrid[var11] = -2;
                        var6.deallocate(var10);
                        class="kw">continue;
                     }

                     this.chunk = var12;
                     var8 = this.chunk.getX();
                     var9 = this.chunk.getZ();
                  }

                  int var21 = this.chunk.getBlock(var1, var2, var3);
                  if (var21 != 0 && BlockType.getAssetMap().getAsset(var21).getMaterial() == BlockMaterial.Solid) {
                     while (var21 != 0) {
                        var21 = this.chunk.getBlock(var1, ++var2, var3);
                        if (var2 > this.maxY) {
                           this.heightGrid[var11] = Integer.MAX_VALUE;
                           var6.deallocate(var10);
                           class="kw">continue label66;
                        }
                     }
                  } else {
                     while (var21 == 0) {
                        var21 = this.chunk.getBlock(var1, --var2, var3);
                        if (var2 < this.minY) {
                           this.heightGrid[var11] = Integer.MIN_VALUE;
                           var6.deallocate(var10);
                           class="kw">continue label66;
                        }
                     }

                     var2++;
                  }

                  this.heightGrid[var11] = var2;
                  this.fullResolutionMap.set(var11);
                  var10[0] = var1 + 1;
                  var10[1] = var2;
                  var10[2] = var3;
                  var10[3] = var4 + 1;
                  var10[4] = var5;
                  this.floodFillQueue.add(var10);
                  int[] var13 = var6.allocate();
                  var13[0] = var1 - 1;
                  var13[1] = var2;
                  var13[2] = var3;
                  var13[3] = var4 - 1;
                  var13[4] = var5;
                  this.floodFillQueue.add(var13);
                  int[] var14 = var6.allocate();
                  var14[0] = var1;
                  var14[1] = var2;
                  var14[2] = var3 + 1;
                  var14[3] = var4;
                  var14[4] = var5 + 1;
                  this.floodFillQueue.add(var14);
                  int[] var15 = var6.allocate();
                  var15[0] = var1;
                  var15[1] = var2;
                  var15[2] = var3 - 1;
                  var15[3] = var4;
                  var15[4] = var5 - 1;
                  this.floodFillQueue.add(var15);
               }
            } else {
               var6.deallocate(var10);
            }
         }
      }
   }

   class="kw">private void findPositions(int var1, int var2) {
      int var3 = 1;

      int var4;
      do {
         var3 *= 2;
         var4 = this.buildLowerResolutionMap(
            (BitSet)this.resolutionMaps.get(var3), this.size / var3, (BitSet)this.resolutionMaps.get(var3 / 2), this.size / (var3 / 2)
         );
      } while (var3 < 8 && var4 / this.desiredPositionCount > 1);

      BitSet var5 = (BitSet)this.resolutionMaps.get(var3);
      int var6 = var5.cardinality();
      if (var6 < this.desiredPositionCount) {
         var3 /= 2;
         var5 = (BitSet)this.resolutionMaps.get(var3);
      }

      int var7 = this.size / 2;
      int var8 = var1 - var7;
      int var9 = var2 - var7;
      SpawnSuppressionController var10 = this.world.getEntityStore().getStore().getResource(SpawnSuppressionController.getResourceType());
      long var11 = ChunkUtil.NOT_FOUND;
      ChunkSuppressionEntry var13 = null;
      Long2ObjectConcurrentHashMap var14 = var10.getChunkSuppressionMap();

      for (int var18 : this.roleIndexes) {
         Builder var19 = NPCPlugin.get().tryGetCachedValidRole(var18);
         if (var19 != null
            && var19.isSpawnable()
            && var19 class="kw">instanceof ISpawnableWithModel
            && this.spawningContext.setSpawnable((ISpawnableWithModel)var19, true)) {
            RoleSpawnParameters var20 = (RoleSpawnParameters)this.spawnWrapper.getRoles().get(var18);
            double[] var21 = var20.getOrComputeMovementModeWeights((ISpawnable)var19, this.spawningContext);
            if (var21 != null) {
               boolean var22 = this.spawnWrapper.getEnableSafeSpawning(var18);
               ObjectArrayList var23 = (ObjectArrayList<FloodFillPositionSelector.WeightedPosition>)this.positionsByRole.get(var18);
               this.lowestSpawnOverYRange = Double.MAX_VALUE;
               this.lowestSpawnOverYRangeMode = null;

               for (int var24 = var5.nextSetBit(0); var24 >= 0; var24 = var5.nextSetBit(var24 + 1)) {
                  int var25 = var24;

                  for (int var26 = var3; var26 > 1; var26 /= 2) {
                     int var27 = var26 / 2;
                     var25 = this.pickOpenSegment(var25, this.size / var26, (BitSet)this.resolutionMaps.get(var27), this.size / var27);
                  }

                  int var34 = var8 + xFromIndex(var25, this.size);
                  int var35 = this.heightGrid[var25];
                  int var28 = var9 + zFromIndex(var25, this.size);
                  long var29 = ChunkUtil.indexChunk(ChunkUtil.chunkCoordinate(var34), ChunkUtil.chunkCoordinate(var28));
                  if (var11 != var29) {
                     var13 = var14.get(var29);
                     var11 = var29;
                  }

                  int var31 = this.getValidMovementModesMask(var34, var35, var28, var18, var13, var21, var22);
                  if (var31 == 0) {
                     if (this.debug != FloodFillPositionSelector.Debug.DISABLED) {
                        this.failedPositionTestIndexes.add(var25);
                     }

                     int var32 = var25;
                     var25 = this.shiftIndexAwayFromWall(var25);
                     if (var25 == var32) {
                        class="kw">continue;
                     }

                     var34 = var8 + xFromIndex(var25, this.size);
                     var35 = this.heightGrid[var25];
                     var28 = var9 + zFromIndex(var25, this.size);
                     var29 = ChunkUtil.indexChunk(ChunkUtil.chunkCoordinate(var34), ChunkUtil.chunkCoordinate(var28));
                     if (var11 != var29) {
                        var13 = var14.get(var29);
                        var11 = var29;
                     }

                     var31 = this.getValidMovementModesMask(var34, var35, var28, var18, var13, var21, var22);
                     if (var31 == 0) {
                        if (this.debug != FloodFillPositionSelector.Debug.DISABLED) {
                           this.failedPositionTestIndexes.add(var25);
                        }
                        class="kw">continue;
                     }
                  }

                  if (this.positionIndexes.add(var25)) {
                     var23.add(new FloodFillPositionSelector.WeightedPosition(var34, var35, var28, var31));
                  }

                  if (var24 == Integer.MAX_VALUE) {
                     break;
                  }
               }

               if (var23.isEmpty() && this.lowestSpawnOverYRangeMode != null && var20.claimSpawnOverYRangeReport()) {
                  SpawningPlugin.get()
                     .getLogger()
                     .at(Level.WARNING)
                     .log(
                        "Spawn beacon %s found no position for role '%s'. Movement mode %s needs a YRange maximum of %d over the beacon. The asset allows %d. Raise YRange in the beacon asset.",
                        this.spawnWrapper.getSpawn().getId(),
                        NPCPlugin.get().getName(var18),
                        this.lowestSpawnOverYRangeMode,
                        MathUtil.ceil(this.lowestSpawnOverYRange) - this.beaconY,
                        this.maxY - this.beaconY
                     );
               }

               int var33 = this.positionIndexes.size();
               if (this.debug != FloodFillPositionSelector.Debug.DISABLED
                  && (var33 < this.desiredPositionCount * 0.3 || var33 > this.desiredPositionCount * 5.0)) {
                  this.irregularCase = true;
               }

               if (this.irregularCase || this.debug == FloodFillPositionSelector.Debug.ALL) {
                  SpawningPlugin.get().getLogger().at(Level.WARNING).log("Role: %s", NPCPlugin.get().getName(var18));
                  SpawningPlugin.get().getLogger().at(Level.WARNING).log("%s", this.debugDumpBaseFloodFill());
                  this.failedPositionTestIndexes.clear();
               }

               this.positionIndexes.clear();
            }
         }
      }
   }

   class="kw">private int buildLowerResolutionMap(@Nonnull BitSet var1, int var2, @Nonnull BitSet var3, int var4) {
      for (int var5 = 0; var5 < var2; var5++) {
         for (int var6 = 0; var6 < var2; var6++) {
            int var7 = var5 * 2;
            int var8 = var6 * 2;
            int var9 = getPositionIndex(var7, var8, var4);
            if (var3.get(var9) || var3.get(var9 + 1) || var3.get(var9 + var4) || var3.get(var9 + var4 + 1)) {
               var1.set(getPositionIndex(var5, var6, var2));
            }
         }
      }

      class="kw">return var1.cardinality();
   }

   class="kw">private int pickOpenSegment(int var1, int var2, @Nonnull BitSet var3, int var4) {
      int var5 = xFromIndex(var1, var2);
      int var6 = zFromIndex(var1, var2);
      int var7 = var5 * 2;
      int var8 = var6 * 2;
      int var9 = getPositionIndex(var7, var8, var4);
      if (var3.get(var9)) {
         this.highResolutionOptions.add(var9);
      }

      int var10 = var9 + 1;
      if (var3.get(var10)) {
         this.highResolutionOptions.add(var10);
      }

      var10 = var9 + var4;
      if (var3.get(var10)) {
         this.highResolutionOptions.add(var10);
      }

      var10 = var9 + var4 + 1;
      if (var3.get(var10)) {
         this.highResolutionOptions.add(var10);
      }

      if (this.highResolutionOptions.size() > 1) {
         IntLists.shuffle(this.highResolutionOptions, ThreadLocalRandom.current());
      }

      var10 = this.highResolutionOptions.getInt(0);
      this.highResolutionOptions.clear();
      class="kw">return var10;
   }

   class="kw">private int shiftIndexAwayFromWall(int var1) {
      int var2 = var1;
      int var3 = var1 - 1;
      if (var3 < 0 || !this.fullResolutionMap.get(var3)) {
         var2++;
      }

      var3 = var1 + 1;
      if (var3 >= this.heightGrid.length || !this.fullResolutionMap.get(var3)) {
         var2--;
      }

      var3 = var1 - this.size;
      if (var3 < 0 || !this.fullResolutionMap.get(var3)) {
         var2 += this.size;
      }

      var3 = var1 + this.size;
      if (var3 >= this.heightGrid.length || !this.fullResolutionMap.get(var3)) {
         var2 -= this.size;
      }

      class="kw">return var2 >= 0 && var2 < this.heightGrid.length && this.fullResolutionMap.get(var2) ? var2 : var1;
   }

   class="kw">private int getValidMovementModesMask(int var1, int var2, int var3, int var4, @Nullable ChunkSuppressionEntry var5, double[] var6, boolean var7) {
      if (!this.spawnWrapper.getSpawn().isOverrideSpawnSuppressors() && var5 != null && var5.isSuppressingRoleAt(var4, var2)) {
         class="kw">return 0;
      }

      if (!this.spawningContext.set(this.world, var1, var2, var3)) {
         class="kw">return 0;
      }

      if (!this.spawnWrapper.withinLightRange(this.spawningContext)) {
         class="kw">return 0;
      }

      IntSet var8 = this.spawnWrapper.getSpawnBlockSet(var4);
      int var9 = this.spawnWrapper.getSpawnFluidTag(var4);
      if (!this.spawningContext.canSpawnOnBlock(var8, var9)) {
         class="kw">return 0;
      }

      int var10 = 0;
      MovementMode[] var11 = MovementMode.values();

      for (int var12 = 0; var12 < var6.length; var12++) {
         if (var6[var12] != 0.0) {
            this.spawningContext.invalidMaterials = -1;
            if (this.spawningContext.setMovementMode(var11[var12], var7)) {
               SpawnTestResult var13 = this.spawningContext.canSpawn();
               if (var13 == SpawnTestResult.TEST_OK) {
                  if (this.spawningContext.ySpawn > this.maxY) {
                     if (this.spawningContext.ySpawn < this.lowestSpawnOverYRange) {
                        this.lowestSpawnOverYRange = this.spawningContext.ySpawn;
                        this.lowestSpawnOverYRangeMode = var11[var12];
                     }
                  } else {
                     var10 |= 1 << var12;
                  }
               }
            }
         }
      }

      class="kw">return var10;
   }

   @Nonnull
   class="kw">private String debugDumpBaseFloodFill() {
      StringBuilder var1 = new StringBuilder();
      int var2 = this.size / 2;
      int var3 = getPositionIndex(var2, var2, this.size);

      for (int var4 = 0; var4 < this.size; var4++) {
         for (int var5 = 0; var5 < this.size; var5++) {
            int var6 = getPositionIndex(var5, var4, this.size);
            if (var6 == var3) {
               var1.append('B');
            } else if (this.positionIndexes.contains(var6)) {
               var1.append('S');
            } else if (this.failedPositionTestIndexes.contains(var6)) {
               var1.append('F');
            } else {
               class="kw">switch (this.heightGrid[var6]) {
                  case Integer.MIN_VALUE:
                     var1.append('v');
                     break;
                  case -2:
                  case -1:
                     var1.append('X');
                     break;
                  case Integer.MAX_VALUE:
                     var1.append('^');
                     break;
                  class="kw">default:
                     var1.append('.');
               }
            }

            var1.append(' ');
         }

         var1.append('\n');
      }

      class="kw">return var1.toString();
   }

   @Nonnull
   class="kw">private String debugDumpLowResolutionMap(@Nonnull BitSet var1, int var2) {
      StringBuilder var3 = new StringBuilder();

      for (int var4 = 0; var4 < var2; var4++) {
         for (int var5 = 0; var5 < var2; var5++) {
            var3.append((char)(var1.get(getPositionIndex(var5, var4, var2)) ? '.' : 'X')).append(' ');
         }

         var3.append('\n');
      }

      class="kw">return var3.toString();
   }

   class="kw">public class="kw">static int getPositionIndex(int var0, int var1, int var2) {
      class="kw">return var0 * var2 + var1;
   }

   class="kw">public class="kw">static int xFromIndex(int var0, int var1) {
      class="kw">return var0 / var1;
   }

   class="kw">public class="kw">static int zFromIndex(int var0, int var1) {
      class="kw">return var0 % var1;
   }

   @Nonnull
   @Override
   class="kw">public Component<EntityStore> clone() {
      FloodFillPositionSelector var1 = new FloodFillPositionSelector(this.world, this.spawnWrapper);
      var1.init();
      class="kw">return var1;
   }

   class="kw">public enum Debug {
      DISABLED,
      IRREGULARITIES,
      ALL;

      Debug() {
      }
   }

   class="kw">public class="kw">static class SortBufferProvider {
      class="kw">protected FloodFillPositionSelector.WeightedPosition[] buffer = new FloodFillPositionSelector.WeightedPosition[10];

      class="kw">public SortBufferProvider() {
      }

      class="kw">public FloodFillPositionSelector.WeightedPosition[] getBuffer(int var1) {
         if (var1 <= this.buffer.length) {
            class="kw">return this.buffer;
         }

         this.buffer = (FloodFillPositionSelector.WeightedPosition[])ObjectArrays.grow(this.buffer, var1);
         class="kw">return this.buffer;
      }
   }

   class="kw">private class="kw">static class WeightedPosition {
      @Nonnull
      class="kw">private class="kw">final Vector3i position;
      class="kw">private double weight;
      class="kw">private class="kw">final int validMovementModesMask;

      class="kw">private WeightedPosition(int var1, int var2, int var3, int var4) {
         this.position = new Vector3i(var1, var2, var3);
         this.validMovementModesMask = var4;
      }

      class="kw">public double getWeight() {
         class="kw">return this.weight;
      }
   }
}