Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 35 | 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 35 - Homing Rockets
    by Chris Hilton

    Continuing on the weapon power-up theme, and taking another look at the MaxCarnage mini-mod, we're looking at rockets that home on their target.

    Rockets are already a powerful weapon... the splash damage gives the target a "kick" that makes it easier to place the second rocket, getting the frag. This is balanced in the tutorial by slowing the rockets down and giving them a tunnel vision view of their target.

    There are also some important issues for playing this modification over a network versus a 56K modem. Quake 3 uses a "fire and forget" system that allows the client to predict where an object will be if its path isn't interrupted. This smooths gameplay and greatly reduces the amount of data sent across a network connection, and is equivalent to long think times for entities.

    Implementing homing missiles will hammer this: a short think time means the rocket entity will frequently update its direction vector while tracking. 56K modem users will be greatly disadvantaged by this, so think carefully about whether you want to implement this in a mod for general release.

    Chris Hilton wrote the code and donated the mod source to Code3Arena, and HypoThermia wrote these words. I've also added a small bug fix to the code that isn't in the source released, you're unlikely to notice it in play though.

    1. About homing rockets

    The concept of a homing rocket is a straight forward one, but I'll cover the details here so you can see why Chris has written the code the way he has. The most important thing is restricting the visibility of targets to the rocket. This is done by only using targets within a certain range of the rocket, and within a given cone of view based on the current direction of travel.

    The rockets also aim at the midbody, rather than the feet or one corner of the model. A bit of common sense really, but attention to detail like this makes for a better mod.

    2. Implementing the code

    All the modifications take place in g_missile.c, we're just adding a new think function for the rocket, and updating the rocket creation code so it uses the new think function.

    First off we've got the new "e;think" function. It needs a little bit of explanation, and should be placed before the fire_rocket() function.

    /*
    ================
    CCH: rocket_think
    
    Fly like an eagle...
    --"Fly Like an Eagle", Steve Miller Band
    ================
    */
    #define ROCKET_SPEED	600
    
    void rocket_think( gentity_t *ent ) {
    	gentity_t	*target, *tent;
    	float		targetlength, tentlength;
    	int		i;
    	vec3_t		tentdir, targetdir, forward, midbody;
    	trace_t		tr;
    
    	target = NULL;
    	targetlength = LIGHTNING_RANGE;
    	// Best way to get forward vector for this rocket?
    	VectorCopy(ent->s.pos.trDelta, forward);
    	VectorNormalize(forward);
    	for (i = 0; i < level.maxclients; i++) {
    		// Here we use tent to point to potential targets
    		tent = &g_entities[i];
    
    		if (!tent->inuse) continue;
    		if (tent == ent->parent) continue;
    		if ( OnSameTeam( tent, ent->parent ) ) continue;
    
    		// Aim for the body, not the feet
    		midbody[0] = tent->r.currentOrigin[0] + 
    			(tent->r.mins[0] + tent->r.maxs[0]) * 0.5;
    		midbody[1] = tent->r.currentOrigin[1] + 
    			(tent->r.mins[1] + tent->r.maxs[1]) * 0.5;
    		midbody[2] = tent->r.currentOrigin[2] + 
    			(tent->r.mins[2] + tent->r.maxs[2]) * 0.5;
    
    		VectorSubtract(midbody, ent->r.currentOrigin, tentdir);
    		tentlength = VectorLength(tentdir);
    		if ( tentlength > targetlength ) continue;
    
    		// Quick normalization of tentdir since 
    		// we already have the length
    		tentdir[0] /= tentlength;
    		tentdir[1] /= tentlength;
    		tentdir[2] /= tentlength;
    		if ( DotProduct(forward, tentdir) < 0.9 ) continue;
    
    		trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, 
    			tent->r.currentOrigin, ENTITYNUM_NONE, MASK_SHOT );
    
    		if ( tent != &g_entities[tr.entityNum] ) continue;
    
    		target = tent;
    		targetlength = tentlength;
    		VectorCopy(tentdir, targetdir);
    	}
    
    	ent->nextthink += 50;
    
    	if (!target) return;
    
    	VectorMA(forward, 0.05, targetdir, targetdir);
    	VectorNormalize(targetdir);
    	VectorScale(targetdir, ROCKET_SPEED, ent->s.pos.trDelta);
    }
    

    Stepping through the code, we first set in targetlength how far ahead of the rocket we'll look for targets, and also grab the direction the rocket is currently flying in.

    Then, looping through all the player entities on the map, we apply a several conditions for rejecting the target. These conditions are:

    1. That the entity is on the map (tent->inuse),
    2. not the person firing the rocket (tent == ent->parent),
    3. not on the same team as the person firing the rocket,
    4. not too far away (tentlength > targetlength),
    5. inside the visible cone (DotProduct(forward, tentdir) < 0.9), and
    6. not obscured by part of the map (trap_Trace()).

    The small bug-fix is in the way tentlength is used to normalize the value of tentdir[], and allows the code to find the closest target player properly.

    The visible cone test uses a little bit of vector math to obtain the cosine of the angle between the two vectors. The value of 0.9 accepts all vectors that differ by up to 25 degrees. Increasing this value closer to 1.0 will narrow the cone, reducing it towards zero will open the cone up.

    Care needs to be taken with size of the cone: there's no code to remove the rocket when its been on the map for a while, it needs to hit something to be removed. You need to use sensible values that allow the rocket to "miss" and so be removed from the map.

    Finally, when the closest target has been found, the direction to the target is mixed with the current direction using VectorMA() so the rocket starts steering towards its victim. Decreasing the value of 0.05 will give the rocket a larger turning circle and make it less responsive. Increase the value too much and the player won't have a chance to get out the way!

     

    All thats left is to link this new think function into the rocket and set the new slower speed at the time of launch. These last two changes go into fire_rocket().

    bolt = G_Spawn();
    bolt->classname = "rocket";
    bolt->nextthink = level.time + 1;	// CCH
    bolt->think = rocket_think;		// CCH
    bolt->s.eType = ET_MISSILE;
    bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    
    
    bolt->s.pos.trType = TR_LINEAR;
    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
    VectorCopy( start, bolt->s.pos.trBase );
    VectorScale( dir, ROCKET_SPEED, bolt->s.pos.trDelta );	// CCH
    SnapVector( bolt->s.pos.trDelta );
    VectorCopy (start, bolt->r.currentOrigin);
    

     

    3. Ideas for change

    Just to round off the tutorial, I thought I'd give a few ideas on the changes you could make to the this code.

    The most obvious is making sure that the missile doesn't stay on the map for too long: eventually calling G_ExplodeMissile() to clean up.

    Giving the player a homing missile detector that beeps when a lock on is gained would balance things up a bit. The more advanced might even want to add this as a powerup like the medikit or teleporter.

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