660 lines
23 KiB
Java
660 lines
23 KiB
Java
package net.minecraft.world.entity.monster.warden;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.Dynamic;
|
|
import java.util.Collections;
|
|
import java.util.Optional;
|
|
import java.util.function.BiConsumer;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.particles.BlockParticleOption;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
|
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
|
|
import net.minecraft.network.protocol.game.DebugPackets;
|
|
import net.minecraft.network.syncher.EntityDataAccessor;
|
|
import net.minecraft.network.syncher.EntityDataSerializers;
|
|
import net.minecraft.network.syncher.SynchedEntityData;
|
|
import net.minecraft.resources.RegistryOps;
|
|
import net.minecraft.server.level.ServerEntity;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.sounds.SoundEvent;
|
|
import net.minecraft.sounds.SoundEvents;
|
|
import net.minecraft.tags.DamageTypeTags;
|
|
import net.minecraft.tags.GameEventTags;
|
|
import net.minecraft.tags.TagKey;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.util.Unit;
|
|
import net.minecraft.util.profiling.Profiler;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
import net.minecraft.world.DifficultyInstance;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.effect.MobEffectInstance;
|
|
import net.minecraft.world.effect.MobEffectUtil;
|
|
import net.minecraft.world.effect.MobEffects;
|
|
import net.minecraft.world.entity.AnimationState;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntityDimensions;
|
|
import net.minecraft.world.entity.EntitySelector;
|
|
import net.minecraft.world.entity.EntitySpawnReason;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.Pose;
|
|
import net.minecraft.world.entity.SpawnGroupData;
|
|
import net.minecraft.world.entity.ai.Brain;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
|
|
import net.minecraft.world.entity.ai.behavior.warden.SonicBoom;
|
|
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
|
|
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
|
|
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
|
import net.minecraft.world.entity.monster.Monster;
|
|
import net.minecraft.world.entity.monster.warden.Warden.1.1;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.level.Explosion;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.LevelReader;
|
|
import net.minecraft.world.level.ServerLevelAccessor;
|
|
import net.minecraft.world.level.block.RenderShape;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.gameevent.DynamicGameEventListener;
|
|
import net.minecraft.world.level.gameevent.EntityPositionSource;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.gameevent.PositionSource;
|
|
import net.minecraft.world.level.gameevent.GameEvent.Context;
|
|
import net.minecraft.world.level.gameevent.vibrations.VibrationSystem;
|
|
import net.minecraft.world.level.gameevent.vibrations.VibrationSystem.Data;
|
|
import net.minecraft.world.level.gameevent.vibrations.VibrationSystem.Listener;
|
|
import net.minecraft.world.level.gameevent.vibrations.VibrationSystem.Ticker;
|
|
import net.minecraft.world.level.gameevent.vibrations.VibrationSystem.User;
|
|
import net.minecraft.world.level.pathfinder.PathFinder;
|
|
import net.minecraft.world.level.pathfinder.PathType;
|
|
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Contract;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class Warden extends Monster implements VibrationSystem {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int VIBRATION_COOLDOWN_TICKS = 40;
|
|
private static final int TIME_TO_USE_MELEE_UNTIL_SONIC_BOOM = 200;
|
|
private static final int MAX_HEALTH = 500;
|
|
private static final float MOVEMENT_SPEED_WHEN_FIGHTING = 0.3F;
|
|
private static final float KNOCKBACK_RESISTANCE = 1.0F;
|
|
private static final float ATTACK_KNOCKBACK = 1.5F;
|
|
private static final int ATTACK_DAMAGE = 30;
|
|
private static final int FOLLOW_RANGE = 24;
|
|
private static final EntityDataAccessor<Integer> CLIENT_ANGER_LEVEL = SynchedEntityData.defineId(Warden.class, EntityDataSerializers.INT);
|
|
private static final int DARKNESS_DISPLAY_LIMIT = 200;
|
|
private static final int DARKNESS_DURATION = 260;
|
|
private static final int DARKNESS_RADIUS = 20;
|
|
private static final int DARKNESS_INTERVAL = 120;
|
|
private static final int ANGERMANAGEMENT_TICK_DELAY = 20;
|
|
private static final int DEFAULT_ANGER = 35;
|
|
private static final int PROJECTILE_ANGER = 10;
|
|
private static final int ON_HURT_ANGER_BOOST = 20;
|
|
private static final int RECENT_PROJECTILE_TICK_THRESHOLD = 100;
|
|
private static final int TOUCH_COOLDOWN_TICKS = 20;
|
|
private static final int DIGGING_PARTICLES_AMOUNT = 30;
|
|
private static final float DIGGING_PARTICLES_DURATION = 4.5F;
|
|
private static final float DIGGING_PARTICLES_OFFSET = 0.7F;
|
|
private static final int PROJECTILE_ANGER_DISTANCE = 30;
|
|
private int tendrilAnimation;
|
|
private int tendrilAnimationO;
|
|
private int heartAnimation;
|
|
private int heartAnimationO;
|
|
public AnimationState roarAnimationState = new AnimationState();
|
|
public AnimationState sniffAnimationState = new AnimationState();
|
|
public AnimationState emergeAnimationState = new AnimationState();
|
|
public AnimationState diggingAnimationState = new AnimationState();
|
|
public AnimationState attackAnimationState = new AnimationState();
|
|
public AnimationState sonicBoomAnimationState = new AnimationState();
|
|
private final DynamicGameEventListener<Listener> dynamicGameEventListener;
|
|
private final User vibrationUser;
|
|
private Data vibrationData;
|
|
AngerManagement angerManagement = new AngerManagement(this::canTargetEntity, Collections.emptyList());
|
|
|
|
public Warden(EntityType<? extends Monster> entityType, Level level) {
|
|
super(entityType, level);
|
|
this.vibrationUser = new Warden.VibrationUser();
|
|
this.vibrationData = new Data();
|
|
this.dynamicGameEventListener = new DynamicGameEventListener<>(new Listener(this));
|
|
this.xpReward = 5;
|
|
this.getNavigation().setCanFloat(true);
|
|
this.setPathfindingMalus(PathType.UNPASSABLE_RAIL, 0.0F);
|
|
this.setPathfindingMalus(PathType.DAMAGE_OTHER, 8.0F);
|
|
this.setPathfindingMalus(PathType.POWDER_SNOW, 8.0F);
|
|
this.setPathfindingMalus(PathType.LAVA, 8.0F);
|
|
this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F);
|
|
this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F);
|
|
}
|
|
|
|
@Override
|
|
public Packet<ClientGamePacketListener> getAddEntityPacket(ServerEntity entity) {
|
|
return new ClientboundAddEntityPacket(this, entity, this.hasPose(Pose.EMERGING) ? 1 : 0);
|
|
}
|
|
|
|
@Override
|
|
public void recreateFromPacket(ClientboundAddEntityPacket packet) {
|
|
super.recreateFromPacket(packet);
|
|
if (packet.getData() == 1) {
|
|
this.setPose(Pose.EMERGING);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean checkSpawnObstruction(LevelReader level) {
|
|
return super.checkSpawnObstruction(level) && level.noCollision(this, this.getType().getDimensions().makeBoundingBox(this.position()));
|
|
}
|
|
|
|
@Override
|
|
public float getWalkTargetValue(BlockPos pos, LevelReader level) {
|
|
return 0.0F;
|
|
}
|
|
|
|
@Override
|
|
public boolean isInvulnerableTo(ServerLevel serverLevel, DamageSource damageSource) {
|
|
return this.isDiggingOrEmerging() && !damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY) ? true : super.isInvulnerableTo(serverLevel, damageSource);
|
|
}
|
|
|
|
boolean isDiggingOrEmerging() {
|
|
return this.hasPose(Pose.DIGGING) || this.hasPose(Pose.EMERGING);
|
|
}
|
|
|
|
@Override
|
|
protected boolean canRide(Entity vehicle) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean canDisableShield() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected float nextStep() {
|
|
return this.moveDist + 0.55F;
|
|
}
|
|
|
|
public static Builder createAttributes() {
|
|
return Monster.createMonsterAttributes()
|
|
.add(Attributes.MAX_HEALTH, 500.0)
|
|
.add(Attributes.MOVEMENT_SPEED, 0.3F)
|
|
.add(Attributes.KNOCKBACK_RESISTANCE, 1.0)
|
|
.add(Attributes.ATTACK_KNOCKBACK, 1.5)
|
|
.add(Attributes.ATTACK_DAMAGE, 30.0)
|
|
.add(Attributes.FOLLOW_RANGE, 24.0);
|
|
}
|
|
|
|
@Override
|
|
public boolean dampensVibrations() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected float getSoundVolume() {
|
|
return 4.0F;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
protected SoundEvent getAmbientSound() {
|
|
return !this.hasPose(Pose.ROARING) && !this.isDiggingOrEmerging() ? this.getAngerLevel().getAmbientSound() : null;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
|
return SoundEvents.WARDEN_HURT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getDeathSound() {
|
|
return SoundEvents.WARDEN_DEATH;
|
|
}
|
|
|
|
@Override
|
|
protected void playStepSound(BlockPos pos, BlockState state) {
|
|
this.playSound(SoundEvents.WARDEN_STEP, 10.0F, 1.0F);
|
|
}
|
|
|
|
@Override
|
|
public boolean doHurtTarget(ServerLevel serverLevel, Entity entity) {
|
|
serverLevel.broadcastEntityEvent(this, (byte)4);
|
|
this.playSound(SoundEvents.WARDEN_ATTACK_IMPACT, 10.0F, this.getVoicePitch());
|
|
SonicBoom.setCooldown(this, 40);
|
|
return super.doHurtTarget(serverLevel, entity);
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(CLIENT_ANGER_LEVEL, 0);
|
|
}
|
|
|
|
public int getClientAngerLevel() {
|
|
return this.entityData.get(CLIENT_ANGER_LEVEL);
|
|
}
|
|
|
|
private void syncClientAngerLevel() {
|
|
this.entityData.set(CLIENT_ANGER_LEVEL, this.getActiveAnger());
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
Ticker.tick(serverLevel, this.vibrationData, this.vibrationUser);
|
|
if (this.isPersistenceRequired() || this.requiresCustomPersistence()) {
|
|
WardenAi.setDigCooldown(this);
|
|
}
|
|
}
|
|
|
|
super.tick();
|
|
if (this.level().isClientSide()) {
|
|
if (this.tickCount % this.getHeartBeatDelay() == 0) {
|
|
this.heartAnimation = 10;
|
|
if (!this.isSilent()) {
|
|
this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.WARDEN_HEARTBEAT, this.getSoundSource(), 5.0F, this.getVoicePitch(), false);
|
|
}
|
|
}
|
|
|
|
this.tendrilAnimationO = this.tendrilAnimation;
|
|
if (this.tendrilAnimation > 0) {
|
|
this.tendrilAnimation--;
|
|
}
|
|
|
|
this.heartAnimationO = this.heartAnimation;
|
|
if (this.heartAnimation > 0) {
|
|
this.heartAnimation--;
|
|
}
|
|
|
|
switch (this.getPose()) {
|
|
case EMERGING:
|
|
this.clientDiggingParticles(this.emergeAnimationState);
|
|
break;
|
|
case DIGGING:
|
|
this.clientDiggingParticles(this.diggingAnimationState);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void customServerAiStep(ServerLevel serverLevel) {
|
|
ProfilerFiller profilerFiller = Profiler.get();
|
|
profilerFiller.push("wardenBrain");
|
|
this.getBrain().tick(serverLevel, this);
|
|
profilerFiller.pop();
|
|
super.customServerAiStep(serverLevel);
|
|
if ((this.tickCount + this.getId()) % 120 == 0) {
|
|
applyDarknessAround(serverLevel, this.position(), this, 20);
|
|
}
|
|
|
|
if (this.tickCount % 20 == 0) {
|
|
this.angerManagement.tick(serverLevel, this::canTargetEntity);
|
|
this.syncClientAngerLevel();
|
|
}
|
|
|
|
WardenAi.updateActivity(this);
|
|
}
|
|
|
|
@Override
|
|
public void handleEntityEvent(byte id) {
|
|
if (id == 4) {
|
|
this.roarAnimationState.stop();
|
|
this.attackAnimationState.start(this.tickCount);
|
|
} else if (id == 61) {
|
|
this.tendrilAnimation = 10;
|
|
} else if (id == 62) {
|
|
this.sonicBoomAnimationState.start(this.tickCount);
|
|
} else {
|
|
super.handleEntityEvent(id);
|
|
}
|
|
}
|
|
|
|
private int getHeartBeatDelay() {
|
|
float f = (float)this.getClientAngerLevel() / AngerLevel.ANGRY.getMinimumAnger();
|
|
return 40 - Mth.floor(Mth.clamp(f, 0.0F, 1.0F) * 30.0F);
|
|
}
|
|
|
|
public float getTendrilAnimation(float partialTick) {
|
|
return Mth.lerp(partialTick, (float)this.tendrilAnimationO, (float)this.tendrilAnimation) / 10.0F;
|
|
}
|
|
|
|
public float getHeartAnimation(float partialTick) {
|
|
return Mth.lerp(partialTick, (float)this.heartAnimationO, (float)this.heartAnimation) / 10.0F;
|
|
}
|
|
|
|
private void clientDiggingParticles(AnimationState animationState) {
|
|
if ((float)animationState.getTimeInMillis(this.tickCount) < 4500.0F) {
|
|
RandomSource randomSource = this.getRandom();
|
|
BlockState blockState = this.getBlockStateOn();
|
|
if (blockState.getRenderShape() != RenderShape.INVISIBLE) {
|
|
for (int i = 0; i < 30; i++) {
|
|
double d = this.getX() + Mth.randomBetween(randomSource, -0.7F, 0.7F);
|
|
double e = this.getY();
|
|
double f = this.getZ() + Mth.randomBetween(randomSource, -0.7F, 0.7F);
|
|
this.level().addParticle(new BlockParticleOption(ParticleTypes.BLOCK, blockState), d, e, f, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
if (DATA_POSE.equals(dataAccessor)) {
|
|
switch (this.getPose()) {
|
|
case EMERGING:
|
|
this.emergeAnimationState.start(this.tickCount);
|
|
break;
|
|
case DIGGING:
|
|
this.diggingAnimationState.start(this.tickCount);
|
|
break;
|
|
case ROARING:
|
|
this.roarAnimationState.start(this.tickCount);
|
|
break;
|
|
case SNIFFING:
|
|
this.sniffAnimationState.start(this.tickCount);
|
|
}
|
|
}
|
|
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
}
|
|
|
|
@Override
|
|
public boolean ignoreExplosion(Explosion explosion) {
|
|
return this.isDiggingOrEmerging();
|
|
}
|
|
|
|
@Override
|
|
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
|
|
return WardenAi.makeBrain(this, dynamic);
|
|
}
|
|
|
|
@Override
|
|
public Brain<Warden> getBrain() {
|
|
return (Brain<Warden>)super.getBrain();
|
|
}
|
|
|
|
@Override
|
|
protected void sendDebugPackets() {
|
|
super.sendDebugPackets();
|
|
DebugPackets.sendEntityBrain(this);
|
|
}
|
|
|
|
@Override
|
|
public void updateDynamicGameEventListener(BiConsumer<DynamicGameEventListener<?>, ServerLevel> listenerConsumer) {
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
listenerConsumer.accept(this.dynamicGameEventListener, serverLevel);
|
|
}
|
|
}
|
|
|
|
@Contract("null->false")
|
|
public boolean canTargetEntity(@Nullable Entity entity) {
|
|
return entity instanceof LivingEntity livingEntity
|
|
&& this.level() == entity.level()
|
|
&& EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity)
|
|
&& !this.isAlliedTo(entity)
|
|
&& livingEntity.getType() != EntityType.ARMOR_STAND
|
|
&& livingEntity.getType() != EntityType.WARDEN
|
|
&& !livingEntity.isInvulnerable()
|
|
&& !livingEntity.isDeadOrDying()
|
|
&& this.level().getWorldBorder().isWithinBounds(livingEntity.getBoundingBox());
|
|
}
|
|
|
|
public static void applyDarknessAround(ServerLevel level, Vec3 pos, @Nullable Entity source, int radius) {
|
|
MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false);
|
|
MobEffectUtil.addEffectToPlayersAround(level, source, pos, radius, mobEffectInstance, 200);
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag compound) {
|
|
super.addAdditionalSaveData(compound);
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
AngerManagement.codec(this::canTargetEntity)
|
|
.encodeStart(registryOps, this.angerManagement)
|
|
.resultOrPartial(string -> LOGGER.error("Failed to encode anger state for Warden: '{}'", string))
|
|
.ifPresent(tag -> compound.put("anger", tag));
|
|
Data.CODEC
|
|
.encodeStart(registryOps, this.vibrationData)
|
|
.resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Warden: '{}'", string))
|
|
.ifPresent(tag -> compound.put("listener", tag));
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag compound) {
|
|
super.readAdditionalSaveData(compound);
|
|
RegistryOps<Tag> registryOps = this.registryAccess().createSerializationContext(NbtOps.INSTANCE);
|
|
if (compound.contains("anger")) {
|
|
AngerManagement.codec(this::canTargetEntity)
|
|
.parse(registryOps, compound.get("anger"))
|
|
.resultOrPartial(string -> LOGGER.error("Failed to parse anger state for Warden: '{}'", string))
|
|
.ifPresent(angerManagement -> this.angerManagement = angerManagement);
|
|
this.syncClientAngerLevel();
|
|
}
|
|
|
|
if (compound.contains("listener", 10)) {
|
|
Data.CODEC
|
|
.parse(registryOps, compound.getCompound("listener"))
|
|
.resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Warden: '{}'", string))
|
|
.ifPresent(data -> this.vibrationData = data);
|
|
}
|
|
}
|
|
|
|
private void playListeningSound() {
|
|
if (!this.hasPose(Pose.ROARING)) {
|
|
this.playSound(this.getAngerLevel().getListeningSound(), 10.0F, this.getVoicePitch());
|
|
}
|
|
}
|
|
|
|
public AngerLevel getAngerLevel() {
|
|
return AngerLevel.byAnger(this.getActiveAnger());
|
|
}
|
|
|
|
private int getActiveAnger() {
|
|
return this.angerManagement.getActiveAnger(this.getTarget());
|
|
}
|
|
|
|
public void clearAnger(Entity entity) {
|
|
this.angerManagement.clearAnger(entity);
|
|
}
|
|
|
|
public void increaseAngerAt(@Nullable Entity entity) {
|
|
this.increaseAngerAt(entity, 35, true);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public void increaseAngerAt(@Nullable Entity entity, int offset, boolean playListeningSound) {
|
|
if (!this.isNoAi() && this.canTargetEntity(entity)) {
|
|
WardenAi.setDigCooldown(this);
|
|
boolean bl = !(this.getTarget() instanceof Player);
|
|
int i = this.angerManagement.increaseAnger(entity, offset);
|
|
if (entity instanceof Player && bl && AngerLevel.byAnger(i).isAngry()) {
|
|
this.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET);
|
|
}
|
|
|
|
if (playListeningSound) {
|
|
this.playListeningSound();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Optional<LivingEntity> getEntityAngryAt() {
|
|
return this.getAngerLevel().isAngry() ? this.angerManagement.getActiveEntity() : Optional.empty();
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public LivingEntity getTarget() {
|
|
return this.getTargetFromBrain();
|
|
}
|
|
|
|
@Override
|
|
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
|
|
return false;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public SpawnGroupData finalizeSpawn(
|
|
ServerLevelAccessor serverLevelAccessor, DifficultyInstance difficultyInstance, EntitySpawnReason entitySpawnReason, @Nullable SpawnGroupData spawnGroupData
|
|
) {
|
|
this.getBrain().setMemoryWithExpiry(MemoryModuleType.DIG_COOLDOWN, Unit.INSTANCE, 1200L);
|
|
if (entitySpawnReason == EntitySpawnReason.TRIGGERED) {
|
|
this.setPose(Pose.EMERGING);
|
|
this.getBrain().setMemoryWithExpiry(MemoryModuleType.IS_EMERGING, Unit.INSTANCE, WardenAi.EMERGE_DURATION);
|
|
this.playSound(SoundEvents.WARDEN_AGITATED, 5.0F, 1.0F);
|
|
}
|
|
|
|
return super.finalizeSpawn(serverLevelAccessor, difficultyInstance, entitySpawnReason, spawnGroupData);
|
|
}
|
|
|
|
@Override
|
|
public boolean hurtServer(ServerLevel serverLevel, DamageSource damageSource, float f) {
|
|
boolean bl = super.hurtServer(serverLevel, damageSource, f);
|
|
if (!this.isNoAi() && !this.isDiggingOrEmerging()) {
|
|
Entity entity = damageSource.getEntity();
|
|
this.increaseAngerAt(entity, AngerLevel.ANGRY.getMinimumAnger() + 20, false);
|
|
if (this.brain.getMemory(MemoryModuleType.ATTACK_TARGET).isEmpty()
|
|
&& entity instanceof LivingEntity livingEntity
|
|
&& (damageSource.isDirect() || this.closerThan(livingEntity, 5.0))) {
|
|
this.setAttackTarget(livingEntity);
|
|
}
|
|
}
|
|
|
|
return bl;
|
|
}
|
|
|
|
public void setAttackTarget(LivingEntity attackTarget) {
|
|
this.getBrain().eraseMemory(MemoryModuleType.ROAR_TARGET);
|
|
this.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, attackTarget);
|
|
this.getBrain().eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
|
|
SonicBoom.setCooldown(this, 200);
|
|
}
|
|
|
|
@Override
|
|
public EntityDimensions getDefaultDimensions(Pose pose) {
|
|
EntityDimensions entityDimensions = super.getDefaultDimensions(pose);
|
|
return this.isDiggingOrEmerging() ? EntityDimensions.fixed(entityDimensions.width(), 1.0F) : entityDimensions;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPushable() {
|
|
return !this.isDiggingOrEmerging() && super.isPushable();
|
|
}
|
|
|
|
@Override
|
|
protected void doPush(Entity entity) {
|
|
if (!this.isNoAi() && !this.getBrain().hasMemoryValue(MemoryModuleType.TOUCH_COOLDOWN)) {
|
|
this.getBrain().setMemoryWithExpiry(MemoryModuleType.TOUCH_COOLDOWN, Unit.INSTANCE, 20L);
|
|
this.increaseAngerAt(entity);
|
|
WardenAi.setDisturbanceLocation(this, entity.blockPosition());
|
|
}
|
|
|
|
super.doPush(entity);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public AngerManagement getAngerManagement() {
|
|
return this.angerManagement;
|
|
}
|
|
|
|
@Override
|
|
protected PathNavigation createNavigation(Level level) {
|
|
return new GroundPathNavigation(this, level) {
|
|
@Override
|
|
protected PathFinder createPathFinder(int maxVisitedNodes) {
|
|
this.nodeEvaluator = new WalkNodeEvaluator();
|
|
this.nodeEvaluator.setCanPassDoors(true);
|
|
return new 1(this, this.nodeEvaluator, maxVisitedNodes);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public Data getVibrationData() {
|
|
return this.vibrationData;
|
|
}
|
|
|
|
@Override
|
|
public User getVibrationUser() {
|
|
return this.vibrationUser;
|
|
}
|
|
|
|
class VibrationUser implements User {
|
|
private static final int GAME_EVENT_LISTENER_RANGE = 16;
|
|
private final PositionSource positionSource = new EntityPositionSource(Warden.this, Warden.this.getEyeHeight());
|
|
|
|
@Override
|
|
public int getListenerRadius() {
|
|
return 16;
|
|
}
|
|
|
|
@Override
|
|
public PositionSource getPositionSource() {
|
|
return this.positionSource;
|
|
}
|
|
|
|
@Override
|
|
public TagKey<GameEvent> getListenableEvents() {
|
|
return GameEventTags.WARDEN_CAN_LISTEN;
|
|
}
|
|
|
|
@Override
|
|
public boolean canTriggerAvoidVibration() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean canReceiveVibration(ServerLevel level, BlockPos pos, Holder<GameEvent> gameEvent, Context context) {
|
|
return !Warden.this.isNoAi()
|
|
&& !Warden.this.isDeadOrDying()
|
|
&& !Warden.this.getBrain().hasMemoryValue(MemoryModuleType.VIBRATION_COOLDOWN)
|
|
&& !Warden.this.isDiggingOrEmerging()
|
|
&& level.getWorldBorder().isWithinBounds(pos)
|
|
? !(context.sourceEntity() instanceof LivingEntity livingEntity && !Warden.this.canTargetEntity(livingEntity))
|
|
: false;
|
|
}
|
|
|
|
@Override
|
|
public void onReceiveVibration(
|
|
ServerLevel level, BlockPos pos, Holder<GameEvent> gameEvent, @Nullable Entity entity, @Nullable Entity playerEntity, float distance
|
|
) {
|
|
if (!Warden.this.isDeadOrDying()) {
|
|
Warden.this.brain.setMemoryWithExpiry(MemoryModuleType.VIBRATION_COOLDOWN, Unit.INSTANCE, 40L);
|
|
level.broadcastEntityEvent(Warden.this, (byte)61);
|
|
Warden.this.playSound(SoundEvents.WARDEN_TENDRIL_CLICKS, 5.0F, Warden.this.getVoicePitch());
|
|
BlockPos blockPos = pos;
|
|
if (playerEntity != null) {
|
|
if (Warden.this.closerThan(playerEntity, 30.0)) {
|
|
if (Warden.this.getBrain().hasMemoryValue(MemoryModuleType.RECENT_PROJECTILE)) {
|
|
if (Warden.this.canTargetEntity(playerEntity)) {
|
|
blockPos = playerEntity.blockPosition();
|
|
}
|
|
|
|
Warden.this.increaseAngerAt(playerEntity);
|
|
} else {
|
|
Warden.this.increaseAngerAt(playerEntity, 10, true);
|
|
}
|
|
}
|
|
|
|
Warden.this.getBrain().setMemoryWithExpiry(MemoryModuleType.RECENT_PROJECTILE, Unit.INSTANCE, 100L);
|
|
} else {
|
|
Warden.this.increaseAngerAt(entity);
|
|
}
|
|
|
|
if (!Warden.this.getAngerLevel().isAngry()) {
|
|
Optional<LivingEntity> optional = Warden.this.angerManagement.getActiveEntity();
|
|
if (playerEntity != null || optional.isEmpty() || optional.get() == entity) {
|
|
WardenAi.setDisturbanceLocation(Warden.this, blockPos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|