C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
// #define TESTING
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using ProtoBuf;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Oxide.Plugins
{
[Info("Friends", "Mevent", "1.22.2")]
[Description("Adds a friends system with a visual interface for quick interaction")]
public class Friends : RustPlugin
{
#region Fields
[PluginReference] private Plugin
ImageLibrary = null,
Notifications = null,
PlayerList = null,
PlayerDatabase = null,
Bank = null;
private const string Layer = "Com.Mevent.Main";
private static Friends _instance;
private readonly Dictionary<ulong, EntityEntry> _playerEntities = new Dictionary<ulong, EntityEntry>();
private class EntityEntry
{
public HashSet<AutoTurret> AutoTurrets = new HashSet<AutoTurret>();
public HashSet<BuildingPrivlidge> BuildingPrivileges = new HashSet<BuildingPrivlidge>();
public HashSet<CodeLock> CodeLocks = new HashSet<CodeLock>();
}
private enum AutoAuthType
{
All,
Turret,
Cupboard,
Doors,
Containers
}
private enum BtnType
{
None,
Doors,
Cupboard,
Turrets,
Containers,
FriendlyFire,
Sams
}
#endregion
#region Config
private static Configuration _config;
private class Configuration
{
[JsonProperty(PropertyName = "Добавлять в команду | Add to team")]
public bool AddTeam = true;
[JsonProperty(PropertyName = "Максимальное кол-во друзей | Max Friends")]
public int MaxFriendsAmount = 3;
[JsonProperty(PropertyName = "Задержка между сообщениями FF | Delay between FF messages")]
public float FFDelay = 1;
[JsonProperty(PropertyName =
"Закрывать интерфейс после нажатия на кнопку | Close the interface after clicking on the button")]
public bool AutoClose = true;
[JsonProperty(PropertyName = "Огонь по друзьям | Friendly Fire")]
public bool FriendlyFire = false;
[JsonProperty(PropertyName = "Двери | Doors")]
public bool Doors = true;
[JsonProperty(PropertyName = "Турели | Turrets")]
public bool Turrets = true;
[JsonProperty(PropertyName = "Шкаф | Cupboard")]
public bool Cupboard = true;
[JsonProperty(PropertyName = "Ящики | Containers")]
public bool Containers = true;
[JsonProperty(PropertyName = "ПВО | SAMs")]
public bool SAMs = true;
[JsonProperty(PropertyName = "Добавлять друга к остальным друзьям? | Add a friend to other friends?")]
public bool UseTeams = true;
[JsonProperty(PropertyName = "Включить логирование в консоль? | Enable logging to the console?")]
public bool LogToConsole = true;
[JsonProperty(PropertyName = "Включить логирование в файл? | Enable logging to the file?")]
public bool LogToFile = true;
[JsonProperty(PropertyName = "Фон | Background")]
public IPanel Background = new IPanel
{
AnchorMin = "0 0", AnchorMax = "1 1",
OffsetMin = "0 0", OffsetMax = "0 0",
Image = string.Empty,
Color = new IColor("#0D1F4E", 95),
isRaw = false,
Sprite = "Assets/Content/UI/UI.Background.Tile.psd",
Material = "Assets/Icons/IconMaterial.mat"
};
[JsonProperty(PropertyName = "Заглавие | Title")]
public IText Title = new IText
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-150 300", OffsetMax = "150 360",
Font = "robotocondensed-bold.ttf",
Align = TextAnchor.MiddleCenter,
FontSize = 38,
Color = new IColor("#FFFFFF", 100)
};
[JsonProperty(PropertyName = "Закрыть | Close")]
public IText Close = new IText
{
AnchorMin = "1 1", AnchorMax = "1 1",
OffsetMin = "-35 -35", OffsetMax = "-5 -5",
Font = "robotocondensed-bold.ttf",
Align = TextAnchor.MiddleCenter,
FontSize = 24,
Color = new IColor("#FFFFFF", 100)
};
[JsonProperty(PropertyName = "Ошибка | Error")]
public IText Error = new IText
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
OffsetMin = "-300 -150", OffsetMax = "300 150",
Font = "robotocondensed-bold.ttf",
Align = TextAnchor.MiddleCenter,
FontSize = 38,
Color = new IColor("#FFFFFF", 100)
};
[JsonProperty(PropertyName = "Назад | Back")]
public IText Back = new IText
{
AnchorMin = "0 0.5", AnchorMax = "0 0.5",
OffsetMin = "0 -40", OffsetMax = "65 40",
Font = "robotocondensed-bold.ttf",
Align = TextAnchor.MiddleCenter,
FontSize = 60,
Color = new IColor("#FFFFFF", 100)
};
[JsonProperty(PropertyName = "Вперёд | Next")]
public IText Next = new IText
{
AnchorMin = "1 0.5", AnchorMax = "1 0.5",
OffsetMin = "-65 -40", OffsetMax = "0 40",
Font = "robotocondensed-bold.ttf",
Align = TextAnchor.MiddleCenter,
FontSize = 60,
Color = new IColor("#FFFFFF", 100)
};
[JsonProperty(PropertyName = "Настройка интерфейса | Interface Settings")]
public IFriendPanel Panel = new IFriendPanel
{
AnchorMin = "0.5 0.5", AnchorMax = "0.5 0.5",
Height = 100,
Width = 820,
Margin = 20,
Count = 4,
Color = new IColor("#1D3676", 100),
Avatar = new InterfacePosition
{
AnchorMin = "0 0", AnchorMax = "0 0",
OffsetMin = "5 5", OffsetMax = "95 95"
},
Nickname = new NickName
{
AnchorMin = "0 0", AnchorMax = "0 0",
OffsetMin = "100 40", OffsetMax = "500 100",
Align = TextAnchor.LowerLeft,
Font = "robotocondensed-regular.ttf",
Color = new IColor("#FFFFFF", 100),
FontSize = 38,
MaxLength = 30
},
Status = new SText
{
AnchorMin = "0 0", AnchorMax = "0 0",
OffsetMin = "100 0", OffsetMax = "200 50",
Align = TextAnchor.UpperLeft,
Font = "robotocondensed-regular.ttf",
Color = new IColor("#B5FFC9", 100),
OfflineColor = new IColor("#B46292", 100),
FontSize = 16
},
Button = new IButton
{
AnchorMin = "1 0.5", AnchorMax = "1 0.5",
Height = 35,
Width = 120,
Margin = 5
},
Buttons = new List<FButton>
{
new FButton
{
Type = BtnType.Doors,
Text = "Двери",
Command = "friend doors {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.Cupboard,
Text = "Шкаф",
Command = "friend cupboard {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.Containers,
Text = "Ящики",
Command = "friend containers {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.Turrets,
Text = "Турели",
Command = "friend turrets {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.None,
Text = "Исключить",
Command = "friend remove {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.FriendlyFire,
Text = "Урон",
Command = "ff {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.None,
Text = "ТП",
Command = "tpr {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
},
new FButton
{
Type = BtnType.Sams,
Text = "ПВО",
Command = "friend sams {user}",
ActiveColor = new IColor("#5D8FDF", 95),
DisactiveColor = new IColor("#5D8FDF", 35),
FontSize = 20,
Font = "robotocondensed-regular.ttf",
Align = TextAnchor.MiddleCenter,
TColor = new IColor("#1D3676", 100)
}
}
};
[JsonProperty(PropertyName = "Приглашение в друзья | Friend Invite")]
public INotify FriendInvite = new INotify
{
Image = "friend",
Url = "https://i.imgur.com/qAxHQIn.png",
Delay = 30,
Buttons = new List<INotifyButton>
{
new INotifyButton
{
AnchorMin = "0.5 0", AnchorMax = "0.5 0",
OffsetMin = "-137.5 5", OffsetMax = "-2.5 25",
Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf",
FontSize = 14,
Color = new IColor("#FFFFFF", 95),
BColor = new IColor("#528A4E", 95),
Command = "friend accept",
Msg = "Accept"
},
new INotifyButton
{
AnchorMin = "0.5 0", AnchorMax = "0.5 0",
OffsetMin = "2.5 5", OffsetMax = "137.5 25",
Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf",
FontSize = 14,
Color = new IColor("#FFFFFF", 95),
BColor = new IColor("#50488A", 95),
Command = "friend cancel",
Msg = "Cancel"
}
}
};
[JsonProperty(PropertyName = "Оповещение | Notification")]
public INotify Notify = new INotify
{
Image = "friend",
Url = "https://i.imgur.com/qAxHQIn.png",
Delay = 5,
Buttons = new List<INotifyButton>()
};
[JsonProperty(PropertyName = "Найти друга | Find a Friend")]
public INotifyButton FindFriendBtn = new INotifyButton
{
AnchorMin = "0.5 0", AnchorMax = "0.5 0",
OffsetMin = "-150 25", OffsetMax = "150 60",
Align = TextAnchor.MiddleCenter,
FontSize = 14,
Font = "robotocondensed-regular.ttf",
Color = new IColor("#5D8FDF", 100),
BColor = new IColor("#1D3676", 100),
Command = "playerslist",
Msg = "FindFriend"
};
[JsonProperty(PropertyName = "PlayerDatabase")]
public PlayerDatabaseConf PlayerDatabase =
new PlayerDatabaseConf(false, "Friends");
public VersionNumber Version;
}
private class PlayerDatabaseConf
{
[JsonProperty(PropertyName = "Включено")]
public bool Enabled;
[JsonProperty(PropertyName = "Поле")] public string Field;
public PlayerDatabaseConf(bool enabled, string field)
{
Enabled = enabled;
Field = field;
}
}
private class NickName : IText
{
[JsonProperty(PropertyName = "Максимальная длина | Max Lenght")]
public int MaxLength;
public new void Get(ref CuiElementContainer container, string parent = "Hud", string name = null,
string text = "")
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
text = text.Substring(0, Mathf.Min(text.Length, MaxLength));
container.Add(new CuiLabel
{
RectTransform =
{AnchorMin = AnchorMin, AnchorMax = AnchorMax, OffsetMin = OffsetMin, OffsetMax = OffsetMax},
Text =
{
Text = $"{text}", Align = Align, FontSize = FontSize, Color = Color.Get(),
Font = Font
}
}, parent, name);
}
}
private class INotify
{
[JsonProperty(PropertyName = "Ключ изображения | Image Key")]
public string Image;
[JsonProperty(PropertyName = "Ссылка на изображение | Image Url")]
public string Url;
[JsonProperty(PropertyName = "Время показа | Show Time")]
public float Delay;
[JsonProperty(PropertyName = "Кнопки | Buttons", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<INotifyButton> Buttons;
}
private class INotifyButton : IText
{
[JsonProperty(PropertyName = "Цвет | Color")]
public IColor BColor;
[JsonProperty(PropertyName = "Ключ языкового файла | Lang Key")]
public string Msg;
[JsonProperty(PropertyName = "Команда | Command")]
public string Command;
public void Get(ref CuiElementContainer container, BasePlayer player, string name = null,
string parent = "")
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
container.Add(new CuiButton
{
RectTransform =
{AnchorMin = AnchorMin, AnchorMax = AnchorMax, OffsetMin = OffsetMin, OffsetMax = OffsetMax},
Text =
{
Text = _instance.Msg(Msg, player.UserIDString), Align = Align, FontSize = FontSize,
Color = Color.Get(),
Font = Font
},
Button =
{
Color = BColor.Get(),
Command = Command
}
}, parent, name);
}
}
private abstract class IAnchors
{
public string AnchorMin;
public string AnchorMax;
}
private class InterfacePosition : IAnchors
{
public string OffsetMin;
public string OffsetMax;
}
private class IFriendPanel : IAnchors
{
[JsonProperty(PropertyName = "Высота | Height")]
public float Height;
[JsonProperty(PropertyName = "Ширина | Width")]
public float Width;
[JsonProperty(PropertyName = "Отступ | Margin")]
public float Margin;
[JsonProperty(PropertyName = "Количество на странице | Count On Page")]
public int Count;
[JsonProperty(PropertyName = "Цвет | Color")]
public IColor Color;
[JsonProperty(PropertyName = "Аватарка | Avatar")]
public InterfacePosition Avatar;
[JsonProperty(PropertyName = "Никнейм | Nickname")]
public NickName Nickname;
[JsonProperty(PropertyName = "Статус | Status")]
public SText Status;
[JsonProperty(PropertyName = "Кнопка | Button")]
public IButton Button;
[JsonProperty(PropertyName = "Кнопки | Buttons", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<FButton> Buttons;
public void Get(ref CuiElementContainer container, BasePlayer player, FriendData data, string parent,
string name,
string oMin, string oMax,
int page,
string mainParent)
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
container.Add(new CuiPanel
{
RectTransform =
{
AnchorMin = AnchorMin, AnchorMax = AnchorMax,
OffsetMin = oMin, OffsetMax = oMax
},
Image = {Color = Color.Get()}
}, parent, name);
if (_instance.ImageLibrary && Avatar != null)
container.Add(new CuiElement
{
Parent = name,
Components =
{
new CuiRawImageComponent
{Png = _instance.ImageLibrary.Call<string>("GetImage", $"avatar_{data.UserId}")},
new CuiRectTransformComponent
{
AnchorMin = Avatar.AnchorMin, AnchorMax = Avatar.AnchorMax,
OffsetMin = Avatar.OffsetMin, OffsetMax = Avatar.OffsetMax
}
}
});
#region Name
var targetId = $"{data.UserId}";
var target = _instance.covalence.Players.FindPlayer(targetId);
var displayName = _instance.GetPlayerName(data.UserId);
var status = target != null && target.IsConnected;
Nickname?.Get(ref container, name, name + ".Nickname", displayName);
Status?.Get(ref container, status, name, name + ".Status",
_instance.Msg(status ? Online : Offline, player.UserIDString));
#endregion
#region Buttons
if (Buttons != null)
{
var xSwitch = -Button.Margin;
for (var i = 0; i < Buttons.Count; i++)
{
var button = Buttons[i];
var up = i < Buttons.Count / 2;
var ySwitch = up ? Button.Height + Button.Margin / 2f : -(Button.Margin / 2f);
button.Get(ref container, data, name, name + $".Btn.{i}", Button.AnchorMin, Button.AnchorMax,
$"{xSwitch - Button.Width} {ySwitch - Button.Height}",
$"{xSwitch} {ySwitch}",
page,
mainParent);
if (i + 1 == Buttons.Count / 2)
xSwitch = -Button.Margin;
else
xSwitch = xSwitch - Button.Margin - Button.Width;
}
}
#endregion
}
}
private class IPanel : InterfacePosition
{
[JsonProperty(PropertyName = "Изображение | Image")]
public string Image;
[JsonProperty(PropertyName = "Цвет | Color")]
public IColor Color;
[JsonProperty(PropertyName = "Сохранять цвет изображения? | Save Image Color")]
public bool isRaw;
[JsonProperty(PropertyName = "Sprite")]
public string Sprite;
[JsonProperty(PropertyName = "Material")]
public string Material;
public void Get(ref CuiElementContainer container, string parent = "Hud", string name = null,
bool cursor = false)
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
if (isRaw)
{
var element = new CuiElement
{
Name = name,
Parent = parent,
Components =
{
new CuiRawImageComponent
{
Png = !string.IsNullOrEmpty(Image)
? _instance.ImageLibrary.Call<string>("GetImage", Image)
: null,
Color = Color.Get(),
Material = Material,
Sprite = !string.IsNullOrEmpty(Sprite) ? Sprite : "Assets/Icons/rust.png"
},
new CuiRectTransformComponent
{
AnchorMin = AnchorMin, AnchorMax = AnchorMax, OffsetMin = OffsetMin,
OffsetMax = OffsetMax
}
}
};
if (cursor) element.Components.Add(new CuiNeedsCursorComponent());
container.Add(element);
}
else
{
container.Add(new CuiPanel
{
RectTransform =
{
AnchorMin = AnchorMin, AnchorMax = AnchorMax, OffsetMin = OffsetMin, OffsetMax = OffsetMax
},
Image =
{
Png = !string.IsNullOrEmpty(Image)
? _instance.ImageLibrary.Call<string>("GetImage", Image)
: null,
Color = Color.Get(),
Sprite =
!string.IsNullOrEmpty(Sprite) ? Sprite : "Assets/Content/UI/UI.Background.Tile.psd",
Material = !string.IsNullOrEmpty(Material) ? Material : "Assets/Icons/IconMaterial.mat"
},
CursorEnabled = cursor
}, parent, name);
}
}
}
private class FButton
{
[JsonProperty(PropertyName = "Тип | Type")] [JsonConverter(typeof(StringEnumConverter))]
public BtnType Type;
[JsonProperty(PropertyName = "Текст | Text")]
public string Text;
[JsonProperty(PropertyName = "Команда | Command")]
public string Command;
[JsonProperty(PropertyName = "Активный Цвет | Active Color")]
public IColor ActiveColor;
[JsonProperty(PropertyName = "Неактивный Цвет | Disactive Color")]
public IColor DisactiveColor;
[JsonProperty(PropertyName = "Font Size")]
public int FontSize;
[JsonProperty(PropertyName = "Font")] public string Font;
[JsonProperty(PropertyName = "Align")] [JsonConverter(typeof(StringEnumConverter))]
public TextAnchor Align;
[JsonProperty(PropertyName = "Text Color")]
public IColor TColor;
public void Get(ref CuiElementContainer container, FriendData data, string parent, string name, string aMin,
string aMax, string oMin, string oMax, int page,
string mainParent)
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
var color = ActiveColor;
switch (Type)
{
case BtnType.Doors:
color = data.Doors ? ActiveColor : DisactiveColor;
break;
case BtnType.Cupboard:
color = data.Cupboard ? ActiveColor : DisactiveColor;
break;
case BtnType.Turrets:
color = data.Turrets ? ActiveColor : DisactiveColor;
break;
case BtnType.FriendlyFire:
color = data.FriendlyFire ? ActiveColor : DisactiveColor;
break;
case BtnType.Containers:
color = data.Containers ? ActiveColor : DisactiveColor;
break;
case BtnType.Sams:
color = data.SAMs ? ActiveColor : DisactiveColor;
break;
}
var text = Text.Replace("{user}", data.UserId.ToString());
var command = Command.Replace("{user}", data.UserId.ToString());
container.Add(new CuiButton
{
RectTransform =
{
AnchorMin = aMin, AnchorMax = aMax,
OffsetMin = oMin, OffsetMax = oMax
},
Text =
{
Text = $"{text}",
Align = Align,
Font = Font,
FontSize = FontSize,
Color = TColor.Get()
},
Button =
{
Command =
$"UI_Friends sendcmd {page} {mainParent} {command}", // command.Contains("chat.say") ? $"friendssendcmd {command}" : $"{command}",
Color = color.Get(),
Close = _config.AutoClose ? Layer : string.Empty
}
}, parent, name);
}
}
private class IButton : IAnchors
{
[JsonProperty(PropertyName = "Высота | Height")]
public float Height;
[JsonProperty(PropertyName = "Ширина | Width")]
public float Width;
[JsonProperty(PropertyName = "Отступ | Margin")]
public float Margin;
}
private class SText : InterfacePosition
{
[JsonProperty(PropertyName = "Font Size")]
public int FontSize;
[JsonProperty(PropertyName = "Font")] public string Font;
[JsonProperty(PropertyName = "Align")] [JsonConverter(typeof(StringEnumConverter))]
public TextAnchor Align;
[JsonProperty(PropertyName = "Цвет игрока онлайн | Online Color")]
public IColor Color;
[JsonProperty(PropertyName = "Цвет игрока оффлайн | Offline Color")]
public IColor OfflineColor;
public void Get(ref CuiElementContainer container, bool online, string parent = "Hud", string name = null,
string text = "")
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
container.Add(new CuiLabel
{
RectTransform =
{AnchorMin = AnchorMin, AnchorMax = AnchorMax, OffsetMin = OffsetMin, OffsetMax = OffsetMax},
Text =
{
Text = $"{text}", Align = Align, FontSize = FontSize,
Color = online ? Color.Get() : OfflineColor.Get(),
Font = Font
}
}, parent, name);
}
}
private class IText : InterfacePosition
{
[JsonProperty(PropertyName = "Font Size")]
public int FontSize;
[JsonProperty(PropertyName = "Font")] public string Font;
[JsonProperty(PropertyName = "Align")] [JsonConverter(typeof(StringEnumConverter))]
public TextAnchor Align;
[JsonProperty(PropertyName = "Text Color")]
public IColor Color;
public void Get(ref CuiElementContainer container, string parent = "Hud", string name = null,
string text = "")
{
if (string.IsNullOrEmpty(name))
name = CuiHelper.GetGuid();
container.Add(new CuiLabel
{
RectTransform =
{AnchorMin = AnchorMin, AnchorMax = AnchorMax, OffsetMin = OffsetMin, OffsetMax = OffsetMax},
Text =
{
Text = $"{text}", Align = Align, FontSize = FontSize, Color = Color.Get(),
Font = Font
}
}, parent, name);
}
}
private class IColor
{
[JsonProperty(PropertyName = "HEX")] public string HEX;
[JsonProperty(PropertyName = "Непрозрачность | Opacity (0 - 100)")]
public float Alpha;
public string Get()
{
if (string.IsNullOrEmpty(HEX)) HEX = "#FFFFFF";
var str = HEX.Trim('#');
if (str.Length != 6) throw new Exception(HEX);
var r = byte.Parse(str.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(str.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(str.Substring(4, 2), NumberStyles.HexNumber);
return $"{(double) r / 255} {(double) g / 255} {(double) b / 255} {Alpha / 100}";
}
public IColor(string hex, float alpha)
{
HEX = hex;
Alpha = alpha;
}
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null) throw new Exception();
if (_config.Version < Version)
UpdateConfigValues();
SaveConfig();
}
catch
{
PrintError("Your configuration file contains an error.Using default configuration values.");
LoadDefaultConfig();
}
}
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
var baseConfig = new Configuration();
if (_config.Version < new VersionNumber(1, 14, 0))
_config.AutoClose = baseConfig.AutoClose;
if (_config.Version < new VersionNumber(1, 15, 0))
{
_config.UseTeams = baseConfig.UseTeams;
_config.SAMs = baseConfig.SAMs;
}
_config.Version = Version;
PrintWarning("Config update completed!");
}
protected override void SaveConfig()
{
Config.WriteObject(_config);
}
protected override void LoadDefaultConfig()
{
_config = new Configuration();
}
#endregion
#region Data
private Dictionary<string, PlayerData> _usersData = new Dictionary<string, PlayerData>();
private ListHashSet<ulong> _changedPlayers = new ListHashSet<ulong>();
#region PlayerDatabase
private PlayerData LoadPlayerDatabaseData(string userId)
{
PlayerData data;
if (_usersData.TryGetValue(userId, out data))
return data;
var success =
PlayerDatabase?.Call<string>("GetPlayerDataRaw", userId, _config.PlayerDatabase.Field);
if (string.IsNullOrEmpty(success))
{
data = new PlayerData
{
UserID = Convert.ToUInt64(userId)
};
SavePlayerDatabaseData(userId, data);
return _usersData[userId] = data;
}
if ((data = JsonConvert.DeserializeObject<PlayerData>(success)) == null)
{
data = new PlayerData
{
UserID = Convert.ToUInt64(userId)
};
SavePlayerDatabaseData(userId, data);
return _usersData[userId] = data;
}
return _usersData[userId] = data;
}
private void SavePlayerDatabaseData(string userId, PlayerData data)
{
if (data == null) return;
var serializeObject = JsonConvert.SerializeObject(data);
if (serializeObject == null) return;
PlayerDatabase?.Call("SetPlayerData", userId, _config.PlayerDatabase.Field, serializeObject);
}
#endregion
#region Classes
private class PlayerData
{
#region Fields
[JsonProperty(PropertyName = "Display Name")]
public string DisplayName;
[JsonProperty(PropertyName = "UserId")]
public ulong UserID;
[JsonProperty(PropertyName = "Friends", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public List<FriendData> Friends = new List<FriendData>();
[JsonProperty(PropertyName = "Removed Friends", ObjectCreationHandling = ObjectCreationHandling.Replace)]
public HashSet<ulong> RemovedFriends = new HashSet<ulong>();
#endregion
#region Utils
public bool IsFriend(ulong friend)
{
return Friends.Exists(check => check.UserId == friend);
}
public FriendData GetFriend(ulong friend)
{
return Friends.Find(check => check.UserId == friend);
}
public void AddFriend(ulong friend)
{
if (!friend.IsSteamId() || IsFriend(friend)) return;
Friends.Add(new FriendData
{
UserId = friend,
FriendlyFire = _config.FriendlyFire,
Doors = _config.Doors,
Turrets = _config.Turrets,
Cupboard = _config.Cupboard,
Containers = _config.Containers,
SAMs = _config.SAMs
});
RemovedFriends.Remove(friend);
OnChanged();
}
public void RemoveFriend(ulong friend)
{
if (Friends.RemoveAll(x => x.UserId == friend) > 0)
RemovedFriends.Add(friend);
OnChanged();
}
#endregion
#region Helpers
private static string BaseFolder()
{
return "Friends" + Path.DirectorySeparatorChar + "Players" + Path.DirectorySeparatorChar;
}
public static string[] GetFiles()
{
return GetFiles(BaseFolder());
}
public static string[] GetFiles(string baseFolder)
{
try
{
var json = ".json".Length;
var paths = Interface.Oxide.DataFileSystem.GetFiles(baseFolder);
for (var i = 0; i < paths.Length; i++)
{
var path = paths[i];
var separatorIndex = path.LastIndexOf(Path.DirectorySeparatorChar);
// We have to do this since GetFiles returns paths instead of filenames
// And other methods require filenames
paths[i] = path.Substring(separatorIndex + 1, path.Length - separatorIndex - 1 - json);
}
return paths;
}
catch
{
return Array.Empty<string>();
}
}
public static PlayerData GetOrLoad(string userId)
{
if (!userId.IsSteamId()) return null;
if (_config.PlayerDatabase.Enabled) return _instance.LoadPlayerDatabaseData(userId);
return GetOrLoad(BaseFolder(), userId);
}
public static PlayerData GetNotLoad(string userId)
{
if (!userId.IsSteamId()) return null;
if (_config.PlayerDatabase.Enabled) return _instance.LoadPlayerDatabaseData(userId);
var data = GetOrLoad(BaseFolder(), userId, false);
return data;
}
public static PlayerData GetOrLoad(string baseFolder, string userId, bool load = true)
{
PlayerData data;
if (_instance._usersData.TryGetValue(userId, out data)) return data;
try
{
data = ReadOnlyObject(baseFolder + userId);
}
catch (Exception e)
{
Interface.Oxide.LogError(e.ToString());
}
return load
? _instance._usersData[userId] = data
: data;
}
public static PlayerData GetOrCreate(string userId)
{
if (!userId.IsSteamId()) return null;
if (_config.PlayerDatabase.Enabled) return _instance.LoadPlayerDatabaseData(userId);
return GetOrLoad(userId) ?? (_instance._usersData[userId] = new PlayerData
{
UserID = Convert.ToUInt64(userId)
});
}
public static bool IsLoaded(string userId)
{
return _instance._usersData.ContainsKey(userId);
}
public static void Save()
{
#if TESTING
Debug.Log($"[Save] count={_instance?._usersData.Count}");
#endif
_instance?._usersData?.Keys.ToList().ForEach(Save);
}
public static void Save(string userId)
{
if (_config.PlayerDatabase.Enabled)
{
_instance.SavePlayerDatabaseData(userId, _instance.LoadPlayerDatabaseData(userId));
return;
}
PlayerData data;
if (!_instance._usersData.TryGetValue(userId, out data))
return;
Interface.Oxide.DataFileSystem.WriteObject(BaseFolder() + userId, data);
_instance?._changedPlayers?.Remove(data.UserID);
}
public static void SaveAndUnload(string userId)
{
Save(userId);
Unload(userId);
}
public static void Unload(string userId)
{
_instance?._usersData?.Remove(userId);
_instance?._changedPlayers?.Remove(Convert.ToUInt64(userId));
}
private static PlayerData ReadOnlyObject(string name)
{
return Interface.Oxide.DataFileSystem.ExistsDatafile(name)
? Interface.Oxide.DataFileSystem.GetFile(name).ReadObject<PlayerData>()
: null;
}
public void OnChanged()
{
_instance?._changedPlayers?.TryAdd(UserID);
}
#endregion
}
private class FriendData
{
[JsonProperty(PropertyName = "UserId")]
public ulong UserId;
[JsonProperty(PropertyName = "FriendlyFire")]
public bool FriendlyFire;
[JsonProperty(PropertyName = "Doors")] public bool Doors;
[JsonProperty(PropertyName = "Turrets")]
public bool Turrets;
[JsonProperty(PropertyName = "Cupboard")]
public bool Cupboard;
[JsonProperty(PropertyName = "Containers")]
public bool Containers;
[JsonProperty(PropertyName = "SAMs")] public bool SAMs;
}
#region Save Coroutine
private Coroutine _saveCoroutine;
private void StartSaveCoroutine()
{
_saveCoroutine = Rust.Global.Runner.StartCoroutine(HandleChangedPlayers());
}
private void StopSaveCoroutine()
{
if (_saveCoroutine != null)
Rust.Global.Runner.StopCoroutine(_saveCoroutine);
}
private IEnumerator HandleChangedPlayers()
{
var count = 0;
while (_changedPlayers.Count > 0)
{
var player = _changedPlayers[0];
_changedPlayers.RemoveAt(0);
PlayerData.Save(player.ToString());
if (++count % 5 == 0)
yield return CoroutineEx.waitForFixedUpdate;
}
}
#endregion
#endregion
#region Utils
private static PlayerData FindPlayerData(string user)
{
foreach (var player in BasePlayer.allPlayerList)
if (player.displayName.StartsWith(user, StringComparison.CurrentCultureIgnoreCase))
return PlayerData.GetOrLoad(player.UserIDString);
return null;
}
#region Migration
private Coroutine _playersMigration;
private IEnumerator StartOnAllPlayers(string[] players,
Action<string> callback = null,
Action onFinish = null)
{
for (var i = 0; i < players.Length; i++)
{
callback?.Invoke(players[i]);
if (i % 10 == 0)
yield return CoroutineEx.waitForFixedUpdate;
}
onFinish?.Invoke();
_playersMigration = null;
}
#endregion
#endregion
#endregion
#region Hooks
private void Init()
{
_instance = this;
AddCovalenceCommand(new[] {"ff", "friendlyfire", "friend", "friends", "f", "fmenu", "f.menu"},
nameof(CmdFriends));
if (!_config.AddTeam)
{
Unsubscribe(nameof(OnTeamLeave));
Unsubscribe(nameof(OnTeamAcceptInvite));
Unsubscribe(nameof(OnTeamKick));
Unsubscribe(nameof(OnTeamInvite));
Unsubscribe(nameof(OnTeamRejectInvite));
}
}
private void OnServerInitialized()
{
if (!ImageLibrary) PrintWarning("IMAGE LIBRARY IS NOT INSTALLED.");
#region Init
foreach (var player in BasePlayer.activePlayerList)
OnPlayerConnected(player);
#endregion
#region Notifications
Notifications?.Call("AddImage", _config.FriendInvite.Image, _config.FriendInvite.Url);
Notifications?.Call("AddImage", _config.Notify.Image, _config.Notify.Url);
#endregion
#region Team
RelationshipManager.maxTeamSize = _config.MaxFriendsAmount + 1;
#endregion
#region Auth
foreach (var entity in BaseNetworkable.serverEntities.OfType<BaseEntity>()) CheckEntity(entity);
#endregion
timer.Every(1, TimeHandle);
}
private void OnServerSave()
{
timer.In(Random.Range(2f, 7f), StartSaveCoroutine);
}
private void Unload()
{
if (_playersMigration != null)
Rust.Global.Runner.StopCoroutine(_playersMigration);
if (_friendsConvertation != null)
Rust.Global.Runner.StopCoroutine(_friendsConvertation);
StopSaveCoroutine();
foreach (var player in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(player, Layer);
PlayerData.SaveAndUnload(player.UserIDString);
}
_instance = null;
_config = null;
}
private void OnPlayerConnected(BasePlayer player)
{
if (player == null || !player.userID.IsSteamId()) return;
var data = PlayerData.GetOrCreate(player.UserIDString);
if (data == null) return;
var oldName = data.DisplayName;
var newName = player.displayName;
if (string.IsNullOrEmpty(oldName) || !oldName.Equals(newName))
{
data.DisplayName = newName;
data.OnChanged();
}
GetAvatar(player.userID,
avatar => ImageLibrary?.Call("AddImage", avatar, $"avatar_{player.UserIDString}"));
if (_config.AddTeam && data.Friends.Count > 0)
{
RelationshipManager.PlayerTeam team;
var friend =
data.Friends.Find(x => RelationshipManager.ServerInstance.playerToTeam.ContainsKey(x.UserId));
if (friend == null)
{
team = RelationshipManager.ServerInstance.CreateTeam();
team.AddPlayer(player);
team.SetTeamLeader(player.userID);
var friendPlayer = FindPlayer(data.Friends);
if (friendPlayer != null) team.AddPlayer(friendPlayer);
}
else
{
team = RelationshipManager.ServerInstance.playerToTeam[friend.UserId];
if (team == null) return;
team.AddPlayer(player);
}
}
}
private void OnPlayerDisconnected(BasePlayer player)
{
if (player == null) return;
PlayerData.SaveAndUnload(player.UserIDString);
}
private void OnEntityTakeDamage(BasePlayer player, HitInfo info)
{
if (player == null || info == null) return;
var initiator = info.InitiatorPlayer;
if (initiator == null) return;
if (!IsFriend(player.userID, initiator.userID) || player.userID == initiator.userID)
return;
var data = PlayerData.GetOrLoad(initiator.UserIDString);
var friend = data?.Friends.Find(x => x.UserId == player.userID);
if (friend == null || friend.FriendlyFire) return;
info.damageTypes.ScaleAll(0);
if (IsCd(initiator.userID)) return;
Notify(initiator, _config.Notify.Delay, Msg(NotifyTitle, initiator.UserIDString),
Msg(FF, initiator.UserIDString, player.displayName), _config.Notify.Image);
SetCd(initiator.userID);
}
#region Team
private void OnTeamLeave(RelationshipManager.PlayerTeam playerTeam, BasePlayer player)
{
if (playerTeam == null || player == null) return;
playerTeam.members.ToList().ForEach(user =>
{
PlayerData.GetOrLoad(player.UserIDString)?.RemoveFriend(user);
PlayerData.GetOrLoad(user.ToString())?.RemoveFriend(player.userID);
});
UpdateAuthList(player.userID, AutoAuthType.All);
UpdateTeamAuthList(playerTeam.members);
}
private void OnTeamAcceptInvite(RelationshipManager.PlayerTeam playerTeam, BasePlayer player)
{
NextTick(() =>
{
if (playerTeam == null || player == null || !player.userID.IsSteamId()) return;
var invite = _invites.Find(x => x.Target == player);
if (invite != null && playerTeam.members.Exists(x => invite.Inviter.userID == x))
{
AcceptInvite(player);
return;
}
if (playerTeam.members.Contains(player.userID))
if (_config.UseTeams)
{
playerTeam.members.ToList().ForEach(user =>
{
if (user == player.userID) return;
PlayerData.GetOrLoad(player.UserIDString)?.AddFriend(user);
PlayerData.GetOrLoad(user.ToString())?.AddFriend(player.userID);
Log("friends", $"Player '{player}' added '{user}' as a friend");
});
UpdateTeamAuthList(playerTeam.members);
}
});
}
private void OnTeamKick(RelationshipManager.PlayerTeam playerTeam, BasePlayer player, ulong target)
{
NextTick(() =>
{
if (playerTeam == null) return;
if (!playerTeam.members.Contains(target))
{
playerTeam.members.ToList().ForEach(user =>
{
PlayerData.GetOrLoad(user.ToString())?.RemoveFriend(target);
PlayerData.GetOrLoad(target.ToString())?.RemoveFriend(user);
});
UpdateAuthList(target, AutoAuthType.All);
UpdateTeamAuthList(playerTeam.members);
}
});
}
private void OnTeamInvite(BasePlayer inviter, BasePlayer target)
{
SendInvite(inviter, target);
}
private void OnTeamRejectInvite(BasePlayer rejector, RelationshipManager.PlayerTeam team)
{
CancelInvite(rejector);
}
private void UpdateTeamAuthList(List<ulong> teamMembers)
{
if (teamMembers.Count <= 0) return;
teamMembers.ForEach(member => UpdateAuthList(member, AutoAuthType.All));
}
#endregion
#region SAMs
private object OnSamSiteTarget(SamSite samSite, BaseVehicle vehicle)
{
if (samSite == null || !samSite.OwnerID.IsSteamId() || vehicle == null)
return null;
var data = PlayerData.GetOrLoad(samSite.OwnerID.ToString());
if (data == null)
return null;
if (vehicle.OwnerID == samSite.OwnerID ||
data.Friends.Exists(friend => friend.UserId == vehicle.OwnerID && friend.SAMs))
return true;
foreach (var mounted in vehicle.allMountPoints.Where(allMountPoint => allMountPoint != null && allMountPoint.mountable != null).Select(allMountPoint => allMountPoint.mountable.GetMounted()).Where(mounted => mounted != null))
{
if (mounted.userID == samSite.OwnerID)
return true;
var friendData = data.GetFriend(mounted.userID);
if (friendData != null && friendData.SAMs)
return true;
}
return null;
}
#endregion
#endregion
#region Commands
private void CmdFriends(IPlayer cov, string command, string[] args)
{
var player = cov?.Object as BasePlayer;
if (player == null) return;
switch (command)
{
case "friendlyfire":
case "ff":
{
var data = PlayerData.GetOrLoad(player.ToString());
if (data == null) return;
if (args.Length < 1)
{
Reply(player, FFErrorSyntax, command);
return;
}
var friends = FindRemoveFriend(player, args[0]);
if (friends == null || friends.Count == 0)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NotFound, player.UserIDString, args[0]), _config.Notify.Image);
return;
}
PlayerData friend;
if (friends.Count > 1)
{
int index;
if (args.Length >= 3 && int.TryParse(args[2], out index))
{
if (index - 1 < 0 || friends.Count <= index - 1)
return;
friend = friends[index - 1];
}
else
{
var f = 0;
var str = string.Join(", ", friends.Select(x =>
{
f++;
return $"{x.DisplayName} ({x.UserID}) [{f}]";
}));
Reply(player, MultipleFound, str, f, args[0], args[1]);
return;
}
}
else
{
friend = friends.FirstOrDefault();
}
if (friend == null)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NotFound, player.UserIDString, args[0]), _config.Notify.Image);
return;
}
var fData = data.GetFriend(friend.UserID);
if (fData == null)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(IsNotFriend, player.UserIDString, friend.DisplayName), _config.Notify.Image);
return;
}
if (args.Length > 2)
switch (args[1].ToLower())
{
case "on":
{
fData.FriendlyFire = true;
break;
}
case "off":
{
fData.FriendlyFire = false;
break;
}
}
else
fData.FriendlyFire = !fData.FriendlyFire;
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(fData.FriendlyFire ? FFOn : FFOff, player.UserIDString), _config.Notify.Image);
break;
}
default:
{
if (args.Length == 0)
{
MainUi(player);
return;
}
switch (args[0].ToLower())
{
case "+":
case "i":
case "inv":
case "invite":
case "add":
{
if (args.Length < 2)
{
Reply(player, ErrorSyntax, command, args[0]);
return;
}
var friends = FindPlayer(args[1]);
if (friends.Count == 0)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NotFound, player.UserIDString, args[1]), _config.Notify.Image);
return;
}
BasePlayer friend;
if (friends.Count > 1)
{
int index;
if (args.Length >= 3 && int.TryParse(args[2], out index))
{
if (index - 1 < 0 || friends.Count <= index - 1)
return;
friend = friends[index - 1];
}
else
{
var f = 0;
var str = string.Join(", ", friends.Select(x =>
{
f++;
return $"{x.displayName} ({x.UserIDString}) [{f}]";
}));
Reply(player, MultipleFound, str, f, args[0], args[1]);
return;
}
}
else
{
friend = friends.FirstOrDefault();
}
if (friend == null)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NotFound, player.UserIDString, args[1]), _config.Notify.Image);
return;
}
if (player.userID == friend.userID)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(CantAddSelf, player.UserIDString), _config.Notify.Image);
return;
}
var pData = PlayerData.GetOrLoad(player.UserIDString);
if (pData == null) return;
if (pData.IsFriend(friend.userID))
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(AlreadyFriends, player.UserIDString), _config.Notify.Image);
return;
}
if (pData.Friends.Count >= _config.MaxFriendsAmount)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(MaxFriends, player.UserIDString), _config.Notify.Image);
return;
}
var fData = PlayerData.GetOrLoad(friend.UserIDString);
if (fData == null) return;
if (fData.Friends.Count >= _config.MaxFriendsAmount)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(TargetMaxFriends, player.UserIDString), _config.Notify.Image);
return;
}
SendInvite(player, friend);
break;
}
case "-":
case "del":
case "delete":
case "rem":
case "remove":
{
if (args.Length < 2)
{
Reply(player, ErrorSyntax, command, args[0]);
return;
}
var friends = FindRemoveFriend(player, args[1]);
if (friends == null || friends.Count == 0)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NotFound, player.UserIDString, args[1]), _config.Notify.Image);
return;
}
PlayerData friend;
if (friends.Count > 1)
{
int index;
if (args.Length >= 3 && int.TryParse(args[2], out index))
{
if (index - 1 < 0 || friends.Count <= index - 1)
return;
friend = friends[index - 1];
}
else
{
var f = 0;
var str = string.Join(", ", friends.Select(x =>
{
f++;
return $"{x.DisplayName} ({x.UserID}) [{f}]";
}));
Reply(player, MultipleFound, str, f, args[0], args[1]);
return;
}
}
else
{
friend = friends.FirstOrDefault();
}
if (friend == null)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NotFound, player.UserIDString, args[1]), _config.Notify.Image);
return;
}
CuiHelper.DestroyUi(player, Layer);
RemoveFriend(player, friend.UserID);
break;
}
case "a":
case "accept":
{
AcceptInvite(player);
break;
}
case "c":
case "cancel":
{
CancelInvite(player);
break;
}
case "list":
{
var data = PlayerData.GetOrLoad(player.UserIDString);
if (data == null) return;
if (data.Friends.Count == 0)
{
Notify(player, _config.Notify.Delay, Msg(NotifyTitle, player.UserIDString),
Msg(NoFriends, player.UserIDString), _config.Notify.Image);
}
else
{
var friends = string.Join(", ",
data.Friends.Select(x =>
$"{PlayerData.GetOrLoad(x.UserId.ToString())?.DisplayName ?? "NONE"} ({x.UserId})"));
Reply(player, Msg(FriendList, player.UserIDString, friends));
}
break;
}
case "doors":
{
if (args.Length < 2)
{
Reply(player, ErrorSyntax, command, args[0]);
return;
}
var friend = FindPlayerData(args[1]);
if (friend == null)
{
Reply(player, NotFound, args[1]);
return;
// ... [Code preview truncated due to length. Full source in attached file] ...