C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Game.Rust.Cui;
using System;
namespace Oxide.Plugins
{
[Info("HitMarkers", "whatsyolo", "1.0.2")]
[Description("Displays hit damage")]
public class HitMarkers : RustPlugin
{
private bool Changed;
private ConfigData configData;
private readonly Dictionary<ulong, List<DamageUI>> activeDamageUIs = new Dictionary<ulong, List<DamageUI>>();
private class ConfigData
{
public int FontSize { get; set; }
public float DisplayDuration { get; set; }
public string TextColor { get; set; }
public string Font { get; set; }
public float VerticalSpacing { get; set; }
public int MaxUIs { get; set; }
}
private class DamageUI
{
public string UIName { get; set; }
public int Damage { get; set; }
public string BaseColor { get; set; }
public float StartTime { get; set; }
}
private ConfigData GetDefaultConfig()
{
return new ConfigData
{
FontSize = 10,
DisplayDuration = 1.0f,
TextColor = "#FFFF00FF",
Font = "robotocondensed-regular.ttf",
VerticalSpacing = 0.04f,
MaxUIs = 20
};
}
protected override void LoadDefaultConfig()
{
Puts("Creating a new configuration file!");
configData = GetDefaultConfig();
Config.WriteObject(configData, true);
}
private void LoadConfigData()
{
try
{
configData = Config.ReadObject<ConfigData>();
if (configData == null)
{
LoadDefaultConfig();
}
else
{
if (configData.MaxUIs <= 0)
{
configData.MaxUIs = GetDefaultConfig().MaxUIs;
SaveConfigData();
}
}
}
catch
{
Puts("Configuration file is corrupted, loading default config...");
LoadDefaultConfig();
}
}
private void SaveConfigData()
{
Config.WriteObject(configData, true);
}
void Init()
{
LoadConfigData();
}
void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (activeDamageUIs.ContainsKey(player.userID))
{
foreach (var damageUI in activeDamageUIs[player.userID])
{
CuiHelper.DestroyUi(player, damageUI.UIName);
}
activeDamageUIs.Remove(player.userID);
}
}
private string HexToRGBA(string hexColor, float alpha = 1f)
{
if (ColorUtility.TryParseHtmlString(hexColor, out Color color))
{
return $"{color.r} {color.g} {color.b} {alpha}";
}
return $"1 1 0 {alpha}";
}
void OnPlayerAttack(BasePlayer attacker, HitInfo hitInfo)
{
if (attacker == null || hitInfo == null || hitInfo.HitEntity == null || !attacker.IsConnected) return;
float totalDamage = 0f;
foreach (var damage in hitInfo.damageTypes.types)
{
totalDamage += damage;
}
if (totalDamage <= 0) return;
int displayDamage = Mathf.RoundToInt(totalDamage);
string baseColor = hitInfo.HitBone == StringPool.Get("head") ? "#FF0000FF" : configData.TextColor;
if (!activeDamageUIs.ContainsKey(attacker.userID))
{
activeDamageUIs[attacker.userID] = new List<DamageUI>();
}
if (activeDamageUIs[attacker.userID].Count >= configData.MaxUIs)
{
var oldest = activeDamageUIs[attacker.userID][0];
CuiHelper.DestroyUi(attacker, oldest.UIName);
activeDamageUIs[attacker.userID].RemoveAt(0);
}
string uiName = $"HitMarkerDamage_{attacker.userID}_{activeDamageUIs[attacker.userID].Count}_{Time.realtimeSinceStartup}";
var damageUI = new DamageUI
{
UIName = uiName,
Damage = displayDamage,
BaseColor = baseColor,
StartTime = Time.realtimeSinceStartup
};
activeDamageUIs[attacker.userID].Add(damageUI);
try
{
const int fadeSteps = 5;
float stepDuration = configData.DisplayDuration / fadeSteps;
UpdateDamageUI(attacker, damageUI, 1f);
int currentStep = 0;
timer.Repeat(stepDuration, fadeSteps, () =>
{
if (attacker == null || !attacker.IsConnected)
{
CuiHelper.DestroyUi(attacker, damageUI.UIName);
activeDamageUIs[attacker.userID].Remove(damageUI);
return;
}
currentStep++;
float alpha = Mathf.Lerp(1f, 0f, (float)currentStep / fadeSteps);
UpdateDamageUI(attacker, damageUI, alpha);
if (currentStep >= fadeSteps)
{
CuiHelper.DestroyUi(attacker, damageUI.UIName);
activeDamageUIs[attacker.userID].Remove(damageUI);
}
});
timer.Once(configData.DisplayDuration + 0.1f, () =>
{
if (attacker != null && attacker.IsConnected && activeDamageUIs[attacker.userID].Contains(damageUI))
{
CuiHelper.DestroyUi(attacker, damageUI.UIName);
activeDamageUIs[attacker.userID].Remove(damageUI);
}
});
}
catch
{
CuiHelper.DestroyUi(attacker, uiName);
activeDamageUIs[attacker.userID].Remove(damageUI);
}
}
private void UpdateDamageUI(BasePlayer player, DamageUI damageUI, float alpha)
{
CuiHelper.DestroyUi(player, damageUI.UIName);
int index = activeDamageUIs[player.userID].IndexOf(damageUI);
if (index == -1) return;
float verticalOffset = index * configData.VerticalSpacing;
// Position just above crosshairs
float baseY = 0.55f;
var cui = new CuiElementContainer();
cui.Add(new CuiElement
{
Name = damageUI.UIName,
Parent = "Overlay",
Components =
{
new CuiTextComponent
{
Text = damageUI.Damage.ToString(),
FontSize = configData.FontSize,
Font = configData.Font,
Color = HexToRGBA(damageUI.BaseColor, alpha),
Align = TextAnchor.MiddleCenter
},
new CuiRectTransformComponent
{
AnchorMin = $"0.49 {baseY + verticalOffset}",
AnchorMax = $"0.51 {baseY + 0.04f + verticalOffset}"
}
}
});
CuiHelper.AddUi(player, cui);
}
}
}