1
0
mirror of https://github.com/alliedmodders/hl2sdk.git synced 2025-01-05 17:13:36 +08:00
hl2sdk/dlls/episodic/ai_behavior_passenger_zombie.cpp
2008-09-15 01:33:59 -05:00

722 lines
22 KiB
C++

//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose: Zombies on cars!
//
//=============================================================================
#include "cbase.h"
#include "npcevent.h"
#include "ai_motor.h"
#include "ai_senses.h"
#include "vehicle_jeep_episodic.h"
#include "ai_behavior_passenger_zombie.h"
#define JUMP_ATTACH_DIST_THRESHOLD 1000
#define JUMP_ATTACH_FACING_THRESHOLD 0.70710678 // cos(45)
#define ATTACH_PREDICTION_INTERVAL 0.2f
#define ATTACH_PREDICTION_FACING_THRESHOLD 0.75f
#define ATTACH_PREDICTION_DIST_THRESHOLD 128
int ACT_PASSENGER_MELEE_ATTACK1;
int ACT_PASSENGER_THREATEN;
int ACT_PASSENGER_FLINCH;
int ACT_PASSENGER_ZOMBIE_LEAP_LOOP;
BEGIN_DATADESC( CAI_PassengerBehaviorZombie )
DEFINE_FIELD( m_flLastVerticalLean, FIELD_FLOAT ),
DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ),
DEFINE_FIELD( m_flNextLeapTime, FIELD_TIME ),
END_DATADESC();
//==============================================================================================
// Passenger damage table
//==============================================================================================
static impactentry_t zombieLinearTable[] =
{
{ 200*200, 100 },
};
static impactentry_t zombieAngularTable[] =
{
{ 100*100, 100 },
};
impactdamagetable_t gZombiePassengerImpactDamageTable =
{
zombieLinearTable,
zombieAngularTable,
ARRAYSIZE(zombieLinearTable),
ARRAYSIZE(zombieAngularTable),
24*24, // minimum linear speed squared
360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
2, // can't take damage from anything under 2kg
5, // anything less than 5kg is "small"
5, // never take more than 5 pts of damage from anything under 5kg
36*36, // <5kg objects must go faster than 36 in/s to do damage
VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
0.0f, // min vel
};
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CAI_PassengerBehaviorZombie::CAI_PassengerBehaviorZombie( void ) :
m_flLastLateralLean( 0.0f ),
m_flLastVerticalLean( 0.0f ),
m_flNextLeapTime( 0.0f )
{
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::CanEnterVehicle( void )
{
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Translate into vehicle passengers
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::TranslateSchedule( int scheduleType )
{
if ( scheduleType == SCHED_MELEE_ATTACK1 )
return SCHED_PASSENGER_MELEE_ATTACK1;
if ( scheduleType == SCHED_RANGE_ATTACK1 )
return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
return BaseClass::TranslateSchedule( scheduleType );
}
//-----------------------------------------------------------------------------
// Purpose: Suppress melee attacks against enemies for the given duration
// Input : flDuration - Amount of time to suppress the attacks
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::SuppressAttack( float flDuration )
{
GetOuter()->SetNextAttack ( gpGlobals->curtime + flDuration );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::EnemyInVehicle( void )
{
// Obviously they're not...
if ( GetOuter()->GetEnemy() == NULL )
return false;
// See if they're in a vehicle, currently
CBaseCombatCharacter *pCCEnemy = GetOuter()->GetEnemy()->MyCombatCharacterPointer();
if ( pCCEnemy && pCCEnemy->IsInAVehicle() )
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::SelectOutsideSchedule( void )
{
// Attaching to target
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
return SCHED_MELEE_ATTACK1;
// Otherwise chase after him
return SCHED_CHASE_ENEMY;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::SelectInsideSchedule( void )
{
// Attacking target
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
return SCHED_PASSENGER_MELEE_ATTACK1;
return SCHED_IDLE_STAND;
}
//-----------------------------------------------------------------------------
// Purpose: Move the zombie to the vehicle
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::SelectSchedule( void )
{
// See if our enemy got out
if ( GetOuter()->GetEnemy() != NULL && EnemyInVehicle() == false )
{
if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
{
// Exit the vehicle
SetCondition( COND_EXITING_VEHICLE );
}
}
// Entering schedule
if ( HasCondition( COND_ENTERING_VEHICLE ) )
{
ClearCondition( COND_ENTERING_VEHICLE );
return SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE;
}
// Exiting schedule
if ( HasCondition( COND_EXITING_VEHICLE ) )
{
ClearCondition( COND_EXITING_VEHICLE );
return SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE;
}
// Select different schedules based on our state
PassengerState_e nState = GetPassengerState();
int nNewSchedule = SCHED_NONE;
if ( nState == PASSENGER_STATE_INSIDE )
{
nNewSchedule = SelectInsideSchedule();
if ( nNewSchedule != SCHED_NONE )
return nNewSchedule;
}
else if ( nState == PASSENGER_STATE_OUTSIDE )
{
nNewSchedule = SelectOutsideSchedule();
if ( nNewSchedule != SCHED_NONE )
return nNewSchedule;
}
// Worst case he just stands here
Assert(0);
return SCHED_IDLE_STAND;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::CanJumpToAttachToVehicle( void )
{
// FIXME: Probably move this up one level and out of this function
if ( m_flNextLeapTime > gpGlobals->curtime )
return false;
// Predict an attachment jump
CBaseEntity *pEnemy = GetOuter()->GetEnemy();
Vector vecPredictedPosition;
UTIL_PredictedPosition( pEnemy, 1.0f, &vecPredictedPosition );
float flDist = UTIL_DistApprox( vecPredictedPosition, GetOuter()->GetAbsOrigin() );
// If we're facing them enough, allow the jump
if ( ( flDist < JUMP_ATTACH_DIST_THRESHOLD ) && UTIL_IsFacingWithinTolerance( GetOuter(), pEnemy, JUMP_ATTACH_FACING_THRESHOLD ) )
{
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Determine if we can jump to be on the enemy's vehicle
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
inline bool CAI_PassengerBehaviorZombie::CanBeOnEnemyVehicle( void )
{
CBaseCombatCharacter *pEnemy = ToBaseCombatCharacter( GetOuter()->GetEnemy() );
if ( pEnemy != NULL )
{
IServerVehicle *pVehicle = pEnemy->GetVehicle();
if ( pVehicle && pVehicle->NPC_HasAvailableSeat( GetRoleName() ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::GatherConditions( void )
{
BaseClass::GatherConditions();
// Always clear the base conditions
ClearCondition( COND_CAN_MELEE_ATTACK1 );
// Behavior when outside the vehicle
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
{
if ( CanBeOnEnemyVehicle() && CanJumpToAttachToVehicle() )
{
SetCondition( COND_CAN_RANGE_ATTACK1 );
}
}
// Behavior when on the car
if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
{
// Check for melee attack
if ( GetOuter()->GetNextAttack() < gpGlobals->curtime )
{
SetCondition( COND_CAN_MELEE_ATTACK1 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Handle death case
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::Event_Killed( const CTakeDamageInfo &info )
{
if ( m_hVehicle )
{
// Stop taking messages from the vehicle
m_hVehicle->RemovePhysicsChild( GetOuter() );
m_hVehicle->NPC_RemovePassenger( GetOuter() );
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
}
BaseClass::Event_Killed( info );
}
//-----------------------------------------------------------------------------
// Purpose: Build our custom interrupt cases for the behavior
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::BuildScheduleTestBits( void )
{
// Always interrupt when we need to get in or out
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
{
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_CAN_RANGE_ATTACK1 ) );
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_ENTERING_VEHICLE ) );
}
BaseClass::BuildScheduleTestBits();
}
//-----------------------------------------------------------------------------
// Purpose: Get the absolute position of the desired attachment point
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::GetAttachmentPoint( Vector *vecPoint )
{
Vector vecEntryOffset, vecFinalOffset;
GetEntryTarget( &vecEntryOffset, NULL );
VectorRotate( vecEntryOffset, m_hVehicle->GetAbsAngles(), vecFinalOffset );
*vecPoint = ( m_hVehicle->GetAbsOrigin() + vecFinalOffset );
}
/*
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_PassengerBehaviorZombie::WithinAttachRange( void )
{
// Target's predicted position
Vector vecPredictedTargetPos;
UTIL_PredictedPosition( GetEnemy(), ATTACH_PREDICTION_INTERVAL, &vecPredictedTargetPos );
// Our predicted position
Vector vecPredictedPos;
UTIL_PredictedPosition( GetOuter(), ATTACH_PREDICTION_INTERVAL, &vecPredictedPos );
// Get our target position
Vector vecAttachPoint;
GetAttachmentPoint( &vecAttachPoint );
// Get our current velocity and direction to the target position
Vector vecVelocity = GetOuter()->GetAbsVelocity();
Vector vecTargetDir = vecAttachPoint - GetOuter()->GetAbsOrigin();
VectorNormalize( vecVelocity );
float flDist = VectorNormalize( vecTargetDir );
float flDot = vecVelocity.Dot( vecTargetDir );
// Must be close enough to the target and also mostly moving towards it
if ( ( flDot > ATTACH_PREDICTION_FACING_THRESHOLD ) && ( flDist < ATTACH_PREDICTION_DIST_THRESHOLD ) )
{
return true;
}
// Not there yet
return false;
}
*/
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
int CAI_PassengerBehaviorZombie::FindExitSequence( void )
{
// Get a list of all our animations
const PassengerSeatAnims_t *pExitAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_EXIT );
if ( pExitAnims == NULL )
return -1;
// Test each animation (sorted by priority) for the best match
for ( int i = 0; i < pExitAnims->Count(); i++ )
{
// Find the activity for this animation name
int nSequence = GetOuter()->LookupSequence( STRING( pExitAnims->Element(i).GetAnimationName() ) );
Assert( nSequence != -1 );
if ( nSequence == -1 )
continue;
return nSequence;
}
return -1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::StartDismount( void )
{
// Leap off the vehicle
int nSequence = FindExitSequence();
Assert( nSequence != -1 );
SetTransitionSequence( nSequence );
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
// This removes the NPC from the vehicle's handling and fires all necessary outputs
m_hVehicle->RemovePhysicsChild( GetOuter() );
m_hVehicle->NPC_RemovePassenger( GetOuter() );
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) );
// Detach from the parent
GetOuter()->SetParent( NULL );
GetOuter()->SetMoveType( MOVETYPE_STEP );
GetMotor()->SetYawLocked( false );
QAngle vecAngles = GetAbsAngles();
vecAngles.z = 0.0f;
GetOuter()->SetAbsAngles( vecAngles );
// HACK: Will this work?
IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
if ( pPhysObj != NULL )
{
pPhysObj->EnableCollisions( true );
}
// Clear this
m_PassengerIntent = PASSENGER_INTENT_NONE;
m_hVehicle = NULL;
SetPassengerState( PASSENGER_STATE_EXITING );
// Get the velocity
Vector vecUp, vecJumpDir;
GetOuter()->GetVectors( &vecJumpDir, NULL, &vecUp );
// Move back and up
vecJumpDir *= random->RandomFloat( -400.0f, -500.0f );
vecJumpDir += vecUp * 150.0f;
GetOuter()->SetAbsVelocity( vecJumpDir );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::FinishDismount( void )
{
SetPassengerState( PASSENGER_STATE_OUTSIDE );
Disable();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_FACE_HINTNODE:
case TASK_FACE_LASTPOSITION:
case TASK_FACE_SAVEPOSITION:
case TASK_FACE_TARGET:
case TASK_FACE_IDEAL:
case TASK_FACE_SCRIPT:
case TASK_FACE_PATH:
TaskComplete();
break;
case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
break;
case TASK_MELEE_ATTACK1:
{
// Swipe
GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_MELEE_ATTACK1 );
// Randomly attack again in the future
float flWait = random->RandomFloat( 0.0f, 1.0f );
SuppressAttack( flWait );
}
break;
case TASK_PASSENGER_ZOMBIE_DISMOUNT:
{
// Start the process of dismounting from the vehicle
StartDismount();
}
break;
default:
BaseClass::StartTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Handle task running
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
{
// Face the entry point
Vector vecAttachPoint;
GetAttachmentPoint( &vecAttachPoint );
GetOuter()->GetMotor()->SetIdealYawToTarget( vecAttachPoint );
// All done when you touch the ground
if ( GetOuter()->GetFlags() & FL_ONGROUND )
{
m_flNextLeapTime = gpGlobals->curtime + 2.0f;
TaskComplete();
return;
}
}
break;
case TASK_MELEE_ATTACK1:
if ( GetOuter()->IsSequenceFinished() )
{
TaskComplete();
}
break;
case TASK_PASSENGER_ZOMBIE_DISMOUNT:
{
if ( GetOuter()->IsSequenceFinished() )
{
// Completely separate from the vehicle
FinishDismount();
TaskComplete();
}
break;
}
default:
BaseClass::RunTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Enter the vehicle
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::EnterVehicle( void )
{
if ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), false ) == false )
return;
// Reserve the seat
if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
return;
// Get a list of all our animations
const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
if ( pEntryAnims == NULL )
return;
// Test each animation (sorted by priority) for the best match
for ( int i = 0; i < pEntryAnims->Count(); i++ )
{
// Find the activity for this animation name
const CPassengerSeatTransition *pTransition = &pEntryAnims->Element(i);
int nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
Assert( nSequence != -1 );
if ( nSequence == -1 )
continue;
SetTransitionSequence( nSequence );
break;
}
// Get in the vehicle
BaseClass::EnterVehicle();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::ExitVehicle( void )
{
BaseClass::ExitVehicle();
// Remove us as a passenger
m_hVehicle->NPC_RemovePassenger( GetOuter() );
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
}
//-----------------------------------------------------------------------------
// Purpose: Calculate our body lean based on our delta velocity
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::CalculateBodyLean( void )
{
// Calculate our lateral displacement from a perfectly centered start
float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );
// FIXME: Framerate dependant!
m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );
// Factor in a "stun" if the zombie was moved too far off course
if ( fabs( m_flLastLateralLean ) > 0.5f )
{
SuppressAttack( 0.5f );
}
// Calc our vertical displacement
float flVerticalDisp = SimpleSplineRemapVal( m_vehicleState.m_vecDeltaVelocity.z, -50.0f, 50.0f, -1.0f, 1.0f );
flVerticalDisp = clamp( flVerticalDisp, -1.0f, 1.0f );
// FIXME: Framerate dependant!
m_flLastVerticalLean = ( m_flLastVerticalLean * 0.75f ) + ( flVerticalDisp * 0.25f );
// Set these parameters
GetOuter()->SetPoseParameter( "lean_lateral", m_flLastLateralLean );
GetOuter()->SetPoseParameter( "lean_vertical", m_flLastVerticalLean );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_PassengerBehaviorZombie::GatherVehicleStateConditions( void )
{
// Call to the base
BaseClass::GatherVehicleStateConditions();
// Only do this if we're on the vehicle
if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
return;
// Calculate how our body is leaning
CalculateBodyLean();
// The forward delta of the vehicle
float flLateralDelta = ( m_vehicleState.m_vecDeltaVelocity.x + m_vehicleState.m_vecDeltaVelocity.y );
// Detect a sudden stop
if ( flLateralDelta < -350.0f )
{
if ( m_hVehicle )
{
Vector vecDamageForce;
m_hVehicle->GetVelocity( &vecDamageForce, NULL );
VectorNormalize( vecDamageForce );
vecDamageForce *= random->RandomFloat( 50000.0f, 60000.0f );
//NDebugOverlay::HorzArrow( GetAbsOrigin(), GetAbsOrigin() + ( vecDamageForce * 256.0f ), 16.0f, 255, 0, 0, 16, true, 2.0f );
// Fake it!
CTakeDamageInfo info( m_hVehicle, m_hVehicle, vecDamageForce, GetOuter()->WorldSpaceCenter(), 200, (DMG_CRUSH|DMG_VEHICLE) );
GetOuter()->TakeDamage( info );
}
}
else if ( flLateralDelta < -150.0f )
{
// FIXME: Realistically this should interrupt and play a schedule to do it
GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_FLINCH );
}
}
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorZombie )
{
DECLARE_ACTIVITY( ACT_PASSENGER_MELEE_ATTACK1 )
DECLARE_ACTIVITY( ACT_PASSENGER_THREATEN )
DECLARE_ACTIVITY( ACT_PASSENGER_FLINCH )
DECLARE_ACTIVITY( ACT_PASSENGER_ZOMBIE_LEAP_LOOP )
DECLARE_TASK( TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 )
DECLARE_TASK( TASK_PASSENGER_ZOMBIE_DISMOUNT )
DEFINE_SCHEDULE
(
SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE,
" Tasks"
" TASK_PASSENGER_ATTACH_TO_VEHICLE 0"
" TASK_PASSENGER_ENTER_VEHICLE 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_THREATEN"
""
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_IDLE"
" TASK_STOP_MOVING 0"
" TASK_PASSENGER_ZOMBIE_DISMOUNT 0"
""
" Interrupts"
" COND_TASK_FAILED"
)
DEFINE_SCHEDULE
(
SCHED_PASSENGER_MELEE_ATTACK1,
" Tasks"
" TASK_ANNOUNCE_ATTACK 1"
" TASK_MELEE_ATTACK1 0"
""
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
" Tasks"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_RANGE_ATTACK1"
" TASK_SET_ACTIVITY ACTIVITY:ACT_PASSENGER_ZOMBIE_LEAP_LOOP"
" TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 0"
" "
" Interrupts"
)
AI_END_CUSTOM_SCHEDULE_PROVIDER()
}