package net.minecraft.server; import com.google.common.collect.Sets; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; // CraftBukkit start import com.destroystokyo.paper.exception.ServerInternalException; import org.bukkit.craftbukkit.util.LongHash; import org.bukkit.craftbukkit.util.LongHashSet; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; // CraftBukkit end public final class SpawnerCreature { private static final int a = (int) Math.pow(17.0D, 2.0D); private final LongHashSet b = new LongHashSet(); // CraftBukkit public SpawnerCreature() {} // Spigot start - get entity count only from chunks being processed in b private int getEntityCount(WorldServer server, Class oClass) { // Paper start - use entire world, not just active chunks. Spigot broke vanilla expectations. if (true) { int sum = 0; for (Chunk c : server.getChunkProviderServer().chunks.values()) { sum += c.entityCount.get(oClass); } return sum; } // Paper end int i = 0; Iterator it = this.b.iterator(); while ( it.hasNext() ) { Long coord = it.next(); int x = LongHash.msw( coord ); int z = LongHash.lsw( coord ); if ( !((ChunkProviderServer)server.chunkProvider).unloadQueue.contains( coord ) && server.isChunkLoaded( x, z, true ) ) { i += server.getChunkAt( x, z ).entityCount.get( oClass ); } } return i; } // Spigot end public int a(WorldServer worldserver, boolean flag, boolean flag1, boolean flag2) { org.spigotmc.AsyncCatcher.catchOp("check for eligible spawn chunks"); // Paper - At least until we figure out what is calling this async if (!flag && !flag1) { return 0; } else { this.b.clear(); int i = 0; Iterator iterator = worldserver.players.iterator(); int j; int k; while (iterator.hasNext()) { EntityHuman entityhuman = (EntityHuman) iterator.next(); if (!entityhuman.isSpectator() && entityhuman.affectsSpawning) { int l = MathHelper.floor(entityhuman.locX / 16.0D); j = MathHelper.floor(entityhuman.locZ / 16.0D); boolean flag3 = true; // Spigot Start byte b0 = worldserver.spigotConfig.mobSpawnRange; b0 = ( b0 > worldserver.spigotConfig.viewDistance ) ? (byte) worldserver.spigotConfig.viewDistance : b0; b0 = ( b0 > 8 ) ? 8 : b0; // Paper start com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent( (org.bukkit.entity.Player) entityhuman.getBukkitEntity(), b0); if (!event.callEvent()) { continue; } b0 = event.getSpawnRadius(); // Paperr end for (int i1 = -b0; i1 <= b0; ++i1) { for (k = -b0; k <= b0; ++k) { boolean flag4 = i1 == -b0 || i1 == b0 || k == -b0 || k == b0; // Spigot End ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i1 + l, k + j); // CraftBukkit start - use LongHash and LongHashSet long chunkCoords = LongHash.toLong(chunkcoordintpair.x, chunkcoordintpair.z); if (!this.b.contains(chunkCoords)) { ++i; if (!flag4 && worldserver.isChunkLoaded(chunkcoordintpair.x, chunkcoordintpair.z, true) && worldserver.getWorldBorder().isInBounds(chunkcoordintpair)) { PlayerChunk playerchunk = worldserver.getPlayerChunkMap().getChunk(chunkcoordintpair.x, chunkcoordintpair.z); if (playerchunk != null && playerchunk.e()) { this.b.add(chunkCoords); // CraftBukkit end } } } } } } } int j1 = 0; BlockPosition blockposition = worldserver.getSpawn(); EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values(); j = aenumcreaturetype.length; for (int k1 = 0; k1 < j; ++k1) { EnumCreatureType enumcreaturetype = aenumcreaturetype[k1]; // CraftBukkit start - Use per-world spawn limits int limit = enumcreaturetype.b(); switch (enumcreaturetype) { case MONSTER: limit = worldserver.getWorld().getMonsterSpawnLimit(); break; case CREATURE: limit = worldserver.getWorld().getAnimalSpawnLimit(); break; case WATER_CREATURE: limit = worldserver.getWorld().getWaterAnimalSpawnLimit(); break; case AMBIENT: limit = worldserver.getWorld().getAmbientSpawnLimit(); break; } if (limit == 0) { continue; } int mobcnt = 0; // Spigot // CraftBukkit end if ((!enumcreaturetype.d() || flag1) && (enumcreaturetype.d() || flag) && (!enumcreaturetype.e() || flag2)) { /* Paper start - As far as I can tell neither of these are even used k = worldserver.a(enumcreaturetype.a()); int l1 = limit * i / a; // CraftBukkit - use per-world limits */ // Paper end if ((mobcnt = getEntityCount(worldserver, enumcreaturetype.a())) <= limit * i / 289) { // Paper - use 17x17 like vanilla (a at top of file) BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(); Iterator iterator1 = this.b.iterator(); int moblimit = (limit * i / 256) - mobcnt + 1; // Spigot - up to 1 more than limit label120: while (iterator1.hasNext() && (moblimit > 0)) { // Spigot - while more allowed // CraftBukkit start = use LongHash and LongObjectHashMap long key = ((Long) iterator1.next()).longValue(); BlockPosition blockposition1 = getRandomPosition(worldserver, LongHash.msw(key), LongHash.lsw(key)); // CraftBukkit int i2 = blockposition1.getX(); int j2 = blockposition1.getY(); int k2 = blockposition1.getZ(); IBlockData iblockdata = worldserver.getWorldBorder().isInBounds(blockposition1) ? worldserver.getTypeIfLoaded(blockposition1) : null; // Paper if (iblockdata != null && !iblockdata.l()) { // Paper int l2 = 0; int i3 = 0; while (i3 < 3) { int j3 = i2; int k3 = j2; int l3 = k2; boolean flag5 = true; BiomeBase.BiomeMeta biomebase_biomemeta = null; GroupDataEntity groupdataentity = null; int i4 = MathHelper.f(Math.random() * 4.0D); int j4 = 0; while (true) { if (j4 < i4) { label113: { j3 += worldserver.random.nextInt(6) - worldserver.random.nextInt(6); k3 += worldserver.random.nextInt(1) - worldserver.random.nextInt(1); l3 += worldserver.random.nextInt(6) - worldserver.random.nextInt(6); blockposition_mutableblockposition.c(j3, k3, l3); float f = (float) j3 + 0.5F; float f1 = (float) l3 + 0.5F; if (worldserver.getWorldBorder().isInBounds(blockposition_mutableblockposition) && worldserver.getChunkIfLoaded(blockposition_mutableblockposition) != null && !worldserver.isPlayerNearby((double) f, (double) k3, (double) f1, 24.0D) && blockposition.distanceSquared((double) f, (double) k3, (double) f1) >= 576.0D) { // Paper - Prevent mob spawning from loading/generating chunks if (biomebase_biomemeta == null) { biomebase_biomemeta = worldserver.a(enumcreaturetype, (BlockPosition) blockposition_mutableblockposition); if (biomebase_biomemeta == null) { break label113; } } if (worldserver.a(enumcreaturetype, biomebase_biomemeta, (BlockPosition) blockposition_mutableblockposition) && a(EntityPositionTypes.a(biomebase_biomemeta.b), worldserver, blockposition_mutableblockposition)) { // Paper start com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event; Class cls = biomebase_biomemeta.b; org.bukkit.entity.EntityType type = EntityTypes.clsToTypeMap.get(cls); if (type != null) { event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( MCUtil.toLocation(worldserver, blockposition_mutableblockposition), type, SpawnReason.NATURAL ); if (!event.callEvent()) { if (event.shouldAbortSpawn()) { continue label120; } j1 += l2; ++j4; continue; } } // Paper end EntityInsentient entityinsentient; try { entityinsentient = (EntityInsentient) biomebase_biomemeta.b.getConstructor(new Class[] { World.class}).newInstance(new Object[] { worldserver}); } catch (Exception exception) { exception.printStackTrace(); ServerInternalException.reportInternalException(exception); // Paper return j1; } entityinsentient.setPositionRotation((double) f, (double) k3, (double) f1, worldserver.random.nextFloat() * 360.0F, 0.0F); if (entityinsentient.P() && entityinsentient.canSpawn()) { groupdataentity = entityinsentient.prepare(worldserver.D(new BlockPosition(entityinsentient)), groupdataentity); if (entityinsentient.canSpawn()) { // CraftBukkit start if (worldserver.addEntity(entityinsentient, SpawnReason.NATURAL)) { ++l2; moblimit--; // Spigot } // CraftBukkit end } else { entityinsentient.die(); } // Spigot start if ( moblimit <= 0 ) { // If we're past limit, stop spawn // Spigot end continue label120; } } j1 += l2; } } ++j4; continue; } } ++i3; break; } } } } } } } return j1; } } private static BlockPosition getRandomPosition(World world, int i, int j) { Chunk chunk = world.getChunkAt(i, j); int k = i * 16 + world.random.nextInt(16); int l = j * 16 + world.random.nextInt(16); int i1 = MathHelper.c(chunk.e(new BlockPosition(k, 0, l)) + 1, 16); int j1 = world.random.nextInt(i1 > 0 ? i1 : chunk.g() + 16 - 1); return new BlockPosition(k, j1, l); } public static boolean a(IBlockData iblockdata) { return iblockdata.k() ? false : (iblockdata.m() ? false : (iblockdata.getMaterial().isLiquid() ? false : !BlockMinecartTrackAbstract.i(iblockdata))); } public static boolean a(EntityInsentient.EnumEntityPositionType entityinsentient_enumentitypositiontype, World world, BlockPosition blockposition) { if (!world.getWorldBorder().a(blockposition)) { return false; } else { IBlockData iblockdata = world.getType(blockposition); if (entityinsentient_enumentitypositiontype == EntityInsentient.EnumEntityPositionType.IN_WATER) { return iblockdata.getMaterial() == Material.WATER && world.getType(blockposition.down()).getMaterial() == Material.WATER && !world.getType(blockposition.up()).l(); } else { BlockPosition blockposition1 = blockposition.down(); if (!world.getType(blockposition1).q()) { return false; } else { Block block = world.getType(blockposition1).getBlock(); boolean flag = block != Blocks.BEDROCK && block != Blocks.BARRIER; return flag && a(iblockdata) && a(world.getType(blockposition.up())); } } } } public static void a(World world, BiomeBase biomebase, int i, int j, int k, int l, Random random) { List list = biomebase.getMobs(EnumCreatureType.CREATURE); if (!list.isEmpty()) { while (random.nextFloat() < biomebase.f()) { BiomeBase.BiomeMeta biomebase_biomemeta = (BiomeBase.BiomeMeta) WeightedRandom.a(world.random, list); int i1 = biomebase_biomemeta.c + random.nextInt(1 + biomebase_biomemeta.d - biomebase_biomemeta.c); GroupDataEntity groupdataentity = null; int j1 = i + random.nextInt(k); int k1 = j + random.nextInt(l); int l1 = j1; int i2 = k1; for (int j2 = 0; j2 < i1; ++j2) { boolean flag = false; for (int k2 = 0; !flag && k2 < 4; ++k2) { BlockPosition blockposition = world.q(new BlockPosition(j1, 0, k1)); if (a(EntityInsentient.EnumEntityPositionType.ON_GROUND, world, blockposition)) { EntityInsentient entityinsentient; try { entityinsentient = (EntityInsentient) biomebase_biomemeta.b.getConstructor(new Class[] { World.class}).newInstance(new Object[] { world}); } catch (Exception exception) { exception.printStackTrace(); ServerInternalException.reportInternalException(exception); // Paper continue; } entityinsentient.setPositionRotation((double) ((float) j1 + 0.5F), (double) blockposition.getY(), (double) ((float) k1 + 0.5F), random.nextFloat() * 360.0F, 0.0F); // CraftBukkit start - Added a reason for spawning this creature, moved entityinsentient.prepare(groupdataentity) up groupdataentity = entityinsentient.prepare(world.D(new BlockPosition(entityinsentient)), groupdataentity); world.addEntity(entityinsentient, SpawnReason.CHUNK_GEN); // CraftBukkit end flag = true; } j1 += random.nextInt(5) - random.nextInt(5); for (k1 += random.nextInt(5) - random.nextInt(5); j1 < i || j1 >= i + k || k1 < j || k1 >= j + k; k1 = i2 + random.nextInt(5) - random.nextInt(5)) { j1 = l1 + random.nextInt(5) - random.nextInt(5); } } } } } } }