1425 lines
54 KiB
Java
1425 lines
54 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.common.annotations.VisibleForTesting
|
|
* com.google.common.collect.Maps
|
|
* it.unimi.dsi.fastutil.objects.Object2IntMap$Entry
|
|
* org.jspecify.annotations.Nullable
|
|
*/
|
|
package net.minecraft.world.entity;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.google.common.collect.Maps;
|
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|
import java.util.ArrayList;
|
|
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.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.resources.Identifier;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.tags.EntityTypeTags;
|
|
import net.minecraft.tags.TagKey;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.debug.DebugBrainDump;
|
|
import net.minecraft.util.debug.DebugGoalInfo;
|
|
import net.minecraft.util.debug.DebugPathInfo;
|
|
import net.minecraft.util.debug.DebugSubscriptions;
|
|
import net.minecraft.util.debug.DebugValueSource;
|
|
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.attribute.EnvironmentAttributes;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.entity.ConversionParams;
|
|
import net.minecraft.world.entity.DropChances;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.EquipmentSlot;
|
|
import net.minecraft.world.entity.EquipmentTable;
|
|
import net.minecraft.world.entity.EquipmentUser;
|
|
import net.minecraft.world.entity.HumanoidArm;
|
|
import net.minecraft.world.entity.Leashable;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.SpawnGroupData;
|
|
import net.minecraft.world.entity.Targeting;
|
|
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.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.goal.WrappedGoal;
|
|
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.component.UseRemainder;
|
|
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.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.gamerules.GameRules;
|
|
import net.minecraft.world.level.material.Fluid;
|
|
import net.minecraft.world.level.pathfinder.Path;
|
|
import net.minecraft.world.level.pathfinder.PathType;
|
|
import net.minecraft.world.level.storage.ValueInput;
|
|
import net.minecraft.world.level.storage.ValueOutput;
|
|
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.phys.Vec3;
|
|
import net.minecraft.world.ticks.ContainerSingleItem;
|
|
import org.jspecify.annotations.Nullable;
|
|
|
|
public abstract class Mob
|
|
extends LivingEntity
|
|
implements Targeting,
|
|
EquipmentUser,
|
|
Leashable {
|
|
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 WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE = 0.1087f;
|
|
public static final float WEARING_ARMOR_UPGRADE_MATERIAL_ATTEMPTS = 3.0f;
|
|
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) - (double)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 Identifier RANDOM_SPAWN_BONUS_ID = Identifier.withDefaultNamespace("random_spawn_bonus");
|
|
public static final String TAG_DROP_CHANCES = "drop_chances";
|
|
public static final String TAG_LEFT_HANDED = "LeftHanded";
|
|
public static final String TAG_CAN_PICK_UP_LOOT = "CanPickUpLoot";
|
|
public static final String TAG_NO_AI = "NoAI";
|
|
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;
|
|
private @Nullable 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;
|
|
private @Nullable Leashable.LeashData leashData;
|
|
private BlockPos homePosition = BlockPos.ZERO;
|
|
private int homeRadius = -1;
|
|
|
|
protected Mob(EntityType<? extends Mob> type, Level level) {
|
|
super((EntityType<? extends LivingEntity>)type, 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 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 riding;
|
|
Entity entity = this.getControlledVehicle();
|
|
Mob inheritFrom = entity instanceof Mob && (riding = (Mob)entity).shouldPassengersInheritMalus() ? riding : this;
|
|
Float malus = inheritFrom.pathfindingMalus.get((Object)pathType);
|
|
return malus == null ? pathType.getMalus() : malus.floatValue();
|
|
}
|
|
|
|
public void setPathfindingMalus(PathType pathType, float cost) {
|
|
this.pathfindingMalus.put(pathType, Float.valueOf(cost));
|
|
}
|
|
|
|
public void onPathfindingStart() {
|
|
}
|
|
|
|
public void onPathfindingDone() {
|
|
}
|
|
|
|
protected BodyRotationControl createBodyControl() {
|
|
return new BodyRotationControl(this);
|
|
}
|
|
|
|
public LookControl getLookControl() {
|
|
return this.lookControl;
|
|
}
|
|
|
|
public MoveControl getMoveControl() {
|
|
Entity entity = this.getControlledVehicle();
|
|
if (entity instanceof Mob) {
|
|
Mob riding = (Mob)entity;
|
|
return riding.getMoveControl();
|
|
}
|
|
return this.moveControl;
|
|
}
|
|
|
|
public JumpControl getJumpControl() {
|
|
return this.jumpControl;
|
|
}
|
|
|
|
public PathNavigation getNavigation() {
|
|
Entity entity = this.getControlledVehicle();
|
|
if (entity instanceof Mob) {
|
|
Mob riding = (Mob)entity;
|
|
return riding.getNavigation();
|
|
}
|
|
return this.navigation;
|
|
}
|
|
|
|
/*
|
|
* Enabled force condition propagation
|
|
* Lifted jumps to return sites
|
|
*/
|
|
@Override
|
|
public @Nullable LivingEntity getControllingPassenger() {
|
|
Entity firstPassenger = this.getFirstPassenger();
|
|
if (this.isNoAi()) return null;
|
|
if (!(firstPassenger instanceof Mob)) return null;
|
|
Mob passenger = (Mob)firstPassenger;
|
|
if (!firstPassenger.canControlVehicle()) return null;
|
|
Mob mob = passenger;
|
|
return mob;
|
|
}
|
|
|
|
public Sensing getSensing() {
|
|
return this.sensing;
|
|
}
|
|
|
|
@Override
|
|
public @Nullable LivingEntity getTarget() {
|
|
return this.target;
|
|
}
|
|
|
|
protected final @Nullable LivingEntity getTargetFromBrain() {
|
|
return this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null);
|
|
}
|
|
|
|
public void setTarget(@Nullable LivingEntity target) {
|
|
this.target = target;
|
|
}
|
|
|
|
@Override
|
|
public boolean canAttackType(EntityType<?> targetType) {
|
|
return targetType != EntityType.GHAST;
|
|
}
|
|
|
|
public boolean canFireProjectileWeapon(ProjectileWeaponItem item) {
|
|
return false;
|
|
}
|
|
|
|
public void ate() {
|
|
this.gameEvent(GameEvent.EAT);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
|
|
super.defineSynchedData(entityData);
|
|
entityData.define(DATA_MOB_FLAGS_ID, (byte)0);
|
|
}
|
|
|
|
public int getAmbientSoundInterval() {
|
|
return 80;
|
|
}
|
|
|
|
public void playAmbientSound() {
|
|
this.makeSound(this.getAmbientSound());
|
|
}
|
|
|
|
@Override
|
|
public void baseTick() {
|
|
super.baseTick();
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.push("mobBaseTick");
|
|
if (this.isAlive() && this.random.nextInt(1000) < this.ambientSoundTime++) {
|
|
this.resetAmbientSoundTime();
|
|
this.playAmbientSound();
|
|
}
|
|
profiler.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 result = this.xpReward;
|
|
for (EquipmentSlot slot : EquipmentSlot.VALUES) {
|
|
ItemStack item;
|
|
if (!slot.canIncreaseExperience() || (item = this.getItemBySlot(slot)).isEmpty() || !(this.dropChances.byEquipment(slot) <= 1.0f)) continue;
|
|
result += 1 + this.random.nextInt(3);
|
|
}
|
|
return result;
|
|
}
|
|
return this.xpReward;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
protected void updateControlFlags() {
|
|
boolean noController = !(this.getControllingPassenger() instanceof Mob);
|
|
boolean notInBoat = !(this.getVehicle() instanceof AbstractBoat);
|
|
this.goalSelector.setControlFlag(Goal.Flag.MOVE, noController);
|
|
this.goalSelector.setControlFlag(Goal.Flag.JUMP, noController && notInBoat);
|
|
this.goalSelector.setControlFlag(Goal.Flag.LOOK, noController);
|
|
}
|
|
|
|
@Override
|
|
protected void tickHeadTurn(float yBodyRotT) {
|
|
this.bodyRotationControl.clientTick();
|
|
}
|
|
|
|
protected @Nullable SoundEvent getAmbientSound() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected void addAdditionalSaveData(ValueOutput output) {
|
|
super.addAdditionalSaveData(output);
|
|
output.putBoolean(TAG_CAN_PICK_UP_LOOT, this.canPickUpLoot());
|
|
output.putBoolean("PersistenceRequired", this.persistenceRequired);
|
|
if (!this.dropChances.equals(DropChances.DEFAULT)) {
|
|
output.store(TAG_DROP_CHANCES, DropChances.CODEC, this.dropChances);
|
|
}
|
|
this.writeLeashData(output, this.leashData);
|
|
if (this.hasHome()) {
|
|
output.putInt("home_radius", this.homeRadius);
|
|
output.store("home_pos", BlockPos.CODEC, this.homePosition);
|
|
}
|
|
output.putBoolean(TAG_LEFT_HANDED, this.isLeftHanded());
|
|
this.lootTable.ifPresent(lootTable -> output.store("DeathLootTable", LootTable.KEY_CODEC, lootTable));
|
|
if (this.lootTableSeed != 0L) {
|
|
output.putLong("DeathLootTableSeed", this.lootTableSeed);
|
|
}
|
|
if (this.isNoAi()) {
|
|
output.putBoolean(TAG_NO_AI, this.isNoAi());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void readAdditionalSaveData(ValueInput input) {
|
|
super.readAdditionalSaveData(input);
|
|
this.setCanPickUpLoot(input.getBooleanOr(TAG_CAN_PICK_UP_LOOT, false));
|
|
this.persistenceRequired = input.getBooleanOr("PersistenceRequired", false);
|
|
this.dropChances = input.read(TAG_DROP_CHANCES, DropChances.CODEC).orElse(DropChances.DEFAULT);
|
|
this.readLeashData(input);
|
|
this.homeRadius = input.getIntOr("home_radius", -1);
|
|
if (this.homeRadius >= 0) {
|
|
this.homePosition = input.read("home_pos", BlockPos.CODEC).orElse(BlockPos.ZERO);
|
|
}
|
|
this.setLeftHanded(input.getBooleanOr(TAG_LEFT_HANDED, false));
|
|
this.lootTable = input.read("DeathLootTable", LootTable.KEY_CODEC);
|
|
this.lootTableSeed = input.getLongOr("DeathLootTableSeed", 0L);
|
|
this.setNoAi(input.getBooleanOr(TAG_NO_AI, false));
|
|
}
|
|
|
|
@Override
|
|
protected void dropFromLootTable(ServerLevel level, DamageSource source, boolean playerKilled) {
|
|
super.dropFromLootTable(level, source, playerKilled);
|
|
this.lootTable = Optional.empty();
|
|
}
|
|
|
|
@Override
|
|
public final Optional<ResourceKey<LootTable>> getLootTable() {
|
|
if (this.lootTable.isPresent()) {
|
|
return this.lootTable;
|
|
}
|
|
return super.getLootTable();
|
|
}
|
|
|
|
@Override
|
|
public long getLootTableSeed() {
|
|
return this.lootTableSeed;
|
|
}
|
|
|
|
public void setZza(float zza) {
|
|
this.zza = zza;
|
|
}
|
|
|
|
public void setYya(float yya) {
|
|
this.yya = yya;
|
|
}
|
|
|
|
public void setXxa(float xxa) {
|
|
this.xxa = xxa;
|
|
}
|
|
|
|
@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);
|
|
this.setDeltaMovement(0.0, 0.0, 0.0);
|
|
this.resetAngularLeashMomentum();
|
|
}
|
|
|
|
@Override
|
|
public void aiStep() {
|
|
super.aiStep();
|
|
if (this.getType().is(EntityTypeTags.BURN_IN_DAYLIGHT)) {
|
|
this.burnUndead();
|
|
}
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.push("looting");
|
|
Level level = this.level();
|
|
if (level instanceof ServerLevel) {
|
|
ServerLevel serverLevel = (ServerLevel)level;
|
|
if (this.canPickUpLoot() && this.isAlive() && !this.dead && serverLevel.getGameRules().get(GameRules.MOB_GRIEFING).booleanValue()) {
|
|
Vec3i pickupReach = this.getPickupReach();
|
|
List<ItemEntity> entities = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate(pickupReach.getX(), pickupReach.getY(), pickupReach.getZ()));
|
|
for (ItemEntity entity : entities) {
|
|
if (entity.isRemoved() || entity.getItem().isEmpty() || entity.hasPickUpDelay() || !this.wantsToPickUp(serverLevel, entity.getItem())) continue;
|
|
this.pickUpItem(serverLevel, entity);
|
|
}
|
|
}
|
|
}
|
|
profiler.pop();
|
|
}
|
|
|
|
protected EquipmentSlot sunProtectionSlot() {
|
|
return EquipmentSlot.HEAD;
|
|
}
|
|
|
|
private void burnUndead() {
|
|
if (!this.isAlive() || !this.isSunBurnTick()) {
|
|
return;
|
|
}
|
|
EquipmentSlot slot = this.sunProtectionSlot();
|
|
ItemStack sunBlocker = this.getItemBySlot(slot);
|
|
if (!sunBlocker.isEmpty()) {
|
|
if (sunBlocker.isDamageableItem()) {
|
|
Item sunBlockerItem = sunBlocker.getItem();
|
|
sunBlocker.setDamageValue(sunBlocker.getDamageValue() + this.random.nextInt(2));
|
|
if (sunBlocker.getDamageValue() >= sunBlocker.getMaxDamage()) {
|
|
this.onEquippedItemBroken(sunBlockerItem, slot);
|
|
this.setItemSlot(slot, ItemStack.EMPTY);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
this.igniteForSeconds(8.0f);
|
|
}
|
|
|
|
private boolean isSunBurnTick() {
|
|
if (!this.level().isClientSide() && this.level().environmentAttributes().getValue(EnvironmentAttributes.MONSTERS_BURN, this.position()).booleanValue()) {
|
|
boolean isInNonBurnableBlock;
|
|
float br = this.getLightLevelDependentMagicValue();
|
|
BlockPos roundedPos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
|
|
boolean bl = isInNonBurnableBlock = this.isInWater() || this.isInPowderSnow || this.wasInPowderSnow;
|
|
if (br > 0.5f && this.random.nextFloat() * 30.0f < (br - 0.4f) * 2.0f && !isInNonBurnableBlock && this.level().canSeeSky(roundedPos)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected Vec3i getPickupReach() {
|
|
return ITEM_PICKUP_REACH;
|
|
}
|
|
|
|
protected void pickUpItem(ServerLevel level, ItemEntity entity) {
|
|
ItemStack itemStack = entity.getItem();
|
|
ItemStack equippedWithStack = this.equipItemIfPossible(level, itemStack.copy());
|
|
if (!equippedWithStack.isEmpty()) {
|
|
this.onItemPickup(entity);
|
|
this.take(entity, equippedWithStack.getCount());
|
|
itemStack.shrink(equippedWithStack.getCount());
|
|
if (itemStack.isEmpty()) {
|
|
entity.discard();
|
|
}
|
|
}
|
|
}
|
|
|
|
public ItemStack equipItemIfPossible(ServerLevel level, ItemStack itemStack) {
|
|
EquipmentSlot slot = this.getEquipmentSlotForItem(itemStack);
|
|
if (!this.isEquippableInSlot(itemStack, slot)) {
|
|
return ItemStack.EMPTY;
|
|
}
|
|
ItemStack current = this.getItemBySlot(slot);
|
|
boolean canReplace = this.canReplaceCurrentItem(itemStack, current, slot);
|
|
if (slot.isArmor() && !canReplace) {
|
|
slot = EquipmentSlot.MAINHAND;
|
|
current = this.getItemBySlot(slot);
|
|
canReplace = current.isEmpty();
|
|
}
|
|
if (canReplace && this.canHoldItem(itemStack)) {
|
|
double dropChance = this.dropChances.byEquipment(slot);
|
|
if (!current.isEmpty() && (double)Math.max(this.random.nextFloat() - 0.1f, 0.0f) < dropChance) {
|
|
this.spawnAtLocation(level, current);
|
|
}
|
|
ItemStack toEquip = slot.limit(itemStack);
|
|
this.setItemSlotAndDropWhenKilled(slot, toEquip);
|
|
return toEquip;
|
|
}
|
|
return ItemStack.EMPTY;
|
|
}
|
|
|
|
protected void setItemSlotAndDropWhenKilled(EquipmentSlot slot, ItemStack itemStack) {
|
|
this.setItemSlot(slot, itemStack);
|
|
this.setGuaranteedDrop(slot);
|
|
this.persistenceRequired = true;
|
|
}
|
|
|
|
protected boolean canShearEquipment(Player player) {
|
|
return !this.isVehicle();
|
|
}
|
|
|
|
public void setGuaranteedDrop(EquipmentSlot slot) {
|
|
this.dropChances = this.dropChances.withGuaranteedDrop(slot);
|
|
}
|
|
|
|
protected boolean canReplaceCurrentItem(ItemStack newItemStack, ItemStack currentItemStack, EquipmentSlot slot) {
|
|
if (currentItemStack.isEmpty()) {
|
|
return true;
|
|
}
|
|
if (slot.isArmor()) {
|
|
return this.compareArmor(newItemStack, currentItemStack, slot);
|
|
}
|
|
if (slot == EquipmentSlot.MAINHAND) {
|
|
return this.compareWeapons(newItemStack, currentItemStack, slot);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean compareArmor(ItemStack newItemStack, ItemStack currentItemStack, EquipmentSlot slot) {
|
|
if (EnchantmentHelper.has(currentItemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) {
|
|
return false;
|
|
}
|
|
double newDefense = this.getApproximateAttributeWith(newItemStack, Attributes.ARMOR, slot);
|
|
double oldDefense = this.getApproximateAttributeWith(currentItemStack, Attributes.ARMOR, slot);
|
|
double newToughness = this.getApproximateAttributeWith(newItemStack, Attributes.ARMOR_TOUGHNESS, slot);
|
|
double oldToughness = this.getApproximateAttributeWith(currentItemStack, Attributes.ARMOR_TOUGHNESS, slot);
|
|
if (newDefense != oldDefense) {
|
|
return newDefense > oldDefense;
|
|
}
|
|
if (newToughness != oldToughness) {
|
|
return newToughness > oldToughness;
|
|
}
|
|
return this.canReplaceEqualItem(newItemStack, currentItemStack);
|
|
}
|
|
|
|
private boolean compareWeapons(ItemStack newItemStack, ItemStack currentItemStack, EquipmentSlot slot) {
|
|
double oldAttackDamage;
|
|
double newAttackDamage;
|
|
TagKey<Item> preferredWeaponType = this.getPreferredWeaponType();
|
|
if (preferredWeaponType != null) {
|
|
if (currentItemStack.is(preferredWeaponType) && !newItemStack.is(preferredWeaponType)) {
|
|
return false;
|
|
}
|
|
if (!currentItemStack.is(preferredWeaponType) && newItemStack.is(preferredWeaponType)) {
|
|
return true;
|
|
}
|
|
}
|
|
if ((newAttackDamage = this.getApproximateAttributeWith(newItemStack, Attributes.ATTACK_DAMAGE, slot)) != (oldAttackDamage = this.getApproximateAttributeWith(currentItemStack, Attributes.ATTACK_DAMAGE, slot))) {
|
|
return newAttackDamage > oldAttackDamage;
|
|
}
|
|
return this.canReplaceEqualItem(newItemStack, currentItemStack);
|
|
}
|
|
|
|
private double getApproximateAttributeWith(ItemStack itemStack, Holder<Attribute> attribute, EquipmentSlot slot) {
|
|
double baseValue = this.getAttributes().hasAttribute(attribute) ? this.getAttributeBaseValue(attribute) : 0.0;
|
|
ItemAttributeModifiers attributeModifiers = itemStack.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY);
|
|
return attributeModifiers.compute(baseValue, slot);
|
|
}
|
|
|
|
public boolean canReplaceEqualItem(ItemStack newItemStack, ItemStack currentItemStack) {
|
|
int currentDamageValue;
|
|
Set<Object2IntMap.Entry<Holder<Enchantment>>> currentEnchantments = currentItemStack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).entrySet();
|
|
Set<Object2IntMap.Entry<Holder<Enchantment>>> newEnchantments = newItemStack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).entrySet();
|
|
if (newEnchantments.size() != currentEnchantments.size()) {
|
|
return newEnchantments.size() > currentEnchantments.size();
|
|
}
|
|
int newDamageValue = newItemStack.getDamageValue();
|
|
if (newDamageValue != (currentDamageValue = currentItemStack.getDamageValue())) {
|
|
return newDamageValue < currentDamageValue;
|
|
}
|
|
return newItemStack.has(DataComponents.CUSTOM_NAME) && !currentItemStack.has(DataComponents.CUSTOM_NAME);
|
|
}
|
|
|
|
public boolean canHoldItem(ItemStack itemStack) {
|
|
return true;
|
|
}
|
|
|
|
public boolean wantsToPickUp(ServerLevel level, ItemStack itemStack) {
|
|
return this.canHoldItem(itemStack);
|
|
}
|
|
|
|
public @Nullable TagKey<Item> getPreferredWeaponType() {
|
|
return null;
|
|
}
|
|
|
|
public boolean removeWhenFarAway(double distSqr) {
|
|
return true;
|
|
}
|
|
|
|
public boolean requiresCustomPersistence() {
|
|
return this.isPassenger();
|
|
}
|
|
|
|
@Override
|
|
public void checkDespawn() {
|
|
if (this.level().getDifficulty() == Difficulty.PEACEFUL && !this.getType().isAllowedInPeaceful()) {
|
|
this.discard();
|
|
return;
|
|
}
|
|
if (this.isPersistenceRequired() || this.requiresCustomPersistence()) {
|
|
this.noActionTime = 0;
|
|
return;
|
|
}
|
|
Player player = this.level().getNearestPlayer(this, -1.0);
|
|
if (player != null) {
|
|
int instantDespawnDistance;
|
|
int despawnDistanceSqr;
|
|
double distSqr = player.distanceToSqr(this);
|
|
if (distSqr > (double)(despawnDistanceSqr = (instantDespawnDistance = this.getType().getCategory().getDespawnDistance()) * instantDespawnDistance) && this.removeWhenFarAway(distSqr)) {
|
|
this.discard();
|
|
}
|
|
int noDespawnDistance = this.getType().getCategory().getNoDespawnDistance();
|
|
int noDespawnDistanceSqr = noDespawnDistance * noDespawnDistance;
|
|
if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && distSqr > (double)noDespawnDistanceSqr && this.removeWhenFarAway(distSqr)) {
|
|
this.discard();
|
|
} else if (distSqr < (double)noDespawnDistanceSqr) {
|
|
this.noActionTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected final void serverAiStep() {
|
|
++this.noActionTime;
|
|
ProfilerFiller profiler = Profiler.get();
|
|
profiler.push("sensing");
|
|
this.sensing.tick();
|
|
profiler.pop();
|
|
int idBasedTickCount = this.tickCount + this.getId();
|
|
if (idBasedTickCount % 2 == 0 || this.tickCount <= 1) {
|
|
profiler.push("targetSelector");
|
|
this.targetSelector.tick();
|
|
profiler.pop();
|
|
profiler.push("goalSelector");
|
|
this.goalSelector.tick();
|
|
profiler.pop();
|
|
} else {
|
|
profiler.push("targetSelector");
|
|
this.targetSelector.tickRunningGoals(false);
|
|
profiler.pop();
|
|
profiler.push("goalSelector");
|
|
this.goalSelector.tickRunningGoals(false);
|
|
profiler.pop();
|
|
}
|
|
profiler.push("navigation");
|
|
this.navigation.tick();
|
|
profiler.pop();
|
|
profiler.push("mob tick");
|
|
this.customServerAiStep((ServerLevel)this.level());
|
|
profiler.pop();
|
|
profiler.push("controls");
|
|
profiler.push("move");
|
|
this.moveControl.tick();
|
|
profiler.popPush("look");
|
|
this.lookControl.tick();
|
|
profiler.popPush("jump");
|
|
this.jumpControl.tick();
|
|
profiler.pop();
|
|
profiler.pop();
|
|
}
|
|
|
|
protected void customServerAiStep(ServerLevel level) {
|
|
}
|
|
|
|
public int getMaxHeadXRot() {
|
|
return 40;
|
|
}
|
|
|
|
public int getMaxHeadYRot() {
|
|
return 75;
|
|
}
|
|
|
|
protected void clampHeadRotationToBody() {
|
|
float limit = this.getMaxHeadYRot();
|
|
float headYRot = this.getYHeadRot();
|
|
float delta = Mth.wrapDegrees(this.yBodyRot - headYRot);
|
|
float targetDelta = Mth.clamp(Mth.wrapDegrees(this.yBodyRot - headYRot), -limit, limit);
|
|
float newHeadYRot = headYRot + delta - targetDelta;
|
|
this.setYHeadRot(newHeadYRot);
|
|
}
|
|
|
|
public int getHeadRotSpeed() {
|
|
return 10;
|
|
}
|
|
|
|
public void lookAt(Entity entity, float yMax, float xMax) {
|
|
double yd;
|
|
double xd = entity.getX() - this.getX();
|
|
double zd = entity.getZ() - this.getZ();
|
|
if (entity instanceof LivingEntity) {
|
|
LivingEntity mob = (LivingEntity)entity;
|
|
yd = mob.getEyeY() - this.getEyeY();
|
|
} else {
|
|
yd = (entity.getBoundingBox().minY + entity.getBoundingBox().maxY) / 2.0 - this.getEyeY();
|
|
}
|
|
double sd = Math.sqrt(xd * xd + zd * zd);
|
|
float yRotD = (float)(Mth.atan2(zd, xd) * 57.2957763671875) - 90.0f;
|
|
float xRotD = (float)(-(Mth.atan2(yd, sd) * 57.2957763671875));
|
|
this.setXRot(this.rotlerp(this.getXRot(), xRotD, xMax));
|
|
this.setYRot(this.rotlerp(this.getYRot(), yRotD, yMax));
|
|
}
|
|
|
|
private float rotlerp(float a, float b, float max) {
|
|
float diff = Mth.wrapDegrees(b - a);
|
|
if (diff > max) {
|
|
diff = max;
|
|
}
|
|
if (diff < -max) {
|
|
diff = -max;
|
|
}
|
|
return a + diff;
|
|
}
|
|
|
|
public static boolean checkMobSpawnRules(EntityType<? extends Mob> type, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
|
|
BlockPos below = pos.below();
|
|
return EntitySpawnReason.isSpawner(spawnReason) || level.getBlockState(below).isValidSpawn(level, below, type);
|
|
}
|
|
|
|
public boolean checkSpawnRules(LevelAccessor level, EntitySpawnReason spawnReason) {
|
|
return true;
|
|
}
|
|
|
|
public boolean checkSpawnObstruction(LevelReader level) {
|
|
return !level.containsAnyLiquid(this.getBoundingBox()) && level.isUnobstructed(this);
|
|
}
|
|
|
|
public int getMaxSpawnClusterSize() {
|
|
return 4;
|
|
}
|
|
|
|
public boolean isMaxGroupSizeReached(int groupSize) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int getMaxFallDistance() {
|
|
if (this.getTarget() == null) {
|
|
return this.getComfortableFallDistance(0.0f);
|
|
}
|
|
int sacrifice = (int)(this.getHealth() - this.getMaxHealth() * 0.33f);
|
|
if ((sacrifice -= (3 - this.level().getDifficulty().getId()) * 4) < 0) {
|
|
sacrifice = 0;
|
|
}
|
|
return this.getComfortableFallDistance(sacrifice);
|
|
}
|
|
|
|
public ItemStack getBodyArmorItem() {
|
|
return this.getItemBySlot(EquipmentSlot.BODY);
|
|
}
|
|
|
|
public boolean isSaddled() {
|
|
return this.hasValidEquippableItemForSlot(EquipmentSlot.SADDLE);
|
|
}
|
|
|
|
public boolean isWearingBodyArmor() {
|
|
return this.hasValidEquippableItemForSlot(EquipmentSlot.BODY);
|
|
}
|
|
|
|
private boolean hasValidEquippableItemForSlot(EquipmentSlot slot) {
|
|
return this.hasItemInSlot(slot) && this.isEquippableInSlot(this.getItemBySlot(slot), slot);
|
|
}
|
|
|
|
public void setBodyArmorItem(ItemStack item) {
|
|
this.setItemSlotAndDropWhenKilled(EquipmentSlot.BODY, item);
|
|
}
|
|
|
|
public Container createEquipmentSlotContainer(final EquipmentSlot slot) {
|
|
return new ContainerSingleItem(){
|
|
|
|
@Override
|
|
public ItemStack getTheItem() {
|
|
return Mob.this.getItemBySlot(slot);
|
|
}
|
|
|
|
@Override
|
|
public void setTheItem(ItemStack itemStack) {
|
|
Mob.this.setItemSlot(slot, itemStack);
|
|
if (!itemStack.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 source, boolean killedByPlayer) {
|
|
super.dropCustomDeathLoot(level, source, killedByPlayer);
|
|
for (EquipmentSlot slot : EquipmentSlot.VALUES) {
|
|
ItemStack itemStack = this.getItemBySlot(slot);
|
|
float dropChance = this.dropChances.byEquipment(slot);
|
|
if (dropChance == 0.0f) continue;
|
|
boolean preserve = this.dropChances.isPreserved(slot);
|
|
Object object = source.getEntity();
|
|
if (object instanceof LivingEntity) {
|
|
LivingEntity livingSource = (LivingEntity)object;
|
|
object = this.level();
|
|
if (object instanceof ServerLevel) {
|
|
ServerLevel serverLevel = (ServerLevel)object;
|
|
dropChance = EnchantmentHelper.processEquipmentDropChance(serverLevel, livingSource, source, dropChance);
|
|
}
|
|
}
|
|
if (itemStack.isEmpty() || EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || !killedByPlayer && !preserve || !(this.random.nextFloat() < dropChance)) continue;
|
|
if (!preserve && 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(slot, ItemStack.EMPTY);
|
|
}
|
|
}
|
|
|
|
public DropChances getDropChances() {
|
|
return this.dropChances;
|
|
}
|
|
|
|
public void dropPreservedEquipment(ServerLevel level) {
|
|
this.dropPreservedEquipment(level, stack -> true);
|
|
}
|
|
|
|
public Set<EquipmentSlot> dropPreservedEquipment(ServerLevel level, Predicate<ItemStack> shouldDrop) {
|
|
HashSet<EquipmentSlot> slotsPreventedFromDropping = new HashSet<EquipmentSlot>();
|
|
for (EquipmentSlot slot : EquipmentSlot.VALUES) {
|
|
ItemStack itemStack = this.getItemBySlot(slot);
|
|
if (itemStack.isEmpty()) continue;
|
|
if (!shouldDrop.test(itemStack)) {
|
|
slotsPreventedFromDropping.add(slot);
|
|
continue;
|
|
}
|
|
if (!this.dropChances.isPreserved(slot)) continue;
|
|
this.setItemSlot(slot, ItemStack.EMPTY);
|
|
this.spawnAtLocation(level, itemStack);
|
|
}
|
|
return slotsPreventedFromDropping;
|
|
}
|
|
|
|
private LootParams createEquipmentParams(ServerLevel serverLevel) {
|
|
return new LootParams.Builder(serverLevel).withParameter(LootContextParams.ORIGIN, this.position()).withParameter(LootContextParams.THIS_ENTITY, this).create(LootContextParamSets.EQUIPMENT);
|
|
}
|
|
|
|
public void equip(EquipmentTable equipment) {
|
|
this.equip(equipment.lootTable(), equipment.slotDropChances());
|
|
}
|
|
|
|
public void equip(ResourceKey<LootTable> lootTable, Map<EquipmentSlot, Float> dropChances) {
|
|
Level level = this.level();
|
|
if (level instanceof ServerLevel) {
|
|
ServerLevel serverLevel = (ServerLevel)level;
|
|
this.equip(lootTable, this.createEquipmentParams(serverLevel), dropChances);
|
|
}
|
|
}
|
|
|
|
protected void populateDefaultEquipmentSlots(RandomSource random, DifficultyInstance difficulty) {
|
|
if (random.nextFloat() < 0.15f * difficulty.getSpecialMultiplier()) {
|
|
int armorType = random.nextInt(3);
|
|
int i = 1;
|
|
while ((float)i <= 3.0f) {
|
|
if (random.nextFloat() < 0.1087f) {
|
|
++armorType;
|
|
}
|
|
++i;
|
|
}
|
|
float partialChance = this.level().getDifficulty() == Difficulty.HARD ? 0.1f : 0.25f;
|
|
boolean first = true;
|
|
for (EquipmentSlot slot : EQUIPMENT_POPULATION_ORDER) {
|
|
Item equip;
|
|
ItemStack itemStack = this.getItemBySlot(slot);
|
|
if (!first && random.nextFloat() < partialChance) break;
|
|
first = false;
|
|
if (!itemStack.isEmpty() || (equip = Mob.getEquipmentForSlot(slot, armorType)) == null) continue;
|
|
this.setItemSlot(slot, new ItemStack(equip));
|
|
}
|
|
}
|
|
}
|
|
|
|
public static @Nullable Item getEquipmentForSlot(EquipmentSlot slot, int type) {
|
|
switch (slot) {
|
|
case HEAD: {
|
|
if (type == 0) {
|
|
return Items.LEATHER_HELMET;
|
|
}
|
|
if (type == 1) {
|
|
return Items.COPPER_HELMET;
|
|
}
|
|
if (type == 2) {
|
|
return Items.GOLDEN_HELMET;
|
|
}
|
|
if (type == 3) {
|
|
return Items.CHAINMAIL_HELMET;
|
|
}
|
|
if (type == 4) {
|
|
return Items.IRON_HELMET;
|
|
}
|
|
if (type == 5) {
|
|
return Items.DIAMOND_HELMET;
|
|
}
|
|
}
|
|
case CHEST: {
|
|
if (type == 0) {
|
|
return Items.LEATHER_CHESTPLATE;
|
|
}
|
|
if (type == 1) {
|
|
return Items.COPPER_CHESTPLATE;
|
|
}
|
|
if (type == 2) {
|
|
return Items.GOLDEN_CHESTPLATE;
|
|
}
|
|
if (type == 3) {
|
|
return Items.CHAINMAIL_CHESTPLATE;
|
|
}
|
|
if (type == 4) {
|
|
return Items.IRON_CHESTPLATE;
|
|
}
|
|
if (type == 5) {
|
|
return Items.DIAMOND_CHESTPLATE;
|
|
}
|
|
}
|
|
case LEGS: {
|
|
if (type == 0) {
|
|
return Items.LEATHER_LEGGINGS;
|
|
}
|
|
if (type == 1) {
|
|
return Items.COPPER_LEGGINGS;
|
|
}
|
|
if (type == 2) {
|
|
return Items.GOLDEN_LEGGINGS;
|
|
}
|
|
if (type == 3) {
|
|
return Items.CHAINMAIL_LEGGINGS;
|
|
}
|
|
if (type == 4) {
|
|
return Items.IRON_LEGGINGS;
|
|
}
|
|
if (type == 5) {
|
|
return Items.DIAMOND_LEGGINGS;
|
|
}
|
|
}
|
|
case FEET: {
|
|
if (type == 0) {
|
|
return Items.LEATHER_BOOTS;
|
|
}
|
|
if (type == 1) {
|
|
return Items.COPPER_BOOTS;
|
|
}
|
|
if (type == 2) {
|
|
return Items.GOLDEN_BOOTS;
|
|
}
|
|
if (type == 3) {
|
|
return Items.CHAINMAIL_BOOTS;
|
|
}
|
|
if (type == 4) {
|
|
return Items.IRON_BOOTS;
|
|
}
|
|
if (type != 5) break;
|
|
return Items.DIAMOND_BOOTS;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected void populateDefaultEquipmentEnchantments(ServerLevelAccessor level, RandomSource random, DifficultyInstance localDifficulty) {
|
|
this.enchantSpawnedWeapon(level, random, localDifficulty);
|
|
for (EquipmentSlot slot : EquipmentSlot.VALUES) {
|
|
if (slot.getType() != EquipmentSlot.Type.HUMANOID_ARMOR) continue;
|
|
this.enchantSpawnedArmor(level, random, slot, localDifficulty);
|
|
}
|
|
}
|
|
|
|
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 chance, DifficultyInstance difficulty) {
|
|
ItemStack itemStack = this.getItemBySlot(slot);
|
|
if (!itemStack.isEmpty() && random.nextFloat() < chance * difficulty.getSpecialMultiplier()) {
|
|
EnchantmentHelper.enchantItemFromProvider(itemStack, level.registryAccess(), VanillaEnchantmentProviders.MOB_SPAWN_EQUIPMENT, difficulty, random);
|
|
this.setItemSlot(slot, itemStack);
|
|
}
|
|
}
|
|
|
|
public @Nullable SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData groupData) {
|
|
RandomSource random = level.getRandom();
|
|
AttributeInstance followRange = Objects.requireNonNull(this.getAttribute(Attributes.FOLLOW_RANGE));
|
|
if (!followRange.hasModifier(RANDOM_SPAWN_BONUS_ID)) {
|
|
followRange.addPermanentModifier(new AttributeModifier(RANDOM_SPAWN_BONUS_ID, random.triangle(0.0, 0.11485000000000001), AttributeModifier.Operation.ADD_MULTIPLIED_BASE));
|
|
}
|
|
this.setLeftHanded(random.nextFloat() < 0.05f);
|
|
return groupData;
|
|
}
|
|
|
|
public void setPersistenceRequired() {
|
|
this.persistenceRequired = true;
|
|
}
|
|
|
|
@Override
|
|
public void setDropChance(EquipmentSlot slot, float percent) {
|
|
this.dropChances = this.dropChances.withEquipmentChance(slot, percent);
|
|
}
|
|
|
|
@Override
|
|
public boolean canPickUpLoot() {
|
|
return this.canPickUpLoot;
|
|
}
|
|
|
|
public void setCanPickUpLoot(boolean canPickUpLoot) {
|
|
this.canPickUpLoot = canPickUpLoot;
|
|
}
|
|
|
|
@Override
|
|
protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
|
|
return this.canPickUpLoot();
|
|
}
|
|
|
|
public boolean isPersistenceRequired() {
|
|
return this.persistenceRequired;
|
|
}
|
|
|
|
@Override
|
|
public InteractionResult interact(Player player, InteractionHand hand) {
|
|
if (!this.isAlive()) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
InteractionResult interactionResult = this.checkAndHandleImportantInteractions(player, hand);
|
|
if (interactionResult.consumesAction()) {
|
|
this.gameEvent(GameEvent.ENTITY_INTERACT, player);
|
|
return interactionResult;
|
|
}
|
|
InteractionResult superReaction = super.interact(player, hand);
|
|
if (superReaction != InteractionResult.PASS) {
|
|
return superReaction;
|
|
}
|
|
interactionResult = this.mobInteract(player, hand);
|
|
if (interactionResult.consumesAction()) {
|
|
this.gameEvent(GameEvent.ENTITY_INTERACT, player);
|
|
return interactionResult;
|
|
}
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
private InteractionResult checkAndHandleImportantInteractions(Player player, InteractionHand hand) {
|
|
InteractionResult nameTagInteractionResult;
|
|
ItemStack itemStack = player.getItemInHand(hand);
|
|
if (itemStack.is(Items.NAME_TAG) && (nameTagInteractionResult = itemStack.interactLivingEntity(player, this, hand)).consumesAction()) {
|
|
return nameTagInteractionResult;
|
|
}
|
|
Item item = itemStack.getItem();
|
|
if (item instanceof SpawnEggItem) {
|
|
SpawnEggItem egg = (SpawnEggItem)item;
|
|
if (this.level() instanceof ServerLevel) {
|
|
Optional<Mob> offspring = egg.spawnOffspringFromSpawnEgg(player, this, this.getType(), (ServerLevel)this.level(), this.position(), itemStack);
|
|
offspring.ifPresent(mob -> this.onOffspringSpawnedFromEgg(player, (Mob)mob));
|
|
if (offspring.isEmpty()) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
}
|
|
return InteractionResult.SUCCESS_SERVER;
|
|
}
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
protected void onOffspringSpawnedFromEgg(Player spawner, Mob offspring) {
|
|
}
|
|
|
|
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
protected void usePlayerItem(Player player, InteractionHand hand, ItemStack itemStack) {
|
|
int beforeUseCount = itemStack.getCount();
|
|
UseRemainder useRemainder = itemStack.get(DataComponents.USE_REMAINDER);
|
|
itemStack.consume(1, player);
|
|
if (useRemainder != null) {
|
|
ItemStack newHandStack = useRemainder.convertIntoRemainder(itemStack, beforeUseCount, player.hasInfiniteMaterials(), player::handleExtraItemsCreatedOnUse);
|
|
player.setItemInHand(hand, newHandStack);
|
|
}
|
|
}
|
|
|
|
public boolean isWithinHome() {
|
|
return this.isWithinHome(this.blockPosition());
|
|
}
|
|
|
|
public boolean isWithinHome(BlockPos pos) {
|
|
if (this.homeRadius == -1) {
|
|
return true;
|
|
}
|
|
return this.homePosition.distSqr(pos) < (double)(this.homeRadius * this.homeRadius);
|
|
}
|
|
|
|
public boolean isWithinHome(Vec3 pos) {
|
|
if (this.homeRadius == -1) {
|
|
return true;
|
|
}
|
|
return this.homePosition.distToCenterSqr(pos) < (double)(this.homeRadius * this.homeRadius);
|
|
}
|
|
|
|
public void setHomeTo(BlockPos newCenter, int radius) {
|
|
this.homePosition = newCenter;
|
|
this.homeRadius = radius;
|
|
}
|
|
|
|
public BlockPos getHomePosition() {
|
|
return this.homePosition;
|
|
}
|
|
|
|
public int getHomeRadius() {
|
|
return this.homeRadius;
|
|
}
|
|
|
|
public void clearHome() {
|
|
this.homeRadius = -1;
|
|
}
|
|
|
|
public boolean hasHome() {
|
|
return this.homeRadius != -1;
|
|
}
|
|
|
|
public <T extends Mob> @Nullable T convertTo(EntityType<T> entityType, ConversionParams params, EntitySpawnReason spawnReason, ConversionParams.AfterConversion<T> afterConversion) {
|
|
if (this.isRemoved()) {
|
|
return null;
|
|
}
|
|
Mob newMob = (Mob)entityType.create(this.level(), spawnReason);
|
|
if (newMob == null) {
|
|
return null;
|
|
}
|
|
params.type().convert(this, newMob, params);
|
|
afterConversion.finalizeConversion(newMob);
|
|
Level level = this.level();
|
|
if (level instanceof ServerLevel) {
|
|
ServerLevel serverLevel = (ServerLevel)level;
|
|
serverLevel.addFreshEntity(newMob);
|
|
}
|
|
if (params.type().shouldDiscardAfterConversion()) {
|
|
this.discard();
|
|
}
|
|
return (T)newMob;
|
|
}
|
|
|
|
public <T extends Mob> @Nullable T convertTo(EntityType<T> entityType, ConversionParams params, ConversionParams.AfterConversion<T> afterConversion) {
|
|
return this.convertTo(entityType, params, EntitySpawnReason.CONVERSION, afterConversion);
|
|
}
|
|
|
|
@Override
|
|
public @Nullable Leashable.LeashData getLeashData() {
|
|
return this.leashData;
|
|
}
|
|
|
|
private void resetAngularLeashMomentum() {
|
|
if (this.leashData != null) {
|
|
this.leashData.angularMomentum = 0.0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setLeashData(@Nullable Leashable.LeashData leashData) {
|
|
this.leashData = leashData;
|
|
}
|
|
|
|
@Override
|
|
public void onLeashRemoved() {
|
|
if (this.getLeashData() == null) {
|
|
this.clearHome();
|
|
}
|
|
}
|
|
|
|
@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 entity, boolean force, boolean sendEventAndTriggers) {
|
|
boolean result = super.startRiding(entity, force, sendEventAndTriggers);
|
|
if (result && this.isLeashed()) {
|
|
this.dropLeash();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean isEffectiveAi() {
|
|
return super.isEffectiveAi() && !this.isNoAi();
|
|
}
|
|
|
|
public void setNoAi(boolean flag) {
|
|
byte val = this.entityData.get(DATA_MOB_FLAGS_ID);
|
|
this.entityData.set(DATA_MOB_FLAGS_ID, flag ? (byte)(val | 1) : (byte)(val & 0xFFFFFFFE));
|
|
}
|
|
|
|
public void setLeftHanded(boolean flag) {
|
|
byte val = this.entityData.get(DATA_MOB_FLAGS_ID);
|
|
this.entityData.set(DATA_MOB_FLAGS_ID, flag ? (byte)(val | 2) : (byte)(val & 0xFFFFFFFD));
|
|
}
|
|
|
|
public void setAggressive(boolean flag) {
|
|
byte val = this.entityData.get(DATA_MOB_FLAGS_ID);
|
|
this.entityData.set(DATA_MOB_FLAGS_ID, flag ? (byte)(val | 4) : (byte)(val & 0xFFFFFFFB));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public void setBaby(boolean baby) {
|
|
}
|
|
|
|
@Override
|
|
public HumanoidArm getMainArm() {
|
|
return this.isLeftHanded() ? HumanoidArm.LEFT : HumanoidArm.RIGHT;
|
|
}
|
|
|
|
public boolean isWithinMeleeAttackRange(LivingEntity target) {
|
|
return this.getAttackBoundingBox().intersects(target.getHitbox());
|
|
}
|
|
|
|
protected AABB getAttackBoundingBox() {
|
|
AABB aabb;
|
|
Entity vehicle = this.getVehicle();
|
|
if (vehicle != null) {
|
|
AABB mountAabb = vehicle.getBoundingBox();
|
|
AABB ownAabb = this.getBoundingBox();
|
|
aabb = new AABB(Math.min(ownAabb.minX, mountAabb.minX), ownAabb.minY, Math.min(ownAabb.minZ, mountAabb.minZ), Math.max(ownAabb.maxX, mountAabb.maxX), ownAabb.maxY, Math.max(ownAabb.maxZ, mountAabb.maxZ));
|
|
} else {
|
|
aabb = this.getBoundingBox();
|
|
}
|
|
return aabb.inflate(DEFAULT_ATTACK_REACH, 0.0, DEFAULT_ATTACK_REACH);
|
|
}
|
|
|
|
@Override
|
|
public boolean doHurtTarget(ServerLevel level, Entity target) {
|
|
float dmg = (float)this.getAttributeValue(Attributes.ATTACK_DAMAGE);
|
|
ItemStack weaponItem = this.getWeaponItem();
|
|
DamageSource damageSource = weaponItem.getDamageSource(this, () -> this.damageSources().mobAttack(this));
|
|
dmg = EnchantmentHelper.modifyDamage(level, weaponItem, target, damageSource, dmg);
|
|
dmg += weaponItem.getItem().getAttackDamageBonus(target, dmg, damageSource);
|
|
Vec3 oldMovement = target.getDeltaMovement();
|
|
boolean wasHurt = target.hurtServer(level, damageSource, dmg);
|
|
if (wasHurt) {
|
|
this.causeExtraKnockback(target, this.getKnockback(target, damageSource), oldMovement);
|
|
if (target instanceof LivingEntity) {
|
|
LivingEntity livingTarget = (LivingEntity)target;
|
|
weaponItem.hurtEnemy(livingTarget, this);
|
|
}
|
|
EnchantmentHelper.doPostAttackEffects(level, target, damageSource);
|
|
this.setLastHurtMob(target);
|
|
this.playAttackSound();
|
|
}
|
|
this.lungeForwardMaybe();
|
|
return wasHurt;
|
|
}
|
|
|
|
@Override
|
|
protected void jumpInLiquid(TagKey<Fluid> type) {
|
|
if (this.getNavigation().canFloat()) {
|
|
super.jumpInLiquid(type);
|
|
} 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> predicate) {
|
|
this.goalSelector.removeAllGoals(predicate);
|
|
}
|
|
|
|
@Override
|
|
protected void removeAfterChangingDimensions() {
|
|
super.removeAfterChangingDimensions();
|
|
for (EquipmentSlot slot : EquipmentSlot.VALUES) {
|
|
ItemStack itemStack = this.getItemBySlot(slot);
|
|
if (itemStack.isEmpty()) continue;
|
|
itemStack.setCount(0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public @Nullable ItemStack getPickResult() {
|
|
SpawnEggItem egg = SpawnEggItem.byId(this.getType());
|
|
if (egg == null) {
|
|
return null;
|
|
}
|
|
return new ItemStack(egg);
|
|
}
|
|
|
|
@Override
|
|
protected void onAttributeUpdated(Holder<Attribute> attribute) {
|
|
super.onAttributeUpdated(attribute);
|
|
if (attribute.is(Attributes.FOLLOW_RANGE) || attribute.is(Attributes.TEMPT_RANGE)) {
|
|
this.getNavigation().updatePathfinderMaxVisitedNodes();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void registerDebugValues(ServerLevel level, DebugValueSource.Registration registration) {
|
|
registration.register(DebugSubscriptions.ENTITY_PATHS, () -> {
|
|
Path path = this.getNavigation().getPath();
|
|
if (path != null && path.debugData() != null) {
|
|
return new DebugPathInfo(path.copy(), this.getNavigation().getMaxDistanceToWaypoint());
|
|
}
|
|
return null;
|
|
});
|
|
registration.register(DebugSubscriptions.GOAL_SELECTORS, () -> {
|
|
Set<WrappedGoal> availableGoals = this.goalSelector.getAvailableGoals();
|
|
ArrayList<DebugGoalInfo.DebugGoal> goalInfo = new ArrayList<DebugGoalInfo.DebugGoal>(availableGoals.size());
|
|
availableGoals.forEach(goal -> goalInfo.add(new DebugGoalInfo.DebugGoal(goal.getPriority(), goal.isRunning(), goal.getGoal().getClass().getSimpleName())));
|
|
return new DebugGoalInfo(goalInfo);
|
|
});
|
|
if (!this.brain.isBrainDead()) {
|
|
registration.register(DebugSubscriptions.BRAINS, () -> DebugBrainDump.takeBrainDump(level, this));
|
|
}
|
|
}
|
|
|
|
public float chargeSpeedModifier() {
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|