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

476 lines
15 KiB
Ucode

//==================================================
//==================================================
// Whisky's Colt Python
// Balance job by PooSH
//==================================================
class Colt extends KFWeapon;
var float ReloadMulti;
var float BulletUnloadRate; // how long does it takes to unload empty bullets
var float BulletLoadRate; // how long does it takes to load a single bullet
var transient bool bInterruptedReload;
var transient float NextBulletLoadTime;
// Implemented revolver-specific reloading
// Reload State 1 (uninterruptable): opening drum and unloading empty bullets. Lasts BulletUnloadRate seconds.
// Reload State 2: loading bullets. Each bullet load takes BulletLoadRate seconds.
// Reload State 3: closing drum.
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 (AmmoAmount(0) <= MagAmmoRemaining )
InterruptReload(); //force interrupt if loaded last bullet
if( Level.TimeSeconds >= ReloadTimer ) {
ActuallyFinishReloading();
}
else if ( !bInterruptedReload && Level.TimeSeconds >= NextBulletLoadTime ) {
if ( MagAmmoRemaining >= MagCapacity || MagAmmoRemaining >= AmmoAmount(0) )
NextBulletLoadTime += 1000; // don't load bullets anymore
else {
MagAmmoRemaining++;
NextBulletLoadTime += BulletLoadRate;
}
}
}
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;
// more bullets left = less time to reload
ReloadRate = default.ReloadRate / ReloadMulti;
ReloadRate -= MagAmmoRemaining * BulletLoadRate;
ReloadTimer = Level.TimeSeconds + ReloadRate;
Instigator.SetAnimAction(WeaponReloadAnim);
}
// 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();
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);
}
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.7/ReloadMulti )
{
// 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 ) {
ReloadTimer = Level.TimeSeconds + 0.5/ReloadMulti;
bInterruptedReload = true;
SetAnimFrame(112, 0 , 1); // go to closing drum stage
}
}
simulated function ClientInterruptReload()
{
if ( Role < ROLE_Authority ) {
bInterruptedReload = true;
SetAnimFrame(112, 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
}
simulated function Notify_DropBullets()
{
if ( !Instigator.IsLocallyControlled() )
return ;
// bullets are reloaded in this order: 2,1,5,4,6,3
switch ( MagAmmoRemaining ) {
case 5:
SetBoneScale(1, 0.0, 'b1');
SetBoneScale(2, 0.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 0.0, 'b4');
SetBoneScale(5, 0.0, 'b5');
SetBoneScale(6, 0.0, 'b6');
break;
case 4:
SetBoneScale(1, 0.0, 'b1');
SetBoneScale(2, 0.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 0.0, 'b4');
SetBoneScale(5, 0.0, 'b5');
SetBoneScale(6, 1.0, 'b6');
break;
case 3:
SetBoneScale(1, 0.0, 'b1');
SetBoneScale(2, 0.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 1.0, 'b4');
SetBoneScale(5, 0.0, 'b5');
SetBoneScale(6, 1.0, 'b6');
break;
case 2:
SetBoneScale(1, 0.0, 'b1');
SetBoneScale(2, 0.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 1.0, 'b4');
SetBoneScale(5, 1.0, 'b5');
SetBoneScale(6, 1.0, 'b6');
break;
case 1:
SetBoneScale(1, 1.0, 'b1');
SetBoneScale(2, 0.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 1.0, 'b4');
SetBoneScale(5, 1.0, 'b5');
SetBoneScale(6, 1.0, 'b6');
break;
default:
SetBoneScale(1, 1.0, 'b1');
SetBoneScale(2, 1.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 1.0, 'b4');
SetBoneScale(5, 1.0, 'b5');
SetBoneScale(6, 1.0, 'b6');
}
}
simulated function Notify_InsertBullets()
{
ShowAllBullets();
switch ( MagAmmoRemaining ) {
case 5: SetAnimFrame(82, 0 , 1); break;
case 4: SetAnimFrame(71.5, 0 , 1); break;
case 3: SetAnimFrame(61, 0 , 1); break;
case 2: SetAnimFrame(50.5, 0 , 1); break;
case 1: SetAnimFrame(38.5, 0 , 1); break;
}
}
simulated function ShowAllBullets()
{
if ( !Instigator.IsLocallyControlled() )
return ;
// restore bones
// bullets are reloaded in this order: 2,1,5,4,6,3
SetBoneScale(1, 1.0, 'b1');
SetBoneScale(2, 1.0, 'b2');
SetBoneScale(3, 1.0, 'b3');
SetBoneScale(4, 1.0, 'b4');
SetBoneScale(5, 1.0, 'b5');
SetBoneScale(6, 1.0, 'b6');
}
defaultproperties
{
BulletUnloadRate=1.000000
BulletLoadRate=0.475000
FirstPersonFlashlightOffset=(X=-20.000000,Y=-22.000000,Z=8.000000)
MagCapacity=6
ReloadRate=5.500000
ReloadAnim="Reload"
ReloadAnimRate=1.200000
bHoldToReload=True
WeaponReloadAnim="Reload_Revolver"
Weight=5.000000
bHasAimingMode=True
IdleAimAnim="IronIdle"
StandardDisplayFOV=70.000000
bModeZeroCanDryFire=True
SleeveNum=0
TraderInfoTexture=Texture'ScrnWeaponPack_T.Colt.Trader_WColt'
MeshRef="ScrnWeaponPack_A.colt_weapon"
SkinRefs(1)="ScrnWeaponPack_T.Colt.ColtV2_T"
HudImageRef="ScrnWeaponPack_T.Colt.WColt_Unselected"
SelectedHudImageRef="ScrnWeaponPack_T.Colt.WColt"
ZoomedDisplayFOV=65.000000
FireModeClass(0)=Class'ScrnWeaponPack.ColtFire'
FireModeClass(1)=Class'KFMod.NoFire'
PutDownAnim="PutDown"
SelectSound=Sound'KF_9MMSnd.9mm_Select'
AIRating=0.250000
CurrentRating=0.250000
bShowChargingBar=True
Description="The Colt Python is a double action handgun chambered for the .357 Magnum cartridge, built on Colt's large I-frame. Pythons have a reputation for accuracy, smooth trigger pull, and a tight cylinder lock-up."
DisplayFOV=70.000000
Priority=110
InventoryGroup=2
GroupOffset=1
PickupClass=Class'ScrnWeaponPack.ColtPickup'
PlayerViewOffset=(X=20.000000,Y=25.000000,Z=-10.000000)
BobDamping=6.000000
AttachmentClass=Class'ScrnWeaponPack.ColtAttachment'
IconCoords=(X1=434,Y1=253,X2=506,Y2=292)
ItemName="Colt Python SE"
}