Quake DeveLS - Flash Grenades

Author: NightHawk
Difficulty: Medium

Well, I bet everyone wonders why anyone would create or use a weapon that doesn't REALLY create any damage, but flash grenades could be a rather effective tool if one knows how to use them. Basically, when the grenade goes off anyone within a certain range will go "blind", that is to say their screen display will flash bright white and slowly fade. This, in combination with some fancy moving around, could be menacing.

1. Client Variables

First of all we need to create some variables in the gclient_t structure. At or around line 830 of "g_local.h", right at the end of the structure definitions, add:

        ...
        float           pickup_msg_time;
        float           respawn_time;           // can respawn when time > this

+       int             grenadeType;
+       float           blindTime, blindBase;
} gclient_t;
What are these, simple: two different fields. The "grenadeType" sets the type of the currently active grenade (I use that instead of a boolean because, in my version, I have six different types. Look for more tutorials soon!). The "blindTime" and "blindBase" sets the duration and delay of the blinding effect on client targets, which will be discussed earlier. At the end of the "g_local.h" file add the following lines to define our different grenade types:
+       #define         GRENADE_NORMAL          0
+       #define         GRENADE_FLASH                   1
2. Initialization

Even though other mechanisms within the DLL source don't make it necessary, I find myself in the habit of initializing all the data. So, in the "p_client.c" file, within the "PutClientInServer" function (around line 855?), add the following:

        ...
        VectorCopy (ent->s.angles, client->ps.viewangles);
        VectorCopy (ent->s.angles, client->v_angle);

+       client->grenadeType = GRENADE_NORMAL;
+       client->blindBase = 0;
+       client->blindTime = 0;
   
        if (!KillBox (ent))
        ...
3. Firing a Flash Grenade

Now we need to modify the "fire_grenade" and "fire_grenade2" in "g_weapon.c" functions so that they check for the type of grenade. In "fire_grenade" (approx. line 463)

        ...
        grenade->owner = self;
        grenade->touch = Grenade_Touch;
        grenade->nextthink = level.time + timer;
        grenade->think = Grenade_Explode;
        grenade->dmg = damage;
        grenade->dmg_radius = damage_radius;
+       if ((self->client) && (self->client->grenadeType == GRENADE_FLASH))
+       {
+               grenade->touch = Flash_Touch;
+               grenade->think = Flash_Explode;
+               grenade->classname = "flash_grenade";
+       }
+       else
                grenade->classname = "grenade";
        ...
... and modify the EXACT same lines as above that appear in the "fire_grenade2" function (approx. line 496).

4. Think and Touch

Ed's note: Now that sounds kinky !

Now we need to make the grenade "think" and "touch". For this, we need to search a given radius for each client, see if he's looking in the direction of the grenade, and "increment his blindness". The touching routine is literally identical to the "Grenade_Touch" routine.

The following is pretty much all "new" code. You could add this somewhere in "g_weapon.c" before the "fire_grenade" functions, or you could create a separate file.

        #define         FLASH_RADIUS                    200
        #define         BLIND_FLASH                     50      // Time of blindness in FRAMES
        
        void Flash_Explode (edict_t *ent)
        {
                vec3_t      offset, origin;
                edict_t *target;

                // Move it off the ground so people are sure to see it
                VectorSet(offset, 0, 0, 10);    
                VectorAdd(ent->s.origin, offset, ent->s.origin);

                if (ent->owner->client)
                        PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);

                target = NULL;
                while ((target = findradius(target, ent->s.origin, FLASH_RADIUS)) != NULL)
                {
                        if (target == ent->owner)
                                continue;       // You know when to close your eyes, don't you?
                        if (!target->client)
                                continue;       // It's not a player
                        if (!visible(ent, target))
                                continue;       // The grenade can't see it
                        if (!infront(target, ent))
                                continue;       // It's not facing it

                        // Increment the blindness counter
                      target->client->blindTime += BLIND_FLASH * 1.5;
                      target->client->blindBase = BLIND_FLASH;

                        // Let the player know what just happened
                        // (It's just as well, he won't see the message immediately!)
                      gi.cprintf(target, PRINT_HIGH, 
                                "You are blinded by a flash grenade!!!\n");

                        // Let the owner of the grenade know it worked
                      gi.cprintf(ent->owner, PRINT_HIGH, 
                                "%s is blinded by your flash grenade!\n",
                                target->client->pers.netname);
                }

                // Blow up the grenade
                BecomeExplosion1(ent);
        }

        void Flash_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
        {
                if (other == ent->owner)
                        return;

                // If it goes in to orbit, it's gone...
                if (surf && (surf->flags & SURF_SKY))
                {
                        G_FreeEdict (ent);
                        return;
                }

                // All this does is make the bouncing noises when it hits something...
                if (!other->takedamage)
                {
                        if (ent->spawnflags & 1)
                        {
                                if (random() > 0.5)
                                        gi.sound (ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb1a.wav"),
                                                1, ATTN_NORM, 0);
                                else
                                        gi.sound (ent, CHAN_VOICE, gi.soundindex("weapons/hgrenb2a.wav"),
                                                1, ATTN_NORM, 0);
                        }
                        else
                                gi.sound (ent, CHAN_VOICE, gi.soundindex("weapons/grenlb1b.wav"),
                                        1, ATTN_NORM, 0);
                        }
                        return;
                }

                // The ONLY DIFFERENCE between this and "Grenade_Touch"!!
                Flash_Explode (ent);    
        }
5. Blinding Effect

Next we need to make the blinding effect. For this, we need to tinker with the viewport's alpha channel. In the same way that a rebreather makes it look greenish, we want it to look solid white a nd gradually fade. So in "p_view.c" there's a function called "SV_CalcBlend". Around line 430 add...

        ...
        else if (contents & CONTENTS_WATER)
                SV_AddBlend (0.5, 0.3, 0.2, 0.5, ent->client->ps.blend);

+       if (ent->client->blindTime > 0)
+       {
+               float alpha = ent->client->blindTime / ent->client->blindBase;
+               if (alpha > 1)
+                       alpha = 1;
+               SV_AddBlend (1, 1, 1, alpha, ent->client->ps.blend);
+       }

        // add for powerups
        ...
Note that if blindTime is greater then blindBase, it will remain solid white.

6. Switching Grenades

Finally, we need to let the user change between the two types. First, we need to create a command function in "g_cmds.c". This is totally new:

        void Cmd_FlashGrenade_f(edict_t *ent)
        {
                if (ent->client->grenadeType == GRENADE_NORMAL)
                {
                        gi.cprintf(ent, PRINT_HIGH, "Flash grenades selected.\n");
                        ent->client->grenadeType = GRENADE_FLASH;
                }
                else
                {
                        gi.cprintf(ent, PRINT_HIGH, "Standard grenades selected.\n");
                        ent->client->grenadeType = GRENADE_NORMAL;
                }
        }
.. and then add it to the command listing. Near the end of "g_cmds.c", add...
        ...
        else if (Q_stricmp (cmd, "wave") == 0)
                Cmd_Wave_f (ent);
+       else if (Q_stricmp (cmd, "flash") == 0)
+               Cmd_FlashGrenade_f (ent);
        ...
And that's it! "Flash on"!!!

Addendum: one thing I added to my version of the above is that, when a person is blinded, it spins them around in a random direction. After all, let's face it: if you get a blinding, searing flash in the face, you're not just going to stand there. Therefore, to the above piece of code within "Flash_Explode", add...

        // Increment the blindness counter
        target->client->blindTime += BLIND_FLASH * 1.5;
        target->client->blindBase = BLIND_FLASH;
+       target->s.angles[YAW] = (rand() % 360); // Whee!

Tutorial by NightHawk .


This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for their great help and support with hosting.
Best viewed with Netscape 4