1963 lines
53 KiB
Ucode
1963 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);
|
|
|
|
/**
|
|
* This function returns the world location for spawning the projectile.
|
|
*/
|
|
simulated native event vector GetFixedPhysicalFireStartLoc();
|
|
|
|
/**
|
|
* 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
|
|
}
|