Camera Placement and Use

I got the idea after reading the lavacam page. This ain't up to the quality of lavacam, but does allow modification so that you can have map entities that allow normal players to use them, al'la Duke nuke'em. I'm not going to tell you how to that, but you should be able to puzzle it out. Currently this will hide the player who is using the cameras. (This is partially designed to that when a large network game is run at my university, we can have a machine linked up to a projection screen). There is also the possibility for team cameras, so each team can check the cameras incomming to their base. Great for a team commander :). I'll be using bits of code from Psykotiks spycam, but you don't need to have done it. All the camera data will be stored in a file separate from the bsp. I'll also be copying stuff from the item placement tut as the principals I'm using are similar, the parsing will be different though.


First of all we need a few variable set up, including a structure to contain the camera information. The cameras I'm desiging will rotate a set amount in the x and y planes. This will be set at the console (The settings will be retained and reused unless reset). Create a file called camera.h (If you are lazy, just download the one in the resources section of the page). Copy the code below into it

typedef struct newcamera_s
{
        vec3_t         origin;
        vec3_t         angles;
        float          h_arc;
        float          h_speed;
        float          v_arc;
        float          v_speed;
        edict_t                *cam;
        struct  newcamera_s *next;
} newcamera_t;
extern newcamera_t   *newcamera_head;
extern qboolean     cam_erasing;
void Cmd_SetCam_f(edict_t *ent);
qboolean addcamera(edict_t *ent);
qboolean showcamera(vec3_t origin,vec3_t angles,float h_arc,float h_speed,float v_arc,float v_speed);
void Cmd_Clearcameras_f(edict_t *ent);
void Cmd_Undocamera_f(edict_t *ent);
void Cam_Die(edict_t *ent);
void CamThink(edict_t *ent);
void Cmd_Savecameras_f(edict_t *ent);
qboolean savecameradata(edict_t *ent);
qboolean loadcameradata(void);
void CamCycleHandler(edict_t *ent);

Now, as we are going to be using this within the client structre (so we know what camera a client is looking through), we will need to add camera.h to g_local.h just after #include "game.h" Just add #include "camera.h" on the line just after it.

Next , go to the end of the client structure (just after qboolean update_chase;and before the }) and add the code below. It should be fairly self explanitory.

// Camera stuff
        int     camtoggle;
        newcamera_t *camwatch;
        int     camcycle;
        int            h_arc;
        int            h_speed;
        int            v_arc;
        int            v_speed;

also just before the last } in g_local.h, paste in the folowing code

// Camera Stuff
        int            h_dir;
        int            v_dir;
        newcamera_t *camowner;

This allows a cam entity to link back to it's owner. It's used in the think for the cameras. The dirs are used to control the direction of travel for the cameras.


I'm not even going to tell you to paste the code for cameras.c into a file. Just download it. Read though it, it should be fairly easy to understand. Add it to your makefile. If you have done the item placement tutorial then you will hit a small problem, I'm using the same function in both this and the item placement , to deal with it , just protoype the function at the end of g_local.h (after the }) and delete one copy of it. That will make it work.

Add this code to the end of putclientinserver (p_client.c) Its just some initilization code

        ent->client->camwatch = NULL;
        ent->client->camtoggle = 0;
        ent->client->h_arc=50;
        ent->client->h_speed=5;
        ent->client->v_arc=50;
        ent->client->v_speed=5; 

Add the code below to ClientThink (again in p_client.c) It just freezes the player so they cant move while looking though a camera. Add it just after the if handling level.intermissiontime

if (ent->client->camwatch && ent->client->camtoggle)
        {
               memset (&pm, 0, sizeof(pm));
               client->ps.pmove.pm_type = PM_FREEZE;
               VectorCopy (client->camwatch->cam->s.angles, client->ps.viewangles);                      // not sure what the next 3 line are for. They were used in a 
               VectorCopy (pm.viewangles, client->v_angle);                               // similar piece of code. execution time will be barley touched 
               VectorCopy (pm.viewangles, client->ps.viewangles); 
               gi.linkentity (ent);
               return; // no movement while in cam
        }

Ok , next we need to change SV_CalcViewOffset (p_view.c)

comment out the code below

        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;

insert this code just below the code you commented out

 if ((!(ent->client->camwatch == NULL)) || ent->client->camtoggle)
{
VectorSet (v, 0, 0, 0); 
ent->client->ps.pmove.origin[0] = ent->client->camwatch->cam->s.origin[0]*8; 
ent->client->ps.pmove.origin[1] = ent->client->camwatch->cam->s.origin[1]*8; 
ent->client->ps.pmove.origin[2] = ent->client->camwatch->cam->s.origin[2]*8; 
VectorCopy (ent->client->camwatch->cam->s.angles, ent->client->ps.viewangles); 
} 
else 
{
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;
}

Add these commands to the end of the command parser in g_cmds.c just before the final else

        else if (Q_stricmp(cmd,"camwatch")==0){
               if (ent->client->camtoggle){
                       ent->client->camtoggle=0;
                       ent->client->camwatch=NULL;
               }
               else
               {
                       if (newcamera_head){
                               ent->client->camtoggle=1;
                               ent->client->camwatch=newcamera_head;
                       }
               }
        }
        else if (Q_stricmp(cmd,"camnext")==0){
               if (ent->client->camtoggle){
                       if (ent->client->camwatch->next==NULL)
                               {
                               ent->client->camwatch=newcamera_head;
                               }                      
                               else
                               {
                               ent->client->camwatch=ent->client->camwatch->next;
                               }
               }
               }
        else if (Q_stricmp(cmd,"camcycle")==0){       
               if (ent->client->camcycle)
                       ent->client->camcycle=0;
               else
                       ent->client->camcycle=1;
               }
        else if (edititems->value){
        if (Q_stricmp (cmd, "placecamera") == 0)
        {        
               if (addcamera(ent))      
               gi.cprintf(ent, PRINT_HIGH, "Camera placed at (%d,%d,%d)\n",(int)ent->s.origin[0], (int)ent->s.origin[1], (int)ent->s.origin[2]);
               else
               gi.cprintf(ent, PRINT_HIGH, "Could not place camera\n",cmd);
        
               return; // We messed up cmd
        }
        else if (Q_stricmp (cmd, "setcam") == 0)
        {        
               Cmd_SetCam_f(ent);
        }
        else if (Q_stricmp (cmd, "clearcameras") == 0)
        {
               Cmd_Clearcameras_f(ent);
        }
        else if (Q_stricmp (cmd, "undocameras") == 0)
        {
               Cmd_Undocamera_f(ent);
        }
        else if (Q_stricmp (cmd, "erasecameras") == 0)
        {
               if (cam_erasing)
               {
                   cam_erasing=0;
                   gi.cprintf(ent, PRINT_HIGH, "New item eraser disabled!\n");
               }
               else
               {
                   cam_erasing=1;
                   gi.cprintf(ent, PRINT_HIGH, "Camera eraser enabled!\nCareful where you walk!\n");
               }
        }
        else if (Q_stricmp (cmd, "savecameras") == 0)
        {
               Cmd_Savecameras_f (ent);
        }
        }

There are 2 cvars you will need to add to the code edititems and camcycletime. the first controls if you can add cameras (1 means you can 0 means you cant). camcycletime controls how long you spend on each camera before moving to the next. find the line which reads extern cvar_t *sv_maplist;[in g_local.h) then add these lines just below it

extern  cvar_t  *camcycletime;
extern  cvar_t  *edititems;

Next open up g_mainc.c and just after the line which reads cvar_t *sv_maplist; add the lines below

cvar_t *camcycletime;
cvar_t  *edititems;

Open up g_save.c and find the line filterban = gi.cvar ("filterban", "1", 0); Add the lines below just under it

camcycletime=gi.cvar("camcycletime","50",0);
edititems = gi.cvar("edititems", "0", CVAR_SERVERINFO|CVAR_LATCH);

Thats the cvars added. Just change them at the console.I suppose you could have camcycletime being part of the client structure instead of a cvar. I just prefer it as a cvar.


add the line below to the end of SpawnEntities function in g_spawn.c

loadcameradata();

Also , add the line below to SP_worldspawn (g_spawn.c) just before the end

newcamera_head=NULL;

To make the camera cycling work you need to call the function that check and increnets the timer. add the line below to the end of ClientBeginServerFrame (p_client.c)

CamCycleHandler(ent);

.

Finally, create a directory called config within your mod directory. The camera files will be stored there.


To add a camera, use noclip to get to where you want it to be. then type placecamera at the console to place a camera pointing where you are facing. Shoot them to get rid of them (would have it being touch them , but then you would have to be flying. you can't touch when noclip is on) all the commands should be self explanetory. Use a negative h_speed to make the camera go left then right.


Resources

  1. cameras.c and camera.h: The two new files for your source
  2. Some sample camera files: when I get round to it

Skunkworks Tutorials