package net.minecraft.world.entity; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import java.util.Arrays; 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.NonNullList; import net.minecraft.core.Vec3i; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.FloatTag; import net.minecraft.nbt.ListTag; 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.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.ProfilerFiller; 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.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; 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.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.Boat; import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.BowItem; import net.minecraft.world.item.CrossbowItem; import net.minecraft.world.item.DiggerItem; 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.SwordItem; import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; import net.minecraft.world.item.enchantment.EnchantmentHelper; 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 org.jetbrains.annotations.Nullable; public abstract class Mob extends LivingEntity implements EquipmentUser, Leashable, Targeting { private static final EntityDataAccessor 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); 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 float DEFAULT_EQUIPMENT_DROP_CHANCE = 0.085F; public static final float PRESERVE_ITEM_DROP_CHANCE_THRESHOLD = 1.0F; public static final int PRESERVE_ITEM_DROP_CHANCE = 2; public static final int UPDATE_GOAL_SELECTOR_EVERY_N_TICKS = 2; private static final double DEFAULT_ATTACK_REACH = Math.sqrt(2.04F) - 0.6F; 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 final NonNullList handItems = NonNullList.withSize(2, ItemStack.EMPTY); protected final float[] handDropChances = new float[2]; private final NonNullList armorItems = NonNullList.withSize(4, ItemStack.EMPTY); protected final float[] armorDropChances = new float[4]; private ItemStack bodyArmorItem = ItemStack.EMPTY; protected float bodyArmorDropChance; private boolean canPickUpLoot; private boolean persistenceRequired; private final Map pathfindingMalus = Maps.newEnumMap(PathType.class); @Nullable private ResourceKey lootTable; private long lootTableSeed; @Nullable private Leashable.LeashData leashData; private BlockPos restrictCenter = BlockPos.ZERO; private float restrictRadius = -1.0F; protected Mob(EntityType entityType, Level level) { super(entityType, level); this.goalSelector = new GoalSelector(level.getProfilerSupplier()); this.targetSelector = new GoalSelector(level.getProfilerSupplier()); 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); Arrays.fill(this.armorDropChances, 0.085F); Arrays.fill(this.handDropChances, 0.085F); this.bodyArmorDropChance = 0.085F; if (level != null && !level.isClientSide) { this.registerGoals(); } } protected void registerGoals() { } public static AttributeSupplier.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(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(); this.level().getProfiler().push("mobBaseTick"); if (this.isAlive() && this.random.nextInt(1000) < this.ambientSoundTime++) { this.resetAmbientSoundTime(); this.playAmbientSound(); } this.level().getProfiler().pop(); } @Override protected void playHurtSound(DamageSource source) { this.resetAmbientSoundTime(); super.playHurtSound(source); } private void resetAmbientSoundTime() { this.ambientSoundTime = -this.getAmbientSoundInterval(); } @Override protected int getBaseExperienceReward() { if (this.xpReward > 0) { int i = this.xpReward; for (int j = 0; j < this.armorItems.size(); j++) { if (!this.armorItems.get(j).isEmpty() && this.armorDropChances[j] <= 1.0F) { i += 1 + this.random.nextInt(3); } } for (int jx = 0; jx < this.handItems.size(); jx++) { if (!this.handItems.get(jx).isEmpty() && this.handDropChances[jx] <= 1.0F) { i += 1 + this.random.nextInt(3); } } if (!this.bodyArmorItem.isEmpty() && this.bodyArmorDropChance <= 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) { for (int i = 0; i < 20; i++) { double d = this.random.nextGaussian() * 0.02; double e = this.random.nextGaussian() * 0.02; double f = this.random.nextGaussian() * 0.02; double g = 10.0; this.level().addParticle(ParticleTypes.POOF, this.getX(1.0) - d * 10.0, this.getRandomY() - e * 10.0, this.getRandomZ(1.0) - f * 10.0, d, e, f); } } 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 Boat); this.goalSelector.setControlFlag(Goal.Flag.MOVE, bl); this.goalSelector.setControlFlag(Goal.Flag.JUMP, bl && bl2); this.goalSelector.setControlFlag(Goal.Flag.LOOK, bl); } @Override protected float tickHeadTurn(float yRot, float animStep) { this.bodyRotationControl.clientTick(); return animStep; } @Nullable protected SoundEvent getAmbientSound() { return null; } @Override public void addAdditionalSaveData(CompoundTag compound) { super.addAdditionalSaveData(compound); compound.putBoolean("CanPickUpLoot", this.canPickUpLoot()); compound.putBoolean("PersistenceRequired", this.persistenceRequired); ListTag listTag = new ListTag(); for (ItemStack itemStack : this.armorItems) { if (!itemStack.isEmpty()) { listTag.add(itemStack.save(this.registryAccess())); } else { listTag.add(new CompoundTag()); } } compound.put("ArmorItems", listTag); ListTag listTag2 = new ListTag(); for (float f : this.armorDropChances) { listTag2.add(FloatTag.valueOf(f)); } compound.put("ArmorDropChances", listTag2); ListTag listTag3 = new ListTag(); for (ItemStack itemStack2 : this.handItems) { if (!itemStack2.isEmpty()) { listTag3.add(itemStack2.save(this.registryAccess())); } else { listTag3.add(new CompoundTag()); } } compound.put("HandItems", listTag3); ListTag listTag4 = new ListTag(); for (float g : this.handDropChances) { listTag4.add(FloatTag.valueOf(g)); } compound.put("HandDropChances", listTag4); if (!this.bodyArmorItem.isEmpty()) { compound.put("body_armor_item", this.bodyArmorItem.save(this.registryAccess())); compound.putFloat("body_armor_drop_chance", this.bodyArmorDropChance); } this.writeLeashData(compound, this.leashData); compound.putBoolean("LeftHanded", this.isLeftHanded()); if (this.lootTable != null) { compound.putString("DeathLootTable", this.lootTable.location().toString()); if (this.lootTableSeed != 0L) { compound.putLong("DeathLootTableSeed", this.lootTableSeed); } } if (this.isNoAi()) { compound.putBoolean("NoAI", this.isNoAi()); } } @Override public void readAdditionalSaveData(CompoundTag compound) { super.readAdditionalSaveData(compound); if (compound.contains("CanPickUpLoot", 1)) { this.setCanPickUpLoot(compound.getBoolean("CanPickUpLoot")); } this.persistenceRequired = compound.getBoolean("PersistenceRequired"); if (compound.contains("ArmorItems", 9)) { ListTag listTag = compound.getList("ArmorItems", 10); for (int i = 0; i < this.armorItems.size(); i++) { CompoundTag compoundTag = listTag.getCompound(i); this.armorItems.set(i, ItemStack.parseOptional(this.registryAccess(), compoundTag)); } } if (compound.contains("ArmorDropChances", 9)) { ListTag listTag = compound.getList("ArmorDropChances", 5); for (int i = 0; i < listTag.size(); i++) { this.armorDropChances[i] = listTag.getFloat(i); } } if (compound.contains("HandItems", 9)) { ListTag listTag = compound.getList("HandItems", 10); for (int i = 0; i < this.handItems.size(); i++) { CompoundTag compoundTag = listTag.getCompound(i); this.handItems.set(i, ItemStack.parseOptional(this.registryAccess(), compoundTag)); } } if (compound.contains("HandDropChances", 9)) { ListTag listTag = compound.getList("HandDropChances", 5); for (int i = 0; i < listTag.size(); i++) { this.handDropChances[i] = listTag.getFloat(i); } } if (compound.contains("body_armor_item", 10)) { this.bodyArmorItem = (ItemStack)ItemStack.parse(this.registryAccess(), compound.getCompound("body_armor_item")).orElse(ItemStack.EMPTY); this.bodyArmorDropChance = compound.getFloat("body_armor_drop_chance"); } else { this.bodyArmorItem = ItemStack.EMPTY; } this.leashData = this.readLeashData(compound); this.setLeftHanded(compound.getBoolean("LeftHanded")); if (compound.contains("DeathLootTable", 8)) { this.lootTable = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(compound.getString("DeathLootTable"))); this.lootTableSeed = compound.getLong("DeathLootTableSeed"); } this.setNoAi(compound.getBoolean("NoAI")); } @Override protected void dropFromLootTable(DamageSource damageSource, boolean hitByPlayer) { super.dropFromLootTable(damageSource, hitByPlayer); this.lootTable = null; } @Override public final ResourceKey getLootTable() { return this.lootTable == null ? this.getDefaultLootTable() : this.lootTable; } protected ResourceKey getDefaultLootTable() { return 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(); this.level().getProfiler().push("looting"); if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && this.level().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(itemEntity.getItem())) { this.pickUpItem(itemEntity); } } } this.level().getProfiler().pop(); } protected Vec3i getPickupReach() { return ITEM_PICKUP_REACH; } /** * Tests if this entity should pick up a weapon or an armor piece. Entity drops current weapon or armor if the new one is better. */ protected void pickUpItem(ItemEntity itemEntity) { ItemStack itemStack = itemEntity.getItem(); ItemStack itemStack2 = this.equipItemIfPossible(itemStack.copy()); if (!itemStack2.isEmpty()) { this.onItemPickup(itemEntity); this.take(itemEntity, itemStack2.getCount()); itemStack.shrink(itemStack2.getCount()); if (itemStack.isEmpty()) { itemEntity.discard(); } } } public ItemStack equipItemIfPossible(ItemStack stack) { EquipmentSlot equipmentSlot = this.getEquipmentSlotForItem(stack); ItemStack itemStack = this.getItemBySlot(equipmentSlot); boolean bl = this.canReplaceCurrentItem(stack, itemStack); if (equipmentSlot.isArmor() && !bl) { equipmentSlot = EquipmentSlot.MAINHAND; itemStack = this.getItemBySlot(equipmentSlot); bl = itemStack.isEmpty(); } if (bl && this.canHoldItem(stack)) { double d = this.getEquipmentDropChance(equipmentSlot); if (!itemStack.isEmpty() && Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d) { this.spawnAtLocation(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) { switch (slot.getType()) { case HAND: this.handDropChances[slot.getIndex()] = 2.0F; break; case HUMANOID_ARMOR: this.armorDropChances[slot.getIndex()] = 2.0F; break; case ANIMAL_ARMOR: this.bodyArmorDropChance = 2.0F; } } protected boolean canReplaceCurrentItem(ItemStack candidate, ItemStack existing) { if (existing.isEmpty()) { return true; } else if (candidate.getItem() instanceof SwordItem) { if (!(existing.getItem() instanceof SwordItem)) { return true; } else { double d = this.getApproximateAttackDamageWithItem(candidate); double e = this.getApproximateAttackDamageWithItem(existing); return d != e ? d > e : this.canReplaceEqualItem(candidate, existing); } } else if (candidate.getItem() instanceof BowItem && existing.getItem() instanceof BowItem) { return this.canReplaceEqualItem(candidate, existing); } else if (candidate.getItem() instanceof CrossbowItem && existing.getItem() instanceof CrossbowItem) { return this.canReplaceEqualItem(candidate, existing); } else if (candidate.getItem() instanceof ArmorItem armorItem) { if (EnchantmentHelper.has(existing, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) { return false; } else if (!(existing.getItem() instanceof ArmorItem)) { return true; } else { ArmorItem armorItem2 = (ArmorItem)existing.getItem(); if (armorItem.getDefense() != armorItem2.getDefense()) { return armorItem.getDefense() > armorItem2.getDefense(); } else { return armorItem.getToughness() != armorItem2.getToughness() ? armorItem.getToughness() > armorItem2.getToughness() : this.canReplaceEqualItem(candidate, existing); } } } else { if (candidate.getItem() instanceof DiggerItem) { if (existing.getItem() instanceof BlockItem) { return true; } if (existing.getItem() instanceof DiggerItem) { double d = this.getApproximateAttackDamageWithItem(candidate); double e = this.getApproximateAttackDamageWithItem(existing); if (d != e) { return d > e; } return this.canReplaceEqualItem(candidate, existing); } } return false; } } private double getApproximateAttackDamageWithItem(ItemStack itemStack) { ItemAttributeModifiers itemAttributeModifiers = itemStack.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY); return itemAttributeModifiers.compute(this.getAttributeBaseValue(Attributes.ATTACK_DAMAGE), EquipmentSlot.MAINHAND); } public boolean canReplaceEqualItem(ItemStack candidate, ItemStack existing) { return candidate.getDamageValue() < existing.getDamageValue() ? true : hasAnyComponentExceptDamage(candidate) && !hasAnyComponentExceptDamage(existing); } private static boolean hasAnyComponentExceptDamage(ItemStack stack) { DataComponentMap dataComponentMap = stack.getComponents(); int i = dataComponentMap.size(); return i > 1 || i == 1 && !dataComponentMap.has(DataComponents.DAMAGE); } public boolean canHoldItem(ItemStack stack) { return true; } public boolean wantsToPickUp(ItemStack stack) { return this.canHoldItem(stack); } 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 = this.level().getProfiler(); 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(); 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() { } /** * 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 type, LevelAccessor level, MobSpawnType spawnType, BlockPos pos, RandomSource random) { BlockPos blockPos = pos.below(); return spawnType == MobSpawnType.SPAWNER || level.getBlockState(blockPos).isValidSpawn(level, blockPos, type); } public boolean checkSpawnRules(LevelAccessor level, MobSpawnType reason) { 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); } } @Override public Iterable getHandSlots() { return this.handItems; } @Override public Iterable getArmorSlots() { return this.armorItems; } public ItemStack getBodyArmorItem() { return this.bodyArmorItem; } @Override public boolean canUseSlot(EquipmentSlot slot) { return slot != EquipmentSlot.BODY; } public boolean isWearingBodyArmor() { return !this.getItemBySlot(EquipmentSlot.BODY).isEmpty(); } public boolean isBodyArmorItem(ItemStack stack) { return false; } public void setBodyArmorItem(ItemStack stack) { this.setItemSlotAndDropWhenKilled(EquipmentSlot.BODY, stack); } @Override public Iterable getArmorAndBodyArmorSlots() { return (Iterable)(this.bodyArmorItem.isEmpty() ? this.armorItems : Iterables.concat(this.armorItems, List.of(this.bodyArmorItem))); } @Override public ItemStack getItemBySlot(EquipmentSlot slot) { return switch (slot.getType()) { case HAND -> (ItemStack)this.handItems.get(slot.getIndex()); case HUMANOID_ARMOR -> (ItemStack)this.armorItems.get(slot.getIndex()); case ANIMAL_ARMOR -> this.bodyArmorItem; }; } @Override public void setItemSlot(EquipmentSlot slot, ItemStack stack) { this.verifyEquippedItem(stack); switch (slot.getType()) { case HAND: this.onEquipItem(slot, this.handItems.set(slot.getIndex(), stack), stack); break; case HUMANOID_ARMOR: this.onEquipItem(slot, this.armorItems.set(slot.getIndex(), stack), stack); break; case ANIMAL_ARMOR: ItemStack itemStack = this.bodyArmorItem; this.bodyArmorItem = stack; this.onEquipItem(slot, itemStack, stack); } } @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.getEquipmentDropChance(equipmentSlot); if (f != 0.0F) { boolean bl = f > 1.0F; 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(itemStack); this.setItemSlot(equipmentSlot, ItemStack.EMPTY); } } } } protected float getEquipmentDropChance(EquipmentSlot slot) { return switch (slot.getType()) { case HAND -> this.handDropChances[slot.getIndex()]; case HUMANOID_ARMOR -> this.armorDropChances[slot.getIndex()]; case ANIMAL_ARMOR -> this.bodyArmorDropChance; }; } public void dropPreservedEquipment() { this.dropPreservedEquipment(itemStack -> true); } public Set dropPreservedEquipment(Predicate predicate) { Set set = new HashSet(); for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) { ItemStack itemStack = this.getItemBySlot(equipmentSlot); if (!itemStack.isEmpty()) { if (!predicate.test(itemStack)) { set.add(equipmentSlot); } else { double d = this.getEquipmentDropChance(equipmentSlot); if (d > 1.0) { this.setItemSlot(equipmentSlot, ItemStack.EMPTY); this.spawnAtLocation(itemStack); } } } } return set; } private LootParams createEquipmentParams(ServerLevel level) { return new 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 equipmentLootTable, Map 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 : EquipmentSlot.values()) { if (equipmentSlot.getType() == EquipmentSlot.Type.HUMANOID_ARMOR) { 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, MobSpawnType spawnType, @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) { switch (slot.getType()) { case HAND: this.handDropChances[slot.getIndex()] = dropChance; break; case HUMANOID_ARMOR: this.armorDropChances[slot.getIndex()] = dropChance; break; case ANIMAL_ARMOR: this.bodyArmorDropChance = dropChance; } } public boolean canPickUpLoot() { return this.canPickUpLoot; } public void setCanPickUpLoot(boolean canPickUpLoot) { this.canPickUpLoot = canPickUpLoot; } @Override public boolean canTakeItem(ItemStack stack) { EquipmentSlot equipmentSlot = this.getEquipmentSlotForItem(stack); return this.getItemBySlot(equipmentSlot).isEmpty() && 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 optional = spawnEggItem.spawnOffspringFromSpawnEgg( player, this, (EntityType)this.getType(), (ServerLevel)this.level(), this.position(), itemStack ); optional.ifPresent(mob -> this.onOffspringSpawnedFromEgg(player, mob)); return optional.isPresent() ? InteractionResult.SUCCESS : InteractionResult.PASS; } else { return InteractionResult.CONSUME; } } 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 convertTo(EntityType entityType, boolean transferInventory) { if (this.isRemoved()) { return null; } else { T mob = (T)entityType.create(this.level()); if (mob == null) { return null; } else { mob.copyPosition(this); mob.setBaby(this.isBaby()); mob.setNoAi(this.isNoAi()); if (this.hasCustomName()) { mob.setCustomName(this.getCustomName()); mob.setCustomNameVisible(this.isCustomNameVisible()); } if (this.isPersistenceRequired()) { mob.setPersistenceRequired(); } mob.setInvulnerable(this.isInvulnerable()); if (transferInventory) { mob.setCanPickUpLoot(this.canPickUpLoot()); for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) { ItemStack itemStack = this.getItemBySlot(equipmentSlot); if (!itemStack.isEmpty()) { mob.setItemSlot(equipmentSlot, itemStack.copyAndClear()); mob.setDropChance(equipmentSlot, this.getEquipmentDropChance(equipmentSlot)); } } } this.level().addFreshEntity(mob); if (this.isPassenger()) { Entity entity = this.getVehicle(); this.stopRiding(); mob.startRiding(entity, true); } this.discard(); return mob; } } } @Nullable @Override public Leashable.LeashData getLeashData() { return this.leashData; } @Override public void setLeashData(@Nullable Leashable.LeashData leashData) { this.leashData = leashData; } @Override public void dropLeash(boolean broadcastPacket, boolean dropItem) { Leashable.super.dropLeash(broadcastPacket, dropItem); if (this.getLeashData() == null) { this.clearRestriction(); } } @Override public void leashTooFarBehaviour() { Leashable.super.leashTooFarBehaviour(); this.goalSelector.disableControlFlag(Goal.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(true, true); } return bl; } @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(Entity target) { float f = (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE); DamageSource damageSource = this.damageSources().mobAttack(this); if (this.level() instanceof ServerLevel serverLevel) { f = EnchantmentHelper.modifyDamage(serverLevel, this.getWeaponItem(), target, damageSource, f); } boolean bl = target.hurt(damageSource, f); if (bl) { float g = this.getKnockback(target, damageSource); if (g > 0.0F && target 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 (this.level() instanceof ServerLevel serverLevel2) { EnchantmentHelper.doPostAttackEffects(serverLevel2, target, damageSource); } this.setLastHurtMob(target); this.playAttackSound(); } return bl; } protected void playAttackSound() { } protected boolean isSunBurnTick() { if (this.level().isDay() && !this.level().isClientSide) { float f = this.getLightLevelDependentMagicValue(); BlockPos blockPos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); boolean bl = this.isInWaterRainOrBubble() || 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 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 filter) { this.goalSelector.removeAllGoals(filter); } @Override protected void removeAfterChangingDimensions() { super.removeAfterChangingDimensions(); this.getAllSlots().forEach(itemStack -> { if (!itemStack.isEmpty()) { itemStack.setCount(0); } }); } @Nullable @Override public ItemStack getPickResult() { SpawnEggItem spawnEggItem = SpawnEggItem.byId(this.getType()); return spawnEggItem == null ? null : new ItemStack(spawnEggItem); } }