1443 lines
55 KiB
Java
1443 lines
55 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.mojang.serialization.Codec
|
|
* io.netty.buffer.ByteBuf
|
|
* org.jspecify.annotations.Nullable
|
|
*/
|
|
package net.minecraft.world.entity.animal;
|
|
|
|
import com.mojang.serialization.Codec;
|
|
import io.netty.buffer.ByteBuf;
|
|
import java.util.EnumSet;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.function.IntFunction;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.advancements.CriteriaTriggers;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.component.DataComponentGetter;
|
|
import net.minecraft.core.component.DataComponentType;
|
|
import net.minecraft.core.component.DataComponents;
|
|
import net.minecraft.core.particles.ItemParticleOption;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.network.codec.ByteBufCodecs;
|
|
import net.minecraft.network.codec.StreamCodec;
|
|
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.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.stats.Stats;
|
|
import net.minecraft.tags.BiomeTags;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.tags.FluidTags;
|
|
import net.minecraft.tags.ItemTags;
|
|
import net.minecraft.util.ByIdMap;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.StringRepresentable;
|
|
import net.minecraft.world.DifficultyInstance;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.AgeableMob;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntityDimensions;
|
|
import net.minecraft.world.entity.EntityReference;
|
|
import net.minecraft.world.entity.EntitySelector;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.ExperienceOrb;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.Pose;
|
|
import net.minecraft.world.entity.SpawnGroupData;
|
|
import net.minecraft.world.entity.TamableAnimal;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.ai.control.LookControl;
|
|
import net.minecraft.world.entity.ai.control.MoveControl;
|
|
import net.minecraft.world.entity.ai.goal.AvoidEntityGoal;
|
|
import net.minecraft.world.entity.ai.goal.BreedGoal;
|
|
import net.minecraft.world.entity.ai.goal.ClimbOnTopOfPowderSnowGoal;
|
|
import net.minecraft.world.entity.ai.goal.FleeSunGoal;
|
|
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
|
import net.minecraft.world.entity.ai.goal.FollowParentGoal;
|
|
import net.minecraft.world.entity.ai.goal.Goal;
|
|
import net.minecraft.world.entity.ai.goal.JumpGoal;
|
|
import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal;
|
|
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
|
|
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
|
|
import net.minecraft.world.entity.ai.goal.MoveToBlockGoal;
|
|
import net.minecraft.world.entity.ai.goal.PanicGoal;
|
|
import net.minecraft.world.entity.ai.goal.StrollThroughVillageGoal;
|
|
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
|
import net.minecraft.world.entity.ai.targeting.TargetingConditions;
|
|
import net.minecraft.world.entity.animal.AbstractFish;
|
|
import net.minecraft.world.entity.animal.AbstractSchoolingFish;
|
|
import net.minecraft.world.entity.animal.Animal;
|
|
import net.minecraft.world.entity.animal.Chicken;
|
|
import net.minecraft.world.entity.animal.PolarBear;
|
|
import net.minecraft.world.entity.animal.Rabbit;
|
|
import net.minecraft.world.entity.animal.Turtle;
|
|
import net.minecraft.world.entity.animal.wolf.Wolf;
|
|
import net.minecraft.world.entity.item.ItemEntity;
|
|
import net.minecraft.world.entity.monster.Monster;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
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.biome.Biome;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.CaveVines;
|
|
import net.minecraft.world.level.block.SweetBerryBushBlock;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.gamerules.GameRules;
|
|
import net.minecraft.world.level.pathfinder.PathType;
|
|
import net.minecraft.world.level.storage.ValueInput;
|
|
import net.minecraft.world.level.storage.ValueOutput;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jspecify.annotations.Nullable;
|
|
|
|
public class Fox
|
|
extends Animal {
|
|
private static final EntityDataAccessor<Integer> DATA_TYPE_ID = SynchedEntityData.defineId(Fox.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Byte> DATA_FLAGS_ID = SynchedEntityData.defineId(Fox.class, EntityDataSerializers.BYTE);
|
|
private static final int FLAG_SITTING = 1;
|
|
public static final int FLAG_CROUCHING = 4;
|
|
public static final int FLAG_INTERESTED = 8;
|
|
public static final int FLAG_POUNCING = 16;
|
|
private static final int FLAG_SLEEPING = 32;
|
|
private static final int FLAG_FACEPLANTED = 64;
|
|
private static final int FLAG_DEFENDING = 128;
|
|
private static final EntityDataAccessor<Optional<EntityReference<LivingEntity>>> DATA_TRUSTED_ID_0 = SynchedEntityData.defineId(Fox.class, EntityDataSerializers.OPTIONAL_LIVING_ENTITY_REFERENCE);
|
|
private static final EntityDataAccessor<Optional<EntityReference<LivingEntity>>> DATA_TRUSTED_ID_1 = SynchedEntityData.defineId(Fox.class, EntityDataSerializers.OPTIONAL_LIVING_ENTITY_REFERENCE);
|
|
private static final Predicate<ItemEntity> ALLOWED_ITEMS = e -> !e.hasPickUpDelay() && e.isAlive();
|
|
private static final Predicate<Entity> TRUSTED_TARGET_SELECTOR = entity -> {
|
|
if (entity instanceof LivingEntity) {
|
|
LivingEntity livingEntity = (LivingEntity)entity;
|
|
return livingEntity.getLastHurtMob() != null && livingEntity.getLastHurtMobTimestamp() < livingEntity.tickCount + 600;
|
|
}
|
|
return false;
|
|
};
|
|
private static final Predicate<Entity> STALKABLE_PREY = entity -> entity instanceof Chicken || entity instanceof Rabbit;
|
|
private static final Predicate<Entity> AVOID_PLAYERS = entity -> !entity.isDiscrete() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test((Entity)entity);
|
|
private static final int MIN_TICKS_BEFORE_EAT = 600;
|
|
private static final EntityDimensions BABY_DIMENSIONS = EntityType.FOX.getDimensions().scale(0.5f).withEyeHeight(0.2975f);
|
|
private static final Codec<List<EntityReference<LivingEntity>>> TRUSTED_LIST_CODEC = EntityReference.codec().listOf();
|
|
private static final boolean DEFAULT_SLEEPING = false;
|
|
private static final boolean DEFAULT_SITTING = false;
|
|
private static final boolean DEFAULT_CROUCHING = false;
|
|
private Goal landTargetGoal;
|
|
private Goal turtleEggTargetGoal;
|
|
private Goal fishTargetGoal;
|
|
private float interestedAngle;
|
|
private float interestedAngleO;
|
|
private float crouchAmount;
|
|
private float crouchAmountO;
|
|
private int ticksSinceEaten;
|
|
|
|
public Fox(EntityType<? extends Fox> type, Level level) {
|
|
super((EntityType<? extends Animal>)type, level);
|
|
this.lookControl = new FoxLookControl();
|
|
this.moveControl = new FoxMoveControl();
|
|
this.setPathfindingMalus(PathType.DANGER_OTHER, 0.0f);
|
|
this.setPathfindingMalus(PathType.DAMAGE_OTHER, 0.0f);
|
|
this.setCanPickUpLoot(true);
|
|
this.getNavigation().setRequiredPathLength(32.0f);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
|
|
super.defineSynchedData(entityData);
|
|
entityData.define(DATA_TRUSTED_ID_0, Optional.empty());
|
|
entityData.define(DATA_TRUSTED_ID_1, Optional.empty());
|
|
entityData.define(DATA_TYPE_ID, Variant.DEFAULT.getId());
|
|
entityData.define(DATA_FLAGS_ID, (byte)0);
|
|
}
|
|
|
|
@Override
|
|
protected void registerGoals() {
|
|
this.landTargetGoal = new NearestAttackableTargetGoal<Animal>(this, Animal.class, 10, false, false, (target, level) -> target instanceof Chicken || target instanceof Rabbit);
|
|
this.turtleEggTargetGoal = new NearestAttackableTargetGoal<Turtle>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR);
|
|
this.fishTargetGoal = new NearestAttackableTargetGoal<AbstractFish>(this, AbstractFish.class, 20, false, false, (target, level) -> target instanceof AbstractSchoolingFish);
|
|
this.goalSelector.addGoal(0, new FoxFloatGoal());
|
|
this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
|
|
this.goalSelector.addGoal(1, new FaceplantGoal());
|
|
this.goalSelector.addGoal(2, new FoxPanicGoal(2.2));
|
|
this.goalSelector.addGoal(3, new FoxBreedGoal(this, 1.0));
|
|
this.goalSelector.addGoal(4, new AvoidEntityGoal<Player>(this, Player.class, 16.0f, 1.6, 1.4, entity -> AVOID_PLAYERS.test((Entity)entity) && !this.trusts((LivingEntity)entity) && !this.isDefending()));
|
|
this.goalSelector.addGoal(4, new AvoidEntityGoal<Wolf>(this, Wolf.class, 8.0f, 1.6, 1.4, entity -> !((Wolf)entity).isTame() && !this.isDefending()));
|
|
this.goalSelector.addGoal(4, new AvoidEntityGoal<PolarBear>(this, PolarBear.class, 8.0f, 1.6, 1.4, entity -> !this.isDefending()));
|
|
this.goalSelector.addGoal(5, new StalkPreyGoal());
|
|
this.goalSelector.addGoal(6, new FoxPounceGoal());
|
|
this.goalSelector.addGoal(6, new SeekShelterGoal(1.25));
|
|
this.goalSelector.addGoal(7, new FoxMeleeAttackGoal((double)1.2f, true));
|
|
this.goalSelector.addGoal(7, new SleepGoal());
|
|
this.goalSelector.addGoal(8, new FoxFollowParentGoal(this, 1.25));
|
|
this.goalSelector.addGoal(9, new FoxStrollThroughVillageGoal(32, 200));
|
|
this.goalSelector.addGoal(10, new FoxEatBerriesGoal((double)1.2f, 12, 1));
|
|
this.goalSelector.addGoal(10, new LeapAtTargetGoal(this, 0.4f));
|
|
this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 1.0));
|
|
this.goalSelector.addGoal(11, new FoxSearchForItemsGoal());
|
|
this.goalSelector.addGoal(12, new FoxLookAtPlayerGoal(this, Player.class, 24.0f));
|
|
this.goalSelector.addGoal(13, new PerchAndSearchGoal());
|
|
this.targetSelector.addGoal(3, new DefendTrustedTargetGoal(LivingEntity.class, false, false, (target, level) -> TRUSTED_TARGET_SELECTOR.test(target) && !this.trusts(target)));
|
|
}
|
|
|
|
@Override
|
|
public void aiStep() {
|
|
if (!this.level().isClientSide() && this.isAlive() && this.isEffectiveAi()) {
|
|
LivingEntity target;
|
|
++this.ticksSinceEaten;
|
|
ItemStack itemInMouth = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
if (this.canEat(itemInMouth)) {
|
|
if (this.ticksSinceEaten > 600) {
|
|
ItemStack remainingFood = itemInMouth.finishUsingItem(this.level(), this);
|
|
if (!remainingFood.isEmpty()) {
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, remainingFood);
|
|
}
|
|
this.ticksSinceEaten = 0;
|
|
} else if (this.ticksSinceEaten > 560 && this.random.nextFloat() < 0.1f) {
|
|
this.playEatingSound();
|
|
this.level().broadcastEntityEvent(this, (byte)45);
|
|
}
|
|
}
|
|
if ((target = this.getTarget()) == null || !target.isAlive()) {
|
|
this.setIsCrouching(false);
|
|
this.setIsInterested(false);
|
|
}
|
|
}
|
|
if (this.isSleeping() || this.isImmobile()) {
|
|
this.jumping = false;
|
|
this.xxa = 0.0f;
|
|
this.zza = 0.0f;
|
|
}
|
|
super.aiStep();
|
|
if (this.isDefending() && this.random.nextFloat() < 0.05f) {
|
|
this.playSound(SoundEvents.FOX_AGGRO, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean isImmobile() {
|
|
return this.isDeadOrDying();
|
|
}
|
|
|
|
private boolean canEat(ItemStack itemInMouth) {
|
|
return itemInMouth.has(DataComponents.FOOD) && this.getTarget() == null && this.onGround() && !this.isSleeping();
|
|
}
|
|
|
|
@Override
|
|
protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) {
|
|
if (random.nextFloat() < 0.2f) {
|
|
float odds = random.nextFloat();
|
|
ItemStack heldInMouth = odds < 0.05f ? new ItemStack(Items.EMERALD) : (odds < 0.2f ? new ItemStack(Items.EGG) : (odds < 0.4f ? (random.nextBoolean() ? new ItemStack(Items.RABBIT_FOOT) : new ItemStack(Items.RABBIT_HIDE)) : (odds < 0.6f ? new ItemStack(Items.WHEAT) : (odds < 0.8f ? new ItemStack(Items.LEATHER) : new ItemStack(Items.FEATHER)))));
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, heldInMouth);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handleEntityEvent(byte id) {
|
|
if (id == 45) {
|
|
ItemStack mouthItem = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
if (!mouthItem.isEmpty()) {
|
|
for (int i = 0; i < 8; ++i) {
|
|
Vec3 direction = new Vec3(((double)this.random.nextFloat() - 0.5) * 0.1, Math.random() * 0.1 + 0.1, 0.0).xRot(-this.getXRot() * ((float)Math.PI / 180)).yRot(-this.getYRot() * ((float)Math.PI / 180));
|
|
this.level().addParticle(new ItemParticleOption(ParticleTypes.ITEM, mouthItem), this.getX() + this.getLookAngle().x / 2.0, this.getY(), this.getZ() + this.getLookAngle().z / 2.0, direction.x, direction.y + 0.05, direction.z);
|
|
}
|
|
}
|
|
} else {
|
|
super.handleEntityEvent(id);
|
|
}
|
|
}
|
|
|
|
public static AttributeSupplier.Builder createAttributes() {
|
|
return Animal.createAnimalAttributes().add(Attributes.MOVEMENT_SPEED, 0.3f).add(Attributes.MAX_HEALTH, 10.0).add(Attributes.ATTACK_DAMAGE, 2.0).add(Attributes.SAFE_FALL_DISTANCE, 5.0).add(Attributes.FOLLOW_RANGE, 32.0);
|
|
}
|
|
|
|
@Override
|
|
public @Nullable Fox getBreedOffspring(ServerLevel level, AgeableMob partner) {
|
|
Fox baby = EntityType.FOX.create(level, EntitySpawnReason.BREEDING);
|
|
if (baby != null) {
|
|
baby.setVariant(this.random.nextBoolean() ? this.getVariant() : ((Fox)partner).getVariant());
|
|
}
|
|
return baby;
|
|
}
|
|
|
|
public static boolean checkFoxSpawnRules(EntityType<Fox> type, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
|
|
return level.getBlockState(pos.below()).is(BlockTags.FOXES_SPAWNABLE_ON) && Fox.isBrightEnoughToSpawn(level, pos);
|
|
}
|
|
|
|
@Override
|
|
public @Nullable SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData) {
|
|
Holder<Biome> biome = level.getBiome(this.blockPosition());
|
|
Variant variant = Variant.byBiome(biome);
|
|
boolean isBaby = false;
|
|
if (groupData instanceof FoxGroupData) {
|
|
FoxGroupData foxGroupData = (FoxGroupData)groupData;
|
|
variant = foxGroupData.variant;
|
|
if (foxGroupData.getGroupSize() >= 2) {
|
|
isBaby = true;
|
|
}
|
|
} else {
|
|
groupData = new FoxGroupData(variant);
|
|
}
|
|
this.setVariant(variant);
|
|
if (isBaby) {
|
|
this.setAge(-24000);
|
|
}
|
|
if (level instanceof ServerLevel) {
|
|
this.setTargetGoals();
|
|
}
|
|
this.populateDefaultEquipmentSlots(level.getRandom(), difficulty);
|
|
return super.finalizeSpawn(level, difficulty, spawnReason, groupData);
|
|
}
|
|
|
|
private void setTargetGoals() {
|
|
if (this.getVariant() == Variant.RED) {
|
|
this.targetSelector.addGoal(4, this.landTargetGoal);
|
|
this.targetSelector.addGoal(4, this.turtleEggTargetGoal);
|
|
this.targetSelector.addGoal(6, this.fishTargetGoal);
|
|
} else {
|
|
this.targetSelector.addGoal(4, this.fishTargetGoal);
|
|
this.targetSelector.addGoal(6, this.landTargetGoal);
|
|
this.targetSelector.addGoal(6, this.turtleEggTargetGoal);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void playEatingSound() {
|
|
this.playSound(SoundEvents.FOX_EAT, 1.0f, 1.0f);
|
|
}
|
|
|
|
@Override
|
|
public EntityDimensions getDefaultDimensions(Pose pose) {
|
|
return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose);
|
|
}
|
|
|
|
public Variant getVariant() {
|
|
return Variant.byId(this.entityData.get(DATA_TYPE_ID));
|
|
}
|
|
|
|
private void setVariant(Variant variant) {
|
|
this.entityData.set(DATA_TYPE_ID, variant.getId());
|
|
}
|
|
|
|
@Override
|
|
public <T> @Nullable T get(DataComponentType<? extends T> type) {
|
|
if (type == DataComponents.FOX_VARIANT) {
|
|
return Fox.castComponentValue(type, this.getVariant());
|
|
}
|
|
return super.get(type);
|
|
}
|
|
|
|
@Override
|
|
protected void applyImplicitComponents(DataComponentGetter components) {
|
|
this.applyImplicitComponentIfPresent(components, DataComponents.FOX_VARIANT);
|
|
super.applyImplicitComponents(components);
|
|
}
|
|
|
|
@Override
|
|
protected <T> boolean applyImplicitComponent(DataComponentType<T> type, T value) {
|
|
if (type == DataComponents.FOX_VARIANT) {
|
|
this.setVariant(Fox.castComponentValue(DataComponents.FOX_VARIANT, value));
|
|
return true;
|
|
}
|
|
return super.applyImplicitComponent(type, value);
|
|
}
|
|
|
|
private Stream<EntityReference<LivingEntity>> getTrustedEntities() {
|
|
return Stream.concat(this.entityData.get(DATA_TRUSTED_ID_0).stream(), this.entityData.get(DATA_TRUSTED_ID_1).stream());
|
|
}
|
|
|
|
private void addTrustedEntity(LivingEntity entity) {
|
|
this.addTrustedEntity(EntityReference.of(entity));
|
|
}
|
|
|
|
private void addTrustedEntity(EntityReference<LivingEntity> reference) {
|
|
if (this.entityData.get(DATA_TRUSTED_ID_0).isPresent()) {
|
|
this.entityData.set(DATA_TRUSTED_ID_1, Optional.of(reference));
|
|
} else {
|
|
this.entityData.set(DATA_TRUSTED_ID_0, Optional.of(reference));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(ValueOutput output) {
|
|
super.addAdditionalSaveData(output);
|
|
output.store("Trusted", TRUSTED_LIST_CODEC, this.getTrustedEntities().toList());
|
|
output.putBoolean("Sleeping", this.isSleeping());
|
|
output.store("Type", Variant.CODEC, this.getVariant());
|
|
output.putBoolean("Sitting", this.isSitting());
|
|
output.putBoolean("Crouching", this.isCrouching());
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(ValueInput input) {
|
|
super.readAdditionalSaveData(input);
|
|
this.clearTrusted();
|
|
input.read("Trusted", TRUSTED_LIST_CODEC).orElse(List.of()).forEach(this::addTrustedEntity);
|
|
this.setSleeping(input.getBooleanOr("Sleeping", false));
|
|
this.setVariant(input.read("Type", Variant.CODEC).orElse(Variant.DEFAULT));
|
|
this.setSitting(input.getBooleanOr("Sitting", false));
|
|
this.setIsCrouching(input.getBooleanOr("Crouching", false));
|
|
if (this.level() instanceof ServerLevel) {
|
|
this.setTargetGoals();
|
|
}
|
|
}
|
|
|
|
private void clearTrusted() {
|
|
this.entityData.set(DATA_TRUSTED_ID_0, Optional.empty());
|
|
this.entityData.set(DATA_TRUSTED_ID_1, Optional.empty());
|
|
}
|
|
|
|
public boolean isSitting() {
|
|
return this.getFlag(1);
|
|
}
|
|
|
|
public void setSitting(boolean value) {
|
|
this.setFlag(1, value);
|
|
}
|
|
|
|
public boolean isFaceplanted() {
|
|
return this.getFlag(64);
|
|
}
|
|
|
|
private void setFaceplanted(boolean faceplanted) {
|
|
this.setFlag(64, faceplanted);
|
|
}
|
|
|
|
private boolean isDefending() {
|
|
return this.getFlag(128);
|
|
}
|
|
|
|
private void setDefending(boolean defending) {
|
|
this.setFlag(128, defending);
|
|
}
|
|
|
|
@Override
|
|
public boolean isSleeping() {
|
|
return this.getFlag(32);
|
|
}
|
|
|
|
private void setSleeping(boolean sleeping) {
|
|
this.setFlag(32, sleeping);
|
|
}
|
|
|
|
private void setFlag(int flag, boolean value) {
|
|
if (value) {
|
|
this.entityData.set(DATA_FLAGS_ID, (byte)(this.entityData.get(DATA_FLAGS_ID) | flag));
|
|
} else {
|
|
this.entityData.set(DATA_FLAGS_ID, (byte)(this.entityData.get(DATA_FLAGS_ID) & ~flag));
|
|
}
|
|
}
|
|
|
|
private boolean getFlag(int flag) {
|
|
return (this.entityData.get(DATA_FLAGS_ID) & flag) != 0;
|
|
}
|
|
|
|
@Override
|
|
protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
|
|
return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot();
|
|
}
|
|
|
|
@Override
|
|
public boolean canHoldItem(ItemStack itemStack) {
|
|
ItemStack heldItemStack = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
return heldItemStack.isEmpty() || this.ticksSinceEaten > 0 && itemStack.has(DataComponents.FOOD) && !heldItemStack.has(DataComponents.FOOD);
|
|
}
|
|
|
|
private void spitOutItem(ItemStack itemStack) {
|
|
if (itemStack.isEmpty() || this.level().isClientSide()) {
|
|
return;
|
|
}
|
|
ItemEntity thrownItem = new ItemEntity(this.level(), this.getX() + this.getLookAngle().x, this.getY() + 1.0, this.getZ() + this.getLookAngle().z, itemStack);
|
|
thrownItem.setPickUpDelay(40);
|
|
thrownItem.setThrower(this);
|
|
this.playSound(SoundEvents.FOX_SPIT, 1.0f, 1.0f);
|
|
this.level().addFreshEntity(thrownItem);
|
|
}
|
|
|
|
private void dropItemStack(ItemStack itemStack) {
|
|
ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
|
|
this.level().addFreshEntity(itemEntity);
|
|
}
|
|
|
|
@Override
|
|
protected void pickUpItem(ServerLevel level, ItemEntity entity) {
|
|
ItemStack itemStack = entity.getItem();
|
|
if (this.canHoldItem(itemStack)) {
|
|
int count = itemStack.getCount();
|
|
if (count > 1) {
|
|
this.dropItemStack(itemStack.split(count - 1));
|
|
}
|
|
this.spitOutItem(this.getItemBySlot(EquipmentSlot.MAINHAND));
|
|
this.onItemPickup(entity);
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, itemStack.split(1));
|
|
this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
|
|
this.take(entity, itemStack.getCount());
|
|
entity.discard();
|
|
this.ticksSinceEaten = 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
if (this.isEffectiveAi()) {
|
|
boolean inWater = this.isInWater();
|
|
if (inWater || this.getTarget() != null || this.level().isThundering()) {
|
|
this.wakeUp();
|
|
}
|
|
if (inWater || this.isSleeping()) {
|
|
this.setSitting(false);
|
|
}
|
|
if (this.isFaceplanted() && this.level().random.nextFloat() < 0.2f) {
|
|
BlockPos pos = this.blockPosition();
|
|
BlockState state = this.level().getBlockState(pos);
|
|
this.level().levelEvent(2001, pos, Block.getId(state));
|
|
}
|
|
}
|
|
this.interestedAngleO = this.interestedAngle;
|
|
this.interestedAngle = this.isInterested() ? (this.interestedAngle += (1.0f - this.interestedAngle) * 0.4f) : (this.interestedAngle += (0.0f - this.interestedAngle) * 0.4f);
|
|
this.crouchAmountO = this.crouchAmount;
|
|
if (this.isCrouching()) {
|
|
this.crouchAmount += 0.2f;
|
|
if (this.crouchAmount > 3.0f) {
|
|
this.crouchAmount = 3.0f;
|
|
}
|
|
} else {
|
|
this.crouchAmount = 0.0f;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isFood(ItemStack itemStack) {
|
|
return itemStack.is(ItemTags.FOX_FOOD);
|
|
}
|
|
|
|
@Override
|
|
protected void onOffspringSpawnedFromEgg(Player spawner, Mob offspring) {
|
|
((Fox)offspring).addTrustedEntity(spawner);
|
|
}
|
|
|
|
public boolean isPouncing() {
|
|
return this.getFlag(16);
|
|
}
|
|
|
|
public void setIsPouncing(boolean pouncing) {
|
|
this.setFlag(16, pouncing);
|
|
}
|
|
|
|
public boolean isFullyCrouched() {
|
|
return this.crouchAmount == 3.0f;
|
|
}
|
|
|
|
public void setIsCrouching(boolean isCrouching) {
|
|
this.setFlag(4, isCrouching);
|
|
}
|
|
|
|
@Override
|
|
public boolean isCrouching() {
|
|
return this.getFlag(4);
|
|
}
|
|
|
|
public void setIsInterested(boolean value) {
|
|
this.setFlag(8, value);
|
|
}
|
|
|
|
public boolean isInterested() {
|
|
return this.getFlag(8);
|
|
}
|
|
|
|
public float getHeadRollAngle(float a) {
|
|
return Mth.lerp(a, this.interestedAngleO, this.interestedAngle) * 0.11f * (float)Math.PI;
|
|
}
|
|
|
|
public float getCrouchAmount(float a) {
|
|
return Mth.lerp(a, this.crouchAmountO, this.crouchAmount);
|
|
}
|
|
|
|
@Override
|
|
public void setTarget(@Nullable LivingEntity target) {
|
|
if (this.isDefending() && target == null) {
|
|
this.setDefending(false);
|
|
}
|
|
super.setTarget(target);
|
|
}
|
|
|
|
private void wakeUp() {
|
|
this.setSleeping(false);
|
|
}
|
|
|
|
private void clearStates() {
|
|
this.setIsInterested(false);
|
|
this.setIsCrouching(false);
|
|
this.setSitting(false);
|
|
this.setSleeping(false);
|
|
this.setDefending(false);
|
|
this.setFaceplanted(false);
|
|
}
|
|
|
|
private boolean canMove() {
|
|
return !this.isSleeping() && !this.isSitting() && !this.isFaceplanted();
|
|
}
|
|
|
|
@Override
|
|
public void playAmbientSound() {
|
|
SoundEvent ambient = this.getAmbientSound();
|
|
if (ambient == SoundEvents.FOX_SCREECH) {
|
|
this.playSound(ambient, 2.0f, this.getVoicePitch());
|
|
} else {
|
|
super.playAmbientSound();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable SoundEvent getAmbientSound() {
|
|
List<Entity> nearbyEntities;
|
|
if (this.isSleeping()) {
|
|
return SoundEvents.FOX_SLEEP;
|
|
}
|
|
if (!this.level().isBrightOutside() && this.random.nextFloat() < 0.1f && (nearbyEntities = this.level().getEntitiesOfClass(Player.class, this.getBoundingBox().inflate(16.0, 16.0, 16.0), EntitySelector.NO_SPECTATORS)).isEmpty()) {
|
|
return SoundEvents.FOX_SCREECH;
|
|
}
|
|
return SoundEvents.FOX_AMBIENT;
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable SoundEvent getHurtSound(DamageSource source) {
|
|
return SoundEvents.FOX_HURT;
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable SoundEvent getDeathSound() {
|
|
return SoundEvents.FOX_DEATH;
|
|
}
|
|
|
|
private boolean trusts(LivingEntity entity) {
|
|
return this.getTrustedEntities().anyMatch(trusted -> trusted.matches(entity));
|
|
}
|
|
|
|
@Override
|
|
protected void dropAllDeathLoot(ServerLevel level, DamageSource source) {
|
|
ItemStack itemStack = this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
if (!itemStack.isEmpty()) {
|
|
this.spawnAtLocation(level, itemStack);
|
|
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
|
}
|
|
super.dropAllDeathLoot(level, source);
|
|
}
|
|
|
|
public static boolean isPathClear(Fox fox, LivingEntity target) {
|
|
double zdiff = target.getZ() - fox.getZ();
|
|
double xdiff = target.getX() - fox.getX();
|
|
double slope = zdiff / xdiff;
|
|
int increments = 6;
|
|
for (int i = 0; i < 6; ++i) {
|
|
double z = slope == 0.0 ? 0.0 : zdiff * (double)((float)i / 6.0f);
|
|
double x = slope == 0.0 ? xdiff * (double)((float)i / 6.0f) : z / slope;
|
|
for (int j = 1; j < 4; ++j) {
|
|
if (fox.level().getBlockState(BlockPos.containing(fox.getX() + x, fox.getY() + (double)j, fox.getZ() + z)).canBeReplaced()) continue;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public Vec3 getLeashOffset() {
|
|
return new Vec3(0.0, 0.55f * this.getEyeHeight(), this.getBbWidth() * 0.4f);
|
|
}
|
|
|
|
public class FoxLookControl
|
|
extends LookControl {
|
|
public FoxLookControl() {
|
|
super(Fox.this);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (!Fox.this.isSleeping()) {
|
|
super.tick();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected boolean resetXRotOnTick() {
|
|
return !Fox.this.isPouncing() && !Fox.this.isCrouching() && !Fox.this.isInterested() && !Fox.this.isFaceplanted();
|
|
}
|
|
}
|
|
|
|
private class FoxMoveControl
|
|
extends MoveControl {
|
|
public FoxMoveControl() {
|
|
super(Fox.this);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (Fox.this.canMove()) {
|
|
super.tick();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static enum Variant implements StringRepresentable
|
|
{
|
|
RED(0, "red"),
|
|
SNOW(1, "snow");
|
|
|
|
public static final Variant DEFAULT;
|
|
public static final StringRepresentable.EnumCodec<Variant> CODEC;
|
|
private static final IntFunction<Variant> BY_ID;
|
|
public static final StreamCodec<ByteBuf, Variant> STREAM_CODEC;
|
|
private final int id;
|
|
private final String name;
|
|
|
|
private Variant(int id, String name) {
|
|
this.id = id;
|
|
this.name = name;
|
|
}
|
|
|
|
@Override
|
|
public String getSerializedName() {
|
|
return this.name;
|
|
}
|
|
|
|
public int getId() {
|
|
return this.id;
|
|
}
|
|
|
|
public static Variant byId(int id) {
|
|
return BY_ID.apply(id);
|
|
}
|
|
|
|
public static Variant byBiome(Holder<Biome> biome) {
|
|
return biome.is(BiomeTags.SPAWNS_SNOW_FOXES) ? SNOW : RED;
|
|
}
|
|
|
|
static {
|
|
DEFAULT = RED;
|
|
CODEC = StringRepresentable.fromEnum(Variant::values);
|
|
BY_ID = ByIdMap.continuous(Variant::getId, Variant.values(), ByIdMap.OutOfBoundsStrategy.ZERO);
|
|
STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Variant::getId);
|
|
}
|
|
}
|
|
|
|
private class FoxFloatGoal
|
|
extends FloatGoal {
|
|
public FoxFloatGoal() {
|
|
super(Fox.this);
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
super.start();
|
|
Fox.this.clearStates();
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return Fox.this.isInWater() && Fox.this.getFluidHeight(FluidTags.WATER) > 0.25 || Fox.this.isInLava();
|
|
}
|
|
}
|
|
|
|
private class FaceplantGoal
|
|
extends Goal {
|
|
int countdown;
|
|
|
|
public FaceplantGoal() {
|
|
this.setFlags(EnumSet.of(Goal.Flag.LOOK, Goal.Flag.JUMP, Goal.Flag.MOVE));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return Fox.this.isFaceplanted();
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return this.canUse() && this.countdown > 0;
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
this.countdown = this.adjustedTickDelay(40);
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
Fox.this.setFaceplanted(false);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
--this.countdown;
|
|
}
|
|
}
|
|
|
|
private class FoxPanicGoal
|
|
extends PanicGoal {
|
|
public FoxPanicGoal(double speedModifier) {
|
|
super(Fox.this, speedModifier);
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldPanic() {
|
|
return !Fox.this.isDefending() && super.shouldPanic();
|
|
}
|
|
}
|
|
|
|
private class FoxBreedGoal
|
|
extends BreedGoal {
|
|
public FoxBreedGoal(Fox fox, double speedModifier) {
|
|
super(fox, speedModifier);
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
((Fox)this.animal).clearStates();
|
|
((Fox)this.partner).clearStates();
|
|
super.start();
|
|
}
|
|
|
|
@Override
|
|
protected void breed() {
|
|
Fox offspring = (Fox)this.animal.getBreedOffspring(this.level, this.partner);
|
|
if (offspring == null) {
|
|
return;
|
|
}
|
|
ServerPlayer animalLoveCause = this.animal.getLoveCause();
|
|
ServerPlayer partnerLoveCause = this.partner.getLoveCause();
|
|
ServerPlayer loveCause = animalLoveCause;
|
|
if (animalLoveCause != null) {
|
|
offspring.addTrustedEntity(animalLoveCause);
|
|
} else {
|
|
loveCause = partnerLoveCause;
|
|
}
|
|
if (partnerLoveCause != null && animalLoveCause != partnerLoveCause) {
|
|
offspring.addTrustedEntity(partnerLoveCause);
|
|
}
|
|
if (loveCause != null) {
|
|
loveCause.awardStat(Stats.ANIMALS_BRED);
|
|
CriteriaTriggers.BRED_ANIMALS.trigger(loveCause, this.animal, this.partner, offspring);
|
|
}
|
|
this.animal.setAge(6000);
|
|
this.partner.setAge(6000);
|
|
this.animal.resetLove();
|
|
this.partner.resetLove();
|
|
offspring.setAge(-24000);
|
|
offspring.snapTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0f, 0.0f);
|
|
this.level.addFreshEntityWithPassengers(offspring);
|
|
this.level.broadcastEntityEvent(this.animal, (byte)18);
|
|
if (this.level.getGameRules().get(GameRules.MOB_DROPS).booleanValue()) {
|
|
this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), this.animal.getRandom().nextInt(7) + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
private class StalkPreyGoal
|
|
extends Goal {
|
|
public StalkPreyGoal() {
|
|
this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
if (Fox.this.isSleeping()) {
|
|
return false;
|
|
}
|
|
LivingEntity target = Fox.this.getTarget();
|
|
return target != null && target.isAlive() && STALKABLE_PREY.test(target) && Fox.this.distanceToSqr(target) > 36.0 && !Fox.this.isCrouching() && !Fox.this.isInterested() && !Fox.this.jumping;
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Fox.this.setSitting(false);
|
|
Fox.this.setFaceplanted(false);
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
LivingEntity target = Fox.this.getTarget();
|
|
if (target != null && Fox.isPathClear(Fox.this, target)) {
|
|
Fox.this.setIsInterested(true);
|
|
Fox.this.setIsCrouching(true);
|
|
Fox.this.getNavigation().stop();
|
|
Fox.this.getLookControl().setLookAt(target, Fox.this.getMaxHeadYRot(), Fox.this.getMaxHeadXRot());
|
|
} else {
|
|
Fox.this.setIsInterested(false);
|
|
Fox.this.setIsCrouching(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
LivingEntity target = Fox.this.getTarget();
|
|
if (target == null) {
|
|
return;
|
|
}
|
|
Fox.this.getLookControl().setLookAt(target, Fox.this.getMaxHeadYRot(), Fox.this.getMaxHeadXRot());
|
|
if (Fox.this.distanceToSqr(target) <= 36.0) {
|
|
Fox.this.setIsInterested(true);
|
|
Fox.this.setIsCrouching(true);
|
|
Fox.this.getNavigation().stop();
|
|
} else {
|
|
Fox.this.getNavigation().moveTo(target, 1.5);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class FoxPounceGoal
|
|
extends JumpGoal {
|
|
@Override
|
|
public boolean canUse() {
|
|
if (!Fox.this.isFullyCrouched()) {
|
|
return false;
|
|
}
|
|
LivingEntity target = Fox.this.getTarget();
|
|
if (target == null || !target.isAlive()) {
|
|
return false;
|
|
}
|
|
if (target.getMotionDirection() != target.getDirection()) {
|
|
return false;
|
|
}
|
|
boolean hasClearPath = Fox.isPathClear(Fox.this, target);
|
|
if (!hasClearPath) {
|
|
Fox.this.getNavigation().createPath(target, 0);
|
|
Fox.this.setIsCrouching(false);
|
|
Fox.this.setIsInterested(false);
|
|
}
|
|
return hasClearPath;
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
LivingEntity target = Fox.this.getTarget();
|
|
if (target == null || !target.isAlive()) {
|
|
return false;
|
|
}
|
|
double yd = Fox.this.getDeltaMovement().y;
|
|
return !(yd * yd < (double)0.05f && Math.abs(Fox.this.getXRot()) < 15.0f && Fox.this.onGround() || Fox.this.isFaceplanted());
|
|
}
|
|
|
|
@Override
|
|
public boolean isInterruptable() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Fox.this.setJumping(true);
|
|
Fox.this.setIsPouncing(true);
|
|
Fox.this.setIsInterested(false);
|
|
LivingEntity target = Fox.this.getTarget();
|
|
if (target != null) {
|
|
Fox.this.getLookControl().setLookAt(target, 60.0f, 30.0f);
|
|
Vec3 uv = new Vec3(target.getX() - Fox.this.getX(), target.getY() - Fox.this.getY(), target.getZ() - Fox.this.getZ()).normalize();
|
|
Fox.this.setDeltaMovement(Fox.this.getDeltaMovement().add(uv.x * 0.8, 0.9, uv.z * 0.8));
|
|
}
|
|
Fox.this.getNavigation().stop();
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
Fox.this.setIsCrouching(false);
|
|
Fox.this.crouchAmount = 0.0f;
|
|
Fox.this.crouchAmountO = 0.0f;
|
|
Fox.this.setIsInterested(false);
|
|
Fox.this.setIsPouncing(false);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
LivingEntity target = Fox.this.getTarget();
|
|
if (target != null) {
|
|
Fox.this.getLookControl().setLookAt(target, 60.0f, 30.0f);
|
|
}
|
|
if (!Fox.this.isFaceplanted()) {
|
|
Vec3 movement = Fox.this.getDeltaMovement();
|
|
if (movement.y * movement.y < (double)0.03f && Fox.this.getXRot() != 0.0f) {
|
|
Fox.this.setXRot(Mth.rotLerp(0.2f, Fox.this.getXRot(), 0.0f));
|
|
} else {
|
|
double direction = movement.horizontalDistance();
|
|
double rotation = Math.signum(-movement.y) * Math.acos(direction / movement.length()) * 57.2957763671875;
|
|
Fox.this.setXRot((float)rotation);
|
|
}
|
|
}
|
|
if (target != null && Fox.this.distanceTo(target) <= 2.0f) {
|
|
Fox.this.doHurtTarget(FoxPounceGoal.getServerLevel(Fox.this.level()), target);
|
|
} else if (Fox.this.getXRot() > 0.0f && Fox.this.onGround() && (float)Fox.this.getDeltaMovement().y != 0.0f && Fox.this.level().getBlockState(Fox.this.blockPosition()).is(Blocks.SNOW)) {
|
|
Fox.this.setXRot(60.0f);
|
|
Fox.this.setTarget(null);
|
|
Fox.this.setFaceplanted(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class SeekShelterGoal
|
|
extends FleeSunGoal {
|
|
private int interval;
|
|
|
|
public SeekShelterGoal(double speedModifier) {
|
|
super(Fox.this, speedModifier);
|
|
this.interval = SeekShelterGoal.reducedTickDelay(100);
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
if (Fox.this.isSleeping() || this.mob.getTarget() != null) {
|
|
return false;
|
|
}
|
|
if (Fox.this.level().isThundering() && Fox.this.level().canSeeSky(this.mob.blockPosition())) {
|
|
return this.setWantedPos();
|
|
}
|
|
if (this.interval > 0) {
|
|
--this.interval;
|
|
return false;
|
|
}
|
|
this.interval = 100;
|
|
BlockPos pos = this.mob.blockPosition();
|
|
return Fox.this.level().isBrightOutside() && Fox.this.level().canSeeSky(pos) && !((ServerLevel)Fox.this.level()).isVillage(pos) && this.setWantedPos();
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Fox.this.clearStates();
|
|
super.start();
|
|
}
|
|
}
|
|
|
|
private class FoxMeleeAttackGoal
|
|
extends MeleeAttackGoal {
|
|
public FoxMeleeAttackGoal(double speedModifier, boolean trackTarget) {
|
|
super(Fox.this, speedModifier, trackTarget);
|
|
}
|
|
|
|
@Override
|
|
protected void checkAndPerformAttack(LivingEntity target) {
|
|
if (this.canPerformAttack(target)) {
|
|
this.resetAttackCooldown();
|
|
this.mob.doHurtTarget(FoxMeleeAttackGoal.getServerLevel(this.mob), target);
|
|
Fox.this.playSound(SoundEvents.FOX_BITE, 1.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Fox.this.setIsInterested(false);
|
|
super.start();
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return !Fox.this.isSitting() && !Fox.this.isSleeping() && !Fox.this.isCrouching() && !Fox.this.isFaceplanted() && super.canUse();
|
|
}
|
|
}
|
|
|
|
private class SleepGoal
|
|
extends FoxBehaviorGoal {
|
|
private static final int WAIT_TIME_BEFORE_SLEEP = SleepGoal.reducedTickDelay(140);
|
|
private int countdown;
|
|
|
|
public SleepGoal() {
|
|
this.countdown = Fox.this.random.nextInt(WAIT_TIME_BEFORE_SLEEP);
|
|
this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK, Goal.Flag.JUMP));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
if (Fox.this.xxa != 0.0f || Fox.this.yya != 0.0f || Fox.this.zza != 0.0f) {
|
|
return false;
|
|
}
|
|
return this.canSleep() || Fox.this.isSleeping();
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return this.canSleep();
|
|
}
|
|
|
|
private boolean canSleep() {
|
|
if (this.countdown > 0) {
|
|
--this.countdown;
|
|
return false;
|
|
}
|
|
return Fox.this.level().isBrightOutside() && this.hasShelter() && !this.alertable() && !Fox.this.isInPowderSnow;
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
this.countdown = Fox.this.random.nextInt(WAIT_TIME_BEFORE_SLEEP);
|
|
Fox.this.clearStates();
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Fox.this.setSitting(false);
|
|
Fox.this.setIsCrouching(false);
|
|
Fox.this.setIsInterested(false);
|
|
Fox.this.setJumping(false);
|
|
Fox.this.setSleeping(true);
|
|
Fox.this.getNavigation().stop();
|
|
Fox.this.getMoveControl().setWantedPosition(Fox.this.getX(), Fox.this.getY(), Fox.this.getZ(), 0.0);
|
|
}
|
|
}
|
|
|
|
private static class FoxFollowParentGoal
|
|
extends FollowParentGoal {
|
|
private final Fox fox;
|
|
|
|
public FoxFollowParentGoal(Fox fox, double speedModifier) {
|
|
super(fox, speedModifier);
|
|
this.fox = fox;
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return !this.fox.isDefending() && super.canUse();
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return !this.fox.isDefending() && super.canContinueToUse();
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
this.fox.clearStates();
|
|
super.start();
|
|
}
|
|
}
|
|
|
|
private class FoxStrollThroughVillageGoal
|
|
extends StrollThroughVillageGoal {
|
|
public FoxStrollThroughVillageGoal(int searchRadius, int interval) {
|
|
super(Fox.this, interval);
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Fox.this.clearStates();
|
|
super.start();
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return super.canUse() && this.canFoxMove();
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return super.canContinueToUse() && this.canFoxMove();
|
|
}
|
|
|
|
private boolean canFoxMove() {
|
|
return !Fox.this.isSleeping() && !Fox.this.isSitting() && !Fox.this.isDefending() && Fox.this.getTarget() == null;
|
|
}
|
|
}
|
|
|
|
public class FoxEatBerriesGoal
|
|
extends MoveToBlockGoal {
|
|
private static final int WAIT_TICKS = 40;
|
|
protected int ticksWaited;
|
|
|
|
public FoxEatBerriesGoal(double speedModifier, int searchRange, int verticalSearchRange) {
|
|
super(Fox.this, speedModifier, searchRange, verticalSearchRange);
|
|
}
|
|
|
|
@Override
|
|
public double acceptedDistance() {
|
|
return 2.0;
|
|
}
|
|
|
|
@Override
|
|
public boolean shouldRecalculatePath() {
|
|
return this.tryTicks % 100 == 0;
|
|
}
|
|
|
|
@Override
|
|
protected boolean isValidTarget(LevelReader level, BlockPos pos) {
|
|
BlockState blockState = level.getBlockState(pos);
|
|
return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (this.isReachedTarget()) {
|
|
if (this.ticksWaited >= 40) {
|
|
this.onReachedTarget();
|
|
} else {
|
|
++this.ticksWaited;
|
|
}
|
|
} else if (!this.isReachedTarget() && Fox.this.random.nextFloat() < 0.05f) {
|
|
Fox.this.playSound(SoundEvents.FOX_SNIFF, 1.0f, 1.0f);
|
|
}
|
|
super.tick();
|
|
}
|
|
|
|
protected void onReachedTarget() {
|
|
if (!FoxEatBerriesGoal.getServerLevel(Fox.this.level()).getGameRules().get(GameRules.MOB_GRIEFING).booleanValue()) {
|
|
return;
|
|
}
|
|
BlockState state = Fox.this.level().getBlockState(this.blockPos);
|
|
if (state.is(Blocks.SWEET_BERRY_BUSH)) {
|
|
this.pickSweetBerries(state);
|
|
} else if (CaveVines.hasGlowBerries(state)) {
|
|
this.pickGlowBerry(state);
|
|
}
|
|
}
|
|
|
|
private void pickGlowBerry(BlockState state) {
|
|
CaveVines.use(Fox.this, state, Fox.this.level(), this.blockPos);
|
|
}
|
|
|
|
private void pickSweetBerries(BlockState state) {
|
|
int age = state.getValue(SweetBerryBushBlock.AGE);
|
|
state.setValue(SweetBerryBushBlock.AGE, 1);
|
|
int count = 1 + Fox.this.level().random.nextInt(2) + (age == 3 ? 1 : 0);
|
|
ItemStack heldItem = Fox.this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
if (heldItem.isEmpty()) {
|
|
Fox.this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.SWEET_BERRIES));
|
|
--count;
|
|
}
|
|
if (count > 0) {
|
|
Block.popResource(Fox.this.level(), this.blockPos, new ItemStack(Items.SWEET_BERRIES, count));
|
|
}
|
|
Fox.this.playSound(SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, 1.0f, 1.0f);
|
|
Fox.this.level().setBlock(this.blockPos, (BlockState)state.setValue(SweetBerryBushBlock.AGE, 1), 2);
|
|
Fox.this.level().gameEvent(GameEvent.BLOCK_CHANGE, this.blockPos, GameEvent.Context.of(Fox.this));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return !Fox.this.isSleeping() && super.canUse();
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
this.ticksWaited = 0;
|
|
Fox.this.setSitting(false);
|
|
super.start();
|
|
}
|
|
}
|
|
|
|
private class FoxSearchForItemsGoal
|
|
extends Goal {
|
|
public FoxSearchForItemsGoal() {
|
|
this.setFlags(EnumSet.of(Goal.Flag.MOVE));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
if (!Fox.this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) {
|
|
return false;
|
|
}
|
|
if (Fox.this.getTarget() != null || Fox.this.getLastHurtByMob() != null) {
|
|
return false;
|
|
}
|
|
if (!Fox.this.canMove()) {
|
|
return false;
|
|
}
|
|
if (Fox.this.getRandom().nextInt(FoxSearchForItemsGoal.reducedTickDelay(10)) != 0) {
|
|
return false;
|
|
}
|
|
List<ItemEntity> items = Fox.this.level().getEntitiesOfClass(ItemEntity.class, Fox.this.getBoundingBox().inflate(8.0, 8.0, 8.0), ALLOWED_ITEMS);
|
|
return !items.isEmpty() && Fox.this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty();
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
List<ItemEntity> items = Fox.this.level().getEntitiesOfClass(ItemEntity.class, Fox.this.getBoundingBox().inflate(8.0, 8.0, 8.0), ALLOWED_ITEMS);
|
|
ItemStack itemStack = Fox.this.getItemBySlot(EquipmentSlot.MAINHAND);
|
|
if (itemStack.isEmpty() && !items.isEmpty()) {
|
|
Fox.this.getNavigation().moveTo(items.get(0), (double)1.2f);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
List<ItemEntity> items = Fox.this.level().getEntitiesOfClass(ItemEntity.class, Fox.this.getBoundingBox().inflate(8.0, 8.0, 8.0), ALLOWED_ITEMS);
|
|
if (!items.isEmpty()) {
|
|
Fox.this.getNavigation().moveTo(items.get(0), (double)1.2f);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class FoxLookAtPlayerGoal
|
|
extends LookAtPlayerGoal {
|
|
public FoxLookAtPlayerGoal(Mob mob, Class<? extends LivingEntity> lookAtType, float lookDistance) {
|
|
super(mob, lookAtType, lookDistance);
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return super.canUse() && !Fox.this.isFaceplanted() && !Fox.this.isInterested();
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return super.canContinueToUse() && !Fox.this.isFaceplanted() && !Fox.this.isInterested();
|
|
}
|
|
}
|
|
|
|
private class PerchAndSearchGoal
|
|
extends FoxBehaviorGoal {
|
|
private double relX;
|
|
private double relZ;
|
|
private int lookTime;
|
|
private int looksRemaining;
|
|
|
|
public PerchAndSearchGoal() {
|
|
this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return Fox.this.getLastHurtByMob() == null && Fox.this.getRandom().nextFloat() < 0.02f && !Fox.this.isSleeping() && Fox.this.getTarget() == null && Fox.this.getNavigation().isDone() && !this.alertable() && !Fox.this.isPouncing() && !Fox.this.isCrouching();
|
|
}
|
|
|
|
@Override
|
|
public boolean canContinueToUse() {
|
|
return this.looksRemaining > 0;
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
this.resetLook();
|
|
this.looksRemaining = 2 + Fox.this.getRandom().nextInt(3);
|
|
Fox.this.setSitting(true);
|
|
Fox.this.getNavigation().stop();
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
Fox.this.setSitting(false);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
--this.lookTime;
|
|
if (this.lookTime <= 0) {
|
|
--this.looksRemaining;
|
|
this.resetLook();
|
|
}
|
|
Fox.this.getLookControl().setLookAt(Fox.this.getX() + this.relX, Fox.this.getEyeY(), Fox.this.getZ() + this.relZ, Fox.this.getMaxHeadYRot(), Fox.this.getMaxHeadXRot());
|
|
}
|
|
|
|
private void resetLook() {
|
|
double rnd = Math.PI * 2 * Fox.this.getRandom().nextDouble();
|
|
this.relX = Math.cos(rnd);
|
|
this.relZ = Math.sin(rnd);
|
|
this.lookTime = this.adjustedTickDelay(80 + Fox.this.getRandom().nextInt(20));
|
|
}
|
|
}
|
|
|
|
private class DefendTrustedTargetGoal
|
|
extends NearestAttackableTargetGoal<LivingEntity> {
|
|
private @Nullable LivingEntity trustedLastHurtBy;
|
|
private @Nullable LivingEntity trustedLastHurt;
|
|
private int timestamp;
|
|
|
|
public DefendTrustedTargetGoal(Class<LivingEntity> targetType, boolean mustSee, @Nullable boolean mustReach, TargetingConditions.Selector subselector) {
|
|
super(Fox.this, targetType, 10, mustSee, mustReach, subselector);
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) {
|
|
return false;
|
|
}
|
|
ServerLevel level = DefendTrustedTargetGoal.getServerLevel(Fox.this.level());
|
|
for (EntityReference<LivingEntity> trustedReference : Fox.this.getTrustedEntities().toList()) {
|
|
LivingEntity trustedEntity = trustedReference.getEntity(level, LivingEntity.class);
|
|
if (trustedEntity == null) continue;
|
|
this.trustedLastHurt = trustedEntity;
|
|
this.trustedLastHurtBy = trustedEntity.getLastHurtByMob();
|
|
int timestamp = trustedEntity.getLastHurtByMobTimestamp();
|
|
return timestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.targetConditions);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
this.setTarget(this.trustedLastHurtBy);
|
|
this.target = this.trustedLastHurtBy;
|
|
if (this.trustedLastHurt != null) {
|
|
this.timestamp = this.trustedLastHurt.getLastHurtByMobTimestamp();
|
|
}
|
|
Fox.this.playSound(SoundEvents.FOX_AGGRO, 1.0f, 1.0f);
|
|
Fox.this.setDefending(true);
|
|
Fox.this.wakeUp();
|
|
super.start();
|
|
}
|
|
}
|
|
|
|
public static class FoxGroupData
|
|
extends AgeableMob.AgeableMobGroupData {
|
|
public final Variant variant;
|
|
|
|
public FoxGroupData(Variant variant) {
|
|
super(false);
|
|
this.variant = variant;
|
|
}
|
|
}
|
|
|
|
private abstract class FoxBehaviorGoal
|
|
extends Goal {
|
|
private final TargetingConditions alertableTargeting;
|
|
|
|
private FoxBehaviorGoal() {
|
|
this.alertableTargeting = TargetingConditions.forCombat().range(12.0).ignoreLineOfSight().selector(new FoxAlertableEntitiesSelector());
|
|
}
|
|
|
|
protected boolean hasShelter() {
|
|
BlockPos foxPos = BlockPos.containing(Fox.this.getX(), Fox.this.getBoundingBox().maxY, Fox.this.getZ());
|
|
return !Fox.this.level().canSeeSky(foxPos) && Fox.this.getWalkTargetValue(foxPos) >= 0.0f;
|
|
}
|
|
|
|
protected boolean alertable() {
|
|
return !FoxBehaviorGoal.getServerLevel(Fox.this.level()).getNearbyEntities(LivingEntity.class, this.alertableTargeting, Fox.this, Fox.this.getBoundingBox().inflate(12.0, 6.0, 12.0)).isEmpty();
|
|
}
|
|
}
|
|
|
|
public class FoxAlertableEntitiesSelector
|
|
implements TargetingConditions.Selector {
|
|
@Override
|
|
public boolean test(LivingEntity target, ServerLevel level) {
|
|
Player player;
|
|
if (target instanceof Fox) {
|
|
return false;
|
|
}
|
|
if (target instanceof Chicken || target instanceof Rabbit || target instanceof Monster) {
|
|
return true;
|
|
}
|
|
if (target instanceof TamableAnimal) {
|
|
return !((TamableAnimal)target).isTame();
|
|
}
|
|
if (target instanceof Player && ((player = (Player)target).isSpectator() || player.isCreative())) {
|
|
return false;
|
|
}
|
|
if (Fox.this.trusts(target)) {
|
|
return false;
|
|
}
|
|
return !target.isSleeping() && !target.isDiscrete();
|
|
}
|
|
}
|
|
}
|
|
|