/* $Id: missile.cpp,v 1.37.4.1 2006/01/20 11:33:52 chfreund Exp $ */ #include "missile.hpp" #include "world.hpp" #include "particles.hpp" #include "random.hpp" /**********************************************************/ // "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 /**********************************************************/ Mix_Chunk* Missile::m_explodeSample, * Missile::m_damageSample; /**********************************************************/ Missile::Missile(): m_AccumulatedDT( 0.0 ) { m_health = 300000; m_mass = 100.0; m_collRectX = -4; m_collRectY = -4; m_collRectWidth = 9; m_collRectHeight = 9; } /**********************************************************/ bool Missile::initialize( const Vector& pos, const Vector& vel, const Uint8 owner, const Sint32 nShrapnels, const Sint32 damage, const real accelTime, const Vector& thrust ) { if( ! m_worldPointer->getMap()->testFilledCollRect( ROUND( pos.x ) + m_collRectX, ROUND( pos.y ) + m_collRectY, m_collRectWidth, m_collRectHeight ) ) { m_worldPointer->deleteObject( this ); // ok // DO NOT TOUCH ANY MISSILE DATA MEMBERS AFTER THIS POINT return false; } m_pos = pos; m_owner = owner; m_nShrapnels = nShrapnels; m_damage = damage; m_accelTimer.setTimeToLive( accelTime ); m_accelTimer.setTimeToSignal( 1.0 ); m_thrust = thrust; m_vel = thrust; updateSpriteSelection(); m_vel = vel; // set missile stencil setCollRect(); return true; } /**********************************************************/ void Missile::update() { preBallistics(); // explode? if( m_health <= 0 ) { explode(); } else { // accelerate missile if( !m_accelTimer.isElapsed() ) { 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 m_worldPointer->newObject( SMOKE_30 )-> setPos( m_pos + m_worldPointer->getRandom().getVector( 5.0 )); } // 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 if( m_accelTimer.isElapsed() ) updateSpriteSelection(); postBallistics(); } } /**********************************************************/ bool Missile::collidedWithObstacle( const Sint32 colX, const Sint32 colY ) { // only explode if it was not the first update to prevent premature explosion if( ! m_firstUpdate ) { explode(); } return m_firstUpdate; } /**********************************************************/ void Missile::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; } /**********************************************************/ int Missile::applyDamage( const Particles::ParticleData& p ) { const int damage = CollidableObject::applyDamage( p ); // create sparks for( int i = 0; i < 2; i++ ) { m_worldPointer->getParticles()->addParticle( Particles::ParticleData( p.pos, m_vel + m_worldPointer->getRandom().getVector( 1 + 7*m_worldPointer->getRandom().getNormedReal() ), Particles::BOUNCE_EARTH | Particles::BOUNCE_OBJECT | Particles::REDUNDANT | Particles::SPARK, 0, 0, Particles::m_N_SPARK_COLORS/2 + (m_worldPointer->getRandom().getUint32() % (Particles::m_N_SPARK_COLORS/2+1)), 0, 0xffffff)); } if( damage > 50 && m_damageSampleTimeout == 0 ) { Audio::getInstance()->playSound( m_damageSample, m_pos ); m_damageSampleTimeout = 10; } if( m_health > 0 ) { updateSpriteSelection(); } return damage; } /**********************************************************/ void Missile::updateSpriteSelection() { const real angle = 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 Missile::serialize( Uint8*& bufferPointer ) const { // expands to a check of the buffer movement START_OBJECT_SERIALIZED_SIZE_CHECK( bufferPointer ); CollidableObject::serialize( bufferPointer ); Serialize::serialize( m_owner, bufferPointer ); Serialize::serialize( m_nShrapnels, bufferPointer ); Serialize::serialize( m_damage, bufferPointer ); // Serialize::serialize( m_accelTimer, bufferPointer ); m_accelTimer.serialize( bufferPointer ); Serialize::serialize( m_thrust, bufferPointer ); Serialize::serialize( m_AccumulatedDT, bufferPointer ); // expands to tag serialization SERIALIZE_OBJECT_TAG( bufferPointer ); // expands to a check of the buffer movement END_OBJECT_SERIALIZED_SIZE_CHECK( bufferPointer, Missile ); } /**********************************************************/ void Missile::deserialize( Uint8*& bufferPointer ) { CollidableObject::deserialize( bufferPointer ); Serialize::deserialize( bufferPointer, m_owner ); Serialize::deserialize( bufferPointer, m_nShrapnels ); Serialize::deserialize( bufferPointer, m_damage ); // Serialize::deserialize( bufferPointer, m_accelTimer ); m_accelTimer.deserialize( bufferPointer ); Serialize::deserialize( bufferPointer, m_thrust ); Serialize::deserialize( bufferPointer, m_AccumulatedDT ); // expands to tag deserialization DESERIALIZE_OBJECT_TAG( bufferPointer ); } /**********************************************************/ Uint32 Missile::getSerializeBufferSize() const { return CollidableObject::getSerializeBufferSize() + Serialize::sizeOf( m_owner ) + Serialize::sizeOf( m_nShrapnels ) + Serialize::sizeOf( m_damage ) // + Serialize::sizeOf( m_accelTimer ) + m_accelTimer.getSerializeBufferSize() + Serialize::sizeOf( m_thrust ) + Serialize::sizeOf( m_AccumulatedDT ) // adds additional size for debugging (see serialize.hpp), // but only, if the tags are used PLUS_TAG_SIZE( 1 ); } /**********************************************************/ void Missile::dump( std::ostream& out ) const { using namespace std; out << "### Missile object ###" << endl; CollidableObject::dump( out ); out << "Missile components:" << endl; out << "\tAcceleration timer: " << m_accelTimer.getTimeToLive() << endl; out << "\tThrust: " << m_thrust.x << " " << m_thrust.y << endl; out << "\tAccumulated DT: " << m_AccumulatedDT << endl; }