/* $Id: homingmissile.cpp,v 1.23.4.1 2006/01/20 11:33:52 chfreund Exp $ */ #include "homingmissile.hpp" #include "avatar.hpp" #include "player.hpp" #include "world.hpp" #include "smoke.hpp" /**********************************************************/ #define ACCELERATE_FUEL 10.0 #define MIN_ACCELERATION 5 #define CHASE_FUEL 100.0 #define CHASING_THRUST_IMPACT 100.0 #define RANGE_OF_SIGHT 220.0 #define SPEED_PREDICTION 3.0 // dependent constants #define TOTAL_FUEL (ACCELERATE_FUEL + CHASE_FUEL) #define RANGE_OF_SIGHT_SQUARE (RANGE_OF_SIGHT * RANGE_OF_SIGHT) // "nearly 1.0". Since the accumulated time is incremented in each // call of update, there might be some roundoff errors. To make sure // that the condition "if(accumulatedDT >= DELTA_T_PER_ANIMATION_STEP)" // is true whenever it should be true, we use 0.99 instead of 1.0 #define DELTA_T_PER_ANIMATION_STEP 0.99 /**********************************************************/ HomingMissile::HomingMissile() { // // take the values from class Missile // // m_health = 300000; m_mass = 100.0; // // m_collRectX = -4; // m_collRectY = -4; // // m_collRectWidth = 9; // m_collRectHeight = 9; // } /**********************************************************/ bool HomingMissile::initialize( const Vector& pos, const Vector& vel, const Uint8 owner, const Sint32 nShrapnels, const Sint32 damage, const Vector& thrust ) { return Missile::initialize( pos, vel, owner, nShrapnels, damage, TOTAL_FUEL, thrust ); } /**********************************************************/ void HomingMissile::update() { preBallistics(); if( m_health <= 0 ) { explode(); } else { // check for prey const std::vector& players = m_worldPointer->getPlayerList(); const Avatar* prey = NULL; real minDist2 = RANGE_OF_SIGHT_SQUARE; // only check for prey, if we could chase it if( !m_accelTimer.isElapsed() ) { for( Uint8 p = 0; p < players.size(); p++ ) { // do not chase the missile's owner if( m_owner != players[p]->getPlayerID() ) { const Avatar* avatar = players[p]->getAvatar(); if( avatar->isLiving() && avatar->isWeaponVisible() ) { const real dist2 = (avatar->getPos() - m_pos).abs2(); // search the nearest avatar // NOTE that due to the initialization of minDistSquare // distsquare will also be < RANGE_OF_SIGHT_SQUARE. if( dist2 < minDist2 ) { minDist2 = dist2; prey = avatar; } } } } } bool ignition = false; // we found a prey if( prey != NULL ) { // if we still have fuel AND if( !m_accelTimer.isElapsed() ) { // we are not in the initial start phase if( m_accelTimer.getTimeToLive() < (TOTAL_FUEL - MIN_ACCELERATION) ) { // turn thrust m_thrust = prey->getPos(); m_thrust += prey->getVel() * SPEED_PREDICTION; m_thrust -= m_pos; m_thrust -= m_vel * SPEED_PREDICTION; m_thrust.normalize() *= CHASING_THRUST_IMPACT; // ignition towards the prey ignition = true; } else { // ignition in the initial start phase ignition = true; prey = NULL; } } // we did not find a prey } else { // we are still in the neutral acceleration phase if( m_accelTimer.getTimeToLive() > CHASE_FUEL ) { ignition = true; } } // engine is running if( ignition ) { // accelerate in direction of thrust m_force += m_thrust; if ( m_accelTimer.hasSignal() ) { // throw out some booster sparks for( int i = 0; i < 15; i++ ) { m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos, m_vel - m_thrust*0.1 + m_worldPointer->getRandom().getVector( 1.5 ), Particles::BOUNCE_EARTH | //Particles::BOUNCE_OBJECT | Particles::REDUNDANT | Particles::SPARK, 2, 0, Particles::m_N_SPARK_COLORS/2 + m_worldPointer->getRandom().getUint32() % (Particles::m_N_SPARK_COLORS/2+1), 1, 0xffffff)); } // generate a smoke cloud Smoke* cloud = static_cast(m_worldPointer->newObject(SMOKE_30)); cloud->setPos( m_pos + localRnd.getVector( 4.0 )); cloud->setVel( m_vel + localRnd.getVector( 2.0 )); // color the cloud, if we chase someone if( prey != NULL ) { cloud->setColor( prey->getPlayerColor() ); // lock the avatar (maybe it has got some authomatic defense) ((Avatar*)prey)->getLockedByHomingMissile( this, minDist2 ); } } // decrement "fuel" m_accelTimer.countDown( m_worldPointer->getDT( m_pos ) ); } doBallistics(); // eventually increment the animation frame m_AccumulatedDT += m_worldPointer->getDT( m_pos ); if( m_AccumulatedDT >= DELTA_T_PER_ANIMATION_STEP ) { m_AccumulatedDT = 0.0; if( ++m_Frame >= m_SequenceLength ) m_Frame = 0; } // select new missile sprite updateSpriteSelection( ignition ); postBallistics(); } } /**********************************************************/ void HomingMissile::explode() { Audio::getInstance()->playSound( m_explodeSample, m_pos ); m_worldPointer->newObject( EXPLOSION_80 )->setPos( m_pos ); for( int s = 0; s < m_nShrapnels; s++ ) { m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos, m_worldPointer->getRandom().getVector( 15 + 5*m_worldPointer->getRandom().getNormedReal() ), // + getSpeed(), otherwise shrapnels will just fly forward Particles::EXPL_EARTH | Particles::EXPL_OBJECT | Particles::DAMAGE | Particles::INVISIBLE, 0, m_owner, 2, m_damage, 0xffffff )); } m_removeMeAfterUpdate = true; } /**********************************************************/ void HomingMissile::updateSpriteSelection( const bool activeEngine ) { // If the engine is running, face in the negative direction // of the thrust vector, otherwise in direction of movement. const real angle = (activeEngine ? ATAN2_REAL( m_thrust.y, m_thrust.x ) : ATAN2_REAL( m_vel.y, m_vel.x )); // set the sequence index. // NOTE: setSequence also updates m_SequenceLength. setSequence( (ROUND( angle*(18.0/M_PI) - 0.5 )+36) % 36 ); // only reset the frame index, if we cannot continue // this movement in a different aiming angle if( getFrame() >= m_SequenceLength ) setFrame( 0 ); } /**********************************************************/ void HomingMissile::dump( std::ostream& out ) const { using namespace std; out << "### HomingMissile object ###" << endl; Missile::dump( out ); }