package net.minecraft.world.entity; import java.util.Optional; import java.util.UUID; import net.minecraft.advancements.CriteriaTriggers; 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.server.level.ServerPlayer; import net.minecraft.server.players.OldUsersConverter; import net.minecraft.tags.TagKey; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.entity.ai.goal.PanicGoal; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.LeavesBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; import net.minecraft.world.scores.PlayerTeam; import org.jetbrains.annotations.Nullable; public abstract class TamableAnimal extends Animal implements OwnableEntity { public static final int TELEPORT_WHEN_DISTANCE_IS_SQ = 144; private static final int MIN_HORIZONTAL_DISTANCE_FROM_TARGET_AFTER_TELEPORTING = 2; private static final int MAX_HORIZONTAL_DISTANCE_FROM_TARGET_AFTER_TELEPORTING = 3; private static final int MAX_VERTICAL_DISTANCE_FROM_TARGET_AFTER_TELEPORTING = 1; protected static final EntityDataAccessor DATA_FLAGS_ID = SynchedEntityData.defineId(TamableAnimal.class, EntityDataSerializers.BYTE); protected static final EntityDataAccessor> DATA_OWNERUUID_ID = SynchedEntityData.defineId( TamableAnimal.class, EntityDataSerializers.OPTIONAL_UUID ); private boolean orderedToSit; protected TamableAnimal(EntityType entityType, Level level) { super(entityType, level); } @Override protected void defineSynchedData(Builder builder) { super.defineSynchedData(builder); builder.define(DATA_FLAGS_ID, (byte)0); builder.define(DATA_OWNERUUID_ID, Optional.empty()); } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); if (this.getOwnerUUID() != null) { tag.putUUID("Owner", this.getOwnerUUID()); } tag.putBoolean("Sitting", this.orderedToSit); } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); UUID uUID; if (tag.hasUUID("Owner")) { uUID = tag.getUUID("Owner"); } else { String string = tag.getString("Owner"); uUID = OldUsersConverter.convertMobOwnerIfNecessary(this.getServer(), string); } if (uUID != null) { try { this.setOwnerUUID(uUID); this.setTame(true, false); } catch (Throwable var4) { this.setTame(false, true); } } else { this.setOwnerUUID(null); this.setTame(false, true); } this.orderedToSit = tag.getBoolean("Sitting"); this.setInSittingPose(this.orderedToSit); } @Override public boolean canBeLeashed() { return true; } @Override public boolean handleLeashAtDistance(Entity leashHolder, float distance) { if (this.isInSittingPose()) { if (distance > 10.0F) { this.dropLeash(); } return false; } else { return super.handleLeashAtDistance(leashHolder, distance); } } /** * Play the taming effect, will either be hearts or smoke depending on status */ protected void spawnTamingParticles(boolean tamed) { ParticleOptions particleOptions = ParticleTypes.HEART; if (!tamed) { 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); } } @Override public void handleEntityEvent(byte id) { if (id == 7) { this.spawnTamingParticles(true); } else if (id == 6) { this.spawnTamingParticles(false); } else { super.handleEntityEvent(id); } } public boolean isTame() { return (this.entityData.get(DATA_FLAGS_ID) & 4) != 0; } public void setTame(boolean tame, boolean applyTamingSideEffects) { byte b = this.entityData.get(DATA_FLAGS_ID); if (tame) { this.entityData.set(DATA_FLAGS_ID, (byte)(b | 4)); } else { this.entityData.set(DATA_FLAGS_ID, (byte)(b & -5)); } if (applyTamingSideEffects) { this.applyTamingSideEffects(); } } protected void applyTamingSideEffects() { } public boolean isInSittingPose() { return (this.entityData.get(DATA_FLAGS_ID) & 1) != 0; } public void setInSittingPose(boolean sitting) { byte b = this.entityData.get(DATA_FLAGS_ID); if (sitting) { this.entityData.set(DATA_FLAGS_ID, (byte)(b | 1)); } else { this.entityData.set(DATA_FLAGS_ID, (byte)(b & -2)); } } @Nullable @Override public UUID getOwnerUUID() { return (UUID)this.entityData.get(DATA_OWNERUUID_ID).orElse(null); } public void setOwnerUUID(@Nullable UUID uuid) { this.entityData.set(DATA_OWNERUUID_ID, Optional.ofNullable(uuid)); } public void tame(Player player) { this.setTame(true, true); this.setOwnerUUID(player.getUUID()); if (player instanceof ServerPlayer serverPlayer) { CriteriaTriggers.TAME_ANIMAL.trigger(serverPlayer, this); } } @Override public boolean canAttack(LivingEntity target) { return this.isOwnedBy(target) ? false : super.canAttack(target); } public boolean isOwnedBy(LivingEntity entity) { return entity == this.getOwner(); } public boolean wantsToAttack(LivingEntity target, LivingEntity owner) { return true; } @Override public PlayerTeam getTeam() { if (this.isTame()) { LivingEntity livingEntity = this.getOwner(); if (livingEntity != null) { return livingEntity.getTeam(); } } return super.getTeam(); } @Override protected boolean considersEntityAsAlly(Entity entity) { if (this.isTame()) { LivingEntity livingEntity = this.getOwner(); if (entity == livingEntity) { return true; } if (livingEntity != null) { return livingEntity.considersEntityAsAlly(entity); } } return super.considersEntityAsAlly(entity); } @Override public void die(DamageSource damageSource) { if (this.level() instanceof ServerLevel serverLevel && serverLevel.getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES) && this.getOwner() instanceof ServerPlayer serverPlayer) { serverPlayer.sendSystemMessage(this.getCombatTracker().getDeathMessage()); } super.die(damageSource); } public boolean isOrderedToSit() { return this.orderedToSit; } public void setOrderedToSit(boolean orderedToSit) { this.orderedToSit = orderedToSit; } public void tryToTeleportToOwner() { LivingEntity livingEntity = this.getOwner(); if (livingEntity != null) { this.teleportToAroundBlockPos(livingEntity.blockPosition()); } } public boolean shouldTryTeleportToOwner() { LivingEntity livingEntity = this.getOwner(); return livingEntity != null && this.distanceToSqr(this.getOwner()) >= 144.0; } private void teleportToAroundBlockPos(BlockPos pos) { for (int i = 0; i < 10; i++) { int j = this.random.nextIntBetweenInclusive(-3, 3); int k = this.random.nextIntBetweenInclusive(-3, 3); if (Math.abs(j) >= 2 || Math.abs(k) >= 2) { int l = this.random.nextIntBetweenInclusive(-1, 1); if (this.maybeTeleportTo(pos.getX() + j, pos.getY() + l, pos.getZ() + k)) { return; } } } } private boolean maybeTeleportTo(int x, int y, int z) { if (!this.canTeleportTo(new BlockPos(x, y, z))) { return false; } else { this.moveTo(x + 0.5, y, z + 0.5, this.getYRot(), this.getXRot()); this.navigation.stop(); return true; } } private boolean canTeleportTo(BlockPos pos) { PathType pathType = WalkNodeEvaluator.getPathTypeStatic(this, pos); if (pathType != PathType.WALKABLE) { return false; } else { BlockState blockState = this.level().getBlockState(pos.below()); if (!this.canFlyToOwner() && blockState.getBlock() instanceof LeavesBlock) { return false; } else { BlockPos blockPos = pos.subtract(this.blockPosition()); return this.level().noCollision(this, this.getBoundingBox().move(blockPos)); } } } public final boolean unableToMoveToOwner() { return this.isOrderedToSit() || this.isPassenger() || this.mayBeLeashed() || this.getOwner() != null && this.getOwner().isSpectator(); } protected boolean canFlyToOwner() { return false; } public class TamableAnimalPanicGoal extends PanicGoal { public TamableAnimalPanicGoal(final double speedModifier, final TagKey panicCausingDamageTypes) { super(TamableAnimal.this, speedModifier, panicCausingDamageTypes); } public TamableAnimalPanicGoal(final double speedModifier) { super(TamableAnimal.this, speedModifier); } @Override public void tick() { if (!TamableAnimal.this.unableToMoveToOwner() && TamableAnimal.this.shouldTryTeleportToOwner()) { TamableAnimal.this.tryToTeleportToOwner(); } super.tick(); } } }