From 5a0c406dbfdc7d10986f492921a116d089227f3d Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Sat, 16 Apr 2022 07:19:42 -0400 Subject: [PATCH 01/15] Added an entity factory manager for custom weapons --- .../server/mapbase/custom_weapon_factory.cpp | 178 ++++++++++++++++++ .../server/mapbase/custom_weapon_factory.h | 76 ++++++++ sp/src/game/server/server_mapbase.vpc | 2 + sp/src/game/server/util.cpp | 9 + sp/src/game/server/util.h | 3 + .../shared/mapbase/weapon_custom_scripted.cpp | 6 + .../shared/mapbase/weapon_custom_scripted.h | 8 + 7 files changed, 282 insertions(+) create mode 100644 sp/src/game/server/mapbase/custom_weapon_factory.cpp create mode 100644 sp/src/game/server/mapbase/custom_weapon_factory.h diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp new file mode 100644 index 0000000000..bb919e976b --- /dev/null +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -0,0 +1,178 @@ +#include "cbase.h" +#include "custom_weapon_factory.h" + +#define GENERIC_MANIFEST_FILE "scripts/mapbase_default_manifest.txt" +#define AUTOLOADED_MANIFEST_FILE UTIL_VarArgs("maps/%s_manifest.txt", MapName()) + +extern ConVar mapbase_load_default_manifest; + + + +CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactorySystem") +{ +} + +void CCustomWeaponSystem::LevelInitPreEntity() +{ + // Check for a generic "mapname_manifest.txt" file and load it. + if (filesystem->FileExists(AUTOLOADED_MANIFEST_FILE, "GAME")) + { + AddManifestFile(AUTOLOADED_MANIFEST_FILE); + } + else + { + // Load the generic script instead. + ParseGenericManifest(); + } +} + +// Get a generic, hardcoded manifest with hardcoded names. +void CCustomWeaponSystem::ParseGenericManifest() +{ + if (!mapbase_load_default_manifest.GetBool()) + return; + + KeyValues* pKV = new KeyValues("DefaultManifest"); + pKV->LoadFromFile(filesystem, GENERIC_MANIFEST_FILE); + + AddManifestFile(pKV/*, true*/); + + pKV->deleteThis(); +} + +void CCustomWeaponSystem::AddManifestFile(const char* file) +{ + KeyValues* pKV = new KeyValues(file); + if (!pKV->LoadFromFile(filesystem, file)) + { + Warning("Mapbase Manifest: \"%s\" is unreadable or missing (can't load KV, check for syntax errors)\n", file); + pKV->deleteThis(); + return; + } + + CGMsg(1, CON_GROUP_MAPBASE_MISC, "===== Mapbase Manifest: Loading manifest file %s =====\n", file); + + AddManifestFile(pKV, false); + + CGMsg(1, CON_GROUP_MAPBASE_MISC, "==============================================================================\n"); + + pKV->deleteThis(); +} + +void CCustomWeaponSystem::AddManifestFile(KeyValues* pKV, bool bDontWarn) +{ + KeyValues* pKey = pKV->FindKey("weapons"); + + if (pKey) + { + char value[MAX_PATH]; + // Parse %mapname%, etc. + bool inparam = false; + CUtlStringList outStrings; + V_SplitString(pKey->GetString(), "%", outStrings); + for (int i = 0; i < outStrings.Count(); i++) + { + if (inparam) + { + if (FStrEq(outStrings[i], "mapname")) + { + Q_strncat(value, MapName(), sizeof(value)); + } + else if (FStrEq(outStrings[i], "language")) + { +#ifdef CLIENT_DLL + char uilanguage[64]; + engine->GetUILanguage(uilanguage, sizeof(uilanguage)); + Q_strncat(value, uilanguage, sizeof(value)); +#else + // Give up, use English + Q_strncat(value, "english", sizeof(value)); +#endif + } + } + else + { + Q_strncat(value, outStrings[i], sizeof(value)); + } + + inparam = !inparam; + } + + outStrings.PurgeAndDeleteElements(); + bDontWarn = pKV->GetBool("NoErrors", bDontWarn); + + LoadCustomWeaponsManifest(value, bDontWarn); + } +} + +#define Factory CustomWeaponsFactoryDictionary() +void CCustomWeaponSystem::LoadCustomWeaponsManifest(const char* file, bool bDontWarn) +{ + KeyValuesAD pKV("weapons_manifest"); + if (pKV->LoadFromFile(filesystem, file, "GAME")) + { + for (KeyValues *pkvWeapon = pKV->GetFirstValue(); pkvWeapon != nullptr; pkvWeapon = pkvWeapon->GetNextValue()) + { + const char* pszClassname = pkvWeapon->GetName(); + KeyValuesAD pkvWeaponScript("WeaponData"); + if (pkvWeaponScript->LoadFromFile(filesystem, pkvWeapon->GetString(), "GAME")) + { + const char* pszFactory = pkvWeaponScript->GetString("custom_factory", nullptr); + unsigned short FactoryIndex = Factory.Find(pszFactory); + if (Factory.IsValidIndex(FactoryIndex)) + { + unsigned short ClassIndex = m_ClassFactories.Find(pszClassname); + if (!m_ClassFactories.IsValidIndex(ClassIndex)) + { + ClassIndex = m_ClassFactories.Insert(pszClassname); + m_ClassFactories[ClassIndex].pOldFactory = EntityFactoryDictionary()->FindFactory(pszClassname); + } + + m_ClassFactories[ClassIndex].sDataFile = file; + m_ClassFactories[ClassIndex].pNewFactory = Factory.Element(FactoryIndex); + EntityFactoryDictionary()->UninstallFactory(pszClassname); + EntityFactoryDictionary()->InstallFactory(m_ClassFactories[ClassIndex].pNewFactory, pszClassname); + } + } + } + } +} +#undef Factory + +void CCustomWeaponSystem::LevelShutdownPostEntity() +{ + for (int i = 0; i < m_ClassFactories.Count(); i++) + { + EntityFactoryDictionary()->UninstallFactory(m_ClassFactories.GetElementName(i)); + const CustomClassName_t& entry = m_ClassFactories.Element(i); + if (entry.pOldFactory) + EntityFactoryDictionary()->InstallFactory(entry.pOldFactory, m_ClassFactories.GetElementName(i)); + } + + m_ClassFactories.Purge(); +} + +void CCustomWeaponSystem::ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName) +{ + ICustomWeapon* pCustom = dynamic_cast (pWeapon); + if (!pCustom) + return; + + unsigned short i = m_ClassFactories.Find(pClassName); + if (!m_ClassFactories.IsValidIndex(i)) + return; + + pCustom->ParseCustomFromWeaponFile(m_ClassFactories[i].sDataFile.String()); +} + +CUtlDict& CustomWeaponsFactoryDictionary() +{ + static CUtlDict dict; + return dict; +} + +static CCustomWeaponSystem g_CustomWeaponsSystem; +CCustomWeaponSystem* CustomWeaponSystem() +{ + return &g_CustomWeaponsSystem; +} diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.h b/sp/src/game/server/mapbase/custom_weapon_factory.h new file mode 100644 index 0000000000..c1af48c6f3 --- /dev/null +++ b/sp/src/game/server/mapbase/custom_weapon_factory.h @@ -0,0 +1,76 @@ +#ifndef CUSTOM_WEAPON_FACTORY_H +#define CUSTOM_WEAPON_FACTORY_H +#pragma once +#include "utldict.h" +#include "utlsymbol.h" + +CUtlDict< IEntityFactory*, unsigned short >& CustomWeaponsFactoryDictionary(); + +class ICustomWeapon +{ +public: + virtual void ParseCustomFromWeaponFile(const char* pFileName) = 0; +}; + +class CCustomWeaponSystem : public CAutoGameSystem +{ +public: + CCustomWeaponSystem(); + + // Level init, shutdown + virtual void LevelInitPreEntity(); + virtual void LevelShutdownPostEntity(); + + void ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName); + +private: + void ParseGenericManifest(); + void AddManifestFile(const char* file); + void AddManifestFile(KeyValues* pKV, bool bDontWarn = false); + void LoadCustomWeaponsManifest(const char* file, bool bDontWarn = false); + + typedef struct CustomClassName_s + { + CUtlSymbol sDataFile; + IEntityFactory* pNewFactory; + IEntityFactory* pOldFactory; + } CustomClassName_t; + CUtlDict m_ClassFactories; +}; + +CCustomWeaponSystem* CustomWeaponSystem(); + +template +class CCustomWeaponEntityFactory : public IEntityFactory +{ +public: + CCustomWeaponEntityFactory(const char* pFactoryClass) + { + CustomWeaponsFactoryDictionary().Insert(pFactoryClass, this); + } + + IServerNetworkable* Create(const char* pClassName) + { + T* pEnt = _CreateEntityTemplate((T*)NULL, pClassName); + CustomWeaponSystem()->ParseWeapon(pEnt, pClassName); + return pEnt->NetworkProp(); + } + + void Destroy(IServerNetworkable* pNetworkable) + { + if (pNetworkable) + { + pNetworkable->Release(); + } + } + + virtual size_t GetEntitySize() + { + return sizeof(T); + } +}; + +#define DEFINE_CUSTOM_WEAPON_FACTORY(factoryName, DLLClassName) \ + static CCustomWeaponEntityFactory custom_weapon_##factoryName##_factory( #factoryName ); + +#endif // !CUSTOM_WEAPON_FACTORY_H diff --git a/sp/src/game/server/server_mapbase.vpc b/sp/src/game/server/server_mapbase.vpc index b04706d48d..345f50847c 100644 --- a/sp/src/game/server/server_mapbase.vpc +++ b/sp/src/game/server/server_mapbase.vpc @@ -63,6 +63,8 @@ $Project $File "mapbase\ai_grenade.h" $File "mapbase\ai_monitor.cpp" $File "mapbase\ai_weaponmodifier.cpp" + $File "mapbase\custom_weapon_factory.cpp" + $File "mapbase\custom_weapon_factory.h" $File "mapbase\closecaption_entity.cpp" $File "mapbase\datadesc_mod.cpp" $File "mapbase\datadesc_mod.h" diff --git a/sp/src/game/server/util.cpp b/sp/src/game/server/util.cpp index 8a97695a9f..6a44cec200 100644 --- a/sp/src/game/server/util.cpp +++ b/sp/src/game/server/util.cpp @@ -76,6 +76,10 @@ class CEntityFactoryDictionary : public IEntityFactoryDictionary virtual const char *GetCannonicalName( const char *pClassName ); void ReportEntitySizes(); +#ifdef MAPBASE + virtual void UninstallFactory(const char* pClassName); +#endif // MAPBASE + private: IEntityFactory *FindFactory( const char *pClassName ); public: @@ -203,6 +207,11 @@ void CEntityFactoryDictionary::ReportEntitySizes() } #ifdef MAPBASE +void CEntityFactoryDictionary::UninstallFactory(const char* pClassName) +{ + m_Factories.Remove(pClassName); +} + int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 ) { CEntityFactoryDictionary *pFactoryDict = (CEntityFactoryDictionary*)EntityFactoryDictionary(); diff --git a/sp/src/game/server/util.h b/sp/src/game/server/util.h index 82f485bacc..525ce341ee 100644 --- a/sp/src/game/server/util.h +++ b/sp/src/game/server/util.h @@ -100,6 +100,9 @@ abstract_class IEntityFactoryDictionary virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ) = 0; virtual IEntityFactory *FindFactory( const char *pClassName ) = 0; virtual const char *GetCannonicalName( const char *pClassName ) = 0; +#ifdef MAPBASE + virtual void UninstallFactory(const char* pClassName) = 0; +#endif // MAPBASE }; IEntityFactoryDictionary *EntityFactoryDictionary(); diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp index d9155f3219..a0dcbda26c 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -581,6 +581,12 @@ int CWeaponCustomScripted::WeaponMeleeAttack2Condition( float flDot, float flDis return BaseClass::WeaponMeleeAttack2Condition( flDot, flDist ); } + +DEFINE_CUSTOM_WEAPON_FACTORY(vscript, CWeaponCustomScripted); +void CWeaponCustomScripted::ParseCustomFromWeaponFile(const char* pFileName) +{ + Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 256); +} #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.h b/sp/src/game/shared/mapbase/weapon_custom_scripted.h index 924f10310b..f2a2a77d47 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.h +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.h @@ -14,6 +14,8 @@ #include "basecombatweapon_shared.h" #ifdef CLIENT_DLL #include "vscript_client.h" +#else +#include "mapbase/custom_weapon_factory.h" #endif // The base class of the scripted weapon is game-specific. @@ -32,6 +34,9 @@ HSCRIPT m_Func_##name; class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM +#ifndef CLIENT_DLL + , public ICustomWeapon +#endif // !CLIENT_DLL { public: DECLARE_CLASS( CWeaponCustomScripted, SCRIPTED_WEAPON_DERIVED_FROM ); @@ -106,6 +111,9 @@ class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM int WeaponRangeAttack2Condition( float flDot, float flDist ); int WeaponMeleeAttack1Condition( float flDot, float flDist ); int WeaponMeleeAttack2Condition( float flDot, float flDist ); + + // Inherited via ICustomWeapon + virtual void ParseCustomFromWeaponFile(const char* pFileName) override; #endif ALLOW_SCRIPT_ACCESS(); From 7877953b6cff641fa2fecd69d99b06430b4daa03 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Sat, 16 Apr 2022 09:18:06 -0400 Subject: [PATCH 02/15] Fixes --- sp/src/game/server/mapbase/custom_weapon_factory.cpp | 8 +++++--- sp/src/game/shared/mapbase/weapon_custom_scripted.cpp | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp index bb919e976b..dea0380c51 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.cpp +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -66,6 +66,8 @@ void CCustomWeaponSystem::AddManifestFile(KeyValues* pKV, bool bDontWarn) if (pKey) { char value[MAX_PATH]; + value[0] = '\0'; + // Parse %mapname%, etc. bool inparam = false; CUtlStringList outStrings; @@ -76,7 +78,7 @@ void CCustomWeaponSystem::AddManifestFile(KeyValues* pKV, bool bDontWarn) { if (FStrEq(outStrings[i], "mapname")) { - Q_strncat(value, MapName(), sizeof(value)); + Q_strncat(value, STRING(gpGlobals->mapname), sizeof(value)); } else if (FStrEq(outStrings[i], "language")) { @@ -128,7 +130,7 @@ void CCustomWeaponSystem::LoadCustomWeaponsManifest(const char* file, bool bDont m_ClassFactories[ClassIndex].pOldFactory = EntityFactoryDictionary()->FindFactory(pszClassname); } - m_ClassFactories[ClassIndex].sDataFile = file; + m_ClassFactories[ClassIndex].sDataFile = pkvWeapon->GetString(); m_ClassFactories[ClassIndex].pNewFactory = Factory.Element(FactoryIndex); EntityFactoryDictionary()->UninstallFactory(pszClassname); EntityFactoryDictionary()->InstallFactory(m_ClassFactories[ClassIndex].pNewFactory, pszClassname); @@ -141,7 +143,7 @@ void CCustomWeaponSystem::LoadCustomWeaponsManifest(const char* file, bool bDont void CCustomWeaponSystem::LevelShutdownPostEntity() { - for (int i = 0; i < m_ClassFactories.Count(); i++) + for (unsigned short i = 0; i < m_ClassFactories.Count(); i++) { EntityFactoryDictionary()->UninstallFactory(m_ClassFactories.GetElementName(i)); const CustomClassName_t& entry = m_ClassFactories.Element(i); diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp index a0dcbda26c..a3ca9d5739 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -427,7 +427,7 @@ void CWeaponCustomScripted::SecondaryAttack( void ) // Purpose: //----------------------------------------------------------------------------- #define ACTIVITY_FUNC_OVERRIDE( name ) ScriptVariant_t retVal; \ - if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal ) && retVal.m_bool == false) \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal ) && !retVal.IsNull()) \ { \ if (retVal.m_type == FIELD_INTEGER) \ { \ From 749f9ffae001a161b45c89a77f27fb341b11c541 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Sun, 17 Apr 2022 15:57:37 -0400 Subject: [PATCH 03/15] Finished parser for vscript weapon --- .../server/mapbase/custom_weapon_factory.cpp | 5 +- .../shared/mapbase/weapon_custom_scripted.cpp | 118 ++++++++++++++++-- .../shared/mapbase/weapon_custom_scripted.h | 4 + 3 files changed, 119 insertions(+), 8 deletions(-) diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp index dea0380c51..5a5d74cb1f 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.cpp +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -2,7 +2,8 @@ #include "custom_weapon_factory.h" #define GENERIC_MANIFEST_FILE "scripts/mapbase_default_manifest.txt" -#define AUTOLOADED_MANIFEST_FILE UTIL_VarArgs("maps/%s_manifest.txt", MapName()) +#define AUTOLOADED_MANIFEST_FILE UTIL_VarArgs("maps/%s_manifest.txt", STRING(gpGlobals->mapname)) +#define GLOBAL_WEAPONS_MANIFEST "scripts/custom_weapon_manifest.txt" extern ConVar mapbase_load_default_manifest; @@ -14,6 +15,8 @@ CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactor void CCustomWeaponSystem::LevelInitPreEntity() { + AddManifestFile(GLOBAL_WEAPONS_MANIFEST); + // Check for a generic "mapname_manifest.txt" file and load it. if (filesystem->FileExists(AUTOLOADED_MANIFEST_FILE, "GAME")) { diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp index a3ca9d5739..10553de547 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -7,6 +7,7 @@ #include "cbase.h" #include "tier1/fmtstr.h" +#include "tier1/utlvector.h" #include "weapon_custom_scripted.h" // memdbgon must be the last include file in a .cpp file!!! @@ -193,13 +194,6 @@ bool CWeaponCustomScripted::RunWeaponHook( ScriptHook_t &hook, HSCRIPT &cached, //----------------------------------------------------------------------------- void CWeaponCustomScripted::Spawn( void ) { -#ifdef CLIENT_DLL - if (m_iszClientScripts[0] != '\0' && ValidateScriptScope()) - { - RunScriptFile( m_iszClientScripts ); - } -#endif - BaseClass::Spawn(); } @@ -586,6 +580,23 @@ DEFINE_CUSTOM_WEAPON_FACTORY(vscript, CWeaponCustomScripted); void CWeaponCustomScripted::ParseCustomFromWeaponFile(const char* pFileName) { Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 256); + KeyValuesAD pKVWeapon("WeaponData"); + if (pKVWeapon->LoadFromFile(filesystem, pFileName, "GAME")) + { + Q_strncpy(m_iszClientScripts.GetForModify(), pKVWeapon->GetString("vscript_file"), 256); + } +} + +extern ConVar sv_script_think_interval; +#else +void CWeaponCustomScripted::OnDataChanged(DataUpdateType_t type) +{ + BaseClass::OnDataChanged(type); + + if (!m_ScriptScope.IsInitialized()) + { + RunVScripts(); + } } #endif @@ -605,3 +616,96 @@ int CWeaponCustomScripted::ActivityListCount( void ) return BaseClass::ActivityListCount(); } + +void CWeaponCustomScripted::RunVScripts() +{ +#ifdef CLIENT_DLL + if (m_iszClientScripts[0] != '\0' && ValidateScriptScope()) + { + RunScriptFile(m_iszClientScripts); + } +#else + if (m_iszVScripts == NULL_STRING && m_iszClientScripts[0] == '\0') + { + return; + } + +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM == NULL) + { + return; + } +#endif + + ValidateScriptScope(); + + // All functions we want to have call chained instead of overwritten + // by other scripts in this entities list. + static const char* sCallChainFunctions[] = + { + "OnPostSpawn", + "Precache" + }; + + ScriptLanguage_t language = g_pScriptVM->GetLanguage(); + + // Make a call chainer for each in this entities scope + for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j) + { + + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter + HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript(CFmtStr("%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j])); + g_pScriptVM->Run(hCreateChainScript, (HSCRIPT)m_ScriptScope); + } + } + + CUtlStringList szScripts; + if (m_iszVScripts != NULL_STRING) + { + V_SplitString(STRING(m_iszVScripts), " ", szScripts); + } + + if (m_iszClientScripts[0] != '\0') + { + szScripts.AddToHead(strdup(m_iszClientScripts.Get())); + } + + for (int i = 0; i < szScripts.Count(); i++) + { +#ifdef MAPBASE + CGMsg(0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i]); +#else + Log("%s executing script: %s\n", GetDebugName(), szScripts[i]); +#endif + + RunScriptFile(szScripts[i], IsWorld()); + + for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j) + { + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter. + HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript(CFmtStr("%sCallChain.PostScriptExecute()", sCallChainFunctions[j])); + g_pScriptVM->Run(hRunPostScriptExecute, (HSCRIPT)m_ScriptScope); + } + } + } + + if (m_iszScriptThinkFunction != NULL_STRING) + { + SetContextThink(&CBaseEntity::ScriptThink, gpGlobals->curtime + sv_script_think_interval.GetFloat(), "ScriptThink"); + } +#endif +} \ No newline at end of file diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.h b/sp/src/game/shared/mapbase/weapon_custom_scripted.h index f2a2a77d47..8cc05cd318 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.h +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.h @@ -50,6 +50,8 @@ class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM bool KeyValue( const char *szKeyName, const char *szValue ); bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + void RunVScripts(); + // Base script has a function for this //void Precache( void ); @@ -114,6 +116,8 @@ class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM // Inherited via ICustomWeapon virtual void ParseCustomFromWeaponFile(const char* pFileName) override; +#else + void OnDataChanged(DataUpdateType_t type); #endif ALLOW_SCRIPT_ACCESS(); From 332856e37efd4e310c61acded9ef8dcd9911a8b0 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Mon, 18 Apr 2022 15:09:57 -0400 Subject: [PATCH 04/15] Added factory for melee weapons --- sp/src/game/client/client_mapbase.vpc | 1 + .../client/mapbase/c_weapon_custom_hl2.cpp | 34 ++ sp/src/game/server/basebludgeonweapon.cpp | 7 +- sp/src/game/server/basebludgeonweapon.h | 4 + .../game/server/mapbase/weapon_custom_hl2.cpp | 296 ++++++++++++++++++ sp/src/game/server/server_mapbase.vpc | 1 + 6 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp create mode 100644 sp/src/game/server/mapbase/weapon_custom_hl2.cpp diff --git a/sp/src/game/client/client_mapbase.vpc b/sp/src/game/client/client_mapbase.vpc index 97a2217d3c..2a8f2afead 100644 --- a/sp/src/game/client/client_mapbase.vpc +++ b/sp/src/game/client/client_mapbase.vpc @@ -65,6 +65,7 @@ $Project $File "mapbase\c_func_fake_worldportal.h" $File "mapbase\c_point_glow.cpp" $File "mapbase\c_vgui_text_display.cpp" + $File "mapbase\c_weapon_custom_hl2.cpp" $File "mapbase\mapbase_autocubemap.cpp" } diff --git a/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp new file mode 100644 index 0000000000..58c46e2506 --- /dev/null +++ b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp @@ -0,0 +1,34 @@ +#include "cbase.h" +#include "c_weapon__stubs.h" +#include "basehlcombatweapon_shared.h" +#include "c_basehlcombatweapon.h" + +// 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: + DECLARE_CLASS(C_HLCustomWeaponMelee, C_BaseHLBludgeonWeapon); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_HLCustomWeaponMelee(); + + virtual const char* GetWeaponScriptName() { return m_iszWeaponScriptName; } +private: + char m_iszWeaponScriptName[128]; +}; + +STUB_WEAPON_CLASS_IMPLEMENT(weapon_hlcustommelee, C_HLCustomWeaponMelee); + +IMPLEMENT_CLIENTCLASS_DT(C_HLCustomWeaponMelee, DT_HLCustomWeaponMelee, CHLCustomWeaponMelee) +RecvPropString(RECVINFO(m_iszWeaponScriptName)), +END_RECV_TABLE(); + +C_HLCustomWeaponMelee::C_HLCustomWeaponMelee() +{ + m_iszWeaponScriptName[0] = '\0'; +} +#pragma endregion \ No newline at end of file diff --git a/sp/src/game/server/basebludgeonweapon.cpp b/sp/src/game/server/basebludgeonweapon.cpp index 57683a19a0..9390361656 100644 --- a/sp/src/game/server/basebludgeonweapon.cpp +++ b/sp/src/game/server/basebludgeonweapon.cpp @@ -162,7 +162,12 @@ void CBaseHLBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity, bool pPlayer->EyeVectors( &hitDirection, NULL, NULL ); VectorNormalize( hitDirection ); - CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#ifdef MAPBASE + CTakeDamageInfo info(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), GetDamageType()); +#else + CTakeDamageInfo info(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), DMG_CLUB); +#endif // MAPBASE + if( pPlayer && pHitEntity->IsNPC() ) { diff --git a/sp/src/game/server/basebludgeonweapon.h b/sp/src/game/server/basebludgeonweapon.h index 6f2a7eaf3d..52f02f9b3d 100644 --- a/sp/src/game/server/basebludgeonweapon.h +++ b/sp/src/game/server/basebludgeonweapon.h @@ -44,6 +44,10 @@ class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon virtual int CapabilitiesGet( void ); virtual int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#ifdef MAPBASE + virtual int GetDamageType() { return DMG_CLUB; } +#endif // MAPBASE + protected: virtual void ImpactEffect( trace_t &trace ); diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp new file mode 100644 index 0000000000..5d966d29a9 --- /dev/null +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -0,0 +1,296 @@ +#include "cbase.h" +#include "custom_weapon_factory.h" +#include "basebludgeonweapon.h" +#include "ai_basenpc.h" +#include "player.h" +#include "npcevent.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#pragma region Melee +const char* g_ppszDamageClasses[] = { + "BLUNT", + "SLASH", + "STUN", + "BURN", +}; + +int g_nDamageClassTypeBits[ARRAYSIZE(g_ppszDamageClasses)] = { + DMG_CLUB, + DMG_SLASH, + DMG_CLUB|DMG_SHOCK, + DMG_CLUB|DMG_BURN, +}; + +class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon +{ +public: + DECLARE_CLASS(CHLCustomWeaponMelee, CBaseHLBludgeonWeapon); + + DECLARE_SERVERCLASS(); + DECLARE_ACTTABLE(); + + CHLCustomWeaponMelee(); + + float GetRange(void) { return m_flMeleeRange; } + float GetFireRate(void) { return m_flRefireRate; } + + void AddViewKick(void); + float GetDamageForActivity(Activity hitActivity); + + virtual int WeaponMeleeAttack1Condition(float flDot, float flDist); + void SecondaryAttack(void) { return; } + + // Animation event + virtual void Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + + // Don't use backup activities + acttable_t* GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } + + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + virtual int GetDamageType() { return g_nDamageClassTypeBits[m_nDamageClass]; } + + virtual void ParseCustomFromWeaponFile(const char* pFileName); + +private: + // Animation event handlers + void HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + +private: + float m_flMeleeRange; + float m_flRefireRate; + float m_flDamage; + float m_flNPCDamage; + byte m_nDamageClass; + + CNetworkString(m_iszWeaponScriptName, 128); +}; + +IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponMelee, DT_HLCustomWeaponMelee) +SendPropString(SENDINFO(m_iszWeaponScriptName)), +END_SEND_TABLE(); + +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_melee, CHLCustomWeaponMelee); + +acttable_t CHLCustomWeaponMelee::m_acttable[] = +{ + { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, + { ACT_GESTURE_MELEE_ATTACK1, ACT_GESTURE_MELEE_ATTACK_SWING, false}, + + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_MELEE, false }, + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, + + { ACT_ARM, ACT_ARM_MELEE, false }, + { ACT_DISARM, ACT_DISARM_MELEE, false }, +#else + { ACT_IDLE, ACT_IDLE_ANGRY_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(CHLCustomWeaponMelee); + +CHLCustomWeaponMelee::CHLCustomWeaponMelee() +{ + m_nDamageClass = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CHLCustomWeaponMelee::GetDamageForActivity(Activity hitActivity) +{ + if ((GetOwner() != NULL) && (GetOwner()->IsPlayer())) + return m_flDamage; + + return m_flNPCDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Add in a view kick for this weapon +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::AddViewKick(void) +{ + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (pPlayer == NULL) + return; + + QAngle punchAng; + + punchAng.x = random->RandomFloat(1.0f, 2.0f); + punchAng.y = random->RandomFloat(-2.0f, -1.0f); + punchAng.z = 0.0f; + + pPlayer->ViewPunch(punchAng); +} + + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +extern ConVar sk_crowbar_lead_time; + +int CHLCustomWeaponMelee::WeaponMeleeAttack1Condition(float flDot, float flDist) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC* pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity* pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + vecVelocity = pEnemy->GetSmoothedVelocity(); + + // Project where the enemy will be in a little while + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random->RandomFloat(-0.3f, 0.2f); + if (dt < 0.0f) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA(pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos); + + Vector vecDelta; + VectorSubtract(vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta); + + if (fabs(vecDelta.z) > 70) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D(); + vecDelta.z = 0.0f; + float flExtrapolatedDist = Vector2DNormalize(vecDelta.AsVector2D()); + if ((flDist > 64) && (flExtrapolatedDist > 64)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + float flExtrapolatedDot = DotProduct2D(vecDelta.AsVector2D(), vecForward.AsVector2D()); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + + +//----------------------------------------------------------------------------- +// Animation event handlers +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::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), m_flNPCDamage, GetDamageType(), 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 CHLCustomWeaponMelee::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; + } +} + +void CHLCustomWeaponMelee::ParseCustomFromWeaponFile(const char* pFileName) +{ + Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 128); + KeyValuesAD pKVWeapon("WeaponData"); + if (pKVWeapon->LoadFromFile(filesystem, pFileName, "GAME")) + { + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flDamage = pkvData->GetFloat("damage"); + m_flNPCDamage = pkvData->GetFloat("damage_npc", m_flDamage); + m_flMeleeRange = pkvData->GetFloat("range", 70.f); + m_flRefireRate = pkvData->GetFloat("rate", 0.7f); + + const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); + if (pszDamageClass) + { + for (byte i = 0; i < ARRAYSIZE(g_ppszDamageClasses); i++) + { + if (V_stricmp(pszDamageClass, g_ppszDamageClasses[i]) == 0) + { + m_nDamageClass = i; + break; + } + } + } + } + } +} +#pragma endregion \ No newline at end of file diff --git a/sp/src/game/server/server_mapbase.vpc b/sp/src/game/server/server_mapbase.vpc index 345f50847c..7d74847e12 100644 --- a/sp/src/game/server/server_mapbase.vpc +++ b/sp/src/game/server/server_mapbase.vpc @@ -88,6 +88,7 @@ $Project $File "mapbase\SystemConvarMod.h" $File "mapbase\variant_tools.h" $File "mapbase\vgui_text_display.cpp" + $File "mapbase\weapon_custom_hl2.cpp" $File "mapbase\logic_eventlistener.cpp" $File "mapbase\logic_register_activator.cpp" From f58b5990206eeda5f73d902aefd6123864cecb05 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Mon, 18 Apr 2022 17:24:41 -0400 Subject: [PATCH 05/15] Fixed custom weapon scripts on client --- sp/src/game/client/c_basecombatweapon.cpp | 7 +++++++ sp/src/game/shared/basecombatweapon_shared.cpp | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/sp/src/game/client/c_basecombatweapon.cpp b/sp/src/game/client/c_basecombatweapon.cpp index 3ae13cdee8..bdb71f02e0 100644 --- a/sp/src/game/client/c_basecombatweapon.cpp +++ b/sp/src/game/client/c_basecombatweapon.cpp @@ -151,6 +151,13 @@ int C_BaseCombatWeapon::GetWorldModelIndex( void ) //----------------------------------------------------------------------------- void C_BaseCombatWeapon::OnDataChanged( DataUpdateType_t updateType ) { +#ifdef MAPBASE + if (updateType == DATA_UPDATE_CREATED) + { + Precache(); + } +#endif // MAPBASE + BaseClass::OnDataChanged(updateType); CHandle< C_BaseCombatWeapon > handle = this; diff --git a/sp/src/game/shared/basecombatweapon_shared.cpp b/sp/src/game/shared/basecombatweapon_shared.cpp index 22fe8b977c..f505fafc21 100644 --- a/sp/src/game/shared/basecombatweapon_shared.cpp +++ b/sp/src/game/shared/basecombatweapon_shared.cpp @@ -164,7 +164,9 @@ void CBaseCombatWeapon::GiveDefaultAmmo( void ) //----------------------------------------------------------------------------- void CBaseCombatWeapon::Spawn( void ) { +#if !defined(CLIENT_DLL) || !defined(MAPBASE) Precache(); +#endif // !defined(CLIENT_DLL) || !defined(MAPBASE) BaseClass::Spawn(); @@ -239,7 +241,7 @@ const unsigned char *CBaseCombatWeapon::GetEncryptionKey( void ) void CBaseCombatWeapon::Precache( void ) { #if defined( CLIENT_DLL ) - Assert( Q_strlen( GetClassname() ) > 0 ); + Assert( Q_strlen(GetWeaponScriptName() ) > 0 ); // Msg( "Client got %s\n", GetClassname() ); #endif m_iPrimaryAmmoType = m_iSecondaryAmmoType = -1; @@ -321,7 +323,7 @@ void CBaseCombatWeapon::Precache( void ) else { // Couldn't read data file, remove myself - Warning( "Error reading weapon data file for: %s\n", GetClassname() ); + Warning( "Error reading weapon data file for: %s\n", GetWeaponScriptName() ); // Remove( ); //don't remove, this gets released soon! } } From e77180547de4a96f952b73fb242d776a41ee5e65 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Mon, 18 Apr 2022 17:25:25 -0400 Subject: [PATCH 06/15] Readiness activities for custom melee weapon --- sp/src/game/server/mapbase/weapon_custom_hl2.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 5d966d29a9..9249198a90 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -87,6 +87,17 @@ acttable_t CHLCustomWeaponMelee::m_acttable[] = { ACT_ARM, ACT_ARM_MELEE, false }, { ACT_DISARM, ACT_DISARM_MELEE, false }, + + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_MELEE, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_MELEE, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + //End readiness activities #else { ACT_IDLE, ACT_IDLE_ANGRY_MELEE, false }, #endif From ee46bc4bd11ad6baffde36dc30de0e267df0d70b Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Mon, 18 Apr 2022 18:12:40 -0400 Subject: [PATCH 07/15] Fixes --- .../server/mapbase/custom_weapon_factory.cpp | 2 +- .../game/server/mapbase/weapon_custom_hl2.cpp | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp index 5a5d74cb1f..e010bc3fed 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.cpp +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -15,7 +15,7 @@ CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactor void CCustomWeaponSystem::LevelInitPreEntity() { - AddManifestFile(GLOBAL_WEAPONS_MANIFEST); + LoadCustomWeaponsManifest(GLOBAL_WEAPONS_MANIFEST); // Check for a generic "mapname_manifest.txt" file and load it. if (filesystem->FileExists(AUTOLOADED_MANIFEST_FILE, "GAME")) diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 9249198a90..22c2aad7e4 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -89,14 +89,30 @@ acttable_t CHLCustomWeaponMelee::m_acttable[] = { ACT_DISARM, ACT_DISARM_MELEE, false }, // Readiness activities (not aiming) - { ACT_IDLE_RELAXED, ACT_IDLE_MELEE, false },//never aims - { ACT_IDLE_STIMULATED, ACT_IDLE_MELEE, false }, - { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + { ACT_IDLE_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_MELEE, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_MELEE, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_MELEE, false }, + { ACT_WALK_AGITATED, ACT_WALK_MELEE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_MELEE, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_MELEE, false }, + { ACT_RUN_AGITATED, ACT_RUN_MELEE, false },//always aims // Readiness activities (aiming) - { ACT_IDLE_AIM_RELAXED, ACT_IDLE_MELEE, false },//never aims - { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_MELEE, false }, - { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_MELEE, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_MELEE, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_MELEE, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_MELEE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_MELEE, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_MELEE, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_MELEE, false },//always aims //End readiness activities #else { ACT_IDLE, ACT_IDLE_ANGRY_MELEE, false }, From 179b7a529881359edff5b812d69557dbc12034f2 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Mon, 18 Apr 2022 23:46:23 -0400 Subject: [PATCH 08/15] 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 58c46e2506..8c89f2d72b 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 f4b3539204..5ea5c65d21 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 6b7ecdd9ce..835fcc6a3e 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 8a0f306bfa..aaed3de062 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 e010bc3fed..a8e17146b9 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 c1af48c6f3..fc01045db2 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 @@ class CCustomWeaponSystem : public CAutoGameSystem 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 22c2aad7e4..c2881c6fbf 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 f505fafc21..a187f1a0d3 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 bd439b8c3f..1b4119998e 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -390,7 +390,7 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM 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 d8844916e4..872c6ad0e9 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 280327447d..4ba7b02cc6 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 @@ class FileWeaponInfo_t char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground bool m_bUsesHands; + + int m_nWeaponRestriction; #endif // CLIENT DLL From 13f422f4c42d4c7613538ddd4bf766f65be258b0 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Tue, 19 Apr 2022 00:57:05 -0400 Subject: [PATCH 09/15] Fix issues --- sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp | 4 ++-- sp/src/game/server/mapbase/weapon_custom_hl2.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 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 8c89f2d72b..9f97a12f10 100644 --- a/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp +++ b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp @@ -33,10 +33,10 @@ C_HLCustomWeaponMelee::C_HLCustomWeaponMelee() -class C_HLCustomWeaponGun : public C_BaseHLBludgeonWeapon +class C_HLCustomWeaponGun : public C_BaseHLCombatWeapon { public: - DECLARE_CLASS(C_HLCustomWeaponGun, C_BaseHLBludgeonWeapon); + DECLARE_CLASS(C_HLCustomWeaponGun, C_BaseHLCombatWeapon); DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index c2881c6fbf..7a374386fb 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -354,6 +354,7 @@ class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon CHLCustomWeaponGun(); virtual void ParseCustomFromWeaponFile(const char* pFileName); + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } // Weapon behaviour virtual void ItemPostFrame(void); // called each frame by the player PostThink @@ -672,9 +673,9 @@ void CHLCustomWeaponGun::ParseCustomFromWeaponFile(const char* pFileName) 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)); + m_vPlayerSpread = Vector(sin(DEG2RAD(flSpread * 0.5f))); + m_vNPCSpread = Vector(sin(DEG2RAD(flNPCSpread * 0.5f))); + m_vAllySpread = Vector(sin(DEG2RAD(flAllySperad * 0.5f))); const char* pszAnimType = pkvData->GetString("anim_type", nullptr); if (pszAnimType) From 0ae65a5a64eefeaf2e8db2a838964a1b1015a47f Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Tue, 19 Apr 2022 02:53:35 -0400 Subject: [PATCH 10/15] Fix weapon selection --- sp/src/game/client/prediction.cpp | 2 +- sp/src/game/server/player_command.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sp/src/game/client/prediction.cpp b/sp/src/game/client/prediction.cpp index d7dfbff7e0..6d3a0439a1 100644 --- a/sp/src/game/client/prediction.cpp +++ b/sp/src/game/client/prediction.cpp @@ -855,7 +855,7 @@ void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper C_BaseCombatWeapon *weapon = dynamic_cast< C_BaseCombatWeapon * >( CBaseEntity::Instance( ucmd->weaponselect ) ); if ( weapon ) { - player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); } } diff --git a/sp/src/game/server/player_command.cpp b/sp/src/game/server/player_command.cpp index d5a6a1f08d..7614babee0 100644 --- a/sp/src/game/server/player_command.cpp +++ b/sp/src/game/server/player_command.cpp @@ -393,7 +393,7 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper if ( weapon ) { VPROF( "player->SelectItem()" ); - player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); } } From d871c6f8fdbdfb159c5035545b297ea6317f1225 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Tue, 19 Apr 2022 03:16:54 -0400 Subject: [PATCH 11/15] Add Mapbase file headers --- sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp | 8 ++++++++ sp/src/game/server/mapbase/custom_weapon_factory.cpp | 8 ++++++++ sp/src/game/server/mapbase/custom_weapon_factory.h | 8 ++++++++ sp/src/game/server/mapbase/weapon_custom_hl2.cpp | 8 ++++++++ 4 files changed, 32 insertions(+) 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 9f97a12f10..71675b0684 100644 --- a/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp +++ b/sp/src/game/client/mapbase/c_weapon_custom_hl2.cpp @@ -1,3 +1,11 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Client classes for Half-Life 2 based custom weapons. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + #include "cbase.h" #include "c_weapon__stubs.h" #include "basehlcombatweapon_shared.h" diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp index a8e17146b9..f742441228 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.cpp +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -1,3 +1,11 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: The central manager of the custom weapons system. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + #include "cbase.h" #include "custom_weapon_factory.h" diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.h b/sp/src/game/server/mapbase/custom_weapon_factory.h index fc01045db2..f027ea86ac 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.h +++ b/sp/src/game/server/mapbase/custom_weapon_factory.h @@ -1,3 +1,11 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: See custom_weapon_factory.cpp +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + #ifndef CUSTOM_WEAPON_FACTORY_H #define CUSTOM_WEAPON_FACTORY_H #pragma once diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 7a374386fb..af29357c96 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -1,3 +1,11 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Custom weapon classes for Half-Life 2 based weapons. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + #include "cbase.h" #include "custom_weapon_factory.h" #include "basebludgeonweapon.h" From fa41a327e7753a3b948eac0ca60f9b351db10fe9 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Wed, 20 Apr 2022 01:48:32 -0400 Subject: [PATCH 12/15] Added a data cache to the custom weapons system --- .../server/mapbase/custom_weapon_factory.cpp | 24 +- .../server/mapbase/custom_weapon_factory.h | 45 +- .../game/server/mapbase/weapon_custom_hl2.cpp | 385 ++++++++++-------- .../shared/mapbase/weapon_custom_scripted.cpp | 19 +- .../shared/mapbase/weapon_custom_scripted.h | 2 +- 5 files changed, 277 insertions(+), 198 deletions(-) diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.cpp b/sp/src/game/server/mapbase/custom_weapon_factory.cpp index f742441228..2feaec16fb 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.cpp +++ b/sp/src/game/server/mapbase/custom_weapon_factory.cpp @@ -134,15 +134,28 @@ void CCustomWeaponSystem::LoadCustomWeaponsManifest(const char* file, bool bDont unsigned short FactoryIndex = Factory.Find(pszFactory); if (Factory.IsValidIndex(FactoryIndex)) { + auto* pFactory = Factory.Element(FactoryIndex); + const void* pData = pFactory->ParseDataFromWeaponFile(pkvWeaponScript); + if (!pData) + continue; + unsigned short ClassIndex = m_ClassFactories.Find(pszClassname); if (!m_ClassFactories.IsValidIndex(ClassIndex)) { ClassIndex = m_ClassFactories.Insert(pszClassname); m_ClassFactories[ClassIndex].pOldFactory = EntityFactoryDictionary()->FindFactory(pszClassname); } + else + { + Assert(m_ClassFactories[ClassIndex].pNewFactory); + Assert(m_ClassFactories[ClassIndex].pData); + + m_ClassFactories[ClassIndex].pNewFactory->ReleaseData(m_ClassFactories[ClassIndex].pData); + } m_ClassFactories[ClassIndex].sDataFile = pkvWeapon->GetString(); - m_ClassFactories[ClassIndex].pNewFactory = Factory.Element(FactoryIndex); + m_ClassFactories[ClassIndex].pNewFactory = pFactory; + m_ClassFactories[ClassIndex].pData = pData; EntityFactoryDictionary()->UninstallFactory(pszClassname); EntityFactoryDictionary()->InstallFactory(m_ClassFactories[ClassIndex].pNewFactory, pszClassname); } @@ -160,6 +173,9 @@ void CCustomWeaponSystem::LevelShutdownPostEntity() const CustomClassName_t& entry = m_ClassFactories.Element(i); if (entry.pOldFactory) EntityFactoryDictionary()->InstallFactory(entry.pOldFactory, m_ClassFactories.GetElementName(i)); + + Assert(entry.pData); + entry.pNewFactory->ReleaseData(entry.pData); } m_ClassFactories.Purge(); @@ -176,12 +192,12 @@ void CCustomWeaponSystem::ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pC if (!m_ClassFactories.IsValidIndex(i)) return; - pCustom->ParseCustomFromWeaponFile(m_ClassFactories[i].sDataFile.String()); + pCustom->InitCustomWeaponFromData(m_ClassFactories[i].pData, m_ClassFactories[i].sDataFile.String()); } -CUtlDict& CustomWeaponsFactoryDictionary() +CUtlDict& CustomWeaponsFactoryDictionary() { - static CUtlDict dict; + static CUtlDict dict; return dict; } diff --git a/sp/src/game/server/mapbase/custom_weapon_factory.h b/sp/src/game/server/mapbase/custom_weapon_factory.h index f027ea86ac..838fd695fb 100644 --- a/sp/src/game/server/mapbase/custom_weapon_factory.h +++ b/sp/src/game/server/mapbase/custom_weapon_factory.h @@ -14,12 +14,17 @@ DECLARE_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); -CUtlDict< IEntityFactory*, unsigned short >& CustomWeaponsFactoryDictionary(); +class ICustomWeaponDataLoader : public IEntityFactory +{ +public: + virtual const void* ParseDataFromWeaponFile(KeyValues* pKV) const = 0; + virtual void ReleaseData(const void* pData) const = 0; +}; class ICustomWeapon { public: - virtual void ParseCustomFromWeaponFile(const char* pFileName) = 0; + virtual void InitCustomWeaponFromData(const void* pData, const char *pszWeaponScript) = 0; }; class CCustomWeaponSystem : public CAutoGameSystem @@ -42,19 +47,22 @@ class CCustomWeaponSystem : public CAutoGameSystem typedef struct CustomClassName_s { CustomWeaponSymbol sDataFile; - IEntityFactory* pNewFactory; + ICustomWeaponDataLoader* pNewFactory; IEntityFactory* pOldFactory; + const void* pData; } CustomClassName_t; CUtlDict m_ClassFactories; }; CCustomWeaponSystem* CustomWeaponSystem(); +CUtlDict< ICustomWeaponDataLoader*, unsigned short >& CustomWeaponsFactoryDictionary(); + template -class CCustomWeaponEntityFactory : public IEntityFactory +class CCustomWeaponEntityFactoryBase : public ICustomWeaponDataLoader { public: - CCustomWeaponEntityFactory(const char* pFactoryClass) + CCustomWeaponEntityFactoryBase(const char* pFactoryClass) { CustomWeaponsFactoryDictionary().Insert(pFactoryClass, this); } @@ -80,7 +88,30 @@ class CCustomWeaponEntityFactory : public IEntityFactory } }; -#define DEFINE_CUSTOM_WEAPON_FACTORY(factoryName, DLLClassName) \ - static CCustomWeaponEntityFactory custom_weapon_##factoryName##_factory( #factoryName ); +template +class CDefaultCustomWeaponEntityFactory : public CCustomWeaponEntityFactoryBase +{ +public: + CDefaultCustomWeaponEntityFactory(const char *pFactoryClass) : CCustomWeaponEntityFactoryBase(pFactoryClass) + {} + + virtual const void* ParseDataFromWeaponFile(KeyValues* pKV) const + { + Data* pData = new Data; + if (pData && pData->Parse(pKV)) + return pData; + + delete pData; + return nullptr; + } + + virtual void ReleaseData(const void* pData) const + { + delete pData; + } +}; + +#define DEFINE_CUSTOM_WEAPON_FACTORY(factoryName, DLLClassName, DataStruct) \ + static CDefaultCustomWeaponEntityFactory custom_weapon_##factoryName##_factory( #factoryName ); #endif // !CUSTOM_WEAPON_FACTORY_H diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index af29357c96..86c9733b82 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -49,6 +49,17 @@ int g_nDamageClassTypeBits[ARRAYSIZE(g_ppszDamageClasses)] = { DMG_CLUB|DMG_BURN, }; +typedef struct HL2CustomMeleeData_s +{ + float m_flMeleeRange; + float m_flRefireRate; + float m_flDamage; + float m_flNPCDamage; + byte m_nDamageClass; + + bool Parse(KeyValues*); +} HL2CustomMeleeData_t; + class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon { public: @@ -59,8 +70,8 @@ class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon CHLCustomWeaponMelee(); - float GetRange(void) { return m_flMeleeRange; } - float GetFireRate(void) { return m_flRefireRate; } + float GetRange(void) { return m_CustomData.m_flMeleeRange; } + float GetFireRate(void) { return m_CustomData.m_flRefireRate; } void AddViewKick(void); float GetDamageForActivity(Activity hitActivity); @@ -76,20 +87,16 @@ class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon int GetBackupActivityListCount() { return 0; } const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } - virtual int GetDamageType() { return g_nDamageClassTypeBits[m_nDamageClass]; } + virtual int GetDamageType() { return g_nDamageClassTypeBits[m_CustomData.m_nDamageClass]; } - virtual void ParseCustomFromWeaponFile(const char* pFileName); + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); private: // Animation event handlers void HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCombatCharacter* pOperator); private: - float m_flMeleeRange; - float m_flRefireRate; - float m_flDamage; - float m_flNPCDamage; - byte m_nDamageClass; + HL2CustomMeleeData_t m_CustomData; CNetworkString(m_iszWeaponScriptName, 128); }; @@ -98,7 +105,41 @@ IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponMelee, DT_HLCustomWeaponMelee) SendPropString(SENDINFO(m_iszWeaponScriptName)), END_SEND_TABLE(); -DEFINE_CUSTOM_WEAPON_FACTORY(hl2_melee, CHLCustomWeaponMelee); +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_melee, CHLCustomWeaponMelee, HL2CustomMeleeData_t); + +bool HL2CustomMeleeData_s::Parse(KeyValues* pKVWeapon) +{ + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flDamage = pkvData->GetFloat("damage"); + m_flNPCDamage = pkvData->GetFloat("damage_npc", m_flDamage); + m_flMeleeRange = pkvData->GetFloat("range", 70.f); + m_flRefireRate = pkvData->GetFloat("rate", 0.7f); + + const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); + if (pszDamageClass) + { + for (byte i = 0; i < ARRAYSIZE(g_ppszDamageClasses); i++) + { + if (V_stricmp(pszDamageClass, g_ppszDamageClasses[i]) == 0) + { + m_nDamageClass = i; + break; + } + } + } + return true; + } + + return false; +} + +void CHLCustomWeaponMelee::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 128); + V_memcpy(&m_CustomData, pData, sizeof(HL2CustomMeleeData_t)); +} acttable_t CHLCustomWeaponMelee::m_acttable[] = { @@ -165,7 +206,6 @@ IMPLEMENT_ACTTABLE(CHLCustomWeaponMelee); CHLCustomWeaponMelee::CHLCustomWeaponMelee() { - m_nDamageClass = 0; } //----------------------------------------------------------------------------- @@ -176,9 +216,9 @@ CHLCustomWeaponMelee::CHLCustomWeaponMelee() float CHLCustomWeaponMelee::GetDamageForActivity(Activity hitActivity) { if ((GetOwner() != NULL) && (GetOwner()->IsPlayer())) - return m_flDamage; + return m_CustomData.m_flDamage; - return m_flNPCDamage; + return m_CustomData.m_flNPCDamage; } //----------------------------------------------------------------------------- @@ -280,7 +320,7 @@ void CHLCustomWeaponMelee::HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCom 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), m_flNPCDamage, GetDamageType(), 0.75); + Vector(-16, -16, -16), Vector(36, 36, 36), m_CustomData.m_flNPCDamage, GetDamageType(), 0.75); // did I hit someone? if (pHurt) @@ -317,36 +357,6 @@ void CHLCustomWeaponMelee::Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCo } } -void CHLCustomWeaponMelee::ParseCustomFromWeaponFile(const char* pFileName) -{ - Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 128); - KeyValuesAD pKVWeapon("WeaponData"); - if (pKVWeapon->LoadFromFile(filesystem, pFileName, "GAME")) - { - KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); - if (pkvData) - { - m_flDamage = pkvData->GetFloat("damage"); - m_flNPCDamage = pkvData->GetFloat("damage_npc", m_flDamage); - m_flMeleeRange = pkvData->GetFloat("range", 70.f); - m_flRefireRate = pkvData->GetFloat("rate", 0.7f); - - const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); - if (pszDamageClass) - { - for (byte i = 0; i < ARRAYSIZE(g_ppszDamageClasses); i++) - { - if (V_stricmp(pszDamageClass, g_ppszDamageClasses[i]) == 0) - { - m_nDamageClass = i; - break; - } - } - } - } - } -} - //-------------------------------------------------------------------------- // // Custom ranged weapon @@ -361,7 +371,7 @@ class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon DECLARE_DATADESC(); CHLCustomWeaponGun(); - virtual void ParseCustomFromWeaponFile(const char* pFileName); + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } // Weapon behaviour @@ -371,15 +381,15 @@ class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon // 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; } + virtual float GetFireRate(void) { return m_CustomData.m_flFireRate; } + virtual int GetMinBurst() { return m_CustomData.m_nMinBurst; } + virtual int GetMaxBurst() { return m_CustomData.m_nMaxBurst; } + virtual float GetMinRestTime() { return m_CustomData.m_RestInterval.start; } + virtual float GetMaxRestTime() { return m_CustomData.m_RestInterval.start + m_CustomData.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 float WeaponAutoAimScale() { return m_CustomData.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); @@ -409,35 +419,54 @@ class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon void CheckZoomToggle(void); void ToggleZoom(void); +public: + typedef struct Data_s + { + 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; + } Data_t; + + struct Cache_s : public Data_s + { + bool m_bFiresUnderwater; // true if this weapon can fire underwater + bool m_bAltFiresUnderwater; // true if this weapon can fire underwater + float m_fMinRange1; // What's the closest this weapon can be used? + float m_fMinRange2; // What's the closest this weapon can be used? + float m_fMaxRange1; // What's the furthest this weapon can be used? + float m_fMaxRange2; // What's the furthest this weapon can be used? + bool m_bReloadsSingly; // True if this weapon reloads 1 round at a time + + bool Parse(KeyValues*); + }; + 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; + Data_t m_CustomData; bool m_bNeedPump; // When emptied completely bool m_bDelayedFire1; // Fire primary when finished reloading @@ -476,28 +505,10 @@ DEFINE_FIELD(m_bInZoom, FIELD_BOOLEAN), DEFINE_FIELD(m_bMustReload, FIELD_BOOLEAN), END_DATADESC(); -DEFINE_CUSTOM_WEAPON_FACTORY(hl2_gun, CHLCustomWeaponGun); +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_gun, CHLCustomWeaponGun, CHLCustomWeaponGun::Cache_s); 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; @@ -508,7 +519,7 @@ CHLCustomWeaponGun::CHLCustomWeaponGun() acttable_t* CHLCustomWeaponGun::ActivityList(void) { - switch (m_nActTableIndex) + switch (m_CustomData.m_nActTableIndex) { default: case ACTTABLE_SMG1: @@ -537,7 +548,7 @@ acttable_t* CHLCustomWeaponGun::ActivityList(void) int CHLCustomWeaponGun::ActivityListCount(void) { - switch (m_nActTableIndex) + switch (m_CustomData.m_nActTableIndex) { default: case ACTTABLE_SMG1: @@ -566,7 +577,7 @@ int CHLCustomWeaponGun::ActivityListCount(void) acttable_t* CHLCustomWeaponGun::GetBackupActivityList(void) { - switch (m_nActTableIndex) + switch (m_CustomData.m_nActTableIndex) { default: case ACTTABLE_SMG1: @@ -587,7 +598,7 @@ acttable_t* CHLCustomWeaponGun::GetBackupActivityList(void) int CHLCustomWeaponGun::GetBackupActivityListCount(void) { - switch (m_nActTableIndex) + switch (m_CustomData.m_nActTableIndex) { default: case ACTTABLE_SMG1: @@ -627,7 +638,7 @@ void ReadIntervalInt(const char* pString, int &iMin, int &iMax) } } -void CHLCustomWeaponGun::ParseCustomFromWeaponFile(const char* pFileName) +bool CHLCustomWeaponGun::Cache_s::Parse(KeyValues* pKVWeapon) { static const char* ppszCustomGunAnimTypes[NUM_GUN_ACT_TABLES] = { "smg", @@ -639,80 +650,93 @@ void CHLCustomWeaponGun::ParseCustomFromWeaponFile(const char* pFileName) "annabelle", }; - Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 128); - KeyValuesAD pKVWeapon("WeaponData"); - if (pKVWeapon->LoadFromFile(filesystem, pFileName, "GAME")) + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) { - 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_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_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"); - } + 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(DEG2RAD(flSpread * 0.5f))); - m_vNPCSpread = Vector(sin(DEG2RAD(flNPCSpread * 0.5f))); - m_vAllySpread = Vector(sin(DEG2RAD(flAllySperad * 0.5f))); + // 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(DEG2RAD(flSpread * 0.5f))); + m_vNPCSpread = Vector(sin(DEG2RAD(flNPCSpread * 0.5f))); + m_vAllySpread = Vector(sin(DEG2RAD(flAllySperad * 0.5f))); - const char* pszAnimType = pkvData->GetString("anim_type", nullptr); - if (pszAnimType) + const char* pszAnimType = pkvData->GetString("anim_type", nullptr); + if (pszAnimType) + { + for (int i = 0; i < NUM_GUN_ACT_TABLES; i++) { - for (int i = 0; i < NUM_GUN_ACT_TABLES; i++) + if (V_stricmp(pszAnimType, ppszCustomGunAnimTypes[i]) == 0) { - if (V_stricmp(pszAnimType, ppszCustomGunAnimTypes[i]) == 0) - { - m_nActTableIndex = i; - break; - } + m_nActTableIndex = i; + break; } } } + + return true; } + + return false; +} + +void CHLCustomWeaponGun::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 128); + const auto* pCache = static_cast (pData); + m_CustomData = *pCache; + m_bFiresUnderwater = pCache->m_bFiresUnderwater; + m_bAltFiresUnderwater = pCache->m_bAltFiresUnderwater; + m_fMinRange1 = pCache->m_fMinRange1; + m_fMinRange2 = pCache->m_fMinRange2; + m_fMaxRange1 = pCache->m_fMaxRange1; + m_fMaxRange2 = pCache->m_fMaxRange2; + m_bReloadsSingly = pCache->m_bReloadsSingly; } const Vector& CHLCustomWeaponGun::GetBulletSpread() { if (!GetOwner() || !GetOwner()->IsNPC()) - return m_vPlayerSpread; + return m_CustomData.m_vPlayerSpread; if (GetOwner()->MyNPCPointer()->IsPlayerAlly()) { // 357 allies should be cooler - return m_vAllySpread; + return m_CustomData.m_vAllySpread; } - return m_vNPCSpread; + return m_CustomData.m_vNPCSpread; } void CHLCustomWeaponGun::AddViewKick(void) @@ -723,7 +747,7 @@ void CHLCustomWeaponGun::AddViewKick(void) if (!pPlayer) return; - if (m_bFullAuto) + if (m_CustomData.m_bFullAuto) { float flDuration = m_fFireDuration; @@ -736,13 +760,13 @@ void CHLCustomWeaponGun::AddViewKick(void) flDuration = MIN(flDuration, 0.75f); } - CHLMachineGun::DoMachineGunKick(pPlayer, 0.5f, m_flMaxVerticalKick, flDuration, m_flSlideLimit); + CHLMachineGun::DoMachineGunKick(pPlayer, 0.5f, m_CustomData.m_flMaxVerticalKick, flDuration, m_CustomData.m_flSlideLimit); } else { QAngle viewPunch; - viewPunch.x = RandomInterval(m_VerticalPunchRange); - viewPunch.y = RandomFloat(-m_flSlideLimit, m_flSlideLimit); + viewPunch.x = RandomInterval(m_CustomData.m_VerticalPunchRange); + viewPunch.y = RandomFloat(-m_CustomData.m_flSlideLimit, m_CustomData.m_flSlideLimit); viewPunch.z = 0.0f; //Add it to the view punch @@ -755,13 +779,13 @@ void CHLCustomWeaponGun::AddViewKick(void) //----------------------------------------------------------------------------- void CHLCustomWeaponGun::CheckZoomToggle(void) { - if (!m_bHasZoom) + if (!m_CustomData.m_bHasZoom) return; CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); int iButtonsTest = IN_ATTACK3; - if (!m_bHasSecondaryFire) + if (!m_CustomData.m_bHasSecondaryFire) iButtonsTest |= IN_ATTACK2; if (pPlayer->m_afButtonPressed & iButtonsTest) @@ -819,7 +843,7 @@ bool CHLCustomWeaponGun::StartReload(void) //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) + if (m_CustomData.m_bUsePumpAnimation && m_iClip1 <= 0) { m_bNeedPump = true; } @@ -844,7 +868,7 @@ bool CHLCustomWeaponGun::StartReload(void) } #endif - if (m_bInZoom && !m_bZoomDuringReload) + if (m_bInZoom && !m_CustomData.m_bZoomDuringReload) { ToggleZoom(); } @@ -897,7 +921,7 @@ bool CHLCustomWeaponGun::Reload(void) } else if (BaseClass::Reload()) { - if (m_bInZoom && !m_bZoomDuringReload) + if (m_bInZoom && !m_CustomData.m_bZoomDuringReload) { ToggleZoom(); } @@ -1032,7 +1056,7 @@ void CHLCustomWeaponGun::ItemBusyFrame(void) { BaseClass::ItemBusyFrame(); - if (m_bZoomDuringReload) + if (m_CustomData.m_bZoomDuringReload) CheckZoomToggle(); } @@ -1050,7 +1074,7 @@ void CHLCustomWeaponGun::ItemPostFrame(void) UpdateAutoFire(); - if (m_bZoomDuringReload || !m_bInReload) + if (m_CustomData.m_bZoomDuringReload || !m_bInReload) CheckZoomToggle(); if (m_bReloadsSingly) @@ -1067,7 +1091,7 @@ void CHLCustomWeaponGun::ItemPostFrame(void) 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)) + else if (m_CustomData.m_bHasSecondaryFire && (pOwner->m_nButtons & IN_ATTACK2) && (m_iClip1 >= 2)) { m_bInReload = false; m_bNeedPump = false; @@ -1106,7 +1130,7 @@ void CHLCustomWeaponGun::ItemPostFrame(void) CheckReload(); } - if (m_bUsePumpAnimation && (m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + if (m_CustomData.m_bUsePumpAnimation && (m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) { m_fFireDuration = 0.f; Pump(); @@ -1121,7 +1145,7 @@ void CHLCustomWeaponGun::ItemPostFrame(void) bool bFired = false; // Secondary attack has priority - if (m_bHasSecondaryFire && !m_bMustReload && (m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) + if (m_CustomData.m_bHasSecondaryFire && !m_bMustReload && (m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) { m_bDelayedFire2 = false; @@ -1259,7 +1283,7 @@ void CHLCustomWeaponGun::PrimaryAttack() if ((UsesClipsForAmmo1() && m_iClip1 == 0) || (!UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType))) return; - if (m_bFullAuto) + if (m_CustomData.m_bFullAuto) { m_nShotsFired++; @@ -1288,7 +1312,7 @@ void CHLCustomWeaponGun::PrimaryAttack() // Fire the bullets FireBulletsInfo_t info; - info.m_iShots = iBulletsToFire * m_nBulletsPerShot; + info.m_iShots = iBulletsToFire * m_CustomData.m_nBulletsPerShot; info.m_vecSrc = pPlayer->Weapon_ShootPosition(); info.m_vecDirShooting = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); info.m_vecSpread = pPlayer->GetAttackSpread(this); @@ -1296,20 +1320,22 @@ void CHLCustomWeaponGun::PrimaryAttack() info.m_iAmmoType = m_iPrimaryAmmoType; info.m_iTracerFreq = 2; FireBullets(info); + + SendWeaponAnim(GetPrimaryAttackActivity()); } else { - if (!m_bNextAttackFromSequence && !m_bUsePumpAnimation && !(pPlayer->m_afButtonPressed & IN_ATTACK)) + if (!m_CustomData.m_bNextAttackFromSequence && !m_CustomData.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(); + SendWeaponAnim(GetPrimaryAttackActivity()); - m_flNextPrimaryAttack = gpGlobals->curtime + ((m_bNextAttackFromSequence || m_bUsePumpAnimation) ? GetViewModelSequenceDuration() : GetFireRate()); + m_flNextPrimaryAttack = gpGlobals->curtime + ((m_CustomData.m_bNextAttackFromSequence || m_CustomData.m_bUsePumpAnimation) ? GetViewModelSequenceDuration() : GetFireRate()); m_iClip1 -= 1; Vector vecSrc = pPlayer->Weapon_ShootPosition(); @@ -1318,9 +1344,9 @@ void CHLCustomWeaponGun::PrimaryAttack() 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); + pPlayer->FireBullets(m_CustomData.m_nBulletsPerShot, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, (m_CustomData.m_nBulletsPerShot > 1), true); - if (m_bUsePumpAnimation && m_iClip1) + if (m_CustomData.m_bUsePumpAnimation && m_iClip1) { // pump so long as some rounds are left. m_bNeedPump = true; @@ -1340,7 +1366,6 @@ void CHLCustomWeaponGun::PrimaryAttack() pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); } - SendWeaponAnim(GetPrimaryAttackActivity()); pPlayer->SetAnimation(PLAYER_ATTACK1); // Register a muzzleflash for the AI @@ -1353,7 +1378,7 @@ void CHLCustomWeaponGun::PrimaryAttack() //----------------------------------------------------------------------------- Activity CHLCustomWeaponGun::GetPrimaryAttackActivity(void) { - if (!m_bUseRecoilAnims || m_nShotsFired < 2) + if (!m_CustomData.m_bUseRecoilAnims || m_nShotsFired < 2) return ACT_VM_PRIMARYATTACK; if (m_nShotsFired < 3) @@ -1423,18 +1448,18 @@ void CHLCustomWeaponGun::FireNPCPrimaryAttack(CBaseCombatCharacter* pOperator, b 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) + const Vector& vecSpread = (bUseWeaponAngles || m_CustomData.m_nBulletsPerShot > 1) ? GetBulletSpread() : VECTOR_CONE_PRECALCULATED; + if (m_CustomData.m_bFullAuto) { int nShots = WeaponSoundRealtime(SINGLE_NPC); - pOperator->FireBullets(nShots * m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->FireBullets(nShots * m_CustomData.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->FireBullets(m_CustomData.m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); pOperator->DoMuzzleFlash(); m_iClip1 = m_iClip1 - 1; } diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp index 10553de547..a43371057d 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -576,15 +576,22 @@ int CWeaponCustomScripted::WeaponMeleeAttack2Condition( float flDot, float flDis return BaseClass::WeaponMeleeAttack2Condition( flDot, flDist ); } -DEFINE_CUSTOM_WEAPON_FACTORY(vscript, CWeaponCustomScripted); -void CWeaponCustomScripted::ParseCustomFromWeaponFile(const char* pFileName) +struct VScriptWeaponCustomData_s { - Q_FileBase(pFileName, m_iszWeaponScriptName.GetForModify(), 256); - KeyValuesAD pKVWeapon("WeaponData"); - if (pKVWeapon->LoadFromFile(filesystem, pFileName, "GAME")) + char cScripts[256]; + + bool Parse(KeyValues* pKVWeapon) { - Q_strncpy(m_iszClientScripts.GetForModify(), pKVWeapon->GetString("vscript_file"), 256); + Q_strncpy(cScripts, pKVWeapon->GetString("vscript_file"), 256); + return true; } +}; + +DEFINE_CUSTOM_WEAPON_FACTORY(vscript, CWeaponCustomScripted, VScriptWeaponCustomData_s); +void CWeaponCustomScripted::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 256); + Q_strncpy(m_iszClientScripts.GetForModify(), static_cast (pData)->cScripts, 256); } extern ConVar sv_script_think_interval; diff --git a/sp/src/game/shared/mapbase/weapon_custom_scripted.h b/sp/src/game/shared/mapbase/weapon_custom_scripted.h index 8cc05cd318..c227e35cff 100644 --- a/sp/src/game/shared/mapbase/weapon_custom_scripted.h +++ b/sp/src/game/shared/mapbase/weapon_custom_scripted.h @@ -115,7 +115,7 @@ class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM int WeaponMeleeAttack2Condition( float flDot, float flDist ); // Inherited via ICustomWeapon - virtual void ParseCustomFromWeaponFile(const char* pFileName) override; + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); #else void OnDataChanged(DataUpdateType_t type); #endif From 31cd394cb700c4555bf18560b5b5f2bffede58ea Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Wed, 20 Apr 2022 03:41:32 -0400 Subject: [PATCH 13/15] More features for custom melee weapon --- sp/src/game/server/basebludgeonweapon.cpp | 174 +++++++++++++++--- sp/src/game/server/basebludgeonweapon.h | 13 ++ .../game/server/mapbase/weapon_custom_hl2.cpp | 16 ++ 3 files changed, 182 insertions(+), 21 deletions(-) diff --git a/sp/src/game/server/basebludgeonweapon.cpp b/sp/src/game/server/basebludgeonweapon.cpp index 9390361656..c755e2ef5c 100644 --- a/sp/src/game/server/basebludgeonweapon.cpp +++ b/sp/src/game/server/basebludgeonweapon.cpp @@ -28,6 +28,15 @@ IMPLEMENT_SERVERCLASS_ST( CBaseHLBludgeonWeapon, DT_BaseHLBludgeonWeapon ) END_SEND_TABLE() +#ifdef MAPBASE +BEGIN_DATADESC(CBaseHLBludgeonWeapon) + +DEFINE_FIELD(m_flDelayedFire, FIELD_TIME), +DEFINE_FIELD(m_bShotDelayed, FIELD_BOOLEAN), + +END_DATADESC() +#endif // MAPBASE + #define BLUDGEON_HULL_DIM 16 static const Vector g_bludgeonMins(-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM); @@ -39,6 +48,9 @@ static const Vector g_bludgeonMaxs(BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM,BLUDGEON_ CBaseHLBludgeonWeapon::CBaseHLBludgeonWeapon() { m_bFiresUnderwater = true; +#ifdef MAPBASE + m_bShotDelayed = false; +#endif // MAPBASE } //----------------------------------------------------------------------------- @@ -96,11 +108,19 @@ void CBaseHLBludgeonWeapon::ItemPostFrame( void ) #ifdef MAPBASE if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) { + m_bShotDelayed = false; WeaponIdle(); return; } -#endif + // 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(); @@ -239,7 +259,7 @@ Activity CBaseHLBludgeonWeapon::ChooseIntersectionPointAndActivity( trace_t &hit } - return ACT_VM_HITCENTER; + return GetPrimaryAttackActivity(); } //----------------------------------------------------------------------------- @@ -297,7 +317,6 @@ void CBaseHLBludgeonWeapon::ImpactEffect( trace_t &traceHit ) UTIL_ImpactTrace( &traceHit, DMG_CLUB ); } - //------------------------------------------------------------------------------ // Purpose : Starts the swing of the weapon and determines the animation // Input : bIsSecondary - is this a secondary attack? @@ -320,10 +339,14 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) Vector swingEnd = swingStart + forward * GetRange(); UTIL_TraceLine( swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); - Activity nHitActivity = ACT_VM_HITCENTER; + Activity nHitActivity = GetPrimaryAttackActivity(); // Like bullets, bludgeon traces have to trace against triggers. - CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#ifdef MAPBASE + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), GetDamageType()); +#else + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), DMG_CLUB); +#endif // MAPBASE triggerInfo.SetDamagePosition( traceHit.startpos ); triggerInfo.SetDamageForce( forward ); TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, forward ); @@ -374,31 +397,20 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) { nHitActivity = bIsSecondary ? ACT_VM_MISSCENTER2 : ACT_VM_MISSCENTER; +#ifndef MAPBASE // We want to test the first swing again Vector testEnd = swingStart + forward * GetRange(); -#ifdef MAPBASE - // Sound has been moved here since we're using the other melee sounds now - WeaponSound( SINGLE ); -#endif - // See if we happened to hit water - ImpactWater( swingStart, testEnd ); + ImpactWater(swingStart, testEnd); +#endif // !MAPBASE } +#ifndef MAPBASE else { -#ifdef MAPBASE - // Other melee sounds - if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) - WeaponSound(MELEE_HIT_WORLD); - else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) - WeaponSound(MELEE_MISS); - else - WeaponSound(MELEE_HIT); -#endif - Hit( traceHit, nHitActivity, bIsSecondary ? true : false ); } +#endif // Send the anim SendWeaponAnim( nHitActivity ); @@ -414,5 +426,125 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) #ifdef MAPBASE pOwner->SetAnimation( PLAYER_ATTACK1 ); + + 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); + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, nHitActivity, bIsSecondary ? true : false); + } + } #endif } + +#ifdef MAPBASE +void CBaseHLBludgeonWeapon::DelayedAttack(void) +{ + m_bShotDelayed = false; + + trace_t traceHit; + + // Try a ray + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + return; + + pOwner->RumbleEffect(RUMBLE_CROWBAR_SWING, 0, RUMBLE_FLAG_RESTART); + + Vector swingStart = pOwner->Weapon_ShootPosition(); + Vector forward; + + forward = pOwner->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT, GetRange()); + + 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 + { + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(GetActivity()), GetDamageType()); + triggerInfo.SetDamagePosition(traceHit.startpos); + triggerInfo.SetDamageForce(forward); + + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, GetActivity(), false); + } +} + +bool CBaseHLBludgeonWeapon::CanHolster(void) +{ + if (m_bShotDelayed) + return false; + + return BaseClass::CanHolster(); +} +#endif // MAPBASE diff --git a/sp/src/game/server/basebludgeonweapon.h b/sp/src/game/server/basebludgeonweapon.h index 52f02f9b3d..57f0ed11d6 100644 --- a/sp/src/game/server/basebludgeonweapon.h +++ b/sp/src/game/server/basebludgeonweapon.h @@ -23,6 +23,9 @@ class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon CBaseHLBludgeonWeapon(); DECLARE_SERVERCLASS(); +#ifdef MAPBASE + DECLARE_DATADESC(); +#endif // MAPBASE virtual void Spawn( void ); virtual void Precache( void ); @@ -30,6 +33,9 @@ class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon //Attack functions virtual void PrimaryAttack( void ); virtual void SecondaryAttack( void ); +#ifdef MAPBASE + void DelayedAttack(void); +#endif // MAPBASE virtual void ItemPostFrame( void ); @@ -46,6 +52,8 @@ class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon #ifdef MAPBASE virtual int GetDamageType() { return DMG_CLUB; } + virtual float GetHitDelay() { return 0.f; } + virtual bool CanHolster(void); #endif // MAPBASE protected: @@ -56,6 +64,11 @@ class CBaseHLBludgeonWeapon : public CBaseHLCombatWeapon void Swing( int bIsSecondary ); void Hit( trace_t &traceHit, Activity nHitActivity, bool bIsSecondary ); Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); + +#ifdef MAPBASE + float m_flDelayedFire; + bool m_bShotDelayed; +#endif // MAPBASE }; #endif \ No newline at end of file diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 86c9733b82..0a5996c50b 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -55,6 +55,8 @@ typedef struct HL2CustomMeleeData_s float m_flRefireRate; float m_flDamage; float m_flNPCDamage; + float m_flHitDelay; + Activity m_nHitActivity = ACT_INVALID; byte m_nDamageClass; bool Parse(KeyValues*); @@ -72,6 +74,7 @@ class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon float GetRange(void) { return m_CustomData.m_flMeleeRange; } float GetFireRate(void) { return m_CustomData.m_flRefireRate; } + float GetHitDelay() { return m_CustomData.m_flHitDelay; } void AddViewKick(void); float GetDamageForActivity(Activity hitActivity); @@ -86,6 +89,9 @@ class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon acttable_t* GetBackupActivityList() { return NULL; } int GetBackupActivityListCount() { return 0; } + //Functions to select animation sequences + virtual Activity GetPrimaryAttackActivity(void) { return m_CustomData.m_nHitActivity; } + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } virtual int GetDamageType() { return g_nDamageClassTypeBits[m_CustomData.m_nDamageClass]; } @@ -116,6 +122,16 @@ bool HL2CustomMeleeData_s::Parse(KeyValues* pKVWeapon) m_flNPCDamage = pkvData->GetFloat("damage_npc", m_flDamage); m_flMeleeRange = pkvData->GetFloat("range", 70.f); m_flRefireRate = pkvData->GetFloat("rate", 0.7f); + m_flHitDelay = pkvData->GetFloat("hitdelay"); + if (pkvData->FindKey("activity_hit")) + { + m_nHitActivity = (Activity)ActivityList_IndexForName(pkvData->GetString("activity_hit")); + } + + if (m_nHitActivity == ACT_INVALID) + { + m_nHitActivity = ACT_VM_HITCENTER; + } const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); if (pszDamageClass) From 37019140bd0ed63816c08303b966f1211a977d14 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Wed, 20 Apr 2022 04:05:34 -0400 Subject: [PATCH 14/15] Fix --- sp/src/game/server/mapbase/weapon_custom_hl2.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 0a5996c50b..834bad2ca3 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -56,8 +56,8 @@ typedef struct HL2CustomMeleeData_s float m_flDamage; float m_flNPCDamage; float m_flHitDelay; - Activity m_nHitActivity = ACT_INVALID; byte m_nDamageClass; + bool m_bHitUsesMissAnim; bool Parse(KeyValues*); } HL2CustomMeleeData_t; @@ -90,7 +90,7 @@ class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon int GetBackupActivityListCount() { return 0; } //Functions to select animation sequences - virtual Activity GetPrimaryAttackActivity(void) { return m_CustomData.m_nHitActivity; } + virtual Activity GetPrimaryAttackActivity(void) { return m_CustomData.m_bHitUsesMissAnim ? ACT_VM_MISSCENTER : BaseClass::GetPrimaryAttackActivity(); } const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } virtual int GetDamageType() { return g_nDamageClassTypeBits[m_CustomData.m_nDamageClass]; } @@ -123,15 +123,7 @@ bool HL2CustomMeleeData_s::Parse(KeyValues* pKVWeapon) m_flMeleeRange = pkvData->GetFloat("range", 70.f); m_flRefireRate = pkvData->GetFloat("rate", 0.7f); m_flHitDelay = pkvData->GetFloat("hitdelay"); - if (pkvData->FindKey("activity_hit")) - { - m_nHitActivity = (Activity)ActivityList_IndexForName(pkvData->GetString("activity_hit")); - } - - if (m_nHitActivity == ACT_INVALID) - { - m_nHitActivity = ACT_VM_HITCENTER; - } + m_bHitUsesMissAnim = pkvData->GetBool("hit_uses_miss_anim"); const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); if (pszDamageClass) From d3978db574ea98093190ae5c99854d69f3899d04 Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Thu, 21 Apr 2022 22:56:24 -0400 Subject: [PATCH 15/15] Fixed custom gun not reseting zoom when holstered --- sp/src/game/server/mapbase/weapon_custom_hl2.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp index 834bad2ca3..7c8241bcb0 100644 --- a/sp/src/game/server/mapbase/weapon_custom_hl2.cpp +++ b/sp/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -386,6 +386,7 @@ class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon 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); + virtual bool Holster(CBaseCombatWeapon* pSwitchingTo = NULL); // Bullet launch information virtual const Vector& GetBulletSpread(void); @@ -782,6 +783,17 @@ void CHLCustomWeaponGun::AddViewKick(void) } } +bool CHLCustomWeaponGun::Holster(CBaseCombatWeapon* pSwitchingTo) +{ + // Stop zooming + if (m_bInZoom) + { + ToggleZoom(); + } + + return BaseClass::Holster(pSwitchingTo); +} + //----------------------------------------------------------------------------- // Purpose: //-----------------------------------------------------------------------------