package net.minecraft.world.entity.animal; import javax.annotation.Nullable; import net.minecraft.advancements.CriteriaTriggers; 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.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stats; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ItemTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.EntityAttachment; import net.minecraft.world.entity.EntityAttachments; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.LightningBolt; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; 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.BreedGoal; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MoveToBlockGoal; import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.ai.goal.RandomStrollGoal; import net.minecraft.world.entity.ai.goal.TemptGoal; import net.minecraft.world.entity.ai.navigation.AmphibiousPathNavigation; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.ai.targeting.TargetingConditions; import net.minecraft.world.entity.ai.util.DefaultRandomPos; import net.minecraft.world.entity.player.Player; 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.LevelReader; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.TurtleEggBlock; 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; public class Turtle extends Animal { private static final EntityDataAccessor HAS_EGG = SynchedEntityData.defineId(Turtle.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor LAYING_EGG = SynchedEntityData.defineId(Turtle.class, EntityDataSerializers.BOOLEAN); private static final float BABY_SCALE = 0.3F; private static final EntityDimensions BABY_DIMENSIONS = EntityType.TURTLE .getDimensions() .withAttachments(EntityAttachments.builder().attach(EntityAttachment.PASSENGER, 0.0F, EntityType.TURTLE.getHeight(), -0.25F)) .scale(0.3F); private static final boolean DEFAULT_HAS_EGG = false; int layEggCounter; public static final TargetingConditions.Selector BABY_ON_LAND_SELECTOR = (p_390657_, p_390658_) -> p_390657_.isBaby() && !p_390657_.isInWater(); BlockPos homePos = BlockPos.ZERO; @Nullable BlockPos travelPos; boolean goingHome; public Turtle(EntityType p_30132_, Level p_30133_) { super(p_30132_, p_30133_); this.setPathfindingMalus(PathType.WATER, 0.0F); this.setPathfindingMalus(PathType.DOOR_IRON_CLOSED, -1.0F); this.setPathfindingMalus(PathType.DOOR_WOOD_CLOSED, -1.0F); this.setPathfindingMalus(PathType.DOOR_OPEN, -1.0F); this.moveControl = new Turtle.TurtleMoveControl(this); } public void setHomePos(BlockPos p_30220_) { this.homePos = p_30220_; } public boolean hasEgg() { return this.entityData.get(HAS_EGG); } void setHasEgg(boolean p_30235_) { this.entityData.set(HAS_EGG, p_30235_); } public boolean isLayingEgg() { return this.entityData.get(LAYING_EGG); } void setLayingEgg(boolean p_30237_) { this.layEggCounter = p_30237_ ? 1 : 0; this.entityData.set(LAYING_EGG, p_30237_); } @Override protected void defineSynchedData(SynchedEntityData.Builder p_327769_) { super.defineSynchedData(p_327769_); p_327769_.define(HAS_EGG, false); p_327769_.define(LAYING_EGG, false); } @Override public void addAdditionalSaveData(CompoundTag p_30176_) { super.addAdditionalSaveData(p_30176_); p_30176_.store("home_pos", BlockPos.CODEC, this.homePos); p_30176_.putBoolean("has_egg", this.hasEgg()); } @Override public void readAdditionalSaveData(CompoundTag p_30162_) { this.setHomePos(p_30162_.read("home_pos", BlockPos.CODEC).orElse(this.blockPosition())); super.readAdditionalSaveData(p_30162_); this.setHasEgg(p_30162_.getBooleanOr("has_egg", false)); } @Nullable @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor p_30153_, DifficultyInstance p_30154_, EntitySpawnReason p_362811_, @Nullable SpawnGroupData p_30156_) { this.setHomePos(this.blockPosition()); return super.finalizeSpawn(p_30153_, p_30154_, p_362811_, p_30156_); } public static boolean checkTurtleSpawnRules( EntityType p_218277_, LevelAccessor p_218278_, EntitySpawnReason p_362525_, BlockPos p_218280_, RandomSource p_218281_ ) { return p_218280_.getY() < p_218278_.getSeaLevel() + 4 && TurtleEggBlock.onSand(p_218278_, p_218280_) && isBrightEnoughToSpawn(p_218278_, p_218280_); } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2)); this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0)); this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0)); this.goalSelector.addGoal(2, new TemptGoal(this, 1.1, p_326986_ -> p_326986_.is(ItemTags.TURTLE_FOOD), false)); this.goalSelector.addGoal(3, new Turtle.TurtleGoToWaterGoal(this, 1.0)); this.goalSelector.addGoal(4, new Turtle.TurtleGoHomeGoal(this, 1.0)); this.goalSelector.addGoal(7, new Turtle.TurtleTravelGoal(this, 1.0)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(9, new Turtle.TurtleRandomStrollGoal(this, 1.0, 100)); } public static AttributeSupplier.Builder createAttributes() { return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 30.0).add(Attributes.MOVEMENT_SPEED, 0.25).add(Attributes.STEP_HEIGHT, 1.0); } @Override public boolean isPushedByFluid() { return false; } @Override public int getAmbientSoundInterval() { return 200; } @Nullable @Override protected SoundEvent getAmbientSound() { return !this.isInWater() && this.onGround() && !this.isBaby() ? SoundEvents.TURTLE_AMBIENT_LAND : super.getAmbientSound(); } @Override protected void playSwimSound(float p_30192_) { super.playSwimSound(p_30192_ * 1.5F); } @Override protected SoundEvent getSwimSound() { return SoundEvents.TURTLE_SWIM; } @Nullable @Override protected SoundEvent getHurtSound(DamageSource p_30202_) { return this.isBaby() ? SoundEvents.TURTLE_HURT_BABY : SoundEvents.TURTLE_HURT; } @Nullable @Override protected SoundEvent getDeathSound() { return this.isBaby() ? SoundEvents.TURTLE_DEATH_BABY : SoundEvents.TURTLE_DEATH; } @Override protected void playStepSound(BlockPos p_30173_, BlockState p_30174_) { SoundEvent soundevent = this.isBaby() ? SoundEvents.TURTLE_SHAMBLE_BABY : SoundEvents.TURTLE_SHAMBLE; this.playSound(soundevent, 0.15F, 1.0F); } @Override public boolean canFallInLove() { return super.canFallInLove() && !this.hasEgg(); } @Override protected float nextStep() { return this.moveDist + 0.15F; } @Override public float getAgeScale() { return this.isBaby() ? 0.3F : 1.0F; } @Override protected PathNavigation createNavigation(Level p_30171_) { return new Turtle.TurtlePathNavigation(this, p_30171_); } @Nullable @Override public AgeableMob getBreedOffspring(ServerLevel p_149068_, AgeableMob p_149069_) { return EntityType.TURTLE.create(p_149068_, EntitySpawnReason.BREEDING); } @Override public boolean isFood(ItemStack p_30231_) { return p_30231_.is(ItemTags.TURTLE_FOOD); } @Override public float getWalkTargetValue(BlockPos p_30159_, LevelReader p_30160_) { if (!this.goingHome && p_30160_.getFluidState(p_30159_).is(FluidTags.WATER)) { return 10.0F; } else { return TurtleEggBlock.onSand(p_30160_, p_30159_) ? 10.0F : p_30160_.getPathfindingCostFromLightLevels(p_30159_); } } @Override public void aiStep() { super.aiStep(); if (this.isAlive() && this.isLayingEgg() && this.layEggCounter >= 1 && this.layEggCounter % 5 == 0) { BlockPos blockpos = this.blockPosition(); if (TurtleEggBlock.onSand(this.level(), blockpos)) { this.level().levelEvent(2001, blockpos, Block.getId(this.level().getBlockState(blockpos.below()))); this.gameEvent(GameEvent.ENTITY_ACTION); } } } @Override protected void ageBoundaryReached() { super.ageBoundaryReached(); if (!this.isBaby() && this.level() instanceof ServerLevel serverlevel && serverlevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { this.spawnAtLocation(serverlevel, Items.TURTLE_SCUTE, 1); } } @Override public void travel(Vec3 p_30218_) { if (this.isInWater()) { this.moveRelative(0.1F, p_30218_); this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.9)); if (this.getTarget() == null && (!this.goingHome || !this.homePos.closerToCenterThan(this.position(), 20.0))) { this.setDeltaMovement(this.getDeltaMovement().add(0.0, -0.005, 0.0)); } } else { super.travel(p_30218_); } } @Override public boolean canBeLeashed() { return false; } @Override public void thunderHit(ServerLevel p_30140_, LightningBolt p_30141_) { this.hurtServer(p_30140_, this.damageSources().lightningBolt(), Float.MAX_VALUE); } @Override public EntityDimensions getDefaultDimensions(Pose p_330174_) { return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(p_330174_); } static class TurtleBreedGoal extends BreedGoal { private final Turtle turtle; TurtleBreedGoal(Turtle p_30244_, double p_30245_) { super(p_30244_, p_30245_); this.turtle = p_30244_; } @Override public boolean canUse() { return super.canUse() && !this.turtle.hasEgg(); } @Override protected void breed() { ServerPlayer serverplayer = this.animal.getLoveCause(); if (serverplayer == null && this.partner.getLoveCause() != null) { serverplayer = this.partner.getLoveCause(); } if (serverplayer != null) { serverplayer.awardStat(Stats.ANIMALS_BRED); CriteriaTriggers.BRED_ANIMALS.trigger(serverplayer, this.animal, this.partner, null); } this.turtle.setHasEgg(true); this.animal.setAge(6000); this.partner.setAge(6000); this.animal.resetLove(); this.partner.resetLove(); RandomSource randomsource = this.animal.getRandom(); if (getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { this.level .addFreshEntity( new ExperienceOrb( this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), randomsource.nextInt(7) + 1 ) ); } } } static class TurtleGoHomeGoal extends Goal { private final Turtle turtle; private final double speedModifier; private boolean stuck; private int closeToHomeTryTicks; private static final int GIVE_UP_TICKS = 600; TurtleGoHomeGoal(Turtle p_30253_, double p_30254_) { this.turtle = p_30253_; this.speedModifier = p_30254_; } @Override public boolean canUse() { if (this.turtle.isBaby()) { return false; } else if (this.turtle.hasEgg()) { return true; } else { return this.turtle.getRandom().nextInt(reducedTickDelay(700)) != 0 ? false : !this.turtle.homePos.closerToCenterThan(this.turtle.position(), 64.0); } } @Override public void start() { this.turtle.goingHome = true; this.stuck = false; this.closeToHomeTryTicks = 0; } @Override public void stop() { this.turtle.goingHome = false; } @Override public boolean canContinueToUse() { return !this.turtle.homePos.closerToCenterThan(this.turtle.position(), 7.0) && !this.stuck && this.closeToHomeTryTicks <= this.adjustedTickDelay(600); } @Override public void tick() { BlockPos blockpos = this.turtle.homePos; boolean flag = blockpos.closerToCenterThan(this.turtle.position(), 16.0); if (flag) { this.closeToHomeTryTicks++; } if (this.turtle.getNavigation().isDone()) { Vec3 vec3 = Vec3.atBottomCenterOf(blockpos); Vec3 vec31 = DefaultRandomPos.getPosTowards(this.turtle, 16, 3, vec3, (float) (Math.PI / 10)); if (vec31 == null) { vec31 = DefaultRandomPos.getPosTowards(this.turtle, 8, 7, vec3, (float) (Math.PI / 2)); } if (vec31 != null && !flag && !this.turtle.level().getBlockState(BlockPos.containing(vec31)).is(Blocks.WATER)) { vec31 = DefaultRandomPos.getPosTowards(this.turtle, 16, 5, vec3, (float) (Math.PI / 2)); } if (vec31 == null) { this.stuck = true; return; } this.turtle.getNavigation().moveTo(vec31.x, vec31.y, vec31.z, this.speedModifier); } } } static class TurtleGoToWaterGoal extends MoveToBlockGoal { private static final int GIVE_UP_TICKS = 1200; private final Turtle turtle; TurtleGoToWaterGoal(Turtle p_30262_, double p_30263_) { super(p_30262_, p_30262_.isBaby() ? 2.0 : p_30263_, 24); this.turtle = p_30262_; this.verticalSearchStart = -1; } @Override public boolean canContinueToUse() { return !this.turtle.isInWater() && this.tryTicks <= 1200 && this.isValidTarget(this.turtle.level(), this.blockPos); } @Override public boolean canUse() { if (this.turtle.isBaby() && !this.turtle.isInWater()) { return super.canUse(); } else { return !this.turtle.goingHome && !this.turtle.isInWater() && !this.turtle.hasEgg() ? super.canUse() : false; } } @Override public boolean shouldRecalculatePath() { return this.tryTicks % 160 == 0; } @Override protected boolean isValidTarget(LevelReader p_30270_, BlockPos p_30271_) { return p_30270_.getBlockState(p_30271_).is(Blocks.WATER); } } static class TurtleLayEggGoal extends MoveToBlockGoal { private final Turtle turtle; TurtleLayEggGoal(Turtle p_30276_, double p_30277_) { super(p_30276_, p_30277_, 16); this.turtle = p_30276_; } @Override public boolean canUse() { return this.turtle.hasEgg() && this.turtle.homePos.closerToCenterThan(this.turtle.position(), 9.0) ? super.canUse() : false; } @Override public boolean canContinueToUse() { return super.canContinueToUse() && this.turtle.hasEgg() && this.turtle.homePos.closerToCenterThan(this.turtle.position(), 9.0); } @Override public void tick() { super.tick(); BlockPos blockpos = this.turtle.blockPosition(); if (!this.turtle.isInWater() && this.isReachedTarget()) { if (this.turtle.layEggCounter < 1) { this.turtle.setLayingEgg(true); } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) { Level level = this.turtle.level(); level.playSound(null, blockpos, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + level.random.nextFloat() * 0.2F); BlockPos blockpos1 = this.blockPos.above(); BlockState blockstate = Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); level.setBlock(blockpos1, blockstate, 3); level.gameEvent(GameEvent.BLOCK_PLACE, blockpos1, GameEvent.Context.of(this.turtle, blockstate)); this.turtle.setHasEgg(false); this.turtle.setLayingEgg(false); this.turtle.setInLoveTime(600); } if (this.turtle.isLayingEgg()) { this.turtle.layEggCounter++; } } } @Override protected boolean isValidTarget(LevelReader p_30280_, BlockPos p_30281_) { return !p_30280_.isEmptyBlock(p_30281_.above()) ? false : TurtleEggBlock.isSand(p_30280_, p_30281_); } } static class TurtleMoveControl extends MoveControl { private final Turtle turtle; TurtleMoveControl(Turtle p_30286_) { super(p_30286_); this.turtle = p_30286_; } private void updateSpeed() { if (this.turtle.isInWater()) { this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0, 0.005, 0.0)); if (!this.turtle.homePos.closerToCenterThan(this.turtle.position(), 16.0)) { this.turtle.setSpeed(Math.max(this.turtle.getSpeed() / 2.0F, 0.08F)); } if (this.turtle.isBaby()) { this.turtle.setSpeed(Math.max(this.turtle.getSpeed() / 3.0F, 0.06F)); } } else if (this.turtle.onGround()) { this.turtle.setSpeed(Math.max(this.turtle.getSpeed() / 2.0F, 0.06F)); } } @Override public void tick() { this.updateSpeed(); if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) { double d0 = this.wantedX - this.turtle.getX(); double d1 = this.wantedY - this.turtle.getY(); double d2 = this.wantedZ - this.turtle.getZ(); double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); if (d3 < 1.0E-5F) { this.mob.setSpeed(0.0F); } else { d1 /= d3; float f = (float)(Mth.atan2(d2, d0) * 180.0F / (float)Math.PI) - 90.0F; this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F)); this.turtle.yBodyRot = this.turtle.getYRot(); float f1 = (float)(this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1)); this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0, this.turtle.getSpeed() * d1 * 0.1, 0.0)); } } else { this.turtle.setSpeed(0.0F); } } } static class TurtlePanicGoal extends PanicGoal { TurtlePanicGoal(Turtle p_30290_, double p_30291_) { super(p_30290_, p_30291_); } @Override public boolean canUse() { if (!this.shouldPanic()) { return false; } else { BlockPos blockpos = this.lookForWater(this.mob.level(), this.mob, 7); if (blockpos != null) { this.posX = blockpos.getX(); this.posY = blockpos.getY(); this.posZ = blockpos.getZ(); return true; } else { return this.findRandomPosition(); } } } } static class TurtlePathNavigation extends AmphibiousPathNavigation { TurtlePathNavigation(Turtle p_30294_, Level p_30295_) { super(p_30294_, p_30295_); } @Override public boolean isStableDestination(BlockPos p_30300_) { return this.mob instanceof Turtle turtle && turtle.travelPos != null ? this.level.getBlockState(p_30300_).is(Blocks.WATER) : !this.level.getBlockState(p_30300_.below()).isAir(); } } static class TurtleRandomStrollGoal extends RandomStrollGoal { private final Turtle turtle; TurtleRandomStrollGoal(Turtle p_30303_, double p_30304_, int p_30305_) { super(p_30303_, p_30304_, p_30305_); this.turtle = p_30303_; } @Override public boolean canUse() { return !this.mob.isInWater() && !this.turtle.goingHome && !this.turtle.hasEgg() ? super.canUse() : false; } } static class TurtleTravelGoal extends Goal { private final Turtle turtle; private final double speedModifier; private boolean stuck; TurtleTravelGoal(Turtle p_30333_, double p_30334_) { this.turtle = p_30333_; this.speedModifier = p_30334_; } @Override public boolean canUse() { return !this.turtle.goingHome && !this.turtle.hasEgg() && this.turtle.isInWater(); } @Override public void start() { int i = 512; int j = 4; RandomSource randomsource = this.turtle.random; int k = randomsource.nextInt(1025) - 512; int l = randomsource.nextInt(9) - 4; int i1 = randomsource.nextInt(1025) - 512; if (l + this.turtle.getY() > this.turtle.level().getSeaLevel() - 1) { l = 0; } this.turtle.travelPos = BlockPos.containing(k + this.turtle.getX(), l + this.turtle.getY(), i1 + this.turtle.getZ()); this.stuck = false; } @Override public void tick() { if (this.turtle.travelPos == null) { this.stuck = true; } else { if (this.turtle.getNavigation().isDone()) { Vec3 vec3 = Vec3.atBottomCenterOf(this.turtle.travelPos); Vec3 vec31 = DefaultRandomPos.getPosTowards(this.turtle, 16, 3, vec3, (float) (Math.PI / 10)); if (vec31 == null) { vec31 = DefaultRandomPos.getPosTowards(this.turtle, 8, 7, vec3, (float) (Math.PI / 2)); } if (vec31 != null) { int i = Mth.floor(vec31.x); int j = Mth.floor(vec31.z); int k = 34; if (!this.turtle.level().hasChunksAt(i - 34, j - 34, i + 34, j + 34)) { vec31 = null; } } if (vec31 == null) { this.stuck = true; return; } this.turtle.getNavigation().moveTo(vec31.x, vec31.y, vec31.z, this.speedModifier); } } } @Override public boolean canContinueToUse() { return !this.turtle.getNavigation().isDone() && !this.stuck && !this.turtle.goingHome && !this.turtle.isInLove() && !this.turtle.hasEgg(); } @Override public void stop() { this.turtle.travelPos = null; super.stop(); } } }