Ricocheting Spikes

Discuss programming in the QuakeC language.
Post Reply
Junrall
Posts: 191
Joined: Mon Sep 21, 2009 12:27 am
Location: North West Oregon, USA
Contact:

Ricocheting Spikes

Post by Junrall »

Been working on this for a while and wish to give something back to the community for all of the questions I've been asking and receiving answers for.

Ricocheting spikes is nothing new and has been done for similar weapons... I can in no way take credit for the idea of ricocheting spikes... in fact, the credit goes to a fellow by the name of Charlie Zimmerman for his version ricocheting pikes. And Dr. Shadowborg for Rebovec (Very useful and very cool!)
All I really did was shrink the code down a bit.

You are welcome to use this to your heart's desire.
If you have any feedback please speak up! Particularly in ways of shrinking the code further or simplifying it... maybe Darkplaces has a better/simpler way of doing something?

Alright.... here is my finished version of ricocheting spikes. Also included is a "bleeding door" fix... nothing new and is not necessary, but allows some realism to the new spike code.

Spikes will now ricochet off of pretty much everything (if "Bleeding Door" fix is applied)... including doors, lifts, plats, and exploding boxes(which still explode as normal). Every now and again a spike will bounce away rather than ricocheting. Also, some spikes will stick into a solid object, including doors, lifts, plats, and exploding boxes. Spikes will travel/move with the door, lift, or plat it is stuck to.

Note: Ricocheting spikes requires the Darkplaces engine and it's dpextensions.qc file as the spike code uses DP's MOVETYPE_BOUNCEMISSILE for ricochets and MOVETYPE_FOLLOW for making a spike follow doors, lifts, and plats.
I do have a non Darkplaces version that works as well, but needs to be cleaned up!
Also note that I used custom sound files which can be downloaded here: http://download299.mediafire.com/txx144 ... Spikes.zip

If you aren't using the "Bleeding Door" fix then you'll need to uncomment two lines.
At the very top:

Code: Select all

//.float i_bleed;
and in RicoSpikeTouch function in the fourth "if" statement:

Code: Select all

//other.i_bleed = TRUE;
Ricocheting Spikes:

Code: Select all

.float sticksoundtype
.float ricodrop;
.entity oldowner;
float crandom;

//.float i_bleed;	// Uncomment this if you are NOT using my Bleading Door Fix.

vector(vector org, vector dir, float justnorm) ReboVec =
{
 	local vector spot1, spot2, olddir, result;

	olddir = normalize(dir);

	if(justnorm)
	{
		traceline(org, org + dir*10, FALSE, self);
	  	result = trace_plane_normal;
	  	return result;
	}
	spot1 = org - (16*olddir);
	spot2 = org + (16*olddir);
	traceline(spot1, spot2, FALSE, self);
	self.origin = trace_endpos;
	result = olddir-(trace_plane_normal*(trace_plane_normal*olddir)*2);
	result = normalize(result);
	return result;
};

void (entity ent, vector ang, vector org) FollowEntity =
{
	self.movetype = MOVETYPE_FOLLOW;			// Tell DP that this entity is going to follow something
	self.solid = SOLID_NOT;			 		 	// Make this entity a non-solid
	self.aiment = other;			 		 	// Make this entity follow the selected entity
	self.punchangle = other.angles;   	 	 	// Set the original angles of the selected entity to this entity
	self.view_ofs = self.origin - other.origin;	// Set this entity's origin to the selected entity's relative origin
	self.v_angle = self.angles - other.angles;	// Set this entity's origin to the selected entity's relative angles
};

void() RicoThink =
{
	if (self.attack_finished < time) // Remove spike when time is up
	{
		remove(self);
	}
	else if (self.ricodrop)	// Spike falls from object that it is sticking to
	{
		self.movetype = MOVETYPE_BOUNCE;	// Cuases the spike to fall from the wall and bounce when it hits a solid
		self.solid = SOLID_BBOX;			// Make solid so that spike makes a bounce sound
		makevectors(self.v_angle);			// Grab the x,y,z values form the vector v_angle and place them into v_right, v_forward, v_up... I think?
		self.avelocity = v_forward * 600;	// Causes the spike to flip over backwards at the speed of 600
	}
   self.nextthink = time + 0.1; 		// Finish the thinking so that the spikes will remove themselves
};

void() RicoSpikeTouch =
{
	local float ran_rico;

	if(self.owner != self)	// Checking to see if spike is it's own owner
	{
		self.oldowner = self.owner; // Store origanal owner (you) away for later use
		self.owner = self;			// Make the spike the owner of itself so that it can damage it's origanal owner
	}

	if (other.solid == SOLID_TRIGGER) // If the spike touched a trigger field, do nothing, just pass through it
		return;

	if (pointcontents(self.origin) == CONTENT_SKY) // Touched the sky, remove the spike and do nothing
	{
		remove(self);
		return;
	}

	if (other.takedamage && self.movetype != MOVETYPE_BOUNCE) // If something that can take damage and if the spike doesn't bounce
	{
		//other.i_bleed = TRUE; //Uncomment this line if you are NOT using my Bleading Door Fix

		if (other.i_bleed) // Bleeding Door Fix - Only living things bleed
			spawn_touchblood (9);

		if(self.oldowner.weapon == IT_NAILGUN)			// Regular Nailgun does 9 damage
			T_Damage (other, self, self.oldowner, 9);
		if(self.oldowner.weapon == IT_SUPER_NAILGUN)	// Super Nailgun does 18 damage
			T_Damage (other, self, self.oldowner, 18);

		if(other.i_bleed) // If the spike hits a living thing then remove the spike.
			remove(self);
	}

	if(self.movetype != MOVETYPE_BOUNCE) // If the spike can bounce then don't spark on solid objects
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);

		// Determines what type of sound (Quake's sounds) to play and what type of spark
		if (self.classname == "wizspike")
			WriteByte (MSG_BROADCAST, TE_WIZSPIKE);
		else if (self.classname == "knightspike")
			WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE);
		else
			WriteByte (MSG_BROADCAST, TE_SPIKE);

		WriteCoord (MSG_BROADCAST, self.origin_x);
		WriteCoord (MSG_BROADCAST, self.origin_y);
		WriteCoord (MSG_BROADCAST, self.origin_z);
	}

	ran_rico = random (); // USed to randomize spike sounds

	if (self.movetype == MOVETYPE_BOUNCE) // If the spike can bounce then make a random sound when it hits a solid
	{
		if (self.sticksoundtype == 1)
			sound (self, CHAN_VOICE, "weapons/ricodrop1.wav", 0.25, ATTN_IDLE);
		else
			sound (self, CHAN_VOICE, "weapons/ricodrop2.wav", 0.25, ATTN_IDLE);
	}
	else if (random() < .4) //Stick
	{
		self.ricodrop = TRUE;										// Tells the think functiion that this spike will eventually become unstuck and fall.
		self.solid = SOLID_NOT;					  				 	// Spike is no longer solid, now it can't do damage if it is stuck into something.
		self.origin = self.origin - 6 * normalize(self.velocity);	// Pushes the spike's origin back 6 units so that just the tip is sticking into something.
		self.avelocity = '0 0 0'; 	  								// Stop the spike from rotating in all directions.
		self.velocity = '0 0 0';  	  								// Stop the spike from moving in any direction.

		if(other.classname == "door" || other.classname == "plat" || other.classname == "train")
		{
			FollowEntity(other, other.angles, other.origin); // If stuck in a door, plat, or train then allow the spike to move with the door, plat, or train. Note: Darkplaces dependant.
		}

		// Make a random sound when the spike sticks into something
		if (ran_rico >= 0.5)
		{
			sound (self, CHAN_VOICE, "weapons/ricostick1.wav", 1, ATTN_IDLE);
			self.sticksoundtype = 1; // Remember what sound it makes. Later, when the spike falls, the bounce/drop sound will sound similar.
		}
		else
		{
			sound (self, CHAN_VOICE, "weapons/ricostick2.wav", 1, ATTN_IDLE);
			self.sticksoundtype = 2;
		}

		self.nextthink = time + random()*10;		// Each spike that sticks will fall out different random times.
		self.attack_finished = self.nextthink + 3;	// Give the spike enough time to fall out and hit the floor.
	}
	else if (random() < .2) // Bounce away from a solid. Note: this is not a ricochet... more like a random bounce-away!
	{
		self.movetype = MOVETYPE_BOUNCE; // Enable the spike to bounce when it hits something
		self.solid = SOLID_NOT;			 // Spike is no longer solid, now it can't do damage.
		self.avelocity = v_forward*crandom()*600 + v_right*crandom()*600 + v_up*crandom()*600;						// Randomly spin/rotate the spike in a direction
		self.velocity = self.velocity*0.75 + v_forward*crandom()*200 + v_right*crandom()*200 + v_up*crandom()*200;	// Randomly accelerate the spike at a random speed/velocity.
	}
	else //ricochet
	{
		self.angles = vectoangles(ReboVec(self.origin, self.velocity, FALSE));	// Orient the spike to the new direction of the ricochet

		// Play random sound when the spike ricochets
		if (ran_rico >= 0.666)
			sound (self, CHAN_VOICE, "weapons/rico1.wav", 1, ATTN_IDLE);
		else if (ran_rico >= 0.333)
			sound (self, CHAN_VOICE, "weapons/rico2.wav", 1, ATTN_IDLE);
		else if (ran_rico >= 0)
			sound (self, CHAN_VOICE, "weapons/rico3.wav", 1, ATTN_IDLE);
	}
};

void(vector org, vector dir) LaunchRicoSpike =
{
	newmis = spawn ();
	newmis.owner = self;
	newmis.movetype = MOVETYPE_BOUNCEMISSILE;
	newmis.solid = SOLID_BBOX;
	newmis.angles = vectoangles(dir);
	newmis.touch = RicoSpikeTouch;
	newmis.classname = "spike";
	newmis.think = RicoThink;
	newmis.nextthink = time;
	newmis.attack_finished = time + 10;
	setmodel (newmis, "progs/spike.mdl");
	setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
	setorigin (newmis, org);
	newmis.velocity = dir * 1000;
};

void(float ox) W_FireRicoSpikes =
{
	local vector	dir;

	makevectors (self.v_angle);

	if (self.ammo_nails >= 2 && self.weapon == IT_SUPER_NAILGUN)
	{
		sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM);
		self.currentammo = self.ammo_nails = self.ammo_nails - 2;
		dir = aim (self, 1000);
		LaunchRicoSpike (self.origin + '0 0 16', dir);
		self.punchangle_x = -2;
		return;
	}

	if (self.ammo_nails < 1)
	{
		self.weapon = W_BestWeapon ();
		W_SetCurrentAmmo ();
		return;
	}

	sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM);
	self.currentammo = self.ammo_nails = self.ammo_nails - 1;
	dir = aim (self, 1000);
	LaunchRicoSpike (self.origin + '0 0 16' + v_right*ox, dir);
	self.punchangle_x = -2;
};

Bleeding Door/Wall/Exploding Box fix:

Code: Select all

Step 1 - Open def.qc and at the very bottom put:
		
	.float i_bleed; // Bleeding Door Fix

Step 2 - Open each monster file and locate each function that starts with "monster_". At or near the bottom of each function put:
	
	self.i_bleed = TRUE; //Bleeding Door Fix

Step 3 - Open client.qc and find PutClientInServer. At or near the bottom put:
	self.i_bleed = TRUE; //Bleeding Door Fix

Step 4 - Open weapons.qc and find W_FireAxe function.
Replace:

	if (trace_ent.takedamage)
	{
		trace_ent.axhitme = 1;
		SpawnBlood (org, '0 0 0', 20);
		T_Damage (trace_ent, self, self, 20);
	}
	else
	{	// hit wall
		sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
		WriteByte (MSG_BROADCAST, TE_GUNSHOT);
		WriteCoord (MSG_BROADCAST, org_x);
		WriteCoord (MSG_BROADCAST, org_y);
		WriteCoord (MSG_BROADCAST, org_z);
	}

with:

	if (trace_ent.takedamage)
	{
		trace_ent.axhitme = 1;
		if (trace_ent.i_bleed)//bleeding Door fix
			SpawnBlood (org, '0 0 0', 20);

		T_Damage (trace_ent, self, self, 20);
	}
	
	if (!trace_ent.i_bleed)//bleeding Door fix 
	{	// hit wall
		sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
		WriteByte (MSG_BROADCAST, TE_GUNSHOT);
		WriteCoord (MSG_BROADCAST, org_x);
		WriteCoord (MSG_BROADCAST, org_y);
		WriteCoord (MSG_BROADCAST, org_z);
	}


Find TraceAttack function.
Replace:

	if (trace_ent.takedamage)
	{
		SpawnBlood (org, vel*0.2, damage);
		AddMultiDamage (trace_ent, damage);
	}
	else
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
		WriteByte (MSG_BROADCAST, TE_GUNSHOT);
		WriteCoord (MSG_BROADCAST, org_x);
		WriteCoord (MSG_BROADCAST, org_y);
		WriteCoord (MSG_BROADCAST, org_z);
	}

With:
	
	if (trace_ent.takedamage)
	{
		if (trace_ent.i_bleed)//bleeding Door fix
			SpawnBlood (org, vel*0.2, damage);
		AddMultiDamage (trace_ent, damage);
	}

	if (!trace_ent.i_bleed)//bleeding Door fix
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
		WriteByte (MSG_BROADCAST, TE_GUNSHOT);
		WriteCoord (MSG_BROADCAST, org_x);
		WriteCoord (MSG_BROADCAST, org_y);
		WriteCoord (MSG_BROADCAST, org_z);
	}

Find LightningDamage function.
Replace all (three of them):

	particle (trace_endpos, '0 0 100', 225, damage*4);

With:

	if (trace_ent.i_bleed)//Bleeding Door Fix
			particle (trace_endpos, '0 0 100', 225, damage*4);

Find spike_touch function.
Replace:
	
	if (other.takedamage)
	{
		spawn_touchblood (9);
		T_Damage (other, self, self.owner, 9);
	}
	else
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);

		if (self.classname == "wizspike")
			WriteByte (MSG_BROADCAST, TE_WIZSPIKE);
		else if (self.classname == "knightspike")
			WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE);
		else
			WriteByte (MSG_BROADCAST, TE_SPIKE);
		WriteCoord (MSG_BROADCAST, self.origin_x);
		WriteCoord (MSG_BROADCAST, self.origin_y);
		WriteCoord (MSG_BROADCAST, self.origin_z);
	}

With:

	if (other.takedamage)
	{
		if (other.i_bleed)//bleeding Door Fix
			spawn_touchblood (9);
		T_Damage (other, self, self.owner, 9);
	}
	
	if (!other.i_bleed)//bleeding Door Fix
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);

		if (self.classname == "wizspike")
			WriteByte (MSG_BROADCAST, TE_WIZSPIKE);
		else if (self.classname == "knightspike")
			WriteByte (MSG_BROADCAST, TE_KNIGHTSPIKE);
		else
			WriteByte (MSG_BROADCAST, TE_SPIKE);
		WriteCoord (MSG_BROADCAST, self.origin_x);
		WriteCoord (MSG_BROADCAST, self.origin_y);
		WriteCoord (MSG_BROADCAST, self.origin_z);
	}

Find spike_superspike_touch function.
Replace:

	if (other.takedamage)
	{
		spawn_touchblood (18);
		T_Damage (other, self, self.owner, 18);
	}
	else
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
		WriteByte (MSG_BROADCAST, TE_SUPERSPIKE);
		WriteCoord (MSG_BROADCAST, self.origin_x);
		WriteCoord (MSG_BROADCAST, self.origin_y);
		WriteCoord (MSG_BROADCAST, self.origin_z);
	}

With:

	if (other.takedamage)
	{
		if (other.i_bleed)//Bleeding Door Fix
			spawn_touchblood (18);
		T_Damage (other, self, self.owner, 18);
	}
	
	if (!other.i_bleed)//Bleeding Door Fix
	{
		WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
		WriteByte (MSG_BROADCAST, TE_SUPERSPIKE);
		WriteCoord (MSG_BROADCAST, self.origin_x);
		WriteCoord (MSG_BROADCAST, self.origin_y);
		WriteCoord (MSG_BROADCAST, self.origin_z);
	}
One thing I should add is decreasing the damage after each ricochet and maybe a bounced spike should do some sort of small damage instead of none.

Let me know what you think!
Good God! You shot my leg off!
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Post by qbism »

Cool, Junrall!

Just gave it a quick try and it works. Can bounce back and damage self. Sticks to plats. Fun!
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Re: Ricocheting Spikes

Post by leileilol »

Junrall wrote:maybe Darkplaces has a better/simpler way of doing something?
MOVETYPE_BOUNCEMISSILE :)
i should not be here
Teiman
Posts: 311
Joined: Sun Jun 03, 2007 9:39 am

Post by Teiman »

What is that ginormeous code post? It bigger than some 8 bits computers.
Junrall
Posts: 191
Joined: Mon Sep 21, 2009 12:27 am
Location: North West Oregon, USA
Contact:

Post by Junrall »

Don't know if you noticed... the spikes will also stick to and ricochet off of exploding boxes... at least the first few will then the box will explode! Kinda cool to see as it's not a normal thing that happens!
leileilol wrote:
Junrall wrote:maybe Darkplaces has a better/simpler way of doing something?
MOVETYPE_BOUNCEMISSILE :)
Yep, that's what I used... I love this movetype!

I do have a rough version of the same ricocheting spikes that do not use MOVETYPE_BOUNCEMISSILE... what a pain!
Good God! You shot my leg off!
Post Reply