Code3Arena

PlanetQuake | Code3Arena | Tutorials | << Prev | Tutorial 13 | 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 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".

    Updated for the 1.27g source code. The function CG_SmokePuff() now takes an extra argument for the fade in time for the graphic.

    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,					// fade in time
    				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.

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