718 lines
26 KiB
Java
718 lines
26 KiB
Java
package net.minecraft.world.entity.animal.wolf;
|
|
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.Registry;
|
|
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.core.registries.Registries;
|
|
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.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.tags.DamageTypeTags;
|
|
import net.minecraft.tags.ItemTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.TimeUtil;
|
|
import net.minecraft.util.valueproviders.UniformInt;
|
|
import net.minecraft.world.DifficultyInstance;
|
|
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.Crackiness;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.NeutralMob;
|
|
import net.minecraft.world.entity.SpawnGroupData;
|
|
import net.minecraft.world.entity.TamableAnimal;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
|
|
import net.minecraft.world.entity.ai.goal.AvoidEntityGoal;
|
|
import net.minecraft.world.entity.ai.goal.BegGoal;
|
|
import net.minecraft.world.entity.ai.goal.BreedGoal;
|
|
import net.minecraft.world.entity.ai.goal.FloatGoal;
|
|
import net.minecraft.world.entity.ai.goal.FollowOwnerGoal;
|
|
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.RandomLookAroundGoal;
|
|
import net.minecraft.world.entity.ai.goal.SitWhenOrderedToGoal;
|
|
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.NonTameRandomTargetGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.OwnerHurtByTargetGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.OwnerHurtTargetGoal;
|
|
import net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal;
|
|
import net.minecraft.world.entity.ai.targeting.TargetingConditions.Selector;
|
|
import net.minecraft.world.entity.animal.Animal;
|
|
import net.minecraft.world.entity.animal.Turtle;
|
|
import net.minecraft.world.entity.animal.horse.AbstractHorse;
|
|
import net.minecraft.world.entity.animal.horse.Llama;
|
|
import net.minecraft.world.entity.decoration.ArmorStand;
|
|
import net.minecraft.world.entity.monster.AbstractSkeleton;
|
|
import net.minecraft.world.entity.monster.Creeper;
|
|
import net.minecraft.world.entity.monster.Ghast;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.entity.variant.SpawnContext;
|
|
import net.minecraft.world.entity.variant.VariantUtils;
|
|
import net.minecraft.world.food.FoodProperties;
|
|
import net.minecraft.world.item.DyeColor;
|
|
import net.minecraft.world.item.DyeItem;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.Items;
|
|
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
|
|
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.LevelAccessor;
|
|
import net.minecraft.world.level.ServerLevelAccessor;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.pathfinder.PathType;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
public class Wolf extends TamableAnimal implements NeutralMob {
|
|
private static final EntityDataAccessor<Boolean> DATA_INTERESTED_ID = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.BOOLEAN);
|
|
private static final EntityDataAccessor<Integer> DATA_COLLAR_COLOR = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Integer> DATA_REMAINING_ANGER_TIME = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.INT);
|
|
private static final EntityDataAccessor<Holder<WolfVariant>> DATA_VARIANT_ID = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.WOLF_VARIANT);
|
|
private static final EntityDataAccessor<Holder<WolfSoundVariant>> DATA_SOUND_VARIANT_ID = SynchedEntityData.defineId(
|
|
Wolf.class, EntityDataSerializers.WOLF_SOUND_VARIANT
|
|
);
|
|
public static final Selector PREY_SELECTOR = (livingEntity, serverLevel) -> {
|
|
EntityType<?> entityType = livingEntity.getType();
|
|
return entityType == EntityType.SHEEP || entityType == EntityType.RABBIT || entityType == EntityType.FOX;
|
|
};
|
|
private static final float START_HEALTH = 8.0F;
|
|
private static final float TAME_HEALTH = 40.0F;
|
|
private static final float ARMOR_REPAIR_UNIT = 0.125F;
|
|
public static final float DEFAULT_TAIL_ANGLE = (float) (Math.PI / 5);
|
|
private static final DyeColor DEFAULT_COLLAR_COLOR = DyeColor.RED;
|
|
private float interestedAngle;
|
|
private float interestedAngleO;
|
|
private boolean isWet;
|
|
private boolean isShaking;
|
|
private float shakeAnim;
|
|
private float shakeAnimO;
|
|
private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39);
|
|
@Nullable
|
|
private UUID persistentAngerTarget;
|
|
|
|
public Wolf(EntityType<? extends Wolf> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.setTame(false, false);
|
|
this.setPathfindingMalus(PathType.POWDER_SNOW, -1.0F);
|
|
this.setPathfindingMalus(PathType.DANGER_POWDER_SNOW, -1.0F);
|
|
}
|
|
|
|
@Override
|
|
protected void registerGoals() {
|
|
this.goalSelector.addGoal(1, new FloatGoal(this));
|
|
this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5, DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES));
|
|
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
|
|
this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal(this, Llama.class, 24.0F, 1.5, 1.5));
|
|
this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F));
|
|
this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, true));
|
|
this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0, 10.0F, 2.0F));
|
|
this.goalSelector.addGoal(7, new BreedGoal(this, 1.0));
|
|
this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 1.0));
|
|
this.goalSelector.addGoal(9, new BegGoal(this, 8.0F));
|
|
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Player.class, 8.0F));
|
|
this.goalSelector.addGoal(10, new RandomLookAroundGoal(this));
|
|
this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this));
|
|
this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this));
|
|
this.targetSelector.addGoal(3, new HurtByTargetGoal(this).setAlertOthers());
|
|
this.targetSelector.addGoal(4, new NearestAttackableTargetGoal(this, Player.class, 10, true, false, this::isAngryAt));
|
|
this.targetSelector.addGoal(5, new NonTameRandomTargetGoal(this, Animal.class, false, PREY_SELECTOR));
|
|
this.targetSelector.addGoal(6, new NonTameRandomTargetGoal(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
|
|
this.targetSelector.addGoal(7, new NearestAttackableTargetGoal(this, AbstractSkeleton.class, false));
|
|
this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true));
|
|
}
|
|
|
|
public ResourceLocation getTexture() {
|
|
WolfVariant wolfVariant = this.getVariant().value();
|
|
if (this.isTame()) {
|
|
return wolfVariant.assetInfo().tame().texturePath();
|
|
} else {
|
|
return this.isAngry() ? wolfVariant.assetInfo().angry().texturePath() : wolfVariant.assetInfo().wild().texturePath();
|
|
}
|
|
}
|
|
|
|
private Holder<WolfVariant> getVariant() {
|
|
return this.entityData.get(DATA_VARIANT_ID);
|
|
}
|
|
|
|
private void setVariant(Holder<WolfVariant> variant) {
|
|
this.entityData.set(DATA_VARIANT_ID, variant);
|
|
}
|
|
|
|
private Holder<WolfSoundVariant> getSoundVariant() {
|
|
return this.entityData.get(DATA_SOUND_VARIANT_ID);
|
|
}
|
|
|
|
private void setSoundVariant(Holder<WolfSoundVariant> soundVariant) {
|
|
this.entityData.set(DATA_SOUND_VARIANT_ID, soundVariant);
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public <T> T get(DataComponentType<? extends T> component) {
|
|
if (component == DataComponents.WOLF_VARIANT) {
|
|
return castComponentValue((DataComponentType<T>)component, this.getVariant());
|
|
} else if (component == DataComponents.WOLF_SOUND_VARIANT) {
|
|
return castComponentValue((DataComponentType<T>)component, this.getSoundVariant());
|
|
} else {
|
|
return component == DataComponents.WOLF_COLLAR ? castComponentValue((DataComponentType<T>)component, this.getCollarColor()) : super.get(component);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void applyImplicitComponents(DataComponentGetter componentGetter) {
|
|
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.WOLF_VARIANT);
|
|
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.WOLF_SOUND_VARIANT);
|
|
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.WOLF_COLLAR);
|
|
super.applyImplicitComponents(componentGetter);
|
|
}
|
|
|
|
@Override
|
|
protected <T> boolean applyImplicitComponent(DataComponentType<T> component, T value) {
|
|
if (component == DataComponents.WOLF_VARIANT) {
|
|
this.setVariant(castComponentValue(DataComponents.WOLF_VARIANT, value));
|
|
return true;
|
|
} else if (component == DataComponents.WOLF_SOUND_VARIANT) {
|
|
this.setSoundVariant(castComponentValue(DataComponents.WOLF_SOUND_VARIANT, value));
|
|
return true;
|
|
} else if (component == DataComponents.WOLF_COLLAR) {
|
|
this.setCollarColor(castComponentValue(DataComponents.WOLF_COLLAR, value));
|
|
return true;
|
|
} else {
|
|
return super.applyImplicitComponent(component, value);
|
|
}
|
|
}
|
|
|
|
public static Builder createAttributes() {
|
|
return Animal.createAnimalAttributes().add(Attributes.MOVEMENT_SPEED, 0.3F).add(Attributes.MAX_HEALTH, 8.0).add(Attributes.ATTACK_DAMAGE, 4.0);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
Registry<WolfSoundVariant> registry = this.registryAccess().lookupOrThrow(Registries.WOLF_SOUND_VARIANT);
|
|
builder.define(DATA_VARIANT_ID, VariantUtils.getDefaultOrAny(this.registryAccess(), WolfVariants.DEFAULT));
|
|
builder.define(DATA_SOUND_VARIANT_ID, (Holder<WolfSoundVariant>)registry.get(WolfSoundVariants.CLASSIC).or(registry::getAny).orElseThrow());
|
|
builder.define(DATA_INTERESTED_ID, false);
|
|
builder.define(DATA_COLLAR_COLOR, DEFAULT_COLLAR_COLOR.getId());
|
|
builder.define(DATA_REMAINING_ANGER_TIME, 0);
|
|
}
|
|
|
|
@Override
|
|
protected void playStepSound(BlockPos pos, BlockState state) {
|
|
this.playSound(SoundEvents.WOLF_STEP, 0.15F, 1.0F);
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag tag) {
|
|
super.addAdditionalSaveData(tag);
|
|
tag.store("CollarColor", DyeColor.LEGACY_ID_CODEC, this.getCollarColor());
|
|
VariantUtils.writeVariant(tag, this.getVariant());
|
|
this.addPersistentAngerSaveData(tag);
|
|
this.getSoundVariant().unwrapKey().ifPresent(resourceKey -> tag.store("sound_variant", ResourceKey.codec(Registries.WOLF_SOUND_VARIANT), resourceKey));
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag tag) {
|
|
super.readAdditionalSaveData(tag);
|
|
VariantUtils.readVariant(tag, this.registryAccess(), Registries.WOLF_VARIANT).ifPresent(this::setVariant);
|
|
this.setCollarColor((DyeColor)tag.read("CollarColor", DyeColor.LEGACY_ID_CODEC).orElse(DEFAULT_COLLAR_COLOR));
|
|
this.readPersistentAngerSaveData(this.level(), tag);
|
|
tag.read("sound_variant", ResourceKey.codec(Registries.WOLF_SOUND_VARIANT))
|
|
.flatMap(resourceKey -> this.registryAccess().lookupOrThrow(Registries.WOLF_SOUND_VARIANT).get(resourceKey))
|
|
.ifPresent(this::setSoundVariant);
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public SpawnGroupData finalizeSpawn(
|
|
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
|
|
) {
|
|
if (spawnGroupData instanceof Wolf.WolfPackData wolfPackData) {
|
|
this.setVariant(wolfPackData.type);
|
|
} else {
|
|
Optional<? extends Holder<WolfVariant>> optional = WolfVariants.selectVariantToSpawn(
|
|
this.random, this.registryAccess(), SpawnContext.create(level, this.blockPosition())
|
|
);
|
|
if (optional.isPresent()) {
|
|
this.setVariant((Holder<WolfVariant>)optional.get());
|
|
spawnGroupData = new Wolf.WolfPackData((Holder<WolfVariant>)optional.get());
|
|
}
|
|
}
|
|
|
|
this.setSoundVariant(WolfSoundVariants.pickRandomSoundVariant(this.registryAccess(), this.random));
|
|
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getAmbientSound() {
|
|
if (this.isAngry()) {
|
|
return this.getSoundVariant().value().growlSound().value();
|
|
} else if (this.random.nextInt(3) == 0) {
|
|
return this.isTame() && this.getHealth() < 20.0F ? this.getSoundVariant().value().whineSound().value() : this.getSoundVariant().value().pantSound().value();
|
|
} else {
|
|
return this.getSoundVariant().value().ambientSound().value();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
|
return this.canArmorAbsorb(damageSource) ? SoundEvents.WOLF_ARMOR_DAMAGE : this.getSoundVariant().value().hurtSound().value();
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getDeathSound() {
|
|
return this.getSoundVariant().value().deathSound().value();
|
|
}
|
|
|
|
@Override
|
|
protected float getSoundVolume() {
|
|
return 0.4F;
|
|
}
|
|
|
|
@Override
|
|
public void aiStep() {
|
|
super.aiStep();
|
|
if (!this.level().isClientSide && this.isWet && !this.isShaking && !this.isPathFinding() && this.onGround()) {
|
|
this.isShaking = true;
|
|
this.shakeAnim = 0.0F;
|
|
this.shakeAnimO = 0.0F;
|
|
this.level().broadcastEntityEvent(this, (byte)8);
|
|
}
|
|
|
|
if (!this.level().isClientSide) {
|
|
this.updatePersistentAnger((ServerLevel)this.level(), true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
if (this.isAlive()) {
|
|
this.interestedAngleO = this.interestedAngle;
|
|
if (this.isInterested()) {
|
|
this.interestedAngle = this.interestedAngle + (1.0F - this.interestedAngle) * 0.4F;
|
|
} else {
|
|
this.interestedAngle = this.interestedAngle + (0.0F - this.interestedAngle) * 0.4F;
|
|
}
|
|
|
|
if (this.isInWaterOrRain()) {
|
|
this.isWet = true;
|
|
if (this.isShaking && !this.level().isClientSide) {
|
|
this.level().broadcastEntityEvent(this, (byte)56);
|
|
this.cancelShake();
|
|
}
|
|
} else if ((this.isWet || this.isShaking) && this.isShaking) {
|
|
if (this.shakeAnim == 0.0F) {
|
|
this.playSound(SoundEvents.WOLF_SHAKE, this.getSoundVolume(), (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
|
|
this.gameEvent(GameEvent.ENTITY_ACTION);
|
|
}
|
|
|
|
this.shakeAnimO = this.shakeAnim;
|
|
this.shakeAnim += 0.05F;
|
|
if (this.shakeAnimO >= 2.0F) {
|
|
this.isWet = false;
|
|
this.isShaking = false;
|
|
this.shakeAnimO = 0.0F;
|
|
this.shakeAnim = 0.0F;
|
|
}
|
|
|
|
if (this.shakeAnim > 0.4F) {
|
|
float f = (float)this.getY();
|
|
int i = (int)(Mth.sin((this.shakeAnim - 0.4F) * (float) Math.PI) * 7.0F);
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
float g = (this.random.nextFloat() * 2.0F - 1.0F) * this.getBbWidth() * 0.5F;
|
|
float h = (this.random.nextFloat() * 2.0F - 1.0F) * this.getBbWidth() * 0.5F;
|
|
this.level().addParticle(ParticleTypes.SPLASH, this.getX() + g, f + 0.8F, this.getZ() + h, vec3.x, vec3.y, vec3.z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void cancelShake() {
|
|
this.isShaking = false;
|
|
this.shakeAnim = 0.0F;
|
|
this.shakeAnimO = 0.0F;
|
|
}
|
|
|
|
@Override
|
|
public void die(DamageSource damageSource) {
|
|
this.isWet = false;
|
|
this.isShaking = false;
|
|
this.shakeAnimO = 0.0F;
|
|
this.shakeAnim = 0.0F;
|
|
super.die(damageSource);
|
|
}
|
|
|
|
public float getWetShade(float partialTick) {
|
|
return !this.isWet ? 1.0F : Math.min(0.75F + Mth.lerp(partialTick, this.shakeAnimO, this.shakeAnim) / 2.0F * 0.25F, 1.0F);
|
|
}
|
|
|
|
public float getShakeAnim(float partialTick) {
|
|
return Mth.lerp(partialTick, this.shakeAnimO, this.shakeAnim);
|
|
}
|
|
|
|
public float getHeadRollAngle(float partialTick) {
|
|
return Mth.lerp(partialTick, this.interestedAngleO, this.interestedAngle) * 0.15F * (float) Math.PI;
|
|
}
|
|
|
|
@Override
|
|
public int getMaxHeadXRot() {
|
|
return this.isInSittingPose() ? 20 : super.getMaxHeadXRot();
|
|
}
|
|
|
|
@Override
|
|
public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
|
|
if (this.isInvulnerableTo(level, damageSource)) {
|
|
return false;
|
|
} else {
|
|
this.setOrderedToSit(false);
|
|
return super.hurtServer(level, damageSource, amount);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void actuallyHurt(ServerLevel level, DamageSource damageSource, float amount) {
|
|
if (!this.canArmorAbsorb(damageSource)) {
|
|
super.actuallyHurt(level, damageSource, amount);
|
|
} else {
|
|
ItemStack itemStack = this.getBodyArmorItem();
|
|
int i = itemStack.getDamageValue();
|
|
int j = itemStack.getMaxDamage();
|
|
itemStack.hurtAndBreak(Mth.ceil(amount), this, EquipmentSlot.BODY);
|
|
if (Crackiness.WOLF_ARMOR.byDamage(i, j) != Crackiness.WOLF_ARMOR.byDamage(this.getBodyArmorItem())) {
|
|
this.playSound(SoundEvents.WOLF_ARMOR_CRACK);
|
|
level.sendParticles(
|
|
new ItemParticleOption(ParticleTypes.ITEM, Items.ARMADILLO_SCUTE.getDefaultInstance()),
|
|
this.getX(),
|
|
this.getY() + 1.0,
|
|
this.getZ(),
|
|
20,
|
|
0.2,
|
|
0.1,
|
|
0.2,
|
|
0.1
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean canArmorAbsorb(DamageSource damageSource) {
|
|
return this.getBodyArmorItem().is(Items.WOLF_ARMOR) && !damageSource.is(DamageTypeTags.BYPASSES_WOLF_ARMOR);
|
|
}
|
|
|
|
@Override
|
|
protected void applyTamingSideEffects() {
|
|
if (this.isTame()) {
|
|
this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(40.0);
|
|
this.setHealth(40.0F);
|
|
} else {
|
|
this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void hurtArmor(DamageSource damageSource, float damageAmount) {
|
|
this.doHurtEquipment(damageSource, damageAmount, new EquipmentSlot[]{EquipmentSlot.BODY});
|
|
}
|
|
|
|
@Override
|
|
public InteractionResult mobInteract(Player player, InteractionHand hand) {
|
|
ItemStack itemStack = player.getItemInHand(hand);
|
|
Item item = itemStack.getItem();
|
|
if (this.isTame()) {
|
|
if (this.isFood(itemStack) && this.getHealth() < this.getMaxHealth()) {
|
|
this.usePlayerItem(player, hand, itemStack);
|
|
FoodProperties foodProperties = itemStack.get(DataComponents.FOOD);
|
|
float f = foodProperties != null ? foodProperties.nutrition() : 1.0F;
|
|
this.heal(2.0F * f);
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
|
|
if (!(item instanceof DyeItem dyeItem && this.isOwnedBy(player))) {
|
|
if (this.isEquippableInSlot(itemStack, EquipmentSlot.BODY) && !this.isWearingBodyArmor() && this.isOwnedBy(player) && !this.isBaby()) {
|
|
this.setBodyArmorItem(itemStack.copyWithCount(1));
|
|
itemStack.consume(1, player);
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
|
|
if (!itemStack.is(Items.SHEARS)
|
|
|| !this.isOwnedBy(player)
|
|
|| !this.isWearingBodyArmor()
|
|
|| EnchantmentHelper.has(this.getBodyArmorItem(), EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) && !player.isCreative()) {
|
|
if (this.isInSittingPose()
|
|
&& this.isWearingBodyArmor()
|
|
&& this.isOwnedBy(player)
|
|
&& this.getBodyArmorItem().isDamaged()
|
|
&& this.getBodyArmorItem().isValidRepairItem(itemStack)) {
|
|
itemStack.shrink(1);
|
|
this.playSound(SoundEvents.WOLF_ARMOR_REPAIR);
|
|
ItemStack itemStack2 = this.getBodyArmorItem();
|
|
int i = (int)(itemStack2.getMaxDamage() * 0.125F);
|
|
itemStack2.setDamageValue(Math.max(0, itemStack2.getDamageValue() - i));
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
|
|
InteractionResult interactionResult = super.mobInteract(player, hand);
|
|
if (!interactionResult.consumesAction() && this.isOwnedBy(player)) {
|
|
this.setOrderedToSit(!this.isOrderedToSit());
|
|
this.jumping = false;
|
|
this.navigation.stop();
|
|
this.setTarget(null);
|
|
return InteractionResult.SUCCESS.withoutItem();
|
|
}
|
|
|
|
return interactionResult;
|
|
}
|
|
|
|
itemStack.hurtAndBreak(1, player, getSlotForHand(hand));
|
|
this.playSound(SoundEvents.ARMOR_UNEQUIP_WOLF);
|
|
ItemStack itemStack2 = this.getBodyArmorItem();
|
|
this.setBodyArmorItem(ItemStack.EMPTY);
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
this.spawnAtLocation(serverLevel, itemStack2);
|
|
}
|
|
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
|
|
DyeColor dyeColor = dyeItem.getDyeColor();
|
|
if (dyeColor != this.getCollarColor()) {
|
|
this.setCollarColor(dyeColor);
|
|
itemStack.consume(1, player);
|
|
return InteractionResult.SUCCESS;
|
|
}
|
|
} else if (!this.level().isClientSide && itemStack.is(Items.BONE) && !this.isAngry()) {
|
|
itemStack.consume(1, player);
|
|
this.tryToTame(player);
|
|
return InteractionResult.SUCCESS_SERVER;
|
|
}
|
|
|
|
return super.mobInteract(player, hand);
|
|
}
|
|
|
|
private void tryToTame(Player player) {
|
|
if (this.random.nextInt(3) == 0) {
|
|
this.tame(player);
|
|
this.navigation.stop();
|
|
this.setTarget(null);
|
|
this.setOrderedToSit(true);
|
|
this.level().broadcastEntityEvent(this, (byte)7);
|
|
} else {
|
|
this.level().broadcastEntityEvent(this, (byte)6);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void handleEntityEvent(byte id) {
|
|
if (id == 8) {
|
|
this.isShaking = true;
|
|
this.shakeAnim = 0.0F;
|
|
this.shakeAnimO = 0.0F;
|
|
} else if (id == 56) {
|
|
this.cancelShake();
|
|
} else {
|
|
super.handleEntityEvent(id);
|
|
}
|
|
}
|
|
|
|
public float getTailAngle() {
|
|
if (this.isAngry()) {
|
|
return 1.5393804F;
|
|
} else if (this.isTame()) {
|
|
float f = this.getMaxHealth();
|
|
float g = (f - this.getHealth()) / f;
|
|
return (0.55F - g * 0.4F) * (float) Math.PI;
|
|
} else {
|
|
return (float) (Math.PI / 5);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isFood(ItemStack stack) {
|
|
return stack.is(ItemTags.WOLF_FOOD);
|
|
}
|
|
|
|
@Override
|
|
public int getMaxSpawnClusterSize() {
|
|
return 8;
|
|
}
|
|
|
|
@Override
|
|
public int getRemainingPersistentAngerTime() {
|
|
return this.entityData.get(DATA_REMAINING_ANGER_TIME);
|
|
}
|
|
|
|
@Override
|
|
public void setRemainingPersistentAngerTime(int remainingPersistentAngerTime) {
|
|
this.entityData.set(DATA_REMAINING_ANGER_TIME, remainingPersistentAngerTime);
|
|
}
|
|
|
|
@Override
|
|
public void startPersistentAngerTimer() {
|
|
this.setRemainingPersistentAngerTime(PERSISTENT_ANGER_TIME.sample(this.random));
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public UUID getPersistentAngerTarget() {
|
|
return this.persistentAngerTarget;
|
|
}
|
|
|
|
@Override
|
|
public void setPersistentAngerTarget(@Nullable UUID persistentAngerTarget) {
|
|
this.persistentAngerTarget = persistentAngerTarget;
|
|
}
|
|
|
|
public DyeColor getCollarColor() {
|
|
return DyeColor.byId(this.entityData.get(DATA_COLLAR_COLOR));
|
|
}
|
|
|
|
private void setCollarColor(DyeColor color) {
|
|
this.entityData.set(DATA_COLLAR_COLOR, color.getId());
|
|
}
|
|
|
|
@Nullable
|
|
public Wolf getBreedOffspring(ServerLevel serverLevel, AgeableMob ageableMob) {
|
|
Wolf wolf = EntityType.WOLF.create(serverLevel, EntitySpawnReason.BREEDING);
|
|
if (wolf != null && ageableMob instanceof Wolf wolf2) {
|
|
if (this.random.nextBoolean()) {
|
|
wolf.setVariant(this.getVariant());
|
|
} else {
|
|
wolf.setVariant(wolf2.getVariant());
|
|
}
|
|
|
|
if (this.isTame()) {
|
|
wolf.setOwnerReference(this.getOwnerReference());
|
|
wolf.setTame(true, true);
|
|
DyeColor dyeColor = this.getCollarColor();
|
|
DyeColor dyeColor2 = wolf2.getCollarColor();
|
|
wolf.setCollarColor(DyeColor.getMixedColor(serverLevel, dyeColor, dyeColor2));
|
|
}
|
|
|
|
wolf.setSoundVariant(WolfSoundVariants.pickRandomSoundVariant(this.registryAccess(), this.random));
|
|
}
|
|
|
|
return wolf;
|
|
}
|
|
|
|
public void setIsInterested(boolean isInterested) {
|
|
this.entityData.set(DATA_INTERESTED_ID, isInterested);
|
|
}
|
|
|
|
@Override
|
|
public boolean canMate(Animal otherAnimal) {
|
|
if (otherAnimal == this) {
|
|
return false;
|
|
} else if (!this.isTame()) {
|
|
return false;
|
|
} else if (!(otherAnimal instanceof Wolf wolf)) {
|
|
return false;
|
|
} else if (!wolf.isTame()) {
|
|
return false;
|
|
} else {
|
|
return wolf.isInSittingPose() ? false : this.isInLove() && wolf.isInLove();
|
|
}
|
|
}
|
|
|
|
public boolean isInterested() {
|
|
return this.entityData.get(DATA_INTERESTED_ID);
|
|
}
|
|
|
|
@Override
|
|
public boolean wantsToAttack(LivingEntity target, LivingEntity owner) {
|
|
if (target instanceof Creeper || target instanceof Ghast || target instanceof ArmorStand) {
|
|
return false;
|
|
} else if (target instanceof Wolf wolf) {
|
|
return !wolf.isTame() || wolf.getOwner() != owner;
|
|
} else if (target instanceof Player player && owner instanceof Player player2 && !player2.canHarmPlayer(player)) {
|
|
return false;
|
|
} else {
|
|
return target instanceof AbstractHorse abstractHorse && abstractHorse.isTamed()
|
|
? false
|
|
: !(target instanceof TamableAnimal tamableAnimal && tamableAnimal.isTame());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canBeLeashed() {
|
|
return !this.isAngry();
|
|
}
|
|
|
|
@Override
|
|
public Vec3 getLeashOffset() {
|
|
return new Vec3(0.0, 0.6F * this.getEyeHeight(), this.getBbWidth() * 0.4F);
|
|
}
|
|
|
|
public static boolean checkWolfSpawnRules(EntityType<Wolf> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
|
|
return level.getBlockState(pos.below()).is(BlockTags.WOLVES_SPAWNABLE_ON) && isBrightEnoughToSpawn(level, pos);
|
|
}
|
|
|
|
class WolfAvoidEntityGoal<T extends LivingEntity> extends AvoidEntityGoal<T> {
|
|
private final Wolf wolf;
|
|
|
|
public WolfAvoidEntityGoal(
|
|
final Wolf wolf, final Class<T> avoidClass, final float maxDistance, final double walkSpeedModifier, final double sprintSpeedModifier
|
|
) {
|
|
super(wolf, avoidClass, maxDistance, walkSpeedModifier, sprintSpeedModifier);
|
|
this.wolf = wolf;
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse() {
|
|
return super.canUse() && this.toAvoid instanceof Llama ? !this.wolf.isTame() && this.avoidLlama((Llama)this.toAvoid) : false;
|
|
}
|
|
|
|
private boolean avoidLlama(Llama llama) {
|
|
return llama.getStrength() >= Wolf.this.random.nextInt(5);
|
|
}
|
|
|
|
@Override
|
|
public void start() {
|
|
Wolf.this.setTarget(null);
|
|
super.start();
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
Wolf.this.setTarget(null);
|
|
super.tick();
|
|
}
|
|
}
|
|
|
|
public static class WolfPackData extends AgeableMob.AgeableMobGroupData {
|
|
public final Holder<WolfVariant> type;
|
|
|
|
public WolfPackData(Holder<WolfVariant> type) {
|
|
super(false);
|
|
this.type = type;
|
|
}
|
|
}
|
|
}
|