1377 lines
42 KiB
Java
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();
|
|
}
|
|
}
|
|
}
|