- Reordered and grouped import statements in various files for better readability. - Removed unnecessary imports and cleaned up unused code. - Simplified constructors and methods in several classes to enhance clarity. - Standardized formatting and spacing for consistency throughout the codebase. - Ensured that all necessary imports are included where required, particularly in structure-related classes.
220 lines
6.8 KiB
Java
220 lines
6.8 KiB
Java
package net.Chipperfluff.chipi.entity;
|
|
|
|
import net.minecraft.block.BlockState;
|
|
import net.minecraft.block.SlabBlock;
|
|
import net.minecraft.block.StairsBlock;
|
|
import net.minecraft.entity.Entity;
|
|
import net.minecraft.entity.EntityType;
|
|
import net.minecraft.entity.LivingEntity;
|
|
import net.minecraft.entity.ai.goal.*;
|
|
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.item.ItemStack;
|
|
import net.minecraft.item.Items;
|
|
import net.minecraft.sound.SoundCategory;
|
|
import net.minecraft.state.property.Properties;
|
|
import net.minecraft.util.ActionResult;
|
|
import net.minecraft.util.Hand;
|
|
import net.minecraft.util.math.BlockPos;
|
|
import net.minecraft.world.World;
|
|
|
|
import net.Chipperfluff.chipi.armor.ProtectionAuraHandler;
|
|
import net.Chipperfluff.chipi.item.ModItems;
|
|
import net.Chipperfluff.chipi.sound.ModSounds;
|
|
import net.Chipperfluff.chipi.util.TickScheduler;
|
|
|
|
public class MepEntity extends PathAwareEntity {
|
|
|
|
private static final int FORGET_TARGET_AFTER_TICKS = 100;
|
|
private static final int DESPAWN_DISTANCE = 300;
|
|
|
|
private boolean angryAtPlayer = false;
|
|
private int ticksSinceLastSeen = 0;
|
|
|
|
public MepEntity(EntityType<? extends PathAwareEntity> entityType, World world) {
|
|
super(entityType, world);
|
|
}
|
|
|
|
// === AI ===
|
|
|
|
@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
|
|
&& !isHardIgnored(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);
|
|
}
|
|
|
|
// === Combat memory ===
|
|
|
|
@Override
|
|
public boolean damage(DamageSource source, float amount) {
|
|
if (source.getAttacker() instanceof PlayerEntity) {
|
|
angryAtPlayer = true;
|
|
ticksSinceLastSeen = 0;
|
|
}
|
|
return super.damage(source, amount);
|
|
}
|
|
|
|
// === ATTACK OVERRIDE (AURA IMMUNITY) ===
|
|
|
|
@Override
|
|
public boolean tryAttack(Entity target) {
|
|
if (target instanceof PlayerEntity player) {
|
|
if (ProtectionAuraHandler.hasAura(player)) {
|
|
return false; // chase but never hit
|
|
}
|
|
}
|
|
return super.tryAttack(target);
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
super.tick();
|
|
|
|
// --- DISTANCE DESPAWN ONLY ---
|
|
if (!this.getWorld().isClient && isTooFarFromAllPlayers()) {
|
|
this.discard();
|
|
return;
|
|
}
|
|
|
|
LivingEntity target = this.getTarget();
|
|
|
|
if (!(target instanceof PlayerEntity player)) {
|
|
angryAtPlayer = false;
|
|
ticksSinceLastSeen = 0;
|
|
return;
|
|
}
|
|
|
|
if (isHardIgnored(player)) {
|
|
clearTarget();
|
|
return;
|
|
}
|
|
|
|
if (this.canSee(player)) {
|
|
ticksSinceLastSeen = 0;
|
|
angryAtPlayer = true;
|
|
this.getNavigation().startMovingTo(player, 1.2D);
|
|
} else {
|
|
ticksSinceLastSeen++;
|
|
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();
|
|
}
|
|
|
|
// === DISTANCE CHECK ===
|
|
|
|
private boolean isTooFarFromAllPlayers() {
|
|
double maxSq = DESPAWN_DISTANCE * DESPAWN_DISTANCE;
|
|
|
|
for (PlayerEntity player : this.getWorld().getPlayers()) {
|
|
if (this.squaredDistanceTo(player) <= maxSq) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// === HARD IGNORE RULES ===
|
|
|
|
private static boolean isHardIgnored(PlayerEntity player) {
|
|
if (player.getZ() < 18) return true;
|
|
return isOnProtectedBlock(player);
|
|
}
|
|
|
|
private static boolean isOnProtectedBlock(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;
|
|
}
|
|
|
|
// === MILKING ===
|
|
|
|
@Override
|
|
public ActionResult interactMob(PlayerEntity player, Hand hand) {
|
|
ItemStack stack = player.getStackInHand(hand);
|
|
|
|
if (stack.isOf(Items.BUCKET) && !this.isBaby()) {
|
|
if (!player.getWorld().isClient) {
|
|
|
|
stack.decrement(1);
|
|
player.giveItemStack(new ItemStack(ModItems.MEP_MILK));
|
|
|
|
float basePitch = 0.3f + this.random.nextFloat() * 1.9f;
|
|
float baseVolume = 0.9f + this.random.nextFloat() * 0.6f;
|
|
|
|
this.getWorld().playSound(
|
|
null,
|
|
this.getBlockPos(),
|
|
ModSounds.MEP_MILK,
|
|
SoundCategory.NEUTRAL,
|
|
baseVolume,
|
|
basePitch
|
|
);
|
|
|
|
if (this.random.nextFloat() < 0.10f) {
|
|
int delay = 10 + this.random.nextInt(21);
|
|
|
|
float echoPitch = basePitch * 0.5f;
|
|
float echoVolume = baseVolume * 0.5f;
|
|
|
|
TickScheduler.schedule(delay, () -> {
|
|
if (!this.isAlive()) return;
|
|
|
|
this.getWorld().playSound(
|
|
null,
|
|
this.getBlockPos(),
|
|
ModSounds.MEP_MILK,
|
|
SoundCategory.NEUTRAL,
|
|
echoVolume,
|
|
echoPitch
|
|
);
|
|
});
|
|
}
|
|
}
|
|
return ActionResult.SUCCESS;
|
|
}
|
|
|
|
return super.interactMob(player, hand);
|
|
}
|
|
}
|