Merge pull request #183 from Petercov/mapbase-feature/custom-weapons

Enhanced custom weapons support
This commit is contained in:
Blixibon 2023-10-24 19:12:05 -05:00 committed by GitHub
commit 6915787c80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2381 additions and 35 deletions

View File

@ -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;

View File

@ -69,6 +69,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"
}

View File

@ -0,0 +1,67 @@
//========= 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"
#include "c_basehlcombatweapon.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
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';
}
class C_HLCustomWeaponGun : public C_BaseHLCombatWeapon
{
public:
DECLARE_CLASS(C_HLCustomWeaponGun, C_BaseHLCombatWeapon);
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';
}

View File

@ -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 );
}
}

View File

@ -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();
@ -162,7 +182,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() )
{
@ -234,7 +259,7 @@ Activity CBaseHLBludgeonWeapon::ChooseIntersectionPointAndActivity( trace_t &hit
}
return ACT_VM_HITCENTER;
return GetPrimaryAttackActivity();
}
//-----------------------------------------------------------------------------
@ -292,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?
@ -315,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 );
@ -369,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 );
@ -409,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

View File

@ -23,6 +23,9 @@ public:
CBaseHLBludgeonWeapon();
DECLARE_SERVERCLASS();
#ifdef MAPBASE
DECLARE_DATADESC();
#endif // MAPBASE
virtual void Spawn( void );
virtual void Precache( void );
@ -30,6 +33,9 @@ public:
//Attack functions
virtual void PrimaryAttack( void );
virtual void SecondaryAttack( void );
#ifdef MAPBASE
void DelayedAttack(void);
#endif // MAPBASE
virtual void ItemPostFrame( void );
@ -44,6 +50,12 @@ public:
virtual int CapabilitiesGet( void );
virtual int WeaponMeleeAttack1Condition( float flDot, float flDist );
#ifdef MAPBASE
virtual int GetDamageType() { return DMG_CLUB; }
virtual float GetHitDelay() { return 0.f; }
virtual bool CanHolster(void);
#endif // MAPBASE
protected:
virtual void ImpactEffect( trace_t &trace );
@ -52,6 +64,11 @@ private:
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

View File

@ -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();

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -0,0 +1,208 @@
//========= 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"
#define GENERIC_MANIFEST_FILE "scripts/mapbase_default_manifest.txt"
#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;
IMPLEMENT_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol);
CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactorySystem")
{
}
void CCustomWeaponSystem::LevelInitPreEntity()
{
LoadCustomWeaponsManifest(GLOBAL_WEAPONS_MANIFEST);
// 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];
value[0] = '\0';
// 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, STRING(gpGlobals->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))
{
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 = pFactory;
m_ClassFactories[ClassIndex].pData = pData;
EntityFactoryDictionary()->UninstallFactory(pszClassname);
EntityFactoryDictionary()->InstallFactory(m_ClassFactories[ClassIndex].pNewFactory, pszClassname);
}
}
}
}
}
#undef Factory
void CCustomWeaponSystem::LevelShutdownPostEntity()
{
for (unsigned short 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));
Assert(entry.pData);
entry.pNewFactory->ReleaseData(entry.pData);
}
m_ClassFactories.Purge();
g_CustomWeaponSymbolSymbolTable.RemoveAll();
}
void CCustomWeaponSystem::ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName)
{
ICustomWeapon* pCustom = dynamic_cast<ICustomWeapon*> (pWeapon);
if (!pCustom)
return;
unsigned short i = m_ClassFactories.Find(pClassName);
if (!m_ClassFactories.IsValidIndex(i))
return;
pCustom->InitCustomWeaponFromData(m_ClassFactories[i].pData, m_ClassFactories[i].sDataFile.String());
}
CUtlDict<ICustomWeaponDataLoader*, unsigned short>& CustomWeaponsFactoryDictionary()
{
static CUtlDict<ICustomWeaponDataLoader*, unsigned short> dict;
return dict;
}
static CCustomWeaponSystem g_CustomWeaponsSystem;
CCustomWeaponSystem* CustomWeaponSystem()
{
return &g_CustomWeaponsSystem;
}

View File

@ -0,0 +1,117 @@
//========= 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
#include "utldict.h"
#include "utlsymbol.h"
DECLARE_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol);
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 InitCustomWeaponFromData(const void* pData, const char *pszWeaponScript) = 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
{
CustomWeaponSymbol sDataFile;
ICustomWeaponDataLoader* pNewFactory;
IEntityFactory* pOldFactory;
const void* pData;
} CustomClassName_t;
CUtlDict<CustomClassName_t, unsigned short> m_ClassFactories;
};
CCustomWeaponSystem* CustomWeaponSystem();
CUtlDict< ICustomWeaponDataLoader*, unsigned short >& CustomWeaponsFactoryDictionary();
template <class T>
class CCustomWeaponEntityFactoryBase : public ICustomWeaponDataLoader
{
public:
CCustomWeaponEntityFactoryBase(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);
}
};
template <class Entity, class Data>
class CDefaultCustomWeaponEntityFactory : public CCustomWeaponEntityFactoryBase<Entity>
{
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<DLLClassName, DataStruct> custom_weapon_##factoryName##_factory( #factoryName );
#endif // !CUSTOM_WEAPON_FACTORY_H

File diff suppressed because it is too large Load Diff

View File

@ -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 );
}
}

View File

@ -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"
@ -86,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"

View File

@ -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();

View File

@ -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();

View File

@ -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!
}
}
@ -2886,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 :

View File

@ -390,7 +390,7 @@ public:
bool IsLocked( CBaseEntity *pAsker );
//All weapons can be picked up by NPCs by default
virtual bool CanBePickedUpByNPCs( void ) { return true; }
virtual bool CanBePickedUpByNPCs(void);
virtual int GetSkinOverride() const { return -1; }

View File

@ -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!!!
@ -198,13 +199,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();
}
@ -432,7 +426,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) \
{ \
@ -586,6 +580,36 @@ int CWeaponCustomScripted::WeaponMeleeAttack2Condition( float flDot, float flDis
return BaseClass::WeaponMeleeAttack2Condition( flDot, flDist );
}
struct VScriptWeaponCustomData_s
{
char cScripts[256];
bool Parse(KeyValues* pKVWeapon)
{
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<const VScriptWeaponCustomData_s *> (pData)->cScripts, 256);
}
extern ConVar sv_script_think_interval;
#else
void CWeaponCustomScripted::OnDataChanged(DataUpdateType_t type)
{
BaseClass::OnDataChanged(type);
if (!m_ScriptScope.IsInitialized())
{
RunVScripts();
}
}
#endif
//-----------------------------------------------------------------------------
@ -604,3 +628,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
}

View File

@ -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 );
@ -45,6 +50,8 @@ public:
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 );
@ -106,6 +113,11 @@ public:
int WeaponRangeAttack2Condition( float flDot, float flDist );
int WeaponMeleeAttack1Condition( float flDot, float flDist );
int WeaponMeleeAttack2Condition( float flDot, float flDist );
// Inherited via ICustomWeapon
virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript);
#else
void OnDataChanged(DataUpdateType_t type);
#endif
ALLOW_SCRIPT_ACCESS();

View File

@ -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.

View File

@ -58,6 +58,17 @@ int GetWeaponSoundFromString( const char *pszString );
class CHudTexture;
class KeyValues;
#ifdef MAPBASE
enum WeaponUsageRestricions_e
{
WPNRESTRICT_NONE = 0,
WPNRESTRICT_PLAYER_ONLY,
WPNRESTRICT_NPCS_ONLY,
NUM_WEAPON_RESTRICTION_TYPES
};
#endif // MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Contains the data read from the weapon's script file.
// It's cached so we only read each weapon's script file once.
@ -125,6 +136,8 @@ public:
char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground
bool m_bUsesHands;
int m_nWeaponRestriction;
#endif
// CLIENT DLL