package net.minecraft.world.entity.animal; import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; 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.network.syncher.SynchedEntityData.Builder; 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.ItemTags; import net.minecraft.util.RandomSource; 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.EntitySelector; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.goal.AvoidEntityGoal; import net.minecraft.world.entity.ai.goal.BreedGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.OcelotAttackGoal; import net.minecraft.world.entity.ai.goal.TemptGoal; import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; 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.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class Ocelot extends Animal { public static final double CROUCH_SPEED_MOD = 0.6; public static final double WALK_SPEED_MOD = 0.8; public static final double SPRINT_SPEED_MOD = 1.33; private static final EntityDataAccessor DATA_TRUSTING = SynchedEntityData.defineId(Ocelot.class, EntityDataSerializers.BOOLEAN); private static final boolean DEFAULT_TRUSTING = false; @Nullable private Ocelot.OcelotAvoidEntityGoal ocelotAvoidPlayersGoal; @Nullable private Ocelot.OcelotTemptGoal temptGoal; public Ocelot(EntityType entityType, Level level) { super(entityType, level); this.reassessTrustingGoals(); } boolean isTrusting() { return this.entityData.get(DATA_TRUSTING); } private void setTrusting(boolean trusting) { this.entityData.set(DATA_TRUSTING, trusting); this.reassessTrustingGoals(); } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); tag.putBoolean("Trusting", this.isTrusting()); } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.setTrusting(tag.getBooleanOr("Trusting", false)); } @Override protected void defineSynchedData(Builder builder) { super.defineSynchedData(builder); builder.define(DATA_TRUSTING, false); } @Override protected void registerGoals() { this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6, itemStack -> itemStack.is(ItemTags.OCELOT_FOOD), true); this.goalSelector.addGoal(1, new FloatGoal(this)); this.goalSelector.addGoal(3, this.temptGoal); this.goalSelector.addGoal(7, new LeapAtTargetGoal(this, 0.3F)); this.goalSelector.addGoal(8, new OcelotAttackGoal(this)); this.goalSelector.addGoal(9, new BreedGoal(this, 0.8)); this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8, 1.0000001E-5F)); this.goalSelector.addGoal(11, new LookAtPlayerGoal(this, Player.class, 10.0F)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal(this, Chicken.class, false)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR)); } @Override public void customServerAiStep(ServerLevel level) { if (this.getMoveControl().hasWanted()) { double d = this.getMoveControl().getSpeedModifier(); if (d == 0.6) { this.setPose(Pose.CROUCHING); this.setSprinting(false); } else if (d == 1.33) { this.setPose(Pose.STANDING); this.setSprinting(true); } else { this.setPose(Pose.STANDING); this.setSprinting(false); } } else { this.setPose(Pose.STANDING); this.setSprinting(false); } } @Override public boolean removeWhenFarAway(double distanceToClosestPlayer) { return !this.isTrusting() && this.tickCount > 2400; } public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() { return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.MOVEMENT_SPEED, 0.3F).add(Attributes.ATTACK_DAMAGE, 3.0); } @Nullable @Override protected SoundEvent getAmbientSound() { return SoundEvents.OCELOT_AMBIENT; } @Override public int getAmbientSoundInterval() { return 900; } @Override protected SoundEvent getHurtSound(DamageSource damageSource) { return SoundEvents.OCELOT_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.OCELOT_DEATH; } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemStack = player.getItemInHand(hand); if ((this.temptGoal == null || this.temptGoal.isRunning()) && !this.isTrusting() && this.isFood(itemStack) && player.distanceToSqr(this) < 9.0) { this.usePlayerItem(player, hand, itemStack); if (!this.level().isClientSide) { if (this.random.nextInt(3) == 0) { this.setTrusting(true); this.spawnTrustingParticles(true); this.level().broadcastEntityEvent(this, (byte)41); } else { this.spawnTrustingParticles(false); this.level().broadcastEntityEvent(this, (byte)40); } } return InteractionResult.SUCCESS; } else { return super.mobInteract(player, hand); } } @Override public void handleEntityEvent(byte id) { if (id == 41) { this.spawnTrustingParticles(true); } else if (id == 40) { this.spawnTrustingParticles(false); } else { super.handleEntityEvent(id); } } private void spawnTrustingParticles(boolean isTrusted) { ParticleOptions particleOptions = ParticleTypes.HEART; if (!isTrusted) { particleOptions = ParticleTypes.SMOKE; } for (int i = 0; i < 7; i++) { double d = this.random.nextGaussian() * 0.02; double e = this.random.nextGaussian() * 0.02; double f = this.random.nextGaussian() * 0.02; this.level().addParticle(particleOptions, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), d, e, f); } } protected void reassessTrustingGoals() { if (this.ocelotAvoidPlayersGoal == null) { this.ocelotAvoidPlayersGoal = new Ocelot.OcelotAvoidEntityGoal<>(this, Player.class, 16.0F, 0.8, 1.33); } this.goalSelector.removeGoal(this.ocelotAvoidPlayersGoal); if (!this.isTrusting()) { this.goalSelector.addGoal(4, this.ocelotAvoidPlayersGoal); } } @Nullable public Ocelot getBreedOffspring(ServerLevel serverLevel, AgeableMob ageableMob) { return EntityType.OCELOT.create(serverLevel, EntitySpawnReason.BREEDING); } @Override public boolean isFood(ItemStack stack) { return stack.is(ItemTags.OCELOT_FOOD); } public static boolean checkOcelotSpawnRules( EntityType entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random ) { return random.nextInt(3) != 0; } @Override public boolean checkSpawnObstruction(LevelReader level) { if (level.isUnobstructed(this) && !level.containsAnyLiquid(this.getBoundingBox())) { BlockPos blockPos = this.blockPosition(); if (blockPos.getY() < level.getSeaLevel()) { return false; } BlockState blockState = level.getBlockState(blockPos.below()); if (blockState.is(Blocks.GRASS_BLOCK) || blockState.is(BlockTags.LEAVES)) { return true; } } return false; } @Nullable @Override public SpawnGroupData finalizeSpawn( ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData ) { if (spawnGroupData == null) { spawnGroupData = new AgeableMob.AgeableMobGroupData(1.0F); } return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData); } @Override public Vec3 getLeashOffset() { return new Vec3(0.0, 0.5F * this.getEyeHeight(), this.getBbWidth() * 0.4F); } @Override public boolean isSteppingCarefully() { return this.isCrouching() || super.isSteppingCarefully(); } static class OcelotAvoidEntityGoal extends AvoidEntityGoal { private final Ocelot ocelot; public OcelotAvoidEntityGoal(Ocelot ocelot, Class entityClassToAvoid, float maxDist, double walkSpeedModifier, double sprintSpeedModifier) { super(ocelot, entityClassToAvoid, maxDist, walkSpeedModifier, sprintSpeedModifier, EntitySelector.NO_CREATIVE_OR_SPECTATOR::test); this.ocelot = ocelot; } @Override public boolean canUse() { return !this.ocelot.isTrusting() && super.canUse(); } @Override public boolean canContinueToUse() { return !this.ocelot.isTrusting() && super.canContinueToUse(); } } static class OcelotTemptGoal extends TemptGoal { private final Ocelot ocelot; public OcelotTemptGoal(Ocelot ocelot, double speedModifier, Predicate items, boolean canScare) { super(ocelot, speedModifier, items, canScare); this.ocelot = ocelot; } @Override protected boolean canScare() { return super.canScare() && !this.ocelot.isTrusting(); } } }