336 lines
12 KiB
Java
336 lines
12 KiB
Java
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<Boolean> DATA_IS_CHARGING = SynchedEntityData.defineId(Ghast.class, EntityDataSerializers.BOOLEAN);
|
|
private static final byte DEFAULT_EXPLOSION_POWER = 1;
|
|
private int explosionPower = 1;
|
|
|
|
public Ghast(EntityType<? extends Ghast> 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<Ghast> 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);
|
|
}
|
|
}
|
|
} |