TUTORIAL 25 - Flashlight and Lasersight
by Drive C:
Since I have just finished and corrected the code for these and I see that no one else has written
a tutorial on this, I thought that I would.
To introduce, this tutorial will produce the effects of lasersight and flashlight when
implemented. The reason I have written this for BOTH flashlight AND lasersight is that
the basic code for the entity and movement are the same and its simpler to write it
once, right? You dont have to use both if you dont want to, its just here if you
need it.
We will be making changes all across the board here. Both cgame and game will be modified:
g_cmds.c
g_weapon.c
cg_main.c ->really minor
cg_ents.c
g.local.h ->really minor
cg_local.h ->really minor
bg_public.h ->really minor
Just one note before we begin. I am not big on making comments here in this tutorial, but please do.
Everywhere you modify the code, make a comment. You do not know (maybe you do) how helpful this can be,
so PLEASE do it->it might save you from doing a whole lot of things over again :).
1. Setting Things Up -> the minor things
We first have to declare some functions, variables, and types so lets do it!
A1. Goto g_local.h, line ~490 and insert below:
void Weapon_HookFree (gentity_t *ent);
void Weapon_HookThink (gentity_t *ent);
void Laser_Gen (gentity_t *ent, int type);
void Laser_Think( gentity_t *self );
A2. Goto g_local.h, line ~274 and insert below:
int lastKillTime; // for multiple kill rewards
qboolean fireHeld; // used for hook
gentity_t *hook; // grapple hook if out
gentity_t *lasersight; // lasersight OR flashlight if in use
// timeResidual is used to handle events that happen every second
// like health / armor countdowns and regeneration
int timeResidual;
B. Goto cg_local.h, line ~568 and insert below:
qhandle_t viewBloodShader;
qhandle_t tracerShader;
qhandle_t crosshairShader[NUM_CROSSHAIRS];
qhandle_t laserShader;
qhandle_t lagometerShader;
C. Goto cg_ents.c, line ~7 and insert:
static void CG_LaserSight( centity_t *cent );
D. Goto cg_main.c, line ~585 and insert below:
cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" );
cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" );
for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) {
cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) );
}
cgs.media.laserShader = trap_R_RegisterShader( "sprites/laser" );
cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" );
cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" );
E. Goto bg_public.h, line ~585 and insert below:
ET_TELEPORT_TRIGGER,
ET_INVISIBLE,
ET_GRAPPLE, // grapple hooked on wall
ET_LASER, // lasersight entity type
ET_EVENTS
What we have done here is: (A1) declared the laser spawner/despawner function. (A2) Added a subordinate entity
to gclient, so we can from gclient structure control the client's laser. (B)
created a new shader for our lasersight in cgame. (C) Declared the laser sight draw function. (D) Told cgame what cg.media to assign
to our shader. (E) created a new entity type ET_LASER.
2. Creating the Command
The first thing to do here is to give the client the ability to
turn on or off the laser. If you are making a mod, you will
most likely change this so that the client cannot simply
type "laser" in the console, rather make it a powerup or
something.
As we all remember from before: go into g_cmds.c down to line ~1220
to the else if statements, and insert this code so that typing "laser"
and "flashlight" in the console turn on/off the laser/flashlight.
else if (Q_stricmp (cmd, "gc") == 0)
Cmd_GameCommand_f( ent );
else if (Q_stricmp (cmd, "setviewpos") == 0)
Cmd_SetViewpos_f( ent );
else if (Q_stricmp (cmd, "laser") == 0)
Laser_Gen( ent, 1 );//1=Laser, 2=Flashlight
else if (Q_stricmp (cmd, "flashlight") == 0)
Laser_Gen( ent, 2 );
Basically, we have created two commands, which repond to console commands /laser and /flashlight, running
our Laser_Gen function with 1 and 2, signifying flashlight and laser, repsectively.
3. Create the Stupid Entity
Go into g_weapon.c, at the very bottom: you can put it anywhere, but I place it here because it kinda pertains to weapon:
/*
============
Laser Sight Stuff
Laser Sight / Flash Light Functions
============
*/
void Laser_Gen( gentity_t *ent, int type ) {
gentity_t *las;
int oldtype;
//Get rid of you?
if ( ent->client->lasersight) {
oldtype = ent->client->lasersight->s.eventParm;
G_FreeEntity( ent->client->lasersight );
ent->client->lasersight = NULL;
if (oldtype == type)
return;
}
las = G_Spawn();
las->nextthink = level.time + 10;
las->think = Laser_Think;
las->r.ownerNum = ent->s.number;
las->parent = ent;
las->s.eType = ET_LASER;
//Lets tell it if flashlight or laser
if (type == 2) {
las->s.eventParm = 2; //tells CG that it is a flashlight
las->classname = "flashlight";
}
else {
las->s.eventParm = 1; //tells CG that it is a laser sight
las->classname = "lasersight";
}
ent->client->lasersight = las;
}
void Laser_Think( gentity_t *self ) {
vec3_t end, start, forward, up;
trace_t tr;
//If Player Dies, You Die -> now thanks to Camouflage!
if (self->parent->client->ps.pm_type == PM_DEAD) {
G_FreeEntity(self);
return;
}
//Set Aiming Directions
AngleVectors(self->parent->client->ps.viewangles, forward, right, up);
CalcMuzzlePoint(self->parent, forward, right, up, start);
VectorMA (start, 8192, forward, end);
//Trace Position
trap_Trace (&tr, start, NULL, NULL, end, self->parent->s.number, MASK_SHOT );
//Did you not hit anything?
if (tr.surfaceFlags & SURF_NOIMPACT || tr.surfaceFlags & SURF_SKY) {
self->nextthink = level.time + 10;
trap_UnlinkEntity(self);
return;
}
//Move you forward to keep you visible
if (tr.fraction != 1) VectorMA(tr.endpos,-4,forward,tr.endpos);
//Set Your position
VectorCopy( tr.endpos, self->r.currentOrigin );
VectorCopy( tr.endpos, self->s.pos.trBase );
vectoangles(tr.plane.normal, self->s.angles);
trap_LinkEntity(self);
//Prep next move
self->nextthink = level.time + 10;
}
Well that will do all the stuff that creates, deletes, and moves our entity. The first function generates the entity
and sets it into thinking every 1/100 second, and it thinks in the bottom function by tracing the player's view,
and setting the laser's position at the end of the trace. Just some notes: I like to sneak variables into unused slots
whenever possible to prevent over-modification (like the weapon drop function -> there're no need for all those changes he made).
This is the reason why I have chosen s.eventparm to sneak whether its a lasersight or flashlight. Then, in cgame, we will be able
to differentiate and draw the thing.
4. Color it, Light it, Whatever
These are modifications to cgame so that an ET_LASER will either create a sprite, or a light, whichever it actually is.
To start, we have to modify the switch statement in cg_ents.c at about ln. 712.
case ET_SPEAKER:
CG_Speaker( cent );
break;
case ET_GRAPPLE:
CG_Grapple( cent );
break;
case ET_LASER:
CG_LaserSight( cent );
break;
}
}
This just tells us that when we have an ET_LASER centity, we will call CG_LaserSight, which will be responsible for actually adding
the graphic to the scene. At the bottom of cg_ents.c:
/*
==================
CG_LaserSight
Creates the laser
==================
*/
static void CG_LaserSight( centity_t *cent ) {
refEntity_t ent;
// create the render entity
memset (&ent, 0, sizeof(ent));
VectorCopy( cent->lerpOrigin, ent.origin);
VectorCopy( cent->lerpOrigin, ent.oldorigin);
if (cent->currentState.eventParm == 1)
{
ent.reType = RT_SPRITE;
ent.radius = 2;
ent.rotation = 0;
ent.customShader = cgs.media.laserShader;
trap_R_AddRefEntityToScene( &ent );
}
else {
trap_R_AddLightToScene(ent.origin, 200, 1, 1, 1);
}
}
This function is fairly simple. What it does first is that it differentiates the laser and flashlight.
If it is a lasersight, then it sets the refentity to a sprite with our shader. If not, it simply adds
a white light with intensity 200 (which turns out to be directly related to the radius of the spot). Basically,
this would be the end of the story except for one thing -> where we gonna get our shader?
5. Screw it, We're Done
Yeah I wish. Fortunately, you can go ahead and compile, cause we're done with the source. Now we have to make
1. your shader and 2. your graphic. This should be a piece of cake for all you shader people out there. If not,
then create a file in the /scripts directory with extension .shader (or append a preexisting shader). Here it is:
sprites/laser
{
cull disable
{
clampmap sprites/laser.jpg
blendfunc GL_ONE GL_ONE
}
}
Now we have to make our image. Fortunately, for all y'all who cant make any images, I have one ready for you:
For those who cannot read
the above shader, you won't know where to put it, and I'm not gonna say, you should be able to figure it out :).
Well guys, that's it. We're done, so go play your game with your laser sight, and make whatever modifications
you need to make, cause c-mon, you cant just copy and paste, you have to learn. So anyway, thanks for letting
me write this and I think maybe this will be worth something to someone. Oh, BTW, thanks to
Quake DeveLS for letting me look over the Q2 laser. It really made me see why mine wasnt working (dang linkentity).
Good Luck all coders -> Drive C:
|