
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);
+ }