//============================================================================= // Copyright (C) 2003 Radical Entertainment Ltd. All rights reserved. // // File: CoinManager.cpp // // Description: Implementation of class CoinManager // // History: 29/1/2003 + Created -- James Harrison // //============================================================================= //======================================== // System Includes //======================================== #include <radmath/matrix.hpp> #include <radmath/random.hpp> #include <p3d/entity.hpp> #include <p3d/view.hpp> #include <p3d/camera.hpp> #include <p3d/matrixstack.hpp> #include <pddi/pddi.hpp> //#include <math.h> //need this if we want extra accurate coin paths. //======================================== // Project Includes //======================================== #include <gameflow/gameflow.h> #include <worldsim/coins/coinmanager.h> #include <worldsim/coins/sparkle.h> #include <worldsim/avatarmanager.h> #include <worldsim/redbrick/vehicle.h> #include <memory/srrmemory.h> #include <events/eventmanager.h> #include <render/intersectmanager/intersectmanager.h> #include <render/rendermanager/rendermanager.h> #include <render/rendermanager/worldrenderlayer.h> #include <data/persistentworldmanager.h> #include <mission/gameplaymanager.h> #include <mission/charactersheet/charactersheetmanager.h> /* Watcher stuff */ #ifndef RAD_RELEASE #include <raddebug.hpp> #include <raddebugwatch.hpp> static float COIN_HOVER = 0.5f; // How far off the ground will the coin hover. #if !defined( RAD_PS2 ) && defined( RAD_RELEASE ) static const float FRAME_RATIO = 1.0f / 60.0f; //static float COIN_DRAG = 0.9848f; // .4 to the power of 1/60 of a second. #elif defined( RAD_RELEASE ) static const float FRAME_RATIO = 1.0f / 30.0f; //static float COIN_DRAG = 0.9700f; // .4 to the power of 1/30 of a second. #else static const float FRAME_RATIO = 1.0f / 15.0f; //static float COIN_DRAG = 0.9407f; // .4 to the power of 1/15 of a second. #endif static float COIN_SPAWN_VELOCITY = 9.5f;// * FRAME_RATIO; static float COIN_EXTRA_VERTICAL = 1.1f;// * FRAME_RATIO; // Use these values if you want accurate coins. static float GRAVITY = 21.0f; // * FRAME_RATIO; static float COIN_DRAG = 0.1f; // Use these if you want faster (but less accurate coins. //static float GRAVIYT = 0.9f * FRAME_RATIO; static float LIFE_TIME = 5.0f; // How many seconds the coin lives for. static float DECAY_TIME = 5.0f; // How long does it take to disappear. #define I_DECAY_TIME ( 1.0f / DECAY_TIME ) // Instead of doing a divide. static float DECAY_EXTRA_SPIN = rmt::PI * 8.0f; // How much extra spin per frame do we want. static float SPIN_MULTIPLIER = 3.0f; static float RANGE = 2.25f; // Coin collection radius. #define INNER_RANGE ( RANGE * ( 4.0f / 3.0f ) ) static float COLLECT_TIME = 1.0f; // How long does it take a coin to suck into the player. #define I_COLLECT_TIME ( 1.0f / COLLECT_TIME ) static float POP_SCALE = 0.4f; // How much pop up does the coin have when it is collected. static float BOUNCE_DAMPENING = 0.6f; // How much energy does the coin retain when it hits the ground. static float FLYING_TIME = 0.25f; // How long does it take to fly up to the HUD. #define I_FLYING_TIME ( 1.0f / FLYING_TIME ) static float MAX_VISIBLITY = 60.0f; // How far away is the coin. static float COUNTER_X = 0.45f; // Where is the destination we are flying the coin up to. static float COUNTER_Y = 0.45f; // Where is the destination we are flying the coin up to. #ifdef RAD_XBOX static float SPARKLE_FLOAT = 0.025f; // How much vertical velocity for the sparkles per frame. #else static float SPARKLE_FLOAT = 0.05f; #endif static float IN_CAR_RANGE_MULTIPLIER = 2.25f; static int dbg_CoinsDrawn = 0; static int dbg_MaxCoins = 0; #else /* Same as the variables available to the watcher, but const so the compiler can optimize them. */ const static float COIN_HOVER = 0.5f; // How far off the ground will the coin hover. // Use this for more accurate coins. //static const float COIN_SPAWN_VELOCITY = 4.0f; //static const float COIN_EXTRA_VERTICAL = 2.0f #if !defined( RAD_PS2 ) && defined ( RAD_RELEASE ) static const float FRAME_RATIO = 1.0f / 60.0f; //static const float COIN_DRAG = 0.9848f; // .4 to the power of 1/60 of a second. #elif defined( RAD_RELEASE ) static const float FRAME_RATIO = 1.0f / 30.0f; //static const float COIN_DRAG = 0.9700f; // .4 to the power of 1/30 of a second. #else static const float FRAME_RATIO = 1.0f / 15.0f; //static const float COIN_DRAG = 0.9407f; // .4 to the power of 1/15 of a second. #endif static const float COIN_SPAWN_VELOCITY = 9.5f;// * FRAME_RATIO; static const float COIN_EXTRA_VERTICAL = 1.1f;// * FRAME_RATIO; static const float GRAVITY = 21.0f; static const float COIN_DRAG = 0.1f; //static const float GRAVITY = 0.9f * FRAME_RATIO; static const float LIFE_TIME = 11.0f; // How many seconds the coin lives for. static const float DECAY_TIME = 5.0f; // How long does it take to disappear. static const float I_DECAY_TIME = 1.0f / DECAY_TIME; // Instead of doing a divide. static const float DECAY_EXTRA_SPIN = rmt::PI * 8.0f; // How much extra spin per frame do we want. static const float SPIN_MULTIPLIER = 3.0f; static const float RANGE = 2.25f; // Coin collection radius. static const float INNER_RANGE = RANGE * ( 4.0f / 3.0f ); static const float COLLECT_TIME = 1.0f; // How long does it take a coin to suck into the player. static const float I_COLLECT_TIME = 1.0f / COLLECT_TIME; static const float POP_SCALE = 0.4f; // How much pop up does the coin have when it is collected. // Use these values if you want accurate coins. /* static const float COIN_DRAG = 0.5f; static const float GRAVITY = 0.9f; */ static const float BOUNCE_DAMPENING = 0.6f; // How much energy does the coin retain when it hits the ground. static const float FLYING_TIME = 0.25f; // How long does it take to fly up to the HUD. static const float I_FLYING_TIME = 1.0f / FLYING_TIME; static const float MAX_VISIBLITY = 60.0f; // How far away is the coin. static const float COUNTER_X = 0.25f; // Where is the destination we are flying the coin up to. static const float COUNTER_Y = 0.4f; // Where is the destination we are flying the coin up to. #ifdef RAD_XBOX static const float SPARKLE_FLOAT = 0.025f; #else static const float SPARKLE_FLOAT = 0.05f; #endif static const float IN_CAR_RANGE_MULTIPLIER = 2.25f; #endif //****************************************************************************** // // Global Data, Local Data, Local Classes // //****************************************************************************** CoinManager* CoinManager::spCoinManager = 0; static const int NUM_COINS = 200; static const int NUM_SPARKLES = 120; // Although we seldom use all these sparkles we do when the vehicle explodes. #ifdef RAD_XBOX static int Sparkle_Rate = 6; static const int Glint_Rate = 4; static const int SPARKLE_RATE_TOGGLE = Sparkle_Rate ^ ( Sparkle_Rate + 2 ); static const int HUD_SPARKLE_RATE = 60; #else static int Sparkle_Rate = 3; static const int Glint_Rate = 2; static const int SPARKLE_RATE_TOGGLE = Sparkle_Rate ^ ( Sparkle_Rate + 1 ); static const int HUD_SPARKLE_RATE = 30; #endif static int DoSparkle = 0; static int DoGlint = 0; static int GlintDelay = 0; //****************************************************************************** // // Public Member Functions // //****************************************************************************** CoinManager* CoinManager::CreateInstance( void ) { rAssertMsg( spCoinManager == 0, "CoinManager already created.\n" ); HeapManager::GetInstance()->PushHeap( GMA_PERSISTENT ); spCoinManager = new CoinManager; rAssert( spCoinManager ); HeapManager::GetInstance()->PopHeap( GMA_PERSISTENT ); return CoinManager::GetInstance(); } CoinManager* CoinManager::GetInstance( void ) { rAssertMsg( spCoinManager != 0, "CoinManager has not been created yet.\n" ); return spCoinManager; } void CoinManager::DestroyInstance( void ) { rAssertMsg( spCoinManager != 0, "CoinManager has not been created.\n" ); delete spCoinManager; spCoinManager = 0; } //============================================================================== // Description: Constructor. // // Parameters: None. // // Return: N/A. // //============================================================================== CoinManager::CoinManager() : m_pCoinDrawable( 0 ), mActiveCoins( NULL), mNumActiveCoins( 0 ), mNextInactiveCoin( 0 ), mNumHUDFlying( 0 ), mHUDSparkle( 0 ), mHUDCoinX( -10.0f ), mHUDCoinY( -10.0f ), mHUDCoinAngle( 0.0f ), mDrawAfterGui(false) { #ifndef RAD_RELEASE radDbgWatchAddFloat( &COIN_SPAWN_VELOCITY, "Spawn - Initial velocity", "Coins", 0, 0, 0.01f, 5.0f ); radDbgWatchAddFloat( &COIN_EXTRA_VERTICAL, "Spawn - Initial extra vertical", "Coins", 0, 0, 0.0f, 2.5f ); radDbgWatchAddFloat( &COIN_DRAG, "Spawn - Movement drag", "Coins", 0, 0, 0.1f, 1.0f ); radDbgWatchAddFloat( &GRAVITY, "Spawn - Gravity", "Coins", 0, 0, 0.1f, 2.0f ); radDbgWatchAddFloat( &BOUNCE_DAMPENING, "Spawn - Bounce energy dampening", "Coins", 0, 0, 0.0f, 2.0f ); radDbgWatchAddFloat( &COIN_HOVER, "Spawn - Ground hover", "Coins", 0, 0, 0.0f, 2.0f ); radDbgWatchAddFloat( &LIFE_TIME, "Duration - Life", "Coins", 0, 0, 1.0f, 30.0f ); radDbgWatchAddFloat( &DECAY_TIME, "Duration - Decay", "Coins", 0, 0, 1.0f, 10.0f ); radDbgWatchAddFloat( &RANGE, "Collection - Radius", "Coins", 0, 0, 0.5f, 10.0f ); radDbgWatchAddFloat( &IN_CAR_RANGE_MULTIPLIER, "Collection - In car multiplier", "Coins", 0, 0, 0.0f, 5.0f ); radDbgWatchAddFloat( &DECAY_EXTRA_SPIN, "Animation - Extra Decay spin", "Coins", 0, 0, 0.0f, 50.0f ); radDbgWatchAddFloat( &SPIN_MULTIPLIER, "Animation - Spin multiplier", "Coins", 0, 0, 0.01f, 5.0f ); radDbgWatchAddFloat( &COLLECT_TIME, "Animation - Collection duration", "Coins", 0, 0, 0.1f, 5.0f ); radDbgWatchAddFloat( &POP_SCALE, "Animation - Collection pop up", "Coins", 0, 0, 0.1f, 1.0f ); radDbgWatchAddFloat( &FLYING_TIME, "Animation - Fly to HUD duration", "Coins", 0, 0, 0.1f, 1.0f ); radDbgWatchAddFloat( &COUNTER_X, "Animation - HUD X position", "Coins", 0, 0, -0.7f, 0.7f ); radDbgWatchAddFloat( &COUNTER_Y, "Animation - HUD Y position", "Coins", 0, 0, -0.7f, 0.7f ); radDbgWatchAddFloat( &SPARKLE_FLOAT, "Animation - Sparkle float", "Coins", 0, 0, 0.0f, 0.5f ); radDbgWatchAddFloat( &MAX_VISIBLITY, "Visibility culling distance", "Coins", 0, 0, 10.0f, 100.0f ); radDbgWatchAddShort( &mNumActiveCoins, "Active coin count", "Coins", 0, 0, 0, NUM_COINS, true ); radDbgWatchAddInt( &dbg_CoinsDrawn, "Drawn coin count", "Coins", 0, 0, 0, NUM_COINS, true ); radDbgWatchAddInt( &dbg_MaxCoins, "Max active coins", "Coins", 0, 0, 0, NUM_COINS, true ); #endif } //============================================================================== // Description: Destructor. // // Parameters: None. // // Return: N/A. // //============================================================================== CoinManager::~CoinManager() { #ifndef RAD_RELEASE radDbgWatchDelete( &COIN_SPAWN_VELOCITY ); radDbgWatchDelete( &COIN_EXTRA_VERTICAL ); radDbgWatchDelete( &COIN_DRAG ); radDbgWatchDelete( &GRAVITY ); radDbgWatchDelete( &BOUNCE_DAMPENING ); radDbgWatchDelete( &COIN_HOVER ); radDbgWatchDelete( &LIFE_TIME ); radDbgWatchDelete( &DECAY_TIME ); radDbgWatchDelete( &RANGE ); radDbgWatchDelete( &DECAY_EXTRA_SPIN ); radDbgWatchDelete( &SPIN_MULTIPLIER ); radDbgWatchDelete( &COLLECT_TIME ); radDbgWatchDelete( &POP_SCALE ); radDbgWatchDelete( &FLYING_TIME ); radDbgWatchDelete( &COUNTER_X ); radDbgWatchDelete( &COUNTER_Y ); radDbgWatchDelete( &SPARKLE_FLOAT ); radDbgWatchDelete( &MAX_VISIBLITY ); radDbgWatchDelete( &mNumActiveCoins ); radDbgWatchDelete( &dbg_CoinsDrawn ); radDbgWatchDelete( &dbg_MaxCoins ); #endif tRefCounted::Release( m_pCoinDrawable ); Destroy( ); } //============================================================================== // Description: Destruction method for all transient data. // // Parameters: None. // // Return: N/A. // //============================================================================== void CoinManager::Destroy( void ) { SetCoinDrawable( NULL ); if( mActiveCoins != NULL ) { GetCheatInputSystem()->UnregisterCallback( this ); GetEventManager()->RemoveAll( this ); delete [] mActiveCoins; mActiveCoins = NULL; } mNumHUDFlying = 0; mNumActiveCoins = 0; } /*============================================================================== Description: This will be called set up as the game session begins. Parameters: ( void ) Return: void =============================================================================*/ void CoinManager::Init( void ) { #ifdef RAD_GAMECUBE HeapManager::GetInstance()->PushHeap( GMA_GC_VMM ); #else HeapManager::GetInstance()->PushHeap( GMA_LEVEL_ZONE ); #endif mActiveCoins = new ActiveCoin[ NUM_COINS ]; rAssert( mActiveCoins ); #ifdef RAD_GAMECUBE HeapManager::GetInstance()->PopHeap( GMA_GC_VMM ); #else HeapManager::GetInstance()->PopHeap( GMA_LEVEL_ZONE ); #endif mNextInactiveCoin = 0; mNumActiveCoins = 0; mNumHUDFlying = 0; DoSparkle = 0; DoGlint = 0; #ifndef RAD_RELEASE dbg_MaxCoins = 0; #endif GetEventManager()->AddListener( this, EVENT_VEHICLE_DESTROYED ); GetEventManager()->AddListener( this, EVENT_DUMP_DYNA_SECTION ); GetCheatInputSystem()->RegisterCallback( this ); } void CoinManager::HandleEvent( EventEnum id, void* pEventData ) { switch ( id ) { case EVENT_VEHICLE_DESTROYED: { Vehicle* vehicle = reinterpret_cast<Vehicle*>( pEventData ); OnVehicleDestroyed( vehicle ); } break; case EVENT_DUMP_DYNA_SECTION: { RemoveWorldCoins( reinterpret_cast<tName*>( pEventData )->GetUID() ); } break; default: { break; } } } /*============================================================================= Go nosing through he active coin array looking for an inactive coin. Update the next inactive coin index while we are at it to speed up the search. =============================================================================*/ CoinManager::ActiveCoin* CoinManager::GetInactiveCoin( void ) { if( mNumActiveCoins >= NUM_COINS ) { return 0; } for( int i = 0; i < NUM_COINS; ++i ) { ActiveCoin* c = &(mActiveCoins[ mNextInactiveCoin ]); mNextInactiveCoin = ( mNextInactiveCoin + 1 ) % NUM_COINS; if( c->State == CS_Inactive ) { c->HeadingCos = 0.0f; c->HeadingSin = 1.0f; return c; } } return 0; } /*============================================================================= Spawn count number of coins at position. You can optionally override the groundY position (handy for exploding wasps), or it takes the Y value form the position. =============================================================================*/ void CoinManager::SpawnCoins( int Count, const rmt::Vector& Position, const rmt::Vector* Direction, bool Force ) { SpawnCoins( Count, Position, Position.y, Direction, Force ); } void CoinManager::SpawnCoins( int Count, const rmt::Vector& Position, float GroundY, const rmt::Vector* Direction, bool Force ) { rAssert( Count >= 0 ); if( Count <= 0 ) { return; } // Check for a valid avatar pointer rTuneAssert( GetAvatarManager() != NULL ); rTuneAssert( GetAvatarManager()->GetAvatarForPlayer(0) != NULL ); bool instaCollect = !Force && GetAvatarManager()->GetAvatarForPlayer( 0 )->IsInCar(); for( int i = 0; i < Count; ++i ) { ActiveCoin* c = GetInactiveCoin(); if( !c ) { CollectCoins( Count - i ); break; } SpawnCoin( *c, Position, GroundY + COIN_HOVER, Direction, instaCollect ); } #ifdef RAD_GAMECUBE HeapMgr()->PushHeap( GMA_GC_VMM ); #else HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); #endif GetEventManager()->TriggerEvent( EVENT_SPAWNED_COINS, reinterpret_cast<void*>( Count ) ); #ifdef RAD_GAMECUBE HeapMgr()->PopHeap( GMA_GC_VMM ); #else HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); #endif } /*============================================================================= This spawns a coin which is immediately collected by the player. Used for giving the player a coin reward and you want to make sure they don't miss it. =============================================================================*/ void CoinManager::SpawnInstantCoins(int Count, const rmt::Vector& Position) { rAssert( Count >= 0 ); if( Count <= 0 ) { return; } // Check for a valid avatar pointer rTuneAssert( GetAvatarManager() != NULL ); rTuneAssert( GetAvatarManager()->GetAvatarForPlayer(0) != NULL ); for( int i = 0; i < Count; ++i ) { ActiveCoin* c = GetInactiveCoin(); if( !c ) { CollectCoins( Count - i ); break; } SpawnCoin(*c, Position, Position.y + COIN_HOVER, 0, true); } #ifdef RAD_GAMECUBE HeapMgr()->PushHeap( GMA_GC_VMM ); #else HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); #endif GetEventManager()->TriggerEvent( EVENT_SPAWNED_COINS, reinterpret_cast<void*>( Count ) ); #ifdef RAD_GAMECUBE HeapMgr()->PopHeap( GMA_GC_VMM ); #else HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); #endif } /*============================================================================= Spawn a single coin. =============================================================================*/ void CoinManager::SpawnCoin( ActiveCoin& Coin, const rmt::Vector& Start, float Ground, const rmt::Vector* Direction, bool InstaCollect ) { Coin.Position = Start; Coin.Position.y = Ground + COIN_HOVER; Coin.State = InstaCollect ? CS_SpawnToCollect : CS_InitialSpawning; Coin.Age = Sparkle::sRandom.Float() * rmt::PI; Coin.Ground = Ground; Coin.Velocity.x = Sparkle::sRandom.FloatSigned(); Coin.Velocity.y = Sparkle::sRandom.Float(); Coin.Velocity.z = Sparkle::sRandom.FloatSigned(); Coin.Velocity.Normalize(); Coin.Velocity.y += COIN_EXTRA_VERTICAL; if( Direction ) { if( Direction->Dot( Coin.Velocity ) < 0 ) { Coin.Velocity.x *= -1.0f; Coin.Velocity.z *= -1.0f; } } Coin.Velocity.Scale(COIN_SPAWN_VELOCITY); ++mNumActiveCoins; #ifndef RAD_RELEASE dbg_MaxCoins = rmt::Max( (int)mNumActiveCoins, dbg_MaxCoins ); #endif } /*============================================================================= Add a HUD coin at the coin counter position and have it fly down to the middle of the screen. Used when the player's coin amount decrements so that they can see that it's going down a little easier. =============================================================================*/ void CoinManager::AddFlyDownCoin(void) { return; // They didn't like it. Method is now a nop...until they decide they want it again. /* ActiveCoin* c = GetInactiveCoin(); if( !c ) { return; } c->Position.x = 0.0f; c->Position.y = 0.0f; c->Position.z = 0.0f; c->State = CS_FlyingFromHUD; c->Age = 0.0f; c->Velocity.Set(-0.5f, -0.5f, 0.0f); c->Velocity.Scale(FLYING_TIME * 2.0f); ++mNumActiveCoins; ++mNumHUDFlying; #ifndef RAD_RELEASE dbg_MaxCoins = rmt::Max( (int)mNumActiveCoins, dbg_MaxCoins ); #endif */ } /*============================================================================= Update the position/animation of all the coins. =============================================================================*/ void CoinManager::Update( int ElapsedMS ) { float deltaSeconds = (float)ElapsedMS * 0.001f; mHUDCoinAngle += deltaSeconds; #ifndef RAD_RELEASE dbg_CoinsDrawn = 0; #endif if(GetGameFlow()->GetCurrentContext() == CONTEXT_PAUSE) { return; } rmt::Vector avatarPos; GetAvatarManager()->GetAvatarForPlayer( 0 )->GetPosition( avatarPos ); bool isInCar = GetAvatarManager()->GetAvatarForPlayer( 0 )->IsInCar(); for( int i = 0; i < NUM_COINS; ++i ) { ActiveCoin& c = mActiveCoins[ i ]; if( c.State != CS_Inactive ) { float decaySpin = 0.0f; if( c.State != CS_InitialSpawning && c.State != CS_SpawnToCollect && c.State != CS_Collecting && c.State != CS_Collected ) { CheckCollection( c, avatarPos, isInCar ? IN_CAR_RANGE_MULTIPLIER : 1.0f ); } if( c.State == CS_Collecting ) { UpdateCollecting( c, avatarPos ); } else if((c.State == CS_FlyingToHUD) || (c.State == CS_FlyingFromHUD)) { UpdateHUDFlying( c ); } else if( c.State != CS_RestingIndefinitely && c.State != CS_Collecting && c.State != CS_Collected ) { if( c.Age > LIFE_TIME + DECAY_TIME ) { // Coin has expired. c.State = CS_Inactive; --mNumActiveCoins; GetSparkleManager()->AddSparkle( rmt::Vector( c.Position.x, c.Position.y + mCoinBounding.radius, c.Position.z ), 0.25f, 0.5f, rmt::Vector( 0.0f, 0.01f, 0.0f ), Sparkle::SE_Vanish ); } else if( c.Age > LIFE_TIME ) { c.State = CS_Decaying; decaySpin = ( c.Age - LIFE_TIME ) * I_DECAY_TIME; decaySpin *= decaySpin; } if( c.State == CS_Spawning || c.State == CS_InitialSpawning || c.State == CS_SpawnToCollect ) { UpdateSpawning(c, avatarPos, deltaSeconds); } } c.Age += deltaSeconds; // Rotate the coin based on age. float coinSin, coinCos; rmt::SinCos( ( c.Age * SPIN_MULTIPLIER ) + ( decaySpin * DECAY_EXTRA_SPIN ), &coinSin, &coinCos ); c.HeadingCos = coinCos; c.HeadingSin = coinSin; } } } /*============================================================================= Damaging out the player's vehicle causes their bank to go down. =============================================================================*/ void CoinManager::OnVehicleDestroyed( Vehicle* DestroyedVehicle ) { if( DestroyedVehicle->mVehicleType == VT_USER ) { int lose = rmt::Min( 20, GetBankValue() ); LoseCoins( lose, &( DestroyedVehicle->GetPosition() ) ); } } /*============================================================================= Whatever drawable is set here will be used for all the coin drawables. Note that there isn't any support for shadows or animation. It's just geometry. =============================================================================*/ void CoinManager::SetCoinDrawable( tDrawable* Drawable ) { tRefCounted::Assign( m_pCoinDrawable, Drawable ); if( m_pCoinDrawable ) { m_pCoinDrawable->GetBoundingSphere( &mCoinBounding ); } else { mCoinBounding.centre.Set( 0.0f, 0.0f, 0.0f ); mCoinBounding.radius = 0.0f; } } /*============================================================================= Check if a coin is within range to do the collection animation to the player. If it is we set the state to Collecting. =============================================================================*/ void CoinManager::CheckCollection( ActiveCoin& Coin, const rmt::Vector& AvatarPos, float RangeMultiplier ) { float x = rmt::Abs( Coin.Position.x - AvatarPos.x ); float z = rmt::Abs( Coin.Position.z - AvatarPos.z ); float y = rmt::Abs( Coin.Position.y - AvatarPos.y ); // Note, no multipler for Y. if( ( x < ( RANGE * RangeMultiplier ) ) && ( z < ( RANGE * RangeMultiplier ) ) && ( y < RANGE ) && ( x + z < ( INNER_RANGE * RangeMultiplier ) ) ) { if( Coin.State == CS_RestingIndefinitely ) { GetPersistentWorldManager()->OnObjectBreak( Coin.PersistentObjectID ); } Coin.State = CS_Collecting; Coin.Age = 0.0f; } } /*============================================================================= Player has picked up a coin. This will increase the player's bank value, fire a coin collected event so that we can play a sound =============================================================================*/ void CoinManager::CollectCoins( int Count ) { AdjustBankValue( Count ); #ifdef RAD_GAMECUBE HeapMgr()->PushHeap( GMA_GC_VMM ); #else HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); #endif GetEventManager()->TriggerEvent( EVENT_COLLECTED_COINS, reinterpret_cast<void*>( Count ) ); #ifdef RAD_GAMECUBE HeapMgr()->PopHeap( GMA_GC_VMM ); #else HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); #endif } /*============================================================================= Player loses coins for whatever reason. We play a sound and HUD animation. If Position isn't null then we respawn the coins at the specified location. =============================================================================*/ void CoinManager::LoseCoins( int Count, const rmt::Vector* Position ) { int count = rmt::Min( Count, GetBankValue() ); rAssert( count >= 0 ); AdjustBankValue( -count ); if( Position ) { // Because we are losing the coins when we are shot (or car explodes) the position tends to be lower //then for a crate. So we raise the position a tiny bit so that the coin's travel is the same as //the designers tune for when kicking the crates around. SpawnCoins( count, rmt::Vector(Position->x, Position->y + 0.25f, Position->z), Position->y, 0, true ); } #ifdef RAD_GAMECUBE HeapMgr()->PushHeap( GMA_GC_VMM ); #else HeapMgr()->PushHeap( GMA_LEVEL_ZONE ); #endif GetEventManager()->TriggerEvent( EVENT_LOST_COINS, reinterpret_cast<void*>( count ) ); #ifdef RAD_GAMECUBE HeapMgr()->PopHeap( GMA_GC_VMM ); #else HeapMgr()->PopHeap( GMA_LEVEL_ZONE ); #endif } int CoinManager::GetBankValue() const { RenderEnums::LevelEnum currentLevel = GetGameplayManager()->GetCurrentLevelIndex(); int bankValue = GetCharacterSheetManager()->GetNumberOfTokens( currentLevel ); return bankValue; } /*============================================================================= Modify the player's bank value without any special effects. =============================================================================*/ void CoinManager::AdjustBankValue( int DeltaCoins ) { /* mBankValue += DeltaCoins; mBankValue = rmt::Max( mBankValue, 0 ); */ // tell the character sheet manager // GetCharacterSheetManager()->AddTokens( GetGameplayManager()->GetCurrentLevelIndex(), DeltaCoins ); if( DeltaCoins > 0 ) { GetEventManager()->TriggerEvent( EVENT_COLLECTED_COINS ); } else if( DeltaCoins < 0 ) { GetEventManager()->TriggerEvent( EVENT_LOST_COINS ); } } /* /*============================================================================= Force bank to a specific value. No special effects. =============================================================================/ void CoinManager::SetBankValue( int Coins ) { rAssert( Coins >= 0 ); mBankValue = Coins; // update character sheet // GetCharacterSheetManager()->SetNumberOfTokens( GetGameplayManager()->GetCurrentLevelIndex(), mBankValue ); } */ /*============================================================================= World coins just sit around until their zone unloads. They take up space in the active coin array so don't use them lightly. =============================================================================*/ void CoinManager::AddWorldCoin( const rmt::Vector& Position, tUID Sector ) { if( !mActiveCoins ) { return; } short persistentID = GetPersistentWorldManager()->GetPersistentObjectID( Sector ); if( persistentID < -1 ) { return; } ActiveCoin* c = GetInactiveCoin(); if( !c ) { return; } c->Position = Position; c->State = CS_RestingIndefinitely; c->Age = Sparkle::sRandom.Float() * rmt::PI; c->Sector = Sector; c->PersistentObjectID = persistentID; ++mNumActiveCoins; #ifndef RAD_RELEASE dbg_MaxCoins = rmt::Max( (int)mNumActiveCoins, dbg_MaxCoins ); #endif } /*============================================================================= Remove any coins resting in the world which are in the sector which was just unloaded. Only remove the coins which are in CS_RestingIndefinite state since others could be spawning or collecting, etc (that's kind of unlikely however). =============================================================================*/ void CoinManager::RemoveWorldCoins( tUID Sector ) { for( int i = 0; i < NUM_COINS; ++i ) { ActiveCoin& c = mActiveCoins[ i ]; if( ( c.State == CS_RestingIndefinitely ) && ( c.Sector == Sector ) ) { c.State = CS_Inactive; --mNumActiveCoins; } } } /*============================================================================= Update the animation for the coins when they are collected. =============================================================================*/ void CoinManager::UpdateCollecting( ActiveCoin& Coin, const rmt::Vector& AvatarPos ) { if( Coin.Age > ( COLLECT_TIME * 0.4f ) ) { Coin.State = CS_Collected; Coin.Age = 0.0f; Coin.HeadingCos = 1; CollectCoins( 1 ); } else { rmt::Vector diff; diff.Sub( AvatarPos, Coin.Position ); diff.Scale( Coin.Age * I_COLLECT_TIME ); float pop = ( COLLECT_TIME - Coin.Age ) * I_COLLECT_TIME; diff.y += pop * POP_SCALE; Coin.Position.Add( diff ); --DoSparkle; if( DoSparkle < 0 ) { DoSparkle = Sparkle_Rate; Sparkle_Rate ^= SPARKLE_RATE_TOGGLE; rmt::Vector vel; vel.Scale(FRAME_RATIO * 0.1f, Coin.Velocity); vel.y += SPARKLE_FLOAT; GetSparkleManager()->AddSparkle( Coin.Position, 0.5f, 1.0f, vel, Sparkle::SE_Trail ); } } } /*============================================================================= Update the spawning animation for the coins. =============================================================================*/ void CoinManager::UpdateSpawning(ActiveCoin& Coin, const rmt::Vector& AvatarPos, float DeltaSeconds) { rmt::Vector vel; vel.Scale(DeltaSeconds, Coin.Velocity); Coin.Velocity.Scale( (float)pow(COIN_DRAG, DeltaSeconds) ); Coin.Velocity.y -= GRAVITY * DeltaSeconds; Coin.Position.Add( vel ); // Use this if we want faster (but less accurate) coins. /* Coin.Velocity.Scale( COIN_DRAG ); Coin.Velocity.y -= GRAVITY; Coin.Position.Add( Coin.Velocity ); */ if( Coin.State == CS_InitialSpawning ) { --DoSparkle; if( DoSparkle < 0 ) { DoSparkle = Sparkle_Rate; Sparkle_Rate ^= SPARKLE_RATE_TOGGLE; GetSparkleManager()->AddSparkle( Coin.Position, 0.5f, 1.0f, rmt::Vector( Coin.Velocity.x * 0.1f, ( Coin.Velocity.y * 0.1f ) + SPARKLE_FLOAT, Coin.Velocity.z * 0.1f ), Sparkle::SE_Trail ); } } if( Coin.Position.y < Coin.Ground ) { // Bounce. Coin.Velocity.y = rmt::Abs( Coin.Velocity.y * BOUNCE_DAMPENING ); if( Coin.State == CS_SpawnToCollect ) { Coin.State = CS_Collecting; Coin.Age = 0.0f; } else { Coin.State = CS_Spawning; if( Coin.Velocity.y < 0.01f ) { Coin.State = CS_Resting; } } Coin.Position.y = Coin.Ground; } } void CoinManager::AddGlint( ActiveCoin& Coin, const rmt::Vector& ToCamera, const rmt::Vector& CoinAxis, const rmt::Vector& CameraRight ) { rmt::Vector pos; pos.Add( Coin.Position, mCoinBounding.centre ); float dot = ToCamera.DotProduct( CoinAxis ); float radius = ( dot < 0.0f ) ? -mCoinBounding.radius : mCoinBounding.radius; pos.ScaleAdd( radius, CoinAxis ); rmt::Vector left; left.Scale( -0.0025f, CameraRight ); GetSparkleManager()->AddSparkle( pos, 1.0f, 0.25f, left, Sparkle::SE_Glint ); } /*============================================================================= Animation of the coin zipping up to the HUD counter. =============================================================================*/ void CoinManager::UpdateHUDFlying( ActiveCoin& Coin ) { // Do fly to HUD animation. if(((Coin.State == CS_FlyingToHUD) && (Coin.Age > FLYING_TIME )) || ((Coin.State == CS_FlyingFromHUD) && (Coin.Age > 0.5f))) { Coin.State = CS_Inactive; --mNumActiveCoins; --mNumHUDFlying; } } void CoinManager::ClearHUDCoins(void) { if(mNumHUDFlying == 0) { return; } for(int i = 0; i < NUM_COINS; ++i) { ActiveCoin& c = mActiveCoins[i]; if((c.State == CS_FlyingToHUD) || (c.State == CS_FlyingFromHUD)) { c.State = CS_Inactive; --mNumActiveCoins; --mNumHUDFlying; } } } /*============================================================================= Render pass called form within the opaque rendering stage of world render. It does a simple distance and camera culling check to determine which coins to draw. We don't add the coins to the DSG since the overhead of moving/adding/ removing them is too much. Also, the DSG nodes are hard coded to only allow a certain number of extra entities over the amount preallocated in the pipeline. It's very possible that we'll end up with more then then that value. =============================================================================*/ void CoinManager::Render( void ) { if( ( mNumActiveCoins - mNumHUDFlying ) > 0 ) { tCamera* camera = p3d::context->GetView()->GetCamera(); rAssert( camera ); const rmt::Matrix& camTrans = camera->GetCameraToWorldMatrix(); for( int i = 0; i < NUM_COINS; ++i ) { ActiveCoin& c = mActiveCoins[ i ]; if( c.State != CS_Inactive ) { if( c.State == CS_Collected ) { camera->WorldToView( c.Position, &c.Position ); c.Velocity.Sub( rmt::Vector( COUNTER_X, COUNTER_Y, 0.0f ), c.Position ); c.State = CS_FlyingToHUD; ++mNumHUDFlying; continue; } if( !m_pCoinDrawable ) { continue; } // Visiblity test. rmt::Vector diff; diff.Sub( camTrans.Row( 3 ), c.Position ); float camDirDot = diff.DotProduct( camTrans.Row( 2 ) ); // Note that the diff vector is towards the camera so the camDirDot will be negative when the camera is facing //the object. So if it's positive then we can assume it's behind the camera. if( camDirDot > 0.0f ) { continue; } // Distance check. float distance = diff.Magnitude(); if( distance > MAX_VISIBLITY ) { continue; } #ifndef RAD_RELEASE ++dbg_CoinsDrawn; #endif if( ( GlintDelay <= 0 ) && ( DoGlint == i ) ) { if( c.State != CS_InitialSpawning && c.State != CS_Collecting && c.State != CS_Collected ) { if(GetGameFlow()->GetCurrentContext() != CONTEXT_PAUSE) { AddGlint( c, diff, rmt::Vector( c.HeadingCos, 0.0f, -c.HeadingSin ), camTrans.Row( 0 ) ); } } } // Down the columns, across the rows. rmt::Matrix transform( c.HeadingCos, 0.0f, -c.HeadingSin, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, c.HeadingSin, 0.0f, c.HeadingCos, 0.0f, c.Position.x, c.Position.y, c.Position.z, 1.0f ); p3d::pddi->PushMultMatrix( PDDI_MATRIX_MODELVIEW, &transform ); m_pCoinDrawable->Display(); p3d::pddi->PopMatrix( PDDI_MATRIX_MODELVIEW ); } } } --GlintDelay; if( GlintDelay < 0 ) { GlintDelay = Glint_Rate; DoGlint = Sparkle::sRandom.IntRanged( 0, NUM_COINS ); } } void CoinManager::SetHUDCoin( int X, int Y, bool IsShowing ) { const static float ScreenWidthRatio = 1.0f / 640.0f; const static float ScreenHeightRatio = 1.0f / 480.0f; const static float ScreenAspect = 4.0f / 3.0f; if( IsShowing ) { mHUDCoinX = ( ( ( (float)X * ScreenWidthRatio ) ) - 0.5f ) * ScreenAspect * ScreenAspect; mHUDCoinY = ( ( ( (float)Y * ScreenHeightRatio ) ) - 0.5f ) * ScreenAspect; } else { mHUDCoinX = -10.f; } } void CoinManager::OnCheatEntered( eCheatID cheatID, bool isEnabled ) { if( cheatID == CHEAT_ID_EXTRA_COINS ) { this->AdjustBankValue( +100 ); // add 100 coins to bank value } } /*============================================================================= Rendering pass for the HUD. Used for when the coin flies up to the HUD counter. We set the state once and do all the rendering. =============================================================================*/ void CoinManager::HUDRender( void ) { if( !m_pCoinDrawable ) { return; } bool renderHUDCoin = false; if( mHUDCoinX > -1.0f && mHUDCoinX < 1.0f && mHUDCoinY > -1.0f && mHUDCoinY < 1.0f ) { renderHUDCoin = true; } if( ( mNumHUDFlying > 0 ) || renderHUDCoin ) { p3d::stack->Push(); bool oldZWrite = p3d::pddi->GetZWrite(); pddiCompareMode oldZComp = p3d::pddi->GetZCompare(); if( oldZWrite ) { p3d::pddi->SetZWrite( false ); } if( oldZComp != PDDI_COMPARE_ALWAYS ) { p3d::pddi->SetZCompare( PDDI_COMPARE_ALWAYS ); } p3d::pddi->SetProjectionMode( PDDI_PROJECTION_ORTHOGRAPHIC ); pddiColour oldAmbient = p3d::pddi->GetAmbientLight(); p3d::pddi->SetAmbientLight( pddiColour( 255, 255, 255 ) ); rmt::Vector pos; if( renderHUDCoin ) { p3d::stack->LoadIdentity(); p3d::stack->Scale( 0.1f, 0.1f, 1.0f ); float coinSin, coinCos; rmt::SinCos( ( mHUDCoinAngle * SPIN_MULTIPLIER ), &coinSin, &coinCos ); rmt::Matrix transform( coinCos, 0.0f, -coinSin, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, coinSin, 0.0f, coinCos, 0.0f, mHUDCoinX * 5.0f, mHUDCoinY * 5.0f, 5.0f, 1.0f ); #ifndef RAD_RELEASE ++dbg_CoinsDrawn; #endif if( mHUDSparkle <= 0 ) { rmt::Vector pos; pos.ScaleAdd( rmt::Vector( mHUDCoinX, mHUDCoinY, 5.0f ), 0.2f, mCoinBounding.centre ); float radius = 0.2f * ( ( coinCos < 0.0f ) ? mCoinBounding.radius : -mCoinBounding.radius ); pos.Add( rmt::Vector( coinCos * radius, 0.0f, coinSin * radius ) ); GetSparkleManager()->AddSparkle( pos, 1.0f, 0.25f, rmt::Vector( 0.0f, 0.0f, 0.0f ), Sparkle::SE_HUDGlint ); mHUDSparkle = Sparkle::sRandom.IntRanged( HUD_SPARKLE_RATE, HUD_SPARKLE_RATE * 3 ); } --mHUDSparkle; p3d::pddi->PushMultMatrix( PDDI_MATRIX_MODELVIEW, &transform ); m_pCoinDrawable->Display(); p3d::pddi->PopMatrix( PDDI_MATRIX_MODELVIEW ); } p3d::stack->LoadIdentity(); p3d::stack->Scale( 0.05f, 0.05f, 1.0f); for( int i = 0; i < NUM_COINS; ++i ) { ActiveCoin& c = mActiveCoins[ i ]; if((c.State == CS_FlyingToHUD) || (c.State == CS_FlyingFromHUD)) { if(c.State == CS_FlyingToHUD) { float a = c.Age * I_FLYING_TIME; pos.ScaleAdd( c.Position, a * a, c.Velocity ); } else { pos.ScaleAdd(c.Position, c.Age * I_FLYING_TIME, c.Velocity); } // Down the columns, across the rows. rmt::Matrix transform( 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, pos.x * 10.0f, pos.y * 10.0f, 5.0f, 1.0f ); #ifndef RAD_RELEASE ++dbg_CoinsDrawn; #endif p3d::pddi->PushMultMatrix( PDDI_MATRIX_MODELVIEW, &transform ); m_pCoinDrawable->Display(); p3d::pddi->PopMatrix( PDDI_MATRIX_MODELVIEW ); } } p3d::pddi->SetProjectionMode(PDDI_PROJECTION_PERSPECTIVE); p3d::pddi->SetAmbientLight( oldAmbient ); if( oldZWrite ) { p3d::pddi->SetZWrite( true ); } if( oldZComp != PDDI_COMPARE_ALWAYS ) { p3d::pddi->SetZCompare( oldZComp ); } p3d::stack->Pop(); } }