From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Fri, 3 Jul 2020 00:03:52 -0500
Subject: [PATCH] Add phantom spawning options


diff --git a/src/main/java/net/minecraft/server/DifficultyDamageScaler.java b/src/main/java/net/minecraft/server/DifficultyDamageScaler.java
index 75745af3435434bddd420536ca3bbea3a9f181d6..84c0ec1b50758b433980765d087e15d8a8eaaaf7 100644
--- a/src/main/java/net/minecraft/server/DifficultyDamageScaler.java
+++ b/src/main/java/net/minecraft/server/DifficultyDamageScaler.java
@@ -13,6 +13,7 @@ public class DifficultyDamageScaler {
         this.b = this.a(enumdifficulty, i, j, f);
     }
 
+    public EnumDifficulty getGlobalDifficulty() { return a(); } // Purpur - OBFHELPER
     public EnumDifficulty a() {
         return this.a;
     }
@@ -21,6 +22,7 @@ public class DifficultyDamageScaler {
         return this.b;
     }
 
+    public boolean isHarderThan(float f) { return a(f); } // Purpur - OBFHELPER
     public boolean a(float f) {
         return this.b > f;
     }
diff --git a/src/main/java/net/minecraft/server/EnumDifficulty.java b/src/main/java/net/minecraft/server/EnumDifficulty.java
index e0e72779c0f4e4856ed5682122fedf0321db11cb..386eae71a146e06ca60f8bf11429fe2131232a21 100644
--- a/src/main/java/net/minecraft/server/EnumDifficulty.java
+++ b/src/main/java/net/minecraft/server/EnumDifficulty.java
@@ -19,6 +19,7 @@ public enum EnumDifficulty {
         this.g = s;
     }
 
+    public int getId() { return a(); } // Purpur - OBFHELPER
     public int a() {
         return this.f;
     }
diff --git a/src/main/java/net/minecraft/server/IBlockLightAccess.java b/src/main/java/net/minecraft/server/IBlockLightAccess.java
index 03a89301f2b3d29e39c6c9d05ee072527f652efc..fa5eb188da64cc1409cc614515e8c70b0a341dd0 100644
--- a/src/main/java/net/minecraft/server/IBlockLightAccess.java
+++ b/src/main/java/net/minecraft/server/IBlockLightAccess.java
@@ -12,6 +12,7 @@ public interface IBlockLightAccess extends IBlockAccess {
         return this.e().b(blockposition, i);
     }
 
+    default boolean isSkyVisible(BlockPosition blockposition) { return e(blockposition); } // Purpur - OBFHELPER
     default boolean e(BlockPosition blockposition) {
         return this.getBrightness(EnumSkyBlock.SKY, blockposition) >= this.K();
     }
diff --git a/src/main/java/net/minecraft/server/MobSpawnerPhantom.java b/src/main/java/net/minecraft/server/MobSpawnerPhantom.java
index 4e3f01bc79b6ed2a322155f29f1d0dcf298c8b82..ac1ea2f0c15bccf94f203194a5a7b10ec125ffb8 100644
--- a/src/main/java/net/minecraft/server/MobSpawnerPhantom.java
+++ b/src/main/java/net/minecraft/server/MobSpawnerPhantom.java
@@ -1,85 +1,99 @@
 package net.minecraft.server;
 
-import java.util.Iterator;
-import java.util.Random;
+import com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent;
 
 public class MobSpawnerPhantom implements MobSpawner {
-
-    private int a;
+    private int spawnDelay;
 
     public MobSpawnerPhantom() {}
 
     @Override
-    public int a(WorldServer worldserver, boolean flag, boolean flag1) {
-        if (!flag) {
+    public int a(WorldServer world, boolean allowMonsters, boolean allowAnimals) {
+        // Purpur start - rewrite entire thing
+        if (!allowMonsters) {
+            return 0;
+        }
+
+        if (!world.getGameRules().getBoolean(GameRules.DO_INSOMNIA)) {
             return 0;
-        } else if (!worldserver.getGameRules().getBoolean(GameRules.DO_INSOMNIA)) {
+        }
+
+        --this.spawnDelay;
+        if (this.spawnDelay > 0) {
             return 0;
-        } else {
-            Random random = worldserver.random;
-
-            --this.a;
-            if (this.a > 0) {
-                return 0;
-            } else {
-                this.a += (60 + random.nextInt(60)) * 20;
-                if (worldserver.c() < 5 && worldserver.getDimensionManager().hasSkyLight()) {
-                    return 0;
-                } else {
-                    int i = 0;
-                    Iterator iterator = worldserver.getPlayers().iterator();
-
-                    while (iterator.hasNext()) {
-                        EntityHuman entityhuman = (EntityHuman) iterator.next();
-
-                        if (!entityhuman.isSpectator() && (!worldserver.paperConfig.phantomIgnoreCreative || !entityhuman.isCreative())) { // Paper
-                            BlockPosition blockposition = entityhuman.getChunkCoordinates();
-
-                            if (!worldserver.getDimensionManager().hasSkyLight() || blockposition.getY() >= worldserver.getSeaLevel() && worldserver.e(blockposition)) {
-                                DifficultyDamageScaler difficultydamagescaler = worldserver.getDamageScaler(blockposition);
-
-                                if (difficultydamagescaler.a(random.nextFloat() * 3.0F)) {
-                                    ServerStatisticManager serverstatisticmanager = ((EntityPlayer) entityhuman).getStatisticManager();
-                                    int j = MathHelper.clamp(serverstatisticmanager.getStatisticValue(StatisticList.CUSTOM.b(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
-                                    boolean flag2 = true;
-
-                                    if (random.nextInt(j) >= 72000) {
-                                        BlockPosition blockposition1 = blockposition.up(20 + random.nextInt(15)).east(-10 + random.nextInt(21)).south(-10 + random.nextInt(21));
-                                        IBlockData iblockdata = worldserver.getType(blockposition1);
-                                        Fluid fluid = worldserver.getFluid(blockposition1);
-
-                                        if (SpawnerCreature.a((IBlockAccess) worldserver, blockposition1, iblockdata, fluid, EntityTypes.PHANTOM)) {
-                                            GroupDataEntity groupdataentity = null;
-                                            int k = 1 + random.nextInt(difficultydamagescaler.a().a() + 1);
-
-                                            for (int l = 0; l < k; ++l) {
-                                                // Paper start
-                                                com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent event = new com.destroystokyo.paper.event.entity.PhantomPreSpawnEvent(MCUtil.toLocation(worldserver, blockposition1), ((EntityPlayer) entityhuman).getBukkitEntity(), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL);
-                                                if (!event.callEvent()) {
-                                                    if (event.shouldAbortSpawn()) {
-                                                        break;
-                                                    }
-                                                    continue;
-                                                }
-                                                // Paper end
-                                                EntityPhantom entityphantom = (EntityPhantom) EntityTypes.PHANTOM.a((World) worldserver);
-                                                entityphantom.spawningEntity = entityhuman.uniqueID; // Paper
-                                                entityphantom.setPositionRotation(blockposition1, 0.0F, 0.0F);
-                                                groupdataentity = entityphantom.prepare(worldserver, difficultydamagescaler, EnumMobSpawn.NATURAL, groupdataentity, (NBTTagCompound) null);
-                                                worldserver.addAllEntities(entityphantom, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
-                                            }
-
-                                            i += k;
-                                        }
-                                    }
-                                }
-                            }
-                        }
+        }
+        this.spawnDelay += world.purpurConfig.phantomSpawnDelayMin + world.random.nextInt(world.purpurConfig.phantomSpawnDelayMax - world.purpurConfig.phantomSpawnDelayMin);
+
+        if (!world.getDimensionManager().hasSkyLight() || world.getSkyDarkness() < world.purpurConfig.phantomSpawnMinSkyDarkness) {
+            return 0;
+        }
+
+        int numberSpawnsAttempted = 0;
+        for (EntityPlayer player : world.getPlayers()) {
+            if (player.isSpectator() || (player.isCreative() && world.paperConfig.phantomIgnoreCreative)) { // Paper
+                continue;
+            }
+
+            BlockPosition playerPos = player.getChunkCoordinates();
+            if (playerPos.getY() < world.getSeaLevel() && world.purpurConfig.phantomSpawnOnlyAboveSeaLevel) {
+                continue;
+            }
+
+            if (!world.isSkyVisible(playerPos) && world.purpurConfig.phantomSpawnOnlyWithVisibleSky) {
+                continue;
+            }
+
+            DifficultyDamageScaler dmgScaler = world.getDamageScaler(playerPos);
+            if (!dmgScaler.isHarderThan(world.random.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) {
+                continue;
+            }
+
+            ServerStatisticManager stats = player.getStatisticManager();
+            int timeSinceRest = MathHelper.clamp(stats.getStatisticValue(StatisticList.CUSTOM.get(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
+            if (world.random.nextInt(timeSinceRest) < world.purpurConfig.phantomSpawnMinTimeSinceSlept) {
+                continue;
+            }
+
+            BlockPosition spawnPos = playerPos
+                    .up(world.purpurConfig.phantomSpawnMinOverhead + world.random.nextInt(world.purpurConfig.phantomSpawnMaxOverhead - world.purpurConfig.phantomSpawnMinOverhead))
+                    .east(-world.purpurConfig.phantomSpawnOverheadRadius + world.random.nextInt(world.purpurConfig.phantomSpawnOverheadRadius + 1))
+                    .south(-world.purpurConfig.phantomSpawnOverheadRadius + world.random.nextInt(world.purpurConfig.phantomSpawnOverheadRadius + 1));
+
+            if (!SpawnerCreature.canSpawn(world, spawnPos, world.getType(spawnPos), world.getFluid(spawnPos), EntityTypes.PHANTOM)) {
+                continue;
+            }
+
+            int difficulty = dmgScaler.getGlobalDifficulty().getId();
+            int spawnAttempts = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficulty : world.purpurConfig.phantomSpawnMaxPerAttempt) + world.purpurConfig.phantomSpawnMinPerAttempt);
+
+            GroupDataEntity groupData = null;
+            for (int count = 0; count < spawnAttempts; ++count) {
+                // Paper start
+                PhantomPreSpawnEvent event = new PhantomPreSpawnEvent(MCUtil.toLocation(world, spawnPos), player.getBukkitEntity(), CreatureSpawnEvent.SpawnReason.NATURAL);
+                if (!event.callEvent()) {
+                    if (event.shouldAbortSpawn()) {
+                        break;
                     }
+                    continue;
+                }
+                // Paper end
 
-                    return i;
+                EntityPhantom phantom = EntityTypes.PHANTOM.create(world);
+                if (phantom == null) {
+                    continue;
                 }
+
+                phantom.spawningEntity = player.uniqueID; // Paper
+                phantom.setPositionRotation(spawnPos, 0.0F, 0.0F);
+                groupData = phantom.prepare(world, dmgScaler, EnumMobSpawn.NATURAL, groupData, null);
+                world.addEntity(phantom, CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
             }
+
+            numberSpawnsAttempted += spawnAttempts;
         }
+
+        return numberSpawnsAttempted;
+        // Purpur end - rewrite entire thing
     }
 }
diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java
index a77b1d61b9dcb0a2a27d7e50357eaf44c99a661e..ae17950438132c5084210252bd345517131d5a99 100644
--- a/src/main/java/net/minecraft/server/SpawnerCreature.java
+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
@@ -385,6 +385,7 @@ public final class SpawnerCreature {
         return new BlockPosition(i, l, j);
     }
 
+    public static boolean canSpawn(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, EntityTypes entitytypes) { return a(iblockaccess, blockposition, iblockdata, fluid, entitytypes); } // Purpur - OBFHELPER
     public static boolean a(IBlockAccess iblockaccess, BlockPosition blockposition, IBlockData iblockdata, Fluid fluid, EntityTypes<?> entitytypes) {
         return iblockdata.r(iblockaccess, blockposition) ? false : (iblockdata.isPowerSource() ? false : (!fluid.isEmpty() ? false : (iblockdata.a((Tag) TagsBlock.PREVENT_MOB_SPAWNING_INSIDE) ? false : !entitytypes.a(iblockdata))));
     }
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index ee1d5e2dc9fb21190a73e0f333e962a19264dd56..8246bdf5e686c2089397e8669cf37968ef3de80d 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -1597,6 +1597,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
         return new DifficultyDamageScaler(this.getDifficulty(), this.getDayTime(), i, f);
     }
 
+    public int getSkyDarkness() { return c(); } // Purpur - OBFHELPER
     @Override
     public int c() {
         return this.d;
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
index 88cac6394f67ec113a781a552261f760fc95eb16..fedc39bdbbd328296c5c08d220d6695fe08fdf91 100644
--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
@@ -352,10 +352,34 @@ public class PurpurWorldConfig {
     public double phantomAttackedByCrystalRadius = 0.0D;
     public float phantomAttackedByCrystalDamage = 1.0F;
     public double phantomOrbitCrystalRadius = 0.0D;
+    public int phantomSpawnDelayMin = 1200;
+    public int phantomSpawnDelayMax = 2400;
+    public int phantomSpawnMinSkyDarkness = 5;
+    public boolean phantomSpawnOnlyAboveSeaLevel = true;
+    public boolean phantomSpawnOnlyWithVisibleSky = true;
+    public double phantomSpawnLocalDifficultyChance = 3.0D;
+    public int phantomSpawnMinTimeSinceSlept = 72000;
+    public int phantomSpawnMinOverhead = 20;
+    public int phantomSpawnMaxOverhead = 35;
+    public int phantomSpawnOverheadRadius = 10;
+    public int phantomSpawnMinPerAttempt = 1;
+    public int phantomSpawnMaxPerAttempt = -1;
     private void phantomSettings() {
         phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius);
         phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage);
         phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius);
+        phantomSpawnDelayMin = getInt("mobs.phantom.spawn.delay.min", phantomSpawnDelayMin);
+        phantomSpawnDelayMax = getInt("mobs.phantom.spawn.delay.max", phantomSpawnDelayMax);
+        phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness);
+        phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel);
+        phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky);
+        phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance);
+        phantomSpawnMinTimeSinceSlept = getInt("mobs.phantom.spawn.min-time-since-slept", phantomSpawnMinTimeSinceSlept);
+        phantomSpawnMinOverhead = getInt("mobs.phantom.spawn.overhead.min", phantomSpawnMinOverhead);
+        phantomSpawnMaxOverhead = getInt("mobs.phantom.spawn.overhead.max", phantomSpawnMaxOverhead);
+        phantomSpawnOverheadRadius = getInt("mobs.phantom.spawn.overhead.radius", phantomSpawnOverheadRadius);
+        phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt);
+        phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt);
     }
 
     public boolean pigGiveSaddleBack = false;
