/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.animal; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.core.BlockPos; 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.Mob; 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.animal.Animal; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; 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.gamerules.GameRules; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; 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; private int layEggCounter; public static final TargetingConditions.Selector BABY_ON_LAND_SELECTOR = (target, level) -> target.isBaby() && !target.isInWater(); private BlockPos homePos = BlockPos.ZERO; private @Nullable BlockPos travelPos; private boolean goingHome; public Turtle(EntityType type, Level level) { super((EntityType)type, level); 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 TurtleMoveControl(this); } public void setHomePos(BlockPos pos) { this.homePos = pos; } public boolean hasEgg() { return this.entityData.get(HAS_EGG); } private void setHasEgg(boolean onOff) { this.entityData.set(HAS_EGG, onOff); } public boolean isLayingEgg() { return this.entityData.get(LAYING_EGG); } private void setLayingEgg(boolean on) { this.layEggCounter = on ? 1 : 0; this.entityData.set(LAYING_EGG, on); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(HAS_EGG, false); entityData.define(LAYING_EGG, false); } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); output.store("home_pos", BlockPos.CODEC, this.homePos); output.putBoolean("has_egg", this.hasEgg()); } @Override protected void readAdditionalSaveData(ValueInput input) { this.setHomePos(input.read("home_pos", BlockPos.CODEC).orElse(this.blockPosition())); super.readAdditionalSaveData(input); this.setHasEgg(input.getBooleanOr("has_egg", false)); } @Override public @Nullable SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData) { this.setHomePos(this.blockPosition()); return super.finalizeSpawn(level, difficulty, spawnReason, groupData); } public static boolean checkTurtleSpawnRules(EntityType type, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { return pos.getY() < level.getSeaLevel() + 4 && TurtleEggBlock.onSand(level, pos) && Turtle.isBrightEnoughToSpawn(level, pos); } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new TurtlePanicGoal(this, 1.2)); this.goalSelector.addGoal(1, new TurtleBreedGoal(this, 1.0)); this.goalSelector.addGoal(1, new TurtleLayEggGoal(this, 1.0)); this.goalSelector.addGoal(2, new TemptGoal(this, 1.1, i -> i.is(ItemTags.TURTLE_FOOD), false)); this.goalSelector.addGoal(3, new TurtleGoToWaterGoal(this, 1.0)); this.goalSelector.addGoal(4, new TurtleGoHomeGoal(this, 1.0)); this.goalSelector.addGoal(7, new TurtleTravelGoal(this, 1.0)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0f)); this.goalSelector.addGoal(9, new 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; } @Override protected @Nullable SoundEvent getAmbientSound() { if (!this.isInWater() && this.onGround() && !this.isBaby()) { return SoundEvents.TURTLE_AMBIENT_LAND; } return super.getAmbientSound(); } @Override protected void playSwimSound(float volume) { super.playSwimSound(volume * 1.5f); } @Override protected SoundEvent getSwimSound() { return SoundEvents.TURTLE_SWIM; } @Override protected @Nullable SoundEvent getHurtSound(DamageSource source) { if (this.isBaby()) { return SoundEvents.TURTLE_HURT_BABY; } return SoundEvents.TURTLE_HURT; } @Override protected @Nullable SoundEvent getDeathSound() { if (this.isBaby()) { return SoundEvents.TURTLE_DEATH_BABY; } return SoundEvents.TURTLE_DEATH; } @Override protected void playStepSound(BlockPos pos, BlockState blockState) { SoundEvent sound = this.isBaby() ? SoundEvents.TURTLE_SHAMBLE_BABY : SoundEvents.TURTLE_SHAMBLE; this.playSound(sound, 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 level) { return new TurtlePathNavigation(this, level); } @Override public @Nullable AgeableMob getBreedOffspring(ServerLevel level, AgeableMob partner) { return EntityType.TURTLE.create(level, EntitySpawnReason.BREEDING); } @Override public boolean isFood(ItemStack itemStack) { return itemStack.is(ItemTags.TURTLE_FOOD); } @Override public float getWalkTargetValue(BlockPos pos, LevelReader level) { if (!this.goingHome && level.getFluidState(pos).is(FluidTags.WATER)) { return 10.0f; } if (TurtleEggBlock.onSand(level, pos)) { return 10.0f; } return level.getPathfindingCostFromLightLevels(pos); } @Override public void aiStep() { super.aiStep(); if (this.isAlive() && this.isLayingEgg() && this.layEggCounter >= 1 && this.layEggCounter % 5 == 0) { BlockPos pos = this.blockPosition(); if (TurtleEggBlock.onSand(this.level(), pos)) { this.level().levelEvent(2001, pos, Block.getId(this.level().getBlockState(pos.below()))); this.gameEvent(GameEvent.ENTITY_ACTION); } } } @Override protected void ageBoundaryReached() { ServerLevel level; Level level2; super.ageBoundaryReached(); if (!this.isBaby() && (level2 = this.level()) instanceof ServerLevel && (level = (ServerLevel)level2).getGameRules().get(GameRules.MOB_DROPS).booleanValue()) { this.dropFromGiftLootTable(level, BuiltInLootTables.TURTLE_GROW, this::spawnAtLocation); } } @Override protected void travelInWater(Vec3 input, double baseGravity, boolean isFalling, double oldY) { this.moveRelative(0.1f, input); 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)); } } @Override public boolean canBeLeashed() { return false; } @Override public void thunderHit(ServerLevel level, LightningBolt lightningBolt) { this.hurtServer(level, this.damageSources().lightningBolt(), Float.MAX_VALUE); } @Override public EntityDimensions getDefaultDimensions(Pose pose) { return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose); } private static class TurtleMoveControl extends MoveControl { private final Turtle turtle; TurtleMoveControl(Turtle turtle) { super(turtle); this.turtle = turtle; } 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() { double zd; double yd; this.updateSpeed(); if (this.operation != MoveControl.Operation.MOVE_TO || this.turtle.getNavigation().isDone()) { this.turtle.setSpeed(0.0f); return; } double xd = this.wantedX - this.turtle.getX(); double dd = Math.sqrt(xd * xd + (yd = this.wantedY - this.turtle.getY()) * yd + (zd = this.wantedZ - this.turtle.getZ()) * zd); if (dd < (double)1.0E-5f) { this.mob.setSpeed(0.0f); return; } yd /= dd; float yRotD = (float)(Mth.atan2(zd, xd) * 57.2957763671875) - 90.0f; this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), yRotD, 90.0f)); this.turtle.yBodyRot = this.turtle.getYRot(); float targetSpeed = (float)(this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); this.turtle.setSpeed(Mth.lerp(0.125f, this.turtle.getSpeed(), targetSpeed)); this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0, (double)this.turtle.getSpeed() * yd * 0.1, 0.0)); } } private static class TurtlePanicGoal extends PanicGoal { TurtlePanicGoal(Turtle turtle, double speedModifier) { super(turtle, speedModifier); } @Override public boolean canUse() { if (!this.shouldPanic()) { return false; } 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; } return this.findRandomPosition(); } } private static class TurtleBreedGoal extends BreedGoal { private final Turtle turtle; TurtleBreedGoal(Turtle turtle, double speedModifier) { super(turtle, speedModifier); this.turtle = turtle; } @Override public boolean canUse() { return super.canUse() && !this.turtle.hasEgg(); } @Override protected void breed() { ServerPlayer loveCause = this.animal.getLoveCause(); if (loveCause == null && this.partner.getLoveCause() != null) { loveCause = this.partner.getLoveCause(); } if (loveCause != null) { loveCause.awardStat(Stats.ANIMALS_BRED); CriteriaTriggers.BRED_ANIMALS.trigger(loveCause, this.animal, this.partner, null); } this.turtle.setHasEgg(true); this.animal.setAge(6000); this.partner.setAge(6000); this.animal.resetLove(); this.partner.resetLove(); RandomSource random = this.animal.getRandom(); if (TurtleBreedGoal.getServerLevel(this.level).getGameRules().get(GameRules.MOB_DROPS).booleanValue()) { this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), random.nextInt(7) + 1)); } } } private static class TurtleLayEggGoal extends MoveToBlockGoal { private final Turtle turtle; TurtleLayEggGoal(Turtle turtle, double speedModifier) { super(turtle, speedModifier, 16); this.turtle = turtle; } @Override public boolean canUse() { if (this.turtle.hasEgg() && this.turtle.homePos.closerToCenterThan(this.turtle.position(), 9.0)) { return super.canUse(); } return 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 turtlePos = 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, turtlePos, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3f, 0.9f + level.random.nextFloat() * 0.2f); BlockPos eggPos = this.blockPos.above(); BlockState eggState = (BlockState)Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1); level.setBlock(eggPos, eggState, 3); level.gameEvent(GameEvent.BLOCK_PLACE, eggPos, GameEvent.Context.of(this.turtle, eggState)); this.turtle.setHasEgg(false); this.turtle.setLayingEgg(false); this.turtle.setInLoveTime(600); } if (this.turtle.isLayingEgg()) { ++this.turtle.layEggCounter; } } } @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { if (!level.isEmptyBlock(pos.above())) { return false; } return TurtleEggBlock.isSand(level, pos); } } private static class TurtleGoToWaterGoal extends MoveToBlockGoal { private static final int GIVE_UP_TICKS = 1200; private final Turtle turtle; private TurtleGoToWaterGoal(Turtle turtle, double speedModifier) { super(turtle, turtle.isBaby() ? 2.0 : speedModifier, 24); this.turtle = turtle; 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(); } if (!(this.turtle.goingHome || this.turtle.isInWater() || this.turtle.hasEgg())) { return super.canUse(); } return false; } @Override public boolean shouldRecalculatePath() { return this.tryTicks % 160 == 0; } @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { return level.getBlockState(pos).is(Blocks.WATER); } } private 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 turtle, double speedModifier) { this.turtle = turtle; this.speedModifier = speedModifier; } @Override public boolean canUse() { if (this.turtle.isBaby()) { return false; } if (this.turtle.hasEgg()) { return true; } if (this.turtle.getRandom().nextInt(TurtleGoHomeGoal.reducedTickDelay(700)) != 0) { return false; } return !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 homePos = this.turtle.homePos; boolean closeToHome = homePos.closerToCenterThan(this.turtle.position(), 16.0); if (closeToHome) { ++this.closeToHomeTryTicks; } if (this.turtle.getNavigation().isDone()) { Vec3 homePosVec = Vec3.atBottomCenterOf(homePos); Vec3 nextPos = DefaultRandomPos.getPosTowards(this.turtle, 16, 3, homePosVec, 0.3141592741012573); if (nextPos == null) { nextPos = DefaultRandomPos.getPosTowards(this.turtle, 8, 7, homePosVec, 1.5707963705062866); } if (nextPos != null && !closeToHome && !this.turtle.level().getBlockState(BlockPos.containing(nextPos)).is(Blocks.WATER)) { nextPos = DefaultRandomPos.getPosTowards(this.turtle, 16, 5, homePosVec, 1.5707963705062866); } if (nextPos == null) { this.stuck = true; return; } this.turtle.getNavigation().moveTo(nextPos.x, nextPos.y, nextPos.z, this.speedModifier); } } } private static class TurtleTravelGoal extends Goal { private final Turtle turtle; private final double speedModifier; private boolean stuck; TurtleTravelGoal(Turtle turtle, double speedModifier) { this.turtle = turtle; this.speedModifier = speedModifier; } @Override public boolean canUse() { return !this.turtle.goingHome && !this.turtle.hasEgg() && this.turtle.isInWater(); } @Override public void start() { int xzDist = 512; int yDist = 4; RandomSource random = this.turtle.random; int xt = random.nextInt(1025) - 512; int yt = random.nextInt(9) - 4; int zt = random.nextInt(1025) - 512; if ((double)yt + this.turtle.getY() > (double)(this.turtle.level().getSeaLevel() - 1)) { yt = 0; } this.turtle.travelPos = BlockPos.containing((double)xt + this.turtle.getX(), (double)yt + this.turtle.getY(), (double)zt + this.turtle.getZ()); this.stuck = false; } @Override public void tick() { if (this.turtle.travelPos == null) { this.stuck = true; return; } if (this.turtle.getNavigation().isDone()) { Vec3 targetPos = Vec3.atBottomCenterOf(this.turtle.travelPos); Vec3 nextPos = DefaultRandomPos.getPosTowards(this.turtle, 16, 3, targetPos, 0.3141592741012573); if (nextPos == null) { nextPos = DefaultRandomPos.getPosTowards(this.turtle, 8, 7, targetPos, 1.5707963705062866); } if (nextPos != null) { int xc = Mth.floor(nextPos.x); int zc = Mth.floor(nextPos.z); int r = 34; if (!this.turtle.level().hasChunksAt(xc - 34, zc - 34, xc + 34, zc + 34)) { nextPos = null; } } if (nextPos == null) { this.stuck = true; return; } this.turtle.getNavigation().moveTo(nextPos.x, nextPos.y, nextPos.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(); } } private static class TurtleRandomStrollGoal extends RandomStrollGoal { private final Turtle turtle; private TurtleRandomStrollGoal(Turtle turtle, double speedModifier, int interval) { super(turtle, speedModifier, interval); this.turtle = turtle; } @Override public boolean canUse() { if (!(this.mob.isInWater() || this.turtle.goingHome || this.turtle.hasEgg())) { return super.canUse(); } return false; } } private static class TurtlePathNavigation extends AmphibiousPathNavigation { TurtlePathNavigation(Turtle mob, Level level) { super(mob, level); } @Override public boolean isStableDestination(BlockPos pos) { Mob mob = this.mob; if (mob instanceof Turtle) { Turtle turtle = (Turtle)mob; if (turtle.travelPos != null) { return this.level.getBlockState(pos).is(Blocks.WATER); } } return !this.level.getBlockState(pos.below()).isAir(); } } }