Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 5 | 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 5 - ARMOR PIERCING RAILS
    by SumFuka

    Do you get frustrated when the enemy you've got aimed up for a rail in the head suddenly ducks behind a pillar ?? Let's teach the chicken-shit a lesson.

    If you like, go and have a read about Vectors. (Like it or not, a good understanding of vector mathematics is essential to quake coding).

    1. HOW DO SLUGS WORK ?

    Let's find the weapon_railgun_fire function at line 334 in g_weapon.c :
    /*
    =================
    weapon_railgun_fire
    =================
    */
    #define	MAX_RAIL_HITS	4
    void weapon_railgun_fire (gentity_t *ent) {
    	vec3_t		end;
    	trace_t		trace;
    	gentity_t	*tent;
    	gentity_t	*traceEnt;
    	int			damage;
    	int			radiusDamage;
    	int			i;
    	int			hits;
    	int			unlinked;
    	gentity_t	*unlinkedEntities[MAX_RAIL_HITS];
    
    	damage = 100 * s_quadFactor;
    	radiusDamage = 30 * s_quadFactor;
    
    	VectorMA (muzzle, 8192, forward, end);
    
    	// trace only against the solids, so the railgun will go through people
    	unlinked = 0;
    	hits = 0;
    	do {
    		trap_Trace (&trace, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
    		if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
    			break;
    		}
    		traceEnt = &g_entities[ trace.entityNum ];
    		if ( traceEnt->takedamage ) {
    			if( LogAccuracyHit( traceEnt, ent ) ) {
    				hits++;
    			}
    			G_Damage (traceEnt, ent, ent, forward, trace.endpos, damage, 0,
    			MOD_RAILGUN);
    		}
    		if ( trace.contents & CONTENTS_SOLID ) {
    			break;		// we hit something solid enough to stop the beam
    		}
    		// unlink this entity, so the next trace will go past it
    		trap_UnlinkEntity( traceEnt );
    		unlinkedEntities[unlinked] = traceEnt;
    		unlinked++;
    	} while ( unlinked < MAX_RAIL_HITS );
    
    	// link back in any entities we unlinked
    	for ( i = 0 ; i < unlinked ; i++ ) {
    		trap_LinkEntity( unlinkedEntities[i] );
    	}
    
    	// the final trace endpos will be the terminal point of the rail trail
    
    	// snap the endpos to integers to save net bandwidth, but nudged towards the line
    	SnapVectorTowards( trace.endpos, muzzle );
    
    	// send railgun beam effect
    	tent = G_TempEntity( trace.endpos, EV_RAILTRAIL );
    
    	// set player number for custom colors on the railtrail
    	tent->s.clientNum = ent->s.clientNum;
    
    	VectorCopy( muzzle, tent->s.origin2 );
    	// move origin a bit to come closer to the drawn gun muzzle
    	VectorMA( tent->s.origin2, 4, right, tent->s.origin2 );
    	VectorMA( tent->s.origin2, -1, up, tent->s.origin2 );
    
    	// no explosion at end if SURF_NOIMPACT, but still make the trail
    	if ( trace.surfaceFlags & SURF_NOIMPACT ) {
    		tent->s.eventParm = 255;	// don't make the explosion at the end
    	} else {
    		tent->s.eventParm = DirToByte( trace.plane.normal );
    	}
    	tent->s.clientNum = ent->s.clientNum;
    
    	// give the shooter a reward sound if they have made two railgun hits in a row
    	if ( hits == 0 ) {
    		// complete miss
    		ent->client->accurateCount = 0;
    	} else {
    		// check for "impressive" reward sound
    		ent->client->accurateCount += hits;
    		if ( ent->client->accurateCount >= 2 ) {
    			ent->client->accurateCount -= 2;
    			ent->client->ps.persistant[PERS_REWARD_COUNT]++;
    			ent->client->ps.persistant[PERS_REWARD] = REWARD_IMPRESSIVE;
    			ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++;
    			// add the sprite over the player's head
    			ent->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE |
    			EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET );
    			ent->client->ps.eFlags |= EF_AWARD_IMPRESSIVE;
    			ent->client->rewardTime = level.time + REWARD_SPRITE_TIME;
    		}
    		ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
    	}
    
    }
    
    Firstly, assume that 'muzzle' is a location vector just in front of the firer, and 'forward' is a direction vector pointing in the direction the client is facing.

    Ok, there's a loop here that basically says do { trace the slug until you hit something; if the slug hits a wall, exit the loop; repeat; }. The do { } simply repeats everything in the curly brackets until the code break's out. Inside this loop, the first line calls the function trap_Trace(blah blah blah) - this traces a line through space from the origin (muzzle) in the direction of the rail (forward) for a maximum distance of 8192 units. If we hit something, trace returns information about what we hit.

    If the slug hits nothing, the do loop simply exits. If the slug hits something damageable ( if (traceEnt->takedamage), e.g. a player or a button ) then the target is damaged (with G_Damage). If the slug hits a wall or the 'edge of the world' ( if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) ) then the loop exits. We want to change this !!

    2. FIRE THROUGH WALLS

    First we need to a new vector variable (directly below the other variables, near the top of the function). After line 344, add :
    void weapon_railgun_fire (gentity_t *ent) {
    	vec3_t		end;
    	trace_t		trace;
    	gentity_t	*tent;
    	gentity_t	*traceEnt;
    	int			damage;
    	int			radiusDamage;
    	int			i;
    	int			hits;
    	int			unlinked;
    	gentity_t	*unlinkedEntities[MAX_RAIL_HITS];
    	vec3_t		tracefrom;	// SUM
    
    A few lines down we can see a 'VectorMA' function call. This creates an 'end' vector that is 8192 units 'forward' of 'muzzle' (the startpoint for the rail). We need to make a copy of 'muzzle' in 'tracefrom', so add a line so that your code looks like this :
    	damage = 100 * s_quadFactor;
    	radiusDamage = 30 * s_quadFactor;
    
    	VectorMA (muzzle, 8192, forward, end);
    	VectorCopy (muzzle, tracefrom);
    
    Next, let's change the trap_Trace function call so that the railgun is traced from 'tracefrom' (instead of muzzle)... there's a good reason for this, read on.
    	// trace only against the solids, so the railgun will go through people
    	unlinked = 0;
    	hits = 0;
    	do {
    		trap_Trace (&trace, tracefrom, NULL, NULL, end, ent->s.number, MASK_SHOT );
    
    Next, we want to change the behaviour of the if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) { ... } block. This if statement detects if our rail slug runs into a 'solid' (e.g. a wall or the sky). What do we want to change ? Well, instead of simply 'breaking' out of the do loop (and marking the endpoint for our slug), we want the slug to keep going through walls. Of course, we still want the slug to stop when we hit the sky. The code for this is :
    		if ( trace.entityNum >= ENTITYNUM_MAX_NORMAL ) {
    			// SUM break if we hit the sky
    			if (trace.surfaceFlags & SURF_SKY)
    				break;
    
    			// Hypo: break if we traversed length of vector tracefrom
    			if (trace.fraction == 1.0)
    				break;
    
    			// otherwise continue tracing thru walls
    			VectorMA (trace.endpos,1,forward,tracefrom);
    			continue;
    		}
    		traceEnt = &g_entities[ trace.entityNum ];
    
    In other words, if we hit the sky (check the surfaceFlags), stop. If we've travelled the full length of the vector then we also need to stop (there's no guarantee we'll ever hit the sky.) Otherwise we've hit a solid wall. We set our new 'tracefrom' position 1 unit forward of the impact point (which effectively tunnels through the wall). The loop then repeats - continue; takes us back to the top of the 'do' loop.

    3. CAN EVERYONE SEE IT ??

    Thanks to WarZone for this section : as it is, the railgun trail is possibly not seen by people far away on the level. The little addition below makes sure that the rail trail entity is 'broadcast' to everyone (not just those in the vicinity of the firer). This makes sense, it would be stupid to have a railgun fire through a wall and frag someone if the victim couldn't see the rail trail !
    	// no explosion at end if SURF_NOIMPACT, but still make the trail
    	if ( trace.surfaceFlags & SURF_NOIMPACT ) {
    		tent->s.eventParm = 255;	// don't make the explosion at the end
    	} else {
    		tent->s.eventParm = DirToByte( trace.plane.normal );
    	}
    	tent->s.clientNum = ent->s.clientNum;
    
    	//send the effect to everyone since it tunnels through walls
    	tent->r.svFlags |= SVF_BROADCAST;
    
    I've asked Mark (WarZone) to write an article for us, explaining how 'broadcasting' works and the deeper subject of what goes on in the quake network code. Look out for some more great stuff from him!

    Ok, the rest of the function remains the same, and our railgun should fire through walls.

    Fire up q3tourney6 and see if you can give Xearo a surprise with your new toy on Nightmare!. "He is got t'go DOWN, man".

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