1597 lines
70 KiB
Ucode
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'NiceSkillSharpshooterHardWork')){
|
|
default.ZoomTime *= class'NiceSkillSharpshooterHardWork'.default.zoomSpeedBonus;
|
|
if(instigator != none && instigator.bIsCrouched)
|
|
PlayerIronSightFOV *= class'NiceSkillSharpshooterHardWork'.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'NiceSkillSharpshooterHardWork')){
|
|
default.ZoomTime *= class'NiceSkillSharpshooterHardWork'.default.zoomSpeedBonus;
|
|
PlayerIronSightFOV *= class'NiceSkillSharpshooterHardWork'.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
|
|
}
|