C#:
using System;
using System.Collections.Generic;
using Oxide.Game.Rust.Cui;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("SpectateRadar", "YourName", "2.1.0")]
[Description("Жесткая блокировка камеры от 1 лица с возвращением админа на его точку земли")]
class SpectateRadar : RustPlugin
{
private Dictionary<ulong, ulong> adminToTarget = new Dictionary<ulong, ulong>();
private Dictionary<ulong, Vector3> adminOriginalPositions = new Dictionary<ulong, Vector3>();
private const string permSpec = "spectateradar.spec";
void OnServerInitialized()
{
permission.RegisterPermission(permSpec, this);
timer.Every(0.3f, UpdateSpectatorUIs);
}
void Unload()
{
foreach (var adminId in adminToTarget.Keys)
{
BasePlayer admin = BasePlayer.FindByID(adminId);
if (admin != null)
{
CuiHelper.DestroyUi(admin, "SpectateUI");
ResetAdminEntity(admin);
}
}
}
[ChatCommand("spec")]
void CmdSpec(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, permSpec))
{
SendReply(player, "У вас нет прав для использования этой команды.");
return;
}
if (args.Length == 0)
{
if (adminToTarget.ContainsKey(player.userID))
{
StopSpectating(player);
}
else
{
SendReply(player, "Использование: /spec <ник / часть ника / SteamID>");
}
return;
}
string searchArg = string.Join(" ", args);
BasePlayer target = FindTarget(searchArg);
if (target == null)
{
SendReply(player, $"Игрок \"{searchArg}\" не найден или не в сети.");
return;
}
if (target == player)
{
SendReply(player, "Вы не можете следить за самим собой.");
return;
}
StartSpectating(player, target);
}
[ChatCommand("specoff")]
void CmdSpecOff(BasePlayer player, string command, string[] args)
{
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, permSpec))
{
SendReply(player, "У вас нет прав для использования этой команды.");
return;
}
if (adminToTarget.ContainsKey(player.userID))
{
StopSpectating(player);
}
else
{
SendReply(player, "Вы сейчас ни за кем не следите.");
}
}
private void StartSpectating(BasePlayer admin, BasePlayer target)
{
admin.SetParent(null, false, false);
adminOriginalPositions[admin.userID] = admin.transform.position;
admin.spectateFilter = target.displayName;
admin.StartSpectating();
admin.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, true);
admin.SetParent(target, uint.MaxValue, false);
admin.ClientRPCPlayer(null, admin, "ForceSpectateMode", 1);
admin.UpdateNetworkGroup();
adminToTarget[admin.userID] = target.userID;
SendReply(admin, $"Вы начали закрепленную слежку за {target.displayName}. Камера заблокирована.");
}
private void StopSpectating(BasePlayer admin)
{
ResetAdminEntity(admin);
CuiHelper.DestroyUi(admin, "SpectateUI");
adminToTarget.Remove(admin.userID);
SendReply(admin, "Слежка прекращена.");
}
private void ResetAdminEntity(BasePlayer admin)
{
admin.SetParent(null, false, false);
admin.StopSpectating();
admin.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, false);
admin.spectateFilter = string.Empty;
Vector3 returnPos = admin.transform.position;
if (adminOriginalPositions.ContainsKey(admin.userID))
{
returnPos = adminOriginalPositions[admin.userID];
adminOriginalPositions.Remove(admin.userID);
}
if (TerrainMeta.HeightMap != null)
{
float terrainHeight = TerrainMeta.HeightMap.GetHeight(returnPos);
returnPos.y = terrainHeight + 0.5f;
}
else
{
RaycastHit hit;
int layerMask = LayerMask.GetMask("Terrain", "World", "Construction");
if (Physics.Raycast(new Vector3(returnPos.x, 400f, returnPos.z), Vector3.down, out hit, 500f, layerMask))
{
returnPos.y = hit.point.y + 0.5f;
}
}
admin.MovePosition(returnPos);
admin.ClientRPCPlayer(null, admin, "ForcePositionTo", returnPos);
admin.UpdateNetworkGroup();
}
private void UpdateSpectatorUIs()
{
List<ulong> toRemove = new List<ulong>();
foreach (var entry in adminToTarget)
{
BasePlayer admin = BasePlayer.FindByID(entry.Key);
if (admin == null || !admin.IsConnected) continue;
if (!admin.IsSpectating())
{
toRemove.Add(entry.Key);
continue;
}
BasePlayer target = BasePlayer.FindByID(entry.Value);
if (target == null || !target.IsConnected || !target.IsAlive())
{
toRemove.Add(entry.Key);
continue;
}
DrawSpectateUI(admin, target);
}
foreach (var adminId in toRemove)
{
BasePlayer admin = BasePlayer.FindByID(adminId);
if (admin != null) ResetAdminEntity(admin);
}
}
private void DrawSpectateUI(BasePlayer admin, BasePlayer target)
{
CuiHelper.DestroyUi(admin, "SpectateUI");
var container = new CuiElementContainer();
string panelName = container.Add(new CuiPanel
{
Image = { Color = "0.08 0.08 0.08 0.85" },
RectTransform = { AnchorMin = "0.33 0.03", AnchorMax = "0.67 0.15" }
}, "Hud", "SpectateUI");
container.Add(new CuiLabel
{
Text = { Text = $"НАБЛЮДЕНИЕ ЗА: {target.displayName.ToUpper()} | HP: {target.health:F0}/{target.MaxHealth():F0}", FontSize = 12, Align = TextAnchor.MiddleCenter, Font = "robotocondensed-bold.ttf", Color = "0.9 0.2 0.2 1" },
RectTransform = { AnchorMin = "0.0 0.75", AnchorMax = "1.0 1.0" }
}, panelName);
float slotWidth = 0.15f;
float spacing = 0.015f;
float startPos = 0.01f;
for (int i = 0; i < 6; i++)
{
float minX = startPos + i * (slotWidth + spacing);
float maxX = minX + slotWidth;
string slotPanel = container.Add(new CuiPanel
{
Image = { Color = "0.18 0.18 0.18 0.6" },
RectTransform = { AnchorMin = $"{minX} 0.08", AnchorMax = $"{maxX} 0.68" }
}, panelName);
Item item = target.inventory.containerBelt.GetSlot(i);
if (item != null)
{
container.Add(new CuiElement
{
Parent = slotPanel,
Components = {
new CuiRawImageComponent { Url = $"https://rustoria.co{item.info.shortname}.png" },
new CuiRectTransformComponent { AnchorMin = "0.1 0.1", AnchorMax = "0.9 0.9" }
}
});
if (item.amount > 1)
{
container.Add(new CuiLabel
{
Text = { Text = $"x{item.amount}", FontSize = 10, Align = TextAnchor.LowerRight, Color = "1 1 1 0.9" },
RectTransform = { AnchorMin = "0.0 0.0", AnchorMax = "0.9 0.25" }
}, slotPanel);
}
}
}
CuiHelper.AddUi(admin, container);
}
private BasePlayer FindTarget(string search)
{
if (string.IsNullOrEmpty(search)) return null;
ulong steamId;
if (search.Length == 17 && ulong.TryParse(search, out steamId))
{
BasePlayer player = BasePlayer.FindByID(steamId);
if (player != null && player.IsConnected) return player;
}
BasePlayer bestMatch = null;
foreach (var player in BasePlayer.activePlayerList)
{
if (player.displayName.Equals(search, StringComparison.OrdinalIgnoreCase))
return player;
if (player.displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
bestMatch = player;
}
return bestMatch;
}
void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (adminToTarget.ContainsKey(player.userID))
adminToTarget.Remove(player.userID);
if (adminOriginalPositions.ContainsKey(player.userID))
adminOriginalPositions.Remove(player.userID);
}
}
}