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.Attributes; import net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder; 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 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 DragonFlightHistory flightHistory = new DragonFlightHistory(); 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 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.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 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(net.minecraft.network.syncher.SynchedEntityData.Builder builder) { super.defineSynchedData(builder); builder.define(DATA_PHASE, EnderDragonPhase.HOVERING.getId()); } @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 { this.flightHistory.record(this.getY(), this.getYRot()); if (this.level() instanceof ServerLevel serverLevel2) { DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase(); dragonPhaseInstance.doServerTick(serverLevel2); if (this.phaseManager.getCurrentPhase() != dragonPhaseInstance) { dragonPhaseInstance = this.phaseManager.getCurrentPhase(); dragonPhaseInstance.doServerTick(serverLevel2); } Vec3 vec32 = dragonPhaseInstance.getFlyTargetLocation(); if (vec32 != null) { double d = vec32.x - this.getX(); double e = vec32.y - this.getY(); double i = vec32.z - this.getZ(); double j = d * d + e * e + i * i; float k = dragonPhaseInstance.getFlySpeed(); double l = Math.sqrt(d * d + i * i); if (l > 0.0) { e = Mth.clamp(e / l, (double)(-k), (double)k); } 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 m = Math.max(((float)vec34.dot(vec33) + 0.5F) / 1.5F, 0.0F); if (Math.abs(d) > 1.0E-5F || Math.abs(i) > 1.0E-5F) { float n = Mth.clamp(Mth.wrapDegrees(180.0F - (float)Mth.atan2(d, i) * (180.0F / (float)Math.PI) - this.getYRot()), -50.0F, 50.0F); this.yRotA *= 0.8F; this.yRotA = this.yRotA + n * dragonPhaseInstance.getTurnSpeed(); this.setYRot(this.getYRot() + this.yRotA * 0.1F); } float n = (float)(2.0 / (j + 1.0)); float o = 0.06F; this.moveRelative(0.06F * (m * n + (1.0F - n)), 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 p = 0.8 + 0.15 * (vec35.dot(vec34) + 1.0) / 2.0; this.setDeltaMovement(this.getDeltaMovement().multiply(p, 0.91F, p)); } } else { if (this.lerpSteps > 0) { this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot); this.lerpSteps--; } this.phaseManager.getCurrentPhase().doClientTick(); } if (!this.level().isClientSide()) { this.applyEffectsFromBlocks(); } this.yBodyRot = this.getYRot(); Vec3[] vec3s = new Vec3[this.subEntities.length]; for (int q = 0; q < this.subEntities.length; q++) { vec3s[q] = new Vec3(this.subEntities[q].getX(), this.subEntities[q].getY(), this.subEntities[q].getZ()); } float r = (float)(this.flightHistory.get(5).y() - this.flightHistory.get(10).y()) * 10.0F * (float) (Math.PI / 180.0); float s = Mth.cos(r); float t = Mth.sin(r); float u = this.getYRot() * (float) (Math.PI / 180.0); float v = Mth.sin(u); float w = Mth.cos(u); this.tickPart(this.body, v * 0.5F, 0.0, -w * 0.5F); this.tickPart(this.wing1, w * 4.5F, 2.0, v * 4.5F); this.tickPart(this.wing2, w * -4.5F, 2.0, v * -4.5F); if (this.level() instanceof ServerLevel serverLevel3 && this.hurtTime == 0) { this.knockBack( serverLevel3, serverLevel3.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( serverLevel3, serverLevel3.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(serverLevel3, serverLevel3.getEntities(this, this.head.getBoundingBox().inflate(1.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); this.hurt(serverLevel3, serverLevel3.getEntities(this, this.neck.getBoundingBox().inflate(1.0), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); } float x = Mth.sin(this.getYRot() * (float) (Math.PI / 180.0) - this.yRotA * 0.01F); float y = Mth.cos(this.getYRot() * (float) (Math.PI / 180.0) - this.yRotA * 0.01F); float z = this.getHeadYOffset(); this.tickPart(this.head, x * 6.5F * s, z + t * 6.5F, -y * 6.5F * s); this.tickPart(this.neck, x * 5.5F * s, z + t * 5.5F, -y * 5.5F * s); DragonFlightHistory.Sample sample = this.flightHistory.get(5); for (int aa = 0; aa < 3; aa++) { EnderDragonPart enderDragonPart = null; if (aa == 0) { enderDragonPart = this.tail1; } if (aa == 1) { enderDragonPart = this.tail2; } if (aa == 2) { enderDragonPart = this.tail3; } DragonFlightHistory.Sample sample2 = this.flightHistory.get(12 + aa * 2); float ab = this.getYRot() * (float) (Math.PI / 180.0) + this.rotWrap(sample2.yRot() - sample.yRot()) * (float) (Math.PI / 180.0); float ac = Mth.sin(ab); float mx = Mth.cos(ab); float n = 1.5F; float o = (aa + 1) * 2.0F; this.tickPart(enderDragonPart, -(v * 1.5F + ac * o) * s, sample2.y() - sample.y() - (o + 1.5F) * t + 1.5, (w * 1.5F + mx * o) * s); } if (this.level() instanceof ServerLevel serverLevel4) { this.inWall = this.checkWalls(serverLevel4, this.head.getBoundingBox()) | this.checkWalls(serverLevel4, this.neck.getBoundingBox()) | this.checkWalls(serverLevel4, this.body.getBoundingBox()); if (this.dragonFight != null) { this.dragonFight.updateDragon(this); } } for (int aa = 0; aa < this.subEntities.length; aa++) { this.subEntities[aa].xo = vec3s[aa].x; this.subEntities[aa].yo = vec3s[aa].y; this.subEntities[aa].zo = vec3s[aa].z; this.subEntities[aa].xOld = vec3s[aa].x; this.subEntities[aa].yOld = vec3s[aa].y; this.subEntities[aa].zOld = vec3s[aa].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 { DragonFlightHistory.Sample sample = this.flightHistory.get(5); DragonFlightHistory.Sample sample2 = this.flightHistory.get(0); return (float)(sample.y() - sample2.y()); } } /** * 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 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 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.hurtServer(level, damageSource, 5.0F); EnchantmentHelper.doPostAttackEffects(level, entity, damageSource); } } } } private void hurt(ServerLevel serverLevel, List list) { for (Entity entity : list) { if (entity instanceof LivingEntity) { DamageSource damageSource = this.damageSources().mobAttack(this); entity.hurtServer(serverLevel, damageSource, 10.0F); 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); } private boolean checkWalls(ServerLevel serverLevel, AABB aABB) { int i = Mth.floor(aABB.minX); int j = Mth.floor(aABB.minY); int k = Mth.floor(aABB.minZ); int l = Mth.floor(aABB.maxX); int m = Mth.floor(aABB.maxY); int n = Mth.floor(aABB.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 = serverLevel.getBlockState(blockPos); if (!blockState.isAir() && !blockState.is(BlockTags.DRAGON_TRANSPARENT)) { if (serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockState.is(BlockTags.DRAGON_IMMUNE)) { bl2 = serverLevel.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)); serverLevel.levelEvent(2008, blockPos2, 0); } return bl; } public boolean hurt(ServerLevel serverLevel, EnderDragonPart enderDragonPart, DamageSource damageSource, float f) { if (this.phaseManager.getCurrentPhase().getPhase() == EnderDragonPhase.DYING) { return false; } else { f = this.phaseManager.getCurrentPhase().onHurt(damageSource, f); if (enderDragonPart != this.head) { f = f / 4.0F + Math.min(f, 1.0F); } if (f < 0.01F) { return false; } else { if (damageSource.getEntity() instanceof Player || damageSource.is(DamageTypeTags.ALWAYS_HURTS_ENDER_DRAGONS)) { float g = this.getHealth(); this.reallyHurt(serverLevel, damageSource, f); 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 + g - this.getHealth(); if (this.sittingDamageReceived > 0.25F * this.getMaxHealth()) { this.sittingDamageReceived = 0.0F; this.phaseManager.setPhase(EnderDragonPhase.TAKEOFF); } } } return true; } } } @Override public boolean hurtServer(ServerLevel serverLevel, DamageSource damageSource, float f) { return this.hurt(serverLevel, this.body, damageSource, f); } protected void reallyHurt(ServerLevel serverLevel, DamageSource damageSource, float f) { super.hurtServer(serverLevel, damageSource, f); } @Override public void kill(ServerLevel serverLevel) { 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); } int i = 500; if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { i = 12000; } if (this.level() instanceof ServerLevel serverLevel) { if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ExperienceOrb.award(serverLevel, this.position(), Mth.floor(i * 0.08F)); } if (this.dragonDeathTime == 1 && !this.isSilent()) { serverLevel.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 serverLevel) { if (serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { ExperienceOrb.award(serverLevel, 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(73, 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 list = Lists.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 Vec3 getHeadLookVector(float partialTicks) { DragonPhaseInstance dragonPhaseInstance = this.phaseManager.getCurrentPhase(); EnderDragonPhase 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(ServerLevel serverLevel, EndCrystal endCrystal, BlockPos blockPos, DamageSource damageSource) { Player player; if (damageSource.getEntity() instanceof Player) { player = (Player)damageSource.getEntity(); } else { player = serverLevel.getNearestPlayer(CRYSTAL_DESTROY_TARGETING, blockPos.getX(), blockPos.getY(), blockPos.getZ()); } if (endCrystal == this.nearestCrystal) { this.hurt(serverLevel, this.head, this.damageSources().explosion(endCrystal, player), 10.0F); } this.phaseManager.getCurrentPhase().onCrystalDestroyed(endCrystal, blockPos, 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; } }