Quick Grenade

From Valve Developer Community
Jump to: navigation, search

This is a copy and paste tutorial for adding a quick grenade function seen in many newer games. What this will do is simply holster the viewmodel, create a second viewmodel with the v_grenade model, play the throw animation, remove the second viewmodel then draw the first viewmodel. Has been tested on a near-new Source 2013 codebase.

Requirements

  • A brain (This one is REALLY important)
  • Knowledge of how to copy and paste text

NOTES

For this to work, the grenade model needs an ACT_VM_THROW activity and the weapon needs a ACT_VM_HOLSTER and ACT_VM_DRAW activities. Otherwise the animation will not play. The animation will also jump on the crossbow. I believe this is because of the ItemPostFrame or something similar resetting the viewmodel back to ACT_VM_IDLE. The following weapons currently have problems:

  • SMG: I believe there are missing animations, specifically ACT_VM_HOLSTER
  • Crossbow: As I said, I believe the problem lies with the animation being reset to ACT_VM_IDLE

I will fix these in the future though sadly now I have to return to my studies. If somebody else can do this in the meantime I would be grateful.

Creating the throw button

First file is in_buttons.h. Add this to the end:

#define IN_THROWGRENADE (1 << 26)

NOTE: You see how the last number on that line is 26? Well if you look at the in_buttons file you will see that the last number in each line counts upwards from 0. So for instance if the last line on the defines is 30, your new line should end with 31. Got that?

Second piece of code is in in_main.cpp. First thing you're going to have to search for is a big clump of lines starting with CalcButtonBits. It should start at approximately line 1457.

CalcButtonBits(bits, IN_THROWGRENADE, s_ClearInputState, &in_throwgrenade, bResetState);

Now we have to jump up to the top of the file and define in_throwgrenade. Just below the includes and convars there should be a bunch of lines starting with kbutton_t. We just add this code to the bottom of the final kbutton_t.

kbutton_t	in_throwgrenade;

Then we go down a bit further to where all the keys are worked out. Just search for void IN_Attack3Up and stick this directly below it.

void IN_ThrowGrenadeDown(const CCommand &args) { KeyDown(&in_throwgrenade, args[1]); }
void IN_ThrowGrenadeUp(const CCommand &args) { KeyUp(&in_throwgrenade, args[1]); }

Now we search for where all the code can be called by the player with ConCommands. The lines should start with "static ConCommand" at approximately line 1570. Put this after the last "static ConCommand"

static ConCommand startgrenadethrow("+throwgrenade", IN_ThrowGrenadeDown);
static ConCommand endgrenadethrow("-throwgrenade", IN_ThrowGrenadeUp);

The Actual Function

First file is hl2_player.h (or hl2mp_player.h or sdk_player.h)

Go down to line 263 and you should see the code declaration

void  HandleSpeedChanges( void );

If you don't, search for it.

Once you've found that line, paste these underneath it.

void  HandleThrowGrenade(void);
void  ThrowGrenade(void);
void  CreateGrenade(void);

Then scroll down a bit right to the end of that class where you should see the line

friend class CHL2GameMovement;

Again, if you don't, search for it

And again, paste these underneath.

float				timeholster;
float				timethrow;
float				timedeploy;
bool				WantThrow;

Now we move to hl2_player.cpp.

First of all, at the top of the file add

#include "grenade_frag.h"

with all the other includes (make sure it is above #include "tier0/memdbgon.h"!!)

You can put this anywhere in the file, I put it underneath HandleSpeedChanges.

void CHL2_Player::HandleThrowGrenade(void)
{
	if ((m_afButtonPressed & IN_THROWGRENADE) && !WantThrow && HasAnyAmmoOfType(12) && HasWeapons())
	{
		timeholster = NULL;
		timethrow = NULL;
		timedeploy = NULL;
		WantThrow = true;
	}

	ThrowGrenade();
}

void CHL2_Player::ThrowGrenade(void)
{
	if (WantThrow)
	{
		CBaseViewModel *vm = GetViewModel(0);
		CBaseViewModel *vm2 = GetViewModel(1);

		//2nd viewmodel creation
		if (!vm2)
		{
			CreateViewModel(1);
			vm2 = GetViewModel(1);
		}

		//HOLSTER SEQUENCING
		int sequence1 = vm->SelectWeightedSequence(ACT_VM_HOLSTER);
		if ((timeholster == NULL) && (sequence1 >= 0))
		{
			vm->SendViewModelMatchingSequence(sequence1);
			timeholster = (gpGlobals->curtime + vm->SequenceDuration(sequence1) + 0.5f);
		}

		//THROW SEQUENCING
		if ((timeholster < gpGlobals->curtime) && (timeholster != NULL))
		{
			vm->AddEffects(EF_NODRAW);
			vm2->SetWeaponModel("models/weapons/v_grenade.mdl", NULL);
			
			
			int sequence2 = vm2->SelectWeightedSequence(ACT_VM_THROW);
			if ((timethrow == NULL) && (sequence2 >= 0))
			{
				vm2->SendViewModelMatchingSequence(sequence2);
				timethrow = (gpGlobals->curtime + vm2->SequenceDuration(sequence2));
				CreateGrenade();
			}
		}

		if ((timethrow < gpGlobals->curtime) && (timethrow != NULL))
		{
			vm2->SetWeaponModel(NULL, NULL);
			UTIL_RemoveImmediate(vm2);
			vm->RemoveEffects(EF_NODRAW);
			int sequence3 = vm->SelectWeightedSequence(ACT_VM_DRAW);
			if ((timedeploy == NULL) && (sequence3 >= 0))
			{
				vm->SendViewModelMatchingSequence(sequence3);
				timedeploy = (gpGlobals->curtime + vm->SequenceDuration(sequence3));
			}
		}

		if ((timedeploy < gpGlobals->curtime) && (timedeploy != NULL))
		{
			//Successfully Thrown A Grenade! Decrement ammo
			RemoveAmmo(1, 12);
			WantThrow = false;
		}
	}
}

void CHL2_Player::CreateGrenade(void)
{
	Vector	vecEye = EyePosition();
	Vector	vForward, vRight;

	EyeVectors(&vForward, &vRight, NULL);
	Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f;
	trace_t tr;

	UTIL_TraceHull(vecEye, vecSrc, -Vector(4.0f + 2, 4.0f + 2, 4.0f + 2), Vector(4.0f + 2, 4.0f + 2, 4.0f + 2),
		PhysicsSolidMaskForEntity(), this, GetCollisionGroup(), &tr);

	if (tr.DidHit())
	{
		vecSrc = tr.endpos;
	}
	vForward[2] += 0.1f;

	Vector vecThrow;
	GetVelocity(&vecThrow, NULL);
	vecThrow += vForward * 1200;
	Fraggrenade_Create(vecSrc, vec3_angle, vecThrow, AngularImpulse(600, random->RandomInt(-1200, 1200), 0), this, 3.0f, false);

	gamestats->Event_WeaponFired(this, true, GetClassname());
}

Okay, you have all of that? It compiles? If not, leave me an angry message telling me why I should not exist here and I'll get right onto fixing it. If so, start the game, bind a key to +throwgrenade, give yourself some grenades and a weapon (just use "impulse 101" to give you everything) and see what happens. Hopefully the gun should holster, then throw a grenade then draw the weapon again.

Credits

  • One tired programmer who hasn't had his coffee fix
  • Valve

Editing/Usage

  • Edit this code if there is a better way to implement this or if you see a fix to a problem that has arisen.
  • Use wherever you want