You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
558 lines
15 KiB
558 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "cbase.h"
|
|
#include "decals.h"
|
|
#include "basegrenade_shared.h"
|
|
#include "shake.h"
|
|
#include "engine/IEngineSound.h"
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
#include "soundent.h"
|
|
#include "entitylist.h"
|
|
#include "gamestats.h"
|
|
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the fireball
|
|
extern short g_sModelIndexWExplosion; // (in combatweapon.cpp) holds the index for the underwater explosion
|
|
extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud
|
|
extern ConVar sk_plr_dmg_grenade;
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
// Global Savedata for friction modifier
|
|
BEGIN_DATADESC( CBaseGrenade )
|
|
// nextGrenade
|
|
DEFINE_FIELD( m_hThrower, FIELD_EHANDLE ),
|
|
// m_fRegisteredSound ???
|
|
DEFINE_FIELD( m_bIsLive, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_DmgRadius, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flDetonateTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flWarnAITime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flDamage, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_iszBounceSound, FIELD_STRING ),
|
|
DEFINE_FIELD( m_bHasWarnedAI, FIELD_BOOLEAN ),
|
|
|
|
// Function Pointers
|
|
DEFINE_THINKFUNC( Smoke ),
|
|
DEFINE_ENTITYFUNC( BounceTouch ),
|
|
DEFINE_ENTITYFUNC( SlideTouch ),
|
|
DEFINE_ENTITYFUNC( ExplodeTouch ),
|
|
DEFINE_USEFUNC( DetonateUse ),
|
|
DEFINE_THINKFUNC( DangerSoundThink ),
|
|
DEFINE_THINKFUNC( PreDetonate ),
|
|
DEFINE_THINKFUNC( Detonate ),
|
|
DEFINE_THINKFUNC( TumbleThink ),
|
|
|
|
END_DATADESC()
|
|
|
|
void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID);
|
|
|
|
#endif
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( BaseGrenade, DT_BaseGrenade )
|
|
|
|
BEGIN_NETWORK_TABLE( CBaseGrenade, DT_BaseGrenade )
|
|
#if !defined( CLIENT_DLL )
|
|
SendPropFloat( SENDINFO( m_flDamage ), 10, SPROP_ROUNDDOWN, 0.0, 256.0f ),
|
|
SendPropFloat( SENDINFO( m_DmgRadius ), 10, SPROP_ROUNDDOWN, 0.0, 1024.0f ),
|
|
SendPropInt( SENDINFO( m_bIsLive ), 1, SPROP_UNSIGNED ),
|
|
// SendPropTime( SENDINFO( m_flDetonateTime ) ),
|
|
SendPropEHandle( SENDINFO( m_hThrower ) ),
|
|
|
|
SendPropVector( SENDINFO( m_vecVelocity ), 0, SPROP_NOSCALE ),
|
|
// HACK: Use same flag bits as player for now
|
|
SendPropInt ( SENDINFO(m_fFlags), PLAYER_FLAG_BITS, SPROP_UNSIGNED, SendProxy_CropFlagsToPlayerFlagBitsLength ),
|
|
#else
|
|
RecvPropFloat( RECVINFO( m_flDamage ) ),
|
|
RecvPropFloat( RECVINFO( m_DmgRadius ) ),
|
|
RecvPropInt( RECVINFO( m_bIsLive ) ),
|
|
// RecvPropTime( RECVINFO( m_flDetonateTime ) ),
|
|
RecvPropEHandle( RECVINFO( m_hThrower ) ),
|
|
|
|
// Need velocity from grenades to make animation system work correctly when running
|
|
RecvPropVector( RECVINFO(m_vecVelocity), 0, RecvProxy_LocalVelocity ),
|
|
|
|
RecvPropInt( RECVINFO( m_fFlags ) ),
|
|
#endif
|
|
END_NETWORK_TABLE()
|
|
|
|
LINK_ENTITY_TO_CLASS( grenade, CBaseGrenade );
|
|
|
|
#if defined( CLIENT_DLL )
|
|
|
|
BEGIN_PREDICTION_DATA( CBaseGrenade )
|
|
|
|
DEFINE_PRED_FIELD( m_hThrower, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bIsLive, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_DmgRadius, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
// DEFINE_PRED_FIELD_TOL( m_flDetonateTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
|
|
DEFINE_PRED_FIELD( m_flDamage, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
|
|
|
DEFINE_PRED_FIELD_TOL( m_vecVelocity, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.5f ),
|
|
DEFINE_PRED_FIELD_TOL( m_flNextAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ),
|
|
|
|
// DEFINE_FIELD( m_fRegisteredSound, FIELD_BOOLEAN ),
|
|
// DEFINE_FIELD( m_iszBounceSound, FIELD_STRING ),
|
|
|
|
END_PREDICTION_DATA()
|
|
|
|
#endif
|
|
|
|
// Grenades flagged with this will be triggered when the owner calls detonateSatchelCharges
|
|
#define SF_DETONATE 0x0001
|
|
|
|
// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution.
|
|
void CBaseGrenade::Explode( trace_t *pTrace, int bitsDamageType )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
SetModelName( NULL_STRING );//invisible
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
|
|
m_takedamage = DAMAGE_NO;
|
|
|
|
// Pull out of the wall a bit
|
|
if ( pTrace->fraction != 1.0 )
|
|
{
|
|
SetAbsOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) );
|
|
}
|
|
|
|
Vector vecAbsOrigin = GetAbsOrigin();
|
|
int contents = UTIL_PointContents ( vecAbsOrigin );
|
|
|
|
#if defined( TF_DLL )
|
|
// Since this code only runs on the server, make sure it shows the tempents it creates.
|
|
// This solves a problem with remote detonating the pipebombs (client wasn't seeing the explosion effect)
|
|
CDisablePredictionFiltering disabler;
|
|
#endif
|
|
|
|
if ( pTrace->fraction != 1.0 )
|
|
{
|
|
Vector vecNormal = pTrace->plane.normal;
|
|
surfacedata_t *pdata = physprops->GetSurfaceData( pTrace->surface.surfaceProps );
|
|
CPASFilter filter( vecAbsOrigin );
|
|
|
|
te->Explosion( filter, -1.0, // don't apply cl_interp delay
|
|
&vecAbsOrigin,
|
|
!( contents & MASK_WATER ) ? g_sModelIndexFireball : g_sModelIndexWExplosion,
|
|
m_DmgRadius * .03,
|
|
25,
|
|
TE_EXPLFLAG_NONE,
|
|
m_DmgRadius,
|
|
m_flDamage,
|
|
&vecNormal,
|
|
(char) pdata->game.material );
|
|
}
|
|
else
|
|
{
|
|
CPASFilter filter( vecAbsOrigin );
|
|
te->Explosion( filter, -1.0, // don't apply cl_interp delay
|
|
&vecAbsOrigin,
|
|
!( contents & MASK_WATER ) ? g_sModelIndexFireball : g_sModelIndexWExplosion,
|
|
m_DmgRadius * .03,
|
|
25,
|
|
TE_EXPLFLAG_NONE,
|
|
m_DmgRadius,
|
|
m_flDamage );
|
|
}
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 );
|
|
#endif
|
|
|
|
// Use the thrower's position as the reported position
|
|
Vector vecReported = m_hThrower ? m_hThrower->GetAbsOrigin() : vec3_origin;
|
|
|
|
CTakeDamageInfo info( this, m_hThrower, GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, 0, &vecReported );
|
|
|
|
RadiusDamage( info, GetAbsOrigin(), m_DmgRadius, CLASS_NONE, NULL );
|
|
|
|
UTIL_DecalTrace( pTrace, "Scorch" );
|
|
|
|
EmitSound( "BaseGrenade.Explode" );
|
|
|
|
SetThink( &CBaseGrenade::SUB_Remove );
|
|
SetTouch( NULL );
|
|
SetSolid( SOLID_NONE );
|
|
|
|
AddEffects( EF_NODRAW );
|
|
SetAbsVelocity( vec3_origin );
|
|
|
|
#if HL2_EPISODIC
|
|
// Because the grenade is zipped out of the world instantly, the EXPLOSION sound that it makes for
|
|
// the AI is also immediately destroyed. For this reason, we now make the grenade entity inert and
|
|
// throw it away in 1/10th of a second instead of right away. Removing the grenade instantly causes
|
|
// intermittent bugs with env_microphones who are listening for explosions. They will 'randomly' not
|
|
// hear explosion sounds when the grenade is removed and the SoundEnt thinks (and removes the sound)
|
|
// before the env_microphone thinks and hears the sound.
|
|
SetNextThink( gpGlobals->curtime + 0.1 );
|
|
#else
|
|
SetNextThink( gpGlobals->curtime );
|
|
#endif//HL2_EPISODIC
|
|
|
|
#if defined( HL2_DLL )
|
|
CBasePlayer *pPlayer = ToBasePlayer( m_hThrower.Get() );
|
|
if ( pPlayer )
|
|
{
|
|
gamestats->Event_WeaponHit( pPlayer, true, "weapon_frag", info );
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
void CBaseGrenade::Smoke( void )
|
|
{
|
|
Vector vecAbsOrigin = GetAbsOrigin();
|
|
if ( UTIL_PointContents ( vecAbsOrigin ) & MASK_WATER )
|
|
{
|
|
UTIL_Bubbles( vecAbsOrigin - Vector( 64, 64, 64 ), vecAbsOrigin + Vector( 64, 64, 64 ), 100 );
|
|
}
|
|
else
|
|
{
|
|
CPVSFilter filter( vecAbsOrigin );
|
|
|
|
te->Smoke( filter, 0.0,
|
|
&vecAbsOrigin, g_sModelIndexSmoke,
|
|
m_DmgRadius * 0.03,
|
|
24 );
|
|
}
|
|
#if !defined( CLIENT_DLL )
|
|
SetThink ( &CBaseGrenade::SUB_Remove );
|
|
#endif
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
|
|
void CBaseGrenade::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
Detonate( );
|
|
}
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseGrenade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
// Support player pickup
|
|
if ( useType == USE_TOGGLE )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( pActivator );
|
|
if ( pPlayer )
|
|
{
|
|
pPlayer->PickupObject( this );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Pass up so we still call any custom Use function
|
|
BaseClass::Use( pActivator, pCaller, useType, value );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Timed grenade, this think is called when time runs out.
|
|
//-----------------------------------------------------------------------------
|
|
void CBaseGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
SetThink( &CBaseGrenade::Detonate );
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
|
|
void CBaseGrenade::PreDetonate( void )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 1.5, this );
|
|
#endif
|
|
|
|
SetThink( &CBaseGrenade::Detonate );
|
|
SetNextThink( gpGlobals->curtime + 1.5 );
|
|
}
|
|
|
|
|
|
void CBaseGrenade::Detonate( void )
|
|
{
|
|
trace_t tr;
|
|
Vector vecSpot;// trace starts here!
|
|
|
|
SetThink( NULL );
|
|
|
|
vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );
|
|
UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr);
|
|
|
|
if( tr.startsolid )
|
|
{
|
|
// Since we blindly moved the explosion origin vertically, we may have inadvertently moved the explosion into a solid,
|
|
// in which case nothing is going to be harmed by the grenade's explosion because all subsequent traces will startsolid.
|
|
// If this is the case, we do the downward trace again from the actual origin of the grenade. (sjb) 3/8/2007 (for ep2_outland_09)
|
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -32), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
|
|
}
|
|
|
|
Explode( &tr, DMG_BLAST );
|
|
|
|
if ( GetShakeAmplitude() )
|
|
{
|
|
UTIL_ScreenShake( GetAbsOrigin(), GetShakeAmplitude(), 150.0, 1.0, GetShakeRadius(), SHAKE_START );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Contact grenade, explode when it touches something
|
|
//
|
|
void CBaseGrenade::ExplodeTouch( CBaseEntity *pOther )
|
|
{
|
|
trace_t tr;
|
|
Vector vecSpot;// trace starts here!
|
|
|
|
Assert( pOther );
|
|
if ( !pOther->IsSolid() )
|
|
return;
|
|
|
|
Vector velDir = GetAbsVelocity();
|
|
VectorNormalize( velDir );
|
|
vecSpot = GetAbsOrigin() - velDir * 32;
|
|
UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
Explode( &tr, DMG_BLAST );
|
|
}
|
|
|
|
|
|
void CBaseGrenade::DangerSoundThink( void )
|
|
{
|
|
if (!IsInWorld())
|
|
{
|
|
Remove( );
|
|
return;
|
|
}
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * 0.5, GetAbsVelocity().Length( ), 0.2, this );
|
|
#endif
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.2 );
|
|
|
|
if (GetWaterLevel() != 0)
|
|
{
|
|
SetAbsVelocity( GetAbsVelocity() * 0.5 );
|
|
}
|
|
}
|
|
|
|
|
|
void CBaseGrenade::BounceTouch( CBaseEntity *pOther )
|
|
{
|
|
if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
|
|
return;
|
|
|
|
// don't hit the guy that launched this grenade
|
|
if ( pOther == GetThrower() )
|
|
return;
|
|
|
|
// only do damage if we're moving fairly fast
|
|
if ( (pOther->m_takedamage != DAMAGE_NO) && (m_flNextAttack < gpGlobals->curtime && GetAbsVelocity().Length() > 100))
|
|
{
|
|
if (m_hThrower)
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
trace_t tr;
|
|
tr = CBaseEntity::GetTouchTrace( );
|
|
ClearMultiDamage( );
|
|
Vector forward;
|
|
AngleVectors( GetLocalAngles(), &forward, NULL, NULL );
|
|
CTakeDamageInfo info( this, m_hThrower, 1, DMG_CLUB );
|
|
CalculateMeleeDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );
|
|
pOther->DispatchTraceAttack( info, forward, &tr );
|
|
ApplyMultiDamage();
|
|
#endif
|
|
}
|
|
m_flNextAttack = gpGlobals->curtime + 1.0; // debounce
|
|
}
|
|
|
|
Vector vecTestVelocity;
|
|
// m_vecAngVelocity = Vector (300, 300, 300);
|
|
|
|
// this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical
|
|
// or thrown very far tend to slow down too quickly for me to always catch just by testing velocity.
|
|
// trimming the Z velocity a bit seems to help quite a bit.
|
|
vecTestVelocity = GetAbsVelocity();
|
|
vecTestVelocity.z *= 0.45;
|
|
|
|
if ( !m_bHasWarnedAI && vecTestVelocity.Length() <= 60 )
|
|
{
|
|
// grenade is moving really slow. It's probably very close to where it will ultimately stop moving.
|
|
// emit the danger sound.
|
|
|
|
// register a radius louder than the explosion, so we make sure everyone gets out of the way
|
|
#if !defined( CLIENT_DLL )
|
|
CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), m_flDamage / 0.4, 0.3, this );
|
|
#endif
|
|
m_bHasWarnedAI = true;
|
|
}
|
|
|
|
if (GetFlags() & FL_ONGROUND)
|
|
{
|
|
// add a bit of static friction
|
|
// SetAbsVelocity( GetAbsVelocity() * 0.8 );
|
|
|
|
// SetSequence( random->RandomInt( 1, 1 ) ); // FIXME: missing tumble animations
|
|
}
|
|
else
|
|
{
|
|
// play bounce sound
|
|
BounceSound();
|
|
}
|
|
m_flPlaybackRate = GetAbsVelocity().Length() / 200.0;
|
|
if (m_flPlaybackRate > 1.0)
|
|
m_flPlaybackRate = 1;
|
|
else if (m_flPlaybackRate < 0.5)
|
|
m_flPlaybackRate = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CBaseGrenade::SlideTouch( CBaseEntity *pOther )
|
|
{
|
|
// don't hit the guy that launched this grenade
|
|
if ( pOther == GetThrower() )
|
|
return;
|
|
|
|
// m_vecAngVelocity = Vector (300, 300, 300);
|
|
|
|
if (GetFlags() & FL_ONGROUND)
|
|
{
|
|
// add a bit of static friction
|
|
// SetAbsVelocity( GetAbsVelocity() * 0.95 );
|
|
|
|
if (GetAbsVelocity().x != 0 || GetAbsVelocity().y != 0)
|
|
{
|
|
// maintain sliding sound
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BounceSound();
|
|
}
|
|
}
|
|
|
|
void CBaseGrenade ::BounceSound( void )
|
|
{
|
|
// Doesn't need to do anything anymore! Physics makes the sound.
|
|
}
|
|
|
|
void CBaseGrenade ::TumbleThink( void )
|
|
{
|
|
if (!IsInWorld())
|
|
{
|
|
Remove( );
|
|
return;
|
|
}
|
|
|
|
StudioFrameAdvance( );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
//
|
|
// Emit a danger sound one second before exploding.
|
|
//
|
|
if (m_flDetonateTime - 1 < gpGlobals->curtime)
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin() + GetAbsVelocity() * (m_flDetonateTime - gpGlobals->curtime), 400, 0.1, this );
|
|
#endif
|
|
}
|
|
|
|
if (m_flDetonateTime <= gpGlobals->curtime)
|
|
{
|
|
SetThink( &CBaseGrenade::Detonate );
|
|
}
|
|
|
|
if (GetWaterLevel() != 0)
|
|
{
|
|
SetAbsVelocity( GetAbsVelocity() * 0.5 );
|
|
m_flPlaybackRate = 0.2;
|
|
}
|
|
}
|
|
|
|
void CBaseGrenade::Precache( void )
|
|
{
|
|
BaseClass::Precache( );
|
|
|
|
PrecacheScriptSound( "BaseGrenade.Explode" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : CBaseCombatCharacter
|
|
//-----------------------------------------------------------------------------
|
|
CBaseCombatCharacter *CBaseGrenade::GetThrower( void )
|
|
{
|
|
CBaseCombatCharacter *pResult = ToBaseCombatCharacter( m_hThrower );
|
|
if ( !pResult && GetOwnerEntity() != NULL )
|
|
{
|
|
pResult = ToBaseCombatCharacter( GetOwnerEntity() );
|
|
}
|
|
return pResult;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CBaseGrenade::SetThrower( CBaseCombatCharacter *pThrower )
|
|
{
|
|
m_hThrower = pThrower;
|
|
|
|
// if this is the first thrower, set it as the original thrower
|
|
if ( NULL == m_hOriginalThrower )
|
|
{
|
|
m_hOriginalThrower = pThrower;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
CBaseGrenade::~CBaseGrenade(void)
|
|
{
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
CBaseGrenade::CBaseGrenade(void)
|
|
{
|
|
m_hThrower = NULL;
|
|
m_hOriginalThrower = NULL;
|
|
m_bIsLive = false;
|
|
m_DmgRadius = 100;
|
|
m_flDetonateTime = 0;
|
|
m_bHasWarnedAI = false;
|
|
|
|
SetSimulatedEveryTick( true );
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|