Title: Player Trail Functions
Difficulty: Moderate
By: Philip (aka Maj.Bitch) 
Email: peblair@inreach.com 
Date: 1-4-99
Note: Please give credit where credit is due. 
======================================================

Here is some source which I put into my bodyguard code but
NEVER USED IT.  Its been garnished from another bot source
with minor changes here and there a bit.  I initially put it 
into my BitchBot source so that if I couldn't get the bot's AI 
to function without following cookie-crumbs then I'd revert to 
this as a backup.  Because I have not utilized this code, it is 
provided for example purposes only..

Again, most of it has been relatively untested and you'll also
have to tailor this code to make it work with the particular
setup of your bot's AI..  Hope this helps other bot developers!

Okay, Let's get started.

=======================================================

Put these new vars into the bottom of your edict_s struct.

//---- Trail Node Vars ------//
  float    nodelook;
  int      nodeorder;
  float    nodeplacetime;
  int      nodenum;
  int      crtype;
  edict_t  *currnode;
  edict_t  *touchednode;
  edict_t  *nextentnode;
  edict_t  *preventnode;


=======================================================

Now, paste all of these new routines into the bottom of 
your p_trail.c file..


--------------------- CUT HERE ------------------------

//=======================================================
//============ NEW BOT NODE FUNCTIONS ===================
//=======================================================

//=======================================================
void PlayerTrail_ShowTrail(void) {
int marker, prev, n;

  if (!trail_active) return;

  prev = trail_head;
  for (marker = trail_head, n = TRAIL_LENGTH; n; n--) {
    if (marker != prev)
      G_Spawn_Trails(TE_BFG_LASER, trail[prev]->s.origin, trail[marker]->s.origin, trail[prev]->s.origin);
    prev = marker;
    marker = NEXT(marker); }
}

//=====================================================
void NewNode(edict_t *ent) {
edict_t *node;

  node = G_Spawn();
  node->classname = "trailnode";
  node->nodenum = atoi(gi.argv(1));

  if (atoi(gi.argv(5)) == 1) {
    node->touch = NodeTouchDuck;
    node->crtype = 1; }
  else {
    node->touch = NodeTouchRun;
    node->crtype = 0; }

  node->solid = SOLID_TRIGGER;
  node->movetype = MOVETYPE_NONE;
  VectorSet(node->mins, -10, -10, -10);
  VectorSet(node->maxs, 10, 10, 10);
  node->s.origin[0] = atoi(gi.argv(2));
  node->s.origin[1] = atoi(gi.argv(3));
  node->s.origin[2] = atoi(gi.argv(4));
  gi.linkentity(node);

  nodes = node->nodenum;
  node->nextentnode = NULL;
  node->preventnode = NULL;
  node->think = NodeThink;
  node->nextthink = level.time + 0.1;
}

//==============================================================
void NodeTouchDuck(edict_t *node, edict_t *bot, cplane_t *plane, csurface_t *surf) {

  if (!bot->isabot) return;

  if (bot->currnode == node && !visible(bot, bot->enemy))
    bot->touchednode = node;

  bot->is_crouching = true;
}

//==============================================================
void NodeTouchRun(edict_t *node, edict_t *bot, cplane_t *plane, csurface_t *surf) {

  if (!bot->isabot) return;

  // bot must be a bot.
  if (bot->currnode == node && !visible(bot, bot->enemy))
    bot->touchednode = node;

  bot->is_crouching = false;
}

//==============================================================
char *vtos2(vec3_t v) {
static int index;
static char str[8][32];
char *s;

  // use an array so that multiple vtos won't collide
  s = str[index];
  index = (index+1)&7;
  Com_sprintf(s, 32, "%i %i %i", (int)v[0], (int)v[1], (int)v[2]);

  return s;
}

//==============================================================
void WriteNodeInfo(edict_t *node) {
FILE *fp ;
cvar_t *game_dir;
int i;
char filename[256];
char nodeinf[8192];

  game_dir = gi.cvar("game", "", 0);
  i =  sprintf(filename, ".\\");
  i += sprintf(filename + i, game_dir->string);
  i += sprintf(filename + i, "\\info\\%s.brb", level.mapname);

  if (node->nodenum == 1)
    strcpy(nodeinf, "");

  if (node->nodenum == 1)
    sprintf(nodeinf, "%s %d %s %d\nwait;wait\n", node->classname, node->nodenum, vtos2(node->s.origin), node->crtype);
  else if (node->nodenum == 35)
    sprintf(nodeinf, "%s%s %d %s %d\nwait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait\n", nodeinf, node->classname, node->nodenum, vtos2(node->s.origin), node->crtype);
  else if (node->nodenum == 70)
    sprintf(nodeinf, "%s%s %d %s %d\nwait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait\n", nodeinf, node->classname, node->nodenum, vtos2(node->s.origin), node->crtype);
  else
    sprintf(nodeinf, "%s%s %d %s %d\nwait;wait\n", nodeinf, node->classname, node->nodenum, vtos2(node->s.origin), node->crtype);

  if (nodes == 100) {
   fp = fopen(filename, "rwxd");
   fwrite(nodeinf, sizeof(nodeinf), 1, fp);
   fclose(fp); }
}

//==============================================================
void NodeThink(edict_t *node) {
edict_t *ent=NULL;
edict_t *ignore;
vec3_t point, dir, start, end;
trace_t tr;

  if (node->nextentnode) return;

  while ((ent = findradius(ent, node->s.origin, 500)) != NULL) {
    if (ent == node) continue;
    if (ent == node->owner) continue;
    if (Q_stricmp(ent->classname, "trailnode") != 0) continue;
    if (ent->nodenum != node->nodenum + 1) continue;
    node->nextentnode = ent;
    ent->preventnode = node;
    if (node->nextentnode) {
      WriteNodeInfo(node);
      break; }
    VectorMA(ent->absmin, 0.5, ent->size, point);
    VectorSubtract(point, node->s.origin, dir);
    VectorNormalize(dir);
    ignore = node;
    VectorCopy(node->s.origin, start);
    VectorMA(start, 2048, dir, end);
    while(1) {
      tr = gi.trace(start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
      if (!tr.ent) break;
      if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) {
        G_Spawn_Splash(TE_LASER_SPARKS, 4, node->s.skinnum, tr.endpos, tr.plane.normal, tr.endpos);
        break; }
      ignore = tr.ent;
      VectorCopy(tr.endpos, start); }
    G_Spawn_Trails(TE_BFG_LASER, node->s.origin, tr.endpos, node->s.origin); }

  node->nextthink = level.time + 1;
}

//==============================================================
void PlaceNode(edict_t *self) {
edict_t *node;

  node = G_Spawn();
  node->classname = "trailnode";
  if (self->client->ps.pmove.pm_flags & PMF_DUCKED) {
    node->touch = NodeTouchDuck;
    node->crtype = 1; }
  else {
    node->touch = NodeTouchRun;
    node->crtype = 0; }

  node->solid = SOLID_TRIGGER;
  node->movetype = MOVETYPE_NONE;
  VectorCopy (self->s.origin, node->s.origin);
  VectorCopy (self->s.angles, node->s.angles);
  node->s.origin[2] = node->s.origin[2] - 10;
  VectorSet (node->mins, -10, -10, -10);
  VectorSet (node->maxs, 10, 10, 10);
  gi.linkentity (node);

  nodes++;
  node->nodenum = nodes;
  node->nextentnode = NULL;
  node->preventnode = NULL;
  node->think = NodeThink;
  node->nextthink = level.time + 0.1;
}

//==============================================================
void timerthink(edict_t *timer1) {
  gi_bprintf(PRINT_HIGH, "Please wait, this will take a few seconds.\n");
  timer1->think = G_FreeEdict;
  timer1->nextthink = level.time + 0.1;
}

//==============================================================
void PlaceBotTrail(edict_t *self) {
cvar_t *game_dir;
int i;
char filename[256];
char execthis[128];
FILE *fp;
edict_t *timer1;

  maxnodes = 100;

  if (nodes >= maxnodes) return;

  if (self->nodeplacetime > level.time) return;

  if (checked != 1) {
    game_dir = gi.cvar ("game", "", 0);
    i =  sprintf(filename, ".\\");
    i += sprintf(filename + i, game_dir->string);
    i += sprintf(filename + i, "\\info\\%s.brb", level.mapname);
    fp = fopen(filename, "rwxd");
    if (fp) {
      gi_bprintf(PRINT_HIGH, "Level path data found, ");
      fclose(fp);
      sprintf(execthis, "exec info/%s.brb\n", level.mapname);
      G_StuffText(self, execthis, true);
      timer1 = G_Spawn();
      timer1->think = timerthink;
      timer1->nextthink = level.time + 0.3;
      checked = noplace = 1; }
    else
      checked = 1; }

  // Start placing nodes.
  if (noplace != 1) {
    PlaceNode(self);
    self->nodeplacetime = level.time + 1; }
}

//========================================================
edict_t *FindNode(edict_t *self) {
edict_t *node=NULL;

  while ((node = findradius(node, self->s.origin, 256)) != NULL) {
    if (Q_stricmp(node->classname, "trailnode") != 0) continue;
    if (!visible(self, node)) continue;
    break; }

  return node;
}

//========================================================
edict_t *FindNextNode(edict_t *self) {
edict_t *node=NULL;

  while ((node = findradius(node, self->s.origin, 1024)) != NULL) {
    if (node == self) continue;
    if (Q_stricmp(node->classname, "trailnode") != 0) continue;
    if (self->currnode == node) continue;
    if (self->touchednode == node) continue;
    if (self->currnode->nodenum != node->nodenum+1) continue;
    if (node->nodenum == 100) continue; // ???
    break; }

  return node;
}

//========================================================
edict_t *FindNewNode(edict_t *self) {
edict_t *node;

  while ((node = findradius(node, self->s.origin, 512)) != NULL) {
    if (self == node) continue;
    if (!node->nodenum > 0) continue;
    break; }

  return node;
}

//========================================================
void TravelToNode(edict_t *self) {
vec3_t nodeloc;
edict_t *goal;
float dist;
vec3_t v, v2;

  if (self->currnode == NULL) return;

  VectorSubtract(self->currnode->s.origin, self->s.origin, v);
  self->ideal_yaw = vectoyaw(v);
  VectorSubtract(self->s.origin, self->currnode->s.origin, v2);
  dist = VectorLength(v2);
  goal = self->currnode;

  if ((rand()&3)==1 || !SV_StepDirection(self, self->ideal_yaw, dist))
    if (self->inuse)
      SV_NewChaseDir(self, goal, dist);

  M_walkmove(self, self->s.angles[YAW], 1);

  VectorSubtract(self->currnode->s.origin, self->s.origin, nodeloc);
  VectorScale(nodeloc, 4, self->velocity);
  vectoangles(nodeloc, self->s.angles);
  self->s.angles[0] = 0;
  if (self->velocity[2] < -200)
    self->velocity[2] = -200;
  if (self->velocity[2] > 350)
    self->velocity[2] = 350;
}

---------------------- END HERE ----------------------

NOTE: These routines are not fully tested and you'll have 
to do some tailoring of this source to make it work in 
conjunction with the way you've got your bot's AI setup.

======================================================
======================================================
Be sure to prototype these functions in the bottom of 
your g_locals.h file:

// Trail node routines
edict_t *FindNextNode(edict_t *monster);
edict_t *FindNode(edict_t *monster);
edict_t *FindNewNode(edict_t *monster);
void NodeTouchRun(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void NodeTouchDuck(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void NodeThink(edict_t *self);
void TravelToNode(edict_t *self);
void NewNode(edict_t *ent);
void PlaceBotTrail(edict_t *self);


======================================================

Have Fun!


regards,
philip