This is IBeMad from the Hypovolemia Mod Team (http://hypovolemia-mod.net). As a programmer, I'm currently tasked with fixing some of the glitches with the shotguns when there is lag. For some reason, it appears as if they're not being predicted properly. We're using the Alien Swarm SDK.
Our CPP file from the spas12
- Code: Select all
#include "cbase.h"
#include "hyp_weapon_SPAS12.h"
#include "in_buttons.h"
#ifdef CLIENT_DLL
#include "c_asw_player.h"
#include "c_asw_weapon.h"
#include "c_asw_marine.h"
#include "c_asw_marine_resource.h"
#include "prediction.h"
#define CASW_Marine C_ASW_Marine
#define CASW_Marine_Resource C_ASW_Marine_Resource
#else
#include "asw_marine.h"
#include "asw_player.h"
#include "asw_weapon.h"
#include "npcevent.h"
#include "shot_manipulator.h"
#include "asw_grenade_prifle.h"
#include "asw_fail_advice.h"
#include "effect_dispatch_data.h"
#include "asw_marine_resource.h"
#include "te_effect_dispatch.h"
#endif
#include "asw_marine_skills.h"
#include "asw_weapon_parse.h"
#include "particle_parse.h"
#include "shot_manipulator.h"
#include "asw_weapon_ammo_bag_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
acttable_t CHYP_Weapon_SPAS12::m_acttable[] =
{
{ ACT_MP_STAND_IDLE, ACT_MP_STAND_AIM_MG, false },
{ ACT_MP_STAND_AIM_IDLE, ACT_MP_STAND_AIM_BAR, false },
{ ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_AIM_BAR, false },
{ ACT_MP_SPRINT, ACT_MP_SPRINT_IDLE_BAR, false },
{ ACT_MP_RUN, ACT_MP_RUN_AIM_MG, false },
{ ACT_MP_WALK, ACT_MP_WALK_AIM_BAR, false },
{ ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_AIM_BAR, false },
{ ACT_MP_JUMP, ACT_MP_JUMP, false },
{ ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_PRIMARYATTACK_BAR, false },
{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_PRIMARYATTACK_BAR, false },
{ ACT_MP_RELOAD_STAND, ACT_MP_RELOAD_BAR, false },
{ ACT_MP_RELOAD_CROUCH, ACT_MP_RELOAD_CROUCH_BAR, false },
};
IMPLEMENT_ACTTABLE(CHYP_Weapon_SPAS12);
IMPLEMENT_NETWORKCLASS_ALIASED( HYP_Weapon_SPAS12, DT_HYP_Weapon_SPAS12 )
BEGIN_NETWORK_TABLE( CHYP_Weapon_SPAS12, DT_HYP_Weapon_SPAS12 )
#ifdef CLIENT_DLL
// recvprops
#else
// sendprops
#endif
END_NETWORK_TABLE()
BEGIN_PREDICTION_DATA( CHYP_Weapon_SPAS12 )
END_PREDICTION_DATA()
LINK_ENTITY_TO_CLASS( hyp_weapon_SPAS12, CHYP_Weapon_SPAS12 );
PRECACHE_WEAPON_REGISTER(hyp_weapon_SPAS12);
#ifndef CLIENT_DLL
extern ConVar asw_debug_marine_damage;
extern ConVar asw_DebugAutoAim;
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CHYP_Weapon_SPAS12 )
END_DATADESC()
#endif /* not client */
extern ConVar asw_weapon_max_shooting_distance;
extern ConVar asw_weapon_force_scale;
CHYP_Weapon_SPAS12::CHYP_Weapon_SPAS12()
{
m_iChamberState = HYP_CHAMBER_EMPTY;
}
CHYP_Weapon_SPAS12::~CHYP_Weapon_SPAS12()
{
}
void CHYP_Weapon_SPAS12::Precache()
{
// precache the weapon model here?
PrecacheScriptSound("ASW_Weapon_PRifle.StunGrenadeExplosion");
PrecacheScriptSound("ASW_PRifle.ReloadA");
PrecacheScriptSound("ASW_PRifle.ReloadB");
PrecacheScriptSound("ASW_PRifle.ReloadC");
PrecacheModel( "swarm/effects/electrostunbeam.vmt" );
PrecacheModel( "swarm/effects/bluemuzzle_nocull.vmt" );
PrecacheModel( "effects/bluelaser2.vmt" );
BaseClass::Precache();
}
float CHYP_Weapon_SPAS12::GetWeaponDamage()
{
return ((m_iActionType == HYP_SEMI_AUTO) ? GetWeaponInfo()->m_fDamageSecondaryBase : GetWeaponInfo()->m_flBaseDamage);
}
//------------------------------------
// Determining what buttons are pressed and based on those results do the correct corresponding actions (primaryattack, action, reload)
//------------------------------------
void CHYP_Weapon_SPAS12::ItemPostFrame()
{
// New owner instead of player
CASW_Marine* pOwner = GetMarine();
if (!pOwner)
return;
bool bThisActive = ( pOwner && pOwner->GetActiveWeapon() == this );
bool bAttack1, bAttack2, bReload, bOldReload, bOldAttack1;
GetButtons(bAttack1, bAttack2, bReload, bOldReload, bOldAttack1 );
GetButtonsOverride(bAttack1, bAttack2, bReload, bOldReload, bOldAttack1 );
// check for clearing our weapon switching bool
if (m_bSwitchingWeapons && gpGlobals->curtime > m_flNextPrimaryAttack)
m_bSwitchingWeapons = false;
// check for clearing our firing bool from reloading
if (m_bInReload && gpGlobals->curtime > m_fReloadClearFiringTime)
ClearIsFiring();
// delayed shot on firing weapon?
if ( m_bShotDelayed && gpGlobals->curtime > m_flDelayedFire )
DelayedAttack();
// some reload checking going on?
if ( UsesClipsForAmmo1() )
CheckReload();
bool bFired = false;
if ( bThisActive )
{
//Track the duration of the fire
//FIXME: What if we're calling ItemBusyFrame?
m_fFireDuration = (bAttack1 ? ( m_fFireDuration + gpGlobals->frametime ) : 0.0f);
if (bAttack2 && (m_flNextSecondaryAttack <= gpGlobals->curtime) && gpGlobals->curtime > pOwner->m_fFFGuardTime)
{
if ( SecondaryAttackUsesPrimaryAmmo() )
{
if ( !IsMeleeWeapon() &&
(( UsesClipsForAmmo1() && !(this->PrimaryAmmoLoaded())) || ( !UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType)<=0 )) )
{
HandleFireOnEmpty();
}
else
{
bFired = true;
SecondaryAttack();
}
}
else if ( UsesSecondaryAmmo() &&
( ( UsesClipsForAmmo2() && m_iClip2 <= 0 ) ||
( !UsesClipsForAmmo2() && pOwner->GetAmmoCount( m_iSecondaryAmmoType ) <= 0 ) ) )
{
if ( m_flNextEmptySoundTime < gpGlobals->curtime )
{
if (!IsSecondaryAttackDisabled())
WeaponSound( EMPTY );
m_flNextSecondaryAttack = m_flNextEmptySoundTime = gpGlobals->curtime + 0.5;
}
}
else
{
bFired = true;
SecondaryAttack();
}
}
if (m_iActionType == HYP_SEMI_AUTO)
{
if (m_bAutoEmptyChamber && m_fAutoEmptyChamber <= gpGlobals->curtime)
{
m_bAutoEmptyChamber = false;
m_iChamberState = HYP_CHAMBER_OPEN; // no shell [empty or loaded]
}
else if (m_bAutoRechamber && m_flNextPrimaryAttack <= gpGlobals->curtime && m_iChamberState != HYP_CHAMBER_LOADED)
{
m_bAutoRechamber = false;
if (m_iClip1 > 0)
{
m_iChamberState = HYP_CHAMBER_LOADED;
#ifdef CLIENT_DLL
m_iClip1--;
DevWarning("[CLIENT] Predicting ammo %i.\n", m_iClip1);
#else
m_iClip1--;
DevWarning("[SERVER] Ammo currently %i.\n", m_iClip1);
#endif
}
else
{
m_iChamberState = HYP_CHAMBER_EMPTY;
}
}
}
if ( !bFired && !bOldAttack1 && bAttack1 && (m_flNextPrimaryAttack <= gpGlobals->curtime) && (m_fNextAction <= gpGlobals->curtime) && gpGlobals->curtime > pOwner->m_fFFGuardTime)
{
if ( m_iChamberState != HYP_CHAMBER_LOADED )
{
HandleFireOnEmpty();
}
else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false)
{
// This weapon doesn't fire underwater
WeaponSound(EMPTY);
m_flNextPrimaryAttack = gpGlobals->curtime + 0.2;
return;
}
else if (m_iChamberState == HYP_CHAMBER_LOADED)
{
//NOTENOTE: There is a bug with this code with regards to the way machine guns catch the leading edge trigger
// on the player hitting the attack key. It relies on the gun catching that case in the same frame.
// However, because the player can also be doing a secondary attack, the edge trigger may be missed.
// We really need to hold onto the edge trigger and only clear the condition when the gun has fired its
// first shot. Right now that's too much of an architecture change -- jdw
PrimaryAttack_SPAS();
m_flNextPrimaryAttack = gpGlobals->curtime + GetWeaponInfo()->m_flFireRate;
}
}
}
if (!bAttack1) // clear our firing var if we don't have the attack button held down (not totally accurate since firing could continue for some time after pulling the trigger, but it's good enough for our purposes)
{
m_bIsFiring = false; // NOTE: Only want to clear primary fire IsFiring bool here (i.e. don't call ClearIsFiring as that'll clear secondary fire too, in subclasses that have it)
if ( bOldAttack1 )
{
OnStoppedFiring();
}
}
// -----------------------
// Reload pressed / Clip Empty
// -----------------------
if ( bReload && UsesClipsForAmmo1())
{
if ( m_bInReload )
{
// todo: check for a fast reload
//Msg("Check for fast reload\n");
}
else
{
// reload when reload is pressed, or if no buttons are down and weapon is empty.
Reload();
m_fFireDuration = 0.0f;
}
}
// -----------------------
// Action buttons down
// -----------------------
// the variables
bool bOldAction = false, bCurrentAction = false;
// get the button states
if (pOwner->IsInhabited() && pOwner->GetCommander())
{
bCurrentAction = !!(pOwner->GetCommander()->m_nButtons & IN_ACTION);
bOldAction = !!(pOwner->m_nOldButtons & IN_ACTION);
}
// command the action if button is just pressed
if (((!bOldAction && bCurrentAction) || m_bIsTargetActionLoading) && m_fNextAction < gpGlobals->curtime)
DoActionMechanism();
// -----------------------
// No buttons down
// -----------------------
if (!(bAttack1 || bAttack2 || bReload || bCurrentAction))
{
// no fire buttons down or reloading
if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) )
{
WeaponIdle();
}
}
}
void CHYP_Weapon_SPAS12::PrimaryAttack_SPAS()
{
CASW_Player *pPlayer = GetCommander();
CASW_Marine *pMarine = GetMarine();
if ( !pMarine )
return;
if (m_iChamberState != HYP_CHAMBER_LOADED)
return;
m_bIsFiring = true;
// MUST call sound before removing a round from the clip of a CMachineGun
WeaponSound(SINGLE);
// tell the marine to tell its weapon to draw the muzzle flash
pMarine->DoMuzzleFlash();
// sets the animation on the weapon model itself
SendWeaponAnim( GetPrimaryAttackActivity() );
#ifdef GAME_DLL // check for turning on lag compensation
if (pPlayer && pMarine->IsInhabited())
{
CASW_Lag_Compensation::RequestLagCompensation( pPlayer, pPlayer->GetCurrentUserCommand() );
}
#endif
FireBulletsInfo_t info;
info.m_vecSrc = pMarine->Weapon_ShootPosition( );
if ( pPlayer && pMarine->IsInhabited() )
{
info.m_vecDirShooting = pPlayer->GetAutoaimVectorForMarine(pMarine, GetAutoAimAmount(), GetVerticalAdjustOnlyAutoAimAmount()); // 45 degrees = 0.707106781187
}
else
{
#ifdef CLIENT_DLL
Msg("Error, clientside firing of a weapon that's being controlled by an AI marine\n");
#else
info.m_vecDirShooting = pMarine->GetActualShootTrajectory( info.m_vecSrc );
#endif
}
info.m_flDistance = asw_weapon_max_shooting_distance.GetFloat();
info.m_iAmmoType = m_iPrimaryAmmoType;
info.m_iTracerFreq = 1; // asw tracer test everytime
info.m_flDamageForceScale = asw_weapon_force_scale.GetFloat();
// determine the new spread
info.m_iShots = GetWeaponInfo()->m_iQuantityPerShot;
info.m_vecSpread = pMarine->GetActiveWeapon()->GetBulletSpread();
CShotManipulator Manipulator( info.m_vecDirShooting );
Vector vecDir;
vecDir = Manipulator.ApplySpread( pMarine->GetActiveWeapon()->GetBulletSpread() );
info.m_vecDirShooting = vecDir;
#ifdef GAME_DLL
switch(pPlayer ? pPlayer->m_iDebugMovementState : -1)
#else
switch(pPlayer ? pPlayer->GetDebugMovementState() : -1)
#endif
{
case 0: //Standing
info.m_vecSpread = ConvertSpread(GetWeaponInfo()->m_fFixedSpread_Hipshoot);
break;
case 1: // jumping
info.m_vecSpread = ConvertSpread(GetWeaponInfo()->m_fFixedSpread_Jumping);
break;
case 2: // running Backwards
case 3: // running
info.m_vecSpread = ConvertSpread(GetWeaponInfo()->m_fFixedSpread_Hipshoot);
break;
case 4: // walking
case 5: // croutching
info.m_vecSpread = ConvertSpread(GetWeaponInfo()->m_fFixedSpread_Aiming);
break;
}
info.m_flDamage = GetWeaponDamage();
pMarine->FireBullets( info );
// increment shooting stats
#ifndef CLIENT_DLL
if (pMarine && pMarine->GetMarineResource())
{
pMarine->GetMarineResource()->UsedWeapon(this, info.m_iShots);
pMarine->OnWeaponFired( this, info.m_iShots );
}
#endif
//#ifndef CLIENT_DLL
pPlayer->ApplyRecoilSpread();
//#endif
if (m_iActionType == HYP_SEMI_AUTO)
{
m_fAutoEmptyChamber = gpGlobals->curtime + GetWeaponInfo()->m_flFireRate/2;
m_bAutoEmptyChamber = true;
m_bAutoRechamber = true;
}
m_iChamberState = HYP_CHAMBER_SPENT;
#ifdef CLIENT_DLL
SetViewRecoil();
#endif
}
void CHYP_Weapon_SPAS12::FinishReload( void )
{
CASW_Marine *pOwner = GetMarine();
if (pOwner)
{
// primary ammo reload
m_iClip1++;
pOwner->SetAmmoCount(pOwner->GetAmmoCount(m_iPrimaryAmmoType)-1, m_iPrimaryAmmoType);//;m_iAmmo[m_iPrimaryAmmoType]
if ( m_bReloadsSingly )
{
m_bInReload = false;
}
#ifdef GAME_DLL
if ( !m_bFastReloadSuccess )
{
pOwner->m_nFastReloadsInARow = 0;
}
#endif
m_bFastReloadSuccess = false;
m_bFastReloadFailure = false;
}
}
#ifdef CLIENT_DLL
const char* CHYP_Weapon_SPAS12::GetPartialReloadSound(int iPart)
{
switch (iPart)
{
case 1: return "ASW_PRifle.ReloadB"; break;
case 2: return "ASW_PRifle.ReloadC"; break;
default: break;
};
return "ASW_PRifle.ReloadA";
}
void CHYP_Weapon_SPAS12::ASWReloadSound(int iType)
{
if (iType == 0)
WeaponSound(SPECIAL2);
}
const char* CHYP_Weapon_SPAS12::GetMuzzleEffectName()
{
// get muzzle
float iState = RandomFloat();
if (iState <= MUZZLE_DEFORM_PERCENT)
return MUZZLE_DEFORM_FXNAME; //10% chance of getting here
else if (iState <= MUZZLE_BRIGHT_PERCENT)
return MUZZLE_BRIGHT_FXNAME; //30% chance of getting here
else
return MUZZLE_WEAK_FXNAME;
}
const char* CHYP_Weapon_SPAS12::GetTracerEffectName()
{
return TRACER_FXNAME;
}
#endif
And our h file...
- Code: Select all
#ifndef _INCLUDED_HYP_WEAPON_SPAS12_H
#define _INCLUDED_HYP_WEAPON_SPAS12_H
#pragma once
#ifdef CLIENT_DLL
#define CHYP_Weapon_SPAS12 C_HYP_Weapon_SPAS12
#define CHYP_Weapon_Shotgun C_HYP_Weapon_Shotgun
#define MUZZLE_BRIGHT_FXNAME "muzzle_spas12_bright"
#define MUZZLE_WEAK_FXNAME "muzzle_spas12_weak"
#define MUZZLE_DEFORM_FXNAME "muzzle_spas12_deform"
#define TRACER_FXNAME "tracer_762"
#else
#include "npc_combine.h"
#endif
#define MUZZLE_BRIGHT_PERCENT 0.40f //Is actually 30%, even though it says .4
#define MUZZLE_DEFORM_PERCENT 0.10f
#include "hyp_weapon_shotgun.h"
class CHYP_Weapon_SPAS12 : public CHYP_Weapon_Shotgun
{
public:
DECLARE_CLASS( CHYP_Weapon_SPAS12, CHYP_Weapon_Shotgun );
DECLARE_NETWORKCLASS();
DECLARE_PREDICTABLE();
DECLARE_ACTTABLE();
CHYP_Weapon_SPAS12();
virtual ~CHYP_Weapon_SPAS12();
void Precache();
virtual bool IsSecondaryAttackDisabled() { return false; }
virtual float GetWeaponDamage();
#ifdef GAME_DLL
DECLARE_DATADESC();
virtual const char* GetPickupClass() { return "weapon_SPAS12"; }
#else
virtual bool HasSecondaryExplosive( void ) const { return false; }
virtual const char* GetPartialReloadSound(int iPart);
virtual const char* GetTracerEffectName();
virtual const char* GetMuzzleEffectName();
void ASWReloadSound(int iType);
#endif
virtual void ItemPostFrame();
virtual void PrimaryAttack_SPAS();
virtual void FinishReload();
// Classification
virtual Class_T Classify( void ) { return (Class_T) CLASS_HYP_SPAS12; }
};
#endif /* _INCLUDED_HYP_WEAPON_SPAS12_H */
Of particular interest is around line 200 in the cpp, which looks like this:
- Code: Select all
if (m_iClip1 > 0)
{
m_iChamberState = HYP_CHAMBER_LOADED;
#ifdef CLIENT_DLL
m_iClip1--;
DevWarning("[CLIENT] Predicting ammo %i.\n", m_iClip1);
#else
m_iClip1--;
DevWarning("[SERVER] Ammo currently %i.\n", m_iClip1);
#endif
}
This is what I see in the console after firing with net_fakelag 50:
- Code: Select all
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[CLIENT] Predicting ammo 7.
[SERVER] Ammo currently 7.
[CLIENT] Predicting ammo 6.
[CLIENT] Predicting ammo 5.
[CLIENT] Predicting ammo 4.
[CLIENT] Predicting ammo 3.
[CLIENT] Predicting ammo 3.
[CLIENT] Predicting ammo 2.
[CLIENT] Predicting ammo 1.
[CLIENT] Predicting ammo 1.
[CLIENT] Predicting ammo 0.
All I have to say is, wtf? How can I go about fixing this? The page on prediction in the valve developer wiki is very vague on how this whole prediction system works.
Thanks,
IBeMad






