C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using ConVar;
using JetBrains.Annotations;
using Network;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using Steamworks;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Networking;
using Color = System.Drawing.Color;
using Graphics = System.Drawing.Graphics;
using Pool = Facepunch.Pool;
using Star = ProtoBuf.PatternFirework.Star;
namespace Oxide.Plugins {
[Info("RustApp", "RustApp.io", "2.6.1")]
public class RustApp : RustPlugin {
#region Variables
// References to other plugin with API
[PluginReference]
private Plugin NoEscape, RaidZone, RaidBlock, MultiFighting, TGPP, ExtRaidBlock;
private static MetaInfo _MetaInfo = MetaInfo.Read();
private static CheckInfo _CheckInfo = CheckInfo.Read();
private static bool _TempWipeMarker = false;
private static RustApp _RustApp;
private static Configuration _Settings;
private static RustAppEngine _RustAppEngine;
private static JsonSerializer _jsonSerializer = JsonSerializer.CreateDefault(null);
private static (string name, string value)[] _ApiHeaders = Array.Empty<(string, string)>();
private static readonly object _false = false;
// avoid boxing for hooks
#endregion
#region Web API
private static class Api {
public static bool ErrorContains(string error, string text) {
return error.Contains(text, StringComparison.OrdinalIgnoreCase);
}
}
private static class CourtApi {
#region Shared
public class PluginServerDto {
public string name = ConVar.Server.hostname;
public string hostname = ConVar.Server.hostname;
public string level = SteamServer.MapName ?? ConVar.Server.level;
public string level_url = ConVar.Server.levelurl;
// Thanks for the information, DezLife
public string level_image_url = MapUploader.ImageUrl;
public int world_size = ConVar.Server.worldsize;
public string description = ConVar.Server.description + " " + ConVar.Server.motd;
public string branch = ConVar.Server.branch;
public string avatar_big = ConVar.Server.logoimage;
public string avatar_url = ConVar.Server.logoimage;
public string banner_url = ConVar.Server.headerimage;
public int online = BasePlayer.activePlayerList.Count + ServerMgr.Instance.connectionQueue.queue.Count + ServerMgr.Instance.connectionQueue.joining.Count;
public int slots = ConVar.Server.maxplayers;
public int reserved = ServerMgr.Instance.connectionQueue.ReservedCount;
public string version = _RustApp.Version.ToString();
public string protocol = Protocol.printable.ToString();
public string performance = _RustApp.TotalHookTime.ToString();
public int port = ConVar.Server.port;
public bool connected = _RustAppEngine?.AuthWorker.IsAuthed ?? false;
}
#endregion
private const string BaseUrl = "https://court.rustapp.io";
#region GetServerInfo
public static StableRequest<object> GetServerInfo() {
return new StableRequest<object>($"{BaseUrl}/plugin/", RequestMethod.GET, null);
}
#endregion
#region SendPairDetails
public class PluginPairPayload : PluginServerDto {
}
public class PluginPairResponse {
[CanBeNull]
public int ttl;
[CanBeNull]
public string token;
}
public static StableRequest<PluginPairResponse> SendPairDetails(string code) {
return new StableRequest<PluginPairResponse>($"{BaseUrl}/plugin/pair?code={code}", RequestMethod.POST, new PluginPairPayload());
}
#endregion
#region StateUpdate
public class PluginStatePlayerMetaDto {
public Dictionary<string, string> tags = new Dictionary<string, string>();
public Dictionary<string, string> fields = new Dictionary<string, string>();
}
public static Dictionary<ulong, PluginStatePlayerDto> players = new Dictionary<ulong, PluginStatePlayerDto>();
public class PluginStatePlayerDto {
public static PluginStatePlayerDto FromConnection(Network.Connection connection, string status) {
var userid = connection.userid;
if (!players.TryGetValue(userid, out var payload)) {
payload = new PluginStatePlayerDto();
players[userid] = payload;
payload.steam_id = connection.player is BasePlayer basePlayer ? basePlayer.UserIDString : userid.ToString();
payload.steam_name = connection.username.Replace("<blank>", "blank");
payload.ip = IPAddressWithoutPort(connection.ipaddress);
payload.no_license = DetectNoLicense(connection);
payload.team = new List<string>();
}
payload.ping = Network.Net.sv.GetAveragePing(connection);
payload.seconds_connected = (int)connection.GetSecondsConnected();
payload.language = _RustApp.lang.GetLanguage(payload.steam_id);
payload.status = status;
try {
payload.meta = CollectPlayerMeta(payload.steam_id, payload.meta);
}
catch {
}
payload.team.Clear();
var newTeam = RelationshipManager.ServerInstance.FindPlayersTeam(userid);
if (newTeam == null) {
return payload;
}
foreach (var member in newTeam.members) {
if (member != userid) {
payload.team.Add(member.ToString());
}
}
return payload;
}
public static PluginStatePlayerDto FromPlayer(BasePlayer player) {
var payload = FromConnection(player.Connection, "active");
payload.position = player.transform.position.ToString();
payload.rotation = player.eyes.rotation.ToString();
payload.coords = MapHelper.PositionToString(player.transform.position);
payload.can_build = DetectBuildingAuth(player);
payload.is_raiding = DetectIsRaidBlock(player);
payload.is_alive = player.IsAlive();
return payload;
}
public string steam_id;
public string steam_name;
public string ip;
public int ping = 0;
public int seconds_connected;
public string language;
[CanBeNull]
public string position;
[CanBeNull]
public string rotation;
[CanBeNull]
public string coords;
public bool can_build = false;
public bool is_raiding = false;
public bool no_license = false;
public bool is_alive = false;
public string status;
public PluginStatePlayerMetaDto meta = new PluginStatePlayerMetaDto();
public List<string> team;
}
public class PluginStateUpdatePayload : PluginServerDto {
public PluginServerDto server_info = new PluginServerDto();
public List<PluginStatePlayerDto> players;
public Dictionary<string, string> disconnected;
public Dictionary<string, string> team_changes;
}
public static StableRequest<object> SendStateUpdate(PluginStateUpdatePayload data) {
return new StableRequest<object>($"{BaseUrl}/plugin/state", RequestMethod.PUT, data);
}
#endregion
#region SendChatMessages
public class PluginChatMessageDto {
public string steam_id;
[CanBeNull]
public string target_steam_id;
public bool is_team;
public string text;
}
public class PluginChatMessagePayload {
public List<PluginChatMessageDto> messages;
}
public static StableRequest<object> SendChatMessages(PluginChatMessagePayload payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/chat", RequestMethod.POST, payload);
}
#endregion
#region SendReports
public class PluginReportDto {
public string initiator_steam_id;
[CanBeNull]
public string target_steam_id;
public List<string> sub_targets_steam_ids;
public string reason;
[CanBeNull]
public string message;
}
public class PluginReportBatchPayload {
public List<CourtApi.PluginReportDto> reports;
}
public static StableRequest<object> SendReports(PluginReportBatchPayload payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/reports", RequestMethod.POST, payload);
}
#endregion
#region SendContact
public static StableRequest<object> SendContact(string steamId, string contact) {
return new StableRequest<object>($"{BaseUrl}/plugin/contact", RequestMethod.POST, new {
steam_id = steamId, message = contact
}
);
}
#endregion
#region SendWipe
public static StableRequest<object> SendWipe() {
return new StableRequest<object>($"{BaseUrl}/plugin/wipe", RequestMethod.POST, null);
}
#endregion
#region Sleeping bag
public class PluginSleepingBagDto {
public string initiator_steam_id;
public string target_steam_id;
public string position;
public bool are_friends;
}
public class PluginSleepingBagBatchDto {
public List<PluginSleepingBagDto> sleeping_bags = new List<PluginSleepingBagDto>();
}
public static StableRequest<object> SleepingBagCreate(PluginSleepingBagBatchDto payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/sleeping-bag", RequestMethod.POST, payload);
}
#endregion
#region BanCreate
public class PluginBanCreatePayload {
public string target_steam_id;
public string reason;
public bool global;
public bool ban_ip;
public string duration;
public string comment;
}
public static StableRequest<object> BanCreate(PluginBanCreatePayload payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/ban", RequestMethod.POST, payload);
}
#endregion
#region BanDelete
public static StableRequest<object> BanDelete(string steamId) {
return new StableRequest<object>($"{BaseUrl}/plugin/unban", RequestMethod.POST, new {
target_steam_id = steamId
}
);
}
#endregion
#region PlayerAlert
public static class PluginPlayerAlertType {
public static readonly string join_with_ip_ban = "join_with_ip_ban";
public static readonly string custom_api = "custom_api";
}
public class PluginPlayerAlertDto {
public string type;
public object meta;
}
public class PluginPlayerAlertJoinWithIpBanMeta {
public string steam_id;
public string ip;
public int ban_id;
}
public class PluginPlayerAlertDugUpStashMeta {
public string steam_id;
public string owner_steam_id;
public string position;
public string square;
}
public static StableRequest<object> CreatePlayerAlerts(List<PluginPlayerAlertDto> alerts) {
return new StableRequest<object>($"{BaseUrl}/plugin/alerts", RequestMethod.POST, new {
alerts
}
);
}
#endregion
#region PlayerAlertCustom
public class PluginPlayerAlertCustomDto {
public string msg;
public object data;
public string custom_icon;
public bool hide_in_table = false;
public string category;
public List<string> custom_links;
}
public class PluginPlayerAlertCustomAlertMeta {
public string name = "";
public string custom_icon = null;
public List<string> custom_links = null;
}
public static StableRequest<object> CreatePlayerAlertsCustom(PluginPlayerAlertCustomDto payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/custom-alert", RequestMethod.POST, payload);
}
#endregion
#region SendSignage
public class PluginSignageCreateDto {
public string steam_id;
public ulong net_id;
public byte[] base64_image;
public string type;
public string position;
public string square;
}
public static StableRequest<object> SendSignage(PluginSignageCreateDto payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/signage", RequestMethod.POST, payload);
}
#endregion
#region SendSignageDestroyed
public class SignageDestroyedDto {
public List<string> net_ids;
}
public static StableRequest<object> SendSignageDestroyed(SignageDestroyedDto payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/signage", RequestMethod.DELETE, payload);
}
#endregion
#region SendKills
public class PluginKillsDto {
public List<PluginKillEntryDto> kills;
}
public class PluginKillEntryDto {
public string initiator_steam_id;
public string target_steam_id;
public string game_time;
public float distance;
public string weapon;
public bool is_headshot;
public List<CombatLogEventDto> hit_history;
}
public class CombatLogEventDto {
public float time;
public string attacker_steam_id;
public string target_steam_id;
public string attacker;
public string target;
public string weapon;
public string ammo;
public string bone;
public float distance;
public float hp_old;
public float hp_new;
public string info;
public int proj_hits;
public float pi;
public float proj_travel;
public float pm;
public int desync;
public bool ad;
public CombatLogEventDto(float time, CombatLog.Event ev) {
if (ev.attacker == "player") {
var attacker = BaseNetworkable.serverEntities.Find(new NetworkableId(ev.attacker_id)) as BasePlayer;
;
this.attacker_steam_id = attacker?.UserIDString ?? "";
}
if (ev.target == "player") {
var target = BaseNetworkable.serverEntities.Find(new NetworkableId(ev.target_id)) as BasePlayer;
this.target_steam_id = target?.UserIDString ?? "";
}
this.time = time - ev.time;
this.attacker = ev.attacker;
this.target = ev.target;
this.weapon = ev.weapon;
this.ammo = ev.ammo;
this.bone = ev.bone;
this.distance = (float)Math.Round(ev.distance, 2);
this.hp_old = (float)Math.Round(ev.health_old, 2);
this.hp_new = (float)Math.Round(ev.health_new, 2);
this.info = ev.info;
this.proj_hits = ev.proj_hits;
this.proj_travel = ev.proj_travel;
this.desync = ev.desync;
this.pi = ev.proj_integrity;
this.pm = ev.proj_mismatch;
this.ad = ev.attacker_dead;
}
public string getInitiator() {
if (this.attacker != "player") {
return this.attacker;
}
return this.attacker_steam_id;
}
public string getTarget() {
if (this.target != "player") {
return this.target;
}
return this.target_steam_id;
}
}
public static StableRequest<object> SendKills(PluginKillsDto payload) {
return new StableRequest<object>($"{BaseUrl}/plugin/kills", RequestMethod.POST, payload);
}
#endregion
#region PlayerMuteGetActive
public class PlayerMuteDto {
public string target_steam_id;
public string reason;
public long left_time_ms;
public long received_at = DateTimeOffset.Now.ToUnixTimeMilliseconds();
private long GetExpiredAt() {
return received_at + left_time_ms;
}
public string GetLeftTime() {
var leftMs = LeftSeconds();
if (leftMs <= 0) return RustApp._RustApp.lang.GetMessage("Time.Seconds", RustApp._RustApp, null).Replace("%COUNT%", "0");
var time = TimeSpan.FromMilliseconds(leftMs);
var parts = new List<string>();
if (time.Days > 0) parts.Add(RustApp._RustApp.lang.GetMessage("Time.Days", RustApp._RustApp, null).Replace("%COUNT%", time.Days.ToString()));
if (time.Hours > 0) parts.Add(RustApp._RustApp.lang.GetMessage("Time.Hours", RustApp._RustApp, null).Replace("%COUNT%", time.Hours.ToString()));
if (time.Minutes > 0) parts.Add(RustApp._RustApp.lang.GetMessage("Time.Minutes", RustApp._RustApp, null).Replace("%COUNT%", time.Minutes.ToString()));
if (time.Seconds > 0) parts.Add(RustApp._RustApp.lang.GetMessage("Time.Seconds", RustApp._RustApp, null).Replace("%COUNT%", time.Seconds.ToString()));
return string.Join(", ", parts);
}
public string GetUnmuteDate() {
var date = DateTimeOffset.FromUnixTimeMilliseconds(GetExpiredAt());
return $"{date.DateTime.ToShortDateString()} {date.DateTime.ToShortTimeString()}";
}
public long LeftSeconds() {
return GetExpiredAt() - DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
}
public class PlayerMuteDtoIn {
public List<PlayerMuteDto> data;
}
public static StableRequest<PlayerMuteDtoIn> PlayerMuteGetActive() {
return new StableRequest<PlayerMuteDtoIn>($"{BaseUrl}/plugin/player-mute/get-active", RequestMethod.GET, null);
}
#endregion
#region PlayerMuteCreate
public class PlayerMuteCreateDto {
public string target_steam_id;
public string reason;
public string duration;
public bool broadcast;
[CanBeNull]
public string comment;
[CanBeNull]
public string references_message;
}
public static StableRequest<object> PlayerMuteCreate(PlayerMuteCreateDto data) {
return new StableRequest<object>($"{BaseUrl}/plugin/player-mute/mute-player", RequestMethod.POST, data);
}
#endregion
#region PlayerMuteDelete
public class PlayerMuteDeleteDto {
public string target_steam_id;
}
public static StableRequest<object> PlayerMuteDelete(PlayerMuteDeleteDto data) {
return new StableRequest<object>($"{BaseUrl}/plugin/player-mute/unmute-player", RequestMethod.POST, data);
}
#endregion
}
private static class QueueApi {
#region Shared
public class QueueTaskResponse {
public string id;
public QueueTaskRequestConfigDto request;
}
public class QueueTaskRequestConfigDto {
public string name;
public JObject data;
}
#endregion
private const string BaseUrl = "https://queue.rustapp.io";
#region GetQueueTasks
public static StableRequest<List<QueueTaskResponse>> GetQueueTasks() {
return new StableRequest<List<QueueTaskResponse>>($"{BaseUrl}/", RequestMethod.GET, null);
}
#endregion
#region ProcessQueueTasks
public class QueueTaskResponsePayload {
public Dictionary<string, object> data;
}
public static StableRequest<object> ProcessQueueTasks(QueueTaskResponsePayload payload) {
return new StableRequest<object>($"{BaseUrl}/", RequestMethod.PUT, payload);
}
#endregion
}
private static class BanApi {
private static readonly string BaseUrl = "https://ban.rustapp.io";
#region BanGetBatch
public class BanGetBatchEntryPayloadDto {
public string steam_id;
public string ip;
}
public class BanGetBatchPayload {
public List<BanGetBatchEntryPayloadDto> players;
}
public class BanGetBatchEntryResponseDto {
public string steam_id;
public string ip;
public List<BanDto> bans;
}
public class BanDto {
public int id;
public string steam_id;
public string ban_ip;
public string reason;
public long expired_at;
public bool ban_ip_active;
public bool computed_is_active;
public int sync_project_id = 0;
public bool sync_should_kick = false;
}
public class BanGetBatchResponse {
public List<BanGetBatchEntryResponseDto> entries;
}
public static StableRequest<BanGetBatchResponse> BanGetBatch(BanGetBatchPayload payload) {
return new StableRequest<BanGetBatchResponse>($"{BaseUrl}/plugin/v2", RequestMethod.POST, payload);
}
#endregion
}
#endregion
#region Configuration
public class MetaInfo {
public static MetaInfo Read() {
if (!Interface.Oxide.DataFileSystem.ExistsDatafile("RustApp_Configuration")) {
_MetaInfo = null;
return null;
}
var obj = Interface.Oxide.DataFileSystem.ReadObject<MetaInfo>("RustApp_Configuration");
_MetaInfo = obj;
return obj;
}
public static void write(MetaInfo courtMeta) {
Interface.Oxide.DataFileSystem.WriteObject("RustApp_Configuration", courtMeta);
MetaInfo.Read();
}
[JsonProperty("It is access for RustApp Court, never edit or share it")]
public string Value;
}
public class CheckInfo {
public static CheckInfo Read() {
if (!Interface.Oxide.DataFileSystem.ExistsDatafile("RustApp_CheckMeta")) {
return new CheckInfo();
}
return Interface.Oxide.DataFileSystem.ReadObject<CheckInfo>("RustApp_CheckMeta");
}
public static void write(CheckInfo courtMeta) {
// Clear checks > 30 days ago
courtMeta.LastChecks = courtMeta.LastChecks.Where(v => _RustApp.CurrentTime() - v.Value < 30 * 24 * 60 * 60).ToDictionary(v => v.Key, v => v.Value);
Interface.Oxide.DataFileSystem.WriteObject("RustApp_CheckMeta", courtMeta);
}
[JsonProperty("List of recent checks to show green-check on player")]
public Dictionary<string, double> LastChecks = new Dictionary<string, double>();
}
private class Configuration {
[JsonProperty("[UI] Chat commands")]
public List<string> report_ui_commands = new List<string>();
[JsonProperty("[UI] Report reasons")]
public List<string> report_ui_reasons = new List<string>();
[JsonProperty("[UI] Cooldown between reports (seconds)")]
public int report_ui_cooldown = 300;
[JsonProperty("[UI] Auto-parse reports from F7 (ingame reports)")]
public bool report_ui_auto_parse = true;
[JsonProperty("[UI • Starter Plan] Show 'recently checked' checkbox (amount of days)")]
public int report_ui_show_check_in = 7;
[JsonProperty("[Chat] SteamID for message avatar (default account contains RustApp logo)")]
public string chat_default_avatar_steamid = "76561198134964268";
[JsonProperty("[Check] Command to send contact")]
public string check_contact_command = "contact";
[JsonProperty("[Components • Custom actions] Allow console command execution")]
public bool components_custom_actions_enabled = true;
[JsonProperty("[Components • Signages] Collect signages")]
public bool components_signages_enabled = true;
[JsonProperty("[Components • Kills] Collect kills")]
public bool components_kills_enabled = true;
[JsonProperty("[Components • Mutes] Support mutes system")]
public bool components_mutes_enabled = true;
public static Configuration Generate() {
return new Configuration {
report_ui_commands = new List<string> {
"report", "reports"
}
, report_ui_reasons = new List<string> {
"Cheat", "Macros", "Abuse"
}
, report_ui_cooldown = 300, report_ui_auto_parse = true, report_ui_show_check_in = 7, chat_default_avatar_steamid = "76561198134964268", components_custom_actions_enabled = true, components_kills_enabled = true, components_signages_enabled = true, components_mutes_enabled = true,
}
;
}
}
#endregion
#region Workers
private class RustAppEngine : RustAppWorker {
public GameObject ChildObjectToWorkers;
public AuthWorker? AuthWorker;
public BanWorker? BanWorker;
public StateWorker? StateWorker;
public CheckWorker? CheckWorker;
public QueueWorker? QueueWorker;
public ChatWorker? ChatWorker;
public ReportWorker? ReportWorker;
public PlayerAlertsWorker? PlayerAlertsWorker;
public SignageWorker? SignageWorker;
public KillsWorker? KillsWorker;
public PlayerMuteWorker? PlayerMuteWorker;
public SleepingBagWorker? SleepingBagWorker;
private void Awake() {
base.Awake();
SetupAuthWorker();
}
public bool IsPairingNow() {
return this.gameObject.GetComponent<PairWorker>() != null;
}
private void SetupAuthWorker() {
SetupHeaders();
AuthWorker = this.gameObject.AddComponent<AuthWorker>();
AuthWorker.OnAuthSuccess += () => {
Trace("Authed success, components enabled");
CreateSubWorkers();
}
;
AuthWorker.OnAuthFailed += () => {
Trace("Auth failed, components disabled");
DestroySubWorkers();
}
;
AuthWorker.CycleAuthUpdate();
}
public void SetupHeaders() {
_ApiHeaders = new[] {
("x-plugin-auth", _MetaInfo?.Value ?? ""), ("x-plugin-version", _RustApp.Version.ToString()), ("x-plugin-port", ConVar.Server.port.ToString()), ("Content-Type", "application/json")
}
;
}
private void CreateSubWorkers() {
ChildObjectToWorkers = this.gameObject.CreateChild();
StateWorker = ChildObjectToWorkers.AddComponent<StateWorker>();
CheckWorker = ChildObjectToWorkers.AddComponent<CheckWorker>();
QueueWorker = ChildObjectToWorkers.AddComponent<QueueWorker>();
ChatWorker = ChildObjectToWorkers.AddComponent<ChatWorker>();
BanWorker = ChildObjectToWorkers.AddComponent<BanWorker>();
ReportWorker = ChildObjectToWorkers.AddComponent<ReportWorker>();
PlayerAlertsWorker = ChildObjectToWorkers.AddComponent<PlayerAlertsWorker>();
SignageWorker = ChildObjectToWorkers.AddComponent<SignageWorker>();
KillsWorker = ChildObjectToWorkers.AddComponent<KillsWorker>();
PlayerMuteWorker = ChildObjectToWorkers.AddComponent<PlayerMuteWorker>();
SleepingBagWorker = ChildObjectToWorkers.AddComponent<SleepingBagWorker>();
}
private void DestroySubWorkers() {
if (ChildObjectToWorkers == null) {
return;
}
UnityEngine.Object.Destroy(ChildObjectToWorkers);
}
}
private class AuthWorker : RustAppWorker {
public bool? IsAuthed;
public Action? OnAuthSuccess;
public Action? OnAuthFailed;
public void CycleAuthUpdate() {
InvokeRepeating(nameof(CheckAuthStatus), 0f, 5f);
}
public void CheckAuthStatus() {
Action onError = () => {
if (IsAuthed == false) {
return;
}
IsAuthed = false;
OnAuthFailed?.Invoke();
}
;
Action onSuccess = () => {
if (IsAuthed == true) {
return;
}
Log("Connection to the service established");
IsAuthed = true;
OnAuthSuccess?.Invoke();
if (_TempWipeMarker) {
_TempWipeMarker = false;
CourtApi.SendWipe().Execute();
}
}
;
CourtApi.GetServerInfo().Execute(() => {
onSuccess();
return;
}
, (err) => {
// secret = ""
var codeError1 = Api.ErrorContains(err, "some of required headers are wrong or missing");
// secret = "123"
var codeError2 = Api.ErrorContains(err, "authorization secret is corrupted");
// server.ip != this.ip || server.port != this.port
var codeError3 = Api.ErrorContains(err, "Check server configuration, required");
if (codeError1 || codeError2 || codeError3) {
if (IsAuthed != false) {
Error("Your server is not paired with our network, follow instructions to pair server:");
Error("1. If you already start pairing, enter 'ra.pair %code%' which you get from our site");
Error("2. Open servers page, press 'connect server', and enter command which you get on it");
}
onError();
return;
}
// version < minVersion
var versionError1 = Api.ErrorContains(err, " is lower than minimal: ");
// if we block some version
var versionError2 = Api.ErrorContains(err, "This version contains serious bug, please update plugin");
if (versionError1 || versionError2) {
Error("Your plugin is outdated, you should download new version!");
Error("1. Open servers page, press 'update' near server to download new version, then just replace plugin");
Error("2. If you don't have 'update' button, press settings icon and choose 'download plugin' button");
onError();
return;
}
// if tariff finished/balance zero
var paymentError1 = Api.ErrorContains(err, "У вас кончились средства на балансе проекта, пополните на");
// If some limits broken
var paymentError2 = Api.ErrorContains(err, "Вы превысили лимиты по");
if (paymentError1) {
Error("Your balance is not enough to continue working with our service, top-up it");
onError();
return;
}
if (paymentError2) {
Error("You have reached your limits, please upgrade your plan");
onError();
return;
}
Debug($"Unknown exception in auth: {err}");
}
);
}
}
private class PairWorker : RustAppWorker {
private string EnteredCode;
public void StartPairing(string code) {
EnteredCode = code;
CourtApi.SendPairDetails(EnteredCode).Execute(() => {
InvokeRepeating(nameof(WaitPairFinish), 0f, 1f);
}
, (err) => {
if (Api.ErrorContains(err, "code not exists")) {
Error("Pairing failed: requested code not exists");
}
else if (Api.ErrorContains(err, "pairing prevented from abuse")) {
Error("Pairing failed: seems this server was already connected to another project, please contact TG: @rustapp_help if you think, that it is wrong");
}
else {
Debug($"Pairing failed: unknown exception {err}");
}
Destroy(this);
}
);
}
public void WaitPairFinish() {
CourtApi.SendPairDetails(EnteredCode).Execute((data) => {
if (data?.token == null || data.token.Length == 0) {
Log("Complete pairing on site...");
return;
}
Action saveData = () => {
MetaInfo.write(new MetaInfo {
Value = data.token
}
);
_RustApp.timer.Once(1f, () => _RustAppEngine?.AuthWorker?.CheckAuthStatus());
_MetaInfo = MetaInfo.Read();
_RustAppEngine.SetupHeaders();
Log("Pairing completed, reloading...");
Destroy(this);
}
;
if (_RustAppEngine?.StateWorker != null) {
_RustAppEngine?.StateWorker?.SendUpdate(() => saveData());
}
else {
saveData();
}
}
, (err) => {
if (Api.ErrorContains(err, "code not exists")) {
Error("Pairing failed: seems you closed modal on site");
}
else {
Error($"Pairing failed: unknown exception {err}");
}
Destroy(this);
}
);
}
}
private class StateWorker : RustAppWorker {
public Dictionary<string, string> DisconnectReasons = new Dictionary<string, string>();
public Dictionary<string, string> TeamChanges = new Dictionary<string, string>();
private void Awake() {
base.Awake();
InvokeRepeating(nameof(CycleSendUpdate), 0f, 5f);
}
public void CycleSendUpdate() {
SendUpdate();
}
public void SendUpdate(Action? onFinished = null) {
var players = Pool.Get<List<CourtApi.PluginStatePlayerDto>>();
CollectPlayers(players);
var disconnected = Pool.Get<Dictionary<string, string>>();
ResurrectDictionary(DisconnectReasons, disconnected);
var teamChanges = Pool.Get<Dictionary<string, string>>();
ResurrectDictionary(TeamChanges, teamChanges);
DisconnectReasons.Clear();
TeamChanges.Clear();
CourtApi.SendStateUpdate(new CourtApi.PluginStateUpdatePayload {
players = players, disconnected = disconnected, team_changes = teamChanges
}
).Execute(() => {
Pool.FreeUnmanaged(ref disconnected);
Pool.FreeUnmanaged(ref teamChanges);
Trace("State was sent successfull");
onFinished?.Invoke();
}
, (err) => {
onFinished?.Invoke();
Debug($"State sent error: {err}");
ResurrectDictionary(disconnected, DisconnectReasons);
Pool.FreeUnmanaged(ref disconnected);
ResurrectDictionary(teamChanges, TeamChanges);
Pool.FreeUnmanaged(ref teamChanges);
}
);
Pool.FreeUnmanaged(ref players);
}
private void CollectPlayers(List<CourtApi.PluginStatePlayerDto> playerStateDtos) {
foreach (var player in BasePlayer.activePlayerList) {
try {
playerStateDtos.Add(CourtApi.PluginStatePlayerDto.FromPlayer(player));
}
catch {
}
}
foreach (var connection in ServerMgr.Instance.connectionQueue.joining) {
try {
playerStateDtos.Add(CourtApi.PluginStatePlayerDto.FromConnection(connection, "joining"));
}
catch {
}
}
foreach (var connection in ServerMgr.Instance.connectionQueue.queue) {
if (connection == null) {
continue;
}
try {
playerStateDtos.Add(CourtApi.PluginStatePlayerDto.FromConnection(connection, "queued"));
}
catch {
}
}
}
private void CollectFakeDisconnects(Dictionary<string, string> disconnect) {
foreach (var player in BasePlayer.activePlayerList) {
try {
disconnect.Add(player.UserIDString, "plugin-unload");
}
catch {
}
}
foreach (var connection in ServerMgr.Instance.connectionQueue.joining) {
try {
disconnect.Add(connection.userid.ToString(), "plugin-unload");
}
catch {
}
}
foreach (var connection in ServerMgr.Instance.connectionQueue.queue) {
if (connection == null) {
continue;
}
try {
disconnect.Add(connection.userid.ToString(), "plugin-unload");
}
catch {
}
}
}
public void OnDestroy() {
base.OnDestroy();
}
}
private class CheckWorker : RustAppWorker {
private Dictionary<string, bool> ShowedNoticyCache = new Dictionary<string, bool>();
public bool IsNoticeActive(string steamId) {
if (!ShowedNoticyCache.TryGetValue(steamId, out var value)) {
return false;
}
return value;
}
public void SetNoticeActive(string steamId, bool value) {
BasePlayer player = null;
if (ulong.TryParse(steamId, out var userid)) {
player = BasePlayer.FindByID(userid);
}
if (player != null) {
// TODO: Deprecated
if (value) {
Interface.Oxide.CallHook("RustApp_OnCheckNoticeShowed", player);
}
else {
Interface.Oxide.CallHook("RustApp_OnCheckNoticeHidden", player);
}
}
// TODO: New hook
Interface.Oxide.CallHook("RustApp_CheckNoticeState", steamId, value);
ShowedNoticyCache[steamId] = value;
if (player != null) {
if (value) {
_RustApp.DrawNoticeInterface(player);
}
else {
CuiHelper.DestroyUi(player, CheckLayer);
}
}
}
public void OnDestroy() {
base.OnDestroy();
foreach (var check in ShowedNoticyCache.ToList()) {
if (check.Value == false) {
continue;
}
SetNoticeActive(check.Key, false);
}
}
}
private class QueueWorker : RustAppWorker {
private List<string> QueueProcessedIds = new List<string>();
private void Awake() {
base.Awake();
InvokeRepeating(nameof(GetQueueTasks), 0f, 1f);
}
private void GetQueueTasks() {
QueueApi.GetQueueTasks().Execute(CallQueueTasks, (error) => {
Debug($"Queue retreive failed {error}");
}
);
}
private void CallQueueTasks(List<QueueApi.QueueTaskResponse> queuesTasks) {
if (queuesTasks == null || queuesTasks.Count == 0) {
return;
}
Dictionary<string, object> queueResponses = new Dictionary<string, object>();
foreach (var task in queuesTasks) {
if (QueueProcessedIds.Contains(task.id)) {
Debug($"This task was already processed: {task.id}");
return;
}
try {
Trace($"Calling: {ConvertToRustAppQueueFormat(task.request.name, true)}");
// To get our official response
var response = (object)_RustApp.Call(ConvertToRustAppQueueFormat(task.request.name, true), task.request.data);
QueueProcessedIds.Add(task.id);
queueResponses.Add(task.id, response);
// Just to broadcast event RustApp_Queue%name%
Interface.Oxide.CallHook(ConvertToRustAppQueueFormat(task.request.name, false), task.request.data);
}
catch (Exception exc) {
Error($"Failed to process task {task.id} {task.request.name}: {exc.ToString()}");
queueResponses.Add(task.id, null);
}
}
ProcessQueueTasks(queueResponses);
}
private void ProcessQueueTasks(Dictionary<string, object> queueResponses) {
if (queueResponses.Keys.Count == 0) {
return;
}
QueueApi.ProcessQueueTasks(new QueueApi.QueueTaskResponsePayload {
data = queueResponses
}
).Execute(() => {
QueueProcessedIds.Clear();
Trace("Ответ по очередям успешно доставлен");
}
, (err) => {
QueueProcessedIds.Clear();
Debug($"Failed to process queue: {err}");
}
);
}
private string ConvertToRustAppQueueFormat(string input, bool isInternalCall) {
var words = input.Replace("court/", "").Split('-');
for (int i = 0; i < words.Length; i++) {
words[i] = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(words[i]);
}
return $"RustApp_{(isInternalCall ? "Internal" : "")}Queue_" + string.Join("", words);
}
}
private class BanWorker : RustAppWorker {
private List<BanApi.BanGetBatchEntryPayloadDto> BanUpdateQueue = new List<BanApi.BanGetBatchEntryPayloadDto>();
// ... [Code preview truncated due to length. Full source in attached file] ...