QUAKE GAME SITE for Everyone Quake 1-Quake 2-Quake 3 Arena Game Site Bringing Back 1996

QUAKE on ANY Operating System Platform,LINUX,Symbian,MAC,Nokia,WINDOWS.QUAKE 1,QUAKE II ,QUAKE III ARENA. Updated Daily. Mods,Servers,Updates,Downloads,Patches,Editors,.bsp,.pk3,PsP,N64,PlayStation

                                                                      Page of Useful Commands

 r_drawentities 0
bind p "screenshot"
demomap map2.md2

keys.c: Mapped the '*' key to screenshot command since I didn't have it in macosx
    if (key == '*')
    {
        if (down)
            Cmd_ExecuteString("screenshot");
    }



C has no namespace so method names are prefixed by domain of activity:

    SCR_    Screen                 : Anything related to the screen (refresh, palette, ...)
    CL_     Communication Layer    : Client side code
    V_      View                   : Anything related to the view setting
    R_      Rendition              : Anything related to the rendition phase
    SV_     Server                 : Server client side


    
WinMain
{
    Qcommon_Init
    {
        COM_InitArgv                         // Just grab command line parameters, nothing to see with Microsoft COM
        Swap_Init                            // Setup bigEndian/LittleEndian conversion flags
        Cbuf_Init
        Cmd_Init                             // Register a set of commands via Cmd_AddCommand (cmdlist,exec,echo,alias,wait)
        Cvar_Init                            // Register further commands via Cmd_AddCommand (set,cvarlist)
        Key_Init                             // Set what keys press are monitored. Register further commmands (bind,unbind,unbindall,bindlist)
                        
        ...                                  // Register command(z_stats,error,quit)
        
        Sys_Init                             // Check OS version. Reject win32 "Quake2 doesn't run on Win32s"
        NET_Init                             // Init winsock variables, do not open the connection yet.
        Netchan_Init                         // Init a few variables.

        SV_Init                         
            SV_InitOperatorCommands          // Register further command via Cmd_AddCommand: Notice "load" command associated with  SV_Loadgame_f method
                                             // SV_Loadgame_f ultimately leads to a calls to SV_InitGameProgs, which assign ge variable a value, providing the engine with an implementation (see not about abstraction) (ge contain all the game methods). ge is assigned using Sys_GetGameAPI()
                                            
        CL_Init                              // Check if running as dedicated server.
        {
            Con_Init                         // Init console and register set of commands via Cmd_AddCommand
            VID_Init                         // Grab videos parameters and register set of commands via Cmd_AddCommand
            S_Init                           // Grab sounds parameters and register set of commands via Cmd_AddCommand
            V_Init                           // Grab rendition parameters (crosshair etc...) and register set of commands via Cmd_AddCommand
            M_Init                           // Register all menu parameters
            SCR_Init                         // Grab further rendition parameters and register set of commands via Cmd_AddCommand
            CDAudio_Init                     // Init CD Music player
            CL_InitLocal                     // Grab networl parameters and Register net commands via Cmd_AddCommand
            IN_Init                          // Grab control parameters (mouse/keyboard/joystick ) and register commands via Cmd_AddCommand
            FS_ExecAutoexec                  // Grab autoexec.cfg location, inject following command: "exec autoexec.cfg\n"
            Cbuf_Execute                     // Exec command from buffer "exec autoexec.cfg\n"
        }
    }

    while(1)
    {
        PeekMessage
        TranslateMessage
        DispatchMessage

        _controlfp                           // Set the floating point precision ...
                                             // Question: Why do that for every frames ? Probably because precision is altered during the loop execution.
                                             // Answer: The is no other call to this function. It appears that floating point precision remains the same during program execution.
        Qcommon_Frame
        {

            setjmp                           // Anything bad happen, the program will jump back here.
            Cbuf_Execute
            SV_Frame                         // Server frame
            {
                
                if (!svs.initialized)        // If playing over network, using a server somwhere, not the local one
                       return;

                       
                rand ()                      // What the fuck ?
                SV_CheckTimeouts
                SV_ReadPackets
                SV_CalcPings
                SV_GiveMsec
                SV_RunGameFrame
                {

                    ge->RunFrame             // ge variable is a struct game_export_t. It comes from gamex86.dll, and work as an abtraction layer. This is how quake2 mod were programmed, generating a new DLL. Quake2 engine did not see any difference between running id's original content and a fan's mod.
                                             // ge gets assigned in SV_InitGameProgs method from sv_game.c
                }
                SV_SendClientMessages
                SV_RecordDemoMessage
                Master_Heartbeat
                SV_PrepWorldFrame    
            }

            
            CL_Frame                          // Client frame
            {
                 if (dedicated->value)  
      // If running as a dedicated server, no need to perform the Client frame.
                       return;    
                
                IN_Frame                    //Mouse only, grab pointer
                CL_ReadPackets
                CL_SendCommand
                {

                    
                    Sys_SendKeyEvents ();  // get new key events
                    IN_Commands ();        // allow mice or other external controllers to add commands
                    Cbuf_Execute ();       // process console commands
                    CL_FixCvarCheats ();   // fix any cheating cvars
                    CL_SendCmd ();         // send intentions now

    // resend a connection request if necessary
                    CL_CheckForResend ();
                }
                CL_PredictMovement

                VID_CheckChanges              // Very neat: Allow to change the DLL (ref_soft.dll or ref_gl.dll) on the fly to change the rendering path.
                    VID_LoadRefresh           // This is the function actually performing the switch by redefining GetRefAPI.
                CL_PrepRefresh
                SCR_UpdateScreen
                {

                    //This method supports stereo rendering, up to two "frames" with slightly different POV can be rendered here.
                    for(int i=0 ; i numsurfaces, surf = r_worldmodel->surfaces + node->firstsurface; c ; c--, surf++)
                                                GL_RenderLightmappedPoly
                                                {
                                                    if (light is dynamic)
                                                    {
                                                        R_BuildLightMap
                                                        R_SetCacheState
                                                        qglTexSubImage2D
                                                    }
                                                }
                                        }
                                        DrawTextureChains ();
                                        R_BlendLightmaps ();
                                        R_DrawSkyBox ();
                                        R_DrawTriangleOutlines ();
                                    }
                                    R_DrawEntitiesOnList            //Rendere explosions, beam, lasers and players
                                    {
                                        //Drawn non transparent things, far to near:
                                        for (i=0 ; i < r_newrefdef.num_entities ; i++)
                                        {
                                            R_DrawAliasModel
                                                GL_DrawAliasFrameLerp    //Models vertices are interpolated lerp style
                                        }
                                        //Drawn transparent things, far to near:
                                        for (i=0 ; i < r_newrefdef.num_entities ; i++)
                                        {
                                            R_DrawAliasModel
                                                GL_DrawAliasFrameLerp    //Models vertices are interpolated lerp style
                                        }
                                    }
                                    R_RenderDlights
                                        for (i=0 ; i < r_newrefdef.num_dlights ; i++, l++)
                                            R_RenderDlight (l);
                                    R_DrawParticles
                                    R_DrawAlphaSurfaces
                                    R_Flash
                                }
                                R_SetLightLevel
                                R_SetGL2D
                            }    
                            SCR_AddDirtyPoint
                            SCR_AddDirtyPoint
                            SCR_DrawCrosshair
                        }
                        SCR_DrawStats // Health and Ammo icons
                        SCR_DrawNet
                        SCR_CheckDrawCenterString
                    
                        SCR_DrawPause
                        SCR_DrawConsole
                        M_Draw
                        SCR_DrawLoading

                        re.EndFrame();
                        
                         //From here we are NOT the rendition DLL anymore.
                    }
                    
                }
                S_Update
                CDAudio_Update
                CL_RunDLights
                CL_RunLightStyles
                SCR_RunCinematic
                SCR_RunConsole
                cls.framecount++;       
                                                         
            }    
            // Note here a lot of time delta are maintained (all,sv,cl,gm,rf)    

        }
    }
}



//John Carmack did Not really bother with cache miss and indirection
Little/Big endian function pointer for conversion

Quake2 BSP format(Gold):
http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
http://jheriko-rtw.blogspot.com/2010/11/dissecting-quake-2-bsp-format.html
RADIOSITY: This article is FUCKING GOOOOLD http://freespace.virgin.net/hugo.elias/radiosity/radiosity.htm !!


Quake2 Model format:
http://icculus.org/~phaethon/q3a/formats/md2-schoenblum.html
10 keyframes per seconds fixed (http://en.wikipedia.org/wiki/Id_Tech_3).

MQuake 2 can be run in both software-rendering and in hardware accelerated mode, for the later you need a OpenGL compatible graphics card in addition to id Softwares official specs below.
Minimum requirements:
A 100% Windows 95/98/ME/NT 4.0/2000-compatible computer system (including compatible 32-bit drivers for CD-ROM drive , video card, sound card and input devices)
Pentium? 90 MHz processor (133 recommended) or higher or Athlon? processor
16MB RAM required for Windows 95/98, 24MB required for all other supported operating systems
Quad-speed CD-ROM drive (600 K/sec. sustained transfer rate)
100% Microsoft?-compatible mouse and driver
25MB of uncompressed hard disk space for game files (Minimum Install), plus 45MB for the Windows swap file
Supports network and Internet play via IPX and TCP/IP

Michael abrash: Quake 2 levels are taking up to an hour to process. (Note, however, that that includes BSPing, PVS calculations, and radiosity lighting, which I’ll discuss later.)
Michael abrash: The most interesting graphics change is in the preprocessing, where John has added support for radiosity lighting;

Tried to build, error found:

winquake.h(26) : fatal error C1083: Cannot open include file: 'dsound.h': No such file or directory

Add to add manually dsound.h reference from DirectX SDK.

Build fine after that.

Starting exploring via the quake2 project:

Just like for Quake 1, WinMain is in sys_win.c

We can find the DLL API acquirment in Sys_GetGameAPI

Main game loop calls method Qcommon_Frame

Abstraction layer in Quake2: Interface design pattern in pure C.

    Rendition now is abstracted, Quake engine doesn't know what is the rendering path, only uses a refexport_t struct to call function pointers. The rendition is still based on BSP/PVS
    According to abrash book, I was expecting Portal mecanisms. PHS (Potentialy Hearable Set), is used for sound sent from server to client. This significantly reduced the bandwidth consumption
    as the time, allowing multiplayer game to host 32 max players instead of 16.
    
    
    Game content is also abstracted via a ge variable, no more Quake C, people not have to compile a DLL (game86x.dll), type game_export_t. Commercial version of Quake2 loaded game_export_t via pointer to gamex86.dll. But quake2 also shipped with source code to generate
    a new DLL, enabling fans to produce MODs.
    
    

    
Command design patterns
Floating point precision alteration at runtime
md2: linear frame interpolation. quake models had 250 faces.  Quake2: Our models run from 400 to 700 with the bosses around 2000 faces.
The explosions are now models.    
Import game_import_t also provides TagMalloc and TagFree for memory managment
Colored DYNAMIC lights (Romero "Masters of doom")    


Piece of history here: R_RenderDlight This is the method responsible of the DYNAMIC LIGHTS BLEND RENDERING. The piece of code that allegibly prompted John Romero to switch Daikatana to use the Quake 2 engine


Question to answer: Does a game.dll can add entities to the list sent to the renderer ?

Sometimes parameters are passed to functions via global variables instead of regular parameter: WTF ?! Is it because of the C/Assembly optimization
portabilty ?


WTF is a bmodel anyway ?
    http://developer.valvesoftware.com/wiki/Source_BSP_File_Format
    A Model, in the terminology of the BSP file format, is a collection of brushes and faces, often called a "bmodel" ??
RLY ??!!

Entities are added and cleared on a per Scene basis (V_ClearScene,V_AddEntity)
CL_AddViewWeapon also calls V_AddEntity



TODO:

        - Find out how the Server's frequency is maintained to 10Hz (time = sv.framenum * 100 msec).
                * Q: Does it mean server time and realtime are not the same ?
                * A: Yes, It does.
                


LIGHTMAPS LOADING:
------------------
Lightmap have 4 mipmapping levels ?(MAXLIGHTMAPS) WRONG: Only textures have 4 mipmap
Lightmap totat byte size is 4 * 128 * 128


From http://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml
Lightmap Lump


Static lighting is handled in the Quake environment using lightmaps,
which are low resolution bitmaps that are combined with the texture
when rendering a face. Lightmaps are unique to a face (unlike textures
which may be used on multiple faces). The maximum size for any lightmap
is 16x16 which is enforced by the BSP creation tool by splitting faces
which would require larger lightmaps. In Quake 1 lightmaps were limited
to grayscale, however Quake 2 allows for 24-bit true color lighting.

Lightmaps are applied either through multi-texturing or multi-pass rendering
and should be multiplied with the texture for a face when rendering.

The lightmap lump contains the data for all of the lightmaps in the world
stored consecutively. Faces store the offset of their lightmap in this lump
in the bsp_face structure's lightmap_offset. The actual lightmap data is
stored in a 24-bits-per-pixel RGB format in a left-right, top-down order
starting at this offset.

The width and height of a lightmap isn't explicitly stored anywhere and
must be computed. If max_u is the maximum u component of all of the texture
coordinates for a face, and min_u is the minimum u component of all the texture
coordinates for a face (and similarly for the max_v and min_v), then the width
and height of the lightmap for the face is given by the computation:

lightmap_width  = ceil(max_u / 16) - floor(min_u / 16) + 1
lightmap_height = ceil(max_v / 16) - floor(min_v / 16) + 1

Previous formula is WRONG:  The maximum size for any lightmap is 17x17



VRAM Consomption:
Textures, 12045 KB, 267units
Lightmap textures: 17textures at 128*128: 48Kb/texture = 816KB
Seems the maximum amount of VRAM consumed is 128 * 128 * 128 * 4 (MAX_LIGHTMAPS * BLOCK_HEIGHT * BLOCK_WIDTH * LIGHTMAP_BYTES)


Mod_LoadFaces
    GL_BeginBuildingLightmaps
    for ( surfnum=0 ; surfnum < count ; surfnum++, in++, out++)
    {
        CalcSurfaceExtents
        GL_CreateSurfaceLightmap
        {
            LM_AllocBlock            // Get a texture id from the pre-allocated set
            LM_UploadBlock            // Upload a lightmap texture
            LM_InitBlock
            R_SetCacheState
            R_BuildLightMap
        }
        GL_BuildPolygonFromSurface
    }
    GL_EndBuildingLightmaps ();
    
    
The Quake 2 palette is saved in the file baseq2/pics/colormap.pcx of your Quake 2 installation.  (open it with GIMP, photoshop poops in your shoes and display greyscale)    

OpenGL quake doesn't use GenTextures ? It uses it's OWN textureID tracking because programmed against openGL 1.0: That's why this is used:
#define    TEXNUM_LIGHTMAPS    1024
#define    TEXNUM_SCRAPS        1152
#define    TEXNUM_IMAGES        1153
#define        MAX_GLTEXTURES    1024


OpenGL renderer is a nice trick. Because there is no palette available, the color switch is incorporated in the lightmap !! The multitexturing hence perform the palette switch AND the lighting !!!



Stats per level:
base1.bsp 1,991,772 bytes   lightmap:725,910 bytes         36%

pak explorer:
pak0.pak    48.000 KB
./demos:       138 KB
./env:       1.200 KB
./maps:      6.000 KB
./models     7.500 KB
./pics       1.000 KB
./sounds:   28.000 KB
./sprites:   1.000 KB
./textures:  2.200 KB



MEMORY SUBSYSTEMS:
==================

- Surface Stack cs_base allocator (Make a graph showing exponential) // 32bits aligned
- Surface stack for BSP ordering
- Hunk allocator (Hunk_Begin uses virtual Alloc), used to load BSP files, // round to cacheline.
- ZONE MEMORY ALLOCATION (common.c) still here (used to load ressources such as images, sounds and textures. But no usage of tags.
- Discuss the way stack,linked list and no memory is allocate

ADD CVAR for movie generation and command for screenshot


CONSOLE SYSTEM:
===============

        Complex system made of function callback, state variable (cvar), alias.
        The console even features a basic bash style completion system (no listing, first match only)(Cmd_CompleteCommand). This system
        is still capable of completing function, alias or cvar names ;) !
        
        Ability to load a script with C comment capability.
        
        All non client calls are also sent to the server over the network

        FUNCTION SUB-SYSTEM (kernel::cmd.c):
        ====================================
        
        Stored in cmd_functions
        
        Linked list, with command name and associated void (*)() function pointer.
        Linear linked list search with strcmp
        
        The function take it's argv and argc from Cmd_Argv abd Cmd_Args global variables.
        
        "cmd" command will be forwared to server via the network
        
        CVAR SUB-SYSTEM (kernel::cvar.c):
        =================================
        
        Stored in cvar_vars
        
        Linear linked list search with strcmp  
        
        To improve performances, some cvar pointer cache cvar lookups (instead of using Cvar_Get every time). This is particularly true in
        the renderer modules, they keep reference to "hand" via a r_lefthand, "gl_lightmap" in gl_lightmap and so on
        
        ALIAS SUB_SYSTEM: (kernel::cmd.c):
        ==================================
        
        Stored in cmd_alias
        
        
        
        Note you don't have to use "set" command to set a cvar you can just type its name and new value: Cmd_ExecuteString will try function,alias and cvars


TIME SYSTEM:
============        

- extratime =  sum of last msec.
- extratime is a counter to skip framerate under cl_maxfps


    cls.frametime = extratime/1000.0;        // How much time was elapsed since last frame was renderer as float
    cl.time += extratime;                    // Same as cls.frametime, used to send back to server
    cls.realtime = curtime;                    // curtime comes from Sys_Milliseconds = time since Quake started
    
    
Netchan still base of networking communication    


TODO:

=====
        - Finish explosing the mipmapping system.
        - Check sound system memory consumption, memory map of Quake2 ?

 Requirements

    C Compiler
    Quake 2 Source code [Available on our Site or on Google]
    Beginning C programming skills

Contents

    Introduction
    GreyBear's Quake 2 programming philosophy
    Down to business
    Modify the item structure
    Create a new command function
    Add a new console command
    Bind a command to a key permanently
    Make it work
    The result
    Contacting the Author

Introduction

This is the first of a series of tutorials on programming modifications to Quake 2. The goal of these tutorials will be to teach the reader concepts that can be applied generally, while using a specific example as an illustration. This is somewhat different Than the method most similar tutorials employ. Most use a specific example and show you how to make that specific change, without giving you the underlying logic behind the mod. This makes it hard to understand how to take the concept and apply it in a new way. The goal of these tutorials is to get the reader to understand why the example works, and show the reader how to take the concept and create new modifications using the principles outlined.

I (GreyBear) am a self-taught programmer who's been programming since 1986 (which should give you a hint how old I must be). Games are still my first love, and I write these things because I never had the web to help me learn. This is my way of repaying those who helped me. I certainly don't know everything. If you think you see a better way to do something covered in a tutorial, please e-mail me with it. Also, please feel free to e-mail me with questions if you have them. I'll try to answer any and all that come my way.

Since I don't have the luxury of teaching you C, I assume in these tutorials that you know C to some extent, and that you are familiar with programming terminology. If not, run out and grab a book on C, and use it like a dictionary to follow along. Kernighan and Ritchie's seminal handbook on C, The C programming language, is an excellent desk reference. You probably should also visit the C tutorial section of this web site before continuing.

GreyBear's Quake 2 programming philosophy

My philosophy about making code changes is this:

Make your changes in your own code files whenever possible. That means for our mods, we'll be adding NEW files to the Quake 2 DLL, not merely inserting our mods into the original files. Why? Well, two reasons. One, it's much easier to find your mods if you keep them in your own files, named by you with descriptive names that you can remember. Two, it maintains the integrity of the original code. As programmers we should respect the work of others. Rather than just hack it up, we should add to it gracefully, and clearly mark our additions as our own.

When we can't follow the above, as in modifying global include files, or modifying global structs, we will segregate our mods in the code, and clearly mark them as our additions. I also highly recommend you use a consistent marker, like your initials. That way, you can search the code for your initials and easily find every mod you make in id code. Again, it makes for easy maintenance.

NOTE: In the body of the tutorial text, code modifications made by us will have a + sign at the end of the line in a comment field to clue you in that the line was added to the original surrounding code.

Down to business

OK, what we're going to cover in this tutorial is the steps necessary to create a new player command in deathmatch that allows a player to drop a weapon, sabotaging it in the process. The next player that picks it up (even the player who sabotaged it originally) and fires it takes damage from the weapon. This modification will show you several important concepts:

    Extending the gitem_t (item) struct to contain new flags or values
    Addition of new console commands, and the method to bind them permanently to keys
    Creation of new game functions, encapsulating them into code files of your own
    Making your functions visible to the game DLL through include files
    Making the weapon code actually behave the way we want it to


Step 1: Modifying the item structure

In order for this particular modification to work properly, we need to have a flag that allows us to keep track of whether a particular weapon has been sabotaged. In Quake 2, every player manipulated object (weapons, power ups, etc.) is described by an item struct. The really cool thing about using structs and having source code available is that we can add new flags or new descriptions to the struct to modify the attributes of an item almost without limit. We'll play it extra safe and add our flags at the end of the struct. This also allows us to group our modifications for easy visiting later.

If you're not already familiar with the item structure, take a peek at it in g_local.h. It's well documented, and easy to understand.

We want to modify the declaration of the gitem_t struct in the g_local.h file. The actual modification is trivial. Find the declaration of the gitem_t struct in g_local.h by doing a search. Immediately above the struct declaration itself, you'll see the declaration of the gitem_t flags. This ought to help you locate it. We then merely add a single line of code to the end of the gitem_t struct. Like so:

    char    *precaches;    // string of all models, sounds, and images this item will use
    int    wpn_sabo;    // + BD 12/24 Flag value to determine whether this item (if a weapon)        
                // + has been sabotaged.
    } gitem_t;


Save the file. That's it!

The explanation: All we did is add a new piece of information to the struct to hold our sabotage flag. Now every item spawned will have this new information. However in most items, it will never be used. You also might be wondering 'Why use an int? Why not a boolean value?' That's a legitimate question, and that approach would work fine. However, I may want to use this flag later to convey more detailed information. Perhaps we might tie the value of the flag to the amount of damage done to the user. Using an int gives us a little more flexibility in the use of this flag, and doesn't hurt anything at this point.

Note the comment documenting the initials of the person making the change, the date, and a brief explanation of what the mod is for. It may seem silly now, but when you re-visit the code in 3 months, you'll be glad you were a little anal about commenting your code.

Step 2: Adding a new console command and binding it to a key

This step is a little more complicated, so follow carefully. We're going to create two new files, and add them to our build list. The first file is a C code file that contains the function that sabotages the selected weapon, and drops it.

The second file is the header that declares the function, so that it can be made visible to other parts of the DLL. The C code we'll use is actually a modified version of a standard game DLL function that drops a weapon. Here's the C code. We'll place it in a file we'll call b_sabdrop.c. You can change the name is you wish, but this is the convention I use. The b_ is for my real first name (Brad). That way I know at a glance that I wrote it. It also follows the Quake 2 naming convention, e.g. g_ for game, q_ for quake, p_ for player, m_ for monster. The rest is merely a descriptive name that describes the code's function. Here's the code:

    // + THIS ENTIRE CODE BLOCK IS NEW!
    /* b_sabdrop.c - Brad Davis
       ===========================
       Provides a method to allow users to sabotage and drop weapons
       Last modified 12/25/97    
    */

    #include "g_local.h"   //Included so we'll have access to needed variables
    #include "b_sabdrop.h" //The forward declaration of our function
    
    /*
    =================
    Cmd_SabDrop_f
    
    Modified from Cmd_InvDrop_f by BD 12/24
    Drop an inventory weapon, sabotaging it in the process. Meant to be bound     to a key.
    =================
    */
    void Cmd_SabDrop_f (edict_t *ent)    //Acting on an entity
    {
        gitem_t        *it;        //The item we're looking at
    
        ValidateSelectedItem (ent);    //Make sure the item we want to look at is valid
    
        if (ent->client->pers.selected_item == -1)        //Whoops. No item selected!
        {    //Print error message and bail out
            gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n");    
            return;
        }
    
        it = &itemlist[ent->client->pers.selected_item];    //Whoops! Can't drop this!
        if (!it->drop)
        {    //Print error message and bail out
            gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
            return;
        }
        //BD 12/24 Sabotage the weapon if we made it to here.
        it->wpn_sabo = 1;        //The flag is now non-zero, meaning it's sabotaged!
        //simple, eh?
        it->drop (ent, it);    //Let Q2 do it's dropping thing...
    }
    // + END OF NEW CODE BLOCK


Save the file as b_sabdrop.c. You'll also need to add this file to your build or make file, or project file. Again, how you do this depends on your compiler.

The explanation: We created a new function, sequestering it in our own new file to keep it separate from the original Q2 code, as per my personal coding philosophy. This function is a very slightly modified version of the standard Q2 item dropping code. All we add is the setting of the wpn_sabo flag to a non-zero value. That tells us when we look at this item later on, that it's a sabotaged weapon, and we should hurt the user.

Extra credit: Find the original function we copied and modified...

Now, the include file. We'll call this file b_sabdrop.h. By now it should be obvious why. This file is quite simple, containing only a prototype of the function that lives in b_sabdrop.c. This way we can make the function visible to other parts of the game DLL by merely adding it to the list of file to include in the appropriate places. Here's the code:

    // + THIS ENTIRE CODE BLOCK IS NEW!
    /* b_sabdrop.h */

    void Cmd_SabDrop_f (edict_t *ent);
    // + END OF NEW CODE BLOCK


Again, save the file, this time as b_sabdrop.h. That was hard, huh? Again, you may need to add this to your make file, but in most cases with modern compilers, the addition of the b_sabdrop.c file, with the include reference will automatically make the file part of the build list as a dependency. Check your compiler docs.

Now, you need to add the code to the makefile or build list. How you do this is dependent on the compiler you're using. I'll leave it to you, intrepid reader, to figure out how this is done in your compiler. Usually, it's pretty simple.

Next, we need to modify the C source code file that handles user input commands. Amazingly, the file we need to modify is g_cmds.c (for game commands. Get it?). We need to do a couple of things. First, we need to make sure the following line appears at the top of the g_cmds.c file to make our new command visible to the rest of the code. Here's what the addition looks like:

    #include "g_local.h"
    #include "m_player.h"
    // + Added 12/25 BD
    #include "b_sabdrop.h" // +


The explanation: This make our modified drop function, Cmd_SabdDrop_f(), visible inside the g_cmds.c file. That way, we can use it just as if it lived inside this file.

Next, we need to add some code to the handler that determines what gets done when a command is issued. We want to add a section that checks to see if the user's desired action matches the string we expect when we want our code executed, and if so, we merely call our function to handle the request. Here's what this addition looks like:

    else if (Q_stricmp (cmd, "invdrop") == 0)
        Cmd_InvDrop_f (ent);
    else if (Q_stricmp (cmd, "sabdrop") == 0)    // +
        Cmd_SabDrop_f (ent);            // +
    else if (Q_stricmp (cmd, "weapprev") == 0)
        Cmd_WeapPrev_f (ent);


Save the file.

The explanation: We added a section of code that looks for a text string from the command console. If it sees that string, which is sabdrop, our sabotage drop function is called, sabotaging the weapon, and dropping it.

Now, we need to identify a key that will be bound to our new sabdrop command. Open the file config.cfg. It will be found in your quake2\baseq2 directory. Pay no attention to the header. We can safely modify the file to suit our new purpose. I bound the new command sabdrop to the 'd' key, as it wasn't being used, and d stood for drop quite nicely, thank you. You can bind it to any unused key you like, however. The trick here, is NOT to bind the key just to 'sabdrop', but to bind the key to 'cmd sabdrop'. So your new line in config.cfg should look like:

    bind d "cmd sabdrop"

Save the file.

Now every time you hit the d key in the game, the selected weapon will be sabotaged and dropped. After we make one other modification, that is...

Making it work

OK, let's recap. We've extended the functionality of the item structure. We've created a new function to tell a weapon that it's sabotaged when dropped with the correct command. We've modified the game code to handle the user's input requesting the weapon be sabotaged.

So what's left? Well, how does the game know how to cause the new user harm when the weapon is used? DOH! We need to modify the weapon's behavior to check and see if it's sabotaged or not. The place to do this? In the file p_weapon.c (player's weapon).

We're going to make this modification for one weapon, the rocket launcher. However, you can make it for any or every weapon if you like. If you decide to do this, I suggest you figure out a reasonable amount of damage for each type of sabotaged weapon. You probably don't want a sabotaged hand blaster to cause as much damage as a sabotaged BFG, would you? That just wouldn't be realistic.

OK, open up p_weapon.c. Locate the function Weapon_RocketLauncher_Fire(). Within this function we need to add a check of our item flag. If the wpn_sabo flag is TRUE (meaning non-zero), the weapon is sabotaged. We then merely need to call a standard quake function, called T_Damage(), with a few new variables, to cause damage to the player firing the weapon. Note that if the firing player hits his target, the amount of damage inflicted is exactly the same despite the sabotage. An interesting extension to this modification would be to have the weapon not fire at all, or to cause less than full damage. Perhaps in a later tutorial we'll revisit this... For now, here's the modification:

    fire_rocket (ent, start, forward, damage, 550, damage_radius, radius_damage);
    
    // + BD - 12/24 Check to see if this weapon is sabotaged. If so. hurt user
    if(ent->client->pers.weapon->wpn_sabo)    // +
    {                    // +
        T_Damage(ent, ent, ent->owner, ent->velocity, ent->s.origin, ent->s.origin, 25, 0, 0); // +
    }                    //+        
    // send muzzle flash
    gi.WriteByte (svc_muzzleflash);The result


Save the file.

The explanation: After the rocket launcher fires (in fire_rocket() ), we check to see if the launcher is sabotaged. The way we do this is a little convoluted, but it's logical once you understand it. Note the line containing ent->client->pers.weapon->wpn_sabo. What this is saying is; Look at the entity structure of the user (ent->client) that's firing the weapon. In that struct, there's another struct that contains persistent information, including the current weapon (pers.weapon). The weapon information is a struct itself, but belongs to the persistent information data. That's why we access it with a dot operator instead of a struct indirection operator. Finally, we access the wpn_sabo flag by looking inside the weapon struct, so again we use the struct indirection operator. Once again, the way to really understand the way these things work is to become familiar with the entity, client and weapon structs by reading the Q2 source code.

The actual damage is done by using a standard Q2 function, T_Damage(). We merely use some new values as arguments that tell the function that the user is damaging himself. Here's the declaration of T_Damage from the g_local.h file:

    void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags);

It's pretty self explanatory. In our case, the user is the target, the inflictor and the attacker. The vectors should all be the player's coordinates. We set the damage at 25 points in this example, but you can change that to taste. I also elected not to have a knockback value set. If you want a more violent appearance, you can elect to set this value to actually knock the user back. The dflags value just tells the game engine what visual effects to use. It only seem s to matter if you're hit with bullets. Otherwise, the graphics are all the same.

Well, our mod is done. Now, all we need do is test it. Compile your newly modified DLL, and either place it in a new subdirectory under the quake2 directory and run quake 2 with the +set game parameter pointing to your new directory, or replace the gamex86.dll in the baseq2 directory with your new one. If you choose the latter, be sure to backup the original gamex86.dll, or else you won't be able to play the original game properly, nor play your saved games. In either case, you need to add one more parameter. Use +set deathmatch 1 on the command line when testing your mod, as this mod only works in deathmatch play. When you look at it, this makes sense, since the monsters in single play don't pick up your weapons and use them. They come with their own built in, and they don't run out of ammo.

I keep a shortcut on my desktop that does all of the above. And since I don't like having all those extra folders around, I just copy the new dll into my quake2\baseq2 folder. I keep a backup of the original dll in the same folder, renamed as a .dlx file. Here's the shortcut target command line I use:

    C:\Quake2\quake2.exe +set deathmatch 1 +map base1

Note that I don't set the game parameter, since I copy the dll right into the quake2 directory.

In the base1 map, there's a rocket launcher right around the corner from where you spawn, on top of the crates. Grab it and fire it. No problem. Then, make the hand blaster the current weapon by hitting the 1 key. Bring up the inventory menu and highlight the rocket launcher. Press the d key ( or the key you bound to our new command). The rocket launcher will be dropped. Pick it up and fire it. OUCH! It works! Now you have a sneaky surprise for the opposition in your next deathmatch game.