Adding Call of Duty Style Low Health Regeneration

From Valve Developer Community
Jump to navigation Jump to search

This is a copy and paste tutorial to add Call of Duty's Low Health Regeneration for single-player.

Demonstration

This feature is inspired by Regenerating Health.

With this feature, Medkit, and Health Chargers are kinda pointless

How does this feature work?

  • Check if the health is below the max health (100 hp) or below 25 hp (critical health state)
  • Start the last damage timer for 5 seconds. It will reset each time player take damage so hide in the safe place
  • While in critical health state, the player will experience breathing like someone in pain sound.
  • After the last damage timer ends, the regeneration function will start and the health will be boosted to 100 again.
  • Repeat cycle :>

Implement

First, open up player.h and search this function

float ConsumeMovementTimeForUserCmdProcessing( float flTimeNeeded )
	{
		if ( m_flMovementTimeForUserCmdProcessingRemaining <= 0.0f )
		{
			return 0.0f;
		}
		else if ( flTimeNeeded > m_flMovementTimeForUserCmdProcessingRemaining + FLT_EPSILON )
		{
			float flResult = m_flMovementTimeForUserCmdProcessingRemaining;
			m_flMovementTimeForUserCmdProcessingRemaining = 0.0f;
			return flResult;
		}
		else
		{
			m_flMovementTimeForUserCmdProcessingRemaining -= flTimeNeeded;
			if ( m_flMovementTimeForUserCmdProcessingRemaining < 0.0f )
				m_flMovementTimeForUserCmdProcessingRemaining = 0.0f;
			return flTimeNeeded;
		}
	}

Under that function, add this lines of code:

void HandleFastRegen();
float m_flLastDamageTime;
bool m_bFastRegenActive;
float m_flFastRegenStartTime;
float m_flNextFastRegenTime;
bool m_isPlayerNearDying = false;
bool m_bBuzzingSoundActive = false;
float m_flBuzzingSoundEndTime;
float m_flNextLowHealthSoundTime;
float m_flStartReliefSoundTime;

player.cpp

Then, we open up the player.cpp

DEFINE_FIELD( m_tbdPrev, FIELD_TIME ),

Below that code, we add this

DEFINE_FIELD(m_flLastDamageTime, FIELD_TIME),
DEFINE_FIELD(m_isPlayerNearDying, FIELD_BOOLEAN),

CBasePlayer::CBasePlayer()

Note.pngNote: I forgot to add this!

On CBasePlayer::CBasePlayer() under m_iReplayEntity = 0; We add this

m_flNextLowHealthSoundTime = 0.0f;
m_flStartReliefSoundTime = 0.0f;

OnTakeDamage()

Look up to OnTakeDamage function and find this code

if ( bitsDamage & DMG_BLAST )
	{
		OnDamagedByExplosion( info );
	}

Then, after that code. Add this code

if (GetHealth() < 100) {
	m_flLastDamageTime = gpGlobals->curtime;
}

if (IsAlive() && GetHealth() < 100 /* && !m_bFastRegenActive*/) {
	
	m_bFastRegenActive = true;
	// HUGAMOD: Start regeneration after 5 seconds
	// but it will reset each time you take damage
	m_flFastRegenStartTime = gpGlobals->curtime + 5.0f;
	m_flNextFastRegenTime = m_flFastRegenStartTime;
}
float flDamage = info.GetDamage();
if (GetHealth() < 25) {
	color32 red = { 128, 0, 0, 128 };
	UTIL_ScreenFade(this, red, 2.15f, 5.0f, FFADE_IN);

// HUGAMOD: Commented out ugly PunchAngle
//ViewPunch(QAngle(random->RandomInt(-2, 2), random->RandomInt(-2, 2), random->RandomInt(-2, 2)));
	if (!m_isPlayerNearDying)
	{
		// HUGAMOD: Checks if the player is in a vehicle so that
		// the player won't get punchangle, it will bug the view
		// if you remove this check (i already bug-tested it :>)
		if (!this->IsInAVehicle()) {
			// HUGAMOD: Ported CSS Headshot PunchAngle
			flDamage *= 4;
			QAngle punchAngle = GetPunchAngle();
			punchAngle.x = flDamage * -0.5;

			if (punchAngle.x < -12)
				punchAngle.x = -12;

			punchAngle.z = flDamage * random->RandomFloat(-1, 1);

			if (punchAngle.z < -9)
				punchAngle.z = -9;

			else if (punchAngle.z > 9)
				punchAngle.z = 9;

			SetPunchAngle(punchAngle);
		}

		EmitSound("Flesh.Headshot");

		m_isPlayerNearDying = true;
	}

	if (!m_bBuzzingSoundActive)
	{
		int effect = random->RandomInt(32, 34);
		CSingleUserRecipientFilter user(this);
		enginesound->SetPlayerDSP(user, effect, false);
		m_flBuzzingSoundEndTime = gpGlobals->curtime + 5.0f;
		m_bBuzzingSoundActive = true;
	}
}

HandleFastRegen()

Below the OnTakeDamage() function, we create another function called HandleFastRegen()

void CBasePlayer::HandleFastRegen()
{
	
	if (gpGlobals->curtime - m_flLastDamageTime < 5.0f)
	{
		// Not enough time has passed, do not start regeneration
		return;
	}
	if (m_bFastRegenActive && gpGlobals->curtime >= m_flFastRegenStartTime)
	{
		if (gpGlobals->curtime >= m_flNextFastRegenTime)
		{
			// Add 5 health each time
			TakeHealth(5, DMG_GENERIC); 
			// Set the next regeneration time for 0.1 seconds
			m_flNextFastRegenTime = gpGlobals->curtime + 0.1f; 
			m_isPlayerNearDying = false;
			// Stop the regeneration if the health reaches 100
			if (GetHealth() >= 100)
			{
				m_bFastRegenActive = false;
			}
			
			if (m_bBuzzingSoundActive && gpGlobals->curtime >= m_flBuzzingSoundEndTime)
			{
				// HUGAMOD: Delays the relief breathing sound 
				// for 0.45 - 0.5 seconds because it will collide with
				// the hurt breathing sound
				m_flStartReliefSoundTime = gpGlobals->curtime + RandomFloat(0.45, 0.5);
				m_bBuzzingSoundActive = false;
			}
		}
	}
	
}

PostThink()

Then, we go to the PostThink function and find this code

#if !defined( NO_ENTITY_PREDICTION )
	// Even if dead simulate entities
	SimulatePlayerSimulatedEntities();
#endif

Below, we add this code

// HUGAMOD: Dead player will ignore this
if (IsAlive()) {
	if (GetHealth() < 25)
	{
		if (gpGlobals->curtime >= m_flNextLowHealthSoundTime)
		{
			// Play hurt breath sound
			EmitSound("Player.Breathhurt");

			// Set the next hurt breath sound time
			m_flNextLowHealthSoundTime = gpGlobals->curtime + 1.0f; // Interval 1 detik
		}
	}
	else
	{
		if (m_flStartReliefSoundTime > 0.0f && gpGlobals->curtime >= m_flStartReliefSoundTime)
		{
			EmitSound("Player.Breathbetter");

			// Reset relief breath time to prevent looping
			m_flStartReliefSoundTime = 0.0f;
		}
		StopSound("Player.Breathhurt"); // Stop breath hurt sound
		// Reset breath hurt time to prevent looping
		m_flNextLowHealthSoundTime = 0.0f;
	}
}
HandleFastRegen();

Lastly, we must precache the sound, you may want to precache the sound either in player.cpp or hl2_player.cpp

PrecacheScriptSound("Player.Breathhurt");
PrecacheScriptSound("Player.Breathbetter");
PrecacheScriptSound("Flesh.Headshot");

Optional

Oh, you can customize the sound to your liking. I personally use this for my custom mod. Don't forget to precache the sound with sound you want to change!

my sound scripts
 
"Player.Breathhurt"
{
	"channel"		"CHAN_STATIC"
	"volume"		"1.0"
	"soundlevel"	"SNDLVL_GUNFIRE"
	"pitch"			"PITCH_NORM"

	"rndwave"
	{
		"wave"		"player/h1_misc/player_breathhurt1.wav"
		"wave"		"player/h1_misc/player_breathhurt2.wav"
		"wave"		"player/h1_misc/player_breathhurt3.wav"
		"wave"		"player/h1_misc/player_breathhurt4.wav"
		"wave"		"player/h1_misc/player_breathhurt5.wav"
		"wave"		"player/h1_misc/player_breathhurt6.wav"
		"wave"		"player/h1_misc/player_breathhurt7.wav"
	}

	
}

"Player.Breathbetter"
{
	"channel"		"CHAN_STATIC"
	"volume"		"1.0"
	"soundlevel"	"SNDLVL_GUNFIRE"
	"pitch"			"PITCH_NORM"


	
	"rndwave"
	{
		"wave"		"player/h1_misc/player_breathbetter1.wav"
		"wave"		"player/h1_misc/player_breathbetter2.wav"
		"wave"		"player/h1_misc/player_breathbetter3.wav"
        }

	
}