minecraft-src/net/minecraft/world/entity/animal/Animal.java
2025-07-04 03:45:38 +03:00

269 lines
8.3 KiB
Java

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<? extends Animal> 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<? extends Animal> 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);
}
}
}