C#:
//Requires: RustNET
using Facepunch;
using Rust;
using System.Collections.Generic;
using System.Diagnostics;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using System.Linq;
using System.Collections;
using Network;
namespace Oxide.Plugins {
[Info("AutomatedSearchlights", "k1lly0u", "0.2.31")]
[Description("Create searchlights that will automatically turn on and follow a variety of objects")]
class AutomatedSearchlights : RustPlugin {
#region Fields
private StoredData storedData;
private DynamicConfigFile data;
private static AutomatedSearchlights ins;
private static LinkManager linkManager;
private static int ctrlLayerMask;
private FrameBudgeter frameBudgeter;
private bool wipeDetected;
private bool automationEnabled;
private bool isInitialized;
private bool nlConsume;
private const string PERM_USE = "automatedsearchlights.use";
private const string PERM_IGNORE = "automatedsearchlights.ignorelimit";
private const string FX_OFFLINE = "assets/prefabs/npc/autoturret/effects/offline.prefab";
private const string FX_ONLINE = "assets/prefabs/npc/autoturret/effects/online.prefab";
private const string FX_ACQUIRED = "assets/prefabs/npc/autoturret/effects/targetacquired.prefab";
private const string FX_LOST = "assets/prefabs/npc/autoturret/effects/targetlost.prefab";
private const string ASUI_OVERLAY = "ASUI_Overlay";
#endregion
#region Oxide Hooks
private void Loaded() {
permission.RegisterPermission(PERM_USE, this);
permission.RegisterPermission(PERM_IGNORE, this);
foreach (string key in configData.Management.Max.Keys) {
if (permission.PermissionExists(key, this)) continue;
permission.RegisterPermission(key, this);
}
lang.RegisterMessages(Messages, this);
data = Interface.Oxide.DataFileSystem.GetFile("RustNET/searchlights");
linkManager = new LinkManager();
}
private void OnServerInitialized() {
ins = this;
frameBudgeter = new GameObject("AutomatedSearchlights.FrameBudgeter").AddComponent<FrameBudgeter>();
LightManager.allManagers = new List<LightManager>();
LoadData();
ctrlLayerMask = LayerMask.GetMask("Default", "Water", "Deployed", "AI", "Player_Movement", "Vehicle_Movement", "World", "Player_Server", "Construction", "Terrain", "Clutter", "Debris", "Tree");
if (wipeDetected) {
storedData = new StoredData();
SaveData();
}
MonitorTime(true);
LoadDefaultImages();
InvokeHandler.Invoke(ServerMgr.Instance, InitializeAllLinks, 10f);
RustNET.RegisterModule(Title, this);
if (!configData.Management.AutoRegister) Unsubscribe(nameof(OnEntitySpawned));
}
private void OnNewSave(string filename) => wipeDetected = true;
private void OnServerSave() => SaveData();
private void OnEntitySpawned(SearchLight searchLight) {
if (searchLight == null) return;
BasePlayer owner = BasePlayer.FindByID(searchLight.OwnerID);
if (owner == null) return;
BuildingManager.Building building = RustNET.GetBuilding(searchLight);
if (building == null) return;
if (!building.GetDominatingBuildingPrivilege()?.IsAuthed(owner) ?? false) return;
RustNET.LinkManager.Link link = RustNET.linkManager.GetLinkOf(building);
if (link == null) return;
if (Vector3.Distance(searchLight.transform.position, link.terminal.DroppedItem.transform.position) > configData.Management.DistanceFromTerminal) return;
LinkManager.LightLink lightLink = linkManager.GetLinkOf(link.terminal.TerminalID);
if (lightLink == null) lightLink = new LinkManager.LightLink(link.terminal.TerminalID, searchLight, "");
else {
int lightLimit = GetMaxLights(owner.userID);
if (!permission.UserHasPermission(owner.UserIDString, PERM_IGNORE) && lightLink.Managers.Count >= lightLimit) return;
lightLink.AddLightToLink(searchLight, link.terminal.TerminalID, "");
}
searchLight.GetComponent<LightManager>().ToggleAutomation(automationEnabled);
SendReply(owner, msg("Success.Set", owner.userID));
SaveData();
}
private void OnEntityKill(BaseNetworkable networkable) {
if (networkable != null) linkManager.OnEntityDeath(networkable);
}
private void OnPlayerDisconnected(BasePlayer player) {
Controller controller = player.GetComponent<Controller>();
if (controller != null) {
LinkManager.LightLink link = linkManager.GetLinkOf(controller);
if (link != null) link.CloseLink(controller);
}
}
private object OnPlayerCommand(BasePlayer player, string command, string[] args) {
if (player != null && player.GetComponent<Controller>()) return false;
return null;
}
private object OnPlayerTick(BasePlayer player, PlayerTick msg, bool wasPlayerStalled) {
Controller controller = player.GetComponent<Controller>();
if (controller != null) return false;
return null;
}
private void Unload() {
UnityEngine.Object.Destroy(frameBudgeter.gameObject);
if (InvokeHandler.IsInvoking(ServerMgr.Instance, InitializeAllLinks)) InvokeHandler.CancelInvoke(ServerMgr.Instance, InitializeAllLinks);
if (isInitialized) SaveData();
linkManager.DestroyAllLinks();
foreach (BasePlayer player in BasePlayer.activePlayerList) CuiHelper.DestroyUi(player, ASUI_OVERLAY);
LightManager.allManagers?.Clear();
LightManager.allManagers = null;
ins = null;
linkManager = null;
}
#endregion
#region Functions
private void InitializeAllLinks() {
for (int i = storedData.registeredSearchlights.Length - 1; i >= 0; i--) {
LightManager.LightData lightData = storedData.registeredSearchlights.ElementAt(i);
SearchLight searchLight = BaseEntity.serverEntities.Find(lightData.lightId) as SearchLight;
if (searchLight == null || (lightData.terminalId != 0 && !RustNET.linkManager.IsValidTerminal(lightData.terminalId))) continue;
LinkManager.LightLink link = linkManager.GetLinkOf(lightData.terminalId);
if (link == null) link = new LinkManager.LightLink(lightData.terminalId, searchLight, lightData.lightName);
else link.AddLightToLink(searchLight, lightData.terminalId, lightData.lightName);
}
MonitorTime(true);
isInitialized = true;
}
private void MonitorTime(bool firstLoad = false) {
if (!configData.Options.NightOnly) {
automationEnabled = true;
return;
}
float currentTime = TOD_Sky.Instance.Cycle.Hour;
bool isNight = (currentTime > 0 && currentTime < configData.Options.Sunrise) || currentTime > configData.Options.Sunset;
if (automationEnabled != isNight || firstLoad) {
automationEnabled = isNight;
ServerMgr.Instance.StartCoroutine(ToggleAllLights(automationEnabled));
}
timer.In(10, ()=> MonitorTime());
}
private IEnumerator ToggleAllLights(bool status) {
foreach(LinkManager.LightLink link in linkManager.links) {
foreach(LightManager manager in link.Managers) {
yield return new WaitForSeconds(UnityEngine.Random.Range(0.1f, 0.2f));
if (manager == null) continue;
manager.ToggleAutomation(status);
}
}
}
private int GetMaxLights(ulong playerId) {
int max = 0;
foreach (KeyValuePair<string, int> entry in configData.Management.Max) {
if (permission.UserHasPermission(playerId.ToString(), entry.Key)) {
if (max < entry.Value) max = entry.Value;
}
}
return max;
}
#endregion
#region RustNET Integration
private void DestroyAllLinks() => linkManager.DestroyAllLinks();
private void OnLinkShutdown(int terminalId) {
LinkManager.LightLink link = linkManager.GetLinkOf(terminalId);
if (link != null) link.OnLinkTerminated(false);
}
private void OnLinkDestroyed(int terminalId) {
LinkManager.LightLink link = linkManager.GetLinkOf(terminalId);
if (link != null) link.OnLinkTerminated(true);
}
private LightManager[] GetAvailableSearchlights(int terminalId) {
LinkManager.LightLink link = linkManager.GetLinkOf(terminalId);
if (link != null) return link.Managers.ToArray();
return new LightManager[0];
}
private bool IsEntityEnabled(int terminalId, uint managerId) {
LinkManager.LightLink link = linkManager.GetLinkOf(terminalId);
if (link != null) {
LightManager manager = link.Managers.Find(x => x.Searchlight.net.ID == managerId);
if (manager != null) return manager.IsEnabled();
}
return false;
}
private void InitializeController(BasePlayer player, uint managerId, int terminalId) {
LinkManager.LightLink link = linkManager.GetLinkOf(terminalId);
if (link != null) link.InitiateLink(player, managerId);
}
private void ToggleAutomation(uint managerId, int terminalId, bool active) {
LinkManager.LightLink link = linkManager.GetLinkOf(terminalId);
if (link != null) {
LightManager manager = link.Managers.Find(x => x.Searchlight.net.ID == managerId);
if (manager != null) {
manager.ToggleSearchAutomation(active);
}
}
}
private string GetHelpString(ulong playerId, bool title) => title ? msg("UI.Help.Title", playerId) : msg("UI.Help", playerId);
private bool AllowPublicAccess() => true;
#endregion
#region Component
private class LinkManager {
public List<LightLink> links = new List<LightLink>();
public LightLink GetLinkOf(LightManager manager) => links.Find(x => x.Managers.Contains(manager)) ?? null;
public LightLink GetLinkOf(Controller controller) => links.Find(x => x.Controllers.Contains(controller)) ?? null;
public LightLink GetLinkOf(int terminalId) => links.Find(x => x.TerminalID == terminalId) ?? null;
public LightLink GetLinkOf(SearchLight searchLight) {
LightManager component = searchLight.GetComponent<LightManager>();
if (component == null) return null;
return GetLinkOf(component);
}
public void OnEntityDeath(BaseNetworkable networkable) {
for (int i = links.Count - 1; i >= 0; i--) links.ElementAt(i).OnEntityDeath(networkable);
}
public void DestroyAllLinks() {
foreach (LightLink link in links) link.OnLinkTerminated(false);
}
public class LightLink {
public int TerminalID {
get;
private set;
}
public List<Controller> Controllers {
get;
private set;
}
public List<LightManager> Managers {
get;
private set;
}
public LightLink() {
}
public LightLink(int terminalId, SearchLight searchLight, string lightName) {
this.TerminalID = terminalId;
this.Controllers = new List<Controller>();
this.Managers = new List<LightManager>();
AddLightToLink(searchLight, terminalId, lightName);
linkManager.links.Add(this);
}
public void AddLightToLink(SearchLight searchLight, int terminalId, string lightName) {
LightManager manager = searchLight.gameObject.AddComponent<LightManager>();
manager.lightName = lightName;
manager.terminalId = terminalId;
Managers.Add(manager);
}
public void InitiateLink(BasePlayer player, uint managerId) {
LightManager manager = Managers.FirstOrDefault(x => x.Searchlight.net.ID == managerId);
if (manager != null) {
player.inventory.crafting.CancelAll(true);
Controller controller = player.gameObject.AddComponent<Controller>();
Controllers.Add(controller);
controller.InitiateLink(TerminalID);
controller.SetLightLink(this);
controller.SetSpectateTarget(Managers.IndexOf(manager));
}
}
public void CloseLink(Controller controller, bool isDead = false) {
if (controller != null) {
Controllers.Remove(controller);
controller.FinishSpectating(isDead);
}
}
public void OnEntityDeath(BaseNetworkable networkable) {
LightManager manager = networkable.GetComponent<LightManager>();
if (manager != null && Managers.Contains(manager)) {
if (manager.Controller != null) {
manager.Controller.player.ChatMessage(ins.msg("Warning.SearchlightDestroyed", manager.Controller.player.userID));
CloseLink(manager.Controller);
}
Managers.Remove(manager);
return;
}
if (networkable.GetComponent<BaseCombatEntity>()) {
foreach (LightManager lightManager in Managers) lightManager.OnEntityDeath(networkable as BaseCombatEntity);
}
}
public void OnLinkTerminated(bool isDestroyed) {
for (int i = Controllers.Count - 1; i >= 0; i--) {
Controller controller = Controllers.ElementAt(i);
controller.player.ChatMessage(isDestroyed ? ins.msg("Warning.TerminalDestroyed", controller.player.userID) : ins.msg("Warning.TerminalShutdown", controller.player.userID));
CloseLink(controller);
}
DestroyLightManagers(isDestroyed);
}
private void DestroyLightManagers(bool isDestroyed) {
foreach (LightManager manager in Managers) {
UnityEngine.Object.Destroy(manager);
}
}
}
}
private class FrameBudgeter : MonoBehaviour {
private double maxMilliseconds = 0.2;
private Stopwatch sw = Stopwatch.StartNew();
private int lastIndex = 0;
private void Update() {
sw.Reset();
sw.Start();
int count = LightManager.allManagers?.Count ?? 0;
if (lastIndex >= count) lastIndex = 0;
for (int i = lastIndex; i < count; i++) {
if (sw.Elapsed.TotalMilliseconds > maxMilliseconds) {
lastIndex = i;
return;
}
LightManager.allManagers[i].DoUpdate();
}
lastIndex = 0;
}
}
private class LightManager : MonoBehaviour {
internal static List<LightManager> allManagers;
public SearchLight Searchlight {
get;
private set;
}
public Controller Controller {
get;
private set;
}
private SphereCollider collider;
private Rigidbody rb;
private BaseCombatEntity targetEntity;
private float maxTargetDistance;
private int threatLevel = 10;
private float searchRadius;
private bool requiresPower;
private ConfigData.LightOptions.FlickerMode flicker;
private ConfigData.LightOptions.SearchMode search;
private ConfigData.DetectionOptions detection;
private Hash<int, HashSet<BaseCombatEntity>> threats;
private Hash<int, float> threatRadius;
private float[] visabilityOffsets = new float[] {
0, 0.2f, -0.2f
}
;
private float nextCheckTime;
private float threatProbabilityRate;
private float idleTime = 0;
private float lostTargetTime = 0;
private Vector3[] searchPattern = new Vector3[9];
private int lastSearchPoint = 0;
private int nextSearchPoint = 1;
private bool searchForwards;
private float searchTime;
private bool resetSearch = true;
private bool isSearching;
public string lightName;
public int terminalId;
public bool isDisabled;
private void Awake() {
Searchlight = GetComponent<SearchLight>();
threatProbabilityRate = 3f + UnityEngine.Random.Range(0.1f, 1.0f);
InitializeSettings();
allManagers.Add(this);
}
internal void DoUpdate() {
if (Searchlight.IsMounted() || Searchlight.HasFlag(BaseEntity.Flags.Reserved5) || !Searchlight.IsPowered()) return;
if (Controller != null && !Controller.lightsOn) return;
if (!flicker.Enabled) {
if (!Searchlight.IsOn()) Searchlight.SetFlag(BaseEntity.Flags.On, true);
}
else {
float healthPercent = Searchlight.health / Searchlight.MaxHealth();
if (healthPercent * 100 <= flicker.Health) {
if (healthPercent * 100 <= flicker.Health / 2) Searchlight.SetFlag(BaseEntity.Flags.On, UnityEngine.Random.Range(1, 5) != 2);
else Searchlight.SetFlag(BaseEntity.Flags.On, UnityEngine.Random.Range(1, 10) != 2);
}
else {
if (!Searchlight.IsOn()) Searchlight.SetFlag(BaseEntity.Flags.On, true);
}
}
if (Controller != null) return;
else {
nextCheckTime += Time.deltaTime;
if (nextCheckTime > threatProbabilityRate) {
CalculateThreatProbability();
nextCheckTime = 0;
}
if (targetEntity == null) {
if (search.Enabled) {
idleTime += Time.deltaTime;
if (idleTime > 3f) {
if (isSearching) {
searchTime = searchTime + Time.deltaTime;
float single = Mathf.InverseLerp(0f, search.Speed / 9, searchTime);
Searchlight.SetTargetAimpoint(Vector3.Lerp(searchPattern[lastSearchPoint], searchPattern[nextSearchPoint], single));
Searchlight.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
if (single >= 1) {
searchTime = 0;
SetNextSearchPoint();
}
}
else if (resetSearch) {
lastSearchPoint = 4;
nextSearchPoint = searchForwards ? 5 : 3;
searchTime = 0;
Searchlight.SetTargetAimpoint(searchPattern[4]);
Searchlight.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
resetSearch = false;
isSearching = true;
}
}
}
}
else {
if (IsObjectVisible(targetEntity) && Vector3.Distance(Searchlight.transform.position, targetEntity.transform.position) <= maxTargetDistance) {
Searchlight.SetTargetAimpoint(targetEntity.transform.position);
Searchlight.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
lostTargetTime = 0;
}
else {
lostTargetTime += Time.deltaTime;
if (lostTargetTime > 3f) ClearAimTarget();
}
}
}
}
private void OnDestroy() {
Destroy(collider);
Destroy(rb);
allManagers?.Remove(this);
}
private void OnTriggerEnter(Collider col) {
BaseCombatEntity potentialTarget = col.gameObject?.GetComponentInParent<BaseCombatEntity>();
if (potentialTarget == null || potentialTarget.IsDestroyed) return;
float threatDistance = Vector3.Distance(Searchlight.transform.position, potentialTarget.transform.position);
if (potentialTarget is BasePlayer) {
if (RustNET.IsFriendlyPlayer(Searchlight.OwnerID, potentialTarget.ToPlayer().userID)) {
if (detection.Friends.Enabled && threatDistance <= detection.Friends.Radius) {
if (threats[detection.Friends.Threat].Contains(potentialTarget)) return;
threats[detection.Friends.Threat].Add(potentialTarget);
}
}
else if (detection.Enemies.Enabled && threatDistance <= detection.Enemies.Radius) {
if (threats[detection.Enemies.Threat].Contains(potentialTarget)) return;
threats[detection.Enemies.Threat].Add(potentialTarget);
}
}
else if (potentialTarget is BaseNpc) {
if (detection.Animals.Enabled && threatDistance <= detection.Animals.Radius) {
if (threats[detection.Animals.Threat].Contains(potentialTarget)) return;
threats[detection.Animals.Threat].Add(potentialTarget);
}
}
else if (potentialTarget is BasicCar || potentialTarget is BradleyAPC) {
if (detection.Vehicles.Enabled && threatDistance <= detection.Vehicles.Radius) {
if (threats[detection.Vehicles.Threat].Contains(potentialTarget)) return;
threats[detection.Vehicles.Threat].Add(potentialTarget);
}
}
else if (potentialTarget is BaseHelicopter) {
if (detection.Helicopters.Enabled && threatDistance <= detection.Helicopters.Radius) {
if (threats[detection.Helicopters.Threat].Contains(potentialTarget)) return;
threats[detection.Helicopters.Threat].Add(potentialTarget);
}
}
}
private void OnTriggerExit(Collider col) {
BaseCombatEntity potentialTarget = col.gameObject.GetComponent<BaseCombatEntity>();
if (potentialTarget == null) return;
foreach(KeyValuePair<int, HashSet<BaseCombatEntity>> threatType in threats) {
if (threatType.Value.Contains(potentialTarget)) {
threatType.Value.Remove(potentialTarget);
break;
}
}
if (potentialTarget == targetEntity) ClearAimTarget();
}
private void InitializeSettings() {
requiresPower = ins.configData.Options.RequirePower;
flicker = ins.configData.Options.Flicker;
search = ins.configData.Options.Search;
detection = ins.configData.Detection;
searchRadius = detection.Animals.Radius;
if (detection.Enemies.Radius > searchRadius) searchRadius = detection.Enemies.Radius;
if (detection.Friends.Radius > searchRadius) searchRadius = detection.Friends.Radius;
if (detection.Helicopters.Radius > searchRadius) searchRadius = detection.Helicopters.Radius;
if (detection.Vehicles.Radius > searchRadius) searchRadius = detection.Vehicles.Radius;
if (search.Enabled) GenerateSearchPattern();
threats = new Hash<int, HashSet<BaseCombatEntity>> {
[detection.Animals.Threat]
= new HashSet<BaseCombatEntity>(), [detection.Enemies.Threat] = new HashSet<BaseCombatEntity>(), [detection.Friends.Threat] = new HashSet<BaseCombatEntity>(), [detection.Helicopters.Threat] = new HashSet<BaseCombatEntity>(), [detection.Vehicles.Threat] = new HashSet<BaseCombatEntity>()
}
;
threatRadius = new Hash<int, float> {
[detection.Animals.Threat]
= detection.Animals.Radius, [detection.Enemies.Threat] = detection.Enemies.Radius, [detection.Friends.Threat] = detection.Friends.Radius, [detection.Helicopters.Threat] = detection.Helicopters.Radius, [detection.Vehicles.Threat] = detection.Vehicles.Radius
}
;
rb = Searchlight.gameObject.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.isKinematic = true;
collider = Searchlight.gameObject.AddComponent<SphereCollider>();
collider.gameObject.layer = (int)Layer.Reserved1;
collider.radius = searchRadius;
collider.isTrigger = true;
}
private void GenerateSearchPattern() {
int count = 0;
for (int i = -40; i <= 40; i += 10) {
searchPattern[count] = Searchlight.transform.position + (Searchlight.transform.rotation * (new Vector3(Mathf.Cos(i * Mathf.Deg2Rad), -0.3f, Mathf.Sin(i * Mathf.Deg2Rad)) * 30));
count++;
}
}
private void SetNextSearchPoint() {
if (nextSearchPoint == 0 || nextSearchPoint == 8) searchForwards = !searchForwards;
lastSearchPoint = nextSearchPoint;
nextSearchPoint = searchForwards ? nextSearchPoint += 1 : nextSearchPoint -= 1;
}
private void CalculateThreatProbability() {
BaseCombatEntity target = null;
float targetDistance = searchRadius;
int threatRating = 10;
foreach (KeyValuePair<int, HashSet<BaseCombatEntity>> list in threats.Where(x => x.Key <= threatLevel && x.Value.Count > 0).OrderByDescending(x => x.Key)) {
foreach (BaseCombatEntity potentialTarget in list.Value) {
if (potentialTarget == null || potentialTarget.IsDestroyed || !IsObjectVisible(potentialTarget)) continue;
float distance = Vector3.Distance(Searchlight.transform.position, potentialTarget.transform.position);
if (distance < threatRadius[list.Key]) {
target = potentialTarget;
targetDistance = distance;
threatRating = list.Key;
}
}
}
if (target == targetEntity) return;
if (target != null) {
if (targetEntity != null) {
if (threatRating < threatLevel || (threatRating == threatLevel && targetDistance < Vector3.Distance(Searchlight.transform.position, target.transform.position))) SetAimTarget(target, threatRating);
}
else SetAimTarget(target, threatRating);
}
}
private void SetAimTarget(BaseCombatEntity target, int threatRating) {
nextCheckTime = 0;
idleTime = 0;
isSearching = false;
targetEntity = target;
if (Searchlight.HasFlag(BaseEntity.Flags.On)) Effect.server.Run(FX_ACQUIRED, Searchlight, 0, Vector3.zero, Vector3.zero, null, false);
maxTargetDistance = target is BasePlayer ? (RustNET.IsFriendlyPlayer(Searchlight.OwnerID, targetEntity.ToPlayer().userID) ? detection.Friends.Radius : detection.Enemies.Radius) : target is BaseNpc ? detection.Animals.Radius : (target is BasicCar || target is BradleyAPC) ? detection.Vehicles.Radius : detection.Helicopters.Radius;
threatLevel = threatRating;
}
private void ClearAimTarget() {
resetSearch = true;
if (targetEntity != null && Searchlight.HasFlag(BaseEntity.Flags.On)) Effect.server.Run(FX_LOST, Searchlight, 0, Vector3.zero, Vector3.zero, null, false);
targetEntity = null;
threatLevel = 10;
}
private bool IsObjectVisible(BaseCombatEntity obj) {
List<RaycastHit> list = Pool.GetList<RaycastHit>();
Vector3 castPoint = Searchlight.transform.position + (Vector3.up * 1.4f);
Vector3 aimPoint = AimOffset(obj);
Vector3 direction = aimPoint - castPoint;
Vector3 cross = Vector3.Cross(direction.normalized, Vector3.up);
float distance = Vector3.Distance(aimPoint, castPoint);
for (int i = 0; i < 3; i++) {
Vector3 altCastPoint = aimPoint + (cross * visabilityOffsets[i]);
Vector3 altDirection = (altCastPoint - castPoint).normalized;
list.Clear();
GamePhysics.TraceAll(new Ray(castPoint, altDirection), 0f, list, distance * 1.1f, 1084434689, QueryTriggerInteraction.UseGlobal);
int num = 0;
while (num < list.Count) {
BaseEntity foundEntity = list[num].GetEntity();
if (foundEntity != null && (foundEntity == obj || foundEntity.EqualNetID(obj))) {
Pool.FreeList<RaycastHit>(ref list);
return true;
}
if (foundEntity == Searchlight) {
num++;
continue;
}
if (!(foundEntity != null) || foundEntity.ShouldBlockProjectiles()) break;
else num++;
}
}
Pool.FreeList<RaycastHit>(ref list);
return false;
}
private Vector3 AimOffset(BaseCombatEntity aimat) {
BasePlayer basePlayer = aimat as BasePlayer;
if (basePlayer != null) {
return basePlayer.eyes.position;
}
return aimat.transform.position + new Vector3(0f, 0.3f, 0f);
}
public void ToggleAutomation(bool status) {
if (isDisabled || enabled == status) return;
if (requiresPower) {
if (!Searchlight.HasFlag(BaseEntity.Flags.Reserved8)) return;
}
else Searchlight.SetFlag(BaseEntity.Flags.Reserved8, status);
enabled = status;
Effect.server.Run(status ? FX_ONLINE : FX_OFFLINE, Searchlight, 0, Vector3.zero, Vector3.zero, null, false);
Searchlight.SetFlag(BaseEntity.Flags.On, status);
}
public void AdjustLightRotation(float rotation) {
bool wasEnabled = enabled;
if (enabled) ToggleAutomation(false);
Searchlight.transform.eulerAngles = new Vector3(0, rotation - 90, 0);
Searchlight.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
if (search.Enabled) {
GenerateSearchPattern();
lastSearchPoint = 4;
nextSearchPoint = searchForwards ? 5 : 3;
searchTime = 0;
}
if (wasEnabled) ToggleAutomation(true);
}
public void SetController(Controller controller) => this.Controller = controller;
public bool IsEnabled() {
return !isDisabled;
}
public void ToggleSearchAutomation(bool status) {
print($"toggle3 {status}");
if (status) {
isDisabled = false;
if (Controller == null) {
if (!enabled && ins.automationEnabled) ToggleAutomation(true);
}
}
else {
print($"toggle4 {enabled} && {Controller == null}");
isDisabled = true;
if (enabled && Controller == null) {
print("toggle ioff");
enabled = false;
if (Searchlight.HasFlag(BaseEntity.Flags.On)) {
Searchlight.SetFlag(BaseEntity.Flags.On, false);
Searchlight.SendNetworkUpdate();
}
}
}
}
public void OnEntityDeath(BaseCombatEntity baseCombatEntity) {
if (baseCombatEntity == targetEntity) ClearAimTarget();
}
public LightData GetLightData() {
if (Searchlight == null || Searchlight.IsDestroyed) return null;
return new LightData(this);
}
public class LightData {
public uint lightId;
public int terminalId;
public string lightName;
public LightData() {
}
public LightData(LightManager manager) {
lightId = manager.Searchlight.net.ID;
terminalId = manager.terminalId;
lightName = manager.lightName;
}
}
}
class Controller : RustNET.Controller {
public LightManager manager {
get;
private set;
}
private LinkManager.LightLink link;
private int spectateIndex = 0;
private bool switchingTargets;
private bool wasEnabled = false;
private bool canCycle;
private Vector3 offset = new Vector3(0, 0.3f, 0);
public bool lightsOn {
get;
private set;
}
public override void Awake() {
base.Awake();
enabled = false;
canCycle = ins.configData.Management.Remote.CanCycle;
player.ClearEntityQueue(null);
}
private void FixedUpdate() {
if (player == null || player.serverInput == null || switchingTargets) return;
InputState input = player.serverInput;
if (manager != null && manager.Controller == this) {
if (input.WasJustPressed(BUTTON.FIRE_PRIMARY)) {
lightsOn = !lightsOn;
if (lightsOn) {
if (manager.Searchlight.IsPowered()) manager.Searchlight.SetFlag(BaseEntity.Flags.On, true);
}
else manager.Searchlight.SetFlag(BaseEntity.Flags.On, false);
}
Vector3 aimTarget = player.transform.position + (Quaternion.Euler(input.current.aimAngles) * (Vector3.forward * 10));
manager.Searchlight.SetTargetAimpoint(aimTarget);
manager.Searchlight.SendNetworkUpdate(BasePlayer.NetworkQueue.Update);
}
if (input.WasJustPressed(BUTTON.USE)) {
enabled = false;
link.CloseLink(this);
}
else {
if (canCycle) {
if (input.WasJustPressed(BUTTON.JUMP)) UpdateSpectateTarget(1);
else if (input.WasJustPressed(BUTTON.DUCK)) UpdateSpectateTarget(-1);
}
}
}
public override void OnDestroy() {
player.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, false);
player.SetPlayerFlag(BasePlayer.PlayerFlags.ThirdPersonViewmode, false);
if (manager != null && manager.Controller == this) manager.SetController(null);
base.OnDestroy();
}
public void SetLightLink(LinkManager.LightLink link) {
this.link = link;
BeginSpectating();
}
public void SetSpectateTarget(int spectateIndex) {
this.spectateIndex = spectateIndex;
manager = link.Managers[spectateIndex];
player.SendEntitySnapshot(manager.Searchlight);
player.gameObject.Identity();
//player.SetParent(manager.searchLight, 0);
RustNET.MovePosition(player, manager.Searchlight.transform.position + (Vector3.up * 1.5f), false);
if (manager.Controller == null) manager.SetController(this);
else player.ChatMessage(ins.msg("Warning.InUse", player.userID));
CreateCameraOverlay();
}
public void UpdateSpectateTarget(int index = 0) {
switchingTargets = true;
player.Invoke(() => switchingTargets = false, 0.25f);
int newIndex = spectateIndex + index;
if (newIndex > link.Managers.Count - 1) newIndex = 0;
else if (newIndex < 0) newIndex = link.Managers.Count - 1;
if (spectateIndex == newIndex) return;
if (manager.Controller == this) manager.SetController(null);
manager = null;
SetSpectateTarget(newIndex);
}
public void BeginSpectating() {
RustNET.StripInventory(player);
player.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, true);
player.gameObject.SetLayerRecursive(10);
player.CancelInvoke("InventoryUpdate");
player.SetPlayerFlag(BasePlayer.PlayerFlags.ThirdPersonViewmode, true);
//player.Command("client.camoffset", new object[] { new Vector3(0, 2.2f, 0) });
NetWrite netWrite = Net.sv.StartWrite();
netWrite.PacketID(Network.Message.Type.EntityDestroy);
netWrite.EntityID(player.net.ID);
netWrite.UInt8((byte)BaseNetworkable.DestroyMode.None);
netWrite.Send(new Network.SendInfo(player.net.group.subscribers.Where(x => x.userid != player.userID).ToList()));
player.ChatMessage(ins.msg("Help.Toggle", player.userID));
if (canCycle) player.ChatMessage(ins.msg("Help.ControlInfo", player.userID));
else player.ChatMessage(ins.msg("Help.ControlInfo.NoCycle", player.userID));
enabled = true;
}
public void FinishSpectating(bool isDead) {
enabled = false;
//player.SetParent(null, 0);
player.SetPlayerFlag(BasePlayer.PlayerFlags.Spectating, false);
player.gameObject.SetLayerRecursive(17);
player.SetPlayerFlag(BasePlayer.PlayerFlags.ThirdPersonViewmode, false);
player.InvokeRepeating("InventoryUpdate", 1f, 0.1f * UnityEngine.Random.Range(0.99f, 1.01f));
player.Command("client.camoffset", new object[] {
new Vector3(0, 1.2f, 0)
}
);
CuiHelper.DestroyUi(player, ASUI_OVERLAY);
if (manager.Controller == this) manager.SetController(null);
if (!isDead) Destroy(this);
}
public override void OnPlayerDeath(HitInfo info) {
enabled = false;
link.CloseLink(this, true);
base.OnPlayerDeath(info);
}
private void CreateCameraOverlay() {
if (!ins.configData.Management.Remote.Overlay) return;
CuiElementContainer container = RustNET.UI.Container("0 0 0 0", "0 0", "1 1", false, "Under", ASUI_OVERLAY);
RustNET.UI.Image(ref container, ins.GetImage("searchlightoverlay"), "0 0", "1 1", ASUI_OVERLAY);
RustNET.UI.Panel(ref container, "0 0 0 0.4", "0.82 0.9", "0.96 0.94", ASUI_OVERLAY);
RustNET.UI.Label(ref container, string.IsNullOrEmpty(manager.lightName) ? string.Format(ins.msg("UI.SearchlightName", player.userID), spectateIndex + 1) : manager.lightName, 13, "0.82 0.9", "0.96 0.94", TextAnchor.MiddleCenter, ASUI_OVERLAY);
CuiHelper.DestroyUi(player, ASUI_OVERLAY);
CuiHelper.AddUi(player, container);
}
}
#endregion
#region UI
private void CreateConsoleWindow(BasePlayer player, int terminalId, int page) {
CuiElementContainer container = RustNET.ins.GetBaseContainer(player, terminalId, Title);
LightManager[] entityIds = GetAvailableSearchlights(terminalId);
RustNET.UI.Panel(ref container, RustNET.uiColors[RustNET.Colors.Panel], "0.04 0.765", "0.96 0.8");
RustNET.UI.Label(ref container, msg("UI.Select.Searchlight", player.userID), 12, "0.05 0.765", "0.5 0.8", TextAnchor.MiddleLeft);
RustNET.UI.Button(ref container, RustNET.uiColors[RustNET.Colors.Button], RustNET.msg("UI.MainMenu", player.userID), 11, "0.82 0.765", "0.96 0.8", $"rustnet.changepage {terminalId}");
if (entityIds == null || entityIds.Length == 0) RustNET.UI.Label(ref container, msg("UI.NoSearchlights", player.userID), 12, "0.05 0.5", "0.95 0.7");
else {
int count = 0;
int startAt = page * 18;
for (int i = startAt; i < (startAt + 18 > entityIds.Length ? entityIds.Length : startAt + 18);
i++) {
LightManager manager = entityIds.ElementAt(i);
RustNET.UI.Panel(ref container, RustNET.uiColors[RustNET.Colors.Panel], $"0.04 {(0.725f - (count * 0.04f))}", $"0.96 {(0.755f - (count * 0.04f))}");
RustNET.UI.Label(ref container, string.IsNullOrEmpty(manager.lightName) ? string.Format(msg("UI.Searchlight", player.userID), count + 1) : $"> {manager.lightName}", 11, $"0.05 {0.725f - (count * 0.04f)}", $"0.31 {0.755f - (count * 0.04f)}", TextAnchor.MiddleLeft);
if (configData.Management.Remote.ToggleEnabled) {
bool isEnabled = manager.IsEnabled();
RustNET.UI.Button(ref container, RustNET.uiColors[RustNET.Colors.Button], isEnabled ? RustNET.msg("UI.Enable", player.userID) : RustNET.msg("UI.Disable", player.userID), 11, $"0.32 {0.725f - (count * 0.04f)}", $"0.53 {0.755f - (count * 0.04f)}", $"automatedsearchlights.toggle {manager.Searchlight.net.ID} {terminalId} {page} {!isEnabled}");
}
if (configData.Management.Remote.RemoteControl) RustNET.UI.Button(ref container, RustNET.uiColors[RustNET.Colors.Button], RustNET.msg("UI.Control", player.userID), 11, $"0.54 {0.725f - (count * 0.04f)}", $"0.75 {0.755f - (count * 0.04f)}", $"automatedsearchlights.control {manager.Searchlight.net.ID} {terminalId}");
count++;
}
int totalPages = entityIds.Length / 18;
RustNET.UI.Button(ref container, RustNET.uiColors[RustNET.Colors.Button], RustNET.msg("UI.Back", player.userID), 11, "0.3 0.01", "0.44 0.04", page > 0 ? $"rustnet.changepage {terminalId} {Title} {page - 1}" : "");
RustNET.UI.Label(ref container, string.Format(RustNET.msg("UI.Page", player.userID), page + 1, totalPages + 1), 11, "0.44 0.01", "0.56 0.04");
RustNET.UI.Button(ref container, RustNET.uiColors[RustNET.Colors.Button], RustNET.msg("UI.Next", player.userID), 11, "0.56 0.01", "0.7 0.04", page + 1 <= totalPages ? $"rustnet.changepage {terminalId} {Title} {page + 1}" : "");
}
CuiHelper.DestroyUi(player, RustNET.RustNET_Panel);
CuiHelper.AddUi(player, container);
}
[ConsoleCommand("automatedsearchlights.toggle")]
private void ccmdToggle(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Connection.player as BasePlayer;
if (player == null) return;
if (!RustNET.linkManager.IsValidTerminal(arg.GetInt(1))) {
CuiHelper.DestroyUi(player, RustNET.RustNET_Panel);
SendReply(player, RustNET.msg("Warning.TerminalDestroyed", player.userID));
return;
}
ToggleAutomation(arg.GetUInt(0), arg.GetInt(1), arg.GetBool(3));
RustNET.ins.DisplayToPlayer(player, arg.GetInt(1), Title, arg.GetInt(2));
}
[ConsoleCommand("automatedsearchlights.control")]
private void ccmdControl(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Connection.player as BasePlayer;
if (player == null) return;
if (!RustNET.linkManager.IsValidTerminal(arg.GetInt(1))) {
CuiHelper.DestroyUi(player, RustNET.RustNET_Panel);
SendReply(player, RustNET.msg("Warning.TerminalDestroyed", player.userID));
return;
}
CuiHelper.DestroyUi(player, RustNET.RustNET_Panel);
InitializeController(player, arg.GetUInt(0), arg.GetInt(1));
}
#endregion
#region Commands
// ... [Code preview truncated due to length. Full source in attached file] ...