C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Oxide.Game.Rust.Cui;
using Oxide.Core.Plugins;
using UnityEngine;
using Newtonsoft.Json;
namespace Oxide.Plugins
{
[Info("Popup Notifications", "emu / k1lly0u", "0.2.0", ResourceId = 1252)]
public class PopupNotifications : RustPlugin
{
private static PopupNotifications ins;
const string popupPanel = "PopupNotification";
private string panelColor;
private string buttonColor;
private string font;
private int fontSize;
#region Oxide Hooks
private void OnServerInitialized()
{
lang.RegisterMessages(Messages, this);
permission.RegisterPermission("popupnotifications.send", this);
panelColor = UI.Color(configData.Options.Color, configData.Options.Alpha);
buttonColor = UI.Color(configData.Options.CloseColor, configData.Options.CloseAlpha);
font = configData.Options.Font;
fontSize = configData.Options.FontSize;
ins = this;
}
private void Unload()
{
foreach(BasePlayer player in BasePlayer.activePlayerList)
{
Notifier notifier = player.GetComponent<Notifier>();
if (notifier != null)
UnityEngine.Object.Destroy(notifier);
}
ins = null;
}
#endregion
#region Functions
private Notifier GetPlayerNotifier(BasePlayer player) => player.GetComponent<Notifier>() ?? player.gameObject.AddComponent<Notifier>();
private void CreatePopupOnPlayer(string message, BasePlayer player, float duration = 0f) => GetPlayerNotifier(player).PopupMessage(message, duration);
private void CreateGlobalPopup(string message, float duration = 0f)
{
foreach (BasePlayer activePlayer in BasePlayer.activePlayerList)
{
CreatePopupOnPlayer(message, activePlayer, duration);
}
}
#endregion
#region API
[HookMethod("CreatePopupNotification")]
private void CreatePopupNotification(string message, BasePlayer player = null, float duration = 0f)
{
if(player != null)
CreatePopupOnPlayer(message, player, (float)duration);
else CreateGlobalPopup(message, (float)duration);
}
#endregion
#region Component
private class Notifier : MonoBehaviour
{
private BasePlayer player;
private List<string> openPanels = new List<string>();
private List<MessageData> messageQueue = new List<MessageData>();
private Vector2 initialPos;
private Vector2 dimensions;
private int lastElementId;
private int activeElements;
private int maxElements;
private void Awake()
{
player = GetComponent<BasePlayer>();
enabled = false;
initialPos = new Vector2(ins.configData.Position.PositionX, ins.configData.Position.PositionY);
dimensions = new Vector2(ins.configData.Position.Width, ins.configData.Position.Height);
maxElements = ins.configData.MaximumMessages;
}
private void OnDestroy()
{
messageQueue.Clear();
foreach (string panel in openPanels)
CuiHelper.DestroyUi(player, panel);
}
public void PopupMessage(string message, float duration)
{
messageQueue.Add(new MessageData(message, popupPanel + lastElementId, duration == 0f ? duration = ins.configData.Duration : duration));
lastElementId++;
if (activeElements < maxElements)
UpdateMessages();
}
private void UpdateMessages()
{
if (activeElements < maxElements)
{
ClearAllElements();
for (int i = 0; i < maxElements; i++)
{
if (messageQueue.Count - 1 < i)
break;
MessageData messageData = messageQueue.ElementAt(i);
CuiElementContainer container = CreateNotification(messageData, i);
AddUi(container, messageData.elementId);
if (!messageData.started)
{
messageData.started = true;
StartCoroutine(DestroyNotification(messageData));
}
}
}
}
private IEnumerator DestroyNotification(MessageData messageData)
{
yield return new WaitForSeconds(messageData.duration);
if (messageData == null)
yield break;
messageQueue.Remove(messageData);
DestroyUi(messageData.elementId);
if (messageQueue.Count > 0)
UpdateMessages();
}
public void DestroyNotification(string elementId)
{
MessageData messageData = messageQueue.Find(x => x.elementId == elementId) ?? null;
if (messageData == null)
return;
messageQueue.Remove(messageData);
DestroyUi(messageData.elementId);
if (messageQueue.Count > 0)
UpdateMessages();
}
private void AddUi(CuiElementContainer container, string elementId)
{
openPanels.Add(elementId);
CuiHelper.AddUi(player, container);
activeElements++;
}
private void DestroyUi(string elementId)
{
openPanels.Remove(elementId);
CuiHelper.DestroyUi(player, elementId);
activeElements--;
}
private void ClearAllElements()
{
foreach (string elementId in openPanels)
CuiHelper.DestroyUi(player, elementId);
openPanels.Clear();
activeElements = 0;
}
private float[] GetFeedPosition(int number)
{
float yPos = initialPos.y - ((dimensions.y + ins.configData.Position.Spacing) * number);
return new float[] { initialPos.x, yPos, initialPos.x + dimensions.x, yPos + dimensions.y };
}
private CuiElementContainer CreateNotification(MessageData messageData, int number)
{
float[] position = GetFeedPosition(number);
CuiElementContainer container = UI.Container(messageData.elementId, $"{position[0]} {position[1]}", $"{position[2]} {position[3]}");
UI.Label(ref container, messageData.elementId, messageData.message);
if (ins.configData.Options.Close)
UI.Button(ref container, messageData.elementId, "X", $"popupmsg.close {messageData.elementId}");
return container;
}
public class MessageData
{
public string message { get; private set; }
public string elementId { get; private set; }
public float duration { get; private set; }
public bool started { get; set; }
public MessageData(string message, string elementId, float duration)
{
this.message = message;
this.elementId = elementId;
this.duration = duration;
started = false;
}
}
}
#endregion
#region UI
static public class UI
{
static public CuiElementContainer Container(string panel, string aMin, string aMax)
{
CuiElementContainer container = new CuiElementContainer()
{
{
new CuiPanel
{
Image = { Color = ins.panelColor },
RectTransform = { AnchorMin = aMin, AnchorMax = aMax },
CursorEnabled = false
},
new CuiElement().Parent = "Overlay",
panel
}
};
return container;
}
static public void Label(ref CuiElementContainer container, string panel, string text)
{
container.Add(new CuiLabel
{
Text = { FontSize = ins.fontSize, Align = TextAnchor.MiddleCenter, Text = text, Font = ins.font },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" }
},
panel);
}
static public void Button(ref CuiElementContainer container, string panel, string text, string command)
{
container.Add(new CuiButton
{
Button = { Color = ins.buttonColor, Command = command },
RectTransform = { AnchorMin = "0.89 0.79", AnchorMax = "0.99 0.99" },
Text = { Text = text, FontSize = 10, Align = TextAnchor.MiddleCenter, Font = ins.font }
},
panel);
}
static public string Color(string hexColor, float alpha)
{
if (hexColor.StartsWith("#"))
hexColor = hexColor.Substring(1);
int red = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
int green = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
int blue = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
return $"{(double)red / 255} {(double)green / 255} {(double)blue / 255} {alpha}";
}
}
#endregion
#region Commands
[ChatCommand("popupmsg")]
private void cmdSendPopupMessage(BasePlayer player, string command, string[] args)
{
if (player.IsAdmin || permission.UserHasPermission(player.UserIDString, "popupnotifications.send"))
{
if (args.Length == 1)
{
CreateGlobalPopup(args[0]);
}
else if (args.Length == 2)
{
var target = GetPlayerByName(args[0]);
if (target is string)
{
SendReply(player, (string)target);
return;
}
if (target as BasePlayer != null)
CreatePopupOnPlayer(args[1], target as BasePlayer);
}
else
SendReply(player, msg("Usage: /popupmsg \"Your message here.\" OR /popupmsg \"player name\" \"You message here.\"", player.UserIDString));
}
}
[ConsoleCommand("popupmsg.global")]
private void ccmdPopupMessageGlobal(ConsoleSystem.Arg arg)
{
if (!arg.IsAdmin)
return;
if (arg.Args == null || arg.Args.Length == 0)
{
Puts(msg("Usage: popupmsg.global \"Your message here.\" <duration>"));
return;
}
if (arg.Args.Length == 1)
CreateGlobalPopup(arg.Args[0]);
else if (arg.Args.Length == 2)
{
float duration;
if (float.TryParse(arg.Args[1], out duration))
CreateGlobalPopup(arg.Args[0], duration);
else
Puts(msg("Invalid duration"));
}
}
[ConsoleCommand("popupmsg.toplayer")]
private void ccmdPopupMessageToPlayer(ConsoleSystem.Arg arg)
{
if (!arg.IsAdmin || arg.Connection != null)
return;
if (arg.Args == null || arg.Args.Length == 0)
{
Puts(msg("Usage: popupmsg.toplayer \"Your message here.\" \"Player name\" <duration>"));
return;
}
if (arg.Args.Length >= 1)
{
var player = GetPlayerByName(arg.Args[1]);
if (player is string)
{
SendReply(arg, (string)player);
return;
}
if (arg.Args.Length == 2)
{
if (player as BasePlayer != null && (player as BasePlayer).IsConnected)
CreatePopupOnPlayer(arg.Args[0], player as BasePlayer);
else Puts(msg("Couldn't send popup notification to player"));
}
else if (arg.Args.Length == 3)
{
if (player as BasePlayer != null && (player as BasePlayer).IsConnected)
{
float duration;
if (float.TryParse(arg.Args[2], out duration))
CreatePopupOnPlayer(arg.Args[0], player as BasePlayer, duration);
else Puts(msg("Invalid duration"));
}
else Puts(msg("Couldn't send popup notification to player"));
}
}
}
private object GetPlayerByName(string name)
{
var players = covalence.Players.FindPlayers(name);
if (players != null)
{
if (players.ToList().Count == 0)
return msg("No players found with that name");
else if (players.ToList().Count > 1)
return msg("Multiple players found with that name");
else if (players.ToArray()[0].Object is BasePlayer)
{
if (!(players.ToArray()[0].Object as BasePlayer).IsConnected)
return string.Format(msg("{0} is not online"), players.ToArray()[0].Name);
return players.ToArray()[0].Object as BasePlayer;
}
}
return msg("Unable to find a valid player");
}
[ConsoleCommand("popupmsg.close")]
private void ccmdCloseCommand(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (player == null)
return;
if (arg.Args.Length == 1)
{
string elementId = arg.Args[0];
GetPlayerNotifier(player).DestroyNotification(elementId);
}
}
#endregion
#region Config
private ConfigData configData;
private class ConfigData
{
[JsonProperty(PropertyName = "Notification duration (in seconds)")]
public int Duration { get; set; }
[JsonProperty(PropertyName = "Maximum notifications shown at any time")]
public int MaximumMessages { get; set; }
[JsonProperty(PropertyName = "UI Positioning")]
public UIPosition Position { get; set; }
[JsonProperty(PropertyName = "UI Options")]
public UIOptions Options { get; set; }
public class UIPosition
{
[JsonProperty(PropertyName = "Position of the left side of notification (0.0 - 1.0)")]
public float PositionX { get; set; }
[JsonProperty(PropertyName = "Position of the bottom of noticiation (0.0 - 1.0)")]
public float PositionY { get; set; }
[JsonProperty(PropertyName = "Width (0.0 - 1.0)")]
public float Width { get; set; }
[JsonProperty(PropertyName = "Height (0.0 - 1.0)")]
public float Height { get; set; }
[JsonProperty(PropertyName = "Space between notification (0.0 - 1.0)")]
public float Spacing { get; set; }
}
public class UIOptions
{
[JsonProperty(PropertyName = "Show close button")]
public bool Close { get; set; }
[JsonProperty(PropertyName = "Panel color (hex)")]
public string Color { get; set; }
[JsonProperty(PropertyName = "Panel transparency (0.0 - 1.0)")]
public float Alpha { get; set; }
[JsonProperty(PropertyName = "Close button color (hex)")]
public string CloseColor { get; set; }
[JsonProperty(PropertyName = "Close button transparency (0.0 - 1.0)")]
public float CloseAlpha { get; set; }
[JsonProperty(PropertyName = "Font")]
public string Font { get; set; }
[JsonProperty(PropertyName = "Font size")]
public int FontSize { get; set; }
}
public Oxide.Core.VersionNumber Version { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
configData = Config.ReadObject<ConfigData>();
if (configData.Version < Version)
UpdateConfigValues();
Config.WriteObject(configData, true);
}
protected override void LoadDefaultConfig() => configData = GetBaseConfig();
private ConfigData GetBaseConfig()
{
return new ConfigData
{
Duration = 8,
MaximumMessages = 6,
Options = new ConfigData.UIOptions
{
Alpha = 0.5f,
CloseAlpha = 0.5f,
Close = true,
CloseColor = "#d85540",
Color = "#2b2b2b",
Font = "droidsansmono.ttf",
FontSize = 12
},
Position = new ConfigData.UIPosition
{
Height = 0.1f,
PositionX = 0.8f,
PositionY = 0.78f,
Spacing = 0.01f,
Width = 0.19f
},
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(configData, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
ConfigData baseConfig = GetBaseConfig();
if (configData.Version < new Core.VersionNumber(0, 2, 0))
configData = baseConfig;
configData.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Localization
private string msg(string key, string id = null) => lang.GetMessage(key, this, id);
Dictionary<string, string> Messages = new Dictionary<string, string>
{
{"Usage: /popupmsg \"Your message here.\" OR /popupmsg \"player name\" \"You message here.\"","Usage: /popupmsg \"Your message here.\" OR /popupmsg \"player name\" \"You message here.\"" },
{"Invalid duration","Invalid duration" },
{"Usage: popupmsg.global \"Your message here.\" duration","Usage: popupmsg.global \"Your message here.\" duration" },
{"Couldn't send popup notification to player","Couldn't send popup notification to player" },
{"Usage: popupmsg.toplayer \"Your message here.\" \"Player name\" <duration>","Usage: popupmsg.toplayer \"Your message here.\" \"Player name\" <duration>" },
{"No players found with that name","No players found with that name" },
{"Multiple players found with that name","Multiple players found with that name" },
{"{0} is not online","{0} is not online" },
{"Unable to find a valid player","Unable to find a valid player" }
};
#endregion
}
}