package playasmob;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.alchemy.Potions;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.entity.projectile.ThrownPotion;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.NeutralMob;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.util.TimeUtil;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.tags.BlockTags;
import net.minecraft.sounds.SoundSource;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.resources.ResourceKey;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.Direction;
import net.minecraft.core.BlockPos;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.client.renderer.entity.state.EndermanRenderState;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EndermanRenderer;
import net.minecraft.client.gui.GuiGraphics;
import java.util.UUID;
import java.util.List;
import net.minecraft.client.Minecraft;
public class EndermanInfo extends MonsterInfo implements NeutralMob {
public static final Synched carryState = Synched.CarryState;
public static final Synched creepy = Synched.Creepy;
public static final Synched staredAt = Synched.StaredAt;
public static final UniformInt angerTime = TimeUtil.rangeOfSeconds(20, 39);
public int lastStareSound = Integer.MIN_VALUE;
public int targetChangeTime;
public int remainingPersistentAngerTime;
public UUID persistentAngerTarget;
public EndermanInfo(Player player, CompoundTag compound) {
super(EntityType.ENDERMAN, EnderMan.class, player, compound);
}
public EndermanInfo(Player player) {
this(player, null);
}
public EndermanInfo() {
this(null);
}
// Custom Code
@Override
public boolean canAttack(LivingEntity target) {
return super.canAttack(target);
}
@Override
public void setLastHurtByPlayer(Player player) {
super.setLastHurtByPlayer(player);
}
@Override
public void setLastHurtByMob(LivingEntity entity) {
super.setLastHurtByMob(entity);
}
@Override
public LivingEntity getLastHurtByMob() {
return super.getLastHurtByMob();
}
@Override
public void reset() {
super.reset();
this.reset(creepy);
this.reset(staredAt);
this.lastStareSound = Integer.MIN_VALUE;
this.targetChangeTime = 0;
this.remainingPersistentAngerTime = 0;
this.persistentAngerTarget = null;
}
@Override
public EntityRenderer getRenderer() {
return new EndermanRenderer(Utils.getContext());
}
@Override
public EntityRenderState createState() {
return new EndermanRenderState();
}
@Override
public void modifyState(EntityRenderState state) {
super.modifyState(state);
if (state instanceof EndermanRenderState endermanState) {
endermanState.isCreepy = this.isCreepy();
if (!this.isVisuallySwimming())
endermanState.carriedBlock = this.getCarriedBlock();
}
}
@Override
public ResourceKey<Level> getRespawnDimension() {
return switch (this.random.nextInt(0, 3)) {
default -> super.getRespawnDimension();
case 0 -> Level.OVERWORLD;
case 1 -> Level.NETHER;
case 2 -> Level.END;
};
}
@Override
public List<ResourceKey<Biome>> getRespawnBiomes(ResourceKey<Level> dimension) {
if (dimension.equals(Level.NETHER))
return List.of(Biomes.NETHER_WASTES, Biomes.SOUL_SAND_VALLEY, Biomes.WARPED_FOREST);
return super.getRespawnBiomes(dimension);
}
@Override
public float getSpeed(float original) {
if (this.hasTarget())
original += 0.15;
return super.getSpeed(original);
}
@Override
public boolean canJump() {
return false;
}
@Override
public void press(int key, boolean pressed, int time) {
if(this.isSpectator())
return;
if (key == 1 && pressed) {
if (this.findTarget() instanceof LivingEntity target) {
this.tryTeleport(target);
} else {
this.teleportForward();
}
}
if (key == 2 && pressed)
this.tryTeleport(null);
if (key == 3 && pressed) {
BlockHitResult result = this.blockHit(this.getAttributeBaseValue(Attributes.BLOCK_INTERACTION_RANGE));
if (result.getType() == HitResult.Type.BLOCK) {
if (this.getCarriedBlock() instanceof BlockState state) {
this.placeCarriedBlock(result);
} else {
this.pickupBlock(result);
}
}
}
}
@Override
public boolean handUseageBlocked() {
return this.getCarriedBlock() != null;
}
@Override
public List<HudIcon> getIcons(float partialTick) {
if (this.isSpectator())
return super.getIcons(partialTick);
HudIcon teleport = new HudIcon("textures/item/ender_pearl.png").key(Keybinds.Key1).useable(this.canTeleport(false));
HudIcon randomTeleport = new HudIcon("textures/item/chorus_fruit.png").key(Keybinds.Key2).useable(this.canTeleport(true));
List<HudIcon> endermanIcons = List.of(teleport, randomTeleport.offsetX(teleport, true));
if (this.getCarriedOrTargetBlock() instanceof BlockState state && state.is(BlockTags.ENDERMAN_HOLDABLE)) {
boolean holdingState = this.getCarriedBlock() instanceof BlockState;
HudIcon block = new HudIcon() {
@Override
public void renderIcon(GuiGraphics graphics, int x, int y) {
if (state instanceof BlockState blockState)
graphics.renderFakeItem(new ItemStack(blockState.getBlock().asItem()), x, y);
}
};
block.key(Keybinds.Key3).using(holdingState);
endermanIcons = Utils.merge(endermanIcons, List.of(block.offsetX(randomTeleport, true)));
}
return Utils.merge(super.getIcons(partialTick), endermanIcons);
}
public boolean canTeleport(boolean isRandom) {
return this.isAlive() && (this.isCreative() || this.getFoodData().getFoodLevel() >= (isRandom ? 7 : 10));
}
public boolean teleportForward() {
BlockHitResult result = this.level().clip(new ClipContext(this.player.getEyePosition(1f), this.player.getEyePosition(1f).add(this.player.getViewVector(1f).scale(32)), ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player));
EntityDimensions hitbox = this.getDimensions(this.getPose());
Vec3 location = result.getLocation();
Vec3 direction = this.player.getEyePosition(1f).subtract(location).normalize();
Vec3 target = location.add(direction.scale(hitbox.width()));
result = this.level().clip(new ClipContext(target, target.subtract(0, hitbox.height() * 1.5, 0), ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player));
if (result.getType() != HitResult.Type.MISS)
target = result.getLocation();
return this.teleport(target.x, target.y, target.z, false);
}
public boolean teleportTowards(Vec3 direction) {
direction.normalize();
double x = this.getX() + (this.random.nextDouble() - 0.5) * 8 - direction.x * 16;
double y = this.getY() + (double) (this.random.nextInt(16) - 8) - direction.y * 16;
double z = this.getZ() + (this.random.nextDouble() - 0.5) * 8 - direction.z * 16;
return this.teleport(x, y, z, true);
}
public boolean tryTeleport(Entity target) {
if (!this.canTeleport(true))
return false;
for (int attempt = 0; attempt < 64; attempt++) {
if (target == null && this.teleport())
return true;
if (target != null && this.teleportTowards(target))
return true;
}
return false;
}
public BlockState getCarriedOrTargetBlock() {
if (this.getCarriedBlock() instanceof BlockState state)
return state;
BlockHitResult result = this.blockHit(this.getAttributeBaseValue(Attributes.BLOCK_INTERACTION_RANGE));
if (result.getType() == HitResult.Type.BLOCK)
return this.level().getBlockState(result.getBlockPos());
return null;
}
public boolean pickupBlock(BlockHitResult result) {
BlockPos pos = result.getBlockPos();
BlockState state = this.level().getBlockState(pos);
if (!state.is(BlockTags.ENDERMAN_HOLDABLE))
return false;
SoundType soundtype = state.getSoundType(this.level(), pos, this.player);
this.level().playSound(player, pos, state.getSoundType(this.level(), pos, this.player).getBreakSound(), SoundSource.BLOCKS, (soundtype.getVolume() + 1) / 2, soundtype.getPitch() * 0.8f);
if(this.isClient())
return true;
this.level().removeBlock(pos, false);
this.level().gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(this.player, state));
this.setCarriedBlock(state.getBlock().defaultBlockState());
return true;
}
public boolean placeCarriedBlock(BlockHitResult result) {
BlockState newState = this.getCarriedBlock();
BlockPlaceContext context = new BlockPlaceContext(this.player, null, ItemStack.EMPTY, result);
if (newState == null || !newState.canSurvive(this.level(), context.getClickedPos()) || !newState.getBlock().isEnabled(this.level().enabledFeatures()) || !context.canPlace())
return false;
if (this.isServer() && !this.level().setBlock(context.getClickedPos(), newState, 11))
return false;
BlockPos pos = context.getClickedPos();
BlockState oldState = this.level().getBlockState(pos);
SoundType soundtype = oldState.getSoundType(this.level(), pos, this.player);
this.level().playSound(player, pos, newState.getSoundType(this.level(), pos, this.player).getPlaceSound(), SoundSource.BLOCKS, (soundtype.getVolume() + 1) / 2, soundtype.getPitch() * 0.8f);
if (this.isClient())
return true;
this.level().gameEvent(GameEvent.BLOCK_PLACE, pos, GameEvent.Context.of(this.player, oldState));
this.setCarriedBlock(null);
return true;
}
@Override
public Diet getDiet() {
return new Diet();
}
// EnderMan.class Code
@Override
public void setTarget(LivingEntity target) {
if (target == null) {
this.targetChangeTime = 0;
this.set(creepy, false);
this.set(staredAt, false);
} else {
this.targetChangeTime = this.tickCount();
this.set(creepy, true);
}
super.setTarget(target);
}
@Override
public void startPersistentAngerTimer() {
this.setRemainingPersistentAngerTime(angerTime.sample(this.random));
}
@Override
public void setRemainingPersistentAngerTime(int time) {
this.remainingPersistentAngerTime = time;
}
@Override
public int getRemainingPersistentAngerTime() {
return this.remainingPersistentAngerTime;
}
@Override
public void setPersistentAngerTarget(UUID uuid) {
this.persistentAngerTarget = uuid;
}
@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, false);
}
}
@Override
public boolean onSyncedDataUpdated(Object object, EntityDataAccessor<?> data) {
if (creepy.data().equals(data) && this.hasBeenStaredAt() && this.level().isClientSide)
this.playStareSound();
return super.onSyncedDataUpdated(null, data);
}
@Override
public CompoundTag saveData() {
CompoundTag compound = super.saveData();
CompoundTag angerData = new CompoundTag();
this.addPersistentAngerSaveData(angerData);
compound.put("angerData", angerData);
return compound;
}
@Override
public void loadData(CompoundTag compound) {
super.loadData(compound);
if (compound.contains("angerData"))
this.readPersistentAngerSaveData(this.level(), compound.getCompound("angerData"));
}
public boolean isBeingStaredBy(Player player) {
return !LivingEntity.PLAYER_NOT_WEARING_DISGUISE_ITEM_FOR_TARGET.test(player, this.player) ? false : this.isLookingAtMe(player, 0.025, true, false, new double[]{this.getEyeY()});
}
@Override
public boolean aiStep(Object object) {
if (this.isClient() && (!this.isSpectator() || Minecraft.getInstance().player instanceof Player client && client.isSpectator())) {
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);
}
}
if (this.level() instanceof ServerLevel server)
this.updatePersistentAnger(server, true);
return super.aiStep(null);
}
@Override
public boolean isSensitiveToWater(boolean original) {
return true;
}
@Override
public void customServerAiStep(ServerLevel server) {
super.customServerAiStep(server);
if (this.tickCount() % 60 != 0)
return;
float light = this.getLightLevelDependentMagicValue();
if (server.isDay() && light > 0.5f && server.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30 < (light - 0.4f) * 2) {
this.getFoodData().eat(FoodFactory.createFoodData(2, 0f));
} else {
this.getFoodData().eat(FoodFactory.createFoodData(1, 0f));
}
}
public boolean teleport() {
if (this.isServer() && this.isAlive()) {
double x = this.getX() + (this.random.nextDouble() - 0.5) * 64.0;
double y = this.getY() + (double) (this.random.nextInt(64) - 32);
double z = this.getZ() + (this.random.nextDouble() - 0.5) * 64.0;
return this.teleport(x, y, z, true);
} else {
return false;
}
}
public boolean teleportTowards(Entity target) {
return this.teleportTowards(new Vec3(this.getX() - target.getX(), this.getY(0.5) - target.getEyeY(), this.getZ() - target.getZ()));
}
public boolean teleport(double x, double y, double z, boolean random) {
if (!this.canTeleport(random) || this.isClient())
return false;
BlockPos.MutableBlockPos location = new BlockPos.MutableBlockPos(x, y, z);
while (location.getY() > this.level().getMinY() && !this.level().getBlockState(location).blocksMotion()) {
location.move(Direction.DOWN);
}
BlockState state = this.level().getBlockState(location);
if (state.blocksMotion() && !state.getFluidState().is(FluidTags.WATER)) {
net.neoforged.neoforge.event.entity.EntityTeleportEvent.EnderEntity event = net.neoforged.neoforge.event.EventHooks.onEnderTeleport(this.player, x, y, z);
if (event.isCanceled())
return false;
Vec3 originLocation = this.position();
boolean success = random ? this.randomTeleport(event.getTargetX(), event.getTargetY(), event.getTargetZ(), true) : true;
if (!random)
this.teleportTo(event.getTargetX(), event.getTargetY(), event.getTargetZ());
if (success) {
this.resetFallDistance();
this.level().gameEvent(GameEvent.TELEPORT, originLocation, GameEvent.Context.of(this.player));
if (!this.isCreative())
this.getFoodData().eat(FoodFactory.createFoodData(random ? -1 : -2, 0));
if (!this.isSilent()) {
this.level().playSound(null, this.player.xo, this.player.yo, this.player.zo, SoundEvents.ENDERMAN_TELEPORT, this.getSoundSource(), 1, 1);
this.playSound(SoundEvents.ENDERMAN_TELEPORT, 1, 1);
}
}
return success;
} else {
return false;
}
}
@Override
public SoundEvent getAmbientSound() {
return this.isCreepy() ? SoundEvents.ENDERMAN_SCREAM : SoundEvents.ENDERMAN_AMBIENT;
}
@Override
public SoundEvent getHurtSound(SoundEvent original, DamageSource source) {
return SoundEvents.ENDERMAN_HURT;
}
@Override
public SoundEvent getDeathSound(SoundEvent original) {
return SoundEvents.ENDERMAN_DEATH;
}
public void setCarriedBlock(BlockState state) {
this.set(carryState, state);
}
public BlockState getCarriedBlock() {
return this.get(carryState);
}
@Override
public boolean hurtServer(boolean original, ServerLevel server, DamageSource source, float damage) {
if (this.isInvulnerableTo(server, source))
return false;
boolean isPotion = source.getDirectEntity() instanceof ThrownPotion;
if (!source.is(DamageTypeTags.IS_PROJECTILE) && !isPotion) {
boolean shouldHurt = super.hurtServer(original, server, source, damage);
/*if (!(source.getEntity() instanceof LivingEntity) && this.random.nextInt(10) != 0)
this.teleport();*/
return shouldHurt;
}
boolean hurtWater = isPotion && source.getDirectEntity() instanceof ThrownPotion potion && this.hurtWithCleanWater(server, source, potion, damage);
if (this.tryTeleport(null))
return true;
return hurtWater;
}
public boolean hurtWithCleanWater(ServerLevel server, DamageSource source, ThrownPotion potion, float damage) {
PotionContents content = potion.getItem().getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
return content.is(Potions.WATER) ? super.hurtServer(server, source, damage) : false;
}
public boolean isCreepy() {
return this.get(creepy);
}
public boolean hasBeenStaredAt() {
return this.get(staredAt);
}
public void setBeingStaredAt() {
this.set(staredAt, true);
}
}