From 814c742554e40554a30cee1915b42457081000c6 Mon Sep 17 00:00:00 2001
From: kaenganxt <kaenganxt@mc-anura.de>
Date: Fri, 20 Jul 2018 16:04:37 +1000
Subject: [PATCH] SPIGOT-840, SPIGOT-2522: [Draft] Add
 EntityPotionEffectChangeEvent

Discussion ongoing in PR #449
---
 nms-patches/CommandEffect.patch               |  29 +++
 nms-patches/EnchantmentWeaponDamage.patch     |  11 +
 nms-patches/EntityAreaEffectCloud.patch       |   9 +
 nms-patches/EntityCaveSpider.patch            |  11 +
 nms-patches/EntityDolphin.patch               |  20 ++
 nms-patches/EntityGuardianElder.patch         |  20 ++
 nms-patches/EntityHuman.patch                 |   9 +
 nms-patches/EntityIllagerIllusioner.patch     |  20 ++
 nms-patches/EntityLiving.patch                | 217 ++++++++++++++----
 nms-patches/EntityParrot.patch                |   9 +
 nms-patches/EntityPlayer.patch                |   2 +-
 nms-patches/EntityPotion.patch                |   2 +-
 nms-patches/EntityPufferFish.patch            |  29 +++
 nms-patches/EntityShulkerBullet.patch         |   9 +
 nms-patches/EntitySkeletonWither.patch        |   9 +
 nms-patches/EntitySpectralArrow.patch         |  11 +
 nms-patches/EntitySpider.patch                |   9 +
 nms-patches/EntityTippedArrow.patch           |  18 ++
 nms-patches/EntityVillager.patch              |   9 +
 nms-patches/EntityWitch.patch                 |  11 +
 nms-patches/EntityWitherSkull.patch           |   8 +-
 nms-patches/EntityZombieHusk.patch            |  11 +
 nms-patches/EntityZombieVillager.patch        |  24 +-
 nms-patches/ItemFish.patch                    |  17 ++
 nms-patches/ItemFood.patch                    |  11 +
 nms-patches/ItemGoldenApple.patch             |  15 ++
 nms-patches/ItemGoldenAppleEnchanted.patch    |  19 ++
 nms-patches/ItemMilkBucket.patch              |  11 +
 nms-patches/ItemPotion.patch                  |  11 +
 nms-patches/TileEntityBeacon.patch            |   2 +-
 nms-patches/TileEntityConduit.patch           |  31 +++
 .../craftbukkit/entity/CraftLivingEntity.java |   5 +-
 .../craftbukkit/event/CraftEventFactory.java  |  35 +++
 33 files changed, 617 insertions(+), 47 deletions(-)
 create mode 100644 nms-patches/CommandEffect.patch
 create mode 100644 nms-patches/EnchantmentWeaponDamage.patch
 create mode 100644 nms-patches/EntityCaveSpider.patch
 create mode 100644 nms-patches/EntityDolphin.patch
 create mode 100644 nms-patches/EntityGuardianElder.patch
 create mode 100644 nms-patches/EntityIllagerIllusioner.patch
 create mode 100644 nms-patches/EntityPufferFish.patch
 create mode 100644 nms-patches/EntitySpectralArrow.patch
 create mode 100644 nms-patches/EntityWitch.patch
 create mode 100644 nms-patches/EntityZombieHusk.patch
 create mode 100644 nms-patches/ItemFish.patch
 create mode 100644 nms-patches/ItemFood.patch
 create mode 100644 nms-patches/ItemGoldenApple.patch
 create mode 100644 nms-patches/ItemGoldenAppleEnchanted.patch
 create mode 100644 nms-patches/ItemMilkBucket.patch
 create mode 100644 nms-patches/ItemPotion.patch
 create mode 100644 nms-patches/TileEntityConduit.patch

diff --git a/nms-patches/CommandEffect.patch b/nms-patches/CommandEffect.patch
new file mode 100644
index 0000000000..42f5524389
--- /dev/null
+++ b/nms-patches/CommandEffect.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/server/CommandEffect.java
++++ b/net/minecraft/server/CommandEffect.java
+@@ -62,7 +62,7 @@
+             if (entity instanceof EntityLiving) {
+                 MobEffect mobeffect = new MobEffect(mobeffectlist, k, i, false, flag);
+ 
+-                if (((EntityLiving) entity).addEffect(mobeffect)) {
++                if (((EntityLiving) entity).addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+                     ++j;
+                 }
+             }
+@@ -88,7 +88,7 @@
+         while (iterator.hasNext()) {
+             Entity entity = (Entity) iterator.next();
+ 
+-            if (entity instanceof EntityLiving && ((EntityLiving) entity).removeAllEffects()) {
++            if (entity instanceof EntityLiving && ((EntityLiving) entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+                 ++i;
+             }
+         }
+@@ -113,7 +113,7 @@
+         while (iterator.hasNext()) {
+             Entity entity = (Entity) iterator.next();
+ 
+-            if (entity instanceof EntityLiving && ((EntityLiving) entity).removeEffect(mobeffectlist)) {
++            if (entity instanceof EntityLiving && ((EntityLiving) entity).removeEffect(mobeffectlist, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+                 ++i;
+             }
+         }
diff --git a/nms-patches/EnchantmentWeaponDamage.patch b/nms-patches/EnchantmentWeaponDamage.patch
new file mode 100644
index 0000000000..c6cfb66e39
--- /dev/null
+++ b/nms-patches/EnchantmentWeaponDamage.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/EnchantmentWeaponDamage.java
++++ b/net/minecraft/server/EnchantmentWeaponDamage.java
+@@ -44,7 +44,7 @@
+             if (this.a == 2 && entityliving1.getMonsterType() == EnumMonsterType.ARTHROPOD) {
+                 int j = 20 + entityliving.getRandom().nextInt(10 * i);
+ 
+-                entityliving1.addEffect(new MobEffect(MobEffects.SLOWER_MOVEMENT, j, 3));
++                entityliving1.addEffect(new MobEffect(MobEffects.SLOWER_MOVEMENT, j, 3), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+             }
+         }
+ 
diff --git a/nms-patches/EntityAreaEffectCloud.patch b/nms-patches/EntityAreaEffectCloud.patch
index ab14a3ca41..1e00f0ad63 100644
--- a/nms-patches/EntityAreaEffectCloud.patch
+++ b/nms-patches/EntityAreaEffectCloud.patch
@@ -60,3 +60,12 @@
                                      this.h.put(entityliving, Integer.valueOf(this.ticksLived + this.reapplicationDelay));
                                      Iterator iterator3 = arraylist.iterator();
  
+@@ -263,7 +295,7 @@
+                                         if (mobeffect1.getMobEffect().isInstant()) {
+                                             mobeffect1.getMobEffect().applyInstantEffect(this, this.getSource(), entityliving, mobeffect1.getAmplifier(), 0.5D);
+                                         } else {
+-                                            entityliving.addEffect(new MobEffect(mobeffect1));
++                                            entityliving.addEffect(new MobEffect(mobeffect1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AREA_EFFECT_CLOUD); // CraftBukkit
+                                         }
+                                     }
+ 
diff --git a/nms-patches/EntityCaveSpider.patch b/nms-patches/EntityCaveSpider.patch
new file mode 100644
index 0000000000..cc15841b7b
--- /dev/null
+++ b/nms-patches/EntityCaveSpider.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/EntityCaveSpider.java
++++ b/net/minecraft/server/EntityCaveSpider.java
+@@ -26,7 +26,7 @@
+                 }
+ 
+                 if (b0 > 0) {
+-                    ((EntityLiving) entity).addEffect(new MobEffect(MobEffects.POISON, b0 * 20, 0));
++                    ((EntityLiving) entity).addEffect(new MobEffect(MobEffects.POISON, b0 * 20, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                 }
+             }
+ 
diff --git a/nms-patches/EntityDolphin.patch b/nms-patches/EntityDolphin.patch
new file mode 100644
index 0000000000..fbb632900d
--- /dev/null
+++ b/nms-patches/EntityDolphin.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/server/EntityDolphin.java
++++ b/net/minecraft/server/EntityDolphin.java
+@@ -412,7 +412,7 @@
+         }
+ 
+         public void c() {
+-            this.c.addEffect(new MobEffect(MobEffects.DOLPHINS_GRACE, 100));
++            this.c.addEffect(new MobEffect(MobEffects.DOLPHINS_GRACE, 100), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
+         }
+ 
+         public void d() {
+@@ -429,7 +429,7 @@
+             }
+ 
+             if (this.c.bb() && this.c.world.random.nextInt(6) == 0) {
+-                this.c.addEffect(new MobEffect(MobEffects.DOLPHINS_GRACE, 100));
++                this.c.addEffect(new MobEffect(MobEffects.DOLPHINS_GRACE, 100), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
+             }
+ 
+         }
diff --git a/nms-patches/EntityGuardianElder.patch b/nms-patches/EntityGuardianElder.patch
new file mode 100644
index 0000000000..23fec4b9d0
--- /dev/null
+++ b/nms-patches/EntityGuardianElder.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/server/EntityGuardianElder.java
++++ b/net/minecraft/server/EntityGuardianElder.java
+@@ -17,7 +17,7 @@
+ 
+     }
+ 
+-    protected void initAttributes() {
++    public void initAttributes() { // CraftBukkit - decompile error
+         super.initAttributes();
+         this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.30000001192092896D);
+         this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(8.0D);
+@@ -68,7 +68,7 @@
+ 
+                 if (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < 2 || entityplayer.getEffect(mobeffectlist).getDuration() < 1200) {
+                     entityplayer.playerConnection.sendPacket(new PacketPlayOutGameStateChange(10, 0.0F));
+-                    entityplayer.addEffect(new MobEffect(mobeffectlist, 6000, 2));
++                    entityplayer.addEffect(new MobEffect(mobeffectlist, 6000, 2), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                 }
+             }
+         }
diff --git a/nms-patches/EntityHuman.patch b/nms-patches/EntityHuman.patch
index d8a3d00c70..4df4e4b2d1 100644
--- a/nms-patches/EntityHuman.patch
+++ b/nms-patches/EntityHuman.patch
@@ -51,6 +51,15 @@
      public EntityHuman(World world, GameProfile gameprofile) {
          super(EntityTypes.PLAYER, world);
          this.cd = ItemStack.a;
+@@ -184,7 +208,7 @@
+         ItemStack itemstack = this.getEquipment(EnumItemSlot.HEAD);
+ 
+         if (itemstack.getItem() == Items.TURTLE_HELMET && !this.a(TagsFluid.a)) {
+-            this.addEffect(new MobEffect(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
++            this.addEffect(new MobEffect(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
+         }
+ 
+     }
 @@ -369,7 +393,8 @@
  
          if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.world.getGameRules().getBoolean("naturalRegeneration")) {
diff --git a/nms-patches/EntityIllagerIllusioner.patch b/nms-patches/EntityIllagerIllusioner.patch
new file mode 100644
index 0000000000..33651b6768
--- /dev/null
+++ b/nms-patches/EntityIllagerIllusioner.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/server/EntityIllagerIllusioner.java
++++ b/net/minecraft/server/EntityIllagerIllusioner.java
+@@ -164,7 +164,7 @@
+         }
+ 
+         protected void j() {
+-            EntityIllagerIllusioner.this.getGoalTarget().addEffect(new MobEffect(MobEffects.BLINDNESS, 400));
++            EntityIllagerIllusioner.this.getGoalTarget().addEffect(new MobEffect(MobEffects.BLINDNESS, 400), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+         }
+ 
+         protected SoundEffect k() {
+@@ -199,7 +199,7 @@
+         }
+ 
+         protected void j() {
+-            EntityIllagerIllusioner.this.addEffect(new MobEffect(MobEffects.INVISIBILITY, 1200));
++            EntityIllagerIllusioner.this.addEffect(new MobEffect(MobEffects.INVISIBILITY, 1200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ILLUSION); // CraftBukkit
+         }
+ 
+         @Nullable
diff --git a/nms-patches/EntityLiving.patch b/nms-patches/EntityLiving.patch
index df052af41f..24f47b0330 100644
--- a/nms-patches/EntityLiving.patch
+++ b/nms-patches/EntityLiving.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/server/EntityLiving.java
 +++ b/net/minecraft/server/EntityLiving.java
-@@ -13,6 +13,24 @@
+@@ -13,6 +13,25 @@
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  
@@ -16,6 +16,7 @@
 +import org.bukkit.entity.Player;
 +import org.bukkit.event.entity.EntityDamageEvent;
 +import org.bukkit.event.entity.EntityDamageEvent.DamageModifier;
++import org.bukkit.event.entity.EntityPotionEffectEvent;
 +import org.bukkit.event.entity.EntityRegainHealthEvent;
 +import org.bukkit.event.entity.EntityResurrectEvent;
 +import org.bukkit.event.entity.EntityTeleportEvent;
@@ -25,7 +26,7 @@
  public abstract class EntityLiving extends Entity {
  
      private static final Logger a = LogManager.getLogger();
-@@ -93,6 +111,20 @@
+@@ -93,6 +112,20 @@
      protected int bw;
      private float bO;
      private float bP;
@@ -46,7 +47,7 @@
  
      protected EntityLiving(EntityTypes<?> entitytypes, World world) {
          super(entitytypes, world);
-@@ -103,7 +135,8 @@
+@@ -103,7 +136,8 @@
          this.updateEffects = true;
          this.activeItem = ItemStack.a;
          this.initAttributes();
@@ -56,7 +57,7 @@
          this.j = true;
          this.aP = (float) ((Math.random() + 1.0D) * 0.009999999776482582D);
          this.setPosition(this.locX, this.locY, this.locZ);
-@@ -145,7 +178,13 @@
+@@ -145,7 +179,13 @@
                  double d1 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
                  int i = (int) (150.0D * d1);
  
@@ -71,7 +72,7 @@
              }
          }
  
-@@ -264,6 +303,18 @@
+@@ -264,6 +304,18 @@
          this.world.methodProfiler.e();
      }
  
@@ -90,7 +91,7 @@
      protected void b(BlockPosition blockposition) {
          int i = EnchantmentManager.a(Enchantments.j, this);
  
-@@ -283,19 +334,19 @@
+@@ -283,19 +335,19 @@
  
      protected void ca() {
          ++this.deathTicks;
@@ -100,14 +101,14 @@
  
 -            if (!this.world.isClientSide && (this.alwaysGivesExp() || this.lastDamageByPlayerTime > 0 && this.isDropExperience() && this.world.getGameRules().getBoolean("doMobLoot"))) {
 -                i = this.getExpValue(this.killer);
+-
+-                while (i > 0) {
+-                    int j = EntityExperienceOrb.getOrbValue(i);
 +            // CraftBukkit start - Update getExpReward() above if the removed if() changes!
 +            i = this.expToDrop;
 +            while (i > 0) {
 +                int j = EntityExperienceOrb.getOrbValue(i);
  
--                while (i > 0) {
--                    int j = EntityExperienceOrb.getOrbValue(i);
--
 -                    i -= j;
 -                    this.world.addEntity(new EntityExperienceOrb(this.world, this.locX, this.locY, this.locZ, j));
 -                }
@@ -119,7 +120,7 @@
  
              this.die();
  
-@@ -455,6 +506,17 @@
+@@ -455,6 +507,17 @@
              }
          }
  
@@ -137,13 +138,30 @@
          if (nbttagcompound.hasKeyOfType("Health", 99)) {
              this.setHealth(nbttagcompound.getFloat("Health"));
          }
-@@ -478,9 +540,15 @@
+@@ -478,9 +541,32 @@
  
      }
  
 +    // CraftBukkit start
 +    private boolean isTickingEffects = false;
-+    private List<Object> effectsToProcess = Lists.newArrayList();
++    private List<ProcessableEffect> effectsToProcess = Lists.newArrayList();
++
++    private static class ProcessableEffect {
++
++        private MobEffectList type;
++        private MobEffect effect;
++        private final EntityPotionEffectEvent.Cause cause;
++
++        private ProcessableEffect(MobEffect effect, EntityPotionEffectEvent.Cause cause) {
++            this.effect = effect;
++            this.cause = cause;
++        }
++
++        private ProcessableEffect(MobEffectList type, EntityPotionEffectEvent.Cause cause) {
++            this.type = type;
++            this.cause = cause;
++        }
++    }
 +    // CraftBukkit end
 +
      protected void tickPotionEffects() {
@@ -153,17 +171,32 @@
          try {
              while (iterator.hasNext()) {
                  MobEffectList mobeffectlist = (MobEffectList) iterator.next();
-@@ -498,6 +566,17 @@
+@@ -488,8 +574,13 @@
+ 
+                 if (!mobeffect.tick(this)) {
+                     if (!this.world.isClientSide) {
++                        // CraftBukkit start
++                        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION);
++                        if (!event.isCancelled()) {
++                            this.b(mobeffect);
++                        }
++                        // CraftBukkit end
+                         iterator.remove();
+-                        this.b(mobeffect);
+                     }
+                 } else if (mobeffect.getDuration() % 600 == 0) {
+                     this.a(mobeffect, false);
+@@ -498,6 +589,17 @@
          } catch (ConcurrentModificationException concurrentmodificationexception) {
              ;
          }
 +        // CraftBukkit start
 +        isTickingEffects = false;
-+        for (Object e : effectsToProcess) {
-+            if (e instanceof MobEffect) {
-+                addEffect((MobEffect) e);
++        for (ProcessableEffect e : effectsToProcess) {
++            if (e.effect != null) {
++                addEffect(e.effect, e.cause);
 +            } else {
-+                removeEffect((MobEffectList) e);
++                removeEffect(e.type, e.cause);
 +            }
 +        }
 +        effectsToProcess.clear();
@@ -171,33 +204,126 @@
  
          if (this.updateEffects) {
              if (!this.world.isClientSide) {
-@@ -604,6 +683,12 @@
+@@ -569,7 +671,13 @@
+         this.datawatcher.set(EntityLiving.g, Integer.valueOf(0));
      }
  
++    // CraftBukkit start
+     public boolean removeAllEffects() {
++        return removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++    }
++
++    public boolean removeAllEffects(EntityPotionEffectEvent.Cause cause) {
++        // CraftBukkit end
+         if (this.world.isClientSide) {
+             return false;
+         } else {
+@@ -578,7 +686,13 @@
+             boolean flag;
+ 
+             for (flag = false; iterator.hasNext(); flag = true) {
+-                this.b((MobEffect) iterator.next());
++                // CraftBukkit start
++                MobEffect effect = (MobEffect) iterator.next();
++                EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
++                if (!event.isCancelled()) {
++                    this.b(effect);
++                }
++                // CraftBukkit end
+                 iterator.remove();
+             }
+ 
+@@ -603,18 +717,44 @@
+         return (MobEffect) this.effects.get(mobeffectlist);
+     }
+ 
++    // CraftBukkit start
      public boolean addEffect(MobEffect mobeffect) {
-+        // CraftBukkit start
++        return addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++    }
++
++    public boolean addEffect(MobEffect mobeffect, EntityPotionEffectEvent.Cause cause) {
 +        if (isTickingEffects) {
-+            effectsToProcess.add(mobeffect);
++            effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
 +            return true;
 +        }
 +        // CraftBukkit end
++
          if (!this.d(mobeffect)) {
              return false;
          } else {
-@@ -640,6 +725,12 @@
+             MobEffect mobeffect1 = (MobEffect) this.effects.get(mobeffect.getMobEffect());
  
++            // CraftBukkit start
++            boolean override = false;
++            if (mobeffect1 != null) {
++                override = new MobEffect(mobeffect1).a(mobeffect);
++            }
++
++            EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override);
++            if (event.isCancelled()) {
++                return false;
++            }
++            // CraftBukkit end
++
+             if (mobeffect1 == null) {
+                 this.effects.put(mobeffect.getMobEffect(), mobeffect);
+                 this.a(mobeffect);
+                 return true;
+-            } else if (mobeffect1.a(mobeffect)) {
+-                this.a(mobeffect1, true);
++                // CraftBukkit start
++            } else if (event.isOverride()) {
++                this.effects.put(mobeffect.getMobEffect(), mobeffect);
++                this.a(mobeffect, true);
++                // CraftBukkit end
+                 return true;
+             } else {
+                 return false;
+@@ -637,14 +777,40 @@
+     public boolean co() {
+         return this.getMonsterType() == EnumMonsterType.UNDEAD;
+     }
+-
++    
++    // CraftBukkit start
      @Nullable
      public MobEffect c(@Nullable MobEffectList mobeffectlist) {
-+        // CraftBukkit start
++        return c(mobeffectlist, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++    }
++
++    @Nullable
++    public MobEffect c(@Nullable MobEffectList mobeffectlist, EntityPotionEffectEvent.Cause cause) {
 +        if (isTickingEffects) {
-+            effectsToProcess.add(mobeffectlist);
++            effectsToProcess.add(new ProcessableEffect(mobeffectlist, cause));
 +            return null;
 +        }
-+        // CraftBukkit end
++
++        MobEffect effect = this.effects.get(mobeffectlist);
++        if (effect == null) {
++            return null;
++        }
++
++        EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause);
++        if (event.isCancelled()) {
++            return null;
++        }
++
          return (MobEffect) this.effects.remove(mobeffectlist);
      }
  
-@@ -681,20 +772,52 @@
+     public boolean removeEffect(MobEffectList mobeffectlist) {
+-        MobEffect mobeffect = this.c(mobeffectlist);
++        return removeEffect(mobeffectlist, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++    }
++
++    public boolean removeEffect(MobEffectList mobeffectlist, EntityPotionEffectEvent.Cause cause) {
++        MobEffect mobeffect = this.c(mobeffectlist, cause);
++        // CraftBukkit end
+ 
+         if (mobeffect != null) {
+             this.b(mobeffect);
+@@ -681,20 +847,52 @@
  
      }
  
@@ -251,7 +377,7 @@
          this.datawatcher.set(EntityLiving.HEALTH, Float.valueOf(MathHelper.a(f, 0.0F, this.getMaxHealth())));
      }
  
-@@ -712,14 +835,16 @@
+@@ -712,14 +910,16 @@
              } else {
                  float f1 = f;
  
@@ -271,7 +397,7 @@
                      this.damageShield(f);
                      f = 0.0F;
                      if (!damagesource.b()) {
-@@ -738,20 +863,39 @@
+@@ -738,20 +938,39 @@
  
                  if ((float) this.noDamageTicks > (float) this.maxNoDamageTicks / 2.0F) {
                      if (f <= this.lastDamage) {
@@ -313,7 +439,7 @@
                  this.aD = 0.0F;
                  Entity entity1 = damagesource.getEntity();
  
-@@ -858,19 +1002,29 @@
+@@ -858,19 +1077,29 @@
              EnumHand[] aenumhand = EnumHand.values();
              int i = aenumhand.length;
  
@@ -347,7 +473,18 @@
                      EntityPlayer entityplayer = (EntityPlayer) this;
  
                      entityplayer.b(StatisticList.ITEM_USED.b(Items.TOTEM_OF_UNDYING));
-@@ -884,7 +1038,7 @@
+@@ -878,13 +1107,15 @@
+                 }
+ 
+                 this.setHealth(1.0F);
+-                this.removeAllEffects();
+-                this.addEffect(new MobEffect(MobEffects.REGENERATION, 900, 1));
+-                this.addEffect(new MobEffect(MobEffects.ABSORBTION, 100, 1));
++                // CraftBukkit start
++                this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++                this.addEffect(new MobEffect(MobEffects.REGENERATION, 900, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++                this.addEffect(new MobEffect(MobEffects.ABSORBTION, 100, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++                // CraftBukkit end
                  this.world.broadcastEntityEffect(this, (byte) 35);
              }
  
@@ -356,7 +493,7 @@
          }
      }
  
-@@ -955,6 +1109,12 @@
+@@ -955,6 +1186,12 @@
                      boolean flag = this.lastDamageByPlayerTime > 0;
  
                      this.a(flag, i, damagesource);
@@ -369,7 +506,7 @@
                  }
              }
  
-@@ -1044,8 +1204,13 @@
+@@ -1044,8 +1281,13 @@
          int i = MathHelper.f((f - 3.0F - f2) * f1);
  
          if (i > 0) {
@@ -384,7 +521,7 @@
              int j = MathHelper.floor(this.locX);
              int k = MathHelper.floor(this.locY - 0.20000000298023224D);
              int l = MathHelper.floor(this.locZ);
-@@ -1072,7 +1237,7 @@
+@@ -1072,7 +1314,7 @@
  
      protected float applyArmorModifier(DamageSource damagesource, float f) {
          if (!damagesource.ignoresArmor()) {
@@ -393,7 +530,7 @@
              f = CombatMath.a(f, (float) this.getArmorStrength(), (float) this.getAttributeInstance(GenericAttributes.i).getValue());
          }
  
-@@ -1085,7 +1250,8 @@
+@@ -1085,7 +1327,8 @@
          } else {
              int i;
  
@@ -403,7 +540,7 @@
                  i = (this.getEffect(MobEffects.RESISTANCE).getAmplifier() + 1) * 5;
                  int j = 25 - i;
                  float f1 = f * (float) j;
-@@ -1106,22 +1272,142 @@
+@@ -1106,22 +1349,142 @@
          }
      }
  
@@ -556,7 +693,7 @@
      }
  
      public CombatTracker getCombatTracker() {
-@@ -1188,6 +1474,7 @@
+@@ -1188,6 +1551,7 @@
      public AttributeMapBase getAttributeMap() {
          if (this.attributeMap == null) {
              this.attributeMap = new AttributeMapServer();
@@ -564,7 +701,7 @@
          }
  
          return this.attributeMap;
-@@ -1490,6 +1777,7 @@
+@@ -1490,6 +1854,7 @@
                  }
  
                  if (this.onGround && !this.world.isClientSide) {
@@ -572,7 +709,7 @@
                      this.setFlag(7, false);
                  }
              } else {
-@@ -1891,6 +2179,7 @@
+@@ -1891,6 +2256,7 @@
          }
  
          if (!this.world.isClientSide) {
@@ -580,7 +717,7 @@
              this.setFlag(7, flag);
          }
  
-@@ -2018,11 +2307,11 @@
+@@ -2018,11 +2384,11 @@
      }
  
      public boolean isInteractable() {
@@ -594,7 +731,7 @@
      }
  
      protected void aA() {
-@@ -2182,7 +2471,27 @@
+@@ -2182,7 +2548,27 @@
      protected void q() {
          if (!this.activeItem.isEmpty() && this.isHandRaised()) {
              this.b(this.activeItem, 16);
@@ -623,7 +760,7 @@
              this.cZ();
          }
  
-@@ -2261,10 +2570,18 @@
+@@ -2261,10 +2647,18 @@
              }
  
              if (flag1) {
diff --git a/nms-patches/EntityParrot.patch b/nms-patches/EntityParrot.patch
index 07a227bb2f..54bd63ae5e 100644
--- a/nms-patches/EntityParrot.patch
+++ b/nms-patches/EntityParrot.patch
@@ -18,6 +18,15 @@
                      this.c(entityhuman);
                      this.s(true);
                      this.world.broadcastEntityEffect(this, (byte) 7);
+@@ -190,7 +190,7 @@
+                 itemstack.subtract(1);
+             }
+ 
+-            this.addEffect(new MobEffect(MobEffects.POISON, 900));
++            this.addEffect(new MobEffect(MobEffects.POISON, 900), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
+             if (entityhuman.u() || !this.bl()) {
+                 this.damageEntity(DamageSource.playerAttack(entityhuman), Float.MAX_VALUE);
+             }
 @@ -310,7 +310,8 @@
              return false;
          } else {
diff --git a/nms-patches/EntityPlayer.patch b/nms-patches/EntityPlayer.patch
index ca573ac6ef..fbb4ce6df2 100644
--- a/nms-patches/EntityPlayer.patch
+++ b/nms-patches/EntityPlayer.patch
@@ -750,7 +750,7 @@
 +        this.exp = 0;
 +        this.deathTicks = 0;
 +        this.setArrowCount(0);
-+        this.removeAllEffects();
++        this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH);
 +        this.updateEffects = true;
 +        this.activeContainer = this.defaultContainer;
 +        this.killer = null;
diff --git a/nms-patches/EntityPotion.patch b/nms-patches/EntityPotion.patch
index f39d8365e8..5b710a81f8 100644
--- a/nms-patches/EntityPotion.patch
+++ b/nms-patches/EntityPotion.patch
@@ -87,7 +87,7 @@
 +                        int i = (int) (d1 * (double) mobeffect.getDuration() + 0.5D);
 +
 +                        if (i > 20) {
-+                            entityliving.addEffect(new MobEffect(mobeffectlist, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isShowParticles()));
++                            entityliving.addEffect(new MobEffect(mobeffectlist, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isShowParticles()), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_SPLASH); // CraftBukkit
                          }
                      }
                  }
diff --git a/nms-patches/EntityPufferFish.patch b/nms-patches/EntityPufferFish.patch
new file mode 100644
index 0000000000..d010db2c1f
--- /dev/null
+++ b/nms-patches/EntityPufferFish.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/server/EntityPufferFish.java
++++ b/net/minecraft/server/EntityPufferFish.java
+@@ -47,7 +47,7 @@
+         this.a(f);
+     }
+ 
+-    protected final void setSize(float f, float f1) {
++    public final void setSize(float f, float f1) { // CraftBukkit - decompile error
+         boolean flag = this.bE > 0.0F;
+ 
+         this.bE = f;
+@@ -140,7 +140,7 @@
+         int i = this.getPuffState();
+ 
+         if (entityinsentient.damageEntity(DamageSource.mobAttack(this), (float) (1 + i))) {
+-            entityinsentient.addEffect(new MobEffect(MobEffects.POISON, 60 * i, 0));
++            entityinsentient.addEffect(new MobEffect(MobEffects.POISON, 60 * i, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+             this.a(SoundEffects.ENTITY_PUFFER_FISH_STING, 1.0F, 1.0F);
+         }
+ 
+@@ -151,7 +151,7 @@
+ 
+         if (entityhuman instanceof EntityPlayer && i > 0 && entityhuman.damageEntity(DamageSource.mobAttack(this), (float) (1 + i))) {
+             ((EntityPlayer) entityhuman).playerConnection.sendPacket(new PacketPlayOutGameStateChange(9, 0.0F));
+-            entityhuman.addEffect(new MobEffect(MobEffects.POISON, 60 * i, 0));
++            entityhuman.addEffect(new MobEffect(MobEffects.POISON, 60 * i, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+         }
+ 
+     }
diff --git a/nms-patches/EntityShulkerBullet.patch b/nms-patches/EntityShulkerBullet.patch
index a3b154008b..38a1c68988 100644
--- a/nms-patches/EntityShulkerBullet.patch
+++ b/nms-patches/EntityShulkerBullet.patch
@@ -38,3 +38,12 @@
          if (movingobjectposition.entity == null) {
              ((WorldServer) this.world).a(Particles.u, this.locX, this.locY, this.locZ, 2, 0.2D, 0.2D, 0.2D, 0.0D);
              this.a(SoundEffects.ENTITY_SHULKER_BULLET_HIT, 1.0F, 1.0F);
+@@ -296,7 +318,7 @@
+             if (flag) {
+                 this.a(this.shooter, movingobjectposition.entity);
+                 if (movingobjectposition.entity instanceof EntityLiving) {
+-                    ((EntityLiving) movingobjectposition.entity).addEffect(new MobEffect(MobEffects.LEVITATION, 200));
++                    ((EntityLiving) movingobjectposition.entity).addEffect(new MobEffect(MobEffects.LEVITATION, 200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                 }
+             }
+         }
diff --git a/nms-patches/EntitySkeletonWither.patch b/nms-patches/EntitySkeletonWither.patch
index d66a1b59e4..089956eefa 100644
--- a/nms-patches/EntitySkeletonWither.patch
+++ b/nms-patches/EntitySkeletonWither.patch
@@ -17,3 +17,12 @@
  
      }
  
+@@ -68,7 +69,7 @@
+             return false;
+         } else {
+             if (entity instanceof EntityLiving) {
+-                ((EntityLiving) entity).addEffect(new MobEffect(MobEffects.WITHER, 200));
++                ((EntityLiving) entity).addEffect(new MobEffect(MobEffects.WITHER, 200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+             }
+ 
+             return true;
diff --git a/nms-patches/EntitySpectralArrow.patch b/nms-patches/EntitySpectralArrow.patch
new file mode 100644
index 0000000000..be4dcc85b1
--- /dev/null
+++ b/nms-patches/EntitySpectralArrow.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/EntitySpectralArrow.java
++++ b/net/minecraft/server/EntitySpectralArrow.java
+@@ -32,7 +32,7 @@
+         super.a(entityliving);
+         MobEffect mobeffect = new MobEffect(MobEffects.GLOWING, this.duration, 0);
+ 
+-        entityliving.addEffect(mobeffect);
++        entityliving.addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+     }
+ 
+     public void a(NBTTagCompound nbttagcompound) {
diff --git a/nms-patches/EntitySpider.patch b/nms-patches/EntitySpider.patch
index ce14684d5c..1820e8dfd8 100644
--- a/nms-patches/EntitySpider.patch
+++ b/nms-patches/EntitySpider.patch
@@ -9,3 +9,12 @@
              entityskeleton.startRiding(this);
          }
  
+@@ -130,7 +130,7 @@
+             MobEffectList mobeffectlist = ((EntitySpider.GroupDataSpider) object).a;
+ 
+             if (mobeffectlist != null) {
+-                this.addEffect(new MobEffect(mobeffectlist, Integer.MAX_VALUE));
++                this.addEffect(new MobEffect(mobeffectlist, Integer.MAX_VALUE), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit
+             }
+         }
+ 
diff --git a/nms-patches/EntityTippedArrow.patch b/nms-patches/EntityTippedArrow.patch
index 12e28241ed..b3e1578b49 100644
--- a/nms-patches/EntityTippedArrow.patch
+++ b/nms-patches/EntityTippedArrow.patch
@@ -26,3 +26,21 @@
      public int getColor() {
          return ((Integer) this.datawatcher.get(EntityTippedArrow.f)).intValue();
      }
+@@ -180,7 +199,7 @@
+ 
+         while (iterator.hasNext()) {
+             mobeffect = (MobEffect) iterator.next();
+-            entityliving.addEffect(new MobEffect(mobeffect.getMobEffect(), Math.max(mobeffect.getDuration() / 8, 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isShowParticles()));
++            entityliving.addEffect(new MobEffect(mobeffect.getMobEffect(), Math.max(mobeffect.getDuration() / 8, 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isShowParticles()), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+         }
+ 
+         if (!this.effects.isEmpty()) {
+@@ -188,7 +207,7 @@
+ 
+             while (iterator.hasNext()) {
+                 mobeffect = (MobEffect) iterator.next();
+-                entityliving.addEffect(mobeffect);
++                entityliving.addEffect(mobeffect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+             }
+         }
+ 
diff --git a/nms-patches/EntityVillager.patch b/nms-patches/EntityVillager.patch
index 201e9a2c6c..6cfcdfc52c 100644
--- a/nms-patches/EntityVillager.patch
+++ b/nms-patches/EntityVillager.patch
@@ -40,6 +40,15 @@
                          }
                      }
  
+@@ -132,7 +147,7 @@
+                     }
+                 }
+ 
+-                this.addEffect(new MobEffect(MobEffects.REGENERATION, 200, 0));
++                this.addEffect(new MobEffect(MobEffects.REGENERATION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.VILLAGER_TRADE); // CraftBukkit
+             }
+         }
+ 
 @@ -434,7 +449,20 @@
                  for (int l = 0; l < k; ++l) {
                      EntityVillager.IMerchantRecipeOption entityvillager_imerchantrecipeoption = aentityvillager_imerchantrecipeoption3[l];
diff --git a/nms-patches/EntityWitch.patch b/nms-patches/EntityWitch.patch
new file mode 100644
index 0000000000..11dbf2fd0a
--- /dev/null
+++ b/nms-patches/EntityWitch.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/EntityWitch.java
++++ b/net/minecraft/server/EntityWitch.java
+@@ -75,7 +75,7 @@
+                             while (iterator.hasNext()) {
+                                 MobEffect mobeffect = (MobEffect) iterator.next();
+ 
+-                                this.addEffect(new MobEffect(mobeffect));
++                                this.addEffect(new MobEffect(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                             }
+                         }
+                     }
diff --git a/nms-patches/EntityWitherSkull.patch b/nms-patches/EntityWitherSkull.patch
index c700d1a945..8e3e63f799 100644
--- a/nms-patches/EntityWitherSkull.patch
+++ b/nms-patches/EntityWitherSkull.patch
@@ -22,7 +22,13 @@
                          }
                      }
                  } else {
-@@ -54,7 +56,15 @@
+@@ -49,12 +51,20 @@
+                     }
+ 
+                     if (b0 > 0) {
+-                        ((EntityLiving) movingobjectposition.entity).addEffect(new MobEffect(MobEffects.WITHER, 20 * b0, 1));
++                        ((EntityLiving) movingobjectposition.entity).addEffect(new MobEffect(MobEffects.WITHER, 20 * b0, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+                     }
                  }
              }
  
diff --git a/nms-patches/EntityZombieHusk.patch b/nms-patches/EntityZombieHusk.patch
new file mode 100644
index 0000000000..96da7f5499
--- /dev/null
+++ b/nms-patches/EntityZombieHusk.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/EntityZombieHusk.java
++++ b/net/minecraft/server/EntityZombieHusk.java
+@@ -43,7 +43,7 @@
+         if (flag && this.getItemInMainHand().isEmpty() && entity instanceof EntityLiving) {
+             float f = this.world.getDamageScaler(new BlockPosition(this)).b();
+ 
+-            ((EntityLiving) entity).addEffect(new MobEffect(MobEffects.HUNGER, 140 * (int) f));
++            ((EntityLiving) entity).addEffect(new MobEffect(MobEffects.HUNGER, 140 * (int) f), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+         }
+ 
+         return flag;
diff --git a/nms-patches/EntityZombieVillager.patch b/nms-patches/EntityZombieVillager.patch
index c4d767bc27..b04cbf5fb5 100644
--- a/nms-patches/EntityZombieVillager.patch
+++ b/nms-patches/EntityZombieVillager.patch
@@ -20,7 +20,20 @@
  
              this.conversionTime -= i;
              if (this.conversionTime <= 0) {
-@@ -123,7 +129,7 @@
+@@ -100,8 +106,10 @@
+         this.bD = uuid;
+         this.conversionTime = i;
+         this.getDataWatcher().set(EntityZombieVillager.a, Boolean.valueOf(true));
+-        this.removeEffect(MobEffects.WEAKNESS);
+-        this.addEffect(new MobEffect(MobEffects.INCREASE_DAMAGE, i, Math.min(this.world.getDifficulty().a() - 1, 0)));
++        // CraftBukkit start
++        this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
++        this.addEffect(new MobEffect(MobEffects.INCREASE_DAMAGE, i, Math.min(this.world.getDifficulty().a() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
++        // CraftBukkit end
+         this.world.broadcastEntityEffect(this, (byte) 16);
+     }
+ 
+@@ -123,7 +131,7 @@
              entityvillager.setCustomNameVisible(this.getCustomNameVisible());
          }
  
@@ -29,3 +42,12 @@
          if (this.bD != null) {
              EntityHuman entityhuman = this.world.b(this.bD);
  
+@@ -132,7 +140,7 @@
+             }
+         }
+ 
+-        entityvillager.addEffect(new MobEffect(MobEffects.CONFUSION, 200, 0));
++        entityvillager.addEffect(new MobEffect(MobEffects.CONFUSION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); // CraftBukkit
+         this.world.a((EntityHuman) null, 1027, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0);
+     }
+ 
diff --git a/nms-patches/ItemFish.patch b/nms-patches/ItemFish.patch
new file mode 100644
index 0000000000..8b52880fd0
--- /dev/null
+++ b/nms-patches/ItemFish.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/server/ItemFish.java
++++ b/net/minecraft/server/ItemFish.java
+@@ -25,9 +25,11 @@
+         ItemFish.EnumFish itemfish_enumfish = ItemFish.EnumFish.a(itemstack);
+ 
+         if (itemfish_enumfish == ItemFish.EnumFish.PUFFERFISH) {
+-            entityhuman.addEffect(new MobEffect(MobEffects.POISON, 1200, 3));
+-            entityhuman.addEffect(new MobEffect(MobEffects.HUNGER, 300, 2));
+-            entityhuman.addEffect(new MobEffect(MobEffects.CONFUSION, 300, 1));
++            // CraftBukkit start
++            entityhuman.addEffect(new MobEffect(MobEffects.POISON, 1200, 3), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            entityhuman.addEffect(new MobEffect(MobEffects.HUNGER, 300, 2), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            entityhuman.addEffect(new MobEffect(MobEffects.CONFUSION, 300, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            // CraftBukkit end
+         }
+ 
+         super.a(itemstack, world, entityhuman);
diff --git a/nms-patches/ItemFood.patch b/nms-patches/ItemFood.patch
new file mode 100644
index 0000000000..06317732cc
--- /dev/null
+++ b/nms-patches/ItemFood.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/ItemFood.java
++++ b/net/minecraft/server/ItemFood.java
+@@ -36,7 +36,7 @@
+ 
+     protected void a(ItemStack itemstack, World world, EntityHuman entityhuman) {
+         if (!world.isClientSide && this.l != null && world.random.nextFloat() < this.m) {
+-            entityhuman.addEffect(new MobEffect(this.l));
++            entityhuman.addEffect(new MobEffect(this.l), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
+         }
+ 
+     }
diff --git a/nms-patches/ItemGoldenApple.patch b/nms-patches/ItemGoldenApple.patch
new file mode 100644
index 0000000000..31b2c07661
--- /dev/null
+++ b/nms-patches/ItemGoldenApple.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/server/ItemGoldenApple.java
++++ b/net/minecraft/server/ItemGoldenApple.java
+@@ -8,8 +8,10 @@
+ 
+     protected void a(ItemStack itemstack, World world, EntityHuman entityhuman) {
+         if (!world.isClientSide) {
+-            entityhuman.addEffect(new MobEffect(MobEffects.REGENERATION, 100, 1));
+-            entityhuman.addEffect(new MobEffect(MobEffects.ABSORBTION, 2400, 0));
++            // CraftBukkit start
++            entityhuman.addEffect(new MobEffect(MobEffects.REGENERATION, 100, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            entityhuman.addEffect(new MobEffect(MobEffects.ABSORBTION, 2400, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            // CraftBukkit end
+         }
+ 
+     }
diff --git a/nms-patches/ItemGoldenAppleEnchanted.patch b/nms-patches/ItemGoldenAppleEnchanted.patch
new file mode 100644
index 0000000000..a24e9bc63c
--- /dev/null
+++ b/nms-patches/ItemGoldenAppleEnchanted.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/server/ItemGoldenAppleEnchanted.java
++++ b/net/minecraft/server/ItemGoldenAppleEnchanted.java
+@@ -8,10 +8,12 @@
+ 
+     protected void a(ItemStack itemstack, World world, EntityHuman entityhuman) {
+         if (!world.isClientSide) {
+-            entityhuman.addEffect(new MobEffect(MobEffects.REGENERATION, 400, 1));
+-            entityhuman.addEffect(new MobEffect(MobEffects.RESISTANCE, 6000, 0));
+-            entityhuman.addEffect(new MobEffect(MobEffects.FIRE_RESISTANCE, 6000, 0));
+-            entityhuman.addEffect(new MobEffect(MobEffects.ABSORBTION, 2400, 3));
++            // CraftBukkit start
++            entityhuman.addEffect(new MobEffect(MobEffects.REGENERATION, 400, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            entityhuman.addEffect(new MobEffect(MobEffects.RESISTANCE, 6000, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            entityhuman.addEffect(new MobEffect(MobEffects.FIRE_RESISTANCE, 6000, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            entityhuman.addEffect(new MobEffect(MobEffects.ABSORBTION, 2400, 3), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD);
++            // CraftBukkit end
+         }
+ 
+     }
diff --git a/nms-patches/ItemMilkBucket.patch b/nms-patches/ItemMilkBucket.patch
new file mode 100644
index 0000000000..f147e504e4
--- /dev/null
+++ b/nms-patches/ItemMilkBucket.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/ItemMilkBucket.java
++++ b/net/minecraft/server/ItemMilkBucket.java
+@@ -19,7 +19,7 @@
+         }
+ 
+         if (!world.isClientSide) {
+-            entityliving.removeAllEffects();
++            entityliving.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit
+         }
+ 
+         return itemstack.isEmpty() ? new ItemStack(Items.BUCKET) : itemstack;
diff --git a/nms-patches/ItemPotion.patch b/nms-patches/ItemPotion.patch
new file mode 100644
index 0000000000..c1277da325
--- /dev/null
+++ b/nms-patches/ItemPotion.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/ItemPotion.java
++++ b/net/minecraft/server/ItemPotion.java
+@@ -30,7 +30,7 @@
+                 if (mobeffect.getMobEffect().isInstant()) {
+                     mobeffect.getMobEffect().applyInstantEffect(entityhuman, entityhuman, entityliving, mobeffect.getAmplifier(), 1.0D);
+                 } else {
+-                    entityliving.addEffect(new MobEffect(mobeffect));
++                    entityliving.addEffect(new MobEffect(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit
+                 }
+             }
+         }
diff --git a/nms-patches/TileEntityBeacon.patch b/nms-patches/TileEntityBeacon.patch
index 84fcb11fbf..e6a987ef72 100644
--- a/nms-patches/TileEntityBeacon.patch
+++ b/nms-patches/TileEntityBeacon.patch
@@ -103,7 +103,7 @@
              while (iterator.hasNext()) {
                  entityhuman = (EntityHuman) iterator.next();
 -                entityhuman.addEffect(new MobEffect(this.primaryEffect, i, b0, true, true));
-+                entityhuman.addEffect(new MobEffect(effects, i, b0, true, true));
++                entityhuman.addEffect(new MobEffect(effects, i, b0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.BEACON);
              }
 +        }
 +    }
diff --git a/nms-patches/TileEntityConduit.patch b/nms-patches/TileEntityConduit.patch
new file mode 100644
index 0000000000..23d9e0fab5
--- /dev/null
+++ b/nms-patches/TileEntityConduit.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/server/TileEntityConduit.java
++++ b/net/minecraft/server/TileEntityConduit.java
+@@ -154,7 +154,7 @@
+                 EntityHuman entityhuman = (EntityHuman) iterator.next();
+ 
+                 if (this.position.m(new BlockPosition(entityhuman)) <= (double) j && entityhuman.ao()) {
+-                    entityhuman.addEffect(new MobEffect(MobEffects.CONDUIT_POWER, 260, 0, true, true));
++                    entityhuman.addEffect(new MobEffect(MobEffects.CONDUIT_POWER, 260, 0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONDUIT); // CraftBukkit
+                 }
+             }
+ 
+@@ -171,8 +171,8 @@
+             this.j = this.l();
+             this.k = null;
+         } else if (this.j == null) {
+-            List list = this.world.a(EntityLiving.class, this.k(), (entityliving) -> {
+-                return entityliving instanceof IMonster && entityliving.ao();
++            List list = this.world.a(EntityLiving.class, this.k(), (Predicate<EntityLiving>) (entitylivingx) -> { // CraftBukkit - decompile error
++                return entitylivingx instanceof IMonster && entitylivingx.ao(); // CraftBukkit - decompile error
+             });
+ 
+             if (!list.isEmpty()) {
+@@ -217,7 +217,7 @@
+ 
+     @Nullable
+     private EntityLiving l() {
+-        List list = this.world.a(EntityLiving.class, this.k(), (entityliving) -> {
++        List list = this.world.a(EntityLiving.class, this.k(), (Predicate<EntityLiving>) (entityliving) -> { // CraftBukkit - decompile error
+             return entityliving.getUniqueID().equals(this.k);
+         });
+ 
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
index 382b8028aa..13100e5d21 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
@@ -68,6 +68,7 @@ import org.bukkit.entity.ThrownPotion;
 import org.bukkit.entity.TippedArrow;
 import org.bukkit.entity.Trident;
 import org.bukkit.entity.WitherSkull;
+import org.bukkit.event.entity.EntityPotionEffectEvent;
 import org.bukkit.event.player.PlayerTeleportEvent;
 import org.bukkit.inventory.EntityEquipment;
 import org.bukkit.inventory.ItemStack;
@@ -262,7 +263,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
             }
             removePotionEffect(effect.getType());
         }
-        getHandle().addEffect(new MobEffect(MobEffectList.fromId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()));
+        getHandle().addEffect(new MobEffect(MobEffectList.fromId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN);
         return true;
     }
 
@@ -285,7 +286,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
     }
 
     public void removePotionEffect(PotionEffectType type) {
-        getHandle().removeEffect(MobEffectList.fromId(type.getId()));
+        getHandle().removeEffect(MobEffectList.fromId(type.getId()), EntityPotionEffectEvent.Cause.PLUGIN);
     }
 
     public Collection<PotionEffect> getActivePotionEffects() {
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 5793bdf4f5..72b5dfd759 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -31,6 +31,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
 import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
 import org.bukkit.craftbukkit.inventory.CraftItemStack;
 import org.bukkit.craftbukkit.inventory.CraftMetaBook;
+import org.bukkit.craftbukkit.potion.CraftPotionUtil;
 import org.bukkit.craftbukkit.util.CraftDamageSource;
 import org.bukkit.craftbukkit.util.CraftMagicNumbers;
 import org.bukkit.entity.AreaEffectCloud;
@@ -67,6 +68,7 @@ import org.bukkit.inventory.meta.BookMeta;
 import org.bukkit.entity.AbstractHorse;
 import org.bukkit.entity.Vehicle;
 import org.bukkit.event.vehicle.VehicleCreateEvent;
+import org.bukkit.potion.PotionEffect;
 
 public class CraftEventFactory {
     public static final DamageSource MELTING = CraftDamageSource.copyOf(DamageSource.BURN);
@@ -1054,6 +1056,39 @@ public class CraftEventFactory {
         return handleBlockFormEvent(world, pos, block, 3);
     }
 
+    public static EntityPotionEffectEvent callEntityPotionEffectChangeEvent(EntityLiving entity, @Nullable MobEffect oldEffect, @Nullable MobEffect newEffect, EntityPotionEffectEvent.Cause cause) {
+        return callEntityPotionEffectChangeEvent(entity, oldEffect, newEffect, cause, true);
+    }
+
+    public static EntityPotionEffectEvent callEntityPotionEffectChangeEvent(EntityLiving entity, @Nullable MobEffect oldEffect, @Nullable MobEffect newEffect, EntityPotionEffectEvent.Cause cause, EntityPotionEffectEvent.Action action) {
+        return callEntityPotionEffectChangeEvent(entity, oldEffect, newEffect, cause, action, true);
+    }
+
+    public static EntityPotionEffectEvent callEntityPotionEffectChangeEvent(EntityLiving entity, @Nullable MobEffect oldEffect, @Nullable MobEffect newEffect, EntityPotionEffectEvent.Cause cause, boolean willOverride) {
+        EntityPotionEffectEvent.Action action = EntityPotionEffectEvent.Action.CHANGED;
+        if (oldEffect == null) {
+            action = EntityPotionEffectEvent.Action.ADDED;
+        } else if (newEffect == null) {
+            action = EntityPotionEffectEvent.Action.REMOVED;
+        }
+
+        return callEntityPotionEffectChangeEvent(entity, oldEffect, newEffect, cause, action, willOverride);
+    }
+
+    public static EntityPotionEffectEvent callEntityPotionEffectChangeEvent(EntityLiving entity, @Nullable MobEffect oldEffect, @Nullable MobEffect newEffect, EntityPotionEffectEvent.Cause cause, EntityPotionEffectEvent.Action action, boolean willOverride) {
+        PotionEffect bukkitOldEffect = (oldEffect == null) ? null : CraftPotionUtil.toBukkit(oldEffect);
+        PotionEffect bukkitNewEffect = (newEffect == null) ? null : CraftPotionUtil.toBukkit(newEffect);
+
+        if (bukkitOldEffect == null && bukkitNewEffect == null) {
+            throw new IllegalStateException("Old and new potion effect are both null");
+        }
+
+        EntityPotionEffectEvent event = new EntityPotionEffectEvent((LivingEntity) entity.getBukkitEntity(), bukkitOldEffect, bukkitNewEffect, cause, action, willOverride);
+        Bukkit.getPluginManager().callEvent(event);
+
+        return event;
+    }
+
     public static boolean handleBlockFormEvent(World world, BlockPosition pos, IBlockData block, @Nullable Entity entity) {
         return handleBlockFormEvent(world, pos, block, 3, entity);
     }