Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 27 | Next >>

menu

  • Home/News
  • ModSource
  • Compiling
  • Help!!!
  • Submission
  • Contributors
  • Staff
  • Downloads

    Tutorials
    < Index >
    1. Mod making 101
    2. Up 'n running
    3. Hello, QWorld!
    4. Infinite Haste
    5. Armor Piercing Rails
    6. Bouncing Rockets
    7. Cloaking
    8. Ladders
    9. Favourite Server
    10. Flame Thrower
    11. Vortex Grenades
    12. Grapple
    13. Lightning Discharge
    14. Locational Damage
    15. Leg Shots
    16. Weapon Switching
    17. Scoreboard frag-rate
    18. Vortex Grenades II
    19. Vulnerable Missiles
    20. Creating Classes
    21. Scrolling Credits
    22. Weapon Dropping
    23. Anti-Gravity Boots
    24. HUD scoreboard
    25. Flashlight and laser
    26. Weapon Positioning
    27. Weapon Reloading
    28. Progressive Zooming
    29. Rotating Doors
    30. Beheading (headshot!)
    31. Alt Weapon Fire
    32. Popup Menus I
    33. Popup Menus II
    34. Cluster Grenades
    35. Homing Rockets
    36. Spreadfire Powerup
    37. Instagib gameplay
    38. Accelerating rockets
    39. Server only Instagib
    40. Advanced Grapple Hook
    41. Unlagging your mod


    Articles
    < Index >
    1. Entities
    2. Vectors
    3. Good Coding
    4. Compilers I
    5. Compilers II
    6. UI Menu Primer I
    7. UI Menu Primer II
    8. UI Menu Primer III
    9. QVM Communication, Cvars, commands
    10. Metrowerks CodeWarrior
    11. 1.27g code, bugs, batch


    Links

  • Quake3 Files
  • Quake3 Forums
  • Q3A Editing Message Board
  • Quake3 Editing


    Feedback

  • SumFuka
  • Calrathan
  • HypoThermia
  • WarZone





    Site Design by:
    ICEmosis Design


  •  
    TUTORIAL 27 - Reloading Weapons
    by Martin Dominguez - Drive C:

    It seems everyone has been e-mailing me for the code for reloading your weapons. I didn't quite understand how or why they thought I had the code. I DID actually have the completed code, so writing the tutorial wasn't very difficult.

    The effects of this code are simple. Everyone knows that not every single weapon can carry the same amount of ammo, right? In Quake 3 though, somehow they can hold 200 rounds regardless of the weapon. We're here to change that!

    Both cgame and game will be modified:

    g_cmds.c
    g_items.c
    g_client.c
    g_weapon.c
    g_local.h

    bg_public.h
    bg_pmove.c

    cg_draw.c

    Once again, I must disclaim the lack of comment used in the source here. Please: When you are typing up the code, tell yourself what you are doing, okay? It really, really helps. For everyone who doesn't know of this yet, you create an in-source comment with the two slash characters "//". This leaves the remainder of the line for your remark.  

    1. The Premise

    Quake 3 coders know that the ammo quantities are stored in the variable entity->client->ps.ammo, which is an array of 16(NUM_MAX_WEAPONS) integers. The reload code, however would require that there be a second array of that exact size to store the ammount held in your "bag" versus the ammount in the weapon. A simple console command would cause an empty weapon to "reload."

    Here's the problem: The function that checks to see if we can pick up ammo from the ground is located in bg_misc.c at line ~755. Since it's located in BG(both games?), it has access to neither game or cgame structures, but only shared structures. Thus the ammount in your "bag" must stay in ps.ammo. When we fire the weapon, ammo must be depleted from the weapon's clip variable (our new variable). Firing the weapon and removing ammo also occurs in BG (bg_pmove.c, line ~1525), so somehow both variables must occur in the ps(playerstate) structure.

    Since no modifications to playerstate have actually resulted in a stable game, we cannot simply create an integer array ps.clipammo.

    We must use some tricky manipulations to get around the problem of both ammo quantities being needed in BG. Here's the trick: since weapon firing functions get passed from bg to game, we can remove the ammo depletions from bg_pmove.c to g_weapon.c and place our ammo in clip variable in the client structure.

    A. Go to g_local.h to about line 277 in struct gclient_s and add:

    int		lastKillTime;			// for multiple kill rewards
    
    qboolean	fireHeld;			// used for hook
    gentity_t	*hook;				// grapple hook if out
    
    
    // Our Ammo Variable, ps.ammo, now refers to Bag Ammo
    int		clipammo[MAX_WEAPONS];	
    
    // timeResidual is used to handle events that happen every second
    // like health / armor countdowns and regeneration
    int		timeResidual;
    };
    
    void Cmd_Reload( gentity_t *ent );
    int ClipAmountForWeapon( int w );
    
    

    Now it's time to relocate the ammo depletion code to game from bg.

    B1. Go into g_weapon.c to line ~572 and insert:

    void FireWeapon( gentity_t *ent ) {
    	
    	//Remove Ammo if not infinite
    	if ( ent->client->clipammo[ ent->client->ps.weapon ] != -1 ) {
    		ent->client->clipammo[ ent->client->ps.weapon ]--;
    	}
    
    	if (ent->client->ps.powerups[PW_QUAD] ) {
    		s_quadFactor = g_quadfactor.value;
    	} else {
    		s_quadFactor = 1;
    	}
    

      B2. Go into bg_pmove.c line ~1525 in PM_Weapon()and use the /*..........*/ marks to set this text to remark:

    // take an ammo away if not infinite
    // This will go within "game" in g_weapon.c because
    // ammo is stored in gclient_t
    /*if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) {
    	pm->ps->ammo[ pm->ps->weapon ]--;
    }*/
    
    // fire weapon
    PM_AddEvent( EV_FIRE_WEAPON );
    

    Another really important thing that should be mentioned is the check for out of ammo, which is associated with our second major problem. The check for out of ammo occurs in bg just above the depletion code, and quite honestly I would prefer to leave it there. The problem is that bg has no access to the game structure "gclient_t," so it cannot possibly check to see if the present weapon is depleted entirely as the ammo in clip variables are stored there.

    Here is where we create a second variable, this time located in the client->ps.stats indexes. All the out of ammo check needs to know is the ammo held in the present weapon's clip, so we can create a variable ps.stats[STAT_AMMO] that will update the ammo in the presently equipped weapon automatically. Later, you will see why this new variable is also needed (it has to do with cgame, can you figure it out?).

    C1. To create the variable, goto bg_public.h, line ~166 and insert:

    // player_state->stats[] indexes
    typedef enum {
    	STAT_HEALTH,
    	STAT_HOLDABLE_ITEM,
    	STAT_WEAPONS,					// 16 bit fields
    	STAT_ARMOR,
    	STAT_DEAD_YAW,					
    	STAT_AMMO,					// Ammo Held in Current Weapon
    	STAT_CLIENTS_READY,
    
    
    

      C2. Let's set the STAT_AMMO variable to be updated on ClientEndFrame(); insert in g_active.c line ~841:

    // burn from lava, etc
    P_WorldEffects (ent);
    
    // apply all the damage taken this frame
    P_DamageFeedback (ent);
    
    // add the EF_CONNECTION flag if we haven't gotten commands recently
    if ( level.time - ent->client->lastCmdTime > 1000 ) {
    	ent->s.eFlags |= EF_CONNECTION;
    } else {
    	ent->s.eFlags &= ~EF_CONNECTION;
    }
    
    //Update the Ammo Amount in weapon
    ent->client->ps.stats[STAT_AMMO] = ent->client->clipammo[ent->client->ps.weapon];
    
    ent->client->ps.stats[STAT_HEALTH] = ent->health;
    

      C3. Let's now use the STAT_AMMO variable to check for out of ammo. Insert in bg_pmove.c, in PM_Weapon(), line ~1520:

    pm->ps->weaponstate = WEAPON_FIRING;
    
    // check for out of ammo
    // modified for changing ammo from ps->ammo to client->clipammo updated to ps->stats[STAT_AMMO]
    if ( ! /*pm->ps->ammo[ pm->ps->weapon ]*/ pm->ps->stats[STAT_AMMO] ) {
    	PM_AddEvent( EV_NOAMMO );
    	pm->ps->weaponTime += 500;
    	return;
    }
    
    // take an ammo away if not infinite

      D1. HypoThermia brought this to my attention, and I think we will all appreciate this. When you pick up a gun from the ground, its nice that it has ammo in it so you don't have to reload it after picking it up. Go to g_items.c to line 139 and we will see the PickupWeapon function. You will later learn what the ClipAmountForWeapon() function is. Simply modify:

    	if ( ent->count < 0 ) {
    		quantity = 0; // None for you, sir!
    	} else {
    		if ( ent->count ) {
    			quantity = ent->count;
    		} else {
    			quantity = ent->item->quantity;
    		}
    
    		// dropped items and teamplay weapons always have full ammo
    		if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) {
    			// respawning rules
    			// drop the quantity if the already have over the minimum
    			/*if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
    				quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
    			} else {
    				quantity = 1;		// only add a single shot
    			}*/
    		}
    	}
    
    	// add the weapon
    	other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
    	
    	/*Add_Ammo( other, ent->item->giTag, quantity );*/
    	quantity = ClipAmountForWeapon(ent->item->giTag);
    	if (other->client->clipammo[ent->item->giTag] > 0 )
    		Add_Ammo( other, ent->item->giTag, quantity );
    	else 
    		other->client->clipammo[ent->item->giTag] = quantity;
    
    	if (ent->item->giTag == WP_GRAPPLING_HOOK)
    		other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo
    
    	// team deathmatch has slow weapon respawns
    	if ( g_gametype.integer == GT_TEAM ) {
    		return RESPAWN_TEAM_WEAPON;
    	}

      D2. Go to g_client.c to line 931, in ClientSpawn(), and give ammo for player to start:

    	client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
    	client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
    	if ( g_gametype.integer == GT_TEAM ) {
    		client->ps.ammo[WP_MACHINEGUN] = 50;
    	} else {
    		client->ps.ammo[WP_MACHINEGUN] = 100;
    	}
    	client->clipammo[WP_MACHINEGUN] = ClipAmountForWeapon(WP_MACHINEGUN);
    	
    	client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
    	client->ps.ammo[WP_GAUNTLET] = -1;
    	client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
    

     

    2. Creating the Command and the ClipAmountForWeapon Function

    Reloading the weapon will be activated by the server-side command "/reload."

    As we all remember from before: go into g_cmds.c down to line ~1101 in ClientCommand() to the else if statements, and insert this code:

    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, "reload") == 0)
    	Cmd_Reload( ent );
    

    Jus before the function ClientCommand() in that file(g_cmds.c), we will add our actual reload function:

    
    /*
    ==================
      Cmd_Reload
    ==================
    */
    void Cmd_Reload( gentity_t *ent )	{
    	int weapon;
    	int amt;
    	int ammotoadd;
    
    	weapon = ent->client->ps.weapon;
    	amt = ClipAmountForWeapon(weapon);
    	ammotoadd = amt;
    
    	if (ent->client->clipammo[weapon] >= ClipAmountForWeapon(weapon))	{
    		trap_SendServerCommand( ent-g_entities,
    			va("print \"No need to reload.\n\""));
    		return;
    	}
    
    
    	ent->client->ps.weaponstate = WEAPON_DROPPING;
    	ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT )
    		^ ANIM_TOGGLEBIT )	| TORSO_DROP;
    	ent->client->ps.weaponTime += 2500;
    	//Play a sound maybe: thats up to you.
    
    
    	if (ent->client->ps.ammo[weapon] == 0) return;
    
    	
    
    	//We can only add ammo(to weapon) what we need
    	if (ent->client->clipammo[weapon] > 0)	{
    			ammotoadd -= ent->client->clipammo[weapon];
    	}
    
    	//We can only remove (from bag) what ammo we have
    	if (ent->client->ps.ammo[weapon] < ammotoadd)	{
    		ammotoadd = ent->client->ps.ammo[weapon];
    	}
    
    	//Remove the ammo from bag
    	ent->client->ps.ammo[weapon] -= ammotoadd;
    
    	//Add the ammo to weapon
    	ent->client->clipammo[weapon] += ammotoadd;
    
    
    }
    
    

    This function simply reloads the weapon. It checks to see first if no reloading is necessary. It then creates the graphics of dropping the weapon, removes from the backpack supply of ammo and adds to the weapon clip.

    As you saw above, we used the integer function ClipAmountForWeapon to determine how much ammo each weapon can hold. We need to add this function right below the Cmd_Reload, but you won't be able to copy and paste. Here, you will tell the game exactly how much ammo each weapon can hold, and that is determined by the coder, not the tutorial.

    I have however, set up an example. Hopefully it will help you to create your own clip quantities. You should be able to figure it out.

    Staying in g_cmds.c add this function:

    
    /*
    ==================
      ClipAmountForWeapon for Cmd_Reload
    ==================
    */
    int ClipAmountForWeapon( int w )	{
    	//How much each clip holds
    	if ( w == WP_MACHINEGUN )  return 24;
    	else if ( w == WP_GRENADE_LAUNCHER ) return 1;
    	else if ( w == WP_SHOTGUN )	return 1;
    	else return 12; //this wont happen unless you copy-and-paste too much
    	}

     

    3. Displaying Ammo Quantities

    All of us love Q3's HUD right? Yeah Right! Well anyway, I think it would be nice if we ammended an already skimpy eye-sore with just one other number. Can you guess what that number is? Well anyway, I told you before that we would be using the STAT_AMMO variable again.

    Since cgame has no access to our Ammo in Gun quantities, there is no way that we can display the ammo in the gun. STAT_AMMO, however, can be utilized in cgame. Only the current weapon's ammo needs to be displayed on the HUD, so I think we're in business.

    The displaying of statistics on your mod's HUD is really up to you, so don't feel you have to do what I am doing here. When I wrote this, I chose a very simple way of displaying Ammo in Gun, and I'm sure there are more economical ways to show it.

    Anyway, go in cg_draw.c (which contains functions to draw everything that is 2D on the screen) to line 429 and insert:

    		CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432,
    			ICON_SIZE, ICON_SIZE, cgs.media.armorIcon );
    	}
    }
    
    //
    // ammo in gun
    //
    value = ps->stats[STAT_AMMO];
    if (value > 0 ) {
    	//First Draw the 3D Model of the Weapon
    	angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0;
    	origin[0] = 80;
    	origin[1] = 0;
    	origin[2] = 0;
    	CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 360, 96,
    		96, cg_weapons[ cent->currentState.weapon ].weaponModel,
    		0, origin, angles );
    
    	//Draw the Text
    	trap_R_SetColor( colors[0] );
    	CG_DrawField (0, 384, 3, value);
    	trap_R_SetColor( NULL );
    
    	// if we didn't draw a 3D icon, draw a 2D icon for weapon
    	if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) {
    		CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 384, ICON_SIZE, ICON_SIZE,
    			cg_weapons[ cg.predictedPlayerState.weapon ].weaponIcon );
    	}
    }
    

    Also, I forgot to mention the out of ammo and low ammo warnings. They are executed in cg_draw.c but originate in cg_playerstate.c at line 50. These warnings respond to the amount of ammo in bag so they are quite useless for the reload modification. I never liked them anyway. If you comment (with the //) out line 52, you can remove the low ammo warning while preserving the out of ammo warning. To make it simpler though, just go into cg_draw.c and comment out line 1821.

      Well that's the tutorial. I hope everyone enjoyed it and that it is easy to understand and useful. To all, good luck and happy coding.

    Drive C:

    PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 27 | Next >>