QuakeC from Scratch tutorials - Chapter 4: The golden mirror...
Created By: Ender
eMail: ender@admdev.com
Difficulty Scale: Hard

Well, it mightn't look like much, but we've come a long way. So far, we have the very most basic elements of Quake. A map, lighting, and Ambient sounds. But still, there is something missing. Something you can't yet see. It should be obvious what it is, as you havn't coded it in. But maybe not. After all, there is a -lot- you havn't coded in.
Player Animation. Something which we will shortly make conspicious by it's absence. In a few lines, I'm going to instruct you on adding a Chasecam to Quake. It's something you will be using a lot of, to make sure things are looking just right.

You are now going to make yet another directory, called 'effects', inside your Scratch\Source directory. Your new directory structure should look as follows:
          - Id1      
C:\quake |- Scratch -         / Weapons   / Entities
         |- Blah     \ Source-|----------- 
         |- Blah              \ Monsters  \ Effects
         |- Blah
          - Blah
Inside this, create a new file. Once again, you will want to add the customary headers. Call this file, 'Chasecam.QC' and place the following code inside it:

| Scratch                                      Http://www.admdev.com/scratch |
| Simple Chasecam. Follows player for above and back slightly. Impulse 100   |

.entity camera;             // Chasecam Entity
void() Chasecam_Update;      // From Chasecam.QC

void() Chasecam_On =
 self.camera = spawn();                       // Create a new Camera entity
 self.camera.classname = "camera";            // This is a Camera
 self.camera.movetype  = MOVETYPE_NONE;       // No movetype
 self.camera.solid     = SOLID_NOT;           // Not solid.
 setmodel(self.camera, "progs/s_bubble.spr");
 setsize(self.camera, '0 0 0', '0 0 0');      // Has no size.
 Chasecam_Update();                            // Update cam
 stuffcmd(self, "r_drawviewmodel 0\ncl_bob 0\ncl_rollangle 0\nv_kickpitch 0\nv_kickroll 0\n");

void() Chasecam_Off =
 stuffcmd(self, "r_drawviewmodel 1\ncl_bob 0.02\ncl_rollangle 2.0\nv_kickpitch 0.6\nv_kickroll 0.6\n");

void() Chasecam_Update =
local vector camera_origin;
 if (!self.camera) return;
 makevectors (self.v_angle);
 traceline (self.origin + self.view_ofs, self.origin + (self.view_ofs * 2) +
           (v_forward * -64) + (v_up * -6), 0, self);

 camera_origin_x = trace_endpos_x;
 camera_origin_y = trace_endpos_y;
 camera_origin_z = trace_endpos_z - self.view_ofs_z;
 camera_origin = camera_origin + trace_plane_normal;
 setorigin (self.camera, camera_origin);

void() ChaseCam_Toggle =    // Toggle Chasecam on/off
 if (!self.camera) {Chasecam_On();} else {Chasecam_Off();}
Add this file (Effects/Chasecam.QC) to the bottom of PROGS.SRC.
This is a rather basic chasecam, that works by creating a new entity and telling Quake to set this as the 'viewpoint' entity. The camera is then moved with the player, whenever Chasecam_Update is called.
There are four things left to do. Firstly, we need to precache s_bubble.spr, so load main.qc and put the lineL
with the other precache.
Next, we use a function called SetViewPoint in the Chasecam, to set the viewpoint entity. But this isn't a normal QuakeC function. It's something we will have to add.
Because this is a function related - vaugely - to entity management, it belongs in 'Internal.QC', so open this file. Add the following code at the end of it:

void(entity ent) SetViewPoint =  // Alter the Viewpoint Entity
 msg_entity = self;         // This message is to myself. 
 WriteByte(1, SVC_SETVIEW); // Network Protocol: Set Viewpoint Entity
 WriteEntity(1, ent);       // Write entity to clients.
This small function act's by using the Quake Network Protocol. SVC number 5, SVC_SETVIEW, is not defined in the usual defs.qc, but is in the one included with Scratch.
There are two other things left to do. Firstly, -when- is Chasecam_Update called? Thus far, only upon creating a camera. We need it to run every frame.
We also need it to run before the Quake physics. This isn't so important with this particular chasecam, but most Chasecam's generally affect the player's angles in such a way that they are required to run before Quake's physics.
If you can remember back to Chapter 1, this is where PlayerPreThink, in Client.QC, becomes useful. Save & Close Internal.QC, and open Client.QC.
Alter PlayerPreThink to read:
void() PlayerPreThink = {CheckImpulses(); Chasecam_Update();};
Also at the top of Client.QC, after the header, add the prototype:
void() Chasecam_Update;      // From Chasecam.QC
Remember from earlier that the Quake Compiler (QCC) doesn't know about this function yet, as Chasecam.QC comes later in the progs.src CheckImpulses? Yes. Now you come to the last point for this tutorial. All Quake players and coders know about impulses. They are those little things you type from the console to call special functions of Quake mod's.. and even.. *gasp* CHEAT!
They also need to be implemented in code, and better sooner than later :) Create yet another file, Impulses.QC, in the main source directory. A header, and (at this point) some simple code will suffice:

| Scratch                                      Http://www.admdev.com/scratch |
| Handle and execute "Impulse" commands - as entered from console.           |

void() CheckImpulses =
if (self.impulse == 100) {ChaseCam_Toggle();}   // If Impulse 100, toggle cam
self.impulse = 0;                              // Clear impulse list.
Very simple. Waiting impulses are help in self.impulse. Here we check the value of this. If it equals 100, turn on the chasecam. After the Impulse has been handled, we reset self.impulse to 0 so we can get the next Impulse command.
Save and close this file, then add it's entry into PROGS.SRC - Because this subroutine always calls functions from earlier in the program, and should only be called from Client.QC itself, it should ALWAYS remain at the bottom of PROGS.SRC.
Note that you will have to add another prototype for CheckImpulses at the top of Client.QC. You should be able to do that yourself by now :)
Compile, and assuming all is well you should be able to enter 'Impulse 100' from the console and the chasecam should activate.
See something wrong? Exactly... this makes way for our next tutorial..