2923 lines
No EOL
74 KiB
C
2923 lines
No EOL
74 KiB
C
/*
|
|
==============================
|
|
|
|
MG AI
|
|
NEW LOW-LEVEL MOVEMENT CODE
|
|
|
|
==============================
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include "g_local.h"
|
|
#include "g_monster.h"
|
|
#include "Random.h"
|
|
#include "vector.h"
|
|
#include "buoy.h"
|
|
#include "g_HitLocation.h"
|
|
#include "Utilities.h"
|
|
#include "m_stats.h"
|
|
#include "g_playstats.h"
|
|
#include "fx.h"
|
|
|
|
#define STEPSIZE 18
|
|
#define YAW_IDEAL 1
|
|
#define YAW_BEST_MOVE 0
|
|
|
|
extern cvar_t *maxclients;
|
|
|
|
//void gkrokon_maintain_waypoints(edict_t *self, float mintel, float foo1, float foo2);
|
|
void ssithraCheckJump (edict_t *self);
|
|
void SV_FixCheckBottom (edict_t *ent);
|
|
qboolean clear_visible (edict_t *self, edict_t *other);
|
|
float ai_face_goal (edict_t *self);
|
|
void ai_flee (edict_t *self, float dist);
|
|
void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope);
|
|
void BecomeDebris(edict_t *self);
|
|
qboolean MG_MoveToGoal (edict_t *self, float dist);
|
|
void MG_BuoyNavigate(edict_t *self);
|
|
qboolean MG_GoToRandomBuoy(edict_t *self);
|
|
|
|
qboolean TB_CheckBottom (edict_t *self);
|
|
qboolean TB_CheckJump (edict_t *self);
|
|
|
|
// AI Targeting Globals
|
|
qboolean enemy_vis; // TRUE if enemy is visible
|
|
qboolean enemy_infront; // TRUE if enemy is in front
|
|
int enemy_range; // range from enemy RANGE_MELEE, RANGE_NEAR, RANGE_MID, RANGE_FAR
|
|
float enemy_yaw; // ideal yaw to face enemy
|
|
vec3_t JUMP_MINS = {-8, -8, 0};
|
|
vec3_t JUMP_MAXS = {8, 8, 4};
|
|
|
|
char *HitLocName [hl_Max] =
|
|
{
|
|
"NonSpecific",//0
|
|
"Head",//1
|
|
"TorsoFront",
|
|
"TorsoBack",
|
|
"ArmUpperLeft",
|
|
"ArmLowerLeft",
|
|
"ArmUpperRight",
|
|
"ArmLowerRight",
|
|
"LegUpperLeft",
|
|
"LegLowerLeft",
|
|
"LegUpperRight",
|
|
"LegLowerRight",
|
|
"BipedPoints",
|
|
"WingedPoints",
|
|
"extra14",
|
|
"extra15",
|
|
"MeleeHit",//16
|
|
};
|
|
|
|
//============================================================================
|
|
|
|
/*
|
|
=============
|
|
ahead
|
|
|
|
returns 1 if the entity is in front (dot > 0.8) of self
|
|
=============
|
|
*/
|
|
qboolean ahead (edict_t *self, edict_t *other)
|
|
{
|
|
vec3_t vec;
|
|
float dot;
|
|
vec3_t forward, check_angles;
|
|
|
|
if(Vec3NotZero(self->v_angle_ofs))
|
|
VectorAdd(self->v_angle_ofs,self->s.angles,check_angles);
|
|
else
|
|
VectorCopy(self->s.angles,check_angles);
|
|
|
|
AngleVectors (check_angles, forward, NULL, NULL);
|
|
VectorSubtract (other->s.origin, self->s.origin, vec);
|
|
VectorNormalize (vec);
|
|
dot = DotProduct (vec, forward);
|
|
|
|
if (dot > 0.8)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LOS
|
|
|
|
returns true if there is an unobstructed spot from point1 to point2
|
|
=============
|
|
*/
|
|
qboolean LOS (edict_t *self, vec3_t point1, vec3_t point2)
|
|
{
|
|
trace_t trace;
|
|
|
|
gi.trace (point1, vec3_origin, vec3_origin, point2, self, MASK_SOLID,&trace);
|
|
|
|
if (trace.fraction == 1.0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
visible_pos
|
|
|
|
returns 1 if the entity is visible to self, even if not infront ()
|
|
=============
|
|
*/
|
|
qboolean visible_pos (edict_t *self, vec3_t spot2)
|
|
{
|
|
vec3_t spot1;
|
|
trace_t trace;
|
|
|
|
VectorCopy (self->s.origin, spot1);
|
|
spot1[2] += self->viewheight;
|
|
gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE,&trace);
|
|
|
|
if (trace.fraction == 1.0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
MG_CheckBottom
|
|
|
|
Returns false if any 2 adjacent cornerpoints of the bottom of the
|
|
entity is off an edge that is not a staircase.
|
|
=============
|
|
*/
|
|
qboolean MG_CheckBottom (edict_t *ent)
|
|
{
|
|
vec3_t mins, maxs, start, stop;
|
|
trace_t trace;
|
|
int x, y, corner;
|
|
float stepsize;
|
|
|
|
qboolean corner_ok[4];
|
|
qboolean easy_ok[2][2];
|
|
qboolean realcheck = false;
|
|
|
|
//normal corner checking
|
|
if(ent->classID==CID_TBEAST)
|
|
{
|
|
return TB_CheckBottom(ent);
|
|
// VectorAdd (ent->s.origin, ent->mins, mins);
|
|
// VectorAdd (ent->s.origin, ent->maxs, maxs);
|
|
// stepsize = 54;//ent->size[2];//very lenient for stepping off stuff
|
|
}
|
|
else//lenient, max 16 corner checking
|
|
{
|
|
VectorCopy (ent->mins, mins);
|
|
VectorCopy (ent->maxs, maxs);
|
|
|
|
//some leniency is ok here, no?
|
|
mins[0] *= 0.75;
|
|
mins[1] *= 0.75;
|
|
maxs[0] *= 0.75;
|
|
maxs[1] *= 0.75;
|
|
|
|
//keep corner checks within 16 of center
|
|
if(mins[0] < -16)
|
|
mins[0] = -16;
|
|
if(mins[1] < -16)
|
|
mins[1] = -16;
|
|
if(maxs[0] > 16)
|
|
maxs[0] = 16;
|
|
if(maxs[1] > 16)
|
|
maxs[1] = 16;
|
|
|
|
if(ent->maxs[0] > maxs[0])
|
|
stepsize = STEPSIZE + (ent->maxs[0] - maxs[0]);
|
|
|
|
VectorAdd (ent->s.origin, mins, mins);
|
|
VectorAdd (ent->s.origin, maxs, maxs);
|
|
}
|
|
|
|
// if all of the points under the corners are solid world, don't bother
|
|
// with the tougher checks
|
|
// the corners must be within 16 of the midpoint
|
|
|
|
start[2] = mins[2] - 1;
|
|
corner = 0;
|
|
for (x=0 ; x<=1 ; x++)//0, 0; 0, 1; 1, 0; 1, 1;
|
|
{
|
|
for (y=0 ; y<=1 ; y++)
|
|
{
|
|
start[0] = x ? maxs[0] : mins[0];
|
|
start[1] = y ? maxs[1] : mins[1];
|
|
if (gi.pointcontents (start) != CONTENTS_SOLID)
|
|
{//only do realcheck if two adjecent corners off ledge
|
|
switch(corner)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
if(!corner_ok[0])
|
|
realcheck = true;
|
|
break;
|
|
case 3:
|
|
if(!corner_ok[2] || !corner_ok[1])
|
|
realcheck = true;
|
|
break;
|
|
}
|
|
easy_ok[x][y] = corner_ok[corner] = false;
|
|
}
|
|
else
|
|
{
|
|
// if(ent->classID==CID_TBEAST)
|
|
// {
|
|
// ent->groundentity = world;
|
|
// return true;//super hack- let big guy go up slopes
|
|
// }
|
|
//check them all to make realcheck faster
|
|
easy_ok[x][y] = corner_ok[corner] = true;
|
|
}
|
|
|
|
corner++;
|
|
}
|
|
}
|
|
|
|
if(!realcheck)
|
|
return true; // we got out easy
|
|
|
|
//
|
|
// check it for real...
|
|
//
|
|
start[2] = mins[2];//bottom
|
|
stop[2] = start[2] - stepsize + 1;//2*STEPSIZE;//bottom - 36
|
|
|
|
// the corners must be within 16 of the midpoint
|
|
corner = 0;
|
|
for (x=0 ; x<=1 ; x++)
|
|
{
|
|
for (y=0 ; y<=1 ; y++)
|
|
{
|
|
if(!easy_ok[x][y])
|
|
{//don't trace the ones that were ok in the easy check
|
|
start[0] = stop[0] = x ? maxs[0] : mins[0];
|
|
start[1] = stop[1] = y ? maxs[1] : mins[1];
|
|
|
|
gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID,&trace);
|
|
|
|
if (trace.fraction >= 1.0)// || start[2] - trace.endpos[2] > STEPSIZE)
|
|
{//this point is off too high of a step
|
|
switch(corner)
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
if(!corner_ok[0])
|
|
return false;
|
|
break;
|
|
case 3:
|
|
if(!corner_ok[2] || !corner_ok[1])
|
|
return false;
|
|
break;
|
|
}
|
|
corner_ok[corner] = false;
|
|
}
|
|
else//only return false if two adjacent corners off a ledge
|
|
{
|
|
if(ent->classID==CID_TBEAST)
|
|
{
|
|
// ent->groundentity = trace.ent;
|
|
return true;//super hack- let big guy go up slopes
|
|
}
|
|
corner_ok[corner] = true;
|
|
}
|
|
}
|
|
else
|
|
corner_ok[corner] = true;
|
|
|
|
corner++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
MG_MoveStep
|
|
|
|
Called by monster program code.
|
|
The move will be adjusted for slopes and stairs, but if the move isn't
|
|
possible, no move is done, false is returned, and
|
|
pr_global_struct->trace_normal is set to the normal of the blocking wall
|
|
=============
|
|
*/
|
|
//FIXME since we need to test end position contents here, can we avoid doing
|
|
//it again later in catagorize position?
|
|
trace_t MG_MoveStep (edict_t *self, vec3_t move, qboolean relink)
|
|
{//only relinks if move succeeds
|
|
float dz;
|
|
vec3_t save_org, test_org, end;
|
|
trace_t trace;
|
|
int i;
|
|
float stepsize;
|
|
vec3_t test;
|
|
int contents, clipmask;
|
|
qboolean slip_under = false;
|
|
|
|
trace.succeeded = false;
|
|
// try the move
|
|
VectorCopy (self->s.origin, save_org);
|
|
if(self->monsterinfo.scale)
|
|
{//scale movement by monster's scale
|
|
//scale here, not before since any function can call this
|
|
VectorScale(move, self->monsterinfo.scale, move);
|
|
}
|
|
|
|
VectorAdd (self->s.origin, move, test_org);
|
|
|
|
|
|
//SWIM AND FLY MONSTERS
|
|
// flying monsters don't step up
|
|
if ( self->flags & (FL_SWIM | FL_FLY) )
|
|
{
|
|
// try one move with vertical motion, then one without
|
|
for (i=0 ; i<2 ; i++)
|
|
{
|
|
VectorAdd (self->s.origin, move, test_org);
|
|
if (i == 0 && self->enemy)
|
|
{
|
|
if (!self->goalentity)
|
|
self->goalentity = self->enemy;
|
|
dz = self->s.origin[2] - self->goalentity->s.origin[2];
|
|
if (self->goalentity->client)
|
|
{
|
|
if (dz > 40)
|
|
test_org[2] -= 8;
|
|
if (!((self->flags & FL_SWIM) && (self->waterlevel < 2)))
|
|
if (dz < 30)
|
|
test_org[2] += 8;
|
|
}
|
|
else
|
|
{
|
|
if (dz > 8)
|
|
test_org[2] -= 8;
|
|
else if (dz > 0)
|
|
test_org[2] -= dz;
|
|
else if (dz < -8)
|
|
test_org[2] += 8;
|
|
else
|
|
test_org[2] += dz;
|
|
}
|
|
}
|
|
gi.trace (self->s.origin, self->mins, self->maxs, test_org, self, MASK_MONSTERSOLID,&trace);
|
|
|
|
// fly monsters don't enter water voluntarily
|
|
if (self->flags & FL_FLY)
|
|
{
|
|
if (!self->waterlevel)
|
|
{
|
|
test[0] = trace.endpos[0];
|
|
test[1] = trace.endpos[1];
|
|
test[2] = trace.endpos[2] + self->mins[2] + 1;
|
|
contents = gi.pointcontents(test);
|
|
if (contents & MASK_WATER)
|
|
{
|
|
QPostMessage(self, MSG_BLOCKED, PRI_DIRECTIVE, NULL);
|
|
return trace;
|
|
}
|
|
}
|
|
}
|
|
|
|
// swim monsters don't exit water voluntarily
|
|
if (self->flags & FL_SWIM)
|
|
{
|
|
if (self->waterlevel < 2)
|
|
{
|
|
test[0] = trace.endpos[0];
|
|
test[1] = trace.endpos[1];
|
|
test[2] = trace.endpos[2] + self->mins[2] + 1;
|
|
contents = gi.pointcontents(test);
|
|
if (!(contents & MASK_WATER))
|
|
{
|
|
QPostMessage(self, MSG_BLOCKED, PRI_DIRECTIVE, NULL);
|
|
return trace;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (trace.fraction == 1)
|
|
{
|
|
VectorCopy (trace.endpos, self->s.origin);
|
|
if (relink)
|
|
{
|
|
gi.linkentity (self);
|
|
G_TouchTriggers (self);
|
|
}
|
|
return trace;//true
|
|
}
|
|
|
|
if (!self->enemy)
|
|
break;
|
|
}
|
|
|
|
return trace;
|
|
}
|
|
//WALK MONSTERS
|
|
// push down from a step height above the wished position
|
|
clipmask = MASK_MONSTERSOLID;
|
|
if (!(self->monsterinfo.aiflags & AI_NOSTEP))
|
|
{
|
|
if(self->classID==CID_TBEAST)
|
|
{
|
|
clipmask = MASK_SOLID;
|
|
stepsize = STEPSIZE * 3;
|
|
}
|
|
else
|
|
stepsize = STEPSIZE;
|
|
}
|
|
else
|
|
stepsize = 1;
|
|
|
|
test_org[2] += stepsize;
|
|
VectorCopy (test_org, end);
|
|
end[2] -= stepsize*2;
|
|
|
|
gi.trace (test_org, self->mins, self->maxs, end, self, clipmask,&trace);
|
|
|
|
if (trace.allsolid)
|
|
{//the step up/down is all solid in front
|
|
QPostMessage(self, MSG_BLOCKED, PRI_DIRECTIVE, NULL);
|
|
return trace;
|
|
}
|
|
|
|
//NOTE: if did the forward trace above, CAN'T have startsolid, so this is ok
|
|
if (trace.startsolid)
|
|
{//can't step up, try down
|
|
test_org[2] -= stepsize;
|
|
gi.trace (test_org, self->mins, self->maxs, end, self, clipmask,&trace);
|
|
if (trace.allsolid || trace.startsolid)
|
|
{
|
|
if(trace.ent)
|
|
{
|
|
if(trace.ent->client)
|
|
{
|
|
slip_under = true;//lets rats walk between legs
|
|
}
|
|
}
|
|
if(!slip_under)
|
|
{
|
|
QPostMessage(self, MSG_BLOCKED, PRI_DIRECTIVE, NULL);
|
|
return trace;
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't go in to water unless only 40% hieght deep or an amphibian
|
|
if (self->waterlevel == 0)
|
|
{//not currently in water
|
|
test[0] = trace.endpos[0];
|
|
test[1] = trace.endpos[1];
|
|
test[2] = trace.endpos[2] + self->mins[2];// + 1;
|
|
test[2] += (self->maxs[2] - self->mins[2]) * 0.4;
|
|
contents = gi.pointcontents(test);
|
|
|
|
if (contents & MASK_WATER && !(self->flags & FL_AMPHIBIAN))
|
|
return trace;
|
|
}
|
|
|
|
if (trace.fraction == 1)
|
|
{//too long of a step down
|
|
if ( self->flags & FL_PARTIALGROUND ||
|
|
self->svflags & SVF_FLOAT ||
|
|
self->classID == CID_TBEAST ||
|
|
(contents&MASK_WATER && self->flags & FL_AMPHIBIAN))//allow swimming monsters to step off ledges into water
|
|
{// if monster had the ground pulled out, go ahead and fall
|
|
//DO THE MOVE!
|
|
VectorAdd (self->s.origin, move, self->s.origin);
|
|
if (relink)
|
|
{
|
|
gi.linkentity (self);
|
|
G_TouchTriggers (self);
|
|
}
|
|
self->groundentity = NULL;
|
|
// SV_Printf ("fall down\n");
|
|
trace.succeeded = true;
|
|
return trace;//true!
|
|
}
|
|
QPostMessage(self, MSG_BLOCKED, PRI_DIRECTIVE, NULL);
|
|
return trace; // walked off an edge
|
|
}
|
|
|
|
// check point traces down for dangling corners
|
|
//DO THE MOVE!
|
|
//ok, put me there
|
|
VectorCopy (trace.endpos, self->s.origin);
|
|
|
|
if(contents&MASK_WATER && self->flags & FL_AMPHIBIAN);
|
|
else if (!MG_CheckBottom(self))// && self->classID!=CID_TBEAST)
|
|
{//uh oh, not completely on solid ground
|
|
if ( self->flags & FL_PARTIALGROUND || self->svflags & SVF_FLOAT)
|
|
{ // entity had floor mostly pulled out from underneath it
|
|
// and is trying to correct or can float
|
|
if (relink)
|
|
{
|
|
gi.linkentity (self);
|
|
G_TouchTriggers (self);
|
|
}
|
|
trace.succeeded = true;
|
|
return trace;//true!
|
|
}
|
|
//whoops, let's not make that move after all
|
|
VectorCopy (save_org, self->s.origin);
|
|
QPostMessage(self, MSG_BLOCKED, PRI_DIRECTIVE, NULL);
|
|
return trace;
|
|
}
|
|
|
|
//OK, we're on the ground completely now
|
|
if ( self->flags & FL_PARTIALGROUND )
|
|
{
|
|
self->flags &= ~FL_PARTIALGROUND;
|
|
}
|
|
self->groundentity = trace.ent;
|
|
self->groundentity_linkcount = trace.ent->linkcount;
|
|
|
|
// the move is ok
|
|
if (relink)
|
|
{
|
|
gi.linkentity (self);
|
|
G_TouchTriggers (self);
|
|
}
|
|
else
|
|
VectorCopy (save_org, self->s.origin);
|
|
|
|
trace.succeeded = true;
|
|
return trace;//true!
|
|
}
|
|
|
|
/*
|
|
===============
|
|
MG_ChangeYaw
|
|
|
|
===============
|
|
*/
|
|
float MG_ChangeWhichYaw (edict_t *self, qboolean ideal_yaw)
|
|
{
|
|
float ideal;
|
|
float current;
|
|
float move;
|
|
float speed;
|
|
|
|
current = anglemod(self->s.angles[YAW]);
|
|
if(ideal_yaw)
|
|
ideal = self->ideal_yaw;
|
|
else
|
|
ideal = self->best_move_yaw;
|
|
|
|
if (current == ideal)
|
|
return false;
|
|
|
|
move = ideal - current;
|
|
speed = self->yaw_speed;
|
|
if (ideal > current)
|
|
{
|
|
if (move >= 180)
|
|
move = move - 360;
|
|
}
|
|
else
|
|
{
|
|
if (move <= -180)
|
|
move = move + 360;
|
|
}
|
|
if (move > 0)
|
|
{
|
|
if (move > speed)
|
|
move = speed;
|
|
}
|
|
else
|
|
{
|
|
if (move < -speed)
|
|
move = -speed;
|
|
}
|
|
|
|
//normal anglemod doesn't have the precision I need to slide along walls
|
|
self->s.angles[YAW] = anglemod_old(current + move);
|
|
return move;
|
|
}
|
|
|
|
float MG_ChangeYaw (edict_t *self)
|
|
{
|
|
return MG_ChangeWhichYaw(self, YAW_IDEAL);
|
|
}
|
|
|
|
qboolean MG_GetGoalPos (edict_t *self, vec3_t goalpos)
|
|
{
|
|
qboolean charge_enemy = false;
|
|
|
|
if(self->monsterinfo.aiflags&AI_STRAIGHT_TO_ENEMY && self->enemy)
|
|
charge_enemy = true;
|
|
|
|
if (self->monsterinfo.searchType == SEARCH_BUOY && !charge_enemy)
|
|
{
|
|
if(self->buoy_index < 0 || self->buoy_index > level.active_buoys)
|
|
{
|
|
#ifdef _DEVEL
|
|
gi.dprintf("Error: SEARCH_BUOY but invalid index!!!\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(level.buoy_list[self->buoy_index].origin, self->monsterinfo.nav_goal);
|
|
VectorCopy(self->monsterinfo.nav_goal, goalpos);
|
|
}
|
|
else if(self->goalentity && !charge_enemy)
|
|
{
|
|
if(self->goalentity == self->enemy && self->ai_mood_flags & AI_MOOD_FLAG_PREDICT && !(self->spawnflags & MSF_FIXED))
|
|
{//predict where he's goin
|
|
M_PredictTargetPosition( self->enemy, self->enemy->velocity, 8, goalpos);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(self->goalentity->s.origin, goalpos);
|
|
}
|
|
}
|
|
else if(self->enemy)
|
|
{
|
|
if (self->ai_mood_flags & AI_MOOD_FLAG_PREDICT && !(self->spawnflags & MSF_FIXED))
|
|
{//predict where he's goin
|
|
M_PredictTargetPosition( self->enemy, self->enemy->velocity, 8, goalpos);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(self->enemy->s.origin, goalpos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("No goal to face!\n");
|
|
#endif
|
|
VectorClear(goalpos);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
float MG_FaceGoal (edict_t *self, qboolean doturn)
|
|
{
|
|
vec3_t vec, goalpos;
|
|
|
|
if(MG_GetGoalPos(self, goalpos))
|
|
{
|
|
VectorSubtract(goalpos, self->s.origin, vec);
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("No goal to face!\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
self->ideal_yaw = vectoyaw(vec);
|
|
|
|
if(doturn)
|
|
return MG_ChangeYaw(self);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
======================
|
|
MG_StepDirection
|
|
|
|
Turns to the movement direction, and walks the current distance if
|
|
facing it.
|
|
|
|
======================
|
|
*/
|
|
qboolean MG_StepDirection (edict_t *self, float yaw, float dist)
|
|
{
|
|
vec3_t move, forward, test_angles;
|
|
trace_t trace;
|
|
|
|
//find vector offset (move to add to origin)
|
|
test_angles[PITCH] = test_angles[ROLL] = 0;
|
|
test_angles[YAW] = yaw;
|
|
AngleVectors(test_angles, forward, NULL, NULL);
|
|
VectorScale(forward, dist, move);
|
|
|
|
//see if can move that way, but don't actually move
|
|
trace = MG_MoveStep (self, move, false);
|
|
|
|
if (trace.succeeded)
|
|
{//move was allowed
|
|
self->best_move_yaw = yaw;//new
|
|
self->monsterinfo.idle_time = level.time + flrand(0.5, 1.25);
|
|
// MG_ChangeWhichYaw (self, YAW_BEST_MOVE);//new
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
MG_NewDir
|
|
|
|
================
|
|
*/
|
|
#define DI_NODIR -1
|
|
void MG_NewDir (edict_t *self, float dist)
|
|
{
|
|
float deltax, deltay;
|
|
float d[3];
|
|
float test_ideal_yaw, old_yaw, turnaround;//, save_yaw;
|
|
vec3_t goal_org;
|
|
|
|
//FIXME: how did we get here with no enemy
|
|
if(!MG_GetGoalPos (self, goal_org))
|
|
return;
|
|
|
|
old_yaw = anglemod( (int)(self->ideal_yaw/45)*45 );
|
|
turnaround = anglemod(old_yaw - 180);
|
|
|
|
deltax = goal_org[0] - self->s.origin[0];
|
|
deltay = goal_org[1] - self->s.origin[1];
|
|
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)
|
|
test_ideal_yaw = d[2] == 90 ? 45 : 315;
|
|
else
|
|
test_ideal_yaw = d[2] == 90 ? 135 : 215;
|
|
|
|
if (test_ideal_yaw != turnaround && MG_StepDirection(self, test_ideal_yaw, dist))
|
|
return;
|
|
}
|
|
|
|
// try other directions
|
|
if ( irand(0, 1) || abs(deltay)>abs(deltax))
|
|
{
|
|
test_ideal_yaw=d[1];
|
|
d[1]=d[2];
|
|
d[2]=test_ideal_yaw;
|
|
}
|
|
|
|
if (d[1]!=DI_NODIR && d[1]!=turnaround
|
|
&& MG_StepDirection(self, d[1], dist))
|
|
return;
|
|
|
|
if (d[2]!=DI_NODIR && d[2]!=turnaround
|
|
&& MG_StepDirection(self, d[2], dist))
|
|
return;
|
|
|
|
/* there is no direct path to the player, so pick another direction */
|
|
|
|
if (old_yaw!=DI_NODIR && MG_StepDirection(self, old_yaw, dist))
|
|
return;
|
|
|
|
if (irand(0, 1)) /*randomly determine direction of search*/
|
|
{
|
|
for (test_ideal_yaw=0 ; test_ideal_yaw<=315 ; test_ideal_yaw += 45)
|
|
if (test_ideal_yaw!=turnaround && MG_StepDirection(self, test_ideal_yaw, dist) )
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
for (test_ideal_yaw=315 ; test_ideal_yaw >=0 ; test_ideal_yaw -= 45)
|
|
if (test_ideal_yaw!=turnaround && MG_StepDirection(self, test_ideal_yaw, dist) )
|
|
return;
|
|
}
|
|
|
|
if (turnaround != DI_NODIR && MG_StepDirection(self, turnaround, dist) )
|
|
return;
|
|
|
|
// can't move, restore yaw?
|
|
|
|
// if a bridge was pulled out from underneath a monster, it may not have
|
|
// a valid standing position at all
|
|
|
|
if (!MG_CheckBottom (self))// && self->classID!=CID_TBEAST)
|
|
SV_FixCheckBottom (self);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
infront_pos
|
|
|
|
returns 1 if the spot is in front (in sight) of self
|
|
=============
|
|
*/
|
|
qboolean infront_pos (edict_t *self, vec3_t pos)
|
|
{
|
|
vec3_t vec;
|
|
float dot;
|
|
vec3_t forward, check_angles;
|
|
|
|
if(Vec3NotZero(self->v_angle_ofs))
|
|
VectorAdd(self->v_angle_ofs,self->s.angles,check_angles);
|
|
else
|
|
VectorCopy(self->s.angles,check_angles);
|
|
|
|
AngleVectors (check_angles, forward, NULL, NULL);
|
|
VectorSubtract (pos, self->s.origin, vec);
|
|
VectorNormalize (vec);
|
|
dot = DotProduct (vec, forward);
|
|
|
|
if (dot > 0.3)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
qboolean MG_ExtraCheckJump (edict_t *self)
|
|
{
|
|
vec3_t vf, source, source2, targ_org, targ_mins;
|
|
vec3_t maxs, mins, save_org;
|
|
trace_t trace;
|
|
float hgt_diff, jump_fdist;
|
|
qboolean jump_up_check = false;
|
|
qboolean check_down = false;
|
|
qboolean can_move = false;
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Extra Check Jump\n");
|
|
#endif
|
|
if (self->monsterinfo.searchType == SEARCH_BUOY)
|
|
{
|
|
if(self->buoy_index < 0 || self->buoy_index > level.active_buoys)
|
|
return false;
|
|
|
|
VectorCopy(level.buoy_list[self->buoy_index].origin, targ_org);
|
|
|
|
if (!(infront_pos(self, targ_org)))
|
|
return false;
|
|
|
|
if (targ_org[2] < self->s.origin[2] - 28)
|
|
{
|
|
check_down = true;
|
|
}
|
|
else
|
|
{
|
|
check_down = false;
|
|
}
|
|
VectorClear(targ_mins);
|
|
}
|
|
else
|
|
{
|
|
if(!self->goalentity)
|
|
return false;
|
|
|
|
if (!(infront(self, self->goalentity)))
|
|
return false;
|
|
|
|
if (self->goalentity->s.origin[2] < self->s.origin[2] - 28)
|
|
{
|
|
check_down = true;
|
|
}
|
|
else
|
|
{
|
|
check_down = false;
|
|
}
|
|
|
|
VectorCopy(self->goalentity->s.origin, targ_org);
|
|
VectorCopy(self->goalentity->mins, targ_mins);
|
|
}
|
|
|
|
if (check_down)
|
|
{//jumping down
|
|
//Setup the trace
|
|
int inwater;
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("check jump down\n");
|
|
#endif
|
|
inwater = (gi.pointcontents(self->s.origin) & CONTENTS_WATER);
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("checking jump down: ");
|
|
#endif
|
|
if(inwater)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("checkdown allsolid\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(self->maxs, maxs);
|
|
VectorCopy(self->s.origin, source);
|
|
AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
jump_fdist = vhlen(targ_org, self->s.origin);
|
|
if(jump_fdist > 128)
|
|
jump_fdist = 128;
|
|
|
|
VectorMA(source, 128, vf, source);
|
|
|
|
maxs[2] += 16;
|
|
gi.trace (self->s.origin, self->mins, maxs, source, self, MASK_MONSTERSOLID,&trace);
|
|
|
|
if (trace.fraction == 1)
|
|
{//clear ahead and above
|
|
VectorCopy(source, source2);
|
|
|
|
source2[2] -= 1024;
|
|
//trace down
|
|
gi.trace (source, self->mins, self->maxs, source2, self, MASK_ALL,&trace);
|
|
|
|
if (trace.allsolid || trace.startsolid)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("checkdown allsolid\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
if (trace.fraction == 1)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("checkdown- too far\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (trace.contents != CONTENTS_SOLID && trace.ent != self->enemy)
|
|
{//didn't hit ground
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("checkjump down->whichjump\n");
|
|
#endif
|
|
VectorSubtract(trace.endpos, self->s.origin, source2);
|
|
VectorNormalize(source2);
|
|
self->ideal_yaw = vectoyaw(source2);
|
|
|
|
VectorMA(self->velocity, 300, vf, self->velocity);
|
|
self->velocity[2]+=150;
|
|
|
|
if(classStatics[self->classID].msgReceivers[MSG_JUMP])
|
|
{
|
|
if(self->classID != CID_RAT && self->classID != CID_SSITHRA)
|
|
{//save vel so can crouch first
|
|
VectorCopy(self->velocity, self->movedir);
|
|
VectorClear(self->velocity);
|
|
}
|
|
QPostMessage(self, MSG_JUMP, PRI_DIRECTIVE, NULL);
|
|
self->nextthink = level.time + 0.01;
|
|
}
|
|
else
|
|
self->nextthink = level.time + 0.3;
|
|
|
|
self->monsterinfo.jump_time = level.time + 1;
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Extra jump down\n");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#ifdef _DEVEL
|
|
else if(MGAI_DEBUG)
|
|
gi.dprintf("checkdown: not clear infront\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(self->s.origin, save_org);
|
|
can_move = M_walkmove (self, self->s.angles[YAW], 64);
|
|
VectorCopy(save_org, self->s.origin);
|
|
|
|
if(can_move)
|
|
return false;
|
|
else
|
|
{//check to jump over something
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("check jump over\n");
|
|
#endif
|
|
AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
VectorCopy(self->s.origin, source);
|
|
VectorMA(source, 128, vf, source2);
|
|
VectorCopy(self->mins, mins);
|
|
mins[2]+=24;//can clear it
|
|
gi.trace(source, mins, self->maxs, source2, self, MASK_SOLID,&trace);
|
|
|
|
if((!trace.allsolid&&!trace.startsolid&&trace.fraction==1.0) || trace.ent == self->enemy)
|
|
{//Go for it!
|
|
|
|
VectorMA(self->velocity, 500*trace.fraction, vf, self->velocity);
|
|
self->velocity[2] += 225;
|
|
|
|
if(classStatics[self->classID].msgReceivers[MSG_JUMP])
|
|
{
|
|
if(self->classID != CID_RAT && self->classID != CID_SSITHRA)
|
|
{//save vel so can crouch first
|
|
VectorCopy(self->velocity, self->movedir);
|
|
VectorClear(self->velocity);
|
|
}
|
|
QPostMessage(self, MSG_JUMP, PRI_DIRECTIVE, NULL);
|
|
self->nextthink = level.time + 0.01;
|
|
}
|
|
else
|
|
self->nextthink = level.time + 0.3;
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Extra jump over\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("check jump up\n");
|
|
#endif
|
|
VectorCopy(self->maxs, maxs);
|
|
VectorCopy(self->s.origin, source);
|
|
|
|
hgt_diff = (targ_org[2] + targ_mins[2]) - (self->s.origin[2] + self->mins[2]) + 32;
|
|
source[2] += hgt_diff;
|
|
gi.trace (self->s.origin, self->mins, self->maxs, source, self, MASK_MONSTERSOLID,&trace);
|
|
|
|
if (trace.fraction == 1)
|
|
{//clear above
|
|
VectorCopy(source, source2);
|
|
|
|
AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
VectorMA(source, 64, vf, source2);
|
|
source2[2] -= 24;
|
|
//trace forward and down a little
|
|
gi.trace (source, self->mins, self->maxs, source2, self, MASK_ALL,&trace);
|
|
|
|
if (trace.allsolid || trace.startsolid)
|
|
return false;
|
|
|
|
if (trace.fraction < 0.1)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Can't jump up, no ledge\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
// {
|
|
// if (stricmp(trace.ent->classname, "worldspawn"))
|
|
// return;
|
|
// }
|
|
else
|
|
{
|
|
VectorSubtract(trace.endpos, self->s.origin, source2);
|
|
source2[2] = 0;
|
|
VectorNormalize(source2);
|
|
self->ideal_yaw = vectoyaw(source2);
|
|
|
|
VectorMA(self->s.origin, 64, source2, source);
|
|
gi.trace(self->s.origin, vec3_origin, vec3_origin, source, self, MASK_SOLID,&trace);
|
|
|
|
VectorScale(source2, 480*trace.fraction, self->velocity);
|
|
self->velocity[2] = hgt_diff*3 + 200;
|
|
|
|
if(classStatics[self->classID].msgReceivers[MSG_JUMP])
|
|
{
|
|
if(self->classID != CID_RAT && self->classID != CID_SSITHRA)
|
|
{//save vel so can crouch first
|
|
VectorCopy(self->velocity, self->movedir);
|
|
VectorClear(self->velocity);
|
|
}
|
|
QPostMessage(self, MSG_JUMP, PRI_DIRECTIVE, NULL);
|
|
self->nextthink = level.time + 0.01;
|
|
}
|
|
else
|
|
self->nextthink = level.time + 0.3;
|
|
self->monsterinfo.jump_time = level.time + 1;
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Extra jump up\n");
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Can't jump up, blocked\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================================================================
|
|
MG_CheckJump()
|
|
Checks to see if the enemy is not at the same level as monster
|
|
or something is blocking the path of the monster. If there is
|
|
a clear jump arc to the enemy and the monster will not land in
|
|
water or lava, the monster will attempt to jump the distance.
|
|
==================================================================
|
|
*/
|
|
qboolean MG_CheckJump (edict_t *self)
|
|
{
|
|
vec3_t spot1, spot2, jumpdir, forward, right, targ_absmin;
|
|
vec3_t up, cont_spot, vis_check_spot, end_spot, targ_org;
|
|
float jump_height, sub_len;
|
|
int contents;
|
|
qboolean ignore_height;
|
|
qboolean jumpup = false;
|
|
trace_t trace;
|
|
|
|
if(irand(1,100) > self->jump_chance)//JumpChanceForClass[self->classID])
|
|
return false;
|
|
|
|
if(self->classID == CID_TBEAST)
|
|
return TB_CheckJump(self);
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Check Jump\n");
|
|
#endif
|
|
//FIXME: Allow jump in/out of water if not too deep
|
|
if(self->flags & FL_INWATER)// && !(self->flags&FL_AMPHIBIAN))?
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Can't jump- inwater\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
if (self->monsterinfo.searchType == SEARCH_BUOY)
|
|
{
|
|
if(self->buoy_index < 0 || self->buoy_index > level.active_buoys)
|
|
return false;
|
|
|
|
VectorCopy(level.buoy_list[self->buoy_index].origin, targ_org);
|
|
VectorCopy(targ_org, targ_absmin);
|
|
|
|
if (!(infront_pos(self, targ_org)))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(!self->goalentity)
|
|
return false;
|
|
|
|
if (!(infront(self, self->goalentity)))
|
|
return false;
|
|
|
|
if(!self->goalentity->groundentity && self->classID != CID_GORGON)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("goalentity in air\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(self->goalentity->s.origin, targ_org);
|
|
VectorAdd(targ_org, self->goalentity->mins, targ_absmin);
|
|
}
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
VectorSubtract(targ_org, self->s.origin, jumpdir);
|
|
VectorNormalize(jumpdir);
|
|
jumpdir[2] = 0;
|
|
jump_height=DotProduct(jumpdir, forward);
|
|
|
|
if(jump_height<0.3)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("jump direction more than 60 degrees off of forward\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(self->s.origin, spot1);
|
|
VectorCopy(targ_org, spot2);
|
|
|
|
jump_height = 16;
|
|
|
|
VectorMA(spot1, 24, forward, cont_spot);
|
|
cont_spot[2] -= 10;
|
|
if(!(gi.pointcontents(cont_spot)&CONTENTS_SOLID))
|
|
ignore_height = true;
|
|
|
|
sub_len = vhlen(spot1, spot2);
|
|
// if(self->classname!="monster_mezzoman"&&!self->spiderType&&self->model!="models/yakman.mdl")
|
|
if(sub_len > 256)
|
|
ignore_height = false;
|
|
|
|
VectorMA(spot1, self->size[0] * 2, forward, vis_check_spot);
|
|
VectorMA(vis_check_spot, self->size[2] * 1.5, up, vis_check_spot);
|
|
//also check to make sure you can't walkmove forward
|
|
if(self->monsterinfo.jump_time > level.time) //Don't jump too many times in a row
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("just jumped\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
else if(!ignore_height&&targ_absmin[2]+36>=self->absmin[2])//&&self->think!=SpiderJumpBegin&&self->classname!="monster_mezzoman"&&self->model!="models/yakman.mdl")
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("not above goalentity, and not spider\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
else if(!self->groundentity)//flags&FL_ONGROUND)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("not on ground\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
else if(sub_len>777 && !ignore_height)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("too far away\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
else if(sub_len <= 100)//&&self->think!=SpiderMeleeBegin)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("too close & not spider\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
//sfs--sure, it's just a dotproduct, but the other checks are a little cheaper
|
|
else if(!infront_pos(self, targ_org))
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("goalentity not in front\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
//sfs--save the trace line for after the easy checks
|
|
else if(!clear_visible_pos(self, targ_org)&&!LOS(self, vis_check_spot, spot2))
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("can't see goalentity\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
//sfs--holding off on the point contents too
|
|
contents = gi.pointcontents(spot2);
|
|
if(!(self->monsterinfo.aiflags&AI_SWIM_OK)&&
|
|
(contents&CONTENTS_WATER||contents&CONTENTS_SLIME||contents&CONTENTS_LAVA))
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("goalentity in water or lava\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(self->s.origin, spot1);
|
|
// spot1=self->s.origin;
|
|
spot1[2] += self->maxs[2];
|
|
//spot1_z=self->absmax_z;
|
|
VectorCopy(spot1, spot2);
|
|
//spot2=spot1;
|
|
spot2[2] += 36;
|
|
//spot2_z+=36;
|
|
|
|
gi.trace(spot1, self->mins, self->maxs, spot2, self, MASK_MONSTERSOLID,&trace);
|
|
|
|
if(trace.fraction<1.0||trace.allsolid||trace.startsolid)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("not enough room above\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
if(!jumpup) // This variable is used without being initialised
|
|
{
|
|
// spot1+=normalize(v_forward)*((self->maxs_x+self->maxs_y)*0.5);
|
|
VectorMA(spot1, (self->maxs[0]+self->maxs[1])*0.5, jumpdir, spot1);
|
|
//spot1+=jumpdir*((self->maxs_x+self->maxs_y)*0.5);
|
|
VectorCopy(spot1, end_spot);
|
|
end_spot[2] += 36;
|
|
|
|
gi.trace(self->s.origin, self->mins, self->maxs, end_spot, self, MASK_MONSTERSOLID,&trace);
|
|
|
|
if(trace.fraction<1.0||trace.allsolid||trace.startsolid)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("not enough room in front\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
VectorMA(spot1, 64, jumpdir, end_spot);
|
|
end_spot[2] -= 500;
|
|
gi.trace(spot1, JUMP_MINS, JUMP_MAXS, end_spot, self, MASK_MONSTERSOLID,&trace);
|
|
// traceline(spot1,spot1+jumpdir*64 - '0 0 500',false,self);
|
|
|
|
contents = gi.pointcontents(trace.endpos);
|
|
if(contents&CONTENTS_WATER||contents&CONTENTS_SLIME||contents&CONTENTS_LAVA)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("won't jump in water\n");
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MG_FaceGoal(self, true);
|
|
//FIXME: make them do whatever jump function they have if they have one
|
|
self->monsterinfo.jump_time = level.time + 2; //Only try to jump once every 7 seconds
|
|
if(!self->s.scale)
|
|
self->s.scale = 1.0;
|
|
if(!jumpup)
|
|
{
|
|
VectorScale(jumpdir, jump_height*18*self->s.scale, self->velocity);
|
|
//self->velocity=jumpdir*jump_height*18*self->scale;//was 18
|
|
self->velocity[2] = jump_height*14*self->s.scale;//was 12
|
|
}
|
|
else
|
|
{
|
|
VectorScale(jumpdir, jump_height*14*self->s.scale, self->velocity);
|
|
//self->velocity=jumpdir*jump_height*14*self->scale;//was 10
|
|
self->velocity[2] = jump_height*17*self->s.scale;//was 14
|
|
}
|
|
//self->groundentity = NULL?
|
|
if(classStatics[self->classID].msgReceivers[MSG_JUMP])
|
|
{
|
|
if(self->classID != CID_RAT && self->classID != CID_SSITHRA)
|
|
{//save vel so can crouch first
|
|
VectorCopy(self->velocity, self->movedir);
|
|
VectorClear(self->velocity);
|
|
}
|
|
QPostMessage(self, MSG_JUMP, PRI_DIRECTIVE, NULL);
|
|
self->nextthink = level.time + 0.01;
|
|
}
|
|
else
|
|
self->nextthink = level.time + 0.3;
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("JUMP!!!\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
MG_WalkMove
|
|
|
|
Tries to step forward dist, returns the trace
|
|
===============
|
|
*/
|
|
trace_t MG_WalkMove (edict_t *self, float yaw, float dist)
|
|
{
|
|
vec3_t move, endpos;
|
|
trace_t trace;
|
|
|
|
yaw = yaw*M_PI*2 / 360;
|
|
|
|
move[0] = cos(yaw)*dist;
|
|
move[1] = sin(yaw)*dist;
|
|
move[2] = 0;
|
|
|
|
trace = MG_MoveStep(self, move, true);
|
|
if(trace.succeeded)
|
|
{
|
|
return trace;
|
|
}
|
|
//FaileD? ok, so what's in front of us
|
|
VectorAdd(self->s.origin, move, endpos);
|
|
//up mins for stairs?
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&trace);
|
|
trace.succeeded = false;
|
|
return trace;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
MG_BoolWalkMove
|
|
|
|
Tries to step forward dist, returns true/false
|
|
===============
|
|
*/
|
|
qboolean MG_BoolWalkMove (edict_t *self, float yaw, float dist)
|
|
{
|
|
vec3_t move;
|
|
trace_t trace;
|
|
|
|
yaw = yaw*M_PI*2 / 360;
|
|
|
|
move[0] = cos(yaw)*dist;
|
|
move[1] = sin(yaw)*dist;
|
|
move[2] = 0;
|
|
|
|
trace = MG_MoveStep(self, move, true);
|
|
if(trace.succeeded)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
MG_TestMove
|
|
|
|
Sees if it can step dist in yaw, but doesn't do the move
|
|
===============
|
|
*/
|
|
qboolean MG_TestMove (edict_t *self, float yaw, float dist)
|
|
{
|
|
vec3_t move;
|
|
trace_t trace;
|
|
|
|
yaw = yaw*M_PI*2 / 360;
|
|
|
|
move[0] = cos(yaw)*dist;
|
|
move[1] = sin(yaw)*dist;
|
|
move[2] = 0;
|
|
|
|
trace = MG_MoveStep(self, move, false);
|
|
if(trace.succeeded)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void MG_CheckEvade (edict_t *self)
|
|
{//FIXME: only check my enemy? See if he's fired (last_attack) recently?
|
|
int hl;
|
|
float ent_dist, proj_offset;
|
|
vec3_t proj_dir, endpos, bad_dir;
|
|
trace_t trace;
|
|
edict_t *ent = NULL;
|
|
vec3_t total_dist;
|
|
float eta;
|
|
|
|
if(!skill->value)
|
|
return;
|
|
//else if(flrand(0, 3) > skill->value)
|
|
// return;
|
|
|
|
while(ent = findradius(ent, self->s.origin, 500))
|
|
{
|
|
if(ent->movetype == MOVETYPE_FLYMISSILE && ent->solid && ent->owner!=self)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Monster checking evade from %s projectile\n", ent->classname);
|
|
#endif
|
|
if(Vec3IsZero(ent->velocity))
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("ERROR: NULL velocity on %s projectile!\n", ent->classname);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(ent->velocity, proj_dir);
|
|
VectorNormalize(proj_dir);
|
|
VectorMA(ent->s.origin, 600, proj_dir, endpos);
|
|
|
|
gi.trace(ent->s.origin, ent->mins, ent->maxs, endpos, ent, MASK_MONSTERSOLID,&trace);
|
|
if(trace.ent == self)
|
|
{//going to get hit!
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Dodging projectile impact, going to hit %s\n", HitLocName[hl]);
|
|
#endif
|
|
hl = T_GetHitLocation(self, ent, trace.endpos);
|
|
VectorSubtract(trace.endpos, ent->s.origin, total_dist);
|
|
eta = VectorLength(total_dist)/VectorLength(ent->velocity);
|
|
QPostMessage(self, MSG_EVADE, PRI_DIRECTIVE, "eif", ent, hl, eta);
|
|
}
|
|
else if(!irand(0,2))
|
|
{
|
|
VectorSubtract(self->s.origin, ent->s.origin, bad_dir);
|
|
ent_dist = VectorNormalize(bad_dir);
|
|
proj_offset = DotProduct(bad_dir, proj_dir);
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Proj dot prod: %f\n", proj_offset);
|
|
#endif
|
|
if(proj_offset > ent_dist/600)//farther it is, smaller angle deviation allowed for evasion
|
|
{//coming pretty close
|
|
VectorMA(ent->s.origin, ent_dist, proj_dir, endpos);//extrapolate to close to me
|
|
gi.trace(endpos, ent->mins, ent->maxs, self->s.origin, ent, MASK_MONSTERSOLID,&trace);
|
|
if(trace.ent == self)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Dodging projectile close pass, going to hit %s\n", HitLocName[hl]);
|
|
#endif
|
|
hl = T_GetHitLocation(self, ent, trace.endpos);
|
|
VectorSubtract(trace.endpos, ent->s.origin, total_dist);
|
|
eta = VectorLength(total_dist)/VectorLength(ent->velocity);
|
|
QPostMessage(self, MSG_EVADE, PRI_DIRECTIVE, "eif", ent, hl, eta);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
ai_run
|
|
|
|
The monster has an enemy it is trying to kill or the monster is fleeing
|
|
=============
|
|
*/
|
|
void old_ai_run (edict_t *self, float dist);
|
|
void ai_run (edict_t *self, float dist)
|
|
{
|
|
float turnamt, i;
|
|
|
|
//if dumb fleeing or fleeing and can't use buoys...
|
|
if((DEACTIVATE_BUOYS||!(self->monsterinfo.aiflags & AI_USING_BUOYS))
|
|
&&
|
|
(self->monsterinfo.aiflags & AI_COWARD ||
|
|
(self->monsterinfo.aiflags&AI_FLEE
|
|
&&
|
|
self->monsterinfo.flee_finished >= level.time)
|
|
)
|
|
)
|
|
{
|
|
ai_flee(self, dist);
|
|
return;
|
|
}
|
|
else if(self->ai_mood_flags & AI_MOOD_FLAG_DUMB_FLEE)
|
|
{
|
|
if(MG_GoToRandomBuoy(self))
|
|
self->monsterinfo.searchType = SEARCH_BUOY;
|
|
else
|
|
{
|
|
ai_flee(self, dist);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(!DEACTIVATE_BUOYS)
|
|
{
|
|
if(self->monsterinfo.aiflags & AI_USING_BUOYS)
|
|
{
|
|
if(!(self->monsterinfo.aiflags&AI_STRAIGHT_TO_ENEMY))
|
|
{
|
|
if(self->pathfind_nextthink<=level.time)
|
|
{
|
|
MG_BuoyNavigate(self);
|
|
self->pathfind_nextthink = level.time + 0.1;//0.3;-maybe TOO optimized, trying every frame again, take out of generic mood think?
|
|
//don't pathfind for the next 3 frames.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
|
|
{//just face enemy
|
|
turnamt = Q_fabs(ai_face_goal(self));
|
|
return;
|
|
}
|
|
|
|
#ifdef _DEVEL
|
|
if(self->goalentity == self->enemy)
|
|
{
|
|
//sfs--only do this visibility check when debugging (gets expensive at big distances)
|
|
if(MGAI_DEBUG && !visible(self, self->enemy))
|
|
{
|
|
gi.dprintf("ERROR: goal is invis enemy!\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if(dist)
|
|
if(!MG_MoveToGoal (self, dist))
|
|
{
|
|
if(self->classID == CID_SSITHRA)
|
|
ssithraCheckJump(self);
|
|
}
|
|
else
|
|
i = 0;
|
|
|
|
if(self->classID!=CID_ASSASSIN)//does his own checks
|
|
if(classStatics[self->classID].msgReceivers[MSG_EVADE])
|
|
{//check for if going to be hit and evade
|
|
MG_CheckEvade(self);
|
|
}
|
|
}
|
|
|
|
void mg_ai_charge (edict_t *self, float dist)
|
|
{
|
|
vec3_t v;
|
|
|
|
if(!self->enemy)
|
|
{
|
|
#ifdef _DEVEL
|
|
gi.dprintf("ERROR: AI_CHARGE at a NULL enemy!\n");
|
|
#endif
|
|
return;//send stand MSG?
|
|
}
|
|
|
|
VectorSubtract (self->enemy->s.origin, self->s.origin, v);
|
|
|
|
self->ideal_yaw = vectoyaw(v);
|
|
|
|
MG_ChangeYaw (self);
|
|
|
|
if (dist)
|
|
MG_WalkMove (self, self->s.angles[YAW], dist);
|
|
|
|
if(self->classID!=CID_ASSASSIN)//does his own checks
|
|
if(classStatics[self->classID].msgReceivers[MSG_EVADE])
|
|
{//check for if going to be hit and evade
|
|
MG_CheckEvade(self);
|
|
}
|
|
}
|
|
|
|
qboolean canmove (edict_t *self)
|
|
{
|
|
if( self->movetype!=PHYSICSTYPE_NONE &&
|
|
self->movetype!=PHYSICSTYPE_PUSH)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void mg_remove_body(edict_t *self)
|
|
{
|
|
vec3_t origin;
|
|
int flag = 0;
|
|
|
|
VectorCopy(self->s.origin, origin);
|
|
origin[2] += (self->mins[2] + 8.0f);
|
|
if(self->classID == CID_RAT)
|
|
flag |= CEF_FLAG6;
|
|
gi.CreateEffect(NULL, FX_CORPSE_REMOVE, flag, origin, "");
|
|
G_SetToFree(self);
|
|
}
|
|
|
|
void body_phase_out (edict_t *self)
|
|
{
|
|
int interval = 30;
|
|
|
|
if(self->s.color.a > interval)
|
|
{
|
|
self->s.color.a -= irand(interval/2, interval);
|
|
self->post_think = body_phase_out;
|
|
self->next_post_think = level.time + 0.05;
|
|
}
|
|
else
|
|
{
|
|
self->s.color.a = 0;
|
|
self->post_think = NULL;
|
|
self->next_post_think = -1;
|
|
|
|
G_SetToFree(self);
|
|
}
|
|
}
|
|
|
|
trace_t MG_AirMove(edict_t *self, vec3_t goalpos, float dist)
|
|
{
|
|
trace_t trace;
|
|
vec3_t endpos, movedir;
|
|
|
|
VectorSubtract(goalpos, self->s.origin, movedir);
|
|
VectorNormalize(movedir);
|
|
|
|
VectorMA(self->s.origin, dist, movedir, endpos);
|
|
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID, &trace);
|
|
|
|
if(trace.allsolid || trace.startsolid || trace.fraction <= 0.01)
|
|
{
|
|
trace.succeeded = false;
|
|
return trace;
|
|
}
|
|
|
|
VectorCopy(trace.endpos, self->s.origin);
|
|
|
|
gi.linkentity(self);
|
|
|
|
trace.succeeded = true;
|
|
return trace;
|
|
}
|
|
|
|
static int FRONT = 0;
|
|
static int BACK = 1;
|
|
static int RIGHT = 2;
|
|
static int LEFT = 3;
|
|
static float MIN_DROP_DIST = 0.125f;
|
|
|
|
void MG_PostDeathThink (edict_t *self)
|
|
{
|
|
float mostdist;
|
|
trace_t trace1, trace2, trace3, trace4, movetrace;
|
|
vec3_t org, endpos, startpos, forward, right;
|
|
int whichtrace = 0;
|
|
float cornerdist[4];
|
|
qboolean frontbackbothclear = false;
|
|
qboolean rightleftbothclear = false;
|
|
|
|
self->post_think = body_phase_out;
|
|
self->next_post_think = level.time + 10;
|
|
|
|
if(!self->groundentity || Vec3NotZero(self->velocity))
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Falling!\n");
|
|
#endif
|
|
if(self->groundentity&&self->friction == 1.0)//check avelocity?
|
|
pitch_roll_for_slope(self, NULL);
|
|
|
|
self->post_think = MG_PostDeathThink;
|
|
self->next_post_think = level.time + 0.1;
|
|
return;
|
|
}
|
|
|
|
cornerdist[FRONT] = cornerdist[BACK] = cornerdist[RIGHT] = cornerdist[LEFT] = 0.0f;
|
|
|
|
mostdist = MIN_DROP_DIST;
|
|
|
|
AngleVectors(self->s.angles, forward, right, NULL);
|
|
VectorCopy(self->s.origin, org);
|
|
org[2]+=self->mins[2];
|
|
|
|
VectorMA(org, self->dead_size, forward, startpos);
|
|
VectorCopy(startpos, endpos);
|
|
endpos[2]-=128;
|
|
gi.trace(startpos, vec3_origin, vec3_origin, endpos, self, MASK_SOLID,&trace1);
|
|
if(!trace1.allsolid&&!trace1.startsolid)
|
|
{
|
|
cornerdist[FRONT] = trace1.fraction;
|
|
if(trace1.fraction>mostdist)
|
|
{
|
|
mostdist = trace1.fraction;
|
|
whichtrace = 1;
|
|
}
|
|
}
|
|
|
|
VectorMA(org, -self->dead_size, forward, startpos);
|
|
VectorCopy(startpos, endpos);
|
|
endpos[2]-=128;
|
|
gi.trace(startpos, vec3_origin, vec3_origin, endpos, self, MASK_SOLID,&trace2);
|
|
if(!trace2.allsolid&&!trace2.startsolid)
|
|
{
|
|
cornerdist[BACK] = trace2.fraction;
|
|
if(trace2.fraction>mostdist)
|
|
{
|
|
mostdist = trace2.fraction;
|
|
whichtrace = 2;
|
|
}
|
|
}
|
|
|
|
VectorMA(org, self->dead_size/2, right, startpos);
|
|
VectorCopy(startpos, endpos);
|
|
endpos[2]-=128;
|
|
gi.trace(startpos, vec3_origin, vec3_origin, endpos, self, MASK_SOLID,&trace3);
|
|
if(!trace3.allsolid&&!trace3.startsolid)
|
|
{
|
|
cornerdist[RIGHT] = trace3.fraction;
|
|
if(trace3.fraction>mostdist)
|
|
{
|
|
mostdist = trace3.fraction;
|
|
whichtrace = 3;
|
|
}
|
|
}
|
|
|
|
VectorMA(org, -self->dead_size/2, right, startpos);
|
|
VectorCopy(startpos, endpos);
|
|
endpos[2]-=128;
|
|
gi.trace(startpos, vec3_origin, vec3_origin, endpos, self, MASK_SOLID,&trace4);
|
|
if(!trace4.allsolid&&!trace4.startsolid)
|
|
{
|
|
cornerdist[LEFT] = trace4.fraction;
|
|
if(trace4.fraction>mostdist)
|
|
{
|
|
mostdist = trace4.fraction;
|
|
whichtrace = 4;
|
|
}
|
|
}
|
|
|
|
//OK! Now if two opposite sides are hanging, use a third if any, else, do nothing
|
|
if(cornerdist[FRONT] > MIN_DROP_DIST && cornerdist[BACK] > MIN_DROP_DIST)
|
|
frontbackbothclear = true;
|
|
|
|
if(cornerdist[RIGHT] > MIN_DROP_DIST && cornerdist[LEFT] > MIN_DROP_DIST)
|
|
rightleftbothclear = true;
|
|
|
|
if(frontbackbothclear && rightleftbothclear)
|
|
return;
|
|
|
|
if(frontbackbothclear)
|
|
{
|
|
if(cornerdist[RIGHT] > MIN_DROP_DIST)
|
|
whichtrace = 3;
|
|
else if(cornerdist[LEFT] > MIN_DROP_DIST)
|
|
whichtrace = 4;
|
|
else
|
|
return;
|
|
}
|
|
|
|
if(rightleftbothclear)
|
|
{
|
|
if(cornerdist[FRONT] > MIN_DROP_DIST)
|
|
whichtrace = 1;
|
|
else if(cornerdist[BACK] > MIN_DROP_DIST)
|
|
whichtrace = 2;
|
|
else
|
|
return;
|
|
}
|
|
|
|
switch(whichtrace)
|
|
{//check for stuck
|
|
case 1:
|
|
VectorMA(self->s.origin, self->maxs[0], forward, endpos);
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&movetrace);
|
|
if(movetrace.allsolid||movetrace.startsolid||movetrace.fraction<1.0)
|
|
if(canmove(movetrace.ent))
|
|
whichtrace = -1;
|
|
else
|
|
whichtrace = 0;
|
|
break;
|
|
case 2:
|
|
VectorMA(self->s.origin, -self->maxs[0], forward, endpos);
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&movetrace);
|
|
if(movetrace.allsolid||movetrace.startsolid||movetrace.fraction<1.0)
|
|
if(canmove(movetrace.ent))
|
|
whichtrace = -1;
|
|
else
|
|
whichtrace = 0;
|
|
break;
|
|
case 3:
|
|
VectorMA(self->s.origin, self->maxs[0], right, endpos);
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&movetrace);
|
|
if(movetrace.allsolid||movetrace.startsolid||movetrace.fraction<1.0)
|
|
if(canmove(movetrace.ent))
|
|
whichtrace = -1;
|
|
else
|
|
whichtrace = 0;
|
|
break;
|
|
case 4:
|
|
VectorMA(self->s.origin, -self->maxs[0], right, endpos);
|
|
gi.trace(self->s.origin, self->mins, self->maxs, endpos, self, MASK_MONSTERSOLID,&movetrace);
|
|
if(movetrace.allsolid||movetrace.startsolid||movetrace.fraction<1.0)
|
|
if(canmove(movetrace.ent))
|
|
whichtrace = -1;
|
|
else
|
|
whichtrace = 0;
|
|
break;
|
|
}
|
|
|
|
switch(whichtrace)
|
|
{
|
|
case 1:
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Forward trace %f\n", trace1.fraction);
|
|
#endif
|
|
VectorMA(self->velocity, 200, forward, self->velocity);
|
|
if(trace1.fraction >= 0.9)
|
|
{
|
|
//can't anymore, origin not in center of deathframe!
|
|
// self->avelocity[PITCH] = -300;
|
|
self->friction = 1.0;
|
|
}
|
|
else
|
|
{
|
|
pitch_roll_for_slope(self, &trace1.plane.normal);
|
|
self->friction = trace1.plane.normal[2] * 0.1;
|
|
}
|
|
self->post_think = MG_PostDeathThink;
|
|
self->next_post_think = level.time + 0.1;
|
|
return;
|
|
break;
|
|
|
|
case 2:
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("back trace %f\n", trace2.fraction);
|
|
#endif
|
|
VectorMA(self->velocity, -200, forward, self->velocity);
|
|
if(trace2.fraction >= 0.9)
|
|
{
|
|
//can't anymore, origin not in center of deathframe!
|
|
// self->avelocity[PITCH] = 300;
|
|
self->friction = 1.0;
|
|
}
|
|
else
|
|
{
|
|
pitch_roll_for_slope(self, &trace2.plane.normal);
|
|
self->friction = trace2.plane.normal[2] * 0.1;
|
|
}
|
|
self->post_think = MG_PostDeathThink;
|
|
self->next_post_think = level.time + 0.1;
|
|
return;
|
|
break;
|
|
|
|
case 3:
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Right trace %f\n", trace3.fraction);
|
|
#endif
|
|
VectorMA(self->velocity, 200, right, self->velocity);
|
|
if(trace3.fraction >= 0.9)
|
|
{
|
|
//can't anymore, origin not in center of deathframe!
|
|
// self->avelocity[ROLL] = -300;
|
|
self->friction = 1.0;
|
|
}
|
|
else
|
|
{
|
|
pitch_roll_for_slope(self, &trace3.plane.normal);
|
|
self->friction = trace3.plane.normal[2] * 0.1;
|
|
}
|
|
self->post_think = MG_PostDeathThink;
|
|
self->next_post_think = level.time + 0.1;
|
|
return;
|
|
break;
|
|
|
|
case 4:
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Left trace %f\n", trace4.fraction);
|
|
#endif
|
|
VectorMA(self->velocity, -200, right, self->velocity);
|
|
if(trace4.fraction >= 0.9)
|
|
{
|
|
//can't anymore, origin not in center of deathframe!
|
|
// self->avelocity[ROLL] = 300;
|
|
self->friction = 1.0;
|
|
}
|
|
else
|
|
{
|
|
pitch_roll_for_slope(self, &trace4.plane.normal);
|
|
self->friction = trace4.plane.normal[2] * 0.1;
|
|
}
|
|
self->post_think = MG_PostDeathThink;
|
|
self->next_post_think = level.time + 0.1;
|
|
return;
|
|
break;
|
|
}
|
|
//on solid ground
|
|
if(whichtrace == -1)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Deadmonster slide = stuck! (size is %4.2f)\n", self->dead_size);
|
|
#endif
|
|
self->post_think = MG_PostDeathThink;
|
|
self->next_post_think = level.time + 2;
|
|
return;
|
|
}
|
|
#ifdef _DEVEL
|
|
else if(MGAI_DEBUG)
|
|
gi.dprintf("Deadmonster slide = On ground (size was %4.2f)\n", self->dead_size);
|
|
#endif
|
|
self->friction = 1.0;
|
|
|
|
VectorClear(self->avelocity);
|
|
pitch_roll_for_slope(self, NULL);
|
|
|
|
if(!self->s.color.r)
|
|
self->s.color.r = 255;
|
|
if(!self->s.color.g)
|
|
self->s.color.g = 255;
|
|
if(!self->s.color.b)
|
|
self->s.color.b = 255;
|
|
self->s.color.a = 255;
|
|
|
|
self->post_think = body_phase_out;
|
|
if(self->classID == CID_RAT)
|
|
self->next_post_think = level.time + flrand(3, 7);
|
|
else
|
|
self->next_post_think = level.time + flrand(10, 20);
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
void MG_CheckLanded (edict_t *self, float next_anim)
|
|
{
|
|
vec3_t pos;
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("self->velocity %f %f %f\n", self->velocity[0], self->velocity[1], self->velocity[2]);
|
|
#endif
|
|
|
|
if(self->groundentity)
|
|
SetAnim(self, (int)next_anim);
|
|
else if(self->velocity[2]<0)
|
|
{
|
|
VectorCopy(self->s.origin, pos);
|
|
pos[2] += self->mins[2];
|
|
VectorMA(pos, 0.5, self->velocity, pos);
|
|
if(gi.pointcontents(pos)&CONTENTS_SOLID)
|
|
SetAnim(self, (int)next_anim);
|
|
}
|
|
}
|
|
|
|
void MG_InAirMove (edict_t *self, float fwdspd,float upspd,float rtspd)
|
|
{//simple addition of velocity, if on ground or not
|
|
vec3_t up, forward, right;
|
|
|
|
if(self->groundentity)//on ground
|
|
return;
|
|
|
|
AngleVectors(self->s.angles, forward, right, up);
|
|
|
|
VectorMA(self->velocity, upspd, up, self->velocity);
|
|
VectorMA(self->velocity, fwdspd, forward, self->velocity);
|
|
VectorMA(self->velocity, rtspd, right, self->velocity);
|
|
}
|
|
|
|
void MG_ApplyJump (edict_t *self)
|
|
{
|
|
self->jump_time = level.time + 0.5;
|
|
VectorCopy(self->movedir, self->velocity);
|
|
VectorNormalize(self->movedir);
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Jump velocity will be: %4.2f %4.2f %4.2f\n", self->velocity[0], self->velocity[1], self->velocity[2]);
|
|
#endif
|
|
self->monsterinfo.aiflags &= ~AI_OVERRIDE_GUIDE;
|
|
}
|
|
|
|
void MG_NoBlocking (edict_t *self)
|
|
{
|
|
self->svflags |= SVF_DEADMONSTER; // now treat as a different content type
|
|
self->msgHandler = DeadMsgHandler; // no more messages at all
|
|
}
|
|
|
|
qboolean MG_GetTargOrg (edict_t *self, vec3_t targ_org)
|
|
{
|
|
if (self->monsterinfo.searchType == SEARCH_BUOY)
|
|
{
|
|
if(self->buoy_index < 0 || self->buoy_index > level.active_buoys)
|
|
{
|
|
VectorClear(targ_org);
|
|
return false;
|
|
}
|
|
VectorCopy(level.buoy_list[self->buoy_index].origin, self->monsterinfo.nav_goal);
|
|
VectorCopy(self->monsterinfo.nav_goal, targ_org);
|
|
}
|
|
else
|
|
{
|
|
if(!self->goalentity)
|
|
{
|
|
VectorClear(targ_org);
|
|
return false;
|
|
}
|
|
|
|
VectorCopy(self->goalentity->s.origin, targ_org);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
qboolean EqualAngle(float angle1, float angle2, float leniency)
|
|
|
|
Sees if the two angles are within leniency degrees of each other
|
|
*/
|
|
qboolean EqualAngle(float angle1, float angle2, float leniency)
|
|
{
|
|
float diff;
|
|
|
|
if(angle1 < -180)
|
|
angle1 += 360;
|
|
else if(angle1 > 180)
|
|
angle1 -= 360;
|
|
|
|
if(angle2 < -180)
|
|
angle2 += 360;
|
|
else if(angle2 > 180)
|
|
angle2 -= 360;
|
|
|
|
diff = angle1 - angle2;
|
|
|
|
if(diff < -180)
|
|
diff += 360;
|
|
else if(diff > 180)
|
|
diff -= 360;
|
|
|
|
if(fabs(diff) > leniency)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
qboolean ok_to_break (edict_t *target)
|
|
|
|
Ok to just smash this damn thing in my way?
|
|
*/
|
|
qboolean ok_to_break (edict_t *target)
|
|
{
|
|
if(!target)
|
|
return false;
|
|
|
|
if(!target->takedamage)
|
|
return false;
|
|
|
|
if(target->health>MAX_BLOCKING_THING_HEALTH)//general damage for pots, barrels, etc.
|
|
return false;
|
|
|
|
if(target->targetname)//supposed to be triggered for some reason
|
|
return false;
|
|
|
|
if(target->svflags&SVF_MONSTER)//another monster
|
|
return false;
|
|
|
|
if(Vec3IsZero(target->s.origin))//breakable_brushes have no origin
|
|
return false;
|
|
|
|
return true;//break it!
|
|
}
|
|
|
|
qboolean MG_MoveToGoal (edict_t *self, float dist)
|
|
{
|
|
trace_t trace;
|
|
float turnamt, distloss, adj_dist, save_yaw, save_yaw_speed, WallDot;//, save_ideal_yaw;
|
|
vec3_t mins, maxs, source, goal_dir;//, vec, save_org;
|
|
qboolean goal_vis=false, hitworld = false, new_best_yaw = false;
|
|
float stepsize, goal_dist, oby;
|
|
|
|
if(self->classID == CID_TBEAST)
|
|
stepsize = STEPSIZE * 3;
|
|
else
|
|
stepsize = STEPSIZE;
|
|
|
|
if(!self->groundentity&&!(self->flags&FL_SWIM)&&!(self->flags&FL_FLY))
|
|
return false;//in air!
|
|
|
|
trace.succeeded = false;
|
|
|
|
if(self->classID != CID_GORGON)//they do their own yawing
|
|
MG_FaceGoal(self, false);//get ideal yaw, but don't turn
|
|
|
|
//are we very close to our goal? problem: what if something in between?
|
|
if(!EqualAngle(self->s.angles[YAW], self->ideal_yaw, self->yaw_speed))
|
|
{//we aren't really facing our ideal yet
|
|
if(self->monsterinfo.searchType == SEARCH_BUOY||self->ai_mood == AI_MOOD_NAVIGATE)
|
|
{
|
|
VectorSubtract(self->monsterinfo.nav_goal, self->s.origin, goal_dir);
|
|
goal_dist = VectorNormalize(goal_dir);
|
|
if(goal_dist < (self->maxs[0] + 24 + dist))
|
|
{//we're close to our goal
|
|
MG_ChangeWhichYaw(self, YAW_IDEAL);
|
|
return true;//so close to enemy, just turn, no movement - not if rat?
|
|
}
|
|
}
|
|
else if(self->enemy)
|
|
{
|
|
VectorSubtract(self->monsterinfo.nav_goal, self->s.origin, goal_dir);
|
|
goal_dist = VectorNormalize(goal_dir);
|
|
if(goal_dist < (self->maxs[0] + self->enemy->maxs[0] + dist*2))
|
|
{//we're close to our goal
|
|
MG_ChangeWhichYaw(self, YAW_IDEAL);
|
|
return true;//so close to enemy, just turn, no movement - not if rat?
|
|
}
|
|
}
|
|
}
|
|
|
|
if(self->monsterinfo.idle_time == -1)
|
|
{//have been told to just turn to ideal_yaw
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_IDEAL));
|
|
//keep turning towards ideal until facing it
|
|
if(turnamt < 1)
|
|
self->monsterinfo.idle_time = 0;
|
|
else
|
|
return true;
|
|
}
|
|
else if(self->monsterinfo.idle_time > level.time)
|
|
{//using best_move_yaw
|
|
if(EqualAngle(self->s.angles[YAW], self->best_move_yaw, 5))
|
|
{//do a test move in the direction I would like to go:
|
|
if(MG_TestMove(self, self->ideal_yaw, dist))
|
|
{//can move in that dir turn there for rest of this
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move in ideal tested true while using best_move...!\n");
|
|
#endif
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_IDEAL));
|
|
//keep turning towards ideal until facing it
|
|
if(turnamt < 1)
|
|
self->monsterinfo.idle_time = 0;
|
|
else
|
|
{
|
|
self->monsterinfo.idle_time = -1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));//turn to temp yaw
|
|
}
|
|
else
|
|
{//using ideal_yaw
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_IDEAL));
|
|
}
|
|
|
|
distloss = turnamt/self->yaw_speed * 0.8;//0.3;
|
|
adj_dist = dist - (dist * distloss);
|
|
|
|
trace = MG_WalkMove(self, self->s.angles[YAW], dist);
|
|
if(trace.succeeded)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward succeeded!\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
else if(self->classID == CID_TBEAST)
|
|
{
|
|
if(trace.fraction<1.0)
|
|
VectorCopy(trace.endpos, self->s.origin);
|
|
}
|
|
|
|
//if facing best_move_yaw and can't move that way, stop trying in that dir now.
|
|
if(self->monsterinfo.idle_time > level.time && self->s.angles[YAW] == self->best_move_yaw)
|
|
{
|
|
new_best_yaw = true;
|
|
oby = self->best_move_yaw;
|
|
self->monsterinfo.idle_time = 0;
|
|
}
|
|
|
|
//bumped into something
|
|
if(trace.ent)
|
|
{
|
|
if(!stricmp(trace.ent->classname, "worldspawn"))
|
|
hitworld = true;
|
|
else
|
|
hitworld = false;
|
|
|
|
if(trace.ent == self->enemy)
|
|
{//bumped into enemy, go get him!
|
|
if(!(self->monsterinfo.aiflags & AI_COWARD) &&
|
|
(!(self->monsterinfo.aiflags&AI_FLEE) || self->monsterinfo.flee_finished < level.time))
|
|
{
|
|
if(!(self->monsterinfo.aiflags&AI_NO_MELEE))
|
|
{
|
|
if(!(self->ai_mood_flags&AI_MOOD_FLAG_IGNORE_ENEMY))
|
|
{
|
|
if(classStatics[self->classID].msgReceivers[MSG_MELEE] && infront(self, self->enemy))
|
|
{
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(trace.ent->svflags & SVF_MONSTER)
|
|
{//if bumped into a monster that's not after an enemy but not ambushing, bring him along
|
|
if(!trace.ent->enemy)
|
|
{
|
|
if(trace.ent->health>0 && !trace.ent->monsterinfo.awake)
|
|
{
|
|
if(!(trace.ent->spawnflags & MSF_AMBUSH))
|
|
{
|
|
if(self->enemy)
|
|
{
|
|
if(self->enemy->client)
|
|
{
|
|
trace.ent->enemy = self->enemy;
|
|
FoundTarget(trace.ent, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
//FIXME: this needs to make sure they can break it
|
|
//also: do not do this if the monsters' enemy is visible (MASK_SOLID check, though)
|
|
else if(!irand(0, 5))
|
|
{//fixme: need to make sure the melee anims can break this
|
|
if(self->classID > CID_RAT && classStatics[self->classID].msgReceivers[MSG_MELEE])
|
|
{
|
|
if(!(self->monsterinfo.aiflags&AI_NO_MELEE))
|
|
{
|
|
if(ok_to_break(trace.ent))
|
|
{
|
|
if(infront(self, trace.ent))
|
|
{//smash it!
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("%s breaking blocking %s!\n", self->classname, trace.ent->classname);
|
|
self->monsterinfo.aiflags |= AI_STRAIGHT_TO_ENEMY;//go straight at enemy, not buoys
|
|
self->oldenemy_debounce_time = level.time + 7;//attack it for 7 seconds max
|
|
self->oldenemy = self->enemy;//remember who I was after
|
|
self->enemy = self->goalentity = trace.ent;//let's nail this sucker
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);//SMACK!
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
if(!hitworld)
|
|
{
|
|
if(self->monsterinfo.idle_time < level.time)
|
|
{//not already following a weird dir
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward hit wall, newdir\n");
|
|
#endif
|
|
self->monsterinfo.idle_time = level.time + flrand(0.5, 1.2);
|
|
self->best_move_yaw = anglemod(180 + self->ideal_yaw);
|
|
MG_NewDir(self, dist);
|
|
}
|
|
return false;
|
|
}
|
|
#ifdef _DEVEL
|
|
else if(MGAI_DEBUG)
|
|
gi.dprintf("Bumped world - t_f: %f t_allsolid: %d, t_startsolid %d\n",trace.fraction, trace.allsolid, trace.startsolid);
|
|
#endif
|
|
}
|
|
|
|
//Ledge?
|
|
if((trace.fraction >= 0.5 + distloss ||self->classID == CID_ASSASSIN) && !trace.allsolid && !trace.startsolid)//a ledge
|
|
{//why not tracefraction == 1.0?
|
|
if(!(self->spawnflags & MSF_FIXED))
|
|
{
|
|
if(MG_CheckJump(self))//can jump off it
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward jumped off ledge!\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
else if(self->classID == CID_ASSASSIN)
|
|
{
|
|
if(MG_ExtraCheckJump(self))
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move straight to goal extra jumped off ledge!\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(trace.fraction >= 0.5)//even assassins skip this
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Can't jump off, getting newdir\n");
|
|
#endif
|
|
if(self->monsterinfo.idle_time < level.time)
|
|
{//not already following some other dir, pick one
|
|
self->monsterinfo.idle_time = level.time + flrand(1, 2);
|
|
self->best_move_yaw = anglemod(180 + self->ideal_yaw);
|
|
MG_NewDir(self, dist);//what if this fails to set one?
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
if(trace.allsolid || trace.startsolid)
|
|
gi.dprintf("Move forward allsolid or startsolid!\n");
|
|
#endif
|
|
//FROM HERE ON, ONLY CHANGES DIR, WILL NOT MOVE!
|
|
|
|
//otherwise, go around it... this ONLY???
|
|
//lock into this new yaw for a bit
|
|
if(self->monsterinfo.idle_time > level.time)
|
|
{//heading somewhere for a few secs, turn here
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Turning to newdir, not bumping\n");
|
|
#endif
|
|
/* turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));
|
|
distloss = turnamt/self->yaw_speed * 0.3;
|
|
dist -= (dist * distloss);*/
|
|
return false;
|
|
}
|
|
|
|
if((hitworld || irand(0,10)<6)&&!goal_vis)
|
|
self->monsterinfo.idle_time = level.time + flrand(1, 2);
|
|
else
|
|
self->monsterinfo.idle_time = level.time + flrand(0.5, 1.25);
|
|
|
|
self->best_move_yaw = anglemod(180 + self->ideal_yaw);
|
|
|
|
//if hit a wall and close to ideal yaw (with 5), try a new dir
|
|
if(Vec3NotZero(trace.plane.normal)&&
|
|
EqualAngle(self->s.angles[YAW], self->ideal_yaw, 5))
|
|
{//a wall?
|
|
vec3_t wall_angles, wall_right, self_forward, new_forward, vf;
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward hit wall, checking left/right/back...\n");
|
|
#endif
|
|
|
|
//If facing a wall, turn faster, more facing the wall, faster the turn
|
|
save_yaw_speed = self->yaw_speed;
|
|
AngleVectors(self->s.angles, self_forward, NULL, NULL);
|
|
WallDot = DotProduct(trace.plane.normal, self_forward);
|
|
if(WallDot>0)
|
|
WallDot = 0;//-1 to 0
|
|
self->yaw_speed *= 1.25 - WallDot;//facing wall head-on = 2.25 times normal yaw speed
|
|
|
|
vectoangles(trace.plane.normal, wall_angles);
|
|
AngleVectors(wall_angles, NULL, wall_right, NULL);
|
|
|
|
if(goal_vis)
|
|
{//can see goal, turn towards IT first
|
|
VectorSubtract(self->goalentity->s.origin, self->s.origin, self_forward);
|
|
VectorNormalize(self_forward);
|
|
}
|
|
|
|
//Get closest angle off that wall to move in
|
|
if(DotProduct(wall_right,self_forward)>0)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turning left\n");
|
|
#endif
|
|
VectorCopy(wall_right, new_forward);
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turning right\n");
|
|
#endif
|
|
VectorScale(wall_right, -1, new_forward);
|
|
}
|
|
|
|
if(irand(0,10)<3)//30% chance of trying other way first
|
|
VectorScale(new_forward, -1, new_forward);
|
|
|
|
self->best_move_yaw=vectoyaw(new_forward);
|
|
|
|
if(new_best_yaw && self->best_move_yaw == oby)
|
|
{
|
|
VectorScale(new_forward, -1, new_forward);
|
|
|
|
self->best_move_yaw=vectoyaw(new_forward);
|
|
}
|
|
|
|
//make sure we can move in chosen dir
|
|
//set up mins and maxes for these moves
|
|
VectorCopy(self->mins, mins);
|
|
VectorCopy(self->maxs, maxs);
|
|
//Account for STEPSIZE
|
|
mins[2] += stepsize;
|
|
if(mins[2] >= self->maxs[2])
|
|
mins[2] = self->maxs[2] - 1;
|
|
//remember yaw in case all these fail!
|
|
save_yaw = self->s.angles[YAW];
|
|
|
|
//Haven't yawed yet, so this is okay
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));
|
|
distloss = turnamt/self->yaw_speed * 0.8;//0.3;
|
|
adj_dist = dist - (dist * distloss);
|
|
|
|
VectorCopy(new_forward, vf);
|
|
//AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
|
|
VectorCopy(self->s.origin, source);
|
|
VectorMA(source, adj_dist, vf, source);
|
|
|
|
gi.trace (self->s.origin, mins, self->maxs, source, self, MASK_SOLID,&trace);//was MASK_SHOT
|
|
|
|
if (trace.fraction < 1||trace.allsolid||trace.startsolid)
|
|
{//Uh oh, try other way
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turn other way\n");
|
|
#endif
|
|
VectorScale(new_forward, -1, new_forward);
|
|
self->best_move_yaw=vectoyaw(new_forward);
|
|
//restore yaw
|
|
self->s.angles[YAW] = save_yaw;
|
|
//try new dir
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));
|
|
distloss = turnamt/self->yaw_speed * 0.8;//0.3;
|
|
adj_dist = dist - (dist * distloss);
|
|
|
|
VectorCopy(new_forward, vf);
|
|
//AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
|
|
VectorMA(source, adj_dist, vf, source);
|
|
|
|
//Account for STEPSIZE
|
|
mins[2] += stepsize;
|
|
if(mins[2] >= self->maxs[2])
|
|
mins[2] = self->maxs[2] - 1;
|
|
|
|
gi.trace (self->s.origin, mins, self->maxs, source, self, MASK_SOLID,&trace);//was MASK_SHOT
|
|
if (trace.fraction < 1||trace.allsolid||trace.startsolid)
|
|
{//Uh oh! Go straight away from wall
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turn all the way around\n");
|
|
#endif
|
|
self->best_move_yaw=wall_angles[YAW];
|
|
//restore yaw
|
|
self->s.angles[YAW] = save_yaw;
|
|
//start turning this move, but don't actually move until next time
|
|
MG_ChangeWhichYaw(self, YAW_BEST_MOVE);
|
|
}
|
|
}
|
|
self->yaw_speed = save_yaw_speed;
|
|
return false;
|
|
}
|
|
else//keep turning to ideal
|
|
self->monsterinfo.idle_time = 0;
|
|
|
|
//Must have bumped into something very strange (other monster?)
|
|
//just pick a new random dir
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Don't know what I hit, choosing newdir for a second\n");
|
|
#endif
|
|
MG_NewDir(self, dist);
|
|
return false;
|
|
}
|
|
|
|
qboolean MG_SwimFlyToGoal (edict_t *self, float dist)
|
|
{
|
|
trace_t trace;
|
|
float turnamt, distloss, adj_dist, save_yaw, save_yaw_speed, WallDot;//, save_ideal_yaw;
|
|
vec3_t mins, maxs, source, goal_dir, goalpos;//, vec, save_org;
|
|
qboolean goal_vis=false, hitworld = false, new_best_yaw = false;
|
|
float goal_dist, oby;
|
|
|
|
trace.succeeded = false;
|
|
|
|
if(self->classID != CID_GORGON)//they do their own yawing
|
|
MG_FaceGoal(self, false);//get ideal yaw, but don't turn
|
|
|
|
//are we very close to our goal? problem: what if something in between?
|
|
if(!EqualAngle(self->s.angles[YAW], self->ideal_yaw, self->yaw_speed))
|
|
{//we aren't really facing our ideal yet
|
|
if(self->monsterinfo.searchType == SEARCH_BUOY||self->ai_mood == AI_MOOD_NAVIGATE)
|
|
{
|
|
VectorSubtract(self->monsterinfo.nav_goal, self->s.origin, goal_dir);
|
|
goal_dist = VectorNormalize(goal_dir);
|
|
if(goal_dist < (self->maxs[0] + 24 + dist))
|
|
{//we're close to our goal
|
|
MG_ChangeWhichYaw(self, YAW_IDEAL);
|
|
return true;//so close to enemy, just turn, no movement - not if rat?
|
|
}
|
|
}
|
|
else if(self->enemy)
|
|
{
|
|
VectorSubtract(self->monsterinfo.nav_goal, self->s.origin, goal_dir);
|
|
goal_dist = VectorNormalize(goal_dir);
|
|
if(goal_dist < (self->maxs[0] + self->enemy->maxs[0] + dist*2))
|
|
{//we're close to our goal
|
|
MG_ChangeWhichYaw(self, YAW_IDEAL);
|
|
return true;//so close to enemy, just turn, no movement - not if rat?
|
|
}
|
|
}
|
|
}
|
|
|
|
if(self->monsterinfo.idle_time == -1)
|
|
{//have been told to just turn to ideal_yaw
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_IDEAL));
|
|
//keep turning towards ideal until facing it
|
|
if(turnamt < 1)
|
|
self->monsterinfo.idle_time = 0;
|
|
else
|
|
return true;
|
|
}
|
|
else if(self->monsterinfo.idle_time > level.time)
|
|
{//using best_move_yaw
|
|
if(EqualAngle(self->s.angles[YAW], self->best_move_yaw, 5))
|
|
{//do a test move in the direction I would like to go:
|
|
if(MG_TestMove(self, self->ideal_yaw, dist))
|
|
{//can move in that dir turn there for rest of this
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move in ideal tested true while using best_move...!\n");
|
|
#endif
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_IDEAL));
|
|
//keep turning towards ideal until facing it
|
|
if(turnamt < 1)
|
|
self->monsterinfo.idle_time = 0;
|
|
else
|
|
{
|
|
self->monsterinfo.idle_time = -1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));//turn to temp yaw
|
|
}
|
|
else
|
|
{//using ideal_yaw
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_IDEAL));
|
|
}
|
|
|
|
distloss = turnamt/self->yaw_speed * 0.8;//0.3;
|
|
adj_dist = dist - (dist * distloss);
|
|
|
|
MG_GetGoalPos(self, goalpos);
|
|
|
|
trace = MG_AirMove(self, goalpos, dist);
|
|
if(trace.succeeded)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward succeeded!\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
//if facing best_move_yaw and can't move that way, stop trying in that dir now.
|
|
if(self->monsterinfo.idle_time > level.time && self->s.angles[YAW] == self->best_move_yaw)
|
|
{
|
|
new_best_yaw = true;
|
|
oby = self->best_move_yaw;
|
|
self->monsterinfo.idle_time = 0;
|
|
}
|
|
|
|
//bumped into something
|
|
if(trace.ent)
|
|
{
|
|
if(!stricmp(trace.ent->classname, "worldspawn"))
|
|
hitworld = true;
|
|
else
|
|
hitworld = false;
|
|
|
|
if(trace.ent == self->enemy)
|
|
{//bumped into enemy, go get him!
|
|
if(!(self->monsterinfo.aiflags & AI_COWARD) &&
|
|
(!(self->monsterinfo.aiflags&AI_FLEE) || self->monsterinfo.flee_finished < level.time))
|
|
{
|
|
if(!(self->monsterinfo.aiflags&AI_NO_MELEE))
|
|
{
|
|
if(!(self->ai_mood_flags&AI_MOOD_FLAG_IGNORE_ENEMY))
|
|
{
|
|
if(classStatics[self->classID].msgReceivers[MSG_MELEE] && infront(self, self->enemy))
|
|
{
|
|
QPostMessage(self, MSG_MELEE, PRI_DIRECTIVE, NULL);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(trace.ent->svflags & SVF_MONSTER)
|
|
{//if bumped into a monster that's not after an enemy but not ambushing, bring him along
|
|
if(!trace.ent->enemy)
|
|
{
|
|
if(trace.ent->health>0 && !trace.ent->monsterinfo.awake)
|
|
{
|
|
if(!(trace.ent->spawnflags & MSF_AMBUSH))
|
|
{
|
|
if(self->enemy)
|
|
{
|
|
if(self->enemy->client)
|
|
{
|
|
trace.ent->enemy = self->enemy;
|
|
FoundTarget(trace.ent, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!hitworld)
|
|
{
|
|
if(self->monsterinfo.idle_time < level.time)
|
|
{//not already following a weird dir
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward hit wall, newdir\n");
|
|
#endif
|
|
self->monsterinfo.idle_time = level.time + flrand(0.5, 1.2);
|
|
self->best_move_yaw = anglemod(180 + self->ideal_yaw);
|
|
MG_NewDir(self, dist);
|
|
}
|
|
return false;
|
|
}
|
|
#ifdef _DEVEL
|
|
else if(MGAI_DEBUG)
|
|
gi.dprintf("Bumped world - t_f: %f t_allsolid: %d, t_startsolid %d\n",trace.fraction, trace.allsolid, trace.startsolid);
|
|
#endif
|
|
}
|
|
|
|
//Ledge?
|
|
/* if(trace.fraction >= 0.5 + distloss && !trace.allsolid && !trace.startsolid)//a ledge
|
|
{//why not tracefraction == 1.0?
|
|
if(trace.fraction >= 0.5)//even assassins skip this
|
|
{
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Can't jump off, getting newdir\n");
|
|
|
|
if(self->monsterinfo.idle_time < level.time)
|
|
{//not already following some other dir, pick one
|
|
self->monsterinfo.idle_time = level.time + flrand(1, 2);
|
|
self->best_move_yaw = anglemod(180 + self->ideal_yaw);
|
|
MG_NewDir(self, dist);//what if this fails to set one?
|
|
}
|
|
return false;
|
|
}
|
|
}*/
|
|
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
if(trace.allsolid || trace.startsolid)
|
|
gi.dprintf("Move forward allsolid or startsolid!\n");
|
|
#endif
|
|
//FROM HERE ON, ONLY CHANGES DIR, WILL NOT MOVE!
|
|
|
|
//otherwise, go around it... this ONLY???
|
|
//lock into this new yaw for a bit
|
|
if(self->monsterinfo.idle_time > level.time)
|
|
{//heading somewhere for a few secs, turn here
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Turning to newdir, not bumping\n");
|
|
#endif
|
|
/* turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));
|
|
distloss = turnamt/self->yaw_speed * 0.3;
|
|
dist -= (dist * distloss);*/
|
|
return false;
|
|
}
|
|
|
|
if((hitworld || irand(0,10)<6)&&!goal_vis)
|
|
self->monsterinfo.idle_time = level.time + flrand(1, 2);
|
|
else
|
|
self->monsterinfo.idle_time = level.time + flrand(0.5, 1.25);
|
|
|
|
self->best_move_yaw = anglemod(180 + self->ideal_yaw);
|
|
|
|
//if hit a wall and close to ideal yaw (with 5), try a new dir
|
|
if(Vec3NotZero(trace.plane.normal)&&
|
|
EqualAngle(self->s.angles[YAW], self->ideal_yaw, 5))
|
|
{//a wall?
|
|
vec3_t wall_angles, wall_right, self_forward, new_forward, vf;
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Move forward hit wall, checking left/right/back...\n");
|
|
#endif
|
|
//If facing a wall, turn faster, more facing the wall, faster the turn
|
|
save_yaw_speed = self->yaw_speed;
|
|
AngleVectors(self->s.angles, self_forward, NULL, NULL);
|
|
WallDot = DotProduct(trace.plane.normal, self_forward);
|
|
if(WallDot>0)
|
|
WallDot = 0;//-1 to 0
|
|
self->yaw_speed *= 1.25 - WallDot;//facing wall head-on = 2.25 times normal yaw speed
|
|
|
|
vectoangles(trace.plane.normal, wall_angles);
|
|
AngleVectors(wall_angles, NULL, wall_right, NULL);
|
|
|
|
if(goal_vis)
|
|
{//can see goal, turn towards IT first
|
|
VectorSubtract(self->goalentity->s.origin, self->s.origin, self_forward);
|
|
VectorNormalize(self_forward);
|
|
}
|
|
|
|
//Get closest angle off that wall to move in
|
|
if(DotProduct(wall_right,self_forward)>0)
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turning left\n");
|
|
#endif
|
|
VectorCopy(wall_right, new_forward);
|
|
}
|
|
else
|
|
{
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turning right\n");
|
|
#endif
|
|
VectorScale(wall_right, -1, new_forward);
|
|
}
|
|
|
|
if(irand(0,10)<3)//30% chance of trying other way first
|
|
VectorScale(new_forward, -1, new_forward);
|
|
|
|
self->best_move_yaw=vectoyaw(new_forward);
|
|
|
|
if(new_best_yaw && self->best_move_yaw == oby)
|
|
{
|
|
VectorScale(new_forward, -1, new_forward);
|
|
|
|
self->best_move_yaw=vectoyaw(new_forward);
|
|
}
|
|
|
|
//make sure we can move in chosen dir
|
|
//set up mins and maxes for these moves
|
|
VectorCopy(self->mins, mins);
|
|
VectorCopy(self->maxs, maxs);
|
|
|
|
//remember yaw in case all these fail!
|
|
save_yaw = self->s.angles[YAW];
|
|
|
|
//Haven't yawed yet, so this is okay
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));
|
|
distloss = turnamt/self->yaw_speed * 0.8;//0.3;
|
|
adj_dist = dist - (dist * distloss);
|
|
|
|
VectorCopy(new_forward, vf);
|
|
//AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
|
|
VectorCopy(self->s.origin, source);
|
|
VectorMA(source, adj_dist, vf, source);
|
|
|
|
gi.trace (self->s.origin, mins, self->maxs, source, self, MASK_SOLID,&trace);//was MASK_SHOT
|
|
|
|
if (trace.fraction < 1||trace.allsolid||trace.startsolid)
|
|
{//Uh oh, try other way
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turn other way\n");
|
|
#endif
|
|
VectorScale(new_forward, -1, new_forward);
|
|
self->best_move_yaw=vectoyaw(new_forward);
|
|
//restore yaw
|
|
self->s.angles[YAW] = save_yaw;
|
|
//try new dir
|
|
turnamt = Q_fabs(MG_ChangeWhichYaw(self, YAW_BEST_MOVE));
|
|
distloss = turnamt/self->yaw_speed * 0.8;//0.3;
|
|
adj_dist = dist - (dist * distloss);
|
|
|
|
VectorCopy(new_forward, vf);
|
|
//AngleVectors(self->s.angles, vf, NULL, NULL);
|
|
|
|
VectorMA(source, adj_dist, vf, source);
|
|
|
|
gi.trace (self->s.origin, mins, self->maxs, source, self, MASK_SOLID,&trace);//was MASK_SHOT
|
|
if (trace.fraction < 1||trace.allsolid||trace.startsolid)
|
|
{//Uh oh! Go straight away from wall
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("turn all the way around\n");
|
|
#endif
|
|
self->best_move_yaw=wall_angles[YAW];
|
|
//restore yaw
|
|
self->s.angles[YAW] = save_yaw;
|
|
//start turning this move, but don't actually move until next time
|
|
MG_ChangeWhichYaw(self, YAW_BEST_MOVE);
|
|
}
|
|
}
|
|
self->yaw_speed = save_yaw_speed;
|
|
return false;
|
|
}
|
|
else//keep turning to ideal
|
|
self->monsterinfo.idle_time = 0;
|
|
|
|
//Must have bumped into something very strange (other monster?)
|
|
//just pick a new random dir
|
|
#ifdef _DEVEL
|
|
if(MGAI_DEBUG)
|
|
gi.dprintf("Don't know what I hit, choosing newdir for a second\n");
|
|
#endif
|
|
|
|
MG_NewDir(self, dist);
|
|
return false;
|
|
} |