package net.minecraft.world.entity.monster; import java.util.EnumSet; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; 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.DamageTypeTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.Difficulty; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.FlyingMob; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.LargeFireball; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; public class Ghast extends FlyingMob implements Enemy { private static final EntityDataAccessor DATA_IS_CHARGING = SynchedEntityData.defineId(Ghast.class, EntityDataSerializers.BOOLEAN); private static final byte DEFAULT_EXPLOSION_POWER = 1; private int explosionPower = 1; public Ghast(EntityType p_32725_, Level p_32726_) { super(p_32725_, p_32726_); this.xpReward = 5; this.moveControl = new Ghast.GhastMoveControl(this); } @Override protected void registerGoals() { this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this)); this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this)); this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this)); this.targetSelector .addGoal( 1, new NearestAttackableTargetGoal<>( this, Player.class, 10, true, false, (p_375133_, p_375134_) -> Math.abs(p_375133_.getY() - this.getY()) <= 4.0 ) ); } public boolean isCharging() { return this.entityData.get(DATA_IS_CHARGING); } public void setCharging(boolean p_32759_) { this.entityData.set(DATA_IS_CHARGING, p_32759_); } public int getExplosionPower() { return this.explosionPower; } @Override protected boolean shouldDespawnInPeaceful() { return true; } private static boolean isReflectedFireball(DamageSource p_238408_) { return p_238408_.getDirectEntity() instanceof LargeFireball && p_238408_.getEntity() instanceof Player; } @Override public boolean isInvulnerableTo(ServerLevel p_363213_, DamageSource p_238289_) { return this.isInvulnerable() && !p_238289_.is(DamageTypeTags.BYPASSES_INVULNERABILITY) || !isReflectedFireball(p_238289_) && super.isInvulnerableTo(p_363213_, p_238289_); } @Override public boolean hurtServer(ServerLevel p_365264_, DamageSource p_366880_, float p_369426_) { if (isReflectedFireball(p_366880_)) { super.hurtServer(p_365264_, p_366880_, 1000.0F); return true; } else { return this.isInvulnerableTo(p_365264_, p_366880_) ? false : super.hurtServer(p_365264_, p_366880_, p_369426_); } } @Override protected void defineSynchedData(SynchedEntityData.Builder p_334321_) { super.defineSynchedData(p_334321_); p_334321_.define(DATA_IS_CHARGING, false); } public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0).add(Attributes.FOLLOW_RANGE, 100.0); } @Override public SoundSource getSoundSource() { return SoundSource.HOSTILE; } @Override protected SoundEvent getAmbientSound() { return SoundEvents.GHAST_AMBIENT; } @Override protected SoundEvent getHurtSound(DamageSource p_32750_) { return SoundEvents.GHAST_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.GHAST_DEATH; } @Override protected float getSoundVolume() { return 5.0F; } public static boolean checkGhastSpawnRules( EntityType p_218985_, LevelAccessor p_218986_, EntitySpawnReason p_366739_, BlockPos p_218988_, RandomSource p_218989_ ) { return p_218986_.getDifficulty() != Difficulty.PEACEFUL && p_218989_.nextInt(20) == 0 && checkMobSpawnRules(p_218985_, p_218986_, p_366739_, p_218988_, p_218989_); } @Override public int getMaxSpawnClusterSize() { return 1; } @Override public void addAdditionalSaveData(CompoundTag p_32744_) { super.addAdditionalSaveData(p_32744_); p_32744_.putByte("ExplosionPower", (byte)this.explosionPower); } @Override public void readAdditionalSaveData(CompoundTag p_32733_) { super.readAdditionalSaveData(p_32733_); this.explosionPower = p_32733_.getByteOr("ExplosionPower", (byte)1); } static class GhastLookGoal extends Goal { private final Ghast ghast; public GhastLookGoal(Ghast p_32762_) { this.ghast = p_32762_; this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } @Override public boolean canUse() { return true; } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { if (this.ghast.getTarget() == null) { Vec3 vec3 = this.ghast.getDeltaMovement(); this.ghast.setYRot(-((float)Mth.atan2(vec3.x, vec3.z)) * (180.0F / (float)Math.PI)); this.ghast.yBodyRot = this.ghast.getYRot(); } else { LivingEntity livingentity = this.ghast.getTarget(); double d0 = 64.0; if (livingentity.distanceToSqr(this.ghast) < 4096.0) { double d1 = livingentity.getX() - this.ghast.getX(); double d2 = livingentity.getZ() - this.ghast.getZ(); this.ghast.setYRot(-((float)Mth.atan2(d1, d2)) * (180.0F / (float)Math.PI)); this.ghast.yBodyRot = this.ghast.getYRot(); } } } } static class GhastMoveControl extends MoveControl { private final Ghast ghast; private int floatDuration; public GhastMoveControl(Ghast p_32768_) { super(p_32768_); this.ghast = p_32768_; } @Override public void tick() { if (this.operation == MoveControl.Operation.MOVE_TO) { if (this.floatDuration-- <= 0) { this.floatDuration = this.floatDuration + this.ghast.getRandom().nextInt(5) + 2; Vec3 vec3 = new Vec3( this.wantedX - this.ghast.getX(), this.wantedY - this.ghast.getY(), this.wantedZ - this.ghast.getZ() ); double d0 = vec3.length(); vec3 = vec3.normalize(); if (this.canReach(vec3, Mth.ceil(d0))) { this.ghast.setDeltaMovement(this.ghast.getDeltaMovement().add(vec3.scale(0.1))); } else { this.operation = MoveControl.Operation.WAIT; } } } } private boolean canReach(Vec3 p_32771_, int p_32772_) { AABB aabb = this.ghast.getBoundingBox(); for (int i = 1; i < p_32772_; i++) { aabb = aabb.move(p_32771_); if (!this.ghast.level().noCollision(this.ghast, aabb)) { return false; } } return true; } } static class GhastShootFireballGoal extends Goal { private final Ghast ghast; public int chargeTime; public GhastShootFireballGoal(Ghast p_32776_) { this.ghast = p_32776_; } @Override public boolean canUse() { return this.ghast.getTarget() != null; } @Override public void start() { this.chargeTime = 0; } @Override public void stop() { this.ghast.setCharging(false); } @Override public boolean requiresUpdateEveryTick() { return true; } @Override public void tick() { LivingEntity livingentity = this.ghast.getTarget(); if (livingentity != null) { double d0 = 64.0; if (livingentity.distanceToSqr(this.ghast) < 4096.0 && this.ghast.hasLineOfSight(livingentity)) { Level level = this.ghast.level(); this.chargeTime++; if (this.chargeTime == 10 && !this.ghast.isSilent()) { level.levelEvent(null, 1015, this.ghast.blockPosition(), 0); } if (this.chargeTime == 20) { double d1 = 4.0; Vec3 vec3 = this.ghast.getViewVector(1.0F); double d2 = livingentity.getX() - (this.ghast.getX() + vec3.x * 4.0); double d3 = livingentity.getY(0.5) - (0.5 + this.ghast.getY(0.5)); double d4 = livingentity.getZ() - (this.ghast.getZ() + vec3.z * 4.0); Vec3 vec31 = new Vec3(d2, d3, d4); if (!this.ghast.isSilent()) { level.levelEvent(null, 1016, this.ghast.blockPosition(), 0); } LargeFireball largefireball = new LargeFireball(level, this.ghast, vec31.normalize(), this.ghast.getExplosionPower()); largefireball.setPos( this.ghast.getX() + vec3.x * 4.0, this.ghast.getY(0.5) + 0.5, largefireball.getZ() + vec3.z * 4.0 ); level.addFreshEntity(largefireball); this.chargeTime = -40; } } else if (this.chargeTime > 0) { this.chargeTime--; } this.ghast.setCharging(this.chargeTime > 10); } } } static class RandomFloatAroundGoal extends Goal { private final Ghast ghast; public RandomFloatAroundGoal(Ghast p_32783_) { this.ghast = p_32783_; this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @Override public boolean canUse() { MoveControl movecontrol = this.ghast.getMoveControl(); if (!movecontrol.hasWanted()) { return true; } else { double d0 = movecontrol.getWantedX() - this.ghast.getX(); double d1 = movecontrol.getWantedY() - this.ghast.getY(); double d2 = movecontrol.getWantedZ() - this.ghast.getZ(); double d3 = d0 * d0 + d1 * d1 + d2 * d2; return d3 < 1.0 || d3 > 3600.0; } } @Override public boolean canContinueToUse() { return false; } @Override public void start() { RandomSource randomsource = this.ghast.getRandom(); double d0 = this.ghast.getX() + (randomsource.nextFloat() * 2.0F - 1.0F) * 16.0F; double d1 = this.ghast.getY() + (randomsource.nextFloat() * 2.0F - 1.0F) * 16.0F; double d2 = this.ghast.getZ() + (randomsource.nextFloat() * 2.0F - 1.0F) * 16.0F; this.ghast.getMoveControl().setWantedPosition(d0, d1, d2, 1.0); } } }