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.network.syncher.SynchedEntityData.Builder; 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.Attributes; import net.minecraft.world.entity.ai.control.MoveControl; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.Goal.Flag; 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 int explosionPower = 1; public Ghast(EntityType entityType, Level level) { super(entityType, level); 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, (livingEntity, serverLevel) -> Math.abs(livingEntity.getY() - this.getY()) <= 4.0) ); } public boolean isCharging() { return this.entityData.get(DATA_IS_CHARGING); } public void setCharging(boolean charging) { this.entityData.set(DATA_IS_CHARGING, charging); } public int getExplosionPower() { return this.explosionPower; } @Override protected boolean shouldDespawnInPeaceful() { return true; } private static boolean isReflectedFireball(DamageSource damageSource) { return damageSource.getDirectEntity() instanceof LargeFireball && damageSource.getEntity() instanceof Player; } @Override public boolean isInvulnerableTo(ServerLevel level, DamageSource damageSource) { return this.isInvulnerable() && !damageSource.is(DamageTypeTags.BYPASSES_INVULNERABILITY) || !isReflectedFireball(damageSource) && super.isInvulnerableTo(level, damageSource); } @Override public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) { if (isReflectedFireball(damageSource)) { super.hurtServer(level, damageSource, 1000.0F); return true; } else { return this.isInvulnerableTo(level, damageSource) ? false : super.hurtServer(level, damageSource, amount); } } @Override protected void defineSynchedData(Builder builder) { super.defineSynchedData(builder); builder.define(DATA_IS_CHARGING, false); } public static net.minecraft.world.entity.ai.attributes.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 damageSource) { return SoundEvents.GHAST_HURT; } @Override protected SoundEvent getDeathSound() { return SoundEvents.GHAST_DEATH; } @Override protected float getSoundVolume() { return 5.0F; } public static boolean checkGhastSpawnRules(EntityType entityType, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { return level.getDifficulty() != Difficulty.PEACEFUL && random.nextInt(20) == 0 && checkMobSpawnRules(entityType, level, spawnReason, pos, random); } @Override public int getMaxSpawnClusterSize() { return 1; } @Override public void addAdditionalSaveData(CompoundTag tag) { super.addAdditionalSaveData(tag); tag.putByte("ExplosionPower", (byte)this.explosionPower); } @Override public void readAdditionalSaveData(CompoundTag tag) { super.readAdditionalSaveData(tag); if (tag.contains("ExplosionPower", 99)) { this.explosionPower = tag.getByte("ExplosionPower"); } } static class GhastLookGoal extends Goal { private final Ghast ghast; public GhastLookGoal(Ghast ghast) { this.ghast = ghast; this.setFlags(EnumSet.of(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 d = 64.0; if (livingEntity.distanceToSqr(this.ghast) < 4096.0) { double e = livingEntity.getX() - this.ghast.getX(); double f = livingEntity.getZ() - this.ghast.getZ(); this.ghast.setYRot(-((float)Mth.atan2(e, f)) * (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 ghast) { super(ghast); this.ghast = ghast; } @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 d = vec3.length(); vec3 = vec3.normalize(); if (this.canReach(vec3, Mth.ceil(d))) { this.ghast.setDeltaMovement(this.ghast.getDeltaMovement().add(vec3.scale(0.1))); } else { this.operation = MoveControl.Operation.WAIT; } } } } private boolean canReach(Vec3 pos, int length) { AABB aABB = this.ghast.getBoundingBox(); for (int i = 1; i < length; i++) { aABB = aABB.move(pos); 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 ghast) { this.ghast = ghast; } @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 d = 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 e = 4.0; Vec3 vec3 = this.ghast.getViewVector(1.0F); double f = livingEntity.getX() - (this.ghast.getX() + vec3.x * 4.0); double g = livingEntity.getY(0.5) - (0.5 + this.ghast.getY(0.5)); double h = livingEntity.getZ() - (this.ghast.getZ() + vec3.z * 4.0); Vec3 vec32 = new Vec3(f, g, h); if (!this.ghast.isSilent()) { level.levelEvent(null, 1016, this.ghast.blockPosition(), 0); } LargeFireball largeFireball = new LargeFireball(level, this.ghast, vec32.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 ghast) { this.ghast = ghast; this.setFlags(EnumSet.of(Flag.MOVE)); } @Override public boolean canUse() { MoveControl moveControl = this.ghast.getMoveControl(); if (!moveControl.hasWanted()) { return true; } else { double d = moveControl.getWantedX() - this.ghast.getX(); double e = moveControl.getWantedY() - this.ghast.getY(); double f = moveControl.getWantedZ() - this.ghast.getZ(); double g = d * d + e * e + f * f; return g < 1.0 || g > 3600.0; } } @Override public boolean canContinueToUse() { return false; } @Override public void start() { RandomSource randomSource = this.ghast.getRandom(); double d = this.ghast.getX() + (randomSource.nextFloat() * 2.0F - 1.0F) * 16.0F; double e = this.ghast.getY() + (randomSource.nextFloat() * 2.0F - 1.0F) * 16.0F; double f = this.ghast.getZ() + (randomSource.nextFloat() * 2.0F - 1.0F) * 16.0F; this.ghast.getMoveControl().setWantedPosition(d, e, f, 1.0); } } }