RPM Swep Bases help

Hey, so I have this problem shown in this thread: http://forum.facepunch.com/showthread.php?t=1432113&p=46279993#post46279993
(it got closed because I accidentally put it in the wrong category)
But
SWEP.Primary.Delay doesn’t work





// Variables that are used on both client and server

SWEP.Author			= "overki11"
SWEP.Contact		= ""
SWEP.Purpose		= ""
SWEP.Instructions	= ""

SWEP.ViewModelFOV	= 70
SWEP.ViewModelFlip	= false
SWEP.ViewModel		= "models/weapons/v_60watt.mdl"
SWEP.WorldModel		= "models/weapons/w_60watt.mdl"
SWEP.AnimPrefix		= "ar2"
SWEP.HoldType		= "ar2"
SWEP.SwayScale		= 2.0
SWEP.BobScale		= 1.5	

SWEP.EnableIdle			= false

// Note: This is how it should have worked. The base weapon would set the category
// then all of the children would have inherited that.
// But a lot of SWEPS have based themselves on this base (probably not on purpose)
// So the category name is now defined in all of the child SWEPS.
SWEP.Category			= "NDG Weapons"
SWEP.m_bFiresUnderwater	= false;
SWEP.m_fFireDuration	= 0.0;
SWEP.m_nShotsFired		= 0;

SWEP.Spawnable			= true
SWEP.AdminSpawnable		= false

SWEP.Primary.Reload			= Sound( "Weapon_AR2.Reload" )
SWEP.Primary.Empty			= Sound( "Weapon_IRifle.Empty" )
SWEP.Primary.Sound			= Sound( "ar2.Fire" )
SWEP.Primary.Damage			= 16
SWEP.Primary.NumShots		= 1
SWEP.Primary.Delay = 500
SWEP.Primary.NumAmmo		= SWEP.Primary.NumShots
SWEP.Primary.Cone			= VECTOR_CONE_3DEGREES
SWEP.Primary.ClipSize		= 75				// Size of a clip
SWEP.Primary.Delay			= 0.1
SWEP.Primary.DefaultClip	= 150				// Default number of bullets in a clip
SWEP.Primary.Automatic		= true				// Automatic/Semi Auto
SWEP.Primary.Ammo			= "AR2"
SWEP.Primary.Tracer			= 2
SWEP.Primary.TracerName		= "AR2Tracer"

SWEP.Secondary.Empty		= "SWEP.Primary.Empty"
SWEP.Secondary.Sound		= Sound( "Weapon_IRifle.Single" )
SWEP.Secondary.Special1		= Sound( "Weapon_CombineGuard.Special1" )
SWEP.Secondary.ClipSize		= -1				// Size of a clip
SWEP.Secondary.Delay		= 0.5
SWEP.Secondary.DefaultClip	= -1				// Default number of bullets in a clip
SWEP.Secondary.Automatic	= true				// Automatic/Semi Auto
SWEP.Secondary.Ammo			= "AR2AltFire"

SWEP.BobScale = 0
SWEP.SwayScale = 0

BobTime = 0
BobTimeLast = CurTime()

SwayAng = nil
SwayOldAng = Angle()
SwayDelta = Angle()

/*---------------------------------------------------------
   Name: SWEP:Initialize( )
   Desc: Called when the weapon is first loaded
---------------------------------------------------------*/
function SWEP:Initialize()

	if ( SERVER ) then
		self:SetNPCMinBurst( 2 )
		self:SetNPCMaxBurst( 5 )
		self:SetNPCFireRate( self.Primary.Delay )
	end

	self:SetWeaponHoldType( self.HoldType )

end


/*---------------------------------------------------------
   Name: SWEP:PrimaryAttack( )
   Desc: +attack1 has been pressed
---------------------------------------------------------*/
function SWEP:PrimaryAttack()

	// Only the player fires this way so we can cast
	local pPlayer = self.Owner;
	if (!pPlayer) then
		return;
	end

	// Make sure we can shoot first
	if ( !self:CanPrimaryAttack() ) then return end

	if ( self.Weapon:Clip1() <= 0 && self.Primary.ClipSize > -1 ) then
		if ( self:Ammo1() > 0 ) then
			self.Weapon:EmitSound( self.Primary.Empty );
			self:Reload();
		else
			self.Weapon:EmitSound( self.Primary.Empty );
			self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay );
			self.Weapon:SendWeaponAnim( ACT_VM_DRYFIRE)

		end

		return;
	end

	if ( self.m_bIsUnderwater && !self.m_bFiresUnderwater ) then
		self.Weapon:EmitSound( self.Primary.Empty );
		self.Weapon:SetNextPrimaryFire( CurTime() + 0.2 );

		return;
	end

	// Abort here to handle burst and auto fire modes
	if ( (self.Primary.ClipSize > -1 && self.Weapon:Clip1() == 0) || ( self.Primary.ClipSize <= -1 && !pPlayer:GetAmmoCount(self.Primary.Ammo) ) ) then
		return;
	end

	pPlayer:MuzzleFlash();

	// To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems,
	// especially if the weapon we're firing has a really fast rate of fire.
	local iBulletsToFire = 0;
	local fireRate = self.Primary.Delay;

	// MUST call sound before removing a round from the clip of a CHLMachineGun
	self.Weapon:EmitSound(self.Primary.Sound);
	self.Weapon:SetNextPrimaryFire( CurTime() + fireRate );
	iBulletsToFire = iBulletsToFire + self.Primary.NumShots;

	// Make sure we don't fire more than the amount in the clip, if this weapon uses clips
	if ( self.Primary.ClipSize > -1 ) then
		if ( iBulletsToFire > self.Weapon:Clip1() ) then
			iBulletsToFire = self.Weapon:Clip1();
		end
		self:TakePrimaryAmmo( self.Primary.NumAmmo );
	end

	self:ShootBullet( self.Primary.Damage, iBulletsToFire, self.Primary.Cone );

	//Factor in the view kick
	if ( !pPlayer:IsNPC() ) then
		self:AddViewKick();
	end

	if ( self.m_nShotsFired < 2 ) then
	self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK )
    elseif ( self.m_nShotsFired < 3 ) then
	self.Weapon:SendWeaponAnim( ACT_VM_RECOIL1 )
	elseif ( self.m_nShotsFired < 4 ) then
    self.Weapon:SendWeaponAnim( ACT_VM_RECOIL2 )
	else
    self.Weapon:SendWeaponAnim( ACT_VM_RECOIL3 )
	end

	pPlayer:SetAnimation( PLAYER_ATTACK1 );

	self.m_nShotsFired = self.m_nShotsFired + 1
	
	self:IdleStuff()

end


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
function SWEP:DoMachineGunKick( pPlayer, dampEasy, maxVerticleKickAngle, fireDurationTime, slideLimitTime )

	local	KICK_MIN_X			= 0.1	//Degrees
	local	KICK_MIN_Y			= 0.1	//Degrees
	local	KICK_MIN_Z			= 0.01	//Degrees

	local vecScratch = Angle( 0, 0, 0 );

	//Find how far into our accuracy degradation we are
	local duration;
	if ( fireDurationTime > slideLimitTime ) then
		duration	= slideLimitTime
	else
		duration	= fireDurationTime;
	end
	local kickPerc = duration / slideLimitTime;

	// do this to get a hard discontinuity, clear out anything under 10 degrees punch
	pPlayer:ViewPunchReset( 10 );

	//Apply this to the view angles as well
	vecScratch.pitch = -( KICK_MIN_X + ( maxVerticleKickAngle * kickPerc ) );
	vecScratch.yaw = -( KICK_MIN_Y + ( maxVerticleKickAngle * kickPerc ) ) / 3;
	vecScratch.roll = KICK_MIN_Z + ( maxVerticleKickAngle * kickPerc ) / 8;

	//Wibble left and right
	if ( math.random( -1, 1 ) >= 0 ) then
		vecScratch.yaw = vecScratch.yaw * -1;
	end

	//Wobble up and down
	if ( math.random( -1, 1 ) >= 0 ) then
		vecScratch.roll = vecScratch.roll * -1;
	end

	//Clip this to our desired min/max
	// vecScratch = UTIL_ClipPunchAngleOffset( vecScratch, vec3_angle, Angle( 24.0, 3.0, 1.0 ) );

	//Add it to the view punch
	// NOTE: 0.5 is just tuned to match the old effect before the punch became simulated
	pPlayer:ViewPunch( vecScratch * 0.5 );

end

//-----------------------------------------------------------------------------
// Purpose:
// Input  : &tr -
//			nDamageType -
//-----------------------------------------------------------------------------
function SWEP:DoImpactEffect( tr )

	local data = EffectData();

	data:SetOrigin( tr.HitPos + ( tr.HitNormal * 1.0 ) );
	data:SetNormal( tr.HitNormal );

	util.Effect( "AR2Impact", data );

end

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
function SWEP:DelayedAttack()

--- COPIRIGHT ME

	self.m_bShotDelayed = false

	if ( !IsValid( self.Owner ) ) then return end

	// Deplete the clip completely
	self:SendWeaponAnim( ACT_VM_SECONDARYATTACK )
	//m_flNextSecondaryAttack = self.Owner->m_flNextAttack = CurTime() + SequenceDuration();

	// Register a muzzleflash for the AI
	self.Owner:MuzzleFlash();

	self:EmitSound( "weapons/ar2/ar2_altfire.wav" );
	//WeaponSound( WPN_DOUBLE );

	local MAX_TRACE_LENGTH = 16368

	// Fire the bullets
	local vecSrc	 = self.Owner:GetShootPos()//Weapon_ShootPosition( );
	local vecAiming = self.Owner:GetAimVector()//GetAutoaimVector( AUTOAIM_2DEGREES );
	local impactPoint = vecSrc + ( vecAiming * MAX_TRACE_LENGTH );

	// Fire the bullets
	local vecVelocity = vecAiming * 1000

	if ( SERVER ) then
		local pBall = ents.Create( "prop_combine_ball" )
		pBall:EmitSound( "NPC_CombineBall.Launch" )

		//pBall:StartWhizSoundThink()
		//pBall:StartLifetime( 4 )

		pBall:PhysicsInit( SOLID_VPHYSICS )
		pBall:SetSolid( SOLID_VPHYSICS)
		pBall:SetMoveType( MOVETYPE_VPHYSICS )
		pBall:SetCollisionGroup( 24 )
		pBall:SetPos( self.Owner:GetShootPos() )
		pBall:SetOwner( self.Owner )
		pBall:SetAbsVelocity( vecVelocity ) // pBall:SetVelocity( vecVelocity )
		pBall:SetSaveValue( "m_flRadius", 10 ) // pBall:SetRadius( 10 )
		pBall:SetSaveValue( "m_vecAbsVelocity", vecVelocity )
		pBall:Spawn()

		pBall:SetSaveValue( "m_bWeaponLaunched", true ) //pBall:SetWeaponLaunched( true )
		pBall:SetSaveValue( "m_flSpeed", vecVelocity:Length() ) // pBall:SetSpeed( vecVelocity.Length() )
		pBall:SetSaveValue( "m_bLaunched", true )
		pBall:SetSaveValue( "m_nState", 2 ) // pBall:SetState( "CPropCombineBall::STATE_THROWN" )

		local phys = pBall:GetPhysicsObject()
		if IsValid( phys ) then
			phys:AddGameFlag( FVPHYSICS_WAS_THROWN ) // PhysSetGameFlags( pBall->VPhysicsGetObject(), FVPHYSICS_WAS_THROWN )
			phys:SetMass( 150 ) // pBall:GetPhysicsObject():SetMass( 150 )
			phys:SetInertia( Vector( 500, 500, 500 ) )
		end

		// Uuuuuugly workaround
		timer.Simple( 4, function() if IsValid( pBall ) then pBall:Fire("Explode") end end )

		self.Owner:ScreenFade( SCREENFADE.IN, Color( 255, 255, 255, 64 ), 0.1, 0 )

		//Disorient the player
		local angles = self.Owner:EyeAngles()

		angles.p = angles.p + math.random( -4, 4 )
		angles.y = angles.y + math.random( -4, 4 )
		angles.r = 0;

		self.Owner:SetEyeAngles( angles )
	end
	self.Owner:ViewPunch( Angle( /*SharedRandomInt( "ar2pax",*/math.random( -8, -12 ), /*SharedRandomInt( "ar2pay", */math.random(1, 2 ), 0 ) );

	// Decrease ammo
	self.Owner:RemoveAmmo( 1, self.Secondary.Ammo )

	// Can shoot again immediately
	self:SetNextPrimaryFire( CurTime() + 0.5 )

	// Can blow up after a short delay (so have time to release mouse button)
	self:SetNextSecondaryFire( CurTime() + 1 )
end

/*---------------------------------------------------------
   Name: SWEP:SecondaryAttack( )
   Desc: +attack2 has been pressed
---------------------------------------------------------*/
function SWEP:SecondaryAttack()

	// Make sure we can shoot first
	if ( !self:CanSecondaryAttack() ) then return end

	if ( self.m_bShotDelayed ) then
		return;
	end

	// Cannot fire underwater
	if ( ( self.Owner && self.Owner:WaterLevel() == 3 ) || self:Ammo2() <= 0 ) then
		self.Weapon:SendWeaponAnim( ACT_VM_DRYFIRE );
		self.Weapon:EmitSound( self.Secondary.Empty );
		self.Weapon:SetNextSecondaryFire( CurTime() + self.Secondary.Delay );
		return;
	end

	self.m_bShotDelayed = true;
	self.Weapon:SetNextPrimaryFire( CurTime() + self.Secondary.Delay );
	self.Weapon:SetNextSecondaryFire( CurTime() + self.Secondary.Delay );
	self.m_flDelayedFire = CurTime() + self.Secondary.Delay;

	self.Weapon:SendWeaponAnim( ACT_VM_FIDGET );
	self.Weapon:EmitSound( self.Secondary.Special1 );

end

/*---------------------------------------------------------
   Name: SWEP:Reload( )
   Desc: Reload is being pressed
---------------------------------------------------------*/
function SWEP:Reload()

	local fRet;
	local fCacheTime = self.Secondary.Delay;

	self.m_fFireDuration = 0.0;
	self:IdleStuff()

	fRet = self.Weapon:DefaultReload( ACT_VM_RELOAD );
	if ( fRet ) then
		// Undo whatever the reload process has done to our secondary
		// attack timer. We allow you to interrupt reloading to fire
		// a grenade.
		self.Weapon:SetNextSecondaryFire( CurTime() + fCacheTime );

		self.Weapon:EmitSound( self.Primary.Reload );
		
		self.m_nShotsFired = 0
		self:IdleStuff()

	end

	return fRet;

end


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
function SWEP:AddViewKick()

	local	EASY_DAMPEN			= 0.5
	local	MAX_VERTICAL_KICK	= 8.0	//Degrees
	local	SLIDE_LIMIT			= 5.0	//Seconds

	//Get the view kick
	local pPlayer = self.Owner;

	if ( pPlayer == NULL ) then
		return;
	end

	self:DoMachineGunKick( pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, self.m_fFireDuration, SLIDE_LIMIT );

end

/*---------------------------------------------------------
   Name: SWEP:PreThink( )
   Desc: Called before every frame
---------------------------------------------------------*/
function SWEP:PreThink()
end


/*---------------------------------------------------------
   Name: SWEP:Think( )
   Desc: Called every frame
---------------------------------------------------------*/
function SWEP:Think()

	if CLIENT and self.EnableIdle then return end
	if self.idledelay and CurTime() > self.idledelay then
		self.idledelay = nil
		self:SendWeaponAnim(ACT_VM_IDLE)
	end

	local pPlayer = self.Owner;

	if ( !pPlayer ) then
		return;
	end

	self:PreThink();

	if ( pPlayer:WaterLevel() >= 3 ) then
		self.m_bIsUnderwater = true;
	else
		self.m_bIsUnderwater = false;
	end

	if ( pPlayer:KeyDown( IN_ATTACK ) ) then
		self.m_fFireDuration	= self.m_fFireDuration + FrameTime();
	elseif ( !pPlayer:KeyDown( IN_ATTACK ) ) then
		self.m_fFireDuration	= 0.0;
		self.m_nShotsFired		= 0;
	end

	// See if we need to fire off our secondary round
	if ( self.m_bShotDelayed && CurTime() > self.m_flDelayedFire ) then
		self:DelayedAttack();
	end

	self.BaseClass:Think();

end


/*---------------------------------------------------------
   Name: SWEP:Deploy( )
   Desc: Whip it out
---------------------------------------------------------*/
function SWEP:Deploy()

	self.Weapon:SendWeaponAnim( ACT_VM_DRAW )
	self:SetDeploySpeed( self.Weapon:SequenceDuration() )
	self:IdleStuff()

	self.m_fFireDuration	= 0.0;
	self.m_bShotDelayed		= false;
	self.m_flDelayedFire	= 0.0;

	return true

end


/*---------------------------------------------------------
   Name: SWEP:ShootBullet( )
   Desc: A convenience function to shoot bullets
---------------------------------------------------------*/
function SWEP:ShootBullet( damage, num_bullets, aimcone )

	// Only the player fires this way so we can cast
	local pPlayer = self.Owner;

	if ( !pPlayer ) then
		return;
	end

	local pHL2MPPlayer = pPlayer;

		// Fire the bullets
	local info = {};
	info.Num = num_bullets;
	info.Src = pHL2MPPlayer:GetShootPos();
	info.Dir = pPlayer:GetAimVector();
	info.Spread = aimcone;
	info.Damage = damage;
	info.Tracer = self.Primary.Tracer;
	info.TracerName = self.Primary.TracerName;

	info.Owner = self.Owner
	info.Weapon = self.Weapon

	info.DoImpactEffect = self.DoImpactEffect;
	info.ShootCallback = self.ShootCallback;

	info.Callback = function( attacker, trace, dmginfo )
		info:DoImpactEffect( trace );
		return info:ShootCallback( attacker, trace, dmginfo );
	end

	pPlayer:FireBullets( info );

end


/*---------------------------------------------------------
   Name: SWEP:ShootCallback( )
   Desc: A convenience function to shoot bullets
---------------------------------------------------------*/
function SWEP:ShootCallback( attacker, trace, dmginfo )
end


/*---------------------------------------------------------
   Name: SWEP:CanPrimaryAttack( )
   Desc: Helper function for checking for no ammo
---------------------------------------------------------*/
function SWEP:CanPrimaryAttack()
	return true
end


/*---------------------------------------------------------
   Name: SWEP:CanSecondaryAttack( )
   Desc: Helper function for checking for no ammo
---------------------------------------------------------*/
function SWEP:CanSecondaryAttack()
	return true
end


/*---------------------------------------------------------
   Name: SetDeploySpeed
   Desc: Sets the weapon deploy speed.
		 This value needs to match on client and server.
---------------------------------------------------------*/
function SWEP:SetDeploySpeed( speed )

	self.m_WeaponDeploySpeed = tonumber( speed / GetConVarNumber( "phys_timescale" ) )

	self.Weapon:SetNextPrimaryFire( CurTime() + speed )
	self.Weapon:SetNextSecondaryFire( CurTime() + speed )

end

/*---------------------------------------------------------
   Name: IdleStuff
   Desc: Helpers for the Idle function.
---------------------------------------------------------*/
function SWEP:IdleStuff()
	if self.EnableIdle then return end
	self.idledelay = CurTime() +self:SequenceDuration()
end

/*---------------------------------------------------------
   Name: CalcViewModelView
   Desc: Overwrites the default GMod v_model system.
---------------------------------------------------------*/
function SWEP:CalcViewModelView(ViewModel, oldPos, oldAng, pos, ang)

	local pPlayer = self.Owner
	local Speed = pPlayer:GetVelocity():Length2D()
	local CT = CurTime()
	local FT = FrameTime()
	local BobCycleMultiplier = Speed / pPlayer:GetRunSpeed()

	BobCycleMultiplier = (BobCycleMultiplier > 1 and math.min(1 + ((BobCycleMultiplier - 1) * 0.2), 5) or BobCycleMultiplier)
	BobTime = BobTime + (CT - BobTimeLast) * (Speed > 0 and (Speed / pPlayer:GetWalkSpeed()) or 0)
	BobTimeLast = CT
	local BobCycleX = math.sin(BobTime * 0.5 % 1 * math.pi * 2) * BobCycleMultiplier
	local BobCycleY = math.sin(BobTime % 1 * math.pi * 2) * BobCycleMultiplier

	oldPos = oldPos + oldAng:Right() * (BobCycleX * 1.5)
	oldPos = oldPos
	oldPos = oldPos + oldAng:Up() * BobCycleY/2

	SwayAng = oldAng - SwayOldAng
	if math.abs(oldAng.y - SwayOldAng.y) > 180 then
		SwayAng.y = (360 - math.abs(oldAng.y - SwayOldAng.y)) * math.abs(oldAng.y - SwayOldAng.y) / (SwayOldAng.y - oldAng.y)
	else
		SwayAng.y = oldAng.y - SwayOldAng.y
	end
	SwayOldAng.p = oldAng.p
	SwayOldAng.y = oldAng.y
	SwayAng.p = math.Clamp(SwayAng.p, -3, 3)
	SwayAng.y = math.Clamp(SwayAng.y, -3, 3)
	SwayDelta = LerpAngle(math.Clamp(FrameTime() * 5, 0, 1), SwayDelta, SwayAng)
	
	return oldPos + oldAng:Up() * SwayDelta.p + oldAng:Right() * SwayDelta.y + oldAng:Up() * oldAng.p / 90 * 2, oldAng
end


I’ll be releasing my base in the near future; but I noticed in your PrimaryAttack you’re allowing glitches through which shouldn’t happen…

Look into IsFirstTimePredicted( ) … Client will call PrimaryAttack many times while the server may only call it once. IsFirstTimePredicted( ) is true when both the Client and Server call it at the same time ( that’s where you’d do your certain calculations to remain in sync )…

In my SWEPs I define: SWEP.FireRatePerMinute = 600;
SWEP.Primary.MagazineCapacity = 30;

and for it to work properly, on SWEP:Initialize( ) I call self:SetPrimaryFireRate( self.FireRatePerMinute or 60 );

Which is just a basic function which updates the primary delay ( shared so it is in sync ).

self.Primary.Delay = 60 / _rpm;

To ensure the delay works properly, in PrimaryAttack call: self:SetNextPrimaryFire( CurTime( ) + self.Primary.Delay );

In summation, you want to do something like:

SWEP.Primary.Delay = 60/800

If your gun fires at 800 rpm.