//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // // Purpose: Declares the base class for paint power users that are props. // //===========================================================================// #ifndef PROP_PAINT_POWER_USER_H #define PROP_PAINT_POWER_USER_H #include "vphysics/friction.h" #include "vphysics/constraints.h" #include "player_pickup.h" #include "paintable_entity.h" #ifndef CLIENT_DLL #include "portal/weapon_physcannon.h" #endif #include "portal_util_shared.h" #include "portal_base2d_shared.h" #include "paint_power_user.h" #include "stick_partner.h" #include "material_index_data_ops_proxy.h" char const* const PROP_PAINT_POWER_USER_DATA_CLASS_NAME = "PropPaintPowerUser"; char const* const UPDATE_PAINT_POWER_CONTEXT = "UpdatePaintPowers"; const float PROP_PAINT_POWER_USER_PICKUP_DROP_TIME = 0.5f; //============================================================================= // class PropPaintPowerUser // Purpose: Base class for props which use paint powers. //============================================================================= template< typename BasePropType > class PropPaintPowerUser : public PaintPowerUser< CPaintableEntity< BasePropType > > // Derive from PaintPowerUser but add CPaintableEntity. { DECLARE_CLASS( PropPaintPowerUser< BasePropType >, PaintPowerUser< CPaintableEntity< BasePropType > > ); DECLARE_DATADESC(); static const datamap_t DataMapInit(); public: //------------------------------------------------------------------------- // Constructor/Virtual Destructor //------------------------------------------------------------------------- PropPaintPowerUser(); virtual ~PropPaintPowerUser(); //------------------------------------------------------------------------- // Prop Overrides //------------------------------------------------------------------------- virtual void Spawn(); virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); virtual void VPhysicsUpdate( IPhysicsObject *pPhysics ); virtual void UpdatePaintPowersFromContacts(); //------------------------------------------------------------------------- // Paintable Entity Overrides //------------------------------------------------------------------------- virtual void Paint( PaintPowerType type, const Vector& worldContactPt ); protected: int m_nOriginalMaterialIndex; // Cached physics material index (it changes) int m_PrePaintedPower; // Power to start with on load typedef typename BaseClass::PaintPowerInfoVector BaseClass_PaintPowerInfoVector; virtual void ChooseActivePaintPowers( BaseClass_PaintPowerInfoVector& activePowers ); static int GetSpeedMaterialIndex(); private: //------------------------------------------------------------------------- // Private Data //------------------------------------------------------------------------- bool m_bHeldByPlayer; float m_flPickedUpTime; //------------------------------------------------------------------------- // Paint Power Effects //------------------------------------------------------------------------- virtual PaintPowerState ActivateSpeedPower( PaintPowerInfo_t& powerInfo ); virtual PaintPowerState UseSpeedPower( PaintPowerInfo_t& powerInfo ); virtual PaintPowerState DeactivateSpeedPower( PaintPowerInfo_t& powerInfo ); virtual PaintPowerState ActivateBouncePower( PaintPowerInfo_t& powerInfo ); virtual PaintPowerState UseBouncePower( PaintPowerInfo_t& powerInfo ); virtual PaintPowerState DeactivateBouncePower( PaintPowerInfo_t& powerInfo ); }; //============================================================================= // PropPaintPowerUser Implementation //============================================================================= // OMFG HACK: Define the data description table. The current macros don't work with templatized classes. // OMFG TODO: Write a generic macro to work with templatized classes. template< typename BasePropType > datamap_t PropPaintPowerUser::m_DataMap = PropPaintPowerUser::DataMapInit(); template< typename BasePropType > datamap_t* PropPaintPowerUser::GetDataDescMap() { return &m_DataMap; } template< typename BasePropType > datamap_t* PropPaintPowerUser::GetBaseMap() { datamap_t *pResult; DataMapAccess((BaseClass *)NULL, &pResult); return pResult; } template< typename BasePropType > const datamap_t PropPaintPowerUser::DataMapInit() { typedef PropPaintPowerUser classNameTypedef; static CDatadescGeneratedNameHolder nameHolder(PROP_PAINT_POWER_USER_DATA_CLASS_NAME); static typedescription_t dataDesc[] = { DEFINE_KEYFIELD( m_PrePaintedPower, FIELD_INTEGER, "PaintPower" ), DEFINE_CUSTOM_FIELD( m_nOriginalMaterialIndex, &GetMaterialIndexDataOpsProxy() ) }; datamap_t dataMap = { dataDesc, SIZE_OF_ARRAY(dataDesc), PROP_PAINT_POWER_USER_DATA_CLASS_NAME, PropPaintPowerUser::GetBaseMap() }; return dataMap; } template< typename BasePropType > PropPaintPowerUser::PropPaintPowerUser() : m_PrePaintedPower(NO_POWER), m_flPickedUpTime( 0.0f ), m_bHeldByPlayer( false ) { } template< typename BasePropType > PropPaintPowerUser::~PropPaintPowerUser() { } template< typename BasePropType > void PropPaintPowerUser::Spawn() { BaseClass::Spawn(); // Store our material index IPhysicsObject* pPhysObject = this->VPhysicsGetObject(); if( pPhysObject ) { m_nOriginalMaterialIndex = pPhysObject->GetMaterialIndex(); } this->AddFlag( FL_AFFECTED_BY_PAINT ); if( m_PrePaintedPower != NO_POWER ) { this->Paint( (PaintPowerType)m_PrePaintedPower, vec3_origin ); } } template< typename BasePropType > void PropPaintPowerUser::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { if( engine->HasPaintmap() ) { CBaseEntity* pOther = pEvent->pEntities[!index]; PaintPowerInfo_t contact; // Get data out of the event Vector vNormal, vPoint; pEvent->pInternalData->GetSurfaceNormal( vNormal ); pEvent->pInternalData->GetContactPoint( vPoint ); // Fill out contact info contact.m_SurfaceNormal = -vNormal; contact.m_ContactPoint = vPoint; contact.m_HandleToOther.Set( pOther ); // Add info to paint power info this->AddSurfacePaintPowerInfo( contact, 0 ); } BaseClass::VPhysicsCollision( index, pEvent ); } template< typename BasePropType > void PropPaintPowerUser::VPhysicsUpdate( IPhysicsObject *pPhysics ) { if( engine->HasPaintmap() ) { UpdatePaintPowersFromContacts(); } BaseClass::VPhysicsUpdate( pPhysics ); } template< typename BasePropType > void PropPaintPowerUser::UpdatePaintPowersFromContacts() { //If the prop is held by a player if( GetPlayerHoldingEntity( this ) ) { //If the prop was not already held by a player if( !m_bHeldByPlayer ) { m_bHeldByPlayer = true; //Set the timer m_flPickedUpTime = gpGlobals->curtime; } } else { m_bHeldByPlayer = false; m_flPickedUpTime = 0.0f; } IPhysicsObject* pPhysObject = this->VPhysicsGetObject(); if( pPhysObject ) { IPhysicsFrictionSnapshot* pSnapShot = pPhysObject->CreateFrictionSnapshot(); while( pSnapShot->IsValid() ) { PaintPowerInfo_t contact; IPhysicsObject *pOther = pSnapShot->GetObject(1); CBaseEntity *pOtherEntity = static_cast(pOther->GetGameData()); Assert(pOtherEntity); if( pOtherEntity != NULL ) { // Get data out of the event Vector vNormal, vPoint; pSnapShot->GetSurfaceNormal( vNormal ); pSnapShot->GetContactPoint( vPoint ); // Fill out contact info contact.m_SurfaceNormal = -vNormal; contact.m_ContactPoint = vPoint; contact.m_HandleToOther.Set( pOtherEntity ); // Add info to paint power info this->AddSurfacePaintPowerInfo( contact, 0 ); } pSnapShot->NextFrictionData(); } pPhysObject->DestroyFrictionSnapshot( pSnapShot ); // Figure out paint powers this->UpdatePaintPowers(); } else { // Clear all current data this->ClearSurfacePaintPowerInfo(); } } template< typename BasePropType > void PropPaintPowerUser::ChooseActivePaintPowers( BaseClass_PaintPowerInfoVector& activePowers ) { this->MapSurfacesToPowers(); // Get the contacts PaintPowerConstRange powerRange = this->GetSurfacePaintPowerInfo(); size_t count = powerRange.second - powerRange.first; // Set our desired paint power to be our current painted color PaintPowerInfo_t desiredPower; desiredPower.m_PaintPowerType = NO_POWER; // Get the first active power, since props can only have one at a time const PaintPowerInfo_t* pHighestPriorityActivePower = this->FindHighestPriorityActivePaintPower(); PaintPowerInfo_t currentPower = pHighestPriorityActivePower ? *pHighestPriorityActivePower : PaintPowerInfo_t( Vector(0, 0, 1), this->GetAbsOrigin(), 0 ); PaintPowerType paintedPower = this->GetPaintedPower(); // If we're touching something if( count != 0 ) { this->PrioritySortSurfacePaintPowerInfo( &DescendingPaintPriorityCompare ); // Default our desired color to be our current painted color so this will default // as our power if all else fails if( paintedPower != NO_POWER ) { for( PaintPowerConstIter i = powerRange.first; i != powerRange.second; ++i ) { if( i->m_PaintPowerType != INVALID_PAINT_POWER ) { desiredPower = *i; desiredPower.m_PaintPowerType = paintedPower; break; } } } // Go through all the surfaces and try to find a power to use for( PaintPowerConstIter i = powerRange.first; i != powerRange.second; ++i ) { const PaintPowerInfo_t& powerInfo = *i; if( currentPower.m_PaintPowerType == SPEED_POWER ) { // Always take bounce when currently using speed if( powerInfo.m_PaintPowerType == BOUNCE_POWER ) { desiredPower = powerInfo; } // Take speed if others are not present else if( desiredPower.m_PaintPowerType == NO_POWER && powerInfo.m_PaintPowerType == SPEED_POWER ) { desiredPower = powerInfo; } } else if( powerInfo.m_PaintPowerType != NO_POWER && powerInfo.m_PaintPowerType < desiredPower.m_PaintPowerType ) { // Accept whatever it was if it's of higher priority desiredPower = powerInfo; } }//for }//if count // Add the power to the active list activePowers.AddToTail( desiredPower ); } template< typename BasePropType > PaintPowerState PropPaintPowerUser::ActivateSpeedPower( PaintPowerInfo_t& powerInfo ) { IPhysicsObject* pPhysObject = this->VPhysicsGetObject(); if( pPhysObject ) { pPhysObject->SetMaterialIndex( ThisClass::GetSpeedMaterialIndex() ); } return ACTIVE_PAINT_POWER; } template< typename BasePropType > PaintPowerState PropPaintPowerUser::UseSpeedPower( PaintPowerInfo_t& powerInfo ) { return ACTIVE_PAINT_POWER; } template< typename BasePropType > PaintPowerState PropPaintPowerUser::DeactivateSpeedPower( PaintPowerInfo_t& powerInfo ) { IPhysicsObject* pPhysObject = this->VPhysicsGetObject(); if( pPhysObject ) { pPhysObject->SetMaterialIndex( m_nOriginalMaterialIndex ); } return INACTIVE_PAINT_POWER; } extern ConVar sv_wall_bounce_trade; extern ConVar bounce_paint_wall_jump_upward_speed; extern ConVar bounce_paint_min_speed; template< typename BasePropType > PaintPowerState PropPaintPowerUser::ActivateBouncePower( PaintPowerInfo_t& info ) { IPhysicsObject* pPhysObject = this->VPhysicsGetObject(); if( pPhysObject ) { float flTrade = sv_wall_bounce_trade.GetFloat(); // We trade some outward velocity for upward velocity const Vector vUp = Vector(0,0,1); Vector vBounceVel(0,0,0); // Cancel out velocity going into the surface Vector velocity; AngularImpulse angularVel; pPhysObject->GetVelocity( &velocity, &angularVel); velocity -= info.m_SurfaceNormal * DotProduct( velocity, info.m_SurfaceNormal ); // Cancel out downward velocity (allows for going up parallel walls) velocity -= vUp * DotProduct( velocity, vUp ); // Store this for later Vector velNorm = velocity; velNorm.NormalizeInPlace(); float flNormDot = DotProduct( vUp, info.m_SurfaceNormal ); float flBounceScale = 0.f; // Add upward velocity if surface normal is facing up, relative to the player if( flNormDot > -0.1f ) { // Extra upward wall bounce velocity flBounceScale = (1.f - DotProduct( vUp, info.m_SurfaceNormal )); vBounceVel += vUp * bounce_paint_wall_jump_upward_speed.GetFloat() * flBounceScale; } else // Downward facing wall. Add velocity in the XY plane { // Vector pointing out of the surface in the XY plane Vector vOut = ( info.m_SurfaceNormal - (vUp * DotProduct( vUp, info.m_SurfaceNormal )) ).Normalized(); // Extra lateral velocity off the wall flBounceScale = DotProduct( vOut, info.m_SurfaceNormal ); vBounceVel += vOut * bounce_paint_wall_jump_upward_speed.GetFloat() * flBounceScale; } // Calculate how much bounce velocity is left to spend after the lateral float fWallBounceScale = flTrade + ( (1.f - flBounceScale) * (1.0 - flTrade) ); // Velocity off of the surface vBounceVel += info.m_SurfaceNormal * bounce_paint_min_speed.GetFloat() * fWallBounceScale; // If we're going to bounce straight up, add some random XY velocity. Bouncing straight up // doesn't look natural. if( vBounceVel.x == 0.f && vBounceVel.y == 0.f ) { vBounceVel += Vector( RandomFloat(-80.f, 80.f), RandomFloat(-80.f, 80.f), 0.f ); } // Dont let our velocity fight the new bounce velocity velocity -= vBounceVel.Normalized() * DotProduct( velocity, vBounceVel.Normalized() ); velocity += vBounceVel; // Add velocity to physics object pPhysObject->SetVelocity( &velocity, &angularVel ); } return DEACTIVATING_PAINT_POWER; } template< typename BasePropType > PaintPowerState PropPaintPowerUser::UseBouncePower( PaintPowerInfo_t& powerInfo ) { return DEACTIVATING_PAINT_POWER; } template< typename BasePropType > PaintPowerState PropPaintPowerUser::DeactivateBouncePower( PaintPowerInfo_t& powerInfo ) { return INACTIVE_PAINT_POWER; } template< typename BasePropType > int PropPaintPowerUser::GetSpeedMaterialIndex() { static int s_SpeedMaterialIndex = physprops->GetSurfaceIndex( "ice" ); return s_SpeedMaterialIndex; } template< typename BasePropType > void PropPaintPowerUser::Paint( PaintPowerType type, const Vector& worldContactPt ) { BaseClass::Paint( type, worldContactPt ); IPhysicsObject* pPhysicsObject = this->VPhysicsGetObject(); if( pPhysicsObject != NULL && pPhysicsObject->IsAsleep() ) { pPhysicsObject->Wake(); } } #endif // ifndef PROP_PAINT_POWER_USER_H