About Custom Vitals Manager
CustomVitalsManager is a lightweight developer-focused Rust plugin for creating and managing custom player status effects using Rust’s native CustomVitalsInfo system. It allows other plugins to display custom vitals with icons, colors, left/right text, timers, active states, and automatic expiry handling directly in the player’s native status/vitals UI.
Features
- Native Rust custom vitals support through ProtoBuf.CustomVitalInfo
- Shared/global vitals visible to all connected players
- Player-specific custom vitals
- Optional automatic expiry/removal
- Manual update, remove, clear, and resend APIs
- Developer hooks for blocking, tracking, and reacting to vital changes
- Uses pooled ProtoBuf objects for cleaner server-side memory handling
Developer API
|-------------------------------------------------------------------------- | CREATE: | 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 | ); | | 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 | ); | |-------------------------------------------------------------------------- | ADD: | uint API_AddVital( | BasePlayer player, | CustomVitalInfo vital, | float expiry = 0f, | bool sendUpdate = true | ); | | 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 | ); | | 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 | ); | | uint API_AddSharedVital( | CustomVitalInfo vital, | float expiry = 0f, | bool sendUpdate = true | ); | | 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 | ); | |-------------------------------------------------------------------------- | UPDATE: | bool API_UpdateVitalText(BasePlayer player, uint id, string leftText = null, string rightText = null, bool sendUpdate = true); | bool API_UpdateVitalText(ulong playerId, uint id, string leftText = null, string rightText = null, bool sendUpdate = true); | bool API_UpdateSharedVitalText(uint id, string leftText = null, string rightText = null, bool sendUpdate = true); | | bool API_SetVitalActive(BasePlayer player, uint id, bool active, bool sendUpdate = true); | bool API_SetVitalActive(ulong playerId, uint id, bool active, bool sendUpdate = true); | bool API_SetSharedVitalActive(uint id, bool active, bool sendUpdate = true); | | bool API_SetVitalTimeLeft(BasePlayer player, uint id, int timeLeft, bool sendUpdate = true); | bool API_SetVitalTimeLeft(ulong playerId, uint id, int timeLeft, bool sendUpdate = true); | bool API_SetSharedVitalTimeLeft(uint id, int timeLeft, bool sendUpdate = true); | | bool API_SetVitalExpiry(BasePlayer player, uint id, float expiry, bool restart = true); | bool API_SetVitalExpiry(ulong playerId, uint id, float expiry, bool restart = true); | bool API_SetSharedVitalExpiry(uint id, float expiry, bool restart = true); | |-------------------------------------------------------------------------- | REMOVE: | bool API_RemoveVital(BasePlayer player, uint id, bool sendUpdate = true); | bool API_RemoveVital(ulong playerId, uint id, bool sendUpdate = true); | bool API_RemoveSharedVital(uint id, bool sendUpdate = true); | |-------------------------------------------------------------------------- | CLEAR: | void API_ClearVitals(BasePlayer player, bool sendUpdate = true); | void API_ClearPlayerVitals(BasePlayer player, bool sendUpdate = true); | void API_ClearPlayerVitals(ulong playerId, bool sendUpdate = true); | void API_ClearSharedVitals(bool sendUpdate = true); | |-------------------------------------------------------------------------- | SEND: | void API_SendVitals(BasePlayer player); | void API_SendVitals(ulong playerId); | void API_SendVitalsToEveryone(); | |-------------------------------------------------------------------------- | CHECK / INFO: | int API_GetSharedVitalCount(); | int API_GetPlayerVitalCount(BasePlayer player); | int API_GetPlayerVitalCount(ulong playerId); | int API_GetTotalPlayerVitalCount(BasePlayer player); | int API_GetTotalPlayerVitalCount(ulong playerId); | | bool API_HasVital(BasePlayer player, uint id); | bool API_HasVital(ulong playerId, uint id); | bool API_HasSharedVital(uint id); | | Dictionary<string, object> API_GetVitalInfo(BasePlayer player, uint id); | Dictionary<string, object> API_GetVitalInfo(ulong playerId, uint id); | Dictionary<string, object> API_GetSharedVitalInfo(uint id); | |-------------------------------------------------------------------------- | HOOKS: | object CanAddCustomVital(BasePlayer player, CustomVitalInfo vital, bool shared); | void OnCustomVitalAdded(BasePlayer player, uint id, bool shared, CustomVitalInfo info); | void OnCustomVitalUpdated(BasePlayer player, uint id, bool shared, CustomVitalInfo info); | void OnCustomVitalRemoved(BasePlayer player, uint id, bool shared); | void OnCustomVitalsCleared(BasePlayer player, bool shared); | void OnCustomVitalsSent(BasePlayer player, int count); | |--------------------------------------------------------------------------
QuoteEXAMPLE PLUGIN
/* |-------------------------------------------------------------------------- | CustomVitalsManager Example Plugin |-------------------------------------------------------------------------- | This plugin demonstrates: | | - How to preload icons with ImageLibrary | - How to get the ImageLibrary image ID with GetImage | - How to pass that image ID into CustomVitalsManager as string icon | - How to add player-specific custom vitals | - How to add shared/global custom vitals | - How to update/remove vitals safely |-------------------------------------------------------------------------- */ using System; using System.Collections.Generic; using Oxide.Core.Plugins; using UnityEngine; namespace Oxide.Plugins { [Info("CustomVitalsExample", "ifte", "1.0.0")] [Description("Example plugin showing how to use CustomVitalsManager with ImageLibrary icons.")] public class CustomVitalsExample : RustPlugin { #region Plugin References [PluginReference] private Plugin CustomVitalsManager, ImageLibrary; #endregion #region Fields /* |-------------------------------------------------------------------------- | Image keys |-------------------------------------------------------------------------- | These keys are what we register into ImageLibrary. | | You can use either: | - A normal key name, example: "cve.boost" | - The URL itself as the key | | Best practice: | Use clean custom keys, then map each key to a URL. |-------------------------------------------------------------------------- */ private const string IconBoost = "cve.boost"; private const string IconBleeding = "cve.bleeding"; private const string IconRadiation = "cve.radiation"; private const string IconEvent = "cve.event"; /* |-------------------------------------------------------------------------- | Image URLs |-------------------------------------------------------------------------- | Replace these URLs with your own hosted PNG icons. | | Recommended: | - PNG | - Transparent background | - 128x128 or 256x256 | - Simple white icon works best because CustomVitalsManager can recolor it |-------------------------------------------------------------------------- */ private readonly Dictionary<string, string> _images = new Dictionary<string, string> { [IconBoost] = "https://i.imgur.com/YOUR_BOOST_ICON.png", [IconBleeding] = "https://i.imgur.com/YOUR_BLEEDING_ICON.png", [IconRadiation] = "https://i.imgur.com/YOUR_RADIATION_ICON.png", [IconEvent] = "https://i.imgur.com/YOUR_EVENT_ICON.png" }; /* |-------------------------------------------------------------------------- | Runtime vital storage |-------------------------------------------------------------------------- | Store the returned vital IDs if you want to update/remove them later. |-------------------------------------------------------------------------- */ private readonly Dictionary<ulong, uint> _boostVitals = new Dictionary<ulong, uint>(); private readonly Dictionary<ulong, uint> _bleedingVitals = new Dictionary<ulong, uint>(); private uint _sharedEventVital; #endregion #region Oxide Hooks private void OnServerInitialized() { if (!CheckDependencies()) return; LoadImages(); /* |-------------------------------------------------------------------------- | Optional shared vital example |-------------------------------------------------------------------------- | Delayed slightly so ImageLibrary has time to register/download icons. |-------------------------------------------------------------------------- */ timer.Once(3f, StartSharedEventVital); } private void Unload() { if (CustomVitalsManager == null) return; foreach (BasePlayer player in BasePlayer.activePlayerList) { CustomVitalsManager.Call("API_ClearPlayerVitals", player, true); } if (_sharedEventVital != 0) { CustomVitalsManager.Call("API_RemoveSharedVital", _sharedEventVital, true); _sharedEventVital = 0; } } private void OnPlayerDisconnected(BasePlayer player, string reason) { if (player == null) return; _boostVitals.Remove(player.userID); _bleedingVitals.Remove(player.userID); } #endregion #region Commands [ChatCommand("vitalboost")] private void CmdVitalBoost(BasePlayer player, string command, string[] args) { if (!CanUse(player)) return; GiveBoostVital(player); SendReply(player, "Boost custom vital added for 60 seconds."); } [ChatCommand("vitalbleed")] private void CmdVitalBleed(BasePlayer player, string command, string[] args) { if (!CanUse(player)) return; GiveBleedingVital(player); SendReply(player, "Bleeding custom vital added for 20 seconds."); } [ChatCommand("vitalremove")] private void CmdVitalRemove(BasePlayer player, string command, string[] args) { if (!CanUse(player)) return; RemovePlayerVitals(player); SendReply(player, "Your example custom vitals were removed."); } [ChatCommand("vitalevent")] private void CmdVitalEvent(BasePlayer player, string command, string[] args) { if (!CanUse(player)) return; if (_sharedEventVital == 0) { StartSharedEventVital(); SendReply(player, "Shared event vital started."); return; } StopSharedEventVital(); SendReply(player, "Shared event vital stopped."); } #endregion #region ImageLibrary private void LoadImages() { if (ImageLibrary == null) return; foreach (KeyValuePair<string, string> entry in _images) { string key = entry.Key; string url = entry.Value; /* |-------------------------------------------------------------------------- | ImageLibrary AddImage |-------------------------------------------------------------------------- | Common ImageLibrary usage: | | ImageLibrary.Call("AddImage", url, key); | | url = direct image URL | key = the name/id you will use later with GetImage |-------------------------------------------------------------------------- */ ImageLibrary.Call("AddImage", url, key); } } private string GetImageId(string imageKey) { /* |-------------------------------------------------------------------------- | THIS IS THE IMPORTANT PART |-------------------------------------------------------------------------- | | CustomVitalsManager's string icon should be the ImageLibrary result: | | string icon = (string)ImageLibrary.Call("GetImage", imageKey); | | Do NOT pass: | "https://i.imgur.com/icon.png" | | Do pass: | ImageLibrary.GetImage result | | Why: | CustomVitalsManager stores your icon string directly into: | | CustomVitalInfo.icon | | Rust's custom vital UI expects the resolved image identifier, not a raw | web URL, when using custom downloaded images. |-------------------------------------------------------------------------- */ if (ImageLibrary == null) return string.Empty; object result = ImageLibrary.Call("GetImage", imageKey); if (result == null) { PrintWarning($"ImageLibrary returned null for image key '{imageKey}'."); return string.Empty; } string imageId = result.ToString(); if (string.IsNullOrEmpty(imageId)) { PrintWarning($"ImageLibrary returned an empty image ID for image key '{imageKey}'."); return string.Empty; } return imageId; } #endregion #region CustomVitalsManager Usage private void GiveBoostVital(BasePlayer player) { if (CustomVitalsManager == null || player == null) return; RemoveBoostVital(player); string icon = GetImageId(IconBoost); if (string.IsNullOrEmpty(icon)) { SendReply(player, "Boost icon is not loaded yet. Try again in a few seconds."); return; } /* |-------------------------------------------------------------------------- | API_AddVital |-------------------------------------------------------------------------- | | Parameter order: | | BasePlayer player | string icon | string iconColor | string backgroundColor | string leftText | string leftTextColor | string rightText | string rightTextColor | int timeLeft | bool active | float expiry | bool sendUpdate |-------------------------------------------------------------------------- */ uint vitalId = Convert.ToUInt32(CustomVitalsManager.Call( "API_AddVital", player, icon, // ImageLibrary GetImage ID "#FFD479", // Icon color "#000000AA", // Background color "BOOST", // Left text "#FFD479", // Left text color "2x / 60s", // Right text "#FFFFFF", // Right text color 60, // Native countdown timer true, // Active 60f, // Auto-remove after 60 seconds true // Send update instantly )); if (vitalId == 0) { SendReply(player, "Failed to add boost vital."); return; } _boostVitals[player.userID] = vitalId; } private void GiveBleedingVital(BasePlayer player) { if (CustomVitalsManager == null || player == null) return; RemoveBleedingVital(player); string icon = GetImageId(IconBleeding); if (string.IsNullOrEmpty(icon)) { SendReply(player, "Bleeding icon is not loaded yet. Try again in a few seconds."); return; } uint vitalId = Convert.ToUInt32(CustomVitalsManager.Call( "API_AddVital", player, icon, // ImageLibrary GetImage ID "#FF4444", // Icon color "#180000DD", // Background color "BLEEDING", // Left text "#FF4444", // Left text color "20s", // Right text "#FFFFFF", // Right text color 20, // Native countdown timer true, // Active 20f, // Auto-remove after 20 seconds true // Send update instantly )); if (vitalId == 0) { SendReply(player, "Failed to add bleeding vital."); return; } _bleedingVitals[player.userID] = vitalId; } private void StartSharedEventVital() { if (CustomVitalsManager == null) return; if (_sharedEventVital != 0) return; string icon = GetImageId(IconEvent); if (string.IsNullOrEmpty(icon)) { PrintWarning("Shared event icon is not loaded yet."); return; } /* |-------------------------------------------------------------------------- | API_AddSharedVital |-------------------------------------------------------------------------- | Shared vitals are visible to every connected player. |-------------------------------------------------------------------------- */ _sharedEventVital = Convert.ToUInt32(CustomVitalsManager.Call( "API_AddSharedVital", icon, // ImageLibrary GetImage ID "#FFAA33", // Icon color "#000000AA", // Background color "SERVER EVENT", // Left text "#FFAA33", // Left text color "DOUBLE GATHER", // Right text "#FFFFFF", // Right text color 3600, // Native countdown timer true, // Active 3600f, // Auto-remove after 1 hour true // Send update to everyone )); if (_sharedEventVital == 0) PrintWarning("Failed to start shared event vital."); } private void StopSharedEventVital() { if (CustomVitalsManager == null || _sharedEventVital == 0) return; CustomVitalsManager.Call("API_RemoveSharedVital", _sharedEventVital, true); _sharedEventVital = 0; } private void RemoveBoostVital(BasePlayer player) { if (CustomVitalsManager == null || player == null) return; uint vitalId; if (!_boostVitals.TryGetValue(player.userID, out vitalId)) return; CustomVitalsManager.Call("API_RemoveVital", player, vitalId, true); _boostVitals.Remove(player.userID); } private void RemoveBleedingVital(BasePlayer player) { if (CustomVitalsManager == null || player == null) return; uint vitalId; if (!_bleedingVitals.TryGetValue(player.userID, out vitalId)) return; CustomVitalsManager.Call("API_RemoveVital", player, vitalId, true); _bleedingVitals.Remove(player.userID); } private void RemovePlayerVitals(BasePlayer player) { RemoveBoostVital(player); RemoveBleedingVital(player); } #endregion #region Developer Hook Examples private object CanAddCustomVital(BasePlayer player, ProtoBuf.CustomVitalInfo vital, bool shared) { /* |-------------------------------------------------------------------------- | Return false to block another plugin from adding a vital. | Return null to allow it. |-------------------------------------------------------------------------- */ return null; } private void OnCustomVitalAdded(BasePlayer player, uint id, bool shared, ProtoBuf.CustomVitalInfo info) { Puts($"Custom vital added | ID: {id} | Shared: {shared}"); } private void OnCustomVitalRemoved(BasePlayer player, uint id, bool shared) { Puts($"Custom vital removed | ID: {id} | Shared: {shared}"); } #endregion #region Helpers private bool CheckDependencies() { bool valid = true; if (CustomVitalsManager == null) { PrintError("CustomVitalsManager is not loaded. This example plugin cannot run."); valid = false; } if (ImageLibrary == null) { PrintError("ImageLibrary is not loaded. Icons will not work."); valid = false; } return valid; } private bool CanUse(BasePlayer player) { if (player == null) return false; if (CustomVitalsManager == null) { SendReply(player, "CustomVitalsManager is not loaded."); return false; } if (ImageLibrary == null) { SendReply(player, "ImageLibrary is not loaded."); return false; } return true; } #endregion } }
Special thanks to CarbonMod
https://carbonmod.gg/
Support
