From b691c43c44ff180d10e7d4a9afc83b98551ff586 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Sun, 1 Mar 2026 12:16:08 +0800 Subject: Initial commit --- Minecraft.World/EnderDragon.cpp | 1941 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1941 insertions(+) create mode 100644 Minecraft.World/EnderDragon.cpp (limited to 'Minecraft.World/EnderDragon.cpp') 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( new BossMobPart(this, L"head", 6, 6) ); + neck = shared_ptr( new BossMobPart(this, L"neck", 6, 6) ); // 4J Added + body = shared_ptr( new BossMobPart(this, L"body", 8, 8) ); + tail1 = shared_ptr( new BossMobPart(this, L"tail", 4, 4) ); + tail2 = shared_ptr( new BossMobPart(this, L"tail", 4, 4) ); + tail3 = shared_ptr( new BossMobPart(this, L"tail", 4, 4) ); + wing1 = shared_ptr( new BossMobPart(this, L"wing", 4, 4) ); + wing2 = shared_ptr( 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 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 > *targets = level->getEntities(shared_from_this(), m_acidArea); + + for( AUTO_VAR(it, targets->begin() ); it != targets->end(); ++it) + { + shared_ptr e = dynamic_pointer_cast( *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 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 ie = shared_ptr( new DragonFireball(level, dynamic_pointer_cast( 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 > *crystals = level->getEntitiesOfClass(typeid(EnderCrystal), bb->grow(maxDist, maxDist, maxDist)); + + shared_ptr crystal = nullptr; + double nearest = Double::MAX_VALUE; + //for (Entity ec : crystals) + for(AUTO_VAR(it, crystals->begin()); it != crystals->end(); ++it) + { + shared_ptr ec = dynamic_pointer_cast( *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 > *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 e = dynamic_pointer_cast( *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 > *entities) +{ + //for (int i = 0; i < entities->size(); i++) + for(AUTO_VAR(it, entities->begin()); it != entities->end(); ++it) + { + shared_ptr e = dynamic_pointer_cast( *it );//entities.get(i); + if (e != NULL) //(e instanceof Mob) + { + DamageSource *damageSource = DamageSource::mobAttack( dynamic_pointer_cast( shared_from_this() )); + e->hurt(damageSource, 10); + delete damageSource; + } + } +} + +void EnderDragon::findNewTarget() +{ + shared_ptr 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, 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(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( 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( 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 > *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 > *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(source->getEntity()) != NULL) + { + if(setSynchedAction(e_EnderdragonAction_StrafePlayer)) + { + attackTarget = dynamic_pointer_cast(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<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; +} -- cgit v1.2.3