mirror of
https://github.com/dhewm/dhewm3-sdk.git
synced 2025-02-25 05:01:33 +00:00
mostly by fixing missing includes and some other compiler errors, also fixed some compiler warnings (mostly uninitialized variables)
5580 lines
148 KiB
C++
5580 lines
148 KiB
C++
/*
|
||
===========================================================================
|
||
|
||
Doom 3 GPL Source Code
|
||
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
||
|
||
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
||
|
||
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
In addition, the Doom 3 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 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.
|
||
|
||
===========================================================================
|
||
*/
|
||
|
||
#include "sys/platform.h"
|
||
#include "idlib/math/Quat.h"
|
||
|
||
#include "gamesys/SysCvar.h"
|
||
#include "Moveable.h"
|
||
#include "SmokeParticles.h"
|
||
|
||
#include "ai/AI.h"
|
||
|
||
static const char *moveCommandString[ NUM_MOVE_COMMANDS ] = {
|
||
"MOVE_NONE",
|
||
"MOVE_FACE_ENEMY",
|
||
"MOVE_FACE_ENTITY",
|
||
"MOVE_TO_ENEMY",
|
||
"MOVE_TO_ENEMYHEIGHT",
|
||
"MOVE_TO_ENTITY",
|
||
"MOVE_OUT_OF_RANGE",
|
||
"MOVE_TO_ATTACK_POSITION",
|
||
"MOVE_TO_COVER",
|
||
"MOVE_TO_POSITION",
|
||
"MOVE_TO_POSITION_DIRECT",
|
||
"MOVE_SLIDE_TO_POSITION",
|
||
"MOVE_WANDER"
|
||
};
|
||
|
||
/*
|
||
=====================
|
||
idMoveState::idMoveState
|
||
=====================
|
||
*/
|
||
idMoveState::idMoveState() {
|
||
moveType = MOVETYPE_ANIM;
|
||
moveCommand = MOVE_NONE;
|
||
moveStatus = MOVE_STATUS_DONE;
|
||
moveDest.Zero();
|
||
moveDir.Set( 1.0f, 0.0f, 0.0f );
|
||
goalEntity = NULL;
|
||
goalEntityOrigin.Zero();
|
||
toAreaNum = 0;
|
||
startTime = 0;
|
||
duration = 0;
|
||
speed = 0.0f;
|
||
range = 0.0f;
|
||
wanderYaw = 0;
|
||
nextWanderTime = 0;
|
||
blockTime = 0;
|
||
obstacle = NULL;
|
||
lastMoveOrigin = vec3_origin;
|
||
lastMoveTime = 0;
|
||
anim = 0;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idMoveState::Save
|
||
=====================
|
||
*/
|
||
void idMoveState::Save( idSaveGame *savefile ) const {
|
||
savefile->WriteInt( (int)moveType );
|
||
savefile->WriteInt( (int)moveCommand );
|
||
savefile->WriteInt( (int)moveStatus );
|
||
savefile->WriteVec3( moveDest );
|
||
savefile->WriteVec3( moveDir );
|
||
goalEntity.Save( savefile );
|
||
savefile->WriteVec3( goalEntityOrigin );
|
||
savefile->WriteInt( toAreaNum );
|
||
savefile->WriteInt( startTime );
|
||
savefile->WriteInt( duration );
|
||
savefile->WriteFloat( speed );
|
||
savefile->WriteFloat( range );
|
||
savefile->WriteFloat( wanderYaw );
|
||
savefile->WriteInt( nextWanderTime );
|
||
savefile->WriteInt( blockTime );
|
||
obstacle.Save( savefile );
|
||
savefile->WriteVec3( lastMoveOrigin );
|
||
savefile->WriteInt( lastMoveTime );
|
||
savefile->WriteInt( anim );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idMoveState::Restore
|
||
=====================
|
||
*/
|
||
void idMoveState::Restore( idRestoreGame *savefile ) {
|
||
savefile->ReadInt( (int &)moveType );
|
||
savefile->ReadInt( (int &)moveCommand );
|
||
savefile->ReadInt( (int &)moveStatus );
|
||
savefile->ReadVec3( moveDest );
|
||
savefile->ReadVec3( moveDir );
|
||
goalEntity.Restore( savefile );
|
||
savefile->ReadVec3( goalEntityOrigin );
|
||
savefile->ReadInt( toAreaNum );
|
||
savefile->ReadInt( startTime );
|
||
savefile->ReadInt( duration );
|
||
savefile->ReadFloat( speed );
|
||
savefile->ReadFloat( range );
|
||
savefile->ReadFloat( wanderYaw );
|
||
savefile->ReadInt( nextWanderTime );
|
||
savefile->ReadInt( blockTime );
|
||
obstacle.Restore( savefile );
|
||
savefile->ReadVec3( lastMoveOrigin );
|
||
savefile->ReadInt( lastMoveTime );
|
||
savefile->ReadInt( anim );
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindCover::idAASFindCover
|
||
============
|
||
*/
|
||
idAASFindCover::idAASFindCover( const idVec3 &hideFromPos ) {
|
||
int numPVSAreas;
|
||
idBounds bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) );
|
||
|
||
// setup PVS
|
||
numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
|
||
hidePVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindCover::~idAASFindCover
|
||
============
|
||
*/
|
||
idAASFindCover::~idAASFindCover() {
|
||
gameLocal.pvs.FreeCurrentPVS( hidePVS );
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindCover::TestArea
|
||
============
|
||
*/
|
||
bool idAASFindCover::TestArea( const idAAS *aas, int areaNum ) {
|
||
idVec3 areaCenter;
|
||
int numPVSAreas;
|
||
int PVSAreas[ idEntity::MAX_PVS_AREAS ];
|
||
|
||
areaCenter = aas->AreaCenter( areaNum );
|
||
areaCenter[ 2 ] += 1.0f;
|
||
|
||
numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
|
||
if ( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) ) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindAreaOutOfRange::idAASFindAreaOutOfRange
|
||
============
|
||
*/
|
||
idAASFindAreaOutOfRange::idAASFindAreaOutOfRange( const idVec3 &targetPos, float maxDist ) {
|
||
this->targetPos = targetPos;
|
||
this->maxDistSqr = maxDist * maxDist;
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindAreaOutOfRange::TestArea
|
||
============
|
||
*/
|
||
bool idAASFindAreaOutOfRange::TestArea( const idAAS *aas, int areaNum ) {
|
||
const idVec3 &areaCenter = aas->AreaCenter( areaNum );
|
||
trace_t trace;
|
||
float dist;
|
||
|
||
dist = ( targetPos.ToVec2() - areaCenter.ToVec2() ).LengthSqr();
|
||
|
||
if ( ( maxDistSqr > 0.0f ) && ( dist < maxDistSqr ) ) {
|
||
return false;
|
||
}
|
||
|
||
gameLocal.clip.TracePoint( trace, targetPos, areaCenter + idVec3( 0.0f, 0.0f, 1.0f ), MASK_OPAQUE, NULL );
|
||
if ( trace.fraction < 1.0f ) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindAttackPosition::idAASFindAttackPosition
|
||
============
|
||
*/
|
||
idAASFindAttackPosition::idAASFindAttackPosition( const idAI *self, const idMat3 &gravityAxis, idEntity *target, const idVec3 &targetPos, const idVec3 &fireOffset ) {
|
||
int numPVSAreas;
|
||
|
||
this->target = target;
|
||
this->targetPos = targetPos;
|
||
this->fireOffset = fireOffset;
|
||
this->self = self;
|
||
this->gravityAxis = gravityAxis;
|
||
|
||
excludeBounds = idBounds( idVec3( -64.0, -64.0f, -8.0f ), idVec3( 64.0, 64.0f, 64.0f ) );
|
||
excludeBounds.TranslateSelf( self->GetPhysics()->GetOrigin() );
|
||
|
||
// setup PVS
|
||
idBounds bounds( targetPos - idVec3( 16, 16, 0 ), targetPos + idVec3( 16, 16, 64 ) );
|
||
numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, idEntity::MAX_PVS_AREAS );
|
||
targetPVS = gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindAttackPosition::~idAASFindAttackPosition
|
||
============
|
||
*/
|
||
idAASFindAttackPosition::~idAASFindAttackPosition() {
|
||
gameLocal.pvs.FreeCurrentPVS( targetPVS );
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAASFindAttackPosition::TestArea
|
||
============
|
||
*/
|
||
bool idAASFindAttackPosition::TestArea( const idAAS *aas, int areaNum ) {
|
||
idVec3 dir;
|
||
idVec3 local_dir;
|
||
idVec3 fromPos;
|
||
idMat3 axis;
|
||
idVec3 areaCenter;
|
||
int numPVSAreas;
|
||
int PVSAreas[ idEntity::MAX_PVS_AREAS ];
|
||
|
||
areaCenter = aas->AreaCenter( areaNum );
|
||
areaCenter[ 2 ] += 1.0f;
|
||
|
||
if ( excludeBounds.ContainsPoint( areaCenter ) ) {
|
||
// too close to where we already are
|
||
return false;
|
||
}
|
||
|
||
numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, idEntity::MAX_PVS_AREAS );
|
||
if ( !gameLocal.pvs.InCurrentPVS( targetPVS, PVSAreas, numPVSAreas ) ) {
|
||
return false;
|
||
}
|
||
|
||
// calculate the world transform of the launch position
|
||
dir = targetPos - areaCenter;
|
||
gravityAxis.ProjectVector( dir, local_dir );
|
||
local_dir.z = 0.0f;
|
||
local_dir.ToVec2().Normalize();
|
||
axis = local_dir.ToMat3();
|
||
fromPos = areaCenter + fireOffset * axis;
|
||
|
||
return self->GetAimDir( fromPos, target, self, dir );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::idAI
|
||
=====================
|
||
*/
|
||
idAI::idAI() {
|
||
aas = NULL;
|
||
travelFlags = TFL_WALK|TFL_AIR;
|
||
|
||
kickForce = 2048.0f;
|
||
ignore_obstacles = false;
|
||
blockedRadius = 0.0f;
|
||
blockedMoveTime = 750;
|
||
blockedAttackTime = 750;
|
||
turnRate = 360.0f;
|
||
turnVel = 0.0f;
|
||
anim_turn_yaw = 0.0f;
|
||
anim_turn_amount = 0.0f;
|
||
anim_turn_angles = 0.0f;
|
||
fly_offset = 0;
|
||
fly_seek_scale = 1.0f;
|
||
fly_roll_scale = 0.0f;
|
||
fly_roll_max = 0.0f;
|
||
fly_roll = 0.0f;
|
||
fly_pitch_scale = 0.0f;
|
||
fly_pitch_max = 0.0f;
|
||
fly_pitch = 0.0f;
|
||
allowMove = false;
|
||
allowHiddenMovement = false;
|
||
fly_speed = 0.0f;
|
||
fly_bob_strength = 0.0f;
|
||
fly_bob_vert = 0.0f;
|
||
fly_bob_horz = 0.0f;
|
||
lastHitCheckResult = false;
|
||
lastHitCheckTime = 0;
|
||
lastAttackTime = 0;
|
||
melee_range = 0.0f;
|
||
projectile_height_to_distance_ratio = 1.0f;
|
||
projectileDef = NULL;
|
||
projectile = NULL;
|
||
projectileClipModel = NULL;
|
||
projectileRadius = 0.0f;
|
||
projectileVelocity = vec3_origin;
|
||
projectileGravity = vec3_origin;
|
||
projectileSpeed = 0.0f;
|
||
chat_snd = NULL;
|
||
chat_min = 0;
|
||
chat_max = 0;
|
||
chat_time = 0;
|
||
talk_state = TALK_NEVER;
|
||
talkTarget = NULL;
|
||
|
||
particles.Clear();
|
||
restartParticles = true;
|
||
useBoneAxis = false;
|
||
|
||
wakeOnFlashlight = false;
|
||
memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
|
||
worldMuzzleFlashHandle = -1;
|
||
|
||
enemy = NULL;
|
||
lastVisibleEnemyPos.Zero();
|
||
lastVisibleEnemyEyeOffset.Zero();
|
||
lastVisibleReachableEnemyPos.Zero();
|
||
lastReachableEnemyPos.Zero();
|
||
shrivel_rate = 0.0f;
|
||
shrivel_start = 0;
|
||
fl.neverDormant = false; // AI's can go dormant
|
||
current_yaw = 0.0f;
|
||
ideal_yaw = 0.0f;
|
||
|
||
num_cinematics = 0;
|
||
current_cinematic = 0;
|
||
|
||
allowEyeFocus = true;
|
||
allowPain = true;
|
||
allowJointMod = true;
|
||
focusEntity = NULL;
|
||
focusTime = 0;
|
||
alignHeadTime = 0;
|
||
forceAlignHeadTime = 0;
|
||
|
||
currentFocusPos.Zero();
|
||
eyeAng.Zero();
|
||
lookAng.Zero();
|
||
destLookAng.Zero();
|
||
lookMin.Zero();
|
||
lookMax.Zero();
|
||
|
||
eyeMin.Zero();
|
||
eyeMax.Zero();
|
||
muzzleFlashEnd = 0;
|
||
flashTime = 0;
|
||
flashJointWorld = INVALID_JOINT;
|
||
|
||
focusJoint = INVALID_JOINT;
|
||
orientationJoint = INVALID_JOINT;
|
||
flyTiltJoint = INVALID_JOINT;
|
||
|
||
eyeVerticalOffset = 0.0f;
|
||
eyeHorizontalOffset = 0.0f;
|
||
eyeFocusRate = 0.0f;
|
||
headFocusRate = 0.0f;
|
||
focusAlignTime = 0;
|
||
|
||
//ivan start
|
||
isXlocked = false;
|
||
fastXpos = 0.0f;
|
||
updXlock = false;
|
||
deltaXfromEnemy = 0.0f;
|
||
//deltaYfromEnemy = 0.0f;
|
||
//deltaZfromEnemy = 0.0f;
|
||
fireMode = 0;
|
||
noPush = false;
|
||
|
||
/*
|
||
modelCallBackDone = false;
|
||
renderEntity.callback = idAI::ModelCallback;
|
||
*/
|
||
//ivan end
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::~idAI
|
||
=====================
|
||
*/
|
||
idAI::~idAI() {
|
||
delete projectileClipModel;
|
||
DeconstructScriptObject();
|
||
scriptObject.Free();
|
||
if ( worldMuzzleFlashHandle != -1 ) {
|
||
gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
|
||
worldMuzzleFlashHandle = -1;
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::Save
|
||
=====================
|
||
*/
|
||
void idAI::Save( idSaveGame *savefile ) const {
|
||
int i;
|
||
|
||
savefile->WriteInt( travelFlags );
|
||
move.Save( savefile );
|
||
savedMove.Save( savefile );
|
||
savefile->WriteFloat( kickForce );
|
||
savefile->WriteBool( ignore_obstacles );
|
||
savefile->WriteFloat( blockedRadius );
|
||
savefile->WriteInt( blockedMoveTime );
|
||
savefile->WriteInt( blockedAttackTime );
|
||
|
||
savefile->WriteFloat( ideal_yaw );
|
||
savefile->WriteFloat( current_yaw );
|
||
savefile->WriteFloat( turnRate );
|
||
savefile->WriteFloat( turnVel );
|
||
savefile->WriteFloat( anim_turn_yaw );
|
||
savefile->WriteFloat( anim_turn_amount );
|
||
savefile->WriteFloat( anim_turn_angles );
|
||
|
||
savefile->WriteStaticObject( physicsObj );
|
||
|
||
savefile->WriteFloat( fly_speed );
|
||
savefile->WriteFloat( fly_bob_strength );
|
||
savefile->WriteFloat( fly_bob_vert );
|
||
savefile->WriteFloat( fly_bob_horz );
|
||
savefile->WriteInt( fly_offset );
|
||
savefile->WriteFloat( fly_seek_scale );
|
||
savefile->WriteFloat( fly_roll_scale );
|
||
savefile->WriteFloat( fly_roll_max );
|
||
savefile->WriteFloat( fly_roll );
|
||
savefile->WriteFloat( fly_pitch_scale );
|
||
savefile->WriteFloat( fly_pitch_max );
|
||
savefile->WriteFloat( fly_pitch );
|
||
|
||
savefile->WriteBool( allowMove );
|
||
savefile->WriteBool( allowHiddenMovement );
|
||
savefile->WriteBool( disableGravity );
|
||
savefile->WriteBool( af_push_moveables );
|
||
|
||
savefile->WriteBool( lastHitCheckResult );
|
||
savefile->WriteInt( lastHitCheckTime );
|
||
savefile->WriteInt( lastAttackTime );
|
||
savefile->WriteFloat( melee_range );
|
||
savefile->WriteFloat( projectile_height_to_distance_ratio );
|
||
|
||
savefile->WriteInt( missileLaunchOffset.Num() );
|
||
for( i = 0; i < missileLaunchOffset.Num(); i++ ) {
|
||
savefile->WriteVec3( missileLaunchOffset[ i ] );
|
||
}
|
||
|
||
idStr projectileName;
|
||
spawnArgs.GetString( "def_projectile", "", projectileName );
|
||
savefile->WriteString( projectileName );
|
||
savefile->WriteFloat( projectileRadius );
|
||
savefile->WriteFloat( projectileSpeed );
|
||
savefile->WriteVec3( projectileVelocity );
|
||
savefile->WriteVec3( projectileGravity );
|
||
projectile.Save( savefile );
|
||
savefile->WriteString( attack );
|
||
|
||
savefile->WriteSoundShader( chat_snd );
|
||
savefile->WriteInt( chat_min );
|
||
savefile->WriteInt( chat_max );
|
||
savefile->WriteInt( chat_time );
|
||
savefile->WriteInt( talk_state );
|
||
talkTarget.Save( savefile );
|
||
|
||
savefile->WriteInt( num_cinematics );
|
||
savefile->WriteInt( current_cinematic );
|
||
|
||
savefile->WriteBool( allowJointMod );
|
||
focusEntity.Save( savefile );
|
||
savefile->WriteVec3( currentFocusPos );
|
||
savefile->WriteInt( focusTime );
|
||
savefile->WriteInt( alignHeadTime );
|
||
savefile->WriteInt( forceAlignHeadTime );
|
||
savefile->WriteAngles( eyeAng );
|
||
savefile->WriteAngles( lookAng );
|
||
savefile->WriteAngles( destLookAng );
|
||
savefile->WriteAngles( lookMin );
|
||
savefile->WriteAngles( lookMax );
|
||
|
||
savefile->WriteInt( lookJoints.Num() );
|
||
for( i = 0; i < lookJoints.Num(); i++ ) {
|
||
savefile->WriteJoint( lookJoints[ i ] );
|
||
savefile->WriteAngles( lookJointAngles[ i ] );
|
||
}
|
||
|
||
savefile->WriteFloat( shrivel_rate );
|
||
savefile->WriteInt( shrivel_start );
|
||
|
||
savefile->WriteInt( particles.Num() );
|
||
for ( i = 0; i < particles.Num(); i++ ) {
|
||
savefile->WriteParticle( particles[i].particle );
|
||
savefile->WriteInt( particles[i].time );
|
||
savefile->WriteJoint( particles[i].joint );
|
||
}
|
||
savefile->WriteBool( restartParticles );
|
||
savefile->WriteBool( useBoneAxis );
|
||
|
||
enemy.Save( savefile );
|
||
savefile->WriteVec3( lastVisibleEnemyPos );
|
||
savefile->WriteVec3( lastVisibleEnemyEyeOffset );
|
||
savefile->WriteVec3( lastVisibleReachableEnemyPos );
|
||
savefile->WriteVec3( lastReachableEnemyPos );
|
||
savefile->WriteBool( wakeOnFlashlight );
|
||
|
||
savefile->WriteAngles( eyeMin );
|
||
savefile->WriteAngles( eyeMax );
|
||
|
||
savefile->WriteFloat( eyeVerticalOffset );
|
||
savefile->WriteFloat( eyeHorizontalOffset );
|
||
savefile->WriteFloat( eyeFocusRate );
|
||
savefile->WriteFloat( headFocusRate );
|
||
savefile->WriteInt( focusAlignTime );
|
||
|
||
savefile->WriteJoint( flashJointWorld );
|
||
savefile->WriteInt( muzzleFlashEnd );
|
||
|
||
savefile->WriteJoint( focusJoint );
|
||
savefile->WriteJoint( orientationJoint );
|
||
savefile->WriteJoint( flyTiltJoint );
|
||
|
||
savefile->WriteBool( GetPhysics() == static_cast<const idPhysics *>(&physicsObj) );
|
||
|
||
//ivan start
|
||
/*
|
||
//moved to idActor
|
||
savefile->WriteBool( isXlocked );
|
||
savefile->WriteBool( updXlock );
|
||
savefile->WriteFloat( lockedXpos );
|
||
*/
|
||
savefile->WriteFloat( deltaXfromEnemy );
|
||
//savefile->WriteFloat( deltaYfromEnemy );
|
||
//savefile->WriteFloat( deltaZfromEnemy );
|
||
savefile->WriteInt( fireMode );
|
||
savefile->WriteBool( noPush );
|
||
//ivan end
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::Restore
|
||
=====================
|
||
*/
|
||
void idAI::Restore( idRestoreGame *savefile ) {
|
||
bool restorePhysics;
|
||
int i;
|
||
int num;
|
||
idBounds bounds;
|
||
|
||
savefile->ReadInt( travelFlags );
|
||
move.Restore( savefile );
|
||
savedMove.Restore( savefile );
|
||
savefile->ReadFloat( kickForce );
|
||
savefile->ReadBool( ignore_obstacles );
|
||
savefile->ReadFloat( blockedRadius );
|
||
savefile->ReadInt( blockedMoveTime );
|
||
savefile->ReadInt( blockedAttackTime );
|
||
|
||
savefile->ReadFloat( ideal_yaw );
|
||
savefile->ReadFloat( current_yaw );
|
||
savefile->ReadFloat( turnRate );
|
||
savefile->ReadFloat( turnVel );
|
||
savefile->ReadFloat( anim_turn_yaw );
|
||
savefile->ReadFloat( anim_turn_amount );
|
||
savefile->ReadFloat( anim_turn_angles );
|
||
|
||
savefile->ReadStaticObject( physicsObj );
|
||
|
||
savefile->ReadFloat( fly_speed );
|
||
savefile->ReadFloat( fly_bob_strength );
|
||
savefile->ReadFloat( fly_bob_vert );
|
||
savefile->ReadFloat( fly_bob_horz );
|
||
savefile->ReadInt( fly_offset );
|
||
savefile->ReadFloat( fly_seek_scale );
|
||
savefile->ReadFloat( fly_roll_scale );
|
||
savefile->ReadFloat( fly_roll_max );
|
||
savefile->ReadFloat( fly_roll );
|
||
savefile->ReadFloat( fly_pitch_scale );
|
||
savefile->ReadFloat( fly_pitch_max );
|
||
savefile->ReadFloat( fly_pitch );
|
||
|
||
savefile->ReadBool( allowMove );
|
||
savefile->ReadBool( allowHiddenMovement );
|
||
savefile->ReadBool( disableGravity );
|
||
savefile->ReadBool( af_push_moveables );
|
||
|
||
savefile->ReadBool( lastHitCheckResult );
|
||
savefile->ReadInt( lastHitCheckTime );
|
||
savefile->ReadInt( lastAttackTime );
|
||
savefile->ReadFloat( melee_range );
|
||
savefile->ReadFloat( projectile_height_to_distance_ratio );
|
||
|
||
savefile->ReadInt( num );
|
||
missileLaunchOffset.SetGranularity( 1 );
|
||
missileLaunchOffset.SetNum( num );
|
||
for( i = 0; i < num; i++ ) {
|
||
savefile->ReadVec3( missileLaunchOffset[ i ] );
|
||
}
|
||
|
||
idStr projectileName;
|
||
savefile->ReadString( projectileName );
|
||
if ( projectileName.Length() ) {
|
||
projectileDef = gameLocal.FindEntityDefDict( projectileName );
|
||
} else {
|
||
projectileDef = NULL;
|
||
}
|
||
savefile->ReadFloat( projectileRadius );
|
||
savefile->ReadFloat( projectileSpeed );
|
||
savefile->ReadVec3( projectileVelocity );
|
||
savefile->ReadVec3( projectileGravity );
|
||
projectile.Restore( savefile );
|
||
savefile->ReadString( attack );
|
||
|
||
savefile->ReadSoundShader( chat_snd );
|
||
savefile->ReadInt( chat_min );
|
||
savefile->ReadInt( chat_max );
|
||
savefile->ReadInt( chat_time );
|
||
savefile->ReadInt( i );
|
||
talk_state = static_cast<talkState_t>( i );
|
||
talkTarget.Restore( savefile );
|
||
|
||
savefile->ReadInt( num_cinematics );
|
||
savefile->ReadInt( current_cinematic );
|
||
|
||
savefile->ReadBool( allowJointMod );
|
||
focusEntity.Restore( savefile );
|
||
savefile->ReadVec3( currentFocusPos );
|
||
savefile->ReadInt( focusTime );
|
||
savefile->ReadInt( alignHeadTime );
|
||
savefile->ReadInt( forceAlignHeadTime );
|
||
savefile->ReadAngles( eyeAng );
|
||
savefile->ReadAngles( lookAng );
|
||
savefile->ReadAngles( destLookAng );
|
||
savefile->ReadAngles( lookMin );
|
||
savefile->ReadAngles( lookMax );
|
||
|
||
savefile->ReadInt( num );
|
||
lookJoints.SetGranularity( 1 );
|
||
lookJoints.SetNum( num );
|
||
lookJointAngles.SetGranularity( 1 );
|
||
lookJointAngles.SetNum( num );
|
||
for( i = 0; i < num; i++ ) {
|
||
savefile->ReadJoint( lookJoints[ i ] );
|
||
savefile->ReadAngles( lookJointAngles[ i ] );
|
||
}
|
||
|
||
savefile->ReadFloat( shrivel_rate );
|
||
savefile->ReadInt( shrivel_start );
|
||
|
||
savefile->ReadInt( num );
|
||
particles.SetNum( num );
|
||
for ( i = 0; i < particles.Num(); i++ ) {
|
||
savefile->ReadParticle( particles[i].particle );
|
||
savefile->ReadInt( particles[i].time );
|
||
savefile->ReadJoint( particles[i].joint );
|
||
}
|
||
savefile->ReadBool( restartParticles );
|
||
savefile->ReadBool( useBoneAxis );
|
||
|
||
enemy.Restore( savefile );
|
||
savefile->ReadVec3( lastVisibleEnemyPos );
|
||
savefile->ReadVec3( lastVisibleEnemyEyeOffset );
|
||
savefile->ReadVec3( lastVisibleReachableEnemyPos );
|
||
savefile->ReadVec3( lastReachableEnemyPos );
|
||
|
||
savefile->ReadBool( wakeOnFlashlight );
|
||
|
||
savefile->ReadAngles( eyeMin );
|
||
savefile->ReadAngles( eyeMax );
|
||
|
||
savefile->ReadFloat( eyeVerticalOffset );
|
||
savefile->ReadFloat( eyeHorizontalOffset );
|
||
savefile->ReadFloat( eyeFocusRate );
|
||
savefile->ReadFloat( headFocusRate );
|
||
savefile->ReadInt( focusAlignTime );
|
||
|
||
savefile->ReadJoint( flashJointWorld );
|
||
savefile->ReadInt( muzzleFlashEnd );
|
||
|
||
savefile->ReadJoint( focusJoint );
|
||
savefile->ReadJoint( orientationJoint );
|
||
savefile->ReadJoint( flyTiltJoint );
|
||
|
||
savefile->ReadBool( restorePhysics );
|
||
|
||
//ivan start
|
||
/*
|
||
//moved to idActor
|
||
savefile->ReadBool( isXlocked );
|
||
savefile->ReadBool( updXlock );
|
||
savefile->ReadFloat( lockedXpos );
|
||
*/
|
||
savefile->ReadFloat( deltaXfromEnemy );
|
||
//savefile->ReadFloat( deltaYfromEnemy );
|
||
//savefile->ReadFloat( deltaZfromEnemy );
|
||
savefile->ReadInt( fireMode );
|
||
savefile->ReadBool( noPush );
|
||
//ivan end
|
||
|
||
|
||
// Set the AAS if the character has the correct gravity vector
|
||
idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
|
||
gravity *= g_gravity.GetFloat();
|
||
if ( gravity == gameLocal.GetGravity() ) {
|
||
SetAAS();
|
||
}
|
||
|
||
SetCombatModel();
|
||
LinkCombat();
|
||
|
||
InitMuzzleFlash();
|
||
|
||
// Link the script variables back to the scriptobject
|
||
LinkScriptVariables();
|
||
|
||
if ( restorePhysics ) {
|
||
RestorePhysics( &physicsObj );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::Spawn
|
||
=====================
|
||
*/
|
||
void idAI::Spawn( void ) {
|
||
const char *jointname;
|
||
const idKeyValue *kv;
|
||
idStr jointName;
|
||
idAngles jointScale;
|
||
jointHandle_t joint;
|
||
idVec3 local_dir;
|
||
bool talks;
|
||
int noMove; //ivan
|
||
|
||
if ( !g_monsters.GetBool() ) {
|
||
PostEventMS( &EV_Remove, 0 );
|
||
return;
|
||
}
|
||
|
||
spawnArgs.GetInt( "team", "1", team );
|
||
spawnArgs.GetInt( "rank", "0", rank );
|
||
spawnArgs.GetInt( "fly_offset", "0", fly_offset );
|
||
spawnArgs.GetFloat( "fly_speed", "100", fly_speed );
|
||
spawnArgs.GetFloat( "fly_bob_strength", "50", fly_bob_strength );
|
||
spawnArgs.GetFloat( "fly_bob_vert", "2", fly_bob_horz );
|
||
spawnArgs.GetFloat( "fly_bob_horz", "2.7", fly_bob_vert );
|
||
spawnArgs.GetFloat( "fly_seek_scale", "4", fly_seek_scale );
|
||
spawnArgs.GetFloat( "fly_roll_scale", "90", fly_roll_scale );
|
||
spawnArgs.GetFloat( "fly_roll_max", "60", fly_roll_max );
|
||
spawnArgs.GetFloat( "fly_pitch_scale", "45", fly_pitch_scale );
|
||
spawnArgs.GetFloat( "fly_pitch_max", "30", fly_pitch_max );
|
||
|
||
spawnArgs.GetFloat( "melee_range", "64", melee_range );
|
||
spawnArgs.GetFloat( "projectile_height_to_distance_ratio", "1", projectile_height_to_distance_ratio );
|
||
|
||
spawnArgs.GetFloat( "turn_rate", "360", turnRate );
|
||
|
||
spawnArgs.GetBool( "talks", "0", talks );
|
||
if ( spawnArgs.GetString( "npc_name", NULL ) != NULL ) {
|
||
if ( talks ) {
|
||
talk_state = TALK_OK;
|
||
} else {
|
||
talk_state = TALK_BUSY;
|
||
}
|
||
} else {
|
||
talk_state = TALK_NEVER;
|
||
}
|
||
|
||
spawnArgs.GetBool( "animate_z", "0", disableGravity );
|
||
spawnArgs.GetBool( "af_push_moveables", "0", af_push_moveables );
|
||
spawnArgs.GetFloat( "kick_force", "4096", kickForce );
|
||
spawnArgs.GetBool( "ignore_obstacles", "1", ignore_obstacles ); //ivan: set default to 1
|
||
spawnArgs.GetFloat( "blockedRadius", "-1", blockedRadius );
|
||
spawnArgs.GetInt( "blockedMoveTime", "750", blockedMoveTime );
|
||
spawnArgs.GetInt( "blockedAttackTime", "750", blockedAttackTime );
|
||
|
||
spawnArgs.GetInt( "num_cinematics", "0", num_cinematics );
|
||
current_cinematic = 0;
|
||
|
||
LinkScriptVariables();
|
||
|
||
fl.takedamage = !spawnArgs.GetBool( "noDamage" );
|
||
enemy = NULL;
|
||
allowMove = true;
|
||
allowHiddenMovement = false;
|
||
|
||
animator.RemoveOriginOffset( true );
|
||
|
||
// create combat collision hull for exact collision detection
|
||
SetCombatModel();
|
||
|
||
lookMin = spawnArgs.GetAngles( "look_min", "-80 -75 0" );
|
||
lookMax = spawnArgs.GetAngles( "look_max", "80 75 0" );
|
||
|
||
lookJoints.SetGranularity( 1 );
|
||
lookJointAngles.SetGranularity( 1 );
|
||
kv = spawnArgs.MatchPrefix( "look_joint", NULL );
|
||
while( kv ) {
|
||
jointName = kv->GetKey();
|
||
jointName.StripLeadingOnce( "look_joint " );
|
||
joint = animator.GetJointHandle( jointName );
|
||
if ( joint == INVALID_JOINT ) {
|
||
gameLocal.Warning( "Unknown look_joint '%s' on entity %s", jointName.c_str(), name.c_str() );
|
||
} else {
|
||
jointScale = spawnArgs.GetAngles( kv->GetKey(), "0 0 0" );
|
||
jointScale.roll = 0.0f;
|
||
|
||
// if no scale on any component, then don't bother adding it. this may be done to
|
||
// zero out rotation from an inherited entitydef.
|
||
if ( jointScale != ang_zero ) {
|
||
lookJoints.Append( joint );
|
||
lookJointAngles.Append( jointScale );
|
||
}
|
||
}
|
||
kv = spawnArgs.MatchPrefix( "look_joint", kv );
|
||
}
|
||
|
||
// calculate joint positions on attack frames so we can do proper "can hit" tests
|
||
CalculateAttackOffsets();
|
||
|
||
eyeMin = spawnArgs.GetAngles( "eye_turn_min", "-10 -30 0" );
|
||
eyeMax = spawnArgs.GetAngles( "eye_turn_max", "10 30 0" );
|
||
eyeVerticalOffset = spawnArgs.GetFloat( "eye_verticle_offset", "5" );
|
||
eyeHorizontalOffset = spawnArgs.GetFloat( "eye_horizontal_offset", "-8" );
|
||
eyeFocusRate = spawnArgs.GetFloat( "eye_focus_rate", "0.5" );
|
||
headFocusRate = spawnArgs.GetFloat( "head_focus_rate", "0.1" );
|
||
focusAlignTime = SEC2MS( spawnArgs.GetFloat( "focus_align_time", "1" ) );
|
||
|
||
flashJointWorld = animator.GetJointHandle( "flash" );
|
||
|
||
if ( head.GetEntity() ) {
|
||
idAnimator *headAnimator = head.GetEntity()->GetAnimator();
|
||
|
||
jointname = spawnArgs.GetString( "bone_focus" );
|
||
if ( *jointname ) {
|
||
focusJoint = headAnimator->GetJointHandle( jointname );
|
||
if ( focusJoint == INVALID_JOINT ) {
|
||
gameLocal.Warning( "Joint '%s' not found on head on '%s'", jointname, name.c_str() );
|
||
}
|
||
}
|
||
} else {
|
||
jointname = spawnArgs.GetString( "bone_focus" );
|
||
if ( *jointname ) {
|
||
focusJoint = animator.GetJointHandle( jointname );
|
||
if ( focusJoint == INVALID_JOINT ) {
|
||
gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
|
||
}
|
||
}
|
||
}
|
||
|
||
jointname = spawnArgs.GetString( "bone_orientation" );
|
||
if ( *jointname ) {
|
||
orientationJoint = animator.GetJointHandle( jointname );
|
||
if ( orientationJoint == INVALID_JOINT ) {
|
||
gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
|
||
}
|
||
}
|
||
|
||
jointname = spawnArgs.GetString( "bone_flytilt" );
|
||
if ( *jointname ) {
|
||
flyTiltJoint = animator.GetJointHandle( jointname );
|
||
if ( flyTiltJoint == INVALID_JOINT ) {
|
||
gameLocal.Warning( "Joint '%s' not found on '%s'", jointname, name.c_str() );
|
||
}
|
||
}
|
||
|
||
InitMuzzleFlash();
|
||
|
||
physicsObj.SetSelf( this );
|
||
physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
|
||
physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
|
||
|
||
if ( spawnArgs.GetBool( "big_monster" ) ) {
|
||
physicsObj.SetContents( 0 );
|
||
physicsObj.SetClipMask( MASK_MONSTERSOLID & ~CONTENTS_BODY );
|
||
} else {
|
||
if ( use_combat_bbox ) {
|
||
physicsObj.SetContents( CONTENTS_BODY|CONTENTS_SOLID );
|
||
} else {
|
||
physicsObj.SetContents( CONTENTS_BODY );
|
||
}
|
||
physicsObj.SetClipMask( MASK_MONSTERSOLID );
|
||
}
|
||
|
||
// move up to make sure the monster is at least an epsilon above the floor
|
||
physicsObj.SetOrigin( GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
|
||
|
||
//lockedXpos = GetPhysics()->GetOrigin().x; //ivan - remember initial pos.
|
||
|
||
if ( num_cinematics ) {
|
||
physicsObj.SetGravity( vec3_origin );
|
||
} else {
|
||
idVec3 gravity = spawnArgs.GetVector( "gravityDir", "0 0 -1" );
|
||
gravity *= g_gravity.GetFloat();
|
||
physicsObj.SetGravity( gravity );
|
||
}
|
||
|
||
SetPhysics( &physicsObj );
|
||
|
||
physicsObj.GetGravityAxis().ProjectVector( viewAxis[ 0 ], local_dir );
|
||
current_yaw = local_dir.ToYaw();
|
||
ideal_yaw = idMath::AngleNormalize180( current_yaw );
|
||
|
||
move.blockTime = 0;
|
||
|
||
SetAAS();
|
||
|
||
projectile = NULL;
|
||
projectileDef = NULL;
|
||
projectileClipModel = NULL;
|
||
idStr projectileName;
|
||
if ( spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) {
|
||
projectileDef = gameLocal.FindEntityDefDict( projectileName );
|
||
CreateProjectile( vec3_origin, viewAxis[ 0 ] );
|
||
projectileRadius = projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius();
|
||
projectileVelocity = idProjectile::GetVelocity( projectileDef );
|
||
projectileGravity = idProjectile::GetGravity( projectileDef );
|
||
projectileSpeed = projectileVelocity.Length();
|
||
delete projectile.GetEntity();
|
||
projectile = NULL;
|
||
}
|
||
|
||
particles.Clear();
|
||
restartParticles = true;
|
||
useBoneAxis = spawnArgs.GetBool( "useBoneAxis" );
|
||
SpawnParticles( "smokeParticleSystem" );
|
||
|
||
if ( num_cinematics || spawnArgs.GetBool( "hide" ) || spawnArgs.GetBool( "teleport" ) || spawnArgs.GetBool( "trigger_anim" ) ) {
|
||
fl.takedamage = false;
|
||
physicsObj.SetContents( 0 );
|
||
physicsObj.GetClipModel()->Unlink();
|
||
Hide();
|
||
} else {
|
||
// play a looping ambient sound if we have one
|
||
StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
|
||
}
|
||
|
||
if ( health <= 0 ) {
|
||
gameLocal.Warning( "entity '%s' doesn't have health set", name.c_str() );
|
||
health = 1;
|
||
}
|
||
|
||
// set up monster chatter
|
||
SetChatSound();
|
||
|
||
BecomeActive( TH_THINK );
|
||
|
||
if ( af_push_moveables ) {
|
||
af.SetupPose( this, gameLocal.time );
|
||
af.GetPhysics()->EnableClip();
|
||
}
|
||
|
||
// init the move variables
|
||
StopMove( MOVE_STATUS_DONE );
|
||
|
||
//ivan start
|
||
//if ( gameLocal.GameState() == GAMESTATE_STARTUP ) { //count them only at map start
|
||
if( !spawnArgs.GetBool( "noStats", "0" ) && ( spawnArgs.GetInt( "team", "1") != 0 ) && !spawnArgs.GetBool( "spawner", "0") ){
|
||
gameLocal.enemies_spawned_counter++;
|
||
|
||
//gameLocal.Printf( "Enemies counter increased: %d ( %s )\n", gameLocal.enemies_spawned_counter, name.c_str() );
|
||
}
|
||
//isXlocked = spawnArgs.GetBool( "isXlocked", "1" ); //ivan - moved to idActor
|
||
//updXlock = spawnArgs.GetBool( "updXlock", "1" ); //ivan - moved to idActor
|
||
|
||
fireMode = spawnArgs.GetInt( "fireMode", "0" );
|
||
|
||
spawnArgs.GetInt( "noMove", "0", noMove );
|
||
if( noMove > 0 ){
|
||
AI_INHIBIT_MOVE = true;
|
||
if( noMove > 1 ){
|
||
noPush = true;
|
||
}
|
||
}
|
||
//ivan end
|
||
}
|
||
|
||
/*
|
||
===================
|
||
idAI::InitMuzzleFlash
|
||
===================
|
||
*/
|
||
void idAI::InitMuzzleFlash( void ) {
|
||
const char *shader;
|
||
idVec3 flashColor;
|
||
|
||
spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader );
|
||
spawnArgs.GetVector( "flashColor", "0 0 0", flashColor );
|
||
float flashRadius = spawnArgs.GetFloat( "flashRadius" );
|
||
flashTime = SEC2MS( spawnArgs.GetFloat( "flashTime", "0.25" ) );
|
||
|
||
memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) );
|
||
|
||
worldMuzzleFlash.pointLight = true;
|
||
worldMuzzleFlash.shader = declManager->FindMaterial( shader, false );
|
||
worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0];
|
||
worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1];
|
||
worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2];
|
||
worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
|
||
worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f;
|
||
worldMuzzleFlash.lightRadius[0] = flashRadius;
|
||
worldMuzzleFlash.lightRadius[1] = flashRadius;
|
||
worldMuzzleFlash.lightRadius[2] = flashRadius;
|
||
|
||
worldMuzzleFlashHandle = -1;
|
||
}
|
||
|
||
/*
|
||
===================
|
||
idAI::List_f
|
||
===================
|
||
*/
|
||
void idAI::List_f( const idCmdArgs &args ) {
|
||
int e;
|
||
idAI *check;
|
||
int count;
|
||
const char *statename;
|
||
|
||
count = 0;
|
||
|
||
gameLocal.Printf( "%-4s %-20s %s\n", " Num", "EntityDef", "Name" );
|
||
gameLocal.Printf( "------------------------------------------------\n" );
|
||
for( e = 0; e < MAX_GENTITIES; e++ ) {
|
||
check = static_cast<idAI *>(gameLocal.entities[ e ]);
|
||
if ( !check || !check->IsType( idAI::Type ) ) {
|
||
continue;
|
||
}
|
||
|
||
if ( check->state ) {
|
||
statename = check->state->Name();
|
||
} else {
|
||
statename = "NULL state";
|
||
}
|
||
|
||
gameLocal.Printf( "%4i: %-20s %-20s %s move: %d\n", e, check->GetEntityDefName(), check->name.c_str(), statename, check->allowMove );
|
||
count++;
|
||
}
|
||
|
||
gameLocal.Printf( "...%d monsters\n", count );
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::DormantBegin
|
||
|
||
called when entity becomes dormant
|
||
================
|
||
*/
|
||
void idAI::DormantBegin( void ) {
|
||
// since dormant happens on a timer, we wont get to update particles to
|
||
// hidden through the think loop, but we need to hide them though.
|
||
if ( particles.Num() ) {
|
||
for ( int i = 0; i < particles.Num(); i++ ) {
|
||
particles[i].time = 0;
|
||
}
|
||
}
|
||
|
||
if ( enemyNode.InList() ) {
|
||
// remove ourselves from the enemy's enemylist
|
||
enemyNode.Remove();
|
||
}
|
||
idActor::DormantBegin();
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::DormantEnd
|
||
|
||
called when entity wakes from being dormant
|
||
================
|
||
*/
|
||
void idAI::DormantEnd( void ) {
|
||
if ( enemy.GetEntity() && !enemyNode.InList() ) {
|
||
// let our enemy know we're back on the trail
|
||
enemyNode.AddToEnd( enemy.GetEntity()->enemyList );
|
||
}
|
||
|
||
if ( particles.Num() ) {
|
||
for ( int i = 0; i < particles.Num(); i++ ) {
|
||
particles[i].time = gameLocal.time;
|
||
}
|
||
}
|
||
|
||
idActor::DormantEnd();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::Think
|
||
=====================
|
||
*/
|
||
void idAI::Think( void ) {
|
||
// if we are completely closed off from the player, don't do anything at all
|
||
if ( CheckDormant() ) {
|
||
//isOnScreen = false; //ivan - fix needed?
|
||
return;
|
||
}
|
||
|
||
if ( thinkFlags & TH_THINK ) {
|
||
// clear out the enemy when he dies or is hidden
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
if ( enemyEnt ) {
|
||
if ( enemyEnt->health <= 0 ) {
|
||
EnemyDead();
|
||
}
|
||
}
|
||
|
||
current_yaw += deltaViewAngles.yaw;
|
||
ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw );
|
||
deltaViewAngles.Zero();
|
||
viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
|
||
|
||
if ( num_cinematics ) {
|
||
if ( !IsHidden() && torsoAnim.AnimDone( 0 ) ) {
|
||
PlayCinematic();
|
||
}
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
} else if ( !allowHiddenMovement && IsHidden() ) {
|
||
// hidden monsters
|
||
UpdateAIScript();
|
||
} else {
|
||
// clear the ik before we do anything else so the skeleton doesn't get updated twice
|
||
walkIK.ClearJointMods();
|
||
|
||
switch( move.moveType ) {
|
||
case MOVETYPE_DEAD :
|
||
// dead monsters
|
||
UpdateAIScript();
|
||
DeadMove();
|
||
break;
|
||
|
||
case MOVETYPE_FLY :
|
||
// flying monsters
|
||
UpdateEnemyPosition();
|
||
UpdateAIScript();
|
||
FlyMove();
|
||
PlayChatter();
|
||
CheckBlink();
|
||
break;
|
||
|
||
case MOVETYPE_STATIC :
|
||
// static monsters
|
||
UpdateEnemyPosition();
|
||
UpdateAIScript();
|
||
StaticMove();
|
||
PlayChatter();
|
||
CheckBlink();
|
||
break;
|
||
|
||
case MOVETYPE_ANIM :
|
||
// animation based movement
|
||
UpdateEnemyPosition();
|
||
UpdateAIScript();
|
||
AnimMove();
|
||
PlayChatter();
|
||
CheckBlink();
|
||
break;
|
||
|
||
case MOVETYPE_SLIDE :
|
||
// velocity based movement
|
||
UpdateEnemyPosition();
|
||
UpdateAIScript();
|
||
SlideMove();
|
||
PlayChatter();
|
||
CheckBlink();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// clear pain flag so that we recieve any damage between now and the next time we run the script
|
||
AI_PAIN = false;
|
||
AI_SPECIAL_DAMAGE = 0;
|
||
AI_PUSHED = false;
|
||
} else if ( thinkFlags & TH_PHYSICS ) {
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
}
|
||
|
||
if ( af_push_moveables ) {
|
||
PushWithAF();
|
||
}
|
||
|
||
if ( fl.hidden && allowHiddenMovement ) {
|
||
// UpdateAnimation won't call frame commands when hidden, so call them here when we allow hidden movement
|
||
animator.ServiceAnims( gameLocal.previousTime, gameLocal.time );
|
||
}
|
||
/* this still draws in retail builds.. not sure why.. don't care at this point.
|
||
if ( !aas && developer.GetBool() && !fl.hidden && !num_cinematics ) {
|
||
gameRenderWorld->DrawText( "No AAS", physicsObj.GetAbsBounds().GetCenter(), 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, gameLocal.msec );
|
||
}
|
||
*/
|
||
|
||
//ivan start
|
||
//upd is on screen
|
||
UpdateIsOnScreen(); //NOTE: my "isOnScreen" value is useful also to other entities.
|
||
//ivan end
|
||
|
||
UpdateMuzzleFlash();
|
||
UpdateAnimation();
|
||
UpdateParticles();
|
||
Present();
|
||
UpdateDamageEffects();
|
||
LinkCombat();
|
||
|
||
//ivan start
|
||
//modelCallBackNotDone = true; //reset to true every frame. Next time ::think will be called it will be already updated.
|
||
//ivan end
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
AI script state management
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
=====================
|
||
idAI::LinkScriptVariables
|
||
=====================
|
||
*/
|
||
void idAI::LinkScriptVariables( void ) {
|
||
AI_TALK.LinkTo( scriptObject, "AI_TALK" );
|
||
AI_DAMAGE.LinkTo( scriptObject, "AI_DAMAGE" );
|
||
AI_PAIN.LinkTo( scriptObject, "AI_PAIN" );
|
||
AI_SPECIAL_DAMAGE.LinkTo( scriptObject, "AI_SPECIAL_DAMAGE" );
|
||
AI_DEAD.LinkTo( scriptObject, "AI_DEAD" );
|
||
AI_ENEMY_VISIBLE.LinkTo( scriptObject, "AI_ENEMY_VISIBLE" );
|
||
AI_ENEMY_IN_FOV.LinkTo( scriptObject, "AI_ENEMY_IN_FOV" );
|
||
AI_ENEMY_DEAD.LinkTo( scriptObject, "AI_ENEMY_DEAD" );
|
||
AI_MOVE_DONE.LinkTo( scriptObject, "AI_MOVE_DONE" );
|
||
AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" );
|
||
AI_ACTIVATED.LinkTo( scriptObject, "AI_ACTIVATED" );
|
||
AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" );
|
||
AI_JUMP.LinkTo( scriptObject, "AI_JUMP" );
|
||
AI_BLOCKED.LinkTo( scriptObject, "AI_BLOCKED" );
|
||
AI_DEST_UNREACHABLE.LinkTo( scriptObject, "AI_DEST_UNREACHABLE" );
|
||
AI_HIT_ENEMY.LinkTo( scriptObject, "AI_HIT_ENEMY" );
|
||
AI_OBSTACLE_IN_PATH.LinkTo( scriptObject, "AI_OBSTACLE_IN_PATH" );
|
||
AI_PUSHED.LinkTo( scriptObject, "AI_PUSHED" );
|
||
//ivan start
|
||
AI_INHIBIT_ATTACK.LinkTo( scriptObject, "AI_INHIBIT_ATTACK" );
|
||
AI_INHIBIT_MOVE.LinkTo( scriptObject, "AI_INHIBIT_MOVE" );
|
||
//ivan end
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::UpdateAIScript
|
||
=====================
|
||
*/
|
||
void idAI::UpdateAIScript( void ) {
|
||
UpdateScript();
|
||
|
||
// clear the hit enemy flag so we catch the next time we hit someone
|
||
AI_HIT_ENEMY = false;
|
||
|
||
if ( allowHiddenMovement || !IsHidden() ) {
|
||
// update the animstate if we're not hidden
|
||
UpdateAnimState();
|
||
}
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
navigation
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
============
|
||
idAI::KickObstacles
|
||
============
|
||
*/
|
||
void idAI::KickObstacles( const idVec3 &dir, float force, idEntity *alwaysKick ) {
|
||
int i, numListedClipModels;
|
||
idBounds clipBounds;
|
||
idEntity *obEnt;
|
||
idClipModel *clipModel;
|
||
idClipModel *clipModelList[ MAX_GENTITIES ];
|
||
int clipmask;
|
||
idVec3 org;
|
||
idVec3 forceVec;
|
||
idVec3 delta;
|
||
idVec2 perpendicular;
|
||
|
||
org = physicsObj.GetOrigin();
|
||
|
||
// find all possible obstacles
|
||
clipBounds = physicsObj.GetAbsBounds();
|
||
clipBounds.TranslateSelf( dir * 32.0f );
|
||
clipBounds.ExpandSelf( 8.0f );
|
||
clipBounds.AddPoint( org );
|
||
clipmask = physicsObj.GetClipMask();
|
||
numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipmask, clipModelList, MAX_GENTITIES );
|
||
for ( i = 0; i < numListedClipModels; i++ ) {
|
||
clipModel = clipModelList[i];
|
||
obEnt = clipModel->GetEntity();
|
||
if ( obEnt == alwaysKick ) {
|
||
// we'll kick this one outside the loop
|
||
continue;
|
||
}
|
||
|
||
if ( !clipModel->IsTraceModel() ) {
|
||
continue;
|
||
}
|
||
|
||
if ( obEnt->IsType( idMoveable::Type ) && obEnt->GetPhysics()->IsPushable() ) {
|
||
delta = obEnt->GetPhysics()->GetOrigin() - org;
|
||
delta.NormalizeFast();
|
||
perpendicular.x = -delta.y;
|
||
perpendicular.y = delta.x;
|
||
delta.z += 0.5f;
|
||
delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
|
||
forceVec = delta * force * obEnt->GetPhysics()->GetMass();
|
||
obEnt->ApplyImpulse( this, 0, obEnt->GetPhysics()->GetOrigin(), forceVec );
|
||
}
|
||
}
|
||
|
||
if ( alwaysKick ) {
|
||
delta = alwaysKick->GetPhysics()->GetOrigin() - org;
|
||
delta.NormalizeFast();
|
||
perpendicular.x = -delta.y;
|
||
perpendicular.y = delta.x;
|
||
delta.z += 0.5f;
|
||
delta.ToVec2() += perpendicular * gameLocal.random.CRandomFloat() * 0.5f;
|
||
forceVec = delta * force * alwaysKick->GetPhysics()->GetMass();
|
||
alwaysKick->ApplyImpulse( this, 0, alwaysKick->GetPhysics()->GetOrigin(), forceVec );
|
||
}
|
||
}
|
||
|
||
/*
|
||
============
|
||
ValidForBounds
|
||
============
|
||
*/
|
||
bool ValidForBounds( const idAASSettings *settings, const idBounds &bounds ) {
|
||
int i;
|
||
|
||
for ( i = 0; i < 3; i++ ) {
|
||
if ( bounds[0][i] < settings->boundingBoxes[0][0][i] ) {
|
||
return false;
|
||
}
|
||
if ( bounds[1][i] > settings->boundingBoxes[0][1][i] ) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SetAAS
|
||
=====================
|
||
*/
|
||
void idAI::SetAAS( void ) {
|
||
idStr use_aas;
|
||
|
||
spawnArgs.GetString( "use_aas", NULL, use_aas );
|
||
aas = gameLocal.GetAAS( use_aas );
|
||
if ( aas ) {
|
||
const idAASSettings *settings = aas->GetSettings();
|
||
if ( settings ) {
|
||
if ( !ValidForBounds( settings, physicsObj.GetBounds() ) ) {
|
||
gameLocal.Error( "%s cannot use use_aas %s\n", name.c_str(), use_aas.c_str() );
|
||
}
|
||
float height = settings->maxStepHeight;
|
||
physicsObj.SetMaxStepHeight( height );
|
||
return;
|
||
} else {
|
||
aas = NULL;
|
||
}
|
||
}
|
||
gameLocal.Printf( "WARNING: %s has no AAS file\n", name.c_str() );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::DrawRoute
|
||
=====================
|
||
*/
|
||
void idAI::DrawRoute( void ) const {
|
||
if ( aas && move.toAreaNum && move.moveCommand != MOVE_NONE && move.moveCommand != MOVE_WANDER && move.moveCommand != MOVE_FACE_ENEMY && move.moveCommand != MOVE_FACE_ENTITY && move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
aas->ShowFlyPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
|
||
} else {
|
||
aas->ShowWalkPath( physicsObj.GetOrigin(), move.toAreaNum, move.moveDest );
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::ReachedPos
|
||
=====================
|
||
*/
|
||
bool idAI::ReachedPos( const idVec3 &pos, const moveCommand_t moveCommand ) const {
|
||
if ( move.moveType == MOVETYPE_SLIDE ) {
|
||
idBounds bnds( idVec3( -4, -4.0f, -8.0f ), idVec3( 4.0f, 4.0f, 64.0f ) );
|
||
bnds.TranslateSelf( physicsObj.GetOrigin() );
|
||
if ( bnds.ContainsPoint( pos ) ) {
|
||
return true;
|
||
}
|
||
} else {
|
||
if ( ( moveCommand == MOVE_TO_ENEMY ) || ( moveCommand == MOVE_TO_ENTITY ) ) {
|
||
if ( physicsObj.GetAbsBounds().IntersectsBounds( idBounds( pos ).Expand( 8.0f ) ) ) {
|
||
return true;
|
||
}
|
||
} else {
|
||
idBounds bnds( idVec3( -16.0, -16.0f, -8.0f ), idVec3( 16.0, 16.0f, 64.0f ) );
|
||
bnds.TranslateSelf( physicsObj.GetOrigin() );
|
||
if ( bnds.ContainsPoint( pos ) ) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::PointReachableAreaNum
|
||
=====================
|
||
*/
|
||
int idAI::PointReachableAreaNum( const idVec3 &pos, const float boundsScale ) const {
|
||
int areaNum;
|
||
idVec3 size;
|
||
idBounds bounds;
|
||
|
||
if ( !aas ) {
|
||
return 0;
|
||
}
|
||
|
||
size = aas->GetSettings()->boundingBoxes[0][1] * boundsScale;
|
||
bounds[0] = -size;
|
||
size.z = 32.0f;
|
||
bounds[1] = size;
|
||
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK | AREA_REACHABLE_FLY );
|
||
} else {
|
||
areaNum = aas->PointReachableAreaNum( pos, bounds, AREA_REACHABLE_WALK );
|
||
}
|
||
|
||
return areaNum;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::PathToGoal
|
||
=====================
|
||
*/
|
||
bool idAI::PathToGoal( aasPath_t &path, int areaNum, const idVec3 &origin, int goalAreaNum, const idVec3 &goalOrigin ) const {
|
||
idVec3 org;
|
||
idVec3 goal;
|
||
|
||
if ( !aas ) {
|
||
return false;
|
||
}
|
||
|
||
org = origin;
|
||
aas->PushPointIntoAreaNum( areaNum, org );
|
||
if ( !areaNum ) {
|
||
return false;
|
||
}
|
||
|
||
goal = goalOrigin;
|
||
aas->PushPointIntoAreaNum( goalAreaNum, goal );
|
||
if ( !goalAreaNum ) {
|
||
return false;
|
||
}
|
||
|
||
//ivan start
|
||
|
||
/*
|
||
se io sono/vado su ALTRO piano E c'<27> strada che collega:
|
||
- isXlocked 0 updXlock 0 --> mai lockato, riesce a fare strada,
|
||
- isXlocked 0 updXlock 1 --> si slocca, riesce a fare strada,
|
||
- isXlocked 1 updXlock 1 --> si slocca, riesce a fare strada,
|
||
- isXlocked 1 updXlock 0 --> <20> lockato, impazzisce cercando di fare strada,
|
||
|
||
Soluz: se lockato e no updXlock, non deve provare a raggiungermi se X diversa.
|
||
*/
|
||
if( isXlocked && !updXlock ){
|
||
if( goal.x != fastXpos ){
|
||
//gameLocal.Printf("fix 1\n");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/*se io sono/vado su STESSO piano E c'<27> strada alternativa su altro piano:
|
||
- isXlocked 0 updXlock 0 --> mai lockato, riesce a fare strada alternativa,
|
||
- isXlocked 0 updXlock 1 --> se gi<67> lockato, impazzisce cercando di fare strada alternativa, (quindi ora var isXlocked = 1)
|
||
- isXlocked 1 updXlock 1 --> <20> lockato, impazzisce cercando di fare strada alternativa,
|
||
- isXlocked 1 updXlock 0 --> <20> lockato, impazzisce cercando di fare strada alternativa,
|
||
|
||
Soluz: se lockato, non deve vedere la strada.
|
||
Miglioramento: se updXlock 1 pu<70> cmq vederla, ma se deve anche sloccare...
|
||
*/
|
||
|
||
/*was:
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
return aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
|
||
} else {
|
||
return aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
|
||
}
|
||
*/
|
||
|
||
bool result;
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
result = aas->FlyPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
|
||
} else {
|
||
result = aas->WalkPathToGoal( path, areaNum, org, goalAreaNum, goal, travelFlags );
|
||
}
|
||
|
||
if( isXlocked && result ){
|
||
if( path.moveGoal.x != fastXpos ){
|
||
//gameLocal.Printf("fix 2\n");
|
||
result = false;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
//ivan end
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TravelDistance
|
||
|
||
Returns the approximate travel distance from one position to the goal, or if no AAS, the straight line distance.
|
||
|
||
This is feakin' slow, so it's not good to do it too many times per frame. It also is slower the further you
|
||
are from the goal, so try to break the goals up into shorter distances.
|
||
=====================
|
||
*/
|
||
float idAI::TravelDistance( const idVec3 &start, const idVec3 &end ) const {
|
||
int fromArea;
|
||
int toArea;
|
||
float dist;
|
||
idVec2 delta;
|
||
|
||
if ( !aas ) {
|
||
// no aas, so just take the straight line distance
|
||
delta = end.ToVec2() - start.ToVec2();
|
||
dist = delta.LengthFast();
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
|
||
gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
|
||
}
|
||
|
||
return dist;
|
||
}
|
||
|
||
fromArea = PointReachableAreaNum( start );
|
||
toArea = PointReachableAreaNum( end );
|
||
|
||
if ( !fromArea || !toArea ) {
|
||
// can't seem to get there
|
||
return -1;
|
||
}
|
||
|
||
if ( fromArea == toArea ) {
|
||
// same area, so just take the straight line distance
|
||
delta = end.ToVec2() - start.ToVec2();
|
||
dist = delta.LengthFast();
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorBlue, start, end, gameLocal.msec, false );
|
||
gameRenderWorld->DrawText( va( "%d", ( int )dist ), ( start + end ) * 0.5f, 0.1f, colorWhite, gameLocal.GetLocalPlayer()->viewAngles.ToMat3() );
|
||
}
|
||
|
||
return dist;
|
||
}
|
||
|
||
idReachability *reach;
|
||
int travelTime;
|
||
if ( !aas->RouteToGoalArea( fromArea, start, toArea, travelFlags, travelTime, &reach ) ) {
|
||
return -1;
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
aas->ShowFlyPath( start, toArea, end );
|
||
} else {
|
||
aas->ShowWalkPath( start, toArea, end );
|
||
}
|
||
}
|
||
|
||
return travelTime;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::StopMove
|
||
=====================
|
||
*/
|
||
void idAI::StopMove( moveStatus_t status ) {
|
||
AI_MOVE_DONE = true;
|
||
AI_FORWARD = false;
|
||
move.moveCommand = MOVE_NONE;
|
||
move.moveStatus = status;
|
||
move.toAreaNum = 0;
|
||
move.goalEntity = NULL;
|
||
move.moveDest = physicsObj.GetOrigin();
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_OBSTACLE_IN_PATH = false;
|
||
AI_BLOCKED = false;
|
||
move.startTime = gameLocal.time;
|
||
move.duration = 0;
|
||
move.range = 0.0f;
|
||
move.speed = 0.0f;
|
||
move.anim = 0;
|
||
move.moveDir.Zero();
|
||
move.lastMoveOrigin.Zero();
|
||
move.lastMoveTime = gameLocal.time;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::FaceEnemy
|
||
|
||
Continually face the enemy's last known position. MoveDone is always true in this case.
|
||
=====================
|
||
*/
|
||
bool idAI::FaceEnemy( void ) {
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
if ( !enemyEnt ) {
|
||
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
||
return false;
|
||
}
|
||
|
||
TurnToward( lastVisibleEnemyPos );
|
||
move.goalEntity = enemyEnt;
|
||
move.moveDest = physicsObj.GetOrigin();
|
||
move.moveCommand = MOVE_FACE_ENEMY;
|
||
move.moveStatus = MOVE_STATUS_WAITING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = 0.0f;
|
||
AI_MOVE_DONE = true;
|
||
AI_FORWARD = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::FaceEntity
|
||
|
||
Continually face the entity position. MoveDone is always true in this case.
|
||
=====================
|
||
*/
|
||
bool idAI::FaceEntity( idEntity *ent ) {
|
||
if ( !ent ) {
|
||
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
||
return false;
|
||
}
|
||
|
||
idVec3 entityOrg = ent->GetPhysics()->GetOrigin();
|
||
TurnToward( entityOrg );
|
||
move.goalEntity = ent;
|
||
move.moveDest = physicsObj.GetOrigin();
|
||
move.moveCommand = MOVE_FACE_ENTITY;
|
||
move.moveStatus = MOVE_STATUS_WAITING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = 0.0f;
|
||
AI_MOVE_DONE = true;
|
||
AI_FORWARD = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::DirectMoveToPosition
|
||
=====================
|
||
*/
|
||
bool idAI::DirectMoveToPosition( const idVec3 &pos ) {
|
||
if ( ReachedPos( pos, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
return true;
|
||
}
|
||
|
||
move.moveDest = pos;
|
||
move.goalEntity = NULL;
|
||
move.moveCommand = MOVE_TO_POSITION_DIRECT;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = fly_speed;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
idVec3 dir = pos - physicsObj.GetOrigin();
|
||
dir.Normalize();
|
||
dir *= fly_speed;
|
||
physicsObj.SetLinearVelocity( dir );
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveToEnemyHeight
|
||
=====================
|
||
*/
|
||
bool idAI::MoveToEnemyHeight( void ) {
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
|
||
if ( !enemyEnt || ( move.moveType != MOVETYPE_FLY ) ) {
|
||
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
||
return false;
|
||
}
|
||
|
||
move.moveDest.z = lastVisibleEnemyPos.z + enemyEnt->EyeOffset().z + fly_offset;
|
||
move.goalEntity = enemyEnt;
|
||
move.moveCommand = MOVE_TO_ENEMYHEIGHT;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = 0.0f;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveToEnemy
|
||
=====================
|
||
*/
|
||
bool idAI::MoveToEnemy( void ) {
|
||
int areaNum;
|
||
aasPath_t path;
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
|
||
if ( !enemyEnt ) {
|
||
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
||
return false;
|
||
}
|
||
|
||
if ( ReachedPos( lastVisibleReachableEnemyPos, MOVE_TO_ENEMY ) ) {
|
||
if ( !ReachedPos( lastVisibleEnemyPos, MOVE_TO_ENEMY ) || !AI_ENEMY_VISIBLE ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
StopMove( MOVE_STATUS_DONE );
|
||
return true;
|
||
}
|
||
|
||
idVec3 pos = lastVisibleReachableEnemyPos;
|
||
|
||
move.toAreaNum = 0;
|
||
if ( aas ) {
|
||
move.toAreaNum = PointReachableAreaNum( pos );
|
||
aas->PushPointIntoAreaNum( move.toAreaNum, pos );
|
||
|
||
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
||
if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( !move.toAreaNum ) {
|
||
// if only trying to update the enemy position
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
if ( !aas ) {
|
||
// keep the move destination up to date for wandering
|
||
move.moveDest = pos;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if ( !NewWanderDir( pos ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( move.moveCommand != MOVE_TO_ENEMY ) {
|
||
move.moveCommand = MOVE_TO_ENEMY;
|
||
move.startTime = gameLocal.time;
|
||
}
|
||
|
||
move.moveDest = pos;
|
||
move.goalEntity = enemyEnt;
|
||
move.speed = fly_speed;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveToEntity
|
||
=====================
|
||
*/
|
||
bool idAI::MoveToEntity( idEntity *ent ) {
|
||
int areaNum;
|
||
aasPath_t path;
|
||
idVec3 pos;
|
||
|
||
if ( !ent ) {
|
||
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
||
return false;
|
||
}
|
||
|
||
pos = ent->GetPhysics()->GetOrigin();
|
||
if ( ( move.moveType != MOVETYPE_FLY ) && ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntityOrigin != pos ) ) ) {
|
||
ent->GetFloorPos( 64.0f, pos );
|
||
}
|
||
|
||
if ( ReachedPos( pos, MOVE_TO_ENTITY ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
return true;
|
||
}
|
||
|
||
move.toAreaNum = 0;
|
||
if ( aas ) {
|
||
move.toAreaNum = PointReachableAreaNum( pos );
|
||
aas->PushPointIntoAreaNum( move.toAreaNum, pos );
|
||
|
||
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
||
if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, pos ) ) {
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( !move.toAreaNum ) {
|
||
// if only trying to update the entity position
|
||
if ( move.moveCommand == MOVE_TO_ENTITY ) {
|
||
if ( !aas ) {
|
||
// keep the move destination up to date for wandering
|
||
move.moveDest = pos;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if ( !NewWanderDir( pos ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( ( move.moveCommand != MOVE_TO_ENTITY ) || ( move.goalEntity.GetEntity() != ent ) ) {
|
||
move.startTime = gameLocal.time;
|
||
move.goalEntity = ent;
|
||
move.moveCommand = MOVE_TO_ENTITY;
|
||
}
|
||
|
||
move.moveDest = pos;
|
||
move.goalEntityOrigin = ent->GetPhysics()->GetOrigin();
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.speed = fly_speed;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveOutOfRange
|
||
=====================
|
||
*/
|
||
bool idAI::MoveOutOfRange( idEntity *ent, float range ) {
|
||
int areaNum;
|
||
aasObstacle_t obstacle;
|
||
aasGoal_t goal;
|
||
idBounds bounds;
|
||
idVec3 pos;
|
||
|
||
if ( !aas || !ent ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
areaNum = PointReachableAreaNum( org );
|
||
|
||
// consider the entity the monster is getting close to as an obstacle
|
||
obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
|
||
|
||
if ( ent == enemy.GetEntity() ) {
|
||
pos = lastVisibleEnemyPos;
|
||
} else {
|
||
pos = ent->GetPhysics()->GetOrigin();
|
||
}
|
||
|
||
idAASFindAreaOutOfRange findGoal( pos, range );
|
||
if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
if ( ReachedPos( goal.origin, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
return true;
|
||
}
|
||
|
||
move.moveDest = goal.origin;
|
||
move.toAreaNum = goal.areaNum;
|
||
move.goalEntity = ent;
|
||
move.moveCommand = MOVE_OUT_OF_RANGE;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.range = range;
|
||
move.speed = fly_speed;
|
||
move.startTime = gameLocal.time;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveToAttackPosition
|
||
=====================
|
||
*/
|
||
bool idAI::MoveToAttackPosition( idEntity *ent, int attack_anim ) {
|
||
int areaNum;
|
||
aasObstacle_t obstacle;
|
||
aasGoal_t goal;
|
||
idBounds bounds;
|
||
idVec3 pos;
|
||
|
||
if ( !aas || !ent ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
areaNum = PointReachableAreaNum( org );
|
||
|
||
// consider the entity the monster is getting close to as an obstacle
|
||
obstacle.absBounds = ent->GetPhysics()->GetAbsBounds();
|
||
|
||
if ( ent == enemy.GetEntity() ) {
|
||
pos = lastVisibleEnemyPos;
|
||
} else {
|
||
pos = ent->GetPhysics()->GetOrigin();
|
||
}
|
||
|
||
idAASFindAttackPosition findGoal( this, physicsObj.GetGravityAxis(), ent, pos, missileLaunchOffset[ attack_anim ] );
|
||
if ( !aas->FindNearestGoal( goal, areaNum, org, pos, travelFlags, &obstacle, 1, findGoal ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
move.moveDest = goal.origin;
|
||
move.toAreaNum = goal.areaNum;
|
||
move.goalEntity = ent;
|
||
move.moveCommand = MOVE_TO_ATTACK_POSITION;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.speed = fly_speed;
|
||
move.startTime = gameLocal.time;
|
||
move.anim = attack_anim;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveToPosition
|
||
=====================
|
||
*/
|
||
bool idAI::MoveToPosition( const idVec3 &pos ) {
|
||
idVec3 org;
|
||
int areaNum;
|
||
aasPath_t path;
|
||
|
||
if ( ReachedPos( pos, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
return true;
|
||
}
|
||
|
||
org = pos;
|
||
move.toAreaNum = 0;
|
||
if ( aas ) {
|
||
move.toAreaNum = PointReachableAreaNum( org );
|
||
aas->PushPointIntoAreaNum( move.toAreaNum, org );
|
||
|
||
areaNum = PointReachableAreaNum( physicsObj.GetOrigin() );
|
||
if ( !PathToGoal( path, areaNum, physicsObj.GetOrigin(), move.toAreaNum, org ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( !move.toAreaNum && !NewWanderDir( org ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
move.moveDest = org;
|
||
move.goalEntity = NULL;
|
||
move.moveCommand = MOVE_TO_POSITION;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = fly_speed;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveToCover
|
||
=====================
|
||
*/
|
||
bool idAI::MoveToCover( idEntity *entity, const idVec3 &hideFromPos ) {
|
||
int areaNum;
|
||
aasObstacle_t obstacle;
|
||
aasGoal_t hideGoal;
|
||
idBounds bounds;
|
||
|
||
if ( !aas || !entity ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
areaNum = PointReachableAreaNum( org );
|
||
|
||
// consider the entity the monster tries to hide from as an obstacle
|
||
obstacle.absBounds = entity->GetPhysics()->GetAbsBounds();
|
||
|
||
idAASFindCover findCover( hideFromPos );
|
||
if ( !aas->FindNearestGoal( hideGoal, areaNum, org, hideFromPos, travelFlags, &obstacle, 1, findCover ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
if ( ReachedPos( hideGoal.origin, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
return true;
|
||
}
|
||
|
||
move.moveDest = hideGoal.origin;
|
||
move.toAreaNum = hideGoal.areaNum;
|
||
move.goalEntity = entity;
|
||
move.moveCommand = MOVE_TO_COVER;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = fly_speed;
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SlideToPosition
|
||
=====================
|
||
*/
|
||
bool idAI::SlideToPosition( const idVec3 &pos, float time ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
|
||
move.moveDest = pos;
|
||
move.goalEntity = NULL;
|
||
move.moveCommand = MOVE_SLIDE_TO_POSITION;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.startTime = gameLocal.time;
|
||
move.duration = idPhysics::SnapTimeToPhysicsFrame( SEC2MS( time ) );
|
||
AI_MOVE_DONE = false;
|
||
AI_DEST_UNREACHABLE = false;
|
||
AI_FORWARD = false;
|
||
|
||
if ( move.duration > 0 ) {
|
||
move.moveDir = ( pos - physicsObj.GetOrigin() ) / MS2SEC( move.duration );
|
||
if ( move.moveType != MOVETYPE_FLY ) {
|
||
move.moveDir.z = 0.0f;
|
||
}
|
||
move.speed = move.moveDir.LengthFast();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::WanderAround
|
||
=====================
|
||
*/
|
||
bool idAI::WanderAround( void ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
|
||
move.moveDest = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
|
||
|
||
//ivan start - lock the X position - don't try to wander around
|
||
if( isXlocked ){
|
||
move.moveDest.x = fastXpos;
|
||
}
|
||
//ivan end
|
||
|
||
if ( !NewWanderDir( move.moveDest ) ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
return false;
|
||
}
|
||
|
||
move.moveCommand = MOVE_WANDER;
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
move.startTime = gameLocal.time;
|
||
move.speed = fly_speed;
|
||
AI_MOVE_DONE = false;
|
||
AI_FORWARD = true;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::MoveDone
|
||
=====================
|
||
*/
|
||
bool idAI::MoveDone( void ) const {
|
||
return ( move.moveCommand == MOVE_NONE );
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::StepDirection
|
||
================
|
||
*/
|
||
bool idAI::StepDirection( float dir ) {
|
||
predictedPath_t path;
|
||
idVec3 org;
|
||
|
||
move.wanderYaw = dir;
|
||
move.moveDir = idAngles( 0, move.wanderYaw, 0 ).ToForward();
|
||
|
||
org = physicsObj.GetOrigin();
|
||
|
||
idAI::PredictPath( this, aas, org, move.moveDir * 48.0f, 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
|
||
|
||
if ( path.blockingEntity && ( ( move.moveCommand == MOVE_TO_ENEMY ) || ( move.moveCommand == MOVE_TO_ENTITY ) ) && ( path.blockingEntity == move.goalEntity.GetEntity() ) ) {
|
||
// don't report being blocked if we ran into our goal entity
|
||
return true;
|
||
}
|
||
|
||
if ( ( move.moveType == MOVETYPE_FLY ) && ( path.endEvent == SE_BLOCKED ) ) {
|
||
float z;
|
||
|
||
move.moveDir = path.endVelocity * 1.0f / 48.0f;
|
||
|
||
// trace down to the floor and see if we can go forward
|
||
idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, -1024.0f ), 1000, 1000, SE_BLOCKED, path );
|
||
|
||
idVec3 floorPos = path.endPos;
|
||
idAI::PredictPath( this, aas, floorPos, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
|
||
if ( !path.endEvent ) {
|
||
move.moveDir.z = -1.0f;
|
||
return true;
|
||
}
|
||
|
||
// trace up to see if we can go over something and go forward
|
||
idAI::PredictPath( this, aas, org, idVec3( 0.0f, 0.0f, 256.0f ), 1000, 1000, SE_BLOCKED, path );
|
||
|
||
idVec3 ceilingPos = path.endPos;
|
||
|
||
for( z = org.z; z <= ceilingPos.z + 64.0f; z += 64.0f ) {
|
||
idVec3 start;
|
||
if ( z <= ceilingPos.z ) {
|
||
start.x = org.x;
|
||
start.y = org.y;
|
||
start.z = z;
|
||
} else {
|
||
start = ceilingPos;
|
||
}
|
||
idAI::PredictPath( this, aas, start, move.moveDir * 48.0f, 1000, 1000, SE_BLOCKED, path );
|
||
if ( !path.endEvent ) {
|
||
move.moveDir.z = 1.0f;
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
return ( path.endEvent == 0 );
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::NewWanderDir
|
||
================
|
||
*/
|
||
bool idAI::NewWanderDir( const idVec3 &dest ) {
|
||
|
||
float deltax, deltay;
|
||
float d[ 3 ];
|
||
float tdir, olddir, turnaround;
|
||
|
||
//ivan start
|
||
if( isXlocked ){
|
||
//gameLocal.Printf("idAI::NewWanderDir : current_yaw %f \n", current_yaw);
|
||
if(current_yaw > 0 && current_yaw < 180){ //case about 90 --> turn to -90
|
||
turnaround = idMath::AngleNormalize360( -90 );
|
||
}else{ //case about -90 --> turn to 90
|
||
turnaround = idMath::AngleNormalize360( 90 );
|
||
}
|
||
}else{
|
||
//ivan end
|
||
move.nextWanderTime = gameLocal.time + ( gameLocal.random.RandomFloat() * 500 + 500 );
|
||
|
||
olddir = idMath::AngleNormalize360( ( int )( current_yaw / 45 ) * 45 );
|
||
|
||
turnaround = idMath::AngleNormalize360( olddir - 180 );
|
||
|
||
idVec3 org = physicsObj.GetOrigin();
|
||
deltax = dest.x - org.x;
|
||
deltay = dest.y - org.y;
|
||
|
||
if ( deltax > 10 ) {
|
||
d[ 1 ]= 0;
|
||
} else if ( deltax < -10 ) {
|
||
d[ 1 ] = 180;
|
||
} else {
|
||
d[ 1 ] = DI_NODIR;
|
||
}
|
||
|
||
|
||
if ( deltay < -10 ) {
|
||
d[ 2 ] = 270;
|
||
} else if ( deltay > 10 ) {
|
||
d[ 2 ] = 90;
|
||
} else {
|
||
d[ 2 ] = DI_NODIR;
|
||
}
|
||
|
||
// try direct route
|
||
if ( d[ 1 ] != DI_NODIR && d[ 2 ] != DI_NODIR ) {
|
||
if ( d[ 1 ] == 0 ) {
|
||
tdir = d[ 2 ] == 90 ? 45 : 315;
|
||
} else {
|
||
tdir = d[ 2 ] == 90 ? 135 : 215;
|
||
}
|
||
|
||
if ( tdir != turnaround && StepDirection( tdir ) ) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// try other directions
|
||
if ( ( gameLocal.random.RandomInt() & 1 ) || idMath::Fabs( deltay ) > idMath::Fabs( deltax ) ) {
|
||
tdir = d[ 1 ];
|
||
d[ 1 ] = d[ 2 ];
|
||
d[ 2 ] = tdir;
|
||
}
|
||
|
||
if ( d[ 1 ] != DI_NODIR && d[ 1 ] != turnaround && StepDirection( d[1] ) ) {
|
||
return true;
|
||
}
|
||
|
||
if ( d[ 2 ] != DI_NODIR && d[ 2 ] != turnaround && StepDirection( d[ 2 ] ) ) {
|
||
return true;
|
||
}
|
||
|
||
// there is no direct path to the player, so pick another direction
|
||
if ( olddir != DI_NODIR && StepDirection( olddir ) ) {
|
||
return true;
|
||
}
|
||
|
||
// randomly determine direction of search
|
||
if ( gameLocal.random.RandomInt() & 1 ) {
|
||
for( tdir = 0; tdir <= 315; tdir += 45 ) {
|
||
if ( tdir != turnaround && StepDirection( tdir ) ) {
|
||
return true;
|
||
}
|
||
}
|
||
} else {
|
||
for ( tdir = 315; tdir >= 0; tdir -= 45 ) {
|
||
if ( tdir != turnaround && StepDirection( tdir ) ) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
} //ivan
|
||
|
||
if ( turnaround != DI_NODIR && StepDirection( turnaround ) ) { //just try turnaround
|
||
return true;
|
||
}
|
||
|
||
// can't move
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::GetMovePos
|
||
=====================
|
||
*/
|
||
bool idAI::GetMovePos( idVec3 &seekPos ) {
|
||
int areaNum;
|
||
aasPath_t path;
|
||
bool result;
|
||
idVec3 org;
|
||
|
||
org = physicsObj.GetOrigin();
|
||
seekPos = org;
|
||
|
||
switch( move.moveCommand ) {
|
||
case MOVE_NONE :
|
||
seekPos = move.moveDest;
|
||
return false;
|
||
break;
|
||
|
||
case MOVE_FACE_ENEMY :
|
||
case MOVE_FACE_ENTITY :
|
||
seekPos = move.moveDest;
|
||
return false;
|
||
break;
|
||
|
||
case MOVE_TO_POSITION_DIRECT :
|
||
seekPos = move.moveDest;
|
||
if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
}
|
||
return false;
|
||
break;
|
||
|
||
case MOVE_SLIDE_TO_POSITION :
|
||
seekPos = org;
|
||
return false;
|
||
break;
|
||
}
|
||
|
||
if ( move.moveCommand == MOVE_TO_ENTITY ) {
|
||
MoveToEntity( move.goalEntity.GetEntity() );
|
||
}
|
||
|
||
move.moveStatus = MOVE_STATUS_MOVING;
|
||
result = false;
|
||
if ( gameLocal.time > move.blockTime ) {
|
||
if ( move.moveCommand == MOVE_WANDER ) {
|
||
move.moveDest = org + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 256.0f;
|
||
} else {
|
||
if ( ReachedPos( move.moveDest, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
seekPos = org;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ( aas && move.toAreaNum ) {
|
||
areaNum = PointReachableAreaNum( org );
|
||
if ( PathToGoal( path, areaNum, org, move.toAreaNum, move.moveDest ) ) {
|
||
seekPos = path.moveGoal;
|
||
result = true;
|
||
move.nextWanderTime = 0;
|
||
} else {
|
||
AI_DEST_UNREACHABLE = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( !result ) {
|
||
// wander around
|
||
if ( ( gameLocal.time > move.nextWanderTime ) || !StepDirection( move.wanderYaw ) ) {
|
||
result = NewWanderDir( move.moveDest );
|
||
if ( !result ) {
|
||
StopMove( MOVE_STATUS_DEST_UNREACHABLE );
|
||
AI_DEST_UNREACHABLE = true;
|
||
seekPos = org;
|
||
return false;
|
||
}
|
||
} else {
|
||
result = true;
|
||
}
|
||
|
||
seekPos = org + move.moveDir * 2048.0f;
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorYellow, org, seekPos, gameLocal.msec, true );
|
||
}
|
||
} else {
|
||
AI_DEST_UNREACHABLE = false;
|
||
}
|
||
|
||
if ( result && ( ai_debugMove.GetBool() ) ) {
|
||
gameRenderWorld->DebugLine( colorCyan, physicsObj.GetOrigin(), seekPos );
|
||
}
|
||
|
||
if( isXlocked ){
|
||
seekPos.x = fastXpos; //ivan - make sure goalPos.x = fastXpos just in case result == true.
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::EntityCanSeePos
|
||
=====================
|
||
*/
|
||
bool idAI::EntityCanSeePos( idActor *actor, const idVec3 &actorOrigin, const idVec3 &pos ) {
|
||
idVec3 eye, point;
|
||
trace_t results;
|
||
pvsHandle_t handle;
|
||
|
||
handle = gameLocal.pvs.SetupCurrentPVS( actor->GetPVSAreas(), actor->GetNumPVSAreas() );
|
||
|
||
if ( !gameLocal.pvs.InCurrentPVS( handle, GetPVSAreas(), GetNumPVSAreas() ) ) {
|
||
gameLocal.pvs.FreeCurrentPVS( handle );
|
||
return false;
|
||
}
|
||
|
||
gameLocal.pvs.FreeCurrentPVS( handle );
|
||
|
||
eye = actorOrigin + actor->EyeOffset();
|
||
|
||
point = pos;
|
||
point[2] += 1.0f;
|
||
|
||
physicsObj.DisableClip();
|
||
|
||
gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
|
||
if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
|
||
physicsObj.EnableClip();
|
||
return true;
|
||
}
|
||
|
||
const idBounds &bounds = physicsObj.GetBounds();
|
||
point[2] += bounds[1][2] - bounds[0][2];
|
||
|
||
gameLocal.clip.TracePoint( results, eye, point, MASK_SOLID, actor );
|
||
physicsObj.EnableClip();
|
||
if ( results.fraction >= 1.0f || ( gameLocal.GetTraceEntity( results ) == this ) ) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::BlockedFailSafe
|
||
=====================
|
||
*/
|
||
void idAI::BlockedFailSafe( void ) {
|
||
if ( !ai_blockedFailSafe.GetBool() || blockedRadius < 0.0f ) {
|
||
return;
|
||
}
|
||
if ( !physicsObj.OnGround() || enemy.GetEntity() == NULL ||
|
||
( physicsObj.GetOrigin() - move.lastMoveOrigin ).LengthSqr() > Square( blockedRadius ) ) {
|
||
move.lastMoveOrigin = physicsObj.GetOrigin();
|
||
move.lastMoveTime = gameLocal.time;
|
||
}
|
||
if ( move.lastMoveTime < gameLocal.time - blockedMoveTime ) {
|
||
if ( lastAttackTime < gameLocal.time - blockedAttackTime ) {
|
||
AI_BLOCKED = true;
|
||
move.lastMoveTime = gameLocal.time;
|
||
}
|
||
}
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
turning
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
=====================
|
||
idAI::Turn
|
||
=====================
|
||
*/
|
||
void idAI::Turn( void ) {
|
||
float diff;
|
||
float diff2;
|
||
float turnAmount;
|
||
animFlags_t animflags;
|
||
|
||
if ( !turnRate ) {
|
||
return;
|
||
}
|
||
|
||
// check if the animator has marker this anim as non-turning
|
||
if ( !legsAnim.Disabled() && !legsAnim.AnimDone( 0 ) ) {
|
||
animflags = legsAnim.GetAnimFlags();
|
||
} else {
|
||
animflags = torsoAnim.GetAnimFlags();
|
||
}
|
||
if ( animflags.ai_no_turn ) {
|
||
return;
|
||
}
|
||
|
||
if ( anim_turn_angles && animflags.anim_turn ) {
|
||
idMat3 rotateAxis;
|
||
|
||
// set the blend between no turn and full turn
|
||
float frac = anim_turn_amount / anim_turn_angles;
|
||
animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, 1.0f - frac );
|
||
animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, frac );
|
||
animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, 1.0f - frac );
|
||
animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, frac );
|
||
|
||
// get the total rotation from the start of the anim
|
||
animator.GetDeltaRotation( 0, gameLocal.time, rotateAxis );
|
||
current_yaw = idMath::AngleNormalize180( anim_turn_yaw + rotateAxis[ 0 ].ToYaw() );
|
||
} else {
|
||
diff = idMath::AngleNormalize180( ideal_yaw - current_yaw );
|
||
turnVel += AI_TURN_SCALE * diff * MS2SEC( gameLocal.msec );
|
||
if ( turnVel > turnRate ) {
|
||
turnVel = turnRate;
|
||
} else if ( turnVel < -turnRate ) {
|
||
turnVel = -turnRate;
|
||
}
|
||
turnAmount = turnVel * MS2SEC( gameLocal.msec );
|
||
if ( ( diff >= 0.0f ) && ( turnAmount >= diff ) ) {
|
||
turnVel = diff / MS2SEC( gameLocal.msec );
|
||
turnAmount = diff;
|
||
} else if ( ( diff <= 0.0f ) && ( turnAmount <= diff ) ) {
|
||
turnVel = diff / MS2SEC( gameLocal.msec );
|
||
turnAmount = diff;
|
||
}
|
||
current_yaw += turnAmount;
|
||
current_yaw = idMath::AngleNormalize180( current_yaw );
|
||
diff2 = idMath::AngleNormalize180( ideal_yaw - current_yaw );
|
||
if ( idMath::Fabs( diff2 ) < 0.1f ) {
|
||
current_yaw = ideal_yaw;
|
||
}
|
||
}
|
||
|
||
viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
gameRenderWorld->DebugLine( colorRed, org, org + idAngles( 0, ideal_yaw, 0 ).ToForward() * 64, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorGreen, org, org + idAngles( 0, current_yaw, 0 ).ToForward() * 48, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorYellow, org, org + idAngles( 0, current_yaw + turnVel, 0 ).ToForward() * 32, gameLocal.msec );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::FacingIdeal
|
||
=====================
|
||
*/
|
||
bool idAI::FacingIdeal( void ) {
|
||
float diff;
|
||
|
||
if ( !turnRate ) {
|
||
return true;
|
||
}
|
||
|
||
diff = idMath::AngleNormalize180( current_yaw - ideal_yaw );
|
||
if ( idMath::Fabs( diff ) < 0.01f ) {
|
||
// force it to be exact
|
||
current_yaw = ideal_yaw;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TurnToward
|
||
=====================
|
||
*/
|
||
bool idAI::TurnToward( float yaw ) {
|
||
ideal_yaw = idMath::AngleNormalize180( yaw );
|
||
bool result = FacingIdeal();
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TurnToward
|
||
=====================
|
||
*/
|
||
bool idAI::TurnToward( const idVec3 &pos ) {
|
||
idVec3 dir;
|
||
idVec3 local_dir;
|
||
float lengthSqr;
|
||
|
||
dir = pos - physicsObj.GetOrigin();
|
||
physicsObj.GetGravityAxis().ProjectVector( dir, local_dir );
|
||
local_dir.z = 0.0f;
|
||
lengthSqr = local_dir.LengthSqr();
|
||
if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) {
|
||
ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() );
|
||
}
|
||
|
||
bool result = FacingIdeal();
|
||
return result;
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
Movement
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
================
|
||
idAI::ApplyImpulse
|
||
================
|
||
*/
|
||
void idAI::ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ) {
|
||
// FIXME: Jim take a look at this and see if this is a reasonable thing to do
|
||
// instead of a spawnArg flag.. Sabaoth is the only slide monster ( and should be the only one for D3 )
|
||
// and we don't want him taking physics impulses as it can knock him off the path
|
||
|
||
//ivan start
|
||
if( noPush ) return;
|
||
//ivan end
|
||
|
||
if ( move.moveType != MOVETYPE_STATIC && move.moveType != MOVETYPE_SLIDE ) {
|
||
idActor::ApplyImpulse( ent, id, point, impulse );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::GetMoveDelta
|
||
=====================
|
||
*/
|
||
void idAI::GetMoveDelta( const idMat3 &oldaxis, const idMat3 &axis, idVec3 &delta ) {
|
||
idVec3 oldModelOrigin;
|
||
idVec3 modelOrigin;
|
||
|
||
animator.GetDelta( gameLocal.time - gameLocal.msec, gameLocal.time, delta );
|
||
delta = axis * delta;
|
||
|
||
if ( modelOffset != vec3_zero ) {
|
||
// the pivot of the monster's model is around its origin, and not around the bounding
|
||
// box's origin, so we have to compensate for this when the model is offset so that
|
||
// the monster still appears to rotate around it's origin.
|
||
oldModelOrigin = modelOffset * oldaxis;
|
||
modelOrigin = modelOffset * axis;
|
||
delta += oldModelOrigin - modelOrigin;
|
||
}
|
||
|
||
delta *= physicsObj.GetGravityAxis();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::CheckObstacleAvoidance
|
||
=====================
|
||
*/
|
||
void idAI::CheckObstacleAvoidance( const idVec3 &goalPos, idVec3 &newPos ) {
|
||
idEntity *obstacle;
|
||
obstaclePath_t path;
|
||
idVec3 dir;
|
||
float dist;
|
||
bool foundPath;
|
||
|
||
if ( ignore_obstacles ) {
|
||
newPos = goalPos;
|
||
move.obstacle = NULL;
|
||
return;
|
||
}
|
||
|
||
const idVec3 &origin = physicsObj.GetOrigin();
|
||
|
||
obstacle = NULL;
|
||
AI_OBSTACLE_IN_PATH = false;
|
||
foundPath = FindPathAroundObstacles( &physicsObj, aas, enemy.GetEntity(), origin, goalPos, path );
|
||
if ( ai_showObstacleAvoidance.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorBlue, goalPos + idVec3( 1.0f, 1.0f, 0.0f ), goalPos + idVec3( 1.0f, 1.0f, 64.0f ), gameLocal.msec );
|
||
gameRenderWorld->DebugLine( foundPath ? colorYellow : colorRed, path.seekPos, path.seekPos + idVec3( 0.0f, 0.0f, 64.0f ), gameLocal.msec );
|
||
}
|
||
|
||
if ( !foundPath ) {
|
||
// couldn't get around obstacles
|
||
if ( path.firstObstacle ) {
|
||
AI_OBSTACLE_IN_PATH = true;
|
||
if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.firstObstacle->GetPhysics()->GetAbsBounds() ) ) {
|
||
obstacle = path.firstObstacle;
|
||
}
|
||
} else if ( path.startPosObstacle ) {
|
||
AI_OBSTACLE_IN_PATH = true;
|
||
if ( physicsObj.GetAbsBounds().Expand( 2.0f ).IntersectsBounds( path.startPosObstacle->GetPhysics()->GetAbsBounds() ) ) {
|
||
obstacle = path.startPosObstacle;
|
||
}
|
||
} else {
|
||
// Blocked by wall
|
||
move.moveStatus = MOVE_STATUS_BLOCKED_BY_WALL;
|
||
}
|
||
#if 0
|
||
} else if ( path.startPosObstacle ) {
|
||
// check if we're past where the our origin was pushed out of the obstacle
|
||
dir = goalPos - origin;
|
||
dir.Normalize();
|
||
dist = ( path.seekPos - origin ) * dir;
|
||
if ( dist < 1.0f ) {
|
||
AI_OBSTACLE_IN_PATH = true;
|
||
obstacle = path.startPosObstacle;
|
||
}
|
||
#endif
|
||
} else if ( path.seekPosObstacle ) {
|
||
// if the AI is very close to the path.seekPos already and path.seekPosObstacle != NULL
|
||
// then we want to push the path.seekPosObstacle entity out of the way
|
||
AI_OBSTACLE_IN_PATH = true;
|
||
|
||
// check if we're past where the goalPos was pushed out of the obstacle
|
||
dir = goalPos - origin;
|
||
dir.Normalize();
|
||
dist = ( path.seekPos - origin ) * dir;
|
||
if ( dist < 1.0f ) {
|
||
obstacle = path.seekPosObstacle;
|
||
}
|
||
}
|
||
|
||
// if we had an obstacle, set our move status based on the type, and kick it out of the way if it's a moveable
|
||
if ( obstacle ) {
|
||
if ( obstacle->IsType( idActor::Type ) ) {
|
||
// monsters aren't kickable
|
||
if ( obstacle == enemy.GetEntity() ) {
|
||
move.moveStatus = MOVE_STATUS_BLOCKED_BY_ENEMY;
|
||
} else {
|
||
move.moveStatus = MOVE_STATUS_BLOCKED_BY_MONSTER;
|
||
}
|
||
} else {
|
||
// try kicking the object out of the way
|
||
move.moveStatus = MOVE_STATUS_BLOCKED_BY_OBJECT;
|
||
}
|
||
newPos = obstacle->GetPhysics()->GetOrigin();
|
||
//newPos = path.seekPos;
|
||
move.obstacle = obstacle;
|
||
} else {
|
||
newPos = path.seekPos;
|
||
move.obstacle = NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::DeadMove
|
||
=====================
|
||
*/
|
||
void idAI::DeadMove( void ) {
|
||
idVec3 delta;
|
||
|
||
GetMoveDelta( viewAxis, viewAxis, delta );
|
||
physicsObj.SetDelta( delta );
|
||
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
|
||
AI_ONGROUND = physicsObj.OnGround();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::AnimMove
|
||
=====================
|
||
*/
|
||
void idAI::AnimMove( void ) {
|
||
idVec3 goalPos;
|
||
idVec3 delta;
|
||
idVec3 goalDelta;
|
||
float goalDist;
|
||
idVec3 newDest;
|
||
|
||
idVec3 oldorigin = physicsObj.GetOrigin();
|
||
idMat3 oldaxis = viewAxis;
|
||
|
||
AI_BLOCKED = false;
|
||
|
||
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
|
||
move.lastMoveOrigin.Zero();
|
||
move.lastMoveTime = gameLocal.time;
|
||
}
|
||
|
||
move.obstacle = NULL;
|
||
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
|
||
TurnToward( lastVisibleEnemyPos );
|
||
goalPos = oldorigin;
|
||
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
||
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
||
goalPos = oldorigin;
|
||
} else if ( GetMovePos( goalPos ) ) {
|
||
if ( move.moveCommand != MOVE_WANDER ) {
|
||
CheckObstacleAvoidance( goalPos, newDest );
|
||
TurnToward( newDest );
|
||
} else {
|
||
TurnToward( goalPos );
|
||
}
|
||
}
|
||
|
||
Turn();
|
||
|
||
//ivan start
|
||
if( AI_INHIBIT_MOVE ){
|
||
delta.Zero();
|
||
}else
|
||
//ivan end
|
||
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
|
||
if ( gameLocal.time < move.startTime + move.duration ) {
|
||
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
|
||
delta = goalPos - oldorigin;
|
||
delta.z = 0.0f;
|
||
} else {
|
||
delta = move.moveDest - oldorigin;
|
||
delta.z = 0.0f;
|
||
StopMove( MOVE_STATUS_DONE );
|
||
}
|
||
} else if ( allowMove ) {
|
||
GetMoveDelta( oldaxis, viewAxis, delta );
|
||
} else {
|
||
delta.Zero();
|
||
}
|
||
|
||
if ( move.moveCommand == MOVE_TO_POSITION ) {
|
||
goalDelta = move.moveDest - oldorigin;
|
||
goalDist = goalDelta.LengthFast();
|
||
if ( goalDist < delta.LengthFast() ) {
|
||
delta = goalDelta;
|
||
}
|
||
}
|
||
|
||
physicsObj.UseFlyMove( false ); //ivan - from D3XP
|
||
|
||
physicsObj.SetDelta( delta );
|
||
physicsObj.ForceDeltaMove( disableGravity );
|
||
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
|
||
}
|
||
|
||
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
|
||
DirectDamage( attack, enemy.GetEntity() );
|
||
} else {
|
||
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
|
||
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
|
||
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
|
||
}
|
||
}
|
||
|
||
BlockedFailSafe();
|
||
|
||
AI_ONGROUND = physicsObj.OnGround();
|
||
|
||
idVec3 org = physicsObj.GetOrigin();
|
||
if ( oldorigin != org ) {
|
||
TouchTriggers();
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
|
||
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
|
||
DrawRoute();
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
Seek
|
||
=====================
|
||
*/
|
||
idVec3 Seek( idVec3 &vel, const idVec3 &org, const idVec3 &goal, float prediction ) {
|
||
idVec3 predictedPos;
|
||
idVec3 goalDelta;
|
||
idVec3 seekVel;
|
||
|
||
// predict our position
|
||
predictedPos = org + vel * prediction;
|
||
goalDelta = goal - predictedPos;
|
||
seekVel = goalDelta * MS2SEC( gameLocal.msec );
|
||
|
||
return seekVel;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SlideMove
|
||
=====================
|
||
*/
|
||
void idAI::SlideMove( void ) {
|
||
idVec3 goalPos;
|
||
idVec3 delta;
|
||
idVec3 goalDelta;
|
||
float goalDist;
|
||
idVec3 newDest;
|
||
|
||
idVec3 oldorigin = physicsObj.GetOrigin();
|
||
|
||
AI_BLOCKED = false;
|
||
|
||
if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
|
||
move.lastMoveOrigin.Zero();
|
||
move.lastMoveTime = gameLocal.time;
|
||
}
|
||
|
||
move.obstacle = NULL;
|
||
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
|
||
TurnToward( lastVisibleEnemyPos );
|
||
goalPos = move.moveDest;
|
||
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
||
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
||
goalPos = move.moveDest;
|
||
} else if ( GetMovePos( goalPos ) ) {
|
||
CheckObstacleAvoidance( goalPos, newDest );
|
||
TurnToward( newDest );
|
||
goalPos = newDest;
|
||
}
|
||
|
||
if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
|
||
if ( gameLocal.time < move.startTime + move.duration ) {
|
||
goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
|
||
} else {
|
||
goalPos = move.moveDest;
|
||
StopMove( MOVE_STATUS_DONE );
|
||
}
|
||
}
|
||
|
||
if ( move.moveCommand == MOVE_TO_POSITION ) {
|
||
goalDelta = move.moveDest - oldorigin;
|
||
goalDist = goalDelta.LengthFast();
|
||
if ( goalDist < delta.LengthFast() ) {
|
||
delta = goalDelta;
|
||
}
|
||
}
|
||
|
||
idVec3 vel = physicsObj.GetLinearVelocity();
|
||
float z = vel.z;
|
||
idVec3 predictedPos = oldorigin + vel * AI_SEEK_PREDICTION;
|
||
|
||
// seek the goal position
|
||
goalDelta = goalPos - predictedPos;
|
||
vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
|
||
vel += goalDelta * MS2SEC( gameLocal.msec );
|
||
|
||
// cap our speed
|
||
vel.Truncate( fly_speed );
|
||
vel.z = z;
|
||
physicsObj.SetLinearVelocity( vel );
|
||
physicsObj.UseVelocityMove( true );
|
||
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
|
||
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
|
||
TurnToward( lastVisibleEnemyPos );
|
||
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
||
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
||
} else if ( move.moveCommand != MOVE_NONE ) {
|
||
if ( vel.ToVec2().LengthSqr() > 0.1f ) {
|
||
TurnToward( vel.ToYaw() );
|
||
}
|
||
}
|
||
Turn();
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
|
||
}
|
||
|
||
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
|
||
DirectDamage( attack, enemy.GetEntity() );
|
||
} else {
|
||
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
|
||
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
|
||
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
|
||
}
|
||
}
|
||
|
||
BlockedFailSafe();
|
||
|
||
AI_ONGROUND = physicsObj.OnGround();
|
||
|
||
idVec3 org = physicsObj.GetOrigin();
|
||
if ( oldorigin != org ) {
|
||
TouchTriggers();
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
|
||
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
|
||
DrawRoute();
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::AdjustFlyingAngles
|
||
=====================
|
||
*/
|
||
void idAI::AdjustFlyingAngles( void ) {
|
||
idVec3 vel;
|
||
float speed;
|
||
float roll;
|
||
float pitch;
|
||
|
||
vel = physicsObj.GetLinearVelocity();
|
||
|
||
speed = vel.Length();
|
||
if ( speed < 5.0f ) {
|
||
roll = 0.0f;
|
||
pitch = 0.0f;
|
||
} else {
|
||
roll = vel * viewAxis[ 1 ] * -fly_roll_scale / fly_speed;
|
||
if ( roll > fly_roll_max ) {
|
||
roll = fly_roll_max;
|
||
} else if ( roll < -fly_roll_max ) {
|
||
roll = -fly_roll_max;
|
||
}
|
||
|
||
pitch = vel * viewAxis[ 2 ] * -fly_pitch_scale / fly_speed;
|
||
if ( pitch > fly_pitch_max ) {
|
||
pitch = fly_pitch_max;
|
||
} else if ( pitch < -fly_pitch_max ) {
|
||
pitch = -fly_pitch_max;
|
||
}
|
||
}
|
||
|
||
fly_roll = fly_roll * 0.95f + roll * 0.05f;
|
||
fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
|
||
|
||
if ( flyTiltJoint != INVALID_JOINT ) {
|
||
animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
|
||
} else {
|
||
viewAxis = idAngles( fly_pitch, current_yaw, fly_roll ).ToMat3();
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::AddFlyBob
|
||
=====================
|
||
*/
|
||
void idAI::AddFlyBob( idVec3 &vel ) {
|
||
idVec3 fly_bob_add;
|
||
float t;
|
||
|
||
if ( fly_bob_strength ) {
|
||
t = MS2SEC( gameLocal.time + entityNumber * 497 );
|
||
fly_bob_add = ( viewAxis[ 1 ] * idMath::Sin16( t * fly_bob_horz ) + viewAxis[ 2 ] * idMath::Sin16( t * fly_bob_vert ) ) * fly_bob_strength;
|
||
vel += fly_bob_add * MS2SEC( gameLocal.msec );
|
||
if ( ai_debugMove.GetBool() ) {
|
||
const idVec3 &origin = physicsObj.GetOrigin();
|
||
gameRenderWorld->DebugArrow( colorOrange, origin, origin + fly_bob_add, 0 );
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::AdjustFlyHeight
|
||
=====================
|
||
*/
|
||
void idAI::AdjustFlyHeight( idVec3 &vel, const idVec3 &goalPos ) {
|
||
const idVec3 &origin = physicsObj.GetOrigin();
|
||
predictedPath_t path;
|
||
idVec3 end;
|
||
idVec3 dest;
|
||
trace_t trace;
|
||
idActor *enemyEnt;
|
||
bool goLower;
|
||
|
||
// make sure we're not flying too high to get through doors
|
||
goLower = false;
|
||
if ( origin.z > goalPos.z ) {
|
||
dest = goalPos;
|
||
dest.z = origin.z + 128.0f;
|
||
idAI::PredictPath( this, aas, goalPos, dest - origin, 1000, 1000, SE_BLOCKED, path );
|
||
if ( path.endPos.z < origin.z ) {
|
||
idVec3 addVel = Seek( vel, origin, path.endPos, AI_SEEK_PREDICTION );
|
||
vel.z += addVel.z;
|
||
goLower = true;
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugBounds( goLower ? colorRed : colorGreen, physicsObj.GetBounds(), path.endPos, gameLocal.msec );
|
||
}
|
||
}
|
||
|
||
if ( !goLower ) {
|
||
// make sure we don't fly too low
|
||
end = origin;
|
||
|
||
enemyEnt = enemy.GetEntity();
|
||
if ( enemyEnt ) {
|
||
end.z = lastVisibleEnemyPos.z + lastVisibleEnemyEyeOffset.z + fly_offset;
|
||
} else {
|
||
// just use the default eye height for the player
|
||
end.z = goalPos.z + DEFAULT_FLY_OFFSET + fly_offset;
|
||
}
|
||
|
||
gameLocal.clip.Translation( trace, origin, end, physicsObj.GetClipModel(), mat3_identity, MASK_MONSTERSOLID, this );
|
||
vel += Seek( vel, origin, trace.endpos, AI_SEEK_PREDICTION );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::FlySeekGoal
|
||
=====================
|
||
*/
|
||
void idAI::FlySeekGoal( idVec3 &vel, idVec3 &goalPos ) {
|
||
idVec3 seekVel;
|
||
|
||
// seek the goal position
|
||
seekVel = Seek( vel, physicsObj.GetOrigin(), goalPos, AI_SEEK_PREDICTION );
|
||
seekVel *= fly_seek_scale;
|
||
vel += seekVel;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::AdjustFlySpeed
|
||
=====================
|
||
*/
|
||
void idAI::AdjustFlySpeed( idVec3 &vel ) {
|
||
float speed;
|
||
|
||
// apply dampening
|
||
vel -= vel * AI_FLY_DAMPENING * MS2SEC( gameLocal.msec );
|
||
|
||
// gradually speed up/slow down to desired speed
|
||
speed = vel.Normalize();
|
||
speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
|
||
if ( speed < 0.0f ) {
|
||
speed = 0.0f;
|
||
} else if ( move.speed && ( speed > move.speed ) ) {
|
||
speed = move.speed;
|
||
}
|
||
|
||
vel *= speed;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::FlyTurn
|
||
=====================
|
||
*/
|
||
void idAI::FlyTurn( void ) {
|
||
if ( move.moveCommand == MOVE_FACE_ENEMY ) {
|
||
TurnToward( lastVisibleEnemyPos );
|
||
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
||
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
||
} else if ( move.speed > 0.0f ) {
|
||
const idVec3 &vel = physicsObj.GetLinearVelocity();
|
||
if ( vel.ToVec2().LengthSqr() > 0.1f ) {
|
||
TurnToward( vel.ToYaw() );
|
||
}
|
||
}
|
||
Turn();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::FlyMove
|
||
=====================
|
||
*/
|
||
void idAI::FlyMove( void ) {
|
||
idVec3 goalPos;
|
||
idVec3 oldorigin;
|
||
idVec3 newDest;
|
||
|
||
AI_BLOCKED = false;
|
||
if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
|
||
StopMove( MOVE_STATUS_DONE );
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameLocal.Printf( "%d: %s: %s, vel = %.2f, sp = %.2f, maxsp = %.2f\n", gameLocal.time, name.c_str(), moveCommandString[ move.moveCommand ], physicsObj.GetLinearVelocity().Length(), move.speed, fly_speed );
|
||
}
|
||
|
||
if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
|
||
idVec3 vel = physicsObj.GetLinearVelocity();
|
||
|
||
if ( GetMovePos( goalPos ) ) {
|
||
CheckObstacleAvoidance( goalPos, newDest );
|
||
goalPos = newDest;
|
||
}
|
||
|
||
if ( move.speed ) {
|
||
FlySeekGoal( vel, goalPos );
|
||
}
|
||
|
||
// add in bobbing
|
||
AddFlyBob( vel );
|
||
|
||
if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
|
||
AdjustFlyHeight( vel, goalPos );
|
||
}
|
||
|
||
AdjustFlySpeed( vel );
|
||
|
||
physicsObj.SetLinearVelocity( vel );
|
||
}
|
||
|
||
// turn
|
||
FlyTurn();
|
||
|
||
// run the physics for this frame
|
||
oldorigin = physicsObj.GetOrigin();
|
||
physicsObj.UseFlyMove( true );
|
||
physicsObj.UseVelocityMove( false );
|
||
physicsObj.SetDelta( vec3_zero );
|
||
physicsObj.ForceDeltaMove( disableGravity );
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
|
||
monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
|
||
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
|
||
DirectDamage( attack, enemy.GetEntity() );
|
||
} else {
|
||
idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
|
||
if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
|
||
KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
|
||
} else if ( moveResult == MM_BLOCKED ) {
|
||
move.blockTime = gameLocal.time + 500;
|
||
AI_BLOCKED = true;
|
||
}
|
||
}
|
||
|
||
idVec3 org = physicsObj.GetOrigin();
|
||
if ( oldorigin != org ) {
|
||
TouchTriggers();
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
|
||
gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
|
||
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
|
||
gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
|
||
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
|
||
DrawRoute();
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::StaticMove
|
||
=====================
|
||
*/
|
||
void idAI::StaticMove( void ) {
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
|
||
if ( AI_DEAD ) {
|
||
return;
|
||
}
|
||
|
||
if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemyEnt ) {
|
||
TurnToward( lastVisibleEnemyPos );
|
||
} else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
|
||
TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
|
||
} else if ( move.moveCommand != MOVE_NONE ) {
|
||
TurnToward( move.moveDest );
|
||
}
|
||
Turn();
|
||
|
||
physicsObj.ForceDeltaMove( true ); // disable gravity
|
||
RunPhysicsWrapper(); //ivan - was: RunPhysics();
|
||
|
||
AI_ONGROUND = false;
|
||
|
||
if ( !af_push_moveables && attack.Length() && TestMelee() ) {
|
||
DirectDamage( attack, enemyEnt );
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorBlue, org, move.moveDest, gameLocal.msec, true );
|
||
gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
|
||
}
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
Damage
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
=====================
|
||
idAI::ReactionTo
|
||
=====================
|
||
*/
|
||
int idAI::ReactionTo( const idEntity *ent ) {
|
||
|
||
if ( ent->fl.hidden ) {
|
||
// ignore hidden entities
|
||
return ATTACK_IGNORE;
|
||
}
|
||
|
||
if ( !ent->IsType( idActor::Type ) ) {
|
||
return ATTACK_IGNORE;
|
||
}
|
||
|
||
const idActor *actor = static_cast<const idActor *>( ent );
|
||
if ( actor->IsType( idPlayer::Type ) && static_cast<const idPlayer *>(actor)->noclip ) {
|
||
// ignore players in noclip mode
|
||
return ATTACK_IGNORE;
|
||
}
|
||
|
||
// actors on different teams will always fight each other
|
||
if ( actor->team != team ) {
|
||
if ( actor->fl.notarget ) {
|
||
// don't attack on sight when attacker is notargeted
|
||
return ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
|
||
}
|
||
return ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE | ATTACK_ON_ACTIVATE;
|
||
}
|
||
|
||
// monsters will fight when attacked by lower ranked monsters. rank 0 never fights back.
|
||
if ( rank && ( actor->rank < rank ) ) {
|
||
return ATTACK_ON_DAMAGE;
|
||
}
|
||
|
||
// don't fight back
|
||
return ATTACK_IGNORE;
|
||
}
|
||
|
||
|
||
/*
|
||
=====================
|
||
idAI::Pain
|
||
=====================
|
||
*/
|
||
bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
||
idActor *actor;
|
||
|
||
AI_PAIN = idActor::Pain( inflictor, attacker, damage, dir, location );
|
||
AI_DAMAGE = true;
|
||
|
||
// force a blink
|
||
blink_time = 0;
|
||
|
||
// ignore damage from self
|
||
if ( attacker != this ) {
|
||
if ( inflictor ) {
|
||
AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
|
||
} else {
|
||
AI_SPECIAL_DAMAGE = 0;
|
||
}
|
||
|
||
if ( enemy.GetEntity() != attacker && attacker->IsType( idActor::Type ) ) {
|
||
actor = ( idActor * )attacker;
|
||
if ( ReactionTo( actor ) & ATTACK_ON_DAMAGE ) {
|
||
gameLocal.AlertAI( actor );
|
||
SetEnemy( actor );
|
||
}
|
||
}
|
||
}
|
||
|
||
return ( AI_PAIN != 0 );
|
||
}
|
||
|
||
|
||
/*
|
||
=====================
|
||
idAI::SpawnParticles
|
||
=====================
|
||
*/
|
||
void idAI::SpawnParticles( const char *keyName ) {
|
||
const idKeyValue *kv = spawnArgs.MatchPrefix( keyName, NULL );
|
||
while ( kv ) {
|
||
particleEmitter_t pe;
|
||
|
||
idStr particleName = kv->GetValue();
|
||
|
||
if ( particleName.Length() ) {
|
||
|
||
idStr jointName = kv->GetValue();
|
||
int dash = jointName.Find('-');
|
||
if ( dash > 0 ) {
|
||
particleName = particleName.Left( dash );
|
||
jointName = jointName.Right( jointName.Length() - dash - 1 );
|
||
}
|
||
|
||
SpawnParticlesOnJoint( pe, particleName, jointName );
|
||
particles.Append( pe );
|
||
}
|
||
|
||
kv = spawnArgs.MatchPrefix( keyName, kv );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SpawnParticlesOnJoint
|
||
=====================
|
||
*/
|
||
const idDeclParticle *idAI::SpawnParticlesOnJoint( particleEmitter_t &pe, const char *particleName, const char *jointName ) {
|
||
idVec3 origin;
|
||
idMat3 axis;
|
||
|
||
if ( *particleName == '\0' ) {
|
||
memset( &pe, 0, sizeof( pe ) );
|
||
return pe.particle;
|
||
}
|
||
|
||
pe.joint = animator.GetJointHandle( jointName );
|
||
if ( pe.joint == INVALID_JOINT ) {
|
||
gameLocal.Warning( "Unknown particleJoint '%s' on '%s'", jointName, name.c_str() );
|
||
pe.time = 0;
|
||
pe.particle = NULL;
|
||
} else {
|
||
animator.GetJointTransform( pe.joint, gameLocal.time, origin, axis );
|
||
origin = renderEntity.origin + origin * renderEntity.axis;
|
||
|
||
BecomeActive( TH_UPDATEPARTICLES );
|
||
if ( !gameLocal.time ) {
|
||
// particles with time of 0 don't show, so set the time differently on the first frame
|
||
pe.time = 1;
|
||
} else {
|
||
pe.time = gameLocal.time;
|
||
}
|
||
pe.particle = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, particleName ) );
|
||
gameLocal.smokeParticles->EmitSmoke( pe.particle, pe.time, gameLocal.random.CRandomFloat(), origin, axis );
|
||
}
|
||
|
||
return pe.particle;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::Killed
|
||
=====================
|
||
*/
|
||
void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
|
||
idAngles ang;
|
||
const char *modelDeath;
|
||
|
||
// make sure the monster is activated
|
||
EndAttack();
|
||
|
||
if ( g_debugDamage.GetBool() ) {
|
||
gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ),
|
||
GetDamageGroup( location ) );
|
||
}
|
||
|
||
if ( inflictor ) {
|
||
AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
|
||
} else {
|
||
AI_SPECIAL_DAMAGE = 0;
|
||
}
|
||
|
||
if ( AI_DEAD ) {
|
||
AI_PAIN = true;
|
||
AI_DAMAGE = true;
|
||
return;
|
||
}
|
||
|
||
// stop all voice sounds
|
||
StopSound( SND_CHANNEL_VOICE, false );
|
||
if ( head.GetEntity() ) {
|
||
head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false );
|
||
head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 );
|
||
}
|
||
|
||
disableGravity = false;
|
||
move.moveType = MOVETYPE_DEAD;
|
||
af_push_moveables = false;
|
||
|
||
physicsObj.UseFlyMove( false );
|
||
physicsObj.ForceDeltaMove( false );
|
||
|
||
// end our looping ambient sound
|
||
StopSound( SND_CHANNEL_AMBIENT, false );
|
||
|
||
if ( attacker && attacker->IsType( idActor::Type ) ) {
|
||
gameLocal.AlertAI( ( idActor * )attacker );
|
||
}
|
||
|
||
// activate targets
|
||
ActivateTargets( attacker );
|
||
|
||
RemoveAttachments();
|
||
RemoveProjectile();
|
||
StopMove( MOVE_STATUS_DONE );
|
||
|
||
ClearEnemy();
|
||
AI_DEAD = true;
|
||
|
||
// make monster nonsolid
|
||
physicsObj.SetContents( 0 );
|
||
physicsObj.GetClipModel()->Unlink();
|
||
|
||
Unbind();
|
||
|
||
if ( StartRagdoll() ) {
|
||
StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
|
||
}
|
||
|
||
if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) {
|
||
// lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here
|
||
StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
|
||
renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
|
||
SetModel( modelDeath );
|
||
physicsObj.SetLinearVelocity( vec3_zero );
|
||
physicsObj.PutToRest();
|
||
physicsObj.DisableImpact();
|
||
}
|
||
|
||
restartParticles = false;
|
||
|
||
state = GetScriptFunction( "state_Killed" );
|
||
SetState( state );
|
||
SetWaitState( "" );
|
||
|
||
const idKeyValue *kv = spawnArgs.MatchPrefix( "def_drops", NULL );
|
||
while( kv ) {
|
||
idDict args;
|
||
|
||
args.Set( "classname", kv->GetValue() );
|
||
args.Set( "origin", physicsObj.GetOrigin().ToString() );
|
||
gameLocal.SpawnEntityDef( args );
|
||
kv = spawnArgs.MatchPrefix( "def_drops", kv );
|
||
}
|
||
|
||
if ( attacker && attacker->IsType( idPlayer::Type ) ) {
|
||
idPlayer *player = static_cast< idPlayer* >( attacker );
|
||
if( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ){
|
||
player->AddAIKill();
|
||
}
|
||
//ivan start - add score only if player is the killer
|
||
player->AddScore( spawnArgs.GetInt( "score", "0" ) );
|
||
//ivan end
|
||
}
|
||
|
||
//ivan start - upd the global counter regardless who is the killer
|
||
if( !spawnArgs.GetBool( "noStats", "0" ) && ( spawnArgs.GetInt( "team", "1") != 0 ) ){
|
||
gameLocal.enemies_killed_counter++;
|
||
}
|
||
//ivan end
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
Targeting/Combat
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
=====================
|
||
idAI::PlayCinematic
|
||
=====================
|
||
*/
|
||
void idAI::PlayCinematic( void ) {
|
||
const char *animname;
|
||
|
||
if ( current_cinematic >= num_cinematics ) {
|
||
if ( g_debugCinematic.GetBool() ) {
|
||
gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
|
||
}
|
||
if ( !spawnArgs.GetBool( "cinematic_no_hide" ) ) {
|
||
Hide();
|
||
}
|
||
current_cinematic = 0;
|
||
ActivateTargets( gameLocal.GetLocalPlayer() );
|
||
fl.neverDormant = false;
|
||
return;
|
||
}
|
||
|
||
Show();
|
||
current_cinematic++;
|
||
|
||
allowJointMod = false;
|
||
allowEyeFocus = false;
|
||
|
||
spawnArgs.GetString( va( "anim%d", current_cinematic ), NULL, &animname );
|
||
if ( !animname ) {
|
||
gameLocal.Warning( "missing 'anim%d' key on %s", current_cinematic, name.c_str() );
|
||
return;
|
||
}
|
||
|
||
if ( g_debugCinematic.GetBool() ) {
|
||
gameLocal.Printf( "%d: '%s' start '%s'\n", gameLocal.framenum, GetName(), animname );
|
||
}
|
||
|
||
headAnim.animBlendFrames = 0;
|
||
headAnim.lastAnimBlendFrames = 0;
|
||
headAnim.BecomeIdle();
|
||
|
||
legsAnim.animBlendFrames = 0;
|
||
legsAnim.lastAnimBlendFrames = 0;
|
||
legsAnim.BecomeIdle();
|
||
|
||
torsoAnim.animBlendFrames = 0;
|
||
torsoAnim.lastAnimBlendFrames = 0;
|
||
ProcessEvent( &AI_PlayAnim, ANIMCHANNEL_TORSO, animname );
|
||
|
||
// make sure our model gets updated
|
||
animator.ForceUpdate();
|
||
|
||
// update the anim bounds
|
||
UpdateAnimation();
|
||
UpdateVisuals();
|
||
Present();
|
||
|
||
if ( head.GetEntity() ) {
|
||
// since the body anim was updated, we need to run physics to update the position of the head
|
||
RunPhysics();
|
||
|
||
// make sure our model gets updated
|
||
head.GetEntity()->GetAnimator()->ForceUpdate();
|
||
|
||
// update the anim bounds
|
||
head.GetEntity()->UpdateAnimation();
|
||
head.GetEntity()->UpdateVisuals();
|
||
head.GetEntity()->Present();
|
||
}
|
||
|
||
fl.neverDormant = true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::Activate
|
||
|
||
Notifies the script that a monster has been activated by a trigger or flashlight
|
||
=====================
|
||
*/
|
||
void idAI::Activate( idEntity *activator ) {
|
||
idPlayer *player;
|
||
|
||
if ( AI_DEAD ) {
|
||
// ignore it when they're dead
|
||
return;
|
||
}
|
||
|
||
// make sure he's not dormant
|
||
dormantStart = 0;
|
||
|
||
if ( num_cinematics ) {
|
||
PlayCinematic();
|
||
} else {
|
||
AI_ACTIVATED = true;
|
||
if ( !activator || !activator->IsType( idPlayer::Type ) ) {
|
||
player = gameLocal.GetLocalPlayer();
|
||
} else {
|
||
player = static_cast<idPlayer *>( activator );
|
||
}
|
||
|
||
if ( ReactionTo( player ) & ATTACK_ON_ACTIVATE ) {
|
||
SetEnemy( player );
|
||
}
|
||
|
||
// update the script in cinematics so that entities don't start anims or show themselves a frame late.
|
||
if ( cinematic ) {
|
||
UpdateAIScript();
|
||
|
||
// make sure our model gets updated
|
||
animator.ForceUpdate();
|
||
|
||
// update the anim bounds
|
||
UpdateAnimation();
|
||
UpdateVisuals();
|
||
Present();
|
||
|
||
if ( head.GetEntity() ) {
|
||
// since the body anim was updated, we need to run physics to update the position of the head
|
||
RunPhysics();
|
||
|
||
// make sure our model gets updated
|
||
head.GetEntity()->GetAnimator()->ForceUpdate();
|
||
|
||
// update the anim bounds
|
||
head.GetEntity()->UpdateAnimation();
|
||
head.GetEntity()->UpdateVisuals();
|
||
head.GetEntity()->Present();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::EnemyDead
|
||
=====================
|
||
*/
|
||
void idAI::EnemyDead( void ) {
|
||
ClearEnemy();
|
||
AI_ENEMY_DEAD = true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TalkTo
|
||
=====================
|
||
*/
|
||
void idAI::TalkTo( idActor *actor ) {
|
||
if ( talk_state != TALK_OK ) {
|
||
return;
|
||
}
|
||
|
||
talkTarget = actor;
|
||
if ( actor ) {
|
||
AI_TALK = true;
|
||
} else {
|
||
AI_TALK = false;
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::GetEnemy
|
||
=====================
|
||
*/
|
||
idActor *idAI::GetEnemy( void ) const {
|
||
return enemy.GetEntity();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::GetTalkState
|
||
=====================
|
||
*/
|
||
talkState_t idAI::GetTalkState( void ) const {
|
||
if ( ( talk_state != TALK_NEVER ) && AI_DEAD ) {
|
||
return TALK_DEAD;
|
||
}
|
||
if ( IsHidden() ) {
|
||
return TALK_NEVER;
|
||
}
|
||
return talk_state;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TouchedByFlashlight
|
||
=====================
|
||
*/
|
||
void idAI::TouchedByFlashlight( idActor *flashlight_owner ) {
|
||
if ( wakeOnFlashlight ) {
|
||
Activate( flashlight_owner );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::ClearEnemy
|
||
=====================
|
||
*/
|
||
void idAI::ClearEnemy( void ) {
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
StopMove( MOVE_STATUS_DEST_NOT_FOUND );
|
||
}
|
||
|
||
enemyNode.Remove();
|
||
enemy = NULL;
|
||
AI_ENEMY_IN_FOV = false;
|
||
AI_ENEMY_VISIBLE = false;
|
||
AI_ENEMY_DEAD = true;
|
||
|
||
SetChatSound();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::EnemyPositionValid
|
||
=====================
|
||
*/
|
||
bool idAI::EnemyPositionValid( void ) const {
|
||
trace_t tr;
|
||
idVec3 muzzle;
|
||
idMat3 axis;
|
||
|
||
if ( !enemy.GetEntity() ) {
|
||
return false;
|
||
}
|
||
|
||
if ( AI_ENEMY_VISIBLE ) {
|
||
return true;
|
||
}
|
||
|
||
gameLocal.clip.TracePoint( tr, GetEyePosition(), lastVisibleEnemyPos + lastVisibleEnemyEyeOffset, MASK_OPAQUE, this );
|
||
if ( tr.fraction < 1.0f ) {
|
||
// can't see the area yet, so don't know if he's there or not
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SetEnemyPosition
|
||
=====================
|
||
*/
|
||
void idAI::SetEnemyPosition( void ) {
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
int enemyAreaNum;
|
||
int areaNum;
|
||
int lastVisibleReachableEnemyAreaNum;
|
||
aasPath_t path;
|
||
idVec3 pos;
|
||
bool onGround;
|
||
|
||
if ( !enemyEnt ) {
|
||
return;
|
||
}
|
||
|
||
lastVisibleReachableEnemyPos = lastReachableEnemyPos;
|
||
lastVisibleEnemyEyeOffset = enemyEnt->EyeOffset();
|
||
lastVisibleEnemyPos = enemyEnt->GetPhysics()->GetOrigin();
|
||
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
pos = lastVisibleEnemyPos;
|
||
onGround = true;
|
||
} else {
|
||
onGround = enemyEnt->GetFloorPos( 64.0f, pos );
|
||
if ( enemyEnt->OnLadder() ) {
|
||
onGround = false;
|
||
}
|
||
}
|
||
|
||
if ( !onGround ) {
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
AI_DEST_UNREACHABLE = true;
|
||
}
|
||
return;
|
||
}
|
||
|
||
// when we don't have an AAS, we can't tell if an enemy is reachable or not,
|
||
// so just assume that he is.
|
||
if ( !aas ) {
|
||
lastVisibleReachableEnemyPos = lastVisibleEnemyPos;
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
AI_DEST_UNREACHABLE = false;
|
||
}
|
||
enemyAreaNum = 0;
|
||
areaNum = 0;
|
||
} else {
|
||
lastVisibleReachableEnemyAreaNum = move.toAreaNum;
|
||
enemyAreaNum = PointReachableAreaNum( lastVisibleEnemyPos, 1.0f );
|
||
if ( !enemyAreaNum ) {
|
||
enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
|
||
pos = lastReachableEnemyPos;
|
||
}
|
||
if ( !enemyAreaNum ) {
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
AI_DEST_UNREACHABLE = true;
|
||
}
|
||
areaNum = 0;
|
||
} else {
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
areaNum = PointReachableAreaNum( org );
|
||
if ( PathToGoal( path, areaNum, org, enemyAreaNum, pos ) ) {
|
||
lastVisibleReachableEnemyPos = pos;
|
||
lastVisibleReachableEnemyAreaNum = enemyAreaNum;
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
AI_DEST_UNREACHABLE = false;
|
||
}
|
||
} else if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
AI_DEST_UNREACHABLE = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( move.moveCommand == MOVE_TO_ENEMY ) {
|
||
if ( !aas ) {
|
||
// keep the move destination up to date for wandering
|
||
move.moveDest = lastVisibleReachableEnemyPos;
|
||
} else if ( enemyAreaNum ) {
|
||
move.toAreaNum = lastVisibleReachableEnemyAreaNum;
|
||
move.moveDest = lastVisibleReachableEnemyPos;
|
||
}
|
||
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
predictedPath_t path;
|
||
idVec3 end = move.moveDest;
|
||
end.z += enemyEnt->EyeOffset().z + fly_offset;
|
||
idAI::PredictPath( this, aas, move.moveDest, end - move.moveDest, 1000, 1000, SE_BLOCKED, path );
|
||
move.moveDest = path.endPos;
|
||
move.toAreaNum = PointReachableAreaNum( move.moveDest, 1.0f );
|
||
}
|
||
}
|
||
}
|
||
|
||
//ivan start
|
||
|
||
|
||
/*
|
||
================
|
||
idEntity::UpdateRenderEntity
|
||
================
|
||
|
||
bool idAI::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
|
||
if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
|
||
return false;
|
||
}
|
||
|
||
//ivan start
|
||
modelCallBackDone = true;
|
||
//ivan end
|
||
|
||
idAnimator *animator = GetAnimator();
|
||
if ( animator ) {
|
||
return animator->CreateFrame( gameLocal.time, false );
|
||
}
|
||
|
||
return false;
|
||
}
|
||
*/
|
||
|
||
/*
|
||
================
|
||
idAI::ModelCallback
|
||
|
||
NOTE: overrides idEntity::ModelCallback just to know if we are visible!
|
||
NOTE: this is indirectly called by UpdateEntityDef in Present, that is called by Think.
|
||
================
|
||
|
||
bool idAI::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
|
||
idAI *ent;
|
||
|
||
ent = static_cast<idAI *>(gameLocal.entities[ renderEntity->entityNum ]);
|
||
if ( !ent ) {
|
||
gameLocal.Error( "idAI::ModelCallback: callback with NULL game entity" );
|
||
}
|
||
|
||
return ent->UpdateRenderEntity( renderEntity, renderView );
|
||
}
|
||
*/
|
||
|
||
//not used
|
||
/*
|
||
bool idAI::AllowSeeEnemy( idActor *enemyEnt ) { //called after UpdateInhibitAttack
|
||
|
||
// let it see on Z axis?
|
||
//if( deltaZfromEnemy > AI_Z_DELTA_CANSEE_MAX ){
|
||
// return false;
|
||
//}
|
||
|
||
//was:
|
||
//test
|
||
if( isXlocked && enemyEnt->isXlocked && (enemyEnt->lockedXpos != lockedXpos ) ){ //both locked on different planes!
|
||
return true;
|
||
}
|
||
|
||
if( !isXlocked || updXlock ){ //if he can (or soon it will able to) follow the enemy --> don't check X delta
|
||
return ( deltaYfromEnemy < gameLocal.aiSeeDistanceY );
|
||
}else{ //isXlocked && !updXlock //cannot follow the enemy --> see enemy only when can attack
|
||
return ( !AI_INHIBIT_ATTACK ); //if attack is not inhibited, we can see
|
||
}
|
||
}
|
||
*/
|
||
|
||
|
||
/*
|
||
=====================
|
||
idAI::RunPhysicsWrapper
|
||
=====================
|
||
*/
|
||
void idAI::RunPhysicsWrapper( void ){
|
||
RunPhysics();
|
||
|
||
//lock the X position
|
||
if( isXlocked ){
|
||
physicsObj.SetOrigin( idVec3( fastXpos, physicsObj.GetOrigin().y, physicsObj.GetOrigin().z ) );
|
||
}else{
|
||
fastXpos = physicsObj.GetOrigin().x; //always upd so others can easily know our X pos
|
||
}
|
||
|
||
//upd is on screen
|
||
//UpdateIsOnScreen(); //NOTE: my "isOnScreen" value is useful also to other entities.
|
||
}
|
||
|
||
bool idAI::AllowSeeActor( idActor *actor ) {
|
||
|
||
float deltaXfromEntity;
|
||
|
||
//one of us is out of screen
|
||
if( !isOnScreen || !actor->isOnScreen ){
|
||
return false;
|
||
}
|
||
|
||
deltaXfromEntity = idMath::Fabs( fastXpos - actor->fastXpos );
|
||
|
||
//too far on X-axis. NOTE: if locked but cannot change, then he can see regardless X pos
|
||
if ( (!isXlocked || updXlock ) && ( deltaXfromEntity > AI_X_DELTA_CANSEE_MAX ) ){
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void idAI::UpdateInhibitAttack( idActor *enemyEnt ) {
|
||
|
||
//upd this also for other functions
|
||
deltaXfromEnemy = idMath::Fabs( fastXpos - enemyEnt->fastXpos );
|
||
|
||
//one of us is out of screen
|
||
if( !isOnScreen || !enemyEnt->isOnScreen ){
|
||
AI_INHIBIT_ATTACK = true;
|
||
//gameLocal.Warning("%s AI_INHIBIT_ATTACK 0", GetName() );
|
||
return;
|
||
}
|
||
|
||
//too far on X-axis. NOTE: if locked but cannot change, then he can fire regardless X pos
|
||
if ( (!isXlocked || updXlock ) && ( deltaXfromEnemy > AI_X_DELTA_CANSEE_MAX ) ){
|
||
AI_INHIBIT_ATTACK = true;
|
||
//gameLocal.Warning("%s AI_INHIBIT_ATTACK 1", GetName() );
|
||
return;
|
||
}
|
||
|
||
AI_INHIBIT_ATTACK = false;
|
||
//gameLocal.Warning("%s AI_INHIBIT_ATTACK NO", GetName() );
|
||
}
|
||
|
||
void idAI::UpdateXlock( idActor *enemyEnt ) {
|
||
/*
|
||
if( deltaYfromEnemy > gameLocal.aiSeeDistanceY ){
|
||
//we can unlock if too far!
|
||
isXlocked = false;
|
||
return; //don't upd if too far (outside of the screen)
|
||
}
|
||
*/
|
||
if( updXlock ){ //Xlocked position can be updated
|
||
if( !enemyEnt->updXlock ){ //never lock with someone who is not locked
|
||
isXlocked = false;
|
||
} else if( deltaXfromEnemy > AI_X_DELTA_LOCK_MIN ){ //too far --> unlock
|
||
isXlocked = false;
|
||
} else if ( !isXlocked ){
|
||
//gameLocal.Printf("%s is now locked\n", GetName() );
|
||
isXlocked = true;
|
||
fastXpos = enemyEnt->fastXpos; //we'll be pushed in place.
|
||
}
|
||
}else if( isXlocked ){ //locked and never update --> clear enemies if cannot fight
|
||
if( AI_INHIBIT_ATTACK ){ //if attack is inhibited, we cannot fight this enemy
|
||
ClearEnemy();
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::UpdateIsOnScreen
|
||
=====================
|
||
*/
|
||
|
||
void idAI::UpdateIsOnScreen( void ) {
|
||
if( IsHidden() ){
|
||
isOnScreen = false;
|
||
return;
|
||
}
|
||
idPlayer *player = gameLocal.GetLocalPlayer();
|
||
if ( !player ) {
|
||
isOnScreen = false; //no player, no camera
|
||
return;
|
||
}
|
||
const idVec3 &myPos = physicsObj.GetOrigin();
|
||
const idVec3 &cameraPos = player->GetRenderView()->vieworg;
|
||
idVec3 delta;
|
||
|
||
//ivan start - upd deltas stuff
|
||
delta = cameraPos - myPos;
|
||
/*
|
||
delta.x = cameraPos.x - myPos.x; //how far we are from the camera.
|
||
delta.y = idMath::Fabs( cameraPos.y - myPos.y ); //horizontal distance
|
||
delta.z = idMath::Fabs( cameraPos.z - myPos.z ); //vertical distance
|
||
|
||
*/
|
||
|
||
|
||
//X axis - how far we are from the camera
|
||
if( delta.x > 0 ){
|
||
//gameLocal.Printf("left\n");
|
||
isOnScreen = false;
|
||
return;
|
||
}else{
|
||
delta.x = -delta.x; //use the positive value next
|
||
}
|
||
|
||
//Y axis - horizontal distance
|
||
if( idMath::Fabs( delta.y ) > delta.x ){ //if horizontal dist is greater than camera dist, than we are out of view (Horizontal FOV 90 --> 90 45 45 triangle)
|
||
isOnScreen = false;
|
||
return;
|
||
}
|
||
|
||
//Z axis - vertical distance
|
||
if( delta.z > EyeHeight() ){ //positive value --> camera is higher than me --> remove the eye offset
|
||
delta.z -= EyeHeight(); //like being closer
|
||
}
|
||
if( idMath::Fabs( delta.z ) > delta.x * 0.75f ){ //if vertical dist is greater than camera dist*0.75, than we are out of view (Vertical FOV < 90 --> NOT 90 45 45 triangle! Use 4:3 ratio).
|
||
isOnScreen = false;
|
||
}else{
|
||
isOnScreen = true;
|
||
}
|
||
|
||
//Assuming FOV = 90, delta.x is also the horizontal max distance
|
||
//isOnScreen = ( (delta.y < delta.x) && (delta.z < delta.x * 0.75f) );
|
||
|
||
/*
|
||
if( isOnScreen ){
|
||
gameLocal.Warning("%s isOnScreen", GetName() );
|
||
}else{
|
||
gameLocal.Warning("%s not isOnScreen", GetName() );
|
||
}
|
||
*/
|
||
}
|
||
|
||
//ivan end
|
||
|
||
|
||
/*
|
||
=====================
|
||
idAI::UpdateEnemyPosition
|
||
=====================
|
||
*/
|
||
void idAI::UpdateEnemyPosition( void ) {
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
int enemyAreaNum;
|
||
int areaNum;
|
||
aasPath_t path;
|
||
idVec3 enemyPos;
|
||
bool onGround;
|
||
|
||
if ( !enemyEnt ) {
|
||
AI_INHIBIT_ATTACK = true;
|
||
return;
|
||
}
|
||
|
||
const idVec3 &org = physicsObj.GetOrigin();
|
||
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
enemyPos = enemyEnt->GetPhysics()->GetOrigin();
|
||
onGround = true;
|
||
} else {
|
||
onGround = enemyEnt->GetFloorPos( 64.0f, enemyPos );
|
||
if ( enemyEnt->OnLadder() ) {
|
||
onGround = false;
|
||
}
|
||
}
|
||
|
||
//ivan start - upd deltas stuff
|
||
UpdateInhibitAttack( enemyEnt ); //Inhibit Attacks based on deltas
|
||
UpdateXlock( enemyEnt );
|
||
if( !enemy.GetEntity() ){ //UpdateXlock could have cleared the enemy
|
||
AI_ENEMY_IN_FOV = false;
|
||
AI_ENEMY_VISIBLE = false;
|
||
return;
|
||
}
|
||
//ivan end
|
||
|
||
if ( onGround ) {
|
||
// when we don't have an AAS, we can't tell if an enemy is reachable or not,
|
||
// so just assume that he is.
|
||
if ( !aas ) {
|
||
enemyAreaNum = 0;
|
||
lastReachableEnemyPos = enemyPos;
|
||
} else {
|
||
enemyAreaNum = PointReachableAreaNum( enemyPos, 1.0f );
|
||
if ( enemyAreaNum ) {
|
||
areaNum = PointReachableAreaNum( org );
|
||
if ( PathToGoal( path, areaNum, org, enemyAreaNum, enemyPos ) ) {
|
||
lastReachableEnemyPos = enemyPos;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
AI_ENEMY_IN_FOV = false;
|
||
AI_ENEMY_VISIBLE = false;
|
||
|
||
if ( CanSee( enemyEnt, false ) ) { //ivan - AllowSeeEnemy( enemyEnt ) &&
|
||
AI_ENEMY_VISIBLE = true;
|
||
if ( CheckFOV( enemyEnt->GetPhysics()->GetOrigin() ) ) {
|
||
AI_ENEMY_IN_FOV = true;
|
||
}
|
||
|
||
SetEnemyPosition();
|
||
} else {
|
||
// check if we heard any sounds in the last frame
|
||
if ( enemyEnt == gameLocal.GetAlertEntity() ) {
|
||
float dist = ( enemyEnt->GetPhysics()->GetOrigin() - org ).LengthSqr();
|
||
if ( dist < Square( AI_HEARING_RANGE ) ) {
|
||
SetEnemyPosition();
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugBounds( colorLtGrey, enemyEnt->GetPhysics()->GetBounds(), lastReachableEnemyPos, gameLocal.msec );
|
||
gameRenderWorld->DebugBounds( colorWhite, enemyEnt->GetPhysics()->GetBounds(), lastVisibleReachableEnemyPos, gameLocal.msec );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SetEnemy
|
||
=====================
|
||
*/
|
||
void idAI::SetEnemy( idActor *newEnemy ) {
|
||
int enemyAreaNum;
|
||
|
||
if ( AI_DEAD ) {
|
||
ClearEnemy();
|
||
return;
|
||
}
|
||
|
||
AI_ENEMY_DEAD = false;
|
||
if ( !newEnemy ) {
|
||
ClearEnemy();
|
||
} else if ( enemy.GetEntity() != newEnemy ) {
|
||
enemy = newEnemy;
|
||
|
||
|
||
//ivan start - upd deltas stuff
|
||
UpdateInhibitAttack( newEnemy ); //Inhibit Attacks based on deltas
|
||
UpdateXlock( newEnemy );
|
||
if( !enemy.GetEntity() ) return; //UpdateXlock could have cleared the enemy
|
||
//ivan end
|
||
|
||
|
||
enemyNode.AddToEnd( newEnemy->enemyList );
|
||
if ( newEnemy->health <= 0 ) {
|
||
EnemyDead();
|
||
return;
|
||
}
|
||
|
||
// let the monster know where the enemy is
|
||
newEnemy->GetAASLocation( aas, lastReachableEnemyPos, enemyAreaNum );
|
||
SetEnemyPosition();
|
||
SetChatSound();
|
||
|
||
lastReachableEnemyPos = lastVisibleEnemyPos;
|
||
lastVisibleReachableEnemyPos = lastReachableEnemyPos;
|
||
enemyAreaNum = PointReachableAreaNum( lastReachableEnemyPos, 1.0f );
|
||
if ( aas && enemyAreaNum ) {
|
||
aas->PushPointIntoAreaNum( enemyAreaNum, lastReachableEnemyPos );
|
||
lastVisibleReachableEnemyPos = lastReachableEnemyPos;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
============
|
||
idAI::FirstVisiblePointOnPath
|
||
============
|
||
*/
|
||
idVec3 idAI::FirstVisiblePointOnPath( const idVec3 origin, const idVec3 &target, int travelFlags ) const {
|
||
int i, areaNum, targetAreaNum, curAreaNum, travelTime;
|
||
idVec3 curOrigin;
|
||
idReachability *reach;
|
||
|
||
if ( !aas ) {
|
||
return origin;
|
||
}
|
||
|
||
areaNum = PointReachableAreaNum( origin );
|
||
targetAreaNum = PointReachableAreaNum( target );
|
||
|
||
if ( !areaNum || !targetAreaNum ) {
|
||
return origin;
|
||
}
|
||
|
||
if ( ( areaNum == targetAreaNum ) || PointVisible( origin ) ) {
|
||
return origin;
|
||
}
|
||
|
||
curAreaNum = areaNum;
|
||
curOrigin = origin;
|
||
|
||
for( i = 0; i < 10; i++ ) {
|
||
|
||
if ( !aas->RouteToGoalArea( curAreaNum, curOrigin, targetAreaNum, travelFlags, travelTime, &reach ) ) {
|
||
break;
|
||
}
|
||
|
||
if ( !reach ) {
|
||
return target;
|
||
}
|
||
|
||
curAreaNum = reach->toAreaNum;
|
||
curOrigin = reach->end;
|
||
|
||
if ( PointVisible( curOrigin ) ) {
|
||
return curOrigin;
|
||
}
|
||
}
|
||
|
||
return origin;
|
||
}
|
||
|
||
/*
|
||
===================
|
||
idAI::CalculateAttackOffsets
|
||
|
||
calculate joint positions on attack frames so we can do proper "can hit" tests
|
||
===================
|
||
*/
|
||
void idAI::CalculateAttackOffsets( void ) {
|
||
const idDeclModelDef *modelDef;
|
||
int num;
|
||
int i;
|
||
int frame;
|
||
const frameCommand_t *command;
|
||
idMat3 axis;
|
||
const idAnim *anim;
|
||
jointHandle_t joint;
|
||
|
||
modelDef = animator.ModelDef();
|
||
if ( !modelDef ) {
|
||
return;
|
||
}
|
||
num = modelDef->NumAnims();
|
||
|
||
// needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim
|
||
animator.RemoveOriginOffset( false );
|
||
|
||
// anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for
|
||
// launch offsets so that anim number can be used without subtracting 1.
|
||
missileLaunchOffset.SetGranularity( 1 );
|
||
missileLaunchOffset.SetNum( num + 1 );
|
||
missileLaunchOffset[ 0 ].Zero();
|
||
|
||
for( i = 1; i <= num; i++ ) {
|
||
missileLaunchOffset[ i ].Zero();
|
||
anim = modelDef->GetAnim( i );
|
||
if ( anim ) {
|
||
frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command );
|
||
if ( frame >= 0 ) {
|
||
joint = animator.GetJointHandle( command->string->c_str() );
|
||
if ( joint == INVALID_JOINT ) {
|
||
gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() );
|
||
}
|
||
GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis );
|
||
}
|
||
}
|
||
}
|
||
|
||
animator.RemoveOriginOffset( true );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::CreateProjectileClipModel
|
||
=====================
|
||
*/
|
||
void idAI::CreateProjectileClipModel( void ) const {
|
||
if ( projectileClipModel == NULL ) {
|
||
idBounds projectileBounds( vec3_origin );
|
||
projectileBounds.ExpandSelf( projectileRadius );
|
||
projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) );
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::GetAimDir
|
||
=====================
|
||
*/
|
||
bool idAI::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const {
|
||
idVec3 targetPos1;
|
||
idVec3 targetPos2;
|
||
idVec3 delta;
|
||
float max_height;
|
||
bool result;
|
||
|
||
// if no aimAtEnt or projectile set
|
||
if ( !aimAtEnt || !projectileDef ) {
|
||
aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis();
|
||
return false;
|
||
}
|
||
|
||
if ( projectileClipModel == NULL ) {
|
||
CreateProjectileClipModel();
|
||
}
|
||
|
||
if ( aimAtEnt == enemy.GetEntity() ) {
|
||
static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 );
|
||
} else if ( aimAtEnt->IsType( idActor::Type ) ) {
|
||
static_cast<idActor *>( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 );
|
||
} else {
|
||
targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter();
|
||
targetPos2 = targetPos1;
|
||
}
|
||
|
||
// try aiming for chest
|
||
delta = firePos - targetPos1;
|
||
max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
|
||
result = PredictTrajectory( firePos, targetPos1, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
|
||
if ( result || !aimAtEnt->IsType( idActor::Type ) ) {
|
||
return result;
|
||
}
|
||
|
||
// try aiming for head
|
||
delta = firePos - targetPos2;
|
||
max_height = delta.LengthFast() * projectile_height_to_distance_ratio;
|
||
result = PredictTrajectory( firePos, targetPos2, projectileSpeed, projectileGravity, projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir );
|
||
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::BeginAttack
|
||
=====================
|
||
*/
|
||
void idAI::BeginAttack( const char *name ) {
|
||
attack = name;
|
||
lastAttackTime = gameLocal.time;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::EndAttack
|
||
=====================
|
||
*/
|
||
void idAI::EndAttack( void ) {
|
||
attack = "";
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::CreateProjectile
|
||
=====================
|
||
*/
|
||
idProjectile *idAI::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) {
|
||
idEntity *ent;
|
||
const char *clsname;
|
||
|
||
if ( !projectile.GetEntity() ) {
|
||
gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
|
||
if ( !ent ) {
|
||
clsname = projectileDef->GetString( "classname" );
|
||
gameLocal.Error( "Could not spawn entityDef '%s'", clsname );
|
||
}
|
||
|
||
if ( !ent->IsType( idProjectile::Type ) ) {
|
||
clsname = ent->GetClassname();
|
||
gameLocal.Error( "'%s' is not an idProjectile", clsname );
|
||
}
|
||
projectile = ( idProjectile * )ent;
|
||
}
|
||
|
||
projectile.GetEntity()->Create( this, pos, dir );
|
||
|
||
return projectile.GetEntity();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::RemoveProjectile
|
||
=====================
|
||
*/
|
||
void idAI::RemoveProjectile( void ) {
|
||
if ( projectile.GetEntity() ) {
|
||
projectile.GetEntity()->PostEventMS( &EV_Remove, 0 );
|
||
projectile = NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::LaunchProjectile
|
||
=====================
|
||
*/
|
||
idProjectile *idAI::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) {
|
||
idVec3 muzzle;
|
||
idVec3 dir;
|
||
idVec3 start;
|
||
trace_t tr;
|
||
idBounds projBounds;
|
||
float distance;
|
||
const idClipModel *projClip;
|
||
float attack_accuracy;
|
||
float attack_cone;
|
||
float projectile_spread;
|
||
float diff;
|
||
float angle = 0.0f;
|
||
float spin;
|
||
idAngles ang;
|
||
int num_projectiles;
|
||
int i;
|
||
idMat3 axis;
|
||
idVec3 tmp;
|
||
idProjectile *lastProjectile;
|
||
|
||
if ( !projectileDef ) {
|
||
gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
|
||
return NULL;
|
||
}
|
||
|
||
attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
|
||
attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
|
||
projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
|
||
num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
|
||
|
||
GetMuzzle( jointname, muzzle, axis );
|
||
|
||
if ( !projectile.GetEntity() ) {
|
||
CreateProjectile( muzzle, axis[ 0 ] );
|
||
}
|
||
|
||
lastProjectile = projectile.GetEntity();
|
||
|
||
if ( target != NULL ) {
|
||
tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
|
||
tmp.Normalize();
|
||
axis = tmp.ToMat3();
|
||
} else {
|
||
axis = viewAxis;
|
||
}
|
||
|
||
// rotate it because the cone points up by default
|
||
tmp = axis[2];
|
||
axis[2] = axis[0];
|
||
axis[0] = -tmp;
|
||
|
||
// make sure the projectile starts inside the monster bounding box
|
||
const idBounds &ownerBounds = physicsObj.GetAbsBounds();
|
||
projClip = lastProjectile->GetPhysics()->GetClipModel();
|
||
projBounds = projClip->GetBounds().Rotate( axis );
|
||
|
||
// 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( muzzle, viewAxis[ 0 ], distance ) ) {
|
||
start = muzzle + 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, projClip, axis, MASK_SHOT_RENDERMODEL, this );
|
||
muzzle = tr.endpos;
|
||
|
||
// set aiming direction
|
||
GetAimDir( muzzle, target, this, dir );
|
||
ang = dir.ToAngles();
|
||
|
||
// adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
|
||
float t = MS2SEC( gameLocal.time + entityNumber * 497 );
|
||
ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
|
||
ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
|
||
|
||
if ( clampToAttackCone ) {
|
||
// clamp the attack direction to be within monster's attack cone so he doesn't do
|
||
// things like throw the missile backwards if you're behind him
|
||
diff = idMath::AngleDelta( ang.yaw, current_yaw );
|
||
if ( diff > attack_cone ) {
|
||
ang.yaw = current_yaw + attack_cone;
|
||
} else if ( diff < -attack_cone ) {
|
||
ang.yaw = current_yaw - attack_cone;
|
||
}
|
||
}
|
||
|
||
axis = ang.ToMat3();
|
||
|
||
//ivan start - fire modes - setup
|
||
int firemodeCounter = 0;
|
||
int firemodeCounterPos = 0;
|
||
idVec3 updown_offset;
|
||
updown_offset.Zero();
|
||
|
||
if( fireMode == AI_FIREMODE_2D_STEP_SPREAD){
|
||
if(num_projectiles > 1){ //Example: spread = 90, num projs = 5
|
||
angle = 2*projectile_spread/(num_projectiles-1); //Spread step: 2* 90 /(5-1) = 45 degrees
|
||
angle = angle/180.0f; //normalized from 0 to 1: 45/180 = 0.5 --> % of 180 degrees to use: 0, 0.25, -0.25, 0.5, -0.5
|
||
}
|
||
}
|
||
//ivan end
|
||
|
||
float spreadRad = DEG2RAD( projectile_spread );
|
||
for( i = 0; i < num_projectiles; i++ ) {
|
||
//ivan start - fire modes - direction
|
||
if( fireMode == AI_FIREMODE_DEFAULT){
|
||
// spread the projectiles out
|
||
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
|
||
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
|
||
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
|
||
}else if( fireMode == AI_FIREMODE_2D_STEP_SPREAD){
|
||
// FIXME: DG: if num_projectiles == 1, angle is not properly initialized
|
||
// (I initialized it to 0 above, but not sure that's the appropriate value)
|
||
dir = (axis[ 0 ] * (1-angle*firemodeCounterPos) ) + ( axis[ 2 ] * angle*firemodeCounter );
|
||
//upd counter
|
||
if(firemodeCounter >= 0){
|
||
firemodeCounter++;
|
||
firemodeCounterPos = firemodeCounter;
|
||
}
|
||
firemodeCounter = - firemodeCounter;
|
||
}else if( fireMode == AI_FIREMODE_2D_PARALLEL_SPREAD){
|
||
dir = axis[ 0 ];
|
||
updown_offset = axis[ 2 ]*projectile_spread*firemodeCounter;
|
||
//upd counter
|
||
if(firemodeCounter >= 0){
|
||
firemodeCounter++;
|
||
}
|
||
firemodeCounter = - firemodeCounter;
|
||
}else if( fireMode == AI_FIREMODE_2D_RANDOM_SPREAD){
|
||
// spread the projectiles out
|
||
angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
|
||
spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
|
||
dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) );
|
||
}else{
|
||
gameLocal.Error("Unknown AI fire mode: %d", fireMode );
|
||
}
|
||
//ivan end
|
||
|
||
dir.Normalize();
|
||
|
||
// launch the projectile
|
||
if ( !projectile.GetEntity() ) {
|
||
CreateProjectile( muzzle, dir );
|
||
}
|
||
lastProjectile = projectile.GetEntity();
|
||
|
||
|
||
//ivan start - fire modes - postion offset
|
||
//lastProjectile->Launch( muzzle, dir, vec3_origin );
|
||
lastProjectile->Launch( muzzle + updown_offset, dir, vec3_origin );
|
||
//ivan test end
|
||
|
||
projectile = NULL;
|
||
}
|
||
|
||
TriggerWeaponEffects( muzzle );
|
||
|
||
lastAttackTime = gameLocal.time;
|
||
|
||
return lastProjectile;
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::DamageFeedback
|
||
|
||
callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
|
||
|
||
FIXME: This gets called when we call idPlayer::CalcDamagePoints from idAI::AttackMelee, which then checks for a saving throw,
|
||
possibly forcing a miss. This is harmless behavior ATM, but is not intuitive.
|
||
================
|
||
*/
|
||
void idAI::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
|
||
if ( ( victim == this ) && inflictor->IsType( idProjectile::Type ) ) {
|
||
// monsters only get half damage from their own projectiles
|
||
damage = ( damage + 1 ) / 2; // round up so we don't do 0 damage
|
||
|
||
} else if ( victim == enemy.GetEntity() ) {
|
||
AI_HIT_ENEMY = true;
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::DirectDamage
|
||
|
||
Causes direct damage to an entity
|
||
|
||
kickDir is specified in the monster's coordinate system, and gives the direction
|
||
that the view kick and knockback should go
|
||
=====================
|
||
*/
|
||
void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) {
|
||
const idDict *meleeDef;
|
||
const char *p;
|
||
const idSoundShader *shader;
|
||
|
||
meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
|
||
if ( !meleeDef ) {
|
||
gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() );
|
||
}
|
||
|
||
if ( !ent->fl.takedamage ) {
|
||
const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" ));
|
||
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
|
||
return;
|
||
}
|
||
|
||
//
|
||
// do the damage
|
||
//
|
||
p = meleeDef->GetString( "snd_hit" );
|
||
if ( p && *p ) {
|
||
shader = declManager->FindSound( p );
|
||
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
|
||
}
|
||
|
||
idVec3 kickDir;
|
||
meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
|
||
|
||
idVec3 globalKickDir;
|
||
globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
|
||
|
||
ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
|
||
|
||
// end the attack if we're a multiframe attack
|
||
EndAttack();
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TestMelee
|
||
=====================
|
||
*/
|
||
bool idAI::TestMelee( void ) const {
|
||
trace_t trace;
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
|
||
if ( !enemyEnt || !melee_range ) {
|
||
return false;
|
||
}
|
||
|
||
//FIXME: make work with gravity vector
|
||
idVec3 org = physicsObj.GetOrigin();
|
||
const idBounds &myBounds = physicsObj.GetBounds();
|
||
idBounds bounds;
|
||
|
||
// expand the bounds out by our melee range
|
||
bounds[0][0] = -melee_range;
|
||
bounds[0][1] = -melee_range;
|
||
bounds[0][2] = myBounds[0][2] - 4.0f;
|
||
bounds[1][0] = melee_range;
|
||
bounds[1][1] = melee_range;
|
||
bounds[1][2] = myBounds[1][2] + 4.0f;
|
||
bounds.TranslateSelf( org );
|
||
|
||
idVec3 enemyOrg = enemyEnt->GetPhysics()->GetOrigin();
|
||
idBounds enemyBounds = enemyEnt->GetPhysics()->GetBounds();
|
||
enemyBounds.TranslateSelf( enemyOrg );
|
||
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugBounds( colorYellow, bounds, vec3_zero, gameLocal.msec );
|
||
}
|
||
|
||
if ( !bounds.IntersectsBounds( enemyBounds ) ) {
|
||
return false;
|
||
}
|
||
|
||
idVec3 start = GetEyePosition();
|
||
idVec3 end = enemyEnt->GetEyePosition();
|
||
|
||
gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
|
||
if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::AttackMelee
|
||
|
||
jointname allows the endpoint to be exactly specified in the model,
|
||
as for the commando tentacle. If not specified, it will be set to
|
||
the facing direction + melee_range.
|
||
|
||
kickDir is specified in the monster's coordinate system, and gives the direction
|
||
that the view kick and knockback should go
|
||
=====================
|
||
*/
|
||
bool idAI::AttackMelee( const char *meleeDefName ) {
|
||
const idDict *meleeDef;
|
||
idActor *enemyEnt = enemy.GetEntity();
|
||
const char *p;
|
||
const idSoundShader *shader;
|
||
|
||
meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
|
||
if ( !meleeDef ) {
|
||
gameLocal.Error( "Unknown melee '%s'", meleeDefName );
|
||
}
|
||
|
||
if ( !enemyEnt ) {
|
||
p = meleeDef->GetString( "snd_miss" );
|
||
if ( p && *p ) {
|
||
shader = declManager->FindSound( p );
|
||
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// check for the "saving throw" automatic melee miss on lethal blow
|
||
// stupid place for this.
|
||
bool forceMiss = false;
|
||
if ( enemyEnt->IsType( idPlayer::Type ) && g_skill.GetInteger() < 2 ) {
|
||
int damage, armor;
|
||
idPlayer *player = static_cast<idPlayer*>( enemyEnt );
|
||
player->CalcDamagePoints( this, this, meleeDef, 1.0f, INVALID_JOINT, &damage, &armor );
|
||
|
||
if ( enemyEnt->health <= damage ) {
|
||
int t = gameLocal.time - player->lastSavingThrowTime;
|
||
if ( t > SAVING_THROW_TIME ) {
|
||
player->lastSavingThrowTime = gameLocal.time;
|
||
t = 0;
|
||
}
|
||
if ( t < 1000 ) {
|
||
gameLocal.Printf( "Saving throw.\n" );
|
||
forceMiss = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// make sure the trace can actually hit the enemy
|
||
if ( forceMiss || !TestMelee() ) {
|
||
// missed
|
||
p = meleeDef->GetString( "snd_miss" );
|
||
if ( p && *p ) {
|
||
shader = declManager->FindSound( p );
|
||
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//
|
||
// do the damage
|
||
//
|
||
p = meleeDef->GetString( "snd_hit" );
|
||
if ( p && *p ) {
|
||
shader = declManager->FindSound( p );
|
||
StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
|
||
}
|
||
|
||
idVec3 kickDir;
|
||
meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
|
||
|
||
idVec3 globalKickDir;
|
||
globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
|
||
|
||
enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
|
||
|
||
lastAttackTime = gameLocal.time;
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::PushWithAF
|
||
================
|
||
*/
|
||
void idAI::PushWithAF( void ) {
|
||
int i, j;
|
||
afTouch_t touchList[ MAX_GENTITIES ];
|
||
idEntity *pushed_ents[ MAX_GENTITIES ];
|
||
idEntity *ent;
|
||
idVec3 vel;
|
||
int num_pushed;
|
||
|
||
num_pushed = 0;
|
||
af.ChangePose( this, gameLocal.time );
|
||
int num = af.EntitiesTouchingAF( touchList );
|
||
for( i = 0; i < num; i++ ) {
|
||
if ( touchList[ i ].touchedEnt->IsType( idProjectile::Type ) ) {
|
||
// skip projectiles
|
||
continue;
|
||
}
|
||
|
||
// make sure we havent pushed this entity already. this avoids causing double damage
|
||
for( j = 0; j < num_pushed; j++ ) {
|
||
if ( pushed_ents[ j ] == touchList[ i ].touchedEnt ) {
|
||
break;
|
||
}
|
||
}
|
||
if ( j >= num_pushed ) {
|
||
ent = touchList[ i ].touchedEnt;
|
||
pushed_ents[num_pushed++] = ent;
|
||
vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin();
|
||
vel.Normalize();
|
||
if ( attack.Length() && ent->IsType( idActor::Type ) ) {
|
||
ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT );
|
||
} else {
|
||
ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
Misc
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
================
|
||
idAI::GetMuzzle
|
||
================
|
||
*/
|
||
void idAI::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) {
|
||
jointHandle_t joint;
|
||
|
||
if ( !jointname || !jointname[ 0 ] ) {
|
||
muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14;
|
||
muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f;
|
||
} else {
|
||
joint = animator.GetJointHandle( jointname );
|
||
if ( joint == INVALID_JOINT ) {
|
||
gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() );
|
||
}
|
||
GetJointWorldTransform( joint, gameLocal.time, muzzle, axis );
|
||
}
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::TriggerWeaponEffects
|
||
================
|
||
*/
|
||
void idAI::TriggerWeaponEffects( const idVec3 &muzzle ) {
|
||
idVec3 org;
|
||
idMat3 axis;
|
||
|
||
if ( !g_muzzleFlash.GetBool() ) {
|
||
return;
|
||
}
|
||
|
||
// muzzle flash
|
||
// offset the shader parms so muzzle flashes show up
|
||
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
|
||
renderEntity.shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat();
|
||
|
||
if ( flashJointWorld != INVALID_JOINT ) {
|
||
GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis );
|
||
|
||
if ( worldMuzzleFlash.lightRadius.x > 0.0f ) {
|
||
worldMuzzleFlash.axis = axis;
|
||
worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
|
||
if ( worldMuzzleFlashHandle != - 1 ) {
|
||
gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
|
||
} else {
|
||
worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash );
|
||
}
|
||
muzzleFlashEnd = gameLocal.time + flashTime;
|
||
UpdateVisuals();
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::UpdateMuzzleFlash
|
||
================
|
||
*/
|
||
void idAI::UpdateMuzzleFlash( void ) {
|
||
if ( worldMuzzleFlashHandle != -1 ) {
|
||
if ( gameLocal.time >= muzzleFlashEnd ) {
|
||
gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle );
|
||
worldMuzzleFlashHandle = -1;
|
||
} else {
|
||
idVec3 muzzle;
|
||
animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
|
||
animator.GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis );
|
||
muzzle = physicsObj.GetOrigin() + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis();
|
||
worldMuzzleFlash.origin = muzzle;
|
||
gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash );
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::Hide
|
||
================
|
||
*/
|
||
void idAI::Hide( void ) {
|
||
idActor::Hide();
|
||
fl.takedamage = false;
|
||
physicsObj.SetContents( 0 );
|
||
physicsObj.GetClipModel()->Unlink();
|
||
StopSound( SND_CHANNEL_AMBIENT, false );
|
||
SetChatSound();
|
||
|
||
AI_ENEMY_IN_FOV = false;
|
||
AI_ENEMY_VISIBLE = false;
|
||
StopMove( MOVE_STATUS_DONE );
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::Show
|
||
================
|
||
*/
|
||
void idAI::Show( void ) {
|
||
idActor::Show();
|
||
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" );
|
||
SetChatSound();
|
||
StartSound( "snd_ambient", SND_CHANNEL_AMBIENT, 0, false, NULL );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::SetChatSound
|
||
=====================
|
||
*/
|
||
void idAI::SetChatSound( void ) {
|
||
const char *snd;
|
||
|
||
if ( IsHidden() ) {
|
||
snd = NULL;
|
||
} else if ( enemy.GetEntity() ) {
|
||
snd = spawnArgs.GetString( "snd_chatter_combat", NULL );
|
||
chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_combat_min", "5" ) );
|
||
chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_combat_max", "10" ) );
|
||
} else if ( !spawnArgs.GetBool( "no_idle_chatter" ) ) {
|
||
snd = spawnArgs.GetString( "snd_chatter", NULL );
|
||
chat_min = SEC2MS( spawnArgs.GetFloat( "chatter_min", "5" ) );
|
||
chat_max = SEC2MS( spawnArgs.GetFloat( "chatter_max", "10" ) );
|
||
} else {
|
||
snd = NULL;
|
||
}
|
||
|
||
if ( snd && *snd ) {
|
||
chat_snd = declManager->FindSound( snd );
|
||
|
||
// set the next chat time
|
||
chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
|
||
} else {
|
||
chat_snd = NULL;
|
||
}
|
||
}
|
||
|
||
/*
|
||
================
|
||
idAI::CanPlayChatterSounds
|
||
|
||
Used for playing chatter sounds on monsters.
|
||
================
|
||
*/
|
||
bool idAI::CanPlayChatterSounds( void ) const {
|
||
if ( AI_DEAD ) {
|
||
return false;
|
||
}
|
||
|
||
if ( IsHidden() ) {
|
||
return false;
|
||
}
|
||
|
||
if ( enemy.GetEntity() ) {
|
||
return true;
|
||
}
|
||
|
||
if ( spawnArgs.GetBool( "no_idle_chatter" ) ) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::PlayChatter
|
||
=====================
|
||
*/
|
||
void idAI::PlayChatter( void ) {
|
||
// check if it's time to play a chat sound
|
||
if ( AI_DEAD || !chat_snd || ( chat_time > gameLocal.time ) ) {
|
||
return;
|
||
}
|
||
|
||
StartSoundShader( chat_snd, SND_CHANNEL_VOICE, 0, false, NULL );
|
||
|
||
// set the next chat time
|
||
chat_time = gameLocal.time + chat_min + gameLocal.random.RandomFloat() * ( chat_max - chat_min );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::UpdateParticles
|
||
=====================
|
||
*/
|
||
void idAI::UpdateParticles( void ) {
|
||
if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
|
||
idVec3 realVector;
|
||
idMat3 realAxis;
|
||
|
||
int particlesAlive = 0;
|
||
for ( int i = 0; i < particles.Num(); i++ ) {
|
||
if ( particles[i].particle && particles[i].time ) {
|
||
particlesAlive++;
|
||
if (af.IsActive()) {
|
||
realAxis = mat3_identity;
|
||
realVector = GetPhysics()->GetOrigin();
|
||
} else {
|
||
animator.GetJointTransform( particles[i].joint, gameLocal.time, realVector, realAxis );
|
||
realAxis *= renderEntity.axis;
|
||
realVector = physicsObj.GetOrigin() + ( realVector + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() );
|
||
}
|
||
|
||
if ( !gameLocal.smokeParticles->EmitSmoke( particles[i].particle, particles[i].time, gameLocal.random.CRandomFloat(), realVector, realAxis )) {
|
||
if ( restartParticles ) {
|
||
particles[i].time = gameLocal.time;
|
||
} else {
|
||
particles[i].time = 0;
|
||
particlesAlive--;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if ( particlesAlive == 0 ) {
|
||
BecomeInactive( TH_UPDATEPARTICLES );
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idAI::TriggerParticles
|
||
=====================
|
||
*/
|
||
void idAI::TriggerParticles( const char *jointName ) {
|
||
jointHandle_t jointNum;
|
||
|
||
jointNum = animator.GetJointHandle( jointName );
|
||
for ( int i = 0; i < particles.Num(); i++ ) {
|
||
if ( particles[i].joint == jointNum ) {
|
||
particles[i].time = gameLocal.time;
|
||
BecomeActive( TH_UPDATEPARTICLES );
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/***********************************************************************
|
||
|
||
Head & torso aiming
|
||
|
||
***********************************************************************/
|
||
|
||
/*
|
||
================
|
||
idAI::UpdateAnimationControllers
|
||
================
|
||
*/
|
||
bool idAI::UpdateAnimationControllers( void ) {
|
||
idVec3 local;
|
||
idVec3 focusPos;
|
||
idQuat jawQuat;
|
||
idVec3 left;
|
||
idVec3 dir;
|
||
idVec3 orientationJointPos;
|
||
idVec3 localDir;
|
||
idAngles newLookAng;
|
||
idAngles diff;
|
||
idMat3 mat;
|
||
idMat3 axis;
|
||
idMat3 orientationJointAxis;
|
||
idAFAttachment *headEnt = head.GetEntity();
|
||
idVec3 eyepos;
|
||
idVec3 pos;
|
||
int i;
|
||
idAngles jointAng;
|
||
float orientationJointYaw;
|
||
|
||
if ( AI_DEAD ) {
|
||
return idActor::UpdateAnimationControllers();
|
||
}
|
||
|
||
if ( orientationJoint == INVALID_JOINT ) {
|
||
orientationJointAxis = viewAxis;
|
||
orientationJointPos = physicsObj.GetOrigin();
|
||
orientationJointYaw = current_yaw;
|
||
} else {
|
||
GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
|
||
orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
|
||
orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
|
||
}
|
||
|
||
if ( focusJoint != INVALID_JOINT ) {
|
||
if ( headEnt ) {
|
||
headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
|
||
} else {
|
||
GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
|
||
}
|
||
eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z;
|
||
if ( ai_debugMove.GetBool() ) {
|
||
gameRenderWorld->DebugLine( colorRed, eyepos, eyepos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
|
||
}
|
||
} else {
|
||
eyepos = GetEyePosition();
|
||
}
|
||
|
||
if ( headEnt ) {
|
||
CopyJointsFromBodyToHead();
|
||
}
|
||
|
||
// Update the IK after we've gotten all the joint positions we need, but before we set any joint positions.
|
||
// Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which
|
||
// are already up to date because of getting the joints in this function) and then sets their positions, which
|
||
// forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled,
|
||
// or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no
|
||
// head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number
|
||
// in order to see how many times an entity transforms the joints per frame.
|
||
idActor::UpdateAnimationControllers();
|
||
|
||
idEntity *focusEnt = focusEntity.GetEntity();
|
||
if ( !allowJointMod || !allowEyeFocus || ( gameLocal.time >= focusTime ) ) {
|
||
focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f;
|
||
} else if ( focusEnt == NULL ) {
|
||
// keep looking at last position until focusTime is up
|
||
focusPos = currentFocusPos;
|
||
} else if ( focusEnt == enemy.GetEntity() ) {
|
||
focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal();
|
||
} else if ( focusEnt->IsType( idActor::Type ) ) {
|
||
focusPos = static_cast<idActor *>( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal();
|
||
} else {
|
||
focusPos = focusEnt->GetPhysics()->GetOrigin();
|
||
}
|
||
|
||
currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
|
||
|
||
// determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles
|
||
dir = focusPos - orientationJointPos;
|
||
newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw );
|
||
newLookAng.roll = 0.0f;
|
||
newLookAng.pitch = 0.0f;
|
||
|
||
#if 0
|
||
gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec );
|
||
#endif
|
||
|
||
// determine pitch from joint position
|
||
dir = focusPos - eyepos;
|
||
dir.NormalizeFast();
|
||
orientationJointAxis.ProjectVector( dir, localDir );
|
||
newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() );
|
||
newLookAng.roll = 0.0f;
|
||
|
||
diff = newLookAng - lookAng;
|
||
|
||
if ( eyeAng != diff ) {
|
||
eyeAng = diff;
|
||
eyeAng.Clamp( eyeMin, eyeMax );
|
||
idAngles angDelta = diff - eyeAng;
|
||
if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
|
||
alignHeadTime = gameLocal.time;
|
||
} else {
|
||
alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
|
||
}
|
||
}
|
||
|
||
if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
|
||
alignHeadTime = gameLocal.time;
|
||
}
|
||
|
||
if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) {
|
||
alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
|
||
destLookAng = newLookAng;
|
||
destLookAng.Clamp( lookMin, lookMax );
|
||
}
|
||
|
||
diff = destLookAng - lookAng;
|
||
if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) {
|
||
if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) {
|
||
diff.pitch = 360.0f - diff.pitch;
|
||
}
|
||
}
|
||
if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
|
||
if ( diff.yaw > 180.0f ) {
|
||
diff.yaw -= 360.0f;
|
||
} else if ( diff.yaw <= -180.0f ) {
|
||
diff.yaw += 360.0f;
|
||
}
|
||
}
|
||
lookAng = lookAng + diff * headFocusRate;
|
||
lookAng.Normalize180();
|
||
|
||
jointAng.roll = 0.0f;
|
||
for( i = 0; i < lookJoints.Num(); i++ ) {
|
||
jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch;
|
||
jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw;
|
||
animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
|
||
}
|
||
|
||
if ( move.moveType == MOVETYPE_FLY ) {
|
||
// lean into turns
|
||
AdjustFlyingAngles();
|
||
}
|
||
|
||
if ( headEnt ) {
|
||
idAnimator *headAnimator = headEnt->GetAnimator();
|
||
|
||
if ( allowEyeFocus ) {
|
||
idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose();
|
||
axis = eyeAxis * orientationJointAxis;
|
||
left = axis[ 1 ] * eyeHorizontalOffset;
|
||
eyepos -= headEnt->GetPhysics()->GetOrigin();
|
||
headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose );
|
||
headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose );
|
||
} else {
|
||
headAnimator->ClearJoint( leftEyeJoint );
|
||
headAnimator->ClearJoint( rightEyeJoint );
|
||
}
|
||
} else {
|
||
if ( allowEyeFocus ) {
|
||
idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3();
|
||
axis = eyeAxis * orientationJointAxis;
|
||
left = axis[ 1 ] * eyeHorizontalOffset;
|
||
eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin();
|
||
animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left );
|
||
animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left );
|
||
} else {
|
||
animator.ClearJoint( leftEyeJoint );
|
||
animator.ClearJoint( rightEyeJoint );
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/***********************************************************************
|
||
|
||
idCombatNode
|
||
|
||
***********************************************************************/
|
||
|
||
const idEventDef EV_CombatNode_MarkUsed( "markUsed" );
|
||
|
||
CLASS_DECLARATION( idEntity, idCombatNode )
|
||
EVENT( EV_CombatNode_MarkUsed, idCombatNode::Event_MarkUsed )
|
||
EVENT( EV_Activate, idCombatNode::Event_Activate )
|
||
END_CLASS
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::idCombatNode
|
||
=====================
|
||
*/
|
||
idCombatNode::idCombatNode( void ) {
|
||
min_dist = 0.0f;
|
||
max_dist = 0.0f;
|
||
cone_dist = 0.0f;
|
||
min_height = 0.0f;
|
||
max_height = 0.0f;
|
||
cone_left.Zero();
|
||
cone_right.Zero();
|
||
offset.Zero();
|
||
disabled = false;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::Save
|
||
=====================
|
||
*/
|
||
void idCombatNode::Save( idSaveGame *savefile ) const {
|
||
savefile->WriteFloat( min_dist );
|
||
savefile->WriteFloat( max_dist );
|
||
savefile->WriteFloat( cone_dist );
|
||
savefile->WriteFloat( min_height );
|
||
savefile->WriteFloat( max_height );
|
||
savefile->WriteVec3( cone_left );
|
||
savefile->WriteVec3( cone_right );
|
||
savefile->WriteVec3( offset );
|
||
savefile->WriteBool( disabled );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::Restore
|
||
=====================
|
||
*/
|
||
void idCombatNode::Restore( idRestoreGame *savefile ) {
|
||
savefile->ReadFloat( min_dist );
|
||
savefile->ReadFloat( max_dist );
|
||
savefile->ReadFloat( cone_dist );
|
||
savefile->ReadFloat( min_height );
|
||
savefile->ReadFloat( max_height );
|
||
savefile->ReadVec3( cone_left );
|
||
savefile->ReadVec3( cone_right );
|
||
savefile->ReadVec3( offset );
|
||
savefile->ReadBool( disabled );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::Spawn
|
||
=====================
|
||
*/
|
||
void idCombatNode::Spawn( void ) {
|
||
float fov;
|
||
float yaw;
|
||
float height;
|
||
|
||
min_dist = spawnArgs.GetFloat( "min" );
|
||
max_dist = spawnArgs.GetFloat( "max" );
|
||
height = spawnArgs.GetFloat( "height" );
|
||
fov = spawnArgs.GetFloat( "fov", "60" );
|
||
offset = spawnArgs.GetVector( "offset" );
|
||
|
||
const idVec3 &org = GetPhysics()->GetOrigin() + offset;
|
||
min_height = org.z - height * 0.5f;
|
||
max_height = min_height + height;
|
||
|
||
const idMat3 &axis = GetPhysics()->GetAxis();
|
||
yaw = axis[ 0 ].ToYaw();
|
||
|
||
idAngles leftang( 0.0f, yaw + fov * 0.5f - 90.0f, 0.0f );
|
||
cone_left = leftang.ToForward();
|
||
|
||
idAngles rightang( 0.0f, yaw - fov * 0.5f + 90.0f, 0.0f );
|
||
cone_right = rightang.ToForward();
|
||
|
||
disabled = spawnArgs.GetBool( "start_off" );
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::IsDisabled
|
||
=====================
|
||
*/
|
||
bool idCombatNode::IsDisabled( void ) const {
|
||
return disabled;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::DrawDebugInfo
|
||
=====================
|
||
*/
|
||
void idCombatNode::DrawDebugInfo( void ) {
|
||
idEntity *ent;
|
||
idCombatNode *node;
|
||
idPlayer *player = gameLocal.GetLocalPlayer();
|
||
idVec4 color;
|
||
idBounds bounds( idVec3( -16, -16, 0 ), idVec3( 16, 16, 0 ) );
|
||
|
||
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
|
||
if ( !ent->IsType( idCombatNode::Type ) ) {
|
||
continue;
|
||
}
|
||
|
||
node = static_cast<idCombatNode *>( ent );
|
||
if ( node->disabled ) {
|
||
color = colorMdGrey;
|
||
} else if ( player && node->EntityInView( player, player->GetPhysics()->GetOrigin() ) ) {
|
||
color = colorYellow;
|
||
} else {
|
||
color = colorRed;
|
||
}
|
||
|
||
idVec3 leftDir( -node->cone_left.y, node->cone_left.x, 0.0f );
|
||
idVec3 rightDir( node->cone_right.y, -node->cone_right.x, 0.0f );
|
||
idVec3 org = node->GetPhysics()->GetOrigin() + node->offset;
|
||
|
||
bounds[ 1 ].z = node->max_height;
|
||
|
||
leftDir.NormalizeFast();
|
||
rightDir.NormalizeFast();
|
||
|
||
const idMat3 &axis = node->GetPhysics()->GetAxis();
|
||
float cone_dot = node->cone_right * axis[ 1 ];
|
||
if ( idMath::Fabs( cone_dot ) > 0.1 ) {
|
||
float cone_dist = node->max_dist / cone_dot;
|
||
idVec3 pos1 = org + leftDir * node->min_dist;
|
||
idVec3 pos2 = org + leftDir * cone_dist;
|
||
idVec3 pos3 = org + rightDir * node->min_dist;
|
||
idVec3 pos4 = org + rightDir * cone_dist;
|
||
|
||
gameRenderWorld->DebugLine( color, node->GetPhysics()->GetOrigin(), ( pos1 + pos3 ) * 0.5f, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( color, pos1, pos2, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( color, pos1, pos3, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( color, pos3, pos4, gameLocal.msec );
|
||
gameRenderWorld->DebugLine( color, pos2, pos4, gameLocal.msec );
|
||
gameRenderWorld->DebugBounds( color, bounds, org, gameLocal.msec );
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::EntityInView
|
||
=====================
|
||
*/
|
||
bool idCombatNode::EntityInView( idActor *actor, const idVec3 &pos ) {
|
||
if ( !actor || ( actor->health <= 0 ) ) {
|
||
return false;
|
||
}
|
||
|
||
const idBounds &bounds = actor->GetPhysics()->GetBounds();
|
||
if ( ( pos.z + bounds[ 1 ].z < min_height ) || ( pos.z + bounds[ 0 ].z >= max_height ) ) {
|
||
return false;
|
||
}
|
||
|
||
const idVec3 &org = GetPhysics()->GetOrigin() + offset;
|
||
const idMat3 &axis = GetPhysics()->GetAxis();
|
||
idVec3 dir = pos - org;
|
||
float dist = dir * axis[ 0 ];
|
||
|
||
if ( ( dist < min_dist ) || ( dist > max_dist ) ) {
|
||
return false;
|
||
}
|
||
|
||
float left_dot = dir * cone_left;
|
||
if ( left_dot < 0.0f ) {
|
||
return false;
|
||
}
|
||
|
||
float right_dot = dir * cone_right;
|
||
if ( right_dot < 0.0f ) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::Event_Activate
|
||
=====================
|
||
*/
|
||
void idCombatNode::Event_Activate( idEntity *activator ) {
|
||
disabled = !disabled;
|
||
}
|
||
|
||
/*
|
||
=====================
|
||
idCombatNode::Event_MarkUsed
|
||
=====================
|
||
*/
|
||
void idCombatNode::Event_MarkUsed( void ) {
|
||
if ( spawnArgs.GetBool( "use_once" ) ) {
|
||
disabled = true;
|
||
}
|
||
}
|
||
|
||
|