/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "precompiled.h" #include "../Game_local.h" /*********************************************************************** AI Events ***********************************************************************/ const idEventDef AI_FindEnemy( "findEnemy", "d", 'e' ); const idEventDef AI_FindEnemyAI( "findEnemyAI", "d", 'e' ); const idEventDef AI_FindEnemyInCombatNodes( "findEnemyInCombatNodes", NULL, 'e' ); const idEventDef AI_ClosestReachableEnemyOfEntity( "closestReachableEnemyOfEntity", "E", 'e' ); const idEventDef AI_HeardSound( "heardSound", "d", 'e' ); const idEventDef AI_SetEnemy( "setEnemy", "E" ); const idEventDef AI_ClearEnemy( "clearEnemy" ); const idEventDef AI_MuzzleFlash( "muzzleFlash", "s" ); const idEventDef AI_CreateMissile( "createMissile", "s", 'e' ); const idEventDef AI_AttackMissile( "attackMissile", "s", 'e' ); const idEventDef AI_FireMissileAtTarget( "fireMissileAtTarget", "ss", 'e' ); const idEventDef AI_LaunchMissile( "launchMissile", "vv", 'e' ); const idEventDef AI_LaunchHomingMissile( "launchHomingMissile" ); const idEventDef AI_SetHomingMissileGoal( "setHomingMissileGoal" ); const idEventDef AI_LaunchProjectile( "launchProjectile", "s" ); const idEventDef AI_AttackMelee( "attackMelee", "s", 'd' ); const idEventDef AI_DirectDamage( "directDamage", "es" ); const idEventDef AI_RadiusDamageFromJoint( "radiusDamageFromJoint", "ss" ); const idEventDef AI_BeginAttack( "attackBegin", "s" ); const idEventDef AI_EndAttack( "attackEnd" ); const idEventDef AI_MeleeAttackToJoint( "meleeAttackToJoint", "ss", 'd' ); const idEventDef AI_RandomPath( "randomPath", NULL, 'e' ); const idEventDef AI_CanBecomeSolid( "canBecomeSolid", NULL, 'f' ); const idEventDef AI_BecomeSolid( "becomeSolid" ); const idEventDef AI_BecomeRagdoll( "becomeRagdoll", NULL, 'd' ); const idEventDef AI_StopRagdoll( "stopRagdoll" ); const idEventDef AI_SetHealth( "setHealth", "f" ); const idEventDef AI_GetHealth( "getHealth", NULL, 'f' ); const idEventDef AI_AllowDamage( "allowDamage" ); const idEventDef AI_IgnoreDamage( "ignoreDamage" ); const idEventDef AI_GetCurrentYaw( "getCurrentYaw", NULL, 'f' ); const idEventDef AI_TurnTo( "turnTo", "f" ); const idEventDef AI_TurnToPos( "turnToPos", "v" ); const idEventDef AI_TurnToEntity( "turnToEntity", "E" ); const idEventDef AI_MoveStatus( "moveStatus", NULL, 'd' ); const idEventDef AI_StopMove( "stopMove" ); const idEventDef AI_MoveToCover( "moveToCover" ); const idEventDef AI_MoveToEnemy( "moveToEnemy" ); const idEventDef AI_MoveToEnemyHeight( "moveToEnemyHeight" ); const idEventDef AI_MoveOutOfRange( "moveOutOfRange", "ef" ); const idEventDef AI_MoveToAttackPosition( "moveToAttackPosition", "es" ); const idEventDef AI_Wander( "wander" ); const idEventDef AI_MoveToEntity( "moveToEntity", "e" ); const idEventDef AI_MoveToPosition( "moveToPosition", "v" ); const idEventDef AI_SlideTo( "slideTo", "vf" ); const idEventDef AI_FacingIdeal( "facingIdeal", NULL, 'd' ); const idEventDef AI_FaceEnemy( "faceEnemy" ); const idEventDef AI_FaceEntity( "faceEntity", "E" ); const idEventDef AI_GetCombatNode( "getCombatNode", NULL, 'e' ); const idEventDef AI_EnemyInCombatCone( "enemyInCombatCone", "Ed", 'd' ); const idEventDef AI_WaitMove( "waitMove" ); const idEventDef AI_GetJumpVelocity( "getJumpVelocity", "vff", 'v' ); const idEventDef AI_GetTrajectoryToPlayer( "getTrajectoryToPlayer", NULL, 'v' ); const idEventDef AI_EntityInAttackCone( "entityInAttackCone", "E", 'd' ); const idEventDef AI_CanSeeEntity( "canSee", "E", 'd' ); const idEventDef AI_SetTalkTarget( "setTalkTarget", "E" ); const idEventDef AI_GetTalkTarget( "getTalkTarget", NULL, 'e' ); const idEventDef AI_SetTalkState( "setTalkState", "d" ); const idEventDef AI_EnemyRange( "enemyRange", NULL, 'f' ); const idEventDef AI_EnemyRange2D( "enemyRange2D", NULL, 'f' ); const idEventDef AI_GetEnemy( "getEnemy", NULL, 'e' ); const idEventDef AI_GetEnemyPos( "getEnemyPos", NULL, 'v' ); const idEventDef AI_GetEnemyEyePos( "getEnemyEyePos", NULL, 'v' ); const idEventDef AI_PredictEnemyPos( "predictEnemyPos", "f", 'v' ); const idEventDef AI_CanHitEnemy( "canHitEnemy", NULL, 'd' ); const idEventDef AI_CanHitEnemyFromAnim( "canHitEnemyFromAnim", "s", 'd' ); const idEventDef AI_CanHitEnemyFromJoint( "canHitEnemyFromJoint", "s", 'd' ); const idEventDef AI_EnemyPositionValid( "enemyPositionValid", NULL, 'd' ); const idEventDef AI_ChargeAttack( "chargeAttack", "s" ); const idEventDef AI_TestChargeAttack( "testChargeAttack", NULL, 'f' ); const idEventDef AI_TestMoveToPosition( "testMoveToPosition", "v", 'd' ); const idEventDef AI_TestAnimMoveTowardEnemy( "testAnimMoveTowardEnemy", "s", 'd' ); const idEventDef AI_TestAnimMove( "testAnimMove", "s", 'd' ); const idEventDef AI_TestMeleeAttack( "testMeleeAttack", NULL, 'd' ); const idEventDef AI_TestAnimAttack( "testAnimAttack", "s", 'd' ); const idEventDef AI_Burn( "burn" ); const idEventDef AI_ClearBurn( "clearBurn" ); const idEventDef AI_PreBurn( "preBurn" ); const idEventDef AI_SetSmokeVisibility( "setSmokeVisibility", "dd" ); const idEventDef AI_NumSmokeEmitters( "numSmokeEmitters", NULL, 'd' ); const idEventDef AI_WaitAction( "waitAction", "s" ); const idEventDef AI_StopThinking( "stopThinking" ); const idEventDef AI_GetTurnDelta( "getTurnDelta", NULL, 'f' ); const idEventDef AI_GetMoveType( "getMoveType", NULL, 'd' ); const idEventDef AI_SetMoveType( "setMoveType", "d" ); const idEventDef AI_SaveMove( "saveMove" ); const idEventDef AI_RestoreMove( "restoreMove" ); const idEventDef AI_AllowMovement( "allowMovement", "f" ); const idEventDef AI_JumpFrame( "" ); 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_SetFlySpeed( "setFlySpeed", "f" ); const idEventDef AI_SetFlyOffset( "setFlyOffset", "d" ); const idEventDef AI_ClearFlyOffset( "clearFlyOffset" ); const idEventDef AI_GetClosestHiddenTarget( "getClosestHiddenTarget", "s", 'e' ); const idEventDef AI_GetRandomTarget( "getRandomTarget", "s", 'e' ); const idEventDef AI_TravelDistanceToPoint( "travelDistanceToPoint", "v", 'f' ); const idEventDef AI_TravelDistanceToEntity( "travelDistanceToEntity", "e", 'f' ); const idEventDef AI_TravelDistanceBetweenPoints( "travelDistanceBetweenPoints", "vv", 'f' ); const idEventDef AI_TravelDistanceBetweenEntities( "travelDistanceBetweenEntities", "ee", 'f' ); const idEventDef AI_LookAtEntity( "lookAt", "Ef" ); const idEventDef AI_LookAtEnemy( "lookAtEnemy", "f" ); const idEventDef AI_SetJointMod( "setBoneMod", "d" ); const idEventDef AI_ThrowMoveable( "throwMoveable" ); const idEventDef AI_ThrowAF( "throwAF" ); const idEventDef AI_RealKill( "" ); const idEventDef AI_Kill( "kill" ); const idEventDef AI_WakeOnFlashlight( "wakeOnFlashlight", "d" ); const idEventDef AI_LocateEnemy( "locateEnemy" ); const idEventDef AI_KickObstacles( "kickObstacles", "Ef" ); const idEventDef AI_GetObstacle( "getObstacle", NULL, 'e' ); const idEventDef AI_PushPointIntoAAS( "pushPointIntoAAS", "v", 'v' ); const idEventDef AI_GetTurnRate( "getTurnRate", NULL, 'f' ); const idEventDef AI_SetTurnRate( "setTurnRate", "f" ); const idEventDef AI_AnimTurn( "animTurn", "f" ); const idEventDef AI_AllowHiddenMovement( "allowHiddenMovement", "d" ); const idEventDef AI_TriggerParticles( "triggerParticles", "s" ); const idEventDef AI_FindActorsInBounds( "findActorsInBounds", "vv", 'e' ); const idEventDef AI_CanReachPosition( "canReachPosition", "v", 'd' ); const idEventDef AI_CanReachEntity( "canReachEntity", "E", 'd' ); const idEventDef AI_CanReachEnemy( "canReachEnemy", NULL, 'd' ); const idEventDef AI_GetReachableEntityPosition( "getReachableEntityPosition", "e", 'v' ); const idEventDef AI_MoveToPositionDirect( "moveToPositionDirect", "v" ); const idEventDef AI_AvoidObstacles( "avoidObstacles", "d" ); const idEventDef AI_TriggerFX( "triggerFX", "ss" ); const idEventDef AI_StartEmitter( "startEmitter", "sss", 'e' ); const idEventDef AI_GetEmitter( "getEmitter", "s", 'e' ); const idEventDef AI_StopEmitter( "stopEmitter", "s" ); CLASS_DECLARATION( idActor, idAI ) EVENT( EV_Activate, idAI::Event_Activate ) EVENT( EV_Touch, idAI::Event_Touch ) EVENT( AI_FindEnemy, idAI::Event_FindEnemy ) EVENT( AI_FindEnemyAI, idAI::Event_FindEnemyAI ) EVENT( AI_FindEnemyInCombatNodes, idAI::Event_FindEnemyInCombatNodes ) EVENT( AI_ClosestReachableEnemyOfEntity, idAI::Event_ClosestReachableEnemyOfEntity ) EVENT( AI_HeardSound, idAI::Event_HeardSound ) EVENT( AI_SetEnemy, idAI::Event_SetEnemy ) EVENT( AI_ClearEnemy, idAI::Event_ClearEnemy ) EVENT( AI_MuzzleFlash, idAI::Event_MuzzleFlash ) EVENT( AI_CreateMissile, idAI::Event_CreateMissile ) EVENT( AI_AttackMissile, idAI::Event_AttackMissile ) EVENT( AI_FireMissileAtTarget, idAI::Event_FireMissileAtTarget ) EVENT( AI_LaunchMissile, idAI::Event_LaunchMissile ) EVENT( AI_LaunchHomingMissile, idAI::Event_LaunchHomingMissile ) EVENT( AI_SetHomingMissileGoal, idAI::Event_SetHomingMissileGoal ) EVENT( AI_LaunchProjectile, idAI::Event_LaunchProjectile ) EVENT( AI_AttackMelee, idAI::Event_AttackMelee ) EVENT( AI_DirectDamage, idAI::Event_DirectDamage ) EVENT( AI_RadiusDamageFromJoint, idAI::Event_RadiusDamageFromJoint ) EVENT( AI_BeginAttack, idAI::Event_BeginAttack ) EVENT( AI_EndAttack, idAI::Event_EndAttack ) EVENT( AI_MeleeAttackToJoint, idAI::Event_MeleeAttackToJoint ) EVENT( AI_RandomPath, idAI::Event_RandomPath ) 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_StopRagdoll, idAI::Event_StopRagdoll ) EVENT( AI_SetHealth, idAI::Event_SetHealth ) EVENT( AI_GetHealth, idAI::Event_GetHealth ) EVENT( AI_AllowDamage, idAI::Event_AllowDamage ) EVENT( AI_IgnoreDamage, idAI::Event_IgnoreDamage ) EVENT( AI_GetCurrentYaw, idAI::Event_GetCurrentYaw ) EVENT( AI_TurnTo, idAI::Event_TurnTo ) EVENT( AI_TurnToPos, idAI::Event_TurnToPos ) EVENT( AI_TurnToEntity, idAI::Event_TurnToEntity ) EVENT( AI_MoveStatus, idAI::Event_MoveStatus ) EVENT( AI_StopMove, idAI::Event_StopMove ) EVENT( AI_MoveToCover, idAI::Event_MoveToCover ) EVENT( AI_MoveToEnemy, idAI::Event_MoveToEnemy ) EVENT( AI_MoveToEnemyHeight, idAI::Event_MoveToEnemyHeight ) EVENT( AI_MoveOutOfRange, idAI::Event_MoveOutOfRange ) EVENT( AI_MoveToAttackPosition, idAI::Event_MoveToAttackPosition ) EVENT( AI_Wander, idAI::Event_Wander ) EVENT( AI_MoveToEntity, idAI::Event_MoveToEntity ) EVENT( AI_MoveToPosition, idAI::Event_MoveToPosition ) EVENT( AI_SlideTo, idAI::Event_SlideTo ) EVENT( AI_FacingIdeal, idAI::Event_FacingIdeal ) EVENT( AI_FaceEnemy, idAI::Event_FaceEnemy ) EVENT( AI_FaceEntity, idAI::Event_FaceEntity ) EVENT( AI_WaitAction, idAI::Event_WaitAction ) EVENT( AI_GetCombatNode, idAI::Event_GetCombatNode ) EVENT( AI_EnemyInCombatCone, idAI::Event_EnemyInCombatCone ) EVENT( AI_WaitMove, idAI::Event_WaitMove ) EVENT( AI_GetJumpVelocity, idAI::Event_GetJumpVelocity ) EVENT( AI_GetTrajectoryToPlayer, idAI::Event_GetTrajectoryToPlayer ) EVENT( AI_EntityInAttackCone, idAI::Event_EntityInAttackCone ) EVENT( AI_CanSeeEntity, idAI::Event_CanSeeEntity ) EVENT( AI_SetTalkTarget, idAI::Event_SetTalkTarget ) EVENT( AI_GetTalkTarget, idAI::Event_GetTalkTarget ) EVENT( AI_SetTalkState, idAI::Event_SetTalkState ) EVENT( AI_EnemyRange, idAI::Event_EnemyRange ) EVENT( AI_EnemyRange2D, idAI::Event_EnemyRange2D ) EVENT( AI_GetEnemy, idAI::Event_GetEnemy ) EVENT( AI_GetEnemyPos, idAI::Event_GetEnemyPos ) EVENT( AI_GetEnemyEyePos, idAI::Event_GetEnemyEyePos ) EVENT( AI_PredictEnemyPos, idAI::Event_PredictEnemyPos ) EVENT( AI_CanHitEnemy, idAI::Event_CanHitEnemy ) EVENT( AI_CanHitEnemyFromAnim, idAI::Event_CanHitEnemyFromAnim ) EVENT( AI_CanHitEnemyFromJoint, idAI::Event_CanHitEnemyFromJoint ) EVENT( AI_EnemyPositionValid, idAI::Event_EnemyPositionValid ) EVENT( AI_ChargeAttack, idAI::Event_ChargeAttack ) EVENT( AI_TestChargeAttack, idAI::Event_TestChargeAttack ) EVENT( AI_TestAnimMoveTowardEnemy, idAI::Event_TestAnimMoveTowardEnemy ) EVENT( AI_TestAnimMove, idAI::Event_TestAnimMove ) EVENT( AI_TestMoveToPosition, idAI::Event_TestMoveToPosition ) EVENT( AI_TestMeleeAttack, idAI::Event_TestMeleeAttack ) EVENT( AI_TestAnimAttack, idAI::Event_TestAnimAttack ) EVENT( AI_Burn, idAI::Event_Burn ) EVENT( AI_PreBurn, idAI::Event_PreBurn ) EVENT( AI_SetSmokeVisibility, idAI::Event_SetSmokeVisibility ) EVENT( AI_NumSmokeEmitters, idAI::Event_NumSmokeEmitters ) EVENT( AI_ClearBurn, idAI::Event_ClearBurn ) EVENT( AI_StopThinking, idAI::Event_StopThinking ) EVENT( AI_GetTurnDelta, idAI::Event_GetTurnDelta ) EVENT( AI_GetMoveType, idAI::Event_GetMoveType ) EVENT( AI_SetMoveType, idAI::Event_SetMoveType ) EVENT( AI_SaveMove, idAI::Event_SaveMove ) EVENT( AI_RestoreMove, idAI::Event_RestoreMove ) EVENT( AI_AllowMovement, idAI::Event_AllowMovement ) EVENT( AI_JumpFrame, idAI::Event_JumpFrame ) 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_SetFlySpeed, idAI::Event_SetFlySpeed ) EVENT( AI_SetFlyOffset, idAI::Event_SetFlyOffset ) EVENT( AI_ClearFlyOffset, idAI::Event_ClearFlyOffset ) EVENT( AI_GetClosestHiddenTarget, idAI::Event_GetClosestHiddenTarget ) EVENT( AI_GetRandomTarget, idAI::Event_GetRandomTarget ) EVENT( AI_TravelDistanceToPoint, idAI::Event_TravelDistanceToPoint ) EVENT( AI_TravelDistanceToEntity, idAI::Event_TravelDistanceToEntity ) EVENT( AI_TravelDistanceBetweenPoints, idAI::Event_TravelDistanceBetweenPoints ) EVENT( AI_TravelDistanceBetweenEntities, idAI::Event_TravelDistanceBetweenEntities ) EVENT( AI_LookAtEntity, idAI::Event_LookAtEntity ) EVENT( AI_LookAtEnemy, idAI::Event_LookAtEnemy ) EVENT( AI_SetJointMod, idAI::Event_SetJointMod ) EVENT( AI_ThrowMoveable, idAI::Event_ThrowMoveable ) EVENT( AI_ThrowAF, idAI::Event_ThrowAF ) EVENT( EV_GetAngles, idAI::Event_GetAngles ) EVENT( EV_SetAngles, idAI::Event_SetAngles ) EVENT( AI_RealKill, idAI::Event_RealKill ) EVENT( AI_Kill, idAI::Event_Kill ) EVENT( AI_WakeOnFlashlight, idAI::Event_WakeOnFlashlight ) EVENT( AI_LocateEnemy, idAI::Event_LocateEnemy ) EVENT( AI_KickObstacles, idAI::Event_KickObstacles ) EVENT( AI_GetObstacle, idAI::Event_GetObstacle ) EVENT( AI_PushPointIntoAAS, idAI::Event_PushPointIntoAAS ) EVENT( AI_GetTurnRate, idAI::Event_GetTurnRate ) EVENT( AI_SetTurnRate, idAI::Event_SetTurnRate ) EVENT( AI_AnimTurn, idAI::Event_AnimTurn ) EVENT( AI_AllowHiddenMovement, idAI::Event_AllowHiddenMovement ) EVENT( AI_TriggerParticles, idAI::Event_TriggerParticles ) EVENT( AI_FindActorsInBounds, idAI::Event_FindActorsInBounds ) EVENT( AI_CanReachPosition, idAI::Event_CanReachPosition ) EVENT( AI_CanReachEntity, idAI::Event_CanReachEntity ) EVENT( AI_CanReachEnemy, idAI::Event_CanReachEnemy ) EVENT( AI_GetReachableEntityPosition, idAI::Event_GetReachableEntityPosition ) EVENT( AI_MoveToPositionDirect, idAI::Event_MoveToPositionDirect ) EVENT( AI_AvoidObstacles, idAI::Event_AvoidObstacles ) EVENT( AI_TriggerFX, idAI::Event_TriggerFX ) EVENT( AI_StartEmitter, idAI::Event_StartEmitter ) EVENT( AI_GetEmitter, idAI::Event_GetEmitter ) EVENT( AI_StopEmitter, idAI::Event_StopEmitter ) END_CLASS /* ===================== idAI::Event_Activate ===================== */ void idAI::Event_Activate( idEntity* activator ) { Activate( activator ); } /* ===================== idAI::Event_Touch ===================== */ void idAI::Event_Touch( idEntity* other, trace_t* trace ) { if( !enemy.GetEntity() && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) { Activate( other ); } AI_PUSHED = true; } /* ===================== idAI::Event_FindEnemy ===================== */ void idAI::Event_FindEnemy( int useFOV ) { int i; idEntity* ent; idActor* actor; if( gameLocal.InPlayerPVS( this ) ) { for( i = 0; i < gameLocal.numClients ; i++ ) { ent = gameLocal.entities[ i ]; if( !ent || !ent->IsType( idActor::Type ) ) { continue; } actor = static_cast( ent ); if( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { continue; } if( CanSee( actor, useFOV != 0 ) ) { idThread::ReturnEntity( actor ); return; } } } idThread::ReturnEntity( NULL ); } /* ===================== idAI::Event_FindEnemyAI ===================== */ void idAI::Event_FindEnemyAI( int useFOV ) { idEntity* ent; idActor* actor; idActor* bestEnemy; float bestDist; float dist; idVec3 delta; pvsHandle_t pvs; pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); bestDist = idMath::INFINITY; bestEnemy = NULL; for( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( idActor::Type ) ) { continue; } actor = static_cast( ent ); if( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { continue; } if( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { continue; } delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); dist = delta.LengthSqr(); if( ( dist < bestDist ) && CanSee( actor, useFOV != 0 ) ) { bestDist = dist; bestEnemy = actor; } } gameLocal.pvs.FreeCurrentPVS( pvs ); idThread::ReturnEntity( bestEnemy ); } /* ===================== idAI::Event_FindEnemyInCombatNodes ===================== */ void idAI::Event_FindEnemyInCombatNodes() { int i, j; idCombatNode* node; idEntity* ent; idEntity* targetEnt; idActor* actor; if( !gameLocal.InPlayerPVS( this ) ) { // don't locate the player when we're not in his PVS idThread::ReturnEntity( NULL ); return; } for( i = 0; i < gameLocal.numClients ; i++ ) { ent = gameLocal.entities[ i ]; if( !ent || !ent->IsType( idActor::Type ) ) { continue; } actor = static_cast( ent ); if( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { continue; } for( j = 0; j < targets.Num(); j++ ) { targetEnt = targets[ j ].GetEntity(); if( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { continue; } node = static_cast( targetEnt ); if( !node->IsDisabled() && node->EntityInView( actor, actor->GetPhysics()->GetOrigin() ) ) { idThread::ReturnEntity( actor ); 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; if( !team_mate->IsType( idActor::Type ) ) { gameLocal.Error( "Entity '%s' is not an AI character or player", team_mate->GetName() ); } actor = static_cast( 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_HeardSound ===================== */ void idAI::Event_HeardSound( int ignore_team ) { // check if we heard any sounds in the last frame idActor* actor = gameLocal.GetAlertEntity(); if( actor != NULL && ( !ignore_team || ( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) && gameLocal.InPlayerPVS( this ) ) { idVec3 pos = actor->GetPhysics()->GetOrigin(); idVec3 org = physicsObj.GetOrigin(); float dist = ( pos - org ).LengthSqr(); if( dist < Square( AI_HEARING_RANGE ) ) { idThread::ReturnEntity( actor ); return; } } idThread::ReturnEntity( NULL ); } /* ===================== idAI::Event_SetEnemy ===================== */ void idAI::Event_SetEnemy( idEntity* ent ) { if( !ent ) { ClearEnemy(); } else if( !ent->IsType( idActor::Type ) ) { gameLocal.Error( "'%s' is not an idActor (player or ai controlled character)", ent->name.c_str() ); } else { SetEnemy( static_cast( ent ) ); } } /* ===================== idAI::Event_ClearEnemy ===================== */ void idAI::Event_ClearEnemy() { ClearEnemy(); } /* ===================== idAI::Event_MuzzleFlash ===================== */ void idAI::Event_MuzzleFlash( const char* jointname ) { idVec3 muzzle; idMat3 axis; GetMuzzle( jointname, muzzle, axis ); TriggerWeaponEffects( muzzle ); } /* ===================== idAI::Event_CreateMissile ===================== */ void idAI::Event_CreateMissile( const char* jointname ) { idVec3 muzzle; idMat3 axis; if( !projectileDef ) { gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); return idThread::ReturnEntity( NULL ); } GetMuzzle( jointname, muzzle, axis ); CreateProjectile( muzzle, viewAxis[ 0 ] * physicsObj.GetGravityAxis() ); if( projectile.GetEntity() ) { if( !jointname || !jointname[ 0 ] ) { projectile.GetEntity()->Bind( this, true ); } else { projectile.GetEntity()->BindToJoint( this, jointname, true ); } } idThread::ReturnEntity( projectile.GetEntity() ); } /* ===================== idAI::Event_AttackMissile ===================== */ void idAI::Event_AttackMissile( const char* jointname ) { idProjectile* proj; proj = LaunchProjectile( jointname, enemy.GetEntity(), true ); idThread::ReturnEntity( proj ); } /* ===================== idAI::Event_FireMissileAtTarget ===================== */ void idAI::Event_FireMissileAtTarget( const char* jointname, const char* targetname ) { idEntity* aent; idProjectile* proj; aent = gameLocal.FindEntity( targetname ); if( !aent ) { gameLocal.Warning( "Entity '%s' not found for 'fireMissileAtTarget'", targetname ); } proj = LaunchProjectile( jointname, aent, false ); idThread::ReturnEntity( proj ); } /* ===================== idAI::Event_LaunchMissile ===================== */ void idAI::Event_LaunchMissile( const idVec3& org, const idAngles& ang ) { idVec3 start; trace_t tr; idBounds projBounds; const idClipModel* projClip; idMat3 axis; float distance; if( !projectileDef ) { gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); idThread::ReturnEntity( NULL ); return; } axis = ang.ToMat3(); if( !projectile.GetEntity() ) { CreateProjectile( org, axis[ 0 ] ); } // make sure the projectile starts inside the monster bounding box const idBounds& ownerBounds = physicsObj.GetAbsBounds(); projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); // check if the owner bounds is bigger than the projectile bounds if( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if( ( ownerBounds - projBounds ).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); // launch the projectile idThread::ReturnEntity( projectile.GetEntity() ); projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); projectile = NULL; TriggerWeaponEffects( tr.endpos ); lastAttackTime = gameLocal.time; } /* ===================== idAI::Event_LaunchProjectile ===================== */ void idAI::Event_LaunchProjectile( const char* entityDefName ) { idVec3 muzzle, start, dir; const idDict* projDef; idMat3 axis; const idClipModel* projClip; idBounds projBounds; trace_t tr; idEntity* ent; const char* clsname; float distance; idProjectile* proj = NULL; projDef = gameLocal.FindEntityDefDict( entityDefName ); gameLocal.SpawnEntityDef( *projDef, &ent, false ); if( ent == NULL ) { clsname = projectileDef->GetString( "classname" ); gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); return; } if( !ent->IsType( idProjectile::Type ) ) { clsname = ent->GetClassname(); gameLocal.Error( "'%s' is not an idProjectile", clsname ); } proj = ( idProjectile* )ent; GetMuzzle( "pistol", muzzle, axis ); proj->Create( this, muzzle, axis[0] ); // make sure the projectile starts inside the monster bounding box const idBounds& ownerBounds = physicsObj.GetAbsBounds(); projClip = proj->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); if( ( ownerBounds - projBounds ).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) { start = muzzle + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, muzzle, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); muzzle = tr.endpos; GetAimDir( muzzle, enemy.GetEntity(), this, dir ); proj->Launch( muzzle, dir, vec3_origin ); TriggerWeaponEffects( muzzle ); } /* ===================== idAI::Event_AttackMelee ===================== */ void idAI::Event_AttackMelee( const char* meleeDefName ) { bool hit; hit = AttackMelee( meleeDefName ); idThread::ReturnInt( hit ); } /* ===================== idAI::Event_DirectDamage ===================== */ void idAI::Event_DirectDamage( idEntity* damageTarget, const char* damageDefName ) { DirectDamage( damageDefName, damageTarget ); } /* ===================== idAI::Event_RadiusDamageFromJoint ===================== */ void idAI::Event_RadiusDamageFromJoint( const char* jointname, const char* damageDefName ) { jointHandle_t joint; idVec3 org; idMat3 axis; if( !jointname || !jointname[ 0 ] ) { org = physicsObj.GetOrigin(); } else { joint = animator.GetJointHandle( jointname ); if( joint == INVALID_JOINT ) { gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); } GetJointWorldTransform( joint, gameLocal.time, org, axis ); } gameLocal.RadiusDamage( org, this, this, this, this, damageDefName ); } /* ===================== idAI::Event_RandomPath ===================== */ void idAI::Event_RandomPath() { idPathCorner* path; path = idPathCorner::RandomPath( this, NULL ); idThread::ReturnEntity( path ); } /* ===================== idAI::Event_BeginAttack ===================== */ void idAI::Event_BeginAttack( const char* name ) { BeginAttack( name ); } /* ===================== idAI::Event_EndAttack ===================== */ void idAI::Event_EndAttack() { EndAttack(); } /* ===================== idAI::Event_MeleeAttackToJoint ===================== */ void idAI::Event_MeleeAttackToJoint( const char* jointname, const char* meleeDefName ) { jointHandle_t joint; idVec3 start; idVec3 end; idMat3 axis; trace_t trace; idEntity* hitEnt; joint = animator.GetJointHandle( jointname ); if( joint == INVALID_JOINT ) { gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); } animator.GetJointTransform( joint, gameLocal.time, end, axis ); end = physicsObj.GetOrigin() + ( end + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); start = GetEyePosition(); if( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorYellow, start, end, 1 ); } gameLocal.clip.TranslationEntities( trace, start, end, NULL, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); if( trace.fraction < 1.0f ) { hitEnt = gameLocal.GetTraceEntity( trace ); if( hitEnt != NULL && hitEnt->IsType( idActor::Type ) ) { DirectDamage( meleeDefName, hitEnt ); idThread::ReturnInt( true ); return; } } idThread::ReturnInt( false ); } /* ===================== idAI::Event_CanBecomeSolid ===================== */ void idAI::Event_CanBecomeSolid() { int i; int num; bool returnValue = true; idEntity* hit; idClipModel* cm; idClipModel* clipModels[ MAX_GENTITIES ]; num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { cm = clipModels[ i ]; // don't check render entities if( cm->IsRenderModel() ) { continue; } hit = cm->GetEntity(); if( ( hit == this ) || !hit->fl.takedamage ) { continue; } // DG: add parenthesis to make precedence obvious and to appease compiler if( ( spawnClearMoveables && hit->IsType( idMoveable::Type ) ) || hit->IsType( idBarrel::Type ) || hit->IsType( idExplodingBarrel::Type ) ) { idVec3 push; push = hit->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); push.z = 30.f; push.NormalizeFast(); if( ( idMath::Fabs( push.x ) < 0.15f ) && ( idMath::Fabs( push.y ) < 0.15f ) ) { push.x = 10.f; push.y = 10.f; push.z = 15.f; push.NormalizeFast(); } push *= 300.f; hit->GetPhysics()->SetLinearVelocity( push ); } if( physicsObj.ClipContents( cm ) ) { returnValue = false; } } idThread::ReturnFloat( returnValue ); } /* ===================== idAI::Event_BecomeSolid ===================== */ void idAI::Event_BecomeSolid() { physicsObj.EnableClip(); if( spawnArgs.GetBool( "big_monster" ) ) { physicsObj.SetContents( 0 ); } else if( use_combat_bbox ) { physicsObj.SetContents( CONTENTS_BODY | CONTENTS_SOLID ); } else { physicsObj.SetContents( CONTENTS_BODY ); } physicsObj.GetClipModel()->Link( gameLocal.clip ); fl.takedamage = !spawnArgs.GetBool( "noDamage" ); } /* ===================== idAI::Event_BecomeNonSolid ===================== */ void idAI::Event_BecomeNonSolid() { fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.GetClipModel()->Unlink(); } /* ===================== idAI::Event_BecomeRagdoll ===================== */ void idAI::Event_BecomeRagdoll() { bool result; result = StartRagdoll(); idThread::ReturnInt( result ); } /* ===================== idAI::Event_StopRagdoll ===================== */ void idAI::Event_StopRagdoll() { StopRagdoll(); // set back the monster physics SetPhysics( &physicsObj ); } /* ===================== idAI::Event_SetHealth ===================== */ void idAI::Event_SetHealth( float newHealth ) { health = newHealth; fl.takedamage = true; if( health > 0 ) { AI_DEAD = false; } else { AI_DEAD = true; } } /* ===================== idAI::Event_GetHealth ===================== */ void idAI::Event_GetHealth() { idThread::ReturnFloat( health ); } /* ===================== idAI::Event_AllowDamage ===================== */ void idAI::Event_AllowDamage() { fl.takedamage = true; } /* ===================== idAI::Event_IgnoreDamage ===================== */ void idAI::Event_IgnoreDamage() { fl.takedamage = false; } /* ===================== idAI::Event_GetCurrentYaw ===================== */ void idAI::Event_GetCurrentYaw() { idThread::ReturnFloat( current_yaw ); } /* ===================== idAI::Event_TurnTo ===================== */ void idAI::Event_TurnTo( float angle ) { TurnToward( angle ); } /* ===================== idAI::Event_TurnToPos ===================== */ void idAI::Event_TurnToPos( const idVec3& pos ) { TurnToward( pos ); } /* ===================== idAI::Event_TurnToEntity ===================== */ void idAI::Event_TurnToEntity( idEntity* ent ) { if( ent ) { TurnToward( ent->GetPhysics()->GetOrigin() ); } } /* ===================== idAI::Event_MoveStatus ===================== */ void idAI::Event_MoveStatus() { idThread::ReturnInt( move.moveStatus ); } /* ===================== idAI::Event_StopMove ===================== */ void idAI::Event_StopMove() { StopMove( MOVE_STATUS_DONE ); } /* ===================== idAI::Event_MoveToCover ===================== */ void idAI::Event_MoveToCover() { idActor* enemyEnt = enemy.GetEntity(); StopMove( MOVE_STATUS_DEST_NOT_FOUND ); if( !enemyEnt || !MoveToCover( enemyEnt, lastVisibleEnemyPos ) ) { return; } } /* ===================== idAI::Event_MoveToEnemy ===================== */ void idAI::Event_MoveToEnemy() { StopMove( MOVE_STATUS_DEST_NOT_FOUND ); if( !enemy.GetEntity() || !MoveToEnemy() ) { return; } } /* ===================== idAI::Event_MoveToEnemyHeight ===================== */ void idAI::Event_MoveToEnemyHeight() { StopMove( MOVE_STATUS_DEST_NOT_FOUND ); MoveToEnemyHeight(); } /* ===================== idAI::Event_MoveOutOfRange ===================== */ void idAI::Event_MoveOutOfRange( idEntity* entity, float range ) { StopMove( MOVE_STATUS_DEST_NOT_FOUND ); MoveOutOfRange( entity, range ); } /* ===================== idAI::Event_MoveToAttackPosition ===================== */ void idAI::Event_MoveToAttackPosition( idEntity* entity, const char* attack_anim ) { int anim; StopMove( MOVE_STATUS_DEST_NOT_FOUND ); anim = GetAnim( ANIMCHANNEL_LEGS, attack_anim ); if( !anim ) { gameLocal.Error( "Unknown anim '%s'", attack_anim ); } MoveToAttackPosition( entity, anim ); } /* ===================== idAI::Event_MoveToEntity ===================== */ void idAI::Event_MoveToEntity( idEntity* ent ) { StopMove( MOVE_STATUS_DEST_NOT_FOUND ); if( ent ) { MoveToEntity( ent ); } } /* ===================== idAI::Event_MoveToPosition ===================== */ void idAI::Event_MoveToPosition( const idVec3& pos ) { StopMove( MOVE_STATUS_DONE ); MoveToPosition( pos ); } /* ===================== idAI::Event_SlideTo ===================== */ void idAI::Event_SlideTo( const idVec3& pos, float time ) { SlideToPosition( pos, time ); } /* ===================== idAI::Event_Wander ===================== */ void idAI::Event_Wander() { WanderAround(); } /* ===================== idAI::Event_FacingIdeal ===================== */ void idAI::Event_FacingIdeal() { bool facing = FacingIdeal(); idThread::ReturnInt( facing ); } /* ===================== idAI::Event_FaceEnemy ===================== */ void idAI::Event_FaceEnemy() { FaceEnemy(); } /* ===================== idAI::Event_FaceEntity ===================== */ void idAI::Event_FaceEntity( idEntity* ent ) { FaceEntity( ent ); } /* ===================== idAI::Event_WaitAction ===================== */ void idAI::Event_WaitAction( const char* waitForState ) { if( idThread::BeginMultiFrameEvent( this, &AI_WaitAction ) ) { SetWaitState( waitForState ); } if( !WaitState() ) { idThread::EndMultiFrameEvent( this, &AI_WaitAction ); } } /* ===================== idAI::Event_GetCombatNode ===================== */ void idAI::Event_GetCombatNode() { int i; float dist; idEntity* targetEnt; idCombatNode* node; float bestDist; idCombatNode* bestNode; idActor* enemyEnt = enemy.GetEntity(); if( !targets.Num() ) { // no combat nodes idThread::ReturnEntity( NULL ); return; } if( !enemyEnt || !EnemyPositionValid() ) { // don't return a combat node if we don't have an enemy or // if we can see he's not in the last place we saw him if( team == 0 ) { // find the closest attack node to the player bestNode = NULL; const idVec3& myPos = physicsObj.GetOrigin(); const idVec3& playerPos = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); bestDist = ( myPos - playerPos ).LengthSqr(); for( i = 0; i < targets.Num(); i++ ) { targetEnt = targets[ i ].GetEntity(); if( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { continue; } node = static_cast( targetEnt ); if( !node->IsDisabled() ) { idVec3 org = node->GetPhysics()->GetOrigin(); dist = ( playerPos - org ).LengthSqr(); if( dist < bestDist ) { bestNode = node; bestDist = dist; } } } idThread::ReturnEntity( bestNode ); return; } idThread::ReturnEntity( NULL ); return; } // find the closest attack node that can see our enemy and is closer than our enemy bestNode = NULL; const idVec3& myPos = physicsObj.GetOrigin(); bestDist = ( myPos - lastVisibleEnemyPos ).LengthSqr(); for( i = 0; i < targets.Num(); i++ ) { targetEnt = targets[ i ].GetEntity(); if( !targetEnt || !targetEnt->IsType( idCombatNode::Type ) ) { continue; } node = static_cast( targetEnt ); if( !node->IsDisabled() && node->EntityInView( enemyEnt, lastVisibleEnemyPos ) ) { idVec3 org = node->GetPhysics()->GetOrigin(); dist = ( myPos - org ).LengthSqr(); if( dist < bestDist ) { bestNode = node; bestDist = dist; } } } idThread::ReturnEntity( bestNode ); } /* ===================== idAI::Event_EnemyInCombatCone ===================== */ void idAI::Event_EnemyInCombatCone( idEntity* ent, int use_current_enemy_location ) { idCombatNode* node; bool result; idActor* enemyEnt = enemy.GetEntity(); if( !targets.Num() ) { // no combat nodes idThread::ReturnInt( false ); return; } if( !enemyEnt ) { // have to have an enemy idThread::ReturnInt( false ); return; } if( !ent || !ent->IsType( idCombatNode::Type ) ) { // not a combat node idThread::ReturnInt( false ); return; } //Allow the level designers define attack nodes that the enemy should never leave. //This is different that the turrent type combat nodes because they can play an animation if( ent->spawnArgs.GetBool( "neverLeave", "0" ) ) { idThread::ReturnInt( true ); return; } node = static_cast( ent ); if( use_current_enemy_location ) { const idVec3& pos = enemyEnt->GetPhysics()->GetOrigin(); result = node->EntityInView( enemyEnt, pos ); } else { result = node->EntityInView( enemyEnt, lastVisibleEnemyPos ); } idThread::ReturnInt( result ); } /* ===================== idAI::Event_WaitMove ===================== */ void idAI::Event_WaitMove() { idThread::BeginMultiFrameEvent( this, &AI_WaitMove ); if( MoveDone() ) { idThread::EndMultiFrameEvent( this, &AI_WaitMove ); } } /* ===================== idAI::Event_GetJumpVelocity ===================== */ void idAI::Event_GetJumpVelocity( const idVec3& pos, float speed, float max_height ) { idVec3 start; idVec3 end; idVec3 dir; float dist; bool result; idEntity* enemyEnt = enemy.GetEntity(); if( !enemyEnt ) { idThread::ReturnVector( vec3_zero ); return; } start = physicsObj.GetOrigin(); end = pos; dir = end - start; dist = dir.Normalize(); if( dist > 16.0f ) { dist -= 16.0f; end -= dir * 16.0f; } result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, max_height, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir ); if( result ) { idThread::ReturnVector( dir * speed ); } else { idThread::ReturnVector( vec3_zero ); } } /* ===================== 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( ideal_yaw - yaw ); if( idMath::Fabs( relYaw ) < ( attack_cone * 0.5f ) ) { idThread::ReturnInt( true ); } else { idThread::ReturnInt( false ); } } /* ===================== idAI::Event_CanSeeEntity ===================== */ void idAI::Event_CanSeeEntity( idEntity* ent ) { if( !ent ) { idThread::ReturnInt( false ); return; } bool cansee = CanSee( ent, false ); idThread::ReturnInt( cansee ); } /* ===================== idAI::Event_SetTalkTarget ===================== */ void idAI::Event_SetTalkTarget( idEntity* target ) { if( target && !target->IsType( idActor::Type ) ) { gameLocal.Error( "Cannot set talk target to '%s'. Not a character or player.", target->GetName() ); } talkTarget = static_cast( target ); if( target ) { AI_TALK = true; } else { AI_TALK = false; } } /* ===================== idAI::Event_GetTalkTarget ===================== */ void idAI::Event_GetTalkTarget() { idThread::ReturnEntity( talkTarget.GetEntity() ); } /* ================ idAI::Event_SetTalkState ================ */ void idAI::Event_SetTalkState( int state ) { if( ( state < 0 ) || ( state >= NUM_TALK_STATES ) ) { gameLocal.Error( "Invalid talk state (%d)", state ); } talk_state = static_cast( state ); } /* ===================== idAI::Event_EnemyRange ===================== */ void idAI::Event_EnemyRange() { float dist; idActor* enemyEnt = enemy.GetEntity(); if( enemyEnt ) { dist = ( enemyEnt->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); } else { // Just some really high number dist = idMath::INFINITY; } idThread::ReturnFloat( dist ); } /* ===================== idAI::Event_EnemyRange2D ===================== */ void idAI::Event_EnemyRange2D() { float dist; idActor* enemyEnt = enemy.GetEntity(); if( enemyEnt ) { dist = ( enemyEnt->GetPhysics()->GetOrigin().ToVec2() - GetPhysics()->GetOrigin().ToVec2() ).Length(); } else { // Just some really high number dist = idMath::INFINITY; } idThread::ReturnFloat( dist ); } /* ===================== idAI::Event_GetEnemy ===================== */ void idAI::Event_GetEnemy() { idThread::ReturnEntity( enemy.GetEntity() ); } /* ===================== idAI::Event_GetEnemyPos ===================== */ void idAI::Event_GetEnemyPos() { idThread::ReturnVector( lastVisibleEnemyPos ); } /* ===================== idAI::Event_GetEnemyEyePos ===================== */ void idAI::Event_GetEnemyEyePos() { idThread::ReturnVector( lastVisibleEnemyPos + lastVisibleEnemyEyeOffset ); } /* ===================== idAI::Event_PredictEnemyPos ===================== */ void idAI::Event_PredictEnemyPos( float time ) { predictedPath_t path; idActor* enemyEnt = enemy.GetEntity(); // if no enemy set if( !enemyEnt ) { idThread::ReturnVector( physicsObj.GetOrigin() ); return; } // predict the enemy movement idAI::PredictPath( enemyEnt, aas, lastVisibleEnemyPos, 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_CanHitEnemy ===================== */ void idAI::Event_CanHitEnemy() { trace_t tr; idEntity* hit; idActor* enemyEnt = enemy.GetEntity(); if( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } // don't check twice per frame if( gameLocal.time == lastHitCheckTime ) { idThread::ReturnInt( lastHitCheckResult ); return; } lastHitCheckTime = gameLocal.time; idVec3 toPos = enemyEnt->GetEyePosition(); idVec3 eye = GetEyePosition(); idVec3 dir; // expand the ray out as far as possible so we can detect anything behind the enemy dir = toPos - eye; dir.Normalize(); toPos = eye + dir * MAX_WORLD_SIZE; gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this ); hit = gameLocal.GetTraceEntity( tr ); if( tr.fraction >= 1.0f || ( hit == enemyEnt ) ) { lastHitCheckResult = true; } else if( ( tr.fraction < 1.0f ) && ( hit->IsType( idAI::Type ) ) && ( static_cast( hit )->team != team ) ) { lastHitCheckResult = true; } else { lastHitCheckResult = false; } idThread::ReturnInt( lastHitCheckResult ); } /* ===================== idAI::Event_CanHitEnemyFromAnim ===================== */ void idAI::Event_CanHitEnemyFromAnim( const char* animname ) { int anim; idVec3 dir; idVec3 local_dir; idVec3 fromPos; idMat3 axis; idVec3 start; trace_t tr; float distance; idActor* enemyEnt = enemy.GetEntity(); if( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } anim = GetAnim( ANIMCHANNEL_LEGS, animname ); if( !anim ) { idThread::ReturnInt( false ); return; } // just do a ray test if close enough if( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { Event_CanHitEnemy(); return; } // calculate the world transform of the launch position const idVec3& org = physicsObj.GetOrigin(); dir = lastVisibleEnemyPos - org; physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); local_dir.z = 0.0f; local_dir.ToVec2().Normalize(); axis = local_dir.ToMat3(); fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis; if( projectileClipModel == NULL ) { CreateProjectileClipModel(); } // check if the owner bounds is bigger than the projectile bounds const idBounds& ownerBounds = physicsObj.GetAbsBounds(); const idBounds& projBounds = projectileClipModel->GetBounds(); if( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if( ( ownerBounds - projBounds ).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, fromPos, projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this ); fromPos = tr.endpos; if( GetAimDir( fromPos, enemy.GetEntity(), this, dir ) ) { idThread::ReturnInt( true ); } else { idThread::ReturnInt( false ); } } /* ===================== idAI::Event_CanHitEnemyFromJoint ===================== */ void idAI::Event_CanHitEnemyFromJoint( const char* jointname ) { trace_t tr; idVec3 muzzle; idMat3 axis; idVec3 start; float distance; idActor* enemyEnt = enemy.GetEntity(); if( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } // don't check twice per frame if( gameLocal.time == lastHitCheckTime ) { idThread::ReturnInt( lastHitCheckResult ); return; } lastHitCheckTime = gameLocal.time; const idVec3& org = physicsObj.GetOrigin(); idVec3 toPos = enemyEnt->GetEyePosition(); jointHandle_t joint = animator.GetJointHandle( jointname ); if( joint == INVALID_JOINT ) { gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); } animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); muzzle = org + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); if( projectileClipModel == NULL ) { CreateProjectileClipModel(); } // check if the owner bounds is bigger than the projectile bounds const idBounds& ownerBounds = physicsObj.GetAbsBounds(); const idBounds& projBounds = projectileClipModel->GetBounds(); if( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if( ( ownerBounds - projBounds ).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, muzzle, projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); muzzle = tr.endpos; gameLocal.clip.Translation( tr, muzzle, toPos, projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); if( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { lastHitCheckResult = true; } else { lastHitCheckResult = false; } idThread::ReturnInt( lastHitCheckResult ); } /* ===================== idAI::Event_EnemyPositionValid ===================== */ void idAI::Event_EnemyPositionValid() { bool result; result = EnemyPositionValid(); idThread::ReturnInt( result ); } /* ===================== idAI::Event_ChargeAttack ===================== */ void idAI::Event_ChargeAttack( const char* damageDef ) { idActor* enemyEnt = enemy.GetEntity(); StopMove( MOVE_STATUS_DEST_NOT_FOUND ); if( enemyEnt ) { idVec3 enemyOrg; if( move.moveType == MOVETYPE_FLY ) { // position destination so that we're in the enemy's view enemyOrg = enemyEnt->GetEyePosition(); enemyOrg -= enemyEnt->GetPhysics()->GetGravityNormal() * fly_offset; } else { enemyOrg = enemyEnt->GetPhysics()->GetOrigin(); } BeginAttack( damageDef ); DirectMoveToPosition( enemyOrg ); TurnToward( enemyOrg ); } } /* ===================== idAI::Event_TestChargeAttack ===================== */ void idAI::Event_TestChargeAttack() { trace_t trace; idActor* enemyEnt = enemy.GetEntity(); predictedPath_t path; idVec3 end; if( !enemyEnt ) { idThread::ReturnFloat( 0.0f ); return; } if( move.moveType == MOVETYPE_FLY ) { // position destination so that we're in the enemy's view end = enemyEnt->GetEyePosition(); end -= enemyEnt->GetPhysics()->GetGravityNormal() * fly_offset; } else { end = enemyEnt->GetPhysics()->GetOrigin(); } idAI::PredictPath( this, aas, physicsObj.GetOrigin(), end - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); if( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), end, 1 ); gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), end, 1 ); } if( ( path.endEvent == 0 ) || ( path.blockingEntity == enemyEnt ) ) { idVec3 delta = end - physicsObj.GetOrigin(); float time = delta.LengthFast(); idThread::ReturnFloat( time ); } else { idThread::ReturnFloat( 0.0f ); } } /* ===================== idAI::Event_TestAnimMoveTowardEnemy ===================== */ void idAI::Event_TestAnimMoveTowardEnemy( const char* animname ) { int anim; predictedPath_t path; idVec3 moveVec; float yaw; idVec3 delta; idActor* enemyEnt; enemyEnt = enemy.GetEntity(); 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( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, 1 ); gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, 1 ); } idThread::ReturnInt( path.endEvent == 0 ); } /* ===================== idAI::Event_TestAnimMove ===================== */ void idAI::Event_TestAnimMove( const char* animname ) { int anim; predictedPath_t path; idVec3 moveVec; 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, ideal_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( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), physicsObj.GetOrigin() + moveVec, 1 ); gameRenderWorld->DebugBounds( path.endEvent == 0 ? colorYellow : colorRed, physicsObj.GetBounds(), physicsObj.GetOrigin() + moveVec, 1 ); } 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( ai_debugMove.GetBool() ) { gameRenderWorld->DebugLine( colorGreen, physicsObj.GetOrigin(), position, 1 ); gameRenderWorld->DebugBounds( colorYellow, physicsObj.GetBounds(), position, 1 ); if( path.endEvent ) { gameRenderWorld->DebugBounds( colorRed, physicsObj.GetBounds(), path.endPos, 1 ); } } idThread::ReturnInt( path.endEvent == 0 ); } /* ===================== idAI::Event_TestMeleeAttack ===================== */ void idAI::Event_TestMeleeAttack() { 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.GetEntity() ) ); } /* ===================== idAI::Event_PreBurn ===================== */ void idAI::Event_PreBurn() { // No grabbing after the burn has started! noGrab = true; // for now this just turns shadows off renderEntity.noShadow = true; } /* ===================== idAI::Event_Burn ===================== */ void idAI::Event_Burn() { renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; SpawnParticles( "smoke_burnParticleSystem" ); UpdateVisuals(); } /* ===================== idAI::Event_ClearBurn ===================== */ void idAI::Event_ClearBurn() { renderEntity.noShadow = spawnArgs.GetBool( "noshadows" ); renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f; UpdateVisuals(); } /* ===================== idAI::Event_SetSmokeVisibility ===================== */ void idAI::Event_SetSmokeVisibility( int num, int on ) { int i; int time; if( num >= particles.Num() ) { gameLocal.Warning( "Particle #%d out of range (%d particles) on entity '%s'", num, particles.Num(), name.c_str() ); return; } if( on != 0 ) { time = gameLocal.time; BecomeActive( TH_UPDATEPARTICLES ); } else { time = 0; } if( num >= 0 ) { particles[ num ].time = time; } else { for( i = 0; i < particles.Num(); i++ ) { particles[ i ].time = time; } } UpdateVisuals(); } /* ===================== idAI::Event_NumSmokeEmitters ===================== */ void idAI::Event_NumSmokeEmitters() { idThread::ReturnInt( particles.Num() ); } /* ===================== idAI::Event_StopThinking ===================== */ void idAI::Event_StopThinking() { BecomeInactive( TH_THINK ); idThread* thread = idThread::CurrentThread(); if( thread ) { thread->DoneProcessing(); } } /* ===================== idAI::Event_GetTurnDelta ===================== */ void idAI::Event_GetTurnDelta() { float amount; if( turnRate ) { amount = idMath::AngleNormalize180( ideal_yaw - current_yaw ); idThread::ReturnFloat( amount ); } else { idThread::ReturnFloat( 0.0f ); } } /* ===================== idAI::Event_GetMoveType ===================== */ void idAI::Event_GetMoveType() { idThread::ReturnInt( move.moveType ); } /* ===================== idAI::Event_SetMoveTypes ===================== */ void idAI::Event_SetMoveType( int moveType ) { if( ( moveType < 0 ) || ( moveType >= NUM_MOVETYPES ) ) { gameLocal.Error( "Invalid movetype %d", moveType ); } move.moveType = static_cast( moveType ); if( move.moveType == MOVETYPE_FLY ) { travelFlags = TFL_WALK | TFL_AIR | TFL_FLY; } else { travelFlags = TFL_WALK | TFL_AIR; } } /* ===================== idAI::Event_SaveMove ===================== */ void idAI::Event_SaveMove() { savedMove = move; } /* ===================== idAI::Event_RestoreMove ===================== */ void idAI::Event_RestoreMove() { idVec3 goalPos; idVec3 dest; switch( savedMove.moveCommand ) { case MOVE_NONE : StopMove( savedMove.moveStatus ); break; case MOVE_FACE_ENEMY : FaceEnemy(); break; case MOVE_FACE_ENTITY : FaceEntity( savedMove.goalEntity.GetEntity() ); break; case MOVE_TO_ENEMY : MoveToEnemy(); break; case MOVE_TO_ENEMYHEIGHT : MoveToEnemyHeight(); break; case MOVE_TO_ENTITY : MoveToEntity( savedMove.goalEntity.GetEntity() ); break; case MOVE_OUT_OF_RANGE : MoveOutOfRange( savedMove.goalEntity.GetEntity(), savedMove.range ); break; case MOVE_TO_ATTACK_POSITION : MoveToAttackPosition( savedMove.goalEntity.GetEntity(), savedMove.anim ); break; case MOVE_TO_COVER : MoveToCover( savedMove.goalEntity.GetEntity(), lastVisibleEnemyPos ); break; case MOVE_TO_POSITION : MoveToPosition( savedMove.moveDest ); break; case MOVE_TO_POSITION_DIRECT : DirectMoveToPosition( savedMove.moveDest ); break; case MOVE_SLIDE_TO_POSITION : SlideToPosition( savedMove.moveDest, savedMove.duration ); break; case MOVE_WANDER : WanderAround(); break; } if( GetMovePos( goalPos ) ) { CheckObstacleAvoidance( goalPos, dest ); } } /* ===================== idAI::Event_AllowMovement ===================== */ void idAI::Event_AllowMovement( float flag ) { allowMove = ( flag != 0.0f ); } /* ===================== idAI::Event_JumpFrame ===================== */ void idAI::Event_JumpFrame() { AI_JUMP = true; } /* ===================== idAI::Event_EnableClip ===================== */ void idAI::Event_EnableClip() { physicsObj.SetClipMask( MASK_MONSTERSOLID ); disableGravity = false; } /* ===================== idAI::Event_DisableClip ===================== */ void idAI::Event_DisableClip() { physicsObj.SetClipMask( 0 ); disableGravity = true; } /* ===================== idAI::Event_EnableGravity ===================== */ void idAI::Event_EnableGravity() { disableGravity = false; } /* ===================== idAI::Event_DisableGravity ===================== */ void idAI::Event_DisableGravity() { disableGravity = true; } /* ===================== idAI::Event_EnableAFPush ===================== */ void idAI::Event_EnableAFPush() { af_push_moveables = true; } /* ===================== idAI::Event_DisableAFPush ===================== */ void idAI::Event_DisableAFPush() { af_push_moveables = false; } /* ===================== idAI::Event_SetFlySpeed ===================== */ void idAI::Event_SetFlySpeed( float speed ) { if( move.speed == fly_speed ) { move.speed = speed; } fly_speed = speed; } /* ================ idAI::Event_SetFlyOffset ================ */ void idAI::Event_SetFlyOffset( int offset ) { fly_offset = offset; } /* ================ idAI::Event_ClearFlyOffset ================ */ void idAI::Event_ClearFlyOffset() { spawnArgs.GetInt( "fly_offset", "0", fly_offset ); } /* ===================== idAI::Event_GetClosestHiddenTarget ===================== */ void idAI::Event_GetClosestHiddenTarget( const char* type ) { int i; idEntity* ent; idEntity* bestEnt; float time; float bestTime; const idVec3& org = physicsObj.GetOrigin(); idActor* enemyEnt = enemy.GetEntity(); if( !enemyEnt ) { // no enemy to hide from idThread::ReturnEntity( NULL ); return; } if( targets.Num() == 1 ) { ent = targets[ 0 ].GetEntity(); if( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) { if( !EntityCanSeePos( enemyEnt, lastVisibleEnemyPos, ent->GetPhysics()->GetOrigin() ) ) { idThread::ReturnEntity( ent ); return; } } idThread::ReturnEntity( NULL ); return; } bestEnt = NULL; bestTime = idMath::INFINITY; for( i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) { const idVec3& destOrg = ent->GetPhysics()->GetOrigin(); time = TravelDistance( org, destOrg ); if( ( time >= 0.0f ) && ( time < bestTime ) ) { if( !EntityCanSeePos( enemyEnt, lastVisibleEnemyPos, destOrg ) ) { bestEnt = ent; bestTime = time; } } } } idThread::ReturnEntity( bestEnt ); } /* ===================== idAI::Event_GetRandomTarget ===================== */ void idAI::Event_GetRandomTarget( const char* type ) { int i; int num; int which; idEntity* ent; idEntity* ents[ MAX_GENTITIES ]; num = 0; for( i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if( ent != NULL && idStr::Cmp( ent->GetEntityDefName(), type ) == 0 ) { ents[ num++ ] = ent; if( num >= MAX_GENTITIES ) { break; } } } if( !num ) { idThread::ReturnEntity( NULL ); return; } which = gameLocal.random.RandomInt( num ); idThread::ReturnEntity( ents[ which ] ); } /* ================ idAI::Event_TravelDistanceToPoint ================ */ void idAI::Event_TravelDistanceToPoint( const idVec3& pos ) { float time; time = TravelDistance( physicsObj.GetOrigin(), pos ); idThread::ReturnFloat( time ); } /* ================ idAI::Event_TravelDistanceToEntity ================ */ void idAI::Event_TravelDistanceToEntity( idEntity* ent ) { float time; time = TravelDistance( physicsObj.GetOrigin(), ent->GetPhysics()->GetOrigin() ); idThread::ReturnFloat( time ); } /* ================ idAI::Event_TravelDistanceBetweenPoints ================ */ void idAI::Event_TravelDistanceBetweenPoints( const idVec3& source, const idVec3& dest ) { float time; time = TravelDistance( source, dest ); idThread::ReturnFloat( time ); } /* ================ idAI::Event_TravelDistanceBetweenEntities ================ */ void idAI::Event_TravelDistanceBetweenEntities( idEntity* source, idEntity* dest ) { float time; assert( source ); assert( dest ); time = TravelDistance( source->GetPhysics()->GetOrigin(), dest->GetPhysics()->GetOrigin() ); idThread::ReturnFloat( time ); } /* ===================== idAI::Event_LookAtEntity ===================== */ void idAI::Event_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_LookAtEnemy ===================== */ void idAI::Event_LookAtEnemy( float duration ) { idActor* enemyEnt; enemyEnt = enemy.GetEntity(); if( ( enemyEnt != focusEntity.GetEntity() ) || ( focusTime < gameLocal.time ) ) { focusEntity = enemyEnt; alignHeadTime = gameLocal.time; forceAlignHeadTime = gameLocal.time + SEC2MS( 1 ); blink_time = 0; } focusTime = gameLocal.time + SEC2MS( duration ); } /* =============== idAI::Event_SetJointMod =============== */ void idAI::Event_SetJointMod( int allow ) { allowJointMod = ( allow != 0 ); } /* ================ idAI::Event_ThrowMoveable ================ */ void idAI::Event_ThrowMoveable() { idEntity* ent; idEntity* moveable = NULL; for( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { if( ent->GetBindMaster() == this && ent->IsType( idMoveable::Type ) ) { moveable = ent; break; } } if( moveable ) { moveable->Unbind(); // RB: 64 bit fixes, changed NULL to 0 moveable->PostEventMS( &EV_SetOwner, 200, 0 ); // RB end } } /* ================ idAI::Event_ThrowAF ================ */ void idAI::Event_ThrowAF() { idEntity* ent; idEntity* af = NULL; for( ent = GetNextTeamEntity(); ent != NULL; ent = ent->GetNextTeamEntity() ) { if( ent->GetBindMaster() == this && ent->IsType( idAFEntity_Base::Type ) ) { af = ent; break; } } if( af ) { af->Unbind(); // RB: 64 bit fixes, changed NULL to 0 af->PostEventMS( &EV_SetOwner, 200, 0 ); // RB end } } /* ================ idAI::Event_SetAngles ================ */ void idAI::Event_SetAngles( idAngles const& ang ) { current_yaw = ang.yaw; viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3(); } /* ================ idAI::Event_GetAngles ================ */ void idAI::Event_GetAngles() { idThread::ReturnVector( idVec3( 0.0f, current_yaw, 0.0f ) ); } /* ================ idAI::Event_GetTrajectoryToPlayer ================ */ void idAI::Event_GetTrajectoryToPlayer() { idVec3 start; idVec3 end; idVec3 dir; float dist; // bool result; idEntity* enemyEnt = enemy.GetEntity(); if( !enemyEnt ) { idThread::ReturnVector( vec3_zero ); return; } end = enemyEnt->GetPhysics()->GetOrigin(); float speed = 400.0f; if( speed <= 0.0f ) { gameLocal.Error( "Invalid speed. speed must be > 0." ); } start = physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 50.0f ); dir = end - start; dist = dir.Normalize(); if( dist > 16.0f ) { dist -= 16.0f; end -= dir * 16.0f; } idVec3 gravity; ballistics_t ballistics[2]; Ballistics( start, end, speed, 0.0f, ballistics ); dir.y = ballistics[0].angle; // result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, 1000.0f, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir ); // if ( result ) { idThread::ReturnVector( dir * speed ); // } else { // idThread::ReturnVector( vec3_zero ); // } } /* ================ idAI::Event_RealKill ================ */ void idAI::Event_RealKill() { 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() { PostEventMS( &AI_RealKill, 0 ); } /* ================ idAI::Event_WakeOnFlashlight ================ */ void idAI::Event_WakeOnFlashlight( int enable ) { wakeOnFlashlight = ( enable != 0 ); } /* ================ idAI::Event_LocateEnemy ================ */ void idAI::Event_LocateEnemy() { idActor* enemyEnt; int areaNum; enemyEnt = enemy.GetEntity(); if( !enemyEnt ) { return; } enemyEnt->GetAASLocation( aas, lastReachableEnemyPos, areaNum ); SetEnemyPosition(); UpdateEnemyPosition(); } /* ================ idAI::Event_KickObstacles ================ */ void idAI::Event_KickObstacles( idEntity* kickEnt, float force ) { idVec3 dir; idEntity* obEnt; if( kickEnt ) { obEnt = kickEnt; } else { obEnt = move.obstacle.GetEntity(); } if( obEnt ) { dir = obEnt->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(); dir.Normalize(); } else { dir = viewAxis[ 0 ]; } KickObstacles( dir, force, obEnt ); } /* ================ idAI::Event_GetObstacle ================ */ void idAI::Event_GetObstacle() { idThread::ReturnEntity( move.obstacle.GetEntity() ); } /* ================ idAI::Event_PushPointIntoAAS ================ */ void idAI::Event_PushPointIntoAAS( const idVec3& pos ) { int areaNum; idVec3 newPos; areaNum = PointReachableAreaNum( pos ); if( areaNum ) { newPos = pos; aas->PushPointIntoAreaNum( areaNum, newPos ); idThread::ReturnVector( newPos ); } else { idThread::ReturnVector( pos ); } } /* ================ idAI::Event_GetTurnRate ================ */ void idAI::Event_GetTurnRate() { idThread::ReturnFloat( turnRate ); } /* ================ idAI::Event_SetTurnRate ================ */ void idAI::Event_SetTurnRate( float rate ) { turnRate = rate; } /* ================ idAI::Event_AnimTurn ================ */ void idAI::Event_AnimTurn( float angles ) { turnVel = 0.0f; anim_turn_angles = angles; if( angles ) { anim_turn_yaw = current_yaw; anim_turn_amount = idMath::Fabs( idMath::AngleNormalize180( current_yaw - ideal_yaw ) ); if( anim_turn_amount > anim_turn_angles ) { anim_turn_amount = anim_turn_angles; } } else { anim_turn_amount = 0.0f; animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f ); animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, 0.0f ); animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f ); animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, 0.0f ); } } /* ================ idAI::Event_AllowHiddenMovement ================ */ void idAI::Event_AllowHiddenMovement( int enable ) { allowHiddenMovement = ( enable != 0 ); } /* ================ idAI::Event_TriggerParticles ================ */ void idAI::Event_TriggerParticles( const char* jointName ) { TriggerParticles( jointName ); } /* ===================== idAI::Event_FindActorsInBounds ===================== */ void idAI::Event_FindActorsInBounds( const idVec3& mins, const idVec3& maxs ) { idEntity* ent; idEntity* entityList[ MAX_GENTITIES ]; int numListedEntities; int i; numListedEntities = gameLocal.clip.EntitiesTouchingBounds( idBounds( mins, maxs ), CONTENTS_BODY, entityList, MAX_GENTITIES ); for( i = 0; i < numListedEntities; i++ ) { ent = entityList[ i ]; if( ent != this && !ent->IsHidden() && ( ent->health > 0 ) && ent->IsType( idActor::Type ) ) { idThread::ReturnEntity( ent ); return; } } idThread::ReturnEntity( NULL ); } /* ================ 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; } if( ent->IsType( idActor::Type ) && static_cast( ent )->OnLadder() ) { 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() { aasPath_t path; int toAreaNum; int areaNum; idVec3 pos; idActor* enemyEnt; enemyEnt = enemy.GetEntity(); if( !enemyEnt ) { idThread::ReturnInt( false ); return; } if( move.moveType != MOVETYPE_FLY ) { if( enemyEnt->OnLadder() ) { idThread::ReturnInt( false ); return; } enemyEnt->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 ) ) { // NOTE: not a good way to return 'false' return idThread::ReturnVector( vec3_zero ); } if( ent->IsType( idActor::Type ) && static_cast( ent )->OnLadder() ) { // NOTE: not a good way to return 'false' return idThread::ReturnVector( vec3_zero ); } } else { pos = ent->GetPhysics()->GetOrigin(); } if( aas ) { toAreaNum = PointReachableAreaNum( pos ); aas->PushPointIntoAreaNum( toAreaNum, pos ); } idThread::ReturnVector( pos ); } /* ================ idAI::Event_MoveToPositionDirect ================ */ void idAI::Event_MoveToPositionDirect( const idVec3& pos ) { StopMove( MOVE_STATUS_DONE ); DirectMoveToPosition( pos ); } /* ================ idAI::Event_AvoidObstacles ================ */ void idAI::Event_AvoidObstacles( int ignore ) { ignore_obstacles = ( ignore == 1 ) ? false : true; } /* ================ idAI::Event_TriggerFX ================ */ void idAI::Event_TriggerFX( const char* joint, const char* fx ) { TriggerFX( joint, fx ); } void idAI::Event_StartEmitter( const char* name, const char* joint, const char* particle ) { idEntity* ent = StartEmitter( name, joint, particle ); idThread::ReturnEntity( ent ); } void idAI::Event_GetEmitter( const char* name ) { idThread::ReturnEntity( GetEmitter( name ) ); } void idAI::Event_StopEmitter( const char* name ) { StopEmitter( name ); } /* ===================== idAI::Event_LaunchHomingMissile ===================== */ void idAI::Event_LaunchHomingMissile() { idVec3 start; trace_t tr; idBounds projBounds; const idClipModel* projClip; idMat3 axis; float distance; if( !projectileDef ) { gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); idThread::ReturnEntity( NULL ); return; } idActor* enemy = GetEnemy(); if( enemy == NULL ) { idThread::ReturnEntity( NULL ); return; } idVec3 org = GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 250.0f ); idVec3 goal = enemy->GetPhysics()->GetOrigin(); homingMissileGoal = goal; // axis = ( goal - org ).ToMat3(); // axis.Identity(); if( !projectile.GetEntity() ) { idHomingProjectile* homing = ( idHomingProjectile* ) CreateProjectile( org, idVec3( 0.0f, 0.0f, 1.0f ) ); if( homing != NULL ) { homing->SetEnemy( enemy ); homing->SetSeekPos( homingMissileGoal ); } } // make sure the projectile starts inside the monster bounding box const idBounds& ownerBounds = physicsObj.GetAbsBounds(); projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); // check if the owner bounds is bigger than the projectile bounds if( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if( ( ownerBounds - projBounds ).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); // launch the projectile idThread::ReturnEntity( projectile.GetEntity() ); idVec3 dir = homingMissileGoal - org; idAngles ang = dir.ToAngles(); ang.pitch = -45.0f; projectile.GetEntity()->Launch( org, ang.ToForward(), vec3_origin ); projectile = NULL; TriggerWeaponEffects( tr.endpos ); lastAttackTime = gameLocal.time; } /* ===================== idAI::Event_SetHomingMissileGoal ===================== */ void idAI::Event_SetHomingMissileGoal() { idActor* enemy = GetEnemy(); if( enemy == NULL ) { idThread::ReturnEntity( NULL ); return; } homingMissileGoal = enemy->GetPhysics()->GetOrigin(); }