1
0
KF2-Dev-Scripts/Engine/Classes/Weapon.uc
2020-12-13 18:01:13 +03:00

1958 lines
53 KiB
Ucode

/**
* Base Weapon implementation.
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class Weapon extends Inventory
native
abstract
config(game)
notplaceable;
/************************************************************************************
* Firing Mode Definition
***********************************************************************************/
/**
* This enum defines the firing type for the weapon.
* EWFT_InstantHit - The weapon traces a shot to determine a hit and immediately causes an effect
* EWFT_Projectile - The weapon spawns a new projectile pointed at the crosshair
* EWFT_Custom - Requires a custom fire sequence
*/
enum EWeaponFireType
{
EWFT_InstantHit,
EWFT_Projectile,
EWFT_Custom,
EWFT_None
};
/** Current FireMode*/
var byte CurrentFireMode;
/** Array of firing states defining available firemodes */
var Array<Name> FiringStatesArray;
/** Defines the type of fire (see Enum above) for each mode */
var Array<EWeaponFireType> WeaponFireTypes;
/** The Class of Projectile to spawn */
var Array< class<Projectile> > WeaponProjectiles;
/** Holds the amount of time a single shot takes */
var() Array<float> FireInterval;
/** How much of a spread between shots */
var() Array<float> Spread;
/** How much damage does a given instanthit shot do */
var() Array<float> InstantHitDamage;
/** momentum transfer scaling for instant hit damage */
var() Array<float> InstantHitMomentum;
/** DamageTypes for Instant Hit Weapons */
var Array< class<DamageType> > InstantHitDamageTypes;
/************************************************************************************
* Firing / Timing / States
***********************************************************************************/
/** How long does it take to Equip this weapon */
var() float EquipTime;
/** How long does it take to put this weapon down */
var() float PutDownTime;
/** Holds an offest for spawning protectile effects. */
var() vector FireOffset;
/** Set to put weapon down at the end of a state. Typically used to change weapons on state changes (weapon up, stopped firing...) */
var bool bWeaponPutDown;
/** Range of Weapon, used for Traces (InstantFire, ProjectileFire, AdjustAim...) */
var() float WeaponRange;
/*********************************************************************************************
* Mesh / Skins / Effects
********************************************************************************************* */
/** Weapon Mesh */
var() editinline MeshComponent Mesh;
/** When no duration is specified, speed to play anims. */
var() float DefaultAnimSpeed;
/*********************************************************************************************
* Inventory Grouping/etc.
********************************************************************************************* */
/** Configurable weapon priority. Ties (mod weapons) are broken by GroupWeight */
var config float Priority;
/** Can player toss his weapon out? Typically false for default inventory. */
var bool bCanThrow;
/** Set from ClientWeaponSet() when it has to go through PendingClientWeaponSet, to preserve those variables. */
var bool bWasOptionalSet;
/** Set from ClientWeaponSet() when it has to go through PendingClientWeaponSet, to preserve those variables. */
var bool bWasDoNotActivate;
/*********************************************************************************************
* AI Hints
********************************************************************************************* */
/** Current AI controlling this weapon */
var protectedwrite AIController AIController;
var array<byte> ShouldFireOnRelease;
var bool bInstantHit;
var bool bMeleeWeapon;
var float AIRating;
/** Cache MaxRange of weapon */
var float CachedMaxRange;
`if(`__TW_)
/*********************************************************************************************
* Functions put here to avoid casting. Implemented in subclasses
*********************************************************************************************/
simulated function bool ShouldOwnerWalk() {return false;}
/**
* Return an FOVAngle used by the PlayerInput class to determine how much to
* reduce the sensitivity when using sights/scopes. Added since the 3d scope
* zooms in farther than the player's FOV. Customize this function for weapons
* that have 3d scopes.
*/
simulated function float GetModifiedFOVAngle()
{
if( PlayerController(Instigator.Controller) != none )
{
return PlayerController(Instigator.Controller).GetFOVAngle();
}
}
/** Used by KFPlayerInputVersus to route startfire calls to the correct class */
simulated function bool ShouldWeaponIgnoreStartFire() { return false; }
`endif
/*********************************************************************************************
* Initialization / System Messages / Utility
*********************************************************************************************/
/** Event called when weapon actor is destroyed */
simulated event Destroyed()
{
// detach weapon from instigator
DetachWeapon();
super.Destroyed();
}
/**
* A notification call when this weapon is removed from the Inventory of a pawn
* @see Inventory::ItemRemovedFromInvManager
*/
function ItemRemovedFromInvManager()
{
`LogInv("");
GotoState('Inactive');
// Stop Firing
ForceEndFire();
// detach weapon from instigator
DetachWeapon();
// Tell the client the weapon has been thrown
ClientWeaponThrown();
Super.ItemRemovedFromInvManager();
if( Instigator != None && Instigator.Weapon == self )
{
Instigator.Weapon = None;
}
}
/**
* Pawn holding this weapon as active weapon just died.
*/
function HolderDied()
{
ServerStopFire( CurrentFireMode );
}
/**
* hook to override Next weapon call.
* For example the physics gun uses it to have mouse wheel change the distance of the held object.
* Warning: only use in firing state, otherwise it breaks weapon switching
*/
simulated function bool DoOverrideNextWeapon()
{
return false;
}
/**
* hook to override Previous weapon call.
*/
simulated function bool DoOverridePrevWeapon()
{
return false;
}
/**
* Drop this weapon out in to the world
*
* @param StartLocation - The World Location to drop this item from
* @param StartVelocity - The initial velocity for the item when dropped
*/
function DropFrom(vector StartLocation, vector StartVelocity)
{
if( !CanThrow() )
{
return;
}
// Become inactive
GotoState('Inactive');
// Stop Firing
ForceEndFire();
// Detach weapon components from instigator
DetachWeapon();
// tell the super to DropFrom() which will
// should remove the item from our inventory
Super.DropFrom(StartLocation, StartVelocity);
AIController = None;
}
/**
* Returns true if this item can be thrown out.
*/
simulated function bool CanThrow()
{
return bCanThrow;
}
/**
* This function is called when the client needs to discard the weapon
*/
reliable client function ClientWeaponThrown()
{
`LogInv("");
if (WorldInfo.NetMode == NM_Client)
{
// Become inactive
GotoState('Inactive');
// if this is the weapon we were carrying, set reference to None.
if( Instigator != None && Instigator.Weapon == Self )
{
Instigator.Weapon = None;
}
// Stop Firing
ForceEndFire();
// Detach weapon components from instigator
DetachWeapon();
}
}
/**
* Returns true if the weapon is firing, used by AI
*/
simulated event bool IsFiring()
{
return FALSE;
}
/**
* Returns true if this weapon wants to deny a ClientWeaponSwitch call
*/
simulated function bool DenyClientWeaponSet()
{
return FALSE;
}
`if(`__TW_)
/** Draws a HUD for this weapon */
simulated function DrawHUD( HUD H, Canvas C );
`endif
/*********************************************************************************************
* Debug / Log
*********************************************************************************************/
/**
* list important Weapon variables on canvas. HUD will call DisplayDebug() on the current ViewTarget when
* the ShowDebug exec is used
*
* @param HUD - HUD with canvas to draw on
* @input out_YL - Height of the current font
* @input out_YPos - Y position on Canvas. out_YPos += out_YL, gives position to draw text for next debug line.
*/
simulated function DisplayDebug(HUD HUD, out float out_YL, out float out_YPos)
{
local Array<String> DebugInfo;
local int i;
GetWeaponDebug( DebugInfo );
Hud.Canvas.SetDrawColor(0,255,0);
for (i=0;i<DebugInfo.Length;i++)
{
Hud.Canvas.DrawText( " " @ DebugInfo[i] );
out_YPos += out_YL;
Hud.Canvas.SetPos(4, out_YPos);
}
}
/**
* Retrieves important weapon debug information as an array of strings. That can then be dumped or displayed on HUD.
*/
simulated function GetWeaponDebug( out Array<String> DebugInfo )
{
local String T;
local int i;
DebugInfo[DebugInfo.Length] = "Weapon:" $ GetItemName(string(Self)) @ "State:" $ GetStateName() @ "Instigator:" $ Instigator @ "Owner:" $ Owner;
DebugInfo[DebugInfo.Length] = "IsFiring():" $ IsFiring() @ "CurrentFireMode:" $ CurrentFireMode @ "bWeaponPutDown:" $ bWeaponPutDown;
if (Instigator != None)
{
DebugInfo[DebugInfo.Length] = "ShotCount:" $ Instigator.ShotCount @ "FlashCount:" $ Instigator.FlashCount @ "FlashLocation:" $ Instigator.FlashLocation;
}
T = "PendingFires:";
for(i=0; i<GetPendingFireLength(); i++)
{
T = T $ PendingFire(i) $ " ";
}
DebugInfo[DebugInfo.Length] = T;
if( Timers.Length > 0 )
{
for (i=0;i<Timers.Length;i++)
{
DebugInfo[DebugInfo.Length] = "Timer" @ Timers[i].FuncName @ Timers[i].Count @ Timers[i].Rate @ int(Timers[i].Count/Timers[i].Rate*100)$"%";
}
}
}
/*********************************************************************************************
* Ammunition / Inventory
*********************************************************************************************/
/**
* Consumes ammunition when firing a shot.
* Subclass me to define weapon ammunition consumption.
*/
function ConsumeAmmo( byte FireModeNum );
/**
* Add ammo to weapon
* @param Amount to add.
* @return Amount actually added. (In case magazine is already full and some ammo is left
*
* Subclass me to define ammo addition rules.
*/
function int AddAmmo(int Amount);
/**
* This function checks to see if the weapon has any ammo available for a given fire mode.
*
* @param FireModeNum - The Fire Mode to Test For
* @param Amount - [Optional] Check to see if this amount is available.
* @return true if ammo is available for Firemode FireModeNum.
*/
simulated function bool HasAmmo( byte FireModeNum, optional int Amount )
{
return true;
}
/**
* returns true if this weapon has any ammo left, regardless of the actual firing mode.
*/
simulated function bool HasAnyAmmo()
{
return true;
}
/*********************************************************************************************
* Pending Fire / Inv Manager
*********************************************************************************************/
final simulated function INT GetPendingFireLength()
{
if( InvManager != none )
{
return InvManager.GetPendingFireLength(Self);
}
return 0;
}
final simulated function bool PendingFire(int FireMode)
{
if( InvManager != none )
{
return InvManager.IsPendingFire(Self, FireMode);
}
return false;
}
final simulated function SetPendingFire(int FireMode)
{
if( InvManager != None )
{
InvManager.SetPendingFire(Self, FireMode);
}
}
final simulated function ClearPendingFire(int FireMode)
{
if( InvManager != None )
{
InvManager.ClearPendingFire(Self, FireMode);
}
}
/**
* Returns the type of projectile to spawn. We use a function so subclasses can
* override it if needed (case in point, homing rockets).
*/
function class<Projectile> GetProjectileClass()
{
return (CurrentFireMode < WeaponProjectiles.length) ? WeaponProjectiles[CurrentFireMode] : None;
}
/**
* Adds any fire spread offset to the passed in rotator
* @param Aim the base aim direction
* @return the adjusted aim direction
*/
simulated function rotator AddSpread(rotator BaseAim)
{
local vector X, Y, Z;
local float CurrentSpread, RandY, RandZ;
CurrentSpread = Spread[CurrentFireMode];
if (CurrentSpread == 0)
{
return BaseAim;
}
else
{
// Add in any spread.
GetAxes(BaseAim, X, Y, Z);
RandY = FRand() - 0.5;
RandZ = Sqrt(0.5 - Square(RandY)) * (FRand() - 0.5);
return rotator(X + RandY * CurrentSpread * Y + RandZ * CurrentSpread * Z);
}
}
/**
* Returns the Maximum Range for this weapon
*/
simulated function float MaxRange()
{
local int i;
if ( CachedMaxRange > 0 )
{
return CachedMaxRange;
}
// return the range of the fire mode that fires farthest
if (bInstantHit)
{
CachedMaxRange = WeaponRange;
}
for (i = 0; i < WeaponProjectiles.length; i++)
{
if (WeaponProjectiles[i] != None)
{
CachedMaxRange = FMax(CachedMaxRange, WeaponProjectiles[i].static.GetRange());
}
}
return CachedMaxRange;
}
/*********************************************************************************************
* AI interface
*********************************************************************************************/
function float GetAIRating()
{
return AIRating;
}
/**
* Returns a weight reflecting the desire to use the
* given weapon, used for AI and player best weapon
* selection.
*
* @return weapon rating (range -1.f to 1.f)
*/
simulated function float GetWeaponRating()
{
if( InvManager != None )
{
return InvManager.GetWeaponRatingFor( Self );
}
if( !HasAnyAmmo() )
{
return -1;
}
return 1;
}
// CanAttack() - return false if out of range, can't see target, etc.
function bool CanAttack(Actor Other)
{
return true;
}
// tells AI that it needs to release the fire button for this weapon to do anything
function bool FireOnRelease()
{
return (ShouldFireOnRelease.Length>0 && ShouldFireOnRelease[CurrentFireMode]!=0);
}
/*********************************************************************************************
* Effects / Mesh / Animations / Sounds
*********************************************************************************************/
/** Returns the AnimNodeSequence the weapon is using to play animations. */
simulated function AnimNodeSequence GetWeaponAnimNodeSeq()
{
local AnimTree Tree;
local AnimNodeSequence AnimSeq;
local SkeletalMeshComponent SkelMesh;
SkelMesh = SkeletalMeshComponent(Mesh);
if(SkelMesh != None)
{
//Try getting an animtree first
Tree = AnimTree(SkelMesh.Animations);
if (Tree != None)
{
AnimSeq = AnimNodeSequence(Tree.Children[0].Anim);
}
else
{
//Old legacy way without an animtree
AnimSeq = AnimNodeSequence(SkelMesh.Animations);
}
return AnimSeq;
}
return None;
}
/**
* Play an animation on the weapon mesh
* Network: Local Player and clients
*
* @param Anim Sequence to play on weapon skeletal mesh
* @param desired duration, in seconds, animation should be played
*/
simulated function PlayWeaponAnimation( Name Sequence, float fDesiredDuration, optional bool bLoop, optional SkeletalMeshComponent SkelMesh)
{
local AnimNodeSequence WeapNode;
local AnimTree Tree;
// do not play on a dedicated server
if( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
if ( SkelMesh == None )
{
SkelMesh = SkeletalMeshComponent(Mesh);
}
// Check we have access to mesh and animations
if( SkelMesh == None || GetWeaponAnimNodeSeq() == None )
{
return;
}
if(fDesiredDuration > 0.0)
{
// @todo - this should call GetWeaponAnimNodeSeq, move 'duration' code into AnimNodeSequence and use that.
SkelMesh.PlayAnim(Sequence, fDesiredDuration, bLoop);
}
else
{
//Try getting an animtree first
Tree = AnimTree(SkelMesh.Animations);
if (Tree != None)
{
WeapNode = AnimNodeSequence(Tree.Children[0].Anim);
}
else
{
WeapNode = AnimNodeSequence(SkelMesh.Animations);
}
WeapNode.SetAnim(Sequence);
WeapNode.PlayAnim(bLoop, DefaultAnimSpeed);
}
}
/**
* Stops an animation on the weapon mesh
* Network: Local Player and clients
*
*/
simulated function StopWeaponAnimation()
{
local AnimNodeSequence AnimSeq;
// do not play on a dedicated server
if( WorldInfo.NetMode == NM_DedicatedServer )
{
return;
}
AnimSeq = GetWeaponAnimNodeSeq();
if( AnimSeq != None )
{
AnimSeq.StopAnim();
}
}
/**
* PlayFireEffects
* Main function to play Weapon fire effects.
* This is called from Pawn::WeaponFired in the base implementation.
*/
simulated function PlayFireEffects( byte FireModeNum, optional vector HitLocation );
/**
* StopFireEffects
* Main function to stop any active effects
* This is called from Pawn::WeaponStoppedFiring
*/
simulated function StopFireEffects(byte FireModeNum);
/*********************************************************************************************
* Timing
*********************************************************************************************/
/**
* Returns interval in seconds between each shot, for the firing state of FireModeNum firing mode.
*
* @param FireModeNum fire mode
* @return Period in seconds of firing mode
*/
simulated function float GetFireInterval( byte FireModeNum )
{
return FireInterval[FireModeNum] > 0 ? FireInterval[FireModeNum] : 0.01;
}
/**
* Sets the timing for the firing state on server and local client.
* By default, a constant looping Rate Of Fire (ROF) is set up.
* When the delay has expired, the RefireCheckTimer event is triggered.
*
* Network: LocalPlayer and Server
*
* @param FireModeNum Fire Mode.
*/
simulated function TimeWeaponFiring( byte FireModeNum )
{
// if weapon is not firing, then start timer. Firing state is responsible to stopping the timer.
if( !IsTimerActive('RefireCheckTimer') )
{
SetTimer( GetFireInterval(FireModeNum), true, nameof(RefireCheckTimer) );
}
}
simulated function RefireCheckTimer();
/**
* Sets the timing for putting a weapon down. The WeaponIsDown event is trigged when expired
*/
simulated function TimeWeaponPutDown()
{
SetTimer( PutDownTime>0 ? PutDownTime : 0.01, false, nameof(WeaponIsDown) );
}
/**
* Sets the timing for equipping a weapon.
* The WeaponEquipped event is trigged when expired
*/
simulated function TimeWeaponEquipping()
{
SetTimer( EquipTime>0 ? EquipTime : 0.01 , false, 'WeaponEquipped');
}
/**
* All inventory use the Activate() function when an item is selected for use.
* For weapons, this function starts the Equipping process. If the weapon is the inactive state,
* it will go to the 'WeaponEquipping' followed by 'Active' state, and ready to be fired.
*/
simulated function Activate()
{
// don't reactivate if already firing
if (!IsFiring())
{
GotoState('WeaponEquipping');
}
}
/**
* This function is called to put a weapon down
*/
simulated function PutDownWeapon()
{
GotoState('WeaponPuttingDown');
}
/**
* When you pickup an weapon, the inventory system has a chance to restrict the pickup.
*/
function bool DenyPickupQuery(class<Inventory> ItemClass, Actor Pickup)
{
// By default, you can only carry a single item of a given class.
if( ItemClass == class )
{
return true;
}
return false;
}
/**
* Called when the weapon runs out of ammo during firing
*/
simulated function WeaponEmpty();
/*********************************************************************************************
* Firtst/Third person weapon attachment functions
*********************************************************************************************/
/**
* Increment Pawn's FlashCount variable.
* This is used to play weapon fire effects on remote clients.
* Call this on the server and local player.
*
* Network: Server and Local Player
*/
simulated function IncrementFlashCount()
{
if( Instigator != None )
{
Instigator.IncrementFlashCount( Self, CurrentFireMode );
}
}
/**
* Clear flashCount variable on Pawn. and call WeaponStoppedFiring event.
* Call this on the server and local player.
*
* Network: Server or Local Player
*/
simulated function ClearFlashCount()
{
if( Instigator != None )
{
Instigator.ClearFlashCount( Self );
}
}
/**
* This function sets up the Location of a hit to be replicated to all remote clients.
*
* Network: Server only
*/
function SetFlashLocation( vector HitLocation )
{
if( Instigator != None )
{
Instigator.SetFlashLocation( Self, CurrentFireMode, HitLocation );
}
}
/**
* Reset flash location variable. and call stop firing.
* Network: Server only
*/
function ClearFlashLocation()
{
if( Instigator != None )
{
Instigator.ClearFlashLocation( Self );
}
}
/**
* AttachWeaponTo is called when it's time to attach the weapon's mesh to a location.
* it should be subclassed.
*/
simulated function AttachWeaponTo( SkeletalMeshComponent MeshCpnt, optional Name SocketName );
/**
* Detach weapon components from instigator. Perform any clean up.
* Should be subclassed.
*/
simulated function DetachWeapon();
/*********************************************************************************************
* Pawn/Controller/View functions
*********************************************************************************************/
reliable client function ClientGivenTo(Pawn NewOwner, bool bDoNotActivate)
{
Super.ClientGivenTo(NewOwner, bDoNotActivate);
// Evaluate if we should switch to this weapon
ClientWeaponSet(TRUE, bDoNotActivate);
}
/**
* is called by the server to tell the client about potential weapon changes after the player runs over
* a weapon (the client decides whether to actually switch weapons or not.
* Network: LocalPlayer
*
* @param bOptionalSet. Set to true if the switch is optional. (simple weapon pickup and weight against current weapon).
* @param bDoNotActivate. Override, do not activate this weapon. It's just been received in the inventory.
*/
reliable client function ClientWeaponSet(bool bOptionalSet, optional bool bDoNotActivate)
{
`LogInv("bOptionalSet:" @ bOptionalSet @ "bDoNotActivate:" @ bDoNotActivate @ "Instigator:" @ Instigator @ "InvManager:" @ InvManager);
// Save variables in case we need to go to PendingClientWeaponSet to wait for replication
bWasOptionalSet = bOptionalSet;
bWasDoNotActivate = bDoNotActivate;
// If weapon's instigator isn't replicated to client, wait for it in PendingClientWeaponSet state
if( Instigator == None )
{
`LogInv("Instigator == None, going to PendingClientWeaponSet");
GotoState('PendingClientWeaponSet');
return;
}
// If InvManager isn't replicated to client, wait for it in PendingClientWeaponSet state
if( InvManager == None )
{
`LogInv("InvManager == None, going to PendingClientWeaponSet");
GotoState('PendingClientWeaponSet');
return;
}
InvManager.ClientWeaponSet(Self, bOptionalSet, bDoNotActivate);
}
/*********************************************************************************************
* Handling the actual Fire Commands
*********************************************************************************************/
/* Weapon Firing Logic overiew:
The weapon system here is designed to be a single code path that follows the same flow on both
the Authoritive server and the local client. Remote clients know nothing about the weapon and utilize
the WeaponAttachment system to see the end results.
1: The InventoryManager (IM) on the Local Client recieves a StartFire call. It calls StartFire().
2: If Local Client is not Authoritive it notifies the server via ServerStartFire().
3: Both StartFire() and ServerStartFire() sync up by calling BeginFire().
4: BeginFire sets the PendingFire flag for the incoming fire Mode
5: BeginFire looks at the current state and if it's in the Active state, it begins the
firing sequence by transitioning to the new fire state as defined by the FiringStatesArray
array. This is done by called SendToFiringState.
6: The Firing Logic is handled in the various firing states. Firing states are responsible for the
following:
a: Continuing to fire if their associated PendingFire is hot
b: Transitioning to a new weapon when out of ammo
c: Transitioning to the "Active" state when no longer firing
The weapon system also receives a StopFire() event from the IM. When this occurs, the following
logic is performed:
1: The IM on the Local Client calls StopFire().
2: If Weapon Stop fire is not on the Authoritive process, it notifes the server via the
ServerStopFire() event.
3: Both StopFire() and ServerStopFire() sync up by calling EndFire().
4: EndFire() clears the PendingFire flag for this outgoing fire mode.
Firing states should be identical in their execution, branching outwards as need. For example,
in the default firing state ('WeaponFiring') the function FireAmmunition() occurs in all applicable processes.
*/
/**
* Called on the LocalPlayer, Fire sends the shoot request to the server (ServerStartFire)
* and them simulates the firing effects locally.
* Call path: PlayerController::StartFire -> Pawn::StartFire -> InventoryManager::StartFire
* Network: LocalPlayer
*/
simulated function StartFire(byte FireModeNum)
{
if( Instigator == None || !Instigator.bNoWeaponFiring )
{
if( Role < Role_Authority )
{
// if we're a client, synchronize server
ServerStartFire(FireModeNum);
}
// Start fire locally
BeginFire(FireModeNum);
}
}
/**
* When StartFire() is called on a client, it replicates the start by calling ServerStartFire. This
* begins the event on server. Server side actors (such as bots) should not call ServerStartFire directly and should
* instead call StartFire().
*
* Network: Dedicated Server only, or Listen Server for remote clients.
*/
reliable server function ServerStartFire(byte FireModeNum)
{
if( Instigator == None || !Instigator.bNoWeaponFiring )
{
// A client has fired, so the server needs to
// begin to fire as well
BeginFire(FireModeNum);
}
}
/**
* BeginFire is the point at which the server and client sync up their code path. It's job is to set
* the weapon in to the firing state.
* Network: LocalPlayer and Server
*/
simulated function BeginFire(Byte FireModeNum)
{
// Flag this mode as pending a fire. The only thing that can remove
// this flag is a Stop Fire/Putdown command.
`LogInv("FireModeNum:" @ FireModeNum);
SetPendingFire(FireModeNum);
}
/**
* This initiates the shutdown of a weapon that is firing.
* Network: Local Player
*/
simulated function StopFire(byte FireModeNum)
{
// Locally shut down the fire sequence
EndFire(FireModeNum);
// Notify the server
if( Role < Role_Authority )
{
ServerStopFire(FireModeNum);
}
}
/**
* When StopFire is called on a client, ServerStopFire is used to initiate the sequence on the server.
* Network: Dedicated Server only, or Listen Server for remote clients.
*/
reliable server function ServerStopFire(byte FireModeNum)
{
EndFire(FireModeNum);
}
/**
* Like BeginFire, this function puts a client and the server in sync and shuts down the
* firing sequence on both.
* Network: LocalPlayer and Server
*/
simulated function EndFire(byte FireModeNum)
{
// Clear the firing flag for this mode
ClearPendingFire(FireModeNum);
}
/**
* Clear all pending fires.
* This is non replicated flag.
*/
simulated function ForceEndFire()
{
local int i, Num;
// Clear all pending fires
if (InvManager != None)
{
Num = GetPendingFireLength();
for (i = 0; i < Num; i++)
{
if (PendingFire(i))
{
EndFire(i);
}
}
}
}
/**
* Send weapon to proper firing state
* Also sets the CurrentFireMode.
* Network: LocalPlayer and Server
*
* @param FireModeNum Fire Mode.
*/
simulated function SendToFiringState(byte FireModeNum)
{
// make sure fire mode is valid
if( FireModeNum >= FiringStatesArray.Length )
{
`LogInv("Invalid FireModeNum");
return;
}
// Needs a state name, and ignores a none fire type
if( FiringStatesArray[FireModeNum] == '' ||
WeaponFireTypes[FireModeNum] == EWFT_None )
{
return;
}
// set current fire mode
SetCurrentFireMode(FireModeNum);
`LogInv(FireModeNum @ "Sending to state:" @ FiringStatesArray[FireModeNum]);
// transition to firing mode state
GotoState(FiringStatesArray[FireModeNum]);
}
/**
* Set current firing mode.
* Network: Local Player and Server.
*/
simulated function SetCurrentFireMode(byte FiringModeNum)
{
// set weapon's current fire mode
CurrentFireMode = FiringModeNum;
// set on instigator, to replicate it to remote clients
if( Instigator != None )
{
Instigator.SetFiringMode(Self, FiringModeNum);
}
}
/**
* Event called when Pawn.FiringMode has been changed.
* bViaReplication indicates if this was the result of a replication call.
*/
simulated function FireModeUpdated(byte FiringMode, bool bViaReplication);
/**
* FireAmmunition: Perform all logic associated with firing a shot
* - Fires ammunition (instant hit or spawn projectile)
* - Consumes ammunition
* - Plays any associated effects (fire sound and whatnot)
*
* Network: LocalPlayer and Server
*/
simulated function FireAmmunition()
{
// Use ammunition to fire
ConsumeAmmo( CurrentFireMode );
// Handle the different fire types
switch( WeaponFireTypes[CurrentFireMode] )
{
case EWFT_InstantHit:
InstantFire();
break;
case EWFT_Projectile:
ProjectileFire();
break;
case EWFT_Custom:
CustomFire();
break;
}
NotifyWeaponFired( CurrentFireMode );
}
/**
* GetAdjustedAim begins a chain of function class that allows the weapon, the pawn and the controller to make
* on the fly adjustments to where this weapon is pointing.
*/
simulated function Rotator GetAdjustedAim( vector StartFireLoc )
{
local rotator R;
// Start the chain, see Pawn.GetAdjustedAimFor()
if( Instigator != None )
{
R = Instigator.GetAdjustedAimFor( Self, StartFireLoc );
}
return AddSpread(R);
}
/**
* Range of weapon
* Used for Traces (CalcWeaponFire, InstantFire, ProjectileFire, AdjustAim...)
* State scoped accessor function. Override in proper state
*
* @return range of weapon, to be used mainly for traces.
*/
simulated event float GetTraceRange()
{
return WeaponRange;
}
/** @return the actor that 'owns' this weapon's traces (i.e. can't be hit by them) */
simulated function Actor GetTraceOwner()
{
return (Instigator != None) ? Instigator : self;
}
/**
* CalcWeaponFire: Simulate an instant hit shot.
* This doesn't deal any damage nor trigger any effect. It just simulates a shot and returns
* the hit information, to be post-processed later.
*
* ImpactList returns a list of ImpactInfo containing all listed impacts during the simulation.
* CalcWeaponFire however returns one impact (return variable) being the first geometry impact
* straight, with no direction change. If you were to do refraction, reflection, bullet penetration
* or something like that, this would return exactly when the crosshair sees:
* The first 'real geometry' impact, skipping invisible triggers and volumes.
*
* @param StartTrace world location to start trace from
* @param EndTrace world location to end trace at
* @param Extent extent of trace performed
* @output ImpactList list of all impacts that occured during simulation
* @return first 'real geometry' impact that occured.
*
* @note if an impact didn't occur, and impact is still returned, with its HitLocation being the EndTrace value.
*/
simulated function ImpactInfo CalcWeaponFire(vector StartTrace, vector EndTrace, optional out array<ImpactInfo> ImpactList, optional vector Extent)
{
local vector HitLocation, HitNormal, Dir;
local Actor HitActor;
local TraceHitInfo HitInfo;
local ImpactInfo CurrentImpact;
local PortalTeleporter Portal;
local float HitDist;
local bool bOldBlockActors, bOldCollideActors;
// Perform trace to retrieve hit info
HitActor = GetTraceOwner().Trace(HitLocation, HitNormal, EndTrace, StartTrace, TRUE, Extent, HitInfo, TRACEFLAG_Bullet);
// If we didn't hit anything, then set the HitLocation as being the EndTrace location
if( HitActor == None )
{
HitLocation = EndTrace;
}
// Convert Trace Information to ImpactInfo type.
CurrentImpact.HitActor = HitActor;
CurrentImpact.HitLocation = HitLocation;
CurrentImpact.HitNormal = HitNormal;
CurrentImpact.RayDir = Normal(EndTrace-StartTrace);
CurrentImpact.StartTrace = StartTrace;
CurrentImpact.HitInfo = HitInfo;
// Add this hit to the ImpactList
ImpactList[ImpactList.Length] = CurrentImpact;
// check to see if we've hit a trigger.
// In this case, we want to add this actor to the list so we can give it damage, and then continue tracing through.
if( HitActor != None )
{
if (PassThroughDamage(HitActor))
{
// disable collision temporarily for the actor we can pass-through
HitActor.bProjTarget = false;
bOldCollideActors = HitActor.bCollideActors;
bOldBlockActors = HitActor.bBlockActors;
if (HitActor.IsA('Pawn'))
{
// For pawns, we need to disable bCollideActors as well
HitActor.SetCollision(false, false);
// recurse another trace
CalcWeaponFire(HitLocation, EndTrace, ImpactList, Extent);
}
else
{
if( bOldBlockActors )
{
HitActor.SetCollision(bOldCollideActors, false);
}
// recurse another trace and override CurrentImpact
CurrentImpact = CalcWeaponFire(HitLocation, EndTrace, ImpactList, Extent);
}
// and reenable collision for the trigger
HitActor.bProjTarget = true;
HitActor.SetCollision(bOldCollideActors, bOldBlockActors);
}
else
{
// if we hit a PortalTeleporter, recurse through
Portal = PortalTeleporter(HitActor);
if( Portal != None && Portal.SisterPortal != None )
{
Dir = EndTrace - StartTrace;
HitDist = VSize(HitLocation - StartTrace);
// calculate new start and end points on the other side of the portal
StartTrace = Portal.TransformHitLocation(HitLocation);
EndTrace = StartTrace + Portal.TransformVectorDir(Normal(Dir) * (VSize(Dir) - HitDist));
//@note: intentionally ignoring return value so our hit of the portal is used for effects
//@todo: need to figure out how to replicate that there should be effects on the other side as well
CalcWeaponFire(StartTrace, EndTrace, ImpactList, Extent);
}
}
}
return CurrentImpact;
}
/**
* returns true if should pass trace through this hitactor
*/
`if(`__TW_)
// removed static for penetration control
simulated function bool PassThroughDamage(Actor HitActor)
`else
simulated static function bool PassThroughDamage(Actor HitActor)
`endif
{
return (!HitActor.bBlockActors && (HitActor.IsA('Trigger') || HitActor.IsA('TriggerVolume')))
|| HitActor.IsA('InteractiveFoliageActor');
}
/**
* Performs an 'Instant Hit' shot.
* Also, sets up replication for remote clients,
* and processes all the impacts to deal proper damage and play effects.
*
* Network: Local Player and Server
*/
simulated function InstantFire()
{
local vector StartTrace, EndTrace;
local Array<ImpactInfo> ImpactList;
local int Idx;
local ImpactInfo RealImpact;
// define range to use for CalcWeaponFire()
StartTrace = Instigator.GetWeaponStartTraceLocation();
EndTrace = StartTrace + vector(GetAdjustedAim(StartTrace)) * GetTraceRange();
// Perform shot
RealImpact = CalcWeaponFire(StartTrace, EndTrace, ImpactList);
if (Role == ROLE_Authority)
{
/* FlushPersistentDebugLines();
DrawDebugSphere( StartTrace, 10, 10, 0, 255, 0 );
DrawDebugSphere( EndTrace, 10, 10, 255, 0, 0 );
DrawDebugSphere( RealImpact.HitLocation, 10, 10, 0, 0, 255 );
`log( self@GetFuncName()@Instigator@RealImpact.HitLocation@RealImpact.HitActor );*/
// Set flash location to trigger client side effects.
// if HitActor == None, then HitLocation represents the end of the trace (maxrange)
// Remote clients perform another trace to retrieve the remaining Hit Information (HitActor, HitNormal, HitInfo...)
// Here, The final impact is replicated. More complex bullet physics (bounce, penetration...)
// would probably have to run a full simulation on remote clients.
SetFlashLocation(RealImpact.HitLocation);
}
// Process all Instant Hits on local player and server (gives damage, spawns any effects).
for (Idx = 0; Idx < ImpactList.Length; Idx++)
{
ProcessInstantHit(CurrentFireMode, ImpactList[Idx]);
}
}
/**
* Processes a successful 'Instant Hit' trace and eventually spawns any effects.
* Network: LocalPlayer and Server
* @param FiringMode: index of firing mode being used
* @param Impact: hit information
* @param NumHits (opt): number of hits to apply using this impact
* this is useful for handling multiple nearby impacts of multihit weapons (e.g. shotguns)
* without having to execute the entire damage code path for each one
* an omitted or <= 0 value indicates a single hit
*/
simulated function ProcessInstantHit(byte FiringMode, ImpactInfo Impact, optional int NumHits)
{
local int TotalDamage;
local KActorFromStatic NewKActor;
local StaticMeshComponent HitStaticMesh;
if (Impact.HitActor != None)
{
// default damage model is just hits * base damage
NumHits = Max(NumHits, 1);
TotalDamage = InstantHitDamage[CurrentFireMode] * NumHits;
if ( Impact.HitActor.bWorldGeometry )
{
HitStaticMesh = StaticMeshComponent(Impact.HitInfo.HitComponent);
if ( (HitStaticMesh != None) && HitStaticMesh.CanBecomeDynamic() )
{
NewKActor = class'KActorFromStatic'.Static.MakeDynamic(HitStaticMesh);
if ( NewKActor != None )
{
Impact.HitActor = NewKActor;
}
}
}
Impact.HitActor.TakeDamage( TotalDamage, Instigator.Controller,
Impact.HitLocation, InstantHitMomentum[FiringMode] * Impact.RayDir,
InstantHitDamageTypes[FiringMode], Impact.HitInfo, self );
}
}
/**
* Fires a projectile.
* Spawns the projectile, but also increment the flash count for remote client effects.
* Network: Local Player and Server
*/
simulated function Projectile ProjectileFire()
{
local vector StartTrace, EndTrace, RealStartLoc, AimDir;
local ImpactInfo TestImpact;
local Projectile SpawnedProjectile;
// tell remote clients that we fired, to trigger effects
IncrementFlashCount();
if( Role == ROLE_Authority )
{
// This is where we would start an instant trace. (what CalcWeaponFire uses)
StartTrace = Instigator.GetWeaponStartTraceLocation();
AimDir = Vector(GetAdjustedAim( StartTrace ));
// this is the location where the projectile is spawned.
RealStartLoc = GetPhysicalFireStartLoc(AimDir);
if( StartTrace != RealStartLoc )
{
// if projectile is spawned at different location of crosshair,
// then simulate an instant trace where crosshair is aiming at, Get hit info.
EndTrace = StartTrace + AimDir * GetTraceRange();
TestImpact = CalcWeaponFire( StartTrace, EndTrace );
// Then we realign projectile aim direction to match where the crosshair did hit.
AimDir = Normal(TestImpact.HitLocation - RealStartLoc);
}
// Spawn projectile
SpawnedProjectile = Spawn(GetProjectileClass(), Self,, RealStartLoc);
if( SpawnedProjectile != None && !SpawnedProjectile.bDeleteMe )
{
SpawnedProjectile.Init( AimDir );
}
// Return it up the line
return SpawnedProjectile;
}
return None;
}
/**
* If the weapon isn't an instant hit, or a simple projectile, it should use the tyoe EWFT_Custom. In those cases
* this function will be called. It should be subclassed by the custom weapon.
*/
simulated function CustomFire();
/**
* This function returns the world location for spawning the visual effects
*/
simulated event vector GetMuzzleLoc()
{
if( Instigator != none )
{
return Instigator.GetPawnViewLocation() + (FireOffset >> Instigator.GetViewRotation());
}
return Location;
}
/**
* This function returns the world location for spawning the projectile, pulled in to the Pawn's collision along the AimDir direction.
*/
simulated native event vector GetPhysicalFireStartLoc(optional vector AimDir);
/**
* Put Down current weapon
* Once the weapon is put down, the InventoryManager will switch to InvManager.PendingWeapon.
*
* @return returns true if the weapon can be put down.
*/
simulated function bool TryPutDown()
{
bWeaponPutDown = TRUE;
return TRUE;
}
/*********************************************************************************************
* State Inactive
* Default state for a weapon. It is not active, cannot fire and resides in player inventory.
*********************************************************************************************/
auto state Inactive
{
/**
* Clear out the PendingFires
*/
simulated event BeginState( Name PreviousStateName )
{
// // Make sure all pending fires are cleared
// ForceEndFire();
}
reliable server function ServerStartFire(byte FireModeNum)
{
Global.ServerStartFire(FireModeNum);
`Warn(WorldInfo.TimeSeconds @ Instigator @ "received ServerStartFire in Inactive State!!!");
// We haven't received the activate yet so pass it along
if( Instigator != None && Instigator.Weapon == Self)
{
`Warn( " - I'm the current weapon, so gotostate active and start firing" );
GotoState('Active');
}
else if( InvManager != None && InvManager.PendingWeapon == Self )
{
// If our weapon is being put down, then let's just trigger the transition now.
if( Instigator.Weapon.IsInState('WeaponPuttingDown') )
{
`Warn( " - I'm the pending weapon, and current weapon is being put down, so force switch now" );
Instigator.Weapon.WeaponIsDown();
}
else
{
`Warn( " - I'm the pending weapon, but current weapon is NOT being put down, so resync client and server" );
InvManager.SetCurrentWeapon(Self);
InvManager.ServerSetCurrentWeapon(Self);
if( Instigator.Weapon != Self && InvManager.PendingWeapon == Self && Instigator.Weapon.IsInState('WeaponPuttingDown') )
{
Instigator.Weapon.WeaponIsDown();
}
}
}
else if( Instigator != None )
{
// Have the client switch to the current weapon
`Warn( " - I'm just in the inventory, so resync client and server" );
InvManager.SetCurrentWeapon(Self);
InvManager.ServerSetCurrentWeapon(Self);
if( Instigator.Weapon != Self && InvManager.PendingWeapon == Self && Instigator.Weapon.IsInState('WeaponPuttingDown') )
{
Instigator.Weapon.WeaponIsDown();
}
}
}
reliable server function ServerStopFire( byte FireModeNum )
{
ClearPendingFire(FireModeNum);
}
/** do not allow firing in the inactive state */
simulated function StartFire( byte FireModeNum );
/** can't put down an inactive weapon */
simulated function bool TryPutDown()
{
return FALSE;
}
}
/*********************************************************************************************
* State Active
* A Weapon this is being held by a pawn should be in the active state. In this state,
* a weapon should loop any number of idle animations, as well as check the PendingFire flags
* to see if a shot has been fired.
*********************************************************************************************/
simulated state Active
{
/** Initialize the weapon as being active and ready to go. */
simulated event BeginState(Name PreviousStateName)
{
local int i;
// Cache a reference to the AI controller
if (Role == ROLE_Authority)
{
CacheAIController();
}
// Check to see if we need to go down
if( bWeaponPutDown )
{
`LogInv("Weapon put down requested during transition, put it down now");
PutDownWeapon();
}
else if ( !HasAnyAmmo() )
{
WeaponEmpty();
}
else
{
// if either of the fire modes are pending, perform them
for( i=0; i<GetPendingFireLength(); i++ )
{
if( PendingFire(i) )
{
BeginFire(i);
break;
}
}
}
}
/** Override BeginFire so that it will enter the firing state right away. */
simulated function BeginFire(byte FireModeNum)
{
if( !bDeleteMe && Instigator != None )
{
Global.BeginFire(FireModeNum);
// in the active state, fire right away if we have the ammunition
if( PendingFire(FireModeNum) && HasAmmo(FireModeNum) )
{
SendToFiringState(FireModeNum);
}
}
}
/**
* ReadyToFire() called by NPC firing weapon. bFinished should only be true if called from the Finished() function
*/
simulated function bool ReadyToFire(bool bFinished)
{
return true;
}
/** Activate() ignored since already active
*/
simulated function Activate()
{
}
/**
* Put the weapon down
*/
simulated function bool TryPutDown()
{
PutDownWeapon();
return TRUE;
}
}
/*********************************************************************************************
* state WeaponFiring
* This is the default Firing State. It's performed on both the client and the server.
*********************************************************************************************/
simulated state WeaponFiring
{
simulated event bool IsFiring()
{
return true;
}
/**
* Timer event, call is set up in Weapon::TimeWeaponFiring().
* The weapon is given a chance to evaluate if another shot should be fired.
* This event defines the weapon's rate of fire.
*/
simulated function RefireCheckTimer()
{
// if switching to another weapon, abort firing and put down right away
if( bWeaponPutDown )
{
`LogInv("Weapon put down requested during fire, put it down now");
PutDownWeapon();
return;
}
// If weapon should keep on firing, then do not leave state and fire again.
if( ShouldRefire() )
{
FireAmmunition();
return;
}
// Otherwise we're done firing
HandleFinishedFiring();
}
simulated event BeginState( Name PreviousStateName )
{
`LogInv("PreviousStateName:" @ PreviousStateName);
// Fire the first shot right away
FireAmmunition();
TimeWeaponFiring( CurrentFireMode );
}
simulated event EndState( Name NextStateName )
{
`LogInv("NextStateName:" @ NextStateName);
// Set weapon as not firing
ClearFlashCount();
ClearFlashLocation();
ClearTimer( nameof(RefireCheckTimer) );
NotifyWeaponFinishedFiring( CurrentFireMode );
}
}
simulated function HandleFinishedFiring()
{
// Go back to active state.
GotoState('Active');
}
/**
* AI function to handle a single shot being fired
*/
function NotifyWeaponFired( byte FireMode )
{
if( AIController != None )
{
AIController.NotifyWeaponFired( self, FireMode );
}
}
/**
* AI function to handle a firing sequence (ie burst/melee strike/etc) being finished
*/
function NotifyWeaponFinishedFiring( byte FireMode )
{
if( AIController != None )
{
AIController.NotifyWeaponFinishedFiring( self, FireMode );
}
}
/**
* Check if current fire mode can/should keep on firing.
* This is called from a firing state after each shot is fired
* to decide if the weapon should fire again, or stop and go to the active state.
* The default behavior, implemented here, is keep on firing while player presses fire
* and there is enough ammo. (Auto Fire).
*
* @return true to fire again, false to stop firing and return to Active State.
*/
simulated function bool ShouldRefire()
{
// if doesn't have ammo to keep on firing, then stop
if( !HasAmmo( CurrentFireMode ) )
{
return false;
}
// refire if owner is still willing to fire
return StillFiring( CurrentFireMode );
}
/**
* This function returns true if the weapon is still firing in a given mode
*/
simulated function bool StillFiring(byte FireMode)
{
return ( PendingFire(FireMode) );
}
/**
* State WeaponEquipping
* The Weapon is in this state while transitioning from Inactive to Active state.
* Typically, the weapon will remain in this state while its selection animation is being played.
* While in this state, the weapon cannot be fired.
*/
simulated state WeaponEquipping
{
simulated event BeginState(Name PreviousStateName)
{
`LogInv("");
TimeWeaponEquipping();
bWeaponPutDown = false;
}
/** Activate() ignored since already becoming active
*/
simulated function Activate()
{
}
simulated event EndState(Name NextStateName)
{
ClearTimer('WeaponEquipped');
}
simulated function WeaponEquipped()
{
if( bWeaponPutDown )
{
// if switched to another weapon, put down right away
PutDownWeapon();
return;
}
GotoState('Active');
}
}
/**
* State WeaponPuttingDown
* Putting down weapon in favor of a new one.
* Weapon is transitioning to the Inactive state.
*/
simulated state WeaponPuttingDown
{
/**
* Time the process and clear the Firing flags
*/
simulated event BeginState(Name PreviousStateName)
{
`LogInv("");
TimeWeaponPutDown();
bWeaponPutDown = FALSE;
// Make sure all pending fires are cleared.
ForceEndFire();
}
/**
* We are done putting the weapon away, remove the mesh/etc.
*/
simulated function WeaponIsDown()
{
if( InvManager.CancelWeaponChange() )
{
return;
}
`LogInv("");
// This weapon is down, remove it from the mesh
DetachWeapon();
// Put weapon to sleep
//@warning: must be before ChangedWeapon() because that can reactivate this weapon in some cases
GotoState('Inactive');
// switch to pending weapon
InvManager.ChangedWeapon();
}
simulated function bool TryPutDown()
{
return FALSE;
}
reliable client function ClientWeaponThrown()
{
// Call Weapon is down before cleaning up this one.
WeaponIsDown();
global.ClientWeaponThrown();
}
simulated event EndState(Name NextStateName)
{
`LogInv("");
ClearTimer('WeaponIsDown');
}
}
simulated function WeaponIsDown();
/*********************************************************************************************
* State PendingClientWeaponSet
* A weapon sets in this state on a remote client while it awaits full replication of all
* properties.
*********************************************************************************************/
State PendingClientWeaponSet
{
simulated function PendingWeaponSetTimer()
{
// When variables are replicated, ClientWeaponSet, will send weapon to another state.
// Therefore aborting this timer.
ClientWeaponSet(bWasOptionalSet, bWasDoNotActivate);
}
/** Event called when weapon enters this state */
simulated event BeginState(Name PreviousStateName)
{
// Set a timer to keep checking for replicated variables.
SetTimer(0.03f, TRUE, nameof(PendingWeaponSetTimer) );
}
/** Event called when weapon leaves this state */
simulated event EndState(Name NextStateName)
{
ClearTimer( nameof(PendingWeaponSetTimer) );
}
}
simulated function CacheAIController()
{
if( Instigator == None )
{
AIController = None;
}
else
{
AIController = AIController(Instigator.Controller);
}
}
/**
* Compute the approximate Screen distance from the camera to whatever is at the center of the viewport.
* Useful for stereoizing the crosshair to reduce eyestrain.
* NOTE: The dotproduct at the end is currently unnecessary, but if you were to use a different value for
* TargetLoc that was not at center of screen, it'd become necessary to do the way screen projection works.
*/
simulated function float GetTargetDistance( )
{
local float VeryFar;
local vector HitLocation, HitNormal, ProjStart, TargetLoc, X, Y, Z;
local rotator CameraRot;
local PlayerController PC;
VeryFar = 32768;
PC = PlayerController(Instigator.Controller);
PC.GetPlayerViewPoint(ProjStart, CameraRot);
GetAxes(CameraRot, X, Y, Z);
TargetLoc = ProjStart + X * VeryFar;
if (None == GetTraceOwner().Trace(HitLocation, HitNormal, TargetLoc, ProjStart, true,,, TRACEFLAG_Bullet))
{
return VeryFar;
}
return (HitLocation - ProjStart) Dot X;
}
`if(`__TW_LIGHTING_MODIFICATIONS_) // Custom lighting channel implementation
/** Set the lighting channels on all the appropriate weapon meshes */
simulated function SetMeshLightingChannels(LightingChannelContainer NewLightingChannels)
{
}
`endif
defaultproperties
{
// Weapons often fire physics impulses which are invalid during physics ticking
TickGroup=TG_PreAsyncWork
Components.Remove(Sprite)
bOnlyRelevantToOwner=true
DroppedPickupClass=class'DroppedPickup'
RespawnTime=+00030.000000
bHidden=true
MessageClass=class'LocalMessage'
bCanThrow=true
bReplicateInstigator=true
bOnlyDirtyReplication=false
RemoteRole=ROLE_SimulatedProxy
AIRating=+0.5
EquipTime=+0.33
PutDownTime=+0.33
WeaponRange=16384
DefaultAnimSpeed=1.0
}