package net.minecraft.world.entity.monster; import com.google.common.annotations.VisibleForTesting; import java.time.LocalDate; import java.time.temporal.ChronoField; import java.util.List; import java.util.function.Predicate; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; 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.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ItemTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.ConversionParams; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySelector; 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.PathfinderMob; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.SpawnPlacements; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.BreakDoorGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal; import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; import net.minecraft.world.entity.ai.goal.RemoveBlockGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.ai.goal.ZombieAttackGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.ai.navigation.GroundPathNavigation; import net.minecraft.world.entity.ai.util.GoalUtils; import net.minecraft.world.entity.animal.Chicken; import net.minecraft.world.entity.animal.IronGolem; import net.minecraft.world.entity.animal.Turtle; import net.minecraft.world.entity.npc.AbstractVillager; import net.minecraft.world.entity.npc.Villager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; public class Zombie extends Monster { private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby"); private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(SPEED_MODIFIER_BABY_ID, 0.5, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge"); private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier( ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05F, AttributeModifier.Operation.ADD_VALUE ); private static final ResourceLocation LEADER_ZOMBIE_BONUS_ID = ResourceLocation.withDefaultNamespace("leader_zombie_bonus"); private static final ResourceLocation ZOMBIE_RANDOM_SPAWN_BONUS_ID = ResourceLocation.withDefaultNamespace("zombie_random_spawn_bonus"); private static final EntityDataAccessor DATA_BABY_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor DATA_SPECIAL_TYPE_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.INT); private static final EntityDataAccessor DATA_DROWNED_CONVERSION_ID = SynchedEntityData.defineId(Zombie.class, EntityDataSerializers.BOOLEAN); public static final float ZOMBIE_LEADER_CHANCE = 0.05F; public static final int REINFORCEMENT_ATTEMPTS = 50; public static final int REINFORCEMENT_RANGE_MAX = 40; public static final int REINFORCEMENT_RANGE_MIN = 7; private static final int NOT_CONVERTING = -1; private static final EntityDimensions BABY_DIMENSIONS = EntityType.ZOMBIE.getDimensions().scale(0.5F).withEyeHeight(0.93F); private static final float BREAK_DOOR_CHANCE = 0.1F; private static final Predicate DOOR_BREAKING_PREDICATE = p_34284_ -> p_34284_ == Difficulty.HARD; private static final boolean DEFAULT_BABY = false; private static final boolean DEFAULT_CAN_BREAK_DOORS = false; private static final int DEFAULT_IN_WATER_TIME = 0; private final BreakDoorGoal breakDoorGoal = new BreakDoorGoal(this, DOOR_BREAKING_PREDICATE); private boolean canBreakDoors = false; private int inWaterTime = 0; private int conversionTime; public Zombie(EntityType p_34271_, Level p_34272_) { super(p_34271_, p_34272_); } public Zombie(Level p_34274_) { this(EntityType.ZOMBIE, p_34274_); } @Override protected void registerGoals() { this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0, 3)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); this.addBehaviourGoals(); } protected void addBehaviourGoals() { this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0, false)); this.goalSelector.addGoal(6, new MoveThroughVillageGoal(this, 1.0, true, 4, this::canBreakDoors)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); } public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes() .add(Attributes.FOLLOW_RANGE, 35.0) .add(Attributes.MOVEMENT_SPEED, 0.23F) .add(Attributes.ATTACK_DAMAGE, 3.0) .add(Attributes.ARMOR, 2.0) .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE); } @Override protected void defineSynchedData(SynchedEntityData.Builder p_336115_) { super.defineSynchedData(p_336115_); p_336115_.define(DATA_BABY_ID, false); p_336115_.define(DATA_SPECIAL_TYPE_ID, 0); p_336115_.define(DATA_DROWNED_CONVERSION_ID, false); } public boolean isUnderWaterConverting() { return this.getEntityData().get(DATA_DROWNED_CONVERSION_ID); } public boolean canBreakDoors() { return this.canBreakDoors; } public void setCanBreakDoors(boolean p_34337_) { if (GoalUtils.hasGroundPathNavigation(this)) { if (this.canBreakDoors != p_34337_) { this.canBreakDoors = p_34337_; ((GroundPathNavigation)this.getNavigation()).setCanOpenDoors(p_34337_); if (p_34337_) { this.goalSelector.addGoal(1, this.breakDoorGoal); } else { this.goalSelector.removeGoal(this.breakDoorGoal); } } } else if (this.canBreakDoors) { this.goalSelector.removeGoal(this.breakDoorGoal); this.canBreakDoors = false; } } @Override public boolean isBaby() { return this.getEntityData().get(DATA_BABY_ID); } @Override protected int getBaseExperienceReward(ServerLevel p_365075_) { if (this.isBaby()) { this.xpReward = (int)(this.xpReward * 2.5); } return super.getBaseExperienceReward(p_365075_); } @Override public void setBaby(boolean p_34309_) { this.getEntityData().set(DATA_BABY_ID, p_34309_); if (this.level() != null && !this.level().isClientSide) { AttributeInstance attributeinstance = this.getAttribute(Attributes.MOVEMENT_SPEED); attributeinstance.removeModifier(SPEED_MODIFIER_BABY_ID); if (p_34309_) { attributeinstance.addTransientModifier(SPEED_MODIFIER_BABY); } } } @Override public void onSyncedDataUpdated(EntityDataAccessor p_34307_) { if (DATA_BABY_ID.equals(p_34307_)) { this.refreshDimensions(); } super.onSyncedDataUpdated(p_34307_); } protected boolean convertsInWater() { return true; } @Override public void tick() { if (!this.level().isClientSide && this.isAlive() && !this.isNoAi()) { if (this.isUnderWaterConverting()) { this.conversionTime--; if (this.conversionTime < 0) { this.doUnderWaterConversion(); } } else if (this.convertsInWater()) { if (this.isEyeInFluid(FluidTags.WATER)) { this.inWaterTime++; if (this.inWaterTime >= 600) { this.startUnderWaterConversion(300); } } else { this.inWaterTime = -1; } } } super.tick(); } @Override public void aiStep() { if (this.isAlive()) { boolean flag = this.isSunSensitive() && this.isSunBurnTick(); if (flag) { ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); if (!itemstack.isEmpty()) { if (itemstack.isDamageableItem()) { Item item = itemstack.getItem(); itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { this.onEquippedItemBroken(item, EquipmentSlot.HEAD); this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); } } flag = false; } if (flag) { this.igniteForSeconds(8.0F); } } } super.aiStep(); } private void startUnderWaterConversion(int p_34279_) { this.conversionTime = p_34279_; this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, true); } protected void doUnderWaterConversion() { this.convertToZombieType(EntityType.DROWNED); if (!this.isSilent()) { this.level().levelEvent(null, 1040, this.blockPosition(), 0); } } protected void convertToZombieType(EntityType p_34311_) { this.convertTo( p_34311_, ConversionParams.single(this, true, true), p_375145_ -> p_375145_.handleAttributes(p_375145_.level().getCurrentDifficultyAt(p_375145_.blockPosition()).getSpecialMultiplier()) ); } @VisibleForTesting public boolean convertVillagerToZombieVillager(ServerLevel p_369430_, Villager p_360910_) { ZombieVillager zombievillager = p_360910_.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(p_360910_, true, true), p_359258_ -> { p_359258_.finalizeSpawn(p_369430_, p_369430_.getCurrentDifficultyAt(p_359258_.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true)); p_359258_.setVillagerData(p_360910_.getVillagerData()); p_359258_.setGossips(p_360910_.getGossips().copy()); p_359258_.setTradeOffers(p_360910_.getOffers().copy()); p_359258_.setVillagerXp(p_360910_.getVillagerXp()); if (!this.isSilent()) { p_369430_.levelEvent(null, 1026, this.blockPosition(), 0); } }); return zombievillager != null; } protected boolean isSunSensitive() { return true; } @Override public boolean hurtServer(ServerLevel p_363771_, DamageSource p_363403_, float p_367238_) { if (!super.hurtServer(p_363771_, p_363403_, p_367238_)) { return false; } else { LivingEntity livingentity = this.getTarget(); if (livingentity == null && p_363403_.getEntity() instanceof LivingEntity) { livingentity = (LivingEntity)p_363403_.getEntity(); } if (livingentity != null && p_363771_.getDifficulty() == Difficulty.HARD && this.random.nextFloat() < this.getAttributeValue(Attributes.SPAWN_REINFORCEMENTS_CHANCE) && p_363771_.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { int i = Mth.floor(this.getX()); int j = Mth.floor(this.getY()); int k = Mth.floor(this.getZ()); EntityType entitytype = this.getType(); Zombie zombie = entitytype.create(p_363771_, EntitySpawnReason.REINFORCEMENT); if (zombie == null) { return true; } for (int l = 0; l < 50; l++) { int i1 = i + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1); int j1 = j + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1); int k1 = k + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1); BlockPos blockpos = new BlockPos(i1, j1, k1); if (SpawnPlacements.isSpawnPositionOk(entitytype, p_363771_, blockpos) && SpawnPlacements.checkSpawnRules(entitytype, p_363771_, EntitySpawnReason.REINFORCEMENT, blockpos, p_363771_.random)) { zombie.setPos(i1, j1, k1); if (!p_363771_.hasNearbyAlivePlayer(i1, j1, k1, 7.0) && p_363771_.isUnobstructed(zombie) && p_363771_.noCollision(zombie) && (zombie.canSpawnInLiquids() || !p_363771_.containsAnyLiquid(zombie.getBoundingBox()))) { zombie.setTarget(livingentity); zombie.finalizeSpawn(p_363771_, p_363771_.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, null); p_363771_.addFreshEntityWithPassengers(zombie); AttributeInstance attributeinstance = this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE); AttributeModifier attributemodifier = attributeinstance.getModifier(REINFORCEMENT_CALLER_CHARGE_ID); double d0 = attributemodifier != null ? attributemodifier.amount() : 0.0; attributeinstance.removeModifier(REINFORCEMENT_CALLER_CHARGE_ID); attributeinstance.addPermanentModifier(new AttributeModifier(REINFORCEMENT_CALLER_CHARGE_ID, d0 - 0.05, AttributeModifier.Operation.ADD_VALUE)); zombie.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).addPermanentModifier(ZOMBIE_REINFORCEMENT_CALLEE_CHARGE); break; } } } } return true; } } @Override public boolean doHurtTarget(ServerLevel p_365730_, Entity p_34276_) { boolean flag = super.doHurtTarget(p_365730_, p_34276_); if (flag) { float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty(); if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < f * 0.3F) { p_34276_.igniteForSeconds(2 * (int)f); } } return flag; } @Override protected SoundEvent getAmbientSound() { return SoundEvents.ZOMBIE_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource p_34327_) { return SoundEvents.ZOMBIE_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.ZOMBIE_DEATH; } protected SoundEvent getStepSound() { return SoundEvents.ZOMBIE_STEP; } @Override protected void playStepSound(BlockPos p_34316_, BlockState p_34317_) { this.playSound(this.getStepSound(), 0.15F, 1.0F); } @Override public EntityType getType() { return (EntityType)super.getType(); } protected boolean canSpawnInLiquids() { return false; } @Override protected void populateDefaultEquipmentSlots(RandomSource p_219165_, DifficultyInstance p_219166_) { super.populateDefaultEquipmentSlots(p_219165_, p_219166_); if (p_219165_.nextFloat() < (this.level().getDifficulty() == Difficulty.HARD ? 0.05F : 0.01F)) { int i = p_219165_.nextInt(3); if (i == 0) { this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); } else { this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SHOVEL)); } } } @Override public void addAdditionalSaveData(CompoundTag p_34319_) { super.addAdditionalSaveData(p_34319_); p_34319_.putBoolean("IsBaby", this.isBaby()); p_34319_.putBoolean("CanBreakDoors", this.canBreakDoors()); p_34319_.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); p_34319_.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); } @Override public void readAdditionalSaveData(CompoundTag p_34305_) { super.readAdditionalSaveData(p_34305_); this.setBaby(p_34305_.getBooleanOr("IsBaby", false)); this.setCanBreakDoors(p_34305_.getBooleanOr("CanBreakDoors", false)); this.inWaterTime = p_34305_.getIntOr("InWaterTime", 0); int i = p_34305_.getIntOr("DrownedConversionTime", -1); if (i != -1) { this.startUnderWaterConversion(i); } else { this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, false); } } @Override public boolean killedEntity(ServerLevel p_219160_, LivingEntity p_219161_) { boolean flag = super.killedEntity(p_219160_, p_219161_); if ((p_219160_.getDifficulty() == Difficulty.NORMAL || p_219160_.getDifficulty() == Difficulty.HARD) && p_219161_ instanceof Villager villager) { if (p_219160_.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) { return flag; } if (this.convertVillagerToZombieVillager(p_219160_, villager)) { flag = false; } } return flag; } @Override public EntityDimensions getDefaultDimensions(Pose p_328975_) { return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(p_328975_); } @Override public boolean canHoldItem(ItemStack p_34332_) { return p_34332_.is(ItemTags.EGGS) && this.isBaby() && this.isPassenger() ? false : super.canHoldItem(p_34332_); } @Override public boolean wantsToPickUp(ServerLevel p_365411_, ItemStack p_182400_) { return p_182400_.is(Items.GLOW_INK_SAC) ? false : super.wantsToPickUp(p_365411_, p_182400_); } @Nullable @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor p_34297_, DifficultyInstance p_34298_, EntitySpawnReason p_367250_, @Nullable SpawnGroupData p_34300_) { RandomSource randomsource = p_34297_.getRandom(); p_34300_ = super.finalizeSpawn(p_34297_, p_34298_, p_367250_, p_34300_); float f = p_34298_.getSpecialMultiplier(); if (p_367250_ != EntitySpawnReason.CONVERSION) { this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f); } if (p_34300_ == null) { p_34300_ = new Zombie.ZombieGroupData(getSpawnAsBabyOdds(randomsource), true); } if (p_34300_ instanceof Zombie.ZombieGroupData zombie$zombiegroupdata) { if (zombie$zombiegroupdata.isBaby) { this.setBaby(true); if (zombie$zombiegroupdata.canSpawnJockey) { if (randomsource.nextFloat() < 0.05) { List list = p_34297_.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0, 3.0, 5.0), EntitySelector.ENTITY_NOT_BEING_RIDDEN); if (!list.isEmpty()) { Chicken chicken = list.get(0); chicken.setChickenJockey(true); this.startRiding(chicken); } } else if (randomsource.nextFloat() < 0.05) { Chicken chicken1 = EntityType.CHICKEN.create(this.level(), EntitySpawnReason.JOCKEY); if (chicken1 != null) { chicken1.snapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F); chicken1.finalizeSpawn(p_34297_, p_34298_, EntitySpawnReason.JOCKEY, null); chicken1.setChickenJockey(true); this.startRiding(chicken1); p_34297_.addFreshEntity(chicken1); } } } } this.setCanBreakDoors(randomsource.nextFloat() < f * 0.1F); if (p_367250_ != EntitySpawnReason.CONVERSION) { this.populateDefaultEquipmentSlots(randomsource, p_34298_); this.populateDefaultEquipmentEnchantments(p_34297_, randomsource, p_34298_); } } if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { LocalDate localdate = LocalDate.now(); int i = localdate.get(ChronoField.DAY_OF_MONTH); int j = localdate.get(ChronoField.MONTH_OF_YEAR); if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); this.setDropChance(EquipmentSlot.HEAD, 0.0F); } } this.handleAttributes(f); return p_34300_; } @VisibleForTesting public void setInWaterTime(int p_361653_) { this.inWaterTime = p_361653_; } @VisibleForTesting public void setConversionTime(int p_365870_) { this.conversionTime = p_365870_; } public static boolean getSpawnAsBabyOdds(RandomSource p_219163_) { return p_219163_.nextFloat() < 0.05F; } protected void handleAttributes(float p_34340_) { this.randomizeReinforcementsChance(); this.getAttribute(Attributes.KNOCKBACK_RESISTANCE) .addOrReplacePermanentModifier(new AttributeModifier(RANDOM_SPAWN_BONUS_ID, this.random.nextDouble() * 0.05F, AttributeModifier.Operation.ADD_VALUE)); double d0 = this.random.nextDouble() * 1.5 * p_34340_; if (d0 > 1.0) { this.getAttribute(Attributes.FOLLOW_RANGE).addOrReplacePermanentModifier(new AttributeModifier(ZOMBIE_RANDOM_SPAWN_BONUS_ID, d0, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)); } if (this.random.nextFloat() < p_34340_ * 0.05F) { this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE) .addOrReplacePermanentModifier(new AttributeModifier(LEADER_ZOMBIE_BONUS_ID, this.random.nextDouble() * 0.25 + 0.5, AttributeModifier.Operation.ADD_VALUE)); this.getAttribute(Attributes.MAX_HEALTH) .addOrReplacePermanentModifier(new AttributeModifier(LEADER_ZOMBIE_BONUS_ID, this.random.nextDouble() * 3.0 + 1.0, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL)); this.setCanBreakDoors(true); } } protected void randomizeReinforcementsChance() { this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.1F); } @Override protected void dropCustomDeathLoot(ServerLevel p_344090_, DamageSource p_34291_, boolean p_34293_) { super.dropCustomDeathLoot(p_344090_, p_34291_, p_34293_); if (p_34291_.getEntity() instanceof Creeper creeper && creeper.canDropMobsSkull()) { ItemStack itemstack = this.getSkull(); if (!itemstack.isEmpty()) { creeper.increaseDroppedSkulls(); this.spawnAtLocation(p_344090_, itemstack); } } } protected ItemStack getSkull() { return new ItemStack(Items.ZOMBIE_HEAD); } class ZombieAttackTurtleEggGoal extends RemoveBlockGoal { ZombieAttackTurtleEggGoal(final PathfinderMob p_34344_, final double p_34345_, final int p_34346_) { super(Blocks.TURTLE_EGG, p_34344_, p_34345_, p_34346_); } @Override public void playDestroyProgressSound(LevelAccessor p_34351_, BlockPos p_34352_) { p_34351_.playSound(null, p_34352_, SoundEvents.ZOMBIE_DESTROY_EGG, SoundSource.HOSTILE, 0.5F, 0.9F + Zombie.this.random.nextFloat() * 0.2F); } @Override public void playBreakSound(Level p_34348_, BlockPos p_34349_) { p_34348_.playSound(null, p_34349_, SoundEvents.TURTLE_EGG_BREAK, SoundSource.BLOCKS, 0.7F, 0.9F + p_34348_.random.nextFloat() * 0.2F); } @Override public double acceptedDistance() { return 1.14; } } public static class ZombieGroupData implements SpawnGroupData { public final boolean isBaby; public final boolean canSpawnJockey; public ZombieGroupData(boolean p_34357_, boolean p_34358_) { this.isBaby = p_34357_; this.canSpawnJockey = p_34358_; } } }