From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Thu, 9 May 2019 18:26:06 -0500
Subject: [PATCH] Phantoms attracted to crystals and crystals shoot phantoms


diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
index 53ea8a6d90faf4f7f8fd0819be4499422bdd4cbe..6ba14f603b8ec69597c70677cc317f802d6afae9 100644
--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java
+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
@@ -99,6 +99,7 @@ public class DamageSource {
         return (new EntityDamageSourceIndirect("thrown", entity, entity1)).c();
     }
 
+    public static DamageSource indirectMagic(Entity entity, @Nullable Entity owner) { return c(entity, owner); } // Purpur - OBFHELPER
     public static DamageSource c(Entity entity, @Nullable Entity entity1) {
         return (new EntityDamageSourceIndirect("indirectMagic", entity, entity1)).setIgnoreArmor().setMagic();
     }
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 7f897bebdc1fe2b7ce9528db9a7dd0fc0cf6c008..5ac6b7759cf43dbbad1bcf74d5d86efd69883cf5 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -2246,8 +2246,8 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne
         return this.a(new ItemStack(imaterial), (float) i);
     }
 
-    @Nullable
-    public EntityItem a(ItemStack itemstack) {
+    @Nullable public EntityItem dropItem(ItemStack itemstack) { return this.a(itemstack); } // Purpur - OBFHELPER
+    @Nullable public EntityItem a(ItemStack itemstack) {
         return this.a(itemstack, 0.0F);
     }
 
diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderCrystal.java
index 9658d93615a51375204481cfe0a1fce6f105fd70..2c983e6a092464d1867fcbe875b2e1461ec8556b 100644
--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderCrystal.java
+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderCrystal.java
@@ -14,6 +14,7 @@ import net.minecraft.server.level.WorldServer;
 import net.minecraft.world.damagesource.DamageSource;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.EntityTypes;
+import net.minecraft.world.entity.monster.EntityPhantom;
 import net.minecraft.world.level.Explosion;
 import net.minecraft.world.level.IBlockAccess;
 import net.minecraft.world.level.World;
@@ -30,6 +31,12 @@ public class EntityEnderCrystal extends Entity {
     private static final DataWatcherObject<Optional<BlockPosition>> c = DataWatcher.a(EntityEnderCrystal.class, DataWatcherRegistry.m);
     private static final DataWatcherObject<Boolean> d = DataWatcher.a(EntityEnderCrystal.class, DataWatcherRegistry.i);
     public int b;
+    // Purpur start
+    private EntityPhantom targetPhantom;
+    private int phantomBeamTicks = 0;
+    private int phantomDamageCooldown = 0;
+    private int idleCooldown = 0;
+    // Purpur end
 
     public EntityEnderCrystal(EntityTypes<? extends EntityEnderCrystal> entitytypes, World world) {
         super(entitytypes, world);
@@ -68,7 +75,50 @@ public class EntityEnderCrystal extends Entity {
             }
         }
 
+        // Purpur start
+        if (world.purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) {
+            return; // on cooldown
+        }
+
+        if (targetPhantom == null) {
+            for (EntityPhantom phantom : world.getEntitiesInAABB(EntityPhantom.class, getBoundingBox().grow(world.purpurConfig.phantomAttackedByCrystalRadius))) {
+                if (phantom.hasLineOfSight(this)) {
+                    attackPhantom(phantom);
+                    break;
+                }
+            }
+        } else {
+            setBeamTarget(new BlockPosition(targetPhantom).add(0, -2, 0));
+            if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) {
+                phantomDamageCooldown--;
+                if (targetPhantom.hasLineOfSight(this)) {
+                    if (phantomDamageCooldown <= 0) {
+                        phantomDamageCooldown = 20;
+                        targetPhantom.damageEntity(DamageSource.indirectMagic(this, this), world.purpurConfig.phantomAttackedByCrystalDamage);
+                    }
+                } else {
+                    forgetPhantom(); // no longer in sight
+                }
+            } else {
+                forgetPhantom(); // attacked long enough
+            }
+        }
+    }
+
+    private void attackPhantom(EntityPhantom phantom) {
+        phantomDamageCooldown = 0;
+        phantomBeamTicks = 60;
+        targetPhantom = phantom;
+    }
+
+    private void forgetPhantom() {
+        targetPhantom = null;
+        setBeamTarget(null);
+        phantomBeamTicks = 0;
+        phantomDamageCooldown = 0;
+        idleCooldown = 60;
     }
+    // Purpur end
 
     @Override
     protected void saveData(NBTTagCompound nbttagcompound) {
diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java b/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java
index 42cf3fa42b73739182d26fbb524ee5b304c799b2..daf738e8f0987aa6ab200189d7a26d166918b8fc 100644
--- a/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java
+++ b/src/main/java/net/minecraft/world/entity/monster/EntityPhantom.java
@@ -12,6 +12,7 @@ import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.syncher.DataWatcher;
 import net.minecraft.network.syncher.DataWatcherObject;
 import net.minecraft.network.syncher.DataWatcherRegistry;
+import net.minecraft.server.level.WorldServer;
 import net.minecraft.sounds.SoundCategory;
 import net.minecraft.sounds.SoundEffect;
 import net.minecraft.sounds.SoundEffects;
@@ -36,18 +37,23 @@ import net.minecraft.world.entity.ai.control.EntityAIBodyControl;
 import net.minecraft.world.entity.ai.goal.PathfinderGoal;
 import net.minecraft.world.entity.ai.targeting.PathfinderTargetCondition;
 import net.minecraft.world.entity.animal.EntityCat;
+import net.minecraft.world.entity.boss.enderdragon.EntityEnderCrystal;
 import net.minecraft.world.entity.player.EntityHuman;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
 import net.minecraft.world.level.World;
 import net.minecraft.world.level.WorldAccess;
 import net.minecraft.world.level.levelgen.HeightMap;
+import net.minecraft.world.level.storage.loot.LootTableInfo;
 import net.minecraft.world.phys.Vec3D;
 
 public class EntityPhantom extends EntityFlying implements IMonster {
 
     private static final DataWatcherObject<Integer> b = DataWatcher.a(EntityPhantom.class, DataWatcherRegistry.b);
-    private Vec3D c;
-    private BlockPosition d;
-    private EntityPhantom.AttackPhase bo;
+    private Vec3D c; public void setHomeOffset(Vec3D offset) { this.c = offset; } public Vec3D getHomeOffset() { return this.c; } // Purpur - OBFHELPER
+    private BlockPosition d; public void setHome(BlockPosition home) { this.d = home; } public BlockPosition getHome() { return this.d; } // Purpur - OBFHELPER
+    private EntityPhantom.AttackPhase bo; public AttackPhase getAttackPhase() { return this.bo; } // Purpur - OBFHELPER
+    private Vec3D crystalPosition; // Purpur
 
     public EntityPhantom(EntityTypes<? extends EntityPhantom> entitytypes, World world) {
         super(entitytypes, world);
@@ -66,11 +72,37 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
     @Override
     protected void initPathfinder() {
-        this.goalSelector.a(1, new EntityPhantom.c());
-        this.goalSelector.a(2, new EntityPhantom.i());
-        this.goalSelector.a(3, new EntityPhantom.e());
-        this.targetSelector.a(1, new EntityPhantom.b());
+        // Purpur start
+        if (world.purpurConfig.phantomOrbitCrystalRadius > 0) {
+            this.goalSelector.a(1, new FindCrystalGoal(this));
+            this.goalSelector.a(2, new OrbitCrystalGoal(this));
+        }
+        this.goalSelector.a(3, new EntityPhantom.c()); // PickAttackGoal
+        this.goalSelector.a(4, new EntityPhantom.i()); // SweepAttackGoal
+        this.goalSelector.a(5, new EntityPhantom.e()); // OrbitPointGoal
+        this.targetSelector.a(1, new EntityPhantom.b()); // AttackPlayer Goal
+        // Purpur end
+    }
+
+    // Purpur start
+    @Override
+    protected LootTableInfo.Builder a(boolean wasRecentlyHit, DamageSource damagesource) { // dropLoot
+        boolean dropped = false;
+        if (killer == null && damagesource.getEntity() instanceof EntityEnderCrystal) {
+            if (random.nextInt(5) < 1) { // 1 out of 5 chance (20%)
+                dropped = dropItem(new ItemStack(Items.PHANTOM_MEMBRANE)) != null;
+            }
+        }
+        if (!dropped) {
+            return super.a(wasRecentlyHit, damagesource); // dropLoot
+        }
+        return new LootTableInfo.Builder((WorldServer) world);
+    }
+
+    public boolean isCirclingCrystal() {
+        return crystalPosition != null;
     }
+    // Purpur end
 
     @Override
     protected void initDatawatcher() {
@@ -235,6 +267,136 @@ public class EntityPhantom extends EntityFlying implements IMonster {
     public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
     // Paper end
 
+    // Purpur start
+    class FindCrystalGoal extends PathfinderGoal {
+        private final EntityPhantom phantom;
+        private EntityEnderCrystal crystal;
+        private java.util.Comparator<EntityEnderCrystal> comparator;
+
+        FindCrystalGoal(EntityPhantom phantom) {
+            this.phantom = phantom;
+            comparator = java.util.Comparator.comparingDouble(phantom::h);
+            this.a(EnumSet.of(PathfinderGoal.Type.LOOK));
+        }
+
+        @Override
+        public boolean a() { // shouldExecute
+            double range = maxTargetRange();
+            List<EntityEnderCrystal> crystals = world.getEntitiesInAABB(EntityEnderCrystal.class, phantom.getBoundingBox().grow(range));
+            if (crystals.isEmpty()) {
+                return false;
+            }
+            crystals.sort(comparator);
+            crystal = crystals.get(0);
+            if (phantom.getDistanceSquared(crystal) > range * range) {
+                crystal = null;
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public boolean b() { // shouldContinueExecuting
+            if (crystal == null || !crystal.isAlive()) {
+                return false;
+            }
+            double range = maxTargetRange();
+            return phantom.getDistanceSquared(crystal) <= (range * range) * 2;
+        }
+
+        @Override
+        public void c() { // startExecuting
+            phantom.crystalPosition = new Vec3D(crystal.locX(), crystal.locY() + (phantom.getRandom().nextInt(10) + 10), crystal.locZ());
+        }
+
+        @Override
+        public void d() { // resetTask
+            crystal = null;
+            phantom.crystalPosition = null;
+            super.d();
+        }
+
+        private double maxTargetRange() {
+            return phantom.world.purpurConfig.phantomOrbitCrystalRadius;
+        }
+
+        class DistanceComparator implements java.util.Comparator<Entity> {
+            private final Entity entity;
+
+            public DistanceComparator(Entity entity) {
+                this.entity = entity;
+            }
+
+            public int compare(Entity entity1, Entity entity2) {
+                return Double.compare(entity.getDistanceSquared(entity1), entity.getDistanceSquared(entity2));
+            }
+        }
+    }
+
+    class OrbitCrystalGoal extends PathfinderGoal {
+        private final EntityPhantom phantom;
+        private float offset;
+        private float radius;
+        private float verticalChange;
+        private float direction;
+
+        OrbitCrystalGoal(EntityPhantom phantom) {
+            this.phantom = phantom;
+            this.a(EnumSet.of(PathfinderGoal.Type.MOVE));
+        }
+
+        @Override
+        public boolean a() { // shouldExecute
+            return phantom.isCirclingCrystal();
+        }
+
+        @Override
+        public void c() { // startExecuting
+            this.radius = 5.0F + phantom.random.nextFloat() * 10.0F;
+            this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
+            this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F;
+            updateOffset();
+        }
+
+        @Override
+        public void e() { // tick
+            if (phantom.random.nextInt(350) == 0) {
+                this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
+            }
+            if (phantom.random.nextInt(250) == 0) {
+                ++this.radius;
+                if (this.radius > 15.0F) {
+                    this.radius = 5.0F;
+                    this.direction = -this.direction;
+                }
+            }
+            if (phantom.random.nextInt(450) == 0) {
+                this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F;
+                updateOffset();
+            }
+            if (phantom.getHomeOffset().c(phantom.locX(), phantom.locY(), phantom.locZ()) < 4.0D) {
+                updateOffset();
+            }
+            if (phantom.getHomeOffset().y < phantom.locY() && !phantom.world.isEmpty((new BlockPosition(phantom)).down(1))) {
+                this.verticalChange = Math.max(1.0F, this.verticalChange);
+                updateOffset();
+            }
+            if (phantom.getHomeOffset().y > phantom.locY() && !phantom.world.isEmpty((new BlockPosition(phantom)).up(1))) {
+                this.verticalChange = Math.min(-1.0F, this.verticalChange);
+                updateOffset();
+            }
+        }
+
+        private void updateOffset() {
+            this.offset += this.direction * 15.0F * 0.017453292F;
+            phantom.setHomeOffset(phantom.crystalPosition.add(
+                    this.radius * MathHelper.cos(this.offset),
+                    -4.0F + this.verticalChange,
+                    this.radius * MathHelper.sin(this.offset)));
+        }
+    }
+    // Purpur end
+
     class b extends PathfinderGoal {
 
         private final PathfinderTargetCondition b;
@@ -247,6 +409,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
         @Override
         public boolean a() {
+            if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
             if (this.c > 0) {
                 --this.c;
                 return false;
@@ -275,6 +438,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
         @Override
         public boolean b() {
+            if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
             EntityLiving entityliving = EntityPhantom.this.getGoalTarget();
 
             return entityliving != null ? EntityPhantom.this.a(entityliving, PathfinderTargetCondition.a) : false;
@@ -289,6 +453,7 @@ public class EntityPhantom extends EntityFlying implements IMonster {
 
         @Override
         public boolean a() {
+            if (isCirclingCrystal()) return false; // Purpur - pathfinder does not have a flag
             EntityLiving entityliving = EntityPhantom.this.getGoalTarget();
 
             return entityliving != null ? EntityPhantom.this.a(EntityPhantom.this.getGoalTarget(), PathfinderTargetCondition.a) : false;
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
index 1ebc8d04020aec7e36dfed5196dde9e9fe6ada0f..d5e1ca888c6fe0910f54e30b3b59af4dfde86cb3 100644
--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
@@ -349,6 +349,15 @@ public class PurpurWorldConfig {
         ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
     }
 
+    public double phantomAttackedByCrystalRadius = 0.0D;
+    public float phantomAttackedByCrystalDamage = 1.0F;
+    public double phantomOrbitCrystalRadius = 0.0D;
+    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);
+    }
+
     public boolean pigGiveSaddleBack = false;
     private void pigSettings() {
         pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack);
