rott/kf_sources/ScrnBalanceSrv/Classes/ScrnM32GrenadeLauncher.uc
2026-07-14 20:27:09 +07:00

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"
}