/* * Decompiled with CFR 0.152. * * Could not load the following classes: * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.monster; import java.util.EnumSet; import java.util.List; import java.util.Optional; 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.Identifier; 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.EntityReference; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.NeutralMob; import net.minecraft.world.entity.PathfinderMob; 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.monster.Endermite; import net.minecraft.world.entity.monster.Monster; 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.Level; import net.minecraft.world.level.LevelReader; 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.gamerules.GameRules; 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.jspecify.annotations.Nullable; public class EnderMan extends Monster implements NeutralMob { private static final Identifier SPEED_MODIFIER_ATTACKING_ID = Identifier.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> DATA_CARRY_STATE = SynchedEntityData.defineId(EnderMan.class, EntityDataSerializers.OPTIONAL_BLOCK_STATE); private static final EntityDataAccessor DATA_CREEPY = SynchedEntityData.defineId(EnderMan.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor 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 long persistentAngerEndTime; private @Nullable EntityReference persistentAngerTarget; public EnderMan(EntityType type, Level level) { super((EntityType)type, level); this.setPathfindingMalus(PathType.WATER, -1.0f); } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); this.goalSelector.addGoal(1, new EndermanFreezeWhenLookedAt(this)); this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal((PathfinderMob)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 EndermanLeaveBlockGoal(this)); this.goalSelector.addGoal(11, new EndermanTakeBlockGoal(this)); this.targetSelector.addGoal(1, new EndermanLookForPlayerGoal(this, this::isAngryAt)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal((Mob)this, Endermite.class, true, false)); this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal(this, false)); } @Override public float getWalkTargetValue(BlockPos pos, LevelReader level) { return 0.0f; } 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 target) { super.setTarget(target); AttributeInstance movementSpeed = this.getAttribute(Attributes.MOVEMENT_SPEED); if (target == null) { this.targetChangeTime = 0; this.entityData.set(DATA_CREEPY, false); this.entityData.set(DATA_STARED_AT, false); movementSpeed.removeModifier(SPEED_MODIFIER_ATTACKING_ID); } else { this.targetChangeTime = this.tickCount; this.entityData.set(DATA_CREEPY, true); if (!movementSpeed.hasModifier(SPEED_MODIFIER_ATTACKING_ID)) { movementSpeed.addTransientModifier(SPEED_MODIFIER_ATTACKING); } } } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(DATA_CARRY_STATE, Optional.empty()); entityData.define(DATA_CREEPY, false); entityData.define(DATA_STARED_AT, false); } @Override public void startPersistentAngerTimer() { this.setTimeToRemainAngry(PERSISTENT_ANGER_TIME.sample(this.random)); } @Override public void setPersistentAngerEndTime(long endTime) { this.persistentAngerEndTime = endTime; } @Override public long getPersistentAngerEndTime() { return this.persistentAngerEndTime; } @Override public void setPersistentAngerTarget(@Nullable EntityReference persistentAngerTarget) { this.persistentAngerTarget = persistentAngerTarget; } @Override public @Nullable EntityReference 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 accessor) { if (DATA_CREEPY.equals(accessor) && this.hasBeenStaredAt() && this.level().isClientSide()) { this.playStareSound(); } super.onSyncedDataUpdated(accessor); } @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(input.read("carriedBlockState", BlockState.CODEC).filter(blockState -> !blockState.isAir()).orElse(null)); this.readPersistentAngerSaveData(this.level(), input); } private boolean isBeingStaredBy(Player player) { if (!LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM.test(player)) { return false; } return this.isLookingAtMe(player, 0.025, true, false, 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) { float br; if (level.isBrightOutside() && this.tickCount >= this.targetChangeTime + 600 && (br = this.getLightLevelDependentMagicValue()) > 0.5f && level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0f < (br - 0.4f) * 2.0f) { this.setTarget(null); this.teleport(); } super.customServerAiStep(level); } protected boolean teleport() { if (this.level().isClientSide() || !this.isAlive()) { return false; } double xx = this.getX() + (this.random.nextDouble() - 0.5) * 64.0; double yy = this.getY() + (double)(this.random.nextInt(64) - 32); double zz = this.getZ() + (this.random.nextDouble() - 0.5) * 64.0; return this.teleport(xx, yy, zz); } private boolean teleportTowards(Entity entity) { Vec3 dir = new Vec3(this.getX() - entity.getX(), this.getY(0.5) - entity.getEyeY(), this.getZ() - entity.getZ()); dir = dir.normalize(); double d = 16.0; double xx = this.getX() + (this.random.nextDouble() - 0.5) * 8.0 - dir.x * 16.0; double yy = this.getY() + (double)(this.random.nextInt(16) - 8) - dir.y * 16.0; double zz = this.getZ() + (this.random.nextDouble() - 0.5) * 8.0 - dir.z * 16.0; return this.teleport(xx, yy, zz); } private boolean teleport(double x, double y, double z) { BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(x, y, z); while (pos.getY() > this.level().getMinY() && !this.level().getBlockState(pos).blocksMotion()) { pos.move(Direction.DOWN); } BlockState blockState = this.level().getBlockState(pos); boolean couldStandOn = blockState.blocksMotion(); boolean isWet = blockState.getFluidState().is(FluidTags.WATER); if (!couldStandOn || isWet) { return false; } Vec3 oldPos = this.position(); boolean result = this.randomTeleport(x, y, z, true); if (result) { this.level().gameEvent(GameEvent.TELEPORT, oldPos, 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 result; } @Override protected SoundEvent getAmbientSound() { return this.isCreepy() ? SoundEvents.ENDERMAN_SCREAM : SoundEvents.ENDERMAN_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.ENDERMAN_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.ENDERMAN_DEATH; } @Override protected void dropCustomDeathLoot(ServerLevel level, DamageSource source, boolean killedByPlayer) { super.dropCustomDeathLoot(level, source, killedByPlayer); BlockState carryingBlock = this.getCarriedBlock(); if (carryingBlock != null) { ItemStack fakeTool = new ItemStack(Items.DIAMOND_AXE); EnchantmentHelper.enchantItemFromProvider(fakeTool, level.registryAccess(), VanillaEnchantmentProviders.ENDERMAN_LOOT_DROP, level.getCurrentDifficultyAt(this.blockPosition()), this.getRandom()); LootParams.Builder params = new LootParams.Builder((ServerLevel)this.level()).withParameter(LootContextParams.ORIGIN, this.position()).withParameter(LootContextParams.TOOL, fakeTool).withOptionalParameter(LootContextParams.THIS_ENTITY, this); List blockDrops = carryingBlock.getDrops(params); for (ItemStack itemStack : blockDrops) { this.spawnAtLocation(level, itemStack); } } } public void setCarriedBlock(@Nullable BlockState carryingBlock) { this.entityData.set(DATA_CARRY_STATE, Optional.ofNullable(carryingBlock)); } public @Nullable BlockState getCarriedBlock() { return this.entityData.get(DATA_CARRY_STATE).orElse(null); } @Override public boolean hurtServer(ServerLevel level, DamageSource source, float damage) { AbstractThrownPotion potion; AbstractThrownPotion thrownPotion; if (this.isInvulnerableTo(level, source)) { return false; } Entity entity = source.getDirectEntity(); AbstractThrownPotion abstractThrownPotion = thrownPotion = entity instanceof AbstractThrownPotion ? (potion = (AbstractThrownPotion)entity) : null; if (source.is(DamageTypeTags.IS_PROJECTILE) || thrownPotion != null) { boolean hurtWithCleanWater = thrownPotion != null && this.hurtWithCleanWater(level, source, thrownPotion, damage); for (int i = 0; i < 64; ++i) { if (!this.teleport()) continue; return true; } return hurtWithCleanWater; } boolean result = super.hurtServer(level, source, damage); if (!(source.getEntity() instanceof LivingEntity) && this.random.nextInt(10) != 0) { this.teleport(); } return result; } private boolean hurtWithCleanWater(ServerLevel level, DamageSource source, AbstractThrownPotion thrownPotion, float damage) { ItemStack potionItemStack = thrownPotion.getItem(); PotionContents potionContents = potionItemStack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY); if (potionContents.is(Potions.WATER)) { return super.hurtServer(level, source, damage); } return 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; } private static class EndermanFreezeWhenLookedAt extends Goal { private final EnderMan enderman; private @Nullable 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(); LivingEntity livingEntity = this.target; if (!(livingEntity instanceof Player)) { return false; } Player playerTarget = (Player)livingEntity; double dist = this.target.distanceToSqr(this.enderman); if (dist > 256.0) { return false; } return this.enderman.isBeingStaredBy(playerTarget); } @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()); } } private 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; } if (!EndermanLeaveBlockGoal.getServerLevel(this.enderman).getGameRules().get(GameRules.MOB_GRIEFING).booleanValue()) { return false; } return this.enderman.getRandom().nextInt(EndermanLeaveBlockGoal.reducedTickDelay(2000)) == 0; } @Override public void tick() { RandomSource random = this.enderman.getRandom(); Level level = this.enderman.level(); int xt = Mth.floor(this.enderman.getX() - 1.0 + random.nextDouble() * 2.0); int yt = Mth.floor(this.enderman.getY() + random.nextDouble() * 2.0); int zt = Mth.floor(this.enderman.getZ() - 1.0 + random.nextDouble() * 2.0); BlockPos pos = new BlockPos(xt, yt, zt); BlockState targetState = level.getBlockState(pos); BlockPos below = pos.below(); BlockState belowState = level.getBlockState(below); BlockState carried = this.enderman.getCarriedBlock(); if (carried == null) { return; } if (this.canPlaceBlock(level, pos, carried = Block.updateFromNeighbourShapes(carried, this.enderman.level(), pos), targetState, belowState, below)) { level.setBlock(pos, carried, 3); level.gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(this.enderman, carried)); this.enderman.setCarriedBlock(null); } } private boolean canPlaceBlock(Level level, BlockPos pos, BlockState carried, BlockState targetState, BlockState belowState, BlockPos below) { return targetState.isAir() && !belowState.isAir() && !belowState.is(Blocks.BEDROCK) && belowState.isCollisionShapeFullBlock(level, below) && carried.canSurvive(level, pos) && level.getEntities(this.enderman, AABB.unitCubeFromLowerCorner(Vec3.atLowerCornerOf(pos))).isEmpty(); } } private 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; } if (!EndermanTakeBlockGoal.getServerLevel(this.enderman).getGameRules().get(GameRules.MOB_GRIEFING).booleanValue()) { return false; } return this.enderman.getRandom().nextInt(EndermanTakeBlockGoal.reducedTickDelay(20)) == 0; } @Override public void tick() { RandomSource random = this.enderman.getRandom(); Level level = this.enderman.level(); int xt = Mth.floor(this.enderman.getX() - 2.0 + random.nextDouble() * 4.0); int yt = Mth.floor(this.enderman.getY() + random.nextDouble() * 3.0); int zt = Mth.floor(this.enderman.getZ() - 2.0 + random.nextDouble() * 4.0); BlockPos pos = new BlockPos(xt, yt, zt); BlockState blockState = level.getBlockState(pos); Vec3 from = new Vec3((double)this.enderman.getBlockX() + 0.5, (double)yt + 0.5, (double)this.enderman.getBlockZ() + 0.5); Vec3 to = new Vec3((double)xt + 0.5, (double)yt + 0.5, (double)zt + 0.5); BlockHitResult result = level.clip(new ClipContext(from, to, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman)); boolean reachable = result.getBlockPos().equals(pos); if (blockState.is(BlockTags.ENDERMAN_HOLDABLE) && reachable) { level.removeBlock(pos, false); level.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(this.enderman, blockState)); this.enderman.setCarriedBlock(blockState.getBlock().defaultBlockState()); } } } private static class EndermanLookForPlayerGoal extends NearestAttackableTargetGoal { private final EnderMan enderman; private @Nullable 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 isAngryAt) { super(enderman, Player.class, 10, false, false, isAngryAt); this.enderman = enderman; this.isAngerInducing = (target, level) -> (enderman.isBeingStaredBy((Player)target) || enderman.isAngryAt(target, level)) && !enderman.hasIndirectPassenger(target); this.startAggroTargetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(this.isAngerInducing); } @Override public boolean canUse() { this.pendingTarget = EndermanLookForPlayerGoal.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, EndermanLookForPlayerGoal.getServerLevel(this.enderman))) { return false; } this.enderman.lookAt(this.pendingTarget, 10.0f, 10.0f); return true; } if (this.target != null) { if (this.enderman.hasIndirectPassenger(this.target)) { return false; } if (this.continueAggroTargetConditions.test(EndermanLookForPlayerGoal.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(); } } } }