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

526 lines
16 KiB
Java

package net.minecraft.world.entity.animal.horse;
import com.mojang.serialization.Codec;
import io.netty.buffer.ByteBuf;
import java.util.function.IntFunction;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponentGetter;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
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.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.tags.ItemTags;
import net.minecraft.util.ByIdMap;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringRepresentable;
import net.minecraft.util.ByIdMap.OutOfBoundsStrategy;
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.EntityAttachment;
import net.minecraft.world.entity.EntityAttachments;
import net.minecraft.world.entity.EntityDimensions;
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.Pose;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
import net.minecraft.world.entity.ai.goal.BreedGoal;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.FollowParentGoal;
import net.minecraft.world.entity.ai.goal.LlamaFollowCaravanGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.PanicGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.RangedAttackGoal;
import net.minecraft.world.entity.ai.goal.RunAroundLikeCrazyGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal;
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.animal.Animal;
import net.minecraft.world.entity.animal.wolf.Wolf;
import net.minecraft.world.entity.monster.RangedAttackMob;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.LlamaSpit;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class Llama extends AbstractChestedHorse implements RangedAttackMob {
private static final int MAX_STRENGTH = 5;
private static final EntityDataAccessor<Integer> DATA_STRENGTH_ID = SynchedEntityData.defineId(Llama.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> DATA_VARIANT_ID = SynchedEntityData.defineId(Llama.class, EntityDataSerializers.INT);
private static final EntityDimensions BABY_DIMENSIONS = EntityType.LLAMA
.getDimensions()
.withAttachments(EntityAttachments.builder().attach(EntityAttachment.PASSENGER, 0.0F, EntityType.LLAMA.getHeight() - 0.8125F, -0.3F))
.scale(0.5F);
boolean didSpit;
@Nullable
private Llama caravanHead;
@Nullable
private Llama caravanTail;
public Llama(EntityType<? extends Llama> entityType, Level level) {
super(entityType, level);
this.getNavigation().setRequiredPathLength(40.0F);
}
public boolean isTraderLlama() {
return false;
}
private void setStrength(int strength) {
this.entityData.set(DATA_STRENGTH_ID, Math.max(1, Math.min(5, strength)));
}
private void setRandomStrength(RandomSource random) {
int i = random.nextFloat() < 0.04F ? 5 : 3;
this.setStrength(1 + random.nextInt(i));
}
public int getStrength() {
return this.entityData.get(DATA_STRENGTH_ID);
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.store("Variant", Llama.Variant.LEGACY_CODEC, this.getVariant());
tag.putInt("Strength", this.getStrength());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
this.setStrength(tag.getIntOr("Strength", 0));
super.readAdditionalSaveData(tag);
this.setVariant((Llama.Variant)tag.read("Variant", Llama.Variant.LEGACY_CODEC).orElse(Llama.Variant.DEFAULT));
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2));
this.goalSelector.addGoal(2, new LlamaFollowCaravanGoal(this, 2.1F));
this.goalSelector.addGoal(3, new RangedAttackGoal(this, 1.25, 40, 20.0F));
this.goalSelector.addGoal(3, new PanicGoal(this, 1.2));
this.goalSelector.addGoal(4, new BreedGoal(this, 1.0));
this.goalSelector.addGoal(5, new TemptGoal(this, 1.25, itemStack -> itemStack.is(ItemTags.LLAMA_TEMPT_ITEMS), false));
this.goalSelector.addGoal(6, new FollowParentGoal(this, 1.0));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.7));
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new Llama.LlamaHurtByTargetGoal(this));
this.targetSelector.addGoal(2, new Llama.LlamaAttackWolfGoal(this));
}
public static Builder createAttributes() {
return createBaseChestedHorseAttributes();
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(DATA_STRENGTH_ID, 0);
builder.define(DATA_VARIANT_ID, 0);
}
public Llama.Variant getVariant() {
return Llama.Variant.byId(this.entityData.get(DATA_VARIANT_ID));
}
private void setVariant(Llama.Variant variant) {
this.entityData.set(DATA_VARIANT_ID, variant.id);
}
@Nullable
@Override
public <T> T get(DataComponentType<? extends T> component) {
return component == DataComponents.LLAMA_VARIANT ? castComponentValue((DataComponentType<T>)component, this.getVariant()) : super.get(component);
}
@Override
protected void applyImplicitComponents(DataComponentGetter componentGetter) {
this.applyImplicitComponentIfPresent(componentGetter, DataComponents.LLAMA_VARIANT);
super.applyImplicitComponents(componentGetter);
}
@Override
protected <T> boolean applyImplicitComponent(DataComponentType<T> component, T value) {
if (component == DataComponents.LLAMA_VARIANT) {
this.setVariant(castComponentValue(DataComponents.LLAMA_VARIANT, value));
return true;
} else {
return super.applyImplicitComponent(component, value);
}
}
@Override
public boolean isFood(ItemStack stack) {
return stack.is(ItemTags.LLAMA_FOOD);
}
@Override
protected boolean handleEating(Player player, ItemStack stack) {
int i = 0;
int j = 0;
float f = 0.0F;
boolean bl = false;
if (stack.is(Items.WHEAT)) {
i = 10;
j = 3;
f = 2.0F;
} else if (stack.is(Blocks.HAY_BLOCK.asItem())) {
i = 90;
j = 6;
f = 10.0F;
if (this.isTamed() && this.getAge() == 0 && this.canFallInLove()) {
bl = true;
this.setInLove(player);
}
}
if (this.getHealth() < this.getMaxHealth() && f > 0.0F) {
this.heal(f);
bl = true;
}
if (this.isBaby() && i > 0) {
this.level().addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), 0.0, 0.0, 0.0);
if (!this.level().isClientSide) {
this.ageUp(i);
bl = true;
}
}
if (j > 0 && (bl || !this.isTamed()) && this.getTemper() < this.getMaxTemper() && !this.level().isClientSide) {
this.modifyTemper(j);
bl = true;
}
if (bl && !this.isSilent()) {
SoundEvent soundEvent = this.getEatingSound();
if (soundEvent != null) {
this.level()
.playSound(
null,
this.getX(),
this.getY(),
this.getZ(),
this.getEatingSound(),
this.getSoundSource(),
1.0F,
1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F
);
}
}
return bl;
}
@Override
public boolean isImmobile() {
return this.isDeadOrDying() || this.isEating();
}
@Nullable
@Override
public SpawnGroupData finalizeSpawn(
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
) {
RandomSource randomSource = level.getRandom();
this.setRandomStrength(randomSource);
Llama.Variant variant;
if (spawnGroupData instanceof Llama.LlamaGroupData) {
variant = ((Llama.LlamaGroupData)spawnGroupData).variant;
} else {
variant = Util.getRandom(Llama.Variant.values(), randomSource);
spawnGroupData = new Llama.LlamaGroupData(variant);
}
this.setVariant(variant);
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
@Override
protected boolean canPerformRearing() {
return false;
}
@Override
protected SoundEvent getAngrySound() {
return SoundEvents.LLAMA_ANGRY;
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.LLAMA_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.LLAMA_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.LLAMA_DEATH;
}
@Nullable
@Override
protected SoundEvent getEatingSound() {
return SoundEvents.LLAMA_EAT;
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
this.playSound(SoundEvents.LLAMA_STEP, 0.15F, 1.0F);
}
@Override
protected void playChestEquipsSound() {
this.playSound(SoundEvents.LLAMA_CHEST, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
}
@Override
public int getInventoryColumns() {
return this.hasChest() ? this.getStrength() : 0;
}
@Override
public boolean canUseSlot(EquipmentSlot slot) {
return true;
}
@Override
public int getMaxTemper() {
return 30;
}
@Override
public boolean canMate(Animal otherAnimal) {
return otherAnimal != this && otherAnimal instanceof Llama && this.canParent() && ((Llama)otherAnimal).canParent();
}
@Nullable
public Llama getBreedOffspring(ServerLevel serverLevel, AgeableMob ageableMob) {
Llama llama = this.makeNewLlama();
if (llama != null) {
this.setOffspringAttributes(ageableMob, llama);
Llama llama2 = (Llama)ageableMob;
int i = this.random.nextInt(Math.max(this.getStrength(), llama2.getStrength())) + 1;
if (this.random.nextFloat() < 0.03F) {
i++;
}
llama.setStrength(i);
llama.setVariant(this.random.nextBoolean() ? this.getVariant() : llama2.getVariant());
}
return llama;
}
@Nullable
protected Llama makeNewLlama() {
return EntityType.LLAMA.create(this.level(), EntitySpawnReason.BREEDING);
}
private void spit(LivingEntity target) {
LlamaSpit llamaSpit = new LlamaSpit(this.level(), this);
double d = target.getX() - this.getX();
double e = target.getY(0.3333333333333333) - llamaSpit.getY();
double f = target.getZ() - this.getZ();
double g = Math.sqrt(d * d + f * f) * 0.2F;
if (this.level() instanceof ServerLevel serverLevel) {
Projectile.spawnProjectileUsingShoot(llamaSpit, serverLevel, ItemStack.EMPTY, d, e + g, f, 1.5F, 10.0F);
}
if (!this.isSilent()) {
this.level()
.playSound(
null,
this.getX(),
this.getY(),
this.getZ(),
SoundEvents.LLAMA_SPIT,
this.getSoundSource(),
1.0F,
1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F
);
}
this.didSpit = true;
}
void setDidSpit(boolean didSpit) {
this.didSpit = didSpit;
}
@Override
public boolean causeFallDamage(double fallDistance, float damageMultiplier, DamageSource damageSource) {
int i = this.calculateFallDamage(fallDistance, damageMultiplier);
if (i <= 0) {
return false;
} else {
if (fallDistance >= 6.0) {
this.hurt(damageSource, i);
this.propagateFallToPassengers(fallDistance, damageMultiplier, damageSource);
}
this.playBlockFallSound();
return true;
}
}
public void leaveCaravan() {
if (this.caravanHead != null) {
this.caravanHead.caravanTail = null;
}
this.caravanHead = null;
}
public void joinCaravan(Llama caravanHead) {
this.caravanHead = caravanHead;
this.caravanHead.caravanTail = this;
}
public boolean hasCaravanTail() {
return this.caravanTail != null;
}
public boolean inCaravan() {
return this.caravanHead != null;
}
@Nullable
public Llama getCaravanHead() {
return this.caravanHead;
}
@Override
protected double followLeashSpeed() {
return 2.0;
}
@Override
protected void followMommy(ServerLevel level) {
if (!this.inCaravan() && this.isBaby()) {
super.followMommy(level);
}
}
@Override
public boolean canEatGrass() {
return false;
}
@Override
public void performRangedAttack(LivingEntity target, float velocity) {
this.spit(target);
}
@Override
public Vec3 getLeashOffset() {
return new Vec3(0.0, 0.75 * this.getEyeHeight(), this.getBbWidth() * 0.5);
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose);
}
@Override
protected Vec3 getPassengerAttachmentPoint(Entity entity, EntityDimensions dimensions, float partialTick) {
return getDefaultPassengerAttachmentPoint(this, entity, dimensions.attachments());
}
static class LlamaAttackWolfGoal extends NearestAttackableTargetGoal<Wolf> {
public LlamaAttackWolfGoal(Llama llama) {
super(llama, Wolf.class, 16, false, true, (livingEntity, serverLevel) -> !((Wolf)livingEntity).isTame());
}
@Override
protected double getFollowDistance() {
return super.getFollowDistance() * 0.25;
}
}
static class LlamaGroupData extends AgeableMob.AgeableMobGroupData {
public final Llama.Variant variant;
LlamaGroupData(Llama.Variant variant) {
super(true);
this.variant = variant;
}
}
static class LlamaHurtByTargetGoal extends HurtByTargetGoal {
public LlamaHurtByTargetGoal(Llama llama) {
super(llama);
}
@Override
public boolean canContinueToUse() {
if (this.mob instanceof Llama llama && llama.didSpit) {
llama.setDidSpit(false);
return false;
} else {
return super.canContinueToUse();
}
}
}
public static enum Variant implements StringRepresentable {
CREAMY(0, "creamy"),
WHITE(1, "white"),
BROWN(2, "brown"),
GRAY(3, "gray");
public static final Llama.Variant DEFAULT = CREAMY;
private static final IntFunction<Llama.Variant> BY_ID = ByIdMap.continuous(Llama.Variant::getId, values(), OutOfBoundsStrategy.CLAMP);
public static final Codec<Llama.Variant> CODEC = StringRepresentable.fromEnum(Llama.Variant::values);
@Deprecated
public static final Codec<Llama.Variant> LEGACY_CODEC = Codec.INT.xmap(BY_ID::apply, Llama.Variant::getId);
public static final StreamCodec<ByteBuf, Llama.Variant> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, Llama.Variant::getId);
final int id;
private final String name;
private Variant(final int id, final String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public static Llama.Variant byId(int id) {
return (Llama.Variant)BY_ID.apply(id);
}
@Override
public String getSerializedName() {
return this.name;
}
}
}