/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.mojang.serialization.Dynamic * org.jspecify.annotations.Nullable */ package net.minecraft.world.entity.animal; import com.mojang.serialization.Dynamic; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket; 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.Mob; import net.minecraft.world.entity.ai.Brain; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; 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; import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.animal.HappyGhastAi; import net.minecraft.world.entity.monster.Ghast; 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; 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.jspecify.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; private int leashHolderTime = 0; private int serverStillTimeout; private static final EntityDataAccessor IS_LEASH_HOLDER = SynchedEntityData.defineId(HappyGhast.class, EntityDataSerializers.BOOLEAN); private static final EntityDataAccessor STAYS_STILL = SynchedEntityData.defineId(HappyGhast.class, EntityDataSerializers.BOOLEAN); private static final float MAX_SCALE = 1.0f; public HappyGhast(EntityType type, Level level) { super((EntityType)type, level); this.moveControl = new Ghast.GhastMoveControl(this, true, this::isOnStillTimeout); this.lookControl = new HappyGhastLookControl(); } private void setServerStillTimeout(int serverStillTimeout) { Level level; if (this.serverStillTimeout <= 0 && serverStillTimeout > 0 && (level = this.level()) instanceof ServerLevel) { ServerLevel serverLevel = (ServerLevel)level; this.syncPacketPositionCodec(this.getX(), this.getY(), this.getZ()); serverLevel.getChunkSource().chunkMap.sendToTrackingPlayers(this, ClientboundEntityPositionSyncPacket.of(this)); } this.serverStillTimeout = serverStillTimeout; this.syncStayStillFlag(); } private PathNavigation createBabyNavigation(Level level) { return new BabyFlyingPathNavigation(this, level); } @Override protected void registerGoals() { this.goalSelector.addGoal(3, new HappyGhastFloatGoal()); this.goalSelector.addGoal(4, new TemptGoal.ForNonPathfinders((Mob)this, 1.0, itemStack -> this.isWearingBodyArmor() || this.isBaby() ? itemStack.is(ItemTags.HAPPY_GHAST_FOOD) : itemStack.is(ItemTags.HAPPY_GHAST_TEMPT_ITEMS), false, 7.0)); this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this, 16)); } private void adultGhastSetup() { this.moveControl = new Ghast.GhastMoveControl(this, true, this::isOnStillTimeout); this.lookControl = new HappyGhastLookControl(); this.navigation = this.createNavigation(this.level()); Level level = this.level(); if (level instanceof ServerLevel) { ServerLevel serverLevel = (ServerLevel)level; this.removeAllGoals(goal -> true); this.registerGoals(); 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 AttributeSupplier.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 ya, boolean onGround, BlockState onState, BlockPos pos) { } @Override public boolean onClimbable() { return false; } @Override public void travel(Vec3 input) { float speed = (float)this.getAttributeValue(Attributes.FLYING_SPEED) * 5.0f / 3.0f; this.travelFlying(input, speed, speed, speed); } @Override public float getWalkTargetValue(BlockPos pos, LevelReader level) { if (!level.isEmptyBlock(pos)) { return 0.0f; } if (level.isEmptyBlock(pos.below()) && !level.isEmptyBlock(pos.below(2))) { return 10.0f; } return 5.0f; } @Override public boolean canBreatheUnderwater() { if (this.isBaby()) { return true; } return super.canBreatheUnderwater(); } @Override protected boolean shouldStayCloseToLeashHolder() { return false; } @Override protected void playStepSound(BlockPos pos, BlockState blockState) { } @Override public float getVoicePitch() { return 1.0f; } @Override public SoundSource getSoundSource() { return SoundSource.NEUTRAL; } @Override public int getAmbientSoundInterval() { int interval = super.getAmbientSoundInterval(); if (this.isVehicle()) { return interval * 6; } return interval; } @Override protected SoundEvent getAmbientSound() { return this.isBaby() ? SoundEvents.GHASTLING_AMBIENT : SoundEvents.HAPPY_GHAST_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource source) { 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 protected float getSoundVolume() { return this.isBaby() ? 1.0f : 4.0f; } @Override public int getMaxSpawnClusterSize() { return 1; } @Override public @Nullable AgeableMob getBreedOffspring(ServerLevel level, AgeableMob partner) { 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 itemStack) { return itemStack.is(ItemTags.HAPPY_GHAST_FOOD); } @Override public boolean canUseSlot(EquipmentSlot slot) { if (slot == EquipmentSlot.BODY) { return this.isAlive() && !this.isBaby(); } return super.canUseSlot(slot); } @Override protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) { return slot == EquipmentSlot.BODY; } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { InteractionResult interactionResult; if (this.isBaby()) { return super.mobInteract(player, hand); } ItemStack itemStack = player.getItemInHand(hand); if (!itemStack.isEmpty() && (interactionResult = itemStack.interactLivingEntity(player, this, hand)).consumesAction()) { return interactionResult; } if (this.isWearingBodyArmor() && !player.isSecondaryUseActive()) { this.doPlayerRide(player); return InteractionResult.SUCCESS; } 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; } @Override public @Nullable LivingEntity getControllingPassenger() { Entity firstPassenger = this.getFirstPassenger(); if (this.isWearingBodyArmor() && !this.isOnStillTimeout() && firstPassenger instanceof Player) { Player player = (Player)firstPassenger; return player; } return super.getControllingPassenger(); } @Override protected Vec3 getRiddenInput(Player controller, Vec3 selfInput) { float strafe = controller.xxa; float forward = 0.0f; float up = 0.0f; if (controller.zza != 0.0f) { float forwardLook = Mth.cos(controller.getXRot() * ((float)Math.PI / 180)); float upLook = -Mth.sin(controller.getXRot() * ((float)Math.PI / 180)); if (controller.zza < 0.0f) { forwardLook *= -0.5f; upLook *= -0.5f; } up = upLook; forward = forwardLook; } if (controller.isJumping()) { up += 0.5f; } return new Vec3(strafe, up, forward).scale((double)3.9f * this.getAttributeValue(Attributes.FLYING_SPEED)); } protected Vec2 getRiddenRotation(LivingEntity controller) { return new Vec2(controller.getXRot() * 0.5f, controller.getYRot()); } @Override protected void tickRidden(Player controller, Vec3 riddenInput) { super.tickRidden(controller, riddenInput); Vec2 rotation = this.getRiddenRotation(controller); float yRot = this.getYRot(); float diff = Mth.wrapDegrees(rotation.y - yRot); float turnSpeed = 0.08f; this.setRot(yRot += diff * 0.08f, rotation.x); this.yBodyRot = this.yHeadRot = yRot; this.yRotO = this.yHeadRot; } protected Brain.Provider brainProvider() { return HappyGhastAi.brainProvider(); } @Override protected Brain makeBrain(Dynamic input) { return HappyGhastAi.makeBrain(this.brainProvider().makeBrain(input)); } @Override protected void customServerAiStep(ServerLevel level) { if (this.isBaby()) { ProfilerFiller profiler = Profiler.get(); profiler.push("happyGhastBrain"); this.brain.tick(level, this); profiler.pop(); profiler.push("happyGhastActivityUpdate"); HappyGhastAi.updateActivity(this); profiler.pop(); } this.checkRestriction(); super.customServerAiStep(level); } @Override public void tick() { super.tick(); if (this.level().isClientSide()) { return; } 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() { if (!this.isBaby() && this.getItemBySlot(EquipmentSlot.BODY).isEmpty()) { return 64; } return 32; } private void checkRestriction() { if (this.isLeashed() || this.isVehicle()) { return; } int radius = this.getHappyGhastRestrictionRadius(); if (this.hasHome() && this.getHomePosition().closerThan(this.blockPosition(), radius + 16) && radius == this.getHomeRadius()) { return; } this.setHomeTo(this.blockPosition(), radius); } private void continuousHeal() { ServerLevel level; block5: { block4: { Level level2 = this.level(); if (!(level2 instanceof ServerLevel)) break block4; level = (ServerLevel)level2; if (this.isAlive() && this.deathTime == 0 && this.getMaxHealth() != this.getHealth()) break block5; } return; } boolean isFastHealing = this.isInClouds() || level.precipitationAt(this.blockPosition()) != Biome.Precipitation.NONE; if (this.tickCount % (isFastHealing ? 20 : 600) == 0) { this.heal(1.0f); } } @Override protected void defineSynchedData(SynchedEntityData.Builder entityData) { super.defineSynchedData(entityData); entityData.define(IS_LEASH_HOLDER, false); entityData.define(STAYS_STILL, false); } private void setLeashHolder(boolean isLeashHolder) { this.entityData.set(IS_LEASH_HOLDER, isLeashHolder); } 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 entity) { if (entity.supportQuadLeash()) { this.leashHolderTime = 5; } } @Override public void addAdditionalSaveData(ValueOutput tag) { super.addAdditionalSaveData(tag); tag.putInt("still_timeout", this.serverStillTimeout); } @Override public void readAdditionalSaveData(ValueInput tag) { super.readAdditionalSaveData(tag); this.setServerStillTimeout(tag.getIntOr("still_timeout", 0)); } public boolean isOnStillTimeout() { return this.staysStill() || this.serverStillTimeout > 0; } private boolean scanPlayerAboveGhast() { AABB happyGhastBb = this.getBoundingBox(); AABB ghastDetectionBox = new AABB(happyGhastBb.minX - 1.0, happyGhastBb.maxY - (double)1.0E-5f, happyGhastBb.minZ - 1.0, happyGhastBb.maxX + 1.0, happyGhastBb.maxY + happyGhastBb.getYsize() / 2.0, happyGhastBb.maxZ + 1.0); for (Player player : this.level().players()) { Entity rootVehicle; if (player.isSpectator() || (rootVehicle = player.getRootVehicle()) instanceof HappyGhast || !ghastDetectionBox.contains(rootVehicle.position())) continue; return true; } return false; } @Override protected BodyRotationControl createBodyControl() { return new HappyGhastBodyRotationControl(); } @Override public boolean canBeCollidedWith(@Nullable Entity other) { if (this.isBaby() || !this.isAlive()) { return false; } if (this.level().isClientSide() && other instanceof Player && other.position().y >= this.getBoundingBox().maxY) { return true; } if (this.isVehicle() && other instanceof HappyGhast) { return true; } return this.isOnStillTimeout(); } @Override public boolean isFlyingVehicle() { return !this.isBaby(); } private class HappyGhastLookControl extends LookControl { private HappyGhastLookControl() { super(HappyGhast.this); } @Override public void tick() { if (HappyGhast.this.isOnStillTimeout()) { float closeAngle = HappyGhastLookControl.wrapDegrees90(HappyGhast.this.getYRot()); HappyGhast.this.setYRot(HappyGhast.this.getYRot() - closeAngle); HappyGhast.this.setYHeadRot(HappyGhast.this.getYRot()); return; } if (this.lookAtCooldown > 0) { --this.lookAtCooldown; double xdd = this.wantedX - HappyGhast.this.getX(); double zdd = this.wantedZ - HappyGhast.this.getZ(); HappyGhast.this.setYRot(-((float)Mth.atan2(xdd, zdd)) * 57.295776f); HappyGhast.this.yHeadRot = HappyGhast.this.yBodyRot = HappyGhast.this.getYRot(); return; } Ghast.faceMovementDirection(this.mob); } public static float wrapDegrees90(float angle) { float normalizedAngle = angle % 90.0f; if (normalizedAngle >= 45.0f) { normalizedAngle -= 90.0f; } if (normalizedAngle < -45.0f) { normalizedAngle += 90.0f; } return normalizedAngle; } } private static class BabyFlyingPathNavigation extends FlyingPathNavigation { public BabyFlyingPathNavigation(HappyGhast mob, Level level) { super(mob, level); this.setCanOpenDoors(false); this.setCanFloat(true); this.setRequiredPathLength(48.0f); } @Override protected boolean canMoveDirectly(Vec3 startPos, Vec3 stopPos) { return BabyFlyingPathNavigation.isClearForMovementBetween(this.mob, startPos, stopPos, false); } } private class HappyGhastFloatGoal extends FloatGoal { public HappyGhastFloatGoal() { super(HappyGhast.this); } @Override public boolean canUse() { return !HappyGhast.this.isOnStillTimeout() && super.canUse(); } } private class HappyGhastBodyRotationControl extends BodyRotationControl { public HappyGhastBodyRotationControl() { super(HappyGhast.this); } @Override public void clientTick() { if (HappyGhast.this.isVehicle()) { HappyGhast.this.yBodyRot = HappyGhast.this.yHeadRot = HappyGhast.this.getYRot(); } super.clientTick(); } } }