From b09d1629eef0e001cec9289536d23a8f97a91fb2 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Sun, 10 Aug 2025 14:12:32 -0500 Subject: [PATCH 01/14] Add NPC, clientside activity, and Mapbase param support to HL2MP weapons --- src/game/client/c_basecombatcharacter.cpp | 19 + src/game/client/c_basecombatcharacter.h | 5 + src/game/client/c_baseplayer.h | 4 + src/game/server/basecombatcharacter.cpp | 70 --- src/game/server/player.cpp | 27 - src/game/server/soundent.cpp | 32 + src/game/shared/ai_basenpc_shared.h | 21 + .../shared/basecombatcharacter_shared.cpp | 69 +++ src/game/shared/basecombatweapon_shared.cpp | 10 +- src/game/shared/baseplayer_shared.cpp | 35 ++ src/game/shared/hl2mp/weapon_357.cpp | 299 +++++++++- src/game/shared/hl2mp/weapon_ar2.cpp | 343 ++++++++++- src/game/shared/hl2mp/weapon_ar2.h | 14 +- src/game/shared/hl2mp/weapon_crossbow.cpp | 479 ++++++++++++++- src/game/shared/hl2mp/weapon_crowbar.cpp | 162 ++++-- src/game/shared/hl2mp/weapon_crowbar.h | 3 - src/game/shared/hl2mp/weapon_frag.cpp | 76 ++- src/game/shared/hl2mp/weapon_hl2mpbase.cpp | 117 ++++ src/game/shared/hl2mp/weapon_hl2mpbase.h | 14 + .../hl2mp/weapon_hl2mpbase_machinegun.cpp | 39 ++ .../hl2mp/weapon_hl2mpbase_machinegun.h | 4 + .../hl2mp/weapon_hl2mpbasebasebludgeon.cpp | 195 ++++++- .../hl2mp/weapon_hl2mpbasebasebludgeon.h | 21 +- .../weapon_hl2mpbasehlmpcombatweapon.cpp | 26 + .../hl2mp/weapon_hl2mpbasehlmpcombatweapon.h | 7 + src/game/shared/hl2mp/weapon_pistol.cpp | 265 ++++++++- src/game/shared/hl2mp/weapon_rpg.cpp | 547 +++++++++++++++++- src/game/shared/hl2mp/weapon_rpg.h | 58 +- src/game/shared/hl2mp/weapon_shotgun.cpp | 323 ++++++++++- src/game/shared/hl2mp/weapon_slam.cpp | 26 +- src/game/shared/hl2mp/weapon_slam.h | 2 +- src/game/shared/hl2mp/weapon_smg1.cpp | 402 ++++++++++++- src/game/shared/hl2mp/weapon_stunstick.cpp | 43 +- src/game/shared/hl2mp/weapon_stunstick.h | 8 +- 34 files changed, 3433 insertions(+), 332 deletions(-) create mode 100644 src/game/shared/ai_basenpc_shared.h diff --git a/src/game/client/c_basecombatcharacter.cpp b/src/game/client/c_basecombatcharacter.cpp index 6b6ac08317d..1be649d41f4 100644 --- a/src/game/client/c_basecombatcharacter.cpp +++ b/src/game/client/c_basecombatcharacter.cpp @@ -109,6 +109,25 @@ void C_BaseCombatCharacter::DoMuzzleFlash() } } +#ifdef MAPBASE +// UNDONE: Should these operate on a list of weapon/items +Activity C_BaseCombatCharacter::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) +{ + Activity translated = baseAct; + + if ( m_hActiveWeapon ) + { + translated = m_hActiveWeapon->ActivityOverride( baseAct, pRequired ); + } + else if (pRequired) + { + *pRequired = false; + } + + return translated; +} +#endif + #ifdef GLOWS_ENABLE //----------------------------------------------------------------------------- // Purpose: diff --git a/src/game/client/c_basecombatcharacter.h b/src/game/client/c_basecombatcharacter.h index 053dd7dc1ab..92f834e9249 100644 --- a/src/game/client/c_basecombatcharacter.h +++ b/src/game/client/c_basecombatcharacter.h @@ -116,6 +116,11 @@ class C_BaseCombatCharacter : public C_BaseFlex HSCRIPT ScriptGetWeapon( int i ); #endif +#ifdef MAPBASE + virtual Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired ); + virtual Activity Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired = false, C_BaseCombatWeapon *pSpecificWeapon = NULL ); +#endif + public: float m_flNextAttack; diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index b6ffbfe1c27..f00a1039418 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -131,6 +131,10 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener virtual Vector Weapon_ShootPosition(); virtual void Weapon_DropPrimary( void ) {} +#ifdef MAPBASE + virtual Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); +#endif + virtual Vector GetAutoaimVector( float flScale ); void SetSuitUpdate(const char *name, int fgroup, int iNoRepeat); diff --git a/src/game/server/basecombatcharacter.cpp b/src/game/server/basecombatcharacter.cpp index a45b72ed8f4..5d09d782024 100644 --- a/src/game/server/basecombatcharacter.cpp +++ b/src/game/server/basecombatcharacter.cpp @@ -2800,76 +2800,6 @@ bool CBaseCombatCharacter::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) return true; } -#ifdef MAPBASE - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -static Activity Weapon_BackupActivityFromList( CBaseCombatCharacter *pBCC, acttable_t *pTable, int actCount, Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pWeapon ) -{ - int i = 0; - for ( ; i < actCount; i++, pTable++ ) - { - if ( activity == pTable->baseAct ) - { - // Don't pick backup activities we don't actually have an animation for. - if (!pBCC->GetModelPtr()->HaveSequenceForActivity(pTable->weaponAct)) - break; - - return (Activity)pTable->weaponAct; - } - } - - // We didn't succeed in finding an activity. See if we can recurse - acttable_t *pBackupTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pTable - i, actCount ); - if (pBackupTable) - { - return Weapon_BackupActivityFromList( pBCC, pBackupTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); - } - - return activity; -} - -//----------------------------------------------------------------------------- -// Purpose: Uses an activity from a different weapon when the activity we were originally looking for does not exist on this character. -// This gives NPCs and players the ability to use weapons they are otherwise unable to use. -//----------------------------------------------------------------------------- -Activity CBaseCombatCharacter::Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pSpecificWeapon ) -{ - CBaseCombatWeapon *pWeapon = pSpecificWeapon ? pSpecificWeapon : GetActiveWeapon(); - if (!pWeapon) - return activity; - - // Make sure the weapon allows this activity to have a backup. - if (!pWeapon->SupportsBackupActivity(activity)) - return activity; - - // UNDONE: Sometimes, a NPC is supposed to use the default activity. Return that if the weapon translation was "not required" and we have an original activity. - /* - if (!weaponTranslationWasRequired && GetModelPtr()->HaveSequenceForActivity(activity) && !IsPlayer()) - { - return activity; - } - */ - - acttable_t *pTable = pWeapon->GetBackupActivityList(); - int actCount = pWeapon->GetBackupActivityListCount(); - if (!pTable) - { - // Look for a default list - acttable_t *pTable = pWeapon->ActivityList( actCount ); - pTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pTable, actCount ); - } - - if (pTable && GetModelPtr()) - { - return Weapon_BackupActivityFromList( this, pTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); - } - - return activity; -} -#endif - //----------------------------------------------------------------------------- // Purpose: // Input : diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index cbedca4487c..fb2da48eef9 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -8149,33 +8149,6 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) } } -#ifdef MAPBASE -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -Activity CBasePlayer::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) -{ - Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); - - if ( GetActiveWeapon() && !GetActiveWeapon()->IsWeaponVisible() && baseAct != ACT_ARM ) - { - // Our weapon is holstered. Use the base activity. - return baseAct; - } - if ( GetModelPtr() && (!GetModelPtr()->HaveSequenceForActivity(weaponTranslation) || baseAct == weaponTranslation) ) - { - // This is used so players can fall back to backup activities in the same way NPCs in Mapbase can - Activity backupActivity = Weapon_BackupActivity(baseAct, pRequired ? *pRequired : false); - if ( baseAct != backupActivity && GetModelPtr()->HaveSequenceForActivity(backupActivity) ) - return backupActivity; - - return baseAct; - } - - return weaponTranslation; -} -#endif - //========================================================= // HasNamedPlayerItem Does the player already have this item? diff --git a/src/game/server/soundent.cpp b/src/game/server/soundent.cpp index a84f767645f..d28125f1a64 100644 --- a/src/game/server/soundent.cpp +++ b/src/game/server/soundent.cpp @@ -9,6 +9,9 @@ #include "soundent.h" #include "game.h" #include "world.h" +#ifdef MAPBASE_MP +#include "ai_basenpc.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -459,6 +462,35 @@ void CSoundEnt::InsertSound ( int iType, const Vector &vecOrigin, int iVolume, f if ( !g_pSoundEnt ) return; +#if defined(MAPBASE_MP) && defined(HL2MP) + // Mapbase adds AI sounds to HL2DM weapons, but this fills up the list very quickly and isn't needed when NPCs aren't actually being used + // Ignore weapon sounds when we're reasonably certain they wouldn't be useful + if ( soundChannelIndex == SOUNDENT_CHANNEL_WEAPON ) + { + bool bHasNPCNearby = false; + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + CAI_BaseNPC *pNPC = ppAIs[i]; + + if ( !pNPC->IsAlive() || pNPC == pOwner ) + continue; + + // Is there any chance this NPC would hear me? + Vector vecDistSqr = vecOrigin - pNPC->EarPosition(); + if (vecDistSqr.LengthSqr() > Square(iVolume)) + continue; + + bHasNPCNearby = true; + break; + } + + // Just don't if there's no NPC nearby + if (!bHasNPCNearby) + return; + } +#endif + if( soundChannelIndex == SOUNDENT_CHANNEL_UNSPECIFIED ) { // No sound channel specified. So just make a new sound. diff --git a/src/game/shared/ai_basenpc_shared.h b/src/game/shared/ai_basenpc_shared.h new file mode 100644 index 00000000000..615a472140b --- /dev/null +++ b/src/game/shared/ai_basenpc_shared.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_BASENPC_SHARED_H +#define AI_BASENPC_SHARED_H + +#ifdef CLIENT_DLL +#include "c_ai_basenpc.h" +#else +#include "ai_basenpc.h" +#endif + +#ifdef CLIENT_DLL +#define CAI_BaseNPC C_AI_BaseNPC +#endif + +#endif // AI_BASENPC_SHARED_H diff --git a/src/game/shared/basecombatcharacter_shared.cpp b/src/game/shared/basecombatcharacter_shared.cpp index d937e59a600..3fdd8bafe86 100644 --- a/src/game/shared/basecombatcharacter_shared.cpp +++ b/src/game/shared/basecombatcharacter_shared.cpp @@ -237,6 +237,75 @@ void CBaseCombatCharacter::InputSetBloodColor( inputdata_t &inputdata ) } #endif +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static Activity Weapon_BackupActivityFromList( CBaseCombatCharacter *pBCC, acttable_t *pTable, int actCount, Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pWeapon ) +{ + int i = 0; + for ( ; i < actCount; i++, pTable++ ) + { + if ( activity == pTable->baseAct ) + { + // Don't pick backup activities we don't actually have an animation for. + if (!pBCC->GetModelPtr()->HaveSequenceForActivity(pTable->weaponAct)) + break; + + return (Activity)pTable->weaponAct; + } + } + + // We didn't succeed in finding an activity. See if we can recurse + acttable_t *pBackupTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pTable - i, actCount ); + if (pBackupTable) + { + return Weapon_BackupActivityFromList( pBCC, pBackupTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); + } + + return activity; +} + +//----------------------------------------------------------------------------- +// Purpose: Uses an activity from a different weapon when the activity we were originally looking for does not exist on this character. +// This gives NPCs and players the ability to use weapons they are otherwise unable to use. +//----------------------------------------------------------------------------- +Activity CBaseCombatCharacter::Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pSpecificWeapon ) +{ + CBaseCombatWeapon *pWeapon = pSpecificWeapon ? pSpecificWeapon : GetActiveWeapon(); + if (!pWeapon) + return activity; + + // Make sure the weapon allows this activity to have a backup. + if (!pWeapon->SupportsBackupActivity(activity)) + return activity; + + // UNDONE: Sometimes, a NPC is supposed to use the default activity. Return that if the weapon translation was "not required" and we have an original activity. + /* + if (!weaponTranslationWasRequired && GetModelPtr()->HaveSequenceForActivity(activity) && !IsPlayer()) + { + return activity; + } + */ + + acttable_t *pTable = pWeapon->GetBackupActivityList(); + int actCount = pWeapon->GetBackupActivityListCount(); + if (!pTable) + { + // Look for a default list + acttable_t *pDefaultTable = pWeapon->ActivityList( actCount ); + pTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pDefaultTable, actCount ); + } + + if (pTable && GetModelPtr()) + { + return Weapon_BackupActivityFromList( this, pTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); + } + + return activity; +} +#endif + //----------------------------------------------------------------------------- /** The main visibility check. Checks all the entity specific reasons that could diff --git a/src/game/shared/basecombatweapon_shared.cpp b/src/game/shared/basecombatweapon_shared.cpp index e1965ebba2b..0a671ab51ba 100644 --- a/src/game/shared/basecombatweapon_shared.cpp +++ b/src/game/shared/basecombatweapon_shared.cpp @@ -1158,7 +1158,7 @@ WeaponClass_t CBaseCombatWeapon::WeaponClassFromString(const char *str) return WEPCLASS_INVALID; } -#ifdef HL2_DLL +#if defined(HL2_DLL) || defined(HL2MP) // HL2MP is effectively used here as "present on client in MP" extern acttable_t *GetSMG1Acttable(); extern int GetSMG1ActtableCount(); @@ -1206,7 +1206,7 @@ int CBaseCombatWeapon::GetBackupActivityListCount() //----------------------------------------------------------------------------- acttable_t *CBaseCombatWeapon::GetDefaultBackupActivityList( acttable_t *pTable, int &actCount ) { -#ifdef HL2_DLL +#if defined(HL2_DLL) || defined(HL2MP) // HL2MP is effectively used here as "present on client in MP" // Ensure this isn't already a default backup activity list if (pTable == GetSMG1Acttable() || pTable == GetPistolActtable()) return NULL; @@ -1318,6 +1318,9 @@ void CBaseCombatWeapon::SetActivity( Activity act, float duration ) { //Adrian: Oh man... #if !defined( CLIENT_DLL ) && (defined( HL2MP ) || defined( PORTAL )) +#ifdef MAPBASE_MP + if ( GetOwner() && GetOwner()->IsPlayer() ) +#endif SetModel( GetWorldModel() ); #endif @@ -1329,6 +1332,9 @@ void CBaseCombatWeapon::SetActivity( Activity act, float duration ) //Adrian: Oh man again... #if !defined( CLIENT_DLL ) && (defined( HL2MP ) || defined( PORTAL )) +#ifdef MAPBASE_MP + if ( GetOwner() && GetOwner()->IsPlayer() ) +#endif SetModel( GetViewModel() ); #endif diff --git a/src/game/shared/baseplayer_shared.cpp b/src/game/shared/baseplayer_shared.cpp index ba0294d4ac9..b703a2e0f11 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -875,6 +875,41 @@ void CBasePlayer::SelectLastItem(void) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CBasePlayer::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) +{ + Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + +#ifdef CLIENT_DLL + // Since other players' weapons have invisible viewmodels + bool bWeaponNotVisible = (GetActiveWeapon() && GetActiveWeapon()->IsEffectActive( EF_NODRAW )) ? true : false; +#else + bool bWeaponNotVisible = (GetActiveWeapon() && !GetActiveWeapon()->IsWeaponVisible()) ? true : false; +#endif + + if ( bWeaponNotVisible && baseAct != ACT_ARM ) + { + // Our weapon is holstered. Use the base activity. + return baseAct; + } + if ( GetModelPtr() && (!GetModelPtr()->HaveSequenceForActivity(weaponTranslation) || baseAct == weaponTranslation) ) + { + // This is used so players can fall back to backup activities in the same way NPCs in Mapbase can + Activity backupActivity = Weapon_BackupActivity(baseAct, pRequired ? *pRequired : false); + if ( baseAct != backupActivity && GetModelPtr()->HaveSequenceForActivity(backupActivity) ) + return backupActivity; + + return baseAct; + } + + return weaponTranslation; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Abort any reloads we're in //----------------------------------------------------------------------------- diff --git a/src/game/shared/hl2mp/weapon_357.cpp b/src/game/shared/hl2mp/weapon_357.cpp index cac81b01bd3..b2d052a57b0 100644 --- a/src/game/shared/hl2mp/weapon_357.cpp +++ b/src/game/shared/hl2mp/weapon_357.cpp @@ -8,12 +8,14 @@ #include "cbase.h" #include "npcevent.h" #include "in_buttons.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" #include #else #include "hl2mp_player.h" + #include "te_effect_dispatch.h" #endif #include "weapon_hl2mpbasehlmpcombatweapon.h" @@ -26,6 +28,11 @@ // CWeapon357 //----------------------------------------------------------------------------- +#ifdef MAPBASE +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); +#endif + class CWeapon357 : public CBaseHL2MPCombatWeapon { DECLARE_CLASS( CWeapon357, CBaseHL2MPCombatWeapon ); @@ -34,11 +41,54 @@ class CWeapon357 : public CBaseHL2MPCombatWeapon CWeapon357( void ); void PrimaryAttack( void ); + +#ifdef MAPBASE + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + +#ifndef CLIENT_DLL + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } +#endif + + virtual int GetMinBurst() { return 1; } + virtual int GetMaxBurst() { return 1; } + virtual float GetMinRestTime( void ) { return 1.0f; } + virtual float GetMaxRestTime( void ) { return 2.5f; } + + virtual float GetFireRate( void ) { return 1.0f; } + + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone = VECTOR_CONE_15DEGREES; + if (!GetOwner() || !GetOwner()->IsNPC()) + return cone; + + static Vector NPCCone = VECTOR_CONE_5DEGREES; + +#ifndef CLIENT_DLL + static Vector AllyCone = VECTOR_CONE_2DEGREES; + if( GetOwner()->MyNPCPointer()->IsPlayerAlly() ) + { + // 357 allies should be cooler + return AllyCone; + } +#endif + + return NPCCone; + } + + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + + virtual acttable_t *GetBackupActivityList() { return GetPistolActtable(); } + virtual int GetBackupActivityListCount() { return GetPistolActtableCount(); } +#endif + DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); +#ifndef CLIENT_DLL + DECLARE_DATADESC(); #endif private: @@ -59,23 +109,140 @@ PRECACHE_WEAPON_REGISTER( weapon_357 ); #ifndef CLIENT_DLL -acttable_t CWeapon357::m_acttable[] = +BEGIN_DATADESC( CWeapon357 ) +END_DATADESC() +#endif + +#ifdef MAPBASE +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 }, -}; +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_REVOLVER, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_REVOLVER, true }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_REVOLVER, true }, + { ACT_RELOAD, ACT_RELOAD_REVOLVER, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_REVOLVER, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_REVOLVER, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_REVOLVER, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_REVOLVER_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_REVOLVER_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_REVOLVER_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_REVOLVER_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_REVOLVER, false }, + { ACT_WALK, ACT_WALK_REVOLVER, true }, + { ACT_RUN, ACT_RUN_REVOLVER, true }, +#else + { ACT_IDLE, ACT_IDLE_PISTOL, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_PISTOL, true }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, true }, + { ACT_RELOAD, ACT_RELOAD_PISTOL, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_PISTOL, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_PISTOL, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_PISTOL,true }, + { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_PISTOL_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_PISTOL_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, false }, + { ACT_WALK, ACT_WALK_PISTOL, false }, + { ACT_RUN, ACT_RUN_PISTOL, false }, +#endif + // + // Activities ported from weapon_alyxgun below + // + // Readiness activities (not aiming) +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, +#else + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, +#endif + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, -IMPLEMENT_ACTTABLE( CWeapon357 ); +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, +#else + { ACT_WALK_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, +#endif + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, +#else + { ACT_RUN_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, +#endif + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities + + // Crouch activities + { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, + { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + + // Readiness translations + { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, + { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, + { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, + { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_REVOLVER_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_REVOLVER_MED, false }, +#endif #ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_REVOLVER, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_REVOLVER, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_REVOLVER, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_REVOLVER, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_REVOLVER, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_REVOLVER, false }, +#else + { 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 }, +#endif +#endif +}; + + +IMPLEMENT_ACTTABLE( CWeapon357 ); + // Allows Weapon_BackupActivity() to access the 357's activity table. acttable_t *Get357Acttable() { @@ -88,8 +255,6 @@ int Get357ActtableCount() } #endif -#endif - //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- @@ -97,8 +262,104 @@ CWeapon357::CWeapon357( void ) { m_bReloadsSingly = false; m_bFiresUnderwater = false; + +#ifdef MAPBASE + m_fMinRange1 = 24; + m_fMaxRange1 = 1000; + m_fMinRange2 = 24; + m_fMaxRange2 = 200; +#endif } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeapon357::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + + switch( pEvent->event ) + { +#ifndef CLIENT_DLL + case EVENT_WEAPON_RELOAD: + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + CEffectData data; + + // Emit six spent shells + for ( int i = 0; i < 6; i++ ) + { + data.m_vOrigin = pOwner->WorldSpaceCenter() + RandomVector( -4, 4 ); + data.m_vAngles = QAngle( 90, random->RandomInt( 0, 360 ), 0 ); + data.m_nEntIndex = entindex(); + + DispatchEffect( "ShellEject", data ); + } + + break; + } +#endif +#ifdef MAPBASE + case EVENT_WEAPON_PISTOL_FIRE: + { +#ifdef CLIENT_DLL + WeaponSound( SINGLE_NPC ); +#else + Vector vecShootOrigin, vecShootDir; + vecShootOrigin = pOperator->Weapon_ShootPosition(); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + Assert( npc != NULL ); + + vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); + + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +#endif + } + break; + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; +#endif + } +} + +#ifndef CLIENT_DLL +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeapon357::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + WeaponSound( SINGLE_NPC ); + + FireBulletsInfo_t info( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_iTracerFreq = 1; + + pOperator->FireBullets( info ); + + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Some things need this. (e.g. the new Force(X)Fire inputs or blindfire actbusy) +//----------------------------------------------------------------------------- +void CWeapon357::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +} +#endif +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -139,7 +400,7 @@ void CWeapon357::PrimaryAttack( void ) m_iClip1--; Vector vecSrc = pPlayer->Weapon_ShootPosition(); - Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); FireBulletsInfo_t info( 1, vecSrc, vecAiming, vec3_origin, MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); info.m_pAttacker = pPlayer; @@ -147,6 +408,10 @@ void CWeapon357::PrimaryAttack( void ) // Fire the bullets, and force the first shot to be perfectly accuracy pPlayer->FireBullets( info ); +#ifndef CLIENT_DLL + pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); +#endif + #ifdef CLIENT_DLL //Disorient the player if ( prediction->IsFirstTimePredicted() ) @@ -162,6 +427,10 @@ void CWeapon357::PrimaryAttack( void ) pPlayer->ViewPunch( QAngle( -8, random->RandomFloat( -2, 2 ), 0 ) ); +#ifndef CLIENT_DLL + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 600, 0.2, GetOwner() ); +#endif + if ( !m_iClip1 && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) { // HEV suit - indicate out of ammo condition diff --git a/src/game/shared/hl2mp/weapon_ar2.cpp b/src/game/shared/hl2mp/weapon_ar2.cpp index 702aa1a81e9..296fb893729 100644 --- a/src/game/shared/hl2mp/weapon_ar2.cpp +++ b/src/game/shared/hl2mp/weapon_ar2.cpp @@ -7,14 +7,19 @@ #include "cbase.h" #include "npcevent.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" #include "c_te_effect_dispatch.h" #else #include "hl2mp_player.h" + #include "soundent.h" #include "te_effect_dispatch.h" #include "prop_combine_ball.h" +#ifdef MAPBASE + #include "npc_playercompanion.h" +#endif #endif #include "weapon_ar2.h" @@ -33,6 +38,16 @@ ConVar sk_weapon_ar2_alt_fire_mass( "sk_weapon_ar2_alt_fire_mass", "150" ); //========================================================= //========================================================= +#ifndef CLIENT_DLL +BEGIN_DATADESC( CWeaponAR2 ) + + DEFINE_FIELD( m_flDelayedFire, FIELD_TIME ), + DEFINE_FIELD( m_bShotDelayed, FIELD_BOOLEAN ), + //DEFINE_FIELD( m_nVentPose, FIELD_INTEGER ), + +END_DATADESC() +#endif + IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAR2, DT_WeaponAR2 ) @@ -58,18 +73,137 @@ 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 }, +#if AR2_ACTIVITY_FIX == 1 + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR2, true }, + { ACT_RELOAD, ACT_RELOAD_AR2, true }, + { ACT_IDLE, ACT_IDLE_AR2, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_AR2, false }, + + { ACT_WALK, ACT_WALK_AR2, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_AR2_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_AR2_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_AR2_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_AR2_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_AR2_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_AR2_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_AR2, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_AR2, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR2, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_AR2, false }, + { ACT_COVER_LOW, ACT_COVER_AR2_LOW, true }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR2_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_AR2_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_AR2_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_AR2, true }, +// { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, +#else + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR2, true }, + { ACT_RELOAD, ACT_RELOAD_SMG1, true }, // FIXME: hook to AR2 unique + { ACT_IDLE, ACT_IDLE_SMG1, true }, // FIXME: hook to AR2 unique + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, // FIXME: hook to AR2 unique + + { ACT_WALK, ACT_WALK_RIFLE, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_AR2, false }, + { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, // FIXME: hook to AR2 unique + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR2_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true }, // FIXME: hook to AR2 unique + { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, +// { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_AR2_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_AR2_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAR2); @@ -87,8 +221,6 @@ int GetAR2ActtableCount() } #endif -#endif - CWeaponAR2::CWeaponAR2( ) { m_fMinRange1 = 65; @@ -202,6 +334,9 @@ void CWeaponAR2::DelayedAttack( void ) // Register a muzzleflash for the AI pOwner->DoMuzzleFlash(); +#ifndef CLIENT_DLL + pOwner->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); +#endif WeaponSound( WPN_DOUBLE ); @@ -234,8 +369,6 @@ void CWeaponAR2::DelayedAttack( void ) angles.y += random->RandomInt( -4, 4 ); angles.z = 0; -// pOwner->SnapEyeAngles( angles ); - pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -12, -8 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); // Decrease ammo @@ -273,6 +406,10 @@ void CWeaponAR2::SecondaryAttack( void ) m_bShotDelayed = true; m_flNextEmptySoundTime = m_flNextPrimaryAttack = m_flNextSecondaryAttack = m_flDelayedFire = gpGlobals->curtime + 0.5f; +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif + SendWeaponAnim( ACT_VM_FIDGET ); WeaponSound( SPECIAL1 ); } @@ -309,6 +446,184 @@ bool CWeaponAR2::Reload( void ) return BaseClass::Reload(); } +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOperator - +//----------------------------------------------------------------------------- +void CWeaponAR2::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ) +{ + Vector vecShootOrigin, vecShootDir; + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + Assert( npc != NULL ); + + if ( bUseWeaponAngles ) + { + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + } + else + { + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); + } + + WeaponSoundRealtime( SINGLE_NPC ); + + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + FireBulletsInfo_t info( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_iTracerFreq = 2; + + pOperator->FireBullets( info ); + + // NOTENOTE: This is overriden on the client-side + // pOperator->DoMuzzleFlash(); + + m_iClip1 = m_iClip1 - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponAR2::FireNPCSecondaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ) +{ + WeaponSound( WPN_DOUBLE ); + + if ( !GetOwner() ) + return; + + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + if ( !pNPC ) + return; + + // Fire! + Vector vecSrc; + Vector vecAiming; + + if ( bUseWeaponAngles ) + { + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecSrc, angShootDir ); + AngleVectors( angShootDir, &vecAiming ); + } + else + { + vecSrc = pNPC->Weapon_ShootPosition( ); + + Vector vecTarget; + +#ifdef MAPBASE + // It's shared across all NPCs now that it's available on more than just soldiers on more than just the AR2. + vecTarget = pNPC->GetAltFireTarget(); +#else + CNPC_Combine *pSoldier = dynamic_cast( pNPC ); + if ( pSoldier ) + { + // In the distant misty past, elite soldiers tried to use bank shots. + // Therefore, we must ask them specifically what direction they are shooting. + vecTarget = pSoldier->GetAltFireTarget(); + } +#ifdef MAPBASE + else if ( CNPC_PlayerCompanion *pCompanion = dynamic_cast( pNPC ) ) + { + // Companions can use energy balls now. Isn't that lovely? + vecTarget = pCompanion->GetAltFireTarget(); + } +#endif + else + { + // All other users of the AR2 alt-fire shoot directly at their enemy. + if ( !pNPC->GetEnemy() ) + return; + + vecTarget = pNPC->GetEnemy()->BodyTarget( vecSrc ); + } +#endif + + vecAiming = vecTarget - vecSrc; + VectorNormalize( vecAiming ); + } + + Vector impactPoint = vecSrc + ( vecAiming * MAX_TRACE_LENGTH ); + + float flAmmoRatio = 1.0f; + float flDuration = RemapValClamped( flAmmoRatio, 0.0f, 1.0f, 0.5f, sk_weapon_ar2_alt_fire_duration.GetFloat() ); + float flRadius = RemapValClamped( flAmmoRatio, 0.0f, 1.0f, 4.0f, sk_weapon_ar2_alt_fire_radius.GetFloat() ); + + // Fire the bullets + Vector vecVelocity = vecAiming * 1000.0f; + + // Fire the combine ball +#ifdef MAPBASE + CBaseEntity *pBall = CreateCombineBall( vecSrc, + vecVelocity, + flRadius, + sk_weapon_ar2_alt_fire_mass.GetFloat(), + flDuration, + pNPC ); + + variant_t var; + var.SetEntity(pBall); + pNPC->FireNamedOutput("OnThrowGrenade", var, pBall, pNPC); +#else + CreateCombineBall( vecSrc, + vecVelocity, + flRadius, + sk_weapon_ar2_alt_fire_mass.GetFloat(), + flDuration, + pNPC ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponAR2::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + if ( bSecondary ) + { + FireNPCSecondaryAttack( pOperator, true ); + } + else + { + // Ensure we have enough rounds in the clip + m_iClip1++; + + FireNPCPrimaryAttack( pOperator, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CWeaponAR2::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_AR2: + { + FireNPCPrimaryAttack( pOperator, false ); + } + break; + + case EVENT_WEAPON_AR2_ALTFIRE: + { + FireNPCSecondaryAttack( pOperator, false ); + } + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/shared/hl2mp/weapon_ar2.h b/src/game/shared/hl2mp/weapon_ar2.h index fa06cef5468..53ebc2aef8b 100644 --- a/src/game/shared/hl2mp/weapon_ar2.h +++ b/src/game/shared/hl2mp/weapon_ar2.h @@ -32,8 +32,9 @@ class CWeaponAR2 : public CHL2MPMachineGun DECLARE_PREDICTABLE(); #ifndef CLIENT_DLL - DECLARE_ACTTABLE(); + DECLARE_DATADESC(); #endif + DECLARE_ACTTABLE(); void ItemPostFrame( void ); void Precache( void ); @@ -45,12 +46,23 @@ class CWeaponAR2 : public CHL2MPMachineGun void AddViewKick( void ); +#ifndef CLIENT_DLL + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ); + void FireNPCSecondaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#endif + int GetMinBurst( void ) { return 2; } int GetMaxBurst( void ) { return 5; } float GetFireRate( void ) { return 0.1f; } bool CanHolster( void ); bool Reload( void ); + +#ifndef CLIENT_DLL + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } +#endif Activity GetPrimaryAttackActivity( void ); diff --git a/src/game/shared/hl2mp/weapon_crossbow.cpp b/src/game/shared/hl2mp/weapon_crossbow.cpp index 6b3d280ee44..4047a9edffb 100644 --- a/src/game/shared/hl2mp/weapon_crossbow.cpp +++ b/src/game/shared/hl2mp/weapon_crossbow.cpp @@ -7,6 +7,7 @@ #include "cbase.h" #include "npcevent.h" #include "in_buttons.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" @@ -40,8 +41,15 @@ extern ConVar sk_plr_dmg_crossbow; extern ConVar sk_npc_dmg_crossbow; +#ifdef MAPBASE +ConVar weapon_crossbow_new_hit_locations( "weapon_crossbow_new_hit_locations", "1", FCVAR_NONE, "Toggles new crossbow knockback that properly pushes back the correct limbs." ); +#endif + void TE_StickyBolt( IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin ); +#define BOLT_SKIN_NORMAL 0 +#define BOLT_SKIN_GLOW 1 + //----------------------------------------------------------------------------- // Crossbow Bolt //----------------------------------------------------------------------------- @@ -50,7 +58,7 @@ class CCrossbowBolt : public CBaseCombatCharacter DECLARE_CLASS( CCrossbowBolt, CBaseCombatCharacter ); public: - CCrossbowBolt() { }; + CCrossbowBolt(); ~CCrossbowBolt(); Class_T Classify( void ) { return CLASS_NONE; } @@ -62,7 +70,20 @@ class CCrossbowBolt : public CBaseCombatCharacter void BoltTouch( CBaseEntity *pOther ); bool CreateVPhysics( void ); unsigned int PhysicsSolidMaskForEntity() const; +#ifdef MAPBASE + static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseCombatCharacter *pentOwner = NULL ) + { + return BoltCreate( vecOrigin, angAngles, 0, pentOwner ); + } + static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CBaseCombatCharacter *pentOwner = NULL ); + + void InputSetDamage( inputdata_t &inputdata ); + float m_flDamage; + + virtual void SetDamage(float flDamage) { m_flDamage = flDamage; } +#else static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CBasePlayer *pentOwner = NULL ); +#endif protected: @@ -70,8 +91,10 @@ class CCrossbowBolt : public CBaseCombatCharacter CHandle m_pGlowSprite; //CHandle m_pGlowTrail; - + +#ifndef MAPBASE int m_iDamage; +#endif DECLARE_DATADESC(); DECLARE_SERVERCLASS(); @@ -87,12 +110,23 @@ BEGIN_DATADESC( CCrossbowBolt ) DEFINE_FIELD( m_pGlowSprite, FIELD_EHANDLE ), //DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDamage", InputSetDamage ), +#endif + END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CCrossbowBolt, DT_CrossbowBolt ) END_SEND_TABLE() +#ifdef MAPBASE +CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CBaseCombatCharacter *pentOwner ) +#else CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CBasePlayer *pentOwner ) +#endif { // Create a new entity with CCrossbowBolt private data CCrossbowBolt *pBolt = (CCrossbowBolt *)CreateEntityByName( "crossbow_bolt" ); @@ -100,12 +134,27 @@ CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle pBolt->SetAbsAngles( angAngles ); pBolt->Spawn(); pBolt->SetOwnerEntity( pentOwner ); - - pBolt->m_iDamage = iDamage; +#ifdef MAPBASE + if (pentOwner && pentOwner->IsNPC()) + pBolt->m_flDamage = sk_npc_dmg_crossbow.GetFloat(); + else if (iDamage != 0) + pBolt->m_flDamage = (float)iDamage; +#endif return pBolt; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCrossbowBolt::CCrossbowBolt( void ) +{ +#ifdef MAPBASE + // Independent bolts without m_flDamage set need damage + m_flDamage = sk_plr_dmg_crossbow.GetFloat(); +#endif +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -194,14 +243,40 @@ void CCrossbowBolt::Precache( void ) PrecacheModel( "sprites/light_glow02_noz.vmt" ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrossbowBolt::InputSetDamage( inputdata_t &inputdata ) +{ + m_flDamage = inputdata.value.Float(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pOther - //----------------------------------------------------------------------------- void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) { - if ( !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) ) - return; + if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) ) + { + // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them. +#ifdef MAPBASE + // But some physics objects that are also triggers (like weapons) shouldn't go through this check. + // + // Note: rpg_missile has the same code, except it properly accounts for weapons in a different way. + // This was discovered after I implemented this and both work fine, but if this ever causes problems, + // use rpg_missile's implementation: + // + // if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) + // + if ( pOther->GetMoveType() == MOVETYPE_NONE && (( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY )) ) +#else + if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) +#endif + return; + } if ( pOther->m_takedamage != DAMAGE_NO ) { @@ -212,9 +287,57 @@ void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) ClearMultiDamage(); VectorNormalize( vecNormalizedVel ); +#if defined(HL2_EPISODIC) + //!!!HACKHACK - specific hack for ep2_outland_10 to allow crossbow bolts to pass through her bounding box when she's crouched in front of the player + // (the player thinks they have clear line of sight because Alyx is crouching, but her BBOx is still full-height and blocks crossbow bolts. + if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->Classify() == CLASS_PLAYER_ALLY_VITAL && FStrEq(STRING(gpGlobals->mapname), "ep2_outland_10") ) + { + // Change the owner to stop further collisions with Alyx. We do this by making her the owner. + // The player won't get credit for this kill but at least the bolt won't magically disappear! + SetOwnerEntity( pOther ); + return; + } +#endif//HL2_EPISODIC + +#ifdef MAPBASE + if (weapon_crossbow_new_hit_locations.GetInt() > 0) + { + // A very experimental and weird way of getting a crossbow bolt to deal accurate knockback. + CBaseAnimating *pOtherAnimating = pOther->GetBaseAnimating(); + if (pOtherAnimating && pOtherAnimating->GetModelPtr() && pOtherAnimating->GetModelPtr()->numbones() > 1) + { + int iClosestBone = -1; + float flCurDistSqr = Square(128.0f); + matrix3x4_t bonetoworld; + Vector vecBonePos; + for (int i = 0; i < pOtherAnimating->GetModelPtr()->numbones(); i++) + { + pOtherAnimating->GetBoneTransform( i, bonetoworld ); + MatrixPosition( bonetoworld, vecBonePos ); + + float flDist = vecBonePos.DistToSqr(GetLocalOrigin()); + if (flDist < flCurDistSqr) + { + iClosestBone = i; + flCurDistSqr = flDist; + } + } + + if (iClosestBone != -1) + { + tr.physicsbone = pOtherAnimating->GetPhysicsBone(iClosestBone); + } + } + } +#endif + if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC() ) { +#ifdef MAPBASE + CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_flDamage, DMG_NEVERGIB ); +#else CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_NEVERGIB ); +#endif dmgInfo.AdjustPlayerDamageInflictedForSkillLevel(); CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f ); dmgInfo.SetDamagePosition( tr.endpos ); @@ -222,7 +345,11 @@ void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) } else { +#ifdef MAPBASE + CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_flDamage, DMG_BULLET | DMG_NEVERGIB ); +#else CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_BULLET | DMG_NEVERGIB ); +#endif CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f ); dmgInfo.SetDamagePosition( tr.endpos ); pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr ); @@ -372,6 +499,9 @@ void CCrossbowBolt::BubbleThink( void ) SetNextThink( gpGlobals->curtime + 0.1f ); + // Make danger sounds out in front of me, to scare snipers back into their hole + CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, GetAbsOrigin() + GetAbsVelocity() * 0.2, 120.0f, 0.5f, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + if ( GetWaterLevel() == 0 ) return; @@ -388,9 +518,9 @@ void CCrossbowBolt::BubbleThink( void ) #define CWeaponCrossbow C_WeaponCrossbow #endif -class CWeaponCrossbow : public CBaseHL2MPCombatWeapon +class CWeaponCrossbow : public CBaseHLCombatWeapon { - DECLARE_CLASS( CWeaponCrossbow, CBaseHL2MPCombatWeapon ); + DECLARE_CLASS( CWeaponCrossbow, CBaseHLCombatWeapon ); public: CWeaponCrossbow( void ); @@ -404,23 +534,55 @@ class CWeaponCrossbow : public CBaseHL2MPCombatWeapon virtual void ItemPostFrame( void ); virtual void ItemBusyFrame( void ); virtual bool SendWeaponAnim( int iActivity ); + virtual bool IsWeaponZoomed() { return m_bInZoom; } -#ifndef CLIENT_DLL virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + +#ifdef MAPBASE +#ifndef CLIENT_DLL + virtual void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + virtual void Reload_NPC( bool bPlaySound = true ); + + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } +#endif + + virtual int GetMinBurst() { return 1; } + virtual int GetMaxBurst() { return 1; } + + virtual float GetMinRestTime( void ) { return 3.0f; } // 1.5f + virtual float GetMaxRestTime( void ) { return 3.0f; } // 2.0f + + virtual float GetFireRate( void ) { return 5.0f; } + + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone = VECTOR_CONE_15DEGREES; + if (!GetOwner() || !GetOwner()->IsNPC()) + return cone; + + static Vector NPCCone = VECTOR_CONE_5DEGREES; + + return NPCCone; + } #endif DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); #ifndef CLIENT_DLL - DECLARE_ACTTABLE(); + DECLARE_DATADESC(); #endif + DECLARE_ACTTABLE(); private: void SetSkin( int skinNum ); void CheckZoomToggle( void ); void FireBolt( void ); +#ifdef MAPBASE + void SetBolt( int iSetting ); + void FireNPCBolt( CAI_BaseNPC *pOwner, Vector &vecShootOrigin, Vector &vecShootDir ); +#endif void ToggleZoom( void ); // Various states for the crossbow's charger @@ -480,15 +642,140 @@ PRECACHE_WEAPON_REGISTER( weapon_crossbow ); #ifndef CLIENT_DLL +BEGIN_DATADESC( CWeaponCrossbow ) + + DEFINE_FIELD( m_bInZoom, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bMustReload, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nChargeState, FIELD_INTEGER ), + DEFINE_FIELD( m_hChargerSprite, FIELD_EHANDLE ), + +END_DATADESC() + +#endif + +#ifdef MAPBASE 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 }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_CROSSBOW, true }, + { ACT_RELOAD, ACT_RELOAD_CROSSBOW, true }, + { ACT_IDLE, ACT_IDLE_CROSSBOW, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_CROSSBOW, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_CROSSBOW_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_CROSSBOW_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_CROSSBOW, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_CROSSBOW_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_CROSSBOW_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_CROSSBOW, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_CROSSBOW_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_CROSSBOW_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_CROSSBOW, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_CROSSBOW_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_CROSSBOW_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_CROSSBOW, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_CROSSBOW_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_CROSSBOW_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_CROSSBOW, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_CROSSBOW_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_CROSSBOW_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_CROSSBOW, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_CROSSBOW, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_CROSSBOW, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_CROSSBOW, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_CROSSBOW, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_CROSSBOW, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_CROSSBOW_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_CROSSBOW_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_CROSSBOW_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_CROSSBOW_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_CROSSBOW, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#else + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, + { ACT_RELOAD, ACT_RELOAD_SMG1, true }, + { ACT_IDLE, ACT_IDLE_SMG1, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, + + { ACT_WALK, ACT_WALK_RIFLE, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG1, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_CROSSBOW_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_CROSSBOW_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponCrossbow); @@ -516,6 +803,13 @@ CWeaponCrossbow::CWeaponCrossbow( void ) m_bFiresUnderwater = true; m_bInZoom = false; m_bMustReload = false; + +#ifdef MAPBASE + m_fMinRange1 = 24; + m_fMaxRange1 = 5000; + m_fMinRange2 = 24; + m_fMaxRange2 = 5000; +#endif } #define CROSSBOW_GLOW_SPRITE "sprites/light_glow02_noz.vmt" @@ -560,11 +854,16 @@ void CWeaponCrossbow::PrimaryAttack( void ) SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration( ACT_VM_PRIMARYATTACK ) ); + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( pPlayer ) + { +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif #ifdef GAME_DLL - CBasePlayer *player = ToBasePlayer( GetOwner() ); - if ( player ) - player->OnMyWeaponFired( this ); + pPlayer->OnMyWeaponFired( this ); #endif + } } //----------------------------------------------------------------------------- @@ -642,7 +941,11 @@ void CWeaponCrossbow::FireBolt( void ) else { WeaponSound( EMPTY ); +#ifdef MAPBASE_MP + m_flNextPrimaryAttack = gpGlobals->curtime + 0.15f; +#else m_flNextPrimaryAttack = 0.15; +#endif } return; @@ -660,6 +963,23 @@ void CWeaponCrossbow::FireBolt( void ) QAngle angAiming; VectorAngles( vecAiming, angAiming ); +#if defined(HL2_EPISODIC) + // !!!HACK - the other piece of the Alyx crossbow bolt hack for Outland_10 (see ::BoltTouch() for more detail) + if( FStrEq(STRING(gpGlobals->mapname), "ep2_outland_10") ) + { + trace_t tr; + UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 24.0f, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr ); + + if( tr.m_pEnt != NULL && tr.m_pEnt->Classify() == CLASS_PLAYER_ALLY_VITAL ) + { + // If Alyx is right in front of the player, make sure the bolt starts outside of the player's BBOX, or the bolt + // will instantly collide with the player after the owner of the bolt is switched to Alyx in ::BoltTouch(). We + // avoid this altogether by making it impossible for the bolt to collide with the player. + vecSrc += vecAiming * 24.0f; + } + } +#endif + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecSrc, angAiming, GetHL2MPWpnData().m_iPlayerDamage, pOwner ); if ( pOwner->GetWaterLevel() == 3 ) @@ -675,11 +995,18 @@ void CWeaponCrossbow::FireBolt( void ) m_iClip1--; +#ifdef MAPBASE + SetBolt( 1 ); +#endif + pOwner->ViewPunch( QAngle( -2, 0, 0 ) ); WeaponSound( SINGLE ); WeaponSound( SPECIAL2 ); +#ifndef CLIENT_DLL + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 200, 0.2 ); +#endif SendWeaponAnim( ACT_VM_PRIMARYATTACK ); if ( !m_iClip1 && pOwner->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) @@ -694,6 +1021,85 @@ void CWeaponCrossbow::FireBolt( void ) SetChargerState( CHARGER_STATE_DISCHARGE ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets whether or not the bolt is visible +//----------------------------------------------------------------------------- +inline void CWeaponCrossbow::SetBolt( int iSetting ) +{ + int iBody = FindBodygroupByName( "bolt" ); + if (iBody != -1 /*|| (GetOwner() && GetOwner()->IsPlayer())*/) // TODO: Player models check the viewmodel instead of the worldmodel, but setting the bodygroup regardless can cause a crash, so we need a better solution + SetBodygroup( iBody, iSetting ); + else + m_nSkin = iSetting; +} + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::FireNPCBolt( CAI_BaseNPC *pOwner, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + Assert(pOwner); + + QAngle angAiming; + VectorAngles( vecShootDir, angAiming ); + + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecShootOrigin, angAiming, pOwner ); + + if ( pOwner->GetWaterLevel() == 3 ) + { + pBolt->SetAbsVelocity( vecShootDir * BOLT_WATER_VELOCITY ); + } + else + { + pBolt->SetAbsVelocity( vecShootDir * BOLT_AIR_VELOCITY ); + } + + m_iClip1--; + + SetBolt( 1 ); + + WeaponSound( SINGLE_NPC ); + WeaponSound( SPECIAL2 ); + + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 200, 0.2 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + 2.5f; + + SetSkin( BOLT_SKIN_GLOW ); + SetChargerState( CHARGER_STATE_DISCHARGE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::Reload_NPC( bool bPlaySound ) +{ + BaseClass::Reload_NPC( bPlaySound ); + + SetBolt( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCBolt( pOperator->MyNPCPointer(), vecShootOrigin, vecShootDir ); + + //m_bMustReload = true; +} +#endif +#endif + //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. @@ -702,11 +1108,18 @@ bool CWeaponCrossbow::Deploy( void ) { if ( m_iClip1 <= 0 ) { +#ifdef MAPBASE + SetBolt( 1 ); +#endif return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() ); } SetSkin( BOLT_SKIN_GLOW ); +#ifdef MAPBASE + SetBolt( 0 ); +#endif + return BaseClass::Deploy(); } @@ -766,7 +1179,11 @@ void CWeaponCrossbow::CreateChargerEffects( void ) #ifndef CLIENT_DLL CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); +#ifdef MAPBASE + if ( m_hChargerSprite != NULL || pOwner == NULL ) +#else if ( m_hChargerSprite != NULL ) +#endif return; m_hChargerSprite = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE, GetAbsOrigin(), false ); @@ -868,6 +1285,10 @@ void CWeaponCrossbow::SetChargerState( ChargerState_t state ) // Shoot some sparks and draw a beam between the two outer points DoLoadEffect(); + +#ifdef MAPBASE + SetBolt( 0 ); +#endif break; #ifndef CLIENT_DLL @@ -928,7 +1349,6 @@ void CWeaponCrossbow::SetChargerState( ChargerState_t state ) } } -#ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: // Input : *pEvent - @@ -953,14 +1373,27 @@ void CWeaponCrossbow::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombat SetChargerState( CHARGER_STATE_READY ); break; +#if defined(MAPBASE) && !defined(CLIENT_DLL) + case EVENT_WEAPON_SMG1: + { + CAI_BaseNPC *pNPC = pOperator->MyNPCPointer(); + Assert(pNPC); + + Vector vecSrc = pNPC->Weapon_ShootPosition(); + Vector vecAiming = pNPC->GetActualShootTrajectory( vecSrc ); + + FireNPCBolt( pNPC, vecSrc, vecAiming ); + //m_bMustReload = true; + } + break; +#endif + 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 diff --git a/src/game/shared/hl2mp/weapon_crowbar.cpp b/src/game/shared/hl2mp/weapon_crowbar.cpp index 55a03d1d691..abd12f7b773 100644 --- a/src/game/shared/hl2mp/weapon_crowbar.cpp +++ b/src/game/shared/hl2mp/weapon_crowbar.cpp @@ -29,6 +29,8 @@ #define CROWBAR_RANGE 75.0f #define CROWBAR_REFIRE 0.4f +ConVar sk_plr_dmg_crowbar ( "sk_plr_dmg_crowbar","0", FCVAR_REPLICATED ); +ConVar sk_npc_dmg_crowbar ( "sk_npc_dmg_crowbar","0", FCVAR_REPLICATED ); //----------------------------------------------------------------------------- // CWeaponCrowbar @@ -45,24 +47,38 @@ END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( weapon_crowbar, CWeaponCrowbar ); PRECACHE_WEAPON_REGISTER( weapon_crowbar ); -#ifndef CLIENT_DLL - -acttable_t CWeaponCrowbar::m_acttable[] = +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 }, + { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, + { ACT_IDLE, ACT_IDLE_ANGRY_MELEE, false }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, + + { ACT_ARM, ACT_ARM_MELEE, false }, + { ACT_DISARM, ACT_DISARM_MELEE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_MELEE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponCrowbar); -#endif - //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- @@ -77,7 +93,10 @@ CWeaponCrowbar::CWeaponCrowbar( void ) //----------------------------------------------------------------------------- float CWeaponCrowbar::GetDamageForActivity( Activity hitActivity ) { - return 25.0f; + if ( ( GetOwner() != NULL ) && ( GetOwner()->IsPlayer() ) ) + return sk_plr_dmg_crowbar.GetFloat(); // 25 + + return sk_npc_dmg_crowbar.GetFloat(); } //----------------------------------------------------------------------------- @@ -101,56 +120,6 @@ void CWeaponCrowbar::AddViewKick( void ) #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!) //----------------------------------------------------------------------------- @@ -201,6 +170,71 @@ int CWeaponCrowbar::WeaponMeleeAttack1Condition( float flDot, float flDist ) return COND_CAN_MELEE_ATTACK1; } + +//----------------------------------------------------------------------------- +// 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 ); + + 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(), 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; + } +} #endif diff --git a/src/game/shared/hl2mp/weapon_crowbar.h b/src/game/shared/hl2mp/weapon_crowbar.h index 2cd10fb9c0a..57301cd41c6 100644 --- a/src/game/shared/hl2mp/weapon_crowbar.h +++ b/src/game/shared/hl2mp/weapon_crowbar.h @@ -35,10 +35,7 @@ class CWeaponCrowbar : public CBaseHL2MPBludgeonWeapon DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); - -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); -#endif CWeaponCrowbar(); diff --git a/src/game/shared/hl2mp/weapon_frag.cpp b/src/game/shared/hl2mp/weapon_frag.cpp index 8f4b7b001ed..a86af4bc1b2 100644 --- a/src/game/shared/hl2mp/weapon_frag.cpp +++ b/src/game/shared/hl2mp/weapon_frag.cpp @@ -15,6 +15,7 @@ #include "hl2mp_player.h" #include "te_effect_dispatch.h" #include "grenade_frag.h" + #include "soundent.h" #endif #include "weapon_ar2.h" @@ -59,12 +60,14 @@ class CWeaponFrag: public CBaseHL2MPCombatWeapon bool Deploy( void ); bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + +#ifndef CLIENT_DLL + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } +#endif 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 ); } @@ -83,28 +86,44 @@ class CWeaponFrag: public CBaseHL2MPCombatWeapon CWeaponFrag( const CWeaponFrag & ); -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); +#ifndef CLIENT_DLL + DECLARE_DATADESC(); #endif }; #ifndef CLIENT_DLL +BEGIN_DATADESC( CWeaponFrag ) + DEFINE_FIELD( m_bRedraw, FIELD_BOOLEAN ), + DEFINE_FIELD( m_AttackPaused, FIELD_INTEGER ), + DEFINE_FIELD( m_fDrawbackFinished, FIELD_BOOLEAN ), +END_DATADESC() + +#endif + 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 }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponFrag); -#endif - IMPLEMENT_NETWORKCLASS_ALIASED( WeaponFrag, DT_WeaponFrag ) BEGIN_NETWORK_TABLE( CWeaponFrag, DT_WeaponFrag ) @@ -153,7 +172,6 @@ void CWeaponFrag::Precache( void ) PrecacheScriptSound( "WeaponFrag.Roll" ); } -#ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: // Input : *pEvent - @@ -199,10 +217,27 @@ void CWeaponFrag::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatChar 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! - } -} + +#ifndef CLIENT_DLL + // Make a sound designed to scare snipers back into their holes! + CBaseCombatCharacter *pOwner = GetOwner(); + + if( pOwner ) + { + Vector vecSrc = pOwner->Weapon_ShootPosition(); + Vector vecDir; + + AngleVectors( pOwner->EyeAngles(), &vecDir ); + + trace_t tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SOLID_BRUSHONLY, pOwner, COLLISION_GROUP_NONE, &tr ); + CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, tr.endpos, 384, 0.2, pOwner ); + } #endif + } +} //----------------------------------------------------------------------------- // Purpose: @@ -488,6 +523,9 @@ void CWeaponFrag::LobGrenade( CBasePlayer *pPlayer ) if ( pGrenade ) { +#ifdef MAPBASE_MP + if ( GetHL2MPWpnData().m_iPlayerDamage > 0 ) +#endif pGrenade->SetDamage( GetHL2MPWpnData().m_iPlayerDamage ); pGrenade->SetDamageRadius( GRENADE_DAMAGE_RADIUS ); } @@ -496,7 +534,11 @@ void CWeaponFrag::LobGrenade( CBasePlayer *pPlayer ) WeaponSound( WPN_DOUBLE ); // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif m_bRedraw = true; } @@ -549,7 +591,11 @@ void CWeaponFrag::RollGrenade( CBasePlayer *pPlayer ) WeaponSound( SPECIAL1 ); // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif m_bRedraw = true; } diff --git a/src/game/shared/hl2mp/weapon_hl2mpbase.cpp b/src/game/shared/hl2mp/weapon_hl2mpbase.cpp index 15995a5be7d..3a37436e1bb 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbase.cpp +++ b/src/game/shared/hl2mp/weapon_hl2mpbase.cpp @@ -10,6 +10,10 @@ #include "takedamageinfo.h" #include "ammodef.h" #include "hl2mp_gamerules.h" +#ifdef MAPBASE +#include "mapbase/protagonist_system.h" +#include "eventlist.h" +#endif #ifdef CLIENT_DLL @@ -292,6 +296,68 @@ void CWeaponHL2MPBase::FireBullets( const FireBulletsInfo_t &info ) BaseClass::FireBullets( modinfo ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CWeaponHL2MPBase::GetViewModel( int viewmodelindex ) const +{ + if (GetOwner() && GetOwner()->IsPlayer() && viewmodelindex == 0) + { + const char *pszProtagVM = g_ProtagonistSystem.GetProtagonist_ViewModel( static_cast(GetOwner()), this ); + if (pszProtagVM) + return pszProtagVM; + } + + return BaseClass::GetViewModel( viewmodelindex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CWeaponHL2MPBase::GetViewmodelFOVOverride() const +{ + if (GetOwner() && GetOwner()->IsPlayer()) + { + float *flVMFOV = g_ProtagonistSystem.GetProtagonist_ViewModelFOV( static_cast(GetOwner()), this ); + if (flVMFOV) + return *flVMFOV; + } + + return BaseClass::GetViewmodelFOVOverride(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponHL2MPBase::UsesHands() const +{ + if (GetOwner() && GetOwner()->IsPlayer()) + { + bool *bProtagUsesHands = g_ProtagonistSystem.GetProtagonist_UsesHands( static_cast(GetOwner()), this ); + if (bProtagUsesHands) + return *bProtagUsesHands; + } + + return BaseClass::UsesHands(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CWeaponHL2MPBase::GetHandRig() const +{ + if (GetOwner() && GetOwner()->IsPlayer()) + { + int *nProtagHandRig = g_ProtagonistSystem.GetProtagonist_HandRig( static_cast(GetOwner()), this ); + if (nProtagHandRig) + return *nProtagHandRig; + } + + return BaseClass::GetHandRig(); +} +#endif + #if defined( CLIENT_DLL ) @@ -299,9 +365,60 @@ void CWeaponHL2MPBase::FireBullets( const FireBulletsInfo_t &info ) #define NUM_MUZZLE_FLASH_TYPES 4 +#ifdef MAPBASE_MP +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CWeaponHL2MPBase::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + if ( (pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_CLIENT) ) + { + /*if ( pEvent->event == AE_NPC_WEAPON_FIRE ) + { + bool bSecondary = (atoi( pEvent->options ) != 0); + Operator_ForceNPCFire( pOperator, bSecondary ); + return; + } + else*/ if ( pEvent->event == AE_WPN_PLAYWPNSOUND ) + { + int iSnd = GetWeaponSoundFromString(pEvent->options); + if ( iSnd != -1 ) + { + WeaponSound( (WeaponSound_t)iSnd ); + } + } + } + + //DevWarning( 2, "Unhandled animation event %d from %s --> %s\n", pEvent->event, pOperator->GetClassname(), GetClassname() ); +} +#endif + bool CWeaponHL2MPBase::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) { +#ifdef MAPBASE_MP + bool bBase = BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); + + if (!bBase) + { + //If the player is receiving this message, pass it through + C_BasePlayer *pOwner = ToBasePlayer( GetOwner() ); + + if ( pOwner != NULL ) + { + animevent_t animEvent; + animEvent.event = event; + animEvent.options = options; + Operator_HandleAnimEvent( &animEvent, pOwner ); + return true; + } + } + + return bBase; +#else return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); +#endif } diff --git a/src/game/shared/hl2mp/weapon_hl2mpbase.h b/src/game/shared/hl2mp/weapon_hl2mpbase.h index 0f21044b1c1..aef902a5119 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbase.h +++ b/src/game/shared/hl2mp/weapon_hl2mpbase.h @@ -13,6 +13,9 @@ #include "hl2mp_player_shared.h" #include "basecombatweapon_shared.h" #include "hl2mp_weapon_parse.h" +#ifdef MAPBASE_MP +#include "npcevent.h" +#endif #if defined( CLIENT_DLL ) #define CWeaponHL2MPBase C_WeaponHL2MPBase @@ -60,6 +63,13 @@ class CWeaponHL2MPBase : public CBaseCombatWeapon virtual void FireBullets( const FireBulletsInfo_t &info ); virtual void FallInit( void ); + +#ifdef MAPBASE + virtual const char *GetViewModel( int viewmodelindex = 0 ) const; + virtual float GetViewmodelFOVOverride() const; + virtual bool UsesHands( void ) const; + virtual int GetHandRig( void ) const; +#endif public: #if defined( CLIENT_DLL ) @@ -69,6 +79,10 @@ class CWeaponHL2MPBase : public CBaseCombatWeapon virtual bool OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ); +#ifdef MAPBASE_MP + virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#endif + #else virtual void Spawn(); diff --git a/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp b/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp index a20cea56b10..0a3baf61227 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp +++ b/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.cpp @@ -10,6 +10,9 @@ #include "c_hl2mp_player.h" #else #include "hl2mp_player.h" + #ifdef MAPBASE + #include "ai_basenpc.h" + #endif #endif #include "weapon_hl2mpbase_machinegun.h" @@ -109,6 +112,10 @@ void CHL2MPMachineGun::PrimaryAttack( void ) //Factor in the view kick AddViewKick(); + +#if defined(MAPBASE_MP) && !defined(CLIENT_DLL) + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pPlayer, SOUNDENT_CHANNEL_WEAPON ); +#endif if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { @@ -118,6 +125,11 @@ void CHL2MPMachineGun::PrimaryAttack( void ) SendWeaponAnim( GetPrimaryAttackActivity() ); pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#if defined(MAPBASE_MP) && !defined(CLIENT_DLL) + // Register a muzzleflash for the AI + pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); +#endif } //----------------------------------------------------------------------------- @@ -132,6 +144,33 @@ void CHL2MPMachineGun::FireBullets( const FireBulletsInfo_t &info ) } } +#if defined(MAPBASE_MP) && !defined(CLIENT_DLL) +//----------------------------------------------------------------------------- +// Purpose: Weapon firing conditions +//----------------------------------------------------------------------------- +int CHL2MPMachineGun::WeaponRangeAttack1Condition( float flDot, float flDist ) +{ + if ( m_iClip1 <=0 ) + { + return COND_NO_PRIMARY_AMMO; + } + else if ( flDist < m_fMinRange1 ) + { + return COND_TOO_CLOSE_TO_ATTACK; + } + else if ( flDist > m_fMaxRange1 ) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if ( flDot < 0.5f ) // UNDONE: Why check this here? Isn't the AI checking this already? + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_RANGE_ATTACK1; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h b/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h index e1cdd3245b9..3ebdf384a63 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h +++ b/src/game/shared/hl2mp/weapon_hl2mpbase_machinegun.h @@ -45,6 +45,10 @@ class CHL2MPMachineGun : public CBaseHL2MPCombatWeapon // utility function static void DoMachineGunKick( CBasePlayer *pPlayer, float dampEasy, float maxVerticleKickAngle, float fireDurationTime, float slideLimitTime ); +#if defined(MAPBASE_MP) && !defined(CLIENT_DLL) + virtual int WeaponRangeAttack1Condition( float flDot, float flDist ); +#endif + private: CHL2MPMachineGun( const CHL2MPMachineGun & ); diff --git a/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp b/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp index 336cfcb7c58..0acac6bd70f 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp +++ b/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp @@ -16,11 +16,18 @@ #if defined( CLIENT_DLL ) #include "c_hl2mp_player.h" + #include "takedamageinfo.h" + #ifdef MAPBASE_MP + #include "c_ai_basenpc.h" + #endif #else #include "hl2mp_player.h" #include "ndebugoverlay.h" #include "te_effect_dispatch.h" #include "ilagcompensationmanager.h" + #ifdef MAPBASE + #include "ai_basenpc.h" + #endif #endif // memdbgon must be the last include file in a .cpp file!!! @@ -69,6 +76,28 @@ void CBaseHL2MPBludgeonWeapon::Precache( void ) BaseClass::Precache(); } +#if defined(MAPBASE) && !defined(CLIENT_DLL) +int CBaseHL2MPBludgeonWeapon::CapabilitiesGet() +{ + return bits_CAP_WEAPON_MELEE_ATTACK1; +} + + +int CBaseHL2MPBludgeonWeapon::WeaponMeleeAttack1Condition( float flDot, float flDist ) +{ + if (flDist > 64) + { + return COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.7) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} +#endif + //------------------------------------------------------------------------------ // Purpose : Update weapon //------------------------------------------------------------------------------ @@ -79,6 +108,22 @@ void CBaseHL2MPBludgeonWeapon::ItemPostFrame( void ) if ( pOwner == NULL ) return; +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + m_bShotDelayed = false; + WeaponIdle(); + return; + } + + // See if we need to fire off our secondary round + if (m_bShotDelayed) + { + if (gpGlobals->curtime > m_flDelayedFire) + DelayedAttack(); + } + else +#endif if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime) ) { PrimaryAttack(); @@ -137,6 +182,11 @@ void CBaseHL2MPBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity ) //Do view kick // AddViewKick(); +#if defined(MAPBASE_MP) && !defined(CLIENT_DLL) + //Make sound for the AI + CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, traceHit.endpos, 400, 0.2f, pPlayer ); +#endif + CBaseEntity *pHitEntity = traceHit.m_pEnt; //Apply damage to a hit target @@ -147,7 +197,11 @@ void CBaseHL2MPBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity ) VectorNormalize( hitDirection ); #ifndef CLIENT_DLL +#ifdef MAPBASE + CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), GetDamageType() ); +#else CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#endif if( pPlayer && pHitEntity->IsNPC() ) { @@ -303,9 +357,12 @@ void CBaseHL2MPBludgeonWeapon::Swing( int bIsSecondary ) #ifndef CLIENT_DLL // Like bullets, bludgeon traces have to trace against triggers. +#ifdef MAPBASE + CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), GetDamageType() ); +#else CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); - TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, vec3_origin ); #endif + TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, vec3_origin ); if ( traceHit.fraction == 1.0 ) { @@ -334,8 +391,11 @@ void CBaseHL2MPBludgeonWeapon::Swing( int bIsSecondary ) } } } +#endif +#ifndef MAPBASE WeaponSound( SINGLE ); +#endif // ------------------------- // Miss @@ -344,16 +404,23 @@ void CBaseHL2MPBludgeonWeapon::Swing( int bIsSecondary ) { nHitActivity = bIsSecondary ? ACT_VM_MISSCENTER2 : ACT_VM_MISSCENTER; +#ifdef MAPBASE + //Play swing sound + WeaponSound( SINGLE ); +#else // We want to test the first swing again Vector testEnd = swingStart + forward * GetRange(); // See if we happened to hit water ImpactWater( swingStart, testEnd ); +#endif } +#ifndef MAPBASE else { Hit( traceHit, nHitActivity ); } +#endif // Send the anim SendWeaponAnim( nHitActivity ); @@ -363,4 +430,130 @@ void CBaseHL2MPBludgeonWeapon::Swing( int bIsSecondary ) //Setup our next attack times m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate(); m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); + +#ifdef MAPBASE + if (GetHitDelay() > 0.f) + { + //Play swing sound + WeaponSound(SINGLE); + + m_flDelayedFire = gpGlobals->curtime + GetHitDelay(); + m_bShotDelayed = true; + } + else + { + if (traceHit.fraction == 1.0f) + { + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + //Play swing sound + WeaponSound(SINGLE); + + // See if we happened to hit water + ImpactWater(swingStart, testEnd); + } + else + { + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); +#ifndef CLIENT_DLL + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); +#endif + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, nHitActivity); + } + } +#endif +} + +#ifdef MAPBASE +void CBaseHL2MPBludgeonWeapon::DelayedAttack(void) +{ + m_bShotDelayed = false; + + 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 ); + + 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 + { + ChooseIntersectionPointAndActivity(traceHit, g_bludgeonMins, g_bludgeonMaxs, pOwner); + } + } + } + + if (traceHit.fraction == 1.0f) + { + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + // See if we happened to hit water + ImpactWater(swingStart, testEnd); + } + else + { +#ifndef CLIENT_DLL + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(GetActivity()), GetDamageType()); + triggerInfo.SetDamagePosition(traceHit.startpos); + triggerInfo.SetDamageForce(forward); +#endif + + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); +#ifndef CLIENT_DLL + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); +#endif + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, GetActivity()); + } +} + +bool CBaseHL2MPBludgeonWeapon::CanHolster(void) +{ + if (m_bShotDelayed) + return false; + + return BaseClass::CanHolster(); } +#endif // MAPBASE diff --git a/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h b/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h index 54160dfddd6..679a0d6c5d0 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h +++ b/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.h @@ -40,6 +40,9 @@ class CBaseHL2MPBludgeonWeapon : public CBaseHL2MPCombatWeapon //Attack functions virtual void PrimaryAttack( void ); virtual void SecondaryAttack( void ); +#ifdef MAPBASE + void DelayedAttack(void); +#endif // MAPBASE virtual void ItemPostFrame( void ); @@ -51,6 +54,17 @@ class CBaseHL2MPBludgeonWeapon : public CBaseHL2MPCombatWeapon virtual float GetRange( void ) { return 32.0f; } virtual float GetDamageForActivity( Activity hitActivity ) { return 1.0f; } +#ifdef MAPBASE +#ifndef CLIENT_DLL + virtual int CapabilitiesGet( void ); + virtual int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#endif + + virtual int GetDamageType() { return DMG_CLUB; } + virtual float GetHitDelay() { return 0.f; } + virtual bool CanHolster( void ); +#endif // MAPBASE + CBaseHL2MPBludgeonWeapon( const CBaseHL2MPBludgeonWeapon & ); virtual bool PlayFleshyHittySoundOnHit() const { return false; } @@ -63,6 +77,11 @@ class CBaseHL2MPBludgeonWeapon : public CBaseHL2MPCombatWeapon void Swing( int bIsSecondary ); void Hit( trace_t &traceHit, Activity nHitActivity ); Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); + +#ifdef MAPBASE + float m_flDelayedFire; + bool m_bShotDelayed; +#endif // MAPBASE }; -#endif +#endif \ No newline at end of file diff --git a/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp b/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp index c697adaac66..84943d8a435 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp +++ b/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.cpp @@ -12,6 +12,10 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +ConVar hl2mp_allow_weapon_lower( "hl2mp_allow_weapon_lower", "0", FCVAR_REPLICATED ); +#endif + LINK_ENTITY_TO_CLASS( basehl2mpcombatweapon, CBaseHL2MPCombatWeapon ); IMPLEMENT_NETWORKCLASS_ALIASED( BaseHL2MPCombatWeapon , DT_BaseHL2MPCombatWeapon ) @@ -82,6 +86,20 @@ void CBaseHL2MPCombatWeapon::ItemHolsterFrame( void ) } } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CBaseHL2MPCombatWeapon::CanLower() +{ +#ifdef MAPBASE + if ( !hl2mp_allow_weapon_lower.GetBool() ) + return BaseClass::CanLower(); +#endif + + if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) + return false; + return true; +} + //----------------------------------------------------------------------------- // Purpose: Drops the weapon into a lowered pose // Output : Returns true on success, false on failure. @@ -308,6 +326,14 @@ float CBaseHL2MPCombatWeapon::CalcViewmodelBob( void ) 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 ); + +#ifdef MAPBASE + if (GetBobScale() != 1.0f) + { + //g_verticalBob *= GetBobScale(); + g_lateralBob *= GetBobScale(); + } +#endif //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) return 0.0f; diff --git a/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h b/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h index e8469326319..ba5c34fa2d2 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h +++ b/src/game/shared/hl2mp/weapon_hl2mpbasehlmpcombatweapon.h @@ -17,6 +17,12 @@ #define CBaseHL2MPCombatWeapon C_BaseHL2MPCombatWeapon #endif +#ifdef MAPBASE + // Alias for migrated HL2 weapons + #undef CBaseHLCombatWeapon + #define CBaseHLCombatWeapon CBaseHL2MPCombatWeapon +#endif + class CBaseHL2MPCombatWeapon : public CWeaponHL2MPBase { #if !defined( CLIENT_DLL ) @@ -33,6 +39,7 @@ class CBaseHL2MPCombatWeapon : public CWeaponHL2MPBase virtual bool WeaponShouldBeLowered( void ); virtual bool Ready( void ); + virtual bool CanLower( void ); virtual bool Lower( void ); virtual bool Deploy( void ); virtual bool Holster( CBaseCombatWeapon *pSwitchingTo ); diff --git a/src/game/shared/hl2mp/weapon_pistol.cpp b/src/game/shared/hl2mp/weapon_pistol.cpp index cbaadfffec6..c69f1e0255b 100644 --- a/src/game/shared/hl2mp/weapon_pistol.cpp +++ b/src/game/shared/hl2mp/weapon_pistol.cpp @@ -7,6 +7,7 @@ #include "cbase.h" #include "npcevent.h" #include "in_buttons.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" @@ -50,12 +51,25 @@ class CWeaponPistol : public CBaseHL2MPCombatWeapon void UpdatePenaltyTime( void ); +#ifndef CLIENT_DLL + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } +#endif Activity GetPrimaryAttackActivity( void ); virtual bool Reload( void ); virtual const Vector& GetBulletSpread( void ) { + // Handle NPCs first + static Vector npcCone = VECTOR_CONE_5DEGREES; + if ( GetOwner() && GetOwner()->IsNPC() ) + return npcCone; + static Vector cone; float ramp = RemapValClamped( m_flAccuracyPenalty, @@ -84,9 +98,17 @@ class CWeaponPistol : public CBaseHL2MPCombatWeapon { return 0.5f; } + +#ifdef MAPBASE + // Pistols are their own backup activities + virtual acttable_t *GetBackupActivityList() { return NULL; } + virtual int GetBackupActivityListCount() { return 0; } +#endif + + DECLARE_ACTTABLE(); #ifndef CLIENT_DLL - DECLARE_ACTTABLE(); + DECLARE_DATADESC(); #endif private: @@ -128,16 +150,149 @@ LINK_ENTITY_TO_CLASS( weapon_pistol, CWeaponPistol ); PRECACHE_WEAPON_REGISTER( weapon_pistol ); #ifndef CLIENT_DLL -acttable_t CWeaponPistol::m_acttable[] = +BEGIN_DATADESC( CWeaponPistol ) + + DEFINE_FIELD( m_flSoonestPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ), + DEFINE_FIELD( m_flAccuracyPenalty, FIELD_FLOAT ), //NOTENOTE: This is NOT tracking game time + DEFINE_FIELD( m_nNumShotsFired, FIELD_INTEGER ), + +END_DATADESC() +#endif + +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 }, + { ACT_IDLE, ACT_IDLE_PISTOL, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_PISTOL, true }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, true }, + { ACT_RELOAD, ACT_RELOAD_PISTOL, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_PISTOL, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_PISTOL, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_PISTOL,true }, + { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_PISTOL_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_PISTOL_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, false }, + { ACT_WALK, ACT_WALK_PISTOL, false }, + { ACT_RUN, ACT_RUN_PISTOL, false }, + +#ifdef MAPBASE + // + // Activities ported from weapon_alyxgun below + // + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_PISTOL_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities +#else + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + + { ACT_RUN_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities +#endif + + // Crouch activities + { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, + { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + + // Readiness translations + { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, + { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, + { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, + { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_PISTOL, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_PISTOL, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_PISTOL, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_PISTOL, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_PISTOL_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_PISTOL_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_PISTOL, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_PISTOL, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_PISTOL, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_PISTOL, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, false }, +#endif +#endif }; @@ -156,8 +311,6 @@ int GetPistolActtableCount() } #endif -#endif - //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- @@ -182,6 +335,79 @@ void CWeaponPistol::Precache( void ) BaseClass::Precache(); } +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeaponPistol::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_PISTOL_FIRE: + { + Vector vecShootOrigin, vecShootDir; + vecShootOrigin = pOperator->Weapon_ShootPosition(); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + Assert( npc != NULL ); + + vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); + +#ifdef MAPBASE + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +#else + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + WeaponSound( SINGLE_NPC ); + pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 ); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; +#endif + } + break; + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + WeaponSound( SINGLE_NPC ); + + FireBulletsInfo_t info( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_iTracerFreq = 2; + + pOperator->FireBullets( info ); + + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Some things need this. (e.g. the new Force(X)Fire inputs or blindfire actbusy) +//----------------------------------------------------------------------------- +void CWeaponPistol::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +} +#endif +#endif //----------------------------------------------------------------------------- // Purpose: @@ -211,6 +437,9 @@ void CWeaponPistol::PrimaryAttack( void ) m_flLastAttackTime = gpGlobals->curtime; m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME; +#ifndef CLIENT_DLL + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); +#endif CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); @@ -331,6 +560,10 @@ bool CWeaponPistol::Reload( void ) return fRet; } +#ifdef MAPBASE +ConVar weapon_pistol_upwards_viewkick( "weapon_pistol_upwards_viewkick", "0" ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -343,7 +576,13 @@ void CWeaponPistol::AddViewKick( void ) QAngle viewPunch; - viewPunch.x = SharedRandomFloat( "pistolpax", 0.25f, 0.5f ); +#ifdef MAPBASE + bool bUpwardsViewKick = weapon_pistol_upwards_viewkick.GetBool(); +#else + bool bUpwardsViewKick = false; +#endif + + viewPunch.x = bUpwardsViewKick ? SharedRandomFloat( "pistolpax", -0.5f, -0.25f ) : SharedRandomFloat( "pistolpax", 0.25f, 0.5f ); viewPunch.y = SharedRandomFloat( "pistolpay", -.6f, .6f ); viewPunch.z = 0.0f; diff --git a/src/game/shared/hl2mp/weapon_rpg.cpp b/src/game/shared/hl2mp/weapon_rpg.cpp index 1859958e3ca..29410bd5d3d 100644 --- a/src/game/shared/hl2mp/weapon_rpg.cpp +++ b/src/game/shared/hl2mp/weapon_rpg.cpp @@ -8,6 +8,7 @@ #include "npcevent.h" #include "in_buttons.h" #include "weapon_rpg.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" @@ -45,6 +46,12 @@ const char *g_pLaserDotThink = "LaserThinkContext"; static ConVar sk_apc_missile_damage("sk_apc_missile_damage", "15"); +ConVar rpg_missle_use_custom_detonators( "rpg_missle_use_custom_detonators", "1" ); +#ifdef MAPBASE +ConVar weapon_rpg_use_old_behavior( "weapon_rpg_use_old_behavior", "0" ); +ConVar weapon_rpg_fire_rate( "weapon_rpg_fire_rate", "4.0" ); +#endif + #define APC_MISSILE_DAMAGE sk_apc_missile_damage.GetFloat() #endif @@ -127,6 +134,7 @@ BEGIN_DATADESC( CMissile ) DEFINE_FIELD( m_flMarkDeadTime, FIELD_TIME ), DEFINE_FIELD( m_flGracePeriodEndsAt, FIELD_TIME ), DEFINE_FIELD( m_flDamage, FIELD_FLOAT ), + DEFINE_FIELD( m_bCreateDangerSounds, FIELD_BOOLEAN ), // Function Pointers DEFINE_FUNCTION( MissileTouch ), @@ -147,6 +155,7 @@ class CWeaponRPG; CMissile::CMissile() { m_hRocketTrail = NULL; + m_bCreateDangerSounds = false; } CMissile::~CMissile() @@ -412,7 +421,11 @@ void CMissile::MissileTouch( CBaseEntity *pOther ) // Don't touch triggers (but DO hit weapons) if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) - return; + { + // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them. + if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) + return; + } Explode(); } @@ -471,8 +484,11 @@ void CMissile::IgniteThink( void ) { CBasePlayer *pPlayer = ToBasePlayer( m_hOwner->GetOwner() ); - color32 white = { 255,225,205,64 }; - UTIL_ScreenFade( pPlayer, white, 0.1f, 0.0f, FFADE_IN ); + if ( pPlayer ) + { + color32 white = { 255,225,205,64 }; + UTIL_ScreenFade( pPlayer, white, 0.1f, 0.0f, FFADE_IN ); + } } CreateSmokeTrail(); @@ -584,6 +600,50 @@ void CMissile::SeekThink( void ) } } + if( hl2_episodic.GetBool() ) + { + if( flBestDist <= ( GetAbsVelocity().Length() * 2.5f ) && FVisible( pBestDot->GetAbsOrigin() ) ) + { + // Scare targets + CSoundEnt::InsertSound( SOUND_DANGER, pBestDot->GetAbsOrigin(), CMissile::EXPLOSION_RADIUS, 0.2f, pBestDot, SOUNDENT_CHANNEL_REPEATED_DANGER, NULL ); + } + } + + if ( rpg_missle_use_custom_detonators.GetBool() ) + { + for ( int i = gm_CustomDetonators.Count() - 1; i >=0; --i ) + { + CustomDetonator_t &detonator = gm_CustomDetonators[i]; + if ( !detonator.hEntity ) + { + gm_CustomDetonators.FastRemove( i ); + } + else + { + const Vector &vPos = detonator.hEntity->CollisionProp()->WorldSpaceCenter(); + if ( detonator.halfHeight > 0 ) + { + if ( fabsf( vPos.z - GetAbsOrigin().z ) < detonator.halfHeight ) + { + if ( ( GetAbsOrigin().AsVector2D() - vPos.AsVector2D() ).LengthSqr() < detonator.radiusSq ) + { + Explode(); + return; + } + } + } + else + { + if ( ( GetAbsOrigin() - vPos ).LengthSqr() < detonator.radiusSq ) + { + Explode(); + return; + } + } + } + } + } + //If we have a dot target if ( pBestDot == NULL ) { @@ -606,6 +666,16 @@ void CMissile::SeekThink( void ) VectorSubtract( targetPos, GetAbsOrigin(), vTargetDir ); float flDist = VectorNormalize( vTargetDir ); + if( pLaserDot->GetTargetEntity() != NULL && flDist <= 240.0f && hl2_episodic.GetBool() ) + { + // Prevent the missile circling the Strider like a Halo in ep1_c17_06. If the missile gets within 20 + // feet of a Strider, tighten up the turn speed of the missile so it can break the halo and strike. (sjb 4/27/2006) + if( pLaserDot->GetTargetEntity()->ClassMatches( "npc_strider" ) ) + { + flHomingSpeed *= 1.75f; + } + } + Vector vDir = GetAbsVelocity(); float flSpeed = VectorNormalize( vDir ); Vector vNewVelocity = vDir; @@ -643,6 +713,17 @@ void CMissile::SeekThink( void ) // Think as soon as possible SetNextThink( gpGlobals->curtime ); + +#ifdef HL2_EPISODIC + + if ( m_bCreateDangerSounds == true ) + { + trace_t tr; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); + + CSoundEnt::InsertSound( SOUND_DANGER, tr.endpos, 100, 0.2, this, SOUNDENT_CHANNEL_REPEATED_DANGER ); + } +#endif } @@ -672,6 +753,33 @@ CMissile *CMissile::Create( const Vector &vecOrigin, const QAngle &vecAngles, ed } +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CUtlVector CMissile::gm_CustomDetonators; + +void CMissile::AddCustomDetonator( CBaseEntity *pEntity, float radius, float height ) +{ + int i = gm_CustomDetonators.AddToTail(); + gm_CustomDetonators[i].hEntity = pEntity; + gm_CustomDetonators[i].radiusSq = Square( radius ); + gm_CustomDetonators[i].halfHeight = height * 0.5f; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMissile::RemoveCustomDetonator( CBaseEntity *pEntity ) +{ + for ( int i = 0; i < gm_CustomDetonators.Count(); i++ ) + { + if ( gm_CustomDetonators[i].hEntity == pEntity ) + { + gm_CustomDetonators.FastRemove( i ); + break; + } + } +} + //----------------------------------------------------------------------------- // This entity is used to create little force boxes that the helicopter @@ -868,6 +976,7 @@ BEGIN_DATADESC( CAPCMissile ) DEFINE_THINKFUNC( BeginSeekThink ), DEFINE_THINKFUNC( AugerStartThink ), DEFINE_THINKFUNC( ExplodeThink ), + DEFINE_THINKFUNC( APCSeekThink ), DEFINE_FUNCTION( APCMissileTouch ), @@ -912,6 +1021,7 @@ void CAPCMissile::Init() CreateSmokeTrail(); SetTouch( &CAPCMissile::APCMissileTouch ); m_flLastHomingSpeed = APC_HOMING_SPEED; + CreateDangerSounds( true ); } @@ -984,10 +1094,43 @@ void CAPCMissile::ExplodeDelay( float flDelay ) void CAPCMissile::BeginSeekThink( void ) { RemoveSolidFlags( FSOLID_NOT_SOLID ); +#ifdef HL2MP SetThink( &CAPCMissile::SeekThink ); +#else + SetThink( &CAPCMissile::APCSeekThink ); +#endif SetNextThink( gpGlobals->curtime ); } +void CAPCMissile::APCSeekThink( void ) +{ + BaseClass::SeekThink(); + + bool bFoundDot = false; + + //If we can't find a dot to follow around then just send me wherever I'm facing so I can blow up in peace. + for( CLaserDot *pEnt = GetLaserDotList(); pEnt != NULL; pEnt = pEnt->m_pNext ) + { + if ( !pEnt->IsOn() ) + continue; + + if ( pEnt->GetOwnerEntity() != GetOwnerEntity() ) + continue; + + bFoundDot = true; + } + + if ( bFoundDot == false ) + { + Vector vDir = GetAbsVelocity(); + VectorNormalize ( vDir ); + + SetAbsVelocity( vDir * 800 ); + + SetThink( NULL ); + } +} + void CAPCMissile::ExplodeThink() { DoExplosion(); @@ -1036,8 +1179,11 @@ void CAPCMissile::DoExplosion( void ) } else { - ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), - APC_MISSILE_DAMAGE, 100, true, 20000 ); +#ifdef HL2_EPISODIC + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), this, APC_MISSILE_DAMAGE, 100, true, 20000 ); +#else + ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), APC_MISSILE_DAMAGE, 100, true, 20000 ); +#endif } } @@ -1254,6 +1400,19 @@ void CAPCMissile::ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActua // RPG //============================================================================= +#ifndef CLIENT_DLL +BEGIN_DATADESC( CWeaponRPG ) + + DEFINE_FIELD( m_bInitialStateUpdate,FIELD_BOOLEAN ), + DEFINE_FIELD( m_bGuiding, FIELD_BOOLEAN ), + DEFINE_FIELD( m_vecNPCLaserDot, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ), + DEFINE_FIELD( m_hMissile, FIELD_EHANDLE ), + DEFINE_FIELD( m_bHideGuiding, FIELD_BOOLEAN ), + +END_DATADESC() +#endif + LINK_ENTITY_TO_CLASS( weapon_rpg, CWeaponRPG ); PRECACHE_WEAPON_REGISTER(weapon_rpg); @@ -1305,22 +1464,61 @@ 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 }, -}; + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_RPG, true }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_RPG_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_RPG_LOW, false }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_RPG, false }, +#endif -IMPLEMENT_ACTTABLE(CWeaponRPG); +#ifdef MAPBASE + // Readiness activities should not be required + { ACT_IDLE_RELAXED, ACT_IDLE_RPG_RELAXED, false }, + { ACT_IDLE_STIMULATED, ACT_IDLE_ANGRY_RPG, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_RPG, false }, +#else + { ACT_IDLE_RELAXED, ACT_IDLE_RPG_RELAXED, true }, + { ACT_IDLE_STIMULATED, ACT_IDLE_ANGRY_RPG, true }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_RPG, true }, +#endif + + { ACT_IDLE, ACT_IDLE_RPG, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_RPG, true }, + { ACT_WALK, ACT_WALK_RPG, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RPG, true }, + { ACT_RUN, ACT_RUN_RPG, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RPG, true }, + { ACT_COVER_LOW, ACT_COVER_LOW_RPG, true }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RPG, false }, + { ACT_DISARM, ACT_DISARM_RPG, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_RPG_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_RPG_MED, false }, +#endif +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_RPG, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG, false }, +#endif #endif +}; + +IMPLEMENT_ACTTABLE(CWeaponRPG); //----------------------------------------------------------------------------- // Purpose: @@ -1394,6 +1592,128 @@ void CWeaponRPG::Activate( void ) } } +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CWeaponRPG::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_SMG1: + { + if ( m_hMissile != NULL ) + return; + + Vector muzzlePoint; + QAngle vecAngles; + + muzzlePoint = GetOwner()->Weapon_ShootPosition(); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + Assert( npc != NULL ); + + Vector vecShootDir = npc->GetActualShootTrajectory( muzzlePoint ); + + // look for a better launch location + Vector altLaunchPoint; + if (GetAttachment( "missile", altLaunchPoint )) + { + // check to see if it's relativly free + trace_t tr; + AI_TraceHull( altLaunchPoint, altLaunchPoint + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction == 1.0) + { + muzzlePoint = altLaunchPoint; + } + } + + VectorAngles( vecShootDir, vecAngles ); + + CMissile *pMissile = CMissile::Create( muzzlePoint, vecAngles, GetOwner()->edict() ); + pMissile->m_hOwner = this; + + // NPCs always get a grace period + pMissile->SetGracePeriod( 0.5 ); + + m_hMissile = pMissile; + + pOperator->DoMuzzleFlash(); + + WeaponSound( SINGLE_NPC ); + + // Make sure our laserdot is off + m_bGuiding = false; + +#ifndef CLIENT_DLL + if ( m_hLaserDot ) + { + m_hLaserDot->TurnOff(); + } +#endif + } + break; + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + if ( m_hMissile != NULL ) + return; + + Vector muzzlePoint, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), muzzlePoint, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + + // look for a better launch location + Vector altLaunchPoint; + if (GetAttachment( "missile", altLaunchPoint )) + { + // check to see if it's relativly free + trace_t tr; + AI_TraceHull( altLaunchPoint, altLaunchPoint + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction == 1.0) + { + muzzlePoint = altLaunchPoint; + } + } + + CMissile *pMissile = CMissile::Create( muzzlePoint, angShootDir, pOperator->edict() ); + pMissile->m_hOwner = this; + + // NPCs always get a grace period + pMissile->SetGracePeriod( 0.5 ); + + m_hMissile = pMissile; + + pOperator->DoMuzzleFlash(); + + WeaponSound( SINGLE_NPC ); + + // Make sure our laserdot is off + m_bGuiding = false; + + if ( m_hLaserDot ) + { + m_hLaserDot->TurnOff(); + } +} +#endif +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1423,12 +1743,6 @@ bool CWeaponRPG::WeaponShouldBeLowered( void ) //----------------------------------------------------------------------------- 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; @@ -1472,14 +1786,53 @@ void CWeaponRPG::PrimaryAttack( void ) pMissile->SetDamage( GetHL2MPWpnData().m_iPlayerDamage ); m_hMissile = pMissile; + + // Register a muzzleflash for the AI + pOwner->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); #endif DecrementAmmo( GetOwner() ); + SendWeaponAnim( ACT_VM_PRIMARYATTACK ); WeaponSound( SINGLE ); // player "shoot" animation - pPlayer->SetAnimation( PLAYER_ATTACK1 ); + pOwner->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); + + // Check to see if we should trigger any RPG firing triggers + int iCount = g_hWeaponFireTriggers.Count(); + for ( int i = 0; i < iCount; i++ ) + { + if ( g_hWeaponFireTriggers[i]->IsTouching( pOwner ) ) + { + if ( FClassnameIs( g_hWeaponFireTriggers[i], "trigger_rpgfire" ) ) + { + g_hWeaponFireTriggers[i]->ActivateMultiTrigger( pOwner ); + } + } + } +#endif + +#ifdef HL2_EPISODIC + if( hl2_episodic.GetBool() ) + { + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + int nAIs = g_AI_Manager.NumAIs(); + + string_t iszStriderClassname = AllocPooledString( "npc_strider" ); + + for ( int i = 0; i < nAIs; i++ ) + { + if( ppAIs[ i ]->m_iClassname == iszStriderClassname ) + { + ppAIs[ i ]->DispatchInteraction( g_interactionPlayerLaunchedRPG, NULL, m_hMissile ); + } + } + } +#endif } //----------------------------------------------------------------------------- @@ -1573,6 +1926,7 @@ void CWeaponRPG::ItemPostFrame( void ) } } +#ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: // Output : Vector @@ -1597,6 +1951,18 @@ Vector CWeaponRPG::GetLaserPosition( void ) //----------------------------------------------------------------------------- void CWeaponRPG::UpdateNPCLaserPosition( const Vector &vecTarget ) { + CreateLaserPointer(); + // Turn the laserdot on + m_bGuiding = true; + m_hLaserDot->TurnOn(); + + Vector muzzlePoint = GetOwner()->Weapon_ShootPosition(); + Vector vecDir = (vecTarget - muzzlePoint); + VectorNormalize( vecDir ); + vecDir = muzzlePoint + ( vecDir * MAX_TRACE_LENGTH ); + UpdateLaserPosition( muzzlePoint, vecDir ); + + SetNPCLaserPosition( vecTarget ); } @@ -1605,6 +1971,8 @@ void CWeaponRPG::UpdateNPCLaserPosition( const Vector &vecTarget ) //----------------------------------------------------------------------------- void CWeaponRPG::SetNPCLaserPosition( const Vector &vecTarget ) { + m_vecNPCLaserDot = vecTarget; + //NDebugOverlay::Box( m_vecNPCLaserDot, -Vector(10,10,10), Vector(10,10,10), 255,0,0, 8, 3 ); } //----------------------------------------------------------------------------- @@ -1612,8 +1980,9 @@ void CWeaponRPG::SetNPCLaserPosition( const Vector &vecTarget ) //----------------------------------------------------------------------------- const Vector &CWeaponRPG::GetNPCLaserPosition( void ) { - return vec3_origin; + return m_vecNPCLaserDot; } +#endif //----------------------------------------------------------------------------- // Purpose: @@ -1752,14 +2121,14 @@ void CWeaponRPG::UpdateLaserPosition( Vector vecMuzzlePos, Vector vecEndPos ) trace_t tr; // Trace out for the endpoint - UTIL_TraceLine( vecMuzzlePos, vecEndPos, (MASK_SHOT & ~CONTENTS_WINDOW), GetOwner(), COLLISION_GROUP_NONE, &tr ); + UTIL_TraceLine( vecMuzzlePos, vecEndPos, (MASK_SHOT & ~CONTENTS_WINDOW), this, 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; @@ -1838,7 +2207,131 @@ bool CWeaponRPG::Reload( void ) return true; } -#ifdef CLIENT_DLL +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CWeaponRPG::WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) +{ + bool bResult = BaseClass::WeaponLOSCondition( ownerPos, targetPos, bSetConditions ); + + if( bResult ) + { + CAI_BaseNPC* npcOwner = GetOwner()->MyNPCPointer(); + + if( npcOwner ) + { + trace_t tr; + + Vector vecRelativeShootPosition; + VectorSubtract( npcOwner->Weapon_ShootPosition(), npcOwner->GetAbsOrigin(), vecRelativeShootPosition ); + Vector vecMuzzle = ownerPos + vecRelativeShootPosition; + Vector vecShootDir = npcOwner->GetActualShootTrajectory( vecMuzzle ); + + // Make sure I have a good 10 feet of wide clearance in front, or I'll blow my teeth out. +#ifdef MAPBASE + // Oh, and don't collide with ourselves or our owner. That would be stupid. + if (!weapon_rpg_use_old_behavior.GetBool()) + { + CTraceFilterSkipTwoEntities pTraceFilter( this, GetOwner(), COLLISION_GROUP_NONE ); + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, &pTraceFilter, &tr ); + } + else + { +#endif + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); +#ifdef MAPBASE + } +#endif + + if( tr.fraction != 1.0f ) + bResult = false; + } + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flDot - +// flDist - +// Output : int +//----------------------------------------------------------------------------- +int CWeaponRPG::WeaponRangeAttack1Condition( float flDot, float flDist ) +{ + if ( m_hMissile != NULL ) + return 0; + + // Ignore vertical distance when doing our RPG distance calculations + CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer(); + if ( pNPC ) + { + CBaseEntity *pEnemy = pNPC->GetEnemy(); + Vector vecToTarget = (pEnemy->GetAbsOrigin() - pNPC->GetAbsOrigin()); + vecToTarget.z = 0; + flDist = vecToTarget.Length(); + } + + if ( flDist < MIN( m_fMinRange1, m_fMinRange2 ) ) + return COND_TOO_CLOSE_TO_ATTACK; + + if ( m_flNextPrimaryAttack > gpGlobals->curtime ) + return 0; + + // See if there's anyone in the way! + CAI_BaseNPC *pOwner = GetOwner()->MyNPCPointer(); + ASSERT( pOwner != NULL ); + + if( pOwner ) + { + // Make sure I don't shoot the world! + trace_t tr; + + Vector vecMuzzle = pOwner->Weapon_ShootPosition(); + Vector vecShootDir = pOwner->GetActualShootTrajectory( vecMuzzle ); + + // Make sure I have a good 10 feet of wide clearance in front, or I'll blow my teeth out. +#ifdef MAPBASE + // Oh, and don't collide with ourselves or our owner. That would be stupid. + if (!weapon_rpg_use_old_behavior.GetBool()) + { + CTraceFilterSkipTwoEntities pTraceFilter( this, GetOwner(), COLLISION_GROUP_NONE ); + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, &pTraceFilter, &tr ); + } + else + { +#endif + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); +#ifdef MAPBASE + } +#endif + + if( tr.fraction != 1.0 ) + { + return COND_WEAPON_SIGHT_OCCLUDED; + } + } + + return COND_CAN_RANGE_ATTACK1; +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::SupportsBackupActivity(Activity activity) +{ + // NPCs shouldn't use their SMG activities to aim and fire RPGs while running. + if (activity == ACT_RUN_AIM || + activity == ACT_WALK_AIM || + activity == ACT_RUN_CROUCH_AIM || + activity == ACT_WALK_CROUCH_AIM) + return false; + + return true; +} +#endif +#else #define RPG_MUZZLE_ATTACHMENT 1 #define RPG_GUIDE_ATTACHMENT 2 diff --git a/src/game/shared/hl2mp/weapon_rpg.h b/src/game/shared/hl2mp/weapon_rpg.h index bbd6f2dd243..2af0ba08012 100644 --- a/src/game/shared/hl2mp/weapon_rpg.h +++ b/src/game/shared/hl2mp/weapon_rpg.h @@ -37,6 +37,8 @@ class CMissile : public CBaseCombatCharacter DECLARE_CLASS( CMissile, CBaseCombatCharacter ); public: + static const int EXPLOSION_RADIUS = 200; + CMissile(); ~CMissile(); @@ -70,6 +72,11 @@ class CMissile : public CBaseCombatCharacter static CMissile *Create( const Vector &vecOrigin, const QAngle &vecAngles, edict_t *pentOwner ); + void CreateDangerSounds( bool bState ){ m_bCreateDangerSounds = bState; } + + static void AddCustomDetonator( CBaseEntity *pEntity, float radius, float height = -1 ); + static void RemoveCustomDetonator( CBaseEntity *pEntity ); + protected: virtual void DoExplosion(); virtual void ComputeActualDotPosition( CLaserDot *pLaserDot, Vector *pActualDotPosition, float *pHomingSpeed ); @@ -86,8 +93,18 @@ class CMissile : public CBaseCombatCharacter float m_flMarkDeadTime; float m_flDamage; + struct CustomDetonator_t + { + EHANDLE hEntity; + float radiusSq; + float halfHeight; + }; + + static CUtlVector gm_CustomDetonators; + private: float m_flGracePeriodEndsAt; + bool m_bCreateDangerSounds; DECLARE_DATADESC(); }; @@ -125,6 +142,8 @@ class CAPCMissile : public CMissile void AimAtSpecificTarget( CBaseEntity *pTarget ); void SetGuidanceHint( const char *pHintName ); + void APCSeekThink( void ); + CAPCMissile *m_pNext; protected: @@ -154,6 +173,9 @@ class CAPCMissile : public CMissile //----------------------------------------------------------------------------- CAPCMissile *FindAPCMissileInCone( const Vector &vecOrigin, const Vector &vecDirection, float flAngle ); +#ifdef MAPBASE +extern ConVar weapon_rpg_fire_rate; +#endif #endif //----------------------------------------------------------------------------- @@ -164,9 +186,9 @@ CAPCMissile *FindAPCMissileInCone( const Vector &vecOrigin, const Vector &vecDir #define CWeaponRPG C_WeaponRPG #endif -class CWeaponRPG : public CBaseHL2MPCombatWeapon +class CWeaponRPG : public CBaseHLCombatWeapon { - DECLARE_CLASS( CWeaponRPG, CBaseHL2MPCombatWeapon ); + DECLARE_CLASS( CWeaponRPG, CBaseHLCombatWeapon ); public: CWeaponRPG(); @@ -178,7 +200,11 @@ class CWeaponRPG : public CBaseHL2MPCombatWeapon void Precache( void ); void PrimaryAttack( void ); +#if defined(MAPBASE) && !defined(CLIENT_DLL) + virtual float GetFireRate( void ) { return weapon_rpg_fire_rate.GetFloat(); }; +#else virtual float GetFireRate( void ) { return 1; }; +#endif void ItemPostFrame( void ); void Activate( void ); @@ -199,6 +225,15 @@ class CWeaponRPG : public CBaseHL2MPCombatWeapon float GetMinRestTime() { return 4.0; } float GetMaxRestTime() { return 4.0; } + bool WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ); + int WeaponRangeAttack1Condition( float flDot, float flDist ); + +#ifndef CLIENT_DLL + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); +#endif +#endif void StartGuiding( void ); void StopGuiding( void ); void ToggleGuiding( void ); @@ -214,10 +249,19 @@ class CWeaponRPG : public CBaseHL2MPCombatWeapon void UpdateLaserPosition( Vector vecMuzzlePos = vec3_origin, Vector vecEndPos = vec3_origin ); Vector GetLaserPosition( void ); +#ifndef CLIENT_DLL + // 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 ); + + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + +#ifdef MAPBASE + bool SupportsBackupActivity( Activity activity ); +#endif +#endif #ifdef CLIENT_DLL @@ -240,10 +284,17 @@ class CWeaponRPG : public CBaseHL2MPCombatWeapon #endif //CLIENT_DLL + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone = VECTOR_CONE_3DEGREES; + return cone; + } + CBaseEntity *GetMissile( void ) { return m_hMissile; } -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); +#ifndef CLIENT_DLL + DECLARE_DATADESC(); #endif protected: @@ -256,6 +307,7 @@ class CWeaponRPG : public CBaseHL2MPCombatWeapon CNetworkVar( Vector, m_vecLaserDot ); #ifndef CLIENT_DLL + Vector m_vecNPCLaserDot; CHandle m_hLaserDot; #endif diff --git a/src/game/shared/hl2mp/weapon_shotgun.cpp b/src/game/shared/hl2mp/weapon_shotgun.cpp index b2f62fb018b..798e037cbb0 100644 --- a/src/game/shared/hl2mp/weapon_shotgun.cpp +++ b/src/game/shared/hl2mp/weapon_shotgun.cpp @@ -7,6 +7,7 @@ #include "cbase.h" #include "npcevent.h" #include "in_buttons.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" @@ -22,6 +23,10 @@ extern ConVar sk_auto_reload_time; extern ConVar sk_plr_num_shotgun_pellets; +#ifdef MAPBASE +extern ConVar sk_plr_num_shotgun_pellets_double; +extern ConVar sk_npc_num_shotgun_pellets; +#endif class CWeaponShotgun : public CBaseHL2MPCombatWeapon { @@ -38,15 +43,35 @@ class CWeaponShotgun : public CBaseHL2MPCombatWeapon CNetworkVar( bool, m_bDelayedReload ); // Reload when finished pump public: +#ifndef CLIENT_DLL + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } +#endif + virtual const Vector& GetBulletSpread( void ) { static Vector cone = VECTOR_CONE_10DEGREES; + +#ifndef CLIENT_DLL + static Vector vitalAllyCone = VECTOR_CONE_3DEGREES; + if( GetOwner() && (GetOwner()->Classify() == CLASS_PLAYER_ALLY_VITAL) ) + { + // Give Alyx's shotgun blasts more a more directed punch. She needs + // to be at least as deadly as she would be with her pistol to stay interesting (sjb) + return vitalAllyCone; + } +#endif + return cone; } virtual int GetMinBurst() { return 1; } virtual int GetMaxBurst() { return 3; } + virtual float GetMinRestTime(); + virtual float GetMaxRestTime(); + + virtual float GetFireRate( void ); + bool StartReload( void ); bool Reload( void ); void FillClip( void ); @@ -59,10 +84,16 @@ class CWeaponShotgun : public CBaseHL2MPCombatWeapon void PrimaryAttack( void ); void SecondaryAttack( void ); void DryFire( void ); - virtual float GetFireRate( void ) { return 0.7; }; #ifndef CLIENT_DLL + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#endif + DECLARE_ACTTABLE(); +#ifndef CLIENT_DLL + DECLARE_DATADESC(); #endif CWeaponShotgun(void); @@ -100,16 +131,140 @@ LINK_ENTITY_TO_CLASS( weapon_shotgun, CWeaponShotgun ); PRECACHE_WEAPON_REGISTER(weapon_shotgun); #ifndef CLIENT_DLL +BEGIN_DATADESC( CWeaponShotgun ) + + DEFINE_FIELD( m_bNeedPump, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bDelayedFire1, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bDelayedFire2, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bDelayedReload, FIELD_BOOLEAN ), + +END_DATADESC() +#endif + 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 }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Note that ACT_IDLE_SHOTGUN_AGITATED seems to be a stand-in for ACT_IDLE_SHOTGUN on citizens, + // but that isn't acceptable for NPCs which don't use readiness activities. + { ACT_IDLE, ACT_IDLE_SHOTGUN, true }, + + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RELOAD, ACT_RELOAD_SHOTGUN, false }, + { ACT_WALK, ACT_WALK_SHOTGUN, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SHOTGUN, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SHOTGUN, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SHOTGUN_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SHOTGUN_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SHOTGUN, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SHOTGUN_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SHOTGUN_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SHOTGUN, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SHOTGUN, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SHOTGUN_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SHOTGUN_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SHOTGUN, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SHOTGUN_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SHOTGUN_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SHOTGUN, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_SHOTGUN, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SHOTGUN, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SHOTGUN, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SHOTGUN_LOW, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_SHOTGUN_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, + { ACT_COVER_LOW, ACT_COVER_SHOTGUN_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SHOTGUN_LOW, false }, +#else + { ACT_IDLE, ACT_IDLE_SMG1, true }, // FIXME: hook to shotgun unique + + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RELOAD, ACT_RELOAD_SHOTGUN, false }, + { ACT_WALK, ACT_WALK_RIFLE, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SHOTGUN, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_SHOTGUN_AGITATED, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_SHOTGUN, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SHOTGUN, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SHOTGUN_LOW, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_SHOTGUN_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_SHOTGUN, true }, + { ACT_DISARM, ACT_DISARM_SHOTGUN, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SHOTGUN_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SHOTGUN_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponShotgun); @@ -127,8 +282,129 @@ int GetShotgunActtableCount() } #endif +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOperator - +//----------------------------------------------------------------------------- +void CWeaponShotgun::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ) +{ + Vector vecShootOrigin, vecShootDir; + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + Assert( npc != NULL ); + WeaponSound( SINGLE_NPC ); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; + + if ( bUseWeaponAngles ) + { + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + } + else + { + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); + } + +#ifdef MAPBASE + FireBulletsInfo_t info( sk_npc_num_shotgun_pellets.GetInt(), vecShootOrigin, vecShootDir, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_iTracerFreq = 0; + + pOperator->FireBullets( info ); +#else + pOperator->FireBullets( 8, vecShootOrigin, vecShootDir, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0 ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponShotgun::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + FireNPCPrimaryAttack( pOperator, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CWeaponShotgun::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_SHOTGUN_FIRE: + { + FireNPCPrimaryAttack( pOperator, false ); + } + break; + + default: + CBaseCombatWeapon::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: When we shipped HL2, the shotgun weapon did not override the +// BaseCombatWeapon default rest time of 0.3 to 0.6 seconds. When +// NPC's fight from a stationary position, their animation events +// govern when they fire so the rate of fire is specified by the +// animation. When NPC's move-and-shoot, the rate of fire is +// specifically controlled by the shot regulator, so it's imporant +// that GetMinRestTime and GetMaxRestTime are implemented and provide +// reasonable defaults for the weapon. To address difficulty concerns, +// we are going to fix the combine's rate of shotgun fire in episodic. +// This change will not affect Alyx using a shotgun in EP1. (sjb) +//----------------------------------------------------------------------------- +float CWeaponShotgun::GetMinRestTime() +{ +#ifndef CLIENT_DLL + if( hl2_episodic.GetBool() && GetOwner() && GetOwner()->Classify() == CLASS_COMBINE ) + { + return 1.2f; + } +#endif + + return BaseClass::GetMinRestTime(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CWeaponShotgun::GetMaxRestTime() +{ +#ifndef CLIENT_DLL + if( hl2_episodic.GetBool() && GetOwner() && GetOwner()->Classify() == CLASS_COMBINE ) + { + return 1.5f; + } +#endif + + return BaseClass::GetMaxRestTime(); +} + +//----------------------------------------------------------------------------- +// Purpose: Time between successive shots in a burst. Also returned for EP2 +// with an eye to not messing up Alyx in EP1. +//----------------------------------------------------------------------------- +float CWeaponShotgun::GetFireRate() +{ +#ifndef CLIENT_DLL + if( hl2_episodic.GetBool() && GetOwner() && GetOwner()->Classify() == CLASS_COMBINE ) + { + return 0.8f; + } #endif + return 0.7; +} //----------------------------------------------------------------------------- // Purpose: Override so only reload one shell at a time @@ -165,6 +441,13 @@ bool CWeaponShotgun::StartReload( void ) pOwner->m_flNextAttack = gpGlobals->curtime; m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#ifdef MAPBASE + if ( pOwner->IsPlayer() ) + { + static_cast(pOwner)->SetAnimation( PLAYER_RELOAD ); + } +#endif + m_bInReload = true; return true; } @@ -321,7 +604,11 @@ void CWeaponShotgun::PrimaryAttack( void ) SendWeaponAnim( ACT_VM_PRIMARYATTACK ); // Don't fire again until fire animation has completed +#ifdef MAPBASE + m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); +#else m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#endif m_iClip1 -= 1; // player "shoot" animation @@ -330,11 +617,19 @@ void CWeaponShotgun::PrimaryAttack( void ) Vector vecSrc = pPlayer->Weapon_ShootPosition( ); Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); +#ifndef CLIENT_DLL + pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 1.0 ); +#endif + +#ifdef HL2MP // TODO: Integrate sk_plr_num_shotgun_pellets 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 ); +#else + pPlayer->FireBullets( sk_plr_num_shotgun_pellets.GetInt(), vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, true, true ); +#endif QAngle punch; punch.Init( SharedRandomFloat( "shotgunpax", -2, -1 ), SharedRandomFloat( "shotgunpay", -2, 2 ), 0 ); @@ -373,11 +668,21 @@ void CWeaponShotgun::SecondaryAttack( void ) SendWeaponAnim( ACT_VM_SECONDARYATTACK ); // Don't fire again until fire animation has completed +#ifdef MAPBASE + m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); +#else m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#endif m_iClip1 -= 2; // Shotgun uses same clip for primary and secondary attacks // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); diff --git a/src/game/shared/hl2mp/weapon_slam.cpp b/src/game/shared/hl2mp/weapon_slam.cpp index 2499b51a30d..f16a196c5f7 100644 --- a/src/game/shared/hl2mp/weapon_slam.cpp +++ b/src/game/shared/hl2mp/weapon_slam.cpp @@ -104,8 +104,9 @@ BEGIN_DATADESC( CWeapon_SLAM ) DEFINE_FUNCTION( SlamTouch ), END_DATADESC() +#endif -acttable_t CWeapon_SLAM::m_acttable[] = +acttable_t CWeapon_SLAM::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SLAM, false }, @@ -121,8 +122,7 @@ acttable_t CWeapon_SLAM::m_acttable[] = #endif }; -IMPLEMENT_ACTTABLE(CWeapon_SLAM); -#endif +IMPLEMENT_ACTTABLE( CWeapon_SLAM ); void CWeapon_SLAM::Spawn( ) @@ -495,12 +495,12 @@ void CWeapon_SLAM::StartTripmineAttach( void ) //----------------------------------------------------------------------------- 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() ); +#ifndef CLIENT_DLL Vector vecSrc = pPlayer->WorldSpaceCenter(); Vector vecFacing = pPlayer->BodyDirection3D( ); vecSrc = vecSrc + vecFacing * 18.0; @@ -529,12 +529,11 @@ void CWeapon_SLAM::SatchelThrow( void ) pSatchel->m_bIsLive = true; pSatchel->m_pMyWeaponSLAM = this; } +#endif pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); pPlayer->SetAnimation( PLAYER_ATTACK1 ); -#endif - // Play throw sound EmitSound( "Weapon_SLAM.SatchelThrow" ); } @@ -577,7 +576,6 @@ void CWeapon_SLAM::StartSatchelThrow( void ) //----------------------------------------------------------------------------- void CWeapon_SLAM::SatchelAttach( void ) { -#ifndef CLIENT_DLL CBaseCombatCharacter *pOwner = GetOwner(); if (!pOwner) { @@ -585,7 +583,8 @@ void CWeapon_SLAM::SatchelAttach( void ) } m_bAttachSatchel = false; - + +#ifndef CLIENT_DLL Vector vecSrc = pOwner->Weapon_ShootPosition( ); Vector vecAiming = pOwner->BodyDirection2D( ); @@ -625,15 +624,24 @@ void CWeapon_SLAM::SatchelAttach( void ) //----------------------------------------------------------------------------- void CWeapon_SLAM::StartSatchelAttach( void ) { -#ifndef CLIENT_DLL +#if !defined(CLIENT_DLL) || defined(MAPBASE_MP) CBaseCombatCharacter *pOwner = GetOwner(); if (!pOwner) { return; } +#ifdef CLIENT_DLL + Vector vecAiming; + + // Must compromise due to no body direction or Weapon_ShootPosition() on client + QAngle angEyeAngles = pOwner->EyeAngles(); + AngleVectors( angEyeAngles, &vecAiming ); + Vector vecSrc = Vector( 0, 32, 0 ); +#else Vector vecSrc = pOwner->Weapon_ShootPosition( ); Vector vecAiming = pOwner->BodyDirection2D( ); +#endif trace_t tr; diff --git a/src/game/shared/hl2mp/weapon_slam.h b/src/game/shared/hl2mp/weapon_slam.h index bf7286122df..c7e52afbf95 100644 --- a/src/game/shared/hl2mp/weapon_slam.h +++ b/src/game/shared/hl2mp/weapon_slam.h @@ -87,8 +87,8 @@ class CWeapon_SLAM : public CBaseHL2MPCombatWeapon CWeapon_SLAM(); -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); +#ifndef CLIENT_DLL DECLARE_DATADESC(); #endif diff --git a/src/game/shared/hl2mp/weapon_smg1.cpp b/src/game/shared/hl2mp/weapon_smg1.cpp index 4d644496cca..7faf76f38a8 100644 --- a/src/game/shared/hl2mp/weapon_smg1.cpp +++ b/src/game/shared/hl2mp/weapon_smg1.cpp @@ -7,16 +7,20 @@ #include "cbase.h" #include "npcevent.h" #include "in_buttons.h" +#include "ai_basenpc_shared.h" #ifdef CLIENT_DLL #include "c_hl2mp_player.h" #else #include "grenade_ar2.h" #include "hl2mp_player.h" + #include "ai_memory.h" + #include "soundent.h" + #include "rumble_shared.h" + #include "gamestats.h" #include "basegrenade_shared.h" #endif -#include "weapon_hl2mpbase.h" #include "weapon_hl2mpbase_machinegun.h" #ifdef CLIENT_DLL @@ -26,7 +30,13 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +extern ConVar sk_npc_dmg_smg1_grenade; +extern ConVar sk_plr_dmg_smg1_grenade; +#define SMG1_GRENADE_DAMAGE sk_plr_dmg_smg1_grenade.GetFloat() +#else #define SMG1_GRENADE_DAMAGE 100.0f +#endif #define SMG1_GRENADE_RADIUS 250.0f class CWeaponSMG1 : public CHL2MPMachineGun @@ -51,6 +61,10 @@ class CWeaponSMG1 : public CHL2MPMachineGun bool Reload( void ); float GetFireRate( void ) { return 0.075f; } // 13.3hz +#ifndef CLIENT_DLL + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + int WeaponRangeAttack2Condition( float flDot, float flDist ); +#endif Activity GetPrimaryAttackActivity( void ); virtual const Vector& GetBulletSpread( void ) @@ -62,7 +76,14 @@ class CWeaponSMG1 : public CHL2MPMachineGun const WeaponProficiencyInfo_t *GetProficiencyValues(); #ifndef CLIENT_DLL + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#endif + DECLARE_ACTTABLE(); +#ifndef CLIENT_DLL + DECLARE_DATADESC(); #endif protected: @@ -86,16 +107,94 @@ LINK_ENTITY_TO_CLASS( weapon_smg1, CWeaponSMG1 ); PRECACHE_WEAPON_REGISTER(weapon_smg1); #ifndef CLIENT_DLL +BEGIN_DATADESC( CWeaponSMG1 ) + + DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), + +END_DATADESC() +#endif + 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 }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, + { ACT_RELOAD, ACT_RELOAD_SMG1, true }, + { ACT_IDLE, ACT_IDLE_SMG1, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, + + { ACT_WALK, ACT_WALK_RIFLE, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG1, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SMG1_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SMG1_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { 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 }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SMG1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSMG1); @@ -112,7 +211,6 @@ int GetSMG1ActtableCount() return ARRAYSIZE(CWeaponSMG1::m_acttable); } #endif -#endif //========================================================= CWeaponSMG1::CWeaponSMG1( ) @@ -138,11 +236,170 @@ void CWeaponSMG1::Precache( void ) //----------------------------------------------------------------------------- void CWeaponSMG1::Equip( CBaseCombatCharacter *pOwner ) { - m_fMaxRange1 = 1400; +#ifndef CLIENT_DLL + if( pOwner->Classify() == CLASS_PLAYER_ALLY ) + { + m_fMaxRange1 = 3000; + } + else +#endif + { + m_fMaxRange1 = 1400; + } BaseClass::Equip( pOwner ); } +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponSMG1::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + // FIXME: use the returned number of bullets to account for >10hz firerate + WeaponSoundRealtime( SINGLE_NPC ); + + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + FireBulletsInfo_t info( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType ); + info.m_iTracerFreq = 2; + + pOperator->FireBullets( info ); + + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponSMG1::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +} + +#ifdef MAPBASE +float GetCurrentGravity( void ); +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponSMG1::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) +{ + switch( pEvent->event ) + { + case EVENT_WEAPON_SMG1: + { + Vector vecShootOrigin, vecShootDir; + QAngle angDiscard; + + // Support old style attachment point firing + if ((pEvent->options == NULL) || (pEvent->options[0] == '\0') || (!pOperator->GetAttachment(pEvent->options, vecShootOrigin, angDiscard))) + { + vecShootOrigin = pOperator->Weapon_ShootPosition(); + } + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + Assert( npc != NULL ); + vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); + + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); + } + break; + +#ifdef MAPBASE + case EVENT_WEAPON_AR2_ALTFIRE: + { + WeaponSound( WPN_DOUBLE ); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + if (!npc) + return; + + Vector vecShootOrigin, vecShootDir; + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetShootEnemyDir( vecShootOrigin ); + + Vector vecTarget = npc->GetAltFireTarget(); + Vector vecThrow; + if (vecTarget == vec3_origin) + AngleVectors( npc->EyeAngles(), &vecThrow ); // Not much else to do, unfortunately + else + { + // Because this is happening right now, we can't "VecCheckThrow" and can only "VecDoThrow", you know what I mean? + // ...Anyway, this borrows from that so we'll never return vec3_origin. + //vecThrow = VecCheckThrow( this, vecShootOrigin, vecTarget, 600.0, 0.5 ); + + vecThrow = (vecTarget - vecShootOrigin); + + // throw at a constant time + float time = vecThrow.Length() / 600.0; + vecThrow = vecThrow * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecThrow.z += (GetCurrentGravity() * 0.5) * time * 0.5; + } + + CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecShootOrigin, vec3_angle, npc ); + pGrenade->SetAbsVelocity( vecThrow ); + pGrenade->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) ); + pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + + pGrenade->SetThrower( npc ); + + pGrenade->SetGravity(0.5); // lower gravity since grenade is aerodynamic and engine doesn't know it. + + pGrenade->SetDamage(sk_npc_dmg_smg1_grenade.GetFloat()); + + variant_t var; + var.SetEntity(pGrenade); + npc->FireNamedOutput("OnThrowGrenade", var, pGrenade, npc); + } + break; +#else + /*//FIXME: Re-enable + case EVENT_WEAPON_AR2_GRENADE: + { + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + + Vector vecShootOrigin, vecShootDir; + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetShootEnemyDir( vecShootOrigin ); + + Vector vecThrow = m_vecTossVelocity; + + CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecShootOrigin, vec3_angle, npc ); + pGrenade->SetAbsVelocity( vecThrow ); + pGrenade->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) ); + pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY ); + pGrenade->m_hOwner = npc; + pGrenade->m_pMyWeaponAR2 = this; + pGrenade->SetDamage(sk_npc_dmg_ar2_grenade.GetFloat()); + + // FIXME: arrgg ,this is hard coded into the weapon??? + m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + + m_iClip2--; + } + break; + */ +#endif + + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Output : Activity @@ -246,8 +503,16 @@ void CWeaponSMG1::SecondaryAttack( void ) SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifndef CLIENT_DLL + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); +#endif + // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif // Decrease ammo pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); @@ -260,7 +525,122 @@ void CWeaponSMG1::SecondaryAttack( void ) // misyl: Stop dryfire taking over if we have 1 ammo left. m_flNextEmptySoundTime = gpGlobals->curtime + 1.0f; + +#ifndef CLIENT_DLL + // Register a muzzleflash for the AI. + pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); +#endif +} + +#define COMBINE_MIN_GRENADE_CLEAR_DIST 256 + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +// Input : flDot - +// flDist - +// Output : int +//----------------------------------------------------------------------------- +int CWeaponSMG1::WeaponRangeAttack2Condition( float flDot, float flDist ) +{ + CAI_BaseNPC *npcOwner = GetOwner()->MyNPCPointer(); + + return COND_NONE; + +/* + // -------------------------------------------------------- + // Assume things haven't changed too much since last time + // -------------------------------------------------------- + if (gpGlobals->curtime < m_flNextGrenadeCheck ) + return m_lastGrenadeCondition; +*/ + + // ----------------------- + // If moving, don't check. + // ----------------------- + if ( npcOwner->IsMoving()) + return COND_NONE; + + CBaseEntity *pEnemy = npcOwner->GetEnemy(); + + if (!pEnemy) + return COND_NONE; + + Vector vecEnemyLKP = npcOwner->GetEnemyLKP(); + if ( !( pEnemy->GetFlags() & FL_ONGROUND ) && pEnemy->GetWaterLevel() == 0 && vecEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + return COND_NONE; + } + + // -------------------------------------- + // Get target vector + // -------------------------------------- + Vector vecTarget; + if (random->RandomInt(0,1)) + { + // magically know where they are + vecTarget = pEnemy->WorldSpaceCenter(); + } + else + { + // toss it to where you last saw them + vecTarget = vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (pEnemy->BodyTarget( GetLocalOrigin() ) - pEnemy->GetLocalOrigin()); + // estimate position + // vecTarget = vecTarget + pEnemy->m_vecVelocity * 2; + + + if ( ( vecTarget - npcOwner->GetLocalOrigin() ).Length2D() <= COMBINE_MIN_GRENADE_CLEAR_DIST ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return (COND_NONE); + } + + // --------------------------------------------------------------------- + // Are any friendlies near the intended grenade impact area? + // --------------------------------------------------------------------- + CBaseEntity *pTarget = NULL; + + while ( ( pTarget = gEntList.FindEntityInSphere( pTarget, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST ) ) != NULL ) + { + //Check to see if the default relationship is hatred, and if so intensify that + if ( npcOwner->IRelationType( pTarget ) == D_LI ) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return (COND_WEAPON_BLOCKED_BY_FRIEND); + } + } + + // --------------------------------------------------------------------- + // Check that throw is legal and clear + // --------------------------------------------------------------------- + // FIXME: speed is based on difficulty... + + Vector vecToss = VecCheckThrow( this, npcOwner->GetLocalOrigin() + Vector(0,0,60), vecTarget, 600.0, 0.5 ); + if ( vecToss != vec3_origin ) + { + m_vecTossVelocity = vecToss; + + // don't check again for a while. + // JAY: HL1 keeps checking - test? + //m_flNextGrenadeCheck = gpGlobals->curtime; + m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second. + return COND_CAN_RANGE_ATTACK2; + } + else + { + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return COND_WEAPON_SIGHT_OCCLUDED; + } } +#endif //----------------------------------------------------------------------------- const WeaponProficiencyInfo_t *CWeaponSMG1::GetProficiencyValues() diff --git a/src/game/shared/hl2mp/weapon_stunstick.cpp b/src/game/shared/hl2mp/weapon_stunstick.cpp index a71ed475f97..2ca3dbcba09 100644 --- a/src/game/shared/hl2mp/weapon_stunstick.cpp +++ b/src/game/shared/hl2mp/weapon_stunstick.cpp @@ -66,20 +66,8 @@ LINK_ENTITY_TO_CLASS( weapon_stunstick, CWeaponStunStick ); PRECACHE_WEAPON_REGISTER( weapon_stunstick ); -#ifndef CLIENT_DLL - acttable_t CWeaponStunStick::m_acttable[] = { -#ifdef HL2MP - { 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 }, -#endif { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, true }, #if EXPANDED_HL2_WEAPON_ACTIVITIES @@ -107,8 +95,6 @@ acttable_t CWeaponStunStick::m_acttable[] = IMPLEMENT_ACTTABLE(CWeaponStunStick); -#endif - //----------------------------------------------------------------------------- // Constructor @@ -410,11 +396,31 @@ void CWeaponStunStick::SetStunState( bool state ) //FIXME: END - Move to client-side - EmitSound( "Weapon_StunStick.Activate" ); +#ifdef MAPBASE + // Support for weapon sound alternative + if (*GetShootSound( SPECIAL1 )) + { + WeaponSound( SPECIAL1 ); + } + else +#endif + { + EmitSound( "Weapon_StunStick.Activate" ); + } } else { - EmitSound( "Weapon_StunStick.Deactivate" ); +#ifdef MAPBASE + // Support for weapon sound alternative + if (*GetShootSound( SPECIAL2 )) + { + WeaponSound( SPECIAL2 ); + } + else +#endif + { + EmitSound( "Weapon_StunStick.Deactivate" ); + } } } @@ -424,9 +430,12 @@ void CWeaponStunStick::SetStunState( bool state ) //----------------------------------------------------------------------------- bool CWeaponStunStick::Deploy( void ) { + if ( BaseClass::Deploy() == false ) + return false; + SetStunState( true ); - return BaseClass::Deploy(); + return true; } //----------------------------------------------------------------------------- diff --git a/src/game/shared/hl2mp/weapon_stunstick.h b/src/game/shared/hl2mp/weapon_stunstick.h index b1bffc5e607..6b7f8834e80 100644 --- a/src/game/shared/hl2mp/weapon_stunstick.h +++ b/src/game/shared/hl2mp/weapon_stunstick.h @@ -13,9 +13,11 @@ #ifdef HL2MP #include "weapon_hl2mpbasebasebludgeon.h" +#define CBaseHLBludgeonWeapon CBaseHL2MPBludgeonWeapon #else #ifdef CLIENT_DLL #include "c_basehlcombatweapon.h" +#define CBaseHLBludgeonWeapon C_BaseHLBludgeonWeapon #else #include "basebludgeonweapon.h" #endif @@ -30,9 +32,6 @@ #ifdef CLIENT_DLL #define CWeaponStunStick C_WeaponStunStick -#ifndef HL2MP -#define CBaseHLBludgeonWeapon C_BaseHLBludgeonWeapon -#endif #endif #ifndef HL2MP @@ -51,10 +50,7 @@ class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); - -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); -#endif #ifdef CLIENT_DLL virtual int DrawModel( int flags ); From e9908d28cad64757f38d9c7dc304a975c54beb7d Mon Sep 17 00:00:00 2001 From: Blixibon Date: Tue, 12 Aug 2025 12:05:04 -0500 Subject: [PATCH 02/14] Add support for Mapbase anim state to HL2MP --- src/game/client/hl2/c_basehlplayer.cpp | 26 ++++++++- src/game/client/hl2/c_basehlplayer.h | 3 + src/game/client/hl2mp/c_hl2mp_player.cpp | 10 +++- src/game/client/hl2mp/c_hl2mp_player.h | 8 +++ src/game/client/movehelper_client.cpp | 5 ++ src/game/server/hl2/hl2_player.cpp | 2 + src/game/server/hl2/hl2_player.h | 2 + src/game/server/hl2mp/hl2mp_player.cpp | 23 ++++++-- src/game/server/hl2mp/hl2mp_player.h | 6 ++ src/game/shared/hl2mp/hl2mp_player_shared.cpp | 57 ++++++++++++++++++- src/game/shared/hl2mp/hl2mp_player_shared.h | 5 ++ 11 files changed, 140 insertions(+), 7 deletions(-) diff --git a/src/game/client/hl2/c_basehlplayer.cpp b/src/game/client/hl2/c_basehlplayer.cpp index 3947a73cffd..84aabad070f 100644 --- a/src/game/client/hl2/c_basehlplayer.cpp +++ b/src/game/client/hl2/c_basehlplayer.cpp @@ -713,6 +713,30 @@ void C_BaseHLPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quatern #ifdef SP_ANIM_STATE +// Set the activity based on an event or current state +void C_BaseHLPlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ +#ifdef MAPBASE_MP + if (!m_pPlayerAnimState) + { + BaseClass::SetAnimation( playerAnim ); + return; + } + + m_pPlayerAnimState->SetPlayerAnimation( playerAnim ); +#endif +} + +void C_BaseHLPlayer::AddAnimStateLayer( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) +{ +#ifdef MAPBASE_MP + if (!m_pPlayerAnimState) + return; + + m_pPlayerAnimState->AddMiscSequence( iSequence, flBlendIn, flBlendOut, flPlaybackRate, bHoldAtEnd, bOnlyWhenStill ); +#endif +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -737,7 +761,7 @@ const Vector &C_BaseHLPlayer::GetRenderOrigin() const QAngle& C_BaseHLPlayer::GetRenderAngles( void ) { #ifdef MAPBASE_MP - if ( m_pPlayerAnimState ) + if ( m_pPlayerAnimState && !IsRagdoll() ) { return m_pPlayerAnimState->GetRenderAngles(); } diff --git a/src/game/client/hl2/c_basehlplayer.h b/src/game/client/hl2/c_basehlplayer.h index e193c2db35b..87f7f29855f 100644 --- a/src/game/client/hl2/c_basehlplayer.h +++ b/src/game/client/hl2/c_basehlplayer.h @@ -71,6 +71,9 @@ class C_BaseHLPlayer : public C_BasePlayer #endif #ifdef SP_ANIM_STATE + void SetAnimation( PLAYER_ANIM playerAnim ); + void AddAnimStateLayer( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ); + virtual const Vector& GetRenderOrigin(); virtual const QAngle& GetRenderAngles( void ); virtual CStudioHdr *OnNewModel(); diff --git a/src/game/client/hl2mp/c_hl2mp_player.cpp b/src/game/client/hl2mp/c_hl2mp_player.cpp index 367a61632e6..bd1ca14aabc 100644 --- a/src/game/client/hl2mp/c_hl2mp_player.cpp +++ b/src/game/client/hl2mp/c_hl2mp_player.cpp @@ -100,7 +100,11 @@ void SpawnBlood (Vector vecSpot, const Vector &vecDir, int bloodColor, float flD #endif CSuitPowerDevice SuitDeviceBreather( bits_SUIT_DEVICE_BREATHER, 6.7f ); // 100 units in 15 seconds (plus three padded seconds) -C_HL2MP_Player::C_HL2MP_Player() : m_PlayerAnimState( this ), m_iv_angEyeAngles( "C_HL2MP_Player::m_iv_angEyeAngles" ) +C_HL2MP_Player::C_HL2MP_Player() : +#ifndef SP_ANIM_STATE + m_PlayerAnimState( this ), +#endif + m_iv_angEyeAngles( "C_HL2MP_Player::m_iv_angEyeAngles" ) { m_iIDEntIndex = 0; m_iSpawnInterpCounterCache = 0; @@ -582,7 +586,9 @@ void C_HL2MP_Player::AddEntity( void ) SetLocalAngles( vTempAngles ); +#ifndef SP_ANIM_STATE m_PlayerAnimState.Update(); +#endif // Zero out model pitch, blending takes care of all of it. SetLocalAnglesDim( X_INDEX, 0 ); @@ -671,6 +677,7 @@ ShadowType_t C_HL2MP_Player::ShadowCastType( void ) } +#ifndef SP_ANIM_STATE const QAngle& C_HL2MP_Player::GetRenderAngles() { if ( IsRagdoll() ) @@ -682,6 +689,7 @@ const QAngle& C_HL2MP_Player::GetRenderAngles() return m_PlayerAnimState.GetRenderAngles(); } } +#endif bool C_HL2MP_Player::ShouldDraw( void ) { diff --git a/src/game/client/hl2mp/c_hl2mp_player.h b/src/game/client/hl2mp/c_hl2mp_player.h index bac7584d683..915973d751b 100644 --- a/src/game/client/hl2mp/c_hl2mp_player.h +++ b/src/game/client/hl2mp/c_hl2mp_player.h @@ -67,7 +67,9 @@ class C_HL2MP_Player : public C_BaseHLPlayer // Should this object cast shadows? virtual ShadowType_t ShadowCastType( void ); virtual C_BaseAnimating *BecomeRagdollOnClient(); +#ifndef SP_ANIM_STATE virtual const QAngle& GetRenderAngles(); +#endif virtual bool ShouldDraw( void ); virtual void OnDataChanged( DataUpdateType_t type ); virtual float GetFOV( void ); @@ -118,13 +120,19 @@ class C_HL2MP_Player : public C_BaseHLPlayer void StopWalking( void ); bool IsWalking( void ) { return m_fIsWalking; } +#ifdef SP_ANIM_STATE + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); +#endif + virtual void PostThink( void ); private: C_HL2MP_Player( const C_HL2MP_Player & ); +#ifndef SP_ANIM_STATE CPlayerAnimState m_PlayerAnimState; +#endif QAngle m_angEyeAngles; diff --git a/src/game/client/movehelper_client.cpp b/src/game/client/movehelper_client.cpp index cb4b3e169ce..90cb82181ea 100644 --- a/src/game/client/movehelper_client.cpp +++ b/src/game/client/movehelper_client.cpp @@ -310,6 +310,11 @@ bool CMoveHelperClient::PlayerFallingDamage(void) void CMoveHelperClient::PlayerSetAnimation( PLAYER_ANIM eAnim ) { // Do nothing on the client. Animations are set on the server. +#ifdef MAPBASE_MP + // The Mapbase anim state uses clientside anims as well + if ( m_pHost ) + m_pHost->SetAnimation( eAnim ); +#endif } bool CMoveHelperClient::IsWorldEntity( const CBaseHandle &handle ) diff --git a/src/game/server/hl2/hl2_player.cpp b/src/game/server/hl2/hl2_player.cpp index c969d6638a8..16357de6fe8 100644 --- a/src/game/server/hl2/hl2_player.cpp +++ b/src/game/server/hl2/hl2_player.cpp @@ -1480,6 +1480,7 @@ void CHL2_Player::SpawnedAtPoint( CBaseEntity *pSpawnPoint ) //----------------------------------------------------------------------------- +#ifndef MAPBASE_MP // See hl2mp_player_shared.cpp ConVar player_use_anim_enabled( "player_use_anim_enabled", "1" ); ConVar player_use_anim_heavy_mass( "player_use_anim_heavy_mass", "20.0" ); @@ -1529,6 +1530,7 @@ Activity CHL2_Player::Weapon_TranslateActivity( Activity baseAct, bool *pRequire return weaponTranslation; } +#endif #ifdef SP_ANIM_STATE // Set the activity based on an event or current state diff --git a/src/game/server/hl2/hl2_player.h b/src/game/server/hl2/hl2_player.h index d6fd5fc98fb..9dd316dc2f4 100644 --- a/src/game/server/hl2/hl2_player.h +++ b/src/game/server/hl2/hl2_player.h @@ -132,7 +132,9 @@ class CHL2_Player : public CBasePlayer // For the logic_playerproxy output void SpawnedAtPoint( CBaseEntity *pSpawnPoint ); +#ifndef MAPBASE_MP Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); +#endif #ifdef SP_ANIM_STATE void SetAnimation( PLAYER_ANIM playerAnim ); diff --git a/src/game/server/hl2mp/hl2mp_player.cpp b/src/game/server/hl2mp/hl2mp_player.cpp index 0ccf03f6600..a4bb501ea4a 100644 --- a/src/game/server/hl2mp/hl2mp_player.cpp +++ b/src/game/server/hl2mp/hl2mp_player.cpp @@ -143,7 +143,10 @@ const char *g_ppszRandomCombineModels[] = #pragma warning( disable : 4355 ) -CHL2MP_Player::CHL2MP_Player() : m_PlayerAnimState( this ) +CHL2MP_Player::CHL2MP_Player() +#ifndef SP_ANIM_STATE + : m_PlayerAnimState( this ) +#endif { m_angEyeAngles.Init(); @@ -643,7 +646,9 @@ void CHL2MP_Player::PostThink( void ) SetCollisionBounds( VEC_CROUCH_TRACE_MIN, VEC_CROUCH_TRACE_MAX ); } +#ifndef SP_ANIM_STATE m_PlayerAnimState.Update(); +#endif // Store the eye angles pitch so the client can compute its animation state correctly. m_angEyeAngles = EyeAngles(); @@ -762,6 +767,11 @@ extern ConVar hl2_normspeed; // Set the activity based on an event or current state void CHL2MP_Player::SetAnimation( PLAYER_ANIM playerAnim ) { +#ifdef SP_ANIM_STATE + BaseClass::SetAnimation( playerAnim ); + return; +#endif + int animDesired; float speed; @@ -1332,9 +1342,14 @@ void CHL2MP_Player::Event_Killed( const CTakeDamageInfo &info ) SetNumAnimOverlays( 0 ); - // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW - // because we still want to transmit to the clients in our PVS. - CreateRagdollEntity(); +#ifdef MAPBASE_MP + if ( !m_bForceServerRagdoll ) +#endif + { + // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW + // because we still want to transmit to the clients in our PVS. + CreateRagdollEntity(); + } DetonateTripmines(); diff --git a/src/game/server/hl2mp/hl2mp_player.h b/src/game/server/hl2mp/hl2mp_player.h index d82662960de..f8db8d97d41 100644 --- a/src/game/server/hl2mp/hl2mp_player.h +++ b/src/game/server/hl2mp/hl2mp_player.h @@ -99,6 +99,10 @@ class CHL2MP_Player : public CHL2_Player void SetPlayerModel( void ); void SetPlayerTeamModel( void ); Activity TranslateTeamActivity( Activity ActToTranslate ); + +#ifdef SP_ANIM_STATE + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); +#endif float GetNextModelChangeTime( void ) { return m_flNextModelChangeTime; } float GetNextTeamChangeTime( void ) { return m_flNextTeamChangeTime; } @@ -159,7 +163,9 @@ class CHL2MP_Player : public CHL2_Player private: CNetworkQAngle( m_angEyeAngles ); +#ifndef SP_ANIM_STATE CPlayerAnimState m_PlayerAnimState; +#endif int m_iLastWeaponFireUsercmd; int m_iModelType; diff --git a/src/game/shared/hl2mp/hl2mp_player_shared.cpp b/src/game/shared/hl2mp/hl2mp_player_shared.cpp index 503d498c225..aee6aa3e8a2 100644 --- a/src/game/shared/hl2mp/hl2mp_player_shared.cpp +++ b/src/game/shared/hl2mp/hl2mp_player_shared.cpp @@ -14,6 +14,10 @@ #else #include "hl2mp_player.h" #endif +#ifdef MAPBASE +#include "weapon_physcannon.h" +#include "hl2mp_gamerules.h" +#endif #include "engine/IEngineSound.h" #include "SoundEmitterSystem/isoundemittersystembase.h" @@ -126,7 +130,57 @@ void CHL2MP_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, f // ANIMATION CODE //========================== +#ifdef SP_ANIM_STATE +ConVar player_use_anim_enabled( "player_use_anim_enabled", "1", FCVAR_REPLICATED ); + +const float PLAYER_USE_ANIM_HEAVY_MASS = 20.0f; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CHL2MP_Player::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) +{ + Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + +#if EXPANDED_HL2DM_ACTIVITIES + // +USE activities + if ( GetUseEntity() && player_use_anim_enabled.GetBool()) + { + CBaseEntity* pHeldEnt = GetPlayerHeldEntity( this ); + float flMass = pHeldEnt ? + (pHeldEnt->VPhysicsGetObject() ? PlayerPickupGetHeldObjectMass( GetUseEntity(), pHeldEnt->VPhysicsGetObject() ) : PLAYER_USE_ANIM_HEAVY_MASS) : + (GetUseEntity()->VPhysicsGetObject() ? GetUseEntity()->VPhysicsGetObject()->GetMass() : PLAYER_USE_ANIM_HEAVY_MASS); + if ( flMass >= PLAYER_USE_ANIM_HEAVY_MASS) + { + // Heavy versions + switch (baseAct) + { + case ACT_HL2MP_IDLE: weaponTranslation = ACT_HL2MP_IDLE_USE_HEAVY; break; + case ACT_HL2MP_RUN: weaponTranslation = ACT_HL2MP_RUN_USE_HEAVY; break; + case ACT_HL2MP_WALK: weaponTranslation = ACT_HL2MP_WALK_USE_HEAVY; break; + case ACT_HL2MP_IDLE_CROUCH: weaponTranslation = ACT_HL2MP_IDLE_CROUCH_USE_HEAVY; break; + case ACT_HL2MP_WALK_CROUCH: weaponTranslation = ACT_HL2MP_WALK_CROUCH_USE_HEAVY; break; + case ACT_HL2MP_JUMP: weaponTranslation = ACT_HL2MP_JUMP_USE_HEAVY; break; + } + } + else + { + switch (baseAct) + { + case ACT_HL2MP_IDLE: weaponTranslation = ACT_HL2MP_IDLE_USE; break; + case ACT_HL2MP_RUN: weaponTranslation = ACT_HL2MP_RUN_USE; break; + case ACT_HL2MP_WALK: weaponTranslation = ACT_HL2MP_WALK_USE; break; + case ACT_HL2MP_IDLE_CROUCH: weaponTranslation = ACT_HL2MP_IDLE_CROUCH_USE; break; + case ACT_HL2MP_WALK_CROUCH: weaponTranslation = ACT_HL2MP_WALK_CROUCH_USE; break; + case ACT_HL2MP_JUMP: weaponTranslation = ACT_HL2MP_JUMP_USE; break; + } + } + } +#endif + return weaponTranslation; +} +#else // Below this many degrees, slow down turning rate linearly #define FADE_TURN_DEGREES 45.0f // After this, need to start turning feet @@ -574,4 +628,5 @@ void CPlayerAnimState::GetOuterAbsVelocity( Vector& vel ) #else vel = GetOuter()->GetAbsVelocity(); #endif -} \ No newline at end of file +} +#endif diff --git a/src/game/shared/hl2mp/hl2mp_player_shared.h b/src/game/shared/hl2mp/hl2mp_player_shared.h index 3aee9237cd4..69a454df420 100644 --- a/src/game/shared/hl2mp/hl2mp_player_shared.h +++ b/src/game/shared/hl2mp/hl2mp_player_shared.h @@ -12,6 +12,9 @@ #define HL2MP_PUSHAWAY_THINK_INTERVAL (1.0f / 20.0f) #include "studio.h" +#ifdef MAPBASE +#include "mapbase/mapbase_playeranimstate.h" +#endif enum { @@ -34,6 +37,7 @@ enum HL2MPPlayerState #define CHL2MP_Player C_HL2MP_Player #endif +#ifndef SP_ANIM_STATE class CPlayerAnimState { public: @@ -93,5 +97,6 @@ class CPlayerAnimState float m_flTurnCorrectionTime; }; +#endif #endif //HL2MP_PLAYER_SHARED_h From da515c55e6188fc2eb602652d9c4e569cc8d9298 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Tue, 12 Aug 2025 12:11:36 -0500 Subject: [PATCH 03/14] Add automatic protagonist support to HL2MP player --- src/game/server/hl2/hl2_player.cpp | 9 +++ src/game/server/hl2mp/hl2mp_player.cpp | 87 ++++++++++++++++++++++- src/game/shared/hl2mp/hl2mp_gamerules.cpp | 21 ++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/game/server/hl2/hl2_player.cpp b/src/game/server/hl2/hl2_player.cpp index 16357de6fe8..9f40ab0edb6 100644 --- a/src/game/server/hl2/hl2_player.cpp +++ b/src/game/server/hl2/hl2_player.cpp @@ -4604,6 +4604,15 @@ void CHL2_Player::SetProtagonist( const char *pszProtagonist ) return; } +#ifdef HL2MP + int nTeam = g_ProtagonistSystem.GetProtagonist_Team( nIndex ); + if (nTeam != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED && nTeam != TEAM_ANY) + { + // Not an acceptable team + return; + } +#endif + if (m_nProtagonistIndex != -1) { // Flush any pre-existing data diff --git a/src/game/server/hl2mp/hl2mp_player.cpp b/src/game/server/hl2mp/hl2mp_player.cpp index a4bb501ea4a..22689d521a3 100644 --- a/src/game/server/hl2mp/hl2mp_player.cpp +++ b/src/game/server/hl2mp/hl2mp_player.cpp @@ -24,6 +24,7 @@ #include "NextBot.h" #ifdef MAPBASE #include "bot/hl2mp_bot_manager.h" +#include "mapbase/protagonist_system.h" #endif #include "engine/IEngineSound.h" @@ -42,6 +43,10 @@ ConVar hl2mp_spawn_frag_fallback_radius( "hl2mp_spawn_frag_fallback_radius", "48 #define HL2MP_COMMAND_MAX_RATE 0.3 +#ifdef MAPBASE +ConVar sv_hl2mp_protagonist_select( "sv_hl2mp_protagonist_select", "0", FCVAR_NONE, "Allows players to select any valid protagonist, rather than being limited to default HL2:DM models." ); +#endif + void DropPrimedFragGrenade( CHL2MP_Player *pPlayer, CBaseCombatWeapon *pGrenade ); LINK_ENTITY_TO_CLASS( player, CHL2MP_Player ); @@ -403,6 +408,23 @@ void CHL2MP_Player::Spawn(void) bool CHL2MP_Player::ValidatePlayerModel( const char *pModel ) { +#ifdef MAPBASE + if (pModel[0] == '#') + { + if (!sv_hl2mp_protagonist_select.GetBool()) + { + ClientPrint( this, HUD_PRINTTALK, "Server does not allow direct protagonist selection" ); + return false; + } + + int nProtagonist = g_ProtagonistSystem.FindProtagonistIndex( pModel + 1 ); + if (nProtagonist != -1) + { + return true; + } + } +#endif + int iModels = ARRAYSIZE( g_ppszRandomCitizenModels ); int i; @@ -444,7 +466,11 @@ void CHL2MP_Player::SetPlayerTeamModel( void ) int modelIndex = modelinfo->GetModelIndex( szModelName ); +#ifdef MAPBASE + if ( (modelIndex == -1 && szModelName[0] != '#') || ValidatePlayerModel(szModelName) == false) +#else if ( modelIndex == -1 || ValidatePlayerModel( szModelName ) == false ) +#endif { szModelName = "models/Combine_Soldier.mdl"; m_iModelType = TEAM_COMBINE; @@ -479,8 +505,34 @@ void CHL2MP_Player::SetPlayerTeamModel( void ) m_iModelType = TEAM_REBELS; } - + +#ifdef MAPBASE + if ( szModelName[0] == '#' ) + { + // Protagonist name. Was already validated above + SetProtagonist( szModelName + 1 ); + } + else + { + // Find out if we have a protagonist for this model + const char *pszProtagonistName = g_ProtagonistSystem.FindProtagonistByModel( szModelName ); + if (pszProtagonistName) + { + SetProtagonist( pszProtagonistName ); + + // Fall back if not accepted + if ( GetProtagonistIndex() == -1 ) + SetModel( szModelName ); + } + else + { + SetModel( szModelName ); + } + } +#else SetModel( szModelName ); +#endif + SetupPlayerSoundsByModel( szModelName ); m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; @@ -543,6 +595,18 @@ void CHL2MP_Player::SetPlayerModel( void ) } } +#ifdef MAPBASE + if (szModelName[0] == '#') + { + // Protagonist name. Was already validated above + SetProtagonist( szModelName + 1 ); + } + else + { + ResetProtagonist(); + } +#endif + int modelIndex = modelinfo->GetModelIndex( szModelName ); if ( modelIndex == -1 ) @@ -556,7 +620,28 @@ void CHL2MP_Player::SetPlayerModel( void ) engine->ClientCommand ( edict(), szReturnString ); } +#ifdef MAPBASE + if (GetProtagonistIndex() == -1) + { + // Find out if we have a protagonist for this model + const char *pszProtagonistName = g_ProtagonistSystem.FindProtagonistByModel( szModelName ); + if (pszProtagonistName) + { + SetProtagonist( pszProtagonistName ); + + // Fall back if not accepted + if ( GetProtagonistIndex() <= -1 ) + SetModel( szModelName ); + } + else + { + SetModel( szModelName ); + } + } +#else SetModel( szModelName ); +#endif + SetupPlayerSoundsByModel( szModelName ); m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index d3d40aeca8c..fe3b9bee11e 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -35,6 +35,7 @@ #include "hl2mp_cvars.h" #ifdef MAPBASE #include "bot/hl2mp_bot_manager.h" + #include "mapbase/protagonist_system.h" #endif extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); @@ -878,6 +879,26 @@ void CHL2MPRules::ClientSettingsChanged( CBasePlayer *pPlayer ) } else { +#ifdef MAPBASE + if (szModelName[0] == '#') + { + int nProtagonist = g_ProtagonistSystem.FindProtagonistIndex( szModelName + 1 ); + if (nProtagonist != -1) + { + int nTeam = g_ProtagonistSystem.GetProtagonist_Team( nProtagonist ); + if ( nTeam == TEAM_REBELS ) + { + pHL2Player->ChangeTeam( TEAM_REBELS ); + } + else if ( nTeam == TEAM_COMBINE ) + { + pHL2Player->ChangeTeam( TEAM_COMBINE ); + } + } + } + else +#endif + if ( Q_stristr( szModelName, "models/human") ) { pHL2Player->ChangeTeam( TEAM_REBELS ); From bd4f6e9e468ed91e570ca2a3be634f293fa71f64 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Tue, 12 Aug 2025 12:13:07 -0500 Subject: [PATCH 04/14] Allow HL2MP player to use response system for death sound instead of hardcoded soundscripts --- src/game/server/hl2mp/hl2mp_player.cpp | 5 +++++ src/game/server/hl2mp/hl2mp_player.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/game/server/hl2mp/hl2mp_player.cpp b/src/game/server/hl2mp/hl2mp_player.cpp index 22689d521a3..b06cd6dfcbb 100644 --- a/src/game/server/hl2mp/hl2mp_player.cpp +++ b/src/game/server/hl2mp/hl2mp_player.cpp @@ -1488,6 +1488,10 @@ void CHL2MP_Player::DeathSound( const CTakeDamageInfo &info ) if ( m_hRagdoll && m_hRagdoll->GetBaseAnimating()->IsDissolving() ) return; +#ifdef HL2MP_PLAYER_USES_RESPONSE_SYSTEM + // This corresponds to the concept used by HL2 NPCs and should work seamlessly with default HL2 response scripts + Speak( "TLK_DEATH" ); +#else char szStepSound[128]; Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.Die", GetPlayerModelSoundPrefix() ); @@ -1513,6 +1517,7 @@ void CHL2MP_Player::DeathSound( const CTakeDamageInfo &info ) ep.m_pOrigin = &vecOrigin; EmitSound( filter, entindex(), ep ); +#endif } CBaseEntity* CHL2MP_Player::EntSelectSpawnPoint( void ) diff --git a/src/game/server/hl2mp/hl2mp_player.h b/src/game/server/hl2mp/hl2mp_player.h index f8db8d97d41..00e17a4f627 100644 --- a/src/game/server/hl2mp/hl2mp_player.h +++ b/src/game/server/hl2mp/hl2mp_player.h @@ -20,6 +20,10 @@ class CHL2MP_Player; #include "hl2mp_gamerules.h" #include "utldict.h" +#ifdef MAPBASE +#define HL2MP_PLAYER_USES_RESPONSE_SYSTEM 1 +#endif + //============================================================================= // >> HL2MP_Player //============================================================================= From 0d7a3adf9b2662020182872b405b2e3e731b8545 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 13 Aug 2025 00:36:02 -0500 Subject: [PATCH 05/14] Add HL2MP player resource override with NPC net names and idle bots in scoreboard --- src/game/client/c_ai_basenpc.cpp | 24 ++ src/game/client/c_ai_basenpc.h | 4 + src/game/client/c_baseplayer.cpp | 2 +- src/game/client/c_baseplayer.h | 2 +- src/game/client/client_hl2mp.vpc | 2 + .../client/hl2mp/c_hl2mp_playerresource.cpp | 221 ++++++++++++ .../client/hl2mp/c_hl2mp_playerresource.h | 66 ++++ .../client/hl2mp/ui/hl2mpclientscoreboard.cpp | 25 ++ src/game/server/ai_basenpc.cpp | 57 +++ src/game/server/ai_basenpc.h | 15 + .../server/hl2mp/hl2mp_player_resource.cpp | 325 ++++++++++++++++++ src/game/server/hl2mp/hl2mp_player_resource.h | 85 +++++ src/game/server/server_hl2mp.vpc | 2 + src/game/shared/ai_basenpc_shared.h | 10 + src/game/shared/hl2mp/hl2mp_gamerules.cpp | 15 + 15 files changed, 853 insertions(+), 2 deletions(-) create mode 100644 src/game/client/hl2mp/c_hl2mp_playerresource.cpp create mode 100644 src/game/client/hl2mp/c_hl2mp_playerresource.h create mode 100644 src/game/server/hl2mp/hl2mp_player_resource.cpp create mode 100644 src/game/server/hl2mp/hl2mp_player_resource.h diff --git a/src/game/client/c_ai_basenpc.cpp b/src/game/client/c_ai_basenpc.cpp index 42ef2bec086..405af1ab57a 100644 --- a/src/game/client/c_ai_basenpc.cpp +++ b/src/game/client/c_ai_basenpc.cpp @@ -12,6 +12,12 @@ #include "c_basehlplayer.h" #endif +#ifdef MAPBASE_MP +#ifdef HL2MP +#include "c_hl2mp_playerresource.h" +#endif +#endif + #include "death_pose.h" // memdbgon must be the last include file in a .cpp file!!! @@ -183,3 +189,21 @@ bool C_AI_BaseNPC::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x return bRet; } +#ifdef MAPBASE_MP +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +const char *C_AI_BaseNPC::GetPlayerName( void ) const +{ +#ifdef HL2MP + if (g_HL2MP_PR) + { + return g_HL2MP_PR->GetNPCName( entindex() ); + } +#endif + + return BaseClass::GetPlayerName(); +} +#endif + diff --git a/src/game/client/c_ai_basenpc.h b/src/game/client/c_ai_basenpc.h index 834be64d70b..037bc32f0ea 100644 --- a/src/game/client/c_ai_basenpc.h +++ b/src/game/client/c_ai_basenpc.h @@ -41,6 +41,10 @@ class C_AI_BaseNPC : public C_BaseCombatCharacter void OnDataChanged( DataUpdateType_t type ); bool ImportantRagdoll( void ) { return m_bImportanRagdoll; } +#ifdef MAPBASE_MP + virtual const char *GetPlayerName( void ) const; +#endif + private: C_AI_BaseNPC( const C_AI_BaseNPC & ); // not defined, not accessible float m_flTimePingEffect; diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index de33f6751c2..c9bde1d97de 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -816,7 +816,7 @@ void C_BasePlayer::FireGameEvent( IGameEvent *event ) //----------------------------------------------------------------------------- // returns the player name //----------------------------------------------------------------------------- -const char * C_BasePlayer::GetPlayerName() +const char * C_BasePlayer::GetPlayerName() const { return g_PR ? g_PR->GetPlayerName( entindex() ) : ""; } diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index f00a1039418..d81d87c7fde 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -314,7 +314,7 @@ class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener virtual void OverrideView( CViewSetup *pSetup ); // returns the player name - const char * GetPlayerName(); + const char * GetPlayerName() const; virtual const Vector GetPlayerMins( void ) const; // uses local player virtual const Vector GetPlayerMaxs( void ) const; // uses local player diff --git a/src/game/client/client_hl2mp.vpc b/src/game/client/client_hl2mp.vpc index b5bfd9da632..3f9b09348c7 100644 --- a/src/game/client/client_hl2mp.vpc +++ b/src/game/client/client_hl2mp.vpc @@ -127,6 +127,8 @@ $Project "Client (HL2MP)" $File "hl2mp\c_te_hl2mp_shotgun_shot.cpp" $File "hl2mp\clientmode_hl2mpnormal.cpp" $File "hl2mp\clientmode_hl2mpnormal.h" + $File "hl2mp\c_hl2mp_playerresource.cpp" + $File "hl2mp\c_hl2mp_playerresource.h" $File "$SRCDIR\game\shared\hl2mp\hl2mp_gamerules.cpp" $File "$SRCDIR\game\shared\hl2mp\hl2mp_gamerules.h" $File "$SRCDIR\game\shared\hl2mp\hl2mp_player_shared.cpp" diff --git a/src/game/client/hl2mp/c_hl2mp_playerresource.cpp b/src/game/client/hl2mp/c_hl2mp_playerresource.cpp new file mode 100644 index 00000000000..a85dbb5f11b --- /dev/null +++ b/src/game/client/hl2mp/c_hl2mp_playerresource.cpp @@ -0,0 +1,221 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: HL2MP's custom CPlayerResource +// +// (Added by Mapbase for generalized use; most code will act as though +// this is a default feature) +// +// Author: Blixibon +// +//=============================================================================// +#include "cbase.h" +#include "c_hl2mp_playerresource.h" +#include "hl2mp_gamerules.h" +#ifdef MAPBASE +#include "vgui/ILocalize.h" +#include "iclientmode.h" +#include "gamestringpool.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +C_HL2MP_PlayerResource *g_HL2MP_PR; + +IMPLEMENT_CLIENTCLASS_DT( C_HL2MP_PlayerResource, DT_HL2MPPlayerResource, CHL2MPPlayerResource ) +#ifdef MAPBASE + RecvPropArray3( RECVINFO_ARRAY( m_iIdleBot ), RecvPropInt( RECVINFO( m_iIdleBot[0] ) ) ), + + // NPCs + RecvPropArray3( RECVINFO_ARRAY( m_iNPCs ), RecvPropInt( RECVINFO( m_iNPCs[0] ) ) ), + RecvPropArray3( RECVINFO_ARRAY( m_iNPCNameIds ), RecvPropInt( RECVINFO( m_iNPCNameIds[0] ) ) ), + RecvPropArray( RecvPropString( RECVINFO( m_iszNPCNames[0] ) ), m_iszNPCNames ), +#endif +END_RECV_TABLE() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_HL2MP_PlayerResource::C_HL2MP_PlayerResource() +{ + g_HL2MP_PR = this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_HL2MP_PlayerResource::~C_HL2MP_PlayerResource() +{ + g_HL2MP_PR = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_HL2MP_PlayerResource::IsConnected( int index ) +{ +#ifdef MAPBASE + if ( IsIdleBot( index ) ) + return false; + //index = GetIdleBotTarget( index ); +#endif + + return BaseClass::IsConnected( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_HL2MP_PlayerResource::IsAlive( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + index = GetIdleBotTarget( index ); +#endif + + return BaseClass::IsAlive( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *C_HL2MP_PlayerResource::GetPlayerName( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + { + // The player resource does not update names for players which are unconnected but valid. + // We can't override player name updating without changing base behavior, so just test it here. + int nBotIdx = GetIdleBotTarget( index ); + if (IsValid( nBotIdx )) + { + if (FStrEq( m_szName[nBotIdx], PLAYER_UNCONNECTED_NAME )) + { + wchar_t *pIdleLabel = g_pVGuiLocalize->Find( "#game_idle" ); + if (!pIdleLabel) + pIdleLabel = L"IDLE"; + + char szNewName[MAX_PLAYER_NAME_LENGTH]; + V_snprintf( szNewName, MAX_PLAYER_NAME_LENGTH, "%s [%ls]", BaseClass::GetPlayerName( index ), pIdleLabel ); + + m_szName[nBotIdx] = AllocPooledString( szNewName ); + return m_szName[nBotIdx]; + } + + return BaseClass::GetPlayerName( nBotIdx ); + } + } +#endif + + return BaseClass::GetPlayerName( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetPing( int index ) +{ + return BaseClass::GetPing( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetPlayerScore( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + index = GetIdleBotTarget( index ); +#endif + + return BaseClass::GetPlayerScore( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetDeaths( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + index = GetIdleBotTarget( index ); +#endif + + return BaseClass::GetDeaths( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetTeam( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + index = GetIdleBotTarget( index ); +#endif + + return BaseClass::GetTeam( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetFrags( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + index = GetIdleBotTarget( index ); +#endif + + return BaseClass::GetFrags( index ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetHealth( int index ) +{ +#ifdef MAPBASE + if ( IsUsingIdleBot( index ) ) + index = GetIdleBotTarget( index ); +#endif + + return BaseClass::GetHealth( index ); +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetIdleBotTarget( int index ) +{ + return abs( m_iIdleBot[index] ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int C_HL2MP_PlayerResource::GetNPCIndex( int nEntIdx ) +{ + for (int i = 0; i < MAX_PLAYER_RESOURCE_AIS; i++) + { + if (m_iNPCs[i] == nEntIdx) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *C_HL2MP_PlayerResource::GetNPCName( int nEntIdx ) +{ + int nIdx = GetNPCIndex( nEntIdx ); + if (nIdx == -1) + return NULL; + + return STRING( m_iszNPCNames[m_iNPCNameIds[nIdx]] ); +} +#endif diff --git a/src/game/client/hl2mp/c_hl2mp_playerresource.h b/src/game/client/hl2mp/c_hl2mp_playerresource.h new file mode 100644 index 00000000000..a089be95a40 --- /dev/null +++ b/src/game/client/hl2mp/c_hl2mp_playerresource.h @@ -0,0 +1,66 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: HL2MP's custom CPlayerResource +// +// (Added by Mapbase for generalized use; most code will act as though +// this is a default feature) +// +// Author: Blixibon +// +//=============================================================================// + +#ifndef C_HL2MP_PLAYERRESOURCE_H +#define C_HL2MP_PLAYERRESOURCE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "c_playerresource.h" +#include "hl2mp_player_shared.h" +#include "ai_basenpc_shared.h" + +class C_HL2MP_PlayerResource : public C_PlayerResource +{ + DECLARE_CLASS( C_HL2MP_PlayerResource, C_PlayerResource ); +public: + DECLARE_CLIENTCLASS(); + + C_HL2MP_PlayerResource(); + virtual ~C_HL2MP_PlayerResource(); + + virtual bool IsConnected( int index ); + virtual bool IsAlive( int index ); + + virtual const char *GetPlayerName( int index ); + virtual int GetPing( int index ); + virtual int GetPlayerScore( int index ); + virtual int GetDeaths( int index ); + virtual int GetTeam( int index ); + virtual int GetFrags( int index ); + virtual int GetHealth( int index ); + +#ifdef MAPBASE + int GetIdleBotTarget( int index ); + inline bool IsIdleBot( int index ) { return m_iIdleBot[index] < 0; } + inline bool IsUsingIdleBot( int index ) { return m_iIdleBot[index] > 0; } + + const char *GetNPCName( int nEntIdx ); + +protected: + + + int m_iIdleBot[MAX_PLAYERS_ARRAY_SAFE]; + + //----------------------------------------- + + int GetNPCIndex( int nEntIdx ); + + int m_iNPCs[MAX_PLAYER_RESOURCE_AIS]; + int m_iNPCNameIds[MAX_PLAYER_RESOURCE_AIS]; + char m_iszNPCNames[MAX_PLAYER_RESOURCE_AI_NAMES][MAX_PLAYER_NAME_LENGTH]; +#endif +}; + +extern C_HL2MP_PlayerResource *g_HL2MP_PR; + +#endif // C_HL2MP_PLAYERRESOURCE_H diff --git a/src/game/client/hl2mp/ui/hl2mpclientscoreboard.cpp b/src/game/client/hl2mp/ui/hl2mpclientscoreboard.cpp index 50adcaad278..40542b9f18f 100644 --- a/src/game/client/hl2mp/ui/hl2mpclientscoreboard.cpp +++ b/src/game/client/hl2mp/ui/hl2mpclientscoreboard.cpp @@ -12,6 +12,9 @@ #include "c_playerresource.h" #include "c_hl2mp_player.h" #include "hl2mp_gamerules.h" +#ifdef MAPBASE +#include "c_hl2mp_playerresource.h" +#endif #include @@ -20,6 +23,9 @@ #include #include #include +#ifdef MAPBASE +#include +#endif #include "voice_status.h" @@ -639,6 +645,25 @@ void CHL2MPClientScoreBoardDialog::UpdatePlayerInfo() // set the row color based on the players team m_pPlayerList->SetItemFgColor( itemID, g_PR->GetTeamColor( g_PR->GetTeam( i ) ) ); +#ifdef MAPBASE + bool bUsingIdleBot = g_HL2MP_PR->IsUsingIdleBot( i ); + if (bUsingIdleBot) + { + // Make this player grey like spectators + m_pPlayerList->SetItemFgColor( itemID, COLOR_GREY ); + } + + int iImageIndex = playerData->GetInt( "avatar" ); + vgui::IImage *pImage = m_pImageList->GetImage( iImageIndex ); + if ( pImage ) + { + // Make avatar less visible when controlled by an idle bot + static const Color clrHalf( 192, 192, 192, 128 ); + static const Color clrFull( 255, 255, 255, 255 ); + pImage->SetColor( bUsingIdleBot ? clrHalf : clrFull ); + } +#endif + playerData->deleteThis(); } else diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index f607c453436..51b709a6030 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -99,6 +99,10 @@ #include "mapbase/matchers.h" #include "items.h" #include "point_camera.h" +#ifdef HL2MP +#include "hl2mp_gamerules.h" +#include "hl2mp_player_resource.h" +#endif #endif #ifdef MAPBASE_VSCRIPT @@ -579,6 +583,11 @@ void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput ) } RemoveActorFromScriptedScenes( this, false /*all scenes*/ ); + +#if defined(HL2MP) && defined(MAPBASE_MP) + if (g_HL2MP_PR) + g_HL2MP_PR->ForceNPCUpdate(); +#endif } else CGMsg( 1, CON_GROUP_NPC_AI, "Unexpected double-death-cleanup\n" ); @@ -4625,6 +4634,19 @@ void CAI_BaseNPC::NPCThink( void ) m_bIsMoving = IsMoving(); +#ifdef MAPBASE_MP + if (bInPVS) + { +#ifdef HL2MP + // Make sure the player resource has us listed if we just entered PVS + if (g_HL2MP_PR->GetNPCIndex( entindex() ) == -1) + { + g_HL2MP_PR->ForceNPCUpdate(); + } +#endif + } +#endif + PostMovement(); SetSimulationTime( gpGlobals->curtime ); @@ -12481,6 +12503,11 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_FIELD( m_FakeSequenceGestureLayer, FIELD_INTEGER ), #endif +#ifdef MAPBASE_MP + //DEFINE_ARRAY( m_szNetname, FIELD_CHARACTER, MAX_PLAYER_NAME_LENGTH ), + DEFINE_KEYFIELD( m_szNetname, FIELD_STRING, "netname" ), +#endif + // Satisfy classcheck // DEFINE_FIELD( m_ScheduleHistory, CUtlVector < AIScheduleChoice_t > ), @@ -12586,6 +12613,10 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedModifier", InputSetSpeedModifier ), +#ifdef MAPBASE_MP + DEFINE_INPUTFUNC( FIELD_STRING, "SetNetName", InputSetNetName ), +#endif + DEFINE_OUTPUT( m_OnStateChange, "OnStateChange" ), #endif @@ -12876,6 +12907,13 @@ void CAI_BaseNPC::Activate( void ) m_ScheduleHistory.RemoveAll(); #endif//AI_MONITOR_FOR_OSCILLATION +#ifdef MAPBASE_MP + if ( GetNetName()[0] == '\0' ) + { + //V_strncpy( m_szNetname.GetForModify(), GetDefaultNetName(), sizeof( m_szNetname ) ); + m_szNetname = AllocPooledString( GetDefaultNetName() ); + } +#endif } void CAI_BaseNPC::Precache( void ) @@ -13421,6 +13459,10 @@ CAI_BaseNPC::CAI_BaseNPC(void) m_FakeSequenceGestureLayer = -1; #endif + +#ifdef MAPBASE_MP + m_szNetname = NULL_STRING; +#endif } //----------------------------------------------------------------------------- @@ -15121,6 +15163,21 @@ void CAI_BaseNPC::InputSetSpeedModifier( inputdata_t &inputdata ) { this->m_flSpeedModifier = inputdata.value.Float(); } + +#ifdef MAPBASE_MP +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetNetName( inputdata_t &inputdata ) +{ + m_szNetname = AllocPooledString( inputdata.value.String() ); + +#ifdef HL2MP + if (g_HL2MP_PR) + g_HL2MP_PR->ForceNPCUpdate(); +#endif +} +#endif #endif //----------------------------------------------------------------------------- diff --git a/src/game/server/ai_basenpc.h b/src/game/server/ai_basenpc.h index 3689b19507a..99e4a988119 100644 --- a/src/game/server/ai_basenpc.h +++ b/src/game/server/ai_basenpc.h @@ -2188,6 +2188,12 @@ class CAI_BaseNPC : public CBaseCombatCharacter, IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_lifeState ); +#ifdef MAPBASE_MP + // User-friendly name used for death notices, etc. + // Now transmitted by player resource + //CNetworkString( m_szNetname, 32 ); + string_t m_szNetname; +#endif //--------------------------------- // Outputs @@ -2445,6 +2451,15 @@ class CAI_BaseNPC : public CBaseCombatCharacter, static ScriptHook_t g_Hook_RunTask; #endif +#ifdef MAPBASE_MP +public: + // Net Name + const char *GetNetName() { return STRING( m_szNetname ); } + string_t GetNetNameID() { return m_szNetname; } + virtual const char *GetDefaultNetName() { return GetClassname(); } + void InputSetNetName( inputdata_t &inputdata ); +#endif + private: // Break into pieces! diff --git a/src/game/server/hl2mp/hl2mp_player_resource.cpp b/src/game/server/hl2mp/hl2mp_player_resource.cpp new file mode 100644 index 00000000000..40ea6985cc4 --- /dev/null +++ b/src/game/server/hl2mp/hl2mp_player_resource.cpp @@ -0,0 +1,325 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: HL2MP's custom CPlayerResource +// +// (Added by Mapbase for generalized use; most code will act as though +// this is a default feature) +// +// Author: Blixibon +// +//=============================================================================// +#include "cbase.h" +#include "hl2mp_player.h" +#include "hl2mp_player_resource.h" +#include "hl2mp_gamerules.h" + +#ifdef MAPBASE +// Don't need to do this much since we only keep track of names and we're forced to update when an NPC enters PVS +// Decrease this if NPCs start tracking more stats +#define NPC_SEND_FREQUENCY 5.0f +#endif + +CHL2MPPlayerResource *g_HL2MP_PR; + +// Datatable +IMPLEMENT_SERVERCLASS_ST( CHL2MPPlayerResource, DT_HL2MPPlayerResource ) +#ifdef MAPBASE + SendPropArray3( SENDINFO_ARRAY3( m_iIdleBot ), SendPropInt( SENDINFO_ARRAY( m_iIdleBot ), -1, SPROP_VARINT ) ), + + // NPCs + SendPropArray3( SENDINFO_ARRAY3( m_iNPCs ), SendPropInt( SENDINFO_ARRAY( m_iNPCs ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ), + SendPropArray3( SENDINFO_ARRAY3( m_iNPCNameIds ), SendPropInt( SENDINFO_ARRAY( m_iNPCNameIds ), -1, SPROP_UNSIGNED | SPROP_VARINT ) ), + SendPropArray( SendPropStringT( SENDINFO_ARRAY( m_iszNPCNames ) ), m_iszNPCNames ), +#endif +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( hl2mp_player_manager, CHL2MPPlayerResource ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHL2MPPlayerResource::CHL2MPPlayerResource( void ) +{ + g_HL2MP_PR = this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHL2MPPlayerResource::~CHL2MPPlayerResource( void ) +{ + g_HL2MP_PR = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::UpdatePlayerData( void ) +{ + BaseClass::UpdatePlayerData(); + +#ifdef MAPBASE + if (m_flNextNPCUpdate < gpGlobals->curtime) + { + UpdateNPCData(); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::UpdateConnectedPlayer( int iIndex, CBasePlayer *pPlayer ) +{ + BaseClass::UpdateConnectedPlayer( iIndex, pPlayer ); + +#ifdef MAPBASE + CHL2MP_Player *pHL2MPPlayer = ToHL2MPPlayer( pPlayer ); + if ( pHL2MPPlayer && pHL2MPPlayer->GetBotTakeOverAvatar() ) + { + // Stores the reciprocal takeover client. + // This index is positive if this client is the real player, negative if this client is the bot. + int nAvatarIdx = pHL2MPPlayer->GetBotTakeOverAvatar()->entindex(); + m_iIdleBot.Set( iIndex, pHL2MPPlayer->IsFakeClient() ? -nAvatarIdx : nAvatarIdx ); + } + else + { + m_iIdleBot.Set( iIndex, 0 ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::UpdateDisconnectedPlayer( int iIndex ) +{ + BaseClass::UpdateDisconnectedPlayer( iIndex ); +} + + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::UpdateNPCData() +{ + if (g_AI_Manager.NumAIs() > 0) + { + // Keep track of who was updated + bool bUpdated[MAX_PLAYER_RESOURCE_AIS]; + memset( bUpdated, 0, sizeof( bool ) * MAX_PLAYER_RESOURCE_AIS ); + + // Update NPCs + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + if ( ppAIs[i] == NULL || !ShouldConsiderNPC( ppAIs[i] ) ) + continue; + + const char *pszNetName = ppAIs[i]->GetNetName(); + if ( pszNetName && *pszNetName ) + { + int nIdx = FindOrAddNPCIndex( ppAIs[i] ); + if (nIdx != -1) + { + UpdateConnectedNPC( nIdx, ppAIs[i] ); + bUpdated[nIdx] = true; + } + } + } + + // Clean up stale NPCs + for ( int i = 0; i < MAX_PLAYER_RESOURCE_AIS; i++ ) + { + if (!bUpdated[i] && m_iNPCs[i] != 0) + { + UpdateDisconnectedNPC( i ); + } + } + + // Now clean up stale NPC names + for (int i = 0; i < MAX_PLAYER_RESOURCE_AI_NAMES; i++) + { + if (m_iszNPCNames[i] != NULL_STRING) + { + bool bInUse = false; + for ( int j = 0; j < MAX_PLAYER_RESOURCE_AIS; j++ ) + { + if (m_iNPCNameIds[j] == i) + { + bInUse = true; + break; + } + } + + if (!bInUse) + { + // Clear this name + m_iszNPCNames.Set( i, NULL_STRING ); + } + } + } + } + + m_flNextNPCUpdate = gpGlobals->curtime + NPC_SEND_FREQUENCY; + m_bForceNPCUpdate = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::ForceNPCUpdate() +{ + if (!m_bForceNPCUpdate) + { + m_flNextNPCUpdate = gpGlobals->curtime + 0.1f; + m_bForceNPCUpdate = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHL2MPPlayerResource::GetNPCIndex( int nEntIdx ) +{ + for (int i = 0; i < MAX_PLAYER_RESOURCE_AIS; i++) + { + if (m_iNPCs[i] == nEntIdx) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CHL2MPPlayerResource::ShouldConsiderNPC( CAI_BaseNPC *pNPC ) +{ + if (pNPC->Classify() == CLASS_NONE || pNPC->IsMarkedForDeletion()) + return false; + + if (!pNPC->HasCondition( COND_IN_PVS ) && !pNPC->ShouldAlwaysThink() + && pNPC->GetState() != NPC_STATE_COMBAT && pNPC->GetState() != NPC_STATE_SCRIPT) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHL2MPPlayerResource::FindOrAddNPCIndex( CAI_BaseNPC *pNPC ) +{ + int nIdx = pNPC->entindex(); + int nFirstEmpty = -1; + for (int i = 0; i < MAX_PLAYER_RESOURCE_AIS; i++) + { + if (m_iNPCs[i] == nIdx) + return i; + + if (nFirstEmpty == -1 && m_iNPCs[i] == 0) + nFirstEmpty = i; + } + + // New index + if (nFirstEmpty != -1) + { + m_iNPCs.Set( nFirstEmpty, nIdx ); + return nFirstEmpty; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::InitNPC( int iIndex ) +{ + m_iNPCs.Set( iIndex, 0 ); + m_iNPCNameIds.Set( iIndex, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::UpdateConnectedNPC( int iIndex, CAI_BaseNPC *pNPC ) +{ + // A direct comparison should be fine since these are pooled strings + string_t netName = pNPC->GetNetNameID(); + if (netName != m_iszNPCNames.Get( m_iNPCNameIds[iIndex] )) + { + // Find this name in our list + m_iNPCNameIds.Set( iIndex, 0 ); + int nFirstEmpty = -1; + for (int i = 0; i < MAX_PLAYER_RESOURCE_AI_NAMES; i++) + { + if (m_iszNPCNames[i] == netName) + { + m_iNPCNameIds.Set( iIndex, i ); + break; + } + + if (nFirstEmpty == -1 && m_iszNPCNames[i] == NULL_STRING) + nFirstEmpty = i; + } + + if (m_iNPCNameIds[iIndex] == 0) + { + if (nFirstEmpty != -1) + { + m_iszNPCNames.Set( nFirstEmpty, netName ); + m_iNPCNameIds.Set( iIndex, nFirstEmpty ); + } + else + { + // TODO: Better error case? + Warning("CHL2MPPlayerResource::UpdateConnectedNPC: No free names (tried to add %s)\n", STRING(netName)); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::UpdateDisconnectedNPC( int iIndex ) +{ + InitNPC( iIndex ); +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::Spawn( void ) +{ + BaseClass::Spawn(); + +#ifdef MAPBASE + for ( int i = 0; i < MAX_PLAYER_RESOURCE_AIS; i++ ) + { + InitNPC( i ); + } + + for ( int i = 0; i < MAX_PLAYER_RESOURCE_AI_NAMES; i++ ) + { + m_iszNPCNames.Set( i, NULL_STRING ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MPPlayerResource::Init( int iIndex ) +{ + BaseClass::Init( iIndex ); +} + + diff --git a/src/game/server/hl2mp/hl2mp_player_resource.h b/src/game/server/hl2mp/hl2mp_player_resource.h new file mode 100644 index 00000000000..b9ef9906b29 --- /dev/null +++ b/src/game/server/hl2mp/hl2mp_player_resource.h @@ -0,0 +1,85 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: HL2MP's custom CPlayerResource +// +// (Added by Mapbase for generalized use; most code will act as though +// this is a default feature) +// +// Author: Blixibon +// +//=============================================================================// + +#ifndef HL2MP_PLAYER_RESOURCE_H +#define HL2MP_PLAYER_RESOURCE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "player_resource.h" +#include "hl2mp_player_shared.h" +#include "ai_basenpc_shared.h" + +class CHL2MPPlayerResource : public CPlayerResource +{ + DECLARE_CLASS( CHL2MPPlayerResource, CPlayerResource ); + +public: + DECLARE_SERVERCLASS(); + + CHL2MPPlayerResource(); + ~CHL2MPPlayerResource(); + + virtual void UpdatePlayerData( void ); + virtual void Spawn( void ); + virtual void Init( int iIndex ) OVERRIDE; + +#ifdef MAPBASE + virtual void UpdateNPCData( void ); + void ForceNPCUpdate(); + + int GetNPCIndex( int nEntIdx ); +#endif + +protected: + virtual void UpdateConnectedPlayer( int iIndex, CBasePlayer *pPlayer ) OVERRIDE; + virtual void UpdateDisconnectedPlayer( int iIndex ) OVERRIDE; + +#ifdef MAPBASE + // If non-zero, this player is involved with a bot taking over for an idle client. + // This is a positive index to the bot if this is the client being taken over, and + // a negative index if this is the bot in question. Both the client and the bot will + // have this set. + CNetworkArray( int, m_iIdleBot, MAX_PLAYERS_ARRAY_SAFE ); + + //-------------------------------------------------------------------- + // NPCs in Player Resource + // + // The HL2MP player resource is also effectively a "NPC resource". + // NPC names are included in the player resource for two reasons: + // + // 1. NPC net names need to be transmitted to players outside of + // their PVS in case they're killed by a different player/NPC + // 2. Default NPC net names are usually shared and shouldn't all + // be transmitted individually by each NPC + // + // We only track NPCs which are in the PVS and can reasonably expect + // to "participate" in the game (i.e. no bullseyes or generic actors). + //-------------------------------------------------------------------- + virtual bool ShouldConsiderNPC( CAI_BaseNPC *pNPC ); + int FindOrAddNPCIndex( CAI_BaseNPC *pNPC ); + virtual void InitNPC( int iIndex ); + virtual void UpdateConnectedNPC( int iIndex, CAI_BaseNPC *pNPC ); + virtual void UpdateDisconnectedNPC( int iIndex ); + + CNetworkArray( int, m_iNPCs, MAX_PLAYER_RESOURCE_AIS ); // Ent indices for every NPC tracked by the player resource + CNetworkArray( int, m_iNPCNameIds, MAX_PLAYER_RESOURCE_AIS ); + CNetworkArray( string_t, m_iszNPCNames, MAX_PLAYER_RESOURCE_AI_NAMES ); + + float m_flNextNPCUpdate; + bool m_bForceNPCUpdate; +#endif +}; + +extern CHL2MPPlayerResource *g_HL2MP_PR; + +#endif // TF_PLAYER_RESOURCE_H diff --git a/src/game/server/server_hl2mp.vpc b/src/game/server/server_hl2mp.vpc index 8427634cbe1..5c1745ef4d5 100644 --- a/src/game/server/server_hl2mp.vpc +++ b/src/game/server/server_hl2mp.vpc @@ -319,6 +319,8 @@ $Project "Server (HL2MP)" $File "$SRCDIR\game\shared\hl2mp\hl2mp_gamerules.h" $File "hl2mp\hl2mp_player.cpp" $File "hl2mp\hl2mp_player.h" + $File "hl2mp\hl2mp_player_resource.cpp" + $File "hl2mp\hl2mp_player_resource.h" $File "$SRCDIR\game\shared\hl2mp\hl2mp_player_shared.cpp" $File "$SRCDIR\game\shared\hl2mp\hl2mp_player_shared.h" $File "$SRCDIR\game\shared\hl2mp\hl2mp_weapon_parse.cpp" diff --git a/src/game/shared/ai_basenpc_shared.h b/src/game/shared/ai_basenpc_shared.h index 615a472140b..a9d47e9cde7 100644 --- a/src/game/shared/ai_basenpc_shared.h +++ b/src/game/shared/ai_basenpc_shared.h @@ -18,4 +18,14 @@ #define CAI_BaseNPC C_AI_BaseNPC #endif +#ifdef MAPBASE +// How many NPCs and net names the player resource can keep track of in multiplayer. +// See hl2mp_player_resource.h for more information. +enum +{ + MAX_PLAYER_RESOURCE_AIS = 48, // Should be big enough for all participating NPCs that can be in any player's PVS and active at once in a level. Invisible NPCs and actors are not considered. + MAX_PLAYER_RESOURCE_AI_NAMES = 16 // Should be big enough for all unique NPC classes plus any potential map-defined net names. +}; +#endif + #endif // AI_BASENPC_SHARED_H diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index fe3b9bee11e..f1f0feeaa8b 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -36,6 +36,8 @@ #ifdef MAPBASE #include "bot/hl2mp_bot_manager.h" #include "mapbase/protagonist_system.h" + #include "ai_basenpc.h" + #include "hl2mp_player_resource.h" #endif extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); @@ -131,6 +133,9 @@ static const char *s_PreserveEnts[] = "predicted_viewmodel", "worldspawn", "point_devshot_camera", +#ifdef MAPBASE + "hl2mp_player_manager", +#endif "", // END Marker }; @@ -236,7 +241,17 @@ void CHL2MPRules::CreateStandardEntities( void ) #ifndef CLIENT_DLL // Create the entity that will send our data to the client. +#ifdef MAPBASE + // Create the player resource + g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "hl2mp_player_manager", vec3_origin, vec3_angle ); + if (g_pPlayerResource) + { + // Some maps may assume it's called player_manager + g_pPlayerResource->SetName( MAKE_STRING( "player_manager" ) ); + } +#else BaseClass::CreateStandardEntities(); +#endif g_pLastCombineSpawn = NULL; g_pLastRebelSpawn = NULL; From c85d1928d6aabe3b9de0d88f503d58adcd551c90 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 13 Aug 2025 09:35:49 -0500 Subject: [PATCH 06/14] Add NPC team-based relationships --- src/game/server/ai_basenpc.cpp | 45 +++++++++++++++++++++++++ src/game/server/basecombatcharacter.cpp | 33 ++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index 51b709a6030..4309bb9843a 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -176,6 +176,10 @@ extern ISoundEmitterSystemBase *soundemitterbase; ConVar ai_dynint_always_enabled( "ai_dynint_always_enabled", "0", FCVAR_NONE, "Makes the \"Don't Care\" setting equivalent to \"Yes\"." ); ConVar ai_debug_fake_sequence_gestures_always_play( "ai_debug_fake_sequence_gestures_always_play", "0", FCVAR_NONE, "Always plays fake sequence gestures." ); + +#ifdef HL2MP +ConVar ai_team_autoassign( "ai_team_autoassign", "1", FCVAR_NONE, "Automatically assigns NPCs to DM teams. Intended for use with ai_team_relationships." ); +#endif #endif #ifndef _RETAIL @@ -8710,6 +8714,47 @@ void CAI_BaseNPC::OnRangeAttack1() void CAI_BaseNPC::InitRelationshipTable(void) { AddRelationship( STRING( m_RelationshipString ), NULL ); + +#if defined(MAPBASE) && defined(HL2MP) + if (ai_team_autoassign.GetBool() && GetTeamNumber() == TEAM_UNASSIGNED) + { + // Assign team based on classify class + switch (Classify()) + { + case CLASS_METROPOLICE: + case CLASS_COMBINE: + case CLASS_MILITARY: + case CLASS_COMBINE_HUNTER: + case CLASS_MANHACK: + case CLASS_STALKER: + case CLASS_PROTOSNIPER: + case CLASS_COMBINE_GUNSHIP: + case CLASS_SCANNER: + ChangeTeam( TEAM_COMBINE ); + break; + + case CLASS_PLAYER_ALLY: + case CLASS_PLAYER_ALLY_VITAL: + case CLASS_CITIZEN_REBEL: + case CLASS_CONSCRIPT: + case CLASS_VORTIGAUNT: + case CLASS_HACKED_ROLLERMINE: + ChangeTeam( TEAM_REBELS ); + break; + + case CLASS_NONE: + { + if (ClassMatches("npc_turret*")) + { + // HACKHACK: Turrets do not have a class until they are enabled. + // TODO: Account for hacked turrets! Could be done with a new virtual function + ChangeTeam( TEAM_COMBINE ); + } + break; + } + } + } +#endif } diff --git a/src/game/server/basecombatcharacter.cpp b/src/game/server/basecombatcharacter.cpp index 5d09d782024..1513beb9786 100644 --- a/src/game/server/basecombatcharacter.cpp +++ b/src/game/server/basecombatcharacter.cpp @@ -84,6 +84,10 @@ ConVar ai_use_visibility_cache( "ai_use_visibility_cache", "1" ); #endif #endif +#ifdef MAPBASE +ConVar ai_team_relationships( "ai_team_relationships", "1", FCVAR_NONE, "If assigned to a team, NPCs will reinterpret default relationships according to which team each player/NPC is on." ); +#endif + BEGIN_DATADESC( CBaseCombatCharacter ) #ifdef INVASION_DLL @@ -3360,6 +3364,35 @@ Disposition_t CBaseCombatCharacter::IRelationType ( CBaseEntity *pTarget ) } #endif +#ifdef MAPBASE + if (ai_team_relationships.GetBool()) + { + // If both me and my target on a team, override default relationships + if (GetTeamNumber() != TEAM_UNASSIGNED && pTarget->GetTeamNumber() != TEAM_UNASSIGNED) + { + // Only override if our relationship with this entity is identical to the default (prevents overriding ai_relationship) + Disposition_t nDisposition = FindEntityRelationship( pTarget )->disposition; + if (nDisposition == GetDefaultRelationshipDisposition( Classify(), pTarget->Classify() )) + { + if (GetTeamNumber() == pTarget->GetTeamNumber()) + { + // Same team = like (neutral if that was the default) + if (nDisposition == D_NU) + return D_NU; + return D_LI; + } + else + { + // Not the same team = hate (fear if that was the default) + if (nDisposition == D_FR) + return D_FR; + return D_HT; + } + } + } + } +#endif + return FindEntityRelationship( pTarget )->disposition; } From 973bd6f516ff1e7d24e13e88f55a6259f3e8f7a9 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 13 Aug 2025 09:44:27 -0500 Subject: [PATCH 07/14] Add HL2MP bullet FX fixes for NPCs and other entities --- src/game/client/c_ai_basenpc.cpp | 105 ++++++++++++++++++++++ src/game/client/c_ai_basenpc.h | 23 +++++ src/game/server/ai_basenpc.cpp | 47 ++++++++++ src/game/server/ai_basenpc.h | 8 ++ src/game/server/basecombatcharacter.h | 4 + src/game/server/hl2/npc_turret_floor.cpp | 4 + src/game/server/hl2/npc_turret_ground.cpp | 4 + src/game/shared/baseentity_shared.cpp | 22 +++++ 8 files changed, 217 insertions(+) diff --git a/src/game/client/c_ai_basenpc.cpp b/src/game/client/c_ai_basenpc.cpp index 405af1ab57a..12d6ecea4f4 100644 --- a/src/game/client/c_ai_basenpc.cpp +++ b/src/game/client/c_ai_basenpc.cpp @@ -13,6 +13,7 @@ #endif #ifdef MAPBASE_MP +#include "takedamageinfo.h" #ifdef HL2MP #include "c_hl2mp_playerresource.h" #endif @@ -25,6 +26,20 @@ #define PING_MAX_TIME 2.0 +#ifdef MAPBASE_MP +BEGIN_RECV_TABLE_NOBASE( C_AI_BaseNPC, DT_BaseNPCGameData ) + RecvPropInt( RECVINFO( m_iHealth ) ), + RecvPropInt( RECVINFO( m_takedamage ) ), + RecvPropInt( RECVINFO( m_bloodColor ) ), + //RecvPropString( RECVINFO( m_szNetname ) ), // Transmitted by player resource now + RecvPropInt( RECVINFO( m_nDefaultPlayerRelationship ) ), +END_RECV_TABLE(); +#endif + +#ifdef CAI_BaseNPC +#undef CAI_BaseNPC +#endif + IMPLEMENT_CLIENTCLASS_DT( C_AI_BaseNPC, DT_AI_BaseNPC, CAI_BaseNPC ) RecvPropInt( RECVINFO( m_lifeState ) ), RecvPropBool( RECVINFO( m_bPerformAvoidance ) ), @@ -37,6 +52,9 @@ IMPLEMENT_CLIENTCLASS_DT( C_AI_BaseNPC, DT_AI_BaseNPC, CAI_BaseNPC ) RecvPropInt( RECVINFO( m_bSpeedModActive ) ), RecvPropBool( RECVINFO( m_bImportanRagdoll ) ), RecvPropFloat( RECVINFO( m_flTimePingEffect ) ), +#ifdef MAPBASE_MP + RecvPropDataTable( "npc_gamedata", 0, 0, &REFERENCE_RECV_TABLE( DT_BaseNPCGameData ) ), +#endif END_RECV_TABLE() extern ConVar cl_npc_speedmod_intime; @@ -53,6 +71,9 @@ bool NPC_IsImportantNPC( C_BaseAnimating *pAnimating ) C_AI_BaseNPC::C_AI_BaseNPC() { +#ifdef MAPBASE_MP + SetBloodColor( DONT_BLEED ); +#endif } //----------------------------------------------------------------------------- @@ -205,5 +226,89 @@ const char *C_AI_BaseNPC::GetPlayerName( void ) const return BaseClass::GetPlayerName(); } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_AI_BaseNPC::DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + m_fNoDamageDecal = false; + + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) + { + if ( IsPlayerAlly( ToBasePlayer( info.GetAttacker() ) ) ) + { + m_fNoDamageDecal = true; + return; + } + } + + BaseClass::DispatchTraceAttack( info, vecDir, ptr, pAccumulator ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_AI_BaseNPC::DecalTrace( trace_t *pTrace, char const *decalName ) +{ + if ( m_fNoDamageDecal ) + { + // Don't do impact decals when we shouldn't + // (adapts an existing hack from singleplayer HL2, see serverside counterpart) + m_fNoDamageDecal = false; + return; + } + BaseClass::DecalTrace( pTrace, decalName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_AI_BaseNPC::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) +{ + if ( m_fNoDamageDecal ) + { + // Don't do impact decals when we shouldn't + // (adapts an existing hack from singleplayer HL2, see serverside counterpart) + m_fNoDamageDecal = false; + return; + } + BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_AI_BaseNPC::IsPlayerAlly( C_BasePlayer *pPlayer ) +{ + if ( pPlayer == NULL ) + { + pPlayer = C_BasePlayer::GetLocalPlayer(); + } + + if ( pPlayer->GetTeamNumber() == TEAM_UNASSIGNED ) + { + // AI relationship code isn't available here, so we currently transmit a var from the server to determine if we're, at least generically, an ally + return (m_nDefaultPlayerRelationship == GR_TEAMMATE); + } + else if (GetTeamNumber() == pPlayer->GetTeamNumber()) + { + // Same team probably means allies + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_AI_BaseNPC::IsNeutralTo( C_BasePlayer *pPlayer ) +{ + if ( IsPlayerAlly( pPlayer ) ) + return false; + + return (m_nDefaultPlayerRelationship == GR_NOTTEAMMATE); +} #endif diff --git a/src/game/client/c_ai_basenpc.h b/src/game/client/c_ai_basenpc.h index 037bc32f0ea..1d5ce338d17 100644 --- a/src/game/client/c_ai_basenpc.h +++ b/src/game/client/c_ai_basenpc.h @@ -13,6 +13,9 @@ #include "c_basecombatcharacter.h" +#ifdef MAPBASE_MP +#include "ai_debug_shared.h" +#endif // NOTE: Moved all controller code into c_basestudiomodel class C_AI_BaseNPC : public C_BaseCombatCharacter @@ -42,7 +45,14 @@ class C_AI_BaseNPC : public C_BaseCombatCharacter bool ImportantRagdoll( void ) { return m_bImportanRagdoll; } #ifdef MAPBASE_MP + virtual int GetHealth() const { return m_iHealth; } + virtual const char *GetPlayerName( void ) const; + virtual void DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator = NULL ); + void DecalTrace( trace_t *pTrace, char const *decalName ); + void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ); + virtual bool IsPlayerAlly( C_BasePlayer *pPlayer = NULL ); + virtual bool IsNeutralTo( C_BasePlayer *pPlayer = NULL ); #endif private: @@ -59,6 +69,19 @@ class C_AI_BaseNPC : public C_BaseCombatCharacter bool m_bFadeCorpse; bool m_bSpeedModActive; bool m_bImportanRagdoll; + +#ifdef MAPBASE_MP + // User-friendly name used for death notices, etc. + // Now transmitted by player resource + //char m_szNetname[32]; + + // Used to determine whether to draw blood, target ID, etc. on the client + // Uses first 3 gamerules relationship return codes (GR_TEAMMATE, GR_NOTTEAMMATE, and GR_ENEMY) + int m_nDefaultPlayerRelationship; + + // Based on the existing decal hack from singleplayer HL2 + bool m_fNoDamageDecal; +#endif }; diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index 4309bb9843a..2c465796b5c 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -4641,6 +4641,23 @@ void CAI_BaseNPC::NPCThink( void ) #ifdef MAPBASE_MP if (bInPVS) { + // Recalculate player relationship for client + CBasePlayer *pPlayer = UTIL_GetNearestPlayer( GetAbsOrigin() ); + if (pPlayer) + { + Disposition_t nDisposition = pPlayer->IRelationType( this ); + switch (nDisposition) + { + case D_LI: m_nDefaultPlayerRelationship = GR_TEAMMATE; break; + case D_FR: + case D_HT: m_nDefaultPlayerRelationship = GR_ENEMY; break; + default: + case D_NU: m_nDefaultPlayerRelationship = GR_NOTTEAMMATE; break; + } + } + else + m_nDefaultPlayerRelationship = GR_NOTTEAMMATE; // Neutral + #ifdef HL2MP // Make sure the player resource has us listed if we just entered PVS if (g_HL2MP_PR->GetNPCIndex( entindex() ) == -1) @@ -12857,6 +12874,33 @@ BEGIN_SIMPLE_DATADESC( AIScheduleState_t ) END_DATADESC() +#ifdef MAPBASE_MP +void *SendProxy_SendBaseNPCGameDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) +{ + // Only send if we can reasonably expect to be a "participant" in the game + // Invisible NPCs are already EF_NODRAW, so this mainly denies generic_actor, npc_furniture, etc. + CAI_BaseNPC *pNPC = ( CAI_BaseNPC * )pStruct; + if ( pNPC != NULL ) + { + Class_T nClass = pNPC->Classify(); + if (nClass == CLASS_NONE) + { + return NULL; + } + } + return (void *)pVarData; +} +REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendBaseNPCGameDataTable ); + +BEGIN_SEND_TABLE_NOBASE( CAI_BaseNPC, DT_BaseNPCGameData ) + SendPropInt( SENDINFO( m_iHealth ), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ), + SendPropInt( SENDINFO( m_takedamage ), 2, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bloodColor ), 3, SPROP_UNSIGNED ), + //SendPropString( SENDINFO( m_szNetname ) ), // Transmitted by player resource now + SendPropInt( SENDINFO( m_nDefaultPlayerRelationship ), 2, SPROP_UNSIGNED ), +END_SEND_TABLE(); +#endif + IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC ) SendPropInt( SENDINFO( m_lifeState ), 3, SPROP_UNSIGNED ), SendPropBool( SENDINFO( m_bPerformAvoidance ) ), @@ -12869,6 +12913,9 @@ IMPLEMENT_SERVERCLASS_ST( CAI_BaseNPC, DT_AI_BaseNPC ) SendPropInt( SENDINFO( m_iSpeedModSpeed ) ), SendPropBool( SENDINFO( m_bImportanRagdoll ) ), SendPropFloat( SENDINFO( m_flTimePingEffect ) ), +#ifdef MAPBASE_MP + SendPropDataTable( "npc_gamedata", 0, &REFERENCE_SEND_TABLE( DT_BaseNPCGameData ), SendProxy_SendBaseNPCGameDataTable ), +#endif END_SEND_TABLE() //------------------------------------- diff --git a/src/game/server/ai_basenpc.h b/src/game/server/ai_basenpc.h index 99e4a988119..b0b2b8c3706 100644 --- a/src/game/server/ai_basenpc.h +++ b/src/game/server/ai_basenpc.h @@ -2189,6 +2189,14 @@ class CAI_BaseNPC : public CBaseCombatCharacter, IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_lifeState ); #ifdef MAPBASE_MP + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth ); + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_takedamage ); + IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_bloodColor ); + + // Used to determine whether to draw blood, target ID, etc. on the client + // Uses first 3 gamerules relationship return codes (GR_TEAMMATE, GR_NOTTEAMMATE, and GR_ENEMY) + CNetworkVar( int, m_nDefaultPlayerRelationship ); + // User-friendly name used for death notices, etc. // Now transmitted by player resource //CNetworkString( m_szNetname, 32 ); diff --git a/src/game/server/basecombatcharacter.h b/src/game/server/basecombatcharacter.h index 913edea3ecd..c329e1ba40b 100644 --- a/src/game/server/basecombatcharacter.h +++ b/src/game/server/basecombatcharacter.h @@ -595,7 +595,11 @@ class CBaseCombatCharacter : public CBaseFlex void DestroyGlowEffect( void ); protected: +#ifdef MAPBASE_MP + CNetworkVarForDerived( int, m_bloodColor ); +#else int m_bloodColor; // color of blood particless +#endif // ------------------- // combat ability data diff --git a/src/game/server/hl2/npc_turret_floor.cpp b/src/game/server/hl2/npc_turret_floor.cpp index f0cae949e4a..210e4ac5c91 100644 --- a/src/game/server/hl2/npc_turret_floor.cpp +++ b/src/game/server/hl2/npc_turret_floor.cpp @@ -370,6 +370,10 @@ void CNPC_FloorTurret::Spawn( void ) CreateVPhysics(); SetState(NPC_STATE_IDLE); + +#ifdef MAPBASE_MP + SetBloodColor( DONT_BLEED ); +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/server/hl2/npc_turret_ground.cpp b/src/game/server/hl2/npc_turret_ground.cpp index c7dceeaeff9..4d617f87fac 100644 --- a/src/game/server/hl2/npc_turret_ground.cpp +++ b/src/game/server/hl2/npc_turret_ground.cpp @@ -150,6 +150,10 @@ void CNPC_GroundTurret::Spawn( void ) GetAttachment( "light", vecPos ); m_vecLightOffset = vecPos - GetAbsOrigin(); + +#ifdef MAPBASE_MP + SetBloodColor( DONT_BLEED ); +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/shared/baseentity_shared.cpp b/src/game/shared/baseentity_shared.cpp index afc0b265df0..a5c3487c731 100644 --- a/src/game/shared/baseentity_shared.cpp +++ b/src/game/shared/baseentity_shared.cpp @@ -85,6 +85,12 @@ ConVar ai_debug_shoot_positions( "ai_debug_shoot_positions", "0", FCVAR_REPLICAT #if defined(MAPBASE) && defined(GAME_DLL) ConVar ai_shot_notify_targets( "ai_shot_notify_targets", "0", FCVAR_NONE, "Allows fired bullets to notify the NPCs and players they are targeting, regardless of whether they hit them or not. Can be used for custom AI and speech." ); + +#if defined(MAPBASE_MP) && defined(HL2MP) +ConVar mp_use_server_bulletfx( "mp_use_server_bulletfx", "2", FCVAR_NONE, "Whether or not to use serverside bullet FX in multiplayer.\n" + "The default for HL2:DM is 0, but this causes issues with entities that don't transfer their damage filters or blood color to the client, such as NPCs.\n" + "While 1 universally re-enables serverside bullet FX, 2 only uses serverside bullet FX for non-players." ); +#endif #endif // Utility func to throttle rate at which the "reasonable position" spew goes out @@ -1643,8 +1649,24 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) bool bDoServerEffects = true; #if defined( HL2MP ) && defined( GAME_DLL ) +#ifdef MAPBASE_MP + switch (mp_use_server_bulletfx.GetInt()) + { + case 0: + default: + bDoServerEffects = false; + break; + case 1: + bDoServerEffects = true; + break; + case 2: + bDoServerEffects = !IsPlayer(); + break; + } +#else bDoServerEffects = false; #endif +#endif #if defined( GAME_DLL ) if( IsPlayer() ) From 2c829df1dd5e44656e750ff79fb8431ae8f6fdd3 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 13 Aug 2025 09:52:08 -0500 Subject: [PATCH 08/14] Allow HL2MP gamerules to derive from HL2 gamerules + add basic integrated HL2MP co-op support --- src/game/server/hl2mp/hl2mp_player.cpp | 59 ++++++++ src/game/shared/hl2/hl2_gamerules.cpp | 26 +++- src/game/shared/hl2/hl2_gamerules.h | 26 +++- src/game/shared/hl2mp/hl2mp_gamerules.cpp | 170 ++++++++++++++++++++++ src/game/shared/hl2mp/hl2mp_gamerules.h | 54 ++++++- 5 files changed, 321 insertions(+), 14 deletions(-) diff --git a/src/game/server/hl2mp/hl2mp_player.cpp b/src/game/server/hl2mp/hl2mp_player.cpp index b06cd6dfcbb..3f80ca10f5d 100644 --- a/src/game/server/hl2mp/hl2mp_player.cpp +++ b/src/game/server/hl2mp/hl2mp_player.cpp @@ -1556,6 +1556,65 @@ CBaseEntity* CHL2MP_Player::EntSelectSpawnPoint( void ) CBaseEntity *pFirstSpot = pSpot; +#ifdef BASIC_HL2MP_COOP + if ( HL2MPRules()->IsCoOp() ) + { + // Try to spawn on the best player we find + extern ConVar hl2mp_avoidteammates; + if ( hl2mp_avoidteammates.GetBool() ) + { + // HACKHACK: Players don't have their collision group set when they first connect, + // which is needed for UTIL_TraceEntity below + if ( GetCollisionGroup() == COLLISION_GROUP_NONE ) + SetCollisionGroup( COLLISION_GROUP_PLAYER ); + + CBasePlayer *pBestPlayer = NULL; + float flBestHealth = 0.25f; + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( !pPlayer || pPlayer == this || !pPlayer->IsAlive() || pPlayer->GetTeamNumber() != GetTeamNumber() ) + continue; + + float flHealth = ((float)pPlayer->GetHealth() / (float)pPlayer->GetMaxHealth()); + if ( flHealth > flBestHealth ) + { + trace_t tr; + UTIL_TraceEntity( this, pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin(), MASK_PLAYERSOLID, &tr ); + if ( !tr.startsolid && !tr.allsolid ) + { + pBestPlayer = pPlayer; + flBestHealth = flHealth; + } + } + } + + if ( pBestPlayer ) + { + pSpot = pBestPlayer; + goto ReturnSpot; + } + } + + // Don't override if teamplay already selected a team spawn + if (FStrEq(pSpawnpointName, "info_player_deathmatch")) + { + pSpawnpointName = "info_player_coop"; + pLastSpawnPoint = g_pLastSpawn; + + if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL ) + { + // Check base class because only the base class checks for master info_player_starts + pSpot = BaseClass::EntSelectSpawnPoint(); + if (pSpot) + goto ReturnSpot; + else + pSpawnpointName = "info_player_deathmatch"; + } + } + } +#endif + do { if ( pSpot ) diff --git a/src/game/shared/hl2/hl2_gamerules.cpp b/src/game/shared/hl2/hl2_gamerules.cpp index d57832ffd80..afb35456b30 100644 --- a/src/game/shared/hl2/hl2_gamerules.cpp +++ b/src/game/shared/hl2/hl2_gamerules.cpp @@ -500,6 +500,9 @@ ConVar alyx_darkness_force( "alyx_darkness_force", "0", FCVAR_CHEAT | FCVAR_REP //----------------------------------------------------------------------------- void CHalfLife2::PlayerSpawn( CBasePlayer *pPlayer ) { +#ifdef MAPBASE_MP + BaseClass::PlayerSpawn( pPlayer ); +#endif } //----------------------------------------------------------------------------- @@ -1622,19 +1625,28 @@ ConVar alyx_darkness_force( "alyx_darkness_force", "0", FCVAR_CHEAT | FCVAR_REP #ifndef CLIENT_DLL if( (info.GetDamageType() & DMG_CRUSH) && info.GetInflictor() && pVictim->MyNPCPointer() ) { +#ifdef MAPBASE + // Friendly fire needs to be handled here. + if( pVictim->MyNPCPointer()->IsPlayerAlly() && !pVictim->MyNPCPointer()->FriendlyFireEnabled() && !info.IsForceFriendlyFire() ) +#else if( pVictim->MyNPCPointer()->IsPlayerAlly() ) +#endif { // A physics object has struck a player ally. Don't allow damage if it // came from the player's physcannon. - CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); +#ifdef MAPBASE_MP // From SecobMod + for (int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( !pPlayer ) + continue; -#ifdef MAPBASE - // Friendly fire needs to be handled here. - if ( pPlayer && !pVictim->MyNPCPointer()->FriendlyFireEnabled() ) #else + CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); + if( pPlayer ) -#endif { +#endif CBaseEntity *pWeapon = pPlayer->HasNamedPlayerItem("weapon_physcannon"); if( pWeapon ) @@ -1768,6 +1780,7 @@ bool CHalfLife2::ShouldCollide( int collisionGroup0, int collisionGroup1 ) if ( collisionGroup0 == HL2COLLISION_GROUP_COMBINE_BALL && collisionGroup1 == HL2COLLISION_GROUP_COMBINE_BALL_NPC ) return false; +#ifndef MAPBASE_MP if ( ( collisionGroup0 == COLLISION_GROUP_WEAPON ) || ( collisionGroup0 == COLLISION_GROUP_PLAYER ) || ( collisionGroup0 == COLLISION_GROUP_PROJECTILE ) ) @@ -1775,6 +1788,7 @@ bool CHalfLife2::ShouldCollide( int collisionGroup0, int collisionGroup1 ) if ( collisionGroup1 == HL2COLLISION_GROUP_COMBINE_BALL ) return false; } +#endif if ( collisionGroup0 == COLLISION_GROUP_DEBRIS ) { @@ -2098,7 +2112,7 @@ void CHalfLife2::SetAllowSPRespawn( bool toggle ) // Global functions. // ------------------------------------------------------------------------------------ // -#ifndef HL2MP +#if !defined(HL2MP) || defined(HL2MP_USES_HL2_GAMERULES) #ifndef PORTAL // shared ammo definition diff --git a/src/game/shared/hl2/hl2_gamerules.h b/src/game/shared/hl2/hl2_gamerules.h index a8eace12d49..97e872879ab 100644 --- a/src/game/shared/hl2/hl2_gamerules.h +++ b/src/game/shared/hl2/hl2_gamerules.h @@ -10,8 +10,20 @@ #pragma once #endif +#ifdef MAPBASE_MP +// By default, CHL2MPRules inherits from the teamplay gamerules while HL2's gamerules sits unused. +// This preprocessor makes HL2's gamerules inherit from the teamplay gamerules, and then makes CHL2MPRules inherit from HL2's gamerules. +// This basically just wedges CHalfLife2 into their inheritance. It also switches to stock HL2 ammo definitions. +// This allows HL2MP to use HL2 gamerules functions and NPC parameters directly. +// Comment out this definition if this is not desired. +#define HL2MP_USES_HL2_GAMERULES 1 +#endif + #include "gamerules.h" #include "singleplay_gamerules.h" +#ifdef HL2MP_USES_HL2_GAMERULES +#include "teamplayroundbased_gamerules.h" +#endif #include "hl2_shareddefs.h" #ifdef CLIENT_DLL @@ -23,6 +35,11 @@ #define FRIENDLY_FIRE_GLOBALNAME "friendly_fire_override" #endif +#ifdef HL2MP_USES_HL2_GAMERULES + #define CHalfLife2Base CTeamplayRules +#else + #define CHalfLife2Base CSingleplayRules +#endif class CHalfLife2Proxy : public CGameRulesProxy { @@ -62,10 +79,10 @@ class CHalfLife2Proxy : public CGameRulesProxy }; -class CHalfLife2 : public CSingleplayRules +class CHalfLife2 : public CHalfLife2Base { public: - DECLARE_CLASS( CHalfLife2, CSingleplayRules ); + DECLARE_CLASS( CHalfLife2, CHalfLife2Base ); // Damage Query Overrides. virtual bool Damage_IsTimeBased( int iDmgType ); @@ -84,6 +101,8 @@ class CHalfLife2 : public CSingleplayRules #ifdef MAPBASE_VSCRIPT virtual void RegisterScriptFunctions( void ); #endif + + bool MegaPhyscannonActive( void ) { return m_bMegaPhysgun; } private: // Rules change for the mega physgun @@ -122,7 +141,6 @@ class CHalfLife2 : public CSingleplayRules bool NPC_ShouldDropHealth( CBasePlayer *pRecipient ); void NPC_DroppedHealth( void ); void NPC_DroppedGrenade( void ); - bool MegaPhyscannonActive( void ) { return m_bMegaPhysgun; } virtual bool IsAlyxInDarknessMode(); @@ -168,7 +186,7 @@ class CHalfLife2 : public CSingleplayRules //----------------------------------------------------------------------------- inline CHalfLife2* HL2GameRules() { -#if ( !defined( HL2_DLL ) && !defined( HL2_CLIENT_DLL ) ) || defined( HL2MP ) +#if ( !defined( HL2_DLL ) && !defined( HL2_CLIENT_DLL ) ) || ( defined( HL2MP ) && !defined( HL2MP_USES_HL2_GAMERULES ) ) Assert( 0 ); // g_pGameRules is NOT an instance of CHalfLife2 and bad things happen #endif diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index f1f0feeaa8b..bbd771d149b 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -47,6 +47,12 @@ 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 ); +#ifdef MAPBASE +ConVar mp_coop( "mp_coop", "0", FCVAR_NOTIFY, "Basic co-op mode. Makes unassigned players friendly to each other." ); + +ConVar sv_hl2mp_item_respawn_coop( "sv_hl2mp_item_respawn_coop", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +ConVar sv_hl2mp_weapon_respawn_coop( "sv_hl2mp_weapon_respawn_coop", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); +#endif extern ConVar mp_chattime; @@ -64,8 +70,14 @@ BEGIN_NETWORK_TABLE_NOBASE( CHL2MPRules, DT_HL2MPRules ) #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bTeamPlayEnabled ) ), + #ifdef BASIC_HL2MP_COOP + RecvPropBool( RECVINFO( m_bCoOpEnabled ) ), + #endif #else SendPropBool( SENDINFO( m_bTeamPlayEnabled ) ), + #ifdef BASIC_HL2MP_COOP + SendPropBool( SENDINFO( m_bCoOpEnabled ) ), + #endif #endif END_NETWORK_TABLE() @@ -74,6 +86,95 @@ END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( hl2mp_gamerules, CHL2MPGameRulesProxy ); IMPLEMENT_NETWORKCLASS_ALIASED( HL2MPGameRulesProxy, DT_HL2MPGameRulesProxy ) +#if defined(MAPBASE) && defined(GAME_DLL) +BEGIN_DATADESC( CHL2MPGameRulesProxy ) + + // These get the gamerules values on save and write to them on restore + DEFINE_FIELD( m_save_AllowDefaultItems, FIELD_BOOLEAN ), + DEFINE_FIELD( m_save_AllowDefaultSuit, FIELD_BOOLEAN ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_STRING, "SetAllowDefaultItems", InputSetAllowDefaultItems ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetAllowDefaultSuit", InputSetAllowDefaultSuit ), + +END_DATADESC() + +void CHL2MPGameRulesProxy::InputSetAllowDefaultItems( inputdata_t &inputdata ) { KeyValue( "SetAllowDefaultItems", inputdata.value.String() ); } +void CHL2MPGameRulesProxy::InputSetAllowDefaultSuit( inputdata_t &inputdata ) { KeyValue( "SetAllowDefaultSuit", inputdata.value.String() ); } + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CHL2MPGameRulesProxy::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "SetAllowDefaultItems")) + { + HL2MPRules()->SetAllowDefaultItems(!FStrEq(szValue, "0")); + } + else if (FStrEq(szKeyName, "SetAllowDefaultSuit")) + { + HL2MPRules()->SetAllowDefaultSuit(!FStrEq(szValue, "0")); + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + +bool CHL2MPGameRulesProxy::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if (FStrEq(szKeyName, "SetAllowDefaultItems")) + { + Q_snprintf( szValue, iMaxLen, "%i", HL2MPRules()->AllowDefaultItems() ); + } + else if (FStrEq(szKeyName, "SetAllowDefaultSuit")) + { + Q_snprintf( szValue, iMaxLen, "%i", HL2MPRules()->AllowDefaultSuit() ); + } + else + { + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Saves the current object out to disk, by iterating through the objects +// data description hierarchy +// Input : &save - save buffer which the class data is written to +// Output : int - 0 if the save failed, 1 on success +//----------------------------------------------------------------------------- +int CHL2MPGameRulesProxy::Save( ISave &save ) +{ + m_save_AllowDefaultItems = HL2MPRules()->AllowDefaultItems(); + m_save_AllowDefaultSuit = HL2MPRules()->AllowDefaultSuit(); + + return BaseClass::Save(save); +} + +//----------------------------------------------------------------------------- +// Purpose: Restores the current object from disk, by iterating through the objects +// data description hierarchy +// Input : &restore - restore buffer which the class data is read from +// Output : int - 0 if the restore failed, 1 on success +//----------------------------------------------------------------------------- +int CHL2MPGameRulesProxy::Restore( IRestore &restore ) +{ + int base = BaseClass::Restore(restore); + + HL2MPRules()->SetAllowDefaultItems( m_save_AllowDefaultItems ); + HL2MPRules()->SetAllowDefaultSuit( m_save_AllowDefaultSuit ); + + return base; +} +#endif + static HL2MPViewVectors g_HL2MPViewVectors( Vector( 0, 0, 64 ), //VEC_VIEW (m_vView) @@ -202,6 +303,9 @@ CHL2MPRules::CHL2MPRules() } m_bTeamPlayEnabled = teamplay.GetBool(); +#ifdef BASIC_HL2MP_COOP + m_bCoOpEnabled = mp_coop.GetBool(); +#endif m_flIntermissionEndTime = 0.0f; m_flGameStartTime = 0; @@ -646,6 +750,20 @@ void CHL2MPRules::RemoveLevelDesignerPlacedObject( CBaseEntity *pEntity ) } } +//========================================================= +//========================================================= +int CHL2MPRules::ItemShouldRespawn( CItem *pItem ) +{ +#ifdef MAPBASE + if ( IsCoOp() && !sv_hl2mp_item_respawn_coop.GetBool() ) + { + return GR_ITEM_RESPAWN_NO; + } +#endif + + return BaseClass::ItemShouldRespawn( pItem ); +} + //========================================================= // Where should this item respawn? // Some game variations may choose to randomize spawn locations @@ -700,6 +818,13 @@ int CHL2MPRules::WeaponShouldRespawn( CBaseCombatWeapon *pWeapon ) { return GR_WEAPON_RESPAWN_NO; } + +#ifdef MAPBASE + if ( IsCoOp() && !sv_hl2mp_weapon_respawn_coop.GetBool() ) + { + return GR_ITEM_RESPAWN_NO; + } +#endif #endif return GR_WEAPON_RESPAWN_YES; @@ -937,6 +1062,19 @@ void CHL2MPRules::ClientSettingsChanged( CBasePlayer *pPlayer ) int CHL2MPRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) { #ifndef CLIENT_DLL +#ifdef MAPBASE + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + // Check for an ai_relationship override + if ( Disposition_t nRel = pPlayer->MyCombatCharacterPointer()->IRelationType( pTarget ) ) + { + if ( nRel == D_HT || nRel == D_FR ) + return GR_NOTTEAMMATE; + else if ( nRel == D_LI ) + return GR_TEAMMATE; + } +#else // 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 ) @@ -947,6 +1085,29 @@ int CHL2MPRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget return GR_TEAMMATE; } #endif +#endif + +#ifdef MAPBASE + if ( IsTeamplay() == false ) + { +#ifdef BASIC_HL2MP_COOP + // All players are friendly by default in co-op + if ( IsCoOp() ) + return GR_TEAMMATE; +#endif + + return GR_NOTTEAMMATE; + } + +#ifdef CLIENT_DLL + if ( pPlayer->GetTeamNumber() != TEAM_UNASSIGNED && pTarget->GetTeamNumber() != TEAM_UNASSIGNED && pPlayer->GetTeamNumber() == pTarget->GetTeamNumber() ) +#else + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) +#endif + { + return GR_TEAMMATE; + } +#endif return GR_NOTTEAMMATE; } @@ -956,6 +1117,11 @@ const char *CHL2MPRules::GetGameDescription( void ) if ( IsTeamplay() ) return "Team Deathmatch"; +#ifdef BASIC_HL2MP_COOP + if ( IsCoOp() ) + return "Co-Op"; +#endif + return "Deathmatch"; } @@ -1050,6 +1216,8 @@ bool CHL2MPRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args ) return false; } +#ifndef HL2MP_USES_HL2_GAMERULES + // shared ammo definition // JAY: Trying to make a more physical bullet response #define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f) @@ -1086,6 +1254,8 @@ CAmmoDef *GetAmmoDef() return &def; } +#endif + #ifdef CLIENT_DLL ConVar cl_autowepswitch( diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.h b/src/game/shared/hl2mp/hl2mp_gamerules.h index 99551316566..50e19130e6c 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.h +++ b/src/game/shared/hl2mp/hl2mp_gamerules.h @@ -18,11 +18,20 @@ #include "gamerules.h" #include "teamplay_gamerules.h" #include "gamevars_shared.h" +#ifdef MAPBASE +#include "hl2_gamerules.h" +#endif #ifndef CLIENT_DLL #include "hl2mp_player.h" #endif +#ifdef MAPBASE +// Adds support for a basic co-op mode which makes unteamed players friendly and is recognized as "co-op" by the game. +// Comment out this definition if this is not desired. +#define BASIC_HL2MP_COOP 1 +#endif + #define VEC_CROUCH_TRACE_MIN HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMin #define VEC_CROUCH_TRACE_MAX HL2MPRules()->GetHL2MPViewVectors()->m_vCrouchTraceMax @@ -38,11 +47,36 @@ enum #define CHL2MPGameRulesProxy C_HL2MPGameRulesProxy #endif -class CHL2MPGameRulesProxy : public CGameRulesProxy +#ifdef HL2MP_USES_HL2_GAMERULES + #define CHL2MPRulesBase CHalfLife2 + #define CHL2MPRulesProxyBase CHalfLife2Proxy +#else + #define CHL2MPRulesBase CTeamplayRules + #define CHL2MPRulesProxyBase CGameRulesProxy +#endif + +class CHL2MPGameRulesProxy : public CHL2MPRulesProxyBase { public: - DECLARE_CLASS( CHL2MPGameRulesProxy, CGameRulesProxy ); + DECLARE_CLASS( CHL2MPGameRulesProxy, CHL2MPRulesProxyBase ); DECLARE_NETWORKCLASS(); + +#if defined(MAPBASE) && defined(GAME_DLL) + bool KeyValue( const char *szKeyName, const char *szValue ); + bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + virtual int Save( ISave &save ); + virtual int Restore( IRestore &restore ); + + // Inputs + void InputSetAllowDefaultItems( inputdata_t &inputdata ); + void InputSetAllowDefaultSuit( inputdata_t &inputdata ); + + bool m_save_AllowDefaultItems; + bool m_save_AllowDefaultSuit; + + DECLARE_DATADESC(); +#endif }; class HL2MPViewVectors : public CViewVectors @@ -79,10 +113,10 @@ class HL2MPViewVectors : public CViewVectors Vector m_vCrouchTraceMax; }; -class CHL2MPRules : public CTeamplayRules +class CHL2MPRules : public CHL2MPRulesBase { public: - DECLARE_CLASS( CHL2MPRules, CTeamplayRules ); + DECLARE_CLASS( CHL2MPRules, CHL2MPRulesBase ); #ifdef CLIENT_DLL @@ -124,6 +158,7 @@ class CHL2MPRules : public CTeamplayRules void OnNavMeshLoad( void ); #ifndef CLIENT_DLL + virtual int ItemShouldRespawn( CItem *pItem ); virtual Vector VecItemRespawnSpot( CItem *pItem ); virtual QAngle VecItemRespawnAngles( CItem *pItem ); virtual float FlItemRespawnTime( CItem *pItem ); @@ -160,13 +195,24 @@ class CHL2MPRules : public CTeamplayRules bool IsTeamplay( void ) { return m_bTeamPlayEnabled; } +#ifdef BASIC_HL2MP_COOP + bool IsDeathmatch( void ) { return !m_bCoOpEnabled; } + bool IsCoOp( void ) { return m_bCoOpEnabled; } +#endif void CheckAllPlayersReady( void ); virtual bool IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer ); + +#ifndef HL2MP_USES_HL2_GAMERULES + bool MegaPhyscannonActive( void ) { return false; } +#endif private: CNetworkVar( bool, m_bTeamPlayEnabled ); +#ifdef BASIC_HL2MP_COOP + CNetworkVar( bool, m_bCoOpEnabled ); +#endif CNetworkVar( float, m_flGameStartTime ); CUtlVector m_hRespawnableItemsAndWeapons; float m_tmNextPeriodicThink; From 7f7ff6acccba305d179455386e4d5bb9425463a3 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 13 Aug 2025 09:56:11 -0500 Subject: [PATCH 09/14] Add support for NPC death notices and target IDs --- src/game/client/hl2mp/hl2mp_hud_target_id.cpp | 55 +++- src/game/client/hl2mp/hud_deathnotice.cpp | 241 +++++++++++++++++- src/game/server/ai_basenpc.cpp | 4 + src/game/shared/gamerules.h | 3 + src/game/shared/hl2mp/hl2mp_gamerules.cpp | 89 +++++++ src/game/shared/hl2mp/hl2mp_gamerules.h | 8 + 6 files changed, 398 insertions(+), 2 deletions(-) diff --git a/src/game/client/hl2mp/hl2mp_hud_target_id.cpp b/src/game/client/hl2mp/hl2mp_hud_target_id.cpp index 795feae8659..dbe4933bd92 100644 --- a/src/game/client/hl2mp/hl2mp_hud_target_id.cpp +++ b/src/game/client/hl2mp/hl2mp_hud_target_id.cpp @@ -13,6 +13,9 @@ #include "iclientmode.h" #include "vgui/ILocalize.h" #include "hl2mp_gamerules.h" +#ifdef MAPBASE_MP +#include "c_ai_basenpc.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -161,8 +164,12 @@ void CTargetID::Paint() bShowPlayerName = true; g_pVGuiLocalize->ConvertANSIToUnicode( pPlayer->GetPlayerName(), wszPlayerName, sizeof(wszPlayerName) ); - + +#ifdef MAPBASE_MP + if ( HL2MPRules()->PlayerRelationship( pLocalPlayer, pPlayer ) == GR_TEAMMATE ) +#else if ( HL2MPRules()->IsTeamplay() == true && pPlayer->InSameTeam(pLocalPlayer) ) +#endif { printFormatString = "#Playerid_sameteam"; bShowHealth = true; @@ -179,6 +186,52 @@ void CTargetID::Paint() wszHealthText[ ARRAYSIZE(wszHealthText)-1 ] = '\0'; } } +#ifdef MAPBASE_MP + else + { + extern ConVar sv_hl2mp_npc_target_id; + if (sv_hl2mp_npc_target_id.GetBool()) + { + C_BaseEntity *pEnt = cl_entitylist->GetEnt( iEntIndex ); + const char *pszPlayerName = pEnt->GetPlayerName(); + + if (pszPlayerName && *pszPlayerName && (!pEnt->IsNPC() || !pEnt->MyNPCPointer()->IsNeutralTo( pLocalPlayer ))) + { + c = GetColorForTargetTeam( pEnt->GetTeamNumber() ); + + bShowPlayerName = true; + + wchar_t *pLocalized = g_pVGuiLocalize->Find( pszPlayerName ); + if (pLocalized) + { + V_wcsncpy( wszPlayerName, pLocalized, sizeof( wszPlayerName ) ); + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( pszPlayerName, wszPlayerName, sizeof( wszPlayerName ) ); + } + + if ( (HL2MPRules()->IsTeamplay() == true && pEnt->InSameTeam(pLocalPlayer)) || (pEnt->IsNPC() && pEnt->MyNPCPointer()->IsPlayerAlly(pLocalPlayer)) ) + { + printFormatString = "#Playerid_sameteam"; + bShowHealth = true; + } + else + { + printFormatString = "#Playerid_diffteam"; + } + + + if ( bShowHealth ) + { + // NPCs show their health directly + _snwprintf( wszHealthText, ARRAYSIZE(wszHealthText) - 1, L"%i", pEnt->GetHealth() ); + wszHealthText[ ARRAYSIZE(wszHealthText)-1 ] = '\0'; + } + } + } + } +#endif if ( printFormatString ) { diff --git a/src/game/client/hl2mp/hud_deathnotice.cpp b/src/game/client/hl2mp/hud_deathnotice.cpp index 400fcf0820c..432b1e5ff5c 100644 --- a/src/game/client/hl2mp/hud_deathnotice.cpp +++ b/src/game/client/hl2mp/hud_deathnotice.cpp @@ -26,7 +26,12 @@ static ConVar hud_deathnotice_time( "hud_deathnotice_time", "6", 0 ); // Player entries in a death notice struct DeathNoticePlayer { +#ifdef MAPBASE_MP + wchar_t szName[MAX_PLAYER_NAME_LENGTH]; + int nTeam; // Saved to show correct team on NPCs and players who switch teams while dead +#else char szName[MAX_PLAYER_NAME_LENGTH]; +#endif int iEntIndex; }; @@ -60,6 +65,11 @@ class CHudDeathNotice : public CHudElement, public vgui::Panel void RetireExpiredDeathNotices( void ); virtual void FireGameEvent( IGameEvent * event ); +#ifdef MAPBASE_MP + void OnNonPlayerKilled( IGameEvent * event ); + + int GetTeamFromIndex( int iEntIndex ); +#endif private: @@ -116,6 +126,9 @@ void CHudDeathNotice::ApplySchemeSettings( IScheme *scheme ) void CHudDeathNotice::Init( void ) { ListenForGameEvent( "player_death" ); +#ifdef MAPBASE_MP + ListenForGameEvent( "npc_death" ); +#endif } //----------------------------------------------------------------------------- @@ -164,6 +177,14 @@ void CHudDeathNotice::Paint() if ( !icon ) continue; +#ifdef MAPBASE_MP + wchar_t *victim = m_DeathNotices[i].Victim.szName; + wchar_t *killer = m_DeathNotices[i].Killer.szName; + + // Get the team numbers for the players involved + int iKillerTeam = m_DeathNotices[i].Killer.nTeam; + int iVictimTeam = m_DeathNotices[i].Victim.nTeam; +#else wchar_t victim[ 256 ]; wchar_t killer[ 256 ]; @@ -179,6 +200,7 @@ void CHudDeathNotice::Paint() g_pVGuiLocalize->ConvertANSIToUnicode( m_DeathNotices[i].Victim.szName, victim, sizeof( victim ) ); g_pVGuiLocalize->ConvertANSIToUnicode( m_DeathNotices[i].Killer.szName, killer, sizeof( killer ) ); +#endif int nLinePadding = vgui::scheme()->GetProportionalScaledValue( 4 ); @@ -277,6 +299,15 @@ void CHudDeathNotice::FireGameEvent( IGameEvent * event ) if ( hud_deathnotice_time.GetFloat() == 0 ) return; +#ifdef MAPBASE_MP + if ( event->GetName()[0] == 'n' ) // npc_death + { + // Use a different function for non-player deaths + OnNonPlayerKilled( event ); + return; + } +#endif + // the event should be "player_death" int killer = engine->GetPlayerForUserID( event->GetInt("attacker") ); int victim = engine->GetPlayerForUserID( event->GetInt("userid") ); @@ -301,9 +332,29 @@ void CHudDeathNotice::FireGameEvent( IGameEvent * event ) } // Get the names of the players - const char *killer_name = g_PR->GetPlayerName( killer ); + const char *killer_name = NULL; const char *victim_name = g_PR->GetPlayerName( victim ); +#ifdef MAPBASE_MP + bool bIsNPC = false; + if (!killer && event->GetInt( "attacker" ) > gpGlobals->maxClients) + { + // Must be a non-player + killer = event->GetInt( "attacker" ); + C_BaseEntity *pKiller = C_BaseEntity::Instance( killer ); + if (pKiller) + { + // Some classes override GetPlayerName for user-friendly names + killer_name = pKiller->GetPlayerName(); + bIsNPC = true; + } + } + else +#endif + { + killer_name = g_PR->GetPlayerName( killer ); + } + if ( !killer_name ) killer_name = ""; if ( !victim_name ) @@ -313,8 +364,34 @@ void CHudDeathNotice::FireGameEvent( IGameEvent * event ) DeathNoticeItem deathMsg; deathMsg.Killer.iEntIndex = killer; deathMsg.Victim.iEntIndex = victim; +#ifdef MAPBASE_MP + if (bIsNPC) + { + // Find a localized version of the killer name if possible + wchar_t *pLocalized = g_pVGuiLocalize->Find( killer_name ); + if (pLocalized) + { + V_wcsncpy( deathMsg.Killer.szName, pLocalized, sizeof( deathMsg.Killer.szName ) ); + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( killer_name, deathMsg.Killer.szName, sizeof( deathMsg.Killer.szName ) ); + } + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( killer_name, deathMsg.Killer.szName, sizeof( deathMsg.Killer.szName ) ); + } + + g_pVGuiLocalize->ConvertANSIToUnicode( victim_name, deathMsg.Victim.szName, sizeof( deathMsg.Victim.szName ) ); + + // Get the team numbers for the players involved + deathMsg.Killer.nTeam = GetTeamFromIndex( killer ); + deathMsg.Victim.nTeam = GetTeamFromIndex( victim ); +#else Q_strncpy( deathMsg.Killer.szName, killer_name, MAX_PLAYER_NAME_LENGTH ); Q_strncpy( deathMsg.Victim.szName, victim_name, MAX_PLAYER_NAME_LENGTH ); +#endif deathMsg.flDisplayTime = gpGlobals->curtime + hud_deathnotice_time.GetFloat(); deathMsg.iSuicide = ( !killer || killer == victim ); @@ -337,16 +414,157 @@ void CHudDeathNotice::FireGameEvent( IGameEvent * event ) { if ( !strcmp( fullkilledwith, "d_worldspawn" ) ) { +#ifdef MAPBASE_MP + Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%ws died.\n", deathMsg.Victim.szName ); +#else Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s died.\n", deathMsg.Victim.szName ); +#endif } else //d_world { +#ifdef MAPBASE_MP + Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%ws suicided.\n", deathMsg.Victim.szName ); +#else Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s suicided.\n", deathMsg.Victim.szName ); +#endif } } else { +#ifdef MAPBASE_MP + Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%ws killed %ws", deathMsg.Killer.szName, deathMsg.Victim.szName ); +#else Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%s killed %s", deathMsg.Killer.szName, deathMsg.Victim.szName ); +#endif + + if ( fullkilledwith && *fullkilledwith && (*fullkilledwith > 13 ) ) + { + Q_strncat( sDeathMsg, VarArgs( " with %s.\n", fullkilledwith+6 ), sizeof( sDeathMsg ), COPY_ALL_CHARACTERS ); + } + } + + Msg( "%s", sDeathMsg ); +} + +#ifdef MAPBASE_MP +//----------------------------------------------------------------------------- +// Purpose: Server's told us that someone's died +//----------------------------------------------------------------------------- +void CHudDeathNotice::OnNonPlayerKilled( IGameEvent * event ) +{ + int killer = event->GetInt("attacker_entindex"); + int victim = event->GetInt("victim_entindex"); + const char *killedwith = event->GetString( "weapon" ); + + char fullkilledwith[128]; + if ( killedwith && *killedwith ) + { + Q_snprintf( fullkilledwith, sizeof(fullkilledwith), "death_%s", killedwith ); + } + else + { + fullkilledwith[0] = 0; + } + + // Get the names of the players + const char *killer_name = NULL; + const char *victim_name = NULL; + + C_BaseEntity *pKiller = C_BaseEntity::Instance( killer ); + if (pKiller) + { + killer_name = pKiller->GetPlayerName(); + } + + C_BaseEntity *pVictim = C_BaseEntity::Instance( victim ); + if (pVictim) + { + victim_name = pVictim->GetPlayerName(); + } + + // For now, don't show death notices for NPCs we don't have net names for + // (probably a NPC which spawned and died off-screen without any players coming into contact with it) + if ((!killer_name || !*killer_name) && (!victim_name || !*victim_name)) + return; + + if ( !killer_name ) + killer_name = ""; + if ( !victim_name ) + victim_name = ""; + + // Do we have too many death messages in the queue? + if ( m_DeathNotices.Count() > 0 && + m_DeathNotices.Count() >= (int)m_flMaxDeathNotices ) + { + // Remove the oldest one in the queue, which will always be the first + m_DeathNotices.Remove(0); + } + + // Make a new death notice + DeathNoticeItem deathMsg; + deathMsg.Killer.iEntIndex = killer; + deathMsg.Victim.iEntIndex = victim; + + // Find a localized version of the killer name if possible + wchar_t *pLocalized = NULL; + if (killer_name && (!pKiller || !pKiller->IsPlayer())) + { + pLocalized = g_pVGuiLocalize->Find( killer_name ); + } + + if (pLocalized) + V_wcsncpy( deathMsg.Killer.szName, pLocalized, sizeof( deathMsg.Killer.szName ) ); + else + g_pVGuiLocalize->ConvertANSIToUnicode( killer_name, deathMsg.Killer.szName, sizeof( deathMsg.Killer.szName ) ); + + // Then the victim name + pLocalized = NULL; + if (victim_name) + { + pLocalized = g_pVGuiLocalize->Find( victim_name ); + } + + if (pLocalized) + V_wcsncpy( deathMsg.Victim.szName, pLocalized, sizeof( deathMsg.Victim.szName ) ); + else + g_pVGuiLocalize->ConvertANSIToUnicode( victim_name, deathMsg.Victim.szName, sizeof( deathMsg.Victim.szName ) ); + + // Get the team numbers for the players involved + deathMsg.Killer.nTeam = GetTeamFromIndex( killer ); + deathMsg.Victim.nTeam = GetTeamFromIndex( victim ); + + deathMsg.flDisplayTime = gpGlobals->curtime + hud_deathnotice_time.GetFloat(); + deathMsg.iSuicide = ( !killer || killer == victim ); + + // Try and find the death identifier in the icon list + deathMsg.iconDeath = gHUD.GetIcon( fullkilledwith ); + + if ( !deathMsg.iconDeath || deathMsg.iSuicide ) + { + // Can't find it, so use the default skull & crossbones icon + deathMsg.iconDeath = m_iconD_skull; + } + + // Add it to our list of death notices + m_DeathNotices.AddToTail( deathMsg ); + + char sDeathMsg[512]; + + // Record the death notice in the console + if ( deathMsg.iSuicide ) + { + if ( !strcmp( fullkilledwith, "d_worldspawn" ) ) + { + Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%ws died.\n", deathMsg.Victim.szName ); + } + else //d_world + { + Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%ws suicided.\n", deathMsg.Victim.szName ); + } + } + else + { + Q_snprintf( sDeathMsg, sizeof( sDeathMsg ), "%ws killed %ws", deathMsg.Killer.szName, deathMsg.Victim.szName ); if ( fullkilledwith && *fullkilledwith && (*fullkilledwith > 13 ) ) { @@ -357,5 +575,26 @@ void CHudDeathNotice::FireGameEvent( IGameEvent * event ) Msg( "%s", sDeathMsg ); } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudDeathNotice::GetTeamFromIndex( int iEntIndex ) +{ + if ( iEntIndex > gpGlobals->maxClients ) + { + // Non-player, get team number directly + C_BaseEntity *pEnt = C_BaseEntity::Instance( iEntIndex ); + if (pEnt) + return pEnt->GetTeamNumber(); + } + else if ( g_PR ) + { + return g_PR->GetTeam( iEntIndex ); + } + + return TEAM_UNASSIGNED; +} +#endif + diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index 2c465796b5c..2fbddb86a2d 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -632,6 +632,10 @@ void CAI_BaseNPC::Event_Killed( const CTakeDamageInfo &info ) return; } +#ifdef MAPBASE + g_pGameRules->NPCKilled( this, info ); +#endif + Wake( false ); //Adrian: Select a death pose to extrapolate the ragdoll's velocity. diff --git a/src/game/shared/gamerules.h b/src/game/shared/gamerules.h index f226c8a61f1..cdc5544dbfb 100644 --- a/src/game/shared/gamerules.h +++ b/src/game/shared/gamerules.h @@ -316,6 +316,9 @@ abstract_class CGameRules : public CAutoGameSystemPerFrame // Client kills/scoring virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) = 0;// how many points do I award whoever kills this player? virtual void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info ) = 0;// Called each time a player dies +#ifdef MAPBASE + virtual void NPCKilled( CAI_BaseNPC *pVictim, const CTakeDamageInfo &info ) {} // Called each time a NPC dies +#endif virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info )= 0;// Call this from within a GameRules class to report an obituary. virtual const char *GetDamageCustomString( const CTakeDamageInfo &info ) { return NULL; } diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index bbd771d149b..815aff1e183 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -48,6 +48,9 @@ ConVar sv_hl2mp_weapon_respawn_time( "sv_hl2mp_weapon_respawn_time", "20", FCVAR 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 ); #ifdef MAPBASE +ConVar sv_hl2mp_npc_deathnotice_as_killer( "sv_hl2mp_npc_deathnotice_as_killer", "1", FCVAR_GAMEDLL, "If enabled, NPCs which kill players will be recognized in death notices." ); +ConVar sv_hl2mp_npc_deathnotice_as_victim( "sv_hl2mp_npc_deathnotice_as_victim", "1", FCVAR_GAMEDLL, "If enabled, killed NPCs will be recognized in death notices. (Requires 'npc_death' in mod events)" ); + ConVar mp_coop( "mp_coop", "0", FCVAR_NOTIFY, "Basic co-op mode. Makes unassigned players friendly to each other." ); ConVar sv_hl2mp_item_respawn_coop( "sv_hl2mp_item_respawn_coop", "0", FCVAR_GAMEDLL | FCVAR_NOTIFY ); @@ -63,6 +66,10 @@ extern CBaseEntity *g_pLastRebelSpawn; #endif +#ifdef MAPBASE +ConVar sv_hl2mp_npc_target_id( "sv_hl2mp_npc_target_id", "1", FCVAR_REPLICATED, "If enabled, NPCs will be identified when players look at them with their crosshairs." ); +#endif + REGISTER_GAMERULES_CLASS( CHL2MPRules ); @@ -410,6 +417,33 @@ void CHL2MPRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &inf } #if defined(MAPBASE) && !defined(CLIENT_DLL) +void CHL2MPRules::NPCKilled( CAI_BaseNPC *pVictim, const CTakeDamageInfo &info ) +{ + // Ignore bullseyes, actors, etc. + if ( pVictim->GetEffects() & EF_NODRAW || pVictim->Classify() == CLASS_NONE ) + return; + + DeathNotice( pVictim, info ); + + if ( IsCoOp() ) + { + CBasePlayer *pScorer = GetDeathScorer( info.GetAttacker(), info.GetInflictor(), pVictim ); + if (pScorer) + { + // Award points for NPC kills in co-op + // (compensating for IPointsForKill() only accepting players) + int nPoints = 1; + + if ( pScorer->IRelationType( pVictim ) == D_LI ) + nPoints = -1; + + pScorer->IncrementFragCount( nPoints ); + } + } + + BaseClass::NPCKilled( pVictim, info ); +} + void CHL2MPRules::PlayerSpawn( CBasePlayer *pPlayer ) { // Don't spawn with items when a bot is taking over a player, or vice versa @@ -880,7 +914,11 @@ void CHL2MPRules::ClientDisconnected( edict_t *pClient ) //========================================================= // Deathnotice. //========================================================= +#ifdef MAPBASE +void CHL2MPRules::DeathNotice( CBaseCombatCharacter *pVictim, const CTakeDamageInfo &info ) +#else void CHL2MPRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) +#endif { #ifndef CLIENT_DLL // Work out what killed the player, and send a message to all clients about it @@ -924,8 +962,33 @@ void CHL2MPRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info } } } +#ifdef MAPBASE + else if ( pKiller && pKiller->IsNPC() && sv_hl2mp_npc_deathnotice_as_killer.GetBool() ) + { + killer_ID = pKiller->entindex(); + + if ( pInflictor ) + { + if ( pInflictor == pKiller ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + if ( pKiller->MyCombatCharacterPointer()->GetActiveWeapon() ) + { + killer_weapon_name = pKiller->MyCombatCharacterPointer()->GetActiveWeapon()->GetClassname(); + } + } + else + { + killer_weapon_name = pInflictor->GetClassname(); // it's just that easy + } + } + } +#endif else { +#ifdef MAPBASE + if ( pInflictor ) +#endif killer_weapon_name = pInflictor->GetClassname(); } @@ -963,10 +1026,36 @@ void CHL2MPRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info } +#ifdef MAPBASE + if ( !pVictim->IsPlayer() ) + { + if ( sv_hl2mp_npc_deathnotice_as_victim.GetBool() && *pVictim->MyNPCPointer()->GetNetName() ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "npc_death" ); + if( event ) + { + if (pScorer) + killer_ID = pScorer->entindex(); + + event->SetInt("victim_entindex", pVictim->entindex() ); + event->SetInt("attacker_entindex", killer_ID ); + event->SetString("weapon", killer_weapon_name ); + event->SetInt( "priority", 6 ); + gameeventmanager->FireEvent( event ); + } + } + return; + } +#endif + IGameEvent *event = gameeventmanager->CreateEvent( "player_death" ); if( event ) { +#ifdef MAPBASE + event->SetInt("userid", static_cast(pVictim)->GetUserID() ); +#else event->SetInt("userid", pVictim->GetUserID() ); +#endif event->SetInt("attacker", killer_ID ); event->SetString("weapon", killer_weapon_name ); event->SetInt( "priority", 7 ); diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.h b/src/game/shared/hl2mp/hl2mp_gamerules.h index 50e19130e6c..d5de3a58666 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.h +++ b/src/game/shared/hl2mp/hl2mp_gamerules.h @@ -143,7 +143,13 @@ class CHL2MPRules : public CHL2MPRulesBase virtual void ClientSettingsChanged( CBasePlayer *pPlayer ); virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); virtual void GoToIntermission( void ); + void GetDeathNoticeData( CBaseEntity *pVictim, const CTakeDamageInfo &info, const char **killer_weapon_name, int &killer_ID ); +#ifdef MAPBASE + virtual void DeathNotice( CBaseCombatCharacter *pVictim, const CTakeDamageInfo &info ); // Supports NPCs + virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ) { DeathNotice( pVictim->MyCombatCharacterPointer(), info ); } +#else virtual void DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info ); +#endif 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"; } @@ -172,6 +178,8 @@ class CHL2MPRules : public CHL2MPRulesBase const char *GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ); #ifdef MAPBASE + void NPCKilled( CAI_BaseNPC *pVictim, const CTakeDamageInfo &info ); + void PlayerSpawn( CBasePlayer *pPlayer ); void PlayerIdle( CBasePlayer *pPlayer ); From a630cb3d730cd5dd6f91bfbb793ce90460c48a6d Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 27 Aug 2025 11:07:29 -0500 Subject: [PATCH 10/14] Add SP changes to HL2MP gravity gun + clientside +USE prediction --- src/game/client/c_baseentity.h | 5 + src/game/client/c_baseplayer.cpp | 7 + src/game/server/hl2/func_tank.cpp | 17 + src/game/server/hl2/func_tank.h | 4 + src/game/shared/hl2mp/weapon_physcannon.cpp | 1476 +++++++++++++++++-- src/game/shared/hl2mp/weapon_physcannon.h | 65 +- 6 files changed, 1441 insertions(+), 133 deletions(-) diff --git a/src/game/client/c_baseentity.h b/src/game/client/c_baseentity.h index 39ce6f79121..86b2a0234b6 100644 --- a/src/game/client/c_baseentity.h +++ b/src/game/client/c_baseentity.h @@ -1047,6 +1047,11 @@ class C_BaseEntity : public IClientEntity // are relative to the attachment on this entity. void SetParent( C_BaseEntity *pParentEntity, int iParentAttachment=0 ); +#ifdef MAPBASE_MP + // Some entities should predict +USE interaction + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} +#endif + bool PhysicsRunThink( thinkmethods_t thinkMethod = THINK_FIRE_ALL_FUNCTIONS ); bool PhysicsRunSpecificThink( int nContextIndex, BASEPTR thinkFunc ); diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index c9bde1d97de..d8c7623a575 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -2448,6 +2448,13 @@ void C_BasePlayer::PostThink( void ) { SetCollisionBounds( VEC_HULL_MIN, VEC_HULL_MAX ); } + +#ifdef MAPBASE_MP + if ( m_hUseEntity != NULL ) + { + m_hUseEntity->Use( this, this, USE_SET, 2 ); + } +#endif if ( !CommentaryModeShouldSwallowInput( this ) ) { diff --git a/src/game/server/hl2/func_tank.cpp b/src/game/server/hl2/func_tank.cpp index a9961fdb149..69def33aa5b 100644 --- a/src/game/server/hl2/func_tank.cpp +++ b/src/game/server/hl2/func_tank.cpp @@ -1111,6 +1111,23 @@ void CFuncTank::UpdateOnRemove( void ) BaseClass::UpdateOnRemove(); } +#ifdef MAPBASE_MP +void CFuncTank::AddEffects( int nEffects ) +{ + if ( nEffects & EF_NODRAW && gpGlobals->maxClients > 1 ) + { + // The client needs to know that we are the +USE entity for prediction, but EF_NODRAW prevents that. + // Swallow nodraw and turn invisible in a way that transmits to the client. + nEffects &= ~(EF_NODRAW); + + nEffects |= EF_NOSHADOW; + SetRenderMode( kRenderNone ); + } + + BaseClass::AddEffects( nEffects ); +} +#endif + //----------------------------------------------------------------------------- // Barrel position diff --git a/src/game/server/hl2/func_tank.h b/src/game/server/hl2/func_tank.h index da945117c8e..0086a9191a7 100644 --- a/src/game/server/hl2/func_tank.h +++ b/src/game/server/hl2/func_tank.h @@ -91,6 +91,10 @@ class CFuncTank : public CBaseEntity bool KeyValue( const char *szKeyName, const char *szValue ); void UpdateOnRemove(); +#ifdef MAPBASE_MP + void AddEffects( int nEffects ); +#endif + void SetYawRate( float flYawRate ) { m_yawRate = flYawRate; } void SetPitchRate( float flPitchRate ) { m_pitchRate = flPitchRate; } diff --git a/src/game/shared/hl2mp/weapon_physcannon.cpp b/src/game/shared/hl2mp/weapon_physcannon.cpp index 0f9a89e9e30..fe2fac0dd6d 100644 --- a/src/game/shared/hl2mp/weapon_physcannon.cpp +++ b/src/game/shared/hl2mp/weapon_physcannon.cpp @@ -13,7 +13,6 @@ #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 @@ -27,6 +26,15 @@ #include "props.h" #include "te_effect_dispatch.h" #include "util.h" + #include "saverestore_utlvector.h" + #include "prop_combine_ball.h" + #include "physobj.h" + #include "citadel_effects_shared.h" + #include "eventqueue.h" + #include "player_pickup.h" +#ifdef MAPBASE + #include "mapbase/GlobalStrings.h" +#endif #endif #include "gamerules.h" @@ -44,6 +52,8 @@ #include "vphysics/friction.h" #include "weapon_physcannon.h" #include "debugoverlay_shared.h" +#include "model_types.h" +#include "hl2mp_gamerules.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -57,14 +67,20 @@ ConVar g_debug_physcannon( "g_debug_physcannon", "0", FCVAR_REPLICATED | FCVAR_C 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_mega_tracelength( "physcannon_mega_tracelength", "850", 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_mega_pullforce( "physcannon_mega_pullforce", "8000", 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 +#ifdef MAPBASE +ConVar sv_player_enable_propsprint("sv_player_enable_propsprint", "1", FCVAR_NONE, "If enabled, allows the player to sprint while holding a physics object" ); +ConVar sv_player_enable_gravgun_sprint("sv_player_enable_gravgun_sprint", "1", FCVAR_NONE, "Enables the player to sprint while holding a phys. object with the gravity gun" ); +#endif extern ConVar hl2_normspeed; extern ConVar hl2_walkspeed; #endif @@ -83,28 +99,116 @@ extern ConVar hl2_walkspeed; #endif // CLIENT_DLL -#ifndef CLIENT_DLL +// ------------------------------------------------------------------------- +// Physcannon trace filter to handle special cases +// ------------------------------------------------------------------------- -void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ) +class CTraceFilterPhyscannon : public CTraceFilterSimple { +public: + DECLARE_CLASS( CTraceFilterPhyscannon, CTraceFilterSimple ); -} + CTraceFilterPhyscannon( const IHandleEntity *passentity, int collisionGroup ) + : CTraceFilterSimple( NULL, collisionGroup ), m_pTraceOwner( passentity ) { } -bool PlayerHasMegaPhysCannon( void ) -{ - return false; -} + // For this test, we only test against entities (then world brushes afterwards) + virtual TraceType_t GetTraceType() const { return TRACE_ENTITIES_ONLY; } -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; -} + bool HasContentsGrate( CBaseEntity *pEntity ) + { + // FIXME: Move this into the GetModelContents() function in base entity + + // Find the contents based on the model type + int nModelType = modelinfo->GetModelType( pEntity->GetModel() ); + if ( nModelType == mod_studio ) + { + CBaseAnimating *pAnim = dynamic_cast(pEntity); + if ( pAnim != NULL ) + { + CStudioHdr *pStudioHdr = pAnim->GetModelPtr(); + if ( pStudioHdr != NULL && (pStudioHdr->contents() & CONTENTS_GRATE) ) + return true; + } + } + else if ( nModelType == mod_brush ) + { + // Brushes poll their contents differently + int contents = modelinfo->GetModelContents( pEntity->GetModelIndex() ); + if ( contents & CONTENTS_GRATE ) + return true; + } + + return false; + } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + // Only skip ourselves (not things we own) + if ( pHandleEntity == m_pTraceOwner ) + return false; + + // Get the entity referenced by this handle + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + if ( pEntity == NULL ) + return false; + + // Handle grate entities differently + if ( HasContentsGrate( pEntity ) ) + { +#ifndef CLIENT_DLL +#ifdef MAPBASE + if (pEntity->CanBePickedUpByPhyscannon()) + return true; +#else + // See if it's a grabbable physics prop + CPhysicsProp *pPhysProp = dynamic_cast(pEntity); + if ( pPhysProp != NULL ) + return pPhysProp->CanBePickedUpByPhyscannon(); + + // See if it's a grabbable physics prop + if ( FClassnameIs( pEntity, "prop_physics" ) ) + { + CPhysicsProp *pPhysProp = dynamic_cast(pEntity); + if ( pPhysProp != NULL ) + return pPhysProp->CanBePickedUpByPhyscannon(); + + // Somehow had a classname that didn't match the class! + Assert(0); + } + else if ( FClassnameIs( pEntity, "func_physbox" ) ) + { + // Must be a moveable physbox + CPhysBox *pPhysBox = dynamic_cast(pEntity); + if ( pPhysBox ) + return pPhysBox->CanBePickedUpByPhyscannon(); + // Somehow had a classname that didn't match the class! + Assert(0); + } +#endif #endif -//----------------------------------------------------------------------------- + // Don't bother with any other sort of grated entity + return false; + } + + // Use the default rules + return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask ); + } + +protected: + const IHandleEntity *m_pTraceOwner; +}; + +// We want to test against brushes alone +class CTraceFilterOnlyBrushes : public CTraceFilterSimple +{ +public: + DECLARE_CLASS( CTraceFilterOnlyBrushes, CTraceFilterSimple ); + CTraceFilterOnlyBrushes( int collisionGroup ) : CTraceFilterSimple( NULL, collisionGroup ) {} + virtual TraceType_t GetTraceType() const { return TRACE_WORLD_ONLY; } +}; + //----------------------------------------------------------------------------- // this will hit skip the pass entity, but not anything it owns // (lets player grab own grenades) @@ -112,12 +216,12 @@ 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 ) @@ -130,6 +234,72 @@ class CTraceFilterNoOwnerTest : public CTraceFilterSimple const IHandleEntity *m_pPassNotOwner; }; + + +//----------------------------------------------------------------------------- +// Purpose: Trace a line the special physcannon way! +//----------------------------------------------------------------------------- +void UTIL_PhyscannonTraceLine( const Vector &vecAbsStart, const Vector &vecAbsEnd, CBaseEntity *pTraceOwner, trace_t *pTrace ) +{ + // Default to HL2 vanilla + if ( hl2_episodic.GetBool() == false ) + { + CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + return; + } + + // First, trace against entities + CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceLine( vecAbsStart, vecAbsEnd, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + + // If we've hit something, test again to make sure no brushes block us + if ( pTrace->m_pEnt != NULL ) + { + trace_t testTrace; + CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE ); + UTIL_TraceLine( pTrace->startpos, pTrace->endpos, MASK_SHOT, &brushFilter, &testTrace ); + + // If we hit a brush, replace the trace with that result + if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid ) + { + *pTrace = testTrace; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Trace a hull for the physcannon +//----------------------------------------------------------------------------- +void UTIL_PhyscannonTraceHull( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &vecAbsMins, const Vector &vecAbsMaxs, CBaseEntity *pTraceOwner, trace_t *pTrace ) +{ + // Default to HL2 vanilla + if ( hl2_episodic.GetBool() == false ) + { + CTraceFilterNoOwnerTest filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + return; + } + + // First, trace against entities + CTraceFilterPhyscannon filter( pTraceOwner, COLLISION_GROUP_NONE ); + UTIL_TraceHull( vecAbsStart, vecAbsEnd, vecAbsMins, vecAbsMaxs, (MASK_SHOT|CONTENTS_GRATE), &filter, pTrace ); + + // If we've hit something, test again to make sure no brushes block us + if ( pTrace->m_pEnt != NULL ) + { + trace_t testTrace; + CTraceFilterOnlyBrushes brushFilter( COLLISION_GROUP_NONE ); + UTIL_TraceHull( pTrace->startpos, pTrace->endpos, vecAbsMins, vecAbsMaxs, MASK_SHOT, &brushFilter, &testTrace ); + + // If we hit a brush, replace the trace with that result + if ( testTrace.fraction < 1.0f || testTrace.startsolid || testTrace.allsolid ) + { + *pTrace = testTrace; + } + } +} + static void MatrixOrthogonalize( matrix3x4_t &matrix, int column ) { Vector columns[3]; @@ -197,6 +367,51 @@ static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector } } +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest ragdoll sub-piece to a location and returns it +// Input : *pTarget - entity that is the potential ragdoll +// &position - position we're testing against +// Output : IPhysicsObject - sub-object (if any) +//----------------------------------------------------------------------------- +IPhysicsObject *GetRagdollChildAtPosition( CBaseEntity *pTarget, const Vector &position ) +{ +#ifdef CLIENT_DLL + return NULL; +#else + // Check for a ragdoll + if ( dynamic_cast( pTarget ) == NULL ) + return NULL; + + // Get the root + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pTarget->VPhysicsGetObjectList( pList, ARRAYSIZE( pList ) ); + + IPhysicsObject *pBestChild = NULL; + float flBestDist = 99999999.0f; + float flDist; + Vector vPos; + + // Find the nearest child to where we're looking + for ( int i = 0; i < count; i++ ) + { + pList[i]->GetPosition( &vPos, NULL ); + + flDist = ( position - vPos ).LengthSqr(); + + if ( flDist < flBestDist ) + { + pBestChild = pList[i]; + flBestDist = flDist; + } + } + + // Make this our base now + pTarget->VPhysicsSwapObject( pBestChild ); + + return pTarget->VPhysicsGetObject(); +#endif +} + //----------------------------------------------------------------------------- // Purpose: Computes a local matrix for the player clamped to valid carry ranges //----------------------------------------------------------------------------- @@ -280,6 +495,10 @@ CGrabController::CGrabController( void ) m_attachedEntity = NULL; m_vecPreferredCarryAngles = vec3_angle; m_bHasPreferredCarryAngles = false; + m_flDistanceOffset = 0; +#ifdef MAPBASE + m_bDontUseListMass = false; +#endif } CGrabController::~CGrabController( void ) @@ -454,6 +673,17 @@ void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, // ComputeMaxSpeed( pEntity, pPhys ); + // If we haven't been killed by a grab, we allow the gun to grab the nearest part of a ragdoll + if ( bUseGrabPosition ) + { + IPhysicsObject *pChild = GetRagdollChildAtPosition( pEntity, vGrabPosition ); + + if ( pChild ) + { + pPhys = pChild; + } + } + // Carried entities can never block LOS m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS(); pEntity->SetBlocksLOS( false ); @@ -479,19 +709,25 @@ void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, { 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 ); +#ifdef MAPBASE + if (!m_bDontUseListMass || pList[i] == pPhys) +#endif + { + m_flLoadWeight += 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_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating m_error = 0; m_contactAmount = 0; @@ -501,14 +737,32 @@ void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment ); } - VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace ); +#ifndef CLIENT_DLL + // Ragdolls don't offset this way + if ( dynamic_cast(pEntity) ) + { + m_attachedPositionObjectSpace.Init(); + } + else +#endif + { + 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(pEntity); if ( pProp ) { +#ifdef MAPBASE + // If the prop has custom carry angles, don't override them + // (regular PreferredCarryAngles() code should cover it) + if (!pProp->m_bUsesCustomCarryAngles) + m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); +#else m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); +#endif + m_flDistanceOffset = pProp->GetCarryDistanceOffset(); } else { @@ -652,11 +906,20 @@ float CGrabController::GetSavedMass( IPhysicsObject *pObject ) // Player pickup controller //----------------------------------------------------------------------------- +#ifdef CLIENT_DLL +#define CPlayerPickupController C_PlayerPickupController +#endif + class CPlayerPickupController : public CBaseEntity { DECLARE_CLASS( CPlayerPickupController, CBaseEntity ); +#ifdef MAPBASE_MP + DECLARE_NETWORKCLASS(); +#endif public: +#ifndef CLIENT_DLL void Init( CBasePlayer *pPlayer, CBaseEntity *pObject ); +#endif void Shutdown( bool bThrown = false ); bool OnControls( CBaseEntity *pControls ) { return true; } void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); @@ -667,16 +930,60 @@ class CPlayerPickupController : public CBaseEntity void VPhysicsUpdate( IPhysicsObject *pPhysics ){} void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {} +#if defined(MAPBASE_MP) && defined(CLIENT_DLL) + void OnDataChanged( DataUpdateType_t type ); + void ManagePredictedObject( void ); +#endif + bool IsHoldingEntity( CBaseEntity *pEnt ); CGrabController &GetGrabController() { return m_grabController; } private: CGrabController m_grabController; + +#ifdef MAPBASE_MP + CNetworkHandle( CBaseEntity, m_hAttachedObject ); + CNetworkHandle( CBasePlayer, m_pPlayer ); + +#ifndef CLIENT_DLL + CNetworkQAngle ( m_attachedAnglesPlayerSpace ); +#else + QAngle m_attachedAnglesPlayerSpace; + + EHANDLE m_hOldAttachedObject; +#endif + + CNetworkVector ( m_attachedPositionObjectSpace ); +#else CBasePlayer *m_pPlayer; +#endif }; LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); +#ifdef MAPBASE_MP +IMPLEMENT_NETWORKCLASS_ALIASED( PlayerPickupController, DT_PlayerPickupController ) + +BEGIN_NETWORK_TABLE( CPlayerPickupController, DT_PlayerPickupController ) +#ifdef CLIENT_DLL + RecvPropEHandle( RECVINFO( m_hAttachedObject ) ), + RecvPropEHandle( RECVINFO( m_pPlayer ) ), + RecvPropVector( RECVINFO( m_attachedPositionObjectSpace ) ), + RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[0] ) ), + RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[1] ) ), + RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[2] ) ), +#else + SendPropEHandle( SENDINFO( m_hAttachedObject ) ), + SendPropEHandle( SENDINFO( m_pPlayer ) ), + 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 ), +#endif +END_NETWORK_TABLE() +#endif + +#ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: // Input : *pPlayer - @@ -684,7 +991,6 @@ LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController ); //----------------------------------------------------------------------------- void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) { -#ifndef CLIENT_DLL // Holster player's weapon if ( pPlayer->GetActiveWeapon() ) { @@ -695,11 +1001,21 @@ void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) } } - CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( pPlayer ); if ( pOwner ) { +#ifndef MAPBASE pOwner->EnableSprint( false ); +#else + if ( sv_player_enable_propsprint.GetBool() == false ) + { + pOwner->EnableSprint( false ); + } + else + { + pOwner->EnableSprint( true ); + } +#endif } // If the target is debris, convert it to non-debris @@ -717,12 +1033,38 @@ void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); Pickup_OnPhysGunPickup( pObject, m_pPlayer ); +#ifdef MAPBASE + bool bUseGrabPos = false; + Vector vecGrabPos; + if ( dynamic_cast( pObject ) ) + { + m_grabController.SetDontUseListMass( true ); + + // Approximate where we're grabbing from + vecGrabPos = pPlayer->EyePosition() + (pPlayer->EyeDirection3D() * 16.0f); + bUseGrabPos = true; + } + else + m_grabController.SetDontUseListMass( false ); + + m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vecGrabPos, bUseGrabPos ); +#else m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false ); +#endif + +#ifdef MAPBASE_MP + // The client needs to know that I'm the +USE entity for prediction + AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); + m_hAttachedObject = pObject; + + m_attachedPositionObjectSpace = m_grabController.m_attachedPositionObjectSpace; + m_attachedAnglesPlayerSpace = m_grabController.m_attachedAnglesPlayerSpace; +#endif m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; m_pPlayer->SetUseEntity( this ); -#endif } +#endif //----------------------------------------------------------------------------- @@ -741,6 +1083,9 @@ void CPlayerPickupController::Shutdown( bool bThrown ) } m_grabController.DetachEntity( bClearVelocity ); +#ifdef MAPBASE_MP + m_hAttachedObject = NULL; +#endif if ( pObject != NULL ) { @@ -750,10 +1095,17 @@ void CPlayerPickupController::Shutdown( bool bThrown ) if ( m_pPlayer ) { CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( m_pPlayer ); +#ifndef MAPBASE if ( pOwner ) { pOwner->EnableSprint( true ); } +#else + if ( pOwner && sv_player_enable_propsprint.GetBool() == false ) + { + pOwner->EnableSprint( true ); + } +#endif m_pPlayer->SetUseEntity( NULL ); if ( m_pPlayer->GetActiveWeapon() ) @@ -771,6 +1123,18 @@ void CPlayerPickupController::Shutdown( bool bThrown ) } Remove(); +#elif defined(MAPBASE_MP) + + m_grabController.DetachEntity( bThrown ); + +#ifdef MAPBASE_MP + if ( m_hAttachedObject ) + { + m_hAttachedObject->VPhysicsDestroyObject(); + m_hAttachedObject = NULL; + } +#endif + #endif } @@ -780,6 +1144,13 @@ void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller { if ( ToBasePlayer(pActivator) == m_pPlayer ) { +#if defined(CLIENT_DLL) && defined(MAPBASE_MP) + if (m_pPlayer.Get() == C_BasePlayer::GetLocalPlayer() && !m_pPlayer->IsObserver()) + ManagePredictedObject(); + else + return; +#endif + CBaseEntity *pAttached = m_grabController.GetAttached(); // UNDONE: Use vphysics stress to decide to drop objects @@ -811,16 +1182,22 @@ void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller if ( m_pPlayer->m_nButtons & IN_ATTACK ) { Shutdown( true ); +#ifndef CLIENT_DLL Vector vecLaunch; m_pPlayer->EyeVectors( &vecLaunch ); // JAY: Scale this with mass because some small objects really go flying +#ifdef MAPBASE + float massFactor = pPhys ? clamp( pPhys->GetMass(), 0.5, 15 ) : 7.5; +#else float massFactor = clamp( pPhys->GetMass(), 0.5, 15 ); +#endif 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 ); +#endif return; } @@ -832,35 +1209,106 @@ void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller } } +#if defined(MAPBASE_MP) && defined(CLIENT_DLL) //----------------------------------------------------------------------------- // Purpose: -// Input : *pEnt - -// Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- -bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt ) +void CPlayerPickupController::OnDataChanged( DataUpdateType_t type ) { - return ( m_grabController.GetAttached() == pEnt ); + BaseClass::OnDataChanged( type ); + + if ( m_pPlayer == NULL ) + { + if ( m_hAttachedObject ) + { + m_hAttachedObject->VPhysicsDestroyObject(); + } + + if ( m_hOldAttachedObject ) + { + m_hOldAttachedObject->VPhysicsDestroyObject(); + } + } } -void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject ) +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerPickupController::ManagePredictedObject( void ) { - -#ifndef CLIENT_DLL - - //Don't pick up if we don't have a phys object. - if ( pObject->VPhysicsGetObject() == NULL ) - return; + CBaseEntity *pAttachedObject = m_hAttachedObject.Get(); - CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer ); - - if ( !pController ) - return; + if ( m_hAttachedObject ) + { + // NOTE :This must happen after OnPhysGunPickup because that can change the mass + if ( pAttachedObject != GetGrabController().GetAttached() ) + { + IPhysicsObject *pPhysics = pAttachedObject->VPhysicsGetObject(); - pController->Init( pPlayer, pObject ); + if ( pPhysics == NULL ) + { + solid_t tmpSolid; + PhysModelParseSolid( tmpSolid, m_hAttachedObject, pAttachedObject->GetModelIndex() ); -#endif + pAttachedObject->VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid ); + } -} + pPhysics = pAttachedObject->VPhysicsGetObject(); + + if ( pPhysics ) + { + m_grabController.SetIgnorePitch( true ); + m_grabController.SetAngleAlignment( 0.866025403784f ); // DOT_30DEGREE from player.h (is there really no client equivalent?) + + GetGrabController().AttachEntity( m_pPlayer, 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 + +//----------------------------------------------------------------------------- +// 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 @@ -878,6 +1326,10 @@ BEGIN_NETWORK_TABLE( CWeaponPhysCannon, DT_WeaponPhysCannon ) RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[2] ) ), RecvPropInt( RECVINFO( m_EffectState ) ), RecvPropBool( RECVINFO( m_bOpen ) ), + RecvPropBool( RECVINFO( m_bIsCurrentlyUpgrading ) ), +#ifdef MAPBASE_MP + RecvPropBool( RECVINFO( m_bDontPredictAttached ) ), +#endif #else SendPropBool( SENDINFO( m_bActive ) ), SendPropEHandle( SENDINFO( m_hAttachedObject ) ), @@ -887,6 +1339,10 @@ BEGIN_NETWORK_TABLE( CWeaponPhysCannon, DT_WeaponPhysCannon ) SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 2 ), 11 ), SendPropInt( SENDINFO( m_EffectState ) ), SendPropBool( SENDINFO( m_bOpen ) ), + SendPropBool( SENDINFO( m_bIsCurrentlyUpgrading ) ), +#ifdef MAPBASE_MP + SendPropBool( SENDINFO( m_bDontPredictAttached ) ), +#endif #endif END_NETWORK_TABLE() @@ -900,21 +1356,27 @@ END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon ); PRECACHE_WEAPON_REGISTER( weapon_physcannon ); -#ifndef CLIENT_DLL - -acttable_t CWeaponPhysCannon::m_acttable[] = +#ifdef MAPBASE +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 }, -}; + { 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 }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN, false }, +#endif -IMPLEMENT_ACTTABLE(CWeaponPhysCannon); + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +}; +IMPLEMENT_ACTTABLE( CWeaponPhysCannon ); #endif @@ -934,6 +1396,19 @@ enum EFFECT_LAUNCH, }; + +//----------------------------------------------------------------------------- +// Do we have the super-phys gun? +//----------------------------------------------------------------------------- +bool PlayerHasMegaPhysCannon() +{ +#ifdef CLIENT_DLL + return false; +#else + return ( HL2GameRules()->MegaPhyscannonActive() == true ); +#endif +} + //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- @@ -965,10 +1440,36 @@ void CWeaponPhysCannon::Precache( void ) PrecacheModel( PHYSCANNON_BEAM_SPRITE_NOZ ); PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" ); + PrecacheScriptSound( "Weapon_Physgun.Off" ); + + PrecacheScriptSound( "Weapon_MegaPhysCannon.DryFire" ); + PrecacheScriptSound( "Weapon_MegaPhysCannon.Launch" ); + PrecacheScriptSound( "Weapon_MegaPhysCannon.Pickup"); + PrecacheScriptSound( "Weapon_MegaPhysCannon.Drop"); + PrecacheScriptSound( "Weapon_MegaPhysCannon.HoldSound"); + PrecacheScriptSound( "Weapon_MegaPhysCannon.ChargeZap"); BaseClass::Precache(); } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::Spawn( void ) +{ + BaseClass::Spawn(); + + m_bPhyscannonState = IsMegaPhysCannon(); + + // The megacannon uses a different skin + if ( IsMegaPhysCannon() ) + { + m_nSkin = MEGACANNON_SKIN; + } +} + + //----------------------------------------------------------------------------- // Purpose: Restore //----------------------------------------------------------------------------- @@ -977,6 +1478,8 @@ void CWeaponPhysCannon::OnRestore() BaseClass::OnRestore(); m_grabController.OnRestore(); + m_bPhyscannonState = IsMegaPhysCannon(); + // Tracker 8106: Physcannon effects disappear through level transition, so // just recreate any effects here if ( m_EffectState != EFFECT_NONE ) @@ -985,7 +1488,6 @@ void CWeaponPhysCannon::OnRestore() } } - //----------------------------------------------------------------------------- // On Remove //----------------------------------------------------------------------------- @@ -1050,7 +1552,7 @@ void CWeaponPhysCannon::OnDataChanged( DataUpdateType_t type ) //----------------------------------------------------------------------------- inline float CWeaponPhysCannon::SpriteScaleFactor() { - return 1.0f; + return IsMegaPhysCannon() ? 1.5f : 1.0f; } @@ -1082,7 +1584,21 @@ bool CWeaponPhysCannon::Deploy( void ) //----------------------------------------------------------------------------- void CWeaponPhysCannon::SetViewModel( void ) { - BaseClass::SetViewModel(); + if ( !IsMegaPhysCannon() ) + { + BaseClass::SetViewModel(); + return; + } + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner == NULL ) + return; + + CBaseViewModel *vm = pOwner->GetViewModel( m_nViewModelIndex ); + if ( vm == NULL ) + return; + + vm->SetWeaponModel( MEGACANNON_MODEL, this ); } //----------------------------------------------------------------------------- @@ -1168,6 +1684,14 @@ void CWeaponPhysCannon::DryFire( void ) SendWeaponAnim( ACT_VM_PRIMARYATTACK ); WeaponSound( EMPTY ); + + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( pOwner ) + { +#ifdef MAPBASE // TODO: Is this animation too dramatic? + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + } } //----------------------------------------------------------------------------- @@ -1227,6 +1751,11 @@ void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &for PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (pPlayer) + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; @@ -1250,6 +1779,29 @@ void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlay pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); } + float mass = 0.0f; + if( pEntity->VPhysicsGetObject() ) + { + mass = pEntity->VPhysicsGetObject()->GetMass(); + } + +#ifdef HL2_EPISODIC + // Warn Alyx if the player is punting a car around. + if( hl2_episodic.GetBool() && mass > 250.0f ) + { + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + int nAIs = g_AI_Manager.NumAIs(); + + for ( int i = 0; i < nAIs; i++ ) + { + if( ppAIs[ i ]->Classify() == CLASS_PLAYER_ALLY_VITAL ) + { + ppAIs[ i ]->DispatchInteraction( g_interactionPlayerPuntedHeavyObject, pEntity, pOwner ); + } + } + } +#endif + Pickup_OnPhysGunPickup( pEntity, pOwner, reason ); } #endif @@ -1261,7 +1813,6 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime ) return; @@ -1313,7 +1864,7 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor forward.Init(); } - if ( !Pickup_ShouldPuntUseLaunchForces( pEntity, PHYSGUN_FORCE_PUNTED ) ) + if ( !IsMegaPhysCannon() && !Pickup_ShouldPuntUseLaunchForces( pEntity, PHYSGUN_FORCE_PUNTED ) ) { int i; @@ -1353,7 +1904,7 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor } else { - ApplyVelocityBasedForce( pEntity, vecForward ); + ApplyVelocityBasedForce( pEntity, vecForward, PHYSGUN_FORCE_PUNTED ); } } } @@ -1369,6 +1920,10 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; @@ -1388,13 +1943,40 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor // ASSUMES: that pEntity is a vphysics entity. // Input : - //----------------------------------------------------------------------------- +#ifdef CLIENT_DLL void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ) +#else +void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward, PhysGunForce_t reason ) +#endif { #ifndef CLIENT_DLL + // Get the launch velocity + Vector vVel = Pickup_PhysGunLaunchVelocity( pEntity, forward, reason ); + + // Get the launch angular impulse + AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, reason ); + + // Get the physics object (MUST have one) IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); - Assert(pPhysicsObject); // Shouldn't ever get here with a non-vphysics object. - if (!pPhysicsObject) + if ( pPhysicsObject == NULL ) + { + Assert( 0 ); + return; + } + + CRagdollProp *pRagdoll = dynamic_cast( pEntity ); + if ( pRagdoll != NULL ) + { + Vector vTempVel; + AngularImpulse vTempAVel; + + ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); + for ( int j = 0; j < pRagdollPhys->listCount; ++j ) + { + pRagdollPhys->list[j].pObject->AddVelocity( &vVel, &aVel ); + } return; + } float flForceMax = physcannon_maxforce.GetFloat(); float flForce = flForceMax; @@ -1407,9 +1989,9 @@ void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vec flForce = SimpleSplineRemapVal(mass, 100, 600, flForceMax, flForceMin); } - Vector vVel = forward * flForce; + //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 ); + //AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, PHYSGUN_FORCE_PUNTED ); pPhysicsObject->AddVelocity( &vVel, &aVel ); @@ -1418,12 +2000,132 @@ void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vec } +//----------------------------------------------------------------------------- +// Punt non-physics +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::PuntRagdoll( 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 + Pickup_OnPhysGunDrop( pEntity, pOwner, LAUNCHED_BY_CANNON ); + + 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 ) ) + { + Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ); + + if( forward.z < 0 ) + { + //reflect, but flatten the trajectory out a bit so it's easier to hit standing targets + forward.z *= -0.65f; + } + + Vector vVel = forward * 1500; + AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, PHYSGUN_FORCE_PUNTED ); + + CRagdollProp *pRagdoll = dynamic_cast( pEntity ); + ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); + + int j; + for ( j = 0; j < pRagdollPhys->listCount; ++j ) + { + pRagdollPhys->list[j].pObject->AddVelocity( &vVel, NULL ); + } + } +#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 ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + + 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; +} + + //----------------------------------------------------------------------------- // Trace length //----------------------------------------------------------------------------- float CWeaponPhysCannon::TraceLength() { - return physcannon_tracelength.GetFloat(); + if ( !IsMegaPhysCannon() ) + { + return physcannon_tracelength.GetFloat(); + } + + return physcannon_mega_tracelength.GetFloat(); +} + +//----------------------------------------------------------------------------- +// If there's any special rejection code you need to do per entity then do it here +// This is kinda nasty but I'd hate to move more physcannon related stuff into CBaseEntity +//----------------------------------------------------------------------------- +bool CWeaponPhysCannon::EntityAllowsPunts( CBaseEntity *pEntity ) +{ +#ifndef CLIENT_DLL + if ( pEntity->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) ) + { + CPhysBox *pPhysBox = dynamic_cast(pEntity); + + if ( pPhysBox != NULL ) + { + if ( pPhysBox->HasSpawnFlags( SF_PHYSBOX_NEVER_PUNT ) ) + { + return false; + } + } + } + + if ( pEntity->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) + { +#ifdef MAPBASE + if (pEntity->IsBaseCombatWeapon() || pEntity->IsCombatItem()) + return false; +#else + CBaseCombatWeapon *pWeapon = dynamic_cast(pEntity); + + if ( pWeapon != NULL ) + { + if ( pWeapon->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) + { + return false; + } + } +#endif + } +#endif + + return true; } @@ -1469,6 +2171,9 @@ void CWeaponPhysCannon::PrimaryAttack( void ) PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif return; } @@ -1487,7 +2192,7 @@ void CWeaponPhysCannon::PrimaryAttack( void ) 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 ); + UTIL_PhyscannonTraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), pOwner, &tr ); bool bValid = true; CBaseEntity *pEntity = tr.m_pEnt; if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) @@ -1502,15 +2207,40 @@ void CWeaponPhysCannon::PrimaryAttack( void ) // If the entity we've hit is invalid, try a traceline instead if ( !bValid ) { - UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + UTIL_PhyscannonTraceLine( start, end, pOwner, &tr ); if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) { - // Play dry-fire sequence - DryFire(); - return; +#ifdef HL2_EPISODIC + if( hl2_episodic.GetBool() ) + { + // Try to find something in a very small cone. + CBaseEntity *pObject = FindObjectInCone( start, forward, physcannon_punt_cone.GetFloat() ); + + if( pObject ) + { + // Trace to the object. + UTIL_PhyscannonTraceLine( start, pObject->WorldSpaceCenter(), pOwner, &tr ); + + if( tr.m_pEnt && tr.m_pEnt == pObject && !(pObject->IsEFlagSet(EFL_NO_PHYSCANNON_INTERACTION)) ) + { + bValid = true; + pEntity = pObject; + } + } + } +#endif } + else + { + bValid = true; + pEntity = tr.m_pEnt; + } + } - pEntity = tr.m_pEnt; + if( !bValid ) + { + DryFire(); + return; } // See if we hit something @@ -1522,26 +2252,72 @@ void CWeaponPhysCannon::PrimaryAttack( void ) return; } - if( GetOwner()->IsPlayer() ) + if( GetOwner()->IsPlayer() && !IsMegaPhysCannon() ) { // Don't let the player zap any NPC's except regular antlions and headcrabs. - if( pEntity->IsPlayer() ) +#ifdef CLIENT_DLL + if( pEntity->IsPlayer() || pEntity->IsNPC() ) +#else + if( pEntity->IsPlayer() || (pEntity->IsNPC() && pEntity->Classify() != CLASS_HEADCRAB && !FClassnameIs(pEntity, "npc_antlion")) ) +#endif { DryFire(); return; } } +#ifndef CLIENT_DLL + if ( IsMegaPhysCannon() ) + { + if ( pEntity->IsNPC() && !pEntity->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) && pEntity->MyNPCPointer()->CanBecomeRagdoll() ) + { + CTakeDamageInfo info( pOwner, pOwner, 1.0f, DMG_GENERIC ); + CBaseEntity *pRagdoll = CreateServerRagdoll( pEntity->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); + pRagdoll->SetCollisionBounds( pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs() ); + + // Necessary to cause it to do the appropriate death cleanup + CTakeDamageInfo ragdollInfo( pOwner, pOwner, 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); + pEntity->TakeDamage( ragdollInfo ); + + PuntRagdoll( pRagdoll, forward, tr ); + return; + } + } +#endif + PuntNonVPhysics( pEntity, forward, tr ); } else { - if ( pEntity->VPhysicsIsFlesh( ) ) + if ( EntityAllowsPunts( pEntity) == false ) { DryFire(); return; } - PuntVPhysics( pEntity, forward, tr ); + + if ( !IsMegaPhysCannon() ) + { + if ( pEntity->VPhysicsIsFlesh( ) ) + { + DryFire(); + return; + } + PuntVPhysics( pEntity, forward, tr ); + } + else + { +#ifndef CLIENT_DLL + if ( dynamic_cast(pEntity) ) + { + PuntRagdoll( pEntity, forward, tr ); + } + else +#endif + { + PuntVPhysics( pEntity, forward, tr ); + } + } } } @@ -1646,6 +2422,30 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit m_grabController.SetIgnorePitch( false ); m_grabController.SetAngleAlignment( 0 ); + bool bKilledByGrab = false; + + bool bIsMegaPhysCannon = IsMegaPhysCannon(); + if ( bIsMegaPhysCannon ) + { + if ( pObject->IsNPC() && !pObject->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) ) + { + Assert( pObject->MyNPCPointer()->CanBecomeRagdoll() ); + CTakeDamageInfo info( GetOwner(), GetOwner(), 1.0f, DMG_GENERIC ); + CBaseEntity *pRagdoll = CreateServerRagdoll( pObject->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); + + pRagdoll->SetCollisionBounds( pObject->CollisionProp()->OBBMins(), pObject->CollisionProp()->OBBMaxs() ); + + // Necessary to cause it to do the appropriate death cleanup + CTakeDamageInfo ragdollInfo( GetOwner(), GetOwner(), 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); + pObject->TakeDamage( ragdollInfo ); + + // Now we act on the ragdoll for the remainder of the time + pObject = pRagdoll; + bKilledByGrab = true; + } + } + IPhysicsObject *pPhysics = pObject->VPhysicsGetObject(); // Must be valid @@ -1657,12 +2457,21 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit m_bActive = true; if( pOwner ) { +#ifdef HL2_EPISODIC + CBreakableProp *pProp = dynamic_cast< CBreakableProp * >( pObject ); + + if ( pProp && pProp->HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) ) + { + pOwner->FlashlightTurnOff(); + } +#endif + // 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_grabController.AttachEntity( pOwner, pObject, pPhysics, bIsMegaPhysCannon, vPosition, (!bKilledByGrab) ); m_hAttachedObject = pObject; m_attachedPositionObjectSpace = m_grabController.m_attachedPositionObjectSpace; m_attachedAnglesPlayerSpace = m_grabController.m_attachedAnglesPlayerSpace; @@ -1675,7 +2484,16 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit m_bResetOwnerEntity = true; } -/* if( pOwner ) +#ifdef MAPBASE_MP + m_bDontPredictAttached = false; + + // HACKHACK + if ( m_hAttachedObject->ClassMatches( "combine_mine" ) ) + { + m_bDontPredictAttached = true; + } + + if( pOwner && sv_player_enable_gravgun_sprint.GetBool() == false ) { pOwner->EnableSprint( false ); @@ -1684,7 +2502,8 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); pOwner->SetMaxSpeed( maxSpeed ); - }*/ + } +#endif // Don't drop again for a slight delay, in case they were pulling objects near them m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f; @@ -1695,14 +2514,8 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit return true; } -CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) +void CWeaponPhysCannon::FindObjectTrace( CBasePlayer *pPlayer, trace_t *pTraceResult ) { - CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); - - Assert( pPlayer ); - if ( pPlayer == NULL ) - return OBJECT_NOT_FOUND; - Vector forward; pPlayer->EyeVectors( &forward ); @@ -1711,17 +2524,33 @@ CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) float testLength = TraceLength() * 4.0f; Vector end = start + forward * testLength; + if( IsMegaPhysCannon() && hl2_episodic.GetBool() ) + { + Vector vecAutoAimDir = pPlayer->GetAutoaimVector( 1.0f, testLength ); + end = start + vecAutoAimDir * 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 ); - + UTIL_PhyscannonTraceLine( start, end, pPlayer, pTraceResult ); + // Try again with a hull trace - if ( ( tr.fraction == 1.0 ) || ( tr.m_pEnt == NULL ) || ( tr.m_pEnt->IsWorld() ) ) + if ( !pTraceResult->DidHitNonWorldEntity() ) { - UTIL_TraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, &filter, &tr ); + UTIL_PhyscannonTraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), pPlayer, pTraceResult ); } +} + +CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) +{ + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + + Assert( pPlayer ); + if ( pPlayer == NULL ) + return OBJECT_NOT_FOUND; + + trace_t tr; + FindObjectTrace( pPlayer, &tr ); CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL; bool bAttach = false; bool bPull = false; @@ -1736,16 +2565,30 @@ CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) } else if ( tr.fraction > 0.25f ) { - bPull = true; + bPull = true; + } + } + + Vector forward; + pPlayer->EyeVectors( &forward ); + + // Setup our positions + Vector start = pPlayer->Weapon_ShootPosition(); + float testLength = TraceLength() * 4.0f; + + // Find anything within a general cone in front + CBaseEntity *pConeEntity = NULL; + if ( !IsMegaPhysCannon() ) + { + if (!bAttach && !bPull) + { + pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() ); } } - - // Find anything within a general cone in front - CBaseEntity *pConeEntity = NULL; - - if (!bAttach && !bPull) + else { - pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() ); + pConeEntity = MegaPhysCannonFindObjectInCone( start, forward, + physcannon_cone.GetFloat(), physcannon_ball_cone.GetFloat(), bAttach || bPull ); } if ( pConeEntity ) @@ -1765,14 +2608,23 @@ CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) if ( CanPickupObject( pEntity ) == false ) { - // Make a noise to signify we can't pick this up - if ( !m_flLastDenySoundPlayed ) + CBaseEntity *pNewObject = Pickup_OnFailedPhysGunPickup( pEntity, start ); + + if ( pNewObject && CanPickupObject( pNewObject ) ) { - m_flLastDenySoundPlayed = true; - WeaponSound( SPECIAL3 ); + pEntity = pNewObject; } + else + { + // Make a noise to signify we can't pick this up + if ( !m_flLastDenySoundPlayed ) + { + m_flLastDenySoundPlayed = true; + WeaponSound( SPECIAL3 ); + } - return OBJECT_NOT_FOUND; + return OBJECT_NOT_FOUND; + } } // Check to see if the object is constrained + needs to be ripped off... @@ -1796,7 +2648,7 @@ CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) // If we're too far, simply start to pull the object towards us Vector pullDir = start - pEntity->WorldSpaceCenter(); VectorNormalize( pullDir ); - pullDir *= physcannon_pullforce.GetFloat(); + pullDir *= IsMegaPhysCannon() ? physcannon_mega_pullforce.GetFloat() : physcannon_pullforce.GetFloat(); float mass = PhysGetEntityMass( pEntity ); if ( mass < 50.0f ) @@ -1804,18 +2656,94 @@ CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) pullDir *= (mass + 0.5) * (1/50.0f); } + CPhysicsProp* pProp = dynamic_cast(pEntity); + if (pProp) { + pProp->OnPhysGunPull( pOwner ); + } + // Nudge it towards us pObj->ApplyForceCenter( pullDir ); return OBJECT_NOT_FOUND; } + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CBaseEntity *CWeaponPhysCannon::MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, + const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls ) +{ + // Find the nearest physics-based item in a cone in front of me. + CBaseEntity *list[1024]; + float flMaxDist = TraceLength() + 1.0; + float flNearestDist = flMaxDist; + bool bNearestIsCombineBall = bOnlyCombineBalls ? true : false; + Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); + Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); + + CBaseEntity *pNearest = NULL; + + int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 ); + for( int i = 0 ; i < count ; i++ ) + { + if ( !list[ i ]->VPhysicsGetObject() ) + continue; + + bool bIsCombineBall = FClassnameIs( list[ i ], "prop_combine_ball" ); + if ( !bIsCombineBall && bNearestIsCombineBall ) + continue; + + // Closer than other objects + Vector los; + VectorSubtract( list[ i ]->WorldSpaceCenter(), vecOrigin, los ); + float flDist = VectorNormalize( los ); + + if ( !bIsCombineBall || bNearestIsCombineBall ) + { + // Closer than other objects + if( flDist >= flNearestDist ) + continue; + + // Cull to the cone + if ( DotProduct( los, vecDir ) <= flCone ) + continue; + } + else + { + // Close enough? + if ( flDist >= flMaxDist ) + continue; + + // Cull to the cone + if ( DotProduct( los, vecDir ) <= flCone ) + continue; + + // NOW: If it's either closer than nearest dist or within the ball cone, use it! + if ( (flDist > flNearestDist) && (DotProduct( los, vecDir ) <= flCombineBallCone) ) + continue; + } + + // Make sure it isn't occluded! + trace_t tr; + UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr ); + if( tr.m_pEnt == list[ i ] ) + { + flNearestDist = flDist; + pNearest = list[ i ]; + bNearestIsCombineBall = bIsCombineBall; + } + } + + return pNearest; +} + + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- 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; + float flNearestDist = physcannon_tracelength.GetFloat() + 1.0; //Use regular distance. Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist ); Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist ); @@ -1839,8 +2767,7 @@ CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const // 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 ); + UTIL_PhyscannonTraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), GetOwner(), &tr ); if( tr.m_pEnt == list[ i ] ) { flNearestDist = flDist; @@ -1886,6 +2813,18 @@ bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) playerAngles.x = clamp( pitch, -75, 75 ); AngleVectors( playerAngles, &forward, &right, &up ); + if ( HL2MPRules()->MegaPhyscannonActive() ) + { + Vector los = ( pEntity->WorldSpaceCenter() - pPlayer->Weapon_ShootPosition() ); + VectorNormalize( los ); + + float flDot = DotProduct( los, forward ); + + //Let go of the item if we turn around too fast. + if ( flDot <= 0.35f ) + return false; + } + // 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(); @@ -1896,6 +2835,9 @@ bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError ) float distance = 24 + ( radius * 2.0f ); + // Add the prop's distance offset + distance += m_flDistanceOffset; + Vector start = pPlayer->Weapon_ShootPosition(); Vector end = start + ( forward * distance ); @@ -1987,7 +2929,7 @@ void CWeaponPhysCannon::UpdateObject( void ) CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); Assert( pPlayer ); - float flError = 12; + float flError = IsMegaPhysCannon() ? 18 : 12; if ( !m_grabController.UpdateObject( pPlayer, flError ) ) { DetachObject(); @@ -2009,8 +2951,17 @@ void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() ); if( pOwner != NULL ) { +#ifndef MAPBASE pOwner->EnableSprint( true ); pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); + +#else + if (sv_player_enable_gravgun_sprint.GetBool() == false) + { + pOwner->EnableSprint( true ); + pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); + } +#endif } CBaseEntity *pObject = m_grabController.GetAttached(); @@ -2059,6 +3010,11 @@ void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) #ifdef CLIENT_DLL void CWeaponPhysCannon::ManagePredictedObject( void ) { +#ifdef MAPBASE_MP + if ( m_bDontPredictAttached ) + return; +#endif + CBaseEntity *pAttachedObject = m_hAttachedObject.Get(); if ( m_hAttachedObject ) @@ -2194,15 +3150,8 @@ void CWeaponPhysCannon::CheckForTarget( void ) 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 ); + FindObjectTrace( pOwner, &tr ); if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) ) { @@ -2226,6 +3175,79 @@ void CWeaponPhysCannon::CheckForTarget( void ) } +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Begin upgrading! +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::BeginUpgrade() +{ + if ( IsMegaPhysCannon() ) + return; + + if ( m_bIsCurrentlyUpgrading ) + return; + + SetSequence( SelectWeightedSequence( ACT_PHYSCANNON_UPGRADE ) ); + ResetSequenceInfo(); + + m_bIsCurrentlyUpgrading = true; + + SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 6.0f, s_pWaitForUpgradeContext ); + + EmitSound( "WeaponDissolve.Charge" ); + + // Bloat our bounds + CollisionProp()->UseTriggerBounds( true, 32.0f ); + + // Turn on the new skin + m_nSkin = MEGACANNON_SKIN; +} + + +//----------------------------------------------------------------------------- +// Wait until we're done upgrading +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::WaitForUpgradeThink() +{ + Assert( m_bIsCurrentlyUpgrading ); + + StudioFrameAdvance(); + if ( !IsActivityFinished() ) + { + SetContextThink( &CWeaponPhysCannon::WaitForUpgradeThink, gpGlobals->curtime + 0.1f, s_pWaitForUpgradeContext ); + return; + } + + if ( !GlobalEntity_IsInTable( "super_phys_gun" ) ) + { + GlobalEntity_Add( MAKE_STRING("super_phys_gun"), gpGlobals->mapname, GLOBAL_ON ); + } + else + { + GlobalEntity_SetState( MAKE_STRING("super_phys_gun"), GLOBAL_ON ); + } + m_bIsCurrentlyUpgrading = false; + + // This is necessary to get the effects to look different + DestroyEffects(); + + // HACK: Hacky notification back to the level that we've finish upgrading + CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, "script_physcannon_upgrade" ); + if ( pEnt ) + { + variant_t emptyVariant; + pEnt->AcceptInput( "Trigger", this, this, emptyVariant, 0 ); + } + + StopSound( "WeaponDissolve.Charge" ); + + // Re-enable weapon pickup + AddSolidFlags( FSOLID_TRIGGER ); + + SetContextThink( NULL, gpGlobals->curtime, s_pWaitForUpgradeContext ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Idle effect (pulsing) //----------------------------------------------------------------------------- @@ -2300,6 +3322,15 @@ void CWeaponPhysCannon::ItemPostFrame() return; } +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + m_nAttack2Debounce = 0; + WeaponIdle(); + return; + } +#endif + //Check for object in pickup range if ( m_bActive == false ) { @@ -2350,6 +3381,17 @@ void CWeaponPhysCannon::ItemPostFrame() { WeaponIdle(); } + + if ( hl2_episodic.GetBool() == true ) + { + if ( IsMegaPhysCannon() ) + { + if ( !( pOwner->m_nButtons & IN_ATTACK ) ) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + } + } } @@ -2372,7 +3414,11 @@ void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce ) m_flRepuntObjectTime = gpGlobals->curtime + 0.5f; // Launch +#ifdef CLIENT_DLL ApplyVelocityBasedForce( pObject, vecDir ); +#else + ApplyVelocityBasedForce( pObject, vecDir, PHYSGUN_FORCE_LAUNCHED ); +#endif // Don't allow the gun to regrab a thrown object!! m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5; @@ -2419,17 +3465,44 @@ bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() ) return false; + if ( pTarget->HasSpawnFlags( SF_PHYSBOX_ALWAYS_PICK_UP ) || pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) ) + { + // It may seem strange to check this spawnflag before we know the class of this object, since the + // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number + // of irrelevant entities that fall through to this next casting check, which is slower. + CPhysBox *pPhysBox = dynamic_cast(pTarget); + + if ( pPhysBox != NULL ) + { + if ( pTarget->HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) ) + return false; + else + return true; + } + } + + if ( pTarget->HasSpawnFlags(SF_PHYSPROP_ALWAYS_PICK_UP) ) + { + // It may seem strange to check this spawnflag before we know the class of this object, since the + // spawnflag only applies to func_physbox, but it can act as a filter of sorts to reduce the number + // of irrelevant entities that fall through to this next casting check, which is slower. + CPhysicsProp *pPhysProp = dynamic_cast(pTarget); + if ( pPhysProp != NULL ) + return true; + } + if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) ) return false; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); - if ( pOwner && pOwner->GetGroundEntity() == pTarget ) return false; - if ( pTarget->VPhysicsIsFlesh( ) ) +#ifdef MAPBASE + // The gravity gun can't pick up vehicles. + if ( pTarget->GetServerVehicle() ) return false; - +#endif IPhysicsObject *pObj = pTarget->VPhysicsGetObject(); if ( pObj && pObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) @@ -2440,7 +3513,20 @@ bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) return CBasePlayer::CanPickupObject( pTarget, 0, 0 ); } - return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 ); + if ( !IsMegaPhysCannon() ) + { + if ( pTarget->VPhysicsIsFlesh( ) ) + return false; + return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 ); + } + + if ( pTarget->IsNPC() && pTarget->MyNPCPointer()->CanBecomeRagdoll() ) + return true; + + if ( dynamic_cast(pTarget) ) + return true; + + return CBasePlayer::CanPickupObject( pTarget, 0, 0 ); #else return false; #endif @@ -2480,6 +3566,13 @@ void CWeaponPhysCannon::OpenElements( void ) //----------------------------------------------------------------------------- void CWeaponPhysCannon::CloseElements( void ) { + // The mega cannon cannot be closed! + if ( IsMegaPhysCannon() ) + { + OpenElements(); + return; + } + if ( m_bOpen == false ) return; @@ -2537,7 +3630,14 @@ CSoundPatch *CWeaponPhysCannon::GetMotorSound( void ) //CPASAttenuationFilter filter( this ); CLocalPlayerFilter filter; - m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM ); + if ( IsMegaPhysCannon() ) + { + m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_MegaPhysCannon.HoldSound", ATTN_NORM ); + } + else + { + m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM ); + } } #endif @@ -2718,7 +3818,6 @@ void CWeaponPhysCannon::StartEffects( void ) } #endif - } //----------------------------------------------------------------------------- @@ -2726,7 +3825,6 @@ void CWeaponPhysCannon::StartEffects( void ) //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectClosed( void ) { - #ifdef CLIENT_DLL // Turn off the end-caps @@ -2736,7 +3834,14 @@ void CWeaponPhysCannon::DoEffectClosed( void ) } #endif +} +//----------------------------------------------------------------------------- +// Closing effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectClosed( void ) +{ + // TODO } //----------------------------------------------------------------------------- @@ -2744,7 +3849,6 @@ void CWeaponPhysCannon::DoEffectClosed( void ) //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectReady( void ) { - #ifdef CLIENT_DLL // Special POV case @@ -2785,7 +3889,6 @@ void CWeaponPhysCannon::DoEffectReady( void ) } #endif - } @@ -2794,7 +3897,6 @@ void CWeaponPhysCannon::DoEffectReady( void ) //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffectHolding( void ) { - #ifdef CLIENT_DLL if ( ShouldDrawUsingViewModel() ) @@ -2880,7 +3982,6 @@ void CWeaponPhysCannon::DoEffectHolding( void ) } #endif - } @@ -2957,7 +4058,31 @@ void CWeaponPhysCannon::DoEffectLaunch( Vector *pos ) m_Parameters[PHYSCANNON_BLAST].SetVisible(); #endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pos - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectLaunch( Vector *pos ) +{ + // TODO +} + +//----------------------------------------------------------------------------- +// Holding effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectHolding( void ) +{ + // TODO +} +//----------------------------------------------------------------------------- +// Ready effects +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffectReady( void ) +{ + // TODO } //----------------------------------------------------------------------------- @@ -2993,12 +4118,46 @@ void CWeaponPhysCannon::DoEffectNone( void ) #endif } +//----------------------------------------------------------------------------- +// Purpose: +// Input : effectType - +// *pos - +//----------------------------------------------------------------------------- +void CWeaponPhysCannon::DoMegaEffect( int effectType, Vector *pos ) +{ + switch( effectType ) + { + case EFFECT_CLOSED: + DoMegaEffectClosed(); + break; + + case EFFECT_READY: + DoMegaEffectReady(); + break; + + case EFFECT_HOLDING: + DoMegaEffectHolding(); + break; + + case EFFECT_LAUNCH: + DoMegaEffectLaunch( pos ); + break; + + default: + case EFFECT_NONE: + break; + } +} + //----------------------------------------------------------------------------- // Purpose: // Input : effectType - //----------------------------------------------------------------------------- void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) { + // Make sure we're active + StartEffects(); + m_EffectState = effectType; #ifdef CLIENT_DLL @@ -3006,6 +4165,13 @@ void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) m_nOldEffectState = m_EffectState; #endif + // Do different effects when upgraded + if ( IsMegaPhysCannon() ) + { + DoMegaEffect( effectType, pos ); + return; + } + switch( effectType ) { case EFFECT_CLOSED: @@ -3038,6 +4204,33 @@ void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos ) //----------------------------------------------------------------------------- const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const { + // Just do this normally if we're a normal physcannon + if ( PlayerHasMegaPhysCannon() == false ) + return BaseClass::GetShootSound( iIndex ); + + // We override this if we're the charged up version + switch( iIndex ) + { + case EMPTY: + return "Weapon_MegaPhysCannon.DryFire"; + break; + + case SINGLE: + return "Weapon_MegaPhysCannon.Launch"; + break; + + case SPECIAL1: + return "Weapon_MegaPhysCannon.Pickup"; + break; + + case MELEE_MISS: + return "Weapon_MegaPhysCannon.Drop"; + break; + + default: + break; + } + return BaseClass::GetShootSound( iIndex ); } @@ -3131,6 +4324,17 @@ void CWeaponPhysCannon::DrawEffectSprite( EffectType_t effectID ) //----------------------------------------------------------------------------- void CWeaponPhysCannon::DrawEffects( void ) { +#ifdef MAPBASE + if ( !ShouldDrawUsingViewModel() ) + { + // Don't duplicate the effects + // (this can happen when drawing externally) + C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( pPlayer && pPlayer->IsLocalPlayer() && pPlayer->InFirstPersonView()) + return; + } +#endif + // Draw the core effects DrawEffectSprite( PHYSCANNON_CORE ); DrawEffectSprite( PHYSCANNON_BLAST ); @@ -3218,6 +4422,15 @@ void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIf } } +#ifndef CLIENT_DLL +void PhysCannonBeginUpgrade( CBaseAnimating *pAnim ) +{ + CWeaponPhysCannon *pWeaponPhyscannon = assert_cast< CWeaponPhysCannon* >( pAnim ); + pWeaponPhyscannon->BeginUpgrade(); +} +#endif + + bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity ) { CPlayerPickupController *pController = dynamic_cast(pPickupControllerEntity); @@ -3264,6 +4477,11 @@ CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer ) return pObject; } +bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject ) +{ + return false; +} + float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject ) { float mass = 0.0f; diff --git a/src/game/shared/hl2mp/weapon_physcannon.h b/src/game/shared/hl2mp/weapon_physcannon.h index 4169a61ff70..92c49c2e903 100644 --- a/src/game/shared/hl2mp/weapon_physcannon.h +++ b/src/game/shared/hl2mp/weapon_physcannon.h @@ -67,6 +67,17 @@ struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t #define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1" #define PHYSCANNON_CENTER_GLOW "sprites/orangecore1" #define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2" + +#define MEGACANNON_BEAM_SPRITE "sprites/lgtning_noz.vmt" +#define MEGACANNON_GLOW_SPRITE "sprites/blueflare1_noz.vmt" +#define MEGACANNON_ENDCAP_SPRITE "sprites/blueflare1_noz.vmt" +#define MEGACANNON_CENTER_GLOW "effects/fluttercore.vmt" +#define MEGACANNON_BLAST_SPRITE "effects/fluttercore.vmt" + +#define MEGACANNON_RAGDOLL_BOOGIE_SPRITE "sprites/lgtning_noz.vmt" + +#define MEGACANNON_MODEL "models/weapons/v_superphyscannon.mdl" +#define MEGACANNON_SKIN 1 #ifdef CLIENT_DLL @@ -213,6 +224,9 @@ class CGrabController : public IMotionEvent float GetLoadWeight( void ) const { return m_flLoadWeight; } void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } +#ifdef MAPBASE + void SetDontUseListMass( bool bDontUse ) { m_bDontUseListMass = bDontUse; } +#endif QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); @@ -243,6 +257,13 @@ class CGrabController : public IMotionEvent EHANDLE m_attachedEntity; QAngle m_vecPreferredCarryAngles; bool m_bHasPreferredCarryAngles; + float m_flDistanceOffset; + +#ifdef MAPBASE + // Prevents using the added mass of every part of the object + // (not saved due to only being used upon attach) + bool m_bDontUseListMass; +#endif IPhysicsMotionController *m_controller; @@ -274,6 +295,7 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon void Drop( const Vector &vecVelocity ); void Precache(); + virtual void Spawn(); virtual void OnRestore(); virtual void StopLoopingSounds(); virtual void UpdateOnRemove(void); @@ -292,6 +314,10 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon bool Deploy( void ); bool HasAnyAmmo( void ) { return true; } + + void InputBecomeMegaCannon( inputdata_t &inputdata ); + + void BeginUpgrade(); virtual void SetViewModel( void ); virtual const char *GetShootSound( int iIndex ) const; @@ -317,6 +343,7 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon OBJECT_BEING_DETACHED, }; + void DoMegaEffect( int effectType, Vector *pos = NULL ); void DoEffect( int effectType, Vector *pos = NULL ); void OpenElements( void ); @@ -328,6 +355,8 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon #ifndef CLIENT_DLL bool AttachObject( CBaseEntity *pObject, const Vector &vPosition ); FindObjectResult_t FindObject( void ); + void FindObjectTrace( CBasePlayer *pPlayer, trace_t *pTraceResult ); + CBaseEntity *MegaPhysCannonFindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone, float flCombineBallCone, bool bOnlyCombineBalls ); CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone ); #endif // !CLIENT_DLL @@ -341,20 +370,39 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon // 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 ); + void PuntRagdoll( CBaseEntity *pEntity, const Vector &forward, trace_t &tr ); // Velocity-based throw common to punt and launch code. +#ifdef CLIENT_DLL void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward ); +#else + void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward, PhysGunForce_t reason = PHYSGUN_FORCE_LAUNCHED ); +#endif // Physgun effects - void DoEffectClosed( void ); + void DoEffectClosed( void ); + void DoMegaEffectClosed( void ); + void DoEffectReady( void ); + void DoMegaEffectReady( void ); + + void DoMegaEffectHolding( void ); void DoEffectHolding( void ); + + void DoMegaEffectLaunch( Vector *pos ); void DoEffectLaunch( Vector *pos ); + void DoEffectNone( void ); - void DoEffectIdle( void ); + void DoEffectIdle( void ); // Trace length float TraceLength(); + + // Do we have the super-phys gun? + inline bool IsMegaPhysCannon() + { + return PlayerHasMegaPhysCannon(); + } // Sprite scale factor float SpriteScaleFactor(); @@ -368,6 +416,9 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon #ifndef CLIENT_DLL // What happens when the physgun picks up something void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason ); + + // Wait until we're done upgrading + void WaitForUpgradeThink(); #endif // !CLIENT_DLL #ifdef CLIENT_DLL @@ -424,6 +475,8 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon #endif // CLIENT_DLL + bool EntityAllowsPunts( CBaseEntity *pEntity ); + 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 @@ -432,6 +485,10 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon CNetworkVar( bool, m_bActive ); CNetworkVar( int, m_EffectState ); // Current state of the effects on the gun CNetworkVar( bool, m_bOpen ); + CNetworkVar( bool, m_bIsCurrentlyUpgrading ); +#ifdef MAPBASE_MP + CNetworkVar( bool, m_bDontPredictAttached ); +#endif bool m_bResetOwnerEntity; @@ -441,15 +498,15 @@ class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon CGrabController m_grabController; + bool m_bPhyscannonState; + float m_flRepuntObjectTime; EHANDLE m_hLastPuntedObject; private: CWeaponPhysCannon( const CWeaponPhysCannon & ); -#ifndef CLIENT_DLL DECLARE_ACTTABLE(); -#endif }; #endif // WEAPON_PHYSCANNON_H From db45486a84252b351fbde1361bdee11f7710022d Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 27 Aug 2025 11:09:47 -0500 Subject: [PATCH 11/14] Add npc_citizen MP squad support --- src/game/server/ai_basenpc.cpp | 30 ++- src/game/server/ai_basenpc.h | 6 + src/game/server/ai_playerally.cpp | 4 + src/game/server/hl2/hl2_player.cpp | 41 +++- src/game/server/hl2/hl2_player.h | 4 + src/game/server/hl2/npc_citizen17.cpp | 263 +++++++++++++++++++++++++- src/game/server/hl2/npc_citizen17.h | 18 +- 7 files changed, 349 insertions(+), 17 deletions(-) diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index 2fbddb86a2d..43e198ae37d 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -14861,9 +14861,35 @@ void CAI_BaseNPC::ClearCommandGoal() //----------------------------------------------------------------------------- bool CAI_BaseNPC::IsInPlayerSquad() const -{ - return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) ); +{ +#ifdef MAPBASE_MP + // If we have a commander, then we are in a player squad + if ( GetPlayerCommander() != NULL ) + return !CAI_Squad::IsSilentMember( this ); +#endif + + return ( m_pSquad && MAKE_STRING(m_pSquad->GetName()) == GetPlayerSquadName() && !CAI_Squad::IsSilentMember(this) ); +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- + +bool CAI_BaseNPC::IsInThisPlayerSquad( CBasePlayer *pPlayer ) const +{ +#ifdef MAPBASE_MP + if ( !m_pSquad || CAI_Squad::IsSilentMember( this ) ) + return false; + + if ( pPlayer != NULL && pPlayer == GetPlayerCommander() ) + return true; + + AssertMsg( GetPlayerCommander() != NULL || MAKE_STRING( m_pSquad->GetName() ) != GetPlayerSquadName(), "In local player squad but GetPlayerCommander() not implemented (required for multiplayer)" ); + return false; +#else + return IsInPlayerSquad(); +#endif } +#endif //----------------------------------------------------------------------------- diff --git a/src/game/server/ai_basenpc.h b/src/game/server/ai_basenpc.h index b0b2b8c3706..af80784cc13 100644 --- a/src/game/server/ai_basenpc.h +++ b/src/game/server/ai_basenpc.h @@ -1240,6 +1240,12 @@ class CAI_BaseNPC : public CBaseCombatCharacter, bool IsInPlayerSquad() const; virtual CAI_BaseNPC *GetSquadCommandRepresentative() { return NULL; } +#ifdef MAPBASE + // Only used in multiplayer: Allows multiple players to have independent commandable squads + virtual CBasePlayer *GetPlayerCommander() const { return NULL; } + bool IsInThisPlayerSquad( CBasePlayer *pPlayer ) const; +#endif + virtual bool TargetOrder( CBaseEntity *pTarget, CAI_BaseNPC **Allies, int numAllies ) { OnTargetOrder(); return true; } virtual void MoveOrder( const Vector &vecDest, CAI_BaseNPC **Allies, int numAllies ) { SetCommandGoal( vecDest ); SetCondition( COND_RECEIVED_ORDERS ); OnMoveOrder(); } diff --git a/src/game/server/ai_playerally.cpp b/src/game/server/ai_playerally.cpp index 61e6ee9f839..3e87637d178 100644 --- a/src/game/server/ai_playerally.cpp +++ b/src/game/server/ai_playerally.cpp @@ -1387,7 +1387,11 @@ void CAI_PlayerAlly::Event_Killed( const CTakeDamageInfo &info ) // notify the player if ( IsInPlayerSquad() ) { +#ifdef MAPBASE_MP + CBasePlayer *player = GetPlayerCommander(); +#else CBasePlayer *player = AI_GetSinglePlayer(); +#endif if ( player ) { #ifdef MAPBASE diff --git a/src/game/server/hl2/hl2_player.cpp b/src/game/server/hl2/hl2_player.cpp index 9f40ab0edb6..8c276d4b3ad 100644 --- a/src/game/server/hl2/hl2_player.cpp +++ b/src/game/server/hl2/hl2_player.cpp @@ -1644,7 +1644,28 @@ void CHL2_Player::Spawn(void) m_Local.m_iHideHUD |= HIDEHUD_CHAT; - m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); +#ifdef MAPBASE_MP + extern ConVar player_squad_mp_shared; + if ( !player_squad_mp_shared.GetBool() ) + { + char szSquadName[32]; + + if ( UTIL_GetLocalPlayer() == this ) + { + Q_strncpy( szSquadName, PLAYER_SQUADNAME, sizeof( szSquadName ) ); + } + else + { + Q_snprintf( szSquadName, sizeof( szSquadName ), "%s%i", PLAYER_SQUADNAME, entindex() ); + } + + m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad( AllocPooledString( szSquadName ) ); + } + else +#endif + { + m_pPlayerAISquad = g_AI_SquadManager.FindCreateSquad(AllocPooledString(PLAYER_SQUADNAME)); + } InitSprinting(); @@ -2198,7 +2219,11 @@ void CHL2_Player::CommanderExecute( CommanderCommand_t command ) for ( i = 0; !bHandled && i < Allies.Count(); i++ ) { +#ifdef MAPBASE_MP + if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly( this ) ) +#else if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly() ) +#endif { bHandled = !CommanderExecuteOne( Allies[i], goal, Allies.Base(), Allies.Count() ); } @@ -2276,7 +2301,7 @@ void CHL2_Player::InputSquadForceGoTo( inputdata_t &inputdata ) for ( i = 0; !bHandled && i < Allies.Count(); i++ ) { - if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly() ) + if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly( this ) ) { bHandled = !CommanderExecuteOne( Allies[i], goal, Allies.Base(), Allies.Count() ); } @@ -2810,7 +2835,11 @@ void CHL2_Player::SetPlayerUnderwater( bool state ) bool CHL2_Player::PassesDamageFilter( const CTakeDamageInfo &info ) { CBaseEntity *pAttacker = info.GetAttacker(); +#ifdef MAPBASE_MP + if( pAttacker && pAttacker->MyNPCPointer() && pAttacker->MyNPCPointer()->IsPlayerAlly( this ) ) +#else if( pAttacker && pAttacker->MyNPCPointer() && pAttacker->MyNPCPointer()->IsPlayerAlly() ) +#endif { return false; } @@ -2976,7 +3005,11 @@ void CHL2_Player::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity ) const float NEAR_Z = 12*12; const float NEAR_XY_SQ = Square( 50*12 ); CAI_BaseNPC *pNpc = g_AI_Manager.AccessAIs()[i]; +#ifdef MAPBASE_MP + if ( pNpc->IsPlayerAlly( this ) ) +#else if ( pNpc->IsPlayerAlly() ) +#endif { const Vector &originNpc = pNpc->GetAbsOrigin(); if ( fabsf( originNpc.z - origin.z ) < NEAR_Z ) @@ -4881,7 +4914,11 @@ void CLogicPlayerProxy::Activate( void ) if ( m_hPlayer == NULL ) { +#ifdef MAPBASE_MP // From SecobMod + m_hPlayer = UTIL_GetLocalPlayer(); +#else m_hPlayer = AI_GetSinglePlayer(); +#endif } } diff --git a/src/game/server/hl2/hl2_player.h b/src/game/server/hl2/hl2_player.h index 9dd316dc2f4..8a408b85e2c 100644 --- a/src/game/server/hl2/hl2_player.h +++ b/src/game/server/hl2/hl2_player.h @@ -204,6 +204,10 @@ class CHL2_Player : public CBasePlayer int GetNumSquadCommandables(); int GetNumSquadCommandableMedics(); +#ifdef MAPBASE_MP + CAI_Squad *GetPlayerSquad() const { return m_pPlayerAISquad; } +#endif + #ifdef MAPBASE void InputSquadForceSummon( inputdata_t &inputdata ); void InputSquadForceGoTo( inputdata_t &inputdata ); diff --git a/src/game/server/hl2/npc_citizen17.cpp b/src/game/server/hl2/npc_citizen17.cpp index 8d753c39be1..181cb7a7302 100644 --- a/src/game/server/hl2/npc_citizen17.cpp +++ b/src/game/server/hl2/npc_citizen17.cpp @@ -111,6 +111,10 @@ ConVar player_squad_autosummon_player_tolerance( "player_squad_autosummon_player ConVar player_squad_autosummon_time_after_combat( "player_squad_autosummon_time_after_combat", "8" ); ConVar player_squad_autosummon_debug( "player_squad_autosummon_debug", "0" ); +#ifdef MAPBASE_MP +ConVar player_squad_mp_shared( "player_squad_mp_shared", "0", FCVAR_NONE, "Whether or not the player squad should be shared between all players in multiplayer (as opposed to working independently)" ); +#endif + #ifdef MAPBASE ConVar npc_citizen_resupplier_adjust_ammo("npc_citizen_resupplier_adjust_ammo", "1", FCVAR_NONE, "If what ammo we give to the player would go over their max, should we adjust what we give accordingly (1) or cancel it altogether? (0)" ); @@ -198,8 +202,12 @@ class CCommandPoint : public CPointEntity CCommandPoint() : m_bNotInTransition(false) { +#ifdef MAPBASE_MP + ++gm_nCommandPoints; +#else if ( ++gm_nCommandPoints > 1 ) DevMsg( "WARNING: More than one citizen command point present\n" ); +#endif } ~CCommandPoint() @@ -367,6 +375,9 @@ BEGIN_DATADESC( CNPC_Citizen ) DEFINE_INPUT( m_bTossesMedkits, FIELD_BOOLEAN, "SetTossMedkits" ), DEFINE_KEYFIELD( m_bAlternateAiming, FIELD_BOOLEAN, "AlternateAiming" ), #endif +#ifdef MAPBASE_MP + DEFINE_FIELD( m_hPlayerCommander, FIELD_EHANDLE ), +#endif DEFINE_OUTPUT( m_OnJoinedPlayerSquad, "OnJoinedPlayerSquad" ), DEFINE_OUTPUT( m_OnLeftPlayerSquad, "OnLeftPlayerSquad" ), @@ -639,10 +650,12 @@ void CNPC_Citizen::Spawn() //----------------------------------------------------------------------------- void CNPC_Citizen::PostNPCInit() { +#ifndef MAPBASE_MP // Command points are now created procedurally for each player if ( !gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME ) ) { CreateEntityByName( COMMAND_POINT_CLASSNAME ); } +#endif if ( IsInPlayerSquad() ) { @@ -964,10 +977,12 @@ void CNPC_Citizen::OnRestore() BaseClass::OnRestore(); +#ifndef MAPBASE_MP // Command points are now created procedurally for each player if ( !gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME ) ) { CreateEntityByName( COMMAND_POINT_CLASSNAME ); } +#endif } //----------------------------------------------------------------------------- @@ -1076,7 +1091,11 @@ void CNPC_Citizen::GatherConditions() if( IsInPlayerSquad() && hl2_episodic.GetBool() ) { // Leave the player squad if someone has made me neutral to player. +#ifdef MAPBASE_MP + if ( IRelationType( GetPlayerCommander() ) == D_NU ) +#else if( IRelationType(UTIL_GetLocalPlayer()) == D_NU ) +#endif { RemoveFromPlayerSquad(); } @@ -1242,8 +1261,11 @@ void CNPC_Citizen::PrescheduleThink() if ( HaveCommandGoal() ) { CBaseEntity *pCommandPoint = gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME ); - +#ifdef MAPBASE_MP + for (; pCommandPoint != NULL; pCommandPoint = gEntList.FindEntityByClassname( pCommandPoint, COMMAND_POINT_CLASSNAME )) +#else if ( pCommandPoint ) +#endif { NDebugOverlay::Cross3D(pCommandPoint->GetAbsOrigin(), 16, 0, 255, 255, false, 0.1 ); } @@ -2642,10 +2664,22 @@ bool CNPC_Citizen::IsPlayerAlly( CBasePlayer *pPlayer ) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -bool CNPC_Citizen::CanJoinPlayerSquad() +bool CNPC_Citizen::CanJoinPlayerSquad( CBasePlayer *pPlayer ) { + if ( pPlayer == NULL ) + pPlayer = UTIL_GetLocalPlayer(); + +#ifdef MAPBASE_MP + if ( GetPlayerCommander() && GetPlayerCommander() != pPlayer ) + { + // Don't join another player's squad unless our commander is dead + if ( GetPlayerCommander()->IsAlive() ) + return false; + } +#else if ( !AI_IsSinglePlayer() ) return false; +#endif if ( m_NPCState == NPC_STATE_SCRIPT || m_NPCState == NPC_STATE_PRONE ) return false; @@ -2660,7 +2694,7 @@ bool CNPC_Citizen::CanJoinPlayerSquad() if ( !CanBeUsedAsAFriend() ) return false; - if ( IRelationType( UTIL_GetLocalPlayer() ) != D_LI ) + if ( IRelationType( pPlayer ) != D_LI ) return false; #ifdef MAPBASE @@ -2692,6 +2726,16 @@ bool CNPC_Citizen::HaveCommandGoal() const //----------------------------------------------------------------------------- bool CNPC_Citizen::IsCommandMoving() { +#ifdef MAPBASE_MP + if ( IsInPlayerSquad() ) + { + if ( m_FollowBehavior.GetFollowTarget() == GetPlayerCommander() || + IsFollowingCommandPoint() ) + { + return ( m_FollowBehavior.IsMovingToFollowTarget() ); + } + } +#else if ( AI_IsSinglePlayer() && IsInPlayerSquad() ) { if ( m_FollowBehavior.GetFollowTarget() == UTIL_GetLocalPlayer() || @@ -2700,6 +2744,7 @@ bool CNPC_Citizen::IsCommandMoving() return ( m_FollowBehavior.IsMovingToFollowTarget() ); } } +#endif return false; } @@ -2707,10 +2752,17 @@ bool CNPC_Citizen::IsCommandMoving() //----------------------------------------------------------------------------- bool CNPC_Citizen::ShouldAutoSummon() { +#ifdef MAPBASE_MP + if ( !IsFollowingCommandPoint() || !GetPlayerCommander() ) + return false; + + CHL2_Player *pPlayer = (CHL2_Player *)GetPlayerCommander(); +#else if ( !AI_IsSinglePlayer() || !IsFollowingCommandPoint() || !IsInPlayerSquad() ) return false; CHL2_Player *pPlayer = (CHL2_Player *)UTIL_GetLocalPlayer(); +#endif float distMovedSq = ( pPlayer->GetAbsOrigin() - m_vAutoSummonAnchor ).LengthSqr(); float moveTolerance = player_squad_autosummon_move_tolerance.GetFloat() * 12; @@ -2900,6 +2952,28 @@ void CNPC_Citizen::SetPlayerAvoidState( void ) } #endif +#ifdef MAPBASE_MP +//----------------------------------------------------------------------------- +// Purpose: Locates a player commander for my squad +//----------------------------------------------------------------------------- +void CNPC_Citizen::FindPlayerCommander() +{ + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if (!pPlayer) + continue; + + // Check if this is their squad + if (GetSquad() == static_cast(pPlayer)->GetPlayerSquad()) + { + m_hPlayerCommander = pPlayer; + break; + } + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: return TRUE if the commander mode should try to give this order // to more people. return FALSE otherwise. For instance, we don't @@ -2909,6 +2983,14 @@ bool CNPC_Citizen::TargetOrder( CBaseEntity *pTarget, CAI_BaseNPC **Allies, int { if ( pTarget->IsPlayer() ) { +#ifdef MAPBASE_MP + if ( player_squad_mp_shared.GetBool() ) + { + // This is our commander now + m_hPlayerCommander = static_cast(pTarget); + } +#endif + // I'm the target! Toggle follow! if( m_FollowBehavior.GetFollowTarget() != pTarget ) { @@ -2939,7 +3021,11 @@ bool CNPC_Citizen::TargetOrder( CBaseEntity *pTarget, CAI_BaseNPC **Allies, int //----------------------------------------------------------------------------- void CNPC_Citizen::MoveOrder( const Vector &vecDest, CAI_BaseNPC **Allies, int numAllies ) { +#ifdef MAPBASE_MP + if ( !GetPlayerCommander() ) +#else if ( !AI_IsSinglePlayer() ) +#endif return; #ifdef MAPBASE @@ -2952,7 +3038,11 @@ void CNPC_Citizen::MoveOrder( const Vector &vecDest, CAI_BaseNPC **Allies, int n return; } +#ifdef MAPBASE_MP + CHL2_Player *pPlayer = (CHL2_Player *)GetPlayerCommander(); +#else CHL2_Player *pPlayer = (CHL2_Player *)UTIL_GetLocalPlayer(); +#endif m_AutoSummonTimer.Set( player_squad_autosummon_time.GetFloat() ); m_vAutoSummonAnchor = pPlayer->GetAbsOrigin(); @@ -3058,31 +3148,52 @@ void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, // Under these conditions, citizens will refuse to go with the player. // Robin: NPCs should always respond to +USE even if someone else has the semaphore. +#ifdef MAPBASE_MP + CBasePlayer *pPlayer = ToBasePlayer( pActivator ); + if ( !CanJoinPlayerSquad( pPlayer ) ) +#else if ( !AI_IsSinglePlayer() || !CanJoinPlayerSquad() ) +#endif { SimpleUse( pActivator, pCaller, useType, value ); return; } +#ifdef MAPBASE_MP + if ( pPlayer ) +#else if ( pActivator == UTIL_GetLocalPlayer() ) +#endif { // Don't say hi after you've been addressed by the player SetSpokeConcept( TLK_HELLO, NULL ); #ifdef MAPBASE +#ifdef MAPBASE_MP + if ( ShouldAllowSquadToggleUse( pPlayer ) || npc_citizen_auto_player_squad_allow_use.GetBool() ) +#else if ( ShouldAllowSquadToggleUse(UTIL_GetLocalPlayer()) || npc_citizen_auto_player_squad_allow_use.GetBool() ) +#endif { // Version of TogglePlayerSquadState() that has "used" as a modifier static const char *szSquadUseModifier = "used:1"; if ( !IsInPlayerSquad() ) { +#ifdef MAPBASE_MP + AddToPlayerSquad( pPlayer ); +#else AddToPlayerSquad(); +#endif if ( HaveCommandGoal() ) { SpeakCommandResponse( TLK_COMMANDED, szSquadUseModifier ); } +#ifdef MAPBASE_MP + else if ( m_FollowBehavior.GetFollowTarget() == pPlayer ) +#else else if ( m_FollowBehavior.GetFollowTarget() == UTIL_GetLocalPlayer() ) +#endif { SpeakCommandResponse( TLK_STARTFOLLOW, szSquadUseModifier ); } @@ -3090,7 +3201,11 @@ void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, else { SpeakCommandResponse( TLK_STOPFOLLOW, szSquadUseModifier ); +#ifdef MAPBASE_MP + RemoveFromPlayerSquad( pPlayer ); +#else RemoveFromPlayerSquad(); +#endif } } #else @@ -3183,11 +3298,25 @@ void CNPC_Citizen::OnMoveToCommandGoalFailed() //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -void CNPC_Citizen::AddToPlayerSquad() +void CNPC_Citizen::AddToPlayerSquad( CBasePlayer *pPlayer ) { + if ( pPlayer == NULL ) + pPlayer = UTIL_GetLocalPlayer(); + +#ifdef MAPBASE_MP + Assert( !IsInThisPlayerSquad( pPlayer ) ); + + CAI_Squad *pPlayerSquad = static_cast(pPlayer)->GetPlayerSquad(); + if (!pPlayerSquad) + return; + + pPlayerSquad->AddToSquad( this ); + m_hPlayerCommander = pPlayer; +#else Assert( !IsInPlayerSquad() ); AddToSquad( AllocPooledString(PLAYER_SQUADNAME) ); +#endif m_hSavedFollowGoalEnt = m_FollowBehavior.GetFollowGoal(); m_FollowBehavior.SetFollowGoalDirect( NULL ); @@ -3198,13 +3327,27 @@ void CNPC_Citizen::AddToPlayerSquad() //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -void CNPC_Citizen::RemoveFromPlayerSquad() +void CNPC_Citizen::RemoveFromPlayerSquad( CBasePlayer *pPlayer ) { + if ( pPlayer == NULL ) + pPlayer = UTIL_GetLocalPlayer(); + +#ifdef MAPBASE_MP + // This isn't our squad + if ( pPlayer != GetPlayerCommander() ) + return; +#endif + Assert( IsInPlayerSquad() ); ClearFollowTarget(); ClearCommandGoal(); +#ifdef MAPBASE_MP + // Don't loop back into another player squad + if ( m_iszOriginalSquad != NULL_STRING && strncmp( STRING( m_iszOriginalSquad ), PLAYER_SQUADNAME, 12 ) != 0 ) +#else if ( m_iszOriginalSquad != NULL_STRING && strcmp( STRING( m_iszOriginalSquad ), PLAYER_SQUADNAME ) != 0 ) +#endif AddToSquad( m_iszOriginalSquad ); else RemoveFromSquad(); @@ -3220,20 +3363,25 @@ void CNPC_Citizen::RemoveFromPlayerSquad() //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -void CNPC_Citizen::TogglePlayerSquadState() +void CNPC_Citizen::TogglePlayerSquadState( CBasePlayer *pPlayer ) { + if ( pPlayer == NULL ) + pPlayer = UTIL_GetLocalPlayer(); + +#ifndef MAPBASE_MP if ( !AI_IsSinglePlayer() ) return; +#endif if ( !IsInPlayerSquad() ) { - AddToPlayerSquad(); + AddToPlayerSquad( pPlayer ); if ( HaveCommandGoal() ) { SpeakCommandResponse( TLK_COMMANDED ); } - else if ( m_FollowBehavior.GetFollowTarget() == UTIL_GetLocalPlayer() ) + else if ( m_FollowBehavior.GetFollowTarget() == pPlayer ) { SpeakCommandResponse( TLK_STARTFOLLOW ); } @@ -3241,7 +3389,7 @@ void CNPC_Citizen::TogglePlayerSquadState() else { SpeakCommandResponse( TLK_STOPFOLLOW ); - RemoveFromPlayerSquad(); + RemoveFromPlayerSquad( pPlayer ); } } @@ -3279,9 +3427,21 @@ void CNPC_Citizen::UpdatePlayerSquad() gm_PlayerSquadEvaluateTimer.Set( 2.0 ); // Remove stragglers +#ifdef MAPBASE_MP + for ( int playerIdx = 1; playerIdx <= gpGlobals->maxClients; playerIdx++ ) + { + pPlayer = UTIL_PlayerByIndex( playerIdx ); + if ( !pPlayer ) + continue; + + CAI_Squad *pPlayerSquad = static_cast(pPlayer)->GetPlayerSquad(); + if ( !pPlayerSquad ) + continue; +#else CAI_Squad *pPlayerSquad = g_AI_SquadManager.FindSquad( MAKE_STRING( PLAYER_SQUADNAME ) ); if ( pPlayerSquad ) { +#endif CUtlVectorFixed squadMembersToRemove; AISquadIter_t iter; @@ -3323,8 +3483,16 @@ void CNPC_Citizen::UpdatePlayerSquad() const float UNCONDITIONAL_JOIN_PLAYER_XY_TOLERANCE_SQ = Square(12*12); const float UNCONDITIONAL_JOIN_PLAYER_Z_TOLERANCE = 5*12; const float SECOND_TIER_JOIN_DIST_SQ = Square(48*12); +#ifdef MAPBASE_MP + for (int playerIdx = 1; playerIdx <= gpGlobals->maxClients; playerIdx++ ) + { + pPlayer = UTIL_PlayerByIndex( playerIdx ); + if ( !pPlayer || !pPlayer->IsAlive() ) + continue; +#else if ( pPlayer && ShouldAutosquad() && !(pPlayer->GetFlags() & FL_NOTARGET ) && pPlayer->IsAlive() ) { +#endif CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); CUtlVector candidates; const Vector &vPlayerPos = pPlayer->GetAbsOrigin(); @@ -3342,7 +3510,18 @@ void CNPC_Citizen::UpdatePlayerSquad() CNPC_Citizen *pCitizen = assert_cast(ppAIs[i]); int iNew; +#ifdef MAPBASE_MP + if ( pCitizen->GetPlayerCommander() == NULL && pCitizen->m_SquadName == GetPlayerSquadName() ) + { + // Citizen is in player squad but doesn't have a commander, possibly because it spawned before the player(s) + pCitizen->FindPlayerCommander(); + Assert( pCitizen->GetPlayerCommander() ); + } + + if ( pCitizen->IsInThisPlayerSquad( pPlayer ) ) +#else if ( pCitizen->IsInPlayerSquad() ) +#endif { iNew = candidates.AddToTail(); candidates[iNew].pCitizen = pCitizen; @@ -3358,7 +3537,11 @@ void CNPC_Citizen::UpdatePlayerSquad() ( pCitizen->m_flTimeLastCloseToPlayer == 0 || gpGlobals->curtime - pCitizen->m_flTimeLastCloseToPlayer > 15.0 ) ) continue; +#ifdef MAPBASE_MP + if ( !pCitizen->CanJoinPlayerSquad( pPlayer ) ) +#else if ( !pCitizen->CanJoinPlayerSquad() ) +#endif continue; #ifdef MAPBASE @@ -3440,7 +3623,11 @@ void CNPC_Citizen::UpdatePlayerSquad() if ( distSq > SECOND_TIER_JOIN_DIST_SQ ) continue; +#ifdef MAPBASE_MP + if ( !pCitizen->CanJoinPlayerSquad( pPlayer ) ) +#else if ( !pCitizen->CanJoinPlayerSquad() ) +#endif continue; if ( !pCitizen->FVisible( pPlayer ) ) @@ -3464,9 +3651,15 @@ void CNPC_Citizen::UpdatePlayerSquad() for ( i = MAX_PLAYER_SQUAD; i < candidates.Count(); i++ ) { +#ifdef MAPBASE_MP + if ( candidates[i].pCitizen->IsInThisPlayerSquad( pPlayer ) ) + { + candidates[i].pCitizen->RemoveFromPlayerSquad( pPlayer ); +#else if ( candidates[i].pCitizen->IsInPlayerSquad() ) { candidates[i].pCitizen->RemoveFromPlayerSquad(); +#endif } } } @@ -3479,9 +3672,15 @@ void CNPC_Citizen::UpdatePlayerSquad() for ( i = 0; i < candidates.Count() && i < MAX_PLAYER_SQUAD; i++ ) { +#ifdef MAPBASE_MP + if ( !candidates[i].pCitizen->IsInThisPlayerSquad( pPlayer ) ) + { + candidates[i].pCitizen->AddToPlayerSquad( pPlayer ); +#else if ( !candidates[i].pCitizen->IsInPlayerSquad() ) { candidates[i].pCitizen->AddToPlayerSquad(); +#endif nJoined++; if ( candidates[i].distSq < closestDistSq ) @@ -3557,8 +3756,10 @@ int CNPC_Citizen::PlayerSquadCandidateSortFunc( const SquadCandidate_t *pLeft, c //----------------------------------------------------------------------------- void CNPC_Citizen::FixupPlayerSquad() { +#ifndef MAPBASE_MP if ( !AI_IsSinglePlayer() ) return; +#endif m_flTimeJoinedPlayerSquad = gpGlobals->curtime; m_bWasInPlayerSquad = true; @@ -3618,7 +3819,11 @@ void CNPC_Citizen::FixupPlayerSquad() } else { +#ifdef MAPBASE_MP + m_FollowBehavior.SetFollowTarget( GetPlayerCommander() ); +#else m_FollowBehavior.SetFollowTarget( UTIL_GetLocalPlayer() ); +#endif m_FollowBehavior.SetParameters( AIF_SIMPLE ); } } @@ -3635,8 +3840,10 @@ void CNPC_Citizen::ClearFollowTarget() //----------------------------------------------------------------------------- void CNPC_Citizen::UpdateFollowCommandPoint() { +#ifndef MAPBASE_MP if ( !AI_IsSinglePlayer() ) return; +#endif if ( IsInPlayerSquad() ) { @@ -3644,11 +3851,25 @@ void CNPC_Citizen::UpdateFollowCommandPoint() { CBaseEntity *pFollowTarget = m_FollowBehavior.GetFollowTarget(); CBaseEntity *pCommandPoint = gEntList.FindEntityByClassname( NULL, COMMAND_POINT_CLASSNAME ); +#ifdef MAPBASE_MP + for (; pCommandPoint != NULL; pCommandPoint = gEntList.FindEntityByClassname( pCommandPoint, COMMAND_POINT_CLASSNAME )) + { + if (pCommandPoint->GetOwnerEntity() == GetPlayerCommander()) + break; + } +#endif if( !pCommandPoint ) { +#ifdef MAPBASE_MP + // Creating new command points is okay in multiplayer + DevMsg( "Creating a new command point\n" ); + pCommandPoint = CreateEntityByName( COMMAND_POINT_CLASSNAME ); + pCommandPoint->SetOwnerEntity( GetPlayerCommander() ); +#else DevMsg("**\nVERY BAD THING\nCommand point vanished! Creating a new one\n**\n"); pCommandPoint = CreateEntityByName( COMMAND_POINT_CLASSNAME ); +#endif } if ( pFollowTarget != pCommandPoint ) @@ -3667,12 +3888,21 @@ void CNPC_Citizen::UpdateFollowCommandPoint() { if ( IsFollowingCommandPoint() ) ClearFollowTarget(); +#ifdef MAPBASE_MP + if ( m_FollowBehavior.GetFollowTarget() != GetPlayerCommander() ) + { + DevMsg( "Expected to be following player, but not\n" ); + m_FollowBehavior.SetFollowTarget( GetPlayerCommander() ); + m_FollowBehavior.SetParameters( AIF_SIMPLE ); + } +#else if ( m_FollowBehavior.GetFollowTarget() != UTIL_GetLocalPlayer() ) { DevMsg( "Expected to be following player, but not\n" ); m_FollowBehavior.SetFollowTarget( UTIL_GetLocalPlayer() ); m_FollowBehavior.SetParameters( AIF_SIMPLE ); } +#endif } } else if ( IsFollowingCommandPoint() ) @@ -3715,8 +3945,10 @@ int __cdecl SquadSortFunc( const SquadMemberInfo_t *pLeft, const SquadMemberInfo CAI_BaseNPC *CNPC_Citizen::GetSquadCommandRepresentative() { +#ifndef MAPBASE_MP if ( !AI_IsSinglePlayer() ) return NULL; +#endif if ( IsInPlayerSquad() ) { @@ -3729,7 +3961,11 @@ CAI_BaseNPC *CNPC_Citizen::GetSquadCommandRepresentative() hCurrent = NULL; CUtlVectorFixed candidates; +#ifdef MAPBASE_MP + CBasePlayer *pPlayer = GetPlayerCommander(); +#else CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); +#endif if ( pPlayer ) { @@ -3770,6 +4006,15 @@ void CNPC_Citizen::SetSquad( CAI_Squad *pSquad ) BaseClass::SetSquad( pSquad ); +#ifdef MAPBASE_MP + m_hPlayerCommander = NULL; + + if ( GetSquad() ) + { + FindPlayerCommander(); + } +#endif + if( IsInPlayerSquad() && !bWasInPlayerSquad ) { m_OnJoinedPlayerSquad.FireOutput(this, this); diff --git a/src/game/server/hl2/npc_citizen17.h b/src/game/server/hl2/npc_citizen17.h index 5d23429d97c..967e0a367dc 100644 --- a/src/game/server/hl2/npc_citizen17.h +++ b/src/game/server/hl2/npc_citizen17.h @@ -181,7 +181,7 @@ class CNPC_Citizen : public CNPC_PlayerCompanion //--------------------------------- bool IsCommandable(); bool IsPlayerAlly( CBasePlayer *pPlayer = NULL ); - bool CanJoinPlayerSquad(); + bool CanJoinPlayerSquad( CBasePlayer *pPlayer = NULL ); bool WasInPlayerSquad(); bool HaveCommandGoal() const; bool IsCommandMoving(); @@ -198,9 +198,9 @@ class CNPC_Citizen : public CNPC_PlayerCompanion #endif bool ShouldSpeakRadio( CBaseEntity *pListener ); void OnMoveToCommandGoalFailed(); - void AddToPlayerSquad(); - void RemoveFromPlayerSquad(); - void TogglePlayerSquadState(); + void AddToPlayerSquad( CBasePlayer *pPlayer = NULL ); + void RemoveFromPlayerSquad( CBasePlayer *pPlayer = NULL ); + void TogglePlayerSquadState( CBasePlayer *pPlayer = NULL ); void UpdatePlayerSquad(); static int __cdecl PlayerSquadCandidateSortFunc( const SquadCandidate_t *, const SquadCandidate_t * ); void FixupPlayerSquad(); @@ -216,6 +216,11 @@ class CNPC_Citizen : public CNPC_PlayerCompanion #ifdef MAPBASE virtual void SetPlayerAvoidState( void ); #endif + +#ifdef MAPBASE_MP + CBasePlayer * GetPlayerCommander() const { return m_hPlayerCommander; } + void FindPlayerCommander(); +#endif //--------------------------------- // Scanner interaction @@ -340,6 +345,11 @@ class CNPC_Citizen : public CNPC_PlayerCompanion bool m_bAlternateAiming; #endif +#ifdef MAPBASE_MP + // The player with command over us + CHandle m_hPlayerCommander; +#endif + CSimpleSimTimer m_AutoSummonTimer; Vector m_vAutoSummonAnchor; From 969e7251801ea2bca6250ab17844151168dd2378 Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 27 Aug 2025 11:13:18 -0500 Subject: [PATCH 12/14] Add HL2MP player collision avoidance + ally blood decal fix --- src/game/client/hl2mp/c_hl2mp_player.cpp | 16 ++++++++++ src/game/client/hl2mp/c_hl2mp_player.h | 6 ++++ src/game/server/hl2mp/hl2mp_player.h | 2 ++ src/game/shared/hl2mp/hl2mp_gamerules.cpp | 1 + src/game/shared/hl2mp/hl2mp_player_shared.cpp | 32 +++++++++++++++++++ .../hl2mp/weapon_hl2mpbasebasebludgeon.cpp | 6 ++++ 6 files changed, 63 insertions(+) diff --git a/src/game/client/hl2mp/c_hl2mp_player.cpp b/src/game/client/hl2mp/c_hl2mp_player.cpp index bd1ca14aabc..01b6ae53a53 100644 --- a/src/game/client/hl2mp/c_hl2mp_player.cpp +++ b/src/game/client/hl2mp/c_hl2mp_player.cpp @@ -992,6 +992,22 @@ IRagdoll* C_HL2MP_Player::GetRepresentativeRagdoll() const } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_HL2MP_Player::DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + if ( info.GetAttacker() ) + { + if ( !friendlyfire.GetInt() && HL2MPRules()->PlayerRelationship( this, info.GetAttacker() ) == GR_TEAMMATE ) + return; + } + + BaseClass::DispatchTraceAttack( info, vecDir, ptr, pAccumulator ); +} +#endif + //HL2MPRAGDOLL diff --git a/src/game/client/hl2mp/c_hl2mp_player.h b/src/game/client/hl2mp/c_hl2mp_player.h index 915973d751b..97a74978c90 100644 --- a/src/game/client/hl2mp/c_hl2mp_player.h +++ b/src/game/client/hl2mp/c_hl2mp_player.h @@ -100,6 +100,12 @@ class C_HL2MP_Player : public C_BaseHLPlayer bool SuitPower_RemoveDevice( const CSuitPowerDevice& device ); bool SuitPower_ShouldRecharge( void ); float SuitPower_GetCurrentPercentage( void ) { return m_HL2Local.m_flSuitPower; } + +#ifdef MAPBASE + virtual void DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator = NULL ); + + virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; +#endif bool CanSprint( void ); void StartSprinting( void ); diff --git a/src/game/server/hl2mp/hl2mp_player.h b/src/game/server/hl2mp/hl2mp_player.h index 00e17a4f627..118385103c1 100644 --- a/src/game/server/hl2mp/hl2mp_player.h +++ b/src/game/server/hl2mp/hl2mp_player.h @@ -116,6 +116,8 @@ class CHL2MP_Player : public CHL2_Player int GetPlayerModelType( void ) { return m_iPlayerSoundType; } #ifdef MAPBASE + virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const; + // TF2-style AFK checking void CheckForIdle( void ); void ResetIdleCheck( void ) { m_flLastAction = gpGlobals->curtime; } diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index 815aff1e183..bae7445274d 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -68,6 +68,7 @@ extern CBaseEntity *g_pLastRebelSpawn; #ifdef MAPBASE ConVar sv_hl2mp_npc_target_id( "sv_hl2mp_npc_target_id", "1", FCVAR_REPLICATED, "If enabled, NPCs will be identified when players look at them with their crosshairs." ); +ConVar hl2mp_avoidteammates( "hl2mp_avoidteammates", "1", FCVAR_REPLICATED, "If enabled, players on the same team will not collide with each other." ); #endif diff --git a/src/game/shared/hl2mp/hl2mp_player_shared.cpp b/src/game/shared/hl2mp/hl2mp_player_shared.cpp index aee6aa3e8a2..ba43014a658 100644 --- a/src/game/shared/hl2mp/hl2mp_player_shared.cpp +++ b/src/game/shared/hl2mp/hl2mp_player_shared.cpp @@ -125,6 +125,38 @@ void CHL2MP_Player::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, f EmitSound( filter, entindex(), ep ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : collisionGroup - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL2MP_Player::ShouldCollide( int collisionGroup, int contentsMask ) const +{ + extern ConVar hl2mp_avoidteammates; + if ( ( collisionGroup == COLLISION_GROUP_PLAYER || collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) && hl2mp_avoidteammates.GetBool() ) + { + // Co-op ignores all players + if ( HL2MPRules() && HL2MPRules()->IsCoOp() ) + return false; + + switch( GetTeamNumber() ) + { + case TEAM_REBELS: + if ( ( contentsMask & CONTENTS_TEAM1 ) ) + return false; + break; + + case TEAM_COMBINE: + if ( ( contentsMask & CONTENTS_TEAM2 ) ) + return false; + break; + } + } + return BaseClass::ShouldCollide( collisionGroup, contentsMask ); +} +#endif + //========================== // ANIMATION CODE diff --git a/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp b/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp index 0acac6bd70f..d4616e5151a 100644 --- a/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp +++ b/src/game/shared/hl2mp/weapon_hl2mpbasebasebludgeon.cpp @@ -220,6 +220,12 @@ void CBaseHL2MPBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity ) if ( this->PlayFleshyHittySoundOnHit() ) WeaponSound( MELEE_HIT ); + +#ifdef MAPBASE_MP + // Don't do impact effect if this is an ally + if ( pHitEntity->MyNPCPointer() && pHitEntity->MyNPCPointer()->IsPlayerAlly( pPlayer ) ) + return; +#endif } // Apply an impact effect From 117a56198885a756460aac1073de7f3f482f622c Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 27 Aug 2025 11:36:41 -0500 Subject: [PATCH 13/14] Fix NPC footstep sounds in HL2MP --- src/game/client/c_baseanimating.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/game/client/c_baseanimating.cpp b/src/game/client/c_baseanimating.cpp index 060c9409720..9ca951d039c 100644 --- a/src/game/client/c_baseanimating.cpp +++ b/src/game/client/c_baseanimating.cpp @@ -4322,7 +4322,13 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int case CL_EVENT_FOOTSTEP_LEFT: { -#ifndef HL2MP +#if !defined(HL2MP) || defined(MAPBASE_MP) +#ifdef MAPBASE_MP + // NPCs should still use footstep sounds in MP + if (!IsNPC()) + break; +#endif + char pSoundName[256]; if ( !options || !options[0] ) { @@ -4348,7 +4354,13 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int case CL_EVENT_FOOTSTEP_RIGHT: { -#ifndef HL2MP +#if !defined(HL2MP) || defined(MAPBASE_MP) +#ifdef MAPBASE_MP + // NPCs should still use footstep sounds in MP + if (!IsNPC()) + break; +#endif + char pSoundName[256]; if ( !options || !options[0] ) { From 06fa2da70039a7604d9a43db09710af8f8b85a7f Mon Sep 17 00:00:00 2001 From: Blixibon Date: Wed, 27 Aug 2025 11:43:51 -0500 Subject: [PATCH 14/14] Nextbots sense weapon sounds from traditional NPCs --- src/game/server/ai_basenpc.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index 43e198ae37d..2a2779f272f 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -103,6 +103,9 @@ #include "hl2mp_gamerules.h" #include "hl2mp_player_resource.h" #endif +#ifdef NEXT_BOT +#include "NextBot.h" +#endif #endif #ifdef MAPBASE_VSCRIPT @@ -1908,6 +1911,11 @@ void CAI_BaseNPC::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int //----------------------------------------------------------------------------- void CAI_BaseNPC::FireBullets( const FireBulletsInfo_t &info ) { +#if defined(MAPBASE) && defined(NEXT_BOT) + if ( GetActiveWeapon() ) + TheNextBots().OnWeaponFired( this, GetActiveWeapon() ); +#endif + #ifdef HL2_DLL // If we're shooting at a bullseye, become perfectly accurate if the bullseye demands it if ( GetEnemy() && GetEnemy()->Classify() == CLASS_BULLSEYE )