C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Facepunch.Extend;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Game.Rust.Cui;
using UnityEngine;
// TODO: Add limits + Multi Permission support + Individual player UI Editors.
namespace Oxide.Plugins
{
[Info("Auto CCTV Stations", "Khan", "1.1.4")]
[Description("Automatically adds monument cctv cameras to all placed computer stations.")]
public class AutoCCTVStations : CovalencePlugin
{
#region Fields
private bool _newsave;
private Configuration _config;
private Hash<ulong, int> _stationPage = new Hash<ulong, int>();
private const string Admin = "autocctvstations.admin";
private const string Use = "autocctvstations.use";
private const string CCTV_OVERLAY = "AutoCCTVStationOverlay";
private const string CCTV_CONTENT_NAME = "AutoCCTVStationContentName";
private const string CCTV_Desc_Overlay = "AutoCCTVStationDescOverlay";
private Dictionary<string, ulong> _codes = new Dictionary<string, ulong>();
private Dictionary<CargoShip, List<CCTV_RC>> _cargoshipcctv = new Dictionary<CargoShip, List<CCTV_RC>>();
#endregion
#region Config
internal class Configuration : SerializableConfiguration
{
[JsonProperty("Sets the CCTV Editor command")]
public string CctvEditorCommand = "cctvedit";
[JsonProperty("Enables Cargo Ship cameras ( Requires Cargo Ship CCTV plugin on umod ")]
public bool EnableCargoCams = false;
public Text Text = new Text();
public Colors Colors = new Colors();
public Dictionary<string, bool> Cameras = new Dictionary<string, bool>();
}
public class Text
{
[JsonProperty("CCTV Auto Station UI Editor")]
public string EditorMsg = "CCTV Auto Station Editor ◝(⁰▿⁰)◜";
[JsonProperty("UI - Back Button Text")]
public string BackButtonText = "◀";
[JsonProperty("UI - Forward Button Text")]
public string ForwardButtonText = "▶";
[JsonProperty("UI - Close Label")]
public string CloseButtonlabel = "✖";
}
public class Colors
{
[JsonProperty("UI - Button Toggle Color")]
public string ToggleColor = "#9ab36d";
public Color TextColor = new Color( "#01579b", 1f);
public Color ButtonBackGroundColor = new Color("#0E0E10", 0.9f);
public Color ButtonGreenText = new Color("#9ab36d", 0.431f);
public Color ButtonGrey = new Color("#bfbfbf", 0.3f);
public Color ButtonGreyText = new Color("#bfbfbf", 1f);
}
private void CheckConfig()
{
_codes.Clear();
bool hasChanged = false;
foreach (var controllable in RemoteControlEntity.allControllables)
{
CCTV_RC cctv = controllable as CCTV_RC;
if (cctv == null || !cctv.isStatic) continue;
if (!_config.Cameras.ContainsKey(cctv.rcIdentifier))
{
_config.Cameras.Add(cctv.rcIdentifier, false);
hasChanged = true;
}
if (_config.Cameras.Count > 0 && _config.Cameras.ContainsKey(cctv.rcIdentifier) && _config.Cameras[cctv.rcIdentifier])
_codes.Add(cctv.rcIdentifier, cctv.net.ID.Value);
}
if (hasChanged)
SaveConfig();
}
private void UpdateConfig()
{
List<string> tempControllable = Facepunch.Pool.Get<List<string>>();
foreach (var controllable in RemoteControlEntity.allControllables)
{
CCTV_RC cctv = controllable as CCTV_RC;
if (cctv == null || !cctv.IsStatic())
continue;
if (!tempControllable.Contains(cctv.rcIdentifier))
tempControllable.Add(cctv.rcIdentifier);
}
int count = 0;
foreach (var camera in _config.Cameras.ToArray())
{
if (tempControllable.Contains(camera.Key))
continue;
if (_config.Cameras.ContainsKey(camera.Key))
{
_config.Cameras.Remove(camera.Key);
count++;
}
}
if (count > 0)
PrintWarning($"removed {count} previously enabled cameras, please update settings");
SaveConfig();
Facepunch.Pool.FreeUnmanaged(ref tempControllable);
}
#endregion
#region Updater
internal class SerializableConfiguration
{
public string ToJson() => JsonConvert.SerializeObject(this);
public Dictionary<string, object> ToDictionary() =>
JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
}
private static class JsonHelper
{
public static object Deserialize(string json) => ToObject(JToken.Parse(json));
private static object ToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
return token.Children<JProperty>()
.ToDictionary(prop => prop.Name, prop => ToObject(prop.Value));
case JTokenType.Array:
return token.Select(ToObject).ToList();
default:
return ((JValue) token).Value;
}
}
}
private bool MaybeUpdateConfig(SerializableConfiguration config)
{
var currentWithDefaults = config.ToDictionary();
var currentRaw = Config.ToDictionary(x => x.Key, x => x.Value);
return MaybeUpdateConfigDict(currentWithDefaults, currentRaw);
}
private bool MaybeUpdateConfigDict(Dictionary<string, object> currentWithDefaults,
Dictionary<string, object> currentRaw)
{
bool changed = false;
foreach (var key in currentWithDefaults.Keys)
{
object currentRawValue;
if (currentRaw.TryGetValue(key, out currentRawValue))
{
var defaultDictValue = currentWithDefaults[key] as Dictionary<string, object>;
var currentDictValue = currentRawValue as Dictionary<string, object>;
if (defaultDictValue != null)
{
if (currentDictValue == null)
{
currentRaw[key] = currentWithDefaults[key];
changed = true;
}
else if (MaybeUpdateConfigDict(defaultDictValue, currentDictValue))
changed = true;
}
}
else
{
currentRaw[key] = currentWithDefaults[key];
changed = true;
}
}
return changed;
}
#endregion
#region Oxide
protected override void LoadDefaultConfig() => _config = new Configuration();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<Configuration>();
if (_config == null)
{
PrintWarning($"Generating Config File for {Name}");
throw new JsonException();
}
if (MaybeUpdateConfig(_config))
{
PrintWarning("Configuration appears to be outdated; updating and saving");
SaveConfig();
}
}
catch (Exception ex)
{
PrintWarning("Failed to load config file (is the config file corrupt?) (" + ex.Message + ")");
}
}
protected override void SaveConfig() => Config.WriteObject(_config, true);
private void Unload()
{
_codes.Clear();
foreach (var player in BasePlayer.activePlayerList)
{
if (player.UserIDString.IsSteamId())
{
DestroyUi(player, true);
CuiHelper.DestroyUi(player, "cctvstation");
CuiHelper.DestroyUi(player, "ButtonForwardstation");
CuiHelper.DestroyUi(player, "ButtonBackstation");
}
}
_stationPage.Clear();
}
private void OnNewSave(string filename)
{
_newsave = true;
}
private void Init()
{
Unsubscribe(nameof(OnCargoShipCCTVSpawned));
Unsubscribe(nameof(OnEntityKill));
}
private void OnServerInitialized()
{
permission.RegisterPermission(Admin, this);
permission.RegisterPermission(Use, this);
CheckConfig();
AddCovalenceCommand(_config.CctvEditorCommand, nameof(CmdCctvEditor));
if (_config.EnableCargoCams)
{
Subscribe(nameof(OnCargoShipCCTVSpawned));
Subscribe(nameof(OnEntityKill));
}
if (_newsave)
UpdateConfig();
}
private void OnEntityBuilt(Planner plan, GameObject gameObject)
{
ComputerStation computerStation = gameObject.ToBaseEntity() as ComputerStation;
NextFrame(() =>
{
if (computerStation == null) return;
if (computerStation.OwnerID != 0UL && permission.UserHasPermission(computerStation.OwnerID.ToString(), Use))
{
foreach (var cam in _codes)
{
if (computerStation.controlBookmarks.Contains(cam.Key)) continue;
computerStation.controlBookmarks.Add(cam.Key);
}
computerStation.SendNetworkUpdateImmediate();
}
});
}
private void OnEntityMounted(ComputerStation station, BasePlayer player)
{
if (station == null || player != null && permission?.UserHasPermission(player.UserIDString, Use) != true) return;
station.SendControlBookmarks(player);
ShowStation(player, station);
}
//Remover tool calls kill & gives the item back which never triggers the dismount hook which results in the UI over lay bug. Added OnEntityKill hook to fix the problem
// This patch will also fix it if any owner level player ent kills the stations while a player is mounted.
private void OnEntityKill(ComputerStation station)
{
if (station._mounted == null)
return;
station.DismountPlayer(station._mounted);
}
private void OnEntityDismounted(ComputerStation station, BasePlayer player)
{
if (station == null) return;
CuiHelper.DestroyUi(player, "cctvstation");
CuiHelper.DestroyUi(player, "ButtonForwardstation");
CuiHelper.DestroyUi(player, "ButtonBackstation");
}
private void OnCargoShipCCTVSpawned(CargoShip boat, List<CCTV_RC> cams)
{
_cargoshipcctv[boat] = cams;
foreach (var station in BaseNetworkable.serverEntities.OfType<ComputerStation>())
{
if (station.isStatic || station.OwnerID != 0UL && !permission.UserHasPermission(station.OwnerID.ToString(), Use)) continue;
foreach (var cam in cams)
{
if (!station.controlBookmarks.Contains(cam.rcIdentifier))
station.controlBookmarks.Add(cam.rcIdentifier);
}
station.SendNetworkUpdateImmediate();
}
}
private void OnEntityKill(CargoShip boat)
{
if (boat == null || !_cargoshipcctv.ContainsKey(boat)) return;
List<CCTV_RC> cams = _cargoshipcctv[boat];
foreach (var station in BaseNetworkable.serverEntities.OfType<ComputerStation>())
{
foreach (var cam in cams)
{
station.controlBookmarks.Remove(cam.rcIdentifier);
}
station.SendNetworkUpdate();
}
_cargoshipcctv.Remove(boat);
}
private void OnBookmarkControlStarted(ComputerStation computerStation, BasePlayer player, string bookmarkName, IRemoteControllable remoteControllable)
{
if (!permission.UserHasPermission(player.UserIDString, Use)) return;
ShowStation(player, computerStation, _stationPage[player.userID]);
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (_stationPage.ContainsKey(player.userID))
_stationPage.Remove(player.userID);
}
#endregion
#region CUI Editor
private CuiElementContainer CreateOverlay()
{
return new CuiElementContainer
{
{
new CuiPanel
{
Image = { Color = "0 0 0 0.98" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "", OffsetMax = "" },
CursorEnabled = true
},
"Overlay", CCTV_OVERLAY
},
{
new CuiLabel //Welcome Msg
{
Text =
{
Text = _config.Text.EditorMsg,
FontSize = 30,
Color = _config.Colors.ButtonGreyText.Rgb,
Align = TextAnchor.MiddleCenter
},
RectTransform =
{
AnchorMin = "0.302 0.65",
AnchorMax = "0.702 0.75"
}
},
CCTV_OVERLAY
},
{
new CuiButton //close button Label
{
Button =
{
Command = $"cctveditstations.close",
Close = CCTV_OVERLAY,
Color = _config.Colors.ButtonGrey.Rgb
},
RectTransform = {AnchorMin = "0.444 0.11", AnchorMax = "0.54 0.16"},
Text =
{
Text = _config.Text.CloseButtonlabel,
FontSize = 20,
Color = _config.Colors.ButtonGreyText.Rgb,
Align = TextAnchor.MiddleCenter
}
},
CCTV_OVERLAY, "close"
}
};
}
private readonly CuiLabel _editorDescription = new CuiLabel
{
Text =
{
Text = "{editorDescription}",
FontSize = 15,
Align = TextAnchor.MiddleCenter
},
RectTransform =
{
AnchorMin = "0.17 0.63",
AnchorMax = "0.81 0.68"
}
};
private void CreateTab(ref CuiElementContainer container, string cam, int editorpageminus, int rowPos)
{
int numberPerRow = 5;
float padding = 0.01f;
float margin = (0.005f);
float width = ((1f - (padding * (numberPerRow + 1))) / numberPerRow);
float height = (width * 0.82f);
int row = (int) Math.Floor((float) rowPos / numberPerRow);
int col = (rowPos - (row * numberPerRow));
container.Add(new CuiButton
{
Button =
{
Command = $"autocctvstations.toggle {cam} {editorpageminus}",
Color = _config.Colors.ButtonBackGroundColor.Rgb //"0.5 0.5 0.5 0.5"
},
RectTransform = // 0.050 <width padding> 0.056
{
AnchorMin = $"{margin + (width * col) + (padding * col)} {(1f - padding) - ((row + 1) * height) - (padding * row)}",
AnchorMax = $"{margin + (width * (col + 1)) + (0.006f + padding * col)} {(1f - padding) - (row * height) - (padding * row)}"
},
Text =
{
Text = $"{cam} \n <color={_config.Colors.ToggleColor}>{_config.Cameras[cam]}</color>",
Align = TextAnchor.MiddleCenter,
Color = _config.Colors.TextColor.Rgb,
//Font = "robotocondensed-regular.ttf",
FontSize = 12
}
}, CCTV_CONTENT_NAME);
}
private void CreateEditorChangePage(ref CuiElementContainer container, int from)
{
container.Add(new CuiButton
{
Button =
{
Command = $"autocctvstations.page {(from - 1)}",
Color = _config.Colors.ButtonGrey.Rgb //"0 0 0 0.40"
},
RectTransform =
{
AnchorMin = "0.344 0.11",
AnchorMax = "0.44 0.16"
},
Text =
{
Text = _config.Text.BackButtonText,
Color = _config.Colors.ButtonGreenText.Rgb,
FontSize = 30,
Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf"
}
},
CCTV_OVERLAY,
"ButtonBack");
container.Add(new CuiButton
{
Button =
{
Command = $"autocctvstations.page {(from + 1)}",
Color = _config.Colors.ButtonGrey.Rgb //"0 0 0 0.40"
},
RectTransform =
{
AnchorMin = "0.544 0.11",
AnchorMax = "0.64 0.16"
},
Text =
{
Text = _config.Text.ForwardButtonText,
Color = _config.Colors.ButtonGreenText.Rgb,
FontSize = 30,
Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf"
}
},
CCTV_OVERLAY,
"ButtonForward");
}
private void DestroyUi(BasePlayer player, bool full = false)
{
CuiHelper.DestroyUi(player, CCTV_CONTENT_NAME);
CuiHelper.DestroyUi(player, CCTV_Desc_Overlay);
CuiHelper.DestroyUi(player, "ButtonForward");
CuiHelper.DestroyUi(player, "ButtonBack");
if (!full) return;
CuiHelper.DestroyUi(player, CCTV_OVERLAY);
}
private void ShowEditor(BasePlayer player, int from = 0, bool fullPaint = false, bool refreshcounter = true)
{
CuiElementContainer container;
_editorDescription.Text.Color = _config.Colors.TextColor.Rgb;
int count = 0;
foreach (var cam in _config.Cameras)
{
if (_config.Cameras[cam.Key])
count++;
}
_editorDescription.Text.Text = $"Enabled Count {count}";
if (fullPaint)
{
CuiHelper.DestroyUi(player, CCTV_OVERLAY);
container = CreateOverlay();
if (!refreshcounter)
container.Add(_editorDescription, CCTV_OVERLAY, CCTV_Desc_Overlay);
}
else
{
container = new CuiElementContainer();
}
if (refreshcounter)
{
CuiHelper.DestroyUi(player, CCTV_Desc_Overlay);
container.Add(_editorDescription, CCTV_OVERLAY, CCTV_Desc_Overlay);
}
CuiHelper.DestroyUi(player, CCTV_CONTENT_NAME);
CuiHelper.DestroyUi(player, "ButtonForward");
CuiHelper.DestroyUi(player, "ButtonBack");
container.Add(new CuiPanel
{
Image = {Color = "0 0 0 0.98"},
RectTransform =
{
AnchorMin = "0.223 0.2",
AnchorMax = "0.763 0.63"
}
}, CCTV_OVERLAY, CCTV_CONTENT_NAME);
int total = _config.Cameras.Count / 30;
from = Mathf.Clamp(from, 0, total);
int rowPos = 0;
foreach (var data in _config.Cameras.Skip(from * 30).Take(30))
{
CreateTab(ref container, data.Key, from, rowPos);
rowPos++;
}
if (total > 0)
CreateEditorChangePage(ref container, from);
CuiHelper.AddUi(player, container);
}
private void CmdCctvEditor(IPlayer player, string command, string[] args)
{
if (player == null || !player.HasPermission(Admin)) return;
ShowEditor(player.Object as BasePlayer, 0, true);
}
#endregion
#region Console Commands
[Command("autocctvstations.page")]
private void CmdEditorPageShow(IPlayer player, string command, string[] args)
{
if (player == null || !player.HasPermission(Admin)) return;
if (args.Length == 0) return;
BasePlayer p = player.Object as BasePlayer;
ShowEditor(p, args[0].ToInt());
}
[Command("autocctvstations.toggle")]
private void CmdToggleButtons(IPlayer player, string command, string[] args)
{
if (player == null || !player.HasPermission(Admin)) return;
if (args.Length < 2) return;
BasePlayer p = player.Object as BasePlayer;
if (_config.Cameras.ContainsKey(args[0]))
_config.Cameras[args[0]] = !_config.Cameras[args[0]];
ShowEditor(p, args[1].ToInt());
}
[Command("cctveditstations.close")]
private void ConsoleEditClose(IPlayer player, string command, string[] args)
{
if (args.Length == 0)
CheckConfig();
SaveConfig();
}
[Command("autocctvstations.station.page")]
private void ConsoleStation(IPlayer player, string command, string[] args)
{
if (args.Length == 0) return;
int page = args[0].ToInt();
BasePlayer p = player.Object as BasePlayer;
ComputerStation station = p.GetMounted() as ComputerStation;
if (station == null) return;
ShowStation(p, station, page);
}
#endregion
#region CUI Station
private CuiLabel _stationDescription = new CuiLabel
{
Text =
{
Text = "{editorDescription}",
FontSize = 15,
Align = TextAnchor.MiddleCenter
},
RectTransform =
{
AnchorMin = "0.1 0.22",
AnchorMax = "0.15 0.25",
/*AnchorMin = "0 0",
AnchorMax = "0 0",
OffsetMin = "140 170",
OffsetMax = "190 190",*/
}
};
private void CreateStationChangePage(ref CuiElementContainer container, int from)
{
container.Add(new CuiButton
{
Button =
{
Command = $"autocctvstations.station.page {(from - 1)}",
Color = _config.Colors.ButtonGrey.Rgb //"0 0 0 0.40"
},
RectTransform =
{
AnchorMin = "0.046 0.1",
AnchorMax = "0.065 0.2",
/*AnchorMin = "0 0",
AnchorMax = "0 0",*/
/*OffsetMin = "55 70",
OffsetMax = "85 150",*/
},
Text =
{
Text = _config.Text.BackButtonText,
Color = _config.Colors.ButtonGreenText.Rgb,
FontSize = 25,
Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf"
}
},
"Overlay",
"ButtonBackstation");
container.Add(new CuiButton
{
Button =
{
Command = $"autocctvstations.station.page {(from + 1)}",
Color = _config.Colors.ButtonGrey.Rgb //"0 0 0 0.40"
},
RectTransform =
{
AnchorMin = "0.194 0.1",
AnchorMax = "0.215 0.2"
/*AnchorMin = "0 0",
AnchorMax = "0 0",
OffsetMin = "250 70",
OffsetMax = "280 150",*/
},
Text =
{
Text = _config.Text.ForwardButtonText,
Color = _config.Colors.ButtonGreenText.Rgb,
FontSize = 25,
Align = TextAnchor.MiddleCenter,
Font = "robotocondensed-regular.ttf"
}
},
"Overlay",
"ButtonForwardstation");
}
public string GenerateControlBookmarkString(ComputerStation station, int page)
{
string controlBookmarkString = "";
foreach (string controlBookmark in station.controlBookmarks.Skip(page * 14).Take(14).ToArray())
{
controlBookmarkString += controlBookmark;
controlBookmarkString += ";";
}
return controlBookmarkString;
}
private void ShowStation(BasePlayer player, ComputerStation station, int from = 0)
{
_stationPage[player.userID] = from;
CuiElementContainer container = new CuiElementContainer();
int total = station.controlBookmarks.Count / 14;
from = Mathf.Clamp(from, 0, total);
//Interface.Call("OnBookmarksSendControl", station, player, string.Empty);
station.ClientRPC(RpcTarget.Player("ReceiveBookmarks", player), GenerateControlBookmarkString(station, from));
if (total > 0)
CreateStationChangePage(ref container, from);
_stationDescription.Text.Color = _config.Colors.TextColor.Rgb;
_stationDescription.Text.Text = $"Page {from + 1}";
container.Add(_stationDescription, "Overlay", "cctvstation");
CuiHelper.DestroyUi(player, "cctvstation");
CuiHelper.DestroyUi(player, "ButtonForwardstation");
CuiHelper.DestroyUi(player, "ButtonBackstation");
CuiHelper.AddUi(player, container);
}
#endregion
#region UI Colors
public class Color
{
public string Hex;
public float A;
[JsonIgnore]
public int R;
[JsonIgnore]
public int G;
[JsonIgnore]
public int B;
[JsonIgnore]
private string _rgb;
[JsonIgnore]
public string Rgb
{
get
{
if (_rgb != null) return _rgb;
if (Hex.StartsWith("#"))
Hex = Hex.Substring(1);
if (Hex.Length == 6)
{
R = int.Parse(Hex.Substring(0, 2), NumberStyles.AllowHexSpecifier);
G = int.Parse(Hex.Substring(2, 2), NumberStyles.AllowHexSpecifier);
B = int.Parse(Hex.Substring(4, 2), NumberStyles.AllowHexSpecifier);
}
_rgb = $"{(double)R / 255} {(double)G / 255} {(double)B / 255} {A}";
return _rgb;
}
}
public Color(string hex, float alpha = 1f)
{
if (hex.StartsWith("#")) hex = hex.Substring(1);
A = alpha;
Hex = "#" + hex;
}
}
#endregion
}
}