From 5a0c406dbfdc7d10986f492921a116d089227f3d Mon Sep 17 00:00:00 2001 From: Peter Covington Date: Sat, 16 Apr 2022 07:19:42 -0400 Subject: [PATCH] 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 00000000..bb919e97 --- /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 00000000..c1af48c6 --- /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 b04706d4..345f5084 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 8a97695a..6a44cec2 100644 --- a/sp/src/game/server/util.cpp +++ b/sp/src/game/server/util.cpp @@ -76,6 +76,10 @@ public: 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 82f485ba..525ce341 100644 --- a/sp/src/game/server/util.h +++ b/sp/src/game/server/util.h @@ -100,6 +100,9 @@ public: 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 d9155f32..a0dcbda2 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 924f1031..f2a2a77d 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 @@ public: 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();