Assimilation Tutorial #3: Guided Rockets


 

Posted by WarZone (209.223.137.*) on April 11, 1999 at 16:06:08:

Assimilation Tutorial #1
By: WarZone


This tutorial will allow you to add a new firing mode to your rocket launcher. In this secondary mode players will be able to control their rockets and guide them into targets! In order to use this code, you MUST credit to Assimilation in your documentation/web site, and link to Assimilation. This tutorials is meant for intermediate to advanced programmers -- ie. there isn't much explanation. Oh well, that's life kiddo.

Okay, let's get started. Open up g_locals.h and add the following variables to the end of the edict_t structure:


  edict_t   *chasetarget; //the rocket to guide
  int       charge; //acceleration of the rocket

Now add the following variables to client_persistant_t:


typedef struct
{
...
  qboolean  view_rocket; // okay to guide a rocket?
  qboolean  guided; //fire guided rockets?
} client_persistant_t;

Now open up g_weapon.c and add this code to the top of the file (after the #includes):


void guideThink (edict_t *self)
{
  vec3_t offset;
  vec3_t forward;
  vec3_t tvect;
  float dist;

  if (!self->owner || !self->owner->inuse)
    return;

  self->charge += 2;
  if (self->charge > 350)
    self->charge = 350;

  if (self->owner->chasetarget != self || !self->owner->inuse)
  {
    VectorCopy (self->velocity, tvect);
    VectorNormalize (tvect);
  }
  else
  {
    VectorSet (offset, 0, 0, self->owner->viewheight);
    VectorAdd (self->owner->s.origin, offset, tvect);
    VectorSubtract (tvect, self->s.origin, tvect);
    AngleVectors (self->owner->client->v_angle, forward, NULL, NULL);
    VectorScale (forward, 99999999, forward);
    VectorAdd (forward, tvect, tvect);
    VectorNormalize (tvect);
  }
  vectoangles (tvect, self->s.angles);
  VectorScale (tvect, self->charge + 350.0, forward);
  VectorCopy (forward, self->velocity);
  self->nextthink = level.time + FRAMETIME;
}

void Guided_Die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
  self->takedamage = DAMAGE_NO;
  self->nextthink = level.time + .1;
  self->think = rocket_explode;
}

Then skip down to fire_rocket() and add the this indicated code:


...
  rocket->touch = rocket_touch;
  rocket->nextthink = level.time + 8000/speed;
  rocket->think = G_FreeEdict;
  rocket->dmg = damage;
  rocket->radius_dmg = radius_damage;
  rocket->dmg_radius = damage_radius;
  rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
  rocket->classname = "rocket";

  if (self->client)
  {
    check_dodge (self, rocket->s.origin, dir, speed);
// new code starts here
    if (self->client->pers.guided)
    {
        self->chasetarget = rocket;
        VectorScale (dir, 350.0, rocket->velocity);
        rocket->nextthink = level.time + 0.2;
        rocket->radius_dmg = radius_damage * .9;
        rocket->dmg_radius = damage_radius * 1.2;
        rocket->think = guideThink;
        rocket->classname = "guided rocket";
        rocket->clipmask = MASK_SHOT;
        rocket->solid = SOLID_BBOX;
        rocket->ss_charge = 0;

        VectorSet(rocket->mins, -6, -6, -6);
        VectorSet(rocket->maxs, 6, 6, 6);
        rocket->mass = 1;
        rocket->health = 10;
        rocket->die = Guided_Die;
        rocket->takedamage = DAMAGE_YES;
        rocket->monsterinfo.aiflags = AI_NOSTEP;
    }
// new code ends here
....

Now open up p_view.c and add the indicated line to SV_CalcViewOffset():


void SV_CalcViewOffset (edict_t *ent)
{
        float          *angles;
        float          bob;
        float          ratio;
        float          delta;
        vec3_t    v;
//===================================
  UpdateRocketCam (ent); // new line!!
...

... now that was the easy part. Next on the agenda is to open up p_client.c and scroll down to player_die() and add the indicated line:


void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
  int  mod;

  VectorClear (self->avelocity);
  self->chasetarget = NULL; // New line!
...

Now open p_weapon.c and scroll down to the Weapon_Generic() function add add the indicated lines:


...
  if (ent->client->weaponstate == WEAPON_READY)
  {
    if ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK)
    {
     if (!ent->client->pers.view_rocket) // new line!!
     { // if (pers.view_rocket)   <---new line!!
       if ((!ent->client->ammo_index) || (ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
       {
         ent->client->ps.gunframe = FRAME_FIRE_FIRST;
         ent->client->weaponstate = WEAPON_FIRING;

         // start the animation
         ent->client->anim_priority = ANIM_ATTACK;
         if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
         {
           ent->s.frame = FRAME_crattak1-1;
           ent->client->anim_end = FRAME_crattak9;
         }
         else
         {
           ent->s.frame = FRAME_attack1-1;
           ent->client->anim_end = FRAME_attack8;
         }
       }
       else
       {
         if (level.time >= ent->pain_debounce_time)
         {
           gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
           ent->pain_debounce_time = level.time + 1;
         }
         NoAmmoWeaponChange (ent);
       }
     } // if (pers.view_rocket)  <---new line!!
    }
    else
    {
      ent->client->pers.view_rocket = false; //new line
      ent->chasetarget = NULL; //new line
...
  if (ent->client->weaponstate == WEAPON_FIRING)
  {
    if (!ent->client->pers.view_rocket) // new line
    { // new line
      for (n = 0; fire_frames[n]; n++)
      {
        if (ent->client->ps.gunframe == fire_frames[n])
        {
          if (ent->client->pers.weapon == FindItem ("rocket launcher") && ent->client->pers.guided) //new line
            ent->client->pers.view_rocket = true; //new line
          fire (ent);
          break;
        }
      }
    }
    else
    {
// new code starts here
      if (!(ent->client->buttons & BUTTON_ATTACK) && !(ent->client->oldbuttons & BUTTON_ATTACK))
      {
        ent->client->pers.view_rocket = false;
        ent->chasetarget = NULL;
      }
// new code ends here
...

WHEW! That was a real pain, but we're on the home stretch now.. open up p_view.c again and add this function to the top:


void UpdateRocketCam (edict_t *ent)
{
  vec3_t temp;

  if (!ent->client->pers.view_rocket || ent->deadflag)
    ent->chasetarget = NULL;

  if (ent->chasetarget && ent->chasetarget->inuse && !Q_stricmp(ent->chasetarget->classname, "guided rocket"))
  {
    AngleVectors (ent->chasetarget->s.angles, temp, NULL, NULL);
    VectorMA (ent->chasetarget->s.origin, 5, temp, temp);
    VectorClear (ent->client->ps.pmove.origin);

    temp[0] *= 8;
    temp[1] *= 8;
    temp[2] += 2;
    temp[2] *= 8;
    VectorCopy (temp, ent->client->ps.pmove.origin);

    ent->client->ps.gunindex = 0;
    VectorClear (ent->client->ps.viewoffset);
    return;
  }
  else
  {
    if (!ent->deadflag && ent->client->pers.weapon)
      ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
    ent->chasetarget = NULL;
  }
}

Now open up p_client.c and add this line to the very end of ClientThink():


...
  UpdateRocketCam (ent);
}

Then scroll up a bit and add this line right above ClientThink():


void UpdateRocketCam (edict_t *ent); // new line
/*
==============
ClientThink

This will be called once for each client frame, which will
usually be a couple times for each server frame.
==============
*/
void ClientThink (edict_t *ent, usercmd_t *ucmd)
{
...

All righty then, all that's left to do is add a command to enable the guided rockets. Open up g_cmds.c and add the indicated lines to ClientCommand():


void ClientCommand (edict_t *ent)
{
...
  else if (Q_stricmp(cmd, "switch_rockets") == 0)
    gi.cprintf(ent, PRINT_HIGH, (ent->client->pers.guided = !ent->client->pers.guided) ? "Guided rockets enabled.\n" : "Normal rockets enabled.\n");
  else // anything that doesn't match a command will be a chat
    Cmd_Say_f (ent, false, true);
}


Finally, we're done! Just compile and bind a key to "switch_rockets" to use your guided rockets.

Enjoy!
-- WarZone