From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Fri, 25 Jun 2021 19:48:21 -0500
Subject: [PATCH] Shulker spawn from bullet options

(0 - 1) / 5.0 = -0.2  (can never happen because self is included in count)
(1 - 1) / 5.0 = 0.0    1.0 - 0.0 = 1.0    100% (just self)
(2 - 1) / 5.0 = 0.2    1.0 - 0.2 = 0.8     80% (1 other shulker)
(3 - 1) / 5.0 = 0.4    1.0 - 0.4 = 0.6     60% (2 other shulkers)
(4 - 1) / 5.0 = 0.6    1.0 - 0.6 = 0.4     40% (3 other shulkers)
(5 - 1) / 5.0 = 0.8    1.0 - 0.8 = 0.2     20% (4 other shulkers)
(6 - 1) / 5.0 = 1.0    1.0 - 1.0 = 0.0      0% (5 other shulkers)
(7 - 1) / 5.0 = 1.2    1.0 - 1.2 = -0.2     0% (6 other shulkers)

diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
index d6fdaa9f9033d8b1e8aa9ef0aace387e286ebce9..03da91ee39851e8d066b8c63dce849e665c59479 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
@@ -496,13 +496,22 @@ public class Shulker extends AbstractGolem implements Enemy {
         Vec3 vec3d = this.position();
         AABB axisalignedbb = this.getBoundingBox();
 
-        if (!this.isClosed() && this.teleportSomewhere()) {
-            int i = this.level.getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(8.0D), Entity::isAlive).size();
-            float f = (float) (i - 1) / 5.0F;
-
-            if (this.level.random.nextFloat() >= f) {
+        if ((!this.level.purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) {
+            // Purpur start
+            float chance = this.level.purpurConfig.shulkerSpawnFromBulletBaseChance;
+            if (!this.level.purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) {
+                int nearby = this.level.getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level.purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size();
+                try {
+                    scriptEngine.eval("nearby = " + nearby);
+                    chance -= (float) scriptEngine.eval(this.level.purpurConfig.shulkerSpawnFromBulletNearbyEquation);
+                } catch (Exception ignore) {
+                    chance -= (float) (nearby - 1) / 5.0F;
+                }
+            }
+            if (this.level.random.nextFloat() <= chance) {
+                // Purpur end
                 Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level);
-                DyeColor enumcolor = this.getColor();
+                DyeColor enumcolor = this.level.purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level.random) : this.getColor(); // Purpur
 
                 if (enumcolor != null) {
                     entityshulker.setColor(enumcolor);
diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java
index f812a75985d26785639491c9a980387a3f261f2d..b11fb39b69f5225ca7da72ca1a2200c7eaf7e873 100644
--- a/src/main/java/net/minecraft/world/item/DyeColor.java
+++ b/src/main/java/net/minecraft/world/item/DyeColor.java
@@ -109,4 +109,10 @@ public enum DyeColor implements StringRepresentable {
     public String getSerializedName() {
         return this.name;
     }
+
+    // Purpur start
+    public static DyeColor random(java.util.Random random) {
+        return BY_ID[random.nextInt(BY_ID.length)];
+    }
+    // Purpur end
 }
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
index 398be3312551c17dce4ae5bd7a898e4d36a040c2..35a0a270fad348a2f60ae3f9a480f1a527f525ed 100644
--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
@@ -1717,6 +1717,11 @@ public class PurpurWorldConfig {
     public boolean shulkerRidable = false;
     public boolean shulkerRidableInWater = false;
     public double shulkerMaxHealth = 30.0D;
+    public float shulkerSpawnFromBulletBaseChance = 1.0F;
+    public boolean shulkerSpawnFromBulletRequireOpenLid = true;
+    public double shulkerSpawnFromBulletNearbyRange = 8.0D;
+    public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0";
+    public boolean shulkerSpawnFromBulletRandomColor = false;
     private void shulkerSettings() {
         shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable);
         shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater);
@@ -1726,6 +1731,11 @@ public class PurpurWorldConfig {
             set("mobs.shulker.attributes.max_health", oldValue);
         }
         shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth);
+        shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance);
+        shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid);
+        shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange);
+        shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation);
+        shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor);
     }
 
     public boolean silverfishRidable = false;
