Simulated Bullets: Difference between revisions
Line 981: | Line 981: | ||
|} | |} | ||
===cl_dll\clientmode_shared.cpp=== | ===cl_dll\clientmode_shared.cpp=== |
Revision as of 20:58, 4 December 2005
Preface
Basically, with simulated bullets, instead of treating each bullet as a separate entity, groups of bullets will be simulated in batch simulation code. Bullet simulation using individual entities is not the way to go because they can fill up the entity list and are just unconventional. This article details the changes to the HL2MP SDK that will achieve this effect. It will also attempt to create a system that works for maxplayers 1 in the HL2MP SDK for those mods that want to be both. And for the singleplayer only mods, just try doing this with entities instead (you'll have way more control).


Procedure
Create this file and add it to both projects of the solution.
#include "ammodef.h" #define BUTTER_MODE -1 #define ONE_HIT_MODE -2 #ifdef CLIENT_DLL//----------------------- class C_BulletManager; extern C_BulletManager *g_pBulletManager; #define CBulletManager C_BulletManager #define CSimulatedBullet C_SimulatedBullet #else//----------------------------------- class CBulletManager; extern CBulletManager *g_pBulletManager; #endif//---------------------------------- inline CBulletManager *BulletManager() { return g_pBulletManager; } extern ConVar g_debug_bullets; class CSimulatedBullet { public: CSimulatedBullet(); CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt, int ammoType, Vector &vecDirShooting, Vector &vecOrigin, bool bTraceHull #ifndef CLIENT_DLL , int nFlags, CBaseEntity *pCaller, int iDamage #endif ); ~CSimulatedBullet(); inline float BulletSpeedRatio(void) { return m_flBulletSpeed/m_flInitialBulletSpeed; } inline bool IsInWorld(void) const { if (m_vecOrigin.x >= MAX_COORD_INTEGER) return false; if (m_vecOrigin.y >= MAX_COORD_INTEGER) return false; if (m_vecOrigin.z >= MAX_COORD_INTEGER) return false; if (m_vecOrigin.x <= MIN_COORD_INTEGER) return false; if (m_vecOrigin.y <= MIN_COORD_INTEGER) return false; if (m_vecOrigin.z <= MIN_COORD_INTEGER) return false; return true; } bool StartSolid(trace_t &ptr, Vector &vecNewRay); bool AllSolid(trace_t &ptr); bool EndSolid(trace_t &ptr); #ifdef CLIENT_DLL bool SimulateBullet(void); #else void EntityImpact(trace_t &ptr); bool SimulateBullet(float flLagCompensation=-1); #endif inline int AmmoIndex(void) const { return m_iAmmoType; } private: bool m_bTraceHull; bool m_bWasInWater; CTraceFilterSkipTwoEntities *m_pTwoEnts; CTraceFilterSimpleList *m_pIgnoreList;//already hit #ifndef CLIENT_DLL CUtlVector<CBaseEntity *> m_CompensationConsiderations;//Couldn't resist #endif EHANDLE m_hCaller; EHANDLE m_hLastHit; float m_flBulletSpeed; float m_flEntryDensity; float m_flInitialBulletSpeed; float m_flRayLength; int m_iAmmoType; #ifndef CLIENT_DLL int m_iDamage; FireBulletsFlags_t m_nFlags; #endif Vector m_vecDirShooting; Vector m_vecOrigin; Vector m_vecEntryPosition; Vector m_vecTraceRay; }; extern CUtlLinkedList<CSimulatedBullet*> g_Bullets; class CBulletManager : public CBaseEntity { DECLARE_CLASS( CBulletManager, CBaseEntity ); public: CBulletManager() { g_Bullets.PurgeAndDeleteElements(); } #ifdef CLIENT_DLL void ClientThink(void); int AddBullet(CSimulatedBullet *pBullet); #else void Think(void); int AddBullet(CSimulatedBullet *pBullet, float flLatency=0.0f); #endif unsigned short RemoveBullet(int index); void UpdateBulletStopSpeed(void); int BulletStopSpeed(void) { return m_iBulletStopSpeed; } private: int m_iBulletStopSpeed; };
Create this file and add it to both projects of the solution.
#include "cbase.h" #include "util_shared.h" #include "bullet_manager.h" #include "shot_manipulator.h" #include "tier0/vprof.h" CBulletManager *g_pBulletManager; CUtlLinkedList<CSimulatedBullet*> g_Bullets; #ifdef CLIENT_DLL//------------------------------------------------- #include "engine/ivdebugoverlay.h" ConVar g_debug_client_bullets( "g_cl_debug_bullets", "0", FCVAR_CHEAT ); extern void FX_PlayerTracer( Vector& start, Vector& end); #else//------------------------------------------------------------- #include "soundent.h" #include "player_pickup.h" #include "ilagcompensationmanager.h" ConVar g_debug_bullets( "g_debug_bullets", "0", FCVAR_CHEAT ); #endif//------------------------------------------------------------ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static void BulletSpeedModifierCallback(ConVar *var, const char *pOldString) { if(var->GetFloat()==0.0f) var->Revert(); } ConVar sv_bullet_speed_modifier( "sv_bullet_speed_modifier", "700.000000", (FCVAR_ARCHIVE | FCVAR_REPLICATED), "Density/(This Value) * (Distance Penetrated) = (Change in Speed)", BulletSpeedModifierCallback ); static void BulletStopSpeedCallback(ConVar *var, const char *pOldString) { int val = var->GetInt(); if(val<ONE_HIT_MODE) var->Revert(); else if(BulletManager()) BulletManager()->UpdateBulletStopSpeed(); } ConVar sv_bullet_stop_speed( "sv_bullet_stop_speed", "40", (FCVAR_ARCHIVE | FCVAR_REPLICATED), "Integral speed at which to remove the bullet from the bullet queue\n-1 is butter mode\n-2 is 1 hit mode", BulletStopSpeedCallback ); LINK_ENTITY_TO_CLASS( bullet_manager, CBulletManager ); //================================================== // Purpose: Constructor //================================================== CSimulatedBullet::CSimulatedBullet() { m_vecOrigin.Init(); m_vecDirShooting.Init(); m_flInitialBulletSpeed = m_flBulletSpeed = 0; m_flEntryDensity = 0.0f; #ifndef CLIENT_DLL m_nFlags = (FireBulletsFlags_t)0; m_iDamage = 0; #endif m_iAmmoType = 0; } //================================================== // Purpose: Constructor //================================================== CSimulatedBullet::CSimulatedBullet( CBaseEntity *pAttacker, CBaseEntity *pAdditionalIgnoreEnt, int ammoType, Vector &vecDirShooting, Vector &vecOrigin, bool bTraceHull #ifndef CLIENT_DLL , int nFlags, CBaseEntity *pCaller, int iDamage #endif ) { // Input validation Assert( pAttacker ); #ifndef CLIENT_DLL Assert( pCaller ); #endif // Create a list of entities with which this bullet does not collide. m_pIgnoreList = new CTraceFilterSimpleList(COLLISION_GROUP_NONE); Assert( m_pIgnoreList ); // Don't collide with the entity firing the bullet. m_pIgnoreList->AddEntityToIgnore(pAttacker); // Don't collide with some optionally-specified entity. if(pAdditionalIgnoreEnt!=NULL) m_pIgnoreList->AddEntityToIgnore(pAdditionalIgnoreEnt); // Not useful yet... may be useful for effects later //m_pTwoEnts = new CTraceFilterSkipTwoEntities(pAttacker,pAdditionalIgnoreEnt,COLLISION_GROUP_NONE); // Basic information about the bullet m_iAmmoType = ammoType; m_flInitialBulletSpeed = m_flBulletSpeed = GetAmmoDef()->GetAmmoOfIndex(ammoType)->bulletSpeed; m_vecDirShooting = vecDirShooting; m_vecOrigin = vecOrigin; m_bTraceHull = bTraceHull; #ifndef CLIENT_DLL m_nFlags = (FireBulletsFlags_t)nFlags; m_iDamage = iDamage; m_hCaller = pCaller; #endif m_flEntryDensity = 0.0f; m_vecTraceRay = vecDirShooting * m_flInitialBulletSpeed; m_flRayLength = m_flInitialBulletSpeed; } //================================================== // Purpose: Deconstructor //================================================== CSimulatedBullet::~CSimulatedBullet() { delete m_pIgnoreList; delete m_pTwoEnts; } #ifdef CLIENT_DLL bool FX_AffectRagdolls(Vector vecOrigin, Vector vecStart, int iDamageType); //================================================== // Purpose: Simulates a bullet through a ray of its bullet speed //================================================== bool C_SimulatedBullet::SimulateBullet(void) { VPROF( "C_SimulatedBullet::SimulateBullet" ); #else bool CSimulatedBullet::SimulateBullet(float flLagCompensation/*=-1 (seconds)*/) { VPROF( "CSimulatedBullet::SimulateBullet" ); #endif if(!IsFinite(m_flBulletSpeed)) return false;//prevent a weird crash trace_t trace; Vector vecTraceStart(m_vecOrigin); Vector vecTraceRay; if(m_flRayLength==m_flBulletSpeed) { vecTraceRay = m_vecTraceRay; } else { vecTraceRay = m_vecTraceRay = m_vecDirShooting * m_flBulletSpeed; m_flRayLength = m_flBulletSpeed; } #ifdef GAME_DLL if(flLagCompensation!=-1) { vecTraceRay *= flLagCompensation * 100; } #endif m_vecOrigin += vecTraceRay; bool bInWater = (UTIL_PointContents(m_vecOrigin)&MASK_SPLITAREAPORTAL)==1; if(!IsInWorld()) { return false; } #ifdef CLIENT_DLL DevMsg("Tracer\n"); FX_PlayerTracer( vecTraceStart, m_vecOrigin ); #endif if(m_bWasInWater!=bInWater) { #ifdef CLIENT_DLL //TODO: water impact effect //CBaseEntity::HandleShotImpactingWater #endif //CLIENT_DLL } if(bInWater) { #ifdef CLIENT_DLL //TODO: 1 bubble clientside #endif //CLIENT_DLL } #ifdef CLIENT_DLL if(g_debug_client_bullets.GetBool()) { debugoverlay->AddLineOverlay( vecTraceStart, m_vecOrigin, 255, 0, 0, true, 10.0f ); } #else if(g_debug_bullets.GetBool()) { NDebugOverlay::Line( vecTraceStart, m_vecOrigin, 255, 255, 255, true, 10.0f ); } #endif m_bWasInWater = bInWater; bool bulletSpeedCheck; do { bulletSpeedCheck = false; if(m_bTraceHull) UTIL_TraceHull( vecTraceStart, m_vecOrigin, Vector(-3, -3, -3), Vector(3, 3, 3), MASK_SHOT, m_pIgnoreList, &trace ); else UTIL_TraceLine( vecTraceStart, m_vecOrigin, MASK_SHOT, m_pIgnoreList, &trace ); if(!(trace.surface.flags&SURF_SKY)) { if(trace.allsolid)//in solid { if(!AllSolid(trace)) return false; bulletSpeedCheck = true; } else if(trace.startsolid)//exit solid { if(!StartSolid(trace, vecTraceRay)) return false; bulletSpeedCheck = true; } else if(trace.fraction!=1.0f)//hit solid { if(!EndSolid(trace)) return false; bulletSpeedCheck = true; } else { //don't do a bullet speed check for not touching anything break; } } if(bulletSpeedCheck) { if(m_flBulletSpeed<=BulletManager()->BulletStopSpeed()) { return false; } } vecTraceStart = trace.endpos + m_vecDirShooting; if(trace.fraction==0.0f) return false; }while(trace.fraction!=1.0f); return true; } //================================================== // Purpose: Simulates when a solid is exited //================================================== bool CSimulatedBullet::StartSolid(trace_t &ptr, Vector &vecTraceRay) { #ifdef CLIENT_DLL //TODO: penetration surface impact stuff #endif //CLIENT_DLL Vector vecExitPosition = ptr.fractionleftsolid * vecTraceRay + ptr.startpos; float flPenetrationDistance = m_vecEntryPosition.DistTo(vecExitPosition); switch(BulletManager()->BulletStopSpeed()) { case BUTTER_MODE: { //Do nothing to bullet speed break; } case ONE_HIT_MODE: { return false; } default: { m_flBulletSpeed -= flPenetrationDistance * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); break; } } return true; } //================================================== // Purpose: Simulates when a solid is being passed through //================================================== bool CSimulatedBullet::AllSolid(trace_t &ptr) { switch(BulletManager()->BulletStopSpeed()) { case BUTTER_MODE: { //Do nothing to bullet speed break; } case ONE_HIT_MODE: { return false; } default: { m_flBulletSpeed -= m_flBulletSpeed * m_flEntryDensity / sv_bullet_speed_modifier.GetFloat(); break; } } return true; } //================================================== // Purpose: Simulate when a surface is hit //================================================== bool CSimulatedBullet::EndSolid(trace_t &ptr) { m_vecEntryPosition = ptr.endpos; #ifndef CLIENT_DLL int soundEntChannel = ( m_nFlags&FIRE_BULLETS_TEMPORARY_DANGER_SOUND ) ? SOUNDENT_CHANNEL_BULLET_IMPACT : SOUNDENT_CHANNEL_UNSPECIFIED; CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_vecEntryPosition, 200, 0.5, m_hCaller, soundEntChannel ); #endif if(FStrEq(ptr.surface.name,"tools/toolsblockbullets")) { return false; } #ifdef CLIENT_DLL int iDamageType = GetAmmoDef()->DamageType( m_iAmmoType ); FX_AffectRagdolls(ptr.startpos, ptr.endpos, iDamageType); UTIL_ImpactTrace( &ptr, iDamageType ); #endif //CLIENT_DLL m_flEntryDensity = physprops->GetSurfaceData(ptr.surface.surfaceProps)->physics.density; if(ptr.DidHitNonWorldEntity()) { if(ptr.m_pEnt!=m_hLastHit) { if(m_pIgnoreList->ShouldHitEntity(ptr.m_pEnt,MASK_SHOT)) { DevMsg("Hit: %s\n",ptr.m_pEnt->GetClassname()); #ifndef CLIENT_DLL EntityImpact(ptr); #endif } } else m_pIgnoreList->AddEntityToIgnore(ptr.m_pEnt); m_hLastHit = ptr.m_pEnt; } if(BulletManager()->BulletStopSpeed()==ONE_HIT_MODE) { return false; } return true; } #ifndef CLIENT_DLL //================================================== // Purpose: Entity impact procedure //================================================== void CSimulatedBullet::EntityImpact(trace_t &ptr) { //TODO: entity impact stuff /*CTakeDamageInfo dmgInfo( this, pAttacker, flActualDamage, nActualDamageType ); CalculateBulletDamageForce( &dmgInfo, info.m_iAmmoType, vecDir, tr.endpos ); dmgInfo.ScaleDamageForce( info.m_flDamageForceScale ); dmgInfo.SetAmmoType( info.m_iAmmoType ); tr.m_pEnt->DispatchTraceAttack( dmgInfo, vecDir, &tr );*/ if ( GetAmmoDef()->Flags(m_iAmmoType) & AMMO_FORCE_DROP_IF_CARRIED ) { // Make sure if the player is holding this, he drops it Pickup_ForcePlayerToDropThisObject( ptr.m_pEnt ); } } //================================================== // Purpose: Simulates all bullets every centisecond //================================================== void CBulletManager::Think(void) #else void CBulletManager::ClientThink(void) #endif { unsigned short iNext=0; for ( unsigned short i=g_Bullets.Head(); i != g_Bullets.InvalidIndex(); i=iNext ) { iNext = g_Bullets.Next( i ); if ( !g_Bullets[i]->SimulateBullet() ) RemoveBullet( i ); } if(g_Bullets.Head()!=g_Bullets.InvalidIndex()) { #ifdef CLIENT_DLL SetNextClientThink( gpGlobals->curtime + 0.01f ); #else SetNextThink( gpGlobals->curtime + 0.01f ); #endif } } //================================================== // Purpose: Called by sv_bullet_stop_speed callback to keep track of resources //================================================== void CBulletManager::UpdateBulletStopSpeed(void) { m_iBulletStopSpeed = sv_bullet_stop_speed.GetInt(); } //================================================== // Purpose: Add bullet to linked list // Handle lag compensation with "prebullet simulation" // Output: Bullet's index (-1 for error) //================================================== #ifdef CLIENT_DLL int CBulletManager::AddBullet(CSimulatedBullet *pBullet) #else int CBulletManager::AddBullet(CSimulatedBullet *pBullet, float flLatency/*=0.0f*/) #endif { if (pBullet->AmmoIndex() == -1) { DevMsg("ERROR: Undefined ammo type!\n"); return -1; } unsigned short index = g_Bullets.AddToTail(pBullet); #ifdef CLIENT_DLL DevMsg( "Client Bullet Created (%i)\n", index); if(g_Bullets.Count()==1) { SetNextClientThink(gpGlobals->curtime + 0.01f); } #else DevMsg( "Bullet Created (%i) LagCompensation %f\n", index, flLatency ); if(flLatency!=0.0f) g_Bullets[index]->SimulateBullet(flLatency); if(g_Bullets.Count()==1) { SetNextThink(gpGlobals->curtime + 0.01f); } #endif return index; } //================================================== // Purpose: Remove the bullet at index from the linked list // Output: Next index //================================================== unsigned short CBulletManager::RemoveBullet(int index) { unsigned short retVal = g_Bullets.Next(index); #ifdef CLIENT_DLL DevMsg("Client "); #endif DevMsg( "Bullet Removed (%i)\n", index ); g_Bullets.Remove(index); if(g_Bullets.Count()==0) { #ifdef CLIENT_DLL SetNextClientThink( TICK_NEVER_THINK ); #else SetNextThink( TICK_NEVER_THINK ); #endif } return retVal; }
Add | #include "bullet_manager.h" |
After | #include "KeyValues.h" |
Add | g_pBulletManager = (CBulletManager*)CBaseEntity::Create( "bullet_manager", vec3_origin, vec3_angle ); |
After | g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle ); |
Change the Ammo_t
structure to the following:
struct Ammo_t { char *pName; int nDamageType; int eTracerType; float physicsForceImpulse; float bulletSpeed; int nMinSplashSize; int nMaxSplashSize; int nFlags; // Values for player/NPC damage and carrying capability // If the integers are set, they override the CVars int pPlrDmg; // CVar for player damage amount int pNPCDmg; // CVar for NPC damage amount int pMaxCarry; // CVar for maximum number can carry const ConVar* pPlrDmgCVar; // CVar for player damage amount const ConVar* pNPCDmgCVar; // CVar for NPC damage amount const ConVar* pMaxCarryCVar; // CVar for maximum number can carry };
In the CAmmoDef
class, change the first two AddAmmoType
function prototypes to the following:
void AddAmmoType(char const* name, int damageType, int tracerType, int plr_dmg, int npc_dmg, int carry, float bulletSpeed, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 ); void AddAmmoType(char const* name, int damageType, int tracerType, char const* plr_cvar, char const* npc_var, char const* carry_cvar, float bulletSpeed, float physicsForceImpulse, int nFlags, int minSplashSize = 4, int maxSplashSize = 8 );
Add | float bulletSpeed, |
Before both | float physicsForceImpulse, |
Add | m_AmmoType[m_nAmmoIndex].bulletSpeed = bulletSpeed; |
Before both | m_AmmoType[m_nAmmoIndex].physicsForceImpulse = physicsForceImpulse; |
Add | #define BULLET_SPEED(ftpersec) 0.12*ftpersec//inches per centisecond |
Before | CAmmoDef *GetAmmoDef() |
Change GetAmmoDef
to the following:
CAmmoDef *GetAmmoDef() { static CAmmoDef def; static bool bInitted = false; if ( !bInitted ) { bInitted = true; // Name Damage Tracer PlrDmg NPCDmg MaxCarry Bulletspeed Physics Force Impulse Flags def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_SPEED(1225), BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("AR2AltFire", DMG_DISSOLVE, TRACER_NONE, 0, 0, 3, 0, 0, 0 ); def.AddAmmoType("Pistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 150, BULLET_SPEED(1225), BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 225, BULLET_SPEED(1225), BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 12, BULLET_SPEED(5000), BULLET_IMPULSE(800, 5000), 0 ); def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, 0, 0, 10, BULLET_SPEED(8000), BULLET_IMPULSE(800, 8000), 0 ); def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, 0, 0, 30, BULLET_SPEED(1200), BULLET_IMPULSE(400, 1200), 0 ); def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0, 0 ); def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0, 0 ); def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0, 0 ); def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0, 0 ); } return &def; }
As you can see, the bullet speed is derived from the ftpersec of the bullet impulses.
Add | #include "bullet_manager.h" |
After | #include "coordsize.h" |
Change CBaseEntity::FireBullets
to the following:
void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) { // Make sure given a valid bullet type if (info.m_iAmmoType == -1) { DevMsg("ERROR: Undefined ammo type!\n"); return; } static int tracerCount; //trace_t tr; CAmmoDef* pAmmoDef = GetAmmoDef(); int nDamageType = pAmmoDef->DamageType(info.m_iAmmoType); int nAmmoFlags = pAmmoDef->Flags(info.m_iAmmoType); int iPlayerDamage = info.m_iPlayerDamage; if ( iPlayerDamage == 0 ) { if ( nAmmoFlags & AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER ) { iPlayerDamage = pAmmoDef->PlrDamage( info.m_iAmmoType ); } } // the default attacker is ourselves CBaseEntity *pAttacker = info.m_pAttacker ? info.m_pAttacker : this; // Make sure we don't have a dangling damage target from a recursive call if ( g_MultiDamage.GetTarget() != NULL ) { ApplyMultiDamage(); } ClearMultiDamage(); g_MultiDamage.SetDamageType( nDamageType | DMG_NEVERGIB ); // Prediction is only usable on players int iSeed = 0; if ( IsPlayer() ) { iSeed = CBaseEntity::GetPredictionRandomSeed() & 255; } //----------------------------------------------------- // Set up our shot manipulator. //----------------------------------------------------- CShotManipulator Manipulator( info.m_vecDirShooting ); #ifdef GAME_DLL // bool bDoImpacts = false; // bool bDoTracers = false; #endif for (int iShot = 0; iShot < info.m_iShots; iShot++) { // bool bHitWater = false; // bool bHitGlass = false; Vector vecDir; // Prediction is only usable on players if ( IsPlayer() ) { RandomSeed( iSeed ); // init random system with this seed } // If we're firing multiple shots, and the first shot has to be bang on target, ignore spread if ( iShot == 0 && info.m_iShots > 1 && (info.m_nFlags & FIRE_BULLETS_FIRST_SHOT_ACCURATE) ) { vecDir = Manipulator.GetShotDirection(); } else { // Don't run the biasing code for the player at the moment. vecDir = Manipulator.ApplySpread( info.m_vecSpread ); } Vector vecSrc(info.m_vecSrc); bool bTraceHull = ( IsPlayer() && info.m_iShots > 1 && iShot % 2 ); CSimulatedBullet *pBullet = new CSimulatedBullet(pAttacker, info.m_pAdditionalIgnoreEnt, info.m_iAmmoType, info.m_nFlags, vecDir, vecSrc, nDamageType, bTraceHull #ifndef CLIENT_DLL , this, info.m_iDamage #endif ); #ifdef CLIENT_DLL BulletManager()->AddBullet(pBullet); #else BulletManager()->AddBullet(pBullet, info.m_flLatency); #endif iSeed++; } #ifdef GAME_DLL ApplyMultiDamage(); #endif }
Change the FireBulletsInfo_t structure to the following:
struct FireBulletsInfo_t { FireBulletsInfo_t() { m_iShots = 1; m_vecSpread.Init( 0, 0, 0 ); m_flDistance = 8192; m_iTracerFreq = 4; m_iDamage = 0; m_iPlayerDamage = 0; m_pAttacker = NULL; m_nFlags = 0; m_pAdditionalIgnoreEnt = NULL; m_flDamageForceScale = 1.0f; #ifdef _DEBUG m_iAmmoType = -1; m_vecSrc.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN ); m_vecDirShooting.Init( VEC_T_NAN, VEC_T_NAN, VEC_T_NAN ); #endif m_flLatency = 0.0f; } FireBulletsInfo_t( int nShots, const Vector &vecSrc, const Vector &vecDir, const Vector &vecSpread, float flDistance, int nAmmoType ) { m_iShots = nShots; m_vecSrc = vecSrc; m_vecDirShooting = vecDir; m_vecSpread = vecSpread; m_flDistance = flDistance; m_iAmmoType = nAmmoType; m_iTracerFreq = 4; m_iDamage = 0; m_iPlayerDamage = 0; m_pAttacker = NULL; m_nFlags = 0; m_pAdditionalIgnoreEnt = NULL; m_flDamageForceScale = 1.0f; m_flLatency = 0.0f; } int m_iShots; Vector m_vecSrc; Vector m_vecDirShooting; Vector m_vecSpread; float m_flDistance; int m_iAmmoType; int m_iTracerFreq; int m_iDamage; int m_iPlayerDamage; // Damage to be used instead of m_iDamage if we hit a player int m_nFlags; // See FireBulletsFlags_t float m_flDamageForceScale; float m_flLatency; CBaseEntity *m_pAttacker; CBaseEntity *m_pAdditionalIgnoreEnt; };
dlls/player_lagcompensation.cpp
Add the following function to this file:
int GetTargetTick(CBasePlayer *player,CUserCmd *cmd) { static CBasePlayer *lastPlayer; static int lastTarget; static int timeRetrieved; if(player==lastPlayer&&timeRetrieved==gpGlobals->tickcount) return lastTarget; // Get true latency lastTarget = 0; // correct is the amout of time we have to correct game time float correct = 0.0f; INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() ); if ( nci ) { // add network latency correct+= nci->GetLatency( FLOW_OUTGOING ); } // calc number of view interpolation ticks - 1 int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime ); // add view interpolation latency see C_BaseEntity::GetInterpolationAmount() correct += TICKS_TO_TIME( lerpTicks ); // check bouns [0,sv_maxunlag] correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() ); // correct tick send by player int targettick = cmd->tick_count - lerpTicks; // calc difference between tick send by player and our latency based tick float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick); if ( fabs( deltaTime ) > 0.2f ) { // difference between cmd time and latency is too big > 200ms, use time correction based on latency // DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime ); targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct ); } lastPlayer = player; lastTarget = targettick; timeRetrieved = gpGlobals->tickcount; return targettick; }
In CLagCompensationManager::StartLagCompensation
Replace
// Get true latency // correct is the amout of time we have to correct game time float correct = 0.0f; INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() ); if ( nci ) { // add network latency correct+= nci->GetLatency( FLOW_OUTGOING ); } // calc number of view interpolation ticks - 1 int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime ); // add view interpolation latency see C_BaseEntity::GetInterpolationAmount() correct += TICKS_TO_TIME( lerpTicks ); // check bouns [0,sv_maxunlag] correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() ); // correct tick send by player int targettick = cmd->tick_count - lerpTicks; // calc difference between tick send by player and our latency based tick float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick); if ( fabs( deltaTime ) > 0.2f ) { // difference between cmd time and latency is too big > 200ms, use time correction based on latency // DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime ); targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct ); }
With
int targettick = GetTargetTick(player, cmd);
dlls/ilagcompensationmanager.h
Add | int GetTargetTick(CBasePlayer *player,CUserCmd *cmd); |
Before | extern ILagCompensationManager *lagcompensation; |
dlls/hl2mp_dll/hl2mp_player.cpp
Add | #include "ilagcompensationmanager.h" |
After | #include "SoundEmitterSystem/isoundemittersystembase.h" |
The bullets will know when they're being fired so in CHL2MP_Player::WantsLagCompensationOnEntity
Comment out | if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) ) return false; |
In CHL2MP_Player::FireBullets
Add | if(gpGlobals->maxClients!=1) modinfo.m_flLatency = TICKS_TO_TIME(GetTargetTick(this,m_pCurrentCommand)); |
Before | BaseClass::FireBullets( modinfo ); |
Add | #include "bullet_manager.h" |
After | #include "c_team.h" |
Put the following function into this file after the function block for __MsgFunc_VGUIMenu
:
static void __MsgFunc_Bullet( bf_read &msg ) { Assert(BulletManager()); if(BulletManager()) { CBaseEntity *pAttacker = cl_entitylist->GetBaseEntity(msg.ReadShort()); int additionalIgnoreIndex = msg.ReadShort(); CBaseEntity *pAdditionalIgnoreEnt = additionalIgnoreIndex!=-1?cl_entitylist->GetBaseEntity(additionalIgnoreIndex):NULL; int ammoType = msg.ReadByte(); int nFlags = msg.ReadShort(); Vector vecDirShooting, vecOrigin; msg.ReadBitVec3Normal(vecDirShooting); msg.ReadBitVec3Coord(vecOrigin); int damageType = msg.ReadShort(); bool bTraceHull = msg.ReadOneBit(); C_SimulatedBullet *pBullet = new C_SimulatedBullet(pAttacker, pAdditionalIgnoreEnt, ammoType, nFlags, vecDirShooting, vecOrigin, damageType, bTraceHull); BulletManager()->AddBullet(pBullet); } }
Add | HOOK_MESSAGE( Bullet ); |
After | HOOK_MESSAGE( VGUIMenu ); |
Add | if(g_pBulletManager) { ClientEntityList().RemoveEntity(g_pBulletManager->GetRefEHandle()); delete g_pBulletManager; } g_pBulletManager = (C_BulletManager *)CreateEntityByName("bullet_manager"); ClientEntityList().AddNonNetworkableEntity(g_pBulletManager); |
After | enginesound->SetPlayerDSP( filter, 0, true ); |