C#:
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System;
using UnityEngine;
namespace Oxide.Plugins {
[Info("RadialMenu", "MTriper", "1.2.9")]
[Description("A customizable quick access panel with all popular and frequently used server commands.")]
class RadialMenu : RustPlugin {
#region Fields
[PluginReference]
private Plugin ImageLibrary, NTeleportation, Teleportation, Friends, Clans;
QuickMenuConfig config;
const bool isRus = false;
//HashSet<string> activeUsers = new HashSet<string>();
Dictionary<string, bool> activeUsers = new Dictionary<string, bool>();
// Value: true - command, false - MMB
#endregion
#region Config
class QuickMenuConfig {
[JsonProperty(isRus ? "Отслеживать нажатие СКМ для открытия радиального меню? [повышенная нагрузка]" : "Track MMB presses to open a radial menu? [increased load]")]
public bool canMMB = true;
[JsonProperty(isRus ? "Команда для открытия радиального меню [если пусто, то отключено]" : "The command to open the radial menu [if empty, it's disabled]")]
public string command = string.Empty;
[JsonProperty(isRus ? "Подсказка о том, как закрыть панель [если используется команда выше]" : "Hint on how to close the panel [if using the command above]")]
public string closeMessage = isRus ? "Введите команду еще раз или нажмите в свободном месте, чтобы закрыть панель" : "Enter the command again or click in an empty space to close the panel";
[JsonProperty(isRus ? "Сообщение игроку в чат если у него нет привилегии" : "Message to player in chat if he does not have a privilege")]
public string needPerm = isRus ? "У вас нет разрешения на использование этой команды!" : "You do not have permission to use this command!";
[JsonProperty(isRus ? "Изображение с 1-им кольцом меню" : "Image with 1 menu ring")]
public string imgLVL1 = "https://i.postimg.cc/BnGyG8Cs/1-lvl.png";
[JsonProperty(isRus ? "Изображение с 2-мя кольцами меню" : "Image with 2 menu rings")]
public string imgLVL2 = "https://i.postimg.cc/Nj7nGgNX/2-lvl.png";
[JsonProperty(isRus ? "Изображение с 3-мя кольцами меню" : "Image with 3 menu rings")]
public string imgLVL3 = "https://i.postimg.cc/VNfVd3TV/3-lvl.png";
[JsonProperty(isRus ? "Загружать изображения игрокам при подключении?" : "Upload images to players when they connect?")]
public bool cacheImages = true;
[JsonProperty(isRus ? "Размер радиального меню" : "Radial menu size")]
public int menuSize = 250;
[JsonProperty(isRus ? "Скорость появления радиального меню" : "Speed of displaying the radial menu")]
public float menuFadeIn = 0.5f;
[JsonProperty(isRus ? "Размер иконки" : "Icon size")]
public int sectionSize = 15;
[JsonProperty(isRus ? "Размер текста" : "Text size")]
public int textSize = 8;
[JsonProperty(isRus ? "Скорость появления иконок" : "Speed of displaying icons")]
public float sectionFadeIn = 0.5f;
[JsonProperty(isRus ? "Расстояние от центра до иконок 1-го уровня" : "Distance from center to 1st level icons")]
public float distanceLVL1 = 0.194f;
[JsonProperty(isRus ? "Расстояние от центра до иконок 2-го уровня" : "Distance from center to 2nd level icons")]
public float distanceLVL2 = 0.32f;
[JsonProperty(isRus ? "Расстояние от центра до иконок 3-го уровня" : "Distance from center to 3rd level icons")]
public float distanceLVL3 = 0.446f;
[JsonProperty(isRus ? "Разделы" : "Sections", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<MenuSectionClass> menuSections = new List<MenuSectionClass> {
new MenuSectionClass {
name = "Teleport", perm = "_homes", img = "https://i.postimg.cc/tJ4f7dG0/homes.png", separate = 15, subsections = {
new MenuSectionClass {
name = "Home 1", perm = "home_1_all", img = "https://i.postimg.cc/V6RpYGTB/home.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "teleport", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", command = "home {0}", colorInactive = "#57de54"
}
, new MenuSectionClass {
name = "Remove", perm = "remove", img = "https://i.postimg.cc/K8k6n5kx/del.png", command = "home remove {0}", colorInactive = "#f55757"
}
}
}
, new MenuSectionClass {
name = "Home 2", perm = "home_2_all", img = "https://i.postimg.cc/V6RpYGTB/home.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "teleport", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", command = "home {0}", colorInactive = "0.34 0.87 0.33 1"
}
, new MenuSectionClass {
name = "Remove", perm = "remove", img = "https://i.postimg.cc/K8k6n5kx/del.png", command = "home remove {0}", colorInactive = "0.96 0.34 0.34 1"
}
}
}
, new MenuSectionClass {
name = "Home 3", perm = "home_3_all", img = "https://i.postimg.cc/V6RpYGTB/home.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "teleport", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", command = "home {0}", colorInactive = "0.34 0.87 0.33 1"
}
, new MenuSectionClass {
name = "Remove", perm = "remove", img = "https://i.postimg.cc/K8k6n5kx/del.png", command = "home remove {0}", colorInactive = "0.96 0.34 0.34 1"
}
}
}
, new MenuSectionClass {
name = "Add", perm = "home_add", img = "https://i.postimg.cc/8PKGd5ZB/add.png", command = "home add {0}"
}
}
}
, new MenuSectionClass {
name = "Friends", perm = "_teammates", img = "https://i.postimg.cc/rmc72Xhw/friends.png", separate = 15, subsections = {
new MenuSectionClass {
name = "Friend 1", perm = "friend_1_all", img = "https://i.postimg.cc/q72WFMxF/friend.png", separate = 20, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "teleport", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", command = "tpr {0}"
}
, new MenuSectionClass {
name = "Trade", perm = "trade", img = "https://i.postimg.cc/BvCMD8gJ/trade.png", command = "trade {0}"
}
}
}
, new MenuSectionClass {
name = "Friend 2", perm = "friend_2_all", img = "https://i.postimg.cc/q72WFMxF/friend.png", separate = 20, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "teleport", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", command = "tpr {0}"
}
, new MenuSectionClass {
name = "Trade", perm = "trade", img = "https://i.postimg.cc/BvCMD8gJ/trade.png", command = "trade {0}"
}
}
}
, new MenuSectionClass {
name = "Friend 3", perm = "friend_3_all", img = "https://i.postimg.cc/q72WFMxF/friend.png", separate = 20, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "teleport", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", command = "tpr {0}"
}
, new MenuSectionClass {
name = "Trade", perm = "trade", img = "https://i.postimg.cc/BvCMD8gJ/trade.png", command = "trade {0}"
}
}
}
,
}
}
, new MenuSectionClass {
name = "Remove", perm = "remove", img = "https://i.postimg.cc/XqP2JTgF/remove.png", command = "remove", neededperm = "removertool.normal, removertool.structure, removertool.external", showAll = true
}
, new MenuSectionClass {
name = "Recycler", perm = "recycler", img = "https://i.postimg.cc/sxx63V5s/recycler.png", command = "rec"
}
, new MenuSectionClass {
name = "Skins", perm = "skins", img = "https://i.postimg.cc/026W3y8C/skins.png", command = "skins", neededperm = "skins.use", onlyPerms = true
}
, new MenuSectionClass {
name = "Craft", perm = "craft", img = "https://i.postimg.cc/FsR5Kvhf/craft.png", command = "craft", neededperm = "craft.use", showAll = true
}
, new MenuSectionClass {
name = "Turrets", perm = "turrets", img = "https://i.postimg.cc/prmZgstd/turrets.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Turret on/off", perm = "one", img = "https://i.postimg.cc/j5dvhsgP/turret-1.png", command = "turret"
}
, new MenuSectionClass {
name = "Turrets on", perm = "all_on", img = "https://i.postimg.cc/mDXjX0Jq/turret-all.png", command = "turret.tc on", colorInactive = "0.34 0.87 0.33 1"
}
, new MenuSectionClass {
name = "Turrets off", perm = "all_off", img = "https://i.postimg.cc/mDXjX0Jq/turret-all.png", command = "turret.tc off", colorInactive = "0.96 0.34 0.34 1"
}
, new MenuSectionClass {
name = "SAMs on", perm = "sams_on", img = "https://i.postimg.cc/nVSS0Dtz/sams.png", command = "sam.tc on", colorInactive = "0.34 0.87 0.33 1"
}
, new MenuSectionClass {
name = "SAMs off", perm = "sams_off", img = "https://i.postimg.cc/nVSS0Dtz/sams.png", command = "sam.tc off", colorInactive = "0.96 0.34 0.34 1"
}
}
}
, new MenuSectionClass {
name = "Notify", perm = "notify_all", img = "https://i.postimg.cc/TYSSYWkS/notify.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Teleport", perm = "tp", img = "https://i.postimg.cc/FKvT7b0P/teleport.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Accept", perm = "accept", img = "https://i.postimg.cc/J44mBJ5z/accept.png", command = "tpa", colorInactive = "0.34 0.87 0.33 1"
}
, new MenuSectionClass {
name = "Cancel", perm = "cancel", img = "https://i.postimg.cc/85SgkrCD/cancel.png", command = "tpc", colorInactive = "0.96 0.34 0.34 1"
}
}
}
, new MenuSectionClass {
name = "Trade", perm = "trade", img = "https://i.postimg.cc/BvCMD8gJ/trade.png", separate = -15, subsections = {
new MenuSectionClass {
name = "Accept", perm = "accept", img = "https://i.postimg.cc/J44mBJ5z/accept.png", command = "trade accept", colorInactive = "0.34 0.87 0.33 1"
}
, new MenuSectionClass {
name = "Cancel", perm = "cancel", img = "https://i.postimg.cc/85SgkrCD/cancel.png", command = "trade cancel", colorInactive = "0.96 0.34 0.34 1"
}
}
}
}
}
}
;
}
class MenuSectionClass {
[JsonProperty(isRus ? "Название раздела" : "Section name")]
public string name = "";
[JsonProperty(isRus ? "Включить раздел?" : "Enable this section?")]
public bool isEnable = true;
[JsonProperty(isRus ? "Permission раздела [обязательно]" : "Section permission [required]")]
public string perm = "";
[JsonProperty(isRus ? "Иконка раздела" : "Section icon")]
public string img = "";
[JsonProperty(isRus ? "Залить иконку цветом ниже?" : "Fill icon with color below?")]
public bool isFill = true;
[JsonProperty(isRus ? "Цвет, когда раздел неактивен [HEX или Unity RGBA]" : "Inactive section color [HEX or Unity RGBA]")]
public string colorInactive = "#fade54";
[JsonProperty(isRus ? "Цвет, когда раздел активен [HEX или Unity RGBA]" : "Active section color [HEX or Unity RGBA]")]
public string colorActive = "#e3e3e3";
[JsonProperty(isRus ? "Команда от имени игрока [если пусто, то используются подразделы ниже]" : "Command executed on behalf of the player [if empty, the subsections below are used]")]
public string command = "";
[JsonProperty(isRus ? "Тип выполняемой команды [true - чат, false - консоль]" : "Command type [true - chat, false - console]")]
public bool isChat = true;
[JsonProperty(isRus ? "Permissions для команды выше [через запятую.необязательно].Проверяет есть ли у игрока такие permissions." : "Permissions for above command [comma separated.optional].Checks if the player has such a permissions.")]
public string neededperm = "";
[JsonProperty(isRus ? "Показывать этот раздел только тем, у кого есть есть привилегии выше?" : "Show this section only to those with permissions above?")]
public bool onlyPerms = false;
[JsonProperty(isRus ? "Показывать этот раздел даже тем, у кого нет привилегий?" : "Show this section even to those who don't have permissions?")]
public bool showAll = false;
[JsonProperty(isRus ? "Расположение подразделов [n - число]: 0 - авто; n - с начала; -n - от активного элемента." : "Subsections layout [n - number]: 0 - auto; n - from the beginning; -n - from an active element")]
public int separate = 0;
[JsonProperty(isRus ? "Подразделы" : "Subsections", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<MenuSectionClass> subsections = new List<MenuSectionClass>();
}
protected override void LoadDefaultConfig() {
config = new QuickMenuConfig();
SaveConfig();
}
void ReadConfig() {
config = Config.ReadObject<QuickMenuConfig>();
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config, true);
IEnumerator CheckConfig() {
if (!config.canMMB) {
Unsubscribe("OnPlayerInput");
Unsubscribe("OnItemSplit");
}
if (config.command != string.Empty) cmd.AddChatCommand(config.command, this, "Command");
cmd.AddChatCommand("rm", this, "Command");
if (string.IsNullOrEmpty(config.imgLVL1) || string.IsNullOrEmpty(config.imgLVL2) || string.IsNullOrEmpty(config.imgLVL3)) {
PrintError("One of the radial menu images is not set!");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
for (int i = config.menuSections.Count - 1; i > -1; i--) {
if (!config.menuSections[i].isEnable) {
config.menuSections.RemoveAt(i);
continue;
}
yield return CoroutineEx.waitForFixedUpdate;
if (config.menuSections[i].perm == "") {
PrintError($"One of the sections ({config.menuSections[i].name}) has no permission set.");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
if (config.menuSections[i].perm == "_homes") {
if ((NTeleportation == null || !NTeleportation.IsLoaded) & (Teleportation == null || !Teleportation.IsLoaded)) PrintError("Supported teleport plugin not found!");
}
if (config.menuSections[i].perm == "_friends") {
if (Friends == null || !Friends.IsLoaded || Friends.Author != "Wulf") PrintError("Supported plugin 'Friends' not found!");
}
if (config.menuSections[i].perm == "_clans") {
if (Clans == null || !Clans.IsLoaded || Clans.Author != "k1lly0u") PrintError("Supported plugin 'Clans' not found!");
}
if (config.menuSections.Where(x => x.perm == config.menuSections[i].perm).Count() > 1) {
PrintError($"Several sections have the same permission - '{config.menuSections[i].perm}'.");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
config.menuSections[i].colorInactive = ToUnityRGBA(config.menuSections[i].colorInactive);
config.menuSections[i].colorActive = ToUnityRGBA(config.menuSections[i].colorActive);
permission.RegisterPermission($"radialmenu.{config.menuSections[i].perm}", this);
ImageLibrary.Call("AddImage", config.menuSections[i].img, $"RadialMenu.{config.menuSections[i].perm}");
if (config.menuSections[i].command == "" && config.menuSections[i].subsections.IsNullOrEmpty()) {
PrintWarning($"The '{config.menuSections[i].name}' section is missing a command and subsections.");
}
for (int j = config.menuSections[i].subsections.Count - 1; j > -1; j--) {
if (!config.menuSections[i].subsections[j].isEnable) {
config.menuSections[i].subsections.RemoveAt(j);
continue;
}
if (config.menuSections[i].subsections[j].perm == "") {
PrintError($"One of the subsections ({config.menuSections[i].subsections[j].name}) has no permission set.");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
if (config.menuSections[i].subsections.Where(x => x.perm == config.menuSections[i].subsections[j].perm).Count() > 1) {
PrintError($"Several subsections have the same permission - '{config.menuSections[i].subsections[j].perm}'.");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
config.menuSections[i].subsections[j].colorInactive = ToUnityRGBA(config.menuSections[i].subsections[j].colorInactive);
config.menuSections[i].subsections[j].colorActive = ToUnityRGBA(config.menuSections[i].subsections[j].colorActive);
if (!config.menuSections[i].perm.Contains("_all")) permission.RegisterPermission($"radialmenu.{config.menuSections[i].perm}.{config.menuSections[i].subsections[j].perm}", this);
ImageLibrary.Call("AddImage", config.menuSections[i].subsections[j].img, $"RadialMenu.{config.menuSections[i].perm}.{config.menuSections[i].subsections[j].perm}");
if (config.menuSections[i].subsections[j].command == "" && config.menuSections[i].subsections[j].subsections.IsNullOrEmpty()) {
PrintWarning($"The '{config.menuSections[i].subsections[j].name}' section is missing a command and subsections.");
}
for (int k = config.menuSections[i].subsections[j].subsections.Count - 1; k > -1; k--) {
if (!config.menuSections[i].subsections[j].subsections[k].isEnable) {
config.menuSections[i].subsections[j].subsections.RemoveAt(k);
continue;
}
if (!config.menuSections[i].subsections[j].subsections[k].subsections.IsNullOrEmpty()) {
PrintError($"Only 2 levels of attachments are allowed - {config.menuSections[i].perm} => {config.menuSections[i].subsections[j].perm} => {config.menuSections[i].subsections[j].subsections[k].perm} ---> {config.menuSections[i].subsections[j].subsections[k].subsections[0].perm}");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
if (config.menuSections[i].subsections[j].subsections[k].perm == "") {
PrintError($"One of the subsections ({config.menuSections[i].subsections[j].subsections[k].name}) has no permission set.");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
if (config.menuSections[i].subsections[j].subsections.Where(x => x.perm == config.menuSections[i].subsections[j].subsections[k].perm).Count() > 1) {
PrintError($"Several subsections have the same permission - '{config.menuSections[i].subsections[j].subsections[k].perm}'.");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
yield break;
}
config.menuSections[i].subsections[j].subsections[k].colorInactive = ToUnityRGBA(config.menuSections[i].subsections[j].subsections[k].colorInactive);
config.menuSections[i].subsections[j].subsections[k].colorActive = ToUnityRGBA(config.menuSections[i].subsections[j].subsections[k].colorActive);
if (!config.menuSections[i].perm.Contains("_all") && !config.menuSections[i].subsections[j].perm.Contains("_all")) permission.RegisterPermission($"radialmenu.{config.menuSections[i].perm}.{config.menuSections[i].subsections[j].perm}.{config.menuSections[i].subsections[j].subsections[k].perm}", this);
ImageLibrary.Call("AddImage", config.menuSections[i].subsections[j].subsections[k].img, $"RadialMenu.{config.menuSections[i].perm}.{config.menuSections[i].subsections[j].perm}.{config.menuSections[i].subsections[j].subsections[k].perm}");
if (config.menuSections[i].subsections[j].subsections[k].command == "") {
PrintWarning($"The '{config.menuSections[i].subsections[j].subsections[k].name}' section is missing a command.");
}
}
}
}
}
#endregion
#region Hooks
private void OnServerInitialized() {
if (!ImageLibrary) {
PrintError("The \"ImageLibrary\" plugin is missing!");
NextTick(() => Interface.Oxide.UnloadPlugin(Title));
return;
}
ReadConfig();
ServerMgr.Instance.StartCoroutine(CheckConfig());
ImageLibrary.Call("AddImage", config.imgLVL1, "RadialMenu.LVL1");
ImageLibrary.Call("AddImage", config.imgLVL2, "RadialMenu.LVL2");
ImageLibrary.Call("AddImage", config.imgLVL3, "RadialMenu.LVL3");
if (!config.cacheImages) Unsubscribe("OnPlayerConnected");
foreach (var item in config.menuSections) ImageLibrary.Call("AddImage", item.img, $"RadialMenu.{item.perm}");
}
void Unload() {
ServerMgr.Instance.StopCoroutine(CheckConfig());
foreach (BasePlayer player in BasePlayer.activePlayerList) CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
}
void OnPlayerConnected(BasePlayer player) {
if (player.IsReceivingSnapshot) {
NextTick(() => OnPlayerConnected(player));
return;
}
ImageLibrary.Call("SendImage", player, "RadialMenu.LVL1");
ImageLibrary.Call("SendImage", player, "RadialMenu.LVL2");
ImageLibrary.Call("SendImage", player, "RadialMenu.LVL3");
}
void OnPlayerDisconnected(BasePlayer player) => activeUsers.Remove(player.UserIDString);
void OnPlayerInput(BasePlayer player, InputState input) {
if (player == null || !input.WasJustReleased(BUTTON.FIRE_THIRD) || !player.CanInteract() || player.inventory.loot.IsLooting()) return;
if (activeUsers.ContainsKey(player.UserIDString)) {
CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
activeUsers.Remove(player.UserIDString);
return;
}
else activeUsers.Add(player.UserIDString, false);
OpenPanel(player);
}
/*object OnInventoryNetworkUpdate(PlayerInventory inventory, ItemContainer container, ProtoBuf.UpdateItemContainer updateItemContainer, PlayerInventory.Type type, bool broadcast)
{
if (container?.playerOwner != null && type != PlayerInventory.Type.Wear)
{
try
{
CuiHelper.DestroyUi(container.playerOwner, "RadialMenu.MainPanel");
activeUsers.Remove(container.playerOwner.UserIDString);
}
catch{}
}
return null;
}*/
Item OnItemSplit(Item item, int amount) {
BasePlayer player = item.GetOwnerPlayer();
if (player != null) {
CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
activeUsers.Remove(player.UserIDString);
}
return null;
}
#endregion
#region Commands
[ConsoleCommand("distributionCommand")]
void DistributionCommand(ConsoleSystem.Arg args) {
BasePlayer player = args?.Player();
if (player == null) return;
if (args.Args[0] == "close") {
CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
activeUsers.Remove(player.UserIDString);
return;
}
string[] sections = args.Args[0].Split('.');
int index = Array.FindIndex(sections, x => x.Contains("_all"));
MenuSectionClass neededSection = config.menuSections.Find(x => x.perm == sections[0]);
switch (index) {
case 0 : if (!permission.UserHasPermission(player.UserIDString, $"RadialMenu.{sections[0]}")) {
SendReply(player, config.needPerm);
OpenPanel(player, sections.Take(sections.Length - 1).ToArray());
return;
}
break;
case 1 : if (!permission.UserHasPermission(player.UserIDString, $"RadialMenu.{sections[0]}.{sections[1]}")) {
SendReply(player, config.needPerm);
OpenPanel(player, sections.Take(sections.Length - 1).ToArray());
return;
}
break;
default : if (!permission.UserHasPermission(player.UserIDString, $"RadialMenu.{args.Args[0]}")) {
SendReply(player, config.needPerm);
OpenPanel(player, sections.Take(sections.Length - 1).ToArray());
return;
}
break;
}
for (int i = 1; i < sections.Length; i++) neededSection = neededSection.subsections.Find(x => x.perm == sections[i]);
if (!UserHasNeededPerm(player.UserIDString, neededSection.neededperm)) {
SendReply(player, config.needPerm);
OpenPanel(player, sections.Take(sections.Length - 1).ToArray());
return;
}
if (args.Args.ElementAtOrDefault(1) == "chat") // Пример: _friends.friend_1.teleport chat tpr *name*
{
player.Command($"chat.say \"/{string.Join(" ", args.Args.Skip(2)).Replace("%steamid%", player.UserIDString, StringComparison.OrdinalIgnoreCase).Replace("%username%", player.displayName, StringComparison.OrdinalIgnoreCase)}\"");
activeUsers.Remove(player.UserIDString);
CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
return;
}
else if (args.Args.ElementAtOrDefault(1) == "console") {
switch (args.Args[2]) {
case "spawn" : if (args.Args.Length == 3) {
player.ConsoleMessage("No entity name provided");
break;
}
RaycastHit hitInfo;
if (Physics.Raycast(player.eyes.BodyRay().origin, player.eyes.HeadRay().direction, out hitInfo, 100, LayerMask.GetMask("Terrain"), QueryTriggerInteraction.Ignore)) ConVar.Entity.svspawn(args.Args[3], hitInfo.point, -player.eyes.MovementForward());
break;
case "spawnat" : if (args.Args.Length <= 4) {
player.ConsoleMessage("Not enough arguments");
break;
}
Vector3 position = args.Args[4]?.ToVector3() ?? Vector3.zero;
Vector3 rotation = args.Args.ElementAtOrDefault(5)?.ToVector3() ?? player.eyes.MovementForward();
ConVar.Entity.svspawn(args.Args[3], position, rotation);
break;
case "spawnhere" : if (args.Args.Length == 3) {
player.ConsoleMessage("No entity name provided");
break;
}
Vector3 position2 = player.eyes.HeadRay().GetPoint(float.Parse(args.Args.ElementAtOrDefault(4) ?? "0"));
Vector3 rotation2 = args.Args.ElementAtOrDefault(5)?.ToVector3() ?? player.eyes.HeadRay().direction;
ConVar.Entity.svspawn(args.Args[3], position2, rotation2);
break;
case "spawnitem" : if (args.Args.Length == 3) {
player.ConsoleMessage("No entity name provided");
break;
}
RaycastHit hitInfo2;
if (Physics.Raycast(player.eyes.BodyRay().origin, player.eyes.HeadRay().direction, out hitInfo2, 100, LayerMask.GetMask("Terrain"), QueryTriggerInteraction.Ignore)) ConVar.Entity.svspawnitem(args.Args[3], hitInfo2.point);
break;
default : player.Command(args.Args[2], args.Args.Skip(3).Select(x => x.Replace("%steamid%", player.UserIDString, StringComparison.OrdinalIgnoreCase).Replace("%username%", player.displayName, StringComparison.OrdinalIgnoreCase)));
break;
}
activeUsers.Remove(player.UserIDString);
CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
return;
}
if (!neededSection.subsections.IsNullOrEmpty()) OpenPanel(player, sections);
}
void Command(BasePlayer player) {
if (player != null && player.CanInteract()) {
if (activeUsers.ContainsKey(player.UserIDString)) {
CuiHelper.DestroyUi(player, "RadialMenu.MainPanel");
activeUsers.Remove(player.UserIDString);
return;
}
else activeUsers.Add(player.UserIDString, true);
OpenPanel(player);
}
}
#endregion
#region Functions
HashSet<MenuSectionClass> GetValidSections(BasePlayer player, string[] lvls) {
HashSet<MenuSectionClass> sections = new HashSet<MenuSectionClass>();
foreach (var lvl1 in config.menuSections) {
MenuSectionClass section1 = new MenuSectionClass();
if (!lvl1.showAll) {
if (permission.UserHasPermission(player.UserIDString, $"RadialMenu.{lvl1.perm}")) {
if (lvl1.onlyPerms && !UserHasNeededPerm(player.UserIDString, lvl1.neededperm)) continue;
section1.neededperm = lvl1.neededperm;
section1.name = lvl1.name;
section1.perm = lvl1.perm;
section1.command = lvl1.command;
section1.isChat = lvl1.isChat;
section1.isFill = lvl1.isFill;
section1.separate = lvl1.separate;
section1.colorActive = lvl1.colorActive;
section1.colorInactive = lvl1.colorInactive;
}
else continue;
}
else {
section1.neededperm = lvl1.neededperm;
section1.name = lvl1.name;
section1.perm = lvl1.perm;
section1.command = lvl1.command;
section1.isChat = lvl1.isChat;
section1.isFill = lvl1.isFill;
section1.separate = lvl1.separate;
section1.colorActive = lvl1.colorActive;
section1.colorInactive = lvl1.colorInactive;
}
if (lvls.IsNullOrEmpty() || lvl1.perm != lvls[0]) {
sections.Add(section1);
continue;
}
foreach (var lvl2 in lvl1.subsections) {
MenuSectionClass section2 = new MenuSectionClass();
if (!lvl2.showAll) {
if (lvl1.perm.Contains("_all") || permission.UserHasPermission(player.UserIDString, $"RadialMenu.{lvl1.perm}.{lvl2.perm}")) {
if (lvl2.onlyPerms && !UserHasNeededPerm(player.UserIDString, lvl2.neededperm)) continue;
section2.neededperm = lvl2.neededperm;
section2.name = lvl2.name;
section2.perm = lvl2.perm;
section2.command = lvl2.command;
section2.isChat = lvl2.isChat;
section2.isFill = lvl2.isFill;
section2.separate = lvl2.separate;
section2.colorActive = lvl2.colorActive;
section2.colorInactive = lvl2.colorInactive;
}
else continue;
}
else {
section2.neededperm = lvl2.neededperm;
section2.name = lvl2.name;
section2.perm = lvl2.perm;
section2.command = lvl2.command;
section2.isChat = lvl2.isChat;
section2.isFill = lvl2.isFill;
section2.separate = lvl2.separate;
section2.colorActive = lvl2.colorActive;
section2.colorInactive = lvl2.colorInactive;
}
if (lvls.IsNullOrEmpty() || lvls.Length != 2 || lvl2.perm != lvls[1]) {
section1.subsections.Add(section2);
continue;
}
foreach (var lvl3 in lvl2.subsections) {
MenuSectionClass section3 = new MenuSectionClass();
if (!lvl3.showAll) {
if (lvl1.perm.Contains("_all") || lvl2.perm.Contains("_all") || permission.UserHasPermission(player.UserIDString, $"RadialMenu.{lvl1.perm}.{lvl2.perm}.{lvl3.perm}")) {
if (lvl3.onlyPerms && !UserHasNeededPerm(player.UserIDString, lvl3.neededperm)) continue;
section3.neededperm = lvl3.neededperm;
section3.name = lvl3.name;
section3.perm = lvl3.perm;
section3.command = lvl3.command;
section3.isChat = lvl3.isChat;
section3.isFill = lvl3.isFill;
section3.colorActive = lvl3.colorActive;
section3.colorInactive = lvl3.colorInactive;
}
else continue;
}
else {
section3.neededperm = lvl3.neededperm;
section3.name = lvl3.name;
section3.perm = lvl3.perm;
section3.command = lvl3.command;
section3.isChat = lvl3.isChat;
section3.isFill = lvl3.isFill;
section3.colorActive = lvl3.colorActive;
section3.colorInactive = lvl3.colorInactive;
}
section2.subsections.Add(section3);
}
if (!section1.subsections.Contains(section2)) section1.subsections.Add(section2);
}
sections.Add(section1);
}
return sections;
}
void AdditionalFunctionality(BasePlayer player, ref HashSet<MenuSectionClass> validSections, string[] lvls) {
if (lvls.IsNullOrEmpty()) return;
switch (lvls[0]) {
case "_homes" : case "_homes_all" : MenuSectionClass homes = validSections.First(x => x.perm.StartsWith("_homes"));
Dictionary<string, Vector3> teleportHomes = new Dictionary<string, Vector3>();
if (NTeleportation != null && NTeleportation.IsLoaded) teleportHomes = NTeleportation?.Call("API_GetHomes", player) as Dictionary<string, Vector3>;
else if (Teleportation != null && Teleportation.IsLoaded && Teleportation.Author == "OxideBro") teleportHomes = Teleportation?.Call("GetHomes", (ulong)player.userID) as Dictionary<string, Vector3>;
else {
PrintWarning("Supported teleport plugin not found!");
homes.subsections.Clear();
return;
}
HashSet<string> delHomes = new HashSet<string>();
int totalHomesCount = homes.subsections.Where(x => x.perm.Contains("home_")).Count();
int passedHomes = 0;
for (int i = 0; i < homes.subsections.Count; i++) {
if (!homes.subsections[i].perm.Contains("home_")) continue;
if (homes.subsections[i].perm == "home_add") {
string grid = MapHelper.PositionToString(player.transform.position);
int c = 1 + teleportHomes.Keys.Count(x => x.Contains(grid));
homes.subsections[i].command = string.Format(homes.subsections[i].command, string.Format("{0}h{1}", grid, c));
continue;
}
if (!teleportHomes.IsNullOrEmpty() && passedHomes < totalHomesCount && passedHomes < teleportHomes.Count) {
homes.subsections[i].name = teleportHomes.ElementAt(passedHomes).Key;
foreach (var item in homes.subsections[i].subsections) {
if (item.command.Contains("{0}")) item.command = string.Format(item.command, homes.subsections[i].name);
}
passedHomes++;
}
else delHomes.Add(homes.subsections[i].perm);
}
foreach (var id in delHomes) homes.subsections.RemoveAll(x => x.perm == id);
break;
case "_clans" : case "_clans_all" : case "_friends" : case "_friends_all" : case "_teammates" : case "_teammates_all" : MenuSectionClass friends = new MenuSectionClass();
List<ulong> friendsList = new List<ulong>();
if (lvls[0].StartsWith("_friends")) {
friends = validSections.First(x => x.perm.StartsWith("_friends"));
if (Friends == null || !Friends.IsLoaded || Friends.Author != "Wulf") {
PrintWarning("Supported plugin 'Friends' not found!");
friends.subsections.Clear();
return;
}
friendsList = Friends?.Call<ulong[]>("GetFriends", (ulong)player.userID).ToList();
}
else if (lvls[0].StartsWith("_teammates")) {
friends = validSections.First(x => x.perm.StartsWith("_teammates"));
friendsList = player.Team?.members.ToList();
}
else if (lvls[0].StartsWith("_clans")) {
friends = validSections.First(x => x.perm.StartsWith("_clans"));
if (Clans == null || !Clans.IsLoaded || Clans.Author != "k1lly0u") {
PrintWarning("Supported plugin 'Clans' not found!");
friends.subsections.Clear();
return;
}
friendsList = Clans?.Call<List<string>>("GetClanMembers", player.UserIDString).Select(ulong.Parse).ToList();
}
HashSet<string> delFriends = new HashSet<string>();
int totalFriendsCount = friends.subsections.Where(x => x.perm.Contains("friend_")).Count();
int passedFriends = 0;
friendsList?.Remove((ulong)player.userID);
for (int i = 0; i < friends.subsections.Count; i++) {
if (!friends.subsections[i].perm.Contains("friend_")) continue;
if (!friendsList.IsNullOrEmpty() && passedFriends < totalFriendsCount && passedFriends < friendsList.Count) {
friends.subsections[i].name = BasePlayer.FindAwakeOrSleeping(friendsList.ElementAt(passedFriends).ToString()).displayName;
foreach (var item in friends.subsections[i].subsections) {
if (item.command.Contains("{0}")) item.command = string.Format(item.command, friends.subsections[i].name);
}
passedFriends++;
}
else delFriends.Add(friends.subsections[i].perm);
}
foreach (var id in delFriends) friends.subsections.RemoveAll(x => x.perm == id);
break;
}
}
bool UserHasNeededPerm(string playerId, string str) {
string[] perms = str.Split(new char[] {
',', ' '
}
, StringSplitOptions.RemoveEmptyEntries);
if (!perms.IsNullOrEmpty()) {
foreach (var item in perms) {
if (!permission.UserHasPermission(playerId, item)) return false;
}
}
return true;
}
string ToUnityRGBA(string hexColor) {
Color color;
if(ColorUtility.TryParseHtmlString(hexColor, out color)) return string.Format("{0:F2} {1:F2} {2:F2} {3:F2}", color.r, color.g, color.b, color.a);
else return hexColor;
}
#endregion
#region Interface
void OpenPanel(BasePlayer player, params string[] lvls) {
if (!activeUsers.ContainsKey(player.UserIDString)) return;
HashSet<MenuSectionClass> validSections = GetValidSections(player, lvls);
if (validSections.IsNullOrEmpty()) return;
bool isCommandOpen = activeUsers[player.UserIDString];
AdditionalFunctionality(player, ref validSections, lvls);
CuiElementContainer container = new CuiElementContainer();
string png, color1, color2;
float menuFadeIn = 0f, section1FadeIn = 0f, section2FadeIn = 0f, section3FadeIn = 0f;
Dictionary<string, float> section1Radians = new Dictionary<string, float>();
if (lvls.IsNullOrEmpty()) {
png = (string) ImageLibrary.Call("GetImage", "RadialMenu.LVL1");
menuFadeIn = config.menuFadeIn;
section1FadeIn = config.sectionFadeIn;
}
else if (lvls.Length == 1) {
if (validSections.First(x => x.perm == lvls[0]).subsections.IsNullOrEmpty()) png = (string) ImageLibrary.Call("GetImage", "RadialMenu.LVL1");
else {
png = (string) ImageLibrary.Call("GetImage", "RadialMenu.LVL2");
section2FadeIn = config.sectionFadeIn;
}
}
else {
png = (string) ImageLibrary.Call("GetImage", "RadialMenu.LVL3");
section3FadeIn = config.sectionFadeIn;
}
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5", OffsetMin = $"-{config.menuSize} -{config.menuSize}", OffsetMax = $"{config.menuSize} {config.menuSize}"
}
, Image = {
Color = "0 0 0 0"
}
, CursorEnabled = true
}
, "Hud", "RadialMenu.MainPanel", "RadialMenu.MainPanel");
if (isCommandOpen) {
container.Add(new CuiButton {
RectTransform = {
AnchorMin = "-1 -1", AnchorMax = "2 2"
}
, Button = {
Color = "0 0 0 0", Command = "dis
// ... [Code preview truncated due to length. Full source in attached file] ...