package net.minecraft.world.entity.monster; import java.util.EnumSet; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.BiomeTags; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.InteractionHand; import net.minecraft.world.damagesource.DamageSource; 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.MoverType; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.MoveToBlockGoal; import net.minecraft.world.entity.ai.goal.RandomStrollGoal; import net.minecraft.world.entity.ai.goal.RangedAttackGoal; 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.navigation.WaterBoundPathNavigation; import net.minecraft.world.entity.ai.util.DefaultRandomPos; import net.minecraft.world.entity.animal.IronGolem; import net.minecraft.world.entity.animal.Turtle; import net.minecraft.world.entity.animal.axolotl.Axolotl; import net.minecraft.world.entity.npc.AbstractVillager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.entity.projectile.ThrownTrident; 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.LevelAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.pathfinder.Path; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.phys.Vec3; public class Drowned extends Zombie implements RangedAttackMob { public static final float NAUTILUS_SHELL_CHANCE = 0.03F; boolean searchingForLand; protected final WaterBoundPathNavigation waterNavigation; protected final GroundPathNavigation groundNavigation; public Drowned(EntityType p_32344_, Level p_32345_) { super(p_32344_, p_32345_); this.moveControl = new Drowned.DrownedMoveControl(this); this.setPathfindingMalus(PathType.WATER, 0.0F); this.waterNavigation = new WaterBoundPathNavigation(this, p_32345_); this.groundNavigation = new GroundPathNavigation(this, p_32345_); } public static AttributeSupplier.Builder createAttributes() { return Zombie.createAttributes().add(Attributes.STEP_HEIGHT, 1.0); } @Override protected void addBehaviourGoals() { this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0)); this.goalSelector.addGoal(2, new Drowned.DrownedTridentAttackGoal(this, 1.0, 40, 10.0F)); this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0, false)); this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0)); this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0, this.level().getSeaLevel())); this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (p_366864_, p_369731_) -> this.okTarget(p_366864_))); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); } @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor p_32372_, DifficultyInstance p_32373_, EntitySpawnReason p_361969_, @Nullable SpawnGroupData p_32375_) { p_32375_ = super.finalizeSpawn(p_32372_, p_32373_, p_361969_, p_32375_); if (this.getItemBySlot(EquipmentSlot.OFFHAND).isEmpty() && p_32372_.getRandom().nextFloat() < 0.03F) { this.setItemSlot(EquipmentSlot.OFFHAND, new ItemStack(Items.NAUTILUS_SHELL)); this.setGuaranteedDrop(EquipmentSlot.OFFHAND); } return p_32375_; } public static boolean checkDrownedSpawnRules( EntityType p_218956_, ServerLevelAccessor p_218957_, EntitySpawnReason p_363818_, BlockPos p_218959_, RandomSource p_218960_ ) { if (!p_218957_.getFluidState(p_218959_.below()).is(FluidTags.WATER) && !EntitySpawnReason.isSpawner(p_363818_)) { return false; } else { Holder holder = p_218957_.getBiome(p_218959_); boolean flag = p_218957_.getDifficulty() != Difficulty.PEACEFUL && (EntitySpawnReason.ignoresLightRequirements(p_363818_) || isDarkEnoughToSpawn(p_218957_, p_218959_, p_218960_)) && (EntitySpawnReason.isSpawner(p_363818_) || p_218957_.getFluidState(p_218959_).is(FluidTags.WATER)); if (!flag || !EntitySpawnReason.isSpawner(p_363818_) && p_363818_ != EntitySpawnReason.REINFORCEMENT) { return holder.is(BiomeTags.MORE_FREQUENT_DROWNED_SPAWNS) ? p_218960_.nextInt(15) == 0 && flag : p_218960_.nextInt(40) == 0 && isDeepEnoughToSpawn(p_218957_, p_218959_) && flag; } else { return true; } } } private static boolean isDeepEnoughToSpawn(LevelAccessor p_32367_, BlockPos p_32368_) { return p_32368_.getY() < p_32367_.getSeaLevel() - 5; } @Override protected SoundEvent getAmbientSound() { return this.isInWater() ? SoundEvents.DROWNED_AMBIENT_WATER : SoundEvents.DROWNED_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource p_32386_) { return this.isInWater() ? SoundEvents.DROWNED_HURT_WATER : SoundEvents.DROWNED_HURT; } @Override protected SoundEvent getDeathSound() { return this.isInWater() ? SoundEvents.DROWNED_DEATH_WATER : SoundEvents.DROWNED_DEATH; } @Override protected SoundEvent getStepSound() { return SoundEvents.DROWNED_STEP; } @Override protected SoundEvent getSwimSound() { return SoundEvents.DROWNED_SWIM; } @Override protected boolean canSpawnInLiquids() { return true; } @Override protected ItemStack getSkull() { return ItemStack.EMPTY; } @Override protected void populateDefaultEquipmentSlots(RandomSource p_218953_, DifficultyInstance p_218954_) { if (p_218953_.nextFloat() > 0.9) { int i = p_218953_.nextInt(16); if (i < 10) { this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.TRIDENT)); } else { this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.FISHING_ROD)); } } } @Override protected boolean canReplaceCurrentItem(ItemStack p_32364_, ItemStack p_32365_, EquipmentSlot p_368868_) { return p_32365_.is(Items.NAUTILUS_SHELL) ? false : super.canReplaceCurrentItem(p_32364_, p_32365_, p_368868_); } @Override protected boolean convertsInWater() { return false; } @Override public boolean checkSpawnObstruction(LevelReader p_32370_) { return p_32370_.isUnobstructed(this); } public boolean okTarget(@Nullable LivingEntity p_32396_) { return p_32396_ != null ? !this.level().isBrightOutside() || p_32396_.isInWater() : false; } @Override public boolean isPushedByFluid() { return !this.isSwimming(); } boolean wantsToSwim() { if (this.searchingForLand) { return true; } else { LivingEntity livingentity = this.getTarget(); return livingentity != null && livingentity.isInWater(); } } @Override public void travel(Vec3 p_32394_) { if (this.isUnderWater() && this.wantsToSwim()) { this.moveRelative(0.01F, p_32394_); this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.9)); } else { super.travel(p_32394_); } } @Override public void updateSwimming() { if (!this.level().isClientSide) { if (this.isEffectiveAi() && this.isUnderWater() && this.wantsToSwim()) { this.navigation = this.waterNavigation; this.setSwimming(true); } else { this.navigation = this.groundNavigation; this.setSwimming(false); } } } @Override public boolean isVisuallySwimming() { return this.isSwimming(); } protected boolean closeToNextPos() { Path path = this.getNavigation().getPath(); if (path != null) { BlockPos blockpos = path.getTarget(); if (blockpos != null) { double d0 = this.distanceToSqr(blockpos.getX(), blockpos.getY(), blockpos.getZ()); if (d0 < 4.0) { return true; } } } return false; } @Override public void performRangedAttack(LivingEntity p_32356_, float p_32357_) { ItemStack itemstack = this.getMainHandItem(); ItemStack itemstack1 = itemstack.is(Items.TRIDENT) ? itemstack : new ItemStack(Items.TRIDENT); ThrownTrident throwntrident = new ThrownTrident(this.level(), this, itemstack1); double d0 = p_32356_.getX() - this.getX(); double d1 = p_32356_.getY(0.3333333333333333) - throwntrident.getY(); double d2 = p_32356_.getZ() - this.getZ(); double d3 = Math.sqrt(d0 * d0 + d2 * d2); if (this.level() instanceof ServerLevel serverlevel) { Projectile.spawnProjectileUsingShoot(throwntrident, serverlevel, itemstack1, d0, d1 + d3 * 0.2F, d2, 1.6F, 14 - this.level().getDifficulty().getId() * 4); } this.playSound(SoundEvents.DROWNED_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); } @Override public TagKey getPreferredWeaponType() { return ItemTags.DROWNED_PREFERRED_WEAPONS; } public void setSearchingForLand(boolean p_32399_) { this.searchingForLand = p_32399_; } static class DrownedAttackGoal extends ZombieAttackGoal { private final Drowned drowned; public DrownedAttackGoal(Drowned p_32402_, double p_32403_, boolean p_32404_) { super(p_32402_, p_32403_, p_32404_); this.drowned = p_32402_; } @Override public boolean canUse() { return super.canUse() && this.drowned.okTarget(this.drowned.getTarget()); } @Override public boolean canContinueToUse() { return super.canContinueToUse() && this.drowned.okTarget(this.drowned.getTarget()); } } static class DrownedGoToBeachGoal extends MoveToBlockGoal { private final Drowned drowned; public DrownedGoToBeachGoal(Drowned p_32409_, double p_32410_) { super(p_32409_, p_32410_, 8, 2); this.drowned = p_32409_; } @Override public boolean canUse() { return super.canUse() && !this.drowned.level().isBrightOutside() && this.drowned.isInWater() && this.drowned.getY() >= this.drowned.level().getSeaLevel() - 3; } @Override public boolean canContinueToUse() { return super.canContinueToUse(); } @Override protected boolean isValidTarget(LevelReader p_32413_, BlockPos p_32414_) { BlockPos blockpos = p_32414_.above(); return p_32413_.isEmptyBlock(blockpos) && p_32413_.isEmptyBlock(blockpos.above()) ? p_32413_.getBlockState(p_32414_).entityCanStandOn(p_32413_, p_32414_, this.drowned) : false; } @Override public void start() { this.drowned.setSearchingForLand(false); this.drowned.navigation = this.drowned.groundNavigation; super.start(); } @Override public void stop() { super.stop(); } } static class DrownedGoToWaterGoal extends Goal { private final PathfinderMob mob; private double wantedX; private double wantedY; private double wantedZ; private final double speedModifier; private final Level level; public DrownedGoToWaterGoal(PathfinderMob p_32425_, double p_32426_) { this.mob = p_32425_; this.speedModifier = p_32426_; this.level = p_32425_.level(); this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canUse() { if (!this.level.isBrightOutside()) { return false; } else if (this.mob.isInWater()) { return false; } else { Vec3 vec3 = this.getWaterPos(); if (vec3 == null) { return false; } else { this.wantedX = vec3.x; this.wantedY = vec3.y; this.wantedZ = vec3.z; return true; } } } @Override public boolean canContinueToUse() { return !this.mob.getNavigation().isDone(); } @Override public void start() { this.mob.getNavigation().moveTo(this.wantedX, this.wantedY, this.wantedZ, this.speedModifier); } @Nullable private Vec3 getWaterPos() { RandomSource randomsource = this.mob.getRandom(); BlockPos blockpos = this.mob.blockPosition(); for (int i = 0; i < 10; i++) { BlockPos blockpos1 = blockpos.offset(randomsource.nextInt(20) - 10, 2 - randomsource.nextInt(8), randomsource.nextInt(20) - 10); if (this.level.getBlockState(blockpos1).is(Blocks.WATER)) { return Vec3.atBottomCenterOf(blockpos1); } } return null; } } static class DrownedMoveControl extends MoveControl { private final Drowned drowned; public DrownedMoveControl(Drowned p_32433_) { super(p_32433_); this.drowned = p_32433_; } @Override public void tick() { LivingEntity livingentity = this.drowned.getTarget(); if (this.drowned.wantsToSwim() && this.drowned.isInWater()) { if (livingentity != null && livingentity.getY() > this.drowned.getY() || this.drowned.searchingForLand) { this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, 0.002, 0.0)); } if (this.operation != MoveControl.Operation.MOVE_TO || this.drowned.getNavigation().isDone()) { this.drowned.setSpeed(0.0F); return; } double d0 = this.wantedX - this.drowned.getX(); double d1 = this.wantedY - this.drowned.getY(); double d2 = this.wantedZ - this.drowned.getZ(); double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); d1 /= d3; float f = (float)(Mth.atan2(d2, d0) * 180.0F / (float)Math.PI) - 90.0F; this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), f, 90.0F)); this.drowned.yBodyRot = this.drowned.getYRot(); float f1 = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1); this.drowned.setSpeed(f2); this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(f2 * d0 * 0.005, f2 * d1 * 0.1, f2 * d2 * 0.005)); } else { if (!this.drowned.onGround()) { this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, -0.008, 0.0)); } super.tick(); } } } static class DrownedSwimUpGoal extends Goal { private final Drowned drowned; private final double speedModifier; private final int seaLevel; private boolean stuck; public DrownedSwimUpGoal(Drowned p_32440_, double p_32441_, int p_32442_) { this.drowned = p_32440_; this.speedModifier = p_32441_; this.seaLevel = p_32442_; } @Override public boolean canUse() { return !this.drowned.level().isBrightOutside() && this.drowned.isInWater() && this.drowned.getY() < this.seaLevel - 2; } @Override public boolean canContinueToUse() { return this.canUse() && !this.stuck; } @Override public void tick() { if (this.drowned.getY() < this.seaLevel - 1 && (this.drowned.getNavigation().isDone() || this.drowned.closeToNextPos())) { Vec3 vec3 = DefaultRandomPos.getPosTowards( this.drowned, 4, 8, new Vec3(this.drowned.getX(), this.seaLevel - 1, this.drowned.getZ()), (float) (Math.PI / 2) ); if (vec3 == null) { this.stuck = true; return; } this.drowned.getNavigation().moveTo(vec3.x, vec3.y, vec3.z, this.speedModifier); } } @Override public void start() { this.drowned.setSearchingForLand(true); this.stuck = false; } @Override public void stop() { this.drowned.setSearchingForLand(false); } } static class DrownedTridentAttackGoal extends RangedAttackGoal { private final Drowned drowned; public DrownedTridentAttackGoal(RangedAttackMob p_32450_, double p_32451_, int p_32452_, float p_32453_) { super(p_32450_, p_32451_, p_32452_, p_32453_); this.drowned = (Drowned)p_32450_; } @Override public boolean canUse() { return super.canUse() && this.drowned.getMainHandItem().is(Items.TRIDENT); } @Override public void start() { super.start(); this.drowned.setAggressive(true); this.drowned.startUsingItem(InteractionHand.MAIN_HAND); } @Override public void stop() { super.stop(); this.drowned.stopUsingItem(); this.drowned.setAggressive(false); } } }