C#:
// -----------------------------------------------------------------------
// Copyright (c) Yac Vaguer. All rights reserved.
// Author: Yac Vaguer
// This is proprietary software. Unauthorized copying, distribution, or modification is strictly prohibited.
// Licensed for use only on servers owned by the purchaser.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("UAVSignal", "Yac Vaguer", "1.8.8")]
[Description("Call a UAV to detect nearby players and NPCs.")]
public class UAVSignal : RustPlugin
{
#region Fields
private const string PermissionUse = "uavsignal.use";
private const string PermissionAdmin = "uavsignal.admin";
private const string SupplySignalShortname = "supply.signal";
private const string F15Prefab = "assets/scripts/entity/misc/f15/f15e.prefab";
private const string TrackedPanelName = "UAVTrackedPanel";
private const string AdminPanelName = "UAVAdminPanel";
private const string AdminContentName = "UAVAdminContent";
private const string TrackedIconImageKey = "uavsignal.tracked.icon";
[PluginReference]
private Plugin ImageLibrary;
private Configuration config;
private int supplySignalItemId;
private bool trackedIconLoaded;
private readonly Dictionary<ulong, UavSession> activeSessions = new Dictionary<ulong, UavSession>();
private readonly HashSet<BasePlayer> playersWithUI = new HashSet<BasePlayer>();
private readonly HashSet<LootContainer> processedContainers = new HashSet<LootContainer>();
private readonly HashSet<ulong> openAdminPanels = new HashSet<ulong>();
private static readonly (string Name, string Description, string Url)[] OtherPlugins =
{
("Business Core", "VIP Assassination Mission system with full business management.", "https://codefling.com/plugins/business-core-vip-assassination-mission"),
("Water Treatment Showdown Event", "PvE/PvP combat event at the Water Treatment monument.", "https://codefling.com/plugins/water-treatment-showdown-event"),
("Ferry Terminal Event", "Action-packed event at the Ferry Terminal monument.", "https://codefling.com/plugins/ferry-terminal-event-1"),
("Simple Patrol Signal", "Call in a patrol of attack helicopters with a thrown signal.", "https://codefling.com/plugins/simplepatrolsignal"),
};
private const string YacVaguerProfileUrl = "https://codefling.com/yac-vaguer";
private static readonly FieldInfo F15MovePositionField = typeof(F15).GetField(
"movePosition",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
);
private class UavSession
{
public BasePlayer Caller;
public readonly HashSet<BasePlayer> PlayersInRadius = new HashSet<BasePlayer>();
public readonly HashSet<ulong> LoggedDetections = new HashSet<ulong>();
public readonly HashSet<ulong> LoggedSkips = new HashSet<ulong>();
public Timer TickTimer;
}
#endregion
#region Initialization
private void Init()
{
permission.RegisterPermission(PermissionUse, this);
permission.RegisterPermission(PermissionAdmin, this);
LoadDefaultMessages();
}
private void OnServerInitialized()
{
LoadConfigValues();
var def = ItemManager.FindItemDefinition(SupplySignalShortname);
supplySignalItemId = def != null ? def.itemid : 0;
RegisterTrackedIconWithImageLibrary();
}
private void OnPluginLoaded(Plugin plugin)
{
if (plugin?.Name == "ImageLibrary")
{
RegisterTrackedIconWithImageLibrary();
}
}
private void RegisterTrackedIconWithImageLibrary()
{
trackedIconLoaded = false;
if (ImageLibrary == null) return;
if (config?.UAV == null || string.IsNullOrEmpty(config.UAV.TrackedIconUrl)) return;
ImageLibrary.Call(
"AddImage",
config.UAV.TrackedIconUrl,
TrackedIconImageKey,
0UL,
new Action(() => { trackedIconLoaded = true; })
);
}
private void Unload()
{
foreach (var session in activeSessions.Values.ToList())
{
DestroyUAV(session, isUnloading: true);
}
activeSessions.Clear();
foreach (var player in playersWithUI.ToList())
{
HideTrackedIcon(player);
}
processedContainers.Clear();
foreach (var id in openAdminPanels.ToList())
{
var p = BasePlayer.FindByID(id);
if (p != null && p.IsConnected)
{
CuiHelper.DestroyUi(p, AdminPanelName);
}
}
openAdminPanels.Clear();
}
#endregion
#region Commands
[ChatCommand("uav")]
private void UAVCommand(BasePlayer player, string command, string[] args)
{
if (!HasAdminPermission(player))
{
player.ChatMessage(GetMessage("NoPermission", player.UserIDString));
return;
}
if (args.Length == 1)
{
List<string> ambiguous;
var targetPlayer = FindPlayer(args[0], out ambiguous);
if (targetPlayer != null)
{
GiveUAVSignal(targetPlayer);
player.ChatMessage(
string.Format(
GetMessage("UAVGiven", player.UserIDString),
targetPlayer.displayName
)
);
}
else if (ambiguous != null)
{
player.ChatMessage(
string.Format(
GetMessage("AmbiguousPlayer", player.UserIDString),
string.Join(", ", ambiguous)
)
);
}
else
{
player.ChatMessage(
string.Format(GetMessage("PlayerNotFound", player.UserIDString), args[0])
);
}
}
else
{
GiveUAVSignal(player);
player.ChatMessage(GetMessage("UAVReceived", player.UserIDString));
}
}
[ConsoleCommand("uav")]
private void UAVConsoleCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !HasAdminPermission(player))
{
arg.ReplyWith(GetMessage("NoPermission", player.UserIDString));
return;
}
if (arg.Args == null || arg.Args.Length == 0)
{
arg.ReplyWith("Usage: uav <player name or ID>");
return;
}
List<string> ambiguous;
string targetArg = arg.Args[0].ToString();
var targetPlayer = FindPlayer(targetArg, out ambiguous);
if (targetPlayer != null)
{
GiveUAVSignal(targetPlayer);
arg.ReplyWith($"UAV signal given to {targetPlayer.displayName}");
}
else if (ambiguous != null)
{
arg.ReplyWith($"Ambiguous match for '{targetArg}': {string.Join(", ", ambiguous)}");
}
else
{
arg.ReplyWith($"Player '{targetArg}' not found.");
}
}
[ChatCommand("uav.admin")]
private void UAVAdminChatCommand(BasePlayer player, string command, string[] args)
{
if (!HasAdminPermission(player))
{
player.ChatMessage(GetMessage("NoPermission", player.UserIDString));
return;
}
OpenAdminPanel(player, "info");
}
[ConsoleCommand("uavadmin.tab")]
private void CmdAdminTab(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasAdminPermission(player)) return;
OpenAdminPanel(player, arg.GetString(0, "info"));
}
[ConsoleCommand("uavadmin.toggle")]
private void CmdAdminToggle(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasAdminPermission(player)) return;
switch (arg.GetString(0))
{
case "debug": config.DebugMode = !config.DebugMode; break;
case "loot": config.Loot.Enabled = !config.Loot.Enabled; break;
case "teammates": config.Tracking.Teammates.Enabled = !config.Tracking.Teammates.Enabled; break;
case "npcs": config.Tracking.NPCs.Enabled = !config.Tracking.NPCs.Enabled; break;
case "otherplayers": config.Tracking.OtherPlayers.Enabled = !config.Tracking.OtherPlayers.Enabled; break;
default: return;
}
SaveConfig();
OpenAdminPanel(player, "info");
}
[ConsoleCommand("uavadmin.reload")]
private void CmdAdminReload(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasAdminPermission(player)) return;
LoadConfigValues();
player.ChatMessage(GetMessage("AdminConfigReloaded", player.UserIDString));
OpenAdminPanel(player, "info");
}
[ConsoleCommand("uavadmin.close")]
private void CmdAdminClose(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
CloseAdminPanel(player);
}
[ConsoleCommand("uavadmin.link")]
private void CmdAdminLink(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasAdminPermission(player)) return;
int idx = arg.GetInt(0, -1);
if (idx < 0 || idx >= OtherPlugins.Length) return;
var p = OtherPlugins[idx];
player.ChatMessage(string.Format(GetMessage("AdminPluginLink", player.UserIDString), p.Name, p.Url));
}
[ConsoleCommand("uavadmin.profile")]
private void CmdAdminProfile(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null || !HasAdminPermission(player)) return;
player.ChatMessage(string.Format(GetMessage("AdminProfileLink", player.UserIDString), YacVaguerProfileUrl));
}
#endregion
#region Event Hooks
private void OnExplosiveThrown(BasePlayer player, BaseEntity entity)
{
if (config == null) return;
if (entity is SupplySignal signal && signal.skinID == config.UAV.SkinID)
{
ThrowUAV(signal, player);
}
}
private void OnExplosiveDropped(BasePlayer player, BaseEntity entity, ThrownWeapon item)
{
if (config == null) return;
if (entity is SupplySignal signal && signal.skinID == config.UAV.SkinID)
{
ThrowUAV(signal, player);
}
}
private void ThrowUAV(SupplySignal signal, BasePlayer player)
{
if (signal == null || signal.IsDestroyed) return;
if (player == null) return;
signal.CancelInvoke(signal.Explode);
signal.CancelInvoke(signal.KillMessage);
signal.Kill();
if (!HasPermission(player))
{
GiveUAVSignal(player);
player.ChatMessage(GetMessage("NoUsePermission", player.UserIDString));
return;
}
StartUAV(player);
}
private void OnEntityDeath(BaseCombatEntity entity, HitInfo info)
{
var player = entity as BasePlayer;
if (player == null) return;
if (playersWithUI.Contains(player))
{
HideTrackedIcon(player);
}
UavSession session;
if (activeSessions.TryGetValue(player.userID.Get(), out session))
{
DestroyUAV(session);
}
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null) return;
if (playersWithUI.Contains(player))
{
HideTrackedIcon(player);
}
UavSession session;
if (activeSessions.TryGetValue(player.userID.Get(), out session))
{
DestroyUAV(session);
}
openAdminPanels.Remove(player.userID.Get());
}
private object CanLootEntity(BasePlayer player, LootContainer container)
{
if (container == null || config == null || !config.Loot.Enabled)
return null;
if (!processedContainers.Add(container))
return null;
string containerName = container.ShortPrefabName;
float dropChance;
if (!config.Loot.Containers.TryGetValue(containerName, out dropChance))
return null;
if (!RollChance(dropChance))
return null;
if (container.inventory == null)
return null;
if (container.inventory.itemList.Count >= container.inventory.capacity)
return null;
var item = ItemManager.CreateByName(SupplySignalShortname, 1, config.UAV.SkinID);
if (item != null)
{
item.name = config.UAV.ItemName;
if (!item.MoveToContainer(container.inventory))
{
item.Remove();
}
}
return null;
}
private void OnEntityKill(LootContainer container)
{
if (container != null)
{
processedContainers.Remove(container);
}
}
#endregion
#region UAV Logic
private void StartUAV(BasePlayer player)
{
if (player == null) return;
ulong id = player.userID.Get();
if (activeSessions.ContainsKey(id))
{
player.ChatMessage(GetMessage("UAVAlreadyActive", player.UserIDString));
GiveUAVSignal(player);
return;
}
var session = new UavSession { Caller = player };
activeSessions[id] = session;
SpawnJet(session);
player.ChatMessage(GetMessage("UAVCalled", player.UserIDString));
DebugLog(
$"UAV started by {player.displayName} at position {player.transform.position}"
);
}
private void SpawnJet(UavSession session)
{
var player = session.Caller;
Vector3 targetPosition = player.transform.position;
Vector3 spawnPosition =
targetPosition + player.transform.forward.normalized * config.Jet.SpawnDistance;
var jet = GameManager.server.CreateEntity(F15Prefab, spawnPosition) as F15;
if (jet == null)
{
PrintError("Failed to create F15 entity.");
activeSessions.Remove(player.userID.Get());
return;
}
jet.transform.position = spawnPosition;
jet.transform.forward = (targetPosition - spawnPosition).normalized;
if (F15MovePositionField != null)
{
F15MovePositionField.SetValue(jet, targetPosition);
}
else
{
PrintWarning("F15.movePosition field not found; jet may not move to target.");
}
jet.defaultAltitude = config.Jet.Altitude;
jet.Spawn();
timer.Once(config.Jet.Duration, () =>
{
if (jet != null && !jet.IsDestroyed)
jet.Kill();
});
ulong callerId = player.userID.Get();
timer.Once(config.UAV.Warmup, () =>
{
if (activeSessions.ContainsKey(callerId))
ActivateUAV(session);
});
DebugLog("Jet spawned and moving to target position");
}
private void ActivateUAV(UavSession session)
{
var player = session.Caller;
if (player == null || !player.IsConnected)
{
DestroyUAV(session);
return;
}
player.ChatMessage(GetMessage("UAVActive", player.UserIDString));
TickSession(session);
session.TickTimer = timer.Every(3f, () => TickSession(session));
timer.Once(config.UAV.Duration, () => DestroyUAV(session));
DebugLog("UAV activated and tick timer started");
}
private void TickSession(UavSession session)
{
var caller = session.Caller;
if (caller == null || !caller.IsConnected)
{
DestroyUAV(session);
return;
}
UpdatePlayersInRadius(session);
MarkPlayers(session);
}
private void UpdatePlayersInRadius(UavSession session)
{
var caller = session.Caller;
var scratch = Pool.Get<List<BasePlayer>>();
Vis.Entities<BasePlayer>(
caller.transform.position,
config.UAV.Radius,
scratch,
Rust.Layers.Mask.Player_Server
);
var previous = Pool.Get<List<BasePlayer>>();
foreach (var p in session.PlayersInRadius)
{
previous.Add(p);
}
session.PlayersInRadius.Clear();
for (int i = 0; i < scratch.Count; i++)
{
var p = scratch[i];
if (p == null || p == caller) continue;
if (p.userID.Get().IsSteamId() && !p.IsConnected) continue;
var category = ClassifyTarget(caller, p);
var setting = GetCategorySetting(category);
if (!setting.Enabled)
{
if (session.LoggedSkips.Add(p.userID.Get()))
{
DebugLog(
$"Skipped {category} '{(p.IsNpc ? p.ShortPrefabName : p.displayName)}' ({category} tracking disabled)"
);
}
continue;
}
session.PlayersInRadius.Add(p);
if (session.LoggedDetections.Add(p.userID.Get()))
{
if (p.IsNpc)
{
DebugLog(
$"UAV has detected NPC '{p.ShortPrefabName}' at position {p.transform.position}"
);
}
else
{
DebugLog(
$"UAV has detected player '{p.displayName}' (ID: {p.UserIDString}) at position {p.transform.position}"
);
}
}
if (!playersWithUI.Contains(p))
{
ShowTrackedIcon(p);
}
}
for (int i = 0; i < previous.Count; i++)
{
var p = previous[i];
if (p != null && !session.PlayersInRadius.Contains(p))
{
DebugLog(
$"Stopped tracking {(p.IsNpc ? "NPC" : "Player")} '{p.displayName}'"
);
}
}
foreach (var p in playersWithUI.ToList())
{
if (!IsTrackedByAnySession(p))
{
HideTrackedIcon(p);
}
}
Pool.FreeUnmanaged(ref scratch);
Pool.FreeUnmanaged(ref previous);
}
private bool IsTrackedByAnySession(BasePlayer player)
{
foreach (var session in activeSessions.Values)
{
if (session.PlayersInRadius.Contains(player))
return true;
}
return false;
}
private void MarkPlayers(UavSession session)
{
var player = session.Caller;
if (player == null || !player.IsConnected)
return;
foreach (var target in session.PlayersInRadius)
{
if (target == null || target.IsDead())
{
DebugLog("Target is null or dead");
continue;
}
var category = ClassifyTarget(player, target);
var type = GetCategorySetting(category).ParsedPingType;
Vector3 pingPosition = target.transform.position + Vector3.up * 2f;
player.AddPingAtLocation(type, pingPosition, 3f, target.net.ID);
DebugLog(
$"Added ping for {category} '{target.displayName}' type {type} at position {pingPosition}"
);
}
}
private void DestroyUAV(UavSession session, bool isUnloading = false)
{
if (session == null) return;
if (session.TickTimer != null && !session.TickTimer.Destroyed)
{
session.TickTimer.Destroy();
}
session.PlayersInRadius.Clear();
session.LoggedDetections.Clear();
session.LoggedSkips.Clear();
var caller = session.Caller;
if (caller != null)
{
activeSessions.Remove(caller.userID.Get());
}
foreach (var p in playersWithUI.ToList())
{
if (!IsTrackedByAnySession(p))
{
HideTrackedIcon(p);
}
}
if (caller != null)
{
if (!isUnloading && caller.IsConnected)
{
caller.ChatMessage(GetMessage("UAVEnded", caller.UserIDString));
}
DebugLog($"UAV ended for {caller.displayName}");
}
}
private void ShowTrackedIcon(BasePlayer player)
{
if (player == null || !player.IsConnected)
return;
if (playersWithUI.Contains(player))
return;
CuiElementContainer elements = new CuiElementContainer();
elements.Add(
new CuiPanel
{
Image = { Color = config.UAV.PanelColor },
RectTransform =
{
AnchorMin = config.UAV.TrackedCUIAnchorMin,
AnchorMax = config.UAV.TrackedCUIAnchorMax,
},
CursorEnabled = false,
},
"Hud",
TrackedPanelName
);
elements.Add(
new CuiLabel
{
Text =
{
Color = config.UAV.TextColor,
FontSize = config.UAV.TextFontSize,
Align = TextAnchor.MiddleLeft,
Text = GetMessage("BeingTracked", player.UserIDString),
},
RectTransform = { AnchorMin = "0.21 0.1", AnchorMax = "0.98 0.9" },
},
TrackedPanelName
);
var iconComponent = new CuiRawImageComponent { Color = "1 1 1 1" };
string cachedPng = null;
if (trackedIconLoaded && ImageLibrary != null)
{
cachedPng = ImageLibrary.Call("GetImage", TrackedIconImageKey, 0UL, false) as string;
}
if (!string.IsNullOrEmpty(cachedPng))
{
iconComponent.Png = cachedPng;
}
else
{
iconComponent.Url = config.UAV.TrackedIconUrl;
}
elements.Add(
new CuiElement
{
Parent = TrackedPanelName,
Components =
{
iconComponent,
new CuiRectTransformComponent { AnchorMin = "0.02 0.12", AnchorMax = "0.20 0.88" },
},
}
);
CuiHelper.AddUi(player, elements);
playersWithUI.Add(player);
}
private void HideTrackedIcon(BasePlayer player)
{
if (player == null)
return;
if (!playersWithUI.Remove(player))
return;
if (player.IsConnected)
{
CuiHelper.DestroyUi(player, TrackedPanelName);
}
}
#endregion
#region Helpers
private bool HasPermission(BasePlayer player)
{
return permission.UserHasPermission(player.UserIDString, PermissionUse);
}
private bool HasAdminPermission(BasePlayer player)
{
return permission.UserHasPermission(player.UserIDString, PermissionAdmin);
}
private BasePlayer FindPlayer(string nameOrId, out List<string> ambiguousMatches)
{
ambiguousMatches = null;
foreach (var p in BasePlayer.activePlayerList)
{
if (p.UserIDString == nameOrId)
return p;
}
foreach (var p in BasePlayer.activePlayerList)
{
if (string.Equals(p.displayName, nameOrId, StringComparison.OrdinalIgnoreCase))
return p;
}
var partial = BasePlayer
.activePlayerList.Where(p =>
p.displayName.IndexOf(nameOrId, StringComparison.OrdinalIgnoreCase) >= 0
)
.ToList();
if (partial.Count == 1)
return partial[0];
if (partial.Count > 1)
{
ambiguousMatches = partial.Select(p => p.displayName).ToList();
}
return null;
}
private void GiveUAVSignal(BasePlayer player)
{
var item = ItemManager.CreateByName(SupplySignalShortname, 1, config.UAV.SkinID);
if (item != null)
{
item.name = config.UAV.ItemName;
player.GiveItem(item);
DebugLog($"Gave UAV signal to {player.displayName}");
}
}
private bool RollChance(float chance)
{
return UnityEngine.Random.Range(0f, 100f) <= chance;
}
private TargetCategory ClassifyTarget(BasePlayer caller, BasePlayer target)
{
if (caller.Team != null && caller.Team.members.Contains(target.userID))
return TargetCategory.Teammate;
if (!target.userID.Get().IsSteamId())
return TargetCategory.NPC;
return TargetCategory.OtherPlayer;
}
private CategorySetting GetCategorySetting(TargetCategory category)
{
switch (category)
{
case TargetCategory.Teammate: return config.Tracking.Teammates;
case TargetCategory.NPC: return config.Tracking.NPCs;
default: return config.Tracking.OtherPlayers;
}
}
private void DebugLog(string message)
{
if (config != null && config.DebugMode)
{
Puts(message);
}
}
#endregion
#region Admin Panel
private void OpenAdminPanel(BasePlayer player, string tab)
{
if (player == null || !player.IsConnected) return;
CuiHelper.DestroyUi(player, AdminPanelName);
var container = new CuiElementContainer();
container.Add(new CuiPanel
{
Image = { Color = "0.1 0.1 0.1 0.96" },
RectTransform = { AnchorMin = "0.18 0.15", AnchorMax = "0.82 0.85" },
CursorEnabled = true,
}, "Overlay", AdminPanelName);
container.Add(new CuiLabel
{
Text = { Text = $"UAV Signal, Admin Panel v{Version}", FontSize = 18, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.02 0.92", AnchorMax = "0.85 0.99" },
}, AdminPanelName);
container.Add(new CuiButton
{
Button = { Color = "0.7 0.2 0.2 1", Command = "uavadmin.close" },
RectTransform = { AnchorMin = "0.95 0.93", AnchorMax = "0.99 0.99" },
Text = { Text = "X", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, AdminPanelName);
bool isInfo = tab != "plugins";
container.Add(new CuiButton
{
Button = { Color = isInfo ? "0.4 0.6 0.4 1" : "0.3 0.3 0.3 1", Command = "uavadmin.tab info" },
RectTransform = { AnchorMin = "0.02 0.84", AnchorMax = "0.25 0.91" },
Text = { Text = "Info & Config", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, AdminPanelName);
container.Add(new CuiButton
{
Button = { Color = !isInfo ? "0.4 0.6 0.4 1" : "0.3 0.3 0.3 1", Command = "uavadmin.tab plugins" },
RectTransform = { AnchorMin = "0.26 0.84", AnchorMax = "0.49 0.91" },
Text = { Text = "More Plugins", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, AdminPanelName);
container.Add(new CuiPanel
{
Image = { Color = "0.05 0.05 0.05 0.92" },
RectTransform = { AnchorMin = "0.02 0.02", AnchorMax = "0.98 0.83" },
}, AdminPanelName, AdminContentName);
if (isInfo)
AddInfoTabContent(container);
else
AddPluginsTabContent(container);
CuiHelper.AddUi(player, container);
openAdminPanels.Add(player.userID.Get());
}
private void AddInfoTabContent(CuiElementContainer container)
{
int trackedCount = 0;
foreach (var s in activeSessions.Values)
trackedCount += s.PlayersInRadius.Count;
string info =
$"Active UAV sessions: {activeSessions.Count}\n" +
$"Players currently tracked: {trackedCount}\n" +
$"Tracked icons shown: {playersWithUI.Count}\n" +
$"Loot drops: {(config.Loot.Enabled ? "ON" : "OFF")}\n" +
$"Debug mode: {(config.DebugMode ? "ON" : "OFF")}";
container.Add(new CuiLabel
{
Text = { Text = "<b>Status</b>", FontSize = 14, Align = TextAnchor.UpperLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.02 0.92", AnchorMax = "0.5 0.99" },
}, AdminContentName);
container.Add(new CuiLabel
{
Text = { Text = info, FontSize = 13, Align = TextAnchor.UpperLeft, Color = "0.9 0.9 0.9 1" },
RectTransform = { AnchorMin = "0.02 0.74", AnchorMax = "0.5 0.91" },
}, AdminContentName);
container.Add(new CuiLabel
{
Text = { Text = "<b>Settings (read-only)</b>", FontSize = 13, Align = TextAnchor.UpperLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.02 0.66", AnchorMax = "0.5 0.73" },
}, AdminContentName);
string nums =
$"UAV Duration: {config.UAV.Duration}s\n" +
$"UAV Radius: {config.UAV.Radius}m\n" +
$"UAV Warmup: {config.UAV.Warmup}s\n" +
$"Jet Altitude: {config.Jet.Altitude}m\n" +
$"Jet Duration: {config.Jet.Duration}s";
container.Add(new CuiLabel
{
Text = { Text = nums, FontSize = 12, Align = TextAnchor.UpperLeft, Color = "0.7 0.7 0.7 1" },
RectTransform = { AnchorMin = "0.02 0.46", AnchorMax = "0.5 0.65" },
}, AdminContentName);
container.Add(new CuiLabel
{
Text = { Text = "<b>Active Skin</b>", FontSize = 13, Align = TextAnchor.UpperLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.02 0.40", AnchorMax = "0.5 0.45" },
}, AdminContentName);
container.Add(new CuiElement
{
Parent = AdminContentName,
Components =
{
new CuiImageComponent
{
ItemId = supplySignalItemId,
SkinId = config.UAV.SkinID,
},
new CuiRectTransformComponent { AnchorMin = "0.02 0.02", AnchorMax = "0.22 0.40" },
},
});
container.Add(new CuiLabel
{
Text = { Text = $"Skin ID:\n{config.UAV.SkinID}", FontSize = 13, Align = TextAnchor.MiddleLeft, Color = "0.85 0.85 0.85 1" },
RectTransform = { AnchorMin = "0.24 0.02", AnchorMax = "0.5 0.40" },
}, AdminContentName);
container.Add(new CuiLabel
{
Text = { Text = "<b>Toggles</b>", FontSize = 14, Align = TextAnchor.UpperLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.55 0.88", AnchorMax = "0.98 0.97" },
}, AdminContentName);
AddToggleRow(container, 0, "Debug Mode", config.DebugMode, "uavadmin.toggle debug");
AddToggleRow(container, 1, "Loot Drops", config.Loot.Enabled, "uavadmin.toggle loot");
AddToggleRow(container, 2, "Track Teammates", config.Tracking.Teammates.Enabled, "uavadmin.toggle teammates");
AddToggleRow(container, 3, "Track NPCs", config.Tracking.NPCs.Enabled, "uavadmin.toggle npcs");
AddToggleRow(container, 4, "Track Other Players", config.Tracking.OtherPlayers.Enabled, "uavadmin.toggle otherplayers");
container.Add(new CuiButton
{
Button = { Color = "0.3 0.5 0.7 1", Command = "uavadmin.reload" },
RectTransform = { AnchorMin = "0.55 0.05", AnchorMax = "0.98 0.14" },
Text = { Text = "Reload Config From Disk", FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, AdminContentName);
}
private void AddToggleRow(CuiElementContainer container, int row, string label, bool state, string command)
{
float top = 0.86f - row * 0.12f;
float bottom = top - 0.10f;
container.Add(new CuiLabel
{
Text = { Text = label, FontSize = 13, Align = TextAnchor.MiddleLeft, Color = "0.9 0.9 0.9 1" },
RectTransform =
{
AnchorMin = $"0.55 {bottom.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture)}",
AnchorMax = $"0.85 {top.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture)}",
},
}, AdminContentName);
container.Add(new CuiButton
{
Button = { Color = state ? "0.3 0.6 0.3 1" : "0.6 0.3 0.3 1", Command = command },
RectTransform =
{
AnchorMin = $"0.86 {bottom.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture)}",
AnchorMax = $"0.98 {top.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture)}",
},
Text = { Text = state ? "ON" : "OFF", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, AdminContentName);
}
private void AddPluginsTabContent(CuiElementContainer container)
{
container.Add(new CuiLabel
{
Text = { Text = "More plugins by Yac Vaguer", FontSize = 16, Align = TextAnchor.MiddleLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.02 0.9", AnchorMax = "0.7 0.98" },
}, AdminContentName);
container.Add(new CuiButton
{
Button = { Color = "0.3 0.5 0.7 1", Command = "uavadmin.profile" },
RectTransform = { AnchorMin = "0.72 0.91", AnchorMax = "0.98 0.98" },
Text = { Text = "Open Codefling profile", FontSize = 12, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, AdminContentName);
for (int i = 0; i < OtherPlugins.Length; i++)
{
var p = OtherPlugins[i];
float top = 0.88f - i * 0.21f;
float bottom = top - 0.18f;
string rowName = AdminContentName + ".row" + i;
container.Add(new CuiPanel
{
Image = { Color = "0.15 0.15 0.15 0.85" },
RectTransform =
{
AnchorMin = $"0.02 {bottom.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture)}",
AnchorMax = $"0.98 {top.ToString("0.000", System.Globalization.CultureInfo.InvariantCulture)}",
},
}, AdminContentName, rowName);
container.Add(new CuiLabel
{
Text = { Text = $"<b>{p.Name}</b>", FontSize = 14, Align = TextAnchor.UpperLeft, Color = "1 1 1 1" },
RectTransform = { AnchorMin = "0.02 0.55", AnchorMax = "0.78 0.95" },
}, rowName);
container.Add(new CuiLabel
{
Text = { Text = p.Description, FontSize = 11, Align = TextAnchor.UpperLeft, Color = "0.75 0.75 0.75 1" },
RectTransform = { AnchorMin = "0.02 0.05", AnchorMax = "0.78 0.55" },
}, rowName);
container.Add(new CuiButton
{
Button = { Color = "0.3 0.5 0.7 1", Command = $"uavadmin.link {i}" },
RectTransform = { AnchorMin = "0.8 0.2", AnchorMax = "0.98 0.8" },
Text = { Text = "Get Link", FontSize = 13, Align = TextAnchor.MiddleCenter, Color = "1 1 1 1" },
}, rowName);
}
}
private void CloseAdminPanel(BasePlayer player)
{
if (player == null) return;
CuiHelper.DestroyUi(player, AdminPanelName);
openAdminPanels.Remove(player.userID.Get());
}
#endregion
#region Configuration
private enum TargetCategory
{
Teammate,
NPC,
OtherPlayer,
}
private class Configuration
{
[JsonProperty("UAV Settings")]
public UAVSettings UAV { get; set; } = new UAVSettings();
[JsonProperty("Jet Settings")]
public JetSettings Jet { get; set; } = new JetSettings();
[JsonProperty("Loot Settings")]
public LootSettings Loot { get; set; } = new LootSettings();
[JsonProperty("Tracking Settings")]
public TrackingSettings Tracking { get; set; } = new TrackingSettings();
[JsonProperty("Debug Mode")]
public bool DebugMode { get; set; } = false;
}
private class TrackingSettings
{
[JsonProperty("Teammates")]
public CategorySetting Teammates { get; set; } =
new CategorySetting { Enabled = false, PingType = "Hostile" };
[JsonProperty("NPCs")]
public CategorySetting NPCs { get; set; } =
new CategorySetting { Enabled = true, PingType = "Gun" };
[JsonProperty("Other Players")]
public CategorySetting OtherPlayers { get; set; } =
new CategorySetting { Enabled = true, PingType = "Hostile" };
}
private class CategorySetting
{
[JsonProperty("Enabled")]
public bool Enabled { get; set; }
[JsonProperty("Ping Type")]
public string PingType { get; set; }
[JsonIgnore]
public BasePlayer.PingType ParsedPingType { get; set; }
}
private class UAVSettings
{
[JsonProperty("Duration (seconds)")]
public float Duration { get; set; } = 180f;
[JsonProperty("Radius")]
public float Radius { get; set; } = 40f;
[JsonProperty("Skin ID")]
public ulong SkinID { get; set; } = 3720628271;
[JsonProperty("Warmup Time (seconds)")]
public float Warmup { get; set; } = 5f;
[JsonProperty("Item Name")]
public string ItemName { get; set; } = "UAV Signal";
[JsonProperty("Tracked Icon URL")]
public string TrackedIconUrl { get; set; } =
"https://media.yacvaguer.cc/rust/uav-signal/tracked-icon.png";
[JsonProperty("Tracked Icon Position (AnchorMin)")]
public string TrackedCUIAnchorMin { get; set; } = "0.006 0.485";
[JsonProperty("Tracked Icon Position (AnchorMax)")]
public string TrackedCUIAnchorMax { get; set; } = "0.14 0.525";
[JsonProperty("Panel Color")]
public string PanelColor { get; set; } = "0.18 0.20 0.22 0.65";
[JsonProperty("Text Color")]
public string TextColor { get; set; } = "0.92 0.94 0.96 1";
[JsonProperty("Text Font Size")]
public int TextFontSize { get; set; } = 12;
}
private class JetSettings
{
[JsonProperty("Altitude")]
public float Altitude { get; set; } = 200f;
[JsonProperty("Spawn Distance")]
public float SpawnDistance { get; set; } = 500f;
[JsonProperty("Duration (seconds)")]
public float Duration { get; set; } = 15f;
}
private class LootSettings
{
[JsonProperty("Enable Loot Drops")]
public bool Enabled { get; set; } = true;
[JsonProperty("Loot Containers and Drop Chances")]
public Dictionary<string, float> Containers { get; set; } =
new Dictionary<string, float>
{
{ "crate_normal", 0f },
{ "crate_normal_2", 0f },
{ "crate_elite", 2f },
{ "heli_crate", 5f },
{ "bradley_crate", 5f },
};
}
protected override void LoadDefaultConfig()
{
config = new Configuration();
SaveConfig();
Puts("Default configuration file created.");
}
private void LoadConfigValues()
{
try
{
config = Config.ReadObject<Configuration>();
if (config == null)
{
LoadDefaultConfig();
}
else
{
SaveConfig();
}
}
catch
{
PrintError("Configuration file is corrupt; loading default configuration.");
LoadDefaultConfig();
}
ResolveTrackingPingTypes();
WarnIfAllTrackingDisabled();
}
private void ResolveTrackingPingTypes()
{
if (config?.Tracking == null) return;
ResolvePingType(config.Tracking.Teammates, BasePlayer.PingType.Hostile, "Teammates");
ResolvePingType(config.Tracking.NPCs, BasePlayer.PingType.Gun, "NPCs");
ResolvePingType(config.Tracking.OtherPlayers, BasePlayer.PingType.Hostile, "Other Players");
}
private void ResolvePingType(CategorySetting category, BasePlayer.PingType fallback, string label)
{
if (category == null) return;
BasePlayer.PingType parsed;
if (Enum.TryParse(category.PingType, true, out parsed) && Enum.IsDefined(typeof(BasePlayer.PingType), parsed))
{
category.ParsedPingType = parsed;
return;
}
PrintWarning(
$"Invalid Ping Type '{category.PingType}' for {label}. Falling back to {fallback}. Valid values: {string.Join(", ", Enum.GetNames(typeof(BasePlayer.PingType)))}."
);
category.ParsedPingType = fallback;
}
private void WarnIfAllTrackingDisabled()
{
if (config?.Tracking == null) return;
if (!config.Tracking.Teammates.Enabled
&& !config.Tracking.NPCs.Enabled
&& !config.Tracking.OtherPlayers.Enabled)
{
PrintWarning("All tracking categories disabled, UAV will detect nothing.");
}
}
protected override void SaveConfig()
{
Config.WriteObject(config, true);
}
#endregion
#region Localization
protected override void LoadDefaultMessages()
{
lang.RegisterMessages(
new Dictionary<string, string>
{
["NoPermission"] = "You do not have permission to use this command.",
["PlayerNotFound"] = "Player '{0}' not found.",
["AmbiguousPlayer"] = "Multiple players match that name: {0}",
["UAVGiven"] = "UAV Signal given to {0}.",
["UAVReceived"] = "You have received a UAV Signal.",
["UAVCalled"] = "UAV has been called to your area.",
["UAVAlreadyActive"] = "A UAV is already active.",
["UAVActive"] = "UAV is now active.",
["UAVEnded"] = "UAV has ended.",
["BeingTracked"] = "You are being tracked",
["NoUsePermission"] = "You do not have permission to throw the UAV Signal.",
["AdminConfigReloaded"] = "UAV configuration reloaded from disk.",
["AdminPluginLink"] = "{0}: {1}",
["AdminProfileLink"] = "Yac Vaguer on Codefling: {0}",
},
this
);
}
private string GetMessage(string key, string userId = null)
{
return lang.GetMessage(key, this, userId);
}
#endregion
}
}