Adding Call of Duty Style Low Health Regeneration
This is a copy and paste tutorial to add Call of Duty's Low Health Regeneration for single-player.
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()

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" } }