StaminaHandler.c
class StaminaConsumerclass StaminaConsumersclass StaminaModifierclass StaminaModifierExponentialclass StaminaModifiersclass StaminaHandlerenum EStaminaMultiplierTypestypedef StaminaCooldownParams
enum EStaminaMultiplierTypes
{
MASK = 1,
FATIGUE,
EPINEPHRINE,
DROWNING,
VOMIT_EXHAUSTION,
DISEASE_PNEUMONIA
}
/**@class Stamina Consumer
* @brief Holds information about Stamina Consumer
*
* @param[in] threshold value needed to allow consume stamina
* @param[in] state keeps state of the consumer non-depleted/depleted
*/
class StaminaConsumer
{
protected float m_ActivationThreshold;
protected float m_DrainThreshold
protected bool m_State;
void StaminaConsumer(float threshold, float threshold2, bool state)
{
m_ActivationThreshold = threshold; //can be activated if above this threshold
m_DrainThreshold = threshold2; //can continually drain until it reaches this threshold
m_State = state;
}
bool GetState() { return m_State; }
void SetState(bool state) { m_State = state; }
float GetActivationThreshold() { return m_ActivationThreshold; }
void SetActivationThreshold(float threshold) { m_ActivationThreshold = threshold; }
float GetDrainThreshold() { return m_DrainThreshold; }
void SetDrainThreshold(float threshold) { m_DrainThreshold = threshold; }
}
class StaminaConsumers
{
protected ref map<EStaminaConsumers, ref StaminaConsumer> m_StaminaConsumers;
void StaminaConsumers()
{
m_StaminaConsumers = new map<EStaminaConsumers, ref StaminaConsumer>;
}
void RegisterConsumer(EStaminaConsumers consumer, float threshold, float depletion_threshold = -1)
{
if (depletion_threshold == -1)
{
depletion_threshold = threshold;
}
if ( !m_StaminaConsumers.Contains(consumer) )
{
//! init of StaminaConsumer - threshold, state
StaminaConsumer sc = new StaminaConsumer(threshold, depletion_threshold, true);
m_StaminaConsumers.Set(consumer, sc);
}
}
bool HasEnoughStaminaFor(EStaminaConsumers consumer, float curStamina, bool isDepleted, float cap)
{
StaminaConsumer sc;
if (m_StaminaConsumers && m_StaminaConsumers.Find(consumer, sc))
{
if (consumer != EStaminaConsumers.SPRINT)
{
if (isDepleted || (curStamina < sc.GetDrainThreshold()))
{
sc.SetState(false);
return false;
}
}
else
{
if (!isDepleted)
{
if (sc.GetState())
{
sc.SetState(true);
return true;
}
}
else
{
sc.SetState(false);
return false;
}
}
if (curStamina > sc.GetDrainThreshold() || curStamina == cap) //Sometimes player can't go up to drain threshold
{
sc.SetState(true);
return true;
}
}
return false;
}
bool HasEnoughStaminaToStart(EStaminaConsumers consumer, float curStamina, bool isDepleted, float cap)
{
StaminaConsumer sc;
if (m_StaminaConsumers && m_StaminaConsumers.Find(consumer, sc))
{
if ((isDepleted || (curStamina < sc.GetActivationThreshold() && curStamina < cap)))
{
sc.SetState(false);
return false;
}
else
{
sc.SetState(true);
return true;
}
}
return false;
}
}
/**@class Stamina Modifier
* @brief Holds information about Stamina Modifier
*
* @param[in] type calculation method
* @param[in] minValue min value substracted from stamina (if type > 0)
* @param[in] maxValue max value substracted from stamina
* @param[in] cooldown how many seconds will not be the stamina replenishing after depleting
*/
class StaminaModifier
{
bool m_InUse = false;
int m_Type;
float m_MinValue, m_MaxValue, m_Multiplier, m_Cooldown, m_StartTime, m_StartTimeAdjustment, m_Duration, m_ProgressTime, m_Tick;
void StaminaModifier(int type, float min, float max, float cooldown, float startTime = 0, float duration = 0)
{
m_Type = type;
m_MinValue = min;
m_MaxValue = max;
m_Cooldown = cooldown;
m_StartTimeAdjustment = startTime;
m_Duration = duration;
m_Tick = 1;
}
int GetType() { return m_Type; }
float GetMinValue() { return m_MinValue; }
void SetMinValue(float val) { m_MinValue = val; }
float GetMaxValue() { return m_MaxValue; }
void SetMaxValue(float val) { m_MaxValue = val; }
float GetCooldown() { return m_Cooldown; }
void SetCooldown(float val) { m_Cooldown = val; }
float GetStartTime() { return m_StartTime; } //Actual game time (progressive modifiers only)
void SetStartTime(float val) { m_StartTime = val; }
float GetStartTimeAdjustment() {return m_StartTimeAdjustment;} //adjustment to current time (progressive modifiers only)
float GetDuration() { return m_Duration; }
float GetDurationAdjusted() { return m_Duration / m_Tick; }
bool IsInUse() { return m_InUse; }
void SetInUse(bool val) { m_InUse = val; }
float GetRunTime() { return m_ProgressTime; }
void AddRunTime(float val) { m_ProgressTime += val; }
void SetRunTimeTick(float val) { m_Tick = val; }
void ResetRunTime() { m_ProgressTime = 0 }
}
class StaminaModifierExponential : StaminaModifier
{
protected ref SMDataExponential m_SMDataEx;
float GetBaseValue() { return m_SMDataEx.m_BaseValue; }
float GetExponent() { return m_SMDataEx.m_Exponent; }
float GetMultiplier() { return m_SMDataEx.m_Multiplier; }
override float GetCooldown() { return m_SMDataEx.m_Cooldown; }
override float GetStartTimeAdjustment() { return m_SMDataEx.m_StartTimeAdjustment; }
override float GetDuration() { return m_SMDataEx.m_Duration; }
override float GetDurationAdjusted() { return m_SMDataEx.m_Duration / m_Tick; }
void SetData(SMDataExponential data) { m_SMDataEx = data; }
}
class StaminaModifiers
{
const int FIXED = 0;
const int RANDOMIZED = 1;
const int LINEAR = 2; //Useful ONLY for regular, over-time stamina drain
const int EXPONENTIAL = 3; //Useful ONLY for regular, over-time stamina drain
protected ref map<EStaminaModifiers, ref StaminaModifier> m_StaminaModifiers;
void StaminaModifiers()
{
m_StaminaModifiers = new map<EStaminaModifiers, ref StaminaModifier>;
}
//! register single value modifier - depletes stamina for that value
void RegisterFixed(EStaminaModifiers modifier, float value, float cooldown = GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION)
{
if ( !m_StaminaModifiers.Contains(modifier) )
{
//! init of StaminaModifier - type and min, max values (min is not relevant for that type)
StaminaModifier sm = new StaminaModifier(FIXED, -1, value, cooldown);
m_StaminaModifiers.Set(modifier, sm);
}
}
//! register randomized modifier - stamina will be depleted by value between min and max value;
void RegisterRandomized(EStaminaModifiers modifier, float minValue, float maxValue, float cooldown = GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION)
{
if ( !m_StaminaModifiers.Contains(modifier) )
{
//! init of StaminaModifier - type, min, max values
StaminaModifier sm = new StaminaModifier(RANDOMIZED, minValue, maxValue, cooldown);
m_StaminaModifiers.Set(modifier, sm);
}
}
//! register lerped modifier - depletes stamina for startValue, and, after a startTime, lerps to endValue over duration
void RegisterLinear(EStaminaModifiers modifier, float startValue, float endValue, float startTime, float duration, float cooldown = GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION)
{
StaminaModifier sm = new StaminaModifier(LINEAR, startValue, endValue, cooldown, startTime, duration);
m_StaminaModifiers.Set(modifier, sm);
}
//! register exponential modifier - depletes stamina for startValue, and, after a startTime, lerps from 0 to exponent over duration
void RegisterExponential(EStaminaModifiers modifier, float startValue, float exponent, float startTime, float duration, float cooldown = GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION)
{
StaminaModifier sm = new StaminaModifier(EXPONENTIAL, startValue, exponent, cooldown, startTime, duration);
m_StaminaModifiers.Set(modifier, sm);
}
//! register exponential modifier, extended parameters
void RegisterExponentialEx(EStaminaModifiers modifier, SMDataExponential data)
{
StaminaModifierExponential smex = new StaminaModifierExponential(EXPONENTIAL, data.m_BaseValue, data.m_Exponent, data.m_Cooldown, data.m_StartTimeAdjustment, data.m_Duration);
smex.SetData(data);
m_StaminaModifiers.Set(modifier, smex);
}
StaminaModifier GetModifierData(EStaminaModifiers modifier)
{
return m_StaminaModifiers.Get(modifier);
}
}
typedef Param1<float> StaminaCooldownParams;
class StaminaHandler
{
protected const float STAMINA_GAIN_THRESHOLD = 25.0;
protected const float STAMINA_GAIN_MODIFIER = 10.0;
protected const float LOAD_DIFF_UPDATE_THRESHOLD = 1.0;
protected const float LOAD_DIFF_CAP_UPDATE_THRESHOLD = 0.01;
protected bool m_InitUpdate;
protected float m_PlayerLoad;
protected float m_StaminaDelta;
protected float m_Stamina;
protected float m_StaminaSynced; //guaranteed to be identical on server and client
protected float m_StaminaCap;
protected float m_StaminaCapSynced; //guaranteed to be identical on server and client
protected float m_StaminaDepletionMultiplierNext; // value being sent via SJ
protected float m_StaminaRecoveryMultiplierNext; // value being sent via SJ
protected float m_StaminaDepletion;
protected float m_StaminaDepletionMultiplier; //controls depletion rate
protected float m_StaminaRecoveryMultiplier; //controls recovery rate
protected float m_Time;
protected ref Param3<float,float,bool> m_StaminaParams;
protected ref HumanMovementState m_State;
protected SHumanCommandMoveSettings m_HumanMoveSettings;
protected PlayerBase m_Player;
protected bool m_StaminaDepleted;
ref map<EStaminaMultiplierTypes, float> m_RegisteredDepletionModifiers;
ref set<EStaminaMultiplierTypes> m_ActiveDepletionModifiers;
ref map<EStaminaMultiplierTypes, float> m_RegisteredRecoveryModifiers;
ref set<EStaminaMultiplierTypes> m_ActiveRecoveryModifiers;
protected bool m_IsInCooldown;
protected ref StaminaConsumers m_StaminaConsumers;
protected ref StaminaModifiers m_StaminaModifiers;
protected float m_LastPlayerLoad = -1;
protected float m_CachedStaminaGainBonus;
protected bool m_IsStaminaGainBonusCached;
protected ref map<int, ref StaminaCooldownParams> m_CooldownMap;
// Movement state caching members
protected int m_LastCommandTypeId = -1;
protected int m_LastMovementIdx = -1;
protected int m_LastStanceIdx = -1;
protected bool m_MovementStateChanged = true; // Force initial processing
protected bool m_ForceLoadRecheck;
#ifdef DIAG_DEVELOPER
protected bool m_StaminaDisabled;
protected float m_DebugTimer = 0;
protected int m_LastPoolInUse = 0;
#endif
void StaminaHandler(PlayerBase player)
{
if (g_Game.IsServer() || !g_Game.IsMultiplayer())
m_StaminaParams = new Param3<float,float,bool>(0, 0, false);
m_State = new HumanMovementState();
m_Player = player;
if (m_Player)
{
m_Player.GetOnPlayerLoadChanged().Insert(MarkLoadDirty);
}
m_InitUpdate = false;
m_Stamina = CfgGameplayHandler.GetStaminaMax();
m_StaminaSynced = CfgGameplayHandler.GetStaminaMax();
m_StaminaCap = CfgGameplayHandler.GetStaminaMax();
m_StaminaCapSynced = CfgGameplayHandler.GetStaminaMax();
m_StaminaDepletionMultiplierNext = 1;
m_StaminaRecoveryMultiplierNext = 1;
m_StaminaDepletion = 0;
m_StaminaDepletionMultiplier = 1;
m_StaminaRecoveryMultiplier = 1;
m_Time = 0;
m_StaminaDepleted = false;
m_IsInCooldown = false;
m_HumanMoveSettings = m_Player.GetDayZPlayerType().CommandMoveSettingsW();
m_IsStaminaGainBonusCached = false;
RegisterStaminaConsumers();
RegisterStaminaModifiers();
m_CooldownMap = new map<int, ref StaminaCooldownParams>();
//----------------- depletion --------------------
m_RegisteredDepletionModifiers = new map<EStaminaMultiplierTypes, float>;
m_ActiveDepletionModifiers = new set<EStaminaMultiplierTypes>;
//----------------- recovery --------------------
m_RegisteredRecoveryModifiers = new map<EStaminaMultiplierTypes, float>;
m_ActiveRecoveryModifiers = new set<EStaminaMultiplierTypes>;
Init();
}
void ~StaminaHandler()
{
if (m_Player)
{
m_Player.GetOnPlayerLoadChanged().Remove(MarkLoadDirty);
}
if (m_CooldownMap)
{
m_CooldownMap.Clear();
}
}
void Init()
{
//----------------- depletion --------------------
m_RegisteredDepletionModifiers.Insert(EStaminaMultiplierTypes.MASK, MaskMdfr.STAMINA_DEPLETION_MODIFIER);
m_RegisteredDepletionModifiers.Insert(EStaminaMultiplierTypes.FATIGUE, FatigueMdfr.STAMINA_DEPLETION_MULTIPLIER);
m_RegisteredDepletionModifiers.Insert(EStaminaMultiplierTypes.EPINEPHRINE, EpinephrineMdfr.STAMINA_DEPLETION_MULTIPLIER);
m_RegisteredDepletionModifiers.Insert(EStaminaMultiplierTypes.VOMIT_EXHAUSTION, VomitSymptom.STAMINA_DEPLETION_MULTIPLIER);
m_RegisteredDepletionModifiers.Insert(EStaminaMultiplierTypes.DISEASE_PNEUMONIA, PneumoniaMdfr.STAMINA_DEPLETION_MULTIPLIER);
//----------------- recovery --------------------
m_RegisteredRecoveryModifiers.Insert(EStaminaMultiplierTypes.MASK, MaskMdfr.STAMINA_RECOVERY_MODIFIER);
m_RegisteredRecoveryModifiers.Insert(EStaminaMultiplierTypes.FATIGUE, FatigueMdfr.STAMINA_RECOVERY_MULTIPLIER);
m_RegisteredRecoveryModifiers.Insert(EStaminaMultiplierTypes.DROWNING, DrowningMdfr.STAMINA_RECOVERY_MULTIPLIER);
m_RegisteredRecoveryModifiers.Insert(EStaminaMultiplierTypes.VOMIT_EXHAUSTION, VomitSymptom.STAMINA_RECOVERY_MULTIPLIER);
m_RegisteredRecoveryModifiers.Insert(EStaminaMultiplierTypes.DISEASE_PNEUMONIA, PneumoniaMdfr.STAMINA_RECOVERY_MULTIPLIER);
}
void ActivateDepletionModifier(EStaminaMultiplierTypes type)
{
if (m_RegisteredDepletionModifiers.Contains(type))
{
m_ActiveDepletionModifiers.Insert(type);
RecalculateDepletionMultiplier();
}
else
{
Error("attempting to activate unregistered depletion modifier");
}
}
void DeactivateDepletionModifier(EStaminaMultiplierTypes type)
{
int index = m_ActiveDepletionModifiers.Find(type);
if (index != -1)
{
m_ActiveDepletionModifiers.Remove(index);
RecalculateDepletionMultiplier();
}
}
void RecalculateDepletionMultiplier()
{
float value = 1;
foreach (int multiplier: m_ActiveDepletionModifiers)
{
value *= m_RegisteredDepletionModifiers.Get(multiplier);
}
if (value != m_StaminaDepletionMultiplier)
SetDepletionMultiplier(value);
}
void ActivateRecoveryModifier(EStaminaMultiplierTypes type)
{
if (m_RegisteredRecoveryModifiers.Contains(type))
{
m_ActiveRecoveryModifiers.Insert(type);
RecalculateRecoveryMultiplier();
}
else
{
Error("attempting to activate unregistered recovery modifier");
}
}
void DeactivateRecoveryModifier(EStaminaMultiplierTypes type)
{
int index = m_ActiveRecoveryModifiers.Find(type);
if (index != -1)
{
m_ActiveRecoveryModifiers.Remove(index);
RecalculateRecoveryMultiplier();
}
}
void RecalculateRecoveryMultiplier()
{
float value = 1;
foreach (int multiplier: m_ActiveRecoveryModifiers)
{
value *= m_RegisteredRecoveryModifiers.Get(multiplier);
}
if (value != m_StaminaRecoveryMultiplier)
{
SetRecoveryMultiplier(value);
}
}
// Check if movement state has changed since last frame
protected bool HasMovementStateChanged()
{
// Only call GetMovementState if we need to check for changes
m_Player.GetMovementState(m_State);
bool changed = (m_State.m_CommandTypeId != m_LastCommandTypeId || m_State.m_iMovement != m_LastMovementIdx || m_State.m_iStanceIdx != m_LastStanceIdx);
if (changed)
{
// Update cached values
m_LastCommandTypeId = m_State.m_CommandTypeId;
m_LastMovementIdx = m_State.m_iMovement;
m_LastStanceIdx = m_State.m_iStanceIdx;
}
return changed;
}
void Update(float deltaT, int pCurrentCommandID)
{
#ifdef DIAG_DEVELOPER
if (m_StaminaDisabled)
return;
#endif
if (!m_Player)
return;
// Process cooldowns - subtract deltaT and remove expired ones
array<int> expiredCooldowns = new array<int>();
foreach (int modifier, StaminaCooldownParams cooldownParams : m_CooldownMap)
{
cooldownParams.param1 -= deltaT;
if (cooldownParams.param1 <= 0)
{
expiredCooldowns.Insert(modifier);
}
}
// Remove expired cooldowns
foreach (int expiredModifier : expiredCooldowns)
{
ResetCooldown(expiredModifier);
}
// Reset so we get current bonus value once in this update loop and then reuse the cached value within this update call
m_IsStaminaGainBonusCached = false;
// Check if movement state has changed
m_MovementStateChanged = HasMovementStateChanged();
bool isAiOrSingleplayer = m_Player.IsAuthorityOwner();
bool isServerOrSingleplayer = m_Player.IsAuthority();
// Calculates actual max stamina based on player's load
if (isServerOrSingleplayer)
{
float oldCap = m_StaminaCap;
bool forceLoadRecheck = m_ForceLoadRecheck;
m_ForceLoadRecheck = false;
m_PlayerLoad = m_Player.GetWeightEx(forceLoadRecheck);
float loadDiff = Math.AbsFloat(m_PlayerLoad - m_LastPlayerLoad);
if (forceLoadRecheck || loadDiff > LOAD_DIFF_UPDATE_THRESHOLD)
{
RecalculateStaminaCap();
m_LastPlayerLoad = m_PlayerLoad;
float capDiff = Math.AbsFloat(oldCap - m_StaminaCap);
if (capDiff > LOAD_DIFF_CAP_UPDATE_THRESHOLD)
{
if (m_Stamina > m_StaminaCap)
{
m_Stamina = m_StaminaCap;
}
m_StaminaSynced = m_Stamina;
m_StaminaCapSynced = m_StaminaCap;
SyncStaminaEx();
m_Time = 0;
}
}
}
else
{
PlayerBaseMove move = PlayerBaseMove.Cast(m_Player.GetNextMove());
if (move)
{
move.m_fStaminaValue = m_Stamina;
move.m_fStaminaCapacity = m_StaminaCap;
}
}
if (isAiOrSingleplayer)
{
m_StaminaSynced = m_Stamina;
m_StaminaCapSynced = m_StaminaCap;
}
// Process movement if: state changed, we have stamina delta, in cooldown, not initialized, or stamina is bewlow cap
if (m_MovementStateChanged || m_StaminaDelta != 0 || m_IsInCooldown || !m_InitUpdate || m_Stamina < m_StaminaCap)
ProcessMovementState();
// Sets current stamina & stores + syncs data with client
ProcessStaminaChanges(deltaT, isServerOrSingleplayer);
ApplyExhaustion();
CheckStaminaState();
m_StaminaDelta = 0;
m_StaminaDepletion = 0; // resets depletion modifier
m_IsStaminaGainBonusCached = false;
if (!m_InitUpdate)
m_InitUpdate = true;
}
protected void ProcessMovementState()
{
switch (m_State.m_CommandTypeId)
{
case DayZPlayerConstants.COMMANDID_MOVE:
StaminaProcessor_Move(m_State);
break;
case DayZPlayerConstants.COMMANDID_LADDER:
StaminaProcessor_Ladder(m_State);
break;
case DayZPlayerConstants.COMMANDID_SWIM:
StaminaProcessor_Swimming(m_State);
break;
case DayZPlayerConstants.COMMANDID_FALL:
case DayZPlayerConstants.COMMANDID_MELEE2:
case DayZPlayerConstants.COMMANDID_CLIMB:
break;
default:
if (!m_IsInCooldown)
{
m_StaminaDelta = GameConstants.STAMINA_GAIN_IDLE_PER_SEC;
}
break;
}
}
protected void ProcessStaminaChanges(float deltaT, bool isServerOrSingleplayer)
{
// Calculate stamina changes
float temp = m_StaminaDelta * deltaT;
if (temp < 0)
{
temp *= m_StaminaDepletionMultiplier;
}
else
{
temp *= m_StaminaRecoveryMultiplier;
}
// Apply stamina depletion first, then add/subtract the delta
float newStamina = m_Stamina - m_StaminaDepletion + temp;
// Clamp to valid range
m_Stamina = Math.Max(0, Math.Min(newStamina, m_StaminaCap));
// Handle floating-point precision issues - snap to cap if very close
if (m_Stamina > (m_StaminaCap - 0.01) && temp > 0)
{
m_Stamina = m_StaminaCap;
}
if (isServerOrSingleplayer)
{
m_Player.GetStatStamina().Set(m_Stamina);
m_Time += deltaT;
if (m_Time >= GameConstants.STAMINA_SYNC_RATE)
{
m_Time = 0;
SyncStaminaEx();
}
}
#ifndef SERVER
m_Player.SetStamina(m_StaminaSynced, m_StaminaCapSynced);
#endif
}
void RecalculateStaminaCap()
{
//! StaminaCap calculation starts when PlayerLoad exceeds STAMINA_WEIGHT_LIMIT_THRESHOLD
if (m_PlayerLoad >= CfgGameplayHandler.GetStaminaWeightLimitThreshold())
{
m_StaminaCap = Math.Max((CfgGameplayHandler.GetStaminaMax() - (((m_PlayerLoad - CfgGameplayHandler.GetStaminaWeightLimitThreshold())/GameConstants.STAMINA_KG_TO_GRAMS) * CfgGameplayHandler.GetStaminaKgToStaminaPercentPenalty())), CfgGameplayHandler.GetStaminaMinCap());
}
else
{
m_StaminaCap = CfgGameplayHandler.GetStaminaMax();
}
m_StaminaCapSynced = m_StaminaCap;
}
//! called from PlayerBase - syncs stamina values on server with client AND sets the value to match on server and client both (m_StaminaSynced guarantees identical values)
void OnSyncJuncture(int pJunctureID, ParamsReadContext pCtx)
{
switch (pJunctureID)
{
case DayZPlayerSyncJunctures.SJ_STAMINA:
float stamina;
float staminaCap;
bool cooldown;
if (!pCtx.Read(stamina) || !pCtx.Read(staminaCap) || !pCtx.Read(cooldown))
{
return;
}
m_Stamina = stamina; //?
m_StaminaSynced = stamina;
m_StaminaCap = staminaCap;
m_StaminaCapSynced = staminaCap;
if (m_Player.GetInstanceType() != DayZPlayerInstanceType.INSTANCETYPE_CLIENT)
{
return;
}
if (staminaCap != m_StaminaCap)
{
m_StaminaCap = staminaCap;
}
m_IsInCooldown = cooldown;
m_Player.SetStamina(m_Stamina, m_StaminaCap);
break;
case DayZPlayerSyncJunctures.SJ_STAMINA_MISC:
ReadAdditionalStaminaInfo(pCtx);
break;
}
}
protected void StaminaProcessor_Move(HumanMovementState pHumanMovementState)
{
switch (pHumanMovementState.m_iMovement)
{
case DayZPlayerConstants.MOVEMENTIDX_SPRINT: //sprint
if (pHumanMovementState.m_iStanceIdx == DayZPlayerConstants.STANCEIDX_ERECT)
{
m_StaminaDelta = -GameConstants.STAMINA_DRAIN_STANDING_SPRINT_PER_SEC * CfgGameplayHandler.GetSprintStaminaModifierErc();
SetCooldown(GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION);
break;
}
else if ( pHumanMovementState.m_iStanceIdx == DayZPlayerConstants.STANCEIDX_CROUCH)
{
m_StaminaDelta = -GameConstants.STAMINA_DRAIN_CROUCHED_SPRINT_PER_SEC * CfgGameplayHandler.GetSprintStaminaModifierCro();
SetCooldown(GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION);
break;
}
m_StaminaDelta = GameConstants.STAMINA_GAIN_JOG_PER_SEC;
break;
case DayZPlayerConstants.MOVEMENTIDX_RUN: //jog
if (m_Player.GetCurrentWaterLevel() >= m_HumanMoveSettings.m_fWaterLevelSpeedRectrictionHigh)
{
m_StaminaDelta = -GameConstants.STAMINA_DRAIN_STANDING_SPRINT_PER_SEC * CfgGameplayHandler.GetSprintStaminaModifierErc();
break;
}
if (!m_IsInCooldown)
{
m_StaminaDelta = (GameConstants.STAMINA_GAIN_JOG_PER_SEC + CalcStaminaGainBonus());
}
break;
case DayZPlayerConstants.MOVEMENTIDX_WALK: //walk
if (!m_IsInCooldown)
{
m_StaminaDelta = (GameConstants.STAMINA_GAIN_WALK_PER_SEC + CalcStaminaGainBonus());
}
break;
case DayZPlayerConstants.MOVEMENTIDX_IDLE: //idle
if (m_Player.IsRolling())
{
m_StaminaDelta = GameConstants.STAMINA_GAIN_ROLL_PER_SEC;
break;
}
if (!m_IsInCooldown)
{
m_StaminaDelta = (GameConstants.STAMINA_GAIN_IDLE_PER_SEC + CalcStaminaGainBonus());
}
break;
default:
if (!m_IsInCooldown)
{
m_StaminaDelta = GameConstants.STAMINA_GAIN_IDLE_PER_SEC;
}
break;
}
}
protected void StaminaProcessor_Ladder(HumanMovementState pHumanMovementState)
{
switch (pHumanMovementState.m_iMovement)
{
case 2: //climb up (fast)
m_StaminaDelta = -GameConstants.STAMINA_DRAIN_LADDER_FAST_PER_SEC * CfgGameplayHandler.GetSprintLadderStaminaModifier();
SetCooldown(GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION);
break;
case 1: //climb up (slow)
if (!m_IsInCooldown)
{
m_StaminaDelta = (GameConstants.STAMINA_GAIN_LADDER_PER_SEC + CalcStaminaGainBonus());
}
break;
default:
if (!m_IsInCooldown)
{
m_StaminaDelta = GameConstants.STAMINA_GAIN_IDLE_PER_SEC + CalcStaminaGainBonus();
}
break;
}
}
protected void StaminaProcessor_Swimming(HumanMovementState pHumanMovementState)
{
switch (pHumanMovementState.m_iMovement)
{
case 3: //swim fast
m_StaminaDelta = -GameConstants.STAMINA_DRAIN_SWIM_FAST_PER_SEC * CfgGameplayHandler.GetSprintSwimmingStaminaModifier();
SetCooldown(GameConstants.STAMINA_REGEN_COOLDOWN_DEPLETION);
break;
case 2: //swim slow
if (!m_IsInCooldown)
{
m_StaminaDelta = (GameConstants.STAMINA_GAIN_SWIM_PER_SEC + CalcStaminaGainBonus());
}
break;
default:
if (!m_IsInCooldown)
{
m_StaminaDelta = GameConstants.STAMINA_GAIN_IDLE_PER_SEC + CalcStaminaGainBonus();
}
break;
}
}
//! stamina sync - server part
protected void SyncStaminaEx()
{
SyncStamina(m_Stamina, m_StaminaCap, m_IsInCooldown);
}
//! stamina sync - server part
protected void SyncStamina(float stamina, float stamina_cap, bool cooldown)
{
m_Player.GetStatStamina().Set(stamina);
ScriptJunctureData pCtx = new ScriptJunctureData();
pCtx.Write(stamina);
pCtx.Write(stamina_cap);
pCtx.Write(cooldown);
m_Player.SendSyncJuncture(DayZPlayerSyncJunctures.SJ_STAMINA,pCtx);
}
//! Method to sync more info for stamina manager. Template parameter means it is very extendable for further use
protected void SyncAdditionalStaminaInfo(Param par)
{
Param2<float,float> p = Param2<float,float>.Cast(par);
ScriptJunctureData pCtx = new ScriptJunctureData();
pCtx.Write(p.param1);
pCtx.Write(p.param2);
m_Player.SendSyncJuncture(DayZPlayerSyncJunctures.SJ_STAMINA_MISC,pCtx);
}
//! Order of read parameters must match the order of writing above
protected void ReadAdditionalStaminaInfo(ParamsReadContext pCtx)
{
float depletionMultiplier;
float recoveryMultiplier;
if (!pCtx.Read(depletionMultiplier) || !pCtx.Read(recoveryMultiplier))
{
return;
}
m_StaminaDepletionMultiplier = depletionMultiplier;
m_StaminaRecoveryMultiplier = recoveryMultiplier;
}
protected void RegisterStaminaConsumers()
{
m_StaminaConsumers = new StaminaConsumers();
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.HOLD_BREATH,
GameConstants.STAMINA_HOLD_BREATH_THRESHOLD_ACTIVATE,
GameConstants.STAMINA_HOLD_BREATH_THRESHOLD_DRAIN,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.SPRINT,
CfgGameplayHandler.GetStaminaMinCap() + 15,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.JUMP,
GameConstants.STAMINA_JUMP_THRESHOLD,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.VAULT,
GameConstants.STAMINA_VAULT_THRESHOLD,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.CLIMB,
GameConstants.STAMINA_CLIMB_THRESHOLD,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.MELEE_HEAVY,
GameConstants.STAMINA_MELEE_HEAVY_THRESHOLD,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.MELEE_EVADE,
GameConstants.STAMINA_MELEE_EVADE_THRESHOLD,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.ROLL,
GameConstants.STAMINA_ROLL_THRESHOLD,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.DROWN,
0,
);
m_StaminaConsumers.RegisterConsumer(
EStaminaConsumers.PUSH,
0,
);
}
protected void RegisterStaminaModifiers()
{
m_StaminaModifiers = new StaminaModifiers();
SMDataHoldBreath data = new SMDataHoldBreath();
m_StaminaModifiers.RegisterExponentialEx(
EStaminaModifiers.HOLD_BREATH,
data,
);
m_StaminaModifiers.RegisterExponentialEx(
EStaminaModifiers.PUSH_CAR,
data,
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.DROWN,
10,
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.JUMP,
GameConstants.STAMINA_DRAIN_JUMP * CfgGameplayHandler.GetObstacleTraversalStaminaModifier(),
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.VAULT,
GameConstants.STAMINA_DRAIN_VAULT * CfgGameplayHandler.GetObstacleTraversalStaminaModifier(),
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.CLIMB,
GameConstants.STAMINA_DRAIN_CLIMB * CfgGameplayHandler.GetObstacleTraversalStaminaModifier(),
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.MELEE_LIGHT,
GameConstants.STAMINA_DRAIN_MELEE_LIGHT * CfgGameplayHandler.GetMeleeStaminaModifier(),
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.MELEE_HEAVY,
GameConstants.STAMINA_DRAIN_MELEE_HEAVY * CfgGameplayHandler.GetMeleeStaminaModifier(),
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.OVERALL_DRAIN,
CfgGameplayHandler.GetStaminaMax(),
5.0,
);
m_StaminaModifiers.RegisterRandomized(
EStaminaModifiers.MELEE_EVADE, 3 * CfgGameplayHandler.GetMeleeStaminaModifier(),
GameConstants.STAMINA_DRAIN_MELEE_EVADE * CfgGameplayHandler.GetMeleeStaminaModifier(),
);
m_StaminaModifiers.RegisterFixed(
EStaminaModifiers.ROLL,
GameConstants.STAMINA_DRAIN_ROLL,
);
}
//! Calulates stamina regain bonus coef based on current stamina cap and level and caches it during one Update call
protected float CalcStaminaGainBonus()
{
if (m_IsStaminaGainBonusCached)
return m_CachedStaminaGainBonus;
if (m_StaminaDepletion > 0)
{
m_CachedStaminaGainBonus = 0;
}
else if (m_Stamina > STAMINA_GAIN_THRESHOLD)
{
m_CachedStaminaGainBonus = Math.Min((m_Stamina/STAMINA_GAIN_MODIFIER), GameConstants.STAMINA_GAIN_BONUS_CAP);
}
else
{
m_CachedStaminaGainBonus = GameConstants.STAMINA_GAIN_BONUS_CAP;
}
m_IsStaminaGainBonusCached = true;
return m_CachedStaminaGainBonus;
}
protected void ApplyExhaustion()
{
//! sets exhaustion look of player based on stamina level
HumanCommandAdditives ad = m_Player.GetCommandModifier_Additives();
float exhaustion_value = 1;
if (m_StaminaCap != 0)
{
exhaustion_value = 1 - ((m_Stamina / (m_StaminaCap * 0.01)) * 0.01);
}
exhaustion_value = Math.Min(1, exhaustion_value);
if (ad)
{
// do not apply exhaustion on local client if player is in ADS/Optics (camera shakes)
if (m_Player.GetInstanceType() == DayZPlayerInstanceType.INSTANCETYPE_CLIENT && (m_Player.IsInOptics() || m_Player.IsInIronsights()))
{
ad.SetExhaustion(0, true);
}
else
{
ad.SetExhaustion(exhaustion_value, true);
}
}
}
//! check if the stamina is completely depleted
protected void CheckStaminaState()
{
if (m_Stamina <= 0)
{
m_StaminaDepleted = true;
//! in case of complete depletion - start a cooldown timer before the regeneration cycle start
if (!m_IsInCooldown)
{
// set this only once
SetCooldown(GameConstants.STAMINA_REGEN_COOLDOWN_EXHAUSTION);
}
}
else
{
m_StaminaDepleted = false;
}
}
//! set cooldown timer between each consume of stamina
protected void SetCooldown(float time, int modifier = -1)
{
StaminaCooldownParams cooldown;
if (!m_CooldownMap.Find(modifier, cooldown))
{
cooldown = new StaminaCooldownParams(0);
m_CooldownMap.Insert(modifier, cooldown);
}
m_IsInCooldown = true;
cooldown.param1 = time; // Set remaining time
}
protected void ResetCooldown(int modifier = -1)
{
StaminaModifier sm = m_StaminaModifiers.GetModifierData(modifier);
if (sm)
{
sm.SetStartTime(-1);
sm.ResetRunTime();
sm.SetInUse(false);
}
// Remove from cooldown map
m_CooldownMap.Remove(modifier);
// Update cooldown state based on remaining active cooldowns
m_IsInCooldown = (m_CooldownMap.Count() > 0);
}
// ---------------------------------------------------
bool HasEnoughStaminaFor(EStaminaConsumers consumer)
{
return m_StaminaConsumers.HasEnoughStaminaFor(consumer, m_StaminaSynced, m_StaminaDepleted, m_StaminaCapSynced);
}
bool HasEnoughStaminaToStart(EStaminaConsumers consumer)
{
return m_StaminaConsumers.HasEnoughStaminaToStart(consumer, m_StaminaSynced, m_StaminaDepleted, m_StaminaCapSynced);
}
void SetStamina(float stamina_value)
{
m_Stamina = Math.Clamp(stamina_value, 0, CfgGameplayHandler.GetStaminaMax());
SyncStaminaEx();
}
float GetStamina()
{
return m_Stamina;
}
float GetStaminaNormalized()
{
return m_Stamina / GetStaminaMax();
}
float GetSyncedStamina()
{
return m_StaminaSynced;
}
float GetSyncedStaminaNormalized()
{
return GetSyncedStamina() / GetStaminaMax();
}
float GetStaminaCap()
{
return m_StaminaCap;
}
float GetSyncedStaminaCap()
{
return m_StaminaCapSynced;
}
float GetStaminaMax()
{
return CfgGameplayHandler.GetStaminaMax();
}
// Only used internaly in this class, use ActivateDepletionModifier/DeactivateDepletionModifier instead
void SetDepletionMultiplier(float val)
{
if (m_StaminaDepletionMultiplier < 0)
m_StaminaDepletionMultiplier = 0;
m_StaminaDepletionMultiplier = val;
SyncAdditionalStaminaInfo(new Param2<float,float>(m_StaminaDepletionMultiplier,m_StaminaRecoveryMultiplier));
}
// Only used internaly in this class, use ActivateRecoveryModifier/DeactivateRecoveryModifier instead
void SetRecoveryMultiplier(float val)
{
if (m_StaminaRecoveryMultiplier < 0)
m_StaminaRecoveryMultiplier = 0;
m_StaminaRecoveryMultiplier = val;
SyncAdditionalStaminaInfo(new Param2<float,float>(m_StaminaDepletionMultiplier,m_StaminaRecoveryMultiplier));
}
float GetDepletionMultiplier()
{
return m_StaminaDepletionMultiplier;
}
float GetRecoveryMultiplier()
{
return m_StaminaRecoveryMultiplier;
}
void DepleteStaminaEx(EStaminaModifiers modifier, float dT = -1, float coef = 1.0)
{
#ifdef DIAG_DEVELOPER
if (m_StaminaDisabled)
return;
#endif
float val = 0.0;
float current_time = m_Player.GetSimulationTimeStamp();
float valueProgress;
StaminaModifier sm = m_StaminaModifiers.GetModifierData(modifier);
// select by modifier type and drain stamina
switch (sm.GetType())
{
case m_StaminaModifiers.FIXED:
if (dT == -1)
{
dT = 1;
}
m_StaminaDepletion += sm.GetMaxValue() * dT * coef;
break;
case m_StaminaModifiers.RANDOMIZED:
val = Math.RandomFloat(sm.GetMinValue(), sm.GetMaxValue());
m_StaminaDepletion += val * coef;
break;
case m_StaminaModifiers.LINEAR:
if (!sm.IsInUse())
{
sm.SetStartTime(current_time + sm.GetStartTimeAdjustment()/dT);
sm.SetRunTimeTick(dT);
sm.SetInUse(true);
}
valueProgress = Math.Clamp((current_time - sm.GetStartTime())/sm.GetDurationAdjusted(), 0, 1 );
val = Math.Lerp(sm.GetMinValue(), sm.GetMaxValue(), valueProgress);
m_StaminaDepletion += val * coef;
break;
case m_StaminaModifiers.EXPONENTIAL:
StaminaModifierExponential smex;
if (!Class.CastTo(smex,sm))
{
ErrorEx("StaminaModifierExponential not found for modifier type: " + sm.GetType());
break;
}
if (!smex.IsInUse())
{
smex.SetStartTime(current_time + smex.GetStartTimeAdjustment()/dT);
smex.SetRunTimeTick(dT);
smex.SetInUse(true);
}
valueProgress = Math.Clamp((current_time - smex.GetStartTime())/smex.GetDurationAdjusted(), 0, 1 );
float exp;
if (Math.AbsFloat(smex.GetBaseValue()) < 1)
{
exp = 1 - Math.Lerp(0, smex.GetExponent(), valueProgress);
val = Math.Pow(smex.GetBaseValue(),exp);
}
else
{
exp = Math.Lerp(Math.Min(0, smex.GetExponent()), Math.Max(0, smex.GetExponent()), valueProgress);
val = Math.Pow(smex.GetBaseValue(),exp) + smex.GetBaseValue() - 1;
}
m_StaminaDepletion += val * smex.GetMultiplier() * coef;
break;
}
//! run cooldown right after depletion
SetCooldown(sm.GetCooldown(),modifier);
m_StaminaDepletion *= m_StaminaDepletionMultiplier;
m_StaminaDepletion = Math.Clamp(m_StaminaDepletion, 0, CfgGameplayHandler.GetStaminaMax());
}
// ADD these new methods - don't remove any existing ones
void ObtainState(PlayerBaseOwnerState state)
{
// we are writing the state data for stamina to the move, use 'GetNextMove'
}
void ObtainMove(PlayerBaseMove move)
{
// unused
}
void RewindState(PlayerBaseOwnerState state, PlayerBaseMove move)
{
m_Stamina = move.m_fStaminaValue;
m_StaminaCap = move.m_fStaminaCapacity;
}
void ReplayMove(PlayerBaseMove move)
{
// mimick receiving of sync juncture
if (move.m_bStaminaSynced)
{
m_StaminaSynced = move.m_fStaminaSyncedValue;
m_StaminaCapSynced = move.m_fStaminaSyncedCapacity;
m_Stamina = m_StaminaSynced;
m_StaminaCap = m_StaminaCapSynced;
}
m_StaminaDepletion = move.m_fStaminaDepletion;
Update(move.GetTimeSlice(), m_Player.GetCurrentCommandID());
}
void MarkLoadDirty()
{
m_ForceLoadRecheck = true;
}
#ifdef DIAG_DEVELOPER
void SetStaminaDisabled(bool value)
{
m_StaminaDisabled = value;
}
#endif
///////////////////
//DEPRECATED BELOW
//////////////////
protected bool m_Debug;
[Obsolete("No replacement")]
void DepleteStamina(EStaminaModifiers modifier, float dT = -1)
{
DepleteStaminaEx(modifier,dT);
}
[Obsolete("Deprecated use, StaminaHandler uses SyncJunctures now!")]
void OnRPC(float stamina, float stamina_cap, bool cooldown);
}