📄 npc_sscanner.cpp
字号:
//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose:
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "npc_sscanner.h"
#include "AI_Default.h"
#include "AI_Hull.h"
#include "AI_Hint.h"
#include "AI_Node.h" // just for GetFloorZ()
#include "AI_Navigator.h"
#include "ai_moveprobe.h"
#include "AI_Squad.h"
#include "explode.h"
#include "grenade_energy.h"
#include "ndebugoverlay.h"
#include "scanner_shield.h"
#include "npc_sscanner_beam.h"
#include "gib.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"
#define SSCANNER_MAX_SPEED 400
#define SSCANNER_NUM_GLOWS 2
#define SSCANNER_GIB_COUNT 5
#define SSCANNER_ATTACK_NEAR_DIST 600
#define SSCANNER_ATTACK_FAR_DIST 800
#define SSCANNER_COVER_NEAR_DIST 60
#define SSCANNER_COVER_FAR_DIST 250
#define SSCANNER_MAX_SHEILD_DIST 400
#define SSCANNER_BANK_RATE 5
#define SSCANNER_ENERGY_WARMUP_TIME 0.5
#define SSCANNER_MIN_GROUND_DIST 150
#define SSCANNER_GIB_COUNT 5
ConVar sk_sscanner_health( "sk_sscanner_health","0");
extern float GetFloorZ(const Vector &origin, float fMaxDrop);
//-----------------------------------------------------------------------------
// Private activities.
//-----------------------------------------------------------------------------
int ACT_SSCANNER_FLINCH_BACK;
int ACT_SSCANNER_FLINCH_FRONT;
int ACT_SSCANNER_FLINCH_LEFT;
int ACT_SSCANNER_FLINCH_RIGHT;
int ACT_SSCANNER_LOOK;
int ACT_SSCANNER_OPEN;
int ACT_SSCANNER_CLOSE;
//-----------------------------------------------------------------------------
// SScanner schedules.
//-----------------------------------------------------------------------------
enum SScannerSchedules
{
SCHED_SSCANNER_HOVER = LAST_SHARED_SCHEDULE,
SCHED_SSCANNER_PATROL,
SCHED_SSCANNER_CHASE_ENEMY,
SCHED_SSCANNER_CHASE_TARGET,
SCHED_SSCANNER_OPEN,
SCHED_SSCANNER_CLOSE,
};
//-----------------------------------------------------------------------------
// Custom tasks.
//-----------------------------------------------------------------------------
enum SScannerTasks
{
TASK_SSCANNER_OPEN = LAST_SHARED_TASK,
TASK_SSCANNER_CLOSE,
};
//-----------------------------------------------------------------------------
// Custom Conditions
//-----------------------------------------------------------------------------
enum SScanner_Conds
{
COND_SSCANNER_FLY_BLOCKED = LAST_SHARED_CONDITION,
COND_SSCANNER_FLY_CLEAR,
COND_SSCANNER_PISSED_OFF,
};
BEGIN_DATADESC( CNPC_SScanner )
DEFINE_FIELD( CNPC_SScanner, m_lastHurtTime, FIELD_FLOAT ),
DEFINE_FIELD( CNPC_SScanner, m_nState, FIELD_INTEGER),
DEFINE_FIELD( CNPC_SScanner, m_bShieldsDisabled, FIELD_BOOLEAN),
DEFINE_FIELD( CNPC_SScanner, m_pShield, FIELD_CLASSPTR ),
DEFINE_FIELD( CNPC_SScanner, m_pShieldBeamL, FIELD_CLASSPTR ),
DEFINE_FIELD( CNPC_SScanner, m_pShieldBeamR, FIELD_CLASSPTR ),
DEFINE_FIELD( CNPC_SScanner, m_fNextShieldCheckTime, FIELD_TIME),
DEFINE_FIELD( CNPC_SScanner, m_fNextFlySoundTime, FIELD_TIME),
DEFINE_FIELD( CNPC_SScanner, m_vCoverPosition, FIELD_POSITION_VECTOR),
END_DATADESC()
LINK_ENTITY_TO_CLASS( npc_sscanner, CNPC_SScanner );
IMPLEMENT_CUSTOM_AI( npc_sscanner, CNPC_SScanner );
CNPC_SScanner::CNPC_SScanner()
{
#ifdef _DEBUG
m_vCurrentBanking.Init();
m_vCoverPosition.Init();
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_SScanner::InitCustomSchedules(void)
{
INIT_CUSTOM_AI(CNPC_SScanner);
ADD_CUSTOM_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_HOVER);
ADD_CUSTOM_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_PATROL);
ADD_CUSTOM_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_CHASE_ENEMY);
ADD_CUSTOM_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_CHASE_TARGET);
ADD_CUSTOM_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_OPEN);
ADD_CUSTOM_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_CLOSE);
ADD_CUSTOM_CONDITION(CNPC_SScanner, COND_SSCANNER_FLY_BLOCKED);
ADD_CUSTOM_CONDITION(CNPC_SScanner, COND_SSCANNER_FLY_CLEAR);
ADD_CUSTOM_CONDITION(CNPC_SScanner, COND_SSCANNER_PISSED_OFF);
ADD_CUSTOM_TASK(CNPC_SScanner, TASK_SSCANNER_OPEN);
ADD_CUSTOM_TASK(CNPC_SScanner, TASK_SSCANNER_CLOSE);
ADD_CUSTOM_ACTIVITY(CNPC_SScanner, ACT_SSCANNER_FLINCH_BACK);
ADD_CUSTOM_ACTIVITY(CNPC_SScanner, ACT_SSCANNER_FLINCH_FRONT);
ADD_CUSTOM_ACTIVITY(CNPC_SScanner, ACT_SSCANNER_FLINCH_LEFT);
ADD_CUSTOM_ACTIVITY(CNPC_SScanner, ACT_SSCANNER_FLINCH_RIGHT);
ADD_CUSTOM_ACTIVITY(CNPC_SScanner, ACT_SSCANNER_OPEN);
ADD_CUSTOM_ACTIVITY(CNPC_SScanner, ACT_SSCANNER_CLOSE);
AI_LOAD_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_HOVER);
AI_LOAD_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_PATROL);
AI_LOAD_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_CHASE_ENEMY);
AI_LOAD_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_CHASE_TARGET);
AI_LOAD_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_OPEN);
AI_LOAD_SCHEDULE(CNPC_SScanner, SCHED_SSCANNER_CLOSE);
}
//-----------------------------------------------------------------------------
// Purpose: Indicates this NPC's place in the relationship table.
//-----------------------------------------------------------------------------
Class_T CNPC_SScanner::Classify(void)
{
return(CLASS_SCANNER);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_SScanner::StopLoopingSounds(void)
{
StopSound( "NPC_SScanner.FlySound" );
BaseClass::StopLoopingSounds();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : Type -
// Output : int - new schedule type
//-----------------------------------------------------------------------------
int CNPC_SScanner::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_FAIL_TAKE_COVER:
return SCHED_SSCANNER_PATROL;
break;
}
return BaseClass::TranslateSchedule(scheduleType);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_SScanner::Event_Killed( const CTakeDamageInfo &info )
{
BaseClass::Event_Killed( info );
StopShield();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_SScanner::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
if ( info.GetDamageType() & DMG_BULLET)
{
g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal);
}
BaseClass::TraceAttack( info, vecDir, ptr );
}
//------------------------------------------------------------------------------
// Purpose : Override to split in two when attacked
// Input :
// Output :
//------------------------------------------------------------------------------
int CNPC_SScanner::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
CTakeDamageInfo info = inputInfo;
// Don't take friendly fire from combine
if (info.GetAttacker()->Classify() == CLASS_COMBINE)
{
info.SetDamage( 0 );
}
else if (m_nState == SSCANNER_OPEN)
{
// Flinch in the direction of the attack
float vAttackYaw = VecToYaw(g_vecAttackDir);
float vAngleDiff = UTIL_AngleDiff( vAttackYaw, m_fHeadYaw );
if (vAngleDiff > -45 && vAngleDiff < 45)
{
SetActivity((Activity)ACT_SSCANNER_FLINCH_BACK);
}
else if (vAngleDiff < -45 && vAngleDiff > -135)
{
SetActivity((Activity)ACT_SSCANNER_FLINCH_LEFT);
}
else if (vAngleDiff > 45 && vAngleDiff < 135)
{
SetActivity((Activity)ACT_SSCANNER_FLINCH_RIGHT);
}
else
{
SetActivity((Activity)ACT_SSCANNER_FLINCH_FRONT);
}
m_lastHurtTime = gpGlobals->curtime;
}
return (BaseClass::OnTakeDamage_Alive( info ));
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
bool CNPC_SScanner::IsValidShieldCover( Vector &vCoverPos )
{
if (GetEnemy() == NULL)
{
return true;
}
// Make sure I can get here
trace_t tr;
AI_TraceEntity( this, GetAbsOrigin(), vCoverPos, MASK_NPCSOLID, &tr );
if (tr.fraction != 1.0)
{
//NDebugOverlay::Cross3D(vCoverPos,Vector(-15,-15,-15),Vector(5,5,5),255,0,0,true,1.0);
return false;
}
// Make sure position is in cover
Vector vThreatEyePos = GetEnemy()->EyePosition();
AI_TraceLine ( vCoverPos, vThreatEyePos, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr );
if (tr.fraction == 1.0)
{
//NDebugOverlay::Cross3D(vCoverPos,Vector(-15,-15,-15),Vector(5,5,5),0,0,255,true,1.0);
return false;
}
else
{
//NDebugOverlay::Cross3D(vCoverPos,Vector(-15,-15,-15),Vector(5,5,5),0,255,0,true,1.0);
return true;
}
}
//------------------------------------------------------------------------------
// Purpose : Attempts to find a position that is in cover from the enemy
// but with in range to use shield.
// Input :
// Output : True if a cover position was set
//------------------------------------------------------------------------------
bool CNPC_SScanner::SetShieldCoverPosition( void )
{
// Make sure I'm shielding someone and have an enemy
if (GetTarget() == NULL ||
GetEnemy() == NULL )
{
m_vCoverPosition = vec3_origin;
return false;
}
// If I have a current cover position check if it's valid
if (m_vCoverPosition != vec3_origin)
{
// If in range of my shield target, and valid cover keep same cover position
if ((m_vCoverPosition - GetTarget()->GetLocalOrigin()).Length() < SSCANNER_COVER_FAR_DIST &&
IsValidShieldCover(m_vCoverPosition) )
{
return true;
}
}
// Otherwise try a random direction
QAngle vAngle = QAngle(0,random->RandomInt(-180,180),0);
Vector vForward;
AngleVectors( vAngle, &vForward );
// Now get the position
Vector vTestPos = GetTarget()->GetLocalOrigin() + vForward * (random->RandomInt(150,240));
vTestPos.z = GetLocalOrigin().z;
// Is it a valid cover position
if (IsValidShieldCover(vTestPos))
{
m_vCoverPosition = vTestPos;
return true;
}
else
{
m_vCoverPosition = vec3_origin;
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles movement towards the last move target.
// Input : flInterval -
//-----------------------------------------------------------------------------
bool CNPC_SScanner::OverridePathMove( float flInterval )
{
CBaseEntity *pMoveTarget = (GetTarget()) ? GetTarget() : GetEnemy();
Vector waypointDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
VectorNormalize(waypointDir);
// -----------------------------------------------------------------
// Check route is blocked
// ------------------------------------------------------------------
Vector checkPos = GetLocalOrigin() + (waypointDir * (m_flSpeed * flInterval));
AIMoveTrace_t moveTrace;
GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), checkPos, MASK_NPCSOLID|CONTENTS_WATER,
pMoveTarget,&moveTrace);
if (IsMoveBlocked( moveTrace ))
{
TaskFail(FAIL_NO_ROUTE);
GetNavigator()->ClearGoal();
return true;
}
// --------------------------------------------------
// Check if I've reached my goal
// --------------------------------------------------
Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
if ( ProgressFlyPath( flInterval, pMoveTarget, MASK_NPCSOLID,
!IsCurSchedule( SCHED_SSCANNER_PATROL ) ) == AINPP_COMPLETE )
{
if (IsCurSchedule( SCHED_SSCANNER_PATROL ))
{
m_vLastPatrolDir = lastPatrolDir;
VectorNormalize(m_vLastPatrolDir);
}
return true;
}
return false;
}
bool CNPC_SScanner::OverrideMove(float flInterval)
{
// ----------------------------------------------
// Select move target
// ----------------------------------------------
CBaseEntity* pMoveTarget = NULL;
float fNearDist = 0;
float fFarDist = 0;
if (GetTarget() != NULL )
{
pMoveTarget = GetTarget();
fNearDist = SSCANNER_COVER_NEAR_DIST;
fFarDist = SSCANNER_COVER_FAR_DIST;
}
else if (GetEnemy() != NULL )
{
pMoveTarget = GetEnemy();
fNearDist = SSCANNER_ATTACK_NEAR_DIST;
fFarDist = SSCANNER_ATTACK_FAR_DIST;
}
// -----------------------------------------
// See if we can fly there directly
// -----------------------------------------
if (pMoveTarget)
{
trace_t tr;
Vector endPos = GetAbsOrigin() + GetCurrentVelocity()*flInterval;
AI_TraceHull(GetAbsOrigin(), pMoveTarget->GetAbsOrigin() + Vector(0,0,150),
GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
if (tr.fraction != 1.0)
{
/*
NDebugOverlay::Cross3D(GetLocalOrigin()+Vector(0,0,-60),Vector(-15,-15,-15),Vector(5,5,5),0,255,255,true,0.1);
*/
SetCondition( COND_SSCANNER_FLY_BLOCKED );
}
else
{
SetCondition( COND_SSCANNER_FLY_CLEAR );
}
}
// -----------------------------------------------------------------
// If I have a route, keep it updated and move toward target
// ------------------------------------------------------------------
if (GetNavigator()->IsGoalActive())
{
if ( OverridePathMove( flInterval ) )
return true;
}
// ----------------------------------------------
// Move to target directly if path is clear
// ----------------------------------------------
else if ( pMoveTarget && HasCondition( COND_SSCANNER_FLY_CLEAR ) )
{
MoveToEntity(flInterval, pMoveTarget, fNearDist, fFarDist);
}
// -----------------------------------------------------------------
// Otherwise just decelerate
// -----------------------------------------------------------------
else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -