NicePack/sources/Weapons/NiceWeapon.uc

1597 lines
70 KiB
Ucode

// Weapon class for NicePack that supports:
// 1. Client-side ammo management
// When using client-side hit detection we don't want to wait for server to send us an update on current ammunition amount.
// So we try and keep track of it on the client instead.
// This is especially important for high fire rate weapons.
// No protection against cheating implemented.
// 2. Client-side multistage reload
// Reloading process is now handled on the client and is tightly tied to the animation. Server only receives updates on ammo amount after reload.
// Reloading now also consists of several stages, which allows to update ammo amount at certain points of the animation and, coupled with reload cancellation, allows for partial completion of reload.
// Stages must be manually set for each weapon by specifying animation frame at which each stage should begin. Also a name of the bone, corresponding to the weapon's magazine must be provided.
// 1) Magazine weapons
// Magazine weapons have up to 4 reload stages:
// - First is a 'prestage', when magazine hasn't yet been removed,
// - Second is a main stage during which old magazine was removed, but new one wasn't yet inserted,
// - Third is weapon charging stage that happens after magazine insertion,
// - Fourth stage contains all the useless animation left-overs.
// 2) Shell-by-shell reload ('single') doesn't really has stages.
// The only "feature" for this type of reload is playing the end of the reload animation in case we're reloading from 0 ammo
// 3) Auto reload
// This describes the type of reload that is a part of a shooting animation for 1-shot weapons (and hunting shotgun).
// It consists of 3 stages:
// - First is unskippable part, usually the shot itself,
// - Second is actual reloading part,
// - Third is a trash part, that contains all the useless animation left-overs.
// Introduced functionality allows to force reload by skipping straight to stage 2.
// Unlike 2 previous reloads includes a stage that cannot (at least shouldn't) be interrupted by any means.
// 3. Reload cancellation
// Initially this functionality was just a copy-paste of a1eat0r's 'Reload options' mutator.
// Now functionality was altered to use introduced stages of reload process.
class NiceWeapon extends KFWeapon
dependson(NicePlainData)
abstract;
var float lastHeadshotTime;
var float stdFireRate;
var float holsteredCompletition;
var bool bLoadResourcesAsMaterial; // Force to load all graphic resources as just materials
var bool bUseFlashlightToToggle;
var int MagAmmoRemainingClient; // Tracks magazine size on client
var bool bRoundInChamber; // Indicates that bullet was loaded in a chamber
var float LastMagUpdateFromClient; // Time of the most recent magazine update, received from the client
var float ReloadDeadLine; // Time, after which server should speed-up reload
var float lastRecordedReloadRate;
var float recordedZoomTime;
// Weapon secondary charge counter
var bool bShowSecondaryCharge;
var int secondaryCharge;
// HUD icons changes
var bool bChangeClipIcon;
var bool bChangeBulletsIcon;
var bool bChangeSecondaryIcon;
var Texture hudClipTexture;
var Texture hudBulletsTexture;
var Texture hudSecondaryTexture;
// Laser-related variables
var bool bLaserActive; // The laser site is active
var bool bAllowFreeDot; // Allows dot from the laser to freely follow movements of the bone (the same way as it usually does during reload)
var() class<InventoryAttachment> LaserAttachmentClass; // First person laser attachment class
var Actor LaserAttachment; // First person laser attachment
var Actor altLaserAttachment; // Alternative laser attachment
var() byte LaserType; // current laser type
var Vector LaserAttachmentOffset; // relative offset from attachment bone
var Vector altLaserAttachmentOffset; // relative offset from alternative attachment bone
var Rotator LaserAttachmentRotation; // How should we rotate the bone, our laser is attached to?
var Rotator altLaserAttachmentRotation; // How should we rotate the bone, our alternative laser is attached to?
var const class<ScrnLocalLaserDot> LaserDotClass;
var ScrnLocalLaserDot LaserDot;
var ScrnLocalLaserDot altLaserDot;
var name LaserAttachmentBone;
var name altLaserAttachmentBone;
// Prossible reasons for reload cancel
enum ERelCancelCause{
CANCEL_FIRE,
CANCEL_ALTFIRE,
CANCEL_NADE,
CANCEL_COOKEDNADE,
CANCEL_SWITCH,
CANCEL_PASSIVESWITCH,
CANCEL_AIM,
CANCEL_RELOAD
};
// Possible reload types for main reload (forced by 'ReloadMeNow()' function)
// Note that weapons with two fire-modes can have 'RTYPE_MAG' or 'RTYPE_SINGLE' for main reload and still use auto reload (e.g. for secondary fire of M4 203)
enum ERelType{
RTYPE_MAG, // Magazine-type reload (like for commando rifles or M14 EBR)
RTYPE_SINGLE, // Single-shell reload (like lar or shotgun)
RTYPE_AUTO // Means that auto reload is a main reload for this weapon (hunting shotgun and 1-shot weapons such as M79, M99, xbow, etc)
};
var ERelType reloadType;
// Possible stages of a magazine reload
enum ERelStage{
RSTAGE_NONE, // Weapon isn't being reloaded
RSTAGE_PREREL, // Magazine wasn't yet removed, reload can be safely interrupted
RSTAGE_MAINREL, // Magazine was already removed, reload cannot be interrupted without penalties
RSTAGE_POSTREL, // Magazine was replaced, reload can be safely interrupted, but post-reload stage (weapon charging) will have to be redone
RSTAGE_TRASH // Non-functioning frames of animation after inserting magazine and charging weapon
};
var bool bServerFiredLastShot;
var bool bGiveObsessiveBonus;
// Magazine reload-related variables
var float reloadPreEndFrame; // Frame number, after which magazine is removed, so interrupting reload will result in zero magazine ammo
var float reloadEndFrame; // Frame number, after which new magazine is inserted, but gun wasn't yet charged, so if reload is interrupted after this stage, - charging would need to be redone later
var float reloadChargeEndFrame; // Frame number, after which weapon's reload can be interrupted without any penalties
var float reloadMagStartFrame; // Frame number, from which to start animation magazine insertion; don't confuse it with a point at which magazine removal starts in a full reload animation
var float reloadChargeStartFrame; // Frame number, from which to start animating weapon charging; don't confuse it with a point at which charging starts in a full reload animation
var bool bMagazineOut; // Indicates if magazine is currently removed from the weapon
var name magazineBone; // Bone that we need to hide when magazine is out
var bool bHasChargePhase; // Weapon needs to be charged at some point
var bool bNeedToCharge; // This flag marks the need to finish post-stage of reload (gun charging stage)
var ERelStage currentRelStage; // Current stage of the magazine reload
// Following variables are a result of poor initial design of this system that didn't account for the need to do reloads of dual weapons
// The whole thing will be rewritten from the ground up, but for now that's far from priority goal
// What I'm adding with these is an event system that would call a 'ReloadEvent' function when reload passes a certain point
// Only magazine reload supports these
struct EventRecord{
var string eventName;
var float eventFrame;
};
var array<EventRecord> relEvents;
var float lastEventCheckFrame;
// Single reload-related variables
var int subReloadStage; // Substage is part of animation between two consecutive shell loadings; they're numbered starting from zero
var bool alwaysPlayAnimEnd; // Should we always force playing end of the reload animation?
var array<float> reloadStages; // Array of frame numbers that indicate moments when ammo should be added
// Auto reload-related structure, enum and variables
// Auto reload activates by itself whenever appropriate animation starts to play; structure object below must be provided for each such animation
struct AutoReloadAnimDesc{
var name animName; // Name of the animation for which instance of this struct is prepared
var float canInterruptFrame; // Frame, starting from which reload can be interrupted
var float trashStartFrame; // Frame from which starts useless part of animation
var float resumeFrame; // Frame from which we must resume reload if it was previously interrupted
var float speedFrame; // Frame from which we must apply reload speed bonus (so that initial, shooting part remains unaffected)
};
// Array that contains information about all possible animations that can treated as auto-reload
var array<AutoReloadAnimDesc> autoReloadsDescriptions;
enum EAutoRelStage{
RAUTOSTAGE_NONE, // Auto reload is inactive
RAUTOSTAGE_UNINTERRUPTIBLE, // Auto reload is active and it's currently uninterruptible (usually that's the shooting part of animation)
RAUTOSTAGE_INTERRUPTIBLE // Auto reload is active and can be interrupted; ammo is reloaded at the end of this state (it will be skipped in case there's no more ammunition)
};
var bool bAllowAutoReloadSkip; // Indicates if switching to another weapon should allow player to skip interruptible part of auto reload completely
var int currentAutoReload; // Active auto reload always corresponds to a certain animation; this is index of it's corresponding data in 'autoReloadsDescriptions'
var EAutoRelStage currentAutoReloadStage;
var bool bAutoReload; // Indicates that current reload is the auto reload
var bool bAutoReloadInterrupted; // Indicates if auto reload was interrupted (and must be repeated as soon as possible)
var bool bAutoReloadPaused; // This is used to 'pause' auto reload in case we need to interrupt it for a moment (and then immediately continue from where we left), i.e. when throwing a grenade
var float autoReloadPauseFrame; // Frame at which current pause began
var bool bAutoReloadRateApplied; // Flag that remembers whether or not we've already applied reload speed up for current auto reload (to avoid constant animation's speed updates)
var float autoReloadSpeedModifier;
// Acrtive reload-related variables
// Active reload state
enum EActiveReloadState{
ACTR_NONE, // Activation wasn't yet attempted during current reload
ACTR_FAIL, // Activation failed
ACTR_SUCCESS // Activation succeeded
};
var bool bCanActiveReload; // Can we even use active reload with this weapon?
var float activeSlowdown; // How much should we slow down the speed of reload if player has failed?
var float activeSpeedup; // How much should we speedup active reload if player succeeded?
var EActiveReloadState activeReloadState; // Current state of active reload, only applicable during reload
var float activeWindow; // How long (0.0 is zero long, 1.0 is all animation) must be a time window during which you can activate active reload
replication{
reliable if(Role < ROLE_Authority)
ServerReduceMag, ServerSetMagSize, ServerSetSndCharge, ServerReload, ServerShiftReloadTime, ServerStopReload,
ServerSetCharging, ServerSetLaserType;
reliable if(Role == ROLE_Authority)
ClientForceInterruptReload, ClientReloadMeNow, ClientSetMagSize, ClientSetSndCharge, ClientPutDown,
ClientThrowGrenade, ClientCookGrenade, ClientTryPendingWeapon, ClientUpdateWeaponMag, ClientSetLaserType,
ClientReloadAmmo;
reliable if(Role == ROLE_Authority)
holsteredCompletition;
}
static function PreloadAssets(Inventory Inv, optional bool bSkipRefCount){
local int i;
if(!bSkipRefCount)
default.ReferenceCount ++;
if(default.Mesh == none && default.MeshRef != "")
UpdateDefaultMesh(SkeletalMesh(DynamicLoadObject(default.MeshRef, class'SkeletalMesh')));
if(default.HudImage == none && default.HudImageRef != "")
default.HudImage = texture(DynamicLoadObject(default.HudImageRef, class'texture'));
if(default.SelectedHudImage == none && default.SelectedHudImageRef != "")
default.SelectedHudImage = texture(DynamicLoadObject(default.SelectedHudImageRef, class'texture'));
if(default.SelectSound == none && default.SelectSoundRef != "")
default.SelectSound = sound(DynamicLoadObject(default.SelectSoundRef, class'sound'));
for(i = 0;i < default.SkinRefs.Length;i ++)
if(default.SkinRefs[i] != "" && (default.Skins.Length < i + 1 || default.Skins[i] == none)){
default.Skins[i] = none;
if(default.bLoadResourcesAsMaterial)
default.Skins[i] = Material(DynamicLoadObject(default.SkinRefs[i], class'Material'));
// Try to load as various types of materials
if(default.Skins[i] == none)
default.Skins[i] = Combiner(DynamicLoadObject(default.SkinRefs[i], class'Combiner', true));
if(default.Skins[i] == none)
default.Skins[i] = FinalBlend(DynamicLoadObject(default.SkinRefs[i], class'FinalBlend', true));
if(default.Skins[i] == none)
default.Skins[i] = Shader(DynamicLoadObject(default.SkinRefs[i], class'Shader', true));
if(default.Skins[i] == none)
default.Skins[i] = Texture(DynamicLoadObject(default.SkinRefs[i], class'Texture', true));
if(default.Skins[i] == none)
default.Skins[i] = Material(DynamicLoadObject(default.SkinRefs[i], class'Material'));
}
if(NiceWeapon(Inv) != none){
Inv.LinkMesh(default.Mesh);
NiceWeapon(Inv).HudImage = default.HudImage;
NiceWeapon(Inv).SelectedHudImage = default.SelectedHudImage;
NiceWeapon(Inv).SelectSound = default.SelectSound;
for(i = 0; i < default.SkinRefs.Length;i ++)
Inv.Skins[i] = default.Skins[i];
}
}
static function bool UnloadAssets(){
local int i;
default.ReferenceCount--;
UpdateDefaultMesh(none);
default.HudImage = none;
default.SelectedHudImage = none;
default.SelectSound = none;
for(i = 0;i < default.SkinRefs.Length; i ++)
default.Skins[i] = none;
return default.ReferenceCount == 0;
}
simulated function PostBeginPlay(){
if(default.recordedZoomTime < 0)
default.recordedZoomTime = ZoomTime;
recordedZoomTime = default.recordedZoomTime;
// Default variables
LastMagUpdateFromClient = 0.0;
bNeedToCharge = false;
bMagazineOut = false;
currentRelStage = RSTAGE_NONE;
lastEventCheckFrame = -1.0;
// Fill sub reload stages
fillSubReloadStages();
// Auto fill reload stages for 'RTYPE_SINGLE' reload
if(reloadType == RTYPE_SINGLE)
UpdateSingleReloadVars();
if(reloadType == RTYPE_AUTO)
bHasChargePhase = false;
if(reloadChargeStartFrame < 0.0 || reloadChargeEndFrame < 0.0)
bHasChargePhase = false;
if(FireModeClass[0] != none)
stdFireRate = FireModeClass[0].default.fireRate;
super.PostBeginPlay();
}
// Allows to prevent leaving iron sights unwillingly
function bool ShouldLeaveIronsight(){
local class<NiceVeterancyTypes> niceVet;
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(Instigator.PlayerReplicationInfo);
if(niceVet != none && niceVet.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillEnforcerUnshakable'))
return false;
return true;
}
// Updates max value for this weapon's ammunition
simulated function UpdateWeaponAmmunition(){
local int i;
for(i = 0;i < NUM_FIRE_MODES;i ++){
if(Ammo[i] == none)
continue;
if(NiceAmmo(Ammo[i]) != none)
NiceAmmo(Ammo[i]).UpdateAmmoAmount();
}
}
function UpdateWeaponMag(){
UpdateMagCapacity(Instigator.PlayerReplicationInfo);
MagAmmoRemaining = Min(MagAmmoRemaining, MagCapacity);
ClientSetMagSize(MagAmmoRemaining, bRoundInChamber);
}
simulated function ClientUpdateWeaponMag(){
local int actualMag;
UpdateMagCapacity(Instigator.PlayerReplicationInfo);
actualMag = MagAmmoRemainingClient;
if(bHasChargePhase && bRoundInChamber && actualMag > 0)
actualMag --;
MagAmmoRemainingClient = Min(MagAmmoRemainingClient, MagCapacity);
if(bHasChargePhase && bRoundInChamber)
actualMag ++;
ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
}
// Overloaded to properly reduce magazine's size
simulated function bool ConsumeAmmo(int Mode, float Load, optional bool bAmountNeededIsMax){
local Inventory Inv;
local bool bOutOfAmmo;
local KFWeapon KFWeap;
if(super(Weapon).ConsumeAmmo(Mode, Load, bAmountNeededIsMax)){
if(Load > 0 && (Mode == 0 || bReduceMagAmmoOnSecondaryFire)){
MagAmmoRemaining -= Load;
if(MagAmmoRemaining < 0)
MagAmmoRemaining = 0;
if(MagAmmoRemaining <= 0)
bRoundInChamber = false;
}
NetUpdateTime = Level.TimeSeconds - 1;
if(FireMode[Mode].AmmoPerFire > 0 && InventoryGroup > 0 && !bMeleeWeapon && bConsumesPhysicalAmmo &&
(Ammo[0] == none || FireMode[0] == none || FireMode[0].AmmoPerFire <= 0 || Ammo[0].AmmoAmount < FireMode[0].AmmoPerFire) &&
(Ammo[1] == none || FireMode[1] == none || FireMode[1].AmmoPerFire <= 0 || Ammo[1].AmmoAmount < FireMode[1].AmmoPerFire)){
bOutOfAmmo = true;
for(Inv = Instigator.Inventory;Inv != none; Inv = Inv.Inventory){
KFWeap = KFWeapon(Inv);
if(Inv.InventoryGroup > 0 && KFWeap != none && !KFWeap.bMeleeWeapon && KFWeap.bConsumesPhysicalAmmo &&
((KFWeap.Ammo[0] != none && KFWeap.FireMode[0] != none && KFWeap.FireMode[0].AmmoPerFire > 0 &&KFWeap.Ammo[0].AmmoAmount >= KFWeap.FireMode[0].AmmoPerFire) ||
(KFWeap.Ammo[1] != none && KFWeap.FireMode[1] != none && KFWeap.FireMode[1].AmmoPerFire > 0 && KFWeap.Ammo[1].AmmoAmount >= KFWeap.FireMode[1].AmmoPerFire))){
bOutOfAmmo = false;
break;
}
}
if(bOutOfAmmo)
PlayerController(Instigator.Controller).Speech('AUTO', 3, "");
}
return true;
}
return false;
}
// Forces update for client's magazine ammo counter
// In case we are using client-side hit-detection, client itself manages remaining ammunition in magazine, but in some cases we want server to dictate current magazine amount
// This function sets client's mag size to a given value
simulated function ClientSetMagSize(int newMag, bool bChambered){
MagAmmoRemainingClient = newMag;
bRoundInChamber = bChambered;
if(MagAmmoRemainingClient > 0 && bHasChargePhase && !bRoundInChamber)
bNeedToCharge = true;
}
// This function allows clients to change magazine size without altering total ammo amount
// It allows clients to provide time-stamps, so that older change won't override a newer one
function ServerSetMagSize(int newMag, bool bChambered, float updateTime){
magAmmoRemaining = newMag;
bRoundInChamber = bChambered;
if(LastMagUpdateFromClient <= updateTime){
LastMagUpdateFromClient = updateTime;
if(newMag > 0)
bServerFiredLastShot = false;
}
}
// This function allows clients to change magazine size along with total ammo amount on the server (to update ammo counter in client-side mode)
// It allows clients to provide time-stamps, so that older change won't override a newer one
// Intended to be used for decreasing ammo count from shooting and cannot increase magazine size
simulated function ServerReduceMag(int newMag, float updateTime, int Mode){
local int delta;
if(Mode == 0 || !bHasSecondaryAmmo){
delta = magAmmoRemaining - newMag;
// Only update later changes that actually decrease magazine
if(LastMagUpdateFromClient <= updateTime && delta > 0){
LastMagUpdateFromClient = updateTime;
ConsumeAmmo(Mode, delta);
}
}
else
ConsumeAmmo(Mode, 1);
}
// Forces either 'AddReloadedAmmo' or 'AddAutoReloadedAmmo' (depending on which one is appropriate) function on client
// Somewhat of a hack to allow server force-add ammo to the weapon
simulated function ClientReloadAmmo(){
if(reloadType == RTYPE_AUTO)
AddAutoReloadedAmmo();
else
AddReloadedAmmo();
bNeedToCharge = false;
bMagazineOut = false;
if(bHasChargePhase)
bRoundInChamber = true;
ResetReloadVars();
}
// Adds appropriate amount of ammo during reload.
// Up to full mag for magazine reload, 1 ammo per call for single reload.
// Isn't called in case of auto reload, use 'AddAutoReloadedAmmo' for that.
simulated function AddReloadedAmmo(){
UpdateMagCapacity(Instigator.PlayerReplicationInfo);
if(reloadType == RTYPE_MAG){
if(AmmoAmount(0) >= MagCapacity){
MagAmmoRemainingClient = MagCapacity;
if(bRoundInChamber)
MagAmmoRemainingClient ++;
}
else
MagAmmoRemainingClient = AmmoAmount(0);
}
else if(reloadType == RTYPE_SINGLE){
if(AmmoAmount(0) - MagAmmoRemainingClient > 0 && MagAmmoRemainingClient < MagCapacity)
MagAmmoRemainingClient ++;
}
ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
if(PlayerController(Instigator.Controller) != none && KFSteamStatsAndAchievements(PlayerController(Instigator.Controller).SteamStatsAndAchievements) != none)
KFSteamStatsAndAchievements(PlayerController(Instigator.Controller).SteamStatsAndAchievements).OnWeaponReloaded();
}
// Function that manages ammo replenishing for auto reload
simulated function AddAutoReloadedAmmo(){
if(reloadType != RTYPE_AUTO){
secondaryCharge = 1;
ServerSetSndCharge(secondaryCharge);
}
else{
if(AmmoAmount(0) > 0)
MagAmmoRemainingClient = 1;
else
MagAmmoRemainingClient = 0;
ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
}
}
simulated function float TimeUntillReload(){
if(reloadType == RTYPE_MAG)
return FMax(reloadChargeEndFrame, reloadEndFrame) * GetAnimDuration(ReloadAnim);
else if(reloadType == RTYPE_SINGLE && reloadStages.Length > 0)
return reloadStages[0] * GetAnimDuration(ReloadAnim);
else if(reloadType == RTYPE_AUTO && autoReloadsDescriptions.Length > 0)
return autoReloadsDescriptions[0].trashStartFrame * GetAnimDuration(autoReloadsDescriptions[0].animName);
return 60.0;
}
// Interrupt the reloading animation, which may reset the weapon's magAmmoRemaining;
// Don't allow Fire() and AltFire() to interrupt by shooting with empty magazine.
simulated function ClientForceInterruptReload(ERelCancelCause cause){
local bool bActualAllowAutoReloadSkip;
local bool bCauseFire, bCauseAltFire, bCauseNade, bCauseAim, bCauseSwitch;
local HUDKillingFloor HUD;
local NiceHumanPawn nicePawn;
local NicePlayerController nicePlayer;
local bool bDisplayInventory, bWeReallyShould;
// Determine causes flags
bCauseFire = cause == CANCEL_FIRE;
bCauseAltFire = cause == CANCEL_ALTFIRE;
bCauseNade = (cause == CANCEL_NADE) || (cause == CANCEL_COOKEDNADE);
bCauseAim = cause == CANCEL_AIM;
bCauseSwitch = cause == CANCEL_SWITCH || cause == CANCEL_PASSIVESWITCH;
// Is player looking at inventory?
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePlayer == none || !bIsReloading)
return;
HUD = HUDKillingFloor(nicePlayer.MyHUD);
bDisplayInventory = HUD != none && HUD.bDisplayInventory;
// General checks: is there any meaning to reset reload for provided cause?
if(bCauseFire)
bWeReallyShould = !bNeedToCharge && (bDisplayInventory || magAmmoRemainingClient >= GetFireMode(0).ammoPerFire);
else if(bCauseAltFire)
bWeReallyShould = !bNeedToCharge && AltFireCanForceInterruptReload()
&& (reloadType == RTYPE_AUTO || !bAutoReload);
else if(bCauseNade){
bWeReallyShould = true;
nicePawn = NiceHumanPawn(nicePlayer.Pawn);
if(nicePawn == none)
bWeReallyShould = false;
else{
if(nicePawn.PlayerGrenade == none)
nicePawn.PlayerGrenade = nicePawn.FindPlayerGrenade();
if(nicePawn.PlayerGrenade == none || !nicePawn.PlayerGrenade.HasAmmo())
bWeReallyShould = false;
}
}
else if(bCauseAim)
bWeReallyShould = magAmmoRemainingClient >= GetFireMode(0).ammoPerFire && GetReloadStage() != RSTAGE_POSTREL;
else
bWeReallyShould = true;
// If this is a magazine type reload - check player's preferences
if(reloadType == RTYPE_MAG){
if(GetReloadStage() == RSTAGE_TRASH)
bWeReallyShould = true;
else if(bCauseFire || bCauseAltFire)
bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByFire;
else if(bCauseSwitch)
bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelBySwitching;
else if(cause == CANCEL_NADE)
bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByNades;
else if(bCauseAim)
bWeReallyShould = bWeReallyShould && nicePlayer.bRelCancelByAiming;
}
// Allow interrupting auto reload (by pausing) to throw a grenade
if(bAutoReload){
if(cause == CANCEL_NADE && bWeReallyShould){
bAutoReloadPaused = true;
autoReloadPauseFrame = GetCurrentAnimFrame();
}
else if(currentAutoReloadStage == RAUTOSTAGE_UNINTERRUPTIBLE)
bWeReallyShould = false;
}
// Interrupt if we really should
if(bIsReloading && bWeReallyShould){
HideMagazine(bMagazineOut);
if(bAutoReload){
bActualAllowAutoReloadSkip = bAllowAutoReloadSkip;
if(KFGameType(Level.Game) != none)
bActualAllowAutoReloadSkip = bAllowAutoReloadSkip
|| KFGameType(Level.Game).WaveNum == KFGameType(Level.Game).FinalWave;
if(bActualAllowAutoReloadSkip && bCauseSwitch && !bAutoReloadPaused){
AddAutoReloadedAmmo();
bAutoReloadRateApplied = false;
}
else if(bCauseSwitch || bCauseNade)
bAutoReloadInterrupted = true;
}
ServerStopReload();
bIsReloading = false;
bAutoReload = false;
lastEventCheckFrame = -1.0;
currentAutoReloadStage = RAUTOSTAGE_NONE;
PlayIdle();
if(bCauseNade){
if(cause == CANCEL_NADE && KFHumanPawn(Instigator) != none)
ClientThrowGrenade();
else if(cause == CANCEL_COOKEDNADE && ScrnHumanPawn(Instigator) != none)
ClientCookGrenade();
}
else if(cause == CANCEL_SWITCH)
ClientPutDown();
}
}
// Indicates if alt. fire should also interrupt reload
simulated function bool AltFireCanForceInterruptReload(){
if(FireModeClass[1] != none && FireModeClass[1] != class'KFMod.NoFire' && (NiceFire(FireMode[1]) == none || !NiceFire(FireMode[1]).bDisabled))
return true;
return false;
}
// Auxiliary functions to force certain function on client-side
simulated function bool ClientPutDown(){
return PutDown();
}
simulated function ClientThrowGrenade(){
if(KFHumanPawn(Instigator) != none)
KFHumanPawn(Instigator).ThrowGrenade();
}
simulated function ClientCookGrenade(){
if(ScrnHumanPawn(Instigator) != none)
ScrnHumanPawn(Instigator).CookGrenade();
}
// Functions that we need to reload in order to allow reload interruption on certain actions
simulated function bool PutDown(){
if(NicePlayerController(Instigator.Controller) != none)
ClientForceInterruptReload(CANCEL_SWITCH);
if(!bIsReloading)
HideMagazine(bMagazineOut);
TurnOffLaser();
return super.PutDown();
}
simulated function Fire(float F){
if(NicePlayerController(Instigator.Controller) != none)
ClientForceInterruptReload(CANCEL_FIRE);
super.Fire(F);
}
simulated function AltFire(float F){
if(NicePlayerController(Instigator.Controller) != none)
ClientForceInterruptReload(CANCEL_ALTFIRE);
Super.AltFire(F);
// Also request auto reload on alt. fire
if(!bHasSecondaryAmmo && AltFireCanForceInterruptReload() && magAmmoRemainingClient <= 0)
ServerRequestAutoReload();
else if(bHasSecondaryAmmo && !bAutoReload && FireModeClass[1] != class'KFMod.NoFire' && secondaryCharge <= 0)
ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame);
}
simulated exec function ToggleIronSights(){
if(NicePlayerController(Instigator.Controller) != none)
ClientForceInterruptReload(CANCEL_AIM);
Super.ToggleIronSights();
}
simulated exec function IronSightZoomIn(){
if(NicePlayerController(Instigator.Controller) != none)
ClientForceInterruptReload(CANCEL_AIM);
Super.IronSightZoomIn();
}
// Function for filling-up reload stages in a single reload
simulated function FillSubReloadStages(){}
simulated function UpdateSingleReloadVars(){
reloadPreEndFrame = 0.0;
if(reloadStages.Length > 0)
reloadEndFrame = reloadStages[reloadStages.Length - 1];
else
reloadEndFrame = 0.0;
reloadChargeStartFrame = -1.0;
reloadChargeEndFrame = -1.0;
bHasChargePhase = false;
}
// Function that setups all the variable for next reload; this DOES NOT start reload animation or reload itself
simulated function SetupReloadVars(optional bool bIsActive, optional int animationIndex){
if(Role == ROLE_Authority)
return;
bIsReloading = true;
bAutoReloadRateApplied = false;
bAutoReloadInterrupted = false;
bAutoReloadPaused = false;
currentAutoReloadStage = RAUTOSTAGE_UNINTERRUPTIBLE;
autoReloadPauseFrame = 0.0;
currentAutoReload = 0;
activeReloadState = ACTR_NONE;
if(bIsActive){
bAutoReload = true;
currentAutoReload = Clamp(animationIndex, 0, autoReloadsDescriptions.Length - 1);
}
}
// Reset all the necessary variables after reloading
simulated function ResetReloadVars(){
if(Role == ROLE_Authority)
return;
ServerStopReload();
bIsReloading = false;
bAutoReload = false;
bAutoReloadInterrupted = false;
currentAutoReloadStage = RAUTOSTAGE_NONE;
currentAutoReload = 0;
activeReloadState = ACTR_NONE;
ClientTryPendingWeapon();
}
// Does what 'SetAnimFrame' does + updates 'lastEventCheckFrame' for correct event handling
// Use this instead of 'SetAnimFrame', unless you have a very specific need and know what you're doing
simulated function ScrollAnim(float newFrame){
SetAnimFrame(newFrame);
lastEventCheckFrame = newFrame;
}
// Starts reload at given rate from given stage.
// If specified stage is either 'RSTAGE_NONE' or 'RSTAGE_PREREL', - start reload from the beginning
simulated function PlayReloadAnimation(float rate, ERelStage stage){
if(!HasAnim(ReloadAnim))
return;
PlayAnim(ReloadAnim, rate, 0.0);
if(stage != RSTAGE_NONE && stage != RSTAGE_PREREL){
if(stage == RSTAGE_MAINREL){
ScrollAnim(reloadMagStartFrame);
HideMagazine(false);
}
else if(stage == RSTAGE_POSTREL)
ScrollAnim(reloadChargeStartFrame);
else if(stage == RSTAGE_TRASH)
ScrollAnim(reloadChargeEndFrame);
}
}
// Resumes previously interrupted auto reload.
// Doesn't do any check for whether or not interruption took place
simulated function ResumeAutoReload(float startFrame){
local float ReloadMulti;
if(!HasAnim(autoReloadsDescriptions[currentAutoReload].animName))
return;
ReloadMulti = GetCurrentReloadMult();
PlayAnim(autoReloadsDescriptions[currentAutoReload].animName, AutoReloadBaseRate() * ReloadMulti, 0.0);
if(startFrame <= 0)
startFrame = 0.0;
ScrollAnim(startFrame);
ServerReload((1 - startFrame) * AutoReloadBaseRate() / ReloadMulti);
SetupReloadVars(true, currentAutoReload);
}
// Returns reload speed that weapon should have at the moment
simulated function float GetFittingReloadSpeed(){
return default.ReloadAnimRate * GetCurrentReloadMult();
}
// Updates current reload rate
simulated function UpdateReloadRate(){
lastRecordedReloadRate = GetFittingReloadSpeed();
if(bIsReloading)
ChangeReloadRate(lastRecordedReloadRate);
}
// Changes rate of current animation, allowing it to continue from the same frame
simulated function ChangeReloadRate(float newRate){
local name SeqName;
local float AnimFrame, AnimRate;
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(AnimFrame < 0)
AnimFrame = 0;
if(!bAutoReload && SeqName != ReloadAnim)
return;
if(bAutoReload)
PlayAnim(SeqName, newRate, 0.0);
else
PlayReloadAnimation(newRate, RSTAGE_NONE);
ScrollAnim(AnimFrame);
}
// New handler for client's reload request
// Can be called directly as a command or automatically, by intercepting ReloadMeNow command (handled in 'NiceInteraction')
exec simulated function ClientReloadMeNow(){
local float ReloadMulti;
local NicePlayerController nicePlayer;
local class<NiceVeterancyTypes> niceVet;
if(reloadType != RTYPE_AUTO && bAutoReload && !IsMagazineFull())
ClientForceInterruptReload(CANCEL_RELOAD);
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePlayer != none)
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePlayer.PlayerReplicationInfo);
bGiveObsessiveBonus = false;
if(bIsReloading)
AttemptActiveReload();
else if(niceVet.static.hasSkill(nicePlayer, class'NiceSkillSupportObsessive') && GetMagazineAmmo() >= float(MagCapacity) * class'NiceSkillSupportObsessive'.default.reloadLevel)
bGiveObsessiveBonus = true;
if(!AllowReload())
return;
if(reloadType == RTYPE_AUTO)
ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame);
else{
SetupReloadVars();
if(bHasAimingMode && bAimingRifle && !bAutoReload){
FireMode[1].bIsFiring = false;
ZoomOut(false);
ServerZoomOut(false);
}
subReloadStage = 0;
ReloadMulti = GetCurrentReloadMult();
if(bMagazineOut && reloadMagStartFrame >= 0){
ServerReload((1 - reloadMagStartFrame) * default.ReloadRate / ReloadMulti);
PlayReloadAnimation(default.ReloadAnimRate * ReloadMulti, RSTAGE_MAINREL);
}
else{
ServerReload(default.ReloadRate / ReloadMulti);
PlayReloadAnimation(default.ReloadAnimRate * ReloadMulti, RSTAGE_NONE);
}
}
}
// Tells server that reload was started and how long should it take
function ServerReload(float duration, optional bool autoReload){
bIsReloading = true;
Instigator.SetAnimAction(WeaponReloadAnim);
ReloadDeadLine = Level.TimeSeconds + duration;
if(bHasAimingMode && bAimingRifle && !autoReload)
FireMode[1].bIsFiring = false;
if(Level.Game.NumPlayers > 1 && KFGameType(Level.Game).bWaveInProgress && KFPlayerController(Instigator.Controller) != none &&
Level.TimeSeconds - KFPlayerController(Instigator.Controller).LastReloadMessageTime > KFPlayerController(Instigator.Controller).ReloadMessageDelay){
KFPlayerController(Instigator.Controller).Speech('AUTO', 2, "");
KFPlayerController(Instigator.Controller).LastReloadMessageTime = Level.TimeSeconds;
}
}
// Shift deadline of reload on server
function ServerShiftReloadTime(float delta){
if(bIsReloading)
ReloadDeadLine += delta;
}
// Forces reload to stop on server
function ServerStopReload(){
bIsReloading = false;
}
// Reloaded to implement new reload mechanism
simulated function WeaponTick(float dt){
local int i;
local name SeqName;
local float ReloadMulti;
local float AnimFrame, AnimRate;
local ERelStage newStage;
if(lastRecordedReloadRate != GetFittingReloadSpeed())
UpdateReloadRate();
// Resume charging magazine weapon next time we can
if(bNeedToCharge && GetReloadStage() == RSTAGE_NONE){
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(SeqName == IdleAnim){
ReloadMulti = GetCurrentReloadMult();
if(Role == ROLE_Authority)
ServerReload((1 - reloadChargeStartFrame) * default.ReloadRate / ReloadMulti);
else{
SetupReloadVars();
PlayReloadAnimation(ReloadAnimRate * ReloadMulti, RSTAGE_POSTREL);
}
}
}
// Resume auto reload from pause next time we can
if(bAutoReloadPaused && Role < ROLE_Authority && ClientGrenadeState == GN_NONE){
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(SeqName == IdleAnim)
ResumeAutoReload(autoReloadPauseFrame);
}
// Resume reloading in case auto reload was interrupted as soon as possible
if(bAutoReloadInterrupted && Role < ROLE_Authority && ClientGrenadeState == GN_none){
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(SeqName == IdleAnim)
ResumeAutoReload(autoReloadsDescriptions[currentAutoReload].resumeFrame);
}
// Try and detect when animation that should trigger auto reload starts
if(autoReloadsDescriptions.Length > 0 && currentAutoReloadStage == RAUTOSTAGE_NONE && Role < ROLE_Authority){
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
for(i = 0;i < autoReloadsDescriptions.Length;i ++)
if(SeqName == autoReloadsDescriptions[i].animName && AnimFrame < autoReloadsDescriptions[i].trashStartFrame){
// Since animation is already running - all we need to do is to setup appropriate variables
SetupReloadVars(true, i);
break;
}
}
// Random TWI's code block appears!
if(bHasAimingMode && bForceLeaveIronsights)
if(!shouldLeaveIronsight())
bForceLeaveIronsights = false;
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;
}
}
// We want to be up to date on this one
UpdateMagCapacity(Instigator.PlayerReplicationInfo);
// Next we have 3 possibilities.
if(Role == ROLE_Authority){
// 1. We're on the server. Then just check if we've reached reload deadline.
if(Level.TimeSeconds >= ReloadDeadLine)
bIsReloading = false;
}
else{
if(bAutoReload)
// 2. It's auto reload. Handle it in it's own tick function.
AutoReloadTick();
else if(Role < ROLE_Authority){
// 3. It's not. Then just handle stage swapping for magazine ('goThroughStages' function) and single reloads ('goThroughSubStages' function)
newStage = GetReloadStage();
if(reloadType == RTYPE_SINGLE && newStage == RSTAGE_MAINREL)
goThroughSubStages();
if(currentRelStage != newStage)
goThroughStages(currentRelStage, newStage);
}
if(Role < ROLE_Authority){
// Call events
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(newStage == RSTAGE_NONE && !bAutoReload)
AnimFrame = 1.0;
if(newStage != RSTAGE_NONE || lastEventCheckFrame > 0 || bAutoReload)
HandleReloadEvents(lastEventCheckFrame, AnimFrame);
if(newStage == RSTAGE_NONE && !bAutoReload)
lastEventCheckFrame = -1.0;
}
}
// Some other TWI's code leftovers
if((Level.NetMode == NM_Client) || Instigator == none || KFFriendlyAI(Instigator.Controller) == none && Instigator.PlayerReplicationInfo == none)
return;
// Turn it off on death / battery expenditure
if(FlashLight != none){
// Keep the 1P weapon client beam up to date.
AdjustLightGraphic();
if(FlashLight.bHasLight){
if(Instigator.Health <= 0 || KFHumanPawn(Instigator).TorchBatteryLife <= 0 || Instigator.PendingWeapon != none ){
KFHumanPawn(Instigator).bTorchOn = false;
ServerSpawnLight();
}
}
}
}
simulated function HandleReloadEvents(float oldFrame, float newFrame){
local int i;
local float currEventFrame;
// Old reload ended and new started between checks somehow, no point in it, but try to fix that
if(oldFrame > newFrame){
if(oldFrame < 1.0)
HandleReloadEvents(oldFrame, 1.0);
if(newFrame > 0.0)
HandleReloadEvents(0.0, newFrame);
return;
}
for(i = 0;i < relEvents.Length;i ++){
currEventFrame = relEvents[i].eventFrame;
if(oldFrame < currEventFrame && newFrame >= currEventFrame)
ReloadEvent(relEvents[i].eventName);
}
if(newFrame > lastEventCheckFrame)
lastEventCheckFrame = newFrame;
}
simulated function ReloadEvent(string eventName){}
// This function is called each tick while auto reload is active
simulated function AutoReloadTick(){
local bool bFinishAutoReload;
local name SeqName;
local float AnimFrame, AnimRate;
bFinishAutoReload = false;
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
// Apply reload rate to the animation's speed when it's time
if(AnimFrame > autoReloadsDescriptions[currentAutoReload].speedFrame && !bAutoReloadRateApplied){
ChangeReloadRate(AutoReloadBaseRate() * GetCurrentReloadMult());
bAutoReloadRateApplied = true;
}
// Change state to interruptible and set reload to finish if there's no ammo
if(AnimFrame > autoReloadsDescriptions[currentAutoReload].canInterruptFrame){
if(autoReloadAmmo() > 0){
currentAutoReloadStage = RAUTOSTAGE_INTERRUPTIBLE;
ClientTryPendingWeapon();
}
else{
bFinishAutoReload = true;
PlayIdle();
}
}
// Might as well end reload when we enter trash stage (or when animation is finished)
if(SeqName != autoReloadsDescriptions[currentAutoReload].animName || AnimFrame > autoReloadsDescriptions[currentAutoReload].trashStartFrame){
bFinishAutoReload = true;
AddAutoReloadedAmmo();
}
// Finish the reload as asked
if(bFinishAutoReload)
ResetReloadVars();
}
simulated function float AutoReloadBaseRate(){
if(reloadType == RTYPE_AUTO && FireModeClass[0] != none
&& (!bHasSecondaryAmmo || FireMode[1] != Class'KFMod.NoFire'))
return FireModeClass[0].default.FireAnimRate;
return FireModeClass[1].default.FireAnimRate;
}
// Called when current state of reload changes; only called on client side
simulated function ReloadChangedStage(ERelStage prevStage, ERelStage newStage){
local NiceHumanPawn nicePawn;
local class<NiceVeterancyTypes> niceVet;
nicePawn = NiceHumanPawn(Instigator);
if(nicePawn != none)
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
// Reload was canceled, so all that is meaningless
if(!bIsReloading)
return;
if(reloadType == RTYPE_MAG){
if(newStage == RSTAGE_MAINREL){
bNeedToCharge = false;
ServerSetCharging(bNeedToCharge);
bMagazineOut = true;
MagAmmoRemainingClient = 0;
if(bHasChargePhase && bRoundInChamber)
MagAmmoRemainingClient ++;
ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
}
else if(newStage == RSTAGE_POSTREL){
bMagazineOut = false;
HideMagazine(false);
if(bHasChargePhase){
if(bRoundInChamber){
bNeedToCharge = false;
PlayIdle();
}
else
bNeedToCharge = true;
}
ServerSetCharging(bNeedToCharge);
}
else if(newStage == RSTAGE_TRASH && (!bHasChargePhase || bRoundInChamber)){
bMagazineOut = false;
HideMagazine(false);
}
else if(newStage == RSTAGE_NONE)
ResetReloadVars();
if(prevStage == RSTAGE_MAINREL && newStage != RSTAGE_NONE)
AddReloadedAmmo();
if(prevStage == RSTAGE_POSTREL && newStage == RSTAGE_TRASH){
bNeedToCharge = false;
bRoundInChamber = true;
ServerSetCharging(bNeedToCharge);
ServerSetMagSize(MagAmmoRemainingClient, bRoundInChamber, Level.TimeSeconds);
}
if(newStage == RSTAGE_TRASH)
ClientTryPendingWeapon();
}
else if(reloadType == RTYPE_SINGLE){
if(newStage == RSTAGE_NONE)
ResetReloadVars();
if(prevStage == RSTAGE_MAINREL && bIsReloading)
goThroughSubStages(true);
}
}
// Return current magazine (also has limited use for single reload) stage of reload
simulated function ERelStage GetReloadStage(){
local name SeqName;
local float AnimFrame, AnimRate;
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(SeqName != ReloadAnim)
return RSTAGE_NONE;
if(AnimFrame < reloadPreEndFrame)
return RSTAGE_PREREL;
if(AnimFrame < reloadEndFrame)
return RSTAGE_MAINREL;
if(AnimFrame < reloadChargeEndFrame && bHasChargePhase)
return RSTAGE_POSTREL;
return RSTAGE_TRASH;
}
// Returns next magazine reload stage
simulated function ERelStage GetNextReloadStage(ERelStage curr){
local byte i;
i = curr;
i ++;
curr = ERelStage(i);
if(curr == RSTAGE_POSTREL && !bHasChargePhase)
curr = RSTAGE_TRASH;
return curr;
}
// Function that goes between given 'prev' and 'next' stages by passing every intermediate stage and calling 'ReloadChangedStage'
simulated function GoThroughStages(ERelStage prev, ERelStage next){
local ERelStage theEnum, limitStage;
if(prev < next || next == RSTAGE_NONE){
theEnum = prev;
if(next == RSTAGE_NONE)
limitStage = RSTAGE_TRASH;
else
limitStage = next;
while(theEnum < limitStage){
theEnum = GetNextReloadStage(theEnum);
ReloadChangedStage(currentRelStage, theEnum);
currentRelStage = theEnum;
}
}
if(prev > next){
ReloadChangedStage(prev, next);
currentRelStage = next;
}
}
simulated function GoThroughSubStages(optional bool bReloadEnded){
local float AnimFrame;
AnimFrame = GetCurrentAnimFrame();
// Conditions: 1. Is this valid stage?
// 2, 3. Can we even load more ammo?
while(subReloadStage < reloadStages.Length && MagAmmoRemainingClient < MagCapacity && AmmoAmount(0) - MagAmmoRemainingClient > 0)
if(bReloadEnded || AnimFrame > reloadStages[subReloadStage]){
AddReloadedAmmo();
subReloadStage ++;
}
else break;
// If reload hasn't ended, we can only load one more shell, but aren't yet at animation's end - scroll animation
// 'subReloadStage' shouldn't be zero at this point, but just in case check
// During reload client dictates size of the magazine
if(subReloadStage > 0 && !bReloadEnded && subReloadStage != reloadStages.Length && (MagAmmoRemainingClient >= MagCapacity || MagAmmoRemainingClient - AmmoAmount(0) >= 0)){
if(alwaysPlayAnimEnd){
AnimFrame = reloadStages[reloadStages.Length - 1] + AnimFrame - reloadStages[subReloadStage - 1];
ScrollAnim(AnimFrame); // Current animation position - previous ammo load position
}
else
PlayIdle();
subReloadStage = reloadStages.Length - 1;
}
}
//Auzilary unction for easy initial generation of sub reload stages for single reload
simulated function GenerateReloadStages(int stagesAmount, int framesAmount, int firstLoadFrame, int loadDelta){
local int i;
local int frame;
local float convFrame;
reloadStages.Length = 0;
frame = firstLoadFrame;
for(i = 0;i < stagesAmount;i ++){
// Next load time
convFrame = float(frame) / float(framesAmount);
reloadStages[reloadStages.Length] = convFrame;
// Shift to the next one
frame += loadDelta;
}
}
simulated function HideMagazine(bool bHide){
if(magazineBone == '')
return;
if(bHide)
SetBoneScale(0, 0.0, magazineBone);
else
SetBoneScale(0, 1.0, magazineBone);
}
simulated function float GetCurrentAnimFrame(){
local name SeqName;
local float AnimFrame, AnimRate;
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
return AnimFrame;
}
simulated function BringUp(optional Weapon PrevWeapon){
// Change HUD icons if necessary
if(Role < ROLE_Authority){
if(bChangeClipIcon && hudClipTexture != none)
HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).ClipsIcon.WidgetTexture = hudClipTexture;
else
HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).ClipsIcon.WidgetTexture =
class'ScrnHUD'.default.ClipsIcon.WidgetTexture;
if(bChangeBulletsIcon && hudBulletsTexture != none)
HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).BulletsInClipIcon.WidgetTexture =
hudBulletsTexture;
else
HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).BulletsInClipIcon.WidgetTexture =
class'ScrnHUD'.default.BulletsInClipIcon.WidgetTexture;
if(bChangeSecondaryIcon && hudSecondaryTexture != none)
HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).SecondaryClipsIcon.WidgetTexture =
hudSecondaryTexture;
else
HUDKillingFloor(NicePlayerController(instigator.controller).myHUD).SecondaryClipsIcon.WidgetTexture =
class'ScrnHUD'.default.SecondaryClipsIcon.WidgetTexture;
}
HideMagazine(bMagazineOut);
super.BringUp(PrevWeapon);
ApplyLaserState();
}
function ServerSetCharging(bool bNewNeedToCharge){
bNeedToCharge = bNewNeedToCharge;
}
// Function that's supposed to return current amount of ammo that's used for auto reload for this weapon
simulated function int AutoReloadAmmo(){
if(FireModeClass[1] == class'KFMod.NoFire')
return AmmoAmount(0);
else
return AmmoAmount(1);
}
simulated function int GetMagazineAmmo(){
if(Role < ROLE_Authority)
return MagAmmoRemainingClient;
else
return MagAmmoRemaining;
}
simulated function bool AllowReload(){
local int actualMagSize;
actualMagSize = GetMagazineAmmo();
if(bHasChargePhase && bRoundInChamber)
actualMagSize --;
UpdateMagCapacity(Instigator.PlayerReplicationInfo);
if(FireMode[0].IsFiring() || FireMode[1].IsFiring() ||
bIsReloading || IsMagazineFull() ||
ClientState == WS_BringUp )
return false;
return true;
}
simulated function bool IsMagazineFull(){
local int totalMagSize, actualMagSize;
totalMagSize = GetMagazineAmmo();
actualMagSize = totalMagSize;
if(bHasChargePhase && bRoundInChamber)
actualMagSize --;
return (actualMagSize >= MagCapacity || totalMagSize >= AmmoAmount(0));
}
exec function ReloadMeNow(){
local NicePlayerController nicePlayer;
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePlayer != none && nicePlayer.bFlagUseServerReload)
ClientReloadMeNow();
}
simulated function float GetCurrentReloadMult(){
local float ReloadMulti;
local float actualActiveSpeedUp;
local float timeDilationSpeedup;
local NiceHumanPawn nicePawn;
local NicePlayerController nicePlayer;
local class<NiceVeterancyTypes> niceVet;
nicePawn = NiceHumanPawn(Instigator);
nicePlayer = NicePlayerController(Instigator.Controller);
if(nicePawn != none)
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
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;
if(bGiveObsessiveBonus)
ReloadMulti *= class'NiceSkillSupportObsessive'.default.reloadBonus;
// Active reload speedup
actualActiveSpeedUp = activeSpeedup;
if (nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoQuickerMags')) {
actualActiveSpeedUp *= 2;
}
if(activeReloadState == ACTR_SUCCESS)
ReloadMulti *= actualActiveSpeedUp;
else if(activeReloadState == ACTR_FAIL)
ReloadMulti *= activeSlowdown;
else if(bCanActiveReload && reloadType == RTYPE_SINGLE && subReloadStage == 0)
ReloadMulti *= actualActiveSpeedUp;
if(nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoZEDProfessional')) {
timeDilationSpeedup = (1.1 / Level.TimeDilation);
}
else {
timeDilationSpeedup = ((1.1 / Level.TimeDilation) - 1.0) * 0.5 + 1.0;
timeDilationSpeedup = FMax(1.0, timeDilationSpeedup);
}
if(nicePlayer != none && niceVet.static.hasSkill(nicePlayer, class'NiceSkillCommandoZEDProfessional'))
ReloadMulti *= timeDilationSpeedup;
if(bAutoReload && bAutoReloadRateApplied)
ReloadMulti *= autoReloadSpeedModifier;
return ReloadMulti;
}
function ServerRequestAutoReload(){
ClientReloadMeNow();
}
simulated function AttemptActiveReload(optional bool bForce){
local float windowStart;
local float windowLenght;
local name SeqName;
local float AnimFrame, AnimRate;
local NiceHumanPawn nicePawn;
local class<NiceVeterancyTypes> niceVet;
// Need this for skill check
nicePawn = NiceHumanPawn(Instigator);
if(nicePawn != none)
niceVet = class'NiceVeterancyTypes'.static.GetVeterancy(nicePawn.PlayerReplicationInfo);
// Does nothing if we aren't even reloading, this is auto reload, we've already succeeded/failed or we'll auto succeed anyway
if(!bIsReloading || !bCanActiveReload || bAutoReload || activeReloadState == ACTR_SUCCESS || activeReloadState == ACTR_FAIL)
return;
// Find starting frame and length of the active reload window (and declare fail if single reload is still in the first sub-stage)
windowStart = -1.0;
if(reloadType == RTYPE_MAG){
windowStart = reloadPreEndFrame;
windowLenght = activeWindow;
}
else if(reloadType == RTYPE_SINGLE){
// Too early!
if(subReloadStage <= 0){
activeReloadState = ACTR_FAIL;
UpdateReloadRate();
return;
}
windowStart = reloadStages[subReloadStage - 1];
windowLenght = activeWindow / MagCapacity;
}
// Something went wrong and active reload is inapplicable
if(windowStart < 0)
return;
GetAnimParams(0, SeqName, AnimFrame, AnimRate);
if(windowStart <= AnimFrame && AnimFrame <= windowStart + windowLenght || bForce)
activeReloadState = ACTR_SUCCESS;
else
activeReloadState = ACTR_FAIL;
UpdateReloadRate();
}
// Function that's called when client tries to use flashlight on a weapon
simulated function SecondDoToggle(){}
simulated function ClientReload(){}
simulated function bool InterruptReload(){
return false;
}
function ServerStopFire(byte Mode){
super(BaseKFWeapon).ServerStopFire(Mode);
}
simulated function ClientTryPendingWeapon(){
if(Instigator.PendingWeapon != none && Instigator.PendingWeapon != self)
Instigator.Controller.ClientSwitchToBestWeapon();
}
simulated function AnimEnd(int channel){
local name anim;
local float frame, rate;
GetAnimParams(0, anim, frame, rate);
if(!FireMode[0].IsInState('FireLoop')){
GetAnimParams(0, anim, frame, rate);
if(ClientState == WS_ReadyToFire)
if((FireMode[0] == none || !FireMode[0].bIsFiring) && (FireMode[1] == none || !FireMode[1].bIsFiring))
PlayIdle();
}
else if(ClientState == WS_ReadyToFire){
if(anim == FireMode[0].FireAnim && HasAnim(FireMode[0].FireEndAnim))
PlayAnim(FireMode[0].FireEndAnim, FireMode[0].FireEndAnimRate, 0.0);
else if (anim== FireMode[1].FireAnim && HasAnim(FireMode[1].FireEndAnim))
PlayAnim(FireMode[1].FireEndAnim, FireMode[1].FireEndAnimRate, 0.0);
else if ((FireMode[0] == none || !FireMode[0].bIsFiring) && (FireMode[1] == none || !FireMode[1].bIsFiring) && !bAutoReloadPaused)
PlayIdle();
}
}
simulated function bool StartFire(int Mode){
if(NiceHighROFFire(FireMode[Mode]) == none || FireMode[Mode].bWaitForRelease)
return super.StartFire(Mode);
if(!super.StartFire(Mode))
return false;
if(AmmoAmount(0) <= 0)
return false;
AnimStopLooping();
if(!FireMode[Mode].IsInState('FireLoop') && (AmmoAmount(0) > 0)){
FireMode[Mode].StartFiring();
return true;
}
else
return false;
return true;
}
simulated event OnZoomOutFinished(){
local name anim;
local float frame, rate;
GetAnimParams(0, anim, frame, rate);
if(!FireMode[0].IsInState('FireLoop'))
super.OnZoomOutFinished();
else if(ClientState == WS_ReadyToFire){
// Play the regular idle anim when we're finished zooming out
if(anim == IdleAimAnim)
PlayIdle();
// Switch looping fire anims if we switched to/from zoomed
else if( FireMode[0].IsInState('FireLoop') && anim == 'Fire_Iron_Loop')
LoopAnim('Fire_Loop', FireMode[0].FireLoopAnimRate, FireMode[0].TweenTime);
}
}
simulated event OnZoomInFinished(){
local name anim;
local float frame, rate;
GetAnimParams(0, anim, frame, rate);
if(!FireMode[0].IsInState('FireLoop'))
super.OnZoomInFinished();
else if(ClientState == WS_ReadyToFire){
// Play the iron idle anim when we're finished zooming in
if(anim == IdleAnim)
PlayIdle();
// Switch looping fire anims if we switched to/from zoomed
else if( FireMode[0].IsInState('FireLoop') && anim == 'Fire_Loop' )
LoopAnim('Fire_Iron_Loop', FireMode[0].FireLoopAnimRate, FireMode[0].TweenTime);
}
}
// Some functions reloaded to force update of magazine size on client's side
function GiveAmmo(int m, WeaponPickup WP, bool bJustSpawned){
super.GiveAmmo(m, WP, bJustSpawned);
ClientSetMagSize(MagAmmoRemaining, bRoundInChamber);
}
simulated function GiveTo(Pawn other, optional Pickup Pickup){
local int actualMagSize;
local NiceWeaponPickup niceWeapPickup;
local NicePlainData.Data dummyData;
niceWeapPickup = NiceWeaponPickup(Pickup);
if(niceWeapPickup != none)
SetNiceData(niceWeapPickup.GetNiceData(), NiceHumanPawn(other));
else
SetNiceData(dummyData, NiceHumanPawn(other));
if(Role == ROLE_Authority){
UpdateMagCapacity(other.PlayerReplicationInfo);
if(NiceWeaponPickup(Pickup) != none)
actualMagSize = NiceWeaponPickup(Pickup).MagAmmoRemaining;
if(bRoundInChamber && actualMagSize > 0)
actualMagSize --;
if(NiceWeaponPickup(Pickup) != none && Pickup.bDropped)
actualMagSize = Clamp(actualMagSize, 0, MagCapacity);
else
actualMagSize = MagCapacity;
MagAmmoRemaining = actualMagSize;
if(bRoundInChamber)
MagAmmoRemaining ++;
super(BaseKFWeapon).GiveTo(other, Pickup);
ClientSetMagSize(MagAmmoRemaining, bRoundInChamber);
}
}
function NicePlainData.Data GetNiceData(){
local NicePlainData.Data transferData;
if(LaserType > 0)
class'NicePlainData'.static.SetInt(transferData, "LaserType", int(LaserType));
class'NicePlainData'.static.SetBool(transferData, "ChamberedRound", bRoundInChamber);
class'NicePlainData'.static.SetInt(transferData, "ChargeAmount", secondaryCharge);
return transferData;
}
function SetNiceData(NicePlainData.Data transferData, optional NiceHumanPawn newOwner){
local int newLaserType;
newLaserType = class'NicePlainData'.static.GetInt(transferData, "LaserType", -1);
if(newLaserType >= 0)
ClientSetLaserType(byte(newLaserType));
bRoundInChamber = class'NicePlainData'.static.GetBool(transferData, "ChamberedRound", false);
secondaryCharge = class'NicePlainData'.static.GetInt(transferData, "ChargeAmount", 1);
ClientSetSndCharge(secondaryCharge);
}
simulated function ApplyLaserState(){
bLaserActive = LaserType > 0;
if(Role < ROLE_Authority)
ServerSetLaserType(LaserType);
if(NiceAttachment(ThirdPersonActor) != none)
NiceAttachment(ThirdPersonActor).SetLaserType(LaserType);
if(!Instigator.IsLocallyControlled())
return;
if(bLaserActive){
if(LaserDot == none)
LaserDot = Spawn(LaserDotClass, self);
LaserDot.SetLaserType(LaserType);
if(altLaserAttachmentBone != ''){
if(altLaserDot == none)
altLaserDot = Spawn(LaserDotClass, self);
altLaserDot.SetLaserType(LaserType);
}
//spawn 1-st person laser attachment for weapon owner
if(LaserAttachment == none){
SetBoneRotation(LaserAttachmentBone, LaserAttachmentRotation);
LaserAttachment = Spawn(LaserAttachmentClass,,,,);
AttachToBone(LaserAttachment, LaserAttachmentBone);
if(LaserAttachment != none)
LaserAttachment.SetRelativeLocation(LaserAttachmentOffset);
}
if(altLaserAttachment == none && altLaserAttachmentBone != ''){
SetBoneRotation(altLaserAttachmentBone, altLaserAttachmentRotation);
altLaserAttachment = Spawn(LaserAttachmentClass,,,,);
AttachToBone(altLaserAttachment, altLaserAttachmentBone);
if(altLaserAttachment != none)
altLaserAttachment.SetRelativeLocation(altLaserAttachmentOffset);
}
ConstantColor'ScrnTex.Laser.LaserColor'.Color = LaserDot.GetLaserColor();
LaserAttachment.bHidden = false;
altLaserAttachment.bHidden = false;
}
else{
if(LaserAttachment != none)
LaserAttachment.bHidden = true;
if(altLaserAttachment != none)
altLaserAttachment.bHidden = true;
if(LaserDot != none)
LaserDot.Destroy();
if(altLaserDot != none)
altLaserDot.Destroy();
}
}
simulated function ToggleLaser(){
if(!Instigator.IsLocallyControlled())
return;
// Will redo this bit later, but so far it'll have to do
if(LaserType == 0)
LaserType = 1;
else if(LaserType == 1)
LaserType = 4;
else if(LaserType == 4)
LaserType = 2;
else
LaserType = 0;
ApplyLaserState();
}
simulated function TurnOffLaser(){
if(!Instigator.IsLocallyControlled())
return;
if(Role < ROLE_Authority)
ServerSetLaserType(0);
bLaserActive = false;
if(LaserAttachment != none)
LaserAttachment.bHidden = true;
if(altLaserAttachment != none)
altLaserAttachment.bHidden = true;
if(LaserDot != none)
LaserDot.Destroy();
if(altLaserDot != none)
altLaserDot.Destroy();
}
function ServerSetLaserType(byte NewLaserType){
LaserType = NewLaserType;
bLaserActive = NewLaserType > 0;
if(NiceAttachment(ThirdPersonActor) != none)
NiceAttachment(ThirdPersonActor).SetLaserType(LaserType);
}
simulated function ClientSetLaserType(byte NewLaserType){
LaserType = NewLaserType;
bLaserActive = NewLaserType > 0;
ApplyLaserState();
}
simulated function NiceFire GetMainFire(){
return NiceFire(FireMode[0]);
}
function ServerSetSndCharge(int newCharge){
secondaryCharge = newCharge;
}
simulated function ClientSetSndCharge(int newCharge){
secondaryCharge = newCharge;
}
simulated function RenderOverlays(Canvas Canvas){
local int i;
local Vector StartTrace, EndTrace;
local Vector HitLocation, HitNormal;
local Actor Other;
local vector X,Y,Z;
local coords C;
local array<Actor> HitActors;
if(Instigator == none)
return;
if(Instigator.Controller != none)
Hand = Instigator.Controller.Handedness;
if((Hand < -1.0) || (Hand > 1.0))
return;
for(i = 0; i < NUM_FIRE_MODES;++ i)
if(FireMode[i] != none)
FireMode[i].DrawMuzzleFlash(Canvas);
SetLocation(Instigator.Location + Instigator.CalcDrawOffset(self));
SetRotation(Instigator.GetViewRotation() + ZoomRotInterp);
// Handle drawing the laser dots
if(LaserDot != none){
if(bIsReloading || bAllowFreeDot){
C = GetBoneCoords(LaserAttachmentBone);
X = C.XAxis;
Y = C.YAxis;
Z = C.ZAxis;
}
else
GetViewAxes(X, Y, Z);
StartTrace = Instigator.Location + Instigator.EyePosition();
EndTrace = StartTrace + 65535 * X;
while(true){
Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
if(ROBulletWhipAttachment(Other) != none){
HitActors[HitActors.Length] = Other;
Other.SetCollision(false);
StartTrace = HitLocation + X;
}
else{
if(other != none && Other != Instigator && Other.Base != Instigator)
EndBeamEffect = HitLocation;
else
EndBeamEffect = EndTrace;
break;
}
}
// restore collision
for(i = 0; i<HitActors.Length;++ i)
HitActors[i].SetCollision(true);
LaserDot.SetLocation(EndBeamEffect - X*LaserDot.ProjectorPullback);
if(Pawn(Other) != none){
LaserDot.SetRotation(Rotator(X));
LaserDot.SetDrawScale(LaserDot.default.DrawScale * 0.5);
}
else if(HitNormal == vect(0,0,0)){
LaserDot.SetRotation(Rotator(-X));
LaserDot.SetDrawScale(LaserDot.default.DrawScale);
}
else{
LaserDot.SetRotation(Rotator(-HitNormal));
LaserDot.SetDrawScale(LaserDot.default.DrawScale);
}
}
if(altLaserDot != none){
if(bIsReloading || bAllowFreeDot){
C = GetBoneCoords(altLaserAttachmentBone);
X = C.XAxis;
Y = C.YAxis;
Z = C.ZAxis;
}
else
GetViewAxes(X, Y, Z);
StartTrace = Instigator.Location + Instigator.EyePosition();
EndTrace = StartTrace + 65535 * X;
while(true){
Other = Trace(HitLocation, HitNormal, EndTrace, StartTrace, true);
if(ROBulletWhipAttachment(Other) != none){
HitActors[HitActors.Length] = Other;
Other.SetCollision(false);
StartTrace = HitLocation + X;
}
else{
if(other != none && Other != Instigator && Other.Base != Instigator)
EndBeamEffect = HitLocation;
else
EndBeamEffect = EndTrace;
break;
}
}
// restore collision
for(i = 0; i<HitActors.Length;++ i)
HitActors[i].SetCollision(true);
altLaserDot.SetLocation(EndBeamEffect - X*altLaserDot.ProjectorPullback);
if(Pawn(Other) != none){
altLaserDot.SetRotation(Rotator(X));
altLaserDot.SetDrawScale(altLaserDot.default.DrawScale * 0.5);
}
else if(HitNormal == vect(0,0,0)){
altLaserDot.SetRotation(Rotator(-X));
altLaserDot.SetDrawScale(altLaserDot.default.DrawScale);
}
else{
altLaserDot.SetRotation(Rotator(-HitNormal));
altLaserDot.SetDrawScale(altLaserDot.default.DrawScale);
}
}
bDrawingFirstPerson = true;
Canvas.DrawActor(self, false, false, DisplayFOV);
bDrawingFirstPerson = false;
}
simulated function ZoomIn(bool bAnimateTransition){
default.ZoomTime = default.recordedZoomTime;
PlayerIronSightFOV = default.PlayerIronSightFOV;
if(class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillSharpshooterControl')){
default.ZoomTime *= class'NiceSkillSharpshooterControl'.default.zoomSpeedBonus;
if(instigator != none && instigator.bIsCrouched)
PlayerIronSightFOV *= class'NiceSkillSharpshooterControl'.default.zoomBonus;
}
super.ZoomIn(bAnimateTransition);
}
simulated function ZoomOut(bool bAnimateTransition){
default.ZoomTime = default.recordedZoomTime;
PlayerIronSightFOV = default.PlayerIronSightFOV;
if(class'NiceVeterancyTypes'.static.hasSkill(NicePlayerController(Instigator.Controller), class'NiceSkillSharpshooterControl')){
default.ZoomTime *= class'NiceSkillSharpshooterControl'.default.zoomSpeedBonus;
PlayerIronSightFOV *= class'NiceSkillSharpshooterControl'.default.zoomBonus;
}
super.ZoomOut(bAnimateTransition);
}
simulated function Destroyed(){
if(LaserDot != none)
LaserDot.Destroy();
if(altLaserDot != none)
altLaserDot.Destroy();
if(LaserAttachment != none)
LaserAttachment.Destroy();
if(altLaserAttachment != none)
altLaserAttachment.Destroy();
super(KFWeapon).Destroyed();
}
defaultproperties
{
recordedZoomTime=-1.000000
SecondaryCharge=1
LaserAttachmentClass=Class'ScrnBalanceSrv.ScrnLaserAttachmentFirstPerson'
LaserDotClass=Class'ScrnBalanceSrv.ScrnLocalLaserDot'
LaserAttachmentBone="LightBone"
MagazineBone="Magazine"
bHasChargePhase=True
autoReloadSpeedModifier=1.000000
bCanActiveReload=True
activeSlowdown=0.850000
activeSpeedup=1.150000
activeWindow=0.060000
bModeZeroCanDryFire=True
}