package net.minecraft.world.entity.animal; import java.util.Optional; import java.util.UUID; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.core.BlockPos; import net.minecraft.core.UUIDUtil; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.stats.Stats; import net.minecraft.tags.BlockTags; import net.minecraft.util.RandomSource; 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.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.ExperienceOrb; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.UseRemainder; import net.minecraft.world.level.BlockAndTintGetter; 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.block.Blocks; import net.minecraft.world.level.pathfinder.PathType; import org.jetbrains.annotations.Nullable; public abstract class Animal extends AgeableMob { protected static final int PARENT_AGE_AFTER_BREEDING = 6000; private static final int DEFAULT_IN_LOVE_TIME = 0; private int inLove = 0; @Nullable private UUID loveCause; protected Animal(EntityType entityType, Level level) { super(entityType, level); this.setPathfindingMalus(PathType.DANGER_FIRE, 16.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F); } public static Builder createAnimalAttributes() { return Mob.createMobAttributes().add(Attributes.TEMPT_RANGE, 10.0); } @Override protected void customServerAiStep(ServerLevel level) { if (this.getAge() != 0) { this.inLove = 0; } super.customServerAiStep(level); } @Override public void aiStep() { super.aiStep(); if (this.getAge() != 0) { this.inLove = 0; } if (this.inLove > 0) { this.inLove--; if (this.inLove % 10 == 0) { double d = this.random.nextGaussian() * 0.02; double e = this.random.nextGaussian() * 0.02; double f = this.random.nextGaussian() * 0.02; this.level().addParticle(ParticleTypes.HEART, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), d, e, f); } } } @Override protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) { this.resetLove(); super.actuallyHurt(level, damageSource, amount); } @Override public float getWalkTargetValue(BlockPos pos, LevelReader level) { return level.getBlockState(pos.below()).is(Blocks.GRASS_BLOCK) ? 10.0F : level.getPathfindingCostFromLightLevels(pos); } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); tag.putInt("InLove", this.inLove); tag.storeNullable("LoveCause", UUIDUtil.CODEC, this.loveCause); } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); this.inLove = tag.getIntOr("InLove", 0); this.loveCause = (UUID)tag.read("LoveCause", UUIDUtil.CODEC).orElse(null); } public static boolean checkAnimalSpawnRules( EntityType entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random ) { boolean bl = EntitySpawnReason.ignoresLightRequirements(spawnReason) || isBrightEnoughToSpawn(level, pos); return level.getBlockState(pos.below()).is(BlockTags.ANIMALS_SPAWNABLE_ON) && bl; } protected static boolean isBrightEnoughToSpawn(BlockAndTintGetter level, BlockPos pos) { return level.getRawBrightness(pos, 0) > 8; } @Override public int getAmbientSoundInterval() { return 120; } @Override public boolean removeWhenFarAway(double distanceToClosestPlayer) { return false; } @Override protected int getBaseExperienceReward(ServerLevel level) { return 1 + this.random.nextInt(3); } /** * Checks if the parameter is an item which this animal can be fed to breed it (wheat, carrots or seeds depending on the animal type) */ public abstract boolean isFood(ItemStack stack); @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemStack = player.getItemInHand(hand); if (this.isFood(itemStack)) { int i = this.getAge(); if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { this.usePlayerItem(player, hand, itemStack); this.setInLove(player); this.playEatingSound(); return InteractionResult.SUCCESS_SERVER; } if (this.isBaby()) { this.usePlayerItem(player, hand, itemStack); this.ageUp(getSpeedUpSecondsWhenFeeding(-i), true); this.playEatingSound(); return InteractionResult.SUCCESS; } if (this.level().isClientSide) { return InteractionResult.CONSUME; } } return super.mobInteract(player, hand); } protected void playEatingSound() { } protected void usePlayerItem(Player player, InteractionHand hand, ItemStack stack) { int i = stack.getCount(); UseRemainder useRemainder = stack.get(DataComponents.USE_REMAINDER); stack.consume(1, player); if (useRemainder != null) { ItemStack itemStack = useRemainder.convertIntoRemainder(stack, i, player.hasInfiniteMaterials(), player::handleExtraItemsCreatedOnUse); player.setItemInHand(hand, itemStack); } } public boolean canFallInLove() { return this.inLove <= 0; } public void setInLove(@Nullable Player player) { this.inLove = 600; if (player != null) { this.loveCause = player.getUUID(); } this.level().broadcastEntityEvent(this, (byte)18); } public void setInLoveTime(int inLove) { this.inLove = inLove; } public int getInLoveTime() { return this.inLove; } @Nullable public ServerPlayer getLoveCause() { if (this.loveCause == null) { return null; } else { Player player = this.level().getPlayerByUUID(this.loveCause); return player instanceof ServerPlayer ? (ServerPlayer)player : null; } } /** * Returns if the entity is currently in 'love mode'. */ public boolean isInLove() { return this.inLove > 0; } public void resetLove() { this.inLove = 0; } /** * Returns {@code true} if the mob is currently able to mate with the specified mob. */ public boolean canMate(Animal otherAnimal) { if (otherAnimal == this) { return false; } else { return otherAnimal.getClass() != this.getClass() ? false : this.isInLove() && otherAnimal.isInLove(); } } public void spawnChildFromBreeding(ServerLevel level, Animal mate) { AgeableMob ageableMob = this.getBreedOffspring(level, mate); if (ageableMob != null) { ageableMob.setBaby(true); ageableMob.snapTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); this.finalizeSpawnChildFromBreeding(level, mate, ageableMob); level.addFreshEntityWithPassengers(ageableMob); } } public void finalizeSpawnChildFromBreeding(ServerLevel level, Animal animal, @Nullable AgeableMob baby) { Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(animal.getLoveCause())).ifPresent(serverPlayer -> { serverPlayer.awardStat(Stats.ANIMALS_BRED); CriteriaTriggers.BRED_ANIMALS.trigger(serverPlayer, this, animal, baby); }); this.setAge(6000); animal.setAge(6000); this.resetLove(); animal.resetLove(); level.broadcastEntityEvent(this, (byte)18); if (level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1)); } } @Override public void handleEntityEvent(byte id) { if (id == 18) { 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(ParticleTypes.HEART, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), d, e, f); } } else { super.handleEntityEvent(id); } } }