Improved Chasecam Tutorial

 

Title: Improved Chasecam Tutorial

Difficulty: Medium

By: Stone Lance

Date: 9-18-98

 

*Thanks to James Williams - a.k.a: sATaN, and WarZone for the first two tutorials

 

*Note: This code was originally based on the Chasecam Tutorial by sATaN improved by WarZone. The cam track function has been totally improved, the restart function has been removed, I added a few new cmd functions, and added some code to the clientThink in p_client.

 

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

*New/Improved Features:

 

-New track function (no more getting stuck in walls)

-Camera works in the water (no more crashing)

-Zooming in and out

-Lowers camera when ducking

-New "unlock/lock" mode.  Unlock the camera then you can rotate the camera

around the

        player to any angle.

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

 

First Step:

  Open g_local.h and goto the end of gclient_s and add these lines (about line

835).

 

               int                                        weapon_sound;

 

               float                      pickup_msg_time;

 

               float                      respawn_time;                 // can respawn when time > this

 

   +  int chasetoggle;

   +  float chasedist1;

   +  edict_t *chasecam;

   +  edict_t *oldplayer;

 

} gclient_t;

 

Note:

  With the chasetoggle value we can figure if the cam shoud be off, on, turning off, or unlocked.

 

  Chasedist1: the zoom distance from the player.

 

Next:

  Now go down to the edict_s structure(around line 870)

 

               gitem_t                *item;                                 // for bonus items

 

               // common data blocks

               moveinfo_t                        moveinfo;

               monsterinfo_t    monsterinfo;

 

   +  float                             chaseAngle;

   +  float                             chasedist2;

};

 

Note:

  Chasedist2: current distance from player

 

Next:

  At the end of g_local.h add:

 

  extern void CheckChasecam_Viewent(edict_t *ent);

 

Next:

  In a new file, s_cam.c add:

 

#include "g_local.h"

 

void ChasecamTrack (edict_t *ent);

 

/*  The ent is the owner of the chasecam  */

void ChasecamStart (edict_t *ent) {

               /* This creates a tempory entity we can manipulate within this function */

               edict_t *chasecam;

 

               /* Tell everything that looks at the toggle that our chasecam is on and working*/

               ent->client->chasetoggle = 1;

 

               /* Make out gun model "non-existent" so it's more

               realistic to the player using the chasecam */

               ent->client->ps.gunindex = 0;

 

               chasecam = G_Spawn ();

               chasecam->owner = ent;

               chasecam->solid = SOLID_NOT;

 

               chasecam->movetype = MOVETYPE_FLYMISSILE;

               // Added by WarZone - Begin

               ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; // this turns off Quake2's inclination to predict where the camera is going,

                                                                                                

               // making a much smoother ride

               ent->svflags |= SVF_NOCLIENT; // this line tells Quake2 not to send the unnecessary info about the camera to other players

               // Added by WarZone - End

               /* Now, make the angles of the player model, (!NOT THE HUMAN VIEW!)

               be copied to the same angle of the chasecam entity */

               VectorCopy (ent->s.angles, chasecam->s.angles);

 

               /* Clear the size of the entity, so it DOES technically have a size,

               but that of '0 0 0'-'0 0 0'. (xyz, xyz). mins = Minimum size, maxs = Maximum size */

               VectorClear (chasecam->mins);

               VectorClear (chasecam->maxs);

 

               /* Make the chasecam's origin (position) be the same as the

               player entity's because as the camera starts, it will force

               itself out slowly backwards from the player model */

               VectorCopy (ent->s.origin, chasecam->s.origin);

 

               chasecam->classname = "chasecam";

               chasecam->prethink = ChasecamTrack;

               ent->client->chasecam = chasecam;

               ent->client->oldplayer = G_Spawn();

}

 

Note:

  Spawns the camera, and sets up all the values

 

Next:

  Continue on... :

 

/* Here, the "ent" is referring to the client, the player that owns the

chasecam, and the "opt" string is telling the function whether to totally get

rid of the camera, or to put it into the background while it checks if the

player is out of the water or not. The "opt" could have easily been a string,

and might have used less memory, but it is easier to have a string as it is

clearer to the reader */

void ChasecamRemove (edict_t *ent){

               if (ent->client->chasetoggle == 0) {

                              ent->client->ps.gunindex =

gi.modelindex(ent->client->pers.weapon->view_model);

                              ent->s.modelindex = ent->client->oldplayer->s.modelindex;

                              VectorClear (ent->client->chasecam->velocity);

 

                     // Added by WarZone - Begin

            ent->svflags &= ~SVF_NOCLIENT;

            // Added by WarZone - End

 

                              free(ent->client->oldplayer->client);

                              G_FreeEdict (ent->client->oldplayer);

                              G_FreeEdict (ent->client->chasecam);

               }

               else {

                              ent->client->chasetoggle = 2;

               }

}

 

Note:

  If the cam is on start zooming back in.  If the camera has finished zooming in

remove it.

 

Next:

  Continue on... :

 

/* The "ent" is the chasecam */

void ChasecamTrack (edict_t *ent) {

               /* Create tempory vectors and trace variables */

               trace_t tr;

               vec3_t spot2, headorg, angle;

               vec3_t forward, right, up;

               float dist;

 

               ent->nextthink = level.time + 0.100;

 

               VectorCopy(ent->owner->s.origin, headorg);

               if(!(ent->owner->client->ps.pmove.pm_flags & PMF_DUCKED))

                              headorg[2] += 25;

               else

                              headorg[2] += 10;

 

               if(ent->owner->client->chasedist1 <= 0)

                              ent->owner->client->chasedist1 = 80;

 

               VectorCopy(ent->owner->client->v_angle, angle);

               /* get the CLIENT's viewangle, and break it down into direction vectors, of

               forward, right, and up. VERY useful */

               AngleVectors (ent->owner->client->ps.viewangles, forward, right, up);

 

               dist = ent->chasedist2 / ent->owner->client->chasedist1;

 

               VectorScale(forward, -ent->owner->client->chasedist1, spot2);  // Find the max distance

               spot2[2] += 5.00;

               VectorScale(spot2, dist, spot2);  // Calculate the current distance

 

               VectorAdd(headorg, spot2, spot2);

               tr = gi.trace (headorg, NULL, NULL, spot2, ent->owner, true);

               VectorSubtract(spot2, headorg, spot2);

 

               VectorScale(spot2, tr.fraction - 0.05, spot2); // Scale the distance if the trace hit a wall

 

               VectorAdd(spot2, headorg, spot2);

               VectorCopy(spot2, ent->s.origin);

               VectorCopy(angle, ent->s.angles);

 

               /* Copy the position of the chasecam now, and stick it to the movedir variable,

               for position checking when we rethink this function */

               VectorCopy (ent->s.origin, ent->movedir);

 

               if(ent->owner->client->chasetoggle == 2){ // If the cam is supposed to turn off, zoom in

                              ent->chasedist2 -= 6;

                              if (ent->chasedist2 <= 0){ // If it has finished zooming in remove the camera

                                             ent->chasedist2 = 0;

                                             ent->owner->client->chasetoggle = 0;

                                             ChasecamRemove(ent->owner);

                              }

               }

               else if (ent->chasedist2 < ent->owner->client->chasedist1){

                              ent->chasedist2 += 6;

                              if (ent->chasedist2 > ent->owner->client->chasedist1)

                                             ent->chasedist2 = ent->owner->client->chasedist1;

               }

               else if (ent->chasedist2 > ent->owner->client->chasedist1){

                              ent->chasedist2 -= 6;

                              if (ent->chasedist2 < ent->owner->client->chasedist1)

                                             ent->chasedist2 = ent->owner->client->chasedist1;

               }

}

 

void Cmd_Chasecam_Toggle (edict_t *ent) {

               if (ent->client->chasetoggle > 0)

                              ChasecamRemove (ent);

               else

                              ChasecamStart (ent);

}

 

void Cmd_Chasecam_Zoom(edict_t *ent, char *opt) {

               if(!strcmp(opt, "out") && ent->client->chasetoggle > 0)

                              ent->client->chasedist1 += 10;

               else if(ent->client->chasetoggle > 0)

                              ent->client->chasedist1 -= 10;

 

               if(ent->client->chasedist1 <= 0)

                              ent->client->chasedist1 = 80;

 

               gi.bprintf(PRINT_HIGH, "Zoom Amount: %f\n", ent->client->chasedist1);

}

 

void Cmd_Chasecam_Viewlock(edict_t *ent) {

               if(ent->client->chasetoggle == 1)

                              ent->client->chasetoggle = 3;

               else if(ent->client->chasetoggle == 3)

                              ent->client->chasetoggle = 1;

               else if(ent->client->chasetoggle == 0){

                              ChasecamStart (ent);

                              ent->client->chasetoggle = 3;

               }

}

 

Note:

  The last three functions toggle the cam on/off, zoom in/out, and cam unlock/lock

 

Final Step in s_cam.c:

 

 

void CheckChasecam_Viewent (edict_t *ent) {

               // Added by WarZone - Begin

               gclient_t *cl;

               if (!ent->client->oldplayer->client) {

                              cl = (gclient_t *)

                              malloc(sizeof(gclient_t));

                              ent->client->oldplayer->client = cl;

               }

               // Added by WarZone - End

 

               if ((ent->client->chasetoggle >= 1) && (ent->client->oldplayer)) {

                              ent->client->oldplayer->s.frame = ent->s.frame;

                              /* Copy the origin, the speed, and the model angle, NOT

                                             literal angle to the display entity */

                              VectorCopy (ent->s.origin, ent->client->oldplayer->s.origin);

                              VectorCopy (ent->velocity, ent->client->oldplayer->velocity);

                              VectorCopy (ent->s.angles, ent->client->oldplayer->s.angles);

                              /* Make sure we are using the same model + skin as selected, as well

                                             as the weapon model the player model is holding. For customized

                                             deathmatch weapon displaying, you can use the modelindex2 for

                                             different weapon changing, as you can read in forthcoming tutorials */

                              // Added by WarZone - Begin

                              ent->client->oldplayer->s = ent->s;

                              // copies over all of the important player related information

                              // Added by WarZone - End

                              gi.linkentity (ent->client->oldplayer);

               }

}

 

Next:

  Now open up g_cmds.c and go to the end of the ClientCommand function

 

               else if (Q_stricmp (cmd, "wave") == 0)

                              Cmd_Wave_f (ent);

 

   +  else if (Q_stricmp (cmd, "chasecam") == 0)

   +          Cmd_Chasecam_Toggle (ent);

   +  else if (Q_stricmp (cmd, "camzoomout") == 0)

   +          Cmd_Chasecam_Zoom(ent, "out");

   +  else if (Q_stricmp (cmd, "camzoomin") == 0)

   +          Cmd_Chasecam_Zoom(ent, "in");

   +  else if (Q_stricmp (cmd, "camviewlock") == 0)

   +          Cmd_Chasecam_Viewlock(ent);

   +  else if (Q_stricmp (cmd, "camreset") == 0){

   +          if (ent->client->chasetoggle != 3 && ent->client->chasetoggle != 0){

   +                         ent->client->chasecam->chaseAngle = 0;

   +          }

   +  }

 

               else // anything that doesn't match a command will be a chat

                              Cmd_Say_f (ent, false, true);

}

 

Note:

  This adds the cam commands

 

Next:

  Open up p_weapon.c and go to the ChangeWeapon function (around line 175), add:

 

                              ent->client->ps.gunindex = 0;

                              return;

               }

 

               ent->client->weaponstate = WEAPON_ACTIVATING;

               ent->client->ps.gunframe = 0;

 

   +  if (!ent->client->chasetoggle)

                              ent->client->ps.gunindex =

gi.modelindex(ent->client->pers.weapon->view_model);

}

 

Next:

  Head down to SV_CalcViewOffset in p_view.c and change:

 

               VectorAdd (v, ent->client->kick_origin, v);

               // absolutely bound offsets

               // so the view can never be outside the player box

 

               if (v[0] < -14)

                              v[0] = -14;

               else if (v[0] > 14)

                              v[0] = 14;

               if (v[1] < -14)

                              v[1] = -14;

               else if (v[1] > 14)

                              v[1] = 14;

               if (v[2] < -22)

                              v[2] = -22;

               else if (v[2] > 30)

                              v[2] = 30;

               VectorCopy (v, ent->client->ps.viewoffset);

}

 

TO:

 

               VectorAdd (v, ent->client->kick_origin, v);

 

               // absolutely bound offsets

               // so the view can never be outside the player box

 

   +  if (!ent->client->chasetoggle){

                              if (v[0] < -14)

                                             v[0] = -14;

                              else if (v[0] > 14)

                                             v[0] = 14;

                              if (v[1] < -14)

                                             v[1] = -14;

                              else if (v[1] > 14)

                                             v[1] = 14;

                              if (v[2] < -22)

                                             v[2] = -22;

                              else if (v[2] > 30)

                                             v[2] = 30;

   +  }

   +  else {

   +          VectorSet (v, 0, 0, 0);

   +          if (ent->client->chasecam != NULL) {

   +                         ent->client->ps.pmove.origin[0] = ent->client->chasecam->s.origin[0]*8;

   +                         ent->client->ps.pmove.origin[1] = ent->client->chasecam->s.origin[1]*8;

   +                         ent->client->ps.pmove.origin[2] = ent->client->chasecam->s.origin[2]*8;

   +                         VectorCopy (ent->client->chasecam->s.angles,

ent->client->ps.viewangles);

   +          }

   +  }

 

               VectorCopy (v, ent->client->ps.viewoffset);

}

 

Also replace:

 

                              ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;

               }

               else

               {

                              // add angles based on weapon kick

 

WITH:

 

                              ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;

               }

               else if (ent->client->chasetoggle == 0)

               {

                              // add angles based on weapon kick

 

Notes: This last little bit is to stop the camera from bobing back and forth

 

Next Step:

  At the bottom of the file add this to the end of ClientEndSeverFrame:

 

               if (ent->client->chasetoggle >= 1)

                              CheckChasecam_Viewent(ent);

 

Next Step:

  Open up p_client.c and go to the begining of the respawn function, and add:

 

               // Added by WarZone - Begin

               if (self->client->oldplayer)

                              G_FreeEdict (self->client->oldplayer);

               if (self->client->chasecam)

                              G_FreeEdict (self->client->chasecam);

               // Added by WarZone - End

 

Next Step:

  Down to PutClientInServer, and add:

 

               ent->clipmask = MASK_PLAYERSOLID;

               ent->model = "players/male/tris.md2";

               ent->pain = player_pain;

               ent->die = player_die;

               ent->waterlevel = 0;

               ent->watertype = 0;

               ent->flags &= ~FL_NO_KNOCKBACK;

               ent->svflags &= ~SVF_DEADMONSTER;

   +  ent->svflags &= ~SVF_NOCLIENT;

}

 

Final Step:

  Go down near the bottom of ClientThink and change:

 

               else

               {

                              VectorCopy (pm.viewangles, client->v_angle);

                              VectorCopy (pm.viewangles, client->ps.viewangles);

               }

 

TO:

               else {

                              vec3_t angle;

                              VectorCopy (pm.viewangles, angle);

                              if (client->chasetoggle == 3){

                                             client->chasecam->chaseAngle = pm.viewangles[YAW] - client->v_angle[YAW];

                                             VectorCopy (client->oldplayer->s.angles, client->v_angle);

                              }

                              else if(client->chasetoggle > 0){

                                             angle[YAW] -= client->chasecam->chaseAngle;

                                             VectorCopy (angle, client->v_angle);

                              }

                              else {

                                             VectorCopy (pm.viewangles, client->v_angle);

                              }

                              VectorCopy (pm.viewangles, client->ps.viewangles);

               }

 

Note:

  This is the main code for the unlock/lock feature.

 

 

That's it all finished.  Now run Quake 2, and have fun.

 

Tutorial by Stone Lance, based off of the tutorial by sATaN and WarZone.

 

 

 

/*  Other problems with fixes  */

 

 

In p_client.c, in the ClientDisconnect section (around line 1360), add the

following:

 

ent->classname = "disconnected";

ent->client->pers.connected = "false";

 

+ if (ent->client->oldplayer) {

+             ent->client->oldplayer->s.modelindex=0;

+             G_FreeEdict(ent->client->oldplayer);

+ }

+ if (ent->client->chasecam)

+             G_FreeEdict(ent->client->chasecam);

 

playernum = ent-g_edicts-1;

 

Thanks to shade for this fix.

 

 

In p_client.c, in the player_die section, add the following:

 

  self->takedamage = DAMAGE_YES;

  self->movetype = MOVETYPE_TOSS;

 

+ if (self->client->chasetoggle > 0){

+             self->client->chasetoggle = 2;

+             ChasecamRemove(self);

+ }