//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// // // Purpose: Shared variables, etc. for the paint gun. // //=============================================================================// #include "cbase.h" #include "paint_color_manager.h" #include "shot_manipulator.h" #include "in_buttons.h" #include "debugoverlay_shared.h" #include "weapon_paintgun_shared.h" #include "paint_sprayer_shared.h" #include "paint_stream_shared.h" #ifdef CLIENT_DLL #include "c_weapon_paintgun.h" #include "c_portal_player.h" #include "igameevents.h" #else #include "weapon_paintgun.h" #include "portal_player.h" #include "paint_database.h" #include "portal_base2d.h" #include "prop_portal_shared.h" #include "env_speaker.h" #include "rumble_shared.h" #include "paint_database.h" //ConVar sv_paint_erase_range("sv_paint_erase_range", "2000", FCVAR_CHEAT); //ConVar sv_num_erase_ray("sv_num_erase_ray", "10", FCVAR_CHEAT, "number of ray that shoots out per shot"); //ConVar sv_debug_suck_erase("sv_debug_suck_erase", "0", FCVAR_CHEAT); extern void Paint( CBaseEntity* pEntity, const Vector& pos, uint8 colorIndex, int nPainted ); extern CPaintDatabase PaintDatabase; #endif #define paintgun_blobs_spread_radius 0.f //ConVar paintgun_blobs_spread_radius( "paintgun_blobs_spread_radius", "0.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "The starting radius of the spread of the paint blobs from the gun" ); #define paintgun_blobs_spread_angle 10.f //ConVar paintgun_blobs_spread_angle( "paintgun_blobs_spread_angle", "10.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "The spread (in degrees) of the paint blobs from the gun" ); #define paintgun_blobs_per_second 40.f //ConVar paintgun_blobs_per_second( "paintgun_blobs_per_second", "40.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "Number of blobs shot out of the paint gun per second" ); #define paintgun_blobs_min_speed 950.f //ConVar paintgun_blobs_min_speed( "paintgun_blobs_min_speed", "950.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "The min speed of the blobs shot out of the paint gun" ); #define paintgun_blobs_max_speed 1050.f //ConVar paintgun_blobs_max_speed( "paintgun_blobs_max_speed", "1050.0f", FCVAR_REPLICATED | FCVAR_CHEAT, "The max speed of the blobs shot out of the paint gun" ); #define paintgun_shoot_position_trace_for_wall 1 //ConVar paintgun_shoot_position_trace_for_wall( "paintgun_shoot_position_trace_for_wall", "1", FCVAR_REPLICATED, "If the paint gun shooting position should test if it is inside a wall" ); #define paintgun_blobs_streak_percent 10.f //ConVar paintgun_blobs_streak_percent( "paintgun_blobs_streak_percent", "10.0f", FCVAR_REPLICATED | FCVAR_CHEAT ); #define paintgun_blobs_min_streak_time 0.1f //ConVar paintgun_blobs_min_streak_time( "paintgun_blobs_min_streak_time", "0.1f", FCVAR_REPLICATED | FCVAR_CHEAT ); #define paintgun_blobs_max_streak_time 0.5f //ConVar paintgun_blobs_max_streak_time( "paintgun_blobs_max_streak_time", "0.5f", FCVAR_REPLICATED | FCVAR_CHEAT ); #define paintgun_blobs_min_streak_speed_dampen 4500.f //ConVar paintgun_blobs_min_streak_speed_dampen( "paintgun_blobs_min_streak_speed_dampen", "4500.0f", FCVAR_REPLICATED | FCVAR_CHEAT ); #define paintgun_blobs_max_streak_speed_dampen 5500.f //ConVar paintgun_blobs_max_streak_speed_dampen( "paintgun_blobs_max_streak_speed_dampen", "5500.0f", FCVAR_REPLICATED | FCVAR_CHEAT ); #define paintgun_max_ammo 60 //ConVar paintgun_max_ammo( "paintgun_max_ammo", "60", FCVAR_REPLICATED, "The maximum amount of paint ammo allowed." ); #define paintgun_ammo_type 0 //ConVar paintgun_ammo_type( "paintgun_ammo_type", "0", FCVAR_REPLICATED, "Type of paint ammo. 0: No ammo, 1: Global ammo per-gun, 2: Ammo per-paint type" ); acttable_t CWeaponPaintGun::m_acttable[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_PRIMARY, false }, { ACT_MP_RUN, ACT_MP_RUN_PRIMARY, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_PRIMARY, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_PRIMARY, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_PRIMARY, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_PRIMARY, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_PRIMARY, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_PRIMARY, false }, { ACT_MP_RUN_SPEEDPAINT, ACT_MP_RUN_SPEEDPAINT_PRIMARY, false }, { ACT_MP_DROWNING_PRIMARY, ACT_MP_DROWNING_PRIMARY, false }, { ACT_MP_LONG_FALL, ACT_MP_LONG_FALL_PRIMARY, false }, { ACT_MP_TRACTORBEAM_FLOAT, ACT_MP_TRACTORBEAM_FLOAT_PRIMARY, false }, { ACT_MP_DEATH_CRUSH, ACT_MP_DEATH_CRUSH_PRIMARY, false }, }; IMPLEMENT_ACTTABLE(CWeaponPaintGun); void CWeaponPaintGun::ItemPostFrame() { bool bWasFiringPaint = m_bFiringPaint; bool bWasFiringErase = m_bFiringErase; // Only the player fires this way so we can cast CPortal_Player *pPlayer = ToPortalPlayer( GetOwner() ); if ( pPlayer == NULL ) return; // The paint clearing secondary function can always be used if( paintgun_ammo_type != PAINT_AMMO_NONE && (pPlayer->m_nButtons & IN_ATTACK2) != 0 ) { // Attack! SecondaryAttack(); } else if( pPlayer->GetUseEntity() == NULL ) { BaseClass::ItemPostFrame(); } // Was shooting neither and is now shooting either if( !bWasFiringPaint && !bWasFiringErase && ( m_bFiringPaint || m_bFiringErase ) ) { #if !defined (CLIENT_DLL) StartShootingSound(); #else pPlayer->SetAnimation( PLAYER_ATTACK1 ); #endif } // Was shooting either and now is shooting neither else if( ( bWasFiringPaint || bWasFiringErase ) && ( !m_bFiringPaint && !m_bFiringErase ) ) { #if !defined (CLIENT_DLL) StopShootingSound(); #else #endif } } void CWeaponPaintGun::PrimaryAttack() { bool bHasSelectedColor = false; #if !defined (CLIENT_DLL) bHasSelectedColor = HasPaintPower( (PaintPowerType)m_nCurrentColor.Get() ); #else // CLIENT_DLL bHasSelectedColor = HasPaintPower( (PaintPowerType)m_nCurrentColor ); #endif // Don't shoot if we dont have the selected color or any color at all if( !HasAnyPaintPower() || !bHasSelectedColor || !HasPaintAmmo( m_nCurrentColor ) ) { m_bFiringPaint = m_bFiringErase = false; return; } #if !defined (CLIENT_DLL) SprayPaint( gpGlobals->frametime, m_nCurrentColor ); if( !m_bFiringPaint ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_painted" ); if ( event ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); assert( pPlayer ); event->SetInt("userid", pPlayer->GetUserID() ); gameeventmanager->FireEvent( event ); } } #else // CLIENT_DLL StartHoseEffect(); //SprayPaint( gpGlobals->frametime, static_cast( m_nCurrentColor ) ); #endif //CLIENT_DLL m_bFiringPaint = true; m_bFiringErase = false; } void CWeaponPaintGun::SecondaryAttack() { if( paintgun_ammo_type == PAINT_AMMO_NONE ) { # ifdef CLIENT_DLL StartHoseEffect(); # else if( !m_bFiringErase ) { IGameEvent *event = gameeventmanager->CreateEvent( "player_erased" ); if ( event ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); assert( pPlayer ); event->SetInt("userid", pPlayer->GetUserID() ); gameeventmanager->FireEvent( event ); } } m_bFiringPaint = false; m_bFiringErase = true; SprayPaint( gpGlobals->frametime, NO_POWER ); # endif } else { ResetAmmo(); #ifdef GAME_DLL PaintDatabase.RemoveAllPaint(); #endif } } bool CWeaponPaintGun::HasPaintPower( PaintPowerType nIndex ) { return m_bHasPaint[nIndex]; } bool CWeaponPaintGun::HasAnyPaintPower() { for( int i = 0; i < PAINT_POWER_TYPE_COUNT; ++i ) { if( HasPaintPower( (PaintPowerType)i ) ) { return true; } } return false; } void CWeaponPaintGun::WeaponIdle() { #ifdef CLIENT_DLL StopHoseEffect(); #else if( m_bFiringPaint || m_bFiringErase ) { StopShootingSound(); } #endif m_bFiringPaint = m_bFiringErase = false; m_flAccumulatedTime = 1.0f/paintgun_blobs_per_second; #ifdef CLIENT_DLL #endif m_nBlobRandomSeed = 0; BaseClass::WeaponIdle(); } bool CWeaponPaintGun::Holster( CBaseCombatWeapon *pSwitchingTo ) { m_bFiringPaint = m_bFiringErase = false; #ifdef CLIENT_DLL ChangeRenderColor(); StopHoseEffect(); #else StopShootingSound(); IGameEvent *event = gameeventmanager->CreateEvent( "holstered_paintgun" ); if ( event ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if( pPlayer ) { event->SetInt("userid", pPlayer->GetUserID() ); gameeventmanager->FireEvent( event ); } } #endif return BaseClass::Holster( pSwitchingTo ); } void CWeaponPaintGun::Drop( const Vector &vecVelocity ) { m_bFiringPaint = m_bFiringErase = false; Color color = MapPowerToVisualColor( m_nCurrentColor ); if ( !HasAnyPaintPower() ) color = MapPowerToVisualColor( NO_POWER ); #ifdef CLIENT_DLL StopHoseEffect(); #else StopShootingSound(); IGameEvent *event = gameeventmanager->CreateEvent( "dropped_paintgun" ); if ( event ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if( pPlayer ) { event->SetInt("userid", pPlayer->GetUserID() ); gameeventmanager->FireEvent( event ); } } #endif SetRenderColor( color.r(), color.g(), color.b() ); BaseClass::Drop( vecVelocity ); } bool CWeaponPaintGun::Deploy() { #ifdef CLIENT_DLL ChangeRenderColor(); #endif #ifndef CLIENT_DLL IGameEvent *event = gameeventmanager->CreateEvent( "deployed_paintgun" ); if ( event ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if( pPlayer ) { event->SetInt("userid", pPlayer->GetUserID() ); event->SetInt("paintcount", GetPaintCount() ); gameeventmanager->FireEvent( event ); } } #endif //Only on server return BaseClass::Deploy(); } void CWeaponPaintGun::SetSubType( int iType ) { m_iSubType = iType; m_nCurrentColor = iType; #ifdef CLIENT_DLL ChangeRenderColor(); #else EmitSound( "Player.WeaponSelected" ); #endif BaseClass::SetSubType( iType ); } PaintPowerType CWeaponPaintGun::GetCurrentPaint() { #ifdef CLIENT_DLL return (PaintPowerType)( m_nCurrentColor ); #else //!CLIENT_DLL return (PaintPowerType)( m_nCurrentColor.Get() ); #endif } //Paint Ammo! bool CWeaponPaintGun::HasPaintAmmo( unsigned paintType ) const { switch( paintgun_ammo_type ) { case PAINT_AMMO_NONE: return true; case PAINT_AMMO_GLOBAL: return m_nPaintAmmo > 0; case PAINT_AMMO_PER_TYPE: Assert( paintType < PAINT_POWER_TYPE_COUNT ); return m_PaintAmmoPerType[MIN( paintType, PAINT_POWER_TYPE_COUNT )] > 0; default: return true; } } void CWeaponPaintGun::DecrementPaintAmmo( unsigned paintType ) { switch( paintgun_ammo_type ) { case PAINT_AMMO_GLOBAL: --m_nPaintAmmo; break; case PAINT_AMMO_PER_TYPE: const int index = MIN( paintType, PAINT_POWER_TYPE_COUNT ); m_PaintAmmoPerType.Set( index, m_PaintAmmoPerType[index] - 1 ); break; } } void CWeaponPaintGun::ResetAmmo() { m_nPaintAmmo = paintgun_max_ammo; const int maxAmmo = paintgun_max_ammo; for( int i = 0; i < PAINT_POWER_TYPE_COUNT; ++i ) { m_PaintAmmoPerType.Set( i, maxAmmo ); } } void CWeaponPaintGun::SprayPaint( float flDeltaTime, int paintType ) { if( flDeltaTime <= 0.0f ) { return; } CPortal_Player *pOwner = ToPortalPlayer( GetOwner() ); if ( pOwner == NULL ) return; CPaintStream *pPaintStream = assert_cast< CPaintStream* >( m_hPaintStream.Get( paintType ).Get() ); if ( !pPaintStream ) return; m_flAccumulatedTime += flDeltaTime; Vector vecEyePosition = pOwner->EyePosition(); Vector vecVelocity = pOwner->GetAbsVelocity(); Vector vecAimDir = pOwner->GetAutoaimVector( 0 ); Vector vecForwardVelocity = vecVelocity.Normalized() * DotProduct( vecVelocity, vecAimDir ); Vector vecBlobFirePos = pOwner->GetPaintGunShootPosition(); if( paintgun_shoot_position_trace_for_wall ) { // Because the muzzle is so long, it can stick through a wall if the player is right up against it. // Make sure to adjust the shoot position in this condition by tracing a line between the eye point and the end of the muzzle. trace_t trace; Ray_t muzzleRay; muzzleRay.Init( vecEyePosition, vecBlobFirePos ); CTraceFilterSimple traceFilter( pOwner, COLLISION_GROUP_NONE ); UTIL_TraceRay( muzzleRay, MASK_SOLID, &traceFilter, &trace ); //Check if there is a portal between the player's eye and the muzzle of the paint gun CPortal_Base2D *pInPortal = NULL; CPortal_Base2D *pOutPortal = NULL; if( UTIL_DidTraceTouchPortals( muzzleRay, trace, &pInPortal, &pOutPortal ) ) { Vector vecPortalForward; AngleVectors( pInPortal->GetAbsAngles(), &vecPortalForward ); Vector vecTraceDir = vecBlobFirePos - vecEyePosition; vecTraceDir.NormalizeInPlace(); if( DotProduct( vecPortalForward, vecTraceDir ) < 0 ) { UTIL_Portal_PointTransform( pInPortal->MatrixThisToLinked(), trace.endpos, vecBlobFirePos ); UTIL_Portal_VectorTransform( pInPortal->MatrixThisToLinked(), vecAimDir, vecAimDir ); } } else if ( trace.fraction < 1.0 && ( !trace.m_pEnt || trace.m_pEnt->m_takedamage == DAMAGE_NO ) ) { // there is something between the eye and the end of the muzzle, most likely a wall // Move the muzzle position to the end position of the trace so that the wall gets painted vecBlobFirePos = trace.endpos; } //vecBlobFirePos = trace.endpos; } const float flBlobPerSecond = 1.0f/paintgun_blobs_per_second; while ( m_flAccumulatedTime >= flBlobPerSecond && HasPaintAmmo( paintType ) ) { m_flAccumulatedTime -= flBlobPerSecond; CPaintBlob *pBlob = FirePaintBlob( vecBlobFirePos, vecBlobFirePos, vecForwardVelocity, vecAimDir, paintType, paintgun_blobs_spread_radius, paintgun_blobs_spread_angle, paintgun_blobs_min_speed, paintgun_blobs_max_speed, paintgun_blobs_streak_percent, paintgun_blobs_min_streak_time, paintgun_blobs_max_streak_time, paintgun_blobs_min_streak_speed_dampen, paintgun_blobs_max_streak_speed_dampen, false, false, pPaintStream, m_nBlobRandomSeed ); pPaintStream->AddPaintBlob( pBlob ); ++m_nBlobRandomSeed; DecrementPaintAmmo( paintType ); } }