From 179b7a529881359edff5b812d69557dbc12034f2 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Mon, 18 Apr 2022 23:46:23 -0400 Subject: [PATCH] Added factory for bullet-firing guns --- .../client/mapbase/c_weapon_custom_hl2.cpp | 29 +- sp/src/game/server/basecombatcharacter.cpp | 1 - sp/src/game/server/hl2/weapon_annabelle.cpp | 46 + sp/src/game/server/hl2/weapon_crossbow.cpp | 10 + .../server/mapbase/custom_weapon_factory.cpp | 3 +- .../server/mapbase/custom_weapon_factory.h | 4 +- .../game/server/mapbase/weapon_custom_hl2.cpp | 1173 ++++++++++++++++- .../game/shared/basecombatweapon_shared.cpp | 9 + sp/src/game/shared/basecombatweapon_shared.h | 2 +- sp/src/game/shared/weapon_parse.cpp | 22 + sp/src/game/shared/weapon_parse.h | 13 + 11 files changed, 1304 insertions(+), 8 deletions(-) diff --git a/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp index 58c46e25..8c89f2d7 100644 --- a/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp +++ b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp @@ -6,7 +6,6 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -#pragma region Melee class C_HLCustomWeaponMelee : public C_BaseHLBludgeonWeapon { public: @@ -31,4 +30,30 @@ C_HLCustomWeaponMelee::C_HLCustomWeaponMelee() { m_iszWeaponScriptName[0] = '\0'; } -#pragma endregion \ No newline at end of file + + + +class C_HLCustomWeaponGun : public C_BaseHLBludgeonWeapon +{ +public: + DECLARE_CLASS(C_HLCustomWeaponGun, C_BaseHLBludgeonWeapon); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_HLCustomWeaponGun(); + + virtual const char* GetWeaponScriptName() { return m_iszWeaponScriptName; } +private: + char m_iszWeaponScriptName[128]; +}; + +STUB_WEAPON_CLASS_IMPLEMENT(weapon_hlcustomgun, C_HLCustomWeaponGun); + +IMPLEMENT_CLIENTCLASS_DT(C_HLCustomWeaponGun, DT_HLCustomWeaponGun, CHLCustomWeaponGun) +RecvPropString(RECVINFO(m_iszWeaponScriptName)), +END_RECV_TABLE(); + +C_HLCustomWeaponGun::C_HLCustomWeaponGun() +{ + m_iszWeaponScriptName[0] = '\0'; +} \ No newline at end of file diff --git a/sp/src/game/server/basecombatcharacter.cpp b/sp/src/game/server/basecombatcharacter.cpp index f4b35392..5ea5c65d 100644 --- a/sp/src/game/server/basecombatcharacter.cpp +++ b/sp/src/game/server/basecombatcharacter.cpp @@ -2789,7 +2789,6 @@ Activity CBaseCombatCharacter::Weapon_BackupActivity( Activity activity, bool we if (pTable && GetModelPtr()) { - int actCount = pWeapon->GetBackupActivityListCount(); return Weapon_BackupActivityFromList( this, pTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); } diff --git a/sp/src/game/server/hl2/weapon_annabelle.cpp b/sp/src/game/server/hl2/weapon_annabelle.cpp index 6b7ecdd9..835fcc6a 100644 --- a/sp/src/game/server/hl2/weapon_annabelle.cpp +++ b/sp/src/game/server/hl2/weapon_annabelle.cpp @@ -121,6 +121,33 @@ acttable_t CWeaponAnnabelle::m_acttable[] = { ACT_RELOAD_LOW, ACT_RELOAD_ANNABELLE_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_ANNABELLE, false }, + // 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_ARM, ACT_ARM_RIFLE, true }, { ACT_DISARM, ACT_DISARM_RIFLE, true }, #else @@ -143,6 +170,13 @@ acttable_t CWeaponAnnabelle::m_acttable[] = { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, false }, #endif +#if EXPANDED_HL2_COVER_ACTIVITIES + { 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 }, @@ -161,6 +195,18 @@ acttable_t CWeaponAnnabelle::m_acttable[] = IMPLEMENT_ACTTABLE(CWeaponAnnabelle); +#ifdef MAPBASE +acttable_t* GetAnnabelleActtable() +{ + return CWeaponAnnabelle::m_acttable; +} + +int GetAnnabelleActtableCount() +{ + return ARRAYSIZE(CWeaponAnnabelle::m_acttable); +} +#endif // MAPBASE + void CWeaponAnnabelle::Precache( void ) { CBaseCombatWeapon::Precache(); diff --git a/sp/src/game/server/hl2/weapon_crossbow.cpp b/sp/src/game/server/hl2/weapon_crossbow.cpp index 8a0f306b..aaed3de0 100644 --- a/sp/src/game/server/hl2/weapon_crossbow.cpp +++ b/sp/src/game/server/hl2/weapon_crossbow.cpp @@ -762,6 +762,16 @@ acttable_t CWeaponCrossbow::m_acttable[] = }; IMPLEMENT_ACTTABLE(CWeaponCrossbow); + +acttable_t* GetCrossbowActtable() +{ + return CWeaponCrossbow::m_acttable; +} + +int GetCrossbowActtableCount() +{ + return ARRAYSIZE(CWeaponCrossbow::m_acttable); +} #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp index e010bc3f..a8e17146 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.cpp +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -7,7 +7,7 @@ extern ConVar mapbase_load_default_manifest; - +IMPLEMENT_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactorySystem") { @@ -155,6 +155,7 @@ void CCustomWeaponSystem::LevelShutdownPostEntity() } m_ClassFactories.Purge(); + g_CustomWeaponSymbolSymbolTable.RemoveAll(); } void CCustomWeaponSystem::ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName) diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.h b/sp/src/game/server/mapbase/custom_weapon_factory.h index c1af48c6..fc01045d 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.h +++ b/sp/src/game/server/mapbase/custom_weapon_factory.h @@ -4,6 +4,8 @@ #include "utldict.h" #include "utlsymbol.h" +DECLARE_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); + CUtlDict< IEntityFactory*, unsigned short >& CustomWeaponsFactoryDictionary(); class ICustomWeapon @@ -31,7 +33,7 @@ private: typedef struct CustomClassName_s { - CUtlSymbol sDataFile; + CustomWeaponSymbol sDataFile; IEntityFactory* pNewFactory; IEntityFactory* pOldFactory; } CustomClassName_t; diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 22c2aad7..c2881c6f 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -4,11 +4,29 @@ #include "ai_basenpc.h" #include "player.h" #include "npcevent.h" +#include "in_buttons.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -#pragma region Melee +// Acttables +extern acttable_t* GetSMG1Acttable(); +extern int GetSMG1ActtableCount(); +extern acttable_t* GetPistolActtable(); +extern int GetPistolActtableCount(); +extern acttable_t* GetShotgunActtable(); +extern int GetShotgunActtableCount(); +extern acttable_t* Get357Acttable(); +extern int Get357ActtableCount(); +extern acttable_t* GetAR2Acttable(); +extern int GetAR2ActtableCount(); +extern acttable_t* GetCrossbowActtable(); +extern int GetCrossbowActtableCount(); +extern acttable_t* GetAnnabelleActtable(); +extern int GetAnnabelleActtableCount(); + + + const char* g_ppszDamageClasses[] = { "BLUNT", "SLASH", @@ -320,4 +338,1155 @@ void CHLCustomWeaponMelee::ParseCustomFromWeaponFile(const char* pFileName) } } } -#pragma endregion \ No newline at end of file + +//-------------------------------------------------------------------------- +// +// Custom ranged weapon +// +//-------------------------------------------------------------------------- + +class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon +{ +public: + DECLARE_CLASS(CHLCustomWeaponGun, CBaseHLCombatWeapon); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CHLCustomWeaponGun(); + virtual void ParseCustomFromWeaponFile(const char* pFileName); + + // Weapon behaviour + virtual void ItemPostFrame(void); // called each frame by the player PostThink + virtual void ItemBusyFrame(void); // called each frame by the player PostThink, if the player's not ready to attack yet + virtual bool ReloadOrSwitchWeapons(void); + + // Bullet launch information + virtual const Vector& GetBulletSpread(void); + virtual float GetFireRate(void) { return m_flFireRate; } + virtual int GetMinBurst() { return m_nMinBurst; } + virtual int GetMaxBurst() { return m_nMaxBurst; } + virtual float GetMinRestTime() { return m_RestInterval.start; } + virtual float GetMaxRestTime() { return m_RestInterval.start + m_RestInterval.range; } + + // Autoaim + virtual float GetMaxAutoAimDeflection() { return 0.99f; } + virtual float WeaponAutoAimScale() { return m_flAutoAimScale; } // allows a weapon to influence the perceived size of the target's autoaim radius. + + virtual void AddViewKick(void); + int WeaponSoundRealtime(WeaponSound_t shoot_type); + + bool StartReload(void); + bool Reload(void); + void FillClip(void); + void FinishReload(void); + void Pump(void); + + void PrimaryAttack(); + + 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); + int CapabilitiesGet(void) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + + Activity GetPrimaryAttackActivity(void); + + virtual acttable_t* ActivityList(void); + virtual int ActivityListCount(void); + + virtual acttable_t* GetBackupActivityList(); + virtual int GetBackupActivityListCount(); +private: + void CheckZoomToggle(void); + void ToggleZoom(void); + +private: + CNetworkString(m_iszWeaponScriptName, 128); + + float m_flFireRate; + int m_nMinBurst; + int m_nMaxBurst; + interval_t m_RestInterval; + + float m_flAutoAimScale; + + Vector m_vPlayerSpread; + Vector m_vAllySpread; + Vector m_vNPCSpread; + int m_nBulletsPerShot; // For shotguns + + // Viewkick + float m_flMaxVerticalKick; + float m_flSlideLimit; + interval_t m_VerticalPunchRange; + + int m_nActTableIndex; + + bool m_bUseRecoilAnims; + bool m_bFullAuto; // True for machine gun, false for semi-auto + bool m_bNextAttackFromSequence; + bool m_bUsePumpAnimation; + bool m_bHasSecondaryFire; + bool m_bHasZoom; + bool m_bZoomDuringReload; + + bool m_bNeedPump; // When emptied completely + bool m_bDelayedFire1; // Fire primary when finished reloading + bool m_bDelayedFire2; // Fire secondary when finished reloading + bool m_bInZoom; + bool m_bMustReload; + + int m_nShotsFired; // Number of consecutive shots fired + float m_flNextSoundTime; // real-time clock of when to make next sound +public: + enum WeaponActTable_e + { + ACTTABLE_SMG1 = 0, + ACTTABLE_PISTOL, + ACTTABLE_REVOLVER, + ACTTABLE_SHOTGUN, + ACTTABLE_AR2, + ACTTABLE_CROSSBOW, + ACTTABLE_ANNABELLE, + + NUM_GUN_ACT_TABLES + }; +}; + +IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponGun, DT_HLCustomWeaponGun) +SendPropString(SENDINFO(m_iszWeaponScriptName)), +END_SEND_TABLE(); + +BEGIN_DATADESC(CHLCustomWeaponGun) +DEFINE_FIELD(m_nShotsFired, FIELD_INTEGER), +DEFINE_FIELD(m_flNextSoundTime, FIELD_TIME), +DEFINE_FIELD(m_bNeedPump, FIELD_BOOLEAN), +DEFINE_FIELD(m_bDelayedFire1, FIELD_BOOLEAN), +DEFINE_FIELD(m_bDelayedFire2, FIELD_BOOLEAN), +DEFINE_FIELD(m_bInZoom, FIELD_BOOLEAN), +DEFINE_FIELD(m_bMustReload, FIELD_BOOLEAN), +END_DATADESC(); + +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_gun, CHLCustomWeaponGun); + +CHLCustomWeaponGun::CHLCustomWeaponGun() +{ + m_flFireRate = 0.5f; + m_nMinBurst = 1; + m_nMaxBurst = 1; + m_RestInterval.start = .3f; + m_RestInterval.range = .3f; + + m_flAutoAimScale = 1.f; + m_nBulletsPerShot = 1; + + m_bUseRecoilAnims = false; + m_bFullAuto = false; + m_bNextAttackFromSequence = false; + m_bUsePumpAnimation = false; + m_bHasSecondaryFire = false; + m_bHasZoom = false; + m_bZoomDuringReload = false; + m_bFiresUnderwater = false; + + m_bNeedPump = false; + m_bDelayedFire1 = false; + m_bDelayedFire2 = false; + m_bInZoom = false; + m_bMustReload = false; + m_nShotsFired = 0; +} + +acttable_t* CHLCustomWeaponGun::ActivityList(void) +{ + switch (m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + return GetSMG1Acttable(); + break; + case ACTTABLE_PISTOL: + return GetPistolActtable(); + break; + case ACTTABLE_REVOLVER: + return Get357Acttable(); + break; + case ACTTABLE_SHOTGUN: + return GetShotgunActtable(); + break; + case ACTTABLE_AR2: + return GetAR2Acttable(); + break; + case ACTTABLE_CROSSBOW: + return GetCrossbowActtable(); + break; + case ACTTABLE_ANNABELLE: + return GetAnnabelleActtable(); + break; + } +} + +int CHLCustomWeaponGun::ActivityListCount(void) +{ + switch (m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + return GetSMG1ActtableCount(); + break; + case ACTTABLE_PISTOL: + return GetPistolActtableCount(); + break; + case ACTTABLE_REVOLVER: + return Get357ActtableCount(); + break; + case ACTTABLE_SHOTGUN: + return GetShotgunActtableCount(); + break; + case ACTTABLE_AR2: + return GetAR2ActtableCount(); + break; + case ACTTABLE_CROSSBOW: + return GetCrossbowActtableCount(); + break; + case ACTTABLE_ANNABELLE: + return GetAnnabelleActtableCount(); + break; + } +} + +acttable_t* CHLCustomWeaponGun::GetBackupActivityList(void) +{ + switch (m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + case ACTTABLE_CROSSBOW: + case ACTTABLE_AR2: + return GetSMG1Acttable(); + break; + case ACTTABLE_PISTOL: + case ACTTABLE_REVOLVER: + return GetPistolActtable(); + break; + case ACTTABLE_SHOTGUN: + case ACTTABLE_ANNABELLE: + return GetShotgunActtable(); + break; + } +} + +int CHLCustomWeaponGun::GetBackupActivityListCount(void) +{ + switch (m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + case ACTTABLE_CROSSBOW: + case ACTTABLE_AR2: + return GetSMG1ActtableCount(); + break; + case ACTTABLE_PISTOL: + case ACTTABLE_REVOLVER: + return GetPistolActtableCount(); + break; + case ACTTABLE_SHOTGUN: + case ACTTABLE_ANNABELLE: + return GetShotgunActtableCount(); + break; + } +} + +void ReadIntervalInt(const char* pString, int &iMin, int &iMax) +{ + char tempString[128]; + Q_strncpy(tempString, pString, sizeof(tempString)); + + char* token = strtok(tempString, ","); + if (token) + { + iMin = atoi(token); + token = strtok(NULL, ","); + if (token) + { + iMax = atoi(token); + } + else + { + iMax = iMin; + } + } +} + +void CHLCustomWeaponGun::ParseCustomFromWeaponFile(const char* pFileName) +{ + static const char* ppszCustomGunAnimTypes[NUM_GUN_ACT_TABLES] = { + "smg", + "pistol", + "revolver", + "shotgun", + "ar2", + "crossbow", + "annabelle", + }; + + Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 128); + KeyValuesAD pKVWeapon("WeaponData"); + if (pKVWeapon->LoadFromFile(filesystem, pFileName, "GAME")) + { + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flFireRate = pkvData->GetFloat("fire_rate", 0.5f); + ReadIntervalInt(pkvData->GetString("npc_burst", "1"), m_nMinBurst, m_nMaxBurst); + m_RestInterval = ReadInterval(pkvData->GetString("npc_rest_time", "0.3,0.6")); + m_flAutoAimScale = pkvData->GetFloat("autoaim_scale", 1.f); + m_bFullAuto = pkvData->GetBool("auto_fire"); + m_nBulletsPerShot = pkvData->GetInt("bullets", 1); + m_bUseRecoilAnims = pkvData->GetBool("recoil_anims", true); + m_bReloadsSingly = pkvData->GetBool("reload_singly"); + m_bFiresUnderwater = pkvData->GetBool("fires_underwater"); + m_bHasZoom = pkvData->GetBool("zoom_enable"); + m_bZoomDuringReload = m_bHasZoom && pkvData->GetBool("zoom_in_reload"); + + m_fMinRange1 = pkvData->GetFloat("range1_min", 65.f); + m_fMinRange2 = pkvData->GetFloat("range2_min", 65.f); + m_fMaxRange1 = pkvData->GetFloat("range1_max", 1024.f); + m_fMaxRange2 = pkvData->GetFloat("range2_max", 1024.f); + + if (m_bFullAuto) + { + m_flMaxVerticalKick = pkvData->GetFloat("viewkick_vertical_max", 1.f); + m_flSlideLimit = pkvData->GetFloat("viewkick_slide_limit", 2.f); + } + else + { + m_flSlideLimit = pkvData->GetFloat("viewpunch_side_max", .6f); + m_VerticalPunchRange = ReadInterval(pkvData->GetString("viewpunch_vertical", "0.25,0.5")); + + m_bNextAttackFromSequence = pkvData->GetBool("next_attack_time_from_sequence"); + m_bUsePumpAnimation = pkvData->GetBool("use_pump_anim"); + } + + // NOTE: The way these are calculated is that each component == sin (degrees/2) + float flSpread = pkvData->GetFloat("spread", 5.f); + float flNPCSpread = pkvData->GetFloat("spread_npc", flSpread); + float flAllySperad = pkvData->GetFloat("spread_ally", flNPCSpread); + m_vPlayerSpread = Vector(sin(flSpread * 0.5f)); + m_vNPCSpread = Vector(sin(flNPCSpread * 0.5f)); + m_vAllySpread = Vector(sin(flAllySperad * 0.5f)); + + const char* pszAnimType = pkvData->GetString("anim_type", nullptr); + if (pszAnimType) + { + for (int i = 0; i < NUM_GUN_ACT_TABLES; i++) + { + if (V_stricmp(pszAnimType, ppszCustomGunAnimTypes[i]) == 0) + { + m_nActTableIndex = i; + break; + } + } + } + } + } +} + +const Vector& CHLCustomWeaponGun::GetBulletSpread() +{ + if (!GetOwner() || !GetOwner()->IsNPC()) + return m_vPlayerSpread; + + if (GetOwner()->MyNPCPointer()->IsPlayerAlly()) + { + // 357 allies should be cooler + return m_vAllySpread; + } + + return m_vNPCSpread; +} + +void CHLCustomWeaponGun::AddViewKick(void) +{ + //Get the view kick + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (!pPlayer) + return; + + if (m_bFullAuto) + { + float flDuration = m_fFireDuration; + + if (g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE) + { + // On the 360 (or in any configuration using the 360 aiming scheme), don't let the + // AR2 progressive into the late, highly inaccurate stages of its kick. Just + // spoof the time to make it look (to the kicking code) like we haven't been + // firing for very long. + flDuration = MIN(flDuration, 0.75f); + } + + CHLMachineGun::DoMachineGunKick(pPlayer, 0.5f, m_flMaxVerticalKick, flDuration, m_flSlideLimit); + } + else + { + QAngle viewPunch; + viewPunch.x = RandomInterval(m_VerticalPunchRange); + viewPunch.y = RandomFloat(-m_flSlideLimit, m_flSlideLimit); + viewPunch.z = 0.0f; + + //Add it to the view punch + pPlayer->ViewPunch(viewPunch); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::CheckZoomToggle(void) +{ + if (!m_bHasZoom) + return; + + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + int iButtonsTest = IN_ATTACK3; + if (!m_bHasSecondaryFire) + iButtonsTest |= IN_ATTACK2; + + if (pPlayer->m_afButtonPressed & iButtonsTest) + { + ToggleZoom(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::ToggleZoom(void) +{ + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (pPlayer == NULL) + return; + + if (m_bInZoom) + { + if (pPlayer->SetFOV(this, 0, 0.2f)) + { + m_bInZoom = false; + } + } + else + { + if (pPlayer->SetFOV(this, 20, 0.1f)) + { + m_bInZoom = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::StartReload(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + // If shotgun totally emptied then a pump animation is needed + + //NOTENOTE: This is kinda lame because the player doesn't get strong feedback on when the reload has finished, + // without the pump. Technically, it's incorrect, but it's good for feedback... + + if (m_bUsePumpAnimation && m_iClip1 <= 0) + { + m_bNeedPump = true; + } + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + SendWeaponAnim(ACT_SHOTGUN_RELOAD_START); + + // Make shotgun shell visible + SetBodygroup(1, 0); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + +#ifdef MAPBASE + if (pOwner->IsPlayer()) + { + static_cast(pOwner)->SetAnimation(PLAYER_RELOAD); + } +#endif + + if (m_bInZoom && !m_bZoomDuringReload) + { + ToggleZoom(); + } + + m_bInReload = true; + m_bMustReload = false; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::Reload(void) +{ + if (m_bReloadsSingly) + { + // Check that StartReload was called first + if (!m_bInReload) + { + Warning("ERROR: Shotgun Reload called incorrectly!\n"); + } + + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + FillClip(); + // Play reload on different channel as otherwise steals channel away from fire sound + WeaponSound(RELOAD); + SendWeaponAnim(ACT_VM_RELOAD); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + + return true; + } + else if (BaseClass::Reload()) + { + if (m_bInZoom && !m_bZoomDuringReload) + { + ToggleZoom(); + } + + m_bMustReload = false; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FinishReload(void) +{ + if (m_bReloadsSingly) + { + // Make shotgun shell invisible + SetBodygroup(1, 1); + + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + m_bInReload = false; + + // Finish reload animation + SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + } + else + { + BaseClass::FinishReload(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FillClip(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + // Add them to the clip + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) > 0) + { + if (Clip1() < GetMaxClip1()) + { + m_iClip1++; + pOwner->RemoveAmmo(1, m_iPrimaryAmmoType); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play weapon pump anim +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Pump(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + m_bNeedPump = false; + + WeaponSound(SPECIAL1); + + // Finish reload animation + SendWeaponAnim(ACT_SHOTGUN_PUMP); + + pOwner->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: If the current weapon has more ammo, reload it. Otherwise, switch +// to the next best weapon we've got. Returns true if it took any action. +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::ReloadOrSwitchWeapons(void) +{ + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + Assert(pOwner); + + m_bFireOnEmpty = false; + + // If we don't have any ammo, switch to the next best weapon + if (!HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime && m_flNextSecondaryAttack < gpGlobals->curtime) + { + // weapon isn't useable, switch. + // Ammo might be overridden to 0, in which case we shouldn't do this + if (((GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) == false) && !HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY) && (g_pGameRules->SwitchToNextBestWeapon(pOwner, this))) + { + m_flNextPrimaryAttack = gpGlobals->curtime + 0.3; + return true; + } + } + else + { + // Weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if (UsesClipsForAmmo1() && !AutoFiresFullClip() && + (m_iClip1 == 0) && + (GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) == false && + m_flNextPrimaryAttack < gpGlobals->curtime && + m_flNextSecondaryAttack < gpGlobals->curtime) + { + // if we're successfully reloading, we're done + if (m_bReloadsSingly) + return StartReload(); + else + return Reload(); + } + } + + return false; +} + +void CHLCustomWeaponGun::ItemBusyFrame(void) +{ + BaseClass::ItemBusyFrame(); + + if (m_bZoomDuringReload) + CheckZoomToggle(); +} + +void CHLCustomWeaponGun::ItemPostFrame(void) +{ + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + return; + + // Debounce the recoiling counter + if ((pOwner->m_nButtons & IN_ATTACK) == false) + { + m_nShotsFired = 0; + } + + UpdateAutoFire(); + + if (m_bZoomDuringReload || !m_bInReload) + CheckZoomToggle(); + + if (m_bReloadsSingly) + { + if (m_bInReload) + { + m_fFireDuration = 0.f; + + // If I'm primary firing and have one round stop reloading and fire + if ((pOwner->m_nButtons & IN_ATTACK) && (m_iClip1 >= 1)) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire1 = true; + } + // If I'm secondary firing and have one round stop reloading and fire + else if (m_bHasSecondaryFire && (pOwner->m_nButtons & IN_ATTACK2) && (m_iClip1 >= 2)) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire2 = true; + } + else if (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + // If out of ammo end reload + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + FinishReload(); + return; + } + // If clip not full reload again + if (m_iClip1 < GetMaxClip1()) + { + Reload(); + return; + } + // Clip full, stop reloading + else + { + FinishReload(); + return; + } + } + } + else + { + // Make shotgun shell invisible + SetBodygroup(1, 1); + } + } + else if (UsesClipsForAmmo1()) + { + CheckReload(); + } + + if (m_bUsePumpAnimation && (m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_fFireDuration = 0.f; + Pump(); + return; + } + + //Track the duration of the fire + //FIXME: Check for IN_ATTACK2 as well? + //FIXME: What if we're calling ItemBusyFrame? + m_fFireDuration = (pOwner->m_nButtons & IN_ATTACK) ? (m_fFireDuration + gpGlobals->frametime) : 0.0f; + + bool bFired = false; + + // Secondary attack has priority + if (m_bHasSecondaryFire && !m_bMustReload && (m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire2 = false; + + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + else if (UsesSecondaryAmmo() && pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { + if (m_flNextEmptySoundTime < gpGlobals->curtime) + { + WeaponSound(EMPTY); + m_flNextSecondaryAttack = m_flNextEmptySoundTime = gpGlobals->curtime + 0.5; + } + } + else if (pOwner->GetWaterLevel() == 3 && m_bAltFiresUnderwater == false) + { + // This weapon doesn't fire underwater + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + // FIXME: This isn't necessarily true if the weapon doesn't have a secondary fire! + // For instance, the crossbow doesn't have a 'real' secondary fire, but it still + // stops the crossbow from firing on the 360 if the player chooses to hold down their + // zoom button. (sjb) Orange Box 7/25/2007 +#if !defined(CLIENT_DLL) + if (!IsX360() || !ClassMatches("weapon_crossbow")) +#endif + { + bFired = ShouldBlockPrimaryFire(); + } + + SecondaryAttack(); + + // Secondary ammo doesn't have a reload animation + if (UsesClipsForAmmo2()) + { + // reload clip2 if empty + if (m_iClip2 < 1) + { + pOwner->RemoveAmmo(1, m_iSecondaryAmmoType); + m_iClip2 = m_iClip2 + 1; + } + } + } + } + + if (!bFired && !m_bMustReload && (m_bDelayedFire1 || pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire1 = false; + + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + // Clip empty? Or out of ammo on a no-clip weapon? + else if ((UsesClipsForAmmo1() && m_iClip1 <= 0) || (!UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)) + { + HandleFireOnEmpty(); + } + else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false) + { + // This weapon doesn't fire underwater + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + //NOTENOTE: There is a bug with this code with regards to the way machine guns catch the leading edge trigger + // on the player hitting the attack key. It relies on the gun catching that case in the same frame. + // However, because the player can also be doing a secondary attack, the edge trigger may be missed. + // We really need to hold onto the edge trigger and only clear the condition when the gun has fired its + // first shot. Right now that's too much of an architecture change -- jdw + + // If the firing button was just pressed, or the alt-fire just released, reset the firing time + if ((pOwner->m_afButtonPressed & IN_ATTACK) || (pOwner->m_afButtonReleased & IN_ATTACK2)) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + + PrimaryAttack(); + + if (AutoFiresFullClip()) + { + m_bFiringWholeClip = true; + } + +#ifdef CLIENT_DLL + pOwner->SetFiredWeapon(true); +#endif + } + } + + // ----------------------- + // Reload pressed / Clip Empty + // ----------------------- + if ((pOwner->m_nButtons & IN_RELOAD || m_bMustReload) && UsesClipsForAmmo1() && !m_bInReload) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + if (m_bReloadsSingly) + StartReload(); + else + Reload(); + + m_fFireDuration = 0.0f; + } + + // ----------------------- + // No buttons down + // ----------------------- + else if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_ATTACK2) || (CanReload() && pOwner->m_nButtons & IN_RELOAD))) + { + // no fire buttons down or reloading + if (!ReloadOrSwitchWeapons() && (m_bInReload == false)) + { + WeaponIdle(); + } + } +} + +void CHLCustomWeaponGun::PrimaryAttack() +{ + // Only the player fires this way so we can cast + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + if (!pPlayer) + return; + + // Abort here to handle burst and auto fire modes + if ((UsesClipsForAmmo1() && m_iClip1 == 0) || (!UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType))) + return; + + if (m_bFullAuto) + { + m_nShotsFired++; + + pPlayer->DoMuzzleFlash(); + + // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, + // especially if the weapon we're firing has a really fast rate of fire. + int iBulletsToFire = 0; + float fireRate = GetFireRate(); + + // MUST call sound before removing a round from the clip of a CHLMachineGun + while (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + WeaponSound(SINGLE, m_flNextPrimaryAttack); + m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; + iBulletsToFire++; + } + + // Make sure we don't fire more than the amount in the clip, if this weapon uses clips + if (UsesClipsForAmmo1()) + { + if (iBulletsToFire > m_iClip1) + iBulletsToFire = m_iClip1; + m_iClip1 -= iBulletsToFire; + } + + // Fire the bullets + FireBulletsInfo_t info; + info.m_iShots = iBulletsToFire * m_nBulletsPerShot; + info.m_vecSrc = pPlayer->Weapon_ShootPosition(); + info.m_vecDirShooting = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); + info.m_vecSpread = pPlayer->GetAttackSpread(this); + info.m_flDistance = MAX_TRACE_LENGTH; + info.m_iAmmoType = m_iPrimaryAmmoType; + info.m_iTracerFreq = 2; + FireBullets(info); + } + else + { + if (!m_bNextAttackFromSequence && !m_bUsePumpAnimation && !(pPlayer->m_afButtonPressed & IN_ATTACK)) + return; + + m_nShotsFired++; + + // MUST call sound before removing a round from the clip of a CMachineGun + WeaponSound(SINGLE); + + pPlayer->DoMuzzleFlash(); + + m_flNextPrimaryAttack = gpGlobals->curtime + ((m_bNextAttackFromSequence || m_bUsePumpAnimation) ? GetViewModelSequenceDuration() : GetFireRate()); + m_iClip1 -= 1; + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); + + pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 1.0); + + // Fire the bullets, and force the first shot to be perfectly accuracy + pPlayer->FireBullets(m_nBulletsPerShot, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, (m_nBulletsPerShot > 1), true); + + if (m_bUsePumpAnimation && m_iClip1) + { + // pump so long as some rounds are left. + m_bNeedPump = true; + } + } + + m_iPrimaryAttacks++; + + //Factor in the view kick + AddViewKick(); + + CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pPlayer); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + SendWeaponAnim(GetPrimaryAttackActivity()); + pPlayer->SetAnimation(PLAYER_ATTACK1); + + // Register a muzzleflash for the AI + pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 0.5); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Activity +//----------------------------------------------------------------------------- +Activity CHLCustomWeaponGun::GetPrimaryAttackActivity(void) +{ + if (!m_bUseRecoilAnims || m_nShotsFired < 2) + return ACT_VM_PRIMARYATTACK; + + if (m_nShotsFired < 3) + return ACT_VM_RECOIL1; + + if (m_nShotsFired < 4) + return ACT_VM_RECOIL2; + + return ACT_VM_RECOIL3; +} + +//----------------------------------------------------------------------------- +// Purpose: Make enough sound events to fill the estimated think interval +// returns: number of shots needed +//----------------------------------------------------------------------------- +int CHLCustomWeaponGun::WeaponSoundRealtime(WeaponSound_t shoot_type) +{ + int numBullets = 0; + + // ran out of time, clamp to current + if (m_flNextSoundTime < gpGlobals->curtime) + { + m_flNextSoundTime = gpGlobals->curtime; + } + + // make enough sound events to fill up the next estimated think interval + float dt = Clamp(m_flAnimTime - m_flPrevAnimTime, 0.f, 0.2f); + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound(SINGLE_NPC, m_flNextSoundTime); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound(SINGLE_NPC, m_flNextSoundTime); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + + return numBullets; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOperator - +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FireNPCPrimaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles) +{ + Vector vecShootOrigin, vecShootDir; + CAI_BaseNPC* npc = pOperator->MyNPCPointer(); + int iMuzzle = LookupAttachment("muzzle"); + + ASSERT(npc != NULL); + + if (bUseWeaponAngles) + { + QAngle angShootDir; + GetAttachment(iMuzzle, vecShootOrigin, angShootDir); + AngleVectors(angShootDir, &vecShootDir); + } + else + { + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetActualShootTrajectory(vecShootOrigin); + } + + CSoundEnt::InsertSound(SOUND_COMBAT | SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2f, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy()); + + const Vector& vecSpread = (bUseWeaponAngles || m_nBulletsPerShot > 1) ? GetBulletSpread() : VECTOR_CONE_PRECALCULATED; + if (m_bFullAuto) + { + int nShots = WeaponSoundRealtime(SINGLE_NPC); + pOperator->FireBullets(nShots * m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - nShots; + } + else + { + WeaponSound(SINGLE_NPC); + pOperator->FireBullets(m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FireNPCSecondaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::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 CHLCustomWeaponGun::Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + switch (pEvent->event) + { + case EVENT_WEAPON_SMG1: + case EVENT_WEAPON_SHOTGUN_FIRE: + case EVENT_WEAPON_AR1: + case EVENT_WEAPON_AR2: + case EVENT_WEAPON_HMG1: + case EVENT_WEAPON_SMG2: + case EVENT_WEAPON_SNIPER_RIFLE_FIRE: + case EVENT_WEAPON_PISTOL_FIRE: + { + FireNPCPrimaryAttack(pOperator, false); + } + break; + + case EVENT_WEAPON_AR2_ALTFIRE: + { + FireNPCSecondaryAttack(pOperator, false); + } + break; + + default: + CBaseCombatWeapon::Operator_HandleAnimEvent(pEvent, pOperator); + break; + } +} diff --git a/sp/src/game/shared/basecombatweapon_shared.cpp b/sp/src/game/shared/basecombatweapon_shared.cpp index f505fafc..a187f1a0 100644 --- a/sp/src/game/shared/basecombatweapon_shared.cpp +++ b/sp/src/game/shared/basecombatweapon_shared.cpp @@ -2888,6 +2888,15 @@ bool CBaseCombatWeapon::IsLocked( CBaseEntity *pAsker ) return ( m_flUnlockTime > gpGlobals->curtime && m_hLocker != pAsker ); } +bool CBaseCombatWeapon::CanBePickedUpByNPCs(void) +{ +#ifdef MAPBASE + return GetWpnData().m_nWeaponRestriction != WPNRESTRICT_PLAYER_ONLY; +#else + return true; +#endif // MAPBASE +} + //----------------------------------------------------------------------------- // Purpose: // Input : diff --git a/sp/src/game/shared/basecombatweapon_shared.h b/sp/src/game/shared/basecombatweapon_shared.h index bd439b8c..1b411999 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -390,7 +390,7 @@ public: bool IsLocked( CBaseEntity *pAsker ); //All weapons can be picked up by NPCs by default - virtual bool CanBePickedUpByNPCs( void ) { return true; } + virtual bool CanBePickedUpByNPCs(void); virtual int GetSkinOverride() const { return -1; } diff --git a/sp/src/game/shared/weapon_parse.cpp b/sp/src/game/shared/weapon_parse.cpp index d8844916..872c6ad0 100644 --- a/sp/src/game/shared/weapon_parse.cpp +++ b/sp/src/game/shared/weapon_parse.cpp @@ -408,6 +408,7 @@ FileWeaponInfo_t::FileWeaponInfo_t() m_flSwaySpeedScale = 1.0f; szDroppedModel[0] = 0; m_bUsesHands = false; + m_nWeaponRestriction = WPNRESTRICT_NONE; #endif } @@ -415,6 +416,14 @@ FileWeaponInfo_t::FileWeaponInfo_t() extern ConVar hud_fastswitch; #endif +#ifdef MAPBASE +const char* pWeaponRestrictions[NUM_WEAPON_RESTRICTION_TYPES] = { + "none", + "player_only", + "npc_only", +}; +#endif // MAPBASE + void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) { // Okay, we tried at least once to look this up... @@ -483,6 +492,19 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam Q_strncpy( szDroppedModel, pKeyValuesData->GetString( "droppedmodel" ), MAX_WEAPON_STRING ); m_bUsesHands = ( pKeyValuesData->GetInt( "uses_hands", 0 ) != 0 ) ? true : false; + + const char* pszRestrictString = pKeyValuesData->GetString("usage_restriction", nullptr); + if (pszRestrictString) + { + for (int i = 0; i < NUM_WEAPON_RESTRICTION_TYPES; i++) + { + if (V_stricmp(pszRestrictString, pWeaponRestrictions[i]) == 0) + { + m_nWeaponRestriction = i; + break; + } + } + } #endif #ifndef MAPBASE // Mapbase makes weapons in the same slot & position swap each other out, which is a feature mods can intentionally use. diff --git a/sp/src/game/shared/weapon_parse.h b/sp/src/game/shared/weapon_parse.h index 28032744..4ba7b02c 100644 --- a/sp/src/game/shared/weapon_parse.h +++ b/sp/src/game/shared/weapon_parse.h @@ -58,6 +58,17 @@ int GetWeaponSoundFromString( const char *pszString ); class CHudTexture; class KeyValues; +#ifdef MAPBASE +enum WeaponUsageRestricions_e +{ + WPNRESTRICT_NONE = 0, + WPNRESTRICT_PLAYER_ONLY, + WPNRESTRICT_NPCS_ONLY, + + NUM_WEAPON_RESTRICTION_TYPES +}; +#endif // MAPBASE + //----------------------------------------------------------------------------- // Purpose: Contains the data read from the weapon's script file. // It's cached so we only read each weapon's script file once. @@ -125,6 +136,8 @@ public: char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground bool m_bUsesHands; + + int m_nWeaponRestriction; #endif // CLIENT DLL