C#:
using System;
using System.Collections.Generic;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using ProtoBuf;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("CustomVitalsManager", "ifte", "1.0.0")]
[Description("A plugin to manage custom vitals (status effects) with icons, text, timers, and more. Supports player-specific and shared vitals all using Rust Native CustomVitalsInfo")]
public class CustomVitalsManager : RustPlugin
{
/*------------------------------------
*
* CustomVitalsManager by Ifte
* Support: https://discord.gg/cCWadjcapr
* Fiverr: https://www.fiverr.com/s/e2pkYY
*
* Special thanks to CarbonMod
* https://carbonmod.gg/
*
------------------------------------*/
#region Vars
private static CustomVitalsManager Instance;
private readonly VitalDictionary<SharedIdentifiableVital> _sharedVitals = new VitalDictionary<SharedIdentifiableVital>();
private readonly Dictionary<ulong, VitalDictionary<PlayerIdentifiableVital>> _playerVitals = new Dictionary<ulong, VitalDictionary<PlayerIdentifiableVital>>();
private const uint DefaultStartVitalId = 100;
private uint _nextVitalId = DefaultStartVitalId;
private readonly float SendOnConnectDelay = 1f;
private readonly bool Debug = false;
#endregion
#region Oxide Hooks
private void Init()
{
Instance = this;
}
private void OnServerInitialized()
{
SendVitalsToEveryone();
}
private void OnPlayerConnected(BasePlayer player)
{
if (!IsValidPlayer(player))
return;
timer.Once(Mathf.Max(0f, SendOnConnectDelay), () =>
{
if (IsValidPlayer(player))
SendVitals(player);
});
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (!IsValidPlayer(player))
return;
ClearVitals(player, false);
}
private void Unload()
{
foreach (var dict in _playerVitals.Values)
dict.ClearVitals(false);
_playerVitals.Clear();
_sharedVitals.ClearVitals(false);
Instance = null;
}
#endregion
#region Public API - Creation
/// <summary>
/// API: Create a ProtoBuf.CustomVitalInfo object.
/// Other plugins may call:
/// CustomVitalsManager.Call("API_RentVitalInfo", "ImageLibraryID", "#FFFFFF", "#000000AA", "LEFT", "#FFFFFF", "RIGHT", "#FFFFFF", 60, true)
/// </summary>
private CustomVitalInfo API_RentVitalInfo(
string icon = null,
string iconColor = null,
string backgroundColor = null,
string leftText = null,
string leftTextColor = null,
string rightText = null,
string rightTextColor = null,
int timeLeft = 0,
bool active = true)
{
return RentVitalInfo(
icon,
ParseColor(iconColor, Color.white),
ParseColor(backgroundColor, default(Color)),
leftText,
ParseColor(leftTextColor, Color.white),
rightText,
ParseColor(rightTextColor, Color.white),
timeLeft,
active);
}
/// <summary>
/// API: Create a ProtoBuf.CustomVitalInfo object using Unity Color values directly.
/// </summary>
private CustomVitalInfo API_RentVitalInfoRaw(
string icon = null,
Color iconColor = default(Color),
Color backgroundColor = default(Color),
string leftText = null,
Color leftTextColor = default(Color),
string rightText = null,
Color rightTextColor = default(Color),
int timeLeft = 0,
bool active = true)
{
return RentVitalInfo(icon, iconColor, backgroundColor, leftText, leftTextColor, rightText, rightTextColor, timeLeft, active);
}
/// <summary>
/// API: Add a player-specific vital from a CustomVitalInfo object. Returns uint vital id, or 0 on failure.
/// </summary>
private uint API_AddVital(BasePlayer player, CustomVitalInfo vital, float expiry = 0f, bool sendUpdate = true)
{
var result = AddVital(player, vital, expiry, sendUpdate);
return result?.Id ?? 0u;
}
/// <summary>
/// API: Add a player-specific vital from simple values. Returns uint vital id, or 0 on failure.
/// </summary>
private uint API_AddVital(
BasePlayer player,
string icon = null,
string iconColor = null,
string backgroundColor = null,
string leftText = null,
string leftTextColor = null,
string rightText = null,
string rightTextColor = null,
int timeLeft = 0,
bool active = true,
float expiry = 0f,
bool sendUpdate = true)
{
var info = API_RentVitalInfo(icon, iconColor, backgroundColor, leftText, leftTextColor, rightText, rightTextColor, timeLeft, active);
return API_AddVital(player, info, expiry, sendUpdate);
}
/// <summary>
/// API: Add a player-specific vital by user id. Returns uint vital id, or 0 on failure.
/// </summary>
private uint API_AddPlayerVital(
ulong playerId,
string icon = null,
string iconColor = null,
string backgroundColor = null,
string leftText = null,
string leftTextColor = null,
string rightText = null,
string rightTextColor = null,
int timeLeft = 0,
bool active = true,
float expiry = 0f,
bool sendUpdate = true)
{
var player = BasePlayer.FindByID(playerId);
if (!IsValidPlayer(player))
return 0u;
return API_AddVital(player, icon, iconColor, backgroundColor, leftText, leftTextColor, rightText, rightTextColor, timeLeft, active, expiry, sendUpdate);
}
/// <summary>
/// API: Add a shared vital from a CustomVitalInfo object. Shared vitals are visible to every connected player.
/// Returns uint vital id, or 0 on failure.
/// </summary>
private uint API_AddSharedVital(CustomVitalInfo vital, float expiry = 0f, bool sendUpdate = true)
{
var result = AddSharedVital(vital, expiry, sendUpdate);
return result?.Id ?? 0u;
}
/// <summary>
/// API: Add a shared vital from simple values. Returns uint vital id, or 0 on failure.
/// </summary>
private uint API_AddSharedVital(
string icon = null,
string iconColor = null,
string backgroundColor = null,
string leftText = null,
string leftTextColor = null,
string rightText = null,
string rightTextColor = null,
int timeLeft = 0,
bool active = true,
float expiry = 0f,
bool sendUpdate = true)
{
var info = API_RentVitalInfo(icon, iconColor, backgroundColor, leftText, leftTextColor, rightText, rightTextColor, timeLeft, active);
return API_AddSharedVital(info, expiry, sendUpdate);
}
#endregion
#region Public API - Lookup / Counts
private int API_GetSharedVitalCount() => _sharedVitals.Count;
private int API_GetPlayerVitalCount(BasePlayer player)
{
return IsValidPlayer(player) ? API_GetPlayerVitalCount(player.userID) : 0;
}
private int API_GetPlayerVitalCount(ulong playerId)
{
return _playerVitals.TryGetValue(playerId, out var vitals) ? vitals.Count : 0;
}
private int API_GetTotalPlayerVitalCount(BasePlayer player)
{
return IsValidPlayer(player) ? API_GetTotalPlayerVitalCount(player.userID) : 0;
}
private int API_GetTotalPlayerVitalCount(ulong playerId)
{
return _sharedVitals.Count + API_GetPlayerVitalCount(playerId);
}
private bool API_HasVital(BasePlayer player, uint id)
{
return IsValidPlayer(player) && _playerVitals.TryGetValue(player.userID, out var vitals) && vitals.Contains(id);
}
private bool API_HasVital(ulong playerId, uint id)
{
return _playerVitals.TryGetValue(playerId, out var vitals) && vitals.Contains(id);
}
private bool API_HasSharedVital(uint id)
{
return _sharedVitals.Contains(id);
}
private Dictionary<string, object> API_GetVitalInfo(BasePlayer player, uint id)
{
return IsValidPlayer(player) && _playerVitals.TryGetValue(player.userID, out var vitals) && vitals.TryGetVital(id, out var vital)
? vital.ToApiDictionary()
: null;
}
private Dictionary<string, object> API_GetVitalInfo(ulong playerId, uint id)
{
return _playerVitals.TryGetValue(playerId, out var vitals) && vitals.TryGetVital(id, out var vital)
? vital.ToApiDictionary()
: null;
}
private Dictionary<string, object> API_GetSharedVitalInfo(uint id)
{
return _sharedVitals.TryGetVital(id, out var vital)
? vital.ToApiDictionary()
: null;
}
#endregion
#region Public API - Update
private bool API_UpdateVitalText(BasePlayer player, uint id, string leftText = null, string rightText = null, bool sendUpdate = true)
{
if (!IsValidPlayer(player) || !_playerVitals.TryGetValue(player.userID, out var vitals) || !vitals.TryGetVital(id, out var vital))
return false;
if (leftText != null)
vital.Info.leftText = leftText;
if (rightText != null)
vital.Info.rightText = rightText;
MarkVitalUpdated(player, vital, false, sendUpdate);
return true;
}
private bool API_UpdateVitalText(ulong playerId, uint id, string leftText = null, string rightText = null, bool sendUpdate = true)
{
return API_UpdateVitalText(BasePlayer.FindByID(playerId), id, leftText, rightText, sendUpdate);
}
private bool API_UpdateSharedVitalText(uint id, string leftText = null, string rightText = null, bool sendUpdate = true)
{
if (!_sharedVitals.TryGetVital(id, out var vital))
return false;
if (leftText != null)
vital.Info.leftText = leftText;
if (rightText != null)
vital.Info.rightText = rightText;
MarkSharedVitalUpdated(vital, sendUpdate);
return true;
}
private bool API_SetVitalActive(BasePlayer player, uint id, bool active, bool sendUpdate = true)
{
if (!IsValidPlayer(player) || !_playerVitals.TryGetValue(player.userID, out var vitals) || !vitals.TryGetVital(id, out var vital))
return false;
vital.Info.active = active;
MarkVitalUpdated(player, vital, false, sendUpdate);
return true;
}
private bool API_SetVitalActive(ulong playerId, uint id, bool active, bool sendUpdate = true)
{
return API_SetVitalActive(BasePlayer.FindByID(playerId), id, active, sendUpdate);
}
private bool API_SetSharedVitalActive(uint id, bool active, bool sendUpdate = true)
{
if (!_sharedVitals.TryGetVital(id, out var vital))
return false;
vital.Info.active = active;
MarkSharedVitalUpdated(vital, sendUpdate);
return true;
}
private bool API_SetVitalTimeLeft(BasePlayer player, uint id, int timeLeft, bool sendUpdate = true)
{
if (!IsValidPlayer(player) || !_playerVitals.TryGetValue(player.userID, out var vitals) || !vitals.TryGetVital(id, out var vital))
return false;
vital.SetTimeLeft(timeLeft);
MarkVitalUpdated(player, vital, false, sendUpdate);
return true;
}
private bool API_SetVitalTimeLeft(ulong playerId, uint id, int timeLeft, bool sendUpdate = true)
{
return API_SetVitalTimeLeft(BasePlayer.FindByID(playerId), id, timeLeft, sendUpdate);
}
private bool API_SetSharedVitalTimeLeft(uint id, int timeLeft, bool sendUpdate = true)
{
if (!_sharedVitals.TryGetVital(id, out var vital))
return false;
vital.SetTimeLeft(timeLeft);
MarkSharedVitalUpdated(vital, sendUpdate);
return true;
}
private bool API_SetVitalExpiry(BasePlayer player, uint id, float expiry, bool restart = true)
{
if (!IsValidPlayer(player) || !_playerVitals.TryGetValue(player.userID, out var vitals) || !vitals.TryGetVital(id, out var vital))
return false;
vital.Expiry = Mathf.Max(0f, expiry);
if (restart)
vital.RestartExpiry();
return true;
}
private bool API_SetVitalExpiry(ulong playerId, uint id, float expiry, bool restart = true)
{
return API_SetVitalExpiry(BasePlayer.FindByID(playerId), id, expiry, restart);
}
private bool API_SetSharedVitalExpiry(uint id, float expiry, bool restart = true)
{
if (!_sharedVitals.TryGetVital(id, out var vital))
return false;
vital.Expiry = Mathf.Max(0f, expiry);
if (restart)
vital.RestartExpiry();
return true;
}
#endregion
#region Public API - Remove / Clear / Send
private bool API_RemoveVital(BasePlayer player, uint id, bool sendUpdate = true)
{
return RemoveVital(player, id, sendUpdate);
}
private bool API_RemoveVital(ulong playerId, uint id, bool sendUpdate = true)
{
return RemoveVital(BasePlayer.FindByID(playerId), id, sendUpdate);
}
private bool API_RemoveSharedVital(uint id, bool sendUpdate = true)
{
return RemoveSharedVital(id, sendUpdate);
}
private void API_ClearVitals(BasePlayer player, bool sendUpdate = true)
{
ClearVitals(player, sendUpdate);
}
private void API_ClearPlayerVitals(BasePlayer player, bool sendUpdate = true)
{
ClearVitals(player, sendUpdate);
}
private void API_ClearPlayerVitals(ulong playerId, bool sendUpdate = true)
{
ClearVitals(BasePlayer.FindByID(playerId), sendUpdate);
}
private void API_ClearSharedVitals(bool sendUpdate = true)
{
ClearSharedVitals(sendUpdate);
}
private void API_SendVitals(BasePlayer player)
{
SendVitals(player);
}
private void API_SendVitals(ulong playerId)
{
SendVitals(BasePlayer.FindByID(playerId));
}
private void API_SendVitalsToEveryone()
{
SendVitalsToEveryone();
}
#endregion
#region Internal Manager Methods
public CustomVitalInfo RentVitalInfo(
string icon = null,
Color iconColor = default(Color),
Color backgroundColor = default(Color),
string leftText = null,
Color leftTextColor = default(Color),
string rightText = null,
Color rightTextColor = default(Color),
int timeLeft = 0,
bool active = true)
{
var vitalInfo = Pool.Get<CustomVitalInfo>();
vitalInfo.icon = icon;
vitalInfo.iconColor = iconColor;
vitalInfo.backgroundColor = backgroundColor;
vitalInfo.leftText = leftText;
vitalInfo.leftTextColor = leftTextColor;
vitalInfo.rightText = rightText;
vitalInfo.rightTextColor = rightTextColor;
vitalInfo.timeLeft = timeLeft;
vitalInfo.active = active;
return vitalInfo;
}
public PlayerIdentifiableVital AddVital(BasePlayer player, CustomVitalInfo vital, float expiry = 0f, bool sendUpdate = true)
{
if (!IsValidPlayer(player) || vital == null)
return null;
var hookResult = Interface.CallHook("CanAddCustomVital", player, vital, false);
if (hookResult is bool allowed && !allowed)
return null;
if (!_playerVitals.TryGetValue(player.userID, out var vitals))
{
vitals = new VitalDictionary<PlayerIdentifiableVital>();
_playerVitals[player.userID] = vitals;
}
var identifiableVital = vitals.AddVital(NextVitalId(), vital, expiry);
identifiableVital.PlayerId = player.userID;
RestartExpiry(identifiableVital);
Interface.CallHook("OnCustomVitalAdded", player, identifiableVital.Id, false, identifiableVital.Info);
if (sendUpdate)
SendVitals(player);
DebugLog($"Added player vital {identifiableVital.Id} to {player.displayName}/{player.UserIDString}");
return identifiableVital;
}
public SharedIdentifiableVital AddSharedVital(CustomVitalInfo vital, float expiry = 0f, bool sendUpdate = true)
{
if (vital == null)
return null;
var hookResult = Interface.CallHook("CanAddCustomVital", null, vital, true);
if (hookResult is bool allowed && !allowed)
return null;
var identifiableVital = _sharedVitals.AddVital(NextVitalId(), vital, expiry);
RestartExpiry(identifiableVital);
Interface.CallHook("OnCustomVitalAdded", null, identifiableVital.Id, true, identifiableVital.Info);
if (sendUpdate)
SendVitalsToEveryone();
DebugLog($"Added shared vital {identifiableVital.Id}");
return identifiableVital;
}
public bool RemoveVital(BasePlayer player, uint id, bool sendUpdate = true)
{
if (!IsValidPlayer(player) || !_playerVitals.TryGetValue(player.userID, out var vitals))
return false;
if (!vitals.TryGetVital(id, out var vital))
return false;
CancelExpiry(vital);
if (!vitals.RemoveVital(id, true))
return false;
Interface.CallHook("OnCustomVitalRemoved", player, id, false);
if (sendUpdate)
SendVitals(player);
DebugLog($"Removed player vital {id} from {player.displayName}/{player.UserIDString}");
return true;
}
public bool RemoveSharedVital(uint id, bool sendUpdate = true)
{
if (!_sharedVitals.TryGetVital(id, out var vital))
return false;
CancelExpiry(vital);
if (!_sharedVitals.RemoveVital(id, true))
return false;
Interface.CallHook("OnCustomVitalRemoved", null, id, true);
if (sendUpdate)
SendVitalsToEveryone();
DebugLog($"Removed shared vital {id}");
return true;
}
public void ClearVitals(BasePlayer player, bool sendUpdate = true)
{
if (!IsValidPlayer(player) || !_playerVitals.TryGetValue(player.userID, out var vitals))
return;
foreach (var vital in vitals.GetSnapshot())
CancelExpiry(vital);
vitals.ClearVitals(true);
Interface.CallHook("OnCustomVitalsCleared", player, false);
if (sendUpdate)
SendVitals(player);
DebugLog($"Cleared player vitals for {player.displayName}/{player.UserIDString}");
}
public void ClearSharedVitals(bool sendUpdate = true)
{
foreach (var vital in _sharedVitals.GetSnapshot())
CancelExpiry(vital);
_sharedVitals.ClearVitals(true);
Interface.CallHook("OnCustomVitalsCleared", null, true);
if (sendUpdate)
SendVitalsToEveryone();
DebugLog("Cleared shared vitals");
}
public void SendVitals(BasePlayer player)
{
if (!IsValidPlayer(player) || CommunityEntity.ServerInstance == null)
return;
var proto = Pool.Get<CustomVitals>();
proto.vitals = Pool.Get<List<CustomVitalInfo>>();
try
{
if (_playerVitals.TryGetValue(player.userID, out var playerDict))
playerDict.AppendVitals(proto);
_sharedVitals.AppendVitals(proto);
CommunityEntity.ServerInstance.SendCustomVitals(player, proto);
Interface.CallHook("OnCustomVitalsSent", player, proto.vitals.Count);
}
finally
{
Pool.Free(ref proto.vitals);
Pool.Free(ref proto);
}
}
public void SendVitalsToEveryone()
{
var players = BasePlayer.activePlayerList;
for (var i = 0; i < players.Count; i++)
SendVitals(players[i]);
}
#endregion
#region Helpers
private uint NextVitalId()
{
if (_nextVitalId == uint.MaxValue)
_nextVitalId = DefaultStartVitalId;
return ++_nextVitalId;
}
private void MarkVitalUpdated(BasePlayer player, PlayerIdentifiableVital vital, bool shared, bool sendUpdate)
{
Interface.CallHook("OnCustomVitalUpdated", player, vital.Id, shared, vital.Info);
if (sendUpdate)
SendVitals(player);
}
private void MarkSharedVitalUpdated(SharedIdentifiableVital vital, bool sendUpdate)
{
Interface.CallHook("OnCustomVitalUpdated", null, vital.Id, true, vital.Info);
if (sendUpdate)
SendVitalsToEveryone();
}
private void RestartExpiry(IdentifiableVital vital)
{
vital.RestartExpiry();
}
private void CancelExpiry(IdentifiableVital vital)
{
vital.CancelExpiry();
}
private static bool IsValidPlayer(BasePlayer player)
{
return player != null && !player.IsDestroyed && player.userID != 0;
}
private static Color ParseColor(string value, Color fallback)
{
if (string.IsNullOrWhiteSpace(value))
return fallback;
value = value.Trim();
if (value[0] == '#')
{
if (ColorUtility.TryParseHtmlString(value, out var parsed))
return parsed;
return fallback;
}
var parts = value.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 3)
return fallback;
if (!float.TryParse(parts[0], out var r) ||
!float.TryParse(parts[1], out var g) ||
!float.TryParse(parts[2], out var b))
return fallback;
var a = 1f;
if (parts.Length >= 4)
float.TryParse(parts[3], out a);
return new Color(r, g, b, a);
}
private static string ColorToString(Color color)
{
return $"{color.r:0.###} {color.g:0.###} {color.b:0.###} {color.a:0.###}";
}
private void DebugLog(string message)
{
if (Debug)
Puts(message);
}
#endregion
#region Data Classes
public abstract class IdentifiableVital
{
public uint Id;
public CustomVitalInfo Info;
public int TotalTimeLeft;
public float Expiry;
private float _timeLeftStartedAt;
private Timer _expiryTimer;
public int CurrentTimeLeft
{
get
{
if (TotalTimeLeft <= 0)
return 0;
var elapsed = Time.realtimeSinceStartup - _timeLeftStartedAt;
return Mathf.Max(TotalTimeLeft - Mathf.FloorToInt(elapsed), 0);
}
}
public virtual void SetTimeLeft(int timeLeft)
{
if (Info == null)
return;
Info.timeLeft = Mathf.Max(0, timeLeft);
TotalTimeLeft = Info.timeLeft;
_timeLeftStartedAt = Time.realtimeSinceStartup;
}
public abstract void SendUpdate();
public abstract void RemoveSelf();
public void RestartExpiry()
{
CancelExpiry();
if (Expiry <= 0f)
return;
_expiryTimer = Instance.timer.Once(Expiry, RemoveSelf);
}
public void CancelExpiry()
{
_expiryTimer?.Destroy();
_expiryTimer = null;
}
public void FreeInfo()
{
if (Info == null)
return;
var info = Info;
Info = null;
Pool.Free(ref info);
}
public Dictionary<string, object> ToApiDictionary()
{
return new Dictionary<string, object>
{
["Id"] = Id,
["Icon"] = Info?.icon,
["IconColor"] = Info == null ? null : ColorToString(Info.iconColor),
["BackgroundColor"] = Info == null ? null : ColorToString(Info.backgroundColor),
["LeftText"] = Info?.leftText,
["LeftTextColor"] = Info == null ? null : ColorToString(Info.leftTextColor),
["RightText"] = Info?.rightText,
["RightTextColor"] = Info == null ? null : ColorToString(Info.rightTextColor),
["TimeLeft"] = CurrentTimeLeft,
["TotalTimeLeft"] = TotalTimeLeft,
["Active"] = Info != null && Info.active,
["Expiry"] = Expiry
};
}
}
public class SharedIdentifiableVital : IdentifiableVital
{
public override void RemoveSelf()
{
if (Instance == null)
return;
Instance.RemoveSharedVital(Id, true);
}
public override void SendUpdate()
{
Instance?.SendVitalsToEveryone();
}
}
public class PlayerIdentifiableVital : IdentifiableVital
{
public ulong PlayerId;
public BasePlayer GetPlayer()
{
return BasePlayer.FindByID(PlayerId);
}
public override void RemoveSelf()
{
if (Instance == null)
return;
var player = GetPlayer();
if (IsValidPlayer(player))
Instance.RemoveVital(player, Id, true);
}
public override void SendUpdate()
{
var player = GetPlayer();
if (IsValidPlayer(player))
Instance.SendVitals(player);
}
public new Dictionary<string, object> ToApiDictionary()
{
var data = base.ToApiDictionary();
data["PlayerId"] = PlayerId;
return data;
}
}
public class VitalDictionary<T> where T : IdentifiableVital, new()
{
private readonly Dictionary<uint, T> _buffer = new Dictionary<uint, T>();
public int Count => _buffer.Count;
public bool HasAny()
{
return _buffer.Count > 0;
}
public bool Contains(uint id)
{
return _buffer.ContainsKey(id);
}
public List<T> GetSnapshot()
{
return new List<T>(_buffer.Values);
}
public void AppendVitals(CustomVitals vitals)
{
if (vitals == null || vitals.vitals == null)
return;
foreach (var identifiableVital in _buffer.Values)
{
if (identifiableVital.Info == null)
continue;
identifiableVital.Info.timeLeft = identifiableVital.CurrentTimeLeft;
vitals.vitals.Add(identifiableVital.Info);
}
}
public T AddVital(uint id, CustomVitalInfo vital, float expiry = 0f)
{
var identifiableVital = new T
{
Id = id,
Info = vital,
Expiry = Mathf.Max(0f, expiry)
};
identifiableVital.SetTimeLeft(vital.timeLeft);
_buffer[identifiableVital.Id] = identifiableVital;
return identifiableVital;
}
public bool TryGetVital(uint id, out T vital)
{
return _buffer.TryGetValue(id, out vital);
}
public bool RemoveVital(uint id, bool freeInfo)
{
if (!_buffer.TryGetValue(id, out var vital))
return false;
vital.CancelExpiry();
if (freeInfo)
vital.FreeInfo();
return _buffer.Remove(id);
}
public void ClearVitals(bool freeInfo)
{
foreach (var vital in _buffer.Values)
{
vital.CancelExpiry();
if (freeInfo)
vital.FreeInfo();
}
_buffer.Clear();
}
}
#endregion
}
}