/* $Id: avatar.cpp,v 1.149.2.1 2006/01/20 11:33:52 chfreund Exp $ */ #include "avatar.hpp" #include "serialize.hpp" #include "audio.hpp" #include "bonusshield.hpp" #include "bonusfuel.hpp" #include "bonushomingvirus.hpp" #include "rope.hpp" #include "hook.hpp" #include "smoke.hpp" #include "stationarygun.hpp" #include /**********************************************************/ #define TURN_WHILE_SHOOTING true /**********************************************************/ const real Avatar::INIT_AIMING_SPEED = 1.0, Avatar::AIMING_SPEED_INCREMENT = 0.5, Avatar::MAX_AIMING_SPEED = 10.0, Avatar::MAX_AIMING_ANGLE = 180 - 15, Avatar::AIMING_ANGLE_TO_RAD = M_PI / 180.0; const real Avatar::INIT_WALKING_SPEED = 1.0, Avatar::MAX_WALKING_SPEED = 5.0, Avatar::MAX_WALKING_SPEED_DIGGING = 2.0; /**********************************************************/ /* When creating an avatar it is invisible at first. Calling Avatar::spawnAvatar() sets the position and initializes the status variables for playing. */ Avatar::Avatar() : m_fuel( MAX_FUEL ), m_maxFuel( MAX_FUEL ), m_gunID( INVALID_OBJECT ), m_aimingAngle( 90.0 ), m_aimingSpeed( 0.0 ), m_walkingSpeed( INIT_WALKING_SPEED ), m_stateMask( ~0 ), m_bonusPack( m_worldPointer ), m_Player( NULL ) { m_bounceThresholdVel2 = 25.0, m_bounceFactorHLVel = 1.5, m_bounceFactorLow = 0.9, m_bounceFactorHigh = 0.5, m_maxGroundedForce2 = 2250000.0; m_maxGroundedForceVert = 170.0; setState( LIVING | VISIBLE | WEAPON_VISIBLE | MOVING_ANY | JUMPING | DIGGING | SHOOTING | AIMING_ANY | ROPE_ANY | MODE_ANY | IN_SKWOERMZONE, false ); m_popupSample = Audio::getInstance()->loadSound( "sound/effects/popup.wav" ); m_messageSink = &m_defaultMessageSink; setFocusPriority( FOCUS_PRIO_AVATAR_BASE ); } /**********************************************************/ Avatar::~Avatar() { DBG( 1 ) CHECK( m_Player == NULL, "Avatar::~Avatar: m_Player != NULL\n" ); // leave gun properly leaveCurrentGun(); } /**********************************************************/ void Avatar::setDirection( const Uint32 direction ) { DBG( 2 ) ASSERT( direction < NUM_DIRECTIONS, "Avatar::setDirection: passed direction %d " "is out of range\n" ); // do not set the new direction, if it were the old one const Uint16 oldDirection = (isFacingRight() ? RIGHT : LEFT); if( direction == oldDirection ) return; setState( FACING_RIGHT, direction == RIGHT ); m_walkingSpeed = INIT_WALKING_SPEED; updateSpriteSelection(); } /**********************************************************/ void Avatar::setAimingDirection( const int dir ) { // if not turning set aiming speed to zero if( dir == 0 ) { m_aimingSpeed = 0.0; } else // if turning in positive direction if( dir > 0 ) { // if just started turning if( m_aimingSpeed < INIT_AIMING_SPEED ) { m_aimingSpeed = INIT_AIMING_SPEED; } else if( m_aimingSpeed < MAX_AIMING_SPEED ) { m_aimingSpeed += AIMING_SPEED_INCREMENT; } setAimingAngle( m_aimingAngle + m_aimingSpeed ); } else // if turning in negative direction if( dir < 0 ) { // if just started turning if( m_aimingSpeed > -INIT_AIMING_SPEED ) { m_aimingSpeed = -INIT_AIMING_SPEED; } else if( m_aimingSpeed > -MAX_AIMING_SPEED ) { m_aimingSpeed -= AIMING_SPEED_INCREMENT; } setAimingAngle( m_aimingAngle + m_aimingSpeed ); } } /**********************************************************/ void Avatar::sitDownInGun( StationaryGun *const gun ) { // developping check // NOTE: We need this strange construct for the second optional // parameter of ASSERT (ID string), because Object::geIDString // itself terminates the program, if an invalid ID is passed. DBG(3) ASSERT( m_gunID == INVALID_OBJECT, "Avatar::sitDownInGun: %d:%s: current gun ID is " "%d (%s), should be -1 (INVALID_OBJECT)\n", getPlayer()->getPlayerID(), getPlayer()->getName().getString(), m_gunID, m_gunID == INVALID_OBJECT ? "never seen" : getIDString(m_gunID) ); // set the state setState( Avatar::MODE_GUN, true ); // set ID of the gun (needed in updateSpriteSelection) m_gunID = gun->getID(); // call the guns registration routine gun->registerPilot( this ); // update sprite selection updateSpriteSelection(); } /**********************************************************/ void Avatar::leaveCurrentGun() { if( ! isInGunMode() ) return; // remove the according flags setState( GROUNDED | MODE_GUN, false ); // search the current gun StationaryGun *gun = 0x0; for( int i = 0; i < getNAttachedObjects(); i++ ) { if( 0x0 != (gun = dynamic_cast (getAttachedObject(i))) ) { break; } } // Release the avatar in the gun (this will also detach // avatar and gun). NOTE that the invocation of StationaryGun:: // registerPilot and StationaryGun::releasePilot is NOT symmetric. // This means register if( gun ) gun->releasePilot(); // reset the gun ID (actually only done for a proper // conding style, since this member should not be accessed // in any non-gun mode) m_gunID = INVALID_OBJECT; // update sprite selection updateSpriteSelection(); } /**********************************************************/ void Avatar::takeRemoteControl() { setState( Avatar::MODE_GM, true ); updateSpriteSelection(); } /**********************************************************/ void Avatar::dropRemoteControl() { setState( Avatar::MODE_GM, false ); updateSpriteSelection(); } /**********************************************************/ void Avatar::dig() { Vector holePosition = m_pos; holePosition.x += 8.0 * getAimingDX(); holePosition.y += 8.0 * getAimingDY(); m_worldPointer->getMap()->makeHole( ROUND( holePosition.x ), ROUND( holePosition.y ) - m_collRectY - m_collRectHeight - 2, m_collRectHeight/2 + 3, Flags::UNDIGGABLE | Flags::INDESTRUCTIBLE ); } /**********************************************************/ void Avatar::testGrounded( bool& groundedMid, bool& groundedFW, bool& groundedBW ) { /* #: collRect F: groundedFW M: groundedMid B: groundedBW facing left: F##### facing right: #####F F##### #####F F##### #####F F##### #####F F##### #####F F##### #####F F##### #####F MMMMMMB BMMMMMM */ int dummy; const int fwX = (isMovingRight() ? m_collRectX + m_collRectWidth : m_collRectX - 1), bwX = (isMovingRight() ? m_collRectX - 1 : m_collRectX + m_collRectWidth); groundedMid = ! (testLinePassableRel( m_collRectX, m_collRectY + m_collRectHeight, m_collRectX + m_collRectWidth - 1, m_collRectY + m_collRectHeight, dummy, dummy, dummy, dummy ) && testPassableRel( fwX, m_collRectY + m_collRectHeight )), groundedFW = ! testLinePassableRel( fwX, m_collRectY, fwX, m_collRectY + m_collRectHeight - 1, dummy, dummy, dummy, dummy ), groundedBW = ! testPassableRel( bwX, m_collRectY + m_collRectHeight ); } /**********************************************************/ int Avatar::applyDamage( const Particles::ParticleData& p ) { // recoil by particles (mass := 1) // factor 2 is unphysical but also more fun Vector forceAdd = p.vel * 2.0; forceAdd /= m_worldPointer->getDT( m_pos ); m_force += forceAdd; setState( GROUNDED, false ); if( m_bonusPack.hasBonus(BONUS_SHIELD) ) { ((BonusShield*)m_bonusPack[BONUS_SHIELD])->absorbDamage( p.vel ); return 0; } m_wasHurt = true; real realDamage = (p.vel - m_vel).abs2(); realDamage *= p.damage; const int damage = min( ROUND( realDamage ), m_health ); LOG(5) INFO( "CollidableObject::applyDamage: health: " "%i, damage: %i\n", m_health, damage ); m_health -= damage; return damage; } /**********************************************************/ int Avatar::applyDamage( const int damage, Player* shooter ) { if( m_bonusPack.hasBonus(BONUS_SHIELD) ) return 0; return CollidableObject::applyDamage( damage, shooter ); } /**********************************************************/ void Avatar::pickUp( Bonus* bonus ) { // The collission rectangle of the bonus must not be set // at this stage, since it will never be removed after // being picked up by an avatar. // let the bonus have "the last word" while being picked up bonus->beingPickedUp( this ); // apply the bonus to the avatar bonus->apply( this ); // if necessary, pack the bonus into the bonus pack if( bonus->mustBePutIntoPack() ) { m_worldPointer->unhookObject( bonus ); // detach all attached objects from the bonus, because the // bonus object itself lives furtheron, in the bonus pack bonus->detachFromAll(); m_bonusPack.add( bonus ); // otherwise delete the consumed bonus } else { bonus->setRemoveMeAfterUpdate( true ); } } /**********************************************************/ void Avatar::getLockedByHomingMissile( HomingMissile *const missile, const real distanceSquare ) { // If the homing missile is inside the minimum transfer distance // for the homing missile virus, AND this avatar has got this bonus, // apply the virus bonus to the missile. if( distanceSquare <= BonusHomingVirus::getInfectionRadiusSquare() && getBonusPack().hasBonus(BONUS_HOMING_VIRUS) ) { (static_cast (getBonusPack()[BONUS_HOMING_VIRUS]))->applyVirus( missile, this ); } } /**********************************************************/ void Avatar::update() { if ( ! isLiving() ) return; Map* map = m_worldPointer->getMap(); // remove avatar stencil removeCollRect(); if( ! isInAnyMode() ) { // normal mode => enable all states setStateMask( ~0 ); } else if( isInGunMode() ) { // mask some states that are not possible during gun mode setStateMask( ~(MOVING_ANY | DIGGING | ROPE_ON | ROPE_REL | SHOOTING | AIMING_ANY) ); } else if( isInGMMode() ) { // mask some states that are not possible during guided missile mode setStateMask( ~(MOVING_ANY | DIGGING | SHOOTING | JUMPING | AIMING_ANY) ); } // handle aiming events if ( isAimingUp () ) setAimingDirection( -1 ); else if( isAimingDown() ) setAimingDirection( 1 ); else setAimingDirection( 0 ); // handle digging if( isDigging() ) dig(); // if moving, update avatar animation frame if( isMovingAny() ) { if( TURN_WHILE_SHOOTING || ! isShooting() ) setDirection( isMovingLeft() ? LEFT : RIGHT ); if( isFacingRight() ^ isMovingRight() ) { // walk backwards if( --m_Frame < 0 ) m_Frame = m_SequenceLength-1; } else { // walk forward if( ++m_Frame >= m_SequenceLength ) m_Frame = 0; } } bool groundedMid, groundedFW, groundedBW; testGrounded( groundedMid, groundedFW, groundedBW ); LOG( 5 ) INFO( "grounded status A: %c%c%c\n", (groundedBW ? '#' : '.'), (groundedMid ? '#' : '.'), (groundedFW ? '#' : '.') ); preBallistics(); const real DT = m_worldPointer->getDT( m_pos ); if( isGrounded() ) { if( isMovingAny() ) { m_walkingSpeed += 0.05; if( isDigging() ) { if( m_walkingSpeed > MAX_WALKING_SPEED_DIGGING ) m_walkingSpeed = MAX_WALKING_SPEED_DIGGING; } else { if( m_walkingSpeed > MAX_WALKING_SPEED ) m_walkingSpeed = MAX_WALKING_SPEED; } real remainingWalkingDistance = m_walkingSpeed * DT; Vector currentVel; while( isGrounded() && (remainingWalkingDistance > 0.0) ) { LOG( 5 ) INFO( "mid: %3i, %3i\n%c%c%c\n%c%c%c\n%c%c%c\n", ROUND( m_pos.x ), ROUND( m_pos.y ), (testPassableRel( -1, -1 ) ? '.' : '#'), (testPassableRel( 0, -1 ) ? '.' : '#'), (testPassableRel( 1, -1 ) ? '.' : '#'), (testPassableRel( -1, 0 ) ? '.' : '#'), (testPassableRel( 0, 0 ) ? '.' : '#'), (testPassableRel( 1, 0 ) ? '.' : '#'), (testPassableRel( -1, 1 ) ? '.' : '#'), (testPassableRel( 0, 1 ) ? '.' : '#'), (testPassableRel( 1, 1 ) ? '.' : '#') ); m_vel.set( 0.0, 0.0 ); if( isDigging() ) dig(); // if obstacle in front and cannot walk down (!groundedMid && groundedBW) => walk up if( groundedFW && ! ( ! groundedMid && groundedBW) ) { m_vel.y -= (remainingWalkingDistance >= 1.0 ? 1.0 : remainingWalkingDistance); LOG( 5 ) INFO( "Avatar::update: walking up\n" ); } else // if no obstacle in front and groundedMid => walk horizontal if ( groundedMid ) { m_vel.x += (isMovingRight() ? 1 : -1) * (remainingWalkingDistance >= 1.0 ? 1.0 : remainingWalkingDistance); LOG( 5 ) INFO( "Avatar::update: walking horiz.\n" ); } // if no obstacle in front and not groundedMid => walk down else { m_vel.y += (remainingWalkingDistance >= 1.0 ? 1.0 : remainingWalkingDistance); LOG( 5 ) INFO( "Avatar::update: walking down\n" ); } const Vector newPos = m_pos + m_vel; if( map->testFilledCollRect( ROUND( newPos.x ) + m_collRectX, ROUND( newPos.y ) + m_collRectY, m_collRectWidth, m_collRectHeight ) ) { m_pos = newPos; currentVel += m_vel; } remainingWalkingDistance--; // still standing on ground? testGrounded( groundedMid, groundedFW, groundedBW ); LOG( 5 ) INFO( "grounded status B: %c%c%c\n", (groundedBW ? '#' : '.'), (groundedMid ? '#' : '.'), (groundedFW ? '#' : '.') ); if( ! (groundedMid || groundedFW || groundedBW) ) { // no, so remove flag LOG( 5 ) INFO( "Avatar::update: GROUNDED lost B\n" ); setState( GROUNDED, false ); } } m_vel = currentVel; } else { m_walkingSpeed = INIT_WALKING_SPEED; m_vel.set( 0.0, 0.0 ); } if( isJumping() ) { LOG( 5 ) INFO( "Avatar::update: jumping => GROUNDED lost\n" ); m_force.y -= (GRAVITATION + 4.5) * m_mass / DT; setState( GROUNDED, false ); } } else { // not standing on ground LOG( 5 ) INFO( "Avatar::update: not GROUNDED\n" ); if( isMovingLeft() ) m_force.x -= 0.1 * m_mass; else if( isMovingRight() ) m_force.x += 0.1 * m_mass; if( isJumping() ) { if( !m_fuel.isElapsed() ) { int numParticles = static_cast( 15 * DT ); for( int i = 0; i < numParticles; i++ ) { real x = 0.5 - m_worldPointer->getRandom().getNormedReal(); x *= 5.0; x += isFacingRight() ? -2 : 2; real y = 1.0 + m_worldPointer->getRandom().getNormedReal(); y *= 5.0; m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos + Vector( 0.0, m_collRectY + m_collRectHeight ), Vector( x, y ), 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 ) ); } m_force.x += (0.08 * (isFacingRight() ? 1 : -1)) * m_mass; m_force.y -= (GRAVITATION + 0.2) * m_mass; m_fuel.countDown( DT ); } else { if( m_fuel.getTimeToLive() < DT && m_fuel.getTimeToLive() > -DT ) { // smoke particles Uint32 smokeColor; int numParticles = static_cast( 15 * DT ); for( int i = 0; i < numParticles; i++ ) { smokeColor = m_worldPointer->getRandom().getUint32Between( 150, 240 ); smokeColor += (smokeColor << 8) + (smokeColor << 16); real x = 0.5 - m_worldPointer->getRandom().getNormedReal(); x *= 4.0; x += isFacingRight() ? -2 : 2; real y = 1.0 + m_worldPointer->getRandom().getNormedReal(); y *= 4.0; m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos + Vector( 0.0, m_collRectY + m_collRectHeight ), Vector( x, y ), Particles::BOUNCE_EARTH | //Particles::BOUNCE_OBJECT | Particles::REDUNDANT, 0, 0, 25, 1, smokeColor ) ); } // smoke sprites Uint32 nSmokeClouds = static_cast( ((m_worldPointer->getRandom().getUint32() & 1) + 1) * DT); while( nSmokeClouds-- ) { Smoke *smoke = static_cast( m_worldPointer->newObject( (m_worldPointer->getRandom().getUint32() & 3) ? SMOKE_15 : SMOKE_30) ); const Vector smokeOffset( localRnd.getSint32Between( 0, 4 ) * (isFacingRight() ? -1 : 1), localRnd.getUint32() & 7 ); smoke->setPos( m_pos + smokeOffset ); /* smoke->setPosition( m_positionX + (isFacingRight() ? localRnd.getSint32Between(-4,0) : localRnd.getSint32Between( 0,4)), m_positionY + m_collRectY + m_collRectHeight + (localRnd.getUint32() & 7) ); */ smoke->setVel( m_vel + localRnd.getVector( 2.0 )); smoke->setColor( 0 ); //TPLCOL_RGB(100,100,100) ); } // new value for fuel m_fuel.setTimeToLive( m_worldPointer->getRandom().getSint32Between( -8, 9 ) ); } else { m_fuel.countDown( -DT ); } } } /* m_speedY += GRAVITATION*DT; const real newPosX = m_positionX+m_speedX*DT, newPosY = m_positionY+m_speedY*DT; LOG( 5 ) INFO( "AAAAA: %.9e %.9e\n", newPosX, newPosY ); // is there something to collide? if( map->testLineCollRect( ROUND( m_positionX ) + m_collRectX, ROUND( m_positionY ) + m_collRectY, ROUND( newPosX ) + m_collRectX, ROUND( newPosY ) + m_collRectY, m_collRectWidth, m_collRectHeight, pcx, pcy, cx, cy ) ) { // no, then just keep on flying m_positionX = newPosX; m_positionY = newPosY; } // yes, there is an in-flight collision else { LOG( 5 ) INFO( "Avatar::update: collision when moving from (%f, %f) to (%f, %f)\n", m_positionX, m_positionY, newPosX, newPosY ); m_positionX = pcx - m_collRectX; m_positionY = pcy - m_collRectY; DBG( 4 ) { CHECK( map->getFlags()->getValueAt( cx, cy ) & (Flags::OBSTACLE_EARTH | Flags::OBSTACLE_OBJECT), "Avatar::update: (cx, cy) is passable %4i, %4i\n", cx, cy ); CHECK( ! (map->getFlags()->getValueAt( pcx, pcy ) & (Flags::OBSTACLE_EARTH | Flags::OBSTACLE_OBJECT)), "Avatar::update: (pcx, pcy) is not passable %4i, %4i\n", pcx, pcy ); CHECK( ! (map->getFlags()->getValueAt( ROUND( m_positionX ), ROUND( m_positionY ) ) & (Flags::OBSTACLE_EARTH | Flags::OBSTACLE_OBJECT)), "Avatar::update: (posX, posY) is not passable %f, %f\n", m_positionX, m_positionY ); } LOG( 5 ) INFO( "Avatar::update: setting pos after collision to (%f, %f)\n", m_positionX, m_positionY ); DBG( 3 ) CHECK( map->testFilledCollRect( ROUND( m_positionX ) + m_collRectX, ROUND( m_positionY ) + m_collRectY, m_collRectWidth, m_collRectHeight ), "Avatar::update: Avatar::update: setting to invalid position\n" ); testGrounded( groundedMid, groundedFW, groundedBW ); */ //############################################################################## /* // groundMid map->drawLine( ROUND( m_positionX + m_collRectX ), ROUND( m_positionY + 1 ), ROUND( m_positionX + m_collRectX + m_collRectWidth - 1 ), ROUND( m_positionY + 1 ), 0xff0000 ); map->setColorAt( ROUND( m_positionX + fwX ), ROUND( m_positionY + 1 ), 0xff0000 ); // groundFW map->drawLine( ROUND( m_positionX + fwX ), ROUND( m_positionY + m_collRectY ), ROUND( m_positionX + fwX ), ROUND( m_positionY + m_collRectY + m_collRectHeight - 1 ), 0x0000ff ); // groundBW map->setColorAt( ROUND( m_positionX + bwX ), ROUND( m_positionY + 1 ), 0x00ff00 ); */ //############################################################################## /* const real speed2 = getSpeed().abs2(); if( speed2 < 25.0 && groundedMid ) { LOG( 5 ) INFO( "Avatar::update: GROUNDED again\n" ); setState( GROUNDED, true ); m_speedX = m_speedY = 0; } else { const real speed = SQRT_REAL( speed2 ), outAngle = map->calculateBounceAngle( m_speedX, m_speedY, cx, cy, Flags::OBSTACLE_EARTH | Flags::OBSTACLE_OBJECT ); m_speedX = speed * (speed > 1.5 ? 0.5 : 1.0) * COS_REAL( outAngle ); m_speedY = speed * (speed > 1.5 ? 0.5 : 1.0) * SIN_REAL( outAngle ); } } */ } doBallistics(); LOG( 5 ) INFO( "BBBBB: %.9e %.9e\n", m_pos.x, m_pos.y ); DBG( 5 ) map->setColorAt( ROUND( m_pos.x ), ROUND( m_pos.y ), 0xffffff ); // generate blood int numParticles = static_cast( ( 5 - ((10*m_health)/m_maxHealth) ) * DT ); for( int i = 0; i < numParticles; i++ ) { const real brightness = 0.7 + 0.3*m_worldPointer->getRandom().getNormedReal(); const Uint32 skincolor = getSkinColor(); Uint32 r = ROUND( static_cast( skincolor >> 16 ) * brightness ); Uint32 g = ROUND( static_cast( skincolor >> 8 ) * brightness ); Uint32 b = ROUND( static_cast( skincolor ) * brightness ); m_worldPointer->getParticles()->addParticle( Particles::ParticleData( m_pos + Vector( m_collRectX + m_collRectWidth/2, m_collRectY + m_collRectHeight/2 ), m_vel + Vector( 3.0*(0.5 - m_worldPointer->getRandom().getNormedReal()), 3.0*(0.5 - m_worldPointer->getRandom().getNormedReal()) ), Particles::BOUNCE_EARTH | Particles::STAIN | Particles::REDUNDANT, 0, 0, 75, 1, (((r << 8) | g) << 8) | b )); } // shoot normal weapons if( isShooting() ) { getPlayer()->getActiveWeapon()->shoot( m_worldPointer, this ); } // handle rope/hook actions // cut rope if( isSetRopeOff() ) { getPlayer()->getWeapon( Player::m_MAX_WEAPONS-1 )->shoot( m_worldPointer, this ); } // release rope if( isSetRopeRel() ) { getPlayer()->getWeapon( Player::m_MAX_WEAPONS-2 )->shoot( m_worldPointer, this ); } // shoot rope if( isSetRopeOn() ) { getPlayer()->getWeapon( Player::m_MAX_WEAPONS-3 )->shoot( m_worldPointer, this ); } // update bonus pack m_bonusPack.update( this ); postBallistics(); } /**********************************************************/ void Avatar::spawnAvatar() { Map* const map = m_worldPointer->getMap(); int x = 0, y = 0; // reset avatar velocity m_vel.set( 0.0, 0.0 ); m_force.set( 0.0, GRAVITATION * m_mass ); // reset weapons getPlayer()->rechargeWeaponsNow(); while( true ) { x = m_collRectWidth + (m_worldPointer->getRandom().getUint32() % (map->getSizeX()-2*m_collRectWidth)), y = m_collRectHeight + (m_worldPointer->getRandom().getUint32() % (map->getSizeY()-2*m_collRectHeight)); map->makeHole( x, y, m_collRectWidth + m_collRectHeight, Flags::UNDIGGABLE ); if( map->testFilledCollRect( x + m_collRectX, y + m_collRectY, m_collRectWidth, m_collRectHeight ) ) break; } // add initial shield bonus BonusShield* shield = dynamic_cast( m_worldPointer->newObject( BONUS_SHIELD )); shield->setProtectionTime( 100 ); // 100 frames = 4 seconds pickUp( shield ); // play sound Audio::getInstance()->playSound( m_popupSample, m_pos ); LOG( 1 ) INFO( "Avatar::spawnAvatar: spawned at (%4i, %4i)\n", x, y ); m_pos.set( x, y ); m_health = m_maxHealth; m_fuel.setTimeToLive( m_maxFuel ); setState( MOVING_ANY | JUMPING | DIGGING | SHOOTING | AIMING_ANY | ROPE_ANY | MODE_ANY | IN_SKWOERMZONE, false ); setState( LIVING | VISIBLE | WEAPON_VISIBLE, true ); setStateMask( ~0 ); setDirection( RIGHT ); // reset gun ID m_gunID = INVALID_OBJECT; // set avatar stencil setCollRect(); if( getPlayer()->isLocalPlayer() ) m_worldPointer->focusNextAvatar(); updateSpriteSelection(); } /**********************************************************/ Vector Avatar::placeBesideCollRect( const int marginLeft, const int marginRight, const int marginTop, const int marginBottom ) { const int dx = isFacingRight() ? m_collRectX + m_collRectWidth + marginRight : m_collRectX - 1 - marginLeft; const int dy = (m_aimingAngle > 90.0) ? m_collRectY + m_collRectHeight + marginBottom : m_collRectY - 1 - marginTop; const real tanValue = TAN_REAL( (isFacingRight() ? -1 : 1) * m_aimingAngle * AIMING_ANGLE_TO_RAD ); Vector posOffset; if( FABS_REAL( tanValue ) > 1e-3 ) { // NOT almost vertical posOffset.y = dx / tanValue; } else posOffset.y = (m_aimingAngle > 90.0) ? 1e+10 : -1e+10; if( FABS_REAL( tanValue ) < 1e+3 ) { // NOT almost horizontal posOffset.x = dy * tanValue; } else posOffset.x = (isFacingRight() ? 1e+10 : -1e+10); real tempOffset; tempOffset = m_collRectX + m_collRectWidth + marginRight; posOffset.x = min( posOffset.x, tempOffset ); tempOffset = m_collRectX - 1 - marginLeft; posOffset.x = max( posOffset.x, tempOffset ); tempOffset = m_collRectY + m_collRectHeight + marginBottom; posOffset.y = min( posOffset.y, tempOffset ); tempOffset = m_collRectY - 1 - marginTop; posOffset.y = max( posOffset.y, tempOffset ); return m_pos + posOffset; } /**********************************************************/ std::vector Avatar::getVisibleObjects() const { std::vector visibleObjects; std::vector* collidables = m_worldPointer->getCollidables(); for ( unsigned int i = 0; i < collidables->size(); i++ ) { CollidableObject* object = (*collidables)[i]; if ( object->isWeaponVisible() && ( ! object->isAvatar() || dynamic_cast( object )->isLiving()) && (object->getPos() - m_pos).abs2() <= 320.0*320.0 ) { visibleObjects.push_back( object ); } } return visibleObjects; } /**********************************************************/ void Avatar::serialize( Uint8*& bufferPointer ) const { // expands to a check of the buffer movement START_OBJECT_SERIALIZED_SIZE_CHECK( bufferPointer ); // serialize base object CollidableObject::serialize( bufferPointer ); // Avatar specific serialisation Serialize::serialize( getNumColors(), getColors(), bufferPointer ); Serialize::serialize( m_maxHealth , bufferPointer ); m_fuel.serialize( bufferPointer ); Serialize::serialize( m_maxFuel , bufferPointer ); Serialize::serialize( m_gunID , bufferPointer ); Serialize::serialize( m_aimingAngle , bufferPointer ); Serialize::serialize( m_aimingSpeed , bufferPointer ); Serialize::serialize( m_walkingSpeed, bufferPointer ); Serialize::serialize( m_stateMask , bufferPointer ); // serialize the bonus pack m_bonusPack.serialize( bufferPointer ); // expands to tag serialization SERIALIZE_OBJECT_TAG( bufferPointer ); // expands to a check of the buffer movement END_OBJECT_SERIALIZED_SIZE_CHECK( bufferPointer, Avatar ); } /**********************************************************/ void Avatar::deserialize( Uint8*& bufferPointer ) { // deserialize base object CollidableObject::deserialize( bufferPointer ); // Avatar specific deserialisation Serialize::deserialize( getNumColors(), bufferPointer, const_cast( getColors() )); Serialize::deserialize( bufferPointer, m_maxHealth ); m_fuel.deserialize( bufferPointer ); Serialize::deserialize( bufferPointer, m_maxFuel ); Serialize::deserialize( bufferPointer, m_gunID ); Serialize::deserialize( bufferPointer, m_aimingAngle ); Serialize::deserialize( bufferPointer, m_aimingSpeed ); Serialize::deserialize( bufferPointer, m_walkingSpeed ); Serialize::deserialize( bufferPointer, m_stateMask ); // deserialize the bonus pack ASSERT( m_worldPointer, "Avatar::deserialize: world pointer is 0\n" ); m_bonusPack.setWorldPointer( m_worldPointer ); m_bonusPack.deserialize( bufferPointer ); // expands to tag deserialization DESERIALIZE_OBJECT_TAG( bufferPointer ); } /**********************************************************/ Uint32 Avatar::getSerializeBufferSize() const { return CollidableObject::getSerializeBufferSize() // base object + getNumColors() * Serialize::sizeOf() // colors + Serialize::sizeOf( m_maxHealth ) + m_fuel.getSerializeBufferSize() + Serialize::sizeOf( m_maxFuel ) + Serialize::sizeOf( m_gunID ) + Serialize::sizeOf( m_aimingAngle ) + Serialize::sizeOf( m_aimingSpeed ) + Serialize::sizeOf( m_walkingSpeed ) + Serialize::sizeOf( m_stateMask ) + m_bonusPack.getSerializeBufferSize() // adds additional size for debugging (see serialize.hpp), // but only, if the tags are used PLUS_TAG_SIZE( 1 ); } /**********************************************************/ void Avatar::dump( std::ostream& out ) const { using namespace std; out << "### Avatar object ###" << endl; CollidableObject::dump( out ); out << "Avatar components:" << endl; out << "\tFuel: " << m_fuel.getTimeToLive() << endl; out << "\tAiming angle: " << m_aimingAngle << endl; out << "\tAiming speed: " << m_aimingSpeed << endl; out << "\tWalking speed: " << m_walkingSpeed << endl; }