From ee756087f5dc7e17a41ae494345ec76a29f33155 Mon Sep 17 00:00:00 2001 From: "ALLEN-PC\\acj30" Date: Mon, 27 Jan 2025 00:23:15 -0600 Subject: [PATCH] HL2 player character switching system --- sp/src/game/client/client_mapbase.vpc | 2 + sp/src/game/client/hl2/c_basehlplayer.cpp | 3 + sp/src/game/client/hl2/c_basehlplayer.h | 8 + sp/src/game/server/hl2/hl2_player.cpp | 167 +++++ sp/src/game/server/hl2/hl2_player.h | 15 + sp/src/game/server/player.cpp | 5 + sp/src/game/server/server_mapbase.vpc | 2 + .../game/shared/basecombatweapon_shared.cpp | 8 + sp/src/game/shared/basecombatweapon_shared.h | 5 +- .../shared/hl2/basehlcombatweapon_shared.cpp | 67 +- .../shared/hl2/basehlcombatweapon_shared.h | 7 + sp/src/game/shared/mapbase/mapbase_shared.cpp | 21 + .../shared/mapbase/protagonist_system.cpp | 693 ++++++++++++++++++ .../game/shared/mapbase/protagonist_system.h | 151 ++++ sp/src/game/shared/weapon_parse.cpp | 20 + sp/src/game/shared/weapon_parse.h | 10 + 16 files changed, 1181 insertions(+), 3 deletions(-) create mode 100644 sp/src/game/shared/mapbase/protagonist_system.cpp create mode 100644 sp/src/game/shared/mapbase/protagonist_system.h diff --git a/sp/src/game/client/client_mapbase.vpc b/sp/src/game/client/client_mapbase.vpc index 7b3b28f361..fb2b98cd0f 100644 --- a/sp/src/game/client/client_mapbase.vpc +++ b/sp/src/game/client/client_mapbase.vpc @@ -49,6 +49,8 @@ $Project $File "$SRCDIR\game\shared\mapbase\matchers.h" $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" + $File "$SRCDIR\game\shared\mapbase\protagonist_system.cpp" + $File "$SRCDIR\game\shared\mapbase\protagonist_system.h" $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.h" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_singletons.cpp" [$MAPBASE_VSCRIPT] diff --git a/sp/src/game/client/hl2/c_basehlplayer.cpp b/sp/src/game/client/hl2/c_basehlplayer.cpp index 15a33ea8c2..f8ad5d441d 100644 --- a/sp/src/game/client/hl2/c_basehlplayer.cpp +++ b/sp/src/game/client/hl2/c_basehlplayer.cpp @@ -31,6 +31,9 @@ ConVar cl_npc_speedmod_outtime( "cl_npc_speedmod_outtime", "1.5", FCVAR_CLIENTDL IMPLEMENT_CLIENTCLASS_DT(C_BaseHLPlayer, DT_HL2_Player, CHL2_Player) RecvPropDataTable( RECVINFO_DT(m_HL2Local),0, &REFERENCE_RECV_TABLE(DT_HL2Local) ), RecvPropBool( RECVINFO( m_fIsSprinting ) ), +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_nProtagonistIndex ) ), +#endif #ifdef SP_ANIM_STATE RecvPropFloat( RECVINFO( m_flAnimRenderYaw ) ), #endif diff --git a/sp/src/game/client/hl2/c_basehlplayer.h b/sp/src/game/client/hl2/c_basehlplayer.h index 368c9bb12c..8cce42a431 100644 --- a/sp/src/game/client/hl2/c_basehlplayer.h +++ b/sp/src/game/client/hl2/c_basehlplayer.h @@ -62,6 +62,10 @@ class C_BaseHLPlayer : public C_BasePlayer bool IsWeaponLowered( void ) { return m_HL2Local.m_bWeaponLowered; } +#ifdef MAPBASE + int GetProtagonistIndex() const { return m_nProtagonistIndex; } +#endif + #ifdef SP_ANIM_STATE virtual const QAngle& GetRenderAngles( void ); #endif @@ -86,6 +90,10 @@ class C_BaseHLPlayer : public C_BasePlayer bool m_bPlayUseDenySound; // Signaled by PlayerUse, but can be unset by HL2 ladder code... float m_flSpeedMod; float m_flExitSpeedMod; + +#ifdef MAPBASE + int m_nProtagonistIndex; +#endif #ifdef SP_ANIM_STATE // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. diff --git a/sp/src/game/server/hl2/hl2_player.cpp b/sp/src/game/server/hl2/hl2_player.cpp index b99f05b25e..7d8d16d468 100644 --- a/sp/src/game/server/hl2/hl2_player.cpp +++ b/sp/src/game/server/hl2/hl2_player.cpp @@ -58,6 +58,7 @@ #ifdef MAPBASE #include "triggers.h" #include "mapbase/variant_tools.h" +#include "mapbase/protagonist_system.h" #endif // memdbgon must be the last include file in a .cpp file!!! @@ -591,6 +592,8 @@ BEGIN_DATADESC( CHL2_Player ) DEFINE_INPUTFUNC( FIELD_VOID, "DisableGeigerCounter", InputDisableGeigerCounter ), DEFINE_INPUTFUNC( FIELD_VOID, "ShowSquadHUD", InputShowSquadHUD ), DEFINE_INPUTFUNC( FIELD_VOID, "HideSquadHUD", InputHideSquadHUD ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetProtagonist", InputSetProtagonist ), #endif DEFINE_SOUNDPATCH( m_sndLeeches ), @@ -605,6 +608,10 @@ BEGIN_DATADESC( CHL2_Player ) DEFINE_FIELD( m_flTimeNextLadderHint, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszProtagonistName, FIELD_STRING, "ProtagonistName" ), +#endif + //DEFINE_FIELD( m_hPlayerProxy, FIELD_EHANDLE ), //Shut up class check! END_DATADESC() @@ -623,6 +630,9 @@ BEGIN_ENT_SCRIPTDESC( CHL2_Player, CBasePlayer, "The HL2 player entity." ) DEFINE_SCRIPTFUNC( RemoveCustomSuitDevice, "Removes a custom suit device ID. (1-3)" ) DEFINE_SCRIPTFUNC( IsCustomSuitDeviceActive, "Checks if a custom suit device is active." ) + DEFINE_SCRIPTFUNC( GetProtagonistName, "Gets the player's protagonist name." ) + DEFINE_SCRIPTFUNC( SetProtagonist, "Sets the player's protagonist entry." ) + #ifdef SP_ANIM_STATE DEFINE_SCRIPTFUNC( AddAnimStateLayer, "Adds a custom sequence index as a misc. layer for the singleplayer anim state, wtih parameters for blending in/out, setting the playback rate, holding the animation at the end, and only playing when the player is still." ) #endif @@ -638,6 +648,10 @@ CHL2_Player::CHL2_Player() m_flArmorReductionTime = 0.0f; m_iArmorReductionFrom = 0; + +#ifdef MAPBASE + m_nProtagonistIndex = -1; +#endif } // @@ -672,6 +686,9 @@ CSuitPowerDevice SuitDeviceCustom[] = IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player) SendPropDataTable(SENDINFO_DT(m_HL2Local), &REFERENCE_SEND_TABLE(DT_HL2Local), SendProxy_SendLocalDataTable), SendPropBool( SENDINFO(m_fIsSprinting) ), +#ifdef MAPBASE + SendPropInt( SENDINFO( m_nProtagonistIndex ), 8, SPROP_UNSIGNED ), +#endif #ifdef SP_ANIM_STATE SendPropFloat( SENDINFO(m_flAnimRenderYaw), 0, SPROP_NOSCALE ), #endif @@ -1244,6 +1261,11 @@ void CHL2_Player::Activate( void ) #endif GetPlayerProxy(); + +#ifdef MAPBASE + if (m_iszProtagonistName != NULL_STRING) + SetProtagonist( STRING( m_iszProtagonistName ) ); +#endif } //------------------------------------------------------------------------------ @@ -1484,6 +1506,8 @@ CStudioHdr *CHL2_Player::OnNewModel() extern char g_szDefaultPlayerModel[MAX_PATH]; extern bool g_bDefaultPlayerDrawExternally; + +extern char g_szDefaultProtagonist[MAX_PROTAGONIST_NAME]; #endif //----------------------------------------------------------------------------- @@ -1516,6 +1540,9 @@ void CHL2_Player::Spawn(void) } SetDrawPlayerModelExternally( g_bDefaultPlayerDrawExternally ); + + if (m_iszProtagonistName == NULL_STRING && *g_szDefaultProtagonist) + m_iszProtagonistName = MAKE_STRING( g_szDefaultProtagonist ); #endif // @@ -4040,6 +4067,10 @@ bool CHL2_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex StopZooming(); } +#ifdef MAPBASE + RefreshProtagonistWeaponData( pWeapon ); +#endif + return BaseClass::Weapon_Switch( pWeapon, viewmodelindex ); } @@ -4435,6 +4466,142 @@ bool CHL2_Player::IsCustomSuitDeviceActive( int iDeviceID ) return SuitPower_IsDeviceActive( SuitDeviceCustom[iDeviceID] ); } + +//----------------------------------------------------------------------------- +// Purpose: Gets our protagonist name, if we have one +//----------------------------------------------------------------------------- +const char *CHL2_Player::GetProtagonistName() const +{ + return STRING( m_iszProtagonistName ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets our protagonist index, if we have one +//----------------------------------------------------------------------------- +int CHL2_Player::GetProtagonistIndex() const +{ + return m_nProtagonistIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our protagonist to the specified entry +//----------------------------------------------------------------------------- +void CHL2_Player::InputSetProtagonist( inputdata_t &inputdata ) +{ + SetProtagonist( inputdata.value.String() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our protagonist to the specified entry +//----------------------------------------------------------------------------- +void CHL2_Player::SetProtagonist( const char *pszProtagonist ) +{ + if (!pszProtagonist || !*pszProtagonist) + { + ResetProtagonist(); + return; + } + + int nIndex = g_ProtagonistSystem.FindProtagonistIndex( pszProtagonist ); + if (nIndex == -1) + { + Warning( "\"%s\" is not a valid protagonist\n", pszProtagonist ); + return; + } + + if (m_nProtagonistIndex != -1) + { + // Flush any pre-existing data + ResetProtagonist(); + } + + m_nProtagonistIndex = nIndex; + m_iszProtagonistName = AllocPooledString( pszProtagonist ); + + RefreshProtagonistData(); +} + +//----------------------------------------------------------------------------- +// Purpose: Resets protagonist data +//----------------------------------------------------------------------------- +void CHL2_Player::ResetProtagonist() +{ + SetModel( g_szDefaultPlayerModel ); + m_nSkin = 0; + m_nBody = 0; + + CBaseViewModel *vm = GetViewModel( 1 ); + if (vm) + { + extern char g_szDefaultHandsModel[MAX_PATH]; + vm->SetWeaponModel( g_szDefaultHandsModel, NULL ); + + vm->m_nSkin = 0; + vm->m_nBody = 0; + } + + // RemoveContext will automatically remove contexts by name, regardless of how values are specified + const char *pszProtagContexts = g_ProtagonistSystem.GetProtagonist_ResponseContexts( this ); + if (pszProtagContexts) + RemoveContext( pszProtagContexts ); + + m_iszProtagonistName = NULL_STRING; + m_nProtagonistIndex = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Refreshes protagonist data +//----------------------------------------------------------------------------- +void CHL2_Player::RefreshProtagonistData() +{ + if (m_nProtagonistIndex == -1) + return; + + g_ProtagonistSystem.PrecacheProtagonist( this, m_nProtagonistIndex ); + + const char *pszProtagModel = g_ProtagonistSystem.GetProtagonist_PlayerModel( this ); + if (pszProtagModel) + SetModel( pszProtagModel ); + + m_nSkin = g_ProtagonistSystem.GetProtagonist_PlayerModelSkin( this ); + m_nBody = g_ProtagonistSystem.GetProtagonist_PlayerModelBody( this ); + + const char *pszProtagContexts = g_ProtagonistSystem.GetProtagonist_ResponseContexts( this ); + if (pszProtagContexts) + AddContext( pszProtagContexts ); + + RefreshProtagonistWeaponData( GetActiveWeapon() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Refreshes protagonist data +//----------------------------------------------------------------------------- +void CHL2_Player::RefreshProtagonistWeaponData( CBaseCombatWeapon *pWeapon ) +{ + if (m_nProtagonistIndex == -1) + return; + + CBaseViewModel *vm = GetViewModel( 1 ); + if (vm) + { + const char *pszHandModel = g_ProtagonistSystem.GetProtagonist_HandModel( this, pWeapon ); + if (pszHandModel) + { + vm->SetWeaponModel( pszHandModel, NULL ); + + vm->m_nSkin = g_ProtagonistSystem.GetProtagonist_HandModelSkin( this, pWeapon ); + vm->m_nBody = g_ProtagonistSystem.GetProtagonist_HandModelBody( this, pWeapon ); + } + else + { + extern char g_szDefaultHandsModel[MAX_PATH]; + vm->SetWeaponModel( g_szDefaultHandsModel, NULL ); + + vm->m_nSkin = 0; + vm->m_nBody = 0; + } + } +} #endif //----------------------------------------------------------------------------- diff --git a/sp/src/game/server/hl2/hl2_player.h b/sp/src/game/server/hl2/hl2_player.h index 12c0386c85..6389e10714 100644 --- a/sp/src/game/server/hl2/hl2_player.h +++ b/sp/src/game/server/hl2/hl2_player.h @@ -358,6 +358,15 @@ class CHL2_Player : public CBasePlayer void AddCustomSuitDevice( int iDeviceID ); void RemoveCustomSuitDevice( int iDeviceID ); bool IsCustomSuitDeviceActive( int iDeviceID ); + + // Protagonist system + const char *GetProtagonistName() const; + int GetProtagonistIndex() const; + void InputSetProtagonist( inputdata_t &inputdata ); + void SetProtagonist( const char *pszProtagonist ); + void ResetProtagonist(); + void RefreshProtagonistData(); + void RefreshProtagonistWeaponData( CBaseCombatWeapon *pWeapon ); #endif CSoundPatch *m_sndLeeches; @@ -442,6 +451,12 @@ class CHL2_Player : public CBasePlayer friend class CHL2GameMovement; +#ifdef MAPBASE + // Protagonist used by protagonist_system.h + string_t m_iszProtagonistName; + CNetworkVar( int, m_nProtagonistIndex ); +#endif + #ifdef SP_ANIM_STATE CSinglePlayerAnimState* m_pPlayerAnimState; diff --git a/sp/src/game/server/player.cpp b/sp/src/game/server/player.cpp index 6fd1c50415..89581d858c 100644 --- a/sp/src/game/server/player.cpp +++ b/sp/src/game/server/player.cpp @@ -4475,6 +4475,11 @@ void CBasePlayer::SetSuitUpdate(const char *name, int fgroup, int iNoRepeatTime) return; } +#ifdef MAPBASE + if ( HasContext("silent_suit", "1") ) + return; +#endif + // if name == NULL, then clear out the queue if (!name) diff --git a/sp/src/game/server/server_mapbase.vpc b/sp/src/game/server/server_mapbase.vpc index 7d74847e12..ec54d292da 100644 --- a/sp/src/game/server/server_mapbase.vpc +++ b/sp/src/game/server/server_mapbase.vpc @@ -48,6 +48,8 @@ $Project $File "$SRCDIR\game\shared\mapbase\matchers.h" $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" + $File "$SRCDIR\game\shared\mapbase\protagonist_system.cpp" + $File "$SRCDIR\game\shared\mapbase\protagonist_system.h" $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.h" [$MAPBASE_VSCRIPT] $File "$SRCDIR\game\shared\mapbase\vscript_singletons.cpp" [$MAPBASE_VSCRIPT] diff --git a/sp/src/game/shared/basecombatweapon_shared.cpp b/sp/src/game/shared/basecombatweapon_shared.cpp index c67ab35acc..a847a5e874 100644 --- a/sp/src/game/shared/basecombatweapon_shared.cpp +++ b/sp/src/game/shared/basecombatweapon_shared.cpp @@ -507,6 +507,11 @@ bool CBaseCombatWeapon::UsesHands() const { return GetWpnData().m_bUsesHands; } + +int CBaseCombatWeapon::GetHandRig() const +{ + return GetWpnData().m_nHandRig; +} #endif //----------------------------------------------------------------------------- @@ -3127,6 +3132,9 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatWeapon, CBaseAnimating, "The base class for all DEFINE_SCRIPTFUNC( GetViewModel, "Get the weapon's view model." ) DEFINE_SCRIPTFUNC( GetDroppedModel, "Get the weapon's unique dropped model if it has one." ) + DEFINE_SCRIPTFUNC( UsesHands, "" ) + DEFINE_SCRIPTFUNC( GetHandRig, "" ) + DEFINE_SCRIPTFUNC( GetWeight, "Get the weapon's weight." ) DEFINE_SCRIPTFUNC( GetPrintName, "" ) diff --git a/sp/src/game/shared/basecombatweapon_shared.h b/sp/src/game/shared/basecombatweapon_shared.h index 1b4119998e..ee90c7c52b 100644 --- a/sp/src/game/shared/basecombatweapon_shared.h +++ b/sp/src/game/shared/basecombatweapon_shared.h @@ -419,12 +419,13 @@ class CBaseCombatWeapon : public BASECOMBATWEAPON_DERIVED_FROM virtual bool UsesClipsForAmmo2( void ) const; bool IsMeleeWeapon() const; #ifdef MAPBASE - float GetViewmodelFOVOverride() const; + virtual float GetViewmodelFOVOverride() const; float GetBobScale() const; float GetSwayScale() const; float GetSwaySpeedScale() const; virtual const char *GetDroppedModel( void ) const; - bool UsesHands( void ) const; + virtual bool UsesHands( void ) const; + virtual int GetHandRig( void ) const; #endif // derive this function if you mod uses encrypted weapon info files diff --git a/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp b/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp index 06c69df7c8..a58e82b51c 100644 --- a/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp +++ b/sp/src/game/shared/hl2/basehlcombatweapon_shared.cpp @@ -6,6 +6,9 @@ #include "cbase.h" #include "basehlcombatweapon_shared.h" +#ifdef MAPBASE +#include "mapbase/protagonist_system.h" +#endif #include "hl2_player_shared.h" @@ -436,4 +439,66 @@ const WeaponProficiencyInfo_t *CBaseHLCombatWeapon::GetDefaultProficiencyValues( return g_BaseWeaponProficiencyTable; } -#endif \ No newline at end of file +#endif + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CBaseHLCombatWeapon::GetViewModel( int viewmodelindex ) const +{ + if (GetOwner() && GetOwner()->IsPlayer() && viewmodelindex == 0) + { + const char *pszProtagVM = g_ProtagonistSystem.GetProtagonist_ViewModel( static_cast(GetOwner()), this ); + if (pszProtagVM) + return pszProtagVM; + } + + return BaseClass::GetViewModel( viewmodelindex ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CBaseHLCombatWeapon::GetViewmodelFOVOverride() const +{ + if (GetOwner() && GetOwner()->IsPlayer()) + { + float *flVMFOV = g_ProtagonistSystem.GetProtagonist_ViewModelFOV( static_cast(GetOwner()), this ); + if (flVMFOV) + return *flVMFOV; + } + + return BaseClass::GetViewmodelFOVOverride(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseHLCombatWeapon::UsesHands() const +{ + if (GetOwner() && GetOwner()->IsPlayer()) + { + bool *bProtagUsesHands = g_ProtagonistSystem.GetProtagonist_UsesHands( static_cast(GetOwner()), this ); + if (bProtagUsesHands) + return *bProtagUsesHands; + } + + return BaseClass::UsesHands(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseHLCombatWeapon::GetHandRig() const +{ + if (GetOwner() && GetOwner()->IsPlayer()) + { + int *nProtagHandRig = g_ProtagonistSystem.GetProtagonist_HandRig( static_cast(GetOwner()), this ); + if (nProtagHandRig) + return *nProtagHandRig; + } + + return BaseClass::GetHandRig(); +} +#endif diff --git a/sp/src/game/shared/hl2/basehlcombatweapon_shared.h b/sp/src/game/shared/hl2/basehlcombatweapon_shared.h index eb11fa8070..3a09ae432a 100644 --- a/sp/src/game/shared/hl2/basehlcombatweapon_shared.h +++ b/sp/src/game/shared/hl2/basehlcombatweapon_shared.h @@ -53,6 +53,13 @@ class CBaseHLCombatWeapon : public CBaseCombatWeapon virtual void ItemHolsterFrame( void ); +#ifdef MAPBASE + virtual const char *GetViewModel( int viewmodelindex = 0 ) const; + virtual float GetViewmodelFOVOverride() const; + virtual bool UsesHands( void ) const; + virtual int GetHandRig( void ) const; +#endif + int m_iPrimaryAttacks; // # of primary attacks performed with this weapon int m_iSecondaryAttacks; // # of secondary attacks performed with this weapon diff --git a/sp/src/game/shared/mapbase/mapbase_shared.cpp b/sp/src/game/shared/mapbase/mapbase_shared.cpp index 8c364b62ea..5ef52b2b9b 100644 --- a/sp/src/game/shared/mapbase/mapbase_shared.cpp +++ b/sp/src/game/shared/mapbase/mapbase_shared.cpp @@ -30,6 +30,9 @@ #include "mapbase/SystemConvarMod.h" #include "gameinterface.h" #endif +#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL) +#include "protagonist_system.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -90,6 +93,11 @@ bool g_bDefaultPlayerDrawExternally; char g_szDefaultHandsModel[MAX_PATH]; int g_iDefaultHandsSkin; int g_iDefaultHandsBody; + +#ifdef HL2_DLL +// See protagonist_system.h +char g_szDefaultProtagonist[MAX_PROTAGONIST_NAME]; +#endif #endif enum @@ -113,6 +121,9 @@ enum #ifdef MAPBASE_VSCRIPT MANIFEST_VSCRIPT, #endif +#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL) + MANIFEST_PROTAGONISTS, // See protagonist_system.h +#endif // Must always be kept below MANIFEST_NUM_TYPES, @@ -153,6 +164,9 @@ static const ManifestType_t gm_szManifestFileStrings[MANIFEST_NUM_TYPES] = { #ifdef MAPBASE_VSCRIPT { "vscript", "mapbase_load_vscript", "Should we load map-specific VScript map spawn files? e.g. \"maps/_mapspawn.nut\"" }, #endif +#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL) + { "protagonists", "mapbase_load_protagonists", "Should we load map-specific protagonist files? e.g. \"maps/_protagonists.txt\"" }, +#endif }; //----------------------------------------------------------------------------- @@ -240,6 +254,10 @@ class CMapbaseSystem : public CAutoGameSystem Q_strncpy( g_szDefaultHandsModel, gameinfo->GetString( "player_default_hands", "models/weapons/v_hands.mdl" ), sizeof( g_szDefaultHandsModel ) ); g_iDefaultHandsSkin = gameinfo->GetInt( "player_default_hands_skin", 0 ); g_iDefaultHandsBody = gameinfo->GetInt( "player_default_hands_body", 0 ); + +#ifdef HL2_DLL + Q_strncpy( g_szDefaultProtagonist, gameinfo->GetString( "player_default_protagonist", "" ), sizeof( g_szDefaultProtagonist ) ); +#endif #endif } gameinfo->deleteThis(); @@ -470,6 +488,9 @@ class CMapbaseSystem : public CAutoGameSystem #endif #ifdef MAPBASE_VSCRIPT case MANIFEST_VSCRIPT: { VScriptRunScript(value, false); } break; +#endif +#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL) + case MANIFEST_PROTAGONISTS: { g_ProtagonistSystem.LoadProtagonistFile( value ); } break; #endif } } diff --git a/sp/src/game/shared/mapbase/protagonist_system.cpp b/sp/src/game/shared/mapbase/protagonist_system.cpp new file mode 100644 index 0000000000..c22a20fab6 --- /dev/null +++ b/sp/src/game/shared/mapbase/protagonist_system.cpp @@ -0,0 +1,693 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: System to easily switch between player characters. +// +// Author: Blixibon +// +//=============================================================================// + +#include "cbase.h" +#include "protagonist_system.h" +#include "weapon_parse.h" +#include "filesystem.h" +#include "hl2_player_shared.h" + +CProtagonistSystem g_ProtagonistSystem; + +//============================================================================= +//============================================================================= +bool CProtagonistSystem::Init() +{ + return true; +} + +void CProtagonistSystem::Shutdown() +{ + PurgeProtagonists(); +} + +void CProtagonistSystem::LevelInitPreEntity() +{ + LoadProtagonistManifest( "scripts/protagonists/protagonists_manifest.txt" ); +} + +void CProtagonistSystem::LevelShutdownPostEntity() +{ + PurgeProtagonists(); +} + +//---------------------------------------------------------------------------- + +void CProtagonistSystem::LoadProtagonistManifest( const char *pszFile ) +{ + KeyValues *pManifest = new KeyValues( "ProtagonistsManifest" ); + if (pManifest->LoadFromFile( filesystem, pszFile )) + { + FOR_EACH_SUBKEY( pManifest, pSubKey ) + { + LoadProtagonistFile( pSubKey->GetString() ); + } + } + pManifest->deleteThis(); +} + +void CProtagonistSystem::LoadProtagonistFile( const char *pszFile ) +{ + KeyValues *pManifest = new KeyValues( "Protagonists" ); + if (pManifest->LoadFromFile( filesystem, pszFile )) + { + FOR_EACH_SUBKEY( pManifest, pProtagKey ) + { + if (!pProtagKey->GetName()[0]) + continue; + + ProtagonistData_t *pProtag = FindProtagonist( pProtagKey->GetName() ); + if (!pProtag) + { + pProtag = &m_Protagonists[m_Protagonists.AddToTail()]; + V_strncpy( pProtag->szName, pProtagKey->GetName(), sizeof( pProtag->szName ) ); + } + + FOR_EACH_SUBKEY( pProtagKey, pSubKey ) + { + const char *pszSubKeyName = pSubKey->GetName(); + + //---------------------------------------------------------------------------- + // Metadata + //---------------------------------------------------------------------------- + if (FStrEq( pszSubKeyName, "inherits_from" )) + { + char szParents[128]; + V_strncpy( szParents, pSubKey->GetString(), sizeof( szParents ) ); + + char *pszToken = strtok( szParents, ";" ); + for (; pszToken != NULL; pszToken = strtok( NULL, ";" )) + { + if (!pszToken || !*pszToken) + continue; + + int nParent = FindProtagonistIndex( pszToken ); + if (nParent >= 0) + { + pProtag->vecParents.FindAndRemove( nParent ); // If it already exists, it will be moved to the front + + pProtag->vecParents.AddToTail( nParent ); + } + } + } + +#ifdef CLIENT_DLL +#else + //---------------------------------------------------------------------------- + // Playermodel + //---------------------------------------------------------------------------- + else if (V_strnicmp( pszSubKeyName, "playermodel", 11 ) == 0) + { + pszSubKeyName += 11; + if (!pszSubKeyName[0]) + { + // Model + pProtag->pszPlayerModel = AllocateString( pSubKey->GetString() ); + } + else if (FStrEq( pszSubKeyName, "_skin" )) + { + // Skin + pProtag->nPlayerSkin = pSubKey->GetInt(); + } + else if (FStrEq( pszSubKeyName, "_body" )) + { + // Bodygroup + pProtag->nPlayerBody = pSubKey->GetInt(); + } + } + + //---------------------------------------------------------------------------- + // Hands + //---------------------------------------------------------------------------- + else if (V_strnicmp( pszSubKeyName, "hands", 5 ) == 0) + { + pszSubKeyName += 5; + if (!pszSubKeyName[0]) + { + // Model + pProtag->pszHandModels[HANDRIG_DEFAULT] = AllocateString( pSubKey->GetString() ); + } + else if (FStrEq( pszSubKeyName, "_skin" )) + { + // Skin + pProtag->nHandSkin = pSubKey->GetInt(); + } + else if (FStrEq( pszSubKeyName, "_body" )) + { + // Bodygroup + pProtag->nHandBody = pSubKey->GetInt(); + } + else + { + // Other Rigs + pszSubKeyName += 1; + for (int i = 0; i < NUM_HAND_RIG_TYPES; i++) + { + extern const char *pHandRigs[NUM_HAND_RIG_TYPES]; + + if (V_stricmp( pszSubKeyName, pHandRigs[i] ) == 0) + { + pProtag->pszHandModels[i] = AllocateString( pSubKey->GetString() ); + break; + } + } + } + } + + //---------------------------------------------------------------------------- + // Responses + //---------------------------------------------------------------------------- + else if (FStrEq( pszSubKeyName, "response_contexts" )) + { + pProtag->pszResponseContexts = AllocateString( pSubKey->GetString() ); + } +#endif + //---------------------------------------------------------------------------- + // Weapon Data + //---------------------------------------------------------------------------- + else if (V_strnicmp( pszSubKeyName, "wpn_viewmodels", 14 ) == 0) + { + // "wpn_viewmodels_c" = models which support arms + bool bHands = FStrEq( pszSubKeyName + 14, "_c" ); + + FOR_EACH_SUBKEY( pSubKey, pWeaponKey ) + { + int i = pProtag->dictWpnData.Find( pWeaponKey->GetName() ); + if (i == pProtag->dictWpnData.InvalidIndex()) + i = pProtag->dictWpnData.Insert( pWeaponKey->GetName() ); + + pProtag->dictWpnData[i].pszVM = AllocateString( pWeaponKey->GetString() ); + pProtag->dictWpnData[i].bUsesHands = bHands; + } + } + else if (FStrEq( pszSubKeyName, "wpn_data" )) // More expanded/explicit + { + FOR_EACH_SUBKEY( pSubKey, pWeaponKey ) + { + int i = pProtag->dictWpnData.Find( pWeaponKey->GetName() ); + if (i == pProtag->dictWpnData.InvalidIndex()) + i = pProtag->dictWpnData.Insert( pWeaponKey->GetName() ); + + const char *pszVM = pWeaponKey->GetString( "viewmodel", NULL ); + if (pszVM) + pProtag->dictWpnData[i].pszVM = AllocateString( pszVM ); + + const char *pszHandRig = pWeaponKey->GetString( "hand_rig", NULL ); + if (pszHandRig) + { + // If there's a specific rig, then it must use hands + pProtag->dictWpnData[i].bUsesHands = true; + + for (int j = 0; j < NUM_HAND_RIG_TYPES; j++) + { + extern const char *pHandRigs[NUM_HAND_RIG_TYPES]; + + if (V_stricmp( pszHandRig, pHandRigs[j] ) == 0) + { + pProtag->dictWpnData[i].nHandRig = j; + break; + } + } + } + else + { + KeyValues *pUsesHands = pWeaponKey->FindKey( "uses_hands" ); + if (pUsesHands) + pProtag->dictWpnData[i].bUsesHands = pUsesHands->GetBool(); + } + + KeyValues *pVMFOV = pWeaponKey->FindKey( "viewmodel_fov" ); + if (pVMFOV) + pProtag->dictWpnData[i].flVMFOV = pVMFOV->GetFloat(); + } + } + } + } + } + pManifest->deleteThis(); +} + +//---------------------------------------------------------------------------- + +CProtagonistSystem::ProtagonistData_t *CProtagonistSystem::GetPlayerProtagonist( const CBasePlayer *pPlayer ) +{ +#ifdef CLIENT_DLL + const C_BaseHLPlayer *pHL2Player = static_cast(pPlayer); +#else + const CHL2_Player *pHL2Player = static_cast(pPlayer); +#endif + if (!pHL2Player) + return NULL; + + int i = pHL2Player->GetProtagonistIndex(); + if (i >= 0) + { + return &g_ProtagonistSystem.m_Protagonists[i]; + } + + return NULL; +} + +CProtagonistSystem::ProtagonistData_t *CProtagonistSystem::FindProtagonist( const char *pszName ) +{ + FOR_EACH_VEC( m_Protagonists, i ) + { + if (FStrEq( pszName, m_Protagonists[i].szName )) + { + return &m_Protagonists[i]; + } + } + + return NULL; +} + +CProtagonistSystem::ProtagonistData_t *CProtagonistSystem::FindProtagonist( int nIndex ) +{ + if (nIndex < 0 || nIndex >= m_Protagonists.Count()) + return NULL; + + return &m_Protagonists[nIndex]; +} + +int CProtagonistSystem::FindProtagonistIndex( const char *pszName ) +{ + FOR_EACH_VEC( m_Protagonists, i ) + { + if (FStrEq( pszName, m_Protagonists[i].szName )) + { + return i; + } + } + + return -1; +} + +void CProtagonistSystem::PrecacheProtagonist( CBaseEntity *pSource, int nIdx ) +{ +#ifndef CLIENT_DLL + if (nIdx < 0) + return; + + ProtagonistData_t &pProtag = m_Protagonists[nIdx]; + + // Playermodel + if (pProtag.pszPlayerModel) + { + pSource->PrecacheModel( pProtag.pszPlayerModel ); + } + + // Hands + for (int i = 0; i < NUM_HAND_RIG_TYPES; i++) + { + if (pProtag.pszHandModels[i]) + { + pSource->PrecacheModel( pProtag.pszHandModels[i] ); + } + } + + // Weapon Data + FOR_EACH_DICT_FAST( pProtag.dictWpnData, i ) + { + if (pProtag.dictWpnData[i].pszVM) + { + pSource->PrecacheModel( pProtag.dictWpnData[i].pszVM ); + } + } +#endif +} + +//---------------------------------------------------------------------------- + +#define GetProtagonistParam( funcName, ... ) \ + ProtagonistData_t *pProtag = GetPlayerProtagonist( pPlayer ); \ + if (!pProtag) \ + return NULL; \ + return funcName( *pProtag, ##__VA_ARGS__ ); \ + +#ifdef CLIENT_DLL +#else +const char *CProtagonistSystem::GetProtagonist_PlayerModel( const CBasePlayer *pPlayer ) +{ + GetProtagonistParam( DoGetProtagonist_PlayerModel ) +} + +int CProtagonistSystem::GetProtagonist_PlayerModelSkin( const CBasePlayer *pPlayer ) +{ + GetProtagonistParam( DoGetProtagonist_PlayerModelSkin ) +} + +int CProtagonistSystem::GetProtagonist_PlayerModelBody( const CBasePlayer *pPlayer ) +{ + GetProtagonistParam( DoGetProtagonist_PlayerModelBody ) +} + +const char *CProtagonistSystem::GetProtagonist_HandModel( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_HandModel, pWeapon ) +} + +int CProtagonistSystem::GetProtagonist_HandModelSkin( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_HandModelSkin, pWeapon ) +} + +int CProtagonistSystem::GetProtagonist_HandModelBody( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_HandModelBody, pWeapon ) +} + +const char *CProtagonistSystem::GetProtagonist_ResponseContexts( const CBasePlayer *pPlayer ) +{ + ProtagonistData_t *pProtag = g_ProtagonistSystem.GetPlayerProtagonist( pPlayer ); + if (!pProtag) + return NULL; + + // Collect contexts from base as well as parents + char szContexts[128] = { 0 }; + g_ProtagonistSystem.DoGetProtagonist_ResponseContexts( *pProtag, szContexts, sizeof( szContexts ) ); + + // Replace trailing comma + int nLast = V_strlen( szContexts )-1; + if (szContexts[nLast] == ',') + szContexts[nLast] = '\0'; + + if (!szContexts[0]) + return NULL; + + return AllocateString( szContexts ); +} +#endif + +const char *CProtagonistSystem::GetProtagonist_ViewModel( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_ViewModel, pWeapon ) +} + +float *CProtagonistSystem::GetProtagonist_ViewModelFOV( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_ViewModelFOV, pWeapon ) +} + +bool *CProtagonistSystem::GetProtagonist_UsesHands( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_UsesHands, pWeapon ) +} + +int *CProtagonistSystem::GetProtagonist_HandRig( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ) +{ + GetProtagonistParam( DoGetProtagonist_HandRig, pWeapon ) +} + +//---------------------------------------------------------------------------- + +#define GetProtagonistRecurse( funcName, ... ) \ + FOR_EACH_VEC( pProtag.vecParents, i ) \ + { \ + ProtagonistData_t *pParent = FindProtagonist(pProtag.vecParents[i]); \ + if (!pParent) \ + continue; \ + auto returnVar = funcName( *pParent, ##__VA_ARGS__ ); \ + if (returnVar) \ + return returnVar; \ + } \ + +#define GetProtagonistRecurseNoReturn( funcName, ... ) \ + FOR_EACH_VEC( pProtag.vecParents, i ) \ + { \ + ProtagonistData_t *pParent = FindProtagonist(pProtag.vecParents[i]); \ + if (!pParent) \ + continue; \ + funcName( *pParent, ##__VA_ARGS__ ); \ + } \ + +#ifdef CLIENT_DLL +#else +const char *CProtagonistSystem::DoGetProtagonist_PlayerModel( ProtagonistData_t &pProtag ) +{ + if (pProtag.pszPlayerModel) + return pProtag.pszPlayerModel; + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_PlayerModel ) + + return NULL; +} + +int CProtagonistSystem::DoGetProtagonist_PlayerModelSkin( ProtagonistData_t &pProtag ) +{ + if (pProtag.nPlayerSkin >= 0) + return pProtag.nPlayerSkin; + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_PlayerModelSkin ) + + return 0; +} + +int CProtagonistSystem::DoGetProtagonist_PlayerModelBody( ProtagonistData_t &pProtag ) +{ + if (pProtag.nPlayerBody >= 0) + return pProtag.nPlayerBody; + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_PlayerModelBody ) + + return 0; +} + +const char *CProtagonistSystem::DoGetProtagonist_HandModel( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + int nRigType = pWeapon->GetHandRig(); + if (pProtag.pszHandModels[nRigType]) + return pProtag.pszHandModels[nRigType]; + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_HandModel, pWeapon ) + + return NULL; +} + +int CProtagonistSystem::DoGetProtagonist_HandModelSkin( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + if (pProtag.nHandSkin >= 0) + return pProtag.nHandSkin; + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_HandModelSkin, pWeapon ) + + return NULL; +} + +int CProtagonistSystem::DoGetProtagonist_HandModelBody( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + if (pProtag.nHandBody >= 0) + return pProtag.nHandBody; + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_HandModelBody, pWeapon ) + + return NULL; +} + +void CProtagonistSystem::DoGetProtagonist_ResponseContexts( ProtagonistData_t &pProtag, char *pszContexts, int nContextsSize ) +{ + if (pProtag.pszResponseContexts) + { + V_strncat( pszContexts, pProtag.pszResponseContexts, nContextsSize ); + V_strncat( pszContexts, ",", nContextsSize ); + } + + // Recursively search parent protagonists + GetProtagonistRecurseNoReturn( DoGetProtagonist_ResponseContexts, pszContexts, nContextsSize ) +} +#endif + +const char *CProtagonistSystem::DoGetProtagonist_ViewModel( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + FOR_EACH_DICT_FAST( pProtag.dictWpnData, i ) + { + // HACKHACK: GetClassname is not const + if (!FStrEq( pProtag.dictWpnData.GetElementName( i ), const_cast(pWeapon)->GetClassname() )) + continue; + + if (pProtag.dictWpnData[i].pszVM) + return pProtag.dictWpnData[i].pszVM; + break; + } + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_ViewModel, pWeapon ) + + return NULL; +} + +float *CProtagonistSystem::DoGetProtagonist_ViewModelFOV( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + FOR_EACH_DICT_FAST( pProtag.dictWpnData, i ) + { + // HACKHACK: GetClassname is not const + if (!FStrEq( pProtag.dictWpnData.GetElementName( i ), const_cast(pWeapon)->GetClassname() )) + continue; + + return &pProtag.dictWpnData[i].flVMFOV; + } + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_ViewModelFOV, pWeapon ) + + return NULL; +} + +bool *CProtagonistSystem::DoGetProtagonist_UsesHands( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + FOR_EACH_DICT_FAST( pProtag.dictWpnData, i ) + { + // HACKHACK: GetClassname is not const + if (!FStrEq( pProtag.dictWpnData.GetElementName( i ), const_cast(pWeapon)->GetClassname() )) + continue; + + return &pProtag.dictWpnData[i].bUsesHands; + } + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_UsesHands, pWeapon ) + + return NULL; +} + +int *CProtagonistSystem::DoGetProtagonist_HandRig( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ) +{ + FOR_EACH_DICT_FAST( pProtag.dictWpnData, i ) + { + // HACKHACK: GetClassname is not const + if (!FStrEq( pProtag.dictWpnData.GetElementName( i ), const_cast(pWeapon)->GetClassname() )) + continue; + + return &pProtag.dictWpnData[i].nHandRig; + } + + // Recursively search parent protagonists + GetProtagonistRecurse( DoGetProtagonist_HandRig, pWeapon ) + + return NULL; +} + +//---------------------------------------------------------------------------- + +const char *CProtagonistSystem::FindString( const char *string ) +{ + unsigned short i = m_Strings.Find( string ); + return i == m_Strings.InvalidIndex() ? NULL : m_Strings[i]; +} + +const char *CProtagonistSystem::AllocateString( const char *string ) +{ + int i = m_Strings.Find( string ); + if (i != m_Strings.InvalidIndex()) + { + return m_Strings[i]; + } + + int len = Q_strlen( string ); + char *out = new char[len + 1]; + Q_memcpy( out, string, len ); + out[len] = 0; + + return m_Strings[m_Strings.Insert( out )]; +} + +//---------------------------------------------------------------------------- + +void CProtagonistSystem::PurgeProtagonists() +{ + m_Protagonists.RemoveAll(); + + for (unsigned int i = 0; i < m_Strings.Count(); i++) + { + delete m_Strings[i]; + } + m_Strings.Purge(); +} + +void CProtagonistSystem::PrintProtagonistData() +{ + Msg( "PROTAGONISTS\n\n" ); + + FOR_EACH_VEC( m_Protagonists, i ) + { + ProtagonistData_t &pProtag = m_Protagonists[i]; + + Msg( "\t\"%s\"\n", pProtag.szName ); + + Msg( "\t\tParents: %i\n", pProtag.vecParents.Count() ); + if (pProtag.vecParents.Count() > 0) + { + FOR_EACH_VEC( pProtag.vecParents, j ) + { + ProtagonistData_t *pParent = FindProtagonist( pProtag.vecParents[j] ); + if (!pParent) + continue; + + Msg( "\t\t\t\"%s\"\n", pParent->szName ); + } + } + +#ifdef CLIENT_DLL +#else + if (pProtag.pszPlayerModel) + Msg( "\t\tPlayer model: \"%s\" (%i, %i)\n", pProtag.pszPlayerModel, pProtag.nPlayerSkin, pProtag.nPlayerBody ); + + for (int j = 0; j < NUM_HAND_RIG_TYPES; j++) + { + extern const char *pHandRigs[NUM_HAND_RIG_TYPES]; + if (pProtag.pszHandModels[j]) + Msg( "\t\tHand %s model: \"%s\" (%i, %i)\n", pHandRigs[j], pProtag.pszHandModels[j], pProtag.nPlayerSkin, pProtag.nPlayerBody ); + } + + // Weapon Data + Msg( "\t\tWeapon Data: %i\n", pProtag.dictWpnData.Count() ); + FOR_EACH_DICT_FAST( pProtag.dictWpnData, j ) + { + Msg( "\t\t\t%s\n", pProtag.dictWpnData.GetElementName(j) ); + if (pProtag.dictWpnData[j].pszVM) + Msg( "\t\t\t\tViewmodel: \"%s\"\n", pProtag.dictWpnData[j].pszVM ); + + Msg( "\t\t\t\tUses hands: %d, hand rig: %i\n", pProtag.dictWpnData[j].bUsesHands, pProtag.dictWpnData[j].nHandRig ); + } +#endif + } +} + +//---------------------------------------------------------------------------- + +CON_COMMAND_SHARED( protagonist_reload, "Reloads protagonist data" ) +{ +#ifdef CLIENT_DLL + if (C_BasePlayer::GetLocalPlayer() == NULL) + { + Msg( "Must be in a level\n" ); + return; + } +#endif + + g_ProtagonistSystem.PurgeProtagonists(); + g_ProtagonistSystem.LoadProtagonistManifest( "scripts/protagonists/protagonists_manifest.txt" ); +} + +CON_COMMAND_SHARED( protagonist_dump, "Dumps protagonist data" ) +{ +#ifdef CLIENT_DLL + if (C_BasePlayer::GetLocalPlayer() == NULL) + { + Msg( "Must be in a level\n" ); + return; + } +#endif + + g_ProtagonistSystem.PrintProtagonistData(); +} diff --git a/sp/src/game/shared/mapbase/protagonist_system.h b/sp/src/game/shared/mapbase/protagonist_system.h new file mode 100644 index 0000000000..4b73cc8057 --- /dev/null +++ b/sp/src/game/shared/mapbase/protagonist_system.h @@ -0,0 +1,151 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: System to easily switch between player characters. +// +// Author: Blixibon +// +//=============================================================================// + +#ifndef PROTAGONIST_SYSTEM_H +#define PROTAGONIST_SYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#ifdef CLIENT_DLL +#include "c_baseplayer.h" +#else +#include "player.h" +#endif + +#define MAX_PROTAGONIST_NAME 32 + +//============================================================================= +//============================================================================= +class CProtagonistSystem : public CAutoGameSystem +{ +public: + CProtagonistSystem() : m_Strings( 256, 0, &StringLessThan ) { } + ~CProtagonistSystem() { PurgeProtagonists(); } + + bool Init(); + void Shutdown(); + void LevelInitPreEntity(); + void LevelShutdownPostEntity(); + + //---------------------------------------------------------------------------- + + void LoadProtagonistManifest( const char *pszFile ); + void LoadProtagonistFile( const char *pszFile ); + + //---------------------------------------------------------------------------- + + int FindProtagonistIndex( const char *pszName ); + + void PrecacheProtagonist( CBaseEntity *pSource, int nIdx ); + + //---------------------------------------------------------------------------- + +#ifdef CLIENT_DLL +#else + const char *GetProtagonist_PlayerModel( const CBasePlayer *pPlayer ); + int GetProtagonist_PlayerModelSkin( const CBasePlayer *pPlayer ); + int GetProtagonist_PlayerModelBody( const CBasePlayer *pPlayer ); + const char *GetProtagonist_HandModel( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + int GetProtagonist_HandModelSkin( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + int GetProtagonist_HandModelBody( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + const char *GetProtagonist_ResponseContexts( const CBasePlayer *pPlayer ); +#endif + + const char *GetProtagonist_ViewModel( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + float *GetProtagonist_ViewModelFOV( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + bool *GetProtagonist_UsesHands( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + int *GetProtagonist_HandRig( const CBasePlayer *pPlayer, const CBaseCombatWeapon *pWeapon ); + + //---------------------------------------------------------------------------- + + void PurgeProtagonists(); + void PrintProtagonistData(); + + //---------------------------------------------------------------------------- + +private: + + struct ProtagonistData_t + { + ProtagonistData_t() + { + + } + + char szName[MAX_PROTAGONIST_NAME]; + + CUtlVector vecParents; + +#ifdef CLIENT_DLL +#else + // Playermodel + const char *pszPlayerModel = NULL; + int nPlayerSkin = -1; + int nPlayerBody = -1; + + // Hands + const char *pszHandModels[NUM_HAND_RIG_TYPES] = {}; + int nHandSkin = -1; + int nHandBody = -1; + + // Responses + const char *pszResponseContexts = NULL; +#endif + + // Weapon Data + struct WeaponDataOverride_t + { + const char *pszVM = NULL; + bool bUsesHands = false; + int nHandRig = 0; + float flVMFOV = 0.0f; + }; + + CUtlDict dictWpnData; + }; + + ProtagonistData_t *GetPlayerProtagonist( const CBasePlayer *pPlayer ); + ProtagonistData_t *FindProtagonist( const char *pszName ); + ProtagonistData_t *FindProtagonist( int nIndex ); + + // For recursion +#ifdef CLIENT_DLL +#else + const char *DoGetProtagonist_PlayerModel( ProtagonistData_t &pProtag ); + int DoGetProtagonist_PlayerModelSkin( ProtagonistData_t &pProtag ); + int DoGetProtagonist_PlayerModelBody( ProtagonistData_t &pProtag ); + const char *DoGetProtagonist_HandModel( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + int DoGetProtagonist_HandModelSkin( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + int DoGetProtagonist_HandModelBody( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + void DoGetProtagonist_ResponseContexts( ProtagonistData_t &pProtag, char *pszContexts, int nContextsSize ); +#endif + + const char *DoGetProtagonist_ViewModel( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + float *DoGetProtagonist_ViewModelFOV( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + bool *DoGetProtagonist_UsesHands( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + int *DoGetProtagonist_HandRig( ProtagonistData_t &pProtag, const CBaseCombatWeapon *pWeapon ); + + //---------------------------------------------------------------------------- + + const char *FindString( const char *string ); + const char *AllocateString( const char *string ); + + //---------------------------------------------------------------------------- + +private: + CUtlVector m_Protagonists; + + // Dedicated strings, copied from game string pool + CUtlRBTree m_Strings; + +}; + +extern CProtagonistSystem g_ProtagonistSystem; + +#endif // PROTAGONIST_SYSTEM_H diff --git a/sp/src/game/shared/weapon_parse.cpp b/sp/src/game/shared/weapon_parse.cpp index 872c6ad0e9..4f7786589c 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_nHandRig = HANDRIG_DEFAULT; m_nWeaponRestriction = WPNRESTRICT_NONE; #endif } @@ -417,6 +418,12 @@ extern ConVar hud_fastswitch; #endif #ifdef MAPBASE +const char* pHandRigs[NUM_HAND_RIG_TYPES] = { + "default", + "css", + "blender", +}; + const char* pWeaponRestrictions[NUM_WEAPON_RESTRICTION_TYPES] = { "none", "player_only", @@ -493,6 +500,19 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam m_bUsesHands = ( pKeyValuesData->GetInt( "uses_hands", 0 ) != 0 ) ? true : false; + const char* pszHandRig = pKeyValuesData->GetString("hand_rig", nullptr); + if (pszHandRig) + { + for (int i = 0; i < NUM_HAND_RIG_TYPES; i++) + { + if (V_stricmp(pszHandRig, pHandRigs[i]) == 0) + { + m_nHandRig = i; + break; + } + } + } + const char* pszRestrictString = pKeyValuesData->GetString("usage_restriction", nullptr); if (pszRestrictString) { diff --git a/sp/src/game/shared/weapon_parse.h b/sp/src/game/shared/weapon_parse.h index 4ba7b02cc6..0d74d95b2c 100644 --- a/sp/src/game/shared/weapon_parse.h +++ b/sp/src/game/shared/weapon_parse.h @@ -59,6 +59,15 @@ class CHudTexture; class KeyValues; #ifdef MAPBASE +enum HandRigTypes_e +{ + HANDRIG_DEFAULT, // Default HL2 rig + HANDRIG_CSS, // CS:S viewmodel rig + HANDRIG_BLENDER, // Blender IK rig + + NUM_HAND_RIG_TYPES +}; + enum WeaponUsageRestricions_e { WPNRESTRICT_NONE = 0, @@ -136,6 +145,7 @@ class FileWeaponInfo_t char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground bool m_bUsesHands; + int m_nHandRig; int m_nWeaponRestriction; #endif