Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 36 | 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 36 - Spreadfire powerup
    by Hal9000

    This nifty new powerup is just the kind of thing you used to find in those retro arcade games. Which is probably just as well its in the Smash mod, because that's based on... you guessed it... a retro arcade game.

    Run around fragging bot opponents for money, using a fixed third person view. Gameplay is very simple - yet surprisingly addictive - with a few extra powerups thrown in for good measure. Great maps complete this mod and really make it fun to play.

    1. Recognizing the powerup

    To kick off we need to add a new power up type, and then define the entity that appears on maps. As there's already a powerup system in place, we don't really need to do any more work to get the item started.

    Adding the new type to bg_public.h, in the powerup_t enumeration (about line 221):

    PW_REDFLAG,
    PW_BLUEFLAG,
    PW_SPREAD,  //Hal9000 spreadfire
    PW_BALL,
    

     

    Then the powerup item is added to the list of entities that the client and server will be aware of. In bg_misc.c in the array bg_itemlist[]:

    /*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16)
    Only in CTF games
    */
    {
    	"team_CTF_blueflag",
    	"sound/teamplay/flagtk_blu.wav",
    	{ 	"models/flags/b_flag.md3",
    		0, 0, 0 },
    /* icon */	"icons/iconf_blu1",
    /* pickup */	"Blue Flag",
    		0,
    		IT_TEAM,
    		PW_BLUEFLAG,
    /* precache */ "",
    /* sounds */ "sound/teamplay/flagcap_blu.wav sound/teamplay/flagtk_blu.wav sound/teamplay/flagret_blu.wav"
    	},
    
    //Hal9000 spreadfire powerup
    /*QUAKED item_spread (.3 .3 1) (-16 -16 -16) (16 16 16) suspended
    */
    {
    	"item_spread", 
    	"sound/items/spread.wav",
    	{ 	"models/powerups/threeway/threeway.md3", 
    		0, 0, 0 },
    /* icon */	"icons/spread",  
    /* pickup */	"Spreadfire",
    		30,
    		IT_POWERUP,
    		PW_SPREAD,
    /* precache */ "",
    /* sounds */ ""
    },
    // end of list marker
    {NULL}
    

    item_spread is a unique name for the item, used to identify and connect it with the entity placed in the map. &qout;sound/items/spread.wav" will be played when the item is picked up. Because it's a powerup, it'll be played globally for all to hear.

    The next set of information in the curly brackets is the list of models used to construct the item. In our case we only have one, "models/powerups/threeway/threeway.md3", but powerups like the quad have a second model for a counter-rotating ring.

    If you want to use the model created for the spreadfire by Hal9000, you'll have to ask for his permission first.

    The small graphical icon called icons/spread" is drawn on the splash screen while the level loads, and is also used when the player has item models turned off. This can (as it does in this case) also name a shader used to specify how the icon is drawn on screen.

    The item info is completed with the name displayed on screen when the item is picked up, and the identification as a powerup of type PW_SPREAD.

     

    2. When the powerup is activated...

    ...we need to fire the weapon with two extra shots to give us a fan shape.

    This is the code from Smash2 that does the job. It only works for weapons that produce a "bolt": rocket launcher, grenade, plasma, and BFG(!), and direct fire or hit-scan weapons like a railgun can't use this code. This is the new weapon_grenadelauncher_fire() in g_weapon.c, with the spreadfire code installed. To use this code in other bolt weapons you'll need to replace the fire_grenade() call with the equivalent bolt creation function call.

    void weapon_grenadelauncher_fire (gentity_t *ent) {
    	gentity_t	*m;
    	gclient_t	*client; //Hal9000
    	float		newforward[] = {0,0,0}; //Hal9000
    	client = ent->client; //Hal9000
    
    	// extra vertical velocity
    	forward[2] += 0.2;
    	VectorNormalize( forward );
    
    	m = fire_grenade (ent, muzzle, forward);
    	m->damage *= s_quadFactor;
    	m->splashDamage *= s_quadFactor;
    
    	//Hal9000 spreadfire
    	if ( client->ps.powerups[PW_SPREAD] ) {
    		//First shot, slightly to the right
    		AngleVectors( client->ps.viewangles, forward, right, up );
    		VectorCopy( forward, newforward );
    		if ( forward[0] >= 0.5 && forward[0] <= 1 ) {
    			newforward[1] += .35;
    		} else if ( forward[0] <= -0.5 && forward[0] >= -1 ) {
    			newforward[1] += .35;
    		} else {
    			newforward[0] += .35;
    		}
    		VectorNormalize( newforward );
    		VectorAdd( newforward, forward, forward );
    		CalcMuzzlePoint( ent, forward, right, up, muzzle );
    
    		m = fire_grenade (ent, muzzle, forward);
    		m->damage *= s_quadFactor;
    		m->splashDamage *= s_quadFactor;
    
    		//Second shot, slightly to the left
    		AngleVectors (client->ps.viewangles, forward, right, up);
    		VectorCopy( forward, newforward );
    		if ( forward[0] >= 0.5 && forward[0] <= 1 ) {
    			newforward[1] -= .35;
    		} else if ( forward[0] <= -0.5 && forward[0] >= -1 ) {
    			newforward[1] -= .35;
    		} else {
    			newforward[0] -= .35;
    		}
    		VectorNormalize( newforward );
    		VectorAdd( newforward, forward, forward );
    		CalcMuzzlePoint ( ent, forward, right, up, muzzle );
    
    		m = fire_grenade (ent, muzzle, forward);
    		m->damage *= s_quadFactor;
    		m->splashDamage *= s_quadFactor;
    	}
    //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, 
    //		m->s.pos.trDelta );	// "real" physics
    }
    

    What this code does is construct a vector (newforward) that's approximately perpendicular to the direction we're facing, and in the horizontal plane. It then adds it to our forward facing direction, and fires a bolt off. This achieves the desired effect of a fan shape, though its only an approximate solution.

     

    Unfortunately there's a small problem. The code assumes that the spreadfire will only be used in the horizontal plane. This is great and works well in SmashV2 where you can only fire horizontally. If you want to add this feature to your mod, and you allow a mouse free-look that changes pitch, then you'll need to use the code from the next section instead.

     

    3. When the powerup is activated (part II)

    I'm going to take a slightly different route with this code just to make it easier to add to any bolt firing weapon. I'll create a generic function that accepts the bolt firing function as a parameter, so we can re-use it in multiple places.

    This is the new function, place it near the top of g_weapon.c:

    
    typedef gentity_t* (*weaponLaunch)(gentity_t*, vec3_t, vec3_t);
    
    /*
    ===============
    SpreadFire_Powerup
    
    HypoThermia: fan bolts at any view pitch
    ===============
    */
    static void SpreadFire_Powerup(gentity_t* ent, weaponLaunch fireFunc)
    {
    	gentity_t	*m;
    	gclient_t	*client;
    	vec3_t		newforward;
    	vec3_t		angles;
    
    	client = ent->client; 
    
    	//First shot, slightly to the right
    	AngleVectors( client->ps.viewangles, forward, right, 0);
    	VectorMA(forward, 0.1, right, newforward);
    	VectorNormalize(newforward);
    	vectoangles(newforward, angles);
    
    	AngleVectors( angles, forward, right, up );
    	CalcMuzzlePoint( ent, forward, right, up, muzzle );
    
    	m = fireFunc (ent, muzzle, forward);
    	m->damage *= s_quadFactor;
    	m->splashDamage *= s_quadFactor;
    
    	//Second shot, slightly to the left
    	AngleVectors( client->ps.viewangles, forward, right, 0);
    	VectorMA(forward, -0.1, right, newforward);
    	VectorNormalize(newforward);
    	vectoangles(newforward, angles);
    
    	AngleVectors( angles, forward, right, up );
    	CalcMuzzlePoint( ent, forward, right, up, muzzle );
    
    	m = fireFunc(ent, muzzle, forward);
    	m->damage *= s_quadFactor;
    	m->splashDamage *= s_quadFactor;
    }
    
    

    It looks pretty similar to the one used in Smash, but uses a feature of AngleVectors to recover a useful vector.

    The client->ps.viewangles contains the YAW (rotate left-right), PITCH (lean forward/back), and ROLL (rotate left/right) data for the view. When used, AngleVectors() returns vectors that, given our orientation in space, will move us forward, to the right edge of the screen, and up to the top of the screen.

    The important thing to understand is that these vectors give us movement directions in the absolute world that have "left-right" and "up-down" meaning in our view of the world.

    How does this relate to the spreadfire? Well each extra shot goes "to the right" and "to the left" in our view of the world. So we mix some of the right vector into the view forward direction using VectorMA() and then reconvert back to the new (YAW, PITCH, ROLL) angles in preparation for calulating the muzzle point.

    Its a long winded way of saying that the spread of bolts is independent of the direction you fire them in!

     

    Finally you have to enable the spreadfire for each of the bolt weapons, this is the modified grenade launcher code:

    void weapon_grenadelauncher_fire (gentity_t *ent) {
    	gentity_t	*m;
    
    	// extra vertical velocity
    	forward[2] += 0.2;
    	VectorNormalize( forward );
    
    	m = fire_grenade (ent, muzzle, forward);
    	m->damage *= s_quadFactor;
    	m->splashDamage *= s_quadFactor;
    
    	if ( client->ps.powerups[PW_SPREAD] )
    		SpreadFire_Powerup(ent, fire_grenade);
    
    //	VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, 
    //		m->s.pos.trDelta );	// "real" physics
    }
    

     

    
    
    

     

    4. Finishing up

    Well that's all you need to do for bolt weapons. Because of the similarity of "fire and forget" for each of these weapons, the code change is much more straightforward. If you want to extend other weapon types then you're going to have to look at each one individually, and extend the "hit-scan" detection code. This is more "repetitive" than difficult, just make sure that you keep the weapon spread independent of the angle of view.

    Take a look at the rest of the SmashV2 source code in the ModSource section. This mod balances code changes with new maps, showing that a fun mod isn't just driven by code alone.

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