package net.Chipperfluff.chipi.entity.custom; import net.minecraft.block.BlockState; import net.minecraft.block.SlabBlock; import net.minecraft.block.StairsBlock; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.ai.goal.ActiveTargetGoal; import net.minecraft.entity.ai.goal.LookAroundGoal; import net.minecraft.entity.ai.goal.LookAtEntityGoal; import net.minecraft.entity.ai.goal.MeleeAttackGoal; import net.minecraft.entity.ai.goal.SwimGoal; import net.minecraft.entity.ai.goal.WanderAroundFarGoal; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.mob.PathAwareEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.state.property.Properties; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; public class MepEntity extends PathAwareEntity { private static final int FORGET_TARGET_AFTER_TICKS = 100; private boolean angryAtPlayer = false; private int ticksSinceLastSeen = 0; public MepEntity(EntityType entityType, World world) { super(entityType, world); } @Override protected void initGoals() { this.goalSelector.add(1, new SwimGoal(this)); this.goalSelector.add(2, new MeleeAttackGoal(this, 1.2D, true)); this.goalSelector.add(3, new WanderAroundFarGoal(this, 1.0D)); this.goalSelector.add(4, new LookAtEntityGoal(this, PlayerEntity.class, 8.0F)); this.goalSelector.add(5, new LookAroundGoal(this)); this.targetSelector.add( 1, new ActiveTargetGoal<>( this, PlayerEntity.class, true, target -> target instanceof PlayerEntity player && !isPlayerProtected(player) ) ); } public static DefaultAttributeContainer.Builder createMepAttributes() { return PathAwareEntity.createMobAttributes() .add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0) .add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 4.0) .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25) .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 32.0); } @Override public boolean damage(DamageSource source, float amount) { if (source.getAttacker() instanceof PlayerEntity) { angryAtPlayer = true; ticksSinceLastSeen = 0; } return super.damage(source, amount); } @Override public void tick() { super.tick(); LivingEntity target = this.getTarget(); if (!(target instanceof PlayerEntity player)) { ticksSinceLastSeen = 0; angryAtPlayer = false; return; } // Protected players instantly cancel targeting if (isPlayerProtected(player)) { clearTarget(); return; } if (this.canSee(player)) { ticksSinceLastSeen = 0; angryAtPlayer = true; this.getNavigation().startMovingTo(player, 1.2D); } else { ticksSinceLastSeen++; // Keep chasing for a while even without LOS if (ticksSinceLastSeen <= FORGET_TARGET_AFTER_TICKS) { this.getNavigation().startMovingTo(player, 1.2D); } else { clearTarget(); } } } private void clearTarget() { angryAtPlayer = false; ticksSinceLastSeen = 0; this.setTarget(null); this.getNavigation().stop(); } private static boolean isPlayerProtected(PlayerEntity player) { BlockPos pos = player.getBlockPos(); BlockState state = player.getWorld().getBlockState(pos); if (!state.contains(Properties.WATERLOGGED) || !state.get(Properties.WATERLOGGED)) { return false; } return state.getBlock() instanceof StairsBlock || state.getBlock() instanceof SlabBlock; } // === Despawn prevention === @Override public boolean cannotDespawn() { return true; } @Override public boolean canImmediatelyDespawn(double distanceSquared) { return false; } }