/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity; import java.util.Optional; import net.minecraft.advancements.CriteriaTriggers; import net.minecraft.core.BlockPos; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.particles.SimpleParticleType; 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.tags.TagKey; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityReference; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.OwnableEntity; import net.minecraft.world.entity.PathfinderMob; 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.Level; import net.minecraft.world.level.block.LeavesBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.scores.PlayerTeam; import org.jspecify.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; private static final boolean DEFAULT_ORDERED_TO_SIT = false; 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_LIVING_ENTITY_REFERENCE); private boolean orderedToSit = false; protected TamableAnimal(EntityType type, Level level) { super((EntityType)type, level); } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(DATA_FLAGS_ID, (byte)0); entityData.define(DATA_OWNERUUID_ID, Optional.empty()); } @Override protected void addAdditionalSaveData(ValueOutput output) { super.addAdditionalSaveData(output); EntityReference owner = this.getOwnerReference(); EntityReference.store(owner, output, "Owner"); output.putBoolean("Sitting", this.orderedToSit); } @Override protected void readAdditionalSaveData(ValueInput input) { super.readAdditionalSaveData(input); EntityReference owner = EntityReference.readWithOldOwnerConversion(input, "Owner", this.level()); if (owner != null) { try { this.entityData.set(DATA_OWNERUUID_ID, Optional.of(owner)); this.setTame(true, false); } catch (Throwable ignored) { this.setTame(false, true); } } else { this.entityData.set(DATA_OWNERUUID_ID, Optional.empty()); this.setTame(false, true); } this.orderedToSit = input.getBooleanOr("Sitting", false); this.setInSittingPose(this.orderedToSit); } @Override public boolean canBeLeashed() { return true; } protected void spawnTamingParticles(boolean success) { SimpleParticleType particle = ParticleTypes.HEART; if (!success) { particle = ParticleTypes.SMOKE; } for (int i = 0; i < 7; ++i) { double xa = this.random.nextGaussian() * 0.02; double ya = this.random.nextGaussian() * 0.02; double za = this.random.nextGaussian() * 0.02; this.level().addParticle(particle, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), xa, ya, za); } } @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 isTame, boolean includeSideEffects) { byte current = this.entityData.get(DATA_FLAGS_ID); if (isTame) { this.entityData.set(DATA_FLAGS_ID, (byte)(current | 4)); } else { this.entityData.set(DATA_FLAGS_ID, (byte)(current & 0xFFFFFFFB)); } if (includeSideEffects) { this.applyTamingSideEffects(); } } protected void applyTamingSideEffects() { } public boolean isInSittingPose() { return (this.entityData.get(DATA_FLAGS_ID) & 1) != 0; } public void setInSittingPose(boolean value) { byte current = this.entityData.get(DATA_FLAGS_ID); if (value) { this.entityData.set(DATA_FLAGS_ID, (byte)(current | 1)); } else { this.entityData.set(DATA_FLAGS_ID, (byte)(current & 0xFFFFFFFE)); } } @Override public @Nullable EntityReference getOwnerReference() { return this.entityData.get(DATA_OWNERUUID_ID).orElse(null); } public void setOwner(@Nullable LivingEntity owner) { this.entityData.set(DATA_OWNERUUID_ID, Optional.ofNullable(owner).map(EntityReference::of)); } public void setOwnerReference(@Nullable EntityReference owner) { this.entityData.set(DATA_OWNERUUID_ID, Optional.ofNullable(owner)); } public void tame(Player player) { this.setTame(true, true); this.setOwner(player); if (player instanceof ServerPlayer) { ServerPlayer serverPlayer = (ServerPlayer)player; CriteriaTriggers.TAME_ANIMAL.trigger(serverPlayer, this); } } @Override public boolean canAttack(LivingEntity target) { if (this.isOwnedBy(target)) { return false; } return super.canAttack(target); } public boolean isOwnedBy(LivingEntity entity) { return entity == this.getOwner(); } public boolean wantsToAttack(LivingEntity target, LivingEntity owner) { return true; } @Override public @Nullable PlayerTeam getTeam() { LivingEntity owner; PlayerTeam ownTeam = super.getTeam(); if (ownTeam != null) { return ownTeam; } if (this.isTame() && (owner = this.getRootOwner()) != null) { return owner.getTeam(); } return null; } @Override protected boolean considersEntityAsAlly(Entity other) { if (this.isTame()) { LivingEntity owner = this.getRootOwner(); if (other == owner) { return true; } if (owner != null) { return owner.considersEntityAsAlly(other); } } return super.considersEntityAsAlly(other); } @Override public void die(DamageSource source) { LivingEntity livingEntity; ServerLevel serverLevel; Level level = this.level(); if (level instanceof ServerLevel && (serverLevel = (ServerLevel)level).getGameRules().get(GameRules.SHOW_DEATH_MESSAGES).booleanValue() && (livingEntity = this.getOwner()) instanceof ServerPlayer) { ServerPlayer serverPlayer = (ServerPlayer)livingEntity; serverPlayer.sendSystemMessage(this.getCombatTracker().getDeathMessage()); } super.die(source); } public boolean isOrderedToSit() { return this.orderedToSit; } public void setOrderedToSit(boolean orderedToSit) { this.orderedToSit = orderedToSit; } public void tryToTeleportToOwner() { LivingEntity owner = this.getOwner(); if (owner != null) { this.teleportToAroundBlockPos(owner.blockPosition()); } } public boolean shouldTryTeleportToOwner() { LivingEntity owner = this.getOwner(); return owner != null && this.distanceToSqr(this.getOwner()) >= 144.0; } private void teleportToAroundBlockPos(BlockPos targetPos) { for (int attempt = 0; attempt < 10; ++attempt) { int xd = this.random.nextIntBetweenInclusive(-3, 3); int zd = this.random.nextIntBetweenInclusive(-3, 3); if (Math.abs(xd) < 2 && Math.abs(zd) < 2) continue; int yd = this.random.nextIntBetweenInclusive(-1, 1); if (!this.maybeTeleportTo(targetPos.getX() + xd, targetPos.getY() + yd, targetPos.getZ() + zd)) continue; return; } } private boolean maybeTeleportTo(int x, int y, int z) { if (!this.canTeleportTo(new BlockPos(x, y, z))) { return false; } this.snapTo((double)x + 0.5, y, (double)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; } BlockState blockStateBelow = this.level().getBlockState(pos.below()); if (!this.canFlyToOwner() && blockStateBelow.getBlock() instanceof LeavesBlock) { return false; } BlockPos delta = pos.subtract(this.blockPosition()); return this.level().noCollision(this, this.getBoundingBox().move(delta)); } 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(double speedModifier, TagKey panicCausingDamageTypes) { super((PathfinderMob)TamableAnimal.this, speedModifier, panicCausingDamageTypes); } public TamableAnimalPanicGoal(double speedModifier) { super(TamableAnimal.this, speedModifier); } @Override public void tick() { if (!TamableAnimal.this.unableToMoveToOwner() && TamableAnimal.this.shouldTryTeleportToOwner()) { TamableAnimal.this.tryToTeleportToOwner(); } super.tick(); } } }