Chargeable weapons

Discuss programming in the QuakeC language.
Post Reply
Senban
Posts: 19
Joined: Sat Apr 14, 2007 8:04 pm

Chargeable weapons

Post by Senban »

I am working on code for hand grenades for use in my mod (SuperCoop). I want to make holding down the attack button charge up the power of the throw and releasing the attack button launch the grenade (think Worms). Does anyone have any ideas how to do this, or can anyone think of a weapon mod that does something similar with .qc files that I could look at?
Lardarse
Posts: 266
Joined: Sat Nov 05, 2005 1:58 pm
Location: Bristol, UK

Post by Lardarse »

Lets try something like this:

Check for self.button0 being pressed. If it is, set a flag, and then set a variable to the current time.

While the flag is set, dheck for the button being released instead of being pressed. When it is relieased, time-that variable we created contains the length of time that the button was pressed for.

I don't know how to miodify this to throw the grenade using an impulse, though...
Senban
Posts: 19
Joined: Sat Apr 14, 2007 8:04 pm

Post by Senban »

The problem I'm having is detecting the release of the attack button.
I've put an

Code: Select all

if (!self.button0)
case in the weapon frame function but it dosn't seem to be picking up the buttons release.
Hand grenades are working as a seperate weapon (as in Quake II) so doing it with impulse commands isn't an issue.
Dr. Shadowborg
InsideQC Staff
Posts: 1120
Joined: Sat Oct 16, 2004 3:34 pm

Post by Dr. Shadowborg »

Have a look at player_nail1() in player.qc. (i.e. it goes to player_nail2() then loops back to player_nail1() again.)

That should give you a few ideas. Also, you may want to put a check in w_setcurrentammo() in weapons.qc so that it doesn't reset your weaponframe to 0 when you pick up an item.
Senban
Posts: 19
Joined: Sat Apr 14, 2007 8:04 pm

Post by Senban »

Thanks guys, I've got it working now. When I get the fine tuning done I'll post the code.
Senban
Posts: 19
Joined: Sat Apr 14, 2007 8:04 pm

Grenade Tutorial

Post by Senban »

This gives you a unique and entertaining secondary weapon. You select it by pressing button 6 twice (or once if you don't have the grenade launcher yet.)
Unlike quakes other weapons holding the fire button down charges the power of the shot and releasing it lobs (or simply drops, depending on how long you charged it up for) the grenade. To make things even more fun if you launch the grenade within arms reach of your target you will attach the grenade to them.

Step 1. Name rank and number.
DEFS.QC
Open defs.qc and make these the last two lines

Code: Select all

float HAND_GRENADES = 3;
.float charge;
Setting HAND_GRENADES to a non binary number means that it doesn't take up a precious item slot, which it doesn't need because you won't be doing any self.items checks with it.

Step 2. Meat and Veg.
WEAPONS.QC

Add this to the precached sounds list at the top of weapons.qc

Code: Select all

precache_sound ("buttons/switch04.wav");


In GrenadeTouch change: if (other.takedamage == DAMAGE_AIM)
to:

Code: Select all

if ((other.takedamage == DAMAGE_AIM) && (self.classname != "hand_grenade"))
This is just to disable the grenade launchers ability to detonate when it hits a monster.

The next set of functions goes in weapons.qc, after "void() superspike_touch ="

Code: Select all

//=============
//HAND GRENADES
//=============
void() PrimeGrenade =
{
	if (self.charge == 0)
		sound (self, CHAN_WEAPON, "player/axhit2.wav", 0.5, ATTN_NORM);
	
	if (self.charge >= 8)
	{
		self.charge = 8;
		self.punchangle_x = -2;
	}
	else
	{
		self.charge = self.charge + 1;
		sprint (self, "*");
	}
	self.attack_finished = time + 0.1;
};
This pulls the pin out with a nice "tink" and starts powering up for the throw.
The sprint gives the player a rudimentary charge up meter.

Code: Select all

void() ThrowGrenade =
{
	local	entity missile, mpuff;
	
	self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
	
	sound (self, CHAN_BODY, "player/plyrjmp8.wav", 1, ATTN_NORM);

	missile = spawn ();
	missile.owner = self;
	missile.movetype = MOVETYPE_BOUNCE;
	missile.solid = SOLID_BBOX;	
		
// set missile speed	

	makevectors (self.v_angle);
		
        missile.velocity = v_forward*65 + v_up * 21.5 + crandom()*v_right*3 + crandom()*v_up*3;
	missile.velocity = missile.velocity * self.charge;
	
	missile.avelocity = '-50 -5 -5';
	missile.avelocity = missile.avelocity * self.charge;
	
	missile.angles = vectoangles(missile.velocity);
	
	missile.classname = "hand_grenade";
	missile.touch = GrenadeTouch;
	
// set missile duration
	missile.nextthink = time + 3;
	missile.think = GrenadeExplode;
	
	setmodel (missile, "progs/grenade.mdl");
	setsize (missile, '0 0 0', '0 0 0');		
	setorigin (missile, self.origin);

	self.charge = 0;
	sprint (self, "\n");
	self.attack_finished = time + 0.4;	
};
This is basically the grenade launcher code but with a different sound and modified velocity and fuse time. The velocity code is the interesting part here.
The sprint ends the charge up "meter".

Code: Select all

void() GrenadeStick =
{
	self.nextthink = time + 0.1;
	self.ammo_cells = self.ammo_cells - 1;
	
	if (self.ammo_cells < 1)
  		GrenadeExplode(); 
	
	self.velocity = self.enemy.velocity;
   	self.angles = self.enemy.angles;
   	self.origin = self.enemy.origin;
};
This bit owes a certain ammount to Homing Sticky Missiles v0.9 by Jonathan Avraham. The grenade is given .ammo_cells to use as a fuse. When the battery runs out the grenade explodes.

Code: Select all

void(entity victim) PlaceGrenade =
{
	local	entity missile, mpuff;	

	self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
	
	sound (victim, CHAN_BODY, "buttons/switch04.wav", 1, ATTN_NORM);
	
	missile = spawn ();
	missile.owner = self;
	missile.movetype = MOVETYPE_NOCLIP;
	missile.solid = SOLID_BBOX;
	
	missile.classname = "hand_grenade";
		
// set missile duration
	missile.nextthink = time + 0.1;
	missile.ammo_cells = 30;
	missile.think = GrenadeStick;
	missile.enemy = victim;	

	setmodel (missile, "progs/grenade.mdl");
	setsize (missile, '0 0 0', '0 0 0');		
	setorigin (missile, victim.origin);

	sprint (self, "\nIt's better to give than to recieve!\n");
	self.charge = 0;	
	self.attack_finished = time + 0.4;	
};
Basicaly a rehash of ThrowGrenade. Look at the way Quake handles nails and supernails to see how this could be done more efficiently.

Code: Select all

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

	makevectors (self.v_angle);
	source = self.origin + '0 0 16';
	traceline (source, source + v_forward*64, FALSE, self);
	if (trace_fraction == 1.0)
	{	
		ThrowGrenade();
		return;
	}
	org = trace_endpos - v_forward*4;	

	if ((trace_ent.flags == trace_ent.flags | FL_MONSTER) || (trace_ent.classname == "player"))	
		PlaceGrenade(trace_ent);
	else
		ThrowGrenade();
};
Simply determines whether grenade will be thrown or placed, based on the axe code.

Next add this to W_SetCurrentAmmo (I put it before the grenade launcher but you don't have to).

Code: Select all

	else if (self.weapon == HAND_GRENADES)
	{
		self.currentammo = self.ammo_rockets;
		self.weaponmodel = "";
		self.weaponframe = 0;
		self.items = self.items | IT_ROCKETS;
	}
This goes in W_Attack anywhere that seems sensible.

Code: Select all

	else if (self.weapon == HAND_GRENADES)
	{		
		PrimeGrenade();		
	}
This goes in W_ChangeWeapon before the grenade launcher.

Code: Select all

	else if ((self.impulse == 6) && (self.items != self.items | IT_GRENADE_LAUNCHER || self.weapon == IT_GRENADE_LAUNCHER))
	{
		if (self.ammo_rockets < 1)
			am = 1;
		else
		{
			self.weapon = HAND_GRENADES;
			self.charge = 0;		
			W_SetCurrentAmmo ();
			return;
		}
	}
This is one of the most important parts of the code as it allows you to select something without checking your items, it's also important to reset self.charge at this point or players would be able to carry charged grenades about with them which is silly.

These bits go between the grenade launcher and the super nailgun in CycleWeaponCommand and CycleWeaponReverseCommand respectively.

Code: Select all

		else if (self.weapon == HAND_GRENADES)
		{
			self.weapon = IT_GRENADE_LAUNCHER;
			if (self.ammo_rockets < 1)
				am = 1;
		}

Code: Select all

		else if (self.weapon == HAND_GRENADES)
		{
			self.weapon = IT_SUPER_NAILGUN;
			if (self.ammo_nails < 2)
				am = 1;
		}
This code does not allow you to select hand grenades by scrolling, but it means you can use weapon scrolling to switch to a different weapon from the grenades.

Step 3. Tidy up and got to bed.
CLIENT.QC

Put this in PlayerPreThink between the death code and the jump code.

Code: Select all

	if (self.weapon == HAND_GRENADES && self.charge > 0 && !self.button0)
	{
		player_rocket1();		
		HandGrenade();
	}
This is the most important piece of code as it let's the player perform an action by releasing the button rather than by pressing it.

Replace the grenade launcher in ClientObituary obit with this lot.

Code: Select all

				if (targ.weapon == IT_GRENADE_LAUNCHER)
				{
					bprint (" tries to put the pin back in\n");
					return;
				}
				if (targ.weapon == HAND_GRENADES)
				{
					bprint (" forgot where he put his hand grenade\n");
					return;
				}
And finally put this after the Lightning Gun obit.

Code: Select all

				if (rnum == HAND_GRENADES)
				{		
					deathstring = " was blown away by ";
					deathstring2 = "'s hand grenade\n";
				}
Post Reply