C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/**
* Changelogs:
* 2.0.0:
* - Global update, full refactor
* - Disabled sockets
* 1.2.7:
* - Global update: vk.com/@rustpanel-update
* 1.2.6:
* - Alerts improvements
* - Version grabber for panel alert
* 1.2.5:
* - Alert supports
* 1.2.4:
* - Fixed troubles with team detecting
* 1.2.3:
* - State update on disconnected players fixes
* 1.2.2:
* - Supports for IP bans
* 1.2.1:
* - Better RaidZone support
* 1.2.0:
* - Support for a raid-blocks plugins
* - Support for a IP in panel
* 1.1.9:
* - Fixing discord sent with double quotes
* - Additional logs when API error
* 1.1.8:
* - Again trying to fix multi-calling-check-show
* - Fixed trouble with socket silent disconnect (probably)
* 1.1.7:
* - Added configuration for open report panel
* - Fixes possible socket errors
* - Auto hide UI on plugin restart
* - Logs for showing UI
* 1.1.6:
* - Changed the sign of the call for verification
* - Added the ability to change the 'report' command in the plugin configuration
* 1.1.5:
* - Realized socket connection with rust panel
* 1.1.4:
* - Fixed stats update on high load
* 1.1.3:
* - Fixed state update failed
* 1.1.2:
* - Kick for ban fixes
* 1.1.1:
* - Change interface
* 1.1.0:
* - Possible fix state update pause...*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using ConVar;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using Random = System.Random;
namespace Oxide.Plugins {
[Info("Rust Panel", "Hougan", "2.0.0")]
public class RustPanel : RustPlugin {
#region QueueService
#region Classes & Interfaces
class Queue {
internal class DetailsInfo {
[JsonProperty("name")]
public string Name;
[JsonProperty("payload")]
public JObject Payload;
}
[JsonProperty("id")]
public string Id;
[JsonProperty("details")]
public DetailsInfo Details;
}
class QueueResolve {
internal class ResolveInfo {
[JsonProperty("success")]
public bool Success;
[JsonProperty("payload")]
public object Payload;
public ResolveInfo(bool success, [CanBeNull] object payload) {
this.Success = success;
this.Payload = payload;
}
}
[JsonProperty("id")]
public string Id;
[JsonProperty("resolve")]
public ResolveInfo Resolve;
public QueueResolve(string id, ResolveInfo resolve) {
this.Id = id;
this.Resolve = resolve;
}
}
#endregion
#region Components
private class QueueService : MonoBehaviour {
/**
* Url of queue api service
* Do not change this value
*/
private static string URL = $"https://internal.rustpanel.ru/v1/queue?id={Settings.Hash}&api={Settings.Token}";
/**
* Flag for detecting and not pushing more than one message per raw
*/
private bool IS_ERROR = false;
private void Start() {
this.Log("успешно загружен / successfull load");
InvokeRepeating(nameof(Sync), 0f, 1f);
}
private void Sync() {
LoadQueues(queues => {
// this.Log("загружены очереди / queues loaded");
var resolves = this.ParseQueues(queues);
Process(resolves, () => {
if (IS_ERROR) {
this.Log($"работа очередей востановлена / queue service re-connected");
IS_ERROR = false;
}
}
, error => {
if (!IS_ERROR) {
this.Log($"не удалось разобрать очереди / failed to process queues", true);
this.Log($"> {error}", true);
IS_ERROR = true;
}
}
);
}
, error => {
if (!IS_ERROR) {
this.Log($"не удалось обработать очереди / failed to parse queues", true);
this.Log($"> {error}", true);
IS_ERROR = true;
}
}
);
}
private List<QueueResolve> ParseQueues(List<Queue> queues) {
return queues.Select(this.ParseQueue).ToList();
}
private QueueResolve ParseQueue(Queue queue) {
var resolve = Interface.Oxide.CallHook("OnRustPanelQueue", queue.Details.Name, queue.Details.Payload);
if (resolve == null) {
this.Log($"Unresolved action: '{queue.Details.Name}'.Payload: '{JsonConvert.SerializeObject(queue.Details.Payload)}'", true);
return new QueueResolve(queue.Id, new QueueResolve.ResolveInfo(false, "UNRESOLVED"));
}
return new QueueResolve(queue.Id, new QueueResolve.ResolveInfo(!(resolve is String), resolve));
}
private void Process(List<QueueResolve> resolves, Action resolve, Action<string> reject) {
object obj = new {
credentials = new {
api = Settings.Token, id = Settings.Hash
}
, chunks = resolves
}
;
Instance.webrequest.Enqueue(QueueService.URL, JsonConvert.SerializeObject(obj), (code, text) => {
if (code != 200) {
reject($"не удалось соединиться [{code}] / connection failed [{code}]");
return;
}
try {
Response response = JsonConvert.DeserializeObject<Response>(text);
if (!response.Success) {
reject("ответ не был успешным / response was not successful");
return;
}
resolve();
}
catch {
reject("не удалось распознать ответ / response was not convertable");
}
}
, Instance, RequestMethod.PUT, new Dictionary<string, string> {
["Content-Type"]
= "application/json"
}
);
}
private void LoadQueues(Action<List<Queue>> resolve, Action<string> reject) {
Instance.webrequest.Enqueue(QueueService.URL, String.Empty, (code, text) => {
if (code != 200) {
reject($"не удалось соединиться [{code}] / connection failed [{code}]");
return;
}
try {
Response response = JsonConvert.DeserializeObject<Response>(text);
if (!response.Success) {
reject("ответ не был успешным / response was not successful");
return;
}
List<Queue> queues = JsonConvert.DeserializeObject<List<Queue>>(JsonConvert.SerializeObject(response.Data));
resolve(queues);
}
catch {
reject("не удалось распознать ответ / response was not convertable");
}
}
, Instance, RequestMethod.GET, new Dictionary<string, string> {
["Content-Type"]
= "application/json"
}
);
}
private void Log(string text, bool isError = false) {
var prepared = $"QueueService: {text}";
if (isError) {
Instance.PrintError(prepared);
return;
}
Instance.PrintWarning(prepared);
}
}
#endregion
#region Implementation
[CanBeNull]
[HookMethod("OnRustPanelQueue")]
private object OnRustPanelQueue(string name, JObject payload) {
switch (name) {
// Message to the global chat
case "chat_send" : return OnChatSend(JsonConvert.DeserializeObject<ChatSendPayload>(payload.ToString()));
// Message to the player DM
case "chat_direct" : return OnChatDirect(JsonConvert.DeserializeObject<ChatDirectPayload>(payload.ToString()));
// Kick player from server
case "player_kick" : return OnPlayerKick(JsonConvert.DeserializeObject<PlayerKickPayload>(payload.ToString()));
// Show check notification to player
case "check_start" : return OnCheckStart(JsonConvert.DeserializeObject<CheckStartPayload>(payload.ToString()));
// Remove check notification from player
case "check_stop" : return OnCheckStop(JsonConvert.DeserializeObject<CheckStopPayload>(payload.ToString()));
// Check for ban and kick user on server
case "send_ban" : return OnSendBan(JsonConvert.DeserializeObject<SendBanPayload>(payload.ToString()));
// Fetch for ban for user
case "fetch_ban" : return OnFetchBan(JsonConvert.DeserializeObject<FetchBanPayload>(payload.ToString()));
}
return null;
}
#region OnChatSend
class ChatSendPayload {
[JsonProperty("targetId")]
public string TargetId;
[JsonProperty("name")]
public string Name;
[JsonProperty("message")]
public string Message;
}
private object OnChatSend(ChatSendPayload payload) {
if (payload.Name == null || payload.Message == null) {
return $"WRONG.PARAMS";
}
var msg = Settings.MsgFormat.Replace("%NAME%", payload.Name).Replace("%MSG%", payload.Message);
foreach (var check in BasePlayer.activePlayerList) {
check.SendConsoleCommand("chat.add", 0, Settings.ShowAvatar ? payload.TargetId : "0", msg);
}
return new {
}
;
}
#endregion
#region OnChatDirect
private class ChatDirectPayload {
[JsonProperty("targetId")]
public string TargetId;
[JsonProperty("message")]
public string Message;
}
private object OnChatDirect(ChatDirectPayload payload) {
if (payload.Message == null || payload.TargetId == null) {
return $"WRONG.PARAMS";
}
var target = BasePlayer.Find(payload.TargetId);
if (!target || !target.IsConnected) {
return "ERROR.PLAYER.OFFLINE";
}
target.ChatMessage(Settings.DirectFormat.Replace("%MSG%", payload.Message));
Instance.SoundToast(target, "Получено сообщение от модератора, посмотрите в чат", 2);
return new {
}
;
}
#endregion
#region OnPlayerKick
private class PlayerKickPayload {
[JsonProperty("targetId")]
public string TargetId;
[JsonProperty("reason")]
public string Reason;
}
private object OnPlayerKick(PlayerKickPayload payload) {
if (payload.TargetId == null) {
return "WRONG.PARAMS";
}
var target = BasePlayer.Find(payload.TargetId.ToString());
if (!target || !target.IsConnected) {
return "ERROR.PLAYER.OFFLINE";
}
target.Kick(payload.Reason ?? "Steam Auth Timeout");
return new {
}
;
}
#endregion
#region OnCheckStart
private class CheckStartPayload {
[JsonProperty("targetId")]
public string TargetId;
}
private object OnCheckStart(CheckStartPayload payload) {
if (payload.TargetId == null) {
return "WRONG.PARAMS";
}
var target = BasePlayer.Find(payload.TargetId);
if (target == null || !target.IsConnected) {
return "PLAYER_OFFLINE";
}
Instance.DrawInterface(target);
return new {
}
;
}
#endregion
#region OnCheckStop
private class CheckStopPayload {
[JsonProperty("targetId")]
public string TargetId;
}
private object OnCheckStop(CheckStopPayload payload) {
if (payload.TargetId == null) {
return "WRONG.PARAMS";
}
var target = BasePlayer.Find(payload.TargetId);
if (target == null || !target.IsConnected) {
return "PLAYER_OFFLINE";
}
CuiHelper.DestroyUi(target, CheckLayer);
Instance.OnPlayerInit(target);
return new {
}
;
}
#endregion
#region OnSendBan
private class SendBanPayload {
[JsonProperty("steamId")]
public string SteamId;
[JsonProperty("steamName")]
public string SteamName;
[JsonProperty("reason")]
public string Reason;
}
private object OnSendBan(SendBanPayload payload) {
if (Settings.BanFormat.Length != 0) {
Instance.Server.Broadcast(Settings.BanFormat.Replace("%TARGET%", payload.SteamName).Replace("%REASON%", payload.Reason));
}
return new {
}
;
}
#endregion
#region OnFetchBan
private class FetchBanPayload {
[JsonProperty("targetId")]
public string TargetId;
}
private object OnFetchBan(FetchBanPayload payload) {
if (payload.TargetId == null) {
return "WRONG.PARAMS";
}
var target = BasePlayer.Find(payload.TargetId);
if (target == null || !target.IsConnected) {
return "PLAYER_OFFLINE";
}
API.GetBans(target, finalObj => {
}
);
return new {
}
;
}
#endregion
#endregion
#endregion
#region UpdateService
#region Classes & Interfaces
class Response {
[JsonProperty("success")]
public bool Success;
[JsonProperty("data")]
public object Data;
public Response(bool success, object data = null) {
this.Success = success;
this.Data = data;
}
}
private class ServerInfo {
[JsonProperty("name")]
public string Name;
[JsonProperty("hash")]
public string Hash;
[JsonProperty("version")]
public string Version;
public static ServerInfo Get() {
return new ServerInfo {
Name = Settings.Name, Hash = Settings.Hash, Version = Instance.Version.ToString()
}
;
}
}
#endregion
#region Components
private class UpdateService : MonoBehaviour {
private void Start() {
this.Log("успешно загружен / successfull load");
Sync();
}
private void Sync() {
try {
var users = UserService.CACHE.Select(v => v.Sync()).Where(v => v != null);
var payload = new {
state = new {
players = users
}
}
;
API.SendUpdate(payload);
// this.Log("отправлено обновление / send update state");
}
catch {
this.Log("не удалось отправить обновление / failed to send update", true);
}
Invoke(nameof(Sync), 5f);
}
private void Log(string text, bool isError = false) {
var prepared = $"UpdateService: {text}";
if (isError) {
Instance.PrintError(prepared);
return;
}
Instance.PrintWarning(prepared);
}
}
#endregion
#endregion
#region UserService
#region Classes & Interfaces
private class UserInfo {
[JsonProperty("UserName")]
public string UserName;
[JsonProperty("UserId")]
public string UserId;
[JsonProperty("IP")]
public string IP;
[JsonProperty("InCup")]
public bool InCup;
[JsonProperty("IsMoving")]
public bool IsMoving;
[JsonProperty("IsRaidBlocked")]
public bool? IsRaidBlocked;
[JsonProperty("Team")]
public List<string> Team = new List<string>();
}
#endregion
#region Components
private class UserService : MonoBehaviour {
public static Dictionary<ulong, double> COOLDOWNS = new Dictionary<ulong, double>();
public static List<UserService> CACHE = new List<UserService>();
private BasePlayer Player;
private Vector3 Position;
private void Start() {
Player = GetComponent<BasePlayer>();
SavePosition();
UserService.CACHE.Add(this);
}
private void OnDestroy() {
UserService.CACHE.Remove(this);
}
public UserInfo Sync() {
if (Player == null || !Player.IsConnected) {
UnityEngine.Object.Destroy(this);
return null;
}
var userInfo = new UserInfo();
userInfo.UserId = Player.UserIDString;
userInfo.UserName = Player.displayName;
userInfo.IP = Player.Connection.ipaddress.Split(':')[0];
userInfo.IsMoving = Vector3.Distance(Position, Player.transform.position) > 0.1f;
userInfo.IsRaidBlocked = this.IsRaidBlocked();
userInfo.InCup = Player.IsBuildingAuthed();
RelationshipManager.PlayerTeam team = RelationshipManager.Instance.FindTeam(Player.currentTeam);
if (team != null) {
userInfo.Team = team.members.Where(v => v != Player.userID).Select(v => v.ToString()).ToList();
}
this.SavePosition();
return userInfo;
}
private void SavePosition() {
Position = Player.transform.position.ToString().ToVector3();
}
private bool? IsRaidBlocked() {
var plugins = new List<Plugin> {
Instance.NoEscape, Instance.RaidZone, Instance.RaidBlock
}
;
var correct = plugins.Find(v => v != null);
if (correct != null) {
try {
switch (correct.Name) {
case "NoEscape" : {
return (bool) correct.Call("IsRaidBlocked", Player);
}
case "RaidZone" : {
return (bool) correct.Call("HasBlock", Player.userID);
}
case "RaidBlock" : {
try {
return (bool) correct.Call("IsInRaid", Player);
}
catch {
return (bool) correct.Call("IsRaidBlocked", Player);
}
}
}
}
catch {
Instance.PrintError($"Detected {correct.Name} plugin, but failed to call API");
}
}
return null;
}
private void Log(string text, bool isError = false) {
var prepared = $"UserService-{Player.userID}: {text}";
if (isError) {
Instance.PrintError(prepared);
return;
}
Instance.PrintWarning(prepared);
}
}
#endregion
#endregion
#region ApiService
#region Classes & Interfaces
private class API {
private static void Log(string module, string text, bool isError = false) {
var prepared = $"ApiModule-{module}: {text}";
if (isError) {
Instance.PrintError(prepared);
return;
}
Instance.PrintWarning(prepared);
}
public class ALERT_TYPE {
public static string SIMPLE = "simple";
public static string STASH = "stash";
}
public static void Request(string module, object data, Action<Response> callback, RequestMethod method = RequestMethod.GET) {
var payload = JsonConvert.SerializeObject(new {
server = ServerInfo.Get(), data
}
);
Instance.webrequest.Enqueue($"{Settings.ApiUri}/{module}", payload, (code, response) => {
if (code != 200) {
API.Log(module, $"ошибка подключения [{code}] / connection error [{code}]", true);
callback(new Response(false));
return;
}
try {
var obj = JsonConvert.DeserializeObject<Response>(response);
callback(obj);
}
catch(Exception exc) {
API.Log(module, $"не удалось прочитать ответ на запрос / failed to parse answer", true);
callback(new Response(false, response));
}
}
, Instance, method, new Dictionary<string, string> {
["Authorization"]
= Settings.Token, ["Content-Type"] = "application/json"
}
);
}
public static void GetBans(BasePlayer player, Action<object> callback = null) {
Request("ban", new {
targetId = player.UserIDString, targetIp = player.Connection.ipaddress.Split(':')[0]
}
, (response) => {
if (response.Success) {
var reason = (response.Data as JObject)["reason"].ToString();
var time = (response.Data as JObject)["time"].ToString();
var isIp = (response.Data as JObject)["ip"] != null;
if (time.Length == 0) {
time = "-";
}
try {
player.SendConsoleCommand($"echo ID:{(response.Data as JObject)["id"].ToString()}");
}
catch {
}
player.Kick(isIp ? "Вам ограничен вход на сервер" : Settings.KickFormat.Replace("%REASON%", reason).Replace("%TIME%", time));
Instance.Puts($"Player {player.UserIDString} was kicked from the server, reason: {reason}");
callback?.Invoke(reason);
}
}
, RequestMethod.POST);
}
public static void SendUpdate(object data) {
try {
Request("state", data, response => {
}
, RequestMethod.POST);
}
catch(Exception exc) {
Instance.PrintError($"Core: ошибка отправки обновления / failed to send update data");
}
}
public static void SendChat(BasePlayer player, string message) {
Request("chat", new {
playerId = player.UserIDString, message = message
}
, response => {
}
, RequestMethod.POST);
}
public static void OnDisconnected(BasePlayer player, string reason) {
Request("disconnect", new {
targetId = player.UserIDString, reason = reason
}
, response => {
}
, RequestMethod.POST);
}
public static void Report(BasePlayer player, BasePlayer target, string type, string description = null) {
Request("report", new {
initiatorId = player.UserIDString, targetId = target.UserIDString, reason = type, info = description
}
, response => {
}
, RequestMethod.POST);
}
public static void SendAlert(string type, object obj) {
Request("alert", new {
type = type, fields = obj
}
, response => {
}
, RequestMethod.POST);
}
public static void SendDiscord(BasePlayer player, string discord) {
Request("discord", new {
discord = discord, steamId = player.UserIDString
}
, response => {
if (response.Success) {
Instance.Player.Message(player, Instance.lang.GetMessage("Contact.Sent", Instance, player.UserIDString) + $"<color=#8393cd> {discord}</color>", 75435345);
Instance.Player.Message(player, Instance.lang.GetMessage("Contact.SentWait", Instance, player.UserIDString), 75435345);
Instance.Puts($"Player {player.UserIDString} sent a discord for communication: {discord}");
}
}
, RequestMethod.POST);
}
}
#endregion
#endregion
#region Configuration
#region Classes & Interfaces
private class Configuration {
[JsonProperty("Server name for control panel")]
public string Name;
[JsonProperty("API URI (Do not change it!)")]
public string ApiUri;
[JsonProperty("API TOKEN (Do not change it!)")]
public string Token;
[JsonProperty("Unique server ID (Do not change it!)")]
public string Hash;
[JsonProperty("Command will open the GUI")]
public string ChatCommand = "report";
[JsonProperty("Report reasons for UI")]
public List<string> Reasons = new List<string>();
[JsonProperty("Report cooldown (in secs)")]
public int ReportCooldown = 150;
[JsonProperty("Additional scan for in-game reports (F7)")]
public bool UseF7Reports = true;
[JsonProperty("Show moderator avatar, when message from control panel")]
public bool ShowAvatar = true;
[JsonProperty("Message from control panel format (Vars: %NAME% - moderator's name, %MSG% - message)")]
public string MsgFormat = "<color=#B1D6F1>%NAME%</color>: %MSG%";
[JsonProperty("Ban kick reason (%REASON% - ban reason, %TIME% - date of unban)")]
public string KickFormat = "Вы забанены на этом сервере, причина: %REASON%";
[JsonProperty("Ban broadcast (%TARGET% - target name, %REASON% - reason, you can keep empty for hide message")]
public string BanFormat = "%TARGET% was banned for %REASON%";
[JsonProperty("Direct message format (when moderator is chatting with suspected player)")]
public string DirectFormat = "<color=orange>Сообщение от модератора:</color>\n%MSG%";
public static Configuration Generate() {
return new Configuration {
Name = "SUMMER RUST MAIN", ApiUri = "https://api-rp3859.rustpanel.ru/api/plugin", Token = "41135f332d823394090b5d7128d687c2", Hash = CreateString(16), Reasons = new List<string> {
"Читы", "Макросы", "Багоюз", "3+"
}
}
;
}
}
static Random rd = new Random();
internal static string CreateString(int stringLength) {
const string allowedChars = "abcdefghijkmnopqrstuvwxyz0123456789";
char[] chars = new char[stringLength];
for (int i = 0; i < stringLength; i++) {
chars[i] = allowedChars[rd.Next(0, allowedChars.Length)];
}
return new string(chars);
}
#endregion
#region Implementation
private void RegisterLang() {
lang.RegisterMessages(new Dictionary<string, string> {
["Header.Find"]
= "FIND PLAYER", ["Header.SubDefault"] = "Who do you want to report?", ["Header.SubFindResults"] = "Here are players, which we found", ["Header.SubFindEmpty"] = "No players was found", ["Header.Search"] = "Search", ["Subject.Head"] = "Select the reason for the report", ["Subject.SubHead"] = "For player %PLAYER%", ["Cooldown"] = "Wait %TIME% sec.", ["Sent"] = "Report succesful sent", ["Contact.Error"] = "You did not sent your Discord", ["Contact.Sent"] = "You sent:", ["Contact.SentWait"] = "If you sent the correct discord - wait for a friend request.", ["Check.Text"] = "<color=#c6bdb4><size=32><b>YOU ARE CALLED FOR CHECK</b></size></color>\n<color=#958D85>You have <color=#c6bdb4><b>5 minutes</b></color> to send discord, and accept friends.\nCommand for send: <b><color=#c6bdb4>/contact</color> <<color=#c6bdb4>Nick#0000</color>></b></color>"
}
, this, "en");
lang.RegisterMessages(new Dictionary<string, string> {
["Header.Find"]
= "НАЙТИ ИГРОКА", ["Header.SubDefault"] = "На кого вы хотите пожаловаться?", ["Header.SubFindResults"] = "Вот игроки, которых мы нашли", ["Header.SubFindEmpty"] = "Игроки не найдены", ["Header.Search"] = "Поиск", ["Subject.Head"] = "Выберите причину репорта", ["Subject.SubHead"] = "На игрока %PLAYER%", ["Cooldown"] = "Подожди %TIME% сек.", ["Sent"] = "Жалоба успешно отправлена", ["Contact.Error"] = "Вы не отправили свой Discord", ["Contact.Sent"] = "Вы отправили:", ["Contact.SentWait"] = "<size=12>Если вы отправили корректный дискорд - ждите заявку в друзья.</size>", ["Check.Text"] = "<color=#c6bdb4><size=32><b>ВЫ ВЫЗВАНЫ НА ПРОВЕРКУ</b></size></color>\n<color=#958D85>У вас есть <color=#c6bdb4><b>5 минут</b></color> чтобы отправить дискорд и принять заявку в друзья.\nКоманда для отправки: <b><color=#c6bdb4>/contact</color> <<color=#c6bdb4>Nick#0000</color>></b></color>"
}
, this, "ru");
PrintWarning("Core: языковые файлы загружены / lang files loaded");
}
private void RegisterCommands() {
cmd.AddChatCommand(Settings.ChatCommand, this, "ChatCmdReport");
PrintWarning("Core: команды зарегистрированы / commands registered");
}
protected override void LoadConfig() {
base.LoadConfig();
try {
Settings = Config.ReadObject<Configuration>();
}
catch {
PrintWarning($"Error reading config, creating one new config!");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig() => Settings = Configuration.Generate();
protected override void SaveConfig() => Config.WriteObject(Settings);
#endregion
#endregion
#region Variables
private static RustPanel Instance;
private static Configuration Settings = Configuration.Generate();
// References for RB plugins to get RB status
[PluginReference]
private Plugin NoEscape, RaidZone, RaidBlock;
#endregion
#region Hooks
private void OnServerInitialized() {
Instance = this;
UserService.CACHE = new List<UserService>();
UserService.COOLDOWNS = new Dictionary<ulong, double>();
RegisterLang();
RegisterCommands();
ServerMgr.Instance.gameObject.AddComponent<QueueService>();
ServerMgr.Instance.gameObject.AddComponent<UpdateService>();
foreach (var player in BasePlayer.activePlayerList) {
OnPlayerInit(player);
}
}
private void Unload() {
var queueComp = ServerMgr.Instance.gameObject.GetComponent<QueueService>();
var updateComp = ServerMgr.Instance.gameObject.GetComponent<UpdateService>();
var userComp = UserService.CACHE;
if (queueComp != null) {
UnityEngine.Object.Destroy(queueComp);
}
if (updateComp != null) {
UnityEngine.Object.Destroy(updateComp);
}
if (userComp != null && userComp.Count > 0) {
userComp.ForEach(UnityEngine.Object.Destroy);
}
foreach (var player in BasePlayer.activePlayerList) {
CuiHelper.DestroyUi(player, CheckLayer);
CuiHelper.DestroyUi(player, ReportLayer);
}
}
private void OnPlayerInit(BasePlayer player) {
if (player == null) return;
player.gameObject.AddComponent<UserService>();
API.GetBans(player);
}
void OnPlayerChat(ConsoleSystem.Arg arg) {
var player = arg.Player();
var message = arg.GetString(0, "text").Trim();
API.SendChat(player, message);
}
private void OnPlayerDisconnected(BasePlayer player, string reason) {
var comp = player.GetComponent<UserService>();
if (comp != null) {
UnityEngine.Object.Destroy(comp);
}
API.OnDisconnected(player, reason);
}
private void OnStashExposed(StashContainer stash, BasePlayer player) {
if (stash == null) {
return;
}
RelationshipManager.PlayerTeam team = RelationshipManager.Instance.FindTeam(player.currentTeam);
if (team != null) {
if (team.members.Contains(stash.OwnerID)) {
return;
}
}
var owner = stash.OwnerID;
if (player.userID == stash.OwnerID) {
return;
}
API.SendAlert(API.ALERT_TYPE.STASH, new {
initiator = new {
value = player.UserIDString, type = "player"
}
, target = new {
value = stash.OwnerID.ToString(), type = "player"
}
, square = new {
value = $"{GridReference(player.transform.position)}", type = "text"
}
}
);
}
private void OnPlayerReported(BasePlayer reporter, string targetName, string targetId, string subject, string message, string type) {
if (!Settings.UseF7Reports) {
return;
}
var target = BasePlayer.Find(targetId) ?? BasePlayer.FindSleeping(targetId);
if (target == null) {
return;
}
API.Report(reporter, target, type, message);
}
#endregion
#region Interface Check
private const string CheckLayer = "RP_PrivateLayer";
private void DrawInterface(BasePlayer player) {
CuiElementContainer container = new CuiElementContainer();
container.Add(new CuiButton {
RectTransform = {
AnchorMin = "0 0.5", AnchorMax = "1 1", OffsetMin = $"-500 -500", OffsetMax = $"500 500"
}
, Button = {
Color = HexToRustFormat("#1C1C1C"), Sprite = "assets/content/ui/ui.circlegradient.png"
}
, Text = {
Text = "", Align = TextAnchor.MiddleCenter
}
}
, "Under", CheckLayer);
string text = lang.GetMessage("Check.Text", this, player.UserIDString);
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
, Text = {
Text = text, Align = TextAnchor.MiddleCenter, Font = "robotocondensed-regular.ttf", FontSize = 16
}
}
, CheckLayer);
CuiHelper.AddUi(player, container);
Effect effect = new Effect("ASSETS/BUNDLED/PREFABS/FX/INVITE_NOTICE.PREFAB".ToLower(), player, 0, new Vector3(), new Vector3());
EffectNetwork.Send(effect, player.Connection);
}
#endregion
#region Interface Choose
[ConsoleCommand("UI_RP_ReportPanel")]
private void CmdConsoleReportPanel(ConsoleSystem.Arg args) {
var player = args.Player();
if (player == null || !args.HasArgs(1)) {
return;
}
switch (args.Args[0].ToLower()) {
case "search" : {
int page = args.HasArgs(2) ? int.Parse(args.Args[1]) : 0;
string search = args.HasArgs(3) ? args.Args[2] : "";
Effect effect = new Effect("assets/prefabs/tools/detonator/effects/unpress.prefab", player, 0, new Vector3(), new Vector3());
EffectNetwork.Send(effect, player.Connection);
DrawReportInterface(player, page, search, true);
break;
}
case "show" : {
string targetId = args.Args[1];
BasePlayer target = BasePlayer.Find(targetId) ?? BasePlayer.FindSleeping(targetId);
Effect effect = new Effect("assets/prefabs/tools/detonator/effects/unpress.prefab", player, 0, new Vector3(), new Vector3());
EffectNetwork.Send(effect, player.Connection);
CuiElementContainer container = new CuiElementContainer();
CuiHelper.DestroyUi(player, ReportLayer + $".T");
container.Add(new CuiPanel {
RectTransform = {
AnchorMin = "0 1", AnchorMax = "0 1", OffsetMin = $"{args.Args[2]} {args.Args[3]}", OffsetMax = $"{args.Args[4]} {args.Args[5]}"
}
, Image = {
Color = "0 0 0 1"
}
}
, ReportLayer + $".L", ReportLayer + $".T");
container.Add(new CuiButton() {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = $"-500 -500", OffsetMax = $"500 500"
}
, Button = {
Close = $"{ReportLayer}.T", Color = "0 0 0 1", Sprite = "assets/content/ui/ui.circlegradient.png"
}
}
, ReportLayer + $".T");
bool leftAlign = bool.Parse(args.Args[6]);
container.Add(new CuiButton() {
RectTransform = {
AnchorMin = $"{(leftAlign ? -1 : 2)} 0", AnchorMax = $"{(leftAlign ? -2 : 3)} 1", OffsetMin = $"-500 -500", OffsetMax = $"500 500"
}
, Button = {
Close = $"{ReportLayer}.T", Color = HexToRustFormat("#282828"), Sprite = "assets/content/ui/ui.circlegradient.png"
}
}
, ReportLayer + $".T");
container.Add(new CuiButton() {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = $"-1111111 -1111111", OffsetMax = $"1111111 1111111"
}
, Button = {
Close = $"{ReportLayer}.T", Color = "0 0 0 0.5", Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat"
}
}
, ReportLayer + $".T");
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = $"{(leftAlign ? "0" : "1")} 0", AnchorMax = $"{(leftAlign ? "0" : "1")} 1", OffsetMin = $"{(leftAlign ? "-350" : "20")} 0", OffsetMax = $"{(leftAlign ? "-20" : "350")} -5"
}
, Text = {
FadeIn = 0.4f, Text = lang.GetMessage("Subject.Head", this, player.UserIDString), Font = "robotocondensed-bold.ttf", FontSize = 24, Align = leftAlign ? TextAnchor.UpperRight : TextAnchor.UpperLeft
}
}
, ReportLayer + ".T");
container.Add(new CuiLabel {
RectTransform = {
AnchorMin = $"{(leftAlign ? "0" : "1")} 0", AnchorMax = $"{(leftAlign ? "0" : "1")} 1", OffsetMin = $"{(leftAlign ? "-250" : "20")} 0", OffsetMax = $"{(leftAlign ? "-20" : "250")} -35"
}
, Text = {
FadeIn = 0.4f, Text = $"{lang.GetMessage("Subject.SubHead", this, player.UserIDString).Replace("%PLAYER%", $"<b> {
target.displayName
}
</b>")}", Font = "robotocondensed-regular.ttf", FontSize = 14, Color = "1 1 1 0.7", Align = leftAlign ? TextAnchor.UpperRight : TextAnchor.UpperLeft
}
}
, ReportLayer + ".T");
container.Add(new CuiElement {
Parent = ReportLayer + $".T", Components = {
new CuiRawImageComponent {
Png = (string) plugins.Find("ImageLibrary").Call("GetImage", target.UserIDString)
}
, new CuiRectTransformComponent {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
}
}
);
for (var i = 0; i < Settings.Reasons.Count; i++) {
var offXMin = (20 + (i * 5)) + i * 80;
var offXMax = 20 + (i * 5) + (i + 1) * 80;
container.Add(new CuiButton() {
RectTransform = {
AnchorMin = $"{(leftAlign ? 0 : 1)} 0", AnchorMax = $"{(leftAlign ? 0 : 1)} 0", OffsetMin = $"{(leftAlign ? -offXMax : offXMin)} 15", OffsetMax = $"{(leftAlign ? -offXMin : offXMax)} 45"
}
, Button = {
FadeIn = 0.4f + i * 0.2f, Color = HexToRustFormat("#FFFFFF4D"), Command = $"UI_RP_ReportPanel report {target.UserIDString} {Settings.Reasons[i].Replace(" ", "0")}"
}
, Text = {
FadeIn = 0.4f, Text = $"{Settings.Reasons[i]}", Align = TextAnchor.MiddleCenter, Color = "1 1 1 1", Font = "robotocondensed-regular.ttf", FontSize = 16
}
}
, ReportLayer + $".T");
}
CuiHelper.AddUi(player, container);
break;
}
case "report" : {
string targetId = args.Args[1];
string reason = args.Args[2].Replace("0", "");
BasePlayer target = BasePlayer.Find(targetId) ?? BasePlayer.FindSleeping(targetId);
API.Report(player, target, reason);
CuiHelper.DestroyUi(player, ReportLayer);
SoundToast(player, lang.GetMessage("Sent", this, player.UserIDString), 2);
if (!UserService.COOLDOWNS.ContainsKey(player.userID)) {
UserService.COOLDOWNS.Add(player.userID, 0);
}
UserService.COOLDOWNS[player.userID] = CurrentTime() + Settings.ReportCooldown;
break;
}
}
}
private static string ReportLayer = "UI_RP_ReportPanelUI";
private void DrawReportInterface(BasePlayer player, int page = 0, string search = "", bool redraw = false) {
var lineAmount = 6;
var lineMargin = 8;
var size = (float) (700 - lineMargin * lineAmount) / lineAmount;
var list = BasePlayer.activePlayerList.ToList();
var finalList = list.FindAll(v => v.displayName.ToLower().Contains(search) || v.UserIDString.ToLower().Contains(search) || search == null).Skip(page * 18).Take(18);
if (finalList.Count() == 0) {
if (search == null) {
DrawReportInterface(player, page - 1);
return;
}
}
CuiElementContainer container = new CuiElementContainer();
if (!redraw) {
CuiHelper.DestroyUi(player, ReportLayer);
container.Add(new CuiPanel {
CursorEnabled = true, RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
, Image = {
Color = HexToRustFormat("#282828E6"), Material = "assets/content/ui/uibackgroundblur-ingamemenu.mat"
}
}
, "Overlay", ReportLayer);
container.Add(new CuiButton() {
RectTransform = {
AnchorMin = "0 0", AnchorMax = "1 1", OffsetMax = "0 0"
}
, Button = {
// ... [Code preview truncated due to length. Full source in attached file] ...