minecraft-src/net/minecraft/world/entity/animal/HappyGhast.java
2025-09-18 12:37:33 +00:00

653 lines
18 KiB
Java

package net.minecraft.world.entity.animal;
import com.mojang.serialization.Dynamic;
import java.util.function.Predicate;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
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.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.ItemTags;
import net.minecraft.util.Mth;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Leashable;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.Brain.Provider;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder;
import net.minecraft.world.entity.ai.control.BodyRotationControl;
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
import net.minecraft.world.entity.ai.control.LookControl;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal.ForNonPathfinders;
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.monster.Ghast;
import net.minecraft.world.entity.monster.Ghast.GhastMoveControl;
import net.minecraft.world.entity.monster.Ghast.RandomFloatAroundGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biome.Precipitation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class HappyGhast extends Animal {
public static final float BABY_SCALE = 0.2375F;
public static final int WANDER_GROUND_DISTANCE = 16;
public static final int SMALL_RESTRICTION_RADIUS = 32;
public static final int LARGE_RESTRICTION_RADIUS = 64;
public static final int RESTRICTION_RADIUS_BUFFER = 16;
public static final int FAST_HEALING_TICKS = 20;
public static final int SLOW_HEALING_TICKS = 600;
public static final int MAX_PASSANGERS = 4;
private static final int STILL_TIMEOUT_ON_LOAD_GRACE_PERIOD = 60;
private static final int MAX_STILL_TIMEOUT = 10;
public static final float SPEED_MULTIPLIER_WHEN_PANICKING = 2.0F;
public static final Predicate<ItemStack> IS_FOOD = itemStack -> itemStack.is(ItemTags.HAPPY_GHAST_FOOD);
private int leashHolderTime = 0;
private int serverStillTimeout;
private static final EntityDataAccessor<Boolean> IS_LEASH_HOLDER = SynchedEntityData.defineId(HappyGhast.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> STAYS_STILL = SynchedEntityData.defineId(HappyGhast.class, EntityDataSerializers.BOOLEAN);
private static final float MAX_SCALE = 1.0F;
public HappyGhast(EntityType<? extends HappyGhast> entityType, Level level) {
super(entityType, level);
this.moveControl = new GhastMoveControl(this, true, this::isOnStillTimeout);
this.lookControl = new HappyGhast.HappyGhastLookControl();
}
private void setServerStillTimeout(int serverStillTimeout) {
if (this.serverStillTimeout <= 0 && serverStillTimeout > 0 && this.level() instanceof ServerLevel serverLevel) {
this.syncPacketPositionCodec(this.getX(), this.getY(), this.getZ());
serverLevel.getChunkSource().chunkMap.broadcast(this, ClientboundEntityPositionSyncPacket.of(this));
}
this.serverStillTimeout = serverStillTimeout;
this.syncStayStillFlag();
}
private PathNavigation createBabyNavigation(Level level) {
return new HappyGhast.BabyFlyingPathNavigation(this, level);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(3, new HappyGhast.HappyGhastFloatGoal());
this.goalSelector
.addGoal(
4,
new ForNonPathfinders(
this,
1.0,
itemStack -> !this.isWearingBodyArmor() && !this.isBaby() ? itemStack.is(ItemTags.HAPPY_GHAST_TEMPT_ITEMS) : IS_FOOD.test(itemStack),
false,
7.0
)
);
this.goalSelector.addGoal(5, new RandomFloatAroundGoal(this, 16));
}
private void adultGhastSetup() {
this.moveControl = new GhastMoveControl(this, true, this::isOnStillTimeout);
this.lookControl = new HappyGhast.HappyGhastLookControl();
this.navigation = this.createNavigation(this.level());
if (this.level() instanceof ServerLevel serverLevel) {
this.removeAllGoals(goal -> true);
this.registerGoals();
((Brain<HappyGhast>)this.brain).stopAll(serverLevel, this);
this.brain.clearMemories();
}
}
private void babyGhastSetup() {
this.moveControl = new FlyingMoveControl(this, 180, true);
this.lookControl = new LookControl(this);
this.navigation = this.createBabyNavigation(this.level());
this.setServerStillTimeout(0);
this.removeAllGoals(goal -> true);
}
@Override
protected void ageBoundaryReached() {
if (this.isBaby()) {
this.babyGhastSetup();
} else {
this.adultGhastSetup();
}
super.ageBoundaryReached();
}
public static Builder createAttributes() {
return Animal.createAnimalAttributes()
.add(Attributes.MAX_HEALTH, 20.0)
.add(Attributes.TEMPT_RANGE, 16.0)
.add(Attributes.FLYING_SPEED, 0.05)
.add(Attributes.MOVEMENT_SPEED, 0.05)
.add(Attributes.FOLLOW_RANGE, 16.0)
.add(Attributes.CAMERA_DISTANCE, 8.0);
}
@Override
protected float sanitizeScale(float scale) {
return Math.min(scale, 1.0F);
}
@Override
protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
}
@Override
public boolean onClimbable() {
return false;
}
@Override
public void travel(Vec3 travelVector) {
float f = (float)this.getAttributeValue(Attributes.FLYING_SPEED) * 5.0F / 3.0F;
this.travelFlying(travelVector, f, f, f);
}
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader level) {
if (!level.isEmptyBlock(pos)) {
return 0.0F;
} else {
return level.isEmptyBlock(pos.below()) && !level.isEmptyBlock(pos.below(2)) ? 10.0F : 5.0F;
}
}
@Override
public boolean canBreatheUnderwater() {
return this.isBaby() ? true : super.canBreatheUnderwater();
}
@Override
protected boolean shouldStayCloseToLeashHolder() {
return false;
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
}
@Override
public float getVoicePitch() {
return 1.0F;
}
@Override
public SoundSource getSoundSource() {
return SoundSource.NEUTRAL;
}
@Override
public int getAmbientSoundInterval() {
int i = super.getAmbientSoundInterval();
return this.isVehicle() ? i * 6 : i;
}
@Override
protected SoundEvent getAmbientSound() {
return this.isBaby() ? SoundEvents.GHASTLING_AMBIENT : SoundEvents.HAPPY_GHAST_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return this.isBaby() ? SoundEvents.GHASTLING_HURT : SoundEvents.HAPPY_GHAST_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return this.isBaby() ? SoundEvents.GHASTLING_DEATH : SoundEvents.HAPPY_GHAST_DEATH;
}
@Override
public int getMaxSpawnClusterSize() {
return 1;
}
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return EntityType.HAPPY_GHAST.create(level, EntitySpawnReason.BREEDING);
}
@Override
public boolean canFallInLove() {
return false;
}
@Override
public float getAgeScale() {
return this.isBaby() ? 0.2375F : 1.0F;
}
@Override
public boolean isFood(ItemStack stack) {
return IS_FOOD.test(stack);
}
@Override
public boolean canUseSlot(EquipmentSlot slot) {
return slot != EquipmentSlot.BODY ? super.canUseSlot(slot) : this.isAlive() && !this.isBaby();
}
@Override
protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
return slot == EquipmentSlot.BODY;
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
if (this.isBaby()) {
return super.mobInteract(player, hand);
} else {
ItemStack itemStack = player.getItemInHand(hand);
if (!itemStack.isEmpty()) {
InteractionResult interactionResult = itemStack.interactLivingEntity(player, this, hand);
if (interactionResult.consumesAction()) {
return interactionResult;
}
}
if (this.isWearingBodyArmor() && !player.isSecondaryUseActive()) {
this.doPlayerRide(player);
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(player, hand);
}
}
}
private void doPlayerRide(Player player) {
if (!this.level().isClientSide) {
player.startRiding(this);
}
}
@Override
protected void addPassenger(Entity passenger) {
if (!this.isVehicle()) {
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_DOWN, this.getSoundSource(), 1.0F, 1.0F);
}
super.addPassenger(passenger);
if (!this.level().isClientSide) {
if (!this.scanPlayerAboveGhast()) {
this.setServerStillTimeout(0);
} else if (this.serverStillTimeout > 10) {
this.setServerStillTimeout(10);
}
}
}
@Override
protected void removePassenger(Entity passenger) {
super.removePassenger(passenger);
if (!this.level().isClientSide) {
this.setServerStillTimeout(10);
}
if (!this.isVehicle()) {
this.clearHome();
this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.HARNESS_GOGGLES_UP, this.getSoundSource(), 1.0F, 1.0F);
}
}
@Override
protected boolean canAddPassenger(Entity passenger) {
return this.getPassengers().size() < 4;
}
@Nullable
@Override
public LivingEntity getControllingPassenger() {
return (LivingEntity)(this.isWearingBodyArmor() && !this.isOnStillTimeout() && this.getFirstPassenger() instanceof Player player
? player
: super.getControllingPassenger());
}
@Override
protected Vec3 getRiddenInput(Player player, Vec3 travelVector) {
float f = player.xxa;
float g = 0.0F;
float h = 0.0F;
if (player.zza != 0.0F) {
float i = Mth.cos(player.getXRot() * (float) (Math.PI / 180.0));
float j = -Mth.sin(player.getXRot() * (float) (Math.PI / 180.0));
if (player.zza < 0.0F) {
i *= -0.5F;
j *= -0.5F;
}
h = j;
g = i;
}
if (player.isJumping()) {
h += 0.5F;
}
return new Vec3(f, h, g).scale(3.9F * this.getAttributeValue(Attributes.FLYING_SPEED));
}
protected Vec2 getRiddenRotation(LivingEntity entity) {
return new Vec2(entity.getXRot() * 0.5F, entity.getYRot());
}
@Override
protected void tickRidden(Player player, Vec3 travelVector) {
super.tickRidden(player, travelVector);
Vec2 vec2 = this.getRiddenRotation(player);
float f = this.getYRot();
float g = Mth.wrapDegrees(vec2.y - f);
float h = 0.08F;
f += g * 0.08F;
this.setRot(f, vec2.x);
this.yRotO = this.yBodyRot = this.yHeadRot = f;
}
@Override
protected Provider<HappyGhast> brainProvider() {
return HappyGhastAi.brainProvider();
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return HappyGhastAi.makeBrain(this.brainProvider().makeBrain(dynamic));
}
@Override
protected void customServerAiStep(ServerLevel level) {
if (this.isBaby()) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("happyGhastBrain");
((Brain<HappyGhast>)this.brain).tick(level, this);
profilerFiller.pop();
profilerFiller.push("happyGhastActivityUpdate");
HappyGhastAi.updateActivity(this);
profilerFiller.pop();
}
this.checkRestriction();
super.customServerAiStep(level);
}
@Override
public void tick() {
super.tick();
if (!this.level().isClientSide()) {
if (this.leashHolderTime > 0) {
this.leashHolderTime--;
}
this.setLeashHolder(this.leashHolderTime > 0);
if (this.serverStillTimeout > 0) {
if (this.tickCount > 60) {
this.serverStillTimeout--;
}
this.setServerStillTimeout(this.serverStillTimeout);
}
if (this.scanPlayerAboveGhast()) {
this.setServerStillTimeout(10);
}
}
}
@Override
public void aiStep() {
if (!this.level().isClientSide) {
this.setRequiresPrecisePosition(this.isOnStillTimeout());
}
super.aiStep();
this.continuousHeal();
}
private int getHappyGhastRestrictionRadius() {
return !this.isBaby() && this.getItemBySlot(EquipmentSlot.BODY).isEmpty() ? 64 : 32;
}
private void checkRestriction() {
if (!this.isLeashed() && !this.isVehicle()) {
int i = this.getHappyGhastRestrictionRadius();
if (!this.hasHome() || !this.getHomePosition().closerThan(this.blockPosition(), i + 16) || i != this.getHomeRadius()) {
this.setHomeTo(this.blockPosition(), i);
}
}
}
private void continuousHeal() {
if (this.level() instanceof ServerLevel serverLevel && this.isAlive() && this.deathTime == 0 && this.getMaxHealth() != this.getHealth()) {
boolean bl = serverLevel.dimensionType().natural() && (this.isInClouds() || serverLevel.precipitationAt(this.blockPosition()) != Precipitation.NONE);
if (this.tickCount % (bl ? 20 : 600) == 0) {
this.heal(1.0F);
}
}
}
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
@Override
protected void defineSynchedData(net.minecraft.network.syncher.SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(IS_LEASH_HOLDER, false);
builder.define(STAYS_STILL, false);
}
private void setLeashHolder(boolean leashHolder) {
this.entityData.set(IS_LEASH_HOLDER, leashHolder);
}
public boolean isLeashHolder() {
return this.entityData.get(IS_LEASH_HOLDER);
}
private void syncStayStillFlag() {
this.entityData.set(STAYS_STILL, this.serverStillTimeout > 0);
}
public boolean staysStill() {
return this.entityData.get(STAYS_STILL);
}
@Override
public boolean supportQuadLeashAsHolder() {
return true;
}
@Override
public Vec3[] getQuadLeashHolderOffsets() {
return Leashable.createQuadLeashOffsets(this, -0.03125, 0.4375, 0.46875, 0.03125);
}
@Override
public Vec3 getLeashOffset() {
return Vec3.ZERO;
}
@Override
public double leashElasticDistance() {
return 10.0;
}
@Override
public double leashSnapDistance() {
return 16.0;
}
@Override
public void onElasticLeashPull() {
super.onElasticLeashPull();
this.getMoveControl().setWait();
}
@Override
public void notifyLeashHolder(Leashable leashHolder) {
if (leashHolder.supportQuadLeash()) {
this.leashHolderTime = 5;
}
}
@Override
public void addAdditionalSaveData(ValueOutput output) {
super.addAdditionalSaveData(output);
output.putInt("still_timeout", this.serverStillTimeout);
}
@Override
public void readAdditionalSaveData(ValueInput input) {
super.readAdditionalSaveData(input);
this.setServerStillTimeout(input.getIntOr("still_timeout", 0));
}
public boolean isOnStillTimeout() {
return this.staysStill() || this.serverStillTimeout > 0;
}
private boolean scanPlayerAboveGhast() {
AABB aABB = this.getBoundingBox();
AABB aABB2 = new AABB(aABB.minX - 1.0, aABB.maxY - 1.0E-5F, aABB.minZ - 1.0, aABB.maxX + 1.0, aABB.maxY + aABB.getYsize() / 2.0, aABB.maxZ + 1.0);
for (Player player : this.level().players()) {
if (!player.isSpectator()) {
Entity entity = player.getRootVehicle();
if (!(entity instanceof HappyGhast) && aABB2.contains(entity.position())) {
return true;
}
}
}
return false;
}
@Override
protected BodyRotationControl createBodyControl() {
return new HappyGhast.HappyGhastBodyRotationControl();
}
@Override
public boolean canBeCollidedWith(@Nullable Entity entity) {
if (!this.isBaby() && this.isAlive()) {
if (this.level().isClientSide() && entity instanceof Player && entity.position().y >= this.getBoundingBox().maxY) {
return true;
} else {
return this.isVehicle() && entity instanceof HappyGhast ? true : this.isOnStillTimeout();
}
} else {
return false;
}
}
@Override
public boolean isFlyingVehicle() {
return !this.isBaby();
}
static class BabyFlyingPathNavigation extends FlyingPathNavigation {
public BabyFlyingPathNavigation(HappyGhast ghast, Level level) {
super(ghast, level);
this.setCanOpenDoors(false);
this.setCanFloat(true);
this.setRequiredPathLength(48.0F);
}
@Override
protected boolean canMoveDirectly(Vec3 posVec31, Vec3 posVec32) {
return isClearForMovementBetween(this.mob, posVec31, posVec32, false);
}
}
class HappyGhastBodyRotationControl extends BodyRotationControl {
public HappyGhastBodyRotationControl() {
super(HappyGhast.this);
}
@Override
public void clientTick() {
if (HappyGhast.this.isVehicle()) {
HappyGhast.this.yHeadRot = HappyGhast.this.getYRot();
HappyGhast.this.yBodyRot = HappyGhast.this.yHeadRot;
}
super.clientTick();
}
}
class HappyGhastFloatGoal extends FloatGoal {
public HappyGhastFloatGoal() {
super(HappyGhast.this);
}
@Override
public boolean canUse() {
return !HappyGhast.this.isOnStillTimeout() && super.canUse();
}
}
class HappyGhastLookControl extends LookControl {
HappyGhastLookControl() {
super(HappyGhast.this);
}
@Override
public void tick() {
if (HappyGhast.this.isOnStillTimeout()) {
float f = wrapDegrees90(HappyGhast.this.getYRot());
HappyGhast.this.setYRot(HappyGhast.this.getYRot() - f);
HappyGhast.this.setYHeadRot(HappyGhast.this.getYRot());
} else if (this.lookAtCooldown > 0) {
this.lookAtCooldown--;
double d = this.wantedX - HappyGhast.this.getX();
double e = this.wantedZ - HappyGhast.this.getZ();
HappyGhast.this.setYRot(-((float)Mth.atan2(d, e)) * (180.0F / (float)Math.PI));
HappyGhast.this.yBodyRot = HappyGhast.this.getYRot();
HappyGhast.this.yHeadRot = HappyGhast.this.yBodyRot;
} else {
Ghast.faceMovementDirection(this.mob);
}
}
public static float wrapDegrees90(float degrees) {
float f = degrees % 90.0F;
if (f >= 45.0F) {
f -= 90.0F;
}
if (f < -45.0F) {
f += 90.0F;
}
return f;
}
}
}