623 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			623 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.entity.monster;
 | |
| 
 | |
| import java.util.EnumSet;
 | |
| import java.util.Optional;
 | |
| import java.util.UUID;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.core.Direction;
 | |
| import net.minecraft.core.component.DataComponents;
 | |
| import net.minecraft.core.particles.ParticleTypes;
 | |
| import net.minecraft.network.syncher.EntityDataAccessor;
 | |
| import net.minecraft.network.syncher.EntityDataSerializers;
 | |
| import net.minecraft.network.syncher.SynchedEntityData;
 | |
| import net.minecraft.resources.ResourceLocation;
 | |
| import net.minecraft.server.level.ServerLevel;
 | |
| import net.minecraft.sounds.SoundEvent;
 | |
| import net.minecraft.sounds.SoundEvents;
 | |
| import net.minecraft.tags.BlockTags;
 | |
| import net.minecraft.tags.DamageTypeTags;
 | |
| import net.minecraft.tags.FluidTags;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.util.RandomSource;
 | |
| import net.minecraft.util.TimeUtil;
 | |
| import net.minecraft.util.valueproviders.UniformInt;
 | |
| import net.minecraft.world.damagesource.DamageSource;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| import net.minecraft.world.entity.EntityType;
 | |
| import net.minecraft.world.entity.LivingEntity;
 | |
| import net.minecraft.world.entity.NeutralMob;
 | |
| 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.goal.FloatGoal;
 | |
| import net.minecraft.world.entity.ai.goal.Goal;
 | |
| import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
 | |
| import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
 | |
| import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
 | |
| import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
 | |
| import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
 | |
| import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
 | |
| import net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal;
 | |
| import net.minecraft.world.entity.ai.targeting.TargetingConditions;
 | |
| import net.minecraft.world.entity.player.Player;
 | |
| import net.minecraft.world.entity.projectile.AbstractThrownPotion;
 | |
| import net.minecraft.world.item.ItemStack;
 | |
| import net.minecraft.world.item.Items;
 | |
| import net.minecraft.world.item.alchemy.PotionContents;
 | |
| import net.minecraft.world.item.alchemy.Potions;
 | |
| import net.minecraft.world.item.enchantment.EnchantmentHelper;
 | |
| import net.minecraft.world.item.enchantment.providers.VanillaEnchantmentProviders;
 | |
| import net.minecraft.world.level.ClipContext;
 | |
| import net.minecraft.world.level.GameRules;
 | |
| import net.minecraft.world.level.Level;
 | |
| import net.minecraft.world.level.block.Block;
 | |
| import net.minecraft.world.level.block.Blocks;
 | |
| import net.minecraft.world.level.block.state.BlockState;
 | |
| import net.minecraft.world.level.gameevent.GameEvent;
 | |
| 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.parameters.LootContextParams;
 | |
| import net.minecraft.world.phys.AABB;
 | |
| import net.minecraft.world.phys.BlockHitResult;
 | |
| import net.minecraft.world.phys.Vec3;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| public class EnderMan extends Monster implements NeutralMob {
 | |
| 	private static final ResourceLocation SPEED_MODIFIER_ATTACKING_ID = ResourceLocation.withDefaultNamespace("attacking");
 | |
| 	private static final AttributeModifier SPEED_MODIFIER_ATTACKING = new AttributeModifier(
 | |
| 		SPEED_MODIFIER_ATTACKING_ID, 0.15F, AttributeModifier.Operation.ADD_VALUE
 | |
| 	);
 | |
| 	private static final int DELAY_BETWEEN_CREEPY_STARE_SOUND = 400;
 | |
| 	private static final int MIN_DEAGGRESSION_TIME = 600;
 | |
| 	private static final EntityDataAccessor<Optional<BlockState>> DATA_CARRY_STATE = SynchedEntityData.defineId(
 | |
| 		EnderMan.class, EntityDataSerializers.OPTIONAL_BLOCK_STATE
 | |
| 	);
 | |
| 	private static final EntityDataAccessor<Boolean> DATA_CREEPY = SynchedEntityData.defineId(EnderMan.class, EntityDataSerializers.BOOLEAN);
 | |
| 	private static final EntityDataAccessor<Boolean> DATA_STARED_AT = SynchedEntityData.defineId(EnderMan.class, EntityDataSerializers.BOOLEAN);
 | |
| 	private int lastStareSound = Integer.MIN_VALUE;
 | |
| 	private int targetChangeTime;
 | |
| 	private static final UniformInt PERSISTENT_ANGER_TIME = TimeUtil.rangeOfSeconds(20, 39);
 | |
| 	private int remainingPersistentAngerTime;
 | |
| 	@Nullable
 | |
| 	private UUID persistentAngerTarget;
 | |
| 
 | |
| 	public EnderMan(EntityType<? extends EnderMan> entityType, Level level) {
 | |
| 		super(entityType, level);
 | |
| 		this.setPathfindingMalus(PathType.WATER, -1.0F);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void registerGoals() {
 | |
| 		this.goalSelector.addGoal(0, new FloatGoal(this));
 | |
| 		this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this));
 | |
| 		this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false));
 | |
| 		this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F));
 | |
| 		this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
 | |
| 		this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
 | |
| 		this.goalSelector.addGoal(10, new EnderMan.EndermanLeaveBlockGoal(this));
 | |
| 		this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this));
 | |
| 		this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt));
 | |
| 		this.targetSelector.addGoal(2, new HurtByTargetGoal(this));
 | |
| 		this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, Endermite.class, true, false));
 | |
| 		this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false));
 | |
| 	}
 | |
| 
 | |
| 	public static AttributeSupplier.Builder createAttributes() {
 | |
| 		return Monster.createMonsterAttributes()
 | |
| 			.add(Attributes.MAX_HEALTH, 40.0)
 | |
| 			.add(Attributes.MOVEMENT_SPEED, 0.3F)
 | |
| 			.add(Attributes.ATTACK_DAMAGE, 7.0)
 | |
| 			.add(Attributes.FOLLOW_RANGE, 64.0)
 | |
| 			.add(Attributes.STEP_HEIGHT, 1.0);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setTarget(@Nullable LivingEntity livingEntity) {
 | |
| 		super.setTarget(livingEntity);
 | |
| 		AttributeInstance attributeInstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
 | |
| 		if (livingEntity == null) {
 | |
| 			this.targetChangeTime = 0;
 | |
| 			this.entityData.set(DATA_CREEPY, false);
 | |
| 			this.entityData.set(DATA_STARED_AT, false);
 | |
| 			attributeInstance.removeModifier(SPEED_MODIFIER_ATTACKING_ID);
 | |
| 		} else {
 | |
| 			this.targetChangeTime = this.tickCount;
 | |
| 			this.entityData.set(DATA_CREEPY, true);
 | |
| 			if (!attributeInstance.hasModifier(SPEED_MODIFIER_ATTACKING_ID)) {
 | |
| 				attributeInstance.addTransientModifier(SPEED_MODIFIER_ATTACKING);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void defineSynchedData(SynchedEntityData.Builder builder) {
 | |
| 		super.defineSynchedData(builder);
 | |
| 		builder.define(DATA_CARRY_STATE, Optional.empty());
 | |
| 		builder.define(DATA_CREEPY, false);
 | |
| 		builder.define(DATA_STARED_AT, false);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void startPersistentAngerTimer() {
 | |
| 		this.setRemainingPersistentAngerTime(PERSISTENT_ANGER_TIME.sample(this.random));
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setRemainingPersistentAngerTime(int remainingPersistentAngerTime) {
 | |
| 		this.remainingPersistentAngerTime = remainingPersistentAngerTime;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public int getRemainingPersistentAngerTime() {
 | |
| 		return this.remainingPersistentAngerTime;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setPersistentAngerTarget(@Nullable UUID persistentAngerTarget) {
 | |
| 		this.persistentAngerTarget = persistentAngerTarget;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public UUID getPersistentAngerTarget() {
 | |
| 		return this.persistentAngerTarget;
 | |
| 	}
 | |
| 
 | |
| 	public void playStareSound() {
 | |
| 		if (this.tickCount >= this.lastStareSound + 400) {
 | |
| 			this.lastStareSound = this.tickCount;
 | |
| 			if (!this.isSilent()) {
 | |
| 				this.level().playLocalSound(this.getX(), this.getEyeY(), this.getZ(), SoundEvents.ENDERMAN_STARE, this.getSoundSource(), 2.5F, 1.0F, false);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
 | |
| 		if (DATA_CREEPY.equals(dataAccessor) && this.hasBeenStaredAt() && this.level().isClientSide) {
 | |
| 			this.playStareSound();
 | |
| 		}
 | |
| 
 | |
| 		super.onSyncedDataUpdated(dataAccessor);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void addAdditionalSaveData(ValueOutput output) {
 | |
| 		super.addAdditionalSaveData(output);
 | |
| 		BlockState blockState = this.getCarriedBlock();
 | |
| 		if (blockState != null) {
 | |
| 			output.store("carriedBlockState", BlockState.CODEC, blockState);
 | |
| 		}
 | |
| 
 | |
| 		this.addPersistentAngerSaveData(output);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void readAdditionalSaveData(ValueInput input) {
 | |
| 		super.readAdditionalSaveData(input);
 | |
| 		this.setCarriedBlock((BlockState)input.read("carriedBlockState", BlockState.CODEC).filter(blockState -> !blockState.isAir()).orElse(null));
 | |
| 		this.readPersistentAngerSaveData(this.level(), input);
 | |
| 	}
 | |
| 
 | |
| 	boolean isBeingStaredBy(Player player) {
 | |
| 		return !LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM.test(player) ? false : this.isLookingAtMe(player, 0.025, true, false, new double[]{this.getEyeY()});
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void aiStep() {
 | |
| 		if (this.level().isClientSide) {
 | |
| 			for (int i = 0; i < 2; i++) {
 | |
| 				this.level()
 | |
| 					.addParticle(
 | |
| 						ParticleTypes.PORTAL,
 | |
| 						this.getRandomX(0.5),
 | |
| 						this.getRandomY() - 0.25,
 | |
| 						this.getRandomZ(0.5),
 | |
| 						(this.random.nextDouble() - 0.5) * 2.0,
 | |
| 						-this.random.nextDouble(),
 | |
| 						(this.random.nextDouble() - 0.5) * 2.0
 | |
| 					);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.jumping = false;
 | |
| 		if (!this.level().isClientSide) {
 | |
| 			this.updatePersistentAnger((ServerLevel)this.level(), true);
 | |
| 		}
 | |
| 
 | |
| 		super.aiStep();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean isSensitiveToWater() {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void customServerAiStep(ServerLevel level) {
 | |
| 		if (level.isBrightOutside() && this.tickCount >= this.targetChangeTime + 600) {
 | |
| 			float f = this.getLightLevelDependentMagicValue();
 | |
| 			if (f > 0.5F && level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F) {
 | |
| 				this.setTarget(null);
 | |
| 				this.teleport();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		super.customServerAiStep(level);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Teleport the enderman to a random nearby position
 | |
| 	 */
 | |
| 	protected boolean teleport() {
 | |
| 		if (!this.level().isClientSide() && this.isAlive()) {
 | |
| 			double d = this.getX() + (this.random.nextDouble() - 0.5) * 64.0;
 | |
| 			double e = this.getY() + (this.random.nextInt(64) - 32);
 | |
| 			double f = this.getZ() + (this.random.nextDouble() - 0.5) * 64.0;
 | |
| 			return this.teleport(d, e, f);
 | |
| 		} else {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Teleport the enderman to another entity
 | |
| 	 */
 | |
| 	boolean teleportTowards(Entity target) {
 | |
| 		Vec3 vec3 = new Vec3(this.getX() - target.getX(), this.getY(0.5) - target.getEyeY(), this.getZ() - target.getZ());
 | |
| 		vec3 = vec3.normalize();
 | |
| 		double d = 16.0;
 | |
| 		double e = this.getX() + (this.random.nextDouble() - 0.5) * 8.0 - vec3.x * 16.0;
 | |
| 		double f = this.getY() + (this.random.nextInt(16) - 8) - vec3.y * 16.0;
 | |
| 		double g = this.getZ() + (this.random.nextDouble() - 0.5) * 8.0 - vec3.z * 16.0;
 | |
| 		return this.teleport(e, f, g);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Teleport the enderman
 | |
| 	 */
 | |
| 	private boolean teleport(double x, double y, double z) {
 | |
| 		BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(x, y, z);
 | |
| 
 | |
| 		while (mutableBlockPos.getY() > this.level().getMinY() && !this.level().getBlockState(mutableBlockPos).blocksMotion()) {
 | |
| 			mutableBlockPos.move(Direction.DOWN);
 | |
| 		}
 | |
| 
 | |
| 		BlockState blockState = this.level().getBlockState(mutableBlockPos);
 | |
| 		boolean bl = blockState.blocksMotion();
 | |
| 		boolean bl2 = blockState.getFluidState().is(FluidTags.WATER);
 | |
| 		if (bl && !bl2) {
 | |
| 			Vec3 vec3 = this.position();
 | |
| 			boolean bl3 = this.randomTeleport(x, y, z, true);
 | |
| 			if (bl3) {
 | |
| 				this.level().gameEvent(GameEvent.TELEPORT, vec3, GameEvent.Context.of(this));
 | |
| 				if (!this.isSilent()) {
 | |
| 					this.level().playSound(null, this.xo, this.yo, this.zo, SoundEvents.ENDERMAN_TELEPORT, this.getSoundSource(), 1.0F, 1.0F);
 | |
| 					this.playSound(SoundEvents.ENDERMAN_TELEPORT, 1.0F, 1.0F);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return bl3;
 | |
| 		} else {
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected SoundEvent getAmbientSound() {
 | |
| 		return this.isCreepy() ? SoundEvents.ENDERMAN_SCREAM : SoundEvents.ENDERMAN_AMBIENT;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected SoundEvent getHurtSound(DamageSource damageSource) {
 | |
| 		return SoundEvents.ENDERMAN_HURT;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected SoundEvent getDeathSound() {
 | |
| 		return SoundEvents.ENDERMAN_DEATH;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void dropCustomDeathLoot(ServerLevel level, DamageSource damageSource, boolean recentlyHit) {
 | |
| 		super.dropCustomDeathLoot(level, damageSource, recentlyHit);
 | |
| 		BlockState blockState = this.getCarriedBlock();
 | |
| 		if (blockState != null) {
 | |
| 			ItemStack itemStack = new ItemStack(Items.DIAMOND_AXE);
 | |
| 			EnchantmentHelper.enchantItemFromProvider(
 | |
| 				itemStack, level.registryAccess(), VanillaEnchantmentProviders.ENDERMAN_LOOT_DROP, level.getCurrentDifficultyAt(this.blockPosition()), this.getRandom()
 | |
| 			);
 | |
| 			LootParams.Builder builder = new LootParams.Builder((ServerLevel)this.level())
 | |
| 				.withParameter(LootContextParams.ORIGIN, this.position())
 | |
| 				.withParameter(LootContextParams.TOOL, itemStack)
 | |
| 				.withOptionalParameter(LootContextParams.THIS_ENTITY, this);
 | |
| 
 | |
| 			for (ItemStack itemStack2 : blockState.getDrops(builder)) {
 | |
| 				this.spawnAtLocation(level, itemStack2);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void setCarriedBlock(@Nullable BlockState state) {
 | |
| 		this.entityData.set(DATA_CARRY_STATE, Optional.ofNullable(state));
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public BlockState getCarriedBlock() {
 | |
| 		return (BlockState)this.entityData.get(DATA_CARRY_STATE).orElse(null);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
 | |
| 		if (this.isInvulnerableTo(level, damageSource)) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			AbstractThrownPotion abstractThrownPotion2 = damageSource.getDirectEntity() instanceof AbstractThrownPotion abstractThrownPotion
 | |
| 				? abstractThrownPotion
 | |
| 				: null;
 | |
| 			if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && abstractThrownPotion2 == null) {
 | |
| 				boolean bl = super.hurtServer(level, damageSource, amount);
 | |
| 				if (!(damageSource.getEntity() instanceof LivingEntity) && this.random.nextInt(10) != 0) {
 | |
| 					this.teleport();
 | |
| 				}
 | |
| 
 | |
| 				return bl;
 | |
| 			} else {
 | |
| 				boolean bl = abstractThrownPotion2 != null && this.hurtWithCleanWater(level, damageSource, abstractThrownPotion2, amount);
 | |
| 
 | |
| 				for (int i = 0; i < 64; i++) {
 | |
| 					if (this.teleport()) {
 | |
| 						return true;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				return bl;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private boolean hurtWithCleanWater(ServerLevel level, DamageSource damageSource, AbstractThrownPotion potion, float damageAmount) {
 | |
| 		ItemStack itemStack = potion.getItem();
 | |
| 		PotionContents potionContents = itemStack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
 | |
| 		return potionContents.is(Potions.WATER) ? super.hurtServer(level, damageSource, damageAmount) : false;
 | |
| 	}
 | |
| 
 | |
| 	public boolean isCreepy() {
 | |
| 		return this.entityData.get(DATA_CREEPY);
 | |
| 	}
 | |
| 
 | |
| 	public boolean hasBeenStaredAt() {
 | |
| 		return this.entityData.get(DATA_STARED_AT);
 | |
| 	}
 | |
| 
 | |
| 	public void setBeingStaredAt() {
 | |
| 		this.entityData.set(DATA_STARED_AT, true);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean requiresCustomPersistence() {
 | |
| 		return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
 | |
| 	}
 | |
| 
 | |
| 	static class EndermanFreezeWhenLookedAt extends Goal {
 | |
| 		private final EnderMan enderman;
 | |
| 		@Nullable
 | |
| 		private LivingEntity target;
 | |
| 
 | |
| 		public EndermanFreezeWhenLookedAt(EnderMan enderman) {
 | |
| 			this.enderman = enderman;
 | |
| 			this.setFlags(EnumSet.of(Goal.Flag.JUMP, Goal.Flag.MOVE));
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public boolean canUse() {
 | |
| 			this.target = this.enderman.getTarget();
 | |
| 			if (this.target instanceof Player player) {
 | |
| 				double d = this.target.distanceToSqr(this.enderman);
 | |
| 				return d > 256.0 ? false : this.enderman.isBeingStaredBy(player);
 | |
| 			} else {
 | |
| 				return false;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void start() {
 | |
| 			this.enderman.getNavigation().stop();
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void tick() {
 | |
| 			this.enderman.getLookControl().setLookAt(this.target.getX(), this.target.getEyeY(), this.target.getZ());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	static class EndermanLeaveBlockGoal extends Goal {
 | |
| 		private final EnderMan enderman;
 | |
| 
 | |
| 		public EndermanLeaveBlockGoal(EnderMan enderman) {
 | |
| 			this.enderman = enderman;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public boolean canUse() {
 | |
| 			if (this.enderman.getCarriedBlock() == null) {
 | |
| 				return false;
 | |
| 			} else {
 | |
| 				return !getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
 | |
| 					? false
 | |
| 					: this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void tick() {
 | |
| 			RandomSource randomSource = this.enderman.getRandom();
 | |
| 			Level level = this.enderman.level();
 | |
| 			int i = Mth.floor(this.enderman.getX() - 1.0 + randomSource.nextDouble() * 2.0);
 | |
| 			int j = Mth.floor(this.enderman.getY() + randomSource.nextDouble() * 2.0);
 | |
| 			int k = Mth.floor(this.enderman.getZ() - 1.0 + randomSource.nextDouble() * 2.0);
 | |
| 			BlockPos blockPos = new BlockPos(i, j, k);
 | |
| 			BlockState blockState = level.getBlockState(blockPos);
 | |
| 			BlockPos blockPos2 = blockPos.below();
 | |
| 			BlockState blockState2 = level.getBlockState(blockPos2);
 | |
| 			BlockState blockState3 = this.enderman.getCarriedBlock();
 | |
| 			if (blockState3 != null) {
 | |
| 				blockState3 = Block.updateFromNeighbourShapes(blockState3, this.enderman.level(), blockPos);
 | |
| 				if (this.canPlaceBlock(level, blockPos, blockState3, blockState, blockState2, blockPos2)) {
 | |
| 					level.setBlock(blockPos, blockState3, 3);
 | |
| 					level.gameEvent(GameEvent.BLOCK_PLACE, blockPos, GameEvent.Context.of(this.enderman, blockState3));
 | |
| 					this.enderman.setCarriedBlock(null);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		private boolean canPlaceBlock(
 | |
| 			Level level, BlockPos destinationPos, BlockState carriedState, BlockState destinationState, BlockState belowDestinationState, BlockPos belowDestinationPos
 | |
| 		) {
 | |
| 			return destinationState.isAir()
 | |
| 				&& !belowDestinationState.isAir()
 | |
| 				&& !belowDestinationState.is(Blocks.BEDROCK)
 | |
| 				&& belowDestinationState.isCollisionShapeFullBlock(level, belowDestinationPos)
 | |
| 				&& carriedState.canSurvive(level, destinationPos)
 | |
| 				&& level.getEntities(this.enderman, AABB.unitCubeFromLowerCorner(Vec3.atLowerCornerOf(destinationPos))).isEmpty();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	static class EndermanLookForPlayerGoal extends NearestAttackableTargetGoal<Player> {
 | |
| 		private final EnderMan enderman;
 | |
| 		/**
 | |
| 		 * The player
 | |
| 		 */
 | |
| 		@Nullable
 | |
| 		private Player pendingTarget;
 | |
| 		private int aggroTime;
 | |
| 		private int teleportTime;
 | |
| 		private final TargetingConditions startAggroTargetConditions;
 | |
| 		private final TargetingConditions continueAggroTargetConditions = TargetingConditions.forCombat().ignoreLineOfSight();
 | |
| 		private final TargetingConditions.Selector isAngerInducing;
 | |
| 
 | |
| 		public EndermanLookForPlayerGoal(EnderMan enderman, @Nullable TargetingConditions.Selector selector) {
 | |
| 			super(enderman, Player.class, 10, false, false, selector);
 | |
| 			this.enderman = enderman;
 | |
| 			this.isAngerInducing = (livingEntity, serverLevel) -> (enderman.isBeingStaredBy((Player)livingEntity) || enderman.isAngryAt(livingEntity, serverLevel))
 | |
| 				&& !enderman.hasIndirectPassenger(livingEntity);
 | |
| 			this.startAggroTargetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(this.isAngerInducing);
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public boolean canUse() {
 | |
| 			this.pendingTarget = getServerLevel(this.enderman).getNearestPlayer(this.startAggroTargetConditions.range(this.getFollowDistance()), this.enderman);
 | |
| 			return this.pendingTarget != null;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void start() {
 | |
| 			this.aggroTime = this.adjustedTickDelay(5);
 | |
| 			this.teleportTime = 0;
 | |
| 			this.enderman.setBeingStaredAt();
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void stop() {
 | |
| 			this.pendingTarget = null;
 | |
| 			super.stop();
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public boolean canContinueToUse() {
 | |
| 			if (this.pendingTarget != null) {
 | |
| 				if (!this.isAngerInducing.test(this.pendingTarget, getServerLevel(this.enderman))) {
 | |
| 					return false;
 | |
| 				} else {
 | |
| 					this.enderman.lookAt(this.pendingTarget, 10.0F, 10.0F);
 | |
| 					return true;
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (this.target != null) {
 | |
| 					if (this.enderman.hasIndirectPassenger(this.target)) {
 | |
| 						return false;
 | |
| 					}
 | |
| 
 | |
| 					if (this.continueAggroTargetConditions.test(getServerLevel(this.enderman), this.enderman, this.target)) {
 | |
| 						return true;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				return super.canContinueToUse();
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void tick() {
 | |
| 			if (this.enderman.getTarget() == null) {
 | |
| 				super.setTarget(null);
 | |
| 			}
 | |
| 
 | |
| 			if (this.pendingTarget != null) {
 | |
| 				if (--this.aggroTime <= 0) {
 | |
| 					this.target = this.pendingTarget;
 | |
| 					this.pendingTarget = null;
 | |
| 					super.start();
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (this.target != null && !this.enderman.isPassenger()) {
 | |
| 					if (this.enderman.isBeingStaredBy((Player)this.target)) {
 | |
| 						if (this.target.distanceToSqr(this.enderman) < 16.0) {
 | |
| 							this.enderman.teleport();
 | |
| 						}
 | |
| 
 | |
| 						this.teleportTime = 0;
 | |
| 					} else if (this.target.distanceToSqr(this.enderman) > 256.0
 | |
| 						&& this.teleportTime++ >= this.adjustedTickDelay(30)
 | |
| 						&& this.enderman.teleportTowards(this.target)) {
 | |
| 						this.teleportTime = 0;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				super.tick();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	static class EndermanTakeBlockGoal extends Goal {
 | |
| 		private final EnderMan enderman;
 | |
| 
 | |
| 		public EndermanTakeBlockGoal(EnderMan enderman) {
 | |
| 			this.enderman = enderman;
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public boolean canUse() {
 | |
| 			if (this.enderman.getCarriedBlock() != null) {
 | |
| 				return false;
 | |
| 			} else {
 | |
| 				return !getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
 | |
| 					? false
 | |
| 					: this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		@Override
 | |
| 		public void tick() {
 | |
| 			RandomSource randomSource = this.enderman.getRandom();
 | |
| 			Level level = this.enderman.level();
 | |
| 			int i = Mth.floor(this.enderman.getX() - 2.0 + randomSource.nextDouble() * 4.0);
 | |
| 			int j = Mth.floor(this.enderman.getY() + randomSource.nextDouble() * 3.0);
 | |
| 			int k = Mth.floor(this.enderman.getZ() - 2.0 + randomSource.nextDouble() * 4.0);
 | |
| 			BlockPos blockPos = new BlockPos(i, j, k);
 | |
| 			BlockState blockState = level.getBlockState(blockPos);
 | |
| 			Vec3 vec3 = new Vec3(this.enderman.getBlockX() + 0.5, j + 0.5, this.enderman.getBlockZ() + 0.5);
 | |
| 			Vec3 vec32 = new Vec3(i + 0.5, j + 0.5, k + 0.5);
 | |
| 			BlockHitResult blockHitResult = level.clip(new ClipContext(vec3, vec32, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman));
 | |
| 			boolean bl = blockHitResult.getBlockPos().equals(blockPos);
 | |
| 			if (blockState.is(BlockTags.ENDERMAN_HOLDABLE) && bl) {
 | |
| 				level.removeBlock(blockPos, false);
 | |
| 				level.gameEvent(GameEvent.BLOCK_DESTROY, blockPos, GameEvent.Context.of(this.enderman, blockState));
 | |
| 				this.enderman.setCarriedBlock(blockState.getBlock().defaultBlockState());
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |