diff options
Diffstat (limited to 'game/shared/hl2mp')
31 files changed, 15727 insertions, 0 deletions
diff --git a/game/shared/hl2mp/hl2mp_gamerules.cpp b/game/shared/hl2mp/hl2mp_gamerules.cpp new file mode 100644 index 0000000..604e409 --- /dev/null +++ b/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -0,0 +1,1280 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "hl2mp_gamerules.h" +#include "viewport_panel_names.h" +#include "gameeventdefs.h" +#include <KeyValues.h> +#include "ammodef.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + + #include "eventqueue.h" + #include "player.h" + #include "gamerules.h" + #include "game.h" + #include "items.h" + #include "entitylist.h" + #include "mapentities.h" + #include "in_buttons.h" + #include <ctype.h> + #include "voice_gamemgr.h" + #include "iscorer.h" + #include "hl2mp_player.h" + #include "weapon_hl2mpbasehlmpcombatweapon.h" + #include "team.h" + #include "voice_gamemgr.h" + #include "hl2mp_gameinterface.h" + #include "hl2mp_cvars.h" + +#ifdef DEBUG + #include "hl2mp_bot_temp.h" +#endif + +extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); + +extern bool FindInList( const char **pStrings, const char *pToFind ); + +ConVar sv_hl2mp_weapon_respawn_time( "sv_hl2mp_weapon_respawn_time", "20", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +ConVar sv_hl2mp_item_respawn_time( "sv_hl2mp_item_respawn_time", "30", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +ConVar sv_report_client_settings("sv_report_client_settings", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); + +extern ConVar mp_chattime; + +extern CBaseEntity *g_pLastCombineSpawn; +extern CBaseEntity *g_pLastRebelSpawn; + +#define WEAPON_MAX_DISTANCE_FROM_SPAWN 64 + +#endif + + +REGISTER_GAMERULES_CLASS( CHL2MPRules ); + +BEGIN_NETWORK_TABLE_NOBASE( CHL2MPRules, DT_HL2MPRules ) + + #ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bTeamPlayEnabled ) ), + #else + SendPropBool( SENDINFO( m_bTeamPlayEnabled ) ), + #endif + +END_NETWORK_TABLE() + + +LINK_ENTITY_TO_CLASS( hl2mp_gamerules, CHL2MPGameRulesProxy ); +IMPLEMENT_NETWORKCLASS_ALIASED( HL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) + +static HL2MPViewVectors g_HL2MPViewVectors( + Vector( 0, 0, 64 ), //VEC_VIEW (m_vView) + + Vector(-16, -16, 0 ), //VEC_HULL_MIN (m_vHullMin) + Vector( 16, 16, 72 ), //VEC_HULL_MAX (m_vHullMax) + + Vector(-16, -16, 0 ), //VEC_DUCK_HULL_MIN (m_vDuckHullMin) + Vector( 16, 16, 36 ), //VEC_DUCK_HULL_MAX (m_vDuckHullMax) + Vector( 0, 0, 28 ), //VEC_DUCK_VIEW (m_vDuckView) + + Vector(-10, -10, -10 ), //VEC_OBS_HULL_MIN (m_vObsHullMin) + Vector( 10, 10, 10 ), //VEC_OBS_HULL_MAX (m_vObsHullMax) + + Vector( 0, 0, 14 ), //VEC_DEAD_VIEWHEIGHT (m_vDeadViewHeight) + + Vector(-16, -16, 0 ), //VEC_CROUCH_TRACE_MIN (m_vCrouchTraceMin) + Vector( 16, 16, 60 ) //VEC_CROUCH_TRACE_MAX (m_vCrouchTraceMax) +); + +static const char *s_PreserveEnts[] = +{ + "ai_network", + "ai_hint", + "hl2mp_gamerules", + "team_manager", + "player_manager", + "env_soundscape", + "env_soundscape_proxy", + "env_soundscape_triggerable", + "env_sun", + "env_wind", + "env_fog_controller", + "func_brush", + "func_wall", + "func_buyzone", + "func_illusionary", + "infodecal", + "info_projecteddecal", + "info_node", + "info_target", + "info_node_hint", + "info_player_deathmatch", + "info_player_combine", + "info_player_rebel", + "info_map_parameters", + "keyframe_rope", + "move_rope", + "info_ladder", + "player", + "point_viewcontrol", + "scene_manager", + "shadow_control", + "sky_camera", + "soundent", + "trigger_soundscape", + "viewmodel", + "predicted_viewmodel", + "worldspawn", + "point_devshot_camera", + "", // END Marker +}; + + + +#ifdef CLIENT_DLL + void RecvProxy_HL2MPRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) + { + CHL2MPRules *pRules = HL2MPRules(); + Assert( pRules ); + *pOut = pRules; + } + + BEGIN_RECV_TABLE( CHL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) + RecvPropDataTable( "hl2mp_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_HL2MPRules ), RecvProxy_HL2MPRules ) + END_RECV_TABLE() +#else + void* SendProxy_HL2MPRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID ) + { + CHL2MPRules *pRules = HL2MPRules(); + Assert( pRules ); + return pRules; + } + + BEGIN_SEND_TABLE( CHL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) + SendPropDataTable( "hl2mp_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_HL2MPRules ), SendProxy_HL2MPRules ) + END_SEND_TABLE() +#endif + +#ifndef CLIENT_DLL + + class CVoiceGameMgrHelper : public IVoiceGameMgrHelper + { + public: + virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity ) + { + return ( pListener->GetTeamNumber() == pTalker->GetTeamNumber() ); + } + }; + CVoiceGameMgrHelper g_VoiceGameMgrHelper; + IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper; + +#endif + +// NOTE: the indices here must match TEAM_TERRORIST, TEAM_CT, TEAM_SPECTATOR, etc. +char *sTeamNames[] = +{ + "Unassigned", + "Spectator", + "Combine", + "Rebels", +}; + +CHL2MPRules::CHL2MPRules() +{ +#ifndef CLIENT_DLL + // Create the team managers + for ( int i = 0; i < ARRAYSIZE( sTeamNames ); i++ ) + { + CTeam *pTeam = static_cast<CTeam*>(CreateEntityByName( "team_manager" )); + pTeam->Init( sTeamNames[i], i ); + + g_Teams.AddToTail( pTeam ); + } + + m_bTeamPlayEnabled = teamplay.GetBool(); + m_flIntermissionEndTime = 0.0f; + m_flGameStartTime = 0; + + m_hRespawnableItemsAndWeapons.RemoveAll(); + m_tmNextPeriodicThink = 0; + m_flRestartGameTime = 0; + m_bCompleteReset = false; + m_bHeardAllPlayersReady = false; + m_bAwaitingReadyRestart = false; + m_bChangelevelDone = false; + +#endif +} + +const CViewVectors* CHL2MPRules::GetViewVectors()const +{ + return &g_HL2MPViewVectors; +} + +const HL2MPViewVectors* CHL2MPRules::GetHL2MPViewVectors()const +{ + return &g_HL2MPViewVectors; +} + +CHL2MPRules::~CHL2MPRules( void ) +{ +#ifndef CLIENT_DLL + // Note, don't delete each team since they are in the gEntList and will + // automatically be deleted from there, instead. + g_Teams.Purge(); +#endif +} + +void CHL2MPRules::CreateStandardEntities( void ) +{ + +#ifndef CLIENT_DLL + // Create the entity that will send our data to the client. + + BaseClass::CreateStandardEntities(); + + g_pLastCombineSpawn = NULL; + g_pLastRebelSpawn = NULL; + +#ifdef DBGFLAG_ASSERT + CBaseEntity *pEnt = +#endif + CBaseEntity::Create( "hl2mp_gamerules", vec3_origin, vec3_angle ); + Assert( pEnt ); +#endif +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHL2MPRules::FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + if ( weaponstay.GetInt() > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return 0; // weapon respawns almost instantly + } + } + + return sv_hl2mp_weapon_respawn_time.GetFloat(); +#endif + + return 0; // weapon respawns almost instantly +} + + +bool CHL2MPRules::IsIntermission( void ) +{ +#ifndef CLIENT_DLL + return m_flIntermissionEndTime > gpGlobals->curtime; +#endif + + return false; +} + +void CHL2MPRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) +{ +#ifndef CLIENT_DLL + if ( IsIntermission() ) + return; + BaseClass::PlayerKilled( pVictim, info ); +#endif +} + + +void CHL2MPRules::Think( void ) +{ + +#ifndef CLIENT_DLL + + CGameRules::Think(); + + if ( g_fGameOver ) // someone else quit the game already + { + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->curtime ) + { + if ( !m_bChangelevelDone ) + { + ChangeLevel(); // intermission is over + m_bChangelevelDone = true; + } + } + + return; + } + +// float flTimeLimit = mp_timelimit.GetFloat() * 60; + float flFragLimit = fraglimit.GetFloat(); + + if ( GetMapRemainingTime() < 0 ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + if( IsTeamplay() == true ) + { + CTeam *pCombine = g_Teams[TEAM_COMBINE]; + CTeam *pRebels = g_Teams[TEAM_REBELS]; + + if ( pCombine->GetScore() >= flFragLimit || pRebels->GetScore() >= flFragLimit ) + { + GoToIntermission(); + return; + } + } + else + { + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->FragCount() >= flFragLimit ) + { + GoToIntermission(); + return; + } + } + } + } + + if ( gpGlobals->curtime > m_tmNextPeriodicThink ) + { + CheckAllPlayersReady(); + CheckRestartGame(); + m_tmNextPeriodicThink = gpGlobals->curtime + 1.0; + } + + if ( m_flRestartGameTime > 0.0f && m_flRestartGameTime <= gpGlobals->curtime ) + { + RestartGame(); + } + + if( m_bAwaitingReadyRestart && m_bHeardAllPlayersReady ) + { + UTIL_ClientPrintAll( HUD_PRINTCENTER, "All players ready. Game will restart in 5 seconds" ); + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "All players ready. Game will restart in 5 seconds" ); + + m_flRestartGameTime = gpGlobals->curtime + 5; + m_bAwaitingReadyRestart = false; + } + + ManageObjectRelocation(); + +#endif +} + +void CHL2MPRules::GoToIntermission( void ) +{ +#ifndef CLIENT_DLL + if ( g_fGameOver ) + return; + + g_fGameOver = true; + + m_flIntermissionEndTime = gpGlobals->curtime + mp_chattime.GetInt(); + + for ( int i = 0; i < MAX_PLAYERS; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + pPlayer->ShowViewPortPanel( PANEL_SCOREBOARD ); + pPlayer->AddFlag( FL_FROZEN ); + } +#endif + +} + +bool CHL2MPRules::CheckGameOver() +{ +#ifndef CLIENT_DLL + if ( g_fGameOver ) // someone else quit the game already + { + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->curtime ) + { + ChangeLevel(); // intermission is over + } + + return true; + } +#endif + + return false; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHL2MPRules::FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + if ( pWeapon && (pWeapon->GetWeaponFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( gEntList.NumberOfEntities() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } +#endif + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHL2MPRules::VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + CWeaponHL2MPBase *pHL2Weapon = dynamic_cast< CWeaponHL2MPBase*>( pWeapon ); + + if ( pHL2Weapon ) + { + return pHL2Weapon->GetOriginalSpawnOrigin(); + } +#endif + + return pWeapon->GetAbsOrigin(); +} + +#ifndef CLIENT_DLL + +CItem* IsManagedObjectAnItem( CBaseEntity *pObject ) +{ + return dynamic_cast< CItem*>( pObject ); +} + +CWeaponHL2MPBase* IsManagedObjectAWeapon( CBaseEntity *pObject ) +{ + return dynamic_cast< CWeaponHL2MPBase*>( pObject ); +} + +bool GetObjectsOriginalParameters( CBaseEntity *pObject, Vector &vOriginalOrigin, QAngle &vOriginalAngles ) +{ + if ( CItem *pItem = IsManagedObjectAnItem( pObject ) ) + { + if ( pItem->m_flNextResetCheckTime > gpGlobals->curtime ) + return false; + + vOriginalOrigin = pItem->GetOriginalSpawnOrigin(); + vOriginalAngles = pItem->GetOriginalSpawnAngles(); + + pItem->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_item_respawn_time.GetFloat(); + return true; + } + else if ( CWeaponHL2MPBase *pWeapon = IsManagedObjectAWeapon( pObject )) + { + if ( pWeapon->m_flNextResetCheckTime > gpGlobals->curtime ) + return false; + + vOriginalOrigin = pWeapon->GetOriginalSpawnOrigin(); + vOriginalAngles = pWeapon->GetOriginalSpawnAngles(); + + pWeapon->m_flNextResetCheckTime = gpGlobals->curtime + sv_hl2mp_weapon_respawn_time.GetFloat(); + return true; + } + + return false; +} + +void CHL2MPRules::ManageObjectRelocation( void ) +{ + int iTotal = m_hRespawnableItemsAndWeapons.Count(); + + if ( iTotal > 0 ) + { + for ( int i = 0; i < iTotal; i++ ) + { + CBaseEntity *pObject = m_hRespawnableItemsAndWeapons[i].Get(); + + if ( pObject ) + { + Vector vSpawOrigin; + QAngle vSpawnAngles; + + if ( GetObjectsOriginalParameters( pObject, vSpawOrigin, vSpawnAngles ) == true ) + { + float flDistanceFromSpawn = (pObject->GetAbsOrigin() - vSpawOrigin ).Length(); + + if ( flDistanceFromSpawn > WEAPON_MAX_DISTANCE_FROM_SPAWN ) + { + bool shouldReset = false; + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + + if ( pPhysics ) + { + shouldReset = pPhysics->IsAsleep(); + } + else + { + shouldReset = (pObject->GetFlags() & FL_ONGROUND) ? true : false; + } + + if ( shouldReset ) + { + pObject->Teleport( &vSpawOrigin, &vSpawnAngles, NULL ); + pObject->EmitSound( "AlyxEmp.Charge" ); + + IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); + + if ( pPhys ) + { + pPhys->Wake(); + } + } + } + } + } + } + } +} + +//========================================================= +//AddLevelDesignerPlacedWeapon +//========================================================= +void CHL2MPRules::AddLevelDesignerPlacedObject( CBaseEntity *pEntity ) +{ + if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) == -1 ) + { + m_hRespawnableItemsAndWeapons.AddToTail( pEntity ); + } +} + +//========================================================= +//RemoveLevelDesignerPlacedWeapon +//========================================================= +void CHL2MPRules::RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ) +{ + if ( m_hRespawnableItemsAndWeapons.Find( pEntity ) != -1 ) + { + m_hRespawnableItemsAndWeapons.FindAndRemove( pEntity ); + } +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHL2MPRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->GetOriginalSpawnOrigin(); +} + +//========================================================= +// What angles should this item use to respawn? +//========================================================= +QAngle CHL2MPRules::VecItemRespawnAngles( CItem *pItem ) +{ + return pItem->GetOriginalSpawnAngles(); +} + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHL2MPRules::FlItemRespawnTime( CItem *pItem ) +{ + return sv_hl2mp_item_respawn_time.GetFloat(); +} + + +//========================================================= +// CanHaveWeapon - returns false if the player is not allowed +// to pick up this weapon +//========================================================= +bool CHL2MPRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ) +{ + if ( weaponstay.GetInt() > 0 ) + { + if ( pPlayer->Weapon_OwnsThisType( pItem->GetClassname(), pItem->GetSubType() ) ) + return false; + } + + return BaseClass::CanHavePlayerItem( pPlayer, pItem ); +} + +#endif + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHL2MPRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) +{ +#ifndef CLIENT_DLL + if ( pWeapon->HasSpawnFlags( SF_NORESPAWN ) ) + { + return GR_WEAPON_RESPAWN_NO; + } +#endif + + return GR_WEAPON_RESPAWN_YES; +} + +//----------------------------------------------------------------------------- +// Purpose: Player has just left the game +//----------------------------------------------------------------------------- +void CHL2MPRules::ClientDisconnected( edict_t *pClient ) +{ +#ifndef CLIENT_DLL + // Msg( "CLIENT DISCONNECTED, REMOVING FROM TEAM.\n" ); + + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + if ( pPlayer ) + { + // Remove the player from his team + if ( pPlayer->GetTeam() ) + { + pPlayer->GetTeam()->RemovePlayer( pPlayer ); + } + } + + BaseClass::ClientDisconnected( pClient ); + +#endif +} + + +//========================================================= +// Deathnotice. +//========================================================= +void CHL2MPRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) +{ +#ifndef CLIENT_DLL + // Work out what killed the player, and send a message to all clients about it + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_ID = 0; + + // Find the killer & the scorer + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor ); + + // Custom kill type? + if ( info.GetDamageCustom() ) + { + killer_weapon_name = GetDamageCustomString( info ); + if ( pScorer ) + { + killer_ID = pScorer->GetUserID(); + } + } + else + { + // Is the killer a client? + if ( pScorer ) + { + killer_ID = pScorer->GetUserID(); + + if ( pInflictor ) + { + if ( pInflictor == pScorer ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + if ( pScorer->GetActiveWeapon() ) + { + killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname(); + } + } + else + { + killer_weapon_name = pInflictor->GetClassname(); // it's just that easy + } + } + } + else + { + killer_weapon_name = pInflictor->GetClassname(); + } + + // strip the NPC_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + { + killer_weapon_name += 7; + } + else if ( strncmp( killer_weapon_name, "npc_", 4 ) == 0 ) + { + killer_weapon_name += 4; + } + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + { + killer_weapon_name += 5; + } + else if ( strstr( killer_weapon_name, "physics" ) ) + { + killer_weapon_name = "physics"; + } + + if ( strcmp( killer_weapon_name, "prop_combine_ball" ) == 0 ) + { + killer_weapon_name = "combine_ball"; + } + else if ( strcmp( killer_weapon_name, "grenade_ar2" ) == 0 ) + { + killer_weapon_name = "smg1_grenade"; + } + else if ( strcmp( killer_weapon_name, "satchel" ) == 0 || strcmp( killer_weapon_name, "tripmine" ) == 0) + { + killer_weapon_name = "slam"; + } + + + } + + IGameEvent *event = gameeventmanager->CreateEvent( "player_death" ); + if( event ) + { + event->SetInt("userid", pVictim->GetUserID() ); + event->SetInt("attacker", killer_ID ); + event->SetString("weapon", killer_weapon_name ); + event->SetInt( "priority", 7 ); + gameeventmanager->FireEvent( event ); + } +#endif + +} + +void CHL2MPRules::ClientSettingsChanged( CBasePlayer *pPlayer ) +{ +#ifndef CLIENT_DLL + + CHL2MP_Player *pHL2Player = ToHL2MPPlayer( pPlayer ); + + if ( pHL2Player == NULL ) + return; + + const char *pCurrentModel = modelinfo->GetModelName( pPlayer->GetModel() ); + const char *szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_playermodel" ); + + //If we're different. + if ( stricmp( szModelName, pCurrentModel ) ) + { + //Too soon, set the cvar back to what it was. + //Note: this will make this function be called again + //but since our models will match it'll just skip this whole dealio. + if ( pHL2Player->GetNextModelChangeTime() >= gpGlobals->curtime ) + { + char szReturnString[512]; + + Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pCurrentModel ); + engine->ClientCommand ( pHL2Player->edict(), szReturnString ); + + Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch.\n", (int)(pHL2Player->GetNextModelChangeTime() - gpGlobals->curtime) ); + ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); + return; + } + + if ( HL2MPRules()->IsTeamplay() == false ) + { + pHL2Player->SetPlayerModel(); + + const char *pszCurrentModelName = modelinfo->GetModelName( pHL2Player->GetModel() ); + + char szReturnString[128]; + Q_snprintf( szReturnString, sizeof( szReturnString ), "Your player model is: %s\n", pszCurrentModelName ); + + ClientPrint( pHL2Player, HUD_PRINTTALK, szReturnString ); + } + else + { + if ( Q_stristr( szModelName, "models/human") ) + { + pHL2Player->ChangeTeam( TEAM_REBELS ); + } + else + { + pHL2Player->ChangeTeam( TEAM_COMBINE ); + } + } + } + if ( sv_report_client_settings.GetInt() == 1 ) + { + UTIL_LogPrintf( "\"%s\" cl_cmdrate = \"%s\"\n", pHL2Player->GetPlayerName(), engine->GetClientConVarValue( pHL2Player->entindex(), "cl_cmdrate" )); + } + + BaseClass::ClientSettingsChanged( pPlayer ); +#endif + +} + +int CHL2MPRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ +#ifndef CLIENT_DLL + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() || IsTeamplay() == false ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } +#endif + + return GR_NOTTEAMMATE; +} + +const char *CHL2MPRules::GetGameDescription( void ) +{ + if ( IsTeamplay() ) + return "Team Deathmatch"; + + return "Deathmatch"; +} + +bool CHL2MPRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ) +{ + return true; +} + +float CHL2MPRules::GetMapRemainingTime() +{ + // if timelimit is disabled, return 0 + if ( mp_timelimit.GetInt() <= 0 ) + return 0; + + // timelimit is in minutes + + float timeleft = (m_flGameStartTime + mp_timelimit.GetInt() * 60.0f ) - gpGlobals->curtime; + + return timeleft; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPRules::Precache( void ) +{ + CBaseEntity::PrecacheScriptSound( "AlyxEmp.Charge" ); +} + +bool CHL2MPRules::ShouldCollide( int collisionGroup0, int collisionGroup1 ) +{ + if ( collisionGroup0 > collisionGroup1 ) + { + // swap so that lowest is always first + V_swap(collisionGroup0,collisionGroup1); + } + + if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) && + collisionGroup1 == COLLISION_GROUP_WEAPON ) + { + return false; + } + + return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 ); + +} + +bool CHL2MPRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) +{ +#ifndef CLIENT_DLL + if( BaseClass::ClientCommand( pEdict, args ) ) + return true; + + + CHL2MP_Player *pPlayer = (CHL2MP_Player *) pEdict; + + if ( pPlayer->ClientCommand( args ) ) + return true; +#endif + + return false; +} + +// shared ammo definition +// JAY: Trying to make a more physical bullet response +#define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) +#define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains)) + +// exaggerate all of the forces, but use real numbers to keep them consistent +#define BULLET_IMPULSE_EXAGGERATION 3.5 +// convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s +#define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION) + + +CAmmoDef *GetAmmoDef() +{ + static CAmmoDef def; + static bool bInitted = false; + + if ( !bInitted ) + { + bInitted = true; + + def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_IMPULSE(200, 1225), 0 ); + def.AddAmmoType("AR2AltFire", DMG_DISSOLVE, TRACER_NONE, 0, 0, 3, 0, 0 ); + def.AddAmmoType("Pistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 150, BULLET_IMPULSE(200, 1225), 0 ); + def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 225, BULLET_IMPULSE(200, 1225), 0 ); + def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 12, BULLET_IMPULSE(800, 5000), 0 ); + def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, 0, 0, 10, BULLET_IMPULSE(800, 8000), 0 ); + def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, 0, 0, 30, BULLET_IMPULSE(400, 1200), 0 ); + def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); + def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); + def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); + def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); + } + + return &def; +} + +#ifdef CLIENT_DLL + + ConVar cl_autowepswitch( + "cl_autowepswitch", + "1", + FCVAR_ARCHIVE | FCVAR_USERINFO, + "Automatically switch to picked up weapons (if more powerful)" ); + +#else + +#ifdef DEBUG + + // Handler for the "bot" command. + void Bot_f() + { + // Look at -count. + int count = 1; + count = clamp( count, 1, 16 ); + + int iTeam = TEAM_COMBINE; + + // Look at -frozen. + bool bFrozen = false; + + // Ok, spawn all the bots. + while ( --count >= 0 ) + { + BotPutInServer( bFrozen, iTeam ); + } + } + + + ConCommand cc_Bot( "bot", Bot_f, "Add a bot.", FCVAR_CHEAT ); + +#endif + + bool CHL2MPRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ) + { + if ( pPlayer->GetActiveWeapon() && pPlayer->IsNetClient() ) + { + // Player has an active item, so let's check cl_autowepswitch. + const char *cl_autowepswitch = engine->GetClientConVarValue( engine->IndexOfEdict( pPlayer->edict() ), "cl_autowepswitch" ); + if ( cl_autowepswitch && atoi( cl_autowepswitch ) <= 0 ) + { + return false; + } + } + + return BaseClass::FShouldSwitchWeapon( pPlayer, pWeapon ); + } + +#endif + +#ifndef CLIENT_DLL + +void CHL2MPRules::RestartGame() +{ + // bounds check + if ( mp_timelimit.GetInt() < 0 ) + { + mp_timelimit.SetValue( 0 ); + } + m_flGameStartTime = gpGlobals->curtime; + if ( !IsFinite( m_flGameStartTime.Get() ) ) + { + Warning( "Trying to set a NaN game start time\n" ); + m_flGameStartTime.GetForModify() = 0.0f; + } + + CleanUpMap(); + + // now respawn all players + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Holster(); + } + pPlayer->RemoveAllItems( true ); + respawn( pPlayer, false ); + pPlayer->Reset(); + } + + // Respawn entities (glass, doors, etc..) + + CTeam *pRebels = GetGlobalTeam( TEAM_REBELS ); + CTeam *pCombine = GetGlobalTeam( TEAM_COMBINE ); + + if ( pRebels ) + { + pRebels->SetScore( 0 ); + } + + if ( pCombine ) + { + pCombine->SetScore( 0 ); + } + + m_flIntermissionEndTime = 0; + m_flRestartGameTime = 0.0; + m_bCompleteReset = false; + + IGameEvent * event = gameeventmanager->CreateEvent( "round_start" ); + if ( event ) + { + event->SetInt("fraglimit", 0 ); + event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted + + event->SetString("objective","DEATHMATCH"); + + gameeventmanager->FireEvent( event ); + } +} + +void CHL2MPRules::CleanUpMap() +{ + // Recreate all the map entities from the map data (preserving their indices), + // then remove everything else except the players. + + // Get rid of all entities except players. + CBaseEntity *pCur = gEntList.FirstEnt(); + while ( pCur ) + { + CBaseHL2MPCombatWeapon *pWeapon = dynamic_cast< CBaseHL2MPCombatWeapon* >( pCur ); + // Weapons with owners don't want to be removed.. + if ( pWeapon ) + { + if ( !pWeapon->GetPlayerOwner() ) + { + UTIL_Remove( pCur ); + } + } + // remove entities that has to be restored on roundrestart (breakables etc) + else if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) + { + UTIL_Remove( pCur ); + } + + pCur = gEntList.NextEnt( pCur ); + } + + // Really remove the entities so we can have access to their slots below. + gEntList.CleanupDeleteList(); + + // Cancel all queued events, in case a func_bomb_target fired some delayed outputs that + // could kill respawning CTs + g_EventQueue.Clear(); + + // Now reload the map entities. + class CHL2MPMapEntityFilter : public IMapEntityFilter + { + public: + virtual bool ShouldCreateEntity( const char *pClassname ) + { + // Don't recreate the preserved entities. + if ( !FindInList( s_PreserveEnts, pClassname ) ) + { + return true; + } + else + { + // Increment our iterator since it's not going to call CreateNextEntity for this ent. + if ( m_iIterator != g_MapEntityRefs.InvalidIndex() ) + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); + + return false; + } + } + + + virtual CBaseEntity* CreateNextEntity( const char *pClassname ) + { + if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) + { + // This shouldn't be possible. When we loaded the map, it should have used + // CCSMapLoadEntityFilter, which should have built the g_MapEntityRefs list + // with the same list of entities we're referring to here. + Assert( false ); + return NULL; + } + else + { + CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; + m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity. + + if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) ) + { + // Doh! The entity was delete and its slot was reused. + // Just use any old edict slot. This case sucks because we lose the baseline. + return CreateEntityByName( pClassname ); + } + else + { + // Cool, the slot where this entity was is free again (most likely, the entity was + // freed above). Now create an entity with this specific index. + return CreateEntityByName( pClassname, ref.m_iEdict ); + } + } + } + + public: + int m_iIterator; // Iterator into g_MapEntityRefs. + }; + CHL2MPMapEntityFilter filter; + filter.m_iIterator = g_MapEntityRefs.Head(); + + // DO NOT CALL SPAWN ON info_node ENTITIES! + + MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); +} + +void CHL2MPRules::CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ) +{ + if( m_bAwaitingReadyRestart && FStrEq( chatmsg, mp_ready_signal.GetString() ) ) + { + if( !pPlayer->IsReady() ) + { + pPlayer->SetReady( true ); + } + } +} + +void CHL2MPRules::CheckRestartGame( void ) +{ + // Restart the game if specified by the server + int iRestartDelay = mp_restartgame.GetInt(); + + if ( iRestartDelay > 0 ) + { + if ( iRestartDelay > 60 ) + iRestartDelay = 60; + + + // let the players know + char strRestartDelay[64]; + Q_snprintf( strRestartDelay, sizeof( strRestartDelay ), "%d", iRestartDelay ); + UTIL_ClientPrintAll( HUD_PRINTCENTER, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "Game will restart in %s1 %s2", strRestartDelay, iRestartDelay == 1 ? "SECOND" : "SECONDS" ); + + m_flRestartGameTime = gpGlobals->curtime + iRestartDelay; + m_bCompleteReset = true; + mp_restartgame.SetValue( 0 ); + } + + if( mp_readyrestart.GetBool() ) + { + m_bAwaitingReadyRestart = true; + m_bHeardAllPlayersReady = false; + + + const char *pszReadyString = mp_ready_signal.GetString(); + + + // Don't let them put anything malicious in there + if( pszReadyString == NULL || Q_strlen(pszReadyString) > 16 ) + { + pszReadyString = "ready"; + } + + IGameEvent *event = gameeventmanager->CreateEvent( "hl2mp_ready_restart" ); + if ( event ) + gameeventmanager->FireEvent( event ); + + mp_readyrestart.SetValue( 0 ); + + // cancel any restart round in progress + m_flRestartGameTime = -1; + } +} + +void CHL2MPRules::CheckAllPlayersReady( void ) +{ + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CHL2MP_Player *pPlayer = (CHL2MP_Player*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + if ( !pPlayer->IsReady() ) + return; + } + m_bHeardAllPlayersReady = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CHL2MPRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ) +{ + if ( !pPlayer ) // dedicated server output + { + return NULL; + } + + const char *pszFormat = NULL; + + // team only + if ( bTeamOnly == TRUE ) + { + if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) + { + pszFormat = "HL2MP_Chat_Spec"; + } + else + { + const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer ); + if ( chatLocation && *chatLocation ) + { + pszFormat = "HL2MP_Chat_Team_Loc"; + } + else + { + pszFormat = "HL2MP_Chat_Team"; + } + } + } + // everyone + else + { + if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR ) + { + pszFormat = "HL2MP_Chat_All"; + } + else + { + pszFormat = "HL2MP_Chat_AllSpec"; + } + } + + return pszFormat; +} + +#endif
\ No newline at end of file diff --git a/game/shared/hl2mp/hl2mp_gamerules.h b/game/shared/hl2mp/hl2mp_gamerules.h new file mode 100644 index 0000000..8d91554 --- /dev/null +++ b/game/shared/hl2mp/hl2mp_gamerules.h @@ -0,0 +1,172 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef HL2MP_GAMERULES_H +#define HL2MP_GAMERULES_H +#pragma once + +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "gamevars_shared.h" + +#ifndef CLIENT_DLL +#include "hl2mp_player.h" +#endif + +#define VEC_CROUCH_TRACE_MIN HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMin +#define VEC_CROUCH_TRACE_MAX HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMax + +enum +{ + TEAM_COMBINE = 2, + TEAM_REBELS, +}; + + +#ifdef CLIENT_DLL + #define CHL2MPRules C_HL2MPRules + #define CHL2MPGameRulesProxy C_HL2MPGameRulesProxy +#endif + +class CHL2MPGameRulesProxy : public CGameRulesProxy +{ +public: + DECLARE_CLASS( CHL2MPGameRulesProxy, CGameRulesProxy ); + DECLARE_NETWORKCLASS(); +}; + +class HL2MPViewVectors : public CViewVectors +{ +public: + HL2MPViewVectors( + Vector vView, + Vector vHullMin, + Vector vHullMax, + Vector vDuckHullMin, + Vector vDuckHullMax, + Vector vDuckView, + Vector vObsHullMin, + Vector vObsHullMax, + Vector vDeadViewHeight, + Vector vCrouchTraceMin, + Vector vCrouchTraceMax ) : + CViewVectors( + vView, + vHullMin, + vHullMax, + vDuckHullMin, + vDuckHullMax, + vDuckView, + vObsHullMin, + vObsHullMax, + vDeadViewHeight ) + { + m_vCrouchTraceMin = vCrouchTraceMin; + m_vCrouchTraceMax = vCrouchTraceMax; + } + + Vector m_vCrouchTraceMin; + Vector m_vCrouchTraceMax; +}; + +class CHL2MPRules : public CTeamplayRules +{ +public: + DECLARE_CLASS( CHL2MPRules, CTeamplayRules ); + +#ifdef CLIENT_DLL + + DECLARE_CLIENTCLASS_NOBASE(); // This makes datatables able to access our private vars. + +#else + + DECLARE_SERVERCLASS_NOBASE(); // This makes datatables able to access our private vars. +#endif + + CHL2MPRules(); + virtual ~CHL2MPRules(); + + virtual void Precache( void ); + virtual bool ShouldCollide( int collisionGroup0, int collisionGroup1 ); + virtual bool ClientCommand( CBaseEntity *pEdict, const CCommand &args ); + + virtual float FlWeaponRespawnTime( CBaseCombatWeapon *pWeapon ); + virtual float FlWeaponTryRespawn( CBaseCombatWeapon *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBaseCombatWeapon *pWeapon ); + virtual int WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ); + virtual void Think( void ); + virtual void CreateStandardEntities( void ); + virtual void ClientSettingsChanged( CBasePlayer *pPlayer ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual void GoToIntermission( void ); + virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ); + virtual const char *GetGameDescription( void ); + // derive this function if you mod uses encrypted weapon info files + virtual const unsigned char *GetEncryptionKey( void ) { return (unsigned char *)"x9Ke0BY7"; } + virtual const CViewVectors* GetViewVectors() const; + const HL2MPViewVectors* GetHL2MPViewVectors() const; + + float GetMapRemainingTime(); + void CleanUpMap(); + void CheckRestartGame(); + void RestartGame(); + +#ifndef CLIENT_DLL + virtual Vector VecItemRespawnSpot( CItem *pItem ); + virtual QAngle VecItemRespawnAngles( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual bool CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pItem ); + virtual bool FShouldSwitchWeapon( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon ); + + void AddLevelDesignerPlacedObject( CBaseEntity *pEntity ); + void RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ); + void ManageObjectRelocation( void ); + void CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ); + const char *GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ); + +#endif + virtual void ClientDisconnected( edict_t *pClient ); + + bool CheckGameOver( void ); + bool IsIntermission( void ); + + void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ); + + + bool IsTeamplay( void ) { return m_bTeamPlayEnabled; } + void CheckAllPlayersReady( void ); + + virtual bool IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ); + +private: + + CNetworkVar( bool, m_bTeamPlayEnabled ); + CNetworkVar( float, m_flGameStartTime ); + CUtlVector<EHANDLE> m_hRespawnableItemsAndWeapons; + float m_tmNextPeriodicThink; + float m_flRestartGameTime; + bool m_bCompleteReset; + bool m_bAwaitingReadyRestart; + bool m_bHeardAllPlayersReady; + +#ifndef CLIENT_DLL + bool m_bChangelevelDone; +#endif +}; + +inline CHL2MPRules* HL2MPRules() +{ + return static_cast<CHL2MPRules*>(g_pGameRules); +} + +#endif //HL2MP_GAMERULES_H diff --git a/game/shared/hl2mp/hl2mp_player_shared.cpp b/game/shared/hl2mp/hl2mp_player_shared.cpp new file mode 100644 index 0000000..503d498 --- /dev/null +++ b/game/shared/hl2mp/hl2mp_player_shared.cpp @@ -0,0 +1,577 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" + +#ifdef CLIENT_DLL +#include "c_hl2mp_player.h" +#include "prediction.h" +#define CRecipientFilter C_RecipientFilter +#else +#include "hl2mp_player.h" +#endif + +#include "engine/IEngineSound.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" + +extern ConVar sv_footsteps; + +const char *g_ppszPlayerSoundPrefixNames[PLAYER_SOUNDS_MAX] = +{ + "NPC_Citizen", + "NPC_CombineS", + "NPC_MetroPolice", +}; + +const char *CHL2MP_Player::GetPlayerModelSoundPrefix( void ) +{ + return g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType]; +} + +void CHL2MP_Player::PrecacheFootStepSounds( void ) +{ + int iFootstepSounds = ARRAYSIZE( g_ppszPlayerSoundPrefixNames ); + int i; + + for ( i = 0; i < iFootstepSounds; ++i ) + { + char szFootStepName[128]; + + Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[i] ); + PrecacheScriptSound( szFootStepName ); + + Q_snprintf( szFootStepName, sizeof( szFootStepName ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[i] ); + PrecacheScriptSound( szFootStepName ); + } +} + +//----------------------------------------------------------------------------- +// Consider the weapon's built-in accuracy, this character's proficiency with +// the weapon, and the status of the target. Use this information to determine +// how accurately to shoot at the target. +//----------------------------------------------------------------------------- +Vector CHL2MP_Player::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ) +{ + if ( pWeapon ) + return pWeapon->GetBulletSpread( WEAPON_PROFICIENCY_PERFECT ); + + return VECTOR_CONE_15DEGREES; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : step - +// fvol - +// force - force sound to play +//----------------------------------------------------------------------------- +void CHL2MP_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) +{ + if ( gpGlobals->maxClients > 1 && !sv_footsteps.GetFloat() ) + return; + +#if defined( CLIENT_DLL ) + // during prediction play footstep sounds only once + if ( !prediction->IsFirstTimePredicted() ) + return; +#endif + + if ( GetFlags() & FL_DUCKING ) + return; + + m_Local.m_nStepside = !m_Local.m_nStepside; + + char szStepSound[128]; + + if ( m_Local.m_nStepside ) + { + Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepLeft", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); + } + else + { + Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.RunFootstepRight", g_ppszPlayerSoundPrefixNames[m_iPlayerSoundType] ); + } + + CSoundParameters params; + if ( GetParametersForSound( szStepSound, params, NULL ) == false ) + return; + + CRecipientFilter filter; + filter.AddRecipientsByPAS( vecOrigin ); + +#ifndef CLIENT_DLL + // im MP, server removed all players in origins PVS, these players + // generate the footsteps clientside + if ( gpGlobals->maxClients > 1 ) + filter.RemoveRecipientsByPVS( vecOrigin ); +#endif + + EmitSound_t ep; + ep.m_nChannel = CHAN_BODY; + ep.m_pSoundName = params.soundname; + ep.m_flVolume = fvol; + ep.m_SoundLevel = params.soundlevel; + ep.m_nFlags = 0; + ep.m_nPitch = params.pitch; + ep.m_pOrigin = &vecOrigin; + + EmitSound( filter, entindex(), ep ); +} + + +//========================== +// ANIMATION CODE +//========================== + + +// Below this many degrees, slow down turning rate linearly +#define FADE_TURN_DEGREES 45.0f +// After this, need to start turning feet +#define MAX_TORSO_ANGLE 90.0f +// Below this amount, don't play a turning animation/perform IK +#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f + +static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); +extern ConVar sv_backspeed; +extern ConVar mp_feetyawrate; +extern ConVar mp_facefronttime; +extern ConVar mp_ik; + +CPlayerAnimState::CPlayerAnimState( CHL2MP_Player *outer ) + : m_pOuter( outer ) +{ + m_flGaitYaw = 0.0f; + m_flGoalFeetYaw = 0.0f; + m_flCurrentFeetYaw = 0.0f; + m_flCurrentTorsoYaw = 0.0f; + m_flLastYaw = 0.0f; + m_flLastTurnTime = 0.0f; + m_flTurnCorrectionTime = 0.0f; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerAnimState::Update() +{ + m_angRender = GetOuter()->GetLocalAngles(); + m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; + + ComputePoseParam_BodyYaw(); + ComputePoseParam_BodyPitch(GetOuter()->GetModelPtr()); + ComputePoseParam_BodyLookYaw(); + + ComputePlaybackRate(); + +#ifdef CLIENT_DLL + GetOuter()->UpdateLookAt(); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerAnimState::ComputePlaybackRate() +{ + // Determine ideal playback rate + Vector vel; + GetOuterAbsVelocity( vel ); + + float speed = vel.Length2D(); + + bool isMoving = ( speed > 0.5f ) ? true : false; + + float maxspeed = GetOuter()->GetSequenceGroundSpeed( GetOuter()->GetSequence() ); + + if ( isMoving && ( maxspeed > 0.0f ) ) + { + float flFactor = 1.0f; + + // Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below + GetOuter()->SetPlaybackRate( ( speed * flFactor ) / maxspeed ); + + // BUG BUG: + // This stuff really should be m_flPlaybackRate = speed / m_flGroundSpeed + } + else + { + GetOuter()->SetPlaybackRate( 1.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBasePlayer +//----------------------------------------------------------------------------- +CHL2MP_Player *CPlayerAnimState::GetOuter() +{ + return m_pOuter; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : dt - +//----------------------------------------------------------------------------- +void CPlayerAnimState::EstimateYaw( void ) +{ + float dt = gpGlobals->frametime; + + if ( !dt ) + { + return; + } + + Vector est_velocity; + QAngle angles; + + GetOuterAbsVelocity( est_velocity ); + + angles = GetOuter()->GetLocalAngles(); + + if ( est_velocity[1] == 0 && est_velocity[0] == 0 ) + { + float flYawDiff = angles[YAW] - m_flGaitYaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if (flYawDiff > 180) + flYawDiff -= 360; + if (flYawDiff < -180) + flYawDiff += 360; + + if (dt < 0.25) + flYawDiff *= dt * 4; + else + flYawDiff *= dt; + + m_flGaitYaw += flYawDiff; + m_flGaitYaw = m_flGaitYaw - (int)(m_flGaitYaw / 360) * 360; + } + else + { + m_flGaitYaw = (atan2(est_velocity[1], est_velocity[0]) * 180 / M_PI); + + if (m_flGaitYaw > 180) + m_flGaitYaw = 180; + else if (m_flGaitYaw < -180) + m_flGaitYaw = -180; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override for backpeddling +// Input : dt - +//----------------------------------------------------------------------------- +void CPlayerAnimState::ComputePoseParam_BodyYaw( void ) +{ + int iYaw = GetOuter()->LookupPoseParameter( "move_yaw" ); + if ( iYaw < 0 ) + return; + + // view direction relative to movement + float flYaw; + + EstimateYaw(); + + QAngle angles = GetOuter()->GetLocalAngles(); + float ang = angles[ YAW ]; + if ( ang > 180.0f ) + { + ang -= 360.0f; + } + else if ( ang < -180.0f ) + { + ang += 360.0f; + } + + // calc side to side turning + flYaw = ang - m_flGaitYaw; + // Invert for mapping into 8way blend + flYaw = -flYaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + + if (flYaw < -180) + { + flYaw = flYaw + 360; + } + else if (flYaw > 180) + { + flYaw = flYaw - 360; + } + + GetOuter()->SetPoseParameter( iYaw, flYaw ); + +#ifndef CLIENT_DLL + //Adrian: Make the model's angle match the legs so the hitboxes match on both sides. + GetOuter()->SetLocalAngles( QAngle( GetOuter()->GetAnimEyeAngles().x, m_flCurrentFeetYaw, 0 ) ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) +{ + // Get pitch from v_angle + float flPitch = GetOuter()->GetLocalAngles()[ PITCH ]; + + if ( flPitch > 180.0f ) + { + flPitch -= 360.0f; + } + flPitch = clamp( flPitch, -90, 90 ); + + QAngle absangles = GetOuter()->GetAbsAngles(); + absangles.x = 0.0f; + m_angRender = absangles; + m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; + + // See if we have a blender for pitch + GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : goal - +// maxrate - +// dt - +// current - +// Output : int +//----------------------------------------------------------------------------- +int CPlayerAnimState::ConvergeAngles( float goal,float maxrate, float dt, float& current ) +{ + int direction = TURN_NONE; + + float anglediff = goal - current; + float anglediffabs = fabs( anglediff ); + + anglediff = AngleNormalize( anglediff ); + + float scale = 1.0f; + if ( anglediffabs <= FADE_TURN_DEGREES ) + { + scale = anglediffabs / FADE_TURN_DEGREES; + // Always do at least a bit of the turn ( 1% ) + scale = clamp( scale, 0.01f, 1.0f ); + } + + float maxmove = maxrate * dt * scale; + + if ( fabs( anglediff ) < maxmove ) + { + current = goal; + } + else + { + if ( anglediff > 0 ) + { + current += maxmove; + direction = TURN_LEFT; + } + else + { + current -= maxmove; + direction = TURN_RIGHT; + } + } + + current = AngleNormalize( current ); + + return direction; +} + +void CPlayerAnimState::ComputePoseParam_BodyLookYaw( void ) +{ + QAngle absangles = GetOuter()->GetAbsAngles(); + absangles.y = AngleNormalize( absangles.y ); + m_angRender = absangles; + m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; + + // See if we even have a blender for pitch + int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" ); + if ( upper_body_yaw < 0 ) + { + return; + } + + // Assume upper and lower bodies are aligned and that we're not turning + float flGoalTorsoYaw = 0.0f; + int turning = TURN_NONE; + float turnrate = 360.0f; + + Vector vel; + + GetOuterAbsVelocity( vel ); + + bool isMoving = ( vel.Length() > 1.0f ) ? true : false; + + if ( !isMoving ) + { + // Just stopped moving, try and clamp feet + if ( m_flLastTurnTime <= 0.0f ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; + // Snap feet to be perfectly aligned with torso/eyes + m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; + m_flCurrentFeetYaw = m_flGoalFeetYaw; + m_nTurningInPlace = TURN_NONE; + } + + // If rotating in place, update stasis timer + if ( m_flLastYaw != GetOuter()->GetAnimEyeAngles().y ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->GetAnimEyeAngles().y; + } + + if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) + { + m_flLastTurnTime = gpGlobals->curtime; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); + + QAngle eyeAngles = GetOuter()->GetAnimEyeAngles(); + QAngle vAngle = GetOuter()->GetLocalAngles(); + + // See how far off current feetyaw is from true yaw + float yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw; + yawdelta = AngleNormalize( yawdelta ); + + bool rotated_too_far = false; + + float yawmagnitude = fabs( yawdelta ); + + // If too far, then need to turn in place + if ( yawmagnitude > 45 ) + { + rotated_too_far = true; + } + + // Standing still for a while, rotate feet around to face forward + // Or rotated too far + // FIXME: Play an in place turning animation + if ( rotated_too_far || + ( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) ) + { + m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; + m_flLastTurnTime = gpGlobals->curtime; + + /* float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw; + if ( yd > 0 ) + { + m_nTurningInPlace = TURN_RIGHT; + } + else if ( yd < 0 ) + { + m_nTurningInPlace = TURN_LEFT; + } + else + { + m_nTurningInPlace = TURN_NONE; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); + yawdelta = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw;*/ + + } + + // Snap upper body into position since the delta is already smoothed for the feet + flGoalTorsoYaw = yawdelta; + m_flCurrentTorsoYaw = flGoalTorsoYaw; + } + else + { + m_flLastTurnTime = 0.0f; + m_nTurningInPlace = TURN_NONE; + m_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->GetAnimEyeAngles().y; + flGoalTorsoYaw = 0.0f; + m_flCurrentTorsoYaw = GetOuter()->GetAnimEyeAngles().y - m_flCurrentFeetYaw; + } + + + if ( turning == TURN_NONE ) + { + m_nTurningInPlace = turning; + } + + if ( m_nTurningInPlace != TURN_NONE ) + { + // If we're close to finishing the turn, then turn off the turning animation + if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION ) + { + m_nTurningInPlace = TURN_NONE; + } + } + + // Rotate entire body into position + absangles = GetOuter()->GetAbsAngles(); + absangles.y = m_flCurrentFeetYaw; + m_angRender = absangles; + m_angRender[ PITCH ] = m_angRender[ ROLL ] = 0.0f; + + GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) ); + + /* + // FIXME: Adrian, what is this? + int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); + + if ( body_yaw >= 0 ) + { + GetOuter()->SetPoseParameter( body_yaw, 30 ); + } + */ + +} + + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : activity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CPlayerAnimState::BodyYawTranslateActivity( Activity activity ) +{ + // Not even standing still, sigh + if ( activity != ACT_IDLE ) + return activity; + + // Not turning + switch ( m_nTurningInPlace ) + { + default: + case TURN_NONE: + return activity; + /* + case TURN_RIGHT: + return ACT_TURNRIGHT45; + case TURN_LEFT: + return ACT_TURNLEFT45; + */ + case TURN_RIGHT: + case TURN_LEFT: + return mp_ik.GetBool() ? ACT_TURN : activity; + } + + Assert( 0 ); + return activity; +} + +const QAngle& CPlayerAnimState::GetRenderAngles() +{ + return m_angRender; +} + + +void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel ) +{ +#if defined( CLIENT_DLL ) + GetOuter()->EstimateAbsVelocity( vel ); +#else + vel = GetOuter()->GetAbsVelocity(); +#endif +}
\ No newline at end of file diff --git a/game/shared/hl2mp/hl2mp_player_shared.h b/game/shared/hl2mp/hl2mp_player_shared.h new file mode 100644 index 0000000..3aee923 --- /dev/null +++ b/game/shared/hl2mp/hl2mp_player_shared.h @@ -0,0 +1,97 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef HL2MP_PLAYER_SHARED_H +#define HL2MP_PLAYER_SHARED_H +#pragma once + +#define HL2MP_PUSHAWAY_THINK_INTERVAL (1.0f / 20.0f) +#include "studio.h" + + +enum +{ + PLAYER_SOUNDS_CITIZEN = 0, + PLAYER_SOUNDS_COMBINESOLDIER, + PLAYER_SOUNDS_METROPOLICE, + PLAYER_SOUNDS_MAX, +}; + +enum HL2MPPlayerState +{ + // Happily running around in the game. + STATE_ACTIVE=0, + STATE_OBSERVER_MODE, // Noclipping around, watching players, etc. + NUM_PLAYER_STATES +}; + + +#if defined( CLIENT_DLL ) +#define CHL2MP_Player C_HL2MP_Player +#endif + +class CPlayerAnimState +{ +public: + enum + { + TURN_NONE = 0, + TURN_LEFT, + TURN_RIGHT + }; + + CPlayerAnimState( CHL2MP_Player *outer ); + + Activity BodyYawTranslateActivity( Activity activity ); + + void Update(); + + const QAngle& GetRenderAngles(); + + void GetPoseParameters( CStudioHdr *pStudioHdr, float poseParameter[MAXSTUDIOPOSEPARAM] ); + + CHL2MP_Player *GetOuter(); + +private: + void GetOuterAbsVelocity( Vector& vel ); + + int ConvergeAngles( float goal,float maxrate, float dt, float& current ); + + void EstimateYaw( void ); + void ComputePoseParam_BodyYaw( void ); + void ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ); + void ComputePoseParam_BodyLookYaw( void ); + + void ComputePlaybackRate(); + + CHL2MP_Player *m_pOuter; + + float m_flGaitYaw; + float m_flStoredCycle; + + // The following variables are used for tweaking the yaw of the upper body when standing still and + // making sure that it smoothly blends in and out once the player starts moving + // Direction feet were facing when we stopped moving + float m_flGoalFeetYaw; + float m_flCurrentFeetYaw; + + float m_flCurrentTorsoYaw; + + // To check if they are rotating in place + float m_flLastYaw; + // Time when we stopped moving + float m_flLastTurnTime; + + // One of the above enums + int m_nTurningInPlace; + + QAngle m_angRender; + + float m_flTurnCorrectionTime; +}; + +#endif //HL2MP_PLAYER_SHARED_h diff --git a/game/shared/hl2mp/hl2mp_weapon_parse.cpp b/game/shared/hl2mp/hl2mp_weapon_parse.cpp new file mode 100644 index 0000000..9b83dce --- /dev/null +++ b/game/shared/hl2mp/hl2mp_weapon_parse.cpp @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include <KeyValues.h> +#include "hl2mp_weapon_parse.h" +#include "ammodef.h" + +FileWeaponInfo_t* CreateWeaponInfo() +{ + return new CHL2MPSWeaponInfo; +} + + + +CHL2MPSWeaponInfo::CHL2MPSWeaponInfo() +{ + m_iPlayerDamage = 0; +} + + +void CHL2MPSWeaponInfo::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) +{ + BaseClass::Parse( pKeyValuesData, szWeaponName ); + + m_iPlayerDamage = pKeyValuesData->GetInt( "damage", 0 ); +} + + diff --git a/game/shared/hl2mp/hl2mp_weapon_parse.h b/game/shared/hl2mp/hl2mp_weapon_parse.h new file mode 100644 index 0000000..2ec16dd --- /dev/null +++ b/game/shared/hl2mp/hl2mp_weapon_parse.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef HL2MP_WEAPON_PARSE_H +#define HL2MP_WEAPON_PARSE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "weapon_parse.h" +#include "networkvar.h" + + +//-------------------------------------------------------------------------------------------------------- +class CHL2MPSWeaponInfo : public FileWeaponInfo_t +{ +public: + DECLARE_CLASS_GAMEROOT( CHL2MPSWeaponInfo, FileWeaponInfo_t ); + + CHL2MPSWeaponInfo(); + + virtual void Parse( ::KeyValues *pKeyValuesData, const char *szWeaponName ); + + +public: + + int m_iPlayerDamage; +}; + + +#endif // HL2MP_WEAPON_PARSE_H diff --git a/game/shared/hl2mp/weapon_357.cpp b/game/shared/hl2mp/weapon_357.cpp new file mode 100644 index 0000000..d943f27 --- /dev/null +++ b/game/shared/hl2mp/weapon_357.cpp @@ -0,0 +1,154 @@ + +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" +#endif + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#ifdef CLIENT_DLL +#define CWeapon357 C_Weapon357 +#endif + +//----------------------------------------------------------------------------- +// CWeapon357 +//----------------------------------------------------------------------------- + +class CWeapon357 : public CBaseHL2MPCombatWeapon +{ + DECLARE_CLASS( CWeapon357, CBaseHL2MPCombatWeapon ); +public: + + CWeapon357( void ); + + void PrimaryAttack( void ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +private: + + CWeapon357( const CWeapon357 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( Weapon357, DT_Weapon357 ) + +BEGIN_NETWORK_TABLE( CWeapon357, DT_Weapon357 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeapon357 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_357, CWeapon357 ); +PRECACHE_WEAPON_REGISTER( weapon_357 ); + + +#ifndef CLIENT_DLL +acttable_t CWeapon357::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, false }, +}; + + + +IMPLEMENT_ACTTABLE( CWeapon357 ); + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWeapon357::CWeapon357( void ) +{ + m_bReloadsSingly = false; + m_bFiresUnderwater = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeapon357::PrimaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( !pPlayer ) + { + return; + } + + if ( m_iClip1 <= 0 ) + { + if ( !m_bFireOnEmpty ) + { + Reload(); + } + else + { + WeaponSound( EMPTY ); + m_flNextPrimaryAttack = 0.15; + } + + return; + } + + WeaponSound( SINGLE ); + pPlayer->DoMuzzleFlash(); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = gpGlobals->curtime + 0.75; + m_flNextSecondaryAttack = gpGlobals->curtime + 0.75; + + m_iClip1--; + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + FireBulletsInfo_t info( 1, vecSrc, vecAiming, vec3_origin, MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_pAttacker = pPlayer; + + // Fire the bullets, and force the first shot to be perfectly accuracy + pPlayer->FireBullets( info ); + + //Disorient the player + QAngle angles = pPlayer->GetLocalAngles(); + + angles.x += random->RandomInt( -1, 1 ); + angles.y += random->RandomInt( -1, 1 ); + angles.z = 0; + +#ifndef CLIENT_DLL + pPlayer->SnapEyeAngles( angles ); +#endif + + pPlayer->ViewPunch( QAngle( -8, random->RandomFloat( -2, 2 ), 0 ) ); + + if ( !m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); + } +} diff --git a/game/shared/hl2mp/weapon_ar2.cpp b/game/shared/hl2mp/weapon_ar2.cpp new file mode 100644 index 0000000..5086db1 --- /dev/null +++ b/game/shared/hl2mp/weapon_ar2.cpp @@ -0,0 +1,312 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" + #include "c_te_effect_dispatch.h" +#else + #include "hl2mp_player.h" + #include "te_effect_dispatch.h" + #include "prop_combine_ball.h" +#endif + +#include "weapon_ar2.h" +#include "effect_dispatch_data.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef CLIENT_DLL +ConVar sk_weapon_ar2_alt_fire_radius( "sk_weapon_ar2_alt_fire_radius", "10" ); +ConVar sk_weapon_ar2_alt_fire_duration( "sk_weapon_ar2_alt_fire_duration", "4" ); +ConVar sk_weapon_ar2_alt_fire_mass( "sk_weapon_ar2_alt_fire_mass", "150" ); +#endif + +//========================================================= +//========================================================= + + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAR2, DT_WeaponAR2 ) + +BEGIN_NETWORK_TABLE( CWeaponAR2, DT_WeaponAR2 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponAR2 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_ar2, CWeaponAR2 ); +PRECACHE_WEAPON_REGISTER(weapon_ar2); + + +#ifndef CLIENT_DLL + +acttable_t CWeaponAR2::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR2, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR2, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponAR2); + +#endif + +CWeaponAR2::CWeaponAR2( ) +{ + m_fMinRange1 = 65; + m_fMaxRange1 = 2048; + + m_fMinRange2 = 256; + m_fMaxRange2 = 1024; + + m_nShotsFired = 0; + m_nVentPose = -1; +} + +void CWeaponAR2::Precache( void ) +{ + BaseClass::Precache(); + +#ifndef CLIENT_DLL + + UTIL_PrecacheOther( "prop_combine_ball" ); + UTIL_PrecacheOther( "env_entity_dissolver" ); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Handle grenade detonate in-air (even when no ammo is left) +//----------------------------------------------------------------------------- +void CWeaponAR2::ItemPostFrame( void ) +{ + // See if we need to fire off our secondary round + if ( m_bShotDelayed && gpGlobals->curtime > m_flDelayedFire ) + { + DelayedAttack(); + } + + // Update our pose parameter for the vents + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner ) + { + CBaseViewModel *pVM = pOwner->GetViewModel(); + + if ( pVM ) + { + if ( m_nVentPose == -1 ) + { + m_nVentPose = pVM->LookupPoseParameter( "VentPoses" ); + } + + float flVentPose = RemapValClamped( m_nShotsFired, 0, 5, 0.0f, 1.0f ); + pVM->SetPoseParameter( m_nVentPose, flVentPose ); + } + } + + BaseClass::ItemPostFrame(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Activity +//----------------------------------------------------------------------------- +Activity CWeaponAR2::GetPrimaryAttackActivity( void ) +{ + if ( m_nShotsFired < 2 ) + return ACT_VM_PRIMARYATTACK; + + if ( m_nShotsFired < 3 ) + return ACT_VM_RECOIL1; + + if ( m_nShotsFired < 4 ) + return ACT_VM_RECOIL2; + + return ACT_VM_RECOIL3; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &tr - +// nDamageType - +//----------------------------------------------------------------------------- +void CWeaponAR2::DoImpactEffect( trace_t &tr, int nDamageType ) +{ + CEffectData data; + + data.m_vOrigin = tr.endpos + ( tr.plane.normal * 1.0f ); + data.m_vNormal = tr.plane.normal; + + DispatchEffect( "AR2Impact", data ); + + BaseClass::DoImpactEffect( tr, nDamageType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponAR2::DelayedAttack( void ) +{ + m_bShotDelayed = false; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + // Deplete the clip completely + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + m_flNextSecondaryAttack = pOwner->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); + + // Register a muzzleflash for the AI + pOwner->DoMuzzleFlash(); + + WeaponSound( WPN_DOUBLE ); + + // Fire the bullets + Vector vecSrc = pOwner->Weapon_ShootPosition( ); + Vector vecAiming = pOwner->GetAutoaimVector( AUTOAIM_2DEGREES ); + Vector impactPoint = vecSrc + ( vecAiming * MAX_TRACE_LENGTH ); + + // Fire the bullets + Vector vecVelocity = vecAiming * 1000.0f; + +#ifndef CLIENT_DLL + // Fire the combine ball + CreateCombineBall( vecSrc, + vecVelocity, + sk_weapon_ar2_alt_fire_radius.GetFloat(), + sk_weapon_ar2_alt_fire_mass.GetFloat(), + sk_weapon_ar2_alt_fire_duration.GetFloat(), + pOwner ); + + // View effects + color32 white = {255, 255, 255, 64}; + UTIL_ScreenFade( pOwner, white, 0.1, 0, FFADE_IN ); +#endif + + //Disorient the player + QAngle angles = pOwner->GetLocalAngles(); + + angles.x += random->RandomInt( -4, 4 ); + angles.y += random->RandomInt( -4, 4 ); + angles.z = 0; + +// pOwner->SnapEyeAngles( angles ); + + pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -8, -12 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); + + // Decrease ammo + pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); + + // Can shoot again immediately + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; + + // Can blow up after a short delay (so have time to release mouse button) + m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponAR2::SecondaryAttack( void ) +{ + if ( m_bShotDelayed ) + return; + + // Cannot fire underwater + if ( GetOwner() && GetOwner()->GetWaterLevel() == 3 ) + { + SendWeaponAnim( ACT_VM_DRYFIRE ); + BaseClass::WeaponSound( EMPTY ); + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; + return; + } + + m_bShotDelayed = true; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = m_flDelayedFire = gpGlobals->curtime + 0.5f; + + SendWeaponAnim( ACT_VM_FIDGET ); + WeaponSound( SPECIAL1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Override if we're waiting to release a shot +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponAR2::CanHolster( void ) +{ + if ( m_bShotDelayed ) + return false; + + return BaseClass::CanHolster(); +} + + +bool CWeaponAR2::Deploy( void ) +{ + m_bShotDelayed = false; + m_flDelayedFire = 0.0f; + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: Override if we're waiting to release a shot +//----------------------------------------------------------------------------- +bool CWeaponAR2::Reload( void ) +{ + if ( m_bShotDelayed ) + return false; + + return BaseClass::Reload(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponAR2::AddViewKick( void ) +{ + #define EASY_DAMPEN 0.5f + #define MAX_VERTICAL_KICK 8.0f //Degrees + #define SLIDE_LIMIT 5.0f //Seconds + + //Get the view kick + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if (!pPlayer) + return; + + DoMachineGunKick( pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, m_fFireDuration, SLIDE_LIMIT ); +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CWeaponAR2::GetProficiencyValues() +{ + static WeaponProficiencyInfo_t proficiencyTable[] = + { + { 7.0, 0.75 }, + { 5.00, 0.75 }, + { 3.0, 0.85 }, + { 5.0/3.0, 0.75 }, + { 1.00, 1.0 }, + }; + + COMPILE_TIME_ASSERT( ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); + + return proficiencyTable; +} diff --git a/game/shared/hl2mp/weapon_ar2.h b/game/shared/hl2mp/weapon_ar2.h new file mode 100644 index 0000000..8aa8dde --- /dev/null +++ b/game/shared/hl2mp/weapon_ar2.h @@ -0,0 +1,84 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Projectile shot from the AR2 +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef WEAPONAR2_H +#define WEAPONAR2_H + +#include "basegrenade_shared.h" +#include "weapon_hl2mpbase_machinegun.h" + +#ifdef CLIENT_DLL +#define CWeaponAR2 C_WeaponAR2 +#endif + +class CWeaponAR2 : public CHL2MPMachineGun +{ +public: + DECLARE_CLASS( CWeaponAR2, CHL2MPMachineGun ); + + CWeaponAR2(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + void ItemPostFrame( void ); + void Precache( void ); + + void SecondaryAttack( void ); + void DelayedAttack( void ); + + const char *GetTracerType( void ) { return "AR2Tracer"; } + + void AddViewKick( void ); + + int GetMinBurst( void ) { return 2; } + int GetMaxBurst( void ) { return 5; } + float GetFireRate( void ) { return 0.1f; } + + bool CanHolster( void ); + bool Reload( void ); + + Activity GetPrimaryAttackActivity( void ); + + void DoImpactEffect( trace_t &tr, int nDamageType ); + + virtual bool Deploy( void ); + + + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone; + + cone = VECTOR_CONE_3DEGREES; + + return cone; + } + + const WeaponProficiencyInfo_t *GetProficiencyValues(); + +private: + CWeaponAR2( const CWeaponAR2 & ); + +protected: + + float m_flDelayedFire; + bool m_bShotDelayed; + int m_nVentPose; + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif +}; + + +#endif //WEAPONAR2_H diff --git a/game/shared/hl2mp/weapon_crossbow.cpp b/game/shared/hl2mp/weapon_crossbow.cpp new file mode 100644 index 0000000..5bada60 --- /dev/null +++ b/game/shared/hl2mp/weapon_crossbow.cpp @@ -0,0 +1,956 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" + #include "c_te_effect_dispatch.h" +#else + #include "hl2mp_player.h" + #include "te_effect_dispatch.h" + #include "IEffects.h" + #include "Sprite.h" + #include "SpriteTrail.h" + #include "beam_shared.h" +#endif + +#include "weapon_hl2mpbasehlmpcombatweapon.h" +#include "effect_dispatch_data.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//#define BOLT_MODEL "models/crossbow_bolt.mdl" +#define BOLT_MODEL "models/weapons/w_missile_closed.mdl" + +#define BOLT_AIR_VELOCITY 3500 +#define BOLT_WATER_VELOCITY 1500 +#define BOLT_SKIN_NORMAL 0 +#define BOLT_SKIN_GLOW 1 + + +#ifndef CLIENT_DLL + +extern ConVar sk_plr_dmg_crossbow; +extern ConVar sk_npc_dmg_crossbow; + +void TE_StickyBolt( IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin ); + +//----------------------------------------------------------------------------- +// Crossbow Bolt +//----------------------------------------------------------------------------- +class CCrossbowBolt : public CBaseCombatCharacter +{ + DECLARE_CLASS( CCrossbowBolt, CBaseCombatCharacter ); + +public: + CCrossbowBolt() { }; + ~CCrossbowBolt(); + + Class_T Classify( void ) { return CLASS_NONE; } + +public: + void Spawn( void ); + void Precache( void ); + void BubbleThink( void ); + void BoltTouch( CBaseEntity *pOther ); + bool CreateVPhysics( void ); + unsigned int PhysicsSolidMaskForEntity() const; + static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CBasePlayer *pentOwner = NULL ); + +protected: + + bool CreateSprites( void ); + + CHandle<CSprite> m_pGlowSprite; + //CHandle<CSpriteTrail> m_pGlowTrail; + + int m_iDamage; + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); +}; +LINK_ENTITY_TO_CLASS( crossbow_bolt, CCrossbowBolt ); + +BEGIN_DATADESC( CCrossbowBolt ) + // Function Pointers + DEFINE_FUNCTION( BubbleThink ), + DEFINE_FUNCTION( BoltTouch ), + + // These are recreated on reload, they don't need storage + DEFINE_FIELD( m_pGlowSprite, FIELD_EHANDLE ), + //DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CCrossbowBolt, DT_CrossbowBolt ) +END_SEND_TABLE() + +CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CBasePlayer *pentOwner ) +{ + // Create a new entity with CCrossbowBolt private data + CCrossbowBolt *pBolt = (CCrossbowBolt *)CreateEntityByName( "crossbow_bolt" ); + UTIL_SetOrigin( pBolt, vecOrigin ); + pBolt->SetAbsAngles( angAngles ); + pBolt->Spawn(); + pBolt->SetOwnerEntity( pentOwner ); + + pBolt->m_iDamage = iDamage; + + return pBolt; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCrossbowBolt::~CCrossbowBolt( void ) +{ + if ( m_pGlowSprite ) + { + UTIL_Remove( m_pGlowSprite ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CCrossbowBolt::CreateVPhysics( void ) +{ + // Create the object in the physics system + VPhysicsInitNormal( SOLID_BBOX, FSOLID_NOT_STANDABLE, false ); + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int CCrossbowBolt::PhysicsSolidMaskForEntity() const +{ + return ( BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX ) & ~CONTENTS_GRATE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CCrossbowBolt::CreateSprites( void ) +{ + // Start up the eye glow + m_pGlowSprite = CSprite::SpriteCreate( "sprites/light_glow02_noz.vmt", GetLocalOrigin(), false ); + + if ( m_pGlowSprite != NULL ) + { + m_pGlowSprite->FollowEntity( this ); + m_pGlowSprite->SetTransparency( kRenderGlow, 255, 255, 255, 128, kRenderFxNoDissipation ); + m_pGlowSprite->SetScale( 0.2f ); + m_pGlowSprite->TurnOff(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrossbowBolt::Spawn( void ) +{ + Precache( ); + + SetModel( "models/crossbow_bolt.mdl" ); + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); + UTIL_SetSize( this, -Vector(1,1,1), Vector(1,1,1) ); + SetSolid( SOLID_BBOX ); + SetGravity( 0.05f ); + + // Make sure we're updated if we're underwater + UpdateWaterState(); + + SetTouch( &CCrossbowBolt::BoltTouch ); + + SetThink( &CCrossbowBolt::BubbleThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); + + CreateSprites(); + + // Make us glow until we've hit the wall + m_nSkin = BOLT_SKIN_GLOW; +} + + +void CCrossbowBolt::Precache( void ) +{ + PrecacheModel( BOLT_MODEL ); + + // This is used by C_TEStickyBolt, despte being different from above!!! + PrecacheModel( "models/crossbow_bolt.mdl" ); + + PrecacheModel( "sprites/light_glow02_noz.vmt" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) ) + return; + + if ( pOther->m_takedamage != DAMAGE_NO ) + { + trace_t tr, tr2; + tr = BaseClass::GetTouchTrace(); + Vector vecNormalizedVel = GetAbsVelocity(); + + ClearMultiDamage(); + VectorNormalize( vecNormalizedVel ); + + if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC() ) + { + CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_NEVERGIB ); + dmgInfo.AdjustPlayerDamageInflictedForSkillLevel(); + CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f ); + dmgInfo.SetDamagePosition( tr.endpos ); + pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr ); + } + else + { + CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_BULLET | DMG_NEVERGIB ); + CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f ); + dmgInfo.SetDamagePosition( tr.endpos ); + pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr ); + } + + ApplyMultiDamage(); + + //Adrian: keep going through the glass. + if ( pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS ) + return; + + SetAbsVelocity( Vector( 0, 0, 0 ) ); + + // play body "thwack" sound + EmitSound( "Weapon_Crossbow.BoltHitBody" ); + + Vector vForward; + + AngleVectors( GetAbsAngles(), &vForward ); + VectorNormalize ( vForward ); + + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_OPAQUE, pOther, COLLISION_GROUP_NONE, &tr2 ); + + if ( tr2.fraction != 1.0f ) + { +// NDebugOverlay::Box( tr2.endpos, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 255, 0, 0, 10 ); +// NDebugOverlay::Box( GetAbsOrigin(), Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0, 0, 255, 0, 10 ); + + if ( tr2.m_pEnt == NULL || ( tr2.m_pEnt && tr2.m_pEnt->GetMoveType() == MOVETYPE_NONE ) ) + { + CEffectData data; + + data.m_vOrigin = tr2.endpos; + data.m_vNormal = vForward; + data.m_nEntIndex = tr2.fraction != 1.0f; + + DispatchEffect( "BoltImpact", data ); + } + } + + SetTouch( NULL ); + SetThink( NULL ); + + UTIL_Remove( this ); + } + else + { + trace_t tr; + tr = BaseClass::GetTouchTrace(); + + // See if we struck the world + if ( pOther->GetMoveType() == MOVETYPE_NONE && !( tr.surface.flags & SURF_SKY ) ) + { + EmitSound( "Weapon_Crossbow.BoltHitWorld" ); + + // if what we hit is static architecture, can stay around for a while. + Vector vecDir = GetAbsVelocity(); + float speed = VectorNormalize( vecDir ); + + // See if we should reflect off this surface + float hitDot = DotProduct( tr.plane.normal, -vecDir ); + + if ( ( hitDot < 0.5f ) && ( speed > 100 ) ) + { + Vector vReflection = 2.0f * tr.plane.normal * hitDot + vecDir; + + QAngle reflectAngles; + + VectorAngles( vReflection, reflectAngles ); + + SetLocalAngles( reflectAngles ); + + SetAbsVelocity( vReflection * speed * 0.75f ); + + // Start to sink faster + SetGravity( 1.0f ); + } + else + { + SetThink( &CCrossbowBolt::SUB_Remove ); + SetNextThink( gpGlobals->curtime + 2.0f ); + + //FIXME: We actually want to stick (with hierarchy) to what we've hit + SetMoveType( MOVETYPE_NONE ); + + Vector vForward; + + AngleVectors( GetAbsAngles(), &vForward ); + VectorNormalize ( vForward ); + + CEffectData data; + + data.m_vOrigin = tr.endpos; + data.m_vNormal = vForward; + data.m_nEntIndex = 0; + + DispatchEffect( "BoltImpact", data ); + + UTIL_ImpactTrace( &tr, DMG_BULLET ); + + AddEffects( EF_NODRAW ); + SetTouch( NULL ); + SetThink( &CCrossbowBolt::SUB_Remove ); + SetNextThink( gpGlobals->curtime + 2.0f ); + + if ( m_pGlowSprite != NULL ) + { + m_pGlowSprite->TurnOn(); + m_pGlowSprite->FadeAndDie( 3.0f ); + } + } + + // Shoot some sparks + if ( UTIL_PointContents( GetAbsOrigin() ) != CONTENTS_WATER) + { + g_pEffects->Sparks( GetAbsOrigin() ); + } + } + else + { + // Put a mark unless we've hit the sky + if ( ( tr.surface.flags & SURF_SKY ) == false ) + { + UTIL_ImpactTrace( &tr, DMG_BULLET ); + } + + UTIL_Remove( this ); + } + } + + if ( g_pGameRules->IsMultiplayer() ) + { +// SetThink( &CCrossbowBolt::ExplodeThink ); +// SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrossbowBolt::BubbleThink( void ) +{ + QAngle angNewAngles; + + VectorAngles( GetAbsVelocity(), angNewAngles ); + SetAbsAngles( angNewAngles ); + + SetNextThink( gpGlobals->curtime + 0.1f ); + + if ( GetWaterLevel() == 0 ) + return; + + UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 ); +} + +#endif + +//----------------------------------------------------------------------------- +// CWeaponCrossbow +//----------------------------------------------------------------------------- + +#ifdef CLIENT_DLL +#define CWeaponCrossbow C_WeaponCrossbow +#endif + +class CWeaponCrossbow : public CBaseHL2MPCombatWeapon +{ + DECLARE_CLASS( CWeaponCrossbow, CBaseHL2MPCombatWeapon ); +public: + + CWeaponCrossbow( void ); + + virtual void Precache( void ); + virtual void PrimaryAttack( void ); + virtual void SecondaryAttack( void ); + virtual bool Deploy( void ); + virtual bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + virtual bool Reload( void ); + virtual void ItemPostFrame( void ); + virtual void ItemBusyFrame( void ); + virtual bool SendWeaponAnim( int iActivity ); + +#ifndef CLIENT_DLL + virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#endif + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +private: + + void SetSkin( int skinNum ); + void CheckZoomToggle( void ); + void FireBolt( void ); + void ToggleZoom( void ); + + // Various states for the crossbow's charger + enum ChargerState_t + { + CHARGER_STATE_START_LOAD, + CHARGER_STATE_START_CHARGE, + CHARGER_STATE_READY, + CHARGER_STATE_DISCHARGE, + CHARGER_STATE_OFF, + }; + + void CreateChargerEffects( void ); + void SetChargerState( ChargerState_t state ); + void DoLoadEffect( void ); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +private: + + // Charger effects + ChargerState_t m_nChargeState; + +#ifndef CLIENT_DLL + CHandle<CSprite> m_hChargerSprite; +#endif + + CNetworkVar( bool, m_bInZoom ); + CNetworkVar( bool, m_bMustReload ); + + CWeaponCrossbow( const CWeaponCrossbow & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCrossbow, DT_WeaponCrossbow ) + +BEGIN_NETWORK_TABLE( CWeaponCrossbow, DT_WeaponCrossbow ) +#ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bInZoom ) ), + RecvPropBool( RECVINFO( m_bMustReload ) ), +#else + SendPropBool( SENDINFO( m_bInZoom ) ), + SendPropBool( SENDINFO( m_bMustReload ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponCrossbow ) + DEFINE_PRED_FIELD( m_bInZoom, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bMustReload, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_crossbow, CWeaponCrossbow ); + +PRECACHE_WEAPON_REGISTER( weapon_crossbow ); + +#ifndef CLIENT_DLL + +acttable_t CWeaponCrossbow::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_CROSSBOW, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_CROSSBOW, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_CROSSBOW, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_CROSSBOW, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_CROSSBOW, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponCrossbow); + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWeaponCrossbow::CWeaponCrossbow( void ) +{ + m_bReloadsSingly = true; + m_bFiresUnderwater = true; + m_bInZoom = false; + m_bMustReload = false; +} + +#define CROSSBOW_GLOW_SPRITE "sprites/light_glow02_noz.vmt" +#define CROSSBOW_GLOW_SPRITE2 "sprites/blueflare1.vmt" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::Precache( void ) +{ +#ifndef CLIENT_DLL + UTIL_PrecacheOther( "crossbow_bolt" ); +#endif + + PrecacheScriptSound( "Weapon_Crossbow.BoltHitBody" ); + PrecacheScriptSound( "Weapon_Crossbow.BoltHitWorld" ); + PrecacheScriptSound( "Weapon_Crossbow.BoltSkewer" ); + + PrecacheModel( CROSSBOW_GLOW_SPRITE ); + PrecacheModel( CROSSBOW_GLOW_SPRITE2 ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::PrimaryAttack( void ) +{ + if ( m_bInZoom && g_pGameRules->IsMultiplayer() ) + { +// FireSniperBolt(); + FireBolt(); + } + else + { + FireBolt(); + } + + // Signal a reload + m_bMustReload = true; + + SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration( ACT_VM_PRIMARYATTACK ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::SecondaryAttack( void ) +{ + //NOTENOTE: The zooming is handled by the post/busy frames +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponCrossbow::Reload( void ) +{ + if ( BaseClass::Reload() ) + { + m_bMustReload = false; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::CheckZoomToggle( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer->m_afButtonPressed & IN_ATTACK2 ) + { + ToggleZoom(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::ItemBusyFrame( void ) +{ + // Allow zoom toggling even when we're reloading + CheckZoomToggle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::ItemPostFrame( void ) +{ + // Allow zoom toggling + CheckZoomToggle(); + + if ( m_bMustReload && HasWeaponIdleTimeElapsed() ) + { + Reload(); + } + + BaseClass::ItemPostFrame(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::FireBolt( void ) +{ + if ( m_iClip1 <= 0 ) + { + if ( !m_bFireOnEmpty ) + { + Reload(); + } + else + { + WeaponSound( EMPTY ); + m_flNextPrimaryAttack = 0.15; + } + + return; + } + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + +#ifndef CLIENT_DLL + Vector vecAiming = pOwner->GetAutoaimVector( 0 ); + Vector vecSrc = pOwner->Weapon_ShootPosition(); + + QAngle angAiming; + VectorAngles( vecAiming, angAiming ); + + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecSrc, angAiming, GetHL2MPWpnData().m_iPlayerDamage, pOwner ); + + if ( pOwner->GetWaterLevel() == 3 ) + { + pBolt->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY ); + } + else + { + pBolt->SetAbsVelocity( vecAiming * BOLT_AIR_VELOCITY ); + } + +#endif + + m_iClip1--; + + pOwner->ViewPunch( QAngle( -2, 0, 0 ) ); + + WeaponSound( SINGLE ); + WeaponSound( SPECIAL2 ); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + if ( !m_iClip1 && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + { + // HEV suit - indicate out of ammo condition + pOwner->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + 0.75; + + DoLoadEffect(); + SetChargerState( CHARGER_STATE_DISCHARGE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponCrossbow::Deploy( void ) +{ + if ( m_iClip1 <= 0 ) + { + return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() ); + } + + SetSkin( BOLT_SKIN_GLOW ); + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSwitchingTo - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponCrossbow::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( m_bInZoom ) + { + ToggleZoom(); + } + + SetChargerState( CHARGER_STATE_OFF ); + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::ToggleZoom( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + +#ifndef CLIENT_DLL + + if ( m_bInZoom ) + { + if ( pPlayer->SetFOV( this, 0, 0.2f ) ) + { + m_bInZoom = false; + } + } + else + { + if ( pPlayer->SetFOV( this, 20, 0.1f ) ) + { + m_bInZoom = true; + } + } +#endif +} + +#define BOLT_TIP_ATTACHMENT 2 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::CreateChargerEffects( void ) +{ +#ifndef CLIENT_DLL + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( m_hChargerSprite != NULL ) + return; + + m_hChargerSprite = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE, GetAbsOrigin(), false ); + + if ( m_hChargerSprite ) + { + m_hChargerSprite->SetAttachment( pOwner->GetViewModel(), BOLT_TIP_ATTACHMENT ); + m_hChargerSprite->SetTransparency( kRenderTransAdd, 255, 128, 0, 255, kRenderFxNoDissipation ); + m_hChargerSprite->SetBrightness( 0 ); + m_hChargerSprite->SetScale( 0.1f ); + m_hChargerSprite->TurnOff(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : skinNum - +//----------------------------------------------------------------------------- +void CWeaponCrossbow::SetSkin( int skinNum ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + CBaseViewModel *pViewModel = pOwner->GetViewModel(); + + if ( pViewModel == NULL ) + return; + + pViewModel->m_nSkin = skinNum; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::DoLoadEffect( void ) +{ + SetSkin( BOLT_SKIN_GLOW ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + CBaseViewModel *pViewModel = pOwner->GetViewModel(); + + if ( pViewModel == NULL ) + return; + + CEffectData data; + +#ifdef CLIENT_DLL + data.m_hEntity = pViewModel->GetRefEHandle(); +#else + data.m_nEntIndex = pViewModel->entindex(); +#endif + data.m_nAttachmentIndex = 1; + + DispatchEffect( "CrossbowLoad", data ); + +#ifndef CLIENT_DLL + + CSprite *pBlast = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE2, GetAbsOrigin(), false ); + + if ( pBlast ) + { + pBlast->SetAttachment( pOwner->GetViewModel(), 1 ); + pBlast->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); + pBlast->SetBrightness( 128 ); + pBlast->SetScale( 0.2f ); + pBlast->FadeOutFromSpawn(); + } +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void CWeaponCrossbow::SetChargerState( ChargerState_t state ) +{ + // Make sure we're setup + CreateChargerEffects(); + + // Don't do this twice + if ( state == m_nChargeState ) + return; + + m_nChargeState = state; + + switch( m_nChargeState ) + { + case CHARGER_STATE_START_LOAD: + + WeaponSound( SPECIAL1 ); + + // Shoot some sparks and draw a beam between the two outer points + DoLoadEffect(); + + break; +#ifndef CLIENT_DLL + case CHARGER_STATE_START_CHARGE: + { + if ( m_hChargerSprite == NULL ) + break; + + m_hChargerSprite->SetBrightness( 32, 0.5f ); + m_hChargerSprite->SetScale( 0.025f, 0.5f ); + m_hChargerSprite->TurnOn(); + } + + break; + + case CHARGER_STATE_READY: + { + // Get fully charged + if ( m_hChargerSprite == NULL ) + break; + + m_hChargerSprite->SetBrightness( 80, 1.0f ); + m_hChargerSprite->SetScale( 0.1f, 0.5f ); + m_hChargerSprite->TurnOn(); + } + + break; + + case CHARGER_STATE_DISCHARGE: + { + SetSkin( BOLT_SKIN_NORMAL ); + + if ( m_hChargerSprite == NULL ) + break; + + m_hChargerSprite->SetBrightness( 0 ); + m_hChargerSprite->TurnOff(); + } + + break; +#endif + case CHARGER_STATE_OFF: + { + SetSkin( BOLT_SKIN_NORMAL ); + +#ifndef CLIENT_DLL + if ( m_hChargerSprite == NULL ) + break; + + m_hChargerSprite->SetBrightness( 0 ); + m_hChargerSprite->TurnOff(); +#endif + } + break; + + default: + break; + } +} + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CWeaponCrossbow::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_THROW: + SetChargerState( CHARGER_STATE_START_LOAD ); + break; + + case EVENT_WEAPON_THROW2: + SetChargerState( CHARGER_STATE_START_CHARGE ); + break; + + case EVENT_WEAPON_THROW3: + SetChargerState( CHARGER_STATE_READY ); + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Set the desired activity for the weapon and its viewmodel counterpart +// Input : iActivity - activity to play +//----------------------------------------------------------------------------- +bool CWeaponCrossbow::SendWeaponAnim( int iActivity ) +{ + int newActivity = iActivity; + + // The last shot needs a non-loaded activity + if ( ( newActivity == ACT_VM_IDLE ) && ( m_iClip1 <= 0 ) ) + { + newActivity = ACT_VM_FIDGET; + } + + //For now, just set the ideal activity and be done with it + return BaseClass::SendWeaponAnim( newActivity ); +} diff --git a/game/shared/hl2mp/weapon_crowbar.cpp b/game/shared/hl2mp/weapon_crowbar.cpp new file mode 100644 index 0000000..55a03d1 --- /dev/null +++ b/game/shared/hl2mp/weapon_crowbar.cpp @@ -0,0 +1,228 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Crowbar - an old favorite +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hl2mp/weapon_crowbar.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" +#include "gamerules.h" +#include "ammodef.h" +#include "mathlib/mathlib.h" +#include "in_buttons.h" +#include "vstdlib/random.h" +#include "npcevent.h" + +#if defined( CLIENT_DLL ) + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" + #include "ai_basenpc.h" +#endif + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define CROWBAR_RANGE 75.0f +#define CROWBAR_REFIRE 0.4f + + +//----------------------------------------------------------------------------- +// CWeaponCrowbar +//----------------------------------------------------------------------------- + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCrowbar, DT_WeaponCrowbar ) + +BEGIN_NETWORK_TABLE( CWeaponCrowbar, DT_WeaponCrowbar ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponCrowbar ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_crowbar, CWeaponCrowbar ); +PRECACHE_WEAPON_REGISTER( weapon_crowbar ); + +#ifndef CLIENT_DLL + +acttable_t CWeaponCrowbar::m_acttable[] = +{ + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponCrowbar); + +#endif + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CWeaponCrowbar::CWeaponCrowbar( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CWeaponCrowbar::GetDamageForActivity( Activity hitActivity ) +{ + return 25.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Add in a view kick for this weapon +//----------------------------------------------------------------------------- +void CWeaponCrowbar::AddViewKick( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + QAngle punchAng; + + punchAng.x = SharedRandomFloat( "crowbarpax", 1.0f, 2.0f ); + punchAng.y = SharedRandomFloat( "crowbarpay", -2.0f, -1.0f ); + punchAng.z = 0.0f; + + pPlayer->ViewPunch( punchAng ); +} + + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Animation event handlers +//----------------------------------------------------------------------------- +void CWeaponCrowbar::HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + // Trace up or down based on where the enemy is... + // But only if we're basically facing that direction + Vector vecDirection; + AngleVectors( GetAbsAngles(), &vecDirection ); + + Vector vecEnd; + VectorMA( pOperator->Weapon_ShootPosition(), 50, vecDirection, vecEnd ); + CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, + Vector(-16,-16,-16), Vector(36,36,36), GetDamageForActivity( GetActivity() ), DMG_CLUB, 0.75 ); + + // did I hit someone? + if ( pHurt ) + { + // play sound + WeaponSound( MELEE_HIT ); + + // Fake a trace impact, so the effects work out like a player's crowbaw + trace_t traceHit; + UTIL_TraceLine( pOperator->Weapon_ShootPosition(), pHurt->GetAbsOrigin(), MASK_SHOT_HULL, pOperator, COLLISION_GROUP_NONE, &traceHit ); + ImpactEffect( traceHit ); + } + else + { + WeaponSound( MELEE_MISS ); + } +} + + +//----------------------------------------------------------------------------- +// Animation event +//----------------------------------------------------------------------------- +void CWeaponCrowbar::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_MELEE_HIT: + HandleAnimEventMeleeHit( pEvent, pOperator ); + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +ConVar sk_crowbar_lead_time( "sk_crowbar_lead_time", "0.9" ); + +int CWeaponCrowbar::WeaponMeleeAttack1Condition( float flDot, float flDist ) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity *pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + vecVelocity = pEnemy->GetSmoothedVelocity( ); + + // Project where the enemy will be in a little while + float dt = sk_crowbar_lead_time.GetFloat(); + dt += SharedRandomFloat( "crowbarmelee1", -0.3f, 0.2f ); + if ( dt < 0.0f ) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA( pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos ); + + Vector vecDelta; + VectorSubtract( vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta ); + + if ( fabs( vecDelta.z ) > 70 ) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D( ); + vecDelta.z = 0.0f; + float flExtrapolatedDist = Vector2DNormalize( vecDelta.AsVector2D() ); + if ((flDist > 64) && (flExtrapolatedDist > 64)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + float flExtrapolatedDot = DotProduct2D( vecDelta.AsVector2D(), vecForward.AsVector2D() ); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + +#endif + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrowbar::Drop( const Vector &vecVelocity ) +{ +#ifndef CLIENT_DLL + UTIL_Remove( this ); +#endif +} + +float CWeaponCrowbar::GetRange( void ) +{ + return CROWBAR_RANGE; +} + +float CWeaponCrowbar::GetFireRate( void ) +{ + return CROWBAR_REFIRE; +} + + diff --git a/game/shared/hl2mp/weapon_crowbar.h b/game/shared/hl2mp/weapon_crowbar.h new file mode 100644 index 0000000..2cd10fb --- /dev/null +++ b/game/shared/hl2mp/weapon_crowbar.h @@ -0,0 +1,70 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef HL2MP_WEAPON_CROWBAR_H +#define HL2MP_WEAPON_CROWBAR_H +#pragma once + + +#include "weapon_hl2mpbasehlmpcombatweapon.h" +#include "weapon_hl2mpbasebasebludgeon.h" + + +#ifdef CLIENT_DLL +#define CWeaponCrowbar C_WeaponCrowbar +#endif + +//----------------------------------------------------------------------------- +// CWeaponCrowbar +//----------------------------------------------------------------------------- + +class CWeaponCrowbar : public CBaseHL2MPBludgeonWeapon +{ +public: + DECLARE_CLASS( CWeaponCrowbar, CBaseHL2MPBludgeonWeapon ); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + + CWeaponCrowbar(); + + float GetRange( void ); + float GetFireRate( void ); + + void AddViewKick( void ); + float GetDamageForActivity( Activity hitActivity ); + void SecondaryAttack( void ) { return; } + + void Drop( const Vector &vecVelocity ); + + + // Animation event +#ifndef CLIENT_DLL + virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + void HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#endif + + CWeaponCrowbar( const CWeaponCrowbar & ); + +private: + +}; + + +#endif // HL2MP_WEAPON_CROWBAR_H + diff --git a/game/shared/hl2mp/weapon_frag.cpp b/game/shared/hl2mp/weapon_frag.cpp new file mode 100644 index 0000000..9ddcb81 --- /dev/null +++ b/game/shared/hl2mp/weapon_frag.cpp @@ -0,0 +1,552 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" + #include "c_te_effect_dispatch.h" +#else + #include "hl2mp_player.h" + #include "te_effect_dispatch.h" + #include "grenade_frag.h" +#endif + +#include "weapon_ar2.h" +#include "effect_dispatch_data.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define GRENADE_TIMER 2.5f //Seconds + +#define GRENADE_PAUSED_NO 0 +#define GRENADE_PAUSED_PRIMARY 1 +#define GRENADE_PAUSED_SECONDARY 2 + +#define GRENADE_RADIUS 4.0f // inches + +#define GRENADE_DAMAGE_RADIUS 250.0f + +#ifdef CLIENT_DLL +#define CWeaponFrag C_WeaponFrag +#endif + +//----------------------------------------------------------------------------- +// Fragmentation grenades +//----------------------------------------------------------------------------- +class CWeaponFrag: public CBaseHL2MPCombatWeapon +{ + DECLARE_CLASS( CWeaponFrag, CBaseHL2MPCombatWeapon ); +public: + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponFrag(); + + void Precache( void ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void DecrementAmmo( CBaseCombatCharacter *pOwner ); + void ItemPostFrame( void ); + + bool Deploy( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + + bool Reload( void ); + +#ifndef CLIENT_DLL + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#endif + + void ThrowGrenade( CBasePlayer *pPlayer ); + bool IsPrimed( bool ) { return ( m_AttackPaused != 0 ); } + +private: + + void RollGrenade( CBasePlayer *pPlayer ); + void LobGrenade( CBasePlayer *pPlayer ); + // check a throw from vecSrc. If not valid, move the position back along the line to vecEye + void CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc ); + + CNetworkVar( bool, m_bRedraw ); //Draw the weapon again after throwing a grenade + + CNetworkVar( int, m_AttackPaused ); + CNetworkVar( bool, m_fDrawbackFinished ); + + CWeaponFrag( const CWeaponFrag & ); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif +}; + +#ifndef CLIENT_DLL + +acttable_t CWeaponFrag::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_GRENADE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_GRENADE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_GRENADE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_GRENADE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_GRENADE, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponFrag); + +#endif + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponFrag, DT_WeaponFrag ) + +BEGIN_NETWORK_TABLE( CWeaponFrag, DT_WeaponFrag ) + +#ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bRedraw ) ), + RecvPropBool( RECVINFO( m_fDrawbackFinished ) ), + RecvPropInt( RECVINFO( m_AttackPaused ) ), +#else + SendPropBool( SENDINFO( m_bRedraw ) ), + SendPropBool( SENDINFO( m_fDrawbackFinished ) ), + SendPropInt( SENDINFO( m_AttackPaused ) ), +#endif + +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponFrag ) + DEFINE_PRED_FIELD( m_bRedraw, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_fDrawbackFinished, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_AttackPaused, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_frag, CWeaponFrag ); +PRECACHE_WEAPON_REGISTER(weapon_frag); + +CWeaponFrag::CWeaponFrag( void ) : + CBaseHL2MPCombatWeapon() +{ + m_bRedraw = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponFrag::Precache( void ) +{ + BaseClass::Precache(); + +#ifndef CLIENT_DLL + UTIL_PrecacheOther( "npc_grenade_frag" ); +#endif + + PrecacheScriptSound( "WeaponFrag.Throw" ); + PrecacheScriptSound( "WeaponFrag.Roll" ); +} + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CWeaponFrag::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + bool fThrewGrenade = false; + + switch( pEvent->event ) + { + case EVENT_WEAPON_SEQUENCE_FINISHED: + m_fDrawbackFinished = true; + break; + + case EVENT_WEAPON_THROW: + ThrowGrenade( pOwner ); + DecrementAmmo( pOwner ); + fThrewGrenade = true; + break; + + case EVENT_WEAPON_THROW2: + RollGrenade( pOwner ); + DecrementAmmo( pOwner ); + fThrewGrenade = true; + break; + + case EVENT_WEAPON_THROW3: + LobGrenade( pOwner ); + DecrementAmmo( pOwner ); + fThrewGrenade = true; + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } + +#define RETHROW_DELAY 0.5 + if( fThrewGrenade ) + { + m_flNextPrimaryAttack = gpGlobals->curtime + RETHROW_DELAY; + m_flNextSecondaryAttack = gpGlobals->curtime + RETHROW_DELAY; + m_flTimeWeaponIdle = FLT_MAX; //NOTE: This is set once the animation has finished up! + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponFrag::Deploy( void ) +{ + m_bRedraw = false; + m_fDrawbackFinished = false; + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponFrag::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + m_bRedraw = false; + m_fDrawbackFinished = false; + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponFrag::Reload( void ) +{ + if ( !HasPrimaryAmmo() ) + return false; + + if ( ( m_bRedraw ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) ) + { + //Redraw the weapon + SendWeaponAnim( ACT_VM_DRAW ); + + //Update our times + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); + m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration(); + + //Mark this as done + m_bRedraw = false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponFrag::SecondaryAttack( void ) +{ + if ( m_bRedraw ) + return; + + if ( !HasPrimaryAmmo() ) + return; + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return; + + CBasePlayer *pPlayer = ToBasePlayer( pOwner ); + + if ( pPlayer == NULL ) + return; + + // Note that this is a secondary attack and prepare the grenade attack to pause. + m_AttackPaused = GRENADE_PAUSED_SECONDARY; + SendWeaponAnim( ACT_VM_PULLBACK_LOW ); + + // Don't let weapon idle interfere in the middle of a throw! + m_flTimeWeaponIdle = FLT_MAX; + m_flNextSecondaryAttack = FLT_MAX; + + // If I'm now out of ammo, switch away + if ( !HasPrimaryAmmo() ) + { + pPlayer->SwitchToNextBestWeapon( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponFrag::PrimaryAttack( void ) +{ + if ( m_bRedraw ) + return; + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + { + return; + } + + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );; + + if ( !pPlayer ) + return; + + // Note that this is a primary attack and prepare the grenade attack to pause. + m_AttackPaused = GRENADE_PAUSED_PRIMARY; + SendWeaponAnim( ACT_VM_PULLBACK_HIGH ); + + // Put both of these off indefinitely. We do not know how long + // the player will hold the grenade. + m_flTimeWeaponIdle = FLT_MAX; + m_flNextPrimaryAttack = FLT_MAX; + + // If I'm now out of ammo, switch away + if ( !HasPrimaryAmmo() ) + { + pPlayer->SwitchToNextBestWeapon( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOwner - +//----------------------------------------------------------------------------- +void CWeaponFrag::DecrementAmmo( CBaseCombatCharacter *pOwner ) +{ + pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponFrag::ItemPostFrame( void ) +{ + if( m_fDrawbackFinished ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if (pOwner) + { + switch( m_AttackPaused ) + { + case GRENADE_PAUSED_PRIMARY: + if( !(pOwner->m_nButtons & IN_ATTACK) ) + { + SendWeaponAnim( ACT_VM_THROW ); + m_fDrawbackFinished = false; + } + break; + + case GRENADE_PAUSED_SECONDARY: + if( !(pOwner->m_nButtons & IN_ATTACK2) ) + { + //See if we're ducking + if ( pOwner->m_nButtons & IN_DUCK ) + { + //Send the weapon animation + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + } + else + { + //Send the weapon animation + SendWeaponAnim( ACT_VM_HAULBACK ); + } + + m_fDrawbackFinished = false; + } + break; + + default: + break; + } + } + } + + BaseClass::ItemPostFrame(); + + if ( m_bRedraw ) + { + if ( IsViewModelSequenceFinished() ) + { + Reload(); + } + } +} + + // check a throw from vecSrc. If not valid, move the position back along the line to vecEye +void CWeaponFrag::CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc ) +{ + trace_t tr; + + UTIL_TraceHull( vecEye, vecSrc, -Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2), Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2), + pPlayer->PhysicsSolidMaskForEntity(), pPlayer, pPlayer->GetCollisionGroup(), &tr ); + + if ( tr.DidHit() ) + { + vecSrc = tr.endpos; + } +} + +void DropPrimedFragGrenade( CHL2MP_Player *pPlayer, CBaseCombatWeapon *pGrenade ) +{ + CWeaponFrag *pWeaponFrag = dynamic_cast<CWeaponFrag*>( pGrenade ); + + if ( pWeaponFrag ) + { + pWeaponFrag->ThrowGrenade( pPlayer ); + pWeaponFrag->DecrementAmmo( pPlayer ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +//----------------------------------------------------------------------------- +void CWeaponFrag::ThrowGrenade( CBasePlayer *pPlayer ) +{ +#ifndef CLIENT_DLL + Vector vecEye = pPlayer->EyePosition(); + Vector vForward, vRight; + + pPlayer->EyeVectors( &vForward, &vRight, NULL ); + Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f; + CheckThrowPosition( pPlayer, vecEye, vecSrc ); +// vForward[0] += 0.1f; + vForward[2] += 0.1f; + + Vector vecThrow; + pPlayer->GetVelocity( &vecThrow, NULL ); + vecThrow += vForward * 1200; + CBaseGrenade *pGrenade = Fraggrenade_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(600,random->RandomInt(-1200,1200),0), pPlayer, GRENADE_TIMER, false ); + + if ( pGrenade ) + { + if ( pPlayer && pPlayer->m_lifeState != LIFE_ALIVE ) + { + pPlayer->GetVelocity( &vecThrow, NULL ); + + IPhysicsObject *pPhysicsObject = pGrenade->VPhysicsGetObject(); + if ( pPhysicsObject ) + { + pPhysicsObject->SetVelocity( &vecThrow, NULL ); + } + } + + pGrenade->SetDamage( GetHL2MPWpnData().m_iPlayerDamage ); + pGrenade->SetDamageRadius( GRENADE_DAMAGE_RADIUS ); + } +#endif + + m_bRedraw = true; + + WeaponSound( SINGLE ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +//----------------------------------------------------------------------------- +void CWeaponFrag::LobGrenade( CBasePlayer *pPlayer ) +{ +#ifndef CLIENT_DLL + Vector vecEye = pPlayer->EyePosition(); + Vector vForward, vRight; + + pPlayer->EyeVectors( &vForward, &vRight, NULL ); + Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f + Vector( 0, 0, -8 ); + CheckThrowPosition( pPlayer, vecEye, vecSrc ); + + Vector vecThrow; + pPlayer->GetVelocity( &vecThrow, NULL ); + vecThrow += vForward * 350 + Vector( 0, 0, 50 ); + CBaseGrenade *pGrenade = Fraggrenade_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(200,random->RandomInt(-600,600),0), pPlayer, GRENADE_TIMER, false ); + + if ( pGrenade ) + { + pGrenade->SetDamage( GetHL2MPWpnData().m_iPlayerDamage ); + pGrenade->SetDamageRadius( GRENADE_DAMAGE_RADIUS ); + } +#endif + + WeaponSound( WPN_DOUBLE ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_bRedraw = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +//----------------------------------------------------------------------------- +void CWeaponFrag::RollGrenade( CBasePlayer *pPlayer ) +{ +#ifndef CLIENT_DLL + // BUGBUG: Hardcoded grenade width of 4 - better not change the model :) + Vector vecSrc; + pPlayer->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecSrc ); + vecSrc.z += GRENADE_RADIUS; + + Vector vecFacing = pPlayer->BodyDirection2D( ); + // no up/down direction + vecFacing.z = 0; + VectorNormalize( vecFacing ); + trace_t tr; + UTIL_TraceLine( vecSrc, vecSrc - Vector(0,0,16), MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction != 1.0 ) + { + // compute forward vec parallel to floor plane and roll grenade along that + Vector tangent; + CrossProduct( vecFacing, tr.plane.normal, tangent ); + CrossProduct( tr.plane.normal, tangent, vecFacing ); + } + vecSrc += (vecFacing * 18.0); + CheckThrowPosition( pPlayer, pPlayer->WorldSpaceCenter(), vecSrc ); + + Vector vecThrow; + pPlayer->GetVelocity( &vecThrow, NULL ); + vecThrow += vecFacing * 700; + // put it on its side + QAngle orientation(0,pPlayer->GetLocalAngles().y,-90); + // roll it + AngularImpulse rotSpeed(0,0,720); + CBaseGrenade *pGrenade = Fraggrenade_Create( vecSrc, orientation, vecThrow, rotSpeed, pPlayer, GRENADE_TIMER, false ); + + if ( pGrenade ) + { + pGrenade->SetDamage( GetHL2MPWpnData().m_iPlayerDamage ); + pGrenade->SetDamageRadius( GRENADE_DAMAGE_RADIUS ); + } + +#endif + + WeaponSound( SPECIAL1 ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_bRedraw = true; +} + diff --git a/game/shared/hl2mp/weapon_hl2mpbase.cpp b/game/shared/hl2mp/weapon_hl2mpbase.cpp new file mode 100644 index 0000000..15995a5 --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbase.cpp @@ -0,0 +1,330 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "in_buttons.h" +#include "takedamageinfo.h" +#include "ammodef.h" +#include "hl2mp_gamerules.h" + + +#ifdef CLIENT_DLL +extern IVModelInfoClient* modelinfo; +#else +extern IVModelInfo* modelinfo; +#endif + + +#if defined( CLIENT_DLL ) + + #include "vgui/ISurface.h" + #include "vgui_controls/Controls.h" + #include "c_hl2mp_player.h" + #include "hud_crosshair.h" + +#else + + #include "hl2mp_player.h" + #include "vphysics/constraints.h" + +#endif + +#include "weapon_hl2mpbase.h" + + +// ----------------------------------------------------------------------------- // +// Global functions. +// ----------------------------------------------------------------------------- // + +bool IsAmmoType( int iAmmoType, const char *pAmmoName ) +{ + return GetAmmoDef()->Index( pAmmoName ) == iAmmoType; +} + +static const char * s_WeaponAliasInfo[] = +{ + "none", // WEAPON_NONE = 0, + + //Melee + "shotgun", //WEAPON_AMERKNIFE, + + NULL, // end of list marker +}; + + +// ----------------------------------------------------------------------------- // +// CWeaponHL2MPBase tables. +// ----------------------------------------------------------------------------- // + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponHL2MPBase, DT_WeaponHL2MPBase ) + +BEGIN_NETWORK_TABLE( CWeaponHL2MPBase, DT_WeaponHL2MPBase ) + +#ifdef CLIENT_DLL + +#else + // world weapon models have no aminations + // SendPropExclude( "DT_AnimTimeMustBeFirst", "m_flAnimTime" ), +// SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), +// SendPropExclude( "DT_LocalActiveWeaponData", "m_flTimeWeaponIdle" ), +#endif + +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponHL2MPBase ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_hl2mp_base, CWeaponHL2MPBase ); + + +#ifdef GAME_DLL + + BEGIN_DATADESC( CWeaponHL2MPBase ) + + END_DATADESC() + +#endif + +// ----------------------------------------------------------------------------- // +// CWeaponHL2MPBase implementation. +// ----------------------------------------------------------------------------- // +CWeaponHL2MPBase::CWeaponHL2MPBase() +{ + SetPredictionEligible( true ); + AddSolidFlags( FSOLID_TRIGGER ); // Nothing collides with these but it gets touches. + + m_flNextResetCheckTime = 0.0f; +} + + +bool CWeaponHL2MPBase::IsPredicted() const +{ + return true; +} + +void CWeaponHL2MPBase::WeaponSound( WeaponSound_t sound_type, float soundtime /* = 0.0f */ ) +{ +#ifdef CLIENT_DLL + + // If we have some sounds from the weapon classname.txt file, play a random one of them + const char *shootsound = GetWpnData().aShootSounds[ sound_type ]; + if ( !shootsound || !shootsound[0] ) + return; + + CBroadcastRecipientFilter filter; // this is client side only + if ( !te->CanPredict() ) + return; + + CBaseEntity::EmitSound( filter, GetPlayerOwner()->entindex(), shootsound, &GetPlayerOwner()->GetAbsOrigin() ); +#else + BaseClass::WeaponSound( sound_type, soundtime ); +#endif +} + + +CBasePlayer* CWeaponHL2MPBase::GetPlayerOwner() const +{ + return dynamic_cast< CBasePlayer* >( GetOwner() ); +} + +CHL2MP_Player* CWeaponHL2MPBase::GetHL2MPPlayerOwner() const +{ + return dynamic_cast< CHL2MP_Player* >( GetOwner() ); +} + +#ifdef CLIENT_DLL + +void CWeaponHL2MPBase::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + if ( GetPredictable() && !ShouldPredict() ) + ShutdownPredictable(); +} + + +bool CWeaponHL2MPBase::ShouldPredict() +{ + if ( GetOwner() && GetOwner() == C_BasePlayer::GetLocalPlayer() ) + return true; + + return BaseClass::ShouldPredict(); +} + + +#else + +void CWeaponHL2MPBase::Spawn() +{ + BaseClass::Spawn(); + + // Set this here to allow players to shoot dropped weapons + SetCollisionGroup( COLLISION_GROUP_WEAPON ); +} + +void CWeaponHL2MPBase::Materialize( void ) +{ + if ( IsEffectActive( EF_NODRAW ) ) + { + // changing from invisible state to visible. + EmitSound( "AlyxEmp.Charge" ); + + RemoveEffects( EF_NODRAW ); + DoMuzzleFlash(); + } + + if ( HasSpawnFlags( SF_NORESPAWN ) == false ) + { + VPhysicsInitNormal( SOLID_BBOX, GetSolidFlags() | FSOLID_TRIGGER, false ); + SetMoveType( MOVETYPE_VPHYSICS ); + + HL2MPRules()->AddLevelDesignerPlacedObject( this ); + } + + if ( HasSpawnFlags( SF_NORESPAWN ) == false ) + { + if ( GetOriginalSpawnOrigin() == vec3_origin ) + { + m_vOriginalSpawnOrigin = GetAbsOrigin(); + m_vOriginalSpawnAngles = GetAbsAngles(); + } + } + + SetPickupTouch(); + + SetThink (NULL); +} + +int CWeaponHL2MPBase::ObjectCaps() +{ + return BaseClass::ObjectCaps() & ~FCAP_IMPULSE_USE; +} + +#endif + +void CWeaponHL2MPBase::FallInit( void ) +{ +#ifndef CLIENT_DLL + SetModel( GetWorldModel() ); + VPhysicsDestroyObject(); + + if ( HasSpawnFlags( SF_NORESPAWN ) == false ) + { + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_TRIGGER ); + + UTIL_DropToFloor( this, MASK_SOLID ); + } + else + { + if ( !VPhysicsInitNormal( SOLID_BBOX, GetSolidFlags() | FSOLID_TRIGGER, false ) ) + { + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_TRIGGER ); + } + else + { + #if !defined( CLIENT_DLL ) + // Constrained start? + if ( HasSpawnFlags( SF_WEAPON_START_CONSTRAINED ) ) + { + //Constrain the weapon in place + IPhysicsObject *pReferenceObject, *pAttachedObject; + + pReferenceObject = g_PhysWorldObject; + pAttachedObject = VPhysicsGetObject(); + + if ( pReferenceObject && pAttachedObject ) + { + constraint_fixedparams_t fixed; + fixed.Defaults(); + fixed.InitWithCurrentObjectState( pReferenceObject, pAttachedObject ); + + fixed.constraint.forceLimit = lbs2kg( 10000 ); + fixed.constraint.torqueLimit = lbs2kg( 10000 ); + + IPhysicsConstraint *pConstraint = GetConstraint(); + + pConstraint = physenv->CreateFixedConstraint( pReferenceObject, pAttachedObject, NULL, fixed ); + + pConstraint->SetGameData( (void *) this ); + } + } + #endif //CLIENT_DLL + } + } + + SetPickupTouch(); + + SetThink( &CBaseCombatWeapon::FallThink ); + + SetNextThink( gpGlobals->curtime + 0.1f ); + +#endif +} + +const CHL2MPSWeaponInfo &CWeaponHL2MPBase::GetHL2MPWpnData() const +{ + const FileWeaponInfo_t *pWeaponInfo = &GetWpnData(); + const CHL2MPSWeaponInfo *pHL2MPInfo; + + #ifdef _DEBUG + pHL2MPInfo = dynamic_cast< const CHL2MPSWeaponInfo* >( pWeaponInfo ); + Assert( pHL2MPInfo ); + #else + pHL2MPInfo = static_cast< const CHL2MPSWeaponInfo* >( pWeaponInfo ); + #endif + + return *pHL2MPInfo; +} +void CWeaponHL2MPBase::FireBullets( const FireBulletsInfo_t &info ) +{ + FireBulletsInfo_t modinfo = info; + + modinfo.m_iPlayerDamage = GetHL2MPWpnData().m_iPlayerDamage; + + BaseClass::FireBullets( modinfo ); +} + + +#if defined( CLIENT_DLL ) + +#include "c_te_effect_dispatch.h" + +#define NUM_MUZZLE_FLASH_TYPES 4 + +bool CWeaponHL2MPBase::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); +} + + +void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ) +{ + QAngle final = in + punch; + + //Clip each component + for ( int i = 0; i < 3; i++ ) + { + if ( final[i] > clip[i] ) + { + final[i] = clip[i]; + } + else if ( final[i] < -clip[i] ) + { + final[i] = -clip[i]; + } + + //Return the result + in[i] = final[i] - punch[i]; + } +} + +#endif + diff --git a/game/shared/hl2mp/weapon_hl2mpbase.h b/game/shared/hl2mp/weapon_hl2mpbase.h new file mode 100644 index 0000000..0f21044 --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbase.h @@ -0,0 +1,93 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_HL2MPBASE_H +#define WEAPON_HL2MPBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "hl2mp_player_shared.h" +#include "basecombatweapon_shared.h" +#include "hl2mp_weapon_parse.h" + +#if defined( CLIENT_DLL ) + #define CWeaponHL2MPBase C_WeaponHL2MPBase + void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ); +#endif + +class CHL2MP_Player; + +// These are the names of the ammo types that go in the CAmmoDefs and that the +// weapon script files reference. + +// Given an ammo type (like from a weapon's GetPrimaryAmmoType()), this compares it +// against the ammo name you specify. +// MIKETODO: this should use indexing instead of searching and strcmp()'ing all the time. +bool IsAmmoType( int iAmmoType, const char *pAmmoName ); + +class CWeaponHL2MPBase : public CBaseCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponHL2MPBase, CBaseCombatWeapon ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponHL2MPBase(); + + #ifdef GAME_DLL + DECLARE_DATADESC(); + + void SendReloadSoundEvent( void ); + + void Materialize( void ); + virtual int ObjectCaps( void ); + #endif + + // All predicted weapons need to implement and return true + virtual bool IsPredicted() const; + + CBasePlayer* GetPlayerOwner() const; + CHL2MP_Player* GetHL2MPPlayerOwner() const; + + void WeaponSound( WeaponSound_t sound_type, float soundtime = 0.0f ); + + CHL2MPSWeaponInfo const &GetHL2MPWpnData() const; + + + virtual void FireBullets( const FireBulletsInfo_t &info ); + virtual void FallInit( void ); + +public: + #if defined( CLIENT_DLL ) + + virtual bool ShouldPredict(); + virtual void OnDataChanged( DataUpdateType_t type ); + + virtual bool OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ); + + #else + + virtual void Spawn(); + + #endif + + float m_flPrevAnimTime; + float m_flNextResetCheckTime; + + Vector GetOriginalSpawnOrigin( void ) { return m_vOriginalSpawnOrigin; } + QAngle GetOriginalSpawnAngles( void ) { return m_vOriginalSpawnAngles; } + +private: + + CWeaponHL2MPBase( const CWeaponHL2MPBase & ); + + Vector m_vOriginalSpawnOrigin; + QAngle m_vOriginalSpawnAngles; +}; + + +#endif // WEAPON_HL2MPBASE_H diff --git a/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp b/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp new file mode 100644 index 0000000..b5a0040 --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp @@ -0,0 +1,242 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#if defined( CLIENT_DLL ) + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" +#endif + +#include "weapon_hl2mpbase_machinegun.h" +#include "in_buttons.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_NETWORKCLASS_ALIASED( HL2MPMachineGun, DT_HL2MPMachineGun ) + +BEGIN_NETWORK_TABLE( CHL2MPMachineGun, DT_HL2MPMachineGun ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CHL2MPMachineGun ) +END_PREDICTION_DATA() + +//========================================================= +// >> CHLSelectFireMachineGun +//========================================================= +BEGIN_DATADESC( CHL2MPMachineGun ) + + DEFINE_FIELD( m_nShotsFired, FIELD_INTEGER ), + DEFINE_FIELD( m_flNextSoundTime, FIELD_TIME ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHL2MPMachineGun::CHL2MPMachineGun( void ) +{ +} + +const Vector &CHL2MPMachineGun::GetBulletSpread( void ) +{ + static Vector cone = VECTOR_CONE_3DEGREES; + return cone; +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CHL2MPMachineGun::PrimaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (!pPlayer) + return; + + // Abort here to handle burst and auto fire modes + if ( (UsesClipsForAmmo1() && m_iClip1 == 0) || ( !UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType) ) ) + return; + + m_nShotsFired++; + + pPlayer->DoMuzzleFlash(); + + // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, + // especially if the weapon we're firing has a really fast rate of fire. + int iBulletsToFire = 0; + float fireRate = GetFireRate(); + + while ( m_flNextPrimaryAttack <= gpGlobals->curtime ) + { + // MUST call sound before removing a round from the clip of a CHLMachineGun + WeaponSound(SINGLE, m_flNextPrimaryAttack); + m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; + iBulletsToFire++; + } + + // Make sure we don't fire more than the amount in the clip, if this weapon uses clips + if ( UsesClipsForAmmo1() ) + { + if ( iBulletsToFire > m_iClip1 ) + iBulletsToFire = m_iClip1; + m_iClip1 -= iBulletsToFire; + } + + CHL2MP_Player *pHL2MPPlayer = ToHL2MPPlayer( pPlayer ); + + // Fire the bullets + FireBulletsInfo_t info; + info.m_iShots = iBulletsToFire; + info.m_vecSrc = pHL2MPPlayer->Weapon_ShootPosition( ); + info.m_vecDirShooting = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + info.m_vecSpread = pHL2MPPlayer->GetAttackSpread( this ); + info.m_flDistance = MAX_TRACE_LENGTH; + info.m_iAmmoType = m_iPrimaryAmmoType; + info.m_iTracerFreq = 2; + FireBullets( info ); + + //Factor in the view kick + AddViewKick(); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + SendWeaponAnim( GetPrimaryAttackActivity() ); + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &info - +//----------------------------------------------------------------------------- +void CHL2MPMachineGun::FireBullets( const FireBulletsInfo_t &info ) +{ + if(CBasePlayer *pPlayer = ToBasePlayer ( GetOwner() ) ) + { + pPlayer->FireBullets(info); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPMachineGun::DoMachineGunKick( CBasePlayer *pPlayer, float dampEasy, float maxVerticleKickAngle, float fireDurationTime, float slideLimitTime ) +{ + #define KICK_MIN_X 0.2f //Degrees + #define KICK_MIN_Y 0.2f //Degrees + #define KICK_MIN_Z 0.1f //Degrees + + QAngle vecScratch; + int iSeed = CBaseEntity::GetPredictionRandomSeed() & 255; + + //Find how far into our accuracy degradation we are + float duration = ( fireDurationTime > slideLimitTime ) ? slideLimitTime : fireDurationTime; + float kickPerc = duration / slideLimitTime; + + // do this to get a hard discontinuity, clear out anything under 10 degrees punch + pPlayer->ViewPunchReset( 10 ); + + //Apply this to the view angles as well + vecScratch.x = -( KICK_MIN_X + ( maxVerticleKickAngle * kickPerc ) ); + vecScratch.y = -( KICK_MIN_Y + ( maxVerticleKickAngle * kickPerc ) ) / 3; + vecScratch.z = KICK_MIN_Z + ( maxVerticleKickAngle * kickPerc ) / 8; + + RandomSeed( iSeed ); + + //Wibble left and right + if ( RandomInt( -1, 1 ) >= 0 ) + vecScratch.y *= -1; + + iSeed++; + + //Wobble up and down + if ( RandomInt( -1, 1 ) >= 0 ) + vecScratch.z *= -1; + + //Clip this to our desired min/max + UTIL_ClipPunchAngleOffset( vecScratch, pPlayer->m_Local.m_vecPunchAngle, QAngle( 24.0f, 3.0f, 1.0f ) ); + + //Add it to the view punch + // NOTE: 0.5 is just tuned to match the old effect before the punch became simulated + pPlayer->ViewPunch( vecScratch * 0.5 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Reset our shots fired +//----------------------------------------------------------------------------- +bool CHL2MPMachineGun::Deploy( void ) +{ + m_nShotsFired = 0; + + return BaseClass::Deploy(); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Make enough sound events to fill the estimated think interval +// returns: number of shots needed +//----------------------------------------------------------------------------- +int CHL2MPMachineGun::WeaponSoundRealtime( WeaponSound_t shoot_type ) +{ + int numBullets = 0; + + // ran out of time, clamp to current + if (m_flNextSoundTime < gpGlobals->curtime) + { + m_flNextSoundTime = gpGlobals->curtime; + } + + // make enough sound events to fill up the next estimated think interval + float dt = clamp( m_flAnimTime - m_flPrevAnimTime, 0, 0.2 ); + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound( SINGLE_NPC, m_flNextSoundTime ); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound( SINGLE_NPC, m_flNextSoundTime ); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + + return numBullets; +} + + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPMachineGun::ItemPostFrame( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + // Debounce the recoiling counter + if ( ( pOwner->m_nButtons & IN_ATTACK ) == false ) + { + m_nShotsFired = 0; + } + + BaseClass::ItemPostFrame(); +} + + diff --git a/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h b/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h new file mode 100644 index 0000000..c7f9a56 --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "weapon_hl2mpbase.h" + +#ifndef BASEHLCOMBATWEAPON_H +#define BASEHLCOMBATWEAPON_H +#ifdef _WIN32 +#pragma once +#endif + +#if defined( CLIENT_DLL ) + #define CHL2MPMachineGun C_HL2MPMachineGun +#endif + +//========================================================= +// Machine gun base class +//========================================================= +class CHL2MPMachineGun : public CWeaponHL2MPBase +{ +public: + DECLARE_CLASS( CHL2MPMachineGun, CWeaponHL2MPBase ); + DECLARE_DATADESC(); + + CHL2MPMachineGun(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + void PrimaryAttack( void ); + + // Default calls through to m_hOwner, but plasma weapons can override and shoot projectiles here. + virtual void ItemPostFrame( void ); + virtual void FireBullets( const FireBulletsInfo_t &info ); + virtual bool Deploy( void ); + + virtual const Vector &GetBulletSpread( void ); + + int WeaponSoundRealtime( WeaponSound_t shoot_type ); + + // utility function + static void DoMachineGunKick( CBasePlayer *pPlayer, float dampEasy, float maxVerticleKickAngle, float fireDurationTime, float slideLimitTime ); + +private: + + CHL2MPMachineGun( const CHL2MPMachineGun & ); + +protected: + + int m_nShotsFired; // Number of consecutive shots fired + + float m_flNextSoundTime; // real-time clock of when to make next sound +}; + +#endif // BASEHLCOMBATWEAPON_H diff --git a/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp b/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp new file mode 100644 index 0000000..21373ca --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp @@ -0,0 +1,363 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_hl2mpbasebasebludgeon.h" +#include "gamerules.h" +#include "ammodef.h" +#include "mathlib/mathlib.h" +#include "in_buttons.h" +#include "animation.h" + +#if defined( CLIENT_DLL ) + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" + #include "ndebugoverlay.h" + #include "te_effect_dispatch.h" + #include "ilagcompensationmanager.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_NETWORKCLASS_ALIASED( BaseHL2MPBludgeonWeapon, DT_BaseHL2MPBludgeonWeapon ) + +BEGIN_NETWORK_TABLE( CBaseHL2MPBludgeonWeapon, DT_BaseHL2MPBludgeonWeapon ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CBaseHL2MPBludgeonWeapon ) +END_PREDICTION_DATA() + +#define BLUDGEON_HULL_DIM 16 + +static const Vector g_bludgeonMins(-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM); +static const Vector g_bludgeonMaxs(BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM); + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CBaseHL2MPBludgeonWeapon::CBaseHL2MPBludgeonWeapon() +{ + m_bFiresUnderwater = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Spawn the weapon +//----------------------------------------------------------------------------- +void CBaseHL2MPBludgeonWeapon::Spawn( void ) +{ + m_fMinRange1 = 0; + m_fMinRange2 = 0; + m_fMaxRange1 = 64; + m_fMaxRange2 = 64; + //Call base class first + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Precache the weapon +//----------------------------------------------------------------------------- +void CBaseHL2MPBludgeonWeapon::Precache( void ) +{ + //Call base class first + BaseClass::Precache(); +} + +//------------------------------------------------------------------------------ +// Purpose : Update weapon +//------------------------------------------------------------------------------ +void CBaseHL2MPBludgeonWeapon::ItemPostFrame( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime) ) + { + PrimaryAttack(); + } + else if ( (pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime) ) + { + SecondaryAttack(); + } + else + { + WeaponIdle(); + return; + } +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHL2MPBludgeonWeapon::PrimaryAttack() +{ + +#ifndef CLIENT_DLL + CHL2MP_Player *pPlayer = ToHL2MPPlayer( GetPlayerOwner() ); + // Move other players back to history positions based on local player's lag + lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() ); +#endif + Swing( false ); +#ifndef CLIENT_DLL + // Move other players back to history positions based on local player's lag + lagcompensation->FinishLagCompensation( pPlayer ); +#endif + +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CBaseHL2MPBludgeonWeapon::SecondaryAttack() +{ + Swing( true ); +} + + +//------------------------------------------------------------------------------ +// Purpose: Implement impact function +//------------------------------------------------------------------------------ +void CBaseHL2MPBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + //Do view kick +// AddViewKick(); + + CBaseEntity *pHitEntity = traceHit.m_pEnt; + + //Apply damage to a hit target + if ( pHitEntity != NULL ) + { + Vector hitDirection; + pPlayer->EyeVectors( &hitDirection, NULL, NULL ); + VectorNormalize( hitDirection ); + +#ifndef CLIENT_DLL + CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); + + if( pPlayer && pHitEntity->IsNPC() ) + { + // If bonking an NPC, adjust damage. + info.AdjustPlayerDamageInflictedForSkillLevel(); + } + + CalculateMeleeDamageForce( &info, hitDirection, traceHit.endpos ); + + pHitEntity->DispatchTraceAttack( info, hitDirection, &traceHit ); + ApplyMultiDamage(); + + // Now hit all triggers along the ray that... + TraceAttackToTriggers( info, traceHit.startpos, traceHit.endpos, hitDirection ); +#endif + WeaponSound( MELEE_HIT ); + } + + // Apply an impact effect + ImpactEffect( traceHit ); +} + +Activity CBaseHL2MPBludgeonWeapon::ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ) +{ + int i, j, k; + float distance; + const float *minmaxs[2] = {mins.Base(), maxs.Base()}; + trace_t tmpTrace; + Vector vecHullEnd = hitTrace.endpos; + Vector vecEnd; + + distance = 1e6f; + Vector vecSrc = hitTrace.startpos; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tmpTrace ); + if ( tmpTrace.fraction == 1.0 ) + { + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tmpTrace ); + if ( tmpTrace.fraction < 1.0 ) + { + float thisDistance = (tmpTrace.endpos - vecSrc).Length(); + if ( thisDistance < distance ) + { + hitTrace = tmpTrace; + distance = thisDistance; + } + } + } + } + } + } + else + { + hitTrace = tmpTrace; + } + + + return ACT_VM_HITCENTER; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &traceHit - +//----------------------------------------------------------------------------- +bool CBaseHL2MPBludgeonWeapon::ImpactWater( const Vector &start, const Vector &end ) +{ + //FIXME: This doesn't handle the case of trying to splash while being underwater, but that's not going to look good + // right now anyway... + + // We must start outside the water + if ( UTIL_PointContents( start ) & (CONTENTS_WATER|CONTENTS_SLIME)) + return false; + + // We must end inside of water + if ( !(UTIL_PointContents( end ) & (CONTENTS_WATER|CONTENTS_SLIME))) + return false; + + trace_t waterTrace; + + UTIL_TraceLine( start, end, (CONTENTS_WATER|CONTENTS_SLIME), GetOwner(), COLLISION_GROUP_NONE, &waterTrace ); + + if ( waterTrace.fraction < 1.0f ) + { +#ifndef CLIENT_DLL + CEffectData data; + + data.m_fFlags = 0; + data.m_vOrigin = waterTrace.endpos; + data.m_vNormal = waterTrace.plane.normal; + data.m_flScale = 8.0f; + + // See if we hit slime + if ( waterTrace.contents & CONTENTS_SLIME ) + { + data.m_fFlags |= FX_WATER_IN_SLIME; + } + + DispatchEffect( "watersplash", data ); +#endif + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHL2MPBludgeonWeapon::ImpactEffect( trace_t &traceHit ) +{ + // See if we hit water (we don't do the other impact effects in this case) + if ( ImpactWater( traceHit.startpos, traceHit.endpos ) ) + return; + + //FIXME: need new decals + UTIL_ImpactTrace( &traceHit, DMG_CLUB ); +} + + +//------------------------------------------------------------------------------ +// Purpose : Starts the swing of the weapon and determines the animation +// Input : bIsSecondary - is this a secondary attack? +//------------------------------------------------------------------------------ +void CBaseHL2MPBludgeonWeapon::Swing( int bIsSecondary ) +{ + trace_t traceHit; + + // Try a ray + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !pOwner ) + return; + + Vector swingStart = pOwner->Weapon_ShootPosition( ); + Vector forward; + + pOwner->EyeVectors( &forward, NULL, NULL ); + + Vector swingEnd = swingStart + forward * GetRange(); + UTIL_TraceLine( swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); + Activity nHitActivity = ACT_VM_HITCENTER; + +#ifndef CLIENT_DLL + // Like bullets, bludgeon traces have to trace against triggers. + CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); + TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, vec3_origin ); +#endif + + if ( traceHit.fraction == 1.0 ) + { + float bludgeonHullRadius = 1.732f * BLUDGEON_HULL_DIM; // hull is +/- 16, so use cuberoot of 2 to determine how big the hull is from center to the corner point + + // Back off by hull "radius" + swingEnd -= forward * bludgeonHullRadius; + + UTIL_TraceHull( swingStart, swingEnd, g_bludgeonMins, g_bludgeonMaxs, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); + if ( traceHit.fraction < 1.0 && traceHit.m_pEnt ) + { + Vector vecToTarget = traceHit.m_pEnt->GetAbsOrigin() - swingStart; + VectorNormalize( vecToTarget ); + + float dot = vecToTarget.Dot( forward ); + + // YWB: Make sure they are sort of facing the guy at least... + if ( dot < 0.70721f ) + { + // Force amiss + traceHit.fraction = 1.0f; + } + else + { + nHitActivity = ChooseIntersectionPointAndActivity( traceHit, g_bludgeonMins, g_bludgeonMaxs, pOwner ); + } + } + } + + WeaponSound( SINGLE ); + + // ------------------------- + // Miss + // ------------------------- + if ( traceHit.fraction == 1.0f ) + { + nHitActivity = bIsSecondary ? ACT_VM_MISSCENTER2 : ACT_VM_MISSCENTER; + + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + // See if we happened to hit water + ImpactWater( swingStart, testEnd ); + } + else + { + Hit( traceHit, nHitActivity ); + } + + // Send the anim + SendWeaponAnim( nHitActivity ); + + pOwner->SetAnimation( PLAYER_ATTACK1 ); + + //Setup our next attack times + m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate(); + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); +} diff --git a/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h b/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h new file mode 100644 index 0000000..d268b32 --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h @@ -0,0 +1,66 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The class from which all bludgeon melee +// weapons are derived. +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#ifndef BASEBLUDGEONWEAPON_H +#define BASEBLUDGEONWEAPON_H + +#ifdef _WIN32 +#pragma once +#endif + + +#if defined( CLIENT_DLL ) +#define CBaseHL2MPBludgeonWeapon C_BaseHL2MPBludgeonWeapon +#endif + +//========================================================= +// CBaseHLBludgeonWeapon +//========================================================= +class CBaseHL2MPBludgeonWeapon : public CBaseHL2MPCombatWeapon +{ + DECLARE_CLASS( CBaseHL2MPBludgeonWeapon, CBaseHL2MPCombatWeapon ); +public: + CBaseHL2MPBludgeonWeapon(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + virtual void Spawn( void ); + virtual void Precache( void ); + + //Attack functions + virtual void PrimaryAttack( void ); + virtual void SecondaryAttack( void ); + + virtual void ItemPostFrame( void ); + + //Functions to select animation sequences + virtual Activity GetPrimaryAttackActivity( void ) { return ACT_VM_HITCENTER; } + virtual Activity GetSecondaryAttackActivity( void ) { return ACT_VM_HITCENTER2; } + + virtual float GetFireRate( void ) { return 0.2f; } + virtual float GetRange( void ) { return 32.0f; } + virtual float GetDamageForActivity( Activity hitActivity ) { return 1.0f; } + + CBaseHL2MPBludgeonWeapon( const CBaseHL2MPBludgeonWeapon & ); + +protected: + virtual void ImpactEffect( trace_t &trace ); + +private: + bool ImpactWater( const Vector &start, const Vector &end ); + void Swing( int bIsSecondary ); + void Hit( trace_t &traceHit, Activity nHitActivity ); + Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); +}; + +#endif
\ No newline at end of file diff --git a/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp b/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp new file mode 100644 index 0000000..361a53f --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp @@ -0,0 +1,416 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#include "hl2mp_player_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( basehl2mpcombatweapon, CBaseHL2MPCombatWeapon ); + +IMPLEMENT_NETWORKCLASS_ALIASED( BaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) + +BEGIN_NETWORK_TABLE( CBaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) +#if !defined( CLIENT_DLL ) +// SendPropInt( SENDINFO( m_bReflectViewModelAnimations ), 1, SPROP_UNSIGNED ), +#else +// RecvPropInt( RECVINFO( m_bReflectViewModelAnimations ) ), +#endif +END_NETWORK_TABLE() + + +#if !defined( CLIENT_DLL ) + +#include "globalstate.h" + +//--------------------------------------------------------- +// Save/Restore +//--------------------------------------------------------- +BEGIN_DATADESC( CBaseHL2MPCombatWeapon ) + + DEFINE_FIELD( m_bLowered, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flRaiseTime, FIELD_TIME ), + DEFINE_FIELD( m_flHolsterTime, FIELD_TIME ), + +END_DATADESC() + +#endif + +BEGIN_PREDICTION_DATA( CBaseHL2MPCombatWeapon ) +END_PREDICTION_DATA() + +extern ConVar sk_auto_reload_time; + +CBaseHL2MPCombatWeapon::CBaseHL2MPCombatWeapon( void ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::ItemHolsterFrame( void ) +{ + BaseClass::ItemHolsterFrame(); + + // Must be player held + if ( GetOwner() && GetOwner()->IsPlayer() == false ) + return; + + // We can't be active + if ( GetOwner()->GetActiveWeapon() == this ) + return; + + // If it's been longer than three seconds, reload + if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() ) + { + // Just load the clip with no animations + FinishReload(); + m_flHolsterTime = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Drops the weapon into a lowered pose +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Lower( void ) +{ + //Don't bother if we don't have the animation + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) + return false; + + m_bLowered = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Brings the weapon up to the ready position +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Ready( void ) +{ + //Don't bother if we don't have the animation + if ( SelectWeightedSequence( ACT_VM_LOWERED_TO_IDLE ) == ACTIVITY_NOT_AVAILABLE ) + return false; + + m_bLowered = false; + m_flRaiseTime = gpGlobals->curtime + 0.5f; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Deploy( void ) +{ + // If we should be lowered, deploy in the lowered position + // We have to ask the player if the last time it checked, the weapon was lowered + if ( GetOwner() && GetOwner()->IsPlayer() ) + { + CHL2MP_Player *pPlayer = assert_cast<CHL2MP_Player*>( GetOwner() ); + if ( pPlayer->IsWeaponLowered() ) + { + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) != ACTIVITY_NOT_AVAILABLE ) + { + if ( DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_IDLE_LOWERED, (char*)GetAnimPrefix() ) ) + { + m_bLowered = true; + + // Stomp the next attack time to fix the fact that the lower idles are long + pPlayer->SetNextAttack( gpGlobals->curtime + 1.0 ); + m_flNextPrimaryAttack = gpGlobals->curtime + 1.0; + m_flNextSecondaryAttack = gpGlobals->curtime + 1.0; + return true; + } + } + } + } + + m_bLowered = false; + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( BaseClass::Holster( pSwitchingTo ) ) + { + SetWeaponVisible( false ); + m_flHolsterTime = gpGlobals->curtime; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::WeaponShouldBeLowered( void ) +{ + // Can't be in the middle of another animation + if ( GetIdealActivity() != ACT_VM_IDLE_LOWERED && GetIdealActivity() != ACT_VM_IDLE && + GetIdealActivity() != ACT_VM_IDLE_TO_LOWERED && GetIdealActivity() != ACT_VM_LOWERED_TO_IDLE ) + return false; + + if ( m_bLowered ) + return true; + +#if !defined( CLIENT_DLL ) + + if ( GlobalEntity_GetState( "friendly_encounter" ) == GLOBAL_ON ) + return true; + +#endif + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Allows the weapon to choose proper weapon idle animation +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::WeaponIdle( void ) +{ + //See if we should idle high or low + if ( WeaponShouldBeLowered() ) + { + // Move to lowered position if we're not there yet + if ( GetActivity() != ACT_VM_IDLE_LOWERED && GetActivity() != ACT_VM_IDLE_TO_LOWERED + && GetActivity() != ACT_TRANSITION ) + { + SendWeaponAnim( ACT_VM_IDLE_LOWERED ); + } + else if ( HasWeaponIdleTimeElapsed() ) + { + // Keep idling low + SendWeaponAnim( ACT_VM_IDLE_LOWERED ); + } + } + else + { + // See if we need to raise immediately + if ( m_flRaiseTime < gpGlobals->curtime && GetActivity() == ACT_VM_IDLE_LOWERED ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + else if ( HasWeaponIdleTimeElapsed() ) + { + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} + +#if defined( CLIENT_DLL ) + +#define HL2_BOB_CYCLE_MIN 1.0f +#define HL2_BOB_CYCLE_MAX 0.45f +#define HL2_BOB 0.002f +#define HL2_BOB_UP 0.5f + +extern float g_lateralBob; +extern float g_verticalBob; + +static ConVar cl_bobcycle( "cl_bobcycle","0.8" ); +static ConVar cl_bob( "cl_bob","0.002" ); +static ConVar cl_bobup( "cl_bobup","0.5" ); + +// Register these cvars if needed for easy tweaking +static ConVar v_iyaw_cycle( "v_iyaw_cycle", "2", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_iroll_cycle( "v_iroll_cycle", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_ipitch_cycle( "v_ipitch_cycle", "1", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_iyaw_level( "v_iyaw_level", "0.3", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_iroll_level( "v_iroll_level", "0.1", FCVAR_REPLICATED | FCVAR_CHEAT ); +static ConVar v_ipitch_level( "v_ipitch_level", "0.3", FCVAR_REPLICATED | FCVAR_CHEAT ); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) +{ + static float bobtime; + static float lastbobtime; + float cycle; + + CBasePlayer *player = ToBasePlayer( GetOwner() ); + //Assert( player ); + + //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it + + if ( ( !gpGlobals->frametime ) || ( player == NULL ) ) + { + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f;// just use old value + } + + //Find the speed of the player + float speed = player->GetLocalVelocity().Length2D(); + + //FIXME: This maximum speed value must come from the server. + // MaxSpeed() is not sufficient for dealing with sprinting - jdw + + speed = clamp( speed, -320, 320 ); + + float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); + + bobtime += ( gpGlobals->curtime - lastbobtime ) * bob_offset; + lastbobtime = gpGlobals->curtime; + + //Calculate the vertical bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX)*HL2_BOB_CYCLE_MAX; + cycle /= HL2_BOB_CYCLE_MAX; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_verticalBob = speed*0.005f; + g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); + + g_verticalBob = clamp( g_verticalBob, -7.0f, 4.0f ); + + //Calculate the lateral bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX*2)*HL2_BOB_CYCLE_MAX*2; + cycle /= HL2_BOB_CYCLE_MAX*2; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_lateralBob = speed*0.005f; + g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); + g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// viewmodelindex - +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) +{ + Vector forward, right; + AngleVectors( angles, &forward, &right, NULL ); + + CalcViewmodelBob(); + + // Apply bob, but scaled down to 40% + VectorMA( origin, g_verticalBob * 0.1f, forward, origin ); + + // Z bob a bit more + origin[2] += g_verticalBob * 0.1f; + + // bob the angles + angles[ ROLL ] += g_verticalBob * 0.5f; + angles[ PITCH ] -= g_verticalBob * 0.4f; + + angles[ YAW ] -= g_lateralBob * 0.3f; + + VectorMA( origin, g_lateralBob * 0.8f, right, origin ); +} + +//----------------------------------------------------------------------------- +Vector CBaseHL2MPCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + return BaseClass::GetBulletSpread( proficiency ); +} + +//----------------------------------------------------------------------------- +float CBaseHL2MPCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) +{ + return BaseClass::GetSpreadBias( proficiency ); +} +//----------------------------------------------------------------------------- + +const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetProficiencyValues() +{ + return NULL; +} + +#else + +// Server stubs +float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) +{ + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// &angles - +// viewmodelindex - +//----------------------------------------------------------------------------- +void CBaseHL2MPCombatWeapon::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) +{ +} + + +//----------------------------------------------------------------------------- +Vector CBaseHL2MPCombatWeapon::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + Vector baseSpread = BaseClass::GetBulletSpread( proficiency ); + + const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); + float flModifier = (pProficiencyValues)[ proficiency ].spreadscale; + return ( baseSpread * flModifier ); +} + +//----------------------------------------------------------------------------- +float CBaseHL2MPCombatWeapon::GetSpreadBias( WeaponProficiency_t proficiency ) +{ + const WeaponProficiencyInfo_t *pProficiencyValues = GetProficiencyValues(); + return (pProficiencyValues)[ proficiency ].bias; +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetProficiencyValues() +{ + return GetDefaultProficiencyValues(); +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CBaseHL2MPCombatWeapon::GetDefaultProficiencyValues() +{ + // Weapon proficiency table. Keep this in sync with WeaponProficiency_t enum in the header!! + static WeaponProficiencyInfo_t g_BaseWeaponProficiencyTable[] = + { + { 2.50, 1.0 }, + { 2.00, 1.0 }, + { 1.50, 1.0 }, + { 1.25, 1.0 }, + { 1.00, 1.0 }, + }; + + COMPILE_TIME_ASSERT( ARRAYSIZE(g_BaseWeaponProficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); + + return g_BaseWeaponProficiencyTable; +} + +#endif
\ No newline at end of file diff --git a/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h b/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h new file mode 100644 index 0000000..e846932 --- /dev/null +++ b/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h @@ -0,0 +1,63 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#ifndef WEAPON_BASEHL2MPCOMBATWEAPON_SHARED_H +#define WEAPON_BASEHL2MPCOMBATWEAPON_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" +#endif + +#include "weapon_hl2mpbase.h" + +#if defined( CLIENT_DLL ) +#define CBaseHL2MPCombatWeapon C_BaseHL2MPCombatWeapon +#endif + +class CBaseHL2MPCombatWeapon : public CWeaponHL2MPBase +{ +#if !defined( CLIENT_DLL ) + DECLARE_DATADESC(); +#endif + + DECLARE_CLASS( CBaseHL2MPCombatWeapon, CWeaponHL2MPBase ); +public: + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CBaseHL2MPCombatWeapon(); + + virtual bool WeaponShouldBeLowered( void ); + + virtual bool Ready( void ); + virtual bool Lower( void ); + virtual bool Deploy( void ); + virtual bool Holster( CBaseCombatWeapon *pSwitchingTo ); + virtual void WeaponIdle( void ); + + virtual void AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ); + virtual float CalcViewmodelBob( void ); + + virtual Vector GetBulletSpread( WeaponProficiency_t proficiency ); + virtual float GetSpreadBias( WeaponProficiency_t proficiency ); + + virtual const WeaponProficiencyInfo_t *GetProficiencyValues(); + static const WeaponProficiencyInfo_t *GetDefaultProficiencyValues(); + + virtual void ItemHolsterFrame( void ); + +protected: + + bool m_bLowered; // Whether the viewmodel is raised or lowered + float m_flRaiseTime; // If lowered, the time we should raise the viewmodel + float m_flHolsterTime; // When the weapon was holstered + +private: + + CBaseHL2MPCombatWeapon( const CBaseHL2MPCombatWeapon & ); +}; + +#endif // WEAPON_BASEHL2MPCOMBATWEAPON_SHARED_H diff --git a/game/shared/hl2mp/weapon_physcannon.cpp b/game/shared/hl2mp/weapon_physcannon.cpp new file mode 100644 index 0000000..4972a1e --- /dev/null +++ b/game/shared/hl2mp/weapon_physcannon.cpp @@ -0,0 +1,3685 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Physics cannon +// +//=============================================================================// + +#include "cbase.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" + #include "vcollide_parse.h" + #include "engine/ivdebugoverlay.h" + #include "iviewrender_beams.h" + #include "beamdraw.h" + #include "c_te_effect_dispatch.h" + #include "model_types.h" + #include "clienteffectprecachesystem.h" + #include "fx_interpvalue.h" +#else + #include "hl2mp_player.h" + #include "soundent.h" + #include "ndebugoverlay.h" + #include "ai_basenpc.h" + #include "player_pickup.h" + #include "physics_prop_ragdoll.h" + #include "globalstate.h" + #include "props.h" + #include "te_effect_dispatch.h" + #include "util.h" +#endif + +#include "gamerules.h" +#include "soundenvelope.h" +#include "engine/IEngineSound.h" +#include "physics.h" +#include "in_buttons.h" +#include "IEffects.h" +#include "shake.h" +#include "beam_shared.h" +#include "Sprite.h" +#include "weapon_physcannon.h" +#include "physics_saverestore.h" +#include "movevars_shared.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" +#include "vphysics/friction.h" +#include "debugoverlay_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define SPRITE_SCALE 128.0f + +static const char *s_pWaitForUpgradeContext = "WaitForUpgrade"; + +ConVar g_debug_physcannon( "g_debug_physcannon", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); + +ConVar physcannon_minforce( "physcannon_minforce", "700", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_maxforce( "physcannon_maxforce", "1500", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_maxmass( "physcannon_maxmass", "250", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_tracelength( "physcannon_tracelength", "250", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_chargetime("physcannon_chargetime", "2", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_pullforce( "physcannon_pullforce", "4000", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_cone( "physcannon_cone", "0.97", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar physcannon_ball_cone( "physcannon_ball_cone", "0.997", FCVAR_REPLICATED | FCVAR_CHEAT ); +ConVar player_throwforce( "player_throwforce", "1000", FCVAR_REPLICATED | FCVAR_CHEAT ); + +#ifndef CLIENT_DLL +extern ConVar hl2_normspeed; +extern ConVar hl2_walkspeed; +#endif + +#define PHYSCANNON_BEAM_SPRITE "sprites/orangelight1.vmt" +#define PHYSCANNON_BEAM_SPRITE_NOZ "sprites/orangelight1_noz.vmt" +#define PHYSCANNON_GLOW_SPRITE "sprites/glow04_noz" +#define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1" +#define PHYSCANNON_CENTER_GLOW "sprites/orangecore1" +#define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2" + +#ifdef CLIENT_DLL + + //Precahce the effects + CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectPhysCannon ) + CLIENTEFFECT_MATERIAL( "sprites/orangelight1" ) + CLIENTEFFECT_MATERIAL( "sprites/orangelight1_noz" ) + CLIENTEFFECT_MATERIAL( PHYSCANNON_GLOW_SPRITE ) + CLIENTEFFECT_MATERIAL( PHYSCANNON_ENDCAP_SPRITE ) + CLIENTEFFECT_MATERIAL( PHYSCANNON_CENTER_GLOW ) + CLIENTEFFECT_MATERIAL( PHYSCANNON_BLAST_SPRITE ) + CLIENTEFFECT_REGISTER_END() + +#endif // CLIENT_DLL + +#ifndef CLIENT_DLL + +void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ) +{ + +} + +bool PlayerHasMegaPhysCannon( void ) +{ + return false; +} + +bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject ) +{ + // BRJ: FIXME! This can't be implemented trivially, so I'm leaving it to Steve or Adrian + Assert( 0 ); + return false; +} + +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// this will hit skip the pass entity, but not anything it owns +// (lets player grab own grenades) +class CTraceFilterNoOwnerTest : public CTraceFilterSimple +{ +public: + DECLARE_CLASS( CTraceFilterNoOwnerTest, CTraceFilterSimple ); + + CTraceFilterNoOwnerTest( const IHandleEntity *passentity, int collisionGroup ) + : CTraceFilterSimple( NULL, collisionGroup ), m_pPassNotOwner(passentity) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + if ( pHandleEntity != m_pPassNotOwner ) + return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); + + return false; + } + +protected: + const IHandleEntity *m_pPassNotOwner; +}; + +static void MatrixOrthogonalize( matrix3x4_t &matrix, int column ) +{ + Vector columns[3]; + int i; + + for ( i = 0; i < 3; i++ ) + { + MatrixGetColumn( matrix, i, columns[i] ); + } + + int index0 = column; + int index1 = (column+1)%3; + int index2 = (column+2)%3; + + columns[index2] = CrossProduct( columns[index0], columns[index1] ); + columns[index1] = CrossProduct( columns[index2], columns[index0] ); + VectorNormalize( columns[index2] ); + VectorNormalize( columns[index1] ); + MatrixSetColumn( columns[index1], index1, matrix ); + MatrixSetColumn( columns[index2], index2, matrix ); +} + +#define SIGN(x) ( (x) < 0 ? -1 : 1 ) + +static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle ) +{ + matrix3x4_t alignMatrix; + AngleMatrix( angles, alignMatrix ); + + // NOTE: Must align z first + for ( int j = 3; --j >= 0; ) + { + Vector vec; + MatrixGetColumn( alignMatrix, j, vec ); + for ( int i = 0; i < 3; i++ ) + { + if ( fabs(vec[i]) > cosineAlignAngle ) + { + vec[i] = SIGN(vec[i]); + vec[(i+1)%3] = 0; + vec[(i+2)%3] = 0; + MatrixSetColumn( vec, j, alignMatrix ); + MatrixOrthogonalize( alignMatrix, j ); + break; + } + } + } + + QAngle out; + MatrixAngles( alignMatrix, out ); + return out; +} + + +static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr ) +{ + physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr ); + + if ( ptr->DidHit() ) + { + ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction; + ptr->startpos = start; + ptr->plane.dist = -ptr->plane.dist; + ptr->plane.normal *= -1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Computes a local matrix for the player clamped to valid carry ranges +//----------------------------------------------------------------------------- +// when looking level, hold bottom of object 8 inches below eye level +#define PLAYER_HOLD_LEVEL_EYES -8 + +// when looking down, hold bottom of object 0 inches from feet +#define PLAYER_HOLD_DOWN_FEET 2 + +// when looking up, hold bottom of object 24 inches above eye level +#define PLAYER_HOLD_UP_EYES 24 + +// use a +/-30 degree range for the entire range of motion of pitch +#define PLAYER_LOOK_PITCH_RANGE 30 + +// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom) +#define PLAYER_REACH_DOWN_DISTANCE 24 + +static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ) +{ + if ( !pPlayer ) + return; + + QAngle angles = pPlayer->EyeAngles(); + Vector origin = pPlayer->EyePosition(); + + // 0-360 / -180-180 + //angles.x = init ? 0 : AngleDistance( angles.x, 0 ); + //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE ); + angles.x = 0; + + float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z; + float eyes = origin.z; + float zoffset = 0; + // moving up (negative pitch is up) + if ( angles.x < 0 ) + { + zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES ); + } + else + { + zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) ); + } + origin.z += zoffset; + angles.x = 0; + AngleMatrix( angles, origin, out ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +// derive from this so we can add save/load data to it +struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t +{ + DECLARE_SIMPLE_DATADESC(); +}; + +BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t ) + + DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( targetRotation, FIELD_VECTOR ), + DEFINE_FIELD( maxAngular, FIELD_FLOAT ), + DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ), + DEFINE_FIELD( maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( dampFactor, FIELD_FLOAT ), + DEFINE_FIELD( teleportDistance, FIELD_FLOAT ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +class CGrabController : public IMotionEvent +{ +public: + + CGrabController( void ); + ~CGrabController( void ); + void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ); + void DetachEntity( bool bClearVelocity ); + void OnRestore(); + + bool UpdateObject( CBasePlayer *pPlayer, float flError ); + + void SetTargetPosition( const Vector &target, const QAngle &targetOrientation ); + float ComputeError(); + float GetLoadWeight( void ) const { return m_flLoadWeight; } + void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } + void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } + QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); + QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); + + CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; } + + IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ); + float GetSavedMass( IPhysicsObject *pObject ); + + QAngle m_attachedAnglesPlayerSpace; + Vector m_attachedPositionObjectSpace; + +private: + // Compute the max speed for an attached object + void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ); + + game_shadowcontrol_params_t m_shadow; + float m_timeToArrive; + float m_errorTime; + float m_error; + float m_contactAmount; + float m_angleAlignment; + bool m_bCarriedEntityBlocksLOS; + bool m_bIgnoreRelativePitch; + + float m_flLoadWeight; + float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + EHANDLE m_attachedEntity; + QAngle m_vecPreferredCarryAngles; + bool m_bHasPreferredCarryAngles; + + + IPhysicsMotionController *m_controller; + int m_frameCount; + friend class CWeaponPhysCannon; +}; + +const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f; +const float REDUCED_CARRY_MASS = 1.0f; + +CGrabController::CGrabController( void ) +{ + m_shadow.dampFactor = 1.0; + m_shadow.teleportDistance = 0; + m_errorTime = 0; + m_error = 0; + // make this controller really stiff! + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; + m_shadow.maxDampSpeed = m_shadow.maxSpeed*2; + m_shadow.maxDampAngular = m_shadow.maxAngular; + m_attachedEntity = NULL; + m_vecPreferredCarryAngles = vec3_angle; + m_bHasPreferredCarryAngles = false; +} + +CGrabController::~CGrabController( void ) +{ + DetachEntity( false ); +} + +void CGrabController::OnRestore() +{ + if ( m_controller ) + { + m_controller->SetEventHandler( this ); + } +} + +void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation ) +{ + m_shadow.targetPosition = target; + m_shadow.targetRotation = targetOrientation; + + m_timeToArrive = gpGlobals->frametime; + + CBaseEntity *pAttached = GetAttached(); + if ( pAttached ) + { + IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); + + if ( pObj != NULL ) + { + pObj->Wake(); + } + else + { + DetachEntity( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CGrabController::ComputeError() +{ + if ( m_errorTime <= 0 ) + return 0; + + CBaseEntity *pAttached = GetAttached(); + if ( pAttached ) + { + Vector pos; + IPhysicsObject *pObj = pAttached->VPhysicsGetObject(); + + if ( pObj ) + { + pObj->GetShadowPosition( &pos, NULL ); + + float error = (m_shadow.targetPosition - pos).Length(); + if ( m_errorTime > 0 ) + { + if ( m_errorTime > 1 ) + { + m_errorTime = 1; + } + float speed = error / m_errorTime; + if ( speed > m_shadow.maxSpeed ) + { + error *= 0.5; + } + m_error = (1-m_errorTime) * m_error + error * m_errorTime; + } + } + else + { + DevMsg( "Object attached to Physcannon has no physics object\n" ); + DetachEntity( false ); + return 9999; // force detach + } + } + + if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) ) + { + m_error *= 3.0f; + } + + m_errorTime = 0; + + return m_error; +} + + +#define MASS_SPEED_SCALE 60 +#define MAX_MASS 40 + +void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics ) +{ +#ifndef CLIENT_DLL + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = DEFAULT_MAX_ANGULAR; + + // Compute total mass... + float flMass = PhysGetEntityMass( pEntity ); + float flMaxMass = physcannon_maxmass.GetFloat(); + if ( flMass <= flMaxMass ) + return; + + float flLerpFactor = clamp( flMass, flMaxMass, 500.0f ); + flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f ); + + float invMass = pPhysics->GetInvMass(); + float invInertia = pPhysics->GetInvInertia().Length(); + + float invMaxMass = 1.0f / MAX_MASS; + float ratio = invMaxMass / invMass; + invMass = invMaxMass; + invInertia *= ratio; + + float maxSpeed = invMass * MASS_SPEED_SCALE * 200; + float maxAngular = invInertia * MASS_SPEED_SCALE * 360; + + m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed ); + m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular ); +#endif +} + + +QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) +{ + if ( m_bIgnoreRelativePitch ) + { + matrix3x4_t test; + QAngle angleTest = pPlayer->EyeAngles(); + angleTest.x = 0; + AngleMatrix( angleTest, test ); + return TransformAnglesToLocalSpace( anglesIn, test ); + } + return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() ); +} + +QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ) +{ + if ( m_bIgnoreRelativePitch ) + { + matrix3x4_t test; + QAngle angleTest = pPlayer->EyeAngles(); + angleTest.x = 0; + AngleMatrix( angleTest, test ); + return TransformAnglesToWorldSpace( anglesIn, test ); + } + return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() ); +} + + +void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition ) +{ + // play the impact sound of the object hitting the player + // used as feedback to let the player know he picked up the object +#ifndef CLIENT_DLL + PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, pPhys->GetMaterialIndex(), pPlayer->VPhysicsGetObject()->GetMaterialIndex(), 1.0, 64 ); +#endif + Vector position; + QAngle angles; + pPhys->GetPosition( &position, &angles ); + // If it has a preferred orientation, use that instead. +#ifndef CLIENT_DLL + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); +#endif + +// ComputeMaxSpeed( pEntity, pPhys ); + + // Carried entities can never block LOS + m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS(); + pEntity->SetBlocksLOS( false ); + m_controller = physenv->CreateMotionController( this ); + m_controller->AttachObject( pPhys, true ); + // Don't do this, it's causing trouble with constraint solvers. + //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY ); + + pPhys->Wake(); + PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + SetTargetPosition( position, angles ); + m_attachedEntity = pEntity; + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + m_flLoadWeight = 0; + float damping = 10; + float flFactor = count / 7.5f; + if ( flFactor < 1.0f ) + { + flFactor = 1.0f; + } + for ( int i = 0; i < count; i++ ) + { + float mass = pList[i]->GetMass(); + pList[i]->GetDamping( NULL, &m_savedRotDamping[i] ); + m_flLoadWeight += mass; + m_savedMass[i] = mass; + + // reduce the mass to prevent the player from adding crazy amounts of energy to the system + pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); + pList[i]->SetDamping( NULL, &damping ); + } + + // Give extra mass to the phys object we're actually picking up + pPhys->SetMass( REDUCED_CARRY_MASS ); + pPhys->EnableDrag( false ); + + m_errorTime = -1.0f; // 1 seconds until error starts accumulating + m_error = 0; + m_contactAmount = 0; + + m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer ); + if ( m_angleAlignment != 0 ) + { + m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment ); + } + + VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace ); + +#ifndef CLIENT_DLL + // If it's a prop, see if it has desired carry angles + CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pEntity); + if ( pProp ) + { + m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); + } + else + { + m_bHasPreferredCarryAngles = false; + } +#else + + m_bHasPreferredCarryAngles = false; +#endif + +} + +static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit ) +{ + Vector vel; + AngularImpulse angVel; + pPhys->GetVelocity( &vel, &angVel ); + float speed = VectorNormalize(vel) - linearLimit; + float angSpeed = VectorNormalize(angVel) - angularLimit; + speed = speed < 0 ? 0 : -speed; + angSpeed = angSpeed < 0 ? 0 : -angSpeed; + vel *= speed; + angVel *= angSpeed; + pPhys->AddVelocity( &vel, &angVel ); +} + +void CGrabController::DetachEntity( bool bClearVelocity ) +{ + CBaseEntity *pEntity = GetAttached(); + if ( pEntity ) + { + // Restore the LS blocking state + pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS ); + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + + for ( int i = 0; i < count; i++ ) + { + IPhysicsObject *pPhys = pList[i]; + if ( !pPhys ) + continue; + + // on the odd chance that it's gone to sleep while under anti-gravity + pPhys->EnableDrag( true ); + pPhys->Wake(); + pPhys->SetMass( m_savedMass[i] ); + pPhys->SetDamping( NULL, &m_savedRotDamping[i] ); + PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + if ( bClearVelocity ) + { + PhysForceClearVelocity( pPhys ); + } + else + { +#ifndef CLIENT_DLL + ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f ); +#endif + } + + } + } + + m_attachedEntity = NULL; + if ( physenv ) + { + physenv->DestroyMotionController( m_controller ); + } + m_controller = NULL; +} + +static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass ) +{ + bool contact = false; + IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot(); + while ( pSnapshot->IsValid() ) + { + IPhysicsObject *pOther = pSnapshot->GetObject( 1 ); + if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass ) + { + contact = true; + break; + } + pSnapshot->NextFrictionData(); + } + pObject->DestroyFrictionSnapshot( pSnapshot ); + return contact; +} + +IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular ) +{ + game_shadowcontrol_params_t shadowParams = m_shadow; + if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) ) + { + m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f ); + } + else + { + m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f ); + } + shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount; +#ifndef CLIENT_DLL + m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime ); +#else + m_timeToArrive = pObject->ComputeShadowControl( shadowParams, (TICK_INTERVAL*2), deltaTime ); +#endif + + // Slide along the current contact points to fix bouncing problems + Vector velocity; + AngularImpulse angVel; + pObject->GetVelocity( &velocity, &angVel ); + PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() ); + pObject->SetVelocityInstantaneous( &velocity, NULL ); + + linear.Init(); + angular.Init(); + m_errorTime += deltaTime; + + return SIM_LOCAL_ACCELERATION; +} + +float CGrabController::GetSavedMass( IPhysicsObject *pObject ) +{ + CBaseEntity *pHeld = m_attachedEntity; + if ( pHeld ) + { + if ( pObject->GetGameData() == (void*)pHeld ) + { + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] == pObject ) + return m_savedMass[i]; + } + } + } + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Player pickup controller +//----------------------------------------------------------------------------- + +class CPlayerPickupController : public CBaseEntity +{ + DECLARE_CLASS( CPlayerPickupController, CBaseEntity ); +public: + void Init( CBasePlayer *pPlayer, CBaseEntity *pObject ); + void Shutdown( bool bThrown = false ); + bool OnControls( CBaseEntity *pControls ) { return true; } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void OnRestore() + { + m_grabController.OnRestore(); + } + void VPhysicsUpdate( IPhysicsObject *pPhysics ){} + void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {} + + bool IsHoldingEntity( CBaseEntity *pEnt ); + CGrabController &GetGrabController() { return m_grabController; } + +private: + CGrabController m_grabController; + CBasePlayer *m_pPlayer; +}; + +LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// *pObject - +//----------------------------------------------------------------------------- +void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) +{ +#ifndef CLIENT_DLL + // Holster player's weapon + if ( pPlayer->GetActiveWeapon() ) + { + if ( !pPlayer->GetActiveWeapon()->Holster() ) + { + Shutdown(); + return; + } + } + + + CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( pPlayer ); + if ( pOwner ) + { + pOwner->EnableSprint( false ); + } + + // If the target is debris, convert it to non-debris + if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) + { + // Interactive debris converts back to debris when it comes to rest + pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); + } + + // done so I'll go across level transitions with the player + SetParent( pPlayer ); + m_grabController.SetIgnorePitch( true ); + m_grabController.SetAngleAlignment( DOT_30DEGREE ); + m_pPlayer = pPlayer; + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + Pickup_OnPhysGunPickup( pObject, m_pPlayer ); + + m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false ); + + m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; + m_pPlayer->SetUseEntity( this ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bool - +//----------------------------------------------------------------------------- +void CPlayerPickupController::Shutdown( bool bThrown ) +{ +#ifndef CLIENT_DLL + CBaseEntity *pObject = m_grabController.GetAttached(); + + bool bClearVelocity = false; + if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) ) + { + bClearVelocity = true; + } + + m_grabController.DetachEntity( bClearVelocity ); + + if ( pObject != NULL ) + { + Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER ); + } + + if ( m_pPlayer ) + { + CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( m_pPlayer ); + if ( pOwner ) + { + pOwner->EnableSprint( true ); + } + + m_pPlayer->SetUseEntity( NULL ); + if ( m_pPlayer->GetActiveWeapon() ) + { + if ( !m_pPlayer->GetActiveWeapon()->Deploy() ) + { + // We tried to restore the player's weapon, but we couldn't. + // This usually happens when they're holding an empty weapon that doesn't + // autoswitch away when out of ammo. Switch to next best weapon. + m_pPlayer->SwitchToNextBestWeapon( NULL ); + } + } + + m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; + } + Remove(); + +#endif + +} + + +void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ToBasePlayer(pActivator) == m_pPlayer ) + { + CBaseEntity *pAttached = m_grabController.GetAttached(); + + // UNDONE: Use vphysics stress to decide to drop objects + // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work + if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 ) + { + Shutdown(); + return; + } + + //Adrian: Oops, our object became motion disabled, let go! + IPhysicsObject *pPhys = pAttached->VPhysicsGetObject(); + if ( pPhys && pPhys->IsMoveable() == false ) + { + Shutdown(); + return; + } + +#if STRESS_TEST + vphysics_objectstress_t stress; + CalculateObjectStress( pPhys, pAttached, &stress ); + if ( stress.exertedStress > 250 ) + { + Shutdown(); + return; + } +#endif + // +ATTACK will throw phys objects + if ( m_pPlayer->m_nButtons & IN_ATTACK ) + { + Shutdown( true ); + Vector vecLaunch; + m_pPlayer->EyeVectors( &vecLaunch ); + // JAY: Scale this with mass because some small objects really go flying + float massFactor = clamp( pPhys->GetMass(), 0.5, 15 ); + massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 ); + vecLaunch *= player_throwforce.GetFloat() * massFactor; + + pPhys->ApplyForceCenter( vecLaunch ); + AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor; + pPhys->ApplyTorqueCenter( aVel ); + return; + } + + if ( useType == USE_SET ) + { + // update position + m_grabController.UpdateObject( m_pPlayer, 12 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEnt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt ) +{ + return ( m_grabController.GetAttached() == pEnt ); +} + +void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject ) +{ + +#ifndef CLIENT_DLL + + //Don't pick up if we don't have a phys object. + if ( pObject->VPhysicsGetObject() == NULL ) + return; + + CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer ); + + if ( !pController ) + return; + + pController->Init( pPlayer, pObject ); + +#endif + +} + +//---------------------------------------------------------------------------------------------------------------------------------------------------------- +// CInterpolatedValue class +//---------------------------------------------------------------------------------------------------------------------------------------------------------- + +#ifdef CLIENT_DLL + +//---------------------------------------------------------------------------------------------------------------------------------------------------------- +// CPhysCannonEffect class +//---------------------------------------------------------------------------------------------------------------------------------------------------------- + +class CPhysCannonEffect +{ +public: + CPhysCannonEffect( void ) : m_vecColor( 255, 255, 255 ), m_bVisible( true ), m_nAttachment( -1 ) {}; + + void SetAttachment( int attachment ) { m_nAttachment = attachment; } + int GetAttachment( void ) const { return m_nAttachment; } + + void SetVisible( bool visible = true ) { m_bVisible = visible; } + int IsVisible( void ) const { return m_bVisible; } + + void SetColor( const Vector &color ) { m_vecColor = color; } + const Vector &GetColor( void ) const { return m_vecColor; } + + bool SetMaterial( const char *materialName ) + { + m_hMaterial.Init( materialName, TEXTURE_GROUP_CLIENT_EFFECTS ); + return ( m_hMaterial != NULL ); + } + + CMaterialReference &GetMaterial( void ) { return m_hMaterial; } + + CInterpolatedValue &GetAlpha( void ) { return m_Alpha; } + CInterpolatedValue &GetScale( void ) { return m_Scale; } + +private: + CInterpolatedValue m_Alpha; + CInterpolatedValue m_Scale; + + Vector m_vecColor; + bool m_bVisible; + int m_nAttachment; + CMaterialReference m_hMaterial; +}; + +//---------------------------------------------------------------------------------------------------------------------------------------------------------- +// CPhysCannonEffectBeam class +//---------------------------------------------------------------------------------------------------------------------------------------------------------- + +class CPhysCannonEffectBeam +{ +public: + CPhysCannonEffectBeam( void ) : m_pBeam( NULL ) {}; + + ~CPhysCannonEffectBeam( void ) + { + Release(); + } + + void Release( void ) + { + if ( m_pBeam != NULL ) + { + m_pBeam->flags = 0; + m_pBeam->die = gpGlobals->curtime - 1; + + m_pBeam = NULL; + } + } + + void Init( int startAttachment, int endAttachment, CBaseEntity *pEntity, bool firstPerson ) + { + if ( m_pBeam != NULL ) + return; + + BeamInfo_t beamInfo; + + beamInfo.m_pStartEnt = pEntity; + beamInfo.m_nStartAttachment = startAttachment; + beamInfo.m_pEndEnt = pEntity; + beamInfo.m_nEndAttachment = endAttachment; + beamInfo.m_nType = TE_BEAMPOINTS; + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_vecEnd = vec3_origin; + + beamInfo.m_pszModelName = ( firstPerson ) ? PHYSCANNON_BEAM_SPRITE_NOZ : PHYSCANNON_BEAM_SPRITE; + + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.0f; + + if ( firstPerson ) + { + beamInfo.m_flWidth = 0.0f; + beamInfo.m_flEndWidth = 4.0f; + } + else + { + beamInfo.m_flWidth = 0.5f; + beamInfo.m_flEndWidth = 2.0f; + } + + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = 16; + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 150.0f; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 30.0; + beamInfo.m_flRed = 255.0; + beamInfo.m_flGreen = 255.0; + beamInfo.m_flBlue = 255.0; + beamInfo.m_nSegments = 8; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = FBEAM_FOREVER; + + m_pBeam = beams->CreateBeamEntPoint( beamInfo ); + } + + void SetVisible( bool state = true ) + { + if ( m_pBeam == NULL ) + return; + + m_pBeam->brightness = ( state ) ? 255.0f : 0.0f; + } + +private: + Beam_t *m_pBeam; +}; + +#endif + +//---------------------------------------------------------------------------------------------------------------------------------------------------------- +// CWeaponPhysCannon class +//---------------------------------------------------------------------------------------------------------------------------------------------------------- + +#ifdef CLIENT_DLL +#define CWeaponPhysCannon C_WeaponPhysCannon +#endif + +class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponPhysCannon, CBaseHL2MPCombatWeapon ); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponPhysCannon( void ); + + void Drop( const Vector &vecVelocity ); + void Precache(); + virtual void OnRestore(); + virtual void StopLoopingSounds(); + virtual void UpdateOnRemove(void); + void PrimaryAttack(); + void SecondaryAttack(); + void WeaponIdle(); + void ItemPreFrame(); + void ItemPostFrame(); + + void ForceDrop( void ); + bool DropIfEntityHeld( CBaseEntity *pTarget ); // Drops its held entity if it matches the entity passed in + CGrabController &GetGrabController() { return m_grabController; } + + bool CanHolster( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + bool Deploy( void ); + + bool HasAnyAmmo( void ) { return true; } + + virtual void SetViewModel( void ); + virtual const char *GetShootSound( int iIndex ) const; + +#ifndef CLIENT_DLL + CNetworkQAngle ( m_attachedAnglesPlayerSpace ); +#else + QAngle m_attachedAnglesPlayerSpace; +#endif + + CNetworkVector ( m_attachedPositionObjectSpace ); + + CNetworkHandle( CBaseEntity, m_hAttachedObject ); + + EHANDLE m_hOldAttachedObject; + +protected: + enum FindObjectResult_t + { + OBJECT_FOUND = 0, + OBJECT_NOT_FOUND, + OBJECT_BEING_DETACHED, + }; + + void DoEffect( int effectType, Vector *pos = NULL ); + + void OpenElements( void ); + void CloseElements( void ); + + // Pickup and throw objects. + bool CanPickupObject( CBaseEntity *pTarget ); + void CheckForTarget( void ); + +#ifndef CLIENT_DLL + bool AttachObject( CBaseEntity *pObject, const Vector &vPosition ); + FindObjectResult_t FindObject( void ); + CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ); +#endif // !CLIENT_DLL + + void UpdateObject( void ); + void DetachObject( bool playSound = true, bool wasLaunched = false ); + void LaunchObject( const Vector &vecDir, float flForce ); + void StartEffects( void ); // Initialize all sprites and beams + void StopEffects( bool stopSound = true ); // Hide all effects temporarily + void DestroyEffects( void ); // Destroy all sprites and beams + + // Punt objects - this is pointing at an object in the world and applying a force to it. + void PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); + void PuntVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); + + // Velocity-based throw common to punt and launch code. + void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ); + + // Physgun effects + void DoEffectClosed( void ); + void DoEffectReady( void ); + void DoEffectHolding( void ); + void DoEffectLaunch( Vector *pos ); + void DoEffectNone( void ); + void DoEffectIdle( void ); + + // Trace length + float TraceLength(); + + // Sprite scale factor + float SpriteScaleFactor(); + + float GetLoadPercentage(); + CSoundPatch *GetMotorSound( void ); + + void DryFire( void ); + void PrimaryFireEffect( void ); + +#ifndef CLIENT_DLL + // What happens when the physgun picks up something + void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ); +#endif // !CLIENT_DLL + +#ifdef CLIENT_DLL + + enum EffectType_t + { + PHYSCANNON_CORE = 0, + + PHYSCANNON_BLAST, + + PHYSCANNON_GLOW1, // Must be in order! + PHYSCANNON_GLOW2, + PHYSCANNON_GLOW3, + PHYSCANNON_GLOW4, + PHYSCANNON_GLOW5, + PHYSCANNON_GLOW6, + + PHYSCANNON_ENDCAP1, // Must be in order! + PHYSCANNON_ENDCAP2, + PHYSCANNON_ENDCAP3, // Only used in third-person! + + NUM_PHYSCANNON_PARAMETERS // Must be last! + }; + +#define NUM_GLOW_SPRITES ((CWeaponPhysCannon::PHYSCANNON_GLOW6-CWeaponPhysCannon::PHYSCANNON_GLOW1)+1) +#define NUM_ENDCAP_SPRITES ((CWeaponPhysCannon::PHYSCANNON_ENDCAP3-CWeaponPhysCannon::PHYSCANNON_ENDCAP1)+1) + +#define NUM_PHYSCANNON_BEAMS 3 + + virtual int DrawModel( int flags ); + virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); + virtual bool IsTransparent( void ); + virtual void OnDataChanged( DataUpdateType_t type ); + virtual void ClientThink( void ); + + void ManagePredictedObject( void ); + void DrawEffects( void ); + void GetEffectParameters( EffectType_t effectID, color32 &color, float &scale, IMaterial **pMaterial, Vector &vecAttachment ); + void DrawEffectSprite( EffectType_t effectID ); + inline bool IsEffectVisible( EffectType_t effectID ); + void UpdateElementPosition( void ); + + // We need to render opaque and translucent pieces + RenderGroup_t GetRenderGroup( void ) { return RENDER_GROUP_TWOPASS; } + + CInterpolatedValue m_ElementParameter; // Used to interpolate the position of the articulated elements + CPhysCannonEffect m_Parameters[NUM_PHYSCANNON_PARAMETERS]; // Interpolated parameters for the effects + CPhysCannonEffectBeam m_Beams[NUM_PHYSCANNON_BEAMS]; // Beams + + int m_nOldEffectState; // Used for parity checks + bool m_bOldOpen; // Used for parity checks + + void NotifyShouldTransmit( ShouldTransmitState_t state ); + +#endif // CLIENT_DLL + + int m_nChangeState; // For delayed state change of elements + float m_flCheckSuppressTime; // Amount of time to suppress the checking for targets + bool m_flLastDenySoundPlayed; // Debounce for deny sound + int m_nAttack2Debounce; + + CNetworkVar( bool, m_bActive ); + CNetworkVar( int, m_EffectState ); // Current state of the effects on the gun + CNetworkVar( bool, m_bOpen ); + + bool m_bResetOwnerEntity; + + float m_flElementDebounce; + + CSoundPatch *m_sndMotor; // Whirring sound for the gun + + CGrabController m_grabController; + + float m_flRepuntObjectTime; + EHANDLE m_hLastPuntedObject; + +private: + CWeaponPhysCannon( const CWeaponPhysCannon & ); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponPhysCannon, DT_WeaponPhysCannon ) + +BEGIN_NETWORK_TABLE( CWeaponPhysCannon, DT_WeaponPhysCannon ) +#ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bActive ) ), + RecvPropEHandle( RECVINFO( m_hAttachedObject ) ), + RecvPropVector( RECVINFO( m_attachedPositionObjectSpace ) ), + RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[0] ) ), + RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[1] ) ), + RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[2] ) ), + RecvPropInt( RECVINFO( m_EffectState ) ), + RecvPropBool( RECVINFO( m_bOpen ) ), +#else + SendPropBool( SENDINFO( m_bActive ) ), + SendPropEHandle( SENDINFO( m_hAttachedObject ) ), + SendPropVector(SENDINFO( m_attachedPositionObjectSpace ), -1, SPROP_COORD), + SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 0 ), 11 ), + SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 1 ), 11 ), + SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 2 ), 11 ), + SendPropInt( SENDINFO( m_EffectState ) ), + SendPropBool( SENDINFO( m_bOpen ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponPhysCannon ) + DEFINE_PRED_FIELD( m_EffectState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bOpen, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon ); +PRECACHE_WEAPON_REGISTER( weapon_physcannon ); + +#ifndef CLIENT_DLL + +acttable_t CWeaponPhysCannon::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PHYSGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PHYSGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PHYSGUN, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponPhysCannon); + +#endif + + +enum +{ + ELEMENT_STATE_NONE = -1, + ELEMENT_STATE_OPEN, + ELEMENT_STATE_CLOSED, +}; + +enum +{ + EFFECT_NONE, + EFFECT_CLOSED, + EFFECT_READY, + EFFECT_HOLDING, + EFFECT_LAUNCH, +}; + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CWeaponPhysCannon::CWeaponPhysCannon( void ) +{ + m_bOpen = false; + m_nChangeState = ELEMENT_STATE_NONE; + m_flCheckSuppressTime = 0.0f; + m_EffectState = (int)EFFECT_NONE; + m_flLastDenySoundPlayed = false; + +#ifdef CLIENT_DLL + m_nOldEffectState = EFFECT_NONE; + m_bOldOpen = false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Precache +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Precache( void ) +{ + PrecacheModel( PHYSCANNON_BEAM_SPRITE ); + PrecacheModel( PHYSCANNON_BEAM_SPRITE_NOZ ); + + PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: Restore +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::OnRestore() +{ + BaseClass::OnRestore(); + m_grabController.OnRestore(); + + // Tracker 8106: Physcannon effects disappear through level transition, so + // just recreate any effects here + if ( m_EffectState != EFFECT_NONE ) + { + DoEffect( m_EffectState, NULL ); + } +} + + +//----------------------------------------------------------------------------- +// On Remove +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::UpdateOnRemove(void) +{ + DestroyEffects( ); + BaseClass::UpdateOnRemove(); +} + +#ifdef CLIENT_DLL +void CWeaponPhysCannon::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + if ( type == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + + C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); + StartEffects(); + } + + if ( GetOwner() == NULL ) + { + if ( m_hAttachedObject ) + { + m_hAttachedObject->VPhysicsDestroyObject(); + } + + if ( m_hOldAttachedObject ) + { + m_hOldAttachedObject->VPhysicsDestroyObject(); + } + } + + // Update effect state when out of parity with the server + if ( m_nOldEffectState != m_EffectState ) + { + DoEffect( m_EffectState ); + m_nOldEffectState = m_EffectState; + } + + // Update element state when out of parity + if ( m_bOldOpen != m_bOpen ) + { + if ( m_bOpen ) + { + m_ElementParameter.InitFromCurrent( 1.0f, 0.2f, INTERP_SPLINE ); + } + else + { + m_ElementParameter.InitFromCurrent( 0.0f, 0.5f, INTERP_SPLINE ); + } + + m_bOldOpen = (bool) m_bOpen; + } +} +#endif + +//----------------------------------------------------------------------------- +// Sprite scale factor +//----------------------------------------------------------------------------- +inline float CWeaponPhysCannon::SpriteScaleFactor() +{ + return 1.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::Deploy( void ) +{ + CloseElements(); + DoEffect( EFFECT_READY ); + + bool bReturn = BaseClass::Deploy(); + + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner ) + { + pOwner->SetNextAttack( gpGlobals->curtime ); + } + + return bReturn; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::SetViewModel( void ) +{ + BaseClass::SetViewModel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the cannon to drop anything it's carrying +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ForceDrop( void ) +{ + CloseElements(); + DetachObject(); + StopEffects(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Drops its held entity if it matches the entity passed in +// Input : *pTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::DropIfEntityHeld( CBaseEntity *pTarget ) +{ + if ( pTarget == NULL ) + return false; + + CBaseEntity *pHeld = m_grabController.GetAttached(); + + if ( pHeld == NULL ) + return false; + + if ( pHeld == pTarget ) + { + ForceDrop(); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Drop( const Vector &vecVelocity ) +{ + ForceDrop(); + +#ifndef CLIENT_DLL + UTIL_Remove( this ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::CanHolster( void ) +{ + //Don't holster this weapon if we're holding onto something + if ( m_bActive ) + return false; + + return BaseClass::CanHolster(); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + //Don't holster this weapon if we're holding onto something + if ( m_bActive ) + return false; + + ForceDrop(); + DestroyEffects(); + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DryFire( void ) +{ + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + WeaponSound( EMPTY ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PrimaryFireEffect( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + pOwner->ViewPunch( QAngle(-6, SharedRandomInt( "physcannonfire", -2,2) ,0) ); + +#ifndef CLIENT_DLL + color32 white = { 245, 245, 255, 32 }; + UTIL_ScreenFade( pOwner, white, 0.1f, 0.0f, FFADE_IN ); +#endif + + WeaponSound( SINGLE ); +} + +#define MAX_KNOCKBACK_FORCE 128 + +//----------------------------------------------------------------------------- +// Punt non-physics +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ) +{ + if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime ) + return; + +#ifndef CLIENT_DLL + CTakeDamageInfo info; + + info.SetAttacker( GetOwner() ); + info.SetInflictor( this ); + info.SetDamage( 1.0f ); + info.SetDamageType( DMG_CRUSH | DMG_PHYSGUN ); + info.SetDamageForce( forward ); // Scale? + info.SetDamagePosition( tr.endpos ); + + m_hLastPuntedObject = pEntity; + m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; + + pEntity->DispatchTraceAttack( info, forward, &tr ); + + ApplyMultiDamage(); + + //Explosion effect + DoEffect( EFFECT_LAUNCH, &tr.endpos ); +#endif + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; +} + + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// What happens when the physgun picks up something +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ) +{ + // If the target is debris, convert it to non-debris + if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) + { + // Interactive debris converts back to debris when it comes to rest + pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); + } + + Pickup_OnPhysGunPickup( pEntity, pOwner, reason ); +} +#endif + +//----------------------------------------------------------------------------- +// Punt vphysics +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + + if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime ) + return; + + m_hLastPuntedObject = pEntity; + m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; + +#ifndef CLIENT_DLL + CTakeDamageInfo info; + + Vector forward = vecForward; + + info.SetAttacker( GetOwner() ); + info.SetInflictor( this ); + info.SetDamage( 0.0f ); + info.SetDamageType( DMG_PHYSGUN ); + pEntity->DispatchTraceAttack( info, forward, &tr ); + ApplyMultiDamage(); + + + if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) ) + { + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + if ( !listCount ) + { + //FIXME: Do we want to do this if there's no physics object? + Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); + DryFire(); + return; + } + + if( forward.z < 0 ) + { + //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets + forward.z *= -0.65f; + } + + // NOTE: Do this first to enable motion (if disabled) - so forces will work + // Tell the object it's been punted + Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); + + // don't push vehicles that are attached to the world via fixed constraints + // they will just wiggle... + if ( (pList[0]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) && pEntity->GetServerVehicle() ) + { + forward.Init(); + } + + if ( !Pickup_ShouldPuntUseLaunchForces( pEntity, PHYSGUN_FORCE_PUNTED ) ) + { + int i; + + // limit mass to avoid punting REALLY huge things + float totalMass = 0; + for ( i = 0; i < listCount; i++ ) + { + totalMass += pList[i]->GetMass(); + } + float maxMass = 250; + IServerVehicle *pVehicle = pEntity->GetServerVehicle(); + if ( pVehicle ) + { + maxMass *= 2.5; // 625 for vehicles + } + float mass = MIN(totalMass, maxMass); // max 250kg of additional force + + // Put some spin on the object + for ( i = 0; i < listCount; i++ ) + { + const float hitObjectFactor = 0.5f; + const float otherObjectFactor = 1.0f - hitObjectFactor; + // Must be light enough + float ratio = pList[i]->GetMass() / totalMass; + if ( pList[i] == pEntity->VPhysicsGetObject() ) + { + ratio += hitObjectFactor; + ratio = MIN(ratio,1.0f); + } + else + { + ratio *= otherObjectFactor; + } + pList[i]->ApplyForceCenter( forward * 15000.0f * ratio ); + pList[i]->ApplyForceOffset( forward * mass * 600.0f * ratio, tr.endpos ); + } + } + else + { + ApplyVelocityBasedForce( pEntity, vecForward ); + } + } + +#endif + // Add recoil + QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 ); + pOwner->ViewPunch( recoil ); + + //Explosion effect + DoEffect( EFFECT_LAUNCH, &tr.endpos ); + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; + + // Don't allow the gun to regrab a thrown object!! + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; +} + +//----------------------------------------------------------------------------- +// Purpose: Applies velocity-based forces to throw the entity. This code is +// called from both punt and launch carried code. +// ASSUMES: that pEntity is a vphysics entity. +// Input : - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ) +{ +#ifndef CLIENT_DLL + IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); + Assert(pPhysicsObject); // Shouldn't ever get here with a non-vphysics object. + if (!pPhysicsObject) + return; + + float flForceMax = physcannon_maxforce.GetFloat(); + float flForce = flForceMax; + + float mass = pPhysicsObject->GetMass(); + if (mass > 100) + { + mass = MIN(mass, 1000); + float flForceMin = physcannon_minforce.GetFloat(); + flForce = SimpleSplineRemapVal(mass, 100, 600, flForceMax, flForceMin); + } + + Vector vVel = forward * flForce; + // FIXME: Josh needs to put a real value in for PHYSGUN_FORCE_PUNTED + AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, PHYSGUN_FORCE_PUNTED ); + + pPhysicsObject->AddVelocity( &vVel, &aVel ); + +#endif + +} + + +//----------------------------------------------------------------------------- +// Trace length +//----------------------------------------------------------------------------- +float CWeaponPhysCannon::TraceLength() +{ + return physcannon_tracelength.GetFloat(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// This mode is a toggle. Primary fire one time to pick up a physics object. +// With an object held, click primary fire again to drop object. +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PrimaryAttack( void ) +{ + if( m_flNextPrimaryAttack > gpGlobals->curtime ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if( m_bActive ) + { + // Punch the object being held!! + Vector forward; + pOwner->EyeVectors( &forward ); + + // Validate the item is within punt range + CBaseEntity *pHeld = m_grabController.GetAttached(); + Assert( pHeld != NULL ); + + if ( pHeld != NULL ) + { + float heldDist = ( pHeld->WorldSpaceCenter() - pOwner->WorldSpaceCenter() ).Length(); + + if ( heldDist > physcannon_tracelength.GetFloat() ) + { + // We can't punt this yet + DryFire(); + return; + } + } + + LaunchObject( forward, physcannon_maxforce.GetFloat() ); + + PrimaryFireEffect(); + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + return; + } + + // If not active, just issue a physics punch in the world. + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; + + Vector forward; + pOwner->EyeVectors( &forward ); + + // NOTE: Notice we're *not* using the mega tracelength here + // when you have the mega cannon. Punting has shorter range. + Vector start, end; + start = pOwner->Weapon_ShootPosition(); + float flPuntDistance = physcannon_tracelength.GetFloat(); + VectorMA( start, flPuntDistance, forward, end ); + + CTraceFilterNoOwnerTest filter( pOwner, COLLISION_GROUP_NONE ); + trace_t tr; + UTIL_TraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + bool bValid = true; + CBaseEntity *pEntity = tr.m_pEnt; + if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) + { + bValid = false; + } + else if ( (pEntity->GetMoveType() != MOVETYPE_VPHYSICS) && ( pEntity->m_takedamage == DAMAGE_NO ) ) + { + bValid = false; + } + + // If the entity we've hit is invalid, try a traceline instead + if ( !bValid ) + { + UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) + { + // Play dry-fire sequence + DryFire(); + return; + } + + pEntity = tr.m_pEnt; + } + + // See if we hit something + if ( pEntity->GetMoveType() != MOVETYPE_VPHYSICS ) + { + if ( pEntity->m_takedamage == DAMAGE_NO ) + { + DryFire(); + return; + } + + if( GetOwner()->IsPlayer() ) + { + // Don't let the player zap any NPC's except regular antlions and headcrabs. + if( pEntity->IsPlayer() ) + { + DryFire(); + return; + } + } + + PuntNonVPhysics( pEntity, forward, tr ); + } + else + { + if ( pEntity->VPhysicsIsFlesh( ) ) + { + DryFire(); + return; + } + PuntVPhysics( pEntity, forward, tr ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Click secondary attack whilst holding an object to hurl it. +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::SecondaryAttack( void ) +{ +#ifndef CLIENT_DLL + if ( m_flNextSecondaryAttack > gpGlobals->curtime ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + // See if we should drop a held item + if ( ( m_bActive ) && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) ) + { + // Drop the held object + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; + + DetachObject(); + + DoEffect( EFFECT_READY ); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + } + else + { + // Otherwise pick it up + FindObjectResult_t result = FindObject(); + switch ( result ) + { + case OBJECT_FOUND: + WeaponSound( SPECIAL1 ); + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; + + // We found an object. Debounce the button + m_nAttack2Debounce |= pOwner->m_nButtons; + break; + + case OBJECT_NOT_FOUND: + m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f; + CloseElements(); + break; + + case OBJECT_BEING_DETACHED: + m_flNextSecondaryAttack = gpGlobals->curtime + 0.01f; + break; + } + + DoEffect( EFFECT_HOLDING ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::WeaponIdle( void ) +{ + if ( HasWeaponIdleTimeElapsed() ) + { + if ( m_bActive ) + { + //Shake when holding an item + SendWeaponAnim( ACT_VM_RELOAD ); + } + else + { + //Otherwise idle simply + SendWeaponAnim( ACT_VM_IDLE ); + } + } +} + +#ifndef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pObject - +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosition ) +{ + + if ( m_bActive ) + return false; + + if ( CanPickupObject( pObject ) == false ) + return false; + + m_grabController.SetIgnorePitch( false ); + m_grabController.SetAngleAlignment( 0 ); + + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); + + // Must be valid + if ( !pPhysics ) + return false; + + CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() ); + + m_bActive = true; + if( pOwner ) + { + // NOTE: This can change the mass; so it must be done before max speed setting + Physgun_OnPhysGunPickup( pObject, pOwner, PICKED_UP_BY_CANNON ); + } + + // NOTE :This must happen after OnPhysGunPickup because that can change the mass + m_grabController.AttachEntity( pOwner, pObject, pPhysics, false, vPosition, false ); + m_hAttachedObject = pObject; + m_attachedPositionObjectSpace = m_grabController.m_attachedPositionObjectSpace; + m_attachedAnglesPlayerSpace = m_grabController.m_attachedAnglesPlayerSpace; + + m_bResetOwnerEntity = false; + + if ( m_hAttachedObject->GetOwnerEntity() == NULL ) + { + m_hAttachedObject->SetOwnerEntity( pOwner ); + m_bResetOwnerEntity = true; + } + +/* if( pOwner ) + { + pOwner->EnableSprint( false ); + + float loadWeight = ( 1.0f - GetLoadPercentage() ); + float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight ); + + //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); + pOwner->SetMaxSpeed( maxSpeed ); + }*/ + + // Don't drop again for a slight delay, in case they were pulling objects near them + m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f; + + DoEffect( EFFECT_HOLDING ); + OpenElements(); + + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).Play( GetMotorSound(), 0.0f, 50 ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 100, 0.5f ); + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.8f, 0.5f ); + } + + + + return true; +} + +CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + Assert( pPlayer ); + if ( pPlayer == NULL ) + return OBJECT_NOT_FOUND; + + Vector forward; + pPlayer->EyeVectors( &forward ); + + // Setup our positions + Vector start = pPlayer->Weapon_ShootPosition(); + float testLength = TraceLength() * 4.0f; + Vector end = start + forward * testLength; + + // Try to find an object by looking straight ahead + trace_t tr; + CTraceFilterNoOwnerTest filter( pPlayer, COLLISION_GROUP_NONE ); + UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + + // Try again with a hull trace + if ( ( tr.fraction == 1.0 ) || ( tr.m_pEnt == NULL ) || ( tr.m_pEnt->IsWorld() ) ) + { + UTIL_TraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + } + + CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL; + bool bAttach = false; + bool bPull = false; + + // If we hit something, pick it up or pull it + if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt ) && ( tr.m_pEnt->IsWorld() == false ) ) + { + // Attempt to attach if within range + if ( tr.fraction <= 0.25f ) + { + bAttach = true; + } + else if ( tr.fraction > 0.25f ) + { + bPull = true; + } + } + + // Find anything within a general cone in front + CBaseEntity *pConeEntity = NULL; + + if (!bAttach && !bPull) + { + pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() ); + } + + if ( pConeEntity ) + { + pEntity = pConeEntity; + + // If the object is near, grab it. Else, pull it a bit. + if ( pEntity->WorldSpaceCenter().DistToSqr( start ) <= (testLength * testLength) ) + { + bAttach = true; + } + else + { + bPull = true; + } + } + + if ( CanPickupObject( pEntity ) == false ) + { + // Make a noise to signify we can't pick this up + if ( !m_flLastDenySoundPlayed ) + { + m_flLastDenySoundPlayed = true; + WeaponSound( SPECIAL3 ); + } + + return OBJECT_NOT_FOUND; + } + + // Check to see if the object is constrained + needs to be ripped off... + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PICKED_UP_BY_CANNON ) ) + return OBJECT_BEING_DETACHED; + + if ( bAttach ) + { + return AttachObject( pEntity, tr.endpos ) ? OBJECT_FOUND : OBJECT_NOT_FOUND; + } + + if ( !bPull ) + return OBJECT_NOT_FOUND; + + // FIXME: This needs to be run through the CanPickupObject logic + IPhysicsObject *pObj = pEntity->VPhysicsGetObject(); + if ( !pObj ) + return OBJECT_NOT_FOUND; + + // If we're too far, simply start to pull the object towards us + Vector pullDir = start - pEntity->WorldSpaceCenter(); + VectorNormalize( pullDir ); + pullDir *= physcannon_pullforce.GetFloat(); + + float mass = PhysGetEntityMass( pEntity ); + if ( mass < 50.0f ) + { + pullDir *= (mass + 0.5) * (1/50.0f); + } + + // Nudge it towards us + pObj->ApplyForceCenter( pullDir ); + return OBJECT_NOT_FOUND; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ) +{ + // Find the nearest physics-based item in a cone in front of me. + CBaseEntity *list[256]; + float flNearestDist = TraceLength() + 1.0; + Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); + Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); + + CBaseEntity *pNearest = NULL; + + int count = UTIL_EntitiesInBox( list, 256, mins, maxs, 0 ); + for( int i = 0 ; i < count ; i++ ) + { + if ( !list[ i ]->VPhysicsGetObject() ) + continue; + + // Closer than other objects + Vector los = ( list[ i ]->WorldSpaceCenter() - vecOrigin ); + float flDist = VectorNormalize( los ); + if( flDist >= flNearestDist ) + continue; + + // Cull to the cone + if ( DotProduct( los, vecDir ) <= flCone ) + continue; + + // Make sure it isn't occluded! + trace_t tr; + CTraceFilterNoOwnerTest filter( GetOwner(), COLLISION_GROUP_NONE ); + UTIL_TraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + if( tr.m_pEnt == list[ i ] ) + { + flNearestDist = flDist; + pNearest = list[ i ]; + } + } + + return pNearest; +} + +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) +{ + CBaseEntity *pEntity = GetAttached(); + if ( !pEntity ) + return false; + if ( ComputeError() > flError ) + return false; + if ( pPlayer->GetGroundEntity() == pEntity ) + return false; + if (!pEntity->VPhysicsGetObject() ) + return false; + + //Adrian: Oops, our object became motion disabled, let go! + IPhysicsObject *pPhys = pEntity->VPhysicsGetObject(); + if ( pPhys && pPhys->IsMoveable() == false ) + { + return false; + } + + if ( m_frameCount == gpGlobals->framecount ) + { + return true; + } + m_frameCount = gpGlobals->framecount; + Vector forward, right, up; + QAngle playerAngles = pPlayer->EyeAngles(); + + float pitch = AngleDistance(playerAngles.x,0); + playerAngles.x = clamp( pitch, -75, 75 ); + AngleVectors( playerAngles, &forward, &right, &up ); + + // Now clamp a sphere of object radius at end to the player's bbox + Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward ); + Vector player2d = pPlayer->CollisionProp()->OBBMaxs(); + float playerRadius = player2d.Length2D(); + float flDot = DotProduct( forward, radial ); + + float radius = playerRadius + fabs( flDot ); + + float distance = 24 + ( radius * 2.0f ); + + Vector start = pPlayer->Weapon_ShootPosition(); + Vector end = start + ( forward * distance ); + + trace_t tr; + CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE ); + Ray_t ray; + ray.Init( start, end ); + enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr ); + + if ( tr.fraction < 0.5 ) + { + end = start + forward * (radius*0.5f); + } + else if ( tr.fraction <= 1.0f ) + { + end = start + forward * ( distance - radius ); + } + + Vector playerMins, playerMaxs, nearest; + pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs ); + Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter(); + CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL ); + + Vector delta = end - nearest; + float len = VectorNormalize(delta); + if ( len < radius ) + { + end = nearest + radius * delta; + } + + QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer ); + + //Show overlays of radius + if ( g_debug_physcannon.GetBool() ) + { + +#ifdef CLIENT_DLL + + debugoverlay->AddBoxOverlay( end, -Vector( 2,2,2 ), Vector(2,2,2), angles, 0, 255, 255, true, 0 ); + + debugoverlay->AddBoxOverlay( GetAttached()->WorldSpaceCenter(), + -Vector( radius, radius, radius), + Vector( radius, radius, radius ), + angles, + 255, 255, 0, + true, + 0.0f ); + +#else + + NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 ); + + NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(), + -Vector( radius+5, radius+5, radius+5), + Vector( radius+5, radius+5, radius+5 ), + 255, 0, 0, + true, + 0.0f ); +#endif + } + +#ifndef CLIENT_DLL + // If it has a preferred orientation, update to ensure we're still oriented correctly. + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles ); + + + // We may be holding a prop that has preferred carry angles + if ( m_bHasPreferredCarryAngles ) + { + matrix3x4_t tmp; + ComputePlayerMatrix( pPlayer, tmp ); + angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp ); + } + +#endif + + matrix3x4_t attachedToWorld; + Vector offset; + AngleMatrix( angles, attachedToWorld ); + VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset ); + + SetTargetPosition( end - offset, angles ); + + return true; +} + +void CWeaponPhysCannon::UpdateObject( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + Assert( pPlayer ); + + float flError = 12; + if ( !m_grabController.UpdateObject( pPlayer, flError ) ) + { + DetachObject(); + return; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) +{ +#ifndef CLIENT_DLL + if ( m_bActive == false ) + return; + + CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() ); + if( pOwner != NULL ) + { + pOwner->EnableSprint( true ); + pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); + } + + CBaseEntity *pObject = m_grabController.GetAttached(); + + m_grabController.DetachEntity( wasLaunched ); + + if ( pObject != NULL ) + { + Pickup_OnPhysGunDrop( pObject, pOwner, wasLaunched ? LAUNCHED_BY_CANNON : DROPPED_BY_CANNON ); + } + + // Stop our looping sound + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); + } + + if ( pObject && m_bResetOwnerEntity == true ) + { + pObject->SetOwnerEntity( NULL ); + } + + m_bActive = false; + m_hAttachedObject = NULL; + + + if ( playSound ) + { + //Play the detach sound + WeaponSound( MELEE_MISS ); + } + +#else + + m_grabController.DetachEntity( wasLaunched ); + + if ( m_hAttachedObject ) + { + m_hAttachedObject->VPhysicsDestroyObject(); + } +#endif +} + + +#ifdef CLIENT_DLL +void CWeaponPhysCannon::ManagePredictedObject( void ) +{ + CBaseEntity *pAttachedObject = m_hAttachedObject.Get(); + + if ( m_hAttachedObject ) + { + // NOTE :This must happen after OnPhysGunPickup because that can change the mass + if ( pAttachedObject != GetGrabController().GetAttached() ) + { + IPhysicsObject *pPhysics = pAttachedObject->VPhysicsGetObject(); + + if ( pPhysics == NULL ) + { + solid_t tmpSolid; + PhysModelParseSolid( tmpSolid, m_hAttachedObject, pAttachedObject->GetModelIndex() ); + + pAttachedObject->VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid ); + } + + pPhysics = pAttachedObject->VPhysicsGetObject(); + + if ( pPhysics ) + { + m_grabController.SetIgnorePitch( false ); + m_grabController.SetAngleAlignment( 0 ); + + GetGrabController().AttachEntity( ToBasePlayer( GetOwner() ), pAttachedObject, pPhysics, false, vec3_origin, false ); + GetGrabController().m_attachedPositionObjectSpace = m_attachedPositionObjectSpace; + GetGrabController().m_attachedAnglesPlayerSpace = m_attachedAnglesPlayerSpace; + } + } + } + else + { + if ( m_hOldAttachedObject && m_hOldAttachedObject->VPhysicsGetObject() ) + { + GetGrabController().DetachEntity( false ); + + m_hOldAttachedObject->VPhysicsDestroyObject(); + } + } + + m_hOldAttachedObject = m_hAttachedObject; +} + +#endif + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Update the pose parameter for the gun +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::UpdateElementPosition( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + float flElementPosition = m_ElementParameter.Interp( gpGlobals->curtime ); + + if ( ShouldDrawUsingViewModel() ) + { + if ( pOwner != NULL ) + { + CBaseViewModel *vm = pOwner->GetViewModel(); + + if ( vm != NULL ) + { + vm->SetPoseParameter( "active", flElementPosition ); + } + } + } + else + { + SetPoseParameter( "active", flElementPosition ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Think function for the client +//----------------------------------------------------------------------------- + +void CWeaponPhysCannon::ClientThink( void ) +{ + // Update our elements visually + UpdateElementPosition(); + + // Update our effects + DoEffectIdle(); +} + +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ItemPreFrame() +{ + BaseClass::ItemPreFrame(); + +#ifdef CLIENT_DLL + C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); + + if ( localplayer && !localplayer->IsObserver() ) + ManagePredictedObject(); +#endif + + // Update the object if the weapon is switched on. + if( m_bActive ) + { + UpdateObject(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::CheckForTarget( void ) +{ +#ifndef CLIENT_DLL + //See if we're suppressing this + if ( m_flCheckSuppressTime > gpGlobals->curtime ) + return; + + // holstered + if ( IsEffectActive( EF_NODRAW ) ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( m_bActive ) + return; + + Vector aimDir; + pOwner->EyeVectors( &aimDir ); + + Vector startPos = pOwner->Weapon_ShootPosition(); + Vector endPos; + VectorMA( startPos, TraceLength(), aimDir, endPos ); + + trace_t tr; + UTIL_TraceHull( startPos, endPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, pOwner, COLLISION_GROUP_NONE, &tr ); + + if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) ) + { + // FIXME: Try just having the elements always open when pointed at a physics object + if ( CanPickupObject( tr.m_pEnt ) || Pickup_ForcePhysGunOpen( tr.m_pEnt, pOwner ) ) + // if ( ( tr.m_pEnt->VPhysicsGetObject() != NULL ) && ( tr.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS ) ) + { + m_nChangeState = ELEMENT_STATE_NONE; + OpenElements(); + return; + } + } + + // Close the elements after a delay to prevent overact state switching + if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState == ELEMENT_STATE_NONE ) ) + { + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.5f; + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Idle effect (pulsing) +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectIdle( void ) +{ +#ifdef CLIENT_DLL + + StartEffects(); + + //if ( ShouldDrawUsingViewModel() ) + { + // Turn on the glow sprites + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 0.075f, 0.05f ) * SPRITE_SCALE ); + m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 24, 32 ) ); + } + + // Turn on the glow sprites + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 3, 5 ) ); + m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 200, 255 ) ); + } + + if ( m_EffectState != EFFECT_HOLDING ) + { + // Turn beams off + m_Beams[0].SetVisible( false ); + m_Beams[1].SetVisible( false ); + m_Beams[2].SetVisible( false ); + } + } + /* + else + { + // Turn on the glow sprites + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 0.075f, 0.05f ) * SPRITE_SCALE ); + m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 24, 32 ) ); + } + + // Turn on the glow sprites + for ( i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 3, 5 ) ); + m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 200, 255 ) ); + } + + if ( m_EffectState != EFFECT_HOLDING ) + { + // Turn beams off + m_Beams[0].SetVisible( false ); + m_Beams[1].SetVisible( false ); + m_Beams[2].SetVisible( false ); + } + } + */ +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ItemPostFrame() +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + { + // We found an object. Debounce the button + m_nAttack2Debounce = 0; + return; + } + + //Check for object in pickup range + if ( m_bActive == false ) + { + CheckForTarget(); + + if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState != ELEMENT_STATE_NONE ) ) + { + if ( m_nChangeState == ELEMENT_STATE_OPEN ) + { + OpenElements(); + } + else if ( m_nChangeState == ELEMENT_STATE_CLOSED ) + { + CloseElements(); + } + + m_nChangeState = ELEMENT_STATE_NONE; + } + } + + // NOTE: Attack2 will be considered to be pressed until the first item is picked up. + int nAttack2Mask = pOwner->m_nButtons & (~m_nAttack2Debounce); + if ( nAttack2Mask & IN_ATTACK2 ) + { + SecondaryAttack(); + } + else + { + // Reset our debouncer + m_flLastDenySoundPlayed = false; + + if ( m_bActive == false ) + { + DoEffect( EFFECT_READY ); + } + } + + if (( pOwner->m_nButtons & IN_ATTACK2 ) == 0 ) + { + m_nAttack2Debounce = 0; + } + + if ( pOwner->m_nButtons & IN_ATTACK ) + { + PrimaryAttack(); + } + else + { + WeaponIdle(); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define PHYSCANNON_DANGER_SOUND_RADIUS 128 + +void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce ) +{ + CBaseEntity *pObject = m_grabController.GetAttached(); + + if ( !(m_hLastPuntedObject == pObject && gpGlobals->curtime < m_flRepuntObjectTime) ) + { + // FIRE!!! + if( pObject != NULL ) + { + DetachObject( false, true ); + + m_hLastPuntedObject = pObject; + m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; + + // Launch + ApplyVelocityBasedForce( pObject, vecDir ); + + // Don't allow the gun to regrab a thrown object!! + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; + + Vector center = pObject->WorldSpaceCenter(); + + //Do repulse effect + DoEffect( EFFECT_LAUNCH, ¢er ); + + m_hAttachedObject = NULL; + m_bActive = false; + } + } + + // Stop our looping sound + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); + } + + //Close the elements and suppress checking for a bit + m_nChangeState = ELEMENT_STATE_CLOSED; + m_flElementDebounce = gpGlobals->curtime + 0.1f; + m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; +} + +bool UTIL_IsCombineBall( CBaseEntity *pEntity ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) +{ +#ifndef CLIENT_DLL + if ( pTarget == NULL ) + return false; + + if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() ) + return false; + + if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) + return false; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner && pOwner->GetGroundEntity() == pTarget ) + return false; + + if ( pTarget->VPhysicsIsFlesh( ) ) + return false; + + IPhysicsObject *pObj = pTarget->VPhysicsGetObject(); + + if ( pObj && pObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + return false; + + if ( UTIL_IsCombineBall( pTarget ) ) + { + return CBasePlayer::CanPickupObject( pTarget, 0, 0 ); + } + + return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 ); +#else + return false; +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::OpenElements( void ) +{ + if ( m_bOpen ) + return; + + WeaponSound( SPECIAL2 ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + SendWeaponAnim( ACT_VM_IDLE ); + + m_bOpen = true; + + DoEffect( EFFECT_READY ); + +#ifdef CLIENT + // Element prediction + m_ElementParameter.InitFromCurrent( 1.0f, 0.2f, INTERP_SPLINE ); + m_bOldOpen = true; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::CloseElements( void ) +{ + if ( m_bOpen == false ) + return; + + WeaponSound( MELEE_HIT ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + SendWeaponAnim( ACT_VM_IDLE ); + + m_bOpen = false; + + if ( GetMotorSound() ) + { + (CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f ); + (CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f ); + } + + DoEffect( EFFECT_CLOSED ); + +#ifdef CLIENT + // Element prediction + m_ElementParameter.InitFromCurrent( 0.0f, 0.5f, INTERP_SPLINE ); + m_bOldOpen = false; +#endif +} + +#define PHYSCANNON_MAX_MASS 500 + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CWeaponPhysCannon::GetLoadPercentage( void ) +{ + float loadWeight = m_grabController.GetLoadWeight(); + loadWeight /= physcannon_maxmass.GetFloat(); + loadWeight = clamp( loadWeight, 0.0f, 1.0f ); + return loadWeight; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CSoundPatch +//----------------------------------------------------------------------------- +CSoundPatch *CWeaponPhysCannon::GetMotorSound( void ) +{ + if ( m_sndMotor == NULL ) + { + CPASAttenuationFilter filter( this ); + + m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM ); + } + + return m_sndMotor; +} + + +//----------------------------------------------------------------------------- +// Shuts down sounds +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::StopLoopingSounds() +{ + if ( m_sndMotor != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndMotor ); + m_sndMotor = NULL; + } + +#ifndef CLIENT_DLL + BaseClass::StopLoopingSounds(); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DestroyEffects( void ) +{ +#ifdef CLIENT_DLL + + // Free our beams + m_Beams[0].Release(); + m_Beams[1].Release(); + m_Beams[2].Release(); + +#endif + + // Stop everything + StopEffects(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::StopEffects( bool stopSound ) +{ + // Turn off our effect state + DoEffect( EFFECT_NONE ); + +#ifndef CLIENT_DLL + //Shut off sounds + if ( stopSound && GetMotorSound() != NULL ) + { + (CSoundEnvelopeController::GetController()).SoundFadeOut( GetMotorSound(), 0.1f ); + } +#endif // !CLIENT_DLL +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::StartEffects( void ) +{ +#ifdef CLIENT_DLL + + // ------------------------------------------ + // Core + // ------------------------------------------ + + if ( m_Parameters[PHYSCANNON_CORE].GetMaterial() == NULL ) + { + m_Parameters[PHYSCANNON_CORE].GetScale().Init( 0.0f, 1.0f, 0.1f ); + m_Parameters[PHYSCANNON_CORE].GetAlpha().Init( 255.0f, 255.0f, 0.1f ); + m_Parameters[PHYSCANNON_CORE].SetAttachment( 1 ); + + if ( m_Parameters[PHYSCANNON_CORE].SetMaterial( PHYSCANNON_CENTER_GLOW ) == false ) + { + // This means the texture was not found + Assert( 0 ); + } + } + + // ------------------------------------------ + // Blast + // ------------------------------------------ + + if ( m_Parameters[PHYSCANNON_BLAST].GetMaterial() == NULL ) + { + m_Parameters[PHYSCANNON_BLAST].GetScale().Init( 0.0f, 1.0f, 0.1f ); + m_Parameters[PHYSCANNON_BLAST].GetAlpha().Init( 255.0f, 255.0f, 0.1f ); + m_Parameters[PHYSCANNON_BLAST].SetAttachment( 1 ); + m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); + + if ( m_Parameters[PHYSCANNON_BLAST].SetMaterial( PHYSCANNON_BLAST_SPRITE ) == false ) + { + // This means the texture was not found + Assert( 0 ); + } + } + + // ------------------------------------------ + // Glows + // ------------------------------------------ + + const char *attachNamesGlowThirdPerson[NUM_GLOW_SPRITES] = + { + "fork1m", + "fork1t", + "fork2m", + "fork2t", + "fork3m", + "fork3t", + }; + + const char *attachNamesGlow[NUM_GLOW_SPRITES] = + { + "fork1b", + "fork1m", + "fork1t", + "fork2b", + "fork2m", + "fork2t" + }; + + //Create the glow sprites + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + if ( m_Parameters[i].GetMaterial() != NULL ) + continue; + + m_Parameters[i].GetScale().SetAbsolute( 0.05f * SPRITE_SCALE ); + m_Parameters[i].GetAlpha().SetAbsolute( 64.0f ); + + // Different for different views + if ( ShouldDrawUsingViewModel() ) + { + m_Parameters[i].SetAttachment( LookupAttachment( attachNamesGlow[i-PHYSCANNON_GLOW1] ) ); + } + else + { + m_Parameters[i].SetAttachment( LookupAttachment( attachNamesGlowThirdPerson[i-PHYSCANNON_GLOW1] ) ); + } + m_Parameters[i].SetColor( Vector( 255, 128, 0 ) ); + + if ( m_Parameters[i].SetMaterial( PHYSCANNON_GLOW_SPRITE ) == false ) + { + // This means the texture was not found + Assert( 0 ); + } + } + + // ------------------------------------------ + // End caps + // ------------------------------------------ + + const char *attachNamesEndCap[NUM_ENDCAP_SPRITES] = + { + "fork1t", + "fork2t", + "fork3t" + }; + + //Create the glow sprites + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + if ( m_Parameters[i].GetMaterial() != NULL ) + continue; + + m_Parameters[i].GetScale().SetAbsolute( 0.05f * SPRITE_SCALE ); + m_Parameters[i].GetAlpha().SetAbsolute( 255.0f ); + m_Parameters[i].SetAttachment( LookupAttachment( attachNamesEndCap[i-PHYSCANNON_ENDCAP1] ) ); + m_Parameters[i].SetVisible( false ); + + if ( m_Parameters[i].SetMaterial( PHYSCANNON_ENDCAP_SPRITE ) == false ) + { + // This means the texture was not found + Assert( 0 ); + } + } + +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Closing effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectClosed( void ) +{ + +#ifdef CLIENT_DLL + + // Turn off the end-caps + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + m_Parameters[i].SetVisible( false ); + } + +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Ready effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectReady( void ) +{ + +#ifdef CLIENT_DLL + + // Special POV case + if ( ShouldDrawUsingViewModel() ) + { + //Turn on the center sprite + m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 14.0f, 0.2f ); + m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 128.0f, 0.2f ); + m_Parameters[PHYSCANNON_CORE].SetVisible(); + } + else + { + //Turn off the center sprite + m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 8.0f, 0.2f ); + m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 0.0f, 0.2f ); + m_Parameters[PHYSCANNON_CORE].SetVisible(); + } + + // Turn on the glow sprites + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + m_Parameters[i].GetScale().InitFromCurrent( 0.4f * SPRITE_SCALE, 0.2f ); + m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f ); + m_Parameters[i].SetVisible(); + } + + // Turn on the glow sprites + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + m_Parameters[i].SetVisible( false ); + } + +#endif + +} + + +//----------------------------------------------------------------------------- +// Holding effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectHolding( void ) +{ + +#ifdef CLIENT_DLL + + if ( ShouldDrawUsingViewModel() ) + { + // Scale up the center sprite + m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 16.0f, 0.2f ); + m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 255.0f, 0.1f ); + m_Parameters[PHYSCANNON_CORE].SetVisible(); + + // Prepare for scale up + m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); + + // Turn on the glow sprites + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + m_Parameters[i].GetScale().InitFromCurrent( 0.5f * SPRITE_SCALE, 0.2f ); + m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f ); + m_Parameters[i].SetVisible(); + } + + // Turn on the glow sprites + // NOTE: The last glow is left off for first-person + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES-1); i++ ) + { + m_Parameters[i].SetVisible(); + } + + // Create our beams + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + CBaseEntity *pBeamEnt = pOwner->GetViewModel(); + + // Setup the beams + m_Beams[0].Init( LookupAttachment( "fork1t" ), 1, pBeamEnt, true ); + m_Beams[1].Init( LookupAttachment( "fork2t" ), 1, pBeamEnt, true ); + + // Set them visible + m_Beams[0].SetVisible(); + m_Beams[1].SetVisible(); + } + else + { + // Scale up the center sprite + m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 14.0f, 0.2f ); + m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 255.0f, 0.1f ); + m_Parameters[PHYSCANNON_CORE].SetVisible(); + + // Prepare for scale up + m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); + + // Turn on the glow sprites + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + m_Parameters[i].GetScale().InitFromCurrent( 0.5f * SPRITE_SCALE, 0.2f ); + m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f ); + m_Parameters[i].SetVisible(); + } + + // Turn on the glow sprites + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + m_Parameters[i].SetVisible(); + } + + // Setup the beams + m_Beams[0].Init( LookupAttachment( "fork1t" ), 1, this, false ); + m_Beams[1].Init( LookupAttachment( "fork2t" ), 1, this, false ); + m_Beams[2].Init( LookupAttachment( "fork3t" ), 1, this, false ); + + // Set them visible + m_Beams[0].SetVisible(); + m_Beams[1].SetVisible(); + m_Beams[2].SetVisible(); + } + +#endif + +} + + +//----------------------------------------------------------------------------- +// Launch effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectLaunch( Vector *pos ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + Vector endPos; + Vector shotDir; + + // See if we need to predict this position + if ( pos == NULL ) + { + // Hit an entity if we're holding one + if ( m_hAttachedObject ) + { + endPos = m_hAttachedObject->WorldSpaceCenter(); + + shotDir = endPos - pOwner->Weapon_ShootPosition(); + VectorNormalize( shotDir ); + } + else + { + // Otherwise try and find the right spot + endPos = pOwner->Weapon_ShootPosition(); + pOwner->EyeVectors( &shotDir ); + + trace_t tr; + UTIL_TraceLine( endPos, endPos + ( shotDir * MAX_TRACE_LENGTH ), MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr ); + + endPos = tr.endpos; + shotDir = endPos - pOwner->Weapon_ShootPosition(); + VectorNormalize( shotDir ); + } + } + else + { + // Use what is supplied + endPos = *pos; + shotDir = ( endPos - pOwner->Weapon_ShootPosition() ); + VectorNormalize( shotDir ); + } + + // End hit + CPVSFilter filter( endPos ); + + // Don't send this to the owning player, they already had it predicted + if ( IsPredicted() ) + { + filter.UsePredictionRules(); + } + + // Do an impact hit + CEffectData data; + data.m_vOrigin = endPos; +#ifdef CLIENT_DLL + data.m_hEntity = GetRefEHandle(); +#else + data.m_nEntIndex = entindex(); +#endif + + te->DispatchEffect( filter, 0.0, data.m_vOrigin, "PhyscannonImpact", data ); + +#ifdef CLIENT_DLL + + //Turn on the blast sprite and scale + m_Parameters[PHYSCANNON_BLAST].GetScale().Init( 8.0f, 64.0f, 0.1f ); + m_Parameters[PHYSCANNON_BLAST].GetAlpha().Init( 255.0f, 0.0f, 0.2f ); + m_Parameters[PHYSCANNON_BLAST].SetVisible(); + +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown for the weapon when it's holstered +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffectNone( void ) +{ +#ifdef CLIENT_DLL + + //Turn off main glows + m_Parameters[PHYSCANNON_CORE].SetVisible( false ); + m_Parameters[PHYSCANNON_BLAST].SetVisible( false ); + + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + m_Parameters[i].SetVisible( false ); + } + + // Turn on the glow sprites + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + m_Parameters[i].SetVisible( false ); + } + + m_Beams[0].SetVisible( false ); + m_Beams[1].SetVisible( false ); + m_Beams[2].SetVisible( false ); + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : effectType - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) +{ + m_EffectState = effectType; + +#ifdef CLIENT_DLL + // Save predicted state + m_nOldEffectState = m_EffectState; +#endif + + switch( effectType ) + { + case EFFECT_CLOSED: + DoEffectClosed( ); + break; + + case EFFECT_READY: + DoEffectReady( ); + break; + + case EFFECT_HOLDING: + DoEffectHolding(); + break; + + case EFFECT_LAUNCH: + DoEffectLaunch( pos ); + break; + + default: + case EFFECT_NONE: + DoEffectNone(); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iIndex - +// Output : const char +//----------------------------------------------------------------------------- +const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const +{ + return BaseClass::GetShootSound( iIndex ); +} + +#ifdef CLIENT_DLL + +extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); + +//----------------------------------------------------------------------------- +// Purpose: Gets the complete list of values needed to render an effect from an +// effect parameter +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::GetEffectParameters( EffectType_t effectID, color32 &color, float &scale, IMaterial **pMaterial, Vector &vecAttachment ) +{ + const float dt = gpGlobals->curtime; + + // Get alpha + float alpha = m_Parameters[effectID].GetAlpha().Interp( dt ); + + // Get scale + scale = m_Parameters[effectID].GetScale().Interp( dt ); + + // Get material + *pMaterial = (IMaterial *) m_Parameters[effectID].GetMaterial(); + + // Setup the color + color.r = (int) m_Parameters[effectID].GetColor().x; + color.g = (int) m_Parameters[effectID].GetColor().y; + color.b = (int) m_Parameters[effectID].GetColor().z; + color.a = (int) alpha; + + // Setup the attachment + int attachment = m_Parameters[effectID].GetAttachment(); + QAngle angles; + + // Format for first-person + if ( ShouldDrawUsingViewModel() ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner != NULL ) + { + pOwner->GetViewModel()->GetAttachment( attachment, vecAttachment, angles ); + ::FormatViewModelAttachment( vecAttachment, true ); + } + } + else + { + GetAttachment( attachment, vecAttachment, angles ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Whether or not an effect is set to display +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::IsEffectVisible( EffectType_t effectID ) +{ + return m_Parameters[effectID].IsVisible(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the effect sprite, given an effect parameter ID +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DrawEffectSprite( EffectType_t effectID ) +{ + color32 color; + float scale; + IMaterial *pMaterial; + Vector vecAttachment; + + // Don't draw invisible effects + if ( IsEffectVisible( effectID ) == false ) + return; + + // Get all of our parameters + GetEffectParameters( effectID, color, scale, &pMaterial, vecAttachment ); + + // Msg( "Scale: %.2f\tAlpha: %.2f\n", scale, alpha ); + + // Don't render fully translucent objects + if ( color.a <= 0.0f ) + return; + + // Draw the sprite + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( pMaterial, this ); + DrawSprite( vecAttachment, scale, scale, color ); +} + +//----------------------------------------------------------------------------- +// Purpose: Render our third-person effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DrawEffects( void ) +{ + // Draw the core effects + DrawEffectSprite( PHYSCANNON_CORE ); + DrawEffectSprite( PHYSCANNON_BLAST ); + + // Draw the glows + for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ ) + { + DrawEffectSprite( (EffectType_t) i ); + } + + // Draw the endcaps + for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ ) + { + DrawEffectSprite( (EffectType_t) i ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Third-person function call to render world model +//----------------------------------------------------------------------------- +int CWeaponPhysCannon::DrawModel( int flags ) +{ + // Only render these on the transparent pass + if ( flags & STUDIO_TRANSPARENCY ) + { + DrawEffects(); + return 1; + } + + // Only do this on the opaque pass + return BaseClass::DrawModel( flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: First-person function call after viewmodel has been drawn +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) +{ + // Render our effects + DrawEffects(); + + // Pass this back up + BaseClass::ViewModelDrawn( pBaseViewModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: We are always considered transparent +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::IsTransparent( void ) +{ + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ + BaseClass::NotifyShouldTransmit(state); + + if ( state == SHOULDTRANSMIT_END ) + { + DoEffect( EFFECT_NONE ); + } +} + +#endif + +//----------------------------------------------------------------------------- +// EXTERNAL API +//----------------------------------------------------------------------------- +void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis ) +{ + CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon); + if ( pCannon ) + { + if ( pOnlyIfHoldingThis ) + { + pCannon->DropIfEntityHeld( pOnlyIfHoldingThis ); + } + else + { + pCannon->ForceDrop(); + } + } +} + +bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity ) +{ + CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity); + + return pController ? pController->IsHoldingEntity( pHeldEntity ) : false; +} + + +float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject ) +{ + float mass = 0.0f; + CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon); + if ( pCannon ) + { + CGrabController &grab = pCannon->GetGrabController(); + mass = grab.GetSavedMass( pHeldObject ); + } + + return mass; +} + +CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon ) +{ + CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon); + if ( pCannon ) + { + CGrabController &grab = pCannon->GetGrabController(); + return grab.GetAttached(); + } + + return NULL; +} + +float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject ) +{ + float mass = 0.0f; + CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity); + if ( pController ) + { + CGrabController &grab = pController->GetGrabController(); + mass = grab.GetSavedMass( pHeldObject ); + } + return mass; +} + +#ifdef CLIENT_DLL + +extern void FX_GaussExplosion( const Vector &pos, const Vector &dir, int type ); + +void CallbackPhyscannonImpact( const CEffectData &data ) +{ + C_BaseEntity *pEnt = data.GetEntity(); + if ( pEnt == NULL ) + return; + + Vector vecAttachment; + QAngle vecAngles; + + C_BaseCombatWeapon *pWeapon = dynamic_cast<C_BaseCombatWeapon *>(pEnt); + + if ( pWeapon == NULL ) + return; + + pWeapon->GetAttachment( 1, vecAttachment, vecAngles ); + + Vector dir = ( data.m_vOrigin - vecAttachment ); + VectorNormalize( dir ); + + // Do special first-person fix-up + if ( pWeapon->GetOwner() == CBasePlayer::GetLocalPlayer() ) + { + // Translate the attachment entity to the viewmodel + C_BasePlayer *pPlayer = dynamic_cast<C_BasePlayer *>(pWeapon->GetOwner()); + + if ( pPlayer ) + { + pEnt = pPlayer->GetViewModel(); + } + + // Format attachment for first-person view! + ::FormatViewModelAttachment( vecAttachment, true ); + + // Explosions at the impact point + FX_GaussExplosion( data.m_vOrigin, -dir, 0 ); + + // Draw a beam + BeamInfo_t beamInfo; + + beamInfo.m_pStartEnt = pEnt; + beamInfo.m_nStartAttachment = 1; + beamInfo.m_pEndEnt = NULL; + beamInfo.m_nEndAttachment = -1; + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_vecEnd = data.m_vOrigin; + beamInfo.m_pszModelName = PHYSCANNON_BEAM_SPRITE; + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.1f; + beamInfo.m_flWidth = 12.0f; + beamInfo.m_flEndWidth = 4.0f; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = 0; + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 0.0f; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 30.0; + beamInfo.m_flRed = 255.0; + beamInfo.m_flGreen = 255.0; + beamInfo.m_flBlue = 255.0; + beamInfo.m_nSegments = 16; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = FBEAM_ONLYNOISEONCE; + + beams->CreateBeamEntPoint( beamInfo ); + } + else + { + // Explosion at the starting point + FX_GaussExplosion( vecAttachment, dir, 0 ); + } +} + +DECLARE_CLIENT_EFFECT( "PhyscannonImpact", CallbackPhyscannonImpact ); + +#endif diff --git a/game/shared/hl2mp/weapon_physcannon.h b/game/shared/hl2mp/weapon_physcannon.h new file mode 100644 index 0000000..03f60f8 --- /dev/null +++ b/game/shared/hl2mp/weapon_physcannon.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WEAPON_PHYSCANNON_H +#define WEAPON_PHYSCANNON_H +#ifdef _WIN32 +#pragma once +#endif + + + +//----------------------------------------------------------------------------- +// Do we have the super-phys gun? +//----------------------------------------------------------------------------- +bool PlayerHasMegaPhysCannon(); + +// force the physcannon to drop an object (if carried) +void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis ); +void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ); + +bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupController, CBaseEntity *pHeldEntity ); +float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject ); +float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject ); + +CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon ); + +#endif // WEAPON_PHYSCANNON_H diff --git a/game/shared/hl2mp/weapon_pistol.cpp b/game/shared/hl2mp/weapon_pistol.cpp new file mode 100644 index 0000000..5a9533c --- /dev/null +++ b/game/shared/hl2mp/weapon_pistol.cpp @@ -0,0 +1,339 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" +#endif + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#define PISTOL_FASTEST_REFIRE_TIME 0.1f +#define PISTOL_FASTEST_DRY_REFIRE_TIME 0.2f + +#define PISTOL_ACCURACY_SHOT_PENALTY_TIME 0.2f // Applied amount of time each shot adds to the time we must recover from +#define PISTOL_ACCURACY_MAXIMUM_PENALTY_TIME 1.5f // Maximum penalty to deal out + +#ifdef CLIENT_DLL +#define CWeaponPistol C_WeaponPistol +#endif + +//----------------------------------------------------------------------------- +// CWeaponPistol +//----------------------------------------------------------------------------- + +class CWeaponPistol : public CBaseHL2MPCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponPistol, CBaseHL2MPCombatWeapon ); + + CWeaponPistol(void); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + void Precache( void ); + void ItemPostFrame( void ); + void ItemPreFrame( void ); + void ItemBusyFrame( void ); + void PrimaryAttack( void ); + void AddViewKick( void ); + void DryFire( void ); + + void UpdatePenaltyTime( void ); + + Activity GetPrimaryAttackActivity( void ); + + virtual bool Reload( void ); + + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone; + + float ramp = RemapValClamped( m_flAccuracyPenalty, + 0.0f, + PISTOL_ACCURACY_MAXIMUM_PENALTY_TIME, + 0.0f, + 1.0f ); + + // We lerp from very accurate to inaccurate over time + VectorLerp( VECTOR_CONE_1DEGREES, VECTOR_CONE_6DEGREES, ramp, cone ); + + return cone; + } + + virtual int GetMinBurst() + { + return 1; + } + + virtual int GetMaxBurst() + { + return 3; + } + + virtual float GetFireRate( void ) + { + return 0.5f; + } + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +private: + CNetworkVar( float, m_flSoonestPrimaryAttack ); + CNetworkVar( float, m_flLastAttackTime ); + CNetworkVar( float, m_flAccuracyPenalty ); + CNetworkVar( int, m_nNumShotsFired ); + +private: + CWeaponPistol( const CWeaponPistol & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponPistol, DT_WeaponPistol ) + +BEGIN_NETWORK_TABLE( CWeaponPistol, DT_WeaponPistol ) +#ifdef CLIENT_DLL + RecvPropTime( RECVINFO( m_flSoonestPrimaryAttack ) ), + RecvPropTime( RECVINFO( m_flLastAttackTime ) ), + RecvPropFloat( RECVINFO( m_flAccuracyPenalty ) ), + RecvPropInt( RECVINFO( m_nNumShotsFired ) ), +#else + SendPropTime( SENDINFO( m_flSoonestPrimaryAttack ) ), + SendPropTime( SENDINFO( m_flLastAttackTime ) ), + SendPropFloat( SENDINFO( m_flAccuracyPenalty ) ), + SendPropInt( SENDINFO( m_nNumShotsFired ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponPistol ) + DEFINE_PRED_FIELD( m_flSoonestPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flLastAttackTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_flAccuracyPenalty, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_nNumShotsFired, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_pistol, CWeaponPistol ); +PRECACHE_WEAPON_REGISTER( weapon_pistol ); + +#ifndef CLIENT_DLL +acttable_t CWeaponPistol::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, false }, +}; + + +IMPLEMENT_ACTTABLE( CWeaponPistol ); + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWeaponPistol::CWeaponPistol( void ) +{ + m_flSoonestPrimaryAttack = gpGlobals->curtime; + m_flAccuracyPenalty = 0.0f; + + m_fMinRange1 = 24; + m_fMaxRange1 = 1500; + m_fMinRange2 = 24; + m_fMaxRange2 = 200; + + m_bFiresUnderwater = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::Precache( void ) +{ + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::DryFire( void ) +{ + WeaponSound( EMPTY ); + SendWeaponAnim( ACT_VM_DRYFIRE ); + + m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_DRY_REFIRE_TIME; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::PrimaryAttack( void ) +{ + if ( ( gpGlobals->curtime - m_flLastAttackTime ) > 0.5f ) + { + m_nNumShotsFired = 0; + } + else + { + m_nNumShotsFired++; + } + + m_flLastAttackTime = gpGlobals->curtime; + m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if( pOwner ) + { + // Each time the player fires the pistol, reset the view punch. This prevents + // the aim from 'drifting off' when the player fires very quickly. This may + // not be the ideal way to achieve this, but it's cheap and it works, which is + // great for a feature we're evaluating. (sjb) + pOwner->ViewPunchReset(); + } + + BaseClass::PrimaryAttack(); + + // Add an accuracy penalty which can move past our maximum penalty time if we're really spastic + m_flAccuracyPenalty += PISTOL_ACCURACY_SHOT_PENALTY_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::UpdatePenaltyTime( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + // Check our penalty time decay + if ( ( ( pOwner->m_nButtons & IN_ATTACK ) == false ) && ( m_flSoonestPrimaryAttack < gpGlobals->curtime ) ) + { + m_flAccuracyPenalty -= gpGlobals->frametime; + m_flAccuracyPenalty = clamp( m_flAccuracyPenalty, 0.0f, PISTOL_ACCURACY_MAXIMUM_PENALTY_TIME ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::ItemPreFrame( void ) +{ + UpdatePenaltyTime(); + + BaseClass::ItemPreFrame(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::ItemBusyFrame( void ) +{ + UpdatePenaltyTime(); + + BaseClass::ItemBusyFrame(); +} + +//----------------------------------------------------------------------------- +// Purpose: Allows firing as fast as button is pressed +//----------------------------------------------------------------------------- +void CWeaponPistol::ItemPostFrame( void ) +{ + BaseClass::ItemPostFrame(); + + if ( m_bInReload ) + return; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( pOwner->m_nButtons & IN_ATTACK2 ) + { + m_flLastAttackTime = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME; + m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME; + m_flNextPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME; + } + + //Allow a refire as fast as the player can click + if ( ( ( pOwner->m_nButtons & IN_ATTACK ) == false ) && ( m_flSoonestPrimaryAttack < gpGlobals->curtime ) ) + { + m_flNextPrimaryAttack = gpGlobals->curtime - 0.1f; + } + else if ( ( pOwner->m_nButtons & IN_ATTACK ) && ( m_flNextPrimaryAttack < gpGlobals->curtime ) && ( m_iClip1 <= 0 ) ) + { + DryFire(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +Activity CWeaponPistol::GetPrimaryAttackActivity( void ) +{ + if ( m_nNumShotsFired < 1 ) + return ACT_VM_PRIMARYATTACK; + + if ( m_nNumShotsFired < 2 ) + return ACT_VM_RECOIL1; + + if ( m_nNumShotsFired < 3 ) + return ACT_VM_RECOIL2; + + return ACT_VM_RECOIL3; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CWeaponPistol::Reload( void ) +{ + bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); + if ( fRet ) + { + WeaponSound( RELOAD ); + m_flAccuracyPenalty = 0.0f; + } + return fRet; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::AddViewKick( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + QAngle viewPunch; + + viewPunch.x = SharedRandomFloat( "pistolpax", 0.25f, 0.5f ); + viewPunch.y = SharedRandomFloat( "pistolpay", -.6f, .6f ); + viewPunch.z = 0.0f; + + //Add it to the view punch + pPlayer->ViewPunch( viewPunch ); +} diff --git a/game/shared/hl2mp/weapon_rpg.cpp b/game/shared/hl2mp/weapon_rpg.cpp new file mode 100644 index 0000000..aa3cc8f --- /dev/null +++ b/game/shared/hl2mp/weapon_rpg.cpp @@ -0,0 +1,2283 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" +#include "weapon_rpg.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" + #include "model_types.h" + #include "beamdraw.h" + #include "fx_line.h" + #include "view.h" +#else + #include "basecombatcharacter.h" + #include "movie_explosion.h" + #include "soundent.h" + #include "player.h" + #include "rope.h" + #include "vstdlib/random.h" + #include "engine/IEngineSound.h" + #include "explode.h" + #include "util.h" + #include "in_buttons.h" + #include "shake.h" + #include "te_effect_dispatch.h" + #include "triggers.h" + #include "smoke_trail.h" + #include "collisionutils.h" + #include "hl2_shareddefs.h" +#endif + +#include "debugoverlay_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define RPG_SPEED 1500 + +#ifndef CLIENT_DLL +const char *g_pLaserDotThink = "LaserThinkContext"; + +static ConVar sk_apc_missile_damage("sk_apc_missile_damage", "15"); +#define APC_MISSILE_DAMAGE sk_apc_missile_damage.GetFloat() + +#endif + +#ifdef CLIENT_DLL +#define CLaserDot C_LaserDot +#endif + +//----------------------------------------------------------------------------- +// Laser Dot +//----------------------------------------------------------------------------- +class CLaserDot : public CBaseEntity +{ + DECLARE_CLASS( CLaserDot, CBaseEntity ); +public: + + CLaserDot( void ); + ~CLaserDot( void ); + + static CLaserDot *Create( const Vector &origin, CBaseEntity *pOwner = NULL, bool bVisibleDot = true ); + + void SetTargetEntity( CBaseEntity *pTarget ) { m_hTargetEnt = pTarget; } + CBaseEntity *GetTargetEntity( void ) { return m_hTargetEnt; } + + void SetLaserPosition( const Vector &origin, const Vector &normal ); + Vector GetChasePosition(); + void TurnOn( void ); + void TurnOff( void ); + bool IsOn() const { return m_bIsOn; } + + void Toggle( void ); + + int ObjectCaps() { return (BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + + void MakeInvisible( void ); + +#ifdef CLIENT_DLL + + virtual bool IsTransparent( void ) { return true; } + virtual RenderGroup_t GetRenderGroup( void ) { return RENDER_GROUP_TRANSLUCENT_ENTITY; } + virtual int DrawModel( int flags ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual bool ShouldDraw( void ) { return (IsEffectActive(EF_NODRAW)==false); } + + CMaterialReference m_hSpriteMaterial; +#endif + +protected: + Vector m_vecSurfaceNormal; + EHANDLE m_hTargetEnt; + bool m_bVisibleLaserDot; + bool m_bIsOn; + + DECLARE_NETWORKCLASS(); + DECLARE_DATADESC(); +public: + CLaserDot *m_pNext; +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( LaserDot, DT_LaserDot ) + +BEGIN_NETWORK_TABLE( CLaserDot, DT_LaserDot ) +END_NETWORK_TABLE() + +#ifndef CLIENT_DLL + +// a list of laser dots to search quickly +CEntityClassList<CLaserDot> g_LaserDotList; +template <> CLaserDot *CEntityClassList<CLaserDot>::m_pClassList = NULL; +CLaserDot *GetLaserDotList() +{ + return g_LaserDotList.m_pClassList; +} + +BEGIN_DATADESC( CMissile ) + + DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ), + DEFINE_FIELD( m_hRocketTrail, FIELD_EHANDLE ), + DEFINE_FIELD( m_flAugerTime, FIELD_TIME ), + DEFINE_FIELD( m_flMarkDeadTime, FIELD_TIME ), + DEFINE_FIELD( m_flGracePeriodEndsAt, FIELD_TIME ), + DEFINE_FIELD( m_flDamage, FIELD_FLOAT ), + + // Function Pointers + DEFINE_FUNCTION( MissileTouch ), + DEFINE_FUNCTION( AccelerateThink ), + DEFINE_FUNCTION( AugerThink ), + DEFINE_FUNCTION( IgniteThink ), + DEFINE_FUNCTION( SeekThink ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( rpg_missile, CMissile ); + +class CWeaponRPG; + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CMissile::CMissile() +{ + m_hRocketTrail = NULL; +} + +CMissile::~CMissile() +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CMissile::Precache( void ) +{ + PrecacheModel( "models/weapons/w_missile.mdl" ); + PrecacheModel( "models/weapons/w_missile_launch.mdl" ); + PrecacheModel( "models/weapons/w_missile_closed.mdl" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CMissile::Spawn( void ) +{ + Precache(); + + SetSolid( SOLID_BBOX ); + SetModel("models/weapons/w_missile_launch.mdl"); + UTIL_SetSize( this, -Vector(4,4,4), Vector(4,4,4) ); + + SetTouch( &CMissile::MissileTouch ); + + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + SetThink( &CMissile::IgniteThink ); + + SetNextThink( gpGlobals->curtime + 0.3f ); + + m_takedamage = DAMAGE_YES; + m_iHealth = m_iMaxHealth = 100; + m_bloodColor = DONT_BLEED; + m_flGracePeriodEndsAt = 0; + + AddFlag( FL_OBJECT ); +} + + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMissile::Event_Killed( const CTakeDamageInfo &info ) +{ + m_takedamage = DAMAGE_NO; + + ShotDown(); +} + +unsigned int CMissile::PhysicsSolidMaskForEntity( void ) const +{ + return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +int CMissile::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + if ( ( info.GetDamageType() & (DMG_MISSILEDEFENSE | DMG_AIRBOAT) ) == false ) + return 0; + + bool bIsDamaged; + if( m_iHealth <= AugerHealth() ) + { + // This missile is already damaged (i.e., already running AugerThink) + bIsDamaged = true; + } + else + { + // This missile isn't damaged enough to wobble in flight yet + bIsDamaged = false; + } + + int nRetVal = BaseClass::OnTakeDamage_Alive( info ); + + if( !bIsDamaged ) + { + if ( m_iHealth <= AugerHealth() ) + { + ShotDown(); + } + } + + return nRetVal; +} + + +//----------------------------------------------------------------------------- +// Purpose: Stops any kind of tracking and shoots dumb +//----------------------------------------------------------------------------- +void CMissile::DumbFire( void ) +{ + SetThink( NULL ); + SetMoveType( MOVETYPE_FLY ); + + SetModel("models/weapons/w_missile.mdl"); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + + EmitSound( "Missile.Ignite" ); + + // Smoke trail. + CreateSmokeTrail(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::SetGracePeriod( float flGracePeriod ) +{ + m_flGracePeriodEndsAt = gpGlobals->curtime + flGracePeriod; + + // Go non-solid until the grace period ends + AddSolidFlags( FSOLID_NOT_SOLID ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMissile::AccelerateThink( void ) +{ + Vector vecForward; + + // !!!UNDONE - make this work exactly the same as HL1 RPG, lest we have looping sound bugs again! + EmitSound( "Missile.Accelerate" ); + + // SetEffects( EF_LIGHT ); + + AngleVectors( GetLocalAngles(), &vecForward ); + SetAbsVelocity( vecForward * RPG_SPEED ); + + SetThink( &CMissile::SeekThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + +#define AUGER_YDEVIANCE 20.0f +#define AUGER_XDEVIANCEUP 8.0f +#define AUGER_XDEVIANCEDOWN 1.0f + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMissile::AugerThink( void ) +{ + // If we've augered long enough, then just explode + if ( m_flAugerTime < gpGlobals->curtime ) + { + Explode(); + return; + } + + if ( m_flMarkDeadTime < gpGlobals->curtime ) + { + m_lifeState = LIFE_DYING; + } + + QAngle angles = GetLocalAngles(); + + angles.y += random->RandomFloat( -AUGER_YDEVIANCE, AUGER_YDEVIANCE ); + angles.x += random->RandomFloat( -AUGER_XDEVIANCEDOWN, AUGER_XDEVIANCEUP ); + + SetLocalAngles( angles ); + + Vector vecForward; + + AngleVectors( GetLocalAngles(), &vecForward ); + + SetAbsVelocity( vecForward * 1000.0f ); + + SetNextThink( gpGlobals->curtime + 0.05f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Causes the missile to spiral to the ground and explode, due to damage +//----------------------------------------------------------------------------- +void CMissile::ShotDown( void ) +{ + CEffectData data; + data.m_vOrigin = GetAbsOrigin(); + + DispatchEffect( "RPGShotDown", data ); + + if ( m_hRocketTrail != NULL ) + { + m_hRocketTrail->m_bDamaged = true; + } + + SetThink( &CMissile::AugerThink ); + SetNextThink( gpGlobals->curtime ); + m_flAugerTime = gpGlobals->curtime + 1.5f; + m_flMarkDeadTime = gpGlobals->curtime + 0.75; + + // Let the RPG start reloading immediately + if ( m_hOwner != NULL ) + { + m_hOwner->NotifyRocketDied(); + m_hOwner = NULL; + } +} + + +//----------------------------------------------------------------------------- +// The actual explosion +//----------------------------------------------------------------------------- +void CMissile::DoExplosion( void ) +{ + // Explode + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), GetDamage(), GetDamage() * 2, + SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::Explode( void ) +{ + // Don't explode against the skybox. Just pretend that + // the missile flies off into the distance. + Vector forward; + + GetVectors( &forward, NULL, NULL ); + + trace_t tr; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + forward * 16, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + m_takedamage = DAMAGE_NO; + SetSolid( SOLID_NONE ); + if( tr.fraction == 1.0 || !(tr.surface.flags & SURF_SKY) ) + { + DoExplosion(); + } + + if( m_hRocketTrail ) + { + m_hRocketTrail->SetLifetime(0.1f); + m_hRocketTrail = NULL; + } + + if ( m_hOwner != NULL ) + { + m_hOwner->NotifyRocketDied(); + m_hOwner = NULL; + } + + StopSound( "Missile.Ignite" ); + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CMissile::MissileTouch( CBaseEntity *pOther ) +{ + Assert( pOther ); + + // Don't touch triggers (but DO hit weapons) + if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) + return; + + Explode(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::CreateSmokeTrail( void ) +{ + if ( m_hRocketTrail ) + return; + + // Smoke trail. + if ( (m_hRocketTrail = RocketTrail::CreateRocketTrail()) != NULL ) + { + m_hRocketTrail->m_Opacity = 0.2f; + m_hRocketTrail->m_SpawnRate = 100; + m_hRocketTrail->m_ParticleLifetime = 0.5f; + m_hRocketTrail->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); + m_hRocketTrail->m_EndColor.Init( 0.0, 0.0, 0.0 ); + m_hRocketTrail->m_StartSize = 8; + m_hRocketTrail->m_EndSize = 32; + m_hRocketTrail->m_SpawnRadius = 4; + m_hRocketTrail->m_MinSpeed = 2; + m_hRocketTrail->m_MaxSpeed = 16; + + m_hRocketTrail->SetLifetime( 999 ); + m_hRocketTrail->FollowEntity( this, "0" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::IgniteThink( void ) +{ + SetMoveType( MOVETYPE_FLY ); + SetModel("models/weapons/w_missile.mdl"); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + //TODO: Play opening sound + + Vector vecForward; + + EmitSound( "Missile.Ignite" ); + + AngleVectors( GetLocalAngles(), &vecForward ); + SetAbsVelocity( vecForward * RPG_SPEED ); + + SetThink( &CMissile::SeekThink ); + SetNextThink( gpGlobals->curtime ); + + if ( m_hOwner && m_hOwner->GetOwner() ) + { + CBasePlayer *pPlayer = ToBasePlayer( m_hOwner->GetOwner() ); + + color32 white = { 255,225,205,64 }; + UTIL_ScreenFade( pPlayer, white, 0.1f, 0.0f, FFADE_IN ); + } + + CreateSmokeTrail(); +} + + +//----------------------------------------------------------------------------- +// Gets the shooting position +//----------------------------------------------------------------------------- +void CMissile::GetShootPosition( CLaserDot *pLaserDot, Vector *pShootPosition ) +{ + if ( pLaserDot->GetOwnerEntity() != NULL ) + { + //FIXME: Do we care this isn't exactly the muzzle position? + *pShootPosition = pLaserDot->GetOwnerEntity()->WorldSpaceCenter(); + } + else + { + *pShootPosition = pLaserDot->GetChasePosition(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#define RPG_HOMING_SPEED 0.125f + +void CMissile::ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ) +{ + *pHomingSpeed = RPG_HOMING_SPEED; + if ( pLaserDot->GetTargetEntity() ) + { + *pActualDotPosition = pLaserDot->GetChasePosition(); + return; + } + + Vector vLaserStart; + GetShootPosition( pLaserDot, &vLaserStart ); + + //Get the laser's vector + Vector vLaserDir; + VectorSubtract( pLaserDot->GetChasePosition(), vLaserStart, vLaserDir ); + + //Find the length of the current laser + float flLaserLength = VectorNormalize( vLaserDir ); + + //Find the length from the missile to the laser's owner + float flMissileLength = GetAbsOrigin().DistTo( vLaserStart ); + + //Find the length from the missile to the laser's position + Vector vecTargetToMissile; + VectorSubtract( GetAbsOrigin(), pLaserDot->GetChasePosition(), vecTargetToMissile ); + float flTargetLength = VectorNormalize( vecTargetToMissile ); + + // See if we should chase the line segment nearest us + if ( ( flMissileLength < flLaserLength ) || ( flTargetLength <= 512.0f ) ) + { + *pActualDotPosition = UTIL_PointOnLineNearestPoint( vLaserStart, pLaserDot->GetChasePosition(), GetAbsOrigin() ); + *pActualDotPosition += ( vLaserDir * 256.0f ); + } + else + { + // Otherwise chase the dot + *pActualDotPosition = pLaserDot->GetChasePosition(); + } + +// NDebugOverlay::Line( pLaserDot->GetChasePosition(), vLaserStart, 0, 255, 0, true, 0.05f ); +// NDebugOverlay::Line( GetAbsOrigin(), *pActualDotPosition, 255, 0, 0, true, 0.05f ); +// NDebugOverlay::Cross3D( *pActualDotPosition, -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.05f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMissile::SeekThink( void ) +{ + CBaseEntity *pBestDot = NULL; + float flBestDist = MAX_TRACE_LENGTH; + float dotDist; + + // If we have a grace period, go solid when it ends + if ( m_flGracePeriodEndsAt ) + { + if ( m_flGracePeriodEndsAt < gpGlobals->curtime ) + { + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_flGracePeriodEndsAt = 0; + } + } + + //Search for all dots relevant to us + for( CLaserDot *pEnt = GetLaserDotList(); pEnt != NULL; pEnt = pEnt->m_pNext ) + { + if ( !pEnt->IsOn() ) + continue; + + if ( pEnt->GetOwnerEntity() != GetOwnerEntity() ) + continue; + + dotDist = (GetAbsOrigin() - pEnt->GetAbsOrigin()).Length(); + + //Find closest + if ( dotDist < flBestDist ) + { + pBestDot = pEnt; + flBestDist = dotDist; + } + } + + //If we have a dot target + if ( pBestDot == NULL ) + { + //Think as soon as possible + SetNextThink( gpGlobals->curtime ); + return; + } + + CLaserDot *pLaserDot = (CLaserDot *)pBestDot; + Vector targetPos; + + float flHomingSpeed; + Vector vecLaserDotPosition; + ComputeActualDotPosition( pLaserDot, &targetPos, &flHomingSpeed ); + + if ( IsSimulatingOnAlternateTicks() ) + flHomingSpeed *= 2; + + Vector vTargetDir; + VectorSubtract( targetPos, GetAbsOrigin(), vTargetDir ); + float flDist = VectorNormalize( vTargetDir ); + + Vector vDir = GetAbsVelocity(); + float flSpeed = VectorNormalize( vDir ); + Vector vNewVelocity = vDir; + if ( gpGlobals->frametime > 0.0f ) + { + if ( flSpeed != 0 ) + { + vNewVelocity = ( flHomingSpeed * vTargetDir ) + ( ( 1 - flHomingSpeed ) * vDir ); + + // This computation may happen to cancel itself out exactly. If so, slam to targetdir. + if ( VectorNormalize( vNewVelocity ) < 1e-3 ) + { + vNewVelocity = (flDist != 0) ? vTargetDir : vDir; + } + } + else + { + vNewVelocity = vTargetDir; + } + } + + QAngle finalAngles; + VectorAngles( vNewVelocity, finalAngles ); + SetAbsAngles( finalAngles ); + + vNewVelocity *= flSpeed; + SetAbsVelocity( vNewVelocity ); + + if( GetAbsVelocity() == vec3_origin ) + { + // Strange circumstances have brought this missile to halt. Just blow it up. + Explode(); + return; + } + + // Think as soon as possible + SetNextThink( gpGlobals->curtime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// +// Input : &vecOrigin - +// &vecAngles - +// NULL - +// +// Output : CMissile +//----------------------------------------------------------------------------- +CMissile *CMissile::Create( const Vector &vecOrigin, const QAngle &vecAngles, edict_t *pentOwner = NULL ) +{ + //CMissile *pMissile = (CMissile *)CreateEntityByName("rpg_missile" ); + CMissile *pMissile = (CMissile *) CBaseEntity::Create( "rpg_missile", vecOrigin, vecAngles, CBaseEntity::Instance( pentOwner ) ); + pMissile->SetOwnerEntity( Instance( pentOwner ) ); + pMissile->Spawn(); + pMissile->AddEffects( EF_NOSHADOW ); + + Vector vecForward; + AngleVectors( vecAngles, &vecForward ); + + pMissile->SetAbsVelocity( vecForward * 300 + Vector( 0,0, 128 ) ); + + return pMissile; +} + + + +//----------------------------------------------------------------------------- +// This entity is used to create little force boxes that the helicopter +// should avoid. +//----------------------------------------------------------------------------- +class CInfoAPCMissileHint : public CBaseEntity +{ + DECLARE_DATADESC(); + +public: + DECLARE_CLASS( CInfoAPCMissileHint, CBaseEntity ); + + virtual void Spawn( ); + virtual void Activate(); + virtual void UpdateOnRemove(); + + static CBaseEntity *FindAimTarget( CBaseEntity *pMissile, const char *pTargetName, + const Vector &vecCurrentTargetPos, const Vector &vecCurrentTargetVel ); + +private: + EHANDLE m_hTarget; + + typedef CHandle<CInfoAPCMissileHint> APCMissileHintHandle_t; + static CUtlVector< APCMissileHintHandle_t > s_APCMissileHints; +}; + + +//----------------------------------------------------------------------------- +// +// This entity is used to create little force boxes that the helicopters should avoid. +// +//----------------------------------------------------------------------------- +CUtlVector< CInfoAPCMissileHint::APCMissileHintHandle_t > CInfoAPCMissileHint::s_APCMissileHints; + +LINK_ENTITY_TO_CLASS( info_apc_missile_hint, CInfoAPCMissileHint ); + +BEGIN_DATADESC( CInfoAPCMissileHint ) + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Spawn, remove +//----------------------------------------------------------------------------- +void CInfoAPCMissileHint::Spawn( ) +{ + SetModel( STRING( GetModelName() ) ); + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_NOT_SOLID ); + AddEffects( EF_NODRAW ); +} + +void CInfoAPCMissileHint::Activate( ) +{ + BaseClass::Activate(); + + m_hTarget = gEntList.FindEntityByName( NULL, m_target ); + if ( m_hTarget == NULL ) + { + DevWarning( "%s: Could not find target '%s'!\n", GetClassname(), STRING( m_target ) ); + } + else + { + s_APCMissileHints.AddToTail( this ); + } +} + +void CInfoAPCMissileHint::UpdateOnRemove( ) +{ + s_APCMissileHints.FindAndRemove( this ); + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Where are how should we avoid? +//----------------------------------------------------------------------------- +#define HINT_PREDICTION_TIME 3.0f + +CBaseEntity *CInfoAPCMissileHint::FindAimTarget( CBaseEntity *pMissile, const char *pTargetName, + const Vector &vecCurrentEnemyPos, const Vector &vecCurrentEnemyVel ) +{ + if ( !pTargetName ) + return NULL; + + float flOOSpeed = pMissile->GetAbsVelocity().Length(); + if ( flOOSpeed != 0.0f ) + { + flOOSpeed = 1.0f / flOOSpeed; + } + + for ( int i = s_APCMissileHints.Count(); --i >= 0; ) + { + CInfoAPCMissileHint *pHint = s_APCMissileHints[i]; + if ( !pHint->NameMatches( pTargetName ) ) + continue; + + if ( !pHint->m_hTarget ) + continue; + + Vector vecMissileToHint, vecMissileToEnemy; + VectorSubtract( pHint->m_hTarget->WorldSpaceCenter(), pMissile->GetAbsOrigin(), vecMissileToHint ); + VectorSubtract( vecCurrentEnemyPos, pMissile->GetAbsOrigin(), vecMissileToEnemy ); + float flDistMissileToHint = VectorNormalize( vecMissileToHint ); + VectorNormalize( vecMissileToEnemy ); + if ( DotProduct( vecMissileToHint, vecMissileToEnemy ) < 0.866f ) + continue; + + // Determine when the target will be inside the volume. + // Project at most 3 seconds in advance + Vector vecRayDelta; + VectorMultiply( vecCurrentEnemyVel, HINT_PREDICTION_TIME, vecRayDelta ); + + BoxTraceInfo_t trace; + if ( !IntersectRayWithOBB( vecCurrentEnemyPos, vecRayDelta, pHint->CollisionProp()->CollisionToWorldTransform(), + pHint->CollisionProp()->OBBMins(), pHint->CollisionProp()->OBBMaxs(), 0.0f, &trace )) + { + continue; + } + + // Determine the amount of time it would take the missile to reach the target + // If we can reach the target within the time it takes for the enemy to reach the + float tSqr = flDistMissileToHint * flOOSpeed / HINT_PREDICTION_TIME; + if ( (tSqr < (trace.t1 * trace.t1)) || (tSqr > (trace.t2 * trace.t2)) ) + continue; + + return pHint->m_hTarget; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// a list of missiles to search quickly +//----------------------------------------------------------------------------- +CEntityClassList<CAPCMissile> g_APCMissileList; +template <> CAPCMissile *CEntityClassList<CAPCMissile>::m_pClassList = NULL; +CAPCMissile *GetAPCMissileList() +{ + return g_APCMissileList.m_pClassList; +} + +//----------------------------------------------------------------------------- +// Finds apc missiles in cone +//----------------------------------------------------------------------------- +CAPCMissile *FindAPCMissileInCone( const Vector &vecOrigin, const Vector &vecDirection, float flAngle ) +{ + float flCosAngle = cos( DEG2RAD( flAngle ) ); + for( CAPCMissile *pEnt = GetAPCMissileList(); pEnt != NULL; pEnt = pEnt->m_pNext ) + { + if ( !pEnt->IsSolid() ) + continue; + + Vector vecDelta; + VectorSubtract( pEnt->GetAbsOrigin(), vecOrigin, vecDelta ); + VectorNormalize( vecDelta ); + float flDot = DotProduct( vecDelta, vecDirection ); + if ( flDot > flCosAngle ) + return pEnt; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// +// Specialized version of the missile +// +//----------------------------------------------------------------------------- +#define MAX_HOMING_DISTANCE 2250.0f +#define MIN_HOMING_DISTANCE 1250.0f +#define MAX_NEAR_HOMING_DISTANCE 1750.0f +#define MIN_NEAR_HOMING_DISTANCE 1000.0f +#define DOWNWARD_BLEND_TIME_START 0.2f +#define MIN_HEIGHT_DIFFERENCE 250.0f +#define MAX_HEIGHT_DIFFERENCE 550.0f +#define CORRECTION_TIME 0.2f +#define APC_LAUNCH_HOMING_SPEED 0.1f +#define APC_HOMING_SPEED 0.025f +#define HOMING_SPEED_ACCEL 0.01f + +BEGIN_DATADESC( CAPCMissile ) + + DEFINE_FIELD( m_flReachedTargetTime, FIELD_TIME ), + DEFINE_FIELD( m_flIgnitionTime, FIELD_TIME ), + DEFINE_FIELD( m_bGuidingDisabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hSpecificTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_strHint, FIELD_STRING ), + DEFINE_FIELD( m_flLastHomingSpeed, FIELD_FLOAT ), +// DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ), + + DEFINE_THINKFUNC( BeginSeekThink ), + DEFINE_THINKFUNC( AugerStartThink ), + DEFINE_THINKFUNC( ExplodeThink ), + + DEFINE_FUNCTION( APCMissileTouch ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( apc_missile, CAPCMissile ); + +CAPCMissile *CAPCMissile::Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, CBaseEntity *pOwner ) +{ + CAPCMissile *pMissile = (CAPCMissile *)CBaseEntity::Create( "apc_missile", vecOrigin, vecAngles, pOwner ); + pMissile->SetOwnerEntity( pOwner ); + pMissile->Spawn(); + pMissile->SetAbsVelocity( vecVelocity ); + pMissile->AddFlag( FL_NOTARGET ); + pMissile->AddEffects( EF_NOSHADOW ); + return pMissile; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CAPCMissile::CAPCMissile() +{ + g_APCMissileList.Insert( this ); +} + +CAPCMissile::~CAPCMissile() +{ + g_APCMissileList.Remove( this ); +} + + +//----------------------------------------------------------------------------- +// Shared initialization code +//----------------------------------------------------------------------------- +void CAPCMissile::Init() +{ + SetMoveType( MOVETYPE_FLY ); + SetModel("models/weapons/w_missile.mdl"); + UTIL_SetSize( this, vec3_origin, vec3_origin ); + CreateSmokeTrail(); + SetTouch( &CAPCMissile::APCMissileTouch ); + m_flLastHomingSpeed = APC_HOMING_SPEED; +} + + +//----------------------------------------------------------------------------- +// For hitting a specific target +//----------------------------------------------------------------------------- +void CAPCMissile::AimAtSpecificTarget( CBaseEntity *pTarget ) +{ + m_hSpecificTarget = pTarget; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CAPCMissile::APCMissileTouch( CBaseEntity *pOther ) +{ + Assert( pOther ); + if ( !pOther->IsSolid() && !pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) ) + return; + + Explode(); +} + + +//----------------------------------------------------------------------------- +// Specialized version of the missile +//----------------------------------------------------------------------------- +void CAPCMissile::IgniteDelay( void ) +{ + m_flIgnitionTime = gpGlobals->curtime + 0.3f; + + SetThink( &CAPCMissile::BeginSeekThink ); + SetNextThink( m_flIgnitionTime ); + Init(); + AddSolidFlags( FSOLID_NOT_SOLID ); +} + +void CAPCMissile::AugerDelay( float flDelay ) +{ + m_flIgnitionTime = gpGlobals->curtime; + SetThink( &CAPCMissile::AugerStartThink ); + SetNextThink( gpGlobals->curtime + flDelay ); + Init(); + DisableGuiding(); +} + +void CAPCMissile::AugerStartThink() +{ + if ( m_hRocketTrail != NULL ) + { + m_hRocketTrail->m_bDamaged = true; + } + m_flAugerTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); + SetThink( &CAPCMissile::AugerThink ); + SetNextThink( gpGlobals->curtime ); +} + +void CAPCMissile::ExplodeDelay( float flDelay ) +{ + m_flIgnitionTime = gpGlobals->curtime; + SetThink( &CAPCMissile::ExplodeThink ); + SetNextThink( gpGlobals->curtime + flDelay ); + Init(); + DisableGuiding(); +} + + +void CAPCMissile::BeginSeekThink( void ) +{ + RemoveSolidFlags( FSOLID_NOT_SOLID ); + SetThink( &CAPCMissile::SeekThink ); + SetNextThink( gpGlobals->curtime ); +} + +void CAPCMissile::ExplodeThink() +{ + DoExplosion(); +} + +//----------------------------------------------------------------------------- +// Health lost at which augering starts +//----------------------------------------------------------------------------- +int CAPCMissile::AugerHealth() +{ + return m_iMaxHealth - 25; +} + + +//----------------------------------------------------------------------------- +// Health lost at which augering starts +//----------------------------------------------------------------------------- +void CAPCMissile::DisableGuiding() +{ + m_bGuidingDisabled = true; +} + + +//----------------------------------------------------------------------------- +// Guidance hints +//----------------------------------------------------------------------------- +void CAPCMissile::SetGuidanceHint( const char *pHintName ) +{ + m_strHint = MAKE_STRING( pHintName ); +} + + +//----------------------------------------------------------------------------- +// The actual explosion +//----------------------------------------------------------------------------- +void CAPCMissile::DoExplosion( void ) +{ + if ( GetWaterLevel() != 0 ) + { + CEffectData data; + data.m_vOrigin = WorldSpaceCenter(); + data.m_flMagnitude = 128; + data.m_flScale = 128; + data.m_fFlags = 0; + DispatchEffect( "WaterSurfaceExplosion", data ); + } + else + { + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), + APC_MISSILE_DAMAGE, 100, true, 20000 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAPCMissile::ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ) +{ + Vector vecTarget = pTarget->BodyTarget( vecShootPosition, false ); + float flShotSpeed = GetAbsVelocity().Length(); + if ( flShotSpeed == 0 ) + { + *pLeadPosition = vecTarget; + return; + } + + Vector vecVelocity = pTarget->GetSmoothedVelocity(); + vecVelocity.z = 0.0f; + float flTargetSpeed = VectorNormalize( vecVelocity ); + Vector vecDelta; + VectorSubtract( vecShootPosition, vecTarget, vecDelta ); + float flTargetToShooter = VectorNormalize( vecDelta ); + float flCosTheta = DotProduct( vecDelta, vecVelocity ); + + // Law of cosines... z^2 = x^2 + y^2 - 2xy cos Theta + // where z = flShooterToPredictedTargetPosition = flShotSpeed * predicted time + // x = flTargetSpeed * predicted time + // y = flTargetToShooter + // solve for predicted time using at^2 + bt + c = 0, t = (-b +/- sqrt( b^2 - 4ac )) / 2a + float a = flTargetSpeed * flTargetSpeed - flShotSpeed * flShotSpeed; + float b = -2.0f * flTargetToShooter * flCosTheta * flTargetSpeed; + float c = flTargetToShooter * flTargetToShooter; + + float flDiscrim = b*b - 4*a*c; + if (flDiscrim < 0) + { + *pLeadPosition = vecTarget; + return; + } + + flDiscrim = sqrt(flDiscrim); + float t = (-b + flDiscrim) / (2.0f * a); + float t2 = (-b - flDiscrim) / (2.0f * a); + if ( t < t2 ) + { + t = t2; + } + + if ( t <= 0.0f ) + { + *pLeadPosition = vecTarget; + return; + } + + VectorMA( vecTarget, flTargetSpeed * t, vecVelocity, *pLeadPosition ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAPCMissile::ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ) +{ + if ( m_bGuidingDisabled ) + { + *pActualDotPosition = GetAbsOrigin(); + *pHomingSpeed = 0.0f; + m_flLastHomingSpeed = *pHomingSpeed; + return; + } + + if ( ( m_strHint != NULL_STRING ) && (!m_hSpecificTarget) ) + { + Vector vecOrigin, vecVelocity; + CBaseEntity *pTarget = pLaserDot->GetTargetEntity(); + if ( pTarget ) + { + vecOrigin = pTarget->BodyTarget( GetAbsOrigin(), false ); + vecVelocity = pTarget->GetSmoothedVelocity(); + } + else + { + vecOrigin = pLaserDot->GetChasePosition(); + vecVelocity = vec3_origin; + } + + m_hSpecificTarget = CInfoAPCMissileHint::FindAimTarget( this, STRING( m_strHint ), vecOrigin, vecVelocity ); + } + + CBaseEntity *pLaserTarget = m_hSpecificTarget ? m_hSpecificTarget.Get() : pLaserDot->GetTargetEntity(); + if ( !pLaserTarget ) + { + BaseClass::ComputeActualDotPosition( pLaserDot, pActualDotPosition, pHomingSpeed ); + m_flLastHomingSpeed = *pHomingSpeed; + return; + } + + if ( pLaserTarget->ClassMatches( "npc_bullseye" ) ) + { + if ( m_flLastHomingSpeed != RPG_HOMING_SPEED ) + { + if (m_flLastHomingSpeed > RPG_HOMING_SPEED) + { + m_flLastHomingSpeed -= HOMING_SPEED_ACCEL * UTIL_GetSimulationInterval(); + if ( m_flLastHomingSpeed < RPG_HOMING_SPEED ) + { + m_flLastHomingSpeed = RPG_HOMING_SPEED; + } + } + else + { + m_flLastHomingSpeed += HOMING_SPEED_ACCEL * UTIL_GetSimulationInterval(); + if ( m_flLastHomingSpeed > RPG_HOMING_SPEED ) + { + m_flLastHomingSpeed = RPG_HOMING_SPEED; + } + } + } + *pHomingSpeed = m_flLastHomingSpeed; + *pActualDotPosition = pLaserTarget->WorldSpaceCenter(); + return; + } + + Vector vLaserStart; + GetShootPosition( pLaserDot, &vLaserStart ); + *pHomingSpeed = APC_LAUNCH_HOMING_SPEED; + + //Get the laser's vector + Vector vecTargetPosition = pLaserTarget->BodyTarget( GetAbsOrigin(), false ); + + // Compute leading position + Vector vecLeadPosition; + ComputeLeadingPosition( GetAbsOrigin(), pLaserTarget, &vecLeadPosition ); + + Vector vecTargetToMissile, vecTargetToShooter; + VectorSubtract( GetAbsOrigin(), vecTargetPosition, vecTargetToMissile ); + VectorSubtract( vLaserStart, vecTargetPosition, vecTargetToShooter ); + + *pActualDotPosition = vecLeadPosition; + + float flMinHomingDistance = MIN_HOMING_DISTANCE; + float flMaxHomingDistance = MAX_HOMING_DISTANCE; + float flBlendTime = gpGlobals->curtime - m_flIgnitionTime; + if ( flBlendTime > DOWNWARD_BLEND_TIME_START ) + { + if ( m_flReachedTargetTime != 0.0f ) + { + *pHomingSpeed = APC_HOMING_SPEED; + float flDeltaTime = clamp( gpGlobals->curtime - m_flReachedTargetTime, 0.0f, CORRECTION_TIME ); + *pHomingSpeed = SimpleSplineRemapVal( flDeltaTime, 0.0f, CORRECTION_TIME, 0.2f, *pHomingSpeed ); + flMinHomingDistance = SimpleSplineRemapVal( flDeltaTime, 0.0f, CORRECTION_TIME, MIN_NEAR_HOMING_DISTANCE, flMinHomingDistance ); + flMaxHomingDistance = SimpleSplineRemapVal( flDeltaTime, 0.0f, CORRECTION_TIME, MAX_NEAR_HOMING_DISTANCE, flMaxHomingDistance ); + } + else + { + flMinHomingDistance = MIN_NEAR_HOMING_DISTANCE; + flMaxHomingDistance = MAX_NEAR_HOMING_DISTANCE; + Vector vecDelta; + VectorSubtract( GetAbsOrigin(), *pActualDotPosition, vecDelta ); + if ( vecDelta.z > MIN_HEIGHT_DIFFERENCE ) + { + float flClampedHeight = clamp( vecDelta.z, MIN_HEIGHT_DIFFERENCE, MAX_HEIGHT_DIFFERENCE ); + float flHeightAdjustFactor = SimpleSplineRemapVal( flClampedHeight, MIN_HEIGHT_DIFFERENCE, MAX_HEIGHT_DIFFERENCE, 0.0f, 1.0f ); + + vecDelta.z = 0.0f; + float flDist = VectorNormalize( vecDelta ); + + float flForwardOffset = 2000.0f; + if ( flDist > flForwardOffset ) + { + Vector vecNewPosition; + VectorMA( GetAbsOrigin(), -flForwardOffset, vecDelta, vecNewPosition ); + vecNewPosition.z = pActualDotPosition->z; + + VectorLerp( *pActualDotPosition, vecNewPosition, flHeightAdjustFactor, *pActualDotPosition ); + } + } + else + { + m_flReachedTargetTime = gpGlobals->curtime; + } + } + + // Allows for players right at the edge of rocket range to be threatened + if ( flBlendTime > 0.6f ) + { + float flTargetLength = GetAbsOrigin().DistTo( pLaserTarget->WorldSpaceCenter() ); + flTargetLength = clamp( flTargetLength, flMinHomingDistance, flMaxHomingDistance ); + *pHomingSpeed = SimpleSplineRemapVal( flTargetLength, flMaxHomingDistance, flMinHomingDistance, *pHomingSpeed, 0.01f ); + } + } + + float flDot = DotProduct2D( vecTargetToShooter.AsVector2D(), vecTargetToMissile.AsVector2D() ); + if ( ( flDot < 0 ) || m_bGuidingDisabled ) + { + *pHomingSpeed = 0.0f; + } + + m_flLastHomingSpeed = *pHomingSpeed; + +// NDebugOverlay::Line( vecLeadPosition, GetAbsOrigin(), 0, 255, 0, true, 0.05f ); +// NDebugOverlay::Line( GetAbsOrigin(), *pActualDotPosition, 255, 0, 0, true, 0.05f ); +// NDebugOverlay::Cross3D( *pActualDotPosition, -Vector(4,4,4), Vector(4,4,4), 255, 0, 0, true, 0.05f ); +} + +#endif + +#define RPG_BEAM_SPRITE "effects/laser1.vmt" +#define RPG_BEAM_SPRITE_NOZ "effects/laser1_noz.vmt" +#define RPG_LASER_SPRITE "sprites/redglow1" + +//============================================================================= +// RPG +//============================================================================= + +LINK_ENTITY_TO_CLASS( weapon_rpg, CWeaponRPG ); +PRECACHE_WEAPON_REGISTER(weapon_rpg); + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponRPG, DT_WeaponRPG ) + +#ifdef CLIENT_DLL +void RecvProxy_MissileDied( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + CWeaponRPG *pRPG = ((CWeaponRPG*)pStruct); + + RecvProxy_IntToEHandle( pData, pStruct, pOut ); + + CBaseEntity *pNewMissile = pRPG->GetMissile(); + + if ( pNewMissile == NULL ) + { + if ( pRPG->GetOwner() && pRPG->GetOwner()->GetActiveWeapon() == pRPG ) + { + pRPG->NotifyRocketDied(); + } + } +} + +#endif + +BEGIN_NETWORK_TABLE( CWeaponRPG, DT_WeaponRPG ) +#ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bInitialStateUpdate ) ), + RecvPropBool( RECVINFO( m_bGuiding ) ), + RecvPropBool( RECVINFO( m_bHideGuiding ) ), + RecvPropEHandle( RECVINFO( m_hMissile ), RecvProxy_MissileDied ), + RecvPropVector( RECVINFO( m_vecLaserDot ) ), +#else + SendPropBool( SENDINFO( m_bInitialStateUpdate ) ), + SendPropBool( SENDINFO( m_bGuiding ) ), + SendPropBool( SENDINFO( m_bHideGuiding ) ), + SendPropEHandle( SENDINFO( m_hMissile ) ), + SendPropVector( SENDINFO( m_vecLaserDot ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL + +BEGIN_PREDICTION_DATA( CWeaponRPG ) + DEFINE_PRED_FIELD( m_bInitialStateUpdate, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bGuiding, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bHideGuiding, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() + +#endif + +#ifndef CLIENT_DLL +acttable_t CWeaponRPG::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_RPG, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_RPG, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_RPG, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_RPG, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_RPG, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_RPG, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_RPG, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponRPG); + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponRPG::CWeaponRPG() +{ + m_bReloadsSingly = true; + m_bInitialStateUpdate= false; + m_bHideGuiding = false; + m_bGuiding = false; + + m_fMinRange1 = m_fMinRange2 = 40*12; + m_fMaxRange1 = m_fMaxRange2 = 500*12; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWeaponRPG::~CWeaponRPG() +{ +#ifndef CLIENT_DLL + if ( m_hLaserDot != NULL ) + { + UTIL_Remove( m_hLaserDot ); + m_hLaserDot = NULL; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Missile.Ignite" ); + PrecacheScriptSound( "Missile.Accelerate" ); + + // Laser dot... + PrecacheModel( "sprites/redglow1.vmt" ); + PrecacheModel( RPG_LASER_SPRITE ); + PrecacheModel( RPG_BEAM_SPRITE ); + PrecacheModel( RPG_BEAM_SPRITE_NOZ ); + +#ifndef CLIENT_DLL + UTIL_PrecacheOther( "rpg_missile" ); +#endif + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Activate( void ) +{ + BaseClass::Activate(); + + // Restore the laser pointer after transition + if ( m_bGuiding ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + if ( pOwner->GetActiveWeapon() == this ) + { + StartGuiding(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::HasAnyAmmo( void ) +{ + if ( m_hMissile != NULL ) + return true; + + return BaseClass::HasAnyAmmo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponRPG::WeaponShouldBeLowered( void ) +{ + // Lower us if we're out of ammo + if ( !HasAnyAmmo() ) + return true; + + return BaseClass::WeaponShouldBeLowered(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::PrimaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if (!pPlayer) + return; + + // Can't have an active missile out + if ( m_hMissile != NULL ) + return; + + // Can't be reloading + if ( GetActivity() == ACT_VM_RELOAD ) + return; + + Vector vecOrigin; + Vector vecForward; + + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner == NULL ) + return; + + Vector vForward, vRight, vUp; + + pOwner->EyeVectors( &vForward, &vRight, &vUp ); + + Vector muzzlePoint = pOwner->Weapon_ShootPosition() + vForward * 12.0f + vRight * 6.0f + vUp * -3.0f; + +#ifndef CLIENT_DLL + QAngle vecAngles; + VectorAngles( vForward, vecAngles ); + + CMissile *pMissile = CMissile::Create( muzzlePoint, vecAngles, GetOwner()->edict() ); + pMissile->m_hOwner = this; + + // If the shot is clear to the player, give the missile a grace period + trace_t tr; + Vector vecEye = pOwner->EyePosition(); + UTIL_TraceLine( vecEye, vecEye + vForward * 128, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction == 1.0 ) + { + pMissile->SetGracePeriod( 0.3 ); + } + + pMissile->SetDamage( GetHL2MPWpnData().m_iPlayerDamage ); + + m_hMissile = pMissile; +#endif + + DecrementAmmo( GetOwner() ); + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + WeaponSound( SINGLE ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOwner - +//----------------------------------------------------------------------------- +void CWeaponRPG::DecrementAmmo( CBaseCombatCharacter *pOwner ) +{ + // Take away our primary ammo type + pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : state - +//----------------------------------------------------------------------------- +void CWeaponRPG::SuppressGuiding( bool state ) +{ + m_bHideGuiding = state; + +#ifndef CLIENT_DLL + + if ( m_hLaserDot == NULL ) + { + StartGuiding(); + + //STILL!? + if ( m_hLaserDot == NULL ) + return; + } + + if ( state ) + { + m_hLaserDot->TurnOff(); + } + else + { + m_hLaserDot->TurnOn(); + } +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Override this if we're guiding a missile currently +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponRPG::Lower( void ) +{ + if ( m_hMissile != NULL ) + return false; + + return BaseClass::Lower(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::ItemPostFrame( void ) +{ + BaseClass::ItemPostFrame(); + + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + //If we're pulling the weapon out for the first time, wait to draw the laser + if ( ( m_bInitialStateUpdate ) && ( GetActivity() != ACT_VM_DRAW ) ) + { + StartGuiding(); + m_bInitialStateUpdate = false; + } + + // Supress our guiding effects if we're lowered + if ( GetIdealActivity() == ACT_VM_IDLE_LOWERED ) + { + SuppressGuiding(); + } + else + { + SuppressGuiding( false ); + } + + //Move the laser + UpdateLaserPosition(); + + if ( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0 && m_hMissile == NULL ) + { + StopGuiding(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Vector +//----------------------------------------------------------------------------- +Vector CWeaponRPG::GetLaserPosition( void ) +{ +#ifndef CLIENT_DLL + CreateLaserPointer(); + + if ( m_hLaserDot != NULL ) + return m_hLaserDot->GetAbsOrigin(); + + //FIXME: The laser dot sprite is not active, this code should not be allowed! + assert(0); +#endif + return vec3_origin; +} + +//----------------------------------------------------------------------------- +// Purpose: NPC RPG users cheat and directly set the laser pointer's origin +// Input : &vecTarget - +//----------------------------------------------------------------------------- +void CWeaponRPG::UpdateNPCLaserPosition( const Vector &vecTarget ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::SetNPCLaserPosition( const Vector &vecTarget ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector &CWeaponRPG::GetNPCLaserPosition( void ) +{ + return vec3_origin; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true if the rocket is being guided, false if it's dumb +//----------------------------------------------------------------------------- +bool CWeaponRPG::IsGuiding( void ) +{ + return m_bGuiding; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponRPG::Deploy( void ) +{ + m_bInitialStateUpdate = true; + + return BaseClass::Deploy(); +} + +bool CWeaponRPG::CanHolster( void ) +{ + //Can't have an active missile out + if ( m_hMissile != NULL ) + return false; + + return BaseClass::CanHolster(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + StopGuiding(); + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: Turn on the guiding laser +//----------------------------------------------------------------------------- +void CWeaponRPG::StartGuiding( void ) +{ + // Don't start back up if we're overriding this + if ( m_bHideGuiding ) + return; + + m_bGuiding = true; + +#ifndef CLIENT_DLL + WeaponSound(SPECIAL1); + + CreateLaserPointer(); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Turn off the guiding laser +//----------------------------------------------------------------------------- +void CWeaponRPG::StopGuiding( void ) +{ + m_bGuiding = false; + +#ifndef CLIENT_DLL + + WeaponSound( SPECIAL2 ); + + // Kill the dot completely + if ( m_hLaserDot != NULL ) + { + m_hLaserDot->TurnOff(); + UTIL_Remove( m_hLaserDot ); + m_hLaserDot = NULL; + } +#else + if ( m_pBeam ) + { + //Tell it to die right away and let the beam code free it. + m_pBeam->brightness = 0.0f; + m_pBeam->flags &= ~FBEAM_FOREVER; + m_pBeam->die = gpGlobals->curtime - 0.1; + m_pBeam = NULL; + } +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle the guiding laser +//----------------------------------------------------------------------------- +void CWeaponRPG::ToggleGuiding( void ) +{ + if ( IsGuiding() ) + { + StopGuiding(); + } + else + { + StartGuiding(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Drop( const Vector &vecVelocity ) +{ + StopGuiding(); + + BaseClass::Drop( vecVelocity ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::UpdateLaserPosition( Vector vecMuzzlePos, Vector vecEndPos ) +{ + +#ifndef CLIENT_DLL + if ( vecMuzzlePos == vec3_origin || vecEndPos == vec3_origin ) + { + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( !pPlayer ) + return; + + vecMuzzlePos = pPlayer->Weapon_ShootPosition(); + Vector forward; + pPlayer->EyeVectors( &forward ); + vecEndPos = vecMuzzlePos + ( forward * MAX_TRACE_LENGTH ); + } + + //Move the laser dot, if active + trace_t tr; + + // Trace out for the endpoint + UTIL_TraceLine( vecMuzzlePos, vecEndPos, (MASK_SHOT & ~CONTENTS_WINDOW), GetOwner(), COLLISION_GROUP_NONE, &tr ); + + // Move the laser sprite + if ( m_hLaserDot != NULL ) + { + Vector laserPos = tr.endpos; + m_hLaserDot->SetLaserPosition( laserPos, tr.plane.normal ); + + if ( tr.DidHitNonWorldEntity() ) + { + CBaseEntity *pHit = tr.m_pEnt; + + if ( ( pHit != NULL ) && ( pHit->m_takedamage ) ) + { + m_hLaserDot->SetTargetEntity( pHit ); + } + else + { + m_hLaserDot->SetTargetEntity( NULL ); + } + } + else + { + m_hLaserDot->SetTargetEntity( NULL ); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::CreateLaserPointer( void ) +{ +#ifndef CLIENT_DLL + if ( m_hLaserDot != NULL ) + return; + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return; + + if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + return; + + m_hLaserDot = CLaserDot::Create( GetAbsOrigin(), GetOwner() ); + m_hLaserDot->TurnOff(); + + UpdateLaserPosition(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::NotifyRocketDied( void ) +{ + m_hMissile = NULL; + + if ( GetActivity() == ACT_VM_RELOAD ) + return; + + Reload(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::Reload( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return false; + + if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + return false; + + WeaponSound( RELOAD ); + + SendWeaponAnim( ACT_VM_RELOAD ); + + return true; +} + +#ifdef CLIENT_DLL + +#define RPG_MUZZLE_ATTACHMENT 1 +#define RPG_GUIDE_ATTACHMENT 2 +#define RPG_GUIDE_TARGET_ATTACHMENT 3 + +#define RPG_GUIDE_ATTACHMENT_3RD 4 +#define RPG_GUIDE_TARGET_ATTACHMENT_3RD 5 + +#define RPG_LASER_BEAM_LENGTH 128 + +extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); + +//----------------------------------------------------------------------------- +// Purpose: Returns the attachment point on either the world or viewmodel +// This should really be worked into the CBaseCombatWeapon class! +//----------------------------------------------------------------------------- +void CWeaponRPG::GetWeaponAttachment( int attachmentId, Vector &outVector, Vector *dir /*= NULL*/ ) +{ + QAngle angles; + + if ( ShouldDrawUsingViewModel() ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner != NULL ) + { + pOwner->GetViewModel()->GetAttachment( attachmentId, outVector, angles ); + ::FormatViewModelAttachment( outVector, true ); + } + } + else + { + // We offset the IDs to make them correct for our world model + BaseClass::GetAttachment( attachmentId, outVector, angles ); + } + + // Supply the direction, if requested + if ( dir != NULL ) + { + AngleVectors( angles, dir, NULL, NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our laser beam +//----------------------------------------------------------------------------- +void CWeaponRPG::InitBeam( void ) +{ + if ( m_pBeam != NULL ) + return; + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return; + + if ( pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + return; + + + BeamInfo_t beamInfo; + + CBaseEntity *pEntity = NULL; + + if ( ShouldDrawUsingViewModel() ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner != NULL ) + { + pEntity = pOwner->GetViewModel(); + } + } + else + { + pEntity = this; + } + + beamInfo.m_pStartEnt = pEntity; + beamInfo.m_pEndEnt = pEntity; + beamInfo.m_nType = TE_BEAMPOINTS; + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_vecEnd = vec3_origin; + + beamInfo.m_pszModelName = ( ShouldDrawUsingViewModel() ) ? RPG_BEAM_SPRITE_NOZ : RPG_BEAM_SPRITE; + + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.0f; + + if ( ShouldDrawUsingViewModel() ) + { + beamInfo.m_flWidth = 2.0f; + beamInfo.m_flEndWidth = 2.0f; + beamInfo.m_nStartAttachment = RPG_GUIDE_ATTACHMENT; + beamInfo.m_nEndAttachment = RPG_GUIDE_TARGET_ATTACHMENT; + } + else + { + beamInfo.m_flWidth = 1.0f; + beamInfo.m_flEndWidth = 1.0f; + beamInfo.m_nStartAttachment = RPG_GUIDE_ATTACHMENT_3RD; + beamInfo.m_nEndAttachment = RPG_GUIDE_TARGET_ATTACHMENT_3RD; + } + + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = 0; + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 1.0f; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 30.0; + beamInfo.m_flRed = 255.0; + beamInfo.m_flGreen = 0.0; + beamInfo.m_flBlue = 0.0; + beamInfo.m_nSegments = 4; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = (FBEAM_FOREVER|FBEAM_SHADEOUT); + + m_pBeam = beams->CreateBeamEntPoint( beamInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw effects for our weapon +//----------------------------------------------------------------------------- +void CWeaponRPG::DrawEffects( void ) +{ + // Must be guiding and not hidden + if ( !m_bGuiding || m_bHideGuiding ) + { + if ( m_pBeam != NULL ) + { + m_pBeam->brightness = 0; + } + + return; + } + + // Setup our sprite + if ( m_hSpriteMaterial == NULL ) + { + m_hSpriteMaterial.Init( RPG_LASER_SPRITE, TEXTURE_GROUP_CLIENT_EFFECTS ); + } + + // Setup our beam + if ( m_hBeamMaterial == NULL ) + { + m_hBeamMaterial.Init( RPG_BEAM_SPRITE, TEXTURE_GROUP_CLIENT_EFFECTS ); + } + + color32 color={255,255,255,255}; + Vector vecAttachment, vecDir; + QAngle angles; + + float scale = 8.0f + random->RandomFloat( -2.0f, 2.0f ); + + int attachmentID = ( ShouldDrawUsingViewModel() ) ? RPG_GUIDE_ATTACHMENT : RPG_GUIDE_ATTACHMENT_3RD; + + GetWeaponAttachment( attachmentID, vecAttachment, &vecDir ); + + // Draw the sprite + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_hSpriteMaterial, this ); + DrawSprite( vecAttachment, scale, scale, color ); + + // Get the beam's run + trace_t tr; + UTIL_TraceLine( vecAttachment, vecAttachment + ( vecDir * RPG_LASER_BEAM_LENGTH ), MASK_SHOT, GetOwner(), COLLISION_GROUP_NONE, &tr ); + + InitBeam(); + + if ( m_pBeam != NULL ) + { + m_pBeam->fadeLength = RPG_LASER_BEAM_LENGTH * tr.fraction; + m_pBeam->brightness = random->RandomInt( 128, 200 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called on third-person weapon drawing +//----------------------------------------------------------------------------- +int CWeaponRPG::DrawModel( int flags ) +{ + // Only render these on the transparent pass + if ( flags & STUDIO_TRANSPARENCY ) + { + DrawEffects(); + return 1; + } + + // Draw the model as normal + return BaseClass::DrawModel( flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called after first-person viewmodel is drawn +//----------------------------------------------------------------------------- +void CWeaponRPG::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) +{ + // Draw our laser effects + DrawEffects(); + + BaseClass::ViewModelDrawn( pBaseViewModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to determine sorting of model when drawn +//----------------------------------------------------------------------------- +bool CWeaponRPG::IsTranslucent( void ) +{ + // Must be guiding and not hidden + if ( m_bGuiding && !m_bHideGuiding ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Turns off effects when leaving the PVS +//----------------------------------------------------------------------------- +void CWeaponRPG::NotifyShouldTransmit( ShouldTransmitState_t state ) +{ + BaseClass::NotifyShouldTransmit(state); + + if ( state == SHOULDTRANSMIT_END ) + { + if ( m_pBeam != NULL ) + { + m_pBeam->brightness = 0.0f; + } + } +} + +#endif //CLIENT_DLL + + +//============================================================================= +// Laser Dot +//============================================================================= + +LINK_ENTITY_TO_CLASS( env_laserdot, CLaserDot ); + +BEGIN_DATADESC( CLaserDot ) + DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ), + DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_FIELD( m_bVisibleLaserDot, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bIsOn, FIELD_BOOLEAN ), + + //DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ), // don't save - regenerated by constructor +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Finds missiles in cone +//----------------------------------------------------------------------------- +CBaseEntity *CreateLaserDot( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ) +{ + return CLaserDot::Create( origin, pOwner, bVisibleDot ); +} + +void SetLaserDotTarget( CBaseEntity *pLaserDot, CBaseEntity *pTarget ) +{ + CLaserDot *pDot = assert_cast< CLaserDot* >(pLaserDot ); + pDot->SetTargetEntity( pTarget ); +} + +void EnableLaserDot( CBaseEntity *pLaserDot, bool bEnable ) +{ + CLaserDot *pDot = assert_cast< CLaserDot* >(pLaserDot ); + if ( bEnable ) + { + pDot->TurnOn(); + } + else + { + pDot->TurnOff(); + } +} + +CLaserDot::CLaserDot( void ) +{ + m_hTargetEnt = NULL; + m_bIsOn = true; +#ifndef CLIENT_DLL + g_LaserDotList.Insert( this ); +#endif +} + +CLaserDot::~CLaserDot( void ) +{ +#ifndef CLIENT_DLL + g_LaserDotList.Remove( this ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// Output : CLaserDot +//----------------------------------------------------------------------------- +CLaserDot *CLaserDot::Create( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ) +{ +#ifndef CLIENT_DLL + CLaserDot *pLaserDot = (CLaserDot *) CBaseEntity::Create( "env_laserdot", origin, QAngle(0,0,0) ); + + if ( pLaserDot == NULL ) + return NULL; + + pLaserDot->m_bVisibleLaserDot = bVisibleDot; + pLaserDot->SetMoveType( MOVETYPE_NONE ); + pLaserDot->AddSolidFlags( FSOLID_NOT_SOLID ); + pLaserDot->AddEffects( EF_NOSHADOW ); + UTIL_SetSize( pLaserDot, -Vector(4,4,4), Vector(4,4,4) ); + + pLaserDot->SetOwnerEntity( pOwner ); + + pLaserDot->AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); + + if ( !bVisibleDot ) + { + pLaserDot->MakeInvisible(); + } + + return pLaserDot; +#else + return NULL; +#endif +} + +void CLaserDot::SetLaserPosition( const Vector &origin, const Vector &normal ) +{ + SetAbsOrigin( origin ); + m_vecSurfaceNormal = normal; +} + +Vector CLaserDot::GetChasePosition() +{ + return GetAbsOrigin() - m_vecSurfaceNormal * 10; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::TurnOn( void ) +{ + m_bIsOn = true; + if ( m_bVisibleLaserDot ) + { + //BaseClass::TurnOn(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::TurnOff( void ) +{ + m_bIsOn = false; + if ( m_bVisibleLaserDot ) + { + //BaseClass::TurnOff(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLaserDot::MakeInvisible( void ) +{ +} + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Draw our sprite +//----------------------------------------------------------------------------- +int CLaserDot::DrawModel( int flags ) +{ + color32 color={255,255,255,255}; + Vector vecAttachment, vecDir; + QAngle angles; + + float scale; + Vector endPos; + + C_HL2MP_Player *pOwner = ToHL2MPPlayer( GetOwnerEntity() ); + + if ( pOwner != NULL && pOwner->IsDormant() == false ) + { + // Always draw the dot in front of our faces when in first-person + if ( pOwner->IsLocalPlayer() ) + { + // Take our view position and orientation + vecAttachment = CurrentViewOrigin(); + vecDir = CurrentViewForward(); + } + else + { + // Take the eye position and direction + vecAttachment = pOwner->EyePosition(); + + QAngle angles = pOwner->GetAnimEyeAngles(); + AngleVectors( angles, &vecDir ); + } + + trace_t tr; + UTIL_TraceLine( vecAttachment, vecAttachment + ( vecDir * MAX_TRACE_LENGTH ), MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr ); + + // Backup off the hit plane + endPos = tr.endpos + ( tr.plane.normal * 4.0f ); + } + else + { + // Just use our position if we can't predict it otherwise + endPos = GetAbsOrigin(); + } + + // Randomly flutter + scale = 16.0f + random->RandomFloat( -4.0f, 4.0f ); + + // Draw our laser dot in space + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_hSpriteMaterial, this ); + DrawSprite( endPos, scale, scale, color ); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our sprite reference +//----------------------------------------------------------------------------- +void CLaserDot::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + m_hSpriteMaterial.Init( RPG_LASER_SPRITE, TEXTURE_GROUP_CLIENT_EFFECTS ); + } +} + +#endif //CLIENT_DLL diff --git a/game/shared/hl2mp/weapon_rpg.h b/game/shared/hl2mp/weapon_rpg.h new file mode 100644 index 0000000..bbd6f2d --- /dev/null +++ b/game/shared/hl2mp/weapon_rpg.h @@ -0,0 +1,267 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef WEAPON_RPG_H +#define WEAPON_RPG_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#ifdef CLIENT_DLL + + #include "iviewrender_beams.h" + +#endif + +#ifndef CLIENT_DLL +#include "Sprite.h" +#include "npcevent.h" +#include "beam_shared.h" + +class CWeaponRPG; +class CLaserDot; +class RocketTrail; + +//########################################################################### +// >> CMissile (missile launcher class is below this one!) +//########################################################################### +class CMissile : public CBaseCombatCharacter +{ + DECLARE_CLASS( CMissile, CBaseCombatCharacter ); + +public: + CMissile(); + ~CMissile(); + +#ifdef HL1_DLL + Class_T Classify( void ) { return CLASS_NONE; } +#else + Class_T Classify( void ) { return CLASS_MISSILE; } +#endif + + void Spawn( void ); + void Precache( void ); + void MissileTouch( CBaseEntity *pOther ); + void Explode( void ); + void ShotDown( void ); + void AccelerateThink( void ); + void AugerThink( void ); + void IgniteThink( void ); + void SeekThink( void ); + void DumbFire( void ); + void SetGracePeriod( float flGracePeriod ); + + int OnTakeDamage_Alive( const CTakeDamageInfo &info ); + void Event_Killed( const CTakeDamageInfo &info ); + + virtual float GetDamage() { return m_flDamage; } + virtual void SetDamage(float flDamage) { m_flDamage = flDamage; } + + unsigned int PhysicsSolidMaskForEntity( void ) const; + + CHandle<CWeaponRPG> m_hOwner; + + static CMissile *Create( const Vector &vecOrigin, const QAngle &vecAngles, edict_t *pentOwner ); + +protected: + virtual void DoExplosion(); + virtual void ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ); + virtual int AugerHealth() { return m_iMaxHealth - 20; } + + // Creates the smoke trail + void CreateSmokeTrail( void ); + + // Gets the shooting position + void GetShootPosition( CLaserDot *pLaserDot, Vector *pShootPosition ); + + CHandle<RocketTrail> m_hRocketTrail; + float m_flAugerTime; // Amount of time to auger before blowing up anyway + float m_flMarkDeadTime; + float m_flDamage; + +private: + float m_flGracePeriodEndsAt; + + DECLARE_DATADESC(); +}; + + +//----------------------------------------------------------------------------- +// Laser dot control +//----------------------------------------------------------------------------- +CBaseEntity *CreateLaserDot( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ); +void SetLaserDotTarget( CBaseEntity *pLaserDot, CBaseEntity *pTarget ); +void EnableLaserDot( CBaseEntity *pLaserDot, bool bEnable ); + + +//----------------------------------------------------------------------------- +// Specialized mizzizzile +//----------------------------------------------------------------------------- +class CAPCMissile : public CMissile +{ + DECLARE_CLASS( CMissile, CMissile ); + DECLARE_DATADESC(); + +public: + static CAPCMissile *Create( const Vector &vecOrigin, const QAngle &vecAngles, const Vector &vecVelocity, CBaseEntity *pOwner ); + + CAPCMissile(); + ~CAPCMissile(); + void IgniteDelay( void ); + void AugerDelay( float flDelayTime ); + void ExplodeDelay( float flDelayTime ); + void DisableGuiding(); +#if defined( HL2_DLL ) + virtual Class_T Classify ( void ) { return CLASS_COMBINE; } +#endif + + void AimAtSpecificTarget( CBaseEntity *pTarget ); + void SetGuidanceHint( const char *pHintName ); + + CAPCMissile *m_pNext; + +protected: + virtual void DoExplosion(); + virtual void ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ); + virtual int AugerHealth(); + +private: + void Init(); + void ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ); + void BeginSeekThink(); + void AugerStartThink(); + void ExplodeThink(); + void APCMissileTouch( CBaseEntity *pOther ); + + float m_flReachedTargetTime; + float m_flIgnitionTime; + bool m_bGuidingDisabled; + float m_flLastHomingSpeed; + EHANDLE m_hSpecificTarget; + string_t m_strHint; +}; + + +//----------------------------------------------------------------------------- +// Finds apc missiles in cone +//----------------------------------------------------------------------------- +CAPCMissile *FindAPCMissileInCone( const Vector &vecOrigin, const Vector &vecDirection, float flAngle ); + +#endif + +//----------------------------------------------------------------------------- +// RPG +//----------------------------------------------------------------------------- + +#ifdef CLIENT_DLL +#define CWeaponRPG C_WeaponRPG +#endif + +class CWeaponRPG : public CBaseHL2MPCombatWeapon +{ + DECLARE_CLASS( CWeaponRPG, CBaseHL2MPCombatWeapon ); +public: + + CWeaponRPG(); + ~CWeaponRPG(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + void Precache( void ); + + void PrimaryAttack( void ); + virtual float GetFireRate( void ) { return 1; }; + void ItemPostFrame( void ); + + void Activate( void ); + void DecrementAmmo( CBaseCombatCharacter *pOwner ); + + bool Deploy( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + bool Reload( void ); + bool WeaponShouldBeLowered( void ); + bool Lower( void ); + + bool CanHolster( void ); + + virtual void Drop( const Vector &vecVelocity ); + + int GetMinBurst() { return 1; } + int GetMaxBurst() { return 1; } + float GetMinRestTime() { return 4.0; } + float GetMaxRestTime() { return 4.0; } + + void StartGuiding( void ); + void StopGuiding( void ); + void ToggleGuiding( void ); + bool IsGuiding( void ); + + void NotifyRocketDied( void ); + + bool HasAnyAmmo( void ); + + void SuppressGuiding( bool state = true ); + + void CreateLaserPointer( void ); + void UpdateLaserPosition( Vector vecMuzzlePos = vec3_origin, Vector vecEndPos = vec3_origin ); + Vector GetLaserPosition( void ); + + // NPC RPG users cheat and directly set the laser pointer's origin + void UpdateNPCLaserPosition( const Vector &vecTarget ); + void SetNPCLaserPosition( const Vector &vecTarget ); + const Vector &GetNPCLaserPosition( void ); + +#ifdef CLIENT_DLL + + // We need to render opaque and translucent pieces + virtual RenderGroup_t GetRenderGroup( void ) { return RENDER_GROUP_TWOPASS; } + + virtual void NotifyShouldTransmit( ShouldTransmitState_t state ); + virtual int DrawModel( int flags ); + virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); + virtual bool IsTranslucent( void ); + + void InitBeam( void ); + void GetWeaponAttachment( int attachmentId, Vector &outVector, Vector *dir = NULL ); + void DrawEffects( void ); +// void DrawLaserDot( void ); + + CMaterialReference m_hSpriteMaterial; // Used for the laser glint + CMaterialReference m_hBeamMaterial; // Used for the laser beam + Beam_t *m_pBeam; // Laser beam temp entity + +#endif //CLIENT_DLL + + CBaseEntity *GetMissile( void ) { return m_hMissile; } + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +protected: + + CNetworkVar( bool, m_bInitialStateUpdate ); + CNetworkVar( bool, m_bGuiding ); + CNetworkVar( bool, m_bHideGuiding ); + + CNetworkHandle( CBaseEntity, m_hMissile ); + CNetworkVar( Vector, m_vecLaserDot ); + +#ifndef CLIENT_DLL + CHandle<CLaserDot> m_hLaserDot; +#endif + +private: + + CWeaponRPG( const CWeaponRPG & ); +}; + +#endif // WEAPON_RPG_H diff --git a/game/shared/hl2mp/weapon_shotgun.cpp b/game/shared/hl2mp/weapon_shotgun.cpp new file mode 100644 index 0000000..da370b5 --- /dev/null +++ b/game/shared/hl2mp/weapon_shotgun.cpp @@ -0,0 +1,634 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" +#endif + +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +#ifdef CLIENT_DLL +#define CWeaponShotgun C_WeaponShotgun +#endif + +extern ConVar sk_auto_reload_time; +extern ConVar sk_plr_num_shotgun_pellets; + +class CWeaponShotgun : public CBaseHL2MPCombatWeapon +{ +public: + DECLARE_CLASS( CWeaponShotgun, CBaseHL2MPCombatWeapon ); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +private: + CNetworkVar( bool, m_bNeedPump ); // When emptied completely + CNetworkVar( bool, m_bDelayedFire1 ); // Fire primary when finished reloading + CNetworkVar( bool, m_bDelayedFire2 ); // Fire secondary when finished reloading + CNetworkVar( bool, m_bDelayedReload ); // Reload when finished pump + +public: + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone = VECTOR_CONE_10DEGREES; + return cone; + } + + virtual int GetMinBurst() { return 1; } + virtual int GetMaxBurst() { return 3; } + + bool StartReload( void ); + bool Reload( void ); + void FillClip( void ); + void FinishReload( void ); + void CheckHolsterReload( void ); + void Pump( void ); +// void WeaponIdle( void ); + void ItemHolsterFrame( void ); + void ItemPostFrame( void ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void DryFire( void ); + virtual float GetFireRate( void ) { return 0.7; }; + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + + CWeaponShotgun(void); + +private: + CWeaponShotgun( const CWeaponShotgun & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponShotgun, DT_WeaponShotgun ) + +BEGIN_NETWORK_TABLE( CWeaponShotgun, DT_WeaponShotgun ) +#ifdef CLIENT_DLL + RecvPropBool( RECVINFO( m_bNeedPump ) ), + RecvPropBool( RECVINFO( m_bDelayedFire1 ) ), + RecvPropBool( RECVINFO( m_bDelayedFire2 ) ), + RecvPropBool( RECVINFO( m_bDelayedReload ) ), +#else + SendPropBool( SENDINFO( m_bNeedPump ) ), + SendPropBool( SENDINFO( m_bDelayedFire1 ) ), + SendPropBool( SENDINFO( m_bDelayedFire2 ) ), + SendPropBool( SENDINFO( m_bDelayedReload ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CWeaponShotgun ) + DEFINE_PRED_FIELD( m_bNeedPump, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bDelayedFire1, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bDelayedFire2, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bDelayedReload, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( weapon_shotgun, CWeaponShotgun ); +PRECACHE_WEAPON_REGISTER(weapon_shotgun); + +#ifndef CLIENT_DLL +acttable_t CWeaponShotgun::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SHOTGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SHOTGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SHOTGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SHOTGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SHOTGUN, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponShotgun); + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CWeaponShotgun::StartReload( void ) +{ + if ( m_bNeedPump ) + return false; + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + SendWeaponAnim( ACT_SHOTGUN_RELOAD_START ); + + // Make shotgun shell visible + SetBodygroup(1,0); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + + m_bInReload = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CWeaponShotgun::Reload( void ) +{ + // Check that StartReload was called first + if (!m_bInReload) + { + Warning("ERROR: Shotgun Reload called incorrectly!\n"); + } + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + FillClip(); + // Play reload on different channel as otherwise steals channel away from fire sound + WeaponSound(RELOAD); + SendWeaponAnim( ACT_VM_RELOAD ); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeaponShotgun::FinishReload( void ) +{ + // Make shotgun shell invisible + SetBodygroup(1,1); + + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return; + + m_bInReload = false; + + // Finish reload animation + SendWeaponAnim( ACT_SHOTGUN_RELOAD_FINISH ); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeaponShotgun::FillClip( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return; + + // Add them to the clip + if ( pOwner->GetAmmoCount( m_iPrimaryAmmoType ) > 0 ) + { + if ( Clip1() < GetMaxClip1() ) + { + m_iClip1++; + pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play weapon pump anim +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeaponShotgun::Pump( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( pOwner == NULL ) + return; + + m_bNeedPump = false; + + if ( m_bDelayedReload ) + { + m_bDelayedReload = false; + StartReload(); + } + + WeaponSound( SPECIAL1 ); + + // Finish reload animation + SendWeaponAnim( ACT_SHOTGUN_PUMP ); + + pOwner->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CWeaponShotgun::DryFire( void ) +{ + WeaponSound(EMPTY); + SendWeaponAnim( ACT_VM_DRYFIRE ); + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CWeaponShotgun::PrimaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if (!pPlayer) + { + return; + } + + // MUST call sound before removing a round from the clip of a CMachineGun + WeaponSound(SINGLE); + + pPlayer->DoMuzzleFlash(); + + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + + // Don't fire again until fire animation has completed + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_iClip1 -= 1; + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = pPlayer->Weapon_ShootPosition( ); + Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + FireBulletsInfo_t info( 7, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_pAttacker = pPlayer; + + // Fire the bullets, and force the first shot to be perfectly accuracy + pPlayer->FireBullets( info ); + + QAngle punch; + punch.Init( SharedRandomFloat( "shotgunpax", -2, -1 ), SharedRandomFloat( "shotgunpay", -2, 2 ), 0 ); + pPlayer->ViewPunch( punch ); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + m_bNeedPump = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// +// +//----------------------------------------------------------------------------- +void CWeaponShotgun::SecondaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if (!pPlayer) + { + return; + } + + pPlayer->m_nButtons &= ~IN_ATTACK2; + // MUST call sound before removing a round from the clip of a CMachineGun + WeaponSound(WPN_DOUBLE); + + pPlayer->DoMuzzleFlash(); + + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + // Don't fire again until fire animation has completed + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_iClip1 -= 2; // Shotgun uses same clip for primary and secondary attacks + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + FireBulletsInfo_t info( 12, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_pAttacker = pPlayer; + + // Fire the bullets, and force the first shot to be perfectly accuracy + pPlayer->FireBullets( info ); + pPlayer->ViewPunch( QAngle(SharedRandomFloat( "shotgunsax", -5, 5 ),0,0) ); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + m_bNeedPump = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Override so shotgun can do mulitple reloads in a row +//----------------------------------------------------------------------------- +void CWeaponShotgun::ItemPostFrame( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if (!pOwner) + { + return; + } + + if ( m_bNeedPump && ( pOwner->m_nButtons & IN_RELOAD ) ) + { + m_bDelayedReload = true; + } + + if (m_bInReload) + { + // If I'm primary firing and have one round stop reloading and fire + if ((pOwner->m_nButtons & IN_ATTACK ) && (m_iClip1 >=1) && !m_bNeedPump ) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire1 = true; + } + // If I'm secondary firing and have two rounds stop reloading and fire + else if ((pOwner->m_nButtons & IN_ATTACK2 ) && (m_iClip1 >=2) && !m_bNeedPump ) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire2 = true; + } + else if (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + // If out of ammo end reload + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <=0) + { + FinishReload(); + return; + } + // If clip not full reload again + if (m_iClip1 < GetMaxClip1()) + { + Reload(); + return; + } + // Clip full, stop reloading + else + { + FinishReload(); + return; + } + } + } + else + { + // Make shotgun shell invisible + SetBodygroup(1,1); + } + + if ((m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + Pump(); + return; + } + + // Shotgun uses same timing and ammo for secondary attack + if ((m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2)&&(m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire2 = false; + + if ( (m_iClip1 <= 1 && UsesClipsForAmmo1())) + { + // If only one shell is left, do a single shot instead + if ( m_iClip1 == 1 ) + { + PrimaryAttack(); + } + else if (!pOwner->GetAmmoCount(m_iPrimaryAmmoType)) + { + DryFire(); + } + else + { + StartReload(); + } + } + + // Fire underwater? + else if (GetOwner()->GetWaterLevel() == 3 && m_bFiresUnderwater == false) + { + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + // If the firing button was just pressed, reset the firing time + if ( pOwner->m_afButtonPressed & IN_ATTACK ) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + SecondaryAttack(); + } + } + else if ( (m_bDelayedFire1 || pOwner->m_nButtons & IN_ATTACK) && m_flNextPrimaryAttack <= gpGlobals->curtime) + { + m_bDelayedFire1 = false; + if ( (m_iClip1 <= 0 && UsesClipsForAmmo1()) || ( !UsesClipsForAmmo1() && !pOwner->GetAmmoCount(m_iPrimaryAmmoType) ) ) + { + if (!pOwner->GetAmmoCount(m_iPrimaryAmmoType)) + { + DryFire(); + } + else + { + StartReload(); + } + } + // Fire underwater? + else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false) + { + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + // If the firing button was just pressed, reset the firing time + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( pPlayer && pPlayer->m_afButtonPressed & IN_ATTACK ) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + PrimaryAttack(); + } + } + + if ( pOwner->m_nButtons & IN_RELOAD && UsesClipsForAmmo1() && !m_bInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + StartReload(); + } + else + { + // no fire buttons down + m_bFireOnEmpty = false; + + if ( !HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime ) + { + // weapon isn't useable, switch. + if ( !(GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && pOwner->SwitchToNextBestWeapon( this ) ) + { + m_flNextPrimaryAttack = gpGlobals->curtime + 0.3; + return; + } + } + else + { + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip1 <= 0 && !(GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < gpGlobals->curtime ) + { + if (StartReload()) + { + // if we've successfully started to reload, we're done + return; + } + } + } + + WeaponIdle( ); + return; + } + +} + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CWeaponShotgun::CWeaponShotgun( void ) +{ + m_bReloadsSingly = true; + + m_bNeedPump = false; + m_bDelayedFire1 = false; + m_bDelayedFire2 = false; + + m_fMinRange1 = 0.0; + m_fMaxRange1 = 500; + m_fMinRange2 = 0.0; + m_fMaxRange2 = 200; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponShotgun::ItemHolsterFrame( void ) +{ + // Must be player held + if ( GetOwner() && GetOwner()->IsPlayer() == false ) + return; + + // We can't be active + if ( GetOwner()->GetActiveWeapon() == this ) + return; + + // If it's been longer than three seconds, reload + if ( ( gpGlobals->curtime - m_flHolsterTime ) > sk_auto_reload_time.GetFloat() ) + { + // Reset the timer + m_flHolsterTime = gpGlobals->curtime; + + if ( GetOwner() == NULL ) + return; + + if ( m_iClip1 == GetMaxClip1() ) + return; + + // Just load the clip with no animations + int ammoFill = MIN( (GetMaxClip1() - m_iClip1), GetOwner()->GetAmmoCount( GetPrimaryAmmoType() ) ); + + GetOwner()->RemoveAmmo( ammoFill, GetPrimaryAmmoType() ); + m_iClip1 += ammoFill; + } +} + +//================================================== +// Purpose: +//================================================== +/* +void CWeaponShotgun::WeaponIdle( void ) +{ + //Only the player fires this way so we can cast + CBasePlayer *pPlayer = GetOwner() + + if ( pPlayer == NULL ) + return; + + //If we're on a target, play the new anim + if ( pPlayer->IsOnTarget() ) + { + SendWeaponAnim( ACT_VM_IDLE_ACTIVE ); + } +} +*/ diff --git a/game/shared/hl2mp/weapon_slam.cpp b/game/shared/hl2mp/weapon_slam.cpp new file mode 100644 index 0000000..bcdec40 --- /dev/null +++ b/game/shared/hl2mp/weapon_slam.cpp @@ -0,0 +1,1052 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "gamerules.h" +#include "npcevent.h" +#include "in_buttons.h" +#include "engine/IEngineSound.h" + +#if defined( CLIENT_DLL ) + #include "c_hl2mp_player.h" +#else + #include "hl2mp_player.h" + #include "grenade_tripmine.h" + #include "grenade_satchel.h" + #include "entitylist.h" + #include "eventqueue.h" +#endif + +#include "hl2mp/weapon_slam.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define SLAM_PRIMARY_VOLUME 450 + +IMPLEMENT_NETWORKCLASS_ALIASED( Weapon_SLAM, DT_Weapon_SLAM ) + +BEGIN_NETWORK_TABLE( CWeapon_SLAM, DT_Weapon_SLAM ) +#ifdef CLIENT_DLL + RecvPropInt( RECVINFO( m_tSlamState ) ), + RecvPropBool( RECVINFO( m_bDetonatorArmed ) ), + RecvPropBool( RECVINFO( m_bNeedDetonatorDraw ) ), + RecvPropBool( RECVINFO( m_bNeedDetonatorHolster ) ), + RecvPropBool( RECVINFO( m_bNeedReload ) ), + RecvPropBool( RECVINFO( m_bClearReload ) ), + RecvPropBool( RECVINFO( m_bThrowSatchel ) ), + RecvPropBool( RECVINFO( m_bAttachSatchel ) ), + RecvPropBool( RECVINFO( m_bAttachTripmine ) ), +#else + SendPropInt( SENDINFO( m_tSlamState ) ), + SendPropBool( SENDINFO( m_bDetonatorArmed ) ), + SendPropBool( SENDINFO( m_bNeedDetonatorDraw ) ), + SendPropBool( SENDINFO( m_bNeedDetonatorHolster ) ), + SendPropBool( SENDINFO( m_bNeedReload ) ), + SendPropBool( SENDINFO( m_bClearReload ) ), + SendPropBool( SENDINFO( m_bThrowSatchel ) ), + SendPropBool( SENDINFO( m_bAttachSatchel ) ), + SendPropBool( SENDINFO( m_bAttachTripmine ) ), +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL + +BEGIN_PREDICTION_DATA( CWeapon_SLAM ) + DEFINE_PRED_FIELD( m_tSlamState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bDetonatorArmed, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bNeedDetonatorDraw, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bNeedDetonatorHolster, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bNeedReload, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bClearReload, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bThrowSatchel, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bAttachSatchel, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), + DEFINE_PRED_FIELD( m_bAttachTripmine, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), +END_PREDICTION_DATA() + +#endif + +LINK_ENTITY_TO_CLASS( weapon_slam, CWeapon_SLAM ); +PRECACHE_WEAPON_REGISTER(weapon_slam); + +#ifndef CLIENT_DLL + +BEGIN_DATADESC( CWeapon_SLAM ) + + DEFINE_FIELD( m_tSlamState, FIELD_INTEGER ), + DEFINE_FIELD( m_bDetonatorArmed, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bNeedDetonatorDraw, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bNeedDetonatorHolster, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bNeedReload, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bClearReload, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bThrowSatchel, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bAttachSatchel, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bAttachTripmine, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flWallSwitchTime, FIELD_TIME ), + + // Function Pointers + DEFINE_FUNCTION( SlamTouch ), + +END_DATADESC() + +acttable_t CWeapon_SLAM::m_acttable[] = +{ + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SLAM, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SLAM, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SLAM, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SLAM, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SLAM, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SLAM, false }, +}; + +IMPLEMENT_ACTTABLE(CWeapon_SLAM); +#endif + + +void CWeapon_SLAM::Spawn( ) +{ + BaseClass::Spawn(); + + Precache( ); + + FallInit();// get ready to fall down + + m_tSlamState = (int)SLAM_SATCHEL_THROW; + m_flWallSwitchTime = 0; + + // Give 1 piece of default ammo when first picked up + m_iClip2 = 1; +} + +void CWeapon_SLAM::Precache( void ) +{ + BaseClass::Precache(); + +#ifndef CLIENT_DLL + UTIL_PrecacheOther( "npc_tripmine" ); + UTIL_PrecacheOther( "npc_satchel" ); +#endif + + PrecacheScriptSound( "Weapon_SLAM.TripMineMode" ); + PrecacheScriptSound( "Weapon_SLAM.SatchelDetonate" ); + PrecacheScriptSound( "Weapon_SLAM.SatchelThrow" ); +} + +//------------------------------------------------------------------------------ +// Purpose : Override to use slam's pickup touch function +// Input : +// Output : +//------------------------------------------------------------------------------ +void CWeapon_SLAM::SetPickupTouch( void ) +{ + SetTouch(&CWeapon_SLAM::SlamTouch); +} + +//----------------------------------------------------------------------------- +// Purpose: Override so give correct ammo +// Input : pOther - the entity that touched me +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SlamTouch( CBaseEntity *pOther ) +{ +#ifdef GAME_DLL + CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pOther ); + + // Can I even pick stuff up? + if ( pBCC && !pBCC->IsAllowedToPickupWeapons() ) + return; +#endif + + // --------------------------------------------------- + // First give weapon to touching entity if allowed + // --------------------------------------------------- + BaseClass::DefaultTouch(pOther); +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CWeapon_SLAM::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + SetThink(NULL); + return BaseClass::Holster(pSwitchingTo); +} + +//----------------------------------------------------------------------------- +// Purpose: SLAM has no reload, but must call weapon idle to update state +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CWeapon_SLAM::Reload( void ) +{ + WeaponIdle( ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::PrimaryAttack( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + if (pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { + return; + } + + switch (m_tSlamState) + { + case SLAM_TRIPMINE_READY: + if (CanAttachSLAM()) + { + StartTripmineAttach(); + } + break; + case SLAM_SATCHEL_THROW: + StartSatchelThrow(); + break; + case SLAM_SATCHEL_ATTACH: + StartSatchelAttach(); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Secondary attack switches between satchel charge and tripmine mode +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SecondaryAttack( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + if (m_bDetonatorArmed) + { + StartSatchelDetonate(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SatchelDetonate() +{ +#ifndef CLIENT_DLL + CBaseEntity *pEntity = NULL; + + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "npc_satchel" )) != NULL) + { + CSatchelCharge *pSatchel = dynamic_cast<CSatchelCharge *>(pEntity); + if (pSatchel->m_bIsLive && pSatchel->GetThrower() && GetOwner() && pSatchel->GetThrower() == GetOwner()) + { + //pSatchel->Use( GetOwner(), GetOwner(), USE_ON, 0 ); + //variant_t emptyVariant; + //pSatchel->AcceptInput( "Explode", NULL, NULL, emptyVariant, 5 ); + g_EventQueue.AddEvent( pSatchel, "Explode", 0.20, GetOwner(), GetOwner() ); + } + } +#endif + // Play sound for pressing the detonator + EmitSound( "Weapon_SLAM.SatchelDetonate" ); + + m_bDetonatorArmed = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if there are any undetonated charges in the world +// that belong to this player +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CWeapon_SLAM::AnyUndetonatedCharges(void) +{ +#ifndef CLIENT_DLL + CBaseEntity *pEntity = NULL; + + while ((pEntity = gEntList.FindEntityByClassname( pEntity, "npc_satchel" )) != NULL) + { + CSatchelCharge* pSatchel = dynamic_cast<CSatchelCharge *>(pEntity); + if (pSatchel->m_bIsLive && pSatchel->GetThrower() && pSatchel->GetThrower() == GetOwner()) + { + return true; + } + } +#endif + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::StartSatchelDetonate() +{ + + if ( GetActivity() != ACT_SLAM_DETONATOR_IDLE && GetActivity() != ACT_SLAM_THROW_IDLE ) + return; + + // ----------------------------------------- + // Play detonate animation + // ----------------------------------------- + if (m_bNeedReload) + { + SendWeaponAnim(ACT_SLAM_DETONATOR_DETONATE); + } + else if (m_tSlamState == SLAM_SATCHEL_ATTACH) + { + SendWeaponAnim(ACT_SLAM_STICKWALL_DETONATE); + } + else if (m_tSlamState == SLAM_SATCHEL_THROW) + { + SendWeaponAnim(ACT_SLAM_THROW_DETONATE); + } + else + { + return; + } + SatchelDetonate(); + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::TripmineAttach( void ) +{ + CHL2MP_Player *pOwner = ToHL2MPPlayer( GetOwner() ); + if (!pOwner) + { + return; + } + + m_bAttachTripmine = false; + + Vector vecSrc, vecAiming; + + // Take the eye position and direction + vecSrc = pOwner->EyePosition(); + + QAngle angles = pOwner->GetLocalAngles(); + + AngleVectors( angles, &vecAiming ); + + trace_t tr; + + UTIL_TraceLine( vecSrc, vecSrc + (vecAiming * 128), MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); + + if (tr.fraction < 1.0) + { + CBaseEntity *pEntity = tr.m_pEnt; + if (pEntity && !(pEntity->GetFlags() & FL_CONVEYOR)) + { + +#ifndef CLIENT_DLL + QAngle angles; + VectorAngles(tr.plane.normal, angles); + + angles.x += 90; + + CBaseEntity *pEnt = CBaseEntity::Create( "npc_tripmine", tr.endpos + tr.plane.normal * 3, angles, NULL ); + + CTripmineGrenade *pMine = (CTripmineGrenade *)pEnt; + pMine->m_hOwner = GetOwner(); + +#endif + + pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::StartTripmineAttach( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (!pPlayer) + { + return; + } + + Vector vecSrc, vecAiming; + + // Take the eye position and direction + vecSrc = pPlayer->EyePosition(); + + QAngle angles = pPlayer->GetLocalAngles(); + + AngleVectors( angles, &vecAiming ); + + trace_t tr; + + UTIL_TraceLine( vecSrc, vecSrc + (vecAiming * 128), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr ); + + if (tr.fraction < 1.0) + { + // ALERT( at_console, "hit %f\n", tr.flFraction ); + + CBaseEntity *pEntity = tr.m_pEnt; + if (pEntity && !(pEntity->GetFlags() & FL_CONVEYOR)) + { + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // ----------------------------------------- + // Play attach animation + // ----------------------------------------- + + if (m_bDetonatorArmed) + { + SendWeaponAnim(ACT_SLAM_STICKWALL_ATTACH); + } + else + { + SendWeaponAnim(ACT_SLAM_TRIPMINE_ATTACH); + } + + m_bNeedReload = true; + m_bAttachTripmine = true; + m_bNeedDetonatorDraw = m_bDetonatorArmed; + } + else + { + // ALERT( at_console, "no deploy\n" ); + } + } + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); +// SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SatchelThrow( void ) +{ +#ifndef CLIENT_DLL + m_bThrowSatchel = false; + + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + Vector vecSrc = pPlayer->WorldSpaceCenter(); + Vector vecFacing = pPlayer->BodyDirection3D( ); + vecSrc = vecSrc + vecFacing * 18.0; + // BUGBUG: is this because vecSrc is not from Weapon_ShootPosition()??? + vecSrc.z += 24.0f; + + Vector vecThrow; + GetOwner()->GetVelocity( &vecThrow, NULL ); + vecThrow += vecFacing * 500; + + // Player may have turned to face a wall during the throw anim in which case + // we don't want to throw the SLAM into the wall + if (CanAttachSLAM()) + { + vecThrow = vecFacing; + vecSrc = pPlayer->WorldSpaceCenter() + vecFacing * 5.0; + } + + CSatchelCharge *pSatchel = (CSatchelCharge*)Create( "npc_satchel", vecSrc, vec3_angle, GetOwner() ); + + if ( pSatchel ) + { + pSatchel->SetThrower( GetOwner() ); + pSatchel->ApplyAbsVelocityImpulse( vecThrow ); + pSatchel->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) ); + pSatchel->m_bIsLive = true; + pSatchel->m_pMyWeaponSLAM = this; + } + + pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#endif + + // Play throw sound + EmitSound( "Weapon_SLAM.SatchelThrow" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::StartSatchelThrow( void ) +{ + // ----------------------------------------- + // Play throw animation + // ----------------------------------------- + if (m_bDetonatorArmed) + { + SendWeaponAnim(ACT_SLAM_THROW_THROW); + } + else + { + SendWeaponAnim(ACT_SLAM_THROW_THROW_ND); + if (!m_bDetonatorArmed) + { + m_bDetonatorArmed = true; + m_bNeedDetonatorDraw = true; + } + } + + m_bNeedReload = true; + m_bThrowSatchel = true; + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SatchelAttach( void ) +{ +#ifndef CLIENT_DLL + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + m_bAttachSatchel = false; + + Vector vecSrc = pOwner->Weapon_ShootPosition( ); + Vector vecAiming = pOwner->BodyDirection2D( ); + + trace_t tr; + + UTIL_TraceLine( vecSrc, vecSrc + (vecAiming * 128), MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); + + if (tr.fraction < 1.0) + { + CBaseEntity *pEntity = tr.m_pEnt; + if (pEntity && !(pEntity->GetFlags() & FL_CONVEYOR)) + { + QAngle angles; + VectorAngles(tr.plane.normal, angles); + angles.y -= 90; + angles.z -= 90; + tr.endpos.z -= 6.0f; + + CSatchelCharge *pSatchel = (CSatchelCharge*)CBaseEntity::Create( "npc_satchel", tr.endpos + tr.plane.normal * 3, angles, NULL ); + pSatchel->SetMoveType( MOVETYPE_FLY ); // no gravity + pSatchel->m_bIsAttached = true; + pSatchel->m_bIsLive = true; + pSatchel->SetThrower( GetOwner() ); + pSatchel->SetOwnerEntity( ((CBaseEntity*)GetOwner()) ); + pSatchel->m_pMyWeaponSLAM = this; + + pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::StartSatchelAttach( void ) +{ +#ifndef CLIENT_DLL + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + Vector vecSrc = pOwner->Weapon_ShootPosition( ); + Vector vecAiming = pOwner->BodyDirection2D( ); + + trace_t tr; + + UTIL_TraceLine( vecSrc, vecSrc + (vecAiming * 128), MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); + + if (tr.fraction < 1.0) + { + CBaseEntity *pEntity = tr.m_pEnt; + if (pEntity && !(pEntity->GetFlags() & FL_CONVEYOR)) + { + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( pOwner ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // ----------------------------------------- + // Play attach animation + // ----------------------------------------- + if (m_bDetonatorArmed) + { + SendWeaponAnim(ACT_SLAM_STICKWALL_ATTACH); + } + else + { + SendWeaponAnim(ACT_SLAM_STICKWALL_ND_ATTACH); + if (!m_bDetonatorArmed) + { + m_bDetonatorArmed = true; + m_bNeedDetonatorDraw = true; + } + } + + m_bNeedReload = true; + m_bAttachSatchel = true; + + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SetSlamState( int newState ) +{ + // Set set and set idle time so animation gets updated with state change + m_tSlamState = newState; + SetWeaponIdleTime( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::SLAMThink( void ) +{ + if ( m_flWallSwitchTime > gpGlobals->curtime ) + return; + + + // If not in tripmine mode we need to check to see if we are close to + // a wall. If we are we go into satchel_attach mode + CBaseCombatCharacter *pOwner = GetOwner(); + + if ( (pOwner && pOwner->GetAmmoCount(m_iSecondaryAmmoType) > 0)) + { + if (CanAttachSLAM()) + { + if (m_tSlamState == SLAM_SATCHEL_THROW) + { + SetSlamState(SLAM_TRIPMINE_READY); + int iAnim = m_bDetonatorArmed ? ACT_SLAM_THROW_TO_STICKWALL : ACT_SLAM_THROW_TO_TRIPMINE_ND; + SendWeaponAnim( iAnim ); + m_flWallSwitchTime = gpGlobals->curtime + SequenceDuration(); + m_bNeedReload = false; + } + } + else + { + if (m_tSlamState == SLAM_TRIPMINE_READY) + { + SetSlamState(SLAM_SATCHEL_THROW); + int iAnim = m_bDetonatorArmed ? ACT_SLAM_STICKWALL_TO_THROW : ACT_SLAM_TRIPMINE_TO_THROW_ND; + SendWeaponAnim( iAnim ); + m_flWallSwitchTime = gpGlobals->curtime + SequenceDuration(); + m_bNeedReload = false; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CWeapon_SLAM::CanAttachSLAM( void ) +{ + CHL2MP_Player *pOwner = ToHL2MPPlayer( GetOwner() ); + + if (!pOwner) + { + return false; + } + + Vector vecSrc, vecAiming; + + // Take the eye position and direction + vecSrc = pOwner->EyePosition(); + + QAngle angles = pOwner->GetLocalAngles(); + + AngleVectors( angles, &vecAiming ); + + trace_t tr; + + Vector vecEnd = vecSrc + (vecAiming * 42); + UTIL_TraceLine( vecSrc, vecEnd, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); + + if (tr.fraction < 1.0) + { + // Don't attach to a living creature + if (tr.m_pEnt) + { + CBaseEntity *pEntity = tr.m_pEnt; + CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( pEntity ); + if (pBCC) + { + return false; + } + } + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override so SLAM to so secondary attack when no secondary ammo +// but satchel is in the world +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::ItemPostFrame( void ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if (!pOwner) + { + return; + } + + SLAMThink(); + + if ((pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) + { + SecondaryAttack(); + } + else if (!m_bNeedReload && (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + PrimaryAttack(); + } + + // ----------------------- + // No buttons down + // ----------------------- + else + { + WeaponIdle( ); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Switch to next best weapon +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::Weapon_Switch( void ) +{ + // Note that we may pick the SLAM again, when we switch + // weapons, in which case we have to save and restore the + // detonator armed state. + // The SLAMs may be about to blow up, but haven't done so yet + // and the deploy function will find the undetonated charges + // and we are armed + bool saveState = m_bDetonatorArmed; + CBaseCombatCharacter *pOwner = GetOwner(); + pOwner->SwitchToNextBestWeapon( pOwner->GetActiveWeapon() ); + if (pOwner->GetActiveWeapon() == this) + { + m_bDetonatorArmed = saveState; + } + +#ifndef CLIENT_DLL + // If not armed and have no ammo + if (!m_bDetonatorArmed && pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { + pOwner->ClearActiveWeapon(); + } +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeapon_SLAM::WeaponIdle( void ) +{ + // Ready to switch animations? + if ( HasWeaponIdleTimeElapsed() ) + { + // Don't allow throw to attach switch unless in idle + if (m_bClearReload) + { + m_bNeedReload = false; + m_bClearReload = false; + } + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return; + } + + int iAnim = 0; + + if (m_bThrowSatchel) + { + SatchelThrow(); + if (m_bDetonatorArmed && !m_bNeedDetonatorDraw) + { + iAnim = ACT_SLAM_THROW_THROW2; + } + else + { + iAnim = ACT_SLAM_THROW_THROW_ND2; + } + } + else if (m_bAttachSatchel) + { + SatchelAttach(); + if (m_bDetonatorArmed && !m_bNeedDetonatorDraw) + { + iAnim = ACT_SLAM_STICKWALL_ATTACH2; + } + else + { + iAnim = ACT_SLAM_STICKWALL_ND_ATTACH2; + } + } + else if (m_bAttachTripmine) + { + TripmineAttach(); + iAnim = m_bNeedDetonatorDraw ? ACT_SLAM_STICKWALL_ATTACH2 : ACT_SLAM_TRIPMINE_ATTACH2; + } + else if ( m_bNeedReload ) + { + // If owner had ammo draw the correct SLAM type + if (pOwner->GetAmmoCount(m_iSecondaryAmmoType) > 0) + { + switch( m_tSlamState) + { + case SLAM_TRIPMINE_READY: + { + iAnim = m_bNeedDetonatorDraw ? ACT_SLAM_STICKWALL_DRAW : ACT_SLAM_TRIPMINE_DRAW; + } + break; + case SLAM_SATCHEL_ATTACH: + { + if (m_bNeedDetonatorHolster) + { + iAnim = ACT_SLAM_STICKWALL_DETONATOR_HOLSTER; + m_bNeedDetonatorHolster = false; + } + else if (m_bDetonatorArmed) + { + iAnim = m_bNeedDetonatorDraw ? ACT_SLAM_DETONATOR_STICKWALL_DRAW : ACT_SLAM_STICKWALL_DRAW; + m_bNeedDetonatorDraw = false; + } + else + { + iAnim = ACT_SLAM_STICKWALL_ND_DRAW; + } + } + break; + case SLAM_SATCHEL_THROW: + { + if (m_bNeedDetonatorHolster) + { + iAnim = ACT_SLAM_THROW_DETONATOR_HOLSTER; + m_bNeedDetonatorHolster = false; + } + else if (m_bDetonatorArmed) + { + iAnim = m_bNeedDetonatorDraw ? ACT_SLAM_DETONATOR_THROW_DRAW : ACT_SLAM_THROW_DRAW; + m_bNeedDetonatorDraw = false; + } + else + { + iAnim = ACT_SLAM_THROW_ND_DRAW; + } + } + break; + } + m_bClearReload = true; + } + // If no ammo and armed, idle with only the detonator + else if (m_bDetonatorArmed) + { + iAnim = m_bNeedDetonatorDraw ? ACT_SLAM_DETONATOR_DRAW : ACT_SLAM_DETONATOR_IDLE; + m_bNeedDetonatorDraw = false; + } + else + { +#ifndef CLIENT_DLL + pOwner->Weapon_Drop( this ); + UTIL_Remove(this); +#endif + } + } + else if (pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { +#ifndef CLIENT_DLL + pOwner->Weapon_Drop( this ); + UTIL_Remove(this); +#endif + } + + // If I don't need to reload just do the appropriate idle + else + { + switch( m_tSlamState) + { + case SLAM_TRIPMINE_READY: + { + iAnim = m_bDetonatorArmed ? ACT_SLAM_STICKWALL_IDLE : ACT_SLAM_TRIPMINE_IDLE; + m_flWallSwitchTime = 0; + } + break; + case SLAM_SATCHEL_THROW: + { + if (m_bNeedDetonatorHolster) + { + iAnim = ACT_SLAM_THROW_DETONATOR_HOLSTER; + m_bNeedDetonatorHolster = false; + } + else + { + iAnim = m_bDetonatorArmed ? ACT_SLAM_THROW_IDLE : ACT_SLAM_THROW_ND_IDLE; + m_flWallSwitchTime = 0; + } + } + break; + case SLAM_SATCHEL_ATTACH: + { + if (m_bNeedDetonatorHolster) + { + iAnim = ACT_SLAM_STICKWALL_DETONATOR_HOLSTER; + m_bNeedDetonatorHolster = false; + } + else + { + iAnim = m_bDetonatorArmed ? ACT_SLAM_STICKWALL_IDLE : ACT_SLAM_TRIPMINE_IDLE; + m_flWallSwitchTime = 0; + } + } + break; + } + } + SendWeaponAnim( iAnim ); + } +} + +bool CWeapon_SLAM::Deploy( void ) +{ + CBaseCombatCharacter *pOwner = GetOwner(); + if (!pOwner) + { + return false; + } + + m_bDetonatorArmed = AnyUndetonatedCharges(); + + + SetModel( GetViewModel() ); + + m_tSlamState = (int)SLAM_SATCHEL_THROW; + + // ------------------------------ + // Pick the right draw animation + // ------------------------------ + int iActivity; + + // If detonator is already armed + m_bNeedReload = false; + if (m_bDetonatorArmed) + { + if (pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { + iActivity = ACT_SLAM_DETONATOR_DRAW; + m_bNeedReload = true; + } + else if (CanAttachSLAM()) + { + iActivity = ACT_SLAM_DETONATOR_STICKWALL_DRAW; + SetSlamState(SLAM_TRIPMINE_READY); + } + else + { + iActivity = ACT_SLAM_DETONATOR_THROW_DRAW; + SetSlamState(SLAM_SATCHEL_THROW); + } + } + else + { + if (CanAttachSLAM()) + { + iActivity = ACT_SLAM_TRIPMINE_DRAW; + SetSlamState(SLAM_TRIPMINE_READY); + } + else + { + iActivity = ACT_SLAM_THROW_ND_DRAW; + SetSlamState(SLAM_SATCHEL_THROW); + } + } + + return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), iActivity, (char*)GetAnimPrefix() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : +// Output : +//----------------------------------------------------------------------------- +CWeapon_SLAM::CWeapon_SLAM(void) +{ + m_tSlamState = (int)SLAM_SATCHEL_THROW; + m_bDetonatorArmed = false; + m_bNeedReload = true; + m_bClearReload = false; + m_bThrowSatchel = false; + m_bAttachSatchel = false; + m_bAttachTripmine = false; + m_bNeedDetonatorDraw = false; + m_bNeedDetonatorHolster = false; +} diff --git a/game/shared/hl2mp/weapon_slam.h b/game/shared/hl2mp/weapon_slam.h new file mode 100644 index 0000000..0b59bed --- /dev/null +++ b/game/shared/hl2mp/weapon_slam.h @@ -0,0 +1,91 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: SLAM +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef WEAPONSLAM_H +#define WEAPONSLAM_H + +#include "basegrenade_shared.h" +#include "weapon_hl2mpbasehlmpcombatweapon.h" + +enum +{ + SLAM_TRIPMINE_READY, + SLAM_SATCHEL_THROW, + SLAM_SATCHEL_ATTACH, +}; + +#ifdef CLIENT_DLL +#define CWeapon_SLAM C_Weapon_SLAM +#endif + +class CWeapon_SLAM : public CBaseHL2MPCombatWeapon +{ +public: + DECLARE_CLASS( CWeapon_SLAM, CBaseHL2MPCombatWeapon ); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CNetworkVar( int, m_tSlamState ); + CNetworkVar( bool, m_bDetonatorArmed ); + CNetworkVar( bool, m_bNeedDetonatorDraw); + CNetworkVar( bool, m_bNeedDetonatorHolster); + CNetworkVar( bool, m_bNeedReload); + CNetworkVar( bool, m_bClearReload); + CNetworkVar( bool, m_bThrowSatchel); + CNetworkVar( bool, m_bAttachSatchel); + CNetworkVar( bool, m_bAttachTripmine); + float m_flWallSwitchTime; + + void Spawn( void ); + void Precache( void ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void WeaponIdle( void ); + void Weapon_Switch( void ); + void SLAMThink( void ); + + void SetPickupTouch( void ); + void SlamTouch( CBaseEntity *pOther ); // default weapon touch + void ItemPostFrame( void ); + bool Reload( void ); + void SetSlamState( int newState ); + bool CanAttachSLAM(void); // In position where can attach SLAM? + bool AnyUndetonatedCharges(void); + void StartTripmineAttach( void ); + void TripmineAttach( void ); + + void StartSatchelDetonate( void ); + void SatchelDetonate( void ); + void StartSatchelThrow( void ); + void StartSatchelAttach( void ); + void SatchelThrow( void ); + void SatchelAttach( void ); + bool Deploy( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + + + CWeapon_SLAM(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); + DECLARE_DATADESC(); +#endif + +private: + CWeapon_SLAM( const CWeapon_SLAM & ); +}; + + +#endif //WEAPONSLAM_H diff --git a/game/shared/hl2mp/weapon_smg1.cpp b/game/shared/hl2mp/weapon_smg1.cpp new file mode 100644 index 0000000..bd6e1d4 --- /dev/null +++ b/game/shared/hl2mp/weapon_smg1.cpp @@ -0,0 +1,264 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "in_buttons.h" + +#ifdef CLIENT_DLL + #include "c_hl2mp_player.h" +#else + #include "grenade_ar2.h" + #include "hl2mp_player.h" + #include "basegrenade_shared.h" +#endif + +#include "weapon_hl2mpbase.h" +#include "weapon_hl2mpbase_machinegun.h" + +#ifdef CLIENT_DLL +#define CWeaponSMG1 C_WeaponSMG1 +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define SMG1_GRENADE_DAMAGE 100.0f +#define SMG1_GRENADE_RADIUS 250.0f + +class CWeaponSMG1 : public CHL2MPMachineGun +{ +public: + DECLARE_CLASS( CWeaponSMG1, CHL2MPMachineGun ); + + CWeaponSMG1(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + + void Precache( void ); + void AddViewKick( void ); + void SecondaryAttack( void ); + + int GetMinBurst() { return 2; } + int GetMaxBurst() { return 5; } + + virtual void Equip( CBaseCombatCharacter *pOwner ); + bool Reload( void ); + + float GetFireRate( void ) { return 0.075f; } // 13.3hz + Activity GetPrimaryAttackActivity( void ); + + virtual const Vector& GetBulletSpread( void ) + { + static const Vector cone = VECTOR_CONE_5DEGREES; + return cone; + } + + const WeaponProficiencyInfo_t *GetProficiencyValues(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +protected: + + Vector m_vecTossVelocity; + float m_flNextGrenadeCheck; + +private: + CWeaponSMG1( const CWeaponSMG1 & ); +}; + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponSMG1, DT_WeaponSMG1 ) + +BEGIN_NETWORK_TABLE( CWeaponSMG1, DT_WeaponSMG1 ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponSMG1 ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_smg1, CWeaponSMG1 ); +PRECACHE_WEAPON_REGISTER(weapon_smg1); + +#ifndef CLIENT_DLL +acttable_t CWeaponSMG1::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG1, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SMG1, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SMG1, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SMG1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SMG1, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SMG1, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponSMG1); +#endif + +//========================================================= +CWeaponSMG1::CWeaponSMG1( ) +{ + m_fMinRange1 = 0;// No minimum range. + m_fMaxRange1 = 1400; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponSMG1::Precache( void ) +{ +#ifndef CLIENT_DLL + UTIL_PrecacheOther("grenade_ar2"); +#endif + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: Give this weapon longer range when wielded by an ally NPC. +//----------------------------------------------------------------------------- +void CWeaponSMG1::Equip( CBaseCombatCharacter *pOwner ) +{ + m_fMaxRange1 = 1400; + + BaseClass::Equip( pOwner ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Activity +//----------------------------------------------------------------------------- +Activity CWeaponSMG1::GetPrimaryAttackActivity( void ) +{ + if ( m_nShotsFired < 2 ) + return ACT_VM_PRIMARYATTACK; + + if ( m_nShotsFired < 3 ) + return ACT_VM_RECOIL1; + + if ( m_nShotsFired < 4 ) + return ACT_VM_RECOIL2; + + return ACT_VM_RECOIL3; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CWeaponSMG1::Reload( void ) +{ + bool fRet; + float fCacheTime = m_flNextSecondaryAttack; + + fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); + if ( fRet ) + { + // Undo whatever the reload process has done to our secondary + // attack timer. We allow you to interrupt reloading to fire + // a grenade. + m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime; + + WeaponSound( RELOAD ); + } + + return fRet; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponSMG1::AddViewKick( void ) +{ + #define EASY_DAMPEN 0.5f + #define MAX_VERTICAL_KICK 1.0f //Degrees + #define SLIDE_LIMIT 2.0f //Seconds + + //Get the view kick + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + DoMachineGunKick( pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, m_fFireDuration, SLIDE_LIMIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponSMG1::SecondaryAttack( void ) +{ + // Only the player fires this way so we can cast + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + if ( pPlayer == NULL ) + return; + + //Must have ammo + if ( ( pPlayer->GetAmmoCount( m_iSecondaryAmmoType ) <= 0 ) || ( pPlayer->GetWaterLevel() == 3 ) ) + { + SendWeaponAnim( ACT_VM_DRYFIRE ); + BaseClass::WeaponSound( EMPTY ); + m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; + return; + } + + if( m_bInReload ) + m_bInReload = false; + + // MUST call sound before removing a round from the clip of a CMachineGun + BaseClass::WeaponSound( WPN_DOUBLE ); + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecThrow; + // Don't autoaim on grenade tosses + AngleVectors( pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), &vecThrow ); + VectorScale( vecThrow, 1000.0f, vecThrow ); + +#ifndef CLIENT_DLL + //Create the grenade + CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecSrc, vec3_angle, pPlayer ); + pGrenade->SetAbsVelocity( vecThrow ); + + pGrenade->SetLocalAngularVelocity( RandomAngle( -400, 400 ) ); + pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + pGrenade->SetThrower( GetOwner() ); + pGrenade->SetDamage( SMG1_GRENADE_DAMAGE ); + pGrenade->SetDamageRadius( SMG1_GRENADE_RADIUS ); +#endif + + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + // player "shoot" animation + pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + // Decrease ammo + pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); + + // Can shoot again immediately + m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; + + // Can blow up after a short delay (so have time to release mouse button) + m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f; +} + +//----------------------------------------------------------------------------- +const WeaponProficiencyInfo_t *CWeaponSMG1::GetProficiencyValues() +{ + static WeaponProficiencyInfo_t proficiencyTable[] = + { + { 7.0, 0.75 }, + { 5.00, 0.75 }, + { 10.0/3.0, 0.75 }, + { 5.0/3.0, 0.75 }, + { 1.00, 1.0 }, + }; + + COMPILE_TIME_ASSERT( ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); + + return proficiencyTable; +} diff --git a/game/shared/hl2mp/weapon_stunstick.cpp b/game/shared/hl2mp/weapon_stunstick.cpp new file mode 100644 index 0000000..9f70698 --- /dev/null +++ b/game/shared/hl2mp/weapon_stunstick.cpp @@ -0,0 +1,902 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Stun Stick- beating stick with a zappy end +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "npcevent.h" +#include "weapon_hl2mpbasebasebludgeon.h" +#include "IEffects.h" +#include "debugoverlay_shared.h" + +#ifndef CLIENT_DLL + #include "npc_metropolice.h" + #include "te_effect_dispatch.h" +#endif + +#ifdef CLIENT_DLL + + #include "iviewrender_beams.h" + #include "beam_shared.h" + #include "materialsystem/imaterial.h" + #include "model_types.h" + #include "c_te_effect_dispatch.h" + #include "fx_quad.h" + #include "fx.h" + + extern void DrawHalo( IMaterial* pMaterial, const Vector &source, float scale, float const *color, float flHDRColorScale ); + extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); + +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern ConVar metropolice_move_and_melee; + +#define STUNSTICK_RANGE 75.0f +#define STUNSTICK_REFIRE 0.8f +#define STUNSTICK_BEAM_MATERIAL "sprites/lgtning.vmt" +#define STUNSTICK_GLOW_MATERIAL "sprites/light_glow02_add" +#define STUNSTICK_GLOW_MATERIAL2 "effects/blueflare1" +#define STUNSTICK_GLOW_MATERIAL_NOZ "sprites/light_glow02_add_noz" + +#ifdef CLIENT_DLL +#define CWeaponStunStick C_WeaponStunStick +#endif + +class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon +{ + DECLARE_CLASS( CWeaponStunStick, CBaseHL2MPBludgeonWeapon ); + +public: + + CWeaponStunStick(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +#ifdef CLIENT_DLL + virtual int DrawModel( int flags ); + virtual void ClientThink( void ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual RenderGroup_t GetRenderGroup( void ); + virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); + +#endif + + virtual void Precache(); + + void Spawn(); + + float GetRange( void ) { return STUNSTICK_RANGE; } + float GetFireRate( void ) { return STUNSTICK_REFIRE; } + + + bool Deploy( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + + void Drop( const Vector &vecVelocity ); + void ImpactEffect( trace_t &traceHit ); + void SecondaryAttack( void ) {} + void SetStunState( bool state ); + bool GetStunState( void ); + +#ifndef CLIENT_DLL + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#endif + + float GetDamageForActivity( Activity hitActivity ); + + CWeaponStunStick( const CWeaponStunStick & ); + +private: + +#ifdef CLIENT_DLL + + #define NUM_BEAM_ATTACHMENTS 9 + + struct stunstickBeamInfo_t + { + int IDs[2]; // 0 - top, 1 - bottom + }; + + stunstickBeamInfo_t m_BeamAttachments[NUM_BEAM_ATTACHMENTS]; // Lookup for arc attachment points on the head of the stick + int m_BeamCenterAttachment; // "Core" of the effect (center of the head) + + void SetupAttachmentPoints( void ); + void DrawFirstPersonEffects( void ); + void DrawThirdPersonEffects( void ); + void DrawEffects( void ); + bool InSwing( void ); + + bool m_bSwungLastFrame; + + #define FADE_DURATION 0.25f + + float m_flFadeTime; + +#endif + + CNetworkVar( bool, m_bActive ); +}; + +//----------------------------------------------------------------------------- +// CWeaponStunStick +//----------------------------------------------------------------------------- +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponStunStick, DT_WeaponStunStick ) + +BEGIN_NETWORK_TABLE( CWeaponStunStick, DT_WeaponStunStick ) +#ifdef CLIENT_DLL + RecvPropInt( RECVINFO( m_bActive ) ), +#else + SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ), +#endif + +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponStunStick ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_stunstick, CWeaponStunStick ); +PRECACHE_WEAPON_REGISTER( weapon_stunstick ); + + +#ifndef CLIENT_DLL + +acttable_t CWeaponStunStick::m_acttable[] = +{ + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +}; + +IMPLEMENT_ACTTABLE(CWeaponStunStick); + +#endif + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CWeaponStunStick::CWeaponStunStick( void ) +{ + // HACK: Don't call SetStunState because this tried to Emit a sound before + // any players are connected which is a bug + m_bActive = false; + +#ifdef CLIENT_DLL + m_bSwungLastFrame = false; + m_flFadeTime = FADE_DURATION; // Start off past the fade point +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CWeaponStunStick::Spawn() +{ + Precache(); + + BaseClass::Spawn(); + AddSolidFlags( FSOLID_NOT_STANDABLE ); +} + +void CWeaponStunStick::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Weapon_StunStick.Activate" ); + PrecacheScriptSound( "Weapon_StunStick.Deactivate" ); + + PrecacheModel( STUNSTICK_BEAM_MATERIAL ); + PrecacheModel( "sprites/light_glow02_add.vmt" ); + PrecacheModel( "effects/blueflare1.vmt" ); + PrecacheModel( "sprites/light_glow02_add_noz.vmt" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CWeaponStunStick::GetDamageForActivity( Activity hitActivity ) +{ + return 40.0f; +} + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +extern ConVar sk_crowbar_lead_time; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponStunStick::ImpactEffect( trace_t &traceHit ) +{ + +//#ifndef CLIENT_DLL + + CEffectData data; + + data.m_vNormal = traceHit.plane.normal; + data.m_vOrigin = traceHit.endpos + ( data.m_vNormal * 4.0f ); + + DispatchEffect( "StunstickImpact", data ); + +//#endif + + //FIXME: need new decals + UTIL_ImpactTrace( &traceHit, DMG_CLUB ); +} + +#ifndef CLIENT_DLL + + +int CWeaponStunStick::WeaponMeleeAttack1Condition( float flDot, float flDist ) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity *pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + AngularImpulse angVelocity; + pEnemy->GetVelocity( &vecVelocity, &angVelocity ); + + // Project where the enemy will be in a little while, add some randomness so he doesn't always hit + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random->RandomFloat( -0.3f, 0.2f ); + if ( dt < 0.0f ) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA( pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos ); + + Vector vecDelta; + VectorSubtract( vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta ); + + if ( fabs( vecDelta.z ) > 70 ) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D( ); + vecDelta.z = 0.0f; + float flExtrapolatedDot = DotProduct2D( vecDelta.AsVector2D(), vecForward.AsVector2D() ); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + float flExtrapolatedDist = Vector2DNormalize( vecDelta.AsVector2D() ); + + if( pEnemy->IsPlayer() ) + { + //Vector vecDir = pEnemy->GetSmoothedVelocity(); + //float flSpeed = VectorNormalize( vecDir ); + + // If player will be in front of me in one-half second, clock his arse. + Vector vecProjectEnemy = pEnemy->GetAbsOrigin() + (pEnemy->GetAbsVelocity() * 0.35); + Vector vecProjectMe = GetAbsOrigin(); + + if( (vecProjectMe - vecProjectEnemy).Length2D() <= 48.0f ) + { + return COND_CAN_MELEE_ATTACK1; + } + } +/* + if( metropolice_move_and_melee.GetBool() ) + { + if( pNPC->IsMoving() ) + { + flTargetDist *= 1.5f; + } + } +*/ + float flTargetDist = 48.0f; + if ((flDist > flTargetDist) && (flExtrapolatedDist > flTargetDist)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + + +void CWeaponStunStick::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_MELEE_HIT: + { + // Trace up or down based on where the enemy is... + // But only if we're basically facing that direction + Vector vecDirection; + AngleVectors( GetAbsAngles(), &vecDirection ); + + CBaseEntity *pEnemy = pOperator->MyNPCPointer() ? pOperator->MyNPCPointer()->GetEnemy() : NULL; + if ( pEnemy ) + { + Vector vecDelta; + VectorSubtract( pEnemy->WorldSpaceCenter(), pOperator->Weapon_ShootPosition(), vecDelta ); + VectorNormalize( vecDelta ); + + Vector2D vecDelta2D = vecDelta.AsVector2D(); + Vector2DNormalize( vecDelta2D ); + if ( DotProduct2D( vecDelta2D, vecDirection.AsVector2D() ) > 0.8f ) + { + vecDirection = vecDelta; + } + } + + Vector vecEnd; + VectorMA( pOperator->Weapon_ShootPosition(), 32, vecDirection, vecEnd ); + // Stretch the swing box down to catch low level physics objects + CBaseEntity *pHurt = pOperator->CheckTraceHullAttack( pOperator->Weapon_ShootPosition(), vecEnd, + Vector(-16,-16,-40), Vector(16,16,16), GetDamageForActivity( GetActivity() ), DMG_CLUB, 0.5f, false ); + + // did I hit someone? + if ( pHurt ) + { + // play sound + WeaponSound( MELEE_HIT ); + + CBasePlayer *pPlayer = ToBasePlayer( pHurt ); + + bool bFlashed = false; + + // Punch angles + if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE) ) + { + float yawKick = random->RandomFloat( -48, -24 ); + + //Kick the player angles + pPlayer->ViewPunch( QAngle( -16, yawKick, 2 ) ); + + Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin(); + + // If the player's on my head, don't knock him up + if ( pPlayer->GetGroundEntity() == pOperator ) + { + dir = vecDirection; + dir.z = 0; + } + + VectorNormalize(dir); + + dir *= 500.0f; + + //If not on ground, then don't make them fly! + if ( !(pPlayer->GetFlags() & FL_ONGROUND ) ) + dir.z = 0.0f; + + //Push the target back + pHurt->ApplyAbsVelocityImpulse( dir ); + + if ( !bFlashed ) + { + color32 red = {128,0,0,128}; + UTIL_ScreenFade( pPlayer, red, 0.5f, 0.1f, FFADE_IN ); + } + + // Force the player to drop anyting they were holding + pPlayer->ForceDropOfCarriedPhysObjects(); + } + + // do effect? + } + else + { + WeaponSound( MELEE_MISS ); + } + } + break; + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Sets the state of the stun stick +//----------------------------------------------------------------------------- +void CWeaponStunStick::SetStunState( bool state ) +{ + m_bActive = state; + + if ( m_bActive ) + { + //FIXME: START - Move to client-side + + Vector vecAttachment; + QAngle vecAttachmentAngles; + + GetAttachment( 1, vecAttachment, vecAttachmentAngles ); + g_pEffects->Sparks( vecAttachment ); + + //FIXME: END - Move to client-side + + EmitSound( "Weapon_StunStick.Activate" ); + } + else + { + EmitSound( "Weapon_StunStick.Deactivate" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponStunStick::Deploy( void ) +{ + SetStunState( true ); + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponStunStick::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( BaseClass::Holster( pSwitchingTo ) == false ) + return false; + + SetStunState( false ); + SetWeaponVisible( false ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecVelocity - +//----------------------------------------------------------------------------- +void CWeaponStunStick::Drop( const Vector &vecVelocity ) +{ + SetStunState( false ); + +#ifndef CLIENT_DLL + UTIL_Remove( this ); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWeaponStunStick::GetStunState( void ) +{ + return m_bActive; +} + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Get the attachment point on a viewmodel that a base weapon is using +//----------------------------------------------------------------------------- +bool UTIL_GetWeaponAttachment( C_BaseCombatWeapon *pWeapon, int attachmentID, Vector &absOrigin, QAngle &absAngles ) +{ + // This is already correct in third-person + if ( pWeapon && pWeapon->ShouldDrawUsingViewModel() == false ) + { + return pWeapon->GetAttachment( attachmentID, absOrigin, absAngles ); + } + + // Otherwise we need to translate the attachment to the viewmodel's version and reformat it + CBasePlayer *pOwner = ToBasePlayer( pWeapon->GetOwner() ); + + if ( pOwner != NULL ) + { + int ret = pOwner->GetViewModel()->GetAttachment( attachmentID, absOrigin, absAngles ); + FormatViewModelAttachment( absOrigin, true ); + + return ret; + } + + // Wasn't found + return false; +} + +#define BEAM_ATTACH_CORE_NAME "sparkrear" + +//----------------------------------------------------------------------------- +// Purpose: Sets up the attachment point lookup for the model +//----------------------------------------------------------------------------- +void C_WeaponStunStick::SetupAttachmentPoints( void ) +{ + // Setup points for both types of views + if ( ShouldDrawUsingViewModel() ) + { + const char *szBeamAttachNamesTop[NUM_BEAM_ATTACHMENTS] = + { + "spark1a","spark2a","spark3a","spark4a", + "spark5a","spark6a","spark7a","spark8a", + "spark9a", + }; + + const char *szBeamAttachNamesBottom[NUM_BEAM_ATTACHMENTS] = + { + "spark1b","spark2b","spark3b","spark4b", + "spark5b","spark6b","spark7b","spark8b", + "spark9b", + }; + + // Lookup and store all connections + for ( int i = 0; i < NUM_BEAM_ATTACHMENTS; i++ ) + { + m_BeamAttachments[i].IDs[0] = LookupAttachment( szBeamAttachNamesTop[i] ); + m_BeamAttachments[i].IDs[1] = LookupAttachment( szBeamAttachNamesBottom[i] ); + } + + // Setup the center beam point + m_BeamCenterAttachment = LookupAttachment( BEAM_ATTACH_CORE_NAME ); + } + else + { + // Setup the center beam point + m_BeamCenterAttachment = 1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the stunstick model (with extra effects) +//----------------------------------------------------------------------------- +int C_WeaponStunStick::DrawModel( int flags ) +{ + if ( ShouldDraw() == false ) + return 0; + + // Only render these on the transparent pass + if ( flags & STUDIO_TRANSPARENCY ) + { + DrawEffects(); + return 1; + } + + return BaseClass::DrawModel( flags ); +} + +//----------------------------------------------------------------------------- +// Purpose: Randomly adds extra effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::ClientThink( void ) +{ + if ( InSwing() == false ) + { + if ( m_bSwungLastFrame ) + { + // Start fading + m_flFadeTime = gpGlobals->curtime; + m_bSwungLastFrame = false; + } + + return; + } + + // Remember if we were swinging last frame + m_bSwungLastFrame = InSwing(); + + if ( IsEffectActive( EF_NODRAW ) ) + return; + + if ( ShouldDrawUsingViewModel() ) + { + // Update our effects + if ( gpGlobals->frametime != 0.0f && ( random->RandomInt( 0, 3 ) == 0 ) ) + { + Vector vecOrigin; + QAngle vecAngles; + + // Inner beams + BeamInfo_t beamInfo; + + int attachment = random->RandomInt( 0, 15 ); + + UTIL_GetWeaponAttachment( this, attachment, vecOrigin, vecAngles ); + ::FormatViewModelAttachment( vecOrigin, false ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + CBaseEntity *pBeamEnt = pOwner->GetViewModel(); + + beamInfo.m_vecStart = vec3_origin; + beamInfo.m_pStartEnt= pBeamEnt; + beamInfo.m_nStartAttachment = attachment; + + beamInfo.m_pEndEnt = NULL; + beamInfo.m_nEndAttachment = -1; + beamInfo.m_vecEnd = vecOrigin + RandomVector( -8, 8 ); + + beamInfo.m_pszModelName = STUNSTICK_BEAM_MATERIAL; + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.05f; + beamInfo.m_flWidth = random->RandomFloat( 1.0f, 2.0f ); + beamInfo.m_flEndWidth = 0; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = random->RandomFloat( 16, 32 ); + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 0.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 1.0f; + beamInfo.m_flRed = 255.0f;; + beamInfo.m_flGreen = 255.0f; + beamInfo.m_flBlue = 255.0f; + beamInfo.m_nSegments = 16; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = 0; + + beams->CreateBeamEntPoint( beamInfo ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Starts the client-side version thinking +//----------------------------------------------------------------------------- +void C_WeaponStunStick::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + if ( updateType == DATA_UPDATE_CREATED ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + SetupAttachmentPoints(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tells us we're always a translucent entity +//----------------------------------------------------------------------------- +RenderGroup_t C_WeaponStunStick::GetRenderGroup( void ) +{ + return RENDER_GROUP_TWOPASS; +} + +//----------------------------------------------------------------------------- +// Purpose: Tells us we're always a translucent entity +//----------------------------------------------------------------------------- +bool C_WeaponStunStick::InSwing( void ) +{ + int activity = GetActivity(); + + // FIXME: This is needed until the actual animation works + if ( ShouldDrawUsingViewModel() == false ) + return true; + + // These are the swing activities this weapon can play + if ( activity == GetPrimaryAttackActivity() || + activity == GetSecondaryAttackActivity() || + activity == ACT_VM_MISSCENTER || + activity == ACT_VM_MISSCENTER2 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawThirdPersonEffects( void ) +{ + Vector vecOrigin; + QAngle vecAngles; + float color[3]; + float scale; + + CMatRenderContextPtr pRenderContext( materials ); + IMaterial *pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL, NULL, false ); + pRenderContext->Bind( pMaterial ); + + // Get bright when swung + if ( InSwing() ) + { + color[0] = color[1] = color[2] = 0.4f; + scale = 22.0f; + } + else + { + color[0] = color[1] = color[2] = 0.1f; + scale = 20.0f; + } + + // Draw an all encompassing glow around the entire head + UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + + if ( InSwing() ) + { + pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL2, NULL, false ); + pRenderContext->Bind( pMaterial ); + + color[0] = color[1] = color[2] = random->RandomFloat( 0.6f, 0.8f ); + scale = random->RandomFloat( 4.0f, 6.0f ); + + // Draw an all encompassing glow around the entire head + UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + + // Update our effects + if ( gpGlobals->frametime != 0.0f && ( random->RandomInt( 0, 5 ) == 0 ) ) + { + Vector vecOrigin; + QAngle vecAngles; + + GetAttachment( 1, vecOrigin, vecAngles ); + + Vector vForward; + AngleVectors( vecAngles, &vForward ); + + Vector vEnd = vecOrigin - vForward * 1.0f; + + // Inner beams + BeamInfo_t beamInfo; + + beamInfo.m_vecStart = vEnd; + Vector offset = RandomVector( -12, 8 ); + + offset += Vector(4,4,4); + beamInfo.m_vecEnd = vecOrigin + offset; + + beamInfo.m_pStartEnt= cl_entitylist->GetEnt( BEAMENT_ENTITY( entindex() ) ); + beamInfo.m_pEndEnt = cl_entitylist->GetEnt( BEAMENT_ENTITY( entindex() ) ); + beamInfo.m_nStartAttachment = 1; + beamInfo.m_nEndAttachment = -1; + + beamInfo.m_nType = TE_BEAMTESLA; + beamInfo.m_pszModelName = STUNSTICK_BEAM_MATERIAL; + beamInfo.m_flHaloScale = 0.0f; + beamInfo.m_flLife = 0.01f; + beamInfo.m_flWidth = random->RandomFloat( 1.0f, 3.0f ); + beamInfo.m_flEndWidth = 0; + beamInfo.m_flFadeLength = 0.0f; + beamInfo.m_flAmplitude = random->RandomFloat( 1, 2 ); + beamInfo.m_flBrightness = 255.0; + beamInfo.m_flSpeed = 0.0; + beamInfo.m_nStartFrame = 0.0; + beamInfo.m_flFrameRate = 1.0f; + beamInfo.m_flRed = 255.0f;; + beamInfo.m_flGreen = 255.0f; + beamInfo.m_flBlue = 255.0f; + beamInfo.m_nSegments = 16; + beamInfo.m_bRenderable = true; + beamInfo.m_nFlags = FBEAM_SHADEOUT; + + beams->CreateBeamPoints( beamInfo ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawFirstPersonEffects( void ) +{ + Vector vecOrigin; + QAngle vecAngles; + float color[3]; + float scale; + + CMatRenderContextPtr pRenderContext( materials ); + IMaterial *pMaterial = materials->FindMaterial( STUNSTICK_GLOW_MATERIAL_NOZ, NULL, false ); + // FIXME: Needs to work with new IMaterial system! + pRenderContext->Bind( pMaterial ); + + // Find where we are in the fade + float fadeAmount = RemapValClamped( gpGlobals->curtime, m_flFadeTime, m_flFadeTime + FADE_DURATION, 1.0f, 0.1f ); + + // Get bright when swung + if ( InSwing() ) + { + color[0] = color[1] = color[2] = 0.4f; + scale = 22.0f; + } + else + { + color[0] = color[1] = color[2] = 0.4f * fadeAmount; + scale = 20.0f; + } + + if ( color[0] > 0.0f ) + { + // Draw an all encompassing glow around the entire head + UTIL_GetWeaponAttachment( this, m_BeamCenterAttachment, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + } + + // Draw bright points at each attachment location + for ( int i = 0; i < (NUM_BEAM_ATTACHMENTS*2)+1; i++ ) + { + if ( InSwing() ) + { + color[0] = color[1] = color[2] = random->RandomFloat( 0.05f, 0.5f ); + scale = random->RandomFloat( 4.0f, 5.0f ); + } + else + { + color[0] = color[1] = color[2] = random->RandomFloat( 0.05f, 0.5f ) * fadeAmount; + scale = random->RandomFloat( 4.0f, 5.0f ) * fadeAmount; + } + + if ( color[0] > 0.0f ) + { + UTIL_GetWeaponAttachment( this, i, vecOrigin, vecAngles ); + DrawHalo( pMaterial, vecOrigin, scale, color ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawEffects( void ) +{ + if ( ShouldDrawUsingViewModel() ) + { + DrawFirstPersonEffects(); + } + else + { + DrawThirdPersonEffects(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Viewmodel was drawn +//----------------------------------------------------------------------------- +void C_WeaponStunStick::ViewModelDrawn( C_BaseViewModel *pBaseViewModel ) +{ + // Don't bother when we're not deployed + if ( IsWeaponVisible() ) + { + // Do all our special effects + DrawEffects(); + } + + BaseClass::ViewModelDrawn( pBaseViewModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a cheap glow quad at our impact point (with sparks) +//----------------------------------------------------------------------------- +void StunstickImpactCallback( const CEffectData &data ) +{ + float scale = random->RandomFloat( 16, 32 ); + + FX_AddQuad( data.m_vOrigin, + data.m_vNormal, + scale, + scale*2.0f, + 1.0f, + 1.0f, + 0.0f, + 0.0f, + random->RandomInt( 0, 360 ), + 0, + Vector( 1.0f, 1.0f, 1.0f ), + 0.1f, + "sprites/light_glow02_add", + 0 ); + + FX_Sparks( data.m_vOrigin, 1, 2, data.m_vNormal, 6, 64, 256 ); +} + +DECLARE_CLIENT_EFFECT( "StunstickImpact", StunstickImpactCallback ); + +#endif
\ No newline at end of file |