2025-12-19 16:06:12 +01:00

135 lines
4.3 KiB
Java

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<? extends PathAwareEntity> 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;
}
}