Quake Strogganoff Lesson: Materials System (Projectile Impacts)

Sat, Feb 15, 2020 4-minute read

In this we’re going to build from our footsteps stuff and add surface specific impacts for weapons. This will once again depend on the use of Q3BSP for storing surface information.

So, in order to call a sound effect at the location of the impact we’re going to do a trace, determine the surface type at the trace end point and play a particle effect/sound/etc at that end point location.

To play a sound effect at a location without spawning an entity, we will use pointsound.

//DP_SV_POINTSOUND
//idea: Dresk
//darkplaces implementation: Dresk
//builtin definitions:
void(vector origin, string sample, float volume, float attenuation) pointsound = #483;
//description:
//Similar to the standard QC sound function, this function takes an origin instead of an entity and omits the channel parameter.
// This allows sounds to be played at arbitrary origins without spawning entities.

First let’s open up weapons.qc and add a new function at the top which will be where we do our trace and call the function which will play effects and sounds based on what surface it impacts.

/*
================
FireBullet

Generic hitscan selector for single bullet based weapons etc...
================
*/

//damage values
#define WEAPON_DEFAULT_DAMAGE 8

void() FireBullet =
{
	local	vector	source;
	local	vector	org;

	makevectors (self.v_angle);
	source = self.origin + '0 0 24';

 	nep_traceline (source, source + v_forward*4096, FALSE, self);
	if (trace_fraction == 1.0)
		return;

	org = trace_endpos - v_forward*4;

	// hit wall
	Q3Surface_Impact(org);

	T_Damage (trace_ent, self, self, WEAPON_DEFAULT_DAMAGE);
};

NOTE: There’s a T_Damage call in there which will always damage everything, for a more custom solution you may want to have something like this:

if (trace_ent.takedamage && self.weapon == WEP_CHAINGUN)
{
  T_Damage (trace_ent, self, self, WEAPON_CHAINGUN_DAMAGE);
}
else if (trace_ent.takedamage && self.weapon == WEP_MACHINEGUN)
{
  T_Damage (trace_ent, self, self, WEAPON_MACHINEGUN_DAMAGE);
}
else
{	// hit wall
  Q3Surface_Impact(org);
}

So that you can have custom damage types/amounts etc… And along with those T_Damage calls, you can add some blood effects however I’d recommend not making everything that takes damage to bleed but rather handle those in the entity’s pain and death think functions. This fixes the secret doors bleeding issues or anything that might take damage but doesn’t necessarily spurt blood everywhere.

Alrighty, back to it. The function we’ll be adding later that will apply the effects is void(vector org) Q3Surface_Impact.

For testing purposes we’ll just change the shotgun to use the new FireBullet();, so head down to the shotgun firing function and replace:

/*
================
W_FireShotgun
================
*/
void() W_FireShotgun =
{
	local vector dir;

	sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM);

	self.punchangle_x = -2;

	self.currentammo = self.ammo_shells = self.ammo_shells - 1;
	dir = aim (self, 100000);
	self.solid = SOLID_BBOX;
	FireBullets (6, dir, '0.04 0.04 0');
	self.solid = SOLID_SLIDEBOX;
};

With:

/*
================
W_FireShotgun
================
*/
void() W_FireShotgun =
{
	local vector dir;

	sound (self, CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM);

	self.punchangle_x = -2;

	self.currentammo = self.ammo_shells = self.ammo_shells - 1;
	dir = aim (self, 100000);
	self.solid = SOLID_BBOX;
	FireBullet();
	self.solid = SOLID_SLIDEBOX;
};

You may be wondering what’s going on with the self.solid changes, this is a workaround that allows trace based attacks to hit entities marked as SOLID_CORPSE, so bullets can gib dead bodies.

With that done, head over to materialsys_surface.qc and add the following function, which will play the actual sounds and effects based on the Q3BSP surface flag.

void(vector org) Q3Surface_Impact =
{
	local float r;

	if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOSTEPS)
	{
		return;
	}
	else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_METALSTEPS)
	{
		r = random() * 2;
		if (r < 1) 		pointsound(org, "world/ric1.wav", 1, ATTN_IDLE);
		else if (r < 2) pointsound(org, "world/ric2.wav", 1, ATTN_IDLE);
		else 			pointsound(org, "world/ric3.wav", 1, ATTN_IDLE);
		pointparticles(particleeffectnum("TE_GUNSHOT"), org, '0 0 0', 1);
	}
	else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_WOOD)
	{
		r = random() * 4;
		if (r < 1) pointsound(org, SOUND_IMPACT_WOOD1, 1, ATTN_IDLE);
		else if (r < 2) pointsound(org, SOUND_IMPACT_WOOD2, 1, ATTN_IDLE);
		else if (r < 3) pointsound(org, SOUND_IMPACT_WOOD3, 1, ATTN_IDLE);
		else            pointsound(org, SOUND_IMPACT_WOOD4, 1, ATTN_IDLE);
		pointparticles(particleeffectnum("TE_WOODIMPACT"), org, '0 0 0', 1);
	}
	else //DEFAULT TO CONCRETE?
	{
		r = random() * 2;
		if (r < 1) 		pointsound(org, "world/ric1.wav", 1, ATTN_IDLE);
		else if (r < 2) pointsound(org, "world/ric2.wav", 1, ATTN_IDLE);
		else 			pointsound(org, "world/ric3.wav", 1, ATTN_IDLE);
		pointparticles(particleeffectnum("TE_GUNSHOT"), org, '0 0 0', 1);
	}
}

The above isn’t finished but it should provide a good start, you’ll want to of course provide your own sounds and particle effects (using effectinfo.txt) as is the case with TE_WOODIMPACT.

The main takeaway here is you can call sounds and effects based on whatever surfaces you add.

Hope this helps and happy modding!