374 lines
13 KiB
Ucode
374 lines
13 KiB
Ucode
/** A M32 with fancy code copied from Poosh's Colt
|
|
* @author Scuddles
|
|
* @see https://steamcommunity.com/groups/ScrNBalance/discussions/6/1696049513783762669/
|
|
*/
|
|
class ScrnM32GrenadeLauncher extends M32GrenadeLauncher;
|
|
|
|
var float ReloadMulti;
|
|
var float BulletLoadRate; // how long does it takes to load a single bullet
|
|
var transient bool bInterruptedReload;
|
|
var transient float NextBulletLoadTime;
|
|
|
|
// Implemented M32-specific reloading
|
|
// Reload State 1 (interruptable): opening cylinders and loading 40mm shells, each shell takes BulletLoadRate seconds. If all 6 shells are loaded in a cycle, don't skip to closing animation.
|
|
// Reload State 2: if 5 or less shells are loaded, skip to closing cylinder (0.4s).
|
|
|
|
simulated function WeaponTick(float dt)
|
|
{
|
|
local float LastSeenSeconds;
|
|
|
|
if( bHasAimingMode ) {
|
|
if( bForceLeaveIronsights ) {
|
|
if( bAimingRifle ) {
|
|
ZoomOut(true);
|
|
|
|
if( Role < ROLE_Authority)
|
|
ServerZoomOut(false);
|
|
}
|
|
bForceLeaveIronsights = false;
|
|
}
|
|
|
|
if( ForceZoomOutTime > 0 ) {
|
|
if( bAimingRifle ) {
|
|
if( Level.TimeSeconds - ForceZoomOutTime > 0 ) {
|
|
ForceZoomOutTime = 0;
|
|
ZoomOut(true);
|
|
if( Role < ROLE_Authority)
|
|
ServerZoomOut(false);
|
|
}
|
|
}
|
|
else {
|
|
ForceZoomOutTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( Role < ROLE_Authority )
|
|
return;
|
|
|
|
if ( bIsReloading ) {
|
|
if( Level.TimeSeconds >= ReloadTimer ) {
|
|
ActuallyFinishReloading();
|
|
}
|
|
else if ( !bInterruptedReload && Level.TimeSeconds >= NextBulletLoadTime ) {
|
|
if ( MagAmmoRemaining >= MagCapacity || MagAmmoRemaining >= AmmoAmount(0) ) {
|
|
NextBulletLoadTime += 1000; // don't load bullets anymore
|
|
InterruptReload(); //Interrupt reload because finished loading in under 6 shells
|
|
}
|
|
else {
|
|
MagAmmoRemaining++;
|
|
NumLoadedThisReload++;
|
|
if (NumLoadedThisReload == 6) {
|
|
bInterruptedReload = True; //prevents interrupt if loaded 6 rounds, since it's actually built into the animation
|
|
ActuallyFinishReloading(); //force reload to finish
|
|
}
|
|
|
|
//interrupt if full
|
|
if ( MagAmmoRemaining > 5 || MagAmmoRemaining >= AmmoAmount(0) ) {
|
|
if ( NumLoadedThisReload != 6)
|
|
{
|
|
InterruptReload();
|
|
}
|
|
}
|
|
NextBulletLoadTime += BulletLoadRate;
|
|
Instigator.SetAnimAction(WeaponReloadAnim); //Loop thirdperson reload animation
|
|
}
|
|
}
|
|
}
|
|
else if( !Instigator.IsHumanControlled() ) { // bot
|
|
LastSeenSeconds = Level.TimeSeconds - Instigator.Controller.LastSeenTime;
|
|
if(MagAmmoRemaining == 0 || ((LastSeenSeconds >= 5 || LastSeenSeconds > MagAmmoRemaining) && MagAmmoRemaining < MagCapacity))
|
|
ReloadMeNow();
|
|
}
|
|
|
|
// Turn it off on death / battery expenditure
|
|
if (FlashLight != none)
|
|
{
|
|
// Keep the 1Pweapon client beam up to date.
|
|
AdjustLightGraphic();
|
|
if (FlashLight.bHasLight)
|
|
{
|
|
if (Instigator.Health <= 0 || KFHumanPawn(Instigator).TorchBatteryLife <= 0 || Instigator.PendingWeapon != none )
|
|
{
|
|
//Log("Killing Light...you're out of batteries, or switched / dropped weapons");
|
|
KFHumanPawn(Instigator).bTorchOn = false;
|
|
ServerSpawnLight();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
simulated function bool AllowReload()
|
|
{
|
|
if ( bIsReloading || MagAmmoRemaining >= AmmoAmount(0) )
|
|
return false;
|
|
|
|
UpdateMagCapacity(Instigator.PlayerReplicationInfo);
|
|
if ( MagAmmoRemaining >= MagCapacity )
|
|
return false;
|
|
|
|
if( KFInvasionBot(Instigator.Controller) != none || KFFriendlyAI(Instigator.Controller) != none )
|
|
return true;
|
|
|
|
return !FireMode[0].IsFiring() && !FireMode[1].IsFiring()
|
|
&& Level.TimeSeconds > (FireMode[0].NextFireTime - 0.1);
|
|
}
|
|
|
|
|
|
// Since vanilla reloading replication is totally fucked, I moved base code into separate,
|
|
// replication-free function, which is executed on both server and client side
|
|
// -- PooSH
|
|
simulated function DoReload()
|
|
{
|
|
if ( bHasAimingMode && bAimingRifle ) {
|
|
FireMode[1].bIsFiring = False;
|
|
ZoomOut(false);
|
|
// ZoomOut() just a moment ago was executed on server side - why to force it again? -- PooSH
|
|
// if( Role < ROLE_Authority)
|
|
// ServerZoomOut(false);
|
|
}
|
|
|
|
if ( KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo) != none && KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill != none )
|
|
ReloadMulti = KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo).ClientVeteranSkill.Static.GetReloadSpeedModifier(KFPlayerReplicationInfo(Instigator.PlayerReplicationInfo), self);
|
|
else
|
|
ReloadMulti = 1.0;
|
|
|
|
bIsReloading = true;
|
|
bInterruptedReload = false;
|
|
|
|
//BulletUnloadRate = default.BulletUnloadRate / ReloadMulti;
|
|
BulletLoadRate = default.BulletLoadRate / ReloadMulti;
|
|
//NextBulletLoadTime = Level.TimeSeconds + BulletUnloadRate + BulletLoadRate; //BulletUnloadRate disabled
|
|
NextBulletLoadTime = Level.TimeSeconds + BulletLoadRate;
|
|
// more bullets left = less time to reload
|
|
ReloadRate = default.ReloadRate / ReloadMulti;
|
|
ReloadRate -= MagAmmoRemaining * BulletLoadRate;
|
|
ReloadTimer = Level.TimeSeconds + ReloadRate;
|
|
if ( MagAmmoRemaining != MagCapacity && AmmoAmount(0) > MagAmmoRemaining)
|
|
Instigator.SetAnimAction(WeaponReloadAnim); //loop animation unless full or out of ammo
|
|
}
|
|
// This function is triggered by client, replicated to server and NOT EXECUTED on client,
|
|
// even if marked as simulated
|
|
|
|
exec function ReloadMeNow()
|
|
{
|
|
local KFPlayerController PC;
|
|
|
|
if ( !AllowReload() )
|
|
return;
|
|
|
|
DoReload();
|
|
ClientReload();
|
|
|
|
NumLoadedThisReload = 0;
|
|
|
|
PC = KFPlayerController(Instigator.Controller);
|
|
if ( PC != none && Level.Game.NumPlayers > 1 && KFGameType(Level.Game).bWaveInProgress
|
|
&& Level.TimeSeconds - PC.LastReloadMessageTime > PC.ReloadMessageDelay )
|
|
{
|
|
KFPlayerController(Instigator.Controller).Speech('AUTO', 2, "");
|
|
KFPlayerController(Instigator.Controller).LastReloadMessageTime = Level.TimeSeconds;
|
|
}
|
|
}
|
|
|
|
function ServerRequestAutoReload()
|
|
{
|
|
ReloadMeNow();
|
|
}
|
|
|
|
// This function now is triggered by ReloadMeNow() and executed on local side only
|
|
simulated function ClientReload()
|
|
{
|
|
DoReload();
|
|
PlayAnim(ReloadAnim, ReloadAnimRate*ReloadMulti, 0.1);
|
|
}
|
|
|
|
// This function now is triggered by ReloadMeNow() and executed on local side only
|
|
|
|
|
|
|
|
function AddReloadedAmmo() {} // magazine is filled in WeaponTick now
|
|
|
|
|
|
simulated function bool StartFire(int Mode)
|
|
{
|
|
if ( MagAmmoRemaining <= 0 )
|
|
return false;
|
|
if ( bIsReloading ) {
|
|
InterruptReload();
|
|
return false;
|
|
}
|
|
return super(Weapon).StartFire(Mode);
|
|
}
|
|
|
|
simulated function AltFire(float F)
|
|
{
|
|
InterruptReload();
|
|
}
|
|
|
|
// another fucked up replication...
|
|
// By the looks if it, InterruptReload() is called only on client, which triggers ServerInterruptReload().
|
|
// Anyway, why server would want to interrupt reload by its own?..
|
|
simulated function bool InterruptReload()
|
|
{
|
|
if( bIsReloading && !bInterruptedReload && (ReloadTimer - Level.TimeSeconds)*ReloadMulti > 0.4 )
|
|
{
|
|
// that's very lame how to do stuff like that - don't repeat it at home ;)
|
|
// in theory client should send server a request to interrupt the reload,
|
|
// and the server send back accept to client.
|
|
// But in such case we have a double chance of screwing the shit up, so let's just
|
|
// do it lazy way.
|
|
ServerInterruptReload();
|
|
ClientInterruptReload();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function ServerInterruptReload()
|
|
{
|
|
if ( Role == ROLE_Authority && bInterruptedReload == false ) {
|
|
ReloadTimer = Level.TimeSeconds + 0.4/ReloadMulti;
|
|
bInterruptedReload = true;
|
|
//PlayAnim(ReloadAnim, ReloadAnimRate*ReloadMulti, 0.1); //added this
|
|
SetAnimFrame(279, 0 , 1); // go to closing drum stage
|
|
}
|
|
}
|
|
|
|
simulated function ClientInterruptReload()
|
|
{
|
|
if ( Role < ROLE_Authority && bInterruptedReload == false) {
|
|
bInterruptedReload = true;
|
|
PlayAnim(ReloadAnim, ReloadAnimRate*ReloadMulti, 0.1); //added this
|
|
SetAnimFrame(279, 0 , 1); // go to closing drum stage
|
|
//ShowAllBullets();
|
|
}
|
|
}
|
|
|
|
simulated exec function ToggleIronSights()
|
|
{
|
|
if( bHasAimingMode ) {
|
|
if( bAimingRifle )
|
|
PerformZoom(false);
|
|
else
|
|
IronSightZoomIn();
|
|
}
|
|
}
|
|
|
|
simulated exec function IronSightZoomIn()
|
|
{
|
|
if( bHasAimingMode ) {
|
|
if( Owner != none && Owner.Physics == PHYS_Falling
|
|
&& Owner.PhysicsVolume.Gravity.Z <= class'PhysicsVolume'.default.Gravity.Z )
|
|
return;
|
|
|
|
if( bIsReloading ) {
|
|
InterruptReload(); // finish reloading while zooming in -- PooSH
|
|
}
|
|
PerformZoom(True);
|
|
}
|
|
}
|
|
|
|
simulated function bool PutDown()
|
|
{
|
|
local int Mode;
|
|
|
|
// continue here, because there is nothing to stop us from interrupting the reload -- PooSH
|
|
if ( bIsReloading )
|
|
InterruptReload();
|
|
|
|
if( bAimingRifle )
|
|
ZoomOut(False);
|
|
|
|
// From Weapon.uc
|
|
if (ClientState == WS_BringUp || ClientState == WS_ReadyToFire)
|
|
{
|
|
if ( (Instigator.PendingWeapon != None) && !Instigator.PendingWeapon.bForceSwitch )
|
|
{
|
|
for (Mode = 0; Mode < NUM_FIRE_MODES; Mode++)
|
|
{
|
|
// if _RO_
|
|
if( FireMode[Mode] == none )
|
|
continue;
|
|
// End _RO_
|
|
|
|
if ( FireMode[Mode].bFireOnRelease && FireMode[Mode].bIsFiring )
|
|
return false;
|
|
if ( FireMode[Mode].NextFireTime > Level.TimeSeconds + FireMode[Mode].FireRate*(1.f - MinReloadPct))
|
|
DownDelay = FMax(DownDelay, FireMode[Mode].NextFireTime - Level.TimeSeconds - FireMode[Mode].FireRate*(1.f - MinReloadPct));
|
|
}
|
|
}
|
|
|
|
if (Instigator.IsLocallyControlled())
|
|
{
|
|
for (Mode = 0; Mode < NUM_FIRE_MODES; Mode++)
|
|
{
|
|
// if _RO_
|
|
if( FireMode[Mode] == none )
|
|
continue;
|
|
// End _RO_
|
|
|
|
if ( FireMode[Mode].bIsFiring )
|
|
ClientStopFire(Mode);
|
|
}
|
|
|
|
if ( DownDelay <= 0 || KFPawn(Instigator).bIsQuickHealing > 0)
|
|
{
|
|
if ( ClientState == WS_BringUp || KFPawn(Instigator).bIsQuickHealing > 0 )
|
|
TweenAnim(SelectAnim,PutDownTime);
|
|
else if ( HasAnim(PutDownAnim) )
|
|
{
|
|
if( ClientGrenadeState == GN_TempDown || KFPawn(Instigator).bIsQuickHealing > 0)
|
|
PlayAnim(PutDownAnim, PutDownAnimRate * (PutDownTime/QuickPutDownTime), 0.0);
|
|
else
|
|
PlayAnim(PutDownAnim, PutDownAnimRate, 0.0);
|
|
|
|
}
|
|
}
|
|
}
|
|
ClientState = WS_PutDown;
|
|
if ( Level.GRI.bFastWeaponSwitching )
|
|
DownDelay = 0;
|
|
if ( DownDelay > 0 )
|
|
{
|
|
SetTimer(DownDelay, false);
|
|
}
|
|
else
|
|
{
|
|
if( ClientGrenadeState == GN_TempDown )
|
|
{
|
|
SetTimer(QuickPutDownTime, false);
|
|
}
|
|
else
|
|
{
|
|
SetTimer(PutDownTime, false);
|
|
}
|
|
}
|
|
}
|
|
for (Mode = 0; Mode < NUM_FIRE_MODES; Mode++)
|
|
{
|
|
// if _RO_
|
|
if( FireMode[Mode] == none )
|
|
continue;
|
|
// End _RO_
|
|
|
|
FireMode[Mode].bServerDelayStartFire = false;
|
|
FireMode[Mode].bServerDelayStopFire = false;
|
|
}
|
|
Instigator.AmbientSound = None;
|
|
OldWeapon = None;
|
|
return true; // return false if preventing weapon switch
|
|
}
|
|
|
|
defaultproperties
|
|
{
|
|
BulletLoadRate=1.400000
|
|
ReloadRate=9.604000
|
|
ReloadAnimRate=1.178570
|
|
FireModeClass(0)=Class'ScrnBalanceSrv.ScrnM32Fire'
|
|
PickupClass=Class'ScrnBalanceSrv.ScrnM32Pickup'
|
|
ItemName="M32 Grenade Launcher SE"
|
|
}
|