ValveSoftware / halflife

Half-Life 1 engine based games
Other
3.71k stars 625 forks source link

Weapons don't shoot at the same rate for different FPS values #802

Closed LMS007 closed 11 years ago

LMS007 commented 11 years ago

Depends on the FPS, but if the weapon delay lines up poorly with the next frame then there is an time creep on the shot. This creep can be as much as 30 milliseconds which will make the MP5 (for example) shoot a full ~33% slower.

This has been a problem since the first version and continues in the new patch.... I suspect counterstrike & TFC have this same issue, but I have not tested it.

alfred-valve commented 11 years ago

related to #81

AnAkkk commented 11 years ago

The problem is that most people use 100 FPS, so if this is changed the shooting rates must be recalculated for the current 100 FPS ones since they are by default probably calculated for 72 FPS.

LMS007 commented 11 years ago

It still does not force the same time for all fps's. The way it needs to be done is it needs to keep tract of the time that the last shot was fired then subtract the delay time (0.1 for the mp5) and find out the "creep" time.

For example: at 41fps each frame is 0.024 seconds long...

so at time 0 we fire

FRAME | TIME | SHOOT

  1. 0.000: FIRE
  2. 0.024 (wait)
  3. 0.048 (wait)
  4. 0.072 (wait)
  5. 0.096 (wait)
  6. 0.120 (Fire) (next shot = 0.120+0.1 = 0.22)
  7. 0.144 (wait)
  8. 0.168 (wait)
  9. 0.192 (wait)
  10. 0.216 (wait)
  11. 0.240 (Fire) (next shot = 0.240 + 0.1 = 0.340) ...

You can see the trend. The creep time is 0.02 for each shot. I was able to correct for creep time and subtract that from the next delay and I get the mp5 to fire its entire clip in 4.90 seconds for any FPS value which is the correct time. Its a lot of work to convert each of the weapons, but I'm going to move my code into weapons.cpp so the call should be minimal.

If the engine could detect what the creep time was for each client and then automatically subtract that from the next requested delay then we don't need to modify tons of files... But that may be tricky to do for all cases.

alfred-valve commented 11 years ago

accruing the slop seems reasonable @LMS007 , if you post some code I can look at applying it.

LMS007 commented 11 years ago

Sorry, just edited my example, i did had the reciprocal of the fps :) fixed it now. Alfred, when i get home I'll post the code tonight, btw, off topic, but how do i opt in for the beta? sorry for being such a nub :)

LMS007 commented 11 years ago

~I don't know how to turn off the mark down... so pretend this unified patch file.~

weapons.cpp


+++ 
@@ -992,12 +992,13 @@
    m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel);
    strcpy( m_pPlayer->m_szAnimExtention, szAnimExt );
    SendWeaponAnim( iAnim, skiplocal, body );

m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0;

-   m_flLastFireTime = 0.0;

  return TRUE;
  }

  BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body )
  @@ -1054,12 +1055,41 @@
  void CBasePlayerWeapon::Holster( int skiplocal /\* = 0 */ )
  { 
    m_fInReload = FALSE; // cancel any reload in progress.
    m_pPlayer->pev->viewmodel = 0; 
    m_pPlayer->pev->weaponmodel = 0;
  }
  +
  +//=========================================================================
  +// GetNextAttackDelay - An accurate way of calcualting the next attack time.
  +//=========================================================================
  +float CBasePlayerWeapon::GetNextAttackDelay( float delay ) {
-   
-   if(m_flLastFireTime == 0 || m_flNextPrimaryAttack == -1) 
-   {
-       // At this point, we are assuming that the client has stopped firing
-       // and we are going to reset our book keeping variables.
-       m_flLastFireTime = gpGlobals->time;
-       m_flPrevPrimaryAttack = delay;
-   }
-   // calculate the time between this shot and the previous
-   float flTimeBetweenFires = gpGlobals->time - m_flLastFireTime;
-   float flCreep = 0.0f;
-   if(flTimeBetweenFires > 0)
-       flCreep = flTimeBetweenFires - m_flPrevPrimaryAttack; // postive or negative
  +
-   // save the last fire time
-   m_flLastFireTime = gpGlobals->time;  
  +
-   float flNextAttack = UTIL_WeaponTimeBase() + delay - flCreep;
-   // we need to remember what the m_flNextPrimaryAttack time is set to for each shot, 
-   // store it as m_flPrevPrimaryAttack.
-   m_flPrevPrimaryAttack = flNextAttack;
-   return flNextAttack;
  +}
  +

  void CBasePlayerAmmo::Spawn( void )
  {
    pev->movetype = MOVETYPE_TOSS;
    pev->solid = SOLID_TRIGGER;
    UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16));

weapons.h


+++ 
@@ -328,21 +328,27 @@
    virtual void WeaponIdle( void ) { return; }                 // called when no buttons pressed
    virtual int UpdateClientData( CBasePlayer *pPlayer );       // sends hud info to client dll, if things have changed
    virtual void RetireWeapon( void );
    virtual BOOL ShouldWeaponIdle( void ) {return FALSE; };
    virtual void Holster( int skiplocal = 0 );
    virtual BOOL UseDecrement( void ) { return FALSE; };
-   virtual float GetNextAttackDelay( float );                  // use this to get an accurate attack delay that accounts for time creep

  int PrimaryAmmoIndex(); 
  int SecondaryAmmoIndex(); 

  void PrintState( void );

  virtual CBasePlayerItem *GetWeaponPtr( void ) { return (CBasePlayerItem *)this; };

  float m_flPumpTime;
  +
-   // hle time creep vars
-   float   m_flPrevPrimaryAttack;
-   float   m_flLastFireTime;  
  +
  int     m_fInSpecialReload;                                 // Are we in the middle of a reload for the shotguns
  float   m_flNextPrimaryAttack;                              // soonest time ItemPostFrame will call PrimaryAttack
  float   m_flNextSecondaryAttack;                            // soonest time ItemPostFrame will call SecondaryAttack
  float   m_flTimeWeaponIdle;                                 // soonest time ItemPostFrame will call WeaponIdle
  int     m_iPrimaryAmmoType;                                 // "primary" ammo index into players m_rgAmmo[]
  int     m_iSecondaryAmmoType;                               // "secondary" ammo index into players m_rgAmmo[]

mp5.cpp


+++ 
@@ -182,14 +182,15 @@
    PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usMP5, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 );

if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) // HEV suit - indicate out of ammo condition m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0);


## \-   m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.1;
-   // m_flNextPrimaryAttack is adjusted for time creep
-   m_flNextPrimaryAttack = GetNextAttackDelay(0.1);
- if ( m_flNextPrimaryAttack < UTIL_WeaponTimeBase() )
      m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.1;

  m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );
  }

And you need to hook up a dummy method on the client so it links. The return value doesn't really matter

LMS007 commented 11 years ago

I'll try just posting the main part of the code

//=========================================================================
// GetNextAttackDelay - An accurate way of calcualting the next attack time.
//=========================================================================
float CBasePlayerWeapon::GetNextAttackDelay( float delay ) {

if(m_flLastFireTime == 0 || m_flNextPrimaryAttack == -1) 
{
    // At this point, we are assuming that the client has stopped firing
    // and we are going to reset our book keeping variables.
    m_flLastFireTime = gpGlobals->time;
    m_flPrevPrimaryAttack = delay;
}
// calculate the time between this shot and the previous
float flTimeBetweenFires = gpGlobals->time - m_flLastFireTime;
float flCreep = 0.0f;
if(flTimeBetweenFires > 0)
    flCreep = flTimeBetweenFires - m_flPrevPrimaryAttack; // postive or negative

// save the last fire time
m_flLastFireTime = gpGlobals->time;     

float flNextAttack = UTIL_WeaponTimeBase() + delay - flCreep;
// we need to remember what the m_flNextPrimaryAttack time is set to for each shot, 
// store it as m_flPrevPrimaryAttack.
m_flPrevPrimaryAttack = flNextAttack;
return flNextAttack;

}

Then add

m_flLastFireTime = 0.0;

inside of DefaultDeploy() or somewhere where it can be set to 0 initially.

Mario6347 commented 11 years ago

try to paste your code into <_pre_></pre> without * ;)

johndrinkwater commented 11 years ago

jfyi, https://gist.github.com/ is really handy for chunks of code :)

MaximilianKohler commented 11 years ago

@LMS007 to opt into the beta you need to have both hl1 and cs 1.6. You have to also have both of them fully installed and updated. Then you right click on them in the steam library, click properties, then the beta tab.

alfred-valve commented 10 years ago

I've reverted this change for CS 1.6, this doesn't behave well for it (it causes stuttering during firing).

AnAkkk commented 10 years ago

Could this bug still be fixed with a fix for the stuttering?

alfred-valve commented 10 years ago

If that can be worked out, for now I haven't found the root cause.

WPMGPRoSToTeMa commented 10 years ago

Half-Life also has bug with stuttering.

HARDSTYLEBG commented 10 years ago

In Half-Life 1 try to shoot with 100 fps and then with 250 fps (9mmAR/MP5)