581 lines
15 KiB
C++
581 lines
15 KiB
C++
// ai_addon.cpp
|
|
#include "cbase.h"
|
|
#include "ai_addon.h"
|
|
#include "ai_basenpc.h"
|
|
#include "ai_behavior_addonhost.h"
|
|
#include "strtools.h"
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// Answers YES if the attachment is on the model and not
|
|
// currently plugged by another add-on.
|
|
//
|
|
// Answers NO if the host's model lacks the attachment or
|
|
// the add-on is currently plugged by another add-on.
|
|
//
|
|
// This is terribly inefficient right now, but it works
|
|
// and lets us move on.
|
|
//---------------------------------------------------------
|
|
bool IsAddOnAttachmentAvailable( CAI_BaseNPC *pHost, char *pszAttachmentName )
|
|
{
|
|
char szOrigialAttachmentName[ 256 ];
|
|
V_strcpy_safe( szOrigialAttachmentName, pszAttachmentName );
|
|
|
|
int iCount = 0;
|
|
|
|
// If this loops 20 times, the translate function probably isn't returning the end of list value "" -Jeep
|
|
while ( iCount < 20 )
|
|
{
|
|
// Try another
|
|
Q_strcpy( pszAttachmentName, szOrigialAttachmentName );
|
|
|
|
pHost->TranslateAddOnAttachment( pszAttachmentName, iCount );
|
|
|
|
if ( pszAttachmentName[ 0 ] == '\0' )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( pHost->LookupAttachment(pszAttachmentName) == 0 )
|
|
{
|
|
// Translated to an attachment that doesn't exist
|
|
Msg("***AddOn Error! Host NPC %s does not have attachment %s\n", pHost->GetDebugName(), pszAttachmentName );
|
|
return false;
|
|
}
|
|
|
|
int iWishedAttachmentID = pHost->LookupAttachment( pszAttachmentName );
|
|
|
|
CAI_BehaviorBase **ppBehaviors = pHost->AccessBehaviors();
|
|
int nBehaviors = pHost->NumBehaviors();
|
|
|
|
bool bAttachmentFilled = false;
|
|
|
|
for ( int i = 0; i < nBehaviors && !bAttachmentFilled; i++ )
|
|
{
|
|
CAI_AddOnBehaviorBase *pAddOnBehavior = dynamic_cast<CAI_AddOnBehaviorBase *>(ppBehaviors[i]);
|
|
if ( pAddOnBehavior )
|
|
{
|
|
CAI_AddOn **ppAddOns = pAddOnBehavior->GetAddOnsBase();
|
|
int nAddOns = pAddOnBehavior->NumAddOns();
|
|
for ( int j = 0; j < nAddOns && !bAttachmentFilled; j++ )
|
|
{
|
|
bAttachmentFilled = ( ppAddOns[j]->GetAttachmentID() == iWishedAttachmentID );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bAttachmentFilled )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
++iCount;
|
|
}
|
|
|
|
// We should never get here
|
|
DevWarning( "Translating the attachment was tried more than 50 times!\n" );
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
int CountAddOns( CAI_BaseNPC *pHost )
|
|
{
|
|
int nAddOns = 0;
|
|
CAI_BehaviorBase **ppBehaviors = pHost->AccessBehaviors();
|
|
int nBehaviors = pHost->NumBehaviors();
|
|
|
|
for ( int i = 0; i < nBehaviors; i++ )
|
|
{
|
|
CAI_AddOnBehaviorBase *pAddOnBehavior = dynamic_cast<CAI_AddOnBehaviorBase *>(ppBehaviors[i]);
|
|
if ( pAddOnBehavior )
|
|
{
|
|
nAddOns += pAddOnBehavior->NumAddOns();
|
|
}
|
|
}
|
|
|
|
return nAddOns;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
BEGIN_DATADESC( CAI_AddOn )
|
|
DEFINE_FIELD( m_hNPCHost, FIELD_EHANDLE ),
|
|
DEFINE_THINKFUNC( DispatchAddOnThink ),
|
|
|
|
DEFINE_FIELD( m_hPhysReplacement, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_iPhysReplacementSolidFlags, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iPhysReplacementMoveType, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_angPhysReplacementLocalOrientation, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_vecPhysReplacementDetatchForce, FIELD_VECTOR ),
|
|
|
|
DEFINE_FIELD( m_bWasAttached, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flNextAttachTime, FIELD_FLOAT ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "Install", InputInstall ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Remove", InputRemove ),
|
|
END_DATADESC()
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::Precache()
|
|
{
|
|
BaseClass::Precache();
|
|
PrecacheModel( GetAddOnModelName() );
|
|
PrecacheScriptSound( "AddOn.Install" );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::Spawn()
|
|
{
|
|
BaseClass::Spawn();
|
|
Precache();
|
|
|
|
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
|
|
|
|
if ( pPhysReplacement )
|
|
{
|
|
// Use the same model as the replacement
|
|
SetModel( pPhysReplacement->GetModelName().ToCStr() );
|
|
}
|
|
else
|
|
{
|
|
SetModel( GetAddOnModelName() );
|
|
}
|
|
|
|
SetCollisionGroup( COLLISION_GROUP_WEAPON );
|
|
|
|
VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags() | FSOLID_TRIGGER, false );
|
|
SetMoveType( MOVETYPE_VPHYSICS );
|
|
|
|
SetThink( &CAI_AddOn::DispatchAddOnThink );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::UpdateOnRemove()
|
|
{
|
|
Remove();
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
int CAI_AddOn::DrawDebugTextOverlays( void )
|
|
{
|
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
// Draw debug text for agent
|
|
m_AgentDebugOverlays = m_debugOverlays;
|
|
|
|
Vector vecLocalCenter;
|
|
|
|
ICollideable *pCollidable = GetCollideable();
|
|
|
|
if ( pCollidable )
|
|
{
|
|
VectorAdd( pCollidable->OBBMins(), pCollidable->OBBMaxs(), vecLocalCenter );
|
|
vecLocalCenter *= 0.5f;
|
|
|
|
if ( ( pCollidable->GetCollisionAngles() == vec3_angle ) || ( vecLocalCenter == vec3_origin ) )
|
|
{
|
|
VectorAdd( vecLocalCenter, pCollidable->GetCollisionOrigin(), m_vecAgentDebugOverlaysPos );
|
|
}
|
|
else
|
|
{
|
|
VectorTransform( vecLocalCenter, pCollidable->CollisionToWorldTransform(), m_vecAgentDebugOverlaysPos );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_vecAgentDebugOverlaysPos = GetAbsOrigin();
|
|
}
|
|
|
|
text_offset = static_cast<CAI_Agent*>( this )->DrawDebugTextOverlays( text_offset );
|
|
|
|
return text_offset;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::GatherConditions()
|
|
{
|
|
CAI_Agent::GatherConditions();
|
|
|
|
ClearCondition( COND_ADDON_LOST_HOST );
|
|
|
|
if( GetNPCHost() )
|
|
{
|
|
m_bWasAttached = true;
|
|
}
|
|
else
|
|
{
|
|
if( m_bWasAttached == true )
|
|
{
|
|
SetCondition( COND_ADDON_LOST_HOST );
|
|
|
|
if ( m_flNextAttachTime != 0.0f && m_flNextAttachTime < gpGlobals->curtime )
|
|
{
|
|
m_flNextAttachTime = 0.0f;
|
|
m_bWasAttached = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
int CAI_AddOn::SelectSchedule( void )
|
|
{
|
|
return SCHED_ADDON_NO_OWNER;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::StartTask( const Task_t *pTask )
|
|
{
|
|
switch( pTask->iTask )
|
|
{
|
|
case TASK_ADDON_WAIT:
|
|
m_flWaitFinished = gpGlobals->curtime + pTask->flTaskData;
|
|
break;
|
|
|
|
case TASK_ADDON_WAIT_RANDOM:
|
|
m_flWaitFinished = gpGlobals->curtime + random->RandomFloat( 0.1f, pTask->flTaskData );
|
|
break;
|
|
|
|
default:
|
|
CAI_Agent::StartTask( pTask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::RunTask( const Task_t *pTask )
|
|
{
|
|
switch( pTask->iTask )
|
|
{
|
|
case TASK_ADDON_WAIT:
|
|
case TASK_ADDON_WAIT_RANDOM:
|
|
if( gpGlobals->curtime > m_flWaitFinished )
|
|
TaskComplete();
|
|
break;
|
|
|
|
default:
|
|
CAI_Agent::RunTask( pTask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CAI_AddOn::SetPhysReplacement( CBaseEntity *pEntity )
|
|
{
|
|
m_hPhysReplacement = pEntity;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
bool CAI_AddOn::Attach( CAI_BaseNPC *pHost )
|
|
{
|
|
// Make sure we're not already attached to someone else!
|
|
Assert( GetAttachmentID() == INVALID_ADDON_ATTACHMENT_ID );
|
|
|
|
char szAttachmentName[ 256 ];
|
|
szAttachmentName[ 0 ] = '\0';
|
|
|
|
PickAttachment( pHost, szAttachmentName );
|
|
|
|
if ( szAttachmentName[ 0 ] == '\0' )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int iAttachmentIndex = pHost->LookupAttachment( szAttachmentName );
|
|
if ( !iAttachmentIndex )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Vector vecOrigin;
|
|
Vector vecForward, vecRight, vecUp;
|
|
QAngle angles;
|
|
|
|
pHost->GetAttachment( iAttachmentIndex, vecOrigin, angles );
|
|
|
|
AngleVectors( angles, &vecForward, &vecRight, &vecUp );
|
|
|
|
SetAbsOrigin( vecOrigin + GetAttachOffset( angles ) );
|
|
SetAbsAngles( GetAttachOrientation( angles ) );
|
|
m_iAttachmentID = iAttachmentIndex;
|
|
SetParent( pHost, iAttachmentIndex );
|
|
|
|
QAngle angLocalAngles = GetLocalOrientation();
|
|
|
|
SetLocalAngles( angLocalAngles );
|
|
|
|
// Stop acting physical
|
|
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
|
|
if ( pPhysicsObject )
|
|
{
|
|
pPhysicsObject->EnableMotion( false );
|
|
pPhysicsObject->EnableCollisions( false );
|
|
}
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
|
|
// Handle the phys replacement
|
|
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
|
|
if ( pPhysReplacement )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
|
pPlayer->ForceDropOfCarriedPhysObjects( pPhysReplacement );
|
|
|
|
pPhysReplacement->AddEffects( EF_NODRAW );
|
|
|
|
m_iPhysReplacementSolidFlags = pPhysReplacement->GetSolidFlags();
|
|
pPhysReplacement->SetSolidFlags( FSOLID_NOT_SOLID );
|
|
|
|
m_iPhysReplacementMoveType = pPhysReplacement->GetMoveType();
|
|
pPhysReplacement->SetMoveType( MOVETYPE_NONE );
|
|
|
|
pPhysReplacement->SetAbsOrigin( vecOrigin + GetAttachOffset( angles ) );
|
|
pPhysReplacement->SetAbsAngles( GetAttachOrientation( angles ) );
|
|
pPhysReplacement->SetParent( pHost, iAttachmentIndex );
|
|
pPhysReplacement->SetOwnerEntity( pHost );
|
|
|
|
m_angPhysReplacementLocalOrientation = pPhysReplacement->GetLocalAngles();
|
|
pPhysReplacement->SetLocalAngles( angLocalAngles );
|
|
|
|
IPhysicsObject *pReplacementPhysObject = pPhysReplacement->VPhysicsGetObject();
|
|
if ( pReplacementPhysObject )
|
|
{
|
|
pReplacementPhysObject->EnableMotion( false );
|
|
pReplacementPhysObject->EnableCollisions( false );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CAI_AddOn::Dettach( void )
|
|
{
|
|
if ( !m_bWasAttached )
|
|
return;
|
|
|
|
m_flNextAttachTime = gpGlobals->curtime + 2.0f;
|
|
|
|
m_hNPCHost.Set( NULL );
|
|
SetParent( NULL );
|
|
|
|
IPhysicsObject *pPhysObject = NULL;
|
|
|
|
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
|
|
if ( pPhysReplacement )
|
|
{
|
|
// Make the replacement visible
|
|
pPhysReplacement->RemoveEffects( EF_NODRAW );
|
|
|
|
pPhysReplacement->SetSolidFlags( m_iPhysReplacementSolidFlags );
|
|
pPhysReplacement->SetMoveType( MoveType_t( m_iPhysReplacementMoveType ) );
|
|
|
|
pPhysObject = pPhysReplacement->VPhysicsGetObject();
|
|
if ( pPhysObject )
|
|
{
|
|
pPhysReplacement->SetMoveType( MOVETYPE_VPHYSICS );
|
|
}
|
|
|
|
pPhysReplacement->SetParent( NULL );
|
|
pPhysReplacement->SetOwnerEntity( NULL );
|
|
|
|
pPhysReplacement->SetLocalAngles( m_angPhysReplacementLocalOrientation );
|
|
|
|
// Kill ourselves off because we're being replaced
|
|
UTIL_Remove( this );
|
|
}
|
|
else
|
|
{
|
|
pPhysObject = VPhysicsGetObject();
|
|
if ( pPhysObject )
|
|
{
|
|
SetMoveType( MOVETYPE_VPHYSICS );
|
|
}
|
|
}
|
|
|
|
if ( pPhysObject )
|
|
{
|
|
// Start acting physical
|
|
pPhysObject->EnableCollisions( true );
|
|
pPhysObject->EnableMotion( true );
|
|
pPhysObject->EnableGravity( true );
|
|
pPhysObject->SetPosition( GetAbsOrigin(), GetAbsAngles(), true );
|
|
pPhysObject->Wake();
|
|
|
|
pPhysObject->AddVelocity( &m_vecPhysReplacementDetatchForce, NULL );
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Return true if I successfully attach to the NPC host.
|
|
//
|
|
// Return false if I am already attached to an NPC, or
|
|
// could not be attached to this host.
|
|
//---------------------------------------------------------
|
|
bool CAI_AddOn::Install( CAI_BaseNPC *pHost, bool bRemoveOnFail )
|
|
{
|
|
if( m_bWasAttached )
|
|
return false;
|
|
|
|
// Associate the addon with this host
|
|
Assert( m_hNPCHost == NULL ); // For now, prevent slamming from one host to the next.
|
|
m_hNPCHost.Set( pHost );
|
|
|
|
// Parent and
|
|
if ( Attach( pHost ) )
|
|
{
|
|
Bind();
|
|
return true;
|
|
}
|
|
|
|
// Failed to attach
|
|
m_hNPCHost = NULL;
|
|
|
|
if ( bRemoveOnFail || m_hPhysReplacement.Get() )
|
|
{
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
CAI_BaseNPC *CAI_AddOn::GetNPCHost()
|
|
{
|
|
return m_hNPCHost.Get();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
CBaseEntity *CAI_AddOn::GetHostEnemy()
|
|
{
|
|
if( !GetNPCHost() )
|
|
return NULL;
|
|
|
|
return GetNPCHost()->GetEnemy();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::DispatchAddOnThink()
|
|
{
|
|
if( GetNPCHost() != NULL && !GetNPCHost()->IsAlive() )
|
|
{
|
|
EjectFromHost();
|
|
return;
|
|
}
|
|
|
|
CAI_Agent::Think(); SetNextThink( gpGlobals->curtime + GetThinkInterval() );
|
|
}
|
|
|
|
QAngle CAI_AddOn::GetLocalOrientation( void )
|
|
{
|
|
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
|
|
|
|
if ( pPhysReplacement )
|
|
{
|
|
CBaseAnimating *pBaseAnimatingReplacement = dynamic_cast<CBaseAnimating *>( pPhysReplacement );
|
|
if ( pBaseAnimatingReplacement )
|
|
{
|
|
int iMuzzle = pBaseAnimatingReplacement->LookupAttachment( "muzzle" );
|
|
if ( iMuzzle )
|
|
{
|
|
// Use the muzzle angles!
|
|
Vector vecMuzzleOrigin;
|
|
QAngle angMuzzleAngles;
|
|
pBaseAnimatingReplacement->GetAttachmentLocal( iMuzzle, vecMuzzleOrigin, angMuzzleAngles );
|
|
return angMuzzleAngles;
|
|
}
|
|
}
|
|
|
|
// Use the local angles
|
|
return pPhysReplacement->GetLocalAngles();
|
|
}
|
|
|
|
// No special angles to use
|
|
return QAngle( 0.0f, 0.0f, 0.0f );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::EjectFromHost()
|
|
{
|
|
Unbind();
|
|
m_hNPCHost.Set( NULL );
|
|
|
|
SetThink( NULL );
|
|
SetParent( NULL );
|
|
|
|
SetSize( Vector( 0,0,0), Vector(0,0,0) );
|
|
SetMoveType( MOVETYPE_FLYGRAVITY );
|
|
SetMoveCollide( MOVECOLLIDE_FLY_BOUNCE );
|
|
SetSolid( SOLID_BBOX );
|
|
|
|
Vector vecDir;
|
|
GetVectors( NULL, NULL, &vecDir );
|
|
|
|
SetAbsVelocity( GetAbsVelocity() + vecDir * RandomFloat(50, 200) );
|
|
QAngle avelocity( RandomFloat( 10, 60), RandomFloat( 10, 60), 0 );
|
|
SetLocalAngularVelocity( avelocity );
|
|
|
|
SetThink( &CBaseEntity::SUB_FadeOut );
|
|
SetNextThink( gpGlobals->curtime + 1.0f );
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::InputInstall( inputdata_t &data )
|
|
{
|
|
CAI_BaseNPC *pHost = dynamic_cast<CAI_BaseNPC *>( gEntList.FindEntityByName( NULL, data.value.String() ) );
|
|
|
|
if( !pHost )
|
|
{
|
|
DevMsg(" AddOn: %s couldn't find Host %s\n", GetDebugName(), data.value.String() );
|
|
}
|
|
else
|
|
{
|
|
Install( pHost );
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
void CAI_AddOn::InputRemove( inputdata_t &data )
|
|
{
|
|
Remove();
|
|
m_hNPCHost.Set( NULL );
|
|
SetThink( NULL );
|
|
SetParent( NULL );
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
|
|
AI_BEGIN_AGENT_(CAI_AddOn,CAI_Agent)
|
|
DECLARE_TASK( TASK_ADDON_WAIT )
|
|
DECLARE_TASK( TASK_ADDON_WAIT_RANDOM )
|
|
|
|
DECLARE_CONDITION( COND_ADDON_LOST_HOST )
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ADDON_NO_OWNER,
|
|
" Tasks"
|
|
" TASK_ADDON_WAIT 1"
|
|
" "
|
|
" Interrupts"
|
|
" "
|
|
)
|
|
AI_END_AGENT()
|