thirtyflightsofloving/awaken2/g_bot.c
Knightmare66 1e5348cbe0 Refactoring and rearrangment of menu code.
Added new graphic for text box.
Made edict_t pointer arrays static in 3ZB2, Awakening2, and Zaero DLLs due to stack size concerns.
2021-08-12 15:30:51 -04:00

7131 lines
172 KiB
C

// g_bot.c
//Maj/Pon++
#include "g_local.h"
#include "m_player.h"
// Special thanks to Ponpoko for his brilliant work upon
// which this work is based.
#define MaxOf(x,y) ((x)>(y)?(x):(y))
//CW++
vec3_t VEC_TMINS4 = {-4.0F, -4.0F, -4.0F};
vec3_t VEC_TMAXS4 = { 4.0F, 4.0F, 4.0F};
//CW--
qboolean Bot_Fall(edict_t *ent, vec3_t pos, float dist);
//=====================================================
//=====================================================
botinfo_t Bot[MAXBOTS+1];
int NumBotsInGame = 0; // [1..MAXBOTS]
route_t Route[MAXNODES];
int TotalRouteNodes = 0;
int CurrentIndex = 0;
qboolean pickup_priority = false;
int trace_priority = 0;
float JumpMax = 0.0F;
vec3_t zvec = {0, 0, 0};
float myrandom = 0.5f;
int SkillLevel[10] = {
//skill 0
FIRE_REFUGE | FIRE_PRESTAYFIRE | FIRE_STAYFIRE, //CW...
//skill 1
FIRE_REFUGE | FIRE_PRESTAYFIRE,
//skill 2
FIRE_REFUGE,
//skill 3
FIRE_REFUGE | FIRE_IGNORE,
//skill 4
FIRE_REFUGE | FIRE_IGNORE | FIRE_AVOIDINVULN,
//skill 5
FIRE_REFUGE | FIRE_IGNORE | FIRE_AVOIDINVULN | FIRE_AVOIDEXPLO,
//skill 6
FIRE_JUMPROC | FIRE_IGNORE | FIRE_AVOIDINVULN | FIRE_AVOIDEXPLO,
//skill 7
FIRE_JUMPROC | FIRE_IGNORE | FIRE_AVOIDINVULN | FIRE_AVOIDEXPLO | FIRE_C4USE,
//skill 8
FIRE_JUMPROC | FIRE_IGNORE | FIRE_AVOIDINVULN | FIRE_AVOIDEXPLO | FIRE_C4USE | FIRE_DODGE,
//skill 9
FIRE_JUMPROC | FIRE_IGNORE | FIRE_AVOIDINVULN | FIRE_AVOIDEXPLO | FIRE_C4USE | FIRE_DODGE | FIRE_QUADUSE
};
typedef char cfg[64];
typedef cfg cfg_t[2];
cfg_t gbot[] = { // ENTRIES MUST == MAXBOTS, else kabooom!
{ "Tyr574[BOT]", "cyborg/tyr574" },
{ "Razor[BOT]", "male/razor" },
{ "Cobalt[BOT]", "female/cobalt" },
{ "Scout[BOT]", "male/scout" },
{ "PS9000[BOT]", "cyborg/ps9000" },
{ "Brianna[BOT]", "female/brianna" },
{ "Recon[BOT]", "male/recon" },
{ "Viper[BOT]", "male/viper" },
{ "Oni911[BOT]", "cyborg/oni911" },
{ "Flak[BOT]", "male/flak" },
{ "Venus[BOT]", "female/venus" },
{ "Pointman[BOT]", "male/pointman" },
{ "Stiletto[BOT]", "female/stiletto" },
{ "Claymore[BOT]", "male/claymore" },
{ "Jezebel[BOT]", "female/jezebel" },
{ "Cypher[BOT]", "male/cypher" },
{ "Athena[BOT]", "female/athena" },
{ "Major[BOT]", "male/major" },
{ "Jungle[BOT]", "female/jungle" },
{ "Howitzer[BOT]", "male/howitzer" },
{ "Ensign[BOT]", "female/ensign" },
{ "NightOps[BOT]", "male/nightops" },
{ "Psycho[BOT]", "male/psycho" },
{ "Voodoo[BOT]", "female/voodoo" },
{ "Rampage[BOT]", "male/rampage" },
{ "Brazen[BOT]", "cyborg/tyr574" },
{ "Zeroid[BOT]", "male/razor" },
{ "Lotus[BOT]", "female/lotus" },
{ "Grunt[BOT]", "male/grunt" },
//CW++
{ "Mu[BOT]", "male/psycho" },
{ "Wiz[BOT]", "male/rampage" },
{ "Monstra[BOT]", "cyborg/ps9000" },
{ "Sn33k[BOT]", "female/jezebel" },
{ "Sherm[BOT]", "male/cypher" },
{ "Bassy[BOT]", "male/claymore" },
{ "Mis[BOT]", "female/lotus" },
{ "QUIET![BOT]", "male/major" },
{ "C.G.[BOT]", "cyborg/tyr574" },
{ "Wyld[BOT]", "male/howitzer" },
{ "Webdude[BOT]", "male/claymore" },
{ "Leadhed[BOT]", "male/pointman" },
{ "Yestah[BOT]", "male/viper" },
{ "Panzi[BOT]", "male/nightops" },
{ "Buzzi[BOT]", "male/claymore" },
{ "Sarkastor[BOT]", "male/viper" },
{ "Deth[BOT]", "male/grunt" },
{ "Flashy[BOT]", "male/flak" },
{ "Ripley[BOT]", "female/brianna" },
{ "Kryten[BOT]", "cyborg/ps9000" },
{ "Zakalwe[BOT]", "cyborg/oni911" }, // Count = 50
//CW--
{ "Killer[BOT]", "male/grunt" }, // 1 extra for safety
};
//===================================
//===================================
//======================================================
//========== BASIC BOT UTILITY FUNCTIONS ===============
//======================================================
//======================================================
qboolean G_EntExists(edict_t *ent)
{
return (ent && ent->client && ent->inuse);
}
//======================================================
qboolean G_ClientNotDead(edict_t *ent)
{
qboolean b1 = (ent->client->ps.pmove.pm_type != PM_DEAD);
qboolean b2 = (ent->deadflag == DEAD_NO);
qboolean b3 = (ent->health > 0);
return (b1 || b2 || b3); //CW
}
//======================================================
qboolean G_ClientInGame(edict_t *ent)
{
if (!G_EntExists(ent))
return false;
if (!G_ClientNotDead(ent))
return false;
if (ent->client->spectator) //CW
return false;
return (ent->client->respawn_time + 5.0 < level.time);
}
//==============================================
float Get_yaw(vec3_t vec)
{
vec3_t out;
double yaw;
VectorCopy(vec, out);
out[2] = 0.0;
VectorNormalize(out);
yaw = (double)(RAD2DEG(acos((double)out[0]))); //CW
if (asin((double)out[1]) < 0)
yaw *= -1.0;
return (float)yaw;
}
//==============================================
float Get_pitch(vec3_t vec)
{
vec3_t out;
float pitch;
VectorCopy(vec, out);
VectorNormalize(out);
pitch = (float)(RAD2DEG(acos((double)out[2]))) - 90.0; //CW
return (float)((pitch < -180.0) ? (pitch + 360.0) : pitch);
}
//==============================================
float Get_vec_yaw(vec3_t vec, float yaw)
{
float vecsyaw;
vecsyaw = Get_yaw(vec);
if (vecsyaw > yaw)
vecsyaw -= yaw;
else
vecsyaw = yaw - vecsyaw;
if (vecsyaw > 180)
vecsyaw = 360 - vecsyaw;
return vecsyaw;
}
//======================================================
void AdjustAngle(edict_t *ent, vec3_t targaim, float aim, float angle_gap) //CW
{
VectorSet(ent->s.angles, (Get_pitch(targaim)), (Get_yaw(targaim)), 0.0F);
ent->s.angles[YAW] += aim * angle_gap * (myrandom - 0.5);
if (ent->s.angles[YAW] > 180.0)
ent->s.angles[YAW] -= 360.0;
else if (ent->s.angles[YAW] < -180.0)
ent->s.angles[YAW] += 360.0;
ent->s.angles[PITCH] += aim * angle_gap * (myrandom - 0.5);
if (ent->s.angles[PITCH] > 90.0)
ent->s.angles[PITCH] = 90.0;
else if (ent->s.angles[PITCH] < -90.0)
ent->s.angles[PITCH] = -90.0;
}
//=============================================
qboolean BankCheck(edict_t *ent, vec3_t pos)
{
trace_t tr;
vec3_t end;
VectorCopy(pos, end);
end[2] = -4096.0; //CW
tr = gi.trace(pos, ent->mins, ent->maxs, end, ent, MASK_BOTSOLIDX);
return !(tr.startsolid || tr.allsolid || (tr.plane.normal[2] < 0.8));
}
//=============================================
qboolean HazardCheck(edict_t *ent, vec3_t pos)
{
trace_t tr;
vec3_t end;
int contents;
VectorCopy(pos, end);
end[2] = -4096.0; //CW
contents = (ent->client->enviro_framenum > level.framenum)?CONTENTS_LAVA:(CONTENTS_LAVA|CONTENTS_SLIME);
tr = gi.trace(pos, ent->mins, ent->maxs, end, ent, MASK_OPAQUE);
return !(tr.contents & contents); // true = no hazard detected; false = hazard detected
}
//=============================================
//CW++
qboolean TriggerHurtCheck(edict_t *ent)
{
edict_t *trighurt = NULL;
qboolean danger_willrobinson = false;
trace_t tr;
vec3_t end;
end[0] = ent->s.origin[0];
end[1] = ent->s.origin[1];
while ((trighurt = G_Find(trighurt, FOFS(classname), "trigger_hurt")) != NULL)
{
if ((ent->s.origin[0] > trighurt->mins[0]) && (ent->s.origin[0] < trighurt->maxs[0]) &&
(ent->s.origin[1] > trighurt->mins[1]) && (ent->s.origin[1] < trighurt->maxs[1]) &&
(ent->s.origin[2] - ent->mins[2] > trighurt->maxs[2]))
{
end[2] = trighurt->maxs[2];
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_SOLID);
if (tr.fraction == 1.0)
{
danger_willrobinson = true;
break;
}
}
}
return danger_willrobinson; // true = trigger_hurt detected below; false = no trigger_hurt detected
}
//CW--
//==============================================
void SetBotAnim(edict_t *ent)
{
gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //CW++
if (ent->client->anim_priority >= ANIM_JUMP)
return;
ent->s.frame = FRAME_jump1 - 1;
ent->client->anim_end = FRAME_jump6;
}
//============================================================
qboolean Get_FlyingSpeed(float bottom, float block, float dist, float *speed)
{
float tdist;
if (bottom >= 40)
{
if (block > 4) return false;
tdist = (dist * block) * 0.250;
}
else if (bottom >= 35)
{
if (block > 5) return false;
tdist = (dist * block) * 0.200;
}
else if (bottom >= 30)
{
if (block > 6) return false;
tdist = (dist * block) * 0.167;
}
else if (bottom >= 20)
{
if (block > 7) return false;
tdist = (dist * block) * 0.143;
}
else if (bottom >= -5)
{
if (block > 8) return false;
tdist = (dist * block) * 0.125;
}
else if (bottom >= -20)
{
if (block > 9) return false;
tdist = (dist * block) * 0.143;
}
else if (bottom >= -35)
{
if (block > 10) return false;
tdist = (dist * block) * 0.167;
}
else if (bottom >= -52)
{
if (block > 11) return false;
tdist = (dist * block) * 0.200;
}
else if (bottom >= -75)
{
if (block > 12) return false;
tdist = (dist * block) * 0.250;
}
else if (bottom >= -95)
{
if (block > 13) return false;
tdist = (dist * block) * 0.333;
}
else if (bottom >=-125)
{
if (block > 14) return false;
tdist = (dist * block) * 0.500;
}
else
{
if (block > 15) return false;
tdist = (dist * block) * 0.500;
}
*speed = tdist / 30.0;
return true;
}
//==========================================
float SetBotXYSpeed(edict_t *ent, float *xyspeed)
{
if (!ent->isabot)
return *xyspeed;
//CW++
if (ent->tractored)
return *xyspeed;
if (ent->client->held_by_agm || ent->client->flung_by_agm || ent->client->thrown_by_agm)
return *xyspeed;
//CW--
if (ent->groundentity && (ent->client->movestate & STS_WAITS))
{
*xyspeed = (VectorLength(ent->groundentity->velocity) < 1) ? 300 : 0;
if (*xyspeed)
ent->client->movestate |= STS_W_DONT; // don't wait
}
else
*xyspeed = (ent->client->camptime > level.time) ? 0 : 300;
return *xyspeed;
}
//==========================================
void SetBotThink(edict_t *ent)
{
if (!ent->isabot)
return;
ent->client->chattime = level.time + (10.0 * (rand() % 7));
ent->client->ping = atoi(Info_ValueForKey(ent->client->pers.userinfo, "ping"));
ent->think = Bot_Think;
ent->nextthink = level.time + FRAMETIME;
}
//==========================================
void ForceRouteReset (edict_t *other)
{
if (!other->isabot)
return;
if (!other->client->routetrace)
return;
if (other->client->pers.routeindex < TotalRouteNodes)
{
if (Route[other->client->pers.routeindex].state == GRS_TELEPORT)
other->client->pers.routeindex++;
if (other->client->pers.routeindex < TotalRouteNodes)
{
if (Route[other->client->pers.routeindex].state == GRS_GRAPRELEASE)
other->client->pers.routeindex++;
}
}
}
//==========================================
void G_FindTrainTeam (void) //CW (various bugfixes)
{
static edict_t *teamlist[MAX_EDICTS + 1]; // Knightmare- made static due to stack size
edict_t *e;
edict_t *t;
edict_t *p;
static char *targethist[MAX_EDICTS]; // Knightmare- made static due to stack size
char *currtarget;
char *currtargetname;
qboolean findteam = false;
int loopindex;
int lc;
int i;
int j;
int k;
e = &g_edicts[(int)maxclients->value+1];
for (i = (int)maxclients->value + 1; i < globals.num_edicts; i++, e++)
{
if (e->inuse && e->classname)
{
if ((e->touch == path_corner_touch) && e->targetname && e->target)
{
currtarget = e->target;
currtargetname = e->targetname;
memset(&teamlist, 0, sizeof(teamlist));
memset(&targethist, 0, sizeof(targethist));
targethist[0] = e->targetname;
lc = 0;
loopindex = 0;
while (lc < MAX_EDICTS)
{
t = &g_edicts[(int)maxclients->value+1];
for (j = (int)maxclients->value + 1; j < globals.num_edicts; j++, t++)
{
if (t->inuse && t->classname)
{
if ((t->use == train_use) && !Q_stricmp(t->target, currtargetname) && (t->trainteam == NULL))
{
for (k = 0; k < lc; k++)
{
if (teamlist[k] == t)
break;
}
if (k == lc)
{
teamlist[lc] = t;
lc++;
}
}
}
}
p = G_PickTarget(currtarget);
if (!p)
break;
currtarget = p->target;
currtargetname = p->targetname;
if (!p->target)
break;
for (k = 0; k < loopindex; k++)
{
if (!Q_stricmp(targethist[k], currtargetname))
break;
}
if (k < loopindex)
{
findteam = true;
break;
}
targethist[loopindex] = currtargetname;
loopindex++;
}
if (findteam && (lc > 0))
{
gi.dprintf("%i train chainings found.\n", lc);
for (k = 0; k < lc; k++)
{
if (teamlist[k+1] == NULL)
{
teamlist[k]->trainteam = teamlist[0];
break;
}
teamlist[k]->trainteam = teamlist[k+1];
}
}
}
}
}
}
//==============================================
void droptofloor2(edict_t *ent)
{
trace_t tr;
vec3_t trmin;
vec3_t trmax;
vec3_t min;
vec3_t mins;
vec3_t maxs;
vec3_t dest;
vec3_t v;
float i;
float j = 0;
float yaw;
VectorSet(ent->mins, -15, -15, -15);
VectorSet(ent->maxs, 8, 8, 15);
if (ent->union_ent && !(ent->item == item_navi2)) //CW
{
dest[0] = (ent->union_ent->s.origin[0] + ent->union_ent->mins[0] + ent->union_ent->s.origin[0] + ent->union_ent->maxs[0]) * 0.5;
dest[1] = (ent->union_ent->s.origin[1] + ent->union_ent->mins[1] + ent->union_ent->s.origin[1] + ent->union_ent->maxs[1]) * 0.5;
for (i = ent->union_ent->s.origin[2] + ent->union_ent->mins[2]; i <= ent->union_ent->s.origin[2] + ent->union_ent->maxs[2] + 16; i++)
{
dest[2] = i;
tr = gi.trace(dest, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
if (!tr.startsolid && !tr.allsolid && (j == 1))
{
j = 2;
break;
}
else if ((tr.startsolid || tr.allsolid) && !j && (tr.ent == ent->union_ent)) //CW
j = 1;
}
VectorCopy(dest, ent->s.origin);
VectorSubtract(ent->s.origin, ent->union_ent->s.origin, ent->moveinfo.dir);
}
ent->s.modelindex = 0;
ent->solid = (ent->item == item_navi3) ? SOLID_NOT : SOLID_TRIGGER;
ent->movetype = MOVETYPE_TOSS;
ent->touch = Touch_Item;
ent->use = NULL;
VectorSet(v, 0, 0, -128);
VectorAdd(ent->s.origin, v, dest);
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
if (tr.startsolid && (ent->classname[0] != 'R' && ent->classname[6] != '1')) //CW
{
gi.dprintf("droptofloor2: %s startsolid at %s.\n", (ent->classname)?ent->classname:"unknown", vtosf(ent->s.origin));
G_FreeEdict(ent);
return;
}
VectorCopy(tr.endpos, ent->s.origin);
if (ent->team)
{
ent->flags &= ~FL_TEAMSLAVE;
ent->chain = ent->teamchain;
ent->teamchain = NULL;
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
if (ent == ent->teammaster)
{
ent->nextthink = level.time + FRAMETIME;
ent->think = DoRespawn;
}
}
if (ent->spawnflags & 2) // NO_TOUCH
{
ent->solid = SOLID_BBOX;
ent->touch = NULL;
ent->s.effects &= ~EF_ROTATE;
ent->s.renderfx &= ~RF_GLOW;
}
if (ent->spawnflags & 1) // TRIGGER_SPAWN
{
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
ent->use = Use_Item;
}
gi.linkentity(ent);
VectorCopy(ent->s.origin, min);
VectorSet(mins, -15, -15, -15);
VectorSet(maxs, 8, 8, 0);
min[2] -= 128;
for (i = 0; i < 8; i++)
{
if (i < 4)
{
yaw = DEG2RAD((90 * i) - 180);
for (j = 32; j < 80; j += 2)
{
trmin[0] = ent->s.origin[0] + cos(yaw) * j;
trmin[1] = ent->s.origin[1] + sin(yaw) * j;
trmin[2] = ent->s.origin[2];
VectorCopy(trmin, trmax);
trmax[2] -= 128;
tr = gi.trace(trmin, mins, maxs, trmax, ent, MASK_PLAYERSOLID);
if ((tr.endpos[2] < ent->s.origin[2] - 16) && (tr.endpos[2] > min[2]))
{
if (!tr.allsolid && !tr.startsolid)
{
min[2] = tr.endpos[2];
min[0] = ent->s.origin[0] + cos(yaw) * (j + 16);
min[1] = ent->s.origin[1] + sin(yaw) * (j + 16);
break;
}
}
}
}
else
{
yaw = DEG2RAD((90 * (i - 4)) - 135);
for (j = 32; j < 80; j += 2)
{
trmin[0] = ent->s.origin[0] + cos(yaw) * 46;
trmin[1] = ent->s.origin[1] + sin(yaw) * 46;
trmin[2] = ent->s.origin[2];
VectorCopy(trmin, trmax);
trmax[2] -= 128;
tr = gi.trace(trmin, NULL, NULL, trmax, ent, MASK_PLAYERSOLID);
if ((tr.endpos[2] < ent->s.origin[2] - 16) && (tr.endpos[2] > min[2]))
{
if (!tr.allsolid && !tr.startsolid)
{
VectorCopy(tr.endpos, min);
break;
}
}
}
}
}
VectorCopy(min, ent->moveinfo.start_origin);
}
//==============================================
void TraceAllSolid(edict_t *ent, vec3_t point, trace_t tr)
{
if (tr.allsolid)
{
trace_t tracep;
vec3_t stp;
vec3_t v1;
vec3_t v2;
VectorSet(v1, -16, -16, -24);
VectorSet(v2, 16, 16, 4);
VectorCopy(ent->s.origin, stp);
stp[2] += 24;
tracep = gi.trace(stp, v1, v2, point, ent, MASK_BOTSOLID);
if (tracep.ent && !tracep.allsolid)
{
if (tracep.ent->classname[0] == 'f')
{
VectorCopy(tracep.endpos, ent->s.origin);
ent->groundentity = tracep.ent;
ent->groundentity_linkcount = tracep.ent->linkcount;
gi.linkentity(ent);
return;
}
}
}
else
{
if (ent->client)
{
ent->client->ground_contents = tr.contents;
ent->client->ground_slope = tr.plane.normal[2];
}
VectorCopy(tr.endpos, ent->s.origin);
ent->groundentity = tr.ent;
ent->groundentity_linkcount = tr.ent->linkcount;
}
gi.linkentity(ent);
}
//==============================================
void ResetGroundSlope(edict_t *ent)
{
if (!ent->isabot)
return;
ent->client->ground_slope = 1.0;
}
//==============================================
void SpawnItem3(edict_t *it_ent, gitem_t *item)
{
it_ent->item = item;
it_ent->s.effects = 0;
it_ent->s.renderfx = 0;
it_ent->s.modelindex = 0;
it_ent->nextthink = level.time + 0.2;
it_ent->think = droptofloor2;
}
//===============================================
void bFuncTrain(edict_t *self)
{
gitem_t *it = item_navi1;
edict_t *it_ent = G_Spawn();
VectorAdd(self->s.origin, self->mins, self->monsterinfo.last_sighting);
it_ent->classname = it->classname;
it_ent->union_ent = self;
self->union_ent = it_ent;
SpawnItem3(it_ent, it);
}
//===============================================
void bFuncDoor(edict_t *ent)
{
VectorAdd(ent->s.origin, ent->mins, ent->monsterinfo.last_sighting);
if (fabs(ent->moveinfo.start_origin[2] - ent->moveinfo.end_origin[2]) > 20.0)
{
gitem_t *it = item_navi3;
edict_t *it_ent = G_Spawn();
it_ent->classname = it->classname;
it_ent->union_ent = ent;
ent->union_ent = it_ent;
SpawnItem3(it_ent, it);
}
}
//===============================================
void bDoorBlocked(edict_t *self)
{
edict_t *ent; //CW
int i;
for (i = 1; i <= maxclients->value; i++)
{
ent = &g_edicts[i]; //CW
if (!ent->isabot)
continue;
if (ent->client->waiting_obj != self)
continue;
if (!ent->client->movestate)
continue;
ent->client->movestate |= STS_W_DONT;
}
}
//===============================================
void bFuncButton(edict_t *ent)
{
edict_t *it_ent = G_Spawn();
gitem_t *it = item_navi2;
vec3_t tdir;
vec3_t tdir2;
vec3_t abs_movedir;
float dist = 1.0;
it_ent->classname = it->classname;
VectorAdd(ent->s.origin, ent->mins, ent->monsterinfo.last_sighting);
VectorCopy(ent->s.origin, it_ent->s.origin);
it_ent->s.origin[0] = (ent->absmin[0] + ent->absmax[0]) * 0.5;
it_ent->s.origin[1] = (ent->absmin[1] + ent->absmax[1]) * 0.5;
it_ent->s.origin[2] = (ent->absmin[2] + ent->absmax[2]) * 0.5;
it_ent->union_ent = ent;
ent->union_ent = it_ent;
VectorSubtract(ent->moveinfo.start_origin, ent->moveinfo.end_origin, abs_movedir);
VectorNormalize(abs_movedir);
while (dist < 500.0)
{
VectorScale(abs_movedir, dist, tdir);
VectorAdd(it_ent->s.origin, tdir, tdir2);
if (!(gi.pointcontents(tdir2) & CONTENTS_SOLID))
break;
dist++;
}
VectorScale(abs_movedir, (dist + 20), tdir);
VectorAdd(it_ent->s.origin, tdir, tdir2);
VectorCopy(tdir2,it_ent->s.origin);
it_ent->item = it;
it_ent->s.effects = 0;
it_ent->s.renderfx = 0;
it_ent->s.modelindex = 0;
it_ent->solid = SOLID_TRIGGER;
it_ent->movetype = MOVETYPE_NONE;
it_ent->touch = Touch_Item;
gi.linkentity(it_ent);
}
//===============================================
void bFuncPlat(edict_t *ent)
{
edict_t *it_ent = G_Spawn();
gitem_t *it = item_navi1;
VectorAdd(ent->s.origin, ent->mins, ent->monsterinfo.last_sighting);
it_ent->classname = it->classname;
it_ent->union_ent = ent;
ent->union_ent = it_ent;
SpawnItem3(it_ent, it);
}
//===============================================
void CheckBotCrushed(edict_t *targ,edict_t *inflictor, int mod)
{
if (!targ->isabot || (mod != MOD_CRUSH))
return;
if (((targ->client->waiting_obj == inflictor) && targ->client->movestate) || (targ->groundentity == inflictor))
targ->client->movestate |= STS_W_DONT;
}
//===============================================
void CheckPrimaryWeapon(edict_t *ent, edict_t *other)
{
if (!other->isabot)
return;
if (ent->item->use)
{
// switch weapon if picked up primary
int wepnum = GetKindWeapon(ent->item);
if (wepnum != MOD_CHAINSAW) //CW
{
if (Bot[other->client->pers.botindex].skill[PRIMARYWEAP] == wepnum) //CW
ent->item->use(other, ent->item);
}
}
}
//===============================================
void Bot_CheckEnemy(gclient_t *client, edict_t *attacker, edict_t *targ, int mod)
{
if (client && targ->isabot && attacker)
{
if (client->battlemode & FIRE_CHICKEN) //CW
client->battlemode &= ~FIRE_CHICKEN;
//CW++
// If tractored, target the trap.
if (mod == MOD_TRAP)
{
edict_t *echeck;
int i;
for (i = 0; i < globals.num_edicts; ++i)
{
echeck = &g_edicts[i];
if (!echeck->inuse)
continue;
if (echeck->client)
continue;
if (!echeck->die)
continue;
if ((echeck->die == Trap_DieFromDamage) && (echeck->enemy == targ))
{
client->current_enemy = echeck;
break;
}
}
}
// If in lava/slime/hurt zone and have a Personal Teleporter, use it.
else if ((mod == MOD_LAVA) || (mod == MOD_SLIME) || (mod == MOD_TRIGGER_HURT))
{
if (targ->client->pers.inventory[ITEM_INDEX(item_teleporter)])
Use_Teleporter(targ, item_teleporter);
}
else if (attacker->client && !(attacker->flags & FL_NOTARGET))
{
qboolean switch_enemy;
switch_enemy = ((mod == MOD_SR_HOMING) || (mod == MOD_SR_DISINT_WAVE) || (mod == MOD_RAILGUN) || (mod == MOD_ROCKET) ||
(mod == MOD_GAUSS_BLASTER) || (mod == MOD_GAUSS_BEAM) || (mod == MOD_FLAMETHROWER) || (mod == MOD_CHAINSAW) ||
(mod == MOD_AGM_DISRUPT) || (mod == MOD_AGM_LAVA_HELD) || (mod == MOD_AGM_SLIME_HELD) || (mod == MOD_AGM_WATER_HELD) ||
(mod == MOD_AGM_TRIG_HURT) || (mod == MOD_AGM_TARG_LASER));
switch_enemy = switch_enemy && (client->current_enemy != attacker);
if (!client->current_enemy || (client->current_enemy && switch_enemy && (client->current_enemy->health > 25)))
{
if (!CheckTeamDamage(targ, attacker))
client->current_enemy = attacker;
}
}
//CW--
}
}
//===============================================
void CheckCampSite(edict_t *ent, edict_t *other)
{
if (!other->isabot)
return;
//CW++
if (!(int)sv_bots_camp->value)
return;
if (!Bot[other->client->pers.botindex].camper)
return;
if (other->client->camping)
return;
//CW--
if ((ent->item != item_health_mega) && (ent->item != item_railgun) && (ent->item != item_bodyarmor) && (ent->item != item_powershield)) //CW
return;
if (other->client->quad_framenum > level.framenum)
return;
if (other->client->camptime >= level.time)
return;
if (random() > BOT_CAMP_PROB) //CW
return;
other->client->camptime = level.time + 20.0 + (rand() % 11); // 20..30
other->client->chattime = level.time + (rand() % 16); // 0..15
other->client->taunttime = other->client->camptime + 10.0; // turn taunting off whilst camping
VectorCopy(ent->s.origin, other->client->lastorigin);
other->client->lastorigin[2] += 16.0;
other->client->campitem = ent->item; //camping near this item
other->client->camping = true; //CW++
}
//======================================================
//======== ROUTE FILE AND BOT CONFIGURATION ============
//======================================================
void LoadBotConfig(void)
{
FILE *filestream;
char name[512];
char skin[512];
int fnum;
int num_bots = 0;
int combat_skill = 0;
int accuracy = 0;
int aggression = 0;
int weapon = 1;
int h_view = 90;
int v_view = 90;
int camper = 1;
qboolean finished = false;
if ((filestream = OpenBotConfigFile(true, true)) != NULL)
{
memset(&name, 0, sizeof(name));
memset(&skin, 0, sizeof(skin));
while (!finished && ((fnum = fscanf(filestream, "%s %s %d %d %d %d %d %d %d", name, skin, &combat_skill, &accuracy, &aggression, &weapon, &h_view, &v_view, &camper)) != EOF))
{
if (strlen(name) >= MAX_NAMELEN)
{
gi.dprintf("** Bot name in line %d is too long.\n", num_bots + 1);
name[MAX_NAMELEN-1] = 0;
}
if (strlen(skin) >= MAX_SKINLEN)
{
gi.dprintf("** Bot skin in line %d is too long.\n", num_bots + 1);
skin[MAX_SKINLEN-1] = 0;
}
memset(&Bot[num_bots], 0, sizeof(botinfo_t));
strncpy(Bot[num_bots].netname, name, MAX_NAMELEN); //r1,CW
strncpy(Bot[num_bots].skin, skin, MAX_SKINLEN); //r1,CW
//combat skill [0..9]
if (combat_skill < 0)
combat_skill = 0;
else if (combat_skill > 9)
combat_skill = 9;
Bot[num_bots].skill[COMBATSKILL] = combat_skill;
//aiming accuracy [0..9]
if (accuracy < 0)
accuracy = 0;
else if (accuracy > 9)
accuracy = 9;
Bot[num_bots].skill[AIMACCURACY] = accuracy;
//aggression [0..9]
if (aggression < 0)
aggression = 0;
else if (aggression > 9)
aggression = 9;
Bot[num_bots].skill[AGGRESSION] = aggression;
//primary weapon [1..14]
if (weapon < 1)
weapon = 1;
else if (weapon > 14)
weapon = 14;
Bot[num_bots].skill[PRIMARYWEAP] = weapon;
//horizontal view range [0..180]
if (h_view < 0)
h_view = 0;
else if (h_view > 180)
h_view = 180;
Bot[num_bots].skill[HRANGEVIEW] = h_view;
//vertical view range [0..180]
if (v_view < 0)
v_view = 0;
else if (v_view > 180)
v_view = 180;
Bot[num_bots].skill[VRANGEVIEW] = v_view;
//camping tendency [0 or 1]
if (camper < 0)
camper = 0;
else if (camper > 1)
camper = 1;
Bot[num_bots].camper = (qboolean)camper;
gi.dprintf("Loaded bot: %s\n", Bot[num_bots].netname);
if (++num_bots == MAXBOTS)
{
gi.dprintf("** Maximum number of bots loaded!\n");
finished = true;
}
memset(&name, 0, sizeof(name));
memset(&skin, 0, sizeof(skin));
}
fclose(filestream);
if (num_bots == 0)
gi.dprintf("** Bot config file has no valid entries\n");
gi.dprintf("\n");
}
}
//===============================================
void RandomizeParameters(int i)
{
//CW++ (rewritten)
int primary_weapon;
Bot[i].skill[AIMACCURACY] = rand() % 10; // [0..9]
Bot[i].skill[AGGRESSION] = rand() % 10; // [0..9]
Bot[i].skill[COMBATSKILL] = rand() % 10; // [0..9]
Bot[i].skill[VRANGEVIEW] = 60 + (10 * (rand() % 7)); // [60..120]
Bot[i].skill[HRANGEVIEW] = 60 + (10 * (rand() % 7)); // [60..120]
Bot[i].camper = rand() % 2; // [0 or 1]
do
{
primary_weapon = 2 + (rand() % 13); // [2..14]
}
while (primary_weapon == WEAP_TRAP); // traps should never be a primary weapon (unless you're Bass[MaN] ;-)
Bot[i].skill[PRIMARYWEAP] = primary_weapon;
}
//CW--
//==============================================
void LoadBotNames(void)
{
int i;
for (i = 0; i < MAXBOTS; i++)
{
memset(&Bot[i], 0, sizeof(botinfo_t));
Com_sprintf(Bot[i].netname, sizeof(Bot[i].netname), "%s", gbot[i][0]);
Com_sprintf(Bot[i].skin, sizeof(Bot[i].skin), "%s", gbot[i][1]);
RandomizeParameters(i);
}
}
//CW++
void LoadBots(void)
{
LoadBotNames();
if ((int)sv_bots_use_file->value)
LoadBotConfig();
}
//CW--
//==================================================
qboolean RTJump_Chk(vec3_t apos, vec3_t tpos)
{
float x;
float l;
float vel;
float yori;
vec3_t v;
vec3_t vv;
int mf = 0;
vel = VEL_BOT_JUMP;
yori = apos[2];
VectorSubtract(tpos, apos, v);
for (x = 1; x <= BOT_FALLCHK_LOOPMAX * 2; ++x)
{
vel -= sv_gravity->value * FRAMETIME;
yori += vel * 0.1;
if (vel > 0)
{
if (mf == 0)
{
if (tpos[2] < yori)
mf = 2;
}
}
else if (x > 1)
{
if (mf == 0)
{
if (tpos[2] < yori)
mf = 2;
}
else if (mf == 2)
{
if (tpos[2] >= yori)
{
mf = 1;
break;
}
}
}
}
VectorCopy(v, vv);
vv[2] = 0;
l = VectorLength(vv);
if (x > 1)
l /= (x - 1);
return ((l < MOVE_SPD_RUN) && (mf == 1));
}
//==============================================
void G_FindRouteLink(edict_t *ent)
{
trace_t rs_trace;
vec3_t v;
float x;
qboolean tpbool;
int i;
int j;
int k;
int l;
int total = 0;
gi.dprintf("Linking routes...");
// get JumpMax
if (JumpMax == 0)
{
x = VEL_BOT_JUMP - (ent->gravity * sv_gravity->value * FRAMETIME);
JumpMax = 0;
while (1)
{
JumpMax += x * FRAMETIME;
x -= ent->gravity * sv_gravity->value * FRAMETIME;
if (x < 0)
break;
}
}
// search
for (i = 0; i < CurrentIndex; i++)
{
if (Route[i].state == GRS_NORMAL)
{
for (j = 0; j < CurrentIndex; j++)
{
if ((abs(i - j) <= 50) || (j == i) || (Route[j].state != GRS_NORMAL))
continue;
VectorSubtract(Route[j].Pt, Route[i].Pt, v);
if ((v[2] > JumpMax) || (v[2] < -500))
continue;
v[2] = 0;
if (VectorLength(v) > 200)
continue;
if ((fabs(v[2]) > 20) || (VectorLength(v) > 64))
{
if (!RTJump_Chk(Route[i].Pt, Route[j].Pt))
continue;
}
tpbool = false;
for (l = -5; l < 6; l++)
{
if ((i + l < 0) || (i + l >= CurrentIndex))
continue;
for (k = 0; k < MAXLINKPOD; k++) //CW
{ //search blanked index
if (!Route[i + l].linkpod[k])
break;
if (abs(Route[i + l].linkpod[k] - j) < 50)
{
tpbool = true;
break;
}
}
if (tpbool)
break;
}
if (tpbool)
continue;
rs_trace = gi.trace(Route[j].Pt, NULL, NULL, Route[i].Pt, ent, MASK_SOLID);
// found!
if (!rs_trace.startsolid && !rs_trace.allsolid && (rs_trace.fraction == 1.0))
{
for (k = 0; k < MAXLINKPOD; k++) //CW
{ //search blanked index
if (!Route[i].linkpod[k])
{
Route[i].linkpod[k] = j;
total++;
break;
}
}
}
}
}
}
gi.dprintf("done!\n"); //CW
G_FreeEdict(ent);
}
//==================================================
void ReadRouteFile(void)
{
edict_t *e;
FILE *fp;
char name[MAX_OSPATH]; //CW
vec3_t v;
int i;
int j;
//CW++
cvar_t *game;
game = gi.cvar("game", "", 0);
if (!*game->string)
Com_sprintf(name, sizeof(name), "%s/botroutes/%s.chn", GAMEVERSION, level.mapname);
else
Com_sprintf(name, sizeof(name), "%s/botroutes/%s.chn", game->string, level.mapname);
//CW--
TotalRouteNodes = 0;
if ((fp = fopen(name, "rb")) != NULL) //CW
{
char code[8];
unsigned int size;
CurrentIndex = 0;
memset(Route, 0, sizeof(Route)); //CW
memset(code, 0, 8);
fread(code, sizeof(char), 8, fp);
fread(&CurrentIndex, sizeof(int), 1, fp);
size = (unsigned int)CurrentIndex * sizeof(route_t);
fread(Route, size, 1, fp);
fclose(fp);
TotalRouteNodes = CurrentIndex;
gi.dprintf("AwakenBots: %d route nodes for map\n", TotalRouteNodes);
}
if (TotalRouteNodes == 0)
{
gi.dprintf("AwakenBots: No route file loaded\n"); //CW
return;
}
for (i = 0; i < TotalRouteNodes; i++)
{
if (((Route[i].state > GRS_TELEPORT) && (Route[i].state <= GRS_PUSHBUTTON)) || (Route[i].state == GRS_REDFLAG) || (Route[i].state ==GRS_BLUEFLAG))
{
edict_t *other = &g_edicts[(int)maxclients->value+1];
for (j = maxclients->value+1; j < globals.num_edicts; j++, other++)
{
if (other && other->inuse)
{
if ((Route[i].state == GRS_ONPLAT) || (Route[i].state == GRS_ONTRAIN) || (Route[i].state == GRS_ONDOOR) || (Route[i].state == GRS_PUSHBUTTON))
{
VectorAdd(other->s.origin, other->mins, v);
if (VectorCompare(Route[i].Pt, v))
{
if ((Route[i].state == GRS_ONPLAT) && (!Q_stricmp(other->classname, "func_plat") || !Q_stricmp(other->classname, "func_plat2")) )
{
Route[i].ent = other;
break;
}
else if ((Route[i].state == GRS_ONTRAIN) && !Q_stricmp(other->classname, "func_train"))
{
Route[i].ent = other;
break;
}
else if ((Route[i].state == GRS_ONDOOR) && !Q_stricmp(other->classname, "func_door"))
{
Route[i].ent = other;
break;
}
else if ((Route[i].state == GRS_PUSHBUTTON) && !Q_stricmp(other->classname, "func_button"))
{
Route[i].ent = other;
break;
}
}
}
else if ((Route[i].state == GRS_ITEMS) || (Route[i].state == GRS_REDFLAG) || (Route[i].state == GRS_BLUEFLAG))
{
if (VectorCompare(Route[i].Pt, other->monsterinfo.last_sighting))
{
Route[i].ent = other;
break;
}
}
}
}
if (j >= globals.num_edicts)
Route[i].state = GRS_NORMAL;
}
}
e = G_Spawn();
e->think = G_FindRouteLink;
e->nextthink = level.time + (FRAMETIME * 2.0);
e->svflags |= SVF_NOCLIENT; //CW
}
void Move_LastRouteIndex(void)
{
int i;
for (i = CurrentIndex - 1; i >= 0; i--)
{
if (Route[i].state)
break;
else if (!Route[i].index)
break;
}
if (!CurrentIndex || !Route[i].index)
CurrentIndex = i;
else
CurrentIndex = i + 1;
if (CurrentIndex < MAXNODES)
{
memset(&Route[CurrentIndex], 0, sizeof(route_t));
if (CurrentIndex > 0)
Route[CurrentIndex].index = Route[CurrentIndex - 1].index + 1;
}
}
//======================================================
//============= SPAWNING BOTS INTO THE GAME ============
//======================================================
//======================================================
char *Random_IP(void)
{
static char ipstr[16];
int ip1;
do
{
ip1 = 128 + (rand() % 128);
} while ((ip1 == 192) || (ip1 == 172));
Com_sprintf(ipstr, sizeof(ipstr), "%d.%d.%d.%d", ip1, (int)(rand()%256), (int)(rand()%256), (int)(rand()%256));
return ipstr;
}
//=============================================
int GetFreeEdict(void)
{
//CW++
edict_t *ent;
//CW--
int i;
for (i = (int)(game.maxclients-1); i >= 0; i--)
{
ent = g_edicts + i + 1; //CW
if (!ent->inuse)
{
G_InitEdict(ent);
return i;
}
}
return -1; // refuse connection
}
//======================================================
void G_MuzzleFlash(short rec_no, vec3_t origin, int flashnum)
{
gi.WriteByte(svc_muzzleflash);
gi.WriteShort(rec_no);
gi.WriteByte(flashnum);
gi.multicast(origin, MULTICAST_PVS);
}
//=============================================
qboolean SpawnBot(int botindex) //CW...
{
edict_t *ent;
char userinfo[512];
int clientnum;
if ((botindex < 0) || (botindex > MAXBOTS - 1))
return false;
//CW++
// Add bot to a public slot.
if ((int)sv_reserved->value > 0)
{
if (g_public_used < (int)maxclients->value - (int)sv_reserved->value)
{ // public slot
clientnum = GetFreeEdict();
if (clientnum < 0)
{
gi.dprintf("AddBots: server is full.\n");
return false;
}
ent = g_edicts + clientnum + 1;
ent->client = &game.clients[clientnum];
ent->isabot = true;
g_slots[(int)sv_reserved->value + g_public_used] = ent;
++g_public_used;
}
else
{ // no free slots
gi.dprintf("AddBots: all player slots are taken.\n");
return false;
}
}
else
{
if (g_public_used < (int)maxclients->value)
{ // public slot
clientnum = GetFreeEdict();
if (clientnum < 0)
{
gi.dprintf("AddBots: server is full.\n");
return false;
}
ent = g_edicts + clientnum + 1;
ent->client = &game.clients[clientnum];
ent->isabot = true;
g_slots[g_public_used] = ent;
++g_public_used;
}
else
{ // no free slots
gi.dprintf("AddBots: all player slots are taken.\n");
return false;
}
}
//CW--
InitClientResp(ent->client);
InitClientPersistant(ent->client);
ent->client->pers.botindex = botindex;
ent->client->pers.routeindex = 0;
//CW++
ent->client->pers.connected = true;
ent->client->normal_rockets = true;
ent->client->agm_disrupt = true;
if (teamgame.match > MATCH_NONE)
ent->client->resp.ready = true;
//CW--
Com_sprintf(userinfo, sizeof(userinfo), "\\name\\%s\\skin\\%s\\fov\\90\\hand\\2\\ip\\%s\\ping\\%3d", Bot[botindex].netname, Bot[botindex].skin, Random_IP(), (int)(100+(rand()%128))); // Maj++ - We store ping in userinfo string. hehe
ClientUserinfoChanged(ent, userinfo); //CW++
PutClientInServer(ent);
gi_bprintf(PRINT_HIGH, "%s was added to the game\n", Bot[botindex].netname); //CW
if (!(int)chedit->value)
G_MuzzleFlash((short)(ent-g_edicts), ent->s.origin, (int)(MZ_LOGIN));
ClientEndServerFrame(ent);
return true;
}
//=============================================
// Temp edict to re-insert active bots into game
//=============================================
void PutNextBotInGame(edict_t *ent)
{
if (ent->count >= MAXBOTS)
{
G_FreeEdict(ent);
return;
}
while (ent->count < MAXBOTS)
{
if (Bot[ent->count].ingame)
{
SpawnBot(ent->count++);
break;
}
ent->count++;
}
ent->nextthink = level.time + 0.5;
}
void RespawnAllBots(void)
{
edict_t *ent;
ent = G_Spawn();
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
ent->count = 0;
ent->nextthink = level.time + 1.0;
ent->think = PutNextBotInGame;
ent->classname = "bot_respawner"; //CW++
}
//CW++
//==================================================
qboolean RemoveBot(edict_t *ent)
{
if (!ent || !ent->isabot)
return false;
Bot[ent->client->pers.botindex].ingame = 0;
ClientDisconnect(ent);
ent->isabot = false;
--NumBotsInGame;
return true;
}
//=============================================
// Temp edict to remove bots from the game
// without causing overflows.
//=============================================
void BotRemover_Think(edict_t *self)
{
edict_t *ent;
int i;
if (self->count < 1)
{
G_FreeEdict(self);
return;
}
if (NumBotsInGame == 0)
{
G_FreeEdict(self);
return;
}
for (i = 1; i <= game.maxclients; i++)
{
ent = &g_edicts[i];
if (RemoveBot(ent))
break;
}
--self->count;
self->nextthink = level.time + FRAMETIME;
}
void RemoveNumBots_Safe(int numbots)
{
edict_t *ent;
edict_t *current_remover = NULL;
if (numbots < 1)
return;
if (NumBotsInGame <= 0)
{
gi.cprintf(NULL, PRINT_HIGH, "There are no bots spawned!\n");
return;
}
if ((current_remover = G_Find(NULL, FOFS(classname), "bot_remover")) == NULL)
{
ent = G_Spawn();
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
ent->classname = "bot_remover";
ent->count = numbots;
ent->think = BotRemover_Think;
ent->nextthink = level.time + FRAMETIME;
}
else
{
current_remover->count += numbots;
if (current_remover->count > MAXBOTS)
current_remover->count = MAXBOTS;
}
}
//=============================================
// Temp edict to spawn bots into the game
// without causing overflows.
//=============================================
void BotSpawner_Think(edict_t *self)
{
int n;
if (self->count < 1)
{
G_FreeEdict(self);
return;
}
if (NumBotsInGame >= MAXBOTS)
{
gi.cprintf(NULL, PRINT_HIGH, "Maximum bots spawned!\n");
G_FreeEdict(self);
return;
}
if ((int)sv_bots_random->value)
{
do
{
n = (int)(rand() % MAXBOTS);
} while (Bot[n].ingame);
}
else
n = NumBotsInGame;
if (!SpawnBot(n))
{
G_FreeEdict(self);
return;
}
Bot[n].ingame = 1;
NumBotsInGame++;
--self->count;
self->nextthink = level.time + FRAMETIME;
}
void SpawnNumBots_Safe(int numbots)
{
edict_t *ent;
edict_t *current_spawner = NULL;
if (numbots < 1)
return;
if (NumBotsInGame >= MAXBOTS)
{
gi.cprintf(NULL, PRINT_HIGH, "Maximum bots spawned!\n");
return;
}
if ((current_spawner = G_Find(NULL, FOFS(classname), "bot_spawner")) == NULL)
{
ent = G_Spawn();
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
ent->classname = "bot_spawner";
ent->count = numbots;
ent->think = BotSpawner_Think;
ent->nextthink = level.time + FRAMETIME;
}
else
{
current_spawner->count += numbots;
if (current_spawner->count > MAXBOTS)
current_spawner->count = MAXBOTS;
}
}
//=======================================================
//============ TAUNTING/CHATTING/INSULTING ==============
//=======================================================
//==============================================
void InsultVictim(edict_t *ent, edict_t *victim)
{
if (!ent->isabot)
return;
if (!victim || !victim->client)
return;
if (ent->client->insulttime > level.time)
return;
if (victim == ent) // bot killed self
{
if (myrandom < 0.2)
{
switch (rand() % 8)
{
case 0: gi_bprintf(3, "%s: OUCH!!!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: I hate that!\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Shit!\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: Not again!\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: Ugghhhh!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: WTF!\n", ent->client->pers.netname); break;
case 6: gi_bprintf(3, "%s: Ohhhhh!\n", ent->client->pers.netname); break;
case 7: gi_bprintf(3, "%s: DOH!\n", ent->client->pers.netname); break; //CW++
}
}
}
else // insults to other players
{
if (myrandom < 0.75)
{
switch (rand() % 20)
{
case 0: gi_bprintf(3, "%s: You REALLY suck!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: YOU SUCK!\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Suck THIS!\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: This sucks!\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: Eat Me!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: You ALL suck!\n", ent->client->pers.netname); break;
case 6: gi_bprintf(3, "%s: Suck THAT!\n", ent->client->pers.netname); break;
case 7: gi_bprintf(3, "%s: Muhhhhaahhhaaa\n", ent->client->pers.netname); break;
case 8: gi_bprintf(3, "%s: Muhaaaaaaaaa!!\n", ent->client->pers.netname); break;
case 9: gi_bprintf(3, "%s: Huuuhhhaaaaaa!!\n", ent->client->pers.netname); break;
case 10: gi_bprintf(3, "%s: Muhhhhhhaaaaa!!\n", ent->client->pers.netname); break;
case 11: gi_bprintf(3, "%s: Whoooooaaaaa!!\n", ent->client->pers.netname); break;
case 12: gi_bprintf(3, "%s: Your sister!!\n", ent->client->pers.netname); break;
case 13: gi_bprintf(3, "%s: Your daughter!!\n", ent->client->pers.netname); break;
case 14: gi_bprintf(3, "%s: Your mama!!\n", ent->client->pers.netname); break;
case 15: gi_bprintf(3, "%s: Arggggghhhh!\n", ent->client->pers.netname); break;
case 16: gi_bprintf(3, "%s: Your daddy!!\n", ent->client->pers.netname); break;
case 17: gi_bprintf(3, "%s: Bite Me!\n", ent->client->pers.netname); break;
case 18: gi_bprintf(3, "%s: Haaa Haaaaa!\n", ent->client->pers.netname); break;
case 19: gi_bprintf(3, "%s: Just testing!\n", ent->client->pers.netname); break; //CW++
}
}
}
ent->client->insulttime = level.time + 60.0 + (10.0 * (rand() % 7));
}
//==============================================
void TauntVictim(edict_t *ent, edict_t *victim)
{
vec3_t vtmp;
if (!ent->isabot || (ent == victim))
return;
if (!victim || !victim->client)
return;
if (ent->client->taunttime > level.time)
return;
// Only taunt if near the victim (don't reset timer).
VectorSubtract(ent->s.origin, victim->s.origin, vtmp);
if (VectorLength(vtmp) > BOT_MAX_TAUNT_DIST) //CW
return;
switch (rand() % 3)
{
case 0:
ent->s.frame = FRAME_flip01 - 1;
ent->client->anim_end = FRAME_flip12;
break;
case 1:
ent->s.frame = FRAME_salute01 - 1;
ent->client->anim_end = FRAME_salute11;
break;
case 2:
ent->s.frame = FRAME_taunt01 - 1;
ent->client->anim_end = FRAME_taunt17;
break;
}
ent->client->taunttime = level.time + 30.0 + (10.0 * (rand() % 7));
}
//==============================================
void RandomChat(edict_t *ent)
{
if (ent->client->chattime > level.time)
return;
if (ent->client->camptime > level.time)
{
if (random() < 0.50) // camp and chat 50% of the time
{
if (ent->client->campitem == item_railgun)
{
switch (rand() % 6)
{
case 0: gi_bprintf(3, "%s: Bring firewood next time!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: Want a roasted weener?\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Want a beer with that?\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: Don't ya just love it?\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: Get the camper at the Railgun!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: There's a camper at the Railgun!\n", ent->client->pers.netname); break;
}
}
else if (ent->client->campitem == item_health_mega)
{
switch (rand() % 6)
{
case 0: gi_bprintf(3, "%s: Kill the MegaHealth camper!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: Want marshmallows?\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Got hotdogs?\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: Damn campers!\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: Get the camper by the MegaHealth!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: Camper at the MegaHealth!\n", ent->client->pers.netname); break;
}
}
//CW++
else if (ent->client->campitem == item_bodyarmor)
{
switch (rand() % 6)
{
case 0: gi_bprintf(3, "%s: Kill the RA camper!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: Want marshmallows?\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Got hotdogs?\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: Damn campers!\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: Get the camper by RA!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: Camper at RA!\n", ent->client->pers.netname); break;
}
}
else if (ent->client->campitem == item_powershield)
{
switch (rand() % 6)
{
case 0: gi_bprintf(3, "%s: Kill the PowerShield camper!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: Want marshmallows?\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Got hotdogs?\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: Damn campers!\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: Get the camper by the PowerShield!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: Camper at the PowerShield!\n", ent->client->pers.netname); break;
}
}
//CW--
}
ent->client->chattime = level.time + 5.0 + (rand() % 6);
}
else
{
if (random() < 0.25)
{
switch (rand() % 6)
{
case 0: gi_bprintf(3, "%s: Bunch of chickenshits!\n", ent->client->pers.netname); break;
case 1: gi_bprintf(3, "%s: Come and get it!\n", ent->client->pers.netname); break;
case 2: gi_bprintf(3, "%s: Who wants a piece of me?\n", ent->client->pers.netname); break;
case 3: gi_bprintf(3, "%s: Where'd everybody go?\n", ent->client->pers.netname); break;
case 4: gi_bprintf(3, "%s: This server sucks!\n", ent->client->pers.netname); break;
case 5: gi_bprintf(3, "%s: Only pussies on this server!\n", ent->client->pers.netname); break;
}
}
ent->client->chattime = level.time + 60.0 + (10.0 * (rand() % 7));
}
}
//=====================================================
//=========== BASIC TRACING ALGORITHMS ================
//=====================================================
//==============================================
qboolean InSight(edict_t *ent, edict_t *other)
{
vec3_t start;
vec3_t end;
trace_t tr;
if (other->client && !G_ClientInGame(other))
return false;
VectorCopy(ent->s.origin, start);
start[2] += ent->viewheight - 8.0;
VectorCopy(other->s.origin, end);
end[2] += other->viewheight - 8.0;
if (gi.pointcontents(start) & CONTENTS_WATER) //CW
{
if (!other->waterlevel)
{
tr = gi.trace(end, NULL, NULL, start, ent, CONTENTS_WINDOW | MASK_OPAQUE | CONTENTS_WATER);
if (tr.surface && (tr.surface->flags & SURF_WARP))
return false;
tr = gi.trace(start, NULL, NULL, end, ent, CONTENTS_WINDOW | MASK_OPAQUE);
return (tr.fraction == 1.0);
}
else
{
VectorCopy(other->s.origin, end);
end[2] -= 16.0;
tr = gi.trace(start, NULL, NULL, end, ent, CONTENTS_SOLID | CONTENTS_WINDOW);
return (tr.fraction == 1.0);
}
}
if (other->waterlevel)
{
VectorCopy(other->s.origin, end);
end[2] += 32.0;
tr = gi.trace(start, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_WATER);
if (tr.surface && (tr.surface->flags & SURF_WARP))
return false;
}
return (gi.trace(start, NULL, NULL, end, ent, CONTENTS_WINDOW | MASK_OPAQUE).fraction == 1.0);
}
//==============================================
qboolean Bot_trace(edict_t *ent, edict_t *other)
{
trace_t tr;
vec3_t ttx;
vec3_t tty;
//CW++
if (!other)
return false;
//CW--
VectorCopy(ent->s.origin, ttx);
VectorCopy(other->s.origin, tty);
if (ent->maxs[2] >= 32)
{
if (tty[2] > ttx[2])
tty[2] += 16;
ttx[2] += 30;
}
else
ttx[2] -= 12;
tr = gi.trace(ttx, NULL, NULL, tty, ent, CONTENTS_WINDOW | MASK_OPAQUE);
if ((tr.fraction == 1.0) && !tr.allsolid && !tr.startsolid)
return true;
if (ent->maxs[2] < 32)
return false;
if (tr.ent && (tr.ent->use == door_use))
{
if (!tr.ent->targetname)
return true;
}
if ((ent->s.origin[2] < other->s.origin[2]) || (ent->s.origin[2] - 24 > other->s.origin[2]))
return false;
ttx[2] -= 36;
tr = gi.trace(ttx, NULL, NULL, other->s.origin, ent, CONTENTS_WINDOW | MASK_OPAQUE);
return ((tr.fraction == 1.0) && !tr.allsolid && !tr.startsolid);
}
//==============================================
qboolean Bot_trace2(edict_t *ent, vec3_t ttz)
{
vec3_t ttx;
VectorCopy(ent->s.origin, ttx);
ttx[2] += (ent->maxs[2] >= 32) ? 24 : -12;
return (gi.trace(ttx, NULL, NULL, ttz ,ent, MASK_OPAQUE).fraction == 1.0);
}
//==================================================
qboolean TraceX(edict_t *ent, vec3_t p2)
{
trace_t tr;
vec3_t v1;
vec3_t v2;
int contents = (CONTENTS_SOLID | CONTENTS_WINDOW);
if (ent->svflags & ~SVF_MONSTER)
{
if (ent->client->waterstate)
{
VectorCopy(ent->mins, v1);
VectorCopy(ent->maxs, v2);
}
else
{
if (ent->client->ps.pmove.pm_flags & ~PMF_DUCKED)
{
VectorSet(v1, -16, -16, -4);
VectorSet(v2, 16, 16, 32);
}
else
{
VectorSet(v1, -4, -4, -4);
VectorSet(v2, 4, 4, 4);
}
}
}
else
{
VectorClear(v1);
VectorClear(v2);
contents |= (CONTENTS_LAVA | CONTENTS_SLIME);
}
tr = gi.trace(ent->s.origin, v1, v2, p2, ent, contents);
if ((tr.fraction == 1.0) && !tr.allsolid && !tr.startsolid)
return true;
if (ent->client->routetrace)
{
if (ent->svflags & SVF_MONSTER)
{
if (tr.ent && (tr.ent->use == door_use))
return (tr.ent->moveinfo.state == PSTATE_UP);
}
}
return false;
}
//============================================================
void Get_RouteOrigin(int index, vec3_t pos)
{
edict_t *e;
// when normal or items
if ((Route[index].state <= GRS_ITEMS) || (Route[index].state >= GRS_GRAPSHOT))
{
if (Route[index].state == GRS_ITEMS)
{
VectorCopy(Route[index].ent->s.origin, pos);
pos[2] += 8;
}
else
VectorCopy(Route[index].Pt, pos);
}
switch (Route[index].state)
{ // when plat
case GRS_ONPLAT:
VectorCopy(Route[index].ent->union_ent->s.origin, pos);
pos[2] += 8;
return;
// when train
case GRS_ONTRAIN:
if (!Route[index].ent->trainteam)
{
VectorCopy(Route[index].ent->union_ent->s.origin,pos);
pos[2] += 8;
return;
}
if (Route[index].ent->target_ent)
{
if (VectorCompare(Route[index].Tcorner, Route[index].ent->target_ent->s.origin))
{
VectorCopy(Route[index].ent->union_ent->s.origin, pos);
pos[2] += 8;
return;
}
}
e = Route[index].ent->trainteam;
while (1)
{
if (e == Route[index].ent)
break;
if (e->target_ent)
{
if (VectorCompare(Route[index].Tcorner, e->target_ent->s.origin))
{
VectorCopy(e->union_ent->s.origin, pos);
pos[2] += 8;
Route[index].ent = e;
return;
}
}
e = e->trainteam;
}
VectorCopy(Route[index].ent->union_ent->s.origin,pos);
pos[2] += 8;
return;
case GRS_ONDOOR:
if (Route[index].ent->union_ent)
{
VectorCopy(Route[index].ent->union_ent->s.origin, pos);
pos[2] += 8;
}
else if (index + 1 < TotalRouteNodes)
{
if (Route[index+1].state <= GRS_ITEMS)
{
VectorCopy(Route[index+1].Pt, pos);
pos[2] += 8;
}
else if (Route[index+1].state <= GRS_ONTRAIN)
{ //when plat or train
VectorCopy(Route[index+1].ent->union_ent->s.origin, pos);
pos[2] += 8;
}
else if (Route[index+1].state == GRS_PUSHBUTTON)
{
VectorCopy(Route[index+1].ent->union_ent->s.origin, pos);
pos[2] += 8;
}
else
VectorCopy(Route[index+1].Pt,pos);
}
else
{
pos[0] = (Route[index].ent->absmin[0] + Route[index].ent->absmax[0]) * 0.5;
pos[1] = (Route[index].ent->absmin[1] + Route[index].ent->absmax[1]) * 0.5;
pos[2] = Route[index].ent->absmax[2];
}
return;
case GRS_PUSHBUTTON:
VectorCopy(Route[index].ent->union_ent->s.origin, pos);
}
}
//==============================================
void GetAimAngle(edict_t *ent, float aim, float dist)
{
vec3_t targaim;
trace_t tr;
int weapon;
weapon = GetKindWeapon(ent->client->pers.weapon);
switch (weapon)
{
case WEAP_CHAINSAW:
case WEAP_DESERTEAGLE: //CW...
case WEAP_GAUSSPISTOL:
case WEAP_JACKHAMMER:
case WEAP_MAC10:
case WEAP_RAILGUN:
case WEAP_AGM:
if (ent->client->current_enemy != ent->client->prev_enemy)
{
if (ent->client->current_enemy->isabot)
VectorSubtract(ent->client->current_enemy->s.old_origin, ent->client->current_enemy->s.origin, targaim);
else
{
VectorCopy(ent->client->current_enemy->velocity, targaim);
VectorInverse(targaim);
}
VectorNormalize(targaim);
VectorMA(ent->client->current_enemy->s.origin, AIM_SFPOS * aim * myrandom, targaim, targaim);
}
else
{
VectorSubtract(ent->client->targ_old_origin, ent->client->current_enemy->s.origin, targaim);
VectorMA(ent->client->current_enemy->s.origin, aim * myrandom, targaim, targaim);
}
VectorSubtract(targaim, ent->s.origin, targaim);
AdjustAngle(ent, targaim, aim, AIM_SFANG_HITSCAN);
break;
case WEAP_C4: //CW...
case WEAP_SHOCKRIFLE:
case WEAP_ROCKETLAUNCHER:
case WEAP_DISCLAUNCHER:
case WEAP_ESG:
case WEAP_FLAMETHROWER:
if (ent->client->current_enemy != ent->client->prev_enemy)
{
if (ent->client->current_enemy->isabot)
VectorSubtract(ent->client->current_enemy->s.origin, ent->client->current_enemy->s.old_origin, targaim);
else
{
VectorCopy(ent->client->current_enemy->velocity, targaim);
VectorScale(targaim, 32.0, targaim);
}
VectorNormalize(targaim);
VectorMA(ent->client->current_enemy->s.origin, (11 - aim) * (dist / 25), targaim, targaim);
}
else
{
VectorSubtract(ent->client->current_enemy->s.origin, ent->client->targ_old_origin, targaim);
targaim[2] *= 0.5;
VectorMA(ent->client->current_enemy->s.origin, (-aim * myrandom) + (dist / 75), targaim, targaim);
}
tr = gi.trace(ent->client->current_enemy->s.origin, NULL, NULL, targaim, ent->client->current_enemy, MASK_SHOT);
VectorCopy(tr.endpos, targaim);
if (weapon == WEAP_ROCKETLAUNCHER) //CW
{
if (targaim[2] < (ent->s.origin[2] + JumpMax))
{
vec3_t vtmp;
targaim[2] -= 24;
VectorCopy(ent->s.origin, vtmp);
vtmp[2] += ent->viewheight - 8;
tr = gi.trace(vtmp, NULL, NULL, targaim, ent, MASK_SHOT);
if (tr.fraction != 1.0)
targaim[2] += 24;
}
else
{
if (targaim[2] > ent->s.origin[2] + JumpMax)
targaim[2] += 5;
}
}
VectorSubtract(targaim, ent->s.origin, targaim);
AdjustAngle(ent, targaim, aim, AIM_SFANG_PROJ);
break;
case WEAP_TRAP: //CW
VectorCopy(ent->client->vtemp, targaim);
VectorSubtract(targaim, ent->s.origin, targaim);
VectorSet(ent->s.angles, (Get_pitch(targaim)), (Get_yaw(targaim)), 0.0F);
break;
default:
break;
}
}
//=================================================
qboolean HasAmmoForWeapon(edict_t *self, gitem_t *weapon)
{
if (weapon == item_chainsaw) //CW
return true;
if ((int)dmflags->value & DF_INFINITE_AMMO)
return true;
return (self->client->pers.inventory[ITEM_INDEX(FindItem(weapon->ammo))] >= weapon->quantity);
}
//========================================================
gitem_t *GetWeaponType(int weapnum)
{
switch (weapnum)
{
case WEAP_CHAINSAW: return item_chainsaw; //CW...
case WEAP_DESERTEAGLE: return item_deserteagle;
case WEAP_GAUSSPISTOL: return item_gausspistol;
case WEAP_JACKHAMMER: return item_jackhammer;
case WEAP_MAC10: return item_mac10;
case WEAP_ESG: return item_esg;
case WEAP_C4: return item_c4;
case WEAP_TRAP: return item_trap;
case WEAP_ROCKETLAUNCHER: return item_rocketlauncher;
case WEAP_FLAMETHROWER: return item_flamethrower;
case WEAP_RAILGUN: return item_railgun;
case WEAP_SHOCKRIFLE: return item_shockrifle;
case WEAP_DISCLAUNCHER: return item_disclauncher;
case WEAP_AGM: return item_agm;
case WEAP_GRAPPLE: return item_grapple;
}
return item_chainsaw;
}
//==============================================
qboolean CanUseWeapon(edict_t *ent, int weapnum)
{
gitem_t *weapon;
//CW++
if ((weapnum == WEAP_FLAMETHROWER) && (ent->waterlevel > 1))
return false;
//CW--
weapon = GetWeaponType(weapnum);
if (ent->client->pers.inventory[ITEM_INDEX(weapon)])
{
if (HasAmmoForWeapon(ent, weapon))
{
if ((ent->client->weaponstate == WEAPON_READY) || (ent->client->weaponstate == WEAPON_FIRING)) //CW
{
if (ent->client->pers.weapon != weapon)
{
ent->client->newweapon = weapon;
ChangeWeapon(ent);
}
return true; // true whether switched or not
}
}
}
return false;
}
//CW++
void UsePrimaryWeapon(edict_t *ent)
{
int my_weapon = GetKindWeapon(ent->client->pers.weapon);
int primary_weapon = Bot[ent->client->pers.botindex].skill[PRIMARYWEAP];
if (my_weapon != primary_weapon)
CanUseWeapon(ent, primary_weapon);
}
//CW--
//==============================================
qboolean Pickup_Navi(edict_t *ent, edict_t *other)
{
int i;
if (!(ent->spawnflags & DROPPED_ITEM))
{
if (ent->item->quantity)
SetRespawn(ent, ent->item->quantity);
}
// on door (up & down)
if ((ent->item == item_navi3) && ent->union_ent)
{
qboolean flg = false;
int j;
int k;
if (ent->target_ent == other)
{
other->client->movestate &= ~STS_WAITS;
other->client->waiting_obj = ent->union_ent;
if (ent->union_ent->spawnflags & PDOOR_TOGGLE)
{
if ((ent->union_ent->moveinfo.state == PSTATE_DOWN) || (ent->union_ent->moveinfo.state == PSTATE_BOTTOM))
other->client->movestate |= STS_W_ONDOORDWN;
else
other->client->movestate |= STS_W_ONDOORUP;
}
else
{
if ((ent->union_ent->moveinfo.state == PSTATE_DOWN) || (ent->union_ent->moveinfo.state == PSTATE_TOP))
other->client->movestate |= STS_W_ONDOORDWN;
else if ((ent->union_ent->moveinfo.state == PSTATE_UP) || (ent->union_ent->moveinfo.state == PSTATE_BOTTOM))
other->client->movestate |= STS_W_ONDOORUP;
}
for (i =- MAX_DOORSEARCH; i < MAX_DOORSEARCH; i++)
{
if (i <= 0)
j = other->client->pers.routeindex - (MAX_DOORSEARCH - i);
else
j = other->client->pers.routeindex+i;
if (j < 0)
continue;
if (j >= TotalRouteNodes)
continue;
if (((Route[j].state == GRS_ONDOOR) && (Route[j].ent == ent->union_ent)) || (Route[j].state == GRS_PUSHBUTTON))
{
vec3_t v;
k = 1;
flg = false;
while (1)
{
if (j + k >= TotalRouteNodes) // overflow
break;
if (j + k >= other->client->pers.routeindex)
{
Get_RouteOrigin(j+k, v);
if (fabs(v[2] - other->s.origin[2]) > JumpMax)
{
flg = true;
break;
}
}
k++;
}
if ((j + k < TotalRouteNodes) && flg)
{
other->client->pers.routeindex = j + k; // set!
break;
}
}
}
if (!flg)
other->client->movestate |= STS_W_DONT; // failed
ent->target_ent = NULL;
}
// not target
SetRespawn(ent, 1000000);
ent->solid = SOLID_NOT;
}
else if (ent->item == item_navi2)
{
for (i = 0; i < 10; i++)
{
if ((other->client->pers.routeindex+i) >= TotalRouteNodes)
break;
if (!Route[other->client->pers.routeindex+i].index)
break;
if (Route[other->client->pers.routeindex+i].state != GRS_PUSHBUTTON)
continue;
if (Route[other->client->pers.routeindex+i].ent == ent->union_ent)
{
other->client->pers.routeindex += i + 1;
break;
}
}
}
return true;
}
//==============================================
qboolean B_UseWeapon(edict_t *ent, float aim, float distance, int my_weapon) //CW...
{ //(heavily modifed)
qboolean see_enemy = false;
edict_t *target = ent->client->current_enemy;
vec3_t vdir;
int enemy_weapon = 0;
int skill;
if (!CanUseWeapon(ent, my_weapon))
return false;
GetAimAngle(ent, aim, distance);
if (trace_priority < TRP_ANGLEKEEP)
trace_priority = TRP_ANGLEKEEP;
if (target->client)
enemy_weapon = GetKindWeapon(target->client->pers.weapon);
skill = Bot[ent->client->pers.botindex].skill[COMBATSKILL];
// Special weapon handling code: Rocket Launcher.
if (my_weapon == WEAP_ROCKETLAUNCHER)
{
see_enemy = InSight(ent, target);
if (SkillLevel[skill] & FIRE_PRESTAYFIRE)
{
if (((distance > 500) && (random() < 0.25)) || (fabs(ent->s.angles[PITCH]) > 45))
{
if ((enemy_weapon < WEAP_ROCKETLAUNCHER) && (ent->groundentity || ent->client->waterstate) && see_enemy)
{
ent->client->battlemode |= FIRE_PRESTAYFIRE;
ent->client->battlecount = 2 + (int)(6 * random());
trace_priority = TRP_ALLKEEP;
return true;
}
}
}
// Check for jumping-when-firing-rockets and explosion-avoidance skill use.
if ((SkillLevel[skill] & FIRE_JUMPROC) && (random() < 0.3) && ((target->s.origin[2] - ent->s.origin[2]) < JumpMax) && !(ent->client->ps.pmove.pm_flags && PMF_DUCKED))
{
if (ent->groundentity && (ent->waterlevel < 2))
{
if (ent->client->routetrace)
{
if (Bot_Fall(ent, ent->s.origin, 0))
{
trace_priority = TRP_ALLKEEP;
if (see_enemy)
ent->client->buttons |= BUTTON_ATTACK;
return true;
}
}
else
{
ent->moveinfo.speed = 0;
ent->velocity[2] = VEL_BOT_JUMP;
SetBotAnim(ent);
trace_priority = TRP_ALLKEEP;
if (see_enemy)
ent->client->buttons |= BUTTON_ATTACK;
return true;
}
}
}
else if ((SkillLevel[skill] & FIRE_AVOIDEXPLO) && see_enemy && (distance < 120) && (random() < 0.5))
{
if (ent->groundentity || ent->client->waterstate)
{
ent->client->battlemode |= FIRE_AVOIDEXPLO;
ent->client->battlecount = 4 + (int)(6 * random());
trace_priority = TRP_ALLKEEP;
return true;
}
}
if (see_enemy)
ent->client->buttons |= BUTTON_ATTACK;
return true;
}
// Special weapon handling code: Shock Rifle and Disc Launcher.
else if ((my_weapon == WEAP_SHOCKRIFLE) || (my_weapon == WEAP_DISCLAUNCHER))
{
see_enemy = InSight(ent, target);
if (see_enemy)
VectorCopy(target->s.origin, ent->client->vtemp);
if (SkillLevel[skill] & FIRE_STAYFIRE)
{
if (see_enemy)
{
ent->client->battlemode |= FIRE_STAYFIRE;
ent->client->battlecount = 8 + (int)(10 * random());
trace_priority = TRP_ALLKEEP;
return true;
}
}
// Check for explosion avoidance (SR).
else if (my_weapon == WEAP_SHOCKRIFLE)
{
if ((SkillLevel[skill] & FIRE_AVOIDEXPLO) && see_enemy && (distance < 150))
{
if (ent->groundentity || ent->client->waterstate)
{
ent->client->battlemode |= FIRE_AVOIDEXPLO;
ent->client->battlecount = 6 + (int)(6 * random());
trace_priority = TRP_ALLKEEP;
return true;
}
}
}
}
// Special weapon handling code: C4.
else if (my_weapon == WEAP_C4)
{
ent->client->battlemode |= FIRE_C4;
ent->client->battlecount = 4 + (int)(8 * random());
trace_priority = TRP_ALLKEEP;
if (ent->client->weaponstate == WEAPON_READY)
ent->client->buttons |= BUTTON_ATTACK;
return true;
}
// Special weapon handling code: Traps.
else if (my_weapon == WEAP_TRAP)
{
see_enemy = InSight(ent, target);
if (see_enemy)
VectorCopy(target->s.origin, ent->client->vtemp);
else
{
AngleVectors(ent->client->v_angle, vdir, NULL, NULL);
VectorMA(ent->s.origin, 500.0, vdir, ent->client->vtemp);
}
}
ent->client->buttons |= BUTTON_ATTACK;
return true;
}
//======================================================
//=========== BOT FIGHTING/COMBAT FUNCTIONS ============
//======================================================
//==============================================
void Combat_LevelX(edict_t *ent, float distance)
{
vec3_t vdir;
qboolean use_weapon = false; //CW++
if (ent->client->battlemode & FIRE_ESTIMATE)
{
float aim = 10.0 - Bot[ent->client->pers.botindex].skill[AIMACCURACY];
//CW++
//only use speculative fire for certain weapons
switch (GetKindWeapon(ent->client->pers.weapon))
{
case WEAP_DESERTEAGLE:
if ((distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_DESERTEAGLE))
use_weapon = true;
break;
case WEAP_JACKHAMMER:
if ((distance < 700) && B_UseWeapon(ent, aim, distance, WEAP_JACKHAMMER))
use_weapon = true;
break;
case WEAP_MAC10:
if ((distance < 400) && B_UseWeapon(ent, aim, distance, WEAP_MAC10))
use_weapon = true;
break;
case WEAP_ESG:
if ((distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_ESG))
use_weapon = true;
break;
case WEAP_C4:
if ((distance < 800) && B_UseWeapon(ent, aim, distance, WEAP_C4))
use_weapon = true;
break;
case WEAP_TRAP:
if ((distance < 800) && B_UseWeapon(ent, aim, distance, WEAP_TRAP))
use_weapon = true;
break;
case WEAP_ROCKETLAUNCHER:
if ((distance > 100) && (distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_ROCKETLAUNCHER))
use_weapon = true;
break;
case WEAP_FLAMETHROWER:
if ((distance > 100) && (distance < 1000) && B_UseWeapon(ent, aim, distance, WEAP_FLAMETHROWER))
{
use_weapon = true;
ent->client->ft_firebomb = true;
}
break;
case WEAP_SHOCKRIFLE:
if ((distance > 100) && (distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_SHOCKRIFLE))
{
use_weapon = true;
ent->client->homing_plasma = true;
}
break;
case WEAP_DISCLAUNCHER:
if ((distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_DISCLAUNCHER))
use_weapon = true;
break;
}
//CW--
VectorSubtract(ent->client->vtemp, ent->s.origin, vdir);
ent->s.angles[YAW] = Get_yaw(vdir);
ent->s.angles[PITCH] = Get_pitch(vdir);
trace_priority = (use_weapon) ? TRP_ALLKEEP : TRP_ANGLEKEEP; //CW
return;
}
VectorSubtract(ent->client->current_enemy->s.origin, ent->s.origin, vdir);
ent->s.angles[YAW] = Get_yaw(vdir);
ent->s.angles[PITCH] = Get_pitch(vdir);
trace_priority = TRP_ANGLEKEEP; // use this angle //CW
}
//==============================================
void Combat_Normal(edict_t *ent, float distance)
{
edict_t *target;
trace_t tr;
vec3_t v;
vec3_t vv;
float aim;
float f;
qboolean danger;
qboolean shift;
int i;
//CW++
vec3_t vdir;
qboolean quad_weapon = false;
int my_weapon;
int enemy_weapon = 0;
int skill;
//CW--
// Set-up useful variables.
aim = 10.0 - Bot[ent->client->pers.botindex].skill[AIMACCURACY];
target = ent->client->current_enemy;
my_weapon = GetKindWeapon(ent->client->pers.weapon); //CW
//CW++
if (target->client)
enemy_weapon = GetKindWeapon(target->client->pers.weapon);
skill = Bot[ent->client->pers.botindex].skill[COMBATSKILL];
//CW--
// Modify aiming for chicken-shooting.
if (ent->client->battlemode == FIRE_CHICKEN)
aim *= BOT_CHICKENAIM_FACTOR; //CW
// Battlemode handling: strafing movement.
if (ent->client->battlemode & FIRE_SHIFT) //CW...
{
GetAimAngle(ent, aim, distance);
if (--ent->client->battlesubcnt > 0)
{
if (ent->groundentity)
{
if (ent->client->battlemode & FIRE_SHIFT_R)
{
ent->client->moveyaw = ent->s.angles[YAW] + 90;
if (ent->client->moveyaw > 180)
ent->client->moveyaw -= 360;
}
else
{
ent->client->moveyaw = ent->s.angles[YAW] - 90;
if (ent->client->moveyaw < -180)
ent->client->moveyaw += 360;
}
trace_priority = TRP_MOVEKEEP;
}
}
else
ent->client->battlemode &= ~FIRE_SHIFT;
}
// Try to dodge (duck or jump) if we have that combat skill.
if (SkillLevel[skill] & FIRE_DODGE) //CW
{
if (ent->groundentity && !ent->waterlevel && target->client)
{
AngleVectors(target->client->v_angle, v, NULL, NULL);
VectorScale(v, BOT_DODGE_ENEMYRANGE, v); //CW
VectorSet(vv, 0, 0, target->viewheight - 8);
VectorAdd(target->s.origin, vv, vv);
VectorAdd(vv, v, v);
tr = gi.trace(vv, VEC_TMINS4, VEC_TMAXS4, v, target, MASK_SHOT); //CW
if (tr.ent == ent)
{
if (tr.endpos[2] > ent->s.origin[2] + 4)
{
if (random() < 0.4)
{
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
ent->client->battleduckcnt = 2 + (8 * random());
}
}
else if (tr.endpos[2] < ent->s.origin[2] + JumpMax - 24)
{
if (ent->client->routetrace)
{
if (Bot_Fall(ent, ent->s.origin, 0))
trace_priority = TRP_MOVEKEEP;
}
else
{
ent->moveinfo.speed = 0.5;
ent->velocity[2] = VEL_BOT_JUMP;
ent->client->anim_priority = ANIM_JUMP;
//CW++
gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
//CW--
}
}
}
}
}
// Battlemode handling: ignore current enemy.
if (ent->client->battlemode & FIRE_IGNORE) //CW...
{
if (--ent->client->battlecount > 0)
{
if (ent->client->current_enemy == ent->client->prev_enemy)
return;
}
ent->client->battlemode = FIRE_NULL;
}
// Battlemode handling: pre hold-and-fire state.
if (ent->client->battlemode & FIRE_PRESTAYFIRE) //CW...
{
if (--ent->client->battlecount > 0)
{
GetAimAngle(ent, aim, distance);
if (ent->groundentity)
{
if (target->client && (target->client->weaponstate == WEAPON_FIRING))
{
if ((enemy_weapon != WEAP_DESERTEAGLE) && (enemy_weapon != WEAP_RAILGUN) && (enemy_weapon != WEAP_CHAINSAW))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
}
trace_priority = TRP_ALLKEEP;
return;
}
if (!(ent->client->battlemode & FIRE_SHIFT))
ent->client->battlemode = FIRE_STAYFIRE;
ent->client->battlecount = 5 + (int)(20 * random());
}
// Battlemode handling: hold-and-fire state.
if (ent->client->battlemode & FIRE_STAYFIRE) //CW...
{
if (--ent->client->battlecount > 0)
{
aim *= 0.95;
GetAimAngle(ent, aim, distance);
if (ent->groundentity && target->client && (target->client->weaponstate == WEAPON_FIRING))
{
if ((enemy_weapon != WEAP_DESERTEAGLE) && (enemy_weapon != WEAP_RAILGUN) && (enemy_weapon != WEAP_CHAINSAW))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
if (!(ent->client->battlemode & FIRE_SHIFT))
trace_priority = TRP_ALLKEEP;
my_weapon = GetKindWeapon(ent->client->pers.weapon);
if (my_weapon == WEAP_SHOCKRIFLE)
ent->client->homing_plasma = (distance > 600) ? true : false;
ent->client->buttons |= BUTTON_ATTACK;
return;
}
ent->client->battlemode = FIRE_NULL;
}
// Battlemode handling: charge the enemy (we'll be Quadded).
if (ent->client->battlemode & FIRE_RUSH) //CW...
{
if (--ent->client->battlecount > 0)
{
aim *= 0.95;
GetAimAngle(ent, aim, distance);
my_weapon = GetKindWeapon(ent->client->pers.weapon);
if (ent->groundentity && target->client && (target->client->weaponstate == WEAPON_FIRING))
{
if ((enemy_weapon != WEAP_DESERTEAGLE) && (enemy_weapon != WEAP_RAILGUN) && (enemy_weapon != WEAP_CHAINSAW))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
trace_priority = TRP_MOVEKEEP;
ent->client->moveyaw = ent->s.angles[YAW];
if ((my_weapon == WEAP_DISCLAUNCHER) || InSight(ent, target))
ent->client->buttons |= BUTTON_ATTACK;
return;
}
ent->client->battlemode = FIRE_NULL;
}
// Battlemode handling: avoid our own weapon explosions.
if (ent->client->battlemode & FIRE_AVOIDEXPLO) //CW...
{
if (--ent->client->battlecount > 0)
{
GetAimAngle(ent, aim, distance);
my_weapon = GetKindWeapon(ent->client->pers.weapon);
if (ent->groundentity && (target->client->weaponstate == WEAPON_FIRING))
{
if ((enemy_weapon != WEAP_DESERTEAGLE) && (enemy_weapon != WEAP_RAILGUN) && (enemy_weapon != WEAP_CHAINSAW))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
trace_priority = TRP_MOVEKEEP;
ent->client->moveyaw = ent->s.angles[YAW] + 180;
if (ent->client->moveyaw > 180)
ent->client->moveyaw -= 360;
if (InSight(ent, target))
ent->client->buttons |= BUTTON_ATTACK;
return;
}
ent->client->battlemode = FIRE_NULL;
}
//CW++
// Battlemode handling: throwing C4.
if (ent->client->battlemode & FIRE_C4)
{
if (--ent->client->battlecount > 0)
{
GetAimAngle(ent, aim, distance);
if (ent->groundentity && target->client && (target->client->weaponstate == WEAPON_FIRING))
{
if ((enemy_weapon != WEAP_DESERTEAGLE) && (enemy_weapon != WEAP_RAILGUN) && (enemy_weapon != WEAP_CHAINSAW))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
trace_priority = TRP_ANGLEKEEP;
ent->client->buttons |= BUTTON_ATTACK;
return;
}
ent->client->buttons &= ~BUTTON_ATTACK;
ent->client->battlemode = FIRE_NULL;
}
//CW--
// Battlemode handling: back-tracking with defensive fire.
if (ent->client->battlemode & FIRE_REFUGE) //CW...
{
if (--ent->client->battlecount > 0)
{
aim *= 0.95;
GetAimAngle(ent, aim, distance);
my_weapon = GetKindWeapon(ent->client->pers.weapon);
if (ent->groundentity && target->client && (target->client->weaponstate == WEAPON_FIRING))
{
if ((enemy_weapon != WEAP_DESERTEAGLE) && (enemy_weapon != WEAP_RAILGUN) && (enemy_weapon != WEAP_CHAINSAW))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
trace_priority = TRP_ANGLEKEEP;
if ((my_weapon == WEAP_DISCLAUNCHER) || InSight(ent, target))
ent->client->buttons |= BUTTON_ATTACK;
return;
}
ent->client->battlemode = FIRE_NULL;
ent->client->pers.routeindex -= 2;
}
// We can see the enemy far below us, and we're on a moving door/plat/train, so fire splash-damage weapons.
if (!(ent->client->combatstate & CTS_ENEM_NSEE) && (ent->client->movestate & STS_WAITSMASK2)) //CW...
{
if (target->s.origin[2] - ent->s.origin[2] < -300) //CW
{
//CW++
//SR
if (CanUseWeapon(ent, WEAP_SHOCKRIFLE))
{
GetAimAngle(ent, aim, distance);
ent->client->homing_plasma = false;
trace_priority = TRP_ANGLEKEEP;
ent->client->buttons |= BUTTON_ATTACK;
return;
}
//RL
if (CanUseWeapon(ent, WEAP_ROCKETLAUNCHER))
{
GetAimAngle(ent, aim, distance);
trace_priority = TRP_ANGLEKEEP;
ent->client->buttons |= BUTTON_ATTACK;
return;
}
//FT
if (CanUseWeapon(ent, WEAP_FLAMETHROWER))
{
GetAimAngle(ent, aim, distance);
ent->client->ft_firebomb = true;
trace_priority = TRP_ANGLEKEEP;
ent->client->buttons |= BUTTON_ATTACK;
return;
}
//C4
if (CanUseWeapon(ent, WEAP_C4))
{
GetAimAngle(ent, aim, distance);
ent->client->battlemode |= FIRE_C4;
ent->client->battlecount = 4 + (int)(8 * random());
trace_priority = TRP_ANGLEKEEP;
if (ent->client->weaponstate == WEAPON_READY)
ent->client->buttons |= BUTTON_ATTACK;
return;
}
//CW--
}
}
// Initiate further dodging (left/right strafing) if we have that combat skill.
if (!(ent->client->battlemode & FIRE_SHIFT) && (SkillLevel[skill] & FIRE_DODGE)) //CW...
{
shift = true;
if (ent->client->routetrace && (enemy_weapon != WEAP_RAILGUN))
{
for (i = ent->client->pers.routeindex; i < (ent->client->pers.routeindex + 10); i++)
{
if (i >= TotalRouteNodes)
break;
if (Route[i].state == GRS_ITEMS)
{
if (Route[i].ent->solid == SOLID_TRIGGER)
{
shift = false;
break;
}
}
}
}
if (shift)
{
GetAimAngle(ent, aim, distance);
f = target->s.angles[YAW] - ent->s.angles[YAW];
if (f > 180)
f = -(360 - f);
else if (f < -180)
f = -(f + 360);
if (f <= -160)
{
ent->client->battlemode |= FIRE_SHIFT_L;
ent->client->battlesubcnt = 5 + (int)(15 * random());
}
else if (f >= 160)
{
ent->client->battlemode |= FIRE_SHIFT_R;
ent->client->battlesubcnt = 5 + (int)(15 * random());
}
}
}
//================================================
// Avoid an invincible enemy if we have that combat skill.
if (target->client && (target->client->invincible_framenum > level.framenum)) //CW...
{
if (SkillLevel[skill] & FIRE_AVOIDINVULN)
{
GetAimAngle(ent, aim, distance);
trace_priority = TRP_MOVEKEEP;
ent->client->moveyaw = ent->s.angles[YAW] + 180; // turn around!
if (ent->client->moveyaw > 180)
ent->client->moveyaw -= 360;
return;
}
}
//================================================
// Choose a good weapon to use with the Quad if we have that combat skill.
if ((ent->client->quad_framenum > level.framenum) && (SkillLevel[skill] & FIRE_QUADUSE)) //CW...
{
//CW++
if (CanUseWeapon(ent, WEAP_AGM))
{
quad_weapon = true;
ent->client->agm_disrupt = true;
my_weapon = WEAP_AGM;
}
else if (CanUseWeapon(ent, WEAP_MAC10))
{
quad_weapon = true;
my_weapon = WEAP_MAC10;
}
else if (CanUseWeapon(ent, WEAP_JACKHAMMER))
{
quad_weapon = true;
my_weapon = WEAP_JACKHAMMER;
}
else if (CanUseWeapon(ent, WEAP_SHOCKRIFLE))
{
quad_weapon = true;
ent->client->homing_plasma = (distance < BOT_RUSH_ENEMYRANGE) ? false : true;
my_weapon = WEAP_SHOCKRIFLE;
}
else if (CanUseWeapon(ent, WEAP_FLAMETHROWER))
{
quad_weapon = true;
ent->client->ft_firebomb = (distance < BOT_RUSH_ENEMYRANGE) ? false : true;
my_weapon = WEAP_FLAMETHROWER;
}
else if (CanUseWeapon(ent, WEAP_DESERTEAGLE))
{
quad_weapon = true;
my_weapon = WEAP_DESERTEAGLE;
}
//CW--
if (quad_weapon)
{
GetAimAngle(ent, aim, distance);
ent->client->buttons |= BUTTON_ATTACK;
trace_priority = TRP_ANGLEKEEP;
// If close to the enemy and they're carrying a weaker weapon, rush them!
if ((distance < BOT_RUSH_ENEMYRANGE) && ((enemy_weapon == WEAP_CHAINSAW) || (enemy_weapon == WEAP_C4) || (enemy_weapon == WEAP_TRAP)))
{
//CW++
// Don't rush in if we know to avoid our own weapon explosions.
if (SkillLevel[skill] & FIRE_AVOIDEXPLO)
{
if ((my_weapon == WEAP_ROCKETLAUNCHER) || (my_weapon == WEAP_SHOCKRIFLE))
return;
}
//CW--
ent->client->battlemode |= FIRE_RUSH;
ent->client->battlecount = 10 + (int)(10 * random());
}
return;
}
}
//================================================
// Back-track with defensive fire if we have that skill.
if (SkillLevel[skill] & FIRE_REFUGE) //CW...
{
if ((ent->client->battlemode == FIRE_NULL) && ent->client->routetrace && (ent->client->pers.routeindex > 1))
{
danger = ((enemy_weapon != WEAP_CHAINSAW) && (enemy_weapon != WEAP_C4) && (enemy_weapon != WEAP_TRAP) && (enemy_weapon != WEAP_GRAPPLE) && (enemy_weapon != WEAP_DESERTEAGLE)) ? 1 : 0;
Get_RouteOrigin(ent->client->pers.routeindex - 2, v);
if ((fabs(v[2] - ent->s.origin[2]) < JumpMax) && danger)
{
my_weapon = GetKindWeapon(ent->client->pers.weapon);
if ((my_weapon == WEAP_ROCKETLAUNCHER) || (my_weapon == WEAP_SHOCKRIFLE))
{
ent->client->battlemode |= FIRE_REFUGE;
ent->client->battlecount = 10 + (int)(10 * random());
trace_priority = TRP_ALLKEEP;
return;
}
}
}
}
if (!ent->client->routetrace && (distance < 100))
{
ent->client->battlecount = 4 + (int)(8 * random());
trace_priority = TRP_ALLKEEP;
}
//CW++
// If we're being held by a trap, the sensible weapon priority list for destroying it is different.
if (ent->client->current_enemy && ent->client->current_enemy->die && (ent->client->current_enemy->die == Trap_DieFromDamage))
{
if (B_UseWeapon(ent, aim, distance, WEAP_MAC10))
goto FIRED;
if (B_UseWeapon(ent, aim, distance, WEAP_JACKHAMMER))
goto FIRED;
if ((ent->waterlevel < 2) && B_UseWeapon(ent, aim, distance, WEAP_FLAMETHROWER))
{
ent->client->ft_firebomb = false;
goto FIRED;
}
if (B_UseWeapon(ent, aim, distance, WEAP_DESERTEAGLE))
goto FIRED;
if (B_UseWeapon(ent, aim, distance, WEAP_CHAINSAW))
goto FIRED;
}
// Try to select our primary weapon first.
switch (Bot[ent->client->pers.botindex].skill[PRIMARYWEAP])
{
case WEAP_CHAINSAW:
if ((distance < 300) && B_UseWeapon(ent, aim, distance, WEAP_CHAINSAW))
goto FIRED;
break;
case WEAP_DESERTEAGLE:
if ((distance < 1000) && B_UseWeapon(ent, aim, distance, WEAP_DESERTEAGLE))
goto FIRED;
break;
case WEAP_GAUSSPISTOL:
if ((distance < 1500) && B_UseWeapon(ent, aim, distance, WEAP_GAUSSPISTOL))
goto FIRED;
break;
case WEAP_JACKHAMMER:
if ((distance < 700) && B_UseWeapon(ent, aim, distance, WEAP_JACKHAMMER))
goto FIRED;
break;
case WEAP_MAC10:
if ((distance < 500) && B_UseWeapon(ent, aim, distance, WEAP_MAC10))
goto FIRED;
break;
case WEAP_ESG:
if ((distance < 1000) && B_UseWeapon(ent, aim, distance, WEAP_ESG))
goto FIRED;
break;
case WEAP_C4:
if ((distance < 600) && B_UseWeapon(ent, aim, distance, WEAP_C4))
goto FIRED;
break;
case WEAP_ROCKETLAUNCHER:
if ((distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_ROCKETLAUNCHER))
{
ent->client->normal_rockets = true;
goto FIRED;
}
break;
case WEAP_FLAMETHROWER:
if ((ent->waterlevel < 2) && (distance < 1000) && B_UseWeapon(ent, aim, distance, WEAP_FLAMETHROWER))
{
ent->client->ft_firebomb = (distance > 500) ? true : false;
goto FIRED;
}
break;
case WEAP_RAILGUN:
if ((distance < 1500) && B_UseWeapon(ent, aim, distance, WEAP_RAILGUN))
goto FIRED;
break;
case WEAP_SHOCKRIFLE:
if ((distance < 1600) && B_UseWeapon(ent, aim, distance, WEAP_SHOCKRIFLE))
{
ent->client->homing_plasma = (distance > 700) ? true : false;
goto FIRED;
}
break;
case WEAP_DISCLAUNCHER:
if ((distance < 1000) && B_UseWeapon(ent, aim, distance, WEAP_DISCLAUNCHER))
goto FIRED;
break;
case WEAP_AGM:
if ((distance < 1200) && B_UseWeapon(ent, aim, distance, WEAP_AGM))
{
ent->client->agm_disrupt = true;
goto FIRED;
}
break;
}
// If we don't have our primary weapon, select the best one from our inventory.
//Mac-10
if (distance < 500)
{
if (B_UseWeapon(ent, aim, distance, WEAP_MAC10))
goto FIRED;
}
//Shock Rifle
if (distance < 1600)
{
if (B_UseWeapon(ent, aim, distance, WEAP_SHOCKRIFLE))
{
ent->client->homing_plasma = (distance > 700) ? true : false;
goto FIRED;
}
}
//Jackhammer
if (distance < 700)
{
if (B_UseWeapon(ent, aim, distance, WEAP_JACKHAMMER))
goto FIRED;
}
//Flamethrower - normal
if ((distance < 500) && (ent->waterlevel < 2))
{
if (B_UseWeapon(ent, aim, distance, WEAP_FLAMETHROWER))
{
ent->client->ft_firebomb = false;
goto FIRED;
}
}
//Gauss Pistol
if (distance < 1500)
{
if (B_UseWeapon(ent, aim, distance, WEAP_GAUSSPISTOL))
goto FIRED;
}
//Railgun
if (distance < 1500)
{
if (B_UseWeapon(ent, aim, distance, WEAP_RAILGUN))
goto FIRED;
}
// AGM - Cellular Disruptor
if (distance < 1200)
{
if (B_UseWeapon(ent, aim, distance, WEAP_AGM))
{
ent->client->agm_disrupt = true;
goto FIRED;
}
}
//Rocket Launcher
if (distance < 1200)
{
if (B_UseWeapon(ent, aim, distance, WEAP_ROCKETLAUNCHER))
{
ent->client->normal_rockets = true;
goto FIRED;
}
}
//E.S.G.
if (distance < 1000)
{
if (B_UseWeapon(ent, aim, distance, WEAP_ESG))
goto FIRED;
}
//Disc Launcher
if (distance < 1000)
{
if (B_UseWeapon(ent, aim, distance, WEAP_DISCLAUNCHER))
goto FIRED;
}
//Flamethrower - firebomb
if ((distance < 1000) && (ent->waterlevel < 2))
{
if (B_UseWeapon(ent, aim, distance, WEAP_FLAMETHROWER))
{
ent->client->ft_firebomb = true;
goto FIRED;
}
}
//C4
if (distance < 600)
{
if (B_UseWeapon(ent, aim, distance, WEAP_C4))
goto FIRED;
}
//Desert Eagle
if (distance < 1000)
{
if (B_UseWeapon(ent, aim, distance, WEAP_DESERTEAGLE))
goto FIRED;
}
// Should be firing?
if (ent->groundentity && (distance > 400) && (SkillLevel[skill] & FIRE_IGNORE)) //CW
{
if (!(ent->client->movestate & STS_WAITSMASK))
{
ent->client->battlemode = FIRE_IGNORE;
ent->client->battlecount = 5 + (int)(10 * random());
}
}
//Traps
if (distance > 150)
{
if (B_UseWeapon(ent, aim, distance, WEAP_TRAP))
goto FIRED;
}
//Chainsaw
if (distance < 300)
{
if (B_UseWeapon(ent, aim, distance, WEAP_CHAINSAW))
goto FIRED;
}
VectorSubtract(ent->client->vtemp, ent->s.origin, vdir);
ent->s.angles[YAW] = Get_yaw(vdir);
ent->s.angles[PITCH] = Get_pitch(vdir);
trace_priority = TRP_ANGLEKEEP;
//CW--
return;
FIRED: // shoot the weapon
//CW++
my_weapon = GetKindWeapon(ent->client->pers.weapon);
// If we're using the AGM and it's charging, release the fire button.
if ((my_weapon == WEAP_AGM) && ent->client->agm_tripped)
ent->client->buttons &= ~BUTTON_ATTACK;
//CW--
if (ent->client->battlemode == FIRE_CHICKEN)
{
if ((--ent->client->battlesubcnt > 0) && ent->groundentity && (ent->waterlevel < 2))
{
f = target->s.angles[YAW] - ent->s.angles[YAW];
if (f > 180)
f = -(360 - f);
if (f < -180)
f = -(f + 360);
if (fabs(f) >= 150)
ent->client->battlemode = FIRE_NULL;
else
{
trace_priority = TRP_ALLKEEP;
if ((ent->client->weaponstate != WEAPON_READY) && (target->s.origin[2] < ent->s.origin[2]))
{
if ((my_weapon == WEAP_ROCKETLAUNCHER) || (my_weapon == WEAP_RAILGUN) || (my_weapon == WEAP_ESG))
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
else if (skill >= 7) //CW
{
if ((my_weapon == WEAP_GAUSSPISTOL) || (my_weapon == WEAP_DESERTEAGLE)) //CW
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
}
}
}
return;
}
else
ent->client->battlemode = FIRE_NULL;
}
else if ((ent->client->battlemode == FIRE_NULL) && (distance > 200) && ent->groundentity && (ent->waterlevel < 2))
{
if (9 * random() > Bot[ent->client->pers.botindex].skill[AGGRESSION])
{
if ((my_weapon > WEAP_CHAINSAW) && (my_weapon != WEAP_GRAPPLE) && target->client && (target->client->current_enemy != ent)) //CW
{
f = target->s.angles[YAW] - ent->s.angles[YAW];
if (f > 180)
f = -(360 - f);
if (f < -180)
f = -(f + 360);
if (fabs(f) < 150)
{
ent->client->battlemode = FIRE_CHICKEN;
ent->client->battlesubcnt = 5 + (int)(random() * 8);
trace_priority = TRP_ALLKEEP;
}
}
}
}
}
//============================================================
void Set_Combatstate(edict_t *ent)
{
edict_t *enemy; //CW++
vec3_t vtmp;
float distance;
if (ent->client->movestate & STS_LADDERUP)
return;
//CW++
enemy = ent->client->current_enemy;
if (!(enemy && enemy->die && (enemy->die == Trap_DieFromDamage)))
{
if (!enemy)
{
ent->client->combatstate &= ~CTS_COMBS; //clear status
return;
}
//target is dead
if (!enemy->inuse || enemy->deadflag || (enemy->solid != SOLID_BBOX))
{
ent->client->battleduckcnt = 0;
ent->client->current_enemy = NULL;
ent->client->combatstate &= ~CTS_COMBS; // clear status
if ((9 * random()) < Bot[ent->client->pers.botindex].skill[COMBATSKILL])
UsePrimaryWeapon(ent);
return;
}
}
if (!enemy)
{
ent->client->combatstate &= ~CTS_COMBS; //clear status
return;
}
//CW--
if (!Bot_trace(ent, ent->client->current_enemy))
{
if (ent->client->targetlock <= level.time)
{
ent->client->current_enemy = NULL;
return;
}
ent->client->combatstate |= CTS_ENEM_NSEE; //can't see enemy
}
else
{
ent->client->combatstate &= ~CTS_ENEM_NSEE; //can see enemy
ent->client->targetlock = level.time + 1.2;
ent->client->battlemode &= ~FIRE_ESTIMATE;
}
VectorSubtract(ent->client->current_enemy->s.origin, ent->s.origin, vtmp);
distance = VectorLength(vtmp);
if (!(ent->client->combatstate & CTS_ENEM_NSEE))
Combat_Normal(ent, distance);
else if (ent->client->battlemode & FIRE_REFUGE) //CW: was combatstate
Combat_Normal(ent, distance);
else
Combat_LevelX(ent, distance);
if (ent->client->current_enemy)
{
ent->client->prev_enemy = ent->client->current_enemy;
VectorCopy(ent->client->current_enemy->s.origin, ent->client->targ_old_origin);
}
}
//====================================================
//============ BOT ENEMY SEARCHING ROUTINES ==========
//====================================================
//====================================================
void Bot_SearchEnemy(edict_t *ent)
{
qboolean tmpflg = false;
edict_t *target = NULL;
edict_t *trent;
vec3_t vdir;
vec3_t end; //CW++
int i;
int j;
if (ent->client->current_enemy)
{
if (Bot_trace(ent, ent->client->current_enemy))
tmpflg = true;
}
j = (random() < 0.5) ? 0 : -1;
for (i = 1; (i <= maxclients->value) && (target == NULL); i++) //CW
{
if (j)
trent = &g_edicts[i];
else
trent = &g_edicts[(int)(maxclients->value)-i+1];
if (!trent->inuse || (ent == trent) || trent->deadflag)
continue;
if (ent->client->current_enemy == trent)
continue;
if (ent->client->current_enemy && (ent->client->current_enemy->health < 1))
continue; // raven - added deadflag check
//CW++
if (CheckTeamDamage(trent, ent)) // don't target team members in TDM
continue;
if (trent->flags & FL_NOTARGET)
continue;
//CW--
if (trent->movetype != MOVETYPE_NOCLIP)
{
if (InSight(ent, trent))
{
VectorSubtract(trent->s.origin, ent->s.origin, vdir);
if (!tmpflg && (target == NULL))
{
float vr = (float)Bot[ent->client->pers.botindex].skill[VRANGEVIEW];
float hr = (float)Bot[ent->client->pers.botindex].skill[HRANGEVIEW];
float pitch = fabs(Get_pitch(vdir) - ent->s.angles[PITCH]);
if (pitch > 180)
pitch = 360 - pitch;
if (pitch <= vr)
{
float yaw = Get_yaw(vdir);
yaw = fabs(yaw - ent->s.angles[YAW]);
if (yaw > 180)
yaw = 360 - yaw;
if ((yaw <= hr) || (ent->client->movestate & STS_WAITS))
target = trent;
}
}
if (!tmpflg && (target == NULL) && trent->mynoise && trent->mynoise2)
{
if (trent->mynoise->teleport_time >= level.time - FRAMETIME)
{
VectorSubtract(trent->mynoise->s.origin, ent->s.origin, vdir);
if (VectorLength(vdir) < BOT_PNOISE_SELF_DIST) //CW
target = trent;
}
if ((target == NULL) && (trent->mynoise2->teleport_time >= level.time - FRAMETIME))
{
VectorSubtract(trent->mynoise2->s.origin, ent->s.origin, vdir); //CW
if (VectorLength(vdir) < BOT_PNOISE_IMPACT_DIST) //CW
target = trent;
}
}
}
else if (!tmpflg && trent->mynoise && trent->mynoise2) //CW...
{
if ((target == NULL) && (trent->mynoise->teleport_time >= level.time - FRAMETIME))
{
trace_t tr;
AngleVectors(trent->client->v_angle, vdir, NULL, NULL);
VectorScale(vdir, BOT_PNOISE_RADIUS, vdir);
VectorAdd(trent->s.origin, vdir, end);
tr = gi.trace(trent->s.origin, NULL, NULL, end, trent, MASK_SHOT);
VectorSubtract(ent->s.origin, tr.endpos, vdir);
if (VectorLength(vdir) < BOT_PNOISE_SELF_DIST)
{
VectorCopy(tr.endpos, end);
tr = gi.trace(ent->s.origin, NULL, NULL, end, ent, MASK_SHOT);
if (tr.fraction == 1.0)
{
target = trent;
ent->client->battlemode |= FIRE_ESTIMATE;
VectorCopy(end, ent->client->vtemp);
}
}
}
//CW++
if ((target == NULL) && (trent->mynoise2->teleport_time >= level.time - FRAMETIME))
{
VectorSubtract(trent->mynoise2->s.origin, ent->s.origin, vdir);
if (VectorLength(vdir) < BOT_PNOISE_IMPACT_DIST)
{
target = trent;
ent->client->battlemode |= FIRE_ESTIMATE;
VectorCopy(trent->mynoise2->s.origin, ent->client->vtemp);
}
}
//CW--
}
}
}
if (target && !tmpflg)
{
ent->client->current_enemy = target;
ent->client->targetlock = level.time + 2.0; //CW++
}
else if (target && ent->client->current_enemy)
{
if (target->client && GetKindWeapon(target->client->pers.weapon) > GetKindWeapon(ent->client->current_enemy->client->pers.weapon)) //CW
{
ent->client->current_enemy = target;
ent->client->targetlock = level.time + 2.0; //CW++
}
}
}
//==============================================
//============ ITEM HANDLING ROUTINES ==========
//==============================================
//==============================================
void InitAllItems(void)
{
// set IT_ARMOR lookups
item_jacketarmor = FindItem("Jacket Armor");
item_combatarmor = FindItem("Combat Armor");
item_bodyarmor = FindItem("Body Armor");
item_armorshard = FindItem("Armor Shard");
item_powerscreen = FindItem("Power Screen");
item_powershield = FindItem("Power Shield");
// set IT_AMMO lookups
item_shells = FindItem("shells");
item_cells = FindItem("cells");
item_bullets = FindItem("bullets");
item_rockets = FindItem("rockets");
item_slugs = FindItem("slugs");
//CW++
// set IT_WEAPON lookups
item_chainsaw = FindItem("chainsaw");
item_deserteagle = FindItem("desert eagle");
item_gausspistol = FindItem("gauss pistol");
item_jackhammer = FindItem("jackhammer");
item_c4 = FindItem("c4");
item_mac10 = FindItem("mac-10");
item_esg = FindItem("e.s.g.");
item_trap = FindItem("traps");
item_rocketlauncher = FindItem("rocket launcher");
item_railgun = FindItem("railgun");
item_flamethrower = FindItem("flamethrower");
item_shockrifle = FindItem("shock rifle");
item_disclauncher = FindItem("disc launcher");
item_agm = FindItem("ag manipulator");
item_grapple = FindItem("grapple");
//CW--
// set IT_HEALTH lookups
item_adrenaline = FindItem("Adrenaline");
item_health = FindItem("Health");
item_stimpak = FindItem("Health");
item_health_large = FindItem("Health");
item_health_mega = FindItem("Health");
// set IT_POWERUP lookups
item_quad = FindItem("Quad Damage");
item_invulnerability = FindItem("Invulnerability");
item_silencer = FindItem("Silencer");
item_breather = FindItem("Rebreather");
item_enviro = FindItem("Environment Suit");
//CW++
item_teleporter = FindItem("Teleporter");
//CW--
// set IT_PACK lookups
item_pack = FindItem("Ammo Pack");
item_bandolier = FindItem("Bandolier");
// set IT_NODE lookups
item_navi1 = FindItem("Roam Navi1");
item_navi2 = FindItem("Roam Navi2");
item_navi3 = FindItem("Roam Navi3");
}
//==============================================
int GetKindWeapon(gitem_t *it)
{
//CW++
if (!it)
return 0;
//CW--
if (it->weaponthink == Weapon_DesertEagle) return WEAP_DESERTEAGLE; //CW...
if (it->weaponthink == Weapon_GaussPistol) return WEAP_GAUSSPISTOL;
if (it->weaponthink == Weapon_Jackhammer) return WEAP_JACKHAMMER;
if (it->weaponthink == Weapon_Mac10) return WEAP_MAC10;
if (it->weaponthink == Weapon_ESG) return WEAP_ESG;
if (it->weaponthink == Weapon_C4) return WEAP_C4;
if (it->weaponthink == Weapon_Trap) return WEAP_TRAP;
if (it->weaponthink == Weapon_RocketLauncher) return WEAP_ROCKETLAUNCHER;
if (it->weaponthink == Weapon_Flamethrower) return WEAP_FLAMETHROWER;
if (it->weaponthink == Weapon_Railgun) return WEAP_RAILGUN;
if (it->weaponthink == Weapon_ShockRifle) return WEAP_SHOCKRIFLE;
if (it->weaponthink == Weapon_DiscLauncher) return WEAP_DISCLAUNCHER;
if (it->weaponthink == Weapon_AGM) return WEAP_AGM;
if (it->weaponthink == CTFWeapon_Grapple) return WEAP_GRAPPLE;
return WEAP_CHAINSAW;
}
//====================================================
//============ BOT MOVEMENT TESTING ROUTINES =========
//====================================================
//============================================================
int Bot_TestMove(edict_t *ent, float ryaw, vec3_t pos, float dist, float *bottom)
{
vec3_t trstart;
vec3_t trend;
vec3_t trmin;
vec3_t trmax;
vec3_t v;
vec3_t vv;
trace_t tr;
float tracelimit;
float yaw;
int contents;
if (ent->waterlevel >= 1)
tracelimit = 75;
else
{
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
tracelimit = 26;
else
tracelimit = JumpMax + 5;
}
VectorSet(trmin, -16, -16, -24);
VectorSet(trmax, 16, 16, 3);
if (ent->client->routetrace)
VectorSet(vv, 16, 16, 0);
else
VectorSet(vv, 16, 16, 3);
if (ent->client->routetrace)
{
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
if (ent->waterlevel < 2)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
if ((v[2] - ent->s.origin[2]) > 20)
trmax[2] = 31;
}
}
}
yaw = DEG2RAD(ryaw);
trend[0] = cos(yaw) * dist;
trend[1] = sin(yaw) * dist;
trend[2] = 0;
VectorAdd(trend, ent->s.origin, trstart);
VectorCopy(trstart, trend);
trend[2] += 1;
tr = gi.trace(trstart, trmin, trmax, trend, ent, MASK_BOTSOLIDX);
trmax[2] += 1;
if (tr.allsolid || tr.startsolid || (tr.fraction != 1.0))
{
float i;
qboolean moveok = false;
VectorCopy(trstart, trend);
for (i = 4; i < tracelimit + 4; i += 4)
{
trstart[2] = ent->s.origin[2] + i;
tr = gi.trace(trstart, trmin, vv, trend, ent, MASK_BOTSOLIDX);
if (!tr.allsolid && !tr.startsolid && (tr.fraction > 0.0))
{
moveok = true;
break;
}
}
if (!moveok)
return 0;
*bottom = tr.endpos[2] - ent->s.origin[2];
if (!ent->client->routetrace)
{
if ((tr.plane.normal[2] < 0.7) && !ent->client->waterstate && ent->groundentity)
return 0;
}
else
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
if ((tr.plane.normal[2] < 0.7) && (v[2] < ent->s.origin[2]))
return 0;
}
if (*bottom > tracelimit - 5)
return 0;
VectorCopy(tr.endpos, pos);
if (trmax[2] == 32)
return 1;
VectorCopy(pos, trend);
trend[2] += 28;
tr = gi.trace(pos, trmin, trmax, trend, ent, MASK_BOTSOLIDX);
return (!tr.allsolid && !tr.startsolid && (tr.fraction == 1.0))?1:2;
}
else
{
VectorCopy(trstart, pos);
VectorCopy(trstart, trend);
trstart[2] = trend[2] - 8190;
tr = gi.trace(trend, trmin, trmax, trstart, ent, MASK_BOTSOLIDX|MASK_OPAQUE);
*bottom = tr.endpos[2] - ent->s.origin[2];
contents = 0;
if (!ent->waterlevel)
{
if (ent->client->enviro_framenum > level.framenum)
contents = CONTENTS_LAVA;
else
contents = (CONTENTS_LAVA | CONTENTS_SLIME);
}
if (tr.contents & contents)
*bottom = -9999;
else if (tr.surface->flags & SURF_SKY)
*bottom = -9999;
if (!ent->waterlevel && !ent->groundentity)
{
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
if ((ent->velocity[2] > 10) && (trmax[2] == 4))
return 2;
}
}
if (trmax[2] == 32)
return 1;
VectorCopy(pos, trend);
trend[2] += 28;
tr = gi.trace(pos, trmin, trmax, trend, ent, MASK_BOTSOLIDX);
return (!tr.allsolid && !tr.startsolid && (tr.fraction == 1.0))?1:2;
}
}
//============================================================
qboolean Bot_Watermove(edict_t *ent, vec3_t pos, float dist, float upd)
{
trace_t tr;
vec3_t trmin;
vec3_t trmax;
vec3_t touchmin;
float max;
float vec;
float i;
float j;
VectorCopy(ent->s.origin, trmax);
trmax[2] += upd;
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, trmax, ent, MASK_BOTSOLIDX);
if (!tr.allsolid && !tr.startsolid && (tr.fraction > 0))
{
VectorCopy(tr.endpos, pos);
return true;
}
VectorCopy(ent->s.origin, trmin);
trmin[2] += upd;
vec = -1;
max = 0;
for (i = 0; i < 360; i += 10)
{
if (i && (upd > -13) && (upd < 0))
break;
if ((i > 60) && (i < 300))
continue;
j = ent->client->moveyaw + i;
if (j > 180)
j -= 360;
else if (j < -180)
j += 360;
else
j = i;
touchmin[0] = cos(j) * 24;
touchmin[1] = sin(j) * 24;
touchmin[2] = 0;
VectorAdd(trmin, touchmin, trmax);
tr = gi.trace(trmax, ent->mins, ent->maxs, trmin, ent, MASK_BOTSOLIDX);
if (!tr.allsolid && !tr.startsolid)
{
VectorAdd(tr.endpos, touchmin, trmax);
tr = gi.trace(trmax, ent->mins, ent->maxs, trmax, ent, MASK_BOTSOLIDX);
if (!tr.allsolid && !tr.startsolid)
{
vec = i;
break;
}
}
}
if (vec == -1)
return false;
VectorCopy(trmax, pos);
if (upd < 0)
ent->velocity[2] = 0;
return true;
}
//============================================================
qboolean Bot_moveW(edict_t *ent, float ryaw, vec3_t pos, float dist, float *bottom)
{
vec3_t trstart;
vec3_t trend;
trace_t tr;
float yaw;
int contents;
if (ent->client->enviro_framenum > level.framenum)
contents = CONTENTS_LAVA;
else
contents = (CONTENTS_LAVA|CONTENTS_SLIME);
yaw = DEG2RAD(ryaw);
trend[0] = cos(yaw) * dist;
trend[1] = sin(yaw) * dist;
trend[2] = 0;
VectorAdd(trend, ent->s.origin, trstart);
VectorCopy(trstart, pos);
VectorCopy(trstart, trend);
trstart[2] = trend[2] - 8190;
tr = gi.trace(trend, ent->mins, ent->maxs, trstart, ent, MASK_BOTSOLIDX|CONTENTS_WATER);
if ((trend[2] - tr.endpos[2]) >= 95)
return false;
if (tr.contents & contents)
return false;
if (!(tr.contents & CONTENTS_WATER))
return false;
*bottom = tr.endpos[2] - ent->s.origin[2];
return true;
}
//============================================================
qboolean Bot_Jump(edict_t *ent, vec3_t pos, float dist)
{
vec3_t temppos;
float yaw;
float tdist;
float bottom;
float speed;
float x;
yaw = ent->client->moveyaw;
Bot_TestMove(ent, yaw, temppos, dist, &bottom);
if (bottom > -JumpMax)
return false;
for (x = 2; x <= 16; x++)
{
tdist = dist * x;
if (Bot_TestMove(ent, yaw, temppos, tdist, &bottom))
{
if ((bottom <= JumpMax) && (bottom > -JumpMax))
{
if (Get_FlyingSpeed(bottom, x, dist, &speed))
{
speed *= 1.5;
if (speed > 1.2)
speed = 1.2;
ent->moveinfo.speed = speed;
ent->velocity[2] = VEL_BOT_JUMP;
SetBotAnim(ent);
return true;
}
}
continue;
}
else
return false;
}
return false;
}
//============================================================
qboolean Bot_Fall(edict_t *ent, vec3_t pos, float dist)
{
vec3_t vdir;
vec3_t vv;
float speed;
float vel;
float z_org;
float ypos;
float x;
float n;
int mf = 0;
int mode = 0;
if (ent->client->routetrace)
{
mode = 2;
Get_RouteOrigin(ent->client->pers.routeindex, vv);
ypos = vv[2];
if (!HazardCheck(ent, vv))
{
if (++ent->client->pers.routeindex >= TotalRouteNodes)
ent->client->pers.routeindex = 0;
return false;
}
z_org = pos[2];
VectorSubtract(vv, pos, vdir);
if (vdir[2] >= 0)
goto JUMPCATCH;
n = 1.0;
vel = ent->velocity[2];
for (x = 1; x <= BOT_FALLCHK_LOOPMAX; ++x, n += x)
{
vel -= (ent->gravity * sv_gravity->value * FRAMETIME);
z_org += vel * FRAMETIME;
if (ypos >= z_org)
{
mf = 1;
break;
}
}
VectorCopy(vdir, vv);
vv[2] = 0;
if (Route[ent->client->pers.routeindex].state == GRS_ONTRAIN)
{
vv[0] += FRAMETIME * Route[ent->client->pers.routeindex].ent->velocity[0] * x;
vv[1] += FRAMETIME * Route[ent->client->pers.routeindex].ent->velocity[1] * x;
}
speed = VectorLength(vv) / x;
if ((speed <= MOVE_SPD_RUN) && mf) //CW...
{
ent->moveinfo.speed = speed / MOVE_SPD_RUN;
VectorCopy(pos, ent->s.origin);
return true;
}
goto JUMPCATCH;
}
goto JMPCHK;
JUMPCATCH:
vel = VEL_BOT_JUMP;
z_org = pos[2];
mf = 0;
for (x = 1; x <= BOT_FALLCHK_LOOPMAX; ++x)
{
vel -= (ent->gravity * sv_gravity->value * FRAMETIME);
z_org += vel * FRAMETIME;
if (vel > 0)
{
if (mf == 0)
{
if (ypos < z_org)
mf = 2;
}
}
else if (x > 1)
{
if (mf == 0)
{
if (ypos < z_org)
mf = 2;
}
else if (mf == 2)
{
if (ypos >= z_org)
{
mf = 1;
break;
}
}
}
}
VectorCopy(vdir, vv);
vv[2] = 0;
if (mode == 2)
{
if (Route[ent->client->pers.routeindex].state == GRS_ONTRAIN)
{
vv[0] += FRAMETIME * Route[ent->client->pers.routeindex].ent->velocity[0] * x;
vv[1] += FRAMETIME * Route[ent->client->pers.routeindex].ent->velocity[1] * x;
}
}
n = VectorLength(vv);
if (x > 1)
n /= (x - 1);
if ((n < MOVE_SPD_RUN) && (mf == 1)) //CW...
{
ent->moveinfo.speed = n / MOVE_SPD_RUN;
VectorCopy(pos, ent->s.origin);
ent->velocity[2] = VEL_BOT_JUMP;
SetBotAnim(ent);
return true;
}
JMPCHK:
if (Bot_Jump(ent, pos, dist))
return true;
return false;
}
//============================================================
qboolean TargetJump(edict_t *ent, vec3_t tpos)
{
vec3_t vdir;
float vel;
float z_org;
float x;
float n;
int mf = 0;
vel = VEL_BOT_JUMP;
z_org = ent->s.origin[2];
//if on hazard object, cause error
if (!HazardCheck(ent, tpos))
return false;
VectorSubtract(tpos, ent->s.origin, vdir);
for (x = 1; x <= BOT_FALLCHK_LOOPMAX * 2; ++x)
{
vel -= (ent->gravity * sv_gravity->value * FRAMETIME);
z_org += vel * FRAMETIME;
if (vel > 0)
{
if (mf == 0)
{
if (tpos[2] < z_org)
mf = 2;
}
}
else if (x > 1)
{
if (mf == 0)
{
if (tpos[2] < z_org)
mf = 2;
}
else if (mf == 2)
{
if (tpos[2] >= z_org)
{
mf = 1;
break;
}
}
}
}
vdir[2] = 0;
n = VectorLength(vdir);
if (x > 1)
n /= (x - 1);
if ((n < MOVE_SPD_RUN) && (mf == 1)) //CW
{
ent->moveinfo.speed = n / MOVE_SPD_RUN;
ent->velocity[2] = VEL_BOT_JUMP; //CW
SetBotAnim(ent);
return true;
}
return false;
}
//============================================================
qboolean TargetJump_Chk(edict_t *ent, vec3_t tpos, float defvel)
{
vec3_t vdir;
float vel;
float z_org;
float x;
float n;
int mf = 0;
vel = defvel + VEL_BOT_JUMP;
z_org = ent->s.origin[2];
//if on hazard object, cause error
if (!HazardCheck(ent, tpos))
return false;
VectorSubtract(tpos, ent->s.origin, vdir);
for (x = 1; x <= BOT_FALLCHK_LOOPMAX * 2; ++x)
{
vel -= (ent->gravity * sv_gravity->value * FRAMETIME);
z_org += vel * FRAMETIME;
if (vel > 0)
{
if (mf == 0)
{
if (tpos[2] < z_org)
mf = 2;
}
}
else if (x > 1)
{
if (mf == 0)
{
if (tpos[2] < z_org)
mf = 2;
}
else if (mf == 2)
{
if (tpos[2] >= z_org)
{
mf = 1;
break;
}
}
}
}
vdir[2] = 0;
n = VectorLength(vdir);
if (x > 1)
n /= (x - 1);
return ((n < MOVE_SPD_RUN) && (mf == 1)); //CW
}
//============================================================
void Get_WaterState(edict_t *ent)
{
//CW++
// Previous waterlevel code meant bots would never drown or be extinguished if on fire.
M_CatagorizePosition(ent);
//CW--
if (ent->waterlevel)
{
trace_t tr;
vec3_t trmin;
vec3_t trmax;
float x;
VectorCopy(ent->s.origin, trmax);
VectorCopy(ent->s.origin, trmin);
trmax[2] -= 24;
trmin[2] += 8;
tr = gi.trace(trmin, NULL, NULL, trmax, ent, MASK_WATER);
x = trmin[2] - tr.endpos[2];
if (tr.allsolid || tr.startsolid || (x < 4.0))
ent->client->waterstate = WAS_IN;
else
ent->client->waterstate = ((x >= 4.0) && (x <= 12.0))?WAS_FLOAT:WAS_NONE;
}
else
ent->client->waterstate = WAS_NONE;
}
//============================================================
void Search_NearbyPod(edict_t *ent)
{
if (Route[ent->client->pers.routeindex].state >= 3)
return;
if ((ent->client->pers.routeindex+1) < TotalRouteNodes)
{
if (Route[ent->client->pers.routeindex+1].state < 3)
{
vec3_t v;
Get_RouteOrigin(ent->client->pers.routeindex+1, v);
if (TraceX(ent, v))
{
vec3_t v1;
vec3_t v2;
float x;
VectorSubtract(v, ent->s.origin, v1);
Get_RouteOrigin(ent->client->pers.routeindex, v);
VectorSubtract(v, ent->s.origin, v2);
x = fabs(v1[2]);
if ((VectorLength(v1) < VectorLength(v2)) && (x <= JumpMax) && (Route[ent->client->pers.routeindex].state <= 1))
ent->client->pers.routeindex++;
else
{
if (ent->client->waterstate == WAS_NONE)
{
if ((v2[2] > JumpMax) && (fabs(v1[2]) < JumpMax))
ent->client->pers.routeindex++;
}
}
}
}
}
}
//========================================================
void Bot_AI(edict_t *ent)
{
gitem_t *it;
edict_t *it_ent = NULL; //CW
edict_t *touch[1024];
edict_t *trent;
edict_t *front;
edict_t *left;
edict_t *right;
edict_t *e;
trace_t tr;
cplane_t plane;
char *str;
vec3_t touchmin;
vec3_t touchmax;
vec3_t v;
vec3_t vv;
vec3_t temppos;
vec3_t trmin;
vec3_t trmax;
vec3_t tmp_org; //CW
vec3_t tmp_vel; //CW
float tmp_yaw; //CW
float dist;
float x;
float yaw = 0.0F; //CW
float iyaw = 0.0F; //CW
float f1;
float f2;
float f3;
float bottom;
int tempflag;
int contents; //CW++
int i;
int j = 0; //CW
int k;
qboolean ladderdrop;
qboolean canrocj;
qboolean waterjumped;
myrandom = random();
trace_priority = TRP_NORMAL;
ent->client->objshot = false;
ent->client->buttons &= ~BUTTON_ATTACK;
//CW++
if (ent->client->agm_enemy)
ent->client->movestate |= STS_AGMMOVE;
else
ent->client->movestate &= ~STS_AGMMOVE;
// Detonate C4 if one is near our current enemy.
if (SkillLevel[Bot[ent->client->pers.botindex].skill[COMBATSKILL]] & FIRE_C4USE)
{
if (ent->client->current_enemy && ent->next_node)
{
edict_t *check;
edict_t *index;
qboolean finished = false;
index = ent->next_node;
while (index && !finished)
{
check = index;
if (index->next_node)
index = index->next_node;
else
finished = true;
if (VecRange(ent->client->current_enemy->s.origin, check->s.origin) < 100)
{
Cmd_Detonate_C4_f(ent);
break;
}
}
}
}
// Solid checks.
contents = gi.pointcontents(ent->s.origin);
if (contents & CONTENTS_SOLID)
T_Damage(ent, ent, ent, ent->s.origin, ent->s.origin, ent->s.origin, 100, 1, 0, MOD_CRUSH);
//CW--
if (VectorCompare(ent->s.origin, ent->s.old_origin))
{
if (!ent->groundentity && !ent->waterlevel)
{
VectorCopy(ent->s.origin, v);
v[2] -= 1.0;
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, v, ent, MASK_BOTSOLIDX);
if (!tr.allsolid && !tr.startsolid)
ent->groundentity = tr.ent;
}
}
//CW++
// Frozen bots can't move!
if (ent->client->frozen_framenum > level.framenum)
{
int n;
for (n = 0; n < 3; ++n)
{
ent->s.old_origin[n] = ent->s.origin[n];
ent->velocity[n] = 0.0;
}
return;
}
//CW--
VectorCopy(ent->s.origin, tmp_org);
VectorCopy(ent->velocity, tmp_vel);
tmp_yaw = ent->s.angles[YAW];
// Check chaining mode.
if ((int)chedit->value)
{
qboolean tracefail = false;
if (!ent->client->routetrace)
tracefail = true; // route off
else if (ent->client->routeindex >= CurrentIndex)
tracefail = true; // index overflow
else if ((Route[ent->client->routeindex].index == 0) && (ent->client->routeindex > 0))
tracefail = true; // index end
if (tracefail)
{
gi.dprintf("Tracing failed for %s.\n", ent->client->pers.netname);
RemoveBot(ent);
return;
}
}
// Get JumpMax.
if (JumpMax == 0)
{
x = VEL_BOT_JUMP - (ent->gravity * sv_gravity->value * FRAMETIME);
while (1)
{
JumpMax += x * FRAMETIME;
x -= ent->gravity * sv_gravity->value * FRAMETIME;
if (x < 0)
break;
}
}
// Set target.
if (!ent->client->havetarget && ent->client->routetrace)
{
int it_num; //CW++
j = Bot[ent->client->pers.botindex].skill[PRIMARYWEAP];
it_num = ITEM_INDEX(GetWeaponType(j)); //CW++
if (j && !ent->client->pers.inventory[it_num]) //CW
{
it = &itemlist[it_num]; //CW
if ((ent->client->enemy_routeindex < ent->client->pers.routeindex) || (ent->client->enemy_routeindex >= TotalRouteNodes))
ent->client->enemy_routeindex = ent->client->pers.routeindex;
for (i = ent->client->enemy_routeindex + 1; i < ent->client->enemy_routeindex + 50; i++)
{
if (i > TotalRouteNodes)
break;
if (Route[i].state == GRS_ITEMS)
{
if (Route[i].ent->item == it)
{
ent->client->havetarget = true;
break;
}
else if (Route[i].ent->solid == SOLID_TRIGGER)
{
if (Route[i].ent->item == &itemlist[it_num]) //CW
{
ent->client->havetarget = true;
break;
}
}
}
}
ent->client->enemy_routeindex = i;
}
else
{
if ((it_num = ITEM_INDEX(item_quad)) > 0) //CW
{
it = &itemlist[it_num];
if ((ent->client->enemy_routeindex < ent->client->pers.routeindex) || (ent->client->enemy_routeindex >= TotalRouteNodes))
ent->client->enemy_routeindex = ent->client->pers.routeindex;
for (i = ent->client->enemy_routeindex + 1; i < ent->client->enemy_routeindex + 25; i++)
{
if (i > TotalRouteNodes)
break;
if ((Route[i].state == GRS_ITEMS) && (Route[i].ent->item == it))
{
if (Route[i].ent->solid == SOLID_TRIGGER)
{
ent->client->havetarget = true;
break;
}
}
}
ent->client->enemy_routeindex = i;
}
}
}
else if (ent->client->havetarget)
{
if (ent->client->enemy_routeindex < ent->client->pers.routeindex)
{
ent->client->havetarget = false;
ent->client->enemy_routeindex = ent->client->pers.routeindex;
}
}
// Bot can rocket jump?
canrocj = (ent->client->pers.inventory[ITEM_INDEX(item_rocketlauncher)] && (ent->client->pers.inventory[ITEM_INDEX(item_rockets)] > 0));
// Ducking check.
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
if ((ent->client->battleduckcnt > 0) && ent->groundentity)
goto DCHCANC;
VectorCopy(ent->s.origin, v);
v[2] += 28;
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, v, ent, MASK_BOTSOLIDX);
if (!tr.startsolid && !tr.allsolid && (tr.fraction == 1.0))
{
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
ent->maxs[2] = 32;
}
}
else if ((ent->velocity[2] > 10) && (ent->groundentity == NULL)) //CW
{
if (!(ent->client->movestate & STS_SJMASK))
{
VectorSet(v, 16, 16, 40);
tr = gi.trace(ent->s.origin, ent->mins, v, ent->s.origin, ent, MASK_BOTSOLIDX);
if (tr.startsolid || tr.allsolid)
{
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
ent->maxs[2] = 4;
}
}
}
DCHCANC:
// Set moving speed.
if (ent->groundentity || ent->waterlevel)
{
if (ent->waterlevel)
{
if (!(ent->client->movestate & STS_WATERJ))
ent->client->movestate &= ~STS_SJMASK;
}
else
ent->client->movestate &= ~STS_SJMASK;
if (ent->groundentity && !ent->waterlevel)
ent->moveinfo.speed = 1.0;
else if (ent->waterlevel && (ent->velocity[2] <= 1))
ent->moveinfo.speed = 1.0;
}
if ((ent->client->ps.pmove.pm_flags & PMF_DUCKED) && ent->groundentity)
dist = MOVE_SPD_DUCK * ent->moveinfo.speed;
else
{
if (!ent->waterlevel) //CW...
dist = MOVE_SPD_RUN * ent->moveinfo.speed;
else
{
if (ent->groundentity && (ent->waterlevel < 2))
dist = MOVE_SPD_RUN * ent->moveinfo.speed;
else
dist = MOVE_SPD_WATER * ent->moveinfo.speed;
}
if (ent->groundentity)
dist *= ent->client->ground_slope;
}
// Get water depth state of bot.
Get_WaterState(ent);
// Search for enemy.
ent->client->enemysearchcnt += 2;
if (ent->client->enemysearchcnt >= 10)
{
Bot_SearchEnemy(ent);
ent->client->enemysearchcnt = 1 + (rand() % 10); //CW
if (ent->client->enemysearchcnt > 10)
ent->client->enemysearchcnt = 10;
else if (ent->client->enemysearchcnt < 0)
ent->client->enemysearchcnt = 0;
}
// Set bot's combat status.
Set_Combatstate(ent);
if (trace_priority == TRP_ALLKEEP)
goto VCHCANSEL;
// Browse target status.
if (ent->client->routetrace)
{
if (Route[ent->client->pers.routeindex].state >= GRS_NORMAL)
Search_NearbyPod(ent);
Get_RouteOrigin(ent->client->pers.routeindex, v);
if (ent->client->movestate & STS_WAITSMASK)
ent->client->routelocktime = level.time + 1.5;
else if ((Route[ent->client->pers.routeindex].state <= GRS_ITEMS) && (v[2] - ent->s.origin[2] > JumpMax) && !ent->client->waterstate && !(ent->client->movestate & STS_LADDERUP))
{
if (ent->client->routelocktime <= level.time)
{
ent->client->routetrace = false;
ent->client->routereleasetime = level.time + 2.0;
}
}
else if (!TraceX(ent, v))
{
k = 0;
if (ent->groundentity)
{
if (ent->groundentity->use == train_use)
{
ent->client->routelocktime = level.time + 1.5;
k = 1;
}
}
if ((ent->client->routelocktime <= level.time) && !k)
{
ent->client->routetrace = false;
ent->client->routereleasetime = level.time + 2.0;
}
}
else
ent->client->routelocktime = level.time + 1.5;
}
if (trace_priority == TRP_ALLKEEP)
goto VCHCANSEL;
// Climbing a ladder.
if (ent->client->movestate & STS_LADDERUP)
{
ent->velocity[2] = VEL_BOT_LADDERUP;
VectorCopy(ent->mins, trmin);
trmin[2] += 20;
yaw = DEG2RAD(ent->client->moveyaw);
touchmin[0] = cos(yaw) * 32;
touchmin[1] = sin(yaw) * 32;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, touchmax);
tr = gi.trace(ent->s.origin, trmin, ent->maxs, touchmax, ent, MASK_BOTSOLID);
plane = tr.plane;
if (!(tr.contents & CONTENTS_LADDER) && !tr.allsolid) // off ladder
{
if ((ent->velocity[2] <= VEL_BOT_LADDERUP) && !ent->waterlevel)
ent->velocity[2] = VEL_BOT_LADDERUP;
ent->client->movestate &= ~STS_LADDERUP;
ent->moveinfo.speed = 0.25;
if (ent->client->routetrace)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
if (VectorLength(v) > 32)
{
VectorSubtract(v, ent->s.origin, v);
ent->client->moveyaw = Get_yaw(v);
if (trace_priority < 2)
ent->s.angles[YAW] = ent->client->moveyaw;
}
else
ent->client->pers.routeindex++;
}
}
else
{
if (!tr.allsolid)
VectorCopy(tr.endpos, ent->s.origin);
VectorCopy(ent->s.origin, touchmin);
touchmin[2] += 8;
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, touchmin, ent, MASK_BOTSOLID);
x = tr.endpos[2] - ent->s.origin[2];
ent->s.origin[2] += x;
e = tr.ent;
if (x == 0)
{
x = Get_yaw(plane.normal);
//right
VectorCopy(ent->s.origin, v);
yaw = x + 90;
if (yaw > 180)
yaw -= 360;
yaw = DEG2RAD(yaw);
touchmin[0] = cos(yaw) * 48;
touchmin[1] = sin(yaw) * 48;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, trmin);
VectorCopy(trmin, trmax);
trmin[2] += 32;
trmax[2] += 64;
tr = gi.trace(trmin, NULL,NULL, trmax, ent, MASK_BOTSOLID);
f1 = tr.fraction;
//left
VectorCopy(ent->s.origin, v);
iyaw = x - 90;
if (iyaw < 180)
iyaw += 360;
iyaw = DEG2RAD(iyaw);
touchmin[0] = cos(iyaw) * 48;
touchmin[1] = sin(iyaw) * 48;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, trmin);
VectorCopy(trmin, trmax);
trmin[2] += 32;
trmax[2] += 64;
tr = gi.trace(trmin, NULL, NULL, trmax, ent, MASK_BOTSOLID);
f2 = tr.fraction;
x = 0.0;
if ((f1 == 1.0) && (f2 != 1.0))
x = yaw;
else if ((f1 != 1.0) && (f2 == 1.0))
x = iyaw;
if (x != 0.0)
{
touchmin[0] = cos(x) * 4;
touchmin[1] = sin(x) * 4;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, trmin);
tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, trmin, ent, MASK_BOTSOLID);
if (tr.startsolid || tr.allsolid)
x = 0;
else
VectorCopy(tr.endpos, ent->s.origin);
}
if (x == 0.0) // off ladder
{
k = 0;
if (e)
{
if (e->use == door_use)
{
if (e->moveinfo.state == PSTATE_UP)
k = 1;
}
}
if (!k)
{
ent->client->moveyaw += 180;
if (ent->client->moveyaw > 180)
ent->client->moveyaw -= 360;
ent->client->movestate &= ~STS_LADDERUP;
ent->moveinfo.speed = 0.25;
}
}
}
}
if (ent->client->movestate & STS_LADDERUP)
{
if (ent->client->routetrace)
{
Get_RouteOrigin(ent->client->pers.routeindex,v);
if (v[2] < ent->s.origin[2])
{
VectorSubtract(ent->s.origin, v, vv);
vv[2] = 0;
if (VectorLength(vv) < 32)
ent->client->pers.routeindex++;
}
}
ent->velocity[0] = 0;
ent->velocity[1] = 0;
goto VCHCANSEL_L;
}
}
// Bot's true moving yaw, yaw pitch set (j is used ground entity check section).
if (ent->groundentity && (ent->waterlevel <= 1) && (trace_priority < TRP_ANGLEKEEP))
ent->s.angles[PITCH] = 0;
if (ent->groundentity && !ent->client->routetrace)
{
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = ent->s.angles[YAW];
}
else if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = ent->client->moveyaw;
if (!ent->client->routetrace && (ent->client->routereleasetime <= level.time))
{
if (ent->client->pers.routeindex >= TotalRouteNodes)
ent->client->pers.routeindex = 0;
for (i = 0; (i < TotalRouteNodes) && (i < MAX_SEARCH); i++)
{
if (Route[ent->client->pers.routeindex].state == GRS_GRAPHOOK)
{
while (1)
{
++ent->client->pers.routeindex;
if (ent->client->pers.routeindex >= TotalRouteNodes)
{
i = TotalRouteNodes;
break;
}
if (Route[ent->client->pers.routeindex].state == GRS_GRAPRELEASE)
{
++ent->client->pers.routeindex;
break;
}
}
continue;
}
else if (Route[ent->client->pers.routeindex].state == GRS_GRAPRELEASE)
{
++ent->client->pers.routeindex;
continue;
}
Get_RouteOrigin(ent->client->pers.routeindex, v);
if ((Route[ent->client->pers.routeindex].state <= GRS_ITEMS) && TraceX(ent, v))
{
if ((fabs(v[2] - ent->s.origin[2]) <= JumpMax) || (ent->client->waterstate == WAS_IN))
{
ent->client->routetrace = true;
ent->client->routelocktime = level.time + 1.5;
break;
}
}
if (++ent->client->pers.routeindex >= TotalRouteNodes)
ent->client->pers.routeindex = 0;
}
}
else if (ent->client->routetrace)
{
if (Route[ent->client->pers.routeindex].state == GRS_ONDOOR)
{
it_ent = Route[ent->client->pers.routeindex].ent;
if (ent->client->pers.routeindex + 1 < TotalRouteNodes)
{
Get_RouteOrigin(ent->client->pers.routeindex+1, v);
ent->client->routetrace = false;
j = TraceX(ent,v);
ent->client->routetrace = true;
if ((!j || (v[2] - ent->s.origin[2] > JumpMax)) && it_ent->union_ent)
{
k = ((it_ent->union_ent->s.origin[2] - ent->s.origin[2]) > JumpMax)?1:0;
VectorSubtract(it_ent->union_ent->s.origin, ent->s.origin, temppos);
yaw = Get_yaw(temppos);
if (trace_priority < TRP_ANGLEKEEP)
{
ent->s.angles[PITCH] = Get_pitch(temppos);
ent->s.angles[YAW] = yaw;
}
temppos[2] = 0;
x = VectorLength(temppos);
if ((x == 0) || k)
{
if (it_ent->nextthink >= level.time)
ent->client->routelocktime = level.time + 1.5;
goto VCHCANSEL;
}
if (x < dist)
dist = x;
if (it_ent->nextthink > level.time)
ent->client->routelocktime = it_ent->nextthink + 1.5;
else
ent->client->routelocktime = level.time + 1.5;
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = yaw;
goto GOMOVE;
}
}
ent->client->pers.routeindex++;
}
if (ent->client->pers.routeindex < TotalRouteNodes)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
k = 0;
if (Route[ent->client->pers.routeindex].state == GRS_PUSHBUTTON)
{
it_ent = Route[ent->client->pers.routeindex].ent;
if (it_ent->health && (it_ent->takedamage || (it_ent->moveinfo.state != PSTATE_TOP)))
k = 2;
else if (it_ent->health)
{
ent->client->pers.routeindex++;
if (ent->client->pers.routeindex < TotalRouteNodes)
Get_RouteOrigin(ent->client->pers.routeindex, v);
}
}
else
{
VectorSet(touchmax, 16, 16, 4);
VectorSet(touchmin, -16, -16, 0);
tr = gi.trace(ent->s.origin, touchmin, touchmax, v, ent, MASK_SHOT);
if ((tr.fraction != 1.0) && tr.ent)
{
if (tr.ent->health || tr.ent->takedamage)
{
if ((tr.ent->classname[0] != 'p') && (tr.ent->classname[0] != 'b'))
{
ent->client->routelocktime = level.time + 1.5;
it_ent = tr.ent;
k = 1;
}
}
}
}
if (k && !(ent->client->buttons & BUTTON_ATTACK))
{
trmin[0] = (it_ent->absmin[0] + it_ent->absmax[0]) * 0.5;
trmin[1] = (it_ent->absmin[1] + it_ent->absmax[1]) * 0.5;
trmin[2] = (it_ent->absmin[2] + it_ent->absmax[2]) * 0.5;
if (k == 2) // if button
{
VectorSet(touchmin, 0, 0, ent->viewheight-8);
VectorAdd(ent->s.origin, touchmin, touchmin);
tr = gi.trace(it_ent->union_ent->s.origin, NULL, NULL, trmin, it_ent->union_ent, MASK_SHOT);
VectorSubtract(tr.endpos, ent->s.origin, trmax);
}
else
VectorSubtract(v, ent->s.origin, trmax);
if (!ent->client->current_enemy && it_ent->takedamage) // shoot!
{
ent->client->newweapon = item_deserteagle; //CW...
ChangeWeapon(ent);
ent->client->pers.weapon->use(ent, item_deserteagle);
}
if (!ent->client->current_enemy || it_ent->takedamage)
{
ent->s.angles[YAW] = Get_yaw(trmax);
ent->s.angles[PITCH] = Get_pitch(trmax);
}
if (it_ent->takedamage)
ent->client->buttons |= BUTTON_ATTACK;
if (k == 2)
{
if (it_ent->moveinfo.state != PSTATE_TOP)
goto VCHCANSEL;
}
else
{
if (!TraceX(ent,v))
goto VCHCANSEL;
}
}
if ((Route[ent->client->pers.routeindex].state == GRS_ONTRAIN) && !ent->client->waterstate)
{
Get_RouteOrigin(ent->client->pers.routeindex-1, trmin);
if ((trmin[2] - ent->s.origin[2] > JumpMax) && (v[2] - ent->s.origin[2] > JumpMax) && (ent->waterlevel < 3))
ent->client->routetrace = false;
}
f2 = (ent->client->waterstate == WAS_IN)?20:((ent->groundentity)?-8:0);
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
f1 = -16;
else
{
if (ent->client->waterstate == WAS_IN)
f1 = 24;
else if (ent->waterlevel && (ent->waterlevel < 3))
f1 = (((v[0] == ent->s.origin[0]) && (v[1] == ent->s.origin[1]))?-300:(-(JumpMax + 64)));
else
f1 = -(JumpMax + 64);
}
yaw = (Route[ent->client->pers.routeindex].state == GRS_ONROTATE) ? -48 : 12;
if ((v[0] <= ent->absmax[0] - yaw) && (v[0] >= ent->absmin[0] + yaw))
{
if ((v[1] <= ent->absmax[1] - yaw) && (v[1] >= ent->absmin[1] + yaw))
{
if (((v[2] <= ent->absmax[2] - f1) && (v[2] >= ent->absmin[2] + f2)) || (Route[ent->client->pers.routeindex].state == GRS_ONROTATE))
{
if (ent->client->pers.routeindex < TotalRouteNodes)
{
if (Route[ent->client->pers.routeindex].state <= GRS_ITEMS)
{
if (ent->client->havetarget)
{
for (i = 0; i < MAXLINKPOD; i++)
{
if (!(k = Route[ent->client->pers.routeindex].linkpod[i]))
break;
// try to change index
if ((k > ent->client->pers.routeindex) && (k < ent->client->enemy_routeindex))
{
ent->client->pers.routeindex = k;
break;
}
}
}
else if (random() < 0.2)
{
for (i = 0; i < MAXLINKPOD; i++)
{
if (!(k = Route[ent->client->pers.routeindex].linkpod[i]))
break;
// try to change index
if ((k > ent->client->pers.routeindex) && (k < ent->client->enemy_routeindex))
{
if (random() < 0.5)
{
ent->client->pers.routeindex = k;
break;
}
}
}
}
}
ent->client->pers.routeindex++;
// not a normal pod
if (!(ent->client->pers.routeindex < TotalRouteNodes))
ent->client->pers.routeindex = 0;
}
}
}
}
if ((ent->client->pers.routeindex < TotalRouteNodes) && trace_priority)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
VectorSubtract(v, ent->s.origin, temppos);
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[PITCH] = Get_pitch(temppos);
k = 0;
if (ent->groundentity || ent->waterlevel)
{
yaw = temppos[2];
temppos[2] = 0;
x = VectorLength(temppos);
//CW
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos); // set the moving yaw
if ((ent->groundentity || ent->waterlevel) && (trace_priority < TRP_ANGLEKEEP))
{
ent->s.angles[YAW] = ent->client->moveyaw;
k = 1;
}
if ((x < dist) && (fabs(yaw) < 20) && k)
{
iyaw = Get_yaw(temppos);
i = Bot_TestMove(ent, iyaw, temppos, x, &bottom);
tr = gi.trace(v, ent->mins, ent->maxs, v, ent, MASK_BOTSOLIDX);
if ((Route[ent->client->pers.routeindex].state == GRS_ITEMS) && !i)
{
if (x < 30)
ent->client->pers.routeindex++;
}
else if (((Route[ent->client->pers.routeindex].state == GRS_ITEMS) || (Route[ent->client->pers.routeindex].state == GRS_NORMAL))
&& !tr.allsolid && !tr.startsolid && HazardCheck(ent, v) && (fabs(bottom) < 20) && i && !ent->waterlevel)
{
if (((v[2] < ent->s.origin[2]) && (bottom < 0)) || ((v[2] >= ent->s.origin[2]) && (bottom >= 0)))
{
VectorCopy(temppos, ent->s.origin);
VectorCopy(v, trmin);
dist -= x;
if (Route[ent->client->pers.routeindex].state <= GRS_ITEMS)
{
if (ent->client->havetarget)
{
for (i = 0; i < MAXLINKPOD; i++)
{
if (!(j = Route[ent->client->pers.routeindex].linkpod[i]))
break;
if ((j > ent->client->pers.routeindex) && (j < ent->client->enemy_routeindex))
{
ent->client->pers.routeindex = j;
break;
}
}
}
}
ent->client->pers.routeindex++;
if (i == 2)
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
Get_RouteOrigin(ent->client->pers.routeindex, v);
VectorSubtract(v, ent->s.origin, temppos);
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[PITCH] = Get_pitch(temppos);
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos);
if (k && trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = ent->client->moveyaw;
}
}
else if (((Route[ent->client->pers.routeindex].state == GRS_ITEMS) || (Route[ent->client->pers.routeindex].state == GRS_NORMAL))
&& (fabs(bottom) < 20) && ent->waterlevel)
{
if (((v[2] < ent->s.origin[2]) && (bottom < 0)) || ((v[2] >= ent->s.origin[2]) && (bottom >= 0)))
{
VectorCopy(temppos, ent->s.origin);
VectorCopy(v, trmin);
dist -= x;
ent->client->pers.routeindex++;
Get_RouteOrigin(ent->client->pers.routeindex, v);
VectorSubtract(v, ent->s.origin, temppos);
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[PITCH] = Get_pitch(temppos);
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos);
if (k && trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = ent->client->moveyaw;
}
else
dist = x;
}
else
dist = x;
}
else if (x < dist)
dist = x;
k = 0;
if ((ent->client->pers.routeindex - 1 >= 0) && ((Route[ent->client->pers.routeindex].state == GRS_ONPLAT) || (Route[ent->client->pers.routeindex].state == GRS_ONTRAIN)))
{
Get_RouteOrigin(ent->client->pers.routeindex-1, v);
if (fabs(v[2] - ent->s.origin[2]) <= JumpMax)
{
if ((ent->client->waterstate < WAS_IN) && (Route[ent->client->pers.routeindex].ent->nextthink > level.time))
k = 1;
}
}
if (k && !(ent->client->movestate & STS_WAITS))
{
if (ent->client->pers.routeindex + 1 < TotalRouteNodes)
{
Get_RouteOrigin(ent->client->pers.routeindex+1, v);
if (v[2] - ent->s.origin[2] > JumpMax)
{
if (Route[ent->client->pers.routeindex].ent->union_ent->s.origin[2] - ent->s.origin[2] > JumpMax)
{
ent->client->waiting_obj = Route[ent->client->pers.routeindex].ent;
ent->client->movestate |= STS_W_COMEPLAT;
k = 0;
for (i = 1; i <= 3; i++)
{
if (ent->client->pers.routeindex - i >= 0)
{
Get_RouteOrigin(ent->client->pers.routeindex-i, v);
if (ent->client->waiting_obj->absmax[0] < v[0] + ent->mins[0])
k = 1;
else if (ent->client->waiting_obj->absmax[1] < v[1] + ent->mins[1])
k = 1;
else if (ent->client->waiting_obj->absmin[0] > v[0] + ent->maxs[0])
k = 1;
else if (ent->client->waiting_obj->absmin[1] > v[1] + ent->maxs[1])
k = 1;
if (k)
break;
}
}
if (k)
VectorCopy(v, ent->client->movtarget_pt);
else
Get_RouteOrigin(ent->client->pers.routeindex-1, ent->client->movtarget_pt);
goto VCHCANSEL;
}
}
}
}
}
}
else if (ent->client->pers.routeindex >= TotalRouteNodes)
{
ent->client->pers.routeindex = 0;
ent->client->routetrace = false;
}
}
else
{
ent->client->pers.routeindex = 0;
ent->client->routetrace = false;
}
}
// Ground entity check.
if (!(ent->client->movestate & STS_W_DOOROPEN) && (!ent->groundentity || (ent->groundentity != ent->client->waiting_obj)))
{
if (!(ent->client->waiting_obj && (ent->client->waiting_obj->use == door_use)))
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
}
if (ent->groundentity && !(ent->client->movestate & STS_WAITS))
{
it_ent = ent->groundentity;
if (it_ent->classname[0] == 'f')
{
// on platform
if (it_ent->use == Use_Plat)
{
if ((it_ent->pos1[2] > it_ent->pos2[2]) && (((it_ent->moveinfo.state == PSTATE_UP) && (it_ent->velocity[2] > 0)) || (it_ent->moveinfo.state == PSTATE_BOTTOM)))
{
ent->client->waiting_obj = it_ent;
ent->client->movestate |= STS_W_ONPLAT;
if (ent->client->routetrace)
{
if (Route[ent->client->pers.routeindex].ent == ent->client->waiting_obj)
{
if (Route[ent->client->pers.routeindex].state == GRS_ONPLAT)
{
if (ent->client->waiting_obj->union_ent->s.origin[2] > ent->s.origin[2] + 32)
{
ent->client->movestate &= ~STS_W_ONPLAT;
ent->client->movestate |= STS_W_COMEPLAT;
}
else
ent->client->pers.routeindex++;
}
}
}
}
}
// on train
else if ((it_ent->use == train_use) && (it_ent->nextthink >= level.time) && ((it_ent->s.origin[2] - it_ent->s.old_origin[2] > 0) || ent->client->routetrace))
{
if (ent->client->routetrace && (ent->client->pers.routeindex > 0))
{
j = 0;
k = ent->client->pers.routeindex - 1;
for (i = 0; i < 3; i++)
{
if (k + i < TotalRouteNodes)
{
if (Route[k+i].state == GRS_ONTRAIN)
{
if (Route[k+i].ent == it_ent)
j = 1;
else if (it_ent->trainteam)
{
e = it_ent->trainteam;
while (1)
{
if (e == it_ent)
break;
if (e == Route[k+i].ent)
{
j = 1;
it_ent = e;
Route[k+i].ent = e;
break;
}
e = e->trainteam;
}
}
else if (it_ent->target_ent)
{
if (VectorCompare(Route[k+i].Tcorner, it_ent->target_ent->s.origin))
{
j = 1;
break;
}
}
if (j)
break;
}
}
else break;
}
if (j) // on train
{
ent->client->movestate |= STS_W_ONTRAIN;
ent->client->waiting_obj = it_ent;
ent->client->pers.routeindex = k + i + 1;
}
}
else
{
if (it_ent->s.origin[2] - it_ent->s.old_origin[2] > 0)
{
ent->client->movestate |= STS_W_ONTRAIN;
ent->client->waiting_obj = it_ent;
}
else if ((it_ent->s.origin[2] - it_ent->s.old_origin[2] > -2) && trace_priority)
{
ent->client->movestate |= STS_W_ONTRAIN;
ent->client->waiting_obj = it_ent;
}
else
ent->client->movestate |= STS_W_DONT;
}
}
}
}
// clear waiting state if flagged
if ((ent->client->movestate & STS_W_DONT) && ent->groundentity)
{
if (ent->client->movestate & STS_W_ONPLAT)
{
if (ent->groundentity->use == Use_Plat)
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
}
else if (ent->client->movestate & STS_W_ONTRAIN)
{
if (ent->groundentity->use == train_use)
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
}
else if (ent->client->movestate & (STS_W_ONDOORUP | STS_W_ONDOORDWN))
{
if (ent->groundentity->use == door_use)
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
}
else
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
}
// on plat
else if ((ent->client->movestate & (STS_W_ONPLAT | STS_W_COMEPLAT | STS_W_ONDOORUP | STS_W_ONDOORDWN)) && !(ent->client->movestate & STS_W_DONT))
{
k = 0;
//if door
if (ent->client->movestate & (STS_W_ONDOORUP | STS_W_ONDOORDWN))
{
if (ent->client->movestate & STS_W_ONDOORUP) // going up
{
if ((ent->client->waiting_obj->moveinfo.state == PSTATE_UP) || (ent->client->waiting_obj->moveinfo.state == PSTATE_BOTTOM))
k = 1;
}
else // going down
{
if ((ent->client->waiting_obj->moveinfo.state == PSTATE_TOP) || (ent->client->waiting_obj->moveinfo.state == PSTATE_DOWN))
k = 1;
}
}
else if (ent->client->movestate & STS_W_COMEPLAT)
{
if (Route[ent->client->pers.routeindex].state == GRS_ONTRAIN)
{
if (!TraceX(ent, Route[ent->client->pers.routeindex].ent->union_ent->s.origin))
k = 1;
if (Route[ent->client->pers.routeindex].ent->union_ent->s.origin[2] + 8 - ent->s.origin[2] > JumpMax)
k = 1;
}
else
{
if (ent->client->waiting_obj->union_ent->s.origin[2] - ent->s.origin[2] > JumpMax)
k = 1;
}
if ((ent->client->pers.routeindex - 1 > 0) && (ent->client->waterstate < WAS_IN))
{
Get_RouteOrigin(ent->client->pers.routeindex-1, trmin);
if ((trmin[2] - ent->s.origin[2] > JumpMax) && (v[2] - ent->s.origin[2] > JumpMax))
k = 0;
}
}
else
{
if ((ent->client->waiting_obj->moveinfo.state == PSTATE_UP) || (ent->client->waiting_obj->moveinfo.state == PSTATE_BOTTOM))
k = 1;
if (ent->client->waiting_obj->moveinfo.state == PSTATE_BOTTOM)
plat_go_up(ent->client->waiting_obj);
if (ent->client->routetrace)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
if (ent->s.origin[2] > v[2])
k = 2;
}
}
// have target
if (k != 1)
{
if (k == 2)
ent->client->movestate |= STS_W_DONT;
else
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
}
else
{
if (ent->client->movestate & STS_W_COMEPLAT)
{
k = 0;
if (ent->client->pers.routeindex - 1 > 0)
{
VectorCopy(ent->client->movtarget_pt, trmax);
trmax[2] = 0;
k = 1;
}
if (!k)
goto VCHCANSEL;
}
else
{
trmax[0] = (ent->client->waiting_obj->absmin[0] + ent->client->waiting_obj->absmax[0]) * 0.5;
trmax[1] = (ent->client->waiting_obj->absmin[1] + ent->client->waiting_obj->absmax[1]) * 0.5;
trmax[2] = 0;
}
VectorSubtract(trmax, ent->s.origin, temppos);
yaw = temppos[2];
temppos[2] = 0;
x = VectorLength(temppos);
if (x == 0)
goto VCHCANSEL; // if center position move cancel
if (x < dist)
dist = x;
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos);
}
}
// on train
else if (ent->client->movestate & STS_W_ONTRAIN)
{
i = 0;
if (ent->client->routetrace)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
if (ent->client->pers.routeindex - 1 >= 0)
{
if (Route[ent->client->pers.routeindex-1].state != GRS_ONTRAIN)
i = 1;
}
else
i = 1;
if (TraceX(ent, v))
{
if (v[2] - ent->s.origin[2] <= JumpMax)
i = 1;
else
ent->client->routelocktime = level.time + 1.5;
}
else
ent->client->routelocktime = level.time + 1.5;
}
else if (j || (ent->client->waiting_obj->s.origin[2] - ent->client->waiting_obj->s.old_origin[2] <= 0))
i = 1; //CW
if (i) //CW...
{
ent->client->movestate |= STS_W_DONT;
ent->client->movestate &= ~STS_WAITS;
}
else
{
k = 0;
if (ent->client->routetrace)
{
tr = gi.trace(ent->s.origin, NULL, NULL, v, ent, MASK_BOTSOLIDX);
if (tr.ent == ent->client->waiting_obj)
{
tr = gi.trace(v, NULL, NULL, ent->s.origin, ent, MASK_BOTSOLIDX);
if (tr.ent == ent->client->waiting_obj)
{
VectorSubtract(v, ent->s.origin, temppos);
k = 1;
}
}
}
if (!k)
{
VectorCopy(ent->client->waiting_obj->union_ent->s.origin, trmax);
trmax[2] += 8;
VectorSubtract(trmax, ent->s.origin, temppos);
yaw = temppos[2];
temppos[2] = 0;
x = VectorLength(temppos);
if (x < dist)
dist = x;
}
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos);
}
goto GOMOVE;
}
//wait for door open
else if (ent->client->movestate & STS_W_DOOROPEN)
{
if (!trace_priority || (ent->client->waiting_obj->moveinfo.state == PSTATE_TOP))
{
ent->client->movestate &= ~STS_WAITS;
ent->client->waiting_obj = NULL;
}
else if ((ent->client->waiting_obj->moveinfo.state == PSTATE_BOTTOM) || (ent->client->waiting_obj->moveinfo.state == PSTATE_UP))
{
VectorSubtract(ent->client->movtarget_pt, ent->s.origin, temppos);
temppos[2] = 0;
dist *= 0.25;
if ((VectorLength(temppos) < 10) || VectorCompare(ent->s.origin, ent->client->movtarget_pt))
{
if (!ent->client->waiting_obj->union_ent)
{
trmin[0] = (ent->client->waiting_obj->absmin[0] + ent->client->waiting_obj->absmax[0]) * 0.5;
trmin[1] = (ent->client->waiting_obj->absmin[1] + ent->client->waiting_obj->absmax[1]) * 0.5;
trmin[2] = (ent->client->waiting_obj->absmin[2] + ent->client->waiting_obj->absmax[2]) * 0.5;
}
else
VectorCopy(ent->client->waiting_obj->union_ent->s.origin, trmin);
trmin[2] += 8;
VectorSubtract(trmin, ent->s.origin, temppos);
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos);
if (trace_priority < TRP_ANGLEKEEP)
{
ent->s.angles[YAW] = ent->client->moveyaw;
ent->s.angles[PITCH] = Get_pitch(temppos);
}
goto VCHCANSEL;
}
else {
if (trace_priority < TRP_MOVEKEEP)
ent->client->moveyaw = Get_yaw(temppos);
if (!ent->client->waiting_obj->union_ent)
{
trmin[0] = (ent->client->waiting_obj->absmin[0] + ent->client->waiting_obj->absmax[0]) * 0.5;
trmin[1] = (ent->client->waiting_obj->absmin[1] + ent->client->waiting_obj->absmax[1]) * 0.5;
trmin[2] = (ent->client->waiting_obj->absmin[2] + ent->client->waiting_obj->absmax[2]) * 0.5;
}
else
VectorCopy(ent->client->waiting_obj->union_ent->s.origin, trmin);
trmin[2] += 8;
VectorSubtract(trmin,ent->s.origin, temppos);
if (trace_priority < TRP_ANGLEKEEP)
{
ent->s.angles[YAW] = Get_yaw(temppos);
ent->s.angles[PITCH] = Get_pitch(temppos);
}
}
}
}
//bot move to moveyaw
GOMOVE:
//jumping
if (!ent->groundentity && !ent->waterlevel)
{
if ((ent->velocity[2] > VEL_BOT_JUMP) && !(ent->client->movestate & STS_SJMASKEXW)) //CW
ent->velocity[2] = VEL_BOT_JUMP;
k = (ent->client->ps.pmove.pm_flags & PMF_DUCKED)?1:0;
for (x = 0; x < 90; x += 10)
{
dist = MOVE_SPD_RUN * ent->moveinfo.speed; //CW
yaw = ent->client->moveyaw + x;
if (yaw > 180)
yaw -= 360;
i = Bot_TestMove(ent, yaw, temppos, dist, &bottom); //CW
if (i)
{
if ((bottom <= 24) && (bottom > 0))
{
if (ent->velocity[2] <= 10)
{
VectorCopy(temppos, ent->s.origin);
break;
}
}
if (!ent->waterlevel && (ent->s.origin[2] > ent->s.old_origin[2]) && ent->client->routetrace
&& !(ent->client->movestate & STS_LADDERUP) && !(ent->client->movestate & STS_SJMASK) //CW
&& (ent->client->pers.routeindex + 1 < TotalRouteNodes)
&& (ent->velocity[2] >= 100) && (ent->velocity[2] < 100 + (ent->gravity * sv_gravity->value * 0.1)))
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
Get_RouteOrigin(ent->client->pers.routeindex+1, vv);
k = 0;
j = Bot_TestMove(ent, yaw, trmin, 16, &f1);
VectorSubtract(v, ent->s.origin, trmin);
if (vv[2] - v[2] > JumpMax)
k = 1;
else if (v[2] - ent->s.origin[2] > JumpMax)
k = 2;
else if (!TargetJump_Chk(ent, vv, 0) && (VectorLength(trmin) < 64))
{
if (TargetJump_Chk(ent, vv, ent->velocity[2]))
k = 1;
}
if (!j)
k = 0;
else
{
if ((f1 > 10) && (f1 < -10))
k = 0;
}
if (k)
{
if (k == 2)
VectorCopy(v, vv);
if (TargetJump(ent, vv))
{
VectorSubtract(vv, ent->s.origin, v);
ent->client->moveyaw = Get_yaw(v);
if (ent->velocity[2] > VEL_BOT_JUMP) //CW
ent->client->movestate |= STS_TURBOJ; //CW
if (k == 1)
ent->client->pers.routeindex++;
break;
}
}
}
if (bottom <= 0)
{
VectorCopy(temppos, ent->s.origin);
if (i == 2)
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
else
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
else
ent->moveinfo.speed = 0.3;
}
else
ent->moveinfo.speed = 0.3;
if (x == 0)
continue;
// left trace
yaw = ent->client->moveyaw - x;
if (yaw < -180)
yaw += 360;
i = Bot_TestMove(ent, yaw, temppos, dist, &bottom);
if (i)
{
if ((bottom <= 24) && (bottom > 0) && (ent->velocity[2] <= 10))
{
VectorCopy(temppos, ent->s.origin);
break;
}
// turbo
if (!ent->waterlevel && (ent->s.origin[2] > ent->s.old_origin[2])
&& ent->client->routetrace && !(ent->client->movestate & STS_LADDERUP) && !(ent->client->movestate & STS_SJMASK) //CW
&& (ent->client->pers.routeindex + 1 < TotalRouteNodes)
&& (ent->velocity[2] >= 100) && (ent->velocity[2] < 100 + (ent->gravity * sv_gravity->value * 0.1)))
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
Get_RouteOrigin(ent->client->pers.routeindex+1, vv);
k = 0;
j = Bot_TestMove(ent, yaw, trmin, 16, &f1);
VectorSubtract(v, ent->s.origin, trmin);
if (vv[2] - v[2] > JumpMax)
k = 1;
else if (v[2] - ent->s.origin[2] > JumpMax)
k = 2;
else if (!TargetJump_Chk(ent, vv, 0) && (VectorLength(trmin) < 64))
{
if (TargetJump_Chk(ent, vv, ent->velocity[2]))
k = 1;
}
if (!j)
k = 0;
else if ((f1 > 10) && (f1 < -10))
k = 0;
if (k)
{
if (k == 2)
VectorCopy(v, vv);
if (TargetJump(ent, vv))
{
VectorSubtract(vv, ent->s.origin, v);
ent->client->moveyaw = Get_yaw(v);
if (ent->velocity[2] > VEL_BOT_JUMP) //CW
ent->client->movestate |= STS_TURBOJ; //CW
if (k == 1)
ent->client->pers.routeindex++;
break;
}
}
}
if (bottom <= 0)
{
VectorCopy(temppos, ent->s.origin);
if (i == 2)
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
else
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
else
ent->moveinfo.speed = 0.3;
}
else
ent->moveinfo.speed = 0.3;
}
if (x >= 90)
{ // jump fail!
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] += ((random() - 0.5) * 360);
if (ent->s.angles[YAW] > 180)
ent->s.angles[YAW] -= 360;
else if (ent->s.angles[YAW] < -180)
ent->s.angles[YAW] += 360;
}
goto VCHCANSEL;
}
// on ground or in water
waterjumped = false;
if (ent->groundentity || ent->waterlevel)
{
if (ent->groundentity && (ent->waterlevel <= 0))
k = 1;
else if (ent->waterlevel)
{
k = 2;
if (ent->client->routetrace)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
VectorSubtract(v, ent->s.origin, vv);
vv[2] = 0;
if ((v[2] < ent->s.origin[2]) && (VectorLength(vv) < 24))
k = 0;
}
if (ent->waterlevel == 3)
k = 0;
}
else if (ent->waterlevel)
k = 0;
else
k = 1;
if (k)
{
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
k = 0;
}
f1 = (ent->client->waterstate) ? BOTTOM_LIMIT_WATER : -JumpMax;
if (ent->client->nextcheck < level.time + 1.0)
{
VectorSubtract(ent->client->my_old_origin, ent->s.origin, temppos);
if (VectorLength(temppos) < 64)
{
if (ent->client->routetrace)
{
if (!(int)chedit->value)
{
ent->client->routetrace = false;
ent->client->pers.routeindex++;
}
}
else
f1 = BOTTOM_LIMITM;
}
if (ent->client->nextcheck < level.time)
{
VectorCopy(ent->s.origin, ent->client->my_old_origin);
ent->client->nextcheck = level.time + 4.0;
}
}
f3 = 20; //movablegap
//this v not modify till do special
if (ent->client->routetrace)
Get_RouteOrigin(ent->client->pers.routeindex, v);
if (ent->waterlevel && ent->client->routetrace)
{
if (v[2] + 20 <= ent->s.origin[2])
{
f2 = 20;
f3 = 0;
}
else
f2 = JumpMax;
}
else
f2 = JumpMax;
ladderdrop = true;
for (x = 0; (x <= 180) && (dist != 0); x += 10)
{
// right trace
yaw = ent->client->moveyaw + x;
if (yaw > 180)
yaw -= 360;
if ((j = Bot_TestMove(ent, yaw, temppos, dist, &bottom)) > 0) //CW
{
if ((x == 0) && !ent->waterlevel && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED))
{
if (ent->client->routetrace)
{
if ((v[2] - (ent->s.origin[2] + bottom) > f2) || ((bottom > 20) && (v[2] > ent->s.origin[2])))
{
ladderdrop = false;
if (Bot_Fall(ent, temppos, dist) && !ent->client->waterstate)
{
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
if (v[2] - ent->s.origin[2] <= JumpMax)
{
if ((Route[ent->client->pers.routeindex].state == GRS_ONTRAIN) && (ent->client->waterstate < WAS_IN))
break;
if (ent->client->pers.routeindex > 0)
{
if ((Route[ent->client->pers.routeindex-1].state == GRS_ONTRAIN) && (Route[ent->client->pers.routeindex-1].ent == ent->groundentity))
break;
}
}
}
else if (ent->groundentity)
{
if (ent->groundentity->use == rotating_use)
{
if (Bot_Fall(ent, temppos, dist))
{
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
}
else if (Route[ent->client->pers.routeindex].state == GRS_ONROTATE)
{
if (!TraceX(ent, v))
break;
if (!HazardCheck(ent, v))
break;
if (!BankCheck(ent, v))
break;
if (Bot_Fall(ent, temppos, dist))
{
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
}
}
}
}
// jumpable 1
if ((bottom > 20) && (bottom <= f2) && (j == 1) && k && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED))
{
ent->moveinfo.speed = 0.15;
if (k == 1)
ent->velocity[2] = VEL_BOT_JUMP; //CW
else
{
ent->moveinfo.speed = 0.1;
// waterjumped
if ((ent->velocity[2] < VEL_BOT_WJUMP) || VectorCompare(ent->s.origin, ent->s.old_origin))
{
ent->velocity[2] = VEL_BOT_WJUMP;
ent->client->movestate |= STS_WATERJ;
}
goto VCHCANSEL;
}
SetBotAnim(ent);
ent->client->moveyaw = yaw;
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
//dropable1
else if ((bottom <= f3) && ((bottom >= f1) || ent->waterlevel))
{
if ((bottom < 0) && !ent->client->waterstate)
{
f2 = FRAMETIME * (ent->velocity[2] - (ent->gravity * sv_gravity->value * FRAMETIME));
if ((bottom >= f2) && (ent->velocity[2] < 0))
temppos[2] += bottom;
else
temppos[2] += f2;
}
if (!ent->tractored) //CW++
VectorCopy(temppos, ent->s.origin);
if (f1 > BOTTOM_LIMIT)
ent->moveinfo.speed = 0.25;
if (j != 1)
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
else
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
if ((x > 30) || !ent->client->routetrace)
{
f2 = ent->client->moveyaw;
ent->client->moveyaw = yaw;
if ((f2 == ent->s.angles[YAW]) && (trace_priority < TRP_ANGLEKEEP))
ent->s.angles[YAW] = yaw;
}
break;
}
// dropable?1
else if ((bottom < f1) && !ent->client->waterstate && (x <= 30))
{
if (ladderdrop && (bottom != -9999) && (ent->client->ground_contents & CONTENTS_LADDER))
{
VectorCopy(temppos, ent->s.origin);
ent->client->moveyaw = yaw;
ent->moveinfo.speed = 0.2;
goto VCHCANSEL;
}
if (ladderdrop && (bottom < 0) && !ent->client->waterstate)
{
if (Bot_moveW(ent, yaw, temppos, dist, &bottom))
{
iyaw = -41;
if ((bottom > -20) && (iyaw < -40))
{
VectorCopy(temppos, ent->s.origin);
break;
}
}
}
// fall!
if (Bot_Fall(ent, temppos, dist))
break;
}
}
if ((x == 0) && (ent->client->battlemode & FIRE_SHIFT))
ent->client->battlemode &= ~FIRE_SHIFT;
if ((x == 0) || (x == 180))
continue;
yaw = ent->client->moveyaw - x;
if (yaw < -180)
yaw += 360;
if ((j = Bot_TestMove(ent, yaw, temppos, dist, &bottom)) > 0) //CW
{
f2 = (ent->client->waterstate == WAS_FLOAT)?TOP_LIMIT_WATER:JumpMax;
if ((bottom > 20) && (bottom <= f2) && (j == 1) && k && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED))
{
ent->moveinfo.speed = 0.15;
if (k == 1)
ent->velocity[2] = VEL_BOT_JUMP;
else
{
ent->moveinfo.speed = 0.1;
// waterjumped
if ((ent->velocity[2] < VEL_BOT_WJUMP) || VectorCompare(ent->s.origin, ent->s.old_origin))
{
ent->velocity[2] = VEL_BOT_WJUMP;
ent->client->movestate |= STS_WATERJ;
}
goto VCHCANSEL;
}
SetBotAnim(ent);
ent->client->moveyaw = yaw;
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
break;
}
// dropable2
else if ((bottom <= f3) && ((bottom >= f1) || ent->waterlevel))
{
if ((bottom < 0) && !ent->client->waterstate)
{
f2 = FRAMETIME * (ent->velocity[2] - (ent->gravity * sv_gravity->value * FRAMETIME));
if ((bottom >= f2) && (ent->velocity[2] < 0))
temppos[2] += bottom;
else
temppos[2] += f2;
}
VectorCopy(temppos, ent->s.origin);
if (f1 > BOTTOM_LIMIT)
ent->moveinfo.speed = 0.25;
if (j != 1)
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
else
ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
if ((x > 30) || !ent->client->routetrace)
{
f2 = ent->client->moveyaw;
ent->client->moveyaw = yaw;
if ((f2 == ent->s.angles[YAW]) && (trace_priority < TRP_ANGLEKEEP))
ent->s.angles[YAW] = yaw;
}
break;
}
// dropable?2
else if ((bottom < f1) && !ent->client->waterstate && (x <= 30))
{
if (ladderdrop && (ent->client->ground_contents & CONTENTS_LADDER) && (bottom != -9999))
{
VectorCopy(temppos, ent->s.origin);
ent->client->moveyaw = yaw;
ent->moveinfo.speed = 0.2;
goto VCHCANSEL;
}
if (ladderdrop && (bottom < 0) && !ent->client->waterstate)
{
if (Bot_moveW(ent, yaw, temppos, dist, &bottom))
{
iyaw = -41;
if ((bottom > -54) && (iyaw < -40))
{
VectorCopy(temppos, ent->s.origin);
break;
}
}
}
// fall2
if (Bot_Fall(ent, temppos, dist))
break;
}
}
}
if (!ent->client->routetrace && !ent->client->current_enemy)
{
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = yaw;
}
if (x >= 70)
{
if (!ent->client->routetrace && (ent->client->current_enemy == NULL))
{
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = yaw;
}
else if (ent->client->routetrace)
{
k = 0;
if ((x > 90) && ent->groundentity)
{
if (ent->groundentity->use == train_use) //CW
k = 1;
}
else if ((x > 90) && (Route[ent->client->routeindex].state == GRS_ONTRAIN))
k = 1;
if (k && (trace_priority < TRP_ANGLEKEEP))
{
VectorCopy(tmp_org, ent->s.origin);
VectorCopy(tmp_vel, ent->velocity);
ent->s.angles[YAW] = tmp_yaw;
goto VCHCANSEL;
}
if (!k)
{
ent->velocity[2] += VEL_BOT_JUMP_NUDGE; //CW
if (++ent->client->routeindex >= CurrentIndex)
ent->client->routeindex = 0;
ent->client->routetrace = false;
}
}
}
if (ent->waterlevel && !waterjumped)
{
k = 0;
VectorCopy(ent->s.origin, temppos);
if (ent->client->routetrace)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
k = 2;
x = v[2] - ent->s.origin[2];
if (x > 13)
x = 13;
else if (x < -13)
x = -13;
if (x < 0)
{
if (Bot_Watermove(ent, temppos, dist, x))
{ // down
VectorCopy(temppos, ent->s.origin);
k = 1;
}
}
else if ((x > 0) && (ent->client->waterstate == WAS_IN) && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED))
{ // up
if (ent->velocity[2] < -10)
ent->velocity[2] = 0;
if (Bot_Watermove(ent, temppos, dist, x))
{
VectorCopy(temppos, ent->s.origin);
k = 1;
}
}
}
else if ((ent->air_finished - 2.0 < level.time) && (ent->client->waterstate == WAS_IN))
{
if (Bot_Watermove(ent, temppos, dist, 13))
{
VectorCopy(temppos, ent->s.origin);
k = 1;
}
else
k = 2;
}
if (k == 1)
Get_WaterState(ent);
if (ent->client->routetrace && (v[2] == ent->s.origin[2]))
k = 3;
if ((!ent->groundentity && !ent->client->waterstate && k && (ent->velocity[2] < 1)) || ((ent->client->waterstate == WAS_IN) && (ent->client->ps.pmove.pm_flags & PMF_DUCKED)))
{
if (Bot_Watermove(ent, temppos, dist, -7) && (k != 3))
VectorCopy(temppos, ent->s.origin);
}
if (ent->client->waterstate == WAS_IN)
ent->moveinfo.decel = level.time;
else if (!k)
{
if ((level.time - ent->moveinfo.decel > 4.0) && !ent->client->routetrace)
{
ent->velocity[2] = -200;
ent->moveinfo.decel = level.time;
}
}
if (ent->groundentity && (ent->waterlevel == 1))
{
VectorSubtract(ent->s.origin, ent->s.old_origin, temppos);
if (!temppos[0] && !temppos[1] && !temppos[2])
ent->velocity[2] += 80;
}
}
// not in water
else if (ent->client->routetrace && !dist)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
if (v[2] < ent->s.origin[2] - 20)
{
if (Bot_Watermove(ent, temppos, dist, -20))
VectorCopy(temppos, ent->s.origin);
}
}
}
// player check door and corner
if (!ent->client->routetrace && trace_priority && (random() < 0.2))
{
VectorCopy(ent->s.origin, v);
VectorCopy(ent->mins, touchmin);
touchmin[2] += 16;
VectorCopy(ent->maxs,touchmax);
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
touchmax[2] = 0;
else
v[2] += 20;
//right
if (random() < 0.5)
{
f1 = ent->client->moveyaw + 90;
if (f1 > 180)
iyaw -= 360;
f2 = ent->client->moveyaw + 135;
if (f2 > 180)
iyaw -= 360;
}
// left
else
{
f1 = ent->client->moveyaw - 90;
if (f1 < 180)
iyaw += 360;
f2 = ent->client->moveyaw - 135;
if (f2 < 180)
iyaw += 360;
}
yaw = DEG2RAD(f1);
trmin[0] = cos(yaw) * 128;
trmin[1] = sin(yaw) * 128;
trmin[2] = 0;
VectorAdd(v, trmin, trmax);
tr = gi.trace(v, NULL, NULL, trmax, ent, MASK_BOTSOLIDX);
x = tr.fraction;
yaw = DEG2RAD(f2);
trmin[0] = cos(yaw) * 128;
trmin[1] = sin(yaw) * 128;
trmin[2] = 0;
VectorAdd(v, trmin, trmax);
tr = gi.trace(v, NULL, NULL, trmax, ent, MASK_BOTSOLIDX);
if ((x > tr.fraction) && (x > 0.5))
ent->client->moveyaw = f1;
}
// push button
it_ent = NULL;
k = 0;
VectorCopy(ent->absmin, touchmin);
VectorCopy(ent->absmax, touchmax);
touchmin[0] -= 48;
touchmin[1] -= 48;
touchmin[2] -= 5;
touchmax[0] += 48;
touchmax[1] += 48;
if ((i = gi.BoxEdicts(touchmin, touchmax, touch, MAX_EDICTS, AREA_SOLID)) > 0) //CW
{
for (j = i-1; j >= 0; j--)
{
trent = touch[j];
if (trent->classname)
{
if (trent->use == button_use)
{
k = 1;
it_ent = trent;
break;
}
else if ((trent->use == door_use) || (trent->use == rotating_use))
{
if (!trent->targetname && !trent->takedamage)
{
if (ent->groundentity != trent)
{
k = 2;
it_ent = trent;
break;
}
}
}
}
}
}
// when touch the button
if ((it_ent != NULL) && (k == 1)) //CW
{
if (it_ent->use && (it_ent->moveinfo.state == PSTATE_BOTTOM) && !it_ent->health)
{
k = 0;
if (ent->client->routetrace && (ent->client->pers.routeindex - 1 > 0))
{
k = 1;
i = ent->client->pers.routeindex;
if (Route[i].state == GRS_PUSHBUTTON)
k = 0;
else if (Route[--i].state == GRS_PUSHBUTTON)
k = 0;
if (!k && (Route[i].ent == it_ent))
ent->client->pers.routeindex = i + 1;
else
k = 1;
}
if (!k && it_ent->target)
{
str = it_ent->target;
e = &g_edicts[(int)maxclients->value+1];
for (i = maxclients->value+1; i < globals.num_edicts; i++, e++)
{
if (!e->inuse || !e->targetname)
continue;
if (!stricmp(str, e->targetname))
{
if (e->classname[0] == 't')
{
if (e->use == trigger_relay_use)
{
if (e->target)
{
str = e->target;
e = &g_edicts[(int)maxclients->value];
i = maxclients->value;
continue;
}
}
}
else if (e->classname[0] == 'f')
{
it_ent->use(it_ent, ent, it_ent);
if ((e->use == door_use) || (e->use == rotating_use))
{
k = 0;
if (!ent->client->routetrace)
{
v[0] = (it_ent->absmin[0] + it_ent->absmax[0]) * 0.5;
v[1] = (it_ent->absmin[1] + it_ent->absmax[1]) * 0.5;
v[2] = (it_ent->absmin[2] + it_ent->absmax[2]) * 0.5;
VectorSubtract(it_ent->union_ent->s.origin, v, temppos);
VectorScale(temppos, 3, v);
VectorAdd(ent->s.origin, v, ent->client->movtarget_pt);
}
else
VectorCopy(ent->s.origin, ent->client->movtarget_pt);
if (fabs(e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2]) > JumpMax)
{
if (e->union_ent == NULL) //CW
{
it = item_navi3;
trent = G_Spawn();
trent->classname = it->classname;
trent->s.origin[0] = (e->absmin[0] + e->absmax[0]) * 0.5;
trent->s.origin[1] = (e->absmin[1] + e->absmax[1]) * 0.5;
trent->s.origin[2] = e->absmax[2] + 16;
trent->union_ent = e;
e->union_ent = trent;
SpawnItem3(trent, it);
}
else
{
trent = e->union_ent;
trent->solid = SOLID_TRIGGER;
trent->svflags &= ~SVF_NOCLIENT;
}
trent->target_ent = ent;
if (e->spawnflags & PDOOR_TOGGLE)
{
f1 = e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2];
k = 1;
}
else
{
f1 = e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2];
if (f1 > 0)
{
if ((e->moveinfo.state == PSTATE_BOTTOM) || (e->moveinfo.state == PSTATE_UP))
{
if (fabs(trent->s.origin[2] - ent->s.origin[2]) < JumpMax)
k = 1;
}
}
else
{
if ((e->moveinfo.state == PSTATE_BOTTOM) || (e->moveinfo.state == PSTATE_UP))
{
if (fabs(trent->s.origin[2] - ent->s.origin[2]) < JumpMax)
k = 1;
}
}
}
}
if (!k)
{
ent->client->waiting_obj = e;
ent->client->movestate &= ~STS_WAITS;
ent->client->movestate |= STS_W_DOOROPEN;
}
else
{
if (e->union_ent->s.origin[2] + 8 - ent->s.origin[2] > JumpMax)
{
ent->client->routetrace = false;
ent->client->movestate &= ~STS_WAITS;
}
}
break;
}
}
}
}
}
else if (!k)
it_ent->use(it_ent, ent, it_ent);
}
}
// when touch the door
else if ((it_ent != NULL) && (k == 2)) //CW
{
if (it_ent->moveinfo.state == PSTATE_BOTTOM)
{
if (it_ent->flags & FL_TEAMSLAVE)
it_ent->teammaster->use(it_ent->teammaster, ent, it_ent->teammaster);
else
it_ent->use(it_ent, ent, it_ent);
}
if (it_ent->moveinfo.state == PSTATE_BOTTOM)
{
VectorCopy(ent->s.origin, ent->client->movtarget_pt);
ent->client->waiting_obj = it_ent;
ent->client->movestate &= ~STS_WAITS;
ent->client->movestate |= STS_W_DOOROPEN;
if (it_ent->flags & FL_TEAMSLAVE)
{
trmin[0] = (it_ent->teammaster->absmin[0] + it_ent->teammaster->absmax[0]) * 0.5;
trmin[1] = (it_ent->teammaster->absmin[1] + it_ent->teammaster->absmax[1]) * 0.5;
trmax[0] = (it_ent->absmin[0] + it_ent->absmax[0]) * 0.5;
trmax[1] = (it_ent->absmin[1] + it_ent->absmax[1]) * 0.5;
temppos[0] = (trmin[0] + trmax[0]) * 0.5;
temppos[1] = (trmin[1] + trmax[1]) * 0.5;
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = Get_yaw(temppos);
}
else
{
trmax[0] = (it_ent->absmin[0] + it_ent->absmax[0]) * 0.5;
trmax[1] = (it_ent->absmin[1] + it_ent->absmax[1]) * 0.5;
VectorSubtract(trmax, ent->s.origin, temppos);
if (trace_priority < TRP_ANGLEKEEP)
ent->s.angles[YAW] = Get_yaw(temppos);
}
}
else if (it_ent->moveinfo.state == PSTATE_UP)
{
VectorCopy(ent->s.origin, ent->client->movtarget_pt);
ent->client->waiting_obj = it_ent;
ent->client->movestate &= ~STS_WAITS;
ent->client->movestate |= STS_W_DOOROPEN;
}
}
VCHCANSEL:
// ladder check
front = left = right = NULL;
k = 0;
if (ent->client->routetrace && (ent->client->pers.routeindex + 1 < TotalRouteNodes))
{
Get_RouteOrigin(ent->client->pers.routeindex+1, v);
if (v[2] - ent->s.origin[2] >= 32)
k = 1;
}
if (k && trace_priority && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED))
{
tempflag = 0;
VectorCopy(ent->mins, trmin);
VectorCopy(ent->maxs, trmax);
trmin[2] += 20;
// front
iyaw = ent->client->moveyaw;
yaw = DEG2RAD(iyaw);
touchmin[0] = cos(yaw) * 32;
touchmin[1] = sin(yaw) * 32;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, touchmax);
tr = gi.trace(ent->s.origin, trmin, ent->maxs, touchmax, ent, MASK_BOTSOLID);
front = tr.ent;
if (tr.contents & CONTENTS_LADDER)
tempflag = 1;
// upper
if (!tempflag && !ent->client->waterstate)
{
trmax[2] += 32;
tr = gi.trace(ent->s.origin, trmin,trmax, touchmax,ent, MASK_BOTSOLID);
if (tr.contents & CONTENTS_LADDER)
tempflag = 2;
}
if (!tempflag && ent->groundentity)
{
Get_RouteOrigin(ent->client->pers.routeindex, v);
v[2] = ent->s.origin[2];
tr = gi.trace(ent->s.origin, trmin, ent->maxs, v, ent, MASK_BOTSOLID);
if (tr.contents & CONTENTS_LADDER)
tempflag = 3;
}
// right
if (tempflag == 0)
{
iyaw = ent->client->moveyaw + 90;
if (iyaw > 180)
iyaw -= 360;
yaw = DEG2RAD(iyaw);
touchmin[0] = cos(yaw) * 32;
touchmin[1] = sin(yaw) * 32;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, touchmax);
tr = gi.trace(ent->s.origin, trmin, ent->maxs, touchmax, ent, MASK_BOTSOLID);
right = tr.ent;
if (tr.contents & CONTENTS_LADDER)
tempflag = 1;
}
//
if (tempflag == 0)
{
iyaw = ent->client->moveyaw - 90;
if (iyaw < -180)
iyaw += 360;
yaw = DEG2RAD(iyaw);
touchmin[0] = cos(yaw) * 32;
touchmin[1] = sin(yaw) * 32;
touchmin[2] = 0;
VectorAdd(ent->s.origin, touchmin, touchmax);
tr = gi.trace(ent->s.origin, trmin, ent->maxs, touchmax, ent, MASK_BOTSOLID);
left = tr.ent;
if (tr.contents & CONTENTS_LADDER)
tempflag = 1;
}
// found ladder
if (tempflag)
{
VectorCopy(tr.endpos, trmax);
VectorCopy(trmax, touchmax);
touchmax[2] += WORLD_SIZE; // was 8192
tr = gi.trace(trmax, trmin, ent->maxs, touchmax, ent, MASK_SOLID);
e = tr.ent;
k = 0;
VectorCopy(tr.endpos, temppos);
VectorAdd(tr.endpos, touchmin, touchmax);
tr = gi.trace(temppos, trmin,ent->maxs, touchmax, ent, MASK_BOTSOLID);
if (e && (e->use == door_use))
k = 1;
if ((!(tr.contents & CONTENTS_LADDER) || k))
{
if (!ent->tractored) //CW
{
ent->velocity[0] = 0;
ent->velocity[1] = 0;
}
if ((ent->client->moveyaw == iyaw) || ent->client->routetrace)
{ // on ladder
if (ent->client->moveyaw != iyaw)
ent->client->moveyaw = iyaw;
ent->s.angles[YAW] = ent->client->moveyaw;
if (tempflag != 3)
VectorCopy(trmax, ent->s.origin);
ent->client->movestate |= STS_LADDERUP;
ent->s.angles[YAW] = ent->client->moveyaw;
ent->s.angles[PITCH] = -29;
if (tempflag == 2)
{
ent->velocity[2] = VEL_BOT_JUMP;
SetBotAnim(ent);
ent->client->movestate |= STS_SJMASK;
ent->moveinfo.speed = 0;
}
else if (tempflag == 3)
{
ent->velocity[2] = VEL_BOT_JUMP;
SetBotAnim(ent);
ent->client->movestate |= STS_SJMASK;
ent->moveinfo.speed = MOVE_SPD_JUMP;
}
//CW++
else if(ent->waterlevel > 1)
ent->velocity[2] = VEL_BOT_WLADDERUP;
else
//CW--
ent->velocity[2] = VEL_BOT_LADDERUP;
}
else
{
ent->client->moveyaw = iyaw;
ent->s.angles[YAW] = ent->client->moveyaw;
}
}
}
}
VCHCANSEL_L:
// player sizebox and special duck set
if ((ent->client->battleduckcnt > 0) && ent->groundentity && (ent->velocity[2] < 10))
{
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
ent->client->battleduckcnt--;
}
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{ // ducked
ent->client->duckedtime = 0;
ent->maxs[2] = 4;
ent->viewheight = -2;
}
else
{ // not ducked
if (ent->client->duckedtime < 1)
ent->client->duckedtime += FRAMETIME;
ent->maxs[2] = 32;
ent->viewheight = 22;
}
VectorCopy(ent->s.angles, ent->client->v_angle);
if (ent->s.angles[PITCH] < -29)
ent->s.angles[PITCH] = -29;
else if (ent->s.angles[PITCH] > 29)
ent->s.angles[PITCH] = 29;
}
//==============================================
void Bot_Camp(edict_t *ent)
{
//CW++
// Reset camping flag once we've had time to move on from the last site.
if (level.time > ent->client->camptime + 5.0)
ent->client->camping = false;
//CW--
if (ent->client->camptime < level.time)
return;
VectorCopy(ent->client->lastorigin, ent->s.origin);
if (random() < 0.2) //CW
ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
// Don't camp if we're using Traps or C4.
if ((ent->client->pers.weapon == item_trap) || (ent->client->pers.weapon == item_c4)) //CW
ent->client->camptime = level.time;
}
//==============================================
void Bot_Think(edict_t *ent)
{
// Reset the bot's enemy info if the enemy has died.
if (ent->client->current_enemy)
{
if (!(ent->client->current_enemy->die && (ent->client->current_enemy->die == Trap_DieFromDamage))) //CW++
{
if (!G_ClientInGame(ent->client->current_enemy))
{
ent->client->battleduckcnt = 0;
ent->client->current_enemy = NULL;
ent->client->combatstate &= ~CTS_ENEM_NSEE;
ent->client->battlemode = FIRE_NULL;
}
}
}
// If the bot is dead, put it back into the game.
if (!G_ClientNotDead(ent))
{
ent->s.modelindex2 = 0;
ent->s.modelindex3 = 0; //CW++
ent->s.modelindex4 = 0; //CW++
ent->client->routetrace = false;
if (ent->client->respawn_time <= level.time)
{
ent->client->respawn_time = level.time;
PutClientInServer(ent);
}
ent->nextthink = level.time + FRAMETIME;
return;
}
// Perform general AI routines for the bot (movement, combat, chatting, camping, etc).
Bot_AI(ent);
if (ent->health < 25) //CW
ent->client->camptime = level.time;
//CW++
else if (ent->client->held_by_agm || ent->client->flung_by_agm || ent->client->thrown_by_agm)
ent->client->camptime = level.time;
else if ((int)sv_bots_camp->value && Bot[ent->client->pers.botindex].camper)
Bot_Camp(ent);
if ((int)sv_bots_chat->value && !ent->client->pers.muted)
RandomChat(ent);
gi.linkentity(ent);
G_TouchTriggers(ent);
// If the bot has a Personal Teleporter and is in trouble, use it to escape.
if (ent->client->pers.inventory[ITEM_INDEX(item_teleporter)])
{
if (ent->health < BOT_TELE_MINHEALTH)
{
if (ent->client->current_enemy && InSight(ent, ent->client->current_enemy))
{
vec3_t v;
float range;
VectorSubtract(ent->client->current_enemy->s.origin, ent->s.origin, v);
if ((range = VectorLength(v)) < BOT_TELE_ENEMYRANGE)
Use_Teleporter(ent, item_teleporter);
}
}
if (ent->velocity[2] < BOT_TELE_FALLSPEED)
{
if (TriggerHurtCheck(ent))
Use_Teleporter(ent, item_teleporter);
}
}
//CW--
ent->nextthink = level.time + FRAMETIME;
}
//Maj/Pon--