2025-12-19 17:49:13 +01:00

198 lines
6.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package net.Chipperfluff.chipi.entity;
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.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.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 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 && !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);
}
// === Combat memory ===
@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)) {
angryAtPlayer = false;
ticksSinceLastSeen = 0;
return;
}
if (isPlayerProtected(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();
}
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;
}
// === MILKING (FEVER DREAM EDITION) ===
@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));
// ---- base sound ----
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
);
// ---- single delayed echo (10% chance) ----
if (this.random.nextFloat() < 0.10f) {
int delay = 10 + this.random.nextInt(21); // 1030 ticks
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);
}
}