Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 34 | 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 34 - Cluster grenades
    by Chris Hilton

    Powering up the weapons that already exist is an excellent way to introduce yourself to the code. Game balance is always an issue here, but you can have a lot of fun with this in a LAN party as you frag your friends in devious and gratuitous ways.

    The grenade launcher is a difficult weapon to use well. The ordnance is unpowered and falls under gravity, and it bounces too. This tute powers up the grenade launcher to create a lethal area to try and run or jump over. Especially deadly in corridors!

    This tutorial updates the cluster grenade work by SumFuka at QDevels for Q2, bringing it in line with the new Q3 code. Chris Hilton wrote the code and donated the mod source to Code3Arena, and HypoThermia wrote these words.

    The coding modifications are all in g_missile.c, with one addition to g_local.h.

     

    1. The cluster grenade design

    When we create entities that exist on the map for a short duration, such as fired weapon ordnance, there's a framework already in place for us to use. Such items are called "Bolts" (the name given to crossbow ammunition). A lot of work has already been done for us to make sure they move through the map smoothly, either under their own power, or on a gravitational trajectory.

    The bolt system can be easily extended to create new types, or separate out similar types of ammo (as we're doing here). A cluster grenade looks like any other grenade, but has the effect of breaking up on impact or after a time delay, so we need two types of ammo. Fortunately the bolt system allows us to add new types with ease.

     

    2. The code

    We'll start by creating our new type of bolt ammunition, by modifying the ammo fired by the grenade launcher. We make this change to fire_grenade() in g_missile.c:

    bolt = G_Spawn();
    bolt->classname = "cgrenade";	// CCH
    bolt->nextthink = level.time + 2500;
    

    By renaming our bolt we've created a new type, thats the minimum you need to do! The bolt is a gentity_t which is an entity created with G_Spawn(). This data structure is only used in this form within the server code, so it can get away with using a pointer to the string name.

    Setting the item type to ET_MISSILE ensures that the G_MissileImpact() function is called. Inside G_MissileImpact() we have to create the cluster of grenades when something is hit. We need a vector for the direction that each grenade will go in:

    gentity_t	*other;
    qboolean	hitClient = qfalse;
    vec3_t		dir;			// CCH
    

    Then the code that recognizes the type of bolt and creates the grenades. This uses a function fire_grenade2() we'll provide in a moment:

    // splash damage (doesn't apply to person directly hit)
    if ( ent->splashDamage ) {
    	if( G_RadiusDamage( trace->endpos, ent->parent, 
    			ent->splashDamage, ent->splashRadius, 
    			other, ent->splashMethodOfDeath ) ) {
    		if( !hitClient ) {
    			g_entities[ent->r.ownerNum].client->
    				ps.persistant[PERS_ACCURACY_HITS]++;
    		}
    	}
    }
    
    // CCH: For cluster grenades
    if (!strcmp(ent->classname, "cgrenade")) {
    	VectorSet(dir, 20, 20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    	VectorSet(dir, -20, 20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    	VectorSet(dir, 20, -20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    	VectorSet(dir, -20, -20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    }
    
    trap_LinkEntity( ent );
    

     

    The vector dir contains the velocity in which each fragment will move: four diagonal directions and a small kick vertically so they bounce nicely.

    Now we need to provide the functionality within the "think" function, called when the game time reaches the nextthink time. The grenade has "timed out" and broken into equally lethal fragments.

    Adding to G_ExplodeMissile() the same code as we put into G_MissileImpact() gives the required effect. If you plan to develop or extend cluster grenades then it would be wise to merge this identical code into a single function. This would give the same effect with either event, without having to copy/paste code back and forth.

    // splash damage
    if ( ent->splashDamage ) {
    	if( G_RadiusDamage( ent->r.currentOrigin, ent->parent, 
    			ent->splashDamage, ent->splashRadius, NULL
    			, ent->splashMethodOfDeath ) ) {
    		g_entities[ent->r.ownerNum].client->
    			ps.persistant[PERS_ACCURACY_HITS]++;
    	}
    }
    
    // CCH: For cluster grenades
    if (!strcmp(ent->classname, "cgrenade")) {
    	VectorSet(dir, 20, 20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    	VectorSet(dir, -20, 20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    	VectorSet(dir, 20, -20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    	VectorSet(dir, -20, -20, 40);
    	fire_grenade2(ent->parent, ent->r.currentOrigin, dir);
    }
    
    trap_LinkEntity( ent );
    

     

    Just the creation of each grenade in the cluster needed now. This is done using the new function fire_grenade2(), which is best placed just before fire_grenade().

    /*
    =================
    CCH: fire_grenade2
    
    38: 62. They will also say, `Our Lord, whosoever prepared this for us,
    do thou multiply manifold his punishment in the Fire.'
    --Holy Quran, translated by Maulvi Sher Ali  
    =================
    */
    gentity_t *fire_grenade2 (gentity_t *self, vec3_t start, vec3_t dir) {
    	gentity_t	*bolt;
    
    	VectorNormalize (dir);
    
    	bolt = G_Spawn();
    	bolt->classname = "grenade";
    	bolt->nextthink = level.time + 2500;
    	bolt->think = G_ExplodeMissile;
    	bolt->s.eType = ET_MISSILE;
    	bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    	bolt->s.weapon = WP_GRENADE_LAUNCHER;
    	bolt->s.eFlags = EF_BOUNCE_HALF;
    	bolt->r.ownerNum = self->s.number;
    	bolt->parent = self;
    	bolt->damage = 100;
    	bolt->splashDamage = 100;
    	bolt->splashRadius = 150;
    	bolt->methodOfDeath = MOD_GRENADE;
    	bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH;
    	bolt->clipmask = MASK_SHOT;
    
    	bolt->s.pos.trType = TR_GRAVITY;
    
    	// move a bit on the very first frame
    	bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
    
    	VectorCopy( start, bolt->s.pos.trBase );
    	VectorScale( dir, 700, bolt->s.pos.trDelta );
    	
    	// save net bandwidth
    	SnapVector( bolt->s.pos.trDelta );
    
    	VectorCopy (start, bolt->r.currentOrigin);
    
    	return bolt;
    }
    

    This is almost an exact copy/paste of the original fire_grenade(), but restoring the original classname for the grenade. If you put "cgrenade" in here then you'd be creating new clusters of grenades every time, crashing the game before filling the level!

    You can tweak these values for slightly different behaviour: the amount of damage and splash damage for example. The 700 in the VectorScale() determines the speed of the fragments (and hence how far they can travel). Other ideas include changing the number of fragments, or a more random disrtibution to the fragments.

     

    The last thing we need to do is declare the function in g_local.h at about line 454:

    //
    // g_missile.c
    //
    void G_RunMissile( gentity_t *ent );
    
    gentity_t *fire_blaster (gentity_t *self, vec3_t start, vec3_t aimdir);
    gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t aimdir);
    gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t aimdir);
    gentity_t *fire_grenade2 (gentity_t *self, vec3_t start, vec3_t aimdir);	// CCH	
    

     

    3. All done!

    Now go off and enjoy this souped up weapon! Particularly effective and useful in a CTF game!

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