TUTORIAL 13 - Lightning
Discharge by The SARACEN
Hello people, this is actually SumFuka taking you through this
tutorial but all the code is The SARACEN's. There's some real
interesting stuff here... we're essentially adding a new 'event' to
the game. This requires modifications to both the game
dll/qvm (so that the effect gets triggered) and the cgame
dll/qvm (so that the client can see the effect). This is gonna be
fun, "unless you is a complete monga".
1. Define Lightning Discharge 'Event' and 'MOD'
Firstly, go into the game project and pull up
bg_public.h. At line 345 we're going to add another 'event type',
just below the other weapon events (rail trails, shotty sprays,
bullet marks etc).
If the C syntax is baffling you, we are defining unique constants
for the entity_event_t datatype. Have a browse through some
of the other event types... not all of them are 'visible things'
(e.g. EV_NOAMMO) but they all do have something in common - when
these events happen the clients need to be 'told' about it.
EV_MISSILE_HIT,
EV_MISSILE_MISS,
EV_RAILTRAIL,
EV_SHOTGUN,
EV_BULLET, // otherEntity is the shooter
EV_LIGHTNING_DISCHARGE, // The SARACEN's Lightning Discharge
EV_PAIN,
EV_DEATH1,
EV_DEATH2,
EV_DEATH3,
EV_OBITUARY,
Similarly, we're going to add another
meansOfDeath_t. The meansOfDeath or MOD is used when
someone dies to pick the appropriate death message (remember
ClientObituary from quake and quake2 ? Kinda similar...).
// means of death
typedef enum {
MOD_UNKNOWN,
MOD_SHOTGUN,
MOD_GAUNTLET,
MOD_MACHINEGUN,
MOD_GRENADE,
MOD_GRENADE_SPLASH,
MOD_ROCKET,
MOD_ROCKET_SPLASH,
MOD_PLASMA,
MOD_PLASMA_SPLASH,
MOD_RAILGUN,
MOD_LIGHTNING,
MOD_LIGHTNING_DISCHARGE, // The SARACEN's Lightning Discharge
MOD_BFG,
MOD_BFG_SPLASH,
MOD_WATER,
MOD_SLIME,
MOD_LAVA,
MOD_CRUSH,
MOD_TELEFRAG,
MOD_FALLING,
MOD_SUICIDE,
MOD_TARGET_LASER,
MOD_TRIGGER_HURT,
MOD_GRAPPLE
} meansOfDeath_t;
Now open up combat.c and at line 159 insert a string
for our new MOD as below. (Why do we need this as well as the
modification above ? Simply, the EV_MOD's are constant values and
since they aren't strings they can't be used in game messages. We
define some useful error strings here for that purpose).
"MOD_RAILGUN",
"MOD_LIGHTNING",
"MOD_LIGHTNING_DISCHARGE", // The SARACEN's Lightning Discharge
"MOD_BFG",
"MOD_BFG_SPLASH",
2. Implement a Water Radius Damage Function Still in
game and g_combat.c, go to line 733 and add the following new
function directly after G_RadiusDamage :
/*
============
G_WaterRadiusDamage for The SARACEN's Lightning Discharge
============
*/
qboolean G_WaterRadiusDamage (vec3_t origin, gentity_t *attacker, float damage, float radius,
gentity_t *ignore, int mod)
{
float points, dist;
gentity_t *ent;
int entityList[MAX_GENTITIES];
int numListedEntities;
vec3_t mins, maxs;
vec3_t v;
vec3_t dir;
int i, e;
qboolean hitClient = qfalse;
if (!(trap_PointContents (origin, -1) & MASK_WATER)) return qfalse;
// if we're not underwater, forget it!
if (radius < 1) radius = 1;
for (i = 0 ; i < 3 ; i++)
{
mins[i] = origin[i] - radius;
maxs[i] = origin[i] + radius;
}
numListedEntities = trap_EntitiesInBox (mins, maxs, entityList, MAX_GENTITIES);
for (e = 0 ; e < numListedEntities ; e++)
{
ent = &g_entities[entityList[e]];
if (ent == ignore) continue;
if (!ent->takedamage) continue;
// find the distance from the edge of the bounding box
for (i = 0 ; i < 3 ; i++)
{
if (origin[i] < ent->r.absmin[i]) v[i] = ent->r.absmin[i] - origin[i];
else if (origin[i] > ent->r.absmax[i]) v[i] = origin[i] - ent->r.absmax[i];
else v[i] = 0;
}
dist = VectorLength(v);
if (dist >= radius) continue;
points = damage * (1.0 - dist / radius);
if (CanDamage (ent, origin) && ent->waterlevel) // must be in the water, somehow!
{
if (LogAccuracyHit (ent, attacker)) hitClient = qtrue;
VectorSubtract (ent->r.currentOrigin, origin, dir);
// push the center of mass higher than the origin so players
// get knocked into the air more
dir[2] += 24;
G_Damage (ent, NULL, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
}
}
return hitClient;
}
3. Modify the Lightning Gun Fire Function Open up
g_weapon.c and find Weapon_LightningFire at line 475. We need
to modify the weapon so that it does a G_WaterRadiusDamage if fired
underwater.
/*
======================================================================
LIGHTNING GUN
======================================================================
*/
void Weapon_LightningFire( gentity_t *ent ) {
trace_t tr;
vec3_t end;
gentity_t *traceEnt, *tent;
int damage;
damage = 8 * s_quadFactor;
VectorMA( muzzle, LIGHTNING_RANGE, forward, end );
// The SARACEN's Lightning Discharge - START
if (trap_PointContents (muzzle, -1) & MASK_WATER)
{
int zaps;
gentity_t *tent;
zaps = ent->client->ps.ammo[WP_LIGHTNING]; // determines size/power of discharge
if (!zaps) return; // prevents any subsequent frames causing second discharge + error
zaps++; // pmove does an ammo[gun]--, so we must compensate
SnapVectorTowards (muzzle, ent->s.origin); // save bandwidth
tent = G_TempEntity (muzzle, EV_LIGHTNING_DISCHARGE);
tent->s.eventParm = zaps; // duration / size of explosion graphic
ent->client->ps.ammo[WP_LIGHTNING] = 0; // drain ent's lightning count
if (G_WaterRadiusDamage (muzzle, ent, damage * zaps,
(damage * zaps) + 16, NULL, MOD_LIGHTNING_DISCHARGE))
g_entities[ent->r.ownerNum].client->ps.persistant[PERS_ACCURACY_HITS]++;
return;
}
// The SARACEN's Lightning Discharge - END
trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );
if ( tr.entityNum == ENTITYNUM_NONE ) {
return;
}
traceEnt = &g_entities[ tr.entityNum ];
if ( traceEnt->takedamage && traceEnt->client ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
tent->s.otherEntityNum = traceEnt->s.number;
tent->s.eventParm = DirToByte( tr.plane.normal );
tent->s.weapon = ent->s.weapon;
if( LogAccuracyHit( traceEnt, ent ) ) {
ent->client->ps.persistant[PERS_ACCURACY_HITS]++;
}
} else if ( !( tr.surfaceFlags & SURF_NOIMPACT ) ) {
tent = G_TempEntity( tr.endpos, EV_MISSILE_MISS );
tent->s.eventParm = DirToByte( tr.plane.normal );
}
if ( traceEnt->takedamage) {
G_Damage( traceEnt, ent, ent, forward, tr.endpos,
damage, 0, MOD_LIGHTNING);
}
}
The bit The SARACEN added is quite straightforward -
first use the trap_PointContents function to test if the
weapon is being fired in the water. A temp entity
EV_LIGHTNING_DISCHARGE is then created and automatically
broadcast to the clients (so that they see it). Then, we do a
G_WaterRadiusDamage proportional to the amount of ammo the
player has remaining (zaps). Anyone within range should be
fried.
3. Implement the Death Messages Ok, go into the
cgame project and open up cg_local.h. First, add the
following function prototype at line 1023 :
localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir,
qhandle_t hModel, qhandle_t shader, int msec,
qboolean isSprite );
void CG_Lightning_Discharge (vec3_t origin, int msec); // The SARACEN's Lightning Discharge
Now open cg_event.c and go to line 155. We need to add
some death messages for our new meansOfDeath :
case MOD_PLASMA_SPLASH:
if ( gender == GENDER_FEMALE )
message = "melted herself";
else if ( gender == GENDER_NEUTER )
message = "melted itself";
else
message = "melted himself";
break;
// The SARACEN's Lightning Discharge - START
case MOD_LIGHTNING_DISCHARGE:
if (gender == GENDER_FEMALE)
message = "discharged herself";
else if (gender == GENDER_NEUTER)
message = "discharged itself";
else
message = "discharged himself";
break;
// The SARACEN's Lightning Discharge - END
case MOD_BFG_SPLASH:
message = "should have used a smaller gun";
break;
default:
if ( gender == GENDER_FEMALE )
message = "killed herself";
else if ( gender == GENDER_NEUTER )
message = "killed itself";
else
message = "killed himself";
break;
Now at line 255 The SARACEN has had some more fun with
the death messages, nice one :
case MOD_RAILGUN:
message = "was railed by";
break;
// The SARACEN's Lightning Discharge - START
/* // original obituary
case MOD_LIGHTNING:
message = "was electrocuted by";
break;
*/ // Classic Quake style obituary - the original and the best!!!
case MOD_LIGHTNING:
message = "was shafted by";
break;
case MOD_LIGHTNING_DISCHARGE:
message = "was discharged by";
break;
// The SARACEN's Lightning Discharge - END
case MOD_BFG:
case MOD_BFG_SPLASH:
message = "was blasted by";
message2 = "'s BFG";
break;
4. Add an Event Hook Now at line 780 we need to
define which function is called when a certain event is triggered
(our lightning discharge!).
case EV_SHOTGUN:
DEBUGNAME("EV_SHOTGUN");
CG_ShotgunFire( es );
break;
// The SARACEN's Lightning Discharge - START
case EV_LIGHTNING_DISCHARGE:
DEBUGNAME("EV_LIGHTNING_DISCHARGE");
CG_Lightning_Discharge (position, es->eventParm); // eventParm is duration/size
break;
// The SARACEN's Lightning Discharge - END
case EV_GENERAL_SOUND:
DEBUGNAME("EV_GENERAL_SOUND");
if ( cgs.gameSounds[ es->eventParm ] ) {
trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] );
} else {
s = CG_ConfigString( CS_SOUNDS + es->eventParm );
trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) );
}
break;
5. Prevent Client from Drawing a Shaft Ok now we
need to stop the client from drawing a shaft on the screen if the
gun is fired underwater (remember we previously modified the firing
behaviour in the game project, and all the visuals are done
in the cgame project which we are now working with).
/*
===============
CG_LightningBolt
Origin will be the exact tag point, which is slightly
different than the muzzle point used for determining hits.
The cent should be the non-predicted cent if it is from the player,
so the endpoint will reflect the simulated strike (lagging the predicted
angle)
===============
*/
static void CG_LightningBolt( centity_t *cent, vec3_t origin ) {
trace_t trace;
refEntity_t beam;
vec3_t forward;
vec3_t muzzlePoint, endPoint;
if ( cent->currentState.weapon != WP_LIGHTNING ) {
return;
}
memset( &beam, 0, sizeof( beam ) );
// find muzzle point for this frame
VectorCopy( cent->lerpOrigin, muzzlePoint );
AngleVectors( cent->lerpAngles, forward, NULL, NULL );
// FIXME: crouch
muzzlePoint[2] += DEFAULT_VIEWHEIGHT;
VectorMA( muzzlePoint, 14, forward, muzzlePoint );
// The SARACEN's Lightning Discharge
if (trap_CM_PointContents (muzzlePoint, 0) & MASK_WATER) return;
// project forward by the lightning range
VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint );
// see if it hit a wall
CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint,
cent->currentState.number, MASK_SHOT );
// this is the endpoint
VectorCopy( trace.endpos, beam.oldorigin );
6. Draw the Effect Open up cg_effect.c and go to
line 166 and let's add some code directly after the
CG_SpawnEffect function.
/*
====================
CG_Lightning_Discharge by The SARACEN
====================
*/
void CG_Lightning_Discharge (vec3_t origin, int msec)
{
localEntity_t *le;
if (msec <= 0) CG_Error ("CG_Lightning_Discharge: msec = %i", msec);
le = CG_SmokePuff ( origin, // where
vec3_origin, // where to
((48 + (msec * 10)) / 16), // radius
1, 1, 1, 1, // RGBA color shift
300 + msec, // duration
cg.time, // start when?
0, // flags (?)
trap_R_RegisterShader ("models/weaphits/electric.tga"));
le->leType = LE_SCALE_FADE;
}
What does this do ? The SARACEN has created a big smoke
puff that lasts a defined number of milliseconds (proportional the
the strength of the discharge - in other words, the amount of ammo
that was discharged).
That's it ! Once again, thanks to The SARACEN for this great code
and I hope that my (me==SumFuka) explanations did it proper justice.
Now go play who's-gonna-get-the-red-armor on the level with the red
armor in the bottom of the water pool.
|