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

1377 lines
42 KiB
Java

package net.minecraft.world.entity;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Vec3i;
import net.minecraft.core.component.DataComponents;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.Container;
import net.minecraft.world.Difficulty;
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.ConversionParams.AfterConversion;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
import net.minecraft.world.entity.ai.control.BodyRotationControl;
import net.minecraft.world.entity.ai.control.JumpControl;
import net.minecraft.world.entity.ai.control.LookControl;
import net.minecraft.world.entity.ai.control.MoveControl;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.GoalSelector;
import net.minecraft.world.entity.ai.goal.Goal.Flag;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.ai.sensing.Sensing;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.AbstractBoat;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.SpawnEggItem;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import net.minecraft.world.item.enchantment.providers.VanillaEnchantmentProviders;
import net.minecraft.world.level.GameRules;
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.gameevent.GameEvent;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.pathfinder.PathType;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.ticks.ContainerSingleItem;
import org.jetbrains.annotations.Nullable;
public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting {
private static final EntityDataAccessor<Byte> DATA_MOB_FLAGS_ID = SynchedEntityData.defineId(Mob.class, EntityDataSerializers.BYTE);
private static final int MOB_FLAG_NO_AI = 1;
private static final int MOB_FLAG_LEFTHANDED = 2;
private static final int MOB_FLAG_AGGRESSIVE = 4;
protected static final int PICKUP_REACH = 1;
private static final Vec3i ITEM_PICKUP_REACH = new Vec3i(1, 0, 1);
private static final List<EquipmentSlot> EQUIPMENT_POPULATION_ORDER = List.of(EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET);
public static final float MAX_WEARING_ARMOR_CHANCE = 0.15F;
public static final float MAX_PICKUP_LOOT_CHANCE = 0.55F;
public static final float MAX_ENCHANTED_ARMOR_CHANCE = 0.5F;
public static final float MAX_ENCHANTED_WEAPON_CHANCE = 0.25F;
public static final int UPDATE_GOAL_SELECTOR_EVERY_N_TICKS = 2;
private static final double DEFAULT_ATTACK_REACH = Math.sqrt(2.04F) - 0.6F;
private static final boolean DEFAULT_CAN_PICK_UP_LOOT = false;
private static final boolean DEFAULT_PERSISTENCE_REQUIRED = false;
private static final boolean DEFAULT_LEFT_HANDED = false;
private static final boolean DEFAULT_NO_AI = false;
protected static final ResourceLocation RANDOM_SPAWN_BONUS_ID = ResourceLocation.withDefaultNamespace("random_spawn_bonus");
public int ambientSoundTime;
protected int xpReward;
protected LookControl lookControl;
protected MoveControl moveControl;
protected JumpControl jumpControl;
private final BodyRotationControl bodyRotationControl;
protected PathNavigation navigation;
protected final GoalSelector goalSelector;
protected final GoalSelector targetSelector;
@Nullable
private LivingEntity target;
private final Sensing sensing;
private DropChances dropChances = DropChances.DEFAULT;
private boolean canPickUpLoot = false;
private boolean persistenceRequired = false;
private final Map<PathType, Float> pathfindingMalus = Maps.newEnumMap(PathType.class);
private Optional<ResourceKey<LootTable>> lootTable = Optional.empty();
private long lootTableSeed;
@Nullable
private Leashable.LeashData leashData;
private BlockPos restrictCenter = BlockPos.ZERO;
private float restrictRadius = -1.0F;
protected Mob(EntityType<? extends Mob> entityType, Level level) {
super(entityType, level);
this.goalSelector = new GoalSelector();
this.targetSelector = new GoalSelector();
this.lookControl = new LookControl(this);
this.moveControl = new MoveControl(this);
this.jumpControl = new JumpControl(this);
this.bodyRotationControl = this.createBodyControl();
this.navigation = this.createNavigation(level);
this.sensing = new Sensing(this);
if (level instanceof ServerLevel) {
this.registerGoals();
}
}
protected void registerGoals() {
}
public static Builder createMobAttributes() {
return LivingEntity.createLivingAttributes().add(Attributes.FOLLOW_RANGE, 16.0);
}
protected PathNavigation createNavigation(Level level) {
return new GroundPathNavigation(this, level);
}
protected boolean shouldPassengersInheritMalus() {
return false;
}
public float getPathfindingMalus(PathType pathType) {
Mob mob2;
if (this.getControlledVehicle() instanceof Mob mob && mob.shouldPassengersInheritMalus()) {
mob2 = mob;
} else {
mob2 = this;
}
Float float_ = (Float)mob2.pathfindingMalus.get(pathType);
return float_ == null ? pathType.getMalus() : float_;
}
public void setPathfindingMalus(PathType pathType, float malus) {
this.pathfindingMalus.put(pathType, malus);
}
public void onPathfindingStart() {
}
public void onPathfindingDone() {
}
protected BodyRotationControl createBodyControl() {
return new BodyRotationControl(this);
}
public LookControl getLookControl() {
return this.lookControl;
}
public MoveControl getMoveControl() {
return this.getControlledVehicle() instanceof Mob mob ? mob.getMoveControl() : this.moveControl;
}
public JumpControl getJumpControl() {
return this.jumpControl;
}
public PathNavigation getNavigation() {
return this.getControlledVehicle() instanceof Mob mob ? mob.getNavigation() : this.navigation;
}
@Nullable
@Override
public LivingEntity getControllingPassenger() {
Entity entity = this.getFirstPassenger();
return !this.isNoAi() && entity instanceof Mob mob && entity.canControlVehicle() ? mob : null;
}
public Sensing getSensing() {
return this.sensing;
}
@Nullable
@Override
public LivingEntity getTarget() {
return this.target;
}
@Nullable
protected final LivingEntity getTargetFromBrain() {
return (LivingEntity)this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null);
}
/**
* Sets the active target the Goal system uses for tracking
*/
public void setTarget(@Nullable LivingEntity target) {
this.target = target;
}
@Override
public boolean canAttackType(EntityType<?> entityType) {
return entityType != EntityType.GHAST;
}
public boolean canFireProjectileWeapon(ProjectileWeaponItem projectileWeapon) {
return false;
}
/**
* Applies the benefits of growing back wool and faster growing up to the acting entity. This function is used in the {@code EatBlockGoal}.
*/
public void ate() {
this.gameEvent(GameEvent.EAT);
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(DATA_MOB_FLAGS_ID, (byte)0);
}
/**
* Get number of ticks, at least during which the living entity will be silent.
*/
public int getAmbientSoundInterval() {
return 80;
}
/**
* Plays living's sound at its position
*/
public void playAmbientSound() {
this.makeSound(this.getAmbientSound());
}
@Override
public void baseTick() {
super.baseTick();
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("mobBaseTick");
if (this.isAlive() && this.random.nextInt(1000) < this.ambientSoundTime++) {
this.resetAmbientSoundTime();
this.playAmbientSound();
}
profilerFiller.pop();
}
@Override
protected void playHurtSound(DamageSource source) {
this.resetAmbientSoundTime();
super.playHurtSound(source);
}
private void resetAmbientSoundTime() {
this.ambientSoundTime = -this.getAmbientSoundInterval();
}
@Override
protected int getBaseExperienceReward(ServerLevel level) {
if (this.xpReward > 0) {
int i = this.xpReward;
for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
if (equipmentSlot.canIncreaseExperience()) {
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
if (!itemStack.isEmpty() && this.dropChances.byEquipment(equipmentSlot) <= 1.0F) {
i += 1 + this.random.nextInt(3);
}
}
}
return i;
} else {
return this.xpReward;
}
}
/**
* Spawns an explosion particle around the Entity's location
*/
public void spawnAnim() {
if (this.level().isClientSide) {
this.makePoofParticles();
} else {
this.level().broadcastEntityEvent(this, (byte)20);
}
}
@Override
public void handleEntityEvent(byte id) {
if (id == 20) {
this.spawnAnim();
} else {
super.handleEntityEvent(id);
}
}
@Override
public void tick() {
super.tick();
if (!this.level().isClientSide && this.tickCount % 5 == 0) {
this.updateControlFlags();
}
}
/**
* Sets MOVE, JUMP, LOOK Goal.Flags depending if entity is riding or been controlled
*/
protected void updateControlFlags() {
boolean bl = !(this.getControllingPassenger() instanceof Mob);
boolean bl2 = !(this.getVehicle() instanceof AbstractBoat);
this.goalSelector.setControlFlag(Flag.MOVE, bl);
this.goalSelector.setControlFlag(Flag.JUMP, bl && bl2);
this.goalSelector.setControlFlag(Flag.LOOK, bl);
}
@Override
protected void tickHeadTurn(float yBodyRot) {
this.bodyRotationControl.clientTick();
}
@Nullable
protected SoundEvent getAmbientSound() {
return null;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean("CanPickUpLoot", this.canPickUpLoot());
tag.putBoolean("PersistenceRequired", this.persistenceRequired);
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
if (!this.dropChances.equals(DropChances.DEFAULT)) {
tag.store("drop_chances", DropChances.CODEC, registryOps, this.dropChances);
}
this.writeLeashData(tag, this.leashData);
tag.putBoolean("LeftHanded", this.isLeftHanded());
this.lootTable.ifPresent(resourceKey -> tag.store("DeathLootTable", LootTable.KEY_CODEC, resourceKey));
if (this.lootTableSeed != 0L) {
tag.putLong("DeathLootTableSeed", this.lootTableSeed);
}
if (this.isNoAi()) {
tag.putBoolean("NoAI", this.isNoAi());
}
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setCanPickUpLoot(tag.getBooleanOr("CanPickUpLoot", false));
this.persistenceRequired = tag.getBooleanOr("PersistenceRequired", false);
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
this.dropChances = (DropChances)tag.read("drop_chances", DropChances.CODEC, registryOps).orElse(DropChances.DEFAULT);
this.readLeashData(tag);
this.setLeftHanded(tag.getBooleanOr("LeftHanded", false));
this.lootTable = tag.read("DeathLootTable", LootTable.KEY_CODEC);
this.lootTableSeed = tag.getLongOr("DeathLootTableSeed", 0L);
this.setNoAi(tag.getBooleanOr("NoAI", false));
}
@Override
protected void dropFromLootTable(ServerLevel level, DamageSource damageSource, boolean playerKill) {
super.dropFromLootTable(level, damageSource, playerKill);
this.lootTable = Optional.empty();
}
@Override
public final Optional<ResourceKey<LootTable>> getLootTable() {
return this.lootTable.isPresent() ? this.lootTable : super.getLootTable();
}
@Override
public long getLootTableSeed() {
return this.lootTableSeed;
}
public void setZza(float amount) {
this.zza = amount;
}
public void setYya(float amount) {
this.yya = amount;
}
public void setXxa(float amount) {
this.xxa = amount;
}
@Override
public void setSpeed(float speed) {
super.setSpeed(speed);
this.setZza(speed);
}
public void stopInPlace() {
this.getNavigation().stop();
this.setXxa(0.0F);
this.setYya(0.0F);
this.setSpeed(0.0F);
}
@Override
public void aiStep() {
super.aiStep();
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("looting");
if (this.level() instanceof ServerLevel serverLevel
&& this.canPickUpLoot()
&& this.isAlive()
&& !this.dead
&& serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
Vec3i vec3i = this.getPickupReach();
for (ItemEntity itemEntity : this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(vec3i.getX(), vec3i.getY(), vec3i.getZ()))) {
if (!itemEntity.isRemoved() && !itemEntity.getItem().isEmpty() && !itemEntity.hasPickUpDelay() && this.wantsToPickUp(serverLevel, itemEntity.getItem())) {
this.pickUpItem(serverLevel, itemEntity);
}
}
}
profilerFiller.pop();
}
protected Vec3i getPickupReach() {
return ITEM_PICKUP_REACH;
}
protected void pickUpItem(ServerLevel level, ItemEntity entity) {
ItemStack itemStack = entity.getItem();
ItemStack itemStack2 = this.equipItemIfPossible(level, itemStack.copy());
if (!itemStack2.isEmpty()) {
this.onItemPickup(entity);
this.take(entity, itemStack2.getCount());
itemStack.shrink(itemStack2.getCount());
if (itemStack.isEmpty()) {
entity.discard();
}
}
}
public ItemStack equipItemIfPossible(ServerLevel level, ItemStack stack) {
EquipmentSlot equipmentSlot = this.getEquipmentSlotForItem(stack);
if (!this.isEquippableInSlot(stack, equipmentSlot)) {
return ItemStack.EMPTY;
} else {
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
boolean bl = this.canReplaceCurrentItem(stack, itemStack, equipmentSlot);
if (equipmentSlot.isArmor() && !bl) {
equipmentSlot = EquipmentSlot.MAINHAND;
itemStack = this.getItemBySlot(equipmentSlot);
bl = itemStack.isEmpty();
}
if (bl && this.canHoldItem(stack)) {
double d = this.dropChances.byEquipment(equipmentSlot);
if (!itemStack.isEmpty() && Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d) {
this.spawnAtLocation(level, itemStack);
}
ItemStack itemStack2 = equipmentSlot.limit(stack);
this.setItemSlotAndDropWhenKilled(equipmentSlot, itemStack2);
return itemStack2;
} else {
return ItemStack.EMPTY;
}
}
}
protected void setItemSlotAndDropWhenKilled(EquipmentSlot slot, ItemStack stack) {
this.setItemSlot(slot, stack);
this.setGuaranteedDrop(slot);
this.persistenceRequired = true;
}
public void setGuaranteedDrop(EquipmentSlot slot) {
this.dropChances = this.dropChances.withGuaranteedDrop(slot);
}
protected boolean canReplaceCurrentItem(ItemStack newItem, ItemStack currentItem, EquipmentSlot slot) {
if (currentItem.isEmpty()) {
return true;
} else if (slot.isArmor()) {
return this.compareArmor(newItem, currentItem, slot);
} else {
return slot == EquipmentSlot.MAINHAND ? this.compareWeapons(newItem, currentItem, slot) : false;
}
}
private boolean compareArmor(ItemStack newItem, ItemStack currentItem, EquipmentSlot slot) {
if (EnchantmentHelper.has(currentItem, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) {
return false;
} else {
double d = this.getApproximateAttributeWith(newItem, Attributes.ARMOR, slot);
double e = this.getApproximateAttributeWith(currentItem, Attributes.ARMOR, slot);
double f = this.getApproximateAttributeWith(newItem, Attributes.ARMOR_TOUGHNESS, slot);
double g = this.getApproximateAttributeWith(currentItem, Attributes.ARMOR_TOUGHNESS, slot);
if (d != e) {
return d > e;
} else {
return f != g ? f > g : this.canReplaceEqualItem(newItem, currentItem);
}
}
}
private boolean compareWeapons(ItemStack newItem, ItemStack currentItem, EquipmentSlot slot) {
TagKey<Item> tagKey = this.getPreferredWeaponType();
if (tagKey != null) {
if (currentItem.is(tagKey) && !newItem.is(tagKey)) {
return false;
}
if (!currentItem.is(tagKey) && newItem.is(tagKey)) {
return true;
}
}
double d = this.getApproximateAttributeWith(newItem, Attributes.ATTACK_DAMAGE, slot);
double e = this.getApproximateAttributeWith(currentItem, Attributes.ATTACK_DAMAGE, slot);
return d != e ? d > e : this.canReplaceEqualItem(newItem, currentItem);
}
private double getApproximateAttributeWith(ItemStack item, Holder<Attribute> attribute, EquipmentSlot slot) {
double d = this.getAttributes().hasAttribute(attribute) ? this.getAttributeBaseValue(attribute) : 0.0;
ItemAttributeModifiers itemAttributeModifiers = item.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY);
return itemAttributeModifiers.compute(d, slot);
}
public boolean canReplaceEqualItem(ItemStack candidate, ItemStack existing) {
Set<Entry<Holder<Enchantment>>> set = existing.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).entrySet();
Set<Entry<Holder<Enchantment>>> set2 = candidate.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).entrySet();
if (set2.size() != set.size()) {
return set2.size() > set.size();
} else {
int i = candidate.getDamageValue();
int j = existing.getDamageValue();
return i != j ? i < j : candidate.has(DataComponents.CUSTOM_NAME) && !existing.has(DataComponents.CUSTOM_NAME);
}
}
public boolean canHoldItem(ItemStack stack) {
return true;
}
public boolean wantsToPickUp(ServerLevel level, ItemStack stack) {
return this.canHoldItem(stack);
}
@Nullable
public TagKey<Item> getPreferredWeaponType() {
return null;
}
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return true;
}
public boolean requiresCustomPersistence() {
return this.isPassenger();
}
protected boolean shouldDespawnInPeaceful() {
return false;
}
@Override
public void checkDespawn() {
if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
this.discard();
} else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
Entity entity = this.level().getNearestPlayer(this, -1.0);
if (entity != null) {
double d = entity.distanceToSqr(this);
int i = this.getType().getCategory().getDespawnDistance();
int j = i * i;
if (d > j && this.removeWhenFarAway(d)) {
this.discard();
}
int k = this.getType().getCategory().getNoDespawnDistance();
int l = k * k;
if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && d > l && this.removeWhenFarAway(d)) {
this.discard();
} else if (d < l) {
this.noActionTime = 0;
}
}
} else {
this.noActionTime = 0;
}
}
@Override
protected final void serverAiStep() {
this.noActionTime++;
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("sensing");
this.sensing.tick();
profilerFiller.pop();
int i = this.tickCount + this.getId();
if (i % 2 != 0 && this.tickCount > 1) {
profilerFiller.push("targetSelector");
this.targetSelector.tickRunningGoals(false);
profilerFiller.pop();
profilerFiller.push("goalSelector");
this.goalSelector.tickRunningGoals(false);
profilerFiller.pop();
} else {
profilerFiller.push("targetSelector");
this.targetSelector.tick();
profilerFiller.pop();
profilerFiller.push("goalSelector");
this.goalSelector.tick();
profilerFiller.pop();
}
profilerFiller.push("navigation");
this.navigation.tick();
profilerFiller.pop();
profilerFiller.push("mob tick");
this.customServerAiStep((ServerLevel)this.level());
profilerFiller.pop();
profilerFiller.push("controls");
profilerFiller.push("move");
this.moveControl.tick();
profilerFiller.popPush("look");
this.lookControl.tick();
profilerFiller.popPush("jump");
this.jumpControl.tick();
profilerFiller.pop();
profilerFiller.pop();
this.sendDebugPackets();
}
protected void sendDebugPackets() {
DebugPackets.sendGoalSelector(this.level(), this, this.goalSelector);
}
protected void customServerAiStep(ServerLevel level) {
}
/**
* The speed it takes to move the entity's head rotation through the faceEntity method.
*/
public int getMaxHeadXRot() {
return 40;
}
public int getMaxHeadYRot() {
return 75;
}
protected void clampHeadRotationToBody() {
float f = this.getMaxHeadYRot();
float g = this.getYHeadRot();
float h = Mth.wrapDegrees(this.yBodyRot - g);
float i = Mth.clamp(Mth.wrapDegrees(this.yBodyRot - g), -f, f);
float j = g + h - i;
this.setYHeadRot(j);
}
public int getHeadRotSpeed() {
return 10;
}
/**
* Changes the X and Y rotation so that this entity is facing the given entity.
*/
public void lookAt(Entity entity, float maxYRotIncrease, float maxXRotIncrease) {
double d = entity.getX() - this.getX();
double e = entity.getZ() - this.getZ();
double f;
if (entity instanceof LivingEntity livingEntity) {
f = livingEntity.getEyeY() - this.getEyeY();
} else {
f = (entity.getBoundingBox().minY + entity.getBoundingBox().maxY) / 2.0 - this.getEyeY();
}
double g = Math.sqrt(d * d + e * e);
float h = (float)(Mth.atan2(e, d) * 180.0F / (float)Math.PI) - 90.0F;
float i = (float)(-(Mth.atan2(f, g) * 180.0F / (float)Math.PI));
this.setXRot(this.rotlerp(this.getXRot(), i, maxXRotIncrease));
this.setYRot(this.rotlerp(this.getYRot(), h, maxYRotIncrease));
}
/**
* Arguments: current rotation, intended rotation, max increment.
*/
private float rotlerp(float angle, float targetAngle, float maxIncrease) {
float f = Mth.wrapDegrees(targetAngle - angle);
if (f > maxIncrease) {
f = maxIncrease;
}
if (f < -maxIncrease) {
f = -maxIncrease;
}
return angle + f;
}
public static boolean checkMobSpawnRules(
EntityType<? extends Mob> entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random
) {
BlockPos blockPos = pos.below();
return EntitySpawnReason.isSpawner(spawnReason) || level.getBlockState(blockPos).isValidSpawn(level, blockPos, entityType);
}
public boolean checkSpawnRules(LevelAccessor level, EntitySpawnReason spawnReason) {
return true;
}
public boolean checkSpawnObstruction(LevelReader level) {
return !level.containsAnyLiquid(this.getBoundingBox()) && level.isUnobstructed(this);
}
/**
* Will return how many at most can spawn in a chunk at once.
*/
public int getMaxSpawnClusterSize() {
return 4;
}
public boolean isMaxGroupSizeReached(int size) {
return false;
}
@Override
public int getMaxFallDistance() {
if (this.getTarget() == null) {
return this.getComfortableFallDistance(0.0F);
} else {
int i = (int)(this.getHealth() - this.getMaxHealth() * 0.33F);
i -= (3 - this.level().getDifficulty().getId()) * 4;
if (i < 0) {
i = 0;
}
return this.getComfortableFallDistance(i);
}
}
public ItemStack getBodyArmorItem() {
return this.getItemBySlot(EquipmentSlot.BODY);
}
public boolean isSaddled() {
return this.hasItemInSlot(EquipmentSlot.SADDLE);
}
public boolean isWearingBodyArmor() {
return this.hasItemInSlot(EquipmentSlot.BODY);
}
public void setBodyArmorItem(ItemStack stack) {
this.setItemSlotAndDropWhenKilled(EquipmentSlot.BODY, stack);
}
public Container createEquipmentSlotContainer(EquipmentSlot slot) {
return new ContainerSingleItem() {
@Override
public ItemStack getTheItem() {
return Mob.this.getItemBySlot(slot);
}
@Override
public void setTheItem(ItemStack item) {
Mob.this.setItemSlot(slot, item);
if (!item.isEmpty()) {
Mob.this.setGuaranteedDrop(slot);
Mob.this.setPersistenceRequired();
}
}
@Override
public void setChanged() {
}
@Override
public boolean stillValid(Player player) {
return player.getVehicle() == Mob.this || player.canInteractWithEntity(Mob.this, 4.0);
}
};
}
@Override
protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
super.dropCustomDeathLoot(level, damageSource, recentlyHit);
for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
float f = this.dropChances.byEquipment(equipmentSlot);
if (f != 0.0F) {
boolean bl = this.dropChances.isPreserved(equipmentSlot);
if (damageSource.getEntity() instanceof LivingEntity livingEntity && this.level() instanceof ServerLevel serverLevel) {
f = EnchantmentHelper.processEquipmentDropChance(serverLevel, livingEntity, damageSource, f);
}
if (!itemStack.isEmpty()
&& !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)
&& (recentlyHit || bl)
&& this.random.nextFloat() < f) {
if (!bl && itemStack.isDamageableItem()) {
itemStack.setDamageValue(itemStack.getMaxDamage() - this.random.nextInt(1 + this.random.nextInt(Math.max(itemStack.getMaxDamage() - 3, 1))));
}
this.spawnAtLocation(level, itemStack);
this.setItemSlot(equipmentSlot, ItemStack.EMPTY);
}
}
}
}
public DropChances getDropChances() {
return this.dropChances;
}
public void dropPreservedEquipment(ServerLevel level) {
this.dropPreservedEquipment(level, itemStack -> true);
}
public Set<EquipmentSlot> dropPreservedEquipment(ServerLevel level, Predicate<ItemStack> filter) {
Set<EquipmentSlot> set = new HashSet();
for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
if (!itemStack.isEmpty()) {
if (!filter.test(itemStack)) {
set.add(equipmentSlot);
} else if (this.dropChances.isPreserved(equipmentSlot)) {
this.setItemSlot(equipmentSlot, ItemStack.EMPTY);
this.spawnAtLocation(level, itemStack);
}
}
}
return set;
}
private LootParams createEquipmentParams(ServerLevel level) {
return new net.minecraft.world.level.storage.loot.LootParams.Builder(level)
.withParameter(LootContextParams.ORIGIN, this.position())
.withParameter(LootContextParams.THIS_ENTITY, this)
.create(LootContextParamSets.EQUIPMENT);
}
public void equip(EquipmentTable equipmentTable) {
this.equip(equipmentTable.lootTable(), equipmentTable.slotDropChances());
}
public void equip(ResourceKey<LootTable> equipmentLootTable, Map<EquipmentSlot, Float> slotDropChances) {
if (this.level() instanceof ServerLevel serverLevel) {
this.equip(equipmentLootTable, this.createEquipmentParams(serverLevel), slotDropChances);
}
}
protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) {
if (random.nextFloat() < 0.15F * difficulty.getSpecialMultiplier()) {
int i = random.nextInt(2);
float f = this.level().getDifficulty() == Difficulty.HARD ? 0.1F : 0.25F;
if (random.nextFloat() < 0.095F) {
i++;
}
if (random.nextFloat() < 0.095F) {
i++;
}
if (random.nextFloat() < 0.095F) {
i++;
}
boolean bl = true;
for (EquipmentSlot equipmentSlot : EQUIPMENT_POPULATION_ORDER) {
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
if (!bl && random.nextFloat() < f) {
break;
}
bl = false;
if (itemStack.isEmpty()) {
Item item = getEquipmentForSlot(equipmentSlot, i);
if (item != null) {
this.setItemSlot(equipmentSlot, new ItemStack(item));
}
}
}
}
}
@Nullable
public static Item getEquipmentForSlot(EquipmentSlot slot, int chance) {
switch (slot) {
case HEAD:
if (chance == 0) {
return Items.LEATHER_HELMET;
} else if (chance == 1) {
return Items.GOLDEN_HELMET;
} else if (chance == 2) {
return Items.CHAINMAIL_HELMET;
} else if (chance == 3) {
return Items.IRON_HELMET;
} else if (chance == 4) {
return Items.DIAMOND_HELMET;
}
case CHEST:
if (chance == 0) {
return Items.LEATHER_CHESTPLATE;
} else if (chance == 1) {
return Items.GOLDEN_CHESTPLATE;
} else if (chance == 2) {
return Items.CHAINMAIL_CHESTPLATE;
} else if (chance == 3) {
return Items.IRON_CHESTPLATE;
} else if (chance == 4) {
return Items.DIAMOND_CHESTPLATE;
}
case LEGS:
if (chance == 0) {
return Items.LEATHER_LEGGINGS;
} else if (chance == 1) {
return Items.GOLDEN_LEGGINGS;
} else if (chance == 2) {
return Items.CHAINMAIL_LEGGINGS;
} else if (chance == 3) {
return Items.IRON_LEGGINGS;
} else if (chance == 4) {
return Items.DIAMOND_LEGGINGS;
}
case FEET:
if (chance == 0) {
return Items.LEATHER_BOOTS;
} else if (chance == 1) {
return Items.GOLDEN_BOOTS;
} else if (chance == 2) {
return Items.CHAINMAIL_BOOTS;
} else if (chance == 3) {
return Items.IRON_BOOTS;
} else if (chance == 4) {
return Items.DIAMOND_BOOTS;
}
default:
return null;
}
}
protected void populateDefaultEquipmentEnchantments(ServerLevelAccessor level, RandomSource random, DifficultyInstance difficulty) {
this.enchantSpawnedWeapon(level, random, difficulty);
for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
if (equipmentSlot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) {
this.enchantSpawnedArmor(level, random, equipmentSlot, difficulty);
}
}
}
protected void enchantSpawnedWeapon(ServerLevelAccessor level, RandomSource random, DifficultyInstance difficulty) {
this.enchantSpawnedEquipment(level, EquipmentSlot.MAINHAND, random, 0.25F, difficulty);
}
protected void enchantSpawnedArmor(ServerLevelAccessor level, RandomSource random, EquipmentSlot slot, DifficultyInstance difficulty) {
this.enchantSpawnedEquipment(level, slot, random, 0.5F, difficulty);
}
private void enchantSpawnedEquipment(ServerLevelAccessor level, EquipmentSlot slot, RandomSource random, float enchantChance, DifficultyInstance difficulty) {
ItemStack itemStack = this.getItemBySlot(slot);
if (!itemStack.isEmpty() && random.nextFloat() < enchantChance * difficulty.getSpecialMultiplier()) {
EnchantmentHelper.enchantItemFromProvider(itemStack, level.registryAccess(), VanillaEnchantmentProviders.MOB_SPAWN_EQUIPMENT, difficulty, random);
this.setItemSlot(slot, itemStack);
}
}
@Nullable
public SpawnGroupData finalizeSpawn(
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
) {
RandomSource randomSource = level.getRandom();
AttributeInstance attributeInstance = (AttributeInstance)Objects.requireNonNull(this.getAttribute(Attributes.FOLLOW_RANGE));
if (!attributeInstance.hasModifier(RANDOM_SPAWN_BONUS_ID)) {
attributeInstance.addPermanentModifier(
new AttributeModifier(RANDOM_SPAWN_BONUS_ID, randomSource.triangle(0.0, 0.11485000000000001), AttributeModifier.Operation.ADD_MULTIPLIED_BASE)
);
}
this.setLeftHanded(randomSource.nextFloat() < 0.05F);
return spawnGroupData;
}
/**
* Enable the Entity persistence
*/
public void setPersistenceRequired() {
this.persistenceRequired = true;
}
@Override
public void setDropChance(EquipmentSlot slot, float dropChance) {
this.dropChances = this.dropChances.withEquipmentChance(slot, dropChance);
}
@Override
public boolean canPickUpLoot() {
return this.canPickUpLoot;
}
public void setCanPickUpLoot(boolean canPickUpLoot) {
this.canPickUpLoot = canPickUpLoot;
}
@Override
protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
return this.canPickUpLoot();
}
/**
* @return {@code true} if this entity may not naturally despawn.
*/
public boolean isPersistenceRequired() {
return this.persistenceRequired;
}
@Override
public final InteractionResult interact(Player player, InteractionHand hand) {
if (!this.isAlive()) {
return InteractionResult.PASS;
} else {
InteractionResult interactionResult = this.checkAndHandleImportantInteractions(player, hand);
if (interactionResult.consumesAction()) {
this.gameEvent(GameEvent.ENTITY_INTERACT, player);
return interactionResult;
} else {
InteractionResult interactionResult2 = super.interact(player, hand);
if (interactionResult2 != InteractionResult.PASS) {
return interactionResult2;
} else {
interactionResult = this.mobInteract(player, hand);
if (interactionResult.consumesAction()) {
this.gameEvent(GameEvent.ENTITY_INTERACT, player);
return interactionResult;
} else {
return InteractionResult.PASS;
}
}
}
}
}
private InteractionResult checkAndHandleImportantInteractions(Player player, InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
if (itemStack.is(Items.NAME_TAG)) {
InteractionResult interactionResult = itemStack.interactLivingEntity(player, this, hand);
if (interactionResult.consumesAction()) {
return interactionResult;
}
}
if (itemStack.getItem() instanceof SpawnEggItem) {
if (this.level() instanceof ServerLevel) {
SpawnEggItem spawnEggItem = (SpawnEggItem)itemStack.getItem();
Optional<Mob> optional = spawnEggItem.spawnOffspringFromSpawnEgg(
player, this, (EntityType<? extends Mob>)this.getType(), (ServerLevel)this.level(), this.position(), itemStack
);
optional.ifPresent(mob -> this.onOffspringSpawnedFromEgg(player, mob));
if (optional.isEmpty()) {
return InteractionResult.PASS;
}
}
return InteractionResult.SUCCESS_SERVER;
} else {
return InteractionResult.PASS;
}
}
protected void onOffspringSpawnedFromEgg(Player player, Mob child) {
}
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
return InteractionResult.PASS;
}
public boolean isWithinRestriction() {
return this.isWithinRestriction(this.blockPosition());
}
public boolean isWithinRestriction(BlockPos pos) {
return this.restrictRadius == -1.0F ? true : this.restrictCenter.distSqr(pos) < this.restrictRadius * this.restrictRadius;
}
public void restrictTo(BlockPos pos, int distance) {
this.restrictCenter = pos;
this.restrictRadius = distance;
}
public BlockPos getRestrictCenter() {
return this.restrictCenter;
}
public float getRestrictRadius() {
return this.restrictRadius;
}
public void clearRestriction() {
this.restrictRadius = -1.0F;
}
public boolean hasRestriction() {
return this.restrictRadius != -1.0F;
}
@Nullable
public <T extends Mob> T convertTo(
EntityType<T> entityType, ConversionParams conversionParams, EntitySpawnReason spawnReason, AfterConversion<T> afterConversion
) {
if (this.isRemoved()) {
return null;
} else {
T mob = (T)entityType.create(this.level(), spawnReason);
if (mob == null) {
return null;
} else {
conversionParams.type().convert(this, mob, conversionParams);
afterConversion.finalizeConversion(mob);
if (this.level() instanceof ServerLevel serverLevel) {
serverLevel.addFreshEntity(mob);
}
if (conversionParams.type().shouldDiscardAfterConversion()) {
this.discard();
}
return mob;
}
}
}
@Nullable
public <T extends Mob> T convertTo(EntityType<T> entityType, ConversionParams coversionParams, AfterConversion<T> afterConversion) {
return this.convertTo(entityType, coversionParams, EntitySpawnReason.CONVERSION, afterConversion);
}
@Nullable
@Override
public Leashable.LeashData getLeashData() {
return this.leashData;
}
@Override
public void setLeashData(@Nullable Leashable.LeashData leashData) {
this.leashData = leashData;
}
@Override
public void onLeashRemoved() {
if (this.getLeashData() == null) {
this.clearRestriction();
}
}
@Override
public void leashTooFarBehaviour() {
Leashable.super.leashTooFarBehaviour();
this.goalSelector.disableControlFlag(Flag.MOVE);
}
@Override
public boolean canBeLeashed() {
return !(this instanceof Enemy);
}
@Override
public boolean startRiding(Entity vehicle, boolean force) {
boolean bl = super.startRiding(vehicle, force);
if (bl && this.isLeashed()) {
this.dropLeash();
}
return bl;
}
@Override
public boolean canSimulateMovement() {
return super.canSimulateMovement() && !this.isNoAi();
}
@Override
public boolean isEffectiveAi() {
return super.isEffectiveAi() && !this.isNoAi();
}
/**
* Set whether this Entity's AI is disabled
*/
public void setNoAi(boolean noAi) {
byte b = this.entityData.get(DATA_MOB_FLAGS_ID);
this.entityData.set(DATA_MOB_FLAGS_ID, noAi ? (byte)(b | 1) : (byte)(b & -2));
}
public void setLeftHanded(boolean leftHanded) {
byte b = this.entityData.get(DATA_MOB_FLAGS_ID);
this.entityData.set(DATA_MOB_FLAGS_ID, leftHanded ? (byte)(b | 2) : (byte)(b & -3));
}
public void setAggressive(boolean aggressive) {
byte b = this.entityData.get(DATA_MOB_FLAGS_ID);
this.entityData.set(DATA_MOB_FLAGS_ID, aggressive ? (byte)(b | 4) : (byte)(b & -5));
}
/**
* Get whether this Entity's AI is disabled
*/
public boolean isNoAi() {
return (this.entityData.get(DATA_MOB_FLAGS_ID) & 1) != 0;
}
public boolean isLeftHanded() {
return (this.entityData.get(DATA_MOB_FLAGS_ID) & 2) != 0;
}
public boolean isAggressive() {
return (this.entityData.get(DATA_MOB_FLAGS_ID) & 4) != 0;
}
/**
* Set whether this mob is a child.
*/
public void setBaby(boolean baby) {
}
@Override
public HumanoidArm getMainArm() {
return this.isLeftHanded() ? HumanoidArm.LEFT : HumanoidArm.RIGHT;
}
public boolean isWithinMeleeAttackRange(LivingEntity entity) {
return this.getAttackBoundingBox().intersects(entity.getHitbox());
}
protected AABB getAttackBoundingBox() {
Entity entity = this.getVehicle();
AABB aABB3;
if (entity != null) {
AABB aABB = entity.getBoundingBox();
AABB aABB2 = this.getBoundingBox();
aABB3 = new AABB(
Math.min(aABB2.minX, aABB.minX), aABB2.minY, Math.min(aABB2.minZ, aABB.minZ), Math.max(aABB2.maxX, aABB.maxX), aABB2.maxY, Math.max(aABB2.maxZ, aABB.maxZ)
);
} else {
aABB3 = this.getBoundingBox();
}
return aABB3.inflate(DEFAULT_ATTACK_REACH, 0.0, DEFAULT_ATTACK_REACH);
}
@Override
public boolean doHurtTarget(ServerLevel level, Entity source) {
float f = (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
ItemStack itemStack = this.getWeaponItem();
DamageSource damageSource = (DamageSource)Optional.ofNullable(itemStack.getItem().getDamageSource(this)).orElse(this.damageSources().mobAttack(this));
f = EnchantmentHelper.modifyDamage(level, itemStack, source, damageSource, f);
f += itemStack.getItem().getAttackDamageBonus(source, f, damageSource);
boolean bl = source.hurtServer(level, damageSource, f);
if (bl) {
float g = this.getKnockback(source, damageSource);
if (g > 0.0F && source instanceof LivingEntity livingEntity) {
livingEntity.knockback(g * 0.5F, Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)));
this.setDeltaMovement(this.getDeltaMovement().multiply(0.6, 1.0, 0.6));
}
if (source instanceof LivingEntity livingEntity) {
itemStack.hurtEnemy(livingEntity, this);
}
EnchantmentHelper.doPostAttackEffects(level, source, damageSource);
this.setLastHurtMob(source);
this.playAttackSound();
}
return bl;
}
protected void playAttackSound() {
}
protected boolean isSunBurnTick() {
if (this.level().isBrightOutside() && !this.level().isClientSide) {
float f = this.getLightLevelDependentMagicValue();
BlockPos blockPos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
boolean bl = this.isInWaterOrRain() || this.isInPowderSnow || this.wasInPowderSnow;
if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !bl && this.level().canSeeSky(blockPos)) {
return true;
}
}
return false;
}
@Override
protected void jumpInLiquid(TagKey<Fluid> fluidTag) {
if (this.getNavigation().canFloat()) {
super.jumpInLiquid(fluidTag);
} else {
this.setDeltaMovement(this.getDeltaMovement().add(0.0, 0.3, 0.0));
}
}
@VisibleForTesting
public void removeFreeWill() {
this.removeAllGoals(goal -> true);
this.getBrain().removeAllBehaviors();
}
public void removeAllGoals(Predicate<Goal> filter) {
this.goalSelector.removeAllGoals(filter);
}
@Override
protected void removeAfterChangingDimensions() {
super.removeAfterChangingDimensions();
for (EquipmentSlot equipmentSlot : EquipmentSlot.VALUES) {
ItemStack itemStack = this.getItemBySlot(equipmentSlot);
if (!itemStack.isEmpty()) {
itemStack.setCount(0);
}
}
}
@Nullable
@Override
public ItemStack getPickResult() {
SpawnEggItem spawnEggItem = SpawnEggItem.byId(this.getType());
return spawnEggItem == null ? null : new ItemStack(spawnEggItem);
}
@Override
protected void onAttributeUpdated(Holder<Attribute> attribute) {
super.onAttributeUpdated(attribute);
if (attribute.is(Attributes.FOLLOW_RANGE) || attribute.is(Attributes.TEMPT_RANGE)) {
this.getNavigation().updatePathfinderMaxVisitedNodes();
}
}
}