package net.minecraft.world.entity.animal; import java.util.Optional; import java.util.UUID; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ItemParticleOption; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.BlockTags; import net.minecraft.tags.DamageTypeTags; import net.minecraft.tags.ItemTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; 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.entity.AgeableMob; import net.minecraft.world.entity.Crackiness; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.NeutralMob; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.TamableAnimal; import net.minecraft.world.entity.VariantHolder; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; 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.LeapAtTargetGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.SitWhenOrderedToGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.ai.goal.target.NonTameRandomTargetGoal; import net.minecraft.world.entity.ai.goal.target.OwnerHurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.OwnerHurtTargetGoal; import net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal; import net.minecraft.world.entity.ai.targeting.TargetingConditions; import net.minecraft.world.entity.animal.horse.AbstractHorse; import net.minecraft.world.entity.animal.horse.Llama; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.entity.monster.AbstractSkeleton; import net.minecraft.world.entity.monster.Creeper; import net.minecraft.world.entity.monster.Ghast; import net.minecraft.world.entity.player.Player; import net.minecraft.world.food.FoodProperties; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.DyeItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder> { private static final EntityDataAccessor DATA_INTERESTED_ID = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor DATA_COLLAR_COLOR = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_REMAINING_ANGER_TIME = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.INT); private static final EntityDataAccessor> DATA_VARIANT_ID = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.WOLF_VARIANT); public static final TargetingConditions.Selector PREY_SELECTOR = (livingEntity, serverLevel) -> { EntityType entityType = livingEntity.getType(); return entityType == EntityType.SHEEP || entityType == EntityType.RABBIT || entityType == EntityType.FOX; }; private static final float START_HEALTH = 8.0F; private static final float TAME_HEALTH = 40.0F; private static final float ARMOR_REPAIR_UNIT = 0.125F; public static final float DEFAULT_TAIL_ANGLE = (float) (Math.PI / 5); private float interestedAngle; private float interestedAngleO; private boolean isWet; private boolean isShaking; private float shakeAnim; private float shakeAnimO; private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39); @Nullable private UUID persistentAngerTarget; public Wolf(EntityType entityType, Level level) { super(entityType, level); this.setTame(false, false); this.setPathfindingMalus(PathType.POWDER_SNOW, -1.0F); this.setPathfindingMalus(PathType.DANGER_POWDER_SNOW, -1.0F); } @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5, DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)); this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal(this, Llama.class, 24.0F, 1.5, 1.5)); this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F)); this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, true)); this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0, 10.0F, 2.0F)); this.goalSelector.addGoal(7, new BreedGoal(this, 1.0)); this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(9, new BegGoal(this, 8.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(10, new RandomLookAroundGoal(this)); this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this)); this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this)); this.targetSelector.addGoal(3, new HurtByTargetGoal(this).setAlertOthers()); this.targetSelector.addGoal(4, new NearestAttackableTargetGoal(this, Player.class, 10, true, false, this::isAngryAt)); this.targetSelector.addGoal(5, new NonTameRandomTargetGoal(this, Animal.class, false, PREY_SELECTOR)); 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)); } public ResourceLocation getTexture() { WolfVariant wolfVariant = this.getVariant().value(); if (this.isTame()) { return wolfVariant.tameTexture(); } else { return this.isAngry() ? wolfVariant.angryTexture() : wolfVariant.wildTexture(); } } public Holder getVariant() { return this.entityData.get(DATA_VARIANT_ID); } public void setVariant(Holder variant) { this.entityData.set(DATA_VARIANT_ID, variant); } public static Builder createAttributes() { return Animal.createAnimalAttributes().add(Attributes.MOVEMENT_SPEED, 0.3F).add(Attributes.MAX_HEALTH, 8.0).add(Attributes.ATTACK_DAMAGE, 4.0); } @Override protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) { super.defineSynchedData(builder); RegistryAccess registryAccess = this.registryAccess(); Registry registry = registryAccess.lookupOrThrow(Registries.WOLF_VARIANT); builder.define(DATA_VARIANT_ID, (Holder)registry.get(WolfVariants.DEFAULT).or(registry::getAny).orElseThrow()); builder.define(DATA_INTERESTED_ID, false); builder.define(DATA_COLLAR_COLOR, DyeColor.RED.getId()); builder.define(DATA_REMAINING_ANGER_TIME, 0); } @Override protected void playStepSound(BlockPos pos, BlockState state) { this.playSound(SoundEvents.WOLF_STEP, 0.15F, 1.0F); } @Override public void addAdditionalSaveData(CompoundTag compound) { super.addAdditionalSaveData(compound); compound.putByte("CollarColor", (byte)this.getCollarColor().getId()); this.getVariant().unwrapKey().ifPresent(resourceKey -> compound.putString("variant", resourceKey.location().toString())); this.addPersistentAngerSaveData(compound); } @Override public void readAdditionalSaveData(CompoundTag compound) { super.readAdditionalSaveData(compound); Optional.ofNullable(ResourceLocation.tryParse(compound.getString("variant"))) .map(resourceLocation -> ResourceKey.create(Registries.WOLF_VARIANT, resourceLocation)) .flatMap(resourceKey -> this.registryAccess().lookupOrThrow(Registries.WOLF_VARIANT).get(resourceKey)) .ifPresent(this::setVariant); if (compound.contains("CollarColor", 99)) { this.setCollarColor(DyeColor.byId(compound.getInt("CollarColor"))); } this.readPersistentAngerSaveData(this.level(), compound); } @Nullable @Override public SpawnGroupData finalizeSpawn( ServerLevelAccessor serverLevelAccessor, DifficultyInstance difficultyInstance, EntitySpawnReason entitySpawnReason, @Nullable SpawnGroupData spawnGroupData ) { Holder holder = serverLevelAccessor.getBiome(this.blockPosition()); Holder holder2; if (spawnGroupData instanceof Wolf.WolfPackData wolfPackData) { holder2 = wolfPackData.type; } else { holder2 = WolfVariants.getSpawnVariant(this.registryAccess(), holder); spawnGroupData = new Wolf.WolfPackData(holder2); } this.setVariant(holder2); return super.finalizeSpawn(serverLevelAccessor, difficultyInstance, entitySpawnReason, spawnGroupData); } @Override protected SoundEvent getAmbientSound() { if (this.isAngry()) { return SoundEvents.WOLF_GROWL; } else if (this.random.nextInt(3) == 0) { return this.isTame() && this.getHealth() < 20.0F ? SoundEvents.WOLF_WHINE : SoundEvents.WOLF_PANT; } else { return SoundEvents.WOLF_AMBIENT; } } @Override protected SoundEvent getHurtSound(DamageSource damageSource) { return this.canArmorAbsorb(damageSource) ? SoundEvents.WOLF_ARMOR_DAMAGE : SoundEvents.WOLF_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.WOLF_DEATH; } @Override protected float getSoundVolume() { return 0.4F; } @Override public void aiStep() { super.aiStep(); if (!this.level().isClientSide && this.isWet && !this.isShaking && !this.isPathFinding() && this.onGround()) { this.isShaking = true; this.shakeAnim = 0.0F; this.shakeAnimO = 0.0F; this.level().broadcastEntityEvent(this, (byte)8); } if (!this.level().isClientSide) { this.updatePersistentAnger((ServerLevel)this.level(), true); } } @Override public void tick() { super.tick(); if (this.isAlive()) { this.interestedAngleO = this.interestedAngle; if (this.isInterested()) { this.interestedAngle = this.interestedAngle + (1.0F - this.interestedAngle) * 0.4F; } else { this.interestedAngle = this.interestedAngle + (0.0F - this.interestedAngle) * 0.4F; } if (this.isInWaterRainOrBubble()) { this.isWet = true; if (this.isShaking && !this.level().isClientSide) { this.level().broadcastEntityEvent(this, (byte)56); this.cancelShake(); } } else if ((this.isWet || this.isShaking) && this.isShaking) { if (this.shakeAnim == 0.0F) { this.playSound(SoundEvents.WOLF_SHAKE, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F); this.gameEvent(GameEvent.ENTITY_ACTION); } this.shakeAnimO = this.shakeAnim; this.shakeAnim += 0.05F; if (this.shakeAnimO >= 2.0F) { this.isWet = false; this.isShaking = false; this.shakeAnimO = 0.0F; this.shakeAnim = 0.0F; } if (this.shakeAnim > 0.4F) { float f = (float)this.getY(); int i = (int)(Mth.sin((this.shakeAnim - 0.4F) * (float) Math.PI) * 7.0F); Vec3 vec3 = this.getDeltaMovement(); for (int j = 0; j < i; j++) { float g = (this.random.nextFloat() * 2.0F - 1.0F) * this.getBbWidth() * 0.5F; float h = (this.random.nextFloat() * 2.0F - 1.0F) * this.getBbWidth() * 0.5F; this.level().addParticle(ParticleTypes.SPLASH, this.getX() + g, f + 0.8F, this.getZ() + h, vec3.x, vec3.y, vec3.z); } } } } } private void cancelShake() { this.isShaking = false; this.shakeAnim = 0.0F; this.shakeAnimO = 0.0F; } @Override public void die(DamageSource damageSource) { this.isWet = false; this.isShaking = false; this.shakeAnimO = 0.0F; this.shakeAnim = 0.0F; super.die(damageSource); } /** * Used when calculating the amount of shading to apply while the wolf is wet. */ public float getWetShade(float partialTicks) { return !this.isWet ? 1.0F : Math.min(0.75F + Mth.lerp(partialTicks, this.shakeAnimO, this.shakeAnim) / 2.0F * 0.25F, 1.0F); } public float getShakeAnim(float f) { return Mth.lerp(f, this.shakeAnimO, this.shakeAnim); } public float getHeadRollAngle(float partialTicks) { return Mth.lerp(partialTicks, this.interestedAngleO, this.interestedAngle) * 0.15F * (float) Math.PI; } @Override public int getMaxHeadXRot() { return this.isInSittingPose() ? 20 : super.getMaxHeadXRot(); } @Override public boolean hurtServer(ServerLevel serverLevel, DamageSource damageSource, float f) { if (this.isInvulnerableTo(serverLevel, damageSource)) { return false; } else { this.setOrderedToSit(false); return super.hurtServer(serverLevel, damageSource, f); } } @Override public boolean canUseSlot(EquipmentSlot slot) { return true; } @Override protected void actuallyHurt(ServerLevel serverLevel, DamageSource damageSource, float f) { if (!this.canArmorAbsorb(damageSource)) { super.actuallyHurt(serverLevel, damageSource, f); } else { ItemStack itemStack = this.getBodyArmorItem(); int i = itemStack.getDamageValue(); int j = itemStack.getMaxDamage(); itemStack.hurtAndBreak(Mth.ceil(f), this, EquipmentSlot.BODY); if (Crackiness.WOLF_ARMOR.byDamage(i, j) != Crackiness.WOLF_ARMOR.byDamage(this.getBodyArmorItem())) { this.playSound(SoundEvents.WOLF_ARMOR_CRACK); serverLevel.sendParticles( new ItemParticleOption(ParticleTypes.ITEM, Items.ARMADILLO_SCUTE.getDefaultInstance()), this.getX(), this.getY() + 1.0, this.getZ(), 20, 0.2, 0.1, 0.2, 0.1 ); } } } private boolean canArmorAbsorb(DamageSource damageSource) { return this.getBodyArmorItem().is(Items.WOLF_ARMOR) && !damageSource.is(DamageTypeTags.BYPASSES_WOLF_ARMOR); } @Override protected void applyTamingSideEffects() { if (this.isTame()) { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(40.0); this.setHealth(40.0F); } else { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0); } } @Override protected void hurtArmor(DamageSource damageSource, float damageAmount) { this.doHurtEquipment(damageSource, damageAmount, new EquipmentSlot[]{EquipmentSlot.BODY}); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemStack = player.getItemInHand(hand); Item item = itemStack.getItem(); if (this.isTame()) { if (this.isFood(itemStack) && this.getHealth() < this.getMaxHealth()) { this.usePlayerItem(player, hand, itemStack); FoodProperties foodProperties = itemStack.get(DataComponents.FOOD); float f = foodProperties != null ? foodProperties.nutrition() : 1.0F; this.heal(2.0F * f); return InteractionResult.SUCCESS; } if (!(item instanceof DyeItem dyeItem && this.isOwnedBy(player))) { if (this.isEquippableInSlot(itemStack, EquipmentSlot.BODY) && !this.isWearingBodyArmor() && this.isOwnedBy(player) && !this.isBaby()) { this.setBodyArmorItem(itemStack.copyWithCount(1)); itemStack.consume(1, player); return InteractionResult.SUCCESS; } if (!itemStack.is(Items.SHEARS) || !this.isOwnedBy(player) || !this.isWearingBodyArmor() || EnchantmentHelper.has(this.getBodyArmorItem(), EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) && !player.isCreative()) { if (this.isInSittingPose() && this.isWearingBodyArmor() && this.isOwnedBy(player) && this.getBodyArmorItem().isDamaged() && this.getBodyArmorItem().isValidRepairItem(itemStack)) { itemStack.shrink(1); this.playSound(SoundEvents.WOLF_ARMOR_REPAIR); ItemStack itemStack2 = this.getBodyArmorItem(); int i = (int)(itemStack2.getMaxDamage() * 0.125F); itemStack2.setDamageValue(Math.max(0, itemStack2.getDamageValue() - i)); return InteractionResult.SUCCESS; } InteractionResult interactionResult = super.mobInteract(player, hand); if (!interactionResult.consumesAction() && this.isOwnedBy(player)) { this.setOrderedToSit(!this.isOrderedToSit()); this.jumping = false; this.navigation.stop(); this.setTarget(null); return InteractionResult.SUCCESS.withoutItem(); } return interactionResult; } itemStack.hurtAndBreak(1, player, getSlotForHand(hand)); this.playSound(SoundEvents.ARMOR_UNEQUIP_WOLF); ItemStack itemStack2 = this.getBodyArmorItem(); this.setBodyArmorItem(ItemStack.EMPTY); if (this.level() instanceof ServerLevel serverLevel) { this.spawnAtLocation(serverLevel, itemStack2); } return InteractionResult.SUCCESS; } DyeColor dyeColor = dyeItem.getDyeColor(); if (dyeColor != this.getCollarColor()) { this.setCollarColor(dyeColor); itemStack.consume(1, player); return InteractionResult.SUCCESS; } } else if (!this.level().isClientSide && itemStack.is(Items.BONE) && !this.isAngry()) { itemStack.consume(1, player); this.tryToTame(player); return InteractionResult.SUCCESS_SERVER; } return super.mobInteract(player, hand); } private void tryToTame(Player player) { if (this.random.nextInt(3) == 0) { this.tame(player); this.navigation.stop(); this.setTarget(null); this.setOrderedToSit(true); this.level().broadcastEntityEvent(this, (byte)7); } else { this.level().broadcastEntityEvent(this, (byte)6); } } @Override public void handleEntityEvent(byte id) { if (id == 8) { this.isShaking = true; this.shakeAnim = 0.0F; this.shakeAnimO = 0.0F; } else if (id == 56) { this.cancelShake(); } else { super.handleEntityEvent(id); } } public float getTailAngle() { if (this.isAngry()) { return 1.5393804F; } else if (this.isTame()) { float f = this.getMaxHealth(); float g = (f - this.getHealth()) / f; return (0.55F - g * 0.4F) * (float) Math.PI; } else { return (float) (Math.PI / 5); } } @Override public boolean isFood(ItemStack stack) { return stack.is(ItemTags.WOLF_FOOD); } @Override public int getMaxSpawnClusterSize() { return 8; } @Override public int getRemainingPersistentAngerTime() { return this.entityData.get(DATA_REMAINING_ANGER_TIME); } @Override public void setRemainingPersistentAngerTime(int remainingPersistentAngerTime) { this.entityData.set(DATA_REMAINING_ANGER_TIME, remainingPersistentAngerTime); } @Override public void startPersistentAngerTimer() { this.setRemainingPersistentAngerTime(PERSISTENT_ANGER_TIME.sample(this.random)); } @Nullable @Override public UUID getPersistentAngerTarget() { return this.persistentAngerTarget; } @Override public void setPersistentAngerTarget(@Nullable UUID persistentAngerTarget) { this.persistentAngerTarget = persistentAngerTarget; } public DyeColor getCollarColor() { return DyeColor.byId(this.entityData.get(DATA_COLLAR_COLOR)); } private void setCollarColor(DyeColor collarColor) { this.entityData.set(DATA_COLLAR_COLOR, collarColor.getId()); } @Nullable public Wolf getBreedOffspring(ServerLevel level, AgeableMob otherParent) { Wolf wolf = EntityType.WOLF.create(level, EntitySpawnReason.BREEDING); if (wolf != null && otherParent instanceof Wolf wolf2) { if (this.random.nextBoolean()) { wolf.setVariant(this.getVariant()); } else { wolf.setVariant(wolf2.getVariant()); } if (this.isTame()) { wolf.setOwnerUUID(this.getOwnerUUID()); wolf.setTame(true, true); if (this.random.nextBoolean()) { wolf.setCollarColor(this.getCollarColor()); } else { wolf.setCollarColor(wolf2.getCollarColor()); } } } return wolf; } public void setIsInterested(boolean isInterested) { this.entityData.set(DATA_INTERESTED_ID, isInterested); } @Override public boolean canMate(Animal otherAnimal) { if (otherAnimal == this) { return false; } else if (!this.isTame()) { return false; } else if (!(otherAnimal instanceof Wolf wolf)) { return false; } else if (!wolf.isTame()) { return false; } else { return wolf.isInSittingPose() ? false : this.isInLove() && wolf.isInLove(); } } public boolean isInterested() { return this.entityData.get(DATA_INTERESTED_ID); } @Override public boolean wantsToAttack(LivingEntity target, LivingEntity owner) { if (target instanceof Creeper || target instanceof Ghast || target instanceof ArmorStand) { return false; } else if (target instanceof Wolf wolf) { return !wolf.isTame() || wolf.getOwner() != owner; } else if (target instanceof Player player && owner instanceof Player player2 && !player2.canHarmPlayer(player)) { return false; } else { return target instanceof AbstractHorse abstractHorse && abstractHorse.isTamed() ? false : !(target instanceof TamableAnimal tamableAnimal && tamableAnimal.isTame()); } } @Override public boolean canBeLeashed() { return !this.isAngry(); } @Override public Vec3 getLeashOffset() { return new Vec3(0.0, 0.6F * this.getEyeHeight(), this.getBbWidth() * 0.4F); } public static boolean checkWolfSpawnRules( EntityType entityType, LevelAccessor levelAccessor, EntitySpawnReason entitySpawnReason, BlockPos blockPos, RandomSource randomSource ) { return levelAccessor.getBlockState(blockPos.below()).is(BlockTags.WOLVES_SPAWNABLE_ON) && isBrightEnoughToSpawn(levelAccessor, blockPos); } class WolfAvoidEntityGoal extends AvoidEntityGoal { private final Wolf wolf; public WolfAvoidEntityGoal( final Wolf wolf, final Class entityClassToAvoid, final float maxDist, final double walkSpeedModifier, final double sprintSpeedModifier ) { super(wolf, entityClassToAvoid, maxDist, walkSpeedModifier, sprintSpeedModifier); this.wolf = wolf; } @Override public boolean canUse() { return super.canUse() && this.toAvoid instanceof Llama ? !this.wolf.isTame() && this.avoidLlama((Llama)this.toAvoid) : false; } private boolean avoidLlama(Llama llama) { return llama.getStrength() >= Wolf.this.random.nextInt(5); } @Override public void start() { Wolf.this.setTarget(null); super.start(); } @Override public void tick() { Wolf.this.setTarget(null); super.tick(); } } public static class WolfPackData extends AgeableMob.AgeableMobGroupData { public final Holder type; public WolfPackData(Holder type) { super(false); this.type = type; } } }