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.
3029 lines
85 KiB
3029 lines
85 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "npcevent.h" |
|
#include "soundenvelope.h" |
|
#include "ai_hint.h" |
|
#include "ai_moveprobe.h" |
|
#include "ai_squad.h" |
|
#include "beam_shared.h" |
|
#include "globalstate.h" |
|
#include "soundent.h" |
|
#include "npc_citizen17.h" |
|
#include "gib.h" |
|
#include "spotlightend.h" |
|
#include "IEffects.h" |
|
#include "items.h" |
|
#include "ai_route.h" |
|
#include "player_pickup.h" |
|
#include "weapon_physcannon.h" |
|
#include "hl2_player.h" |
|
#include "npc_scanner.h" |
|
#include "materialsystem/imaterialsystemhardwareconfig.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Singleton interfaces |
|
//----------------------------------------------------------------------------- |
|
extern IMaterialSystemHardwareConfig *g_pMaterialSystemHardwareConfig; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Parameters for how the scanner relates to citizens. |
|
//----------------------------------------------------------------------------- |
|
#define SCANNER_CIT_INSPECT_DELAY 10 // Check for citizens this often |
|
#define SCANNER_CIT_INSPECT_GROUND_DIST 500 // How far to look for citizens to inspect |
|
#define SCANNER_CIT_INSPECT_FLY_DIST 1500 // How far to look for citizens to inspect |
|
|
|
#define SCANNER_CIT_INSPECT_LENGTH 5 // How long does the inspection last |
|
#define SCANNER_HINT_INSPECT_LENGTH 5 // How long does the inspection last |
|
#define SCANNER_SOUND_INSPECT_LENGTH 5 // How long does the inspection last |
|
|
|
#define SCANNER_HINT_INSPECT_DELAY 15 // Check for hint nodes this often |
|
|
|
#define SPOTLIGHT_WIDTH 32 |
|
|
|
#define SCANNER_SPOTLIGHT_NEAR_DIST 64 |
|
#define SCANNER_SPOTLIGHT_FAR_DIST 256 |
|
#define SCANNER_SPOTLIGHT_FLY_HEIGHT 72 |
|
#define SCANNER_NOSPOTLIGHT_FLY_HEIGHT 72 |
|
|
|
#define SCANNER_FLASH_MIN_DIST 900 // How far does flash effect enemy |
|
#define SCANNER_FLASH_MAX_DIST 1200 // How far does flash effect enemy |
|
|
|
#define SCANNER_FLASH_MAX_VALUE 240 // How bright is maximum flash |
|
|
|
#define SCANNER_PHOTO_NEAR_DIST 64 |
|
#define SCANNER_PHOTO_FAR_DIST 128 |
|
|
|
#define SCANNER_FOLLOW_DIST 128 |
|
|
|
#define SCANNER_NUM_GIBS 6 // Number of gibs in gib file |
|
|
|
// Strider Scout Scanners |
|
#define SCANNER_SCOUT_MAX_SPEED 150 |
|
|
|
ConVar sk_scanner_health( "sk_scanner_health","0"); |
|
ConVar g_debug_cscanner( "g_debug_cscanner", "0" ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Private activities. |
|
//----------------------------------------------------------------------------- |
|
static int ACT_SCANNER_SMALL_FLINCH_ALERT = 0; |
|
static int ACT_SCANNER_SMALL_FLINCH_COMBAT = 0; |
|
static int ACT_SCANNER_INSPECT = 0; |
|
static int ACT_SCANNER_WALK_ALERT = 0; |
|
static int ACT_SCANNER_WALK_COMBAT = 0; |
|
static int ACT_SCANNER_FLARE = 0; |
|
static int ACT_SCANNER_RETRACT = 0; |
|
static int ACT_SCANNER_FLARE_PRONGS = 0; |
|
static int ACT_SCANNER_RETRACT_PRONGS = 0; |
|
static int ACT_SCANNER_FLARE_START = 0; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Interactions |
|
//----------------------------------------------------------------------------- |
|
int g_interactionScannerInspect = 0; |
|
int g_interactionScannerInspectBegin = 0; |
|
int g_interactionScannerInspectHandsUp = 0; |
|
int g_interactionScannerInspectShowArmband = 0;//<<TEMP>>still to be completed |
|
int g_interactionScannerInspectDone = 0; |
|
int g_interactionScannerSupportEntity = 0; |
|
int g_interactionScannerSupportPosition = 0; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Animation events |
|
//------------------------------------------------------------------------ |
|
int AE_SCANNER_CLOSED; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Attachment points |
|
//----------------------------------------------------------------------------- |
|
#define SCANNER_ATTACHMENT_LIGHT "light" |
|
#define SCANNER_ATTACHMENT_FLASH 1 |
|
#define SCANNER_ATTACHMENT_LPRONG 2 |
|
#define SCANNER_ATTACHMENT_RPRONG 3 |
|
|
|
//----------------------------------------------------------------------------- |
|
// Other defines. |
|
//----------------------------------------------------------------------------- |
|
#define SCANNER_MAX_BEAMS 4 |
|
|
|
BEGIN_DATADESC( CNPC_CScanner ) |
|
|
|
DEFINE_SOUNDPATCH( m_pEngineSound ), |
|
|
|
DEFINE_EMBEDDED( m_KilledInfo ), |
|
DEFINE_FIELD( m_flGoalOverrideDistance, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_bPhotoTaken, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_vInspectPos, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_fInspectEndTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_fCheckCitizenTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_fCheckHintTime, FIELD_TIME ), |
|
DEFINE_KEYFIELD( m_bShouldInspect, FIELD_BOOLEAN, "ShouldInspect" ), |
|
DEFINE_KEYFIELD( m_bOnlyInspectPlayers, FIELD_BOOLEAN, "OnlyInspectPlayers" ), |
|
DEFINE_KEYFIELD( m_bNeverInspectPlayers,FIELD_BOOLEAN, "NeverInspectPlayers" ), |
|
DEFINE_FIELD( m_fNextPhotographTime, FIELD_TIME ), |
|
// DEFINE_FIELD( m_pEyeFlash, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( m_vSpotlightTargetPos, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vSpotlightCurrentPos, FIELD_POSITION_VECTOR ), |
|
// don't save (recreated after restore/transition) |
|
// DEFINE_FIELD( m_hSpotlight, FIELD_EHANDLE ), |
|
// DEFINE_FIELD( m_hSpotlightTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_vSpotlightDir, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vSpotlightAngVelocity, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flSpotlightCurLength, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_fNextSpotlightTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_nHaloSprite, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_fNextFlySoundTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_nFlyMode, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nPoseTail, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nPoseDynamo, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nPoseFlare, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nPoseFaceVert, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nPoseFaceHoriz, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_bIsClawScanner, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bIsOpen, FIELD_BOOLEAN ), |
|
|
|
// DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ), |
|
|
|
DEFINE_FIELD( m_pSmokeTrail, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( m_flFlyNoiseBase, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_vecDiveBombDirection, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flDiveBombRollForce, FIELD_FLOAT ), |
|
|
|
DEFINE_KEYFIELD( m_flSpotlightMaxLength, FIELD_FLOAT, "SpotlightLength"), |
|
DEFINE_KEYFIELD( m_flSpotlightGoalWidth, FIELD_FLOAT, "SpotlightWidth"), |
|
|
|
// Physics Influence |
|
DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), |
|
|
|
DEFINE_KEYFIELD( m_bNoLight, FIELD_BOOLEAN, "SpotlightDisabled" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpotlight", InputDisableSpotlight ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "InspectTargetPhoto", InputInspectTargetPhoto ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "InspectTargetSpotlight", InputInspectTargetSpotlight ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "InputShouldInspect", InputShouldInspect ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetFollowTarget", InputSetFollowTarget ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearFollowTarget", InputClearFollowTarget ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "DeployMine", InputDeployMine ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "EquipMine", InputEquipMine ), |
|
|
|
DEFINE_OUTPUT( m_OnPhotographPlayer, "OnPhotographPlayer" ), |
|
DEFINE_OUTPUT( m_OnPhotographNPC, "OnPhotographNPC" ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
LINK_ENTITY_TO_CLASS(npc_cscanner, CNPC_CScanner); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CNPC_CScanner::CNPC_CScanner() |
|
{ |
|
#ifdef _DEBUG |
|
m_vInspectPos.Init(); |
|
m_vSpotlightTargetPos.Init(); |
|
m_vSpotlightCurrentPos.Init(); |
|
m_vSpotlightDir.Init(); |
|
m_vSpotlightAngVelocity.Init(); |
|
#endif |
|
m_bShouldInspect = true; |
|
m_bOnlyInspectPlayers = false; |
|
m_bNeverInspectPlayers = false; |
|
|
|
char szMapName[256]; |
|
Q_strncpy(szMapName, STRING(gpGlobals->mapname), sizeof(szMapName) ); |
|
Q_strlower(szMapName); |
|
|
|
if( !Q_strnicmp( szMapName, "d3_c17", 6 ) ) |
|
{ |
|
// Streetwar scanners are claw scanners |
|
m_bIsClawScanner = true; |
|
} |
|
else |
|
{ |
|
m_bIsClawScanner = false; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::Spawn(void) |
|
{ |
|
// Check for user error |
|
if (m_flSpotlightMaxLength <= 0) |
|
{ |
|
DevMsg("CNPC_CScanner::Spawn: Invalid spotlight length <= 0, setting to 500\n"); |
|
m_flSpotlightMaxLength = 500; |
|
} |
|
|
|
if (m_flSpotlightGoalWidth <= 0) |
|
{ |
|
DevMsg("CNPC_CScanner::Spawn: Invalid spotlight width <= 0, setting to 100\n"); |
|
m_flSpotlightGoalWidth = 100; |
|
} |
|
|
|
if (m_flSpotlightGoalWidth > MAX_BEAM_WIDTH ) |
|
{ |
|
DevMsg("CNPC_CScanner::Spawn: Invalid spotlight width %.1f (max %.1f).\n", m_flSpotlightGoalWidth, MAX_BEAM_WIDTH ); |
|
m_flSpotlightGoalWidth = MAX_BEAM_WIDTH; |
|
} |
|
|
|
Precache(); |
|
|
|
if( m_bIsClawScanner ) |
|
{ |
|
SetModel( "models/shield_scanner.mdl"); |
|
} |
|
else |
|
{ |
|
SetModel( "models/combine_scanner.mdl"); |
|
} |
|
|
|
m_iHealth = sk_scanner_health.GetFloat(); |
|
m_iMaxHealth = m_iHealth; |
|
|
|
// ------------------------------------ |
|
// Init all class vars |
|
// ------------------------------------ |
|
m_vInspectPos = vec3_origin; |
|
m_fInspectEndTime = 0; |
|
m_fCheckCitizenTime = gpGlobals->curtime + SCANNER_CIT_INSPECT_DELAY; |
|
m_fCheckHintTime = gpGlobals->curtime + SCANNER_HINT_INSPECT_DELAY; |
|
m_fNextPhotographTime = 0; |
|
|
|
m_vSpotlightTargetPos = vec3_origin; |
|
m_vSpotlightCurrentPos = vec3_origin; |
|
|
|
m_hSpotlight = NULL; |
|
m_hSpotlightTarget = NULL; |
|
|
|
AngleVectors( GetLocalAngles(), &m_vSpotlightDir ); |
|
m_vSpotlightAngVelocity = vec3_origin; |
|
|
|
m_pEyeFlash = 0; |
|
m_fNextSpotlightTime = 0; |
|
m_nFlyMode = SCANNER_FLY_PATROL; |
|
m_vCurrentBanking = m_vSpotlightDir; |
|
m_flSpotlightCurLength = m_flSpotlightMaxLength; |
|
|
|
m_nPoseTail = LookupPoseParameter( "tail_control" ); |
|
m_nPoseDynamo = LookupPoseParameter( "dynamo_wheel" ); |
|
m_nPoseFlare = LookupPoseParameter( "alert_control" ); |
|
m_nPoseFaceVert = LookupPoseParameter( "flex_vert" ); |
|
m_nPoseFaceHoriz = LookupPoseParameter( "flex_horz" ); |
|
|
|
// -------------------------------------------- |
|
|
|
CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 ); |
|
|
|
m_bPhotoTaken = false; |
|
|
|
BaseClass::Spawn(); |
|
|
|
// Watch for this error state |
|
if ( m_bOnlyInspectPlayers && m_bNeverInspectPlayers ) |
|
{ |
|
Assert( 0 ); |
|
Warning( "ERROR: Scanner set to never and always inspect players!\n" ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::Activate() |
|
{ |
|
BaseClass::Activate(); |
|
|
|
// Have to do this here because sprites do not go across level transitions |
|
m_pEyeFlash = CSprite::SpriteCreate( "sprites/blueflare1.vmt", GetLocalOrigin(), FALSE ); |
|
m_pEyeFlash->SetTransparency( kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation ); |
|
m_pEyeFlash->SetAttachment( this, LookupAttachment( SCANNER_ATTACHMENT_LIGHT ) ); |
|
m_pEyeFlash->SetBrightness( 0 ); |
|
m_pEyeFlash->SetScale( 1.4 ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Override to split in two when attacked |
|
//------------------------------------------------------------------------------ |
|
int CNPC_CScanner::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
// Turn off my spotlight when shot |
|
SpotlightDestroy(); |
|
m_fNextSpotlightTime = gpGlobals->curtime + 2.0f; |
|
|
|
return (BaseClass::OnTakeDamage_Alive( info )); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::Gib( void ) |
|
{ |
|
if ( IsMarkedForDeletion() ) |
|
return; |
|
|
|
// Spawn all gibs |
|
if( m_bIsClawScanner ) |
|
{ |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib1.mdl"); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib2.mdl"); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib3.mdl"); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib4.mdl"); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib5.mdl"); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib6.mdl"); |
|
} |
|
else |
|
{ |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/scanner_gib01.mdl" ); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/scanner_gib02.mdl" ); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/scanner_gib04.mdl" ); |
|
CGib::SpawnSpecificGibs( this, 1, 500, 250, "models/gibs/scanner_gib05.mdl" ); |
|
} |
|
|
|
// Add a random chance of spawning a battery... |
|
if ( !HasSpawnFlags(SF_NPC_NO_WEAPON_DROP) && random->RandomFloat( 0.0f, 1.0f) < 0.3f ) |
|
{ |
|
CItem *pBattery = (CItem*)CreateEntityByName("item_battery"); |
|
if ( pBattery ) |
|
{ |
|
pBattery->SetAbsOrigin( GetAbsOrigin() ); |
|
pBattery->SetAbsVelocity( GetAbsVelocity() ); |
|
pBattery->SetLocalAngularVelocity( GetLocalAngularVelocity() ); |
|
pBattery->ActivateWhenAtRest(); |
|
pBattery->Spawn(); |
|
} |
|
} |
|
|
|
DeployMine(); |
|
|
|
BaseClass::Gib(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pInflictor - |
|
// pAttacker - |
|
// flDamage - |
|
// bitsDamageType - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
// Copy off the takedamage info that killed me, since we're not going to call |
|
// up into the base class's Event_Killed() until we gib. (gibbing is ultimate death) |
|
m_KilledInfo = info; |
|
|
|
DeployMine(); |
|
|
|
ClearInspectTarget(); |
|
|
|
// Interrupt whatever schedule I'm on |
|
SetCondition(COND_SCHEDULE_DONE); |
|
|
|
// Remove spotlight |
|
SpotlightDestroy(); |
|
|
|
// Remove sprite |
|
UTIL_Remove(m_pEyeFlash); |
|
m_pEyeFlash = NULL; |
|
|
|
// If I have an enemy and I'm up high, do a dive bomb (unless dissolved) |
|
if ( !m_bIsClawScanner && GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false ) |
|
{ |
|
Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin(); |
|
if ( ( vecDelta.z > 120 ) && ( vecDelta.Length() > 360 ) ) |
|
{ |
|
// If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again. |
|
// This is especially bad if someone machineguns the divebombing scanner. |
|
AttackDivebomb(); |
|
return; |
|
} |
|
} |
|
|
|
Gib(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tells use whether or not the NPC cares about a given type of hint node. |
|
// Input : sHint - |
|
// Output : TRUE if the NPC is interested in this hint type, FALSE if not. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_CScanner::FValidateHintType(CAI_Hint *pHint) |
|
{ |
|
return( pHint->HintType() == HINT_WORLD_WINDOW ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : Type - |
|
//----------------------------------------------------------------------------- |
|
int CNPC_CScanner::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch ( scheduleType ) |
|
{ |
|
case SCHED_IDLE_STAND: |
|
{ |
|
return SCHED_SCANNER_PATROL; |
|
} |
|
|
|
case SCHED_SCANNER_PATROL: |
|
return SCHED_CSCANNER_PATROL; |
|
} |
|
return BaseClass::TranslateSchedule(scheduleType); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : idealActivity - |
|
// *pIdealWeaponActivity - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
Activity CNPC_CScanner::NPC_TranslateActivity( Activity eNewActivity ) |
|
{ |
|
if( !m_bIsClawScanner ) |
|
{ |
|
return BaseClass::NPC_TranslateActivity( eNewActivity ); |
|
} |
|
|
|
// The claw scanner came along a little late and doesn't have the activities |
|
// of the city scanner. So Just pick between these three |
|
if( eNewActivity == ACT_DISARM ) |
|
{ |
|
// Closing up. |
|
return eNewActivity; |
|
} |
|
|
|
if( m_bIsOpen ) |
|
{ |
|
return ACT_IDLE_ANGRY; |
|
} |
|
else |
|
{ |
|
return ACT_IDLE; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
if( pEvent->event == AE_SCANNER_CLOSED ) |
|
{ |
|
m_bIsOpen = false; |
|
SetActivity( ACT_IDLE ); |
|
return; |
|
} |
|
|
|
BaseClass::HandleAnimEvent( pEvent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
char *CNPC_CScanner::GetEngineSound( void ) |
|
{ |
|
if( m_bIsClawScanner ) |
|
return "NPC_SScanner.FlyLoop"; |
|
|
|
return "NPC_CScanner.FlyLoop"; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Plays the engine sound. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::NPCThink(void) |
|
{ |
|
if (!IsAlive()) |
|
{ |
|
SetActivity((Activity)ACT_SCANNER_RETRACT_PRONGS); |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
else |
|
{ |
|
BaseClass::NPCThink(); |
|
SpotlightUpdate(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::Precache(void) |
|
{ |
|
// Model |
|
if( m_bIsClawScanner ) |
|
{ |
|
PrecacheModel("models/shield_scanner.mdl"); |
|
|
|
PrecacheModel("models/gibs/Shield_Scanner_Gib1.mdl"); |
|
PrecacheModel("models/gibs/Shield_Scanner_Gib2.mdl"); |
|
PrecacheModel("models/gibs/Shield_Scanner_Gib3.mdl"); |
|
PrecacheModel("models/gibs/Shield_Scanner_Gib4.mdl"); |
|
PrecacheModel("models/gibs/Shield_Scanner_Gib5.mdl"); |
|
PrecacheModel("models/gibs/Shield_Scanner_Gib6.mdl"); |
|
|
|
PrecacheScriptSound( "NPC_SScanner.Shoot"); |
|
PrecacheScriptSound( "NPC_SScanner.Alert" ); |
|
PrecacheScriptSound( "NPC_SScanner.Die" ); |
|
PrecacheScriptSound( "NPC_SScanner.Combat" ); |
|
PrecacheScriptSound( "NPC_SScanner.Idle" ); |
|
PrecacheScriptSound( "NPC_SScanner.Pain" ); |
|
PrecacheScriptSound( "NPC_SScanner.TakePhoto" ); |
|
PrecacheScriptSound( "NPC_SScanner.AttackFlash" ); |
|
PrecacheScriptSound( "NPC_SScanner.DiveBombFlyby" ); |
|
PrecacheScriptSound( "NPC_SScanner.DiveBomb" ); |
|
PrecacheScriptSound( "NPC_SScanner.DeployMine" ); |
|
|
|
PrecacheScriptSound( "NPC_SScanner.FlyLoop" ); |
|
UTIL_PrecacheOther( "combine_mine" ); |
|
} |
|
else |
|
{ |
|
PrecacheModel("models/combine_scanner.mdl"); |
|
|
|
PrecacheModel("models/gibs/scanner_gib01.mdl" ); |
|
PrecacheModel("models/gibs/scanner_gib02.mdl" ); |
|
PrecacheModel("models/gibs/scanner_gib02.mdl" ); |
|
PrecacheModel("models/gibs/scanner_gib04.mdl" ); |
|
PrecacheModel("models/gibs/scanner_gib05.mdl" ); |
|
|
|
PrecacheScriptSound( "NPC_CScanner.Shoot"); |
|
PrecacheScriptSound( "NPC_CScanner.Alert" ); |
|
PrecacheScriptSound( "NPC_CScanner.Die" ); |
|
PrecacheScriptSound( "NPC_CScanner.Combat" ); |
|
PrecacheScriptSound( "NPC_CScanner.Idle" ); |
|
PrecacheScriptSound( "NPC_CScanner.Pain" ); |
|
PrecacheScriptSound( "NPC_CScanner.TakePhoto" ); |
|
PrecacheScriptSound( "NPC_CScanner.AttackFlash" ); |
|
PrecacheScriptSound( "NPC_CScanner.DiveBombFlyby" ); |
|
PrecacheScriptSound( "NPC_CScanner.DiveBomb" ); |
|
PrecacheScriptSound( "NPC_CScanner.DeployMine" ); |
|
|
|
PrecacheScriptSound( "NPC_CScanner.FlyLoop" ); |
|
} |
|
|
|
// Sprites |
|
m_nHaloSprite = PrecacheModel("sprites/light_glow03.vmt"); |
|
PrecacheModel( "sprites/glow_test02.vmt" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Request help inspecting from other squad members |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::RequestInspectSupport(void) |
|
{ |
|
if (m_pSquad) |
|
{ |
|
AISquadIter_t iter; |
|
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) ) |
|
{ |
|
if (pSquadMember != this) |
|
{ |
|
if (GetTarget()) |
|
{ |
|
pSquadMember->DispatchInteraction(g_interactionScannerSupportEntity,((void *)((CBaseEntity*)GetTarget())),this); |
|
} |
|
else |
|
{ |
|
pSquadMember->DispatchInteraction(g_interactionScannerSupportPosition,((void *)m_vInspectPos.Base()),this); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool CNPC_CScanner::IsValidInspectTarget(CBaseEntity *pEntity) |
|
{ |
|
// If a citizen, make sure he can be inspected again |
|
if (pEntity->Classify() == CLASS_CITIZEN_PASSIVE) |
|
{ |
|
if (((CNPC_Citizen*)pEntity)->GetNextScannerInspectTime() > gpGlobals->curtime) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
// Make sure no other squad member has already chosen to |
|
// inspect this entity |
|
if (m_pSquad) |
|
{ |
|
AISquadIter_t iter; |
|
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) ) |
|
{ |
|
if (pSquadMember->GetTarget() == pEntity) |
|
{ |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
// Do not inspect friendly targets |
|
if ( IRelationType( pEntity ) == D_LI ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
CBaseEntity* CNPC_CScanner::BestInspectTarget(void) |
|
{ |
|
if ( !m_bShouldInspect ) |
|
return NULL; |
|
|
|
CBaseEntity* pBestEntity = NULL; |
|
float fBestDist = MAX_COORD_RANGE; |
|
float fTestDist; |
|
|
|
CBaseEntity *pEntity = NULL; |
|
|
|
// If I have a spotlight, search from the spotlight position |
|
// otherwise search from my position |
|
Vector vSearchOrigin; |
|
float fSearchDist; |
|
if (m_hSpotlightTarget != NULL) |
|
{ |
|
vSearchOrigin = m_hSpotlightTarget->GetAbsOrigin(); |
|
fSearchDist = SCANNER_CIT_INSPECT_GROUND_DIST; |
|
} |
|
else |
|
{ |
|
vSearchOrigin = WorldSpaceCenter(); |
|
fSearchDist = SCANNER_CIT_INSPECT_FLY_DIST; |
|
} |
|
|
|
if ( m_bOnlyInspectPlayers ) |
|
{ |
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
if ( !pPlayer ) |
|
return NULL; |
|
|
|
if ( !pPlayer->IsAlive() || (pPlayer->GetFlags() & FL_NOTARGET) ) |
|
return NULL; |
|
|
|
return WorldSpaceCenter().DistToSqr( pPlayer->EyePosition() ) <= (fSearchDist * fSearchDist) ? pPlayer : NULL; |
|
} |
|
|
|
CUtlVector<CBaseEntity *> candidates; |
|
float fSearchDistSq = fSearchDist * fSearchDist; |
|
int i; |
|
|
|
// Inspect players unless told otherwise |
|
if ( m_bNeverInspectPlayers == false ) |
|
{ |
|
// Players |
|
for ( i = 1; i <= gpGlobals->maxClients; i++ ) |
|
{ |
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
if ( pPlayer ) |
|
{ |
|
if ( vSearchOrigin.DistToSqr(pPlayer->GetAbsOrigin()) < fSearchDistSq ) |
|
{ |
|
candidates.AddToTail( pPlayer ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// NPCs |
|
CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); |
|
|
|
for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) |
|
{ |
|
if ( ppAIs[i] != this && vSearchOrigin.DistToSqr(ppAIs[i]->GetAbsOrigin()) < fSearchDistSq ) |
|
{ |
|
candidates.AddToTail( ppAIs[i] ); |
|
} |
|
} |
|
|
|
for ( i = 0; i < candidates.Count(); i++ ) |
|
{ |
|
pEntity = candidates[i]; |
|
Assert( pEntity != this && (pEntity->MyNPCPointer() || pEntity->IsPlayer() ) ); |
|
|
|
CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); |
|
if ( ( pNPC && pNPC->Classify() == CLASS_CITIZEN_PASSIVE ) || pEntity->IsPlayer() ) |
|
{ |
|
if ( pEntity->GetFlags() & FL_NOTARGET ) |
|
continue; |
|
|
|
if ( pEntity->IsAlive() == false ) |
|
continue; |
|
|
|
// Ensure it's within line of sight |
|
if ( !FVisible( pEntity ) ) |
|
continue; |
|
|
|
fTestDist = ( GetAbsOrigin() - pEntity->EyePosition() ).Length(); |
|
if ( fTestDist < fBestDist ) |
|
{ |
|
if ( IsValidInspectTarget( pEntity ) ) |
|
{ |
|
fBestDist = fTestDist; |
|
pBestEntity = pEntity; |
|
} |
|
} |
|
} |
|
} |
|
return pBestEntity; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Clears any previous inspect target and set inspect target to |
|
// the given entity and set the durection of the inspection |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::SetInspectTargetToEnt(CBaseEntity *pEntity, float fInspectDuration) |
|
{ |
|
ClearInspectTarget(); |
|
SetTarget(pEntity); |
|
|
|
m_fInspectEndTime = gpGlobals->curtime + fInspectDuration; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Clears any previous inspect target and set inspect target to |
|
// the given hint node and set the durection of the inspection |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::SetInspectTargetToHint(CAI_Hint *pHint, float fInspectDuration) |
|
{ |
|
ClearInspectTarget(); |
|
|
|
float yaw = pHint->Yaw(); |
|
// -------------------------------------------- |
|
// Figure out the location that the hint hits |
|
// -------------------------------------------- |
|
Vector vHintDir = UTIL_YawToVector( yaw ); |
|
|
|
Vector vHintOrigin; |
|
pHint->GetPosition( this, &vHintOrigin ); |
|
|
|
Vector vHintEnd = vHintOrigin + (vHintDir * 512); |
|
|
|
trace_t tr; |
|
AI_TraceLine ( vHintOrigin, vHintEnd, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr); |
|
|
|
if ( g_debug_cscanner.GetBool() ) |
|
{ |
|
NDebugOverlay::Line( vHintOrigin, tr.endpos, 255, 0, 0, true, 4.0f ); |
|
NDebugOverlay::Cross3D( tr.endpos, -Vector(8,8,8), Vector(8,8,8), 255, 0, 0, true, 4.0f ); |
|
} |
|
|
|
if (tr.fraction == 1.0f ) |
|
{ |
|
DevMsg("ERROR: Scanner hint node not facing a surface!\n"); |
|
} |
|
else |
|
{ |
|
SetHintNode( pHint ); |
|
m_vInspectPos = tr.endpos; |
|
pHint->Lock( this ); |
|
|
|
m_fInspectEndTime = gpGlobals->curtime + fInspectDuration; |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Clears any previous inspect target and set inspect target to |
|
// the given position and set the durection of the inspection |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration) |
|
{ |
|
ClearInspectTarget(); |
|
m_vInspectPos = vInspectPos; |
|
|
|
m_fInspectEndTime = gpGlobals->curtime + fInspectDuration; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Clears out any previous inspection targets |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::ClearInspectTarget(void) |
|
{ |
|
if ( GetIdealState() != NPC_STATE_SCRIPT ) |
|
{ |
|
SetTarget( NULL ); |
|
} |
|
|
|
ClearHintNode( SCANNER_HINT_INSPECT_LENGTH ); |
|
m_vInspectPos = vec3_origin; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Returns true if there is a position to be inspected. |
|
//------------------------------------------------------------------------------ |
|
bool CNPC_CScanner::HaveInspectTarget( void ) |
|
{ |
|
if ( GetTarget() != NULL ) |
|
return true; |
|
|
|
if ( m_vInspectPos != vec3_origin ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
Vector CNPC_CScanner::InspectTargetPosition(void) |
|
{ |
|
// If we have a target, return an adjust position |
|
if ( GetTarget() != NULL ) |
|
{ |
|
Vector vEyePos = GetTarget()->EyePosition(); |
|
|
|
// If in spotlight mode, aim for ground below target unless is client |
|
if ( m_nFlyMode == SCANNER_FLY_SPOT && !(GetTarget()->GetFlags() & FL_CLIENT) ) |
|
{ |
|
Vector vInspectPos; |
|
vInspectPos.x = vEyePos.x; |
|
vInspectPos.y = vEyePos.y; |
|
vInspectPos.z = GetFloorZ( vEyePos ); |
|
|
|
// Let's take three-quarters between eyes and ground |
|
vInspectPos.z += ( vEyePos.z - vInspectPos.z ) * 0.75f; |
|
|
|
return vInspectPos; |
|
} |
|
else |
|
{ |
|
// Otherwise aim for eyes |
|
return vEyePos; |
|
} |
|
} |
|
else if ( m_vInspectPos != vec3_origin ) |
|
{ |
|
return m_vInspectPos; |
|
} |
|
else |
|
{ |
|
DevMsg("InspectTargetPosition called with no target!\n"); |
|
|
|
return m_vInspectPos; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputShouldInspect( inputdata_t &inputdata ) |
|
{ |
|
m_bShouldInspect = ( inputdata.value.Int() != 0 ); |
|
|
|
if ( !m_bShouldInspect ) |
|
{ |
|
if ( GetEnemy() == GetTarget() ) |
|
SetEnemy(NULL); |
|
ClearInspectTarget(); |
|
SetTarget(NULL); |
|
SpotlightDestroy(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::DeployMine() |
|
{ |
|
CBaseEntity *child; |
|
// iterate through all children |
|
for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() ) |
|
{ |
|
if( FClassnameIs( child, "combine_mine" ) ) |
|
{ |
|
child->SetParent( NULL ); |
|
child->SetAbsVelocity( GetAbsVelocity() ); |
|
child->SetOwnerEntity( this ); |
|
|
|
ScannerEmitSound( "DeployMine" ); |
|
|
|
IPhysicsObject *pPhysObj = child->VPhysicsGetObject(); |
|
if( pPhysObj ) |
|
{ |
|
// Make sure the mine's awake |
|
pPhysObj->Wake(); |
|
} |
|
|
|
if( m_bIsClawScanner ) |
|
{ |
|
// Fold up. |
|
SetActivity( ACT_DISARM ); |
|
} |
|
|
|
return; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CNPC_CScanner::GetMaxSpeed() |
|
{ |
|
if( IsStriderScout() ) |
|
{ |
|
return SCANNER_SCOUT_MAX_SPEED; |
|
} |
|
|
|
return BaseClass::GetMaxSpeed(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputDeployMine(inputdata_t &inputdata) |
|
{ |
|
DeployMine(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputEquipMine(inputdata_t &inputdata) |
|
{ |
|
CBaseEntity *child; |
|
// iterate through all children |
|
for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() ) |
|
{ |
|
if( FClassnameIs( child, "combine_mine" ) ) |
|
{ |
|
// Already have a mine! |
|
return; |
|
} |
|
} |
|
|
|
CBaseEntity *pEnt; |
|
|
|
pEnt = CreateEntityByName( "combine_mine" ); |
|
bool bPlacedMine = false; |
|
|
|
if( m_bIsClawScanner ) |
|
{ |
|
Vector vecOrigin; |
|
QAngle angles; |
|
int attachment; |
|
|
|
attachment = LookupAttachment( "claw" ); |
|
|
|
if( attachment > -1 ) |
|
{ |
|
GetAttachment( attachment, vecOrigin, angles ); |
|
|
|
pEnt->SetAbsOrigin( vecOrigin ); |
|
pEnt->SetAbsAngles( angles ); |
|
pEnt->SetOwnerEntity( this ); |
|
pEnt->SetParent( this, attachment ); |
|
|
|
m_bIsOpen = true; |
|
SetActivity( ACT_IDLE_ANGRY ); |
|
bPlacedMine = true; |
|
} |
|
} |
|
|
|
|
|
if( !bPlacedMine ) |
|
{ |
|
Vector vecMineLocation = GetAbsOrigin(); |
|
vecMineLocation.z -= 32.0; |
|
|
|
pEnt->SetAbsOrigin( vecMineLocation ); |
|
pEnt->SetAbsAngles( GetAbsAngles() ); |
|
pEnt->SetOwnerEntity( this ); |
|
pEnt->SetParent( this ); |
|
} |
|
|
|
pEnt->Spawn(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tells the scanner to go photograph an entity. |
|
// Input : String name or classname of the entity to inspect. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputInspectTargetPhoto(inputdata_t &inputdata) |
|
{ |
|
m_vLastPatrolDir = vec3_origin; |
|
m_bPhotoTaken = false; |
|
InspectTarget( inputdata, SCANNER_FLY_PHOTO ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tells the scanner to go spotlight an entity. |
|
// Input : String name or classname of the entity to inspect. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputInspectTargetSpotlight(inputdata_t &inputdata) |
|
{ |
|
InspectTarget( inputdata, SCANNER_FLY_SPOT ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tells the scanner to go photo or spotlight an entity. |
|
// Input : String name or classname of the entity to inspect. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InspectTarget( inputdata_t &inputdata, ScannerFlyMode_t eFlyMode ) |
|
{ |
|
CBaseEntity *pEnt = gEntList.FindEntityGeneric( NULL, inputdata.value.String(), this, inputdata.pActivator ); |
|
|
|
if ( pEnt != NULL ) |
|
{ |
|
// Set and begin to inspect our target |
|
SetInspectTargetToEnt( pEnt, SCANNER_CIT_INSPECT_LENGTH ); |
|
|
|
m_nFlyMode = eFlyMode; |
|
SetCondition( COND_CSCANNER_HAVE_INSPECT_TARGET ); |
|
|
|
// Stop us from any other navigation we were doing |
|
GetNavigator()->ClearGoal(); |
|
} |
|
else |
|
{ |
|
DevMsg( "InspectTarget: target %s not found!\n", inputdata.value.String() ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_CScanner::MovingToInspectTarget( void ) |
|
{ |
|
// If we're flying to a photograph target and the photo isn't yet taken, we're still moving to it |
|
if ( m_nFlyMode == SCANNER_FLY_PHOTO && m_bPhotoTaken == false ) |
|
return true; |
|
|
|
// If we're still on a path, then we're still moving |
|
if ( HaveInspectTarget() && GetNavigator()->IsGoalActive() ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::GatherConditions( void ) |
|
{ |
|
BaseClass::GatherConditions(); |
|
|
|
// Clear out our old conditions |
|
ClearCondition( COND_CSCANNER_INSPECT_DONE ); |
|
ClearCondition( COND_CSCANNER_HAVE_INSPECT_TARGET ); |
|
ClearCondition( COND_CSCANNER_SPOT_ON_TARGET ); |
|
ClearCondition( COND_CSCANNER_CAN_PHOTOGRAPH ); |
|
|
|
// We don't do any of these checks if we have an enemy |
|
if ( GetEnemy() ) |
|
return; |
|
|
|
// -------------------------------------- |
|
// COND_CSCANNER_INSPECT_DONE |
|
// |
|
// If my inspection over |
|
// --------------------------------------------------------- |
|
|
|
// Refresh our timing if we're still moving to our inspection target |
|
if ( MovingToInspectTarget() ) |
|
{ |
|
m_fInspectEndTime = gpGlobals->curtime + SCANNER_CIT_INSPECT_LENGTH; |
|
} |
|
|
|
// Update our follow times |
|
if ( HaveInspectTarget() && gpGlobals->curtime > m_fInspectEndTime && m_nFlyMode != SCANNER_FLY_FOLLOW ) |
|
{ |
|
SetCondition ( COND_CSCANNER_INSPECT_DONE ); |
|
|
|
m_fCheckCitizenTime = gpGlobals->curtime + SCANNER_CIT_INSPECT_DELAY; |
|
m_fCheckHintTime = gpGlobals->curtime + SCANNER_HINT_INSPECT_DELAY; |
|
ClearInspectTarget(); |
|
} |
|
|
|
// ---------------------------------------------------------- |
|
// If I heard a sound and I don't have an enemy, inspect it |
|
// ---------------------------------------------------------- |
|
if ( ( HasCondition( COND_HEAR_COMBAT ) || HasCondition( COND_HEAR_DANGER ) ) && m_nFlyMode != SCANNER_FLY_FOLLOW ) |
|
{ |
|
CSound *pSound = GetBestSound(); |
|
|
|
if ( pSound ) |
|
{ |
|
// Chase an owner if we can |
|
if ( pSound->m_hOwner != NULL ) |
|
{ |
|
// Don't inspect sounds of things we like |
|
if ( IRelationType( pSound->m_hOwner ) != D_LI ) |
|
{ |
|
// Only bother if we can see it |
|
if ( FVisible( pSound->m_hOwner ) ) |
|
{ |
|
SetInspectTargetToEnt( pSound->m_hOwner, SCANNER_SOUND_INSPECT_LENGTH ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Otherwise chase the specific sound |
|
Vector vSoundPos = pSound->GetSoundOrigin(); |
|
SetInspectTargetToPos( vSoundPos, SCANNER_SOUND_INSPECT_LENGTH ); |
|
} |
|
|
|
m_nFlyMode = (random->RandomInt(0,2)==0) ? SCANNER_FLY_SPOT : SCANNER_FLY_PHOTO; |
|
} |
|
} |
|
|
|
// -------------------------------------- |
|
// COND_CSCANNER_HAVE_INSPECT_TARGET |
|
// |
|
// Look for a nearby citizen or player to hassle. |
|
// --------------------------------------------------------- |
|
|
|
// Check for citizens to inspect |
|
if ( gpGlobals->curtime > m_fCheckCitizenTime && HaveInspectTarget() == false ) |
|
{ |
|
CBaseEntity *pBestEntity = BestInspectTarget(); |
|
|
|
if ( pBestEntity != NULL ) |
|
{ |
|
SetInspectTargetToEnt( pBestEntity, SCANNER_CIT_INSPECT_LENGTH ); |
|
m_nFlyMode = (random->RandomInt(0,3)==0) ? SCANNER_FLY_SPOT : SCANNER_FLY_PHOTO; |
|
SetCondition ( COND_CSCANNER_HAVE_INSPECT_TARGET ); |
|
} |
|
} |
|
|
|
// Check for hints to inspect |
|
if ( gpGlobals->curtime > m_fCheckHintTime && HaveInspectTarget() == false ) |
|
{ |
|
SetHintNode( CAI_HintManager::FindHint( this, HINT_WORLD_WINDOW, 0, SCANNER_CIT_INSPECT_FLY_DIST ) ); |
|
|
|
if ( GetHintNode() ) |
|
{ |
|
m_fCheckHintTime = gpGlobals->curtime + SCANNER_HINT_INSPECT_DELAY; |
|
|
|
m_nFlyMode = (random->RandomInt(0,2)==0) ? SCANNER_FLY_SPOT : SCANNER_FLY_PHOTO; |
|
|
|
SetInspectTargetToHint( GetHintNode(), SCANNER_HINT_INSPECT_LENGTH ); |
|
|
|
SetCondition ( COND_CSCANNER_HAVE_INSPECT_TARGET ); |
|
} |
|
} |
|
|
|
// -------------------------------------- |
|
// COND_CSCANNER_SPOT_ON_TARGET |
|
// |
|
// True when spotlight is on target ent |
|
// -------------------------------------- |
|
|
|
if ( m_hSpotlightTarget != NULL && HaveInspectTarget() && m_hSpotlightTarget->GetSmoothedVelocity().Length() < 25 ) |
|
{ |
|
// If I have a target entity, check my spotlight against the |
|
// actual position of the entity |
|
if (GetTarget()) |
|
{ |
|
float fInspectDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length(); |
|
if ( fInspectDist < 100 ) |
|
{ |
|
SetCondition( COND_CSCANNER_SPOT_ON_TARGET ); |
|
} |
|
} |
|
// Otherwise just check by beam direction |
|
else |
|
{ |
|
Vector vTargetDir = SpotlightTargetPos() - GetLocalOrigin(); |
|
VectorNormalize(vTargetDir); |
|
float dotpr = DotProduct(vTargetDir, m_vSpotlightDir); |
|
if (dotpr > 0.95) |
|
{ |
|
SetCondition( COND_CSCANNER_SPOT_ON_TARGET ); |
|
} |
|
} |
|
} |
|
|
|
// -------------------------------------------- |
|
// COND_CSCANNER_CAN_PHOTOGRAPH |
|
// |
|
// True when can photograph target ent |
|
// -------------------------------------------- |
|
|
|
ClearCondition( COND_CSCANNER_CAN_PHOTOGRAPH ); |
|
|
|
if ( m_nFlyMode == SCANNER_FLY_PHOTO ) |
|
{ |
|
// Make sure I have something to photograph and I'm ready to photograph and I'm not moving to fast |
|
if ( gpGlobals->curtime > m_fNextPhotographTime && HaveInspectTarget() && GetCurrentVelocity().LengthSqr() < (64*64) ) |
|
{ |
|
// Check that I'm in the right distance range |
|
float fInspectDist = (InspectTargetPosition() - GetAbsOrigin()).Length2D(); |
|
|
|
// See if we're within range |
|
if ( fInspectDist > SCANNER_PHOTO_NEAR_DIST && fInspectDist < SCANNER_PHOTO_FAR_DIST ) |
|
{ |
|
// Make sure we're looking at the target |
|
if ( UTIL_AngleDiff( GetAbsAngles().y, VecToYaw( InspectTargetPosition() - GetAbsOrigin() ) ) < 4.0f ) |
|
{ |
|
trace_t tr; |
|
AI_TraceLine ( GetAbsOrigin(), InspectTargetPosition(), MASK_BLOCKLOS, GetTarget(), COLLISION_GROUP_NONE, &tr); |
|
|
|
if ( tr.fraction == 1.0f ) |
|
{ |
|
SetCondition( COND_CSCANNER_CAN_PHOTOGRAPH ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::PrescheduleThink(void) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
// Go back to idling if we're done |
|
if ( GetIdealActivity() == ACT_SCANNER_FLARE_START ) |
|
{ |
|
if ( IsSequenceFinished() ) |
|
{ |
|
SetIdealActivity( (Activity) ACT_IDLE ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Overridden because if the player is a criminal, we hate them. |
|
// Input : pTarget - Entity with which to determine relationship. |
|
// Output : Returns relationship value. |
|
//----------------------------------------------------------------------------- |
|
Disposition_t CNPC_CScanner::IRelationType(CBaseEntity *pTarget) |
|
{ |
|
// If it's the player and they are a criminal, we hates them |
|
if ( pTarget && pTarget->Classify() == CLASS_PLAYER ) |
|
{ |
|
if ( GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON ) |
|
return D_NU; |
|
} |
|
|
|
return BaseClass::IRelationType( pTarget ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::RunTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_CSCANNER_PHOTOGRAPH: |
|
{ |
|
if ( IsWaitFinished() ) |
|
{ |
|
// If light was on turn it off |
|
if ( m_pEyeFlash->GetBrightness() > 0 ) |
|
{ |
|
m_pEyeFlash->SetBrightness( 0 ); |
|
|
|
// I'm done with this target |
|
if ( gpGlobals->curtime > m_fInspectEndTime ) |
|
{ |
|
ClearInspectTarget(); |
|
TaskComplete(); |
|
} |
|
// Otherwise take another picture |
|
else |
|
{ |
|
SetWait( 5.0f, 10.0f ); |
|
} |
|
} |
|
// If light was off, take another picture |
|
else |
|
{ |
|
TakePhoto(); |
|
SetWait( 0.1f ); |
|
} |
|
} |
|
break; |
|
} |
|
case TASK_CSCANNER_ATTACK_PRE_FLASH: |
|
{ |
|
AttackPreFlash(); |
|
|
|
if ( IsWaitFinished() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
case TASK_CSCANNER_ATTACK_FLASH: |
|
{ |
|
if (IsWaitFinished()) |
|
{ |
|
AttackFlashBlind(); |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
default: |
|
{ |
|
BaseClass::RunTask(pTask); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets the appropriate next schedule based on current condition |
|
// bits. |
|
//----------------------------------------------------------------------------- |
|
int CNPC_CScanner::SelectSchedule(void) |
|
{ |
|
// Turn our flash off in case we were interrupted while it was on. |
|
if ( m_pEyeFlash ) |
|
{ |
|
m_pEyeFlash->SetBrightness( 0 ); |
|
} |
|
|
|
// ---------------------------------------------------- |
|
// If I'm dead, go into a dive bomb |
|
// ---------------------------------------------------- |
|
if ( m_iHealth <= 0 ) |
|
{ |
|
m_flSpeed = SCANNER_MAX_DIVE_BOMB_SPEED; |
|
return SCHED_SCANNER_ATTACK_DIVEBOMB; |
|
} |
|
|
|
// ------------------------------- |
|
// If I'm in a script sequence |
|
// ------------------------------- |
|
if ( m_NPCState == NPC_STATE_SCRIPT ) |
|
return(BaseClass::SelectSchedule()); |
|
|
|
// ------------------------------- |
|
// Flinch |
|
// ------------------------------- |
|
if ( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) ) |
|
{ |
|
if ( IsHeldByPhyscannon( ) ) |
|
return SCHED_SMALL_FLINCH; |
|
|
|
if ( m_NPCState == NPC_STATE_IDLE ) |
|
return SCHED_SMALL_FLINCH; |
|
|
|
if ( m_NPCState == NPC_STATE_ALERT ) |
|
{ |
|
if ( m_iHealth < ( 3 * sk_scanner_health.GetFloat() / 4 )) |
|
return SCHED_TAKE_COVER_FROM_ORIGIN; |
|
|
|
if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 ) |
|
return SCHED_SMALL_FLINCH; |
|
} |
|
else |
|
{ |
|
if ( random->RandomInt( 0, 10 ) < 4 ) |
|
return SCHED_SMALL_FLINCH; |
|
} |
|
} |
|
|
|
// I'm being held by the physcannon... struggle! |
|
if ( IsHeldByPhyscannon( ) ) |
|
return SCHED_SCANNER_HELD_BY_PHYSCANNON; |
|
|
|
// ---------------------------------------------------------- |
|
// If I have an enemy |
|
// ---------------------------------------------------------- |
|
if ( GetEnemy() != NULL && GetEnemy()->IsAlive() && m_bShouldInspect ) |
|
{ |
|
// Always chase the enemy |
|
SetInspectTargetToEnt( GetEnemy(), 9999 ); |
|
|
|
// Patrol if the enemy has vanished |
|
if ( HasCondition( COND_LOST_ENEMY ) ) |
|
return SCHED_SCANNER_PATROL; |
|
|
|
// Chase via route if we're directly blocked |
|
if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) ) |
|
return SCHED_SCANNER_CHASE_ENEMY; |
|
|
|
// Attack if it's time |
|
if ( gpGlobals->curtime < m_flNextAttack ) |
|
return SCHED_CSCANNER_SPOTLIGHT_HOVER; |
|
|
|
// Melee attack if possible |
|
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) |
|
{ |
|
if ( random->RandomInt(0,1) ) |
|
return SCHED_CSCANNER_ATTACK_FLASH; |
|
|
|
// TODO: a schedule where he makes an alarm sound? |
|
return SCHED_SCANNER_CHASE_ENEMY; |
|
} |
|
|
|
// If I'm far from the enemy, stay up high and approach in spotlight mode |
|
float fAttack2DDist = ( GetEnemyLKP() - GetAbsOrigin() ).Length2D(); |
|
|
|
if ( fAttack2DDist > SCANNER_ATTACK_FAR_DIST ) |
|
return SCHED_CSCANNER_SPOTLIGHT_HOVER; |
|
|
|
// Otherwise fly in low for attack |
|
return SCHED_SCANNER_ATTACK_HOVER; |
|
} |
|
|
|
// ---------------------------------------------------------- |
|
// If I have something to inspect |
|
// ---------------------------------------------------------- |
|
if ( HaveInspectTarget() ) |
|
{ |
|
// Pathfind to our goal |
|
if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) ) |
|
return SCHED_CSCANNER_MOVE_TO_INSPECT; |
|
|
|
// If I was chasing, pick with photographing or spotlighting |
|
if ( m_nFlyMode == SCANNER_FLY_CHASE ) |
|
{ |
|
m_nFlyMode = (random->RandomInt(0,1)==0) ? SCANNER_FLY_SPOT : SCANNER_FLY_PHOTO; |
|
} |
|
|
|
// Handle spotlight |
|
if ( m_nFlyMode == SCANNER_FLY_SPOT ) |
|
{ |
|
if (HasCondition( COND_CSCANNER_SPOT_ON_TARGET )) |
|
{ |
|
if (GetTarget()) |
|
{ |
|
RequestInspectSupport(); |
|
|
|
CAI_BaseNPC *pNPC = GetTarget()->MyNPCPointer(); |
|
// If I'm leading the inspection, so verbal inspection |
|
if (pNPC && pNPC->GetTarget() == this) |
|
{ |
|
return SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT; |
|
} |
|
|
|
return SCHED_CSCANNER_SPOTLIGHT_HOVER; |
|
} |
|
|
|
return SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS; |
|
} |
|
|
|
return SCHED_CSCANNER_SPOTLIGHT_HOVER; |
|
} |
|
|
|
// Handle photographing |
|
if ( m_nFlyMode == SCANNER_FLY_PHOTO ) |
|
{ |
|
if ( HasCondition( COND_CSCANNER_CAN_PHOTOGRAPH )) |
|
return SCHED_CSCANNER_PHOTOGRAPH; |
|
|
|
return SCHED_CSCANNER_PHOTOGRAPH_HOVER; |
|
} |
|
|
|
// Handle following after a target |
|
if ( m_nFlyMode == SCANNER_FLY_FOLLOW ) |
|
{ |
|
//TODO: Randomly make noise, photograph, etc |
|
return SCHED_SCANNER_FOLLOW_HOVER; |
|
} |
|
|
|
// Handle patrolling |
|
if ( ( m_nFlyMode == SCANNER_FLY_PATROL ) || ( m_nFlyMode == SCANNER_FLY_FAST ) ) |
|
return SCHED_SCANNER_PATROL; |
|
} |
|
|
|
// Default to patrolling around |
|
return SCHED_SCANNER_PATROL; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::SpotlightDestroy(void) |
|
{ |
|
if ( m_hSpotlight ) |
|
{ |
|
UTIL_Remove(m_hSpotlight); |
|
m_hSpotlight = NULL; |
|
|
|
UTIL_Remove(m_hSpotlightTarget); |
|
m_hSpotlightTarget = NULL; |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::SpotlightCreate(void) |
|
{ |
|
// Make sure we don't already have one |
|
if ( m_hSpotlight != NULL ) |
|
return; |
|
|
|
// Can we create a spotlight yet? |
|
if ( gpGlobals->curtime < m_fNextSpotlightTime ) |
|
return; |
|
|
|
// If I have an enemy, start spotlight on my enemy |
|
if (GetEnemy() != NULL) |
|
{ |
|
Vector vEnemyPos = GetEnemyLKP(); |
|
Vector vTargetPos = vEnemyPos; |
|
vTargetPos.z = GetFloorZ(vEnemyPos); |
|
m_vSpotlightDir = vTargetPos - GetLocalOrigin(); |
|
VectorNormalize(m_vSpotlightDir); |
|
} |
|
// If I have an target, start spotlight on my target |
|
else if (GetTarget() != NULL) |
|
{ |
|
Vector vTargetPos = GetTarget()->GetLocalOrigin(); |
|
vTargetPos.z = GetFloorZ(GetTarget()->GetLocalOrigin()); |
|
m_vSpotlightDir = vTargetPos - GetLocalOrigin(); |
|
VectorNormalize(m_vSpotlightDir); |
|
} |
|
// Other wise just start looking down |
|
else |
|
{ |
|
m_vSpotlightDir = Vector(0,0,-1); |
|
} |
|
|
|
trace_t tr; |
|
AI_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + m_vSpotlightDir * 2024, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
m_hSpotlightTarget = (CSpotlightEnd*)CreateEntityByName( "spotlight_end" ); |
|
m_hSpotlightTarget->Spawn(); |
|
m_hSpotlightTarget->SetLocalOrigin( tr.endpos ); |
|
m_hSpotlightTarget->SetOwnerEntity( this ); |
|
// YWB: Because the scanner only moves the target during think, make sure we interpolate over 0.1 sec instead of every tick!!! |
|
m_hSpotlightTarget->SetSimulatedEveryTick( false ); |
|
|
|
// Using the same color as the beam... |
|
m_hSpotlightTarget->SetRenderColor( 255, 255, 255 ); |
|
m_hSpotlightTarget->m_Radius = m_flSpotlightMaxLength; |
|
|
|
m_hSpotlight = CBeam::BeamCreate( "sprites/glow_test02.vmt", SPOTLIGHT_WIDTH ); |
|
// Set the temporary spawnflag on the beam so it doesn't save (we'll recreate it on restore) |
|
m_hSpotlight->AddSpawnFlags( SF_BEAM_TEMPORARY ); |
|
m_hSpotlight->SetColor( 255, 255, 255 ); |
|
m_hSpotlight->SetHaloTexture( m_nHaloSprite ); |
|
m_hSpotlight->SetHaloScale( 32 ); |
|
m_hSpotlight->SetEndWidth( m_hSpotlight->GetWidth() ); |
|
m_hSpotlight->SetBeamFlags( (FBEAM_SHADEOUT|FBEAM_NOTILE) ); |
|
m_hSpotlight->SetBrightness( 32 ); |
|
m_hSpotlight->SetNoise( 0 ); |
|
m_hSpotlight->EntsInit( this, m_hSpotlightTarget ); |
|
m_hSpotlight->SetHDRColorScale( 0.75f ); // Scale this back a bit on HDR maps |
|
// attach to light |
|
m_hSpotlight->SetStartAttachment( LookupAttachment( SCANNER_ATTACHMENT_LIGHT ) ); |
|
|
|
m_vSpotlightAngVelocity = vec3_origin; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
Vector CNPC_CScanner::SpotlightTargetPos(void) |
|
{ |
|
// ---------------------------------------------- |
|
// If I have an enemy |
|
// ---------------------------------------------- |
|
if (GetEnemy() != NULL) |
|
{ |
|
// If I can see my enemy aim for him |
|
if (HasCondition(COND_SEE_ENEMY)) |
|
{ |
|
// If its client aim for his eyes |
|
if (GetEnemy()->GetFlags() & FL_CLIENT) |
|
{ |
|
m_vSpotlightTargetPos = GetEnemy()->EyePosition(); |
|
} |
|
// Otherwise same for his feet |
|
else |
|
{ |
|
m_vSpotlightTargetPos = GetEnemy()->GetLocalOrigin(); |
|
m_vSpotlightTargetPos.z = GetFloorZ(GetEnemy()->GetLocalOrigin()); |
|
} |
|
} |
|
// Otherwise aim for last known position if I can see LKP |
|
else |
|
{ |
|
Vector vLKP = GetEnemyLKP(); |
|
m_vSpotlightTargetPos.x = vLKP.x; |
|
m_vSpotlightTargetPos.y = vLKP.y; |
|
m_vSpotlightTargetPos.z = GetFloorZ(vLKP); |
|
} |
|
} |
|
// ---------------------------------------------- |
|
// If I have an inspect target |
|
// ---------------------------------------------- |
|
else if (HaveInspectTarget()) |
|
{ |
|
m_vSpotlightTargetPos = InspectTargetPosition(); |
|
} |
|
else |
|
{ |
|
// This creates a nice patrol spotlight sweep |
|
// in the direction that I'm travelling |
|
m_vSpotlightTargetPos = GetCurrentVelocity(); |
|
m_vSpotlightTargetPos.z = 0; |
|
VectorNormalize( m_vSpotlightTargetPos ); |
|
m_vSpotlightTargetPos *= 5; |
|
|
|
float noiseScale = 2.5; |
|
const Vector &noiseMod = GetNoiseMod(); |
|
m_vSpotlightTargetPos.x += noiseScale*sin(noiseMod.x * gpGlobals->curtime + noiseMod.x); |
|
m_vSpotlightTargetPos.y += noiseScale*cos(noiseMod.y* gpGlobals->curtime + noiseMod.y); |
|
m_vSpotlightTargetPos.z -= fabs(noiseScale*cos(noiseMod.z* gpGlobals->curtime + noiseMod.z) ); |
|
m_vSpotlightTargetPos = GetLocalOrigin()+m_vSpotlightTargetPos * 2024; |
|
} |
|
|
|
return m_vSpotlightTargetPos; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
Vector CNPC_CScanner::SpotlightCurrentPos(void) |
|
{ |
|
Vector vTargetDir = SpotlightTargetPos() - GetLocalOrigin(); |
|
VectorNormalize(vTargetDir); |
|
|
|
if (!m_hSpotlight) |
|
{ |
|
DevMsg("Spotlight pos. called w/o spotlight!\n"); |
|
return vec3_origin; |
|
} |
|
// ------------------------------------------------- |
|
// Beam has momentum relative to it's ground speed |
|
// so sclae the turn rate based on its distance |
|
// from the beam source |
|
// ------------------------------------------------- |
|
float fBeamDist = (m_hSpotlightTarget->GetLocalOrigin() - GetLocalOrigin()).Length(); |
|
|
|
float fBeamTurnRate = atan(50/fBeamDist); |
|
Vector vNewAngVelocity = fBeamTurnRate * (vTargetDir - m_vSpotlightDir); |
|
|
|
float myDecay = 0.4; |
|
m_vSpotlightAngVelocity = (myDecay * m_vSpotlightAngVelocity + (1-myDecay) * vNewAngVelocity); |
|
|
|
// ------------------------------ |
|
// Limit overall angular speed |
|
// ----------------------------- |
|
if (m_vSpotlightAngVelocity.Length() > 1) |
|
{ |
|
|
|
Vector velDir = m_vSpotlightAngVelocity; |
|
VectorNormalize(velDir); |
|
m_vSpotlightAngVelocity = velDir * 1; |
|
} |
|
|
|
// ------------------------------ |
|
// Calculate new beam direction |
|
// ------------------------------ |
|
m_vSpotlightDir = m_vSpotlightDir + m_vSpotlightAngVelocity; |
|
m_vSpotlightDir = m_vSpotlightDir; |
|
VectorNormalize(m_vSpotlightDir); |
|
|
|
|
|
// --------------------------------------------- |
|
// Get beam end point. Only collide with |
|
// solid objects, not npcs |
|
// --------------------------------------------- |
|
trace_t tr; |
|
Vector vTraceEnd = GetAbsOrigin() + (m_vSpotlightDir * 2 * m_flSpotlightMaxLength); |
|
AI_TraceLine ( GetAbsOrigin(), vTraceEnd, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr); |
|
|
|
return (tr.endpos); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Update the direction and position of my spotlight |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::SpotlightUpdate(void) |
|
{ |
|
//FIXME: JDW - E3 Hack |
|
if ( m_bNoLight ) |
|
{ |
|
if ( m_hSpotlight ) |
|
{ |
|
SpotlightDestroy(); |
|
} |
|
|
|
return; |
|
} |
|
|
|
if ((m_nFlyMode != SCANNER_FLY_SPOT) && |
|
(m_nFlyMode != SCANNER_FLY_PATROL) && |
|
(m_nFlyMode != SCANNER_FLY_FAST)) |
|
{ |
|
if ( m_hSpotlight ) |
|
{ |
|
SpotlightDestroy(); |
|
} |
|
return; |
|
} |
|
|
|
// If I don't have a spotlight attempt to create one |
|
|
|
if ( m_hSpotlight == NULL ) |
|
{ |
|
SpotlightCreate(); |
|
|
|
if ( m_hSpotlight== NULL ) |
|
return; |
|
} |
|
|
|
// Calculate the new homing target position |
|
m_vSpotlightCurrentPos = SpotlightCurrentPos(); |
|
|
|
// ------------------------------------------------------------------ |
|
// If I'm not facing the spotlight turn it off |
|
// ------------------------------------------------------------------ |
|
Vector vSpotDir = m_vSpotlightCurrentPos - GetAbsOrigin(); |
|
VectorNormalize(vSpotDir); |
|
|
|
Vector vForward; |
|
AngleVectors( GetAbsAngles(), &vForward ); |
|
|
|
float dotpr = DotProduct( vForward, vSpotDir ); |
|
|
|
if ( dotpr < 0.0 ) |
|
{ |
|
// Leave spotlight off for a while |
|
m_fNextSpotlightTime = gpGlobals->curtime + 3.0f; |
|
|
|
SpotlightDestroy(); |
|
return; |
|
} |
|
|
|
// -------------------------------------------------------------- |
|
// Update spotlight target velocity |
|
// -------------------------------------------------------------- |
|
Vector vTargetDir = (m_vSpotlightCurrentPos - m_hSpotlightTarget->GetLocalOrigin()); |
|
float vTargetDist = vTargetDir.Length(); |
|
|
|
Vector vecNewVelocity = vTargetDir; |
|
VectorNormalize(vecNewVelocity); |
|
vecNewVelocity *= (10 * vTargetDist); |
|
|
|
// If a large move is requested, just jump to final spot as we |
|
// probably hit a discontinuity |
|
if (vecNewVelocity.Length() > 200) |
|
{ |
|
VectorNormalize(vecNewVelocity); |
|
vecNewVelocity *= 200; |
|
m_hSpotlightTarget->SetLocalOrigin( m_vSpotlightCurrentPos ); |
|
} |
|
m_hSpotlightTarget->SetAbsVelocity( vecNewVelocity ); |
|
|
|
m_hSpotlightTarget->m_vSpotlightOrg = GetAbsOrigin(); |
|
|
|
// Avoid sudden change in where beam fades out when cross disconinuities |
|
m_hSpotlightTarget->m_vSpotlightDir = m_hSpotlightTarget->GetLocalOrigin() - m_hSpotlightTarget->m_vSpotlightOrg; |
|
float flBeamLength = VectorNormalize( m_hSpotlightTarget->m_vSpotlightDir ); |
|
m_flSpotlightCurLength = (0.80*m_flSpotlightCurLength) + (0.2*flBeamLength); |
|
|
|
// Fade out spotlight end if past max length. |
|
if (m_flSpotlightCurLength > 2*m_flSpotlightMaxLength) |
|
{ |
|
m_hSpotlightTarget->SetRenderColorA( 0 ); |
|
m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); |
|
} |
|
else if (m_flSpotlightCurLength > m_flSpotlightMaxLength) |
|
{ |
|
m_hSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) ); |
|
m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); |
|
} |
|
else |
|
{ |
|
m_hSpotlightTarget->SetRenderColorA( 1.0 ); |
|
m_hSpotlight->SetFadeLength(m_flSpotlightCurLength); |
|
} |
|
|
|
// Adjust end width to keep beam width constant |
|
float flNewWidth = SPOTLIGHT_WIDTH * ( flBeamLength/m_flSpotlightMaxLength); |
|
|
|
m_hSpotlight->SetWidth(flNewWidth); |
|
m_hSpotlight->SetEndWidth(flNewWidth); |
|
|
|
m_hSpotlightTarget->m_flLightScale = 0.0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called just before we are deleted. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::UpdateOnRemove( void ) |
|
{ |
|
SpotlightDestroy(); |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::TakePhoto(void) |
|
{ |
|
ScannerEmitSound( "TakePhoto" ); |
|
|
|
m_pEyeFlash->SetScale( 1.4 ); |
|
m_pEyeFlash->SetBrightness( 255 ); |
|
m_pEyeFlash->SetColor(255,255,255); |
|
|
|
Vector vRawPos = InspectTargetPosition(); |
|
Vector vLightPos = vRawPos; |
|
|
|
// If taking picture of entity, aim at feet |
|
if ( GetTarget() ) |
|
{ |
|
if ( GetTarget()->IsPlayer() ) |
|
{ |
|
m_OnPhotographPlayer.FireOutput( GetTarget(), this ); |
|
BlindFlashTarget( GetTarget() ); |
|
} |
|
|
|
if ( GetTarget()->MyNPCPointer() != NULL ) |
|
{ |
|
m_OnPhotographNPC.FireOutput( GetTarget(), this ); |
|
GetTarget()->MyNPCPointer()->DispatchInteraction( g_interactionScannerInspectBegin, NULL, this ); |
|
} |
|
} |
|
|
|
SetIdealActivity( (Activity) ACT_SCANNER_FLARE_START ); |
|
|
|
m_bPhotoTaken = true; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::AttackPreFlash(void) |
|
{ |
|
ScannerEmitSound( "TakePhoto" ); |
|
|
|
// If off turn on, if on turn off |
|
if (m_pEyeFlash->GetBrightness() == 0) |
|
{ |
|
m_pEyeFlash->SetScale( 0.5 ); |
|
m_pEyeFlash->SetBrightness( 255 ); |
|
m_pEyeFlash->SetColor(255,0,0); |
|
} |
|
else |
|
{ |
|
m_pEyeFlash->SetBrightness( 0 ); |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::AttackFlash(void) |
|
{ |
|
ScannerEmitSound( "AttackFlash" ); |
|
m_pEyeFlash->SetScale( 1.8 ); |
|
m_pEyeFlash->SetBrightness( 255 ); |
|
m_pEyeFlash->SetColor(255,255,255); |
|
|
|
if (GetEnemy() != NULL) |
|
{ |
|
Vector pos = GetEnemyLKP(); |
|
CBroadcastRecipientFilter filter; |
|
te->DynamicLight( filter, 0.0, &pos, 200, 200, 255, 0, 300, 0.2, 50 ); |
|
|
|
if (GetEnemy()->IsPlayer()) |
|
{ |
|
m_OnPhotographPlayer.FireOutput(GetTarget(), this); |
|
} |
|
else if( GetEnemy()->MyNPCPointer() ) |
|
{ |
|
m_OnPhotographNPC.FireOutput(GetTarget(), this); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTarget - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::BlindFlashTarget( CBaseEntity *pTarget ) |
|
{ |
|
// Tell all the striders this person is here! |
|
CAI_BaseNPC ** ppAIs = g_AI_Manager.AccessAIs(); |
|
int nAIs = g_AI_Manager.NumAIs(); |
|
|
|
if( IsStriderScout() ) |
|
{ |
|
for ( int i = 0; i < nAIs; i++ ) |
|
{ |
|
if( FClassnameIs( ppAIs[ i ], "npc_strider" ) ) |
|
{ |
|
ppAIs[ i ]->UpdateEnemyMemory( pTarget, pTarget->GetAbsOrigin(), this ); |
|
} |
|
} |
|
} |
|
|
|
// Only bother with player |
|
if ( pTarget->IsPlayer() == false ) |
|
return; |
|
|
|
// Scale the flash value by how closely the player is looking at me |
|
Vector vFlashDir = GetAbsOrigin() - pTarget->EyePosition(); |
|
VectorNormalize(vFlashDir); |
|
|
|
Vector vFacing; |
|
AngleVectors( pTarget->EyeAngles(), &vFacing ); |
|
|
|
float dotPr = DotProduct( vFlashDir, vFacing ); |
|
|
|
// Not if behind us |
|
if ( dotPr > 0.5f ) |
|
{ |
|
// Make sure nothing in the way |
|
trace_t tr; |
|
AI_TraceLine ( GetAbsOrigin(), pTarget->EyePosition(), MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.startsolid == false && tr.fraction == 1.0) |
|
{ |
|
color32 white = { 255, 255, 255, (byte)(SCANNER_FLASH_MAX_VALUE * dotPr) }; |
|
|
|
if ( ( g_pMaterialSystemHardwareConfig != NULL ) && ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) ) |
|
{ |
|
white.a = ( byte )( ( float )white.a * 0.9f ); |
|
} |
|
|
|
float flFadeTime = ( IsX360() ) ? 0.5f : 3.0f; |
|
UTIL_ScreenFade( pTarget, white, flFadeTime, 0.5, FFADE_IN ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::AttackFlashBlind(void) |
|
{ |
|
if( GetEnemy() ) |
|
{ |
|
BlindFlashTarget( GetEnemy() ); |
|
} |
|
|
|
m_pEyeFlash->SetBrightness( 0 ); |
|
|
|
float fAttackDelay = random->RandomFloat(SCANNER_ATTACK_MIN_DELAY,SCANNER_ATTACK_MAX_DELAY); |
|
|
|
if( IsStriderScout() ) |
|
{ |
|
// Make strider scouts more snappy. |
|
fAttackDelay *= 0.5; |
|
} |
|
|
|
m_flNextAttack = gpGlobals->curtime + fAttackDelay; |
|
m_fNextSpotlightTime = gpGlobals->curtime + 1.0f; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
void CNPC_CScanner::AttackDivebomb( void ) |
|
{ |
|
if (m_hSpotlight) |
|
{ |
|
SpotlightDestroy(); |
|
} |
|
|
|
BaseClass::AttackDivebomb(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pTask - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::StartTask( const Task_t *pTask ) |
|
{ |
|
switch (pTask->iTask) |
|
{ |
|
case TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET: |
|
{ |
|
// Must have somewhere to fly to |
|
if ( HaveInspectTarget() == false ) |
|
{ |
|
TaskFail( "No inspection target to fly to!\n" ); |
|
return; |
|
} |
|
|
|
if ( GetTarget() ) |
|
{ |
|
//FIXME: Tweak |
|
//Vector idealPos = IdealGoalForMovement( InspectTargetPosition(), GetAbsOrigin(), 128.0f, 128.0f ); |
|
|
|
AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin ); |
|
|
|
if ( GetNavigator()->SetGoal( goal ) ) |
|
{ |
|
TaskComplete(); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
AI_NavGoal_t goal( GOALTYPE_LOCATION, InspectTargetPosition() ); |
|
|
|
if ( GetNavigator()->SetGoal( goal ) ) |
|
{ |
|
TaskComplete(); |
|
return; |
|
} |
|
} |
|
|
|
// Don't try and inspect this target again for a few seconds |
|
CNPC_Citizen *pCitizen = dynamic_cast<CNPC_Citizen *>( GetTarget() ); |
|
if ( pCitizen ) |
|
{ |
|
pCitizen->SetNextScannerInspectTime( gpGlobals->curtime + 5.0 ); |
|
} |
|
|
|
TaskFail("No route to inspection target!\n"); |
|
} |
|
break; |
|
|
|
case TASK_CSCANNER_SPOT_INSPECT_ON: |
|
{ |
|
if (GetTarget() == NULL) |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else |
|
{ |
|
CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer(); |
|
if (!pNPC) |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else |
|
{ |
|
pNPC->DispatchInteraction(g_interactionScannerInspectBegin,NULL,this); |
|
|
|
// Now we need some time to inspect |
|
m_fInspectEndTime = gpGlobals->curtime + SCANNER_CIT_INSPECT_LENGTH; |
|
TaskComplete(); |
|
} |
|
} |
|
break; |
|
} |
|
case TASK_CSCANNER_SPOT_INSPECT_WAIT: |
|
{ |
|
if (GetTarget() == NULL) |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else |
|
{ |
|
CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer(); |
|
if (!pNPC) |
|
{ |
|
SetTarget( NULL ); |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else |
|
{ |
|
//<<TEMP>>//<<TEMP>> armband too! |
|
pNPC->DispatchInteraction(g_interactionScannerInspectHandsUp,NULL,this); |
|
} |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
case TASK_CSCANNER_SPOT_INSPECT_OFF: |
|
{ |
|
if (GetTarget() == NULL) |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else |
|
{ |
|
CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer(); |
|
if (!pNPC) |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else |
|
{ |
|
pNPC->DispatchInteraction(g_interactionScannerInspectDone,NULL,this); |
|
|
|
// Clear target entity and don't inspect again for a while |
|
SetTarget( NULL ); |
|
m_fCheckCitizenTime = gpGlobals->curtime + SCANNER_CIT_INSPECT_DELAY; |
|
TaskComplete(); |
|
} |
|
} |
|
break; |
|
} |
|
case TASK_CSCANNER_CLEAR_INSPECT_TARGET: |
|
{ |
|
ClearInspectTarget(); |
|
|
|
TaskComplete(); |
|
break; |
|
} |
|
|
|
case TASK_CSCANNER_SET_FLY_SPOT: |
|
{ |
|
m_nFlyMode = SCANNER_FLY_SPOT; |
|
TaskComplete(); |
|
break; |
|
} |
|
|
|
case TASK_CSCANNER_SET_FLY_PHOTO: |
|
{ |
|
m_nFlyMode = SCANNER_FLY_PHOTO; |
|
m_bPhotoTaken = false; |
|
|
|
// Leave spotlight off for a while |
|
m_fNextSpotlightTime = gpGlobals->curtime + 2.0; |
|
|
|
TaskComplete(); |
|
break; |
|
} |
|
|
|
case TASK_CSCANNER_PHOTOGRAPH: |
|
{ |
|
TakePhoto(); |
|
SetWait( 0.1 ); |
|
break; |
|
} |
|
|
|
case TASK_CSCANNER_ATTACK_PRE_FLASH: |
|
{ |
|
if( IsStriderScout() ) |
|
{ |
|
Vector vecScare = GetEnemy()->EarPosition(); |
|
Vector vecDir = WorldSpaceCenter() - vecScare; |
|
VectorNormalize( vecDir ); |
|
vecScare += vecDir * 64.0f; |
|
|
|
CSoundEnt::InsertSound( SOUND_DANGER, vecScare, 256, 1.0, this ); |
|
} |
|
|
|
if (m_pEyeFlash) |
|
{ |
|
AttackPreFlash(); |
|
// Flash red for a while |
|
SetWait( 1.0f ); |
|
} |
|
else |
|
{ |
|
TaskFail("No Flash"); |
|
} |
|
break; |
|
} |
|
|
|
case TASK_CSCANNER_ATTACK_FLASH: |
|
{ |
|
AttackFlash(); |
|
// Blinding occurs slightly later |
|
SetWait( 0.05 ); |
|
break; |
|
} |
|
|
|
// Override to go to inspect target position whether or not is an entity |
|
case TASK_GET_PATH_TO_TARGET: |
|
{ |
|
if (!HaveInspectTarget()) |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
} |
|
else if (GetHintNode()) |
|
{ |
|
Vector vNodePos; |
|
GetHintNode()->GetPosition(this,&vNodePos); |
|
|
|
GetNavigator()->SetGoal( vNodePos ); |
|
} |
|
else |
|
{ |
|
AI_NavGoal_t goal( (const Vector &)InspectTargetPosition() ); |
|
goal.pTarget = GetTarget(); |
|
GetNavigator()->SetGoal( goal ); |
|
} |
|
break; |
|
} |
|
default: |
|
BaseClass::StartTask(pTask); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
char *CNPC_CScanner::GetScannerSoundPrefix( void ) |
|
{ |
|
if( m_bIsClawScanner ) |
|
return "NPC_SScanner"; |
|
|
|
return "NPC_CScanner"; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
float CNPC_CScanner::MinGroundDist( void ) |
|
{ |
|
if ( m_nFlyMode == SCANNER_FLY_SPOT && !GetHintNode() ) |
|
{ |
|
return SCANNER_SPOTLIGHT_FLY_HEIGHT; |
|
} |
|
|
|
return SCANNER_NOSPOTLIGHT_FLY_HEIGHT; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::AdjustScannerVelocity( void ) |
|
{ |
|
if ( m_bIsClawScanner ) |
|
{ |
|
m_vCurrentVelocity *= ( 1 + sin( ( gpGlobals->curtime + m_flFlyNoiseBase ) * 2.5f ) * .1 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : flInterval - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_CScanner::OverrideMove( float flInterval ) |
|
{ |
|
// ---------------------------------------------- |
|
// If dive bombing |
|
// ---------------------------------------------- |
|
if (m_nFlyMode == SCANNER_FLY_DIVE) |
|
{ |
|
MoveToDivebomb( flInterval ); |
|
} |
|
else |
|
{ |
|
Vector vMoveTargetPos(0,0,0); |
|
CBaseEntity *pMoveTarget = NULL; |
|
|
|
// The original line of code was, due to the accidental use of '|' instead of |
|
// '&', always true. Replacing with 'true' to suppress the warning without changing |
|
// the (long-standing) behavior. |
|
if ( true ) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) ) |
|
{ |
|
// Select move target |
|
if ( GetTarget() != NULL ) |
|
{ |
|
pMoveTarget = GetTarget(); |
|
} |
|
else if ( GetEnemy() != NULL ) |
|
{ |
|
pMoveTarget = GetEnemy(); |
|
} |
|
|
|
// Select move target position |
|
if ( HaveInspectTarget() ) |
|
{ |
|
vMoveTargetPos = InspectTargetPosition(); |
|
} |
|
else if ( GetEnemy() != NULL ) |
|
{ |
|
vMoveTargetPos = GetEnemy()->GetAbsOrigin(); |
|
} |
|
} |
|
else |
|
{ |
|
vMoveTargetPos = GetNavigator()->GetCurWaypointPos(); |
|
} |
|
|
|
ClearCondition( COND_SCANNER_FLY_CLEAR ); |
|
ClearCondition( COND_SCANNER_FLY_BLOCKED ); |
|
|
|
// See if we can fly there directly |
|
if ( pMoveTarget || HaveInspectTarget() ) |
|
{ |
|
trace_t tr; |
|
AI_TraceHull( GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
float fTargetDist = (1.0f-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length(); |
|
|
|
if ( ( tr.m_pEnt == pMoveTarget ) || ( fTargetDist < 50 ) ) |
|
{ |
|
if ( g_debug_cscanner.GetBool() ) |
|
{ |
|
NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0,255,0, true, 0); |
|
NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1); |
|
} |
|
|
|
SetCondition( COND_SCANNER_FLY_CLEAR ); |
|
} |
|
else |
|
{ |
|
//HANDY DEBUG TOOL |
|
if ( g_debug_cscanner.GetBool() ) |
|
{ |
|
NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255,0,0, true, 0); |
|
NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1); |
|
} |
|
|
|
SetCondition( COND_SCANNER_FLY_BLOCKED ); |
|
} |
|
} |
|
|
|
// If I have a route, keep it updated and move toward target |
|
if ( GetNavigator()->IsGoalActive() ) |
|
{ |
|
if ( OverridePathMove( pMoveTarget, flInterval ) ) |
|
{ |
|
BlendPhyscannonLaunchSpeed(); |
|
return true; |
|
} |
|
} |
|
else if (m_nFlyMode == SCANNER_FLY_SPOT) |
|
{ |
|
MoveToSpotlight( flInterval ); |
|
} |
|
// If photographing |
|
else if ( m_nFlyMode == SCANNER_FLY_PHOTO ) |
|
{ |
|
MoveToPhotograph( flInterval ); |
|
} |
|
else if ( m_nFlyMode == SCANNER_FLY_FOLLOW ) |
|
{ |
|
MoveToSpotlight( flInterval ); |
|
} |
|
// ---------------------------------------------- |
|
// If attacking |
|
// ---------------------------------------------- |
|
else if (m_nFlyMode == SCANNER_FLY_ATTACK) |
|
{ |
|
if ( m_hSpotlight ) |
|
{ |
|
SpotlightDestroy(); |
|
} |
|
|
|
MoveToAttack( flInterval ); |
|
} |
|
// ----------------------------------------------------------------- |
|
// If I don't have a route, just decelerate |
|
// ----------------------------------------------------------------- |
|
else if (!GetNavigator()->IsGoalActive()) |
|
{ |
|
float myDecay = 9.5; |
|
Decelerate( flInterval, myDecay); |
|
} |
|
} |
|
|
|
MoveExecute_Alive( flInterval ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Accelerates toward a given position. |
|
// Input : flInterval - Time interval over which to move. |
|
// vecMoveTarget - Position to move toward. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget ) |
|
{ |
|
// Don't move if stalling |
|
if ( m_flEngineStallTime > gpGlobals->curtime ) |
|
return; |
|
|
|
// Look at our inspection target if we have one |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
// Otherwise at our enemy |
|
TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() ); |
|
} |
|
else if ( HaveInspectTarget() ) |
|
{ |
|
TurnHeadToTarget( flInterval, InspectTargetPosition() ); |
|
} |
|
else |
|
{ |
|
// Otherwise face our motion direction |
|
TurnHeadToTarget( flInterval, vecMoveTarget ); |
|
} |
|
|
|
// ------------------------------------- |
|
// Move towards our target |
|
// ------------------------------------- |
|
float myAccel; |
|
float myZAccel = 400.0f; |
|
float myDecay = 0.15f; |
|
|
|
Vector vecCurrentDir; |
|
|
|
// Get the relationship between my current velocity and the way I want to be going. |
|
vecCurrentDir = GetCurrentVelocity(); |
|
VectorNormalize( vecCurrentDir ); |
|
|
|
Vector targetDir = vecMoveTarget - GetAbsOrigin(); |
|
float flDist = VectorNormalize(targetDir); |
|
|
|
float flDot; |
|
flDot = DotProduct( targetDir, vecCurrentDir ); |
|
|
|
if( flDot > 0.25 ) |
|
{ |
|
// If my target is in front of me, my flight model is a bit more accurate. |
|
myAccel = 250; |
|
} |
|
else |
|
{ |
|
// Have a harder time correcting my course if I'm currently flying away from my target. |
|
myAccel = 128; |
|
} |
|
|
|
if ( myAccel > flDist / flInterval ) |
|
{ |
|
myAccel = flDist / flInterval; |
|
} |
|
|
|
if ( myZAccel > flDist / flInterval ) |
|
{ |
|
myZAccel = flDist / flInterval; |
|
} |
|
|
|
MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); |
|
|
|
// calc relative banking targets |
|
Vector forward, right, up; |
|
GetVectors( &forward, &right, &up ); |
|
|
|
m_vCurrentBanking.x = targetDir.x; |
|
m_vCurrentBanking.z = 120.0f * DotProduct( right, targetDir ); |
|
m_vCurrentBanking.y = 0; |
|
|
|
float speedPerc = SimpleSplineRemapVal( GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f ); |
|
|
|
speedPerc = clamp( speedPerc, 0.0f, 1.0f ); |
|
|
|
m_vCurrentBanking *= speedPerc; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : flInterval - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::MoveToSpotlight( float flInterval ) |
|
{ |
|
if ( flInterval <= 0 ) |
|
return; |
|
|
|
Vector vTargetPos; |
|
|
|
if ( HaveInspectTarget() ) |
|
{ |
|
vTargetPos = InspectTargetPosition(); |
|
} |
|
else if ( GetEnemy() != NULL ) |
|
{ |
|
vTargetPos = GetEnemyLKP(); |
|
} |
|
else |
|
{ |
|
return; |
|
} |
|
|
|
//float flDesiredDist = SCANNER_SPOTLIGHT_NEAR_DIST + ( ( SCANNER_SPOTLIGHT_FAR_DIST - SCANNER_SPOTLIGHT_NEAR_DIST ) / 2 ); |
|
|
|
float flIdealHeightDiff = SCANNER_SPOTLIGHT_NEAR_DIST; |
|
if( IsEnemyPlayerInSuit() ) |
|
{ |
|
flIdealHeightDiff *= 0.5; |
|
} |
|
|
|
Vector idealPos = IdealGoalForMovement( vTargetPos, GetAbsOrigin(), GetGoalDistance(), flIdealHeightDiff ); |
|
|
|
MoveToTarget( flInterval, idealPos ); |
|
|
|
//TODO: Re-implement? |
|
|
|
/* |
|
// ------------------------------------------------ |
|
// Also keep my distance from other squad members |
|
// unless I'm inspecting |
|
// ------------------------------------------------ |
|
if (m_pSquad && |
|
gpGlobals->curtime > m_fInspectEndTime) |
|
{ |
|
CBaseEntity* pNearest = m_pSquad->NearestSquadMember(this); |
|
if (pNearest) |
|
{ |
|
Vector vNearestDir = (pNearest->GetLocalOrigin() - GetLocalOrigin()); |
|
if (vNearestDir.Length() < SCANNER_SQUAD_FLY_DIST) |
|
{ |
|
vNearestDir = pNearest->GetLocalOrigin() - GetLocalOrigin(); |
|
VectorNormalize(vNearestDir); |
|
vFlyDirection -= 0.5*vNearestDir; |
|
} |
|
} |
|
} |
|
|
|
// --------------------------------------------------------- |
|
// Add evasion if I have taken damage recently |
|
// --------------------------------------------------------- |
|
if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime) |
|
{ |
|
vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer()); |
|
} |
|
*/ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CNPC_CScanner::GetGoalDistance( void ) |
|
{ |
|
if ( m_flGoalOverrideDistance != 0.0f ) |
|
return m_flGoalOverrideDistance; |
|
|
|
switch ( m_nFlyMode ) |
|
{ |
|
case SCANNER_FLY_PHOTO: |
|
return ( SCANNER_PHOTO_NEAR_DIST + ( ( SCANNER_PHOTO_FAR_DIST - SCANNER_PHOTO_NEAR_DIST ) / 2 ) ); |
|
break; |
|
|
|
case SCANNER_FLY_SPOT: |
|
{ |
|
float goalDist = ( SCANNER_SPOTLIGHT_NEAR_DIST + ( ( SCANNER_SPOTLIGHT_FAR_DIST - SCANNER_SPOTLIGHT_NEAR_DIST ) / 2 ) ); |
|
if( IsEnemyPlayerInSuit() ) |
|
{ |
|
goalDist *= 0.5; |
|
} |
|
return goalDist; |
|
} |
|
break; |
|
|
|
case SCANNER_FLY_FOLLOW: |
|
return ( SCANNER_FOLLOW_DIST ); |
|
break; |
|
} |
|
|
|
return BaseClass::GetGoalDistance(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::MoveToPhotograph(float flInterval) |
|
{ |
|
if ( HaveInspectTarget() == false ) |
|
return; |
|
|
|
//float flDesiredDist = SCANNER_PHOTO_NEAR_DIST + ( ( SCANNER_PHOTO_FAR_DIST - SCANNER_PHOTO_NEAR_DIST ) / 2 ); |
|
|
|
Vector idealPos = IdealGoalForMovement( InspectTargetPosition(), GetAbsOrigin(), GetGoalDistance(), 32.0f ); |
|
|
|
MoveToTarget( flInterval, idealPos ); |
|
|
|
//FIXME: Re-implement? |
|
|
|
/* |
|
// ------------------------------------------------ |
|
// Also keep my distance from other squad members |
|
// unless I'm inspecting |
|
// ------------------------------------------------ |
|
if (m_pSquad && |
|
gpGlobals->curtime > m_fInspectEndTime) |
|
{ |
|
CBaseEntity* pNearest = m_pSquad->NearestSquadMember(this); |
|
if (pNearest) |
|
{ |
|
Vector vNearestDir = (pNearest->GetLocalOrigin() - GetLocalOrigin()); |
|
if (vNearestDir.Length() < SCANNER_SQUAD_FLY_DIST) |
|
{ |
|
vNearestDir = pNearest->GetLocalOrigin() - GetLocalOrigin(); |
|
VectorNormalize(vNearestDir); |
|
vFlyDirection -= 0.5*vNearestDir; |
|
} |
|
} |
|
} |
|
*/ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is a generic function (to be implemented by sub-classes) to |
|
// handle specific interactions between different types of characters |
|
// (For example the barnacle grabbing an NPC) |
|
// Input : Constant for the type of interaction |
|
// Output : true - if sub-class has a response for the interaction |
|
// false - if sub-class has no response |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_CScanner::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* pSourceEnt) |
|
{ |
|
// TODO:: - doing this by just an interrupt contition would be a lot better! |
|
if (interactionType == g_interactionScannerSupportEntity) |
|
{ |
|
// Only accept help request if I'm not already busy |
|
if (GetEnemy() == NULL && !HaveInspectTarget()) |
|
{ |
|
// Only accept if target is a reasonable distance away |
|
CBaseEntity* pTarget = (CBaseEntity*)data; |
|
float fTargetDist = (pTarget->GetLocalOrigin() - GetLocalOrigin()).Length(); |
|
|
|
if (fTargetDist < SCANNER_SQUAD_HELP_DIST) |
|
{ |
|
float fInspectTime = (((CNPC_CScanner*)pSourceEnt)->m_fInspectEndTime - gpGlobals->curtime); |
|
SetInspectTargetToEnt(pTarget,fInspectTime); |
|
|
|
if (random->RandomInt(0,2)==0) |
|
{ |
|
SetSchedule(SCHED_CSCANNER_PHOTOGRAPH_HOVER); |
|
} |
|
else |
|
{ |
|
SetSchedule(SCHED_CSCANNER_SPOTLIGHT_HOVER); |
|
} |
|
return true; |
|
} |
|
} |
|
} |
|
else if (interactionType == g_interactionScannerSupportPosition) |
|
{ |
|
// Only accept help request if I'm not already busy |
|
if (GetEnemy() == NULL && !HaveInspectTarget()) |
|
{ |
|
// Only accept if target is a reasonable distance away |
|
Vector vInspectPos; |
|
vInspectPos.x = ((Vector *)data)->x; |
|
vInspectPos.y = ((Vector *)data)->y; |
|
vInspectPos.z = ((Vector *)data)->z; |
|
|
|
float fTargetDist = (vInspectPos - GetLocalOrigin()).Length(); |
|
|
|
if (fTargetDist < SCANNER_SQUAD_HELP_DIST) |
|
{ |
|
float fInspectTime = (((CNPC_CScanner*)pSourceEnt)->m_fInspectEndTime - gpGlobals->curtime); |
|
SetInspectTargetToPos(vInspectPos,fInspectTime); |
|
|
|
if (random->RandomInt(0,2)==0) |
|
{ |
|
SetSchedule(SCHED_CSCANNER_PHOTOGRAPH_HOVER); |
|
} |
|
else |
|
{ |
|
SetSchedule(SCHED_CSCANNER_SPOTLIGHT_HOVER); |
|
} |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputDisableSpotlight( inputdata_t &inputdata ) |
|
{ |
|
m_bNoLight = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CNPC_CScanner::GetHeadTurnRate( void ) |
|
{ |
|
if ( GetEnemy() ) |
|
return 800.0f; |
|
|
|
if ( HaveInspectTarget() ) |
|
return 500.0f; |
|
|
|
return BaseClass::GetHeadTurnRate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputSetFollowTarget( inputdata_t &inputdata ) |
|
{ |
|
InspectTarget( inputdata, SCANNER_FLY_FOLLOW ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_CScanner::InputClearFollowTarget( inputdata_t &inputdata ) |
|
{ |
|
SetInspectTargetToEnt( NULL, 0 ); |
|
|
|
m_nFlyMode = SCANNER_FLY_PATROL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Schedules |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
AI_BEGIN_CUSTOM_NPC( npc_cscanner, CNPC_CScanner ) |
|
DECLARE_TASK(TASK_CSCANNER_SET_FLY_PHOTO) |
|
DECLARE_TASK(TASK_CSCANNER_SET_FLY_SPOT) |
|
DECLARE_TASK(TASK_CSCANNER_PHOTOGRAPH) |
|
DECLARE_TASK(TASK_CSCANNER_ATTACK_PRE_FLASH) |
|
DECLARE_TASK(TASK_CSCANNER_ATTACK_FLASH) |
|
DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_ON) |
|
DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_WAIT) |
|
DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_OFF) |
|
DECLARE_TASK(TASK_CSCANNER_CLEAR_INSPECT_TARGET) |
|
DECLARE_TASK(TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET) |
|
|
|
DECLARE_CONDITION(COND_CSCANNER_HAVE_INSPECT_TARGET) |
|
DECLARE_CONDITION(COND_CSCANNER_INSPECT_DONE) |
|
DECLARE_CONDITION(COND_CSCANNER_CAN_PHOTOGRAPH) |
|
DECLARE_CONDITION(COND_CSCANNER_SPOT_ON_TARGET) |
|
|
|
DECLARE_ACTIVITY(ACT_SCANNER_SMALL_FLINCH_ALERT) |
|
DECLARE_ACTIVITY(ACT_SCANNER_SMALL_FLINCH_COMBAT) |
|
DECLARE_ACTIVITY(ACT_SCANNER_INSPECT) |
|
DECLARE_ACTIVITY(ACT_SCANNER_WALK_ALERT) |
|
DECLARE_ACTIVITY(ACT_SCANNER_WALK_COMBAT) |
|
DECLARE_ACTIVITY(ACT_SCANNER_FLARE) |
|
DECLARE_ACTIVITY(ACT_SCANNER_RETRACT) |
|
DECLARE_ACTIVITY(ACT_SCANNER_FLARE_PRONGS) |
|
DECLARE_ACTIVITY(ACT_SCANNER_RETRACT_PRONGS) |
|
DECLARE_ACTIVITY(ACT_SCANNER_FLARE_START) |
|
|
|
DECLARE_ANIMEVENT( AE_SCANNER_CLOSED ) |
|
|
|
DECLARE_INTERACTION(g_interactionScannerInspect) |
|
DECLARE_INTERACTION(g_interactionScannerInspectBegin) |
|
DECLARE_INTERACTION(g_interactionScannerInspectDone) |
|
DECLARE_INTERACTION(g_interactionScannerInspectHandsUp) |
|
DECLARE_INTERACTION(g_interactionScannerInspectShowArmband) |
|
DECLARE_INTERACTION(g_interactionScannerSupportEntity) |
|
DECLARE_INTERACTION(g_interactionScannerSupportPosition) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_PATROL |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_PATROL, |
|
|
|
" Tasks" |
|
" TASK_CSCANNER_CLEAR_INSPECT_TARGET 0" |
|
" TASK_SCANNER_SET_FLY_PATROL 0" |
|
" TASK_SET_TOLERANCE_DISTANCE 32" |
|
" TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck |
|
" TASK_GET_PATH_TO_RANDOM_NODE 2000" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts" |
|
" COND_GIVE_WAY" |
|
" COND_NEW_ENEMY" |
|
" COND_SEE_ENEMY" |
|
" COND_SEE_FEAR" |
|
" COND_HEAR_COMBAT" |
|
" COND_HEAR_DANGER" |
|
" COND_HEAR_PLAYER" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_PROVOKED" |
|
" COND_CSCANNER_HAVE_INSPECT_TARGET" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_SPOTLIGHT_HOVER |
|
// |
|
// Hover above target entity, trying to get spotlight |
|
// on my target |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_SPOTLIGHT_HOVER, |
|
|
|
" Tasks" |
|
" TASK_CSCANNER_SET_FLY_SPOT 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_WALK " |
|
" TASK_WAIT 1" |
|
"" |
|
" Interrupts" |
|
" COND_CSCANNER_SPOT_ON_TARGET" |
|
" COND_CSCANNER_INSPECT_DONE" |
|
" COND_SCANNER_FLY_BLOCKED" |
|
" COND_NEW_ENEMY" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS |
|
// |
|
// Inspect a position once spotlight is on it |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS, |
|
|
|
" Tasks" |
|
" TASK_CSCANNER_SET_FLY_SPOT 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_SCANNER_INSPECT" |
|
" TASK_SPEAK_SENTENCE 3" // Curious sound |
|
" TASK_WAIT 5" |
|
" TASK_CSCANNER_CLEAR_INSPECT_TARGET 0" |
|
"" |
|
" Interrupts" |
|
" COND_CSCANNER_INSPECT_DONE" |
|
" COND_HEAR_DANGER" |
|
" COND_HEAR_COMBAT" |
|
" COND_NEW_ENEMY" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT |
|
// |
|
// Inspect a citizen once spotlight is on it |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT, |
|
|
|
" Tasks" |
|
" TASK_CSCANNER_SET_FLY_SPOT 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_SCANNER_INSPECT" |
|
" TASK_SPEAK_SENTENCE 0" // Stop! |
|
" TASK_WAIT 1" |
|
" TASK_CSCANNER_SPOT_INSPECT_ON 0" |
|
" TASK_WAIT 2" |
|
" TASK_SPEAK_SENTENCE 1" // Hands on head or Show Armband! |
|
" TASK_WAIT 1" |
|
" TASK_CSCANNER_SPOT_INSPECT_WAIT 0" |
|
" TASK_WAIT 5" |
|
" TASK_SPEAK_SENTENCE 2" // Free to go! |
|
" TASK_WAIT 1" |
|
" TASK_CSCANNER_SPOT_INSPECT_OFF 0" |
|
" TASK_CSCANNER_CLEAR_INSPECT_TARGET 0" |
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_PHOTOGRAPH_HOVER |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_PHOTOGRAPH_HOVER, |
|
|
|
" Tasks" |
|
" TASK_CSCANNER_SET_FLY_PHOTO 0" |
|
" TASK_WAIT 2" |
|
"" |
|
" Interrupts" |
|
" COND_CSCANNER_INSPECT_DONE" |
|
" COND_CSCANNER_CAN_PHOTOGRAPH" |
|
" COND_SCANNER_FLY_BLOCKED" |
|
" COND_NEW_ENEMY" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_PHOTOGRAPH |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_PHOTOGRAPH, |
|
|
|
" Tasks" |
|
" TASK_CSCANNER_SET_FLY_PHOTO 0" |
|
" TASK_CSCANNER_PHOTOGRAPH 0" |
|
"" |
|
" Interrupts" |
|
" COND_CSCANNER_INSPECT_DONE" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_ATTACK_FLASH |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_ATTACK_FLASH, |
|
|
|
" Tasks" |
|
" TASK_SCANNER_SET_FLY_ATTACK 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" |
|
" TASK_CSCANNER_ATTACK_PRE_FLASH 0 " |
|
" TASK_CSCANNER_ATTACK_FLASH 0" |
|
" TASK_WAIT 0.5" |
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_CSCANNER_MOVE_TO_INSPECT |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_CSCANNER_MOVE_TO_INSPECT, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL" |
|
" TASK_SET_TOLERANCE_DISTANCE 128" |
|
" TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts" |
|
" COND_SCANNER_FLY_CLEAR" |
|
" COND_NEW_ENEMY" |
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON" |
|
) |
|
|
|
AI_END_CUSTOM_NPC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Claw Scanner |
|
// |
|
// Scanner that always spawns as a claw scanner |
|
//----------------------------------------------------------------------------- |
|
|
|
class CNPC_ClawScanner : public CNPC_CScanner |
|
{ |
|
DECLARE_CLASS( CNPC_ClawScanner, CNPC_CScanner ); |
|
|
|
public: |
|
CNPC_ClawScanner(); |
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
BEGIN_DATADESC( CNPC_ClawScanner ) |
|
END_DATADESC() |
|
|
|
|
|
LINK_ENTITY_TO_CLASS(npc_clawscanner, CNPC_ClawScanner); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CNPC_ClawScanner::CNPC_ClawScanner() |
|
{ |
|
// override our superclass's setting |
|
BecomeClawScanner(); |
|
}
|
|
|