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".
|