From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Encode42 <me@encode42.dev>
Date: Tue, 8 Dec 2020 17:15:15 -0500
Subject: [PATCH] Configurable chance for wolves to spawn rabid

Configurable chance to spawn a wolf that is rabid.
Rabid wolves attack all players, mobs, and animals.

diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
index ef4abaf68de01b0879f7d0b330d2d57cc6bd10f9..3e7409ebf1f94b9cf55f2d0b0fe17ca8ec44659f 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
@@ -10,22 +10,28 @@ import net.minecraft.network.syncher.EntityDataAccessor;
 import net.minecraft.network.syncher.EntityDataSerializers;
 import net.minecraft.network.syncher.SynchedEntityData;
 import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
 import net.minecraft.sounds.SoundEvent;
 import net.minecraft.sounds.SoundEvents;
 import net.minecraft.util.Mth;
 import net.minecraft.util.TimeUtil;
 import net.minecraft.util.valueproviders.UniformInt;
+import net.minecraft.world.DifficultyInstance;
 import net.minecraft.world.InteractionHand;
 import net.minecraft.world.InteractionResult;
 import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.effect.MobEffects;
 import net.minecraft.world.entity.AgeableMob;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.EntityDimensions;
 import net.minecraft.world.entity.EntityType;
 import net.minecraft.world.entity.LivingEntity;
 import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.MobSpawnType;
 import net.minecraft.world.entity.NeutralMob;
 import net.minecraft.world.entity.Pose;
+import net.minecraft.world.entity.SpawnGroupData;
 import net.minecraft.world.entity.TamableAnimal;
 import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
 import net.minecraft.world.entity.ai.attributes.Attributes;
@@ -34,6 +40,7 @@ import net.minecraft.world.entity.ai.goal.BegGoal;
 import net.minecraft.world.entity.ai.goal.BreedGoal;
 import net.minecraft.world.entity.ai.goal.FloatGoal;
 import net.minecraft.world.entity.ai.goal.FollowOwnerGoal;
+import net.minecraft.world.entity.ai.goal.Goal;
 import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal;
 import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
 import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
@@ -59,6 +66,7 @@ import net.minecraft.world.item.Item;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
 import net.minecraft.world.level.Level;
+import net.minecraft.world.level.ServerLevelAccessor;
 import net.minecraft.world.level.block.state.BlockState;
 import net.minecraft.world.level.gameevent.GameEvent;
 import net.minecraft.world.phys.Vec3;
@@ -77,6 +85,37 @@ public class Wolf extends TamableAnimal implements NeutralMob {
 
         return entitytypes == EntityType.SHEEP || entitytypes == EntityType.RABBIT || entitytypes == EntityType.FOX;
     };
+    // Purpur start - rabid wolf spawn chance
+    private boolean isRabid = false;
+    private static final Predicate<LivingEntity> RABID_PREDICATE = entity -> entity instanceof ServerPlayer || entity instanceof Mob;
+    private final Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR);
+    private final Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE);
+    private static final class AvoidRabidWolfGoal extends AvoidEntityGoal<Wolf> {
+        private final Wolf wolf;
+
+        public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) {
+            super(wolf, Wolf.class, distance, minSpeed, maxSpeed);
+            this.wolf = wolf;
+        }
+
+        @Override
+        public boolean canUse() {
+            return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves
+        }
+
+        @Override
+        public void start() {
+            this.wolf.setTarget(null);
+            super.start();
+        }
+
+        @Override
+        public void tick() {
+            this.wolf.setTarget(null);
+            super.tick();
+        }
+    }
+    // Purpur end
     private static final float START_HEALTH = 8.0F;
     private static final float TAME_HEALTH = 20.0F;
     private float interestedAngle;
@@ -118,6 +157,37 @@ public class Wolf extends TamableAnimal implements NeutralMob {
     public int getPurpurBreedTime() {
         return this.level.purpurConfig.wolfBreedingTicks;
     }
+
+    public boolean isRabid() {
+        return this.isRabid;
+    }
+
+    public void setRabid(boolean isRabid) {
+        this.isRabid = isRabid;
+        updatePathfinders(true);
+    }
+
+    public void updatePathfinders(boolean modifyEffects) {
+        this.targetSelector.removeGoal(PATHFINDER_VANILLA);
+        this.targetSelector.removeGoal(PATHFINDER_RABID);
+        if (this.isRabid) {
+            setTame(false);
+            setOwnerUUID(null);
+            this.targetSelector.addGoal(5, PATHFINDER_RABID);
+            if (modifyEffects) this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 1200));
+        } else {
+            this.targetSelector.addGoal(5, PATHFINDER_VANILLA);
+            this.stopBeingAngry();
+            if (modifyEffects) this.removeEffect(MobEffects.CONFUSION);
+        }
+    }
+
+    @Override
+    public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType type, @Nullable SpawnGroupData data, @Nullable CompoundTag nbt) {
+        this.isRabid = level.purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= level.purpurConfig.wolfNaturalRabid;
+        this.updatePathfinders(false);
+        return super.finalizeSpawn(world, difficulty, type, data, nbt);
+    }
     // Purpur end
 
     @Override
@@ -126,6 +196,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
         this.goalSelector.addGoal(1, new net.pl3x.purpur.entity.ai.HasRider(this)); // Purpur
         this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
         this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5D, 1.5D));
+        this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur
         this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F));
         this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0D, true));
         this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0D, 10.0F, 2.0F, false));
@@ -139,7 +210,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
         this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this));
         this.targetSelector.addGoal(3, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0])); // CraftBukkit - decompile error
         this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
-        this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR));
+        // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); // Purpur - moved to updatePathfinders()
         this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
         this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false));
         this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true));
@@ -184,6 +255,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
     public void addAdditionalSaveData(CompoundTag nbt) {
         super.addAdditionalSaveData(nbt);
         nbt.putByte("CollarColor", (byte) this.getCollarColor().getId());
+        nbt.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur
         this.addPersistentAngerSaveData(nbt);
     }
 
@@ -193,6 +265,10 @@ public class Wolf extends TamableAnimal implements NeutralMob {
         if (nbt.contains("CollarColor", 99)) {
             this.setCollarColor(DyeColor.byId(nbt.getInt("CollarColor")));
         }
+        // Purpur start
+        this.isRabid = nbt.getBoolean("Purpur.IsRabid");
+        this.updatePathfinders(false);
+        // Purpur end
 
         this.readPersistentAngerSaveData(this.level, nbt);
     }
@@ -237,6 +313,11 @@ public class Wolf extends TamableAnimal implements NeutralMob {
     public void tick() {
         super.tick();
         if (this.isAlive()) {
+            // Purpur start
+            if (this.age % 300 == 0 && this.isRabid()) {
+                this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 400));
+            }
+            // Purpur end
             this.interestedAngleO = this.interestedAngle;
             if (this.isInterested()) {
                 this.interestedAngle += (1.0F - this.interestedAngle) * 0.4F;
@@ -434,6 +515,20 @@ public class Wolf extends TamableAnimal implements NeutralMob {
 
                 return InteractionResult.SUCCESS;
             }
+            // Purpur start
+            else if (this.level.purpurConfig.wolfMilkCuresRabies && itemstack.getItem() == Items.MILK_BUCKET && this.isRabid()) {
+                if (!player.isCreative()) {
+                    player.setItemInHand(hand, new ItemStack(Items.BUCKET));
+                }
+                this.setRabid(false);
+                for (int i = 0; i < 10; ++i) {
+                    ((ServerLevel) level).sendParticles(((ServerLevel) level).players, null, ParticleTypes.HAPPY_VILLAGER,
+                            getX() + random.nextFloat(), getY() + (random.nextFloat() * 1.5), getZ() + random.nextFloat(), 1,
+                            random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
+                }
+                return InteractionResult.SUCCESS;
+            }
+            // Purpur end
 
             return super.mobInteract(player, hand);
         }
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
index 63fb94c323f1f0d63f46a42ccdb2ebe44ab77cc1..46338c0dcbe2edc03bb755950475bee95ff6602a 100644
--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
@@ -1768,6 +1768,8 @@ public class PurpurWorldConfig {
     public boolean wolfRidable = false;
     public boolean wolfRidableInWater = false;
     public double wolfMaxHealth = 8.0D;
+    public boolean wolfMilkCuresRabies = true;
+    public double wolfNaturalRabid = 0.0D;
     public int wolfBreedingTicks = 6000;
     private void wolfSettings() {
         wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable);
@@ -1778,6 +1780,8 @@ public class PurpurWorldConfig {
             set("mobs.wolf.attributes.max_health", oldValue);
         }
         wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth);
+        wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies);
+        wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid);
         wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks);
     }
 
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
index f856b42201c17f8da21251e54fcf052336916e70..ac0803d42be2f36a2f40487ee31413d059d2f0e2 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
@@ -43,4 +43,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf {
     public void setCollarColor(DyeColor color) {
         this.getHandle().setCollarColor(net.minecraft.world.item.DyeColor.byId(color.getWoolData()));
     }
+
+    // Purpur start
+    @Override
+    public boolean isRabid() {
+        return getHandle().isRabid();
+    }
+
+    @Override
+    public void setRabid(boolean isRabid) {
+        getHandle().setRabid(isRabid);
+    }
+    // Purpur end
 }
