FloodFillPositionSelector class

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

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

implements: Component<EntityStore>

Поля (135)

МодификаторыТипИмя
DISABLED, IRREGULARITIES, ALL
final int MAX_SPAWN_POSITIONS_HINT
break
break
break
break
break
break
protected FloodFillPositionSelector.WeightedPosition[] buffer
continue
continue
continue
private final Vector3i position
private final int validMovementModesMask
var1
StringBuilder var1
FloodFillPositionSelector var1
int var10
int[] var10
SpawnSuppressionController var10
int var10
var10
var10
var10
int var10
int var11
int var11
long var11
var11
var11
MovementMode[] var11
double var12
WorldChunk var12
int[] var13
ChunkSuppressionEntry var13
var13
var13
SpawnTestResult var13
double var14
int[] var14
Long2ObjectConcurrentHashMap var14
int[] var15
FloodFillPositionSelector.WeightedPosition var17
double var18
double var19
var19
Builder var19
var2
int var2
int var2
RoleSpawnParameters var20
int var21
var21
var21
double[] var21
FloodFillPositionSelector.WeightedPosition var22
boolean var22
ObjectArrayList var23
SpawnSuppressionController var24
long var25
int var25
var25
var25
ChunkSuppressionEntry var27
int var27
int var28
int var28
var28
int var29
long var29
var29
int var3
ISpawnable var3
int var3
var3
int var3
int var3
var3
var3
var3
int var3
StringBuilder var3
int var30
FloodFillPositionSelector.WeightedPosition[] var31
int var31
var31
int var32
int var33
var33
int var33
FloodFillPositionSelector.WeightedPosition var34
var34
int var34
var34
Vector3i var35
int var35
var35
int var4
var4
int var4
var4
int var5
var5
BitSet var5
var5
int var5
int var6
ObjectArrayList var6
int var6
int var6
int var6
int var6
RoleSpawnParameters[] var7
RoleSpawnParameters var7
int[] var7
int[] var7
int var7
int var7
int var7
ISpawnable var8
int var8
var8
int var8
int var8
int var8
IntSet var8
double[] var9
BitSet var9
int var9
var9
int var9
int var9
int var9
int var9
private double weight

Методы (77)

МодификаторыВозвратСигнатура
Debug Debug()
public SortBufferProviderpublic SortBufferProvider()
private WeightedPositionprivate WeightedPosition(int var1, int var2, int var3, int var4)
private int buildLowerResolutionMapint buildLowerResolutionMap(@Nonnull BitSet var1, int var2, @Nonnull BitSet var3, int var4)
public void buildPositionCachevoid buildPositionCache(@Nonnull Vector3d var1, @Nonnull FloodFillEntryPoolSimple var2)
private void findPositionsvoid findPositions(int var1, int var2)
private void floodFillvoid floodFill(int var1, int var2, int var3, int var4, int var5, @Nonnull FloodFillEntryPoolSimple var6)
for for(int var4 = 1; var4 <= 8; var4 *= 2)
for for(int var5 = 0; var5 < this.roleIndexes.length; var5++)
for for(int var1 = 1; var1 <= 8; var1 *= 2)
for for(int var4 : this.roleIndexes)
for for(int var8 = 2; var8 <= 8; var8 *= 2)
for for(int var18 : this.roleIndexes)
for for(int var26 = var3; var26 > 1; var26 /= 2)
for for(int var5 = 0; var5 < var2; var5++)
for for(int var6 = 0; var6 < var2; var6++)
for for(int var12 = 0; var12 < var6.length; var12++)
for for(int var4 = 0; var4 < this.size; var4++)
for for(int var5 = 0; var5 < this.size; var5++)
for for(int var4 = 0; var4 < var2; var4++)
for for(int var5 = 0; var5 < var2; var5++)
abstract for for(int var16 = 0; var16 < var6.size()
abstract for for(int var21 = 0; var21 < var29 && var21 < var6.size()
abstract for for(int var23 = 0; var23 < var29 && var23 < var6.size()
abstract for for(int var24 = var5.nextSetBit(0)
public void forceRebuildCachevoid forceRebuildCache()
public FloodFillPositionSelector.WeightedPosition[] getBufferpublic FloodFillPositionSelector.WeightedPosition[] getBuffer(int var1)
static ComponentType<EntityStore, FloodFillPositionSelector> getComponentTypeComponentType<EntityStore, FloodFillPositionSelector> getComponentType()
static int getPositionIndexint getPositionIndex(int var0, int var1, int var2)
private int getValidMovementModesMaskint getValidMovementModesMask(int var1, int var2, int var3, int var4, @Nullable ChunkSuppressionEntry var5, double[] var6, boolean var7)
public double getWeightpublic double getWeight()
public boolean hasPositionsForRoleboolean hasPositionsForRole(int var1)
public boolean hasValidMovementModeWeightsboolean hasValidMovementModeWeights(int var1, @Nonnull SpawningContext var2)
if if(this.debug != FloodFillPositionSelector.Debug.DISABLED)
if if(var8 == null)
if if(var9 == null)
if if(var22.weight == 0.0)
if if(var19 == 0.0)
if if(var19 < 0.0)
if if(var34 == null)
if if(this.chunk != null)
if if(this.desiredPositionCount > 0)
if if(this.chunk != null)
if if(var4 >= 0 && var4 < this.size && var5 >= 0 && var5 < this.size)
if if(this.heightGrid[var11] != -1)
if if(var12 == null)
if if(var2 > this.maxY)
if if(var2 < this.minY)
if if(var6 < this.desiredPositionCount)
if if(var21 != null)
if if(var11 != var29)
if if(var31 == 0)
if if(this.debug != FloodFillPositionSelector.Debug.DISABLED)
if if(var25 == var32)
if if(var11 != var29)
if if(var31 == 0)
if if(this.debug != FloodFillPositionSelector.Debug.DISABLED)
if if(var24 == Integer.MAX_VALUE)
if if(this.irregularCase || this.debug == FloodFillPositionSelector.Debug.ALL)
if if(var6[var12] != 0.0)
if if(var13 == SpawnTestResult.TEST_OK)
if if(this.spawningContext.ySpawn > this.maxY)
if if(this.spawningContext.ySpawn < this.lowestSpawnOverYRange)
if if(var6 == var3)
if if(var1 <= this.buffer.length)
public void initvoid init()
private int pickOpenSegmentint pickOpenSegment(int var1, int var2, @Nonnull BitSet var3, int var4)
public boolean prepareSpawnContextboolean prepareSpawnContext(@Nonnull Vector3d var1, int var2, int var3, @Nonnull SpawningContext var4, @Nonnull BeaconSpawnWrapper var5)
public void setCalculatePositionsAftervoid setCalculatePositionsAfter(double var1)
private int shiftIndexAwayFromWallint shiftIndexAwayFromWall(int var1)
public boolean shouldRebuildCacheboolean shouldRebuildCache()
switch switch(this.heightGrid[var6])
public boolean tickCalculatePositionsAfterboolean tickCalculatePositionsAfter(float var1)
while while(var21 != 0)
while while(var21 == 0)
static int xFromIndexint xFromIndex(int var0, int var1)
static int zFromIndexint zFromIndex(int var0, int var1)

Исходный код

Показать/скрыть
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;
      }
   }
}