2025-11-24 22:52:51 +03:00

475 lines
18 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* org.jspecify.annotations.Nullable
*/
package net.minecraft.world.entity.monster;
import java.util.EnumSet;
import java.util.function.BooleanSupplier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
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.BlockTags;
import net.minecraft.tags.DamageTypeTags;
import net.minecraft.tags.FluidTags;
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.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.monster.Enemy;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.LargeFireball;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.material.FluidState;
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.Vec3;
import org.jspecify.annotations.Nullable;
public class Ghast
extends Mob
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> type, Level level) {
super((EntityType<? extends Mob>)type, level);
this.xpReward = 5;
this.moveControl = new GhastMoveControl(this, false, () -> false);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(5, new RandomFloatAroundGoal(this));
this.goalSelector.addGoal(7, new GhastLookGoal(this));
this.goalSelector.addGoal(7, new GhastShootFireballGoal(this));
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<Player>(this, Player.class, 10, true, false, (target, level) -> Math.abs(target.getY() - this.getY()) <= 4.0));
}
public boolean isCharging() {
return this.entityData.get(DATA_IS_CHARGING);
}
public void setCharging(boolean onOff) {
this.entityData.set(DATA_IS_CHARGING, onOff);
}
public int getExplosionPower() {
return this.explosionPower;
}
private static boolean isReflectedFireball(DamageSource source) {
return source.getDirectEntity() instanceof LargeFireball && source.getEntity() instanceof Player;
}
@Override
public boolean isInvulnerableTo(ServerLevel level, DamageSource source) {
return this.isInvulnerable() && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY) || !Ghast.isReflectedFireball(source) && super.isInvulnerableTo(level, source);
}
@Override
protected void checkFallDamage(double ya, boolean onGround, BlockState onState, BlockPos pos) {
}
@Override
public boolean onClimbable() {
return false;
}
@Override
public void travel(Vec3 input) {
this.travelFlying(input, 0.02f);
}
@Override
public boolean hurtServer(ServerLevel level, DamageSource source, float damage) {
if (Ghast.isReflectedFireball(source)) {
super.hurtServer(level, source, 1000.0f);
return true;
}
if (this.isInvulnerableTo(level, source)) {
return false;
}
return super.hurtServer(level, source, damage);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder entityData) {
super.defineSynchedData(entityData);
entityData.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).add(Attributes.CAMERA_DISTANCE, 8.0).add(Attributes.FLYING_SPEED, 0.06);
}
@Override
public SoundSource getSoundSource() {
return SoundSource.HOSTILE;
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.GHAST_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource source) {
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> type, LevelAccessor level, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
return level.getDifficulty() != Difficulty.PEACEFUL && random.nextInt(20) == 0 && Ghast.checkMobSpawnRules(type, level, spawnReason, pos, random);
}
@Override
public int getMaxSpawnClusterSize() {
return 1;
}
@Override
protected void addAdditionalSaveData(ValueOutput output) {
super.addAdditionalSaveData(output);
output.putByte("ExplosionPower", (byte)this.explosionPower);
}
@Override
protected void readAdditionalSaveData(ValueInput input) {
super.readAdditionalSaveData(input);
this.explosionPower = input.getByteOr("ExplosionPower", (byte)1);
}
@Override
public boolean supportQuadLeashAsHolder() {
return true;
}
@Override
public double leashElasticDistance() {
return 10.0;
}
@Override
public double leashSnapDistance() {
return 16.0;
}
public static void faceMovementDirection(Mob ghast) {
if (ghast.getTarget() == null) {
Vec3 movement = ghast.getDeltaMovement();
ghast.setYRot(-((float)Mth.atan2(movement.x, movement.z)) * 57.295776f);
ghast.yBodyRot = ghast.getYRot();
} else {
LivingEntity target = ghast.getTarget();
double maxDist = 64.0;
if (target.distanceToSqr(ghast) < 4096.0) {
double xdd = target.getX() - ghast.getX();
double zdd = target.getZ() - ghast.getZ();
ghast.setYRot(-((float)Mth.atan2(xdd, zdd)) * 57.295776f);
ghast.yBodyRot = ghast.getYRot();
}
}
}
public static class GhastMoveControl
extends MoveControl {
private final Mob ghast;
private int floatDuration;
private final boolean careful;
private final BooleanSupplier shouldBeStopped;
public GhastMoveControl(Mob ghast, boolean careful, BooleanSupplier shouldBeStopped) {
super(ghast);
this.ghast = ghast;
this.careful = careful;
this.shouldBeStopped = shouldBeStopped;
}
@Override
public void tick() {
if (this.shouldBeStopped.getAsBoolean()) {
this.operation = MoveControl.Operation.WAIT;
this.ghast.stopInPlace();
}
if (this.operation != MoveControl.Operation.MOVE_TO) {
return;
}
if (this.floatDuration-- <= 0) {
this.floatDuration += this.ghast.getRandom().nextInt(5) + 2;
Vec3 travel = new Vec3(this.wantedX - this.ghast.getX(), this.wantedY - this.ghast.getY(), this.wantedZ - this.ghast.getZ());
if (this.canReach(travel)) {
this.ghast.setDeltaMovement(this.ghast.getDeltaMovement().add(travel.normalize().scale(this.ghast.getAttributeValue(Attributes.FLYING_SPEED) * 5.0 / 3.0)));
} else {
this.operation = MoveControl.Operation.WAIT;
}
}
}
private boolean canReach(Vec3 travel) {
AABB aabb = this.ghast.getBoundingBox();
AABB aabbAtDestination = aabb.move(travel);
if (this.careful) {
for (BlockPos pos : BlockPos.betweenClosed(aabbAtDestination.inflate(1.0))) {
if (this.blockTraversalPossible(this.ghast.level(), null, null, pos, false, false)) continue;
return false;
}
}
boolean isInWater = this.ghast.isInWater();
boolean isInLava = this.ghast.isInLava();
Vec3 start = this.ghast.position();
Vec3 end = start.add(travel);
return BlockGetter.forEachBlockIntersectedBetween(start, end, aabbAtDestination, (blockPos, i) -> {
if (aabb.intersects(blockPos)) {
return true;
}
return this.blockTraversalPossible(this.ghast.level(), start, end, blockPos, isInWater, isInLava);
});
}
private boolean blockTraversalPossible(BlockGetter level, @Nullable Vec3 start, @Nullable Vec3 end, BlockPos pos, boolean canPathThroughWater, boolean canPathThroughLava) {
boolean pathNoCollisions;
boolean preciseBlockCollisions;
BlockState state = level.getBlockState(pos);
if (state.isAir()) {
return true;
}
boolean bl = preciseBlockCollisions = start != null && end != null;
boolean bl2 = preciseBlockCollisions ? !this.ghast.collidedWithShapeMovingFrom(start, end, state.getCollisionShape(level, pos).move(new Vec3(pos)).toAabbs()) : (pathNoCollisions = state.getCollisionShape(level, pos).isEmpty());
if (!this.careful) {
return pathNoCollisions;
}
if (state.is(BlockTags.HAPPY_GHAST_AVOIDS)) {
return false;
}
FluidState fluidState = level.getFluidState(pos);
if (!(fluidState.isEmpty() || preciseBlockCollisions && !this.ghast.collidedWithFluid(fluidState, pos, start, end))) {
if (fluidState.is(FluidTags.WATER)) {
return canPathThroughWater;
}
if (fluidState.is(FluidTags.LAVA)) {
return canPathThroughLava;
}
}
return pathNoCollisions;
}
}
public static class RandomFloatAroundGoal
extends Goal {
private static final int MAX_ATTEMPTS = 64;
private final Mob ghast;
private final int distanceToBlocks;
public RandomFloatAroundGoal(Mob ghast) {
this(ghast, 0);
}
public RandomFloatAroundGoal(Mob ghast, int distanceToBlocks) {
this.ghast = ghast;
this.distanceToBlocks = distanceToBlocks;
this.setFlags(EnumSet.of(Goal.Flag.MOVE));
}
@Override
public boolean canUse() {
double zd;
double yd;
MoveControl moveControl = this.ghast.getMoveControl();
if (!moveControl.hasWanted()) {
return true;
}
double xd = moveControl.getWantedX() - this.ghast.getX();
double dd = xd * xd + (yd = moveControl.getWantedY() - this.ghast.getY()) * yd + (zd = moveControl.getWantedZ() - this.ghast.getZ()) * zd;
return dd < 1.0 || dd > 3600.0;
}
@Override
public boolean canContinueToUse() {
return false;
}
@Override
public void start() {
Vec3 result = RandomFloatAroundGoal.getSuitableFlyToPosition(this.ghast, this.distanceToBlocks);
this.ghast.getMoveControl().setWantedPosition(result.x(), result.y(), result.z(), 1.0);
}
public static Vec3 getSuitableFlyToPosition(Mob mob, int distanceToBlocks) {
BlockPos pos;
int heightY;
Level level = mob.level();
RandomSource random = mob.getRandom();
Vec3 center = mob.position();
Vec3 result = null;
for (int i = 0; i < 64; ++i) {
result = RandomFloatAroundGoal.chooseRandomPositionWithRestriction(mob, center, random);
if (result == null || !RandomFloatAroundGoal.isGoodTarget(level, result, distanceToBlocks)) continue;
return result;
}
if (result == null) {
result = RandomFloatAroundGoal.chooseRandomPosition(center, random);
}
if ((heightY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, (pos = BlockPos.containing(result)).getX(), pos.getZ())) < pos.getY() && heightY > level.getMinY()) {
result = new Vec3(result.x(), mob.getY() - Math.abs(mob.getY() - result.y()), result.z());
}
return result;
}
private static boolean isGoodTarget(Level level, Vec3 target, int distanceToBlocks) {
if (distanceToBlocks <= 0) {
return true;
}
BlockPos pos = BlockPos.containing(target);
if (!level.getBlockState(pos).isAir()) {
return false;
}
for (Direction dir : Direction.values()) {
for (int i = 1; i < distanceToBlocks; ++i) {
BlockPos offset = pos.relative(dir, i);
if (level.getBlockState(offset).isAir()) continue;
return true;
}
}
return false;
}
private static Vec3 chooseRandomPosition(Vec3 center, RandomSource random) {
double xTarget = center.x() + (double)((random.nextFloat() * 2.0f - 1.0f) * 16.0f);
double yTarget = center.y() + (double)((random.nextFloat() * 2.0f - 1.0f) * 16.0f);
double zTarget = center.z() + (double)((random.nextFloat() * 2.0f - 1.0f) * 16.0f);
return new Vec3(xTarget, yTarget, zTarget);
}
private static @Nullable Vec3 chooseRandomPositionWithRestriction(Mob mob, Vec3 center, RandomSource random) {
Vec3 target = RandomFloatAroundGoal.chooseRandomPosition(center, random);
if (mob.hasHome() && !mob.isWithinHome(target)) {
return null;
}
return target;
}
}
public static class GhastLookGoal
extends Goal {
private final Mob ghast;
public GhastLookGoal(Mob ghast) {
this.ghast = ghast;
this.setFlags(EnumSet.of(Goal.Flag.LOOK));
}
@Override
public boolean canUse() {
return true;
}
@Override
public boolean requiresUpdateEveryTick() {
return true;
}
@Override
public void tick() {
Ghast.faceMovementDirection(this.ghast);
}
}
private 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 target = this.ghast.getTarget();
if (target == null) {
return;
}
double maxDist = 64.0;
if (target.distanceToSqr(this.ghast) < 4096.0 && this.ghast.hasLineOfSight(target)) {
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 d = 4.0;
Vec3 viewVector = this.ghast.getViewVector(1.0f);
double xdd = target.getX() - (this.ghast.getX() + viewVector.x * 4.0);
double ydd = target.getY(0.5) - (0.5 + this.ghast.getY(0.5));
double zdd = target.getZ() - (this.ghast.getZ() + viewVector.z * 4.0);
Vec3 direction = new Vec3(xdd, ydd, zdd);
if (!this.ghast.isSilent()) {
level.levelEvent(null, 1016, this.ghast.blockPosition(), 0);
}
LargeFireball entity = new LargeFireball(level, (LivingEntity)this.ghast, direction.normalize(), this.ghast.getExplosionPower());
entity.setPos(this.ghast.getX() + viewVector.x * 4.0, this.ghast.getY(0.5) + 0.5, entity.getZ() + viewVector.z * 4.0);
level.addFreshEntity(entity);
this.chargeTime = -40;
}
} else if (this.chargeTime > 0) {
--this.chargeTime;
}
this.ghast.setCharging(this.chargeTime > 10);
}
}
}