1146 lines
31 KiB
C++
1146 lines
31 KiB
C++
|
|
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
|
|
#include "AI.h"
|
|
|
|
#ifndef __GAME_PROJECTILE_H__
|
|
#include "../Projectile.h"
|
|
#endif
|
|
|
|
#include "AI_Manager.h"
|
|
#include "../vehicle/Vehicle.h"
|
|
#include "../spawner.h"
|
|
|
|
|
|
#define idAI_EVENT( eventname, inparms, outparm) \
|
|
const idEventDef AI_##eventname( #eventname, inparms, outparm); \
|
|
void idAI::Event_##eventname
|
|
|
|
|
|
/***********************************************************************
|
|
|
|
AI Events
|
|
|
|
***********************************************************************/
|
|
|
|
// Get / Set
|
|
const idEventDef AI_GetLeader ( "getLeader", NULL, 'e' );
|
|
const idEventDef AI_SetLeader ( "setLeader", "E" );
|
|
const idEventDef AI_GetEnemy ( "getEnemy", NULL, 'e' );
|
|
const idEventDef AI_SetEnemy ( "setEnemy", "E" );
|
|
const idEventDef AI_SetHealth ( "setHealth", "f" );
|
|
const idEventDef AI_SetTalkState ( "setTalkState", "d" );
|
|
const idEventDef AI_SetScript ( "setScript", "ss" );
|
|
const idEventDef AI_SetMoveSpeed ( "setMoveSpeed", "d" );
|
|
const idEventDef AI_SetPassivePrefix ( "setPassivePrefix", "s" );
|
|
|
|
// Enable / Disable
|
|
const idEventDef AI_EnableClip ( "enableClip" );
|
|
const idEventDef AI_DisableClip ( "disableClip" );
|
|
const idEventDef AI_EnableGravity ( "enableGravity" );
|
|
const idEventDef AI_DisableGravity ( "disableGravity" );
|
|
const idEventDef AI_EnableAFPush ( "enableAFPush" );
|
|
const idEventDef AI_DisableAFPush ( "disableAFPush" );
|
|
const idEventDef AI_EnableDamage ( "enableDamage" );
|
|
const idEventDef AI_DisableDamage ( "disableDamage" );
|
|
const idEventDef AI_EnableTarget ( "enableTarget" );
|
|
const idEventDef AI_DisableTarget ( "disableTarget" );
|
|
const idEventDef AI_EnableMovement ( "enableMovement" );
|
|
const idEventDef AI_DisableMovement ( "disableMovement" );
|
|
const idEventDef AI_TakeDamage ( "takeDamage", "f" );
|
|
const idEventDef AI_SetUndying ( "setUndying", "f" );
|
|
const idEventDef AI_EnableAutoBlink ( "enableAutoBlink" );
|
|
const idEventDef AI_DisableAutoBlink ( "disableAutoBlink" );
|
|
|
|
// Scripted Sequences
|
|
const idEventDef AI_ScriptedMove ( "scriptedMove", "efd" );
|
|
const idEventDef AI_ScriptedFace ( "scriptedFace", "ed" );
|
|
const idEventDef AI_ScriptedAnim ( "scriptedAnim", "sddd" );
|
|
const idEventDef AI_ScriptedPlaybackMove ( "scriptedPlaybackMove", "sdd" );
|
|
const idEventDef AI_ScriptedPlaybackAim ( "scriptedPlaybackAim", "sdd" );
|
|
const idEventDef AI_ScriptedAction ( "scriptedAction", "ed" );
|
|
const idEventDef AI_ScriptedDone ( "scriptedDone", NULL, 'd' );
|
|
const idEventDef AI_ScriptedStop ( "scriptedStop" );
|
|
const idEventDef AI_ScriptedJumpDown ( "scriptedJumpDown", "f" );
|
|
|
|
// Misc
|
|
const idEventDef AI_LookAt ( "lookAt", "E" );
|
|
const idEventDef AI_Attack ( "attack", "ss" );
|
|
const idEventDef AI_LockEnemyOrigin ( "lockEnemyOrigin" );
|
|
const idEventDef AI_StopThinking ( "stopThinking" );
|
|
const idEventDef AI_JumpFrame ( "<jumpframe>" );
|
|
const idEventDef AI_RealKill ( "<kill>" );
|
|
const idEventDef AI_Kill ( "kill" );
|
|
const idEventDef AI_RemoveUpdateSpawner ( "removeUpdateSpawner" );
|
|
const idEventDef AI_AllowHiddenMovement ( "allowHiddenMovement", "d" );
|
|
const idEventDef AI_Speak ( "speak", "s" );
|
|
const idEventDef AI_SpeakRandom ( "speakRandom", "s" );
|
|
const idEventDef AI_IsSpeaking ( "isSpeaking", NULL, 'f' );
|
|
const idEventDef AI_IsTethered ( "isTethered", NULL, 'f' );
|
|
const idEventDef AI_IsWithinTether ( "isWithinTether", NULL, 'f' );
|
|
const idEventDef AI_LaunchMissile ( "launchMissile", "vv", 'e' );
|
|
const idEventDef AI_AttackMelee ( "attackMelee", "s", 'd' );
|
|
const idEventDef AI_DirectDamage ( "directDamage", "es" );
|
|
const idEventDef AI_RadiusDamageFromJoint ( "radiusDamageFromJoint", "ss" );
|
|
const idEventDef AI_MeleeAttackToJoint ( "meleeAttackToJoint", "ss", 'd' );
|
|
const idEventDef AI_CanBecomeSolid ( "canBecomeSolid", NULL, 'f' );
|
|
const idEventDef AI_BecomeSolid ( "becomeSolid" );
|
|
const idEventDef AI_BecomeRagdoll ( "becomeRagdoll", NULL, 'd' );
|
|
const idEventDef AI_BecomePassive ( "becomePassive", "d" );
|
|
const idEventDef AI_BecomeAggressive ( "becomeAggressive" );
|
|
const idEventDef AI_StopRagdoll ( "stopRagdoll" );
|
|
const idEventDef AI_FaceEnemy ( "faceEnemy" );
|
|
const idEventDef AI_FaceEntity ( "faceEntity", "E" );
|
|
|
|
//jshepard
|
|
const idEventDef AI_FindEnemy ( "findEnemy", "f", 'e');
|
|
|
|
void idAI::Event_Activate( idEntity *activator ) { Activate( activator );}
|
|
void idAI::Event_Touch( idEntity *other, trace_t *trace ) { OnTouch( other, trace ); }
|
|
void idAI::Event_SetEnemy( idEntity *ent ) { if ( !ent ) ClearEnemy(); else SetEnemy( ent );}
|
|
void idAI::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { DirectDamage( damageDefName, damageTarget ); }
|
|
void idAI::Event_RadiusDamageFromJoint( const char *jointname, const char *damageDefName ) { RadiusDamageFromJoint( jointname, damageDefName ); }
|
|
void idAI::Event_CanBecomeSolid( void ) { idThread::ReturnFloat( CanBecomeSolid() ); }
|
|
void idAI::Event_BecomeSolid( void ) { BecomeSolid(); }
|
|
void idAI::Event_BecomeNonSolid( void ) { BecomeNonSolid(); }
|
|
void idAI::Event_BecomeRagdoll( void ) { idThread::ReturnInt( StartRagdoll() ); }
|
|
void idAI::Event_StopRagdoll( void ) { StopRagdoll(); SetPhysics( &physicsObj ); }
|
|
void idAI::Event_SetHealth( float newHealth ) { health = newHealth; fl.takedamage = true; if( health > 0 ) aifl.dead = false; else aifl.dead = true; }
|
|
void idAI::Event_FaceEnemy( void ) { FaceEnemy(); }
|
|
void idAI::Event_FaceEntity( idEntity *ent ) { FaceEntity( ent ); }
|
|
void idAI::Event_SetTalkState( int state ) { SetTalkState ( (talkState_t)state ); }
|
|
void idAI::Event_Speak( const char *speechDecl ) { Speak( speechDecl ); }
|
|
void idAI::Event_SpeakRandom( const char *speechDecl ) { Speak( speechDecl, true ); }
|
|
void idAI::Event_GetLeader( void ) { idThread::ReturnEntity( leader ); }
|
|
void idAI::Event_SetLeader( idEntity* ent ) { SetLeader ( ent ); }
|
|
void idAI::Event_GetEnemy( void ) { idThread::ReturnEntity( enemy.ent ); }
|
|
void idAI::Event_TakeDamage( float takeDamage ) { fl.takedamage = ( takeDamage ) ? true : false; }
|
|
void idAI::Event_SetUndying( float setUndying ) { aifl.undying = ( setUndying ) ? true : false; }
|
|
|
|
|
|
void idAI::Event_IsSpeaking ( void ) { idThread::ReturnFloat ( IsSpeaking ( ) ); }
|
|
void idAI::Event_IsTethered ( void ) { idThread::ReturnFloat ( IsTethered ( ) ); }
|
|
void idAI::Event_IsWithinTether ( void ) { idThread::ReturnFloat ( IsWithinTether ( ) ); }
|
|
void idAI::Event_IsMoving ( void ) { idThread::ReturnFloat ( move.fl.moving ); }
|
|
|
|
CLASS_DECLARATION( idActor, idAI )
|
|
EVENT( EV_Activate, idAI::Event_Activate )
|
|
EVENT( EV_Touch, idAI::Event_Touch )
|
|
|
|
// Enable / Disable
|
|
EVENT( AI_EnableClip, idAI::Event_EnableClip )
|
|
EVENT( AI_DisableClip, idAI::Event_DisableClip )
|
|
EVENT( AI_EnableGravity, idAI::Event_EnableGravity )
|
|
EVENT( AI_DisableGravity, idAI::Event_DisableGravity )
|
|
EVENT( AI_EnableAFPush, idAI::Event_EnableAFPush )
|
|
EVENT( AI_DisableAFPush, idAI::Event_DisableAFPush )
|
|
EVENT( AI_EnableDamage, idAI::Event_EnableDamage )
|
|
EVENT( AI_DisableDamage, idAI::Event_DisableDamage )
|
|
EVENT( AI_EnablePain, idAI::Event_EnablePain )
|
|
EVENT( AI_DisablePain, idAI::Event_DisablePain )
|
|
EVENT( AI_EnableTarget, idAI::Event_EnableTarget )
|
|
EVENT( AI_DisableTarget, idAI::Event_DisableTarget )
|
|
EVENT( AI_TakeDamage, idAI::Event_TakeDamage )
|
|
EVENT( AI_SetUndying, idAI::Event_SetUndying )
|
|
EVENT( AI_EnableAutoBlink, idAI::Event_EnableAutoBlink )
|
|
EVENT( AI_DisableAutoBlink, idAI::Event_DisableAutoBlink )
|
|
|
|
// Scripted sequences
|
|
EVENT( AI_ScriptedMove, idAI::Event_ScriptedMove )
|
|
EVENT( AI_ScriptedFace, idAI::Event_ScriptedFace )
|
|
EVENT( AI_ScriptedAnim, idAI::Event_ScriptedAnim )
|
|
EVENT( AI_ScriptedAction, idAI::Event_ScriptedAction )
|
|
EVENT( AI_ScriptedPlaybackMove, idAI::Event_ScriptedPlaybackMove )
|
|
EVENT( AI_ScriptedPlaybackAim, idAI::Event_ScriptedPlaybackAim )
|
|
EVENT( AI_ScriptedDone, idAI::Event_ScriptedDone )
|
|
EVENT( AI_ScriptedStop, idAI::Event_ScriptedStop )
|
|
EVENT( AI_ScriptedJumpDown, idAI::Event_ScriptedJumpDown )
|
|
|
|
// Get / Set
|
|
EVENT( AI_SetTalkState, idAI::Event_SetTalkState )
|
|
EVENT( AI_SetLeader, idAI::Event_SetLeader )
|
|
EVENT( AI_GetLeader, idAI::Event_GetLeader )
|
|
EVENT( AI_SetEnemy, idAI::Event_SetEnemy )
|
|
EVENT( AI_GetEnemy, idAI::Event_GetEnemy )
|
|
EVENT( EV_GetAngles, idAI::Event_GetAngles )
|
|
EVENT( EV_SetAngles, idAI::Event_SetAngles )
|
|
EVENT( AI_SetScript, idAI::Event_SetScript )
|
|
EVENT( AI_SetMoveSpeed, idAI::Event_SetMoveSpeed )
|
|
EVENT( AI_SetPassivePrefix, idAI::Event_SetPassivePrefix )
|
|
|
|
// Misc
|
|
EVENT( AI_Attack, idAI::Event_Attack )
|
|
EVENT( AI_AttackMelee, idAI::Event_AttackMelee )
|
|
|
|
EVENT( AI_LookAt, idAI::Event_LookAt )
|
|
EVENT( AI_DirectDamage, idAI::Event_DirectDamage )
|
|
EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint )
|
|
EVENT( AI_CanBecomeSolid, idAI::Event_CanBecomeSolid )
|
|
EVENT( AI_BecomeSolid, idAI::Event_BecomeSolid )
|
|
EVENT( EV_BecomeNonSolid, idAI::Event_BecomeNonSolid )
|
|
EVENT( AI_BecomeRagdoll, idAI::Event_BecomeRagdoll )
|
|
EVENT( AI_BecomePassive, idAI::Event_BecomePassive )
|
|
EVENT( AI_BecomeAggressive, idAI::Event_BecomeAggressive )
|
|
EVENT( AI_StopRagdoll, idAI::Event_StopRagdoll )
|
|
EVENT( AI_SetHealth, idAI::Event_SetHealth )
|
|
EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy )
|
|
EVENT( AI_FaceEntity, idAI::Event_FaceEntity )
|
|
EVENT( AI_StopThinking, idAI::Event_StopThinking )
|
|
EVENT( AI_LockEnemyOrigin, idAI::Event_LockEnemyOrigin )
|
|
EVENT( AI_JumpFrame, idAI::Event_JumpFrame )
|
|
EVENT( AI_RealKill, idAI::Event_RealKill )
|
|
EVENT( AI_Kill, idAI::Event_Kill )
|
|
EVENT( AI_RemoveUpdateSpawner, idAI::Event_RemoveUpdateSpawner )
|
|
EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement )
|
|
EVENT( AI_Speak, idAI::Event_Speak )
|
|
EVENT( AI_SpeakRandom, idAI::Event_SpeakRandom )
|
|
EVENT( AI_IsSpeaking, idAI::Event_IsSpeaking )
|
|
EVENT( AI_IsTethered, idAI::Event_IsTethered )
|
|
EVENT( AI_IsWithinTether, idAI::Event_IsWithinTether )
|
|
EVENT( EV_IsMoving, idAI::Event_IsMoving )
|
|
EVENT( AI_TakeDamage, idAI::Event_TakeDamage )
|
|
EVENT( AI_FindEnemy, idAI::Event_FindEnemy )
|
|
EVENT( EV_SetKey, idAI::Event_SetKey )
|
|
// RAVEN BEGIN
|
|
// twhitaker: needed this for difficulty settings
|
|
EVENT( EV_PostSpawn, idAI::Event_PostSpawn )
|
|
// RAVEN END
|
|
END_CLASS
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_PredictEnemyPos
|
|
=====================
|
|
*/
|
|
void idAI::Event_PredictEnemyPos( float time ) {
|
|
predictedPath_t path;
|
|
idEntity* enemyEnt = enemy.ent;
|
|
|
|
// if no enemy set
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnVector( physicsObj.GetOrigin() );
|
|
return;
|
|
}
|
|
|
|
// predict the enemy movement
|
|
idAI::PredictPath( enemyEnt, aas, enemy.lastKnownPosition, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
idThread::ReturnVector( path.endPos );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestAnimMoveTowardEnemy
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestAnimMoveTowardEnemy( const char *animname ) {
|
|
int anim;
|
|
predictedPath_t path;
|
|
idVec3 moveVec;
|
|
float yaw;
|
|
idVec3 delta;
|
|
idEntity *enemyEnt;
|
|
|
|
enemyEnt = enemy.ent;
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
delta = enemyEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin();
|
|
yaw = delta.ToYaw();
|
|
|
|
moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis();
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( DebugFilter(ai_debugMove) ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec );
|
|
gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec );
|
|
}
|
|
|
|
idThread::ReturnInt( path.endEvent == 0 );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestAnimMove
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestAnimMove( const char *animname ) {
|
|
int anim;
|
|
predictedPath_t path;
|
|
idVec3 moveVec;
|
|
int animLen;
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
moveVec = animator.TotalMovementDelta( anim ) * idAngles( 0.0f, move.ideal_yaw, 0.0f ).ToMat3() * physicsObj.GetGravityAxis();
|
|
animLen = animator.AnimLength( anim );
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), moveVec, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( DebugFilter(ai_debugMove) ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, gameLocal.msec );
|
|
gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, gameLocal.msec );
|
|
}
|
|
|
|
idThread::ReturnInt( path.endEvent == 0 );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestMoveToPosition
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestMoveToPosition( const idVec3 &position ) {
|
|
predictedPath_t path;
|
|
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), position - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
if ( DebugFilter(ai_debugMove) ) {
|
|
gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, gameLocal.msec );
|
|
gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, gameLocal.msec );
|
|
if ( path.endEvent ) {
|
|
gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, gameLocal.msec );
|
|
}
|
|
}
|
|
|
|
idThread::ReturnInt( path.endEvent == 0 );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestMeleeAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestMeleeAttack( void ) {
|
|
bool result = TestMelee();
|
|
idThread::ReturnInt( result );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_TestAnimAttack
|
|
=====================
|
|
*/
|
|
void idAI::Event_TestAnimAttack( const char *animname ) {
|
|
int anim;
|
|
predictedPath_t path;
|
|
|
|
anim = GetAnim( ANIMCHANNEL_LEGS, animname );
|
|
if ( !anim ) {
|
|
gameLocal.DWarning( "missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName() );
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
idAI::PredictPath( this, aas, physicsObj.GetOrigin(), animator.TotalMovementDelta( anim ), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
|
|
|
idThread::ReturnInt( path.blockingEntity && ( path.blockingEntity == enemy.ent ) );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LockEnemyOrigin
|
|
=====================
|
|
*/
|
|
void idAI::Event_LockEnemyOrigin ( void ) {
|
|
enemy.fl.lockOrigin = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_StopThinking
|
|
=====================
|
|
*/
|
|
void idAI::Event_StopThinking( void ) {
|
|
BecomeInactive( TH_THINK );
|
|
idThread *thread = idThread::CurrentThread();
|
|
if ( thread ) {
|
|
thread->DoneProcessing();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_JumpFrame
|
|
=====================
|
|
*/
|
|
void idAI::Event_JumpFrame( void ) {
|
|
aifl.jump = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableClip
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableClip( void ) {
|
|
physicsObj.SetClipMask( MASK_MONSTERSOLID );
|
|
Event_EnableGravity ( );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableClip
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableClip( void ) {
|
|
physicsObj.SetClipMask( 0 );
|
|
Event_DisableGravity ( );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableGravity
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableGravity( void ) {
|
|
OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, false );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableGravity
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableGravity( void ) {
|
|
OverrideFlag ( AIFLAGOVERRIDE_NOGRAVITY, true );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableAFPush
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableAFPush( void ) {
|
|
move.fl.allowPushMovables = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableAFPush
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableAFPush( void ) {
|
|
move.fl.allowPushMovables = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableDamage
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableDamage ( void ) {
|
|
OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, true );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableDamage
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableDamage ( void ) {
|
|
OverrideFlag ( AIFLAGOVERRIDE_DAMAGE, false );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idAI::Event_DisablePain
|
|
===============
|
|
*/
|
|
void idAI::Event_DisablePain( void ) {
|
|
OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, true );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idAI::Event_EnablePain
|
|
===============
|
|
*/
|
|
void idAI::Event_EnablePain( void ) {
|
|
OverrideFlag ( AIFLAGOVERRIDE_DISABLEPAIN, false );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idAI::Event_EnableTarget
|
|
===============
|
|
*/
|
|
void idAI::Event_EnableTarget ( void ) {
|
|
fl.notarget = false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idAI::Event_DisableTarget
|
|
===============
|
|
*/
|
|
void idAI::Event_DisableTarget ( void ) {
|
|
fl.notarget = true;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EnableAutoBlink
|
|
=====================
|
|
*/
|
|
void idAI::Event_EnableAutoBlink( void ) {
|
|
fl.allowAutoBlink = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_DisableAutoBlink
|
|
=====================
|
|
*/
|
|
void idAI::Event_DisableAutoBlink( void ) {
|
|
fl.allowAutoBlink = false;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_BecomeAggressive
|
|
=====================
|
|
*/
|
|
void idAI::Event_BecomeAggressive ( void ) {
|
|
combat.fl.ignoreEnemies = false;
|
|
combat.fl.aware = true;
|
|
ForceTacticalUpdate ( );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_BecomePassive
|
|
=====================
|
|
*/
|
|
void idAI::Event_BecomePassive ( int ignoreEnemies ) {
|
|
combat.fl.ignoreEnemies = (ignoreEnemies != 0);
|
|
combat.fl.aware = false;
|
|
SetEnemy ( NULL );
|
|
ForceTacticalUpdate ( );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_LookAt
|
|
=====================
|
|
*/
|
|
void idAI::Event_LookAt ( idEntity* lookAt ) {
|
|
lookTarget = lookAt;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::LookAtEntity
|
|
=====================
|
|
*/
|
|
void idAI::LookAtEntity( idEntity *ent, float duration ) {
|
|
if ( ent == this ) {
|
|
ent = NULL;
|
|
}
|
|
|
|
if ( ( ent != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) {
|
|
focusEntity = ent;
|
|
alignHeadTime = gameLocal.time;
|
|
forceAlignHeadTime = gameLocal.time + SEC2MS( 1 );
|
|
blink_time = 0;
|
|
}
|
|
|
|
focusTime = gameLocal.time + SEC2MS( duration );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ThrowMoveable
|
|
================
|
|
*/
|
|
void idAI::Event_ThrowMoveable( void ) {
|
|
idEntity *ent;
|
|
idEntity *moveable = NULL;
|
|
|
|
for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) {
|
|
// RAVEN BEGIN
|
|
// jnewquist: Use accessor for static class type
|
|
if ( ent->GetBindMaster() == this && ent->IsType( idMoveable::GetClassType() ) ) {
|
|
// RAVEN END
|
|
moveable = ent;
|
|
break;
|
|
}
|
|
}
|
|
if ( moveable ) {
|
|
moveable->Unbind();
|
|
moveable->PostEventMS( &EV_SetOwner, 200, NULL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ThrowAF
|
|
================
|
|
*/
|
|
void idAI::Event_ThrowAF( void ) {
|
|
idEntity *ent;
|
|
idEntity *af = NULL;
|
|
|
|
for ( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) {
|
|
// RAVEN BEGIN
|
|
// jnewquist: Use accessor for static class type
|
|
if ( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::GetClassType() ) ) {
|
|
// RAVEN END
|
|
af = ent;
|
|
break;
|
|
}
|
|
}
|
|
if ( af ) {
|
|
af->Unbind();
|
|
af->PostEventMS( &EV_SetOwner, 200, NULL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetAngles
|
|
================
|
|
*/
|
|
void idAI::Event_SetAngles( idAngles const &ang ) {
|
|
move.current_yaw = ang.yaw;
|
|
viewAxis = idAngles( 0, move.current_yaw, 0 ).ToMat3();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetAngles
|
|
================
|
|
*/
|
|
void idAI::Event_GetAngles( void ) {
|
|
idThread::ReturnVector( idVec3( 0.0f, move.current_yaw, 0.0f ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_RealKill
|
|
================
|
|
*/
|
|
void idAI::Event_RealKill( void ) {
|
|
health = 0;
|
|
|
|
if ( af.IsLoaded() ) {
|
|
// clear impacts
|
|
af.Rest();
|
|
|
|
// physics is turned off by calling af.Rest()
|
|
BecomeActive( TH_PHYSICS );
|
|
}
|
|
|
|
Killed( this, this, 0, vec3_zero, INVALID_JOINT );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_Kill
|
|
================
|
|
*/
|
|
void idAI::Event_Kill( void ) {
|
|
PostEventMS( &AI_RealKill, 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_RemoveUpdateSpawner
|
|
================
|
|
*/
|
|
void idAI::Event_RemoveUpdateSpawner( void ) {
|
|
// Detach from any spawners
|
|
if( GetSpawner() ) {
|
|
GetSpawner()->Detach( this );
|
|
SetSpawner( NULL );
|
|
}
|
|
|
|
PostEventMS( &EV_Remove, 0 );
|
|
}
|
|
/*
|
|
=====================
|
|
idAI::Event_FindActorsInBounds
|
|
=====================
|
|
*/
|
|
void idAI::Event_FindActorsInBounds( const idVec3 &mins, const idVec3 &maxs ) {
|
|
idEntity * ent;
|
|
idEntity * entityList[ MAX_GENTITIES ];
|
|
int numListedEntities;
|
|
int i;
|
|
|
|
// RAVEN BEGIN
|
|
// ddynerman: multiple clip worlds
|
|
numListedEntities = gameLocal.EntitiesTouchingBounds( this, idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES );
|
|
// RAVEN END
|
|
for( i = 0; i < numListedEntities; i++ ) {
|
|
ent = entityList[ i ];
|
|
// RAVEN BEGIN
|
|
// jnewquist: Use accessor for static class type
|
|
if ( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::GetClassType() ) ) {
|
|
// RAVEN END
|
|
idThread::ReturnEntity( ent );
|
|
return;
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( NULL );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_ClosestReachableEnemyOfEntity
|
|
=====================
|
|
*/
|
|
void idAI::Event_ClosestReachableEnemyOfEntity( idEntity *team_mate ) {
|
|
idActor *actor;
|
|
idActor *ent;
|
|
idActor *bestEnt;
|
|
float bestDistSquared;
|
|
float distSquared;
|
|
idVec3 delta;
|
|
int areaNum;
|
|
int enemyAreaNum;
|
|
aasPath_t path;
|
|
|
|
// RAVEN BEGIN
|
|
// jnewquist: Use accessor for static class type
|
|
if ( !team_mate->IsType( idActor::GetClassType() ) ) {
|
|
// RAVEN END
|
|
gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() );
|
|
}
|
|
|
|
actor = static_cast<idActor *>( team_mate );
|
|
|
|
const idVec3 &origin = physicsObj.GetOrigin();
|
|
areaNum = PointReachableAreaNum( origin );
|
|
|
|
bestDistSquared = idMath::INFINITY;
|
|
bestEnt = NULL;
|
|
for( ent = actor->enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) {
|
|
if ( ent->fl.hidden ) {
|
|
continue;
|
|
}
|
|
delta = ent->GetPhysics()->GetOrigin() - origin;
|
|
distSquared = delta.LengthSqr();
|
|
if ( distSquared < bestDistSquared ) {
|
|
const idVec3 &enemyPos = ent->GetPhysics()->GetOrigin();
|
|
enemyAreaNum = PointReachableAreaNum( enemyPos );
|
|
if ( ( areaNum != 0 ) && PathToGoal( path, areaNum, origin, enemyAreaNum, enemyPos ) ) {
|
|
bestEnt = ent;
|
|
bestDistSquared = distSquared;
|
|
}
|
|
}
|
|
}
|
|
|
|
idThread::ReturnEntity( bestEnt );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
idAI::Event_EntityInAttackCone
|
|
=====================
|
|
*/
|
|
void idAI::Event_EntityInAttackCone( idEntity *ent ) {
|
|
float attack_cone;
|
|
idVec3 delta;
|
|
float yaw;
|
|
float relYaw;
|
|
|
|
if ( !ent ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
delta = ent->GetPhysics()->GetOrigin() - GetEyePosition();
|
|
|
|
// get our gravity normal
|
|
const idVec3 &gravityDir = GetPhysics()->GetGravityNormal();
|
|
|
|
// infinite vertical vision, so project it onto our orientation plane
|
|
delta -= gravityDir * ( gravityDir * delta );
|
|
|
|
delta.Normalize();
|
|
yaw = delta.ToYaw();
|
|
|
|
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
|
|
relYaw = idMath::AngleNormalize180( move.ideal_yaw - yaw );
|
|
if ( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) {
|
|
idThread::ReturnInt( true );
|
|
} else {
|
|
idThread::ReturnInt( false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_CanReachPosition
|
|
================
|
|
*/
|
|
void idAI::Event_CanReachPosition( const idVec3 &pos ) {
|
|
aasPath_t path;
|
|
int toAreaNum;
|
|
int areaNum;
|
|
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, physicsObj.GetOrigin(), toAreaNum, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
} else {
|
|
idThread::ReturnInt( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_CanReachEntity
|
|
================
|
|
*/
|
|
void idAI::Event_CanReachEntity( idEntity *ent ) {
|
|
aasPath_t path;
|
|
int toAreaNum;
|
|
int areaNum;
|
|
idVec3 pos;
|
|
|
|
if ( !ent ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
if ( move.moveType != MOVETYPE_FLY ) {
|
|
if ( !ent->GetFloorPos( 64.0f, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
// RAVEN BEGIN
|
|
// jnewquist: Use accessor for static class type
|
|
if ( ent->IsType( idActor::GetClassType() ) && static_cast<idActor *>( ent )->OnLadder() ) {
|
|
// RAVEN END
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
} else {
|
|
pos = ent->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
if ( !toAreaNum ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
areaNum = PointReachableAreaNum( org );
|
|
if ( !toAreaNum || !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
} else {
|
|
idThread::ReturnInt( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_CanReachEnemy
|
|
================
|
|
*/
|
|
void idAI::Event_CanReachEnemy( void ) {
|
|
aasPath_t path;
|
|
int toAreaNum = 0;
|
|
int areaNum;
|
|
idVec3 pos;
|
|
idEntity *enemyEnt;
|
|
|
|
enemyEnt = enemy.ent;
|
|
if ( !enemyEnt ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
if ( move.moveType != MOVETYPE_FLY ) {
|
|
if( enemyEnt->IsType( idActor::GetClassType() ) ){
|
|
idActor *enemyAct = static_cast<idActor *>( enemyEnt );
|
|
if ( enemyAct->OnLadder() ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
enemyAct->GetAASLocation( aas, pos, toAreaNum );
|
|
}
|
|
} else {
|
|
pos = enemyEnt->GetPhysics()->GetOrigin();
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
}
|
|
|
|
if ( !toAreaNum ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
|
|
const idVec3 &org = physicsObj.GetOrigin();
|
|
areaNum = PointReachableAreaNum( org );
|
|
if ( !PathToGoal( path, areaNum, org, toAreaNum, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
} else {
|
|
idThread::ReturnInt( true );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_GetReachableEntityPosition
|
|
================
|
|
*/
|
|
void idAI::Event_GetReachableEntityPosition( idEntity *ent ) {
|
|
int toAreaNum;
|
|
idVec3 pos;
|
|
|
|
if ( move.moveType != MOVETYPE_FLY ) {
|
|
if ( !ent->GetFloorPos( 64.0f, pos ) ) {
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
// RAVEN BEGIN
|
|
// jnewquist: Use accessor for static class type
|
|
if ( ent->IsType( idActor::GetClassType() ) && static_cast<idActor *>( ent )->OnLadder() ) {
|
|
// RAVEN END
|
|
idThread::ReturnInt( false );
|
|
return;
|
|
}
|
|
} else {
|
|
pos = ent->GetPhysics()->GetOrigin();
|
|
}
|
|
|
|
if ( aas ) {
|
|
toAreaNum = PointReachableAreaNum( pos );
|
|
aas->PushPointIntoAreaNum( toAreaNum, pos );
|
|
}
|
|
|
|
idThread::ReturnVector( pos );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedMove
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedMove ( idEntity* destEnt, float minDist, bool endWithIdle ) {
|
|
ScriptedMove ( destEnt, minDist, endWithIdle );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedFace
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedFace ( idEntity* faceEnt, bool endWithIdle ) {
|
|
ScriptedFace ( faceEnt, endWithIdle );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedAnim
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedAnim ( const char* animname, int blendFrames, bool loop, bool endWithIdle ) {
|
|
ScriptedAnim ( animname, blendFrames, loop, endWithIdle );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedAction
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedAction ( idEntity* actionEnt, bool endWithIdle ) {
|
|
ScriptedAction ( actionEnt, endWithIdle );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedPlaybackMove
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedPlaybackMove ( const char* playback, int flags, int numFrames ) {
|
|
ScriptedPlaybackMove ( playback, flags, numFrames );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedPlaybackAim
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedPlaybackAim( const char* playback, int flags, int numFrames ) {
|
|
ScriptedPlaybackAim ( playback, flags, numFrames );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedDone
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedDone ( void ) {
|
|
idThread::ReturnFloat ( !aifl.scripted );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedStop
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedStop ( void ) {
|
|
ScriptedStop ( );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_AllowHiddenMovement
|
|
================
|
|
*/
|
|
void idAI::Event_AllowHiddenMovement( int enable ) {
|
|
move.fl.allowHiddenMove = ( enable != 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetScript
|
|
================
|
|
*/
|
|
void idAI::Event_SetScript ( const char* scriptName, const char* funcName ) {
|
|
SetScript ( scriptName, funcName );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetMoveSpeed
|
|
================
|
|
*/
|
|
void idAI::Event_SetMoveSpeed ( int speed ) {
|
|
switch ( speed ) {
|
|
case AIMOVESPEED_DEFAULT:
|
|
move.fl.noRun = false;
|
|
move.fl.noWalk = false;
|
|
break;
|
|
|
|
case AIMOVESPEED_RUN:
|
|
move.fl.noRun = false;
|
|
move.fl.noWalk = true;
|
|
break;
|
|
|
|
case AIMOVESPEED_WALK:
|
|
move.fl.noRun = true;
|
|
move.fl.noWalk = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetPassivePrefix
|
|
================
|
|
*/
|
|
void idAI::Event_SetPassivePrefix ( const char* prefix ) {
|
|
SetPassivePrefix ( prefix );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_Attack
|
|
================
|
|
*/
|
|
void idAI::Event_Attack ( const char* attackName, const char* jointName ) {
|
|
Attack ( attackName, animator.GetJointHandle ( jointName ), enemy.ent ); // , physicsObj.GetPushedLinearVelocity ( ) );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_AttackMelee
|
|
================
|
|
*/
|
|
void idAI::Event_AttackMelee( const char* meleeName ) {
|
|
const idDict* meleeDict;
|
|
meleeDict = gameLocal.FindEntityDefDict ( spawnArgs.GetString ( va("def_attack_%s", meleeName ) ), false );
|
|
if ( !meleeDict ) {
|
|
gameLocal.Error ( "missing meleeDef '%s' for ai entity '%s'", meleeName, GetName() );
|
|
}
|
|
AttackMelee ( meleeName, meleeDict );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_ScriptedJumpDown
|
|
================
|
|
*/
|
|
void idAI::Event_ScriptedJumpDown( float yaw ) {
|
|
if ( animator.HasAnim( "jumpdown_start" ) )
|
|
{
|
|
aifl.scripted = true;
|
|
move.ideal_yaw = yaw;
|
|
SetState( "State_ScriptedJumpDown" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_FindEnemy
|
|
================
|
|
*/
|
|
void idAI::Event_FindEnemy( float distSqr ) {
|
|
idThread::ReturnEntity ( FindEnemy( false, 1, distSqr ));
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_SetKey
|
|
================
|
|
*/
|
|
void idAI::Event_SetKey( const char *key, const char *value ) {
|
|
spawnArgs.Set( key, value );
|
|
|
|
OnSetKey ( key, value );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idAI::Event_PostSpawn
|
|
================
|
|
*/
|
|
void idAI::Event_PostSpawn( void ) {
|
|
// RAVEN BEGIN
|
|
// twhitaker: difficulty levels
|
|
if ( team == TEAM_MARINE ) {
|
|
//health /= 1.0f + gameLocal.GetDifficultyModifier( );
|
|
|
|
//buddies are a little more healthy on hard & nightmare since the baddies deal so much more damage
|
|
switch ( g_skill.GetInteger() ) {
|
|
case 3:
|
|
health *= 1.4f;
|
|
break;
|
|
case 2:
|
|
health *= 1.2f;
|
|
break;
|
|
case 0:
|
|
health *= 1.2f;
|
|
break;
|
|
case 1:
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
health *= 1.0f + gameLocal.GetDifficultyModifier( );
|
|
}
|
|
// RAVEN END
|
|
}
|