584 lines
25 KiB
Java
584 lines
25 KiB
Java
/*
|
|
* 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<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 long persistentAngerEndTime;
|
|
private @Nullable EntityReference<LivingEntity> persistentAngerTarget;
|
|
|
|
public EnderMan(EntityType<? extends EnderMan> type, Level level) {
|
|
super((EntityType<? extends Monster>)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<Endermite>((Mob)this, Endermite.class, true, false));
|
|
this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<EnderMan>(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<LivingEntity> persistentAngerTarget) {
|
|
this.persistentAngerTarget = persistentAngerTarget;
|
|
}
|
|
|
|
@Override
|
|
public @Nullable EntityReference<LivingEntity> 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<ItemStack> 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<Player> {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|