933 lines
31 KiB
Java
933 lines
31 KiB
Java
package net.minecraft.world.entity.boss.enderdragon;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.util.List;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
|
|
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.util.Mth;
|
|
import net.minecraft.world.damagesource.DamageSource;
|
|
import net.minecraft.world.effect.MobEffectInstance;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntitySelector;
|
|
import net.minecraft.world.entity.EntityType;
|
|
import net.minecraft.world.entity.ExperienceOrb;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.Mob;
|
|
import net.minecraft.world.entity.MoverType;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.ai.targeting.TargetingConditions;
|
|
import net.minecraft.world.entity.boss.EnderDragonPart;
|
|
import net.minecraft.world.entity.boss.enderdragon.phases.DragonPhaseInstance;
|
|
import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhase;
|
|
import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhaseManager;
|
|
import net.minecraft.world.entity.monster.Enemy;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.enchantment.EnchantmentHelper;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.dimension.end.EndDragonFight;
|
|
import net.minecraft.world.level.gameevent.GameEvent;
|
|
import net.minecraft.world.level.levelgen.Heightmap;
|
|
import net.minecraft.world.level.levelgen.feature.EndPodiumFeature;
|
|
import net.minecraft.world.level.pathfinder.BinaryHeap;
|
|
import net.minecraft.world.level.pathfinder.Node;
|
|
import net.minecraft.world.level.pathfinder.Path;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class EnderDragon extends Mob implements Enemy {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final EntityDataAccessor<Integer> DATA_PHASE = SynchedEntityData.defineId(EnderDragon.class, EntityDataSerializers.INT);
|
|
private static final TargetingConditions CRYSTAL_DESTROY_TARGETING = TargetingConditions.forCombat().range(64.0);
|
|
private static final int GROWL_INTERVAL_MIN = 200;
|
|
private static final int GROWL_INTERVAL_MAX = 400;
|
|
private static final float SITTING_ALLOWED_DAMAGE_PERCENTAGE = 0.25F;
|
|
private static final String DRAGON_DEATH_TIME_KEY = "DragonDeathTime";
|
|
private static final String DRAGON_PHASE_KEY = "DragonPhase";
|
|
public final double[][] positions = new double[64][3];
|
|
public int posPointer = -1;
|
|
private final EnderDragonPart[] subEntities;
|
|
public final EnderDragonPart head;
|
|
private final EnderDragonPart neck;
|
|
private final EnderDragonPart body;
|
|
private final EnderDragonPart tail1;
|
|
private final EnderDragonPart tail2;
|
|
private final EnderDragonPart tail3;
|
|
private final EnderDragonPart wing1;
|
|
private final EnderDragonPart wing2;
|
|
public float oFlapTime;
|
|
public float flapTime;
|
|
public boolean inWall;
|
|
public int dragonDeathTime;
|
|
public float yRotA;
|
|
@Nullable
|
|
public EndCrystal nearestCrystal;
|
|
@Nullable
|
|
private EndDragonFight dragonFight;
|
|
private BlockPos fightOrigin = BlockPos.ZERO;
|
|
private final EnderDragonPhaseManager phaseManager;
|
|
private int growlTime = 100;
|
|
private float sittingDamageReceived;
|
|
private final Node[] nodes = new Node[24];
|
|
private final int[] nodeAdjacency = new int[24];
|
|
private final BinaryHeap openSet = new BinaryHeap();
|
|
|
|
public EnderDragon(EntityType<? extends EnderDragon> entityType, Level level) {
|
|
super(EntityType.ENDER_DRAGON, level);
|
|
this.head = new EnderDragonPart(this, "head", 1.0F, 1.0F);
|
|
this.neck = new EnderDragonPart(this, "neck", 3.0F, 3.0F);
|
|
this.body = new EnderDragonPart(this, "body", 5.0F, 3.0F);
|
|
this.tail1 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
|
|
this.tail2 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
|
|
this.tail3 = new EnderDragonPart(this, "tail", 2.0F, 2.0F);
|
|
this.wing1 = new EnderDragonPart(this, "wing", 4.0F, 2.0F);
|
|
this.wing2 = new EnderDragonPart(this, "wing", 4.0F, 2.0F);
|
|
this.subEntities = new EnderDragonPart[]{this.head, this.neck, this.body, this.tail1, this.tail2, this.tail3, this.wing1, this.wing2};
|
|
this.setHealth(this.getMaxHealth());
|
|
this.noPhysics = true;
|
|
this.noCulling = true;
|
|
this.phaseManager = new EnderDragonPhaseManager(this);
|
|
}
|
|
|
|
public void setDragonFight(EndDragonFight dragonFight) {
|
|
this.dragonFight = dragonFight;
|
|
}
|
|
|
|
public void setFightOrigin(BlockPos fightOrigin) {
|
|
this.fightOrigin = fightOrigin;
|
|
}
|
|
|
|
public BlockPos getFightOrigin() {
|
|
return this.fightOrigin;
|
|
}
|
|
|
|
public static AttributeSupplier.Builder createAttributes() {
|
|
return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0);
|
|
}
|
|
|
|
@Override
|
|
public boolean isFlapping() {
|
|
float f = Mth.cos(this.flapTime * (float) (Math.PI * 2));
|
|
float g = Mth.cos(this.oFlapTime * (float) (Math.PI * 2));
|
|
return g <= -0.3F && f >= -0.3F;
|
|
}
|
|
|
|
@Override
|
|
public void onFlap() {
|
|
if (this.level().isClientSide && !this.isSilent()) {
|
|
this.level()
|
|
.playLocalSound(
|
|
this.getX(), this.getY(), this.getZ(), SoundEvents.ENDER_DRAGON_FLAP, this.getSoundSource(), 5.0F, 0.8F + this.random.nextFloat() * 0.3F, false
|
|
);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
|
super.defineSynchedData(builder);
|
|
builder.define(DATA_PHASE, EnderDragonPhase.HOVERING.getId());
|
|
}
|
|
|
|
/**
|
|
* Returns a double[3] array with movement offsets, used to calculate trailing tail/neck positions. [0] = yaw offset, [1] = y offset, [2] = unused, always 0. Parameters: buffer index offset, partial ticks.
|
|
*/
|
|
public double[] getLatencyPos(int bufferIndexOffset, float partialTicks) {
|
|
if (this.isDeadOrDying()) {
|
|
partialTicks = 0.0F;
|
|
}
|
|
|
|
partialTicks = 1.0F - partialTicks;
|
|
int i = this.posPointer - bufferIndexOffset & 63;
|
|
int j = this.posPointer - bufferIndexOffset - 1 & 63;
|
|
double[] ds = new double[3];
|
|
double d = this.positions[i][0];
|
|
double e = Mth.wrapDegrees(this.positions[j][0] - d);
|
|
ds[0] = d + e * partialTicks;
|
|
d = this.positions[i][1];
|
|
e = this.positions[j][1] - d;
|
|
ds[1] = d + e * partialTicks;
|
|
ds[2] = Mth.lerp((double)partialTicks, this.positions[i][2], this.positions[j][2]);
|
|
return ds;
|
|
}
|
|
|
|
@Override
|
|
public void aiStep() {
|
|
this.processFlappingMovement();
|
|
if (this.level().isClientSide) {
|
|
this.setHealth(this.getHealth());
|
|
if (!this.isSilent() && !this.phaseManager.getCurrentPhase().isSitting() && --this.growlTime < 0) {
|
|
this.level()
|
|
.playLocalSound(
|
|
this.getX(), this.getY(), this.getZ(), SoundEvents.ENDER_DRAGON_GROWL, this.getSoundSource(), 2.5F, 0.8F + this.random.nextFloat() * 0.3F, false
|
|
);
|
|
this.growlTime = 200 + this.random.nextInt(200);
|
|
}
|
|
}
|
|
|
|
if (this.dragonFight == null && this.level() instanceof ServerLevel serverLevel) {
|
|
EndDragonFight endDragonFight = serverLevel.getDragonFight();
|
|
if (endDragonFight != null && this.getUUID().equals(endDragonFight.getDragonUUID())) {
|
|
this.dragonFight = endDragonFight;
|
|
}
|
|
}
|
|
|
|
this.oFlapTime = this.flapTime;
|
|
if (this.isDeadOrDying()) {
|
|
float f = (this.random.nextFloat() - 0.5F) * 8.0F;
|
|
float g = (this.random.nextFloat() - 0.5F) * 4.0F;
|
|
float h = (this.random.nextFloat() - 0.5F) * 8.0F;
|
|
this.level().addParticle(ParticleTypes.EXPLOSION, this.getX() + f, this.getY() + 2.0 + g, this.getZ() + h, 0.0, 0.0, 0.0);
|
|
} else {
|
|
this.checkCrystals();
|
|
Vec3 vec3 = this.getDeltaMovement();
|
|
float g = 0.2F / ((float)vec3.horizontalDistance() * 10.0F + 1.0F);
|
|
g *= (float)Math.pow(2.0, vec3.y);
|
|
if (this.phaseManager.getCurrentPhase().isSitting()) {
|
|
this.flapTime += 0.1F;
|
|
} else if (this.inWall) {
|
|
this.flapTime += g * 0.5F;
|
|
} else {
|
|
this.flapTime += g;
|
|
}
|
|
|
|
this.setYRot(Mth.wrapDegrees(this.getYRot()));
|
|
if (this.isNoAi()) {
|
|
this.flapTime = 0.5F;
|
|
} else {
|
|
if (this.posPointer < 0) {
|
|
for (int i = 0; i < this.positions.length; i++) {
|
|
this.positions[i][0] = this.getYRot();
|
|
this.positions[i][1] = this.getY();
|
|
}
|
|
}
|
|
|
|
if (++this.posPointer == this.positions.length) {
|
|
this.posPointer = 0;
|
|
}
|
|
|
|
this.positions[this.posPointer][0] = this.getYRot();
|
|
this.positions[this.posPointer][1] = this.getY();
|
|
if (this.level().isClientSide) {
|
|
if (this.lerpSteps > 0) {
|
|
this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot);
|
|
this.lerpSteps--;
|
|
}
|
|
|
|
this.phaseManager.getCurrentPhase().doClientTick();
|
|
} else {
|
|
DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
|
|
dragonPhaseInstance.doServerTick();
|
|
if (this.phaseManager.getCurrentPhase() != dragonPhaseInstance) {
|
|
dragonPhaseInstance = this.phaseManager.getCurrentPhase();
|
|
dragonPhaseInstance.doServerTick();
|
|
}
|
|
|
|
Vec3 vec32 = dragonPhaseInstance.getFlyTargetLocation();
|
|
if (vec32 != null) {
|
|
double d = vec32.x - this.getX();
|
|
double e = vec32.y - this.getY();
|
|
double j = vec32.z - this.getZ();
|
|
double k = d * d + e * e + j * j;
|
|
float l = dragonPhaseInstance.getFlySpeed();
|
|
double m = Math.sqrt(d * d + j * j);
|
|
if (m > 0.0) {
|
|
e = Mth.clamp(e / m, (double)(-l), (double)l);
|
|
}
|
|
|
|
this.setDeltaMovement(this.getDeltaMovement().add(0.0, e * 0.01, 0.0));
|
|
this.setYRot(Mth.wrapDegrees(this.getYRot()));
|
|
Vec3 vec33 = vec32.subtract(this.getX(), this.getY(), this.getZ()).normalize();
|
|
Vec3 vec34 = new Vec3(
|
|
Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)), this.getDeltaMovement().y, -Mth.cos(this.getYRot() * (float) (Math.PI / 180.0))
|
|
)
|
|
.normalize();
|
|
float n = Math.max(((float)vec34.dot(vec33) + 0.5F) / 1.5F, 0.0F);
|
|
if (Math.abs(d) > 1.0E-5F || Math.abs(j) > 1.0E-5F) {
|
|
float o = Mth.clamp(Mth.wrapDegrees(180.0F - (float)Mth.atan2(d, j) * (180.0F / (float)Math.PI) - this.getYRot()), -50.0F, 50.0F);
|
|
this.yRotA *= 0.8F;
|
|
this.yRotA = this.yRotA + o * dragonPhaseInstance.getTurnSpeed();
|
|
this.setYRot(this.getYRot() + this.yRotA * 0.1F);
|
|
}
|
|
|
|
float o = (float)(2.0 / (k + 1.0));
|
|
float p = 0.06F;
|
|
this.moveRelative(0.06F * (n * o + (1.0F - o)), new Vec3(0.0, 0.0, -1.0));
|
|
if (this.inWall) {
|
|
this.move(MoverType.SELF, this.getDeltaMovement().scale(0.8F));
|
|
} else {
|
|
this.move(MoverType.SELF, this.getDeltaMovement());
|
|
}
|
|
|
|
Vec3 vec35 = this.getDeltaMovement().normalize();
|
|
double q = 0.8 + 0.15 * (vec35.dot(vec34) + 1.0) / 2.0;
|
|
this.setDeltaMovement(this.getDeltaMovement().multiply(q, 0.91F, q));
|
|
}
|
|
}
|
|
|
|
this.yBodyRot = this.getYRot();
|
|
Vec3[] vec3s = new Vec3[this.subEntities.length];
|
|
|
|
for (int r = 0; r < this.subEntities.length; r++) {
|
|
vec3s[r] = new Vec3(this.subEntities[r].getX(), this.subEntities[r].getY(), this.subEntities[r].getZ());
|
|
}
|
|
|
|
float s = (float)(this.getLatencyPos(5, 1.0F)[1] - this.getLatencyPos(10, 1.0F)[1]) * 10.0F * (float) (Math.PI / 180.0);
|
|
float t = Mth.cos(s);
|
|
float u = Mth.sin(s);
|
|
float v = this.getYRot() * (float) (Math.PI / 180.0);
|
|
float w = Mth.sin(v);
|
|
float x = Mth.cos(v);
|
|
this.tickPart(this.body, w * 0.5F, 0.0, -x * 0.5F);
|
|
this.tickPart(this.wing1, x * 4.5F, 2.0, w * 4.5F);
|
|
this.tickPart(this.wing2, x * -4.5F, 2.0, w * -4.5F);
|
|
if (this.level() instanceof ServerLevel serverLevel2 && this.hurtTime == 0) {
|
|
this.knockBack(
|
|
serverLevel2,
|
|
serverLevel2.getEntities(this, this.wing1.getBoundingBox().inflate(4.0, 2.0, 4.0).move(0.0, -2.0, 0.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR)
|
|
);
|
|
this.knockBack(
|
|
serverLevel2,
|
|
serverLevel2.getEntities(this, this.wing2.getBoundingBox().inflate(4.0, 2.0, 4.0).move(0.0, -2.0, 0.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR)
|
|
);
|
|
this.hurt(serverLevel2.getEntities(this, this.head.getBoundingBox().inflate(1.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
|
|
this.hurt(serverLevel2.getEntities(this, this.neck.getBoundingBox().inflate(1.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
|
|
}
|
|
|
|
float y = Mth.sin(this.getYRot() * (float) (Math.PI / 180.0) - this.yRotA * 0.01F);
|
|
float z = Mth.cos(this.getYRot() * (float) (Math.PI / 180.0) - this.yRotA * 0.01F);
|
|
float aa = this.getHeadYOffset();
|
|
this.tickPart(this.head, y * 6.5F * t, aa + u * 6.5F, -z * 6.5F * t);
|
|
this.tickPart(this.neck, y * 5.5F * t, aa + u * 5.5F, -z * 5.5F * t);
|
|
double[] ds = this.getLatencyPos(5, 1.0F);
|
|
|
|
for (int ab = 0; ab < 3; ab++) {
|
|
EnderDragonPart enderDragonPart = null;
|
|
if (ab == 0) {
|
|
enderDragonPart = this.tail1;
|
|
}
|
|
|
|
if (ab == 1) {
|
|
enderDragonPart = this.tail2;
|
|
}
|
|
|
|
if (ab == 2) {
|
|
enderDragonPart = this.tail3;
|
|
}
|
|
|
|
double[] es = this.getLatencyPos(12 + ab * 2, 1.0F);
|
|
float ac = this.getYRot() * (float) (Math.PI / 180.0) + this.rotWrap(es[0] - ds[0]) * (float) (Math.PI / 180.0);
|
|
float nx = Mth.sin(ac);
|
|
float o = Mth.cos(ac);
|
|
float p = 1.5F;
|
|
float ad = (ab + 1) * 2.0F;
|
|
this.tickPart(enderDragonPart, -(w * 1.5F + nx * ad) * t, es[1] - ds[1] - (ad + 1.5F) * u + 1.5, (x * 1.5F + o * ad) * t);
|
|
}
|
|
|
|
if (!this.level().isClientSide) {
|
|
this.inWall = this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox());
|
|
if (this.dragonFight != null) {
|
|
this.dragonFight.updateDragon(this);
|
|
}
|
|
}
|
|
|
|
for (int ab = 0; ab < this.subEntities.length; ab++) {
|
|
this.subEntities[ab].xo = vec3s[ab].x;
|
|
this.subEntities[ab].yo = vec3s[ab].y;
|
|
this.subEntities[ab].zo = vec3s[ab].z;
|
|
this.subEntities[ab].xOld = vec3s[ab].x;
|
|
this.subEntities[ab].yOld = vec3s[ab].y;
|
|
this.subEntities[ab].zOld = vec3s[ab].z;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void tickPart(EnderDragonPart part, double offsetX, double offsetY, double offsetZ) {
|
|
part.setPos(this.getX() + offsetX, this.getY() + offsetY, this.getZ() + offsetZ);
|
|
}
|
|
|
|
private float getHeadYOffset() {
|
|
if (this.phaseManager.getCurrentPhase().isSitting()) {
|
|
return -1.0F;
|
|
} else {
|
|
double[] ds = this.getLatencyPos(5, 1.0F);
|
|
double[] es = this.getLatencyPos(0, 1.0F);
|
|
return (float)(ds[1] - es[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the state of the dragon's current endercrystal.
|
|
*/
|
|
private void checkCrystals() {
|
|
if (this.nearestCrystal != null) {
|
|
if (this.nearestCrystal.isRemoved()) {
|
|
this.nearestCrystal = null;
|
|
} else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
|
|
this.setHealth(this.getHealth() + 1.0F);
|
|
}
|
|
}
|
|
|
|
if (this.random.nextInt(10) == 0) {
|
|
List<EndCrystal> list = this.level().getEntitiesOfClass(EndCrystal.class, this.getBoundingBox().inflate(32.0));
|
|
EndCrystal endCrystal = null;
|
|
double d = Double.MAX_VALUE;
|
|
|
|
for (EndCrystal endCrystal2 : list) {
|
|
double e = endCrystal2.distanceToSqr(this);
|
|
if (e < d) {
|
|
d = e;
|
|
endCrystal = endCrystal2;
|
|
}
|
|
}
|
|
|
|
this.nearestCrystal = endCrystal;
|
|
}
|
|
}
|
|
|
|
private void knockBack(ServerLevel level, List<Entity> targets) {
|
|
double d = (this.body.getBoundingBox().minX + this.body.getBoundingBox().maxX) / 2.0;
|
|
double e = (this.body.getBoundingBox().minZ + this.body.getBoundingBox().maxZ) / 2.0;
|
|
|
|
for (Entity entity : targets) {
|
|
if (entity instanceof LivingEntity livingEntity) {
|
|
double f = entity.getX() - d;
|
|
double g = entity.getZ() - e;
|
|
double h = Math.max(f * f + g * g, 0.1);
|
|
entity.push(f / h * 4.0, 0.2F, g / h * 4.0);
|
|
if (!this.phaseManager.getCurrentPhase().isSitting() && livingEntity.getLastHurtByMobTimestamp() < entity.tickCount - 2) {
|
|
DamageSource damageSource = this.damageSources().mobAttack(this);
|
|
entity.hurt(damageSource, 5.0F);
|
|
EnchantmentHelper.doPostAttackEffects(level, entity, damageSource);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attacks all entities inside this list, dealing 5 hearts of damage.
|
|
*/
|
|
private void hurt(List<Entity> entities) {
|
|
for (Entity entity : entities) {
|
|
if (entity instanceof LivingEntity) {
|
|
DamageSource damageSource = this.damageSources().mobAttack(this);
|
|
entity.hurt(damageSource, 10.0F);
|
|
if (this.level() instanceof ServerLevel serverLevel) {
|
|
EnchantmentHelper.doPostAttackEffects(serverLevel, entity, damageSource);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simplifies the value of a number by adding/subtracting 180 to the point that the number is between -180 and 180.
|
|
*/
|
|
private float rotWrap(double angle) {
|
|
return (float)Mth.wrapDegrees(angle);
|
|
}
|
|
|
|
/**
|
|
* Destroys all blocks that aren't associated with 'The End' inside the given bounding box.
|
|
*/
|
|
private boolean checkWalls(AABB area) {
|
|
int i = Mth.floor(area.minX);
|
|
int j = Mth.floor(area.minY);
|
|
int k = Mth.floor(area.minZ);
|
|
int l = Mth.floor(area.maxX);
|
|
int m = Mth.floor(area.maxY);
|
|
int n = Mth.floor(area.maxZ);
|
|
boolean bl = false;
|
|
boolean bl2 = false;
|
|
|
|
for (int o = i; o <= l; o++) {
|
|
for (int p = j; p <= m; p++) {
|
|
for (int q = k; q <= n; q++) {
|
|
BlockPos blockPos = new BlockPos(o, p, q);
|
|
BlockState blockState = this.level().getBlockState(blockPos);
|
|
if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) {
|
|
if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) {
|
|
bl2 = this.level().removeBlock(blockPos, false) || bl2;
|
|
} else {
|
|
bl = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bl2) {
|
|
BlockPos blockPos2 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(m - j + 1), k + this.random.nextInt(n - k + 1));
|
|
this.level().levelEvent(2008, blockPos2, 0);
|
|
}
|
|
|
|
return bl;
|
|
}
|
|
|
|
public boolean hurt(EnderDragonPart part, DamageSource source, float damage) {
|
|
if (this.phaseManager.getCurrentPhase().getPhase() == EnderDragonPhase.DYING) {
|
|
return false;
|
|
} else {
|
|
damage = this.phaseManager.getCurrentPhase().onHurt(source, damage);
|
|
if (part != this.head) {
|
|
damage = damage / 4.0F + Math.min(damage, 1.0F);
|
|
}
|
|
|
|
if (damage < 0.01F) {
|
|
return false;
|
|
} else {
|
|
if (source.getEntity() instanceof Player || source.is(DamageTypeTags.ALWAYS_HURTS_ENDER_DRAGONS)) {
|
|
float f = this.getHealth();
|
|
this.reallyHurt(source, damage);
|
|
if (this.isDeadOrDying() && !this.phaseManager.getCurrentPhase().isSitting()) {
|
|
this.setHealth(1.0F);
|
|
this.phaseManager.setPhase(EnderDragonPhase.DYING);
|
|
}
|
|
|
|
if (this.phaseManager.getCurrentPhase().isSitting()) {
|
|
this.sittingDamageReceived = this.sittingDamageReceived + f - this.getHealth();
|
|
if (this.sittingDamageReceived > 0.25F * this.getMaxHealth()) {
|
|
this.sittingDamageReceived = 0.0F;
|
|
this.phaseManager.setPhase(EnderDragonPhase.TAKEOFF);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hurt(DamageSource source, float amount) {
|
|
return !this.level().isClientSide ? this.hurt(this.body, source, amount) : false;
|
|
}
|
|
|
|
/**
|
|
* Provides a way to cause damage to an ender dragon.
|
|
*/
|
|
protected boolean reallyHurt(DamageSource damageSource, float amount) {
|
|
return super.hurt(damageSource, amount);
|
|
}
|
|
|
|
@Override
|
|
public void kill() {
|
|
this.remove(Entity.RemovalReason.KILLED);
|
|
this.gameEvent(GameEvent.ENTITY_DIE);
|
|
if (this.dragonFight != null) {
|
|
this.dragonFight.updateDragon(this);
|
|
this.dragonFight.setDragonKilled(this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void tickDeath() {
|
|
if (this.dragonFight != null) {
|
|
this.dragonFight.updateDragon(this);
|
|
}
|
|
|
|
this.dragonDeathTime++;
|
|
if (this.dragonDeathTime >= 180 && this.dragonDeathTime <= 200) {
|
|
float f = (this.random.nextFloat() - 0.5F) * 8.0F;
|
|
float g = (this.random.nextFloat() - 0.5F) * 4.0F;
|
|
float h = (this.random.nextFloat() - 0.5F) * 8.0F;
|
|
this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + f, this.getY() + 2.0 + g, this.getZ() + h, 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
boolean bl = this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
|
|
int i = 500;
|
|
if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
|
|
i = 12000;
|
|
}
|
|
|
|
if (this.level() instanceof ServerLevel) {
|
|
if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && bl) {
|
|
ExperienceOrb.award((ServerLevel)this.level(), this.position(), Mth.floor(i * 0.08F));
|
|
}
|
|
|
|
if (this.dragonDeathTime == 1 && !this.isSilent()) {
|
|
this.level().globalLevelEvent(1028, this.blockPosition(), 0);
|
|
}
|
|
}
|
|
|
|
this.move(MoverType.SELF, new Vec3(0.0, 0.1F, 0.0));
|
|
if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) {
|
|
if (bl) {
|
|
ExperienceOrb.award((ServerLevel)this.level(), this.position(), Mth.floor(i * 0.2F));
|
|
}
|
|
|
|
if (this.dragonFight != null) {
|
|
this.dragonFight.setDragonKilled(this);
|
|
}
|
|
|
|
this.remove(Entity.RemovalReason.KILLED);
|
|
this.gameEvent(GameEvent.ENTITY_DIE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates values for the fields pathPoints, and neighbors, and then returns the nearest pathPoint to the specified position.
|
|
*/
|
|
public int findClosestNode() {
|
|
if (this.nodes[0] == null) {
|
|
for (int i = 0; i < 24; i++) {
|
|
int j = 5;
|
|
int l;
|
|
int m;
|
|
if (i < 12) {
|
|
l = Mth.floor(60.0F * Mth.cos(2.0F * ((float) -Math.PI + (float) (Math.PI / 12) * i)));
|
|
m = Mth.floor(60.0F * Mth.sin(2.0F * ((float) -Math.PI + (float) (Math.PI / 12) * i)));
|
|
} else if (i < 20) {
|
|
int k = i - 12;
|
|
l = Mth.floor(40.0F * Mth.cos(2.0F * ((float) -Math.PI + (float) (Math.PI / 8) * k)));
|
|
m = Mth.floor(40.0F * Mth.sin(2.0F * ((float) -Math.PI + (float) (Math.PI / 8) * k)));
|
|
j += 10;
|
|
} else {
|
|
int var7 = i - 20;
|
|
l = Mth.floor(20.0F * Mth.cos(2.0F * ((float) -Math.PI + (float) (Math.PI / 4) * var7)));
|
|
m = Mth.floor(20.0F * Mth.sin(2.0F * ((float) -Math.PI + (float) (Math.PI / 4) * var7)));
|
|
}
|
|
|
|
int n = Math.max(this.level().getSeaLevel() + 10, this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, new BlockPos(l, 0, m)).getY() + j);
|
|
this.nodes[i] = new Node(l, n, m);
|
|
}
|
|
|
|
this.nodeAdjacency[0] = 6146;
|
|
this.nodeAdjacency[1] = 8197;
|
|
this.nodeAdjacency[2] = 8202;
|
|
this.nodeAdjacency[3] = 16404;
|
|
this.nodeAdjacency[4] = 32808;
|
|
this.nodeAdjacency[5] = 32848;
|
|
this.nodeAdjacency[6] = 65696;
|
|
this.nodeAdjacency[7] = 131392;
|
|
this.nodeAdjacency[8] = 131712;
|
|
this.nodeAdjacency[9] = 263424;
|
|
this.nodeAdjacency[10] = 526848;
|
|
this.nodeAdjacency[11] = 525313;
|
|
this.nodeAdjacency[12] = 1581057;
|
|
this.nodeAdjacency[13] = 3166214;
|
|
this.nodeAdjacency[14] = 2138120;
|
|
this.nodeAdjacency[15] = 6373424;
|
|
this.nodeAdjacency[16] = 4358208;
|
|
this.nodeAdjacency[17] = 12910976;
|
|
this.nodeAdjacency[18] = 9044480;
|
|
this.nodeAdjacency[19] = 9706496;
|
|
this.nodeAdjacency[20] = 15216640;
|
|
this.nodeAdjacency[21] = 13688832;
|
|
this.nodeAdjacency[22] = 11763712;
|
|
this.nodeAdjacency[23] = 8257536;
|
|
}
|
|
|
|
return this.findClosestNode(this.getX(), this.getY(), this.getZ());
|
|
}
|
|
|
|
/**
|
|
* Returns the index into pathPoints of the nearest PathPoint.
|
|
*/
|
|
public int findClosestNode(double x, double y, double z) {
|
|
float f = 10000.0F;
|
|
int i = 0;
|
|
Node node = new Node(Mth.floor(x), Mth.floor(y), Mth.floor(z));
|
|
int j = 0;
|
|
if (this.dragonFight == null || this.dragonFight.getCrystalsAlive() == 0) {
|
|
j = 12;
|
|
}
|
|
|
|
for (int k = j; k < 24; k++) {
|
|
if (this.nodes[k] != null) {
|
|
float g = this.nodes[k].distanceToSqr(node);
|
|
if (g < f) {
|
|
f = g;
|
|
i = k;
|
|
}
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* Find and return a path among the circles described by pathPoints, or null if the shortest path would just be directly between the start and finish with no intermediate points.
|
|
*
|
|
* Starting with pathPoint[startIdx], it searches the neighboring points (and their neighboring points, and so on) until it reaches pathPoint[finishIdx], at which point it calls makePath to seal the deal.
|
|
*/
|
|
@Nullable
|
|
public Path findPath(int startIndex, int finishIndex, @Nullable Node andThen) {
|
|
for (int i = 0; i < 24; i++) {
|
|
Node node = this.nodes[i];
|
|
node.closed = false;
|
|
node.f = 0.0F;
|
|
node.g = 0.0F;
|
|
node.h = 0.0F;
|
|
node.cameFrom = null;
|
|
node.heapIdx = -1;
|
|
}
|
|
|
|
Node node2 = this.nodes[startIndex];
|
|
Node node = this.nodes[finishIndex];
|
|
node2.g = 0.0F;
|
|
node2.h = node2.distanceTo(node);
|
|
node2.f = node2.h;
|
|
this.openSet.clear();
|
|
this.openSet.insert(node2);
|
|
Node node3 = node2;
|
|
int j = 0;
|
|
if (this.dragonFight == null || this.dragonFight.getCrystalsAlive() == 0) {
|
|
j = 12;
|
|
}
|
|
|
|
while (!this.openSet.isEmpty()) {
|
|
Node node4 = this.openSet.pop();
|
|
if (node4.equals(node)) {
|
|
if (andThen != null) {
|
|
andThen.cameFrom = node;
|
|
node = andThen;
|
|
}
|
|
|
|
return this.reconstructPath(node2, node);
|
|
}
|
|
|
|
if (node4.distanceTo(node) < node3.distanceTo(node)) {
|
|
node3 = node4;
|
|
}
|
|
|
|
node4.closed = true;
|
|
int k = 0;
|
|
|
|
for (int l = 0; l < 24; l++) {
|
|
if (this.nodes[l] == node4) {
|
|
k = l;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int lx = j; lx < 24; lx++) {
|
|
if ((this.nodeAdjacency[k] & 1 << lx) > 0) {
|
|
Node node5 = this.nodes[lx];
|
|
if (!node5.closed) {
|
|
float f = node4.g + node4.distanceTo(node5);
|
|
if (!node5.inOpenSet() || f < node5.g) {
|
|
node5.cameFrom = node4;
|
|
node5.g = f;
|
|
node5.h = node5.distanceTo(node);
|
|
if (node5.inOpenSet()) {
|
|
this.openSet.changeCost(node5, node5.g + node5.h);
|
|
} else {
|
|
node5.f = node5.g + node5.h;
|
|
this.openSet.insert(node5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node3 == node2) {
|
|
return null;
|
|
} else {
|
|
LOGGER.debug("Failed to find path from {} to {}", startIndex, finishIndex);
|
|
if (andThen != null) {
|
|
andThen.cameFrom = node3;
|
|
node3 = andThen;
|
|
}
|
|
|
|
return this.reconstructPath(node2, node3);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create and return a new PathEntity defining a path from the start to the finish, using the connections already made by the caller, findPath.
|
|
*/
|
|
private Path reconstructPath(Node start, Node finish) {
|
|
List<Node> list = Lists.<Node>newArrayList();
|
|
Node node = finish;
|
|
list.add(0, finish);
|
|
|
|
while (node.cameFrom != null) {
|
|
node = node.cameFrom;
|
|
list.add(0, node);
|
|
}
|
|
|
|
return new Path(list, new BlockPos(finish.x, finish.y, finish.z), true);
|
|
}
|
|
|
|
@Override
|
|
public void addAdditionalSaveData(CompoundTag compound) {
|
|
super.addAdditionalSaveData(compound);
|
|
compound.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
|
|
compound.putInt("DragonDeathTime", this.dragonDeathTime);
|
|
}
|
|
|
|
@Override
|
|
public void readAdditionalSaveData(CompoundTag compound) {
|
|
super.readAdditionalSaveData(compound);
|
|
if (compound.contains("DragonPhase")) {
|
|
this.phaseManager.setPhase(EnderDragonPhase.getById(compound.getInt("DragonPhase")));
|
|
}
|
|
|
|
if (compound.contains("DragonDeathTime")) {
|
|
this.dragonDeathTime = compound.getInt("DragonDeathTime");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void checkDespawn() {
|
|
}
|
|
|
|
public EnderDragonPart[] getSubEntities() {
|
|
return this.subEntities;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPickable() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public SoundSource getSoundSource() {
|
|
return SoundSource.HOSTILE;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getAmbientSound() {
|
|
return SoundEvents.ENDER_DRAGON_AMBIENT;
|
|
}
|
|
|
|
@Override
|
|
protected SoundEvent getHurtSound(DamageSource damageSource) {
|
|
return SoundEvents.ENDER_DRAGON_HURT;
|
|
}
|
|
|
|
@Override
|
|
protected float getSoundVolume() {
|
|
return 5.0F;
|
|
}
|
|
|
|
public float getHeadPartYOffset(int partIndex, double[] spineEndOffsets, double[] headPartOffsets) {
|
|
DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
|
|
EnderDragonPhase<? extends DragonPhaseInstance> enderDragonPhase = dragonPhaseInstance.getPhase();
|
|
double e;
|
|
if (enderDragonPhase == EnderDragonPhase.LANDING || enderDragonPhase == EnderDragonPhase.TAKEOFF) {
|
|
BlockPos blockPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
|
|
double d = Math.max(Math.sqrt(blockPos.distToCenterSqr(this.position())) / 4.0, 1.0);
|
|
e = partIndex / d;
|
|
} else if (dragonPhaseInstance.isSitting()) {
|
|
e = partIndex;
|
|
} else if (partIndex == 6) {
|
|
e = 0.0;
|
|
} else {
|
|
e = headPartOffsets[1] - spineEndOffsets[1];
|
|
}
|
|
|
|
return (float)e;
|
|
}
|
|
|
|
public Vec3 getHeadLookVector(float partialTicks) {
|
|
DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase();
|
|
EnderDragonPhase<? extends DragonPhaseInstance> enderDragonPhase = dragonPhaseInstance.getPhase();
|
|
Vec3 vec3;
|
|
if (enderDragonPhase == EnderDragonPhase.LANDING || enderDragonPhase == EnderDragonPhase.TAKEOFF) {
|
|
BlockPos blockPos = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
|
|
float f = Math.max((float)Math.sqrt(blockPos.distToCenterSqr(this.position())) / 4.0F, 1.0F);
|
|
float g = 6.0F / f;
|
|
float h = this.getXRot();
|
|
float i = 1.5F;
|
|
this.setXRot(-g * 1.5F * 5.0F);
|
|
vec3 = this.getViewVector(partialTicks);
|
|
this.setXRot(h);
|
|
} else if (dragonPhaseInstance.isSitting()) {
|
|
float j = this.getXRot();
|
|
float f = 1.5F;
|
|
this.setXRot(-45.0F);
|
|
vec3 = this.getViewVector(partialTicks);
|
|
this.setXRot(j);
|
|
} else {
|
|
vec3 = this.getViewVector(partialTicks);
|
|
}
|
|
|
|
return vec3;
|
|
}
|
|
|
|
public void onCrystalDestroyed(EndCrystal crystal, BlockPos pos, DamageSource damageSource) {
|
|
Player player;
|
|
if (damageSource.getEntity() instanceof Player) {
|
|
player = (Player)damageSource.getEntity();
|
|
} else {
|
|
player = this.level().getNearestPlayer(CRYSTAL_DESTROY_TARGETING, pos.getX(), pos.getY(), pos.getZ());
|
|
}
|
|
|
|
if (crystal == this.nearestCrystal) {
|
|
this.hurt(this.head, this.damageSources().explosion(crystal, player), 10.0F);
|
|
}
|
|
|
|
this.phaseManager.getCurrentPhase().onCrystalDestroyed(crystal, pos, damageSource, player);
|
|
}
|
|
|
|
@Override
|
|
public void onSyncedDataUpdated(EntityDataAccessor<?> dataAccessor) {
|
|
if (DATA_PHASE.equals(dataAccessor) && this.level().isClientSide) {
|
|
this.phaseManager.setPhase(EnderDragonPhase.getById(this.getEntityData().get(DATA_PHASE)));
|
|
}
|
|
|
|
super.onSyncedDataUpdated(dataAccessor);
|
|
}
|
|
|
|
public EnderDragonPhaseManager getPhaseManager() {
|
|
return this.phaseManager;
|
|
}
|
|
|
|
@Nullable
|
|
public EndDragonFight getDragonFight() {
|
|
return this.dragonFight;
|
|
}
|
|
|
|
@Override
|
|
public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected boolean canRide(Entity vehicle) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean canUsePortal(boolean allowPassengers) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void recreateFromPacket(ClientboundAddEntityPacket packet) {
|
|
super.recreateFromPacket(packet);
|
|
EnderDragonPart[] enderDragonParts = this.getSubEntities();
|
|
|
|
for (int i = 0; i < enderDragonParts.length; i++) {
|
|
enderDragonParts[i].setId(i + packet.getId());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canAttack(LivingEntity target) {
|
|
return target.canBeSeenAsEnemy();
|
|
}
|
|
|
|
@Override
|
|
protected float sanitizeScale(float scale) {
|
|
return 1.0F;
|
|
}
|
|
}
|