aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.World/EnderDragon.cpp
diff options
context:
space:
mode:
authordaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
committerdaoge_cmd <3523206925@qq.com>2026-03-01 12:16:08 +0800
commitb691c43c44ff180d10e7d4a9afc83b98551ff586 (patch)
tree3e9849222cbc6ba49f2f1fc6e5fe7179632c7390 /Minecraft.World/EnderDragon.cpp
parentdef8cb415354ac390b7e89052a50605285f1aca9 (diff)
Initial commit
Diffstat (limited to 'Minecraft.World/EnderDragon.cpp')
-rw-r--r--Minecraft.World/EnderDragon.cpp1941
1 files changed, 1941 insertions, 0 deletions
diff --git a/Minecraft.World/EnderDragon.cpp b/Minecraft.World/EnderDragon.cpp
new file mode 100644
index 00000000..2ccd453c
--- /dev/null
+++ b/Minecraft.World/EnderDragon.cpp
@@ -0,0 +1,1941 @@
+#include "stdafx.h"
+#include "net.minecraft.world.level.h"
+#include "net.minecraft.world.level.tile.h"
+#include "net.minecraft.world.entity.h"
+#include "net.minecraft.world.entity.boss.h"
+#include "net.minecraft.world.entity.projectile.h"
+#include "net.minecraft.world.phys.h"
+#include "net.minecraft.world.damagesource.h"
+#include "BasicTypeContainers.h"
+#include "..\Minecraft.Client\Textures.h"
+#include "net.minecraft.world.entity.boss.enderdragon.h"
+#include "net.minecraft.world.level.pathfinder.h"
+#include "SharedConstants.h"
+#include "EnderDragon.h"
+
+#define PRINT_DRAGON_STATE_CHANGE_MESSAGES 1
+
+
+
+// 4J Added for new dragon behaviour
+const int EnderDragon::CRYSTAL_COUNT = 8;
+const int EnderDragon::FLAME_TICKS = 60;
+const float EnderDragon::FLAME_ANGLE = 22.5f;
+const int EnderDragon::FLAME_PASSES = 4; // How many times it covers FLAME_ANGLE in FLAME_TICKS
+const int EnderDragon::FLAME_FREQUENCY = 2; // Every FLAME_FREQUENCY ticks it sets fire to blocks while doing a flame pass
+const int EnderDragon::FLAME_RANGE = 10;
+
+const int EnderDragon::ATTACK_TICKS = SharedConstants::TICKS_PER_SECOND * 2; // Time for the dragon roar to play
+
+const int EnderDragon::SITTING_ATTACK_Y_VIEW_RANGE = 10; // The player must be now lower and no higher than the dragon by this amount
+const int EnderDragon::SITTING_ATTACK_VIEW_RANGE = EnderDragon::FLAME_RANGE * 2;
+const int EnderDragon::SITTING_ATTACK_RANGE = EnderDragon::FLAME_RANGE * 2;
+const int EnderDragon::SITTING_POST_ATTACK_IDLE_TICKS = 40;
+const int EnderDragon::SITTING_SCANNING_IDLE_TICKS = 100;
+const int EnderDragon::SITTING_FLAME_ATTACKS_COUNT = 4; // How many times the dragons does the scan/roar/flame cycle before flying off
+
+// The percentage of max health that the dragon will take while in the "Sitting" states before flying away
+const float EnderDragon::SITTING_ALLOWED_DAMAGE_PERCENTAGE = 0.25f;
+
+void EnderDragon::_init()
+{
+ // 4J Stu - This function call had to be moved here from the Entity ctor to ensure that
+ // the derived version of the function is called
+ this->defineSynchedData();
+
+ // 4J Stu - This function call had to be moved here from the Entity ctor to ensure that the derived version of the function is called
+ health = getMaxHealth();
+
+ xTarget = yTarget = zTarget = 0.0;
+ posPointer = -1;
+ oFlapTime = 0;
+ flapTime = 0;
+ newTarget = false;
+ inWall = false;
+ attackTarget = nullptr;
+ dragonDeathTime = 0;
+ nearestCrystal = nullptr;
+
+ // 4J Stu - Added for new dragon behaviour
+ m_remainingCrystalsCount = CRYSTAL_COUNT;
+ m_fireballCharge = 0;
+ m_holdingPatternAngle = 0.0f;
+ m_holdingPatternClockwise = true;
+ setSynchedAction(e_EnderdragonAction_HoldingPattern);
+ m_actionTicks = 0;
+ m_sittingDamageReceived = 0;
+ m_headYRot = 0.0;
+ m_acidArea = AABB::newPermanent(-4,-10,-3,6,3,3);
+ m_flameAttacks = 0;
+
+ for (int i = 0; i < positionsLength; i++)
+ {
+ positions[i][0] = 0;
+ positions[i][1] = 0;
+ positions[i][2] = 0;
+ }
+
+ m_nodes = new NodeArray(24);
+ openSet = new BinaryHeap();
+ m_currentPath = NULL;
+}
+
+EnderDragon::EnderDragon(Level *level) : BossMob(level)
+{
+ _init();
+
+ head = shared_ptr<BossMobPart>( new BossMobPart(this, L"head", 6, 6) );
+ neck = shared_ptr<BossMobPart>( new BossMobPart(this, L"neck", 6, 6) ); // 4J Added
+ body = shared_ptr<BossMobPart>( new BossMobPart(this, L"body", 8, 8) );
+ tail1 = shared_ptr<BossMobPart>( new BossMobPart(this, L"tail", 4, 4) );
+ tail2 = shared_ptr<BossMobPart>( new BossMobPart(this, L"tail", 4, 4) );
+ tail3 = shared_ptr<BossMobPart>( new BossMobPart(this, L"tail", 4, 4) );
+ wing1 = shared_ptr<BossMobPart>( new BossMobPart(this, L"wing", 4, 4) );
+ wing2 = shared_ptr<BossMobPart>( new BossMobPart(this, L"wing", 4, 4) );
+
+ subEntities.push_back(head);
+ subEntities.push_back(neck); // 4J Added
+ subEntities.push_back(body);
+ subEntities.push_back(tail1);
+ subEntities.push_back(tail2);
+ subEntities.push_back(tail3);
+ subEntities.push_back(wing1);
+ subEntities.push_back(wing2);
+
+ maxHealth = 200;
+ setHealth(maxHealth);
+
+ this->textureIdx = TN_MOB_ENDERDRAGON; // 4J was "/mob/enderdragon/ender.png";
+ setSize(16, 8);
+
+ noPhysics = true;
+ fireImmune = true;
+
+ yTarget = 100;
+
+ m_iGrowlTimer=100;
+
+ noCulling = true;
+}
+
+EnderDragon::~EnderDragon()
+{
+ if(m_nodes->data != NULL)
+ {
+ for(unsigned int i = 0; i < m_nodes->length; ++i)
+ {
+ if(m_nodes->data[i]!=NULL) delete m_nodes->data[i];
+ }
+ delete [] m_nodes->data;
+ }
+ delete openSet;
+ if( m_currentPath != NULL ) delete m_currentPath;
+}
+
+void EnderDragon::defineSynchedData()
+{
+ BossMob::defineSynchedData();
+
+ entityData->define(DATA_ID_SYNCHED_HEALTH, maxHealth);
+
+ // 4J Added for new dragon behaviour
+ entityData->define(DATA_ID_SYNCHED_ACTION, e_EnderdragonAction_HoldingPattern);
+}
+
+void EnderDragon::getLatencyPos(doubleArray result, int step, float a)
+{
+ if (health <= 0)
+ {
+ a = 0;
+ }
+
+ a = 1 - a;
+
+ int p0 = (posPointer - step * 1) & 63;
+ int p1 = (posPointer - step * 1 - 1) & 63;
+
+ // positions is a ring buffer of size positionsLength (64) storing positional information per tick
+ // positions[i][0] is y rotation
+ // positions[i][1] is y position
+ // positions[i][2] is currently always 0
+
+ double yr0 = positions[p0][0];
+ double yrd = Mth::wrapDegrees(positions[p1][0] - yr0);
+ result[0] = yr0 + yrd * a;
+
+ yr0 = positions[p0][1];
+ yrd = positions[p1][1] - yr0;
+
+ result[1] = yr0 + yrd * a;
+ result[2] = positions[p0][2] + (positions[p1][2] - positions[p0][2]) * a;
+}
+
+void EnderDragon::aiStep()
+{
+ if (!level->isClientSide)
+ {
+ entityData->set(DATA_ID_SYNCHED_HEALTH, health);
+ }
+ else
+ {
+ // 4J Stu - If saved when dead we need to make sure that the actual health is updated correctly on the client
+ // Fix for TU9: Content: Gameplay: Enderdragon respawns after loading game which was previously saved at point of hes death
+ health = getSynchedHealth();
+
+ float flap = Mth::cos(flapTime * PI * 2);
+ float oldFlap = Mth::cos(oFlapTime * PI * 2);
+
+ if (oldFlap <= -0.3f && flap >= -0.3f)
+ {
+ level->playLocalSound(x, y, z, eSoundType_MOB_ENDERDRAGON_MOVE, 1, 0.8f + random->nextFloat() * .3f, 100.0f);
+ }
+ // play a growl every now and then
+ if(! (getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking))
+ {
+ m_iGrowlTimer--;
+ if(m_iGrowlTimer<0)
+ {
+ level->playLocalSound(x, y, z, eSoundType_MOB_ENDERDRAGON_GROWL, 0.5f, 0.8f + random->nextFloat() * .3f, 100.0f);
+ m_iGrowlTimer=200+(random->nextInt(200));
+ }
+ }
+ }
+
+ oFlapTime = flapTime;
+
+ if (health <= 0)
+ {
+ // level.addParticle("explode", x + random.nextFloat() * bbWidth * 2 - bbWidth, y + random.nextFloat() * bbHeight, z + random.nextFloat() * bbWidth * 2 - bbWidth, 0, 0, 0);
+ float xo = (random->nextFloat() - 0.5f) * 8;
+ float yo = (random->nextFloat() - 0.5f) * 4;
+ float zo = (random->nextFloat() - 0.5f) * 8;
+ level->addParticle(eParticleType_largeexplode, x + xo, y + 2 + yo, z + zo, 0, 0, 0);
+ return;
+ }
+
+ checkCrystals();
+
+ float flapSpeed = 0.2f / (sqrt(xd * xd + zd * zd) * 10.0f + 1);
+ flapSpeed *= (float) pow(2.0, yd);
+ if ( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ {
+ //app.DebugPrintf("flapSpeed is %f\n", flapSpeed);
+ //flapTime += flapSpeed * 2;
+ flapTime += 0.1f;
+ }
+ else if (inWall)
+ {
+ flapTime += flapSpeed * 0.5f;
+ }
+ else
+ {
+ flapTime += flapSpeed;
+ }
+
+ yRot = Mth::wrapDegrees(yRot);
+
+ if (posPointer < 0)
+ {
+ for (int i = 0; i < positionsLength; i++)
+ {
+ positions[i][0] = yRot;
+ positions[i][1] = y;
+ }
+ }
+
+ if (++posPointer == positionsLength) posPointer = 0;
+ positions[posPointer][0] = yRot;
+ positions[posPointer][1] = y;
+
+
+ if (level->isClientSide)
+ {
+ if (lSteps > 0)
+ {
+ double xt = x + (lx - x) / lSteps;
+ double yt = y + (ly - y) / lSteps;
+ double zt = z + (lz - z) / lSteps;
+
+ // 4J Stu - The movement is so small that this head animation doesn't look good
+ //if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming )
+ //{
+ // double yrd = lyr - (yRot + m_headYRot);
+ // while (yrd < -180)
+ // yrd += 360;
+ // while (yrd >= 180)
+ // yrd -= 360;
+
+ // m_headYRot += (yrd) / lSteps;
+ //}
+ //else
+ {
+ double yrd = Mth::wrapDegrees(lyr - yRot);
+
+ m_headYRot = 0.0;
+ yRot += (yrd) / lSteps;
+ }
+ xRot += (lxr - xRot) / lSteps;
+
+ lSteps--;
+ this->setPos(xt, yt, zt);
+ this->setRot(yRot, xRot);
+
+ /*
+ * List<AABB> collisions = level.getCubes(this, bb.shrink(1 / 32.0, 0, 1 /
+ * 32.0)); if (collisions.size() > 0) { double yTop = 0; for (int i = 0; i <
+ * collisions.size(); i++) { AABB ab = collisions.get(i); if (ab.y1 > yTop) yTop
+ * = ab.y1; } yt += yTop - bb.y0; setPos(xt, yt, zt); }
+ */
+
+ }
+
+ if( getSynchedAction() == e_EnderdragonAction_Landing || (getSynchedAction() == e_EnderdragonAction_Sitting_Flaming && tickCount%2==0) )
+ {
+ double xP = 0.0;
+ double yP = 0.0;
+ double zP = 0.0;
+ Vec3 *v = getHeadLookVector(1); //getViewVector(1);
+ //app.DebugPrintf("View vector is (%f,%f,%f) - lsteps %d\n", v->x, v->y, v->z, lSteps);
+ //unsigned int d = 0;
+ //for(unsigned int d = 1; d < 3; ++d)
+ {
+ Vec3 *vN = v->normalize();
+ vN->yRot(-PI/4);
+ for(unsigned int i = 0; i < 8; ++i)
+ {
+ if(getSynchedAction() == e_EnderdragonAction_Landing)
+ {
+ //for(unsigned int j = 0; j < 6; ++j)
+ {
+ xP = head->x;// - vN->x * d;
+ yP = head->bb->y0 + head->bbHeight / 2;// - vN->y * d; //head->y + head->bbHeight / 2 + 0.5f - v->y * d;
+ zP = head->z;// - vN->z * d;
+ xP += (level->random->nextBoolean()?1:-1) * level->random->nextFloat()/2;
+ yP += (level->random->nextBoolean()?1:-1) * level->random->nextFloat()/2;
+ zP += (level->random->nextBoolean()?1:-1) * level->random->nextFloat()/2;
+ level->addParticle(eParticleType_dragonbreath, xP, yP, zP, (-vN->x * 0.08) + xd, (-vN->y * 0.3) + yd, (-vN->z * 0.08) + zd);
+ }
+ }
+ else
+ {
+ double yVelocity = 0.6;
+ double xzVelocity = 0.08;
+ for(unsigned int j = 0; j < 6; ++j)
+ {
+ xP = head->x;// - vN->x * d;
+ yP = head->bb->y0 + head->bbHeight / 2;// - vN->y * d; //head->y + head->bbHeight / 2 + 0.5f - v->y * d;
+ zP = head->z;// - vN->z * d;
+ xP += (level->random->nextBoolean()?1:-1) * level->random->nextFloat()/2;
+ yP += (level->random->nextBoolean()?1:-1) * level->random->nextFloat()/2;
+ zP += (level->random->nextBoolean()?1:-1) * level->random->nextFloat()/2;
+ level->addParticle(eParticleType_dragonbreath, xP, yP, zP, -vN->x * xzVelocity*j, -vN->y * yVelocity, -vN->z * xzVelocity*j);
+ }
+ }
+ vN->yRot(PI/(2*8) );
+ }
+ }
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Attacking )
+ {
+ // AP - changed this to use playLocalSound because no sound could be heard with playSound (cos it's a stub function)
+ level->playLocalSound(x, y, z, eSoundType_MOB_ENDERDRAGON_GROWL, 0.5f, 0.8f + random->nextFloat() * .3f, 100.0f);
+ }
+ }
+ else
+ {
+ double xdd = xTarget - x;
+ double ydd = yTarget - y;
+ double zdd = zTarget - z;
+
+ double dist = xdd * xdd + ydd * ydd + zdd * zdd;
+
+ if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming )
+ {
+ --m_actionTicks;
+ if(m_actionTicks <= 0)
+ {
+ if( m_flameAttacks >= SITTING_FLAME_ATTACKS_COUNT)
+ {
+ setSynchedAction(e_EnderdragonAction_Takeoff);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: Takeoff\n");
+#endif
+ newTarget = true;
+ }
+ else
+ {
+ setSynchedAction(e_EnderdragonAction_Sitting_Scanning);
+ attackTarget = level->getNearestPlayer( shared_from_this(), SITTING_ATTACK_VIEW_RANGE, SITTING_ATTACK_Y_VIEW_RANGE );
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: SittingScanning\n");
+#endif
+ }
+ }
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Scanning )
+ {
+ attackTarget = level->getNearestPlayer( shared_from_this(), SITTING_ATTACK_VIEW_RANGE, SITTING_ATTACK_Y_VIEW_RANGE );
+
+ ++m_actionTicks;
+ if( attackTarget != NULL )
+ {
+ if(m_actionTicks > SITTING_SCANNING_IDLE_TICKS/4)
+ {
+ setSynchedAction(e_EnderdragonAction_Sitting_Attacking);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: SittingAttacking\n");
+#endif
+ m_actionTicks = ATTACK_TICKS;
+ }
+ }
+ else
+ {
+ if(m_actionTicks >= SITTING_SCANNING_IDLE_TICKS)
+ {
+ setSynchedAction(e_EnderdragonAction_Takeoff);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: Takeoff\n");
+#endif
+ newTarget = true;
+ }
+ }
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Attacking )
+ {
+ --m_actionTicks;
+ if(m_actionTicks <= 0)
+ {
+ ++m_flameAttacks;
+ setSynchedAction(e_EnderdragonAction_Sitting_Flaming);
+ attackTarget = level->getNearestPlayer( shared_from_this(), SITTING_ATTACK_VIEW_RANGE, SITTING_ATTACK_Y_VIEW_RANGE );
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: SittingFlaming\n");
+#endif
+ m_actionTicks = FLAME_TICKS;
+ }
+ }
+ else if( !newTarget && getSynchedAction() == e_EnderdragonAction_Takeoff)
+ {
+ int eggHeight = level->getTopSolidBlock(PODIUM_X_POS,PODIUM_Z_POS); //level->getHeightmap(4,4);
+
+ float dist = distanceToSqr(PODIUM_X_POS, eggHeight, PODIUM_Z_POS);
+ if(dist > (10.0f * 10.0f) )
+ {
+ setSynchedAction(e_EnderdragonAction_HoldingPattern);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: HoldingPattern\n");
+#endif
+ }
+ }
+ else if (newTarget || ( (getSynchedAction() != e_EnderdragonAction_Landing && dist < 10 * 10) || dist < 1) || dist > 150 * 150 || horizontalCollision || verticalCollision)
+ {
+ findNewTarget();
+ }
+
+ if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming || getSynchedAction() == e_EnderdragonAction_Landing )
+ {
+ if( m_actionTicks < (FLAME_TICKS - 10) )
+ {
+ vector<shared_ptr<Entity> > *targets = level->getEntities(shared_from_this(), m_acidArea);
+
+ for( AUTO_VAR(it, targets->begin() ); it != targets->end(); ++it)
+ {
+ shared_ptr<Mob> e = dynamic_pointer_cast<Mob>( *it );
+ if (e != NULL)
+ {
+ //app.DebugPrintf("Attacking entity with acid\n");
+ e->hurt(DamageSource::dragonbreath, 2);
+ }
+ }
+ }
+ }
+ if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming )
+ {
+ // No movement
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Scanning )
+ {
+ if( attackTarget != NULL)
+ {
+ Vec3 *aim = Vec3::newTemp((attackTarget->x - x), 0, (attackTarget->z - z))->normalize();
+ Vec3 *dir = Vec3::newTemp(sin(yRot * PI / 180), 0, -cos(yRot * PI / 180))->normalize();
+ float dot = (float)dir->dot(aim);
+ float angleDegs = acos(dot)*180/PI;
+ angleDegs = angleDegs + 0.5f;
+
+ if( angleDegs < 0 || angleDegs > 10 )
+ {
+ double xdd = attackTarget->x - head->x;
+ //double ydd = (attackTarget->bb->y0 + attackTarget->bbHeight / 2) - (head->y + head->bbHeight / 2);
+ double zdd = attackTarget->z - head->z;
+
+ double yRotT = (180) - atan2(xdd, zdd) * 180 / PI;
+ double yRotD = Mth::wrapDegrees(yRotT - yRot);
+
+ if (yRotD > 50) yRotD = 50;
+ if (yRotD < -50) yRotD = -50;
+
+ double xd = xTarget - x;
+ double zd = zTarget - z;
+ yRotA *= 0.80f;
+
+ float rotSpeed = sqrt(xd * xd + zd * zd) * 1 + 1;
+ double distToTarget = sqrt(xd * xd + zd * zd) * 1 + 1;
+ if (distToTarget > 40) distToTarget = 40;
+ yRotA += yRotD * ((0.7f / distToTarget) / rotSpeed);
+ yRot += yRotA;
+ }
+ else
+ {
+ //setSynchedAction(e_EnderdragonAction_Sitting_Flaming);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ //app.DebugPrintf("Dragon action is now : SittingFlaming\n");
+#endif
+ //m_actionTicks = FLAME_TICKS;
+ }
+ }
+ else
+ {
+ //setSynchedAction(e_EnderdragonAction_Sitting_Flaming);
+ //app.DebugPrintf("Dragon action is now : SittingFlaming\n");
+ //m_actionTicks = 0;
+ }
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Attacking )
+ {
+
+ }
+ else
+ {
+// double xTargetO = xTarget;
+// double yTargetO = yTarget;
+// double zTargetO = zTarget;
+ if (getSynchedAction() == e_EnderdragonAction_StrafePlayer && attackTarget != NULL && m_currentPath != NULL && m_currentPath->isDone())
+ {
+ xTarget = attackTarget->x;
+ zTarget = attackTarget->z;
+
+ double xd = xTarget - x;
+ double zd = zTarget - z;
+ double sd = sqrt(xd * xd + zd * zd);
+ double ho = 0.4f + sd / 80.0f - 1;
+ if (ho > 10) ho = 10;
+ yTarget = attackTarget->bb->y0 + ho;
+ }
+ else
+ {
+ //xTarget += random->nextGaussian() * 2;
+ //zTarget += random->nextGaussian() * 2;
+ }
+ ydd = ydd / (sqrt(xdd * xdd + zdd * zdd));
+ float max = 0.6f;
+ if(getSynchedAction() == e_EnderdragonAction_Landing) max = 1.5f;
+ if (ydd < -max) ydd = -max;
+ if (ydd > max) ydd = max;
+ yd += (ydd) * 0.1f;
+ while (yRot < -180)
+ yRot += 180 * 2;
+ while (yRot >= 180)
+ yRot -= 180 * 2;
+
+
+ double yRotT = (180) - atan2(xdd, zdd) * 180 / PI;
+ double yRotD = yRotT - yRot;
+ while (yRotD < -180)
+ yRotD += 180 * 2;
+ while (yRotD >= 180)
+ yRotD -= 180 * 2;
+
+
+ if (yRotD > 50) yRotD = 50;
+ if (yRotD < -50) yRotD = -50;
+
+ Vec3 *aim = Vec3::newTemp((xTarget - x), (yTarget - y), (zTarget - z))->normalize();
+ Vec3 *dir = Vec3::newTemp(sin(yRot * PI / 180), yd, -cos(yRot * PI / 180))->normalize();
+ float dot = (float) (dir->dot(aim) + 0.5f) / 1.5f;
+ if (dot < 0) dot = 0;
+
+ yRotA *= 0.80f;
+
+ float rotSpeed = sqrt(xd * xd + zd * zd) * 1 + 1;
+ double distToTarget = sqrt(xd * xd + zd * zd) * 1 + 1;
+ if (distToTarget > 40) distToTarget = 40;
+ if(getSynchedAction() == e_EnderdragonAction_Landing)
+ {
+ yRotA += yRotD * (distToTarget / rotSpeed);
+ }
+ else
+ {
+ yRotA += yRotD * ((0.7f / distToTarget) / rotSpeed);
+ }
+ yRot += yRotA * 0.1f;
+
+ float span = (float) (2.0f / (distToTarget + 1));
+ float speed = 0.06f;
+ moveRelative(0, -1, speed * (dot * span + (1 - span)));
+ if (inWall)
+ {
+ move(xd * 0.8f, yd * 0.8f, zd * 0.8f);
+ }
+ else
+ {
+ move(xd, yd, zd);
+
+ }
+
+ Vec3 *actual = Vec3::newTemp(xd, yd, zd)->normalize();
+ float slide = (float) (actual->dot(dir) + 1) / 2.0f;
+ slide = 0.8f + 0.15f * slide;
+
+
+ xd *= slide;
+ zd *= slide;
+ yd *= 0.91f;
+ }
+ }
+
+ yBodyRot = yRot;
+
+ head->bbWidth = head->bbHeight = 1; // 4J Stu - Replaced what was "head" with "neck" //3;
+ neck->bbWidth = neck->bbHeight = 3;
+ tail1->bbWidth = tail1->bbHeight = 2;
+ tail2->bbWidth = tail2->bbHeight = 2;
+ tail3->bbWidth = tail3->bbHeight = 2;
+ body->bbHeight = 3;
+ body->bbWidth = 5;
+ wing1->bbHeight = 2;
+ wing1->bbWidth = 4;
+ wing2->bbHeight = 3;
+ wing2->bbWidth = 4;
+
+ //double latencyPosAcomponents[3],latencyPosBcomponents[3];
+ //doubleArray latencyPosA = doubleArray(latencyPosAcomponents,3);
+ //doubleArray latencyPosB = doubleArray(latencyPosBcomponents,3);
+ //getLatencyPos(latencyPosA, 5, 1);
+ //getLatencyPos(latencyPosB, 10, 1);
+
+ //float tilt = (float) (latencyPosA[1] - latencyPosB[1]) * 10 / 180.0f * PI;
+ float tilt = (float) getTilt(1) / 180.0f * PI;
+ float ccTilt = cos(tilt);
+
+ // 4J Stu - ssTilt was negative sin(tilt), but this causes the bounding boxes of the parts to head in the wrong y direction
+ // i.e. head moves up when tilting forward, and down when tilting backwards
+ float ssTilt = sin(tilt);
+
+
+ float rot1 = yRot * PI / 180;
+ float ss1 = sin(rot1);
+ float cc1 = cos(rot1);
+
+ body->tick();
+ body->moveTo(x + ss1 * 0.5f, y, z - cc1 * 0.5f, 0, 0);
+ wing1->tick();
+ wing1->moveTo(x + cc1 * 4.5f, y + 2, z + ss1 * 4.5f, 0, 0);
+ wing2->tick();
+ wing2->moveTo(x - cc1 * 4.5f, y + 2, z - ss1 * 4.5f, 0, 0);
+
+ if (!level->isClientSide) checkAttack();
+ if (!level->isClientSide && hurtDuration == 0)
+ {
+ knockBack(level->getEntities(shared_from_this(), wing1->bb->grow(4, 2, 4)->move(0, -2, 0)));
+ knockBack(level->getEntities(shared_from_this(), wing2->bb->grow(4, 2, 4)->move(0, -2, 0)));
+ hurt(level->getEntities(shared_from_this(), neck->bb->grow(1, 1, 1)));
+ hurt(level->getEntities(shared_from_this(), head->bb->grow(1, 1, 1)));
+ }
+
+ double p1components[3];
+ doubleArray p1 = doubleArray(p1components, 3);
+ getLatencyPos(p1, 5, 1);
+
+ {
+ //double p0components[3];
+ //doubleArray p0 = doubleArray(p0components, 3);
+ //getLatencyPos(p0, 0, 1);
+
+ double yRotDiff = getHeadYRotDiff(1);
+
+ float ss = sin((yRot + yRotDiff) * PI / 180 - yRotA * 0.01f);
+ float cc = cos((yRot + yRotDiff) * PI / 180 - yRotA * 0.01f);
+ head->tick();
+ neck->tick();
+ double yOffset = getHeadYOffset(1); // (p0[1] - p1[1]) * 1
+
+ // 4J Stu - Changed the head entity to only be the head, and not include the neck parts
+ head->moveTo(x + ss * 6.5f * ccTilt, y + yOffset + ssTilt * 6.5f, z - cc * 6.5f * ccTilt, 0, 0);
+
+ // Neck position is where the java code used to move the "head" object which was head and neck
+ neck->moveTo(x + ss * 5.5f * ccTilt, y + yOffset + ssTilt * 5.5f, z - cc * 5.5f * ccTilt, 0, 0);
+
+ double acidX = x + ss * 9.5f * ccTilt;
+ double acidY = y + yOffset + ssTilt * 10.5f;
+ double acidZ = z - cc * 9.5f * ccTilt;
+ m_acidArea->set(acidX - 5, acidY - 17, acidZ - 5, acidX + 5, acidY + 4, acidZ + 5);
+
+ //app.DebugPrintf("\nDragon is %s, yRot = %f, yRotA = %f, ss = %f, cc = %f, ccTilt = %f\n",level->isClientSide?"client":"server", yRot, yRotA, ss, cc, ccTilt);
+ //app.DebugPrintf("Body (%f,%f,%f) to (%f,%f,%f)\n", body->bb->x0, body->bb->y0, body->bb->z0, body->bb->x1, body->bb->y1, body->bb->z1);
+ //app.DebugPrintf("Neck (%f,%f,%f) to (%f,%f,%f)\n", neck->bb->x0, neck->bb->y0, neck->bb->z0, neck->bb->x1, neck->bb->y1, neck->bb->z1);
+ //app.DebugPrintf("Head (%f,%f,%f) to (%f,%f,%f)\n", head->bb->x0, head->bb->y0, head->bb->z0, head->bb->x1, head->bb->y1, head->bb->z1);
+ //app.DebugPrintf("Acid (%f,%f,%f) to (%f,%f,%f)\n\n", m_acidArea->x0, m_acidArea->y0, m_acidArea->z0, m_acidArea->x1, m_acidArea->y1, m_acidArea->z1);
+ }
+
+ // Curls/straightens the tail
+ for (int i = 0; i < 3; i++)
+ {
+ shared_ptr<BossMobPart> part = nullptr;
+
+ if (i == 0) part = tail1;
+ if (i == 1) part = tail2;
+ if (i == 2) part = tail3;
+
+ double p0components[3];
+ doubleArray p0 = doubleArray(p0components, 3);
+ getLatencyPos(p0, 12 + i * 2, 1);
+
+ float rot = yRot * PI / 180 + rotWrap(p0[0] - p1[0]) * PI / 180 * (1);
+ float ss = sin(rot);
+ float cc = cos(rot);
+
+ float dd1 = 1.5f;
+ float dd = (i + 1) * 2.0f;
+ part->tick();
+ part->moveTo(x - (ss1 * dd1 + ss * dd) * ccTilt, y + (p0[1] - p1[1]) * 1 - (dd + dd1) * ssTilt + 1.5f, z + (cc1 * dd1 + cc * dd) * ccTilt, 0, 0);
+ }
+
+
+// 4J Stu - Fireball attack taken from Ghast
+ if (!level->isClientSide)
+ {
+ double maxDist = 64.0f;
+ if (getSynchedAction() == e_EnderdragonAction_StrafePlayer && attackTarget != NULL && attackTarget->distanceToSqr(shared_from_this()) < maxDist * maxDist)
+ {
+ if (this->canSee(attackTarget))
+ {
+ m_fireballCharge++;
+ Vec3 *aim = Vec3::newTemp((attackTarget->x - x), 0, (attackTarget->z - z))->normalize();
+ Vec3 *dir = Vec3::newTemp(sin(yRot * PI / 180), 0, -cos(yRot * PI / 180))->normalize();
+ float dot = (float)dir->dot(aim);
+ float angleDegs = acos(dot)*180/PI;
+ angleDegs = angleDegs + 0.5f;
+
+ if (m_fireballCharge >= 20 && ( angleDegs >= 0 && angleDegs < 10 ))
+ {
+ double d = 1;
+ Vec3 *v = getViewVector(1);
+ float startingX = head->x - v->x * d;
+ float startingY = head->y + head->bbHeight / 2 + 0.5f;
+ float startingZ = head->z - v->z * d;
+
+ double xdd = attackTarget->x - startingX;
+ double ydd = (attackTarget->bb->y0 + attackTarget->bbHeight / 2) - (startingY + head->bbHeight / 2);
+ double zdd = attackTarget->z - startingZ;
+
+ level->levelEvent(nullptr, LevelEvent::SOUND_GHAST_FIREBALL, (int) x, (int) y, (int) z, 0);
+ shared_ptr<DragonFireball> ie = shared_ptr<DragonFireball>( new DragonFireball(level, dynamic_pointer_cast<Mob>( shared_from_this() ), xdd, ydd, zdd) );
+ ie->x = startingX;
+ ie->y = startingY;
+ ie->z = startingZ;
+ level->addEntity(ie);
+ m_fireballCharge = 0;
+
+ app.DebugPrintf("Finding new target due to having fired a fireball\n");
+ if( m_currentPath != NULL )
+ {
+ while(!m_currentPath->isDone())
+ {
+ m_currentPath->next();
+ }
+ }
+ newTarget = true;
+ findNewTarget();
+ }
+ }
+ else
+ {
+ if (m_fireballCharge > 0) m_fireballCharge--;
+ }
+ }
+ else
+ {
+ if (m_fireballCharge > 0) m_fireballCharge--;
+ }
+ }
+// End fireball attack
+
+ if (!level->isClientSide)
+ {
+ inWall = checkWalls(head->bb) | checkWalls(neck->bb) | checkWalls(body->bb);
+ }
+}
+
+void EnderDragon::checkCrystals()
+{
+ if (nearestCrystal != NULL)
+ {
+ if (nearestCrystal->removed)
+ {
+ if (!level->isClientSide)
+ {
+ hurt(head, DamageSource::explosion, 10);
+ }
+
+ nearestCrystal = nullptr;
+ }
+ else if (tickCount % 10 == 0)
+ {
+ if (health < maxHealth) health++;
+ }
+ }
+
+ if (random->nextInt(10) == 0)
+ {
+ float maxDist = 32;
+ vector<shared_ptr<Entity> > *crystals = level->getEntitiesOfClass(typeid(EnderCrystal), bb->grow(maxDist, maxDist, maxDist));
+
+ shared_ptr<EnderCrystal> crystal = nullptr;
+ double nearest = Double::MAX_VALUE;
+ //for (Entity ec : crystals)
+ for(AUTO_VAR(it, crystals->begin()); it != crystals->end(); ++it)
+ {
+ shared_ptr<EnderCrystal> ec = dynamic_pointer_cast<EnderCrystal>( *it );
+ double dist = ec->distanceToSqr(shared_from_this() );
+ if (dist < nearest)
+ {
+ nearest = dist;
+ crystal = ec;
+ }
+ }
+ delete crystals;
+
+
+ nearestCrystal = crystal;
+ }
+}
+
+void EnderDragon::checkAttack()
+{
+ //if (tickCount % 20 == 0)
+ {
+// Vec3 *v = getViewVector(1);
+// double xdd = 0;
+// double ydd = -1;
+// double zdd = 0;
+
+ // double x = (body.bb.x0 + body.bb.x1) / 2;
+ // double y = (body.bb.y0 + body.bb.y1) / 2 - 2;
+ // double z = (body.bb.z0 + body.bb.z1) / 2;
+
+ }
+}
+
+void EnderDragon::knockBack(vector<shared_ptr<Entity> > *entities)
+{
+ double xm = (body->bb->x0 + body->bb->x1) / 2;
+ // double ym = (body.bb.y0 + body.bb.y1) / 2;
+ double zm = (body->bb->z0 + body->bb->z1) / 2;
+
+ //for (Entity e : entities)
+ for(AUTO_VAR(it, entities->begin()); it != entities->end(); ++it)
+ {
+ shared_ptr<Mob> e = dynamic_pointer_cast<Mob>( *it );
+ if (e != NULL)//(e instanceof Mob)
+ {
+ double xd = e->x - xm;
+ double zd = e->z - zm;
+ double dd = xd * xd + zd * zd;
+ e->push(xd / dd * 4, 0.2f, zd / dd * 4);
+ }
+ }
+}
+
+void EnderDragon::hurt(vector<shared_ptr<Entity> > *entities)
+{
+ //for (int i = 0; i < entities->size(); i++)
+ for(AUTO_VAR(it, entities->begin()); it != entities->end(); ++it)
+ {
+ shared_ptr<Mob> e = dynamic_pointer_cast<Mob>( *it );//entities.get(i);
+ if (e != NULL) //(e instanceof Mob)
+ {
+ DamageSource *damageSource = DamageSource::mobAttack( dynamic_pointer_cast<Mob>( shared_from_this() ));
+ e->hurt(damageSource, 10);
+ delete damageSource;
+ }
+ }
+}
+
+void EnderDragon::findNewTarget()
+{
+ shared_ptr<Player> playerNearestToEgg = nullptr;
+
+ // Update current action
+ switch(getSynchedAction())
+ {
+ case e_EnderdragonAction_Takeoff:
+ case e_EnderdragonAction_HoldingPattern:
+ {
+ if(!newTarget && m_currentPath != NULL && m_currentPath->isDone())
+ {
+ // Distance is 64, which is the radius of the circle
+ int eggHeight = max(level->seaLevel + 5, level->getTopSolidBlock(PODIUM_X_POS,PODIUM_Z_POS)); //level->getHeightmap(4,4);
+ playerNearestToEgg = level->getNearestPlayer(PODIUM_X_POS, eggHeight, PODIUM_Z_POS, 64.0);
+ double dist = 64.0f;
+ if(playerNearestToEgg != NULL)
+ {
+ dist = playerNearestToEgg->distanceToSqr(PODIUM_X_POS, eggHeight, PODIUM_Z_POS);
+ dist /= (8*8*8);
+ }
+ //app.DebugPrintf("Adjusted dist is %f\n", dist);
+
+ if( random->nextInt(m_remainingCrystalsCount + 3) == 0 )
+ {
+ setSynchedAction(e_EnderdragonAction_LandingApproach);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: LandingApproach\n");
+#endif
+ }
+ // More likely to strafe a player if they are close to the egg, or there are not many crystals remaining
+ else if( playerNearestToEgg != NULL && (random->nextInt( abs(dist) + 2 ) == 0 || random->nextInt( m_remainingCrystalsCount + 2 ) == 0) )
+ {
+ setSynchedAction(e_EnderdragonAction_StrafePlayer);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: StrafePlayer\n");
+#endif
+ }
+ }
+ }
+ break;
+ case e_EnderdragonAction_StrafePlayer:
+ // Always return to the holding pattern after strafing
+ if(m_currentPath == NULL || (m_currentPath->isDone() && newTarget) )
+ {
+ setSynchedAction(e_EnderdragonAction_HoldingPattern);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: HoldingPattern\n");
+#endif
+ }
+ break;
+ case e_EnderdragonAction_Landing:
+// setSynchedAction(e_EnderdragonAction_Sitting_Flaming);
+//#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+// app.DebugPrintf("Dragon action is now: SittingFlaming\n");
+//#endif
+// m_actionTicks = FLAME_TICKS;
+
+ m_flameAttacks = 0;
+ setSynchedAction(e_EnderdragonAction_Sitting_Scanning);
+ attackTarget = level->getNearestPlayer( shared_from_this(), SITTING_ATTACK_VIEW_RANGE, SITTING_ATTACK_Y_VIEW_RANGE );
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: SittingScanning\n");
+#endif
+ m_actionTicks = 0;
+ break;
+ };
+
+ newTarget = false;
+
+ //if (random->nextInt(2) == 0 && level->players.size() > 0)
+ if(getSynchedAction() == e_EnderdragonAction_StrafePlayer && playerNearestToEgg != NULL)
+ {
+ attackTarget = playerNearestToEgg;
+ strafeAttackTarget();
+ }
+ else if(getSynchedAction() == e_EnderdragonAction_LandingApproach)
+ {
+ // Generate a new path if we don't currently have one
+ if( m_currentPath == NULL || m_currentPath->isDone() )
+ {
+ int currentNodeIndex = findClosestNode();
+
+ // To get the angle to the player correct when landing, head to a node diametrically opposite the player, then swoop in to 4,4
+ int eggHeight = max( level->seaLevel + 5, level->getTopSolidBlock(PODIUM_X_POS,PODIUM_Z_POS) ); //level->getHeightmap(4,4);
+ playerNearestToEgg = level->getNearestPlayer(PODIUM_X_POS, eggHeight, PODIUM_Z_POS, 128.0);
+
+ int targetNodeIndex = 0 ;
+ if(playerNearestToEgg != NULL)
+ {
+ Vec3 *aim = Vec3::newTemp(playerNearestToEgg->x, 0, playerNearestToEgg->z)->normalize();
+ //app.DebugPrintf("Final marker node near (%f,%d,%f)\n", -aim->x*40,105,-aim->z*40 );
+ targetNodeIndex = findClosestNode(-aim->x*40,105.0,-aim->z*40);
+ }
+ else
+ {
+ targetNodeIndex = findClosestNode(40.0, eggHeight, 0.0);
+ }
+ Node finalNode(PODIUM_X_POS, eggHeight, PODIUM_Z_POS);
+
+ if(m_currentPath != NULL) delete m_currentPath;
+ m_currentPath = findPath(currentNodeIndex,targetNodeIndex, &finalNode);
+
+ // Always skip the first node (as that's where we are already)
+ if(m_currentPath != NULL) m_currentPath->next();
+ }
+
+ m_actionTicks = 0;
+
+ navigateToNextPathNode();
+
+ if(m_currentPath != NULL && m_currentPath->isDone())
+ {
+ setSynchedAction(e_EnderdragonAction_Landing);
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: Landing\n");
+#endif
+ }
+ }
+ else if(getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning)
+ {
+ // Does no movement
+ }
+ else
+ {
+ // Default is e_EnderdragonAction_HoldingPattern
+ // Generate a new path if we don't currently have one
+ if(m_currentPath == NULL || m_currentPath->isDone() )
+ {
+ int currentNodeIndex = findClosestNode();
+ int targetNodeIndex = currentNodeIndex;
+ //if(random->nextInt(4) == 0) m_holdingPatternClockwise = !m_holdingPatternClockwise;
+
+ if( getSynchedAction() == e_EnderdragonAction_Takeoff)
+ {
+ Vec3 *v = getHeadLookVector(1);
+ targetNodeIndex = findClosestNode(-v->x*40,105.0,-v->z*40);
+ }
+ else
+ {
+ if(random->nextInt(8) == 0)
+ {
+ m_holdingPatternClockwise = !m_holdingPatternClockwise;
+ targetNodeIndex = targetNodeIndex + 6;
+ }
+
+ if(m_holdingPatternClockwise) targetNodeIndex = targetNodeIndex + 1;
+ else targetNodeIndex = targetNodeIndex - 1;
+ }
+
+ if(m_remainingCrystalsCount <= 0)
+ {
+ // If no crystals left, navigate only between nodes 12-19
+ targetNodeIndex -= 12;
+ targetNodeIndex = targetNodeIndex&7; // 4J-RR - was %8, but that could create a result of -1 here when targetNodeIndex was 11
+ targetNodeIndex += 12;
+ }
+ else
+ {
+ // If crystals are left, navigate only between nodes 0-11
+ targetNodeIndex = targetNodeIndex%12;
+ if(targetNodeIndex < 0) targetNodeIndex += 12;
+ }
+
+ if(m_currentPath != NULL) delete m_currentPath;
+ m_currentPath = findPath(currentNodeIndex,targetNodeIndex);
+
+ // Always skip the first node (as that's where we are already)
+ if(m_currentPath != NULL) m_currentPath->next();
+ }
+
+ navigateToNextPathNode();
+
+ if(getSynchedAction() != e_EnderdragonAction_StrafePlayer) attackTarget = nullptr;
+ }
+}
+
+float EnderDragon::rotWrap(double d)
+{
+ while (d >= 180)
+ d -= 360;
+ while (d < -180)
+ d += 360;
+ return (float) d;
+}
+
+bool EnderDragon::checkWalls(AABB *bb)
+{
+ int x0 = Mth::floor(bb->x0);
+ int y0 = Mth::floor(bb->y0);
+ int z0 = Mth::floor(bb->z0);
+ int x1 = Mth::floor(bb->x1);
+ int y1 = Mth::floor(bb->y1);
+ int z1 = Mth::floor(bb->z1);
+ bool hitWall = false;
+ bool destroyedTile = false;
+ for (int x = x0; x <= x1; x++)
+ {
+ for (int y = y0; y <= y1; y++)
+ {
+ for (int z = z0; z <= z1; z++)
+ {
+ int t = level->getTile(x, y, z);
+ // 4J Stu - Don't remove fire
+ if (t == 0 || t == Tile::fire_Id)
+ {
+
+ }
+ else if (t == Tile::obsidian_Id || t == Tile::whiteStone_Id || t == Tile::unbreakable_Id)
+ {
+ hitWall = true;
+ }
+ else
+ {
+ destroyedTile = true;
+ level->setTile(x, y, z, 0);
+ }
+ }
+ }
+ }
+
+ if (destroyedTile)
+ {
+ double x = bb->x0 + (bb->x1 - bb->x0) * random->nextFloat();
+ double y = bb->y0 + (bb->y1 - bb->y0) * random->nextFloat();
+ double z = bb->z0 + (bb->z1 - bb->z0) * random->nextFloat();
+ level->addParticle(eParticleType_largeexplode, x, y, z, 0, 0, 0);
+ }
+
+ return hitWall;
+}
+
+bool EnderDragon::hurt(shared_ptr<BossMobPart> bossMobPart, DamageSource *source, int damage)
+{
+ if (bossMobPart != head)
+ {
+ damage = damage / 4 + 1;
+ }
+
+ //float rot1 = yRot * PI / 180;
+ //float ss1 = sin(rot1);
+ //float cc1 = cos(rot1);
+
+ //xTarget = x + ss1 * 5 + (random->nextFloat() - 0.5f) * 2;
+ //yTarget = y + random->nextFloat() * 3 + 1;
+ //zTarget = z - cc1 * 5 + (random->nextFloat() - 0.5f) * 2;
+ //attackTarget = NULL;
+
+ if (source == DamageSource::explosion || (dynamic_pointer_cast<Player>(source->getEntity()) != NULL))
+ {
+ int healthBefore = health;
+ reallyHurt(source, damage);
+
+ //if(!level->isClientSide) app.DebugPrintf("Health is now %d\n", health);
+ if( health <= 0 &&
+ !( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking) )
+ {
+ health = 1;
+
+ if( setSynchedAction(e_EnderdragonAction_LandingApproach) )
+ {
+ if( m_currentPath != NULL )
+ {
+ while(!m_currentPath->isDone())
+ {
+ m_currentPath->next();
+ }
+ }
+ app.DebugPrintf("Dragon should be dead, so landing.\n");
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: LandingApproach\n");
+#endif
+ findNewTarget();
+ }
+ }
+
+ if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ {
+ m_sittingDamageReceived += healthBefore - health;
+
+ if(m_sittingDamageReceived > (SITTING_ALLOWED_DAMAGE_PERCENTAGE*getMaxHealth() ) )
+ {
+ m_sittingDamageReceived = 0;
+ setSynchedAction(e_EnderdragonAction_Takeoff);
+ newTarget = true;
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: Takeoff\n");
+#endif
+ }
+ }
+ }
+ return true;
+}
+
+void EnderDragon::tickDeath()
+{
+ if( getSynchedAction() != e_EnderdragonAction_Sitting_Flaming &&
+ getSynchedAction() != e_EnderdragonAction_Sitting_Scanning &&
+ getSynchedAction() != e_EnderdragonAction_Sitting_Attacking)
+ {
+ if(!level->isClientSide) health = 1;
+ return;
+ }
+
+ dragonDeathTime++;
+ if (dragonDeathTime >= 180 && dragonDeathTime <= 200)
+ {
+ float xo = (random->nextFloat() - 0.5f) * 8;
+ float yo = (random->nextFloat() - 0.5f) * 4;
+ float zo = (random->nextFloat() - 0.5f) * 8;
+ level->addParticle(eParticleType_hugeexplosion, x + xo, y + 2 + yo, z + zo, 0, 0, 0);
+ }
+ if (!level->isClientSide)
+ {
+ if (dragonDeathTime > 150 && dragonDeathTime % 5 == 0)
+ {
+ int xpCount = 1000;
+ while (xpCount > 0)
+ {
+ int newCount = ExperienceOrb::getExperienceValue(xpCount);
+ xpCount -= newCount;
+ level->addEntity(shared_ptr<ExperienceOrb>( new ExperienceOrb(level, x, y, z, newCount) ));
+ }
+ }
+ if (dragonDeathTime == 1)
+ {
+ //level->globalLevelEvent(LevelEvent::SOUND_DRAGON_DEATH, (int) x, (int) y, (int) z, 0);
+ level->levelEvent(LevelEvent::SOUND_DRAGON_DEATH, (int) x, (int) y, (int) z, 0);
+ }
+ }
+ move(0, 0.1f, 0);
+ yBodyRot = yRot += 20.0f;
+
+ if (dragonDeathTime == 200 && !level->isClientSide)
+ {
+ //level->levelEvent(NULL, LevelEvent::ENDERDRAGON_KILLED, (int) x, (int) y, (int) z, 0);
+
+ int xpCount = 2000;
+ while (xpCount > 0)
+ {
+ int newCount = ExperienceOrb::getExperienceValue(xpCount);
+ xpCount -= newCount;
+ level->addEntity(shared_ptr<ExperienceOrb>( new ExperienceOrb(level, x, y, z, newCount)));
+ }
+ int xo = 5 + random->nextInt(2) * 2 - 1;
+ int zo = 5 + random->nextInt(2) * 2 - 1;
+ if (random->nextInt(2) == 0)
+ {
+ xo = 0;
+ }
+ else
+ {
+ zo = 0;
+ }
+ // 4J-PB changed to center this between the pillars
+ spawnExitPortal(0,0);//Mth::floor(x), Mth::floor(z));
+ remove();
+ }
+}
+
+void EnderDragon::spawnExitPortal(int x, int z)
+{
+ int y = level->seaLevel;
+
+ TheEndPortal::allowAnywhere(true);
+
+ int r = 4;
+ for (int yy = y - 1; yy <= y + 32; yy++)
+ {
+ for (int xx = x - r; xx <= x + r; xx++)
+ {
+ for (int zz = z - r; zz <= z + r; zz++)
+ {
+ double xd = xx - x;
+ double zd = zz - z;
+ double d = sqrt(xd * xd + zd * zd);
+ if (d <= r - 0.5)
+ {
+ if (yy < y)
+ {
+ if (d > r - 1 - 0.5)
+ {
+ }
+ else
+ {
+ level->setTile(xx, yy, zz, Tile::unbreakable_Id);
+ }
+ }
+ else if (yy > y)
+ {
+ level->setTile(xx, yy, zz, 0);
+ }
+ else
+ {
+ if (d > r - 1 - 0.5)
+ {
+ level->setTile(xx, yy, zz, Tile::unbreakable_Id);
+ }
+ else
+ {
+ level->setTile(xx, yy, zz, Tile::endPortalTile_Id);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ level->setTile(x, y + 0, z, Tile::unbreakable_Id);
+ level->setTile(x, y + 1, z, Tile::unbreakable_Id);
+ level->setTile(x, y + 2, z, Tile::unbreakable_Id);
+ level->setTile(x - 1, y + 2, z, Tile::torch_Id);
+ level->setTile(x + 1, y + 2, z, Tile::torch_Id);
+ level->setTile(x, y + 2, z - 1, Tile::torch_Id);
+ level->setTile(x, y + 2, z + 1, Tile::torch_Id);
+ level->setTile(x, y + 3, z, Tile::unbreakable_Id);
+ level->setTile(x, y + 4, z, Tile::dragonEgg_Id);
+
+ // 4J-PB - The podium can be floating with nothing under it, so put some whiteStone under it if this is the case
+ for (int yy = y - 5; yy < y - 1; yy++)
+ {
+ for (int xx = x - (r - 1); xx <= x + (r - 1); xx++)
+ {
+ for (int zz = z - (r - 1); zz <= z + (r - 1); zz++)
+ {
+ if(level->isEmptyTile(xx,yy,zz))
+ {
+ level->setTile(xx, yy, zz, Tile::whiteStone_Id);
+ }
+ }
+ }
+ }
+
+ TheEndPortal::allowAnywhere(false);
+}
+
+void EnderDragon::checkDespawn()
+{
+}
+
+vector<shared_ptr<Entity> > *EnderDragon::getSubEntities()
+{
+ return &subEntities;
+}
+
+bool EnderDragon::isPickable()
+{
+ return false;
+}
+
+// Fix for TU9 Enderdragon sound hits being the player sound hits - moved this forward from later version
+int EnderDragon::getHurtSound()
+{
+ return eSoundType_MOB_ENDERDRAGON_HIT;
+}
+
+int EnderDragon::getSynchedHealth()
+{
+ return entityData->getInteger(DATA_ID_SYNCHED_HEALTH);
+}
+
+// 4J Added for new dragon behaviour
+bool EnderDragon::setSynchedAction(EEnderdragonAction action, bool force /*= false*/)
+{
+ bool validTransition = false;
+ // Check if this is a valid state transition
+ switch(getSynchedAction())
+ {
+ case e_EnderdragonAction_HoldingPattern:
+ switch(action)
+ {
+ case e_EnderdragonAction_StrafePlayer:
+ case e_EnderdragonAction_LandingApproach:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_StrafePlayer:
+ switch(action)
+ {
+ case e_EnderdragonAction_HoldingPattern:
+ case e_EnderdragonAction_LandingApproach:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_LandingApproach:
+ switch(action)
+ {
+ case e_EnderdragonAction_Landing:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_Landing:
+ switch(action)
+ {
+ case e_EnderdragonAction_Sitting_Flaming:
+ case e_EnderdragonAction_Sitting_Scanning:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_Takeoff:
+ switch(action)
+ {
+ case e_EnderdragonAction_HoldingPattern:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_Sitting_Flaming:
+ switch(action)
+ {
+ case e_EnderdragonAction_Sitting_Scanning:
+ case e_EnderdragonAction_Sitting_Attacking:
+ case e_EnderdragonAction_Takeoff:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_Sitting_Scanning:
+ switch(action)
+ {
+ case e_EnderdragonAction_Sitting_Flaming:
+ case e_EnderdragonAction_Sitting_Attacking:
+ case e_EnderdragonAction_Takeoff:
+ validTransition = true;
+ break;
+ };
+ break;
+ case e_EnderdragonAction_Sitting_Attacking:
+ switch(action)
+ {
+ case e_EnderdragonAction_Sitting_Flaming:
+ case e_EnderdragonAction_Sitting_Scanning:
+ case e_EnderdragonAction_Takeoff:
+ validTransition = true;
+ break;
+ };
+ break;
+ };
+
+ if( force || validTransition )
+ {
+ entityData->set(DATA_ID_SYNCHED_ACTION, action);
+ }
+ else
+ {
+ app.DebugPrintf("EnderDragon: Invalid state transition from %d to %d\n", getSynchedAction(), action);
+ }
+
+ return force || validTransition;
+}
+
+EnderDragon::EEnderdragonAction EnderDragon::getSynchedAction()
+{
+ return (EEnderdragonAction)entityData->getInteger(DATA_ID_SYNCHED_ACTION);
+}
+
+void EnderDragon::handleCrystalDestroyed(DamageSource *source)
+{
+ AABB *tempBB = AABB::newTemp(PODIUM_X_POS,84.0,PODIUM_Z_POS,PODIUM_X_POS+1.0,85.0,PODIUM_Z_POS+1.0);
+ vector<shared_ptr<Entity> > *crystals = level->getEntitiesOfClass(typeid(EnderCrystal), tempBB->grow(48, 40, 48));
+ m_remainingCrystalsCount = (int)crystals->size() - 1;
+ if(m_remainingCrystalsCount < 0) m_remainingCrystalsCount = 0;
+ delete crystals;
+
+ app.DebugPrintf("Crystal count is now %d\n",m_remainingCrystalsCount);
+
+ //--m_remainingCrystalsCount;
+
+ if(m_remainingCrystalsCount%2 == 0)
+ {
+ if(setSynchedAction(e_EnderdragonAction_LandingApproach))
+ {
+ if( m_currentPath != NULL )
+ {
+ while(!m_currentPath->isDone())
+ {
+ m_currentPath->next();
+ }
+ }
+ m_actionTicks = 1;
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: LandingApproach\n");
+#endif
+ }
+ }
+ else if(dynamic_pointer_cast<Player>(source->getEntity()) != NULL)
+ {
+ if(setSynchedAction(e_EnderdragonAction_StrafePlayer))
+ {
+ attackTarget = dynamic_pointer_cast<Player>(source->getEntity());
+#if PRINT_DRAGON_STATE_CHANGE_MESSAGES
+ app.DebugPrintf("Dragon action is now: StrafePlayer\n");
+#endif
+ strafeAttackTarget();
+ }
+ }
+}
+
+void EnderDragon::strafeAttackTarget()
+{
+ app.DebugPrintf("Setting path to strafe attack target\n");
+ int currentNodeIndex = findClosestNode();
+ int targetNodeIndex = findClosestNode(attackTarget->x,attackTarget->y,attackTarget->z);
+
+ int finalXTarget = attackTarget->x;
+ int finalZTarget = attackTarget->z;
+
+ double xd = finalXTarget - x;
+ double zd = finalZTarget - z;
+ double sd = sqrt(xd * xd + zd * zd);
+ double ho = 0.4f + sd / 80.0f - 1;
+ if (ho > 10) ho = 10;
+ int finalYTarget = attackTarget->bb->y0 + ho;
+
+ Node finalNode(finalXTarget, finalYTarget, finalZTarget);
+
+ if(m_currentPath != NULL) delete m_currentPath;
+ m_currentPath = findPath(currentNodeIndex,targetNodeIndex, &finalNode);
+
+ if(m_currentPath != NULL)
+ {
+ // Always skip the first node (as that's where we are already)
+ m_currentPath->next();
+
+ navigateToNextPathNode();
+ }
+}
+
+void EnderDragon::navigateToNextPathNode()
+{
+ if(m_currentPath != NULL && !m_currentPath->isDone())
+ {
+ Vec3 *curr = m_currentPath->currentPos();
+
+ m_currentPath->next();
+ xTarget = curr->x;
+
+ if(getSynchedAction() == e_EnderdragonAction_LandingApproach && m_currentPath->isDone())
+ {
+ // When heading to the last node on the landing approach, we want the yCoord to be exact
+ yTarget = curr->y;
+ }
+ else
+ {
+ do
+ {
+ yTarget = curr->y + random->nextFloat() * 20;
+ } while( yTarget < (curr->y) );
+ }
+ zTarget = curr->z;
+ app.DebugPrintf("Path node pos is (%f,%f,%f)\n",curr->x,curr->y,curr->z);
+ app.DebugPrintf("Setting new target to (%f,%f,%f)\n",xTarget, yTarget, zTarget);
+ }
+}
+
+int EnderDragon::findClosestNode()
+{
+ // Setup all the nodes on the first time this is called
+ if(m_nodes->data[0] == NULL)
+ {
+ // Path nodes for navigation
+ // 0 - 11 are the outer ring at 60 blocks from centre
+ // 12 - 19 are the middle ring at 40 blocks from centre
+ // 20 - 23 are the inner ring at 20 blocks from centre
+ int nodeX=0;
+ int nodeY=0;
+ int nodeZ=0;
+ int multiplier = 0;
+ for(unsigned int i = 0; i < 24; ++i)
+ {
+ int yAdjustment = 5;
+ multiplier = i;
+ if(i < 12)
+ {
+ nodeX=60 * Mth::cos(2*(-PI+(PI/12)*multiplier));
+ nodeZ=60 * Mth::sin(2*(-PI+(PI/12)*multiplier));
+ }
+ else if(i < 20)
+ {
+ multiplier -= 12;
+ nodeX=40 * Mth::cos(2*(-PI+(PI/8)*multiplier));
+ nodeZ=40 * Mth::sin(2*(-PI+(PI/8)*multiplier));
+ yAdjustment += 10; // Make the target well above the top of the towers
+ }
+ else
+ {
+ multiplier -= 20;
+ nodeX=20 * Mth::cos(2*(-PI+(PI/4)*multiplier));
+ nodeZ=20 * Mth::sin(2*(-PI+(PI/4)*multiplier));
+ }
+ // Fix for #77202 - TU9: Content: Gameplay: The Ender Dragon sometimes flies through terrain
+ // Add minimum height
+ nodeY = max( (level->seaLevel + 10), level->getTopSolidBlock(nodeX, nodeZ) + yAdjustment );
+
+ app.DebugPrintf("Node %d is at (%d,%d,%d)\n", i, nodeX, nodeY, nodeZ);
+
+ m_nodes->data[i] = new Node(nodeX,nodeY,nodeZ);
+
+ //level->setTile(nodeX,nodeY,nodeZ,Tile::obsidian_Id);
+ }
+
+ m_nodeAdjacency[0] = (1<<11) | (1<<1) | (1<<12);
+ m_nodeAdjacency[1] = (1<<0) | (1<<2) | (1<<13);
+ m_nodeAdjacency[2] = (1<<1) | (1<<3) | (1<<13);
+ m_nodeAdjacency[3] = (1<<2) | (1<<4) | (1<<14);
+ m_nodeAdjacency[4] = (1<<3) | (1<<5) | (1<<15);
+ m_nodeAdjacency[5] = (1<<4) | (1<<6) | (1<<15);
+ m_nodeAdjacency[6] = (1<<5) | (1<<7) | (1<<16);
+ m_nodeAdjacency[7] = (1<<6) | (1<<8) | (1<<17);
+ m_nodeAdjacency[8] = (1<<7) | (1<<9) | (1<<17);
+ m_nodeAdjacency[9] = (1<<8) | (1<<10) | (1<<18);
+ m_nodeAdjacency[10] = (1<<9) | (1<<11) | (1<<19);
+ m_nodeAdjacency[11] = (1<<10) | (1<<0) | (1<<19);
+
+ m_nodeAdjacency[12] = (1<<0) | (1<<13) | (1<<20) | (1<<19);
+ m_nodeAdjacency[13] = (1<<1) | (1<<2) | (1<<14) | (1<<21) | (1<<20) | (1<<12);
+ m_nodeAdjacency[14] = (1<<3) | (1<<15) | (1<<21) | (1<<13);
+ m_nodeAdjacency[15] = (1<<4) | (1<<5) | (1<<16) | (1<<22) | (1<<21) | (1<<14);
+ m_nodeAdjacency[16] = (1<<6) | (1<<17) | (1<<22) | (1<<15);
+ m_nodeAdjacency[17] = (1<<7) | (1<<8) | (1<<18) | (1<<23) | (1<<22) | (1<<16);
+ m_nodeAdjacency[18] = (1<<9) | (1<<19) | (1<<23) | (1<<17);
+ m_nodeAdjacency[19] = (1<<10) | (1<<11) | (1<<12) | (1<<20) | (1<<23) | (1<<18);
+
+ m_nodeAdjacency[20] = (1<<12) | (1<<13) | (1<<21) | (1<<22) | (1<<23) | (1<<19);
+ m_nodeAdjacency[21] = (1<<14) | (1<<15) | (1<<22) | (1<<23) | (1<<20) | (1<<13);
+ m_nodeAdjacency[22] = (1<<15) | (1<<16) | (1<<17) | (1<<23) | (1<<20) | (1<<21);
+ m_nodeAdjacency[23] = (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21) | (1<<22);
+ }
+
+ return findClosestNode(x,y,z);
+}
+
+int EnderDragon::findClosestNode(double tX, double tY, double tZ)
+{
+ float closestDist = 100.0f;
+ int closestIndex = 0;
+ Node *currentPos = new Node((int) floor(tX), (int) floor(tY), (int) floor(tZ));
+ int startIndex = 0;
+ if(m_remainingCrystalsCount <= 0)
+ {
+ // If not crystals are left then we try and stay in the middle ring and avoid the outer ring
+ startIndex = 12;
+ }
+ for(unsigned int i = startIndex; i < 24; ++i)
+ {
+ if( m_nodes->data[i] != NULL )
+ {
+ float dist = m_nodes->data[i]->distanceTo(currentPos);
+ if(dist < closestDist)
+ {
+ closestDist = dist;
+ closestIndex = i;
+ }
+ }
+ }
+ delete currentPos;
+ return closestIndex;
+}
+
+// 4J Stu - A* taken from PathFinder and modified
+Path *EnderDragon::findPath(int startIndex, int endIndex, Node *finalNode /* = NULL */)
+{
+ for(unsigned int i = 0; i < 24; ++i)
+ {
+ Node *n = m_nodes->data[i];
+ n->closed = false;
+ n->f = 0;
+ n->g = 0;
+ n->h = 0;
+ n->cameFrom = NULL;
+ n->heapIdx = -1;
+ }
+
+ Node *from = m_nodes->data[startIndex];
+ Node *to = m_nodes->data[endIndex];
+
+ from->g = 0;
+ from->h = from->distanceTo(to);
+ from->f = from->h;
+
+ openSet->clear();
+ openSet->insert(from);
+
+ Node *closest = from;
+
+ int minimumNodeIndex = 0;
+ if(m_remainingCrystalsCount <= 0)
+ {
+ // If not crystals are left then we try and stay in the middle ring and avoid the outer ring
+ minimumNodeIndex = 12;
+ }
+
+ while (!openSet->isEmpty())
+ {
+ Node *x = openSet->pop();
+
+ if (x->equals(to))
+ {
+ app.DebugPrintf("Found path from %d to %d\n", startIndex, endIndex);
+ if(finalNode != NULL)
+ {
+ finalNode->cameFrom = to;
+ to = finalNode;
+ }
+ return reconstruct_path(from, to);
+ }
+
+ if (x->distanceTo(to) < closest->distanceTo(to))
+ {
+ closest = x;
+ }
+ x->closed = true;
+
+ unsigned int xIndex = 0;
+ for(unsigned int i = 0; i < 24; ++i)
+ {
+ if(m_nodes->data[i] == x)
+ {
+ xIndex = i;
+ break;
+ }
+ }
+
+ for (int i = minimumNodeIndex; i < 24; i++)
+ {
+ if(m_nodeAdjacency[xIndex] & (1<<i))
+ {
+ Node *y = m_nodes->data[i];
+
+ if(y->closed) continue;
+
+ float tentative_g_score = x->g + x->distanceTo(y);
+ if (!y->inOpenSet() || tentative_g_score < y->g)
+ {
+ y->cameFrom = x;
+ y->g = tentative_g_score;
+ y->h = y->distanceTo(to);
+ if (y->inOpenSet())
+ {
+ openSet->changeCost(y, y->g + y->h);
+ }
+ else
+ {
+ y->f = y->g + y->h;
+ openSet->insert(y);
+ }
+ }
+ }
+ }
+ }
+
+ if (closest == from) return NULL;
+ app.DebugPrintf("Failed to find path from %d to %d\n", startIndex, endIndex);
+ if(finalNode != NULL)
+ {
+ finalNode->cameFrom = closest;
+ closest = finalNode;
+ }
+ return reconstruct_path(from, closest);
+}
+
+// function reconstruct_path(came_from,current_node)
+Path *EnderDragon::reconstruct_path(Node *from, Node *to)
+{
+ int count = 1;
+ Node *n = to;
+ while (n->cameFrom != NULL)
+ {
+ count++;
+ n = n->cameFrom;
+ }
+
+ NodeArray nodes = NodeArray(count);
+ n = to;
+ nodes.data[--count] = n;
+ while (n->cameFrom != NULL)
+ {
+ n = n->cameFrom;
+ nodes.data[--count] = n;
+ }
+ Path *ret = new Path(nodes);
+ delete [] nodes.data;
+ return ret;
+}
+
+void EnderDragon::addAdditonalSaveData(CompoundTag *entityTag)
+{
+ app.DebugPrintf("Adding EnderDragon additional save data\n");
+ entityTag->putShort(L"RemainingCrystals", m_remainingCrystalsCount);
+ entityTag->putInt(L"DragonState", (int)getSynchedAction() );
+
+ BossMob::addAdditonalSaveData(entityTag);
+}
+
+void EnderDragon::readAdditionalSaveData(CompoundTag *tag)
+{
+ app.DebugPrintf("Reading EnderDragon additional save data\n");
+ m_remainingCrystalsCount = tag->getShort(L"RemainingCrystals");
+ if(!tag->contains(L"RemainingCrystals")) m_remainingCrystalsCount = CRYSTAL_COUNT;
+
+ if(tag->contains(L"DragonState")) setSynchedAction( (EEnderdragonAction)tag->getInt(L"DragonState"), true);
+
+ BossMob::readAdditionalSaveData(tag);
+}
+
+float EnderDragon::getTilt(float a)
+{
+ float tilt = 0.0f;
+ //if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ // getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ // getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ //{
+ // tilt = -25.0f;
+ // xRot = -25.0f;
+ //}
+ //else
+ {
+ double latencyPosAcomponents[3],latencyPosBcomponents[3];
+ doubleArray latencyPosA = doubleArray(latencyPosAcomponents,3);
+ doubleArray latencyPosB = doubleArray(latencyPosBcomponents,3);
+ getLatencyPos(latencyPosA, 5, a);
+ getLatencyPos(latencyPosB, 10, a);
+
+ tilt = (latencyPosA[1] - latencyPosB[1]) * 10;
+ }
+ //app.DebugPrintf("Tilt is %f\n", tilt);
+
+ return tilt;
+}
+
+double EnderDragon::getHeadYOffset(float a)
+{
+ double headYOffset = 0.0;
+ if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ {
+ headYOffset = -1.0;
+ }
+ else
+ {
+ double p1components[3];
+ doubleArray p1 = doubleArray(p1components, 3);
+ getLatencyPos(p1, 5, 1);
+
+ double p0components[3];
+ doubleArray p0 = doubleArray(p0components, 3);
+ getLatencyPos(p0, 0, 1);
+
+ headYOffset = (p0[1] - p1[1]) * 1;
+ }
+ //app.DebugPrintf("headYOffset is %f\n", headYOffset);
+ return headYOffset;
+}
+
+double EnderDragon::getHeadYRotDiff(float a)
+{
+ double result = 0.0;
+ //if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ // getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ // getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ //{
+ // result = m_headYRot;
+ //}
+ return result;
+}
+
+double EnderDragon::getHeadPartYOffset(int partIndex, doubleArray bodyPos, doubleArray partPos)
+{
+ double result = 0.0;
+ if( getSynchedAction() == e_EnderdragonAction_Landing || getSynchedAction() == e_EnderdragonAction_Takeoff )
+ {
+ int eggHeight = level->getTopSolidBlock(PODIUM_X_POS,PODIUM_Z_POS); //level->getHeightmap(4,4);
+ float dist = sqrt( distanceToSqr(PODIUM_X_POS, eggHeight, PODIUM_Z_POS) )/4;
+ if( dist < 1.0f ) dist = 1.0f;
+ result = partIndex / dist;
+ //app.DebugPrintf("getHeadPartYOffset - dist = %f, result = %f (%d)\n", dist, result, partIndex);
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ {
+ result = partIndex;
+ }
+ else
+ {
+ if(partIndex == 6)
+ {
+ result = 0.0;
+ }
+ else
+ {
+ result = partPos[1] - bodyPos[1];
+ }
+ }
+ //app.DebugPrintf("Part %d is at %f\n", partIndex, result);
+ return result;
+}
+
+double EnderDragon::getHeadPartYRotDiff(int partIndex, doubleArray bodyPos, doubleArray partPos)
+{
+ double result = 0.0;
+ //if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ // getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ // getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ //{
+ // result = m_headYRot / (7 - partIndex);
+ //}
+ //else
+ {
+ result = partPos[0] - bodyPos[0];
+ }
+ //app.DebugPrintf("Part %d is at %f\n", partIndex, result);
+ return result;
+}
+
+Vec3 *EnderDragon::getHeadLookVector(float a)
+{
+ Vec3 *result = NULL;
+
+ if( getSynchedAction() == e_EnderdragonAction_Landing || getSynchedAction() == e_EnderdragonAction_Takeoff )
+ {
+ int eggHeight = level->getTopSolidBlock(PODIUM_X_POS,PODIUM_Z_POS); //level->getHeightmap(4,4);
+ float dist = sqrt(distanceToSqr(PODIUM_X_POS, eggHeight, PODIUM_Z_POS))/4;
+ if( dist < 1.0f ) dist = 1.0f;
+ // The 6.0f is dragon->getHeadPartYOffset(6, start, p)
+ float yOffset = 6.0f / dist;
+
+ double xRotTemp = xRot;
+ double rotScale = 1.5f;
+ xRot = -yOffset * rotScale * 5.0f;
+
+ double yRotTemp = yRot;
+ yRot += getHeadYRotDiff(a);
+
+ result = getViewVector(a);
+
+ xRot = xRotTemp;
+ yRot = yRotTemp;
+ }
+ else if( getSynchedAction() == e_EnderdragonAction_Sitting_Flaming ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Scanning ||
+ getSynchedAction() == e_EnderdragonAction_Sitting_Attacking)
+ {
+ double xRotTemp = xRot;
+ double rotScale = 1.5f;
+ // The 6.0f is dragon->getHeadPartYOffset(6, start, p)
+ xRot = -6.0f * rotScale * 5.0f;
+
+ double yRotTemp = yRot;
+ yRot += getHeadYRotDiff(a);
+
+ result = getViewVector(a);
+
+ xRot = xRotTemp;
+ yRot = yRotTemp;
+ }
+ else
+ {
+ result = getViewVector(a);
+ }
+ return result;
+}